@team-agent/installer 0.2.10 → 0.3.0
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 +1077 -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 +1141 -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 +436 -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 +1063 -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 +525 -0
- package/crates/team-agent/src/leader/rediscover.rs +1099 -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 +234 -0
- package/crates/team-agent/src/leader/tests/identity.rs +206 -0
- package/crates/team-agent/src/leader/tests/idle.rs +271 -0
- package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
- package/crates/team-agent/src/leader/tests/lease_claim.rs +253 -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 +487 -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 +1833 -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 +933 -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 +685 -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 +159 -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 +388 -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 +542 -0
- package/crates/team-agent/src/messaging/helpers.rs +209 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +340 -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 +537 -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 +582 -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 +656 -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 +586 -0
- package/crates/team-agent/src/tmux_backend.rs +758 -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 +90 -106
- 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 -83
- package/src/team_agent/coordinator/lifecycle.py +0 -363
- 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 -200
- package/src/team_agent/idle_takeover.py +0 -59
- package/src/team_agent/idle_takeover_wiring.py +0 -111
- 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 -254
- package/src/team_agent/messaging/delivery.py +0 -473
- 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 -457
- 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 -86
- 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 -1239
- 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 -143
- 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 -602
- 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,265 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
use super::agent_ops::lanea_team_ws;
|
|
3
|
+
use super::lane_ops::{LaneSpawns, LaneTransport};
|
|
4
|
+
use super::launch_spawn::{
|
|
5
|
+
restart_ws_two_resumable_workers, seed_healthy_coordinator, DELEG_ROLE_ALPHA, DELEG_ROLE_BRAVO,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type RecordedSpawns = LaneSpawns;
|
|
9
|
+
|
|
10
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
11
|
+
// BUG-1 [highest · regression] — respawn into a DEAD tmux session/`-L` server
|
|
12
|
+
// must RE-CREATE the session (new-session / spawn_first), NOT add a window to a
|
|
13
|
+
// server that no longer exists (new-window / spawn_into → "no server running" →
|
|
14
|
+
// orphaned agent). `reset --discard-session` kills the agent's last window; tmux
|
|
15
|
+
// then destroys the whole session AND its CP-1 `-L` server. The respawn that
|
|
16
|
+
// follows must therefore new-session.
|
|
17
|
+
//
|
|
18
|
+
// Golden runtime.py:1017 `_tmux_start_command_for_agent_window`:
|
|
19
|
+
// if _tmux_session_exists(session_name): # live `tmux has-session -t <name>`
|
|
20
|
+
// return ["tmux","new-window",...], "new-window"
|
|
21
|
+
// return ["tmux","new-session","-d",...], "new-session"
|
|
22
|
+
// The new-session-vs-new-window choice is driven by a LIVE `has-session` probe,
|
|
23
|
+
// NOT by whether state["session_name"] is a non-empty string.
|
|
24
|
+
//
|
|
25
|
+
// Today `start_agent_with_transport` computes `into_existing_session =
|
|
26
|
+
// session_name_present(&state)` (a pure state-string check; restart.rs:129) and
|
|
27
|
+
// NEVER consults `transport.has_session(...)` — so a discarded/dead session
|
|
28
|
+
// (state string still set) takes the spawn_into branch. The socket fix
|
|
29
|
+
// (0f886a9/4435f9a) covered status/shutdown but missed this lifecycle respawn path.
|
|
30
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
31
|
+
/// Recording transport with a CONFIGURABLE `has_session` — models the real
|
|
32
|
+
/// `tmux has-session` probe the respawn spawn-decision MUST consult. Records
|
|
33
|
+
/// every spawn (kind + argv) like `RecordingLaunchTransport`.
|
|
34
|
+
struct SessionProbeRecordingTransport {
|
|
35
|
+
spawns: RecordedSpawns,
|
|
36
|
+
session_exists: bool,
|
|
37
|
+
}
|
|
38
|
+
impl crate::transport::Transport for SessionProbeRecordingTransport {
|
|
39
|
+
fn kind(&self) -> crate::transport::BackendKind {
|
|
40
|
+
crate::transport::BackendKind::Tmux
|
|
41
|
+
}
|
|
42
|
+
fn spawn_first(&self, session: &crate::transport::SessionName, window: &crate::transport::WindowName, argv: &[String], _cwd: &std::path::Path, _env: &std::collections::BTreeMap<String, String>) -> Result<crate::transport::SpawnResult, crate::transport::TransportError> {
|
|
43
|
+
self.spawns.lock().unwrap().push(("spawn_first".to_string(), argv.to_vec()));
|
|
44
|
+
Ok(crate::transport::SpawnResult { pane_id: crate::transport::PaneId::new("%0"), session: session.clone(), window: window.clone(), child_pid: None })
|
|
45
|
+
}
|
|
46
|
+
fn spawn_into(&self, session: &crate::transport::SessionName, window: &crate::transport::WindowName, argv: &[String], _cwd: &std::path::Path, _env: &std::collections::BTreeMap<String, String>) -> Result<crate::transport::SpawnResult, crate::transport::TransportError> {
|
|
47
|
+
self.spawns.lock().unwrap().push(("spawn_into".to_string(), argv.to_vec()));
|
|
48
|
+
Ok(crate::transport::SpawnResult { pane_id: crate::transport::PaneId::new(format!("%{}", window.as_str())), session: session.clone(), window: window.clone(), child_pid: None })
|
|
49
|
+
}
|
|
50
|
+
fn inject(&self, _t: &crate::transport::Target, _p: &crate::transport::InjectPayload, _s: crate::transport::Key, _b: bool) -> Result<crate::transport::InjectReport, crate::transport::TransportError> {
|
|
51
|
+
unimplemented!("not reached by start_agent respawn")
|
|
52
|
+
}
|
|
53
|
+
fn send_keys(&self, _t: &crate::transport::Target, _k: &[crate::transport::Key]) -> Result<(), crate::transport::TransportError> {
|
|
54
|
+
unimplemented!("not reached")
|
|
55
|
+
}
|
|
56
|
+
fn capture(&self, _t: &crate::transport::Target, _r: crate::transport::CaptureRange) -> Result<crate::transport::CapturedText, crate::transport::TransportError> {
|
|
57
|
+
unimplemented!("not reached")
|
|
58
|
+
}
|
|
59
|
+
fn query(&self, _t: &crate::transport::Target, _f: crate::transport::PaneField) -> Result<Option<String>, crate::transport::TransportError> {
|
|
60
|
+
unimplemented!("not reached")
|
|
61
|
+
}
|
|
62
|
+
fn liveness(&self, _p: &crate::transport::PaneId) -> Result<crate::model::enums::PaneLiveness, crate::transport::TransportError> {
|
|
63
|
+
unimplemented!("not reached")
|
|
64
|
+
}
|
|
65
|
+
fn list_targets(&self) -> Result<Vec<crate::transport::PaneInfo>, crate::transport::TransportError> {
|
|
66
|
+
unimplemented!("not reached")
|
|
67
|
+
}
|
|
68
|
+
fn has_session(&self, _s: &crate::transport::SessionName) -> Result<bool, crate::transport::TransportError> {
|
|
69
|
+
Ok(self.session_exists)
|
|
70
|
+
}
|
|
71
|
+
fn list_windows(&self, _s: &crate::transport::SessionName) -> Result<Vec<crate::transport::WindowName>, crate::transport::TransportError> {
|
|
72
|
+
// a dead server has no windows; a live one has the agent window.
|
|
73
|
+
Ok(if self.session_exists { vec![crate::transport::WindowName::new("alpha")] } else { Vec::new() })
|
|
74
|
+
}
|
|
75
|
+
fn set_session_env(&self, _s: &crate::transport::SessionName, _k: &str, _v: &str) -> Result<crate::transport::SetEnvOutcome, crate::transport::TransportError> {
|
|
76
|
+
unimplemented!("not reached")
|
|
77
|
+
}
|
|
78
|
+
fn kill_session(&self, _s: &crate::transport::SessionName) -> Result<(), crate::transport::TransportError> {
|
|
79
|
+
unimplemented!("not reached")
|
|
80
|
+
}
|
|
81
|
+
fn kill_window(&self, _t: &crate::transport::Target) -> Result<(), crate::transport::TransportError> {
|
|
82
|
+
Ok(())
|
|
83
|
+
}
|
|
84
|
+
fn attach_session(&self, _s: &crate::transport::SessionName) -> Result<crate::transport::AttachOutcome, crate::transport::TransportError> {
|
|
85
|
+
unimplemented!("not reached")
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
fn respawn_ws_one_resumable_worker() -> PathBuf {
|
|
89
|
+
let ws = temp_ws().join("respawn_dead_session");
|
|
90
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
91
|
+
crate::state::persist::save_runtime_state(
|
|
92
|
+
&ws,
|
|
93
|
+
&json!({
|
|
94
|
+
"session_name": "team-sa",
|
|
95
|
+
"agents": {"alpha": {"status": "running", "provider": "codex", "session_id": "sess-a", "first_send_at": "2026-05-27T10:00:00+00:00"}}
|
|
96
|
+
}),
|
|
97
|
+
)
|
|
98
|
+
.unwrap();
|
|
99
|
+
seed_healthy_coordinator(&ws);
|
|
100
|
+
ws
|
|
101
|
+
}
|
|
102
|
+
// RED — the respawn-time spawn decision must consult the LIVE session probe. With
|
|
103
|
+
// `has_session=false` (the `-L` server destroyed by --discard-session killing the
|
|
104
|
+
// last window), the respawn MUST new-session (spawn_first). Today `into_existing_session
|
|
105
|
+
// = session_name_present(&state)` is true (state string still "team-sa") so the code
|
|
106
|
+
// records spawn_into against a dead server -> assertion fails -> RED. NOT a panic:
|
|
107
|
+
// the transport implements has_session, so the only failure is the wrong spawn kind.
|
|
108
|
+
#[test]
|
|
109
|
+
fn start_agent_respawn_into_dead_session_uses_new_session_not_new_window() {
|
|
110
|
+
let ws = respawn_ws_one_resumable_worker();
|
|
111
|
+
let spawns = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
|
|
112
|
+
// session_exists=false models the dead CP-1 `-L` server after --discard-session.
|
|
113
|
+
let transport = SessionProbeRecordingTransport {
|
|
114
|
+
spawns: std::sync::Arc::clone(&spawns),
|
|
115
|
+
session_exists: false,
|
|
116
|
+
};
|
|
117
|
+
let _ = start_agent_with_transport(&ws, &AgentId::new("alpha"), false, false, false, None, &transport);
|
|
118
|
+
let recorded = spawns.lock().unwrap().clone();
|
|
119
|
+
assert_eq!(recorded.len(), 1, "exactly one respawn for alpha; got {recorded:?}");
|
|
120
|
+
assert_eq!(
|
|
121
|
+
recorded[0].0, "spawn_first",
|
|
122
|
+
"golden runtime.py:1017: when `tmux has-session` is FALSE (dead -L server after \
|
|
123
|
+
--discard-session destroyed the last window), the respawn MUST new-session \
|
|
124
|
+
(spawn_first), NOT new-window (spawn_into) — spawn_into a dead server crashes with \
|
|
125
|
+
'no server running' and orphans the agent. The decision must come from \
|
|
126
|
+
transport.has_session(), not session_name_present(&state); got {recorded:?}"
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
130
|
+
// reset/remove CANONICAL-WORKSPACE regression-locks (bug-A class: e17096c/0f886a9/4435f9a).
|
|
131
|
+
// rt-host-a @ c262bf7: `reset`/`remove --workspace <teamdir/.team>` resolved to the SUBPATH's own
|
|
132
|
+
// .team/runtime instead of the LIVE parent run-workspace -> they operated on a detached context
|
|
133
|
+
// (live roster/windows untouched; respawn went nowhere -> post-reset dispatch died). `start` dodged it
|
|
134
|
+
// by being invoked with $ws. FIX (porter-a): reset/remove canonicalize the run-workspace
|
|
135
|
+
// (lifecycle_paths -> run_workspace = canonical_run_workspace(input)), read spec from the teamdir, and
|
|
136
|
+
// reset reuses start's live-session-probe respawn. These LOCK the canonical resolution so it can't
|
|
137
|
+
// re-regress.
|
|
138
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
139
|
+
// item ① — reset --discard-session must REBUILD the worker's window via the SAME respawn path as start
|
|
140
|
+
// (reset_agent_at_paths -> start_agent_with_transport). Into a LIVE session (has_session=true) the
|
|
141
|
+
// rebuild is a new-window (spawn_into). The regression had reset operate on a detached runtime so the
|
|
142
|
+
// window was never rebuilt. OS-safe: SessionProbeRecordingTransport (no real tmux) + seeded coordinator.
|
|
143
|
+
#[test]
|
|
144
|
+
fn reset_agent_discard_session_rebuilds_window_via_start_respawn() {
|
|
145
|
+
let ws = restart_ws_two_resumable_workers(); // compiled spec + state(alpha,bravo running) + seeded coordinator
|
|
146
|
+
let spawns = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
|
|
147
|
+
// live session present (other workers keep it alive) but alpha's window was discarded.
|
|
148
|
+
let transport = SessionProbeRecordingTransport {
|
|
149
|
+
spawns: std::sync::Arc::clone(&spawns),
|
|
150
|
+
session_exists: true,
|
|
151
|
+
};
|
|
152
|
+
let _ = reset_agent_with_transport(&ws, &AgentId::new("alpha"), true, false, None, &transport);
|
|
153
|
+
let recorded = spawns.lock().unwrap().clone();
|
|
154
|
+
assert!(
|
|
155
|
+
!recorded.is_empty(),
|
|
156
|
+
"reset --discard-session must RESPAWN alpha's window (reuse start's respawn path); the bug-A \
|
|
157
|
+
regression operated on a detached runtime so nothing was rebuilt -> ZERO spawns. got {recorded:?}"
|
|
158
|
+
);
|
|
159
|
+
assert_eq!(
|
|
160
|
+
recorded.last().unwrap().0, "spawn_into",
|
|
161
|
+
"into a LIVE session (has_session=true) the window rebuild is a new-window (spawn_into); got {recorded:?}"
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
// item ② — remove invoked with a NON-canonical input (the workspace's own `.team` subpath) must resolve
|
|
165
|
+
// the CANONICAL live run-workspace and apply the removal to the LIVE roster — not the subpath's detached
|
|
166
|
+
// runtime. canonical_run_workspace($ws/.team) == $ws. The regression made remove "succeed" against a
|
|
167
|
+
// detached context, leaving the live roster [alpha,bravo] intact. (lanea_team_ws re-points routing to
|
|
168
|
+
// bravo so removing alpha validates cleanly.)
|
|
169
|
+
#[test]
|
|
170
|
+
fn remove_agent_via_team_subpath_applies_to_canonical_live_roster() {
|
|
171
|
+
let ws = lanea_team_ws("stopped"); // canonical workspace: spec + state roster [alpha,bravo]
|
|
172
|
+
let subpath = ws.join(".team"); // non-canonical input; canonicalizes back to $ws
|
|
173
|
+
let tx = LaneTransport::new("team-laneateam", &[]);
|
|
174
|
+
let outcome = remove_agent_with_transport(&subpath, &aid("alpha"), true, true, None, &tx);
|
|
175
|
+
assert!(
|
|
176
|
+
matches!(outcome, Ok(RemoveAgentOutcome::Removed { .. })),
|
|
177
|
+
"remove via the .team subpath must resolve the canonical live workspace and succeed; got {outcome:?}"
|
|
178
|
+
);
|
|
179
|
+
let state = crate::state::persist::load_runtime_state(&ws).expect("load LIVE $ws state");
|
|
180
|
+
assert!(
|
|
181
|
+
state.get("agents").and_then(serde_json::Value::as_object).is_some_and(|a| !a.contains_key("alpha")),
|
|
182
|
+
"remove must apply to the LIVE $ws roster (canonical resolution), removing alpha; the bug-A \
|
|
183
|
+
regression operated on the subpath's detached runtime, leaving the live roster intact. state={state}"
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
/// bug-B [✦ DIVERGENCE] fixture — a NORMAL compiled 2-agent team with routing/tasks LEFT EXACTLY AS
|
|
187
|
+
/// COMPILED (route-alpha + route-bravo, default_assignee=alpha, tasks[0].assignee=alpha). Unlike
|
|
188
|
+
/// lanea_team_ws / lanea_ws_agents (which deliberately re-point refs away so a remove validates), this
|
|
189
|
+
/// is the REAL "remove a normal worker" case — the one golden cannot handle (every compiled agent is
|
|
190
|
+
/// routed, compiler.py:57). Both workers seeded `stopped` (no tmux); LaneTransport reports no windows.
|
|
191
|
+
fn bugb_routed_team_ws() -> PathBuf {
|
|
192
|
+
let ws = temp_ws().join("bugb_routed");
|
|
193
|
+
std::fs::create_dir_all(ws.join("agents")).unwrap();
|
|
194
|
+
std::fs::write(ws.join("TEAM.md"), "---\nname: laneateam\nobjective: bug-B routed-remove probe.\nprovider: codex\n---\n\nteam.\n").unwrap();
|
|
195
|
+
std::fs::write(ws.join("agents").join("alpha.md"), DELEG_ROLE_ALPHA).unwrap();
|
|
196
|
+
std::fs::write(ws.join("agents").join("bravo.md"), DELEG_ROLE_BRAVO).unwrap();
|
|
197
|
+
let spec = crate::compiler::compile_team(&ws).expect("compile bug-B team");
|
|
198
|
+
let yaml = crate::model::yaml::dumps(&spec);
|
|
199
|
+
// sanity: the UNMODIFIED compiled spec really routes bravo — that route-bravo rule is the dangling
|
|
200
|
+
// ref the cascade-prune must drop (and which makes golden's validate_spec raise + rollback).
|
|
201
|
+
assert!(yaml.contains("assign_to: \"bravo\""), "fixture: compiled spec must carry route-bravo (assign_to bravo); got:\n{yaml}");
|
|
202
|
+
std::fs::write(ws.join("team.spec.yaml"), yaml).unwrap();
|
|
203
|
+
crate::state::persist::save_runtime_state(
|
|
204
|
+
&ws,
|
|
205
|
+
&json!({
|
|
206
|
+
"session_name": "team-laneateam",
|
|
207
|
+
"agents": {
|
|
208
|
+
"alpha": {"status": "stopped", "provider": "codex", "window": "alpha"},
|
|
209
|
+
"bravo": {"status": "stopped", "provider": "codex", "window": "bravo"}
|
|
210
|
+
}
|
|
211
|
+
}),
|
|
212
|
+
)
|
|
213
|
+
.unwrap();
|
|
214
|
+
ws
|
|
215
|
+
}
|
|
216
|
+
// ── bug-B [✦ DIVERGENCE — REVERSES the previously-DECLINED byte-parity decision (ticket:154)] ─────────
|
|
217
|
+
// Removing a NORMAL routed worker must SUCCEED and surgically PRUNE that worker's dangling routing rule,
|
|
218
|
+
// NOT refuse+rollback. Golden agents.py:88-94 removes the agent only from `agents` + `runtime.startup_order`
|
|
219
|
+
// (routing UNTOUCHED), then validate_spec RAISES `/routing/rules/N/assign_to: unknown agent 'bravo'`
|
|
220
|
+
// (spec.py:341-346) -> remove catches -> rollback (rollback_ok=true). Because compile_team auto-wires a
|
|
221
|
+
// `route-<id>` rule (assign_to:<id>) for EVERY agent (compiler.py:57), NO compiled worker is removable in
|
|
222
|
+
// golden. Probed live (/tmp/probe_removeB2.py): validate_spec of the bravo-removed spec raises exactly
|
|
223
|
+
// "/routing/rules/1/assign_to: unknown agent 'bravo'". The advisor/leader requests the cascade-prune
|
|
224
|
+
// parity-IMPROVEMENT (ticket:155,197): remove must drop routing rules whose assign_to == the removed
|
|
225
|
+
// agent (and reset default_assignee if it pointed at it) so the spec stays valid. Current Rust faithfully
|
|
226
|
+
// mirrors golden (restart.rs:897-899 validate_spec -> Err(Compile) -> rollback) -> RED.
|
|
227
|
+
//
|
|
228
|
+
// NOTE for the porter: this REVERSES the documented byte-parity intent at restart.rs:931-937 and the
|
|
229
|
+
// lanea_team_ws/lanea_ws_agents fixtures' re-point workaround. Implementing the prune is correct ONLY
|
|
230
|
+
// if the leader's ✦ divergence ruling stands — do not "fix" by reverting this test.
|
|
231
|
+
//
|
|
232
|
+
// Removing `bravo` (the SECOND agent) isolates the routing-rule prune cleanly: only `route-bravo` dangles;
|
|
233
|
+
// default_assignee (alpha) and tasks[0].assignee (alpha) stay valid, so this test does NOT entangle the
|
|
234
|
+
// default_assignee / task-reassignment policy.
|
|
235
|
+
#[test]
|
|
236
|
+
fn remove_routed_worker_prunes_dangling_routing_rule_divergence() {
|
|
237
|
+
let ws = bugb_routed_team_ws();
|
|
238
|
+
let tx = LaneTransport::new("team-laneateam", &[]); // bravo not running (no windows)
|
|
239
|
+
let outcome = remove_agent_with_transport(&ws, &aid("bravo"), true, true, None, &tx); // from_spec + force
|
|
240
|
+
assert!(
|
|
241
|
+
matches!(outcome, Ok(RemoveAgentOutcome::Removed { .. })),
|
|
242
|
+
"✦ divergence: removing the routed worker 'bravo' must SUCCEED (Removed), not refuse+rollback. \
|
|
243
|
+
Golden + current Rust raise /routing/rules/.../assign_to: unknown agent 'bravo' -> \
|
|
244
|
+
StatePersist rollback_ok=true; got {outcome:?}"
|
|
245
|
+
);
|
|
246
|
+
let yaml = std::fs::read_to_string(ws.join("team.spec.yaml")).expect("read spec after remove");
|
|
247
|
+
assert!(
|
|
248
|
+
!yaml.contains("assign_to: \"bravo\""),
|
|
249
|
+
"✦ divergence: remove must PRUNE the dangling route-bravo rule (assign_to: \"bravo\"); spec still has it:\n{yaml}"
|
|
250
|
+
);
|
|
251
|
+
assert!(
|
|
252
|
+
yaml.contains("assign_to: \"alpha\""),
|
|
253
|
+
"the prune must be SURGICAL — route-alpha (the STAYING agent) must remain; got:\n{yaml}"
|
|
254
|
+
);
|
|
255
|
+
assert!(
|
|
256
|
+
yaml.contains("default_assignee: \"alpha\""),
|
|
257
|
+
"default_assignee (alpha, still present) must be untouched by the prune; got:\n{yaml}"
|
|
258
|
+
);
|
|
259
|
+
let state = crate::state::persist::load_runtime_state(&ws).expect("load state");
|
|
260
|
+
assert!(
|
|
261
|
+
state.get("agents").and_then(serde_json::Value::as_object).is_some_and(|a| !a.contains_key("bravo")),
|
|
262
|
+
"bravo must also be removed from state.agents; got {state:?}"
|
|
263
|
+
);
|
|
264
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
265
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
2
|
+
use super::*;
|
|
3
|
+
use serde_json::json;
|
|
4
|
+
use std::sync::atomic::{AtomicU32, Ordering};
|
|
5
|
+
|
|
6
|
+
static SEQ: AtomicU32 = AtomicU32::new(0);
|
|
7
|
+
|
|
8
|
+
/// 隔离的空 workspace。仿 sibling(event_log.rs)风格,不依赖 tempfile dev-dep。
|
|
9
|
+
fn temp_ws() -> PathBuf {
|
|
10
|
+
let n = SEQ.fetch_add(1, Ordering::Relaxed);
|
|
11
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_lc_{}_{}", std::process::id(), n));
|
|
12
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
13
|
+
ws
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
fn aid(s: &str) -> AgentId {
|
|
17
|
+
AgentId::new(s)
|
|
18
|
+
}
|
|
19
|
+
fn sess(s: &str) -> SessionName {
|
|
20
|
+
SessionName(s.to_string())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
mod core;
|
|
24
|
+
mod launch_spawn;
|
|
25
|
+
mod agent_ops;
|
|
26
|
+
mod lane_ops;
|
|
27
|
+
mod main_preserved;
|