@harness-mix/cli 0.1.10 → 0.1.12

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 (306) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/NOTICE +16 -2
  3. package/README.md +265 -3
  4. package/docs/cross-platform.md +3 -3
  5. package/docs/multi-agent-collaboration.md +37 -23
  6. package/docs/native-acp.md +2 -2
  7. package/docs/native-codex.md +5 -5
  8. package/docs/{superpowers/specs → specs}/2026-09-13-native-hardening-design.md +2 -2
  9. package/docs/storage-verification-design.md +5 -5
  10. package/licenses/lobe-icons-MIT.txt +21 -0
  11. package/output/native-build/desktop-controller.mjs +24 -24
  12. package/output/native-build/harness-mix-secret.exe +0 -0
  13. package/output/native-build/harness-mix-shim.exe +0 -0
  14. package/output/native-build/renderer-extension.js +1325 -658
  15. package/package.json +8 -8
  16. package/scripts/adapters-test.cjs +9 -1
  17. package/scripts/antigravity-adapter-test.cjs +381 -284
  18. package/scripts/build-native.cjs +64 -46
  19. package/scripts/codex-accounts-test.cjs +1 -1
  20. package/scripts/codex-accounts-ui-smoke.cjs +1 -1
  21. package/scripts/collaboration-recovery-test.cjs +11 -2
  22. package/scripts/collaboration-test.cjs +114 -27
  23. package/scripts/collaboration-ui-smoke.cjs +45 -8
  24. package/scripts/concurrency-layers-test.cjs +5 -0
  25. package/scripts/diagnostics.cjs +1 -1
  26. package/scripts/e2e-native-host.cjs +13 -13
  27. package/scripts/error-kind-test.cjs +3 -3
  28. package/scripts/integrations-test.cjs +2 -2
  29. package/scripts/integrations-ui-smoke.cjs +6 -6
  30. package/scripts/native-config-test.cjs +6 -6
  31. package/scripts/native-job-object-test.cjs +1 -1
  32. package/scripts/native-protocol-test.cjs +44 -38
  33. package/scripts/native-rollback-test.cjs +1 -1
  34. package/scripts/native-secret-test.cjs +1 -1
  35. package/scripts/native-shim-test.cjs +1 -1
  36. package/scripts/native-sidebar-test.cjs +4 -4
  37. package/scripts/native-ui-smoke.cjs +12 -12
  38. package/scripts/pets-test.cjs +223 -3
  39. package/scripts/platform-test.cjs +6 -6
  40. package/scripts/publish-native-package.cjs +1 -1
  41. package/scripts/test-live-mentions.cjs +2 -2
  42. package/scripts/workspace-test.cjs +4 -2
  43. package/src/assets/icons/PROVENANCE.md +92 -0
  44. package/src/main/adapters/antigravity.js +1415 -1368
  45. package/src/main/adapters/claude.js +1 -1
  46. package/src/main/adapters/codex-app-server.js +1 -1
  47. package/src/main/adapters/dsh.js +1 -1
  48. package/src/main/adapters/index.js +1 -1
  49. package/src/main/adapters/pi-usage.js +2 -2
  50. package/src/main/harness-adapter/error-kind.js +1 -1
  51. package/src/main/harness-adapter/manifest.js +7 -1
  52. package/src/main/host/collaboration-tools.js +8 -1
  53. package/src/main/host/collaboration-worktree.js +27 -1
  54. package/src/main/host/collaboration.js +348 -15
  55. package/src/main/host/runtime.js +23 -15
  56. package/src/main/native/codex-accounts.js +1 -1
  57. package/src/main/native/config.js +9 -9
  58. package/src/main/native/host.js +1 -1
  59. package/src/main/native/launcher.js +10 -8
  60. package/src/main/native/pets.js +188 -49
  61. package/src/main/native/platform.js +19 -9
  62. package/src/main/native/protocol.js +83 -80
  63. package/src/main/native/rs/crates/appx/Cargo.toml +1 -1
  64. package/src/main/native/rs/crates/secret/Cargo.toml +1 -1
  65. package/src/main/native/rs/crates/secret/src/main.rs +3 -3
  66. package/src/main/native/rs/crates/shim/Cargo.toml +1 -1
  67. package/src/main/native/rs/crates/shim/src/main.rs +2 -2
  68. package/src/main/native/thread-list.js +1 -1
  69. package/src/main/shared-contracts/capability.js +3 -1
  70. package/src/main/workspace/core-review.js +4 -3
  71. package/src/native-ui/README.md +2 -2
  72. package/src/native-ui/desktop-control/dist/contract-audit.js +4 -4
  73. package/src/native-ui/desktop-control/dist/index.d.ts +1 -1
  74. package/src/native-ui/desktop-control/dist/index.js +2 -2
  75. package/src/native-ui/desktop-control/dist/index.js.map +1 -1
  76. package/src/native-ui/desktop-control/dist/main-process-title-policy.js +5 -5
  77. package/src/native-ui/desktop-control/dist/main-process-title-policy.js.map +1 -1
  78. package/src/native-ui/desktop-control/dist/production-controller.js +3 -3
  79. package/src/native-ui/desktop-control/dist/production-controller.js.map +1 -1
  80. package/src/native-ui/desktop-control/dist/release-main.js +1 -1
  81. package/src/native-ui/desktop-control/dist/release-main.js.map +1 -1
  82. package/src/native-ui/desktop-control/dist/renderer-cdp-control-session.js +1 -1
  83. package/src/native-ui/desktop-control/dist/renderer-cdp-control-session.js.map +1 -1
  84. package/src/native-ui/desktop-control/dist/renderer-control-session.js +3 -3
  85. package/src/native-ui/desktop-control/dist/renderer-control-session.js.map +1 -1
  86. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-runtime.js +25 -25
  87. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-runtime.js.map +1 -1
  88. package/src/native-ui/desktop-control/dist/tsconfig.tsbuildinfo +1 -1
  89. package/src/native-ui/desktop-control/package.json +2 -2
  90. package/src/native-ui/desktop-control/scripts/build-release.mjs +2 -2
  91. package/src/native-ui/desktop-control/src/contract-audit.ts +4 -4
  92. package/src/native-ui/desktop-control/src/index.ts +2 -2
  93. package/src/native-ui/desktop-control/src/main-process-title-policy.ts +5 -5
  94. package/src/native-ui/desktop-control/src/production-controller.ts +3 -3
  95. package/src/native-ui/desktop-control/src/release-main.ts +1 -1
  96. package/src/native-ui/desktop-control/src/renderer-cdp-control-session.ts +1 -1
  97. package/src/native-ui/desktop-control/src/renderer-control-session.ts +3 -3
  98. package/src/native-ui/desktop-control/src/renderer-draft-prewarm-runtime.ts +25 -25
  99. package/src/native-ui/desktop-control/test/index.test.ts +1 -1
  100. package/src/native-ui/desktop-control/test/production-controller.test.ts +1 -1
  101. package/src/native-ui/desktop-control/test/renderer-cdp-control-session.test.ts +1 -1
  102. package/src/native-ui/desktop-control/test/renderer-draft-prewarm-policy.test.ts +43 -43
  103. package/src/native-ui/renderer-extension/dist/types/agent-group-preference.d.ts +1 -1
  104. package/src/native-ui/renderer-extension/dist/types/agent-group-preference.d.ts.map +1 -1
  105. package/src/native-ui/renderer-extension/dist/types/agent-selection-state.d.ts +1 -1
  106. package/src/native-ui/renderer-extension/dist/types/agent-selection-state.d.ts.map +1 -1
  107. package/src/native-ui/renderer-extension/dist/types/contract-audit.d.ts +1 -1
  108. package/src/native-ui/renderer-extension/dist/types/contract-audit.d.ts.map +1 -1
  109. package/src/native-ui/renderer-extension/dist/types/index.d.ts +3 -1
  110. package/src/native-ui/renderer-extension/dist/types/index.d.ts.map +1 -1
  111. package/src/native-ui/renderer-extension/dist/types/production-entry.d.ts +1 -1
  112. package/src/native-ui/renderer-extension/dist/types/production-entry.d.ts.map +1 -1
  113. package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts +2 -2
  114. package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts.map +1 -1
  115. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts +3 -3
  116. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
  117. package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-options.d.ts +1 -1
  118. package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-options.d.ts.map +1 -1
  119. package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-state.d.ts +1 -1
  120. package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-state.d.ts.map +1 -1
  121. package/src/native-ui/renderer-extension/dist/types/renderer-collab-cards.d.ts +1 -1
  122. package/src/native-ui/renderer-extension/dist/types/renderer-collab-cards.d.ts.map +1 -1
  123. package/src/native-ui/renderer-extension/dist/types/renderer-composer-dom.d.ts +1 -1
  124. package/src/native-ui/renderer-extension/dist/types/renderer-composer-dom.d.ts.map +1 -1
  125. package/src/native-ui/renderer-extension/dist/types/renderer-credits-control.d.ts +1 -1
  126. package/src/native-ui/renderer-extension/dist/types/renderer-credits-control.d.ts.map +1 -1
  127. package/src/native-ui/renderer-extension/dist/types/renderer-fork-control.d.ts +1 -1
  128. package/src/native-ui/renderer-extension/dist/types/renderer-fork-control.d.ts.map +1 -1
  129. package/src/native-ui/renderer-extension/dist/types/renderer-harness-command-claim.d.ts +1 -1
  130. package/src/native-ui/renderer-extension/dist/types/renderer-harness-command-claim.d.ts.map +1 -1
  131. package/src/native-ui/renderer-extension/dist/types/renderer-harness-command-control.d.ts +1 -1
  132. package/src/native-ui/renderer-extension/dist/types/renderer-harness-command-control.d.ts.map +1 -1
  133. package/src/native-ui/renderer-extension/dist/types/renderer-harness-handoff.d.ts +1 -1
  134. package/src/native-ui/renderer-extension/dist/types/renderer-harness-handoff.d.ts.map +1 -1
  135. package/src/native-ui/renderer-extension/dist/types/renderer-harness-localization.d.ts +1 -1
  136. package/src/native-ui/renderer-extension/dist/types/renderer-harness-localization.d.ts.map +1 -1
  137. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts +1 -0
  138. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts.map +1 -1
  139. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +36 -31
  140. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -1
  141. package/src/native-ui/renderer-extension/dist/types/renderer-model-picker.d.ts +1 -1
  142. package/src/native-ui/renderer-extension/dist/types/renderer-model-picker.d.ts.map +1 -1
  143. package/src/native-ui/renderer-extension/dist/types/renderer-new-thread-preference.d.ts +2 -2
  144. package/src/native-ui/renderer-extension/dist/types/renderer-new-thread-preference.d.ts.map +1 -1
  145. package/src/native-ui/renderer-extension/dist/types/renderer-permission-mode-picker.d.ts +1 -1
  146. package/src/native-ui/renderer-extension/dist/types/renderer-permission-mode-picker.d.ts.map +1 -1
  147. package/src/native-ui/renderer-extension/dist/types/renderer-permission-mode-preference.d.ts +2 -2
  148. package/src/native-ui/renderer-extension/dist/types/renderer-permission-mode-preference.d.ts.map +1 -1
  149. package/src/native-ui/renderer-extension/dist/types/renderer-request-sender.d.ts +18 -2
  150. package/src/native-ui/renderer-extension/dist/types/renderer-request-sender.d.ts.map +1 -1
  151. package/src/native-ui/renderer-extension/dist/types/renderer-session-import-client.d.ts +1 -1
  152. package/src/native-ui/renderer-extension/dist/types/renderer-session-import-client.d.ts.map +1 -1
  153. package/src/native-ui/renderer-extension/dist/types/renderer-sidebar-agent-icons.d.ts +2 -2
  154. package/src/native-ui/renderer-extension/dist/types/renderer-sidebar-agent-icons.d.ts.map +1 -1
  155. package/src/native-ui/renderer-extension/dist/types/renderer-team-cards.d.ts +58 -0
  156. package/src/native-ui/renderer-extension/dist/types/renderer-team-cards.d.ts.map +1 -0
  157. package/src/native-ui/renderer-extension/dist/types/renderer-transcript-dom.d.ts +1 -1
  158. package/src/native-ui/renderer-extension/dist/types/renderer-trigger-chip-style.d.ts +1 -1
  159. package/src/native-ui/renderer-extension/dist/types/renderer-trigger-chip-style.d.ts.map +1 -1
  160. package/src/native-ui/renderer-extension/dist/types/renderer-usage-control.d.ts +1 -1
  161. package/src/native-ui/renderer-extension/dist/types/renderer-usage-control.d.ts.map +1 -1
  162. package/src/native-ui/renderer-extension/dist/types/settings/accounts-list.d.ts +1 -1
  163. package/src/native-ui/renderer-extension/dist/types/settings/accounts-list.d.ts.map +1 -1
  164. package/src/native-ui/renderer-extension/dist/types/settings/accounts-page.d.ts +1 -1
  165. package/src/native-ui/renderer-extension/dist/types/settings/accounts-page.d.ts.map +1 -1
  166. package/src/native-ui/renderer-extension/dist/types/settings/accounts-usage.d.ts +1 -1
  167. package/src/native-ui/renderer-extension/dist/types/settings/accounts-usage.d.ts.map +1 -1
  168. package/src/native-ui/renderer-extension/dist/types/settings/connections-page.d.ts +3 -3
  169. package/src/native-ui/renderer-extension/dist/types/settings/connections-page.d.ts.map +1 -1
  170. package/src/native-ui/renderer-extension/dist/types/settings/harness-accounts.d.ts +1 -1
  171. package/src/native-ui/renderer-extension/dist/types/settings/harness-accounts.d.ts.map +1 -1
  172. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +4 -4
  173. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
  174. package/src/native-ui/renderer-extension/dist/types/settings/pet-market.d.ts.map +1 -1
  175. package/src/native-ui/renderer-extension/dist/types/settings/session-import-list-controls.d.ts +1 -1
  176. package/src/native-ui/renderer-extension/dist/types/settings/session-import-list-controls.d.ts.map +1 -1
  177. package/src/native-ui/renderer-extension/dist/types/settings/session-import-page.d.ts +1 -1
  178. package/src/native-ui/renderer-extension/dist/types/settings/session-import-page.d.ts.map +1 -1
  179. package/src/native-ui/renderer-extension/dist/types/settings/shell.d.ts +2 -2
  180. package/src/native-ui/renderer-extension/dist/types/settings/shell.d.ts.map +1 -1
  181. package/src/native-ui/renderer-extension/dist/types/settings/trigger.d.ts +1 -1
  182. package/src/native-ui/renderer-extension/dist/types/settings/trigger.d.ts.map +1 -1
  183. package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
  184. package/src/native-ui/renderer-extension/dist/types/versioned-renderer-adapter.d.ts +18 -18
  185. package/src/native-ui/renderer-extension/dist/types/versioned-renderer-adapter.d.ts.map +1 -1
  186. package/src/native-ui/renderer-extension/package.json +2 -2
  187. package/src/native-ui/renderer-extension/src/agent-group-preference.ts +1 -1
  188. package/src/native-ui/renderer-extension/src/agent-selection-state.ts +31 -16
  189. package/src/native-ui/renderer-extension/src/audit-entry.ts +1 -1
  190. package/src/native-ui/renderer-extension/src/contract-audit.ts +4 -4
  191. package/src/native-ui/renderer-extension/src/index.ts +4 -2
  192. package/src/native-ui/renderer-extension/src/install-renderer-binding.ts +2 -2
  193. package/src/native-ui/renderer-extension/src/production-entry.ts +3 -3
  194. package/src/native-ui/renderer-extension/src/renderer-agent-picker.ts +7 -7
  195. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +112 -89
  196. package/src/native-ui/renderer-extension/src/renderer-codex-account-options.ts +1 -1
  197. package/src/native-ui/renderer-extension/src/renderer-codex-account-state.ts +1 -1
  198. package/src/native-ui/renderer-extension/src/renderer-collab-cards.ts +1 -1
  199. package/src/native-ui/renderer-extension/src/renderer-composer-dom.ts +12 -12
  200. package/src/native-ui/renderer-extension/src/renderer-credits-control.ts +7 -7
  201. package/src/native-ui/renderer-extension/src/renderer-external-steering.ts +3 -3
  202. package/src/native-ui/renderer-extension/src/renderer-fork-control.ts +1 -1
  203. package/src/native-ui/renderer-extension/src/renderer-harness-command-claim.ts +1 -1
  204. package/src/native-ui/renderer-extension/src/renderer-harness-command-control.ts +3 -3
  205. package/src/native-ui/renderer-extension/src/renderer-harness-handoff.ts +1 -1
  206. package/src/native-ui/renderer-extension/src/renderer-harness-localization.ts +1 -1
  207. package/src/native-ui/renderer-extension/src/renderer-harness-mentions.ts +4 -0
  208. package/src/native-ui/renderer-extension/src/renderer-integrations-client.ts +5 -5
  209. package/src/native-ui/renderer-extension/src/renderer-model-client.ts +63 -42
  210. package/src/native-ui/renderer-extension/src/renderer-model-picker.ts +16 -16
  211. package/src/native-ui/renderer-extension/src/renderer-new-thread-preference.ts +2 -2
  212. package/src/native-ui/renderer-extension/src/renderer-permission-mode-picker.ts +3 -3
  213. package/src/native-ui/renderer-extension/src/renderer-permission-mode-preference.ts +2 -2
  214. package/src/native-ui/renderer-extension/src/renderer-request-sender.ts +46 -3
  215. package/src/native-ui/renderer-extension/src/renderer-session-import-client.ts +4 -4
  216. package/src/native-ui/renderer-extension/src/renderer-sidebar-agent-icons.ts +24 -20
  217. package/src/native-ui/renderer-extension/src/renderer-team-cards.ts +312 -0
  218. package/src/native-ui/renderer-extension/src/renderer-transcript-dom.ts +1 -1
  219. package/src/native-ui/renderer-extension/src/renderer-trigger-chip-style.ts +2 -2
  220. package/src/native-ui/renderer-extension/src/renderer-usage-control.ts +4 -4
  221. package/src/native-ui/renderer-extension/src/settings/accounts-list.ts +1 -1
  222. package/src/native-ui/renderer-extension/src/settings/accounts-page.ts +1 -1
  223. package/src/native-ui/renderer-extension/src/settings/accounts-usage.ts +1 -1
  224. package/src/native-ui/renderer-extension/src/settings/connections-page.ts +7 -7
  225. package/src/native-ui/renderer-extension/src/settings/harness-accounts.ts +1 -1
  226. package/src/native-ui/renderer-extension/src/settings/icons.ts +2 -2
  227. package/src/native-ui/renderer-extension/src/settings/pages.ts +11 -11
  228. package/src/native-ui/renderer-extension/src/settings/pet-market.css +33 -0
  229. package/src/native-ui/renderer-extension/src/settings/pet-market.ts +173 -91
  230. package/src/native-ui/renderer-extension/src/settings/pets-client.ts +5 -5
  231. package/src/native-ui/renderer-extension/src/settings/release-notes.ts +1 -1
  232. package/src/native-ui/renderer-extension/src/settings/session-import-list-controls.ts +1 -1
  233. package/src/native-ui/renderer-extension/src/settings/session-import-page.ts +1 -1
  234. package/src/native-ui/renderer-extension/src/settings/shell.css +15 -15
  235. package/src/native-ui/renderer-extension/src/settings/shell.ts +11 -11
  236. package/src/native-ui/renderer-extension/src/settings/skin-runtime.ts +17 -17
  237. package/src/native-ui/renderer-extension/src/settings/trigger.ts +1 -1
  238. package/src/native-ui/renderer-extension/src/versioned-renderer-adapter.ts +20 -20
  239. package/src/native-ui/renderer-extension/test/agent-selection-state.test.ts +60 -1
  240. package/src/native-ui/renderer-extension/test/codebuddy-migration.test.ts +1 -1
  241. package/src/native-ui/renderer-extension/test/renderer-agent-picker.test.ts +1 -1
  242. package/src/native-ui/renderer-extension/test/renderer-binding-probe-host-catalog.test.ts +15 -9
  243. package/src/native-ui/renderer-extension/test/renderer-binding-probe.test.ts +15 -15
  244. package/src/native-ui/renderer-extension/test/renderer-codex-account-state.test.ts +1 -1
  245. package/src/native-ui/renderer-extension/test/renderer-composer-permissions.test.ts +4 -4
  246. package/src/native-ui/renderer-extension/test/renderer-external-steering-rpc.test.ts +2 -2
  247. package/src/native-ui/renderer-extension/test/renderer-external-steering.test.ts +3 -3
  248. package/src/native-ui/renderer-extension/test/renderer-fork-control.test.ts +4 -4
  249. package/src/native-ui/renderer-extension/test/renderer-harness-command-claim.test.ts +1 -1
  250. package/src/native-ui/renderer-extension/test/renderer-harness-command-control.test.ts +1 -1
  251. package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +12 -11
  252. package/src/native-ui/renderer-extension/test/renderer-model-picker.test.ts +1 -1
  253. package/src/native-ui/renderer-extension/test/renderer-native-thread-ownership.test.ts +6 -6
  254. package/src/native-ui/renderer-extension/test/renderer-new-thread-preference.test.ts +1 -1
  255. package/src/native-ui/renderer-extension/test/renderer-permission-mode-picker.test.ts +1 -1
  256. package/src/native-ui/renderer-extension/test/renderer-permission-mode-preference.test.ts +1 -1
  257. package/src/native-ui/renderer-extension/test/renderer-request-timeout.test.ts +80 -0
  258. package/src/native-ui/renderer-extension/test/renderer-settings-lifecycle.test.ts +2 -2
  259. package/src/native-ui/renderer-extension/test/renderer-sidebar-agent-icons.test.ts +10 -3
  260. package/src/native-ui/renderer-extension/test/renderer-unsupported-methods.test.ts +1 -1
  261. package/src/native-ui/renderer-extension/test/settings/pages.test.ts +5 -5
  262. package/src/native-ui/renderer-extension/test/settings/release-notes.test.ts +9 -9
  263. package/src/native-ui/renderer-extension/test/versioned-renderer-adapter.test.ts +3 -3
  264. package/src/native-ui/shared-contracts/dist/errors.d.ts +2 -2
  265. package/src/native-ui/shared-contracts/dist/errors.d.ts.map +1 -1
  266. package/src/native-ui/shared-contracts/dist/errors.js +1 -1
  267. package/src/native-ui/shared-contracts/dist/errors.js.map +1 -1
  268. package/src/native-ui/shared-contracts/dist/harness-models.d.ts +40 -0
  269. package/src/native-ui/shared-contracts/dist/harness-models.d.ts.map +1 -1
  270. package/src/native-ui/shared-contracts/dist/harness-models.js +23 -2
  271. package/src/native-ui/shared-contracts/dist/harness-models.js.map +1 -1
  272. package/src/native-ui/shared-contracts/dist/harness-route.d.ts +1 -1
  273. package/src/native-ui/shared-contracts/dist/harness-route.d.ts.map +1 -1
  274. package/src/native-ui/shared-contracts/dist/harness-route.js +1 -1
  275. package/src/native-ui/shared-contracts/dist/harness-route.js.map +1 -1
  276. package/src/native-ui/shared-contracts/dist/harness-session-import.d.ts +1 -1
  277. package/src/native-ui/shared-contracts/dist/harness-session-import.d.ts.map +1 -1
  278. package/src/native-ui/shared-contracts/dist/harness-session-import.js +1 -1
  279. package/src/native-ui/shared-contracts/dist/harness-session-import.js.map +1 -1
  280. package/src/native-ui/shared-contracts/dist/index.d.ts +3 -3
  281. package/src/native-ui/shared-contracts/dist/index.d.ts.map +1 -1
  282. package/src/native-ui/shared-contracts/dist/index.js +2 -2
  283. package/src/native-ui/shared-contracts/dist/index.js.map +1 -1
  284. package/src/native-ui/shared-contracts/dist/tsconfig.tsbuildinfo +1 -1
  285. package/src/native-ui/shared-contracts/dist/updates.js +1 -1
  286. package/src/native-ui/shared-contracts/dist/updates.js.map +1 -1
  287. package/src/native-ui/shared-contracts/package.json +1 -1
  288. package/src/native-ui/shared-contracts/src/errors.ts +3 -3
  289. package/src/native-ui/shared-contracts/src/harness-models.ts +23 -2
  290. package/src/native-ui/shared-contracts/src/harness-route.ts +1 -1
  291. package/src/native-ui/shared-contracts/src/harness-session-import.ts +1 -1
  292. package/src/native-ui/shared-contracts/src/index.ts +3 -3
  293. package/src/native-ui/shared-contracts/src/updates.ts +1 -1
  294. package/src/native-ui/shared-contracts/test/browser-bundle.test.ts +3 -3
  295. package/src/native-ui/shared-contracts/test/deepseek-modern-sessions.test.ts +1 -1
  296. package/src/native-ui/shared-contracts/test/errors.test.ts +9 -9
  297. package/src/native-ui/shared-contracts/test/harness-commands.test.ts +1 -1
  298. package/src/native-ui/shared-contracts/test/harness-models.test.ts +3 -3
  299. package/src/native-ui/shared-contracts/test/harness-permission-modes.test.ts +1 -1
  300. package/src/native-ui/shared-contracts/test/harness-plugins.test.ts +1 -1
  301. package/src/native-ui/shared-contracts/test/harness-session-import.test.ts +1 -1
  302. package/src/native-ui/shared-contracts/test/index.test.ts +3 -3
  303. package/src/native-ui/shared-contracts/test/json-rpc.test.ts +1 -1
  304. package/src/native-ui/shared-contracts/test/thread-usage.test.ts +1 -1
  305. package/src/native-ui/shared-contracts/test/updates.test.ts +4 -4
  306. /package/licenses/{codex-host-MIT.txt → bytepioneer-MIT.txt} +0 -0
@@ -1,56 +1,74 @@
1
- const fs = require('node:fs');
2
- const path = require('node:path');
3
- const { execFileSync } = require('node:child_process');
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const { execFileSync } = require('node:child_process');
4
4
  const esbuild = require('esbuild');
5
5
  const os = require('node:os');
6
6
  const { executableName } = require('../src/main/native/platform');
7
- const { getAllIconsDictionary, MODEL_FAMILIES } = require('../src/main/native/icons');
8
- const root = path.resolve(__dirname, '..');
9
- const out = path.join(root, 'output/native-build');
10
-
11
- function cargoCommand() {
7
+ const { getAllIconsDictionary, MODEL_FAMILIES } = require('../src/main/native/icons');
8
+ const root = path.resolve(__dirname, '..');
9
+ const out = path.join(root, 'output/native-build');
10
+
11
+ function cargoCommand() {
12
12
  const candidates = ['cargo', path.join(os.homedir(), '.cargo/bin', executableName('cargo'))];
13
- for (const candidate of candidates) {
14
- try {
15
- execFileSync(candidate, ['--version'], { stdio: 'ignore', windowsHide: true });
16
- return candidate;
17
- } catch { /* try the next candidate */ }
18
- }
19
- throw new Error('Rust toolchain (cargo) not found; install via https://rustup.rs (use the x86_64-pc-windows-gnu host when Visual Studio Build Tools are absent)');
20
- }
21
-
22
- async function main() {
23
- fs.mkdirSync(out, { recursive: true });
24
- const alias = { '@codexhost/shared-contracts': path.join(root, 'src/native-ui/shared-contracts/src/index.ts') };
25
- const common = { bundle: true, alias, logLevel: 'warning', target: 'es2024' };
26
- const icons = getAllIconsDictionary();
27
- icons.harnesses['claude-code'] = icons.harnesses.claude;
28
- icons.harnesses['deepseek-harness'] = icons.harnesses.dsh;
29
- await esbuild.build({ ...common, entryPoints: ['src/native-ui/renderer-extension/src/production-entry.ts'],
30
- platform: 'browser', format: 'iife', outfile: path.join(out, 'renderer-extension.js'),
13
+ for (const candidate of candidates) {
14
+ try {
15
+ execFileSync(candidate, ['--version'], { stdio: 'ignore', windowsHide: true });
16
+ return candidate;
17
+ } catch { /* try the next candidate */ }
18
+ }
19
+ throw new Error('Rust toolchain (cargo) not found; install via https://rustup.rs (use the x86_64-pc-windows-gnu host when Visual Studio Build Tools are absent)');
20
+ }
21
+
22
+ // A byte comparison is the only honest way to tell "the running executable is
23
+ // already current" apart from "the running executable is stale".
24
+ function sameContents(left, right) {
25
+ try {
26
+ if (fs.statSync(left).size !== fs.statSync(right).size) return false;
27
+ return fs.readFileSync(left).equals(fs.readFileSync(right));
28
+ } catch { return false; }
29
+ }
30
+
31
+ async function main() {
32
+ fs.mkdirSync(out, { recursive: true });
33
+ const alias = { '@harnessmix/shared-contracts': path.join(root, 'src/native-ui/shared-contracts/src/index.ts') };
34
+ const common = { bundle: true, alias, logLevel: 'warning', target: 'es2024' };
35
+ const icons = getAllIconsDictionary();
36
+ icons.harnesses['claude-code'] = icons.harnesses.claude;
37
+ icons.harnesses['deepseek-harness'] = icons.harnesses.dsh;
38
+ await esbuild.build({ ...common, entryPoints: ['src/native-ui/renderer-extension/src/production-entry.ts'],
39
+ platform: 'browser', format: 'iife', outfile: path.join(out, 'renderer-extension.js'),
31
40
  loader: { '.svg': 'dataurl', '.png': 'dataurl', '.webp': 'dataurl', '.css': 'text' },
32
- banner: { js: `globalThis.__HARNESS_MIX_ICONS__=${JSON.stringify(icons)};globalThis.__HARNESS_MIX_MODEL_FAMILIES__=${JSON.stringify(MODEL_FAMILIES.map(f => ({ id: f.id, pattern: f.regex.source })))};` } });
33
- await esbuild.build({ ...common, entryPoints: ['src/native-ui/desktop-control/src/release-main.ts'],
34
- platform: 'node', format: 'esm', outfile: path.join(out, 'desktop-controller.mjs') });
41
+ banner: { js: `globalThis.__HARNESS_MIX_ICONS__=${JSON.stringify(icons)};globalThis.__HARNESS_MIX_MODEL_FAMILIES__=${JSON.stringify(MODEL_FAMILIES.map(f => ({ id: f.id, pattern: f.regex.source })))};` } });
42
+ await esbuild.build({ ...common, entryPoints: ['src/native-ui/desktop-control/src/release-main.ts'],
43
+ platform: 'node', format: 'esm', outfile: path.join(out, 'desktop-controller.mjs') });
35
44
  execFileSync(cargoCommand(), ['build', '--release', '--manifest-path', path.join(root, 'src/main/native/rs/Cargo.toml'),
36
45
  ...(process.platform === 'win32' ? [] : ['--package', 'harness-mix-shim'])], { stdio: 'inherit', windowsHide: true });
37
- const target = path.join(root, 'src/main/native/rs/target/release');
46
+ const target = path.join(root, 'src/main/native/rs/target/release');
38
47
  for (const exe of process.platform === 'win32' ? ['harness-mix-shim.exe', 'harness-mix-appx.exe', 'harness-mix-secret.exe'] : ['harness-mix-shim']) {
39
- const source = path.join(target, exe);
40
- const destination = path.join(out, exe);
41
- // An unchanged executable may be running while UI-only fixes are built.
42
- try {
48
+ const source = path.join(target, exe);
49
+ const destination = path.join(out, exe);
50
+ try {
43
51
  fs.copyFileSync(source, destination);
44
52
  if (process.platform !== 'win32') fs.chmodSync(destination, 0o755);
45
- } catch (err) {
46
- if (err && err.code === 'EBUSY') {
47
- console.warn(`[build:native] Notice: ${exe} is in use, keeping existing binary.`);
48
- } else {
49
- throw err;
50
- }
51
- }
52
- }
53
- fs.writeFileSync(path.join(out, 'node-path.txt'), process.execPath);
54
- console.log('Harness Mix local Renderer, Desktop Controller and Shim built.');
55
- }
56
- main().catch(error => { console.error(error); process.exitCode = 1; });
53
+ } catch (err) {
54
+ if (err && err.code === 'EBUSY') {
55
+ // A running Codex Desktop holds the deployed binary. Keeping it is only
56
+ // safe when it is byte-identical: an unchanged executable may be running
57
+ // while UI-only fixes are built, but a changed one would leave the Shim
58
+ // and the kernel speaking different environments.
59
+ if (sameContents(source, destination)) {
60
+ console.warn(`[build:native] Notice: ${exe} is in use, keeping the identical deployed binary.`);
61
+ } else {
62
+ throw new Error(`[build:native] ${exe} is locked by a running Codex Desktop and its contents changed. ` +
63
+ 'Close Codex Desktop (or run npm start from an external terminal) and build again, ' +
64
+ 'otherwise the deployed Shim keeps speaking the previous protocol.');
65
+ }
66
+ } else {
67
+ throw err;
68
+ }
69
+ }
70
+ }
71
+ fs.writeFileSync(path.join(out, 'node-path.txt'), process.execPath);
72
+ console.log('Harness Mix local Renderer, Desktop Controller and Shim built.');
73
+ }
74
+ main().catch(error => { console.error(error); process.exitCode = 1; });
@@ -64,7 +64,7 @@ async function main() {
64
64
  const login = await manager.startLogin(created.account.accountId);
65
65
  assert.equal(login.userCode, 'WXYZ-1234');
66
66
  server.notify({ method: 'account/login/completed', params: { loginId: login.loginId, success: true, error: null } });
67
- assert.deepEqual(emitted.at(-1), { method: 'codexhost/account/login/completed', params: { accountId: created.account.accountId, loginId: login.loginId, success: true, error: null } });
67
+ assert.deepEqual(emitted.at(-1), { method: 'harnessmix/account/login/completed', params: { accountId: created.account.accountId, loginId: login.loginId, success: true, error: null } });
68
68
 
69
69
  const registry = fs.readFileSync(path.join(root, 'codex-accounts', 'accounts.json'), 'utf8');
70
70
  assert.equal(registry.includes('owner@example.com'), false, 'Registry must not persist native identity or credentials');
@@ -15,7 +15,7 @@ app.whenReady().then(async () => {
15
15
  resolveDir: process.cwd(),
16
16
  },
17
17
  bundle: true,
18
- alias: { '@codexhost/shared-contracts': path.resolve('src/native-ui/shared-contracts/src/index.ts') },
18
+ alias: { '@harnessmix/shared-contracts': path.resolve('src/native-ui/shared-contracts/src/index.ts') },
19
19
  platform: 'browser',
20
20
  format: 'iife',
21
21
  globalName: 'AccountUI',
@@ -59,14 +59,18 @@ async function main() {
59
59
 
60
60
  const rt = new HostRuntime({ dataDirectory: path.join(root, 'data') }); await rt.store.load();
61
61
  await rt.collaboration.initialize();
62
- rt.collaboration.jobs.set('saved', { id: 'saved', owner: 'parent', agent: 'pi', childId: 'child', status: 'running', task: 'continue', workspace });
62
+ rt.collaboration.jobs.set('saved', { id: 'saved', owner: 'parent', agent: 'pi', childId: 'child', status: 'running', task: 'continue', workspace, teamId: 'team', memberId: 'member', teamTaskId: 'team-task' });
63
63
  rt.collaboration.jobs.set('finished', { id: 'finished', owner: 'parent', agent: 'pi', childId: 'done', status: 'completed', result: 'kept' });
64
+ rt.collaboration.teams.set('team', { id: 'team', owner: 'parent', name: 'Recovery team', goal: 'Resume safely', status: 'active', members: [{ id: 'member', name: 'Builder', role: 'Continue work', agent: 'pi', status: 'working', childId: 'child' }], tasks: [{ id: 'team-task', title: 'Continue', description: 'Resume after restart', assignee: 'member', dependsOn: [], status: 'in_progress' }], messages: [], updatedAt: Date.now() });
64
65
  await rt.collaboration.save();
66
+ await rt.collaboration.saveTeams();
65
67
  const restarted = new Collaboration(rt); await restarted.initialize();
66
68
  assert.equal(restarted.jobs.get('saved').status, 'interrupted');
67
69
  assert.equal(restarted.jobs.get('saved').childId, 'child');
68
70
  assert.equal(restarted.jobs.get('saved').workspace.cwd, workspace.cwd);
69
71
  assert.equal(restarted.jobs.get('finished').result, 'kept');
72
+ assert.equal(restarted.teams.get('team').members[0].status, 'interrupted');
73
+ assert.equal(restarted.teams.get('team').tasks[0].status, 'interrupted');
70
74
  assert.equal(restarted.keys.size, 0, 'No session authorization keys persisted');
71
75
  // Resume must reuse the durable child identity, rather than spawn a replacement.
72
76
  const parent = { id: 'parent', cwd: repo, title: 'Lead' }, child = { id: 'child', parentThreadId: 'parent', cwd: workspace.cwd };
@@ -78,10 +82,15 @@ async function main() {
78
82
  async createThread() { throw new Error('Duplicate child created'); }, async cancel() {} };
79
83
  restarted.runtime = fake;
80
84
  await restarted.call('parent', 'resume_delegation', { task_id: 'saved' });
85
+ assert.equal(restarted.teams.get('team').members[0].status, 'working');
86
+ assert.equal(restarted.teams.get('team').tasks[0].status, 'in_progress');
81
87
  await restarted.jobs.get('saved').done;
82
88
  assert.match(received, /do not repeat completed side effects/);
83
89
  assert.equal(restarted.jobs.get('saved').result, 'resumed');
84
90
  assert.equal(restarted.jobs.get('saved').childId, 'child');
91
+ assert.equal(restarted.teams.get('team').members[0].status, 'ready');
92
+ assert.equal(restarted.teams.get('team').tasks[0].status, 'completed');
93
+ assert.equal(restarted.teams.get('team').status, 'completed');
85
94
  const nativeReview = await restarted.call('parent', 'review_delegation_changes', { task_id: 'saved' });
86
95
  assert.equal(nativeReview.digest, review.digest); assert.match(nativeReview.patch, /worker/);
87
96
  rt.collaboration.jobs.clear();
@@ -92,7 +101,7 @@ async function main() {
92
101
  async readNative() { calls.push('read'); return [{ id: 'u', role: 'user', text: 'old question', at: 1 }, { id: 'a', role: 'assistant', text: 'old answer', at: 2 }]; },
93
102
  });
94
103
  const protocol = new NativeProtocol(rt, () => {});
95
- const rows = await protocol.request('codexhost/harness/session-import/list', { harnessId: 'all-harnesses', query: 'Native' });
104
+ const rows = await protocol.request('harnessmix/harness/session-import/list', { harnessId: 'all-harnesses', query: 'Native' });
96
105
  assert.equal(rows.total, 1);
97
106
  const params = { harnessId: 'all-harnesses', nativeSessionId: rows.candidates[0].nativeSessionId };
98
107
  const context = await rt.history.context(params);
@@ -2,7 +2,7 @@ const assert = require('node:assert/strict');
2
2
  const fs = require('node:fs/promises');
3
3
  const path = require('node:path');
4
4
  const { HostRuntime } = require('../src/main/host/runtime');
5
- const { mentionedAgents } = require('../src/main/host/collaboration');
5
+ const { mentionedAgents, teamTaskDepths, teamPhase, teamProgress } = require('../src/main/host/collaboration');
6
6
  const { JsonlProcess } = require('../src/main/host/jsonl');
7
7
  const wait = async fn => { for (let i = 0; i < 300; i++) { if (fn()) return; await new Promise(r => setTimeout(r, 10)); } throw new Error('Timed out'); };
8
8
 
@@ -10,42 +10,61 @@ async function main() {
10
10
  const root = await fs.mkdtemp(path.resolve('output/collaboration-'));
11
11
  const rt = new HostRuntime({ dataDirectory: path.join(root, 'data') });
12
12
  await rt.store.load();
13
- let connection, active = 0, maximum = 0;
13
+ let connection, teamConnection, active = 0, maximum = 0;
14
14
  const pending = new Map();
15
15
  let leadPrompt = '';
16
16
  const lead = { manifest: { id: 'lead', name: 'Lead', capabilities: { collaborationTools: true } },
17
17
  async open(input) { connection = input.collaboration; return { emit: input.emit, collaborationEnabled: true }; },
18
18
  async send(s, text) { leadPrompt = text; }, async cancel() {}, async close() {} };
19
- const worker = { manifest: { id: 'worker', name: 'Worker', aliases: ['w'], capabilities: {} },
20
- async open(input) { assert.equal(input.collaboration, undefined); return { id: input.thread.id, emit: input.emit }; },
19
+ const worker = { manifest: { id: 'worker', name: 'Worker', aliases: ['w'], capabilities: { collaborationTools: true } },
20
+ async open(input) {
21
+ if (input.collaboration) teamConnection = input.collaboration;
22
+ else assert.equal(input.collaboration, undefined);
23
+ return { id: input.thread.id, emit: input.emit, collaborationEnabled: !!input.collaboration };
24
+ },
21
25
  async send(s, text) { active++; maximum = Math.max(maximum, active); pending.set(s.id, { s, text }); },
22
26
  async cancel(s) { if (pending.delete(s.id)) active--; }, async close() {} };
27
+ const reviewer = { ...worker, manifest: { id: 'reviewer', name: 'Reviewer', capabilities: { collaborationTools: true } } };
23
28
  rt.adapters.set('lead', lead); rt.status.lead = { available: true };
24
29
  rt.adapters.set('worker', worker); rt.status.worker = { available: true };
25
- const parent = await rt.createThread({ harnessId: 'lead', cwd: root });
26
- rt.collaboration.jobs.set('interrupted-fixture', { id: 'interrupted-fixture', owner: parent.id, agent: 'worker', childId: 'old-child', status: 'interrupted' });
30
+ rt.adapters.set('reviewer', reviewer); rt.status.reviewer = { available: true };
31
+ const parent = await rt.createThread({ harnessId: 'lead', cwd: root });
32
+ rt.collaboration.jobs.set('interrupted-fixture', { id: 'interrupted-fixture', owner: parent.id, agent: 'worker', childId: 'old-child', status: 'interrupted' });
27
33
  const ownerConnection = connection;
28
34
  const transport = new JsonlProcess(connection.command, connection.args, { env: { ...process.env, ...connection.env } }, {});
29
35
  const call = (name, args) => rt.collaboration.call(parent.id, name, args);
30
36
  const finish = (id, answer) => { const { s } = pending.get(id); pending.delete(id); active--; s.emit({ kind: 'text-delta', text: answer }); s.emit({ kind: 'completed', finalAnswer: true }); };
31
37
  try {
32
- assert.deepEqual(mentionedAgents('ask #w and [Worker](harness-mix://agent/worker) `#lead` issue#lead #worker/foo', rt), ['worker']);
33
- assert.deepEqual(mentionedAgents('leave @w to native Codex mentions', rt), []);
38
+ assert.deepEqual(mentionedAgents('ask #w and [Worker](harness-mix://agent/worker) `#lead` issue#lead #worker/foo', rt), ['worker']);
39
+ assert.deepEqual(mentionedAgents('leave @w to native Codex mentions', rt), []);
40
+ assert.deepEqual(mentionedAgents('创建 Agent Team:Worker 负责开发,Reviewer 负责审查。', rt), ['worker', 'reviewer'], '纯文本 Harness 名称可在明确团队语境中授权');
41
+ assert.deepEqual(mentionedAgents('讨论 Worker 和 Reviewer 的界面显示', rt), [], '普通产品讨论不得误启动跨 Harness 协作');
42
+ assert.deepEqual(mentionedAgents('组建团队,但不要从 `Worker` 或 ```Reviewer``` 调度', rt), [], '代码区域中的名称不得作为授权');
43
+ assert.deepEqual(mentionedAgents('让 issue-worker 参与团队', rt), [], '较长标识符内的 Harness 子串不得误匹配');
44
+ const depthFixture = [{ id: 'a', dependsOn: [] }, { id: 'b', dependsOn: ['a'] }, { id: 'c', dependsOn: ['b', 'missing'] }];
45
+ assert.equal(teamTaskDepths(depthFixture).get('c'), 2, 'Depth follows the longest dependency chain and ignores unknown ids');
46
+ assert.equal(teamTaskDepths(depthFixture).get('a'), 0);
47
+ const cyclic = teamTaskDepths([{ id: 'x', dependsOn: ['y'] }, { id: 'y', dependsOn: ['x'] }]);
48
+ assert.ok(Number.isFinite(cyclic.get('x')) && Number.isFinite(cyclic.get('y')), 'Cyclic graphs terminate');
49
+ assert.equal(teamPhase({ tasks: [] }), 'forming');
50
+ assert.equal(teamProgress([{ status: 'completed' }, { status: 'blocked' }]).percent, 50);
34
51
  await assert.rejects(call('list_agents', {}), /no longer active/);
35
52
  rt.history.context = async ({ nativeSessionId }) => { assert.equal(nativeSessionId, 'c2Vzc2lvbg'); return { harnessId: 'pi', title: 'Old session', cwd: root, transcript: 'User: prior question\nAssistant: prior answer' }; };
36
- await rt.send(parent.id, '#worker review files #[Old session](harness-mix://session/c2Vzc2lvbg)');
37
- assert.ok(rt.execution.isRunning(parent.id));
38
- assert.match(leadPrompt, /untrusted historical data[\s\S]*prior answer/);
39
- assert.match(leadPrompt, /Recovery checkpoint:[\s\S]*interrupted-fixture \(worker\)[\s\S]*call list_delegations now/);
40
- assert.equal(rt.collaboration.jobs.get('interrupted-fixture').status, 'interrupted', 'Prompt injection never auto-resumes interrupted work');
41
- rt.collaboration.jobs.delete('interrupted-fixture');
53
+ await rt.send(parent.id, '#worker #reviewer review files #[Old session](harness-mix://session/c2Vzc2lvbg)');
54
+ assert.ok(rt.execution.isRunning(parent.id));
55
+ assert.match(leadPrompt, /untrusted historical data[\s\S]*prior answer/);
56
+ assert.match(leadPrompt, /Recovery checkpoint:[\s\S]*interrupted-fixture \(worker\)[\s\S]*call list_delegations now/);
57
+ assert.equal(rt.collaboration.jobs.get('interrupted-fixture').status, 'interrupted', 'Prompt injection never auto-resumes interrupted work');
58
+ rt.collaboration.jobs.delete('interrupted-fixture');
42
59
  assert.ok(!JSON.stringify(rt.core.getItemsForTurn(rt.execution.lastTurn(parent.id).id)).includes('[Harness Mix collaboration]'), 'Routing guidance stays out of displayed user text');
43
60
  const init = await transport.request('initialize', { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'test', version: '1' } });
44
61
  assert.ok(init.capabilities.tools);
45
62
  const catalog = await transport.request('tools/list', {});
46
- assert.equal(catalog.tools.length, 10);
63
+ assert.equal(catalog.tools.length, 15);
47
64
  assert.ok(catalog.tools.some(tool => tool.name === 'review_delegation_changes'));
48
65
  assert.ok(catalog.tools.some(tool => tool.name === 'apply_delegation_changes'));
66
+ assert.ok(catalog.tools.some(tool => tool.name === 'create_agent_team'));
67
+ assert.equal(catalog.tools.find(tool => tool.name === 'create_agent_team').inputSchema.properties.members.maxItems, 6, 'A Team supports six specialist members plus its Lead');
49
68
  await call('update_agent_plan', { steps: [{ text: 'Develop then review', status: 'in_progress' }] });
50
69
  assert.ok(rt.core.getItemsForTurn(rt.execution.lastTurn(parent.id).id).some(item => item.type === 'plan' && item.entries[0].text === 'Develop then review'));
51
70
  const first = JSON.parse((await transport.request('tools/call', { name: 'delegate_to_agent', arguments: { agent_type: 'worker', task: 'one' } })).content[0].text);
@@ -87,22 +106,90 @@ async function main() {
87
106
  await wait(() => pending.size === 1);
88
107
  finish(jobs[0].childId, 'follow-up-result');
89
108
  assert.equal((await call('get_delegation_status', { task_ids: [first.task_id], wait_ms: 3000 }))[0].result, 'follow-up-result');
109
+ const team = await call('create_agent_team', { name: 'Release team', goal: 'Ship a verified change', members: [{ name: 'Builder', role: 'Implement and coordinate', agent_type: 'worker' }, { name: 'Reviewer', role: 'Independently verify', agent_type: 'reviewer' }, { name: 'Frontend', role: 'Integrate the native UI', agent_type: 'worker' }, { name: 'QA', role: 'Run the regression matrix', agent_type: 'reviewer' }, { name: 'Docs', role: 'Document the delivery', agent_type: 'worker' }, { name: 'Release', role: 'Verify Git and final Diff', agent_type: 'reviewer' }] });
110
+ assert.equal(team.lead.agent, 'lead');
111
+ assert.match(team.lead.role, /Lead/);
112
+ assert.equal(team.members.length, 6);
113
+ const teamTask = (await call('assign_team_task', { team_id: team.team_id, title: 'Build', description: 'Implement the change', assignee: team.members[0].id })).task;
114
+ const dependentAssign = await call('assign_team_task', { team_id: team.team_id, title: 'Verify', description: 'Verify the result', assignee: team.members[1].id, depends_on: [teamTask.id] });
115
+ const dependent = dependentAssign.task;
116
+ assert.equal(dependent.status, 'blocked');
117
+ assert.equal(dependentAssign.team.phase, 'waiting', 'Assigned work with nothing in flight reads as waiting');
118
+ assert.deepEqual(dependentAssign.team.progress, { total: 2, pending: 1, blocked: 1, in_progress: 0, completed: 0, failed: 0, interrupted: 0, percent: 0 });
119
+ assert.equal(dependentAssign.team.tasks.find(entry => entry.id === dependent.id).depth, 1, 'Dependent task sits one dependency lane deep');
120
+ assert.equal(dependentAssign.team.tasks.find(entry => entry.id === teamTask.id).depth, 0);
121
+ const teamJob = await call('delegate_to_agent', { agent_type: 'worker', task: 'Implement as Builder', team_id: team.team_id, member_id: team.members[0].id, team_task_id: teamTask.id, isolation: 'shared' });
122
+ await wait(() => pending.size === 1 && teamConnection);
123
+ const teammateTransport = new JsonlProcess(teamConnection.command, teamConnection.args, { env: { ...process.env, ...teamConnection.env } }, {});
124
+ await teammateTransport.request('initialize', { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'teammate-test', version: '1' } });
125
+ const teammateState = JSON.parse((await teammateTransport.request('tools/call', { name: 'get_team_state', arguments: { team_id: team.team_id } })).content[0].text);
126
+ assert.equal(teammateState.members[0].display_status, 'working');
127
+ assert.equal(teammateState.phase, 'running', 'In-flight work reads as running');
128
+ assert.equal(teammateState.progress.in_progress, 1);
129
+ await teammateTransport.request('tools/call', { name: 'send_team_message', arguments: { team_id: team.team_id, to: 'lead', task_id: teamTask.id, kind: 'review-request', message: 'Implementation is ready for verification.' } });
130
+ await assert.rejects(rt.collaboration.call(parent.id, 'send_team_message', { team_id: team.team_id, to: 'lead', message: 'bad kind', kind: 'bogus' }), /invalid_value|Invalid/, 'Message kind is a closed enum');
131
+ const teamChild = rt.collaboration.jobs.get(teamJob.task_id).childId;
132
+ await assert.rejects(rt.collaboration.call(teamChild, 'assign_team_task', { team_id: team.team_id, title: 'Escalate', description: 'Must be lead-owned', assignee: team.members[0].id }), /Only the Team Lead/);
133
+ await assert.rejects(rt.collaboration.call(teamChild, 'delegate_to_agent', { agent_type: 'worker', task: 'recursive' }), /Only lead/);
134
+ finish(teamChild, 'team-result');
135
+ await wait(() => rt.collaboration.jobs.get(teamJob.task_id).status === 'completed');
136
+ const settledTeam = await call('get_team_state', { team_id: team.team_id });
137
+ assert.equal(settledTeam.tasks.find(entry => entry.id === teamTask.id).status, 'completed');
138
+ assert.equal(settledTeam.tasks.find(entry => entry.id === dependent.id).status, 'pending');
139
+ assert.equal(settledTeam.messages[0].fromName, 'Builder');
140
+ assert.equal(settledTeam.messages[0].kind, 'review-request', 'Mailbox messages keep their typed kind');
141
+ assert.equal(settledTeam.phase, 'waiting', 'Build settled but dependent review is not delegated yet');
142
+ assert.equal(settledTeam.progress.completed, 1);
143
+ assert.equal(settledTeam.members[0].child_thread_id ?? settledTeam.members[0].childId, teamChild);
144
+ const verifyJob = await call('delegate_to_agent', { agent_type: 'reviewer', task: 'Verify independently as Reviewer', team_id: team.team_id, member_id: team.members[1].id, team_task_id: dependent.id, isolation: 'shared' });
145
+ await wait(() => rt.collaboration.jobs.get(verifyJob.task_id).childId && pending.has(rt.collaboration.jobs.get(verifyJob.task_id).childId));
146
+ const reviewerChild = rt.collaboration.jobs.get(verifyJob.task_id).childId;
147
+ assert.notEqual(reviewerChild, teamChild, 'Different Harness teammates own different native sessions');
148
+ finish(reviewerChild, 'verification-result');
149
+ await wait(() => rt.collaboration.jobs.get(verifyJob.task_id).status === 'completed');
150
+ const polish = (await call('assign_team_task', { team_id: team.team_id, title: 'Polish', description: 'Address final details', assignee: team.members[0].id, depends_on: [dependent.id] })).task;
151
+ const polishJob = await call('delegate_to_agent', { agent_type: 'worker', task: 'Polish in the persistent Builder session', team_id: team.team_id, member_id: team.members[0].id, team_task_id: polish.id, isolation: 'worktree' });
152
+ assert.equal(polishJob.child_thread_id, teamChild, 'A teammate keeps one native session across team tasks');
153
+ assert.equal(polishJob.workspace.cwd, rt.collaboration.jobs.get(teamJob.task_id).workspace.cwd, 'A teammate keeps its existing workspace across team tasks');
154
+ await wait(() => pending.has(teamChild));
155
+ finish(teamChild, 'polish-result');
156
+ await wait(() => rt.collaboration.jobs.get(polishJob.task_id).status === 'completed');
157
+ const inspectedTeam = await rt.collaboration.inspectTeam(parent.id, team.team_id);
158
+ assert.equal(inspectedTeam.team.team_id, team.team_id);
159
+ assert.equal(inspectedTeam.team.phase, 'completed', 'All team tasks settled');
160
+ assert.equal(inspectedTeam.team.progress.percent, 100);
161
+ assert.ok(inspectedTeam.snapshots.length >= 8, 'Team lifecycle is preserved as replayable snapshots');
162
+ assert.ok(inspectedTeam.snapshots.some(entry => entry.action === 'member_session_ready'));
163
+ assert.ok(inspectedTeam.snapshots.some(entry => entry.action === 'task_settled'));
164
+ assert.equal((await rt.collaboration.inspectTeam(teamChild, team.team_id)).team.team_id, team.team_id, 'Members may inspect their own shared team state');
165
+ const { NativeProtocol } = require('../src/main/native/protocol');
166
+ const protocolTeam = await new NativeProtocol(rt, () => {}).request('harnessmix/thread/team/inspect', { threadId: parent.id, teamId: team.team_id });
167
+ assert.equal(protocolTeam.snapshots.at(-1).team.team_id, team.team_id, 'Renderer protocol exposes the persisted Team timeline');
168
+ await rt.collaboration.saveTeams();
169
+ const persistedTeams = JSON.parse(await fs.readFile(path.join(root, 'data', 'collaboration', 'teams.json'), 'utf8'));
170
+ assert.equal(persistedTeams[0].id, team.team_id);
171
+ assert.equal(persistedTeams[0].history.length, inspectedTeam.snapshots.length);
172
+ teammateTransport.stop();
90
173
  await assert.rejects(call('get_delegation_status', { task_ids: ['foreign-task'] }), /Unknown task/);
91
174
  await assert.rejects(rt.collaboration.call(jobs[0].childId, 'delegate_to_agent', { agent_type: 'worker', task: 'recursive' }), /Only lead/);
92
- await assert.rejects(call('delegate_to_agent', { agent_type: 'worker', task: 'x', unexpected: true }));
93
- const savedMentions = parent.activeMentions;
94
- delete parent.activeMentions;
95
- await assert.rejects(call('delegate_to_agent', { agent_type: 'worker', task: 'unauthorized' }), /用户本轮未显式委派/);
175
+ await assert.rejects(call('delegate_to_agent', { agent_type: 'worker', task: 'x', unexpected: true }));
176
+ const savedMentions = parent.activeMentions;
177
+ delete parent.activeMentions;
178
+ await assert.rejects(call('delegate_to_agent', { agent_type: 'worker', task: 'unauthorized' }), /用户本轮未显式委派/);
96
179
  parent.activeMentions = savedMentions;
97
180
  const denied = await fetch(ownerConnection.env.HARNESS_MIX_COLLAB_URL, { method: 'POST', headers: { Authorization: 'Bearer invalid' }, body: '{}' });
98
- assert.equal(denied.status, 403);
99
- const outsider = await rt.createThread({ harnessId: 'worker', cwd: root });
100
- await rt.send(outsider.id, 'unrelated');
101
- assert.equal(outsider.messages.at(-1).concurrent, true, 'Independent same-workspace turns are marked concurrent');
102
- await rt.cancel(outsider.id); await wait(() => !pending.has(outsider.id));
103
- for (let i = 0; i < 4; i++) await call('delegate_to_agent', { agent_type: 'worker', task: 'cancel me', isolation: 'shared' });
104
- await wait(() => pending.size === 4);
105
- await assert.rejects(call('delegate_to_agent', { agent_type: 'worker', task: 'too many' }), /four concurrent/);
181
+ assert.equal(denied.status, 403);
182
+ const outsider = await rt.createThread({ harnessId: 'worker', cwd: root });
183
+ await assert.rejects(rt.collaboration.inspectTeam(outsider.id, team.team_id), /not a team participant/);
184
+ const noTeam = await rt.collaboration.inspectTeam(outsider.id);
185
+ assert.equal(noTeam.team, null, 'Inspecting a non-team thread without a teamId answers benignly');
186
+ assert.deepEqual(noTeam.snapshots, []);
187
+ await rt.send(outsider.id, 'unrelated');
188
+ assert.equal(outsider.messages.at(-1).concurrent, true, 'Independent same-workspace turns are marked concurrent');
189
+ await rt.cancel(outsider.id); await wait(() => !pending.has(outsider.id));
190
+ for (let i = 0; i < 6; i++) await call('delegate_to_agent', { agent_type: 'worker', task: 'cancel me', isolation: 'shared' });
191
+ await wait(() => pending.size === 6);
192
+ await assert.rejects(call('delegate_to_agent', { agent_type: 'worker', task: 'too many' }), /six concurrent/);
106
193
  await rt.cancel(parent.id);
107
194
  await wait(() => !pending.size);
108
195
  assert.equal(rt.execution.lastTurn(parent.id).status, 'cancelled');
@@ -4,14 +4,17 @@ const fs = require('node:fs/promises');
4
4
  const path = require('node:path');
5
5
  app.disableHardwareAcceleration();
6
6
  app.whenReady().then(async () => {
7
- const bundle = await require('esbuild').build({ stdin: { contents: `export * from './src/native-ui/renderer-extension/src/renderer-harness-mentions.ts';`, resolveDir: process.cwd() }, bundle: true, platform: 'browser', format: 'iife', globalName: 'Mentions', write: false });
8
- const win = new BrowserWindow({ show: false, width: 900, height: 650, webPreferences: { sandbox: true, contextIsolation: true, offscreen: true, backgroundThrottling: false } });
7
+ const bundle = await require('esbuild').build({ stdin: { contents: `export * from './src/native-ui/renderer-extension/src/renderer-harness-mentions.ts'; export * from './src/native-ui/renderer-extension/src/renderer-team-cards.ts';`, resolveDir: process.cwd() }, bundle: true, platform: 'browser', format: 'iife', globalName: 'Mentions', write: false });
8
+ const win = new BrowserWindow({ show: false, width: 1024, height: 716, webPreferences: { sandbox: true, contextIsolation: true, offscreen: true, backgroundThrottling: false } });
9
9
  try {
10
- await win.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent('<body style="font:16px system-ui;padding:32px"><h2>Harness Mix · 多 Agent 协作</h2><p>主任务负责分工、收集结果与验证。</p><div data-codex-composer-root data-composer-draft-id="textarea-smoke" style="position:absolute;left:40px;bottom:50px;width:750px;box-sizing:border-box;padding:14px;border:2px solid #aaa;border-radius:16px"><textarea style="width:100%;height:90px;border:0;outline:0;resize:none;font:16px system-ui" placeholder="输入 # 选择协作 Harness"></textarea></div><div data-codex-composer-root data-composer-draft-id="rich-smoke" style="position:fixed;left:-1200px;width:400px"><div contenteditable="true" style="border:1px solid gray;padding:16px">你负责后端,</div></div></body>'));
10
+ await win.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent('<body style="font:16px system-ui;padding:32px"><h2>Harness Mix · 多 Agent 协作</h2><p>主任务负责分工、收集结果与验证。</p><div id="thread-content-anchor"></div><div data-codex-composer-root data-composer-draft-id="textarea-smoke" style="position:absolute;left:40px;bottom:50px;width:750px;box-sizing:border-box;padding:14px;border:2px solid #aaa;border-radius:16px"><textarea style="width:100%;height:90px;border:0;outline:0;resize:none;font:16px system-ui" placeholder="输入 # 选择协作 Harness"></textarea></div><div data-codex-composer-root data-composer-draft-id="rich-smoke" style="position:fixed;left:-1200px;width:400px"><div contenteditable="true" style="border:1px solid gray;padding:16px">你负责后端,</div></div></body>'));
11
11
  await win.webContents.executeJavaScript(`globalThis.__HARNESS_MIX_ICONS__ = ${JSON.stringify(require('../src/main/native/icons').getAllIconsDictionary())}`);
12
12
  await win.webContents.executeJavaScript(bundle.outputFiles[0].text);
13
- const result = await win.webContents.executeJavaScript(`(async () => {
13
+ const result = await win.webContents.executeJavaScript(`(async () => {
14
14
  const api = Mentions.installHarnessMentions(async () => ({agents:[{id:'pi',name:'Pi',available:true,lead:true},{id:'claude',name:'Claude Code',available:true,lead:true},{id:'dsh',name:'DeepSeek Harness',available:false,lead:false}],sessions:[{id:'c2Vzc2lvbg',title:'旧版登录审查',harnessId:'claude',cwd:'E:/project',running:false}]}));
15
+ const teamState = {action:'message_sent',team_id:'team-1',lead_thread_id:'lead-thread',name:'Release Team',goal:'实现并验证 Agent Team',status:'active',updated_at:2,lead:{id:'lead',name:'Team Lead',role:'Codex · 协调与验收',agent:'codex',display_status:'working'},members:[{id:'member-1',name:'Backend',role:'后端架构与 Host Runtime',agent:'pi',childId:'builder-thread',display_status:'working'},{id:'member-2',name:'Reviewer',role:'独立代码审查与质量验收',agent:'claude',display_status:'ready'},{id:'member-3',name:'Frontend',role:'Codex 原生界面集成',agent:'codex',display_status:'working'},{id:'member-4',name:'QA',role:'测试矩阵与回归验证',agent:'dsh',display_status:'ready'},{id:'member-5',name:'Docs',role:'文档与交付说明',agent:'opencode',display_status:'ready'},{id:'member-6',name:'Release',role:'Git、Diff 与最终发布检查',agent:'antigravity',display_status:'blocked'}],tasks:[{id:'task-1',title:'实现 Team Runtime',assignee:'member-1',dependsOn:[],status:'in_progress'},{id:'task-2',title:'独立验证实现',assignee:'member-2',dependsOn:['task-1'],status:'blocked'},{id:'task-3',title:'实现内嵌团队页面',assignee:'member-3',dependsOn:[],status:'in_progress'},{id:'task-4',title:'执行完整回归',assignee:'member-4',dependsOn:['task-1','task-3'],status:'blocked'},{id:'task-5',title:'更新协作文档',assignee:'member-5',dependsOn:[],status:'completed'},{id:'task-6',title:'核对最终 Diff',assignee:'member-6',dependsOn:['task-2','task-4'],status:'blocked'}],messages:[{id:'message-1',from:'member-1',fromName:'Backend',to:'member-2',body:'Runtime 已就绪,请准备审查。',at:1}]};
16
+ globalThis.openedTeamThread = null;
17
+ const teamApi = Mentions.installTeamCards({inspectTeam:async () => ({team:teamState,snapshots:[{id:'snapshot-1',action:'team_created',at:1,team:{...teamState,tasks:[],messages:[]}},{id:'snapshot-2',action:'message_sent',at:2,team:teamState}]}),openThread:async id => { globalThis.openedTeamThread=id; },activeThread:() => ({threadId:'lead-thread',anchor:document.querySelector('#thread-content-anchor')})});
15
18
  const editor = document.querySelector('textarea');
16
19
  editor.focus(); editor.value = '@cl'; editor.setSelectionRange(editor.value.length,editor.value.length); editor.dispatchEvent(new Event('input',{bubbles:true}));
17
20
  await new Promise(r=>setTimeout(r,30));
@@ -47,7 +50,14 @@ app.whenReady().then(async () => {
47
50
  const sessionBadge=[...document.querySelectorAll('[data-harness-mix-selected-mentions] [data-harness-mix-mention-badge]')].some(b=>b.title.startsWith('移除历史会话'));
48
51
  editor.value='请分别实现并审查这个任务。'; editor.setSelectionRange(editor.value.length,editor.value.length); editor.dispatchEvent(new Event('input',{bubbles:true}));
49
52
  globalThis.mentionSmokeEditor = editor;
50
- return {atTriggerStayedNative,filtered,text,richText,badge,badgeLabel:badgeButton?.getAttribute('aria-label'),inline,selectedAgentCount,icons,disabled,sessionBadge};
53
+ const teamPanel = document.querySelector('.harness-mix-team-panel');
54
+ const compact = {name:teamPanel?.querySelector('.harness-mix-team-name')?.textContent,panels:document.querySelectorAll('.harness-mix-team-panel').length,button:teamPanel?.querySelector('.harness-mix-team-open')?.textContent,icons:teamPanel?.querySelectorAll('img').length,topMounted:teamPanel?.nextElementSibling?.id === 'thread-content-anchor'};
55
+ teamPanel.querySelector('.harness-mix-team-open').click(); await new Promise(r=>setTimeout(r,30));
56
+ const workbench=document.querySelector('.harness-mix-team-workbench');
57
+ const builder=workbench.querySelector('[data-agent="pi"]'); builder.click(); await new Promise(r=>setTimeout(r,0));
58
+ const teamResult = {compact,name:workbench?.querySelector('.harness-mix-team-name')?.textContent,workbenches:document.querySelectorAll('.harness-mix-team-workbench').length,position:getComputedStyle(workbench).position,bodyOverflow:document.body.style.overflow,icons:workbench?.querySelectorAll('img').length,roles:[...workbench?.querySelectorAll('[data-agent]') ?? []].map(node=>node.getAttribute('data-agent')),lanes:workbench?.querySelectorAll('.harness-mix-team-lanes > div').length,hasTask:workbench?.textContent.includes('实现 Team Runtime'),hasRole:workbench?.textContent.includes('后端架构与 Host Runtime')&&workbench?.textContent.includes('独立代码审查与质量验收'),hasLeader:workbench?.textContent.includes('主导者'),hasMessage:workbench?.textContent.includes('Runtime 已就绪'),timeline:workbench?.querySelector('.harness-mix-team-timeline input')?.max,opened:globalThis.openedTeamThread};
59
+ globalThis.teamSmoke = teamApi;
60
+ return {atTriggerStayedNative,filtered,text,richText,badge,badgeLabel:badgeButton?.getAttribute('aria-label'),inline,selectedAgentCount,icons,disabled,sessionBadge,teamResult};
51
61
  })()`);
52
62
  assert.equal(result.atTriggerStayedNative, true);
53
63
  assert.equal(result.filtered, 1);
@@ -58,13 +68,40 @@ app.whenReady().then(async () => {
58
68
  assert.equal(result.inline, true);
59
69
  assert.equal(result.selectedAgentCount, 2);
60
70
  assert.equal(result.sessionBadge, true);
71
+ assert.equal(result.teamResult.name, 'Release Team');
72
+ assert.equal(result.teamResult.compact.panels, 1);
73
+ assert.equal(result.teamResult.compact.button, '展开详情');
74
+ assert.equal(result.teamResult.compact.topMounted, true, 'Agent Team launcher mounts before the native conversation content');
75
+ assert.equal(result.teamResult.workbenches, 0, 'Opening a member native session closes the workbench');
76
+ assert.notEqual(result.teamResult.position, 'fixed', 'Team details stay embedded in the Codex conversation layout');
77
+ assert.equal(result.teamResult.bodyOverflow, '', 'Opening Team details must not lock or cover the native Codex UI');
78
+ assert.equal(result.teamResult.opened, 'builder-thread');
79
+ assert.ok(result.teamResult.icons >= 5, 'Lead/member/task/message routes use real Harness icons');
80
+ assert.deepEqual(result.teamResult.roles, ['codex', 'pi', 'claude', 'codex', 'dsh', 'opencode', 'antigravity']);
81
+ assert.equal(result.teamResult.lanes, 6);
82
+ assert.equal(result.teamResult.hasTask, true);
83
+ assert.equal(result.teamResult.hasRole, true);
84
+ assert.equal(result.teamResult.hasLeader, true);
85
+ assert.equal(result.teamResult.hasMessage, true);
86
+ assert.equal(result.teamResult.timeline, '1');
61
87
  const out = path.resolve('output/collaboration-ui'); await fs.mkdir(out, { recursive: true });
88
+ await win.webContents.executeJavaScript('window.scrollTo(0,0)');
89
+ await fs.writeFile(path.join(out, 'team-inline-compact.png'), (await win.webContents.capturePage()).toPNG());
90
+ const compactBounds = await win.webContents.executeJavaScript(`(() => { const r=document.querySelector('.harness-mix-team-panel').getBoundingClientRect(); return {x:Math.max(0,Math.floor(r.x)-8),y:Math.max(0,Math.floor(r.y)-8),width:Math.ceil(r.width)+16,height:Math.ceil(r.height)+16}; })()`);
91
+ await fs.writeFile(path.join(out, 'team-inline-compact-focus.png'), (await win.webContents.capturePage(compactBounds)).toPNG());
92
+ await win.webContents.executeJavaScript(`document.querySelector('.harness-mix-team-open').click()`);
62
93
  await new Promise(r => setTimeout(r,100));
63
- await fs.writeFile(path.join(out, 'mentions.png'), (await win.webContents.capturePage()).toPNG());
64
- const submittedText = await win.webContents.executeJavaScript(`(() => { const root=mentionSmokeEditor.closest('[data-codex-composer-root]'); root.dispatchEvent(new Event('submit',{bubbles:true})); return mentionSmokeEditor.value; })()`);
94
+ const expanded = (await win.webContents.capturePage()).toPNG();
95
+ await fs.writeFile(path.join(out, 'team-inline-expanded.png'), expanded);
96
+ await fs.writeFile(path.join(out, 'mentions.png'), expanded);
97
+ const expandedBounds = await win.webContents.executeJavaScript(`(() => { const a=document.querySelector('.harness-mix-team-panel').getBoundingClientRect(), b=document.querySelector('.harness-mix-team-workbench').getBoundingClientRect(), left=Math.min(a.left,b.left), top=Math.min(a.top,b.top), right=Math.max(a.right,b.right), bottom=Math.max(a.bottom,b.bottom); return {x:Math.max(0,Math.floor(left)-8),y:Math.max(0,Math.floor(top)-8),width:Math.ceil(right-left)+16,height:Math.ceil(bottom-top)+16}; })()`);
98
+ await win.webContents.executeJavaScript(`document.querySelector('[data-composer-draft-id="textarea-smoke"]').style.visibility='hidden'`);
99
+ await fs.writeFile(path.join(out, 'team-inline-expanded-focus.png'), (await win.webContents.capturePage(expandedBounds)).toPNG());
100
+ await win.webContents.executeJavaScript(`document.querySelector('[data-composer-draft-id="textarea-smoke"]').style.visibility=''`);
101
+ const submittedText = await win.webContents.executeJavaScript(`(() => { const root=mentionSmokeEditor.closest('[data-codex-composer-root]'); mentionSmoke.prepareSubmission(root); return mentionSmokeEditor.value; })()`);
65
102
  assert.match(submittedText, /#claude/);
66
103
  assert.match(submittedText, /#pi/);
67
- await win.webContents.executeJavaScript('mentionSmoke.dispose()');
104
+ await win.webContents.executeJavaScript('mentionSmoke.dispose(); teamSmoke.dispose()');
68
105
  assert.equal(await win.webContents.executeJavaScript('document.querySelectorAll("[data-harness-mix-mentions]").length'), 0);
69
106
  console.log('PASS: Electron native composer # Harness/session icons, badges, keyboard selection, textarea/rich text and cleanup');
70
107
  } finally { win.destroy(); app.quit(); }
@@ -124,6 +124,11 @@ const until = async (predicate, timeoutMs = 4000) => {
124
124
  // Test 2: 第三层 —— 基于 Git Worktree 的物理隔离 (Worktree Isolation)
125
125
  // -------------------------------------------------------------
126
126
  console.log('--- Testing Layer 3: Git Worktree Physical Isolation ---');
127
+ await assert.rejects(
128
+ rt.createThread({ harnessId: 'mock-worker', cwd: sharedDir, worktree: true }),
129
+ /无法建立隔离工作区|not a git repository/i,
130
+ 'An explicit Worktree request must never silently fall back to the shared directory'
131
+ );
127
132
  const gitRepo = path.join(tmpDir, 'git-project');
128
133
  await fs.mkdir(gitRepo);
129
134
  await git(gitRepo, ['init']);
@@ -29,7 +29,7 @@ const tail = (file, lines = 200) => {
29
29
  const write = (name, content) => fs.writeFileSync(path.join(stage, name), String(content));
30
30
 
31
31
  const env = nativeEnvironment();
32
- const data = env.CODEXHOST_DATA_DIR;
32
+ const data = env.HARNESSMIX_DATA_DIR;
33
33
  write('versions.json', JSON.stringify({
34
34
  'harness-mix': pkg.version,
35
35
  node: process.version,
@@ -12,12 +12,12 @@ async function main() {
12
12
  assert.ok(stock && fs.existsSync(stock), 'Official Codex executable must exist');
13
13
  const directory = path.resolve('output/native-host', `run-${Date.now()}`);
14
14
  fs.mkdirSync(directory, { recursive: true });
15
- const env = nativeEnvironment({ ...process.env, CODEXHOST_DATA_DIR: path.join(directory, 'data') });
15
+ const env = nativeEnvironment({ ...process.env, HARNESSMIX_DATA_DIR: path.join(directory, 'data') });
16
16
  Object.assign(env, {
17
- CODEXHOST_STOCK_CODEX_PATH: stock,
18
- CODEXHOST_HOST_NODE_PATH: process.execPath,
19
- CODEXHOST_HOST_RUNTIME_PATH: paths.wrapper,
20
- CODEXHOST_DEFAULT_AGENT: 'codex',
17
+ HARNESSMIX_STOCK_CODEX_PATH: stock,
18
+ HARNESSMIX_HOST_NODE_PATH: process.execPath,
19
+ HARNESSMIX_HOST_RUNTIME_PATH: paths.wrapper,
20
+ HARNESSMIX_DEFAULT_AGENT: 'codex',
21
21
  });
22
22
  const events = [];
23
23
  const diagnostics = [];
@@ -44,11 +44,11 @@ async function main() {
44
44
  assert.deepEqual(ownership.codex, { mode: 'official-passthrough', managedRoute: 'codex-harness' });
45
45
  report.checks.push('Harness Mix HostRuntime and ProtocolCore own external execution');
46
46
  report.checks.push('official Codex route is separate from the managed Codex worker');
47
- const plugins = await request('codexhost/harness/plugins/list', {});
47
+ const plugins = await request('harnessmix/harness/plugins/list', {});
48
48
  assert.ok(plugins.plugins.some(plugin => (plugin.id || plugin.manifest?.id) === 'pi'), 'Pi plugin registered');
49
49
  report.checks.push('external harness plugins registered');
50
50
  for (const harnessId of ['pi', 'claude-code', 'deepseek-harness']) {
51
- const inspection = await request('codexhost/harness/inspect', { harnessId, cwd: directory });
51
+ const inspection = await request('harnessmix/harness/inspect', { harnessId, cwd: directory });
52
52
  report[harnessId] = inspection;
53
53
  console.log(`${harnessId}: ${inspection.status}; models=${inspection.catalog?.models?.length || 0}${inspection.error ? `; ${inspection.error.message}` : ''}`);
54
54
  assert.equal(inspection.status, 'ready', `${harnessId} must be ready`);
@@ -56,12 +56,12 @@ async function main() {
56
56
  const officialModels = await request('model/list', {});
57
57
  assert.ok(Array.isArray(officialModels.data), 'Official model/list passes through');
58
58
  report.checks.push('official model/list passthrough');
59
- const codexAccounts = await request('codexhost/account/refresh', {});
59
+ const codexAccounts = await request('harnessmix/account/refresh', {});
60
60
  const officialAccount = codexAccounts.accounts?.find(account => account.management === 'native');
61
61
  assert.ok(officialAccount, 'Official Codex account is projected into Account management');
62
62
  assert.equal(officialAccount.authenticated, true, 'Current official Codex login is detected');
63
63
  assert.ok(officialAccount.email, 'Current ChatGPT account identity is available from account/read');
64
- const officialAccountUsage = await request('codexhost/account/usage/inspect', { accountId: officialAccount.accountId });
64
+ const officialAccountUsage = await request('harnessmix/account/usage/inspect', { accountId: officialAccount.accountId });
65
65
  assert.ok(officialAccountUsage.accountCredits, 'Official Codex rate limits are projected');
66
66
  report.account = {
67
67
  authenticated: officialAccount.authenticated,
@@ -94,7 +94,7 @@ async function main() {
94
94
  report.checks.push('real official Codex thread/turn passthrough');
95
95
  }
96
96
  // Real Host + stock-server section catalog, with test threads isolated in output/.
97
- const sidebarThread = await request('thread/start', { cwd: directory, model: 'codexhost/pi-native' });
97
+ const sidebarThread = await request('thread/start', { cwd: directory, model: 'harnessmix/pi-native' });
98
98
  const sidebarId = sidebarThread.thread.id;
99
99
  assert.equal(sidebarThread.thread.sessionId, sidebarId);
100
100
  const descendants = await request('thread/list', { ancestorThreadId: sidebarId, sourceKinds: ['subAgentThreadSpawn'] });
@@ -109,14 +109,14 @@ async function main() {
109
109
  await request('thread/section/move', { threadId: sidebarId, sectionId: null });
110
110
  assert.equal((await inSection()).length, 0, 'Unpin stays removed on the next list query');
111
111
  const resumed = await request('thread/resume', { threadId: sidebarId });
112
- assert.equal(resumed.model, 'codexhost/pi-native');
113
- const inspected = await request('codexhost/thread/inspect', { threadId: sidebarId });
112
+ assert.equal(resumed.model, 'harnessmix/pi-native');
113
+ const inspected = await request('harnessmix/thread/inspect', { threadId: sidebarId });
114
114
  assert.equal(inspected.harnessId, 'pi');
115
115
  assert.equal(inspected.transportModelId, resumed.model);
116
116
  report.checks.push('external identity, no self-descendants, pin/unpin and resume ownership');
117
117
  if (process.argv.includes('--live')) {
118
118
  const harnessId = process.argv.find(arg => arg.startsWith('--harness='))?.split('=')[1] || 'pi';
119
- const started = await request('thread/start', { cwd: directory, model: `codexhost/${harnessId}-native` });
119
+ const started = await request('thread/start', { cwd: directory, model: `harnessmix/${harnessId}-native` });
120
120
  assert.ok(started.thread?.id, 'Native external thread created');
121
121
  const threadId = started.thread.id;
122
122
  const turn = await request('turn/start', {
@@ -90,15 +90,15 @@ function main() {
90
90
  const noInfo = protocol.turn({ id: 'turn_10', status: 'error', error: 'boom' });
91
91
  assert.equal(noInfo.error.codexErrorInfo, null);
92
92
  return (async () => {
93
- const state = await protocol.request('codexhost/harness/turn-error', { threadId: 'thread_3' });
93
+ const state = await protocol.request('harnessmix/harness/turn-error', { threadId: 'thread_3' });
94
94
  assert.equal(state.errorKind, 'auth');
95
95
  assert.equal(state.error, 'Invalid API key');
96
96
  assert.deepEqual(state.actions, ['login', 'newSession']); // claude-code 有 loginCommand
97
97
  fakeRuntime.threads[0].errorKind = null;
98
- const cleared = await protocol.request('codexhost/harness/turn-error', { threadId: 'thread_3' });
98
+ const cleared = await protocol.request('harnessmix/harness/turn-error', { threadId: 'thread_3' });
99
99
  assert.equal(cleared.errorKind, null);
100
100
  assert.deepEqual(cleared.actions, []);
101
- await assert.rejects(protocol.request('codexhost/harness/turn-error', { threadId: 'nope' }), /Unknown thread/);
101
+ await assert.rejects(protocol.request('harnessmix/harness/turn-error', { threadId: 'nope' }), /Unknown thread/);
102
102
  protocol.close?.();
103
103
  console.log('PASS: error classification (explicit/codexErrorInfo/status/pattern/unknown), actions, normalizer/core/thread projection, turn-error query');
104
104
  })();