@team-agent/installer 0.5.42 → 0.5.43
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 +27 -7
- 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 +47 -52
- 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 +52 -19
- package/crates/team-agent/src/leader/helpers.rs +7 -1
- package/crates/team-agent/src/leader/lease.rs +166 -80
- package/crates/team-agent/src/leader/owner_bind.rs +55 -22
- 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/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 +6 -1
- 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/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.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 +21 -22
- 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/repository.rs +1 -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 +27 -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
|
@@ -174,7 +174,10 @@ fn prepare_profile_launch(
|
|
|
174
174
|
claude_config_dir = Some(dir);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
env_overlay.extend(compatible_api_network_exports(
|
|
177
|
+
env_overlay.extend(compatible_api_network_exports(
|
|
178
|
+
agent.auth_mode,
|
|
179
|
+
&loaded.values,
|
|
180
|
+
));
|
|
178
181
|
if agent.auth_mode == AuthMode::CompatibleApi && profile_proxy_mode(&loaded.values) == "direct"
|
|
179
182
|
{
|
|
180
183
|
for key in COMPATIBLE_NETWORK_ENV_KEYS {
|
|
@@ -307,7 +310,11 @@ fn validate_profile(
|
|
|
307
310
|
{
|
|
308
311
|
let profile_model = profile_model(&loaded.values);
|
|
309
312
|
if let Some(profile_model) = profile_model {
|
|
310
|
-
if agent
|
|
313
|
+
if agent
|
|
314
|
+
.model
|
|
315
|
+
.as_deref()
|
|
316
|
+
.is_some_and(|model| model != profile_model)
|
|
317
|
+
{
|
|
311
318
|
return Err(LifecycleError::RequirementUnmet(format!(
|
|
312
319
|
"role/team model does not match profile MODEL in {}",
|
|
313
320
|
loaded.path.display()
|
|
@@ -320,8 +327,7 @@ fn validate_profile(
|
|
|
320
327
|
if key == &"API_KEY" {
|
|
321
328
|
continue;
|
|
322
329
|
}
|
|
323
|
-
if !profile_value_or_alternate(&loaded.values, key).is_some_and(|value| !value.is_empty())
|
|
324
|
-
{
|
|
330
|
+
if !profile_value_or_alternate(&loaded.values, key).is_some_and(|value| !value.is_empty()) {
|
|
325
331
|
missing.push(*key);
|
|
326
332
|
}
|
|
327
333
|
}
|
|
@@ -393,16 +399,24 @@ fn provider_env_exports(
|
|
|
393
399
|
// Subscription/OfficialApi 不导出 COPILOT_PROVIDER_*(避免误改 auth 通道)。
|
|
394
400
|
Provider::Copilot => {
|
|
395
401
|
if auth_mode == AuthMode::CompatibleApi {
|
|
396
|
-
if let Some(value) =
|
|
402
|
+
if let Some(value) =
|
|
403
|
+
value_or_alternate(values, "COPILOT_PROVIDER_BASE_URL", "BASE_URL")
|
|
404
|
+
{
|
|
397
405
|
exports.insert("COPILOT_PROVIDER_BASE_URL".to_string(), value.to_string());
|
|
398
406
|
}
|
|
399
|
-
if let Some(value) =
|
|
407
|
+
if let Some(value) =
|
|
408
|
+
value_or_alternate(values, "COPILOT_PROVIDER_TYPE", "PROVIDER_TYPE")
|
|
409
|
+
{
|
|
400
410
|
exports.insert("COPILOT_PROVIDER_TYPE".to_string(), value.to_string());
|
|
401
411
|
}
|
|
402
|
-
if let Some(value) =
|
|
412
|
+
if let Some(value) =
|
|
413
|
+
value_or_alternate(values, "COPILOT_PROVIDER_API_KEY", "API_KEY")
|
|
414
|
+
{
|
|
403
415
|
exports.insert("COPILOT_PROVIDER_API_KEY".to_string(), value.to_string());
|
|
404
416
|
}
|
|
405
|
-
if let Some(value) =
|
|
417
|
+
if let Some(value) =
|
|
418
|
+
value_or_alternate(values, "COPILOT_PROVIDER_WIRE_API", "WIRE_API")
|
|
419
|
+
{
|
|
406
420
|
exports.insert("COPILOT_PROVIDER_WIRE_API".to_string(), value.to_string());
|
|
407
421
|
}
|
|
408
422
|
if let Some(value) = value_or_alternate(values, "COPILOT_MODEL", "MODEL") {
|
|
@@ -505,8 +519,8 @@ fn provider_command_overrides(
|
|
|
505
519
|
// Python provider_env.py:62-79 (_provider_command_overrides codex branch).
|
|
506
520
|
match agent.provider {
|
|
507
521
|
Provider::Codex => {
|
|
508
|
-
codex_profile =
|
|
509
|
-
.map(str::to_string);
|
|
522
|
+
codex_profile =
|
|
523
|
+
value_or_alternate(values, "CODEX_PROFILE", "NATIVE_PROFILE").map(str::to_string);
|
|
510
524
|
let model_provider = values.get("MODEL_PROVIDER").filter(|v| !v.is_empty());
|
|
511
525
|
let base_url = values.get("BASE_URL").filter(|v| !v.is_empty());
|
|
512
526
|
if agent.auth_mode == AuthMode::CompatibleApi {
|
|
@@ -620,8 +634,14 @@ fn ensure_compatible_claude_config(dir: &Path, workspace: &Path) -> Result<(), L
|
|
|
620
634
|
"skipDangerousModePermissionPrompt",
|
|
621
635
|
serde_json::json!(true),
|
|
622
636
|
);
|
|
623
|
-
settings.insert(
|
|
624
|
-
|
|
637
|
+
settings.insert(
|
|
638
|
+
"hasCompletedOnboarding".to_string(),
|
|
639
|
+
serde_json::json!(true),
|
|
640
|
+
);
|
|
641
|
+
settings.insert(
|
|
642
|
+
"hasTrustDialogAccepted".to_string(),
|
|
643
|
+
serde_json::json!(true),
|
|
644
|
+
);
|
|
625
645
|
insert_if_missing(
|
|
626
646
|
&mut settings,
|
|
627
647
|
"lastOnboardingVersion",
|
|
@@ -642,7 +662,10 @@ fn ensure_compatible_claude_config(dir: &Path, workspace: &Path) -> Result<(), L
|
|
|
642
662
|
|
|
643
663
|
let state_path = dir.join(".claude.json");
|
|
644
664
|
let mut state = read_json_object(&state_path)?;
|
|
645
|
-
state.insert(
|
|
665
|
+
state.insert(
|
|
666
|
+
"hasCompletedOnboarding".to_string(),
|
|
667
|
+
serde_json::json!(true),
|
|
668
|
+
);
|
|
646
669
|
insert_if_missing(
|
|
647
670
|
&mut state,
|
|
648
671
|
"lastOnboardingVersion",
|
|
@@ -665,8 +688,14 @@ fn ensure_compatible_claude_mcp_config(
|
|
|
665
688
|
) -> Result<(), LifecycleError> {
|
|
666
689
|
let state_path = dir.join(".claude.json");
|
|
667
690
|
let mut state = read_json_object(&state_path)?;
|
|
668
|
-
state.insert(
|
|
669
|
-
|
|
691
|
+
state.insert(
|
|
692
|
+
"hasCompletedOnboarding".to_string(),
|
|
693
|
+
serde_json::json!(true),
|
|
694
|
+
);
|
|
695
|
+
state.insert(
|
|
696
|
+
"hasTrustDialogAccepted".to_string(),
|
|
697
|
+
serde_json::json!(true),
|
|
698
|
+
);
|
|
670
699
|
insert_if_missing(
|
|
671
700
|
&mut state,
|
|
672
701
|
"projectOnboardingSeenCount",
|
|
@@ -686,7 +715,10 @@ fn ensure_claude_projects(
|
|
|
686
715
|
workspace: &Path,
|
|
687
716
|
mcp_servers: Option<&serde_json::Value>,
|
|
688
717
|
) {
|
|
689
|
-
if !state
|
|
718
|
+
if !state
|
|
719
|
+
.get("projects")
|
|
720
|
+
.is_some_and(serde_json::Value::is_object)
|
|
721
|
+
{
|
|
690
722
|
state.insert("projects".to_string(), serde_json::json!({}));
|
|
691
723
|
}
|
|
692
724
|
let Some(projects) = state
|
|
@@ -703,7 +735,10 @@ fn ensure_claude_projects(
|
|
|
703
735
|
let Some(project) = entry.as_object_mut() else {
|
|
704
736
|
continue;
|
|
705
737
|
};
|
|
706
|
-
project.insert(
|
|
738
|
+
project.insert(
|
|
739
|
+
"hasTrustDialogAccepted".to_string(),
|
|
740
|
+
serde_json::json!(true),
|
|
741
|
+
);
|
|
707
742
|
insert_if_missing(project, "projectOnboardingSeenCount", serde_json::json!(1));
|
|
708
743
|
if let Some(mcp_servers) = mcp_servers {
|
|
709
744
|
insert_if_missing(project, "allowedTools", serde_json::json!([]));
|
|
@@ -892,22 +927,26 @@ pub(crate) fn profile_value_or_alternate<'a>(
|
|
|
892
927
|
.get(key)
|
|
893
928
|
.filter(|value| !value.is_empty())
|
|
894
929
|
.map(String::as_str)
|
|
895
|
-
.or_else(||
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
930
|
+
.or_else(|| {
|
|
931
|
+
match key {
|
|
932
|
+
"API_KEY" => values
|
|
933
|
+
.get("ANTHROPIC_API_KEY")
|
|
934
|
+
.or_else(|| values.get("ANTHROPIC_AUTH_TOKEN"))
|
|
935
|
+
.or_else(|| values.get("AUTH_TOKEN"))
|
|
936
|
+
.or_else(|| values.get("CLAUDE_API_KEY"))
|
|
937
|
+
.or_else(|| values.get("CLAUDE_AUTH_TOKEN"))
|
|
938
|
+
.or_else(|| values.get("BEARER_TOKEN"))
|
|
939
|
+
.or_else(|| values.get("TEAM_AGENT_PROVIDER_API_KEY"))
|
|
940
|
+
.or_else(|| values.get("OPENAI_API_KEY")),
|
|
941
|
+
"BASE_URL" => values
|
|
942
|
+
.get("ANTHROPIC_BASE_URL")
|
|
943
|
+
.or_else(|| values.get("OPENAI_BASE_URL")),
|
|
944
|
+
"MODEL" => values.get("ANTHROPIC_MODEL"),
|
|
945
|
+
_ => None,
|
|
946
|
+
}
|
|
947
|
+
.filter(|value| !value.is_empty())
|
|
948
|
+
.map(String::as_str)
|
|
949
|
+
})
|
|
911
950
|
}
|
|
912
951
|
|
|
913
952
|
pub(crate) fn value_or_alternate<'a>(
|
|
@@ -926,7 +965,11 @@ pub(crate) fn profile_model(values: &BTreeMap<String, String>) -> Option<&str> {
|
|
|
926
965
|
values
|
|
927
966
|
.get("MODEL")
|
|
928
967
|
.filter(|value| !value.is_empty())
|
|
929
|
-
.or_else(||
|
|
968
|
+
.or_else(|| {
|
|
969
|
+
values
|
|
970
|
+
.get("ANTHROPIC_MODEL")
|
|
971
|
+
.filter(|value| !value.is_empty())
|
|
972
|
+
})
|
|
930
973
|
.map(String::as_str)
|
|
931
974
|
}
|
|
932
975
|
|
|
@@ -187,7 +187,9 @@ impl SmokeTarget {
|
|
|
187
187
|
}
|
|
188
188
|
// C-7-1 cr verdict: copilot 一期 subscription-only,无 BYOK HTTP smoke
|
|
189
189
|
// 入口;同 GeminiCli/Fake 走 unsupported_provider_smoke_skipped。
|
|
190
|
-
Provider::Copilot | Provider::GeminiCli | Provider::Fake =>
|
|
190
|
+
Provider::Copilot | Provider::GeminiCli | Provider::Fake => {
|
|
191
|
+
Err("unsupported_provider_smoke_skipped")
|
|
192
|
+
}
|
|
191
193
|
}
|
|
192
194
|
}
|
|
193
195
|
|
|
@@ -826,12 +826,7 @@ pub(crate) fn start_agent_at_paths_for_recovery(
|
|
|
826
826
|
// subsequent start creates a fresh provider process instead of a Noop.
|
|
827
827
|
// Stop errors are absorbed into the start attempt result — the important
|
|
828
828
|
// invariant is that a successful start returns Running, never Noop.
|
|
829
|
-
let _ = stop_agent_with_transport(
|
|
830
|
-
&paths.run_workspace,
|
|
831
|
-
agent_id,
|
|
832
|
-
team,
|
|
833
|
-
transport,
|
|
834
|
-
);
|
|
829
|
+
let _ = stop_agent_with_transport(&paths.run_workspace, agent_id, team, transport);
|
|
835
830
|
let start_result = start_agent_with_transport(
|
|
836
831
|
&paths.run_workspace,
|
|
837
832
|
agent_id,
|
|
@@ -26,10 +26,7 @@ pub fn halt_plan(
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/// `plan_status(workspace, plan_id)`(`orchestrator/__init__.py:177`)。读 plan 持久态。
|
|
29
|
-
pub fn plan_status(
|
|
30
|
-
workspace: &Path,
|
|
31
|
-
plan_id: &PlanId,
|
|
32
|
-
) -> Result<PlanState, LifecycleError> {
|
|
29
|
+
pub fn plan_status(workspace: &Path, plan_id: &PlanId) -> Result<PlanState, LifecycleError> {
|
|
33
30
|
let path = plan_state_path(workspace, plan_id);
|
|
34
31
|
if !path.exists() {
|
|
35
32
|
return Err(LifecycleError::InvalidPlan(format!(
|
|
@@ -37,11 +37,12 @@ impl SessionPreflight {
|
|
|
37
37
|
/// kill is safe. Pure: no I/O, no mutation.
|
|
38
38
|
pub fn check_session_preflight(state: &serde_json::Value) -> SessionPreflight {
|
|
39
39
|
let sessions = RuntimeSessions::from_state(state);
|
|
40
|
-
if let Some(anomaly) = sessions
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
if let Some(anomaly) = sessions.anomalies.iter().find(|a| {
|
|
41
|
+
matches!(
|
|
42
|
+
a,
|
|
43
|
+
RuntimeSessionAnomaly::WorkerSessionNameIsLeaderPrefixed { .. }
|
|
44
|
+
)
|
|
45
|
+
}) {
|
|
45
46
|
if let RuntimeSessionAnomaly::WorkerSessionNameIsLeaderPrefixed { value } = anomaly {
|
|
46
47
|
return SessionPreflight::WorkerSessionIsLeaderSession {
|
|
47
48
|
session_name: value.clone(),
|
|
@@ -523,7 +523,10 @@ fn restart_with_selected_team_and_transport(
|
|
|
523
523
|
// as pre-0.5.38 (fake harness + missing agent branches). Where a
|
|
524
524
|
// parallel Some(Ok(spawn)) is present we skip the transport spawn
|
|
525
525
|
// and jump straight to verify + mark.
|
|
526
|
-
let parallel_result = parallel_outcomes
|
|
526
|
+
let parallel_result = parallel_outcomes
|
|
527
|
+
.get(decision_index)
|
|
528
|
+
.cloned()
|
|
529
|
+
.unwrap_or(None);
|
|
527
530
|
if let Some(parallel_result) = parallel_result {
|
|
528
531
|
match parallel_result {
|
|
529
532
|
Ok(pspawn) => {
|
|
@@ -642,11 +645,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
642
645
|
"tmux_server_crashed: session {} disappeared during replacement build; refusing to touch original agent state",
|
|
643
646
|
session_name.as_str()
|
|
644
647
|
);
|
|
645
|
-
failed_agents.push(restart_failed_agent(
|
|
646
|
-
decision,
|
|
647
|
-
"tmux_server_crashed",
|
|
648
|
-
error,
|
|
649
|
-
));
|
|
648
|
+
failed_agents.push(restart_failed_agent(decision, "tmux_server_crashed", error));
|
|
650
649
|
continue;
|
|
651
650
|
}
|
|
652
651
|
if let Some(previous) = successful_agents.pop() {
|
|
@@ -741,10 +740,9 @@ fn restart_with_selected_team_and_transport(
|
|
|
741
740
|
continue;
|
|
742
741
|
}
|
|
743
742
|
let pane_verify_ms = u64::try_from(verify_start.elapsed().as_millis()).unwrap_or(u64::MAX);
|
|
744
|
-
let transport_spawn_ms =
|
|
745
|
-
(verify_start.saturating_duration_since(spawn_start)).as_millis()
|
|
746
|
-
|
|
747
|
-
.unwrap_or(u64::MAX);
|
|
743
|
+
let transport_spawn_ms =
|
|
744
|
+
u64::try_from((verify_start.saturating_duration_since(spawn_start)).as_millis())
|
|
745
|
+
.unwrap_or(u64::MAX);
|
|
748
746
|
// 0.5.38 Step 1: per-worker spawn timing so operators can identify
|
|
749
747
|
// whether wall time is spent in command-plan compilation, transport
|
|
750
748
|
// spawn, pane verification, or provider startup prompts.
|
|
@@ -1096,7 +1094,11 @@ fn restart_with_selected_team_and_transport(
|
|
|
1096
1094
|
drop(lifecycle_lock);
|
|
1097
1095
|
let coordinator =
|
|
1098
1096
|
start_coordinator_for_workspace(&selected.run_workspace, Some(&selected.team_key))?;
|
|
1099
|
-
phase_timer.emit(
|
|
1097
|
+
phase_timer.emit(
|
|
1098
|
+
&selected.run_workspace,
|
|
1099
|
+
"restart.phase",
|
|
1100
|
+
"coordinator_start",
|
|
1101
|
+
);
|
|
1100
1102
|
let coordinator_started = coordinator.ok;
|
|
1101
1103
|
phase_timer.emit(&selected.run_workspace, "restart.phase", "readiness_wait");
|
|
1102
1104
|
wait_restart_readiness_or_timeout(
|
|
@@ -2420,12 +2422,11 @@ fn run_bounded_parallel_worker_spawns(
|
|
|
2420
2422
|
let _ = concurrency_placeholder(inputs_len); // reserved for future config
|
|
2421
2423
|
let inputs_shared: Vec<Option<SpawnInput>> = inputs.into_iter().map(Some).collect();
|
|
2422
2424
|
let inputs_shared = std::sync::Arc::new(std::sync::Mutex::new(inputs_shared));
|
|
2423
|
-
let next_submit_slot =
|
|
2424
|
-
std::sync::Mutex::new(0usize),
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
|
|
2425
|
+
let next_submit_slot =
|
|
2426
|
+
std::sync::Arc::new((std::sync::Mutex::new(0usize), std::sync::Condvar::new()));
|
|
2427
|
+
let results: std::sync::Arc<
|
|
2428
|
+
std::sync::Mutex<Vec<(usize, Result<ParallelSpawnResult, String>)>>,
|
|
2429
|
+
> = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
|
|
2429
2430
|
std::thread::scope(|scope| {
|
|
2430
2431
|
for slot in 0..inputs_len {
|
|
2431
2432
|
let inputs_shared = std::sync::Arc::clone(&inputs_shared);
|
|
@@ -2530,8 +2531,8 @@ fn apply_marked_respawn(
|
|
|
2530
2531
|
fatal_resume_failure: &mut bool,
|
|
2531
2532
|
) {
|
|
2532
2533
|
let verify_start = std::time::Instant::now();
|
|
2533
|
-
if let Err(error) =
|
|
2534
|
-
.and_then(|_| {
|
|
2534
|
+
if let Err(error) =
|
|
2535
|
+
verify_spawned_agent_live(&decision.agent_id, spawn, transport).and_then(|_| {
|
|
2535
2536
|
mark_agent_respawned(
|
|
2536
2537
|
state,
|
|
2537
2538
|
&decision.agent_id,
|
|
@@ -2553,9 +2554,12 @@ fn apply_marked_respawn(
|
|
|
2553
2554
|
return;
|
|
2554
2555
|
}
|
|
2555
2556
|
let pane_verify_ms = u64::try_from(verify_start.elapsed().as_millis()).unwrap_or(u64::MAX);
|
|
2556
|
-
let transport_spawn_ms =
|
|
2557
|
-
|
|
2558
|
-
.
|
|
2557
|
+
let transport_spawn_ms = u64::try_from(
|
|
2558
|
+
verify_start
|
|
2559
|
+
.saturating_duration_since(spawn_start)
|
|
2560
|
+
.as_millis(),
|
|
2561
|
+
)
|
|
2562
|
+
.unwrap_or(u64::MAX);
|
|
2559
2563
|
write_worker_spawn_timing_event(
|
|
2560
2564
|
run_workspace,
|
|
2561
2565
|
phase_timer.elapsed_ms(),
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
use super
|
|
2
|
-
|
|
1
|
+
use super::agent::{
|
|
2
|
+
resolve_team_scoped_state_or_refuse, start_agent_at_paths, stop_agent_at_paths,
|
|
3
|
+
};
|
|
3
4
|
use super::common::*;
|
|
4
5
|
use super::team_state::write_team_state;
|
|
6
|
+
use super::*;
|
|
5
7
|
use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
|
|
6
8
|
|
|
7
9
|
/// `remove_agent(workspace, agent_id, from_spec, force, team)`(`lifecycle/agents.py:22`)。
|
|
@@ -21,8 +23,7 @@ pub fn remove_agent(
|
|
|
21
23
|
team,
|
|
22
24
|
agent_id: Some(agent_id),
|
|
23
25
|
})?;
|
|
24
|
-
let transport =
|
|
25
|
-
lifecycle_worker_tmux_backend_for_selected_state(&paths.run_workspace, team)?;
|
|
26
|
+
let transport = lifecycle_worker_tmux_backend_for_selected_state(&paths.run_workspace, team)?;
|
|
26
27
|
remove_agent_at_paths(
|
|
27
28
|
&paths.run_workspace,
|
|
28
29
|
&paths.spec_workspace,
|
|
@@ -66,8 +67,7 @@ pub fn remove_agent_flag_requirements(
|
|
|
66
67
|
team: Option<&str>,
|
|
67
68
|
) -> Result<RemoveAgentFlagRequirements, LifecycleError> {
|
|
68
69
|
let paths = lifecycle_paths(workspace, team)?;
|
|
69
|
-
let transport =
|
|
70
|
-
lifecycle_worker_tmux_backend_for_selected_state(&paths.run_workspace, team)?;
|
|
70
|
+
let transport = lifecycle_worker_tmux_backend_for_selected_state(&paths.run_workspace, team)?;
|
|
71
71
|
Ok(remove_agent_preflight(
|
|
72
72
|
&paths.run_workspace,
|
|
73
73
|
&paths.spec_workspace,
|
|
@@ -207,7 +207,8 @@ fn remove_agent_at_paths(
|
|
|
207
207
|
// golden agents.py:110-133: restore is best-effort (collects per-artifact errors, restores ALL),
|
|
208
208
|
// and the ORIGINAL operation error is ALWAYS re-raised, annotated with rollback_ok — a
|
|
209
209
|
// restore-step failure only flips rollback_ok, it never replaces the surfaced cause.
|
|
210
|
-
let restore_errors =
|
|
210
|
+
let restore_errors =
|
|
211
|
+
rollback.restore(paths.run_workspace, paths.spec_workspace, team, transport);
|
|
211
212
|
let rollback_ok = restore_errors.is_empty();
|
|
212
213
|
let rollback_event = RemoveRollbackEvent {
|
|
213
214
|
agent_id,
|
|
@@ -243,7 +244,13 @@ fn remove_agent_inner(
|
|
|
243
244
|
let mut stopped = false;
|
|
244
245
|
let mut cleared_locations = Vec::new();
|
|
245
246
|
if force && agent_is_running(&working_state, agent_id, transport) {
|
|
246
|
-
let stop = stop_agent_at_paths(
|
|
247
|
+
let stop = stop_agent_at_paths(
|
|
248
|
+
paths.run_workspace,
|
|
249
|
+
paths.spec_workspace,
|
|
250
|
+
agent_id,
|
|
251
|
+
team,
|
|
252
|
+
transport,
|
|
253
|
+
)?;
|
|
247
254
|
stopped = stop.stopped;
|
|
248
255
|
write_remove_step_event(
|
|
249
256
|
paths.run_workspace,
|
|
@@ -265,7 +272,7 @@ fn remove_agent_inner(
|
|
|
265
272
|
&removed_state,
|
|
266
273
|
&[agent_id.as_str()],
|
|
267
274
|
)
|
|
268
|
-
|
|
275
|
+
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
269
276
|
cleared_locations.push(serde_json::json!("state.json:agents"));
|
|
270
277
|
write_remove_step_event(
|
|
271
278
|
paths.run_workspace,
|
|
@@ -282,7 +289,9 @@ fn remove_agent_inner(
|
|
|
282
289
|
}
|
|
283
290
|
// golden agents.py:96-100,157: state_file = the team_state.md path written from removed_spec/state.
|
|
284
291
|
let team_state_path = write_team_state(paths.spec_workspace, &removed_spec, &removed_state)?;
|
|
285
|
-
cleared_locations.push(serde_json::json!(team_state_path
|
|
292
|
+
cleared_locations.push(serde_json::json!(team_state_path
|
|
293
|
+
.to_string_lossy()
|
|
294
|
+
.to_string()));
|
|
286
295
|
write_remove_step_event(
|
|
287
296
|
paths.run_workspace,
|
|
288
297
|
agent_id,
|
|
@@ -290,8 +299,11 @@ fn remove_agent_inner(
|
|
|
290
299
|
&team_state_path.to_string_lossy(),
|
|
291
300
|
None,
|
|
292
301
|
)?;
|
|
293
|
-
std::fs::write(
|
|
294
|
-
.
|
|
302
|
+
std::fs::write(
|
|
303
|
+
paths.spec_workspace.join("team.spec.yaml"),
|
|
304
|
+
yaml::dumps(&removed_spec),
|
|
305
|
+
)
|
|
306
|
+
.map_err(|e| LifecycleError::StatePersist(format!("write spec: {e}")))?;
|
|
295
307
|
cleared_locations.push(serde_json::json!("team.spec.yaml"));
|
|
296
308
|
write_remove_step_event(
|
|
297
309
|
paths.run_workspace,
|
|
@@ -519,7 +531,11 @@ fn runtime_without_startup_agent(runtime: &YamlValue, agent_id: &AgentId) -> Yam
|
|
|
519
531
|
.map(|items| {
|
|
520
532
|
items
|
|
521
533
|
.iter()
|
|
522
|
-
.filter(|item|
|
|
534
|
+
.filter(|item| {
|
|
535
|
+
item.as_str()
|
|
536
|
+
.map(|id| id != agent_id.as_str())
|
|
537
|
+
.unwrap_or(true)
|
|
538
|
+
})
|
|
523
539
|
.cloned()
|
|
524
540
|
.collect::<Vec<_>>()
|
|
525
541
|
})
|
|
@@ -538,9 +554,7 @@ fn routing_without_agent(routing: &YamlValue, agent_id: &AgentId) -> YamlValue {
|
|
|
538
554
|
};
|
|
539
555
|
let mut out = Vec::new();
|
|
540
556
|
for (key, value) in pairs {
|
|
541
|
-
if key == "default_assignee"
|
|
542
|
-
&& value.as_str().is_some_and(|id| id == agent_id.as_str())
|
|
543
|
-
{
|
|
557
|
+
if key == "default_assignee" && value.as_str().is_some_and(|id| id == agent_id.as_str()) {
|
|
544
558
|
out.push((key.clone(), YamlValue::Str(String::new())));
|
|
545
559
|
} else if key == "rules" {
|
|
546
560
|
let rules = value
|
|
@@ -549,8 +563,7 @@ fn routing_without_agent(routing: &YamlValue, agent_id: &AgentId) -> YamlValue {
|
|
|
549
563
|
items
|
|
550
564
|
.iter()
|
|
551
565
|
.filter(|rule| {
|
|
552
|
-
rule
|
|
553
|
-
.get("assign_to")
|
|
566
|
+
rule.get("assign_to")
|
|
554
567
|
.and_then(YamlValue::as_str)
|
|
555
568
|
.map(|id| id != agent_id.as_str())
|
|
556
569
|
.unwrap_or(true)
|
|
@@ -587,11 +600,7 @@ fn task_without_agent_assignee(task: &YamlValue, agent_id: &AgentId) -> YamlValu
|
|
|
587
600
|
pairs
|
|
588
601
|
.iter()
|
|
589
602
|
.map(|(key, value)| {
|
|
590
|
-
if key == "assignee"
|
|
591
|
-
&& value
|
|
592
|
-
.as_str()
|
|
593
|
-
.is_some_and(|id| id == agent_id.as_str())
|
|
594
|
-
{
|
|
603
|
+
if key == "assignee" && value.as_str().is_some_and(|id| id == agent_id.as_str()) {
|
|
595
604
|
(key.clone(), YamlValue::Str(String::new()))
|
|
596
605
|
} else {
|
|
597
606
|
(key.clone(), value.clone())
|
|
@@ -601,18 +610,12 @@ fn task_without_agent_assignee(task: &YamlValue, agent_id: &AgentId) -> YamlValu
|
|
|
601
610
|
)
|
|
602
611
|
}
|
|
603
612
|
|
|
604
|
-
fn remove_dynamic_role_file(
|
|
605
|
-
path: &Path,
|
|
606
|
-
required: bool,
|
|
607
|
-
) -> Result<bool, LifecycleError> {
|
|
613
|
+
fn remove_dynamic_role_file(path: &Path, required: bool) -> Result<bool, LifecycleError> {
|
|
608
614
|
match std::fs::remove_file(path) {
|
|
609
615
|
Ok(()) => Ok(true),
|
|
610
|
-
Err(e) if e.kind() == std::io::ErrorKind::NotFound && required =>
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
path.display()
|
|
614
|
-
)))
|
|
615
|
-
}
|
|
616
|
+
Err(e) if e.kind() == std::io::ErrorKind::NotFound && required => Err(
|
|
617
|
+
LifecycleError::StatePersist(format!("dynamic role file missing: {}", path.display())),
|
|
618
|
+
),
|
|
616
619
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
|
|
617
620
|
Err(e) => Err(LifecycleError::StatePersist(format!(
|
|
618
621
|
"remove role file {}: {e}",
|
|
@@ -660,7 +663,10 @@ fn delete_agent_health(workspace: &Path, agent_id: &AgentId) -> Result<bool, Lif
|
|
|
660
663
|
let conn = crate::db::schema::open_db(store.db_path())
|
|
661
664
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
662
665
|
let changed = conn
|
|
663
|
-
.execute(
|
|
666
|
+
.execute(
|
|
667
|
+
"delete from agent_health where agent_id = ?1",
|
|
668
|
+
[agent_id.as_str()],
|
|
669
|
+
)
|
|
664
670
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
665
671
|
Ok(changed > 0)
|
|
666
672
|
}
|
|
@@ -739,7 +745,11 @@ impl RemoveRollback {
|
|
|
739
745
|
let team_state_text = match std::fs::read_to_string(&team_state_path) {
|
|
740
746
|
Ok(text) => Some(text),
|
|
741
747
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
|
|
742
|
-
Err(e) =>
|
|
748
|
+
Err(e) => {
|
|
749
|
+
return Err(LifecycleError::StatePersist(format!(
|
|
750
|
+
"read team_state: {e}"
|
|
751
|
+
)))
|
|
752
|
+
}
|
|
743
753
|
};
|
|
744
754
|
let dynamic_role_path = dynamic_role_file_path(workspace, state, agent_id);
|
|
745
755
|
let dynamic_role_bytes = match std::fs::read(&dynamic_role_path) {
|