@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
|
@@ -74,7 +74,9 @@ pub fn leader_identity_context(
|
|
|
74
74
|
}
|
|
75
75
|
if let Some(state_uuid) = state
|
|
76
76
|
.and_then(|s| get_path_str(s, &["team_owner", "leader_session_uuid"]))
|
|
77
|
-
.or_else(||
|
|
77
|
+
.or_else(|| {
|
|
78
|
+
state.and_then(|s| get_path_str(s, &["leader_receiver", "leader_session_uuid"]))
|
|
79
|
+
})
|
|
78
80
|
{
|
|
79
81
|
return Ok(LeaderIdentity {
|
|
80
82
|
leader_session_uuid: serde_json::from_value(Value::String(state_uuid))?,
|
|
@@ -144,7 +146,13 @@ pub fn bind_owner_from_caller_pane(
|
|
|
144
146
|
let os_user = std::env::var("USER")
|
|
145
147
|
.or_else(|_| std::env::var("USERNAME"))
|
|
146
148
|
.unwrap_or_default();
|
|
147
|
-
let identity = family_a_identity(
|
|
149
|
+
let identity = family_a_identity(
|
|
150
|
+
workspace,
|
|
151
|
+
team_id,
|
|
152
|
+
override_uuid,
|
|
153
|
+
&machine_fingerprint,
|
|
154
|
+
&os_user,
|
|
155
|
+
)?;
|
|
148
156
|
let owner = TeamOwner {
|
|
149
157
|
pane_id: PaneId::new(pane.clone()),
|
|
150
158
|
provider,
|
|
@@ -281,19 +289,23 @@ fn tmux_pane_info(workspace: &Path, pane: &str) -> Option<PaneInfo> {
|
|
|
281
289
|
.ok()?
|
|
282
290
|
.into_iter()
|
|
283
291
|
.find(|info| info.pane_id == target)
|
|
284
|
-
.or_else(||
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
292
|
+
.or_else(|| {
|
|
293
|
+
tmux_pane_current_command(workspace, pane)
|
|
294
|
+
.ok()
|
|
295
|
+
.map(|command| PaneInfo {
|
|
296
|
+
pane_id: target,
|
|
297
|
+
session: SessionName::new(""),
|
|
298
|
+
window_index: None,
|
|
299
|
+
window_name: None,
|
|
300
|
+
pane_index: None,
|
|
301
|
+
tty: None,
|
|
302
|
+
current_command: (!command.is_empty()).then_some(command),
|
|
303
|
+
current_path: None,
|
|
304
|
+
active: true,
|
|
305
|
+
pane_pid: None,
|
|
306
|
+
leader_env: Default::default(),
|
|
307
|
+
})
|
|
308
|
+
})
|
|
297
309
|
}
|
|
298
310
|
|
|
299
311
|
// NOTE: `derive_leader_session_uuid`(`leader_binding.py:146`)已由
|
|
@@ -308,16 +320,31 @@ mod e11_provider_bind_tests {
|
|
|
308
320
|
// E11 层2:copilot leader 命令必须绑成 Provider::Copilot(此前缺臂 → _ => Codex 误绑)。
|
|
309
321
|
#[test]
|
|
310
322
|
fn copilot_command_binds_copilot_not_codex() {
|
|
311
|
-
assert_eq!(
|
|
312
|
-
|
|
323
|
+
assert_eq!(
|
|
324
|
+
super::super::attribute_command_provider("copilot --banner -C /ws"),
|
|
325
|
+
Some(Provider::Copilot)
|
|
326
|
+
);
|
|
327
|
+
assert_eq!(
|
|
328
|
+
super::super::attribute_command_provider("/opt/homebrew/bin/copilot"),
|
|
329
|
+
Some(Provider::Copilot)
|
|
330
|
+
);
|
|
313
331
|
assert_eq!(owner_bind_provider_wire("copilot --banner"), "copilot");
|
|
314
332
|
}
|
|
315
333
|
|
|
316
334
|
#[test]
|
|
317
335
|
fn known_commands_map_via_single_source() {
|
|
318
|
-
assert_eq!(
|
|
319
|
-
|
|
320
|
-
|
|
336
|
+
assert_eq!(
|
|
337
|
+
super::super::attribute_command_provider("claude"),
|
|
338
|
+
Some(Provider::ClaudeCode)
|
|
339
|
+
);
|
|
340
|
+
assert_eq!(
|
|
341
|
+
super::super::attribute_command_provider("codex"),
|
|
342
|
+
Some(Provider::Codex)
|
|
343
|
+
);
|
|
344
|
+
assert_eq!(
|
|
345
|
+
super::super::attribute_command_provider("fake"),
|
|
346
|
+
Some(Provider::Fake)
|
|
347
|
+
);
|
|
321
348
|
assert_eq!(owner_bind_provider_wire("claude"), "claude");
|
|
322
349
|
assert_eq!(owner_bind_provider_wire("codex"), "codex");
|
|
323
350
|
}
|
|
@@ -325,8 +352,14 @@ mod e11_provider_bind_tests {
|
|
|
325
352
|
// E11 层2:未知命令不再静默默认 codex —— attribution → None,wire → ""。
|
|
326
353
|
#[test]
|
|
327
354
|
fn unknown_command_is_none_not_silent_codex() {
|
|
328
|
-
assert_eq!(
|
|
329
|
-
|
|
355
|
+
assert_eq!(
|
|
356
|
+
super::super::attribute_command_provider("node /some/thing.js"),
|
|
357
|
+
None
|
|
358
|
+
);
|
|
359
|
+
assert_eq!(
|
|
360
|
+
super::super::attribute_command_provider("totally-unknown"),
|
|
361
|
+
None
|
|
362
|
+
);
|
|
330
363
|
assert_eq!(owner_bind_provider_wire("totally-unknown"), "");
|
|
331
364
|
}
|
|
332
365
|
}
|
|
@@ -206,8 +206,14 @@ fn rediscover_updates_unique_env_triple_match() {
|
|
|
206
206
|
let ws = ws("rediscover");
|
|
207
207
|
let mut env = BTreeMap::new();
|
|
208
208
|
env.insert("TEAM_AGENT_LEADER_PANE_ID".to_string(), "%r".to_string());
|
|
209
|
-
env.insert(
|
|
210
|
-
|
|
209
|
+
env.insert(
|
|
210
|
+
"TEAM_AGENT_LEADER_PROVIDER".to_string(),
|
|
211
|
+
"codex".to_string(),
|
|
212
|
+
);
|
|
213
|
+
env.insert(
|
|
214
|
+
"TEAM_AGENT_MACHINE_FINGERPRINT".to_string(),
|
|
215
|
+
"fp".to_string(),
|
|
216
|
+
);
|
|
211
217
|
let target = PaneInfo {
|
|
212
218
|
pane_id: PaneId::new("%r"),
|
|
213
219
|
session: SessionName::new("sess"),
|
|
@@ -235,10 +241,14 @@ fn rediscover_updates_unique_env_triple_match() {
|
|
|
235
241
|
});
|
|
236
242
|
let event_log = crate::event_log::EventLog::new(&ws);
|
|
237
243
|
|
|
238
|
-
let out =
|
|
244
|
+
let out =
|
|
245
|
+
rediscover_leader_receiver_from_targets(&ws, &mut state, &[target], &event_log).unwrap();
|
|
239
246
|
|
|
240
247
|
assert_eq!(out["status"], json!("updated"));
|
|
241
|
-
assert!(
|
|
248
|
+
assert!(
|
|
249
|
+
out.get("ok").is_none(),
|
|
250
|
+
"golden rediscover result has no ok wrapper"
|
|
251
|
+
);
|
|
242
252
|
assert_eq!(out["owner_identity"]["pane_id"], json!("%r"));
|
|
243
253
|
// Stage 3d: receiver lives at canonical teams.<team_key>.leader_receiver.
|
|
244
254
|
let team_key = crate::state::projection::team_state_key(&state);
|
|
@@ -255,9 +265,15 @@ fn rediscover_updates_unique_env_triple_match() {
|
|
|
255
265
|
assert!(rebound.get("reason").is_some_and(Value::is_null));
|
|
256
266
|
assert_eq!(rebound["old_pane_id"], json!("%r"));
|
|
257
267
|
assert_eq!(rebound["new_pane_id"], json!("%r"));
|
|
258
|
-
assert_eq!(
|
|
268
|
+
assert_eq!(
|
|
269
|
+
rebound["owner_identity"]["machine_fingerprint"],
|
|
270
|
+
json!("fp")
|
|
271
|
+
);
|
|
259
272
|
assert_eq!(rebound["uuid_prefix"], json!("OWNERUUI"));
|
|
260
|
-
assert_eq!(
|
|
273
|
+
assert_eq!(
|
|
274
|
+
receiver["discovery"],
|
|
275
|
+
json!("stale_rediscovery_unique_candidate")
|
|
276
|
+
);
|
|
261
277
|
}
|
|
262
278
|
|
|
263
279
|
#[test]
|
|
@@ -276,8 +292,14 @@ fn rediscover_owner_identity_passthrough_sets_discovery_and_reason() {
|
|
|
276
292
|
pane_pid: None,
|
|
277
293
|
leader_env: BTreeMap::from([
|
|
278
294
|
("TEAM_AGENT_LEADER_PANE_ID".to_string(), "%raw".to_string()),
|
|
279
|
-
(
|
|
280
|
-
|
|
295
|
+
(
|
|
296
|
+
"TEAM_AGENT_LEADER_PROVIDER".to_string(),
|
|
297
|
+
"codex".to_string(),
|
|
298
|
+
),
|
|
299
|
+
(
|
|
300
|
+
"TEAM_AGENT_MACHINE_FINGERPRINT".to_string(),
|
|
301
|
+
"fp".to_string(),
|
|
302
|
+
),
|
|
281
303
|
]),
|
|
282
304
|
};
|
|
283
305
|
let raw_identity = json!({
|
|
@@ -305,8 +327,14 @@ fn rediscover_owner_identity_passthrough_sets_discovery_and_reason() {
|
|
|
305
327
|
// Stage 3d: receiver lives at canonical teams.<team_key>.leader_receiver.
|
|
306
328
|
let team_key = crate::state::projection::team_state_key(&state);
|
|
307
329
|
let receiver = &state["teams"][&team_key]["leader_receiver"];
|
|
308
|
-
assert_eq!(
|
|
309
|
-
|
|
330
|
+
assert_eq!(
|
|
331
|
+
receiver["discovery"],
|
|
332
|
+
json!("stale_rediscovery_owner_identity")
|
|
333
|
+
);
|
|
334
|
+
let rebound = event_named(
|
|
335
|
+
&event_log.tail(10).unwrap(),
|
|
336
|
+
"leader_receiver.rebind_applied",
|
|
337
|
+
);
|
|
310
338
|
assert_eq!(rebound["reason"], json!("stale_receiver"));
|
|
311
339
|
assert_eq!(rebound["owner_identity"], raw_identity);
|
|
312
340
|
assert_eq!(rebound["uuid_prefix"], json!("RAWUUID1"));
|
|
@@ -328,16 +356,23 @@ fn rediscover_missing_and_ambiguous_use_golden_status_names() {
|
|
|
328
356
|
"claimed_via": "claim-leader"
|
|
329
357
|
}
|
|
330
358
|
});
|
|
331
|
-
let missing =
|
|
359
|
+
let missing =
|
|
360
|
+
rediscover_leader_receiver_from_targets(&ws, &mut state, &[], &event_log).unwrap();
|
|
332
361
|
assert_eq!(missing["status"], json!("missing"));
|
|
333
362
|
assert!(missing.get("ok").is_none());
|
|
334
363
|
assert_eq!(missing["owner_identity"]["pane_id"], json!("%owner"));
|
|
335
|
-
let missing_event = event_named(
|
|
364
|
+
let missing_event = event_named(
|
|
365
|
+
&event_log.tail(10).unwrap(),
|
|
366
|
+
"leader_receiver.rediscover_missing",
|
|
367
|
+
);
|
|
336
368
|
assert_eq!(missing_event["owner_identity"]["pane_id"], json!("%owner"));
|
|
337
369
|
assert_eq!(missing_event["provider"], json!("codex"));
|
|
338
370
|
assert_eq!(missing_event["old_target"], json!("%owner"));
|
|
339
371
|
assert_eq!(missing_event["candidate_count"], json!(0));
|
|
340
|
-
let missing_rebind = last_event_named(
|
|
372
|
+
let missing_rebind = last_event_named(
|
|
373
|
+
&event_log.tail(10).unwrap(),
|
|
374
|
+
"leader_receiver.rebind_required",
|
|
375
|
+
);
|
|
341
376
|
assert!(missing_rebind.get("reason").is_some_and(Value::is_null));
|
|
342
377
|
assert_eq!(missing_rebind["provider"], json!("codex"));
|
|
343
378
|
assert_eq!(missing_rebind["team_id"], json!("sess"));
|
|
@@ -350,9 +385,18 @@ fn rediscover_missing_and_ambiguous_use_golden_status_names() {
|
|
|
350
385
|
assert!(missing_rebind.get("invalidation_reason").is_none());
|
|
351
386
|
|
|
352
387
|
let mut env = BTreeMap::new();
|
|
353
|
-
env.insert(
|
|
354
|
-
|
|
355
|
-
|
|
388
|
+
env.insert(
|
|
389
|
+
"TEAM_AGENT_LEADER_PANE_ID".to_string(),
|
|
390
|
+
"%owner".to_string(),
|
|
391
|
+
);
|
|
392
|
+
env.insert(
|
|
393
|
+
"TEAM_AGENT_LEADER_PROVIDER".to_string(),
|
|
394
|
+
"codex".to_string(),
|
|
395
|
+
);
|
|
396
|
+
env.insert(
|
|
397
|
+
"TEAM_AGENT_MACHINE_FINGERPRINT".to_string(),
|
|
398
|
+
"fp".to_string(),
|
|
399
|
+
);
|
|
356
400
|
let mk = |pane: &str, window: u32, pane_index: u32, tty: &str| PaneInfo {
|
|
357
401
|
pane_id: PaneId::new(pane),
|
|
358
402
|
session: SessionName::new("sess"),
|
|
@@ -375,18 +419,29 @@ fn rediscover_missing_and_ambiguous_use_golden_status_names() {
|
|
|
375
419
|
.unwrap();
|
|
376
420
|
assert_eq!(ambiguous["status"], json!("ambiguous"));
|
|
377
421
|
assert!(ambiguous.get("ok").is_none());
|
|
378
|
-
assert_eq!(
|
|
422
|
+
assert_eq!(
|
|
423
|
+
ambiguous["owner_candidates"].as_array().map(Vec::len),
|
|
424
|
+
Some(2)
|
|
425
|
+
);
|
|
379
426
|
assert_eq!(ambiguous["owner_candidates"][0]["pane_id"], json!("%a"));
|
|
380
427
|
assert_eq!(ambiguous["owner_candidates"][0]["window_index"], json!(1));
|
|
381
428
|
assert_eq!(ambiguous["owner_candidates"][0]["pane_index"], json!(0));
|
|
382
|
-
assert_eq!(
|
|
383
|
-
|
|
429
|
+
assert_eq!(
|
|
430
|
+
ambiguous["owner_candidates"][0]["pane_tty"],
|
|
431
|
+
json!("/ttys001")
|
|
432
|
+
);
|
|
433
|
+
assert_eq!(
|
|
434
|
+
ambiguous["owner_candidates"][0]["current_command"],
|
|
435
|
+
json!("codex")
|
|
436
|
+
);
|
|
384
437
|
assert_eq!(ambiguous["owner_candidates"][0]["fingerprint"], json!("fp"));
|
|
385
438
|
assert!(ambiguous["owner_candidates"][0]["current_path"]
|
|
386
439
|
.as_str()
|
|
387
440
|
.is_some_and(|path| path.contains("rediscover-%a")));
|
|
388
441
|
assert!(ambiguous.get("incident").is_none());
|
|
389
|
-
assert!(ambiguous["incident_id"]
|
|
442
|
+
assert!(ambiguous["incident_id"]
|
|
443
|
+
.as_str()
|
|
444
|
+
.is_some_and(|id| id.starts_with("rediscover_")));
|
|
390
445
|
assert_eq!(ambiguous["deduped"], json!(false));
|
|
391
446
|
let events = event_log.tail(20).unwrap();
|
|
392
447
|
let ambiguous_event = event_named(&events, "leader_receiver.rediscover_ambiguous");
|
|
@@ -398,7 +453,10 @@ fn rediscover_missing_and_ambiguous_use_golden_status_names() {
|
|
|
398
453
|
assert_eq!(candidates_event["provider"], json!("codex"));
|
|
399
454
|
assert_eq!(candidates_event["team_id"], json!("sess"));
|
|
400
455
|
assert_eq!(candidates_event["uuid_prefix"], json!("OWNERUUI"));
|
|
401
|
-
assert_eq!(
|
|
456
|
+
assert_eq!(
|
|
457
|
+
candidates_event["debounce_bucket"],
|
|
458
|
+
ambiguous["incident_id"]
|
|
459
|
+
);
|
|
402
460
|
assert_eq!(candidates_event["reason"], json!("force_confirm_required"));
|
|
403
461
|
assert_eq!(candidates_event["queued"].as_array().map(Vec::len), Some(2));
|
|
404
462
|
}
|
|
@@ -538,19 +596,25 @@ fn rediscover_failed_uses_golden_status_and_rebind_shape() {
|
|
|
538
596
|
.unwrap();
|
|
539
597
|
|
|
540
598
|
assert_eq!(out["status"], json!("failed"));
|
|
541
|
-
assert!(out["error"]
|
|
599
|
+
assert!(out["error"]
|
|
600
|
+
.as_str()
|
|
601
|
+
.is_some_and(|error| error.contains("scan failed")));
|
|
542
602
|
assert!(out.get("ok").is_none());
|
|
543
603
|
let events = event_log.tail(10).unwrap();
|
|
544
604
|
let failed = event_named(&events, "leader_receiver.rediscover_failed");
|
|
545
605
|
assert_eq!(failed["provider"], json!("codex"));
|
|
546
|
-
assert!(failed["error"]
|
|
606
|
+
assert!(failed["error"]
|
|
607
|
+
.as_str()
|
|
608
|
+
.is_some_and(|error| error.contains("scan failed")));
|
|
547
609
|
let rebind = event_named(&events, "leader_receiver.rebind_required");
|
|
548
610
|
assert_eq!(rebind["old_pane_id"], json!("%old"));
|
|
549
611
|
assert_eq!(rebind["reason"], json!("stale_receiver"));
|
|
550
612
|
assert_eq!(rebind["provider"], json!("codex"));
|
|
551
613
|
assert_eq!(rebind["team_id"], json!("sess"));
|
|
552
614
|
assert_eq!(rebind["rediscovery_status"], json!("failed"));
|
|
553
|
-
assert!(rebind["error"]
|
|
615
|
+
assert!(rebind["error"]
|
|
616
|
+
.as_str()
|
|
617
|
+
.is_some_and(|error| error.contains("scan failed")));
|
|
554
618
|
assert!(rebind.get("owner_identity").is_none());
|
|
555
619
|
assert!(rebind.get("uuid_prefix").is_none());
|
|
556
620
|
assert!(rebind.get("invalidation_reason").is_none());
|
|
@@ -6,11 +6,13 @@ use std::path::{Path, PathBuf};
|
|
|
6
6
|
use serde_json::{json, Value};
|
|
7
7
|
|
|
8
8
|
use crate::model::ids::{LeaderSessionUuid, OwnerEpoch};
|
|
9
|
-
use crate::provider::Provider;
|
|
10
9
|
use crate::provider::wire::provider_wire;
|
|
10
|
+
use crate::provider::Provider;
|
|
11
11
|
use crate::transport::{PaneId, PaneInfo, SessionName, Transport, WindowName};
|
|
12
12
|
|
|
13
|
-
use super::helpers::{
|
|
13
|
+
use super::helpers::{
|
|
14
|
+
get_path_str, now_ts, parse_provider, prefix, resolve_workspace_for_hash, sha1_hex_prefix,
|
|
15
|
+
};
|
|
14
16
|
use super::{
|
|
15
17
|
ClaimedVia, Discovery, LeaderError, LeaderEvent, LeaderReceiver, LeaseSource, ReceiverMode,
|
|
16
18
|
ReceiverStatus, TeamOwner,
|
|
@@ -56,8 +58,21 @@ pub fn try_readopt_leader_pane(
|
|
|
56
58
|
.or_else(|| owner_record.and_then(|owner| owner.leader_session_uuid.clone()))
|
|
57
59
|
.or_else(|| receiver.leader_session_uuid.clone());
|
|
58
60
|
let provider = candidate.provider.unwrap_or(receiver_provider);
|
|
59
|
-
let rebound = receiver_from_candidate(
|
|
60
|
-
|
|
61
|
+
let rebound = receiver_from_candidate(
|
|
62
|
+
&candidate,
|
|
63
|
+
receiver,
|
|
64
|
+
provider,
|
|
65
|
+
uuid.clone(),
|
|
66
|
+
owner_epoch,
|
|
67
|
+
Discovery::AttachReadopt,
|
|
68
|
+
);
|
|
69
|
+
let owner = owner_from_candidate(
|
|
70
|
+
&candidate,
|
|
71
|
+
provider,
|
|
72
|
+
uuid,
|
|
73
|
+
owner_epoch,
|
|
74
|
+
ClaimedVia::AttachLeader,
|
|
75
|
+
);
|
|
61
76
|
let mut owner_identity = OwnerIdentity::from_state(workspace, state)?;
|
|
62
77
|
if let Some(record) = owner_record {
|
|
63
78
|
owner_identity.pane_id = Some(record.pane_id.as_str().to_string());
|
|
@@ -123,7 +138,9 @@ pub fn rediscover_leader_receiver(
|
|
|
123
138
|
event_log: &crate::event_log::EventLog,
|
|
124
139
|
) -> Result<Value, LeaderError> {
|
|
125
140
|
let identity = OwnerIdentity::from_state(workspace, state)?;
|
|
126
|
-
rediscover_leader_receiver_with_identity(
|
|
141
|
+
rediscover_leader_receiver_with_identity(
|
|
142
|
+
workspace, state, transport, event_log, &identity, None,
|
|
143
|
+
)
|
|
127
144
|
}
|
|
128
145
|
|
|
129
146
|
pub fn rediscover_leader_receiver_with_owner_identity(
|
|
@@ -164,7 +181,13 @@ fn rediscover_leader_receiver_with_identity(
|
|
|
164
181
|
"error": error.as_str(),
|
|
165
182
|
}),
|
|
166
183
|
)?;
|
|
167
|
-
emit_failed_rebind_required(
|
|
184
|
+
emit_failed_rebind_required(
|
|
185
|
+
event_log,
|
|
186
|
+
state,
|
|
187
|
+
identity,
|
|
188
|
+
invalidation_reason,
|
|
189
|
+
error.as_str(),
|
|
190
|
+
)?;
|
|
168
191
|
return Ok(json!({
|
|
169
192
|
"status": "failed",
|
|
170
193
|
"error": error,
|
|
@@ -189,12 +212,7 @@ pub fn rediscover_leader_receiver_from_targets(
|
|
|
189
212
|
) -> Result<Value, LeaderError> {
|
|
190
213
|
let identity = OwnerIdentity::from_state(workspace, state)?;
|
|
191
214
|
rediscover_leader_receiver_from_targets_with_identity(
|
|
192
|
-
workspace,
|
|
193
|
-
state,
|
|
194
|
-
targets,
|
|
195
|
-
event_log,
|
|
196
|
-
&identity,
|
|
197
|
-
None,
|
|
215
|
+
workspace, state, targets, event_log, &identity, None,
|
|
198
216
|
)
|
|
199
217
|
}
|
|
200
218
|
|
|
@@ -268,7 +286,14 @@ fn rediscover_leader_receiver_from_targets_with_identity(
|
|
|
268
286
|
)?;
|
|
269
287
|
}
|
|
270
288
|
if has_owner_identity {
|
|
271
|
-
emit_rebind_required(
|
|
289
|
+
emit_rebind_required(
|
|
290
|
+
event_log,
|
|
291
|
+
"ambiguous",
|
|
292
|
+
state,
|
|
293
|
+
identity,
|
|
294
|
+
invalidation_reason,
|
|
295
|
+
"confirm rediscover leader receiver",
|
|
296
|
+
)?;
|
|
272
297
|
return Ok(json!({
|
|
273
298
|
"status": "ambiguous",
|
|
274
299
|
"owner_candidates": candidate_values(&candidates, identity.from_caller),
|
|
@@ -504,13 +529,16 @@ impl OwnerIdentity {
|
|
|
504
529
|
}
|
|
505
530
|
|
|
506
531
|
fn has_match_identity(&self) -> bool {
|
|
507
|
-
self.pane_id.is_some()
|
|
532
|
+
self.pane_id.is_some()
|
|
533
|
+
|| self.leader_session_uuid.is_some()
|
|
534
|
+
|| !self.machine_fingerprint.is_empty()
|
|
508
535
|
}
|
|
509
536
|
}
|
|
510
537
|
|
|
511
538
|
fn target_from_value(value: &Value) -> Option<LeaderTarget> {
|
|
512
539
|
let pane_id = get_str(value, "pane_id").or_else(|| get_str(value, "pane"))?;
|
|
513
|
-
let current_command =
|
|
540
|
+
let current_command =
|
|
541
|
+
get_str(value, "pane_current_command").or_else(|| get_str(value, "current_command"));
|
|
514
542
|
let leader_env = map_env(value.get("leader_env").or_else(|| value.get("env")));
|
|
515
543
|
let pane_pid = get_u32(value, "pane_pid");
|
|
516
544
|
let provider = super::attribute_pane_provider(&PaneInfo {
|
|
@@ -553,7 +581,8 @@ fn target_from_value(value: &Value) -> Option<LeaderTarget> {
|
|
|
553
581
|
leader_env,
|
|
554
582
|
provider,
|
|
555
583
|
leader_session_uuid,
|
|
556
|
-
fingerprint: get_str(value, "fingerprint")
|
|
584
|
+
fingerprint: get_str(value, "fingerprint")
|
|
585
|
+
.or_else(|| get_str(value, "machine_fingerprint")),
|
|
557
586
|
})
|
|
558
587
|
}
|
|
559
588
|
|
|
@@ -648,7 +677,13 @@ fn candidate_pane_ids(candidates: &[LeaderTarget]) -> Vec<String> {
|
|
|
648
677
|
|
|
649
678
|
fn ambiguous_incident_id(identity: &OwnerIdentity, panes: &[String]) -> String {
|
|
650
679
|
let mut bytes = Vec::new();
|
|
651
|
-
bytes.extend_from_slice(
|
|
680
|
+
bytes.extend_from_slice(
|
|
681
|
+
identity
|
|
682
|
+
.provider
|
|
683
|
+
.map(provider_wire)
|
|
684
|
+
.unwrap_or("")
|
|
685
|
+
.as_bytes(),
|
|
686
|
+
);
|
|
652
687
|
bytes.push(0);
|
|
653
688
|
bytes.extend_from_slice(identity.team_id.as_bytes());
|
|
654
689
|
bytes.push(0);
|
|
@@ -752,7 +787,8 @@ fn ambiguous_candidates_already_broadcast(
|
|
|
752
787
|
incident_id: &str,
|
|
753
788
|
) -> Result<bool, LeaderError> {
|
|
754
789
|
Ok(event_log.tail(200)?.iter().any(|event| {
|
|
755
|
-
event.get("event").and_then(Value::as_str)
|
|
790
|
+
event.get("event").and_then(Value::as_str)
|
|
791
|
+
== Some(LeaderEvent::ReceiverAmbiguousCandidates.name())
|
|
756
792
|
&& event.get("incident_id").and_then(Value::as_str) == Some(incident_id)
|
|
757
793
|
}))
|
|
758
794
|
}
|
|
@@ -915,11 +951,15 @@ fn env_triple_matches(target: &LeaderTarget, identity: &OwnerIdentity) -> bool {
|
|
|
915
951
|
&& target
|
|
916
952
|
.leader_env
|
|
917
953
|
.get("TEAM_AGENT_LEADER_PROVIDER")
|
|
918
|
-
.is_some_and(|value|
|
|
954
|
+
.is_some_and(|value| {
|
|
955
|
+
identity
|
|
956
|
+
.provider
|
|
957
|
+
.is_some_and(|provider| value == provider_wire(provider))
|
|
958
|
+
})
|
|
919
959
|
&& target
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
960
|
+
.leader_env
|
|
961
|
+
.get("TEAM_AGENT_MACHINE_FINGERPRINT")
|
|
962
|
+
.is_some_and(|value| value == &identity.machine_fingerprint)
|
|
923
963
|
}
|
|
924
964
|
|
|
925
965
|
fn different_live_owner_uuid_mismatch(
|
|
@@ -984,7 +1024,12 @@ fn owner_from_candidate(
|
|
|
984
1024
|
machine_fingerprint: candidate
|
|
985
1025
|
.fingerprint
|
|
986
1026
|
.clone()
|
|
987
|
-
.or_else(||
|
|
1027
|
+
.or_else(|| {
|
|
1028
|
+
candidate
|
|
1029
|
+
.leader_env
|
|
1030
|
+
.get("TEAM_AGENT_MACHINE_FINGERPRINT")
|
|
1031
|
+
.cloned()
|
|
1032
|
+
})
|
|
988
1033
|
.unwrap_or_default(),
|
|
989
1034
|
leader_session_uuid: uuid,
|
|
990
1035
|
owner_epoch: epoch,
|
|
@@ -1067,7 +1112,9 @@ fn write_readopt_state(
|
|
|
1067
1112
|
*state = json!({});
|
|
1068
1113
|
}
|
|
1069
1114
|
if !state.is_object() {
|
|
1070
|
-
return Err(LeaderError::Validation(
|
|
1115
|
+
return Err(LeaderError::Validation(
|
|
1116
|
+
"state root is not an object".to_string(),
|
|
1117
|
+
));
|
|
1071
1118
|
}
|
|
1072
1119
|
let team_key = crate::state::projection::team_state_key(state);
|
|
1073
1120
|
let record = crate::state::ownership::OwnershipWrite::new()
|
|
@@ -1086,7 +1133,9 @@ fn write_receiver_state(
|
|
|
1086
1133
|
*state = json!({});
|
|
1087
1134
|
}
|
|
1088
1135
|
if !state.is_object() {
|
|
1089
|
-
return Err(LeaderError::Validation(
|
|
1136
|
+
return Err(LeaderError::Validation(
|
|
1137
|
+
"state root is not an object".to_string(),
|
|
1138
|
+
));
|
|
1090
1139
|
}
|
|
1091
1140
|
let team_key = crate::state::projection::team_state_key(state);
|
|
1092
1141
|
let record = crate::state::ownership::OwnershipWrite::new()
|