@harness-mix/cli 0.1.8 → 0.1.9

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.
Files changed (132) hide show
  1. package/NOTICE +12 -0
  2. package/README.md +51 -43
  3. package/docs/harness-management.md +1 -1
  4. package/docs/native-acp.md +7 -2
  5. package/docs/task-handoff-design.md +65 -0
  6. package/licenses/codex-styler-Apache-2.0.txt +193 -0
  7. package/licenses/heige-codex-skin-studio-MIT.txt +22 -0
  8. package/output/native-build/desktop-controller.mjs +2 -1
  9. package/output/native-build/renderer-extension.js +651 -9
  10. package/package.json +188 -188
  11. package/scripts/acp-capabilities-probe.cjs +1 -0
  12. package/scripts/build-native.cjs +1 -1
  13. package/scripts/codex-adapter-test.cjs +44 -0
  14. package/scripts/collaboration-recovery-test.cjs +3 -0
  15. package/scripts/e2e-codex.cjs +3 -1
  16. package/scripts/e2e-native-acp.cjs +1 -1
  17. package/scripts/error-kind-test.cjs +107 -0
  18. package/scripts/handoff-checkpoint-test.cjs +38 -0
  19. package/scripts/integrations-test.cjs +5 -2
  20. package/scripts/kiro-cursor-adapters-test.cjs +18 -0
  21. package/scripts/native-acp-depth-test.cjs +1 -1
  22. package/scripts/native-protocol-test.cjs +60 -4
  23. package/scripts/native-ui-smoke.cjs +92 -2
  24. package/scripts/switch-harness-test.cjs +79 -12
  25. package/scripts/tool-concurrency-regression-test.cjs +8 -6
  26. package/src/assets/icons/cline-color.svg +1 -0
  27. package/src/assets/skins/codex-styler/ASSET_LICENSES.md +36 -0
  28. package/src/assets/skins/codex-styler/NOTICE +11 -0
  29. package/src/assets/skins/codex-styler/themes/gilded-grandeur/hero.webp +0 -0
  30. package/src/assets/skins/codex-styler/themes/merry-big-top/hero.webp +0 -0
  31. package/src/assets/skins/codex-styler/themes/nocturne-studio/hero.webp +0 -0
  32. package/src/assets/skins/codex-styler/themes/quiet-garden/hero.webp +0 -0
  33. package/src/assets/skins/heige/ASSET_PROVENANCE.md +56 -0
  34. package/src/assets/skins/heige/NOTICE.md +13 -0
  35. package/src/assets/skins/heige/themes/dalao-dianyan/hero.webp +0 -0
  36. package/src/assets/skins/heige/themes/dalao-dianyan/theme.json +14 -0
  37. package/src/assets/skins/heige/themes/deepspace-dawn/hero.webp +0 -0
  38. package/src/assets/skins/heige/themes/deepspace-dawn/theme.json +14 -0
  39. package/src/assets/skins/heige/themes/deepspace-star/hero.webp +0 -0
  40. package/src/assets/skins/heige/themes/deepspace-star/theme.json +14 -0
  41. package/src/assets/skins/heige/themes/dragonball-nimbus/hero.webp +0 -0
  42. package/src/assets/skins/heige/themes/dragonball-nimbus/theme.json +16 -0
  43. package/src/assets/skins/heige/themes/dragonball-super-saiyan/hero.webp +0 -0
  44. package/src/assets/skins/heige/themes/dragonball-super-saiyan/theme.json +16 -0
  45. package/src/assets/skins/heige/themes/genshin-dawn/hero.webp +0 -0
  46. package/src/assets/skins/heige/themes/genshin-dawn/theme.json +14 -0
  47. package/src/assets/skins/heige/themes/genshin-night/hero.webp +0 -0
  48. package/src/assets/skins/heige/themes/genshin-night/theme.json +14 -0
  49. package/src/assets/skins/heige/themes/miku-488137/hero.webp +0 -0
  50. package/src/assets/skins/heige/themes/miku-488137/logo.webp +0 -0
  51. package/src/assets/skins/heige/themes/miku-488137/polaroid.webp +0 -0
  52. package/src/assets/skins/heige/themes/miku-488137/theme.json +16 -0
  53. package/src/assets/skins/heige/themes/naruto-hokage/hero.webp +0 -0
  54. package/src/assets/skins/heige/themes/naruto-hokage/theme.json +14 -0
  55. package/src/assets/skins/heige/themes/naruto-sasuke/hero.webp +0 -0
  56. package/src/assets/skins/heige/themes/naruto-sasuke/theme.json +14 -0
  57. package/src/assets/skins/heige/themes/wuthering-echo/hero.webp +0 -0
  58. package/src/assets/skins/heige/themes/wuthering-echo/theme.json +14 -0
  59. package/src/assets/skins/heige/themes/wuthering-tide/hero.webp +0 -0
  60. package/src/assets/skins/heige/themes/wuthering-tide/theme.json +14 -0
  61. package/src/main/adapters/cline.js +22 -0
  62. package/src/main/adapters/codex.js +78 -8
  63. package/src/main/adapters/index.js +2 -1
  64. package/src/main/adapters/native-acp-command.js +1 -0
  65. package/src/main/harness-adapter/error-kind.js +82 -0
  66. package/src/main/harness-adapter/event-normalizer.js +7 -1
  67. package/src/main/host/core-session.js +1 -1
  68. package/src/main/host/handoff-access.js +23 -4
  69. package/src/main/host/handoff-checkpoints.js +9 -2
  70. package/src/main/host/handoff-tools.js +3 -0
  71. package/src/main/host/handoff.js +11 -2
  72. package/src/main/host/runtime.js +272 -163
  73. package/src/main/host/session-history.js +54 -15
  74. package/src/main/native/icons.js +3 -2
  75. package/src/main/native/pets.js +234 -0
  76. package/src/main/native/protocol.js +197 -56
  77. package/src/main/protocol-core/projector.js +44 -41
  78. package/src/native-ui/desktop-control/dist/production-controller.d.ts.map +1 -1
  79. package/src/native-ui/desktop-control/dist/production-controller.js +1 -0
  80. package/src/native-ui/desktop-control/dist/production-controller.js.map +1 -1
  81. package/src/native-ui/desktop-control/dist/tsconfig.tsbuildinfo +1 -1
  82. package/src/native-ui/desktop-control/src/production-controller.ts +1 -0
  83. package/src/native-ui/desktop-control/test/production-controller.test.ts +1 -0
  84. package/src/native-ui/renderer-extension/dist/types/agent-selection-state.d.ts +4 -2
  85. package/src/native-ui/renderer-extension/dist/types/agent-selection-state.d.ts.map +1 -1
  86. package/src/native-ui/renderer-extension/dist/types/renderer-agent-icon.d.ts.map +1 -1
  87. package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts.map +1 -1
  88. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
  89. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts.map +1 -1
  90. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts.map +1 -1
  91. package/src/native-ui/renderer-extension/dist/types/renderer-sidebar-agent-icons.d.ts.map +1 -1
  92. package/src/native-ui/renderer-extension/dist/types/settings/connections-page.d.ts.map +1 -1
  93. package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts +1 -1
  94. package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts.map +1 -1
  95. package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts.map +1 -1
  96. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +1 -1
  97. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
  98. package/src/native-ui/renderer-extension/dist/types/settings/shell.d.ts.map +1 -1
  99. package/src/native-ui/renderer-extension/dist/types/settings/skin-market.d.ts +4 -0
  100. package/src/native-ui/renderer-extension/dist/types/settings/skin-market.d.ts.map +1 -0
  101. package/src/native-ui/renderer-extension/dist/types/settings/skin-runtime.d.ts +31 -0
  102. package/src/native-ui/renderer-extension/dist/types/settings/skin-runtime.d.ts.map +1 -0
  103. package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
  104. package/src/native-ui/renderer-extension/dist/types/versioned-renderer-adapter.d.ts.map +1 -1
  105. package/src/native-ui/renderer-extension/package.json +1 -1
  106. package/src/native-ui/renderer-extension/src/agent-selection-state.ts +16 -0
  107. package/src/native-ui/renderer-extension/src/assets.d.ts +5 -0
  108. package/src/native-ui/renderer-extension/src/renderer-agent-icon.ts +5 -4
  109. package/src/native-ui/renderer-extension/src/renderer-agent-picker.ts +1 -0
  110. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +9 -6
  111. package/src/native-ui/renderer-extension/src/renderer-harness-handoff.ts +2 -2
  112. package/src/native-ui/renderer-extension/src/renderer-harness-mentions.ts +2 -0
  113. package/src/native-ui/renderer-extension/src/renderer-settings-lifecycle.ts +2 -0
  114. package/src/native-ui/renderer-extension/src/renderer-sidebar-agent-icons.ts +1 -0
  115. package/src/native-ui/renderer-extension/src/settings/connections-page.ts +2 -0
  116. package/src/native-ui/renderer-extension/src/settings/icons.ts +6 -0
  117. package/src/native-ui/renderer-extension/src/settings/localization.ts +4 -2
  118. package/src/native-ui/renderer-extension/src/settings/pages.ts +5 -2
  119. package/src/native-ui/renderer-extension/src/settings/shell.ts +3 -2
  120. package/src/native-ui/renderer-extension/src/settings/skin-market.css +213 -0
  121. package/src/native-ui/renderer-extension/src/settings/skin-market.ts +174 -0
  122. package/src/native-ui/renderer-extension/src/settings/skin-runtime.ts +399 -0
  123. package/src/native-ui/renderer-extension/src/versioned-renderer-adapter.ts +2 -1
  124. package/src/native-ui/renderer-extension/test/settings/localization.test.ts +11 -1
  125. package/src/native-ui/renderer-extension/test/settings/shell.test.ts +3 -0
  126. package/src/native-ui/renderer-extension/test/settings/skin-runtime.test.ts +136 -0
  127. package/src/native-ui/shared-contracts/dist/thread-harness-switch.d.ts +7 -0
  128. package/src/native-ui/shared-contracts/dist/thread-harness-switch.d.ts.map +1 -1
  129. package/src/native-ui/shared-contracts/dist/thread-harness-switch.js +3 -0
  130. package/src/native-ui/shared-contracts/dist/thread-harness-switch.js.map +1 -1
  131. package/src/native-ui/shared-contracts/dist/tsconfig.tsbuildinfo +1 -1
  132. package/src/native-ui/shared-contracts/src/thread-harness-switch.ts +3 -0
@@ -0,0 +1,399 @@
1
+ import dalaoDianyanHero from "../../../../assets/skins/heige/themes/dalao-dianyan/hero.webp";
2
+ import deepspaceDawnHero from "../../../../assets/skins/heige/themes/deepspace-dawn/hero.webp";
3
+ import deepspaceStarHero from "../../../../assets/skins/heige/themes/deepspace-star/hero.webp";
4
+ import dragonballNimbusHero from "../../../../assets/skins/heige/themes/dragonball-nimbus/hero.webp";
5
+ import dragonballSuperSaiyanHero from "../../../../assets/skins/heige/themes/dragonball-super-saiyan/hero.webp";
6
+ import genshinDawnHero from "../../../../assets/skins/heige/themes/genshin-dawn/hero.webp";
7
+ import genshinNightHero from "../../../../assets/skins/heige/themes/genshin-night/hero.webp";
8
+ import mikuHero from "../../../../assets/skins/heige/themes/miku-488137/hero.webp";
9
+ import mikuLogo from "../../../../assets/skins/heige/themes/miku-488137/logo.webp";
10
+ import mikuPolaroid from "../../../../assets/skins/heige/themes/miku-488137/polaroid.webp";
11
+ import narutoHokageHero from "../../../../assets/skins/heige/themes/naruto-hokage/hero.webp";
12
+ import narutoSasukeHero from "../../../../assets/skins/heige/themes/naruto-sasuke/hero.webp";
13
+ import wutheringEchoHero from "../../../../assets/skins/heige/themes/wuthering-echo/hero.webp";
14
+ import wutheringTideHero from "../../../../assets/skins/heige/themes/wuthering-tide/hero.webp";
15
+ import gildedGrandeurHero from "../../../../assets/skins/codex-styler/themes/gilded-grandeur/hero.webp";
16
+ import merryBigTopHero from "../../../../assets/skins/codex-styler/themes/merry-big-top/hero.webp";
17
+ import nocturneStudioHero from "../../../../assets/skins/codex-styler/themes/nocturne-studio/hero.webp";
18
+ import quietGardenHero from "../../../../assets/skins/codex-styler/themes/quiet-garden/hero.webp";
19
+
20
+ export const RENDERER_SKIN_STORAGE_KEY = "harness-mix.renderer-skin.v1";
21
+ export const RENDERER_SKIN_STYLE_ID = "harness-mix-renderer-skin";
22
+ export const RENDERER_SKIN_ATTRIBUTE = "data-harness-mix-skin";
23
+
24
+ export const RENDERER_SKIN_IDS = [
25
+ "native", "miku-488137", "genshin-dawn", "genshin-night", "wuthering-echo",
26
+ "wuthering-tide", "naruto-hokage", "naruto-sasuke", "deepspace-dawn",
27
+ "deepspace-star", "dragonball-nimbus", "dragonball-super-saiyan", "dalao-dianyan",
28
+ "styler-gilded-grandeur", "styler-merry-big-top", "styler-nocturne-studio",
29
+ "styler-quiet-garden",
30
+ ] as const;
31
+
32
+ export type RendererSkinId = (typeof RENDERER_SKIN_IDS)[number];
33
+
34
+ export interface RendererSkinDefinition {
35
+ readonly id: RendererSkinId;
36
+ readonly name: string;
37
+ readonly sourceName: string;
38
+ readonly sourceUrl: string | null;
39
+ readonly heroUrl: string | null;
40
+ readonly logoUrl: string | null;
41
+ readonly polaroidUrl: string | null;
42
+ readonly palette: readonly [surface: string, secondary: string, accent: string, text: string];
43
+ readonly preview: string;
44
+ readonly focus: string;
45
+ readonly dark: boolean;
46
+ }
47
+
48
+ const HEIGE_SOURCE_URL = "https://github.com/HeiGeAi/heige-codex-skin-studio";
49
+ const CODEX_STYLER_SOURCE_URL = "https://github.com/xuhuanstudio/codex-styler";
50
+
51
+ function heigeSkin(
52
+ id: Exclude<RendererSkinId, "native">,
53
+ name: string,
54
+ heroUrl: string,
55
+ palette: RendererSkinDefinition["palette"],
56
+ focus: string,
57
+ dark: boolean,
58
+ decorations: Pick<RendererSkinDefinition, "logoUrl" | "polaroidUrl"> = {
59
+ logoUrl: null,
60
+ polaroidUrl: null,
61
+ },
62
+ ): RendererSkinDefinition {
63
+ return Object.freeze({
64
+ id, name, heroUrl, palette, focus, dark, ...decorations,
65
+ sourceName: "HeiGe Codex Skin Studio",
66
+ sourceUrl: HEIGE_SOURCE_URL,
67
+ preview: `linear-gradient(${dark ? "rgb(3 7 12 / 16%)" : "rgb(255 255 255 / 8%)"}, transparent)`,
68
+ });
69
+ }
70
+
71
+ function codexStylerSkin(
72
+ id: Exclude<RendererSkinId, "native">,
73
+ name: string,
74
+ heroUrl: string,
75
+ palette: RendererSkinDefinition["palette"],
76
+ focus: string,
77
+ ): RendererSkinDefinition {
78
+ return Object.freeze({
79
+ id, name, heroUrl, logoUrl: null, polaroidUrl: null, palette, focus, dark: true,
80
+ sourceName: "Codex Styler · CC BY 4.0",
81
+ sourceUrl: CODEX_STYLER_SOURCE_URL,
82
+ preview: "linear-gradient(rgb(3 7 12 / 16%), transparent)",
83
+ });
84
+ }
85
+
86
+ export const RENDERER_SKINS: readonly RendererSkinDefinition[] = Object.freeze([
87
+ Object.freeze({
88
+ id: "native", name: "Native Codex", sourceName: "OpenAI Codex", sourceUrl: null,
89
+ heroUrl: null, logoUrl: null, polaroidUrl: null,
90
+ palette: ["#f7f7f7", "#d7d7d7", "#171717", "#171717"] as const,
91
+ preview: "linear-gradient(145deg, #f8f8f8 0 58%, #ececec 58% 100%)",
92
+ focus: "50% 50%", dark: false,
93
+ }),
94
+ heigeSkin(
95
+ "miku-488137",
96
+ "Miku 488137",
97
+ mikuHero,
98
+ ["#f5f6fc", "#ed6ec1", "#19c9e5", "#122c60"],
99
+ "50% 33%",
100
+ false,
101
+ { logoUrl: mikuLogo, polaroidUrl: mikuPolaroid },
102
+ ),
103
+ heigeSkin("genshin-dawn", "原神 · 晨曦", genshinDawnHero, ["#f2f1fb", "#e0aa3e", "#5b7fd6", "#2c3a6b"], "50% 24%", false),
104
+ heigeSkin("genshin-night", "原神 · 星夜", genshinNightHero, ["#171a2e", "#7a86d8", "#e0b458", "#f0e6c8"], "50% 17%", true),
105
+ heigeSkin("wuthering-echo", "鸣潮 · 共鸣", wutheringEchoHero, ["#16121f", "#a98fe8", "#56e0d8", "#e4def2"], "50% 27%", true),
106
+ heigeSkin("wuthering-tide", "鸣潮 · 声骸", wutheringTideHero, ["#0d1418", "#9aa8b0", "#3fd6d0", "#d8eef0"], "50% 30%", true),
107
+ heigeSkin("naruto-hokage", "火影 · 鸣人", narutoHokageHero, ["#17110b", "#ffd166", "#f2801e", "#ffe3c2"], "50% 27%", true),
108
+ heigeSkin("naruto-sasuke", "火影 · 佐助", narutoSasukeHero, ["#171019", "#7fb3ff", "#d8443c", "#ffd9d2"], "50% 24%", true),
109
+ heigeSkin("deepspace-dawn", "恋与深空 · 晨曦", deepspaceDawnHero, ["#f6f2fb", "#f097c8", "#8f7fe8", "#4a4668"], "50% 8%", false),
110
+ heigeSkin("deepspace-star", "恋与深空 · 星辰", deepspaceStarHero, ["#201a40", "#f097c8", "#9d8bff", "#e8e2ff"], "50% 8%", true),
111
+ heigeSkin("dragonball-nimbus", "龙珠 · 筋斗云", dragonballNimbusHero, ["#f3f7ff", "#f6c445", "#4fc3f7", "#14213d"], "72% 24%", false),
112
+ heigeSkin("dragonball-super-saiyan", "龙珠 · 超级赛亚人", dragonballSuperSaiyanHero, ["#fff8e8", "#52c7f2", "#f5c451", "#282033"], "67% 3%", false),
113
+ heigeSkin("dalao-dianyan", "大佬 · 点烟", dalaoDianyanHero, ["#111111", "#9aa3b0", "#e09a52", "#f2e8da"], "50% 8%", true),
114
+ codexStylerSkin("styler-gilded-grandeur", "金辉盛境", gildedGrandeurHero, ["#090704", "#5b4721", "#e8bd55", "#fff2cd"], "54% 48%"),
115
+ codexStylerSkin("styler-merry-big-top", "欢乐大帐篷", merryBigTopHero, ["#100b14", "#5a3d4b", "#ff755e", "#fff2dc"], "55% 48%"),
116
+ codexStylerSkin("styler-nocturne-studio", "夜曲工作室", nocturneStudioHero, ["#090b0d", "#34383a", "#e9a066", "#f5efe6"], "58% 46%"),
117
+ codexStylerSkin("styler-quiet-garden", "静谧花园", quietGardenHero, ["#101612", "#364339", "#9fc29a", "#edf2e8"], "52% 50%"),
118
+ ]);
119
+
120
+ interface RendererSkinStorage {
121
+ getItem(key: string): string | null;
122
+ setItem(key: string, value: string): void;
123
+ removeItem(key: string): void;
124
+ }
125
+
126
+ function documentSkinStorage(ownerDocument: Document): RendererSkinStorage | null {
127
+ try { return ownerDocument.defaultView?.localStorage ?? null; } catch { return null; }
128
+ }
129
+
130
+ function isRendererSkinId(value: string | null): value is RendererSkinId {
131
+ return (RENDERER_SKIN_IDS as readonly string[]).includes(value ?? "");
132
+ }
133
+
134
+ export function rendererSkinDefinition(id: RendererSkinId): RendererSkinDefinition {
135
+ return RENDERER_SKINS.find((skin) => skin.id === id) ?? RENDERER_SKINS[0]!;
136
+ }
137
+
138
+ export function readRendererSkin(storage?: RendererSkinStorage | null): RendererSkinId {
139
+ try {
140
+ const stored = storage?.getItem(RENDERER_SKIN_STORAGE_KEY) ?? null;
141
+ return isRendererSkinId(stored) ? stored : "native";
142
+ } catch { return "native"; }
143
+ }
144
+
145
+ export function readActiveRendererSkin(ownerDocument: Document = document): RendererSkinId {
146
+ const active = ownerDocument.documentElement.getAttribute(RENDERER_SKIN_ATTRIBUTE);
147
+ return isRendererSkinId(active) ? active : readRendererSkin(documentSkinStorage(ownerDocument));
148
+ }
149
+
150
+ function skinCss(skin: RendererSkinDefinition): string {
151
+ const [surface, secondary, accent, text] = skin.palette;
152
+ const image = skin.heroUrl ? `url("${skin.heroUrl}")` : skin.preview;
153
+ const sidebarVeil = skin.dark
154
+ ? `color-mix(in srgb, ${surface} 91%, transparent)`
155
+ : `color-mix(in srgb, ${surface} 88%, transparent)`;
156
+ const lowerVeil = skin.dark
157
+ ? `color-mix(in srgb, ${surface} 68%, transparent)`
158
+ : `color-mix(in srgb, ${surface} 72%, transparent)`;
159
+ const panelOpacity = skin.dark ? "84%" : "82%";
160
+ const secondaryText = `color-mix(in srgb, ${text} 72%, transparent)`;
161
+ const tertiaryText = `color-mix(in srgb, ${text} 54%, transparent)`;
162
+ const logoCss = skin.logoUrl ? `
163
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] .app-shell-left-panel button[aria-haspopup="menu"][aria-label*="ChatGPT"],
164
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] .app-shell-left-panel button[aria-haspopup="menu"][aria-label*="Codex"] {
165
+ width: min(214px, calc(100% - 12px)); height: 72px !important; margin: 4px 6px 0;
166
+ background: url("${skin.logoUrl}") left center / contain no-repeat !important;
167
+ }
168
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] .app-shell-left-panel button[aria-haspopup="menu"][aria-label*="ChatGPT"] > :where(span, svg),
169
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] .app-shell-left-panel button[aria-haspopup="menu"][aria-label*="Codex"] > :where(span, svg) { visibility: hidden !important; }
170
+ ` : "";
171
+ const polaroidCss = skin.polaroidUrl ? `
172
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] body::after {
173
+ content: ""; position: fixed; right: clamp(12px, 2vw, 24px); bottom: clamp(72px, 11vh, 108px);
174
+ width: clamp(108px, 11.5vw, 168px); aspect-ratio: 2 / 3;
175
+ background: url("${skin.polaroidUrl}") center / contain no-repeat;
176
+ filter: drop-shadow(0 12px 26px color-mix(in srgb, ${text} 24%, transparent));
177
+ pointer-events: none; z-index: 15;
178
+ }
179
+ @media (max-width: 760px) {
180
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] body::after { display: none; }
181
+ }
182
+ ` : "";
183
+ return `
184
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] {
185
+ color-scheme: ${skin.dark ? "dark" : "light"};
186
+ --color-text: ${text}; --color-text-primary: ${text}; --color-token-text-primary: ${text};
187
+ --color-token-foreground: ${text}; --color-text-prose: ${text}; --color-text-emphasis: ${text};
188
+ --color-text-primary-surface: ${text}; --color-text-primary-soft: ${text};
189
+ --color-text-primary-soft-alt: ${text}; --color-text-primary-ghost: ${text};
190
+ --color-text-primary-ghost-hover: ${text}; --color-text-primary-outline: ${text};
191
+ --color-text-primary-outline-hover: ${text};
192
+ --color-text-secondary: ${secondaryText}; --color-token-text-secondary: ${secondaryText};
193
+ --color-text-secondary-soft: ${secondaryText}; --color-text-secondary-soft-alt: ${secondaryText};
194
+ --color-text-secondary-ghost: ${secondaryText}; --color-text-secondary-ghost-hover: ${text};
195
+ --color-text-secondary-outline: ${secondaryText}; --color-text-secondary-outline-hover: ${text};
196
+ --color-text-secondary-solid: ${secondaryText};
197
+ --color-text-tertiary: ${tertiaryText}; --color-token-text-tertiary: ${tertiaryText};
198
+ --color-token-description-foreground: ${tertiaryText};
199
+ --color-token-dropdown-foreground: ${text};
200
+ --app-color-text-foreground: ${text}; --app-color-text-foreground-secondary: ${secondaryText};
201
+ --app-color-text-foreground-tertiary: ${tertiaryText};
202
+ --app-color-foreground-application-menu: ${text};
203
+ --app-color-text-button-secondary: ${text}; --app-color-text-button-tertiary: ${tertiaryText};
204
+ --wb-text-primary: ${text}; --wb-text-secondary: ${secondaryText}; --wb-text-tertiary: ${tertiaryText};
205
+ --color-control-thumb-foreground: ${text};
206
+ --color-text-mode-toggle-inactive: ${secondaryText};
207
+ --color-surface: color-mix(in srgb, ${surface} 92%, transparent);
208
+ --color-surface-secondary: color-mix(in srgb, ${surface} 88%, transparent);
209
+ --color-surface-tertiary: color-mix(in srgb, ${surface} 94%, transparent);
210
+ --color-surface-elevated: color-mix(in srgb, ${surface} 96%, transparent);
211
+ --color-surface-elevated-secondary: color-mix(in srgb, ${surface} 96%, transparent);
212
+ --color-background-surface: color-mix(in srgb, ${surface} 90%, transparent);
213
+ --color-background-panel: color-mix(in srgb, ${surface} 94%, transparent);
214
+ --color-background-callout-surface: color-mix(in srgb, ${surface} 94%, transparent);
215
+ --color-background-control-opaque: ${surface};
216
+ --color-background-page-search: color-mix(in srgb, ${surface} 92%, transparent);
217
+ --color-background-composer-action-bar: color-mix(in srgb, ${surface} 88%, transparent);
218
+ --color-background-execution-output: color-mix(in srgb, ${surface} 88%, transparent);
219
+ --color-background-primary-soft: color-mix(in srgb, ${surface} 92%, transparent);
220
+ --color-background-primary-soft-alpha: color-mix(in srgb, ${surface} 88%, transparent);
221
+ --color-background-primary-soft-hover: color-mix(in srgb, ${surface} 80%, ${accent});
222
+ --color-background-primary-soft-active: color-mix(in srgb, ${surface} 72%, ${accent});
223
+ --color-background-primary-ghost-hover: color-mix(in srgb, ${text} 8%, transparent);
224
+ --color-background-secondary-soft: color-mix(in srgb, ${text} 7%, transparent);
225
+ --color-background-secondary-soft-alpha: color-mix(in srgb, ${text} 7%, transparent);
226
+ --color-background-secondary-soft-hover: color-mix(in srgb, ${text} 11%, transparent);
227
+ --color-background-other-user-message: color-mix(in srgb, ${surface} 76%, transparent);
228
+ --color-background-user-message-compact: color-mix(in srgb, ${surface} 88%, transparent);
229
+ --color-border: color-mix(in srgb, ${text} 16%, transparent);
230
+ --color-border-subtle: color-mix(in srgb, ${text} 8%, transparent);
231
+ --color-border-strong: color-mix(in srgb, ${text} 20%, transparent);
232
+ --color-token-main-surface-primary: color-mix(in srgb, ${surface} 92%, transparent);
233
+ --color-token-dropdown-background: color-mix(in srgb, ${surface} 96%, transparent);
234
+ --color-token-list-hover-background: color-mix(in srgb, ${text} 8%, transparent);
235
+ --color-token-border: color-mix(in srgb, ${text} 16%, transparent);
236
+ --color-token-border-default: color-mix(in srgb, ${text} 16%, transparent);
237
+ --color-token-border-light: color-mix(in srgb, ${text} 9%, transparent);
238
+ --color-token-border-heavy: color-mix(in srgb, ${text} 22%, transparent);
239
+ --color-token-input-border: color-mix(in srgb, ${text} 20%, transparent);
240
+ --color-codex-diff-surface: color-mix(in srgb, ${surface} 94%, ${text});
241
+ --color-token-diff-surface: color-mix(in srgb, ${surface} 94%, ${text});
242
+ --color-codex-editor-inline-code-background: color-mix(in srgb, ${surface} 92%, transparent);
243
+ --color-codex-terminal-background: ${surface};
244
+ --codex-base-surface: ${surface};
245
+ --app-color-background-surface: color-mix(in srgb, ${surface} 92%, transparent);
246
+ --app-color-background-surface-under: color-mix(in srgb, ${surface} 88%, transparent);
247
+ --app-color-background-control: color-mix(in srgb, ${surface} 96%, transparent);
248
+ --app-color-background-elevated-primary: color-mix(in srgb, ${surface} 96%, transparent);
249
+ --app-color-background-elevated-primary-opaque: ${surface};
250
+ --app-color-background-elevated-secondary: color-mix(in srgb, ${surface} 96%, transparent);
251
+ --app-color-background-elevated-secondary-opaque: ${surface};
252
+ --app-color-background-editor-opaque: ${surface};
253
+ --app-color-background-application-menu: color-mix(in srgb, ${surface} 96%, transparent);
254
+ --app-color-background-button-secondary: color-mix(in srgb, ${text} 7%, transparent);
255
+ --app-color-background-button-secondary-hover: color-mix(in srgb, ${text} 11%, transparent);
256
+ --app-color-border: color-mix(in srgb, ${text} 16%, transparent);
257
+ --app-color-border-light: color-mix(in srgb, ${text} 9%, transparent);
258
+ --app-color-border-heavy: color-mix(in srgb, ${text} 22%, transparent);
259
+ --wb-surface-primary: color-mix(in srgb, ${surface} 92%, transparent);
260
+ --wb-surface-secondary: color-mix(in srgb, ${surface} 88%, transparent);
261
+ --wb-border: color-mix(in srgb, ${text} 16%, transparent);
262
+ --wb-border-hover: color-mix(in srgb, ${text} 22%, transparent);
263
+ --vscode-foreground: ${text}; --vscode-descriptionForeground: ${secondaryText};
264
+ --vscode-editor-foreground: ${text}; --vscode-input-foreground: ${text};
265
+ --vscode-editor-background: ${surface}; --vscode-editorPane-background: ${surface};
266
+ --vscode-input-background: color-mix(in srgb, ${surface} 96%, transparent);
267
+ --vscode-dropdown-background: color-mix(in srgb, ${surface} 96%, transparent);
268
+ --vscode-menu-background: color-mix(in srgb, ${surface} 96%, transparent);
269
+ --vscode-panel-background: color-mix(in srgb, ${surface} 96%, transparent);
270
+ --vscode-terminal-background: ${surface};
271
+ --vscode-list-hoverBackground: color-mix(in srgb, ${text} 8%, transparent);
272
+ --vscode-list-activeSelectionBackground: color-mix(in srgb, ${accent} 24%, transparent);
273
+ --vscode-list-activeSelectionForeground: ${text};
274
+ --vscode-list-inactiveSelectionBackground: color-mix(in srgb, ${accent} 16%, transparent);
275
+ --vscode-list-inactiveSelectionForeground: ${text};
276
+ --background: color-mix(in srgb, ${surface} ${panelOpacity}, transparent);
277
+ --foreground: ${text}; --card: color-mix(in srgb, ${surface} 88%, transparent);
278
+ --card-foreground: ${text}; --popover: color-mix(in srgb, ${surface} 94%, transparent);
279
+ --popover-foreground: ${text}; --primary: ${accent}; --primary-foreground: ${surface};
280
+ --secondary: color-mix(in srgb, ${surface} 82%, ${secondary}); --secondary-foreground: ${text};
281
+ --muted: color-mix(in srgb, ${surface} 88%, ${secondary});
282
+ --muted-foreground: color-mix(in srgb, ${text} 68%, ${surface});
283
+ --accent: color-mix(in srgb, ${surface} 74%, ${accent}); --accent-foreground: ${text};
284
+ --border: color-mix(in srgb, ${text} 16%, transparent); --input: color-mix(in srgb, ${text} 18%, transparent); --ring: ${accent};
285
+ --sidebar: color-mix(in srgb, ${surface} 88%, transparent); --sidebar-background: color-mix(in srgb, ${surface} 88%, transparent);
286
+ --sidebar-foreground: ${text}; --sidebar-primary: ${accent}; --sidebar-primary-foreground: ${surface};
287
+ --sidebar-accent: color-mix(in srgb, ${surface} 74%, ${accent}); --sidebar-accent-foreground: ${text};
288
+ --sidebar-border: color-mix(in srgb, ${text} 14%, transparent); --sidebar-ring: ${accent};
289
+ --main-surface-primary: color-mix(in srgb, ${surface} 84%, transparent);
290
+ --main-surface-secondary: color-mix(in srgb, ${surface} 72%, transparent);
291
+ --sidebar-surface-primary: color-mix(in srgb, ${surface} 88%, transparent);
292
+ --sidebar-surface-secondary: color-mix(in srgb, ${surface} 76%, transparent);
293
+ --message-surface: color-mix(in srgb, ${surface} 82%, transparent);
294
+ --composer-surface: color-mix(in srgb, ${surface} 88%, transparent);
295
+ }
296
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] body {
297
+ color: ${text}; background: ${surface} !important;
298
+ }
299
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] body > #root {
300
+ min-height: 100vh; color: ${text} !important;
301
+ background-color: transparent !important;
302
+ background-image:
303
+ linear-gradient(90deg, ${sidebarVeil} 0 22%, transparent 46%),
304
+ linear-gradient(180deg, transparent 0 43%, ${lowerVeil} 100%),
305
+ ${image} !important;
306
+ background-position: left top, left top, ${skin.focus} !important;
307
+ background-repeat: no-repeat !important;
308
+ background-size: 100% 100%, 100% 100%, cover !important;
309
+ }
310
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] :where(
311
+ .main-surface,
312
+ .browser-main-surface,
313
+ [data-app-shell-main-surface="default"],
314
+ main[class*="_MainContentSurface_"]
315
+ ) {
316
+ background: linear-gradient(180deg, transparent 0 40%, color-mix(in srgb, ${surface} 70%, transparent) 100%) !important;
317
+ }
318
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] .app-shell-left-panel {
319
+ background: color-mix(in srgb, ${surface} 88%, transparent) !important;
320
+ border-right-color: color-mix(in srgb, ${accent} 32%, transparent) !important;
321
+ backdrop-filter: none !important;
322
+ }
323
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] :where(.bg-background, .bg-sidebar, .bg-card, .bg-token-main-surface-primary, .bg-token-sidebar-surface-primary) {
324
+ background: color-mix(in srgb, ${surface} ${panelOpacity}, transparent) !important;
325
+ }
326
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] :where(
327
+ [data-response-annotation-conversation],
328
+ [data-local-conversation-final-assistant]:not(:has([data-response-annotation-conversation]))
329
+ ) {
330
+ box-sizing: border-box;
331
+ color: ${text} !important;
332
+ background: color-mix(in srgb, ${surface} 90%, transparent) !important;
333
+ border: 1px solid color-mix(in srgb, ${accent} 16%, transparent) !important;
334
+ border-radius: 18px;
335
+ padding: 14px 16px 12px;
336
+ box-shadow: 0 8px 24px color-mix(in srgb, ${text} 8%, transparent) !important;
337
+ backdrop-filter: none !important;
338
+ }
339
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] :where(.composer-surface-chrome, [data-user-message-bubble], [data-codex-approval-surface]) {
340
+ color: ${text} !important;
341
+ background: color-mix(in srgb, ${surface} 82%, transparent) !important;
342
+ border-color: color-mix(in srgb, ${accent} 24%, transparent) !important;
343
+ box-shadow: 0 8px 24px color-mix(in srgb, ${accent} 12%, transparent) !important;
344
+ backdrop-filter: none !important;
345
+ }
346
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] [data-app-action-sidebar-thread-active="true"] {
347
+ background: linear-gradient(90deg, color-mix(in srgb, ${accent} 22%, transparent), color-mix(in srgb, ${secondary} 16%, transparent)) !important;
348
+ }
349
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] :where(.border-border, .border-sidebar-border) { border-color: color-mix(in srgb, ${text} 14%, transparent) !important; }
350
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] [data-codexhost-settings-shell] {
351
+ --settings-bg: color-mix(in srgb, ${surface} 90%, transparent);
352
+ --settings-sidebar: color-mix(in srgb, ${surface} 86%, ${secondary});
353
+ --settings-panel: color-mix(in srgb, ${surface} 90%, transparent);
354
+ --settings-surface: color-mix(in srgb, ${surface} 82%, ${secondary});
355
+ --settings-surface-hover: color-mix(in srgb, ${surface} 72%, ${accent});
356
+ --settings-inset: color-mix(in srgb, ${surface} 92%, ${secondary});
357
+ --settings-text: ${text}; --settings-muted: color-mix(in srgb, ${text} 68%, ${surface});
358
+ --settings-subtle: color-mix(in srgb, ${text} 52%, ${surface});
359
+ --settings-border: color-mix(in srgb, ${text} 14%, transparent); --settings-focus: ${accent};
360
+ --settings-primary: ${accent}; --settings-primary-hover: color-mix(in srgb, ${accent} 82%, white);
361
+ --settings-primary-text: ${surface};
362
+ }
363
+ ${logoCss}${polaroidCss}
364
+ @media (prefers-reduced-transparency: reduce) {
365
+ html[${RENDERER_SKIN_ATTRIBUTE}="${skin.id}"] :where(.app-shell-left-panel, [data-response-annotation-conversation], .composer-surface-chrome, [data-user-message-bubble], [data-codex-approval-surface]) {
366
+ background-color: ${surface} !important;
367
+ }
368
+ }`;
369
+ }
370
+
371
+ export function applyRendererSkin(
372
+ id: RendererSkinId,
373
+ ownerDocument: Document = document,
374
+ storage?: RendererSkinStorage | null,
375
+ ): RendererSkinId {
376
+ const resolvedStorage = storage === undefined ? documentSkinStorage(ownerDocument) : storage;
377
+ const currentStyle = ownerDocument.getElementById(RENDERER_SKIN_STYLE_ID);
378
+ if (id === "native") {
379
+ currentStyle?.remove();
380
+ ownerDocument.documentElement.removeAttribute(RENDERER_SKIN_ATTRIBUTE);
381
+ try { resolvedStorage?.removeItem(RENDERER_SKIN_STORAGE_KEY); } catch { /* visual reset succeeded */ }
382
+ return id;
383
+ }
384
+ const skin = rendererSkinDefinition(id);
385
+ const style = currentStyle ?? ownerDocument.createElement("style");
386
+ style.id = RENDERER_SKIN_STYLE_ID;
387
+ style.textContent = skinCss(skin);
388
+ if (!currentStyle) ownerDocument.head.append(style);
389
+ ownerDocument.documentElement.setAttribute(RENDERER_SKIN_ATTRIBUTE, id);
390
+ try { resolvedStorage?.setItem(RENDERER_SKIN_STORAGE_KEY, id); } catch { /* session-only fallback */ }
391
+ return id;
392
+ }
393
+
394
+ export function restoreRendererSkin(ownerWindow: Window = window): RendererSkinId {
395
+ const storage = documentSkinStorage(ownerWindow.document);
396
+ const id = readRendererSkin(storage);
397
+ if (id === "native") return id;
398
+ return applyRendererSkin(id, ownerWindow.document, storage);
399
+ }
@@ -156,6 +156,7 @@ function transportModelIdForAgent(agent: RendererAgent): string | null {
156
156
  if (agent === "zcode") return encodeHarnessPluginRoute({ harnessId: harnessIdSchema.parse("zcode") });
157
157
  if (agent === "trae") return encodeHarnessPluginRoute({ harnessId: harnessIdSchema.parse("trae") });
158
158
  if (agent === "cursor-cli") return encodeHarnessPluginRoute({ harnessId: harnessIdSchema.parse("cursor-cli") });
159
+ if (agent === "cline") return encodeHarnessPluginRoute({ harnessId: harnessIdSchema.parse("cline") });
159
160
  if (agent === 'codex-harness') return encodeHarnessPluginRoute({ harnessId: harnessIdSchema.parse(agent) });
160
161
  return null;
161
162
  }
@@ -955,7 +956,7 @@ export function modelSelectionForAgent(
955
956
  ...(thinkingOptionId ? { thinkingOptionId } : {}),
956
957
  ...(permissionModeId ? { permissionModeId } : {}),
957
958
  })
958
- : agent === "openclaw" || agent === "hermes" || agent === 'codex-harness' || agent === 'qoder' || agent === 'codebuddy' || agent === 'zcode' || agent === 'trae' || agent === 'cursor-cli'
959
+ : agent === "openclaw" || agent === "hermes" || agent === 'codex-harness' || agent === 'qoder' || agent === 'codebuddy' || agent === 'zcode' || agent === 'trae' || agent === 'cursor-cli' || agent === 'cline'
959
960
  ? encodeHarnessPluginRoute({
960
961
  harnessId: harnessIdSchema.parse(agent),
961
962
  ...(model ? { model } : {}),
@@ -75,6 +75,16 @@ describe("Renderer settings localization", () => {
75
75
  createDefaultRendererSettingsPages(rendererSettingsMessages("zh-CN")).map(
76
76
  ({ label }) => label,
77
77
  ),
78
- ).toEqual(["连接", "账号", "MCP", "Skills", "会话导入", "存储与验证", "更新", "关于"]);
78
+ ).toEqual([
79
+ "连接",
80
+ "账号",
81
+ "MCP",
82
+ "Skills",
83
+ "会话导入",
84
+ "存储与验证",
85
+ "皮肤",
86
+ "更新",
87
+ "关于",
88
+ ]);
79
89
  });
80
90
  });
@@ -27,6 +27,7 @@ describe("Renderer settings foundation", () => {
27
27
  "Skills",
28
28
  "Session Import",
29
29
  "Storage & verification",
30
+ "Skins",
30
31
  "Updates",
31
32
  "About",
32
33
  ]);
@@ -37,6 +38,7 @@ describe("Renderer settings foundation", () => {
37
38
  "session-import",
38
39
  "session-import",
39
40
  "storage",
41
+ "palette",
40
42
  "updates",
41
43
  "about",
42
44
  ]);
@@ -73,6 +75,7 @@ describe("Renderer settings foundation", () => {
73
75
  "skills",
74
76
  "session-import",
75
77
  "storage",
78
+ "skins",
76
79
  "updates",
77
80
  "about",
78
81
  ]);
@@ -0,0 +1,136 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ RENDERER_SKIN_ATTRIBUTE,
5
+ RENDERER_SKIN_STORAGE_KEY,
6
+ RENDERER_SKIN_STYLE_ID,
7
+ RENDERER_SKINS,
8
+ applyRendererSkin,
9
+ readRendererSkin,
10
+ } from "../../src/settings/skin-runtime.js";
11
+
12
+ class MemoryStorage {
13
+ readonly values = new Map<string, string>();
14
+
15
+ getItem(key: string): string | null {
16
+ return this.values.get(key) ?? null;
17
+ }
18
+
19
+ setItem(key: string, value: string): void {
20
+ this.values.set(key, value);
21
+ }
22
+
23
+ removeItem(key: string): void {
24
+ this.values.delete(key);
25
+ }
26
+ }
27
+
28
+ function fakeDocument() {
29
+ const attributes = new Map<string, string>();
30
+ let style: { id: string; textContent: string; remove(): void } | null = null;
31
+ const document = {
32
+ documentElement: {
33
+ setAttribute(name: string, value: string) {
34
+ attributes.set(name, value);
35
+ },
36
+ removeAttribute(name: string) {
37
+ attributes.delete(name);
38
+ },
39
+ },
40
+ head: {
41
+ append(next: typeof style) {
42
+ style = next;
43
+ },
44
+ },
45
+ getElementById(id: string) {
46
+ return style?.id === id ? style : null;
47
+ },
48
+ createElement() {
49
+ const element = {
50
+ id: "",
51
+ textContent: "",
52
+ remove() {
53
+ style = null;
54
+ },
55
+ };
56
+ return element;
57
+ },
58
+ } as unknown as Document;
59
+ return { document, attributes, getStyle: () => style };
60
+ }
61
+
62
+ describe("renderer skin runtime", () => {
63
+ it("ships a native escape hatch and visual-only attributed references", () => {
64
+ expect(RENDERER_SKINS.map(({ id }) => id)).toEqual([
65
+ "native",
66
+ "miku-488137",
67
+ "genshin-dawn",
68
+ "genshin-night",
69
+ "wuthering-echo",
70
+ "wuthering-tide",
71
+ "naruto-hokage",
72
+ "naruto-sasuke",
73
+ "deepspace-dawn",
74
+ "deepspace-star",
75
+ "dragonball-nimbus",
76
+ "dragonball-super-saiyan",
77
+ "dalao-dianyan",
78
+ "styler-gilded-grandeur",
79
+ "styler-merry-big-top",
80
+ "styler-nocturne-studio",
81
+ "styler-quiet-garden",
82
+ ]);
83
+ expect(RENDERER_SKINS[0]?.sourceUrl).toBeNull();
84
+ expect(RENDERER_SKINS.slice(1).every(({ sourceUrl }) => sourceUrl?.startsWith("https://github.com/"))).toBe(true);
85
+ expect(RENDERER_SKINS.slice(1).every(({ heroUrl }) => Boolean(heroUrl))).toBe(true);
86
+ const miku = RENDERER_SKINS.find(({ id }) => id === "miku-488137");
87
+ expect(miku?.logoUrl).toContain("logo.webp");
88
+ expect(miku?.polaroidUrl).toContain("polaroid.webp");
89
+ });
90
+
91
+ it("applies, persists, and fully removes a skin override", () => {
92
+ const storage = new MemoryStorage();
93
+ const fixture = fakeDocument();
94
+
95
+ applyRendererSkin("miku-488137", fixture.document, storage);
96
+ expect(fixture.attributes.get(RENDERER_SKIN_ATTRIBUTE)).toBe("miku-488137");
97
+ expect(fixture.getStyle()?.id).toBe(RENDERER_SKIN_STYLE_ID);
98
+ expect(fixture.getStyle()?.textContent).toContain("--sidebar-background");
99
+ expect(fixture.getStyle()?.textContent).not.toContain("<script");
100
+ expect(fixture.getStyle()?.textContent).toContain("background-image");
101
+ expect(fixture.getStyle()?.textContent).toContain('main[class*="_MainContentSurface_"]');
102
+ expect(fixture.getStyle()?.textContent).toContain('.main-surface');
103
+ expect(fixture.getStyle()?.textContent).toContain('[data-app-shell-main-surface="default"]');
104
+ expect(fixture.getStyle()?.textContent).toContain("[data-response-annotation-conversation]");
105
+ expect(fixture.getStyle()?.textContent).toContain("90%, transparent");
106
+ expect(fixture.getStyle()?.textContent).not.toContain("background: transparent !important; border-color: transparent");
107
+ expect(fixture.getStyle()?.textContent).toContain("--color-text-primary");
108
+ expect(fixture.getStyle()?.textContent).toContain("--color-token-foreground");
109
+ expect(fixture.getStyle()?.textContent).toContain("--app-color-text-foreground");
110
+ expect(fixture.getStyle()?.textContent).toContain("--wb-text-primary");
111
+ expect(fixture.getStyle()?.textContent).toContain("--color-surface-elevated-secondary");
112
+ expect(fixture.getStyle()?.textContent).toContain("--color-background-primary-soft-alpha");
113
+ expect(fixture.getStyle()?.textContent).toContain("--app-color-background-elevated-secondary");
114
+ expect(fixture.getStyle()?.textContent).toContain("--color-codex-diff-surface");
115
+ expect(fixture.getStyle()?.textContent).toContain("--vscode-editor-background");
116
+ expect(fixture.getStyle()?.textContent).toContain("logo.webp");
117
+ expect(fixture.getStyle()?.textContent).toContain("polaroid.webp");
118
+ expect(fixture.getStyle()?.textContent).toContain("body::after");
119
+ expect(fixture.getStyle()?.textContent).not.toContain("background-attachment: fixed");
120
+ expect(fixture.getStyle()?.textContent).not.toContain("backdrop-filter: blur");
121
+ expect(fixture.getStyle()?.textContent).toContain("hero.webp");
122
+ expect(storage.getItem(RENDERER_SKIN_STORAGE_KEY)).toBe("miku-488137");
123
+ expect(readRendererSkin(storage)).toBe("miku-488137");
124
+
125
+ applyRendererSkin("native", fixture.document, storage);
126
+ expect(fixture.attributes.has(RENDERER_SKIN_ATTRIBUTE)).toBe(false);
127
+ expect(fixture.getStyle()).toBeNull();
128
+ expect(storage.getItem(RENDERER_SKIN_STORAGE_KEY)).toBeNull();
129
+ });
130
+
131
+ it("falls back to native for stale persisted values", () => {
132
+ const storage = new MemoryStorage();
133
+ storage.setItem(RENDERER_SKIN_STORAGE_KEY, "removed-theme");
134
+ expect(readRendererSkin(storage)).toBe("native");
135
+ });
136
+ });
@@ -40,6 +40,13 @@ export type ThreadHarnessSwitchParams = z.infer<typeof threadHarnessSwitchParams
40
40
  export declare const threadHarnessSwitchResultSchema: z.ZodObject<{
41
41
  threadId: z.core.$ZodBranded<z.ZodString, "HostThreadId", "out">;
42
42
  checkpointId: z.ZodString;
43
+ phase: z.ZodOptional<z.ZodEnum<{
44
+ ready: "ready";
45
+ "rolled-back": "rolled-back";
46
+ cancelled: "cancelled";
47
+ }>>;
48
+ fromHarnessId: z.ZodOptional<z.ZodString>;
49
+ toHarnessId: z.ZodOptional<z.ZodString>;
43
50
  }, z.core.$strict>;
44
51
  export type ThreadHarnessSwitchResult = z.infer<typeof threadHarnessSwitchResultSchema>;
45
52
  //# sourceMappingURL=thread-harness-switch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"thread-harness-switch.d.ts","sourceRoot":"","sources":["../src/thread-harness-switch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,0BAA0B;;;;;EAA8D,CAAC;AACtG,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,eAAO,MAAM,4BAA4B;;;;;;kBAQ9B,CAAC;AACZ,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;GAGG;AACH,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;kBAQjC,CAAC;AAEZ,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,eAAO,MAAM,+BAA+B;;;kBAKjC,CAAC;AAEZ,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC"}
1
+ {"version":3,"file":"thread-harness-switch.d.ts","sourceRoot":"","sources":["../src/thread-harness-switch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,0BAA0B;;;;;EAA8D,CAAC;AACtG,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,eAAO,MAAM,4BAA4B;;;;;;kBAQ9B,CAAC;AACZ,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;GAGG;AACH,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;kBAQjC,CAAC;AAEZ,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,eAAO,MAAM,+BAA+B;;;;;;;;;;kBAQjC,CAAC;AAEZ,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC"}
@@ -27,6 +27,9 @@ export const threadHarnessSwitchResultSchema = z
27
27
  .object({
28
28
  threadId: hostThreadIdSchema,
29
29
  checkpointId: z.string().min(1),
30
+ phase: z.enum(["ready", "rolled-back", "cancelled"]).optional(),
31
+ fromHarnessId: z.string().min(1).optional(),
32
+ toHarnessId: z.string().min(1).optional(),
30
33
  })
31
34
  .strict();
32
35
  //# sourceMappingURL=thread-harness-switch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"thread-harness-switch.js","sourceRoot":"","sources":["../src/thread-harness-switch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;AAGtG,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC;KAC1C,MAAM,CAAC;IACN,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE;IACzB,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;IACjB,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE;IAClB,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE;CACxB,CAAC;KACD,MAAM,EAAE,CAAC;AAGZ;;;GAGG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC;KAC7C,MAAM,CAAC;IACN,QAAQ,EAAE,kBAAkB;IAC5B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;IACrC,MAAM,EAAE,0BAA0B,CAAC,QAAQ,EAAE;IAC7C,QAAQ,EAAE,4BAA4B,CAAC,QAAQ,EAAE;CAClD,CAAC;KACD,MAAM,EAAE,CAAC;AAIZ,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC;KAC7C,MAAM,CAAC;IACN,QAAQ,EAAE,kBAAkB;IAC5B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAChC,CAAC;KACD,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"thread-harness-switch.js","sourceRoot":"","sources":["../src/thread-harness-switch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;AAGtG,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC;KAC1C,MAAM,CAAC;IACN,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE;IACzB,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;IACjB,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE;IAClB,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE;CACxB,CAAC;KACD,MAAM,EAAE,CAAC;AAGZ;;;GAGG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC;KAC7C,MAAM,CAAC;IACN,QAAQ,EAAE,kBAAkB;IAC5B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;IACrC,MAAM,EAAE,0BAA0B,CAAC,QAAQ,EAAE;IAC7C,QAAQ,EAAE,4BAA4B,CAAC,QAAQ,EAAE;CAClD,CAAC;KACD,MAAM,EAAE,CAAC;AAIZ,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC;KAC7C,MAAM,CAAC;IACN,QAAQ,EAAE,kBAAkB;IAC5B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC/D,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC3C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC1C,CAAC;KACD,MAAM,EAAE,CAAC"}