@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,302 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
const REPAIR_SPEC_TEMPLATE: &str = r#"version: 1
|
|
4
|
+
team:
|
|
5
|
+
name: "repair-team"
|
|
6
|
+
mode: "supervisor_worker"
|
|
7
|
+
objective: "Exercise repair-state."
|
|
8
|
+
workspace: "__WS__"
|
|
9
|
+
leader:
|
|
10
|
+
id: "leader"
|
|
11
|
+
role: "leader"
|
|
12
|
+
provider: "fake"
|
|
13
|
+
model: null
|
|
14
|
+
tools:
|
|
15
|
+
- "fs_read"
|
|
16
|
+
- "fs_list"
|
|
17
|
+
- "mcp_team"
|
|
18
|
+
context_policy:
|
|
19
|
+
keep_user_thread: true
|
|
20
|
+
receive_worker_outputs: "structured_only"
|
|
21
|
+
max_worker_result_tokens: 2000
|
|
22
|
+
agents:
|
|
23
|
+
- id: "fake_impl"
|
|
24
|
+
role: "implementation_engineer"
|
|
25
|
+
provider: "fake"
|
|
26
|
+
model: null
|
|
27
|
+
working_directory: "__WS__"
|
|
28
|
+
system_prompt:
|
|
29
|
+
inline: "Handle fake implementation tasks."
|
|
30
|
+
file: null
|
|
31
|
+
tools:
|
|
32
|
+
- "fs_read"
|
|
33
|
+
- "mcp_team"
|
|
34
|
+
permission_mode: "restricted"
|
|
35
|
+
preferred_for:
|
|
36
|
+
- "implementation"
|
|
37
|
+
avoid_for: []
|
|
38
|
+
output_contract:
|
|
39
|
+
format: "result_envelope_v1"
|
|
40
|
+
required_fields:
|
|
41
|
+
- "task_id"
|
|
42
|
+
- "status"
|
|
43
|
+
- "summary"
|
|
44
|
+
- "artifacts"
|
|
45
|
+
routing:
|
|
46
|
+
default_assignee: "leader"
|
|
47
|
+
rules:
|
|
48
|
+
- id: "implementation-to-fake"
|
|
49
|
+
match:
|
|
50
|
+
type:
|
|
51
|
+
- "implementation"
|
|
52
|
+
assign_to: "fake_impl"
|
|
53
|
+
priority: 10
|
|
54
|
+
communication:
|
|
55
|
+
protocol: "mcp_inbox"
|
|
56
|
+
topology: "leader_centered"
|
|
57
|
+
worker_to_worker: true
|
|
58
|
+
ack_timeout_sec: 2
|
|
59
|
+
result_format: "result_envelope_v1"
|
|
60
|
+
message_store:
|
|
61
|
+
sqlite: ".team/runtime/team.db"
|
|
62
|
+
mirror_files: ".team/messages"
|
|
63
|
+
runtime:
|
|
64
|
+
backend: "tmux"
|
|
65
|
+
display_backend: "none"
|
|
66
|
+
session_name: "team-agent-repair"
|
|
67
|
+
auto_launch: true
|
|
68
|
+
require_user_approval_before_launch: false
|
|
69
|
+
max_active_agents: 1
|
|
70
|
+
startup_order:
|
|
71
|
+
- "fake_impl"
|
|
72
|
+
context:
|
|
73
|
+
state_file: "team_state.md"
|
|
74
|
+
artifact_dir: ".team/artifacts"
|
|
75
|
+
log_dir: ".team/logs"
|
|
76
|
+
summarization:
|
|
77
|
+
worker_full_logs: "retain_outside_leader_context"
|
|
78
|
+
state_update: "after_each_result"
|
|
79
|
+
tasks:
|
|
80
|
+
- id: "task_impl"
|
|
81
|
+
title: "Fake implementation"
|
|
82
|
+
type: "implementation"
|
|
83
|
+
assignee: null
|
|
84
|
+
deps: []
|
|
85
|
+
acceptance:
|
|
86
|
+
- "fake result collected"
|
|
87
|
+
status: "pending"
|
|
88
|
+
"#;
|
|
89
|
+
|
|
90
|
+
fn seed_repair_workspace(ws: &std::path::Path) {
|
|
91
|
+
std::fs::create_dir_all(ws.join(".team").join("logs")).unwrap();
|
|
92
|
+
std::fs::create_dir_all(ws.join(".team").join("runtime")).unwrap();
|
|
93
|
+
let spec_path = ws.join("team.spec.yaml");
|
|
94
|
+
std::fs::write(
|
|
95
|
+
&spec_path,
|
|
96
|
+
REPAIR_SPEC_TEMPLATE.replace("__WS__", &ws.to_string_lossy()),
|
|
97
|
+
)
|
|
98
|
+
.unwrap();
|
|
99
|
+
crate::state::persist::save_runtime_state(
|
|
100
|
+
ws,
|
|
101
|
+
&json!({
|
|
102
|
+
"spec_path": spec_path.to_string_lossy(),
|
|
103
|
+
"session_name": "team-agent-repair",
|
|
104
|
+
"leader": {"id": "leader"},
|
|
105
|
+
"agents": {"fake_impl": {"status": "stopped", "provider": "fake"}},
|
|
106
|
+
"tasks": [{
|
|
107
|
+
"id": "task_impl",
|
|
108
|
+
"title": "Fake implementation",
|
|
109
|
+
"type": "implementation",
|
|
110
|
+
"assignee": null,
|
|
111
|
+
"deps": [],
|
|
112
|
+
"acceptance": ["fake result collected"],
|
|
113
|
+
"status": "pending"
|
|
114
|
+
}]
|
|
115
|
+
}),
|
|
116
|
+
)
|
|
117
|
+
.unwrap();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fn repair_args(
|
|
121
|
+
ws: &std::path::Path,
|
|
122
|
+
status: &str,
|
|
123
|
+
summary: Option<&str>,
|
|
124
|
+
json: bool,
|
|
125
|
+
) -> RepairStateArgs {
|
|
126
|
+
RepairStateArgs {
|
|
127
|
+
workspace: ws.to_path_buf(),
|
|
128
|
+
task_id: "task_impl".to_string(),
|
|
129
|
+
assignee: Some("fake_impl".to_string()),
|
|
130
|
+
status: status.to_string(),
|
|
131
|
+
summary: summary.map(str::to_string),
|
|
132
|
+
json,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Golden source:
|
|
137
|
+
// - cli/parser.py:303-310 registers `repair-state --workspace --task --assignee --status --summary --json`.
|
|
138
|
+
// - cli/commands.py:196-203 delegates all five args to `runtime.repair_state`.
|
|
139
|
+
// - diagnose/quick_start.py:285-324:
|
|
140
|
+
// * loads state + spec, validates assignee against spec agents plus leader id,
|
|
141
|
+
// * validates status against task_graph.py TASK_STATUSES,
|
|
142
|
+
// * before/after are three-field projections `{assignee,status,last_result_summary}`,
|
|
143
|
+
// * summary writes `last_result_summary` (not `summary`),
|
|
144
|
+
// * writes EventLog event `repair_state.task` with task_id/before/after,
|
|
145
|
+
// * returns `{ok,task_id,before,after,state_file}` in that insertion order.
|
|
146
|
+
// - task_graph.py:5-14 legal statuses are exactly:
|
|
147
|
+
// blocked,cancelled,done,failed,needs_retry,pending,ready,running.
|
|
148
|
+
//
|
|
149
|
+
// Golden probe:
|
|
150
|
+
// PYTHONPATH=/Users/alauda/Documents/code/team-agent-public/src python3 /tmp/probe_repair_state_cli.py
|
|
151
|
+
#[test]
|
|
152
|
+
fn repair_state_success_json_human_and_event_byte_shape() {
|
|
153
|
+
let ws = tmp_workspace();
|
|
154
|
+
seed_repair_workspace(&ws);
|
|
155
|
+
|
|
156
|
+
let json_result = cmd_repair_state(&repair_args(&ws, "done", Some("patched"), true))
|
|
157
|
+
.expect("repair-state success should return ok");
|
|
158
|
+
assert_eq!(json_result.exit, ExitCode::Ok);
|
|
159
|
+
assert_eq!(
|
|
160
|
+
emit(&json_result.output, true).unwrap(),
|
|
161
|
+
format!(
|
|
162
|
+
"{{\n \"after\": {{\n \"assignee\": \"fake_impl\",\n \"last_result_summary\": \"patched\",\n \"status\": \"done\"\n }},\n \"before\": {{\n \"assignee\": null,\n \"last_result_summary\": null,\n \"status\": \"pending\"\n }},\n \"ok\": true,\n \"state_file\": \"{}\",\n \"task_id\": \"task_impl\"\n}}",
|
|
163
|
+
ws.join("team_state.md").to_string_lossy()
|
|
164
|
+
),
|
|
165
|
+
"repair-state --json must match Python pretty sorted JSON byte shape",
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
let event_line = std::fs::read_to_string(ws.join(".team/logs/events.jsonl"))
|
|
169
|
+
.expect("repair-state must write events.jsonl")
|
|
170
|
+
.lines()
|
|
171
|
+
.last()
|
|
172
|
+
.expect("repair-state must append an event")
|
|
173
|
+
.to_string();
|
|
174
|
+
assert!(
|
|
175
|
+
event_line.starts_with("{\"after\": {\"assignee\": \"fake_impl\", \"last_result_summary\": \"patched\", \"status\": \"done\"}, \"before\": {\"assignee\": null, \"last_result_summary\": null, \"status\": \"pending\"}, \"event\": \"repair_state.task\", \"task_id\": \"task_impl\", \"ts\": \""),
|
|
176
|
+
"repair_state.task event must be Python sort_keys JSON with after/before/event/task_id/ts order; got {event_line}",
|
|
177
|
+
);
|
|
178
|
+
assert!(event_line.ends_with("\"}"), "event line must end with timestamp string; got {event_line}");
|
|
179
|
+
|
|
180
|
+
let ws_human = tmp_workspace();
|
|
181
|
+
seed_repair_workspace(&ws_human);
|
|
182
|
+
let human_result = cmd_repair_state(&repair_args(&ws_human, "done", Some("patched"), false))
|
|
183
|
+
.expect("repair-state human success should return ok");
|
|
184
|
+
assert_eq!(
|
|
185
|
+
emit(&human_result.output, false).unwrap(),
|
|
186
|
+
format!(
|
|
187
|
+
"ok: True\ntask_id: task_impl\nbefore: {{\"assignee\": null, \"status\": \"pending\", \"last_result_summary\": null}}\nafter: {{\"assignee\": \"fake_impl\", \"status\": \"done\", \"last_result_summary\": \"patched\"}}\nstate_file: {}",
|
|
188
|
+
ws_human.join("team_state.md").to_string_lossy()
|
|
189
|
+
),
|
|
190
|
+
"repair-state human output must preserve Python returned-dict and nested-dict insertion order",
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
let state = crate::state::persist::load_runtime_state(&ws).unwrap();
|
|
194
|
+
assert_eq!(
|
|
195
|
+
state["tasks"][0]["last_result_summary"],
|
|
196
|
+
json!("patched"),
|
|
197
|
+
"golden writes summary into last_result_summary, not summary",
|
|
198
|
+
);
|
|
199
|
+
assert!(
|
|
200
|
+
state["tasks"][0].get("summary").is_none(),
|
|
201
|
+
"golden does not create a task.summary field during repair-state",
|
|
202
|
+
);
|
|
203
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
204
|
+
let _ = std::fs::remove_dir_all(&ws_human);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#[test]
|
|
208
|
+
fn repair_state_rejects_status_outside_task_statuses() {
|
|
209
|
+
let ws = tmp_workspace();
|
|
210
|
+
seed_repair_workspace(&ws);
|
|
211
|
+
let legal_statuses = vec![
|
|
212
|
+
"blocked",
|
|
213
|
+
"cancelled",
|
|
214
|
+
"done",
|
|
215
|
+
"failed",
|
|
216
|
+
"needs_retry",
|
|
217
|
+
"pending",
|
|
218
|
+
"ready",
|
|
219
|
+
"running",
|
|
220
|
+
];
|
|
221
|
+
assert_eq!(
|
|
222
|
+
legal_statuses,
|
|
223
|
+
vec![
|
|
224
|
+
"blocked",
|
|
225
|
+
"cancelled",
|
|
226
|
+
"done",
|
|
227
|
+
"failed",
|
|
228
|
+
"needs_retry",
|
|
229
|
+
"pending",
|
|
230
|
+
"ready",
|
|
231
|
+
"running"
|
|
232
|
+
],
|
|
233
|
+
"golden TASK_STATUSES from task_graph.py:5 must stay locked",
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
let err = cmd_repair_state(&repair_args(&ws, "assigned", None, true))
|
|
237
|
+
.expect_err("status outside TASK_STATUSES must error");
|
|
238
|
+
assert_eq!(err.to_string(), "unknown task status for repair: assigned");
|
|
239
|
+
let payload = err.to_payload(&ws.join(".team/logs/cli-error-123.log"), "repair-state");
|
|
240
|
+
assert_eq!(
|
|
241
|
+
serde_json::to_string(&payload).unwrap(),
|
|
242
|
+
format!(
|
|
243
|
+
"{{\"ok\":false,\"error\":\"unknown task status for repair: assigned\",\"action\":\"run `team-agent doctor` or inspect the log path shown here\",\"log\":\"{}\"}}",
|
|
244
|
+
ws.join(".team/logs/cli-error-123.log").to_string_lossy()
|
|
245
|
+
),
|
|
246
|
+
"repair-state --json error envelope must match Python compact key order and text",
|
|
247
|
+
);
|
|
248
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// CONTRACT (real-machine repair_state product FAIL): rt-host-d ran `repair-state --task rp1 --status done`
|
|
252
|
+
// in a quick-start workspace whose spec lives in a teamdir (state.spec_path -> <ws>/teamdir/team.spec.yaml),
|
|
253
|
+
// NOT at <ws>/team.spec.yaml. Rust cmd_repair_state calls load_team_spec(workspace) which HARDCODES
|
|
254
|
+
// workspace.join("team.spec.yaml") (adapters.rs:982) -> read_to_string fails 'No such file or directory'.
|
|
255
|
+
// golden (quick_start.py:295) resolves spec_path = state.get("spec_path", workspace/"team.spec.yaml") and
|
|
256
|
+
// SUCCEEDS. NB: golden ALSO writes team_state.md (write_team_state -> state.py mkdir+write) and returns
|
|
257
|
+
// state_file=team_state.md, so the fix is NOT to drop write_team_state (that diverges from golden and breaks
|
|
258
|
+
// repair_state_success_json_human_and_event_byte_shape) — it is to resolve the spec from state.spec_path
|
|
259
|
+
// (Rust already has load_team_spec_optional(workspace, state) that does exactly this).
|
|
260
|
+
#[test]
|
|
261
|
+
fn contract_repair_state_resolves_spec_from_state_spec_path_teamdir_layout() {
|
|
262
|
+
let ws = tmp_workspace();
|
|
263
|
+
std::fs::create_dir_all(ws.join(".team").join("logs")).unwrap();
|
|
264
|
+
std::fs::create_dir_all(ws.join(".team").join("runtime")).unwrap();
|
|
265
|
+
let teamdir = ws.join("teamdir");
|
|
266
|
+
std::fs::create_dir_all(&teamdir).unwrap();
|
|
267
|
+
let spec_path = teamdir.join("team.spec.yaml");
|
|
268
|
+
std::fs::write(
|
|
269
|
+
&spec_path,
|
|
270
|
+
REPAIR_SPEC_TEMPLATE.replace("__WS__", &ws.to_string_lossy()),
|
|
271
|
+
)
|
|
272
|
+
.unwrap();
|
|
273
|
+
// NOTE: NO <ws>/team.spec.yaml (the hardcoded path) and NO pre-existing team_state.md.
|
|
274
|
+
crate::state::persist::save_runtime_state(
|
|
275
|
+
&ws,
|
|
276
|
+
&json!({
|
|
277
|
+
"spec_path": spec_path.to_string_lossy(),
|
|
278
|
+
"session_name": "team-agent-repair",
|
|
279
|
+
"leader": {"id": "leader"},
|
|
280
|
+
"agents": {"fake_impl": {"status": "running", "provider": "fake"}},
|
|
281
|
+
"tasks": [{
|
|
282
|
+
"id": "task_impl", "title": "x", "type": "implementation",
|
|
283
|
+
"assignee": null, "deps": [], "acceptance": ["a"], "status": "pending"
|
|
284
|
+
}]
|
|
285
|
+
}),
|
|
286
|
+
)
|
|
287
|
+
.unwrap();
|
|
288
|
+
|
|
289
|
+
let cmd = cmd_repair_state(&repair_args(&ws, "done", Some("patched"), true)).expect(
|
|
290
|
+
"CONTRACT: repair-state --status done must resolve the spec from state.spec_path (golden \
|
|
291
|
+
quick_start.py:295), not the hardcoded <ws>/team.spec.yaml (adapters.rs:982) which is absent in \
|
|
292
|
+
the teamdir layout -> currently io 'No such file or directory'",
|
|
293
|
+
);
|
|
294
|
+
assert_eq!(cmd.exit, ExitCode::Ok);
|
|
295
|
+
let state = crate::state::persist::load_runtime_state(&ws).unwrap();
|
|
296
|
+
assert_eq!(
|
|
297
|
+
state["tasks"][0]["status"],
|
|
298
|
+
json!("done"),
|
|
299
|
+
"the task must persist status=done after repair"
|
|
300
|
+
);
|
|
301
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
302
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
struct TeamSocketGuard {
|
|
4
|
+
ws: std::path::PathBuf,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
impl Drop for TeamSocketGuard {
|
|
8
|
+
fn drop(&mut self) {
|
|
9
|
+
crate::tmux_backend::TmuxBackend::for_workspace(&self.ws).kill_server();
|
|
10
|
+
if let Some(uid) = current_uid() {
|
|
11
|
+
let socket = crate::tmux_backend::socket_name_for_workspace(&self.ws);
|
|
12
|
+
for root in ["/private/tmp", "/tmp"] {
|
|
13
|
+
let path = std::path::Path::new(root).join(format!("tmux-{uid}")).join(&socket);
|
|
14
|
+
let _ = std::fs::remove_file(path);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fn current_uid() -> Option<String> {
|
|
21
|
+
let output = std::process::Command::new("id").arg("-u").output().ok()?;
|
|
22
|
+
if !output.status.success() {
|
|
23
|
+
return None;
|
|
24
|
+
}
|
|
25
|
+
String::from_utf8(output.stdout).ok().map(|s| s.trim().to_string()).filter(|s| !s.is_empty())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// =========================================================================
|
|
29
|
+
// (A) CLI dispatch — run(argv, cwd) parses each subcommand and routes to its (already-tested)
|
|
30
|
+
// cmd_*, mapping the exit code. Today run() only handles codex/claude passthrough + help, else
|
|
31
|
+
// ExitCode::Error. These prove run DISPATCHES (observable per-command effect + exit). Golden
|
|
32
|
+
// cli/parser.py:main. OS-safe handlers only; spawn-bearing routes are #[ignore] real-machine.
|
|
33
|
+
// =========================================================================
|
|
34
|
+
|
|
35
|
+
#[test]
|
|
36
|
+
fn run_dispatches_status_to_handler_returns_ok() {
|
|
37
|
+
// run([status --workspace <seeded> --json]) -> cmd_status -> status_port::status (reads state)
|
|
38
|
+
// -> ExitCode::Ok. Today: Error (no dispatch). OS-safe (status only reads runtime state).
|
|
39
|
+
let ws = seed_status_workspace();
|
|
40
|
+
let argv = vec![
|
|
41
|
+
"status".to_string(),
|
|
42
|
+
"--workspace".to_string(),
|
|
43
|
+
ws.to_string_lossy().to_string(),
|
|
44
|
+
"--json".to_string(),
|
|
45
|
+
];
|
|
46
|
+
assert_eq!(
|
|
47
|
+
run(&argv, Path::new(".")),
|
|
48
|
+
ExitCode::Ok,
|
|
49
|
+
"run must dispatch `status` to cmd_status and return Ok (not today's hardcoded Error)"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#[test]
|
|
54
|
+
fn run_dispatches_send_to_handler_returns_ok() {
|
|
55
|
+
// run([send w1 hello --sender leader --workspace <seeded>]) -> cmd_send -> messaging::send_message
|
|
56
|
+
// -> ok (w1 is an in-team agent) -> ExitCode::Ok. w1 must be seeded: golden refuses non-team targets.
|
|
57
|
+
//
|
|
58
|
+
// OLD seed: flat `{"agents": {"w1": ...}}`.
|
|
59
|
+
// NEW seed (Bug 1/2 — team-in-team state scope, see
|
|
60
|
+
// tests/team_in_team_state_scope_red.rs): send_message projects the raw state
|
|
61
|
+
// through the active team_key, so the in-team agent now lives under
|
|
62
|
+
// `teams[<key>].agents`. Behavior under test (dispatch + ok exit) unchanged.
|
|
63
|
+
let ws = deleg_uniq_dir("run-send");
|
|
64
|
+
let _ = crate::message_store::MessageStore::open(&ws).unwrap();
|
|
65
|
+
crate::state::persist::save_runtime_state(
|
|
66
|
+
&ws,
|
|
67
|
+
&serde_json::json!({
|
|
68
|
+
"active_team_key": "current",
|
|
69
|
+
"teams": {"current": {"agents": {"w1": {"provider": "codex"}}}}
|
|
70
|
+
}),
|
|
71
|
+
)
|
|
72
|
+
.unwrap();
|
|
73
|
+
let argv = vec![
|
|
74
|
+
"send".to_string(),
|
|
75
|
+
"w1".to_string(),
|
|
76
|
+
"hello".to_string(),
|
|
77
|
+
"--sender".to_string(),
|
|
78
|
+
"leader".to_string(),
|
|
79
|
+
"--workspace".to_string(),
|
|
80
|
+
ws.to_string_lossy().to_string(),
|
|
81
|
+
];
|
|
82
|
+
assert_eq!(
|
|
83
|
+
run(&argv, Path::new(".")),
|
|
84
|
+
ExitCode::Ok,
|
|
85
|
+
"run must dispatch `send` to cmd_send and derive ExitCode::Ok from the delivery outcome"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[test]
|
|
90
|
+
fn run_unknown_subcommand_is_usage() {
|
|
91
|
+
// An unknown subcommand routes to argparse-style usage (exit 2), never a handler.
|
|
92
|
+
assert_eq!(
|
|
93
|
+
run(&["totally-not-a-subcommand".to_string()], Path::new(".")),
|
|
94
|
+
ExitCode::Usage,
|
|
95
|
+
"an unknown subcommand must map to ExitCode::Usage"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#[test]
|
|
100
|
+
#[ignore = "real-machine: `coordinator --once` dispatches to run_daemon which drives a real \
|
|
101
|
+
Coordinator (TmuxBackend); in-process needs a run_daemon_with_coordinator seam"]
|
|
102
|
+
fn run_dispatches_coordinator_once_writes_meta() {
|
|
103
|
+
// run([coordinator --workspace <ws> --once]) -> run_daemon(DaemonArgs{once:true}) -> writes the
|
|
104
|
+
// coordinator boot metadata + ExitCode::Ok. (real machine — drives a real backend.)
|
|
105
|
+
let ws = seed_status_workspace();
|
|
106
|
+
let argv = vec![
|
|
107
|
+
"coordinator".to_string(),
|
|
108
|
+
"--workspace".to_string(),
|
|
109
|
+
ws.to_string_lossy().to_string(),
|
|
110
|
+
"--once".to_string(),
|
|
111
|
+
];
|
|
112
|
+
let exit = run(&argv, Path::new("."));
|
|
113
|
+
let wp = crate::coordinator::WorkspacePath::new(ws);
|
|
114
|
+
assert!(
|
|
115
|
+
crate::coordinator::coordinator_meta_path(&wp).exists(),
|
|
116
|
+
"coordinator --once must dispatch to run_daemon and write the coordinator metadata"
|
|
117
|
+
);
|
|
118
|
+
assert_eq!(exit, ExitCode::Ok);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[test]
|
|
122
|
+
#[ignore = "real-machine: quick-start --yes spawns a real team (tmux) + coordinator daemon"]
|
|
123
|
+
fn run_dispatches_quick_start_compiles_spec() {
|
|
124
|
+
// The full quick-start path spawns workers + the coordinator; on a real machine we assert it
|
|
125
|
+
// dispatched to cmd_quick_start (team.spec.yaml compiled under the team dir) + ExitCode::Ok.
|
|
126
|
+
let dir = std::env::temp_dir().join(format!("ta-cli-qs-{}", std::process::id()));
|
|
127
|
+
std::fs::create_dir_all(dir.join("agents")).unwrap();
|
|
128
|
+
std::fs::write(dir.join("TEAM.md"), "---\nname: t\nobjective: o\nprovider: codex\n---\n\nteam.\n").unwrap();
|
|
129
|
+
std::fs::write(
|
|
130
|
+
dir.join("agents").join("implementer.md"),
|
|
131
|
+
"---\nname: implementer\nrole: Impl\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nImpl.\n",
|
|
132
|
+
)
|
|
133
|
+
.unwrap();
|
|
134
|
+
let argv = vec!["quick-start".to_string(), dir.to_string_lossy().to_string(), "--yes".to_string()];
|
|
135
|
+
let exit = run(&argv, Path::new("."));
|
|
136
|
+
assert!(dir.join("team.spec.yaml").exists(), "quick-start must compile the spec under the team dir");
|
|
137
|
+
assert_eq!(exit, ExitCode::Ok);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// =========================================================================
|
|
141
|
+
// (DELEGATION) cli *_port handlers must DELEGATE TO THE REAL SUBSYSTEMS (crate::lifecycle /
|
|
142
|
+
// crate::coordinator / crate::message_store), not the placeholder stubs in cli/mod.rs (which do
|
|
143
|
+
// `let _ = (...)` and fabricate canned Values). Each test asserts the REAL subsystem's effect
|
|
144
|
+
// (spec compiled, real error text, real coordinator_health, real DB counts) appears in the
|
|
145
|
+
// handler's CmdResult/Value — distinguishable from the placeholder's canned output. OS edge mocked;
|
|
146
|
+
// real tmux spawn / real daemon = #[ignore]. Golden: lifecycle/launch.rs+restart.rs, compiler.rs,
|
|
147
|
+
// coordinator/health.rs, message_store.
|
|
148
|
+
// =========================================================================
|
|
149
|
+
#[test]
|
|
150
|
+
fn cli_quick_start_invokes_real_lifecycle_compiles_spec() {
|
|
151
|
+
let team = deleg_team_dir_with_healthy_coordinator();
|
|
152
|
+
let ws = crate::model::paths::team_workspace(&team).unwrap();
|
|
153
|
+
let _guard = TeamSocketGuard { ws };
|
|
154
|
+
let args = QuickStartArgs {
|
|
155
|
+
agents_dir: team.clone(),
|
|
156
|
+
name: None,
|
|
157
|
+
team_id: None,
|
|
158
|
+
yes: true,
|
|
159
|
+
fresh: false,
|
|
160
|
+
json: true,
|
|
161
|
+
};
|
|
162
|
+
let _ = cmd_quick_start(&args); // real quick_start compiles the spec before any coordinator/launch step
|
|
163
|
+
assert!(
|
|
164
|
+
team.join("team.spec.yaml").exists(),
|
|
165
|
+
"cmd_quick_start must delegate to crate::lifecycle::quick_start, which compiles team.spec.yaml \
|
|
166
|
+
under the team dir; the placeholder never writes it"
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 2 [P0] — cmd_quick_start over an INVALID role doc surfaces the REAL compiler error (missing
|
|
171
|
+
// `provider`), proving delegation to crate::lifecycle (compile fails before any spawn). The
|
|
172
|
+
// placeholder returns a canned {ok:true} with no error -> RED. OS-safe (compile fails early).
|
|
173
|
+
#[test]
|
|
174
|
+
fn cli_quick_start_invalid_spec_surfaces_real_compile_error() {
|
|
175
|
+
let team = deleg_uniq_dir("qsbad");
|
|
176
|
+
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
177
|
+
std::fs::write(team.join("TEAM.md"), DELEG_TEAM_MD).unwrap();
|
|
178
|
+
std::fs::write(team.join("agents").join("broken.md"), DELEG_INVALID_ROLE).unwrap();
|
|
179
|
+
let args = QuickStartArgs {
|
|
180
|
+
agents_dir: team,
|
|
181
|
+
name: None,
|
|
182
|
+
team_id: None,
|
|
183
|
+
yes: false,
|
|
184
|
+
fresh: false,
|
|
185
|
+
json: true,
|
|
186
|
+
};
|
|
187
|
+
let text = outcome_text(cmd_quick_start(&args));
|
|
188
|
+
assert!(
|
|
189
|
+
text.contains("provider"),
|
|
190
|
+
"cmd_quick_start must surface the REAL compiler error for a role doc missing `provider` \
|
|
191
|
+
(proving delegation to crate::lifecycle, not the placeholder's canned ok:true); got {text}"
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 3 [P1] — cmd_restart on a no-spec workspace surfaces the REAL crate::lifecycle::restart error
|
|
196
|
+
// "missing spec for restart" (restart.rs:147). The placeholder returns {ok:true, allow_fresh, team}
|
|
197
|
+
// -> RED. OS-safe (restart errors on the missing spec before any spawn).
|
|
198
|
+
#[test]
|
|
199
|
+
fn cli_restart_missing_spec_surfaces_real_teamselect() {
|
|
200
|
+
let ws = deleg_uniq_dir("restart"); // no team.spec.yaml
|
|
201
|
+
let args = RestartArgs { workspace: ws, team: None, allow_fresh: false, json: true };
|
|
202
|
+
let text = outcome_text(cmd_restart(&args));
|
|
203
|
+
assert!(
|
|
204
|
+
text.contains("missing spec for restart"),
|
|
205
|
+
"cmd_restart must surface the REAL crate::lifecycle::restart 'missing spec for restart' error \
|
|
206
|
+
(not the placeholder's canned ok:true); got {text}"
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 4 [P1] — cmd_add_agent with a DUPLICATE agent id surfaces the REAL crate::lifecycle::add_agent
|
|
211
|
+
// "agent id already exists" error (launch.rs:252). The placeholder returns {ok:true} -> RED.
|
|
212
|
+
// OS-safe (the dup check fires before compile/spawn).
|
|
213
|
+
#[test]
|
|
214
|
+
fn cli_add_agent_duplicate_id_surfaces_real_error() {
|
|
215
|
+
let team = deleg_uniq_dir("addagent");
|
|
216
|
+
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
217
|
+
std::fs::write(team.join("TEAM.md"), DELEG_TEAM_MD).unwrap();
|
|
218
|
+
std::fs::write(team.join("agents").join("implementer.md"), DELEG_VALID_ROLE).unwrap(); // 'implementer' already exists
|
|
219
|
+
let dup_role = team.join("dup-role.md");
|
|
220
|
+
std::fs::write(&dup_role, DELEG_VALID_ROLE).unwrap(); // role file must exist
|
|
221
|
+
let args = AddAgentArgs {
|
|
222
|
+
agent: "implementer".to_string(),
|
|
223
|
+
workspace: team,
|
|
224
|
+
team: None,
|
|
225
|
+
role_file: dup_role.to_string_lossy().to_string(),
|
|
226
|
+
no_display: false,
|
|
227
|
+
json: true,
|
|
228
|
+
};
|
|
229
|
+
let text = outcome_text(cmd_add_agent(&args));
|
|
230
|
+
assert!(
|
|
231
|
+
text.contains("already exists"),
|
|
232
|
+
"cmd_add_agent must surface the REAL crate::lifecycle::add_agent 'agent id already exists' error \
|
|
233
|
+
(not the placeholder's canned ok:true); got {text}"
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 5 [P1] — cmd_status --json reflects the REAL coordinator_health + real DB message/result counts.
|
|
238
|
+
// The placeholder status_port::status hardcodes coordinator={status:stopped, schema_ok:false} and
|
|
239
|
+
// messages/results={count:0}. Seed: state.json + a real team.db (2 messages + 1 result) + a healthy
|
|
240
|
+
// coordinator (this pid + metadata) -> the real path must surface running-coordinator + non-zero
|
|
241
|
+
// counts. OS-safe (all reads/seeds, no spawn).
|
|
242
|
+
#[test]
|
|
243
|
+
fn cli_status_reflects_real_coordinator_health_and_db_counts() {
|
|
244
|
+
let ws = seed_status_workspace(); // writes .team/runtime/state.json
|
|
245
|
+
let store = crate::message_store::MessageStore::open(&ws).unwrap();
|
|
246
|
+
let _ = store.create_message(Some("t1"), "leader", "a1", "hi", None, true, None).unwrap();
|
|
247
|
+
let _ = store.create_message(Some("t1"), "leader", "a1", "again", None, true, None).unwrap();
|
|
248
|
+
let conn = crate::db::schema::open_db(store.db_path()).unwrap();
|
|
249
|
+
conn.execute(
|
|
250
|
+
"insert into results(result_id, owner_team_id, task_id, agent_id, envelope, status, created_at) \
|
|
251
|
+
values ('r1', null, 't1', 'a1', '{}', 'success', '2026-01-01T00:00:00Z')",
|
|
252
|
+
[],
|
|
253
|
+
)
|
|
254
|
+
.unwrap();
|
|
255
|
+
drop(store);
|
|
256
|
+
// a HEALTHY coordinator at the status workspace (this process's pid + matching metadata).
|
|
257
|
+
let wp = crate::coordinator::WorkspacePath::new(ws.clone());
|
|
258
|
+
let me = crate::coordinator::Pid::new(std::process::id());
|
|
259
|
+
crate::coordinator::write_coordinator_metadata(&wp, me, crate::coordinator::MetadataSource::Boot).unwrap();
|
|
260
|
+
std::fs::write(crate::coordinator::coordinator_pid_path(&wp), me.to_string()).unwrap();
|
|
261
|
+
|
|
262
|
+
let args = StatusArgs { agent: None, workspace: ws, detail: true, summary: false, json: true };
|
|
263
|
+
let r = cmd_status(&args).expect("cmd_status");
|
|
264
|
+
let value = match r.output {
|
|
265
|
+
CmdOutput::Json(v) => v,
|
|
266
|
+
other => panic!("status --json must yield a Json CmdOutput; got {other:?}"),
|
|
267
|
+
};
|
|
268
|
+
assert_ne!(
|
|
269
|
+
value["coordinator"],
|
|
270
|
+
json!({"status": "stopped", "schema_ok": false}),
|
|
271
|
+
"status `coordinator` must reflect the REAL coordinator_health (seeded running), not the \
|
|
272
|
+
placeholder's hardcoded {{status:stopped, schema_ok:false}}; got {}",
|
|
273
|
+
value["coordinator"]
|
|
274
|
+
);
|
|
275
|
+
assert_ne!(
|
|
276
|
+
value["messages"],
|
|
277
|
+
json!({"count": 0}),
|
|
278
|
+
"status `messages` must reflect the REAL DB rows (2 seeded), not the placeholder's {{count:0}}; got {}",
|
|
279
|
+
value["messages"]
|
|
280
|
+
);
|
|
281
|
+
assert_ne!(
|
|
282
|
+
value["results"],
|
|
283
|
+
json!({"count": 0}),
|
|
284
|
+
"status `results` must reflect the REAL DB result row, not the placeholder's {{count:0}}; got {}",
|
|
285
|
+
value["results"]
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 6 [P1] — cmd_shutdown delegates to the REAL crate::coordinator::stop_coordinator (+ tmux
|
|
290
|
+
// kill-session). The placeholder lifecycle_port::shutdown returns only {ok:true, keep_logs, team}.
|
|
291
|
+
// #[ignore] real-machine: the real shutdown spawns `tmux kill-session` (and SIGTERMs the coordinator);
|
|
292
|
+
// on a no-coordinator workspace the real stop_coordinator outcome (stopped/missing) must surface.
|
|
293
|
+
#[test]
|
|
294
|
+
#[ignore = "real-machine: cmd_shutdown runs the real tmux kill-session + SIGTERMs the coordinator daemon"]
|
|
295
|
+
fn cli_shutdown_invokes_real_stop_coordinator() {
|
|
296
|
+
let ws = seed_status_workspace();
|
|
297
|
+
let args = ShutdownArgs { workspace: ws, team: None, keep_logs: true, json: true };
|
|
298
|
+
let text = outcome_text(cmd_shutdown(&args));
|
|
299
|
+
let lower = text.to_lowercase();
|
|
300
|
+
assert!(
|
|
301
|
+
lower.contains("coordinator") || lower.contains("stopped") || lower.contains("missing"),
|
|
302
|
+
"cmd_shutdown must delegate to the real coordinator::stop_coordinator and surface its outcome \
|
|
303
|
+
(no coordinator -> stopped/missing); the placeholder returns only {{ok,keep_logs,team}}; got {text}"
|
|
304
|
+
);
|
|
305
|
+
}
|