@team-agent/installer 0.5.61 → 0.5.63
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/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +5 -2
- package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
- package/crates/team-agent/src/cli/diagnose.rs +1 -0
- package/crates/team-agent/src/cli/emit.rs +41 -7
- package/crates/team-agent/src/cli/helpers.rs +1 -0
- package/crates/team-agent/src/cli/leader.rs +6 -1
- package/crates/team-agent/src/cli/leaders.rs +2 -0
- package/crates/team-agent/src/cli/mod.rs +33 -3
- package/crates/team-agent/src/cli/named_address.rs +8 -1
- package/crates/team-agent/src/cli/profile.rs +2 -1
- package/crates/team-agent/src/cli/send/coordinator.rs +1 -0
- package/crates/team-agent/src/cli/send/mailbox.rs +3 -0
- package/crates/team-agent/src/cli/send/persist.rs +1 -0
- package/crates/team-agent/src/cli/send/presentation.rs +1 -0
- package/crates/team-agent/src/cli/send/resolve.rs +206 -1
- package/crates/team-agent/src/cli/send.rs +2 -1
- package/crates/team-agent/src/cli/spec.rs +1 -0
- package/crates/team-agent/src/cli/status.rs +2 -1
- package/crates/team-agent/src/cli/status_port/compact.rs +1 -0
- package/crates/team-agent/src/cli/status_port/format.rs +1 -0
- package/crates/team-agent/src/cli/status_port/inbox.rs +1 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
- package/crates/team-agent/src/cli/types.rs +3 -2
- package/crates/team-agent/src/communication_mode/mod.rs +1 -0
- package/crates/team-agent/src/compiler.rs +1 -0
- package/crates/team-agent/src/conpty/backend.rs +1 -0
- package/crates/team-agent/src/conpty/mod.rs +1 -0
- package/crates/team-agent/src/coordinator/backoff.rs +5 -8
- package/crates/team-agent/src/coordinator/conpty_shim.rs +3 -2
- package/crates/team-agent/src/coordinator/health.rs +1 -0
- package/crates/team-agent/src/coordinator/mod.rs +2 -2
- package/crates/team-agent/src/coordinator/orphan.rs +4 -116
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +15 -866
- package/crates/team-agent/src/coordinator/steps/delivery.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +2 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +1 -0
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +0 -174
- package/crates/team-agent/src/coordinator/tests/api_error_recovery.rs +139 -375
- package/crates/team-agent/src/coordinator/tests/basics.rs +0 -184
- package/crates/team-agent/src/coordinator/tests/mod.rs +2 -17
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -49
- package/crates/team-agent/src/coordinator/tick.rs +23 -40
- package/crates/team-agent/src/coordinator/types.rs +3 -176
- package/crates/team-agent/src/db/agent_health_capture.rs +1 -0
- package/crates/team-agent/src/db/message_store.rs +2 -1
- package/crates/team-agent/src/db/migration.rs +5 -4
- package/crates/team-agent/src/db/mod.rs +1 -0
- package/crates/team-agent/src/db/schema.rs +1 -0
- package/crates/team-agent/src/diagnose/comms.rs +2 -1
- package/crates/team-agent/src/diagnose/mod.rs +1 -0
- package/crates/team-agent/src/diagnose/orphans.rs +1 -0
- package/crates/team-agent/src/layout/manager.rs +3 -7
- package/crates/team-agent/src/layout/mod.rs +2 -2
- package/crates/team-agent/src/layout/overlay.rs +6 -1
- package/crates/team-agent/src/layout/placement.rs +1 -0
- package/crates/team-agent/src/layout/recovery.rs +3 -0
- package/crates/team-agent/src/layout/runtime_sessions.rs +8 -3
- package/crates/team-agent/src/layout/sessions.rs +8 -1
- package/crates/team-agent/src/layout/tmux_endpoint.rs +3 -0
- package/crates/team-agent/src/layout/worker_env.rs +3 -0
- package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
- package/crates/team-agent/src/leader/helpers.rs +1 -0
- package/crates/team-agent/src/leader/incident.rs +1 -0
- package/crates/team-agent/src/leader/lease.rs +27 -22
- package/crates/team-agent/src/leader/mod.rs +2 -3
- package/crates/team-agent/src/leader/owner_bind.rs +13 -3
- package/crates/team-agent/src/leader/provider_attribution.rs +1 -0
- package/crates/team-agent/src/leader/rediscover.rs +4 -17
- package/crates/team-agent/src/leader/registry.rs +3 -15
- package/crates/team-agent/src/leader/start.rs +373 -125
- package/crates/team-agent/src/leader/takeover.rs +10 -232
- package/crates/team-agent/src/leader/tests/claim_leader_mailbox_flush_contract.rs +544 -0
- package/crates/team-agent/src/leader/tests/historical_inventory_upgrade_gate_red.rs +624 -0
- package/crates/team-agent/src/leader/tests/identity.rs +7 -2
- package/crates/team-agent/src/leader/tests/idle.rs +0 -193
- package/crates/team-agent/src/leader/tests/lease_api.rs +0 -45
- package/crates/team-agent/src/leader/tests/mod.rs +4 -41
- package/crates/team-agent/src/leader/tests/rehomed_env.rs +83 -0
- package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1273 -0
- package/crates/team-agent/src/leader/tests/terminal_and_replay_vs_rerender_invariants_red.rs +417 -0
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +22 -103
- package/crates/team-agent/src/leader/types.rs +1 -28
- package/crates/team-agent/src/lib.rs +3 -0
- package/crates/team-agent/src/lifecycle/display.rs +7 -1
- package/crates/team-agent/src/lifecycle/helpers.rs +3 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +4 -26
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -27
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -27
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +4 -32
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch.rs +11 -12
- package/crates/team-agent/src/lifecycle/mod.rs +2 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +3 -66
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +2 -2
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -5
- package/crates/team-agent/src/lifecycle/restart/remove.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/selection.rs +3 -3
- package/crates/team-agent/src/lifecycle/restart.rs +13 -15
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1007 -0
- package/crates/team-agent/src/lifecycle/tests/b0_legacy_snapshot_nonauthority_contract.rs +265 -0
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +459 -0
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +849 -0
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +323 -0
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +423 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +737 -0
- package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +1187 -0
- package/crates/team-agent/src/lifecycle/tests/codex_weak_window_attribution_red.rs +683 -0
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +394 -0
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +1856 -0
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +917 -0
- package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +481 -0
- package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +301 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +516 -0
- package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +986 -0
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +651 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -0
- package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +332 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +846 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_residual_g1_g4_red.rs +648 -0
- package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +910 -0
- package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +630 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1083 -0
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +1677 -0
- package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +410 -0
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +428 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1032 -0
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +869 -0
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1249 -0
- package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +342 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +542 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1103 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1276 -0
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_roster_red.rs +145 -0
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +603 -0
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +837 -0
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +928 -0
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +951 -0
- package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +327 -0
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +760 -0
- package/crates/team-agent/src/lifecycle/tests.rs +59 -0
- package/crates/team-agent/src/lifecycle/types.rs +0 -9
- package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -0
- package/crates/team-agent/src/mcp_server/mod.rs +1 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +4 -3
- package/crates/team-agent/src/mcp_server/tools.rs +1 -0
- package/crates/team-agent/src/mcp_server/types.rs +1 -18
- package/crates/team-agent/src/mcp_server/wire.rs +5 -5
- package/crates/team-agent/src/messaging/activity.rs +1 -0
- package/crates/team-agent/src/messaging/address.rs +1 -0
- package/crates/team-agent/src/messaging/delivery.rs +4 -3
- package/crates/team-agent/src/messaging/helpers.rs +2 -1
- package/crates/team-agent/src/messaging/leader_channel.rs +1 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +2 -1
- package/crates/team-agent/src/messaging/mod.rs +10 -14
- package/crates/team-agent/src/messaging/peers.rs +2 -0
- package/crates/team-agent/src/messaging/persist.rs +3 -1
- package/crates/team-agent/src/messaging/presentation.rs +2 -1
- package/crates/team-agent/src/messaging/results.rs +1 -0
- package/crates/team-agent/src/messaging/scheduler.rs +1 -0
- package/crates/team-agent/src/messaging/selftest.rs +1 -1
- package/crates/team-agent/src/messaging/send.rs +41 -8
- package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
- package/crates/team-agent/src/messaging/types.rs +2 -2
- package/crates/team-agent/src/messaging/watchers.rs +19 -13
- package/crates/team-agent/src/model/enums.rs +1 -0
- package/crates/team-agent/src/model/errors.rs +1 -0
- package/crates/team-agent/src/model/ids.rs +1 -0
- package/crates/team-agent/src/model/mod.rs +1 -0
- package/crates/team-agent/src/model/name_similarity.rs +1 -0
- package/crates/team-agent/src/model/pane_authority_refusal.rs +1 -0
- package/crates/team-agent/src/model/paths.rs +1 -0
- package/crates/team-agent/src/model/permissions.rs +3 -2
- package/crates/team-agent/src/model/routing.rs +1 -0
- package/crates/team-agent/src/model/spec.rs +1 -0
- package/crates/team-agent/src/model/task_graph.rs +1 -0
- package/crates/team-agent/src/model/yaml/tests.rs +1 -0
- package/crates/team-agent/src/model/yaml.rs +1 -0
- package/crates/team-agent/src/packaging/install.rs +9 -147
- package/crates/team-agent/src/packaging/migrate.rs +2 -0
- package/crates/team-agent/src/packaging/mod.rs +1 -0
- package/crates/team-agent/src/packaging/repair.rs +2 -0
- package/crates/team-agent/src/packaging/tests.rs +29 -218
- package/crates/team-agent/src/packaging/types.rs +8 -15
- package/crates/team-agent/src/platform/argv.rs +4 -0
- package/crates/team-agent/src/platform/errors.rs +10 -1
- package/crates/team-agent/src/platform/file_lock.rs +4 -157
- package/crates/team-agent/src/platform/mod.rs +0 -61
- package/crates/team-agent/src/platform/process.rs +1 -0
- package/crates/team-agent/src/provider/adapter.rs +1 -0
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -0
- package/crates/team-agent/src/provider/adapters/claude_fork.rs +1 -0
- package/crates/team-agent/src/provider/adapters/codex.rs +1 -0
- package/crates/team-agent/src/provider/adapters/copilot.rs +1 -0
- package/crates/team-agent/src/provider/adapters/copilot_fork.rs +1 -0
- package/crates/team-agent/src/provider/adapters/fake.rs +1 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
- package/crates/team-agent/src/provider/approvals/mod.rs +1 -0
- package/crates/team-agent/src/provider/approvals/parsing.rs +2 -5
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +6 -5
- package/crates/team-agent/src/provider/classify.rs +1 -0
- package/crates/team-agent/src/provider/faults.rs +1 -26
- package/crates/team-agent/src/provider/helpers.rs +1 -0
- package/crates/team-agent/src/provider/mod.rs +1 -1
- package/crates/team-agent/src/provider/session/capture.rs +1 -4
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork.rs +1 -0
- package/crates/team-agent/src/provider/session/mod.rs +2 -4
- package/crates/team-agent/src/provider/session/resume.rs +2 -209
- package/crates/team-agent/src/provider/session_scan/claude.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
- package/crates/team-agent/src/provider/session_scan.rs +1 -42
- package/crates/team-agent/src/provider/startup_prompt.rs +1 -0
- package/crates/team-agent/src/provider/types.rs +1 -0
- package/crates/team-agent/src/provider/wire.rs +1 -0
- package/crates/team-agent/src/state/identity.rs +16 -5
- package/crates/team-agent/src/state/identity_keys.rs +1 -0
- package/crates/team-agent/src/state/mod.rs +2 -0
- package/crates/team-agent/src/state/owner_gate.rs +5 -0
- package/crates/team-agent/src/state/ownership.rs +10 -4
- package/crates/team-agent/src/state/paths.rs +12 -0
- package/crates/team-agent/src/state/persist.rs +9 -3
- package/crates/team-agent/src/state/projection.rs +15 -2
- package/crates/team-agent/src/state/repository/intent.rs +1 -0
- package/crates/team-agent/src/state/repository/tests.rs +1 -0
- package/crates/team-agent/src/state/repository.rs +7 -13
- package/crates/team-agent/src/state/selector.rs +1 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +39 -0
- package/crates/team-agent/src/tmux_backend.rs +63 -0
- package/crates/team-agent/src/transport.rs +15 -0
- package/package.json +4 -4
- package/crates/team-agent/src/leader/inject.rs +0 -33
- package/crates/team-agent/src/provider/command.rs +0 -80
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! §19/§3 移植 `permissions.py`:role/provider/alias → 规范化权限矩阵。
|
|
2
3
|
//!
|
|
3
4
|
//! Python 全程裸 `str` + `dict[str, Any]`(`expand_tools`/`resolve_permissions`/
|
|
@@ -56,7 +57,7 @@ impl Tool {
|
|
|
56
57
|
///
|
|
57
58
|
/// 注:返回**声明序集合**;Python 返回的是 role 字面顺序的 list,但下游 `resolve_*`
|
|
58
59
|
/// 立即过 `expand_tools`(去重+排序),顺序不参与对拍,故此处用 `BTreeSet` 即可。
|
|
59
|
-
|
|
60
|
+
fn default_tools_for_role(role: &str) -> BTreeSet<Tool> {
|
|
60
61
|
use Tool::*;
|
|
61
62
|
let tools: &[Tool] = match role {
|
|
62
63
|
"leader" | "supervisor" => &[FsRead, FsList, McpTeam, ProviderBuiltin],
|
|
@@ -175,7 +176,7 @@ pub fn is_canonical_tool(name: &str) -> bool {
|
|
|
175
176
|
///
|
|
176
177
|
/// 表里没有的 provider(如 `claude`)= Python 的 `.get(provider, {})` → 空表 →
|
|
177
178
|
/// 每个 tool 再 `.get(tool, "prompt_only")` → 全 `prompt_only`(对拍 `R-unknownprov`)。
|
|
178
|
-
|
|
179
|
+
fn provider_enforcement(provider: Provider, tool: Tool) -> Enforcement {
|
|
179
180
|
use Enforcement::{Hard, PromptOnly};
|
|
180
181
|
use Tool::*;
|
|
181
182
|
match provider {
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
//!
|
|
2
|
-
//!
|
|
1
|
+
//!
|
|
2
|
+
//! installer 行为入口:`install` / `install_skill` / `diagnose_path`
|
|
3
|
+
//! + 文件系统副作用 helper(copytree / stale diff)。
|
|
3
4
|
|
|
4
5
|
use std::path::{Path, PathBuf};
|
|
5
6
|
|
|
6
7
|
use super::types::{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
SkillTarget, UninstallOptions, UninstallOutcome, Version,
|
|
8
|
+
BinDir, DoctorStatus, InstallOptions, InstallReport, PackagingError, PathDiagnostic, PathHint,
|
|
9
|
+
Prefix, SkillDestDir, SkillInstallOptions, SkillInstallOutcome, SkillTarget, Version,
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
///
|
|
12
13
|
/// installer `install`(首装,`install.mjs:48`)。写 bin/wrapper + 装 skill(`--target all`)+ 跑 doctor。
|
|
13
14
|
/// **首装无二进制替换**(`InstallReport.replace == None`)。
|
|
14
15
|
/// // REAL-MACHINE-E2E:全副作用(写 bin / 拷 skill / 探 PATH / 跑 doctor)只能真机/容器 clean-install 验。
|
|
@@ -41,66 +42,7 @@ pub fn install(opts: &InstallOptions) -> Result<InstallReport, PackagingError> {
|
|
|
41
42
|
})
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
///
|
|
45
|
-
/// **有二进制替换**(`InstallReport.replace == Some(..)`);失败回滚到 `.previous`(bug-084 同源)。
|
|
46
|
-
/// // REAL-MACHINE-E2E:原子替换 / 跨卷 fallback / rollback 只能真机/容器验。
|
|
47
|
-
pub fn update(opts: &InstallOptions) -> Result<InstallReport, PackagingError> {
|
|
48
|
-
let mut report = install(opts)?;
|
|
49
|
-
report.replace = Some(atomic_replace_binary(
|
|
50
|
-
&opts.self_binary,
|
|
51
|
-
&report.installed_bin,
|
|
52
|
-
)?);
|
|
53
|
-
Ok(report)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/// installer `uninstall`(`install.mjs:109`)。删 bin/wrapper + skill;默认保留 runtime/workspace。
|
|
57
|
-
/// `purge_runtime=true` 且检测无 team 在跑才真 purge,否则 `purge_refused_team_running=true`。
|
|
58
|
-
/// // REAL-MACHINE-E2E:真删 + team-running 判定(经 state 投影)需真机/容器验。
|
|
59
|
-
pub fn uninstall(opts: &UninstallOptions) -> Result<UninstallOutcome, PackagingError> {
|
|
60
|
-
let mut removed_bins = Vec::new();
|
|
61
|
-
for name in ["team-agent", "codex-team-agent", "claude-team-agent"] {
|
|
62
|
-
let path = opts.prefix.0.join("bin").join(name);
|
|
63
|
-
if path.exists() {
|
|
64
|
-
std::fs::remove_file(&path)?;
|
|
65
|
-
removed_bins.push(path);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let home = home_dir();
|
|
70
|
-
let mut removed_skill_dirs = Vec::new();
|
|
71
|
-
for target in SkillTarget::SINGLE_TARGETS {
|
|
72
|
-
if let Some(dest) = target.dest_dir(&home) {
|
|
73
|
-
if dest.0.exists() {
|
|
74
|
-
std::fs::remove_dir_all(&dest.0)?;
|
|
75
|
-
removed_skill_dirs.push(dest);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let mut purged_runtime = false;
|
|
81
|
-
let mut purge_refused_team_running = false;
|
|
82
|
-
if opts.purge_runtime {
|
|
83
|
-
if let Some(workspace) = &opts.workspace {
|
|
84
|
-
if workspace_has_running_team(workspace)? {
|
|
85
|
-
purge_refused_team_running = true;
|
|
86
|
-
} else {
|
|
87
|
-
let team_dir = workspace.join(".team");
|
|
88
|
-
if team_dir.exists() {
|
|
89
|
-
std::fs::remove_dir_all(&team_dir)?;
|
|
90
|
-
}
|
|
91
|
-
purged_runtime = true;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
Ok(UninstallOutcome {
|
|
97
|
-
removed_bins,
|
|
98
|
-
removed_skill_dirs,
|
|
99
|
-
purged_runtime,
|
|
100
|
-
purge_refused_team_running,
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
|
|
45
|
+
///
|
|
104
46
|
/// `team-agent install-skill`(`commands.py:451`)。repo `skills/team-agent/` → `~/.codex|.claude`。
|
|
105
47
|
/// `--target all` fan-out 两者;`--dest` 不能与 `--target all` 组合(`commands.py:453` → Err)。
|
|
106
48
|
/// 拷前清陈旧残留(修 `dirs_exist_ok` 残留);`--dry-run` 只报告不落地。
|
|
@@ -161,9 +103,10 @@ pub fn install_skill(
|
|
|
161
103
|
Ok(out)
|
|
162
104
|
}
|
|
163
105
|
|
|
106
|
+
///
|
|
164
107
|
/// 「bin 不在 PATH」诊断(`bincheck.mjs` 等价;下载即跑也要提示 PATH/可执行位)。
|
|
165
108
|
/// 纯诊断(无副作用,可单测除真探 PATH 外的逻辑)。
|
|
166
|
-
|
|
109
|
+
fn diagnose_path(bin_dir: &BinDir) -> Result<PathHint, PackagingError> {
|
|
167
110
|
let path_var = std::env::var("PATH").unwrap_or_default();
|
|
168
111
|
let entries: Vec<PathBuf> = path_var
|
|
169
112
|
.split(':')
|
|
@@ -212,69 +155,6 @@ fn default_skill_source() -> PathBuf {
|
|
|
212
155
|
.join("team-agent")
|
|
213
156
|
}
|
|
214
157
|
|
|
215
|
-
fn atomic_replace_binary(
|
|
216
|
-
source: &Path,
|
|
217
|
-
dest: &Path,
|
|
218
|
-
) -> Result<AtomicReplaceOutcome, PackagingError> {
|
|
219
|
-
if !source.exists() {
|
|
220
|
-
return Err(PackagingError::Io(std::io::Error::new(
|
|
221
|
-
std::io::ErrorKind::NotFound,
|
|
222
|
-
format!("self binary not found: {}", source.display()),
|
|
223
|
-
)));
|
|
224
|
-
}
|
|
225
|
-
let Some(parent) = dest.parent() else {
|
|
226
|
-
return Err(PackagingError::InvalidOptions(format!(
|
|
227
|
-
"binary destination has no parent: {}",
|
|
228
|
-
dest.display()
|
|
229
|
-
)));
|
|
230
|
-
};
|
|
231
|
-
std::fs::create_dir_all(parent)?;
|
|
232
|
-
|
|
233
|
-
let backup = dest.with_extension("previous");
|
|
234
|
-
let tmp = dest.with_extension(format!("tmp-{}", std::process::id()));
|
|
235
|
-
if tmp.exists() {
|
|
236
|
-
remove_path(&tmp)?;
|
|
237
|
-
}
|
|
238
|
-
std::fs::copy(source, &tmp)?;
|
|
239
|
-
|
|
240
|
-
if backup.exists() {
|
|
241
|
-
remove_path(&backup)?;
|
|
242
|
-
}
|
|
243
|
-
if dest.exists() {
|
|
244
|
-
std::fs::rename(dest, &backup)?;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
match std::fs::rename(&tmp, dest) {
|
|
248
|
-
Ok(()) => Ok(AtomicReplaceOutcome::Replaced { backup }),
|
|
249
|
-
Err(err) => {
|
|
250
|
-
let rollback = if backup.exists() {
|
|
251
|
-
std::fs::rename(&backup, dest)
|
|
252
|
-
} else {
|
|
253
|
-
Ok(())
|
|
254
|
-
};
|
|
255
|
-
let _ = remove_path(&tmp);
|
|
256
|
-
match rollback {
|
|
257
|
-
Ok(()) => Ok(AtomicReplaceOutcome::RolledBack {
|
|
258
|
-
restored_from: backup,
|
|
259
|
-
error: err.to_string(),
|
|
260
|
-
}),
|
|
261
|
-
Err(rollback_err) => Err(PackagingError::ReplaceFailed(format!(
|
|
262
|
-
"replace failed: {err}; rollback failed: {rollback_err}"
|
|
263
|
-
))),
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
fn remove_path(path: &Path) -> Result<(), PackagingError> {
|
|
270
|
-
if path.is_dir() {
|
|
271
|
-
std::fs::remove_dir_all(path)?;
|
|
272
|
-
} else if path.exists() {
|
|
273
|
-
std::fs::remove_file(path)?;
|
|
274
|
-
}
|
|
275
|
-
Ok(())
|
|
276
|
-
}
|
|
277
|
-
|
|
278
158
|
fn collect_files(path: &Path) -> Result<Vec<PathBuf>, PackagingError> {
|
|
279
159
|
let mut out = Vec::new();
|
|
280
160
|
if !path.exists() {
|
|
@@ -337,21 +217,3 @@ fn copy_tree_inner(source: &Path, dest: &Path) -> Result<(), PackagingError> {
|
|
|
337
217
|
}
|
|
338
218
|
Ok(())
|
|
339
219
|
}
|
|
340
|
-
|
|
341
|
-
fn workspace_has_running_team(workspace: &Path) -> Result<bool, PackagingError> {
|
|
342
|
-
let path = workspace.join(".team").join("state.json");
|
|
343
|
-
if !path.exists() {
|
|
344
|
-
return Ok(false);
|
|
345
|
-
}
|
|
346
|
-
let text = std::fs::read_to_string(path)?;
|
|
347
|
-
let value: serde_json::Value = serde_json::from_str(&text)
|
|
348
|
-
.map_err(|e| PackagingError::State(format!("state parse failed: {e}")))?;
|
|
349
|
-
let Some(teams) = value.get("teams").and_then(serde_json::Value::as_object) else {
|
|
350
|
-
return Ok(false);
|
|
351
|
-
};
|
|
352
|
-
Ok(teams.values().any(|team| {
|
|
353
|
-
team.get("status")
|
|
354
|
-
.and_then(serde_json::Value::as_str)
|
|
355
|
-
.is_some_and(|s| s.eq_ignore_ascii_case("running"))
|
|
356
|
-
}))
|
|
357
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! `doctor` 自检入口:把 step 3 schema_diagnosis +(placeholder)step 11/12 gate 结论归一成
|
|
2
3
|
//! typed [`DoctorStatus`]。
|
|
3
4
|
|
|
@@ -6,6 +7,7 @@ use super::types::{
|
|
|
6
7
|
};
|
|
7
8
|
use crate::db::migration::schema_diagnosis_workspace;
|
|
8
9
|
|
|
10
|
+
///
|
|
9
11
|
/// `team-agent doctor`(`commands.py:218`)。packaging 的自检入口:把 step 3 schema_diagnosis +
|
|
10
12
|
/// (placeholder)step 11 comms / step 12 orphan gate 结论归一成 typed [`DoctorStatus`]。
|
|
11
13
|
/// **§84**:只调 step 3/11/12 的 trait 入口,注入 mock 时 provider 调用计数 = 0;绝不触发 prompt/token。
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! schema migration / repair 转调:`doctor --fix-schema` → step 3 `fix_schema_layout`,包成
|
|
2
3
|
//! [`MigrationOutcome`]。
|
|
3
4
|
|
|
@@ -7,6 +8,7 @@ use super::types::{MigrationOutcome, PackagingError};
|
|
|
7
8
|
use crate::db::migration::{fix_schema_layout, FixResult};
|
|
8
9
|
use crate::db::schema::SCHEMA_VERSION;
|
|
9
10
|
|
|
11
|
+
///
|
|
10
12
|
/// `team-agent doctor --fix-schema`(`commands.py:239`)→ 转调 step 3 [`fix_schema_layout`]。
|
|
11
13
|
/// migration·repair 实体在 step 3;packaging 仅转调 + 包成 [`MigrationOutcome`]。
|
|
12
14
|
/// // REAL-MACHINE-E2E:破坏性 rebuild(撞锁/备份/rollback)的真路径需真机验;转调逻辑可单测。
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
2
3
|
#![allow(non_snake_case)]
|
|
3
4
|
use super::*;
|
|
@@ -505,17 +506,21 @@ fn repair_schema_active_lock_is_blocked_no_backup() {
|
|
|
505
506
|
|
|
506
507
|
#[test]
|
|
507
508
|
fn diagnose_path_when_bin_on_path_reports_on_path() {
|
|
508
|
-
// bin_dir 在当前 PATH → OnPath{bin_dir}
|
|
509
|
-
// 取 PATH 首个真实条目作为 bin_dir,保证「在 PATH 上」。
|
|
509
|
+
// bin_dir 在当前 PATH → install report 的 path_hint 是 OnPath{bin_dir}。
|
|
510
510
|
let path_var = std::env::var("PATH").unwrap_or_default();
|
|
511
|
-
let
|
|
511
|
+
let bin = path_var
|
|
512
512
|
.split(':')
|
|
513
|
-
.
|
|
514
|
-
.
|
|
515
|
-
|
|
516
|
-
|
|
513
|
+
.filter(|p| !p.is_empty())
|
|
514
|
+
.map(PathBuf::from)
|
|
515
|
+
.find(|p| p.ends_with("bin"))
|
|
516
|
+
.expect("PATH has at least one .../bin entry");
|
|
517
|
+
let mut opts = install_opts(SkillTarget::Codex);
|
|
518
|
+
opts.prefix = Prefix(bin.parent().expect("bin dir has parent").to_path_buf());
|
|
519
|
+
let hint = install(&opts)
|
|
520
|
+
.expect("install reports on-path bin")
|
|
521
|
+
.path_hint;
|
|
517
522
|
match hint {
|
|
518
|
-
PathHint::OnPath { bin_dir } => assert_eq!(bin_dir,
|
|
523
|
+
PathHint::OnPath { bin_dir } => assert_eq!(bin_dir, bin),
|
|
519
524
|
PathHint::NotOnPath { .. } => panic!("PATH entry should report OnPath"),
|
|
520
525
|
}
|
|
521
526
|
}
|
|
@@ -523,15 +528,20 @@ fn diagnose_path_when_bin_on_path_reports_on_path() {
|
|
|
523
528
|
#[test]
|
|
524
529
|
fn diagnose_path_not_on_path_npmrc_prefix_is_none_no_npm() {
|
|
525
530
|
// Rust 版无 npm 路径 → npmrc_prefix == None (skeleton:257).
|
|
526
|
-
// 用一个绝不在 PATH
|
|
527
|
-
let
|
|
528
|
-
let
|
|
531
|
+
// 用一个绝不在 PATH 的安装前缀,经当前公开 install 路径取得诊断。
|
|
532
|
+
let prefix = PathBuf::from("/zzz-definitely-not-on-path-9f3a");
|
|
533
|
+
let bin = prefix.join("bin");
|
|
534
|
+
let mut opts = install_opts(SkillTarget::Codex);
|
|
535
|
+
opts.prefix = Prefix(prefix);
|
|
536
|
+
let hint = install(&opts)
|
|
537
|
+
.expect("install reports off-path bin")
|
|
538
|
+
.path_hint;
|
|
529
539
|
match hint {
|
|
530
540
|
PathHint::NotOnPath {
|
|
531
541
|
bin_dir,
|
|
532
542
|
diagnostic,
|
|
533
543
|
} => {
|
|
534
|
-
assert_eq!(bin_dir,
|
|
544
|
+
assert_eq!(bin_dir, bin);
|
|
535
545
|
// Rust 无 npm → 绝不重新引入 .npmrc 解析.
|
|
536
546
|
assert_eq!(diagnostic.npmrc_prefix, None);
|
|
537
547
|
// path_entries == 当前 PATH 条目数 (bincheck.mjs:43).
|
|
@@ -649,136 +659,8 @@ fn install_skill_real_copy_removes_stale_files() {
|
|
|
649
659
|
}
|
|
650
660
|
|
|
651
661
|
// ───────────────────────────────────────────────────────────────────────
|
|
652
|
-
//
|
|
653
|
-
// - 默认 purge_runtime=false → purged_runtime=false,不删 workspace/.team.
|
|
654
|
-
// - purge_runtime=true 且检测有 team 在跑 → 拒绝:purge_refused_team_running=true
|
|
655
|
-
// (或返 PurgeRefusedTeamRunning err — 取实现纪律,这里钉「不真删」)。
|
|
656
|
-
// 真删 / team-running 判定 #[ignore]。
|
|
657
|
-
// ───────────────────────────────────────────────────────────────────────
|
|
658
|
-
|
|
659
|
-
/// TEST-SUPPORT seed helper: build a workspace whose state.json projects a RUNNING team
|
|
660
|
-
/// (status "running" + a live coordinator pid). The team-running guard reads this projection.
|
|
661
|
-
/// Real impl (pure scaffolding, not a production fn) so the NEW-CONTRACT guard test has a
|
|
662
|
-
/// concrete fixture instead of a bare nonexistent path.
|
|
663
|
-
fn seed_workspace_with_running_team(tag: &str) -> PathBuf {
|
|
664
|
-
let ws = std::env::temp_dir().join(format!("ta-ws-running-{}-{}", std::process::id(), tag));
|
|
665
|
-
let team_dir = ws.join(".team");
|
|
666
|
-
std::fs::create_dir_all(&team_dir).expect("seed .team dir");
|
|
667
|
-
// state.json shape mirrors step-5 state projection: a team marked running.
|
|
668
|
-
let state = serde_json::json!({
|
|
669
|
-
"teams": {
|
|
670
|
-
"demo": { "status": "running", "coordinator_pid": std::process::id() }
|
|
671
|
-
}
|
|
672
|
-
});
|
|
673
|
-
std::fs::write(
|
|
674
|
-
team_dir.join("state.json"),
|
|
675
|
-
serde_json::to_vec_pretty(&state).unwrap(),
|
|
676
|
-
)
|
|
677
|
-
.expect("seed state.json");
|
|
678
|
-
ws
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
/// TEST-SUPPORT seed helper: an idle workspace (state.json present, no running team).
|
|
682
|
-
fn seed_workspace_idle(tag: &str) -> PathBuf {
|
|
683
|
-
let ws = std::env::temp_dir().join(format!("ta-ws-idle-{}-{}", std::process::id(), tag));
|
|
684
|
-
let team_dir = ws.join(".team");
|
|
685
|
-
std::fs::create_dir_all(&team_dir).expect("seed .team dir");
|
|
686
|
-
std::fs::write(
|
|
687
|
-
team_dir.join("state.json"),
|
|
688
|
-
serde_json::to_vec_pretty(&serde_json::json!({ "teams": {} })).unwrap(),
|
|
689
|
-
)
|
|
690
|
-
.expect("seed state.json");
|
|
691
|
-
ws
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
#[test]
|
|
695
|
-
#[serial_test::serial(env)]
|
|
696
|
-
fn uninstall_default_does_not_purge_runtime() {
|
|
697
|
-
// PORT-GOLDEN (install.mjs:127-129): default (no --purge-runtime) leaves runtime; the
|
|
698
|
-
// "runtime directories are left ... for rollback" branch. Default must NOT purge
|
|
699
|
-
// workspace/.team. With workspace seeded, default must STILL leave it untouched on disk.
|
|
700
|
-
// Isolate HOME: uninstall() now removes ~/.codex|.claude skill dirs (reads HOME). Without an
|
|
701
|
-
// isolated empty HOME this test would (a) delete the real user's skill dir and (b) race
|
|
702
|
-
// p2_uninstall_removes_both_skill_dirs' HOME mutation → remove_dir_all NotFound flake under
|
|
703
|
-
// parallel cargo. Shared ENV_LOCK_PKG serializes the two HOME-touching uninstall tests.
|
|
704
|
-
let _g = ENV_LOCK_PKG.lock().unwrap_or_else(|p| p.into_inner());
|
|
705
|
-
let home = std::env::temp_dir().join(format!("ta-uninst-default-home-{}", std::process::id()));
|
|
706
|
-
std::fs::create_dir_all(&home).unwrap();
|
|
707
|
-
let _h = HomeGuard::set(&home);
|
|
708
|
-
let ws = seed_workspace_idle("default-nopurge");
|
|
709
|
-
let opts = UninstallOptions {
|
|
710
|
-
prefix: Prefix(std::env::temp_dir().join(format!("ta-uninst-{}", std::process::id()))),
|
|
711
|
-
purge_runtime: false,
|
|
712
|
-
workspace: Some(ws.clone()),
|
|
713
|
-
};
|
|
714
|
-
let outcome = uninstall(&opts).expect("default uninstall");
|
|
715
|
-
assert!(!outcome.purged_runtime, "default must NOT purge runtime");
|
|
716
|
-
assert!(
|
|
717
|
-
!outcome.purge_refused_team_running,
|
|
718
|
-
"no purge requested → no refusal"
|
|
719
|
-
);
|
|
720
|
-
// SAFETY INVARIANT (card §uninstall 绝不默认删 workspace/.team): the seeded workspace
|
|
721
|
-
// .team must still exist after a default uninstall.
|
|
722
|
-
assert!(
|
|
723
|
-
ws.join(".team").join("state.json").exists(),
|
|
724
|
-
"default uninstall must NEVER delete workspace/.team"
|
|
725
|
-
);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
#[test]
|
|
729
|
-
#[ignore = "NEW-CONTRACT (Rust hardening, NOT a Python port-golden): real team-running guard \
|
|
730
|
-
needs live state projection (step 5). Python install.mjs:123-128 has NO such guard \
|
|
731
|
-
— it purges unconditionally on --purge-runtime. Confirmed with gate w59ds828k: this \
|
|
732
|
-
is intentional hardening backed by card §uninstall prose 'pass --purge-runtime only \
|
|
733
|
-
when no teams are running.' Marked NEW-CONTRACT, not PORT."]
|
|
734
|
-
fn uninstall_purge_refused_when_team_running_NEW_CONTRACT() {
|
|
735
|
-
// NEW-CONTRACT safety guard: purge_runtime=true but workspace projects a RUNNING team →
|
|
736
|
-
// REFUSE purge (purge_refused_team_running=true OR PurgeRefusedTeamRunning err). The
|
|
737
|
-
// seeded workspace .team MUST survive regardless. This behavior is NOT in install.mjs.
|
|
738
|
-
let ws = seed_workspace_with_running_team("guard");
|
|
739
|
-
let opts = UninstallOptions {
|
|
740
|
-
prefix: Prefix(std::env::temp_dir().join("ta-uninst-guard")),
|
|
741
|
-
purge_runtime: true,
|
|
742
|
-
workspace: Some(ws.clone()),
|
|
743
|
-
};
|
|
744
|
-
match uninstall(&opts) {
|
|
745
|
-
Ok(o) => {
|
|
746
|
-
assert!(!o.purged_runtime, "must not purge while team running");
|
|
747
|
-
assert!(o.purge_refused_team_running, "must set refusal flag");
|
|
748
|
-
}
|
|
749
|
-
Err(PackagingError::PurgeRefusedTeamRunning(refused_ws)) => {
|
|
750
|
-
assert_eq!(refused_ws, ws, "refusal must name the running workspace");
|
|
751
|
-
}
|
|
752
|
-
Err(other) => panic!("expected refusal, got {other:?}"),
|
|
753
|
-
}
|
|
754
|
-
// Hard invariant: refused purge must leave the workspace fully intact.
|
|
755
|
-
assert!(
|
|
756
|
-
ws.join(".team").join("state.json").exists(),
|
|
757
|
-
"refused purge must NEVER delete workspace/.team"
|
|
758
|
-
);
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
#[test]
|
|
762
|
-
#[ignore = "REAL-MACHINE-E2E: PORT-GOLDEN — --purge-runtime with NO running team really removes \
|
|
763
|
-
the runtime root (install.mjs:123-126 unconditional rmSync). File-system side effect."]
|
|
764
|
-
fn uninstall_purge_runtime_idle_workspace_purges_PORT_GOLDEN() {
|
|
765
|
-
// PORT-GOLDEN (install.mjs:123-126): --purge-runtime DOES purge when no team is running.
|
|
766
|
-
// This is the faithful Python behavior (the guard above is the only Rust addition).
|
|
767
|
-
let ws = seed_workspace_idle("port-purge");
|
|
768
|
-
let opts = UninstallOptions {
|
|
769
|
-
prefix: Prefix(std::env::temp_dir().join("ta-uninst-port-purge")),
|
|
770
|
-
purge_runtime: true,
|
|
771
|
-
workspace: Some(ws.clone()),
|
|
772
|
-
};
|
|
773
|
-
let outcome = uninstall(&opts).expect("purge on idle workspace");
|
|
774
|
-
assert!(outcome.purged_runtime, "idle + --purge-runtime → purged");
|
|
775
|
-
assert!(!outcome.purge_refused_team_running, "no team → no refusal");
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// ───────────────────────────────────────────────────────────────────────
|
|
779
|
-
// install / update — install.mjs:48-95。
|
|
662
|
+
// install — install.mjs:48-95。
|
|
780
663
|
// - install 首装:InstallReport.replace == None (无二进制替换).
|
|
781
|
-
// - update:replace == Some(..) (有原子替换;失败回滚到 .previous,bug-084 同源).
|
|
782
664
|
// - installer 默认 skill_target = All (install.mjs:74 `--target all`).
|
|
783
665
|
// 全副作用 #[ignore] (写 bin / 拷 skill / 探 PATH / 跑 doctor) — clean-install E2E.
|
|
784
666
|
// ───────────────────────────────────────────────────────────────────────
|
|
@@ -804,19 +686,6 @@ fn install_first_time_has_no_binary_replace() {
|
|
|
804
686
|
assert_eq!(report.version, Version::current());
|
|
805
687
|
}
|
|
806
688
|
|
|
807
|
-
#[test]
|
|
808
|
-
#[ignore = "REAL-MACHINE-E2E: atomic binary replace + .previous backup + rollback (bug-084 同源)"]
|
|
809
|
-
fn update_performs_atomic_binary_replace() {
|
|
810
|
-
// install.mjs:60-66 — update 有 dest→backup + tmp→dest 原子替换.
|
|
811
|
-
let opts = install_opts(SkillTarget::All);
|
|
812
|
-
let report = update(&opts).expect("update");
|
|
813
|
-
match report.replace {
|
|
814
|
-
Some(AtomicReplaceOutcome::Replaced { .. })
|
|
815
|
-
| Some(AtomicReplaceOutcome::ReplacedCrossDevice { .. }) => {}
|
|
816
|
-
other => panic!("update must replace binary, got {other:?}"),
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
689
|
#[test]
|
|
821
690
|
fn atomic_replace_outcome_serde_tag_outcome() {
|
|
822
691
|
// #[serde(tag = "outcome")] — RolledBack carries restored_from + error.
|
|
@@ -846,7 +715,7 @@ fn install_skill_dry_run_is_pure_no_provider_state() {
|
|
|
846
715
|
// hold the same `ENV_LOCK_PKG` + `HomeGuard::set` guards; without
|
|
847
716
|
// matching guards here, two dry-run runs can observe HOME after
|
|
848
717
|
// a real-copy test swapped it, producing spurious diffs. Same
|
|
849
|
-
// env critical section as install
|
|
718
|
+
// env critical section as the other HOME-mutating install tests.
|
|
850
719
|
let _g = ENV_LOCK_PKG.lock().unwrap_or_else(|p| p.into_inner());
|
|
851
720
|
let home = std::env::temp_dir().join(format!(
|
|
852
721
|
"ta-0543-pkg-dry-{}-{}",
|
|
@@ -877,8 +746,11 @@ fn install_skill_dry_run_is_pure_no_provider_state() {
|
|
|
877
746
|
fn diagnose_path_empty_path_has_zero_entries() {
|
|
878
747
|
// bincheck.mjs:43 — searchPath ? split.length : 0;空 PATH → 0 entries.
|
|
879
748
|
// (真改 process env PATH 影响并行测试,故 ignore;实现层应支持注入 PATH.)
|
|
880
|
-
let
|
|
881
|
-
|
|
749
|
+
let mut opts = install_opts(SkillTarget::Codex);
|
|
750
|
+
opts.prefix = Prefix(PathBuf::from("/anything"));
|
|
751
|
+
let hint = install(&opts)
|
|
752
|
+
.expect("install reports empty PATH")
|
|
753
|
+
.path_hint;
|
|
882
754
|
if let PathHint::NotOnPath { diagnostic, .. } = hint {
|
|
883
755
|
assert_eq!(diagnostic.path_entries, 0);
|
|
884
756
|
} else {
|
|
@@ -948,64 +820,3 @@ fn install_skill_all_real_copies_to_three_provider_locations() {
|
|
|
948
820
|
);
|
|
949
821
|
}
|
|
950
822
|
}
|
|
951
|
-
|
|
952
|
-
// P1 — update() must perform a REAL atomic replace (rename dest→.previous), not fabricate
|
|
953
|
-
// a Replaced outcome whose backup file never exists (install.mjs:60-66; bug-084).
|
|
954
|
-
#[test]
|
|
955
|
-
fn p2_update_creates_real_atomic_replace_backup() {
|
|
956
|
-
let base = std::env::temp_dir().join(format!("ta-p2-update-{}", std::process::id()));
|
|
957
|
-
let prefix = base.join("prefix");
|
|
958
|
-
std::fs::create_dir_all(prefix.join("bin")).unwrap();
|
|
959
|
-
let dest = prefix.join("bin").join("team-agent");
|
|
960
|
-
std::fs::write(&dest, b"OLD BINARY").unwrap(); // pre-existing bin to back up
|
|
961
|
-
let self_bin = base.join("team-agent-new");
|
|
962
|
-
std::fs::write(&self_bin, b"NEW BINARY").unwrap();
|
|
963
|
-
|
|
964
|
-
let opts = InstallOptions {
|
|
965
|
-
prefix: Prefix(prefix.clone()),
|
|
966
|
-
self_binary: self_bin,
|
|
967
|
-
skill_target: SkillTarget::All,
|
|
968
|
-
};
|
|
969
|
-
let report = update(&opts).unwrap();
|
|
970
|
-
let backup = match report.replace {
|
|
971
|
-
Some(AtomicReplaceOutcome::Replaced { backup }) => backup,
|
|
972
|
-
other => panic!("update must report a Replaced atomic replace, got {other:?}"),
|
|
973
|
-
};
|
|
974
|
-
assert!(
|
|
975
|
-
backup.exists(),
|
|
976
|
-
"update() must actually rename dest→.previous; the claimed backup file must exist on disk"
|
|
977
|
-
);
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
// P1 — uninstall() must remove ALL provider skill dirs (~/.codex|.claude|.copilot/skills/team-agent)
|
|
981
|
-
// and record them (install.mjs:115-122 + E9 copilot). Table-driven over SkillTarget::SINGLE_TARGETS.
|
|
982
|
-
#[test]
|
|
983
|
-
#[serial_test::serial(env)]
|
|
984
|
-
fn p2_uninstall_removes_all_provider_skill_dirs() {
|
|
985
|
-
let _g = ENV_LOCK_PKG.lock().unwrap_or_else(|p| p.into_inner());
|
|
986
|
-
let base = std::env::temp_dir().join(format!("ta-p2-uninst-{}", std::process::id()));
|
|
987
|
-
let home = base.join("home");
|
|
988
|
-
let codex = home.join(".codex").join("skills").join("team-agent");
|
|
989
|
-
let claude = home.join(".claude").join("skills").join("team-agent");
|
|
990
|
-
let copilot = home.join(".copilot").join("skills").join("team-agent");
|
|
991
|
-
for d in [&codex, &claude, &copilot] {
|
|
992
|
-
std::fs::create_dir_all(d).unwrap();
|
|
993
|
-
std::fs::write(d.join("SKILL.md"), b"x").unwrap();
|
|
994
|
-
}
|
|
995
|
-
let _h = HomeGuard::set(&home);
|
|
996
|
-
|
|
997
|
-
let opts = UninstallOptions {
|
|
998
|
-
prefix: Prefix(base.join("prefix")),
|
|
999
|
-
purge_runtime: false,
|
|
1000
|
-
workspace: None,
|
|
1001
|
-
};
|
|
1002
|
-
let out = uninstall(&opts).unwrap();
|
|
1003
|
-
assert_eq!(
|
|
1004
|
-
out.removed_skill_dirs.len(),
|
|
1005
|
-
3,
|
|
1006
|
-
"uninstall must remove ~/.codex, ~/.claude AND ~/.copilot skill dirs"
|
|
1007
|
-
);
|
|
1008
|
-
assert!(!codex.exists(), "~/.codex skill dir must be removed");
|
|
1009
|
-
assert!(!claude.exists(), "~/.claude skill dir must be removed");
|
|
1010
|
-
assert!(!copilot.exists(), "~/.copilot skill dir must be removed");
|
|
1011
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! packaging 共享类型 —— §19 散字符串 → enum / §3 路径·版本 newtype / typed Outcome·Report /
|
|
2
3
|
//! entry fn 入参 / PackagingError / 平台能力声明(纯函数 `platform_support`)。
|
|
3
4
|
|
|
@@ -21,18 +22,6 @@ use crate::db::DbError;
|
|
|
21
22
|
// §19 散字符串态 → 穷尽 enum
|
|
22
23
|
// ===========================================================================
|
|
23
24
|
|
|
24
|
-
/// installer 子命令(`install.mjs:20-30` argv 散字符串 `install/update/doctor/uninstall/help`)。
|
|
25
|
-
/// `unknown command` 走 exit 2 —— Rust 侧由 clap(step 14)在解析层挡掉,此 enum 是穷尽 match 无 fallthrough。
|
|
26
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
27
|
-
#[serde(rename_all = "snake_case")]
|
|
28
|
-
pub enum InstallerCommand {
|
|
29
|
-
Install,
|
|
30
|
-
Update,
|
|
31
|
-
Doctor,
|
|
32
|
-
Uninstall,
|
|
33
|
-
Help,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
25
|
/// skill 安装目标(`cli/parser.py:443` choices `codex/claude/all`)。`_skill_dest_dir` 据此选
|
|
37
26
|
/// `~/.codex` vs `~/.claude`(`commands.py:467-472`)。**散字符串必须 enum**,与 step 8 Provider 对齐。
|
|
38
27
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
@@ -57,6 +46,7 @@ impl SkillTarget {
|
|
|
57
46
|
SkillTarget::Copilot,
|
|
58
47
|
];
|
|
59
48
|
|
|
49
|
+
///
|
|
60
50
|
/// 单目标 → 对应 provider(`All` 无单一 provider → `None`)。与 [`Provider`] 对齐,防散字符串再生。
|
|
61
51
|
pub fn provider(self) -> Option<Provider> {
|
|
62
52
|
match self {
|
|
@@ -67,6 +57,7 @@ impl SkillTarget {
|
|
|
67
57
|
}
|
|
68
58
|
}
|
|
69
59
|
|
|
60
|
+
///
|
|
70
61
|
/// `_skill_dest_dir`:`~/.codex|.claude|.copilot/skills/team-agent`(`All` fan-out 全集,非单 dir → None)。
|
|
71
62
|
pub fn dest_dir(self, home: &Path) -> Option<SkillDestDir> {
|
|
72
63
|
match self {
|
|
@@ -128,6 +119,7 @@ pub enum BlockerSource {
|
|
|
128
119
|
pub struct Version(pub String);
|
|
129
120
|
|
|
130
121
|
impl Version {
|
|
122
|
+
///
|
|
131
123
|
/// 编译期注入的唯一版本(`env!("CARGO_PKG_VERSION")`)。
|
|
132
124
|
pub fn current() -> Self {
|
|
133
125
|
Self(env!("CARGO_PKG_VERSION").to_string())
|
|
@@ -161,7 +153,7 @@ pub struct SkillDestDir(pub PathBuf);
|
|
|
161
153
|
/// 此 enum 只用于「release 矩阵覆盖面」声明。
|
|
162
154
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
163
155
|
#[serde(rename_all = "snake_case")]
|
|
164
|
-
pub enum ReleaseTarget {
|
|
156
|
+
pub(super) enum ReleaseTarget {
|
|
165
157
|
MacosAarch64,
|
|
166
158
|
MacosX8664,
|
|
167
159
|
LinuxX8664,
|
|
@@ -181,7 +173,7 @@ pub enum ReleaseTarget {
|
|
|
181
173
|
/// Truth source: `.team/artifacts/0.5.x-windows-portability-cr-verdict.md` §C-1。
|
|
182
174
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
183
175
|
#[serde(rename_all = "snake_case", tag = "level")]
|
|
184
|
-
pub enum PlatformSupport {
|
|
176
|
+
pub(super) enum PlatformSupport {
|
|
185
177
|
/// 原生一等(macOS/Linux tmux)。
|
|
186
178
|
Native,
|
|
187
179
|
/// 需 WSL + tmux backend(显式标要求,不假装原生)。
|
|
@@ -196,8 +188,9 @@ pub enum PlatformSupport {
|
|
|
196
188
|
Unsupported { reason: String },
|
|
197
189
|
}
|
|
198
190
|
|
|
191
|
+
///
|
|
199
192
|
/// release 矩阵中某目标平台的支持等级(§8 如实声明;能力门归 step 9)。纯函数。
|
|
200
|
-
pub fn platform_support(target: ReleaseTarget) -> PlatformSupport {
|
|
193
|
+
pub(super) fn platform_support(target: ReleaseTarget) -> PlatformSupport {
|
|
201
194
|
match target {
|
|
202
195
|
ReleaseTarget::MacosAarch64
|
|
203
196
|
| ReleaseTarget::MacosX8664
|