@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
|
@@ -40,20 +40,38 @@ pub fn attach_leader(
|
|
|
40
40
|
let targets = attach_leader_targets(workspace, &state);
|
|
41
41
|
let pane_id = pane
|
|
42
42
|
.cloned()
|
|
43
|
-
.or_else(||
|
|
43
|
+
.or_else(|| {
|
|
44
|
+
std::env::var("TMUX_PANE")
|
|
45
|
+
.ok()
|
|
46
|
+
.filter(|p| !p.is_empty())
|
|
47
|
+
.map(PaneId::new)
|
|
48
|
+
})
|
|
44
49
|
.ok_or_else(|| LeaderError::Validation("tmux pane not found".to_string()))?;
|
|
45
50
|
let non_empty_pane_id = NonEmptyPaneId::try_from_pane(&pane_id)?;
|
|
46
51
|
let Some(target) = targets.iter().find(|target| target.info.pane_id == pane_id) else {
|
|
47
|
-
return Err(LeaderError::Validation(format!(
|
|
52
|
+
return Err(LeaderError::Validation(format!(
|
|
53
|
+
"tmux pane not found: {pane_id}"
|
|
54
|
+
)));
|
|
48
55
|
};
|
|
49
|
-
let mut receiver = receiver_for_attach_target(
|
|
56
|
+
let mut receiver = receiver_for_attach_target(
|
|
57
|
+
workspace,
|
|
58
|
+
&state,
|
|
59
|
+
&target.info,
|
|
60
|
+
provider,
|
|
61
|
+
Discovery::ExplicitPane,
|
|
62
|
+
)?;
|
|
50
63
|
if let Some(endpoint) = target.endpoint.as_ref() {
|
|
51
64
|
receiver.tmux_socket = Some(endpoint.clone());
|
|
52
65
|
}
|
|
53
66
|
let validation = validate_attach_target(workspace, &state, &target.info, provider);
|
|
54
67
|
if validation.is_err() {
|
|
55
68
|
let pane_info = pane_info_value(&target.info);
|
|
56
|
-
let targets_value = Value::Array(
|
|
69
|
+
let targets_value = Value::Array(
|
|
70
|
+
targets
|
|
71
|
+
.iter()
|
|
72
|
+
.map(|target| pane_info_value(&target.info))
|
|
73
|
+
.collect(),
|
|
74
|
+
);
|
|
57
75
|
let owner_record = state_owner(&state);
|
|
58
76
|
if let Some((readopted, validation)) = crate::leader::try_readopt_leader_pane(
|
|
59
77
|
workspace,
|
|
@@ -66,8 +84,11 @@ pub fn attach_leader(
|
|
|
66
84
|
LeaseSource::Manual,
|
|
67
85
|
&event_log,
|
|
68
86
|
)? {
|
|
69
|
-
if let Some(endpoint) = target.endpoint.as_deref() {
|
|
70
|
-
|
|
87
|
+
if let Some(endpoint) = target.endpoint.as_deref() {
|
|
88
|
+
quiet_fake_leader_pane_echo(provider, &target.info, endpoint);
|
|
89
|
+
}
|
|
90
|
+
let _ =
|
|
91
|
+
requeue_exhausted_watchers_after_attach(workspace, &state, &event_log, &pane_id)?;
|
|
71
92
|
return Ok(LeaseResult {
|
|
72
93
|
ok: true,
|
|
73
94
|
status: LeaseStatus::Claimed,
|
|
@@ -90,7 +111,9 @@ pub fn attach_leader(
|
|
|
90
111
|
"reason": validation.err().unwrap_or("leader_pane_validation_failed"),
|
|
91
112
|
}),
|
|
92
113
|
)?;
|
|
93
|
-
return Err(LeaderError::Validation(format!(
|
|
114
|
+
return Err(LeaderError::Validation(format!(
|
|
115
|
+
"leader pane validation failed: {pane_id}"
|
|
116
|
+
)));
|
|
94
117
|
}
|
|
95
118
|
let epoch = current_owner_epoch(&state);
|
|
96
119
|
if state.get("team_owner").is_some() {
|
|
@@ -100,7 +123,9 @@ pub fn attach_leader(
|
|
|
100
123
|
super::LeaderEvent::ReceiverAttached.name(),
|
|
101
124
|
json!({"pane_id": pane_id.as_str(), "owner_epoch": epoch.0}),
|
|
102
125
|
)?;
|
|
103
|
-
if let Some(endpoint) = target.endpoint.as_deref() {
|
|
126
|
+
if let Some(endpoint) = target.endpoint.as_deref() {
|
|
127
|
+
quiet_fake_leader_pane_echo(provider, &target.info, endpoint);
|
|
128
|
+
}
|
|
104
129
|
let _ = requeue_exhausted_watchers_after_attach(workspace, &state, &event_log, &pane_id)?;
|
|
105
130
|
return Ok(LeaseResult {
|
|
106
131
|
ok: true,
|
|
@@ -125,7 +150,9 @@ pub fn attach_leader(
|
|
|
125
150
|
super::LeaderEvent::ReceiverAttached.name(),
|
|
126
151
|
json!({"pane_id": pane_id.as_str(), "owner_epoch": next_epoch.0}),
|
|
127
152
|
)?;
|
|
128
|
-
if let Some(endpoint) = target.endpoint.as_deref() {
|
|
153
|
+
if let Some(endpoint) = target.endpoint.as_deref() {
|
|
154
|
+
quiet_fake_leader_pane_echo(provider, &target.info, endpoint);
|
|
155
|
+
}
|
|
129
156
|
let _ = requeue_exhausted_watchers_after_attach(workspace, &state, &event_log, &pane_id)?;
|
|
130
157
|
Ok(LeaseResult {
|
|
131
158
|
ok: true,
|
|
@@ -279,7 +306,10 @@ fn attach_leader_targets(workspace: &Path, state: &Value) -> Vec<AttachLeaderTar
|
|
|
279
306
|
.list_targets()
|
|
280
307
|
.unwrap_or_default()
|
|
281
308
|
.into_iter()
|
|
282
|
-
.map(|info| AttachLeaderTarget {
|
|
309
|
+
.map(|info| AttachLeaderTarget {
|
|
310
|
+
info,
|
|
311
|
+
endpoint: None,
|
|
312
|
+
}),
|
|
283
313
|
);
|
|
284
314
|
targets
|
|
285
315
|
}
|
|
@@ -336,11 +366,7 @@ fn requeue_exhausted_watchers_after_attach(
|
|
|
336
366
|
let store = MessageStore::open(workspace)?;
|
|
337
367
|
let team_id = TeamKey::new(crate::state::projection::team_state_key(state));
|
|
338
368
|
let notices = crate::messaging::requeue_delivery_exhausted_watchers(
|
|
339
|
-
workspace,
|
|
340
|
-
&store,
|
|
341
|
-
event_log,
|
|
342
|
-
&team_id,
|
|
343
|
-
pane_id,
|
|
369
|
+
workspace, &store, event_log, &team_id, pane_id,
|
|
344
370
|
)?;
|
|
345
371
|
event_log.write(
|
|
346
372
|
super::LeaderEvent::ReceiverRequeuedExhaustedWatchers.name(),
|
|
@@ -359,7 +385,10 @@ pub(crate) fn requeued_exhausted_watchers_event_payload(
|
|
|
359
385
|
_team_id: &TeamKey,
|
|
360
386
|
notices: &[crate::messaging::WatcherNotice],
|
|
361
387
|
) -> serde_json::Value {
|
|
362
|
-
let watcher_ids: Vec<&str> = notices
|
|
388
|
+
let watcher_ids: Vec<&str> = notices
|
|
389
|
+
.iter()
|
|
390
|
+
.map(|notice| notice.watcher_id.as_str())
|
|
391
|
+
.collect();
|
|
363
392
|
json!({
|
|
364
393
|
"watcher_ids": watcher_ids,
|
|
365
394
|
"count": watcher_ids.len(),
|
|
@@ -382,16 +411,32 @@ pub fn attach_leader_to_state(
|
|
|
382
411
|
require_current: bool,
|
|
383
412
|
) -> Result<(LeaderReceiver, Value), LeaderError> {
|
|
384
413
|
let _ = (source, require_current);
|
|
385
|
-
let pane_id = pane
|
|
414
|
+
let pane_id = pane
|
|
415
|
+
.cloned()
|
|
416
|
+
.ok_or_else(|| LeaderError::Validation("tmux pane not found".to_string()))?;
|
|
386
417
|
let non_empty_pane_id = NonEmptyPaneId::try_from_pane(&pane_id)?;
|
|
387
418
|
let identity = leader_identity_context(workspace, None, Some(state))?;
|
|
388
419
|
let epoch = current_owner_epoch(state);
|
|
389
|
-
let receiver = make_receiver(
|
|
420
|
+
let receiver = make_receiver(
|
|
421
|
+
provider,
|
|
422
|
+
&non_empty_pane_id,
|
|
423
|
+
&identity.leader_session_uuid,
|
|
424
|
+
epoch,
|
|
425
|
+
Discovery::EnvPane,
|
|
426
|
+
None,
|
|
427
|
+
);
|
|
390
428
|
if state.get("team_owner").is_some() {
|
|
391
429
|
write_receiver_to_state(state, &receiver)?;
|
|
392
430
|
} else {
|
|
393
431
|
let next_epoch = OwnerEpoch(epoch.0.saturating_add(1));
|
|
394
|
-
let receiver = make_receiver(
|
|
432
|
+
let receiver = make_receiver(
|
|
433
|
+
provider,
|
|
434
|
+
&non_empty_pane_id,
|
|
435
|
+
&identity.leader_session_uuid,
|
|
436
|
+
next_epoch,
|
|
437
|
+
Discovery::EnvPane,
|
|
438
|
+
None,
|
|
439
|
+
);
|
|
395
440
|
let owner = make_owner(provider, &non_empty_pane_id, &identity, next_epoch);
|
|
396
441
|
write_binding_to_state(state, &receiver, &owner)?;
|
|
397
442
|
write_lease_dual_state(workspace, state)?;
|
|
@@ -436,9 +481,13 @@ pub fn claim_leader(
|
|
|
436
481
|
let caller = std::env::var("TMUX_PANE")
|
|
437
482
|
.ok()
|
|
438
483
|
.filter(|pane| !pane.is_empty())
|
|
439
|
-
.or_else(||
|
|
484
|
+
.or_else(|| {
|
|
485
|
+
std::env::var("TEAM_AGENT_LEADER_PANE_ID")
|
|
486
|
+
.ok()
|
|
487
|
+
.filter(|pane| !pane.is_empty())
|
|
488
|
+
})
|
|
440
489
|
.unwrap_or_default();
|
|
441
|
-
let raw_state = crate::state::persist::
|
|
490
|
+
let raw_state = crate::state::persist::load_runtime_state_without_migrations(workspace)?;
|
|
442
491
|
let event_log = crate::event_log::EventLog::new(workspace);
|
|
443
492
|
let targets = claim_leader_targets(workspace, &raw_state);
|
|
444
493
|
let caller_candidate = targets
|
|
@@ -456,17 +505,36 @@ pub fn claim_leader(
|
|
|
456
505
|
.ok()
|
|
457
506
|
.filter(|team| !team.is_empty());
|
|
458
507
|
let explicit_team = team.filter(|team| !team.is_empty());
|
|
508
|
+
let active_team = crate::messaging::leader_receiver::active_team_key(workspace, &raw_state);
|
|
509
|
+
let active_team_from_state = raw_state
|
|
510
|
+
.get("active_team_key")
|
|
511
|
+
.and_then(Value::as_str)
|
|
512
|
+
.filter(|team| !team.is_empty());
|
|
459
513
|
let requested_team = explicit_team
|
|
460
|
-
.
|
|
461
|
-
.or_else(||
|
|
462
|
-
|
|
514
|
+
.or(active_team_from_state)
|
|
515
|
+
.or_else(|| {
|
|
516
|
+
caller_target
|
|
517
|
+
.as_ref()
|
|
518
|
+
.and_then(|target| target.team_id.as_deref())
|
|
519
|
+
})
|
|
520
|
+
.or(env_team.as_deref())
|
|
521
|
+
.or(Some(active_team.as_str()));
|
|
463
522
|
let team_id = TeamKey::new(
|
|
464
523
|
requested_team
|
|
465
524
|
.map(str::to_string)
|
|
466
|
-
.unwrap_or_else(||
|
|
525
|
+
.unwrap_or_else(|| active_team.clone()),
|
|
467
526
|
);
|
|
468
|
-
let
|
|
469
|
-
|
|
527
|
+
let scoped_team = if team_id.as_str() == active_team
|
|
528
|
+
|| raw_state
|
|
529
|
+
.get("teams")
|
|
530
|
+
.and_then(|teams| teams.get(team_id.as_str()))
|
|
531
|
+
.is_some()
|
|
532
|
+
{
|
|
533
|
+
Some(team_id.as_str())
|
|
534
|
+
} else {
|
|
535
|
+
None
|
|
536
|
+
};
|
|
537
|
+
let scoped_team = scoped_team.filter(|team| {
|
|
470
538
|
*team == active_team
|
|
471
539
|
|| raw_state
|
|
472
540
|
.get("teams")
|
|
@@ -504,12 +572,7 @@ pub fn claim_leader(
|
|
|
504
572
|
if let Some(pane) = result.bound_pane_id.as_ref() {
|
|
505
573
|
let store = MessageStore::open(workspace)?;
|
|
506
574
|
crate::messaging::watchers::requeue_after_claim_leader(
|
|
507
|
-
workspace,
|
|
508
|
-
&store,
|
|
509
|
-
&event_log,
|
|
510
|
-
&team_id,
|
|
511
|
-
pane,
|
|
512
|
-
None,
|
|
575
|
+
workspace, &store, &event_log, &team_id, pane, None,
|
|
513
576
|
)?;
|
|
514
577
|
}
|
|
515
578
|
}
|
|
@@ -568,7 +631,9 @@ struct NonEmptyPaneId(PaneId);
|
|
|
568
631
|
impl NonEmptyPaneId {
|
|
569
632
|
fn try_from_pane(pane: &PaneId) -> Result<Self, LeaderError> {
|
|
570
633
|
if pane.as_str().trim().is_empty() {
|
|
571
|
-
return Err(LeaderError::Validation(
|
|
634
|
+
return Err(LeaderError::Validation(
|
|
635
|
+
"leader pane id is empty".to_string(),
|
|
636
|
+
));
|
|
572
637
|
}
|
|
573
638
|
Ok(Self(pane.clone()))
|
|
574
639
|
}
|
|
@@ -640,14 +705,13 @@ fn claim_lease_no_incident_with_target(
|
|
|
640
705
|
} else {
|
|
641
706
|
None
|
|
642
707
|
};
|
|
643
|
-
let (mut topology_convergence, converged) =
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
);
|
|
708
|
+
let (mut topology_convergence, converged) = apply_endpoint_convergence(
|
|
709
|
+
state,
|
|
710
|
+
team_id,
|
|
711
|
+
convergence_candidate,
|
|
712
|
+
candidate_source,
|
|
713
|
+
pre_epoch,
|
|
714
|
+
);
|
|
651
715
|
if converged {
|
|
652
716
|
write_claim_state(workspace, state, scoped_team, team)?;
|
|
653
717
|
topology_convergence = verify_persisted_topology_convergence(
|
|
@@ -681,14 +745,12 @@ fn claim_lease_no_incident_with_target(
|
|
|
681
745
|
topology_convergence,
|
|
682
746
|
});
|
|
683
747
|
}
|
|
684
|
-
let owner_live = bound_pane_id
|
|
685
|
-
.
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
liveness.liveness(pane) == PaneLiveness::Live
|
|
691
|
-
});
|
|
748
|
+
let owner_live = bound_pane_id.as_deref().is_some_and(|pane| {
|
|
749
|
+
if pane == caller_pane.as_str() && !bound_endpoint_matches_caller {
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
liveness.liveness(pane) == PaneLiveness::Live
|
|
753
|
+
});
|
|
692
754
|
if owner_live && !confirm {
|
|
693
755
|
emit_lease_refusal(
|
|
694
756
|
event_log,
|
|
@@ -724,9 +786,9 @@ fn claim_lease_no_incident_with_target(
|
|
|
724
786
|
));
|
|
725
787
|
}
|
|
726
788
|
let locked_bound_pane = bound_pane(&locked);
|
|
727
|
-
let locked_owner_live = locked_bound_pane
|
|
728
|
-
.
|
|
729
|
-
|
|
789
|
+
let locked_owner_live = locked_bound_pane.as_deref().is_some_and(|pane| {
|
|
790
|
+
pane != caller_pane.as_str() && liveness.liveness(pane) == PaneLiveness::Live
|
|
791
|
+
});
|
|
730
792
|
if locked_owner_live && !confirm {
|
|
731
793
|
emit_lease_refusal(
|
|
732
794
|
event_log,
|
|
@@ -750,10 +812,8 @@ fn claim_lease_no_incident_with_target(
|
|
|
750
812
|
// pane → delivery routes worker messages to itself (loop) and the leader
|
|
751
813
|
// loses its handle (the macmini "hand-handle mapping 灾难" truth source).
|
|
752
814
|
if let Some(caller_pane_info) = caller_pane_info {
|
|
753
|
-
match crate::topology::classify_registered_worker_for_observed_pane(
|
|
754
|
-
|
|
755
|
-
caller_pane_info,
|
|
756
|
-
) {
|
|
815
|
+
match crate::topology::classify_registered_worker_for_observed_pane(state, caller_pane_info)
|
|
816
|
+
{
|
|
757
817
|
crate::topology::WorkerPaneBindingMatch::LiveSameWorker { agent_id } => {
|
|
758
818
|
emit_lease_refusal(
|
|
759
819
|
event_log,
|
|
@@ -829,14 +889,13 @@ fn claim_lease_no_incident_with_target(
|
|
|
829
889
|
} else {
|
|
830
890
|
None
|
|
831
891
|
};
|
|
832
|
-
let (mut topology_convergence, converged) =
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
);
|
|
892
|
+
let (mut topology_convergence, converged) = apply_endpoint_convergence(
|
|
893
|
+
state,
|
|
894
|
+
team_id,
|
|
895
|
+
receiver.tmux_socket.as_deref(),
|
|
896
|
+
candidate_source,
|
|
897
|
+
next_epoch,
|
|
898
|
+
);
|
|
840
899
|
write_claim_state(workspace, state, scoped_team, team)?;
|
|
841
900
|
if converged {
|
|
842
901
|
topology_convergence = verify_persisted_topology_convergence(
|
|
@@ -858,7 +917,12 @@ fn claim_lease_no_incident_with_target(
|
|
|
858
917
|
));
|
|
859
918
|
}
|
|
860
919
|
}
|
|
861
|
-
let uuid_prefix = identity
|
|
920
|
+
let uuid_prefix = identity
|
|
921
|
+
.leader_session_uuid
|
|
922
|
+
.as_str()
|
|
923
|
+
.chars()
|
|
924
|
+
.take(8)
|
|
925
|
+
.collect::<String>();
|
|
862
926
|
if reason == LeaseReason::PreviousOwnerPaneDead {
|
|
863
927
|
event_log.write(
|
|
864
928
|
super::LeaderEvent::OwnerAdoptedOnRestart.name(),
|
|
@@ -1105,7 +1169,10 @@ fn mark_convergence_persisted(metadata: &mut Value, team_id: &str) {
|
|
|
1105
1169
|
return;
|
|
1106
1170
|
};
|
|
1107
1171
|
obj.insert("persisted".to_string(), json!(true));
|
|
1108
|
-
obj.insert(
|
|
1172
|
+
obj.insert(
|
|
1173
|
+
"checked_paths".to_string(),
|
|
1174
|
+
json!(convergence_checked_paths(team_id)),
|
|
1175
|
+
);
|
|
1109
1176
|
}
|
|
1110
1177
|
|
|
1111
1178
|
fn mark_convergence_persistence_conflict(metadata: &mut Value, team_id: &str) {
|
|
@@ -1114,7 +1181,10 @@ fn mark_convergence_persistence_conflict(metadata: &mut Value, team_id: &str) {
|
|
|
1114
1181
|
};
|
|
1115
1182
|
obj.insert("status".to_string(), json!("persistence_conflict"));
|
|
1116
1183
|
obj.insert("persisted".to_string(), json!(false));
|
|
1117
|
-
obj.insert(
|
|
1184
|
+
obj.insert(
|
|
1185
|
+
"checked_paths".to_string(),
|
|
1186
|
+
json!(convergence_checked_paths(team_id)),
|
|
1187
|
+
);
|
|
1118
1188
|
obj.insert(
|
|
1119
1189
|
"action".to_string(),
|
|
1120
1190
|
json!("endpoint convergence was not durably persisted; retry claim-leader or takeover"),
|
|
@@ -1170,7 +1240,11 @@ fn refused(
|
|
|
1170
1240
|
owner: None,
|
|
1171
1241
|
owner_epoch: epoch,
|
|
1172
1242
|
reason: Some(reason),
|
|
1173
|
-
action: if action.is_empty() {
|
|
1243
|
+
action: if action.is_empty() {
|
|
1244
|
+
None
|
|
1245
|
+
} else {
|
|
1246
|
+
Some(action.to_string())
|
|
1247
|
+
},
|
|
1174
1248
|
bound_pane_id,
|
|
1175
1249
|
topology_convergence: None,
|
|
1176
1250
|
}
|
|
@@ -1178,7 +1252,8 @@ fn refused(
|
|
|
1178
1252
|
|
|
1179
1253
|
fn current_owner_epoch(state: &Value) -> OwnerEpoch {
|
|
1180
1254
|
let owner_epoch = get_path_u64(state, &["team_owner", "owner_epoch"]).filter(|v| *v != 0);
|
|
1181
|
-
let receiver_epoch =
|
|
1255
|
+
let receiver_epoch =
|
|
1256
|
+
get_path_u64(state, &["leader_receiver", "owner_epoch"]).filter(|v| *v != 0);
|
|
1182
1257
|
OwnerEpoch(owner_epoch.or(receiver_epoch).unwrap_or(0))
|
|
1183
1258
|
}
|
|
1184
1259
|
|
|
@@ -1189,7 +1264,9 @@ fn bound_pane(state: &Value) -> Option<String> {
|
|
|
1189
1264
|
}
|
|
1190
1265
|
|
|
1191
1266
|
fn bound_endpoint_matches_current_process(state: &Value) -> bool {
|
|
1192
|
-
let Some(bound) =
|
|
1267
|
+
let Some(bound) =
|
|
1268
|
+
get_path_str(state, &["leader_receiver", "tmux_socket"]).filter(|v| !v.is_empty())
|
|
1269
|
+
else {
|
|
1193
1270
|
return true;
|
|
1194
1271
|
};
|
|
1195
1272
|
let Some(current) = crate::tmux_backend::socket_name_from_tmux_env() else {
|
|
@@ -1316,7 +1393,11 @@ fn claim_target_from_pane_info(workspace: &Path, target: &PaneInfo) -> Option<Le
|
|
|
1316
1393
|
Some(LeaderClaimTarget {
|
|
1317
1394
|
provider,
|
|
1318
1395
|
leader_session_uuid: target_leader_session_uuid(target),
|
|
1319
|
-
team_id: target
|
|
1396
|
+
team_id: target
|
|
1397
|
+
.leader_env
|
|
1398
|
+
.get("TEAM_AGENT_TEAM_ID")
|
|
1399
|
+
.filter(|raw| !raw.is_empty())
|
|
1400
|
+
.cloned(),
|
|
1320
1401
|
pane_info: Some(target.clone()),
|
|
1321
1402
|
endpoint: None,
|
|
1322
1403
|
})
|
|
@@ -1359,7 +1440,10 @@ fn validate_attach_target(
|
|
|
1359
1440
|
.or_else(|| get_path_str(state, &["leader_receiver", "leader_session_uuid"]));
|
|
1360
1441
|
if let (Some(recorded), Some(target_uuid)) = (
|
|
1361
1442
|
recorded_uuid.as_deref(),
|
|
1362
|
-
claim_target
|
|
1443
|
+
claim_target
|
|
1444
|
+
.leader_session_uuid
|
|
1445
|
+
.as_ref()
|
|
1446
|
+
.map(|u| u.as_str()),
|
|
1363
1447
|
) {
|
|
1364
1448
|
if recorded != target_uuid {
|
|
1365
1449
|
return Err("leader_session_uuid_mismatch");
|
|
@@ -1472,15 +1556,19 @@ impl crate::state::owner_gate::PaneLivenessProbe for TargetScanLiveness {
|
|
|
1472
1556
|
}
|
|
1473
1557
|
}
|
|
1474
1558
|
|
|
1475
|
-
fn locked_runtime_state(
|
|
1559
|
+
fn locked_runtime_state(
|
|
1560
|
+
workspace: &Path,
|
|
1561
|
+
scoped_team: Option<&str>,
|
|
1562
|
+
) -> Result<Option<Value>, LeaderError> {
|
|
1476
1563
|
let path = crate::state::persist::runtime_state_path(workspace);
|
|
1477
1564
|
if !path.exists() {
|
|
1478
1565
|
return Ok(None);
|
|
1479
1566
|
}
|
|
1567
|
+
let raw = crate::state::persist::load_runtime_state_without_migrations(workspace)?;
|
|
1480
1568
|
let state = if let Some(team) = scoped_team {
|
|
1481
|
-
crate::state::projection::
|
|
1569
|
+
crate::state::projection::project_top_level_view(&raw, team)
|
|
1482
1570
|
} else {
|
|
1483
|
-
|
|
1571
|
+
raw
|
|
1484
1572
|
};
|
|
1485
1573
|
Ok(Some(state))
|
|
1486
1574
|
}
|
|
@@ -1537,9 +1625,13 @@ fn make_receiver(
|
|
|
1537
1625
|
provider,
|
|
1538
1626
|
pane_id: pane.as_pane_id().clone(),
|
|
1539
1627
|
session_name: target.as_ref().map(|t| t.session.clone()),
|
|
1540
|
-
window_index: target
|
|
1628
|
+
window_index: target
|
|
1629
|
+
.as_ref()
|
|
1630
|
+
.and_then(|t| t.window_index.map(|v| v.to_string())),
|
|
1541
1631
|
window_name: target.as_ref().and_then(|t| t.window_name.clone()),
|
|
1542
|
-
pane_index: target
|
|
1632
|
+
pane_index: target
|
|
1633
|
+
.as_ref()
|
|
1634
|
+
.and_then(|t| t.pane_index.map(|v| v.to_string())),
|
|
1543
1635
|
pane_tty: target.as_ref().and_then(|t| t.tty.clone()),
|
|
1544
1636
|
pane_current_command: target.as_ref().and_then(|t| t.current_command.clone()),
|
|
1545
1637
|
tmux_socket: crate::tmux_backend::socket_name_from_tmux_env(),
|
|
@@ -1557,8 +1649,12 @@ fn receiver_fingerprint(target: &PaneInfo) -> String {
|
|
|
1557
1649
|
format!(
|
|
1558
1650
|
"{}|{}|{}|{}",
|
|
1559
1651
|
target.session.as_str(),
|
|
1560
|
-
target
|
|
1561
|
-
|
|
1652
|
+
target
|
|
1653
|
+
.window_index
|
|
1654
|
+
.map_or_else(String::new, |v| v.to_string()),
|
|
1655
|
+
target
|
|
1656
|
+
.pane_index
|
|
1657
|
+
.map_or_else(String::new, |v| v.to_string()),
|
|
1562
1658
|
target.tty.as_deref().unwrap_or("")
|
|
1563
1659
|
)
|
|
1564
1660
|
}
|
|
@@ -1605,7 +1701,9 @@ fn write_binding_to_state(
|
|
|
1605
1701
|
*state = json!({});
|
|
1606
1702
|
}
|
|
1607
1703
|
if !state.is_object() {
|
|
1608
|
-
return Err(LeaderError::Validation(
|
|
1704
|
+
return Err(LeaderError::Validation(
|
|
1705
|
+
"state root is not an object".to_string(),
|
|
1706
|
+
));
|
|
1609
1707
|
}
|
|
1610
1708
|
let team_key = canonical_owner_write_key(state);
|
|
1611
1709
|
let record = crate::state::ownership::OwnershipWrite::new()
|
|
@@ -1624,7 +1722,9 @@ fn write_receiver_to_state(
|
|
|
1624
1722
|
*state = json!({});
|
|
1625
1723
|
}
|
|
1626
1724
|
if !state.is_object() {
|
|
1627
|
-
return Err(LeaderError::Validation(
|
|
1725
|
+
return Err(LeaderError::Validation(
|
|
1726
|
+
"state root is not an object".to_string(),
|
|
1727
|
+
));
|
|
1628
1728
|
}
|
|
1629
1729
|
let team_key = canonical_owner_write_key(state);
|
|
1630
1730
|
let record = crate::state::ownership::OwnershipWrite::new()
|
|
@@ -1787,8 +1887,12 @@ fn write_claim_state(
|
|
|
1787
1887
|
}
|
|
1788
1888
|
}
|
|
1789
1889
|
|
|
1790
|
-
fn save_claim_team_scoped_state(
|
|
1791
|
-
|
|
1890
|
+
fn save_claim_team_scoped_state(
|
|
1891
|
+
workspace: &Path,
|
|
1892
|
+
state: &Value,
|
|
1893
|
+
target_key: &str,
|
|
1894
|
+
) -> Result<(), LeaderError> {
|
|
1895
|
+
let existing = crate::state::persist::load_runtime_state_without_migrations(workspace)?;
|
|
1792
1896
|
let mut teams = existing
|
|
1793
1897
|
.get("teams")
|
|
1794
1898
|
.and_then(Value::as_object)
|
|
@@ -1924,10 +2028,14 @@ fn compact_team_state_preserving_claim_fields(state: &Value, target_key: &str) -
|
|
|
1924
2028
|
/// drifted. Every touch of the legacy path constants below is marked
|
|
1925
2029
|
/// `B0_DIAGNOSTIC_LEGACY_SNAPSHOT_READ` so the RED3 grep guard admits
|
|
1926
2030
|
/// them as documented exceptions.
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2031
|
+
type BP = Path;
|
|
2032
|
+
type BJ = Value;
|
|
2033
|
+
type BF = PathBuf;
|
|
2034
|
+
type B0 = Result<Option<Value>, LeaderError>;
|
|
2035
|
+
|
|
2036
|
+
pub fn detect_dual_state_divergence(w: &BP, s: &BJ) -> B0 /* B0_DIAGNOSTIC_LEGACY_SNAPSHOT_READ */ {
|
|
2037
|
+
let workspace = w;
|
|
2038
|
+
let state = s;
|
|
1931
2039
|
let Some(session_name) = state.get("session_name").and_then(Value::as_str) else {
|
|
1932
2040
|
return Ok(None);
|
|
1933
2041
|
};
|
|
@@ -1997,7 +2105,9 @@ fn agent_binding_summary(state: &Value) -> Value {
|
|
|
1997
2105
|
Value::Object(out)
|
|
1998
2106
|
}
|
|
1999
2107
|
|
|
2000
|
-
fn readable_team_snapshot_path(
|
|
2108
|
+
fn readable_team_snapshot_path(w: &BP, n: &str) -> BF /* B0_DIAGNOSTIC_LEGACY_SNAPSHOT_READ */ {
|
|
2109
|
+
let workspace = w;
|
|
2110
|
+
let session_name = n;
|
|
2001
2111
|
let safe_path = crate::lifecycle::helpers::team_snapshot_path(workspace, session_name); // B0_DIAGNOSTIC_LEGACY_SNAPSHOT_READ: reuses helpers safe legacy path.
|
|
2002
2112
|
if safe_path.exists() {
|
|
2003
2113
|
return safe_path;
|
|
@@ -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
|
}
|