@harness-mix/cli 0.1.11 → 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.
- package/CHANGELOG.md +16 -0
- package/NOTICE +16 -2
- package/README.md +265 -3
- package/docs/cross-platform.md +3 -3
- package/docs/multi-agent-collaboration.md +37 -23
- package/docs/native-acp.md +2 -2
- package/docs/native-codex.md +5 -5
- package/docs/{superpowers/specs → specs}/2026-09-13-native-hardening-design.md +2 -2
- package/docs/storage-verification-design.md +5 -5
- package/licenses/lobe-icons-MIT.txt +21 -0
- package/output/native-build/desktop-controller.mjs +24 -24
- package/output/native-build/harness-mix-secret.exe +0 -0
- package/output/native-build/harness-mix-shim.exe +0 -0
- package/output/native-build/renderer-extension.js +1325 -658
- package/package.json +2 -2
- package/scripts/adapters-test.cjs +9 -1
- package/scripts/antigravity-adapter-test.cjs +381 -284
- package/scripts/build-native.cjs +64 -46
- package/scripts/codex-accounts-test.cjs +1 -1
- package/scripts/codex-accounts-ui-smoke.cjs +1 -1
- package/scripts/collaboration-recovery-test.cjs +11 -2
- package/scripts/collaboration-test.cjs +114 -27
- package/scripts/collaboration-ui-smoke.cjs +45 -8
- package/scripts/concurrency-layers-test.cjs +5 -0
- package/scripts/diagnostics.cjs +1 -1
- package/scripts/e2e-native-host.cjs +13 -13
- package/scripts/error-kind-test.cjs +3 -3
- package/scripts/integrations-test.cjs +2 -2
- package/scripts/integrations-ui-smoke.cjs +6 -6
- package/scripts/native-config-test.cjs +6 -6
- package/scripts/native-job-object-test.cjs +1 -1
- package/scripts/native-protocol-test.cjs +44 -38
- package/scripts/native-rollback-test.cjs +1 -1
- package/scripts/native-secret-test.cjs +1 -1
- package/scripts/native-shim-test.cjs +1 -1
- package/scripts/native-sidebar-test.cjs +4 -4
- package/scripts/native-ui-smoke.cjs +12 -12
- package/scripts/pets-test.cjs +223 -3
- package/scripts/platform-test.cjs +6 -6
- package/scripts/publish-native-package.cjs +1 -1
- package/scripts/test-live-mentions.cjs +2 -2
- package/scripts/workspace-test.cjs +4 -2
- package/src/assets/icons/PROVENANCE.md +92 -0
- package/src/main/adapters/antigravity.js +1415 -1368
- package/src/main/adapters/claude.js +1 -1
- package/src/main/adapters/codex-app-server.js +1 -1
- package/src/main/adapters/dsh.js +1 -1
- package/src/main/adapters/index.js +1 -1
- package/src/main/adapters/pi-usage.js +2 -2
- package/src/main/harness-adapter/error-kind.js +1 -1
- package/src/main/harness-adapter/manifest.js +7 -1
- package/src/main/host/collaboration-tools.js +8 -1
- package/src/main/host/collaboration-worktree.js +27 -1
- package/src/main/host/collaboration.js +348 -15
- package/src/main/host/runtime.js +23 -15
- package/src/main/native/codex-accounts.js +1 -1
- package/src/main/native/config.js +9 -9
- package/src/main/native/host.js +1 -1
- package/src/main/native/launcher.js +10 -8
- package/src/main/native/pets.js +188 -49
- package/src/main/native/platform.js +19 -9
- package/src/main/native/protocol.js +83 -80
- package/src/main/native/rs/crates/appx/Cargo.toml +1 -1
- package/src/main/native/rs/crates/secret/Cargo.toml +1 -1
- package/src/main/native/rs/crates/secret/src/main.rs +3 -3
- package/src/main/native/rs/crates/shim/Cargo.toml +1 -1
- package/src/main/native/rs/crates/shim/src/main.rs +2 -2
- package/src/main/native/thread-list.js +1 -1
- package/src/main/shared-contracts/capability.js +3 -1
- package/src/main/workspace/core-review.js +4 -3
- package/src/native-ui/README.md +2 -2
- package/src/native-ui/desktop-control/dist/contract-audit.js +4 -4
- package/src/native-ui/desktop-control/dist/index.d.ts +1 -1
- package/src/native-ui/desktop-control/dist/index.js +2 -2
- package/src/native-ui/desktop-control/dist/index.js.map +1 -1
- package/src/native-ui/desktop-control/dist/main-process-title-policy.js +5 -5
- package/src/native-ui/desktop-control/dist/main-process-title-policy.js.map +1 -1
- package/src/native-ui/desktop-control/dist/production-controller.js +3 -3
- package/src/native-ui/desktop-control/dist/production-controller.js.map +1 -1
- package/src/native-ui/desktop-control/dist/release-main.js +1 -1
- package/src/native-ui/desktop-control/dist/release-main.js.map +1 -1
- package/src/native-ui/desktop-control/dist/renderer-cdp-control-session.js +1 -1
- package/src/native-ui/desktop-control/dist/renderer-cdp-control-session.js.map +1 -1
- package/src/native-ui/desktop-control/dist/renderer-control-session.js +3 -3
- package/src/native-ui/desktop-control/dist/renderer-control-session.js.map +1 -1
- package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-runtime.js +25 -25
- package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-runtime.js.map +1 -1
- package/src/native-ui/desktop-control/dist/tsconfig.tsbuildinfo +1 -1
- package/src/native-ui/desktop-control/package.json +2 -2
- package/src/native-ui/desktop-control/scripts/build-release.mjs +2 -2
- package/src/native-ui/desktop-control/src/contract-audit.ts +4 -4
- package/src/native-ui/desktop-control/src/index.ts +2 -2
- package/src/native-ui/desktop-control/src/main-process-title-policy.ts +5 -5
- package/src/native-ui/desktop-control/src/production-controller.ts +3 -3
- package/src/native-ui/desktop-control/src/release-main.ts +1 -1
- package/src/native-ui/desktop-control/src/renderer-cdp-control-session.ts +1 -1
- package/src/native-ui/desktop-control/src/renderer-control-session.ts +3 -3
- package/src/native-ui/desktop-control/src/renderer-draft-prewarm-runtime.ts +25 -25
- package/src/native-ui/desktop-control/test/index.test.ts +1 -1
- package/src/native-ui/desktop-control/test/production-controller.test.ts +1 -1
- package/src/native-ui/desktop-control/test/renderer-cdp-control-session.test.ts +1 -1
- package/src/native-ui/desktop-control/test/renderer-draft-prewarm-policy.test.ts +43 -43
- package/src/native-ui/renderer-extension/dist/types/agent-group-preference.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/agent-group-preference.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/agent-selection-state.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/agent-selection-state.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/contract-audit.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/contract-audit.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/index.d.ts +3 -1
- package/src/native-ui/renderer-extension/dist/types/index.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/production-entry.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/production-entry.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts +2 -2
- package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts +3 -3
- package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-options.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-options.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-state.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-state.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-collab-cards.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-collab-cards.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-composer-dom.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-composer-dom.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-credits-control.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-credits-control.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-fork-control.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-fork-control.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-command-claim.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-command-claim.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-command-control.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-command-control.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-handoff.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-handoff.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-localization.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-localization.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts +1 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +36 -31
- package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-model-picker.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-model-picker.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-new-thread-preference.d.ts +2 -2
- package/src/native-ui/renderer-extension/dist/types/renderer-new-thread-preference.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-permission-mode-picker.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-permission-mode-picker.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-permission-mode-preference.d.ts +2 -2
- package/src/native-ui/renderer-extension/dist/types/renderer-permission-mode-preference.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-request-sender.d.ts +18 -2
- package/src/native-ui/renderer-extension/dist/types/renderer-request-sender.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-session-import-client.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-session-import-client.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-sidebar-agent-icons.d.ts +2 -2
- package/src/native-ui/renderer-extension/dist/types/renderer-sidebar-agent-icons.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-team-cards.d.ts +58 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-team-cards.d.ts.map +1 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-transcript-dom.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-trigger-chip-style.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-trigger-chip-style.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-usage-control.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-usage-control.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/accounts-list.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/accounts-list.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/accounts-page.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/accounts-page.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/accounts-usage.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/accounts-usage.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/connections-page.d.ts +3 -3
- package/src/native-ui/renderer-extension/dist/types/settings/connections-page.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/harness-accounts.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/harness-accounts.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +4 -4
- package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/pet-market.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/session-import-list-controls.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/session-import-list-controls.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/session-import-page.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/session-import-page.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/shell.d.ts +2 -2
- package/src/native-ui/renderer-extension/dist/types/settings/shell.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/trigger.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/trigger.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
- package/src/native-ui/renderer-extension/dist/types/versioned-renderer-adapter.d.ts +18 -18
- package/src/native-ui/renderer-extension/dist/types/versioned-renderer-adapter.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/package.json +2 -2
- package/src/native-ui/renderer-extension/src/agent-group-preference.ts +1 -1
- package/src/native-ui/renderer-extension/src/agent-selection-state.ts +31 -16
- package/src/native-ui/renderer-extension/src/audit-entry.ts +1 -1
- package/src/native-ui/renderer-extension/src/contract-audit.ts +4 -4
- package/src/native-ui/renderer-extension/src/index.ts +4 -2
- package/src/native-ui/renderer-extension/src/install-renderer-binding.ts +2 -2
- package/src/native-ui/renderer-extension/src/production-entry.ts +3 -3
- package/src/native-ui/renderer-extension/src/renderer-agent-picker.ts +7 -7
- package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +112 -89
- package/src/native-ui/renderer-extension/src/renderer-codex-account-options.ts +1 -1
- package/src/native-ui/renderer-extension/src/renderer-codex-account-state.ts +1 -1
- package/src/native-ui/renderer-extension/src/renderer-collab-cards.ts +1 -1
- package/src/native-ui/renderer-extension/src/renderer-composer-dom.ts +12 -12
- package/src/native-ui/renderer-extension/src/renderer-credits-control.ts +7 -7
- package/src/native-ui/renderer-extension/src/renderer-external-steering.ts +3 -3
- package/src/native-ui/renderer-extension/src/renderer-fork-control.ts +1 -1
- package/src/native-ui/renderer-extension/src/renderer-harness-command-claim.ts +1 -1
- package/src/native-ui/renderer-extension/src/renderer-harness-command-control.ts +3 -3
- package/src/native-ui/renderer-extension/src/renderer-harness-handoff.ts +1 -1
- package/src/native-ui/renderer-extension/src/renderer-harness-localization.ts +1 -1
- package/src/native-ui/renderer-extension/src/renderer-harness-mentions.ts +4 -0
- package/src/native-ui/renderer-extension/src/renderer-integrations-client.ts +5 -5
- package/src/native-ui/renderer-extension/src/renderer-model-client.ts +63 -42
- package/src/native-ui/renderer-extension/src/renderer-model-picker.ts +16 -16
- package/src/native-ui/renderer-extension/src/renderer-new-thread-preference.ts +2 -2
- package/src/native-ui/renderer-extension/src/renderer-permission-mode-picker.ts +3 -3
- package/src/native-ui/renderer-extension/src/renderer-permission-mode-preference.ts +2 -2
- package/src/native-ui/renderer-extension/src/renderer-request-sender.ts +46 -3
- package/src/native-ui/renderer-extension/src/renderer-session-import-client.ts +4 -4
- package/src/native-ui/renderer-extension/src/renderer-sidebar-agent-icons.ts +24 -20
- package/src/native-ui/renderer-extension/src/renderer-team-cards.ts +312 -0
- package/src/native-ui/renderer-extension/src/renderer-transcript-dom.ts +1 -1
- package/src/native-ui/renderer-extension/src/renderer-trigger-chip-style.ts +2 -2
- package/src/native-ui/renderer-extension/src/renderer-usage-control.ts +4 -4
- package/src/native-ui/renderer-extension/src/settings/accounts-list.ts +1 -1
- package/src/native-ui/renderer-extension/src/settings/accounts-page.ts +1 -1
- package/src/native-ui/renderer-extension/src/settings/accounts-usage.ts +1 -1
- package/src/native-ui/renderer-extension/src/settings/connections-page.ts +7 -7
- package/src/native-ui/renderer-extension/src/settings/harness-accounts.ts +1 -1
- package/src/native-ui/renderer-extension/src/settings/icons.ts +2 -2
- package/src/native-ui/renderer-extension/src/settings/pages.ts +11 -11
- package/src/native-ui/renderer-extension/src/settings/pet-market.css +33 -0
- package/src/native-ui/renderer-extension/src/settings/pet-market.ts +173 -91
- package/src/native-ui/renderer-extension/src/settings/pets-client.ts +5 -5
- package/src/native-ui/renderer-extension/src/settings/release-notes.ts +1 -1
- package/src/native-ui/renderer-extension/src/settings/session-import-list-controls.ts +1 -1
- package/src/native-ui/renderer-extension/src/settings/session-import-page.ts +1 -1
- package/src/native-ui/renderer-extension/src/settings/shell.css +15 -15
- package/src/native-ui/renderer-extension/src/settings/shell.ts +11 -11
- package/src/native-ui/renderer-extension/src/settings/skin-runtime.ts +17 -17
- package/src/native-ui/renderer-extension/src/settings/trigger.ts +1 -1
- package/src/native-ui/renderer-extension/src/versioned-renderer-adapter.ts +20 -20
- package/src/native-ui/renderer-extension/test/agent-selection-state.test.ts +60 -1
- package/src/native-ui/renderer-extension/test/codebuddy-migration.test.ts +1 -1
- package/src/native-ui/renderer-extension/test/renderer-agent-picker.test.ts +1 -1
- package/src/native-ui/renderer-extension/test/renderer-binding-probe-host-catalog.test.ts +15 -9
- package/src/native-ui/renderer-extension/test/renderer-binding-probe.test.ts +15 -15
- package/src/native-ui/renderer-extension/test/renderer-codex-account-state.test.ts +1 -1
- package/src/native-ui/renderer-extension/test/renderer-composer-permissions.test.ts +4 -4
- package/src/native-ui/renderer-extension/test/renderer-external-steering-rpc.test.ts +2 -2
- package/src/native-ui/renderer-extension/test/renderer-external-steering.test.ts +3 -3
- package/src/native-ui/renderer-extension/test/renderer-fork-control.test.ts +4 -4
- package/src/native-ui/renderer-extension/test/renderer-harness-command-claim.test.ts +1 -1
- package/src/native-ui/renderer-extension/test/renderer-harness-command-control.test.ts +1 -1
- package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +12 -11
- package/src/native-ui/renderer-extension/test/renderer-model-picker.test.ts +1 -1
- package/src/native-ui/renderer-extension/test/renderer-native-thread-ownership.test.ts +6 -6
- package/src/native-ui/renderer-extension/test/renderer-new-thread-preference.test.ts +1 -1
- package/src/native-ui/renderer-extension/test/renderer-permission-mode-picker.test.ts +1 -1
- package/src/native-ui/renderer-extension/test/renderer-permission-mode-preference.test.ts +1 -1
- package/src/native-ui/renderer-extension/test/renderer-request-timeout.test.ts +80 -0
- package/src/native-ui/renderer-extension/test/renderer-settings-lifecycle.test.ts +2 -2
- package/src/native-ui/renderer-extension/test/renderer-sidebar-agent-icons.test.ts +10 -3
- package/src/native-ui/renderer-extension/test/renderer-unsupported-methods.test.ts +1 -1
- package/src/native-ui/renderer-extension/test/settings/pages.test.ts +5 -5
- package/src/native-ui/renderer-extension/test/settings/release-notes.test.ts +9 -9
- package/src/native-ui/renderer-extension/test/versioned-renderer-adapter.test.ts +3 -3
- package/src/native-ui/shared-contracts/dist/errors.d.ts +2 -2
- package/src/native-ui/shared-contracts/dist/errors.d.ts.map +1 -1
- package/src/native-ui/shared-contracts/dist/errors.js +1 -1
- package/src/native-ui/shared-contracts/dist/errors.js.map +1 -1
- package/src/native-ui/shared-contracts/dist/harness-models.d.ts +40 -0
- package/src/native-ui/shared-contracts/dist/harness-models.d.ts.map +1 -1
- package/src/native-ui/shared-contracts/dist/harness-models.js +23 -2
- package/src/native-ui/shared-contracts/dist/harness-models.js.map +1 -1
- package/src/native-ui/shared-contracts/dist/harness-route.d.ts +1 -1
- package/src/native-ui/shared-contracts/dist/harness-route.d.ts.map +1 -1
- package/src/native-ui/shared-contracts/dist/harness-route.js +1 -1
- package/src/native-ui/shared-contracts/dist/harness-route.js.map +1 -1
- package/src/native-ui/shared-contracts/dist/harness-session-import.d.ts +1 -1
- package/src/native-ui/shared-contracts/dist/harness-session-import.d.ts.map +1 -1
- package/src/native-ui/shared-contracts/dist/harness-session-import.js +1 -1
- package/src/native-ui/shared-contracts/dist/harness-session-import.js.map +1 -1
- package/src/native-ui/shared-contracts/dist/index.d.ts +3 -3
- package/src/native-ui/shared-contracts/dist/index.d.ts.map +1 -1
- package/src/native-ui/shared-contracts/dist/index.js +2 -2
- package/src/native-ui/shared-contracts/dist/index.js.map +1 -1
- package/src/native-ui/shared-contracts/dist/tsconfig.tsbuildinfo +1 -1
- package/src/native-ui/shared-contracts/dist/updates.js +1 -1
- package/src/native-ui/shared-contracts/dist/updates.js.map +1 -1
- package/src/native-ui/shared-contracts/package.json +1 -1
- package/src/native-ui/shared-contracts/src/errors.ts +3 -3
- package/src/native-ui/shared-contracts/src/harness-models.ts +23 -2
- package/src/native-ui/shared-contracts/src/harness-route.ts +1 -1
- package/src/native-ui/shared-contracts/src/harness-session-import.ts +1 -1
- package/src/native-ui/shared-contracts/src/index.ts +3 -3
- package/src/native-ui/shared-contracts/src/updates.ts +1 -1
- package/src/native-ui/shared-contracts/test/browser-bundle.test.ts +3 -3
- package/src/native-ui/shared-contracts/test/deepseek-modern-sessions.test.ts +1 -1
- package/src/native-ui/shared-contracts/test/errors.test.ts +9 -9
- package/src/native-ui/shared-contracts/test/harness-commands.test.ts +1 -1
- package/src/native-ui/shared-contracts/test/harness-models.test.ts +3 -3
- package/src/native-ui/shared-contracts/test/harness-permission-modes.test.ts +1 -1
- package/src/native-ui/shared-contracts/test/harness-plugins.test.ts +1 -1
- package/src/native-ui/shared-contracts/test/harness-session-import.test.ts +1 -1
- package/src/native-ui/shared-contracts/test/index.test.ts +3 -3
- package/src/native-ui/shared-contracts/test/json-rpc.test.ts +1 -1
- package/src/native-ui/shared-contracts/test/thread-usage.test.ts +1 -1
- package/src/native-ui/shared-contracts/test/updates.test.ts +4 -4
- /package/licenses/{codex-host-MIT.txt → bytepioneer-MIT.txt} +0 -0
package/scripts/build-native.cjs
CHANGED
|
@@ -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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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: '
|
|
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: { '@
|
|
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('
|
|
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) {
|
|
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
|
-
|
|
26
|
-
|
|
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,
|
|
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.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await
|
|
105
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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']);
|
package/scripts/diagnostics.cjs
CHANGED
|
@@ -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.
|
|
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,
|
|
15
|
+
const env = nativeEnvironment({ ...process.env, HARNESSMIX_DATA_DIR: path.join(directory, 'data') });
|
|
16
16
|
Object.assign(env, {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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: '
|
|
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, '
|
|
113
|
-
const inspected = await request('
|
|
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: `
|
|
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('
|
|
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('
|
|
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('
|
|
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
|
})();
|