@team-agent/installer 0.5.117 → 0.5.119
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/mod.rs +20 -0
- package/crates/team-agent/src/db/message_store.rs +10 -3
- package/crates/team-agent/src/leader/start.rs +27 -47
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +2 -7
- package/crates/team-agent/src/messaging/delivery.rs +157 -136
- package/crates/team-agent/src/messaging/delivery_consumption_tests.rs +223 -0
- package/crates/team-agent/src/messaging/enter_rescue.rs +182 -154
- package/crates/team-agent/src/messaging/enter_rescue_tests.rs +344 -277
- package/crates/team-agent/src/messaging/mod.rs +1 -1
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +6 -5
- package/crates/team-agent/src/messaging/tests/spine.rs +27 -28
- package/crates/team-agent/src/tmux_backend/enter_rescue.rs +325 -0
- package/crates/team-agent/src/tmux_backend/enter_rescue_tests.rs +432 -0
- package/crates/team-agent/src/tmux_backend/native_input.rs +19 -0
- package/crates/team-agent/src/tmux_backend/native_input_tests.rs +141 -4
- package/crates/team-agent/src/tmux_backend.rs +27 -0
- package/crates/team-agent/src/transport/enter_rescue.rs +166 -0
- package/crates/team-agent/src/transport/native_input.rs +136 -10
- package/crates/team-agent/src/transport/test_support.rs +25 -0
- package/crates/team-agent/src/transport.rs +34 -0
- package/package.json +4 -4
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
|
@@ -424,6 +424,26 @@ pub mod lifecycle_port {
|
|
|
424
424
|
});
|
|
425
425
|
if let Some(object) = value.as_object_mut() {
|
|
426
426
|
object.extend(facts);
|
|
427
|
+
// Only a plain Pi entry can be retried verbatim with this command.
|
|
428
|
+
// Never drop explicit attach/external intent or provider arguments,
|
|
429
|
+
// clear the caller's environment, or adopt the unverified pane.
|
|
430
|
+
if cfg!(unix)
|
|
431
|
+
&& provider == Provider::Pi
|
|
432
|
+
&& attach == &LeaderLauncherArgs::default()
|
|
433
|
+
{
|
|
434
|
+
let command = "env -u TMUX -u TMUX_PANE team-agent pi";
|
|
435
|
+
object.insert("recovery_command".to_string(), json!(command));
|
|
436
|
+
object.insert(
|
|
437
|
+
ACTION_FIELD.to_string(),
|
|
438
|
+
json!(format!(
|
|
439
|
+
"The inherited TMUX/TMUX_PANE could not be authorized for \
|
|
440
|
+
requested_workspace; this is not an empty-directory error. \
|
|
441
|
+
To explicitly retry from requested_workspace, run `{command}`. \
|
|
442
|
+
This removes the inherited variables only for the new process \
|
|
443
|
+
and does not detach or modify your current tmux pane. {action}"
|
|
444
|
+
)),
|
|
445
|
+
);
|
|
446
|
+
}
|
|
427
447
|
}
|
|
428
448
|
value
|
|
429
449
|
}
|
|
@@ -379,9 +379,10 @@ impl MessageStore {
|
|
|
379
379
|
status: &str,
|
|
380
380
|
error: Option<&str>,
|
|
381
381
|
) -> Result<(), MessageStoreError> {
|
|
382
|
-
let conn = crate::db::schema::open_db(&self.path)?;
|
|
382
|
+
let mut conn = crate::db::schema::open_db(&self.path)?;
|
|
383
|
+
let tx = conn.transaction()?;
|
|
383
384
|
let now = now_ts();
|
|
384
|
-
let changed =
|
|
385
|
+
let changed = tx.execute(
|
|
385
386
|
"update messages
|
|
386
387
|
set status = case
|
|
387
388
|
when status = 'acknowledged'
|
|
@@ -407,7 +408,7 @@ impl MessageStore {
|
|
|
407
408
|
params![message_id, status, now, error],
|
|
408
409
|
)?;
|
|
409
410
|
if changed == 0 {
|
|
410
|
-
let prior: Option<String> =
|
|
411
|
+
let prior: Option<String> = tx
|
|
411
412
|
.query_row(
|
|
412
413
|
"select status from messages where message_id = ?1",
|
|
413
414
|
params![message_id],
|
|
@@ -449,6 +450,12 @@ impl MessageStore {
|
|
|
449
450
|
)?;
|
|
450
451
|
return Ok(());
|
|
451
452
|
}
|
|
453
|
+
if status == "submitted_unverified" {
|
|
454
|
+
// Uncertainty and its independent safety net are one durable transition.
|
|
455
|
+
// Registration never depends on provider, parser, binding or paste proof.
|
|
456
|
+
crate::messaging::enter_rescue::schedule_in_tx(&tx, message_id, chrono::Utc::now())?;
|
|
457
|
+
}
|
|
458
|
+
tx.commit()?;
|
|
452
459
|
Ok(())
|
|
453
460
|
}
|
|
454
461
|
|
|
@@ -48,9 +48,6 @@ enum ManagedClientAttachMode {
|
|
|
48
48
|
SwitchClient,
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const DIFFERENT_TMUX_SERVER_PREFIX: &str =
|
|
52
|
-
"managed launcher refuses a different ambient tmux server";
|
|
53
|
-
|
|
54
51
|
impl PreparedLeaderStart {
|
|
55
52
|
pub(crate) fn plan(&self) -> &LeaderStartPlan {
|
|
56
53
|
&self.plan
|
|
@@ -141,8 +138,6 @@ pub(crate) fn prepare_leader_start_with_nested_attach(
|
|
|
141
138
|
let (ambient_authority, managed_client_attach_mode, managed_exec_provider) =
|
|
142
139
|
if explicit_external_path || state_external_path {
|
|
143
140
|
(ambient_pane_authority_preflight(workspace)?, None, false)
|
|
144
|
-
} else if workspace_state_is_managed_provider_reentry(workspace) {
|
|
145
|
-
(ambient_pane_authority_preflight(workspace)?, None, true)
|
|
146
141
|
} else {
|
|
147
142
|
managed_launcher_ambient_route(
|
|
148
143
|
provider,
|
|
@@ -159,7 +154,7 @@ pub(crate) fn prepare_leader_start_with_nested_attach(
|
|
|
159
154
|
confirm_attach,
|
|
160
155
|
attach_session,
|
|
161
156
|
external_leader,
|
|
162
|
-
|
|
157
|
+
ambient_authority.is_some(),
|
|
163
158
|
managed_client_attach_mode,
|
|
164
159
|
managed_exec_provider,
|
|
165
160
|
allow_nested_attach,
|
|
@@ -613,64 +608,49 @@ fn managed_launcher_ambient_route(
|
|
|
613
608
|
),
|
|
614
609
|
PrepareLeaderStartError,
|
|
615
610
|
> {
|
|
616
|
-
let
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
611
|
+
let isolated = || (None, Some(ManagedClientAttachMode::AttachSession), false);
|
|
612
|
+
let authority = match ambient_pane_authority_preflight(workspace) {
|
|
613
|
+
Ok(Some(authority)) => authority,
|
|
614
|
+
Ok(None) => return Ok(isolated()),
|
|
615
|
+
Err(PrepareLeaderStartError::PaneAuthorityRefused(_)) if !allow_nested_attach => {
|
|
616
|
+
// An inherited tuple cannot authorize adoption, but an ordinary
|
|
617
|
+
// managed launch can still create its own workspace pane. Keep
|
|
618
|
+
// explicit external/attach and nested-attach intent fail-closed.
|
|
619
|
+
return Ok(isolated());
|
|
620
|
+
}
|
|
621
|
+
Err(error) => return Err(error),
|
|
622
622
|
};
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
))
|
|
628
|
-
})?;
|
|
629
|
-
let observed_endpoint = validated_ambient_tmux_endpoint(workspace, &tmux)
|
|
630
|
-
.map_err(PrepareLeaderStartError::PaneAuthorityRefused)?;
|
|
631
|
-
if provider == Provider::Pi && provider_args.is_empty() && !allow_nested_attach {
|
|
632
|
-
// Pi's zero-argument managed entry already owns this verified pane as its
|
|
633
|
-
// user-facing terminal. Replacing the current process is safe on either
|
|
634
|
-
// the workspace server or another server; switching/attaching would move
|
|
635
|
-
// the user away from the positively verified workspace pane.
|
|
636
|
-
let authority = verified_ambient_pane_authority(workspace, &tmux)
|
|
637
|
-
.map_err(PrepareLeaderStartError::PaneAuthorityRefused)?;
|
|
623
|
+
if workspace_state_is_managed_provider_reentry(workspace)
|
|
624
|
+
|| (provider == Provider::Pi && provider_args.is_empty() && !allow_nested_attach)
|
|
625
|
+
{
|
|
626
|
+
// Only verified current-pane authority permits replacing the caller.
|
|
638
627
|
return Ok((Some(authority), None, true));
|
|
639
628
|
}
|
|
640
|
-
let target_socket_name = TmuxBackend::for_workspace(workspace)
|
|
641
|
-
.tmux_endpoint()
|
|
642
|
-
.ok_or_else(|| LeaderError::Start("workspace tmux endpoint missing".to_string()))?;
|
|
643
629
|
let target_endpoint = crate::tmux_backend::socket_path_for_workspace(workspace);
|
|
644
630
|
let same_server = target_endpoint.as_ref().is_some_and(|target| {
|
|
645
|
-
let observed = Path::new(&
|
|
631
|
+
let observed = Path::new(&authority.endpoint)
|
|
646
632
|
.canonicalize()
|
|
647
|
-
.unwrap_or_else(|_| PathBuf::from(&
|
|
633
|
+
.unwrap_or_else(|_| PathBuf::from(&authority.endpoint));
|
|
648
634
|
let target = target.canonicalize().unwrap_or_else(|_| target.clone());
|
|
649
635
|
observed == target
|
|
650
636
|
});
|
|
651
637
|
if same_server {
|
|
652
|
-
let authority = verified_ambient_pane_authority(workspace, &tmux)
|
|
653
|
-
.map_err(PrepareLeaderStartError::PaneAuthorityRefused)?;
|
|
654
638
|
return Ok((
|
|
655
639
|
Some(authority),
|
|
656
640
|
Some(ManagedClientAttachMode::SwitchClient),
|
|
657
641
|
false,
|
|
658
642
|
));
|
|
659
643
|
}
|
|
660
|
-
if
|
|
661
|
-
return
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
644
|
+
if allow_nested_attach {
|
|
645
|
+
return Ok((
|
|
646
|
+
Some(authority),
|
|
647
|
+
Some(ManagedClientAttachMode::AttachSession),
|
|
648
|
+
false,
|
|
649
|
+
));
|
|
666
650
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
Ok((
|
|
670
|
-
Some(authority),
|
|
671
|
-
Some(ManagedClientAttachMode::AttachSession),
|
|
672
|
-
false,
|
|
673
|
-
))
|
|
651
|
+
// Do not switch/adopt the unrelated server. The existing managed attach
|
|
652
|
+
// child clears TMUX/TMUX_PANE without changing the caller's environment.
|
|
653
|
+
Ok(isolated())
|
|
674
654
|
}
|
|
675
655
|
|
|
676
656
|
fn validated_ambient_tmux_endpoint(
|
|
@@ -4,7 +4,7 @@ use std::time::Duration;
|
|
|
4
4
|
|
|
5
5
|
use crate::lifecycle::launch::pi_mcp::pi_seat_paths;
|
|
6
6
|
use crate::messaging::delivery::{
|
|
7
|
-
paste_to_submit_floor_for_recipient, recipient_allows_explicit_queue_flush,
|
|
7
|
+
paste_to_submit_floor_for_recipient, recipient_allows_explicit_queue_flush,
|
|
8
8
|
recipient_requires_single_enter,
|
|
9
9
|
};
|
|
10
10
|
use crate::model::enums::{Provider, ProviderEffort};
|
|
@@ -186,17 +186,12 @@ fn pi_delivery_submits_one_enter_and_never_flushes_or_retries() {
|
|
|
186
186
|
paste_to_submit_floor_for_recipient(&idle, "worker-a"),
|
|
187
187
|
Duration::ZERO
|
|
188
188
|
);
|
|
189
|
-
assert!(!recipient_is_busy(&idle, "worker-a"));
|
|
190
|
-
|
|
191
189
|
let busy = serde_json::json!({
|
|
192
190
|
"agents": {
|
|
193
191
|
"worker-a": {"provider": "pi", "status": "busy"}
|
|
194
192
|
}
|
|
195
193
|
});
|
|
196
|
-
|
|
197
|
-
recipient_is_busy(&busy, "worker-a"),
|
|
198
|
-
"known busy must take the existing send.deferred_busy path before paste"
|
|
199
|
-
);
|
|
194
|
+
// Working native CLIs queue the same single-Enter input; no idle admission.
|
|
200
195
|
assert!(recipient_requires_single_enter(&busy, "worker-a"));
|
|
201
196
|
assert!(!recipient_allows_explicit_queue_flush(&busy, "worker-a"));
|
|
202
197
|
}
|