@team-agent/installer 0.5.42 → 0.5.44
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 +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +129 -54
- package/crates/team-agent/src/cli/diagnose.rs +1 -2
- package/crates/team-agent/src/cli/emit.rs +1 -7
- package/crates/team-agent/src/cli/helpers.rs +3 -1
- package/crates/team-agent/src/cli/leader.rs +2 -1
- package/crates/team-agent/src/cli/mod.rs +126 -16
- package/crates/team-agent/src/cli/named_address.rs +14 -5
- package/crates/team-agent/src/cli/profile.rs +19 -7
- package/crates/team-agent/src/cli/send.rs +9 -3
- package/crates/team-agent/src/cli/status.rs +8 -30
- package/crates/team-agent/src/cli/status_port.rs +1341 -1272
- package/crates/team-agent/src/cli/tests/base.rs +738 -660
- package/crates/team-agent/src/cli/tests/compile.rs +45 -18
- package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
- package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
- package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
- package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
- package/crates/team-agent/src/cli/tests/mod.rs +17 -7
- package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
- package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
- package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
- package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
- package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
- package/crates/team-agent/src/codex_app_server.rs +2 -5
- package/crates/team-agent/src/compiler/tests.rs +139 -33
- package/crates/team-agent/src/compiler.rs +55 -22
- package/crates/team-agent/src/conpty/backend.rs +23 -33
- package/crates/team-agent/src/coordinator/backoff.rs +2 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
- package/crates/team-agent/src/coordinator/health.rs +46 -31
- package/crates/team-agent/src/coordinator/mod.rs +3 -3
- package/crates/team-agent/src/coordinator/orphan.rs +22 -10
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +51 -56
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
- package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
- package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
- package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
- package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
- package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
- package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
- package/crates/team-agent/src/db/message_store.rs +138 -30
- package/crates/team-agent/src/db/migration.rs +249 -61
- package/crates/team-agent/src/db/schema.rs +303 -82
- package/crates/team-agent/src/diagnose/comms.rs +9 -2
- package/crates/team-agent/src/diagnose/mod.rs +1 -3
- package/crates/team-agent/src/diagnose/orphans.rs +79 -61
- package/crates/team-agent/src/event_log.rs +70 -16
- package/crates/team-agent/src/layout/manager.rs +15 -4
- package/crates/team-agent/src/layout/mod.rs +4 -4
- package/crates/team-agent/src/layout/overlay.rs +10 -3
- package/crates/team-agent/src/layout/placement.rs +5 -1
- package/crates/team-agent/src/layout/recovery.rs +4 -2
- package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
- package/crates/team-agent/src/layout/sessions.rs +17 -9
- package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
- package/crates/team-agent/src/layout/worker_env.rs +87 -19
- package/crates/team-agent/src/leader/helpers.rs +7 -1
- package/crates/team-agent/src/leader/lease.rs +199 -89
- package/crates/team-agent/src/leader/owner_bind.rs +55 -22
- package/crates/team-agent/src/leader/provider_attribution.rs +25 -6
- package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
- package/crates/team-agent/src/leader/rediscover.rs +74 -25
- package/crates/team-agent/src/leader/registry.rs +1 -1
- package/crates/team-agent/src/leader/start.rs +75 -54
- package/crates/team-agent/src/leader/takeover.rs +46 -11
- package/crates/team-agent/src/leader/tests/basics.rs +320 -167
- package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
- package/crates/team-agent/src/leader/tests/identity.rs +428 -356
- package/crates/team-agent/src/leader/tests/idle.rs +285 -254
- package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
- package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
- package/crates/team-agent/src/leader/tests/mod.rs +115 -99
- package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
- package/crates/team-agent/src/lib.rs +4 -4
- package/crates/team-agent/src/lifecycle/display.rs +7 -3
- package/crates/team-agent/src/lifecycle/launch.rs +55 -15
- package/crates/team-agent/src/lifecycle/mod.rs +9 -1
- package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
- package/crates/team-agent/src/lifecycle/restart/common.rs +6 -2
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests.rs +2 -2
- package/crates/team-agent/src/main.rs +4 -4
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
- package/crates/team-agent/src/mcp_server/mod.rs +11 -2
- package/crates/team-agent/src/mcp_server/types.rs +14 -3
- package/crates/team-agent/src/mcp_server/wire.rs +230 -68
- package/crates/team-agent/src/messaging/delivery.rs +20 -18
- package/crates/team-agent/src/messaging/helpers.rs +25 -4
- package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
- package/crates/team-agent/src/messaging/mod.rs +2 -3
- package/crates/team-agent/src/messaging/selftest.rs +46 -26
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
- package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
- package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
- package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
- package/crates/team-agent/src/messaging/trust.rs +25 -2
- package/crates/team-agent/src/messaging/watchers.rs +37 -9
- package/crates/team-agent/src/model/enums.rs +65 -16
- package/crates/team-agent/src/model/ids.rs +12 -3
- package/crates/team-agent/src/model/paths.rs +28 -7
- package/crates/team-agent/src/model/permissions.rs +176 -33
- package/crates/team-agent/src/model/routing.rs +66 -20
- package/crates/team-agent/src/model/spec.rs +365 -69
- package/crates/team-agent/src/model/task_graph.rs +36 -9
- package/crates/team-agent/src/model/yaml/tests.rs +24 -6
- package/crates/team-agent/src/model/yaml.rs +7 -6
- package/crates/team-agent/src/packaging/install.rs +23 -9
- package/crates/team-agent/src/packaging/migrate.rs +5 -7
- package/crates/team-agent/src/packaging/mod.rs +9 -1
- package/crates/team-agent/src/packaging/repair.rs +13 -6
- package/crates/team-agent/src/packaging/tests.rs +63 -16
- package/crates/team-agent/src/packaging/types.rs +22 -7
- package/crates/team-agent/src/platform/argv.rs +4 -1
- package/crates/team-agent/src/platform/file_lock.rs +22 -8
- package/crates/team-agent/src/platform/process.rs +54 -24
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
- package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
- package/crates/team-agent/src/provider/classify.rs +127 -42
- package/crates/team-agent/src/provider/faults.rs +17 -5
- package/crates/team-agent/src/provider/helpers.rs +6 -5
- package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
- package/crates/team-agent/src/state/persist.rs +28 -0
- package/crates/team-agent/src/state/repository.rs +8 -3
- package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
- package/crates/team-agent/src/tmux_backend.rs +80 -44
- package/crates/team-agent/src/topology.rs +40 -20
- package/crates/team-agent/src/transport/test_support.rs +20 -23
- package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
- package/crates/team-agent/src/transport/tests/mod.rs +178 -187
- package/crates/team-agent/src/transport/tests/wire.rs +561 -525
- package/crates/team-agent/src/transport.rs +12 -17
- package/crates/team-agent/src/transport_factory.rs +29 -14
- package/package.json +4 -4
|
@@ -30,30 +30,102 @@ fn rotation_marker_is_byte_exact() {
|
|
|
30
30
|
#[test]
|
|
31
31
|
fn status_enums_serialize_to_exact_python_strings() {
|
|
32
32
|
let cases: &[(String, &str)] = &[
|
|
33
|
-
(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
33
|
+
(
|
|
34
|
+
serde_json::to_string(&CoordinatorHealthStatus::Missing).unwrap(),
|
|
35
|
+
"\"missing\"",
|
|
36
|
+
),
|
|
37
|
+
(
|
|
38
|
+
serde_json::to_string(&CoordinatorHealthStatus::InvalidPid).unwrap(),
|
|
39
|
+
"\"invalid_pid\"",
|
|
40
|
+
),
|
|
41
|
+
(
|
|
42
|
+
serde_json::to_string(&CoordinatorHealthStatus::Running).unwrap(),
|
|
43
|
+
"\"running\"",
|
|
44
|
+
),
|
|
45
|
+
(
|
|
46
|
+
serde_json::to_string(&CoordinatorHealthStatus::Stale).unwrap(),
|
|
47
|
+
"\"stale\"",
|
|
48
|
+
),
|
|
49
|
+
(
|
|
50
|
+
serde_json::to_string(&StartOutcome::AlreadyRunning).unwrap(),
|
|
51
|
+
"\"already_running\"",
|
|
52
|
+
),
|
|
53
|
+
(
|
|
54
|
+
serde_json::to_string(&StartOutcome::RestartIncompatibleStopFailed).unwrap(),
|
|
55
|
+
"\"restart_incompatible_stop_failed\"",
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
serde_json::to_string(&StartOutcome::SchemaIncompatible).unwrap(),
|
|
59
|
+
"\"schema_incompatible\"",
|
|
60
|
+
),
|
|
61
|
+
(
|
|
62
|
+
serde_json::to_string(&StartOutcome::Started).unwrap(),
|
|
63
|
+
"\"started\"",
|
|
64
|
+
),
|
|
65
|
+
(
|
|
66
|
+
serde_json::to_string(&StartOutcome::StartedAfterRotation).unwrap(),
|
|
67
|
+
"\"started_after_rotation\"",
|
|
68
|
+
),
|
|
69
|
+
(
|
|
70
|
+
serde_json::to_string(&StopOutcome::Missing).unwrap(),
|
|
71
|
+
"\"missing\"",
|
|
72
|
+
),
|
|
73
|
+
(
|
|
74
|
+
serde_json::to_string(&StopOutcome::InvalidPidRemoved).unwrap(),
|
|
75
|
+
"\"invalid_pid_removed\"",
|
|
76
|
+
),
|
|
77
|
+
(
|
|
78
|
+
serde_json::to_string(&StopOutcome::KillFailed).unwrap(),
|
|
79
|
+
"\"kill_failed\"",
|
|
80
|
+
),
|
|
81
|
+
(
|
|
82
|
+
serde_json::to_string(&StopOutcome::Stopped).unwrap(),
|
|
83
|
+
"\"stopped\"",
|
|
84
|
+
),
|
|
85
|
+
(
|
|
86
|
+
serde_json::to_string(&TickStopReason::TmuxSessionMissing).unwrap(),
|
|
87
|
+
"\"tmux_session_missing\"",
|
|
88
|
+
),
|
|
89
|
+
(
|
|
90
|
+
serde_json::to_string(&TickStopReason::PersistenceDegraded).unwrap(),
|
|
91
|
+
"\"persistence_degraded\"",
|
|
92
|
+
),
|
|
93
|
+
(
|
|
94
|
+
serde_json::to_string(&MetadataSource::Boot).unwrap(),
|
|
95
|
+
"\"boot\"",
|
|
96
|
+
),
|
|
97
|
+
(
|
|
98
|
+
serde_json::to_string(&MetadataSource::Start).unwrap(),
|
|
99
|
+
"\"start\"",
|
|
100
|
+
),
|
|
101
|
+
(
|
|
102
|
+
serde_json::to_string(&AbnormalDecision::Skip).unwrap(),
|
|
103
|
+
"\"skip\"",
|
|
104
|
+
),
|
|
105
|
+
(
|
|
106
|
+
serde_json::to_string(&AbnormalDecision::NotifyBlacklist).unwrap(),
|
|
107
|
+
"\"notify_blacklist\"",
|
|
108
|
+
),
|
|
109
|
+
(
|
|
110
|
+
serde_json::to_string(&AbnormalDecision::NotifyDefault).unwrap(),
|
|
111
|
+
"\"notify_default\"",
|
|
112
|
+
),
|
|
113
|
+
(
|
|
114
|
+
serde_json::to_string(&WholeTeamGoneClass::Alive).unwrap(),
|
|
115
|
+
"\"alive\"",
|
|
116
|
+
),
|
|
117
|
+
(
|
|
118
|
+
serde_json::to_string(&WholeTeamGoneClass::CleanShutdown).unwrap(),
|
|
119
|
+
"\"clean_shutdown\"",
|
|
120
|
+
),
|
|
121
|
+
(
|
|
122
|
+
serde_json::to_string(&WholeTeamGoneClass::RestartInProgress).unwrap(),
|
|
123
|
+
"\"restart_in_progress\"",
|
|
124
|
+
),
|
|
125
|
+
(
|
|
126
|
+
serde_json::to_string(&WholeTeamGoneClass::UnexpectedExit).unwrap(),
|
|
127
|
+
"\"unexpected_exit\"",
|
|
128
|
+
),
|
|
57
129
|
];
|
|
58
130
|
for (got, want) in cases {
|
|
59
131
|
assert_eq!(got, want);
|
|
@@ -65,26 +137,64 @@ fn coordinator_event_tags_are_byte_stable() {
|
|
|
65
137
|
// 逐一钉 events.jsonl tag(events.py 稳定契约)。
|
|
66
138
|
let pid = Pid(4242);
|
|
67
139
|
let cases: Vec<(CoordinatorEvent, &str)> = vec![
|
|
68
|
-
(
|
|
69
|
-
|
|
140
|
+
(
|
|
141
|
+
CoordinatorEvent::Boot {
|
|
142
|
+
workspace: "/w".into(),
|
|
143
|
+
once: true,
|
|
144
|
+
},
|
|
145
|
+
"coordinator.boot",
|
|
146
|
+
),
|
|
147
|
+
(
|
|
148
|
+
CoordinatorEvent::Started {
|
|
149
|
+
pid,
|
|
150
|
+
log: "/l".into(),
|
|
151
|
+
},
|
|
152
|
+
"coordinator.started",
|
|
153
|
+
),
|
|
70
154
|
(CoordinatorEvent::Stopped { pid }, "coordinator.stopped"),
|
|
71
155
|
(CoordinatorEvent::Exit { stop: true }, "coordinator.exit"),
|
|
72
|
-
(CoordinatorEvent::SessionMissing { session: "s".into() }, "coordinator.session_missing"),
|
|
73
156
|
(
|
|
74
|
-
CoordinatorEvent::
|
|
157
|
+
CoordinatorEvent::SessionMissing {
|
|
158
|
+
session: "s".into(),
|
|
159
|
+
},
|
|
160
|
+
"coordinator.session_missing",
|
|
161
|
+
),
|
|
162
|
+
(
|
|
163
|
+
CoordinatorEvent::OrphanSelfTerminate {
|
|
164
|
+
initial_ppid: 9,
|
|
165
|
+
current_ppid: 1,
|
|
166
|
+
workspace: "/w".into(),
|
|
167
|
+
},
|
|
75
168
|
"coordinator.orphan_self_terminate",
|
|
76
169
|
),
|
|
77
170
|
(
|
|
78
|
-
CoordinatorEvent::TickError {
|
|
171
|
+
CoordinatorEvent::TickError {
|
|
172
|
+
error: "boom".into(),
|
|
173
|
+
exc_type: "OSError".into(),
|
|
174
|
+
consecutive_failures: 1,
|
|
175
|
+
next_sleep_sec: 5.0,
|
|
176
|
+
},
|
|
79
177
|
"coordinator.tick_error",
|
|
80
178
|
),
|
|
81
179
|
(
|
|
82
|
-
CoordinatorEvent::TickErrorSuppressed {
|
|
180
|
+
CoordinatorEvent::TickErrorSuppressed {
|
|
181
|
+
consecutive_failures: 2,
|
|
182
|
+
next_sleep_sec: 10.0,
|
|
183
|
+
},
|
|
83
184
|
"coordinator.tick_error.suppressed",
|
|
84
185
|
),
|
|
85
|
-
(CoordinatorEvent::TickRecovered { consecutive_failures: 3 }, "coordinator.tick_recovered"),
|
|
86
186
|
(
|
|
87
|
-
CoordinatorEvent::
|
|
187
|
+
CoordinatorEvent::TickRecovered {
|
|
188
|
+
consecutive_failures: 3,
|
|
189
|
+
},
|
|
190
|
+
"coordinator.tick_recovered",
|
|
191
|
+
),
|
|
192
|
+
(
|
|
193
|
+
CoordinatorEvent::RestartIncompatible {
|
|
194
|
+
pid: Some(pid),
|
|
195
|
+
expected_protocol: 2,
|
|
196
|
+
expected_schema: 3,
|
|
197
|
+
},
|
|
88
198
|
"coordinator.restart_incompatible",
|
|
89
199
|
),
|
|
90
200
|
(
|
|
@@ -92,7 +202,10 @@ fn coordinator_event_tags_are_byte_stable() {
|
|
|
92
202
|
"coordinator.restart_incompatible_stop_failed",
|
|
93
203
|
),
|
|
94
204
|
(
|
|
95
|
-
CoordinatorEvent::SchemaIncompatible {
|
|
205
|
+
CoordinatorEvent::SchemaIncompatible {
|
|
206
|
+
table: Some("messages".into()),
|
|
207
|
+
missing_columns: vec!["owner_team_id".into()],
|
|
208
|
+
},
|
|
96
209
|
"coordinator.schema_incompatible",
|
|
97
210
|
),
|
|
98
211
|
(
|
|
@@ -106,17 +219,33 @@ fn coordinator_event_tags_are_byte_stable() {
|
|
|
106
219
|
"idle_takeover.unknown_persistent",
|
|
107
220
|
),
|
|
108
221
|
(
|
|
109
|
-
CoordinatorEvent::AbnormalNotify {
|
|
222
|
+
CoordinatorEvent::AbnormalNotify {
|
|
223
|
+
signature: "sig".into(),
|
|
224
|
+
turn_id: None,
|
|
225
|
+
decision: AbnormalDecision::NotifyDefault,
|
|
226
|
+
},
|
|
110
227
|
"abnormal.notify",
|
|
111
228
|
),
|
|
112
229
|
(
|
|
113
|
-
CoordinatorEvent::AbnormalWholeTeamGone {
|
|
230
|
+
CoordinatorEvent::AbnormalWholeTeamGone {
|
|
231
|
+
classification: WholeTeamGoneClass::UnexpectedExit,
|
|
232
|
+
},
|
|
114
233
|
"abnormal.whole_team_gone",
|
|
115
234
|
),
|
|
116
|
-
(CoordinatorEvent::LeaderNotificationLogPruned { removed: 7 }, "leader_notification.log_pruned"),
|
|
117
|
-
(CoordinatorEvent::LeaderNotificationPruneFailed { error: "io".into() }, "leader_notification.prune_failed"),
|
|
118
235
|
(
|
|
119
|
-
CoordinatorEvent::
|
|
236
|
+
CoordinatorEvent::LeaderNotificationLogPruned { removed: 7 },
|
|
237
|
+
"leader_notification.log_pruned",
|
|
238
|
+
),
|
|
239
|
+
(
|
|
240
|
+
CoordinatorEvent::LeaderNotificationPruneFailed { error: "io".into() },
|
|
241
|
+
"leader_notification.prune_failed",
|
|
242
|
+
),
|
|
243
|
+
(
|
|
244
|
+
CoordinatorEvent::RuntimeStateSaveFailed {
|
|
245
|
+
phase: "tick_end".into(),
|
|
246
|
+
error: "replace".into(),
|
|
247
|
+
exc_type: "OSError".into(),
|
|
248
|
+
},
|
|
120
249
|
"runtime.state.save_failed",
|
|
121
250
|
),
|
|
122
251
|
];
|
|
@@ -157,11 +286,20 @@ fn unknown_persistent_event_provider_none_is_null_not_missing() {
|
|
|
157
286
|
let json = serde_json::to_value(&evt).unwrap();
|
|
158
287
|
assert_eq!(json["event"], "idle_takeover.unknown_persistent");
|
|
159
288
|
assert_eq!(json["node_id"], "w7");
|
|
160
|
-
assert!(
|
|
289
|
+
assert!(
|
|
290
|
+
json.get("provider").is_some(),
|
|
291
|
+
"provider key MUST be present (None→null)"
|
|
292
|
+
);
|
|
161
293
|
assert!(json["provider"].is_null(), "provider None → JSON null");
|
|
162
|
-
assert!(
|
|
294
|
+
assert!(
|
|
295
|
+
json.get("auth_mode").is_some(),
|
|
296
|
+
"auth_mode key MUST be present"
|
|
297
|
+
);
|
|
163
298
|
assert!(json["auth_mode"].is_null(), "auth_mode None → JSON null");
|
|
164
|
-
assert!(
|
|
299
|
+
assert!(
|
|
300
|
+
json.get("rollout_path").is_some(),
|
|
301
|
+
"rollout_path key MUST be present"
|
|
302
|
+
);
|
|
165
303
|
assert!(json["rollout_path"].is_null());
|
|
166
304
|
assert_eq!(json["consecutive_ticks"], 72);
|
|
167
305
|
}
|
|
@@ -12,7 +12,11 @@ use super::*;
|
|
|
12
12
|
fn daemon_ws() -> (WorkspacePath, std::path::PathBuf) {
|
|
13
13
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
14
14
|
static N: AtomicU64 = AtomicU64::new(0);
|
|
15
|
-
let dir = std::env::temp_dir().join(format!(
|
|
15
|
+
let dir = std::env::temp_dir().join(format!(
|
|
16
|
+
"ta-rs-daemon-{}-{}",
|
|
17
|
+
std::process::id(),
|
|
18
|
+
N.fetch_add(1, Ordering::Relaxed)
|
|
19
|
+
));
|
|
16
20
|
std::fs::create_dir_all(crate::model::paths::runtime_dir(&dir)).unwrap();
|
|
17
21
|
let _ = crate::message_store::MessageStore::open(&dir).unwrap(); // create the schema (schema_ok)
|
|
18
22
|
(WorkspacePath::new(dir.clone()), dir)
|
|
@@ -35,8 +39,15 @@ fn coordinator_health_running_with_matching_metadata_is_ok() {
|
|
|
35
39
|
write_coordinator_metadata(&wp, me, MetadataSource::Boot).unwrap();
|
|
36
40
|
std::fs::write(coordinator_pid_path(&wp), me.0.to_string()).unwrap();
|
|
37
41
|
let h = coordinator_health(&wp);
|
|
38
|
-
assert_eq!(
|
|
39
|
-
|
|
42
|
+
assert_eq!(
|
|
43
|
+
h.status,
|
|
44
|
+
CoordinatorHealthStatus::Running,
|
|
45
|
+
"a live pid -> status running"
|
|
46
|
+
);
|
|
47
|
+
assert!(
|
|
48
|
+
h.metadata_ok,
|
|
49
|
+
"pid+protocol+schema all match -> metadata_ok"
|
|
50
|
+
);
|
|
40
51
|
assert!(h.ok, "running ∧ metadata_ok ∧ schema_ok -> healthy");
|
|
41
52
|
}
|
|
42
53
|
|
|
@@ -48,7 +59,11 @@ fn coordinator_health_dead_pid_is_stale_not_ok() {
|
|
|
48
59
|
write_coordinator_metadata(&wp, dead, MetadataSource::Boot).unwrap();
|
|
49
60
|
std::fs::write(coordinator_pid_path(&wp), dead.0.to_string()).unwrap();
|
|
50
61
|
let h = coordinator_health(&wp);
|
|
51
|
-
assert_eq!(
|
|
62
|
+
assert_eq!(
|
|
63
|
+
h.status,
|
|
64
|
+
CoordinatorHealthStatus::Stale,
|
|
65
|
+
"a dead pid -> status stale"
|
|
66
|
+
);
|
|
52
67
|
assert!(!h.ok, "a stale daemon is not healthy");
|
|
53
68
|
}
|
|
54
69
|
|
|
@@ -60,7 +75,11 @@ fn start_coordinator_when_healthy_is_already_running_no_spawn() {
|
|
|
60
75
|
write_coordinator_metadata(&wp, me, MetadataSource::Boot).unwrap();
|
|
61
76
|
std::fs::write(coordinator_pid_path(&wp), me.0.to_string()).unwrap();
|
|
62
77
|
let report = start_coordinator(&wp).expect("start_coordinator");
|
|
63
|
-
assert_eq!(
|
|
78
|
+
assert_eq!(
|
|
79
|
+
report.status,
|
|
80
|
+
StartOutcome::AlreadyRunning,
|
|
81
|
+
"a healthy coordinator -> AlreadyRunning (no spawn)"
|
|
82
|
+
);
|
|
64
83
|
assert!(report.ok);
|
|
65
84
|
assert_eq!(report.pid, Some(me));
|
|
66
85
|
}
|
|
@@ -86,10 +105,22 @@ fn start_coordinator_fresh_workspace_decides_started() {
|
|
|
86
105
|
single-tick testing"]
|
|
87
106
|
fn run_daemon_once_writes_boot_metadata_and_returns_ok() {
|
|
88
107
|
let (wp, dir) = daemon_ws();
|
|
89
|
-
crate::state::persist::save_runtime_state(
|
|
90
|
-
|
|
108
|
+
crate::state::persist::save_runtime_state(
|
|
109
|
+
&dir,
|
|
110
|
+
&serde_json::json!({"session_name": "team-x", "agents": {}}),
|
|
111
|
+
)
|
|
112
|
+
.unwrap();
|
|
113
|
+
let r = run_daemon(DaemonArgs {
|
|
114
|
+
workspace: wp.clone(),
|
|
115
|
+
once: true,
|
|
116
|
+
tick_interval_sec: None,
|
|
117
|
+
team_key: None,
|
|
118
|
+
});
|
|
91
119
|
assert!(r.is_ok(), "run_daemon --once must return Ok; got {r:?}");
|
|
92
|
-
assert!(
|
|
120
|
+
assert!(
|
|
121
|
+
coordinator_meta_path(&wp).exists(),
|
|
122
|
+
"run_daemon must write the coordinator boot metadata"
|
|
123
|
+
);
|
|
93
124
|
}
|
|
94
125
|
|
|
95
126
|
// ═════════════════════════════════════════════════════════════════════════
|
|
@@ -403,8 +434,11 @@ fn tick_genuine_fast_session_miss_still_stops() {
|
|
|
403
434
|
// genuinely gone) => {ok:false, stop:true, reason:tmux_session_missing}. The OTHER side of the
|
|
404
435
|
// fork from the timeout case: a definitive miss MUST still stop the daemon. LOCK (already GREEN):
|
|
405
436
|
// guards the genuine-miss stop from being swallowed when the timeout tolerance is added.
|
|
406
|
-
let (coord, _dir, _seen) =
|
|
407
|
-
|
|
437
|
+
let (coord, _dir, _seen) = coord_over_staged_tmux(
|
|
438
|
+
"team-spine",
|
|
439
|
+
vec![RunnerStep::Exit(false)],
|
|
440
|
+
RunnerStep::Exit(false),
|
|
441
|
+
);
|
|
408
442
|
let report = coord
|
|
409
443
|
.tick()
|
|
410
444
|
.expect("a definitive miss is a typed stop report, not an Err");
|
|
@@ -453,15 +487,21 @@ fn run_daemon_backs_off_on_transient_tick_err_then_recovers_without_exiting() {
|
|
|
453
487
|
let err_idx = tags
|
|
454
488
|
.iter()
|
|
455
489
|
.position(|t| *t == "coordinator.tick_error")
|
|
456
|
-
.unwrap_or_else(||
|
|
490
|
+
.unwrap_or_else(|| {
|
|
491
|
+
panic!("a transient tick Err must log coordinator.tick_error; got {tags:?}")
|
|
492
|
+
});
|
|
457
493
|
let rec_idx = tags
|
|
458
494
|
.iter()
|
|
459
495
|
.position(|t| *t == "coordinator.tick_recovered")
|
|
460
|
-
.unwrap_or_else(||
|
|
496
|
+
.unwrap_or_else(|| {
|
|
497
|
+
panic!("the recovering Ok tick must log coordinator.tick_recovered; got {tags:?}")
|
|
498
|
+
});
|
|
461
499
|
let exit_idx = tags
|
|
462
500
|
.iter()
|
|
463
501
|
.position(|t| *t == "coordinator.exit")
|
|
464
|
-
.unwrap_or_else(||
|
|
502
|
+
.unwrap_or_else(|| {
|
|
503
|
+
panic!("the daemon must log coordinator.exit once it stops; got {tags:?}")
|
|
504
|
+
});
|
|
465
505
|
assert!(
|
|
466
506
|
err_idx < rec_idx,
|
|
467
507
|
"tick_recovered must FOLLOW tick_error (backoff then recover); got {tags:?}"
|
|
@@ -20,17 +20,38 @@ impl Transport for CapturingTransport {
|
|
|
20
20
|
fn kind(&self) -> BackendKind {
|
|
21
21
|
BackendKind::Tmux
|
|
22
22
|
}
|
|
23
|
-
fn spawn_first(
|
|
23
|
+
fn spawn_first(
|
|
24
|
+
&self,
|
|
25
|
+
_s: &SessionName,
|
|
26
|
+
_w: &WindowName,
|
|
27
|
+
_a: &[String],
|
|
28
|
+
_c: &std::path::Path,
|
|
29
|
+
_e: &std::collections::BTreeMap<String, String>,
|
|
30
|
+
) -> Result<SpawnResult, TransportError> {
|
|
24
31
|
unimplemented!("not reached")
|
|
25
32
|
}
|
|
26
|
-
fn spawn_into(
|
|
33
|
+
fn spawn_into(
|
|
34
|
+
&self,
|
|
35
|
+
_s: &SessionName,
|
|
36
|
+
_w: &WindowName,
|
|
37
|
+
_a: &[String],
|
|
38
|
+
_c: &std::path::Path,
|
|
39
|
+
_e: &std::collections::BTreeMap<String, String>,
|
|
40
|
+
) -> Result<SpawnResult, TransportError> {
|
|
27
41
|
unimplemented!("not reached")
|
|
28
42
|
}
|
|
29
|
-
fn inject(
|
|
43
|
+
fn inject(
|
|
44
|
+
&self,
|
|
45
|
+
_t: &Target,
|
|
46
|
+
_p: &InjectPayload,
|
|
47
|
+
_s: Key,
|
|
48
|
+
_b: bool,
|
|
49
|
+
) -> Result<InjectReport, TransportError> {
|
|
30
50
|
Ok(InjectReport {
|
|
31
51
|
stage_reached: crate::transport::InjectStage::Submit,
|
|
32
52
|
inject_verification: crate::transport::InjectVerification::CaptureContainsToken,
|
|
33
|
-
submit_verification:
|
|
53
|
+
submit_verification:
|
|
54
|
+
crate::transport::SubmitVerification::EnterSentWithoutPlaceholderCheck,
|
|
34
55
|
turn_verification: crate::transport::TurnVerification::NotYetObserved,
|
|
35
56
|
attempts: 1,
|
|
36
57
|
submit_diagnostics: None,
|
|
@@ -40,7 +61,10 @@ impl Transport for CapturingTransport {
|
|
|
40
61
|
Ok(())
|
|
41
62
|
}
|
|
42
63
|
fn capture(&self, _t: &Target, range: CaptureRange) -> Result<CapturedText, TransportError> {
|
|
43
|
-
Ok(CapturedText {
|
|
64
|
+
Ok(CapturedText {
|
|
65
|
+
text: self.scrollback.clone(),
|
|
66
|
+
range,
|
|
67
|
+
})
|
|
44
68
|
}
|
|
45
69
|
fn query(&self, _t: &Target, _f: PaneField) -> Result<Option<String>, TransportError> {
|
|
46
70
|
Ok(None)
|
|
@@ -57,7 +81,12 @@ impl Transport for CapturingTransport {
|
|
|
57
81
|
fn list_windows(&self, _s: &SessionName) -> Result<Vec<WindowName>, TransportError> {
|
|
58
82
|
Ok(vec![WindowName::new("w1")])
|
|
59
83
|
}
|
|
60
|
-
fn set_session_env(
|
|
84
|
+
fn set_session_env(
|
|
85
|
+
&self,
|
|
86
|
+
_s: &SessionName,
|
|
87
|
+
_k: &str,
|
|
88
|
+
_v: &str,
|
|
89
|
+
) -> Result<SetEnvOutcome, TransportError> {
|
|
61
90
|
Ok(SetEnvOutcome::Applied)
|
|
62
91
|
}
|
|
63
92
|
fn kill_session(&self, _s: &SessionName) -> Result<(), TransportError> {
|
|
@@ -74,7 +103,10 @@ impl Transport for CapturingTransport {
|
|
|
74
103
|
/// Build a Coordinator over a real seeded workspace (truthy session_name + the given agents map) with
|
|
75
104
|
/// the CapturingTransport staging `scrollback` for every pane. Returns the workspace dir so the test
|
|
76
105
|
/// can load_runtime_state after the tick.
|
|
77
|
-
fn seeded_health_coord(
|
|
106
|
+
fn seeded_health_coord(
|
|
107
|
+
agents: serde_json::Value,
|
|
108
|
+
scrollback: &str,
|
|
109
|
+
) -> (Coordinator, std::path::PathBuf) {
|
|
78
110
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
79
111
|
static N: AtomicU64 = AtomicU64::new(0);
|
|
80
112
|
let dir = std::env::temp_dir().join(format!(
|
|
@@ -93,7 +125,9 @@ fn seeded_health_coord(agents: serde_json::Value, scrollback: &str) -> (Coordina
|
|
|
93
125
|
let coord = Coordinator::for_test(
|
|
94
126
|
ws,
|
|
95
127
|
reg,
|
|
96
|
-
Box::new(CapturingTransport {
|
|
128
|
+
Box::new(CapturingTransport {
|
|
129
|
+
scrollback: scrollback.to_string(),
|
|
130
|
+
}),
|
|
97
131
|
None,
|
|
98
132
|
None,
|
|
99
133
|
);
|
|
@@ -136,11 +170,18 @@ fn spine2_sync_health_classifies_idle_scrollback() {
|
|
|
136
170
|
// NEVER as idle. Today: no activity written.
|
|
137
171
|
#[test]
|
|
138
172
|
fn spine2_sync_health_unknown_scrollback_never_idle() {
|
|
139
|
-
let (coord, dir) = seeded_health_coord(
|
|
173
|
+
let (coord, dir) = seeded_health_coord(
|
|
174
|
+
one_agent("codex"),
|
|
175
|
+
"garbled noise xyz 12345 no recognizable signal",
|
|
176
|
+
);
|
|
140
177
|
coord.tick().expect("tick");
|
|
141
178
|
let status = agent_activity_status(&dir, "w1");
|
|
142
179
|
assert!(status.is_some(), "sync_health must classify the agent (write activity); today the probe writes nothing. got {status:?}");
|
|
143
|
-
assert_ne!(
|
|
180
|
+
assert_ne!(
|
|
181
|
+
status.as_deref(),
|
|
182
|
+
Some("idle"),
|
|
183
|
+
"§11: an UNKNOWN scrollback must NEVER be classified idle"
|
|
184
|
+
);
|
|
144
185
|
}
|
|
145
186
|
|
|
146
187
|
// P0 §11 — a WORKING scrollback classifies the agent but never idle.
|
|
@@ -149,8 +190,15 @@ fn spine2_sync_health_working_scrollback_never_idle() {
|
|
|
149
190
|
let (coord, dir) = seeded_health_coord(one_agent("codex"), "Working (5s · esc to interrupt)");
|
|
150
191
|
coord.tick().expect("tick");
|
|
151
192
|
let status = agent_activity_status(&dir, "w1");
|
|
152
|
-
assert!(
|
|
153
|
-
|
|
193
|
+
assert!(
|
|
194
|
+
status.is_some(),
|
|
195
|
+
"sync_health must classify a working agent; today no activity. got {status:?}"
|
|
196
|
+
);
|
|
197
|
+
assert_ne!(
|
|
198
|
+
status.as_deref(),
|
|
199
|
+
Some("idle"),
|
|
200
|
+
"§11: a WORKING scrollback must not be idle"
|
|
201
|
+
);
|
|
154
202
|
}
|
|
155
203
|
|
|
156
204
|
// P1 — sync_health records last_output_at on a pane delta (so detect_stuck / take-over downstream can
|
|
@@ -172,7 +220,11 @@ fn spine2_sync_health_records_last_output_at() {
|
|
|
172
220
|
fn spine2_capture_missing_captures_session_id_for_missing_agent() {
|
|
173
221
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
174
222
|
static N: AtomicU64 = AtomicU64::new(0);
|
|
175
|
-
let tdir = std::env::temp_dir().join(format!(
|
|
223
|
+
let tdir = std::env::temp_dir().join(format!(
|
|
224
|
+
"ta-rs-health-tx-{}-{}",
|
|
225
|
+
std::process::id(),
|
|
226
|
+
N.fetch_add(1, Ordering::Relaxed)
|
|
227
|
+
));
|
|
176
228
|
std::fs::create_dir_all(&tdir).unwrap();
|
|
177
229
|
// lane-046-capture-gap: Claude no-expected_session_id capture now requires
|
|
178
230
|
// positive_agent_id_match (TEAM_AGENT_ID=<id>) OR agent_path_match. Without
|
|
@@ -195,7 +247,9 @@ fn spine2_capture_missing_captures_session_id_for_missing_agent() {
|
|
|
195
247
|
"capture_missing must capture+persist a session_id for an agent with a discoverable transcript; today it's a probe"
|
|
196
248
|
);
|
|
197
249
|
assert_eq!(
|
|
198
|
-
agent_field(&dir, "w2", "session_id")
|
|
250
|
+
agent_field(&dir, "w2", "session_id")
|
|
251
|
+
.and_then(|v| v.as_str().map(str::to_string))
|
|
252
|
+
.as_deref(),
|
|
199
253
|
Some("existing-sess"),
|
|
200
254
|
"an agent that already has a session_id must be untouched"
|
|
201
255
|
);
|
|
@@ -208,7 +262,9 @@ fn spine2_capture_missing_captures_session_id_for_missing_agent() {
|
|
|
208
262
|
fn spine2_sync_health_working_status_delivers_same_tick() {
|
|
209
263
|
let (coord, dir) = seeded_health_coord(one_agent("codex"), "Working (5s · esc to interrupt)");
|
|
210
264
|
let store = MessageStore::open(&dir).unwrap();
|
|
211
|
-
let mid = store
|
|
265
|
+
let mid = store
|
|
266
|
+
.create_message(Some("t"), "leader", "w1", "hi", None, true, None)
|
|
267
|
+
.unwrap();
|
|
212
268
|
let conn = crate::db::schema::open_db(store.db_path()).unwrap();
|
|
213
269
|
conn.execute(
|
|
214
270
|
"insert into agent_health(owner_team_id, agent_id, status, updated_at)
|
|
@@ -223,7 +279,9 @@ fn spine2_sync_health_working_status_delivers_same_tick() {
|
|
|
223
279
|
coord.tick().expect("tick");
|
|
224
280
|
let events = read_event_log_dir(&dir);
|
|
225
281
|
assert!(
|
|
226
|
-
!events
|
|
282
|
+
!events
|
|
283
|
+
.iter()
|
|
284
|
+
.any(|e| e.get("event").and_then(|v| v.as_str()) == Some("send.deferred_busy")),
|
|
227
285
|
"turn-level WORKING must not trigger lifecycle busy deferral; got {events:?}"
|
|
228
286
|
);
|
|
229
287
|
assert!(
|
|
@@ -250,7 +308,9 @@ fn spine2_sync_health_working_status_delivers_same_tick() {
|
|
|
250
308
|
fn contract_working_worker_stays_alive_and_deliverable_in_real_tick() {
|
|
251
309
|
let (coord, dir) = seeded_health_coord(one_agent("codex"), "Working (5s · esc to interrupt)");
|
|
252
310
|
let store = MessageStore::open(&dir).unwrap();
|
|
253
|
-
let _mid = store
|
|
311
|
+
let _mid = store
|
|
312
|
+
.create_message(Some("t"), "leader", "w1", "hi", None, true, None)
|
|
313
|
+
.unwrap();
|
|
254
314
|
drop(store);
|
|
255
315
|
coord.tick().expect("tick");
|
|
256
316
|
let status = agent_field(&dir, "w1", "status").and_then(|v| v.as_str().map(str::to_string));
|
|
@@ -406,8 +466,7 @@ fn rm039_stat001_jsonl_close_turn_preserves_e47_idle_classification() {
|
|
|
406
466
|
// The pane Tail(40) still shows the live working spinner — the E47
|
|
407
467
|
// stale-working scenario. The JSONL `task_complete` MUST close the
|
|
408
468
|
// turn anyway.
|
|
409
|
-
let (coord, dir) =
|
|
410
|
-
seeded_health_coord(agents, "Working (5s · esc to interrupt)\n• Working\n");
|
|
469
|
+
let (coord, dir) = seeded_health_coord(agents, "Working (5s · esc to interrupt)\n• Working\n");
|
|
411
470
|
coord.tick().expect("tick");
|
|
412
471
|
let status = agent_activity_status(&dir, "stat-worker");
|
|
413
472
|
assert_eq!(
|