@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,390 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
// =========================================================================
|
|
4
|
+
// WAVE-2 NON-SUB CHECKPOINT — 9 MISSING CLI subcommands (ABSENT from cli/emit.rs dispatch).
|
|
5
|
+
// emit.rs:53-78 `dispatch` has NO arm for: sessions, peek, collect, e2e, diagnose, repair-state,
|
|
6
|
+
// validate-result, preflight, wait-ready -> they fall to `_ => Ok(ExitCode::Error)`. These REDs
|
|
7
|
+
// assert the dispatch ROUTES each subcommand: `run([sub,...]) == ExitCode::Ok` for a golden
|
|
8
|
+
// EXIT-0 scenario (today unrouted -> ExitCode::Error -> RED; green once the porter adds the
|
|
9
|
+
// dispatch arm + handler). Golden exit codes + JSON shapes probed via `python3 -m team_agent <sub>`.
|
|
10
|
+
//
|
|
11
|
+
// OBSERVABILITY NOTE: `run()` exposes only ExitCode (Ok=0/Error=1); it prints via println!, which
|
|
12
|
+
// libtest intercepts (thread-local capture), so an fd-level stdout byte-capture is unreliable
|
|
13
|
+
// under `cargo test`. The exact golden --json byte-shape is therefore LOCKED in each doc-comment
|
|
14
|
+
// as the porter's parity obligation; the in-process assertion is the routing (exit code). A
|
|
15
|
+
// follow-up can byte-lock output once each handler exists as a callable `cmd_*`/`*_port` symbol.
|
|
16
|
+
//
|
|
17
|
+
// Only golden-EXIT-0, tmux-SAFE scenarios make a clean RED (an exit-1 scenario's Error is
|
|
18
|
+
// indistinguishable from the unknown-subcommand Error -> false-green, forbidden). preflight/
|
|
19
|
+
// wait-ready/e2e/peek cannot reach golden-exit-0 on CI without real tmux/providers/a live team,
|
|
20
|
+
// so they are #[ignore] real-effect seams (documented shape), NOT false-green exit-1 asserts.
|
|
21
|
+
// =========================================================================
|
|
22
|
+
|
|
23
|
+
fn cli_argv(items: &[&str]) -> Vec<String> {
|
|
24
|
+
items.iter().map(|s| (*s).to_string()).collect()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// The exact golden fake spec (cli/e2e.py `_fake_spec`, dumped via simple_yaml) with the literal
|
|
28
|
+
/// `/WS` placeholder substituted to the real workspace — the minimal VALID team.spec.yaml that
|
|
29
|
+
/// `collect`/`repair-state` (load_spec) accept. (load_spec rejects partial specs: requires
|
|
30
|
+
/// communication/context/leader/routing/runtime/tasks.)
|
|
31
|
+
const FAKE_SPEC_YAML: &str = r#"version: 1
|
|
32
|
+
team:
|
|
33
|
+
name: "fake-e2e"
|
|
34
|
+
mode: "supervisor_worker"
|
|
35
|
+
objective: "Exercise fake provider orchestration."
|
|
36
|
+
workspace: "/WS"
|
|
37
|
+
leader:
|
|
38
|
+
id: "leader"
|
|
39
|
+
role: "leader"
|
|
40
|
+
provider: "fake"
|
|
41
|
+
model: null
|
|
42
|
+
tools:
|
|
43
|
+
- "fs_read"
|
|
44
|
+
- "fs_list"
|
|
45
|
+
- "mcp_team"
|
|
46
|
+
context_policy:
|
|
47
|
+
keep_user_thread: true
|
|
48
|
+
receive_worker_outputs: "structured_only"
|
|
49
|
+
max_worker_result_tokens: 2000
|
|
50
|
+
agents:
|
|
51
|
+
- id: "fake_impl"
|
|
52
|
+
role: "implementation_engineer"
|
|
53
|
+
provider: "fake"
|
|
54
|
+
model: null
|
|
55
|
+
working_directory: "/WS"
|
|
56
|
+
system_prompt:
|
|
57
|
+
inline: "Handle fake implementation tasks."
|
|
58
|
+
file: null
|
|
59
|
+
tools:
|
|
60
|
+
- "fs_read"
|
|
61
|
+
- "fs_write"
|
|
62
|
+
- "fs_list"
|
|
63
|
+
- "execute_bash"
|
|
64
|
+
- "git_diff"
|
|
65
|
+
- "mcp_team"
|
|
66
|
+
- "provider_builtin"
|
|
67
|
+
permission_mode: "restricted"
|
|
68
|
+
preferred_for:
|
|
69
|
+
- "implementation"
|
|
70
|
+
avoid_for: []
|
|
71
|
+
output_contract:
|
|
72
|
+
format: "result_envelope_v1"
|
|
73
|
+
required_fields:
|
|
74
|
+
- "task_id"
|
|
75
|
+
- "status"
|
|
76
|
+
- "summary"
|
|
77
|
+
- "artifacts"
|
|
78
|
+
routing:
|
|
79
|
+
default_assignee: "leader"
|
|
80
|
+
rules:
|
|
81
|
+
- id: "implementation-to-fake"
|
|
82
|
+
match:
|
|
83
|
+
type:
|
|
84
|
+
- "implementation"
|
|
85
|
+
assign_to: "fake_impl"
|
|
86
|
+
priority: 10
|
|
87
|
+
communication:
|
|
88
|
+
protocol: "mcp_inbox"
|
|
89
|
+
topology: "leader_centered"
|
|
90
|
+
worker_to_worker: true
|
|
91
|
+
ack_timeout_sec: 2
|
|
92
|
+
result_format: "result_envelope_v1"
|
|
93
|
+
message_store:
|
|
94
|
+
sqlite: ".team/runtime/team.db"
|
|
95
|
+
mirror_files: ".team/messages"
|
|
96
|
+
runtime:
|
|
97
|
+
backend: "tmux"
|
|
98
|
+
display_backend: "none"
|
|
99
|
+
session_name: "team-agent-fake-e2e"
|
|
100
|
+
auto_launch: true
|
|
101
|
+
require_user_approval_before_launch: false
|
|
102
|
+
max_active_agents: 1
|
|
103
|
+
startup_order:
|
|
104
|
+
- "fake_impl"
|
|
105
|
+
context:
|
|
106
|
+
state_file: "team_state.md"
|
|
107
|
+
artifact_dir: ".team/artifacts"
|
|
108
|
+
log_dir: ".team/logs"
|
|
109
|
+
summarization:
|
|
110
|
+
worker_full_logs: "retain_outside_leader_context"
|
|
111
|
+
state_update: "after_each_result"
|
|
112
|
+
tasks:
|
|
113
|
+
- id: "task_impl"
|
|
114
|
+
title: "Fake implementation"
|
|
115
|
+
type: "implementation"
|
|
116
|
+
assignee: null
|
|
117
|
+
deps: []
|
|
118
|
+
acceptance:
|
|
119
|
+
- "fake result collected"
|
|
120
|
+
status: "pending"
|
|
121
|
+
requires_tools:
|
|
122
|
+
- "fs_write"
|
|
123
|
+
- "execute_bash"
|
|
124
|
+
files:
|
|
125
|
+
- "src/example.py"
|
|
126
|
+
risk: "low"
|
|
127
|
+
"#;
|
|
128
|
+
|
|
129
|
+
fn seed_team_spec(ws: &std::path::Path) {
|
|
130
|
+
let spec = FAKE_SPEC_YAML.replace("/WS", &ws.to_string_lossy());
|
|
131
|
+
std::fs::write(ws.join("team.spec.yaml"), spec).unwrap();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── sessions ── golden cli/parser.py:230 `cmd_sessions` -> runtime.sessions(ws). EXIT 0.
|
|
135
|
+
// `team-agent sessions --workspace <ws> --json` on an empty ws ->
|
|
136
|
+
// {"ok":true,"sessions":[],"workspace":"<ws>"} (--json sort_keys). RED: unrouted -> Error.
|
|
137
|
+
#[test]
|
|
138
|
+
fn dispatch_routes_sessions_subcommand() {
|
|
139
|
+
let ws = tmp_workspace();
|
|
140
|
+
let code = run(&cli_argv(&["sessions", "--workspace", &ws.to_string_lossy(), "--json"]), &ws);
|
|
141
|
+
assert_eq!(
|
|
142
|
+
code,
|
|
143
|
+
ExitCode::Ok,
|
|
144
|
+
"`sessions` must ROUTE to cmd_sessions (golden parser.py:230, exit 0 {{ok,sessions,workspace}}); \
|
|
145
|
+
today it falls to the unknown-subcommand arm (emit.rs:77) -> Error"
|
|
146
|
+
);
|
|
147
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── validate-result ── golden parser.py:312 `cmd_validate_result` (commands.py:206). A FULL valid
|
|
151
|
+
// result_envelope_v1 -> {"agent_id":"a1","ok":true,"status":"success","task_id":"t1"} EXIT 0.
|
|
152
|
+
// RED: unrouted -> Error. (A partial envelope golden-exits 1 — that would be false-green, so the
|
|
153
|
+
// RED uses the complete envelope that golden accepts.)
|
|
154
|
+
#[test]
|
|
155
|
+
fn dispatch_routes_validate_result_valid_envelope() {
|
|
156
|
+
let envelope = r#"{"schema_version":"result_envelope_v1","task_id":"t1","agent_id":"a1","status":"success","summary":"done","artifacts":[],"changes":[],"tests":[],"risks":[],"next_actions":[]}"#;
|
|
157
|
+
let code = run(&cli_argv(&["validate-result", envelope, "--json"]), std::path::Path::new("."));
|
|
158
|
+
assert_eq!(
|
|
159
|
+
code,
|
|
160
|
+
ExitCode::Ok,
|
|
161
|
+
"`validate-result <valid envelope> --json` must ROUTE to cmd_validate_result (parser.py:312) \
|
|
162
|
+
and exit 0 with {{agent_id,ok,status,task_id}}; today -> unknown-subcommand Error"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ── collect ── golden parser.py:292 `cmd_collect` -> runtime.collect(ws). With a valid
|
|
167
|
+
// team.spec.yaml present and nothing to collect -> EXIT 0, golden:
|
|
168
|
+
// {"collected":[],"collected_results":[],"coordinator":{"ok":false,"status":"not_required"},
|
|
169
|
+
// "delivered_messages":[],"invalid_results":[],"ok":true,"results":{...},"state_file":"<ws>/team_state.md"}
|
|
170
|
+
// RED: unrouted -> Error.
|
|
171
|
+
#[test]
|
|
172
|
+
fn dispatch_routes_collect_with_spec() {
|
|
173
|
+
let ws = tmp_workspace();
|
|
174
|
+
seed_team_spec(&ws);
|
|
175
|
+
let code = run(&cli_argv(&["collect", "--workspace", &ws.to_string_lossy(), "--json"]), &ws);
|
|
176
|
+
assert_eq!(
|
|
177
|
+
code,
|
|
178
|
+
ExitCode::Ok,
|
|
179
|
+
"`collect` must ROUTE to cmd_collect (parser.py:292); with a valid spec golden exits 0 \
|
|
180
|
+
{{collected,collected_results,coordinator,delivered_messages,invalid_results,ok,results,state_file}}; \
|
|
181
|
+
today -> unknown-subcommand Error"
|
|
182
|
+
);
|
|
183
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── repair-state ── golden parser.py:303 `cmd_repair_state` -> runtime.repair_state (quick_start.py:285).
|
|
187
|
+
// `--task <id>` required; `--status` must be in TASK_STATUSES{blocked,cancelled,done,failed,
|
|
188
|
+
// needs_retry,pending,ready,running}. With a seeded task + --status done -> EXIT 0:
|
|
189
|
+
// {"after":{...},"before":{...},"ok":true,"state_file":"<ws>/team_state.md","task_id":"fake_impl"}
|
|
190
|
+
// RED: unrouted -> Error.
|
|
191
|
+
#[test]
|
|
192
|
+
fn dispatch_routes_repair_state_with_task() {
|
|
193
|
+
let ws = tmp_workspace();
|
|
194
|
+
seed_team_spec(&ws);
|
|
195
|
+
std::fs::write(
|
|
196
|
+
ws.join(".team").join("runtime").join("state.json"),
|
|
197
|
+
serde_json::to_vec(&json!({
|
|
198
|
+
"leader": {"id": "leader"},
|
|
199
|
+
"tasks": [{"id": "fake_impl", "title": "impl", "status": "open", "assignee": "fake_impl", "type": "implementation"}],
|
|
200
|
+
}))
|
|
201
|
+
.unwrap(),
|
|
202
|
+
)
|
|
203
|
+
.unwrap();
|
|
204
|
+
let code = run(
|
|
205
|
+
&cli_argv(&[
|
|
206
|
+
"repair-state", "--workspace", &ws.to_string_lossy(),
|
|
207
|
+
"--task", "fake_impl", "--status", "done", "--summary", "ok", "--json",
|
|
208
|
+
]),
|
|
209
|
+
&ws,
|
|
210
|
+
);
|
|
211
|
+
assert_eq!(
|
|
212
|
+
code,
|
|
213
|
+
ExitCode::Ok,
|
|
214
|
+
"`repair-state --task <id> --status done` must ROUTE to cmd_repair_state (parser.py:303) and \
|
|
215
|
+
exit 0 {{after,before,ok,state_file,task_id}}; today -> unknown-subcommand Error"
|
|
216
|
+
);
|
|
217
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── diagnose ── golden parser.py:298 `cmd_diagnose` -> runtime.diagnose(ws) (diagnose/health.py:19).
|
|
221
|
+
// ok:true only when there are zero issues. Seed leader_receiver=attached + NO session_name + NO
|
|
222
|
+
// agents -> issues=[] -> EXIT 0, golden top keys (sort): event_log,issues,ok,runtime,suggested_repairs.
|
|
223
|
+
// RED: unrouted -> Error.
|
|
224
|
+
#[test]
|
|
225
|
+
fn dispatch_routes_diagnose_healthy_leader() {
|
|
226
|
+
let ws = tmp_workspace();
|
|
227
|
+
std::fs::write(
|
|
228
|
+
ws.join(".team").join("runtime").join("state.json"),
|
|
229
|
+
serde_json::to_vec(&json!({
|
|
230
|
+
"leader": {"id": "leader"},
|
|
231
|
+
"leader_receiver": {"mode": "direct_tmux", "status": "attached", "pane_id": "%1", "provider": "codex"},
|
|
232
|
+
}))
|
|
233
|
+
.unwrap(),
|
|
234
|
+
)
|
|
235
|
+
.unwrap();
|
|
236
|
+
let code = run(&cli_argv(&["diagnose", "--workspace", &ws.to_string_lossy(), "--json"]), &ws);
|
|
237
|
+
assert_eq!(
|
|
238
|
+
code,
|
|
239
|
+
ExitCode::Ok,
|
|
240
|
+
"`diagnose` must ROUTE to cmd_diagnose (parser.py:298); a healthy (attached, no-session, \
|
|
241
|
+
no-agent) state yields zero issues -> exit 0 {{event_log,issues,ok,runtime,suggested_repairs}}; \
|
|
242
|
+
today -> unknown-subcommand Error"
|
|
243
|
+
);
|
|
244
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── preflight (#[ignore] real-machine) ── golden parser.py:160 `cmd_preflight` -> runtime.preflight
|
|
248
|
+
// (Path(args.team)). Validates a team role-doc dir: checks compile(TEAM.md)/tmux/ghostty/rust_core/
|
|
249
|
+
// profile_dir; golden -> {"blockers":[...],"checks":[...],"details_log":...,"next_actions":[...],
|
|
250
|
+
// "ok":bool,"summary":...}. Reaches ok:true (exit 0) ONLY with a valid TEAM.md + ghostty installed,
|
|
251
|
+
// which CI cannot supply (ghostty absent -> blocker) -> not a clean in-process exit-0 RED.
|
|
252
|
+
#[test]
|
|
253
|
+
#[ignore = "real-machine: `preflight --team <dir>` needs a valid TEAM.md role-doc dir + ghostty to \
|
|
254
|
+
reach ok:true; on CI it golden-exits 1 (compile/ghostty blockers) which is \
|
|
255
|
+
indistinguishable from the unknown-subcommand Error (false-green). Routes to cmd_preflight \
|
|
256
|
+
(parser.py:160); shape {blockers,checks,details_log,next_actions,ok,summary}"]
|
|
257
|
+
fn dispatch_routes_preflight_real_machine() {
|
|
258
|
+
let ws = tmp_workspace();
|
|
259
|
+
let code = run(&cli_argv(&["preflight", "--team", &ws.to_string_lossy(), "--json"]), &ws);
|
|
260
|
+
assert_eq!(code, ExitCode::Ok, "preflight must ROUTE + exit 0 on a valid team dir");
|
|
261
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ── wait-ready (#[ignore] real-machine) ── golden parser.py:171 `cmd_wait_ready` -> runtime.wait_ready
|
|
265
|
+
// (ws, timeout). Polls worker readiness; golden -> {"details_log":...,"next_actions":[...],"ok":bool,
|
|
266
|
+
// "readiness":{cli_prompt_ready,mcp_ready,process_started,task_prompt_delivered},"summary":...}.
|
|
267
|
+
// ok:true (exit 0) needs a LIVE ready team; CI has none -> golden-exits 1 (false-green vs unknown).
|
|
268
|
+
#[test]
|
|
269
|
+
#[ignore = "real-machine: `wait-ready` polls a LIVE team's readiness; with no workers golden-exits 1 \
|
|
270
|
+
(false-green vs unknown-subcommand Error). Routes to cmd_wait_ready (parser.py:171); shape \
|
|
271
|
+
{details_log,next_actions,ok,readiness{cli_prompt_ready,mcp_ready,process_started,task_prompt_delivered},summary}"]
|
|
272
|
+
fn dispatch_routes_wait_ready_real_machine() {
|
|
273
|
+
let ws = tmp_workspace();
|
|
274
|
+
let code = run(&cli_argv(&["wait-ready", "--workspace", &ws.to_string_lossy(), "--timeout", "1", "--json"]), &ws);
|
|
275
|
+
assert_eq!(code, ExitCode::Ok, "wait-ready must ROUTE + exit 0 once the team is ready");
|
|
276
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ── e2e --providers fake (#[ignore] real-machine) ── golden parser.py:449 `cmd_e2e` (cli/e2e.py:12).
|
|
280
|
+
// `--providers fake` runs a REAL end-to-end: runtime.launch(spec, auto_approve=True) (spawns a tmux
|
|
281
|
+
// team) -> send_message -> sleep -> collect -> shutdown. Result envelope (e2e.py:16,48-56):
|
|
282
|
+
// {"workspace":str,"providers":{"fake":{"ok":bool,"launch":{...},"send":{...},"collect":{...},
|
|
283
|
+
// "shutdown":{...}}},"ok":bool}. Real tmux spawn -> #[ignore] (NOT runnable in-process).
|
|
284
|
+
#[test]
|
|
285
|
+
#[ignore = "real-machine: `e2e --providers fake` calls runtime.launch -> spawns a REAL tmux team \
|
|
286
|
+
(send/collect/shutdown). Routes to cmd_e2e (parser.py:449 / e2e.py:12); envelope \
|
|
287
|
+
{workspace,providers:{fake:{ok,launch,send,collect,shutdown}},ok}"]
|
|
288
|
+
fn dispatch_routes_e2e_fake_real_machine() {
|
|
289
|
+
let ws = tmp_workspace();
|
|
290
|
+
let code = run(&cli_argv(&["e2e", "--providers", "fake", "--workspace", &ws.to_string_lossy(), "--json"]), &ws);
|
|
291
|
+
assert_eq!(code, ExitCode::Ok, "e2e --providers fake must ROUTE + run the fake end-to-end (ok:true)");
|
|
292
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ── peek (#[ignore] real-machine) ── golden parser.py:201 `cmd_peek` (commands.py:118). Requires
|
|
296
|
+
// `--allow-raw-screen` (else TeamAgentError) + a mutually-exclusive --head/--tail/--search; then
|
|
297
|
+
// runtime.peek captures a LIVE tmux pane (tmux capture-pane). Real tmux -> #[ignore]. Routes to
|
|
298
|
+
// cmd_peek; --json returns the peek dict (text + capture metadata).
|
|
299
|
+
#[test]
|
|
300
|
+
#[ignore = "real-machine: `peek <agent> --tail N --allow-raw-screen` captures a LIVE tmux pane \
|
|
301
|
+
(tmux capture-pane); needs a running worker pane. Routes to cmd_peek (parser.py:201); \
|
|
302
|
+
--json returns the peek dict (raw screen text + metadata)"]
|
|
303
|
+
fn dispatch_routes_peek_real_machine() {
|
|
304
|
+
let ws = tmp_workspace();
|
|
305
|
+
let code = run(
|
|
306
|
+
&cli_argv(&["peek", "fake_impl", "--workspace", &ws.to_string_lossy(), "--tail", "20", "--allow-raw-screen", "--json"]),
|
|
307
|
+
&ws,
|
|
308
|
+
);
|
|
309
|
+
assert_eq!(code, ExitCode::Ok, "peek must ROUTE + capture the live pane (real machine)");
|
|
310
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// CONTRACT (shared-root, real-machine-driven; golden = correct-behavior baseline): wait_readiness
|
|
314
|
+
// derives cli_prompt_ready from the LIFECYCLE status — an alive worker (status="running") IS
|
|
315
|
+
// cli_prompt_ready (golden quick_start.py:173 `status ∈ {running, busy}`). Rust (diagnose.rs:280)
|
|
316
|
+
// requires cli_prompt_ready flag / startup_prompts=="complete" / status=="ready" and does NOT accept
|
|
317
|
+
// "running" → an alive fake worker never becomes ready → wait_ready times out. Same shared root as the
|
|
318
|
+
// deferred_busy regression: lifecycle "running" is the authoritative alive/ready signal, not a
|
|
319
|
+
// turn-level flag. (process_started/mcp_ready/task_prompt_delivered are satisfied here so `ready`
|
|
320
|
+
// hinges solely on the cli_prompt_ready derivation.)
|
|
321
|
+
#[test]
|
|
322
|
+
fn contract_alive_worker_running_is_cli_prompt_ready_and_ready() {
|
|
323
|
+
let state = serde_json::json!({"agents": {"w1": {
|
|
324
|
+
"status": "running",
|
|
325
|
+
"pane_id": "%1",
|
|
326
|
+
"mcp_ready": true,
|
|
327
|
+
"first_send_at": "2026-01-01T00:00:00Z"
|
|
328
|
+
}}});
|
|
329
|
+
let r = crate::cli::diagnose::wait_readiness(&state);
|
|
330
|
+
assert_eq!(
|
|
331
|
+
r.get("cli_prompt_ready").and_then(serde_json::Value::as_bool),
|
|
332
|
+
Some(true),
|
|
333
|
+
"CONTRACT: an alive worker (lifecycle status=running) is cli_prompt_ready (golden status∈{{running,busy}}); got {r:?}"
|
|
334
|
+
);
|
|
335
|
+
assert_eq!(
|
|
336
|
+
r.get("ready").and_then(serde_json::Value::as_bool),
|
|
337
|
+
Some(true),
|
|
338
|
+
"status=running + pane_id + mcp_ready + first_send_at → all four readiness signals derive true → ready (no timeout); got {r:?}"
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// CONTRACT (real-machine wait_ready product FAIL @ 8ea5df5): a live fake quick-start worker times out
|
|
343
|
+
// with process_started=false + mcp_ready=false. golden (quick_start.py:172/174):
|
|
344
|
+
// process_started = bool(last["tmux_session_present"]) (the live tmux session exists)
|
|
345
|
+
// mcp_ready = all(Path(agent["mcp_config"]).exists()) (each agent's mcp_config FILE exists)
|
|
346
|
+
// The Rust launch (launch.rs:220-228) persists status="running" + mcp_config (path) but NO pane_id/pid
|
|
347
|
+
// and NO mcp_ready flag. wait_readiness reads per-agent pane_id/pid for process_started (-> false) and an
|
|
348
|
+
// mcp_ready FLAG for mcp_ready (-> false), so a live worker never becomes ready. The shared-root fix only
|
|
349
|
+
// corrected cli_prompt_ready (status=running); these two signals still read the wrong source. This RED
|
|
350
|
+
// uses the REALISTIC post-launch state (no synthetic pane_id / mcp_ready flag).
|
|
351
|
+
#[test]
|
|
352
|
+
fn contract_wait_ready_derives_process_started_from_session_and_mcp_ready_from_file() {
|
|
353
|
+
let dir = std::env::temp_dir().join(format!(
|
|
354
|
+
"ta-wr-{}-{}",
|
|
355
|
+
std::process::id(),
|
|
356
|
+
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
|
|
357
|
+
));
|
|
358
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
359
|
+
let mcp = dir.join("mcp.json");
|
|
360
|
+
std::fs::write(&mcp, "{}").unwrap();
|
|
361
|
+
let state = serde_json::json!({
|
|
362
|
+
"session_name": "ta-fake",
|
|
363
|
+
"tmux_session_present": true, // golden status() top-level signal: the live tmux session exists
|
|
364
|
+
"agents": { "w1": {
|
|
365
|
+
"status": "running",
|
|
366
|
+
"mcp_config": mcp.to_string_lossy(),
|
|
367
|
+
"first_send_at": "2026-01-01T00:00:00Z"
|
|
368
|
+
}}
|
|
369
|
+
});
|
|
370
|
+
let r = crate::cli::diagnose::wait_readiness(&state);
|
|
371
|
+
assert_eq!(
|
|
372
|
+
r.get("process_started").and_then(serde_json::Value::as_bool),
|
|
373
|
+
Some(true),
|
|
374
|
+
"CONTRACT: process_started derives from tmux_session_present (golden quick_start.py:172), NOT \
|
|
375
|
+
per-agent pane_id/pid (the launch writes neither); got {r:?}"
|
|
376
|
+
);
|
|
377
|
+
assert_eq!(
|
|
378
|
+
r.get("mcp_ready").and_then(serde_json::Value::as_bool),
|
|
379
|
+
Some(true),
|
|
380
|
+
"CONTRACT: mcp_ready derives from each agent's mcp_config FILE existence (golden quick_start.py:174), \
|
|
381
|
+
NOT an mcp_ready flag (never set); got {r:?}"
|
|
382
|
+
);
|
|
383
|
+
assert_eq!(
|
|
384
|
+
r.get("ready").and_then(serde_json::Value::as_bool),
|
|
385
|
+
Some(true),
|
|
386
|
+
"a live fake quick-start worker (session present + status running + mcp_config file + task sent) \
|
|
387
|
+
must be ready, not timeout; got {r:?}"
|
|
388
|
+
);
|
|
389
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
390
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
//! cli · tests — RED 契约 lane(verbatim 从原 cli.rs 的 `#[cfg(test)] mod tests` 迁移)。
|
|
2
|
+
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
3
|
+
|
|
4
|
+
use super::*;
|
|
5
|
+
use serde_json::json;
|
|
6
|
+
|
|
7
|
+
fn tmp_workspace() -> std::path::PathBuf {
|
|
8
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
9
|
+
static CTR: AtomicU64 = AtomicU64::new(0);
|
|
10
|
+
let dir = std::env::temp_dir().join(format!(
|
|
11
|
+
"ta-cli-test-{}-{}",
|
|
12
|
+
std::process::id(),
|
|
13
|
+
CTR.fetch_add(1, Ordering::Relaxed)
|
|
14
|
+
));
|
|
15
|
+
std::fs::create_dir_all(dir.join(".team").join("runtime")).unwrap();
|
|
16
|
+
dir
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
fn seed_status_workspace() -> std::path::PathBuf {
|
|
20
|
+
let dir = std::env::temp_dir().join(format!(
|
|
21
|
+
"ta-cli-status-{}-{}",
|
|
22
|
+
std::process::id(),
|
|
23
|
+
std::time::SystemTime::now()
|
|
24
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
25
|
+
.unwrap()
|
|
26
|
+
.as_nanos()
|
|
27
|
+
));
|
|
28
|
+
let team = dir.join(".team");
|
|
29
|
+
std::fs::create_dir_all(team.join("runtime")).unwrap();
|
|
30
|
+
let state = json!({
|
|
31
|
+
"session_name": "seeded-team",
|
|
32
|
+
"leader": {"id": "leader"},
|
|
33
|
+
"leader_receiver": {"pane_id": "%3", "pane_current_command": "codex", "status": "running"},
|
|
34
|
+
"agents": {"a1": {"status": "running", "first_send_at": "2026-01-01T00:00:00Z"}},
|
|
35
|
+
"tasks": [{"id": "t1", "title": "demo", "status": "open", "assignee": "a1"}],
|
|
36
|
+
});
|
|
37
|
+
std::fs::write(
|
|
38
|
+
team.join("runtime").join("state.json"),
|
|
39
|
+
serde_json::to_vec_pretty(&state).unwrap(),
|
|
40
|
+
)
|
|
41
|
+
.unwrap();
|
|
42
|
+
dir
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const DELEG_VALID_ROLE: &str = "---\nname: implementer\nrole: Implementation Engineer\nprovider: fake\nmodel: fake\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nImplement bounded tasks.\n";
|
|
46
|
+
const DELEG_INVALID_ROLE: &str = "---\nname: broken\nrole: Broken Worker\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nNo provider field.\n";
|
|
47
|
+
const DELEG_TEAM_MD: &str = "---\nname: clidelegteam\nobjective: Delegation probe.\nprovider: fake\n---\n\nteam.\n";
|
|
48
|
+
|
|
49
|
+
fn deleg_uniq_dir(tag: &str) -> std::path::PathBuf {
|
|
50
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
51
|
+
static N: AtomicU64 = AtomicU64::new(0);
|
|
52
|
+
let dir = std::env::temp_dir().join(format!(
|
|
53
|
+
"ta-cli-deleg-{}-{}-{}",
|
|
54
|
+
tag,
|
|
55
|
+
std::process::id(),
|
|
56
|
+
N.fetch_add(1, Ordering::Relaxed)
|
|
57
|
+
));
|
|
58
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
59
|
+
dir
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fn deleg_team_dir_with_healthy_coordinator() -> std::path::PathBuf {
|
|
63
|
+
let base = deleg_uniq_dir("qs");
|
|
64
|
+
let team = base.join("teamdir");
|
|
65
|
+
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
66
|
+
std::fs::write(team.join("TEAM.md"), DELEG_TEAM_MD).unwrap();
|
|
67
|
+
std::fs::write(team.join("agents").join("implementer.md"), DELEG_VALID_ROLE).unwrap();
|
|
68
|
+
let wp = crate::coordinator::WorkspacePath::new(base.clone());
|
|
69
|
+
std::fs::create_dir_all(crate::model::paths::runtime_dir(&base)).unwrap();
|
|
70
|
+
let _ = crate::message_store::MessageStore::open(&base).unwrap();
|
|
71
|
+
let me = crate::coordinator::Pid::new(std::process::id());
|
|
72
|
+
crate::coordinator::write_coordinator_metadata(&wp, me, crate::coordinator::MetadataSource::Boot).unwrap();
|
|
73
|
+
std::fs::write(crate::coordinator::coordinator_pid_path(&wp), me.to_string()).unwrap();
|
|
74
|
+
team
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn outcome_text(r: Result<CmdResult, CliError>) -> String {
|
|
78
|
+
match r {
|
|
79
|
+
Ok(cmd) => format!("{cmd:?}"),
|
|
80
|
+
Err(e) => e.to_string(),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
mod base;
|
|
85
|
+
mod compile;
|
|
86
|
+
mod divergence;
|
|
87
|
+
mod lane_c;
|
|
88
|
+
mod leader_watch;
|
|
89
|
+
mod missing_subcommands;
|
|
90
|
+
mod repair_state_byte_lock;
|
|
91
|
+
mod peer_allow;
|
|
92
|
+
mod run_delegation;
|
|
93
|
+
mod status_send;
|
|
94
|
+
mod verb_validate;
|
|
95
|
+
mod verb_settle;
|
|
96
|
+
mod verb_profile;
|
|
97
|
+
mod main_preserved;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// P0-1 LANE — `allow-peer-talk` byte-lock (was ZERO-test; a SHIPPED compat-noop).
|
|
5
|
+
//
|
|
6
|
+
// golden: cli/commands.py:422 -> messaging/leader.py:25 allow_peer_talk(ws,a,b)
|
|
7
|
+
// -> message_store/core.py:326 allow_peer (INSERT OR IGNORE BOTH (a,b) & (b,a))
|
|
8
|
+
// + events.py:27-35 EventLog.write (sort_keys=True -> .team/logs/events.jsonl).
|
|
9
|
+
//
|
|
10
|
+
// Golden bytes captured live (PYTHONPATH=.../src python3 /tmp/probe_app*.py):
|
|
11
|
+
// return (insertion order): {"ok":true,"a":..,"b":..,"status":"compat_noop",
|
|
12
|
+
// "reason":"team_scoped_peer_messages_enabled"}
|
|
13
|
+
// return (--json sort_keys): keys a,b,ok,reason,status
|
|
14
|
+
// event line (sort_keys): {"a":..,"b":..,"event":"communication.peer_allowed","ts":..}
|
|
15
|
+
// idempotent: 2nd call -> identical return + a SECOND event line
|
|
16
|
+
// (event fires per-call; DB rows are insert-or-ignore)
|
|
17
|
+
// unknown/undeclared agents: NO validation -> ok:true compat_noop, exit 0
|
|
18
|
+
//
|
|
19
|
+
// Rust impl: messaging/peers.rs:11 + message_store.rs:251 + adapters.rs:173.
|
|
20
|
+
// serde_json `preserve_order` is ON (Cargo.toml:24) so the default (human) output
|
|
21
|
+
// iterates dict keys in golden insertion order. These tests LOCK the contract so a
|
|
22
|
+
// porter/refactor cannot silently drift this live verb.
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
fn app_out(r: CmdResult) -> serde_json::Value {
|
|
26
|
+
match r.output {
|
|
27
|
+
CmdOutput::Json(v) => v,
|
|
28
|
+
_ => panic!("allow-peer-talk must emit a Json CmdOutput"),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn app_events(ws: &std::path::Path) -> Vec<String> {
|
|
33
|
+
std::fs::read_to_string(ws.join(".team").join("logs").join("events.jsonl"))
|
|
34
|
+
.unwrap_or_default()
|
|
35
|
+
.lines()
|
|
36
|
+
.map(str::to_string)
|
|
37
|
+
.collect()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn app_call(ws: &std::path::Path, a: &str, b: &str) -> CmdResult {
|
|
41
|
+
cmd_allow_peer_talk(&AllowPeerTalkArgs {
|
|
42
|
+
a: a.to_string(),
|
|
43
|
+
b: b.to_string(),
|
|
44
|
+
workspace: ws.to_path_buf(),
|
|
45
|
+
json: false,
|
|
46
|
+
})
|
|
47
|
+
.expect("allow-peer-talk is a compat-noop and must not error")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── return dict: exact compat_noop shape + golden insertion order (drives human emit) ──
|
|
51
|
+
#[test]
|
|
52
|
+
fn allow_peer_talk_output_byte_locks_golden_compat_noop() {
|
|
53
|
+
let ws = tmp_workspace();
|
|
54
|
+
let v = app_out(app_call(&ws, "alpha", "bravo"));
|
|
55
|
+
assert_eq!(
|
|
56
|
+
v,
|
|
57
|
+
json!({
|
|
58
|
+
"ok": true,
|
|
59
|
+
"a": "alpha",
|
|
60
|
+
"b": "bravo",
|
|
61
|
+
"status": "compat_noop",
|
|
62
|
+
"reason": "team_scoped_peer_messages_enabled"
|
|
63
|
+
}),
|
|
64
|
+
"golden leader.py:28 return dict (key set + values); got {v:?}"
|
|
65
|
+
);
|
|
66
|
+
let order: Vec<&str> = v.as_object().expect("object").keys().map(String::as_str).collect();
|
|
67
|
+
assert_eq!(
|
|
68
|
+
order,
|
|
69
|
+
vec!["ok", "a", "b", "status", "reason"],
|
|
70
|
+
"golden dict insertion order ok,a,b,status,reason drives the default human emit key order \
|
|
71
|
+
(helpers.py:16-19); got {order:?}"
|
|
72
|
+
);
|
|
73
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── event: exactly one `communication.peer_allowed` line, sort_keys byte form, payload {a,b} ──
|
|
77
|
+
#[test]
|
|
78
|
+
fn allow_peer_talk_event_byte_locks_golden_line() {
|
|
79
|
+
let ws = tmp_workspace();
|
|
80
|
+
let _ = app_call(&ws, "alpha", "bravo");
|
|
81
|
+
let lines = app_events(&ws);
|
|
82
|
+
assert_eq!(lines.len(), 1, "exactly one event per call; got {lines:?}");
|
|
83
|
+
let v: serde_json::Value = serde_json::from_str(&lines[0]).expect("event line must be json");
|
|
84
|
+
let ts = v["ts"].as_str().expect("event has string ts").to_string();
|
|
85
|
+
let expected = format!(
|
|
86
|
+
"{{\"a\": \"alpha\", \"b\": \"bravo\", \"event\": \"communication.peer_allowed\", \"ts\": \"{ts}\"}}"
|
|
87
|
+
);
|
|
88
|
+
assert_eq!(
|
|
89
|
+
lines[0], expected,
|
|
90
|
+
"golden events.py:35 json.dumps(...,sort_keys=True) line: sorted keys a,b,event,ts with \
|
|
91
|
+
Python `, `/`: ` spacing; got {}",
|
|
92
|
+
lines[0]
|
|
93
|
+
);
|
|
94
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── idempotency: identical return, event fires on EVERY call (rows are insert-or-ignore) ──
|
|
98
|
+
#[test]
|
|
99
|
+
fn allow_peer_talk_is_idempotent_and_fires_event_per_call() {
|
|
100
|
+
let ws = tmp_workspace();
|
|
101
|
+
let v1 = app_out(app_call(&ws, "alpha", "bravo"));
|
|
102
|
+
let v2 = app_out(app_call(&ws, "alpha", "bravo")); // duplicate pair
|
|
103
|
+
assert_eq!(v1, v2, "repeated allow returns identical compat_noop output");
|
|
104
|
+
let lines = app_events(&ws);
|
|
105
|
+
assert_eq!(
|
|
106
|
+
lines.len(),
|
|
107
|
+
2,
|
|
108
|
+
"golden fires communication.peer_allowed on EVERY call (event NOT deduped; only DB rows \
|
|
109
|
+
are insert-or-ignore); got {lines:?}"
|
|
110
|
+
);
|
|
111
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── permissive: undeclared/unknown agents still succeed (golden has NO validation path) ──
|
|
115
|
+
#[test]
|
|
116
|
+
fn allow_peer_talk_unknown_agents_succeed_no_validation() {
|
|
117
|
+
let ws = tmp_workspace(); // no state.json / no spec: agents never declared
|
|
118
|
+
let r = app_call(&ws, "ghost", "phantom-x");
|
|
119
|
+
assert_eq!(
|
|
120
|
+
r.exit,
|
|
121
|
+
ExitCode::Ok,
|
|
122
|
+
"golden leader.py:25-28 never validates agent ids -> no error, exit 0"
|
|
123
|
+
);
|
|
124
|
+
let v = app_out(r);
|
|
125
|
+
assert_eq!(
|
|
126
|
+
v,
|
|
127
|
+
json!({
|
|
128
|
+
"ok": true,
|
|
129
|
+
"a": "ghost",
|
|
130
|
+
"b": "phantom-x",
|
|
131
|
+
"status": "compat_noop",
|
|
132
|
+
"reason": "team_scoped_peer_messages_enabled"
|
|
133
|
+
}),
|
|
134
|
+
"unknown agents still get compat_noop (no unknown-agent error string/exit); got {v:?}"
|
|
135
|
+
);
|
|
136
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
137
|
+
}
|