@harness-mix/cli 0.1.4 → 0.1.6

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 (146) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +26 -96
  3. package/docs/chatgpt-web-sidebar.md +7 -1
  4. package/docs/cross-platform.md +15 -0
  5. package/docs/harness-handoff-design.md +377 -0
  6. package/docs/harness-management.md +28 -1
  7. package/docs/multi-agent-collaboration.md +9 -9
  8. package/docs/native-codex.md +21 -10
  9. package/output/native-build/harness-mix-shim.exe +0 -0
  10. package/output/native-build/renderer-extension.js +2596 -135
  11. package/package.json +20 -10
  12. package/scripts/chatgpt-context-ui-smoke.cjs +16 -0
  13. package/scripts/codex-accounts-test.cjs +81 -0
  14. package/scripts/codex-accounts-ui-smoke.cjs +70 -0
  15. package/scripts/collaboration-test.cjs +8 -3
  16. package/scripts/collaboration-ui-smoke.cjs +40 -26
  17. package/scripts/e2e-integrations.cjs +52 -0
  18. package/scripts/e2e-native-host.cjs +39 -0
  19. package/scripts/handoff-checkpoint-test.cjs +83 -0
  20. package/scripts/install-native.cjs +4 -1
  21. package/scripts/integrations-test.cjs +123 -0
  22. package/scripts/integrations-ui-smoke.cjs +124 -0
  23. package/scripts/native-protocol-test.cjs +180 -5
  24. package/scripts/native-secret-test.cjs +2 -2
  25. package/scripts/native-shim-test.cjs +20 -4
  26. package/scripts/native-sidebar-test.cjs +7 -0
  27. package/scripts/native-skill-roots-test.cjs +90 -0
  28. package/scripts/native-ui-smoke.cjs +58 -2
  29. package/scripts/publish-native-package.cjs +8 -5
  30. package/scripts/support/integrations-mcp-fixture.cjs +15 -0
  31. package/scripts/switch-harness-test.cjs +27 -1
  32. package/scripts/test-live-mentions.cjs +152 -0
  33. package/src/main/adapters/acp.js +3 -3
  34. package/src/main/adapters/antigravity.js +17 -17
  35. package/src/main/adapters/claude.js +50 -10
  36. package/src/main/adapters/codebuddy.js +1 -0
  37. package/src/main/adapters/codex-app-server.js +20 -13
  38. package/src/main/adapters/codex.js +25 -5
  39. package/src/main/adapters/cursor.js +5 -1
  40. package/src/main/adapters/dsh.js +9 -1
  41. package/src/main/adapters/grok.js +7 -3
  42. package/src/main/adapters/hermes.js +8 -0
  43. package/src/main/adapters/kiro.js +2 -1
  44. package/src/main/adapters/managed-mcp.js +25 -0
  45. package/src/main/adapters/native-acp.js +4 -4
  46. package/src/main/adapters/omp.js +11 -0
  47. package/src/main/adapters/openclaw.js +6 -0
  48. package/src/main/adapters/opencode.js +13 -3
  49. package/src/main/adapters/pi.js +5 -0
  50. package/src/main/adapters/qoder.js +1 -0
  51. package/src/main/adapters/trae.js +4 -0
  52. package/src/main/adapters/zcode.js +3 -0
  53. package/src/main/harness-adapter/manifest.js +17 -2
  54. package/src/main/host/collaboration.js +57 -11
  55. package/src/main/host/handoff-access.js +53 -0
  56. package/src/main/host/handoff-checkpoints.js +206 -0
  57. package/src/main/host/handoff-mcp.cjs +38 -0
  58. package/src/main/host/handoff-tools.js +13 -0
  59. package/src/main/host/handoff.js +28 -6
  60. package/src/main/host/integrations.js +339 -0
  61. package/src/main/host/runtime.js +95 -15
  62. package/src/main/host/store.js +4 -4
  63. package/src/main/native/codex-accounts.js +260 -0
  64. package/src/main/native/host.js +1 -1
  65. package/src/main/native/launcher.js +2 -2
  66. package/src/main/native/protocol.js +176 -14
  67. package/src/main/native/redact.js +1 -1
  68. package/src/main/native/rs/crates/shim/src/main.rs +155 -12
  69. package/src/main/native/thread-list.js +3 -0
  70. package/src/native-ui/desktop-control/dist/tsconfig.tsbuildinfo +1 -1
  71. package/src/native-ui/renderer-extension/dist/types/harness-mix-settings.d.ts.map +1 -1
  72. package/src/native-ui/renderer-extension/dist/types/index.d.ts +4 -0
  73. package/src/native-ui/renderer-extension/dist/types/index.d.ts.map +1 -1
  74. package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts +2 -0
  75. package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts.map +1 -1
  76. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
  77. package/src/native-ui/renderer-extension/dist/types/renderer-chatgpt-context.d.ts +36 -0
  78. package/src/native-ui/renderer-extension/dist/types/renderer-chatgpt-context.d.ts.map +1 -0
  79. package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-state.d.ts +1 -0
  80. package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-state.d.ts.map +1 -1
  81. package/src/native-ui/renderer-extension/dist/types/renderer-composer-dom.d.ts +3 -1
  82. package/src/native-ui/renderer-extension/dist/types/renderer-composer-dom.d.ts.map +1 -1
  83. package/src/native-ui/renderer-extension/dist/types/renderer-harness-handoff.d.ts +53 -0
  84. package/src/native-ui/renderer-extension/dist/types/renderer-harness-handoff.d.ts.map +1 -0
  85. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts +11 -0
  86. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts.map +1 -1
  87. package/src/native-ui/renderer-extension/dist/types/renderer-integrations-client.d.ts +105 -0
  88. package/src/native-ui/renderer-extension/dist/types/renderer-integrations-client.d.ts.map +1 -0
  89. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +7 -1
  90. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -1
  91. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts +1 -0
  92. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts.map +1 -1
  93. package/src/native-ui/renderer-extension/dist/types/settings/accounts-list.d.ts +2 -0
  94. package/src/native-ui/renderer-extension/dist/types/settings/accounts-list.d.ts.map +1 -1
  95. package/src/native-ui/renderer-extension/dist/types/settings/accounts-page.d.ts +1 -0
  96. package/src/native-ui/renderer-extension/dist/types/settings/accounts-page.d.ts.map +1 -1
  97. package/src/native-ui/renderer-extension/dist/types/settings/integrations-page.d.ts +6 -0
  98. package/src/native-ui/renderer-extension/dist/types/settings/integrations-page.d.ts.map +1 -0
  99. package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts +5 -0
  100. package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts.map +1 -1
  101. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +8 -4
  102. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
  103. package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
  104. package/src/native-ui/renderer-extension/src/harness-mix-settings.ts +4 -1
  105. package/src/native-ui/renderer-extension/src/index.ts +28 -0
  106. package/src/native-ui/renderer-extension/src/renderer-agent-icon.ts +4 -4
  107. package/src/native-ui/renderer-extension/src/renderer-agent-picker.ts +46 -6
  108. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +115 -18
  109. package/src/native-ui/renderer-extension/src/renderer-chatgpt-context.ts +87 -0
  110. package/src/native-ui/renderer-extension/src/renderer-codex-account-state.ts +10 -0
  111. package/src/native-ui/renderer-extension/src/renderer-composer-dom.ts +10 -0
  112. package/src/native-ui/renderer-extension/src/renderer-harness-handoff.ts +330 -0
  113. package/src/native-ui/renderer-extension/src/renderer-harness-mentions.ts +556 -123
  114. package/src/native-ui/renderer-extension/src/renderer-integrations-client.ts +59 -0
  115. package/src/native-ui/renderer-extension/src/renderer-model-client.ts +17 -1
  116. package/src/native-ui/renderer-extension/src/renderer-settings-lifecycle.ts +2 -0
  117. package/src/native-ui/renderer-extension/src/settings/accounts-list.ts +16 -1
  118. package/src/native-ui/renderer-extension/src/settings/accounts-page.ts +32 -3
  119. package/src/native-ui/renderer-extension/src/settings/integrations-page.ts +1474 -0
  120. package/src/native-ui/renderer-extension/src/settings/localization.ts +32 -13
  121. package/src/native-ui/renderer-extension/src/settings/pages.ts +49 -18
  122. package/src/native-ui/renderer-extension/src/settings/shell.css +103 -0
  123. package/src/native-ui/renderer-extension/test/renderer-agent-picker.test.ts +44 -13
  124. package/src/native-ui/renderer-extension/test/renderer-chatgpt-context.test.ts +21 -0
  125. package/src/native-ui/renderer-extension/test/renderer-codex-account-state.test.ts +13 -1
  126. package/src/native-ui/renderer-extension/test/renderer-harness-handoff.test.ts +20 -0
  127. package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +9 -2
  128. package/src/native-ui/renderer-extension/test/settings/localization.test.ts +1 -1
  129. package/src/native-ui/renderer-extension/test/settings/pages.test.ts +77 -8
  130. package/src/native-ui/renderer-extension/test/settings/shell.test.ts +6 -0
  131. package/src/native-ui/shared-contracts/dist/codex-accounts.d.ts +19 -4
  132. package/src/native-ui/shared-contracts/dist/codex-accounts.d.ts.map +1 -1
  133. package/src/native-ui/shared-contracts/dist/codex-accounts.js +2 -0
  134. package/src/native-ui/shared-contracts/dist/codex-accounts.js.map +1 -1
  135. package/src/native-ui/shared-contracts/dist/index.d.ts +2 -2
  136. package/src/native-ui/shared-contracts/dist/index.d.ts.map +1 -1
  137. package/src/native-ui/shared-contracts/dist/index.js +1 -1
  138. package/src/native-ui/shared-contracts/dist/index.js.map +1 -1
  139. package/src/native-ui/shared-contracts/dist/thread-harness-switch.d.ts +29 -0
  140. package/src/native-ui/shared-contracts/dist/thread-harness-switch.d.ts.map +1 -1
  141. package/src/native-ui/shared-contracts/dist/thread-harness-switch.js +13 -0
  142. package/src/native-ui/shared-contracts/dist/thread-harness-switch.js.map +1 -1
  143. package/src/native-ui/shared-contracts/dist/tsconfig.tsbuildinfo +1 -1
  144. package/src/native-ui/shared-contracts/src/codex-accounts.ts +2 -0
  145. package/src/native-ui/shared-contracts/src/index.ts +8 -1
  146. package/src/native-ui/shared-contracts/src/thread-harness-switch.ts +17 -0
@@ -88,7 +88,12 @@ export interface RendererSettingsMessages {
88
88
  readonly accountDeleteFailed: string;
89
89
  readonly accountActive: string;
90
90
  readonly accountUse: string;
91
- readonly accountSignIn: string;
91
+ readonly accountSignIn: string;
92
+ readonly accountSignOut: string;
93
+ readonly accountSignOutConfirm: string;
94
+ readonly accountSigningOut: string;
95
+ readonly accountSignedOut: string;
96
+ readonly accountSignOutFailed: string;
92
97
  readonly accountSigningIn: string;
93
98
  readonly accountDeviceCodePrerequisite: string;
94
99
  readonly accountVerificationDescription: string;
@@ -299,9 +304,9 @@ const ENGLISH_MESSAGES: RendererSettingsMessages = Object.freeze({
299
304
  sessionImportRetrying: "Opening...",
300
305
  connectionsDescription:
301
306
  "View runtime status by Host. Select an item to inspect details or complete its setup.",
302
- accountsDescription: "Choose the default account for new Codex tasks and check account limits.",
303
- accountTaskHint:
304
- "Existing tasks keep the account they were created with. Multi-account is currently available for Codex.",
307
+ accountsDescription: "Manage isolated native Codex accounts and compare each account's live limits.",
308
+ accountTaskHint:
309
+ "Signing in or switching accounts does not affect tasks already running. New tasks use the selected account.",
305
310
  accountConnected: "Connected accounts",
306
311
  accountDefaultBadge: "Default",
307
312
  accountAdd: "Add Account",
@@ -327,7 +332,12 @@ const ENGLISH_MESSAGES: RendererSettingsMessages = Object.freeze({
327
332
  accountDeleteFailed: "Could not delete the Account.",
328
333
  accountActive: "Default",
329
334
  accountUse: "Set as default",
330
- accountSignIn: "Sign in",
335
+ accountSignIn: "Sign in",
336
+ accountSignOut: "Sign out",
337
+ accountSignOutConfirm: "Sign out of the current official Codex account?",
338
+ accountSigningOut: "Signing out of official Codex...",
339
+ accountSignedOut: "Signed out of official Codex.",
340
+ accountSignOutFailed: "Could not sign out of official Codex.",
331
341
  accountSigningIn: "Starting device sign-in...",
332
342
  accountDeviceCodePrerequisite:
333
343
  "Before signing in, enable “Enable device code authorization for Codex” in Web Settings → Account security & sign-in.",
@@ -498,9 +508,11 @@ const ENGLISH_MESSAGES: RendererSettingsMessages = Object.freeze({
498
508
  aboutOpenSource: "Harness Mix is an open-source project. The source code is available at:",
499
509
  aboutStarCallout: "⭐ If this project helps you, please give us a Star! ⭐",
500
510
  aboutRepository: "Open-source repository",
501
- pageLabels: Object.freeze({
502
- connections: "Connections",
503
- accounts: "Accounts",
511
+ pageLabels: Object.freeze({
512
+ connections: "Connections",
513
+ accounts: "Accounts",
514
+ mcp: 'MCP',
515
+ skills: 'Skills',
504
516
  "session-import": "Session Import",
505
517
  updates: "Updates",
506
518
  about: "About",
@@ -554,8 +566,8 @@ const CHINESE_MESSAGES: RendererSettingsMessages = Object.freeze({
554
566
  sessionImportRetryOpen: "重试打开",
555
567
  sessionImportRetrying: "正在打开……",
556
568
  connectionsDescription: "按 Host 查看运行时状态。选择一项,在右侧检查详情或完成配置。",
557
- accountsDescription: "选择新建 Codex 任务的默认账号,并查看各账号额度。",
558
- accountTaskHint: "切换默认账号不会影响已开始的任务。目前仅支持 Codex 多账号。",
569
+ accountsDescription: "管理相互隔离的原生 Codex 账号,并对比每个账号的实时额度。",
570
+ accountTaskHint: "登录或切换账号不会影响已开始的任务;新任务使用当前选中的账号。",
559
571
  accountConnected: "已连接账号",
560
572
  accountDefaultBadge: "默认",
561
573
  accountAdd: "添加账号",
@@ -581,7 +593,12 @@ const CHINESE_MESSAGES: RendererSettingsMessages = Object.freeze({
581
593
  accountDeleteFailed: "删除账号失败。",
582
594
  accountActive: "默认账号",
583
595
  accountUse: "设为默认",
584
- accountSignIn: "登录",
596
+ accountSignIn: "登录",
597
+ accountSignOut: "退出登录",
598
+ accountSignOutConfirm: "确定退出当前官方 Codex 账号吗?",
599
+ accountSigningOut: "正在退出官方 Codex...",
600
+ accountSignedOut: "已退出官方 Codex。",
601
+ accountSignOutFailed: "退出官方 Codex 失败。",
585
602
  accountSigningIn: "正在启动设备登录...",
586
603
  accountDeviceCodePrerequisite:
587
604
  "登录前,请先在 Web 端的“设置 → 账号安全与登录”中开启“为 Codex 启用设备代码授权”。",
@@ -750,8 +767,10 @@ const CHINESE_MESSAGES: RendererSettingsMessages = Object.freeze({
750
767
  aboutRepository: "开源仓库",
751
768
  pageLabels: Object.freeze({
752
769
  connections: "连接",
753
- accounts: "账号",
754
- "session-import": "会话导入",
770
+ accounts: "账号",
771
+ mcp: 'MCP',
772
+ skills: 'Skills',
773
+ "session-import": "会话导入",
755
774
  updates: "更新",
756
775
  about: "关于",
757
776
  }),
@@ -26,7 +26,9 @@ import {
26
26
  type RendererSessionImportClient,
27
27
  type RendererImportedThreadOpener,
28
28
  } from "./session-import-page.js";
29
- import { createReleaseNotesElement } from "./release-notes.js";
29
+ import { createReleaseNotesElement } from "./release-notes.js";
30
+ import { createMcpSettingsPage, createSkillsSettingsPage } from './integrations-page.js';
31
+ import type { RendererIntegrationsClient } from '../renderer-integrations-client.js';
30
32
  import { createAccountsSettingsPage, type RendererCodexAccountClient } from "./accounts-page.js";
31
33
 
32
34
  export type {
@@ -71,7 +73,9 @@ function windowsInstallerDownloadUrl(window: Window | null | undefined, version:
71
73
 
72
74
  export const DEFAULT_RENDERER_SETTINGS_PAGE_IDS = [
73
75
  "connections",
74
- "accounts",
76
+ "accounts",
77
+ "mcp",
78
+ "skills",
75
79
  "session-import",
76
80
  "updates",
77
81
  "about",
@@ -79,11 +83,12 @@ export const DEFAULT_RENDERER_SETTINGS_PAGE_IDS = [
79
83
 
80
84
  export type DefaultRendererSettingsPageId = (typeof DEFAULT_RENDERER_SETTINGS_PAGE_IDS)[number];
81
85
 
82
- export interface RendererUpdateClient {
83
- checkUpdate(): Promise<UpdateCheckResult>;
84
- startUpdate(): Promise<UpdateStartResult>;
85
- readUpdateStatus(): Promise<UpdateStatusResult>;
86
- }
86
+ export interface RendererUpdateClient {
87
+ checkUpdate(): Promise<UpdateCheckResult>;
88
+ startUpdate(): Promise<UpdateStartResult>;
89
+ readUpdateStatus(): Promise<UpdateStatusResult>;
90
+ readCurrentVersion?(): Promise<{ version: string }>;
91
+ }
87
92
 
88
93
  function panelIconName(view: string): RendererSettingsIconName {
89
94
  if (view === "failed" || view === "error") return "alert";
@@ -154,7 +159,10 @@ function formatUpdateBytes(value: number): string {
154
159
  return `${scaled.toFixed(scaled >= 10 ? 0 : 1)} ${unit}`;
155
160
  }
156
161
 
157
- export function aboutPage(messages: RendererSettingsMessages): RendererSettingsPageDefinition {
162
+ export function aboutPage(
163
+ messages: RendererSettingsMessages,
164
+ getClient: () => RendererUpdateClient | null = () => null,
165
+ ): RendererSettingsPageDefinition {
158
166
  return Object.freeze({
159
167
  id: "about",
160
168
  label: messages.pageLabels.about,
@@ -177,7 +185,7 @@ export function aboutPage(messages: RendererSettingsMessages): RendererSettingsP
177
185
  versionRow.className = "settings-about-version-row";
178
186
  const versionBadge = document.createElement("span");
179
187
  versionBadge.className = "settings-about-version-badge";
180
- versionBadge.textContent = "v0.1.2";
188
+ versionBadge.textContent = "v…";
181
189
  const checkUpdateBtn = document.createElement("button");
182
190
  checkUpdateBtn.type = "button";
183
191
  checkUpdateBtn.className = "settings-command-button settings-command-button--secondary";
@@ -217,9 +225,27 @@ export function aboutPage(messages: RendererSettingsMessages): RendererSettingsP
217
225
  repositoryUrl,
218
226
  );
219
227
  repositorySection.append(openSource, repository);
220
- panel.append(product, tagline, versionRow, introduction, starCallout, repositorySection);
221
- context.content.append(heading, panel);
222
- return undefined;
228
+ panel.append(product, tagline, versionRow, introduction, starCallout, repositorySection);
229
+ context.content.append(heading, panel);
230
+ const client = getClient();
231
+ const readVersion = client?.readCurrentVersion
232
+ ? () => client.readCurrentVersion!()
233
+ : client
234
+ ? () => client.checkUpdate().then(({ currentVersion }) => ({ version: currentVersion }))
235
+ : null;
236
+ if (readVersion) {
237
+ void context.runLatest(readVersion, {
238
+ success(result) {
239
+ versionBadge.textContent = `v${result.version}`;
240
+ },
241
+ failure() {
242
+ versionBadge.textContent = "v—";
243
+ },
244
+ });
245
+ } else {
246
+ versionBadge.textContent = "v—";
247
+ }
248
+ return undefined;
223
249
  },
224
250
  });
225
251
  }
@@ -597,15 +623,18 @@ export function createDefaultRendererSettingsPages(
597
623
  getDiagnostics: () => RendererConnectionDiagnostics | null = () => null,
598
624
  getAccountClient: () => RendererCodexAccountClient | null = () => null,
599
625
  getSessionImportClient: () => RendererSessionImportClient | null = () => null,
600
- openImportedThread: RendererImportedThreadOpener = () =>
601
- Promise.reject(new Error("Imported Thread navigation is unavailable")),
626
+ openImportedThread: RendererImportedThreadOpener = () =>
627
+ Promise.reject(new Error("Imported Thread navigation is unavailable")),
628
+ getIntegrationsClient: () => RendererIntegrationsClient | null = () => null,
602
629
  ): readonly RendererSettingsPageDefinition[] {
603
630
  return Object.freeze([
604
631
  createConnectionsSettingsPage(messages, getDiagnostics),
605
- createAccountsSettingsPage(messages, getAccountClient),
632
+ createAccountsSettingsPage(messages, getAccountClient),
633
+ createMcpSettingsPage(messages, getIntegrationsClient),
634
+ createSkillsSettingsPage(messages, getIntegrationsClient),
606
635
  createSessionImportSettingsPage(messages, getSessionImportClient, openImportedThread),
607
636
  updatesPage(messages, getUpdateClient),
608
- aboutPage(messages),
637
+ aboutPage(messages, getUpdateClient),
609
638
  ]);
610
639
  }
611
640
 
@@ -615,7 +644,8 @@ export function createDefaultRendererSettingsRegistry(
615
644
  getDiagnostics: () => RendererConnectionDiagnostics | null = () => null,
616
645
  getAccountClient: () => RendererCodexAccountClient | null = () => null,
617
646
  getSessionImportClient: () => RendererSessionImportClient | null = () => null,
618
- openImportedThread?: RendererImportedThreadOpener,
647
+ openImportedThread?: RendererImportedThreadOpener,
648
+ getIntegrationsClient?: () => RendererIntegrationsClient | null,
619
649
  ): RendererSettingsPageRegistry {
620
650
  return createRendererSettingsPageRegistry(
621
651
  createDefaultRendererSettingsPages(
@@ -624,7 +654,8 @@ export function createDefaultRendererSettingsRegistry(
624
654
  getDiagnostics,
625
655
  getAccountClient,
626
656
  getSessionImportClient,
627
- openImportedThread,
657
+ openImportedThread,
658
+ getIntegrationsClient,
628
659
  ),
629
660
  );
630
661
  }
@@ -2236,3 +2236,106 @@ button {
2236
2236
  .settings-install-steps li {
2237
2237
  margin-bottom: 2px;
2238
2238
  }
2239
+ /* MCP and Skills share native settings controls while keeping separate flows. */
2240
+ .settings-integrations-controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 18px 0 12px; }
2241
+ .settings-integrations-controls input { flex: 1; min-width: 180px; }
2242
+ .settings-integrations-controls input[hidden] { display: none; }
2243
+ .settings-integrations-controls select, .settings-integrations-controls input,
2244
+ .settings-integrations-field input, .settings-integrations-toolbar input { box-sizing: border-box; padding: 9px 11px; border: 1px solid var(--settings-border, #8885); border-radius: 8px; color: inherit; background: var(--settings-input, transparent); font: inherit; }
2245
+ .settings-integrations-status { min-height: 18px; margin: 0 0 6px; color: var(--settings-muted); font-size: 12px; }
2246
+ .settings-integrations-body p { font-size: 12px; line-height: 1.6; opacity: .8; }
2247
+ .settings-integrations-toolbar { display: flex; gap: 10px; align-items: center; margin: 8px 0 16px; }
2248
+ .settings-integrations-toolbar input { flex: 1; }
2249
+ .settings-integrations-list { display: grid; gap: 8px; }
2250
+ .settings-integrations-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; border: 1px solid var(--settings-border, #8885); padding: 14px; border-radius: 10px; background: var(--settings-surface, transparent); }
2251
+ .settings-integrations-row > div { flex: 1; min-width: 180px; }
2252
+ .settings-integrations-row p { margin: 3px 0; }
2253
+ .settings-integrations-row code { display: block; overflow-wrap: anywhere; font-size: 11px; color: var(--settings-muted); }
2254
+ .settings-integrations-row--native { border-style: dashed; }
2255
+ .settings-integrations-empty { padding: 36px 18px; border: 1px dashed var(--settings-border, #8885); border-radius: 10px; text-align: center; color: var(--settings-muted); font-size: 12px; }
2256
+ .settings-integrations-editor-head { display: flex; align-items: center; gap: 12px; }
2257
+ .settings-integrations-editor-head h3 { margin: 0; }
2258
+ .settings-integrations-form { display: grid; gap: 14px; margin: 16px 0 28px; }
2259
+ .settings-integrations-form--card { padding: 16px; border: 1px solid var(--settings-border, #8885); border-radius: 12px; }
2260
+ .settings-integrations-field { display: grid; gap: 6px; font-size: 12px; }
2261
+ .settings-integrations-form > button { justify-self: start; }
2262
+ .settings-mcp-args { display: grid; gap: 7px; }
2263
+ .settings-mcp-arg { display: flex; gap: 8px; align-items: center; }
2264
+ .settings-mcp-arg input { flex: 1; }
2265
+ .settings-mcp-kv-row { display: flex; gap: 8px; align-items: center; }
2266
+ .settings-mcp-kv-row input { flex: 1; }
2267
+ .settings-mcp-add-arg { margin-top: 7px; }
2268
+ .settings-mcp-docs-link { display: inline-flex; align-items: center; gap: 4px; color: var(--settings-muted, #888); font-size: 13px; text-decoration: none; transition: color 150ms ease; }
2269
+ .settings-mcp-docs-link:hover { color: var(--settings-text, #fff); text-decoration: underline; }
2270
+ .settings-integrations-editor-head .settings-command-button--danger { margin-left: auto; }
2271
+ .settings-mcp-type-toggle { display: inline-flex; padding: 3px; border-radius: 8px; background: var(--settings-inset, rgba(255, 255, 255, 0.06)); border: 1px solid var(--settings-border, #8885); gap: 2px; width: fit-content; }
2272
+ .settings-mcp-type-btn { padding: 5px 14px; border-radius: 6px; border: none; background: transparent; color: var(--settings-muted, #888); font: inherit; font-size: 12px; font-weight: 500; cursor: pointer; transition: all 120ms ease; }
2273
+ .settings-mcp-type-btn:hover { color: var(--settings-text, #fff); }
2274
+ .settings-mcp-type-btn[data-active="true"] { background: var(--settings-active, rgba(255, 255, 255, 0.15)); color: var(--settings-text, #fff); font-weight: 600; }
2275
+ .settings-mcp-notice { margin: 4px 0 0; color: var(--settings-muted, #888); font-size: 12px; line-height: 1.5; }
2276
+ .settings-mcp-section { display: grid; gap: 14px; }
2277
+ .settings-mcp-icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; padding: 0; border: 1px solid var(--settings-border, #8885); border-radius: 8px; background: var(--settings-input, transparent); color: var(--settings-muted, #888); cursor: pointer; flex: none; transition: all 120ms ease; }
2278
+ .settings-mcp-icon-btn:hover:not(:disabled) { background: rgba(239, 68, 68, 0.15); border-color: rgba(239, 68, 68, 0.4); color: #f87171; }
2279
+ .settings-mcp-icon-btn svg { width: 14px; height: 14px; display: block; }
2280
+ .settings-integrations-callout { margin: 0; padding: 10px 12px; border-radius: 8px; background: var(--settings-inset); }
2281
+ .settings-skill-dropzone { display: grid; justify-items: center; gap: 8px; padding: 34px 22px; margin: 8px 0 18px; border: 1.5px dashed var(--settings-border, #8887); border-radius: 14px; background: var(--settings-inset); text-align: center; transition: border-color 120ms ease, background 120ms ease, transform 120ms ease; }
2282
+ .settings-skill-dropzone > span { max-width: 520px; color: var(--settings-muted); font-size: 12px; line-height: 18px; }
2283
+ .settings-skill-dropzone[data-drag-over="true"] { border-color: var(--settings-accent, #10a37f); background: var(--settings-accent-subtle, rgba(16, 163, 127, .1)); transform: scale(.995); }
2284
+ .settings-skill-dropzone[data-disabled="true"] { opacity: .55; }
2285
+ .settings-skills-target-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin-top: 6px; }
2286
+ .settings-skills-target-row select { padding: 6px 10px; border: 1px solid var(--settings-border, #8885); border-radius: 8px; background: var(--settings-input, transparent); color: inherit; font: inherit; font-size: 12px; }
2287
+ .settings-skills-overview-stats { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 6px 0 10px; font-size: 12px; color: var(--settings-muted); }
2288
+ .settings-skills-tabs { display: inline-flex; padding: 3px; border-radius: 8px; background: var(--settings-inset, rgba(255, 255, 255, 0.06)); border: 1px solid var(--settings-border, #8885); gap: 2px; }
2289
+ .settings-skills-tab-btn { padding: 5px 14px; border-radius: 6px; border: none; background: transparent; color: var(--settings-muted, #888); font: inherit; font-size: 12px; font-weight: 500; cursor: pointer; transition: all 120ms ease; }
2290
+ .settings-skills-tab-btn:hover { color: var(--settings-text, #fff); }
2291
+ .settings-skills-tab-btn[data-active="true"] { background: var(--settings-active, rgba(255, 255, 255, 0.15)); color: var(--settings-text, #fff); font-weight: 600; }
2292
+ .settings-skills-overview-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 14px; margin-top: 10px; }
2293
+ .settings-skills-harness-card { border: 1px solid var(--settings-border, #8885); border-radius: 12px; padding: 14px; background: var(--settings-surface, transparent); display: flex; flex-direction: column; gap: 10px; }
2294
+ .settings-skills-harness-head { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--settings-border, #8883); padding-bottom: 8px; }
2295
+ .settings-skills-harness-head strong { font-size: 14px; }
2296
+ .settings-skills-badge { font-size: 11px; padding: 2px 8px; border-radius: 10px; background: var(--settings-inset, rgba(255, 255, 255, 0.08)); color: var(--settings-muted); }
2297
+ .settings-skills-badge--active { background: rgba(16, 163, 127, 0.15); color: #10a37f; }
2298
+ .settings-skills-list-compact { display: flex; flex-direction: column; gap: 8px; max-height: 280px; overflow-y: auto; }
2299
+ .settings-skills-item-row { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; background: var(--settings-inset, rgba(255, 255, 255, 0.03)); font-size: 12px; }
2300
+ .settings-skills-item-row strong { font-size: 13px; }
2301
+ .settings-skills-item-row code { font-size: 10px; color: var(--settings-muted); overflow-wrap: anywhere; display: block; }
2302
+ .settings-skills-item-actions { display: flex; gap: 6px; align-items: center; flex: none; }
2303
+ .settings-skills-matrix-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
2304
+ .settings-skills-matrix-card { border: 1px solid var(--settings-border, #8885); border-radius: 10px; padding: 12px 14px; background: var(--settings-surface, transparent); display: flex; flex-direction: column; gap: 8px; }
2305
+ .settings-skills-matrix-head { display: flex; justify-content: space-between; align-items: center; }
2306
+ .settings-skills-matrix-head span { font-size: 11px; color: var(--settings-muted); }
2307
+ .settings-skills-tags { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
2308
+ .settings-skills-tag { font-size: 11px; padding: 3px 8px; border-radius: 6px; background: rgba(16, 163, 127, 0.15); color: #10a37f; border: 1px solid rgba(16, 163, 127, 0.3); }
2309
+ .settings-skills-sync-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 4px; }
2310
+ .settings-skills-sync-modal { border: 1px solid var(--settings-border, #8885); border-radius: 12px; background: var(--settings-bg, #1e1e1e); color: inherit; padding: 20px 24px; max-width: 420px; box-shadow: 0 16px 32px rgba(0, 0, 0, 0.5); }
2311
+ .settings-skills-sync-modal::backdrop { background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(2px); }
2312
+ .settings-skills-sync-modal h3 { margin: 0 0 8px; font-size: 16px; }
2313
+ .settings-skills-sync-modal p { margin: 0 0 12px; font-size: 12px; color: var(--settings-muted); }
2314
+ .settings-mcp-overview-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 14px; margin-top: 10px; }
2315
+ .settings-mcp-harness-card { border: 1px solid var(--settings-border, #8885); border-radius: 12px; padding: 14px; background: var(--settings-surface, transparent); display: flex; flex-direction: column; gap: 10px; }
2316
+ .settings-mcp-harness-head { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--settings-border, #8883); padding-bottom: 8px; }
2317
+ .settings-mcp-harness-head strong { font-size: 14px; }
2318
+ .settings-mcp-native-box { padding: 8px 10px; border-radius: 8px; background: rgba(16, 163, 127, 0.08); border: 1px dashed rgba(16, 163, 127, 0.3); font-size: 11px; display: flex; flex-direction: column; gap: 4px; }
2319
+ .settings-mcp-native-title { font-weight: 600; color: #10a37f; display: flex; align-items: center; gap: 6px; }
2320
+ .settings-mcp-native-item { display: flex; flex-direction: column; gap: 2px; color: var(--settings-muted); }
2321
+ .settings-mcp-native-item code { font-size: 10px; overflow-wrap: anywhere; }
2322
+ .settings-mcp-list-compact { display: flex; flex-direction: column; gap: 8px; max-height: 280px; overflow-y: auto; }
2323
+ .settings-mcp-item-row { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; background: var(--settings-inset, rgba(255, 255, 255, 0.03)); font-size: 12px; }
2324
+ .settings-mcp-item-row strong { font-size: 13px; }
2325
+ .settings-mcp-item-row code { font-size: 10px; color: var(--settings-muted); overflow-wrap: anywhere; display: block; }
2326
+ .settings-mcp-item-actions { display: flex; gap: 6px; align-items: center; flex: none; }
2327
+ .settings-mcp-card-footer { display: flex; justify-content: flex-end; margin-top: auto; padding-top: 6px; border-top: 1px solid var(--settings-border, #8882); }
2328
+ .settings-mcp-matrix-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
2329
+ .settings-mcp-matrix-card { border: 1px solid var(--settings-border, #8885); border-radius: 10px; padding: 12px 14px; background: var(--settings-surface, transparent); display: flex; flex-direction: column; gap: 8px; }
2330
+ .settings-mcp-matrix-head { display: flex; justify-content: space-between; align-items: center; }
2331
+ .settings-mcp-matrix-head span { font-size: 11px; color: var(--settings-muted); }
2332
+ .settings-mcp-tags { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
2333
+ .settings-mcp-tag { font-size: 11px; padding: 3px 8px; border-radius: 6px; background: rgba(16, 163, 127, 0.15); color: #10a37f; border: 1px solid rgba(16, 163, 127, 0.3); }
2334
+ .settings-mcp-tag--disabled { background: rgba(255, 255, 255, 0.05); color: var(--settings-muted); border-color: var(--settings-border, #8885); }
2335
+ .settings-mcp-sync-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 4px; }
2336
+ .settings-mcp-distribute-modal { border: 1px solid var(--settings-border, #8885); border-radius: 12px; background: var(--settings-bg, #1e1e1e); color: inherit; padding: 20px 24px; max-width: 540px; width: 92%; max-height: 85vh; overflow-y: auto; box-shadow: 0 16px 32px rgba(0, 0, 0, 0.5); }
2337
+ .settings-mcp-distribute-modal::backdrop { background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(2px); }
2338
+ .settings-mcp-distribute-modal h3 { margin: 0 0 8px; font-size: 16px; }
2339
+ .settings-mcp-distribute-modal p { margin: 0 0 12px; font-size: 12px; color: var(--settings-muted); }
2340
+
2341
+
@@ -79,7 +79,7 @@ describe("Renderer Agent picker presentation", () => {
79
79
  isDefault: false,
80
80
  },
81
81
  ),
82
- ).toBe("Agent: Codex · reviewer@example.com (locked)");
82
+ ).toBe("Agent: Codex(官方原生) · reviewer@example.com (locked)");
83
83
  expect(rendererAgentPickerTooltip({ agent: "claude-code", phase: "locked" }, undefined)).toBe(
84
84
  "Agent: Claude Code (locked)",
85
85
  );
@@ -94,7 +94,7 @@ describe("Renderer Agent picker presentation", () => {
94
94
  "grok",
95
95
  ]),
96
96
  ).toEqual({
97
- label: "Codex",
97
+ label: "Codex(官方原生)",
98
98
  triggerDisabled: false,
99
99
  nativeModelHidden: false,
100
100
  optionDisabled: { codex: false, pi: true, "claude-code": true, grok: true },
@@ -107,27 +107,58 @@ describe("Renderer Agent picker presentation", () => {
107
107
  expect(
108
108
  rendererAgentPickerView({ agent: "codex", phase: "draft" }, "ready", false, ["codex"], {}, 2),
109
109
  ).toMatchObject({
110
- label: "Codex",
110
+ label: "Codex(官方原生)",
111
111
  triggerDisabled: false,
112
112
  optionDisabled: { codex: false },
113
113
  });
114
114
  });
115
115
 
116
- it("hides the native Model for an external Agent and locks submitted selection", () => {
116
+ it("turns a submitted external Agent picker into a Harness handoff picker", () => {
117
117
  expect(
118
- rendererAgentPickerView({ agent: "pi", phase: "locked" }, "ready", false, ["codex", "pi"], {
119
- pi: "ready",
120
- }),
118
+ rendererAgentPickerView(
119
+ { agent: "pi", phase: "locked" },
120
+ "ready",
121
+ false,
122
+ ["codex", "pi", "claude-code"],
123
+ { pi: "ready", "claude-code": "ready" },
124
+ ),
121
125
  ).toEqual({
122
126
  label: "Pi",
123
- triggerDisabled: true,
127
+ triggerDisabled: false,
124
128
  nativeModelHidden: true,
125
- optionDisabled: { codex: true, pi: true },
126
- downloadVisible: { pi: false },
127
- errorVisible: { pi: false },
129
+ optionDisabled: { codex: true, pi: true, "claude-code": false },
130
+ downloadVisible: { pi: false, "claude-code": false },
131
+ errorVisible: { pi: false, "claude-code": false },
128
132
  });
129
133
  });
130
134
 
135
+ it("offers another ready external Harness while keeping official Codex unavailable", () => {
136
+ expect(
137
+ rendererAgentPickerView(
138
+ { agent: "pi", phase: "locked" },
139
+ "ready",
140
+ false,
141
+ ["codex", "pi", "claude-code", "grok"],
142
+ { pi: "ready", "claude-code": "ready", grok: "unavailable" },
143
+ ),
144
+ ).toMatchObject({
145
+ triggerDisabled: false,
146
+ optionDisabled: { codex: true, pi: true, "claude-code": false, grok: true },
147
+ });
148
+ });
149
+
150
+ it("keeps an official Codex conversation locked because HostRuntime does not own it", () => {
151
+ expect(
152
+ rendererAgentPickerView(
153
+ { agent: "codex", phase: "locked" },
154
+ "ready",
155
+ false,
156
+ ["codex", "pi"],
157
+ { pi: "ready" },
158
+ ),
159
+ ).toMatchObject({ triggerDisabled: true, optionDisabled: { codex: true, pi: true } });
160
+ });
161
+
131
162
  it("hides the native Model and disables all choices while switching", () => {
132
163
  expect(
133
164
  rendererAgentPickerView({ agent: "codex", phase: "draft" }, "ready", true, ["codex", "pi"]),
@@ -145,7 +176,7 @@ describe("Renderer Agent picker presentation", () => {
145
176
  pi: "notInstalled",
146
177
  }),
147
178
  ).toEqual({
148
- label: "Codex",
179
+ label: "Codex(官方原生)",
149
180
  triggerDisabled: false,
150
181
  nativeModelHidden: false,
151
182
  optionDisabled: { codex: false, pi: true },
@@ -160,7 +191,7 @@ describe("Renderer Agent picker presentation", () => {
160
191
  pi: "error",
161
192
  }),
162
193
  ).toEqual({
163
- label: "Codex",
194
+ label: "Codex(官方原生)",
164
195
  triggerDisabled: false,
165
196
  nativeModelHidden: false,
166
197
  optionDisabled: { codex: false, pi: true },
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { CHATGPT_CONTEXT_MAX_LENGTH, buildChatGptContextDraft, isChatGptQuickChatEditor, redactChatGptContextText, sanitizeChatGptSourceUrl } from "../src/renderer-chatgpt-context.js";
3
+
4
+ describe("ChatGPT context package", () => {
5
+ it("marks imported text as untrusted context", () => {
6
+ const draft = buildChatGptContextDraft({ title: "架构讨论", sourceUrl: "https://chatgpt.com/c/example?utm_source=secret#turn", mode: "loaded-messages", messages: [{ role: "user", text: "给一个迁移方案" }, { role: "assistant", text: "先建立共享契约" }], truncated: false });
7
+ expect(draft).toContain("不可信历史资料"); expect(draft).toContain("不恢复原聊天"); expect(draft).toContain("User: 给一个迁移方案"); expect(draft).toContain("Assistant: 先建立共享契约"); expect(draft).toContain("https://chatgpt.com/c/example"); expect(draft).not.toContain("utm_source");
8
+ });
9
+ it("redacts credentials and caps the package", () => {
10
+ expect(redactChatGptContextText("Authorization: Bearer abcdefghijklmnopqrstuvwxyz sk-abcdefghijklmnopqrstuvwxyz123456")).not.toContain("abcdefghijklmnopqrstuvwxyz");
11
+ const draft = buildChatGptContextDraft({ title: "大段聊天", mode: "selection", messages: [{ role: "user", text: "x".repeat(CHATGPT_CONTEXT_MAX_LENGTH * 2) }], truncated: true });
12
+ expect(draft.length).toBeLessThanOrEqual(CHATGPT_CONTEXT_MAX_LENGTH); expect(draft).toContain("已截断");
13
+ });
14
+ it("accepts only sanitized HTTPS ChatGPT links", () => {
15
+ expect(sanitizeChatGptSourceUrl("https://chatgpt.com/c/abc?token=nope#part")).toBe("https://chatgpt.com/c/abc");
16
+ expect(sanitizeChatGptSourceUrl("http://chatgpt.com/c/abc")).toBeUndefined(); expect(sanitizeChatGptSourceUrl("https://example.com/c/abc")).toBeUndefined();
17
+ });
18
+ it("ignores partial DOM fixtures that are not browser Elements", () => {
19
+ expect(isChatGptQuickChatEditor({} as Element)).toBe(false);
20
+ });
21
+ });
@@ -1,5 +1,8 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
- import { RendererCodexAccountState } from "../src/renderer-codex-account-state.js";
2
+ import {
3
+ codexAccountRouteOverride,
4
+ RendererCodexAccountState,
5
+ } from "../src/renderer-codex-account-state.js";
3
6
  import { createRendererModelClient } from "../src/renderer-model-client.js";
4
7
 
5
8
  const account = (accountId: string, active = false) => ({
@@ -17,6 +20,15 @@ function createState(sendRequest: (method: string, params: unknown) => Promise<u
17
20
  }
18
21
 
19
22
  describe("Host-scoped Codex Account state", () => {
23
+ it("routes isolated Accounts through Harness Mix even when they are the active default", () => {
24
+ const accounts = [
25
+ { ...account("official"), management: "native" as const },
26
+ { ...account("work", true), management: "isolated" as const },
27
+ ];
28
+ expect(codexAccountRouteOverride(accounts, "work")).toBe("work");
29
+ expect(codexAccountRouteOverride(accounts, "official")).toBeNull();
30
+ });
31
+
20
32
  it("coalesces concurrent refreshes on the same concrete client", async () => {
21
33
  const response = Promise.withResolvers<unknown>();
22
34
  const sendRequest = vi.fn(() => response.promise);
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ HARNESS_HANDOFF_NOTE_MAX_LENGTH,
5
+ rendererHarnessHandoffMessages,
6
+ } from "../src/renderer-harness-handoff.js";
7
+
8
+ describe("Renderer Harness handoff", () => {
9
+ it("uses a bounded note compatible with the Host switch contract", () => {
10
+ expect(HARNESS_HANDOFF_NOTE_MAX_LENGTH).toBe(2000);
11
+ });
12
+
13
+ it("explains that the task and working tree stay in place", () => {
14
+ const messages = rendererHarnessHandoffMessages("zh-CN");
15
+ expect(messages.title).toBe("接力当前任务");
16
+ expect(messages.description).toMatch(/任务窗口.*对话记录.*文件现场/);
17
+ expect(messages.context).toMatch(/近期对话.*最新方案.*计划.*变更文件/);
18
+ expect(messages.running).toMatch(/先停止或等待完成/);
19
+ });
20
+ });
@@ -278,12 +278,13 @@ describe("Renderer fixed Model request client", () => {
278
278
  },
279
279
  })
280
280
  .mockResolvedValueOnce({ status: null })
281
- .mockResolvedValueOnce({ threadId: "thread-1" });
281
+ .mockResolvedValueOnce({ threadId: "thread-1", checkpointId: "handoff-1" });
282
282
  const client = createRendererModelClient([{ addNotificationCallback, sendRequest }]);
283
283
  if (!client) throw new Error("Synthetic Model client was not created");
284
284
  expect(Object.keys(client).sort()).toEqual([
285
285
  "activateCodexAccount",
286
286
  "cancelCodexAccountLogin",
287
+ "changeSkill",
287
288
  "checkUpdate",
288
289
  "consumeCodexAccountResetCredit",
289
290
  "createCodexAccount",
@@ -299,18 +300,24 @@ describe("Renderer fixed Model request client", () => {
299
300
  "inspectThreadCommands",
300
301
  "inspectThreadUsage",
301
302
  "installHarness",
303
+ "integrationCatalog",
302
304
  "listCodexAccounts",
303
305
  "listCollaborationAgents",
304
306
  "listHarnessAccounts",
305
307
  "listHarnessPlugins",
306
308
  "listHarnessSessions",
309
+ "listIntegrations",
307
310
  "listSessionImportSources",
308
311
  "listThreadOwnership",
309
312
  "loginHarnessAccount",
313
+ "logoutCodexAccount",
310
314
  "messageThread",
311
315
  "openHarnessWebUi",
316
+ "readCurrentVersion",
312
317
  "readUpdateStatus",
313
318
  "refreshCodexAccounts",
319
+ "removeMcp",
320
+ "saveMcp",
314
321
  "selectThreadModel",
315
322
  "selectThreadPermissionMode",
316
323
  "selectThreadThinking",
@@ -444,7 +451,7 @@ describe("Renderer fixed Model request client", () => {
444
451
  harnessId: "claude-code",
445
452
  note: "继续补测试",
446
453
  }),
447
- ).resolves.toEqual({ threadId: "thread-1" });
454
+ ).resolves.toEqual({ threadId: "thread-1", checkpointId: "handoff-1" });
448
455
  expect(sendRequest).toHaveBeenNthCalledWith(13, THREAD_HARNESS_SWITCH_METHOD, {
449
456
  threadId: "thread-1",
450
457
  harnessId: "claude-code",
@@ -75,6 +75,6 @@ describe("Renderer settings localization", () => {
75
75
  createDefaultRendererSettingsPages(rendererSettingsMessages("zh-CN")).map(
76
76
  ({ label }) => label,
77
77
  ),
78
- ).toEqual(["连接", "账号", "会话导入", "更新", "关于"]);
78
+ ).toEqual(["连接", "账号", "MCP", "Skills", "会话导入", "更新", "关于"]);
79
79
  });
80
80
  });