@team-agent/installer 0.5.41 → 0.5.43
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 +27 -7
- 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 +47 -52
- 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/coordinator/tick.rs +19 -1
- 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 +52 -19
- package/crates/team-agent/src/leader/helpers.rs +7 -1
- package/crates/team-agent/src/leader/lease.rs +195 -82
- package/crates/team-agent/src/leader/owner_bind.rs +55 -22
- 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/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 +6 -1
- 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/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.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 +21 -22
- 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/repository.rs +27 -14
- 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 +27 -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
|
@@ -19,18 +19,34 @@ fn p2_trust_refuses_subdirectory_and_sibling_of_workspace() {
|
|
|
19
19
|
|
|
20
20
|
let subdir = format!("Allow Codex to write to {canon}/subproject ?");
|
|
21
21
|
let s = attempt_trust_auto_answer(
|
|
22
|
-
&canonical,
|
|
22
|
+
&canonical,
|
|
23
|
+
&t,
|
|
24
|
+
Some(&pane),
|
|
25
|
+
&subdir,
|
|
26
|
+
&PaneWidthQuery::Ok { pane_width: 200 },
|
|
27
|
+
&log,
|
|
23
28
|
)
|
|
24
29
|
.unwrap();
|
|
25
|
-
assert!(
|
|
30
|
+
assert!(
|
|
31
|
+
!s.answered,
|
|
32
|
+
"a SUBDIRECTORY of the workspace must NOT auto-answer trust"
|
|
33
|
+
);
|
|
26
34
|
assert_eq!(s.reason, "workspace_dir_mismatch");
|
|
27
35
|
|
|
28
36
|
let sibling = format!("Allow Codex to write to {canon}-backup ?");
|
|
29
37
|
let sib = attempt_trust_auto_answer(
|
|
30
|
-
&canonical,
|
|
38
|
+
&canonical,
|
|
39
|
+
&t,
|
|
40
|
+
Some(&pane),
|
|
41
|
+
&sibling,
|
|
42
|
+
&PaneWidthQuery::Ok { pane_width: 200 },
|
|
43
|
+
&log,
|
|
31
44
|
)
|
|
32
45
|
.unwrap();
|
|
33
|
-
assert!(
|
|
46
|
+
assert!(
|
|
47
|
+
!sib.answered,
|
|
48
|
+
"a SIBLING (<ws>-backup) must NOT auto-answer trust"
|
|
49
|
+
);
|
|
34
50
|
assert_eq!(sib.reason, "workspace_dir_mismatch");
|
|
35
51
|
}
|
|
36
52
|
|
|
@@ -106,14 +122,12 @@ fn p2_classify_activity_reads_command_stale_and_prompts() {
|
|
|
106
122
|
assert_eq!((e.status, e.confidence), (ActivityStatus::Uncertain, 0.5));
|
|
107
123
|
// (5b) E47 structural Working: a real codex live spinner in the bottom
|
|
108
124
|
// active region must still classify Working 0.9.
|
|
109
|
-
let e_struct =
|
|
110
|
-
&st,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
);
|
|
116
|
-
assert_eq!((e_struct.status, e_struct.confidence), (ActivityStatus::Working, 0.9));
|
|
125
|
+
let e_struct =
|
|
126
|
+
classify_agent_activity(&st, "• Working (12s · esc to interrupt)", false, None, None);
|
|
127
|
+
assert_eq!(
|
|
128
|
+
(e_struct.status, e_struct.confidence),
|
|
129
|
+
(ActivityStatus::Working, 0.9)
|
|
130
|
+
);
|
|
117
131
|
}
|
|
118
132
|
|
|
119
133
|
// P1 — scheduler dispatch must be exhaustive: an unknown kind surfaces an error, not a
|
|
@@ -160,17 +174,38 @@ impl Transport for DeliverOkTransport {
|
|
|
160
174
|
fn kind(&self) -> BackendKind {
|
|
161
175
|
BackendKind::Tmux
|
|
162
176
|
}
|
|
163
|
-
fn spawn_first(
|
|
177
|
+
fn spawn_first(
|
|
178
|
+
&self,
|
|
179
|
+
_s: &SessionName,
|
|
180
|
+
_w: &WindowName,
|
|
181
|
+
_a: &[String],
|
|
182
|
+
_c: &Path,
|
|
183
|
+
_e: &BTreeMap<String, String>,
|
|
184
|
+
) -> Result<SpawnResult, TransportError> {
|
|
164
185
|
unimplemented!("not reached in delivery")
|
|
165
186
|
}
|
|
166
|
-
fn spawn_into(
|
|
187
|
+
fn spawn_into(
|
|
188
|
+
&self,
|
|
189
|
+
_s: &SessionName,
|
|
190
|
+
_w: &WindowName,
|
|
191
|
+
_a: &[String],
|
|
192
|
+
_c: &Path,
|
|
193
|
+
_e: &BTreeMap<String, String>,
|
|
194
|
+
) -> Result<SpawnResult, TransportError> {
|
|
167
195
|
unimplemented!("not reached in delivery")
|
|
168
196
|
}
|
|
169
|
-
fn inject(
|
|
197
|
+
fn inject(
|
|
198
|
+
&self,
|
|
199
|
+
_t: &Target,
|
|
200
|
+
_p: &InjectPayload,
|
|
201
|
+
_s: Key,
|
|
202
|
+
_b: bool,
|
|
203
|
+
) -> Result<InjectReport, TransportError> {
|
|
170
204
|
Ok(InjectReport {
|
|
171
205
|
stage_reached: crate::transport::InjectStage::Submit,
|
|
172
206
|
inject_verification: crate::transport::InjectVerification::CaptureContainsToken,
|
|
173
|
-
submit_verification:
|
|
207
|
+
submit_verification:
|
|
208
|
+
crate::transport::SubmitVerification::EnterSentWithoutPlaceholderCheck,
|
|
174
209
|
turn_verification: crate::transport::TurnVerification::NotYetObserved,
|
|
175
210
|
attempts: 1,
|
|
176
211
|
submit_diagnostics: None,
|
|
@@ -180,7 +215,10 @@ impl Transport for DeliverOkTransport {
|
|
|
180
215
|
Ok(())
|
|
181
216
|
}
|
|
182
217
|
fn capture(&self, _t: &Target, range: CaptureRange) -> Result<CapturedText, TransportError> {
|
|
183
|
-
Ok(CapturedText {
|
|
218
|
+
Ok(CapturedText {
|
|
219
|
+
text: String::new(),
|
|
220
|
+
range,
|
|
221
|
+
})
|
|
184
222
|
}
|
|
185
223
|
fn query(&self, _t: &Target, _f: PaneField) -> Result<Option<String>, TransportError> {
|
|
186
224
|
Ok(None)
|
|
@@ -197,7 +235,12 @@ impl Transport for DeliverOkTransport {
|
|
|
197
235
|
fn list_windows(&self, _s: &SessionName) -> Result<Vec<WindowName>, TransportError> {
|
|
198
236
|
Ok(Vec::new())
|
|
199
237
|
}
|
|
200
|
-
fn set_session_env(
|
|
238
|
+
fn set_session_env(
|
|
239
|
+
&self,
|
|
240
|
+
_s: &SessionName,
|
|
241
|
+
_k: &str,
|
|
242
|
+
_v: &str,
|
|
243
|
+
) -> Result<SetEnvOutcome, TransportError> {
|
|
201
244
|
Ok(SetEnvOutcome::Applied)
|
|
202
245
|
}
|
|
203
246
|
fn kill_session(&self, _s: &SessionName) -> Result<(), TransportError> {
|
|
@@ -213,15 +256,29 @@ impl Transport for DeliverOkTransport {
|
|
|
213
256
|
|
|
214
257
|
fn set_message_status(store: &MessageStore, message_id: &str, status: &str) {
|
|
215
258
|
let conn = crate::db::schema::open_db(store.db_path()).unwrap();
|
|
216
|
-
conn.execute(
|
|
259
|
+
conn.execute(
|
|
260
|
+
"update messages set status = ?2 where message_id = ?1",
|
|
261
|
+
sql_params![message_id, status],
|
|
262
|
+
)
|
|
263
|
+
.unwrap();
|
|
217
264
|
}
|
|
218
265
|
fn message_status_of(store: &MessageStore, message_id: &str) -> String {
|
|
219
266
|
let conn = crate::db::schema::open_db(store.db_path()).unwrap();
|
|
220
|
-
conn.query_row(
|
|
267
|
+
conn.query_row(
|
|
268
|
+
"select status from messages where message_id = ?1",
|
|
269
|
+
sql_params![message_id],
|
|
270
|
+
|r| r.get::<_, String>(0),
|
|
271
|
+
)
|
|
272
|
+
.unwrap()
|
|
221
273
|
}
|
|
222
274
|
fn scheduled_status_of(store: &MessageStore, id: i64) -> String {
|
|
223
275
|
let conn = crate::db::schema::open_db(store.db_path()).unwrap();
|
|
224
|
-
conn.query_row(
|
|
276
|
+
conn.query_row(
|
|
277
|
+
"select status from scheduled_events where id = ?1",
|
|
278
|
+
[id],
|
|
279
|
+
|r| r.get::<_, String>(0),
|
|
280
|
+
)
|
|
281
|
+
.unwrap()
|
|
225
282
|
}
|
|
226
283
|
fn seed_agent_health(store: &MessageStore, agent_id: &str, status: &str) {
|
|
227
284
|
let conn = crate::db::schema::open_db(store.db_path()).unwrap();
|
|
@@ -245,7 +302,10 @@ fn seed_event_due(store: &MessageStore, kind: &str, due_at: &str, payload: &str)
|
|
|
245
302
|
fn read_event_log(ws: &Path) -> Vec<serde_json::Value> {
|
|
246
303
|
let path = crate::model::paths::logs_dir(ws).join("events.jsonl");
|
|
247
304
|
match std::fs::read_to_string(&path) {
|
|
248
|
-
Ok(text) => text
|
|
305
|
+
Ok(text) => text
|
|
306
|
+
.lines()
|
|
307
|
+
.filter_map(|l| serde_json::from_str(l).ok())
|
|
308
|
+
.collect(),
|
|
249
309
|
Err(_) => Vec::new(),
|
|
250
310
|
}
|
|
251
311
|
}
|
|
@@ -257,17 +317,31 @@ fn spine_delivery_skips_queued_statuses() {
|
|
|
257
317
|
let ws = tmp_ws("deliver-queued");
|
|
258
318
|
let store = store_for(&ws);
|
|
259
319
|
let log = EventLog::new(&ws);
|
|
260
|
-
let _acc = store
|
|
261
|
-
|
|
262
|
-
|
|
320
|
+
let _acc = store
|
|
321
|
+
.create_message(Some("t"), "leader", "w1", "hi", None, true, None)
|
|
322
|
+
.unwrap();
|
|
323
|
+
let qidle = store
|
|
324
|
+
.create_message(Some("t"), "leader", "w1", "hi", None, true, None)
|
|
325
|
+
.unwrap();
|
|
326
|
+
let qstart = store
|
|
327
|
+
.create_message(Some("t"), "leader", "w1", "hi", None, true, None)
|
|
328
|
+
.unwrap();
|
|
263
329
|
set_message_status(&store, &qidle, "queued_until_idle");
|
|
264
330
|
set_message_status(&store, &qstart, "queued_until_start");
|
|
265
331
|
let state = serde_json::json!({"agents": {"w1": {}}});
|
|
266
332
|
|
|
267
333
|
let _ = deliver_pending_messages(&ws, &state, &DeliverOkTransport, &log).unwrap();
|
|
268
334
|
|
|
269
|
-
assert_eq!(
|
|
270
|
-
|
|
335
|
+
assert_eq!(
|
|
336
|
+
message_status_of(&store, &qidle),
|
|
337
|
+
"queued_until_idle",
|
|
338
|
+
"queued_until_idle must NOT be claimed/injected/marked by deliver_pending"
|
|
339
|
+
);
|
|
340
|
+
assert_eq!(
|
|
341
|
+
message_status_of(&store, &qstart),
|
|
342
|
+
"queued_until_start",
|
|
343
|
+
"queued_until_start must NOT be touched by deliver_pending"
|
|
344
|
+
);
|
|
271
345
|
}
|
|
272
346
|
|
|
273
347
|
// #2 (CONTRACT, corrected) — busy recipient = LIFECYCLE status=="busy" → emit ONE send.deferred_busy
|
|
@@ -280,21 +354,40 @@ fn spine_delivery_busy_recipient_defers_with_event() {
|
|
|
280
354
|
let ws = tmp_ws("deliver-busy");
|
|
281
355
|
let store = store_for(&ws);
|
|
282
356
|
let log = EventLog::new(&ws);
|
|
283
|
-
let mid = store
|
|
357
|
+
let mid = store
|
|
358
|
+
.create_message(Some("t"), "leader", "w1", "hi", None, true, None)
|
|
359
|
+
.unwrap();
|
|
284
360
|
let state = serde_json::json!({"agents": {"w1": {"status": "busy"}}});
|
|
285
361
|
|
|
286
362
|
let delivered = deliver_pending_messages(&ws, &state, &DeliverOkTransport, &log).unwrap();
|
|
287
363
|
|
|
288
|
-
assert!(
|
|
289
|
-
|
|
364
|
+
assert!(
|
|
365
|
+
!delivered.contains(&mid),
|
|
366
|
+
"a busy recipient's message must NOT be delivered"
|
|
367
|
+
);
|
|
368
|
+
assert_eq!(
|
|
369
|
+
message_status_of(&store, &mid),
|
|
370
|
+
"accepted",
|
|
371
|
+
"the row must stay 'accepted' (no-drop, left for a later tick)"
|
|
372
|
+
);
|
|
290
373
|
let events = read_event_log(&ws);
|
|
291
374
|
let deferred: Vec<_> = events
|
|
292
375
|
.iter()
|
|
293
376
|
.filter(|e| e.get("event").and_then(|v| v.as_str()) == Some("send.deferred_busy"))
|
|
294
377
|
.collect();
|
|
295
|
-
assert_eq!(
|
|
296
|
-
|
|
297
|
-
|
|
378
|
+
assert_eq!(
|
|
379
|
+
deferred.len(),
|
|
380
|
+
1,
|
|
381
|
+
"exactly one send.deferred_busy event expected; got {events:?}"
|
|
382
|
+
);
|
|
383
|
+
assert_eq!(
|
|
384
|
+
deferred[0].get("recipient").and_then(|v| v.as_str()),
|
|
385
|
+
Some("w1")
|
|
386
|
+
);
|
|
387
|
+
assert_eq!(
|
|
388
|
+
deferred[0].get("reason").and_then(|v| v.as_str()),
|
|
389
|
+
Some("recipient_busy")
|
|
390
|
+
);
|
|
298
391
|
}
|
|
299
392
|
|
|
300
393
|
// CONTRACT (shared-root, real-machine-driven; golden = correct-behavior baseline): an ALIVE worker
|
|
@@ -309,7 +402,9 @@ fn contract_alive_worker_with_working_health_is_deliverable_not_deferred() {
|
|
|
309
402
|
let ws = tmp_ws("deliver-alive-working");
|
|
310
403
|
let store = store_for(&ws);
|
|
311
404
|
let log = EventLog::new(&ws);
|
|
312
|
-
let mid = store
|
|
405
|
+
let mid = store
|
|
406
|
+
.create_message(Some("t"), "leader", "w1", "hi", None, true, None)
|
|
407
|
+
.unwrap();
|
|
313
408
|
seed_agent_health(&store, "w1", "WORKING"); // turn-level state — must NOT gate delivery
|
|
314
409
|
let state = serde_json::json!({"agents": {"w1": {"status": "running"}}}); // lifecycle: alive
|
|
315
410
|
|
|
@@ -360,7 +455,12 @@ fn spine_scheduler_poison_event_does_not_halt_batch() {
|
|
|
360
455
|
let store = store_for(&ws);
|
|
361
456
|
let log = EventLog::new(&ws);
|
|
362
457
|
// earlier due_at → selected first; malformed JSON payload errors at from_str.
|
|
363
|
-
let poison = seed_event_due(
|
|
458
|
+
let poison = seed_event_due(
|
|
459
|
+
&store,
|
|
460
|
+
"send",
|
|
461
|
+
"2000-01-01T00:00:00+00:00",
|
|
462
|
+
"{not valid json",
|
|
463
|
+
);
|
|
364
464
|
// later due_at → selected after poison; healthy.
|
|
365
465
|
let healthy = seed_event_due(&store, "health_ping", "2000-01-02T00:00:00+00:00", "{}");
|
|
366
466
|
|
|
@@ -381,7 +481,9 @@ fn spine_scheduler_poison_event_does_not_halt_batch() {
|
|
|
381
481
|
// failure is loud: scheduler.event_failed event emitted for the poison.
|
|
382
482
|
let events = read_event_log(&ws);
|
|
383
483
|
assert!(
|
|
384
|
-
events
|
|
484
|
+
events
|
|
485
|
+
.iter()
|
|
486
|
+
.any(|e| e.get("event").and_then(|v| v.as_str()) == Some("scheduler.event_failed")),
|
|
385
487
|
"U1 #5: a poison event must emit scheduler.event_failed (failure must be loud, not silent)"
|
|
386
488
|
);
|
|
387
489
|
}
|
|
@@ -423,7 +525,9 @@ fn spine_delivery_injects_resolvable_target_not_bare_agent_pane() {
|
|
|
423
525
|
let ws = tmp_ws("deliver-target");
|
|
424
526
|
let store = store_for(&ws);
|
|
425
527
|
let log = EventLog::new(&ws);
|
|
426
|
-
let _mid = store
|
|
528
|
+
let _mid = store
|
|
529
|
+
.create_message(Some("t"), "leader", "w1", "hi", None, true, None)
|
|
530
|
+
.unwrap();
|
|
427
531
|
// in-team, non-busy agent -> deliver proceeds; the session-qualified resolution uses session_name + window.
|
|
428
532
|
let state = serde_json::json!({
|
|
429
533
|
"session_name": "team-x",
|
|
@@ -434,7 +538,11 @@ fn spine_delivery_injects_resolvable_target_not_bare_agent_pane() {
|
|
|
434
538
|
let _ = deliver_pending_messages(&ws, &state, &transport, &log).unwrap();
|
|
435
539
|
|
|
436
540
|
let recorded = transport.inject_targets();
|
|
437
|
-
assert_eq!(
|
|
541
|
+
assert_eq!(
|
|
542
|
+
recorded.len(),
|
|
543
|
+
1,
|
|
544
|
+
"deliver must inject the one pending message exactly once; got {recorded:?}"
|
|
545
|
+
);
|
|
438
546
|
let target = recorded[0].clone();
|
|
439
547
|
// THE BUG (rt-host-a #3): deliver builds Target::Pane(PaneId(message.recipient)) — the agent id as a
|
|
440
548
|
// BARE pane, which a non-attached coordinator can't resolve ("can't find pane: w1").
|
|
@@ -473,7 +581,9 @@ fn spine_delivery_injects_rendered_protocol_block_not_raw_content() {
|
|
|
473
581
|
let ws = tmp_ws("deliver-render");
|
|
474
582
|
let store = store_for(&ws);
|
|
475
583
|
let log = EventLog::new(&ws);
|
|
476
|
-
let mid = store
|
|
584
|
+
let mid = store
|
|
585
|
+
.create_message(Some("t1"), "leader", "w1", "do the thing", None, true, None)
|
|
586
|
+
.unwrap();
|
|
477
587
|
let state = serde_json::json!({
|
|
478
588
|
"session_name": "team-x",
|
|
479
589
|
"agents": {"w1": {"status": "idle", "window": "w1"}}
|
|
@@ -483,7 +593,11 @@ fn spine_delivery_injects_rendered_protocol_block_not_raw_content() {
|
|
|
483
593
|
let _ = deliver_pending_messages(&ws, &state, &transport, &log).unwrap();
|
|
484
594
|
|
|
485
595
|
let recorded = transport.inject_payloads();
|
|
486
|
-
assert_eq!(
|
|
596
|
+
assert_eq!(
|
|
597
|
+
recorded.len(),
|
|
598
|
+
1,
|
|
599
|
+
"deliver must inject the one pending message exactly once; got {recorded:?}"
|
|
600
|
+
);
|
|
487
601
|
let payload = recorded[0].clone();
|
|
488
602
|
// THE BUG (rt-host-a #4): deliver injects the RAW content -> the worker never reports (it only builds
|
|
489
603
|
// a result_envelope on the rendered block with the token).
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
use super::*;
|
|
35
35
|
use crate::messaging::deliver_pending_message;
|
|
36
36
|
use crate::transport::test_support::OfflineTransport;
|
|
37
|
-
use crate::transport::{
|
|
37
|
+
use crate::transport::{PaneId, PaneLiveness, SessionName, WindowName};
|
|
38
38
|
|
|
39
39
|
fn pane_info_w(pane_id: &str, session: &str, window: &str) -> PaneInfo {
|
|
40
40
|
PaneInfo {
|
|
@@ -97,8 +97,7 @@ fn u1_a_leader_pane_drift_falls_back_to_live_session_window_not_failed() {
|
|
|
97
97
|
.with_liveness("%old", PaneLiveness::Dead)
|
|
98
98
|
.with_liveness("%new", PaneLiveness::Live);
|
|
99
99
|
|
|
100
|
-
let out = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state)
|
|
101
|
-
.unwrap();
|
|
100
|
+
let out = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state).unwrap();
|
|
102
101
|
|
|
103
102
|
assert!(
|
|
104
103
|
out.reason != Some(DeliveryRefusal::LeaderNotAttached),
|
|
@@ -108,14 +107,15 @@ fn u1_a_leader_pane_drift_falls_back_to_live_session_window_not_failed() {
|
|
|
108
107
|
out.message_status.0
|
|
109
108
|
);
|
|
110
109
|
assert_ne!(
|
|
111
|
-
out.message_status.0,
|
|
112
|
-
"failed",
|
|
110
|
+
out.message_status.0, "failed",
|
|
113
111
|
"U1-A: drift must not be terminal; got status={}",
|
|
114
112
|
out.message_status.0
|
|
115
113
|
);
|
|
116
114
|
let inject_targets = transport.inject_targets();
|
|
117
115
|
assert!(
|
|
118
|
-
inject_targets
|
|
116
|
+
inject_targets
|
|
117
|
+
.iter()
|
|
118
|
+
.any(|t| matches!(t, Target::Pane(p) if p.as_str() == "%new")),
|
|
119
119
|
"U1-A: fallback chain must inject into the LIVE pane (%new), not the stale \
|
|
120
120
|
%old; injected targets={inject_targets:?}"
|
|
121
121
|
);
|
|
@@ -147,8 +147,7 @@ fn u1_a_leader_truly_not_attached_still_leader_not_attached_not_synthetic_target
|
|
|
147
147
|
.with_targets(vec![])
|
|
148
148
|
.with_session_present(false);
|
|
149
149
|
|
|
150
|
-
let out = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state)
|
|
151
|
-
.unwrap();
|
|
150
|
+
let out = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state).unwrap();
|
|
152
151
|
|
|
153
152
|
assert_eq!(
|
|
154
153
|
out.reason,
|
|
@@ -205,22 +204,19 @@ fn u1_b_list_targets_err_defers_does_not_reuse_unvalidated_cached_pane() {
|
|
|
205
204
|
.with_session_present(true)
|
|
206
205
|
.with_default_liveness(PaneLiveness::Live); // cached looks alive in isolation
|
|
207
206
|
|
|
208
|
-
let out = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state)
|
|
209
|
-
.unwrap();
|
|
207
|
+
let out = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state).unwrap();
|
|
210
208
|
|
|
211
209
|
assert!(
|
|
212
210
|
!out.ok,
|
|
213
211
|
"U1-B: jitter must not declare delivered (false-green guard)"
|
|
214
212
|
);
|
|
215
213
|
assert_ne!(
|
|
216
|
-
out.message_status.0,
|
|
217
|
-
"delivered",
|
|
214
|
+
out.message_status.0, "delivered",
|
|
218
215
|
"U1-B: jitter must not mark delivered; got status={}",
|
|
219
216
|
out.message_status.0
|
|
220
217
|
);
|
|
221
218
|
assert_ne!(
|
|
222
|
-
out.message_status.0,
|
|
223
|
-
"failed",
|
|
219
|
+
out.message_status.0, "failed",
|
|
224
220
|
"U1-B: jitter must not terminal-fail this tick (defer); got status={}",
|
|
225
221
|
out.message_status.0
|
|
226
222
|
);
|
|
@@ -233,8 +229,14 @@ fn u1_b_list_targets_err_defers_does_not_reuse_unvalidated_cached_pane() {
|
|
|
233
229
|
let events = log.tail(0).unwrap();
|
|
234
230
|
assert!(
|
|
235
231
|
events.iter().any(|event| {
|
|
236
|
-
let reason = event
|
|
237
|
-
|
|
232
|
+
let reason = event
|
|
233
|
+
.get("reason")
|
|
234
|
+
.and_then(serde_json::Value::as_str)
|
|
235
|
+
.unwrap_or("");
|
|
236
|
+
let verif = event
|
|
237
|
+
.get("verification")
|
|
238
|
+
.and_then(serde_json::Value::as_str)
|
|
239
|
+
.unwrap_or("");
|
|
238
240
|
reason.contains("list_targets")
|
|
239
241
|
|| verif.contains("list_targets")
|
|
240
242
|
|| reason.contains("server_jitter")
|
|
@@ -280,8 +282,7 @@ fn u1_b_list_targets_ok_empty_is_authoritative_not_deferred() {
|
|
|
280
282
|
.with_session_present(true)
|
|
281
283
|
.with_default_liveness(PaneLiveness::Unknown);
|
|
282
284
|
|
|
283
|
-
let out = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state)
|
|
284
|
-
.unwrap();
|
|
285
|
+
let out = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state).unwrap();
|
|
285
286
|
|
|
286
287
|
// We don't assert ok=true (the fake provider may or may not roundtrip);
|
|
287
288
|
// we assert the pre-existing inject ATTEMPT happened (i.e. NOT deferred
|
|
@@ -353,8 +354,7 @@ fn u1_c_delivery_peek_uses_tail_eighty_not_full() {
|
|
|
353
354
|
.with_default_liveness(PaneLiveness::Live)
|
|
354
355
|
.with_capture_for_pane("%cdx", "OpenAI Codex\ncodex>\n");
|
|
355
356
|
|
|
356
|
-
let _ = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state)
|
|
357
|
-
.unwrap();
|
|
357
|
+
let _ = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state).unwrap();
|
|
358
358
|
|
|
359
359
|
let ranges = transport.capture_ranges();
|
|
360
360
|
assert!(
|
|
@@ -363,7 +363,9 @@ fn u1_c_delivery_peek_uses_tail_eighty_not_full() {
|
|
|
363
363
|
startup_prompts; got no capture calls"
|
|
364
364
|
);
|
|
365
365
|
assert!(
|
|
366
|
-
ranges
|
|
366
|
+
ranges
|
|
367
|
+
.iter()
|
|
368
|
+
.any(|r| matches!(r, CaptureRange::Tail(n) if *n == 80)),
|
|
367
369
|
"U1-C: delivery peek site must request Tail(80), not Full. ranges={ranges:?}. \
|
|
368
370
|
Full was the pre-wave-2 default and caused scrolled-off answered-trust \
|
|
369
371
|
residue to be classified as actionable, parking idle codex panes forever."
|
|
@@ -415,16 +417,13 @@ Do you trust the contents of this directory?
|
|
|
415
417
|
.with_default_liveness(PaneLiveness::Live)
|
|
416
418
|
.with_capture_for_pane("%cdx", live_trust);
|
|
417
419
|
|
|
418
|
-
let out = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state)
|
|
419
|
-
.unwrap();
|
|
420
|
+
let out = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state).unwrap();
|
|
420
421
|
|
|
421
422
|
assert_eq!(
|
|
422
|
-
out.message_status.0,
|
|
423
|
-
"queued_until_trust",
|
|
423
|
+
out.message_status.0, "queued_until_trust",
|
|
424
424
|
"U1-C regression guard: a live trust modal in Tail(80) must still park as \
|
|
425
425
|
queued_until_trust; got status={} reason={:?}",
|
|
426
|
-
out.message_status.0,
|
|
427
|
-
out.reason
|
|
426
|
+
out.message_status.0, out.reason
|
|
428
427
|
);
|
|
429
428
|
}
|
|
430
429
|
|
|
@@ -553,8 +552,7 @@ fn u1_a_projected_team_state_with_null_session_still_finds_drifted_leader_pane()
|
|
|
553
552
|
.with_liveness("%old", PaneLiveness::Dead)
|
|
554
553
|
.with_liveness("%new", PaneLiveness::Live);
|
|
555
554
|
|
|
556
|
-
let out = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state)
|
|
557
|
-
.unwrap();
|
|
555
|
+
let out = deliver_pending_message(&ws, &store, &transport, &message_id, &log, &state).unwrap();
|
|
558
556
|
|
|
559
557
|
assert!(
|
|
560
558
|
out.reason != Some(DeliveryRefusal::LeaderNotAttached),
|
|
@@ -568,15 +566,16 @@ fn u1_a_projected_team_state_with_null_session_still_finds_drifted_leader_pane()
|
|
|
568
566
|
out.message_status.0
|
|
569
567
|
);
|
|
570
568
|
assert_ne!(
|
|
571
|
-
out.message_status.0,
|
|
572
|
-
"failed",
|
|
569
|
+
out.message_status.0, "failed",
|
|
573
570
|
"U1-A projection-null escape: drift via projected state must not be \
|
|
574
571
|
terminal; got status={}",
|
|
575
572
|
out.message_status.0
|
|
576
573
|
);
|
|
577
574
|
let inject_targets = transport.inject_targets();
|
|
578
575
|
assert!(
|
|
579
|
-
inject_targets
|
|
576
|
+
inject_targets
|
|
577
|
+
.iter()
|
|
578
|
+
.any(|t| matches!(t, Target::Pane(p) if p.as_str() == "%new")),
|
|
580
579
|
"U1-A projection-null escape: fallback chain must inject into the LIVE \
|
|
581
580
|
pane (%new) reachable via session+window probe; injected={inject_targets:?}"
|
|
582
581
|
);
|
|
@@ -135,8 +135,31 @@ fn path_tokens(text: &str) -> Vec<String> {
|
|
|
135
135
|
fn token_trim_char(c: char) -> bool {
|
|
136
136
|
matches!(
|
|
137
137
|
c,
|
|
138
|
-
'?' | '!'
|
|
139
|
-
| '
|
|
138
|
+
'?' | '!'
|
|
139
|
+
| ','
|
|
140
|
+
| ';'
|
|
141
|
+
| ':'
|
|
142
|
+
| '"'
|
|
143
|
+
| '\''
|
|
144
|
+
| '`'
|
|
145
|
+
| '['
|
|
146
|
+
| ']'
|
|
147
|
+
| '('
|
|
148
|
+
| ')'
|
|
149
|
+
| '{'
|
|
150
|
+
| '}'
|
|
151
|
+
| '<'
|
|
152
|
+
| '>'
|
|
153
|
+
| '│'
|
|
154
|
+
| '┃'
|
|
155
|
+
| '┆'
|
|
156
|
+
| '┊'
|
|
157
|
+
| '┌'
|
|
158
|
+
| '┐'
|
|
159
|
+
| '└'
|
|
160
|
+
| '┘'
|
|
161
|
+
| '─'
|
|
162
|
+
| '═'
|
|
140
163
|
)
|
|
141
164
|
}
|
|
142
165
|
|
|
@@ -29,7 +29,10 @@ pub fn notify_result_watchers(
|
|
|
29
29
|
let conn = crate::db::schema::open_db(store.db_path())?;
|
|
30
30
|
let result_task = result.get("task_id").and_then(|v| v.as_str());
|
|
31
31
|
let result_agent = result.get("agent_id").and_then(|v| v.as_str());
|
|
32
|
-
let result_id = result
|
|
32
|
+
let result_id = result
|
|
33
|
+
.get("result_id")
|
|
34
|
+
.and_then(|v| v.as_str())
|
|
35
|
+
.map(ToString::to_string);
|
|
33
36
|
let mut matched: Vec<&serde_json::Value> = watchers
|
|
34
37
|
.iter()
|
|
35
38
|
.filter(|w| watcher_matches(w, result_task, result_agent))
|
|
@@ -145,7 +148,13 @@ fn deliver_primary_watcher(
|
|
|
145
148
|
.get("message_id")
|
|
146
149
|
.and_then(|v| v.as_str())
|
|
147
150
|
.map(ToString::to_string);
|
|
148
|
-
mark_watcher_notified(
|
|
151
|
+
mark_watcher_notified(
|
|
152
|
+
conn,
|
|
153
|
+
event_log,
|
|
154
|
+
watcher_id,
|
|
155
|
+
result_id,
|
|
156
|
+
message_id.as_deref(),
|
|
157
|
+
)?;
|
|
149
158
|
return Ok(WatcherNotice {
|
|
150
159
|
watcher_id: watcher_id.to_string(),
|
|
151
160
|
result_id: Some(result_id.to_string()),
|
|
@@ -171,7 +180,10 @@ fn deliver_primary_watcher(
|
|
|
171
180
|
let content = format_result_watcher_notification(result);
|
|
172
181
|
let message_id = store.create_message(
|
|
173
182
|
result_task,
|
|
174
|
-
watcher
|
|
183
|
+
watcher
|
|
184
|
+
.get("leader_id")
|
|
185
|
+
.and_then(|v| v.as_str())
|
|
186
|
+
.unwrap_or("team-agent"),
|
|
175
187
|
"leader",
|
|
176
188
|
&content,
|
|
177
189
|
None,
|
|
@@ -596,7 +608,9 @@ pub fn delivered_result_message(
|
|
|
596
608
|
for row in rows.into_iter().rev() {
|
|
597
609
|
let (message_id, content) = row;
|
|
598
610
|
if result_id_from_text(&content).as_deref() == Some(result_id) {
|
|
599
|
-
return Ok(Some(
|
|
611
|
+
return Ok(Some(
|
|
612
|
+
serde_json::json!({"message_id": message_id, "content": content}),
|
|
613
|
+
));
|
|
600
614
|
}
|
|
601
615
|
}
|
|
602
616
|
Ok(None)
|
|
@@ -624,11 +638,25 @@ pub fn result_id_from_text(content: &str) -> Option<String> {
|
|
|
624
638
|
/// `format_result_watcher_notification` (`result_delivery.py:521`):拼 watcher 通知文案 +
|
|
625
639
|
/// `Result id: <id>` 行。**格式必须字节级稳定** (golden fixture,与 [`result_id_from_text`] 对偶)。
|
|
626
640
|
pub fn format_result_watcher_notification(result: &serde_json::Value) -> String {
|
|
627
|
-
let task_id = result
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
let
|
|
641
|
+
let task_id = result
|
|
642
|
+
.get("task_id")
|
|
643
|
+
.and_then(|v| v.as_str())
|
|
644
|
+
.unwrap_or("unknown task");
|
|
645
|
+
let agent_id = result
|
|
646
|
+
.get("agent_id")
|
|
647
|
+
.and_then(|v| v.as_str())
|
|
648
|
+
.unwrap_or("unknown agent");
|
|
649
|
+
let status = result
|
|
650
|
+
.get("status")
|
|
651
|
+
.and_then(|v| v.as_str())
|
|
652
|
+
.unwrap_or("unknown");
|
|
653
|
+
let summary = result
|
|
654
|
+
.get("summary")
|
|
655
|
+
.and_then(|v| v.as_str())
|
|
656
|
+
.unwrap_or("completed");
|
|
657
|
+
let mut lines = vec![format!(
|
|
658
|
+
"Task {task_id} reported {status} from {agent_id}: {summary}"
|
|
659
|
+
)];
|
|
632
660
|
if let Some(tests) = result.get("tests").and_then(|v| v.as_array()) {
|
|
633
661
|
let rendered: Vec<String> = tests
|
|
634
662
|
.iter()
|