@team-agent/installer 0.2.11 → 0.3.1
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 +744 -0
- package/Cargo.toml +34 -0
- package/crates/team-agent/Cargo.toml +33 -0
- package/crates/team-agent/src/cli/adapters.rs +1343 -0
- package/crates/team-agent/src/cli/diagnose.rs +554 -0
- package/crates/team-agent/src/cli/emit.rs +1204 -0
- package/crates/team-agent/src/cli/helpers.rs +88 -0
- package/crates/team-agent/src/cli/leader.rs +216 -0
- package/crates/team-agent/src/cli/mod.rs +1207 -0
- package/crates/team-agent/src/cli/profile.rs +306 -0
- package/crates/team-agent/src/cli/send.rs +215 -0
- package/crates/team-agent/src/cli/status.rs +179 -0
- package/crates/team-agent/src/cli/status_port.rs +502 -0
- package/crates/team-agent/src/cli/tests/base.rs +616 -0
- package/crates/team-agent/src/cli/tests/compile.rs +96 -0
- package/crates/team-agent/src/cli/tests/divergence.rs +509 -0
- package/crates/team-agent/src/cli/tests/lane_c.rs +333 -0
- package/crates/team-agent/src/cli/tests/leader_watch.rs +395 -0
- package/crates/team-agent/src/cli/tests/main_preserved.rs +675 -0
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +390 -0
- package/crates/team-agent/src/cli/tests/mod.rs +97 -0
- package/crates/team-agent/src/cli/tests/peer_allow.rs +137 -0
- package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +302 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +305 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +385 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +182 -0
- package/crates/team-agent/src/cli/tests/verb_settle.rs +236 -0
- package/crates/team-agent/src/cli/tests/verb_validate.rs +184 -0
- package/crates/team-agent/src/cli/types.rs +605 -0
- package/crates/team-agent/src/compiler/tests.rs +701 -0
- package/crates/team-agent/src/compiler.rs +489 -0
- package/crates/team-agent/src/coordinator/backoff.rs +153 -0
- package/crates/team-agent/src/coordinator/health.rs +557 -0
- package/crates/team-agent/src/coordinator/mod.rs +80 -0
- package/crates/team-agent/src/coordinator/orphan.rs +179 -0
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +255 -0
- package/crates/team-agent/src/coordinator/tests/basics.rs +262 -0
- package/crates/team-agent/src/coordinator/tests/daemon.rs +323 -0
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +263 -0
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +136 -0
- package/crates/team-agent/src/coordinator/tests/mod.rs +310 -0
- package/crates/team-agent/src/coordinator/tests/spine.rs +261 -0
- package/crates/team-agent/src/coordinator/tests/takeover.rs +227 -0
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +256 -0
- package/crates/team-agent/src/coordinator/tests/watch.rs +167 -0
- package/crates/team-agent/src/coordinator/tick.rs +2032 -0
- package/crates/team-agent/src/coordinator/types.rs +584 -0
- package/crates/team-agent/src/db/migration.rs +716 -0
- package/crates/team-agent/src/db/mod.rs +23 -0
- package/crates/team-agent/src/db/schema.rs +378 -0
- package/crates/team-agent/src/event_log.rs +375 -0
- package/crates/team-agent/src/fake_worker.rs +253 -0
- package/crates/team-agent/src/leader/helpers.rs +190 -0
- package/crates/team-agent/src/leader/inject.rs +33 -0
- package/crates/team-agent/src/leader/lease.rs +1084 -0
- package/crates/team-agent/src/leader/mod.rs +99 -0
- package/crates/team-agent/src/leader/owner_bind.rs +292 -0
- package/crates/team-agent/src/leader/rediscover/tests.rs +526 -0
- package/crates/team-agent/src/leader/rediscover.rs +1101 -0
- package/crates/team-agent/src/leader/start.rs +273 -0
- package/crates/team-agent/src/leader/takeover.rs +235 -0
- package/crates/team-agent/src/leader/tests/basics.rs +183 -0
- package/crates/team-agent/src/leader/tests/byte_findings.rs +237 -0
- package/crates/team-agent/src/leader/tests/identity.rs +206 -0
- package/crates/team-agent/src/leader/tests/idle.rs +272 -0
- package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
- package/crates/team-agent/src/leader/tests/lease_claim.rs +410 -0
- package/crates/team-agent/src/leader/tests/mod.rs +125 -0
- package/crates/team-agent/src/leader/tests/rediscover.rs +351 -0
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +204 -0
- package/crates/team-agent/src/leader/types.rs +489 -0
- package/crates/team-agent/src/lib.rs +85 -0
- package/crates/team-agent/src/lifecycle/display.rs +228 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +227 -0
- package/crates/team-agent/src/lifecycle/launch.rs +2109 -0
- package/crates/team-agent/src/lifecycle/mod.rs +62 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +533 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +517 -0
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +41 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +268 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +780 -0
- package/crates/team-agent/src/lifecycle/restart/selection.rs +208 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +242 -0
- package/crates/team-agent/src/lifecycle/restart.rs +76 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +455 -0
- package/crates/team-agent/src/lifecycle/tests/core.rs +989 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +583 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +985 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +265 -0
- package/crates/team-agent/src/lifecycle/tests.rs +27 -0
- package/crates/team-agent/src/lifecycle/types.rs +710 -0
- package/crates/team-agent/src/main.rs +41 -0
- package/crates/team-agent/src/mcp_server/helpers.rs +228 -0
- package/crates/team-agent/src/mcp_server/mod.rs +183 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +312 -0
- package/crates/team-agent/src/mcp_server/tests/golden.rs +283 -0
- package/crates/team-agent/src/mcp_server/tests/normalize.rs +244 -0
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +189 -0
- package/crates/team-agent/src/mcp_server/tests/send.rs +222 -0
- package/crates/team-agent/src/mcp_server/tests/tools.rs +158 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +187 -0
- package/crates/team-agent/src/mcp_server/tests.rs +38 -0
- package/crates/team-agent/src/mcp_server/tools.rs +603 -0
- package/crates/team-agent/src/mcp_server/types.rs +421 -0
- package/crates/team-agent/src/mcp_server/wire.rs +468 -0
- package/crates/team-agent/src/message_store.rs +767 -0
- package/crates/team-agent/src/messaging/activity.rs +433 -0
- package/crates/team-agent/src/messaging/delivery.rs +743 -0
- package/crates/team-agent/src/messaging/helpers.rs +209 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +329 -0
- package/crates/team-agent/src/messaging/mod.rs +147 -0
- package/crates/team-agent/src/messaging/peers.rs +32 -0
- package/crates/team-agent/src/messaging/results.rs +553 -0
- package/crates/team-agent/src/messaging/scheduler.rs +344 -0
- package/crates/team-agent/src/messaging/selftest.rs +100 -0
- package/crates/team-agent/src/messaging/send.rs +578 -0
- package/crates/team-agent/src/messaging/tests/basic.rs +357 -0
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +122 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +293 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +1422 -0
- package/crates/team-agent/src/messaging/tests/spine.rs +437 -0
- package/crates/team-agent/src/messaging/trust.rs +192 -0
- package/crates/team-agent/src/messaging/types.rs +355 -0
- package/crates/team-agent/src/messaging/watchers.rs +591 -0
- package/crates/team-agent/src/model/enums.rs +311 -0
- package/crates/team-agent/src/model/errors.rs +17 -0
- package/crates/team-agent/src/model/ids.rs +155 -0
- package/crates/team-agent/src/model/mod.rs +22 -0
- package/crates/team-agent/src/model/paths.rs +228 -0
- package/crates/team-agent/src/model/permissions.rs +567 -0
- package/crates/team-agent/src/model/routing.rs +340 -0
- package/crates/team-agent/src/model/spec.rs +680 -0
- package/crates/team-agent/src/model/task_graph.rs +380 -0
- package/crates/team-agent/src/model/testdata/fuzz.golden.yaml +43 -0
- package/crates/team-agent/src/model/testdata/fuzz.yaml +43 -0
- package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +207 -0
- package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +206 -0
- package/crates/team-agent/src/model/testdata/team.spec.yaml +206 -0
- package/crates/team-agent/src/model/yaml/tests.rs +288 -0
- package/crates/team-agent/src/model/yaml.rs +800 -0
- package/crates/team-agent/src/packaging/install.rs +305 -0
- package/crates/team-agent/src/packaging/migrate.rs +30 -0
- package/crates/team-agent/src/packaging/mod.rs +82 -0
- package/crates/team-agent/src/packaging/repair.rs +24 -0
- package/crates/team-agent/src/packaging/tests.rs +829 -0
- package/crates/team-agent/src/packaging/types.rs +369 -0
- package/crates/team-agent/src/provider/adapter.rs +801 -0
- package/crates/team-agent/src/provider/approvals/mod.rs +2 -0
- package/crates/team-agent/src/provider/approvals/parsing.rs +452 -0
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +163 -0
- package/crates/team-agent/src/provider/classify.rs +456 -0
- package/crates/team-agent/src/provider/faults.rs +136 -0
- package/crates/team-agent/src/provider/helpers.rs +41 -0
- package/crates/team-agent/src/provider/mod.rs +53 -0
- package/crates/team-agent/src/provider/startup_prompt.rs +423 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +239 -0
- package/crates/team-agent/src/provider/tests/classify.rs +240 -0
- package/crates/team-agent/src/provider/tests/faults.rs +120 -0
- package/crates/team-agent/src/provider/tests/idle.rs +208 -0
- package/crates/team-agent/src/provider/tests/wire.rs +213 -0
- package/crates/team-agent/src/provider/tests.rs +31 -0
- package/crates/team-agent/src/provider/types.rs +424 -0
- package/crates/team-agent/src/state/identity.rs +659 -0
- package/crates/team-agent/src/state/mod.rs +58 -0
- package/crates/team-agent/src/state/owner_gate.rs +423 -0
- package/crates/team-agent/src/state/persist.rs +712 -0
- package/crates/team-agent/src/state/projection.rs +657 -0
- package/crates/team-agent/src/state/selector.rs +105 -0
- package/crates/team-agent/src/state/testdata/state-rich.canonical.json +133 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +765 -0
- package/crates/team-agent/src/tmux_backend.rs +810 -0
- package/crates/team-agent/src/transport/test_support.rs +252 -0
- package/crates/team-agent/src/transport/tests/behavior.rs +327 -0
- package/crates/team-agent/src/transport/tests/mod.rs +199 -0
- package/crates/team-agent/src/transport/tests/wire.rs +527 -0
- package/crates/team-agent/src/transport.rs +774 -0
- package/npm/install.mjs +118 -112
- package/package.json +15 -13
- package/crates/team-agent-core/Cargo.toml +0 -12
- package/crates/team-agent-core/src/lib.rs +0 -332
- package/crates/team-agent-core/src/main.rs +0 -152
- package/pyproject.toml +0 -18
- package/scripts/install.py +0 -88
- package/scripts/run_regression_tests.py +0 -83
- package/src/team_agent/__init__.py +0 -3
- package/src/team_agent/__main__.py +0 -5
- package/src/team_agent/_legacy_pane_discovery.py +0 -186
- package/src/team_agent/abnormal_track.py +0 -253
- package/src/team_agent/approvals/__init__.py +0 -65
- package/src/team_agent/approvals/constants.py +0 -6
- package/src/team_agent/approvals/parsing.py +0 -176
- package/src/team_agent/approvals/runtime_prompts.py +0 -171
- package/src/team_agent/approvals/status.py +0 -176
- package/src/team_agent/cli/__init__.py +0 -137
- package/src/team_agent/cli/commands.py +0 -481
- package/src/team_agent/cli/e2e.py +0 -202
- package/src/team_agent/cli/helpers.py +0 -226
- package/src/team_agent/cli/parser.py +0 -540
- package/src/team_agent/compiler.py +0 -334
- package/src/team_agent/coordinator/__init__.py +0 -53
- package/src/team_agent/coordinator/__main__.py +0 -119
- package/src/team_agent/coordinator/lifecycle.py +0 -411
- package/src/team_agent/coordinator/metadata.py +0 -61
- package/src/team_agent/coordinator/paths.py +0 -17
- package/src/team_agent/diagnose/__init__.py +0 -48
- package/src/team_agent/diagnose/checks.py +0 -101
- package/src/team_agent/diagnose/comms.py +0 -213
- package/src/team_agent/diagnose/health.py +0 -241
- package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
- package/src/team_agent/diagnose/preflight.py +0 -194
- package/src/team_agent/diagnose/quick_start.py +0 -324
- package/src/team_agent/display/__init__.py +0 -92
- package/src/team_agent/display/adaptive.py +0 -511
- package/src/team_agent/display/backend.py +0 -46
- package/src/team_agent/display/close.py +0 -154
- package/src/team_agent/display/ghostty.py +0 -77
- package/src/team_agent/display/rebuild.py +0 -102
- package/src/team_agent/display/tiling.py +0 -156
- package/src/team_agent/display/worker_window.py +0 -114
- package/src/team_agent/display/workspace.py +0 -382
- package/src/team_agent/errors.py +0 -10
- package/src/team_agent/events.py +0 -84
- package/src/team_agent/fake_worker.py +0 -80
- package/src/team_agent/idle_predicate.py +0 -218
- package/src/team_agent/idle_takeover.py +0 -59
- package/src/team_agent/idle_takeover_wiring.py +0 -114
- package/src/team_agent/launch/__init__.py +0 -41
- package/src/team_agent/launch/bootstrap.py +0 -85
- package/src/team_agent/launch/config.py +0 -106
- package/src/team_agent/launch/core.py +0 -301
- package/src/team_agent/launch/requirements.py +0 -57
- package/src/team_agent/leader/__init__.py +0 -926
- package/src/team_agent/leader_binding.py +0 -183
- package/src/team_agent/lifecycle/__init__.py +0 -5
- package/src/team_agent/lifecycle/agents.py +0 -278
- package/src/team_agent/lifecycle/operations.py +0 -411
- package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
- package/src/team_agent/lifecycle/start.py +0 -363
- package/src/team_agent/mcp_server/__init__.py +0 -42
- package/src/team_agent/mcp_server/__main__.py +0 -7
- package/src/team_agent/mcp_server/contracts.py +0 -148
- package/src/team_agent/mcp_server/normalize.py +0 -257
- package/src/team_agent/mcp_server/server.py +0 -150
- package/src/team_agent/mcp_server/tools.py +0 -352
- package/src/team_agent/message_store/__init__.py +0 -23
- package/src/team_agent/message_store/agent_health.py +0 -113
- package/src/team_agent/message_store/core.py +0 -497
- package/src/team_agent/message_store/leader_notification_log.py +0 -198
- package/src/team_agent/message_store/result_watchers.py +0 -251
- package/src/team_agent/message_store/schema.py +0 -308
- package/src/team_agent/message_store/schema_migration.py +0 -448
- package/src/team_agent/messaging/__init__.py +0 -1
- package/src/team_agent/messaging/activity_detector.py +0 -262
- package/src/team_agent/messaging/delivery.py +0 -504
- package/src/team_agent/messaging/deps.py +0 -247
- package/src/team_agent/messaging/idle_alerts.py +0 -423
- package/src/team_agent/messaging/internal_delivery.py +0 -46
- package/src/team_agent/messaging/leader.py +0 -497
- package/src/team_agent/messaging/leader_api_errors.py +0 -216
- package/src/team_agent/messaging/leader_panes.py +0 -673
- package/src/team_agent/messaging/owner_bypass.py +0 -29
- package/src/team_agent/messaging/result_delivery.py +0 -539
- package/src/team_agent/messaging/results.py +0 -447
- package/src/team_agent/messaging/scheduler.py +0 -450
- package/src/team_agent/messaging/send.py +0 -532
- package/src/team_agent/messaging/session_drift.py +0 -94
- package/src/team_agent/messaging/tmux_io.py +0 -506
- package/src/team_agent/messaging/tmux_prompt.py +0 -338
- package/src/team_agent/messaging/trust_auto_answer.py +0 -52
- package/src/team_agent/orchestrator/__init__.py +0 -376
- package/src/team_agent/orchestrator/plan.py +0 -122
- package/src/team_agent/orchestrator/state.py +0 -128
- package/src/team_agent/paths.py +0 -45
- package/src/team_agent/permissions.py +0 -123
- package/src/team_agent/profiles/__init__.py +0 -82
- package/src/team_agent/profiles/constants.py +0 -19
- package/src/team_agent/profiles/core.py +0 -407
- package/src/team_agent/profiles/helpers.py +0 -69
- package/src/team_agent/profiles/provider_env.py +0 -188
- package/src/team_agent/profiles/smoke.py +0 -201
- package/src/team_agent/provider_cli/__init__.py +0 -43
- package/src/team_agent/provider_cli/adapter.py +0 -172
- package/src/team_agent/provider_cli/base.py +0 -48
- package/src/team_agent/provider_cli/claude.py +0 -503
- package/src/team_agent/provider_cli/codex.py +0 -336
- package/src/team_agent/provider_cli/copilot.py +0 -8
- package/src/team_agent/provider_cli/fake.py +0 -39
- package/src/team_agent/provider_cli/gemini.py +0 -95
- package/src/team_agent/provider_cli/opencode.py +0 -8
- package/src/team_agent/provider_cli/prompt.py +0 -62
- package/src/team_agent/provider_cli/registry.py +0 -18
- package/src/team_agent/provider_cli/unsupported.py +0 -32
- package/src/team_agent/provider_state/README.md +0 -78
- package/src/team_agent/provider_state/__init__.py +0 -91
- package/src/team_agent/provider_state/claude.py +0 -86
- package/src/team_agent/provider_state/codex.py +0 -84
- package/src/team_agent/provider_state/common.py +0 -207
- package/src/team_agent/provider_state/registry.py +0 -118
- package/src/team_agent/providers.py +0 -163
- package/src/team_agent/quality_gates.py +0 -104
- package/src/team_agent/restart/__init__.py +0 -34
- package/src/team_agent/restart/orchestration.py +0 -554
- package/src/team_agent/restart/selection.py +0 -89
- package/src/team_agent/restart/snapshot.py +0 -70
- package/src/team_agent/routing.py +0 -84
- package/src/team_agent/runtime.py +0 -1243
- package/src/team_agent/rust_core.py +0 -327
- package/src/team_agent/sessions/__init__.py +0 -25
- package/src/team_agent/sessions/capture.py +0 -144
- package/src/team_agent/sessions/inventory.py +0 -44
- package/src/team_agent/sessions/resume.py +0 -135
- package/src/team_agent/simple_yaml.py +0 -236
- package/src/team_agent/spec.py +0 -370
- package/src/team_agent/state.py +0 -693
- package/src/team_agent/status/__init__.py +0 -63
- package/src/team_agent/status/approvals.py +0 -52
- package/src/team_agent/status/compact.py +0 -158
- package/src/team_agent/status/constants.py +0 -18
- package/src/team_agent/status/inbox.py +0 -58
- package/src/team_agent/status/peek.py +0 -117
- package/src/team_agent/status/queries.py +0 -199
- package/src/team_agent/task_graph.py +0 -80
- package/src/team_agent/terminal.py +0 -57
- package/src/team_agent/wake.py +0 -58
- package/src/team_agent/watch/__init__.py +0 -145
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
//! R12 leader rediscover ambiguity-incident — core cluster (R12-0 two-tier + R12-1/2/3).
|
|
2
|
+
//! Offline byte-lock via rediscover_leader_receiver_from_targets_with_owner_identity (injected
|
|
3
|
+
//! &[PaneInfo], no real tmux). golden: messaging/leader_panes.py:101-187.
|
|
4
|
+
use super::*;
|
|
5
|
+
|
|
6
|
+
fn r12_ws(tag: &str) -> std::path::PathBuf {
|
|
7
|
+
let ws = std::env::temp_dir().join(format!(
|
|
8
|
+
"ta-r12-{}-{}-{}",
|
|
9
|
+
tag,
|
|
10
|
+
std::process::id(),
|
|
11
|
+
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
|
|
12
|
+
));
|
|
13
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
14
|
+
ws
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fn usable_target(pane: &str, cmd: &str) -> crate::transport::PaneInfo {
|
|
18
|
+
crate::transport::PaneInfo {
|
|
19
|
+
pane_id: crate::transport::PaneId::new(pane),
|
|
20
|
+
session: crate::transport::SessionName::new("s"),
|
|
21
|
+
window_index: Some(0),
|
|
22
|
+
window_name: None,
|
|
23
|
+
pane_index: Some(0),
|
|
24
|
+
tty: None,
|
|
25
|
+
current_command: Some(cmd.to_string()),
|
|
26
|
+
current_path: None,
|
|
27
|
+
active: true,
|
|
28
|
+
pane_pid: None,
|
|
29
|
+
leader_env: std::collections::BTreeMap::new(),
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn r12_events(ws: &std::path::Path) -> Vec<serde_json::Value> {
|
|
34
|
+
crate::event_log::EventLog::new(ws).tail(0).unwrap_or_default()
|
|
35
|
+
}
|
|
36
|
+
fn find_ev<'a>(evs: &'a [serde_json::Value], name: &str) -> Option<&'a serde_json::Value> {
|
|
37
|
+
evs.iter().rev().find(|e| e.get("event").and_then(|v| v.as_str()) == Some(name))
|
|
38
|
+
}
|
|
39
|
+
fn r12_keys(e: &serde_json::Value) -> std::collections::BTreeSet<String> {
|
|
40
|
+
e.as_object()
|
|
41
|
+
.map(|o| o.keys().filter(|k| *k != "ts" && *k != "event").cloned().collect())
|
|
42
|
+
.unwrap_or_default()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// usable target that matches an owner_identity via leader_session_uuid (drives the OWNER-ambiguous
|
|
46
|
+
// path D3: golden _rediscover_leader_receiver:133-145, owner present + >1 owner_candidates → broadcast).
|
|
47
|
+
fn owner_matching_target(pane: &str, cmd: &str, uuid: &str) -> crate::transport::PaneInfo {
|
|
48
|
+
let mut t = usable_target(pane, cmd);
|
|
49
|
+
t.leader_env
|
|
50
|
+
.insert("TEAM_AGENT_LEADER_SESSION_UUID".to_string(), uuid.to_string());
|
|
51
|
+
t
|
|
52
|
+
}
|
|
53
|
+
fn count_ev(evs: &[serde_json::Value], name: &str) -> usize {
|
|
54
|
+
evs.iter().filter(|e| e.get("event").and_then(|v| v.as_str()) == Some(name)).count()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// R12-0 + R12-3: NO owner_identity + 2 command-usable targets -> golden two-tier AMBIGUOUS (command-usable
|
|
58
|
+
// candidates, no owner filter), return key "candidates". Rust unified filter (command ∩ matches_owner_identity)
|
|
59
|
+
// with an empty identity matches nothing -> 0 candidates -> "missing" => loses the golden no-owner path.
|
|
60
|
+
#[test]
|
|
61
|
+
fn r12_no_owner_two_usable_targets_is_ambiguous_not_missing() {
|
|
62
|
+
let ws = r12_ws("noownerambig");
|
|
63
|
+
let log = crate::event_log::EventLog::new(&ws);
|
|
64
|
+
let mut state = serde_json::json!({"leader_receiver": {"pane_id": "%old", "provider": "codex"}});
|
|
65
|
+
let targets = vec![usable_target("%a", "codex"), usable_target("%b", "codex")];
|
|
66
|
+
let out = crate::leader::rediscover_leader_receiver_from_targets_with_owner_identity(
|
|
67
|
+
&ws, &mut state, &targets, &log, &serde_json::json!({}), None,
|
|
68
|
+
)
|
|
69
|
+
.unwrap();
|
|
70
|
+
assert_eq!(
|
|
71
|
+
out.get("status").and_then(|v| v.as_str()),
|
|
72
|
+
Some("ambiguous"),
|
|
73
|
+
"R12-0: no owner_identity + 2 command-usable -> golden two-tier AMBIGUOUS (command-usable, no owner \
|
|
74
|
+
filter); Rust unified filter loses the no-owner path -> missing. out={out:?}"
|
|
75
|
+
);
|
|
76
|
+
assert!(
|
|
77
|
+
out.get("candidates").is_some() && out.get("owner_candidates").is_none(),
|
|
78
|
+
"R12-3: ambiguous return key is 'candidates' (golden), not 'owner_candidates'; out={out:?}"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// R12-0/R12-1/R12-2: NO owner_identity + 0 command-usable -> golden no-owner MISSING (D7): rediscover_missing
|
|
83
|
+
// = {provider, old_target} LEAN; rebind_required = {..., rediscovery_status:"missing"} (no recovery_action/
|
|
84
|
+
// owner_identity). Rust attaches owner_identity + candidate_count + recovery_action.
|
|
85
|
+
#[test]
|
|
86
|
+
fn r12_no_owner_zero_usable_missing_is_lean_payload() {
|
|
87
|
+
let ws = r12_ws("noownermiss");
|
|
88
|
+
let log = crate::event_log::EventLog::new(&ws);
|
|
89
|
+
let mut state = serde_json::json!({"leader_receiver": {"pane_id": "%old", "provider": "codex"}});
|
|
90
|
+
let targets: Vec<crate::transport::PaneInfo> = vec![];
|
|
91
|
+
let out = crate::leader::rediscover_leader_receiver_from_targets_with_owner_identity(
|
|
92
|
+
&ws, &mut state, &targets, &log, &serde_json::json!({}), None,
|
|
93
|
+
)
|
|
94
|
+
.unwrap();
|
|
95
|
+
assert_eq!(out.get("status").and_then(|v| v.as_str()), Some("missing"));
|
|
96
|
+
let evs = r12_events(&ws);
|
|
97
|
+
let miss = find_ev(&evs, "leader_receiver.rediscover_missing").expect("rediscover_missing");
|
|
98
|
+
let mk = r12_keys(miss);
|
|
99
|
+
assert!(
|
|
100
|
+
!mk.contains("owner_identity") && !mk.contains("candidate_count"),
|
|
101
|
+
"R12-2: no-owner D7 rediscover_missing must be lean {{provider, old_target}} (golden leader_panes.py:185), \
|
|
102
|
+
not carry owner_identity/candidate_count; got {mk:?}"
|
|
103
|
+
);
|
|
104
|
+
let rebind = find_ev(&evs, "leader_receiver.rebind_required").expect("rebind_required");
|
|
105
|
+
let rk = r12_keys(rebind);
|
|
106
|
+
assert!(
|
|
107
|
+
!rk.contains("recovery_action") && !rk.contains("owner_identity"),
|
|
108
|
+
"R12-1: no-owner D7 rebind_required (golden leader_panes.py:186) has rediscovery_status only, NO \
|
|
109
|
+
recovery_action/owner_identity; got {rk:?}"
|
|
110
|
+
);
|
|
111
|
+
assert_eq!(rebind.get("rediscovery_status").and_then(|v| v.as_str()), Some("missing"));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// R12-1/R12-2: owner_identity PRESENT + non-matching + 2 command-usable -> golden owner-MISSING (D4):
|
|
115
|
+
// rediscover_missing candidate_count == len(command-usable)=2 + owner_identity; rebind_required carries
|
|
116
|
+
// recovery_action (golden string) + owner_identity + uuid_prefix, NO rediscovery_status. Rust: 0 candidates
|
|
117
|
+
// (post owner filter) -> candidate_count 0 + unified rebind (recovery_action="run team-agent attach-leader...").
|
|
118
|
+
#[test]
|
|
119
|
+
fn r12_owner_missing_candidate_count_is_command_usable_len_and_rebind_shape() {
|
|
120
|
+
let ws = r12_ws("ownermiss");
|
|
121
|
+
let log = crate::event_log::EventLog::new(&ws);
|
|
122
|
+
let mut state = serde_json::json!({"leader_receiver": {"pane_id": "%old", "provider": "codex"}});
|
|
123
|
+
let owner = serde_json::json!({
|
|
124
|
+
"pane_id": "%owner", "leader_session_uuid": "U-abcdef01",
|
|
125
|
+
"machine_fingerprint": "F", "provider": "codex", "team_id": "team-a"
|
|
126
|
+
});
|
|
127
|
+
let targets = vec![usable_target("%a", "codex"), usable_target("%b", "codex")];
|
|
128
|
+
let out = crate::leader::rediscover_leader_receiver_from_targets_with_owner_identity(
|
|
129
|
+
&ws, &mut state, &targets, &log, &owner, None,
|
|
130
|
+
)
|
|
131
|
+
.unwrap();
|
|
132
|
+
assert_eq!(out.get("status").and_then(|v| v.as_str()), Some("missing"));
|
|
133
|
+
let evs = r12_events(&ws);
|
|
134
|
+
let miss = find_ev(&evs, "leader_receiver.rediscover_missing").expect("rediscover_missing");
|
|
135
|
+
assert_eq!(
|
|
136
|
+
miss.get("candidate_count").and_then(|v| v.as_u64()),
|
|
137
|
+
Some(2),
|
|
138
|
+
"R12-2: owner-missing D4 candidate_count == len(command-usable)=2 (golden leader_panes.py:161, \
|
|
139
|
+
candidate_count is PRE owner-filter), not 0; miss={miss:?}"
|
|
140
|
+
);
|
|
141
|
+
let rebind = find_ev(&evs, "leader_receiver.rebind_required").expect("rebind_required");
|
|
142
|
+
assert_eq!(
|
|
143
|
+
rebind.get("recovery_action").and_then(|v| v.as_str()),
|
|
144
|
+
Some("open the owning leader pane or run team-agent claim-leader --confirm from a matching pane"),
|
|
145
|
+
"R12-1: owner-missing D4 rebind recovery_action == golden string (leader_panes.py:171)"
|
|
146
|
+
);
|
|
147
|
+
assert!(
|
|
148
|
+
rebind.get("rediscovery_status").is_none(),
|
|
149
|
+
"R12-1: owner-missing D4 rebind has NO rediscovery_status (golden D4); rebind={rebind:?}"
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// R12-5: owner present + 2 owner-matching usable -> OWNER-ambiguous (D3) broadcasts the
|
|
154
|
+
// leader_receiver.ambiguous_candidates event. golden payload (probe-captured, sort_keys):
|
|
155
|
+
// {candidates:[sorted pane_ids], debounce_bucket, incident_id, old_pane_id, provider,
|
|
156
|
+
// reason:"force_confirm_required", team_id, uuid_prefix}.
|
|
157
|
+
// Rust emit_ambiguous_candidates (rediscover.rs:650) emits key "pane_ids" (not "candidates") and
|
|
158
|
+
// embeds owner_identity + invalidation_reason + queued[] (golden has none of these in this event;
|
|
159
|
+
// the per-candidate queue is R12-6's separate ambiguous_candidate_queued events).
|
|
160
|
+
#[test]
|
|
161
|
+
fn r12_5_ambiguous_candidates_event_is_full_golden_candidates_key() {
|
|
162
|
+
let ws = r12_ws("ambigbroadcast");
|
|
163
|
+
let log = crate::event_log::EventLog::new(&ws);
|
|
164
|
+
let mut state = serde_json::json!({"leader_receiver": {"pane_id": "%old", "provider": "codex"}});
|
|
165
|
+
let owner = serde_json::json!({
|
|
166
|
+
"pane_id": "%owner", "leader_session_uuid": "U-abcdef01",
|
|
167
|
+
"machine_fingerprint": "F", "provider": "codex", "team_id": "team-a"
|
|
168
|
+
});
|
|
169
|
+
let targets = vec![
|
|
170
|
+
owner_matching_target("%b", "codex", "U-abcdef01"),
|
|
171
|
+
owner_matching_target("%a", "codex", "U-abcdef01"),
|
|
172
|
+
];
|
|
173
|
+
let out = crate::leader::rediscover_leader_receiver_from_targets_with_owner_identity(
|
|
174
|
+
&ws, &mut state, &targets, &log, &owner, None,
|
|
175
|
+
)
|
|
176
|
+
.unwrap();
|
|
177
|
+
assert_eq!(out.get("status").and_then(|v| v.as_str()), Some("ambiguous"));
|
|
178
|
+
let evs = r12_events(&ws);
|
|
179
|
+
let bc = find_ev(&evs, "leader_receiver.ambiguous_candidates")
|
|
180
|
+
.expect("OWNER-ambiguous must broadcast leader_receiver.ambiguous_candidates");
|
|
181
|
+
let k = r12_keys(bc);
|
|
182
|
+
assert!(
|
|
183
|
+
k.contains("candidates") && !k.contains("pane_ids"),
|
|
184
|
+
"R12-5: broadcast key is 'candidates' (golden _broadcast_ambiguous_candidates:270), not 'pane_ids'; got {k:?}"
|
|
185
|
+
);
|
|
186
|
+
assert!(
|
|
187
|
+
!k.contains("queued") && !k.contains("owner_identity") && !k.contains("invalidation_reason"),
|
|
188
|
+
"R12-5: golden ambiguous_candidates carries NO queued[]/owner_identity/invalidation_reason (per-candidate \
|
|
189
|
+
queue is the separate ambiguous_candidate_queued event, R12-6); got {k:?}"
|
|
190
|
+
);
|
|
191
|
+
assert_eq!(
|
|
192
|
+
bc.get("candidates").cloned(),
|
|
193
|
+
Some(serde_json::json!(["%a", "%b"])),
|
|
194
|
+
"R12-5: candidates == sorted pane_ids (golden candidate_ids = sorted); bc={bc:?}"
|
|
195
|
+
);
|
|
196
|
+
assert_eq!(bc.get("reason").and_then(|v| v.as_str()), Some("force_confirm_required"));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// R12-5 dedup: golden _broadcast_ambiguous_candidates:263 skips re-broadcast when a prior
|
|
200
|
+
// ambiguous_candidates with the same incident_id is in event_log.tail(200) (returns deduped:true).
|
|
201
|
+
// Rust incident_id is deterministic (✦ R12-4), so a repeat drive yields the SAME incident_id; Rust
|
|
202
|
+
// hardcodes deduped:false (rediscover.rs:245/255) and re-emits every time -> RED (2 events, golden 1).
|
|
203
|
+
#[test]
|
|
204
|
+
fn r12_5_ambiguous_candidates_dedups_on_repeat_incident() {
|
|
205
|
+
let ws = r12_ws("ambigdedup");
|
|
206
|
+
let log = crate::event_log::EventLog::new(&ws);
|
|
207
|
+
let mut state = serde_json::json!({"leader_receiver": {"pane_id": "%old", "provider": "codex"}});
|
|
208
|
+
let owner = serde_json::json!({
|
|
209
|
+
"pane_id": "%owner", "leader_session_uuid": "U-abcdef01",
|
|
210
|
+
"machine_fingerprint": "F", "provider": "codex", "team_id": "team-a"
|
|
211
|
+
});
|
|
212
|
+
let targets = vec![
|
|
213
|
+
owner_matching_target("%a", "codex", "U-abcdef01"),
|
|
214
|
+
owner_matching_target("%b", "codex", "U-abcdef01"),
|
|
215
|
+
];
|
|
216
|
+
for _ in 0..2 {
|
|
217
|
+
crate::leader::rediscover_leader_receiver_from_targets_with_owner_identity(
|
|
218
|
+
&ws, &mut state, &targets, &log, &owner, None,
|
|
219
|
+
)
|
|
220
|
+
.unwrap();
|
|
221
|
+
}
|
|
222
|
+
let evs = r12_events(&ws);
|
|
223
|
+
assert_eq!(
|
|
224
|
+
count_ev(&evs, "leader_receiver.ambiguous_candidates"),
|
|
225
|
+
1,
|
|
226
|
+
"R12-5: repeat drive with the same incident_id must dedup the broadcast (golden tail(200) skip, \
|
|
227
|
+
leader_panes.py:263); Rust re-emits every time. ambiguous_candidates count={}",
|
|
228
|
+
count_ev(&evs, "leader_receiver.ambiguous_candidates")
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// R12-6: golden _broadcast_ambiguous_candidates:279-294 loops candidates and emits a SEPARATE
|
|
233
|
+
// leader_receiver.ambiguous_candidate_queued event per candidate (probe-captured keys
|
|
234
|
+
// {error, event, incident_id, ok, pane_id}), tagged with the broadcast's incident_id. Rust embeds a
|
|
235
|
+
// queued:[{pane_id,...}] array INSIDE the single ambiguous_candidates event (rediscover.rs:663) and
|
|
236
|
+
// emits ZERO per-candidate events. NOTE (surfaced): the ok/error VALUES come from the tmux inject
|
|
237
|
+
// (real-tmux); the offline from_targets seam can assert structure (separate events, incident_id,
|
|
238
|
+
// pane_id) but not the inject result — the porter/leader decide the offline ok/error semantics.
|
|
239
|
+
#[test]
|
|
240
|
+
fn r12_6_per_candidate_emits_separate_ambiguous_candidate_queued_events() {
|
|
241
|
+
let ws = r12_ws("percand");
|
|
242
|
+
let log = crate::event_log::EventLog::new(&ws);
|
|
243
|
+
let mut state = serde_json::json!({"leader_receiver": {"pane_id": "%old", "provider": "codex"}});
|
|
244
|
+
let owner = serde_json::json!({
|
|
245
|
+
"pane_id": "%owner", "leader_session_uuid": "U-abcdef01",
|
|
246
|
+
"machine_fingerprint": "F", "provider": "codex", "team_id": "team-a"
|
|
247
|
+
});
|
|
248
|
+
let targets = vec![
|
|
249
|
+
owner_matching_target("%a", "codex", "U-abcdef01"),
|
|
250
|
+
owner_matching_target("%b", "codex", "U-abcdef01"),
|
|
251
|
+
];
|
|
252
|
+
crate::leader::rediscover_leader_receiver_from_targets_with_owner_identity(
|
|
253
|
+
&ws, &mut state, &targets, &log, &owner, None,
|
|
254
|
+
)
|
|
255
|
+
.unwrap();
|
|
256
|
+
let evs = r12_events(&ws);
|
|
257
|
+
let n = count_ev(&evs, "leader_receiver.ambiguous_candidate_queued");
|
|
258
|
+
assert_eq!(
|
|
259
|
+
n, 2,
|
|
260
|
+
"R12-6: each candidate must emit a SEPARATE leader_receiver.ambiguous_candidate_queued event \
|
|
261
|
+
(golden leader_panes.py:288); Rust embeds queued[] in the single ambiguous_candidates event and \
|
|
262
|
+
emits none. count={n}"
|
|
263
|
+
);
|
|
264
|
+
let qpanes: std::collections::BTreeSet<String> = evs
|
|
265
|
+
.iter()
|
|
266
|
+
.filter(|e| e.get("event").and_then(|v| v.as_str()) == Some("leader_receiver.ambiguous_candidate_queued"))
|
|
267
|
+
.filter_map(|e| e.get("pane_id").and_then(|v| v.as_str()).map(str::to_string))
|
|
268
|
+
.collect();
|
|
269
|
+
assert_eq!(
|
|
270
|
+
qpanes,
|
|
271
|
+
["%a".to_string(), "%b".to_string()].into_iter().collect(),
|
|
272
|
+
"R12-6: per-candidate queued events cover each candidate pane_id; got {qpanes:?}"
|
|
273
|
+
);
|
|
274
|
+
let bc = find_ev(&evs, "leader_receiver.ambiguous_candidates").expect("broadcast");
|
|
275
|
+
let iid = bc.get("incident_id").cloned();
|
|
276
|
+
let q = find_ev(&evs, "leader_receiver.ambiguous_candidate_queued").expect("queued");
|
|
277
|
+
assert_eq!(
|
|
278
|
+
q.get("incident_id").cloned(),
|
|
279
|
+
iid,
|
|
280
|
+
"R12-6: each ambiguous_candidate_queued carries the broadcast incident_id (golden :290); q={q:?}"
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// helper: pull the candidate-raw array from the ambiguous return regardless of the R12-3 key rename.
|
|
285
|
+
fn r12_return_candidates(out: &serde_json::Value) -> &Vec<serde_json::Value> {
|
|
286
|
+
out.get("candidates")
|
|
287
|
+
.or_else(|| out.get("owner_candidates"))
|
|
288
|
+
.and_then(|v| v.as_array())
|
|
289
|
+
.expect("ambiguous return must carry a candidate array")
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// R12-7: golden returns the candidate RAW as the passthrough tmux target dict (probe-captured keys
|
|
293
|
+
// {leader_session_uuid, pane_current_command, pane_current_path, pane_id}). Rust reconstructs via
|
|
294
|
+
// pane_info_value (rediscover.rs:568) with "current_command"/"current_path". Assert golden spelling.
|
|
295
|
+
#[test]
|
|
296
|
+
fn r12_7_candidate_raw_uses_golden_pane_current_spelling() {
|
|
297
|
+
let ws = r12_ws("rawkeys");
|
|
298
|
+
let log = crate::event_log::EventLog::new(&ws);
|
|
299
|
+
let mut state = serde_json::json!({"leader_receiver": {"pane_id": "%old", "provider": "codex"}});
|
|
300
|
+
let owner = serde_json::json!({
|
|
301
|
+
"pane_id": "%owner", "leader_session_uuid": "U-abcdef01",
|
|
302
|
+
"machine_fingerprint": "F", "provider": "codex", "team_id": "team-a"
|
|
303
|
+
});
|
|
304
|
+
let targets = vec![
|
|
305
|
+
owner_matching_target("%a", "codex", "U-abcdef01"),
|
|
306
|
+
owner_matching_target("%b", "codex", "U-abcdef01"),
|
|
307
|
+
];
|
|
308
|
+
let out = crate::leader::rediscover_leader_receiver_from_targets_with_owner_identity(
|
|
309
|
+
&ws, &mut state, &targets, &log, &owner, None,
|
|
310
|
+
)
|
|
311
|
+
.unwrap();
|
|
312
|
+
let c0 = &r12_return_candidates(&out)[0];
|
|
313
|
+
let k = r12_keys(c0);
|
|
314
|
+
assert!(
|
|
315
|
+
k.contains("pane_current_command") && k.contains("pane_current_path"),
|
|
316
|
+
"R12-7: candidate raw uses golden tmux spelling pane_current_command/pane_current_path (passthrough); \
|
|
317
|
+
got {k:?}"
|
|
318
|
+
);
|
|
319
|
+
assert!(
|
|
320
|
+
!k.contains("current_command") && !k.contains("current_path"),
|
|
321
|
+
"R12-7: Rust pane_info_value spelling current_command/current_path must not leak; got {k:?}"
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// R12-7: golden provider = str(receiver.provider or "codex") (leader_panes.py:108) — DEFAULTS to "codex"
|
|
326
|
+
// when absent, applied to every emitted event's provider field. Rust uses identity.provider (null when
|
|
327
|
+
// absent). Drive with no provider anywhere -> golden event provider "codex", Rust null.
|
|
328
|
+
#[test]
|
|
329
|
+
fn r12_7_event_provider_defaults_to_codex_when_absent() {
|
|
330
|
+
let ws = r12_ws("providerdefault");
|
|
331
|
+
let log = crate::event_log::EventLog::new(&ws);
|
|
332
|
+
let mut state = serde_json::json!({"leader_receiver": {"pane_id": "%old"}});
|
|
333
|
+
let owner = serde_json::json!({"leader_session_uuid": "U-abcdef01", "team_id": "team-a"});
|
|
334
|
+
let targets = vec![
|
|
335
|
+
owner_matching_target("%a", "codex", "U-abcdef01"),
|
|
336
|
+
owner_matching_target("%b", "codex", "U-abcdef01"),
|
|
337
|
+
];
|
|
338
|
+
let out = crate::leader::rediscover_leader_receiver_from_targets_with_owner_identity(
|
|
339
|
+
&ws, &mut state, &targets, &log, &owner, None,
|
|
340
|
+
)
|
|
341
|
+
.unwrap();
|
|
342
|
+
assert_eq!(out.get("status").and_then(|v| v.as_str()), Some("ambiguous"));
|
|
343
|
+
let evs = r12_events(&ws);
|
|
344
|
+
let amb = find_ev(&evs, "leader_receiver.rediscover_ambiguous").expect("rediscover_ambiguous");
|
|
345
|
+
assert_eq!(
|
|
346
|
+
amb.get("provider").and_then(|v| v.as_str()),
|
|
347
|
+
Some("codex"),
|
|
348
|
+
"R12-7: event provider defaults to \"codex\" when absent (golden leader_panes.py:108 \
|
|
349
|
+
receiver.provider or 'codex'); Rust emits identity.provider=null. amb={amb:?}"
|
|
350
|
+
);
|
|
351
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
// =====================================================================
|
|
4
|
+
// 4. wake 层纯函数(unimplemented → RED)。golden:probe_leader.py 5 分支 + boundary。
|
|
5
|
+
// =====================================================================
|
|
6
|
+
|
|
7
|
+
#[test]
|
|
8
|
+
fn should_reread_no_file_when_current_mtime_none() {
|
|
9
|
+
// current_mtime is None → {reread:false, no_file}(wake.py:31-32)。
|
|
10
|
+
let d = should_reread(None, None, None, 100.0, 60.0);
|
|
11
|
+
assert!(!d.reread);
|
|
12
|
+
assert_eq!(d.reason, RereadReason::NoFile);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#[test]
|
|
16
|
+
fn should_reread_never_classified() {
|
|
17
|
+
// last_classified_mtime is None → {reread:true, never_classified}。
|
|
18
|
+
let d = should_reread(None, Some(10.0), None, 100.0, 60.0);
|
|
19
|
+
assert!(d.reread);
|
|
20
|
+
assert_eq!(d.reason, RereadReason::NeverClassified);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[test]
|
|
24
|
+
fn should_reread_file_changed() {
|
|
25
|
+
// current != last_classified → {reread:true, file_changed}。
|
|
26
|
+
let d = should_reread(Some(5.0), Some(20.0), Some(10.0), 100.0, 60.0);
|
|
27
|
+
assert!(d.reread);
|
|
28
|
+
assert_eq!(d.reason, RereadReason::FileChanged);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#[test]
|
|
32
|
+
fn should_reread_quiescent_already_classified_at_and_past_debounce() {
|
|
33
|
+
// current==last_classified 且 silent_for >= debounce → quiescent(不再重读)。
|
|
34
|
+
// silent_for = max(0, now-current_mtime) = 100-10 = 90 >= 60。
|
|
35
|
+
let d = should_reread(Some(10.0), Some(10.0), Some(10.0), 100.0, 60.0);
|
|
36
|
+
assert!(!d.reread);
|
|
37
|
+
assert_eq!(d.reason, RereadReason::QuiescentAlreadyClassified);
|
|
38
|
+
// 边界:silent_for 恰 == debounce(now-current=60)→ 仍 quiescent(>= 比较)。
|
|
39
|
+
let b = should_reread(Some(40.0), Some(40.0), Some(40.0), 100.0, 60.0);
|
|
40
|
+
assert!(!b.reread);
|
|
41
|
+
assert_eq!(b.reason, RereadReason::QuiescentAlreadyClassified);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[test]
|
|
45
|
+
fn should_reread_unchanged_within_debounce() {
|
|
46
|
+
// current==last_classified 且 silent_for < debounce → unchanged。
|
|
47
|
+
// now-current = 100-95 = 5 < 60。注:last_mtime 在 Python body 中未被使用。
|
|
48
|
+
let d = should_reread(Some(10.0), Some(95.0), Some(95.0), 100.0, 60.0);
|
|
49
|
+
assert!(!d.reread);
|
|
50
|
+
assert_eq!(d.reason, RereadReason::Unchanged);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#[test]
|
|
54
|
+
fn on_file_changed_adds_node_sorted_and_records_mtime() {
|
|
55
|
+
// golden probe_leader.py:add b → pending ["b"];add a → sorted ["a","b"]。
|
|
56
|
+
let s0 = on_file_changed(None, "b", 1.0);
|
|
57
|
+
assert_eq!(s0.pending, vec!["b".to_string()]);
|
|
58
|
+
assert_eq!(s0.mtimes.get("b"), Some(&1.0));
|
|
59
|
+
let s1 = on_file_changed(Some(&s0), "a", 2.0);
|
|
60
|
+
assert_eq!(s1.pending, vec!["a".to_string(), "b".to_string()], "pending 必须 sorted");
|
|
61
|
+
assert_eq!(s1.mtimes.get("a"), Some(&2.0));
|
|
62
|
+
// 重复 add b:pending 仍是 set(不重复),mtime 被更新到 3.0。
|
|
63
|
+
let s2 = on_file_changed(Some(&s1), "b", 3.0);
|
|
64
|
+
assert_eq!(s2.pending, vec!["a".to_string(), "b".to_string()]);
|
|
65
|
+
assert_eq!(s2.mtimes.get("b"), Some(&3.0));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#[test]
|
|
69
|
+
fn take_pending_drains_sorted_and_clears_pending_but_keeps_mtimes() {
|
|
70
|
+
// golden probe_leader.py:drain → (["a","b"], state{pending:[], mtimes 保留})。
|
|
71
|
+
let mut st = on_file_changed(None, "b", 3.0);
|
|
72
|
+
st = on_file_changed(Some(&st), "a", 2.0);
|
|
73
|
+
let (drained, after) = take_pending(Some(&st));
|
|
74
|
+
assert_eq!(drained, vec!["a".to_string(), "b".to_string()]);
|
|
75
|
+
assert!(after.pending.is_empty());
|
|
76
|
+
// mtimes 不被 drain 清空。
|
|
77
|
+
assert_eq!(after.mtimes.get("a"), Some(&2.0));
|
|
78
|
+
assert_eq!(after.mtimes.get("b"), Some(&3.0));
|
|
79
|
+
// 再 drain → 空。
|
|
80
|
+
let (drained2, _after2) = take_pending(Some(&after));
|
|
81
|
+
assert!(drained2.is_empty());
|
|
82
|
+
// drain None → ([], default state)。
|
|
83
|
+
let (none_drained, none_state) = take_pending(None);
|
|
84
|
+
assert!(none_drained.is_empty());
|
|
85
|
+
assert!(none_state.pending.is_empty());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// =====================================================================
|
|
89
|
+
// 5. leader_session_name — sha1 派生 + 文件夹消毒(unimplemented → RED)
|
|
90
|
+
// =====================================================================
|
|
91
|
+
|
|
92
|
+
// 公式:team-agent-leader-<provider>-<sanitized folder[:48]>-<sha1(resolve(ws))[:8]>。
|
|
93
|
+
// 用真实 temp 目录,sha1/sanitize 在测试内复算后断言函数输出与之一致(probe 已验证公式)。
|
|
94
|
+
#[test]
|
|
95
|
+
fn leader_session_name_formula_and_sanitization() {
|
|
96
|
+
// 公式 = team-agent-leader-<provider>-<sanitized folder>-<sha1(resolve(ws))[:8]>。
|
|
97
|
+
// sha1 复算需 sha1 crate(本测试不引);改为断言格式不变量(provider/消毒/8-hex 后缀),
|
|
98
|
+
// 字节级 sha1 由 golden probe_leader.py 已验证公式正确。
|
|
99
|
+
let base = std::env::temp_dir().join(format!("ta_rs_lsn_{}", std::process::id()));
|
|
100
|
+
let weird = base.join("My Proj!@#name");
|
|
101
|
+
std::fs::create_dir_all(&weird).unwrap();
|
|
102
|
+
let got = leader_session_name(Provider::Codex, &weird);
|
|
103
|
+
// 前缀 + provider + 消毒后的 folder(非字母数字_.- → '_')。
|
|
104
|
+
let s = got.as_str();
|
|
105
|
+
assert!(s.starts_with("team-agent-leader-codex-My_Proj___name-"), "got {s}");
|
|
106
|
+
// sha1[:8] 后缀:8 个 hex。
|
|
107
|
+
let suffix = s.rsplit('-').next().unwrap();
|
|
108
|
+
assert_eq!(suffix.len(), 8, "sha1 前缀须 8 hex,got {suffix}");
|
|
109
|
+
assert!(suffix.chars().all(|c| c.is_ascii_hexdigit()));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// folder 名消毒成空 → 回退 "workspace"(probe_leader.py allsym 用例)。
|
|
113
|
+
#[test]
|
|
114
|
+
fn leader_session_name_empty_sanitized_folder_falls_back_to_workspace() {
|
|
115
|
+
let base = std::env::temp_dir().join(format!("ta_rs_lsn2_{}", std::process::id()));
|
|
116
|
+
// 全符号目录名 → 消毒后 strip('._-') 为空 → "workspace"。
|
|
117
|
+
let allsym = base.join("@@@");
|
|
118
|
+
std::fs::create_dir_all(&allsym).unwrap();
|
|
119
|
+
let got = leader_session_name(Provider::Codex, &allsym);
|
|
120
|
+
assert!(
|
|
121
|
+
got.as_str().contains("-workspace-"),
|
|
122
|
+
"全符号 folder 应回退 'workspace',got {}",
|
|
123
|
+
got.as_str()
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// claude_code provider 出现在 session 名里(probe:team-agent-leader-claude_code-...)。
|
|
128
|
+
#[test]
|
|
129
|
+
fn leader_session_name_uses_claude_code_provider_string() {
|
|
130
|
+
let base = std::env::temp_dir().join(format!("ta_rs_lsn3_{}", std::process::id()));
|
|
131
|
+
let dir = base.join("proj");
|
|
132
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
133
|
+
let got = leader_session_name(Provider::ClaudeCode, &dir);
|
|
134
|
+
assert!(got.as_str().starts_with("team-agent-leader-claude_code-proj-"), "got {}", got.as_str());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// =====================================================================
|
|
138
|
+
// 6. Family A 正源 owner 绑定 — bind_owner_from_caller_pane(unimplemented → RED)
|
|
139
|
+
// =====================================================================
|
|
140
|
+
|
|
141
|
+
// $TMUX_PANE 缺 → refuse + reason=caller_pane_missing(leader_binding.py:79-95)。
|
|
142
|
+
// 此处只能在 $TMUX_PANE 缺失环境下断言(测试进程通常无 TMUX_PANE)。
|
|
143
|
+
#[test]
|
|
144
|
+
fn bind_owner_refuses_when_caller_pane_missing() {
|
|
145
|
+
// 防御:确保本测试看到的环境无 TMUX_PANE(若 CI 在 tmux 内跑,跳过断言形态)。
|
|
146
|
+
if std::env::var_os("TMUX_PANE").is_some() {
|
|
147
|
+
// 在 tmux 内:正源存在,不该走 refuse 分支;此用例只验缺失分支,直接返回。
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_bind_{}", std::process::id()));
|
|
151
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
152
|
+
let team = TeamKey::new("default");
|
|
153
|
+
let res = bind_owner_from_caller_pane(&ws, &team, None).unwrap();
|
|
154
|
+
assert!(!res.ok);
|
|
155
|
+
assert_eq!(res.reason, Some(LeaseReason::CallerPaneMissing));
|
|
156
|
+
// caller_pane_id 为空(probe:""),hint 为 _HINT_RUN_FROM_LEADER_PANE。
|
|
157
|
+
assert_eq!(res.caller_pane_id, PaneId::new(""));
|
|
158
|
+
assert_eq!(res.caller_current_command, "");
|
|
159
|
+
assert_eq!(
|
|
160
|
+
res.hint.as_deref(),
|
|
161
|
+
Some("run team-agent from inside your leader pane (the tmux pane you want to own this team).")
|
|
162
|
+
);
|
|
163
|
+
assert_eq!(res.team_id, team);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// owner.bind_refused 事件名字节锁(LeaderEvent::name unimplemented → RED;与 #5 重叠但锁 binding 路径)。
|
|
167
|
+
#[test]
|
|
168
|
+
fn owner_bind_refused_event_name_is_owner_bind_refused() {
|
|
169
|
+
assert_eq!(LeaderEvent::OwnerBindRefused.name(), "owner.bind_refused");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// emit_owner_bound_event:成功绑定 hook(owner.bound_from_caller_pane;leader_binding.py:162-183)。
|
|
173
|
+
// 强化(no-full-uuid-leak 命门):事件只写 derived_uuid_prefix == derived[:12](12 hex),
|
|
174
|
+
// old uuid 为 None → old_uuid_prefix == ""(空串,非缺省);全 32 hex uuid 绝不出现在任何字段。
|
|
175
|
+
// unimplemented → RED。
|
|
176
|
+
#[test]
|
|
177
|
+
fn emit_owner_bound_event_logs_prefix_only_never_full_uuid() {
|
|
178
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_emit_{}", std::process::id()));
|
|
179
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
180
|
+
let caller = PaneId::new("%7");
|
|
181
|
+
let derived = uuid("fp", "/ws", "u", "default");
|
|
182
|
+
let full = derived.as_str().to_string();
|
|
183
|
+
assert_eq!(full.len(), 32, "derive 产 32 hex");
|
|
184
|
+
let prefix12 = full[..12].to_string();
|
|
185
|
+
emit_owner_bound_event(&ws, &caller, "codex", &derived, &TeamKey::new("default"), None).unwrap();
|
|
186
|
+
// 读回审计事件:恰一条 owner.bound_from_caller_pane。
|
|
187
|
+
let events = crate::event_log::EventLog::new(&ws).tail(50).unwrap();
|
|
188
|
+
let ev = events
|
|
189
|
+
.iter()
|
|
190
|
+
.find(|e| e["event"] == serde_json::json!("owner.bound_from_caller_pane"))
|
|
191
|
+
.expect("必写 owner.bound_from_caller_pane");
|
|
192
|
+
assert_eq!(ev["caller_pane_id"], serde_json::json!("%7"));
|
|
193
|
+
assert_eq!(ev["caller_current_command"], serde_json::json!("codex"));
|
|
194
|
+
assert_eq!(ev["team_id"], serde_json::json!("default"));
|
|
195
|
+
// derived_uuid_prefix == derived[:12](只前缀,12 hex)。
|
|
196
|
+
assert_eq!(ev["derived_uuid_prefix"], serde_json::json!(prefix12));
|
|
197
|
+
// old uuid=None → old_uuid_prefix == ""(空串,非 null/缺省;golden probe 已验)。
|
|
198
|
+
assert_eq!(ev["old_uuid_prefix"], serde_json::json!(""));
|
|
199
|
+
// no-full-uuid-leak:整条事件序列化文本里绝不出现完整 32-hex uuid。
|
|
200
|
+
let raw = serde_json::to_string(ev).unwrap();
|
|
201
|
+
assert!(!raw.contains(&full), "审计事件绝不泄露完整 leader_session_uuid");
|
|
202
|
+
// 审计事件名字节锁。
|
|
203
|
+
assert_eq!(LeaderEvent::OwnerBoundFromCallerPane.name(), "owner.bound_from_caller_pane");
|
|
204
|
+
}
|