onlycode 1.18.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -146
- package/src/account/account.ts +0 -463
- package/src/account/repo.ts +0 -173
- package/src/account/schema.ts +0 -99
- package/src/account/url.ts +0 -8
- package/src/acp/agent.ts +0 -95
- package/src/acp/config-option.ts +0 -203
- package/src/acp/content.ts +0 -250
- package/src/acp/directory.ts +0 -210
- package/src/acp/error.ts +0 -90
- package/src/acp/event.ts +0 -342
- package/src/acp/permission.ts +0 -124
- package/src/acp/profile.ts +0 -42
- package/src/acp/service.ts +0 -1048
- package/src/acp/session.ts +0 -231
- package/src/acp/tool.ts +0 -367
- package/src/acp/usage.ts +0 -232
- package/src/agent/agent.ts +0 -459
- package/src/agent/generate.txt +0 -75
- package/src/agent/prompt/compaction.txt +0 -9
- package/src/agent/prompt/explore.txt +0 -18
- package/src/agent/prompt/summary.txt +0 -11
- package/src/agent/prompt/title.txt +0 -44
- package/src/agent/subagent-permissions.ts +0 -27
- package/src/audio.d.ts +0 -14
- package/src/auth/index.ts +0 -99
- package/src/background/job.ts +0 -39
- package/src/bus/global.ts +0 -22
- package/src/cli/bootstrap.ts +0 -11
- package/src/cli/cmd/account.ts +0 -264
- package/src/cli/cmd/acp.ts +0 -73
- package/src/cli/cmd/agent.ts +0 -259
- package/src/cli/cmd/attach.ts +0 -97
- package/src/cli/cmd/cmd.ts +0 -7
- package/src/cli/cmd/db.ts +0 -62
- package/src/cli/cmd/debug/agent.handler.ts +0 -193
- package/src/cli/cmd/debug/agent.ts +0 -27
- package/src/cli/cmd/debug/config.ts +0 -14
- package/src/cli/cmd/debug/file.ts +0 -73
- package/src/cli/cmd/debug/index.ts +0 -87
- package/src/cli/cmd/debug/lsp.ts +0 -50
- package/src/cli/cmd/debug/ripgrep.ts +0 -79
- package/src/cli/cmd/debug/scrap.ts +0 -15
- package/src/cli/cmd/debug/skill.ts +0 -15
- package/src/cli/cmd/debug/snapshot.ts +0 -50
- package/src/cli/cmd/debug/startup.ts +0 -11
- package/src/cli/cmd/debug/v2.ts +0 -44
- package/src/cli/cmd/export.ts +0 -292
- package/src/cli/cmd/generate.ts +0 -54
- package/src/cli/cmd/github.handler.ts +0 -1593
- package/src/cli/cmd/github.shared.ts +0 -30
- package/src/cli/cmd/github.ts +0 -42
- package/src/cli/cmd/import.ts +0 -224
- package/src/cli/cmd/mcp.ts +0 -849
- package/src/cli/cmd/models.ts +0 -66
- package/src/cli/cmd/plug.ts +0 -230
- package/src/cli/cmd/pr.ts +0 -115
- package/src/cli/cmd/prompt-display.ts +0 -1
- package/src/cli/cmd/providers.ts +0 -534
- package/src/cli/cmd/run/demo.ts +0 -1274
- package/src/cli/cmd/run/entry.body.ts +0 -205
- package/src/cli/cmd/run/footer.command.tsx +0 -1064
- package/src/cli/cmd/run/footer.menu.tsx +0 -351
- package/src/cli/cmd/run/footer.permission.tsx +0 -472
- package/src/cli/cmd/run/footer.prompt.tsx +0 -1306
- package/src/cli/cmd/run/footer.question.tsx +0 -573
- package/src/cli/cmd/run/footer.subagent.tsx +0 -173
- package/src/cli/cmd/run/footer.ts +0 -1129
- package/src/cli/cmd/run/footer.view.tsx +0 -943
- package/src/cli/cmd/run/footer.width.ts +0 -27
- package/src/cli/cmd/run/permission.shared.ts +0 -256
- package/src/cli/cmd/run/prompt.editor.ts +0 -157
- package/src/cli/cmd/run/prompt.shared.ts +0 -153
- package/src/cli/cmd/run/question.shared.ts +0 -340
- package/src/cli/cmd/run/runtime.boot.ts +0 -202
- package/src/cli/cmd/run/runtime.lifecycle.ts +0 -406
- package/src/cli/cmd/run/runtime.queue.ts +0 -349
- package/src/cli/cmd/run/runtime.shared.ts +0 -17
- package/src/cli/cmd/run/runtime.stdin.ts +0 -37
- package/src/cli/cmd/run/runtime.ts +0 -814
- package/src/cli/cmd/run/scrollback.shared.ts +0 -92
- package/src/cli/cmd/run/scrollback.surface.ts +0 -431
- package/src/cli/cmd/run/scrollback.writer.tsx +0 -352
- package/src/cli/cmd/run/session-data.ts +0 -1113
- package/src/cli/cmd/run/session-replay.ts +0 -374
- package/src/cli/cmd/run/session.shared.ts +0 -196
- package/src/cli/cmd/run/splash.ts +0 -275
- package/src/cli/cmd/run/stream.transport.ts +0 -1462
- package/src/cli/cmd/run/stream.ts +0 -175
- package/src/cli/cmd/run/subagent-data.ts +0 -876
- package/src/cli/cmd/run/theme.ts +0 -690
- package/src/cli/cmd/run/tool.ts +0 -1489
- package/src/cli/cmd/run/trace.ts +0 -94
- package/src/cli/cmd/run/turn-summary.ts +0 -47
- package/src/cli/cmd/run/types.ts +0 -350
- package/src/cli/cmd/run/variant.shared.ts +0 -215
- package/src/cli/cmd/run.ts +0 -894
- package/src/cli/cmd/serve.ts +0 -24
- package/src/cli/cmd/session.ts +0 -147
- package/src/cli/cmd/stats.ts +0 -393
- package/src/cli/cmd/tui.ts +0 -224
- package/src/cli/cmd/uninstall.ts +0 -353
- package/src/cli/cmd/upgrade.ts +0 -74
- package/src/cli/cmd/web.ts +0 -84
- package/src/cli/effect/prompt.ts +0 -37
- package/src/cli/effect-cmd.ts +0 -96
- package/src/cli/error.ts +0 -130
- package/src/cli/heap.ts +0 -45
- package/src/cli/logo-pixel.ts +0 -35
- package/src/cli/logo.ts +0 -1
- package/src/cli/network.ts +0 -64
- package/src/cli/tui/layer.ts +0 -7
- package/src/cli/tui/validate-session.ts +0 -29
- package/src/cli/tui/worker.ts +0 -71
- package/src/cli/ui.ts +0 -148
- package/src/cli/upgrade.ts +0 -53
- package/src/command/index.ts +0 -184
- package/src/command/template/initialize.txt +0 -66
- package/src/command/template/review.txt +0 -101
- package/src/config/agent.ts +0 -59
- package/src/config/command.ts +0 -39
- package/src/config/config.ts +0 -686
- package/src/config/entry-name.ts +0 -19
- package/src/config/managed.ts +0 -69
- package/src/config/markdown.ts +0 -36
- package/src/config/parse.ts +0 -79
- package/src/config/paths.ts +0 -45
- package/src/config/plugin.ts +0 -79
- package/src/config/tui-cwd.ts +0 -5
- package/src/config/tui-host-attention.ts +0 -21
- package/src/config/tui-migrate.ts +0 -132
- package/src/config/tui.ts +0 -274
- package/src/config/variable.ts +0 -91
- package/src/control-plane/adapters/index.ts +0 -41
- package/src/control-plane/adapters/worktree.ts +0 -96
- package/src/control-plane/dev/README.md +0 -19
- package/src/control-plane/dev/debug-workspace-plugin.ts +0 -73
- package/src/control-plane/types.ts +0 -59
- package/src/control-plane/util.ts +0 -39
- package/src/control-plane/workspace-adapter-runtime.ts +0 -51
- package/src/control-plane/workspace-context.ts +0 -26
- package/src/control-plane/workspace.ts +0 -989
- package/src/effect/app-runtime.ts +0 -132
- package/src/effect/bootstrap-runtime.ts +0 -23
- package/src/effect/bridge.ts +0 -84
- package/src/effect/config-service.ts +0 -67
- package/src/effect/instance-ref.ts +0 -11
- package/src/effect/instance-registry.ts +0 -12
- package/src/effect/instance-state.ts +0 -69
- package/src/effect/promise.ts +0 -17
- package/src/effect/run-service.ts +0 -47
- package/src/effect/runner.ts +0 -217
- package/src/effect/runtime-flags.ts +0 -79
- package/src/env/index.ts +0 -43
- package/src/event-v2-bridge.ts +0 -79
- package/src/format/formatter.ts +0 -404
- package/src/format/index.ts +0 -205
- package/src/git/index.ts +0 -350
- package/src/id/id.ts +0 -80
- package/src/ide/index.ts +0 -61
- package/src/image/image.ts +0 -174
- package/src/index.ts +0 -142
- package/src/installation/index.ts +0 -350
- package/src/lsp/client.ts +0 -650
- package/src/lsp/diagnostic.ts +0 -29
- package/src/lsp/language.ts +0 -121
- package/src/lsp/launch.ts +0 -21
- package/src/lsp/lsp.ts +0 -511
- package/src/lsp/server.ts +0 -1983
- package/src/markdown.d.ts +0 -4
- package/src/mcp/auth.ts +0 -174
- package/src/mcp/catalog.ts +0 -153
- package/src/mcp/index.ts +0 -953
- package/src/mcp/oauth-callback.ts +0 -233
- package/src/mcp/oauth-provider.ts +0 -206
- package/src/node.ts +0 -4
- package/src/patch/index.ts +0 -686
- package/src/permission/arity.ts +0 -163
- package/src/permission/evaluate.ts +0 -1
- package/src/permission/index.ts +0 -230
- package/src/plugin/azure.ts +0 -26
- package/src/plugin/cloudflare.ts +0 -76
- package/src/plugin/digitalocean.ts +0 -383
- package/src/plugin/github-copilot/copilot.ts +0 -414
- package/src/plugin/github-copilot/models.ts +0 -246
- package/src/plugin/index.ts +0 -316
- package/src/plugin/install.ts +0 -439
- package/src/plugin/loader.ts +0 -237
- package/src/plugin/meta.ts +0 -188
- package/src/plugin/openai/README.md +0 -31
- package/src/plugin/openai/codex.ts +0 -641
- package/src/plugin/openai/ws-pool.ts +0 -270
- package/src/plugin/openai/ws.ts +0 -381
- package/src/plugin/pty-environment.ts +0 -24
- package/src/plugin/shared.ts +0 -323
- package/src/plugin/snowflake-cortex.ts +0 -529
- package/src/plugin/tui/internal.ts +0 -10
- package/src/plugin/tui/runtime.ts +0 -1130
- package/src/plugin/xai.ts +0 -716
- package/src/project/bootstrap-service.ts +0 -9
- package/src/project/bootstrap.ts +0 -76
- package/src/project/instance-context.ts +0 -24
- package/src/project/instance-layer.ts +0 -11
- package/src/project/instance-runtime.ts +0 -16
- package/src/project/instance-store.ts +0 -209
- package/src/project/project.ts +0 -519
- package/src/project/vcs.ts +0 -431
- package/src/provider/auth.ts +0 -233
- package/src/provider/error.ts +0 -188
- package/src/provider/model-status.ts +0 -8
- package/src/provider/provider.ts +0 -1975
- package/src/provider/transform.ts +0 -1543
- package/src/question/index.ts +0 -229
- package/src/question/schema.ts +0 -10
- package/src/server/auth.ts +0 -48
- package/src/server/event.ts +0 -13
- package/src/server/global-lifecycle.ts +0 -28
- package/src/server/init-projectors.ts +0 -3
- package/src/server/mdns.ts +0 -47
- package/src/server/projectors.ts +0 -1
- package/src/server/proxy-util.ts +0 -48
- package/src/server/routes/instance/httpapi/AGENTS.md +0 -39
- package/src/server/routes/instance/httpapi/api.ts +0 -78
- package/src/server/routes/instance/httpapi/errors.ts +0 -193
- package/src/server/routes/instance/httpapi/groups/config.ts +0 -65
- package/src/server/routes/instance/httpapi/groups/control-plane.ts +0 -35
- package/src/server/routes/instance/httpapi/groups/control.ts +0 -76
- package/src/server/routes/instance/httpapi/groups/event.ts +0 -29
- package/src/server/routes/instance/httpapi/groups/experimental.ts +0 -275
- package/src/server/routes/instance/httpapi/groups/file.ts +0 -185
- package/src/server/routes/instance/httpapi/groups/global.ts +0 -138
- package/src/server/routes/instance/httpapi/groups/instance.ts +0 -206
- package/src/server/routes/instance/httpapi/groups/mcp.ts +0 -156
- package/src/server/routes/instance/httpapi/groups/metadata.ts +0 -18
- package/src/server/routes/instance/httpapi/groups/permission.ts +0 -61
- package/src/server/routes/instance/httpapi/groups/project-copy.ts +0 -32
- package/src/server/routes/instance/httpapi/groups/project.ts +0 -93
- package/src/server/routes/instance/httpapi/groups/provider.ts +0 -101
- package/src/server/routes/instance/httpapi/groups/pty.ts +0 -172
- package/src/server/routes/instance/httpapi/groups/query.ts +0 -12
- package/src/server/routes/instance/httpapi/groups/question.ts +0 -74
- package/src/server/routes/instance/httpapi/groups/session.ts +0 -462
- package/src/server/routes/instance/httpapi/groups/sync.ts +0 -113
- package/src/server/routes/instance/httpapi/groups/tui.ts +0 -208
- package/src/server/routes/instance/httpapi/groups/workspace.ts +0 -141
- package/src/server/routes/instance/httpapi/handlers/config.ts +0 -34
- package/src/server/routes/instance/httpapi/handlers/control-plane.ts +0 -37
- package/src/server/routes/instance/httpapi/handlers/control.ts +0 -43
- package/src/server/routes/instance/httpapi/handlers/event.ts +0 -99
- package/src/server/routes/instance/httpapi/handlers/experimental.ts +0 -192
- package/src/server/routes/instance/httpapi/handlers/file.ts +0 -139
- package/src/server/routes/instance/httpapi/handlers/global.ts +0 -156
- package/src/server/routes/instance/httpapi/handlers/instance.ts +0 -110
- package/src/server/routes/instance/httpapi/handlers/mcp.ts +0 -111
- package/src/server/routes/instance/httpapi/handlers/permission.ts +0 -41
- package/src/server/routes/instance/httpapi/handlers/project-copy.ts +0 -83
- package/src/server/routes/instance/httpapi/handlers/project.ts +0 -63
- package/src/server/routes/instance/httpapi/handlers/provider.ts +0 -113
- package/src/server/routes/instance/httpapi/handlers/pty.ts +0 -273
- package/src/server/routes/instance/httpapi/handlers/question.ts +0 -54
- package/src/server/routes/instance/httpapi/handlers/session-errors.ts +0 -21
- package/src/server/routes/instance/httpapi/handlers/session.ts +0 -440
- package/src/server/routes/instance/httpapi/handlers/sync.ts +0 -89
- package/src/server/routes/instance/httpapi/handlers/tui.ts +0 -131
- package/src/server/routes/instance/httpapi/handlers/workspace.ts +0 -102
- package/src/server/routes/instance/httpapi/lifecycle.ts +0 -54
- package/src/server/routes/instance/httpapi/middleware/authorization.ts +0 -150
- package/src/server/routes/instance/httpapi/middleware/compression.ts +0 -64
- package/src/server/routes/instance/httpapi/middleware/cors-vary.ts +0 -29
- package/src/server/routes/instance/httpapi/middleware/error.ts +0 -43
- package/src/server/routes/instance/httpapi/middleware/fence.ts +0 -25
- package/src/server/routes/instance/httpapi/middleware/instance-context.ts +0 -43
- package/src/server/routes/instance/httpapi/middleware/proxy.ts +0 -108
- package/src/server/routes/instance/httpapi/middleware/schema-error.ts +0 -41
- package/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +0 -250
- package/src/server/routes/instance/httpapi/public.ts +0 -535
- package/src/server/routes/instance/httpapi/server.ts +0 -298
- package/src/server/routes/instance/httpapi/websocket-tracker.ts +0 -57
- package/src/server/server.ts +0 -225
- package/src/server/shared/fence.ts +0 -60
- package/src/server/shared/pty-ticket.ts +0 -15
- package/src/server/shared/public-ui.ts +0 -12
- package/src/server/shared/tui-control.ts +0 -28
- package/src/server/shared/ui.ts +0 -108
- package/src/server/shared/workspace-routing.ts +0 -38
- package/src/server/tui-event.ts +0 -53
- package/src/session/compaction.ts +0 -620
- package/src/session/instruction.ts +0 -241
- package/src/session/llm/AGENTS.md +0 -90
- package/src/session/llm/ai-sdk.ts +0 -288
- package/src/session/llm/native-request.ts +0 -196
- package/src/session/llm/native-runtime.ts +0 -195
- package/src/session/llm/request.ts +0 -216
- package/src/session/llm.ts +0 -415
- package/src/session/message-error.ts +0 -14
- package/src/session/message-v2.ts +0 -747
- package/src/session/message.ts +0 -148
- package/src/session/overflow.ts +0 -34
- package/src/session/processor.ts +0 -1084
- package/src/session/prompt/anthropic.txt +0 -109
- package/src/session/prompt/beast.txt +0 -151
- package/src/session/prompt/build-switch.txt +0 -9
- package/src/session/prompt/codex.txt +0 -83
- package/src/session/prompt/copilot-gpt-5.txt +0 -147
- package/src/session/prompt/default.txt +0 -99
- package/src/session/prompt/gemini.txt +0 -159
- package/src/session/prompt/gpt.txt +0 -111
- package/src/session/prompt/kimi.txt +0 -99
- package/src/session/prompt/plan-mode.txt +0 -74
- package/src/session/prompt/plan-reminder-anthropic.txt +0 -71
- package/src/session/prompt/plan.txt +0 -30
- package/src/session/prompt/trinity.txt +0 -101
- package/src/session/prompt.ts +0 -1707
- package/src/session/reminders.ts +0 -92
- package/src/session/retry.ts +0 -201
- package/src/session/revert.ts +0 -160
- package/src/session/run-state.ts +0 -156
- package/src/session/schema.ts +0 -26
- package/src/session/session.ts +0 -1119
- package/src/session/status.ts +0 -97
- package/src/session/summary.ts +0 -165
- package/src/session/system.ts +0 -117
- package/src/session/todo.ts +0 -90
- package/src/session/tools.ts +0 -207
- package/src/share/session.ts +0 -61
- package/src/share/share-next.ts +0 -385
- package/src/skill/discovery.ts +0 -109
- package/src/skill/index.ts +0 -366
- package/src/snapshot/index.ts +0 -808
- package/src/sql.d.ts +0 -4
- package/src/storage/schema.ts +0 -5
- package/src/storage/storage.ts +0 -329
- package/src/sync/README.md +0 -179
- package/src/sync/schema.ts +0 -11
- package/src/temporary.ts +0 -31
- package/src/tool/apply_patch.ts +0 -315
- package/src/tool/apply_patch.txt +0 -33
- package/src/tool/apply_patch.zh.txt +0 -33
- package/src/tool/description.ts +0 -100
- package/src/tool/edit.ts +0 -739
- package/src/tool/edit.txt +0 -10
- package/src/tool/edit.zh.txt +0 -10
- package/src/tool/external-directory.ts +0 -49
- package/src/tool/glob.ts +0 -78
- package/src/tool/glob.txt +0 -6
- package/src/tool/glob.zh.txt +0 -6
- package/src/tool/grep.ts +0 -114
- package/src/tool/grep.txt +0 -8
- package/src/tool/grep.zh.txt +0 -8
- package/src/tool/invalid.ts +0 -21
- package/src/tool/json-schema.ts +0 -164
- package/src/tool/lsp.ts +0 -115
- package/src/tool/lsp.txt +0 -24
- package/src/tool/lsp.zh.txt +0 -24
- package/src/tool/mcp-websearch.ts +0 -96
- package/src/tool/plan-enter.txt +0 -14
- package/src/tool/plan-enter.zh.txt +0 -14
- package/src/tool/plan-exit.txt +0 -13
- package/src/tool/plan-exit.zh.txt +0 -13
- package/src/tool/plan.ts +0 -81
- package/src/tool/question.ts +0 -46
- package/src/tool/question.txt +0 -10
- package/src/tool/question.zh.txt +0 -10
- package/src/tool/read.ts +0 -388
- package/src/tool/read.txt +0 -14
- package/src/tool/read.zh.txt +0 -14
- package/src/tool/registry.ts +0 -440
- package/src/tool/schema.ts +0 -14
- package/src/tool/shell/id.ts +0 -19
- package/src/tool/shell/prompt.ts +0 -307
- package/src/tool/shell/shell.txt +0 -21
- package/src/tool/shell.ts +0 -657
- package/src/tool/skill.ts +0 -73
- package/src/tool/skill.txt +0 -5
- package/src/tool/skill.zh.txt +0 -5
- package/src/tool/task.ts +0 -348
- package/src/tool/task.txt +0 -19
- package/src/tool/task.zh.txt +0 -19
- package/src/tool/todo.ts +0 -59
- package/src/tool/todowrite.txt +0 -44
- package/src/tool/todowrite.zh.txt +0 -44
- package/src/tool/tool.ts +0 -183
- package/src/tool/truncate.ts +0 -158
- package/src/tool/truncation-dir.ts +0 -4
- package/src/tool/webfetch.ts +0 -194
- package/src/tool/webfetch.txt +0 -13
- package/src/tool/webfetch.zh.txt +0 -13
- package/src/tool/websearch.ts +0 -145
- package/src/tool/websearch.txt +0 -14
- package/src/tool/websearch.zh.txt +0 -14
- package/src/tool/write.ts +0 -106
- package/src/tool/write.txt +0 -8
- package/src/tool/write.zh.txt +0 -8
- package/src/util/archive.ts +0 -17
- package/src/util/bom.ts +0 -27
- package/src/util/data-url.ts +0 -9
- package/src/util/defer.ts +0 -10
- package/src/util/effect-http-client.ts +0 -11
- package/src/util/error.ts +0 -1
- package/src/util/filesystem.ts +0 -251
- package/src/util/html.ts +0 -8
- package/src/util/iife.ts +0 -3
- package/src/util/lazy.ts +0 -20
- package/src/util/local-context.ts +0 -25
- package/src/util/locale.ts +0 -2
- package/src/util/media.ts +0 -26
- package/src/util/process.ts +0 -177
- package/src/util/proxy-env.ts +0 -72
- package/src/util/queue.ts +0 -32
- package/src/util/record.ts +0 -1
- package/src/util/repository.ts +0 -232
- package/src/util/rpc.ts +0 -66
- package/src/util/signal.ts +0 -12
- package/src/util/timeout.ts +0 -13
- package/src/util/token.ts +0 -1
- package/src/util/wildcard.ts +0 -59
- package/src/worktree/index.ts +0 -654
|
@@ -1,1130 +0,0 @@
|
|
|
1
|
-
import { runtimeModules as keymapRuntimeModules } from "@opentui/keymap/runtime-modules"
|
|
2
|
-
import { ensureRuntimePluginSupport } from "@opentui/solid/runtime-plugin-support/configure"
|
|
3
|
-
import {
|
|
4
|
-
type TuiDispose,
|
|
5
|
-
type TuiPlugin,
|
|
6
|
-
type TuiPluginApi,
|
|
7
|
-
type TuiPluginInstallResult,
|
|
8
|
-
type TuiPluginModule,
|
|
9
|
-
type TuiPluginMeta,
|
|
10
|
-
type TuiPluginStatus,
|
|
11
|
-
type TuiSlotPlugin,
|
|
12
|
-
type TuiTheme,
|
|
13
|
-
} from "@opencode-ai/plugin/tui"
|
|
14
|
-
import path from "path"
|
|
15
|
-
import { fileURLToPath } from "url"
|
|
16
|
-
import { TuiConfig } from "@/config/tui"
|
|
17
|
-
import { errorData, errorMessage } from "@opencode-ai/tui/util/error"
|
|
18
|
-
import { isRecord } from "@opencode-ai/tui/util/record"
|
|
19
|
-
import { resolveHostAttentionSoundPaths } from "@/config/tui-host-attention"
|
|
20
|
-
import {
|
|
21
|
-
readPackageThemes,
|
|
22
|
-
readPluginId,
|
|
23
|
-
readV1Plugin,
|
|
24
|
-
resolvePluginId,
|
|
25
|
-
type PluginPackage,
|
|
26
|
-
type PluginSource,
|
|
27
|
-
} from "@/plugin/shared"
|
|
28
|
-
import { PluginLoader } from "@/plugin/loader"
|
|
29
|
-
import { PluginMeta } from "@/plugin/meta"
|
|
30
|
-
import { installPlugin as installModulePlugin, patchPluginConfig, readPluginManifest } from "@/plugin/install"
|
|
31
|
-
import { hasTheme, upsertTheme } from "@opencode-ai/tui/context/theme"
|
|
32
|
-
import { Global } from "@opencode-ai/core/global"
|
|
33
|
-
import { Filesystem } from "@/util/filesystem"
|
|
34
|
-
import { Process } from "@/util/process"
|
|
35
|
-
import { Flock } from "@opencode-ai/core/util/flock"
|
|
36
|
-
import { Flag } from "@opencode-ai/core/flag/flag"
|
|
37
|
-
import { internalTuiPlugins, type InternalTuiPlugin } from "./internal"
|
|
38
|
-
import type { HostPluginApi, HostSlots } from "@opencode-ai/tui/plugin/slots"
|
|
39
|
-
import { ConfigPlugin } from "@/config/plugin"
|
|
40
|
-
import { ConfigPluginV1 } from "@opencode-ai/core/v1/config/plugin"
|
|
41
|
-
import { createCommandShim } from "@opencode-ai/tui/plugin/command-shim"
|
|
42
|
-
import { RuntimeFlags } from "@/effect/runtime-flags"
|
|
43
|
-
import { Effect } from "effect"
|
|
44
|
-
import { createPluginRuntime, type PluginRuntime, type TuiPluginHost } from "@opencode-ai/tui/plugin/runtime"
|
|
45
|
-
|
|
46
|
-
ensureRuntimePluginSupport({ additional: keymapRuntimeModules })
|
|
47
|
-
|
|
48
|
-
type PluginLoad = {
|
|
49
|
-
options: ConfigPluginV1.Options | undefined
|
|
50
|
-
spec: string
|
|
51
|
-
target: string
|
|
52
|
-
retry: boolean
|
|
53
|
-
source: PluginSource | "internal"
|
|
54
|
-
id: string
|
|
55
|
-
module: TuiPluginModule
|
|
56
|
-
origin: ConfigPlugin.Origin
|
|
57
|
-
plugin_root: string
|
|
58
|
-
theme_files: string[]
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
type Api = HostPluginApi
|
|
62
|
-
|
|
63
|
-
type PluginScope = {
|
|
64
|
-
lifecycle: TuiPluginApi["lifecycle"]
|
|
65
|
-
track: (fn: (() => void) | undefined) => () => void
|
|
66
|
-
dispose: () => Promise<void>
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
type PluginEntry = {
|
|
70
|
-
id: string
|
|
71
|
-
load: PluginLoad
|
|
72
|
-
meta: TuiPluginMeta
|
|
73
|
-
themes: Record<string, PluginMeta.Theme>
|
|
74
|
-
plugin: TuiPlugin
|
|
75
|
-
enabled: boolean
|
|
76
|
-
scope?: PluginScope
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const ScopedKeymapMethods = new Set<PropertyKey>([
|
|
80
|
-
"acquireResource",
|
|
81
|
-
"registerLayer",
|
|
82
|
-
"registerLayerFields",
|
|
83
|
-
"prependLayerBindingsTransformer",
|
|
84
|
-
"appendLayerBindingsTransformer",
|
|
85
|
-
"prependBindingTransformer",
|
|
86
|
-
"appendBindingTransformer",
|
|
87
|
-
"prependBindingParser",
|
|
88
|
-
"appendBindingParser",
|
|
89
|
-
"registerToken",
|
|
90
|
-
"registerSequencePattern",
|
|
91
|
-
"prependBindingExpander",
|
|
92
|
-
"appendBindingExpander",
|
|
93
|
-
"registerBindingFields",
|
|
94
|
-
"registerCommandFields",
|
|
95
|
-
"prependCommandTransformer",
|
|
96
|
-
"appendCommandTransformer",
|
|
97
|
-
"prependCommandResolver",
|
|
98
|
-
"appendCommandResolver",
|
|
99
|
-
"prependLayerAnalyzer",
|
|
100
|
-
"appendLayerAnalyzer",
|
|
101
|
-
"intercept",
|
|
102
|
-
"on",
|
|
103
|
-
"prependEventMatchResolver",
|
|
104
|
-
"appendEventMatchResolver",
|
|
105
|
-
"prependDisambiguationResolver",
|
|
106
|
-
"appendDisambiguationResolver",
|
|
107
|
-
])
|
|
108
|
-
|
|
109
|
-
type RuntimeState = {
|
|
110
|
-
directory: string
|
|
111
|
-
api: Api
|
|
112
|
-
view: PluginRuntime
|
|
113
|
-
dispose?: () => void
|
|
114
|
-
slots: HostSlots
|
|
115
|
-
plugins: PluginEntry[]
|
|
116
|
-
plugins_by_id: Map<string, PluginEntry>
|
|
117
|
-
pending: Map<string, ConfigPlugin.Origin>
|
|
118
|
-
dispose_timeout_ms: number
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const DISPOSE_TIMEOUT_MS = 5000
|
|
122
|
-
const KV_KEY = "plugin_enabled"
|
|
123
|
-
const EMPTY_TUI: TuiPluginModule = {
|
|
124
|
-
tui: async () => {},
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function fail(message: string, data: Record<string, unknown>) {
|
|
128
|
-
if (!("error" in data)) {
|
|
129
|
-
console.error(`[tui.plugin] ${message}`, data)
|
|
130
|
-
return
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const text = `${message}: ${errorMessage(data.error)}`
|
|
134
|
-
const next = { ...data, error: errorData(data.error) }
|
|
135
|
-
console.error(`[tui.plugin] ${text}`, next)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function warn(message: string, data: Record<string, unknown>) {
|
|
139
|
-
console.warn(`[tui.plugin] ${message}`, data)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function createScopedKeymap(keymap: TuiPluginApi["keymap"], scope: PluginScope): TuiPluginApi["keymap"] {
|
|
143
|
-
const cache = new Map<PropertyKey, unknown>()
|
|
144
|
-
return new Proxy(keymap, {
|
|
145
|
-
get(target, prop) {
|
|
146
|
-
const value = Reflect.get(target, prop, target)
|
|
147
|
-
if (typeof value !== "function") return value
|
|
148
|
-
if (cache.has(prop)) return cache.get(prop)
|
|
149
|
-
const fn = ScopedKeymapMethods.has(prop)
|
|
150
|
-
? (...args: unknown[]) => {
|
|
151
|
-
const dispose = (value as (...args: unknown[]) => unknown).apply(target, args)
|
|
152
|
-
return scope.track(typeof dispose === "function" ? (dispose as () => void) : undefined)
|
|
153
|
-
}
|
|
154
|
-
: (...args: unknown[]) => (value as (...args: unknown[]) => unknown).apply(target, args)
|
|
155
|
-
cache.set(prop, fn)
|
|
156
|
-
return fn
|
|
157
|
-
},
|
|
158
|
-
})
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function createScopedAttention(
|
|
162
|
-
attention: TuiPluginApi["attention"],
|
|
163
|
-
scope: PluginScope,
|
|
164
|
-
root: string,
|
|
165
|
-
): TuiPluginApi["attention"] {
|
|
166
|
-
return {
|
|
167
|
-
notify(input) {
|
|
168
|
-
return attention.notify(input)
|
|
169
|
-
},
|
|
170
|
-
soundboard: {
|
|
171
|
-
registerPack(pack) {
|
|
172
|
-
return scope.track(
|
|
173
|
-
attention.soundboard.registerPack({
|
|
174
|
-
...pack,
|
|
175
|
-
sounds: resolveHostAttentionSoundPaths(root, pack.sounds, { trim: true }),
|
|
176
|
-
}),
|
|
177
|
-
)
|
|
178
|
-
},
|
|
179
|
-
activate(id, options) {
|
|
180
|
-
return attention.soundboard.activate(id, options)
|
|
181
|
-
},
|
|
182
|
-
current() {
|
|
183
|
-
return attention.soundboard.current()
|
|
184
|
-
},
|
|
185
|
-
list() {
|
|
186
|
-
return attention.soundboard.list()
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function createScopedMode(mode: TuiPluginApi["mode"], scope: PluginScope): TuiPluginApi["mode"] {
|
|
193
|
-
return {
|
|
194
|
-
current() {
|
|
195
|
-
return mode.current()
|
|
196
|
-
},
|
|
197
|
-
push(value) {
|
|
198
|
-
return scope.track(mode.push(value))
|
|
199
|
-
},
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
type CleanupResult = { type: "ok" } | { type: "error"; error: unknown } | { type: "timeout" }
|
|
204
|
-
|
|
205
|
-
function runCleanup(fn: () => unknown, ms: number): Promise<CleanupResult> {
|
|
206
|
-
return new Promise((resolve) => {
|
|
207
|
-
const timer = setTimeout(() => {
|
|
208
|
-
resolve({ type: "timeout" })
|
|
209
|
-
}, ms)
|
|
210
|
-
|
|
211
|
-
Promise.resolve()
|
|
212
|
-
.then(fn)
|
|
213
|
-
.then(
|
|
214
|
-
() => {
|
|
215
|
-
resolve({ type: "ok" })
|
|
216
|
-
},
|
|
217
|
-
(error) => {
|
|
218
|
-
resolve({ type: "error", error })
|
|
219
|
-
},
|
|
220
|
-
)
|
|
221
|
-
.finally(() => {
|
|
222
|
-
clearTimeout(timer)
|
|
223
|
-
})
|
|
224
|
-
})
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function isTheme(value: unknown) {
|
|
228
|
-
if (!isRecord(value)) return false
|
|
229
|
-
if (!("theme" in value)) return false
|
|
230
|
-
if (!isRecord(value.theme)) return false
|
|
231
|
-
return true
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function resolveRoot(root: string) {
|
|
235
|
-
if (root.startsWith("file://")) {
|
|
236
|
-
const file = fileURLToPath(root)
|
|
237
|
-
if (root.endsWith("/")) return file
|
|
238
|
-
return path.dirname(file)
|
|
239
|
-
}
|
|
240
|
-
if (path.isAbsolute(root)) return root
|
|
241
|
-
return path.resolve(process.cwd(), root)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function createThemeInstaller(
|
|
245
|
-
meta: ConfigPlugin.Origin,
|
|
246
|
-
root: string,
|
|
247
|
-
spec: string,
|
|
248
|
-
plugin: PluginEntry,
|
|
249
|
-
): TuiTheme["install"] {
|
|
250
|
-
return async (file) => {
|
|
251
|
-
const src = Filesystem.resolveFilePath(root, file)
|
|
252
|
-
const name = path.basename(src, path.extname(src))
|
|
253
|
-
const source_dir = path.dirname(meta.source)
|
|
254
|
-
const local_dir =
|
|
255
|
-
path.basename(source_dir) === ".opencode"
|
|
256
|
-
? path.join(source_dir, "themes")
|
|
257
|
-
: path.join(source_dir, ".opencode", "themes")
|
|
258
|
-
const dest_dir = meta.scope === "local" ? local_dir : path.join(Global.Path.config, "themes")
|
|
259
|
-
const dest = path.join(dest_dir, `${name}.json`)
|
|
260
|
-
const stat = await Filesystem.statAsync(src)
|
|
261
|
-
const mtime = stat ? Math.floor(typeof stat.mtimeMs === "bigint" ? Number(stat.mtimeMs) : stat.mtimeMs) : undefined
|
|
262
|
-
const size = stat ? (typeof stat.size === "bigint" ? Number(stat.size) : stat.size) : undefined
|
|
263
|
-
const info = {
|
|
264
|
-
src,
|
|
265
|
-
dest,
|
|
266
|
-
mtime,
|
|
267
|
-
size,
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
await Flock.withLock(`tui-theme:${dest}`, async () => {
|
|
271
|
-
const save = async () => {
|
|
272
|
-
plugin.themes[name] = info
|
|
273
|
-
await PluginMeta.setTheme(plugin.id, name, info).catch(() => {})
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const exists = hasTheme(name)
|
|
277
|
-
const prev = plugin.themes[name]
|
|
278
|
-
if (exists) {
|
|
279
|
-
if (plugin.meta.state !== "updated") {
|
|
280
|
-
if (!prev && (await Filesystem.exists(dest))) {
|
|
281
|
-
await save()
|
|
282
|
-
}
|
|
283
|
-
return
|
|
284
|
-
}
|
|
285
|
-
if (prev?.dest === dest && prev.mtime === mtime && prev.size === size) return
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const text = await Filesystem.readText(src).catch(() => undefined)
|
|
289
|
-
if (text === undefined) return
|
|
290
|
-
|
|
291
|
-
const fail = Symbol()
|
|
292
|
-
const data = await Promise.resolve(text)
|
|
293
|
-
.then((x) => JSON.parse(x))
|
|
294
|
-
.catch(() => fail)
|
|
295
|
-
if (data === fail) return
|
|
296
|
-
|
|
297
|
-
if (!isTheme(data)) {
|
|
298
|
-
return
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (exists || !(await Filesystem.exists(dest))) {
|
|
302
|
-
await Filesystem.write(dest, text).catch(() => {})
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
upsertTheme(name, data)
|
|
306
|
-
await save()
|
|
307
|
-
}).catch(() => {})
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function createMeta(
|
|
312
|
-
source: PluginLoad["source"],
|
|
313
|
-
spec: string,
|
|
314
|
-
target: string,
|
|
315
|
-
meta: { state: PluginMeta.State; entry: PluginMeta.Entry } | undefined,
|
|
316
|
-
id?: string,
|
|
317
|
-
): TuiPluginMeta {
|
|
318
|
-
if (meta) {
|
|
319
|
-
return {
|
|
320
|
-
state: meta.state,
|
|
321
|
-
...meta.entry,
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const now = Date.now()
|
|
326
|
-
return {
|
|
327
|
-
state: source === "internal" ? "same" : "first",
|
|
328
|
-
id: id ?? spec,
|
|
329
|
-
source,
|
|
330
|
-
spec,
|
|
331
|
-
target,
|
|
332
|
-
first_time: now,
|
|
333
|
-
last_time: now,
|
|
334
|
-
time_changed: now,
|
|
335
|
-
load_count: 1,
|
|
336
|
-
fingerprint: target,
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function loadInternalPlugin(item: InternalTuiPlugin): PluginLoad {
|
|
341
|
-
const spec = item.id
|
|
342
|
-
const target = spec
|
|
343
|
-
|
|
344
|
-
return {
|
|
345
|
-
options: undefined,
|
|
346
|
-
spec,
|
|
347
|
-
target,
|
|
348
|
-
retry: false,
|
|
349
|
-
source: "internal",
|
|
350
|
-
id: item.id,
|
|
351
|
-
module: item,
|
|
352
|
-
origin: {
|
|
353
|
-
spec,
|
|
354
|
-
scope: "global",
|
|
355
|
-
source: target,
|
|
356
|
-
},
|
|
357
|
-
plugin_root: process.cwd(),
|
|
358
|
-
theme_files: [],
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async function readThemeFiles(spec: string, pkg?: PluginPackage) {
|
|
363
|
-
if (!pkg) return [] as string[]
|
|
364
|
-
return Promise.resolve()
|
|
365
|
-
.then(() => readPackageThemes(spec, pkg))
|
|
366
|
-
.catch((error) => {
|
|
367
|
-
warn("invalid tui plugin oc-themes", {
|
|
368
|
-
path: spec,
|
|
369
|
-
pkg: pkg.pkg,
|
|
370
|
-
error,
|
|
371
|
-
})
|
|
372
|
-
return [] as string[]
|
|
373
|
-
})
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
async function syncPluginThemes(plugin: PluginEntry) {
|
|
377
|
-
if (!plugin.load.theme_files.length) return
|
|
378
|
-
if (plugin.meta.state === "same") return
|
|
379
|
-
const install = createThemeInstaller(plugin.load.origin, plugin.load.plugin_root, plugin.load.spec, plugin)
|
|
380
|
-
for (const file of plugin.load.theme_files) {
|
|
381
|
-
await install(file).catch((error) => {
|
|
382
|
-
warn("failed to sync tui plugin oc-themes", { path: plugin.load.spec, id: plugin.id, theme: file, error })
|
|
383
|
-
})
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function createPluginScope(load: PluginLoad, id: string, disposeTimeoutMs: number) {
|
|
388
|
-
const ctrl = new AbortController()
|
|
389
|
-
let list: { key: symbol; fn: TuiDispose }[] = []
|
|
390
|
-
let done = false
|
|
391
|
-
|
|
392
|
-
const onDispose = (fn: TuiDispose) => {
|
|
393
|
-
if (done) return () => {}
|
|
394
|
-
const key = Symbol()
|
|
395
|
-
list.push({ key, fn })
|
|
396
|
-
let drop = false
|
|
397
|
-
return () => {
|
|
398
|
-
if (drop) return
|
|
399
|
-
drop = true
|
|
400
|
-
list = list.filter((x) => x.key !== key)
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const track = (fn: (() => void) | undefined) => {
|
|
405
|
-
if (!fn) return () => {}
|
|
406
|
-
let drop = false
|
|
407
|
-
let off = () => {}
|
|
408
|
-
const wrapped = () => {
|
|
409
|
-
if (drop) return
|
|
410
|
-
drop = true
|
|
411
|
-
off()
|
|
412
|
-
fn()
|
|
413
|
-
}
|
|
414
|
-
off = onDispose(wrapped)
|
|
415
|
-
return wrapped
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const lifecycle: TuiPluginApi["lifecycle"] = {
|
|
419
|
-
signal: ctrl.signal,
|
|
420
|
-
onDispose,
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const dispose = async () => {
|
|
424
|
-
if (done) return
|
|
425
|
-
done = true
|
|
426
|
-
ctrl.abort()
|
|
427
|
-
const queue = [...list].reverse()
|
|
428
|
-
list = []
|
|
429
|
-
const until = Date.now() + disposeTimeoutMs
|
|
430
|
-
for (const item of queue) {
|
|
431
|
-
const left = until - Date.now()
|
|
432
|
-
if (left <= 0) {
|
|
433
|
-
fail("timed out cleaning up tui plugin", {
|
|
434
|
-
path: load.spec,
|
|
435
|
-
id,
|
|
436
|
-
timeout: disposeTimeoutMs,
|
|
437
|
-
})
|
|
438
|
-
break
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const out = await runCleanup(item.fn, left)
|
|
442
|
-
if (out.type === "ok") continue
|
|
443
|
-
if (out.type === "timeout") {
|
|
444
|
-
fail("timed out cleaning up tui plugin", {
|
|
445
|
-
path: load.spec,
|
|
446
|
-
id,
|
|
447
|
-
timeout: disposeTimeoutMs,
|
|
448
|
-
})
|
|
449
|
-
break
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
if (out.type === "error") {
|
|
453
|
-
fail("failed to clean up tui plugin", {
|
|
454
|
-
path: load.spec,
|
|
455
|
-
id,
|
|
456
|
-
error: out.error,
|
|
457
|
-
})
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
return {
|
|
463
|
-
lifecycle,
|
|
464
|
-
track,
|
|
465
|
-
dispose,
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function readPluginEnabledMap(value: unknown) {
|
|
470
|
-
if (!isRecord(value)) return {}
|
|
471
|
-
return Object.fromEntries(
|
|
472
|
-
Object.entries(value).filter((item): item is [string, boolean] => typeof item[1] === "boolean"),
|
|
473
|
-
)
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function pluginEnabledState(state: RuntimeState, config: TuiConfig.Resolved) {
|
|
477
|
-
return {
|
|
478
|
-
...readPluginEnabledMap(config.plugin_enabled),
|
|
479
|
-
...readPluginEnabledMap(state.api.kv.get(KV_KEY, {})),
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function writePluginEnabledState(api: Api, id: string, enabled: boolean) {
|
|
484
|
-
api.kv.set(KV_KEY, {
|
|
485
|
-
...readPluginEnabledMap(api.kv.get(KV_KEY, {})),
|
|
486
|
-
[id]: enabled,
|
|
487
|
-
})
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
function listPluginStatus(state: RuntimeState): TuiPluginStatus[] {
|
|
491
|
-
return state.plugins.map((plugin) => ({
|
|
492
|
-
id: plugin.id,
|
|
493
|
-
source: plugin.meta.source,
|
|
494
|
-
spec: plugin.meta.spec,
|
|
495
|
-
target: plugin.meta.target,
|
|
496
|
-
enabled: plugin.enabled,
|
|
497
|
-
active: plugin.scope !== undefined,
|
|
498
|
-
}))
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
async function deactivatePluginEntry(state: RuntimeState, plugin: PluginEntry, persist: boolean) {
|
|
502
|
-
plugin.enabled = false
|
|
503
|
-
if (persist) writePluginEnabledState(state.api, plugin.id, false)
|
|
504
|
-
if (!plugin.scope) {
|
|
505
|
-
state.view.update({ status: listPluginStatus(state) })
|
|
506
|
-
return true
|
|
507
|
-
}
|
|
508
|
-
const scope = plugin.scope
|
|
509
|
-
plugin.scope = undefined
|
|
510
|
-
await scope.dispose()
|
|
511
|
-
state.view.update({ status: listPluginStatus(state) })
|
|
512
|
-
return true
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
async function activatePluginEntry(state: RuntimeState, plugin: PluginEntry, persist: boolean) {
|
|
516
|
-
plugin.enabled = true
|
|
517
|
-
if (persist) writePluginEnabledState(state.api, plugin.id, true)
|
|
518
|
-
if (plugin.scope) {
|
|
519
|
-
state.view.update({ status: listPluginStatus(state) })
|
|
520
|
-
return true
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const scope = createPluginScope(plugin.load, plugin.id, state.dispose_timeout_ms)
|
|
524
|
-
const api = pluginApi(state, plugin, scope, plugin.id)
|
|
525
|
-
const ok = await Promise.resolve()
|
|
526
|
-
.then(async () => {
|
|
527
|
-
await syncPluginThemes(plugin)
|
|
528
|
-
await plugin.plugin(api, plugin.load.options, plugin.meta)
|
|
529
|
-
return true
|
|
530
|
-
})
|
|
531
|
-
.catch((error) => {
|
|
532
|
-
fail("failed to initialize tui plugin", {
|
|
533
|
-
path: plugin.load.spec,
|
|
534
|
-
id: plugin.id,
|
|
535
|
-
error,
|
|
536
|
-
})
|
|
537
|
-
return false
|
|
538
|
-
})
|
|
539
|
-
|
|
540
|
-
if (!ok) {
|
|
541
|
-
await scope.dispose()
|
|
542
|
-
state.view.update({ status: listPluginStatus(state) })
|
|
543
|
-
return false
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
if (!plugin.enabled) {
|
|
547
|
-
await scope.dispose()
|
|
548
|
-
state.view.update({ status: listPluginStatus(state) })
|
|
549
|
-
return true
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
plugin.scope = scope
|
|
553
|
-
state.view.update({ status: listPluginStatus(state) })
|
|
554
|
-
return true
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
async function activatePluginById(state: RuntimeState | undefined, id: string, persist: boolean) {
|
|
558
|
-
if (!state) return false
|
|
559
|
-
const plugin = state.plugins_by_id.get(id)
|
|
560
|
-
if (!plugin) return false
|
|
561
|
-
return activatePluginEntry(state, plugin, persist)
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
async function deactivatePluginById(state: RuntimeState | undefined, id: string, persist: boolean) {
|
|
565
|
-
if (!state) return false
|
|
566
|
-
const plugin = state.plugins_by_id.get(id)
|
|
567
|
-
if (!plugin) return false
|
|
568
|
-
return deactivatePluginEntry(state, plugin, persist)
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
function pluginApi(runtime: RuntimeState, plugin: PluginEntry, scope: PluginScope, base: string): TuiPluginApi {
|
|
572
|
-
const api = runtime.api
|
|
573
|
-
const host = runtime.slots
|
|
574
|
-
const load = plugin.load
|
|
575
|
-
|
|
576
|
-
const route: TuiPluginApi["route"] = {
|
|
577
|
-
register(list) {
|
|
578
|
-
return scope.track(api.route.register(list))
|
|
579
|
-
},
|
|
580
|
-
navigate(name, params) {
|
|
581
|
-
api.route.navigate(name, params)
|
|
582
|
-
},
|
|
583
|
-
get current() {
|
|
584
|
-
return api.route.current
|
|
585
|
-
},
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
const theme: TuiPluginApi["theme"] = Object.assign(Object.create(api.theme), {
|
|
589
|
-
install: createThemeInstaller(load.origin, load.plugin_root, load.spec, plugin),
|
|
590
|
-
})
|
|
591
|
-
|
|
592
|
-
const event: TuiPluginApi["event"] = {
|
|
593
|
-
on(type, handler) {
|
|
594
|
-
return scope.track(api.event.on(type, handler))
|
|
595
|
-
},
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
const keymap = createScopedKeymap(api.keymap, scope)
|
|
599
|
-
|
|
600
|
-
let count = 0
|
|
601
|
-
|
|
602
|
-
const slots: TuiPluginApi["slots"] = {
|
|
603
|
-
register(plugin: TuiSlotPlugin) {
|
|
604
|
-
const id = count ? `${base}:${count}` : base
|
|
605
|
-
count += 1
|
|
606
|
-
scope.track(host.register({ ...plugin, id }))
|
|
607
|
-
return id
|
|
608
|
-
},
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
return {
|
|
612
|
-
app: api.app,
|
|
613
|
-
attention: createScopedAttention(api.attention, scope, load.plugin_root),
|
|
614
|
-
// Keep deprecated `api.command` working for v1 plugins; remove in v2.
|
|
615
|
-
command: createCommandShim(keymap, api.ui.dialog, api.tuiConfig.keybinds),
|
|
616
|
-
keys: api.keys,
|
|
617
|
-
keymap,
|
|
618
|
-
mode: createScopedMode(api.mode, scope),
|
|
619
|
-
route,
|
|
620
|
-
ui: api.ui,
|
|
621
|
-
tuiConfig: api.tuiConfig,
|
|
622
|
-
kv: api.kv,
|
|
623
|
-
state: api.state,
|
|
624
|
-
theme,
|
|
625
|
-
get client() {
|
|
626
|
-
return api.client
|
|
627
|
-
},
|
|
628
|
-
event,
|
|
629
|
-
renderer: api.renderer,
|
|
630
|
-
slots,
|
|
631
|
-
plugins: {
|
|
632
|
-
list() {
|
|
633
|
-
return listPluginStatus(runtime)
|
|
634
|
-
},
|
|
635
|
-
activate(id) {
|
|
636
|
-
return activatePluginById(runtime, id, true)
|
|
637
|
-
},
|
|
638
|
-
deactivate(id) {
|
|
639
|
-
return deactivatePluginById(runtime, id, true)
|
|
640
|
-
},
|
|
641
|
-
add(spec) {
|
|
642
|
-
return addPluginBySpec(runtime, spec)
|
|
643
|
-
},
|
|
644
|
-
install(spec, options) {
|
|
645
|
-
return installPluginBySpec(runtime, spec, options?.global)
|
|
646
|
-
},
|
|
647
|
-
},
|
|
648
|
-
lifecycle: scope.lifecycle,
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
function addPluginEntry(state: RuntimeState, plugin: PluginEntry) {
|
|
653
|
-
if (state.plugins_by_id.has(plugin.id)) {
|
|
654
|
-
fail("duplicate tui plugin id", {
|
|
655
|
-
id: plugin.id,
|
|
656
|
-
path: plugin.load.spec,
|
|
657
|
-
})
|
|
658
|
-
return false
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
state.plugins_by_id.set(plugin.id, plugin)
|
|
662
|
-
state.plugins.push(plugin)
|
|
663
|
-
return true
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
function applyInitialPluginEnabledState(state: RuntimeState, config: TuiConfig.Resolved) {
|
|
667
|
-
const map = pluginEnabledState(state, config)
|
|
668
|
-
for (const plugin of state.plugins) {
|
|
669
|
-
const enabled = map[plugin.id]
|
|
670
|
-
if (enabled === undefined) continue
|
|
671
|
-
plugin.enabled = enabled
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
async function resolveExternalPlugins(list: ConfigPlugin.Origin[], wait: () => Promise<void>) {
|
|
676
|
-
return PluginLoader.loadExternal({
|
|
677
|
-
items: list,
|
|
678
|
-
kind: "tui",
|
|
679
|
-
wait: async () => {
|
|
680
|
-
await wait().catch(() => {})
|
|
681
|
-
},
|
|
682
|
-
finish: async (loaded, origin, retry) => {
|
|
683
|
-
const mod = await Promise.resolve()
|
|
684
|
-
.then(() => readV1Plugin(loaded.mod as Record<string, unknown>, loaded.spec, "tui") as TuiPluginModule)
|
|
685
|
-
.catch((error) => {
|
|
686
|
-
fail("failed to load tui plugin", {
|
|
687
|
-
path: loaded.spec,
|
|
688
|
-
target: loaded.entry,
|
|
689
|
-
retry,
|
|
690
|
-
error,
|
|
691
|
-
})
|
|
692
|
-
return
|
|
693
|
-
})
|
|
694
|
-
if (!mod) return
|
|
695
|
-
|
|
696
|
-
const id = await resolvePluginId(
|
|
697
|
-
loaded.source,
|
|
698
|
-
loaded.spec,
|
|
699
|
-
loaded.target,
|
|
700
|
-
readPluginId(mod.id, loaded.spec),
|
|
701
|
-
loaded.pkg,
|
|
702
|
-
).catch((error) => {
|
|
703
|
-
fail("failed to load tui plugin", { path: loaded.spec, target: loaded.target, retry, error })
|
|
704
|
-
return
|
|
705
|
-
})
|
|
706
|
-
if (!id) return
|
|
707
|
-
|
|
708
|
-
const theme_files = await readThemeFiles(loaded.spec, loaded.pkg)
|
|
709
|
-
|
|
710
|
-
return {
|
|
711
|
-
options: loaded.options,
|
|
712
|
-
spec: loaded.spec,
|
|
713
|
-
target: loaded.target,
|
|
714
|
-
retry,
|
|
715
|
-
source: loaded.source,
|
|
716
|
-
id,
|
|
717
|
-
module: mod,
|
|
718
|
-
origin,
|
|
719
|
-
plugin_root: loaded.pkg?.dir ?? resolveRoot(loaded.target),
|
|
720
|
-
theme_files,
|
|
721
|
-
}
|
|
722
|
-
},
|
|
723
|
-
missing: async (loaded, origin, retry) => {
|
|
724
|
-
const theme_files = await readThemeFiles(loaded.spec, loaded.pkg)
|
|
725
|
-
if (!theme_files.length) return
|
|
726
|
-
|
|
727
|
-
const name =
|
|
728
|
-
typeof loaded.pkg?.json.name === "string" && loaded.pkg.json.name.trim().length > 0
|
|
729
|
-
? loaded.pkg.json.name.trim()
|
|
730
|
-
: undefined
|
|
731
|
-
const id = await resolvePluginId(loaded.source, loaded.spec, loaded.target, name, loaded.pkg).catch((error) => {
|
|
732
|
-
fail("failed to load tui plugin", { path: loaded.spec, target: loaded.target, retry, error })
|
|
733
|
-
return
|
|
734
|
-
})
|
|
735
|
-
if (!id) return
|
|
736
|
-
|
|
737
|
-
return {
|
|
738
|
-
options: loaded.options,
|
|
739
|
-
spec: loaded.spec,
|
|
740
|
-
target: loaded.target,
|
|
741
|
-
retry,
|
|
742
|
-
source: loaded.source,
|
|
743
|
-
id,
|
|
744
|
-
module: EMPTY_TUI,
|
|
745
|
-
origin,
|
|
746
|
-
plugin_root: loaded.pkg?.dir ?? resolveRoot(loaded.target),
|
|
747
|
-
theme_files,
|
|
748
|
-
}
|
|
749
|
-
},
|
|
750
|
-
report: {
|
|
751
|
-
start() {},
|
|
752
|
-
missing(candidate, retry, message) {
|
|
753
|
-
warn("tui plugin has no entrypoint", { path: candidate.plan.spec, retry, message })
|
|
754
|
-
},
|
|
755
|
-
error(candidate, retry, stage, error, resolved) {
|
|
756
|
-
const spec = candidate.plan.spec
|
|
757
|
-
if (stage === "install") {
|
|
758
|
-
fail("failed to resolve tui plugin", { path: spec, retry, error })
|
|
759
|
-
return
|
|
760
|
-
}
|
|
761
|
-
if (stage === "compatibility") {
|
|
762
|
-
fail("tui plugin incompatible", { path: spec, retry, error })
|
|
763
|
-
return
|
|
764
|
-
}
|
|
765
|
-
if (stage === "entry") {
|
|
766
|
-
fail("failed to resolve tui plugin entry", { path: spec, retry, error })
|
|
767
|
-
return
|
|
768
|
-
}
|
|
769
|
-
fail("failed to load tui plugin", { path: spec, target: resolved?.entry, retry, error })
|
|
770
|
-
},
|
|
771
|
-
},
|
|
772
|
-
})
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
async function addExternalPluginEntries(state: RuntimeState, ready: PluginLoad[]) {
|
|
776
|
-
if (!ready.length) return { plugins: [] as PluginEntry[], ok: true }
|
|
777
|
-
|
|
778
|
-
const meta = await PluginMeta.touchMany(
|
|
779
|
-
ready.map((item) => ({
|
|
780
|
-
spec: item.spec,
|
|
781
|
-
target: item.target,
|
|
782
|
-
id: item.id,
|
|
783
|
-
})),
|
|
784
|
-
).catch(() => undefined)
|
|
785
|
-
|
|
786
|
-
const plugins: PluginEntry[] = []
|
|
787
|
-
let ok = true
|
|
788
|
-
for (let i = 0; i < ready.length; i++) {
|
|
789
|
-
const entry = ready[i]
|
|
790
|
-
if (!entry) continue
|
|
791
|
-
const hit = meta?.[i]
|
|
792
|
-
const info = createMeta(entry.source, entry.spec, entry.target, hit, entry.id)
|
|
793
|
-
const themes = hit?.entry.themes ? { ...hit.entry.themes } : {}
|
|
794
|
-
const plugin: PluginEntry = {
|
|
795
|
-
id: entry.id,
|
|
796
|
-
load: entry,
|
|
797
|
-
meta: info,
|
|
798
|
-
themes,
|
|
799
|
-
plugin: entry.module.tui,
|
|
800
|
-
enabled: true,
|
|
801
|
-
}
|
|
802
|
-
if (!addPluginEntry(state, plugin)) {
|
|
803
|
-
ok = false
|
|
804
|
-
continue
|
|
805
|
-
}
|
|
806
|
-
plugins.push(plugin)
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
return { plugins, ok }
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
function defaultPluginOrigin(state: RuntimeState, spec: string): ConfigPlugin.Origin {
|
|
813
|
-
return {
|
|
814
|
-
spec,
|
|
815
|
-
scope: "local",
|
|
816
|
-
source: state.api.state.path.config || path.join(state.directory, ".opencode", "tui.json"),
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
function installCause(err: unknown) {
|
|
821
|
-
if (!err || typeof err !== "object") return
|
|
822
|
-
if (!("cause" in err)) return
|
|
823
|
-
return (err as { cause?: unknown }).cause
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
function installDetail(err: unknown) {
|
|
827
|
-
const hit = installCause(err) ?? err
|
|
828
|
-
if (!(hit instanceof Process.RunFailedError)) {
|
|
829
|
-
return {
|
|
830
|
-
message: errorMessage(hit),
|
|
831
|
-
missing: false,
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
const lines = hit.stderr
|
|
836
|
-
.toString()
|
|
837
|
-
.split(/\r?\n/)
|
|
838
|
-
.map((line) => line.trim())
|
|
839
|
-
.filter(Boolean)
|
|
840
|
-
const errs = lines.filter((line) => line.startsWith("error:")).map((line) => line.replace(/^error:\s*/, ""))
|
|
841
|
-
return {
|
|
842
|
-
message: errs[0] ?? lines.at(-1) ?? errorMessage(hit),
|
|
843
|
-
missing: lines.some((line) => line.includes("No version matching")),
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
async function addPluginBySpec(state: RuntimeState | undefined, raw: string) {
|
|
848
|
-
if (!state) return false
|
|
849
|
-
const spec = raw.trim()
|
|
850
|
-
if (!spec) return false
|
|
851
|
-
|
|
852
|
-
const cfg = state.pending.get(spec) ?? defaultPluginOrigin(state, spec)
|
|
853
|
-
const next = ConfigPlugin.pluginSpecifier(cfg.spec)
|
|
854
|
-
if (state.plugins.some((plugin) => plugin.load.spec === next)) {
|
|
855
|
-
state.pending.delete(spec)
|
|
856
|
-
return true
|
|
857
|
-
}
|
|
858
|
-
const ready = await resolveExternalPlugins([cfg], () => TuiConfig.waitForDependencies()).catch((error) => {
|
|
859
|
-
fail("failed to add tui plugin", { path: next, error })
|
|
860
|
-
return [] as PluginLoad[]
|
|
861
|
-
})
|
|
862
|
-
if (!ready.length) {
|
|
863
|
-
return false
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
const first = ready[0]
|
|
867
|
-
if (!first) {
|
|
868
|
-
fail("failed to add tui plugin", { path: next })
|
|
869
|
-
return false
|
|
870
|
-
}
|
|
871
|
-
if (state.plugins_by_id.has(first.id)) {
|
|
872
|
-
state.pending.delete(spec)
|
|
873
|
-
return true
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
const out = await addExternalPluginEntries(state, [first])
|
|
877
|
-
let ok = out.ok && out.plugins.length > 0
|
|
878
|
-
for (const plugin of out.plugins) {
|
|
879
|
-
const active = await activatePluginEntry(state, plugin, false)
|
|
880
|
-
if (!active) ok = false
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
if (ok) state.pending.delete(spec)
|
|
884
|
-
if (!ok) {
|
|
885
|
-
fail("failed to add tui plugin", { path: next })
|
|
886
|
-
}
|
|
887
|
-
return ok
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
async function installPluginBySpec(
|
|
891
|
-
state: RuntimeState | undefined,
|
|
892
|
-
raw: string,
|
|
893
|
-
global = false,
|
|
894
|
-
): Promise<TuiPluginInstallResult> {
|
|
895
|
-
if (!state) {
|
|
896
|
-
return {
|
|
897
|
-
ok: false,
|
|
898
|
-
message: "Plugin runtime is not ready.",
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
const spec = raw.trim()
|
|
903
|
-
if (!spec) {
|
|
904
|
-
return {
|
|
905
|
-
ok: false,
|
|
906
|
-
message: "Plugin package name is required",
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
const dir = state.api.state.path
|
|
911
|
-
if (!dir.directory) {
|
|
912
|
-
return {
|
|
913
|
-
ok: false,
|
|
914
|
-
message: "Paths are still syncing. Try again in a moment.",
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
const install = await installModulePlugin(spec)
|
|
919
|
-
if (!install.ok) {
|
|
920
|
-
const out = installDetail(install.error)
|
|
921
|
-
return {
|
|
922
|
-
ok: false,
|
|
923
|
-
message: out.message,
|
|
924
|
-
missing: out.missing,
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
const manifest = await readPluginManifest(install.target)
|
|
929
|
-
if (!manifest.ok) {
|
|
930
|
-
if (manifest.code === "manifest_no_targets") {
|
|
931
|
-
return {
|
|
932
|
-
ok: false,
|
|
933
|
-
message: `"${spec}" does not expose plugin entrypoints or oc-themes in package.json`,
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
return {
|
|
938
|
-
ok: false,
|
|
939
|
-
message: `Installed "${spec}" but failed to read ${manifest.file}`,
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
const patch = await patchPluginConfig({
|
|
944
|
-
spec,
|
|
945
|
-
targets: manifest.targets,
|
|
946
|
-
global,
|
|
947
|
-
vcs: dir.worktree && dir.worktree !== "/" ? "git" : undefined,
|
|
948
|
-
worktree: dir.worktree,
|
|
949
|
-
directory: dir.directory,
|
|
950
|
-
})
|
|
951
|
-
if (!patch.ok) {
|
|
952
|
-
if (patch.code === "invalid_json") {
|
|
953
|
-
return {
|
|
954
|
-
ok: false,
|
|
955
|
-
message: `Invalid JSON in ${patch.file} (${patch.parse} at line ${patch.line}, column ${patch.col})`,
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
return {
|
|
960
|
-
ok: false,
|
|
961
|
-
message: errorMessage(patch.error),
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
const tui = manifest.targets.find((item) => item.kind === "tui")
|
|
966
|
-
if (tui) {
|
|
967
|
-
const file = patch.items.find((item) => item.kind === "tui")?.file
|
|
968
|
-
const next = tui.opts ? ([spec, tui.opts] as ConfigPluginV1.Spec) : spec
|
|
969
|
-
state.pending.set(spec, {
|
|
970
|
-
spec: next,
|
|
971
|
-
scope: global ? "global" : "local",
|
|
972
|
-
source: (file ?? dir.config) || path.join(patch.dir, "tui.json"),
|
|
973
|
-
})
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
return {
|
|
977
|
-
ok: true,
|
|
978
|
-
dir: patch.dir,
|
|
979
|
-
tui: Boolean(tui),
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
let dir = ""
|
|
984
|
-
let loaded: Promise<void> | undefined
|
|
985
|
-
let runtime: RuntimeState | undefined
|
|
986
|
-
|
|
987
|
-
export async function init(input: {
|
|
988
|
-
api: HostPluginApi
|
|
989
|
-
config: TuiConfig.Resolved & TuiConfig.HostMetadata
|
|
990
|
-
runtime?: PluginRuntime
|
|
991
|
-
dispose?: () => void
|
|
992
|
-
disposeTimeoutMs?: number
|
|
993
|
-
}) {
|
|
994
|
-
const cwd = process.cwd()
|
|
995
|
-
if (loaded) {
|
|
996
|
-
if (dir !== cwd) {
|
|
997
|
-
throw new Error(`TuiPluginRuntime.init() called with a different working directory. expected=${dir} got=${cwd}`)
|
|
998
|
-
}
|
|
999
|
-
return loaded
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
dir = cwd
|
|
1003
|
-
loaded = load({ ...input, runtime: input.runtime ?? createPluginRuntime() })
|
|
1004
|
-
return loaded
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
export function list() {
|
|
1008
|
-
if (!runtime) return []
|
|
1009
|
-
return listPluginStatus(runtime)
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
export async function activatePlugin(id: string) {
|
|
1013
|
-
return activatePluginById(runtime, id, true)
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
export async function deactivatePlugin(id: string) {
|
|
1017
|
-
return deactivatePluginById(runtime, id, true)
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
export async function addPlugin(spec: string) {
|
|
1021
|
-
return addPluginBySpec(runtime, spec)
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
export async function installPlugin(spec: string, options?: { global?: boolean }) {
|
|
1025
|
-
return installPluginBySpec(runtime, spec, options?.global)
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
export async function dispose() {
|
|
1029
|
-
const task = loaded
|
|
1030
|
-
loaded = undefined
|
|
1031
|
-
dir = ""
|
|
1032
|
-
if (task) await task.catch((error) => fail("failed to finish loading tui plugins during disposal", { error }))
|
|
1033
|
-
const state = runtime
|
|
1034
|
-
runtime = undefined
|
|
1035
|
-
if (!state) return
|
|
1036
|
-
const queue = [...state.plugins].reverse()
|
|
1037
|
-
for (const plugin of queue) {
|
|
1038
|
-
await deactivatePluginEntry(state, plugin, false).catch((error) =>
|
|
1039
|
-
fail("failed to dispose tui plugin", { id: plugin.id, error }),
|
|
1040
|
-
)
|
|
1041
|
-
}
|
|
1042
|
-
try {
|
|
1043
|
-
state.dispose?.()
|
|
1044
|
-
} finally {
|
|
1045
|
-
state.slots.dispose()
|
|
1046
|
-
state.view.clear()
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
async function load(input: {
|
|
1051
|
-
api: Api
|
|
1052
|
-
config: TuiConfig.Resolved & TuiConfig.HostMetadata
|
|
1053
|
-
runtime: PluginRuntime
|
|
1054
|
-
dispose?: () => void
|
|
1055
|
-
disposeTimeoutMs?: number
|
|
1056
|
-
}) {
|
|
1057
|
-
const { api, config } = input
|
|
1058
|
-
const cwd = process.cwd()
|
|
1059
|
-
const slots = input.runtime.setupSlots(api)
|
|
1060
|
-
const next: RuntimeState = {
|
|
1061
|
-
directory: cwd,
|
|
1062
|
-
api,
|
|
1063
|
-
view: input.runtime,
|
|
1064
|
-
dispose: input.dispose,
|
|
1065
|
-
slots,
|
|
1066
|
-
plugins: [],
|
|
1067
|
-
plugins_by_id: new Map(),
|
|
1068
|
-
pending: new Map(),
|
|
1069
|
-
dispose_timeout_ms: input.disposeTimeoutMs ?? DISPOSE_TIMEOUT_MS,
|
|
1070
|
-
}
|
|
1071
|
-
runtime = next
|
|
1072
|
-
next.view.update({
|
|
1073
|
-
commands: {
|
|
1074
|
-
activate: activatePlugin,
|
|
1075
|
-
deactivate: deactivatePlugin,
|
|
1076
|
-
add: addPlugin,
|
|
1077
|
-
install: installPlugin,
|
|
1078
|
-
},
|
|
1079
|
-
status: listPluginStatus(next),
|
|
1080
|
-
})
|
|
1081
|
-
try {
|
|
1082
|
-
const flags = await Effect.runPromise(
|
|
1083
|
-
Effect.gen(function* () {
|
|
1084
|
-
return yield* RuntimeFlags.Service
|
|
1085
|
-
}).pipe(Effect.provide(RuntimeFlags.defaultLayer)),
|
|
1086
|
-
)
|
|
1087
|
-
const pluginOrigins = config.plugin_origins ?? (await TuiConfig.pluginOrigins())
|
|
1088
|
-
const records = Flag.OPENCODE_PURE ? [] : pluginOrigins
|
|
1089
|
-
if (Flag.OPENCODE_PURE && pluginOrigins.length) {
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
for (const item of internalTuiPlugins(flags)) {
|
|
1093
|
-
const entry = loadInternalPlugin(item)
|
|
1094
|
-
const meta = createMeta(entry.source, entry.spec, entry.target, undefined, entry.id)
|
|
1095
|
-
addPluginEntry(next, {
|
|
1096
|
-
id: entry.id,
|
|
1097
|
-
load: entry,
|
|
1098
|
-
meta,
|
|
1099
|
-
themes: {},
|
|
1100
|
-
plugin: entry.module.tui,
|
|
1101
|
-
enabled: item.enabled ?? true,
|
|
1102
|
-
})
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
const ready = await resolveExternalPlugins(records, () => TuiConfig.waitForDependencies())
|
|
1106
|
-
await addExternalPluginEntries(next, ready)
|
|
1107
|
-
|
|
1108
|
-
applyInitialPluginEnabledState(next, config)
|
|
1109
|
-
for (const plugin of next.plugins) {
|
|
1110
|
-
if (!plugin.enabled) continue
|
|
1111
|
-
// Keep plugin execution sequential for deterministic side effects:
|
|
1112
|
-
// command registration order affects keybind/command precedence,
|
|
1113
|
-
// route registration is last-wins when ids collide,
|
|
1114
|
-
// and hook chains rely on stable plugin ordering.
|
|
1115
|
-
await activatePluginEntry(next, plugin, false)
|
|
1116
|
-
}
|
|
1117
|
-
next.view.update({ status: listPluginStatus(next) })
|
|
1118
|
-
} catch (error) {
|
|
1119
|
-
fail("failed to load tui plugins", { directory: cwd, error })
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
export function createLegacyTuiPluginHost(): TuiPluginHost {
|
|
1124
|
-
return {
|
|
1125
|
-
start: init,
|
|
1126
|
-
dispose,
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
export * as TuiPluginRuntime from "./runtime"
|