diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml
new file mode 100644
index 000000000000..e16eb3b08a5e
--- /dev/null
+++ b/.github/workflows/sync-upstream.yml
@@ -0,0 +1,39 @@
+name: Sync Fork with Upstream
+
+on:
+ schedule:
+ - cron: "0 6 * * *"
+ workflow_dispatch:
+
+jobs:
+ sync:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Rebase on upstream
+ id: rebase
+ continue-on-error: true
+ run: |
+ git remote add upstream https://github.com/anomalyco/opencode.git || true
+ git fetch upstream
+ git checkout dev
+ git rebase upstream/dev
+ git push origin dev --force-with-lease
+
+ - name: Create PR on conflict
+ if: steps.rebase.outcome == 'failure'
+ run: |
+ git rebase --abort
+ git checkout -b sync/conflict-$(date +%Y-%m-%d) upstream/dev
+ git merge origin/dev --no-edit || true
+ git add -A && git commit -m "sync: conflicts $(date +%Y-%m-%d)" --no-verify
+ git push origin sync/conflict-$(date +%Y-%m-%d)
+ gh pr create --base dev --head sync/conflict-$(date +%Y-%m-%d) \
+ --title "Sync upstream - conflicts $(date +%Y-%m-%d)" \
+ --body "Rebase failed due to conflicts. Please resolve manually."
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts
index f0b3fa828a78..46cf42a92f93 100755
--- a/packages/opencode/script/build.ts
+++ b/packages/opencode/script/build.ts
@@ -1,6 +1,6 @@
#!/usr/bin/env bun
-import solidPlugin from "../node_modules/@opentui/solid/scripts/solid-plugin"
+import solidPlugin from "@opentui/solid/bun-plugin"
import path from "path"
import fs from "fs"
import { $ } from "bun"
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
index 77872eedaddd..dafbe5676d71 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -77,6 +77,7 @@ import { PermissionPrompt } from "./permission"
import { QuestionPrompt } from "./question"
import { DialogExportOptions } from "../../ui/dialog-export-options"
import { formatTranscript } from "../../util/transcript"
+import { getSecurityIndicator } from "../../util/provider"
import { UI } from "@/cli/ui.ts"
addDefaultParsers(parsers.parsers)
@@ -1300,7 +1301,22 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
▣{" "}
{" "}
{Locale.titlecase(props.message.mode)}
- · {props.message.modelID}
+ {(() => {
+ const indicator = getSecurityIndicator(props.message.providerID, !!props.message.error)
+ return (
+
+ {" · "}
+ {indicator.label}
+ {props.message.modelID}
+
+ )
+ })()}
· {Locale.duration(duration())}
diff --git a/packages/opencode/src/cli/cmd/tui/util/provider.ts b/packages/opencode/src/cli/cmd/tui/util/provider.ts
new file mode 100644
index 000000000000..d7db4b7db61e
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/util/provider.ts
@@ -0,0 +1,16 @@
+/**
+ * Returns a security indicator label and status for the given provider.
+ *
+ * When the provider is "concrete-security", the channel is expected to be
+ * encrypted end-to-end. If an error occurred during the exchange the
+ * indicator signals an unsecure state; otherwise it confirms the channel
+ * is secure. For any other provider, no indicator is shown.
+ */
+export function getSecurityIndicator(providerID: string, hasError: boolean) {
+ if (providerID === "concrete-security") {
+ return hasError
+ ? { label: "⚠ Unsecure ", status: "error" as const }
+ : { label: "🔐 Secure ", status: "secure" as const }
+ }
+ return { label: "", status: "none" as const }
+}
diff --git a/packages/opencode/test/cli/tui/provider.test.ts b/packages/opencode/test/cli/tui/provider.test.ts
new file mode 100644
index 000000000000..ce42edec6565
--- /dev/null
+++ b/packages/opencode/test/cli/tui/provider.test.ts
@@ -0,0 +1,31 @@
+/**
+ * Tests for the security indicator displayed in the session header.
+ *
+ * The "concrete-security" provider uses an encrypted channel, so the UI
+ * shows a visual cue reflecting whether the exchange succeeded (secure)
+ * or failed (unsecure). Other providers should produce no indicator.
+ */
+import { describe, expect, test } from "bun:test"
+import { getSecurityIndicator } from "../../../src/cli/cmd/tui/util/provider"
+
+describe("getSecurityIndicator", () => {
+ // When the provider is "concrete-security" and no error occurred,
+ // the channel is considered secure.
+ test("concrete-security without error returns secure", () => {
+ const result = getSecurityIndicator("concrete-security", false)
+ expect(result).toEqual({ label: "🔐 Secure ", status: "secure" })
+ })
+
+ // When the provider is "concrete-security" but an error occurred,
+ // the channel is flagged as unsecure.
+ test("concrete-security with error returns unsecure", () => {
+ const result = getSecurityIndicator("concrete-security", true)
+ expect(result).toEqual({ label: "⚠ Unsecure ", status: "error" })
+ })
+
+ // Non-regression: any other provider should produce no indicator at all.
+ test("other provider returns empty label", () => {
+ const result = getSecurityIndicator("anthropic", false)
+ expect(result).toEqual({ label: "", status: "none" })
+ })
+})
diff --git a/user_secure_configuration_example.json b/user_secure_configuration_example.json
new file mode 100644
index 000000000000..6e1b11c49570
--- /dev/null
+++ b/user_secure_configuration_example.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://opencode.ai/config.json",
+ "model": "concrete-security/secure-gpt-oss-120b",
+ "provider": {
+ "opencode": { "options": {} },
+ "concrete-security": {
+ "npm": "file:///Users/kcelia/Concrete-Security/ratls/ai-provider/dist/index.js",
+ "api": "https://vllm.concrete-security.com/v1",
+ "options": { "sdk": "@ai-sdk/anthropic", "apiKey": "dummy-key" },
+ "models": {
+ "secure-gpt-oss-120b": {
+ "id": "openai/gpt-oss-120b",
+ "name": "Secure gpt-oss-120b"
+ }
+ }
+ }
+ },
+ "mcp": {},
+ "tools": { "github-triage": false, "github-pr-search": false }
+}