@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,385 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
// =========================================================================
|
|
4
|
+
// status_compact_flag (commands.py:99): compact = !detail — CLI 独占不变量
|
|
5
|
+
// gate: 'detail=false => compact=true mapping, the one byte-level invariant CLI owns'.
|
|
6
|
+
// =========================================================================
|
|
7
|
+
|
|
8
|
+
#[test]
|
|
9
|
+
fn status_compact_flag_default_is_compact() {
|
|
10
|
+
// golden: cmd_status without --detail -> runtime.status(compact=not(False)) == compact=True.
|
|
11
|
+
assert!(status_compact_flag(false), "detail=false MUST map to compact=true (commands.py:99)");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#[test]
|
|
15
|
+
fn status_compact_flag_detail_is_full() {
|
|
16
|
+
// golden: cmd_status --detail -> runtime.status(compact=not(True)) == compact=False.
|
|
17
|
+
assert!(!status_compact_flag(true), "detail=true MUST map to compact=false (full projection)");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// =========================================================================
|
|
21
|
+
// status_port::status — REAL caller against SEEDED fixture (gate: 'zero callers').
|
|
22
|
+
// Asserts the --json projection shape that the compact-vs-detail wiring selects.
|
|
23
|
+
// RED: status_port::status is unimplemented!() so the call panics until ported.
|
|
24
|
+
// =========================================================================
|
|
25
|
+
|
|
26
|
+
#[test]
|
|
27
|
+
fn status_port_status_compact_json_shape_against_seeded_fixture() {
|
|
28
|
+
// cmd_status json branch (detail=false) delegates status_port::status(compact=true).
|
|
29
|
+
// compact_status (status/compact.py:8-37) projects to a STABLE key set; assert the
|
|
30
|
+
// load-bearing keys survive and `last_events` is bounded (compact truncates events).
|
|
31
|
+
let ws = seed_status_workspace();
|
|
32
|
+
let v = status_port::status(&ws, /*compact=*/ true, /*detail=*/ false)
|
|
33
|
+
.expect("seeded fixture status should project a value");
|
|
34
|
+
let obj = v.as_object().expect("--json status is a dict");
|
|
35
|
+
// compact_status's exact top-level key set (compact.py:9-37):
|
|
36
|
+
for key in [
|
|
37
|
+
"team",
|
|
38
|
+
"session_name",
|
|
39
|
+
"tmux_session_present",
|
|
40
|
+
"leader_receiver",
|
|
41
|
+
"agents",
|
|
42
|
+
"agent_health",
|
|
43
|
+
"tasks",
|
|
44
|
+
"messages",
|
|
45
|
+
"queued_messages",
|
|
46
|
+
"results",
|
|
47
|
+
"latest_results",
|
|
48
|
+
"coordinator",
|
|
49
|
+
"last_events",
|
|
50
|
+
] {
|
|
51
|
+
assert!(obj.contains_key(key), "compact status missing key `{key}`");
|
|
52
|
+
}
|
|
53
|
+
// seeded agent surfaces through the projection.
|
|
54
|
+
assert!(
|
|
55
|
+
obj["agents"].as_object().unwrap().contains_key("a1"),
|
|
56
|
+
"seeded agent a1 must appear in compact agents projection"
|
|
57
|
+
);
|
|
58
|
+
// compact bounds: queued_messages[:8] and latest_results[:5] -> arrays.
|
|
59
|
+
assert!(obj["queued_messages"].is_array());
|
|
60
|
+
assert!(obj["latest_results"].as_array().unwrap().len() <= 5);
|
|
61
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[test]
|
|
65
|
+
fn status_port_status_detail_full_keeps_uncompacted_events() {
|
|
66
|
+
// cmd_status --json --detail -> status_port::status(compact=false): the FULL dict
|
|
67
|
+
// (queries.py:65-79) is returned WITHOUT compact_status truncation. Distinguishing
|
|
68
|
+
// invariant: full result preserves `tasks` rows verbatim (not compact_task-projected),
|
|
69
|
+
// so the seeded task's full row (incl. fields compact would drop) survives.
|
|
70
|
+
let ws = seed_status_workspace();
|
|
71
|
+
let full = status_port::status(&ws, /*compact=*/ false, /*detail=*/ true)
|
|
72
|
+
.expect("seeded fixture full status should project a value");
|
|
73
|
+
let compact = status_port::status(&ws, /*compact=*/ true, /*detail=*/ false)
|
|
74
|
+
.expect("seeded fixture compact status should project a value");
|
|
75
|
+
// The CLI-owned invariant: detail=true => compact=false; the two projections MUST
|
|
76
|
+
// differ in shape (full carries `messages`/`results` count maps + verbatim tasks).
|
|
77
|
+
assert_ne!(full, compact, "detail (full) and default (compact) projections must differ");
|
|
78
|
+
let full_obj = full.as_object().expect("full status is a dict");
|
|
79
|
+
assert!(full_obj.contains_key("last_events"), "full status keeps last_events");
|
|
80
|
+
assert_eq!(full_obj["agents"].as_object().unwrap().len(), 1);
|
|
81
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// =========================================================================
|
|
85
|
+
// send_options_from_args (commands.py:170-177): SendArgs->SendOptions 旗标取反
|
|
86
|
+
// gate: 'no_ack:true => requires_ack:false and no_wait:true => wait_visible:false';
|
|
87
|
+
// watch_result flag maps into SendOptions.
|
|
88
|
+
// RED: send_options_from_args is unimplemented!() until ported.
|
|
89
|
+
// =========================================================================
|
|
90
|
+
|
|
91
|
+
fn send_args_fixture() -> SendArgs {
|
|
92
|
+
SendArgs {
|
|
93
|
+
target: Some("alice".into()),
|
|
94
|
+
message: vec!["hello".into(), "world".into(), "foo".into()],
|
|
95
|
+
targets: None,
|
|
96
|
+
workspace: PathBuf::from("."),
|
|
97
|
+
team: Some("teamA".into()),
|
|
98
|
+
task: Some("t-1".into()),
|
|
99
|
+
sender: "leader".into(),
|
|
100
|
+
no_ack: true,
|
|
101
|
+
no_wait: true,
|
|
102
|
+
watch_result: true,
|
|
103
|
+
timeout: 12.5,
|
|
104
|
+
confirm_human: false,
|
|
105
|
+
json: false,
|
|
106
|
+
message_id: None,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#[test]
|
|
111
|
+
fn send_options_negates_no_ack_and_no_wait_and_carries_watch() {
|
|
112
|
+
// golden (commands.py:172,174,176): requires_ack=not no_ack; wait_visible=not no_wait;
|
|
113
|
+
// watch_result passthrough. With no_ack=true,no_wait=true,watch_result=true:
|
|
114
|
+
// requires_ack=false, wait_visible=false, watch_result=true.
|
|
115
|
+
let opts = send_options_from_args(&send_args_fixture());
|
|
116
|
+
assert!(!opts.requires_ack, "no_ack:true MUST map to requires_ack:false (off-by-inversion guard)");
|
|
117
|
+
assert!(!opts.wait_visible, "no_wait:true MUST map to wait_visible:false");
|
|
118
|
+
assert!(opts.watch_result, "watch_result flag MUST pass through into SendOptions");
|
|
119
|
+
assert!(!opts.confirm_human);
|
|
120
|
+
assert_eq!(opts.sender, "leader");
|
|
121
|
+
assert_eq!(opts.timeout, 12.5);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#[test]
|
|
125
|
+
fn send_options_default_flags_are_acked_and_waited() {
|
|
126
|
+
// golden: no_ack=false,no_wait=false,watch_result=false ->
|
|
127
|
+
// requires_ack=true, wait_visible=true, watch_result=false (Python defaults inverted back).
|
|
128
|
+
let args = SendArgs {
|
|
129
|
+
no_ack: false,
|
|
130
|
+
no_wait: false,
|
|
131
|
+
watch_result: false,
|
|
132
|
+
..send_args_fixture()
|
|
133
|
+
};
|
|
134
|
+
let opts = send_options_from_args(&args);
|
|
135
|
+
assert!(opts.requires_ack, "no_ack:false MUST map to requires_ack:true");
|
|
136
|
+
assert!(opts.wait_visible, "no_wait:false MUST map to wait_visible:true");
|
|
137
|
+
assert!(!opts.watch_result);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// =========================================================================
|
|
141
|
+
// cmd_send — REAL caller (gate: 'cmd_send has NO test beyond send_target').
|
|
142
|
+
// Asserts (1) message Vec joined by single space surfaces to send_message,
|
|
143
|
+
// (2) the registered-watcher notice ({status:'registered',...} -> result['watch'],
|
|
144
|
+
// send.py:326-337) survives into CmdResult Json output,
|
|
145
|
+
// (3) DeliveryOutcome->exit-code derivation (ok=true -> ExitCode::Ok).
|
|
146
|
+
// RED: cmd_send is unimplemented!() so it panics until ported.
|
|
147
|
+
// =========================================================================
|
|
148
|
+
|
|
149
|
+
#[test]
|
|
150
|
+
fn cmd_send_joins_message_with_single_space() {
|
|
151
|
+
// golden (commands.py:169): " ".join(["hello","world","foo"]) == "hello world foo".
|
|
152
|
+
// Drive cmd_send; the joined content must reach send_message (RED until ported).
|
|
153
|
+
let r = cmd_send(&send_args_fixture()).expect("cmd_send returns CmdResult");
|
|
154
|
+
// The delegate's DeliveryOutcome -> Json must carry an `ok` key feeding exit-code.
|
|
155
|
+
match r.output {
|
|
156
|
+
CmdOutput::Json(ref v) => {
|
|
157
|
+
assert!(v.get("ok").is_some(), "send result Json must carry `ok`");
|
|
158
|
+
}
|
|
159
|
+
other => panic!("cmd_send must emit Json DeliveryOutcome, got {other:?}"),
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
fn cmd_send_watch_result_surfaces_registered_notice() {
|
|
165
|
+
// gate CRITICAL: --watch-result -> SendOptions.watch_result=true -> send_message attaches
|
|
166
|
+
// result['watch']={status:'registered', watcher_id, task_id, agent_id, notice}
|
|
167
|
+
// (send.py:322-337). That dict MUST survive verbatim into CmdResult Json output.
|
|
168
|
+
let r = cmd_send(&send_args_fixture()).expect("cmd_send returns CmdResult");
|
|
169
|
+
let v = match r.output {
|
|
170
|
+
CmdOutput::Json(v) => v,
|
|
171
|
+
other => panic!("expected Json, got {other:?}"),
|
|
172
|
+
};
|
|
173
|
+
let watch = v
|
|
174
|
+
.get("watch")
|
|
175
|
+
.expect("watch_result:true MUST attach result['watch'] (send.py:326)");
|
|
176
|
+
assert_eq!(
|
|
177
|
+
watch.get("status").and_then(|s| s.as_str()),
|
|
178
|
+
Some("registered"),
|
|
179
|
+
"registered-watcher notice status must be exactly 'registered'"
|
|
180
|
+
);
|
|
181
|
+
assert!(watch.get("watcher_id").is_some(), "watch notice carries watcher_id");
|
|
182
|
+
assert_eq!(
|
|
183
|
+
watch.get("agent_id").and_then(|s| s.as_str()),
|
|
184
|
+
Some("alice"),
|
|
185
|
+
"watch notice agent_id == the send target"
|
|
186
|
+
);
|
|
187
|
+
// non-queued notice golden bytes (send.py:335):
|
|
188
|
+
assert_eq!(
|
|
189
|
+
watch.get("notice").and_then(|s| s.as_str()),
|
|
190
|
+
Some("Team Agent will collect the result and notify the leader when this task reports completion.")
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#[test]
|
|
195
|
+
fn cmd_send_failed_outcome_yields_error_exit() {
|
|
196
|
+
// DeliveryOutcome ok=false (e.g. refused) -> from_json -> ExitCode::Error (parser.py:507).
|
|
197
|
+
// A failed send to a target must propagate non-zero exit reporting through CmdResult.
|
|
198
|
+
let args = SendArgs {
|
|
199
|
+
target: Some("nonexistent".into()),
|
|
200
|
+
no_ack: false,
|
|
201
|
+
no_wait: false,
|
|
202
|
+
watch_result: false,
|
|
203
|
+
..send_args_fixture()
|
|
204
|
+
};
|
|
205
|
+
let r = cmd_send(&args).expect("cmd_send returns CmdResult even on delivery failure");
|
|
206
|
+
if let CmdOutput::Json(ref v) = r.output {
|
|
207
|
+
if v.get("ok").and_then(|b| b.as_bool()) == Some(false) {
|
|
208
|
+
assert_eq!(
|
|
209
|
+
r.exit,
|
|
210
|
+
ExitCode::Error,
|
|
211
|
+
"ok:false DeliveryOutcome MUST derive ExitCode::Error (non-zero exit)"
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
219
|
+
// coordinator.ok — non-compact status carries the FULL coordinator_health (incl. `ok`); compact
|
|
220
|
+
// strips to {status,pid,metadata_ok,schema_ok} (golden queries.py:77 + compact.py:35; ok =
|
|
221
|
+
// running∧metadata_ok∧schema_ok, coordinator/lifecycle.py:26-46). Deterministic missing-coordinator
|
|
222
|
+
// fixture (no pid → ok:false, status:"missing"). Dual-assertion catches both branch directions.
|
|
223
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
224
|
+
#[test]
|
|
225
|
+
fn status_noncompact_coordinator_includes_ok() {
|
|
226
|
+
let ws = seed_status_workspace();
|
|
227
|
+
let v = status_port::status(&ws, /*compact=*/ false, /*detail=*/ true).expect("status");
|
|
228
|
+
let coord = v.get("coordinator").and_then(|c| c.as_object()).expect("coordinator object");
|
|
229
|
+
assert!(
|
|
230
|
+
coord.contains_key("ok"),
|
|
231
|
+
"non-compact coordinator MUST carry `ok` (golden queries.py:77 full coordinator_health); got keys {:?}",
|
|
232
|
+
coord.keys().collect::<Vec<_>>()
|
|
233
|
+
);
|
|
234
|
+
for key in ["ok", "status", "pid", "metadata", "metadata_ok", "schema_ok"] {
|
|
235
|
+
assert!(coord.contains_key(key), "non-compact coordinator missing `{key}`");
|
|
236
|
+
}
|
|
237
|
+
assert_eq!(
|
|
238
|
+
coord.get("ok").and_then(|v| v.as_bool()),
|
|
239
|
+
Some(false),
|
|
240
|
+
"missing-coordinator fixture → ok:false (running∧metadata_ok∧schema_ok)"
|
|
241
|
+
);
|
|
242
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
#[test]
|
|
246
|
+
fn status_compact_coordinator_omits_ok() {
|
|
247
|
+
let ws = seed_status_workspace();
|
|
248
|
+
let v = status_port::status(&ws, /*compact=*/ true, /*detail=*/ false).expect("status");
|
|
249
|
+
let coord = v.get("coordinator").and_then(|c| c.as_object()).expect("coordinator object");
|
|
250
|
+
let keys: std::collections::BTreeSet<&str> = coord.keys().map(String::as_str).collect();
|
|
251
|
+
let expected: std::collections::BTreeSet<&str> =
|
|
252
|
+
["metadata_ok", "pid", "schema_ok", "status"].into_iter().collect();
|
|
253
|
+
assert_eq!(
|
|
254
|
+
keys, expected,
|
|
255
|
+
"compact coordinator key set must be EXACTLY {{metadata_ok,pid,schema_ok,status}} (no ok/metadata)"
|
|
256
|
+
);
|
|
257
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
261
|
+
// P0 (b) — CLI `send --task <unknown>` (route_task_id=true default = routing) MUST surface the
|
|
262
|
+
// golden error envelope {ok:false, error:"unknown task id:<id>", action, log} + exit 1 — NOT a
|
|
263
|
+
// silent 0-byte swallow (rt-host-b), and NO "validation:" prefix. Lock.
|
|
264
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
265
|
+
// OLD seed: flat `{session_name, agents:{w1}, tasks:[]}`.
|
|
266
|
+
// NEW seed (Bug 1/2 — team-in-team state scope, see tests/team_in_team_state_scope_red.rs):
|
|
267
|
+
// cmd_send projects state through the active team_key before reaching the unknown-task
|
|
268
|
+
// gate, so agents/tasks must live under `teams[<key>].*` to be visible at projection
|
|
269
|
+
// time. The "unknown task -> golden envelope" behavior being asserted is unchanged.
|
|
270
|
+
#[test]
|
|
271
|
+
fn cmd_send_unknown_task_surfaces_golden_error_envelope_not_silent() {
|
|
272
|
+
let ws = std::env::temp_dir().join(format!(
|
|
273
|
+
"ta-cli-sendunk-{}-{}",
|
|
274
|
+
std::process::id(),
|
|
275
|
+
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
|
|
276
|
+
));
|
|
277
|
+
std::fs::create_dir_all(ws.join(".team").join("runtime")).unwrap();
|
|
278
|
+
std::fs::write(
|
|
279
|
+
ws.join(".team").join("runtime").join("state.json"),
|
|
280
|
+
serde_json::to_vec_pretty(&json!({
|
|
281
|
+
"session_name": "team-x",
|
|
282
|
+
"active_team_key": "current",
|
|
283
|
+
"teams": {"current": {
|
|
284
|
+
"session_name": "team-x",
|
|
285
|
+
"agents": { "w1": { "status": "running" } },
|
|
286
|
+
"tasks": []
|
|
287
|
+
}}
|
|
288
|
+
})).unwrap(),
|
|
289
|
+
).unwrap();
|
|
290
|
+
let _ = crate::message_store::MessageStore::open(&ws);
|
|
291
|
+
let args = SendArgs {
|
|
292
|
+
target: Some("w1".into()),
|
|
293
|
+
targets: None,
|
|
294
|
+
task: Some("t-unknown".into()),
|
|
295
|
+
message: vec!["go".into()],
|
|
296
|
+
workspace: ws.clone(),
|
|
297
|
+
team: None,
|
|
298
|
+
watch_result: false,
|
|
299
|
+
json: true,
|
|
300
|
+
..send_args_fixture()
|
|
301
|
+
};
|
|
302
|
+
// route_task_id defaults true (CLI routing path) → the error MUST surface, not silently swallow.
|
|
303
|
+
let err = cmd_send(&args).expect_err(
|
|
304
|
+
"CLI send --task <unknown> must surface an error (route_task_id=true routing), not a silent 0-byte send"
|
|
305
|
+
);
|
|
306
|
+
let payload = err.to_payload(std::path::Path::new("/tmp/ta-cli-err.log"), "send");
|
|
307
|
+
assert!(!payload.ok, "error envelope ok must be false");
|
|
308
|
+
assert_eq!(
|
|
309
|
+
payload.error, "unknown task id: t-unknown",
|
|
310
|
+
"CLI error field == golden bare message (golden runtime.py:1032 str(exc)); NO 'validation:' prefix"
|
|
311
|
+
);
|
|
312
|
+
assert_eq!(payload.action, "run `team-agent doctor` or inspect the log path shown here");
|
|
313
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// P0 (b') — the SWALLOW guard: `run()` (the CLI process entry) MUST RENDER the send error, not
|
|
317
|
+
// discard Err(CliError) via unwrap_or (advisor %7 root cause). Proxy: emit_cli_error WRITES a
|
|
318
|
+
// `.team/logs/cli-error-*.log` (and prints the compact envelope) — if run() swallowed, neither
|
|
319
|
+
// happens. So a cli-error log containing the BARE "unknown task id: <id>" (no "validation:" prefix)
|
|
320
|
+
// + ExitCode::Error proves run() rendered. Drives the real argv→(exit,render) path.
|
|
321
|
+
// OLD/NEW: same Bug 1/2 seed sync as cmd_send_unknown_task_*; the render-vs-swallow
|
|
322
|
+
// behavior under test is unchanged.
|
|
323
|
+
#[test]
|
|
324
|
+
fn run_send_unknown_task_renders_error_not_silent_swallow() {
|
|
325
|
+
let ws = std::env::temp_dir().join(format!(
|
|
326
|
+
"ta-run-sendunk-{}-{}",
|
|
327
|
+
std::process::id(),
|
|
328
|
+
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
|
|
329
|
+
));
|
|
330
|
+
std::fs::create_dir_all(ws.join(".team").join("runtime")).unwrap();
|
|
331
|
+
std::fs::write(
|
|
332
|
+
ws.join(".team").join("runtime").join("state.json"),
|
|
333
|
+
serde_json::to_vec_pretty(&json!({
|
|
334
|
+
"session_name": "team-x",
|
|
335
|
+
"active_team_key": "current",
|
|
336
|
+
"teams": {"current": {
|
|
337
|
+
"session_name": "team-x",
|
|
338
|
+
"agents": { "w1": { "status": "running" } },
|
|
339
|
+
"tasks": []
|
|
340
|
+
}}
|
|
341
|
+
})).unwrap(),
|
|
342
|
+
).unwrap();
|
|
343
|
+
let _ = crate::message_store::MessageStore::open(&ws);
|
|
344
|
+
let argv: Vec<String> = ["send", "w1", "--task", "t-unknown", "go", "--json"]
|
|
345
|
+
.iter().map(ToString::to_string).collect();
|
|
346
|
+
let code = run(&argv, &ws);
|
|
347
|
+
assert_eq!(code, ExitCode::Error, "run(send --task <unknown>) must exit Error, not Ok");
|
|
348
|
+
// run() must have RENDERED (emit_cli_error wrote the cli-error log); a swallow leaves none.
|
|
349
|
+
let logs_dir = ws.join(".team").join("logs");
|
|
350
|
+
let mut found = String::new();
|
|
351
|
+
if let Ok(entries) = std::fs::read_dir(&logs_dir) {
|
|
352
|
+
for entry in entries.flatten() {
|
|
353
|
+
let name = entry.file_name().to_string_lossy().to_string();
|
|
354
|
+
if name.starts_with("cli-error-") {
|
|
355
|
+
found = std::fs::read_to_string(entry.path()).unwrap_or_default();
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
assert!(
|
|
361
|
+
found.contains("unknown task id: t-unknown"),
|
|
362
|
+
"run() must RENDER the send error (cli-error log written with the bare message) — a silent \
|
|
363
|
+
swallow (unwrap_or discards Err) leaves no log. got log body: {found:?}"
|
|
364
|
+
);
|
|
365
|
+
assert!(
|
|
366
|
+
!found.contains("validation:"),
|
|
367
|
+
"rendered error must be the bare golden message, NO 'validation:' prefix; got {found:?}"
|
|
368
|
+
);
|
|
369
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// R8 D6 (c-lite offline byte-lock): the CLI requeued_exhausted_watchers return projection, extracted
|
|
373
|
+
// into a pure helper, must project the golden event's watcher_ids STRING list (leader/__init__.py:56) —
|
|
374
|
+
// NOT the Rust `requeued` Vec<WatcherNotice> objects.
|
|
375
|
+
#[test]
|
|
376
|
+
fn r8_project_requeued_exhausted_watchers_golden_string_list() {
|
|
377
|
+
// golden attach event shape (what D4 emits): {watcher_ids:[str], count, trigger}.
|
|
378
|
+
let golden_event = serde_json::json!({"watcher_ids": ["w1", "w2"], "count": 2, "trigger": "attach_leader"});
|
|
379
|
+
let projected = crate::cli::leader_port::project_requeued_exhausted_watchers(&golden_event);
|
|
380
|
+
let list = projected.as_array().expect("requeued_exhausted_watchers must be a JSON array");
|
|
381
|
+
let ids: Vec<&str> = list.iter().filter_map(|v| v.as_str()).collect();
|
|
382
|
+
assert_eq!(ids, vec!["w1", "w2"],
|
|
383
|
+
"D6: CLI requeued_exhausted_watchers must project the golden watcher_ids STRING list \
|
|
384
|
+
(leader/__init__.py:56), not the `requeued` Vec<WatcherNotice> objects; got {projected:?}");
|
|
385
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
fn profile_argv(items: &[&str]) -> Vec<String> {
|
|
4
|
+
items.iter().map(|s| (*s).to_string()).collect()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
fn profiles_dir(ws: &std::path::Path) -> std::path::PathBuf {
|
|
8
|
+
ws.join(".team").join("current").join("profiles")
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
fn seed_official_api_profile(ws: &std::path::Path) {
|
|
12
|
+
let dir = profiles_dir(ws);
|
|
13
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
14
|
+
std::fs::write(dir.join("AGENTS.md"), "# Team Agent Profile Secret Boundary\n").unwrap();
|
|
15
|
+
std::fs::write(dir.join("CLAUDE.md"), "# Team Agent Profile Secret Boundary\n").unwrap();
|
|
16
|
+
std::fs::write(
|
|
17
|
+
dir.join("api_prof.env"),
|
|
18
|
+
"AUTH_MODE=official_api\nPROFILE_NAME=api_prof\nAPI_KEY=sk-test-secret\nMODEL=gpt-test\n",
|
|
19
|
+
)
|
|
20
|
+
.unwrap();
|
|
21
|
+
std::fs::write(
|
|
22
|
+
dir.join("api_prof.example.env"),
|
|
23
|
+
"AUTH_MODE=official_api\nPROFILE_NAME=api_prof\nAPI_KEY=\nMODEL=\n",
|
|
24
|
+
)
|
|
25
|
+
.unwrap();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Golden source:
|
|
29
|
+
// - cli/parser.py:131-150 registers `profile {init,doctor,show}`:
|
|
30
|
+
// * init NAME --workspace . --team TEAM --auth-mode choices(sorted AUTH_MODES) --json
|
|
31
|
+
// * doctor NAME --workspace . --team TEAM --json
|
|
32
|
+
// * show NAME --workspace . --team TEAM --json
|
|
33
|
+
// - cli/commands.py:47-67 resolves scope then delegates to profiles.init_profile/
|
|
34
|
+
// doctor_profile/show_profile.
|
|
35
|
+
// - profiles/core.py:51-93 init_profile writes `.team/current/profiles/{name}.env`,
|
|
36
|
+
// `{name}.example.env`, AGENTS.md and CLAUDE.md boundary files, chmods real .env to 0600,
|
|
37
|
+
// and returns keys `{ok,profile,auth_mode,path,template_path,created_profile,
|
|
38
|
+
// created_template,secret_written,safe_inspection_command,
|
|
39
|
+
// raw_file_read_allowed_for_agents,instruction}`.
|
|
40
|
+
// - cli/helpers.py:12-23 emits success JSON as `json.dumps(indent=2, sort_keys=True)`;
|
|
41
|
+
// human dict output preserves return insertion order.
|
|
42
|
+
//
|
|
43
|
+
// Golden probe:
|
|
44
|
+
// PYTHONPATH=/Users/alauda/Documents/code/team-agent-public/src \
|
|
45
|
+
// python3 /tmp/probe_profile_cli.py
|
|
46
|
+
// profile init codex_sub --auth-mode subscription --json rc=0 and creates:
|
|
47
|
+
// codex_sub.env = "AUTH_MODE=subscription\nPROFILE_NAME=codex_sub\n" (mode 0600),
|
|
48
|
+
// codex_sub.example.env with same body, plus AGENTS.md/CLAUDE.md secret boundary files.
|
|
49
|
+
#[test]
|
|
50
|
+
fn profile_init_routes_and_creates_secret_boundary_files() {
|
|
51
|
+
let ws = tmp_workspace();
|
|
52
|
+
let code = run(
|
|
53
|
+
&profile_argv(&[
|
|
54
|
+
"profile",
|
|
55
|
+
"init",
|
|
56
|
+
"codex_sub",
|
|
57
|
+
"--workspace",
|
|
58
|
+
".",
|
|
59
|
+
"--auth-mode",
|
|
60
|
+
"subscription",
|
|
61
|
+
"--json",
|
|
62
|
+
]),
|
|
63
|
+
&ws,
|
|
64
|
+
);
|
|
65
|
+
assert_eq!(code, ExitCode::Ok, "`profile init ... --json` must route and exit 0");
|
|
66
|
+
|
|
67
|
+
let dir = profiles_dir(&ws);
|
|
68
|
+
assert!(
|
|
69
|
+
dir.join("AGENTS.md").exists(),
|
|
70
|
+
"profile init must create AGENTS.md secret boundary"
|
|
71
|
+
);
|
|
72
|
+
assert!(
|
|
73
|
+
dir.join("CLAUDE.md").exists(),
|
|
74
|
+
"profile init must create CLAUDE.md secret boundary"
|
|
75
|
+
);
|
|
76
|
+
assert_eq!(
|
|
77
|
+
std::fs::read_to_string(dir.join("codex_sub.env")).unwrap(),
|
|
78
|
+
"AUTH_MODE=subscription\nPROFILE_NAME=codex_sub\n",
|
|
79
|
+
"subscription profile template body must match golden"
|
|
80
|
+
);
|
|
81
|
+
assert_eq!(
|
|
82
|
+
std::fs::read_to_string(dir.join("codex_sub.example.env")).unwrap(),
|
|
83
|
+
"AUTH_MODE=subscription\nPROFILE_NAME=codex_sub\n",
|
|
84
|
+
"subscription example template body must match golden"
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
let second = run(
|
|
88
|
+
&profile_argv(&[
|
|
89
|
+
"profile",
|
|
90
|
+
"init",
|
|
91
|
+
"codex_sub",
|
|
92
|
+
"--workspace",
|
|
93
|
+
".",
|
|
94
|
+
"--auth-mode",
|
|
95
|
+
"subscription",
|
|
96
|
+
"--json",
|
|
97
|
+
]),
|
|
98
|
+
&ws,
|
|
99
|
+
);
|
|
100
|
+
assert_eq!(second, ExitCode::Ok, "profile init is idempotent and still exits 0");
|
|
101
|
+
assert_eq!(
|
|
102
|
+
std::fs::read_to_string(dir.join("codex_sub.env")).unwrap(),
|
|
103
|
+
"AUTH_MODE=subscription\nPROFILE_NAME=codex_sub\n",
|
|
104
|
+
"idempotent init must not rewrite existing profile bytes"
|
|
105
|
+
);
|
|
106
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Golden source:
|
|
110
|
+
// - profiles/core.py:95-119 doctor_profile returns ok=true for existing profiles and
|
|
111
|
+
// ok=false for missing profiles; parser.py:506-508 maps result.ok false to exit 1.
|
|
112
|
+
// - Existing JSON sorted keys include auth_mode, credential_present, keys_present,
|
|
113
|
+
// ok, path, profile, raw_file_read_allowed_for_agents, redaction_engine,
|
|
114
|
+
// safe_for_agent_context, safe_inspection_command, secret_keys_present,
|
|
115
|
+
// secret_values_printed, suggestion, template_path.
|
|
116
|
+
// - Missing doctor JSON exits 1 and carries suggestion
|
|
117
|
+
// "Run team-agent profile init missing --auth-mode subscription.".
|
|
118
|
+
#[test]
|
|
119
|
+
fn profile_doctor_routes_existing_ok_and_missing_error() {
|
|
120
|
+
let ws = tmp_workspace();
|
|
121
|
+
seed_official_api_profile(&ws);
|
|
122
|
+
|
|
123
|
+
let existing = run(
|
|
124
|
+
&profile_argv(&[
|
|
125
|
+
"profile",
|
|
126
|
+
"doctor",
|
|
127
|
+
"api_prof",
|
|
128
|
+
"--workspace",
|
|
129
|
+
".",
|
|
130
|
+
"--json",
|
|
131
|
+
]),
|
|
132
|
+
&ws,
|
|
133
|
+
);
|
|
134
|
+
assert_eq!(existing, ExitCode::Ok, "profile doctor existing profile must exit 0");
|
|
135
|
+
|
|
136
|
+
let missing = run(
|
|
137
|
+
&profile_argv(&[
|
|
138
|
+
"profile",
|
|
139
|
+
"doctor",
|
|
140
|
+
"missing",
|
|
141
|
+
"--workspace",
|
|
142
|
+
".",
|
|
143
|
+
"--json",
|
|
144
|
+
]),
|
|
145
|
+
&ws,
|
|
146
|
+
);
|
|
147
|
+
assert_eq!(missing, ExitCode::Error, "profile doctor missing profile must exit 1");
|
|
148
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Golden source:
|
|
152
|
+
// - profiles/core.py:121-146 show_profile returns redacted values sorted by key.
|
|
153
|
+
// - profiles/helpers.py:40-58 marks API_KEY secret values as `{present:true,redacted:true}`
|
|
154
|
+
// and never includes the raw secret; non-secret AUTH_MODE/MODEL/PROFILE_NAME carry `value`.
|
|
155
|
+
// - Human output preserves returned dict insertion order:
|
|
156
|
+
// ok, profile, credential_present, auth_mode, values, keys_present, secret_keys_present,
|
|
157
|
+
// missing_common, safe_for_agent_context, secret_values_printed,
|
|
158
|
+
// raw_file_read_allowed_for_agents, instruction.
|
|
159
|
+
#[test]
|
|
160
|
+
fn profile_show_routes_and_preserves_redacted_secret_contract() {
|
|
161
|
+
let ws = tmp_workspace();
|
|
162
|
+
seed_official_api_profile(&ws);
|
|
163
|
+
|
|
164
|
+
let code = run(
|
|
165
|
+
&profile_argv(&["profile", "show", "api_prof", "--workspace", ".", "--json"]),
|
|
166
|
+
&ws,
|
|
167
|
+
);
|
|
168
|
+
assert_eq!(code, ExitCode::Ok, "profile show existing profile must exit 0");
|
|
169
|
+
|
|
170
|
+
let missing = run(
|
|
171
|
+
&profile_argv(&["profile", "show", "missing", "--workspace", ".", "--json"]),
|
|
172
|
+
&ws,
|
|
173
|
+
);
|
|
174
|
+
assert_eq!(missing, ExitCode::Error, "profile show missing profile must exit 1");
|
|
175
|
+
|
|
176
|
+
let code_human = run(
|
|
177
|
+
&profile_argv(&["profile", "show", "api_prof", "--workspace", "."]),
|
|
178
|
+
&ws,
|
|
179
|
+
);
|
|
180
|
+
assert_eq!(code_human, ExitCode::Ok, "profile show human output path must route");
|
|
181
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
182
|
+
}
|