carthagent 1.0.7 → 1.1.0-beta.1
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/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-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/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 +1171 -0
- package/lib/opencode/cli.mjs +252 -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/tui/framing.mjs +1 -80
- 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 +59 -31
- 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 +69 -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 +496 -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/daily-evidence.test.mjs +62 -0
- package/tests/delivery-review.test.mjs +57 -2
- package/tests/delivery.test.mjs +22 -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 +31 -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 +71 -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 +245 -18
- 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/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 +43 -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 +876 -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 +87 -63
- 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
package/docs/opencode-runtime.md
CHANGED
|
@@ -5,8 +5,20 @@ CTG runs the package-owned native OpenCode v2.0.32 CLI from the pinned
|
|
|
5
5
|
user installation. An explicitly supplied `CTG_OPENCODE_BIN` is accepted only
|
|
6
6
|
when its version is exactly 2.0.32.
|
|
7
7
|
|
|
8
|
+
Runtime identity records the concrete executable path and SHA-256, including
|
|
9
|
+
when an explicit bare command is resolved through the supplied `PATH`. A
|
|
10
|
+
healthy process registered to a different runtime remains untouched and blocks
|
|
11
|
+
selection changes. A dead registration, including a legacy record without an
|
|
12
|
+
identity, is removed only after its PID is confirmed dead, so it cannot
|
|
13
|
+
permanently prevent startup.
|
|
14
|
+
|
|
8
15
|
## Isolation and persistence
|
|
9
16
|
|
|
17
|
+
An additional separately distributed pinned source variant is documented in
|
|
18
|
+
[`opencode-owned-runtime.md`](./opencode-owned-runtime.md). It is opt-in via
|
|
19
|
+
`CTG_OWNED_OPENCODE_RUNTIME`; unset behavior remains the package-owned
|
|
20
|
+
`@opencode/cli@2.0.32` runtime described here.
|
|
21
|
+
|
|
10
22
|
The default CTG profile is `~/.carthagent/opencode`. `CTG_OPENCODE_HOME` (or a
|
|
11
23
|
`root` argument in the runtime API) can select another profile. CTG applies
|
|
12
24
|
private `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME`, and
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Live plugin theme provider proof
|
|
2
|
+
|
|
3
|
+
This focused integration test closes a boundary left open by
|
|
4
|
+
`plugin-theme.test.tsx` in the approved live-theme API patch: those unit tests
|
|
5
|
+
mock `host.themes`, while this proof mounts the real Solid `ThemeProvider` and
|
|
6
|
+
`ConfigProvider` and passes the actual provider service to `createPluginContext`.
|
|
7
|
+
|
|
8
|
+
## Reproduction
|
|
9
|
+
|
|
10
|
+
Start from upstream OpenCode
|
|
11
|
+
`843a6c514e606b436a7fe713422ace0a93ad3d61` (v2.0.32), apply
|
|
12
|
+
`session-ui-base.patch` followed by `live-theme-api-base.patch`, then apply
|
|
13
|
+
`plugin-theme-provider.patch`. Then run:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
bun --cwd packages/tui test test/plugin-theme-provider.test.tsx
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The test provides dual-mode and dark-only custom theme documents in memory via
|
|
20
|
+
the real provider theme source. It verifies the plugin API reports the selected
|
|
21
|
+
theme and resolved mode, rendered provider tokens change reactively without a
|
|
22
|
+
provider remount, and successful changes update the settings object through the
|
|
23
|
+
injected in-memory `ConfigProvider` service. This proves the config update call
|
|
24
|
+
and in-process state, not filesystem persistence or survival across a restart.
|
|
25
|
+
Unknown theme names and unsupported explicit modes return `false` without
|
|
26
|
+
changing the selection, mode, tokens, or settings object. Omitting a mode for a
|
|
27
|
+
dark-only theme selects the actual dark fallback and updates the selected name
|
|
28
|
+
in the in-memory config service.
|
|
29
|
+
|
|
30
|
+
No external provider or network is used; both the theme documents and config
|
|
31
|
+
service are test-local. No host API changes beyond the approved patch are made.
|
|
32
|
+
|
|
33
|
+
## Validation
|
|
34
|
+
|
|
35
|
+
Run against the exact upstream base plus the two approved patch files, with Bun
|
|
36
|
+
1.4.2:
|
|
37
|
+
|
|
38
|
+
All five consecutive reruns passed (each **1 pass, 0 fail, 18 expectations**).
|
|
39
|
+
|
|
40
|
+
The test-only source patch is `plugin-theme-provider.patch`.
|
|
41
|
+
|
|
42
|
+
## Independently verified patch digests
|
|
43
|
+
|
|
44
|
+
SHA-256 digests for the exact patch inputs used in the independent review:
|
|
45
|
+
|
|
46
|
+
| Patch | SHA-256 |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `session-ui-base.patch` (base patch) | `36379e0bf4ec0f63929c12b5ac96dfc73df60b124cef9f4adeb36d86287bc92f` |
|
|
49
|
+
| `live-theme-api-base.patch` | `0bcb1462688c00a09564e9874c3e01429baf92fe004bfc6400704fb24133c123` |
|
|
50
|
+
| `plugin-theme-provider.patch` (test-only patch) | `1afe7901f43261ed1ffa324b1dcf3a317404ecd2175956a1513ca29139427714` |
|
|
51
|
+
|
|
52
|
+
`bun typecheck` from `packages/tui` also passed. The isolated source checkout's
|
|
53
|
+
Git common directory was removed by an environment restart, but its source tree
|
|
54
|
+
and test remain at `/tmp/opencode/ctg-live-theme-proof-clean` for evidence. The
|
|
55
|
+
test was rerun after a fresh frozen-lockfile install and passed.
|
|
@@ -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
|
+
+})
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
--- a/packages/tui/test/plugin-theme-provider.test.tsx
|
|
2
|
+
+++ b/packages/tui/test/plugin-theme-provider.test.tsx
|
|
3
|
+
@@ -0,0 +1,104 @@
|
|
4
|
+
+/** @jsxImportSource @opentui/solid */
|
|
5
|
+
+import { testRender } from "@opentui/solid"
|
|
6
|
+
+import { expect, test } from "bun:test"
|
|
7
|
+
+import { ConfigProvider, type Info } from "../src/config"
|
|
8
|
+
+import { ThemeProvider, useThemes } from "../src/context/theme"
|
|
9
|
+
+import { createPluginContext, type Registry, type usePluginHost } from "../src/plugin/api"
|
|
10
|
+
+import opencode from "../src/theme/assets/v2/opencode.json" with { type: "json" }
|
|
11
|
+
+import { emptyThemeSource, tmpdir } from "./fixture/fixture"
|
|
12
|
+
+import { TestTuiContexts } from "./fixture/tui-environment"
|
|
13
|
+
+import { createTuiResolvedConfig } from "./fixture/tui-runtime"
|
|
14
|
+
+
|
|
15
|
+
+const custom = {
|
|
16
|
+
+ ...opencode,
|
|
17
|
+
+ base: {
|
|
18
|
+
+ ...opencode.base,
|
|
19
|
+
+ background: { ...opencode.base.background, base: "#aabbcc" },
|
|
20
|
+
+ text: { ...opencode.base.text, base: "#123456" },
|
|
21
|
+
+ },
|
|
22
|
+
+ dark: { hue: opencode.dark.hue },
|
|
23
|
+
+ light: { hue: opencode.dark.hue },
|
|
24
|
+
+}
|
|
25
|
+
+const darkOnly = {
|
|
26
|
+
+ ...custom,
|
|
27
|
+
+ base: {
|
|
28
|
+
+ ...custom.base,
|
|
29
|
+
+ background: { ...custom.base.background, base: "#334455" },
|
|
30
|
+
+ },
|
|
31
|
+
+ light: undefined,
|
|
32
|
+
+}
|
|
33
|
+
+
|
|
34
|
+
+test("plugin theme API reactively updates real ThemeProvider palette and persists selection", async () => {
|
|
35
|
+
+ await using temporary = await tmpdir()
|
|
36
|
+
+ const settings: Info = {}
|
|
37
|
+
+ let themes!: ReturnType<typeof useThemes>
|
|
38
|
+
+ let plugin!: ReturnType<typeof createPluginContext>
|
|
39
|
+
+ const app = await testRender(() => (
|
|
40
|
+
+ <TestTuiContexts paths={{ state: temporary.path }}>
|
|
41
|
+
+ <ConfigProvider
|
|
42
|
+
+ config={createTuiResolvedConfig(settings)}
|
|
43
|
+
+ service={{
|
|
44
|
+
+ get: async () => settings,
|
|
45
|
+
+ update: async (update) => {
|
|
46
|
+
+ update(settings)
|
|
47
|
+
+ return settings
|
|
48
|
+
+ },
|
|
49
|
+
+ }}
|
|
50
|
+
+ >
|
|
51
|
+
+ <ThemeProvider
|
|
52
|
+
+ mode="dark"
|
|
53
|
+
+ source={{
|
|
54
|
+
+ ...emptyThemeSource,
|
|
55
|
+
+ discover: async () => ({ "fixture-live": custom, "fixture-dark-only": darkOnly }),
|
|
56
|
+
+ }}
|
|
57
|
+
+ >
|
|
58
|
+
+ <Probe />
|
|
59
|
+
+ </ThemeProvider>
|
|
60
|
+
+ </ConfigProvider>
|
|
61
|
+
+ </TestTuiContexts>
|
|
62
|
+
+ ))
|
|
63
|
+
+
|
|
64
|
+
+ function Probe() {
|
|
65
|
+
+ themes = useThemes()
|
|
66
|
+
+ const host = {
|
|
67
|
+
+ app: {}, client: {}, keymap: {}, shortcuts: {}, keymapState: {}, sessionTabs: {},
|
|
68
|
+
+ themes,
|
|
69
|
+
+ } as unknown as ReturnType<typeof usePluginHost>
|
|
70
|
+
+ const registry: Registry = { has: () => false, set() {}, remove() {}, active: () => true }
|
|
71
|
+
+ plugin = createPluginContext({ host, id: "fixture", options: undefined, owned: [], registry })
|
|
72
|
+
+ return <text>{`${plugin.ui.theme.current().name} ${plugin.ui.theme.current().mode} ${plugin.theme.background.base} ${plugin.theme.text.base}`}</text>
|
|
73
|
+
+ }
|
|
74
|
+
+
|
|
75
|
+
+ try {
|
|
76
|
+
+ app.renderer.start()
|
|
77
|
+
+ await app.waitForFrame(() => themes?.ready)
|
|
78
|
+
+ await app.waitForFrame((frame) => frame.includes("opencode dark"))
|
|
79
|
+
+ expect(plugin.ui.theme.current()).toEqual({ name: "opencode", mode: "dark" })
|
|
80
|
+
+
|
|
81
|
+
+ expect(plugin.ui.theme.set({ name: "fixture-live", mode: "light" })).toBe(true)
|
|
82
|
+
+ await app.waitForFrame((frame) => frame.includes("fixture-live light"))
|
|
83
|
+
+ expect(plugin.ui.theme.current()).toEqual({ name: "fixture-live", mode: "light" })
|
|
84
|
+
+ expect(settings.theme).toEqual({ name: "fixture-live", mode: "light" })
|
|
85
|
+
+ expect(plugin.theme.background.base.toInts()).toEqual([170, 187, 204, 255])
|
|
86
|
+
+ expect(plugin.theme.text.base.toInts()).toEqual([18, 52, 86, 255])
|
|
87
|
+
+
|
|
88
|
+
+ const before = structuredClone(settings.theme)
|
|
89
|
+
+ expect(plugin.ui.theme.set({ name: "missing-theme", mode: "dark" })).toBe(false)
|
|
90
|
+
+ expect(plugin.ui.theme.set({ name: "fixture-dark-only", mode: "light" })).toBe(false)
|
|
91
|
+
+ expect(plugin.ui.theme.current()).toEqual({ name: "fixture-live", mode: "light" })
|
|
92
|
+
+ expect(settings.theme).toEqual(before)
|
|
93
|
+
+ expect(plugin.ui.theme.set({ name: "fixture-dark-only" })).toBe(true)
|
|
94
|
+
+ await app.waitForFrame((frame) => frame.includes("fixture-dark-only dark"))
|
|
95
|
+
+ expect(plugin.theme.background.base.toInts()).toEqual([51, 68, 85, 255])
|
|
96
|
+
+ expect(plugin.ui.theme.current()).toEqual({ name: "fixture-dark-only", mode: "dark" })
|
|
97
|
+
+ expect(settings.theme).toEqual({ name: "fixture-dark-only", mode: "light" })
|
|
98
|
+
+ const afterFallback = structuredClone(settings.theme)
|
|
99
|
+
+ expect(plugin.ui.theme.set({ name: "fixture-live", mode: "invalid" as "dark" })).toBe(false)
|
|
100
|
+
+ await Bun.sleep(20)
|
|
101
|
+
+ expect(plugin.ui.theme.current()).toEqual({ name: "fixture-dark-only", mode: "dark" })
|
|
102
|
+
+ expect(settings.theme).toEqual(afterFallback)
|
|
103
|
+
+ expect(before).toEqual({ name: "fixture-live", mode: "light" })
|
|
104
|
+
+ } finally {
|
|
105
|
+
+ app.renderer.destroy()
|
|
106
|
+
+ }
|
|
107
|
+
+})
|