@team-agent/installer 0.2.11 → 0.3.1
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 +1204 -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 +1207 -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 +557 -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 +1084 -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 +526 -0
- package/crates/team-agent/src/leader/rediscover.rs +1101 -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 +237 -0
- package/crates/team-agent/src/leader/tests/identity.rs +206 -0
- package/crates/team-agent/src/leader/tests/idle.rs +272 -0
- package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
- package/crates/team-agent/src/leader/tests/lease_claim.rs +410 -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 +489 -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 +2109 -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 +985 -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 +710 -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 +187 -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 +468 -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 +743 -0
- package/crates/team-agent/src/messaging/helpers.rs +209 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +329 -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 +553 -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 +578 -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 +659 -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 +765 -0
- package/crates/team-agent/src/tmux_backend.rs +810 -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 +118 -112
- package/package.json +15 -13
- package/crates/team-agent-core/Cargo.toml +0 -12
- package/crates/team-agent-core/src/lib.rs +0 -332
- package/crates/team-agent-core/src/main.rs +0 -152
- package/pyproject.toml +0 -18
- package/scripts/install.py +0 -88
- package/scripts/run_regression_tests.py +0 -83
- package/src/team_agent/__init__.py +0 -3
- package/src/team_agent/__main__.py +0 -5
- package/src/team_agent/_legacy_pane_discovery.py +0 -186
- package/src/team_agent/abnormal_track.py +0 -253
- package/src/team_agent/approvals/__init__.py +0 -65
- package/src/team_agent/approvals/constants.py +0 -6
- package/src/team_agent/approvals/parsing.py +0 -176
- package/src/team_agent/approvals/runtime_prompts.py +0 -171
- package/src/team_agent/approvals/status.py +0 -176
- package/src/team_agent/cli/__init__.py +0 -137
- package/src/team_agent/cli/commands.py +0 -481
- package/src/team_agent/cli/e2e.py +0 -202
- package/src/team_agent/cli/helpers.py +0 -226
- package/src/team_agent/cli/parser.py +0 -540
- package/src/team_agent/compiler.py +0 -334
- package/src/team_agent/coordinator/__init__.py +0 -53
- package/src/team_agent/coordinator/__main__.py +0 -119
- package/src/team_agent/coordinator/lifecycle.py +0 -411
- package/src/team_agent/coordinator/metadata.py +0 -61
- package/src/team_agent/coordinator/paths.py +0 -17
- package/src/team_agent/diagnose/__init__.py +0 -48
- package/src/team_agent/diagnose/checks.py +0 -101
- package/src/team_agent/diagnose/comms.py +0 -213
- package/src/team_agent/diagnose/health.py +0 -241
- package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
- package/src/team_agent/diagnose/preflight.py +0 -194
- package/src/team_agent/diagnose/quick_start.py +0 -324
- package/src/team_agent/display/__init__.py +0 -92
- package/src/team_agent/display/adaptive.py +0 -511
- package/src/team_agent/display/backend.py +0 -46
- package/src/team_agent/display/close.py +0 -154
- package/src/team_agent/display/ghostty.py +0 -77
- package/src/team_agent/display/rebuild.py +0 -102
- package/src/team_agent/display/tiling.py +0 -156
- package/src/team_agent/display/worker_window.py +0 -114
- package/src/team_agent/display/workspace.py +0 -382
- package/src/team_agent/errors.py +0 -10
- package/src/team_agent/events.py +0 -84
- package/src/team_agent/fake_worker.py +0 -80
- package/src/team_agent/idle_predicate.py +0 -218
- package/src/team_agent/idle_takeover.py +0 -59
- package/src/team_agent/idle_takeover_wiring.py +0 -114
- package/src/team_agent/launch/__init__.py +0 -41
- package/src/team_agent/launch/bootstrap.py +0 -85
- package/src/team_agent/launch/config.py +0 -106
- package/src/team_agent/launch/core.py +0 -301
- package/src/team_agent/launch/requirements.py +0 -57
- package/src/team_agent/leader/__init__.py +0 -926
- package/src/team_agent/leader_binding.py +0 -183
- package/src/team_agent/lifecycle/__init__.py +0 -5
- package/src/team_agent/lifecycle/agents.py +0 -278
- package/src/team_agent/lifecycle/operations.py +0 -411
- package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
- package/src/team_agent/lifecycle/start.py +0 -363
- package/src/team_agent/mcp_server/__init__.py +0 -42
- package/src/team_agent/mcp_server/__main__.py +0 -7
- package/src/team_agent/mcp_server/contracts.py +0 -148
- package/src/team_agent/mcp_server/normalize.py +0 -257
- package/src/team_agent/mcp_server/server.py +0 -150
- package/src/team_agent/mcp_server/tools.py +0 -352
- package/src/team_agent/message_store/__init__.py +0 -23
- package/src/team_agent/message_store/agent_health.py +0 -113
- package/src/team_agent/message_store/core.py +0 -497
- package/src/team_agent/message_store/leader_notification_log.py +0 -198
- package/src/team_agent/message_store/result_watchers.py +0 -251
- package/src/team_agent/message_store/schema.py +0 -308
- package/src/team_agent/message_store/schema_migration.py +0 -448
- package/src/team_agent/messaging/__init__.py +0 -1
- package/src/team_agent/messaging/activity_detector.py +0 -262
- package/src/team_agent/messaging/delivery.py +0 -504
- package/src/team_agent/messaging/deps.py +0 -247
- package/src/team_agent/messaging/idle_alerts.py +0 -423
- package/src/team_agent/messaging/internal_delivery.py +0 -46
- package/src/team_agent/messaging/leader.py +0 -497
- package/src/team_agent/messaging/leader_api_errors.py +0 -216
- package/src/team_agent/messaging/leader_panes.py +0 -673
- package/src/team_agent/messaging/owner_bypass.py +0 -29
- package/src/team_agent/messaging/result_delivery.py +0 -539
- package/src/team_agent/messaging/results.py +0 -447
- package/src/team_agent/messaging/scheduler.py +0 -450
- package/src/team_agent/messaging/send.py +0 -532
- package/src/team_agent/messaging/session_drift.py +0 -94
- package/src/team_agent/messaging/tmux_io.py +0 -506
- package/src/team_agent/messaging/tmux_prompt.py +0 -338
- package/src/team_agent/messaging/trust_auto_answer.py +0 -52
- package/src/team_agent/orchestrator/__init__.py +0 -376
- package/src/team_agent/orchestrator/plan.py +0 -122
- package/src/team_agent/orchestrator/state.py +0 -128
- package/src/team_agent/paths.py +0 -45
- package/src/team_agent/permissions.py +0 -123
- package/src/team_agent/profiles/__init__.py +0 -82
- package/src/team_agent/profiles/constants.py +0 -19
- package/src/team_agent/profiles/core.py +0 -407
- package/src/team_agent/profiles/helpers.py +0 -69
- package/src/team_agent/profiles/provider_env.py +0 -188
- package/src/team_agent/profiles/smoke.py +0 -201
- package/src/team_agent/provider_cli/__init__.py +0 -43
- package/src/team_agent/provider_cli/adapter.py +0 -172
- package/src/team_agent/provider_cli/base.py +0 -48
- package/src/team_agent/provider_cli/claude.py +0 -503
- package/src/team_agent/provider_cli/codex.py +0 -336
- package/src/team_agent/provider_cli/copilot.py +0 -8
- package/src/team_agent/provider_cli/fake.py +0 -39
- package/src/team_agent/provider_cli/gemini.py +0 -95
- package/src/team_agent/provider_cli/opencode.py +0 -8
- package/src/team_agent/provider_cli/prompt.py +0 -62
- package/src/team_agent/provider_cli/registry.py +0 -18
- package/src/team_agent/provider_cli/unsupported.py +0 -32
- package/src/team_agent/provider_state/README.md +0 -78
- package/src/team_agent/provider_state/__init__.py +0 -91
- package/src/team_agent/provider_state/claude.py +0 -86
- package/src/team_agent/provider_state/codex.py +0 -84
- package/src/team_agent/provider_state/common.py +0 -207
- package/src/team_agent/provider_state/registry.py +0 -118
- package/src/team_agent/providers.py +0 -163
- package/src/team_agent/quality_gates.py +0 -104
- package/src/team_agent/restart/__init__.py +0 -34
- package/src/team_agent/restart/orchestration.py +0 -554
- package/src/team_agent/restart/selection.py +0 -89
- package/src/team_agent/restart/snapshot.py +0 -70
- package/src/team_agent/routing.py +0 -84
- package/src/team_agent/runtime.py +0 -1243
- package/src/team_agent/rust_core.py +0 -327
- package/src/team_agent/sessions/__init__.py +0 -25
- package/src/team_agent/sessions/capture.py +0 -144
- package/src/team_agent/sessions/inventory.py +0 -44
- package/src/team_agent/sessions/resume.py +0 -135
- package/src/team_agent/simple_yaml.py +0 -236
- package/src/team_agent/spec.py +0 -370
- package/src/team_agent/state.py +0 -693
- package/src/team_agent/status/__init__.py +0 -63
- package/src/team_agent/status/approvals.py +0 -52
- package/src/team_agent/status/compact.py +0 -158
- package/src/team_agent/status/constants.py +0 -18
- package/src/team_agent/status/inbox.py +0 -58
- package/src/team_agent/status/peek.py +0 -117
- package/src/team_agent/status/queries.py +0 -199
- package/src/team_agent/task_graph.py +0 -80
- package/src/team_agent/terminal.py +0 -57
- package/src/team_agent/wake.py +0 -58
- package/src/team_agent/watch/__init__.py +0 -145
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//! `team-agent` 二进制入口。
|
|
2
|
+
|
|
3
|
+
fn main() -> anyhow::Result<()> {
|
|
4
|
+
let mut args = std::env::args().skip(1);
|
|
5
|
+
let command = args.next();
|
|
6
|
+
if matches!(command.as_deref(), Some("fake-worker")) {
|
|
7
|
+
let mut workspace = None;
|
|
8
|
+
let mut agent_id = None;
|
|
9
|
+
while let Some(arg) = args.next() {
|
|
10
|
+
match arg.as_str() {
|
|
11
|
+
"--workspace" => workspace = args.next().map(std::path::PathBuf::from),
|
|
12
|
+
"--agent-id" => agent_id = args.next(),
|
|
13
|
+
other => return Err(anyhow::anyhow!("unknown fake-worker argument: {other}")),
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
let workspace = workspace
|
|
17
|
+
.ok_or_else(|| anyhow::anyhow!("fake-worker requires --workspace <path>"))?;
|
|
18
|
+
let agent_id = agent_id
|
|
19
|
+
.ok_or_else(|| anyhow::anyhow!("fake-worker requires --agent-id <id>"))?;
|
|
20
|
+
let stdin = std::io::stdin();
|
|
21
|
+
let stdout = std::io::stdout();
|
|
22
|
+
team_agent::fake_worker::run(&workspace, &agent_id, stdin.lock(), stdout.lock())
|
|
23
|
+
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
|
|
24
|
+
return Ok(());
|
|
25
|
+
}
|
|
26
|
+
if matches!(command.as_deref(), Some("mcp-server")) {
|
|
27
|
+
let mut workspace = None;
|
|
28
|
+
while let Some(arg) = args.next() {
|
|
29
|
+
match arg.as_str() {
|
|
30
|
+
"--workspace" => workspace = args.next().map(std::path::PathBuf::from),
|
|
31
|
+
other => return Err(anyhow::anyhow!("unknown mcp-server argument: {other}")),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let workspace = workspace.unwrap_or(std::env::current_dir()?);
|
|
35
|
+
team_agent::mcp_server::main(&workspace, &[])?;
|
|
36
|
+
return Ok(());
|
|
37
|
+
}
|
|
38
|
+
let argv = std::env::args().skip(1).collect::<Vec<_>>();
|
|
39
|
+
let cwd = std::env::current_dir()?;
|
|
40
|
+
std::process::exit(team_agent::cli::run(&argv, &cwd).code());
|
|
41
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
//! step 14a · mcp_server::helpers — pure regularization + shared free helpers.
|
|
2
|
+
|
|
3
|
+
use std::io::Write as _;
|
|
4
|
+
use std::path::Path;
|
|
5
|
+
|
|
6
|
+
use serde::Serialize;
|
|
7
|
+
use serde_json::Value;
|
|
8
|
+
|
|
9
|
+
use crate::messaging::{DeliveryOutcome, MessageTarget};
|
|
10
|
+
use crate::state::persist::load_runtime_state;
|
|
11
|
+
|
|
12
|
+
use super::types::{NormalizedReportEnvelope, ToolError, ToolErrorReason};
|
|
13
|
+
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
15
|
+
// MODULE HELPERS (tools.py:16-69) — pure regularization, contract-callable.
|
|
16
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
17
|
+
|
|
18
|
+
/// `_requires_ack_for_target` (`tools.py:16-19`): leader-only targets default to no
|
|
19
|
+
/// ack; any non-leader target → requires ack.
|
|
20
|
+
pub fn requires_ack_for_target(to: &MessageTarget) -> bool {
|
|
21
|
+
match to {
|
|
22
|
+
MessageTarget::Single(target) => !(target == "leader" || target == "Leader"),
|
|
23
|
+
MessageTarget::Broadcast => true,
|
|
24
|
+
MessageTarget::Fanout(targets) => targets
|
|
25
|
+
.iter()
|
|
26
|
+
.any(|target| !(target == "leader" || target == "Leader")),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// `_is_worker_recipient` (`tools.py:22-27`): a single string target that is not
|
|
31
|
+
/// `""`/`"*"`/`"leader"`/`"Leader"` → worker recipient (async accepted path).
|
|
32
|
+
pub fn is_worker_recipient(to: &MessageTarget) -> bool {
|
|
33
|
+
match to {
|
|
34
|
+
MessageTarget::Single(target) => {
|
|
35
|
+
!(target.is_empty() || target == "*" || target == "leader" || target == "Leader")
|
|
36
|
+
}
|
|
37
|
+
MessageTarget::Broadcast | MessageTarget::Fanout(_) => false,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// `_merge_tasks_by_id` (`tools.py:30-49`): dedupe a task list keyed by `id`,
|
|
42
|
+
/// `prefer` entries winning on duplicates (so an earlier `done` is not regressed).
|
|
43
|
+
pub fn merge_tasks_by_id(prefer: &[Value], fallback: &[Value]) -> Vec<Value> {
|
|
44
|
+
let mut seen = std::collections::BTreeSet::new();
|
|
45
|
+
let mut out = Vec::new();
|
|
46
|
+
for item in prefer.iter().chain(fallback.iter()) {
|
|
47
|
+
let Some(id) = item.get("id").and_then(Value::as_str) else {
|
|
48
|
+
continue;
|
|
49
|
+
};
|
|
50
|
+
if seen.insert(id.to_string()) && item.is_object() {
|
|
51
|
+
out.push(item.clone());
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
out
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub(crate) fn tool_error_reason_wire(reason: ToolErrorReason) -> &'static str {
|
|
58
|
+
match reason {
|
|
59
|
+
ToolErrorReason::UnknownTool => "unknown_tool",
|
|
60
|
+
ToolErrorReason::InvalidToolArguments => "invalid_tool_arguments",
|
|
61
|
+
ToolErrorReason::InternalRuntimeError => "internal_runtime_error",
|
|
62
|
+
ToolErrorReason::PeerNotInScope => "peer_not_in_scope",
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub(crate) fn normalize_token(value: Option<&str>) -> String {
|
|
67
|
+
value
|
|
68
|
+
.unwrap_or("")
|
|
69
|
+
.trim()
|
|
70
|
+
.to_ascii_lowercase()
|
|
71
|
+
.replace(['-', ' '], "_")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
pub(crate) fn text_field(value: &Value, key: &str) -> Option<String> {
|
|
75
|
+
value.get(key).and_then(text_of_value)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pub(crate) fn text_of_value(value: &Value) -> Option<String> {
|
|
79
|
+
match value {
|
|
80
|
+
Value::Null => None,
|
|
81
|
+
Value::String(s) => non_empty_string(s).map(ToString::to_string),
|
|
82
|
+
Value::Number(n) => Some(n.to_string()),
|
|
83
|
+
Value::Bool(b) => Some(if *b { "True" } else { "False" }.to_string()),
|
|
84
|
+
Value::Array(_) | Value::Object(_) => None,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pub(crate) fn items_from_value(value: Option<&Value>) -> Vec<Value> {
|
|
89
|
+
match value {
|
|
90
|
+
Some(Value::Array(items)) => items.clone(),
|
|
91
|
+
Some(Value::Null) | None => Vec::new(),
|
|
92
|
+
Some(other) => vec![other.clone()],
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pub(crate) fn non_empty_string(value: &str) -> Option<&str> {
|
|
97
|
+
let trimmed = value.trim();
|
|
98
|
+
if trimmed.is_empty() {
|
|
99
|
+
None
|
|
100
|
+
} else {
|
|
101
|
+
Some(trimmed)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
pub(crate) fn enum_value<T: Serialize>(value: T) -> Value {
|
|
106
|
+
match serde_json::to_value(value) {
|
|
107
|
+
Ok(value) => value,
|
|
108
|
+
Err(_) => Value::Null,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
pub(crate) fn json_dumps_default(value: &Value) -> String {
|
|
113
|
+
let mut bytes = Vec::new();
|
|
114
|
+
let mut ser = serde_json::Serializer::with_formatter(&mut bytes, PythonJsonFormatter);
|
|
115
|
+
if value.serialize(&mut ser).is_err() {
|
|
116
|
+
return "{}".to_string();
|
|
117
|
+
}
|
|
118
|
+
String::from_utf8(bytes).unwrap_or_else(|_| "{}".to_string())
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
struct PythonJsonFormatter;
|
|
122
|
+
|
|
123
|
+
impl serde_json::ser::Formatter for PythonJsonFormatter {
|
|
124
|
+
fn begin_array_value<W: ?Sized + std::io::Write>(
|
|
125
|
+
&mut self,
|
|
126
|
+
writer: &mut W,
|
|
127
|
+
first: bool,
|
|
128
|
+
) -> std::io::Result<()> {
|
|
129
|
+
if first {
|
|
130
|
+
Ok(())
|
|
131
|
+
} else {
|
|
132
|
+
writer.write_all(b", ")
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fn begin_object_key<W: ?Sized + std::io::Write>(
|
|
137
|
+
&mut self,
|
|
138
|
+
writer: &mut W,
|
|
139
|
+
first: bool,
|
|
140
|
+
) -> std::io::Result<()> {
|
|
141
|
+
if first {
|
|
142
|
+
Ok(())
|
|
143
|
+
} else {
|
|
144
|
+
writer.write_all(b", ")
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn begin_object_value<W: ?Sized + std::io::Write>(&mut self, writer: &mut W) -> std::io::Result<()> {
|
|
149
|
+
writer.write_all(b": ")
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
pub(crate) fn normalized_envelope_value(env: &NormalizedReportEnvelope) -> Value {
|
|
154
|
+
match serde_json::to_value(env) {
|
|
155
|
+
Ok(value) => value,
|
|
156
|
+
Err(_) => serde_json::json!({
|
|
157
|
+
"schema_version": "result_envelope_v1",
|
|
158
|
+
"task_id": env.task_id.as_str(),
|
|
159
|
+
"agent_id": env.agent_id.as_str(),
|
|
160
|
+
"status": "success",
|
|
161
|
+
"summary": env.summary,
|
|
162
|
+
"changes": [], "tests": [], "risks": [], "artifacts": [], "next_actions": []
|
|
163
|
+
}),
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
pub(crate) fn ensure_object(value: &mut Value) {
|
|
168
|
+
if !value.is_object() {
|
|
169
|
+
*value = Value::Object(serde_json::Map::new());
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
pub(crate) fn insert_array(obj: &mut serde_json::Map<String, Value>, key: &str, value: Option<&[Value]>) {
|
|
174
|
+
if let Some(items) = value {
|
|
175
|
+
obj.insert(key.to_string(), Value::Array(items.to_vec()));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
pub(crate) fn tool_runtime_error(err: impl std::fmt::Display) -> ToolError {
|
|
180
|
+
ToolError::new(
|
|
181
|
+
ToolErrorReason::InternalRuntimeError,
|
|
182
|
+
ToolError::public_exception_message(&err.to_string(), "RuntimeError"),
|
|
183
|
+
"RuntimeError",
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
pub(crate) fn object_fields(value: Value) -> serde_json::Map<String, Value> {
|
|
188
|
+
match value {
|
|
189
|
+
Value::Object(map) => map,
|
|
190
|
+
other => {
|
|
191
|
+
let mut map = serde_json::Map::new();
|
|
192
|
+
map.insert("ok".to_string(), Value::Bool(true));
|
|
193
|
+
map.insert("value".to_string(), other);
|
|
194
|
+
map
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
pub(crate) fn delivery_outcome_value(out: &DeliveryOutcome) -> Value {
|
|
200
|
+
serde_json::json!({
|
|
201
|
+
"ok": out.ok,
|
|
202
|
+
"status": enum_value(out.status),
|
|
203
|
+
"message_id": out.message_id,
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
pub(crate) fn latest_task_for_assignee(workspace: &Path, agent_id: &str) -> Option<String> {
|
|
208
|
+
let state = load_runtime_state(workspace).ok()?;
|
|
209
|
+
let tasks = state.get("tasks").and_then(Value::as_array)?;
|
|
210
|
+
for task in tasks.iter().rev() {
|
|
211
|
+
let assignee = task.get("assignee").and_then(Value::as_str)?;
|
|
212
|
+
if assignee != agent_id {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
let status = task
|
|
216
|
+
.get("status")
|
|
217
|
+
.and_then(Value::as_str)
|
|
218
|
+
.unwrap_or("")
|
|
219
|
+
.to_ascii_lowercase();
|
|
220
|
+
if matches!(status.as_str(), "done" | "success" | "failed" | "blocked" | "cancelled") {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if let Some(id) = task.get("id").and_then(text_of_value) {
|
|
224
|
+
return Some(id);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
None
|
|
228
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
//! step 14a · mcp_server — stdio MCP server (`team_orchestrator`) over JSON-RPC 2.0.
|
|
2
|
+
//!
|
|
3
|
+
//! Card: `docs/phase0/subsystems/14-mcp_cli.md` (MCP half).
|
|
4
|
+
//! Truth source (READ-ONLY) `team-agent-public` @ v0.2.11 / `439bef8`:
|
|
5
|
+
//! - `mcp_server/server.py` — stdio loop + JSON-RPC route (`dispatch`/`handle_mcp`/`main`)
|
|
6
|
+
//! - `mcp_server/tools.py` — `TeamOrchestratorTools`: the 12 typed tool handlers
|
|
7
|
+
//! - `mcp_server/contracts.py` — `TOOLS`: name/description/inputSchema (wire single-truth)
|
|
8
|
+
//! - `mcp_server/normalize.py` — result envelope / compact-result regularization
|
|
9
|
+
//! - `mcp_server/__init__.py` — package re-export surface locked by boundary tests
|
|
10
|
+
//!
|
|
11
|
+
//! SCOPE — this is the TYPE + BEHAVIORAL-ENTRY-FN layer so RED contracts can both
|
|
12
|
+
//! NAME the wire/envelope types AND CALL real handlers and assert against rich
|
|
13
|
+
//! return values. It is "the thinnest shell": it owns the wire protocol shape, the
|
|
14
|
+
//! tool-regularization rules, the error envelope, and identity/scope anchoring —
|
|
15
|
+
//! everything durable is delegated to step 5/6/7/11/13. Bodies are
|
|
16
|
+
//! `unimplemented!("step14 port: …")`.
|
|
17
|
+
//!
|
|
18
|
+
//! REUSE (do NOT redefine):
|
|
19
|
+
//! - [`MessageStore`] (step 7) — `request_human` creates the leader message row.
|
|
20
|
+
//! - [`EventLog`] (step 4) — `mcp.scope_resolved` / `mcp.send_message_refused` /
|
|
21
|
+
//! `mcp.identity_inference_failed` / `mcp.task_inference_failed` audit events.
|
|
22
|
+
//! - [`load_runtime_state`] / [`save_runtime_state`] (step 5 persist) — `assign_task`
|
|
23
|
+
//! / `update_state` read-modify-write; `get_visible_peers` reads team scope.
|
|
24
|
+
//! - [`messaging`] (step 11) — `send_message` / `report_result` / `collect` /
|
|
25
|
+
//! `stuck_list` / `stuck_cancel` delegated by the tool handlers.
|
|
26
|
+
//! - [`crate::model::enums`] (step 2) — [`ResultStatus`] / [`ChangeKind`] /
|
|
27
|
+
//! [`TestStatus`] / [`RiskSeverity`] are the normalized result-envelope value
|
|
28
|
+
//! enums; this layer ONLY does string-alias regularization onto them.
|
|
29
|
+
//! - [`AgentId`] / [`TaskId`] / [`TeamKey`] (step 2 ids) — identity/scope anchors.
|
|
30
|
+
//!
|
|
31
|
+
//! 铁律 (card §11, Rust 绝不重蹈 Python 坑):
|
|
32
|
+
//! - **scope 锚 env, 禁候选扫描** (C13-C17/bug-064/082): sender identity =
|
|
33
|
+
//! spawn-time `TEAM_AGENT_ID`; scope = `TEAM_AGENT_OWNER_TEAM_ID`. `to="*"`
|
|
34
|
+
//! defaults to the sender team; `scope="workspace"` is the only cross-team
|
|
35
|
+
//! opt-in. A peer not in scope → typed [`ToolError`] with
|
|
36
|
+
//! [`ToolErrorReason::PeerNotInScope`] — never leak other-team peer names.
|
|
37
|
+
//! - **错误信封冗余键** (server.py:98-106): `reason == error_code` and
|
|
38
|
+
//! `message == error` are byte-stable downstream contracts — preserved verbatim
|
|
39
|
+
//! in [`ToolError`]'s serialization, NOT "cleaned up".
|
|
40
|
+
//! - **notifications/* 不回包** (server.py:49-50): `notifications/*` → [`RpcMethod::
|
|
41
|
+
//! Notification`] → [`handle_mcp`] returns `None`; the loop `continue`s. Emitting
|
|
42
|
+
//! a frame here would corrupt the stdout JSON-RPC stream.
|
|
43
|
+
//! - **stdout 是传输通道** (server.py:135): every error is surfaced ON stdout as a
|
|
44
|
+
//! JSON-RPC frame; logs/warnings MUST go to stderr/file, never stdout.
|
|
45
|
+
//! - **worker-recipient 异步 accepted** (tools.py:176-183): a worker recipient with
|
|
46
|
+
//! a message_id → [`SendOutcome::WorkerAccepted`] carrying the byte-stable
|
|
47
|
+
//! `poll_via = "team-agent inbox <id>"`; leader/`*` → [`SendOutcome::Direct`].
|
|
48
|
+
//! - **兜底字符串字节级保留** (bug-085): `_infer_task_id` failure → `"manual"` (not
|
|
49
|
+
//! None); `_infer_agent_id` failure → `None` → caller routes to `"unknown"`.
|
|
50
|
+
//!
|
|
51
|
+
//! §10 deny: this subsystem is NOT a daemon/coordinator path, so the MCP shell does
|
|
52
|
+
//! not force top-level `#![deny(unwrap/expect/panic)]` (leader decides at
|
|
53
|
+
//! integration; card §109 carves out only `diagnose::comms::evaluate_idle_behavior`
|
|
54
|
+
//! and `diagnose::orphan::*`, which live in the CLI/diagnose lane, not here). All
|
|
55
|
+
//! fallible handlers return `Result<_, McpError>` regardless.
|
|
56
|
+
|
|
57
|
+
// ROUND-0 skeleton: fn bodies are all unimplemented!() so imports/fields/methods are
|
|
58
|
+
// not yet exercised; P2 porter removes this when implementing.
|
|
59
|
+
#![allow(dead_code, unused_imports, unused_variables, clippy::result_large_err, clippy::doc_overindented_list_items, clippy::doc_lazy_continuation, clippy::io_other_error)]
|
|
60
|
+
// §10:MCP stdio handlers 实现层禁 unwrap/expect/panic(unimplemented!() stub 不被拦);tests 子模块各自 allow。
|
|
61
|
+
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
62
|
+
|
|
63
|
+
use std::collections::BTreeSet;
|
|
64
|
+
use std::io::Write as _;
|
|
65
|
+
use std::path::{Path, PathBuf};
|
|
66
|
+
|
|
67
|
+
use serde::{Deserialize, Serialize};
|
|
68
|
+
use serde_json::Value;
|
|
69
|
+
use thiserror::Error;
|
|
70
|
+
|
|
71
|
+
// ── REUSE: step 2 model (ids + normalized-envelope value enums) ─────────────
|
|
72
|
+
use crate::model::enums::{ChangeKind, ResultStatus, RiskSeverity, TestStatus};
|
|
73
|
+
use crate::model::ids::{AgentId, TaskId, TeamKey};
|
|
74
|
+
|
|
75
|
+
// ── REUSE: step 4 event_log / step 7 message_store ──────────────────────────
|
|
76
|
+
use crate::event_log::EventLog;
|
|
77
|
+
use crate::message_store::MessageStore;
|
|
78
|
+
|
|
79
|
+
// ── REUSE: step 5 state persist / projection ────────────────────────────────
|
|
80
|
+
use crate::state::persist::{load_runtime_state, save_runtime_state};
|
|
81
|
+
|
|
82
|
+
// ── REUSE: step 11 messaging delegate surface ───────────────────────────────
|
|
83
|
+
use crate::messaging::{self, DeliveryOutcome, MessageTarget, SendOptions};
|
|
84
|
+
|
|
85
|
+
pub mod helpers;
|
|
86
|
+
pub mod normalize;
|
|
87
|
+
pub mod tools;
|
|
88
|
+
pub mod types;
|
|
89
|
+
pub mod wire;
|
|
90
|
+
|
|
91
|
+
// ── re-export: 保持 `crate::mcp_server::X` 与 test `super::X` 解析不变 ─────────
|
|
92
|
+
pub use helpers::*;
|
|
93
|
+
pub use normalize::*;
|
|
94
|
+
pub use tools::*;
|
|
95
|
+
pub use types::*;
|
|
96
|
+
pub use wire::*;
|
|
97
|
+
|
|
98
|
+
// pub(crate) 子项 (normalize 的 list helpers、wire 的 dispatch_tool 等) 经此再导出,
|
|
99
|
+
// 使 `#[cfg(test)] mod tests` 的 `use super::*` 与跨子模块引用解析不变。
|
|
100
|
+
pub(crate) use helpers::{
|
|
101
|
+
delivery_outcome_value, ensure_object, enum_value, insert_array, latest_task_for_assignee,
|
|
102
|
+
non_empty_string, normalize_token, normalized_envelope_value, object_fields, text_field,
|
|
103
|
+
text_of_value, tool_error_reason_wire, tool_runtime_error,
|
|
104
|
+
};
|
|
105
|
+
pub(crate) use normalize::{
|
|
106
|
+
normalize_artifacts, normalize_changes, normalize_next_actions, normalize_risks, normalize_tests,
|
|
107
|
+
};
|
|
108
|
+
pub(crate) use wire::dispatch_tool;
|
|
109
|
+
|
|
110
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
111
|
+
// CROSS-DEP PLACEHOLDERS — step 13 lifecycle / team_state surface not yet in tree.
|
|
112
|
+
// The 13/15 sibling lanes are in flight; do NOT guess their authoritative names.
|
|
113
|
+
// Leader reconciles these at integration.
|
|
114
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
115
|
+
|
|
116
|
+
/// **PLACEHOLDER** — step 13 lifecycle `runtime.{stop,reset,add,fork}_agent`. The
|
|
117
|
+
/// lifecycle lane is not yet in the tree; these tool handlers delegate to it. Minimal
|
|
118
|
+
/// local stubs so the handler signatures compile and contracts can name the
|
|
119
|
+
/// delegation. Leader swaps for the authoritative step-13 surface at integration.
|
|
120
|
+
pub mod lifecycle_placeholder {
|
|
121
|
+
use super::*;
|
|
122
|
+
|
|
123
|
+
/// `runtime.stop_agent(workspace, agent_id)` (step 13).
|
|
124
|
+
pub fn stop_agent(workspace: &Path, agent_id: &str) -> Result<Value, McpError> {
|
|
125
|
+
let _ = workspace;
|
|
126
|
+
Ok(serde_json::json!({"ok": true, "status": "stopped", "agent_id": agent_id}))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// `runtime.reset_agent(workspace, agent_id, discard_session)` (step 13).
|
|
130
|
+
pub fn reset_agent(workspace: &Path, agent_id: &str, discard_session: bool) -> Result<Value, McpError> {
|
|
131
|
+
let _ = workspace;
|
|
132
|
+
Ok(serde_json::json!({"ok": true, "status": "reset", "agent_id": agent_id, "discard_session": discard_session}))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// `runtime.add_agent(workspace, new_agent_id, role_file_path)` (step 13).
|
|
136
|
+
pub fn add_agent(workspace: &Path, new_agent_id: &str, role_file_path: &str) -> Result<Value, McpError> {
|
|
137
|
+
let _ = workspace;
|
|
138
|
+
Ok(serde_json::json!({"ok": true, "status": "added", "agent_id": new_agent_id, "role_file_path": role_file_path}))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// `runtime.fork_agent(workspace, source_agent_id, as_agent_id, label)` (step 13).
|
|
142
|
+
pub fn fork_agent(workspace: &Path, source_agent_id: &str, as_agent_id: &str, label: Option<&str>) -> Result<Value, McpError> {
|
|
143
|
+
let _ = workspace;
|
|
144
|
+
Ok(serde_json::json!({"ok": true, "status": "forked", "source_agent_id": source_agent_id, "agent_id": as_agent_id, "label": label}))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// `runtime.status(workspace, as_json=true, compact=true)` (step 13 status
|
|
148
|
+
/// projection; `tools.py:328`).
|
|
149
|
+
pub fn runtime_status(workspace: &Path, compact: bool) -> Result<Value, McpError> {
|
|
150
|
+
let _ = (workspace, compact);
|
|
151
|
+
Ok(serde_json::json!({"ok": true, "status": "ok"}))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// `state.write_team_state(workspace, spec, state)` (step 5/13 team_state.md
|
|
155
|
+
/// rewrite; `tools.py:324`). Step 5 persist exists, but this writer is not yet
|
|
156
|
+
/// exported; placeholder until the persist/lifecycle lane lands it.
|
|
157
|
+
pub fn write_team_state(workspace: &Path, spec: &Value, state: &Value) -> Result<PathBuf, McpError> {
|
|
158
|
+
let rel = spec
|
|
159
|
+
.get("context")
|
|
160
|
+
.and_then(|v| v.get("state_file"))
|
|
161
|
+
.and_then(Value::as_str)
|
|
162
|
+
.unwrap_or("team_state.md");
|
|
163
|
+
let path = workspace.join(rel);
|
|
164
|
+
if let Some(parent) = path.parent() {
|
|
165
|
+
std::fs::create_dir_all(parent)?;
|
|
166
|
+
}
|
|
167
|
+
let mut text = String::from("# Team State\n\n## Notes\n\n");
|
|
168
|
+
if let Some(notes) = state.get("notes").and_then(Value::as_array) {
|
|
169
|
+
for note in notes {
|
|
170
|
+
if let Some(note) = note.as_str() {
|
|
171
|
+
text.push_str("- ");
|
|
172
|
+
text.push_str(note);
|
|
173
|
+
text.push('\n');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
std::fs::write(&path, text)?;
|
|
178
|
+
Ok(path)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[cfg(test)]
|
|
183
|
+
mod tests;
|