@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,213 @@
|
|
|
1
|
+
#[test]
|
|
2
|
+
fn turnstate_wire_strings_are_exact_python() {
|
|
3
|
+
// provider_state/common.py state values + idle_takeover_contract enum.
|
|
4
|
+
assert_eq!(ser(&TurnState::Idle), "\"idle\"");
|
|
5
|
+
assert_eq!(ser(&TurnState::Working), "\"working\"");
|
|
6
|
+
assert_eq!(ser(&TurnState::IdleInterrupted), "\"idle_interrupted\"");
|
|
7
|
+
assert_eq!(ser(&TurnState::BlockedOnHuman), "\"blocked_on_human\"");
|
|
8
|
+
assert_eq!(ser(&TurnState::Abnormal), "\"abnormal\"");
|
|
9
|
+
assert_eq!(ser(&TurnState::Unknown), "\"unknown\"");
|
|
10
|
+
// round-trip every variant (no rename drift on Deserialize either).
|
|
11
|
+
for (s, v) in [
|
|
12
|
+
("idle", TurnState::Idle),
|
|
13
|
+
("working", TurnState::Working),
|
|
14
|
+
("idle_interrupted", TurnState::IdleInterrupted),
|
|
15
|
+
("blocked_on_human", TurnState::BlockedOnHuman),
|
|
16
|
+
("abnormal", TurnState::Abnormal),
|
|
17
|
+
("unknown", TurnState::Unknown),
|
|
18
|
+
] {
|
|
19
|
+
let de: TurnState = serde_json::from_str(&format!("\"{s}\"")).expect("de");
|
|
20
|
+
assert_eq!(de, v);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[test]
|
|
25
|
+
fn factkind_wire_strings_are_exact_python() {
|
|
26
|
+
// provider_state/common.py:15-20
|
|
27
|
+
assert_eq!(ser(&FactKind::TurnOpen), "\"turn_open\"");
|
|
28
|
+
assert_eq!(ser(&FactKind::TurnComplete), "\"turn_complete\"");
|
|
29
|
+
assert_eq!(ser(&FactKind::Interrupted), "\"interrupted\"");
|
|
30
|
+
assert_eq!(ser(&FactKind::Failed), "\"failed\"");
|
|
31
|
+
assert_eq!(ser(&FactKind::Approval), "\"approval\"");
|
|
32
|
+
assert_eq!(ser(&FactKind::Error), "\"error\"");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#[test]
|
|
36
|
+
fn process_liveness_wire_strings_are_exact_python() {
|
|
37
|
+
// provider_state/common.py:109 three-valued.
|
|
38
|
+
assert_eq!(ser(&ProcessLiveness::Alive), "\"alive\"");
|
|
39
|
+
assert_eq!(ser(&ProcessLiveness::Dead), "\"dead\"");
|
|
40
|
+
assert_eq!(ser(&ProcessLiveness::Unverifiable), "\"unverifiable\"");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[test]
|
|
44
|
+
fn capture_via_wire_strings_are_exact_python() {
|
|
45
|
+
// claude.py:101/371, codex.py:84
|
|
46
|
+
assert_eq!(ser(&CaptureVia::FsWatch), "\"fs_watch\"");
|
|
47
|
+
assert_eq!(ser(&CaptureVia::FsMtimeFallback), "\"fs_mtime_fallback\"");
|
|
48
|
+
assert_eq!(ser(&CaptureVia::FsRepair), "\"fs_repair\"");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[test]
|
|
52
|
+
fn confidence_wire_strings_are_exact_python() {
|
|
53
|
+
assert_eq!(ser(&Confidence::High), "\"high\"");
|
|
54
|
+
assert_eq!(ser(&Confidence::Medium), "\"medium\"");
|
|
55
|
+
assert_eq!(ser(&Confidence::Low), "\"low\"");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[test]
|
|
59
|
+
fn health_status_is_uppercase_python() {
|
|
60
|
+
// approvals/status.py:114-124,98 — these are UPPERCASE in Python.
|
|
61
|
+
assert_eq!(ser(&HealthStatus::Running), "\"RUNNING\"");
|
|
62
|
+
assert_eq!(ser(&HealthStatus::Idle), "\"IDLE\"");
|
|
63
|
+
assert_eq!(ser(&HealthStatus::Working), "\"WORKING\"");
|
|
64
|
+
assert_eq!(ser(&HealthStatus::Blocked), "\"BLOCKED\"");
|
|
65
|
+
assert_eq!(ser(&HealthStatus::Error), "\"ERROR\"");
|
|
66
|
+
assert_eq!(ser(&HealthStatus::Done), "\"DONE\"");
|
|
67
|
+
assert_eq!(ser(&HealthStatus::Stuck), "\"STUCK\"");
|
|
68
|
+
assert_eq!(ser(&HealthStatus::Uncertain), "\"UNCERTAIN\"");
|
|
69
|
+
assert_eq!(ser(&HealthStatus::AwaitingApproval), "\"AWAITING_APPROVAL\"");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#[test]
|
|
73
|
+
fn agent_runtime_status_wire_strings_are_exact_python() {
|
|
74
|
+
// approvals/status.py:175 (lowercase snake)
|
|
75
|
+
assert_eq!(ser(&AgentRuntimeStatus::Running), "\"running\"");
|
|
76
|
+
assert_eq!(ser(&AgentRuntimeStatus::Busy), "\"busy\"");
|
|
77
|
+
assert_eq!(ser(&AgentRuntimeStatus::Error), "\"error\"");
|
|
78
|
+
assert_eq!(ser(&AgentRuntimeStatus::Missing), "\"missing\"");
|
|
79
|
+
assert_eq!(ser(&AgentRuntimeStatus::Paused), "\"paused\"");
|
|
80
|
+
assert_eq!(ser(&AgentRuntimeStatus::Stopped), "\"stopped\"");
|
|
81
|
+
assert_eq!(
|
|
82
|
+
ser(&AgentRuntimeStatus::AwaitingTrustPrompt),
|
|
83
|
+
"\"awaiting_trust_prompt\""
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#[test]
|
|
88
|
+
fn approval_kind_and_auth_hint_wire_strings() {
|
|
89
|
+
// parsing.py:30/55/65
|
|
90
|
+
assert_eq!(ser(&ApprovalKind::McpTool), "\"mcp_tool\"");
|
|
91
|
+
assert_eq!(ser(&ApprovalKind::Command), "\"command\"");
|
|
92
|
+
assert_eq!(ser(&ApprovalKind::Unknown), "\"unknown\"");
|
|
93
|
+
// adapter.py:38
|
|
94
|
+
assert_eq!(ser(&AuthHintStatus::Present), "\"present\"");
|
|
95
|
+
assert_eq!(ser(&AuthHintStatus::Missing), "\"missing\"");
|
|
96
|
+
assert_eq!(
|
|
97
|
+
ser(&AuthHintStatus::MissingOrUnknown),
|
|
98
|
+
"\"missing_or_unknown\""
|
|
99
|
+
);
|
|
100
|
+
assert_eq!(ser(&AuthHintStatus::Unknown), "\"unknown\"");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#[test]
|
|
104
|
+
fn no_ping_reason_fixed_strings_match_python() {
|
|
105
|
+
// idle_predicate.py fixed reason strings.
|
|
106
|
+
assert_eq!(
|
|
107
|
+
ser(&NoPingReason::NotArmedNoWorkerTurn),
|
|
108
|
+
"\"not_armed_no_worker_turn\""
|
|
109
|
+
);
|
|
110
|
+
assert_eq!(ser(&NoPingReason::Acknowledged), "\"acknowledged\"");
|
|
111
|
+
assert_eq!(ser(&NoPingReason::DebounceActive), "\"debounce_active\"");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#[test]
|
|
115
|
+
fn provider_aliases_share_one_wire_family_and_gemini_cli_renamed() {
|
|
116
|
+
// model::enums::Provider re-export: claude / claude_code are distinct
|
|
117
|
+
// wire keys (providers.py:38-44 ADAPTERS) but reader-side normalize to
|
|
118
|
+
// claude (provider_state/__init__.py:88) — encoded behaviorally below.
|
|
119
|
+
assert_eq!(ser(&Provider::Claude), "\"claude\"");
|
|
120
|
+
assert_eq!(ser(&Provider::ClaudeCode), "\"claude_code\"");
|
|
121
|
+
assert_eq!(ser(&Provider::Codex), "\"codex\"");
|
|
122
|
+
assert_eq!(ser(&Provider::GeminiCli), "\"gemini_cli\"");
|
|
123
|
+
assert_eq!(ser(&Provider::Fake), "\"fake\"");
|
|
124
|
+
// auth_mode wire (profiles/constants.py:6)
|
|
125
|
+
assert_eq!(ser(&AuthMode::CompatibleApi), "\"compatible_api\"");
|
|
126
|
+
assert_eq!(ser(&AuthMode::Subscription), "\"subscription\"");
|
|
127
|
+
assert_eq!(ser(&AuthMode::OfficialApi), "\"official_api\"");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// -------------------------------------------------------------------
|
|
131
|
+
// TIER 1 · PREDICATE CONTRACTS (§11 unknown≠idle / _CLOSING) — pure, locked
|
|
132
|
+
// -------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
#[test]
|
|
135
|
+
fn only_idle_and_idle_interrupted_allow_takeover_ping() {
|
|
136
|
+
// idle_predicate.py:46-49 — _IDLE_STATES = {idle, idle_interrupted}.
|
|
137
|
+
// C12: interrupted counts as idle (annotated). EVERYTHING else blocks.
|
|
138
|
+
assert!(TurnState::Idle.is_idle_for_takeover());
|
|
139
|
+
assert!(TurnState::IdleInterrupted.is_idle_for_takeover());
|
|
140
|
+
// §11 bug: Unknown must NEVER be idle — explicit, no `_ => idle`.
|
|
141
|
+
assert!(!TurnState::Unknown.is_idle_for_takeover());
|
|
142
|
+
assert!(!TurnState::Working.is_idle_for_takeover());
|
|
143
|
+
assert!(!TurnState::BlockedOnHuman.is_idle_for_takeover());
|
|
144
|
+
assert!(!TurnState::Abnormal.is_idle_for_takeover());
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#[test]
|
|
148
|
+
fn closing_facts_are_exactly_complete_interrupted_failed() {
|
|
149
|
+
// common.py:22 — _CLOSING = {turn_complete, interrupted, failed}.
|
|
150
|
+
assert!(FactKind::TurnComplete.is_closing());
|
|
151
|
+
assert!(FactKind::Interrupted.is_closing());
|
|
152
|
+
assert!(FactKind::Failed.is_closing());
|
|
153
|
+
// turn_open / approval / error are NOT closing.
|
|
154
|
+
assert!(!FactKind::TurnOpen.is_closing());
|
|
155
|
+
assert!(!FactKind::Approval.is_closing());
|
|
156
|
+
assert!(!FactKind::Error.is_closing());
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// -------------------------------------------------------------------
|
|
160
|
+
// TIER 1 · NEWTYPE / PAYLOAD shape contracts (bug-085 None穿透) — pure
|
|
161
|
+
// -------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
fn newtypes_are_serde_transparent() {
|
|
165
|
+
// §3 id-混传 newtypes must serialize as the bare scalar (transparent).
|
|
166
|
+
assert_eq!(ser(&SessionId::new("abc-123")), "\"abc-123\"");
|
|
167
|
+
assert_eq!(ser(&TurnId::new("t-9")), "\"t-9\"");
|
|
168
|
+
assert_eq!(
|
|
169
|
+
ser(&ApprovalFingerprint::new("700dc5c0a9e4e3e8")),
|
|
170
|
+
"\"700dc5c0a9e4e3e8\""
|
|
171
|
+
);
|
|
172
|
+
// RolloutPath transparent over the path string.
|
|
173
|
+
assert_eq!(
|
|
174
|
+
ser(&RolloutPath::new(PathBuf::from("/x/y.jsonl"))),
|
|
175
|
+
"\"/x/y.jsonl\""
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#[test]
|
|
180
|
+
fn captured_session_bug085_fallback_shape_roundtrips_with_nulls() {
|
|
181
|
+
// bug-085 (claude.py:365-372): compatible_api fallback yields
|
|
182
|
+
// session_id=None, captured_via=fs_mtime_fallback, confidence=low,
|
|
183
|
+
// rollout_path SET. None must serialize as JSON null (穷尽 Option).
|
|
184
|
+
let cs = CapturedSession {
|
|
185
|
+
session_id: None,
|
|
186
|
+
rollout_path: Some(RolloutPath::new(PathBuf::from("/p/s.jsonl"))),
|
|
187
|
+
captured_via: CaptureVia::FsMtimeFallback,
|
|
188
|
+
attribution_confidence: Confidence::Low,
|
|
189
|
+
spawn_cwd: PathBuf::from("/cwd"),
|
|
190
|
+
};
|
|
191
|
+
let j: serde_json::Value = serde_json::to_value(&cs).expect("to_value");
|
|
192
|
+
assert!(j["session_id"].is_null(), "session_id must be JSON null");
|
|
193
|
+
assert_eq!(j["captured_via"], "fs_mtime_fallback");
|
|
194
|
+
assert_eq!(j["attribution_confidence"], "low");
|
|
195
|
+
assert_eq!(j["rollout_path"], "/p/s.jsonl");
|
|
196
|
+
// round trip back to the same struct.
|
|
197
|
+
let back: CapturedSession = serde_json::from_value(j).expect("from_value");
|
|
198
|
+
assert_eq!(back, cs);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// -------------------------------------------------------------------
|
|
202
|
+
// TIER 2 · BEHAVIORAL — drive through get_adapter(..) (unimplemented → RED)
|
|
203
|
+
// Golden semantics annotated; each test panics today at get_adapter and only
|
|
204
|
+
// greens when the adapter + classify/idle/approval pipeline is implemented.
|
|
205
|
+
// -------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
// ---- (a) turn-state classify: rollout_path=None / unreadable → Unknown ----
|
|
208
|
+
|
|
209
|
+
// Golden probes (PYTHONPATH=…/src python3 /tmp/probe_classify.py against v0.2.11
|
|
210
|
+
// truth source): every state/reason/turn_id/annotations value below is the
|
|
211
|
+
// exact dict returned by provider_state.read_turn_state.
|
|
212
|
+
|
|
213
|
+
// Fixture builders — minimal JSONL transcripts that the readers recognize.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// RED CONTRACTS (wave-2) — step 8 provider parity, encoded against v0.2.11
|
|
3
|
+
// truth source `team-agent-public` @ 439bef8. Golden values captured via
|
|
4
|
+
// PYTHONPATH=…/src python3 /tmp/probe_{turn_state,idle_predicate,approvals}.py.
|
|
5
|
+
//
|
|
6
|
+
// Two tiers:
|
|
7
|
+
// * WIRE/PREDICATE tier — pure enum serde + helper-method contracts. These pin
|
|
8
|
+
// the exact Python byte strings (`TurnState`/`FactKind`/`Confidence`/…) and
|
|
9
|
+
// the §11 fail-safe predicates so the porter cannot drift a rename or sneak a
|
|
10
|
+
// `_ => idle` fallthrough. (Skeleton enums are already concrete → tier is the
|
|
11
|
+
// locked spec the impl must keep satisfying, not the RED driver.)
|
|
12
|
+
// * BEHAVIORAL tier — drives every parity-critical behavior through
|
|
13
|
+
// `get_adapter(..)` + `ProviderAdapter` trait methods, which are
|
|
14
|
+
// `unimplemented!()` in ROUND-0 → these PANIC = genuinely RED today, and only
|
|
15
|
+
// green once the porter wires the adapters AND the neutral classify/idle/
|
|
16
|
+
// approval pipeline the trait sits on top of.
|
|
17
|
+
// ===========================================================================
|
|
18
|
+
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
19
|
+
use super::*;
|
|
20
|
+
use serde::Serialize;
|
|
21
|
+
use std::path::PathBuf;
|
|
22
|
+
|
|
23
|
+
fn ser(v: &impl Serialize) -> String {
|
|
24
|
+
serde_json::to_string(v).expect("serialize")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
include!("tests/wire.rs");
|
|
28
|
+
include!("tests/classify.rs");
|
|
29
|
+
include!("tests/idle.rs");
|
|
30
|
+
include!("tests/adapter.rs");
|
|
31
|
+
include!("tests/faults.rs");
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
//! provider 共享类型:enums / newtypes / ProviderCaps / ProviderError / 捕获+classify payload / 占位结构。
|
|
2
|
+
|
|
3
|
+
use std::path::PathBuf;
|
|
4
|
+
|
|
5
|
+
use serde::{Deserialize, Serialize};
|
|
6
|
+
use thiserror::Error;
|
|
7
|
+
|
|
8
|
+
// ===========================================================================
|
|
9
|
+
// ENUMS (穷尽 + serde rename 到精确 Python 字符串)
|
|
10
|
+
// ===========================================================================
|
|
11
|
+
|
|
12
|
+
/// node 分类结果(`provider_state/common.py` / `idle_takeover.py`)。
|
|
13
|
+
/// **doc §49 铁律:`Unknown` 必须显式 block ping,绝不 fallthrough 成 idle**
|
|
14
|
+
/// (`idle_predicate.py:46-49` 任何非 `{idle, idle_interrupted}` 立刻 block)。
|
|
15
|
+
/// `idle_takeover_contract.md` 列为稳定 contract enum。
|
|
16
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
17
|
+
#[serde(rename_all = "snake_case")]
|
|
18
|
+
pub enum TurnState {
|
|
19
|
+
Idle,
|
|
20
|
+
Working,
|
|
21
|
+
IdleInterrupted,
|
|
22
|
+
BlockedOnHuman,
|
|
23
|
+
Abnormal,
|
|
24
|
+
Unknown,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl TurnState {
|
|
28
|
+
/// take-over predicate 仅对 `{Idle, IdleInterrupted}` 放行 ping
|
|
29
|
+
/// (`idle_predicate.py:46-49`;C12 interrupted 算 idle 带注解)。其余一律 block。
|
|
30
|
+
pub fn is_idle_for_takeover(self) -> bool {
|
|
31
|
+
matches!(self, Self::Idle | Self::IdleInterrupted)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// reader 输出的归一 lifecycle 事件类型(`provider_state/common.py:15-20`)。
|
|
36
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
37
|
+
#[serde(rename_all = "snake_case")]
|
|
38
|
+
pub enum FactKind {
|
|
39
|
+
TurnOpen,
|
|
40
|
+
TurnComplete,
|
|
41
|
+
Interrupted,
|
|
42
|
+
Failed,
|
|
43
|
+
Approval,
|
|
44
|
+
Error,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
impl FactKind {
|
|
48
|
+
/// `_CLOSING = {complete, interrupted, failed}`(`common.py:22`)——闭合一个 open turn。
|
|
49
|
+
pub fn is_closing(self) -> bool {
|
|
50
|
+
matches!(self, Self::TurnComplete | Self::Interrupted | Self::Failed)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// process identity guard 三值判定(`provider_state/common.py:109`)。
|
|
55
|
+
/// **ADJUDICATION**:doc 原名 `Liveness`,与既有 `model::enums::PaneLiveness`
|
|
56
|
+
/// (tmux pane 存活,**不同概念**)同名冲突 → 本 enum 命名 `ProcessLiveness` 避撞。
|
|
57
|
+
/// C4:`Unverifiable ≠ Alive`,绝不乐观读成 working。
|
|
58
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
59
|
+
#[serde(rename_all = "snake_case")]
|
|
60
|
+
pub enum ProcessLiveness {
|
|
61
|
+
Alive,
|
|
62
|
+
Dead,
|
|
63
|
+
Unverifiable,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// agent runtime status(`approvals/status.py:175` / `refresh_agent_runtime_statuses`)。
|
|
67
|
+
/// §19 散字符串态 → enum。归 step 5 state 但 step 8 写。
|
|
68
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
69
|
+
#[serde(rename_all = "snake_case")]
|
|
70
|
+
pub enum AgentRuntimeStatus {
|
|
71
|
+
Running,
|
|
72
|
+
Busy,
|
|
73
|
+
Error,
|
|
74
|
+
Missing,
|
|
75
|
+
Paused,
|
|
76
|
+
Stopped,
|
|
77
|
+
AwaitingTrustPrompt,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// agent health label(`approvals/status.py:114-124,98`)。message-store(step 7)写入。
|
|
81
|
+
/// **注意:Python 此处是大写串**(`"RUNNING"/.../"AWAITING_APPROVAL"`),serde SCREAMING_SNAKE_CASE
|
|
82
|
+
/// (多词变体 `AwaitingApproval` → `"AWAITING_APPROVAL"` 带下划线,非 UPPERCASE 的 "AWAITINGAPPROVAL")。
|
|
83
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
84
|
+
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
85
|
+
pub enum HealthStatus {
|
|
86
|
+
Running,
|
|
87
|
+
Idle,
|
|
88
|
+
Working,
|
|
89
|
+
Blocked,
|
|
90
|
+
Error,
|
|
91
|
+
Done,
|
|
92
|
+
Stuck,
|
|
93
|
+
Uncertain,
|
|
94
|
+
AwaitingApproval,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
pub fn agent_health_status(status: &str) -> HealthStatus {
|
|
98
|
+
match status.to_ascii_lowercase().as_str() {
|
|
99
|
+
"busy" => HealthStatus::Running,
|
|
100
|
+
"running" => HealthStatus::Idle,
|
|
101
|
+
"working" => HealthStatus::Working,
|
|
102
|
+
"paused" | "blocked" | "awaiting_approval" | "awaiting_trust_prompt" => {
|
|
103
|
+
HealthStatus::Blocked
|
|
104
|
+
}
|
|
105
|
+
"error" | "missing" | "interrupted" => HealthStatus::Error,
|
|
106
|
+
"stopped" | "done" => HealthStatus::Done,
|
|
107
|
+
"stuck" => HealthStatus::Stuck,
|
|
108
|
+
"uncertain" => HealthStatus::Uncertain,
|
|
109
|
+
_ => HealthStatus::Idle,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// approval prompt kind(`approvals/parsing.py:30/55/65`)。
|
|
114
|
+
/// 自动 approve 只放行 `McpTool` ∩ 白名单。
|
|
115
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
116
|
+
#[serde(rename_all = "snake_case")]
|
|
117
|
+
pub enum ApprovalKind {
|
|
118
|
+
McpTool,
|
|
119
|
+
Command,
|
|
120
|
+
Unknown,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// session 捕获来源(`claude.py:101/371`、`codex.py:84`)。golden fixture 字段,
|
|
124
|
+
/// §5 EventLog 名稳定一致。
|
|
125
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
126
|
+
#[serde(rename_all = "snake_case")]
|
|
127
|
+
pub enum CaptureVia {
|
|
128
|
+
FsWatch,
|
|
129
|
+
FsMtimeFallback,
|
|
130
|
+
FsRepair,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// attribution confidence(doc §56)。bug-085 fallback 固定 `Low`。
|
|
134
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
135
|
+
#[serde(rename_all = "snake_case")]
|
|
136
|
+
pub enum Confidence {
|
|
137
|
+
High,
|
|
138
|
+
Medium,
|
|
139
|
+
Low,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// auth_hint 状态(`adapter.py:38` 等)。doctor 用。
|
|
143
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
144
|
+
#[serde(rename_all = "snake_case")]
|
|
145
|
+
pub enum AuthHintStatus {
|
|
146
|
+
Present,
|
|
147
|
+
Missing,
|
|
148
|
+
MissingOrUnknown,
|
|
149
|
+
Unknown,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// take-over reason / event 名(`idle_predicate.py` `_result` 的 `reason` 字段)。
|
|
153
|
+
/// §5 EventLog:JSON 事件名 Rust 必须字节一致。固定串变体用 serde rename;
|
|
154
|
+
/// `Node(TurnState)` 承载 `"node_<state>"` 动态形态(`idle_predicate.py:49`)。
|
|
155
|
+
///
|
|
156
|
+
/// **铁律(BLOOD-LINE PIN, bug-071/077/085)**:任何非 `{Idle, IdleInterrupted}` node
|
|
157
|
+
/// 必须命中 `Node(state)` 并 block ping;`Unknown` 渲染 `"node_unknown"`,绝不 fallthrough idle。
|
|
158
|
+
/// `reason_str()` 给出 Python `read_turn_state`/`evaluate_takeover_reminder` 的精确 reason 串。
|
|
159
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
160
|
+
#[serde(rename_all = "snake_case")]
|
|
161
|
+
pub enum NoPingReason {
|
|
162
|
+
/// worker 从未 open 过 turn(`idle_predicate.py:61`)。
|
|
163
|
+
NotArmedNoWorkerTurn,
|
|
164
|
+
/// suppress flag 置位(acknowledge-idle 后,`idle_predicate.py:63`)。
|
|
165
|
+
Acknowledged,
|
|
166
|
+
/// armed 但 elapsed < debounce(`idle_predicate.py:65`)。
|
|
167
|
+
DebounceActive,
|
|
168
|
+
/// 本 episode 已 ping 过一次(`idle_predicate.py:67`)。
|
|
169
|
+
AlreadyPingedThisEpisode,
|
|
170
|
+
/// 全 idle + armed + debounce 到期 → **ping 侧 reason**(`idle_predicate.py:72`,should_ping=True)。
|
|
171
|
+
AllIdleDebounceElapsed,
|
|
172
|
+
/// 无 node(`idle_predicate.py:52`)。
|
|
173
|
+
NoNodes,
|
|
174
|
+
/// node 处于非 idle 态被 block,渲染 `"node_<state>"`(`idle_predicate.py:49`)。
|
|
175
|
+
/// `Unknown` → `"node_unknown"`(missing-state 同样归 unknown)。
|
|
176
|
+
Node(TurnState),
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
impl NoPingReason {
|
|
180
|
+
/// Python `_result` 的 `reason` 字段精确串(`idle_predicate.py`)。
|
|
181
|
+
/// 固定变体直接给串;`Node(state)` 拼成 `"node_<state-wire>"`。
|
|
182
|
+
pub fn reason_str(&self) -> String {
|
|
183
|
+
match self {
|
|
184
|
+
Self::NotArmedNoWorkerTurn => "not_armed_no_worker_turn".to_string(),
|
|
185
|
+
Self::Acknowledged => "acknowledged".to_string(),
|
|
186
|
+
Self::DebounceActive => "debounce_active".to_string(),
|
|
187
|
+
Self::AlreadyPingedThisEpisode => "already_pinged_this_episode".to_string(),
|
|
188
|
+
Self::AllIdleDebounceElapsed => "all_idle_debounce_elapsed".to_string(),
|
|
189
|
+
Self::NoNodes => "no_nodes".to_string(),
|
|
190
|
+
Self::Node(state) => {
|
|
191
|
+
let wire = match state {
|
|
192
|
+
TurnState::Idle => "idle",
|
|
193
|
+
TurnState::Working => "working",
|
|
194
|
+
TurnState::IdleInterrupted => "idle_interrupted",
|
|
195
|
+
TurnState::BlockedOnHuman => "blocked_on_human",
|
|
196
|
+
TurnState::Abnormal => "abnormal",
|
|
197
|
+
TurnState::Unknown => "unknown",
|
|
198
|
+
};
|
|
199
|
+
format!("node_{wire}")
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ===========================================================================
|
|
206
|
+
// NEWTYPES (透明 String/PathBuf 包装 — id 混传根因,§3)
|
|
207
|
+
// ===========================================================================
|
|
208
|
+
|
|
209
|
+
/// provider session id(uuid)。**bug-085:`None` 合法**(compatible_api fallback,
|
|
210
|
+
/// `claude.py:366`)——调用面用 `Option<SessionId>`,穷尽 match `None` 不崩。
|
|
211
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
|
212
|
+
#[serde(transparent)]
|
|
213
|
+
pub struct SessionId(pub String);
|
|
214
|
+
|
|
215
|
+
impl SessionId {
|
|
216
|
+
pub fn new(s: impl Into<String>) -> Self {
|
|
217
|
+
Self(s.into())
|
|
218
|
+
}
|
|
219
|
+
pub fn as_str(&self) -> &str {
|
|
220
|
+
&self.0
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
impl std::fmt::Display for SessionId {
|
|
225
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
226
|
+
f.write_str(&self.0)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// provider session 日志路径(claude transcript / codex rollout)。
|
|
231
|
+
/// **bug-085:`None` → node 留 `Unknown`,不得当 idle**(doc §53)。
|
|
232
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
|
233
|
+
#[serde(transparent)]
|
|
234
|
+
pub struct RolloutPath(pub PathBuf);
|
|
235
|
+
|
|
236
|
+
impl RolloutPath {
|
|
237
|
+
pub fn new(p: impl Into<PathBuf>) -> Self {
|
|
238
|
+
Self(p.into())
|
|
239
|
+
}
|
|
240
|
+
pub fn as_path(&self) -> &std::path::Path {
|
|
241
|
+
&self.0
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// turn id(claude `requestId`/`uuid`;codex `turn_id`)。abnormal dedup key 一半
|
|
246
|
+
/// `(signature, turn_id)`(C8,`claude.py:54-62`)。`None` 合法 → `Option<TurnId>`。
|
|
247
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
|
248
|
+
#[serde(transparent)]
|
|
249
|
+
pub struct TurnId(pub String);
|
|
250
|
+
|
|
251
|
+
impl TurnId {
|
|
252
|
+
pub fn new(s: impl Into<String>) -> Self {
|
|
253
|
+
Self(s.into())
|
|
254
|
+
}
|
|
255
|
+
pub fn as_str(&self) -> &str {
|
|
256
|
+
&self.0
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// approval prompt fingerprint = sha256[:16] hex(`approvals/parsing.py:138`)。
|
|
261
|
+
/// 幂等 dedup key。doc §62 给两选项(`[u8;8]` / `String`)——选 transparent `String`
|
|
262
|
+
/// 与既有 hex newtype 风格(`LeaderSessionUuid`)一致,且序列化字节 == Python hex 串。
|
|
263
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
|
264
|
+
#[serde(transparent)]
|
|
265
|
+
pub struct ApprovalFingerprint(pub String);
|
|
266
|
+
|
|
267
|
+
impl ApprovalFingerprint {
|
|
268
|
+
pub fn new(s: impl Into<String>) -> Self {
|
|
269
|
+
Self(s.into())
|
|
270
|
+
}
|
|
271
|
+
pub fn as_str(&self) -> &str {
|
|
272
|
+
&self.0
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// abnormal/fault fact 签名(`claude.py:51/57` `"signature"`、`codex.py:65/80`)。
|
|
277
|
+
/// C8 dedup key 的另一半 `(Signature, Option<TurnId>)`。固定取值:
|
|
278
|
+
/// `api_error` / `tool_result_is_error` / `turn_failed` / `approval_required`。
|
|
279
|
+
/// transparent String 与 hex newtype 风格一致,序列化字节 == Python 串。
|
|
280
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
|
281
|
+
#[serde(transparent)]
|
|
282
|
+
pub struct Signature(pub String);
|
|
283
|
+
|
|
284
|
+
impl Signature {
|
|
285
|
+
pub fn new(s: impl Into<String>) -> Self {
|
|
286
|
+
Self(s.into())
|
|
287
|
+
}
|
|
288
|
+
pub fn as_str(&self) -> &str {
|
|
289
|
+
&self.0
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ===========================================================================
|
|
294
|
+
// STRUCT
|
|
295
|
+
// ===========================================================================
|
|
296
|
+
|
|
297
|
+
/// provider 能力位(doc §59)。`supports_session_fork` 还依赖
|
|
298
|
+
/// `auth_mode != compatible_api`(`claude.py:54`/`codex.py:45`)——`fork` 的运行期
|
|
299
|
+
/// 真值由 `ProviderAdapter::fork` + auth_mode 共同决定,此 struct 是静态声明。
|
|
300
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
301
|
+
pub struct ProviderCaps {
|
|
302
|
+
pub resume: bool,
|
|
303
|
+
pub fork: bool,
|
|
304
|
+
pub native_mcp_config: bool,
|
|
305
|
+
pub writes_global_settings: bool,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ===========================================================================
|
|
309
|
+
// ERROR
|
|
310
|
+
// ===========================================================================
|
|
311
|
+
|
|
312
|
+
/// step 8 provider 错误。**ADJUDICATION**:不复用 `model::errors::ModelError`
|
|
313
|
+
/// (那是 spec/envelope 校验层);provider 层有自己的失败语义:
|
|
314
|
+
/// - `CapabilityUnsupported`:占位 provider(copilot/opencode)调用即拒
|
|
315
|
+
/// (`unsupported.py:31` `ProviderCapabilityError`)——doc §126「绝不静默返回空命令」。
|
|
316
|
+
/// - `ResumeUnavailable`:bug-085 / 不可 resume 时**干净 raise 不崩**(`adapter.py` `ResumeUnavailable`)。
|
|
317
|
+
/// - `Io`:tmux capture / send-keys / 文件读写子进程失败(daemon tick 必须返 Result 不 panic)。
|
|
318
|
+
///
|
|
319
|
+
/// 实现层补充变体时保持 fallible 边界;§10 deny-lock 由 leader 加。
|
|
320
|
+
#[derive(Debug, Error)]
|
|
321
|
+
pub enum ProviderError {
|
|
322
|
+
/// 占位 provider 能力未实现(copilot/opencode)。
|
|
323
|
+
#[error("provider capability unsupported: {0}")]
|
|
324
|
+
CapabilityUnsupported(String),
|
|
325
|
+
/// resume 不可用(bug-085 compatible_api `session_id=None` 等)。
|
|
326
|
+
#[error("resume unavailable: {0}")]
|
|
327
|
+
ResumeUnavailable(String),
|
|
328
|
+
/// session 捕获失败 / 超时。
|
|
329
|
+
#[error("session capture failed: {0}")]
|
|
330
|
+
CaptureFailed(String),
|
|
331
|
+
/// 命令构造 / model 校验失败。
|
|
332
|
+
#[error("provider command error: {0}")]
|
|
333
|
+
Command(String),
|
|
334
|
+
/// tmux / 文件 / 子进程 I/O 失败(返 Result 不 panic,株连 bug-084 是禁区)。
|
|
335
|
+
#[error("provider io error: {0}")]
|
|
336
|
+
Io(String),
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ===========================================================================
|
|
340
|
+
// 捕获返回 payload (doc §73:capture_session_id 返 6 键 dict 的 typed 版)
|
|
341
|
+
// ===========================================================================
|
|
342
|
+
|
|
343
|
+
/// `capture_session_id` 成功返回(`claude.py:73`/`codex.py:62` 的 typed dict)。
|
|
344
|
+
/// bug-085:`session_id` 可为 `None`,`rollout_path` 可为 `None`(半状态合法)。
|
|
345
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
346
|
+
pub struct CapturedSession {
|
|
347
|
+
pub session_id: Option<SessionId>,
|
|
348
|
+
pub rollout_path: Option<RolloutPath>,
|
|
349
|
+
pub captured_via: CaptureVia,
|
|
350
|
+
pub attribution_confidence: Confidence,
|
|
351
|
+
pub spawn_cwd: PathBuf,
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ===========================================================================
|
|
355
|
+
// CLASSIFY 结果 (provider_state.common._result 的 typed 版 — doc §73)
|
|
356
|
+
// ===========================================================================
|
|
357
|
+
|
|
358
|
+
/// classify 结果来源(`provider_state/common.py:_result` 的 `source` 字段)。
|
|
359
|
+
/// `session_file`:verdict 出自日志 lifecycle fact;`process_guard`:open turn 被
|
|
360
|
+
/// process-identity 判定 demote;`registry`:未知 provider 兜底。
|
|
361
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
362
|
+
#[serde(rename_all = "snake_case")]
|
|
363
|
+
pub enum ClassifySource {
|
|
364
|
+
SessionFile,
|
|
365
|
+
ProcessGuard,
|
|
366
|
+
Registry,
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/// 中性 classify 结果(`provider_state.common.decide_state`/`_result` 的 6 键 dict)。
|
|
370
|
+
/// **铁律**:`state == Unknown` 时 `reason` 必为 `unreadable_or_empty` /
|
|
371
|
+
/// `no_turn_lifecycle_fact` / `process_identity_unverified` / `unknown_provider`,
|
|
372
|
+
/// 且 `is_idle_for_takeover() == false`(穷尽,无 `_ => idle`)。
|
|
373
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
374
|
+
pub struct ClassifyResult {
|
|
375
|
+
pub state: TurnState,
|
|
376
|
+
pub turn_id: Option<TurnId>,
|
|
377
|
+
pub reason: String,
|
|
378
|
+
pub source: ClassifySource,
|
|
379
|
+
pub annotations: Vec<String>,
|
|
380
|
+
pub diagnostics: Vec<serde_json::Value>,
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/// abnormal track 消费的 fault/approval fact(`provider_state.read_fault_facts`)。
|
|
384
|
+
/// C8 dedup key = `(signature, turn_id)`。`turn_id` 可 `None`(`api_error` 无 ids)。
|
|
385
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
386
|
+
pub struct FaultFact {
|
|
387
|
+
pub signature: Signature,
|
|
388
|
+
pub turn_id: Option<TurnId>,
|
|
389
|
+
pub kind: FactKind,
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/// take-over reminder 判定结果(`idle_predicate.evaluate_takeover_reminder` `_result`)。
|
|
393
|
+
/// `interrupted_nodes`:C12 idle_interrupted node 的 id 列表(注解穿透进 ping 结果)。
|
|
394
|
+
/// `message`:should_ping 时携带的中性提醒文案,否则 `None`。
|
|
395
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
396
|
+
pub struct RemindResult {
|
|
397
|
+
pub should_ping: bool,
|
|
398
|
+
pub reason: NoPingReason,
|
|
399
|
+
pub interrupted_nodes: Vec<String>,
|
|
400
|
+
pub message: Option<String>,
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ===========================================================================
|
|
404
|
+
// 占位结构 (impl 阶段填充;ROUND-0 仅命名让 trait 签名编得过)
|
|
405
|
+
// ===========================================================================
|
|
406
|
+
|
|
407
|
+
/// MCP server 配置(step 6 spec compiler 产出 / 本子系统消费)。
|
|
408
|
+
/// ROUND-0 占位:字段在 step 8 impl 时对照 `ensure_compatible_claude_mcp_config` 填充。
|
|
409
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
410
|
+
pub struct McpConfig {
|
|
411
|
+
/// 占位:实际 server map / transport / env 注入在 impl 时补。
|
|
412
|
+
pub raw: serde_json::Value,
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/// pane→status 识别正则集(`provider_cli/claude.py:225`/`codex.py:140`
|
|
416
|
+
/// `status_patterns()` 返回的 idle/processing/error 三正则)。
|
|
417
|
+
/// claude:`idle=r"[>❯]\s"` `processing=r"[✶✢✽✻✳·].*…"` `error="Error|Traceback"`;
|
|
418
|
+
/// codex:`idle=r"(›|❯|codex>)"` `processing=r"•.*esc to interrupt"` `error="Error|Traceback|panic"`。
|
|
419
|
+
#[derive(Debug, Clone)]
|
|
420
|
+
pub struct StatusPatterns {
|
|
421
|
+
pub idle: regex::Regex,
|
|
422
|
+
pub processing: regex::Regex,
|
|
423
|
+
pub error: regex::Regex,
|
|
424
|
+
}
|