@toolkit-cli/toolkode 1.3.7
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/AGENTS.md +69 -0
- package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
- package/Dockerfile +18 -0
- package/README.md +15 -0
- package/bin/opencode +179 -0
- package/bin/toolkode +17 -0
- package/bin/toolkode.cjs +190 -0
- package/bunfig.toml +7 -0
- package/drizzle.config.ts +10 -0
- package/git +0 -0
- package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
- package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
- package/migration/20260211171708_add_project_commands/migration.sql +1 -0
- package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
- package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
- package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
- package/migration/20260225215848_workspace/migration.sql +7 -0
- package/migration/20260225215848_workspace/snapshot.json +959 -0
- package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
- package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
- package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
- package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
- package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
- package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
- package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
- package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
- package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
- package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
- package/migration/20260323234822_events/migration.sql +13 -0
- package/migration/20260323234822_events/snapshot.json +1271 -0
- package/package.json +160 -0
- package/parsers-config.ts +290 -0
- package/script/build-node.ts +54 -0
- package/script/build.ts +276 -0
- package/script/check-migrations.ts +16 -0
- package/script/postinstall.mjs +131 -0
- package/script/publish.ts +181 -0
- package/script/schema.ts +63 -0
- package/script/seed-e2e.ts +60 -0
- package/script/upgrade-opentui.ts +64 -0
- package/specs/effect-migration.md +293 -0
- package/specs/tui-plugins.md +389 -0
- package/src/account/account.sql.ts +39 -0
- package/src/account/index.ts +397 -0
- package/src/account/repo.ts +163 -0
- package/src/account/schema.ts +91 -0
- package/src/acp/README.md +174 -0
- package/src/acp/agent.ts +1743 -0
- package/src/acp/session.ts +116 -0
- package/src/acp/types.ts +24 -0
- package/src/agent/agent.ts +418 -0
- package/src/agent/generate.txt +75 -0
- package/src/agent/prompt/compaction.txt +14 -0
- package/src/agent/prompt/explore.txt +18 -0
- package/src/agent/prompt/summary.txt +11 -0
- package/src/agent/prompt/title.txt +44 -0
- package/src/auth/index.ts +115 -0
- package/src/bun/index.ts +128 -0
- package/src/bun/registry.ts +50 -0
- package/src/bus/bus-event.ts +40 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +184 -0
- package/src/channel/index.ts +231 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/account.ts +257 -0
- package/src/cli/cmd/acp.ts +70 -0
- package/src/cli/cmd/agent.ts +245 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/db.ts +119 -0
- package/src/cli/cmd/debug/agent.ts +167 -0
- package/src/cli/cmd/debug/config.ts +16 -0
- package/src/cli/cmd/debug/file.ts +97 -0
- package/src/cli/cmd/debug/index.ts +48 -0
- package/src/cli/cmd/debug/lsp.ts +53 -0
- package/src/cli/cmd/debug/ripgrep.ts +87 -0
- package/src/cli/cmd/debug/scrap.ts +16 -0
- package/src/cli/cmd/debug/skill.ts +16 -0
- package/src/cli/cmd/debug/snapshot.ts +52 -0
- package/src/cli/cmd/export.ts +89 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +1646 -0
- package/src/cli/cmd/import.ts +207 -0
- package/src/cli/cmd/mcp.ts +754 -0
- package/src/cli/cmd/models.ts +78 -0
- package/src/cli/cmd/plug.ts +231 -0
- package/src/cli/cmd/pr.ts +127 -0
- package/src/cli/cmd/providers.ts +482 -0
- package/src/cli/cmd/run.ts +738 -0
- package/src/cli/cmd/serve.ts +42 -0
- package/src/cli/cmd/session.ts +159 -0
- package/src/cli/cmd/stats.ts +410 -0
- package/src/cli/cmd/tui/app.tsx +1255 -0
- package/src/cli/cmd/tui/attach.ts +88 -0
- package/src/cli/cmd/tui/component/border.tsx +21 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-command.tsx +171 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +264 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +334 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
- package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +168 -0
- package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/dialog-variant.tsx +29 -0
- package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +320 -0
- package/src/cli/cmd/tui/component/error-component.tsx +91 -0
- package/src/cli/cmd/tui/component/logo.tsx +86 -0
- package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +667 -0
- package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +1353 -0
- package/src/cli/cmd/tui/component/prompt/part.ts +16 -0
- package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
- package/src/cli/cmd/tui/component/spinner.tsx +24 -0
- package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
- package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
- package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
- package/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx +151 -0
- package/src/cli/cmd/tui/context/args.tsx +15 -0
- package/src/cli/cmd/tui/context/directory.ts +13 -0
- package/src/cli/cmd/tui/context/exit.tsx +60 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/keybind.tsx +105 -0
- package/src/cli/cmd/tui/context/kv.tsx +52 -0
- package/src/cli/cmd/tui/context/local.tsx +406 -0
- package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +52 -0
- package/src/cli/cmd/tui/context/sdk.tsx +128 -0
- package/src/cli/cmd/tui/context/sync.tsx +504 -0
- package/src/cli/cmd/tui/context/theme/amber.json +245 -0
- package/src/cli/cmd/tui/context/theme/amiga.json +245 -0
- package/src/cli/cmd/tui/context/theme/atari.json +245 -0
- package/src/cli/cmd/tui/context/theme/aura.json +69 -0
- package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
- package/src/cli/cmd/tui/context/theme/borland.json +245 -0
- package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
- package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
- package/src/cli/cmd/tui/context/theme/commodore.json +245 -0
- package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
- package/src/cli/cmd/tui/context/theme/dos-edit.json +245 -0
- package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
- package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
- package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
- package/src/cli/cmd/tui/context/theme/github.json +233 -0
- package/src/cli/cmd/tui/context/theme/gnu.json +245 -0
- package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
- package/src/cli/cmd/tui/context/theme/hacker.json +245 -0
- package/src/cli/cmd/tui/context/theme/irix.json +245 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
- package/src/cli/cmd/tui/context/theme/mac84.json +245 -0
- package/src/cli/cmd/tui/context/theme/material.json +235 -0
- package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
- package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
- package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
- package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
- package/src/cli/cmd/tui/context/theme/nord.json +223 -0
- package/src/cli/cmd/tui/context/theme/norton.json +245 -0
- package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
- package/src/cli/cmd/tui/context/theme/opencode.json +245 -0
- package/src/cli/cmd/tui/context/theme/orng.json +249 -0
- package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
- package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
- package/src/cli/cmd/tui/context/theme/pine.json +245 -0
- package/src/cli/cmd/tui/context/theme/retrowave.json +245 -0
- package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
- package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
- package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
- package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
- package/src/cli/cmd/tui/context/theme/toolkode.json +245 -0
- package/src/cli/cmd/tui/context/theme/tron.json +245 -0
- package/src/cli/cmd/tui/context/theme/ubuntu.json +245 -0
- package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
- package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
- package/src/cli/cmd/tui/context/theme/vt100.json +245 -0
- package/src/cli/cmd/tui/context/theme/xcode.json +245 -0
- package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
- package/src/cli/cmd/tui/context/theme.tsx +1288 -0
- package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
- package/src/cli/cmd/tui/event.ts +49 -0
- package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +152 -0
- package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/agents-panel.tsx +95 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/btw-panel.tsx +105 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/commands-panel.tsx +40 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +63 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/git-panel.tsx +36 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/loop-panel.tsx +124 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/session-panel.tsx +48 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
- package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +270 -0
- package/src/cli/cmd/tui/plugin/api.tsx +420 -0
- package/src/cli/cmd/tui/plugin/index.ts +3 -0
- package/src/cli/cmd/tui/plugin/internal.ts +37 -0
- package/src/cli/cmd/tui/plugin/runtime.ts +967 -0
- package/src/cli/cmd/tui/plugin/slots.tsx +61 -0
- package/src/cli/cmd/tui/routes/home.tsx +173 -0
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +2229 -0
- package/src/cli/cmd/tui/routes/session/permission.tsx +685 -0
- package/src/cli/cmd/tui/routes/session/question.tsx +467 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +72 -0
- package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +131 -0
- package/src/cli/cmd/tui/thread.ts +232 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +89 -0
- package/src/cli/cmd/tui/ui/dialog-export-options.tsx +208 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +106 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +402 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +192 -0
- package/src/cli/cmd/tui/ui/link.tsx +28 -0
- package/src/cli/cmd/tui/ui/spinner.ts +368 -0
- package/src/cli/cmd/tui/ui/toast.tsx +100 -0
- package/src/cli/cmd/tui/util/clipboard.ts +192 -0
- package/src/cli/cmd/tui/util/editor.ts +37 -0
- package/src/cli/cmd/tui/util/selection.ts +25 -0
- package/src/cli/cmd/tui/util/signal.ts +7 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/util/transcript.ts +98 -0
- package/src/cli/cmd/tui/win32.ts +129 -0
- package/src/cli/cmd/tui/worker.ts +204 -0
- package/src/cli/cmd/uninstall.ts +353 -0
- package/src/cli/cmd/upgrade.ts +73 -0
- package/src/cli/cmd/web.ts +81 -0
- package/src/cli/effect/prompt.ts +25 -0
- package/src/cli/error.ts +46 -0
- package/src/cli/logo.ts +7 -0
- package/src/cli/network.ts +60 -0
- package/src/cli/ui.ts +116 -0
- package/src/cli/upgrade.ts +31 -0
- package/src/command/index.ts +195 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +101 -0
- package/src/config/config.ts +1693 -0
- package/src/config/markdown.ts +99 -0
- package/src/config/migrate-tui-config.ts +155 -0
- package/src/config/paths.ts +174 -0
- package/src/config/tui-schema.ts +36 -0
- package/src/config/tui.ts +212 -0
- package/src/control-plane/adaptors/index.ts +20 -0
- package/src/control-plane/adaptors/worktree.ts +38 -0
- package/src/control-plane/schema.ts +17 -0
- package/src/control-plane/sse.ts +66 -0
- package/src/control-plane/types.ts +21 -0
- package/src/control-plane/workspace.sql.ts +17 -0
- package/src/control-plane/workspace.ts +154 -0
- package/src/cron/index.ts +241 -0
- package/src/cron/parse.ts +189 -0
- package/src/effect/cross-spawn-spawner.ts +479 -0
- package/src/effect/instance-registry.ts +12 -0
- package/src/effect/instance-state.ts +47 -0
- package/src/effect/run-service.ts +19 -0
- package/src/env/index.ts +28 -0
- package/src/file/ignore.ts +82 -0
- package/src/file/index.ts +693 -0
- package/src/file/protected.ts +59 -0
- package/src/file/ripgrep.ts +376 -0
- package/src/file/time.ts +128 -0
- package/src/file/watcher.ts +171 -0
- package/src/filesystem/index.ts +226 -0
- package/src/flag/flag.ts +157 -0
- package/src/format/formatter.ts +396 -0
- package/src/format/index.ts +199 -0
- package/src/global/index.ts +54 -0
- package/src/hooks/index.ts +302 -0
- package/src/id/id.ts +85 -0
- package/src/ide/index.ts +74 -0
- package/src/index.ts +243 -0
- package/src/installation/index.ts +363 -0
- package/src/lsp/client.ts +252 -0
- package/src/lsp/index.ts +558 -0
- package/src/lsp/language.ts +120 -0
- package/src/lsp/launch.ts +21 -0
- package/src/lsp/server.ts +2093 -0
- package/src/mcp/auth.ts +181 -0
- package/src/mcp/index.ts +926 -0
- package/src/mcp/oauth-callback.ts +215 -0
- package/src/mcp/oauth-provider.ts +185 -0
- package/src/node.ts +1 -0
- package/src/patch/index.ts +680 -0
- package/src/permission/arity.ts +163 -0
- package/src/permission/evaluate.ts +15 -0
- package/src/permission/index.ts +322 -0
- package/src/permission/schema.ts +17 -0
- package/src/plugin/codex.ts +628 -0
- package/src/plugin/copilot.ts +343 -0
- package/src/plugin/index.ts +331 -0
- package/src/plugin/install.ts +384 -0
- package/src/plugin/meta.ts +165 -0
- package/src/plugin/shared.ts +172 -0
- package/src/project/bootstrap.ts +31 -0
- package/src/project/instance.ts +167 -0
- package/src/project/project.sql.ts +16 -0
- package/src/project/project.ts +519 -0
- package/src/project/schema.ts +16 -0
- package/src/project/state.ts +70 -0
- package/src/project/vcs.ts +124 -0
- package/src/provider/auth.ts +252 -0
- package/src/provider/error.ts +197 -0
- package/src/provider/models.ts +138 -0
- package/src/provider/provider.ts +1593 -0
- package/src/provider/schema.ts +39 -0
- package/src/provider/sdk/copilot/README.md +5 -0
- package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
- package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
- package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +815 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +83 -0
- package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
- package/src/provider/sdk/copilot/index.ts +2 -0
- package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
- package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
- package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
- package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
- package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
- package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1769 -0
- package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
- package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
- package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
- package/src/provider/sdk/copilot/responses/tool/file-search.ts +127 -0
- package/src/provider/sdk/copilot/responses/tool/image-generation.ts +114 -0
- package/src/provider/sdk/copilot/responses/tool/local-shell.ts +64 -0
- package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
- package/src/provider/sdk/copilot/responses/tool/web-search.ts +102 -0
- package/src/provider/toolkit-manifest.ts +110 -0
- package/src/provider/transform.ts +1045 -0
- package/src/pty/index.ts +397 -0
- package/src/pty/schema.ts +17 -0
- package/src/question/index.ts +221 -0
- package/src/question/schema.ts +17 -0
- package/src/server/error.ts +36 -0
- package/src/server/event.ts +7 -0
- package/src/server/instance.ts +285 -0
- package/src/server/mdns.ts +60 -0
- package/src/server/middleware.ts +29 -0
- package/src/server/projectors.ts +28 -0
- package/src/server/router.ts +99 -0
- package/src/server/routes/config.ts +92 -0
- package/src/server/routes/event.ts +83 -0
- package/src/server/routes/experimental.ts +271 -0
- package/src/server/routes/file.ts +197 -0
- package/src/server/routes/global.ts +339 -0
- package/src/server/routes/mcp.ts +225 -0
- package/src/server/routes/permission.ts +69 -0
- package/src/server/routes/project.ts +118 -0
- package/src/server/routes/provider.ts +171 -0
- package/src/server/routes/pty.ts +211 -0
- package/src/server/routes/question.ts +99 -0
- package/src/server/routes/session.ts +1031 -0
- package/src/server/routes/tui.ts +379 -0
- package/src/server/routes/workspace.ts +94 -0
- package/src/server/server.ts +312 -0
- package/src/session/compaction.ts +424 -0
- package/src/session/index.ts +882 -0
- package/src/session/instruction.ts +321 -0
- package/src/session/llm.ts +341 -0
- package/src/session/message-v2.ts +1030 -0
- package/src/session/message.ts +191 -0
- package/src/session/overflow.ts +22 -0
- package/src/session/processor.ts +554 -0
- package/src/session/projectors.ts +135 -0
- package/src/session/prompt/anthropic.txt +105 -0
- package/src/session/prompt/beast.txt +147 -0
- package/src/session/prompt/build-switch.txt +5 -0
- package/src/session/prompt/codex.txt +79 -0
- package/src/session/prompt/copilot-gpt-5.txt +143 -0
- package/src/session/prompt/default.txt +108 -0
- package/src/session/prompt/gemini.txt +155 -0
- package/src/session/prompt/gpt.txt +107 -0
- package/src/session/prompt/max-steps.txt +16 -0
- package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
- package/src/session/prompt/plan.txt +26 -0
- package/src/session/prompt/trinity.txt +97 -0
- package/src/session/prompt.ts +2058 -0
- package/src/session/retry.ts +106 -0
- package/src/session/revert.ts +138 -0
- package/src/session/schema.ts +38 -0
- package/src/session/session.sql.ts +103 -0
- package/src/session/status.ts +102 -0
- package/src/session/summary.ts +170 -0
- package/src/session/system.ts +74 -0
- package/src/session/todo.ts +57 -0
- package/src/share/share-next.ts +288 -0
- package/src/share/share.sql.ts +13 -0
- package/src/shell/shell.ts +73 -0
- package/src/skill/discovery.ts +116 -0
- package/src/skill/index.ts +284 -0
- package/src/skills-marketplace/index.ts +305 -0
- package/src/snapshot/index.ts +489 -0
- package/src/sql.d.ts +4 -0
- package/src/storage/db.bun.ts +8 -0
- package/src/storage/db.node.ts +8 -0
- package/src/storage/db.ts +177 -0
- package/src/storage/json-migration.ts +425 -0
- package/src/storage/schema.sql.ts +10 -0
- package/src/storage/schema.ts +5 -0
- package/src/storage/storage.ts +217 -0
- package/src/sync/README.md +179 -0
- package/src/sync/event.sql.ts +16 -0
- package/src/sync/index.ts +263 -0
- package/src/sync/schema.ts +14 -0
- package/src/team/index.ts +428 -0
- package/src/tool/apply_patch.ts +281 -0
- package/src/tool/apply_patch.txt +33 -0
- package/src/tool/bash.ts +271 -0
- package/src/tool/bash.txt +115 -0
- package/src/tool/batch.ts +183 -0
- package/src/tool/batch.txt +24 -0
- package/src/tool/codesearch.ts +132 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/cron-create.ts +54 -0
- package/src/tool/cron-create.txt +16 -0
- package/src/tool/cron-delete.ts +29 -0
- package/src/tool/cron-delete.txt +1 -0
- package/src/tool/cron-list.ts +41 -0
- package/src/tool/cron-list.txt +1 -0
- package/src/tool/edit.ts +667 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/external-directory.ts +32 -0
- package/src/tool/glob.ts +78 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +156 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +121 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp.ts +97 -0
- package/src/tool/lsp.txt +19 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/plan-enter.txt +14 -0
- package/src/tool/plan-exit.txt +13 -0
- package/src/tool/plan.ts +131 -0
- package/src/tool/question.ts +33 -0
- package/src/tool/question.txt +10 -0
- package/src/tool/read.ts +293 -0
- package/src/tool/read.txt +14 -0
- package/src/tool/registry.ts +232 -0
- package/src/tool/schema.ts +17 -0
- package/src/tool/send-message.ts +59 -0
- package/src/tool/send-message.txt +7 -0
- package/src/tool/skill.ts +105 -0
- package/src/tool/task.ts +230 -0
- package/src/tool/task.txt +62 -0
- package/src/tool/team.ts +235 -0
- package/src/tool/team.txt +22 -0
- package/src/tool/todo.ts +31 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +90 -0
- package/src/tool/truncate.ts +144 -0
- package/src/tool/truncation-dir.ts +4 -0
- package/src/tool/webfetch.ts +206 -0
- package/src/tool/webfetch.txt +13 -0
- package/src/tool/websearch.ts +150 -0
- package/src/tool/websearch.txt +14 -0
- package/src/tool/write.ts +84 -0
- package/src/tool/write.txt +8 -0
- package/src/util/abort.ts +35 -0
- package/src/util/archive.ts +17 -0
- package/src/util/color.ts +19 -0
- package/src/util/context.ts +25 -0
- package/src/util/data-url.ts +9 -0
- package/src/util/defer.ts +12 -0
- package/src/util/effect-http-client.ts +11 -0
- package/src/util/effect-zod.ts +98 -0
- package/src/util/error.ts +77 -0
- package/src/util/filesystem.ts +203 -0
- package/src/util/flock.ts +333 -0
- package/src/util/fn.ts +21 -0
- package/src/util/format.ts +20 -0
- package/src/util/git.ts +35 -0
- package/src/util/glob.ts +34 -0
- package/src/util/hash.ts +7 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +103 -0
- package/src/util/lazy.ts +23 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +182 -0
- package/src/util/network.ts +9 -0
- package/src/util/process.ts +172 -0
- package/src/util/queue.ts +32 -0
- package/src/util/record.ts +3 -0
- package/src/util/rpc.ts +66 -0
- package/src/util/schema.ts +53 -0
- package/src/util/scrap.ts +10 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +14 -0
- package/src/util/token.ts +7 -0
- package/src/util/update-schema.ts +13 -0
- package/src/util/which.ts +14 -0
- package/src/util/wildcard.ts +59 -0
- package/src/worktree/index.ts +638 -0
- package/sst-env.d.ts +10 -0
- package/test/AGENTS.md +81 -0
- package/test/account/repo.test.ts +326 -0
- package/test/account/service.test.ts +282 -0
- package/test/acp/agent-interface.test.ts +51 -0
- package/test/acp/event-subscription.test.ts +685 -0
- package/test/agent/agent.test.ts +717 -0
- package/test/auth/auth.test.ts +58 -0
- package/test/bun.test.ts +53 -0
- package/test/bus/bus-effect.test.ts +164 -0
- package/test/bus/bus-integration.test.ts +87 -0
- package/test/bus/bus.test.ts +219 -0
- package/test/cli/account.test.ts +26 -0
- package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
- package/test/cli/github-action.test.ts +198 -0
- package/test/cli/github-remote.test.ts +80 -0
- package/test/cli/import.test.ts +54 -0
- package/test/cli/plugin-auth-picker.test.ts +120 -0
- package/test/cli/tui/keybind-plugin.test.ts +90 -0
- package/test/cli/tui/plugin-add.test.ts +61 -0
- package/test/cli/tui/plugin-install.test.ts +95 -0
- package/test/cli/tui/plugin-lifecycle.test.ts +225 -0
- package/test/cli/tui/plugin-loader-entrypoint.test.ts +189 -0
- package/test/cli/tui/plugin-loader-pure.test.ts +71 -0
- package/test/cli/tui/plugin-loader.test.ts +563 -0
- package/test/cli/tui/plugin-toggle.test.ts +157 -0
- package/test/cli/tui/theme-store.test.ts +51 -0
- package/test/cli/tui/thread.test.ts +128 -0
- package/test/cli/tui/transcript.test.ts +322 -0
- package/test/config/agent-color.test.ts +71 -0
- package/test/config/config.test.ts +2187 -0
- package/test/config/fixtures/empty-frontmatter.md +4 -0
- package/test/config/fixtures/frontmatter.md +28 -0
- package/test/config/fixtures/markdown-header.md +11 -0
- package/test/config/fixtures/no-frontmatter.md +1 -0
- package/test/config/fixtures/weird-model-id.md +13 -0
- package/test/config/markdown.test.ts +228 -0
- package/test/config/tui.test.ts +667 -0
- package/test/control-plane/sse.test.ts +56 -0
- package/test/effect/cross-spawn-spawner.test.ts +402 -0
- package/test/effect/instance-state.test.ts +384 -0
- package/test/effect/run-service.test.ts +46 -0
- package/test/file/fsmonitor.test.ts +62 -0
- package/test/file/ignore.test.ts +10 -0
- package/test/file/index.test.ts +946 -0
- package/test/file/path-traversal.test.ts +198 -0
- package/test/file/ripgrep.test.ts +54 -0
- package/test/file/time.test.ts +354 -0
- package/test/file/watcher.test.ts +247 -0
- package/test/filesystem/filesystem.test.ts +319 -0
- package/test/fixture/db.ts +11 -0
- package/test/fixture/fixture.test.ts +26 -0
- package/test/fixture/fixture.ts +141 -0
- package/test/fixture/flock-worker.ts +72 -0
- package/test/fixture/lsp/fake-lsp-server.js +77 -0
- package/test/fixture/plug-worker.ts +93 -0
- package/test/fixture/plugin-meta-worker.ts +26 -0
- package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
- package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
- package/test/fixture/skills/cloudflare/SKILL.md +211 -0
- package/test/fixture/skills/index.json +6 -0
- package/test/fixture/tui-plugin.ts +335 -0
- package/test/fixture/tui-runtime.ts +34 -0
- package/test/format/format.test.ts +179 -0
- package/test/ide/ide.test.ts +82 -0
- package/test/installation/installation.test.ts +151 -0
- package/test/keybind.test.ts +421 -0
- package/test/lib/effect.ts +37 -0
- package/test/lib/filesystem.ts +10 -0
- package/test/lsp/client.test.ts +95 -0
- package/test/lsp/index.test.ts +55 -0
- package/test/lsp/launch.test.ts +22 -0
- package/test/lsp/lifecycle.test.ts +147 -0
- package/test/mcp/headers.test.ts +153 -0
- package/test/mcp/lifecycle.test.ts +750 -0
- package/test/mcp/oauth-auto-connect.test.ts +199 -0
- package/test/mcp/oauth-browser.test.ts +249 -0
- package/test/memory/abort-leak.test.ts +137 -0
- package/test/patch/patch.test.ts +348 -0
- package/test/permission/arity.test.ts +33 -0
- package/test/permission/next.test.ts +1148 -0
- package/test/permission-task.test.ts +323 -0
- package/test/plugin/auth-override.test.ts +74 -0
- package/test/plugin/codex.test.ts +123 -0
- package/test/plugin/install-concurrency.test.ts +134 -0
- package/test/plugin/install.test.ts +504 -0
- package/test/plugin/loader-shared.test.ts +625 -0
- package/test/plugin/meta.test.ts +137 -0
- package/test/plugin/trigger.test.ts +111 -0
- package/test/preload.ts +90 -0
- package/test/project/migrate-global.test.ts +140 -0
- package/test/project/project.test.ts +459 -0
- package/test/project/state.test.ts +115 -0
- package/test/project/vcs.test.ts +116 -0
- package/test/project/worktree-remove.test.ts +96 -0
- package/test/project/worktree.test.ts +173 -0
- package/test/provider/amazon-bedrock.test.ts +447 -0
- package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
- package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
- package/test/provider/gitlab-duo.test.ts +412 -0
- package/test/provider/provider.test.ts +2284 -0
- package/test/provider/transform.test.ts +2758 -0
- package/test/pty/pty-output-isolation.test.ts +141 -0
- package/test/pty/pty-session.test.ts +92 -0
- package/test/question/question.test.ts +453 -0
- package/test/server/global-session-list.test.ts +89 -0
- package/test/server/project-init-git.test.ts +121 -0
- package/test/server/session-list.test.ts +90 -0
- package/test/server/session-messages.test.ts +132 -0
- package/test/server/session-select.test.ts +78 -0
- package/test/session/compaction.test.ts +1094 -0
- package/test/session/instruction.test.ts +170 -0
- package/test/session/llm.test.ts +882 -0
- package/test/session/message-v2.test.ts +957 -0
- package/test/session/messages-pagination.test.ts +115 -0
- package/test/session/processor-effect.test.ts +838 -0
- package/test/session/prompt.test.ts +518 -0
- package/test/session/retry.test.ts +232 -0
- package/test/session/revert-compact.test.ts +286 -0
- package/test/session/session.test.ts +142 -0
- package/test/session/structured-output-integration.test.ts +233 -0
- package/test/session/structured-output.test.ts +391 -0
- package/test/session/system.test.ts +59 -0
- package/test/share/share-next.test.ts +76 -0
- package/test/skill/discovery.test.ts +116 -0
- package/test/skill/skill.test.ts +392 -0
- package/test/snapshot/snapshot.test.ts +1235 -0
- package/test/storage/db.test.ts +14 -0
- package/test/storage/json-migration.test.ts +849 -0
- package/test/sync/index.test.ts +191 -0
- package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
- package/test/tool/apply_patch.test.ts +567 -0
- package/test/tool/bash.test.ts +403 -0
- package/test/tool/edit.test.ts +681 -0
- package/test/tool/external-directory.test.ts +128 -0
- package/test/tool/fixtures/large-image.png +0 -0
- package/test/tool/fixtures/models-api.json +38413 -0
- package/test/tool/grep.test.ts +111 -0
- package/test/tool/question.test.ts +108 -0
- package/test/tool/read.test.ts +509 -0
- package/test/tool/registry.test.ts +126 -0
- package/test/tool/skill.test.ts +167 -0
- package/test/tool/task.test.ts +49 -0
- package/test/tool/truncation.test.ts +161 -0
- package/test/tool/webfetch.test.ts +101 -0
- package/test/tool/write.test.ts +353 -0
- package/test/util/data-url.test.ts +14 -0
- package/test/util/effect-zod.test.ts +61 -0
- package/test/util/error.test.ts +38 -0
- package/test/util/filesystem.test.ts +558 -0
- package/test/util/flock.test.ts +383 -0
- package/test/util/format.test.ts +59 -0
- package/test/util/glob.test.ts +164 -0
- package/test/util/iife.test.ts +36 -0
- package/test/util/lazy.test.ts +50 -0
- package/test/util/lock.test.ts +72 -0
- package/test/util/module.test.ts +59 -0
- package/test/util/process.test.ts +128 -0
- package/test/util/timeout.test.ts +21 -0
- package/test/util/which.test.ts +100 -0
- package/test/util/wildcard.test.ts +90 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { afterEach, describe, test, expect } from "bun:test"
|
|
2
|
+
import { Permission } from "../src/permission"
|
|
3
|
+
import { Config } from "../src/config/config"
|
|
4
|
+
import { Instance } from "../src/project/instance"
|
|
5
|
+
import { tmpdir } from "./fixture/fixture"
|
|
6
|
+
|
|
7
|
+
afterEach(async () => {
|
|
8
|
+
await Instance.disposeAll()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
describe("Permission.evaluate for permission.task", () => {
|
|
12
|
+
const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): Permission.Ruleset =>
|
|
13
|
+
Object.entries(rules).map(([pattern, action]) => ({
|
|
14
|
+
permission: "task",
|
|
15
|
+
pattern,
|
|
16
|
+
action,
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
test("returns ask when no match (default)", () => {
|
|
20
|
+
expect(Permission.evaluate("task", "code-reviewer", []).action).toBe("ask")
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test("returns deny for explicit deny", () => {
|
|
24
|
+
const ruleset = createRuleset({ "code-reviewer": "deny" })
|
|
25
|
+
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test("returns allow for explicit allow", () => {
|
|
29
|
+
const ruleset = createRuleset({ "code-reviewer": "allow" })
|
|
30
|
+
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("allow")
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test("returns ask for explicit ask", () => {
|
|
34
|
+
const ruleset = createRuleset({ "code-reviewer": "ask" })
|
|
35
|
+
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("ask")
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test("matches wildcard patterns with deny", () => {
|
|
39
|
+
const ruleset = createRuleset({ "orchestrator-*": "deny" })
|
|
40
|
+
expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("deny")
|
|
41
|
+
expect(Permission.evaluate("task", "orchestrator-slow", ruleset).action).toBe("deny")
|
|
42
|
+
expect(Permission.evaluate("task", "general", ruleset).action).toBe("ask")
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test("matches wildcard patterns with allow", () => {
|
|
46
|
+
const ruleset = createRuleset({ "orchestrator-*": "allow" })
|
|
47
|
+
expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow")
|
|
48
|
+
expect(Permission.evaluate("task", "orchestrator-slow", ruleset).action).toBe("allow")
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test("matches wildcard patterns with ask", () => {
|
|
52
|
+
const ruleset = createRuleset({ "orchestrator-*": "ask" })
|
|
53
|
+
expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("ask")
|
|
54
|
+
const globalRuleset = createRuleset({ "*": "ask" })
|
|
55
|
+
expect(Permission.evaluate("task", "code-reviewer", globalRuleset).action).toBe("ask")
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("later rules take precedence (last match wins)", () => {
|
|
59
|
+
const ruleset = createRuleset({
|
|
60
|
+
"orchestrator-*": "deny",
|
|
61
|
+
"orchestrator-fast": "allow",
|
|
62
|
+
})
|
|
63
|
+
expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow")
|
|
64
|
+
expect(Permission.evaluate("task", "orchestrator-slow", ruleset).action).toBe("deny")
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test("matches global wildcard", () => {
|
|
68
|
+
expect(Permission.evaluate("task", "any-agent", createRuleset({ "*": "allow" })).action).toBe("allow")
|
|
69
|
+
expect(Permission.evaluate("task", "any-agent", createRuleset({ "*": "deny" })).action).toBe("deny")
|
|
70
|
+
expect(Permission.evaluate("task", "any-agent", createRuleset({ "*": "ask" })).action).toBe("ask")
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe("Permission.disabled for task tool", () => {
|
|
75
|
+
// Note: The `disabled` function checks if a TOOL should be completely removed from the tool list.
|
|
76
|
+
// It only disables a tool when there's a rule with `pattern: "*"` and `action: "deny"`.
|
|
77
|
+
// It does NOT evaluate complex subagent patterns - those are handled at runtime by `evaluate`.
|
|
78
|
+
const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): Permission.Ruleset =>
|
|
79
|
+
Object.entries(rules).map(([pattern, action]) => ({
|
|
80
|
+
permission: "task",
|
|
81
|
+
pattern,
|
|
82
|
+
action,
|
|
83
|
+
}))
|
|
84
|
+
|
|
85
|
+
test("task tool is disabled when global deny pattern exists (even with specific allows)", () => {
|
|
86
|
+
// When "*": "deny" exists, the task tool is disabled because the disabled() function
|
|
87
|
+
// only checks for wildcard deny patterns - it doesn't consider that specific subagents might be allowed
|
|
88
|
+
const ruleset = createRuleset({
|
|
89
|
+
"orchestrator-*": "allow",
|
|
90
|
+
"*": "deny",
|
|
91
|
+
})
|
|
92
|
+
const disabled = Permission.disabled(["task", "bash", "read"], ruleset)
|
|
93
|
+
// The task tool IS disabled because there's a pattern: "*" with action: "deny"
|
|
94
|
+
expect(disabled.has("task")).toBe(true)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test("task tool is disabled when global deny pattern exists (even with ask overrides)", () => {
|
|
98
|
+
const ruleset = createRuleset({
|
|
99
|
+
"orchestrator-*": "ask",
|
|
100
|
+
"*": "deny",
|
|
101
|
+
})
|
|
102
|
+
const disabled = Permission.disabled(["task"], ruleset)
|
|
103
|
+
// The task tool IS disabled because there's a pattern: "*" with action: "deny"
|
|
104
|
+
expect(disabled.has("task")).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test("task tool is disabled when global deny pattern exists", () => {
|
|
108
|
+
const ruleset = createRuleset({ "*": "deny" })
|
|
109
|
+
const disabled = Permission.disabled(["task"], ruleset)
|
|
110
|
+
expect(disabled.has("task")).toBe(true)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test("task tool is NOT disabled when only specific patterns are denied (no wildcard)", () => {
|
|
114
|
+
// The disabled() function only disables tools when pattern: "*" && action: "deny"
|
|
115
|
+
// Specific subagent denies don't disable the task tool - those are handled at runtime
|
|
116
|
+
const ruleset = createRuleset({
|
|
117
|
+
"orchestrator-*": "deny",
|
|
118
|
+
general: "deny",
|
|
119
|
+
})
|
|
120
|
+
const disabled = Permission.disabled(["task"], ruleset)
|
|
121
|
+
// The task tool is NOT disabled because no rule has pattern: "*" with action: "deny"
|
|
122
|
+
expect(disabled.has("task")).toBe(false)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test("task tool is enabled when no task rules exist (default ask)", () => {
|
|
126
|
+
const disabled = Permission.disabled(["task"], [])
|
|
127
|
+
expect(disabled.has("task")).toBe(false)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test("task tool is NOT disabled when last wildcard pattern is allow", () => {
|
|
131
|
+
// Last matching rule wins - if wildcard allow comes after wildcard deny, tool is enabled
|
|
132
|
+
const ruleset = createRuleset({
|
|
133
|
+
"*": "deny",
|
|
134
|
+
"orchestrator-coder": "allow",
|
|
135
|
+
})
|
|
136
|
+
const disabled = Permission.disabled(["task"], ruleset)
|
|
137
|
+
// The disabled() function uses findLast and checks if the last matching rule
|
|
138
|
+
// has pattern: "*" and action: "deny". In this case, the last rule matching
|
|
139
|
+
// "task" permission has pattern "orchestrator-coder", not "*", so not disabled
|
|
140
|
+
expect(disabled.has("task")).toBe(false)
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
// Integration tests that load permissions from real config files
|
|
145
|
+
describe("permission.task with real config files", () => {
|
|
146
|
+
test("loads task permissions from opencode.json config", async () => {
|
|
147
|
+
await using tmp = await tmpdir({
|
|
148
|
+
git: true,
|
|
149
|
+
config: {
|
|
150
|
+
permission: {
|
|
151
|
+
task: {
|
|
152
|
+
"*": "allow",
|
|
153
|
+
"code-reviewer": "deny",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
await Instance.provide({
|
|
159
|
+
directory: tmp.path,
|
|
160
|
+
fn: async () => {
|
|
161
|
+
const config = await Config.get()
|
|
162
|
+
const ruleset = Permission.fromConfig(config.permission ?? {})
|
|
163
|
+
// general and orchestrator-fast should be allowed, code-reviewer denied
|
|
164
|
+
expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow")
|
|
165
|
+
expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow")
|
|
166
|
+
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test("loads task permissions with wildcard patterns from config", async () => {
|
|
172
|
+
await using tmp = await tmpdir({
|
|
173
|
+
git: true,
|
|
174
|
+
config: {
|
|
175
|
+
permission: {
|
|
176
|
+
task: {
|
|
177
|
+
"*": "ask",
|
|
178
|
+
"orchestrator-*": "deny",
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
})
|
|
183
|
+
await Instance.provide({
|
|
184
|
+
directory: tmp.path,
|
|
185
|
+
fn: async () => {
|
|
186
|
+
const config = await Config.get()
|
|
187
|
+
const ruleset = Permission.fromConfig(config.permission ?? {})
|
|
188
|
+
// general and code-reviewer should be ask, orchestrator-* denied
|
|
189
|
+
expect(Permission.evaluate("task", "general", ruleset).action).toBe("ask")
|
|
190
|
+
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("ask")
|
|
191
|
+
expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("deny")
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test("evaluate respects task permission from config", async () => {
|
|
197
|
+
await using tmp = await tmpdir({
|
|
198
|
+
git: true,
|
|
199
|
+
config: {
|
|
200
|
+
permission: {
|
|
201
|
+
task: {
|
|
202
|
+
general: "allow",
|
|
203
|
+
"code-reviewer": "deny",
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
await Instance.provide({
|
|
209
|
+
directory: tmp.path,
|
|
210
|
+
fn: async () => {
|
|
211
|
+
const config = await Config.get()
|
|
212
|
+
const ruleset = Permission.fromConfig(config.permission ?? {})
|
|
213
|
+
expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow")
|
|
214
|
+
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
|
|
215
|
+
// Unspecified agents default to "ask"
|
|
216
|
+
expect(Permission.evaluate("task", "unknown-agent", ruleset).action).toBe("ask")
|
|
217
|
+
},
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test("mixed permission config with task and other tools", async () => {
|
|
222
|
+
await using tmp = await tmpdir({
|
|
223
|
+
git: true,
|
|
224
|
+
config: {
|
|
225
|
+
permission: {
|
|
226
|
+
bash: "allow",
|
|
227
|
+
edit: "ask",
|
|
228
|
+
task: {
|
|
229
|
+
"*": "deny",
|
|
230
|
+
general: "allow",
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
})
|
|
235
|
+
await Instance.provide({
|
|
236
|
+
directory: tmp.path,
|
|
237
|
+
fn: async () => {
|
|
238
|
+
const config = await Config.get()
|
|
239
|
+
const ruleset = Permission.fromConfig(config.permission ?? {})
|
|
240
|
+
|
|
241
|
+
// Verify task permissions
|
|
242
|
+
expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow")
|
|
243
|
+
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
|
|
244
|
+
|
|
245
|
+
// Verify other tool permissions
|
|
246
|
+
expect(Permission.evaluate("bash", "*", ruleset).action).toBe("allow")
|
|
247
|
+
expect(Permission.evaluate("edit", "*", ruleset).action).toBe("ask")
|
|
248
|
+
|
|
249
|
+
// Verify disabled tools
|
|
250
|
+
const disabled = Permission.disabled(["bash", "edit", "task"], ruleset)
|
|
251
|
+
expect(disabled.has("bash")).toBe(false)
|
|
252
|
+
expect(disabled.has("edit")).toBe(false)
|
|
253
|
+
// task is NOT disabled because disabled() uses findLast, and the last rule
|
|
254
|
+
// matching "task" permission is {pattern: "general", action: "allow"}, not pattern: "*"
|
|
255
|
+
expect(disabled.has("task")).toBe(false)
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
test("task tool disabled when global deny comes last in config", async () => {
|
|
261
|
+
await using tmp = await tmpdir({
|
|
262
|
+
git: true,
|
|
263
|
+
config: {
|
|
264
|
+
permission: {
|
|
265
|
+
task: {
|
|
266
|
+
general: "allow",
|
|
267
|
+
"code-reviewer": "allow",
|
|
268
|
+
"*": "deny",
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
})
|
|
273
|
+
await Instance.provide({
|
|
274
|
+
directory: tmp.path,
|
|
275
|
+
fn: async () => {
|
|
276
|
+
const config = await Config.get()
|
|
277
|
+
const ruleset = Permission.fromConfig(config.permission ?? {})
|
|
278
|
+
|
|
279
|
+
// Last matching rule wins - "*" deny is last, so all agents are denied
|
|
280
|
+
expect(Permission.evaluate("task", "general", ruleset).action).toBe("deny")
|
|
281
|
+
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
|
|
282
|
+
expect(Permission.evaluate("task", "unknown", ruleset).action).toBe("deny")
|
|
283
|
+
|
|
284
|
+
// Since "*": "deny" is the last rule, disabled() finds it with findLast
|
|
285
|
+
// and sees pattern: "*" with action: "deny", so task is disabled
|
|
286
|
+
const disabled = Permission.disabled(["task"], ruleset)
|
|
287
|
+
expect(disabled.has("task")).toBe(true)
|
|
288
|
+
},
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
test("task tool NOT disabled when specific allow comes last in config", async () => {
|
|
293
|
+
await using tmp = await tmpdir({
|
|
294
|
+
git: true,
|
|
295
|
+
config: {
|
|
296
|
+
permission: {
|
|
297
|
+
task: {
|
|
298
|
+
"*": "deny",
|
|
299
|
+
general: "allow",
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
})
|
|
304
|
+
await Instance.provide({
|
|
305
|
+
directory: tmp.path,
|
|
306
|
+
fn: async () => {
|
|
307
|
+
const config = await Config.get()
|
|
308
|
+
const ruleset = Permission.fromConfig(config.permission ?? {})
|
|
309
|
+
|
|
310
|
+
// Evaluate uses findLast - "general" allow comes after "*" deny
|
|
311
|
+
expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow")
|
|
312
|
+
// Other agents still denied by the earlier "*" deny
|
|
313
|
+
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
|
|
314
|
+
|
|
315
|
+
// disabled() uses findLast and checks if the last rule has pattern: "*" with action: "deny"
|
|
316
|
+
// In this case, the last rule is {pattern: "general", action: "allow"}, not pattern: "*"
|
|
317
|
+
// So the task tool is NOT disabled (even though most subagents are denied)
|
|
318
|
+
const disabled = Permission.disabled(["task"], ruleset)
|
|
319
|
+
expect(disabled.has("task")).toBe(false)
|
|
320
|
+
},
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import fs from "fs/promises"
|
|
4
|
+
import { tmpdir } from "../fixture/fixture"
|
|
5
|
+
import { Instance } from "../../src/project/instance"
|
|
6
|
+
import { ProviderAuth } from "../../src/provider/auth"
|
|
7
|
+
import { ProviderID } from "../../src/provider/schema"
|
|
8
|
+
|
|
9
|
+
describe("plugin.auth-override", () => {
|
|
10
|
+
test("user plugin overrides built-in github-copilot auth", async () => {
|
|
11
|
+
await using tmp = await tmpdir({
|
|
12
|
+
init: async (dir) => {
|
|
13
|
+
const pluginDir = path.join(dir, ".opencode", "plugin")
|
|
14
|
+
await fs.mkdir(pluginDir, { recursive: true })
|
|
15
|
+
|
|
16
|
+
await Bun.write(
|
|
17
|
+
path.join(pluginDir, "custom-copilot-auth.ts"),
|
|
18
|
+
[
|
|
19
|
+
"export default {",
|
|
20
|
+
' id: "demo.custom-copilot-auth",',
|
|
21
|
+
" server: async () => ({",
|
|
22
|
+
" auth: {",
|
|
23
|
+
' provider: "github-copilot",',
|
|
24
|
+
" methods: [",
|
|
25
|
+
' { type: "api", label: "Test Override Auth" },',
|
|
26
|
+
" ],",
|
|
27
|
+
" loader: async () => ({ access: 'test-token' }),",
|
|
28
|
+
" },",
|
|
29
|
+
" }),",
|
|
30
|
+
"}",
|
|
31
|
+
"",
|
|
32
|
+
].join("\n"),
|
|
33
|
+
)
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
await using plain = await tmpdir()
|
|
38
|
+
|
|
39
|
+
const methods = await Instance.provide({
|
|
40
|
+
directory: tmp.path,
|
|
41
|
+
fn: async () => {
|
|
42
|
+
return ProviderAuth.methods()
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const plainMethods = await Instance.provide({
|
|
47
|
+
directory: plain.path,
|
|
48
|
+
fn: async () => {
|
|
49
|
+
return ProviderAuth.methods()
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const copilot = methods[ProviderID.make("github-copilot")]
|
|
54
|
+
expect(copilot).toBeDefined()
|
|
55
|
+
expect(copilot.length).toBe(1)
|
|
56
|
+
expect(copilot[0].label).toBe("Test Override Auth")
|
|
57
|
+
expect(plainMethods[ProviderID.make("github-copilot")][0].label).not.toBe("Test Override Auth")
|
|
58
|
+
}, 30000) // Increased timeout for plugin installation
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const file = path.join(import.meta.dir, "../../src/plugin/index.ts")
|
|
62
|
+
|
|
63
|
+
describe("plugin.config-hook-error-isolation", () => {
|
|
64
|
+
test("config hooks are individually error-isolated in the layer factory", async () => {
|
|
65
|
+
const src = await Bun.file(file).text()
|
|
66
|
+
|
|
67
|
+
// Each hook's config call is wrapped in Effect.tryPromise with error logging + Effect.ignore
|
|
68
|
+
expect(src).toContain("plugin config hook failed")
|
|
69
|
+
|
|
70
|
+
const pattern =
|
|
71
|
+
/for\s*\(const hook of hooks\)\s*\{[\s\S]*?Effect\.tryPromise[\s\S]*?\.config\?\.\([\s\S]*?plugin config hook failed[\s\S]*?Effect\.ignore/
|
|
72
|
+
expect(pattern.test(src)).toBe(true)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import {
|
|
3
|
+
parseJwtClaims,
|
|
4
|
+
extractAccountIdFromClaims,
|
|
5
|
+
extractAccountId,
|
|
6
|
+
type IdTokenClaims,
|
|
7
|
+
} from "../../src/plugin/codex"
|
|
8
|
+
|
|
9
|
+
function createTestJwt(payload: object): string {
|
|
10
|
+
const header = Buffer.from(JSON.stringify({ alg: "none" })).toString("base64url")
|
|
11
|
+
const body = Buffer.from(JSON.stringify(payload)).toString("base64url")
|
|
12
|
+
return `${header}.${body}.sig`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("plugin.codex", () => {
|
|
16
|
+
describe("parseJwtClaims", () => {
|
|
17
|
+
test("parses valid JWT with claims", () => {
|
|
18
|
+
const payload = { email: "test@example.com", chatgpt_account_id: "acc-123" }
|
|
19
|
+
const jwt = createTestJwt(payload)
|
|
20
|
+
const claims = parseJwtClaims(jwt)
|
|
21
|
+
expect(claims).toEqual(payload)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test("returns undefined for JWT with less than 3 parts", () => {
|
|
25
|
+
expect(parseJwtClaims("invalid")).toBeUndefined()
|
|
26
|
+
expect(parseJwtClaims("only.two")).toBeUndefined()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("returns undefined for invalid base64", () => {
|
|
30
|
+
expect(parseJwtClaims("a.!!!invalid!!!.b")).toBeUndefined()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test("returns undefined for invalid JSON payload", () => {
|
|
34
|
+
const header = Buffer.from("{}").toString("base64url")
|
|
35
|
+
const invalidJson = Buffer.from("not json").toString("base64url")
|
|
36
|
+
expect(parseJwtClaims(`${header}.${invalidJson}.sig`)).toBeUndefined()
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe("extractAccountIdFromClaims", () => {
|
|
41
|
+
test("extracts chatgpt_account_id from root", () => {
|
|
42
|
+
const claims: IdTokenClaims = { chatgpt_account_id: "acc-root" }
|
|
43
|
+
expect(extractAccountIdFromClaims(claims)).toBe("acc-root")
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test("extracts chatgpt_account_id from nested https://api.openai.com/auth", () => {
|
|
47
|
+
const claims: IdTokenClaims = {
|
|
48
|
+
"https://api.openai.com/auth": { chatgpt_account_id: "acc-nested" },
|
|
49
|
+
}
|
|
50
|
+
expect(extractAccountIdFromClaims(claims)).toBe("acc-nested")
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test("prefers root over nested", () => {
|
|
54
|
+
const claims: IdTokenClaims = {
|
|
55
|
+
chatgpt_account_id: "acc-root",
|
|
56
|
+
"https://api.openai.com/auth": { chatgpt_account_id: "acc-nested" },
|
|
57
|
+
}
|
|
58
|
+
expect(extractAccountIdFromClaims(claims)).toBe("acc-root")
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test("extracts from organizations array as fallback", () => {
|
|
62
|
+
const claims: IdTokenClaims = {
|
|
63
|
+
organizations: [{ id: "org-123" }, { id: "org-456" }],
|
|
64
|
+
}
|
|
65
|
+
expect(extractAccountIdFromClaims(claims)).toBe("org-123")
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test("returns undefined when no accountId found", () => {
|
|
69
|
+
const claims: IdTokenClaims = { email: "test@example.com" }
|
|
70
|
+
expect(extractAccountIdFromClaims(claims)).toBeUndefined()
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe("extractAccountId", () => {
|
|
75
|
+
test("extracts from id_token first", () => {
|
|
76
|
+
const idToken = createTestJwt({ chatgpt_account_id: "from-id-token" })
|
|
77
|
+
const accessToken = createTestJwt({ chatgpt_account_id: "from-access-token" })
|
|
78
|
+
expect(
|
|
79
|
+
extractAccountId({
|
|
80
|
+
id_token: idToken,
|
|
81
|
+
access_token: accessToken,
|
|
82
|
+
refresh_token: "rt",
|
|
83
|
+
}),
|
|
84
|
+
).toBe("from-id-token")
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test("falls back to access_token when id_token has no accountId", () => {
|
|
88
|
+
const idToken = createTestJwt({ email: "test@example.com" })
|
|
89
|
+
const accessToken = createTestJwt({
|
|
90
|
+
"https://api.openai.com/auth": { chatgpt_account_id: "from-access" },
|
|
91
|
+
})
|
|
92
|
+
expect(
|
|
93
|
+
extractAccountId({
|
|
94
|
+
id_token: idToken,
|
|
95
|
+
access_token: accessToken,
|
|
96
|
+
refresh_token: "rt",
|
|
97
|
+
}),
|
|
98
|
+
).toBe("from-access")
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test("returns undefined when no tokens have accountId", () => {
|
|
102
|
+
const token = createTestJwt({ email: "test@example.com" })
|
|
103
|
+
expect(
|
|
104
|
+
extractAccountId({
|
|
105
|
+
id_token: token,
|
|
106
|
+
access_token: token,
|
|
107
|
+
refresh_token: "rt",
|
|
108
|
+
}),
|
|
109
|
+
).toBeUndefined()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test("handles missing id_token", () => {
|
|
113
|
+
const accessToken = createTestJwt({ chatgpt_account_id: "acc-123" })
|
|
114
|
+
expect(
|
|
115
|
+
extractAccountId({
|
|
116
|
+
id_token: "",
|
|
117
|
+
access_token: accessToken,
|
|
118
|
+
refresh_token: "rt",
|
|
119
|
+
}),
|
|
120
|
+
).toBe("acc-123")
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
})
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import fs from "fs/promises"
|
|
3
|
+
import path from "path"
|
|
4
|
+
|
|
5
|
+
import { Process } from "../../src/util/process"
|
|
6
|
+
import { Filesystem } from "../../src/util/filesystem"
|
|
7
|
+
import { tmpdir } from "../fixture/fixture"
|
|
8
|
+
|
|
9
|
+
const root = path.join(import.meta.dir, "../..")
|
|
10
|
+
const worker = path.join(import.meta.dir, "../fixture/plug-worker.ts")
|
|
11
|
+
|
|
12
|
+
type Msg = {
|
|
13
|
+
dir: string
|
|
14
|
+
target: string
|
|
15
|
+
mod: string
|
|
16
|
+
holdMs?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function run(msg: Msg) {
|
|
20
|
+
return Process.run([process.execPath, worker, JSON.stringify(msg)], {
|
|
21
|
+
cwd: root,
|
|
22
|
+
nothrow: true,
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function plugin(dir: string, kinds: Array<"server" | "tui">) {
|
|
27
|
+
const p = path.join(dir, "plugin")
|
|
28
|
+
await fs.mkdir(p, { recursive: true })
|
|
29
|
+
await Bun.write(
|
|
30
|
+
path.join(p, "package.json"),
|
|
31
|
+
JSON.stringify(
|
|
32
|
+
{
|
|
33
|
+
name: "acme",
|
|
34
|
+
version: "1.0.0",
|
|
35
|
+
"oc-plugin": kinds,
|
|
36
|
+
},
|
|
37
|
+
null,
|
|
38
|
+
2,
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
return p
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function read(file: string) {
|
|
45
|
+
return Filesystem.readJson<{ plugin?: unknown[] }>(file)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function mods(prefix: string, n: number) {
|
|
49
|
+
return Array.from({ length: n }, (_, i) => `${prefix}-${i}@1.0.0`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function expectPlugins(list: unknown[] | undefined, expectMods: string[]) {
|
|
53
|
+
expect(Array.isArray(list)).toBe(true)
|
|
54
|
+
const hit = (list ?? []).filter((item): item is string => typeof item === "string")
|
|
55
|
+
expect(hit.length).toBe(expectMods.length)
|
|
56
|
+
expect(new Set(hit)).toEqual(new Set(expectMods))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe("plugin.install.concurrent", () => {
|
|
60
|
+
test("serializes concurrent server config updates across processes", async () => {
|
|
61
|
+
await using tmp = await tmpdir()
|
|
62
|
+
const target = await plugin(tmp.path, ["server"])
|
|
63
|
+
const all = mods("mod-server", 12)
|
|
64
|
+
|
|
65
|
+
const out = await Promise.all(
|
|
66
|
+
all.map((mod) =>
|
|
67
|
+
run({
|
|
68
|
+
dir: tmp.path,
|
|
69
|
+
target,
|
|
70
|
+
mod,
|
|
71
|
+
holdMs: 30,
|
|
72
|
+
}),
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
expect(out.map((x) => x.code)).toEqual(Array.from({ length: all.length }, () => 0))
|
|
77
|
+
expect(out.map((x) => x.stderr.toString()).filter(Boolean)).toEqual([])
|
|
78
|
+
|
|
79
|
+
const cfg = await read(path.join(tmp.path, ".opencode", "opencode.jsonc"))
|
|
80
|
+
expectPlugins(cfg.plugin, all)
|
|
81
|
+
}, 25_000)
|
|
82
|
+
|
|
83
|
+
test("serializes concurrent server+tui config updates across processes", async () => {
|
|
84
|
+
await using tmp = await tmpdir()
|
|
85
|
+
const target = await plugin(tmp.path, ["server", "tui"])
|
|
86
|
+
const all = mods("mod-both", 10)
|
|
87
|
+
|
|
88
|
+
const out = await Promise.all(
|
|
89
|
+
all.map((mod) =>
|
|
90
|
+
run({
|
|
91
|
+
dir: tmp.path,
|
|
92
|
+
target,
|
|
93
|
+
mod,
|
|
94
|
+
holdMs: 30,
|
|
95
|
+
}),
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
expect(out.map((x) => x.code)).toEqual(Array.from({ length: all.length }, () => 0))
|
|
100
|
+
expect(out.map((x) => x.stderr.toString()).filter(Boolean)).toEqual([])
|
|
101
|
+
|
|
102
|
+
const server = await read(path.join(tmp.path, ".opencode", "opencode.jsonc"))
|
|
103
|
+
const tui = await read(path.join(tmp.path, ".opencode", "tui.jsonc"))
|
|
104
|
+
expectPlugins(server.plugin, all)
|
|
105
|
+
expectPlugins(tui.plugin, all)
|
|
106
|
+
}, 25_000)
|
|
107
|
+
|
|
108
|
+
test("preserves updates when existing config uses .json", async () => {
|
|
109
|
+
await using tmp = await tmpdir()
|
|
110
|
+
const target = await plugin(tmp.path, ["server"])
|
|
111
|
+
const cfg = path.join(tmp.path, ".opencode", "opencode.json")
|
|
112
|
+
await fs.mkdir(path.dirname(cfg), { recursive: true })
|
|
113
|
+
await Bun.write(cfg, JSON.stringify({ plugin: ["seed@1.0.0"] }, null, 2))
|
|
114
|
+
|
|
115
|
+
const next = mods("mod-json", 8)
|
|
116
|
+
const out = await Promise.all(
|
|
117
|
+
next.map((mod) =>
|
|
118
|
+
run({
|
|
119
|
+
dir: tmp.path,
|
|
120
|
+
target,
|
|
121
|
+
mod,
|
|
122
|
+
holdMs: 30,
|
|
123
|
+
}),
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
expect(out.map((x) => x.code)).toEqual(Array.from({ length: next.length }, () => 0))
|
|
128
|
+
expect(out.map((x) => x.stderr.toString()).filter(Boolean)).toEqual([])
|
|
129
|
+
|
|
130
|
+
const json = await read(cfg)
|
|
131
|
+
expectPlugins(json.plugin, ["seed@1.0.0", ...next])
|
|
132
|
+
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false)
|
|
133
|
+
}, 25_000)
|
|
134
|
+
})
|