Преглед изворни кода

Merge remote-tracking branch 'upstream/dev' into refactor-shells

LukeParkerDev пре 19 часа
родитељ
комит
9dde86acbe
100 измењених фајлова са 567 додато и 266 уклоњено
  1. 2 2
      .github/workflows/review.yml
  2. 19 10
      .opencode/skills/effect/SKILL.md
  3. 63 55
      bun.lock
  4. 4 4
      nix/hashes.json
  5. 3 2
      package.json
  6. 2 2
      packages/app/package.json
  7. 1 1
      packages/app/src/components/dialog-edit-project.tsx
  8. 1 1
      packages/app/src/components/dialog-fork.tsx
  9. 1 1
      packages/app/src/components/dialog-select-directory.tsx
  10. 2 2
      packages/app/src/components/dialog-select-file.tsx
  11. 1 1
      packages/app/src/components/prompt-input/build-request-parts.ts
  12. 1 1
      packages/app/src/components/prompt-input/context-items.tsx
  13. 1 1
      packages/app/src/components/prompt-input/slash-popover.tsx
  14. 1 1
      packages/app/src/components/prompt-input/submit.test.ts
  15. 2 2
      packages/app/src/components/prompt-input/submit.ts
  16. 2 2
      packages/app/src/components/session/session-context-tab.tsx
  17. 1 1
      packages/app/src/components/session/session-header.tsx
  18. 1 1
      packages/app/src/components/session/session-new-view.tsx
  19. 1 1
      packages/app/src/components/session/session-sortable-tab.tsx
  20. 1 1
      packages/app/src/context/file.tsx
  21. 1 1
      packages/app/src/context/global-sync.tsx
  22. 2 2
      packages/app/src/context/global-sync/bootstrap.ts
  23. 1 1
      packages/app/src/context/global-sync/event-reducer.ts
  24. 1 1
      packages/app/src/context/local.tsx
  25. 2 2
      packages/app/src/context/notification.tsx
  26. 1 1
      packages/app/src/context/permission-auto-respond.test.ts
  27. 1 1
      packages/app/src/context/permission-auto-respond.ts
  28. 1 1
      packages/app/src/context/prompt.tsx
  29. 2 2
      packages/app/src/context/sync.tsx
  30. 1 1
      packages/app/src/pages/directory-layout.tsx
  31. 1 1
      packages/app/src/pages/home.tsx
  32. 4 4
      packages/app/src/pages/layout.tsx
  33. 1 1
      packages/app/src/pages/layout/helpers.ts
  34. 1 1
      packages/app/src/pages/layout/sidebar-items.tsx
  35. 1 1
      packages/app/src/pages/layout/sidebar-project.tsx
  36. 2 2
      packages/app/src/pages/layout/sidebar-workspace.tsx
  37. 1 1
      packages/app/src/pages/session.tsx
  38. 1 1
      packages/app/src/pages/session/file-tabs.tsx
  39. 2 2
      packages/app/src/pages/session/message-timeline.tsx
  40. 1 1
      packages/app/src/pages/session/use-session-commands.tsx
  41. 1 1
      packages/app/src/utils/base64.ts
  42. 1 1
      packages/app/src/utils/persist.ts
  43. 1 1
      packages/console/app/package.json
  44. 1 1
      packages/console/core/package.json
  45. 1 1
      packages/console/function/package.json
  46. 1 1
      packages/console/mail/package.json
  47. 16 6
      packages/core/package.json
  48. 1 10
      packages/core/src/cross-spawn-spawner.ts
  49. 1 1
      packages/core/src/effect/logger.ts
  50. 0 0
      packages/core/src/effect/memo-map.ts
  51. 4 4
      packages/core/src/effect/observability.ts
  52. 4 2
      packages/core/src/effect/runtime.ts
  53. 0 0
      packages/core/src/filesystem.ts
  54. 0 0
      packages/core/src/flag/flag.ts
  55. 65 0
      packages/core/src/global.ts
  56. 0 0
      packages/core/src/installation/version.ts
  57. 9 7
      packages/core/src/npm.ts
  58. 0 0
      packages/core/src/util/array.ts
  59. 0 0
      packages/core/src/util/binary.ts
  60. 0 0
      packages/core/src/util/effect-flock.ts
  61. 0 0
      packages/core/src/util/encode.ts
  62. 0 0
      packages/core/src/util/error.ts
  63. 0 0
      packages/core/src/util/flock.ts
  64. 0 0
      packages/core/src/util/fn.ts
  65. 0 0
      packages/core/src/util/glob.ts
  66. 0 0
      packages/core/src/util/hash.ts
  67. 0 0
      packages/core/src/util/identifier.ts
  68. 0 0
      packages/core/src/util/iife.ts
  69. 0 0
      packages/core/src/util/lazy.ts
  70. 2 2
      packages/core/src/util/log.ts
  71. 0 0
      packages/core/src/util/module.ts
  72. 0 0
      packages/core/src/util/opencode-process.ts
  73. 0 0
      packages/core/src/util/path.ts
  74. 0 0
      packages/core/src/util/retry.ts
  75. 0 0
      packages/core/src/util/slug.ts
  76. 0 0
      packages/core/sst-env.d.ts
  77. 14 4
      packages/core/test/effect/cross-spawn-spawner.test.ts
  78. 1 1
      packages/core/test/effect/observability.test.ts
  79. 1 1
      packages/core/test/filesystem/filesystem.test.ts
  80. 3 3
      packages/core/test/fixture/effect-flock-worker.ts
  81. 1 1
      packages/core/test/fixture/flock-worker.ts
  82. 0 0
      packages/core/test/lib/effect.ts
  83. 4 4
      packages/core/test/util/effect-flock.test.ts
  84. 2 2
      packages/core/test/util/flock.test.ts
  85. 0 0
      packages/core/tsconfig.json
  86. 1 1
      packages/desktop-electron/package.json
  87. 1 1
      packages/desktop/package.json
  88. 2 2
      packages/enterprise/package.json
  89. 2 2
      packages/enterprise/src/core/share.ts
  90. 1 1
      packages/enterprise/src/core/storage.ts
  91. 3 3
      packages/enterprise/src/routes/share/[shareID].tsx
  92. 1 1
      packages/enterprise/test/core/share.test.ts
  93. 6 6
      packages/extensions/zed/extension.toml
  94. 1 1
      packages/function/package.json
  95. 5 8
      packages/opencode/package.json
  96. 237 30
      packages/opencode/specs/effect/http-api.md
  97. 2 2
      packages/opencode/src/acp/agent.ts
  98. 30 31
      packages/opencode/src/agent/agent.ts
  99. 2 2
      packages/opencode/src/auth/index.ts
  100. 1 1
      packages/opencode/src/cli/cmd/agent.ts

+ 2 - 2
.github/workflows/review.yml

@@ -45,13 +45,13 @@ jobs:
 
       - name: Check PR guidelines compliance
         env:
-          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           OPENCODE_PERMISSION: '{ "bash": {  "*": "deny", "gh*": "allow", "gh pr review*": "deny" } }'
           PR_TITLE: ${{ steps.pr-details.outputs.title }}
         run: |
           PR_BODY=$(jq -r .body pr_data.json)
-          opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${PR_TITLE}'
+          opencode run -m opencode/gpt-5.5 --variant medium "A new pull request has been created: '${PR_TITLE}'
 
           <pr-number>
           ${{ steps.pr-number.outputs.number }}

+ 19 - 10
.opencode/skills/effect/SKILL.md

@@ -1,21 +1,30 @@
 ---
 name: effect
-description: Answer questions about the Effect framework
+description: Work with Effect v4 / effect-smol TypeScript code in this repo
 ---
 
 # Effect
 
-This codebase uses Effect, a framework for writing typescript.
+This codebase uses Effect for typed, composable TypeScript services, schemas, and workflows.
 
-## How to Answer Effect Questions
+## Source Of Truth
 
-1. Clone the Effect repository: `https://github.com/Effect-TS/effect-smol` to
-   `.opencode/references/effect-smol` in this project NOT the skill folder.
-2. Use the explore agent to search the codebase for answers about Effect patterns, APIs, and concepts
-3. Provide responses based on the actual Effect source code and documentation
+Use the current Effect v4 / effect-smol source, not memory or older Effect v2/v3 examples.
+
+1. If `.opencode/references/effect-smol` is missing, clone `https://github.com/Effect-TS/effect-smol` there. Do this in the project, not in the skill folder.
+2. Search `.opencode/references/effect-smol` for exact APIs, examples, tests, and naming patterns before answering or implementing Effect-specific code.
+3. Also inspect existing repo code for local house style before introducing new patterns.
+4. Prefer answers and implementations backed by specific source files or nearby repo examples.
 
 ## Guidelines
 
-- Always use the explore agent with the cloned repository when answering Effect-related questions
-- Reference specific files and patterns found in the Effect codebase
-- Do not answer from memory - always verify against the source
+- Prefer current Effect v4 APIs and project-local patterns over old blog posts, examples, or package-memory guesses.
+- Use `Effect.gen(function* () { ... })` for multi-step workflows.
+- Use `Effect.fn("Name")` or `Effect.fnUntraced(...)` for named effects when adding reusable service methods or important workflows.
+- Prefer Effect `Schema` for API and domain data shapes. Use branded schemas for IDs and `Schema.TaggedErrorClass` for typed domain errors when modeling new error surfaces.
+- Keep HTTP handlers thin: decode input, read request context, call services, and map transport errors. Put business rules in services.
+- In Effect service code, prefer Effect-aware platform abstractions and dependencies over ad hoc promises where the surrounding code already does so.
+- Keep layer composition explicit. Avoid broad hidden provisioning that makes missing dependencies hard to see.
+- In tests, prefer the repo's existing Effect test helpers and live tests for filesystem, git, child process, locks, or timing behavior.
+- Do not introduce `any`, non-null assertions, unchecked casts, or older Effect APIs just to satisfy types.
+- Do not answer from memory. Verify against `.opencode/references/effect-smol` or nearby code first.

+ 63 - 55
bun.lock

@@ -29,11 +29,11 @@
     },
     "packages/app": {
       "name": "@opencode-ai/app",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "@kobalte/core": "catalog:",
+        "@opencode-ai/core": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
-        "@opencode-ai/shared": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
         "@shikijs/transformers": "3.9.2",
         "@solid-primitives/active-element": "2.1.3",
@@ -83,7 +83,7 @@
     },
     "packages/console/app": {
       "name": "@opencode-ai/console-app",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "@cloudflare/vite-plugin": "1.15.2",
         "@ibm/plex": "6.4.1",
@@ -117,7 +117,7 @@
     },
     "packages/console/core": {
       "name": "@opencode-ai/console-core",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "@aws-sdk/client-sts": "3.782.0",
         "@jsx-email/render": "1.1.1",
@@ -144,7 +144,7 @@
     },
     "packages/console/function": {
       "name": "@opencode-ai/console-function",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "@ai-sdk/anthropic": "3.0.64",
         "@ai-sdk/openai": "3.0.48",
@@ -168,7 +168,7 @@
     },
     "packages/console/mail": {
       "name": "@opencode-ai/console-mail",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "@jsx-email/all": "2.2.3",
         "@jsx-email/cli": "1.4.3",
@@ -190,9 +190,43 @@
         "cloudflare": "5.2.0",
       },
     },
+    "packages/core": {
+      "name": "@opencode-ai/core",
+      "version": "1.14.25",
+      "bin": {
+        "opencode": "./bin/opencode",
+      },
+      "dependencies": {
+        "@effect/opentelemetry": "catalog:",
+        "@effect/platform-node": "catalog:",
+        "@npmcli/arborist": "9.4.0",
+        "@npmcli/config": "10.8.1",
+        "@opentelemetry/api": "1.9.0",
+        "@opentelemetry/context-async-hooks": "2.6.1",
+        "@opentelemetry/exporter-trace-otlp-http": "0.214.0",
+        "@opentelemetry/sdk-trace-base": "2.6.1",
+        "cross-spawn": "catalog:",
+        "effect": "catalog:",
+        "glob": "13.0.5",
+        "mime-types": "3.0.2",
+        "minimatch": "10.2.5",
+        "npm-package-arg": "13.0.2",
+        "semver": "^7.6.3",
+        "xdg-basedir": "5.1.0",
+        "zod": "catalog:",
+      },
+      "devDependencies": {
+        "@tsconfig/bun": "catalog:",
+        "@types/bun": "catalog:",
+        "@types/cross-spawn": "catalog:",
+        "@types/npm-package-arg": "6.1.4",
+        "@types/npmcli__arborist": "6.3.3",
+        "@types/semver": "catalog:",
+      },
+    },
     "packages/desktop": {
       "name": "@opencode-ai/desktop",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "@opencode-ai/app": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
@@ -225,7 +259,7 @@
     },
     "packages/desktop-electron": {
       "name": "@opencode-ai/desktop-electron",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "drizzle-orm": "catalog:",
         "effect": "catalog:",
@@ -269,9 +303,9 @@
     },
     "packages/enterprise": {
       "name": "@opencode-ai/enterprise",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
-        "@opencode-ai/shared": "workspace:*",
+        "@opencode-ai/core": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
         "@pierre/diffs": "catalog:",
         "@solidjs/meta": "catalog:",
@@ -298,7 +332,7 @@
     },
     "packages/function": {
       "name": "@opencode-ai/function",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "@octokit/auth-app": "8.0.1",
         "@octokit/rest": "catalog:",
@@ -314,7 +348,7 @@
     },
     "packages/opencode": {
       "name": "opencode",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "bin": {
         "opencode": "./bin/opencode",
       },
@@ -353,8 +387,6 @@
         "@hono/zod-validator": "catalog:",
         "@lydell/node-pty": "catalog:",
         "@modelcontextprotocol/sdk": "1.27.1",
-        "@npmcli/arborist": "9.4.0",
-        "@npmcli/config": "10.8.1",
         "@octokit/graphql": "9.0.2",
         "@octokit/rest": "catalog:",
         "@openauthjs/openauth": "catalog:",
@@ -367,8 +399,8 @@
         "@opentelemetry/exporter-trace-otlp-http": "0.214.0",
         "@opentelemetry/sdk-trace-base": "2.6.1",
         "@opentelemetry/sdk-trace-node": "2.6.1",
-        "@opentui/core": "0.1.103",
-        "@opentui/solid": "0.1.103",
+        "@opentui/core": "catalog:",
+        "@opentui/solid": "catalog:",
         "@parcel/watcher": "2.5.1",
         "@pierre/diffs": "catalog:",
         "@solid-primitives/event-bus": "1.1.2",
@@ -403,7 +435,7 @@
         "open": "10.1.2",
         "opencode-gitlab-auth": "2.0.1",
         "opencode-poe-auth": "0.0.1",
-        "opentui-spinner": "0.0.6",
+        "opentui-spinner": "catalog:",
         "partial-json": "0.1.7",
         "remeda": "catalog:",
         "semver": "^7.6.3",
@@ -426,8 +458,8 @@
         "@babel/core": "7.28.4",
         "@effect/language-service": "0.84.2",
         "@octokit/webhooks-types": "7.6.1",
+        "@opencode-ai/core": "workspace:*",
         "@opencode-ai/script": "workspace:*",
-        "@opencode-ai/shared": "workspace:*",
         "@parcel/watcher-darwin-arm64": "2.5.1",
         "@parcel/watcher-darwin-x64": "2.5.1",
         "@parcel/watcher-linux-arm64-glibc": "2.5.1",
@@ -443,7 +475,6 @@
         "@types/cross-spawn": "catalog:",
         "@types/mime-types": "3.0.1",
         "@types/npm-package-arg": "6.1.4",
-        "@types/npmcli__arborist": "6.3.3",
         "@types/semver": "^7.5.8",
         "@types/turndown": "5.0.5",
         "@types/which": "3.0.4",
@@ -460,15 +491,15 @@
     },
     "packages/plugin": {
       "name": "@opencode-ai/plugin",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "effect": "catalog:",
         "zod": "catalog:",
       },
       "devDependencies": {
-        "@opentui/core": "0.1.103",
-        "@opentui/solid": "0.1.103",
+        "@opentui/core": "catalog:",
+        "@opentui/solid": "catalog:",
         "@tsconfig/node22": "catalog:",
         "@types/node": "catalog:",
         "@typescript/native-preview": "catalog:",
@@ -495,7 +526,7 @@
     },
     "packages/sdk/js": {
       "name": "@opencode-ai/sdk",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "cross-spawn": "catalog:",
       },
@@ -508,33 +539,9 @@
         "typescript": "catalog:",
       },
     },
-    "packages/shared": {
-      "name": "@opencode-ai/shared",
-      "version": "1.14.24",
-      "bin": {
-        "opencode": "./bin/opencode",
-      },
-      "dependencies": {
-        "@effect/platform-node": "catalog:",
-        "@npmcli/arborist": "catalog:",
-        "effect": "catalog:",
-        "glob": "13.0.5",
-        "mime-types": "3.0.2",
-        "minimatch": "10.2.5",
-        "semver": "catalog:",
-        "xdg-basedir": "5.1.0",
-        "zod": "catalog:",
-      },
-      "devDependencies": {
-        "@tsconfig/bun": "catalog:",
-        "@types/bun": "catalog:",
-        "@types/npmcli__arborist": "6.3.3",
-        "@types/semver": "catalog:",
-      },
-    },
     "packages/slack": {
       "name": "@opencode-ai/slack",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@slack/bolt": "^3.17.1",
@@ -569,11 +576,11 @@
     },
     "packages/ui": {
       "name": "@opencode-ai/ui",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "@kobalte/core": "catalog:",
+        "@opencode-ai/core": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
-        "@opencode-ai/shared": "workspace:*",
         "@pierre/diffs": "catalog:",
         "@shikijs/transformers": "3.9.2",
         "@solid-primitives/bounds": "0.1.3",
@@ -618,7 +625,7 @@
     },
     "packages/web": {
       "name": "@opencode-ai/web",
-      "version": "1.14.24",
+      "version": "1.14.25",
       "dependencies": {
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/markdown-remark": "6.3.1",
@@ -678,8 +685,8 @@
     "@npmcli/arborist": "9.4.0",
     "@octokit/rest": "22.0.0",
     "@openauthjs/openauth": "0.0.0-20250322224806",
-    "@opentui/core": "0.1.99",
-    "@opentui/solid": "0.1.99",
+    "@opentui/core": "0.1.103",
+    "@opentui/solid": "0.1.103",
     "@pierre/diffs": "1.1.0-beta.18",
     "@playwright/test": "1.59.1",
     "@solid-primitives/storage": "4.3.3",
@@ -708,6 +715,7 @@
     "luxon": "3.6.1",
     "marked": "17.0.1",
     "marked-shiki": "1.2.1",
+    "opentui-spinner": "0.0.6",
     "remeda": "2.26.0",
     "remend": "1.3.0",
     "semver": "7.7.4",
@@ -1553,6 +1561,8 @@
 
     "@opencode-ai/console-resource": ["@opencode-ai/console-resource@workspace:packages/console/resource"],
 
+    "@opencode-ai/core": ["@opencode-ai/core@workspace:packages/core"],
+
     "@opencode-ai/desktop": ["@opencode-ai/desktop@workspace:packages/desktop"],
 
     "@opencode-ai/desktop-electron": ["@opencode-ai/desktop-electron@workspace:packages/desktop-electron"],
@@ -1567,8 +1577,6 @@
 
     "@opencode-ai/sdk": ["@opencode-ai/sdk@workspace:packages/sdk/js"],
 
-    "@opencode-ai/shared": ["@opencode-ai/shared@workspace:packages/shared"],
-
     "@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"],
 
     "@opencode-ai/storybook": ["@opencode-ai/storybook@workspace:packages/storybook"],

+ 4 - 4
nix/hashes.json

@@ -1,8 +1,8 @@
 {
   "nodeModules": {
-    "x86_64-linux": "sha256-V1Rt2k7ujkqGw4pDkn++WALTy1fAugvoKLhKvwFKkss=",
-    "aarch64-linux": "sha256-ho0AuGbJ1qw9Hvb3EbGC8f0lWqqgUslvda/wTe32MFo=",
-    "aarch64-darwin": "sha256-hdUyNmp+snwtnBckHXsPMgNFUYS1sYDdngkk+AXVqzc=",
-    "x86_64-darwin": "sha256-P57LpQNF8fplFKQBBIukhOKbIugbViyBUIUjClXohuk="
+    "x86_64-linux": "sha256-LpzWEZzURUEj7fcHGvh33gM7D9GNPE+XIvU0/hmdcQM=",
+    "aarch64-linux": "sha256-0zdO3zuj6g9cMZFEOsvQJcKKcPjGVZJ2DkJdDcb2VCM=",
+    "aarch64-darwin": "sha256-dmT8R9Pmzh5tjO8NCCCtENiQpJQeifQpVdhaty1MXOs=",
+    "x86_64-darwin": "sha256-Q6rAQRoC6WaMAQl++YHAZmbNuO303cWgGaYzXaRlzy4="
   }
 }

+ 3 - 2
package.json

@@ -34,8 +34,8 @@
       "@types/cross-spawn": "6.0.6",
       "@octokit/rest": "22.0.0",
       "@hono/zod-validator": "0.4.2",
-      "@opentui/core": "0.1.99",
-      "@opentui/solid": "0.1.99",
+      "@opentui/core": "0.1.103",
+      "@opentui/solid": "0.1.103",
       "ulid": "3.0.1",
       "@kobalte/core": "0.13.11",
       "@types/luxon": "3.7.1",
@@ -46,6 +46,7 @@
       "@cloudflare/workers-types": "4.20251008.0",
       "@openauthjs/openauth": "0.0.0-20250322224806",
       "@pierre/diffs": "1.1.0-beta.18",
+      "opentui-spinner": "0.0.6",
       "@solid-primitives/storage": "4.3.3",
       "@tailwindcss/vite": "4.1.11",
       "diff": "8.0.2",

+ 2 - 2
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/app",
-  "version": "1.14.24",
+  "version": "1.14.25",
   "description": "",
   "type": "module",
   "exports": {
@@ -42,7 +42,7 @@
     "@kobalte/core": "catalog:",
     "@opencode-ai/sdk": "workspace:*",
     "@opencode-ai/ui": "workspace:*",
-    "@opencode-ai/shared": "workspace:*",
+    "@opencode-ai/core": "workspace:*",
     "@shikijs/transformers": "3.9.2",
     "@solid-primitives/active-element": "2.1.3",
     "@solid-primitives/audio": "1.4.2",

+ 1 - 1
packages/app/src/components/dialog-edit-project.tsx

@@ -9,7 +9,7 @@ import { createStore } from "solid-js/store"
 import { useGlobalSDK } from "@/context/global-sdk"
 import { useGlobalSync } from "@/context/global-sync"
 import { type LocalProject, getAvatarColors } from "@/context/layout"
-import { getFilename } from "@opencode-ai/shared/util/path"
+import { getFilename } from "@opencode-ai/core/util/path"
 import { Avatar } from "@opencode-ai/ui/avatar"
 import { useLanguage } from "@/context/language"
 import { getProjectAvatarSource } from "@/pages/layout/sidebar-items"

+ 1 - 1
packages/app/src/components/dialog-fork.tsx

@@ -9,7 +9,7 @@ import { List } from "@opencode-ai/ui/list"
 import { showToast } from "@opencode-ai/ui/toast"
 import { extractPromptFromParts } from "@/utils/prompt"
 import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client"
-import { base64Encode } from "@opencode-ai/shared/util/encode"
+import { base64Encode } from "@opencode-ai/core/util/encode"
 import { useLanguage } from "@/context/language"
 
 interface ForkableMessage {

+ 1 - 1
packages/app/src/components/dialog-select-directory.tsx

@@ -3,7 +3,7 @@ import { Dialog } from "@opencode-ai/ui/dialog"
 import { FileIcon } from "@opencode-ai/ui/file-icon"
 import { List } from "@opencode-ai/ui/list"
 import type { ListRef } from "@opencode-ai/ui/list"
-import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
+import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
 import fuzzysort from "fuzzysort"
 import { createMemo, createResource, createSignal } from "solid-js"
 import { useGlobalSDK } from "@/context/global-sdk"

+ 2 - 2
packages/app/src/components/dialog-select-file.tsx

@@ -4,8 +4,8 @@ import { FileIcon } from "@opencode-ai/ui/file-icon"
 import { Icon } from "@opencode-ai/ui/icon"
 import { Keybind } from "@opencode-ai/ui/keybind"
 import { List } from "@opencode-ai/ui/list"
-import { base64Encode } from "@opencode-ai/shared/util/encode"
-import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
+import { base64Encode } from "@opencode-ai/core/util/encode"
+import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
 import { useNavigate } from "@solidjs/router"
 import { createMemo, createSignal, Match, onCleanup, Show, Switch } from "solid-js"
 import { formatKeybind, useCommand, type CommandOption } from "@/context/command"

+ 1 - 1
packages/app/src/components/prompt-input/build-request-parts.ts

@@ -1,4 +1,4 @@
-import { getFilename } from "@opencode-ai/shared/util/path"
+import { getFilename } from "@opencode-ai/core/util/path"
 import { type AgentPartInput, type FilePartInput, type Part, type TextPartInput } from "@opencode-ai/sdk/v2/client"
 import type { FileSelection } from "@/context/file"
 import { encodeFilePath } from "@/context/file/path"

+ 1 - 1
packages/app/src/components/prompt-input/context-items.tsx

@@ -2,7 +2,7 @@ import { Component, For, Show } from "solid-js"
 import { FileIcon } from "@opencode-ai/ui/file-icon"
 import { IconButton } from "@opencode-ai/ui/icon-button"
 import { Tooltip } from "@opencode-ai/ui/tooltip"
-import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/shared/util/path"
+import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/core/util/path"
 import type { ContextItem } from "@/context/prompt"
 
 type PromptContextItem = ContextItem & { key: string }

+ 1 - 1
packages/app/src/components/prompt-input/slash-popover.tsx

@@ -1,7 +1,7 @@
 import { Component, For, Match, Show, Switch } from "solid-js"
 import { FileIcon } from "@opencode-ai/ui/file-icon"
 import { Icon } from "@opencode-ai/ui/icon"
-import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
+import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
 
 export type AtOption =
   | { type: "agent"; name: string; display: string }

+ 1 - 1
packages/app/src/components/prompt-input/submit.test.ts

@@ -74,7 +74,7 @@ beforeAll(async () => {
     showToast: () => 0,
   }))
 
-  mock.module("@opencode-ai/shared/util/encode", () => ({
+  mock.module("@opencode-ai/core/util/encode", () => ({
     base64Encode: (value: string) => value,
   }))
 

+ 2 - 2
packages/app/src/components/prompt-input/submit.ts

@@ -1,7 +1,7 @@
 import type { Message, Session } from "@opencode-ai/sdk/v2/client"
 import { showToast } from "@opencode-ai/ui/toast"
-import { base64Encode } from "@opencode-ai/shared/util/encode"
-import { Binary } from "@opencode-ai/shared/util/binary"
+import { base64Encode } from "@opencode-ai/core/util/encode"
+import { Binary } from "@opencode-ai/core/util/binary"
 import { useNavigate, useParams } from "@solidjs/router"
 import { batch, type Accessor } from "solid-js"
 import type { FileSelection } from "@/context/file"

+ 2 - 2
packages/app/src/components/session/session-context-tab.tsx

@@ -1,8 +1,8 @@
 import { createMemo, createEffect, on, onCleanup, For, Show } from "solid-js"
 import type { JSX } from "solid-js"
 import { useSync } from "@/context/sync"
-import { checksum } from "@opencode-ai/shared/util/encode"
-import { findLast } from "@opencode-ai/shared/util/array"
+import { checksum } from "@opencode-ai/core/util/encode"
+import { findLast } from "@opencode-ai/core/util/array"
 import { same } from "@/utils/same"
 import { Icon } from "@opencode-ai/ui/icon"
 import { Accordion } from "@opencode-ai/ui/accordion"

+ 1 - 1
packages/app/src/components/session/session-header.tsx

@@ -7,7 +7,7 @@ import { Keybind } from "@opencode-ai/ui/keybind"
 import { Spinner } from "@opencode-ai/ui/spinner"
 import { showToast } from "@opencode-ai/ui/toast"
 import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
-import { getFilename } from "@opencode-ai/shared/util/path"
+import { getFilename } from "@opencode-ai/core/util/path"
 import { createEffect, createMemo, createSignal, For, onMount, Show } from "solid-js"
 import { createStore } from "solid-js/store"
 import { Portal } from "solid-js/web"

+ 1 - 1
packages/app/src/components/session/session-new-view.tsx

@@ -5,7 +5,7 @@ import { useSDK } from "@/context/sdk"
 import { useLanguage } from "@/context/language"
 import { Icon } from "@opencode-ai/ui/icon"
 import { Mark } from "@opencode-ai/ui/logo"
-import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
+import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
 
 const MAIN_WORKTREE = "main"
 const CREATE_WORKTREE = "create"

+ 1 - 1
packages/app/src/components/session/session-sortable-tab.tsx

@@ -5,7 +5,7 @@ import { FileIcon } from "@opencode-ai/ui/file-icon"
 import { IconButton } from "@opencode-ai/ui/icon-button"
 import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
 import { Tabs } from "@opencode-ai/ui/tabs"
-import { getFilename } from "@opencode-ai/shared/util/path"
+import { getFilename } from "@opencode-ai/core/util/path"
 import { useFile } from "@/context/file"
 import { useLanguage } from "@/context/language"
 import { useCommand } from "@/context/command"

+ 1 - 1
packages/app/src/context/file.tsx

@@ -3,7 +3,7 @@ import { createStore, produce, reconcile } from "solid-js/store"
 import { createSimpleContext } from "@opencode-ai/ui/context"
 import { showToast } from "@opencode-ai/ui/toast"
 import { useParams } from "@solidjs/router"
-import { getFilename } from "@opencode-ai/shared/util/path"
+import { getFilename } from "@opencode-ai/core/util/path"
 import { useSDK } from "./sdk"
 import { useSync } from "./sync"
 import { useLanguage } from "@/context/language"

+ 1 - 1
packages/app/src/context/global-sync.tsx

@@ -8,7 +8,7 @@ import type {
   Todo,
 } from "@opencode-ai/sdk/v2/client"
 import { showToast } from "@opencode-ai/ui/toast"
-import { getFilename } from "@opencode-ai/shared/util/path"
+import { getFilename } from "@opencode-ai/core/util/path"
 import { batch, createContext, getOwner, onCleanup, onMount, type ParentProps, untrack, useContext } from "solid-js"
 import { createStore, produce, reconcile } from "solid-js/store"
 import { useLanguage } from "@/context/language"

+ 2 - 2
packages/app/src/context/global-sync/bootstrap.ts

@@ -11,8 +11,8 @@ import type {
   Todo,
 } from "@opencode-ai/sdk/v2/client"
 import { showToast } from "@opencode-ai/ui/toast"
-import { getFilename } from "@opencode-ai/shared/util/path"
-import { retry } from "@opencode-ai/shared/util/retry"
+import { getFilename } from "@opencode-ai/core/util/path"
+import { retry } from "@opencode-ai/core/util/retry"
 import { batch } from "solid-js"
 import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
 import type { State, VcsCache } from "./types"

+ 1 - 1
packages/app/src/context/global-sync/event-reducer.ts

@@ -1,4 +1,4 @@
-import { Binary } from "@opencode-ai/shared/util/binary"
+import { Binary } from "@opencode-ai/core/util/binary"
 import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store"
 import type {
   Message,

+ 1 - 1
packages/app/src/context/local.tsx

@@ -1,5 +1,5 @@
 import { createSimpleContext } from "@opencode-ai/ui/context"
-import { base64Encode } from "@opencode-ai/shared/util/encode"
+import { base64Encode } from "@opencode-ai/core/util/encode"
 import { useParams } from "@solidjs/router"
 import { batch, createEffect, createMemo } from "solid-js"
 import { createStore } from "solid-js/store"

+ 2 - 2
packages/app/src/context/notification.tsx

@@ -7,8 +7,8 @@ import { useGlobalSync } from "./global-sync"
 import { usePlatform } from "@/context/platform"
 import { useLanguage } from "@/context/language"
 import { useSettings } from "@/context/settings"
-import { Binary } from "@opencode-ai/shared/util/binary"
-import { base64Encode } from "@opencode-ai/shared/util/encode"
+import { Binary } from "@opencode-ai/core/util/binary"
+import { base64Encode } from "@opencode-ai/core/util/encode"
 import { decode64 } from "@/utils/base64"
 import { EventSessionError } from "@opencode-ai/sdk/v2"
 import { Persist, persisted } from "@/utils/persist"

+ 1 - 1
packages/app/src/context/permission-auto-respond.test.ts

@@ -1,6 +1,6 @@
 import { describe, expect, test } from "bun:test"
 import type { PermissionRequest, Session } from "@opencode-ai/sdk/v2/client"
-import { base64Encode } from "@opencode-ai/shared/util/encode"
+import { base64Encode } from "@opencode-ai/core/util/encode"
 import { autoRespondsPermission, isDirectoryAutoAccepting } from "./permission-auto-respond"
 
 const session = (input: { id: string; parentID?: string }) =>

+ 1 - 1
packages/app/src/context/permission-auto-respond.ts

@@ -1,4 +1,4 @@
-import { base64Encode } from "@opencode-ai/shared/util/encode"
+import { base64Encode } from "@opencode-ai/core/util/encode"
 
 export function acceptKey(sessionID: string, directory?: string) {
   if (!directory) return sessionID

+ 1 - 1
packages/app/src/context/prompt.tsx

@@ -1,5 +1,5 @@
 import { createSimpleContext } from "@opencode-ai/ui/context"
-import { checksum } from "@opencode-ai/shared/util/encode"
+import { checksum } from "@opencode-ai/core/util/encode"
 import { useParams } from "@solidjs/router"
 import { batch, createMemo, createRoot, getOwner, onCleanup } from "solid-js"
 import { createStore, type SetStoreFunction } from "solid-js/store"

+ 2 - 2
packages/app/src/context/sync.tsx

@@ -1,7 +1,7 @@
 import { batch, createMemo } from "solid-js"
 import { createStore, produce, reconcile } from "solid-js/store"
-import { Binary } from "@opencode-ai/shared/util/binary"
-import { retry } from "@opencode-ai/shared/util/retry"
+import { Binary } from "@opencode-ai/core/util/binary"
+import { retry } from "@opencode-ai/core/util/retry"
 import { createSimpleContext } from "@opencode-ai/ui/context"
 import {
   clearSessionPrefetch,

+ 1 - 1
packages/app/src/pages/directory-layout.tsx

@@ -1,6 +1,6 @@
 import { DataProvider } from "@opencode-ai/ui/context"
 import { showToast } from "@opencode-ai/ui/toast"
-import { base64Encode } from "@opencode-ai/shared/util/encode"
+import { base64Encode } from "@opencode-ai/core/util/encode"
 import { useLocation, useNavigate, useParams } from "@solidjs/router"
 import { createEffect, createMemo, createResource, type ParentProps, Show } from "solid-js"
 import { useLanguage } from "@/context/language"

+ 1 - 1
packages/app/src/pages/home.tsx

@@ -3,7 +3,7 @@ import { Button } from "@opencode-ai/ui/button"
 import { Logo } from "@opencode-ai/ui/logo"
 import { useLayout } from "@/context/layout"
 import { useNavigate } from "@solidjs/router"
-import { base64Encode } from "@opencode-ai/shared/util/encode"
+import { base64Encode } from "@opencode-ai/core/util/encode"
 import { Icon } from "@opencode-ai/ui/icon"
 import { usePlatform } from "@/context/platform"
 import { DateTime } from "luxon"

+ 4 - 4
packages/app/src/pages/layout.tsx

@@ -17,7 +17,7 @@ import { useLocation, useNavigate, useParams } from "@solidjs/router"
 import { useLayout, LocalProject } from "@/context/layout"
 import { useGlobalSync } from "@/context/global-sync"
 import { Persist, persisted } from "@/utils/persist"
-import { base64Encode } from "@opencode-ai/shared/util/encode"
+import { base64Encode } from "@opencode-ai/core/util/encode"
 import { decode64 } from "@/utils/base64"
 import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
 import { Button } from "@opencode-ai/ui/button"
@@ -25,7 +25,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
 import { Tooltip } from "@opencode-ai/ui/tooltip"
 import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
 import { Dialog } from "@opencode-ai/ui/dialog"
-import { getFilename } from "@opencode-ai/shared/util/path"
+import { getFilename } from "@opencode-ai/core/util/path"
 import { Session, type Message } from "@opencode-ai/sdk/v2/client"
 import { usePlatform } from "@/context/platform"
 import { useSettings } from "@/context/settings"
@@ -48,8 +48,8 @@ import {
 } from "@/context/global-sync/session-prefetch"
 import { useNotification } from "@/context/notification"
 import { usePermission } from "@/context/permission"
-import { Binary } from "@opencode-ai/shared/util/binary"
-import { retry } from "@opencode-ai/shared/util/retry"
+import { Binary } from "@opencode-ai/core/util/binary"
+import { retry } from "@opencode-ai/core/util/retry"
 import { playSoundById } from "@/utils/sound"
 import { createAim } from "@/utils/aim"
 import { setNavigate } from "@/utils/notification-click"

+ 1 - 1
packages/app/src/pages/layout/helpers.ts

@@ -1,4 +1,4 @@
-import { getFilename } from "@opencode-ai/shared/util/path"
+import { getFilename } from "@opencode-ai/core/util/path"
 import { type Session } from "@opencode-ai/sdk/v2/client"
 
 type SessionStore = {

+ 1 - 1
packages/app/src/pages/layout/sidebar-items.tsx

@@ -4,7 +4,7 @@ import { Icon } from "@opencode-ai/ui/icon"
 import { IconButton } from "@opencode-ai/ui/icon-button"
 import { Spinner } from "@opencode-ai/ui/spinner"
 import { Tooltip } from "@opencode-ai/ui/tooltip"
-import { getFilename } from "@opencode-ai/shared/util/path"
+import { getFilename } from "@opencode-ai/core/util/path"
 import { A, useParams } from "@solidjs/router"
 import { type Accessor, createMemo, For, type JSX, Match, Show, Switch } from "solid-js"
 import { useGlobalSync } from "@/context/global-sync"

+ 1 - 1
packages/app/src/pages/layout/sidebar-project.tsx

@@ -1,6 +1,6 @@
 import { createMemo, For, Show, type Accessor, type JSX } from "solid-js"
 import { createStore } from "solid-js/store"
-import { base64Encode } from "@opencode-ai/shared/util/encode"
+import { base64Encode } from "@opencode-ai/core/util/encode"
 import { Button } from "@opencode-ai/ui/button"
 import { ContextMenu } from "@opencode-ai/ui/context-menu"
 import { HoverCard } from "@opencode-ai/ui/hover-card"

+ 2 - 2
packages/app/src/pages/layout/sidebar-workspace.tsx

@@ -3,8 +3,8 @@ import { createEffect, createMemo, For, Show, type Accessor, type JSX } from "so
 import { createStore } from "solid-js/store"
 import { createSortable } from "@thisbeyond/solid-dnd"
 import { createMediaQuery } from "@solid-primitives/media"
-import { base64Encode } from "@opencode-ai/shared/util/encode"
-import { getFilename } from "@opencode-ai/shared/util/path"
+import { base64Encode } from "@opencode-ai/core/util/encode"
+import { getFilename } from "@opencode-ai/core/util/path"
 import { Button } from "@opencode-ai/ui/button"
 import { Collapsible } from "@opencode-ai/ui/collapsible"
 import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"

+ 1 - 1
packages/app/src/pages/session.tsx

@@ -28,7 +28,7 @@ import { createAutoScroll } from "@opencode-ai/ui/hooks"
 import { previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge"
 import { Button } from "@opencode-ai/ui/button"
 import { showToast } from "@opencode-ai/ui/toast"
-import { checksum } from "@opencode-ai/shared/util/encode"
+import { checksum } from "@opencode-ai/core/util/encode"
 import { useSearchParams } from "@solidjs/router"
 import { NewSessionView, SessionHeader } from "@/components/session"
 import { useComments } from "@/context/comments"

+ 1 - 1
packages/app/src/pages/session/file-tabs.tsx

@@ -6,7 +6,7 @@ import type { FileSearchHandle } from "@opencode-ai/ui/file"
 import { useFileComponent } from "@opencode-ai/ui/context/file"
 import { cloneSelectedLineRange, previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge"
 import { createLineCommentController } from "@opencode-ai/ui/line-comment-annotations"
-import { sampledChecksum } from "@opencode-ai/shared/util/encode"
+import { sampledChecksum } from "@opencode-ai/core/util/encode"
 import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
 import { IconButton } from "@opencode-ai/ui/icon-button"
 import { Tabs } from "@opencode-ai/ui/tabs"

+ 2 - 2
packages/app/src/pages/session/message-timeline.tsx

@@ -15,8 +15,8 @@ import { ScrollView } from "@opencode-ai/ui/scroll-view"
 import { TextField } from "@opencode-ai/ui/text-field"
 import type { AssistantMessage, Message as MessageType, Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2"
 import { showToast } from "@opencode-ai/ui/toast"
-import { Binary } from "@opencode-ai/shared/util/binary"
-import { getFilename } from "@opencode-ai/shared/util/path"
+import { Binary } from "@opencode-ai/core/util/binary"
+import { getFilename } from "@opencode-ai/core/util/path"
 import { Popover as KobaltePopover } from "@kobalte/core/popover"
 import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture"
 import { SessionContextUsage } from "@/components/session-context-usage"

+ 1 - 1
packages/app/src/pages/session/use-session-commands.tsx

@@ -14,7 +14,7 @@ import { useSettings } from "@/context/settings"
 import { useSync } from "@/context/sync"
 import { useTerminal } from "@/context/terminal"
 import { showToast } from "@opencode-ai/ui/toast"
-import { findLast } from "@opencode-ai/shared/util/array"
+import { findLast } from "@opencode-ai/core/util/array"
 import { createSessionTabs } from "@/pages/session/helpers"
 import { extractPromptFromParts } from "@/utils/prompt"
 import { UserMessage } from "@opencode-ai/sdk/v2"

+ 1 - 1
packages/app/src/utils/base64.ts

@@ -1,4 +1,4 @@
-import { base64Decode } from "@opencode-ai/shared/util/encode"
+import { base64Decode } from "@opencode-ai/core/util/encode"
 
 export function decode64(value: string | undefined) {
   if (value === undefined) return

+ 1 - 1
packages/app/src/utils/persist.ts

@@ -1,6 +1,6 @@
 import { Platform, usePlatform } from "@/context/platform"
 import { makePersisted, type AsyncStorage, type SyncStorage } from "@solid-primitives/storage"
-import { checksum } from "@opencode-ai/shared/util/encode"
+import { checksum } from "@opencode-ai/core/util/encode"
 import { createResource, type Accessor } from "solid-js"
 import type { SetStoreFunction, Store } from "solid-js/store"
 

+ 1 - 1
packages/console/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-app",
-  "version": "1.14.24",
+  "version": "1.14.25",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/console/core/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/console-core",
-  "version": "1.14.24",
+  "version": "1.14.25",
   "private": true,
   "type": "module",
   "license": "MIT",

+ 1 - 1
packages/console/function/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-function",
-  "version": "1.14.24",
+  "version": "1.14.25",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "type": "module",

+ 1 - 1
packages/console/mail/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-mail",
-  "version": "1.14.24",
+  "version": "1.14.25",
   "dependencies": {
     "@jsx-email/all": "2.2.3",
     "@jsx-email/cli": "1.4.3",

+ 16 - 6
packages/shared/package.json → packages/core/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.14.24",
-  "name": "@opencode-ai/shared",
+  "version": "1.14.25",
+  "name": "@opencode-ai/core",
   "type": "module",
   "license": "MIT",
   "private": true,
@@ -18,18 +18,28 @@
   "imports": {},
   "devDependencies": {
     "@tsconfig/bun": "catalog:",
-    "@types/semver": "catalog:",
     "@types/bun": "catalog:",
-    "@types/npmcli__arborist": "6.3.3"
+    "@types/cross-spawn": "catalog:",
+    "@types/npm-package-arg": "6.1.4",
+    "@types/npmcli__arborist": "6.3.3",
+    "@types/semver": "catalog:"
   },
   "dependencies": {
+    "@effect/opentelemetry": "catalog:",
     "@effect/platform-node": "catalog:",
-    "@npmcli/arborist": "catalog:",
+    "@npmcli/arborist": "9.4.0",
+    "@npmcli/config": "10.8.1",
+    "@opentelemetry/api": "1.9.0",
+    "@opentelemetry/context-async-hooks": "2.6.1",
+    "@opentelemetry/exporter-trace-otlp-http": "0.214.0",
+    "@opentelemetry/sdk-trace-base": "2.6.1",
     "effect": "catalog:",
+    "cross-spawn": "catalog:",
     "glob": "13.0.5",
     "mime-types": "3.0.2",
     "minimatch": "10.2.5",
-    "semver": "catalog:",
+    "npm-package-arg": "13.0.2",
+    "semver": "^7.6.3",
     "xdg-basedir": "5.1.0",
     "zod": "catalog:"
   },

+ 1 - 10
packages/opencode/src/effect/cross-spawn-spawner.ts → packages/core/src/cross-spawn-spawner.ts

@@ -502,13 +502,4 @@ export const layer: Layer.Layer<ChildProcessSpawner, never, FileSystem.FileSyste
 
 export const defaultLayer = layer.pipe(Layer.provide(NodeFileSystem.layer), Layer.provide(NodePath.layer))
 
-import { lazy } from "@/util/lazy"
-
-const rt = lazy(async () => {
-  // Dynamic import to avoid circular dep: cross-spawn-spawner → run-service → Instance → project → cross-spawn-spawner
-  const { makeRuntime } = await import("@/effect/run-service")
-  return makeRuntime(ChildProcessSpawner, defaultLayer)
-})
-
-type RT = Awaited<ReturnType<typeof rt>>
-export const runPromiseExit: RT["runPromiseExit"] = async (...args) => (await rt()).runPromiseExit(...(args as [any]))
+export * as CrossSpawnSpawner from "./cross-spawn-spawner"

+ 1 - 1
packages/opencode/src/effect/logger.ts → packages/core/src/effect/logger.ts

@@ -1,5 +1,5 @@
 import { Cause, Effect, Logger, References } from "effect"
-import { Log } from "@/util"
+import * as Log from "../util/log"
 
 type Fields = Record<string, unknown>
 

+ 0 - 0
packages/opencode/src/effect/memo-map.ts → packages/core/src/effect/memo-map.ts


+ 4 - 4
packages/opencode/src/effect/observability.ts → packages/core/src/effect/observability.ts

@@ -2,9 +2,9 @@ import { Effect, Layer, Logger } from "effect"
 import { FetchHttpClient } from "effect/unstable/http"
 import { OtlpLogger, OtlpSerialization } from "effect/unstable/observability"
 import * as EffectLogger from "./logger"
-import { Flag } from "@/flag/flag"
-import { InstallationChannel, InstallationVersion } from "@/installation/version"
-import { ensureProcessMetadata } from "@/util/opencode-process"
+import { Flag } from "../flag/flag"
+import { InstallationChannel, InstallationVersion } from "../installation/version"
+import { ensureProcessMetadata } from "../util/opencode-process"
 
 const base = Flag.OTEL_EXPORTER_OTLP_ENDPOINT
 export const enabled = !!base
@@ -76,7 +76,7 @@ const traces = async () => {
   // register(), so the global @opentelemetry/api context manager stays
   // as the no-op default. Non-Effect code (like the AI SDK) that calls
   // tracer.startActiveSpan() relies on context.active() to find the
-  // parent span  without a real context manager every span starts a
+  // parent span - without a real context manager every span starts a
   // new trace. Registering AsyncLocalStorageContextManager fixes this.
   const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks")
   const { context } = await import("@opentelemetry/api")

+ 4 - 2
packages/opencode/src/effect/runtime.ts → packages/core/src/effect/runtime.ts

@@ -1,11 +1,13 @@
-import { Observability } from "./observability"
 import { Layer, type Context, ManagedRuntime, type Effect } from "effect"
 import { memoMap } from "./memo-map"
+import { Observability } from "./observability"
 
 export function makeRuntime<I, S, E>(service: Context.Service<I, S>, layer: Layer.Layer<I, E>) {
   let rt: ManagedRuntime.ManagedRuntime<I, E> | undefined
   const getRuntime = () =>
-    (rt ??= ManagedRuntime.make(Layer.provideMerge(layer, Observability.layer) as Layer.Layer<I, E>, { memoMap }))
+    (rt ??= ManagedRuntime.make(Layer.provideMerge(layer, Observability.layer) as Layer.Layer<I, E>, {
+      memoMap,
+    }))
 
   return {
     runSync: <A, Err>(fn: (svc: S) => Effect.Effect<A, Err, I>) => getRuntime().runSync(service.use(fn)),

+ 0 - 0
packages/shared/src/filesystem.ts → packages/core/src/filesystem.ts


+ 0 - 0
packages/opencode/src/flag/flag.ts → packages/core/src/flag/flag.ts


+ 65 - 0
packages/core/src/global.ts

@@ -0,0 +1,65 @@
+import path from "path"
+import fs from "fs/promises"
+import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir"
+import os from "os"
+import { Context, Effect, Layer } from "effect"
+import { Flock } from "./util/flock"
+
+const app = "opencode"
+const data = path.join(xdgData!, app)
+const cache = path.join(xdgCache!, app)
+const config = path.join(xdgConfig!, app)
+const state = path.join(xdgState!, app)
+
+const paths = {
+  get home() {
+    return process.env.OPENCODE_TEST_HOME ?? os.homedir()
+  },
+  data,
+  bin: path.join(cache, "bin"),
+  log: path.join(data, "log"),
+  cache,
+  config,
+  state,
+}
+
+export const Path = paths
+
+Flock.setGlobal({ state })
+
+await Promise.all([
+  fs.mkdir(Path.data, { recursive: true }),
+  fs.mkdir(Path.config, { recursive: true }),
+  fs.mkdir(Path.state, { recursive: true }),
+  fs.mkdir(Path.log, { recursive: true }),
+  fs.mkdir(Path.bin, { recursive: true }),
+])
+
+export class Service extends Context.Service<Service, Interface>()("@opencode/Global") {}
+
+export interface Interface {
+  readonly home: string
+  readonly data: string
+  readonly cache: string
+  readonly config: string
+  readonly state: string
+  readonly bin: string
+  readonly log: string
+}
+
+export const layer = Layer.effect(
+  Service,
+  Effect.gen(function* () {
+    return Service.of({
+      home: Path.home,
+      data: Path.data,
+      cache: Path.cache,
+      config: Path.config,
+      state: Path.state,
+      bin: Path.bin,
+      log: Path.log,
+    })
+  }),
+)
+
+export * as Global from "./global"

+ 0 - 0
packages/opencode/src/installation/version.ts → packages/core/src/installation/version.ts


+ 9 - 7
packages/opencode/src/npm/index.ts → packages/core/src/npm.ts

@@ -1,20 +1,22 @@
-export * as Npm from "."
+export * as Npm from "./npm"
 
 import path from "path"
 import { fileURLToPath } from "url"
 import npa from "npm-package-arg"
 import semver from "semver"
+// @ts-expect-error npm does not publish types for this internal config API.
 import Config from "@npmcli/config"
+// @ts-expect-error npm does not publish types for this internal config API.
 import { definitions, flatten, nerfDarts, shorthands } from "@npmcli/config/lib/definitions/index.js"
 import { Effect, Schema, Context, Layer, Option, FileSystem, Stream } from "effect"
 import { NodeFileSystem } from "@effect/platform-node"
-import { AppFileSystem } from "@opencode-ai/shared/filesystem"
-import { Global } from "@opencode-ai/shared/global"
-import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
+import { AppFileSystem } from "./filesystem"
+import { Global } from "./global"
+import { EffectFlock } from "./util/effect-flock"
+import { makeRuntime } from "./effect/runtime"
 import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
 
-import * as CrossSpawnSpawner from "../effect/cross-spawn-spawner"
-import { makeRuntime } from "../effect/runtime"
+import { CrossSpawnSpawner } from "./cross-spawn-spawner"
 
 export class InstallFailedError extends Schema.TaggedErrorClass<InstallFailedError>()("NpmInstallFailedError", {
   add: Schema.Array(Schema.String).pipe(Schema.optional),
@@ -45,7 +47,7 @@ export interface Interface {
 export class Service extends Context.Service<Service, Interface>()("@opencode/Npm") {}
 
 const illegal = process.platform === "win32" ? new Set(["<", ">", ":", '"', "|", "?", "*"]) : undefined
-const npmPath = fileURLToPath(new URL("../..", import.meta.url))
+const npmPath = fileURLToPath(new URL("..", import.meta.url))
 
 export function sanitize(pkg: string) {
   if (!illegal) return pkg

+ 0 - 0
packages/shared/src/util/array.ts → packages/core/src/util/array.ts


+ 0 - 0
packages/shared/src/util/binary.ts → packages/core/src/util/binary.ts


+ 0 - 0
packages/shared/src/util/effect-flock.ts → packages/core/src/util/effect-flock.ts


+ 0 - 0
packages/shared/src/util/encode.ts → packages/core/src/util/encode.ts


+ 0 - 0
packages/shared/src/util/error.ts → packages/core/src/util/error.ts


+ 0 - 0
packages/shared/src/util/flock.ts → packages/core/src/util/flock.ts


+ 0 - 0
packages/shared/src/util/fn.ts → packages/core/src/util/fn.ts


+ 0 - 0
packages/shared/src/util/glob.ts → packages/core/src/util/glob.ts


+ 0 - 0
packages/shared/src/util/hash.ts → packages/core/src/util/hash.ts


+ 0 - 0
packages/shared/src/util/identifier.ts → packages/core/src/util/identifier.ts


+ 0 - 0
packages/shared/src/util/iife.ts → packages/core/src/util/iife.ts


+ 0 - 0
packages/shared/src/util/lazy.ts → packages/core/src/util/lazy.ts


+ 2 - 2
packages/opencode/src/util/log.ts → packages/core/src/util/log.ts

@@ -1,9 +1,9 @@
 import path from "path"
 import fs from "fs/promises"
 import { createWriteStream } from "fs"
-import { Global } from "../global"
+import * as Global from "../global"
 import z from "zod"
-import { Glob } from "@opencode-ai/shared/util/glob"
+import { Glob } from "./glob"
 
 export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" })
 export type Level = z.infer<typeof Level>

+ 0 - 0
packages/shared/src/util/module.ts → packages/core/src/util/module.ts


+ 0 - 0
packages/opencode/src/util/opencode-process.ts → packages/core/src/util/opencode-process.ts


+ 0 - 0
packages/shared/src/util/path.ts → packages/core/src/util/path.ts


+ 0 - 0
packages/shared/src/util/retry.ts → packages/core/src/util/retry.ts


+ 0 - 0
packages/shared/src/util/slug.ts → packages/core/src/util/slug.ts


+ 0 - 0
packages/shared/sst-env.d.ts → packages/core/sst-env.d.ts


+ 14 - 4
packages/opencode/test/effect/cross-spawn-spawner.test.ts → packages/core/test/effect/cross-spawn-spawner.test.ts

@@ -1,11 +1,11 @@
 import { describe, expect } from "bun:test"
 import fs from "node:fs/promises"
+import os from "node:os"
 import path from "node:path"
 import { Effect, Exit, Stream } from "effect"
 import type * as PlatformError from "effect/PlatformError"
 import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
-import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
-import { tmpdir } from "../fixture/fixture"
+import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { testEffect } from "../lib/effect"
 
 const live = CrossSpawnSpawner.defaultLayer
@@ -39,11 +39,21 @@ function alive(pid: number) {
   }
 }
 
+async function tmpdir() {
+  const dir = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-core-test-"))
+  return {
+    path: dir,
+    async [Symbol.asyncDispose]() {
+      await fs.rm(dir, { recursive: true, force: true })
+    },
+  }
+}
+
 async function gone(pid: number, timeout = 5_000) {
   const end = Date.now() + timeout
   while (Date.now() < end) {
     if (!alive(pid)) return true
-    await Bun.sleep(50)
+    await new Promise((resolve) => setTimeout(resolve, 50))
   }
   return !alive(pid)
 }
@@ -395,7 +405,7 @@ describe("cross-spawn spawner", () => {
         const file = path.join(dir, "echo cmd.cmd")
 
         yield* Effect.promise(() => fs.mkdir(dir, { recursive: true }))
-        yield* Effect.promise(() => Bun.write(file, "@echo off\r\nif %~1==--stdio exit /b 0\r\nexit /b 7\r\n"))
+        yield* Effect.promise(() => fs.writeFile(file, "@echo off\r\nif %~1==--stdio exit /b 0\r\nexit /b 7\r\n"))
 
         const code = yield* ChildProcessSpawner.ChildProcessSpawner.use((svc) =>
           svc.exitCode(

+ 1 - 1
packages/opencode/test/effect/observability.test.ts → packages/core/test/effect/observability.test.ts

@@ -1,5 +1,5 @@
 import { afterEach, describe, expect, test } from "bun:test"
-import { resource } from "../../src/effect/observability"
+import { resource } from "@opencode-ai/core/effect/observability"
 
 const otelResourceAttributes = process.env.OTEL_RESOURCE_ATTRIBUTES
 const opencodeClient = process.env.OPENCODE_CLIENT

+ 1 - 1
packages/shared/test/filesystem/filesystem.test.ts → packages/core/test/filesystem/filesystem.test.ts

@@ -1,7 +1,7 @@
 import { describe, test, expect } from "bun:test"
 import { Effect, Layer, FileSystem } from "effect"
 import { NodeFileSystem } from "@effect/platform-node"
-import { AppFileSystem } from "@opencode-ai/shared/filesystem"
+import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { testEffect } from "../lib/effect"
 import path from "path"
 

+ 3 - 3
packages/shared/test/fixture/effect-flock-worker.ts → packages/core/test/fixture/effect-flock-worker.ts

@@ -1,9 +1,9 @@
 import fs from "fs/promises"
 import os from "os"
 import { Effect, Layer } from "effect"
-import { AppFileSystem } from "@opencode-ai/shared/filesystem"
-import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
-import { Global } from "@opencode-ai/shared/global"
+import { AppFileSystem } from "@opencode-ai/core/filesystem"
+import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
+import { Global } from "@opencode-ai/core/global"
 
 type Msg = {
   key: string

+ 1 - 1
packages/shared/test/fixture/flock-worker.ts → packages/core/test/fixture/flock-worker.ts

@@ -1,5 +1,5 @@
 import fs from "fs/promises"
-import { Flock } from "@opencode-ai/shared/util/flock"
+import { Flock } from "@opencode-ai/core/util/flock"
 
 type Msg = {
   key: string

+ 0 - 0
packages/shared/test/lib/effect.ts → packages/core/test/lib/effect.ts


+ 4 - 4
packages/shared/test/util/effect-flock.test.ts → packages/core/test/util/effect-flock.test.ts

@@ -5,10 +5,10 @@ import path from "path"
 import os from "os"
 import { Cause, Effect, Exit, Layer } from "effect"
 import { testEffect } from "../lib/effect"
-import { AppFileSystem } from "@opencode-ai/shared/filesystem"
-import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
-import { Global } from "@opencode-ai/shared/global"
-import { Hash } from "@opencode-ai/shared/util/hash"
+import { AppFileSystem } from "@opencode-ai/core/filesystem"
+import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
+import { Global } from "@opencode-ai/core/global"
+import { Hash } from "@opencode-ai/core/util/hash"
 
 function lock(dir: string, key: string) {
   return path.join(dir, Hash.fast(key) + ".lock")

+ 2 - 2
packages/shared/test/util/flock.test.ts → packages/core/test/util/flock.test.ts

@@ -3,8 +3,8 @@ import fs from "fs/promises"
 import { spawn } from "child_process"
 import path from "path"
 import os from "os"
-import { Flock } from "@opencode-ai/shared/util/flock"
-import { Hash } from "@opencode-ai/shared/util/hash"
+import { Flock } from "@opencode-ai/core/util/flock"
+import { Hash } from "@opencode-ai/core/util/hash"
 
 type Msg = {
   key: string

+ 0 - 0
packages/shared/tsconfig.json → packages/core/tsconfig.json


+ 1 - 1
packages/desktop-electron/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@opencode-ai/desktop-electron",
   "private": true,
-  "version": "1.14.24",
+  "version": "1.14.25",
   "type": "module",
   "license": "MIT",
   "homepage": "https://opencode.ai",

+ 1 - 1
packages/desktop/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@opencode-ai/desktop",
   "private": true,
-  "version": "1.14.24",
+  "version": "1.14.25",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 2 - 2
packages/enterprise/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/enterprise",
-  "version": "1.14.24",
+  "version": "1.14.25",
   "private": true,
   "type": "module",
   "license": "MIT",
@@ -13,7 +13,7 @@
     "shell-prod": "sst shell --target Teams --stage production"
   },
   "dependencies": {
-    "@opencode-ai/shared": "workspace:*",
+    "@opencode-ai/core": "workspace:*",
     "@opencode-ai/ui": "workspace:*",
     "aws4fetch": "^1.0.20",
     "@pierre/diffs": "catalog:",

+ 2 - 2
packages/enterprise/src/core/share.ts

@@ -1,6 +1,6 @@
 import { Message, Model, Part, Session, SnapshotFileDiff } from "@opencode-ai/sdk/v2"
-import { fn } from "@opencode-ai/shared/util/fn"
-import { iife } from "@opencode-ai/shared/util/iife"
+import { fn } from "@opencode-ai/core/util/fn"
+import { iife } from "@opencode-ai/core/util/iife"
 import z from "zod"
 import { Storage } from "./storage"
 

+ 1 - 1
packages/enterprise/src/core/storage.ts

@@ -1,5 +1,5 @@
 import { AwsClient } from "aws4fetch"
-import { lazy } from "@opencode-ai/shared/util/lazy"
+import { lazy } from "@opencode-ai/core/util/lazy"
 
 export namespace Storage {
   export interface Adapter {

+ 3 - 3
packages/enterprise/src/routes/share/[shareID].tsx

@@ -10,9 +10,9 @@ import { Share } from "~/core/share"
 import { Logo, Mark } from "@opencode-ai/ui/logo"
 import { IconButton } from "@opencode-ai/ui/icon-button"
 import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
-import { iife } from "@opencode-ai/shared/util/iife"
-import { Binary } from "@opencode-ai/shared/util/binary"
-import { NamedError } from "@opencode-ai/shared/util/error"
+import { iife } from "@opencode-ai/core/util/iife"
+import { Binary } from "@opencode-ai/core/util/binary"
+import { NamedError } from "@opencode-ai/core/util/error"
 import { DateTime } from "luxon"
 import { createStore } from "solid-js/store"
 import z from "zod"

+ 1 - 1
packages/enterprise/test/core/share.test.ts

@@ -1,7 +1,7 @@
 import { describe, expect, test } from "bun:test"
 import { Share } from "../../src/core/share"
 import { Storage } from "../../src/core/storage"
-import { Identifier } from "@opencode-ai/shared/util/identifier"
+import { Identifier } from "@opencode-ai/core/util/identifier"
 
 describe.concurrent("core.share", () => {
   test("should create a share", async () => {

+ 6 - 6
packages/extensions/zed/extension.toml

@@ -1,7 +1,7 @@
 id = "opencode"
 name = "OpenCode"
 description = "The open source coding agent."
-version = "1.14.24"
+version = "1.14.25"
 schema_version = 1
 authors = ["Anomaly"]
 repository = "https://github.com/anomalyco/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
 icon = "./icons/opencode.svg"
 
 [agent_servers.opencode.targets.darwin-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-darwin-arm64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-darwin-arm64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-darwin-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-darwin-x64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-linux-arm64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-linux-arm64.tar.gz"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.linux-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-linux-x64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-linux-x64.tar.gz"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.windows-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-windows-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-windows-x64.zip"
 cmd = "./opencode.exe"
 args = ["acp"]

+ 1 - 1
packages/function/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/function",
-  "version": "1.14.24",
+  "version": "1.14.25",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "type": "module",

+ 5 - 8
packages/opencode/package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.14.24",
+  "version": "1.14.25",
   "name": "opencode",
   "type": "module",
   "license": "MIT",
@@ -45,7 +45,7 @@
     "@effect/language-service": "0.84.2",
     "@octokit/webhooks-types": "7.6.1",
     "@opencode-ai/script": "workspace:*",
-    "@opencode-ai/shared": "workspace:*",
+    "@opencode-ai/core": "workspace:*",
     "@parcel/watcher-darwin-arm64": "2.5.1",
     "@parcel/watcher-darwin-x64": "2.5.1",
     "@parcel/watcher-linux-arm64-glibc": "2.5.1",
@@ -61,7 +61,6 @@
     "@types/cross-spawn": "catalog:",
     "@types/mime-types": "3.0.1",
     "@types/npm-package-arg": "6.1.4",
-    "@types/npmcli__arborist": "6.3.3",
     "@types/semver": "^7.5.8",
     "@types/turndown": "5.0.5",
     "@types/which": "3.0.4",
@@ -110,8 +109,6 @@
     "@hono/zod-validator": "catalog:",
     "@lydell/node-pty": "catalog:",
     "@modelcontextprotocol/sdk": "1.27.1",
-    "@npmcli/arborist": "9.4.0",
-    "@npmcli/config": "10.8.1",
     "@octokit/graphql": "9.0.2",
     "@octokit/rest": "catalog:",
     "@openauthjs/openauth": "catalog:",
@@ -124,8 +121,8 @@
     "@opentelemetry/exporter-trace-otlp-http": "0.214.0",
     "@opentelemetry/sdk-trace-base": "2.6.1",
     "@opentelemetry/sdk-trace-node": "2.6.1",
-    "@opentui/core": "0.1.103",
-    "@opentui/solid": "0.1.103",
+    "@opentui/core": "catalog:",
+    "@opentui/solid": "catalog:",
     "@parcel/watcher": "2.5.1",
     "@pierre/diffs": "catalog:",
     "@solid-primitives/event-bus": "1.1.2",
@@ -160,7 +157,7 @@
     "open": "10.1.2",
     "opencode-gitlab-auth": "2.0.1",
     "opencode-poe-auth": "0.0.1",
-    "opentui-spinner": "0.0.6",
+    "opentui-spinner": "catalog:",
     "partial-json": "0.1.7",
     "remeda": "catalog:",
     "semver": "^7.6.3",

+ 237 - 30
packages/opencode/specs/effect/http-api.md

@@ -30,6 +30,46 @@ Plan for replacing instance Hono route implementations with Effect `HttpApi` whi
 - Regenerate the SDK after schema or OpenAPI-affecting changes and verify the diff is expected.
 - Do not delete a Hono route until the SDK/OpenAPI pipeline no longer depends on its Hono `describeRoute` entry.
 
+## Route Slice Checklist
+
+Use this checklist for each small HttpApi migration PR:
+
+1. Read the legacy Hono route and copy behavior exactly, including default values, headers, operation IDs, response schemas, and status codes.
+2. Put the new `HttpApiGroup`, route paths, DTO schemas, and handlers in `src/server/routes/instance/httpapi/*`.
+3. Mount the new paths in `src/server/routes/instance/index.ts` only inside the `OPENCODE_EXPERIMENTAL_HTTPAPI` block.
+4. Use `InstanceState.context` / `InstanceState.directory` inside HttpApi handlers instead of `Instance.directory`, `Instance.worktree`, or `Instance.project` ALS globals.
+5. Reuse existing services directly. If a service returns plain objects, use `Schema.Struct`; use `Schema.Class` only when handlers return actual class instances.
+6. Keep legacy Hono routes and `.zod` compatibility in place for SDK/OpenAPI generation.
+7. Add tests that hit the Hono-mounted bridge via `InstanceRoutes`, not only the raw `HttpApi` web handler, when the route depends on auth or instance context.
+8. Run `bun typecheck` from `packages/opencode`, relevant `bun run test:ci ...` tests from `packages/opencode`, and `./packages/sdk/js/script/build.ts` from the repo root.
+
+## Hono Deletion Checklist
+
+Use this checklist before deleting any Hono route implementation. A route being `bridged` is not enough.
+
+1. `HttpApi` parity is complete for the route path, method, auth behavior, query parameters, request body, response status, response headers, and error status.
+2. The route is mounted by default, not only behind `OPENCODE_EXPERIMENTAL_HTTPAPI`.
+3. If a fallback flag exists, tests cover both the default `HttpApi` path and the fallback Hono path until the fallback is removed.
+4. OpenAPI generation uses the Effect `HttpApi` route as the source for that path.
+5. Generated SDK output is unchanged from the Hono-generated contract, or the SDK diff is intentionally reviewed and accepted.
+6. The legacy Hono `describeRoute`, validator, and handler for that path are removed.
+7. Any duplicate Zod-only DTOs are deleted or kept only as `.zod` compatibility on the canonical Effect Schema.
+8. Bridge tests exist for auth, instance selection, success response, and route-specific side effects.
+9. Mutation routes prove persisted side effects and cleanup behavior in tests. If the mutation disposes/reloads the active instance, disposal happens through an explicit post-response lifecycle hook rather than inline handler teardown.
+10. Streaming, SSE, websocket, and UI bridge routes have a specific non-Hono replacement plan. Do not force them through `HttpApi` if raw Effect HTTP is a better fit.
+
+Hono can be removed from the instance server only after all mounted Hono route groups meet this checklist and `server/routes/instance/index.ts` no longer depends on Hono routing for default behavior.
+
+## Experimental Read Slice Guidance
+
+For the experimental route group, port read-only JSON routes before mutations:
+
+- Good first batch: `GET /console`, `GET /console/orgs`, `GET /tool/ids`, `GET /resource`.
+- Consider `GET /worktree` only if the handler uses `InstanceState.context` instead of `Instance.project`.
+- Defer `POST /console/switch`, worktree create/remove/reset, and `GET /session` to separate PRs because they mutate state or have broader pagination/session behavior.
+- Preserve response headers such as pagination cursors if a route is ported.
+- If SDK generation changes, explain whether it is a semantic contract change or a generator-equivalent type normalization.
+
 ## Schema Notes
 
 - Use `Schema.Struct(...).annotate({ identifier })` for named OpenAPI refs when handlers return plain objects.
@@ -70,7 +110,7 @@ Good near-term candidates:
 - top-level reads: `GET /path`, `GET /vcs`, `GET /vcs/diff`, `GET /command`, `GET /agent`, `GET /skill`, `GET /lsp`, `GET /formatter`
 - simple mutations: `POST /instance/dispose`
 - experimental JSON reads: console, tool, worktree list, resource list
-- deferred JSON mutations: `PATCH /config`, project git init, workspace/worktree create/remove/reset, file search, MCP auth flows
+- deferred JSON mutations: workspace/worktree create/remove/reset, file search, MCP auth flows
 
 Keep large or stateful groups for later:
 
@@ -130,31 +170,198 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
 
 ## Current Route Status
 
-| Area                     | Status            | Notes                                                          |
-| ------------------------ | ----------------- | -------------------------------------------------------------- |
-| `question`               | `bridged`         | `GET /question`, reply, reject                                 |
-| `permission`             | `bridged`         | list and reply                                                 |
-| `provider`               | `bridged`         | list, auth, OAuth authorize/callback                           |
-| `config`                 | `bridged` partial | reads only; mutation remains Hono                              |
-| `project`                | `bridged` partial | reads only; git-init remains Hono                              |
-| `file`                   | `bridged` partial | list/content/status only                                       |
-| `mcp`                    | `bridged` partial | status only                                                    |
-| `workspace`              | `implemented`     | `HttpApi` group exists, but bridge mounting needs verification |
-| top-level instance reads | `next`            | path, vcs, command, agent, skill, lsp, formatter               |
-| experimental JSON routes | `next/later`      | console, tool, worktree, resource, global session list         |
-| `session`                | `later/special`   | large stateful surface plus streaming                          |
-| `sync`                   | `later`           | process/control side effects                                   |
-| `event`                  | `special`         | SSE                                                            |
-| `pty`                    | `special`         | websocket                                                      |
-| `tui`                    | `special`         | UI bridge                                                      |
-
-## Next PRs
-
-1. Add bridge-level auth and instance-context tests for the current `HttpApi` bridge.
-2. Produce a generated route inventory from Hono registrations and update `Current Route Status` with exact paths.
-3. Fix the `workspace` status: mount it if it should be reachable, or remove it from the composed `HttpApi` layer.
-4. Port the top-level JSON reads.
-5. Start the Effect OpenAPI/SDK generation path for already-bridged routes.
+| Area                      | Status            | Notes                                                                                              |
+| ------------------------- | ----------------- | -------------------------------------------------------------------------------------------------- |
+| `question`                | `bridged`         | `GET /question`, reply, reject                                                                     |
+| `permission`              | `bridged`         | list and reply                                                                                     |
+| `provider`                | `bridged`         | list, auth, OAuth authorize/callback                                                               |
+| `config`                  | `bridged`         | read, providers, update                                                                            |
+| `project`                 | `bridged`         | list, current, git init, update                                                                    |
+| `file`                    | `bridged` partial | find text/file/symbol, list/content/status                                                         |
+| `mcp`                     | `bridged`         | status, add, OAuth, connect/disconnect                                                             |
+| `workspace`               | `bridged` partial | adaptor/list/status; create/remove/session-restore remain                                          |
+| top-level instance routes | `bridged`         | path, vcs, command, agent, skill, lsp, formatter, dispose                                          |
+| experimental JSON routes  | `bridged` partial | console reads, tool ids, worktree list/mutations, resource list; global session list remains later |
+| `session`                 | `later/special`   | large stateful surface plus streaming                                                              |
+| `sync`                    | `later`           | process/control side effects                                                                       |
+| `event`                   | `special`         | SSE                                                                                                |
+| `pty`                     | `special`         | websocket                                                                                          |
+| `tui`                     | `special`         | UI bridge                                                                                          |
+
+## Full Route Checklist
+
+This checklist tracks bridge parity only. Checked routes are available through the experimental `HttpApi` bridge; Hono deletion is tracked separately by the deletion checklist above.
+
+### Top-Level Instance Routes
+
+- [x] `POST /instance/dispose` - dispose active instance after response.
+- [x] `GET /path` - current directory and worktree paths.
+- [x] `GET /vcs` - current VCS status.
+- [x] `GET /vcs/diff` - VCS diff summary.
+- [x] `GET /command` - command catalog.
+- [x] `GET /agent` - agent catalog.
+- [x] `GET /skill` - skill catalog.
+- [x] `GET /lsp` - LSP status.
+- [x] `GET /formatter` - formatter status.
+
+### Config Routes
+
+- [x] `GET /config` - read config.
+- [x] `PATCH /config` - update config and dispose active instance after response.
+- [x] `GET /config/providers` - config provider summary.
+
+### Project Routes
+
+- [x] `GET /project` - list projects.
+- [x] `GET /project/current` - current project.
+- [x] `POST /project/git/init` - initialize git and reload active instance after response.
+- [x] `PATCH /project/:projectID` - update project metadata.
+
+### Provider Routes
+
+- [x] `GET /provider` - list providers.
+- [x] `GET /provider/auth` - list provider auth methods.
+- [x] `POST /provider/:providerID/oauth/authorize` - start provider OAuth.
+- [x] `POST /provider/:providerID/oauth/callback` - finish provider OAuth.
+
+### Question Routes
+
+- [x] `GET /question` - list questions.
+- [x] `POST /question/:requestID/reply` - reply to question.
+- [x] `POST /question/:requestID/reject` - reject question.
+
+### Permission Routes
+
+- [x] `GET /permission` - list permission requests.
+- [x] `POST /permission/:requestID/reply` - reply to permission request.
+
+### File Routes
+
+- [x] `GET /find` - text search.
+- [x] `GET /find/file` - file search.
+- [x] `GET /find/symbol` - symbol search.
+- [x] `GET /file` - list directory entries.
+- [x] `GET /file/content` - read file content.
+- [x] `GET /file/status` - file status.
+
+### MCP Routes
+
+- [x] `GET /mcp` - MCP status.
+- [x] `POST /mcp` - add MCP server at runtime.
+- [x] `POST /mcp/:name/auth` - start MCP OAuth.
+- [x] `POST /mcp/:name/auth/callback` - finish MCP OAuth callback.
+- [x] `POST /mcp/:name/auth/authenticate` - run MCP OAuth authenticate flow.
+- [x] `DELETE /mcp/:name/auth` - remove MCP OAuth credentials.
+- [x] `POST /mcp/:name/connect` - connect MCP server.
+- [x] `POST /mcp/:name/disconnect` - disconnect MCP server.
+
+### Experimental Routes
+
+- [x] `GET /experimental/console` - active Console provider metadata.
+- [x] `GET /experimental/console/orgs` - switchable Console orgs.
+- [ ] `POST /experimental/console/switch` - switch active Console org.
+- [x] `GET /experimental/tool/ids` - tool IDs.
+- [ ] `GET /experimental/tool` - tools for provider/model.
+- [x] `GET /experimental/worktree` - list worktrees.
+- [x] `POST /experimental/worktree` - create worktree.
+- [x] `DELETE /experimental/worktree` - remove worktree.
+- [x] `POST /experimental/worktree/reset` - reset worktree.
+- [ ] `GET /experimental/session` - global session list.
+- [x] `GET /experimental/resource` - MCP resources.
+
+### Workspace Routes
+
+- [x] `GET /experimental/workspace/adaptor` - list workspace adaptors.
+- [ ] `POST /experimental/workspace` - create workspace.
+- [x] `GET /experimental/workspace` - list workspaces.
+- [x] `GET /experimental/workspace/status` - workspace status.
+- [ ] `DELETE /experimental/workspace/:id` - remove workspace.
+- [ ] `POST /experimental/workspace/:id/session-restore` - restore session into workspace.
+
+### Sync Routes
+
+- [ ] `POST /sync/start` - start workspace sync.
+- [ ] `POST /sync/replay` - replay sync events.
+- [ ] `POST /sync/history` - list sync event history.
+
+### Session Routes
+
+- [ ] `GET /session` - list sessions.
+- [ ] `GET /session/status` - session status map.
+- [ ] `GET /session/:sessionID` - get session.
+- [ ] `GET /session/:sessionID/children` - get child sessions.
+- [ ] `GET /session/:sessionID/todo` - get session todos.
+- [ ] `POST /session` - create session.
+- [ ] `DELETE /session/:sessionID` - delete session.
+- [ ] `PATCH /session/:sessionID` - update session metadata.
+- [ ] `POST /session/:sessionID/init` - run project init command.
+- [ ] `POST /session/:sessionID/fork` - fork session.
+- [ ] `POST /session/:sessionID/abort` - abort session.
+- [ ] `POST /session/:sessionID/share` - share session.
+- [ ] `GET /session/:sessionID/diff` - session diff.
+- [ ] `DELETE /session/:sessionID/share` - unshare session.
+- [ ] `POST /session/:sessionID/summarize` - summarize session.
+- [ ] `GET /session/:sessionID/message` - list session messages.
+- [ ] `GET /session/:sessionID/message/:messageID` - get message.
+- [ ] `DELETE /session/:sessionID/message/:messageID` - delete message.
+- [ ] `DELETE /session/:sessionID/message/:messageID/part/:partID` - delete part.
+- [ ] `PATCH /session/:sessionID/message/:messageID/part/:partID` - update part.
+- [ ] `POST /session/:sessionID/message` - prompt with streaming response.
+- [ ] `POST /session/:sessionID/prompt_async` - async prompt.
+- [ ] `POST /session/:sessionID/command` - run command.
+- [ ] `POST /session/:sessionID/shell` - run shell command.
+- [ ] `POST /session/:sessionID/revert` - revert message.
+- [ ] `POST /session/:sessionID/unrevert` - restore reverted messages.
+- [ ] `POST /session/:sessionID/permissions/:permissionID` - deprecated permission response route.
+
+### Event Routes
+
+- [ ] `GET /event` - SSE event stream; replace with raw Effect HTTP, not `HttpApi`.
+
+### PTY Routes
+
+- [ ] `GET /pty` - list PTY sessions.
+- [ ] `POST /pty` - create PTY session.
+- [ ] `GET /pty/:ptyID` - get PTY session.
+- [ ] `PUT /pty/:ptyID` - update PTY session.
+- [ ] `DELETE /pty/:ptyID` - remove PTY session.
+- [ ] `GET /pty/:ptyID/connect` - PTY websocket; replace with raw Effect HTTP/websocket support.
+
+### TUI Routes
+
+- [ ] `POST /tui/append-prompt` - append prompt.
+- [ ] `POST /tui/open-help` - open help.
+- [ ] `POST /tui/open-sessions` - open sessions.
+- [ ] `POST /tui/open-themes` - open themes.
+- [ ] `POST /tui/open-models` - open models.
+- [ ] `POST /tui/submit-prompt` - submit prompt.
+- [ ] `POST /tui/clear-prompt` - clear prompt.
+- [ ] `POST /tui/execute-command` - execute command.
+- [ ] `POST /tui/show-toast` - show toast.
+- [ ] `POST /tui/publish` - publish TUI event.
+- [ ] `POST /tui/select-session` - select session.
+- [ ] `GET /tui/control/next` - get next TUI request.
+- [ ] `POST /tui/control/response` - submit TUI control response.
+
+## Remaining PR Plan
+
+Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays reviewable.
+
+1. [x] Bridge `PATCH /project/:projectID`.
+2. [x] Bridge MCP add/connect/disconnect routes.
+3. [x] Bridge MCP OAuth routes: start, callback, authenticate, remove.
+4. [ ] Bridge experimental console switch and tool list routes.
+5. [ ] Bridge experimental global session list.
+6. [ ] Bridge workspace create/remove/session-restore routes.
+7. [ ] Bridge sync start/replay/history routes.
+8. [ ] Bridge session read routes: list, status, get, children, todo, diff, messages.
+9. [ ] Bridge session lifecycle mutation routes: create, delete, update, fork, abort.
+10. [ ] Bridge session share/summary/message/part mutation routes.
+11. [ ] Replace event SSE with non-Hono Effect HTTP.
+12. [ ] Replace pty websocket/control routes with non-Hono Effect HTTP.
+13. [ ] Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer.
+14. [ ] Switch OpenAPI/SDK generation to Effect routes and compare SDK output.
+15. [ ] Flip ported JSON routes default-on, keep a short fallback, then delete replaced Hono route files.
 
 ## Checklist
 
@@ -164,10 +371,10 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
 - [x] Provide auth, instance lookup, and observability in the Effect route layer.
 - [x] Attach auth middleware in route modules.
 - [x] Support `auth_token` as a query security scheme.
-- [ ] Add bridge-level auth and instance tests.
-- [ ] Complete exact Hono route inventory.
-- [ ] Resolve implemented-but-unmounted route groups.
-- [ ] Port remaining JSON routes.
+- [x] Add bridge-level auth and instance tests.
+- [x] Complete exact Hono route inventory.
+- [x] Resolve implemented-but-unmounted route groups.
+- [x] Port remaining top-level JSON reads.
 - [ ] Generate SDK/OpenAPI from Effect routes.
 - [ ] Flip ported JSON routes to default-on with fallback.
 - [ ] Delete replaced Hono route implementations.

+ 2 - 2
packages/opencode/src/acp/agent.ts

@@ -34,7 +34,7 @@ import {
 import { Log } from "../util"
 import { pathToFileURL } from "url"
 import { Filesystem } from "../util"
-import { Hash } from "@opencode-ai/shared/util/hash"
+import { Hash } from "@opencode-ai/core/util/hash"
 import { ACPSessionManager } from "./session"
 import type { ACPConfig } from "./types"
 import { Provider } from "../provider"
@@ -50,7 +50,7 @@ import { Result, Schema } from "effect"
 import { LoadAPIKeyError } from "ai"
 import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
 import { applyPatch } from "diff"
-import { InstallationVersion } from "@/installation/version"
+import { InstallationVersion } from "@opencode-ai/core/installation/version"
 import { ShellToolID } from "@/tool/shell/id"
 
 type ModeOption = { id: string; name: string; description?: string }

+ 30 - 31
packages/opencode/src/agent/agent.ts

@@ -3,7 +3,6 @@ import z from "zod"
 import { Provider } from "../provider"
 import { ModelID, ProviderID } from "../provider/schema"
 import { generateObject, streamObject, type ModelMessage } from "ai"
-import { Instance } from "../project/instance"
 import { Truncate } from "../tool"
 import { Auth } from "../auth"
 import { ProviderTransform } from "../provider"
@@ -15,41 +14,41 @@ import PROMPT_SUMMARY from "./prompt/summary.txt"
 import PROMPT_TITLE from "./prompt/title.txt"
 import { Permission } from "@/permission"
 import { mergeDeep, pipe, sortBy, values } from "remeda"
-import { Global } from "@/global"
+import { Global } from "@opencode-ai/core/global"
 import path from "path"
 import { Plugin } from "@/plugin"
 import { Skill } from "../skill"
-import { Effect, Context, Layer } from "effect"
+import { Effect, Context, Layer, Schema } from "effect"
 import { InstanceState } from "@/effect"
 import * as Option from "effect/Option"
 import * as OtelTracer from "@effect/opentelemetry/Tracer"
+import { zod } from "@/util/effect-zod"
+import { withStatics, type DeepMutable } from "@/util/schema"
 
-export const Info = z
-  .object({
-    name: z.string(),
-    description: z.string().optional(),
-    mode: z.enum(["subagent", "primary", "all"]),
-    native: z.boolean().optional(),
-    hidden: z.boolean().optional(),
-    topP: z.number().optional(),
-    temperature: z.number().optional(),
-    color: z.string().optional(),
-    permission: Permission.Ruleset.zod,
-    model: z
-      .object({
-        modelID: ModelID.zod,
-        providerID: ProviderID.zod,
-      })
-      .optional(),
-    variant: z.string().optional(),
-    prompt: z.string().optional(),
-    options: z.record(z.string(), z.any()),
-    steps: z.number().int().positive().optional(),
-  })
-  .meta({
-    ref: "Agent",
-  })
-export type Info = z.infer<typeof Info>
+export const Info = Schema.Struct({
+  name: Schema.String,
+  description: Schema.optional(Schema.String),
+  mode: Schema.Literals(["subagent", "primary", "all"]),
+  native: Schema.optional(Schema.Boolean),
+  hidden: Schema.optional(Schema.Boolean),
+  topP: Schema.optional(Schema.Number),
+  temperature: Schema.optional(Schema.Number),
+  color: Schema.optional(Schema.String),
+  permission: Permission.Ruleset,
+  model: Schema.optional(
+    Schema.Struct({
+      modelID: ModelID,
+      providerID: ProviderID,
+    }),
+  ),
+  variant: Schema.optional(Schema.String),
+  prompt: Schema.optional(Schema.String),
+  options: Schema.Record(Schema.String, Schema.Unknown),
+  steps: Schema.optional(Schema.Number),
+})
+  .annotate({ identifier: "Agent" })
+  .pipe(withStatics((s) => ({ zod: zod(s) })))
+export type Info = DeepMutable<Schema.Schema.Type<typeof Info>>
 
 export interface Interface {
   readonly get: (agent: string) => Effect.Effect<Info>
@@ -79,7 +78,7 @@ export const layer = Layer.effect(
     const provider = yield* Provider.Service
 
     const state = yield* InstanceState.make<State>(
-      Effect.fn("Agent.state")(function* (_ctx) {
+      Effect.fn("Agent.state")(function* (ctx) {
         const cfg = yield* config.get()
         const skillDirs = yield* skill.dirs()
         const whitelistedDirs = [Truncate.GLOB, ...skillDirs.map((dir) => path.join(dir, "*"))]
@@ -136,7 +135,7 @@ export const layer = Layer.effect(
                 edit: {
                   "*": "deny",
                   [path.join(".opencode", "plans", "*.md")]: "allow",
-                  [path.relative(Instance.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow",
+                  [path.relative(ctx.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow",
                 },
               }),
               user,

+ 2 - 2
packages/opencode/src/auth/index.ts

@@ -1,8 +1,8 @@
 import path from "path"
 import { Effect, Layer, Record, Result, Schema, Context } from "effect"
 import { zod } from "@/util/effect-zod"
-import { Global } from "../global"
-import { AppFileSystem } from "@opencode-ai/shared/filesystem"
+import { Global } from "@opencode-ai/core/global"
+import { AppFileSystem } from "@opencode-ai/core/filesystem"
 
 export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key"
 

+ 1 - 1
packages/opencode/src/cli/cmd/agent.ts

@@ -2,7 +2,7 @@ import { cmd } from "./cmd"
 import * as prompts from "@clack/prompts"
 import { AppRuntime } from "@/effect/app-runtime"
 import { UI } from "../ui"
-import { Global } from "../../global"
+import { Global } from "@opencode-ai/core/global"
 import { Agent } from "../../agent/agent"
 import { Provider } from "../../provider"
 import path from "path"

Неке датотеке нису приказане због велике количине промена