@team-agent/installer 0.3.12 → 0.3.14
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 +23 -0
- package/crates/team-agent/src/cli/leader.rs +2 -5
- package/crates/team-agent/src/cli/mod.rs +258 -79
- package/crates/team-agent/src/cli/tests/compile.rs +69 -0
- package/crates/team-agent/src/cli/tests/main_preserved.rs +68 -0
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +160 -2
- package/crates/team-agent/src/compiler.rs +30 -0
- package/crates/team-agent/src/coordinator/tick.rs +16 -83
- package/crates/team-agent/src/leader/lease.rs +4 -20
- package/crates/team-agent/src/leader/mod.rs +3 -1
- package/crates/team-agent/src/leader/owner_bind.rs +46 -48
- package/crates/team-agent/src/leader/provider_attribution.rs +214 -0
- package/crates/team-agent/src/leader/rediscover/tests.rs +3 -3
- package/crates/team-agent/src/leader/rediscover.rs +34 -17
- package/crates/team-agent/src/leader/start.rs +279 -4
- package/crates/team-agent/src/lifecycle/launch.rs +279 -24
- package/crates/team-agent/src/lifecycle/restart/common.rs +50 -40
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +343 -49
- package/crates/team-agent/src/lifecycle/restart/selection.rs +9 -6
- package/crates/team-agent/src/lifecycle/tests/core.rs +181 -45
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +390 -61
- package/crates/team-agent/src/lifecycle/types.rs +28 -0
- package/crates/team-agent/src/messaging/delivery.rs +5 -1
- package/crates/team-agent/src/messaging/helpers.rs +1 -1
- package/crates/team-agent/src/messaging/tests/runtime.rs +14 -0
- package/crates/team-agent/src/provider/adapter.rs +6 -3
- package/crates/team-agent/src/provider/startup_prompt.rs +173 -0
- package/crates/team-agent/src/provider/testdata/copilot-ready-marker.txt +5 -0
- package/crates/team-agent/src/provider/testdata/copilot-trust-prompt.txt +19 -0
- package/crates/team-agent/src/tmux_backend.rs +5 -0
- package/crates/team-agent/src/transport/test_support.rs +22 -7
- package/crates/team-agent/src/transport.rs +4 -0
- package/package.json +4 -4
|
@@ -620,6 +620,23 @@ pub enum RestartReport {
|
|
|
620
620
|
next_actions: Vec<String>,
|
|
621
621
|
attach_commands: Vec<String>,
|
|
622
622
|
},
|
|
623
|
+
/// At least one worker failed during live spawn, but other workers were isolated
|
|
624
|
+
/// and restarted. The CLI reports `status=partial` and exits non-zero.
|
|
625
|
+
Partial {
|
|
626
|
+
session_name: SessionName,
|
|
627
|
+
agents: Vec<RestartedAgent>,
|
|
628
|
+
failed_agents: Vec<RestartFailedAgent>,
|
|
629
|
+
coordinator_started: bool,
|
|
630
|
+
next_actions: Vec<String>,
|
|
631
|
+
attach_commands: Vec<String>,
|
|
632
|
+
},
|
|
633
|
+
/// All workers failed during live spawn. No worker is reported as restarted.
|
|
634
|
+
Failed {
|
|
635
|
+
session_name: SessionName,
|
|
636
|
+
failed_agents: Vec<RestartFailedAgent>,
|
|
637
|
+
next_actions: Vec<String>,
|
|
638
|
+
attach_commands: Vec<String>,
|
|
639
|
+
},
|
|
623
640
|
/// atomic refusal(`reason=resume_atomicity`):某 interacted worker 不可 resume
|
|
624
641
|
/// 且非 allow_fresh。**nothing created yet**,无需回滚。
|
|
625
642
|
RefusedResumeAtomicity {
|
|
@@ -652,6 +669,17 @@ pub struct RestartedAgent {
|
|
|
652
669
|
pub session_id: Option<SessionId>,
|
|
653
670
|
}
|
|
654
671
|
|
|
672
|
+
/// Worker restart failure isolated from the rest of the restart pass.
|
|
673
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
674
|
+
pub struct RestartFailedAgent {
|
|
675
|
+
pub agent_id: AgentId,
|
|
676
|
+
pub restart_mode: StartMode,
|
|
677
|
+
pub decision: ResumeDecision,
|
|
678
|
+
pub session_id: Option<SessionId>,
|
|
679
|
+
pub phase: String,
|
|
680
|
+
pub error: String,
|
|
681
|
+
}
|
|
682
|
+
|
|
655
683
|
/// atomic refusal 里的不可重建 worker(`orchestration.py:517`)。
|
|
656
684
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
657
685
|
pub struct UnresumableWorker {
|
|
@@ -744,7 +744,7 @@ fn recipient_pane_has_actionable_startup_prompt(
|
|
|
744
744
|
let provider = agent
|
|
745
745
|
.and_then(|agent| agent.get("provider"))
|
|
746
746
|
.and_then(serde_json::Value::as_str);
|
|
747
|
-
if !matches!(provider, Some("codex" | "claude" | "claude_code")) {
|
|
747
|
+
if !matches!(provider, Some("codex" | "claude" | "claude_code" | "copilot")) {
|
|
748
748
|
return false;
|
|
749
749
|
}
|
|
750
750
|
// step2-retry/scrollback root-cause (rt binary 6c9c6c1c): once the agent's
|
|
@@ -777,6 +777,10 @@ fn recipient_pane_has_actionable_startup_prompt(
|
|
|
777
777
|
crate::provider::classify_claude_startup_screen(&captured),
|
|
778
778
|
crate::provider::StartupScreenDecision::AnswerWorkspaceTrust
|
|
779
779
|
),
|
|
780
|
+
Some("copilot") => matches!(
|
|
781
|
+
crate::provider::classify_copilot_startup_screen(&captured),
|
|
782
|
+
crate::provider::StartupScreenDecision::AnswerWorkspaceTrust
|
|
783
|
+
),
|
|
780
784
|
_ => false,
|
|
781
785
|
}
|
|
782
786
|
}
|
|
@@ -123,7 +123,7 @@ pub(crate) fn non_provider_command(command: &str) -> Option<&str> {
|
|
|
123
123
|
let base = command.rsplit('/').next().unwrap_or(command);
|
|
124
124
|
let normalized = base.to_ascii_lowercase();
|
|
125
125
|
match normalized.as_str() {
|
|
126
|
-
"" | "codex" | "claude" | "gemini" | "openai" | "team-agent" => None,
|
|
126
|
+
"" | "codex" | "claude" | "copilot" | "gemini" | "openai" | "team-agent" => None,
|
|
127
127
|
_ => Some(base),
|
|
128
128
|
}
|
|
129
129
|
}
|
|
@@ -174,6 +174,20 @@ fn classify_idle_prompt_beats_recent_output_for_just_launched_agent() {
|
|
|
174
174
|
);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
#[test]
|
|
178
|
+
fn classify_copilot_idle_prompt_is_not_masked_by_current_command() {
|
|
179
|
+
let state = json(serde_json::json!({}));
|
|
180
|
+
let a = classify_agent_activity(
|
|
181
|
+
&state,
|
|
182
|
+
" / commands · ? help\n❯ \n",
|
|
183
|
+
false,
|
|
184
|
+
Some("copilot"),
|
|
185
|
+
None,
|
|
186
|
+
);
|
|
187
|
+
assert_eq!(a.status, ActivityStatus::Idle);
|
|
188
|
+
assert_eq!(a.confidence, 0.9);
|
|
189
|
+
}
|
|
190
|
+
|
|
177
191
|
// ════════════════════════════════════════════════════════════════════════
|
|
178
192
|
// GROUP H — attempt_trust_auto_answer: own-vs-foreign realpath + fail-safe
|
|
179
193
|
// pane-width + opt-in gate + reason byte-locks. leader_panes.py:383-470.
|
|
@@ -233,6 +233,11 @@ pub trait ProviderAdapter {
|
|
|
233
233
|
transport, target, checks, sleep_s,
|
|
234
234
|
)
|
|
235
235
|
}
|
|
236
|
+
Provider::Copilot => {
|
|
237
|
+
super::startup_prompt::copilot_handle_startup_prompts(
|
|
238
|
+
transport, target, checks, sleep_s,
|
|
239
|
+
)
|
|
240
|
+
}
|
|
236
241
|
_ => super::startup_prompt::StartupPromptOutcome::default(),
|
|
237
242
|
}))
|
|
238
243
|
.unwrap_or_default()
|
|
@@ -869,9 +874,7 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
869
874
|
match self.provider {
|
|
870
875
|
Provider::Claude | Provider::ClaudeCode => patterns(r"[>❯]\s", r"[✶✢✽✻✳·].*…", r"Error|Traceback"),
|
|
871
876
|
Provider::Codex => patterns(r"(›|❯|codex>)", r"•.*esc to interrupt", r"Error|Traceback|panic"),
|
|
872
|
-
|
|
873
|
-
// 仅 error 行至少能识别;idle/processing 留 Unknown(N11 守,classify→None)。
|
|
874
|
-
Provider::Copilot => patterns(r">", r"working|processing", r"Error|panic"),
|
|
877
|
+
Provider::Copilot => patterns(r"(?m)^\s*❯\s*$| / commands · \? help", r"working|processing", r"Error|panic"),
|
|
875
878
|
Provider::GeminiCli | Provider::Fake => patterns(r">", r"working|processing", r"Error|Traceback"),
|
|
876
879
|
}
|
|
877
880
|
}
|
|
@@ -28,6 +28,18 @@ const CLAUDE_TRUST_MARKERS: &[&str] = &[
|
|
|
28
28
|
"Enter to confirm",
|
|
29
29
|
];
|
|
30
30
|
const CLAUDE_READY_MARKERS: &[&str] = &["Claude Code"];
|
|
31
|
+
pub const COPILOT_TRUST_PROMPT_MARKER: &str =
|
|
32
|
+
include_str!("testdata/copilot-trust-prompt.txt");
|
|
33
|
+
pub const COPILOT_READY_MARKER: &str =
|
|
34
|
+
include_str!("testdata/copilot-ready-marker.txt");
|
|
35
|
+
const COPILOT_TRUST_MARKERS: &[&str] = &[COPILOT_TRUST_PROMPT_MARKER];
|
|
36
|
+
const COPILOT_READY_MARKERS: &[&str] = &[COPILOT_READY_MARKER];
|
|
37
|
+
const COPILOT_TRUST_TITLE: &str = "Confirm folder trust";
|
|
38
|
+
const COPILOT_TRUST_QUESTION: &str = "Do you trust the files in this folder?";
|
|
39
|
+
const COPILOT_TRUST_YES_SESSION: &str = "❯ 1. Yes";
|
|
40
|
+
const COPILOT_TRUST_REMEMBER: &str = "2. Yes, and remember this folder for future sessions";
|
|
41
|
+
const COPILOT_TRUST_NO: &str = "3. No (Esc)";
|
|
42
|
+
const COPILOT_READY_FOOTER: &str = " / commands · ? help";
|
|
31
43
|
/// Plain ready markers (not the bare `›` glyph — that glyph also indicates a
|
|
32
44
|
/// numbered-menu selector and is handled by [`rightmost_input_prompt_glyph`] with
|
|
33
45
|
/// shape gating per N15 / CR-063: detect by SHAPE, not a single Unicode codepoint).
|
|
@@ -106,6 +118,20 @@ pub fn classify_claude_startup_screen(output: &str) -> StartupScreenDecision {
|
|
|
106
118
|
}
|
|
107
119
|
}
|
|
108
120
|
|
|
121
|
+
pub fn classify_copilot_startup_screen(output: &str) -> StartupScreenDecision {
|
|
122
|
+
let ready_pos = copilot_ready_pos(output);
|
|
123
|
+
if has_active_copilot_trust_shape(output)
|
|
124
|
+
&& is_more_recent(copilot_trust_pos(output), ready_pos)
|
|
125
|
+
{
|
|
126
|
+
return StartupScreenDecision::AnswerWorkspaceTrust;
|
|
127
|
+
}
|
|
128
|
+
if ready_pos.is_some() {
|
|
129
|
+
StartupScreenDecision::Ready
|
|
130
|
+
} else {
|
|
131
|
+
StartupScreenDecision::KeepPolling
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
109
135
|
/// Actionable trust shape (N15): the captured text contains a trust phrase AND a
|
|
110
136
|
/// numbered-menu selector line `› <digit>. `. This is the modal-still-active signal
|
|
111
137
|
/// that survives Codex's pre-rendering of the banner/input prompt below the menu.
|
|
@@ -161,6 +187,53 @@ fn has_claude_ready_shape(output: &str) -> bool {
|
|
|
161
187
|
&& rightmost_claude_input_prompt_glyph(output).is_some()
|
|
162
188
|
}
|
|
163
189
|
|
|
190
|
+
fn has_active_copilot_trust_shape(output: &str) -> bool {
|
|
191
|
+
if max_rfind(output, COPILOT_TRUST_MARKERS).is_some() {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
[
|
|
195
|
+
COPILOT_TRUST_TITLE,
|
|
196
|
+
COPILOT_TRUST_QUESTION,
|
|
197
|
+
COPILOT_TRUST_YES_SESSION,
|
|
198
|
+
COPILOT_TRUST_REMEMBER,
|
|
199
|
+
COPILOT_TRUST_NO,
|
|
200
|
+
]
|
|
201
|
+
.iter()
|
|
202
|
+
.all(|marker| output.contains(marker))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fn copilot_trust_pos(output: &str) -> Option<usize> {
|
|
206
|
+
max_two(
|
|
207
|
+
max_rfind(output, COPILOT_TRUST_MARKERS),
|
|
208
|
+
max_rfind(output, &[COPILOT_TRUST_TITLE, COPILOT_TRUST_QUESTION]),
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fn copilot_ready_pos(output: &str) -> Option<usize> {
|
|
213
|
+
let fixture_pos = max_rfind(output, COPILOT_READY_MARKERS);
|
|
214
|
+
let footer_pos = output.rfind(COPILOT_READY_FOOTER);
|
|
215
|
+
let prompt_pos = rightmost_copilot_input_prompt_glyph(output);
|
|
216
|
+
if footer_pos.is_some() && prompt_pos.is_some() {
|
|
217
|
+
max_two(fixture_pos, max_two(footer_pos, prompt_pos))
|
|
218
|
+
} else {
|
|
219
|
+
fixture_pos
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
fn rightmost_copilot_input_prompt_glyph(output: &str) -> Option<usize> {
|
|
224
|
+
let mut best = None;
|
|
225
|
+
let mut offset = 0;
|
|
226
|
+
for line in output.split_inclusive('\n') {
|
|
227
|
+
if line.trim() == "❯" {
|
|
228
|
+
if let Some(idx) = line.find('❯') {
|
|
229
|
+
best = Some(offset + idx);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
offset += line.len();
|
|
233
|
+
}
|
|
234
|
+
best
|
|
235
|
+
}
|
|
236
|
+
|
|
164
237
|
fn rightmost_claude_input_prompt_glyph(output: &str) -> Option<usize> {
|
|
165
238
|
let mut best = rightmost_input_prompt_for_glyph(output, '❯');
|
|
166
239
|
best = max_two(best, rightmost_input_prompt_for_glyph(output, '>'));
|
|
@@ -316,6 +389,40 @@ pub fn claude_handle_startup_prompts(
|
|
|
316
389
|
StartupPromptOutcome { handled, capture_error }
|
|
317
390
|
}
|
|
318
391
|
|
|
392
|
+
pub fn copilot_handle_startup_prompts(
|
|
393
|
+
transport: &dyn Transport,
|
|
394
|
+
target: &Target,
|
|
395
|
+
checks: usize,
|
|
396
|
+
sleep_s: f64,
|
|
397
|
+
) -> StartupPromptOutcome {
|
|
398
|
+
let mut handled = Vec::new();
|
|
399
|
+
let mut capture_error: Option<String> = None;
|
|
400
|
+
for _ in 0..checks {
|
|
401
|
+
let screen = match transport.capture(target, CaptureRange::Full) {
|
|
402
|
+
Ok(captured) => captured.text,
|
|
403
|
+
Err(error) => {
|
|
404
|
+
capture_error.get_or_insert_with(|| error.to_string());
|
|
405
|
+
String::new()
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
match classify_copilot_startup_screen(&screen) {
|
|
409
|
+
StartupScreenDecision::AnswerWorkspaceTrust => {
|
|
410
|
+
let _ = transport.send_keys(target, &[Key::Enter]);
|
|
411
|
+
handled.push(HandledPrompt {
|
|
412
|
+
prompt: "copilot_workspace_trust".to_string(),
|
|
413
|
+
action: "sent_enter_yes_session".to_string(),
|
|
414
|
+
});
|
|
415
|
+
sleep_between_polls(sleep_s);
|
|
416
|
+
}
|
|
417
|
+
StartupScreenDecision::Ready => break,
|
|
418
|
+
StartupScreenDecision::SkipUpdatePrompt | StartupScreenDecision::KeepPolling => {
|
|
419
|
+
sleep_between_polls(sleep_s);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
StartupPromptOutcome { handled, capture_error }
|
|
424
|
+
}
|
|
425
|
+
|
|
319
426
|
fn max_rfind(output: &str, needles: &[&str]) -> Option<usize> {
|
|
320
427
|
needles.iter().filter_map(|needle| output.rfind(needle)).max()
|
|
321
428
|
}
|
|
@@ -445,6 +552,44 @@ mod tests {
|
|
|
445
552
|
);
|
|
446
553
|
}
|
|
447
554
|
|
|
555
|
+
#[test]
|
|
556
|
+
fn copilot_trust_fixture_answers_yes_for_this_session() {
|
|
557
|
+
assert_eq!(
|
|
558
|
+
classify_copilot_startup_screen(COPILOT_TRUST_PROMPT_MARKER),
|
|
559
|
+
StartupScreenDecision::AnswerWorkspaceTrust
|
|
560
|
+
);
|
|
561
|
+
assert!(
|
|
562
|
+
COPILOT_TRUST_PROMPT_MARKER.contains(COPILOT_TRUST_YES_SESSION),
|
|
563
|
+
"copilot trust default must remain option 1, not persistent option 2"
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
#[test]
|
|
568
|
+
fn copilot_trust_prompt_tolerates_path_variation() {
|
|
569
|
+
let dir_b = COPILOT_TRUST_PROMPT_MARKER.replace("/dir-a", "/dir-b");
|
|
570
|
+
assert_eq!(
|
|
571
|
+
classify_copilot_startup_screen(&dir_b),
|
|
572
|
+
StartupScreenDecision::AnswerWorkspaceTrust
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
#[test]
|
|
577
|
+
fn copilot_ready_fixture_is_ready_without_trust_prompt() {
|
|
578
|
+
assert_eq!(
|
|
579
|
+
classify_copilot_startup_screen(COPILOT_READY_MARKER),
|
|
580
|
+
StartupScreenDecision::Ready
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
#[test]
|
|
585
|
+
fn copilot_ready_after_stale_trust_wins() {
|
|
586
|
+
let screen = format!("{COPILOT_TRUST_PROMPT_MARKER}\n{COPILOT_READY_MARKER}");
|
|
587
|
+
assert_eq!(
|
|
588
|
+
classify_copilot_startup_screen(&screen),
|
|
589
|
+
StartupScreenDecision::Ready
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
|
|
448
593
|
// ── ④ transport.capture() SEAM — the loop answers trust then breaks on ready, via the seam ───────
|
|
449
594
|
/// Scripted transport: `capture` pops the next canned screen; `send_keys` records the keys. All
|
|
450
595
|
/// other methods are unreachable by the startup-prompt loop.
|
|
@@ -532,4 +677,32 @@ mod tests {
|
|
|
532
677
|
"on workspace-trust the loop must send Enter via the transport.capture() seam; got {sent:?}"
|
|
533
678
|
);
|
|
534
679
|
}
|
|
680
|
+
|
|
681
|
+
#[test]
|
|
682
|
+
fn copilot_loop_answers_trust_then_breaks_on_ready_via_capture_seam() {
|
|
683
|
+
let t = ScriptedTransport {
|
|
684
|
+
screens: Mutex::new(vec![
|
|
685
|
+
COPILOT_TRUST_PROMPT_MARKER.to_string(),
|
|
686
|
+
COPILOT_READY_MARKER.to_string(),
|
|
687
|
+
]),
|
|
688
|
+
sent: Mutex::new(Vec::new()),
|
|
689
|
+
};
|
|
690
|
+
let target = Target::Pane(PaneId::new("%1"));
|
|
691
|
+
|
|
692
|
+
let handled = copilot_handle_startup_prompts(&t, &target, 5, 0.0).handled;
|
|
693
|
+
|
|
694
|
+
assert_eq!(
|
|
695
|
+
handled,
|
|
696
|
+
vec![HandledPrompt {
|
|
697
|
+
prompt: "copilot_workspace_trust".to_string(),
|
|
698
|
+
action: "sent_enter_yes_session".to_string(),
|
|
699
|
+
}]
|
|
700
|
+
);
|
|
701
|
+
let sent = t.sent.lock().unwrap();
|
|
702
|
+
assert_eq!(
|
|
703
|
+
sent.iter().filter(|keys| keys.as_slice() == [Key::Enter]).count(),
|
|
704
|
+
1,
|
|
705
|
+
"copilot trust prompt must choose option 1 with one Enter; got {sent:?}"
|
|
706
|
+
);
|
|
707
|
+
}
|
|
535
708
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
2
|
+
│ Confirm folder trust │
|
|
3
|
+
│ ──────────────────────────────────────────────────────────────────────────── │
|
|
4
|
+
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
|
5
|
+
│ │ /private/tmp/copilot-trust-fixture-20260611T160323Z/dir-a │ │
|
|
6
|
+
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
|
7
|
+
│ │
|
|
8
|
+
│ Copilot can read files in this folder and, with your permission, edit them │
|
|
9
|
+
│ or run code and shell commands. It will remember your permissions for the │
|
|
10
|
+
│ rest of this session. │
|
|
11
|
+
│ │
|
|
12
|
+
│ Do you trust the files in this folder? │
|
|
13
|
+
│ │
|
|
14
|
+
│ ❯ 1. Yes │
|
|
15
|
+
│ 2. Yes, and remember this folder for future sessions │
|
|
16
|
+
│ 3. No (Esc) │
|
|
17
|
+
│ │
|
|
18
|
+
│ ↑/↓ to navigate · enter to select · esc to cancel │
|
|
19
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
@@ -1041,6 +1041,11 @@ impl Transport for TmuxBackend {
|
|
|
1041
1041
|
Ok(SetEnvOutcome::Applied)
|
|
1042
1042
|
}
|
|
1043
1043
|
|
|
1044
|
+
fn kill_server(&self) -> Result<(), TransportError> {
|
|
1045
|
+
TmuxBackend::kill_server(self);
|
|
1046
|
+
Ok(())
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1044
1049
|
fn kill_session(&self, session: &SessionName) -> Result<(), TransportError> {
|
|
1045
1050
|
let argv = self.tmux_argv(&[
|
|
1046
1051
|
"tmux".to_string(),
|
|
@@ -25,6 +25,7 @@ struct OfflineState {
|
|
|
25
25
|
targets: Vec<PaneInfo>,
|
|
26
26
|
windows: Vec<WindowName>,
|
|
27
27
|
pane_presence: BTreeMap<String, bool>,
|
|
28
|
+
spawn_failures: BTreeMap<String, String>,
|
|
28
29
|
spawned_panes_addressable: bool,
|
|
29
30
|
liveness: BTreeMap<String, PaneLiveness>,
|
|
30
31
|
default_liveness: PaneLiveness,
|
|
@@ -42,6 +43,7 @@ impl Default for OfflineState {
|
|
|
42
43
|
targets: Vec::new(),
|
|
43
44
|
windows: Vec::new(),
|
|
44
45
|
pane_presence: BTreeMap::new(),
|
|
46
|
+
spawn_failures: BTreeMap::new(),
|
|
45
47
|
spawned_panes_addressable: true,
|
|
46
48
|
liveness: BTreeMap::new(),
|
|
47
49
|
default_liveness: PaneLiveness::Unknown,
|
|
@@ -102,6 +104,13 @@ impl OfflineTransport {
|
|
|
102
104
|
self
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
pub fn with_spawn_failure(self, window: impl Into<String>, error: impl Into<String>) -> Self {
|
|
108
|
+
self.with_state(|state| {
|
|
109
|
+
state.spawn_failures.insert(window.into(), error.into());
|
|
110
|
+
});
|
|
111
|
+
self
|
|
112
|
+
}
|
|
113
|
+
|
|
105
114
|
pub fn with_spawned_panes_addressable(self, present: bool) -> Self {
|
|
106
115
|
self.with_state(|state| state.spawned_panes_addressable = present);
|
|
107
116
|
self
|
|
@@ -149,10 +158,16 @@ impl OfflineTransport {
|
|
|
149
158
|
session: &SessionName,
|
|
150
159
|
window: &WindowName,
|
|
151
160
|
argv: &[String],
|
|
152
|
-
) -> SpawnResult {
|
|
161
|
+
) -> Result<SpawnResult, TransportError> {
|
|
153
162
|
let pane_index = self.with_state(|state| {
|
|
154
163
|
state.calls.push(kind);
|
|
155
164
|
state.spawns.push(SpawnRecord { kind: kind.to_string(), argv: argv.to_vec() });
|
|
165
|
+
if let Some(error) = state.spawn_failures.get(window.as_str()) {
|
|
166
|
+
return Err(TransportError::Spawn {
|
|
167
|
+
backend: BackendKind::Tmux,
|
|
168
|
+
source: std::io::Error::other(error.clone()),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
156
171
|
if kind == "spawn_first" && !state.session_absent_after_spawn_first {
|
|
157
172
|
state.session_present = true;
|
|
158
173
|
}
|
|
@@ -160,14 +175,14 @@ impl OfflineTransport {
|
|
|
160
175
|
state
|
|
161
176
|
.pane_presence
|
|
162
177
|
.insert(format!("%{pane_index}"), state.spawned_panes_addressable);
|
|
163
|
-
pane_index
|
|
164
|
-
})
|
|
165
|
-
SpawnResult {
|
|
178
|
+
Ok(pane_index)
|
|
179
|
+
})?;
|
|
180
|
+
Ok(SpawnResult {
|
|
166
181
|
pane_id: PaneId::new(format!("%{pane_index}")),
|
|
167
182
|
session: session.clone(),
|
|
168
183
|
window: window.clone(),
|
|
169
184
|
child_pid: None,
|
|
170
|
-
}
|
|
185
|
+
})
|
|
171
186
|
}
|
|
172
187
|
|
|
173
188
|
fn inject_report() -> InjectReport {
|
|
@@ -194,7 +209,7 @@ impl Transport for OfflineTransport {
|
|
|
194
209
|
_cwd: &Path,
|
|
195
210
|
_env: &BTreeMap<String, String>,
|
|
196
211
|
) -> Result<SpawnResult, TransportError> {
|
|
197
|
-
|
|
212
|
+
self.spawn_result("spawn_first", session, window, argv)
|
|
198
213
|
}
|
|
199
214
|
|
|
200
215
|
fn spawn_into(
|
|
@@ -205,7 +220,7 @@ impl Transport for OfflineTransport {
|
|
|
205
220
|
_cwd: &Path,
|
|
206
221
|
_env: &BTreeMap<String, String>,
|
|
207
222
|
) -> Result<SpawnResult, TransportError> {
|
|
208
|
-
|
|
223
|
+
self.spawn_result("spawn_into", session, window, argv)
|
|
209
224
|
}
|
|
210
225
|
|
|
211
226
|
fn inject(
|
|
@@ -519,6 +519,10 @@ pub trait Transport: Send + Sync {
|
|
|
519
519
|
|
|
520
520
|
// —— LIFECYCLE(SL)——
|
|
521
521
|
|
|
522
|
+
fn kill_server(&self) -> Result<(), TransportError> {
|
|
523
|
+
Ok(())
|
|
524
|
+
}
|
|
525
|
+
|
|
522
526
|
fn kill_session(&self, session: &SessionName) -> Result<(), TransportError>;
|
|
523
527
|
|
|
524
528
|
fn kill_window(&self, target: &Target) -> Result<(), TransportError>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "npx installer for Team Agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codex",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"team-agent-installer": "npm/install.mjs"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@team-agent/cli-darwin-arm64": "0.3.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.3.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.3.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.3.14",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.3.14",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.3.14"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|