diff --git a/NOTICE b/NOTICE
index d2c78f4..cc8dbe4 100644
--- a/NOTICE
+++ b/NOTICE
@@ -9,10 +9,11 @@ Licensed under the Apache License, Version 2.0. See LICENSE for the full text.
Third-party software redistributed in OneAgent binaries
================================================================================
-The target-specific dependency inventory is generated from the production Go
-build graph and the frozen frontend production dependency graph. The complete
-inventory is distributed as THIRD_PARTY_NOTICES.md, with the corresponding
-license and notice texts under licenses/ in every binary artifact.
+The target-specific dependency and reviewed static-asset inventory is generated
+from the production Go build graph, the frozen frontend production dependency
+graph, and frontend/src/components/icons/asset-rights.json. The complete inventory
+is distributed as THIRD_PARTY_NOTICES.md, with the corresponding license and
+notice texts under licenses/ in every binary artifact.
The tracked source of that generated material is third_party/. Run
`scripts/generate_third_party_licenses.py --check` to verify that it still
@@ -39,17 +40,13 @@ explicit request.
Third-party trademarks
================================================================================
-The application displays each supported Agent's own published mark so a user
-can tell which tool a row refers to. This is nominative use: the marks
-identify the referenced products and do not imply endorsement, affiliation or
-sponsorship. They are not OneAgent product artwork, are not recoloured or
-restyled, and remain the property of their respective owners. Provenance for
-each file is recorded inline in
-frontend/src/components/icons/agents.tsx.
+The application identifies supported Agents in a row using either a generic
+OneAgent UI symbol or a separately audited image asset. Generic Lucide symbols
+are used for Claude Code, Cursor, Hermes, Kilo CLI, and Aider so their website
+favicons are not copied into the release. The three retained image assets are
+listed with source, license, owner, and SHA-256 in
+frontend/src/components/icons/asset-rights.json.
-Marks included: Claude Code (Anthropic), Codex (OpenAI), Cursor (Anysphere),
-Kilo CLI (Kilo Code), Aider, OpenCode, OpenClaw, Hermes (Nous Research).
-
-Two marks are redistributed under an open-source license rather than as
-favicons: the OpenAI and OpenCode glyphs come from lobehub/icons-static-svg
-(MIT), and the OpenClaw glyph from openclaw/openclaw (MIT).
+This is nominative use: the labels identify the referenced products and do not
+imply endorsement, affiliation or sponsorship. The image assets are not
+recoloured or restyled and remain the property of their respective owners.
diff --git a/docs/internal/cc-switch-reference-notes.md b/docs/internal/cc-switch-reference-notes.md
index e1ad140..d2741bb 100644
--- a/docs/internal/cc-switch-reference-notes.md
+++ b/docs/internal/cc-switch-reference-notes.md
@@ -72,7 +72,7 @@ CC Switch 依赖 **`@lobehub/icons-static-svg`**(MIT,723 个 AI 品牌 SVG
| Kilo CLI | `kilocode.ai/favicon/favicon.svg` |
| Aider | `aider.chat/assets/icons/favicon-32x32.png` |
-因此不再有任何自绘图标。**结论:判断某个品牌「没有官方图标」之前,要查该项目自己的站点,而不是只查一个图标集。**
+这些来源只能证明「站点上存在一个图像」,不能单独证明 OneAgent 有权复制并随二进制再分发。当前实现只保留有完整权利清单的 Codex、OpenCode、OpenClaw 资产;Claude Code、Cursor、Hermes、Kilo CLI 和 Aider 使用通用 Lucide 符号。**结论:判断某个品牌「没有官方图标」之前,要查该项目自己的站点;决定能否随产品再分发之前,还要取得许可证或书面授权。**
两个资产不能直接用,需换等价版本:`openai.com/favicon.svg` 与 `opencode.ai/favicon.svg` 都带 `:root` CSS 变量和 `prefers-color-scheme` 媒体查询,内联进宿主文档会污染全局样式;`aider.chat/assets/logo.svg` 是 200×60 的文字标加高斯模糊滤镜,不是方形图标。
diff --git a/frontend/src/components/icons/agents.test.tsx b/frontend/src/components/icons/agents.test.tsx
index c690fda..554d623 100644
--- a/frontend/src/components/icons/agents.test.tsx
+++ b/frontend/src/components/icons/agents.test.tsx
@@ -1,7 +1,7 @@
import { render } from "@testing-library/react";
import { describe, expect, it } from "vitest";
-import { AGENT_ICON_IDS, AgentIcon, agentMarkSource, agentTagline } from "./agents";
+import { AGENT_ICON_IDS, AgentIcon, agentMarkKind, agentMarkRights, agentMarkSource, agentTagline } from "./agents";
const AUTO_AGENTS = ["codex", "claude-code", "opencode", "kilo-cli", "aider"];
// Shown on the first screen alongside them, so they need marks of their own even
@@ -16,19 +16,33 @@ describe("AgentIcon", () => {
for (const id of ALL) {
expect(AGENT_ICON_IDS).toContain(id);
}
- const sources = ALL.map((id) => {
+ const rendered = ALL.map((id) => {
const { container } = render();
- return container.querySelector("img")?.getAttribute("src") ?? "";
+ return container.innerHTML;
});
- expect(new Set(sources).size).toBe(ALL.length);
- expect(sources.every(Boolean)).toBe(true);
+ expect(new Set(rendered).size).toBe(ALL.length);
+ expect(rendered.every(Boolean)).toBe(true);
});
- it("records where every mark came from", () => {
- // Provenance is the thing that makes these safe to ship: each is the
- // project's own published artwork, not something drawn to look like it.
- for (const id of ALL) {
- expect(agentMarkSource(id)).toMatch(/\.(svg|png)|MIT/);
+ it("uses generic Lucide marks for assets without redistribution evidence", () => {
+ for (const id of ["claude-code", "cursor", "hermes", "kilo-cli", "aider"]) {
+ const { container } = render();
+ expect(agentMarkKind(id)).toBe("generic");
+ expect(container.querySelector("svg[data-mark-kind=generic]")).not.toBeNull();
+ expect(container.querySelector("img")).toBeNull();
+ expect(agentMarkRights(id)).toBeNull();
+ }
+ });
+
+ it("records auditable rights for every redistributed image asset", () => {
+ for (const id of ["codex", "opencode", "openclaw"]) {
+ const rights = agentMarkRights(id);
+ expect(agentMarkKind(id)).toBe("asset");
+ expect(rights?.license).toBe("MIT");
+ expect(rights?.source).toMatch(/^https:\/\//);
+ expect(rights?.sha256).toMatch(/^[a-f0-9]{64}$/);
+ expect(rights?.licenseSource).toMatch(/^licenses\//);
+ expect(agentMarkSource(id)).toBe(rights?.source);
}
expect(agentMarkSource("brand-new-agent")).toBe("");
});
@@ -54,11 +68,13 @@ describe("AgentIcon", () => {
for (const size of [18, 20]) {
for (const id of ALL) {
const { container } = render();
- const img = container.querySelector("img");
- expect(img?.getAttribute("width")).toBe(String(size));
- expect(img?.getAttribute("height")).toBe(String(size));
- // contain, so a mark with tighter padding cannot overflow the box.
- expect(img?.style.objectFit).toBe("contain");
+ const mark = container.querySelector("img, svg");
+ expect(mark?.getAttribute("width")).toBe(String(size));
+ expect(mark?.getAttribute("height")).toBe(String(size));
+ if (mark?.tagName === "IMG") {
+ // contain, so a mark with tighter padding cannot overflow the box.
+ expect((mark as HTMLImageElement).style.objectFit).toBe("contain");
+ }
}
}
});
diff --git a/frontend/src/components/icons/agents.tsx b/frontend/src/components/icons/agents.tsx
index 2f3f4e3..956e636 100644
--- a/frontend/src/components/icons/agents.tsx
+++ b/frontend/src/components/icons/agents.tsx
@@ -1,43 +1,64 @@
/**
- * Agent marks, taken from each project's own published artwork.
+ * Agent marks used to identify rows in OneAgent.
*
- * Earlier revisions drew these by hand to keep one monochrome style. That
- * inverted the priority: a glyph has to be recognised before it is tidy, and a
- * hand-drawn "claw" read as a cup. Every Agent here has an official mark, so
- * every mark is the official one, in the brand's own colours.
+ * Three image assets remain because their exact source, MIT license text,
+ * copyright owner, and SHA-256 are tracked in asset-rights.json. Agents whose
+ * published artwork does not have an auditable redistribution basis use generic
+ * Lucide marks instead; those marks identify a row without copying a vendor
+ * favicon into the release.
*
- * Uniformity now comes from the container rather than from redrawing: one square
- * box, one size per context, consistent padding. That is how a folder of
- * unrelated app icons still reads as a set.
- *
- * Trademark note: these identify which Agent a row refers to — nominative use.
- * They are not OneAgent product artwork and are not recoloured or restyled.
- * Sources are recorded per entry; assets live beside this file and are inlined
- * at build time, since the release policy forbids external references.
+ * Trademark note: the labels identify which Agent a row refers to. The generic
+ * marks are OneAgent UI symbols, not vendor artwork. The three licensed image
+ * marks are not recoloured or restyled.
*/
-import { Bot } from "lucide-react";
+import {
+ Blocks,
+ Bot,
+ Braces,
+ GitBranch,
+ MousePointer2,
+ Sparkles,
+ type LucideIcon,
+} from "lucide-react";
import { sourceTranslate, type Translate, type TranslationKey } from "../../i18n";
-import aiderMark from "./assets/aider.png";
-import claudeMark from "./assets/claude-code.svg";
+import assetRightsManifest from "./asset-rights.json";
import codexMark from "./assets/codex.svg";
-import cursorMark from "./assets/cursor.svg";
-import hermesMark from "./assets/hermes.png";
-import kiloMark from "./assets/kilo-cli.svg";
import openclawMark from "./assets/openclaw.svg";
import opencodeMark from "./assets/opencode.svg";
-/** Where each mark came from, so the next person can re-check it. */
-const MARKS: Record = {
- codex: { src: codexMark, source: "lobehub/icons-static-svg openai (MIT)" },
- "claude-code": { src: claudeMark, source: "claude.ai/favicon.svg" },
- cursor: { src: cursorMark, source: "cursor.com/favicon.svg" },
- opencode: { src: opencodeMark, source: "lobehub/icons-static-svg opencode (MIT)" },
- openclaw: { src: openclawMark, source: "openclaw/openclaw docs/assets/pixel-lobster.svg (MIT)" },
- hermes: { src: hermesMark, source: "hermes-agent.nousresearch.com/icon.png" },
- "kilo-cli": { src: kiloMark, source: "kilocode.ai/favicon/favicon.svg" },
- aider: { src: aiderMark, source: "aider.chat/assets/icons/favicon-32x32.png" },
+type AssetRights = (typeof assetRightsManifest.assets)[keyof typeof assetRightsManifest.assets];
+type AssetMark = { kind: "asset"; src: string; source: string; rights: AssetRights };
+type GenericMark = { kind: "generic"; Icon: LucideIcon; source: string };
+type Mark = AssetMark | GenericMark;
+
+const GENERIC_SOURCE = "lucide-react@1.25.0 (ISC)";
+
+const MARKS: Record = {
+ codex: {
+ kind: "asset",
+ src: codexMark,
+ source: assetRightsManifest.assets.codex.source,
+ rights: assetRightsManifest.assets.codex,
+ },
+ opencode: {
+ kind: "asset",
+ src: opencodeMark,
+ source: assetRightsManifest.assets.opencode.source,
+ rights: assetRightsManifest.assets.opencode,
+ },
+ openclaw: {
+ kind: "asset",
+ src: openclawMark,
+ source: assetRightsManifest.assets.openclaw.source,
+ rights: assetRightsManifest.assets.openclaw,
+ },
+ "claude-code": { kind: "generic", Icon: Braces, source: GENERIC_SOURCE },
+ cursor: { kind: "generic", Icon: MousePointer2, source: GENERIC_SOURCE },
+ hermes: { kind: "generic", Icon: Sparkles, source: GENERIC_SOURCE },
+ "kilo-cli": { kind: "generic", Icon: Blocks, source: GENERIC_SOURCE },
+ aider: { kind: "generic", Icon: GitBranch, source: GENERIC_SOURCE },
};
/** One-line positioning shown on hover; never a restatement of the name. */
@@ -59,17 +80,48 @@ export function agentTagline(agentId: string, t: Translate = sourceTranslate): s
return tagline ? t(tagline) : "";
}
-/** The provenance of an Agent's mark, for the reference notes and tests. */
+/** The source URL or package provenance displayed in reference notes/tests. */
export function agentMarkSource(agentId: string): string {
return MARKS[agentId]?.source ?? "";
}
+/** Whether an Agent uses a licensed image asset, a generic mark, or fallback. */
+export function agentMarkKind(agentId: string): "asset" | "generic" | "fallback" {
+ return MARKS[agentId]?.kind ?? "fallback";
+}
+
+/** Auditable rights for an image asset; generic marks intentionally return null. */
+export function agentMarkRights(agentId: string): AssetRights | null {
+ const mark = MARKS[agentId];
+ return mark?.kind === "asset" ? mark.rights : null;
+}
+
export function AgentIcon({ agentId, size = 18 }: { agentId: string; size?: number }) {
const mark = MARKS[agentId];
if (!mark) {
- // An Agent added to agents.lock.json before its mark is fetched still
+ // An Agent added to agents.lock.json before its mark is assigned still
// renders rather than leaving a blank square.
- return ;
+ return (
+
+ );
+ }
+ if (mark.kind === "generic") {
+ const Icon = mark.Icon;
+ return (
+
+ );
}
return (
diff --git a/frontend/src/components/icons/asset-rights.json b/frontend/src/components/icons/asset-rights.json
new file mode 100644
index 0000000..b06f135
--- /dev/null
+++ b/frontend/src/components/icons/asset-rights.json
@@ -0,0 +1,29 @@
+{
+ "schemaVersion": 1,
+ "assets": {
+ "codex": {
+ "file": "assets/codex.svg",
+ "source": "https://github.com/lobehub/lobe-icons/blob/master/packages/static-svg/icons/openai.svg",
+ "license": "MIT",
+ "licenseSource": "licenses/lobehub/LICENSE",
+ "copyrightOwner": "LobeHub contributors",
+ "sha256": "a595df6b423920c67a7f8f73c063e4bfb72d415948097b6cac063a2366bb5186"
+ },
+ "opencode": {
+ "file": "assets/opencode.svg",
+ "source": "https://github.com/lobehub/lobe-icons/blob/master/packages/static-svg/icons/opencode.svg",
+ "license": "MIT",
+ "licenseSource": "licenses/lobehub/LICENSE",
+ "copyrightOwner": "LobeHub contributors",
+ "sha256": "7cfa6e9d6726f7c9fa26c7d9aef0dfec52d20a137380454340f30f12ccbfd302"
+ },
+ "openclaw": {
+ "file": "assets/openclaw.svg",
+ "source": "https://github.com/openclaw/openclaw/blob/main/docs/assets/pixel-lobster.svg",
+ "license": "MIT",
+ "licenseSource": "licenses/openclaw/LICENSE",
+ "copyrightOwner": "OpenClaw contributors",
+ "sha256": "1ee82ff331b7295074bf6f60a0812c5f64daad65b2395d5d5e47a6c6c5180a7c"
+ }
+ }
+}
diff --git a/frontend/src/components/icons/assets/aider.png b/frontend/src/components/icons/assets/aider.png
deleted file mode 100644
index 0a92130..0000000
Binary files a/frontend/src/components/icons/assets/aider.png and /dev/null differ
diff --git a/frontend/src/components/icons/assets/claude-code.svg b/frontend/src/components/icons/assets/claude-code.svg
deleted file mode 100644
index 26d103d..0000000
--- a/frontend/src/components/icons/assets/claude-code.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/frontend/src/components/icons/assets/cursor.svg b/frontend/src/components/icons/assets/cursor.svg
deleted file mode 100644
index f814fdc..0000000
--- a/frontend/src/components/icons/assets/cursor.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/src/components/icons/assets/hermes.png b/frontend/src/components/icons/assets/hermes.png
deleted file mode 100644
index 2d629d2..0000000
Binary files a/frontend/src/components/icons/assets/hermes.png and /dev/null differ
diff --git a/frontend/src/components/icons/assets/kilo-cli.svg b/frontend/src/components/icons/assets/kilo-cli.svg
deleted file mode 100644
index c4b6bb8..0000000
--- a/frontend/src/components/icons/assets/kilo-cli.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
\ No newline at end of file
diff --git a/frontend/src/components/icons/licenses/lobehub/LICENSE b/frontend/src/components/icons/licenses/lobehub/LICENSE
new file mode 100644
index 0000000..1dd53d2
--- /dev/null
+++ b/frontend/src/components/icons/licenses/lobehub/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 LobeHub
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/frontend/src/components/icons/licenses/openclaw/LICENSE b/frontend/src/components/icons/licenses/openclaw/LICENSE
new file mode 100644
index 0000000..ebaebf7
--- /dev/null
+++ b/frontend/src/components/icons/licenses/openclaw/LICENSE
@@ -0,0 +1,24 @@
+MIT License
+
+Copyright (c) 2026 OpenClaw Foundation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+Third-party notices for incorporated or adapted code are recorded in
+THIRD_PARTY_NOTICES.md.
diff --git a/scripts/generate_third_party_licenses.py b/scripts/generate_third_party_licenses.py
index d5826ef..5685e83 100755
--- a/scripts/generate_third_party_licenses.py
+++ b/scripts/generate_third_party_licenses.py
@@ -11,6 +11,7 @@
import argparse
import dataclasses
import difflib
+import hashlib
import json
import os
import re
@@ -25,6 +26,8 @@
ROOT = Path(__file__).resolve().parent.parent
DEFAULT_OUTPUT = ROOT / "third_party"
FRONTEND = ROOT / "frontend"
+ICON_ROOT = FRONTEND / "src" / "components" / "icons"
+ASSET_RIGHTS_MANIFEST = ICON_ROOT / "asset-rights.json"
TARGETS = (
("macos-x64", "darwin", "amd64", "1"),
("macos-arm64", "darwin", "arm64", "1"),
@@ -187,6 +190,47 @@ def npm_license_source(name: str, package_path: Path) -> Path:
raise RuntimeError(f"npm package {name} has no bundled license file in {package_path}")
+def copy_license_file(source: Path, destination_directory: Path, relative_root: Path) -> tuple[str, ...]:
+ if not source.is_file():
+ raise RuntimeError(f"asset license source does not exist: {source}")
+ destination_directory.mkdir(parents=True, exist_ok=True)
+ destination = destination_directory / "LICENSE"
+ text = source.read_text(encoding="utf-8")
+ normalized = "\n".join(line.rstrip() for line in text.splitlines()).rstrip() + "\n"
+ destination.write_text(normalized, encoding="utf-8")
+ return (destination.relative_to(relative_root).as_posix(),)
+
+
+def collect_asset_dependencies(output: Path) -> list[Dependency]:
+ manifest = json.loads(ASSET_RIGHTS_MANIFEST.read_text(encoding="utf-8"))
+ if manifest.get("schemaVersion") != 1:
+ raise RuntimeError("unsupported asset rights manifest schema")
+ dependencies: list[Dependency] = []
+ for name, rights in sorted(manifest.get("assets", {}).items()):
+ asset = ICON_ROOT / str(rights["file"])
+ digest = hashlib.sha256(asset.read_bytes()).hexdigest()
+ expected = str(rights["sha256"]).lower()
+ if digest != expected:
+ raise RuntimeError(f"asset hash mismatch for {name}: {digest} != {expected}")
+ license_source = ICON_ROOT / str(rights["licenseSource"])
+ copied = copy_license_file(
+ source=license_source,
+ destination_directory=output / "licenses" / "assets" / safe_component(name),
+ relative_root=output,
+ )
+ dependencies.append(
+ Dependency(
+ ecosystem="asset",
+ name=name,
+ version=digest,
+ license=str(rights["license"]),
+ platforms=("frontend",),
+ license_files=copied,
+ )
+ )
+ return dependencies
+
+
def collect_npm_dependencies(output: Path) -> list[Dependency]:
dependencies: list[Dependency] = []
inventory = pnpm_license_inventory()
@@ -245,7 +289,7 @@ def generate(output: Path) -> None:
if output.exists():
shutil.rmtree(output)
output.mkdir(parents=True)
- dependencies = collect_go_dependencies(output) + collect_npm_dependencies(output)
+ dependencies = collect_go_dependencies(output) + collect_npm_dependencies(output) + collect_asset_dependencies(output)
dependencies.sort()
(output / "THIRD_PARTY_NOTICES.md").write_text(render_notice(dependencies), encoding="utf-8")
manifest = {
diff --git a/scripts/test_generate_third_party_licenses.py b/scripts/test_generate_third_party_licenses.py
index d491368..69cedae 100644
--- a/scripts/test_generate_third_party_licenses.py
+++ b/scripts/test_generate_third_party_licenses.py
@@ -7,6 +7,7 @@
from scripts.generate_third_party_licenses import (
Dependency,
+ collect_asset_dependencies,
compare_trees,
copy_license_files,
parse_json_stream,
@@ -37,6 +38,17 @@ def test_normalizes_line_endings_and_trailing_whitespace(self) -> None:
self.assertEqual((output / "licenses/pkg/LICENSE").read_bytes(), b"Line one\nLine two\n")
+class AssetDependencyTests(unittest.TestCase):
+ def test_collects_only_rights_manifest_assets_and_copies_license_text(self) -> None:
+ with tempfile.TemporaryDirectory() as output_dir:
+ dependencies = collect_asset_dependencies(Path(output_dir))
+ self.assertEqual([dependency.name for dependency in dependencies], ["codex", "openclaw", "opencode"])
+ for dependency in dependencies:
+ self.assertEqual(dependency.license, "MIT")
+ for license_file in dependency.license_files:
+ self.assertTrue((Path(output_dir) / license_file).is_file())
+
+
class NoticeRenderingTests(unittest.TestCase):
def test_renders_target_platforms_and_license_paths_deterministically(self) -> None:
dependencies = [
diff --git a/third_party/THIRD_PARTY_NOTICES.md b/third_party/THIRD_PARTY_NOTICES.md
index 8b92327..901250f 100644
--- a/third_party/THIRD_PARTY_NOTICES.md
+++ b/third_party/THIRD_PARTY_NOTICES.md
@@ -9,6 +9,9 @@ the embedded frontend. Full license and notice texts are included under
| Ecosystem | Package | Version | Targets | License | License files |
| --- | --- | --- | --- | --- | --- |
+| asset | `codex` | `a595df6b423920c67a7f8f73c063e4bfb72d415948097b6cac063a2366bb5186` | frontend | MIT | `licenses/assets/codex/LICENSE` |
+| asset | `openclaw` | `1ee82ff331b7295074bf6f60a0812c5f64daad65b2395d5d5e47a6c6c5180a7c` | frontend | MIT | `licenses/assets/openclaw/LICENSE` |
+| asset | `opencode` | `7cfa6e9d6726f7c9fa26c7d9aef0dfec52d20a137380454340f30f12ccbfd302` | frontend | MIT | `licenses/assets/opencode/LICENSE` |
| go | `github.com/adrg/xdg` | `v0.5.3` | macos-arm64, macos-x64, windows-arm64, windows-x64 | MIT | `licenses/go/github.com_adrg_xdg@v0.5.3/LICENSE` |
| go | `github.com/go-ole/go-ole` | `v1.3.0` | windows-arm64, windows-x64 | MIT | `licenses/go/github.com_go-ole_go-ole@v1.3.0/LICENSE` |
| go | `github.com/pelletier/go-toml/v2` | `v2.4.3` | macos-arm64, macos-x64, windows-arm64, windows-x64 | MIT | `licenses/go/github.com_pelletier_go-toml_v2@v2.4.3/LICENSE` |
diff --git a/third_party/licenses/assets/codex/LICENSE b/third_party/licenses/assets/codex/LICENSE
new file mode 100644
index 0000000..1dd53d2
--- /dev/null
+++ b/third_party/licenses/assets/codex/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 LobeHub
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/third_party/licenses/assets/openclaw/LICENSE b/third_party/licenses/assets/openclaw/LICENSE
new file mode 100644
index 0000000..ebaebf7
--- /dev/null
+++ b/third_party/licenses/assets/openclaw/LICENSE
@@ -0,0 +1,24 @@
+MIT License
+
+Copyright (c) 2026 OpenClaw Foundation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+Third-party notices for incorporated or adapted code are recorded in
+THIRD_PARTY_NOTICES.md.
diff --git a/third_party/licenses/assets/opencode/LICENSE b/third_party/licenses/assets/opencode/LICENSE
new file mode 100644
index 0000000..1dd53d2
--- /dev/null
+++ b/third_party/licenses/assets/opencode/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 LobeHub
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/third_party/manifest.json b/third_party/manifest.json
index 6564b58..74376bb 100644
--- a/third_party/manifest.json
+++ b/third_party/manifest.json
@@ -1,5 +1,41 @@
{
"dependencies": [
+ {
+ "ecosystem": "asset",
+ "license": "MIT",
+ "license_files": [
+ "licenses/assets/codex/LICENSE"
+ ],
+ "name": "codex",
+ "platforms": [
+ "frontend"
+ ],
+ "version": "a595df6b423920c67a7f8f73c063e4bfb72d415948097b6cac063a2366bb5186"
+ },
+ {
+ "ecosystem": "asset",
+ "license": "MIT",
+ "license_files": [
+ "licenses/assets/openclaw/LICENSE"
+ ],
+ "name": "openclaw",
+ "platforms": [
+ "frontend"
+ ],
+ "version": "1ee82ff331b7295074bf6f60a0812c5f64daad65b2395d5d5e47a6c6c5180a7c"
+ },
+ {
+ "ecosystem": "asset",
+ "license": "MIT",
+ "license_files": [
+ "licenses/assets/opencode/LICENSE"
+ ],
+ "name": "opencode",
+ "platforms": [
+ "frontend"
+ ],
+ "version": "7cfa6e9d6726f7c9fa26c7d9aef0dfec52d20a137380454340f30f12ccbfd302"
+ },
{
"ecosystem": "go",
"license": "MIT",