@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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
use super::*;
|
|
2
1
|
use super::spine::{message_status, DeliveringTransport};
|
|
2
|
+
use super::*;
|
|
3
3
|
|
|
4
4
|
// ═════════════════════════════════════════════════════════════════════════
|
|
5
5
|
// P0 REGRESSION (root cause pinned) — a per-agent capture/health-check FAILURE must not abort the
|
|
@@ -31,20 +31,42 @@ struct CaptureFailsDeliverTransport {
|
|
|
31
31
|
}
|
|
32
32
|
impl CaptureFailsDeliverTransport {
|
|
33
33
|
fn new() -> Self {
|
|
34
|
-
Self {
|
|
34
|
+
Self {
|
|
35
|
+
inner: DeliveringTransport::new(),
|
|
36
|
+
}
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
impl Transport for CaptureFailsDeliverTransport {
|
|
38
40
|
fn kind(&self) -> BackendKind {
|
|
39
41
|
self.inner.kind()
|
|
40
42
|
}
|
|
41
|
-
fn spawn_first(
|
|
43
|
+
fn spawn_first(
|
|
44
|
+
&self,
|
|
45
|
+
s: &SessionName,
|
|
46
|
+
w: &WindowName,
|
|
47
|
+
a: &[String],
|
|
48
|
+
c: &std::path::Path,
|
|
49
|
+
e: &std::collections::BTreeMap<String, String>,
|
|
50
|
+
) -> Result<SpawnResult, TransportError> {
|
|
42
51
|
self.inner.spawn_first(s, w, a, c, e)
|
|
43
52
|
}
|
|
44
|
-
fn spawn_into(
|
|
53
|
+
fn spawn_into(
|
|
54
|
+
&self,
|
|
55
|
+
s: &SessionName,
|
|
56
|
+
w: &WindowName,
|
|
57
|
+
a: &[String],
|
|
58
|
+
c: &std::path::Path,
|
|
59
|
+
e: &std::collections::BTreeMap<String, String>,
|
|
60
|
+
) -> Result<SpawnResult, TransportError> {
|
|
45
61
|
self.inner.spawn_into(s, w, a, c, e)
|
|
46
62
|
}
|
|
47
|
-
fn inject(
|
|
63
|
+
fn inject(
|
|
64
|
+
&self,
|
|
65
|
+
t: &Target,
|
|
66
|
+
p: &InjectPayload,
|
|
67
|
+
submit: Key,
|
|
68
|
+
bracketed: bool,
|
|
69
|
+
) -> Result<InjectReport, TransportError> {
|
|
48
70
|
self.inner.inject(t, p, submit, bracketed)
|
|
49
71
|
}
|
|
50
72
|
fn send_keys(&self, t: &Target, k: &[Key]) -> Result<(), TransportError> {
|
|
@@ -52,7 +74,9 @@ impl Transport for CaptureFailsDeliverTransport {
|
|
|
52
74
|
}
|
|
53
75
|
fn capture(&self, _t: &Target, _r: CaptureRange) -> Result<CapturedText, TransportError> {
|
|
54
76
|
// the agent's window is gone — capture fails (tmux can't find the target).
|
|
55
|
-
Err(TransportError::TargetNotFound {
|
|
77
|
+
Err(TransportError::TargetNotFound {
|
|
78
|
+
target: "window gone (stopped agent)".to_string(),
|
|
79
|
+
})
|
|
56
80
|
}
|
|
57
81
|
fn query(&self, t: &Target, f: PaneField) -> Result<Option<String>, TransportError> {
|
|
58
82
|
self.inner.query(t, f)
|
|
@@ -69,7 +93,12 @@ impl Transport for CaptureFailsDeliverTransport {
|
|
|
69
93
|
fn list_windows(&self, s: &SessionName) -> Result<Vec<WindowName>, TransportError> {
|
|
70
94
|
self.inner.list_windows(s)
|
|
71
95
|
}
|
|
72
|
-
fn set_session_env(
|
|
96
|
+
fn set_session_env(
|
|
97
|
+
&self,
|
|
98
|
+
s: &SessionName,
|
|
99
|
+
k: &str,
|
|
100
|
+
v: &str,
|
|
101
|
+
) -> Result<SetEnvOutcome, TransportError> {
|
|
73
102
|
self.inner.set_session_env(s, k, v)
|
|
74
103
|
}
|
|
75
104
|
fn kill_session(&self, s: &SessionName) -> Result<(), TransportError> {
|
|
@@ -87,7 +116,10 @@ fn tick_swallows_capture_failure_and_still_delivers_to_other_agent() {
|
|
|
87
116
|
let dir = std::env::temp_dir().join(format!(
|
|
88
117
|
"team-agent-coord-stopreg-{}-{}",
|
|
89
118
|
std::process::id(),
|
|
90
|
-
std::time::SystemTime::now()
|
|
119
|
+
std::time::SystemTime::now()
|
|
120
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
121
|
+
.unwrap()
|
|
122
|
+
.as_nanos()
|
|
91
123
|
));
|
|
92
124
|
std::fs::create_dir_all(&dir).unwrap();
|
|
93
125
|
// w1 STOPPED (window gone -> capture fails), w2 ACTIVE — w2 keeps the tmux session alive.
|
|
@@ -104,14 +136,32 @@ fn tick_swallows_capture_failure_and_still_delivers_to_other_agent() {
|
|
|
104
136
|
.unwrap();
|
|
105
137
|
let store = MessageStore::open(&dir).unwrap();
|
|
106
138
|
let mid = store
|
|
107
|
-
.create_message(
|
|
139
|
+
.create_message(
|
|
140
|
+
Some("task-1"),
|
|
141
|
+
"leader",
|
|
142
|
+
"w2",
|
|
143
|
+
"after stop",
|
|
144
|
+
None,
|
|
145
|
+
true,
|
|
146
|
+
None,
|
|
147
|
+
)
|
|
108
148
|
.unwrap();
|
|
109
149
|
drop(store);
|
|
110
|
-
assert_eq!(
|
|
150
|
+
assert_eq!(
|
|
151
|
+
message_status(&dir, &mid),
|
|
152
|
+
"accepted",
|
|
153
|
+
"precondition: a fresh message is 'accepted'"
|
|
154
|
+
);
|
|
111
155
|
let ws = WorkspacePath::new(dir.clone());
|
|
112
156
|
let reg: Box<dyn ProviderRegistry> = Box::new(MockRegistry::new(&[], &[]));
|
|
113
157
|
// capture() ERRORS (window gone) but inject delivers + session present.
|
|
114
|
-
let coord = Coordinator::for_test(
|
|
158
|
+
let coord = Coordinator::for_test(
|
|
159
|
+
ws,
|
|
160
|
+
reg,
|
|
161
|
+
Box::new(CaptureFailsDeliverTransport::new()),
|
|
162
|
+
None,
|
|
163
|
+
None,
|
|
164
|
+
);
|
|
115
165
|
let result = coord.tick();
|
|
116
166
|
// (a) a per-agent capture failure must NOT abort the whole tick.
|
|
117
167
|
assert!(
|
|
@@ -68,10 +68,16 @@ struct MapMarkerStore {
|
|
|
68
68
|
}
|
|
69
69
|
impl MapMarkerStore {
|
|
70
70
|
fn ok() -> Self {
|
|
71
|
-
Self {
|
|
71
|
+
Self {
|
|
72
|
+
markers: Default::default(),
|
|
73
|
+
fail: false,
|
|
74
|
+
}
|
|
72
75
|
}
|
|
73
76
|
fn failing() -> Self {
|
|
74
|
-
Self {
|
|
77
|
+
Self {
|
|
78
|
+
markers: Default::default(),
|
|
79
|
+
fail: true,
|
|
80
|
+
}
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
83
|
impl MarkerStore for MapMarkerStore {
|
|
@@ -159,19 +165,11 @@ impl Transport for MockTransport {
|
|
|
159
165
|
self.record("send_keys");
|
|
160
166
|
self.inner.send_keys(t, keys)
|
|
161
167
|
}
|
|
162
|
-
fn capture(
|
|
163
|
-
&self,
|
|
164
|
-
t: &Target,
|
|
165
|
-
range: CaptureRange,
|
|
166
|
-
) -> Result<CapturedText, TransportError> {
|
|
168
|
+
fn capture(&self, t: &Target, range: CaptureRange) -> Result<CapturedText, TransportError> {
|
|
167
169
|
self.record("capture");
|
|
168
170
|
self.inner.capture(t, range)
|
|
169
171
|
}
|
|
170
|
-
fn query(
|
|
171
|
-
&self,
|
|
172
|
-
t: &Target,
|
|
173
|
-
f: PaneField,
|
|
174
|
-
) -> Result<Option<String>, TransportError> {
|
|
172
|
+
fn query(&self, t: &Target, f: PaneField) -> Result<Option<String>, TransportError> {
|
|
175
173
|
self.record("query");
|
|
176
174
|
self.inner.query(t, f)
|
|
177
175
|
}
|
|
@@ -187,10 +185,7 @@ impl Transport for MockTransport {
|
|
|
187
185
|
self.record("has_session");
|
|
188
186
|
self.inner.has_session(s)
|
|
189
187
|
}
|
|
190
|
-
fn list_windows(
|
|
191
|
-
&self,
|
|
192
|
-
s: &SessionName,
|
|
193
|
-
) -> Result<Vec<WindowName>, TransportError> {
|
|
188
|
+
fn list_windows(&self, s: &SessionName) -> Result<Vec<WindowName>, TransportError> {
|
|
194
189
|
self.record("list_windows");
|
|
195
190
|
self.inner.list_windows(s)
|
|
196
191
|
}
|
|
@@ -211,10 +206,7 @@ impl Transport for MockTransport {
|
|
|
211
206
|
self.record("kill_window");
|
|
212
207
|
self.inner.kill_window(t)
|
|
213
208
|
}
|
|
214
|
-
fn attach_session(
|
|
215
|
-
&self,
|
|
216
|
-
s: &SessionName,
|
|
217
|
-
) -> Result<AttachOutcome, TransportError> {
|
|
209
|
+
fn attach_session(&self, s: &SessionName) -> Result<AttachOutcome, TransportError> {
|
|
218
210
|
self.record("attach_session");
|
|
219
211
|
self.inner.attach_session(s)
|
|
220
212
|
}
|
|
@@ -229,7 +221,10 @@ fn coord_for_test(
|
|
|
229
221
|
session_present: bool,
|
|
230
222
|
save_hook: Option<SaveHook>,
|
|
231
223
|
recorder: Option<OrderRecorder>,
|
|
232
|
-
) -> (
|
|
224
|
+
) -> (
|
|
225
|
+
Coordinator,
|
|
226
|
+
std::sync::Arc<std::sync::Mutex<Vec<&'static str>>>,
|
|
227
|
+
) {
|
|
233
228
|
let dir = std::env::temp_dir().join(format!(
|
|
234
229
|
"team-agent-coord-tick-{}-{}",
|
|
235
230
|
std::process::id(),
|
|
@@ -243,13 +238,7 @@ fn coord_for_test(
|
|
|
243
238
|
let reg: Box<dyn ProviderRegistry> = Box::new(MockRegistry::new(&[], &[]));
|
|
244
239
|
let transport = MockTransport::new(session_present);
|
|
245
240
|
let calls = std::sync::Arc::clone(&transport.calls);
|
|
246
|
-
let coord = Coordinator::for_test(
|
|
247
|
-
ws,
|
|
248
|
-
reg,
|
|
249
|
-
Box::new(transport),
|
|
250
|
-
save_hook,
|
|
251
|
-
recorder,
|
|
252
|
-
);
|
|
241
|
+
let coord = Coordinator::for_test(ws, reg, Box::new(transport), save_hook, recorder);
|
|
253
242
|
(coord, calls)
|
|
254
243
|
}
|
|
255
244
|
|
|
@@ -261,7 +250,10 @@ fn coord_for_test(
|
|
|
261
250
|
fn coord_for_test_with_session(
|
|
262
251
|
session_present: bool,
|
|
263
252
|
session_name: &str,
|
|
264
|
-
) -> (
|
|
253
|
+
) -> (
|
|
254
|
+
Coordinator,
|
|
255
|
+
std::sync::Arc<std::sync::Mutex<Vec<&'static str>>>,
|
|
256
|
+
) {
|
|
265
257
|
let dir = std::env::temp_dir().join(format!(
|
|
266
258
|
"team-agent-coord-tick-sess-{}-{}",
|
|
267
259
|
std::process::id(),
|
|
@@ -296,21 +288,23 @@ fn failing_save_hook() -> SaveHook {
|
|
|
296
288
|
fn read_event_log_dir(dir: &std::path::Path) -> Vec<serde_json::Value> {
|
|
297
289
|
let path = crate::model::paths::logs_dir(dir).join("events.jsonl");
|
|
298
290
|
match std::fs::read_to_string(&path) {
|
|
299
|
-
Ok(text) => text
|
|
291
|
+
Ok(text) => text
|
|
292
|
+
.lines()
|
|
293
|
+
.filter_map(|l| serde_json::from_str(l).ok())
|
|
294
|
+
.collect(),
|
|
300
295
|
Err(_) => Vec::new(),
|
|
301
296
|
}
|
|
302
297
|
}
|
|
303
298
|
|
|
304
|
-
|
|
305
|
-
mod basics;
|
|
299
|
+
mod a0_lostupdate;
|
|
306
300
|
mod abnormal;
|
|
307
|
-
mod
|
|
308
|
-
mod
|
|
309
|
-
mod spine;
|
|
310
|
-
mod health_sync;
|
|
311
|
-
mod takeover;
|
|
301
|
+
mod api_error_recovery;
|
|
302
|
+
mod basics;
|
|
312
303
|
mod daemon;
|
|
313
304
|
mod energy;
|
|
305
|
+
mod health_sync;
|
|
314
306
|
mod main_preserved;
|
|
315
|
-
mod
|
|
316
|
-
mod
|
|
307
|
+
mod spine;
|
|
308
|
+
mod takeover;
|
|
309
|
+
mod tick_core;
|
|
310
|
+
mod watch;
|
|
@@ -18,7 +18,9 @@ pub(super) struct DeliveringTransport {
|
|
|
18
18
|
}
|
|
19
19
|
impl DeliveringTransport {
|
|
20
20
|
pub(super) fn new() -> Self {
|
|
21
|
-
Self {
|
|
21
|
+
Self {
|
|
22
|
+
inner: MockTransport::new(true),
|
|
23
|
+
}
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
impl Transport for DeliveringTransport {
|
|
@@ -56,7 +58,8 @@ impl Transport for DeliveringTransport {
|
|
|
56
58
|
Ok(InjectReport {
|
|
57
59
|
stage_reached: crate::transport::InjectStage::Submit,
|
|
58
60
|
inject_verification: crate::transport::InjectVerification::CaptureContainsToken,
|
|
59
|
-
submit_verification:
|
|
61
|
+
submit_verification:
|
|
62
|
+
crate::transport::SubmitVerification::EnterSentWithoutPlaceholderCheck,
|
|
60
63
|
turn_verification: crate::transport::TurnVerification::NotYetObserved,
|
|
61
64
|
attempts: 1,
|
|
62
65
|
submit_diagnostics: None,
|
|
@@ -83,7 +86,12 @@ impl Transport for DeliveringTransport {
|
|
|
83
86
|
fn list_windows(&self, s: &SessionName) -> Result<Vec<WindowName>, TransportError> {
|
|
84
87
|
self.inner.list_windows(s)
|
|
85
88
|
}
|
|
86
|
-
fn set_session_env(
|
|
89
|
+
fn set_session_env(
|
|
90
|
+
&self,
|
|
91
|
+
s: &SessionName,
|
|
92
|
+
k: &str,
|
|
93
|
+
v: &str,
|
|
94
|
+
) -> Result<SetEnvOutcome, TransportError> {
|
|
87
95
|
self.inner.set_session_env(s, k, v)
|
|
88
96
|
}
|
|
89
97
|
fn kill_session(&self, s: &SessionName) -> Result<(), TransportError> {
|
|
@@ -146,8 +154,12 @@ fn seed_due_health_ping(dir: &std::path::Path) -> i64 {
|
|
|
146
154
|
fn scheduled_status(dir: &std::path::Path, id: i64) -> String {
|
|
147
155
|
let store = MessageStore::open(dir).unwrap();
|
|
148
156
|
let conn = crate::db::schema::open_db(store.db_path()).unwrap();
|
|
149
|
-
conn.query_row(
|
|
150
|
-
|
|
157
|
+
conn.query_row(
|
|
158
|
+
"select status from scheduled_events where id = ?1",
|
|
159
|
+
[id],
|
|
160
|
+
|r| r.get::<_, String>(0),
|
|
161
|
+
)
|
|
162
|
+
.unwrap()
|
|
151
163
|
}
|
|
152
164
|
|
|
153
165
|
pub(super) fn message_status(dir: &std::path::Path, message_id: &str) -> String {
|
|
@@ -167,7 +179,11 @@ pub(super) fn message_status(dir: &std::path::Path, message_id: &str) -> String
|
|
|
167
179
|
fn spine_tick_fires_due_scheduled_event_and_marks_db_done() {
|
|
168
180
|
let (coord, dir) = seeded_spine_coord(Box::new(MockTransport::new(true)), None);
|
|
169
181
|
let id = seed_due_health_ping(&dir);
|
|
170
|
-
assert_eq!(
|
|
182
|
+
assert_eq!(
|
|
183
|
+
scheduled_status(&dir, id),
|
|
184
|
+
"pending",
|
|
185
|
+
"precondition: the seeded row starts pending"
|
|
186
|
+
);
|
|
171
187
|
|
|
172
188
|
let report = coord.tick().expect("tick returns a typed report");
|
|
173
189
|
|
|
@@ -189,14 +205,23 @@ fn spine_tick_fires_due_scheduled_event_and_marks_db_done() {
|
|
|
189
205
|
// mutation — AFTER the real obligation side-effects, not after no-op probes.
|
|
190
206
|
#[test]
|
|
191
207
|
fn spine_tick_save_failure_still_persists_real_scheduled_mutation() {
|
|
192
|
-
let (coord, dir) = seeded_spine_coord(
|
|
208
|
+
let (coord, dir) = seeded_spine_coord(
|
|
209
|
+
Box::new(MockTransport::new(true)),
|
|
210
|
+
Some(failing_save_hook()),
|
|
211
|
+
);
|
|
193
212
|
let id = seed_due_health_ping(&dir);
|
|
194
213
|
|
|
195
|
-
let report = coord
|
|
214
|
+
let report = coord
|
|
215
|
+
.tick()
|
|
216
|
+
.expect("a degraded tick is Ok(TickReport), not Err");
|
|
196
217
|
|
|
197
218
|
// degraded report shape (bug-084: save failure → ok=false / persisted=false, NOT Err).
|
|
198
219
|
assert!(!report.ok, "save failure → ok=false");
|
|
199
|
-
assert_eq!(
|
|
220
|
+
assert_eq!(
|
|
221
|
+
report.persisted,
|
|
222
|
+
Some(false),
|
|
223
|
+
"save failure → persisted=Some(false)"
|
|
224
|
+
);
|
|
200
225
|
assert_eq!(report.reason, Some(TickStopReason::PersistenceDegraded));
|
|
201
226
|
// the REAL db side-effect happened BEFORE the (failed) save.
|
|
202
227
|
assert_eq!(
|
|
@@ -221,10 +246,22 @@ fn spine_tick_delivers_pending_message_drives_real_db_or_report() {
|
|
|
221
246
|
let (coord, dir) = seeded_spine_coord(Box::new(DeliveringTransport::new()), None);
|
|
222
247
|
let store = MessageStore::open(&dir).unwrap();
|
|
223
248
|
let mid = store
|
|
224
|
-
.create_message(
|
|
249
|
+
.create_message(
|
|
250
|
+
Some("task-1"),
|
|
251
|
+
"leader",
|
|
252
|
+
"w1",
|
|
253
|
+
"do the thing",
|
|
254
|
+
None,
|
|
255
|
+
true,
|
|
256
|
+
None,
|
|
257
|
+
)
|
|
225
258
|
.unwrap();
|
|
226
259
|
drop(store);
|
|
227
|
-
assert_eq!(
|
|
260
|
+
assert_eq!(
|
|
261
|
+
message_status(&dir, &mid),
|
|
262
|
+
"accepted",
|
|
263
|
+
"precondition: a fresh message is 'accepted'"
|
|
264
|
+
);
|
|
228
265
|
|
|
229
266
|
let report = coord.tick().expect("tick returns a typed report");
|
|
230
267
|
|
|
@@ -251,7 +288,10 @@ fn spine_tick_session_missing_emits_event() {
|
|
|
251
288
|
// tmux gate fire → the tick stops. The gate must ALSO emit coordinator.session_missing.
|
|
252
289
|
let (coord, dir) = seeded_spine_coord(Box::new(MockTransport::new(false)), None);
|
|
253
290
|
let report = coord.tick().expect("tick returns a typed report");
|
|
254
|
-
assert!(
|
|
291
|
+
assert!(
|
|
292
|
+
report.stop,
|
|
293
|
+
"precondition: a missing session stops the tick"
|
|
294
|
+
);
|
|
255
295
|
let events = read_event_log_dir(&dir);
|
|
256
296
|
assert!(
|
|
257
297
|
events
|
|
@@ -21,18 +21,40 @@ impl Transport for CountingCaptureTransport {
|
|
|
21
21
|
fn kind(&self) -> BackendKind {
|
|
22
22
|
BackendKind::Tmux
|
|
23
23
|
}
|
|
24
|
-
fn spawn_first(
|
|
24
|
+
fn spawn_first(
|
|
25
|
+
&self,
|
|
26
|
+
_s: &SessionName,
|
|
27
|
+
_w: &WindowName,
|
|
28
|
+
_a: &[String],
|
|
29
|
+
_c: &std::path::Path,
|
|
30
|
+
_e: &std::collections::BTreeMap<String, String>,
|
|
31
|
+
) -> Result<SpawnResult, TransportError> {
|
|
25
32
|
unimplemented!("not reached")
|
|
26
33
|
}
|
|
27
|
-
fn spawn_into(
|
|
34
|
+
fn spawn_into(
|
|
35
|
+
&self,
|
|
36
|
+
_s: &SessionName,
|
|
37
|
+
_w: &WindowName,
|
|
38
|
+
_a: &[String],
|
|
39
|
+
_c: &std::path::Path,
|
|
40
|
+
_e: &std::collections::BTreeMap<String, String>,
|
|
41
|
+
) -> Result<SpawnResult, TransportError> {
|
|
28
42
|
unimplemented!("not reached")
|
|
29
43
|
}
|
|
30
|
-
fn inject(
|
|
31
|
-
self
|
|
44
|
+
fn inject(
|
|
45
|
+
&self,
|
|
46
|
+
_t: &Target,
|
|
47
|
+
_p: &InjectPayload,
|
|
48
|
+
_s: Key,
|
|
49
|
+
_b: bool,
|
|
50
|
+
) -> Result<InjectReport, TransportError> {
|
|
51
|
+
self.injects
|
|
52
|
+
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
|
32
53
|
Ok(InjectReport {
|
|
33
54
|
stage_reached: crate::transport::InjectStage::Submit,
|
|
34
55
|
inject_verification: crate::transport::InjectVerification::CaptureContainsToken,
|
|
35
|
-
submit_verification:
|
|
56
|
+
submit_verification:
|
|
57
|
+
crate::transport::SubmitVerification::EnterSentWithoutPlaceholderCheck,
|
|
36
58
|
turn_verification: crate::transport::TurnVerification::NotYetObserved,
|
|
37
59
|
attempts: 1,
|
|
38
60
|
submit_diagnostics: None,
|
|
@@ -42,7 +64,10 @@ impl Transport for CountingCaptureTransport {
|
|
|
42
64
|
Ok(())
|
|
43
65
|
}
|
|
44
66
|
fn capture(&self, _t: &Target, range: CaptureRange) -> Result<CapturedText, TransportError> {
|
|
45
|
-
Ok(CapturedText {
|
|
67
|
+
Ok(CapturedText {
|
|
68
|
+
text: self.scrollback.clone(),
|
|
69
|
+
range,
|
|
70
|
+
})
|
|
46
71
|
}
|
|
47
72
|
fn query(&self, _t: &Target, _f: PaneField) -> Result<Option<String>, TransportError> {
|
|
48
73
|
Ok(None)
|
|
@@ -59,7 +84,12 @@ impl Transport for CountingCaptureTransport {
|
|
|
59
84
|
fn list_windows(&self, _s: &SessionName) -> Result<Vec<WindowName>, TransportError> {
|
|
60
85
|
Ok(Vec::new())
|
|
61
86
|
}
|
|
62
|
-
fn set_session_env(
|
|
87
|
+
fn set_session_env(
|
|
88
|
+
&self,
|
|
89
|
+
_s: &SessionName,
|
|
90
|
+
_k: &str,
|
|
91
|
+
_v: &str,
|
|
92
|
+
) -> Result<SetEnvOutcome, TransportError> {
|
|
63
93
|
Ok(SetEnvOutcome::Applied)
|
|
64
94
|
}
|
|
65
95
|
fn kill_session(&self, _s: &SessionName) -> Result<(), TransportError> {
|
|
@@ -76,17 +106,30 @@ impl Transport for CountingCaptureTransport {
|
|
|
76
106
|
fn slice2b_dir() -> std::path::PathBuf {
|
|
77
107
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
78
108
|
static N: AtomicU64 = AtomicU64::new(0);
|
|
79
|
-
let dir = std::env::temp_dir().join(format!(
|
|
109
|
+
let dir = std::env::temp_dir().join(format!(
|
|
110
|
+
"ta-rs-2b-{}-{}",
|
|
111
|
+
std::process::id(),
|
|
112
|
+
N.fetch_add(1, Ordering::Relaxed)
|
|
113
|
+
));
|
|
80
114
|
std::fs::create_dir_all(&dir).unwrap();
|
|
81
115
|
dir
|
|
82
116
|
}
|
|
83
117
|
|
|
84
118
|
/// Build a coordinator over a workspace seeded with `state` (verbatim) + a counting transport.
|
|
85
|
-
fn takeover_coord(
|
|
119
|
+
fn takeover_coord(
|
|
120
|
+
state: serde_json::Value,
|
|
121
|
+
) -> (
|
|
122
|
+
Coordinator,
|
|
123
|
+
std::path::PathBuf,
|
|
124
|
+
std::sync::Arc<std::sync::atomic::AtomicUsize>,
|
|
125
|
+
) {
|
|
86
126
|
let dir = slice2b_dir();
|
|
87
127
|
crate::state::persist::save_runtime_state(&dir, &state).unwrap();
|
|
88
128
|
let injects = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
|
89
|
-
let transport = CountingCaptureTransport {
|
|
129
|
+
let transport = CountingCaptureTransport {
|
|
130
|
+
scrollback: String::new(),
|
|
131
|
+
injects: std::sync::Arc::clone(&injects),
|
|
132
|
+
};
|
|
90
133
|
let ws = WorkspacePath::new(dir.clone());
|
|
91
134
|
let reg: Box<dyn ProviderRegistry> = Box::new(MockRegistry::new(&[], &[]));
|
|
92
135
|
let coord = Coordinator::for_test(ws, reg, Box::new(transport), None, None);
|
|
@@ -94,7 +137,13 @@ fn takeover_coord(state: serde_json::Value) -> (Coordinator, std::path::PathBuf,
|
|
|
94
137
|
}
|
|
95
138
|
|
|
96
139
|
/// Seed an agent_health row (real DB) — what sync_health writes and detect_stuck/detect_deadlocks read.
|
|
97
|
-
fn seed_agent_health(
|
|
140
|
+
fn seed_agent_health(
|
|
141
|
+
dir: &std::path::Path,
|
|
142
|
+
agent_id: &str,
|
|
143
|
+
status: &str,
|
|
144
|
+
last_output_at: &str,
|
|
145
|
+
owner_team_id: Option<&str>,
|
|
146
|
+
) {
|
|
98
147
|
let store = MessageStore::open(dir).unwrap();
|
|
99
148
|
let conn = crate::db::schema::open_db(store.db_path()).unwrap();
|
|
100
149
|
conn.execute(
|
|
@@ -106,7 +155,9 @@ fn seed_agent_health(dir: &std::path::Path, agent_id: &str, status: &str, last_o
|
|
|
106
155
|
}
|
|
107
156
|
|
|
108
157
|
fn has_event(dir: &std::path::Path, name: &str) -> bool {
|
|
109
|
-
read_event_log_dir(dir)
|
|
158
|
+
read_event_log_dir(dir)
|
|
159
|
+
.iter()
|
|
160
|
+
.any(|e| e.get("event").and_then(|v| v.as_str()) == Some(name))
|
|
110
161
|
}
|
|
111
162
|
|
|
112
163
|
// #236 nag_removal (N35) — detect_stuck no longer synthesized in coordinator tick.
|
|
@@ -123,7 +174,9 @@ fn slice2b_detect_stuck_no_longer_synthesized_by_framework_n35() {
|
|
|
123
174
|
}));
|
|
124
175
|
seed_agent_health(&dir, "w1", "RUNNING", &stale, Some("team-h"));
|
|
125
176
|
let store = MessageStore::open(&dir).unwrap();
|
|
126
|
-
let _ = store
|
|
177
|
+
let _ = store
|
|
178
|
+
.create_message(Some("t"), "leader", "w1", "do work", None, true, None)
|
|
179
|
+
.unwrap();
|
|
127
180
|
drop(store);
|
|
128
181
|
|
|
129
182
|
let report = coord.tick().expect("tick");
|
|
@@ -148,7 +201,9 @@ fn slice2b_detect_deadlocks_no_longer_synthesized_by_framework_n35() {
|
|
|
148
201
|
}));
|
|
149
202
|
seed_agent_health(&dir, "w1", "IDLE", &now, Some("team-h"));
|
|
150
203
|
let store = MessageStore::open(&dir).unwrap();
|
|
151
|
-
let _ = store
|
|
204
|
+
let _ = store
|
|
205
|
+
.create_message(Some("t"), "w2", "w1", "are you done?", None, true, None)
|
|
206
|
+
.unwrap();
|
|
152
207
|
drop(store);
|
|
153
208
|
|
|
154
209
|
let report = coord.tick().expect("tick");
|
|
@@ -199,7 +254,10 @@ fn slice2b_takeover_unarmed_idle_worker_is_never_pinged() {
|
|
|
199
254
|
// NO coordinator.idle_takeover_monitor → not armed → reason not_armed_no_worker_turn.
|
|
200
255
|
}));
|
|
201
256
|
coord.tick().expect("tick");
|
|
202
|
-
assert!(
|
|
257
|
+
assert!(
|
|
258
|
+
!has_event(&dir, "idle_takeover.reminder"),
|
|
259
|
+
"a NOT-armed worker must never be pinged (§121)"
|
|
260
|
+
);
|
|
203
261
|
assert_eq!(
|
|
204
262
|
injects.load(std::sync::atomic::Ordering::Relaxed),
|
|
205
263
|
0,
|