@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
|
@@ -8,7 +8,7 @@ const manifest = {
|
|
|
8
8
|
name: "Claude Code",
|
|
9
9
|
icon: "claude-color.svg",
|
|
10
10
|
aliases: ['claude', 'claude-code'],
|
|
11
|
-
//
|
|
11
|
+
// 完整接入:官方 Agent SDK query() 常驻双向 stream-json 会话,
|
|
12
12
|
// 审批/提问经 canUseTool 桥接回 Host,中断/模型/权限模式走原生控制协议。
|
|
13
13
|
capabilities: { collaborationTools: true, streaming: true, thinking: true, tools: true, approvals: true, questions: true, models: true, thinkingLevels: true, permissionModes: true, resume: true, fork: true, forkFromMessage: true, compaction: true, usage: true, contextUsage: true, attachments: true },
|
|
14
14
|
};
|
|
@@ -16,7 +16,7 @@ class CodexAppServer {
|
|
|
16
16
|
this.closed = false;
|
|
17
17
|
this.codexHome = codexHome ? require('node:path').resolve(codexHome) : null;
|
|
18
18
|
// The Desktop-bundled CLI and the PATH CLI can be different versions.
|
|
19
|
-
const executable = process.env.HARNESS_MIX_CODEX_EXECUTABLE || process.env.
|
|
19
|
+
const executable = process.env.HARNESS_MIX_CODEX_EXECUTABLE || process.env.HARNESSMIX_STOCK_CODEX_PATH;
|
|
20
20
|
const { command, args } = executable ? { command: executable, args: ['app-server', '--listen', 'stdio://'] } : cliSpawn('codex', ['app-server', '--stdio']);
|
|
21
21
|
const env = { ...process.env, ...(this.codexHome ? { CODEX_HOME: this.codexHome } : {}) };
|
|
22
22
|
this.process = new JsonlProcess(command, args, { env }, {
|
package/src/main/adapters/dsh.js
CHANGED
|
@@ -11,7 +11,7 @@ const manifest = {
|
|
|
11
11
|
name: "DeepSeek Harness",
|
|
12
12
|
icon: "deepseek-color.svg",
|
|
13
13
|
aliases: ["dsh", "deepseek-harness"],
|
|
14
|
-
//
|
|
14
|
+
// 完整接入:官方 Web Remote 协议(Typert RPC + remote.mux 流),
|
|
15
15
|
// 审批/提问走 $events waterfall 原生应答;ACP 自动化面(无 fork/提问/plan)已弃用。
|
|
16
16
|
capabilities: { collaborationTools: true, plan: true, streaming: true, thinking: true, tools: true, approvals: true, questions: true, models: true, thinkingLevels: true, permissionModes: false, resume: true, fork: true, forkFromMessage: true, compaction: true, usage: true, contextUsage: true, attachments: true },
|
|
17
17
|
};
|
|
@@ -17,7 +17,7 @@ const cursor = require('./cursor');
|
|
|
17
17
|
const cline = require('./cline');
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Adapter
|
|
20
|
+
* Adapter 注册表:Manifest + 工厂 + Adapter + Session。
|
|
21
21
|
* 新增 Harness 时:实现同形状模块并加入此列表,Renderer 无需改动协议。
|
|
22
22
|
*/
|
|
23
23
|
const REGISTRY = [antigravity, pi, omp, dsh, claude, codex, opencode, grok, openclaw, hermes, qoder, codebuddy, zcode, trae, kiro, cursor, cline];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const valid = n => typeof n === 'number' && Number.isFinite(n) && n >= 0;
|
|
2
2
|
const clean = obj => Object.fromEntries(Object.entries(obj).filter(([, v]) => valid(v)));
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
//
|
|
4
|
+
// Cache tokens are accounted separately from uncached input, matching the native
|
|
5
|
+
// accounting. Session totals and latest-request cache rate must not mix.
|
|
6
6
|
function sessionUsage(data = {}) {
|
|
7
7
|
const t = data.tokens ?? {}, c = data.contextUsage ?? {};
|
|
8
8
|
return { ...clean({ input: t.input, output: t.output, cacheRead: t.cacheRead,
|
|
@@ -49,7 +49,13 @@ function normalizeCapabilities(caps = {}) {
|
|
|
49
49
|
return createCapabilities({
|
|
50
50
|
conversation: { streaming: caps.streaming === true, reasoning: caps.thinking === true, plan: caps.plan === true, compaction: caps.compaction === true, attachments: caps.attachments === true },
|
|
51
51
|
interaction: { approval: caps.approvals === true, question: caps.questions === true, permissionMode: caps.permissionModes === true },
|
|
52
|
-
workspace: {
|
|
52
|
+
workspace: {
|
|
53
|
+
git: true,
|
|
54
|
+
worktree: true,
|
|
55
|
+
finalDiff: true,
|
|
56
|
+
nativeDiff: caps.nativeDiff === true,
|
|
57
|
+
nativePatch: caps.nativePatch === true,
|
|
58
|
+
},
|
|
53
59
|
session: { resume: caps.resume === true, fork: caps.fork === true, forkFromMessage: caps.fork === true && caps.forkFromMessage === true },
|
|
54
60
|
model: { selection: caps.models === true, thinkingLevel: caps.thinkingLevels === true },
|
|
55
61
|
usage: { tokens: caps.usage === true, context: caps.contextUsage === true, cost: caps.cost === true },
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
const object = (properties, required = []) => ({ type: 'object', properties, required, additionalProperties: false });
|
|
2
2
|
const taskId = { type: 'string', minLength: 1 };
|
|
3
3
|
const task = { type: 'string', minLength: 1, maxLength: 16000 };
|
|
4
|
+
const teamId = { type: 'string', minLength: 1 };
|
|
5
|
+
const memberId = { type: 'string', minLength: 1 };
|
|
4
6
|
const tools = [
|
|
5
7
|
{ name: 'list_agents', description: 'List native Harnesses available for delegation and their IDs.', inputSchema: object({}) },
|
|
6
|
-
{ name: 'delegate_to_agent', description: 'Start an independent native Harness session asynchronously. An @Agent mention explicitly assigns that agent its associated work. Include all necessary context. By default workers share the lead working directory, so later reviewers see earlier edits. Wait for development before starting dependent review; do not edit overlapping files concurrently. Use isolation=worktree for independent isolated experiments (auto also isolates Git projects). Returns task_id; collect results before finishing.', inputSchema: object({ agent_type: taskId, task, isolation: { type: 'string', enum: ['auto', 'worktree', 'shared'] } }, ['agent_type', 'task']) },
|
|
8
|
+
{ name: 'delegate_to_agent', description: 'Start an independent native Harness session asynchronously. For Agent Team work, pass team_id, member_id and team_task_id so the native session becomes that durable teammate and can use the shared task graph and mailbox. An @Agent mention explicitly assigns that agent its associated work. Include all necessary context. By default workers share the lead working directory, so later reviewers see earlier edits. Wait for development before starting dependent review; do not edit overlapping files concurrently. Use isolation=worktree for independent isolated experiments (auto also isolates Git projects). Returns task_id; collect results before finishing.', inputSchema: object({ agent_type: taskId, task, isolation: { type: 'string', enum: ['auto', 'worktree', 'shared'] }, team_id: teamId, member_id: memberId, team_task_id: taskId }, ['agent_type', 'task']) },
|
|
9
|
+
{ name: 'create_agent_team', description: 'Create a durable multi-Harness Agent Team with one lead and up to six named members with explicit roles. This creates shared team state, not worker sessions; assign tasks and delegate them to members next.', inputSchema: object({ name: { type: 'string', minLength: 1, maxLength: 120 }, goal: task, members: { type: 'array', minItems: 1, maxItems: 6, items: object({ name: { type: 'string', minLength: 1, maxLength: 80 }, role: { type: 'string', minLength: 1, maxLength: 240 }, agent_type: taskId }, ['name', 'role', 'agent_type']) } }, ['name', 'goal', 'members']) },
|
|
10
|
+
{ name: 'assign_team_task', description: 'Add a task to the shared team task graph. Dependencies must already exist. The assignee is a team member id or unique member name.', inputSchema: object({ team_id: teamId, title: { type: 'string', minLength: 1, maxLength: 160 }, description: task, assignee: memberId, depends_on: { type: 'array', maxItems: 16, items: taskId } }, ['team_id', 'title', 'description', 'assignee']) },
|
|
11
|
+
{ name: 'get_team_state', description: 'Read the current team roster, shared task graph and recent mailbox messages. Available to the lead and native teammate sessions. The projection includes a coarse phase (forming/running/waiting/completed), per-status progress counters, and each task\'s dependency depth so parallel lanes and pipeline stages are visible without walking depends_on by hand.', inputSchema: object({ team_id: teamId }, ['team_id']) },
|
|
12
|
+
{ name: 'update_team_task', description: 'Update task status/result in the shared graph. Teammates may update only tasks assigned to themselves; the lead may update any task.', inputSchema: object({ team_id: teamId, task_id: taskId, status: { type: 'string', enum: ['pending', 'blocked', 'in_progress', 'completed', 'failed'] }, result: { type: 'string', maxLength: 48000 } }, ['team_id', 'task_id', 'status']) },
|
|
13
|
+
{ name: 'send_team_message', description: 'Send a durable teammate-to-teammate mailbox message. Use a member id/name, lead, or * for broadcast. An idle native recipient is notified directly in its own session. Set kind to classify the hand-off: handoff (work passes to another member), review-request, or review-result; pair it with task_id so the recipient can locate the shared task. Defaults to text.', inputSchema: object({ team_id: teamId, to: { type: 'string', minLength: 1 }, message: task, task_id: taskId, kind: { type: 'string', enum: ['text', 'handoff', 'review-request', 'review-result'] } }, ['team_id', 'to', 'message']) },
|
|
7
14
|
{ name: 'update_agent_plan', description: 'Publish or update the lead collaboration plan shown in the conversation. Include development, review, fixes and final verification as appropriate. Mark steps completed only after collecting and verifying real results.', inputSchema: object({ steps: { type: 'array', minItems: 1, maxItems: 16, items: object({ text: task, status: { type: 'string', enum: ['pending', 'in_progress', 'completed'] } }, ['text', 'status']) } }, ['steps']) },
|
|
8
15
|
{ name: 'list_delegations', description: 'List this lead session\'s durable child tasks, including interrupted tasks from earlier Host runs.', inputSchema: object({}) },
|
|
9
16
|
{ name: 'resume_delegation', description: 'Continue an interrupted child in its recorded native session and workspace. Inspect existing progress; do not replay completed side effects.', inputSchema: object({ task_id: taskId }, ['task_id']) },
|
|
@@ -13,6 +13,32 @@ async function git(cwd, args, env = {}) {
|
|
|
13
13
|
})).stdout;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
async function inspectWorkspace(cwd, workspace = null) {
|
|
17
|
+
const result = {
|
|
18
|
+
hostManaged: true,
|
|
19
|
+
git: { available: false },
|
|
20
|
+
worktree: { available: false, active: workspace?.mode === 'worktree' },
|
|
21
|
+
finalDiff: { available: true, source: 'snapshot' },
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
const root = (await git(cwd, ['rev-parse', '--show-toplevel'])).trim();
|
|
25
|
+
const head = (await git(cwd, ['rev-parse', '--verify', 'HEAD'])).trim();
|
|
26
|
+
let branch = null;
|
|
27
|
+
try { branch = (await git(cwd, ['symbolic-ref', '--quiet', '--short', 'HEAD'])).trim() || null; } catch {}
|
|
28
|
+
const dirty = Boolean((await git(cwd, ['status', '--porcelain', '--untracked-files=normal'])).trim());
|
|
29
|
+
result.git = { available: true, root, head, branch, dirty };
|
|
30
|
+
result.worktree = {
|
|
31
|
+
available: true,
|
|
32
|
+
active: workspace?.mode === 'worktree',
|
|
33
|
+
...(workspace?.branch ? { branch: workspace.branch } : {}),
|
|
34
|
+
...(workspace?.root ? { root: workspace.root } : {}),
|
|
35
|
+
};
|
|
36
|
+
} catch (error) {
|
|
37
|
+
result.git.reason = /not a git repository/i.test(error.stderr || '') ? 'not-a-git-repository' : 'git-unavailable';
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
16
42
|
// A private index snapshots tracked and non-ignored files without staging the user's index.
|
|
17
43
|
async function tree(cwd) {
|
|
18
44
|
const index = path.join(os.tmpdir(), `harness-mix-index-${randomUUID()}`);
|
|
@@ -148,4 +174,4 @@ async function pushWorkspace(workspace, remote = 'origin', remoteBranch = worksp
|
|
|
148
174
|
return { remote, branch: remoteBranch, commit: newCommit, pushed: true };
|
|
149
175
|
}
|
|
150
176
|
|
|
151
|
-
module.exports = { createWorkspace, reviewWorkspace, applyWorkspace, removeWorkspace, discardWorkspace, pushWorkspace, git, tree };
|
|
177
|
+
module.exports = { createWorkspace, inspectWorkspace, reviewWorkspace, applyWorkspace, removeWorkspace, discardWorkspace, pushWorkspace, git, tree };
|
|
@@ -7,6 +7,50 @@ const { Store } = require('./store');
|
|
|
7
7
|
const { createWorkspace, reviewWorkspace, applyWorkspace, discardWorkspace, pushWorkspace } = require('./collaboration-worktree');
|
|
8
8
|
const validators = new Map(tools.map(tool => [tool.name, z.fromJSONSchema(tool.inputSchema)]));
|
|
9
9
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
10
|
+
const MAX_CONCURRENT_SUBTASKS = 6;
|
|
11
|
+
const MAX_SUBTASKS_PER_TURN = 16;
|
|
12
|
+
|
|
13
|
+
// Dependency depth of each task: the longest chain of prerequisites, used to
|
|
14
|
+
// lay the team board out in lanes.
|
|
15
|
+
// Missing dependency ids are ignored (the runtime already rejects unknown ids at
|
|
16
|
+
// assign time); the cycle back-edge returns 0 so corrupted graphs terminate.
|
|
17
|
+
function teamTaskDepths(tasks) {
|
|
18
|
+
const byId = new Map(tasks.map(task => [task.id, task]));
|
|
19
|
+
const depths = new Map();
|
|
20
|
+
const visiting = new Set();
|
|
21
|
+
const depthOf = id => {
|
|
22
|
+
if (depths.has(id)) return depths.get(id);
|
|
23
|
+
if (visiting.has(id)) return 0;
|
|
24
|
+
const task = byId.get(id);
|
|
25
|
+
if (!task) return 0;
|
|
26
|
+
visiting.add(id);
|
|
27
|
+
const deps = (task.dependsOn || []).filter(dep => byId.has(dep));
|
|
28
|
+
const depth = deps.length ? 1 + Math.max(...deps.map(depthOf)) : 0;
|
|
29
|
+
visiting.delete(id);
|
|
30
|
+
depths.set(id, depth);
|
|
31
|
+
return depth;
|
|
32
|
+
};
|
|
33
|
+
for (const task of tasks) depthOf(task.id);
|
|
34
|
+
return depths;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Coarse team phase: forming (no tasks yet),
|
|
38
|
+
// running (at least one task in flight), waiting (unfinished work but nothing
|
|
39
|
+
// running — the lead still has to delegate or unblock it), completed.
|
|
40
|
+
function teamPhase(team) {
|
|
41
|
+
if (!team.tasks.length) return 'forming';
|
|
42
|
+
if (team.tasks.every(task => task.status === 'completed')) return 'completed';
|
|
43
|
+
return team.tasks.some(task => task.status === 'in_progress') ? 'running' : 'waiting';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Per-status task counters plus completion percent, so a reader can reconstruct
|
|
47
|
+
// the workbench progress bar without walking the task list itself.
|
|
48
|
+
function teamProgress(tasks) {
|
|
49
|
+
const progress = { total: tasks.length, pending: 0, blocked: 0, in_progress: 0, completed: 0, failed: 0, interrupted: 0 };
|
|
50
|
+
for (const task of tasks) if (progress[task.status] !== undefined) progress[task.status] += 1;
|
|
51
|
+
progress.percent = progress.total ? Math.round((progress.completed / progress.total) * 100) : 0;
|
|
52
|
+
return progress;
|
|
53
|
+
}
|
|
10
54
|
|
|
11
55
|
function defaultWorkerPermissionMode(agent) {
|
|
12
56
|
switch (agent) {
|
|
@@ -33,15 +77,32 @@ class Collaboration {
|
|
|
33
77
|
this.cancelling = new Set();
|
|
34
78
|
this.closing = false;
|
|
35
79
|
this.store = new Store(path.join(runtime.store.directory, 'collaboration'));
|
|
80
|
+
this.teamStore = new Store(path.join(runtime.store.directory, 'collaboration'), 'teams.json');
|
|
81
|
+
this.teams = new Map();
|
|
36
82
|
}
|
|
37
83
|
|
|
38
84
|
async initialize() {
|
|
39
|
-
if (!this.loading) this.loading = this.store.load().then(async rows => {
|
|
85
|
+
if (!this.loading) this.loading = Promise.all([this.store.load(), this.teamStore.load()]).then(async ([rows, teams]) => {
|
|
40
86
|
for (const row of rows) {
|
|
41
87
|
if (!row.id || !row.owner || !row.agent) throw new Error('Invalid collaboration history');
|
|
42
88
|
this.jobs.set(row.id, { ...row, ...(row.status === 'running' ? { status: 'interrupted', error: 'Host restarted; resume this native session explicitly.' } : {}) });
|
|
43
89
|
}
|
|
90
|
+
for (const team of teams) {
|
|
91
|
+
if (!team.id || !team.owner || !Array.isArray(team.members) || !Array.isArray(team.tasks) || !Array.isArray(team.messages)) throw new Error('Invalid Agent Team history');
|
|
92
|
+
if (!Array.isArray(team.history)) team.history = [];
|
|
93
|
+
this.teams.set(team.id, team);
|
|
94
|
+
}
|
|
95
|
+
for (const job of this.jobs.values()) {
|
|
96
|
+
if (job.status !== 'interrupted' || !job.teamId) continue;
|
|
97
|
+
const team = this.teams.get(job.teamId);
|
|
98
|
+
const member = team?.members.find(entry => entry.id === job.memberId);
|
|
99
|
+
const task = team?.tasks.find(entry => entry.id === job.teamTaskId);
|
|
100
|
+
if (member) member.status = 'interrupted';
|
|
101
|
+
if (task?.status === 'in_progress') task.status = 'interrupted';
|
|
102
|
+
if (team) this.refreshTeamStatus(team);
|
|
103
|
+
}
|
|
44
104
|
await this.save();
|
|
105
|
+
await this.saveTeams();
|
|
45
106
|
});
|
|
46
107
|
return this.loading;
|
|
47
108
|
}
|
|
@@ -50,8 +111,92 @@ class Collaboration {
|
|
|
50
111
|
return this.store.save([...this.jobs.values()].map(({ done, cancelling, followupPending, applying, ...job }) => job));
|
|
51
112
|
}
|
|
52
113
|
|
|
114
|
+
saveTeams() { return this.teamStore.save([...this.teams.values()]); }
|
|
115
|
+
|
|
116
|
+
recordTeamSnapshot(team, action) {
|
|
117
|
+
if (!Array.isArray(team.history)) team.history = [];
|
|
118
|
+
team.history.push({ id: randomUUID(), action, at: Date.now(), team: this.teamView(team) });
|
|
119
|
+
if (team.history.length > 200) team.history.splice(0, team.history.length - 200);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async publishTeam(team, action) {
|
|
123
|
+
this.recordTeamSnapshot(team, action);
|
|
124
|
+
await this.saveTeams();
|
|
125
|
+
this.emitTeam(team, action);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async inspectTeam(threadId, teamId) {
|
|
129
|
+
await this.initialize();
|
|
130
|
+
const participant = this.participant(threadId, teamId);
|
|
131
|
+
if (!participant) {
|
|
132
|
+
// An explicit teamId is an ownership check: outsiders are denied. Without
|
|
133
|
+
// one the caller only asks "does this thread belong to a team?" — the
|
|
134
|
+
// renderer polls exactly that for every active thread, so answer benignly
|
|
135
|
+
// instead of failing an internal error on every poll.
|
|
136
|
+
if (teamId) throw new Error('Unknown team or caller is not a team participant');
|
|
137
|
+
return { team: null, snapshots: [] };
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
team: this.teamView(participant.team),
|
|
141
|
+
snapshots: (participant.team.history || []).map(snapshot => ({ ...snapshot, team: { ...snapshot.team } })),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
refreshTeamStatus(team) {
|
|
146
|
+
team.status = team.tasks.length && team.tasks.every(task => task.status === 'completed') ? 'completed' : 'active';
|
|
147
|
+
team.updatedAt = Date.now();
|
|
148
|
+
}
|
|
149
|
+
|
|
53
150
|
list(owner) { return [...this.jobs.values()].filter(j => !owner || j.owner === owner).map(j => this.view(j)); }
|
|
54
151
|
|
|
152
|
+
participant(threadId, teamId) {
|
|
153
|
+
for (const team of this.teams.values()) {
|
|
154
|
+
if (teamId && team.id !== teamId) continue;
|
|
155
|
+
if (team.owner === threadId) return { team, kind: 'lead', id: 'lead', name: 'Lead' };
|
|
156
|
+
const member = team.members.find(entry => entry.childId === threadId);
|
|
157
|
+
if (member) return { team, kind: 'member', id: member.id, name: member.name, member };
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
isTeamParticipantThread(threadId) { return !!this.participant(threadId); }
|
|
163
|
+
|
|
164
|
+
teamFor(principal, teamId) {
|
|
165
|
+
const participant = this.participant(principal, teamId);
|
|
166
|
+
if (!participant) throw new Error('Unknown team or caller is not a team participant');
|
|
167
|
+
return participant;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
resolveMember(team, value) {
|
|
171
|
+
const normalized = String(value).toLowerCase();
|
|
172
|
+
const matches = team.members.filter(member => member.id === value || member.name.toLowerCase() === normalized);
|
|
173
|
+
if (matches.length !== 1) throw new Error(matches.length ? 'Team member name is ambiguous; use member_id' : 'Unknown team member');
|
|
174
|
+
return matches[0];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
teamView(team) {
|
|
178
|
+
const leadThread = this.runtime.threads.find(thread => thread.id === team.owner);
|
|
179
|
+
const leadAgent = leadThread?.harnessId ?? 'codex';
|
|
180
|
+
const leadName = this.runtime.adapters.get(leadAgent)?.manifest?.name ?? leadAgent;
|
|
181
|
+
const depths = teamTaskDepths(team.tasks);
|
|
182
|
+
return {
|
|
183
|
+
team_id: team.id, name: team.name, goal: team.goal, status: team.status, lead_thread_id: team.owner,
|
|
184
|
+
phase: teamPhase(team), progress: teamProgress(team.tasks),
|
|
185
|
+
lead: { id: 'lead', name: 'Team Lead', role: `${leadName} · 协调与验收`, agent: leadAgent, display_status: this.runtime.execution.isRunning(team.owner) ? 'working' : 'ready' },
|
|
186
|
+
members: team.members.map(member => ({ ...member, display_status: member.childId && this.runtime.execution.isRunning(member.childId) ? 'working' : member.status })),
|
|
187
|
+
tasks: team.tasks.map(task => ({ ...task, depth: depths.get(task.id) ?? 0 })), messages: team.messages.slice(-40).map(message => ({ ...message })),
|
|
188
|
+
updated_at: team.updatedAt,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
emitTeam(team, action) {
|
|
193
|
+
if (!this.runtime.execution.isRunning(team.owner)) return;
|
|
194
|
+
this.runtime.emitCollaboration(team.owner, {
|
|
195
|
+
kind: 'tool', toolCallId: `agent-team:${team.id}`, title: `Agent Team · ${team.name}`,
|
|
196
|
+
state: 'running', input: team.goal, output: JSON.stringify({ action, ...this.teamView(team) }),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
55
200
|
async review(id) {
|
|
56
201
|
const job = this.jobs.get(id);
|
|
57
202
|
if (!job || job.status === 'running') throw new Error('子任务尚未完成');
|
|
@@ -126,24 +271,108 @@ class Collaboration {
|
|
|
126
271
|
return job;
|
|
127
272
|
}
|
|
128
273
|
|
|
274
|
+
async teamCall(principal, name, args) {
|
|
275
|
+
const rt = this.runtime;
|
|
276
|
+
if (name === 'create_agent_team') {
|
|
277
|
+
const lead = rt.threads.find(thread => thread.id === principal);
|
|
278
|
+
if (!lead || lead.parentThreadId) throw new Error('Only a lead task can create an Agent Team');
|
|
279
|
+
const names = new Set();
|
|
280
|
+
const members = args.members.map(entry => {
|
|
281
|
+
const agent = rt.resolveHarnessId(entry.agent_type);
|
|
282
|
+
if (!agent || !rt.status[agent]?.available) throw new Error(`Target Harness unavailable: ${entry.agent_type}`);
|
|
283
|
+
if (!rt.adapters.get(agent)?.manifest?.capabilities?.collaborationTools) throw new Error(`Harness cannot participate in Agent Team messaging: ${agent}`);
|
|
284
|
+
if (!lead.activeMentions?.includes(agent)) throw new Error(`Agent Team member ${entry.name} uses unselected Harness "${agent}"`);
|
|
285
|
+
const key = entry.name.trim().toLowerCase();
|
|
286
|
+
if (names.has(key)) throw new Error('Agent Team member names must be unique');
|
|
287
|
+
names.add(key);
|
|
288
|
+
return { id: randomUUID(), name: entry.name.trim(), role: entry.role.trim(), agent, status: 'ready' };
|
|
289
|
+
});
|
|
290
|
+
const team = { id: randomUUID(), owner: principal, name: args.name.trim(), goal: args.goal, status: 'active', members, tasks: [], messages: [], history: [], createdAt: Date.now(), updatedAt: Date.now() };
|
|
291
|
+
this.teams.set(team.id, team);
|
|
292
|
+
await this.publishTeam(team, 'team_created');
|
|
293
|
+
return this.teamView(team);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const participant = this.teamFor(principal, args.team_id);
|
|
297
|
+
const { team } = participant;
|
|
298
|
+
if (name === 'get_team_state') return this.teamView(team);
|
|
299
|
+
if (name === 'assign_team_task') {
|
|
300
|
+
if (participant.kind !== 'lead') throw new Error('Only the Team Lead can assign team tasks');
|
|
301
|
+
const assignee = this.resolveMember(team, args.assignee);
|
|
302
|
+
const dependencies = [...new Set(args.depends_on ?? [])];
|
|
303
|
+
if (dependencies.some(id => !team.tasks.some(task => task.id === id))) throw new Error('Unknown dependency task');
|
|
304
|
+
const taskEntry = { id: randomUUID(), title: args.title.trim(), description: args.description, assignee: assignee.id, dependsOn: dependencies, status: dependencies.length ? 'blocked' : 'pending', createdAt: Date.now(), updatedAt: Date.now() };
|
|
305
|
+
team.tasks.push(taskEntry);
|
|
306
|
+
this.refreshTeamStatus(team);
|
|
307
|
+
await this.publishTeam(team, 'task_assigned');
|
|
308
|
+
return { task: taskEntry, team: this.teamView(team) };
|
|
309
|
+
}
|
|
310
|
+
if (name === 'update_team_task') {
|
|
311
|
+
const taskEntry = team.tasks.find(task => task.id === args.task_id);
|
|
312
|
+
if (!taskEntry) throw new Error('Unknown team task');
|
|
313
|
+
if (participant.kind === 'member' && taskEntry.assignee !== participant.id) throw new Error('A teammate can update only its assigned tasks');
|
|
314
|
+
if (participant.kind === 'member' && !['in_progress', 'completed', 'failed'].includes(args.status)) throw new Error('A teammate can only start, complete, or fail its assigned task');
|
|
315
|
+
if (participant.kind === 'member' && taskEntry.status === 'completed') throw new Error('A completed task can only be reopened by the Team Lead');
|
|
316
|
+
if (['in_progress', 'completed'].includes(args.status) && taskEntry.dependsOn.some(id => team.tasks.find(task => task.id === id)?.status !== 'completed')) throw new Error('Task dependencies are not complete');
|
|
317
|
+
taskEntry.status = args.status;
|
|
318
|
+
taskEntry.updatedAt = Date.now();
|
|
319
|
+
if (args.result !== undefined) taskEntry.result = args.result;
|
|
320
|
+
for (const candidate of team.tasks) {
|
|
321
|
+
if (candidate.status === 'blocked' && candidate.dependsOn.every(id => team.tasks.find(task => task.id === id)?.status === 'completed')) candidate.status = 'pending';
|
|
322
|
+
}
|
|
323
|
+
this.refreshTeamStatus(team);
|
|
324
|
+
await this.publishTeam(team, 'task_updated');
|
|
325
|
+
return { task: { ...taskEntry }, team: this.teamView(team) };
|
|
326
|
+
}
|
|
327
|
+
if (name === 'send_team_message') {
|
|
328
|
+
const target = args.to === '*' || args.to.toLowerCase() === 'lead' ? args.to.toLowerCase() : this.resolveMember(team, args.to).id;
|
|
329
|
+
if (args.task_id && !team.tasks.some(task => task.id === args.task_id)) throw new Error('Unknown team task');
|
|
330
|
+
const message = { id: randomUUID(), from: participant.id, fromName: participant.name, to: target, kind: args.kind || 'text', body: args.message, ...(args.task_id ? { taskId: args.task_id } : {}), at: Date.now(), delivery: 'mailbox' };
|
|
331
|
+
team.messages.push(message);
|
|
332
|
+
if (team.messages.length > 200) team.messages.splice(0, team.messages.length - 200);
|
|
333
|
+
const recipients = target === '*' ? team.members.filter(member => member.id !== participant.id) : team.members.filter(member => member.id === target);
|
|
334
|
+
for (const recipient of recipients) {
|
|
335
|
+
if (!recipient.childId || rt.execution.isRunning(recipient.childId)) continue;
|
|
336
|
+
const envelope = `[Harness Mix Agent Team message]\nTeam: ${team.name} (${team.id})\nFrom: ${participant.name}\nType: ${message.kind}\n${args.task_id ? `Task: ${args.task_id}\n` : ''}Message: ${args.message}\n\nTreat this as teammate input. Inspect shared team state with get_team_state, coordinate through send_team_message, and update only your assigned tasks.`;
|
|
337
|
+
message.delivery = 'native_session';
|
|
338
|
+
const recipientJob = [...this.jobs.values()].reverse().find(job => job.teamId === team.id && job.memberId === recipient.id && job.childId === recipient.childId);
|
|
339
|
+
void rt.send(recipient.childId, envelope, { collaborationOf: team.owner, isolated: recipientJob?.workspace?.mode === 'worktree' }).catch(error => {
|
|
340
|
+
message.delivery = 'mailbox'; message.deliveryError = error.message; void this.saveTeams();
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
team.updatedAt = Date.now();
|
|
344
|
+
await this.publishTeam(team, 'message_sent');
|
|
345
|
+
return { message, team: this.teamView(team) };
|
|
346
|
+
}
|
|
347
|
+
throw new Error('Unknown Agent Team operation');
|
|
348
|
+
}
|
|
349
|
+
|
|
129
350
|
view(job) {
|
|
130
351
|
const pending = job.childId ? this.runtime.core.interactions?.pending(job.childId)?.[0] : null;
|
|
131
352
|
return { task_id: job.id, parent_thread_id: job.owner, child_thread_id: job.childId, agent_type: job.agent, status: job.status,
|
|
353
|
+
team_id: job.teamId, member_id: job.memberId, team_task_id: job.teamTaskId,
|
|
132
354
|
display_status: pending ? 'waiting_approval' : job.status, attention: pending ? { type: pending.type, title: pending.title, message: pending.message } : undefined,
|
|
133
355
|
task: job.task, workspace: job.workspace, applied: !!job.appliedDigest, result: job.result, error: job.error,
|
|
134
356
|
diff: job.diff, digest: job.digest, branch: job.workspace?.branch };
|
|
135
357
|
}
|
|
136
358
|
|
|
137
|
-
async call(
|
|
359
|
+
async call(principal, name, args) {
|
|
138
360
|
await this.initialize();
|
|
139
361
|
if (this.closing) throw new Error('Host is closing');
|
|
140
362
|
if (!validators.has(name)) throw new Error('Unknown collaboration tool');
|
|
141
363
|
args = validators.get(name).parse(args);
|
|
142
364
|
const rt = this.runtime;
|
|
365
|
+
const teamTools = new Set(['create_agent_team', 'assign_team_task', 'get_team_state', 'update_team_task', 'send_team_message']);
|
|
366
|
+
const participant = this.participant(principal, args.team_id);
|
|
367
|
+
const owner = participant?.team.owner ?? principal;
|
|
143
368
|
const parent = rt.threads.find(t => t.id === owner);
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
369
|
+
if (teamTools.has(name)) {
|
|
370
|
+
if (!rt.execution.isRunning(principal) || (principal === owner && this.cancelling.has(owner))) throw new Error('Collaboration turn is no longer active');
|
|
371
|
+
return this.teamCall(principal, name, args);
|
|
372
|
+
}
|
|
373
|
+
if (!parent || principal !== owner || parent.parentThreadId) throw new Error('Only lead tasks can delegate');
|
|
374
|
+
if (!rt.execution.isRunning(principal) || this.cancelling.has(owner)) throw new Error('Collaboration turn is no longer active');
|
|
375
|
+
if (name === 'list_agents') return [...rt.adapters.values()].map(a => ({ agent_type: a.manifest.id, name: a.manifest.name, available: !!rt.status[a.manifest.id]?.available, team_capable: !!a.manifest.capabilities?.collaborationTools }));
|
|
147
376
|
if (name === 'list_delegations') return this.list(owner);
|
|
148
377
|
if (name === 'update_agent_plan') {
|
|
149
378
|
rt.emitCollaboration(owner, { kind: 'plan', entries: args.steps });
|
|
@@ -154,18 +383,43 @@ class Collaboration {
|
|
|
154
383
|
if (!agent || !rt.status[agent]?.available) throw new Error('Target Harness unavailable');
|
|
155
384
|
// Server-side enforcement: multi-agent collaboration can ONLY start when the user explicitly selected/mentioned agents.
|
|
156
385
|
if (!parent.activeMentions || !parent.activeMentions.length) {
|
|
157
|
-
throw new Error('跨 Harness
|
|
386
|
+
throw new Error('跨 Harness 协作仅在用户显式选择 Agent,或在团队/委派语境中明确写出 Harness 名称时允许启动(例如 #pi #claude,或“用 Pi 开发、Claude 审查组成团队”)。用户本轮未显式委派,不能由大模型自行决定启动跨 Harness 协作。');
|
|
158
387
|
}
|
|
159
388
|
if (!parent.activeMentions.includes(agent)) {
|
|
160
389
|
throw new Error(`用户仅显式指定了 [${parent.activeMentions.join(', ')}],不能委派给未指定的 "${agent}"。请向用户确认是否需要委派给其他 Harness。`);
|
|
161
390
|
}
|
|
391
|
+
let team, member, teamTask, previousMemberJob;
|
|
392
|
+
const teamFields = [args.team_id, args.member_id, args.team_task_id].filter(Boolean).length;
|
|
393
|
+
if (teamFields && teamFields !== 3) throw new Error('Agent Team delegation requires team_id, member_id and team_task_id together');
|
|
394
|
+
if (teamFields) {
|
|
395
|
+
({ team } = this.teamFor(owner, args.team_id));
|
|
396
|
+
member = this.resolveMember(team, args.member_id);
|
|
397
|
+
teamTask = team.tasks.find(entry => entry.id === args.team_task_id);
|
|
398
|
+
if (!teamTask || teamTask.assignee !== member.id) throw new Error('Team task is not assigned to this member');
|
|
399
|
+
if (member.agent !== agent) throw new Error('Delegated Harness does not match the team member');
|
|
400
|
+
if (member.childId && !rt.threads.some(thread => thread.id === member.childId)) delete member.childId;
|
|
401
|
+
previousMemberJob = member.childId ? [...this.jobs.values()].reverse().find(job => job.teamId === team.id && job.memberId === member.id && job.childId === member.childId) : null;
|
|
402
|
+
if (teamTask.dependsOn.some(id => team.tasks.find(entry => entry.id === id)?.status !== 'completed')) throw new Error('Team task dependencies are not complete');
|
|
403
|
+
if (teamTask.status === 'completed') throw new Error('Team task is already completed');
|
|
404
|
+
if (teamTask.status === 'in_progress') throw new Error('Team task is already running');
|
|
405
|
+
if ([...this.jobs.values()].some(job => job.teamId === team.id && job.memberId === member.id && job.status === 'running')) throw new Error('Team member is already working on another task');
|
|
406
|
+
}
|
|
162
407
|
const jobs = [...this.jobs.values()].filter(j => j.owner === owner);
|
|
163
|
-
if (jobs.filter(j => j.status === 'running').length >=
|
|
164
|
-
if (jobs.filter(j => j.turnId === rt.execution.lastTurn(owner)?.id).length >=
|
|
165
|
-
const job = { id: randomUUID(), owner, agent, turnId: rt.execution.lastTurn(owner).id, status: 'running', task: args.task,
|
|
408
|
+
if (jobs.filter(j => j.status === 'running').length >= MAX_CONCURRENT_SUBTASKS) throw new Error('At most six concurrent subtasks; collect existing results first');
|
|
409
|
+
if (jobs.filter(j => j.turnId === rt.execution.lastTurn(owner)?.id).length >= MAX_SUBTASKS_PER_TURN) throw new Error('At most sixteen subtasks per lead turn');
|
|
410
|
+
const job = { id: randomUUID(), owner, agent, turnId: rt.execution.lastTurn(owner).id, status: 'running', task: args.task,
|
|
411
|
+
isolation: previousMemberJob?.isolation ?? args.isolation ?? 'shared',
|
|
412
|
+
...(previousMemberJob?.workspace ? { workspace: previousMemberJob.workspace } : {}),
|
|
413
|
+
...(team ? { teamId: team.id, memberId: member.id, teamTaskId: teamTask.id, ...(member.childId ? { childId: member.childId } : {}) } : {}) };
|
|
166
414
|
this.jobs.set(job.id, job);
|
|
415
|
+
if (team) {
|
|
416
|
+
teamTask.status = 'in_progress'; teamTask.jobId = job.id; teamTask.updatedAt = Date.now();
|
|
417
|
+
member.status = 'working'; this.refreshTeamStatus(team);
|
|
418
|
+
await this.publishTeam(team, 'task_started');
|
|
419
|
+
}
|
|
167
420
|
await this.save();
|
|
168
|
-
|
|
421
|
+
const teamPrompt = team ? `${args.task}\n\n[Harness Mix Agent Team]\nTeam: ${team.name} (${team.id})\nShared goal: ${team.goal}\nYou are ${member.name}. Role: ${member.role}\nAssigned task: ${teamTask.title} (${teamTask.id})\nYou are a persistent teammate, not a one-shot subagent. Read shared state with get_team_state, update your assigned task with update_team_task, and coordinate directly with teammates through send_team_message. Do not create or assign team members.` : args.task;
|
|
422
|
+
job.done = this.run(parent, job, teamPrompt);
|
|
169
423
|
return this.view(job);
|
|
170
424
|
}
|
|
171
425
|
if (name === 'get_delegation_status') {
|
|
@@ -185,11 +439,19 @@ class Collaboration {
|
|
|
185
439
|
job.followupPending = true;
|
|
186
440
|
try { await job.done; } finally { job.followupPending = false; }
|
|
187
441
|
if (this.closing || !rt.execution.isRunning(owner) || this.cancelling.has(owner)) throw new Error('Lead turn is no longer active');
|
|
188
|
-
if ([...this.jobs.values()].filter(j => j.owner === owner && j.status === 'running').length >=
|
|
442
|
+
if ([...this.jobs.values()].filter(j => j.owner === owner && j.status === 'running').length >= MAX_CONCURRENT_SUBTASKS) throw new Error('At most six concurrent subtasks');
|
|
189
443
|
job.status = 'running'; job.result = undefined; job.error = undefined;
|
|
190
444
|
job.turnId = rt.execution.lastTurn(owner).id;
|
|
191
445
|
const task = name === 'resume_delegation' ? `Continue the interrupted task in this existing workspace. Inspect existing progress before acting; do not repeat completed side effects. Original task:\n${job.task}` : args.task;
|
|
192
446
|
if (name !== 'resume_delegation') job.task = task;
|
|
447
|
+
if (job.teamId) {
|
|
448
|
+
const team = this.teams.get(job.teamId);
|
|
449
|
+
const member = team?.members.find(entry => entry.id === job.memberId);
|
|
450
|
+
const teamTask = team?.tasks.find(entry => entry.id === job.teamTaskId);
|
|
451
|
+
if (member) member.status = 'working';
|
|
452
|
+
if (teamTask) { teamTask.status = 'in_progress'; teamTask.updatedAt = Date.now(); }
|
|
453
|
+
if (team) { this.refreshTeamStatus(team); await this.publishTeam(team, name === 'resume_delegation' ? 'task_resumed' : 'task_followup'); }
|
|
454
|
+
}
|
|
193
455
|
await this.save();
|
|
194
456
|
job.done = this.run(parent, job, task);
|
|
195
457
|
return this.view(job);
|
|
@@ -213,7 +475,13 @@ class Collaboration {
|
|
|
213
475
|
const child = job.childId ? rt.threads.find(t => t.id === job.childId) : await rt.createThread({
|
|
214
476
|
harnessId: job.agent, cwd: job.workspace.cwd, title: `${parent.title} › ${task.slice(0, 40)}`, parentThreadId: parent.id,
|
|
215
477
|
options: { ...(workerPermMode ? { permissionMode: workerPermMode } : {}) },
|
|
216
|
-
onCreated: async thread => {
|
|
478
|
+
onCreated: async thread => {
|
|
479
|
+
job.childId = thread.id;
|
|
480
|
+
const team = job.teamId ? this.teams.get(job.teamId) : null;
|
|
481
|
+
const member = team?.members.find(entry => entry.id === job.memberId);
|
|
482
|
+
if (member) { member.childId = thread.id; member.status = 'working'; team.updatedAt = Date.now(); await this.publishTeam(team, 'member_session_ready'); }
|
|
483
|
+
await this.save();
|
|
484
|
+
}
|
|
217
485
|
});
|
|
218
486
|
if (!child) throw new Error('Native child history is missing; no replacement session was created');
|
|
219
487
|
job.childId = child.id;
|
|
@@ -248,6 +516,21 @@ class Collaboration {
|
|
|
248
516
|
const messages = rt.core.getItemsForTurn(turn.id).filter(i => i.type === 'agent_message');
|
|
249
517
|
const finals = messages.filter(i => i.phase === 'final');
|
|
250
518
|
job.result = (finals.length ? finals : messages).map(i => i.content || '').join('\n').slice(0, 48000);
|
|
519
|
+
if (job.teamId) {
|
|
520
|
+
const team = this.teams.get(job.teamId);
|
|
521
|
+
const member = team?.members.find(entry => entry.id === job.memberId);
|
|
522
|
+
const teamTask = team?.tasks.find(entry => entry.id === job.teamTaskId);
|
|
523
|
+
if (member) member.status = 'ready';
|
|
524
|
+
if (teamTask && teamTask.status === 'in_progress') {
|
|
525
|
+
teamTask.status = job.status === 'completed' ? 'completed' : job.status === 'cancelled' ? 'pending' : 'failed';
|
|
526
|
+
teamTask.result = job.result;
|
|
527
|
+
teamTask.updatedAt = Date.now();
|
|
528
|
+
for (const candidate of team.tasks) {
|
|
529
|
+
if (candidate.status === 'blocked' && candidate.dependsOn.every(id => team.tasks.find(entry => entry.id === id)?.status === 'completed')) candidate.status = 'pending';
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (team) { this.refreshTeamStatus(team); await this.publishTeam(team, 'task_settled'); }
|
|
533
|
+
}
|
|
251
534
|
if (job.workspace?.mode === 'worktree' && job.status === 'completed') {
|
|
252
535
|
try {
|
|
253
536
|
const rev = await reviewWorkspace(job.workspace);
|
|
@@ -255,7 +538,17 @@ class Collaboration {
|
|
|
255
538
|
job.digest = rev.digest;
|
|
256
539
|
} catch {}
|
|
257
540
|
}
|
|
258
|
-
} catch (error) {
|
|
541
|
+
} catch (error) {
|
|
542
|
+
if (job.status === 'running') { job.status = 'failed'; job.error = error.message; }
|
|
543
|
+
if (job.teamId) {
|
|
544
|
+
const team = this.teams.get(job.teamId);
|
|
545
|
+
const member = team?.members.find(entry => entry.id === job.memberId);
|
|
546
|
+
const teamTask = team?.tasks.find(entry => entry.id === job.teamTaskId);
|
|
547
|
+
if (member) member.status = 'ready';
|
|
548
|
+
if (teamTask) { teamTask.status = 'failed'; teamTask.result = error.message; teamTask.updatedAt = Date.now(); }
|
|
549
|
+
if (team) { this.refreshTeamStatus(team); await this.publishTeam(team, 'task_failed'); }
|
|
550
|
+
}
|
|
551
|
+
}
|
|
259
552
|
finally { await this.save(); emit({ kind: 'tool', toolCallId, state: job.status === 'completed' ? 'done' : 'error', output: JSON.stringify(this.view(job)) }); }
|
|
260
553
|
}
|
|
261
554
|
|
|
@@ -270,7 +563,18 @@ class Collaboration {
|
|
|
270
563
|
new Promise(r => setTimeout(r, 3_000)),
|
|
271
564
|
]).catch(() => {});
|
|
272
565
|
}
|
|
273
|
-
} finally {
|
|
566
|
+
} finally {
|
|
567
|
+
job.cancelling = false;
|
|
568
|
+
if (job.teamId) {
|
|
569
|
+
const team = this.teams.get(job.teamId);
|
|
570
|
+
const member = team?.members.find(entry => entry.id === job.memberId);
|
|
571
|
+
const teamTask = team?.tasks.find(entry => entry.id === job.teamTaskId);
|
|
572
|
+
if (member) member.status = this.closing ? 'interrupted' : 'ready';
|
|
573
|
+
if (teamTask?.status === 'in_progress') { teamTask.status = this.closing ? 'interrupted' : 'pending'; teamTask.updatedAt = Date.now(); }
|
|
574
|
+
if (team) { this.refreshTeamStatus(team); await this.publishTeam(team, this.closing ? 'task_interrupted' : 'task_cancelled'); }
|
|
575
|
+
}
|
|
576
|
+
await this.save();
|
|
577
|
+
}
|
|
274
578
|
}
|
|
275
579
|
|
|
276
580
|
async cancelOwner(owner) {
|
|
@@ -292,6 +596,7 @@ class Collaboration {
|
|
|
292
596
|
await Promise.all([...this.jobs.values()].map(j => j.done));
|
|
293
597
|
this.keys.clear();
|
|
294
598
|
await this.save();
|
|
599
|
+
await this.saveTeams();
|
|
295
600
|
if (this.server) await new Promise(resolve => this.server.close(resolve));
|
|
296
601
|
}
|
|
297
602
|
}
|
|
@@ -306,7 +611,35 @@ function mentionedAgents(text, runtime) {
|
|
|
306
611
|
const id = runtime.resolveHarnessId(match[1] || match[2]);
|
|
307
612
|
if (id) ids.add(id);
|
|
308
613
|
}
|
|
614
|
+
// Natural-language team requests may name Harnesses without using the picker.
|
|
615
|
+
// Require an explicit collaboration intent so ordinary product discussion such
|
|
616
|
+
// as "Codex UI" does not silently authorize cross-Harness delegation.
|
|
617
|
+
const teamIntent = /agent\s*team|团队|组队|协作|委派|调度|分工|成员|队长|主导者|\b(?:team|delegate|delegation|assign|member|teammate)\b/i.test(prose);
|
|
618
|
+
if (teamIntent) {
|
|
619
|
+
for (const adapter of runtime.adapters.values()) {
|
|
620
|
+
const labels = [adapter.manifest.id, adapter.manifest.name, ...(adapter.manifest.aliases ?? [])]
|
|
621
|
+
.filter(label => String(label ?? '').trim().length >= 2)
|
|
622
|
+
.sort((a, b) => String(b).length - String(a).length);
|
|
623
|
+
if (labels.some(label => containsPlainAgentName(prose, label))) ids.add(adapter.manifest.id);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
309
626
|
return [...ids];
|
|
310
627
|
}
|
|
311
628
|
|
|
312
|
-
|
|
629
|
+
function containsPlainAgentName(text, label) {
|
|
630
|
+
const haystack = String(text).toLowerCase();
|
|
631
|
+
const needle = String(label).trim().toLowerCase();
|
|
632
|
+
for (let offset = haystack.indexOf(needle); offset !== -1; offset = haystack.indexOf(needle, offset + 1)) {
|
|
633
|
+
const before = offset === 0 ? '' : haystack[offset - 1];
|
|
634
|
+
const afterIndex = offset + needle.length;
|
|
635
|
+
const after = afterIndex === haystack.length ? '' : haystack[afterIndex];
|
|
636
|
+
if (plainNameBoundary(before) && plainNameBoundary(after)) return true;
|
|
637
|
+
}
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function plainNameBoundary(char) {
|
|
642
|
+
return !char || /[\s\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff,;:!?()[\]{}"',。;:、!?“”‘’]/u.test(char);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
module.exports = { Collaboration, mentionedAgents, defaultWorkerPermissionMode, teamTaskDepths, teamPhase, teamProgress };
|