@harness-mix/cli 0.1.5 → 0.1.7

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 (92) hide show
  1. package/README.md +86 -56
  2. package/docs/harness-management.md +4 -4
  3. package/docs/multi-agent-collaboration.md +9 -9
  4. package/docs/storage-verification-design.md +49 -0
  5. package/output/native-build/renderer-extension.js +4210 -2273
  6. package/package.json +11 -9
  7. package/scripts/codebuddy-migration-test.cjs +1 -1
  8. package/scripts/collaboration-test.cjs +8 -3
  9. package/scripts/collaboration-ui-smoke.cjs +40 -26
  10. package/scripts/core-runtime-test.cjs +9 -1
  11. package/scripts/integrations-test.cjs +63 -4
  12. package/scripts/integrations-ui-smoke.cjs +77 -16
  13. package/scripts/native-protocol-test.cjs +113 -2
  14. package/scripts/native-sidebar-test.cjs +7 -0
  15. package/scripts/native-skill-roots-test.cjs +90 -0
  16. package/scripts/native-ui-smoke.cjs +23 -4
  17. package/scripts/projector-test.cjs +13 -0
  18. package/scripts/storage-verification-test.cjs +78 -0
  19. package/scripts/switch-harness-test.cjs +1 -1
  20. package/scripts/test-live-mentions.cjs +152 -0
  21. package/src/main/adapters/antigravity.js +20 -17
  22. package/src/main/adapters/claude.js +41 -8
  23. package/src/main/adapters/codebuddy.js +1 -0
  24. package/src/main/adapters/codex.js +20 -2
  25. package/src/main/adapters/cursor.js +5 -1
  26. package/src/main/adapters/dsh.js +8 -1
  27. package/src/main/adapters/grok.js +4 -1
  28. package/src/main/adapters/hermes.js +8 -0
  29. package/src/main/adapters/kiro.js +2 -1
  30. package/src/main/adapters/managed-mcp.js +15 -2
  31. package/src/main/adapters/omp.js +11 -0
  32. package/src/main/adapters/openclaw.js +6 -0
  33. package/src/main/adapters/opencode.js +5 -1
  34. package/src/main/adapters/pi.js +5 -1
  35. package/src/main/adapters/qoder.js +1 -0
  36. package/src/main/adapters/trae.js +4 -0
  37. package/src/main/adapters/zcode.js +3 -0
  38. package/src/main/host/collaboration.js +59 -11
  39. package/src/main/host/core-session.js +7 -1
  40. package/src/main/host/handoff-checkpoints.js +8 -1
  41. package/src/main/host/integrations.js +164 -26
  42. package/src/main/host/runtime.js +270 -116
  43. package/src/main/host/store.js +32 -11
  44. package/src/main/host/thread-storage.js +73 -0
  45. package/src/main/host/thread-store.js +123 -0
  46. package/src/main/host/verification-gates.js +118 -0
  47. package/src/main/native/protocol.js +209 -83
  48. package/src/main/native/thread-list.js +3 -0
  49. package/src/main/protocol-core/projector.js +20 -3
  50. package/src/main/shared-contracts/event.js +2 -1
  51. package/src/main/shared-contracts/item.js +1 -0
  52. package/src/native-ui/renderer-extension/dist/types/harness-mix-settings.d.ts +2 -2
  53. package/src/native-ui/renderer-extension/dist/types/harness-mix-settings.d.ts.map +1 -1
  54. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
  55. package/src/native-ui/renderer-extension/dist/types/renderer-chatgpt-context.d.ts.map +1 -1
  56. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts +11 -0
  57. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts.map +1 -1
  58. package/src/native-ui/renderer-extension/dist/types/renderer-integrations-client.d.ts +29 -4
  59. package/src/native-ui/renderer-extension/dist/types/renderer-integrations-client.d.ts.map +1 -1
  60. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +5 -0
  61. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -1
  62. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts +2 -1
  63. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts.map +1 -1
  64. package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts +1 -1
  65. package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts.map +1 -1
  66. package/src/native-ui/renderer-extension/dist/types/settings/integrations-page.d.ts +2 -1
  67. package/src/native-ui/renderer-extension/dist/types/settings/integrations-page.d.ts.map +1 -1
  68. package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts.map +1 -1
  69. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +5 -3
  70. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
  71. package/src/native-ui/renderer-extension/dist/types/settings/storage-page.d.ts +20 -0
  72. package/src/native-ui/renderer-extension/dist/types/settings/storage-page.d.ts.map +1 -0
  73. package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
  74. package/src/native-ui/renderer-extension/src/harness-mix-settings.ts +8 -88
  75. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +150 -168
  76. package/src/native-ui/renderer-extension/src/renderer-chatgpt-context.ts +5 -1
  77. package/src/native-ui/renderer-extension/src/renderer-harness-mentions.ts +556 -123
  78. package/src/native-ui/renderer-extension/src/renderer-integrations-client.ts +36 -3
  79. package/src/native-ui/renderer-extension/src/renderer-model-client.ts +4 -0
  80. package/src/native-ui/renderer-extension/src/renderer-settings-lifecycle.ts +12 -0
  81. package/src/native-ui/renderer-extension/src/settings/icons.ts +6 -3
  82. package/src/native-ui/renderer-extension/src/settings/integrations-page.ts +1434 -62
  83. package/src/native-ui/renderer-extension/src/settings/localization.ts +8 -4
  84. package/src/native-ui/renderer-extension/src/settings/pages.ts +17 -8
  85. package/src/native-ui/renderer-extension/src/settings/shell.css +103 -8
  86. package/src/native-ui/renderer-extension/src/settings/storage-page.ts +74 -0
  87. package/src/native-ui/renderer-extension/test/renderer-chatgpt-context.test.ts +4 -1
  88. package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +2 -0
  89. package/src/native-ui/renderer-extension/test/settings/harness-settings.test.ts +5 -0
  90. package/src/native-ui/renderer-extension/test/settings/localization.test.ts +1 -1
  91. package/src/native-ui/renderer-extension/test/settings/pages.test.ts +50 -4
  92. package/src/native-ui/renderer-extension/test/settings/shell.test.ts +8 -2
@@ -511,8 +511,10 @@ const ENGLISH_MESSAGES: RendererSettingsMessages = Object.freeze({
511
511
  pageLabels: Object.freeze({
512
512
  connections: "Connections",
513
513
  accounts: "Accounts",
514
- integrations: 'MCP / Skills',
515
- "session-import": "Session Import",
514
+ mcp: 'MCP',
515
+ skills: 'Skills',
516
+ "session-import": "Session Import",
517
+ storage: "Storage & verification",
516
518
  updates: "Updates",
517
519
  about: "About",
518
520
  }),
@@ -764,11 +766,13 @@ const CHINESE_MESSAGES: RendererSettingsMessages = Object.freeze({
764
766
  aboutOpenSource: "Harness Mix 是一个开源项目,开源地址:",
765
767
  aboutStarCallout: "⭐ 如果这个项目对你有帮助,请给我们一个 Star!⭐",
766
768
  aboutRepository: "开源仓库",
767
- pageLabels: Object.freeze({
769
+ pageLabels: Object.freeze({
768
770
  connections: "连接",
769
771
  accounts: "账号",
770
- integrations: 'MCP / Skills',
772
+ mcp: 'MCP',
773
+ skills: 'Skills',
771
774
  "session-import": "会话导入",
775
+ storage: "存储与验证",
772
776
  updates: "更新",
773
777
  about: "关于",
774
778
  }),
@@ -27,9 +27,10 @@ import {
27
27
  type RendererImportedThreadOpener,
28
28
  } from "./session-import-page.js";
29
29
  import { createReleaseNotesElement } from "./release-notes.js";
30
- import { createIntegrationsSettingsPage } from './integrations-page.js';
30
+ import { createMcpSettingsPage, createSkillsSettingsPage } from './integrations-page.js';
31
31
  import type { RendererIntegrationsClient } from '../renderer-integrations-client.js';
32
- import { createAccountsSettingsPage, type RendererCodexAccountClient } from "./accounts-page.js";
32
+ import { createAccountsSettingsPage, type RendererCodexAccountClient } from "./accounts-page.js";
33
+ import { createStorageSettingsPage, type RendererStorageClient } from './storage-page.js';
33
34
 
34
35
  export type {
35
36
  RendererConnectionAgentSnapshot,
@@ -74,8 +75,10 @@ function windowsInstallerDownloadUrl(window: Window | null | undefined, version:
74
75
  export const DEFAULT_RENDERER_SETTINGS_PAGE_IDS = [
75
76
  "connections",
76
77
  "accounts",
77
- "integrations",
78
- "session-import",
78
+ "mcp",
79
+ "skills",
80
+ "session-import",
81
+ "storage",
79
82
  "updates",
80
83
  "about",
81
84
  ] as const;
@@ -625,12 +628,15 @@ export function createDefaultRendererSettingsPages(
625
628
  openImportedThread: RendererImportedThreadOpener = () =>
626
629
  Promise.reject(new Error("Imported Thread navigation is unavailable")),
627
630
  getIntegrationsClient: () => RendererIntegrationsClient | null = () => null,
628
- ): readonly RendererSettingsPageDefinition[] {
631
+ getStorageClient: () => RendererStorageClient | null = () => null,
632
+ ): readonly RendererSettingsPageDefinition[] {
629
633
  return Object.freeze([
630
634
  createConnectionsSettingsPage(messages, getDiagnostics),
631
635
  createAccountsSettingsPage(messages, getAccountClient),
632
- createIntegrationsSettingsPage(messages, getIntegrationsClient),
633
- createSessionImportSettingsPage(messages, getSessionImportClient, openImportedThread),
636
+ createMcpSettingsPage(messages, getIntegrationsClient),
637
+ createSkillsSettingsPage(messages, getIntegrationsClient),
638
+ createSessionImportSettingsPage(messages, getSessionImportClient, openImportedThread),
639
+ createStorageSettingsPage(messages, getStorageClient),
634
640
  updatesPage(messages, getUpdateClient),
635
641
  aboutPage(messages, getUpdateClient),
636
642
  ]);
@@ -644,6 +650,7 @@ export function createDefaultRendererSettingsRegistry(
644
650
  getSessionImportClient: () => RendererSessionImportClient | null = () => null,
645
651
  openImportedThread?: RendererImportedThreadOpener,
646
652
  getIntegrationsClient?: () => RendererIntegrationsClient | null,
653
+ getStorageClient?: () => RendererStorageClient | null,
647
654
  ): RendererSettingsPageRegistry {
648
655
  return createRendererSettingsPageRegistry(
649
656
  createDefaultRendererSettingsPages(
@@ -654,8 +661,10 @@ export function createDefaultRendererSettingsRegistry(
654
661
  getSessionImportClient,
655
662
  openImportedThread,
656
663
  getIntegrationsClient,
664
+ getStorageClient,
657
665
  ),
658
666
  );
659
667
  }
660
668
 
661
- export type { RendererCodexAccountClient } from "./accounts-page.js";
669
+ export type { RendererCodexAccountClient } from "./accounts-page.js";
670
+ export type { RendererStorageClient, RendererStorageInspection } from './storage-page.js';
@@ -1813,13 +1813,19 @@ button {
1813
1813
  outline-offset: -2px;
1814
1814
  }
1815
1815
 
1816
- .settings-update-actions {
1816
+ .settings-update-actions {
1817
1817
  display: flex;
1818
1818
  align-items: center;
1819
1819
  min-width: 0;
1820
1820
  gap: 12px;
1821
1821
  flex-wrap: wrap;
1822
- }
1822
+ }
1823
+
1824
+ .settings-storage-block {
1825
+ display: grid;
1826
+ gap: 12px;
1827
+ margin-bottom: 16px;
1828
+ }
1823
1829
 
1824
1830
  .settings-command-button,
1825
1831
  .settings-update-link {
@@ -2236,16 +2242,105 @@ button {
2236
2242
  .settings-install-steps li {
2237
2243
  margin-bottom: 2px;
2238
2244
  }
2239
- /* MCP / Skills share the existing settings shell and its theme. */
2240
- .settings-integrations-controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 18px 0; }
2245
+ /* MCP and Skills share native settings controls while keeping separate flows. */
2246
+ .settings-integrations-controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 18px 0 12px; }
2241
2247
  .settings-integrations-controls input { flex: 1; min-width: 180px; }
2242
2248
  .settings-integrations-controls input[hidden] { display: none; }
2243
2249
  .settings-integrations-controls select, .settings-integrations-controls input,
2244
- .settings-integrations-field input, .settings-integrations-field textarea { box-sizing: border-box; padding: 8px 10px; border: 1px solid var(--border, #8885); border-radius: 7px; color: inherit; background: transparent; font: inherit; }
2250
+ .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; }
2251
+ .settings-integrations-status { min-height: 18px; margin: 0 0 6px; color: var(--settings-muted); font-size: 12px; }
2245
2252
  .settings-integrations-body p { font-size: 12px; line-height: 1.6; opacity: .8; }
2246
- .settings-integrations-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; border: 1px solid var(--border, #8885); padding: 14px; margin: 10px 0; border-radius: 10px; }
2253
+ .settings-integrations-toolbar { display: flex; gap: 10px; align-items: center; margin: 8px 0 16px; }
2254
+ .settings-integrations-toolbar input { flex: 1; }
2255
+ .settings-integrations-list { display: grid; gap: 8px; }
2256
+ .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); }
2247
2257
  .settings-integrations-row > div { flex: 1; min-width: 180px; }
2248
- .settings-integrations-row code { display: block; overflow-wrap: anywhere; font-size: 11px; }
2249
- .settings-integrations-form { display: grid; gap: 12px; margin: 16px 0 28px; }
2258
+ .settings-integrations-row p { margin: 3px 0; }
2259
+ .settings-integrations-row code { display: block; overflow-wrap: anywhere; font-size: 11px; color: var(--settings-muted); }
2260
+ .settings-integrations-row--native { border-style: dashed; }
2261
+ .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; }
2262
+ .settings-integrations-editor-head { display: flex; align-items: center; gap: 12px; }
2263
+ .settings-integrations-editor-head h3 { margin: 0; }
2264
+ .settings-integrations-form { display: grid; gap: 14px; margin: 16px 0 28px; }
2265
+ .settings-integrations-form--card { padding: 16px; border: 1px solid var(--settings-border, #8885); border-radius: 12px; }
2250
2266
  .settings-integrations-field { display: grid; gap: 6px; font-size: 12px; }
2251
2267
  .settings-integrations-form > button { justify-self: start; }
2268
+ .settings-mcp-args { display: grid; gap: 7px; }
2269
+ .settings-mcp-arg { display: flex; gap: 8px; align-items: center; }
2270
+ .settings-mcp-arg input { flex: 1; }
2271
+ .settings-mcp-kv-row { display: flex; gap: 8px; align-items: center; }
2272
+ .settings-mcp-kv-row input { flex: 1; }
2273
+ .settings-mcp-add-arg { margin-top: 7px; }
2274
+ .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; }
2275
+ .settings-mcp-docs-link:hover { color: var(--settings-text, #fff); text-decoration: underline; }
2276
+ .settings-integrations-editor-head .settings-command-button--danger { margin-left: auto; }
2277
+ .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; }
2278
+ .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; }
2279
+ .settings-mcp-type-btn:hover { color: var(--settings-text, #fff); }
2280
+ .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; }
2281
+ .settings-mcp-notice { margin: 4px 0 0; color: var(--settings-muted, #888); font-size: 12px; line-height: 1.5; }
2282
+ .settings-mcp-section { display: grid; gap: 14px; }
2283
+ .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; }
2284
+ .settings-mcp-icon-btn:hover:not(:disabled) { background: rgba(239, 68, 68, 0.15); border-color: rgba(239, 68, 68, 0.4); color: #f87171; }
2285
+ .settings-mcp-icon-btn svg { width: 14px; height: 14px; display: block; }
2286
+ .settings-integrations-callout { margin: 0; padding: 10px 12px; border-radius: 8px; background: var(--settings-inset); }
2287
+ .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; }
2288
+ .settings-skill-dropzone > span { max-width: 520px; color: var(--settings-muted); font-size: 12px; line-height: 18px; }
2289
+ .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); }
2290
+ .settings-skill-dropzone[data-disabled="true"] { opacity: .55; }
2291
+ .settings-skills-target-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin-top: 6px; }
2292
+ .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; }
2293
+ .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); }
2294
+ .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; }
2295
+ .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; }
2296
+ .settings-skills-tab-btn:hover { color: var(--settings-text, #fff); }
2297
+ .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; }
2298
+ .settings-skills-overview-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 14px; margin-top: 10px; }
2299
+ .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; }
2300
+ .settings-skills-harness-head { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--settings-border, #8883); padding-bottom: 8px; }
2301
+ .settings-skills-harness-head strong { font-size: 14px; }
2302
+ .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); }
2303
+ .settings-skills-badge--active { background: rgba(16, 163, 127, 0.15); color: #10a37f; }
2304
+ .settings-skills-list-compact { display: flex; flex-direction: column; gap: 8px; max-height: 280px; overflow-y: auto; }
2305
+ .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; }
2306
+ .settings-skills-item-row strong { font-size: 13px; }
2307
+ .settings-skills-item-row code { font-size: 10px; color: var(--settings-muted); overflow-wrap: anywhere; display: block; }
2308
+ .settings-skills-item-actions { display: flex; gap: 6px; align-items: center; flex: none; }
2309
+ .settings-skills-matrix-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
2310
+ .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; }
2311
+ .settings-skills-matrix-head { display: flex; justify-content: space-between; align-items: center; }
2312
+ .settings-skills-matrix-head span { font-size: 11px; color: var(--settings-muted); }
2313
+ .settings-skills-tags { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
2314
+ .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); }
2315
+ .settings-skills-sync-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 4px; }
2316
+ .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); }
2317
+ .settings-skills-sync-modal::backdrop { background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(2px); }
2318
+ .settings-skills-sync-modal h3 { margin: 0 0 8px; font-size: 16px; }
2319
+ .settings-skills-sync-modal p { margin: 0 0 12px; font-size: 12px; color: var(--settings-muted); }
2320
+ .settings-mcp-overview-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 14px; margin-top: 10px; }
2321
+ .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; }
2322
+ .settings-mcp-harness-head { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--settings-border, #8883); padding-bottom: 8px; }
2323
+ .settings-mcp-harness-head strong { font-size: 14px; }
2324
+ .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; }
2325
+ .settings-mcp-native-title { font-weight: 600; color: #10a37f; display: flex; align-items: center; gap: 6px; }
2326
+ .settings-mcp-native-item { display: flex; flex-direction: column; gap: 2px; color: var(--settings-muted); }
2327
+ .settings-mcp-native-item code { font-size: 10px; overflow-wrap: anywhere; }
2328
+ .settings-mcp-list-compact { display: flex; flex-direction: column; gap: 8px; max-height: 280px; overflow-y: auto; }
2329
+ .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; }
2330
+ .settings-mcp-item-row strong { font-size: 13px; }
2331
+ .settings-mcp-item-row code { font-size: 10px; color: var(--settings-muted); overflow-wrap: anywhere; display: block; }
2332
+ .settings-mcp-item-actions { display: flex; gap: 6px; align-items: center; flex: none; }
2333
+ .settings-mcp-card-footer { display: flex; justify-content: flex-end; margin-top: auto; padding-top: 6px; border-top: 1px solid var(--settings-border, #8882); }
2334
+ .settings-mcp-matrix-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
2335
+ .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; }
2336
+ .settings-mcp-matrix-head { display: flex; justify-content: space-between; align-items: center; }
2337
+ .settings-mcp-matrix-head span { font-size: 11px; color: var(--settings-muted); }
2338
+ .settings-mcp-tags { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
2339
+ .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); }
2340
+ .settings-mcp-tag--disabled { background: rgba(255, 255, 255, 0.05); color: var(--settings-muted); border-color: var(--settings-border, #8885); }
2341
+ .settings-mcp-sync-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 4px; }
2342
+ .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); }
2343
+ .settings-mcp-distribute-modal::backdrop { background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(2px); }
2344
+ .settings-mcp-distribute-modal h3 { margin: 0 0 8px; font-size: 16px; }
2345
+ .settings-mcp-distribute-modal p { margin: 0 0 12px; font-size: 12px; color: var(--settings-muted); }
2346
+
@@ -0,0 +1,74 @@
1
+ import type { RendererSettingsPageDefinition, RendererSettingsPageMountContext } from './core.js';
2
+ import type { RendererSettingsMessages } from './localization.js';
3
+ import { createRendererSettingsIcon } from './icons.js';
4
+
5
+ export interface RendererStorageInspection {
6
+ readonly storageSchemaVersion: number;
7
+ readonly threadCount: number;
8
+ readonly loadedThreadCount: number;
9
+ readonly indexBytes: number;
10
+ readonly recordBytes: number;
11
+ readonly recordCount: number;
12
+ readonly legacyBytes: number;
13
+ }
14
+
15
+ export interface RendererStorageClient {
16
+ inspectStorage(): Promise<RendererStorageInspection>;
17
+ optimizeStorage(): Promise<{ before: RendererStorageInspection; after: RendererStorageInspection }>;
18
+ }
19
+
20
+ function bytes(value: number): string {
21
+ if (value < 1024) return `${value} B`;
22
+ const units = ['KB', 'MB', 'GB'];
23
+ let scaled = value;
24
+ let unit = 'B';
25
+ for (const next of units) { scaled /= 1024; unit = next; if (scaled < 1024) break; }
26
+ return `${scaled.toFixed(scaled >= 10 ? 0 : 1)} ${unit}`;
27
+ }
28
+
29
+ export function createStorageSettingsPage(messages: RendererSettingsMessages, getClient: () => RendererStorageClient | null): RendererSettingsPageDefinition {
30
+ const zh = messages.locale === 'zh-CN';
31
+ return Object.freeze({
32
+ id: 'storage', label: zh ? '存储与验证' : 'Storage & verification', icon: 'storage',
33
+ mount(context: RendererSettingsPageMountContext) {
34
+ const document = context.content.ownerDocument;
35
+ const heading = document.createElement('div'); heading.className = 'settings-section-label'; heading.textContent = zh ? '会话存储' : 'Session storage';
36
+ const storage = document.createElement('section'); storage.className = 'settings-storage-block';
37
+ const panel = document.createElement('section'); panel.className = 'settings-update-panel'; panel.setAttribute('aria-live', 'polite');
38
+ const actions = document.createElement('div'); actions.className = 'settings-update-actions';
39
+ const optimize = document.createElement('button'); optimize.type = 'button'; optimize.className = 'settings-command-button';
40
+ optimize.append(createRendererSettingsIcon('storage', 16), zh ? '优化存储' : 'Optimize storage');
41
+ actions.append(optimize);
42
+ const render = (value: RendererStorageInspection): void => {
43
+ panel.replaceChildren();
44
+ const title = document.createElement('strong'); title.className = 'settings-update-panel__title'; title.textContent = `Schema v${value.storageSchemaVersion}`;
45
+ const copy = document.createElement('p'); copy.className = 'settings-update-summary';
46
+ copy.textContent = zh
47
+ ? `${value.threadCount} 个任务,冷启动仅载入 ${value.loadedThreadCount} 个完整任务;索引 ${bytes(value.indexBytes)},分片 ${bytes(value.recordBytes)}。`
48
+ : `${value.threadCount} tasks; ${value.loadedThreadCount} full tasks loaded at cold start. Index ${bytes(value.indexBytes)}, shards ${bytes(value.recordBytes)}.`;
49
+ panel.append(title, copy);
50
+ };
51
+ const client = getClient();
52
+ if (!client) { panel.textContent = zh ? '当前 Host 不支持存储治理。' : 'Storage governance is unavailable on this Host.'; optimize.disabled = true; }
53
+ else {
54
+ void context.runLatest(() => client.inspectStorage(), { success: render, failure(error) { panel.textContent = error instanceof Error ? error.message : String(error); } });
55
+ optimize.addEventListener('click', () => {
56
+ optimize.disabled = true;
57
+ void context.runLatest(() => client.optimizeStorage(), {
58
+ success(result) { optimize.disabled = false; render(result.after); },
59
+ failure(error) { optimize.disabled = false; panel.textContent = error instanceof Error ? error.message : String(error); },
60
+ });
61
+ });
62
+ }
63
+ const gate = document.createElement('section'); gate.className = 'settings-about-panel';
64
+ const gateTitle = document.createElement('strong'); gateTitle.textContent = zh ? '任务验证门禁' : 'Task verification gates';
65
+ const gateCopy = document.createElement('p'); gateCopy.textContent = zh
66
+ ? '在任务命令面板使用 /gate 配置策略,使用 /verify 立即运行。验证报告会显示在对应回合并随接力证据传递。'
67
+ : 'Use /gate in a task command palette to configure policy and /verify to run it. Reports appear on the Turn and travel with handoff evidence.';
68
+ gate.append(gateTitle, gateCopy);
69
+ storage.append(panel, actions);
70
+ context.content.append(heading, storage, gate);
71
+ return undefined;
72
+ },
73
+ });
74
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { CHATGPT_CONTEXT_MAX_LENGTH, buildChatGptContextDraft, redactChatGptContextText, sanitizeChatGptSourceUrl } from "../src/renderer-chatgpt-context.js";
2
+ import { CHATGPT_CONTEXT_MAX_LENGTH, buildChatGptContextDraft, isChatGptQuickChatEditor, redactChatGptContextText, sanitizeChatGptSourceUrl } from "../src/renderer-chatgpt-context.js";
3
3
 
4
4
  describe("ChatGPT context package", () => {
5
5
  it("marks imported text as untrusted context", () => {
@@ -15,4 +15,7 @@ describe("ChatGPT context package", () => {
15
15
  expect(sanitizeChatGptSourceUrl("https://chatgpt.com/c/abc?token=nope#part")).toBe("https://chatgpt.com/c/abc");
16
16
  expect(sanitizeChatGptSourceUrl("http://chatgpt.com/c/abc")).toBeUndefined(); expect(sanitizeChatGptSourceUrl("https://example.com/c/abc")).toBeUndefined();
17
17
  });
18
+ it("ignores partial DOM fixtures that are not browser Elements", () => {
19
+ expect(isChatGptQuickChatEditor({} as Element)).toBe(false);
20
+ });
18
21
  });
@@ -296,6 +296,7 @@ describe("Renderer fixed Model request client", () => {
296
296
  "inspectCodexAccountUsage",
297
297
  "inspectHarness",
298
298
  "inspectHarnessCommands",
299
+ "inspectStorage",
299
300
  "inspectThread",
300
301
  "inspectThreadCommands",
301
302
  "inspectThreadUsage",
@@ -313,6 +314,7 @@ describe("Renderer fixed Model request client", () => {
313
314
  "logoutCodexAccount",
314
315
  "messageThread",
315
316
  "openHarnessWebUi",
317
+ "optimizeStorage",
316
318
  "readCurrentVersion",
317
319
  "readUpdateStatus",
318
320
  "refreshCodexAccounts",
@@ -15,8 +15,13 @@ import { rendererSettingsMessages } from "../../src/settings/localization.js";
15
15
  import { RendererSettingsPageScope } from "../../src/settings/core.js";
16
16
  import { KNOWN_RENDERER_AGENTS, type ExternalRendererAgent } from "../../src/agent-selection-state.js";
17
17
  import { installRendererSettingsLifecycle } from "../../src/harness-mix-settings.js";
18
+ import { installRendererSettingsLifecycle as installCanonicalRendererSettingsLifecycle } from "../../src/renderer-settings-lifecycle.js";
18
19
 
19
20
  describe("Harness Mix Settings & Model Configuration", () => {
21
+ it("uses the canonical settings lifecycle from the live renderer entrypoint", () => {
22
+ expect(installRendererSettingsLifecycle).toBe(installCanonicalRendererSettingsLifecycle);
23
+ });
24
+
20
25
  it("provides known install commands without guessing npm packages for native CLIs", () => {
21
26
  const externalAgents = KNOWN_RENDERER_AGENTS.filter(
22
27
  (agent): agent is ExternalRendererAgent => agent !== "codex",
@@ -75,6 +75,6 @@ describe("Renderer settings localization", () => {
75
75
  createDefaultRendererSettingsPages(rendererSettingsMessages("zh-CN")).map(
76
76
  ({ label }) => label,
77
77
  ),
78
- ).toEqual(["连接", "账号", "MCP / Skills", "会话导入", "更新", "关于"]);
78
+ ).toEqual(["连接", "账号", "MCP", "Skills", "会话导入", "存储与验证", "更新", "关于"]);
79
79
  });
80
80
  });
@@ -2030,7 +2030,7 @@ describe("Renderer Session Import page", () => {
2030
2030
  scope.dispose();
2031
2031
  });
2032
2032
 
2033
- it("ignores a Session list that resolves after the settings page is disposed", async () => {
2033
+ it("ignores a Session list that resolves after the settings page is disposed", async () => {
2034
2034
  const listed = deferred<{ candidates: never[]; total: number }>();
2035
2035
  const client = {
2036
2036
  listSessionImportSources: vi.fn(async () => ({
@@ -2064,6 +2064,52 @@ describe("Renderer Session Import page", () => {
2064
2064
  await Promise.resolve();
2065
2065
  await Promise.resolve();
2066
2066
 
2067
- expect(visibleText(content)).toBe(before);
2068
- });
2069
- });
2067
+ expect(visibleText(content)).toBe(before);
2068
+ });
2069
+
2070
+ it("shows sharded storage metrics and optimizes loaded task data", async () => {
2071
+ const inspection = {
2072
+ storageSchemaVersion: 3,
2073
+ threadCount: 12,
2074
+ loadedThreadCount: 2,
2075
+ indexBytes: 2048,
2076
+ recordBytes: 5 * 1024 * 1024,
2077
+ recordCount: 12,
2078
+ legacyBytes: 0,
2079
+ };
2080
+ const client = {
2081
+ inspectStorage: vi.fn(async () => inspection),
2082
+ optimizeStorage: vi.fn(async () => ({ before: inspection, after: { ...inspection, recordBytes: 4 * 1024 * 1024 } })),
2083
+ };
2084
+ const page = createDefaultRendererSettingsPages(
2085
+ rendererSettingsMessages("zh-CN"),
2086
+ () => null,
2087
+ () => null,
2088
+ () => null,
2089
+ () => null,
2090
+ undefined,
2091
+ () => null,
2092
+ () => client,
2093
+ ).find(({ id }) => id === "storage");
2094
+ if (!page) throw new Error("Storage page is not registered");
2095
+ const document = new FakeDocument();
2096
+ const content = document.createElement("main");
2097
+ const scope = new RendererSettingsPageScope();
2098
+ page.mount({
2099
+ content: content as unknown as HTMLElement,
2100
+ signal: scope.signal,
2101
+ runLatest: (operation, handlers) => scope.runLatest(operation, handlers),
2102
+ });
2103
+
2104
+ await vi.waitFor(() => expect(visibleText(content)).toContain("12 个任务"));
2105
+ expect(visibleText(content)).toContain("Schema v3");
2106
+ expect(visibleText(content)).toContain("2.0 KB");
2107
+ expect(visibleText(content)).toContain("5.0 MB");
2108
+ const optimize = descendants(content).find((element) => element.className === "settings-command-button");
2109
+ if (!optimize) throw new Error("Optimize storage action is not rendered");
2110
+ optimize.dispatch("click");
2111
+ await vi.waitFor(() => expect(client.optimizeStorage).toHaveBeenCalledOnce());
2112
+ await vi.waitFor(() => expect(visibleText(content)).toContain("4.0 MB"));
2113
+ scope.dispose();
2114
+ });
2115
+ });
@@ -23,8 +23,10 @@ describe("Renderer settings foundation", () => {
23
23
  expect(pages.map(({ label }) => label)).toEqual([
24
24
  "Connections",
25
25
  "Accounts",
26
- "MCP / Skills",
26
+ "MCP",
27
+ "Skills",
27
28
  "Session Import",
29
+ "Storage & verification",
28
30
  "Updates",
29
31
  "About",
30
32
  ]);
@@ -33,6 +35,8 @@ describe("Renderer settings foundation", () => {
33
35
  "accounts",
34
36
  "connections",
35
37
  "session-import",
38
+ "session-import",
39
+ "storage",
36
40
  "updates",
37
41
  "about",
38
42
  ]);
@@ -65,8 +69,10 @@ describe("Renderer settings foundation", () => {
65
69
  expect(pages.map(({ id }) => id)).toEqual([
66
70
  "connections",
67
71
  "accounts",
68
- "integrations",
72
+ "mcp",
73
+ "skills",
69
74
  "session-import",
75
+ "storage",
70
76
  "updates",
71
77
  "about",
72
78
  ]);