carthagent 1.0.7 → 1.1.0-beta.2
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/README.md +140 -83
- package/bin/ctg.mjs +14 -732
- package/docs/beta-rewards-e2e-plan.md +48 -0
- package/docs/beta-rewards-e2e-results.md +84 -0
- package/docs/cloud-onboarding-acceptance.md +65 -0
- package/docs/glyph-review/host-chrome-glyph-compatibility-v4.md +83 -0
- package/docs/glyph-review/test-isolation-v1.md +26 -0
- package/docs/glyph-review/typecheck-base.log +1 -0
- package/docs/glyph-review/typecheck-v4.log +1 -0
- package/docs/glyph-review/unicode-inventory-v4.csv +402 -0
- package/docs/help-dispatch-review.md +69 -0
- package/docs/launch-readiness-live-results.md +65 -0
- package/docs/legacy-cli.md +150 -0
- package/docs/live-theme-plugin-api.md +18 -0
- package/docs/native-cli.md +102 -0
- package/docs/native-compatibility.md +16 -0
- package/docs/native-daily-quests.md +132 -0
- package/docs/native-glyph-pty.md +59 -0
- package/docs/native-manual-testing.md +336 -0
- package/docs/native-slash-acceptance-handoff.md +49 -0
- package/docs/native-terminal-paired-failure-2026-10-17.md +40 -0
- package/docs/native-terminal-performance.md +310 -0
- package/docs/native-terminal-semantics-evidence/README.md +25 -0
- package/docs/native-terminal-semantics-evidence/controlled-host-model-selftest.json +16 -0
- package/docs/native-terminal-semantics-evidence/current-acceptance-status.json +59 -0
- package/docs/native-terminal-semantics-evidence/final-binary-focused-85bb44a.json +154 -0
- package/docs/native-terminal-semantics-evidence/final-binary-full-incomplete-85bb44a.json +160 -0
- package/docs/native-terminal-semantics-evidence/final-binary-layout-85bb44a.json +149 -0
- package/docs/native-terminal-semantics-evidence/final-binary-layout-interactive-85bb44a.json +159 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-picker-escape-unchanged-85bb44a.json +44 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-picker-exact-identity-85bb44a.json +47 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-picker-unchanged-model-incomplete-85bb44a.json +58 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-selftest.json +16 -0
- package/docs/native-terminal-semantics-evidence/final-binary-picker-cancel-incomplete-85bb44a.json +62 -0
- package/docs/native-terminal-semantics-evidence/focused-clipboard-title-stale-binary.json +161 -0
- package/docs/native-terminal-semantics-evidence/layout-stale-binary-observation-not-accepted.json +153 -0
- package/docs/native-terminal-semantics-evidence/picker-latest-incomplete-attempt.json +62 -0
- package/docs/native-terminal-semantics-evidence/picker-previous-probe-pass.json +61 -0
- package/docs/native-terminal-semantics.md +89 -0
- package/docs/native-upgrade.md +175 -0
- package/docs/opencode-composed-source-review.md +89 -0
- package/docs/opencode-migration.md +225 -9
- package/docs/opencode-owned-runtime.md +241 -0
- package/docs/opencode-runtime.md +12 -0
- package/docs/opencode-theme-provider-proof/README.md +55 -0
- package/docs/opencode-theme-provider-proof/live-theme-api-base.patch +122 -0
- package/docs/opencode-theme-provider-proof/plugin-theme-provider.patch +107 -0
- package/docs/opencode-theme-provider-proof/session-ui-base.patch +1217 -0
- package/docs/owned-host-controls-proof.md +39 -0
- package/docs/owned-runtime-release-manifest.md +42 -0
- package/docs/owned-visual-clear-proof.md +40 -0
- package/docs/patches/opencode-v2.0.32-glyph-spinner-test-isolation-v1.patch +50 -0
- package/docs/patches/opencode-v2.0.32-glyphs.patch +3670 -0
- package/docs/patches/opencode-v2.0.32-help-dispatch.patch +156 -0
- package/docs/patches/opencode-v2.0.32-live-theme-api.patch +122 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v1.patch +5272 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v2.patch +6052 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v3.patch +8657 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v4.patch +9928 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v5.patch +13965 -0
- package/docs/patches/opencode-v2.0.32-session-ui.patch +5137 -0
- package/docs/release-notes-beta.1.md +60 -0
- package/docs/release-notes-beta.2.md +60 -0
- package/docs/release-notes-native-draft.md +73 -0
- package/docs/release-notes-preview.10.md +121 -0
- package/docs/release-notes-preview.11.md +76 -0
- package/docs/release-notes-preview.12.md +55 -0
- package/docs/release-notes-preview.13.md +68 -0
- package/docs/release-notes-preview.9.md +130 -0
- package/docs/source-preview.md +120 -0
- package/evaluate.mjs +28 -1
- package/lib/budget.mjs +1 -1
- package/lib/cloud/client.mjs +13 -7
- package/lib/codegraph.mjs +1 -1
- package/lib/delivery-review.mjs +19 -6
- package/lib/delivery.mjs +3 -2
- package/lib/environment.mjs +8 -0
- package/lib/guidance.mjs +1 -1
- package/lib/opencode/cli-runtime.mjs +1188 -0
- package/lib/opencode/cli.mjs +253 -0
- package/lib/opencode/credentials.mjs +63 -8
- package/lib/opencode/demo.mjs +53 -0
- package/lib/opencode/evaluation-budget-plugin/index.ts +3 -0
- package/lib/opencode/evaluation.mjs +32 -12
- package/lib/opencode/headless.mjs +22 -5
- package/lib/opencode/provider-policy.mjs +23 -0
- package/lib/opencode/review.mjs +37 -16
- package/lib/opencode/runtime-artifact.mjs +335 -0
- package/lib/opencode/runtime-github-transport.mjs +121 -0
- package/lib/opencode/runtime-release.json +36 -0
- package/lib/opencode/runtime.mjs +299 -20
- package/lib/opencode/service.mjs +77 -35
- package/lib/opencode/session-options.mjs +6 -2
- package/lib/opencode/update.mjs +222 -0
- package/lib/opencode/workspace.mjs +1 -1
- package/lib/planning-advice.mjs +2 -2
- package/lib/providers/experiential.mjs +2 -2
- package/lib/providers/names.mjs +1 -1
- package/lib/tui/framing.mjs +1 -80
- package/lib/tui/models.mjs +1 -1
- package/lib/update.mjs +10 -1
- package/package.json +33 -7
- package/plugins/carthagent/README.md +23 -0
- package/plugins/carthagent/src/bootstrap.ts +1 -1
- package/plugins/carthagent/src/daily-evidence.mjs +57 -0
- package/plugins/carthagent/src/daily-quests-rpc.ts +50 -0
- package/plugins/carthagent/src/daily-source-rpc.ts +52 -0
- package/plugins/carthagent/src/index.ts +71 -24
- package/plugins/carthagent/src/rpc.ts +51 -6
- package/plugins/carthagent/src/server/codegraph.ts +12 -5
- package/plugins/carthagent/src/server/daily-quests-rpc.ts +277 -0
- package/plugins/carthagent/src/server/daily-source-rpc.ts +243 -0
- package/plugins/carthagent/src/server/delivery.ts +227 -112
- package/plugins/carthagent/src/server/framing.mjs +80 -0
- package/plugins/carthagent/src/server/policy.ts +1 -1
- package/plugins/carthagent/src/server/providers.ts +65 -36
- package/plugins/carthagent/src/server/session-controls.ts +3 -1
- package/plugins/carthagent/src/server/ship-agents.ts +77 -0
- package/plugins/carthagent/src/server/ship-context.mjs +60 -0
- package/plugins/carthagent/src/server/ship-guidance.ts +41 -0
- package/plugins/carthagent/src/server/ship-rpc.ts +71 -0
- package/plugins/carthagent/src/server/voyages-rpc.ts +280 -0
- package/plugins/carthagent/src/testing.ts +5 -0
- package/plugins/carthagent/src/tui.tsx +506 -0
- package/plugins/carthagent/src/ui/client.ts +127 -0
- package/plugins/carthagent/src/ui/commands.ts +70 -0
- package/plugins/carthagent/src/ui/daily-editor-focus.mjs +52 -0
- package/plugins/carthagent/src/ui/daily-editor.mjs +4 -0
- package/plugins/carthagent/src/ui/daily-quests-events.mjs +18 -0
- package/plugins/carthagent/src/ui/daily-quests-state.mjs +218 -0
- package/plugins/carthagent/src/ui/daily-quests.tsx +235 -0
- package/plugins/carthagent/src/ui/host-state.ts +42 -0
- package/plugins/carthagent/src/ui/model-command.ts +32 -0
- package/plugins/carthagent/src/ui/native-navigation.ts +23 -0
- package/plugins/carthagent/src/ui/native-review.d.mts +33 -0
- package/plugins/carthagent/src/ui/native-review.mjs +159 -0
- package/plugins/carthagent/src/ui/resume-session.mjs +34 -0
- package/plugins/carthagent/src/ui/select-mouse.ts +17 -0
- package/plugins/carthagent/src/ui/session-preferences.d.mts +7 -0
- package/plugins/carthagent/src/ui/session-preferences.mjs +14 -0
- package/plugins/carthagent/src/ui/theme-provision.d.mts +3 -0
- package/plugins/carthagent/src/ui/theme-provision.mjs +134 -0
- package/plugins/carthagent/src/ui/voyages-navigation-state.mjs +56 -0
- package/plugins/carthagent/src/ui/voyages-navigation.tsx +131 -0
- package/plugins/carthagent/src/ui/voyages-state.mjs +165 -0
- package/plugins/carthagent/src/ui/voyages.tsx +131 -0
- package/plugins/carthagent/src/ui/workspace-live.ts +61 -0
- package/plugins/carthagent/src/ui/workspace.tsx +379 -0
- package/plugins/carthagent/src/voyages-rpc.ts +64 -0
- package/plugins/carthagent/src/workspace-rpc.ts +36 -0
- package/plugins/carthagent/tests/commands.test.ts +107 -0
- package/plugins/carthagent/tests/session-preferences.test.mjs +41 -0
- package/plugins/carthagent/tests/voyages-navigation.test.tsx +269 -0
- package/plugins/carthagent/themes/carthage.json +67 -0
- package/plugins/carthagent/tui.ts +26 -1
- package/script/ctg/build-owned-runtime.sh +166 -0
- package/tests/budget-engine.test.mjs +4 -3
- package/tests/cli.test.mjs +52 -96
- package/tests/cloud-client.test.mjs +2 -2
- package/tests/daily-evidence.test.mjs +62 -0
- package/tests/delivery-review.test.mjs +57 -2
- package/tests/delivery.test.mjs +22 -1
- package/tests/experiential-extension.test.mjs +2 -2
- package/tests/experiential.test.mjs +1 -1
- package/tests/fixtures/native-discovery-plugin/server.ts +15 -0
- package/tests/generate-owned-runtime-release.test.mjs +354 -0
- package/tests/installed-runtime-contract.test.mjs +43 -0
- package/tests/native-cli-host-smoke.mjs +457 -0
- package/tests/native-daily-editor-focus.test.mjs +213 -0
- package/tests/native-daily-editor.test.mjs +43 -0
- package/tests/native-daily-quests-layout.test.mjs +55 -0
- package/tests/native-daily-quests-rpc.test.mjs +456 -0
- package/tests/native-daily-quests-ui.test.mjs +778 -0
- package/tests/native-daily-source.test.mjs +525 -0
- package/tests/native-demo-ui.test.mjs +48 -0
- package/tests/native-evaluation-admission.test.mjs +24 -0
- package/tests/native-fixture-browser-cleanup.test.mjs +65 -0
- package/tests/native-fixture-runtime.test.mjs +111 -0
- package/tests/native-fixture-stop-diagnostics.test.mjs +67 -0
- package/tests/native-glyph-pty.test.mjs +107 -0
- package/tests/native-manual-projects.test.mjs +170 -0
- package/tests/native-model-command.test.mjs +67 -0
- package/tests/native-owned-runtime-env.test.mjs +140 -0
- package/tests/native-paired-guards.test.mjs +464 -0
- package/tests/native-provider-policy.test.mjs +33 -0
- package/tests/native-pty-invocation.test.mjs +114 -0
- package/tests/native-pty-runtime.test.mjs +45 -0
- package/tests/native-resume-session.test.mjs +46 -0
- package/tests/native-review-consumer.test.mjs +286 -0
- package/tests/native-runtime-identity.test.mjs +214 -0
- package/tests/native-select-mouse.test.mjs +23 -0
- package/tests/native-ship-guidance.test.mjs +16 -0
- package/tests/native-ship-parity.test.mjs +526 -0
- package/tests/native-ship-plan.test.mjs +85 -0
- package/tests/native-ship-rpc.test.mjs +83 -0
- package/tests/native-ui-commands.test.mjs +6 -0
- package/tests/native-voyages-layout.test.mjs +28 -0
- package/tests/native-voyages-navigation.test.mjs +169 -0
- package/tests/native-voyages-rpc.test.mjs +397 -0
- package/tests/native-voyages-ui.test.mjs +307 -0
- package/tests/opencode-atomic-write.test.mjs +55 -0
- package/tests/opencode-cli.test.mjs +1312 -0
- package/tests/opencode-codegraph-plugin.test.mjs +47 -0
- package/tests/opencode-delivery.test.mjs +16 -0
- package/tests/opencode-providers.test.mjs +248 -20
- package/tests/opencode-runtime.test.mjs +597 -8
- package/tests/opencode-session-controls.test.mjs +15 -0
- package/tests/opencode-ship-rpc.test.mjs +71 -0
- package/tests/opencode-update.test.mjs +475 -0
- package/tests/opencode-workspace.test.mjs +51 -2
- package/tests/owned-runtime-publish-workflow.test.mjs +477 -0
- package/tests/release-workflow.test.mjs +46 -0
- package/tests/runtime-artifact.test.mjs +269 -0
- package/tests/runtime-development-trust.test.mjs +231 -0
- package/tests/runtime-github-transport.test.mjs +132 -0
- package/tests/runtime-token-env.test.mjs +23 -0
- package/tests/server.test.mjs +3 -1
- package/tests/ship-telemetry.test.mjs +1 -1
- package/tests/source-preview.test.mjs +253 -0
- package/tests/test_native_terminal_probe.py +76 -0
- package/tests/test_native_terminal_semantics.py +232 -0
- package/tests/tui.test.mjs +2 -2
- package/tests/update.test.mjs +42 -5
- package/tests/verify-draft-runtime-gate.test.mjs +280 -0
- package/tests/verify-owned-runtime-release.test.mjs +371 -0
- package/tests/workspace-live.test.mjs +127 -0
- package/tools/benchmark/diagnose-request.mjs +59 -36
- package/tools/benchmark/eval-common.mjs +3 -3
- package/tools/benchmark/evaluators.test.mjs +4 -4
- package/tools/benchmark/guard.test.mjs +35 -2
- package/tools/benchmark/guard.ts +20 -7
- package/tools/benchmark/planning-startup.test.mjs +30 -34
- package/tools/benchmark/runner.mjs +12 -7
- package/tools/benchmark/runner.test.mjs +23 -7
- package/tools/benchmark/upstream-error.test.mjs +16 -2
- package/tools/generate-owned-runtime-release.mjs +217 -0
- package/tools/installed-runtime-contract.mjs +16 -0
- package/tools/manual-runtime-manifest.mjs +39 -0
- package/tools/native-fixture-browser-cleanup.mjs +59 -0
- package/tools/native-fixture-stop-diagnostics.mjs +45 -0
- package/tools/native-fixture.mjs +199 -40
- package/tools/native-glyph-helpers.py +127 -0
- package/tools/native-glyph-pty.py +144 -0
- package/tools/native-manual-projects.mjs +259 -0
- package/tools/native-manual.mjs +338 -0
- package/tools/native-model-picker-acceptance.py +306 -0
- package/tools/native-owned-runtime-env.sh +47 -0
- package/tools/native-paired-guards.mjs +282 -0
- package/tools/native-paired-manifest.mjs +72 -0
- package/tools/native-paired-orchestration.sh +51 -0
- package/tools/native-plugin-readiness.mjs +33 -0
- package/tools/native-plugin-readiness.test.mjs +53 -0
- package/tools/native-pty-invocation.mjs +26 -0
- package/tools/native-pty-runtime.mjs +83 -0
- package/tools/native-pty-smoke.py +147 -24
- package/tools/native-public-repo-shell-result.mjs +13 -0
- package/tools/native-public-repo-shell-result.test.mjs +28 -0
- package/tools/native-runtime-identity.mjs +201 -0
- package/tools/native-slash-acceptance-env.py +38 -0
- package/tools/native-terminal-semantics.py +882 -0
- package/tools/owned-shell-observer/index.ts +16 -0
- package/tools/owned-shell-observer/package.json +1 -0
- package/tools/owned-visual-clear-env.py +48 -0
- package/tools/owned-visual-clear-model.py +21 -0
- package/tools/pinned-owned-runtime-inputs.mjs +46 -0
- package/tools/prepare-native-manual.mjs +122 -0
- package/tools/run-native-terminal-paired.sh +403 -0
- package/tools/runtime-token-env.mjs +8 -0
- package/tools/seed-native-switch-fixture.mjs +79 -0
- package/tools/test-native-controls.mjs +3 -1
- package/tools/test-native-daily-quests.mjs +65 -0
- package/tools/test-native-default-cli.mjs +106 -0
- package/tools/test-native-delivery-host.mjs +102 -20
- package/tools/test-native-evaluation.mjs +7 -3
- package/tools/test-native-glyph-pty.mjs +94 -0
- package/tools/test-native-host.mjs +91 -65
- package/tools/test-native-outcomes.mjs +3 -1
- package/tools/test-native-package.mjs +206 -38
- package/tools/test-native-performance.mjs +26 -0
- package/tools/test-native-product.mjs +90 -0
- package/tools/test-native-pty.mjs +34 -9
- package/tools/test-native-public-repo.mjs +224 -0
- package/tools/test-native-refresh-reentry.mjs +106 -0
- package/tools/test-native-report.mjs +27 -20
- package/tools/test-native-review.mjs +75 -0
- package/tools/test-native-runtime.mjs +120 -0
- package/tools/test-native-ship-models.mjs +176 -0
- package/tools/test-native-ship-walkthrough.mjs +96 -0
- package/tools/test-native-slash-commands.mjs +125 -0
- package/tools/test-native-slash-commands.py +242 -0
- package/tools/test-native-swe-host.mjs +224 -55
- package/tools/test-native-tasks.mjs +77 -0
- package/tools/test-native-terminal-performance.py +887 -0
- package/tools/test-native-terminal-readiness.sh +85 -0
- package/tools/test-native-terminal-semantics-installed.mjs +38 -0
- package/tools/test-native-visual-clear-installed.mjs +48 -0
- package/tools/test-owned-host-controls.mjs +238 -0
- package/tools/test-owned-opencode-runtime.mjs +35 -0
- package/tools/test-owned-visual-clear-env.py +120 -0
- package/tools/test-owned-visual-clear.py +373 -0
- package/tools/verify-draft-runtime-gate.mjs +138 -0
- package/tools/verify-owned-runtime-release.mjs +114 -0
- package/tools/verify-release-version.mjs +21 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
diff --git a/packages/tui/src/component/prompt/autocomplete.tsx b/packages/tui/src/component/prompt/autocomplete.tsx
|
|
2
|
+
index c669491..2c0c63a 100644
|
|
3
|
+
--- a/packages/tui/src/component/prompt/autocomplete.tsx
|
|
4
|
+
+++ b/packages/tui/src/component/prompt/autocomplete.tsx
|
|
5
|
+
@@ -472,15 +472,28 @@ export function Autocomplete(props: {
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const commands = createMemo((): AutocompleteOption[] => {
|
|
9
|
+
- const results: AutocompleteOption[] = keymapCommands().flatMap((command) => {
|
|
10
|
+
+ const registered = keymapCommands()
|
|
11
|
+
+ let help: KeymapCommand | undefined
|
|
12
|
+
+ const results: AutocompleteOption[] = registered.flatMap((command) => {
|
|
13
|
+
const slash = command.slash
|
|
14
|
+
if (!slash) return []
|
|
15
|
+
- return [slash.name, ...(slash.aliases ?? [])].map((name) => ({
|
|
16
|
+
+ const names = [slash.name, ...(slash.aliases ?? [])]
|
|
17
|
+
+ if (names.includes("help") && command.id !== "help.show") help = command
|
|
18
|
+
+ return names.filter((name) => name !== "help").map((name) => ({
|
|
19
|
+
display: `/${name}`,
|
|
20
|
+
description: command.description ?? command.title,
|
|
21
|
+
onSelect: slash.arguments ? () => insertSlash(name) : command.run,
|
|
22
|
+
}))
|
|
23
|
+
})
|
|
24
|
+
+ const helpCommand = help ?? registered.find((command) => command.id === "help.show")
|
|
25
|
+
+ if (helpCommand?.slash) {
|
|
26
|
+
+ const slash = helpCommand.slash
|
|
27
|
+
+ results.push({
|
|
28
|
+
+ display: "/help",
|
|
29
|
+
+ description: helpCommand.description ?? helpCommand.title,
|
|
30
|
+
+ onSelect: slash.arguments ? () => insertSlash("help") : helpCommand.run,
|
|
31
|
+
+ })
|
|
32
|
+
+ }
|
|
33
|
+
const commandNames = new Set<string>()
|
|
34
|
+
|
|
35
|
+
for (const serverCommand of data.location.command.list(location.current) ?? []) {
|
|
36
|
+
diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx
|
|
37
|
+
index a9f699c..63fe438 100644
|
|
38
|
+
--- a/packages/tui/src/component/prompt/index.tsx
|
|
39
|
+
+++ b/packages/tui/src/component/prompt/index.tsx
|
|
40
|
+
@@ -173,11 +173,15 @@ function formatEditorContext(selection: EditorSelection) {
|
|
41
|
+
function argumentSlash(input: string, commands: readonly KeymapCommand[]) {
|
|
42
|
+
const head = parseSlashHead(input, /\s/)
|
|
43
|
+
if (!head) return
|
|
44
|
+
- const command = commands.find(
|
|
45
|
+
+ const matches = commands.filter(
|
|
46
|
+
(command) =>
|
|
47
|
+
command.slash?.arguments &&
|
|
48
|
+
(command.slash.name === head.name || command.slash.aliases?.includes(head.name) === true),
|
|
49
|
+
)
|
|
50
|
+
+ // The built-in help action is a fallback; let an extension own the natural
|
|
51
|
+
+ // `/help` spelling regardless of which layer happened to register first.
|
|
52
|
+
+ const command =
|
|
53
|
+
+ head.name === "help" ? (matches.findLast((item) => item.id !== "help.show") ?? matches[0]) : matches[0]
|
|
54
|
+
if (!command) return
|
|
55
|
+
return { command, input: head.arguments }
|
|
56
|
+
}
|
|
57
|
+
diff --git a/packages/tui/test/plugin-keymap.test.tsx b/packages/tui/test/plugin-keymap.test.tsx
|
|
58
|
+
index 0790e9a..23f502c 100644
|
|
59
|
+
--- a/packages/tui/test/plugin-keymap.test.tsx
|
|
60
|
+
+++ b/packages/tui/test/plugin-keymap.test.tsx
|
|
61
|
+
@@ -59,6 +59,38 @@ test("setup layers before and after await reach keyboard, palette, and slash acr
|
|
62
|
+
await setup.waitForFrame((frame) => frame.includes("Sync keymap ran 1"))
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
+test("a plugin /help overrides native /help for natural autocomplete selection and restores it on disable", async () => {
|
|
66
|
+
+ await using plugin = await copyHelpPlugin()
|
|
67
|
+
+ const requests: URL[] = []
|
|
68
|
+
+ await using setup = await createAppFixture({
|
|
69
|
+
+ config: { animations: false, plugins: [plugin.directory] },
|
|
70
|
+
+ fetch(url) {
|
|
71
|
+
+ requests.push(url)
|
|
72
|
+
+ },
|
|
73
|
+
+ })
|
|
74
|
+
+ await setup.ready
|
|
75
|
+
+ await setup.waitForFrame((frame) => frame.includes("Help override ready"))
|
|
76
|
+
+
|
|
77
|
+
+ await setup.mockInput.typeText("/help")
|
|
78
|
+
+ await setup.waitForFrame((frame) => frame.includes("/help"))
|
|
79
|
+
+ setup.mockInput.pressEnter()
|
|
80
|
+
+ await setup.waitForFrame((frame) => frame.includes("Plugin help handler 2"))
|
|
81
|
+
+ expect(requests.some((url) => url.pathname === "/session")).toBe(false)
|
|
82
|
+
+
|
|
83
|
+
+ setup.mockInput.pressEscape()
|
|
84
|
+
+ await openPlugin(setup, "help-override")
|
|
85
|
+
+ await setup.waitForFrame((frame) => frame.includes("disable"))
|
|
86
|
+
+ setup.mockInput.pressEnter()
|
|
87
|
+
+ await setup.waitForFrame((frame) => frame.includes("inactive"))
|
|
88
|
+
+ setup.mockInput.pressEscape()
|
|
89
|
+
+
|
|
90
|
+
+ await setup.mockInput.typeText("/help")
|
|
91
|
+
+ await setup.waitForFrame((frame) => frame.includes("/help"))
|
|
92
|
+
+ setup.mockInput.pressEnter()
|
|
93
|
+
+ await setup.waitForFrame((frame) => frame.includes("Press ctrl+p to see all available actions"))
|
|
94
|
+
+ expect(requests.some((url) => url.pathname === "/session")).toBe(false)
|
|
95
|
+
+})
|
|
96
|
+
+
|
|
97
|
+
test("a component layer ends when its component unmounts while the plugin stays active", async () => {
|
|
98
|
+
await using plugin = await copyPlugin()
|
|
99
|
+
await using setup = await createAppFixture({ config: { animations: false, plugins: [plugin.directory] } })
|
|
100
|
+
@@ -138,6 +170,41 @@ async function copyPlugin() {
|
|
101
|
+
return { directory, entry, [Symbol.asyncDispose]: root[Symbol.asyncDispose] }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
+async function copyHelpPlugin() {
|
|
105
|
+
+ const root = await tmpdir()
|
|
106
|
+
+ const directory = path.join(root.path, "help-override")
|
|
107
|
+
+ const entry = path.join(directory, "tui.tsx")
|
|
108
|
+
+ await mkdir(directory)
|
|
109
|
+
+ await symlink(path.join(import.meta.dir, "../node_modules"), path.join(directory, "node_modules"))
|
|
110
|
+
+ await Bun.write(entry, `
|
|
111
|
+
+ import { Plugin } from "@opencode/plugin/tui"
|
|
112
|
+
+ export default Plugin.define({
|
|
113
|
+
+ id: "fixture.help-override",
|
|
114
|
+
+ setup(context) {
|
|
115
|
+
+ context.keymap.layer(() => ({
|
|
116
|
+
+ mode: "global",
|
|
117
|
+
+ commands: [
|
|
118
|
+
+ {
|
|
119
|
+
+ id: "fixture.help-override.first",
|
|
120
|
+
+ title: "First plugin help",
|
|
121
|
+
+ slash: { name: "help" },
|
|
122
|
+
+ run: () => context.ui.dialog.show(() => <text>Plugin help handler 1</text>),
|
|
123
|
+
+ },
|
|
124
|
+
+ {
|
|
125
|
+
+ id: "fixture.help-override.second",
|
|
126
|
+
+ title: "Second plugin help",
|
|
127
|
+
+ slash: { name: "help" },
|
|
128
|
+
+ run: () => context.ui.dialog.show(() => <text>Plugin help handler 2</text>),
|
|
129
|
+
+ },
|
|
130
|
+
+ ],
|
|
131
|
+
+ }))
|
|
132
|
+
+ context.ui.toast.show({ message: "Help override ready" })
|
|
133
|
+
+ },
|
|
134
|
+
+ })
|
|
135
|
+
+ `)
|
|
136
|
+
+ return { directory, [Symbol.asyncDispose]: root[Symbol.asyncDispose] }
|
|
137
|
+
+}
|
|
138
|
+
+
|
|
139
|
+
async function search(setup: Fixture, query: string) {
|
|
140
|
+
setup.mockInput.pressKey("p", { ctrl: true })
|
|
141
|
+
await setup.waitForFrame((frame) => frame.includes("Commands"))
|
|
142
|
+
@@ -145,11 +212,11 @@ async function search(setup: Fixture, query: string) {
|
|
143
|
+
await setup.mockInput.typeText(query)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
-async function openPlugin(setup: Fixture) {
|
|
147
|
+
+async function openPlugin(setup: Fixture, query = "setup-keymap") {
|
|
148
|
+
await setup.mockInput.typeText("/plugins")
|
|
149
|
+
await setup.waitForFrame((frame) => frame.includes("/plugins"))
|
|
150
|
+
setup.mockInput.pressEnter()
|
|
151
|
+
- await setup.waitForFrame((frame) => frame.includes("fixture.setup-keymap"))
|
|
152
|
+
+ await setup.waitForFrame((frame) => frame.includes(query === "help-override" ? "fixture.help-override" : "fixture.setup-keymap"))
|
|
153
|
+
await setup.waitFor(() => setup.renderer.currentFocusedEditor instanceof InputRenderable)
|
|
154
|
+
- await setup.mockInput.typeText("setup-keymap")
|
|
155
|
+
+ await setup.mockInput.typeText(query)
|
|
156
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
diff --git a/packages/plugin/src/tui/context.ts b/packages/plugin/src/tui/context.ts
|
|
2
|
+
index fe641b3..21ec351 100644
|
|
3
|
+
--- a/packages/plugin/src/tui/context.ts
|
|
4
|
+
+++ b/packages/plugin/src/tui/context.ts
|
|
5
|
+
@@ -490,6 +490,13 @@ export interface Keymap {
|
|
6
|
+
export interface UI {
|
|
7
|
+
readonly dialog: Dialog
|
|
8
|
+
readonly toast: Toast
|
|
9
|
+
+ /** Live host theme controls. Theme files must already be available to the host. */
|
|
10
|
+
+ readonly theme: {
|
|
11
|
+
+ /** Returns the selected theme name and active color mode. */
|
|
12
|
+
+ current(): { readonly name: string; readonly mode: "dark" | "light" }
|
|
13
|
+
+ /** Switches the live theme and optionally locks its color mode; returns false if either is unavailable. */
|
|
14
|
+
+ set(selection: { readonly name: string; readonly mode?: "dark" | "light" }): boolean
|
|
15
|
+
+ }
|
|
16
|
+
readonly format: {
|
|
17
|
+
path(value: string): string
|
|
18
|
+
}
|
|
19
|
+
diff --git a/packages/tui/src/context/theme.tsx b/packages/tui/src/context/theme.tsx
|
|
20
|
+
index ed568e6..2d7d363 100644
|
|
21
|
+
--- a/packages/tui/src/context/theme.tsx
|
|
22
|
+
+++ b/packages/tui/src/context/theme.tsx
|
|
23
|
+
@@ -94,6 +94,7 @@ type Themes = {
|
|
24
|
+
mode: Accessor<"dark" | "light">
|
|
25
|
+
modes: Accessor<readonly ("dark" | "light")[]>
|
|
26
|
+
supports(mode: "dark" | "light"): boolean
|
|
27
|
+
+ supportsThemeMode(theme: string, mode: "dark" | "light"): boolean
|
|
28
|
+
locked: Accessor<boolean>
|
|
29
|
+
lock(): void
|
|
30
|
+
unlock(): void
|
|
31
|
+
@@ -329,6 +330,10 @@ const themeContext = createSimpleContext({
|
|
32
|
+
mode,
|
|
33
|
+
modes,
|
|
34
|
+
supports: (requested) => modes().includes(requested),
|
|
35
|
+
+ supportsThemeMode: (theme, requested) => {
|
|
36
|
+
+ const source = available()[theme]
|
|
37
|
+
+ return source !== undefined && themeModes(parseTheme(source, theme)).includes(requested)
|
|
38
|
+
+ },
|
|
39
|
+
locked: () => store.lock !== undefined,
|
|
40
|
+
lock: () => pin(mode()),
|
|
41
|
+
unlock: free,
|
|
42
|
+
diff --git a/packages/tui/src/plugin/api.tsx b/packages/tui/src/plugin/api.tsx
|
|
43
|
+
index 35a4eac..4150ba9 100644
|
|
44
|
+
--- a/packages/tui/src/plugin/api.tsx
|
|
45
|
+
+++ b/packages/tui/src/plugin/api.tsx
|
|
46
|
+
@@ -204,6 +204,16 @@ export function createPluginContext(input: {
|
|
47
|
+
ui: {
|
|
48
|
+
dialog: dialogApi,
|
|
49
|
+
toast: toastApi,
|
|
50
|
+
+ theme: {
|
|
51
|
+
+ current: () => ({ name: host.themes.selected, mode: host.themes.mode() }),
|
|
52
|
+
+ set(selection) {
|
|
53
|
+
+ if (!selection || typeof selection.name !== "string" || !host.themes.has(selection.name)) return false
|
|
54
|
+
+ if (selection.mode !== undefined && !host.themes.supportsThemeMode(selection.name, selection.mode)) return false
|
|
55
|
+
+ if (!host.themes.set(selection.name)) return false
|
|
56
|
+
+ if (selection.mode !== undefined) host.themes.setMode(selection.mode)
|
|
57
|
+
+ return true
|
|
58
|
+
+ },
|
|
59
|
+
+ },
|
|
60
|
+
format: {
|
|
61
|
+
path: (value) => abbreviateHome(value, host.paths.home),
|
|
62
|
+
},
|
|
63
|
+
diff --git a/packages/tui/test/plugin-theme.test.tsx b/packages/tui/test/plugin-theme.test.tsx
|
|
64
|
+
new file mode 100644
|
|
65
|
+
index 0000000..9a46b4d
|
|
66
|
+
--- /dev/null
|
|
67
|
+
+++ b/packages/tui/test/plugin-theme.test.tsx
|
|
68
|
+
@@ -0,0 +1,54 @@
|
|
69
|
+
+import { expect, test } from "bun:test"
|
|
70
|
+
+import { createPluginContext, type Registry, type usePluginHost } from "../src/plugin/api"
|
|
71
|
+
+
|
|
72
|
+
+function setup() {
|
|
73
|
+
+ let selected = "opencode"
|
|
74
|
+
+ let mode: "dark" | "light" = "dark"
|
|
75
|
+
+ const available = new Set(["opencode", "custom"])
|
|
76
|
+
+ const host = {
|
|
77
|
+
+ app: {},
|
|
78
|
+
+ client: {},
|
|
79
|
+
+ keymap: {},
|
|
80
|
+
+ shortcuts: {},
|
|
81
|
+
+ keymapState: {},
|
|
82
|
+
+ sessionTabs: {},
|
|
83
|
+
+ themes: {
|
|
84
|
+
+ get selected() {
|
|
85
|
+
+ return selected
|
|
86
|
+
+ },
|
|
87
|
+
+ mode: () => mode,
|
|
88
|
+
+ has: (name: string) => available.has(name),
|
|
89
|
+
+ supportsThemeMode: (name: string, requested: "dark" | "light") => requested === "dark" || name === "custom",
|
|
90
|
+
+ set(name: string) {
|
|
91
|
+
+ if (!available.has(name)) return false
|
|
92
|
+
+ selected = name
|
|
93
|
+
+ return true
|
|
94
|
+
+ },
|
|
95
|
+
+ setMode(requested: "dark" | "light") {
|
|
96
|
+
+ mode = requested
|
|
97
|
+
+ return true
|
|
98
|
+
+ },
|
|
99
|
+
+ },
|
|
100
|
+
+ } as unknown as ReturnType<typeof usePluginHost>
|
|
101
|
+
+ const registry: Registry = { has: () => false, set() {}, remove() {}, active: () => true }
|
|
102
|
+
+ const theme = createPluginContext({ host, id: "test", options: undefined, owned: [], registry }).ui.theme
|
|
103
|
+
+ return { theme, current: () => ({ selected, mode }) }
|
|
104
|
+
+}
|
|
105
|
+
+
|
|
106
|
+
+test("plugin theme setter applies available theme and mode live through the host service", () => {
|
|
107
|
+
+ const harness = setup()
|
|
108
|
+
+ expect(harness.theme.current()).toEqual({ name: "opencode", mode: "dark" })
|
|
109
|
+
+ expect(harness.theme.set({ name: "custom", mode: "light" })).toBe(true)
|
|
110
|
+
+ expect(harness.theme.current()).toEqual({ name: "custom", mode: "light" })
|
|
111
|
+
+ expect(harness.current()).toEqual({ selected: "custom", mode: "light" })
|
|
112
|
+
+})
|
|
113
|
+
+
|
|
114
|
+
+test("plugin theme setter rejects unknown themes and unsupported modes without partial changes", () => {
|
|
115
|
+
+ const harness = setup()
|
|
116
|
+
+ expect(harness.theme.set({ name: "missing", mode: "dark" })).toBe(false)
|
|
117
|
+
+ expect(harness.theme.set({ name: "custom", mode: "light" })).toBe(true)
|
|
118
|
+
+ expect(harness.theme.set({ name: "opencode", mode: "light" })).toBe(false)
|
|
119
|
+
+ expect(harness.theme.current()).toEqual({ name: "custom", mode: "light" })
|
|
120
|
+
+ expect(harness.theme.set({ name: "opencode" })).toBe(true)
|
|
121
|
+
+ expect(harness.theme.current()).toEqual({ name: "opencode", mode: "light" })
|
|
122
|
+
+})
|