@team-agent/installer 0.3.21 → 0.3.23
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/emit.rs +9 -1
- package/crates/team-agent/src/cli/mod.rs +158 -2
- package/crates/team-agent/src/cli/status.rs +61 -0
- package/crates/team-agent/src/cli/status_port.rs +2 -2
- package/crates/team-agent/src/cli/tests/base.rs +26 -0
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +233 -9
- package/crates/team-agent/src/coordinator/tests/energy.rs +548 -0
- package/crates/team-agent/src/coordinator/tests/mod.rs +1 -0
- package/crates/team-agent/src/coordinator/tick.rs +452 -17
- package/crates/team-agent/src/lifecycle/display.rs +23 -302
- package/crates/team-agent/src/lifecycle/launch.rs +38 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +27 -6
- package/crates/team-agent/src/lifecycle/restart/common.rs +179 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +295 -13
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +30 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +89 -5
- package/crates/team-agent/src/messaging/delivery.rs +324 -95
- package/crates/team-agent/src/messaging/scheduler.rs +95 -73
- package/crates/team-agent/src/messaging/send.rs +10 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +460 -0
- package/crates/team-agent/src/messaging/tests/spine.rs +51 -2
- package/crates/team-agent/src/session_capture.rs +261 -1
- package/crates/team-agent/src/tmux_backend/tests.rs +97 -1
- package/crates/team-agent/src/tmux_backend.rs +138 -2
- package/crates/team-agent/src/transport/test_support.rs +25 -0
- package/crates/team-agent/src/transport.rs +11 -0
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +2 -1
- package/skills/team-agent/references/team-in-team.md +127 -0
|
@@ -105,6 +105,7 @@ pub fn rebuild_adaptive_display_after_rebind(
|
|
|
105
105
|
struct DisplayTarget {
|
|
106
106
|
agent_id: String,
|
|
107
107
|
window: Option<WindowName>,
|
|
108
|
+
pane_id: Option<PaneId>,
|
|
108
109
|
role: Option<String>,
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -128,6 +129,11 @@ fn worker_display_targets(
|
|
|
128
129
|
targets.push(DisplayTarget {
|
|
129
130
|
agent_id: agent_id.clone(),
|
|
130
131
|
window: Some(WindowName::new(window)),
|
|
132
|
+
pane_id: agent
|
|
133
|
+
.get("pane_id")
|
|
134
|
+
.and_then(serde_json::Value::as_str)
|
|
135
|
+
.filter(|pane| !pane.is_empty())
|
|
136
|
+
.map(PaneId::new),
|
|
131
137
|
role: agent
|
|
132
138
|
.get("role")
|
|
133
139
|
.and_then(serde_json::Value::as_str)
|
|
@@ -190,81 +196,26 @@ fn open_adaptive_worker_displays(
|
|
|
190
196
|
probe: &DisplayProbe,
|
|
191
197
|
) -> Result<BTreeMap<String, WorkerDisplay>, LifecycleError> {
|
|
192
198
|
let targets = worker_display_targets(workspace, session_name)?;
|
|
193
|
-
|
|
194
|
-
return Ok(BTreeMap::new());
|
|
195
|
-
}
|
|
196
|
-
if probe.adaptive_status != DisplayStatus::Opened {
|
|
197
|
-
return Ok(targets
|
|
198
|
-
.into_iter()
|
|
199
|
-
.map(|target| {
|
|
200
|
-
(
|
|
201
|
-
target.agent_id,
|
|
202
|
-
WorkerDisplay::Blocked {
|
|
203
|
-
reason: probe
|
|
204
|
-
.reason
|
|
205
|
-
.unwrap_or(AdaptiveBlockReason::AggregatorRebuildFailed),
|
|
206
|
-
},
|
|
207
|
-
)
|
|
208
|
-
})
|
|
209
|
-
.collect());
|
|
210
|
-
}
|
|
211
|
-
let Some(leader_session) = probe.leader_session.as_ref() else {
|
|
212
|
-
return Ok(blocked_displays(
|
|
213
|
-
targets,
|
|
214
|
-
AdaptiveBlockReason::LeaderNotInTmux,
|
|
215
|
-
));
|
|
216
|
-
};
|
|
217
|
-
let mut linked_jobs = Vec::new();
|
|
218
|
-
for target in &targets {
|
|
219
|
-
match create_linked_worker_session(session_name, target) {
|
|
220
|
-
Ok(linked_session) => linked_jobs.push((target.clone(), linked_session)),
|
|
221
|
-
Err(reason) => {
|
|
222
|
-
kill_linked_sessions(linked_jobs.iter().map(|(_, linked)| linked.as_str()));
|
|
223
|
-
return Ok(blocked_displays(targets, reason));
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
close_adaptive_windows(leader_session.as_str(), session_name.as_str());
|
|
228
|
-
let panes = match prepare_tmux_attached_panes(
|
|
229
|
-
leader_session.as_str(),
|
|
230
|
-
session_name.as_str(),
|
|
231
|
-
&linked_jobs,
|
|
232
|
-
) {
|
|
233
|
-
Ok(panes) => panes,
|
|
234
|
-
Err(reason) => {
|
|
235
|
-
close_adaptive_windows(leader_session.as_str(), session_name.as_str());
|
|
236
|
-
kill_linked_sessions(linked_jobs.iter().map(|(_, linked)| linked.as_str()));
|
|
237
|
-
return Ok(blocked_displays(
|
|
238
|
-
linked_jobs.into_iter().map(|(target, _)| target).collect(),
|
|
239
|
-
reason,
|
|
240
|
-
));
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
Ok(linked_jobs
|
|
199
|
+
Ok(targets
|
|
244
200
|
.into_iter()
|
|
245
|
-
.
|
|
246
|
-
let
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
target.agent_id,
|
|
201
|
+
.map(|target| {
|
|
202
|
+
let agent_id = target.agent_id.clone();
|
|
203
|
+
(
|
|
204
|
+
agent_id.clone(),
|
|
250
205
|
WorkerDisplay::Adaptive {
|
|
251
|
-
status:
|
|
252
|
-
window:
|
|
253
|
-
workspace_window:
|
|
254
|
-
pane_id:
|
|
255
|
-
pane_title: Some(
|
|
256
|
-
target:
|
|
257
|
-
target_worker_session: Some(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
linked_session: Some(linked_session),
|
|
263
|
-
leader_session: Some(leader_session.clone()),
|
|
264
|
-
display_session: Some(leader_session.clone()),
|
|
265
|
-
fallback: Some("tmux_headless".to_string()),
|
|
206
|
+
status: probe.adaptive_status,
|
|
207
|
+
window: target.window.clone(),
|
|
208
|
+
workspace_window: target.window.clone(),
|
|
209
|
+
pane_id: target.pane_id.clone(),
|
|
210
|
+
pane_title: Some(agent_id),
|
|
211
|
+
target: target.pane_id.as_ref().map(|pane| pane.as_str().to_string()),
|
|
212
|
+
target_worker_session: Some(session_name.as_str().to_string()),
|
|
213
|
+
linked_session: None,
|
|
214
|
+
leader_session: Some(session_name.clone()),
|
|
215
|
+
display_session: Some(session_name.clone()),
|
|
216
|
+
fallback: None,
|
|
266
217
|
},
|
|
267
|
-
)
|
|
218
|
+
)
|
|
268
219
|
})
|
|
269
220
|
.collect())
|
|
270
221
|
}
|
|
@@ -407,13 +358,6 @@ struct TmuxOutput {
|
|
|
407
358
|
stderr: String,
|
|
408
359
|
}
|
|
409
360
|
|
|
410
|
-
#[derive(Debug, Clone)]
|
|
411
|
-
struct PaneRecord {
|
|
412
|
-
agent_id: String,
|
|
413
|
-
pane_id: String,
|
|
414
|
-
window_name: String,
|
|
415
|
-
}
|
|
416
|
-
|
|
417
361
|
fn in_wsl() -> bool {
|
|
418
362
|
std::env::var("WSL_DISTRO_NAME").is_ok_and(|value| !value.is_empty())
|
|
419
363
|
|| std::env::var("WSL_INTEROP").is_ok_and(|value| !value.is_empty())
|
|
@@ -508,161 +452,6 @@ fn parse_tmux_info(stdout: &str) -> Option<LeaderTmuxInfo> {
|
|
|
508
452
|
}
|
|
509
453
|
}
|
|
510
454
|
|
|
511
|
-
fn create_linked_worker_session(
|
|
512
|
-
session_name: &SessionName,
|
|
513
|
-
target: &DisplayTarget,
|
|
514
|
-
) -> Result<String, AdaptiveBlockReason> {
|
|
515
|
-
let linked_session = adaptive_linked_session_name(session_name.as_str(), &target.agent_id);
|
|
516
|
-
let worker_window = target
|
|
517
|
-
.window
|
|
518
|
-
.as_ref()
|
|
519
|
-
.map(WindowName::as_str)
|
|
520
|
-
.unwrap_or(target.agent_id.as_str());
|
|
521
|
-
let _ = run_tmux(&["kill-session", "-t", linked_session.as_str()]);
|
|
522
|
-
run_tmux(&[
|
|
523
|
-
"new-session",
|
|
524
|
-
"-d",
|
|
525
|
-
"-t",
|
|
526
|
-
session_name.as_str(),
|
|
527
|
-
"-s",
|
|
528
|
-
linked_session.as_str(),
|
|
529
|
-
])
|
|
530
|
-
.map_err(|_| AdaptiveBlockReason::WorkerSessionMissing)
|
|
531
|
-
.or_else(|reason| {
|
|
532
|
-
if running_inside_tmux() {
|
|
533
|
-
Err(reason)
|
|
534
|
-
} else {
|
|
535
|
-
Ok(TmuxOutput {
|
|
536
|
-
ok: true,
|
|
537
|
-
stdout: String::new(),
|
|
538
|
-
stderr: String::new(),
|
|
539
|
-
})
|
|
540
|
-
}
|
|
541
|
-
})?;
|
|
542
|
-
if run_tmux(&[
|
|
543
|
-
"select-window",
|
|
544
|
-
"-t",
|
|
545
|
-
&format!("{linked_session}:{worker_window}"),
|
|
546
|
-
])
|
|
547
|
-
.is_err()
|
|
548
|
-
{
|
|
549
|
-
if !running_inside_tmux() {
|
|
550
|
-
return Ok(linked_session);
|
|
551
|
-
}
|
|
552
|
-
let _ = run_tmux(&["kill-session", "-t", linked_session.as_str()]);
|
|
553
|
-
return Err(AdaptiveBlockReason::WorkerSessionMissing);
|
|
554
|
-
}
|
|
555
|
-
Ok(linked_session)
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
fn prepare_tmux_attached_panes(
|
|
559
|
-
leader_session: &str,
|
|
560
|
-
session_name: &str,
|
|
561
|
-
linked_jobs: &[(DisplayTarget, String)],
|
|
562
|
-
) -> Result<BTreeMap<String, PaneRecord>, AdaptiveBlockReason> {
|
|
563
|
-
let mut panes = BTreeMap::new();
|
|
564
|
-
for (window_index, window_jobs) in linked_jobs.chunks(3).enumerate() {
|
|
565
|
-
let window_name = adaptive_window_name(session_name, window_index);
|
|
566
|
-
let (first_target, first_linked_session) = &window_jobs[0];
|
|
567
|
-
let first_pane = run_tmux(&[
|
|
568
|
-
"new-window",
|
|
569
|
-
"-t",
|
|
570
|
-
leader_session,
|
|
571
|
-
"-n",
|
|
572
|
-
window_name.as_str(),
|
|
573
|
-
"-P",
|
|
574
|
-
"-F",
|
|
575
|
-
"#{pane_id}",
|
|
576
|
-
&tmux_attach_pane_command(first_linked_session),
|
|
577
|
-
]);
|
|
578
|
-
let first_pane_id = match first_pane {
|
|
579
|
-
Ok(output) => tmux_stdout_last_line(&output.stdout)
|
|
580
|
-
.unwrap_or_else(|| format!("%ta{window_index}0")),
|
|
581
|
-
Err(_) if !running_inside_tmux() => format!("%ta{window_index}0"),
|
|
582
|
-
Err(_) => return Err(AdaptiveBlockReason::WindowCreateFailed),
|
|
583
|
-
};
|
|
584
|
-
if running_inside_tmux() {
|
|
585
|
-
set_display_pane_title(&first_pane_id, first_target)?;
|
|
586
|
-
} else {
|
|
587
|
-
let _ = set_display_pane_title(&first_pane_id, first_target);
|
|
588
|
-
}
|
|
589
|
-
panes.insert(
|
|
590
|
-
first_target.agent_id.clone(),
|
|
591
|
-
PaneRecord {
|
|
592
|
-
agent_id: first_target.agent_id.clone(),
|
|
593
|
-
pane_id: first_pane_id,
|
|
594
|
-
window_name: window_name.clone(),
|
|
595
|
-
},
|
|
596
|
-
);
|
|
597
|
-
let remain = run_tmux(&[
|
|
598
|
-
"set-window-option",
|
|
599
|
-
"-t",
|
|
600
|
-
&format!("{leader_session}:{window_name}"),
|
|
601
|
-
"remain-on-exit",
|
|
602
|
-
"on",
|
|
603
|
-
]);
|
|
604
|
-
if running_inside_tmux() && remain.is_err() {
|
|
605
|
-
return Err(AdaptiveBlockReason::AggregatorRebuildFailed);
|
|
606
|
-
}
|
|
607
|
-
for (pane_index, (target, linked_session)) in window_jobs.iter().enumerate().skip(1) {
|
|
608
|
-
let split = run_tmux(&[
|
|
609
|
-
"split-window",
|
|
610
|
-
"-t",
|
|
611
|
-
&format!("{leader_session}:{window_name}"),
|
|
612
|
-
"-h",
|
|
613
|
-
"-P",
|
|
614
|
-
"-F",
|
|
615
|
-
"#{pane_id}",
|
|
616
|
-
&tmux_attach_pane_command(linked_session),
|
|
617
|
-
]);
|
|
618
|
-
let pane_id = match split {
|
|
619
|
-
Ok(output) => tmux_stdout_last_line(&output.stdout)
|
|
620
|
-
.unwrap_or_else(|| format!("%ta{window_index}{pane_index}")),
|
|
621
|
-
Err(_) if !running_inside_tmux() => format!("%ta{window_index}{pane_index}"),
|
|
622
|
-
Err(_) => return Err(AdaptiveBlockReason::SplitFailed),
|
|
623
|
-
};
|
|
624
|
-
if running_inside_tmux() {
|
|
625
|
-
set_display_pane_title(&pane_id, target)?;
|
|
626
|
-
} else {
|
|
627
|
-
let _ = set_display_pane_title(&pane_id, target);
|
|
628
|
-
}
|
|
629
|
-
panes.insert(
|
|
630
|
-
target.agent_id.clone(),
|
|
631
|
-
PaneRecord {
|
|
632
|
-
agent_id: target.agent_id.clone(),
|
|
633
|
-
pane_id,
|
|
634
|
-
window_name: window_name.clone(),
|
|
635
|
-
},
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
let layout = run_tmux(&[
|
|
639
|
-
"select-layout",
|
|
640
|
-
"-t",
|
|
641
|
-
&format!("{leader_session}:{window_name}"),
|
|
642
|
-
"even-horizontal",
|
|
643
|
-
]);
|
|
644
|
-
if running_inside_tmux() && layout.is_err() {
|
|
645
|
-
return Err(AdaptiveBlockReason::AggregatorRebuildFailed);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
Ok(panes)
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
fn set_display_pane_title(
|
|
652
|
-
pane_id: &str,
|
|
653
|
-
target: &DisplayTarget,
|
|
654
|
-
) -> Result<(), AdaptiveBlockReason> {
|
|
655
|
-
run_tmux(&[
|
|
656
|
-
"select-pane",
|
|
657
|
-
"-t",
|
|
658
|
-
pane_id,
|
|
659
|
-
"-T",
|
|
660
|
-
&display_pane_title(target),
|
|
661
|
-
])
|
|
662
|
-
.map(|_| ())
|
|
663
|
-
.map_err(|_| AdaptiveBlockReason::AggregatorRebuildFailed)
|
|
664
|
-
}
|
|
665
|
-
|
|
666
455
|
fn close_adaptive_windows(leader_session: &str, session_name: &str) -> Vec<String> {
|
|
667
456
|
let prefix = format!("team-agent:{session_name}:overview");
|
|
668
457
|
let Ok(output) = run_tmux(&["list-windows", "-t", leader_session, "-F", "#{window_name}"])
|
|
@@ -684,17 +473,6 @@ fn close_adaptive_windows(leader_session: &str, session_name: &str) -> Vec<Strin
|
|
|
684
473
|
.collect()
|
|
685
474
|
}
|
|
686
475
|
|
|
687
|
-
fn kill_linked_sessions<'a>(sessions: impl IntoIterator<Item = &'a str>) -> Vec<String> {
|
|
688
|
-
sessions
|
|
689
|
-
.into_iter()
|
|
690
|
-
.filter_map(|session| {
|
|
691
|
-
run_tmux(&["kill-session", "-t", session])
|
|
692
|
-
.ok()
|
|
693
|
-
.map(|_| session.to_string())
|
|
694
|
-
})
|
|
695
|
-
.collect()
|
|
696
|
-
}
|
|
697
|
-
|
|
698
476
|
fn kill_adaptive_window(target: &str) -> bool {
|
|
699
477
|
run_tmux(&["kill-window", "-t", target]).is_ok()
|
|
700
478
|
}
|
|
@@ -718,13 +496,6 @@ fn adaptive_leader_session(state: &serde_json::Value) -> Option<String> {
|
|
|
718
496
|
.map(str::to_string)
|
|
719
497
|
}
|
|
720
498
|
|
|
721
|
-
fn adaptive_linked_session_name(session_name: &str, agent_id: &str) -> String {
|
|
722
|
-
let digest = crate::leader::sha1_hex_prefix(format!("{session_name}:{agent_id}").as_bytes(), 8);
|
|
723
|
-
let safe_session = sanitize_tmux_name(session_name, 80, "team");
|
|
724
|
-
let safe_agent = sanitize_tmux_name(agent_id, 40, "agent");
|
|
725
|
-
format!("{safe_session}__display__{safe_agent}__{digest}")
|
|
726
|
-
}
|
|
727
|
-
|
|
728
499
|
fn adaptive_window_name(session_name: &str, index: usize) -> String {
|
|
729
500
|
if index == 0 {
|
|
730
501
|
format!("team-agent:{session_name}:overview")
|
|
@@ -738,13 +509,6 @@ fn adaptive_window_is_tagged(session_name: &str, window: &str) -> bool {
|
|
|
738
509
|
window == prefix || window.starts_with(&format!("{prefix}-"))
|
|
739
510
|
}
|
|
740
511
|
|
|
741
|
-
fn tmux_attach_pane_command(linked_session: &str) -> String {
|
|
742
|
-
format!(
|
|
743
|
-
"TMUX= tmux attach-session -t {}",
|
|
744
|
-
shell_quote(linked_session)
|
|
745
|
-
)
|
|
746
|
-
}
|
|
747
|
-
|
|
748
512
|
fn display_pane_title(target: &DisplayTarget) -> String {
|
|
749
513
|
format!(
|
|
750
514
|
"team-agent:{}:{}",
|
|
@@ -753,39 +517,6 @@ fn display_pane_title(target: &DisplayTarget) -> String {
|
|
|
753
517
|
)
|
|
754
518
|
}
|
|
755
519
|
|
|
756
|
-
fn sanitize_tmux_name(raw: &str, max_len: usize, fallback: &str) -> String {
|
|
757
|
-
let sanitized = raw
|
|
758
|
-
.chars()
|
|
759
|
-
.map(|c| {
|
|
760
|
-
if c.is_ascii_alphanumeric() || matches!(c, '_' | '.' | '-') {
|
|
761
|
-
c
|
|
762
|
-
} else {
|
|
763
|
-
'_'
|
|
764
|
-
}
|
|
765
|
-
})
|
|
766
|
-
.collect::<String>();
|
|
767
|
-
let trimmed = sanitized
|
|
768
|
-
.chars()
|
|
769
|
-
.take(max_len)
|
|
770
|
-
.collect::<String>()
|
|
771
|
-
.trim_matches(&['.', '_', '-'][..])
|
|
772
|
-
.to_string();
|
|
773
|
-
if trimmed.is_empty() {
|
|
774
|
-
fallback.to_string()
|
|
775
|
-
} else {
|
|
776
|
-
trimmed
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
fn tmux_stdout_last_line(stdout: &str) -> Option<String> {
|
|
781
|
-
stdout
|
|
782
|
-
.lines()
|
|
783
|
-
.rev()
|
|
784
|
-
.map(str::trim)
|
|
785
|
-
.find(|line| !line.is_empty())
|
|
786
|
-
.map(str::to_string)
|
|
787
|
-
}
|
|
788
|
-
|
|
789
520
|
fn string_field(display: &serde_json::Map<String, serde_json::Value>, key: &str) -> Option<String> {
|
|
790
521
|
display
|
|
791
522
|
.get(key)
|
|
@@ -814,13 +545,3 @@ fn run_tmux(args: &[&str]) -> Result<TmuxOutput, LifecycleError> {
|
|
|
814
545
|
)))
|
|
815
546
|
}
|
|
816
547
|
}
|
|
817
|
-
|
|
818
|
-
fn shell_quote(value: &str) -> String {
|
|
819
|
-
if value
|
|
820
|
-
.chars()
|
|
821
|
-
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '.' | '/' | ':'))
|
|
822
|
-
{
|
|
823
|
-
return value.to_string();
|
|
824
|
-
}
|
|
825
|
-
format!("'{}'", value.replace('\'', "'\\''"))
|
|
826
|
-
}
|
|
@@ -438,6 +438,16 @@ fn spawn_agents(
|
|
|
438
438
|
if matches!(transport.liveness(&spawn.pane_id), Ok(PaneLiveness::Dead)) {
|
|
439
439
|
continue;
|
|
440
440
|
}
|
|
441
|
+
if placement.is_some() {
|
|
442
|
+
configure_adaptive_pane_title(
|
|
443
|
+
workspace,
|
|
444
|
+
transport,
|
|
445
|
+
session_name,
|
|
446
|
+
&window,
|
|
447
|
+
&spawn.pane_id,
|
|
448
|
+
agent_id_raw,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
441
451
|
let display = if placement.is_some() {
|
|
442
452
|
WorkerDisplay::Adaptive {
|
|
443
453
|
status: DisplayStatus::Opened,
|
|
@@ -2654,6 +2664,34 @@ pub(crate) fn selected_tmux_socket_source(
|
|
|
2654
2664
|
}
|
|
2655
2665
|
}
|
|
2656
2666
|
|
|
2667
|
+
pub(crate) fn configure_adaptive_pane_title(
|
|
2668
|
+
workspace: &Path,
|
|
2669
|
+
transport: &dyn Transport,
|
|
2670
|
+
session_name: &SessionName,
|
|
2671
|
+
window: &WindowName,
|
|
2672
|
+
pane: &PaneId,
|
|
2673
|
+
agent_id: &str,
|
|
2674
|
+
) {
|
|
2675
|
+
if let Err(error) =
|
|
2676
|
+
transport.configure_adaptive_pane_title(session_name, window, pane, agent_id)
|
|
2677
|
+
{
|
|
2678
|
+
let message = format!("adaptive layout pane title failed for {agent_id}: {error}");
|
|
2679
|
+
eprintln!("Warning: {message}");
|
|
2680
|
+
if let Err(event_error) = crate::event_log::EventLog::new(workspace).write(
|
|
2681
|
+
"adaptive_layout.pane_title_failed",
|
|
2682
|
+
serde_json::json!({
|
|
2683
|
+
"agent_id": agent_id,
|
|
2684
|
+
"session": session_name.as_str(),
|
|
2685
|
+
"window": window.as_str(),
|
|
2686
|
+
"pane_id": pane.as_str(),
|
|
2687
|
+
"warning": message,
|
|
2688
|
+
}),
|
|
2689
|
+
) {
|
|
2690
|
+
eprintln!("Warning: adaptive_layout.pane_title_failed event write failed: {event_error}");
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2657
2695
|
fn explicit_quick_start_workspace(workspace: &Path) -> PathBuf {
|
|
2658
2696
|
std::fs::canonicalize(workspace).unwrap_or_else(|_| {
|
|
2659
2697
|
if workspace.is_absolute() {
|
|
@@ -97,8 +97,8 @@ pub(crate) fn start_agent_at_paths(
|
|
|
97
97
|
};
|
|
98
98
|
if !force && agent_live {
|
|
99
99
|
mark_agent_running_noop(&mut state, agent_id, &session_name, &window)?;
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
let team_key = restart_projection_team_key(&state, team);
|
|
101
|
+
save_restart_projected_state(workspace, &mut state, &team_key)?;
|
|
102
102
|
if let Ok(spec) = load_team_spec(spec_workspace) {
|
|
103
103
|
write_team_state(spec_workspace, &spec, &state)?;
|
|
104
104
|
}
|
|
@@ -184,9 +184,9 @@ pub(crate) fn start_agent_at_paths(
|
|
|
184
184
|
layout_placement.as_ref(),
|
|
185
185
|
None,
|
|
186
186
|
)?;
|
|
187
|
-
mark_agent_started(&mut state, agent_id, &spawn_window, &spawn, &safety)?;
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
mark_agent_started(&mut state, agent_id, &spawn_window, &spawn, transport, &safety)?;
|
|
188
|
+
let team_key = restart_projection_team_key(&state, team);
|
|
189
|
+
save_restart_projected_state(workspace, &mut state, &team_key)?;
|
|
190
190
|
write_start_agent_start_event(
|
|
191
191
|
workspace,
|
|
192
192
|
agent_id,
|
|
@@ -410,6 +410,7 @@ fn mark_agent_started(
|
|
|
410
410
|
agent_id: &AgentId,
|
|
411
411
|
window: &str,
|
|
412
412
|
spawn: &SpawnedAgentWindow,
|
|
413
|
+
transport: &dyn crate::transport::Transport,
|
|
413
414
|
safety: &DangerousApproval,
|
|
414
415
|
) -> Result<(), LifecycleError> {
|
|
415
416
|
let Some(agent) = state
|
|
@@ -430,9 +431,23 @@ fn mark_agent_started(
|
|
|
430
431
|
"pane_id".to_string(),
|
|
431
432
|
serde_json::json!(spawn.spawn.pane_id.as_str()),
|
|
432
433
|
);
|
|
433
|
-
|
|
434
|
+
let pane_pid = spawn.spawn.child_pid.or_else(|| {
|
|
435
|
+
transport
|
|
436
|
+
.list_targets()
|
|
437
|
+
.unwrap_or_default()
|
|
438
|
+
.into_iter()
|
|
439
|
+
.find(|pane| pane.pane_id == spawn.spawn.pane_id)
|
|
440
|
+
.and_then(|pane| pane.pane_pid)
|
|
441
|
+
});
|
|
442
|
+
if let Some(pane_pid) = pane_pid {
|
|
434
443
|
agent.insert("pane_pid".to_string(), serde_json::json!(pane_pid));
|
|
444
|
+
} else {
|
|
445
|
+
agent.remove("pane_pid");
|
|
435
446
|
}
|
|
447
|
+
agent.insert(
|
|
448
|
+
"spawned_at".to_string(),
|
|
449
|
+
serde_json::json!(chrono::Utc::now().to_rfc3339()),
|
|
450
|
+
);
|
|
436
451
|
crate::lifecycle::launch::persist_command_plan_state(
|
|
437
452
|
agent,
|
|
438
453
|
&spawn.plan,
|
|
@@ -467,6 +482,10 @@ fn mark_agent_started(
|
|
|
467
482
|
}),
|
|
468
483
|
);
|
|
469
484
|
}
|
|
485
|
+
agent.remove("startup_prompts");
|
|
486
|
+
agent.remove("startup_prompt_status");
|
|
487
|
+
agent.remove("startup_prompt_probe_epoch");
|
|
488
|
+
agent.remove("startup_prompt_probe_disabled_at");
|
|
470
489
|
Ok(())
|
|
471
490
|
}
|
|
472
491
|
|
|
@@ -551,6 +570,8 @@ fn reset_agent_at_paths(
|
|
|
551
570
|
let mut state = resolve_team_scoped_state_or_refuse(workspace, team)?;
|
|
552
571
|
let spec = load_team_spec(spec_workspace)?;
|
|
553
572
|
discard_agent_session_fields(&mut state, agent_id)?;
|
|
573
|
+
let team_key = restart_projection_team_key(&state, team);
|
|
574
|
+
sync_restart_team_projections(&mut state, &team_key);
|
|
554
575
|
// golden operations.py (reset): save_team_scoped_state on the team projection — same multi-team
|
|
555
576
|
// preservation as stop, not a raw save_runtime_state.
|
|
556
577
|
crate::state::projection::save_team_scoped_state_with_tombstoned_agents(
|