@team-agent/installer 0.2.11 → 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 -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,395 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
// =========================================================================
|
|
4
|
+
// (DELEGATION sweep, rt-host-a loop #2) — cmd_send persistence + shutdown teardown.
|
|
5
|
+
// =========================================================================
|
|
6
|
+
|
|
7
|
+
// 1 [P0, CONFIRMED BUG] — cmd_send (cli/send.rs) returns delivery_json synthetic (true,"delivered")
|
|
8
|
+
// and NEVER calls messaging::send_message, so NO `messages` row is persisted. RED: after cmd_send to
|
|
9
|
+
// a worker on a seeded ws (real MessageStore), assert a messages row was PERSISTED (query by
|
|
10
|
+
// recipient+content) — proving the real messaging::send_message -> MessageStore.create_message path.
|
|
11
|
+
// DB-persist is observable without a transport. (messaging::send_message is ALSO a stub today, so the
|
|
12
|
+
// porter wires BOTH cmd_send->send_message AND send_message->persist.)
|
|
13
|
+
//
|
|
14
|
+
// OLD seed: flat `{"agents": {"w1": ...}}` worked because send_message read agents
|
|
15
|
+
// directly off the raw runtime state.
|
|
16
|
+
// NEW seed (Bug 1/2 — team-in-team state scope, see tests/team_in_team_state_scope_red.rs):
|
|
17
|
+
// cmd_send → resolve_active_team yields a team_key, send_message projects the
|
|
18
|
+
// raw state through `project_top_level_view(team_key)` which reads agents off
|
|
19
|
+
// `teams[team_key].agents`. The seed therefore lives under `teams.current.agents`
|
|
20
|
+
// with `active_team_key=current`; the delegation + persistence behavior under test
|
|
21
|
+
// is unchanged — only the shape of "an in-team recipient" is now nested.
|
|
22
|
+
#[test]
|
|
23
|
+
fn cli_send_persists_real_message_row() {
|
|
24
|
+
let ws = deleg_uniq_dir("send");
|
|
25
|
+
let _ = crate::message_store::MessageStore::open(&ws).unwrap(); // real store at the workspace
|
|
26
|
+
// w1 must be a known team agent — golden send.py refuses non-team targets
|
|
27
|
+
// (target_not_in_team); an in-team recipient is the one that persists. Bug 1/2
|
|
28
|
+
// scopes agents under teams[<key>].agents (NEW shape).
|
|
29
|
+
crate::state::persist::save_runtime_state(
|
|
30
|
+
&ws,
|
|
31
|
+
&serde_json::json!({
|
|
32
|
+
"active_team_key": "current",
|
|
33
|
+
"teams": {"current": {"agents": {"w1": {"provider": "codex"}}}}
|
|
34
|
+
}),
|
|
35
|
+
)
|
|
36
|
+
.unwrap();
|
|
37
|
+
let args = SendArgs {
|
|
38
|
+
target: Some("w1".to_string()),
|
|
39
|
+
message: vec!["hello-real-delegation".to_string()],
|
|
40
|
+
targets: None,
|
|
41
|
+
workspace: ws.clone(),
|
|
42
|
+
team: None,
|
|
43
|
+
task: None,
|
|
44
|
+
sender: "leader".to_string(),
|
|
45
|
+
no_ack: false,
|
|
46
|
+
no_wait: true,
|
|
47
|
+
watch_result: false,
|
|
48
|
+
timeout: 0.0,
|
|
49
|
+
confirm_human: false,
|
|
50
|
+
json: true,
|
|
51
|
+
message_id: None,
|
|
52
|
+
};
|
|
53
|
+
let _ = cmd_send(&args);
|
|
54
|
+
|
|
55
|
+
let store = crate::message_store::MessageStore::open(&ws).unwrap();
|
|
56
|
+
let conn = crate::db::schema::open_db(store.db_path()).unwrap();
|
|
57
|
+
let count: i64 = conn
|
|
58
|
+
.query_row(
|
|
59
|
+
"select count(*) from messages where recipient = 'w1' and content = 'hello-real-delegation'",
|
|
60
|
+
[],
|
|
61
|
+
|r| r.get(0),
|
|
62
|
+
)
|
|
63
|
+
.unwrap();
|
|
64
|
+
assert!(
|
|
65
|
+
count >= 1,
|
|
66
|
+
"cmd_send must delegate to messaging::send_message and PERSIST a `messages` row (recipient=w1); \
|
|
67
|
+
the synthetic delivery_json writes NO DB row -> count={count}"
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 5 [P1, CONFIRMED PARTIAL] — shutdown stops the coordinator but NEVER kills the team tmux session
|
|
72
|
+
// (-> orphan worker panes). #[ignore] real-machine: the wired shutdown kills the real tmux session.
|
|
73
|
+
// SEAM NEEDED (note to porter): add shutdown_with_transport(workspace, keep_logs, team, &dyn Transport)
|
|
74
|
+
// (mirror restart_with_transport) so this can assert IN-PROCESS that transport.kill_session(team
|
|
75
|
+
// session) was called via a RecordingTransport — the clean "team session killed / workers reaped"
|
|
76
|
+
// observable. Until that seam lands, this asserts the real teardown surfaces (session-kill / stop).
|
|
77
|
+
#[test]
|
|
78
|
+
#[ignore = "real-machine: shutdown kills the team tmux session. PORTER SEAM: add \
|
|
79
|
+
shutdown_with_transport(workspace, keep_logs, team, &dyn Transport) so kill_session is \
|
|
80
|
+
assertable in-process via a RecordingTransport (workers reaped, no orphan panes)."]
|
|
81
|
+
fn cli_shutdown_kills_team_session_real_teardown() {
|
|
82
|
+
let ws = seed_status_workspace(); // state.json with a running agent + session_name
|
|
83
|
+
let args = ShutdownArgs { workspace: ws, team: None, keep_logs: true, json: true };
|
|
84
|
+
let text = format!("{:?}", cmd_shutdown(&args)).to_lowercase();
|
|
85
|
+
assert!(
|
|
86
|
+
text.contains("session") || text.contains("killed") || text.contains("kill_session"),
|
|
87
|
+
"shutdown must KILL the team tmux session + reap workers (real teardown); the rt-host-a partial \
|
|
88
|
+
bug stops the coordinator but leaves the session + workers running (orphans); got {text}"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// =========================================================================
|
|
93
|
+
// WAVE-2 Lane B — CLI leader handler delegation byte-parity (leader_port::*).
|
|
94
|
+
// The three CLI verbs are thin pass-throughs (cli/commands.py:152-161):
|
|
95
|
+
// cmd_takeover -> runtime.takeover(ws, team, confirm)
|
|
96
|
+
// cmd_claim_leader -> runtime.claim_leader(ws, team, confirm) (Family A)
|
|
97
|
+
// cmd_identity -> runtime.leader_identity(ws, team)
|
|
98
|
+
// leader_port::{takeover,claim_leader,leader_identity} are STUBS returning
|
|
99
|
+
// the WRONG shape today -> these LOCK the golden dict so the porter wires
|
|
100
|
+
// them into leader::* / runtime.* and matches byte-for-byte.
|
|
101
|
+
// Golden re-probed @ team-agent-public (probe_claim.py / probe_rtclaim.py /
|
|
102
|
+
// probe_lid.py). Label: RED = stub returns wrong shape today.
|
|
103
|
+
// =========================================================================
|
|
104
|
+
|
|
105
|
+
fn leader_port_ws(tag: &str) -> std::path::PathBuf {
|
|
106
|
+
let dir = std::env::temp_dir().join(format!(
|
|
107
|
+
"ta-cli-leaderport-{}-{}",
|
|
108
|
+
tag,
|
|
109
|
+
std::process::id()
|
|
110
|
+
));
|
|
111
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
112
|
+
dir
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// #235 / I-RN-3 — explicit takeover is unconditional once the caller has a live pane:
|
|
116
|
+
// it is not a permission gate and must replace a live owner, advancing owner_epoch.
|
|
117
|
+
// `--confirm` may remain a UX affordance, but it is not the authority check.
|
|
118
|
+
#[test]
|
|
119
|
+
fn leader_port_takeover_refuses_without_confirm_byte_parity_obsolete_now_unconditional() {
|
|
120
|
+
let cli = std::fs::read_to_string(
|
|
121
|
+
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/cli/mod.rs"),
|
|
122
|
+
)
|
|
123
|
+
.unwrap();
|
|
124
|
+
assert_eq!(
|
|
125
|
+
cli.matches("claim_leader(workspace, team, true)").count(),
|
|
126
|
+
1,
|
|
127
|
+
"takeover must call the lease path as an explicit takeover, not pass the CLI confirm flag as a permission gate"
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
let ws = leader_port_ws("tk_unconditional_live");
|
|
131
|
+
let team_id = crate::model::ids::TeamKey::new("current");
|
|
132
|
+
let caller = crate::transport::PaneId::new("%5");
|
|
133
|
+
let mut state = json!({
|
|
134
|
+
"session_name": "team-agent-x",
|
|
135
|
+
"team_owner": {
|
|
136
|
+
"pane_id": "%1",
|
|
137
|
+
"provider": "codex",
|
|
138
|
+
"machine_fingerprint": "fp",
|
|
139
|
+
"leader_session_uuid": "OWNERUUID",
|
|
140
|
+
"owner_epoch": 2,
|
|
141
|
+
"claimed_at": "t",
|
|
142
|
+
"claimed_via": "claim-leader"
|
|
143
|
+
},
|
|
144
|
+
"leader_receiver": {
|
|
145
|
+
"pane_id": "%1",
|
|
146
|
+
"provider": "codex",
|
|
147
|
+
"owner_epoch": 2,
|
|
148
|
+
"leader_session_uuid": "OWNERUUID"
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
let event_log = crate::event_log::EventLog::new(&ws);
|
|
152
|
+
let live = LeaderPortSeededLiveness::new(&["%1", "%5"]);
|
|
153
|
+
let r = crate::leader::claim_lease_no_incident(
|
|
154
|
+
&ws,
|
|
155
|
+
&mut state,
|
|
156
|
+
None,
|
|
157
|
+
&team_id,
|
|
158
|
+
&caller,
|
|
159
|
+
true,
|
|
160
|
+
&event_log,
|
|
161
|
+
&live,
|
|
162
|
+
)
|
|
163
|
+
.unwrap();
|
|
164
|
+
|
|
165
|
+
assert!(r.ok, "explicit takeover must succeed even when old owner pane is live: {r:?}");
|
|
166
|
+
assert_eq!(r.status, crate::leader::LeaseStatus::Claimed);
|
|
167
|
+
assert_eq!(r.owner_epoch, Some(crate::model::ids::OwnerEpoch(3)));
|
|
168
|
+
assert_eq!(r.bound_pane_id, Some(caller.clone()));
|
|
169
|
+
assert_eq!(state["leader_receiver"]["pane_id"], json!("%5"));
|
|
170
|
+
assert_eq!(state["team_owner"]["pane_id"], json!("%5"));
|
|
171
|
+
assert_eq!(state["team_owner"]["owner_epoch"], json!(3));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
struct LeaderPortSeededLiveness {
|
|
175
|
+
live_panes: std::collections::BTreeSet<String>,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
impl LeaderPortSeededLiveness {
|
|
179
|
+
fn new(panes: &[&str]) -> Self {
|
|
180
|
+
Self {
|
|
181
|
+
live_panes: panes.iter().map(|pane| (*pane).to_string()).collect(),
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
impl crate::state::owner_gate::PaneLivenessProbe for LeaderPortSeededLiveness {
|
|
187
|
+
fn liveness(&self, pane_id: &str) -> crate::model::enums::PaneLiveness {
|
|
188
|
+
if self.live_panes.contains(pane_id) {
|
|
189
|
+
crate::model::enums::PaneLiveness::Live
|
|
190
|
+
} else {
|
|
191
|
+
crate::model::enums::PaneLiveness::Dead
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// RED — takeover(confirm=true) with no $TMUX_PANE: the Family A positive-source
|
|
197
|
+
// bind gate fires -> refused caller_pane_missing with the bind diagnostic dict.
|
|
198
|
+
// golden probe_claim.py takeover(confirm=True): {ok:false, status:"refused",
|
|
199
|
+
// reason:"caller_pane_missing", caller_pane_id:"", caller_current_command:"",
|
|
200
|
+
// hint:"run team-agent from inside your leader pane (the tmux pane you want to
|
|
201
|
+
// own this team)."}. Current stub returns {ok:true,...} (wrong) -> RED.
|
|
202
|
+
#[test]
|
|
203
|
+
#[ignore = "RED needs $TMUX_PANE ABSENT (Family A bind gate); run `--ignored` in a non-tmux shell. \
|
|
204
|
+
Inside tmux the live-pane resolver would engage. Seam: porter wires leader_port::takeover \
|
|
205
|
+
-> runtime.takeover whose Family A bind refuses caller_pane_missing when $TMUX_PANE missing."]
|
|
206
|
+
fn leader_port_takeover_confirm_without_pane_refuses_caller_pane_missing() {
|
|
207
|
+
if std::env::var_os("TMUX_PANE").is_some() {
|
|
208
|
+
return; // inside tmux the bind gate would pass; this case verifies the missing arm.
|
|
209
|
+
}
|
|
210
|
+
let ws = leader_port_ws("tk_confirm_nopane");
|
|
211
|
+
// seed a resolvable team so the gate reaches the bind step (not team_target_unresolved).
|
|
212
|
+
let st = json!({"session_name": "team-agent-x", "teams": {"team-agent-x": {}}});
|
|
213
|
+
let path = crate::state::persist::runtime_state_path(&ws);
|
|
214
|
+
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
|
215
|
+
std::fs::write(&path, serde_json::to_string(&st).unwrap()).unwrap();
|
|
216
|
+
let v = super::leader_port::takeover(&ws, Some("team-agent-x"), true).unwrap();
|
|
217
|
+
assert_eq!(v["ok"], json!(false));
|
|
218
|
+
assert_eq!(v["status"], json!("refused"));
|
|
219
|
+
assert_eq!(v["reason"], json!("caller_pane_missing"));
|
|
220
|
+
assert_eq!(v["caller_pane_id"], json!(""));
|
|
221
|
+
assert_eq!(v["caller_current_command"], json!(""));
|
|
222
|
+
assert_eq!(
|
|
223
|
+
v["hint"],
|
|
224
|
+
json!("run team-agent from inside your leader pane (the tmux pane you want to own this team).")
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// RED — claim_leader(confirm=false) with no $TMUX_PANE: runtime.claim_leader is
|
|
229
|
+
// ALSO Family A (runtime.py:791) -> the bind gate fires FIRST, so the no-pane
|
|
230
|
+
// refusal is caller_pane_missing (NOT the leader-lane "not_in_tmux_pane").
|
|
231
|
+
// golden probe_rtclaim.py. This pins the runtime-vs-leader distinction:
|
|
232
|
+
// the CLI projection must reflect runtime.claim_leader's Family A bind gate.
|
|
233
|
+
// Current stub returns {ok:true, inbox_hint:...} (wrong) -> RED.
|
|
234
|
+
#[test]
|
|
235
|
+
#[ignore = "RED needs $TMUX_PANE ABSENT (Family A bind gate fires first); run `--ignored` in a \
|
|
236
|
+
non-tmux shell. Inside tmux the resolver would engage. Seam: porter wires \
|
|
237
|
+
leader_port::claim_leader -> runtime.claim_leader (Family A) whose bind refuses \
|
|
238
|
+
caller_pane_missing (NOT leader-lane not_in_tmux_pane) when $TMUX_PANE missing."]
|
|
239
|
+
fn leader_port_claim_leader_no_pane_refuses_caller_pane_missing_family_a() {
|
|
240
|
+
if std::env::var_os("TMUX_PANE").is_some() {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
let ws = leader_port_ws("claim_nopane");
|
|
244
|
+
let v = super::leader_port::claim_leader(&ws, None, false).unwrap();
|
|
245
|
+
assert_eq!(v["ok"], json!(false));
|
|
246
|
+
assert_eq!(v["status"], json!("refused"));
|
|
247
|
+
assert_eq!(
|
|
248
|
+
v["reason"],
|
|
249
|
+
json!("caller_pane_missing"),
|
|
250
|
+
"runtime.claim_leader (Family A) bind gate -> caller_pane_missing, NOT not_in_tmux_pane"
|
|
251
|
+
);
|
|
252
|
+
assert_eq!(v["caller_pane_id"], json!(""));
|
|
253
|
+
assert_eq!(
|
|
254
|
+
v["hint"],
|
|
255
|
+
json!("run team-agent from inside your leader pane (the tmux pane you want to own this team).")
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// RED — leader_identity(): CLI directly emits leader.leader_identity's 9-key
|
|
260
|
+
// dict (runtime.leader_identity is imported from leader). golden probe_lid.py
|
|
261
|
+
// keys: ok, uuid_prefix, machine_fingerprint, workspace_abspath, os_user,
|
|
262
|
+
// team_id, current_pane_id, last_seen_at, source. Current stub returns
|
|
263
|
+
// {ok:true, team:...} (wrong shape) -> RED.
|
|
264
|
+
#[test]
|
|
265
|
+
fn leader_port_leader_identity_emits_nine_key_dict() {
|
|
266
|
+
let ws = leader_port_ws("identity");
|
|
267
|
+
std::fs::create_dir_all(crate::model::paths::runtime_dir(&ws)).unwrap();
|
|
268
|
+
let v = super::leader_port::leader_identity(&ws, None).unwrap();
|
|
269
|
+
assert_eq!(v["ok"], json!(true));
|
|
270
|
+
let obj = v.as_object().expect("identity → JSON object");
|
|
271
|
+
for key in [
|
|
272
|
+
"ok", "uuid_prefix", "machine_fingerprint", "workspace_abspath",
|
|
273
|
+
"os_user", "team_id", "current_pane_id", "last_seen_at", "source",
|
|
274
|
+
] {
|
|
275
|
+
assert!(obj.contains_key(key), "golden identity dict must carry '{key}', got {obj:?}");
|
|
276
|
+
}
|
|
277
|
+
// no override/state uuid → source is the leader-plan "derived" string.
|
|
278
|
+
assert_eq!(v["source"], json!("derived"));
|
|
279
|
+
// uuid_prefix is exactly 12 hex chars (derive[:12]).
|
|
280
|
+
let prefix = v["uuid_prefix"].as_str().expect("uuid_prefix str");
|
|
281
|
+
assert_eq!(prefix.len(), 12, "uuid_prefix == derived[:12]");
|
|
282
|
+
assert!(prefix.chars().all(|c| c.is_ascii_hexdigit()));
|
|
283
|
+
// no team registered + no TMUX_PANE/receiver → these are JSON null.
|
|
284
|
+
if std::env::var_os("TMUX_PANE").is_none() {
|
|
285
|
+
assert_eq!(v["current_pane_id"], serde_json::Value::Null);
|
|
286
|
+
}
|
|
287
|
+
assert_eq!(v["last_seen_at"], serde_json::Value::Null);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ── cmd_watch (cli/adapters.rs:58) [RED] — today a CmdResult::none() no-op ───────────────────────
|
|
291
|
+
// Golden cmd_watch (cli/commands.py:103-109) DELEGATES to run_watch(workspace.resolve(), team) and
|
|
292
|
+
// exits 0. Golden run_watch (watch/__init__.py:25-37) is a `while True` LIVE TAIL that streams
|
|
293
|
+
// render_event_line output (collect_watch_lines) for the watched team. The Rust cmd_watch returns
|
|
294
|
+
// CmdResult::none() — a no-op that never touches the watch subsystem. The watch LINE SHAPE is itself
|
|
295
|
+
// byte-locked by coordinator::render_event_line (coordinator/tests.rs GROUP H); the cli contract here
|
|
296
|
+
// is that cmd_watch must DELEGATE and surface those rendered lines. Seeded a result_received event ->
|
|
297
|
+
// golden render = "result_received: <agent> -> <summary>" (render_event_line: agent_id + summary[:80]).
|
|
298
|
+
//
|
|
299
|
+
// RED confirmed TODAY: cmd_watch=none() returns immediately (no watch line) -> the assertion fails
|
|
300
|
+
// (and does NOT hang). PORTER: wire cmd_watch -> coordinator::run_watch(resolved workspace, team,
|
|
301
|
+
// interval, sink). Since golden's tail is `while True`, the cli port needs a TERMINATING / bounded
|
|
302
|
+
// entry to be both byte-parity AND unit-testable (collect the current watch lines into the CmdResult,
|
|
303
|
+
// or stream to stdout with a bounded test seam) — a blocking unit test is unacceptable.
|
|
304
|
+
#[test]
|
|
305
|
+
fn cmd_watch_delegates_and_surfaces_rendered_watch_lines_not_noop() {
|
|
306
|
+
let ws = tmp_workspace();
|
|
307
|
+
let logs = crate::model::paths::logs_dir(&ws);
|
|
308
|
+
std::fs::create_dir_all(&logs).unwrap();
|
|
309
|
+
std::fs::write(
|
|
310
|
+
logs.join("events.jsonl"),
|
|
311
|
+
"{\"event\":\"result_received\",\"agent_id\":\"alpha\",\"summary\":\"did the thing\"}\n",
|
|
312
|
+
)
|
|
313
|
+
.unwrap();
|
|
314
|
+
let r = cmd_watch(&WatchArgs { workspace: ws.clone(), team: None })
|
|
315
|
+
.expect("cmd_watch returns a CmdResult");
|
|
316
|
+
let text = match &r.output {
|
|
317
|
+
CmdOutput::Human(s) => s.clone(),
|
|
318
|
+
CmdOutput::Json(v) => v.to_string(),
|
|
319
|
+
CmdOutput::None => String::new(),
|
|
320
|
+
};
|
|
321
|
+
assert!(
|
|
322
|
+
text.contains("result_received: alpha -> did the thing"),
|
|
323
|
+
"cmd_watch must DELEGATE to the watch subsystem (run_watch -> render_event_line) and surface the \
|
|
324
|
+
rendered watch line; today it is a CmdResult::none() no-op. got output={:?}",
|
|
325
|
+
r.output
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// D7 [WARN] — WAVE-2 Lane B: empty caller binds refuse as caller_pane_missing.
|
|
330
|
+
#[test]
|
|
331
|
+
#[serial_test::serial(env)]
|
|
332
|
+
fn d7_lease_refusal_dict_is_golden_minimal_four_keys() {
|
|
333
|
+
use std::sync::Mutex;
|
|
334
|
+
static D7_ENV: Mutex<()> = Mutex::new(());
|
|
335
|
+
let _g = D7_ENV.lock().unwrap_or_else(|p| p.into_inner());
|
|
336
|
+
let _env = EnvGuard::set(&[
|
|
337
|
+
("TMUX_PANE", Some("")), // empty caller -> caller_pane_missing via the lease path
|
|
338
|
+
("TEAM_AGENT_LEADER_PANE_ID", None),
|
|
339
|
+
]);
|
|
340
|
+
let ws = tmp_workspace();
|
|
341
|
+
crate::state::persist::save_runtime_state(&ws, &json!({})).unwrap(); // claim_leader loads state
|
|
342
|
+
let result = super::leader_port::claim_leader(&ws, None, false);
|
|
343
|
+
let v = result.expect("claim_leader projection");
|
|
344
|
+
let obj = v.as_object().expect("lease dict");
|
|
345
|
+
assert_eq!(
|
|
346
|
+
obj.get("reason").and_then(|r| r.as_str()),
|
|
347
|
+
Some("caller_pane_missing"),
|
|
348
|
+
"precondition: empty caller -> caller_pane_missing; got {v:?}"
|
|
349
|
+
);
|
|
350
|
+
let keys: std::collections::BTreeSet<&str> = obj.keys().map(String::as_str).collect();
|
|
351
|
+
assert_eq!(
|
|
352
|
+
keys,
|
|
353
|
+
["ok", "status", "reason", "caller_pane_id", "caller_current_command", "hint"]
|
|
354
|
+
.into_iter()
|
|
355
|
+
.collect::<std::collections::BTreeSet<_>>(),
|
|
356
|
+
"golden caller_pane_missing refusal key set"
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
struct EnvGuard {
|
|
361
|
+
previous: Vec<(&'static str, Option<String>)>,
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
impl EnvGuard {
|
|
365
|
+
fn set(values: &[(&'static str, Option<&'static str>)]) -> Self {
|
|
366
|
+
let previous = values
|
|
367
|
+
.iter()
|
|
368
|
+
.map(|(key, _)| (*key, std::env::var(key).ok()))
|
|
369
|
+
.collect::<Vec<_>>();
|
|
370
|
+
for (key, value) in values {
|
|
371
|
+
unsafe {
|
|
372
|
+
if let Some(value) = value {
|
|
373
|
+
std::env::set_var(key, value);
|
|
374
|
+
} else {
|
|
375
|
+
std::env::remove_var(key);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
Self { previous }
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
impl Drop for EnvGuard {
|
|
384
|
+
fn drop(&mut self) {
|
|
385
|
+
for (key, value) in self.previous.drain(..).rev() {
|
|
386
|
+
unsafe {
|
|
387
|
+
if let Some(value) = value {
|
|
388
|
+
std::env::set_var(key, value);
|
|
389
|
+
} else {
|
|
390
|
+
std::env::remove_var(key);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|