@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
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
|
@@ -80,14 +80,15 @@ pub fn cmd_init(args: &InitArgs) -> Result<CmdResult, CliError> {
|
|
|
80
80
|
if args.force || !state_path.exists() {
|
|
81
81
|
std::fs::write(&state_path, INIT_STATE_TEMPLATE)?;
|
|
82
82
|
}
|
|
83
|
-
crate::event_log::EventLog::new(&args.workspace)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
crate::event_log::EventLog::new(&args.workspace)
|
|
84
|
+
.write(
|
|
85
|
+
"init",
|
|
86
|
+
json!({
|
|
87
|
+
"spec_path": spec_path.to_string_lossy().to_string(),
|
|
88
|
+
"state_path": state_path.to_string_lossy().to_string(),
|
|
89
|
+
}),
|
|
90
|
+
)
|
|
91
|
+
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
91
92
|
Ok(CmdResult::from_json(
|
|
92
93
|
json!({
|
|
93
94
|
"ok": true,
|
|
@@ -122,7 +123,10 @@ pub fn cmd_quick_start(args: &QuickStartArgs) -> Result<CmdResult, CliError> {
|
|
|
122
123
|
.and_then(|readiness| readiness.get("ready"))
|
|
123
124
|
.and_then(Value::as_bool)
|
|
124
125
|
.unwrap_or(true);
|
|
125
|
-
let status = value
|
|
126
|
+
let status = value
|
|
127
|
+
.get("status")
|
|
128
|
+
.and_then(Value::as_str)
|
|
129
|
+
.map(str::to_string);
|
|
126
130
|
if args.json
|
|
127
131
|
|| value.get("ok").and_then(Value::as_bool) == Some(false)
|
|
128
132
|
|| session_capture_incomplete
|
|
@@ -173,8 +177,8 @@ fn append_reminder(text: String, reminder: &str) -> String {
|
|
|
173
177
|
|
|
174
178
|
/// `cmd_compile`(`commands.py:42`)。
|
|
175
179
|
pub fn cmd_compile(args: &CompileArgs) -> Result<CmdResult, CliError> {
|
|
176
|
-
let spec =
|
|
177
|
-
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
180
|
+
let spec =
|
|
181
|
+
crate::compiler::compile_team(&args.team).map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
178
182
|
warn_ignored_owner_team_id(&args.team);
|
|
179
183
|
std::fs::write(&args.out, crate::model::yaml::dumps(&spec))?;
|
|
180
184
|
Ok(CmdResult::from_json(
|
|
@@ -194,7 +198,10 @@ fn warn_ignored_owner_team_id(team_dir: &std::path::Path) {
|
|
|
194
198
|
};
|
|
195
199
|
let workspace = crate::model::paths::team_workspace(team_dir)
|
|
196
200
|
.unwrap_or_else(|_| team_dir.parent().unwrap_or(team_dir).to_path_buf());
|
|
197
|
-
eprintln!(
|
|
201
|
+
eprintln!(
|
|
202
|
+
"Warning: ignored TEAM.md {}={}",
|
|
203
|
+
ignored.field, ignored.value
|
|
204
|
+
);
|
|
198
205
|
eprintln!("Reason: owner identity is the canonical runtime team key, not TEAM.md front matter");
|
|
199
206
|
eprintln!("Action: remove {} from TEAM.md", ignored.field);
|
|
200
207
|
let fields = json!({
|
|
@@ -205,7 +212,9 @@ fn warn_ignored_owner_team_id(team_dir: &std::path::Path) {
|
|
|
205
212
|
"reason": "owner identity is derived from the canonical runtime team key",
|
|
206
213
|
"action": "remove owner_team_id from TEAM.md",
|
|
207
214
|
});
|
|
208
|
-
if let Err(err) =
|
|
215
|
+
if let Err(err) =
|
|
216
|
+
crate::event_log::EventLog::new(&workspace).write("spec.field_ignored", fields)
|
|
217
|
+
{
|
|
209
218
|
eprintln!("Warning: spec.field_ignored event write failed: {err}");
|
|
210
219
|
}
|
|
211
220
|
}
|
|
@@ -362,17 +371,17 @@ pub fn cmd_watch(args: &WatchArgs) -> Result<CmdResult, CliError> {
|
|
|
362
371
|
}
|
|
363
372
|
#[cfg(test)]
|
|
364
373
|
{
|
|
365
|
-
|
|
374
|
+
let store = crate::message_store::MessageStore::open(&args.workspace)
|
|
375
|
+
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
376
|
+
let mut cursor = crate::coordinator::WatchCursor::default();
|
|
377
|
+
let lines = crate::coordinator::collect_watch_lines(
|
|
378
|
+
&workspace,
|
|
379
|
+
&mut cursor,
|
|
380
|
+
&store,
|
|
381
|
+
args.team.as_deref(),
|
|
382
|
+
)
|
|
366
383
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
367
|
-
|
|
368
|
-
let lines = crate::coordinator::collect_watch_lines(
|
|
369
|
-
&workspace,
|
|
370
|
-
&mut cursor,
|
|
371
|
-
&store,
|
|
372
|
-
args.team.as_deref(),
|
|
373
|
-
)
|
|
374
|
-
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
375
|
-
Ok(CmdResult::human(lines.join("\n")))
|
|
384
|
+
Ok(CmdResult::human(lines.join("\n")))
|
|
376
385
|
}
|
|
377
386
|
}
|
|
378
387
|
|
|
@@ -494,7 +503,11 @@ pub fn cmd_diagnose(args: &DiagnoseArgs) -> Result<CmdResult, CliError> {
|
|
|
494
503
|
)
|
|
495
504
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
496
505
|
let state = selected.state;
|
|
497
|
-
let event_log = selected
|
|
506
|
+
let event_log = selected
|
|
507
|
+
.run_workspace
|
|
508
|
+
.join(".team")
|
|
509
|
+
.join("logs")
|
|
510
|
+
.join("events.jsonl");
|
|
498
511
|
// 0.5.x Phase 1d Batch 3: factory-routed diagnose backend so
|
|
499
512
|
// conpty teams get accurate `has_session` / capture from the shim
|
|
500
513
|
// rather than a workspace tmux fallback (which would always miss
|
|
@@ -537,19 +550,13 @@ pub fn cmd_diagnose(args: &DiagnoseArgs) -> Result<CmdResult, CliError> {
|
|
|
537
550
|
/// `cmd_preflight`(`parser.py:160`)。
|
|
538
551
|
pub fn cmd_preflight(args: &PreflightArgs) -> Result<CmdResult, CliError> {
|
|
539
552
|
let report = build_preflight_report(&args.team)?;
|
|
540
|
-
Ok(CmdResult::from_json(
|
|
541
|
-
report,
|
|
542
|
-
args.json,
|
|
543
|
-
))
|
|
553
|
+
Ok(CmdResult::from_json(report, args.json))
|
|
544
554
|
}
|
|
545
555
|
|
|
546
556
|
/// `cmd_wait_ready`(`parser.py:171`)。
|
|
547
557
|
pub fn cmd_wait_ready(args: &WaitReadyArgs) -> Result<CmdResult, CliError> {
|
|
548
558
|
let report = build_wait_ready_report(&args.workspace, args.timeout, args.team.as_deref())?;
|
|
549
|
-
Ok(CmdResult::from_json(
|
|
550
|
-
report,
|
|
551
|
-
args.json,
|
|
552
|
-
))
|
|
559
|
+
Ok(CmdResult::from_json(report, args.json))
|
|
553
560
|
}
|
|
554
561
|
|
|
555
562
|
/// `cmd_e2e`(`parser.py:449`)。
|
|
@@ -579,10 +586,15 @@ pub fn cmd_e2e(args: &E2eArgs) -> Result<CmdResult, CliError> {
|
|
|
579
586
|
/// `cmd_peek`(`commands.py:118`)。
|
|
580
587
|
pub fn cmd_peek(args: &PeekArgs) -> Result<CmdResult, CliError> {
|
|
581
588
|
if !args.allow_raw_screen {
|
|
582
|
-
return Err(CliError::Usage(
|
|
589
|
+
return Err(CliError::Usage(
|
|
590
|
+
"peek requires --allow-raw-screen".to_string(),
|
|
591
|
+
));
|
|
583
592
|
}
|
|
584
593
|
let state = crate::state::persist::load_runtime_state(&args.workspace)?;
|
|
585
|
-
let Some(agent_state) = state
|
|
594
|
+
let Some(agent_state) = state
|
|
595
|
+
.get("agents")
|
|
596
|
+
.and_then(|agents| agents.get(&args.agent))
|
|
597
|
+
else {
|
|
586
598
|
return Ok(CmdResult::from_json(
|
|
587
599
|
json!({
|
|
588
600
|
"ok": false,
|
|
@@ -661,13 +673,29 @@ fn peek_unavailable(agent: &str, json: bool) -> CmdResult {
|
|
|
661
673
|
)
|
|
662
674
|
}
|
|
663
675
|
|
|
664
|
-
fn agent_pane_id(
|
|
665
|
-
|
|
676
|
+
fn agent_pane_id(
|
|
677
|
+
state: &Value,
|
|
678
|
+
agent: &str,
|
|
679
|
+
agent_state: &Value,
|
|
680
|
+
) -> Option<(String, String, String)> {
|
|
681
|
+
let session = state
|
|
682
|
+
.get("session_name")
|
|
683
|
+
.and_then(Value::as_str)
|
|
684
|
+
.filter(|s| !s.is_empty())?;
|
|
666
685
|
let window = ["window", "window_name"]
|
|
667
686
|
.iter()
|
|
668
|
-
.find_map(|key|
|
|
687
|
+
.find_map(|key| {
|
|
688
|
+
agent_state
|
|
689
|
+
.get(*key)
|
|
690
|
+
.and_then(Value::as_str)
|
|
691
|
+
.filter(|s| !s.is_empty())
|
|
692
|
+
})
|
|
669
693
|
.unwrap_or(agent);
|
|
670
|
-
Some((
|
|
694
|
+
Some((
|
|
695
|
+
session.to_string(),
|
|
696
|
+
window.to_string(),
|
|
697
|
+
format!("{session}:{window}"),
|
|
698
|
+
))
|
|
671
699
|
}
|
|
672
700
|
|
|
673
701
|
fn run_fake_e2e(workspace: &Path) -> Result<Value, CliError> {
|
|
@@ -787,7 +815,10 @@ fn command_on_path(command: &str) -> bool {
|
|
|
787
815
|
}
|
|
788
816
|
|
|
789
817
|
fn command_version(command: &str) -> Value {
|
|
790
|
-
match std::process::Command::new(command)
|
|
818
|
+
match std::process::Command::new(command)
|
|
819
|
+
.arg("--version")
|
|
820
|
+
.output()
|
|
821
|
+
{
|
|
791
822
|
Ok(output) if output.status.success() => {
|
|
792
823
|
let text = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
793
824
|
if text.is_empty() {
|
|
@@ -959,7 +990,10 @@ tasks:
|
|
|
959
990
|
|
|
960
991
|
fn sessions_overview(state: &Value, spec: Option<&crate::model::yaml::Value>) -> Value {
|
|
961
992
|
let mut rows = Vec::new();
|
|
962
|
-
if let Some(agents) = spec
|
|
993
|
+
if let Some(agents) = spec
|
|
994
|
+
.and_then(|v| v.get("agents"))
|
|
995
|
+
.and_then(crate::model::yaml::Value::as_list)
|
|
996
|
+
{
|
|
963
997
|
for agent in agents {
|
|
964
998
|
let Some(agent_id) = agent.get("id").and_then(crate::model::yaml::Value::as_str) else {
|
|
965
999
|
continue;
|
|
@@ -1015,7 +1049,10 @@ fn session_row(
|
|
|
1015
1049
|
}
|
|
1016
1050
|
|
|
1017
1051
|
fn yaml_str(value: &crate::model::yaml::Value, key: &str) -> Option<String> {
|
|
1018
|
-
value
|
|
1052
|
+
value
|
|
1053
|
+
.get(key)
|
|
1054
|
+
.and_then(crate::model::yaml::Value::as_str)
|
|
1055
|
+
.map(ToString::to_string)
|
|
1019
1056
|
}
|
|
1020
1057
|
|
|
1021
1058
|
fn field_or_null(value: &Value, keys: &[&str]) -> Value {
|
|
@@ -1041,7 +1078,11 @@ fn last_task_for_agent(state: &Value, agent_id: &str) -> Value {
|
|
|
1041
1078
|
.rev()
|
|
1042
1079
|
.find(|task| task.get("assignee").and_then(Value::as_str) == Some(agent_id))
|
|
1043
1080
|
})
|
|
1044
|
-
.and_then(|task|
|
|
1081
|
+
.and_then(|task| {
|
|
1082
|
+
task.get("id")
|
|
1083
|
+
.and_then(Value::as_str)
|
|
1084
|
+
.map(ToString::to_string)
|
|
1085
|
+
})
|
|
1045
1086
|
.map(Value::String)
|
|
1046
1087
|
.unwrap_or(Value::Null)
|
|
1047
1088
|
}
|
|
@@ -1067,8 +1108,10 @@ fn window_target(agent_state: &Value, agent_id: &str) -> Value {
|
|
|
1067
1108
|
}
|
|
1068
1109
|
}
|
|
1069
1110
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1111
|
+
fn load_team_spec_optional(
|
|
1112
|
+
workspace: &Path,
|
|
1113
|
+
state: &Value,
|
|
1114
|
+
) -> Result<Option<crate::model::yaml::Value>, CliError> {
|
|
1072
1115
|
let spec_path = state
|
|
1073
1116
|
.get("spec_path")
|
|
1074
1117
|
.and_then(Value::as_str)
|
|
@@ -1196,7 +1239,10 @@ fn format_inbox_human(
|
|
|
1196
1239
|
Ok(lines.join("\n"))
|
|
1197
1240
|
}
|
|
1198
1241
|
|
|
1199
|
-
fn uncollected_result_count(
|
|
1242
|
+
fn uncollected_result_count(
|
|
1243
|
+
workspace: &Path,
|
|
1244
|
+
owner_team_id: Option<&str>,
|
|
1245
|
+
) -> Result<i64, CliError> {
|
|
1200
1246
|
let store = crate::message_store::MessageStore::open(workspace)
|
|
1201
1247
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
1202
1248
|
let conn = crate::db::schema::open_db(store.db_path())
|
|
@@ -1401,10 +1447,16 @@ pub fn cmd_doctor(args: &DoctorArgs) -> Result<CmdResult, CliError> {
|
|
|
1401
1447
|
));
|
|
1402
1448
|
}
|
|
1403
1449
|
if args.comms || matches!(args.gate, Some(DoctorGate::Comms)) {
|
|
1404
|
-
let value = crate::diagnose::comms::doctor_comms_json(
|
|
1450
|
+
let value = crate::diagnose::comms::doctor_comms_json(
|
|
1451
|
+
&args.workspace,
|
|
1452
|
+
args.team.as_deref(),
|
|
1453
|
+
Some("comms"),
|
|
1454
|
+
)?;
|
|
1405
1455
|
if !args.json {
|
|
1406
1456
|
let json_tail = serde_json::to_string_pretty(&sort_json(&value))?;
|
|
1407
|
-
return Ok(CmdResult::human(format!(
|
|
1457
|
+
return Ok(CmdResult::human(format!(
|
|
1458
|
+
"{COMMS_BOUNDARY_TEXT}\n{json_tail}"
|
|
1459
|
+
)));
|
|
1408
1460
|
}
|
|
1409
1461
|
return Ok(CmdResult::from_json(value, true));
|
|
1410
1462
|
}
|
|
@@ -1439,9 +1491,18 @@ mod tests {
|
|
|
1439
1491
|
});
|
|
1440
1492
|
let out = quickstart_human(&value);
|
|
1441
1493
|
assert!(out.contains("team started"), "must keep summary; got {out}");
|
|
1442
|
-
assert!(
|
|
1443
|
-
|
|
1444
|
-
|
|
1494
|
+
assert!(
|
|
1495
|
+
out.contains("attach:"),
|
|
1496
|
+
"must render attach block; got {out}"
|
|
1497
|
+
);
|
|
1498
|
+
assert!(
|
|
1499
|
+
out.contains("team-y:w1") && out.contains("team-y:w2"),
|
|
1500
|
+
"must list each attach cmd; got {out}"
|
|
1501
|
+
);
|
|
1502
|
+
assert!(
|
|
1503
|
+
out.ends_with(crate::cli::QUICK_START_REMINDER),
|
|
1504
|
+
"must append harness reminder; got {out}"
|
|
1505
|
+
);
|
|
1445
1506
|
}
|
|
1446
1507
|
|
|
1447
1508
|
#[test]
|
|
@@ -1467,11 +1528,18 @@ mod tests {
|
|
|
1467
1528
|
"w1": {"pane_id": "%7", "window": "w1"}
|
|
1468
1529
|
}
|
|
1469
1530
|
});
|
|
1470
|
-
let agent = state
|
|
1531
|
+
let agent = state
|
|
1532
|
+
.get("agents")
|
|
1533
|
+
.and_then(|agents| agents.get("w1"))
|
|
1534
|
+
.unwrap();
|
|
1471
1535
|
|
|
1472
1536
|
assert_eq!(
|
|
1473
1537
|
agent_pane_id(&state, "w1", agent).unwrap(),
|
|
1474
|
-
(
|
|
1538
|
+
(
|
|
1539
|
+
"team-x".to_string(),
|
|
1540
|
+
"w1".to_string(),
|
|
1541
|
+
"team-x:w1".to_string()
|
|
1542
|
+
)
|
|
1475
1543
|
);
|
|
1476
1544
|
}
|
|
1477
1545
|
|
|
@@ -1483,11 +1551,18 @@ mod tests {
|
|
|
1483
1551
|
"w1": {"window": "worker-one"}
|
|
1484
1552
|
}
|
|
1485
1553
|
});
|
|
1486
|
-
let agent = state
|
|
1554
|
+
let agent = state
|
|
1555
|
+
.get("agents")
|
|
1556
|
+
.and_then(|agents| agents.get("w1"))
|
|
1557
|
+
.unwrap();
|
|
1487
1558
|
|
|
1488
1559
|
assert_eq!(
|
|
1489
1560
|
agent_pane_id(&state, "w1", agent).unwrap(),
|
|
1490
|
-
(
|
|
1561
|
+
(
|
|
1562
|
+
"team-x".to_string(),
|
|
1563
|
+
"worker-one".to_string(),
|
|
1564
|
+
"team-x:worker-one".to_string()
|
|
1565
|
+
)
|
|
1491
1566
|
);
|
|
1492
1567
|
}
|
|
1493
1568
|
}
|
|
@@ -57,8 +57,7 @@ pub(crate) fn diagnose_runtime(state: &Value, backend: &dyn Transport) -> (Value
|
|
|
57
57
|
let error_str = error.to_string();
|
|
58
58
|
let issue_id = classify_tmux_server_error(&error_str);
|
|
59
59
|
issues.push(json!(issue_id));
|
|
60
|
-
let mut hint =
|
|
61
|
-
recovery_hint(session_name, issue_id, "team-agent diagnose");
|
|
60
|
+
let mut hint = recovery_hint(session_name, issue_id, "team-agent diagnose");
|
|
62
61
|
if let Some(obj) = hint.as_object_mut() {
|
|
63
62
|
obj.insert("reason".to_string(), Value::String(error_str));
|
|
64
63
|
}
|
|
@@ -1673,13 +1673,7 @@ mod tests {
|
|
|
1673
1673
|
fn hidden_commands_not_in_default_help() {
|
|
1674
1674
|
let top_help = command_help(None);
|
|
1675
1675
|
let visible = visible_help_commands(&top_help);
|
|
1676
|
-
for command in [
|
|
1677
|
-
"leaders",
|
|
1678
|
-
"doctor",
|
|
1679
|
-
"e2e",
|
|
1680
|
-
"peek",
|
|
1681
|
-
"coordinator",
|
|
1682
|
-
] {
|
|
1676
|
+
for command in ["leaders", "doctor", "e2e", "peek", "coordinator"] {
|
|
1683
1677
|
assert!(
|
|
1684
1678
|
!visible.iter().any(|visible| visible == command),
|
|
1685
1679
|
"`{command}` must stay hidden from default help"
|
|
@@ -30,7 +30,9 @@ pub(crate) fn python_truthy(value: &Value) -> bool {
|
|
|
30
30
|
match value {
|
|
31
31
|
Value::Null => false,
|
|
32
32
|
Value::Bool(b) => *b,
|
|
33
|
-
Value::Number(n) => n
|
|
33
|
+
Value::Number(n) => n
|
|
34
|
+
.as_i64()
|
|
35
|
+
.map(|v| v != 0)
|
|
34
36
|
.or_else(|| n.as_u64().map(|v| v != 0))
|
|
35
37
|
.or_else(|| n.as_f64().map(|v| v != 0.0))
|
|
36
38
|
.unwrap_or(false),
|
|
@@ -30,7 +30,8 @@ pub fn leader_launcher_args(values: &[String]) -> Result<LeaderLauncherArgs, Cli
|
|
|
30
30
|
)));
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
out.provider_args
|
|
33
|
+
out.provider_args
|
|
34
|
+
.extend(values.iter().skip(idx + 1).cloned());
|
|
34
35
|
break;
|
|
35
36
|
} else if token == "--attach" || token == "--attach-existing" {
|
|
36
37
|
out.attach_existing = true;
|
|
@@ -3347,8 +3347,7 @@ pub mod lifecycle_port {
|
|
|
3347
3347
|
let repair_team = team
|
|
3348
3348
|
.filter(|team| !team.is_empty())
|
|
3349
3349
|
.unwrap_or(session_name.as_str());
|
|
3350
|
-
let shutdown_scoped =
|
|
3351
|
-
format!("team-agent shutdown --team {repair_team}");
|
|
3350
|
+
let shutdown_scoped = format!("team-agent shutdown --team {repair_team}");
|
|
3352
3351
|
json!({
|
|
3353
3352
|
"ok": false,
|
|
3354
3353
|
"status": "refused_build_before_destroy",
|
|
@@ -4101,7 +4100,7 @@ pub mod leader_port {
|
|
|
4101
4100
|
"action": "rerun with --confirm to claim ownership of this team",
|
|
4102
4101
|
}));
|
|
4103
4102
|
}
|
|
4104
|
-
let state = crate::state::persist::
|
|
4103
|
+
let state = crate::state::persist::load_runtime_state_without_migrations(workspace)
|
|
4105
4104
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
4106
4105
|
let Some(team_id) = resolve_owner_team_id(&state, team) else {
|
|
4107
4106
|
return Ok(json!({
|
|
@@ -4113,8 +4112,7 @@ pub mod leader_port {
|
|
|
4113
4112
|
}));
|
|
4114
4113
|
};
|
|
4115
4114
|
let requested_team = team;
|
|
4116
|
-
let
|
|
4117
|
-
let resolved_team = explicit_team.map(|_| team_id.as_str().to_string());
|
|
4115
|
+
let resolved_team = Some(team_id.as_str().to_string());
|
|
4118
4116
|
if !positive_caller_pane_env_present() {
|
|
4119
4117
|
let bind = crate::leader::bind_owner_from_caller_pane(workspace, &team_id, None)
|
|
4120
4118
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
@@ -4125,9 +4123,21 @@ pub mod leader_port {
|
|
|
4125
4123
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
4126
4124
|
let mut value = lease_value(result);
|
|
4127
4125
|
insert_resolved_team(&mut value, requested_team, resolved_team.as_deref());
|
|
4126
|
+
append_team_session_ready(workspace, resolved_team.as_deref(), &mut value);
|
|
4128
4127
|
if value.get("ok").and_then(Value::as_bool) == Some(true) {
|
|
4129
|
-
emit_topology_convergence_event(
|
|
4130
|
-
|
|
4128
|
+
emit_topology_convergence_event(
|
|
4129
|
+
workspace,
|
|
4130
|
+
resolved_team.as_deref(),
|
|
4131
|
+
"takeover",
|
|
4132
|
+
&value,
|
|
4133
|
+
);
|
|
4134
|
+
register_after_binding_success(
|
|
4135
|
+
workspace,
|
|
4136
|
+
resolved_team.as_deref(),
|
|
4137
|
+
"takeover",
|
|
4138
|
+
&mut value,
|
|
4139
|
+
);
|
|
4140
|
+
restore_non_target_teams(workspace, &state, resolved_team.as_deref());
|
|
4131
4141
|
}
|
|
4132
4142
|
Ok(value)
|
|
4133
4143
|
}
|
|
@@ -4137,7 +4147,7 @@ pub mod leader_port {
|
|
|
4137
4147
|
team: Option<&str>,
|
|
4138
4148
|
confirm: bool,
|
|
4139
4149
|
) -> Result<Value, CliError> {
|
|
4140
|
-
let state = crate::state::persist::
|
|
4150
|
+
let state = crate::state::persist::load_runtime_state_without_migrations(workspace)
|
|
4141
4151
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
4142
4152
|
let Some(team_id) = resolve_owner_team_id(&state, team) else {
|
|
4143
4153
|
return Ok(json!({
|
|
@@ -4156,15 +4166,26 @@ pub mod leader_port {
|
|
|
4156
4166
|
}
|
|
4157
4167
|
return Ok(owner_bind_value(bind));
|
|
4158
4168
|
}
|
|
4159
|
-
let
|
|
4160
|
-
let resolved_team = explicit_team.map(|_| team_id.as_str().to_string());
|
|
4169
|
+
let resolved_team = Some(team_id.as_str().to_string());
|
|
4161
4170
|
let result = crate::leader::claim_leader(workspace, resolved_team.as_deref(), confirm)
|
|
4162
4171
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
4163
4172
|
let mut value = lease_value(result);
|
|
4164
4173
|
insert_resolved_team(&mut value, team, resolved_team.as_deref());
|
|
4174
|
+
append_team_session_ready(workspace, resolved_team.as_deref(), &mut value);
|
|
4165
4175
|
if value.get("ok").and_then(Value::as_bool) == Some(true) {
|
|
4166
|
-
emit_topology_convergence_event(
|
|
4167
|
-
|
|
4176
|
+
emit_topology_convergence_event(
|
|
4177
|
+
workspace,
|
|
4178
|
+
resolved_team.as_deref(),
|
|
4179
|
+
"claim-leader",
|
|
4180
|
+
&value,
|
|
4181
|
+
);
|
|
4182
|
+
register_after_binding_success(
|
|
4183
|
+
workspace,
|
|
4184
|
+
resolved_team.as_deref(),
|
|
4185
|
+
"claim-leader",
|
|
4186
|
+
&mut value,
|
|
4187
|
+
);
|
|
4188
|
+
restore_non_target_teams(workspace, &state, resolved_team.as_deref());
|
|
4168
4189
|
}
|
|
4169
4190
|
Ok(value)
|
|
4170
4191
|
}
|
|
@@ -4260,6 +4281,7 @@ pub mod leader_port {
|
|
|
4260
4281
|
"owner_epoch": convergence.get("owner_epoch").cloned().unwrap_or(Value::Null),
|
|
4261
4282
|
"persisted": convergence.get("persisted").cloned().unwrap_or(Value::Bool(false)),
|
|
4262
4283
|
"checked_paths": convergence.get("checked_paths").cloned().unwrap_or_else(|| json!([])),
|
|
4284
|
+
"team_session_ready": response.get("team_session_ready").cloned().unwrap_or(Value::Null),
|
|
4263
4285
|
}),
|
|
4264
4286
|
);
|
|
4265
4287
|
}
|
|
@@ -4325,14 +4347,102 @@ pub mod leader_port {
|
|
|
4325
4347
|
| crate::state::projection::OwnerTeamResolution::Ambiguous { .. } => None,
|
|
4326
4348
|
}
|
|
4327
4349
|
}
|
|
4328
|
-
None => Some(TeamKey::new(
|
|
4329
|
-
|
|
4330
|
-
|
|
4350
|
+
None => Some(TeamKey::new(active_or_derived_team_key(state))),
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
fn active_or_derived_team_key(state: &Value) -> String {
|
|
4355
|
+
state
|
|
4356
|
+
.get("active_team_key")
|
|
4357
|
+
.and_then(Value::as_str)
|
|
4358
|
+
.filter(|team| !team.is_empty())
|
|
4359
|
+
.map(str::to_string)
|
|
4360
|
+
.unwrap_or_else(|| crate::state::projection::team_state_key(state))
|
|
4361
|
+
}
|
|
4362
|
+
|
|
4363
|
+
fn append_team_session_ready(workspace: &Path, team: Option<&str>, response: &mut Value) {
|
|
4364
|
+
let Some(obj) = response.as_object_mut() else {
|
|
4365
|
+
return;
|
|
4366
|
+
};
|
|
4367
|
+
let Some(convergence) = obj.get("topology_convergence") else {
|
|
4368
|
+
return;
|
|
4369
|
+
};
|
|
4370
|
+
if convergence.get("status").and_then(Value::as_str) != Some("converged") {
|
|
4371
|
+
return;
|
|
4372
|
+
}
|
|
4373
|
+
let Some(endpoint) = convergence
|
|
4374
|
+
.get("new_tmux_endpoint")
|
|
4375
|
+
.and_then(Value::as_str)
|
|
4376
|
+
.filter(|endpoint| !endpoint.is_empty())
|
|
4377
|
+
else {
|
|
4378
|
+
obj.insert("team_session_ready".to_string(), Value::Null);
|
|
4379
|
+
return;
|
|
4380
|
+
};
|
|
4381
|
+
let session_name = crate::state::persist::load_runtime_state_without_migrations(workspace)
|
|
4382
|
+
.ok()
|
|
4383
|
+
.and_then(|state| target_team_session_name(&state, team));
|
|
4384
|
+
let ready = session_name
|
|
4385
|
+
.as_deref()
|
|
4386
|
+
.and_then(|session| crate::topology::team_session_ready_on_endpoint(endpoint, session));
|
|
4387
|
+
obj.insert(
|
|
4388
|
+
"team_session_ready".to_string(),
|
|
4389
|
+
ready.map(Value::Bool).unwrap_or(Value::Null),
|
|
4390
|
+
);
|
|
4391
|
+
}
|
|
4392
|
+
|
|
4393
|
+
fn target_team_session_name(state: &Value, team: Option<&str>) -> Option<String> {
|
|
4394
|
+
team.and_then(|team| {
|
|
4395
|
+
state
|
|
4396
|
+
.get("teams")
|
|
4397
|
+
.and_then(Value::as_object)
|
|
4398
|
+
.and_then(|teams| teams.get(team))
|
|
4399
|
+
})
|
|
4400
|
+
.and_then(|team_state| team_state.get("session_name"))
|
|
4401
|
+
.and_then(Value::as_str)
|
|
4402
|
+
.filter(|session| !session.is_empty())
|
|
4403
|
+
.or_else(|| state.get("session_name").and_then(Value::as_str))
|
|
4404
|
+
.filter(|session| !session.is_empty())
|
|
4405
|
+
.map(str::to_string)
|
|
4406
|
+
}
|
|
4407
|
+
|
|
4408
|
+
fn restore_non_target_teams(workspace: &Path, before: &Value, target: Option<&str>) {
|
|
4409
|
+
let Some(target) = target.filter(|team| !team.is_empty()) else {
|
|
4410
|
+
return;
|
|
4411
|
+
};
|
|
4412
|
+
let Some(before_teams) = before.get("teams").and_then(Value::as_object) else {
|
|
4413
|
+
return;
|
|
4414
|
+
};
|
|
4415
|
+
let Ok(mut latest) =
|
|
4416
|
+
crate::state::persist::load_runtime_state_without_migrations(workspace)
|
|
4417
|
+
else {
|
|
4418
|
+
return;
|
|
4419
|
+
};
|
|
4420
|
+
let Some(latest_teams) = latest.get_mut("teams").and_then(Value::as_object_mut) else {
|
|
4421
|
+
return;
|
|
4422
|
+
};
|
|
4423
|
+
let mut changed = false;
|
|
4424
|
+
for (team, before_entry) in before_teams {
|
|
4425
|
+
if team == target {
|
|
4426
|
+
continue;
|
|
4427
|
+
}
|
|
4428
|
+
if latest_teams.get(team) != Some(before_entry) {
|
|
4429
|
+
latest_teams.insert(team.clone(), before_entry.clone());
|
|
4430
|
+
changed = true;
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
if changed {
|
|
4434
|
+
let _ = crate::state::repository::StateRepository::new(workspace).save(
|
|
4435
|
+
crate::state::repository::StateWriteIntent::LeaderBindingRestoreNonTargetTeams {
|
|
4436
|
+
target_team_key: target,
|
|
4437
|
+
},
|
|
4438
|
+
&latest,
|
|
4439
|
+
);
|
|
4331
4440
|
}
|
|
4332
4441
|
}
|
|
4333
4442
|
|
|
4334
4443
|
fn insert_resolved_team(value: &mut Value, requested: Option<&str>, resolved: Option<&str>) {
|
|
4335
|
-
let (Some(requested), Some(resolved)) =
|
|
4444
|
+
let (Some(requested), Some(resolved)) =
|
|
4445
|
+
(requested.filter(|team| !team.is_empty()), resolved)
|
|
4336
4446
|
else {
|
|
4337
4447
|
return;
|
|
4338
4448
|
};
|
|
@@ -97,9 +97,15 @@ impl NamedAddressError {
|
|
|
97
97
|
obj.insert("error".to_string(), Value::String(self.message.clone()));
|
|
98
98
|
obj.insert("action".to_string(), Value::String(self.action.clone()));
|
|
99
99
|
obj.insert("log".to_string(), Value::String(self.log.clone()));
|
|
100
|
-
obj.insert(
|
|
100
|
+
obj.insert(
|
|
101
|
+
"candidates".to_string(),
|
|
102
|
+
Value::Array(self.candidates.clone()),
|
|
103
|
+
);
|
|
101
104
|
if let Some(requested) = self.requested_team.as_deref() {
|
|
102
|
-
obj.insert(
|
|
105
|
+
obj.insert(
|
|
106
|
+
"requested_team".to_string(),
|
|
107
|
+
Value::String(requested.to_string()),
|
|
108
|
+
);
|
|
103
109
|
}
|
|
104
110
|
if !self.available_team_keys.is_empty() {
|
|
105
111
|
obj.insert(
|
|
@@ -113,7 +119,10 @@ impl NamedAddressError {
|
|
|
113
119
|
);
|
|
114
120
|
}
|
|
115
121
|
if let Some(suggested) = self.suggested_name.as_deref() {
|
|
116
|
-
obj.insert(
|
|
122
|
+
obj.insert(
|
|
123
|
+
"suggested_name".to_string(),
|
|
124
|
+
Value::String(suggested.to_string()),
|
|
125
|
+
);
|
|
117
126
|
}
|
|
118
127
|
Value::Object(obj)
|
|
119
128
|
}
|
|
@@ -662,8 +671,8 @@ fn resolve_leader(
|
|
|
662
671
|
// silently reused the `current` team's receiver and reported a stale
|
|
663
672
|
// pane_id). Legacy single-team compat is preserved only when the state
|
|
664
673
|
// has no `teams` map yet and the requested key equals `active_team_key`.
|
|
665
|
-
let receiver =
|
|
666
|
-
|
|
674
|
+
let receiver =
|
|
675
|
+
resolve_team_scoped_leader_receiver(state, team, target_workspace)?.ok_or_else(|| {
|
|
667
676
|
let mut err = leader_not_attached_third_party(
|
|
668
677
|
target_workspace,
|
|
669
678
|
team,
|