@team-agent/installer 0.5.112 → 0.5.113
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 +100 -44
- package/crates/team-agent/src/cli/emit.rs +127 -98
- package/crates/team-agent/src/cli/leaders.rs +32 -32
- package/crates/team-agent/src/cli/mod.rs +6 -6
- package/crates/team-agent/src/cli/named_address.rs +17 -16
- package/crates/team-agent/src/cli/route.rs +4 -4
- package/crates/team-agent/src/cli/send/presentation.rs +19 -18
- package/crates/team-agent/src/cli/send/resolve.rs +11 -11
- package/crates/team-agent/src/cli/send.rs +4 -4
- package/crates/team-agent/src/cli/spec.rs +29 -29
- package/crates/team-agent/src/cli/status.rs +7 -7
- package/crates/team-agent/src/cli/tests/base.rs +6 -6
- package/crates/team-agent/src/cli/tests/divergence.rs +3 -2
- package/crates/team-agent/src/cli/tests/lane_c.rs +1 -1
- package/crates/team-agent/src/cli/tests/named_address.rs +3 -3
- package/crates/team-agent/src/cli/tests/status_send.rs +7 -7
- package/crates/team-agent/src/cli/triage.rs +53 -2
- package/crates/team-agent/src/cli/types.rs +8 -8
- package/crates/team-agent/src/compiler/tests.rs +12 -0
- package/crates/team-agent/src/compiler.rs +0 -9
- package/crates/team-agent/src/leader/start.rs +5 -0
- package/crates/team-agent/src/leader/tests/identity.rs +21 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +172 -13
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +325 -14
- package/crates/team-agent/src/lifecycle/launch.rs +2 -2
- package/crates/team-agent/src/lifecycle/profile_launch.rs +58 -46
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +97 -52
- package/crates/team-agent/src/lifecycle/restart.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +3 -2
- package/crates/team-agent/src/provider/adapter.rs +8 -59
- package/crates/team-agent/src/provider/adapters/claude.rs +6 -14
- package/crates/team-agent/src/provider/adapters/codex.rs +8 -18
- package/crates/team-agent/src/provider/adapters/copilot.rs +5 -22
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +8 -45
- package/crates/team-agent/src/provider/adapters/grok.rs +7 -31
- package/crates/team-agent/src/provider/adapters/pi.rs +7 -18
- package/crates/team-agent/src/provider/command_helpers.rs +45 -0
- package/crates/team-agent/src/provider/mod.rs +1 -0
- package/crates/team-agent/src/provider/model_catalog.rs +21 -28
- package/crates/team-agent/src/provider/tests/adapter.rs +70 -0
- package/crates/team-agent/src/provider/tests/command_helpers.rs +55 -0
- package/package.json +4 -4
|
@@ -41,6 +41,71 @@ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest}
|
|
|
41
41
|
// `pane_verify_ms`, `startup_prompt_handler_ms`, `tmux_start_mode`) so
|
|
42
42
|
// bounded-concurrency spawn can be justified with real numbers.
|
|
43
43
|
|
|
44
|
+
/// Stable wire labels for launch/restart timing events, not readiness states.
|
|
45
|
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
46
|
+
pub(crate) enum LifecyclePhase {
|
|
47
|
+
ResolveContext,
|
|
48
|
+
CompileSpec,
|
|
49
|
+
PlanClassification,
|
|
50
|
+
Teardown,
|
|
51
|
+
SpawnAll,
|
|
52
|
+
SaveState,
|
|
53
|
+
CoordinatorStart,
|
|
54
|
+
ReadinessWait,
|
|
55
|
+
Completed,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
impl LifecyclePhase {
|
|
59
|
+
pub(crate) fn as_str(self) -> &'static str {
|
|
60
|
+
match self {
|
|
61
|
+
Self::ResolveContext => "resolve_context",
|
|
62
|
+
Self::CompileSpec => "compile_spec",
|
|
63
|
+
Self::PlanClassification => "plan_classification",
|
|
64
|
+
Self::Teardown => "teardown",
|
|
65
|
+
Self::SpawnAll => "spawn_all",
|
|
66
|
+
Self::SaveState => "save_state",
|
|
67
|
+
Self::CoordinatorStart => "coordinator_start",
|
|
68
|
+
Self::ReadinessWait => "readiness_wait",
|
|
69
|
+
Self::Completed => "completed",
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Pure event formatting shared by launch/restart; no clock, I/O or readiness policy.
|
|
74
|
+
pub(crate) fn event_fields(self, elapsed_ms: u64) -> serde_json::Value {
|
|
75
|
+
serde_json::json!({
|
|
76
|
+
"phase": self.as_str(),
|
|
77
|
+
"elapsed_ms": elapsed_ms,
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#[test]
|
|
83
|
+
fn lifecycle_phase_wire_labels_remain_stable() {
|
|
84
|
+
for (phase, wire) in [
|
|
85
|
+
(LifecyclePhase::ResolveContext, "resolve_context"),
|
|
86
|
+
(LifecyclePhase::CompileSpec, "compile_spec"),
|
|
87
|
+
(LifecyclePhase::PlanClassification, "plan_classification"),
|
|
88
|
+
(LifecyclePhase::Teardown, "teardown"),
|
|
89
|
+
(LifecyclePhase::SpawnAll, "spawn_all"),
|
|
90
|
+
(LifecyclePhase::SaveState, "save_state"),
|
|
91
|
+
(LifecyclePhase::CoordinatorStart, "coordinator_start"),
|
|
92
|
+
(LifecyclePhase::ReadinessWait, "readiness_wait"),
|
|
93
|
+
(LifecyclePhase::Completed, "completed"),
|
|
94
|
+
] {
|
|
95
|
+
assert_eq!(phase.as_str(), wire);
|
|
96
|
+
for elapsed_ms in [0, 123, u64::MAX] {
|
|
97
|
+
assert_eq!(phase.event_fields(elapsed_ms), serde_json::json!({
|
|
98
|
+
"phase": wire,
|
|
99
|
+
"elapsed_ms": elapsed_ms,
|
|
100
|
+
}));
|
|
101
|
+
assert_eq!(
|
|
102
|
+
serde_json::to_string(&phase.event_fields(elapsed_ms)).expect("phase JSON"),
|
|
103
|
+
format!("{{\"phase\":\"{wire}\",\"elapsed_ms\":{elapsed_ms}}}"),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
44
109
|
pub(crate) struct RestartPhaseTimer {
|
|
45
110
|
started_at: std::time::Instant,
|
|
46
111
|
}
|
|
@@ -71,15 +136,9 @@ impl RestartPhaseTimer {
|
|
|
71
136
|
/// phase: 阶段名
|
|
72
137
|
/// returns: 写事件失败被吞掉,不影响主流程
|
|
73
138
|
/// ---
|
|
74
|
-
pub(crate) fn emit(&self, workspace: &Path, kind: &'static str, phase:
|
|
139
|
+
pub(crate) fn emit(&self, workspace: &Path, kind: &'static str, phase: LifecyclePhase) {
|
|
75
140
|
let event_log = crate::event_log::EventLog::new(workspace);
|
|
76
|
-
let _ = event_log.write(
|
|
77
|
-
kind,
|
|
78
|
-
serde_json::json!({
|
|
79
|
-
"phase": phase,
|
|
80
|
-
"elapsed_ms": self.elapsed_ms(),
|
|
81
|
-
}),
|
|
82
|
-
);
|
|
141
|
+
let _ = event_log.write(kind, phase.event_fields(self.elapsed_ms()));
|
|
83
142
|
}
|
|
84
143
|
}
|
|
85
144
|
|
|
@@ -305,7 +364,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
305
364
|
// resolved a context; downstream phases are emitted with monotonic
|
|
306
365
|
// `elapsed_ms` for at-a-glance latency triage.
|
|
307
366
|
let phase_timer = RestartPhaseTimer::start();
|
|
308
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
367
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::ResolveContext);
|
|
309
368
|
let lifecycle_lock = acquire_agent_lifecycle_lock(LifecycleLockRequest {
|
|
310
369
|
workspace: &selected.run_workspace,
|
|
311
370
|
operation: "restart",
|
|
@@ -345,7 +404,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
345
404
|
// 写 runtime spec。角色缺(TEAM.md/agents 不在)→ 显式拒(列缺哪些),旧 spec 原地保留不删不用。
|
|
346
405
|
let spec =
|
|
347
406
|
rebuild_runtime_spec_from_roles(&selected.run_workspace, &selected.team_key, &state)?;
|
|
348
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
407
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::CompileSpec);
|
|
349
408
|
// 重建后 spec_workspace 恒为 runtime spec 的父目录(.team/runtime/<team_key>/)。
|
|
350
409
|
let runtime_spec =
|
|
351
410
|
crate::model::paths::runtime_spec_path(&selected.run_workspace, &selected.team_key);
|
|
@@ -441,7 +500,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
441
500
|
phase_timer.emit(
|
|
442
501
|
&selected.run_workspace,
|
|
443
502
|
"restart.phase",
|
|
444
|
-
|
|
503
|
+
LifecyclePhase::PlanClassification,
|
|
445
504
|
);
|
|
446
505
|
write_restart_resume_decision_events(
|
|
447
506
|
&selected.run_workspace,
|
|
@@ -558,8 +617,8 @@ fn restart_with_selected_team_and_transport(
|
|
|
558
617
|
&topology_authority_agent_ids,
|
|
559
618
|
)?;
|
|
560
619
|
}
|
|
561
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
562
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
620
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::Teardown);
|
|
621
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::SpawnAll);
|
|
563
622
|
// 0.5.40 Slice 3 (tmux-server-death-locate §7 Slice 3): when the
|
|
564
623
|
// pre-spawn teardown was DEFERRED (live worker session with
|
|
565
624
|
// authoritative running agents), snapshot the pre-spawn agent rows
|
|
@@ -582,8 +641,8 @@ fn restart_with_selected_team_and_transport(
|
|
|
582
641
|
// tmux session is created deterministically; the remaining independent
|
|
583
642
|
// decisions run their `transport.spawn_into` in parallel via a thread
|
|
584
643
|
// scope with concurrency capped at `min(4, workers-1)`. Every spawn
|
|
585
|
-
// result is collected in-memory; `
|
|
586
|
-
//
|
|
644
|
+
// result is collected in-memory; `mark_agent_respawned` is then
|
|
645
|
+
// applied in ORIGINAL PLAN ORDER so
|
|
587
646
|
// persisted `spawn_epoch` / `spawned_at` / `pane_id` / `window` stay
|
|
588
647
|
// deterministic regardless of thread completion order. Failure
|
|
589
648
|
// aggregation stays equivalent to serial: per-agent errors go into
|
|
@@ -837,17 +896,14 @@ fn restart_with_selected_team_and_transport(
|
|
|
837
896
|
agent.get("dangerously_skip_permissions"),
|
|
838
897
|
Some(serde_json::Value::Bool(true))
|
|
839
898
|
);
|
|
840
|
-
if let Err(error) =
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
launched_bypass,
|
|
849
|
-
)
|
|
850
|
-
})
|
|
899
|
+
if let Err(error) = mark_agent_respawned(
|
|
900
|
+
&mut state,
|
|
901
|
+
&decision.agent_id,
|
|
902
|
+
decision.restart_mode,
|
|
903
|
+
&spawn,
|
|
904
|
+
transport,
|
|
905
|
+
launched_bypass,
|
|
906
|
+
)
|
|
851
907
|
{
|
|
852
908
|
let error = error.to_string();
|
|
853
909
|
mark_agent_restart_failed(&mut state, decision, &error);
|
|
@@ -1026,7 +1082,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
1026
1082
|
&capture_backfill_skip_agent_ids,
|
|
1027
1083
|
&topology_authority_agent_ids,
|
|
1028
1084
|
)?;
|
|
1029
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
1085
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::SaveState);
|
|
1030
1086
|
if fatal_resume_failure {
|
|
1031
1087
|
let attach_commands = Vec::new();
|
|
1032
1088
|
let next_actions = restart_failure_next_actions(&failed_agents);
|
|
@@ -1304,10 +1360,10 @@ fn restart_with_selected_team_and_transport(
|
|
|
1304
1360
|
phase_timer.emit(
|
|
1305
1361
|
&selected.run_workspace,
|
|
1306
1362
|
"restart.phase",
|
|
1307
|
-
|
|
1363
|
+
LifecyclePhase::CoordinatorStart,
|
|
1308
1364
|
);
|
|
1309
1365
|
let coordinator_started = coordinator.ok;
|
|
1310
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
1366
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::ReadinessWait);
|
|
1311
1367
|
wait_restart_readiness_or_timeout(
|
|
1312
1368
|
&selected.run_workspace,
|
|
1313
1369
|
&state,
|
|
@@ -1331,7 +1387,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
1331
1387
|
let mut next_actions = Vec::new();
|
|
1332
1388
|
if !failed_agents.is_empty() {
|
|
1333
1389
|
next_actions.extend(restart_failure_next_actions(&failed_agents));
|
|
1334
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
1390
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::Completed);
|
|
1335
1391
|
write_restart_completed_event(
|
|
1336
1392
|
&selected.run_workspace,
|
|
1337
1393
|
&successful_agents,
|
|
@@ -1360,7 +1416,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
1360
1416
|
leader_bind_reason,
|
|
1361
1417
|
});
|
|
1362
1418
|
}
|
|
1363
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
1419
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::Completed);
|
|
1364
1420
|
write_restart_completed_event(
|
|
1365
1421
|
&selected.run_workspace,
|
|
1366
1422
|
&successful_agents,
|
|
@@ -1852,14 +1908,6 @@ fn yes_no(value: bool) -> &'static str {
|
|
|
1852
1908
|
}
|
|
1853
1909
|
}
|
|
1854
1910
|
|
|
1855
|
-
fn verify_spawned_agent_live(
|
|
1856
|
-
_agent_id: &AgentId,
|
|
1857
|
-
_spawn: &SpawnedAgentWindow,
|
|
1858
|
-
_transport: &dyn crate::transport::Transport,
|
|
1859
|
-
) -> Result<(), LifecycleError> {
|
|
1860
|
-
Ok(())
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
1911
|
/// 0.3.30 Bug 1: restart success path auto-attach.
|
|
1864
1912
|
/// When `TMUX_PANE` is present in the caller's env, treat the restart as if
|
|
1865
1913
|
/// the user had also run `attach-leader` from that pane. Mirrors quick-start's
|
|
@@ -3088,8 +3136,8 @@ fn run_bounded_parallel_worker_spawns(
|
|
|
3088
3136
|
outcomes
|
|
3089
3137
|
}
|
|
3090
3138
|
|
|
3091
|
-
/// 0.5.38 Step 2: serial post-spawn stage. Runs
|
|
3092
|
-
///
|
|
3139
|
+
/// 0.5.38 Step 2: serial post-spawn stage. Runs mark_agent_respawned
|
|
3140
|
+
/// in plan order, emits worker.spawn_timing, and
|
|
3093
3141
|
/// updates the successful/failed agent lists exactly like the pre-0.5.38
|
|
3094
3142
|
/// serial loop. Keeping this serial guarantees deterministic persisted
|
|
3095
3143
|
/// state (`spawn_epoch`, `spawned_at`, etc.) regardless of parallel
|
|
@@ -3111,17 +3159,14 @@ fn apply_marked_respawn(
|
|
|
3111
3159
|
launched_bypass: bool,
|
|
3112
3160
|
) {
|
|
3113
3161
|
let verify_start = std::time::Instant::now();
|
|
3114
|
-
if let Err(error) =
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
launched_bypass,
|
|
3123
|
-
)
|
|
3124
|
-
})
|
|
3162
|
+
if let Err(error) = mark_agent_respawned(
|
|
3163
|
+
state,
|
|
3164
|
+
&decision.agent_id,
|
|
3165
|
+
decision.restart_mode,
|
|
3166
|
+
spawn,
|
|
3167
|
+
transport,
|
|
3168
|
+
launched_bypass,
|
|
3169
|
+
)
|
|
3125
3170
|
{
|
|
3126
3171
|
let error = error.to_string();
|
|
3127
3172
|
mark_agent_restart_failed(state, decision, &error);
|
|
@@ -100,7 +100,7 @@ pub(crate) use rebuild::restart_with_transport_with_session_convergence_deadline
|
|
|
100
100
|
// timer + worker timing writer so `lifecycle::launch::launch_with_transport_in_workspace`
|
|
101
101
|
// can emit the same instrumentation event family with `source="launch"`.
|
|
102
102
|
pub(crate) use rebuild::{
|
|
103
|
-
provider_wire_from_state, write_worker_spawn_timing_event, RestartPhaseTimer,
|
|
103
|
+
provider_wire_from_state, write_worker_spawn_timing_event, LifecyclePhase, RestartPhaseTimer,
|
|
104
104
|
};
|
|
105
105
|
pub use rebuild::{restart, restart_with_session_convergence_deadline};
|
|
106
106
|
pub(crate) use rebuild::{
|
|
@@ -165,14 +165,15 @@ fn purge_agent_help_and_dispatch_are_consistent() {
|
|
|
165
165
|
output_text(&case.run(["purge-agent", "ghost", "--workspace", case.ws(), "--json"]));
|
|
166
166
|
if help.contains("purge-agent") {
|
|
167
167
|
assert!(
|
|
168
|
-
!command.contains("invalid choice") && !command.contains("unknown subcommand")
|
|
168
|
+
!command.contains("invalid choice") && !command.contains("unknown subcommand")
|
|
169
|
+
&& !command.contains("Unknown command:") && !command.contains("Commands:"),
|
|
169
170
|
"RED6: help exposes purge-agent, so dispatch must be real or the command must be removed from help; help={help} command={command}"
|
|
170
171
|
);
|
|
171
172
|
} else {
|
|
172
173
|
assert!(
|
|
173
174
|
command.contains("unknown subcommand")
|
|
174
175
|
|| command.contains("invalid choice")
|
|
175
|
-
|| command.contains("
|
|
176
|
+
|| command.contains("Unknown command:"),
|
|
176
177
|
"RED6: if purge-agent is not implemented, help and dispatch must agree it is absent; help={help} command={command}"
|
|
177
178
|
);
|
|
178
179
|
}
|
|
@@ -300,6 +300,10 @@ pub fn get_adapter(p: Provider) -> Box<dyn ProviderAdapter> {
|
|
|
300
300
|
Box::new(BasicProviderAdapter { provider: p })
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
#[cfg(test)]
|
|
304
|
+
#[path = "tests/command_helpers.rs"]
|
|
305
|
+
mod command_helpers_tests;
|
|
306
|
+
|
|
303
307
|
#[derive(Debug, Clone, Copy)]
|
|
304
308
|
pub(crate) struct BasicProviderAdapter {
|
|
305
309
|
provider: Provider,
|
|
@@ -480,22 +484,13 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
480
484
|
Ok(argv)
|
|
481
485
|
}
|
|
482
486
|
Provider::Grok => Ok(grok_launch_command(
|
|
483
|
-
self,
|
|
484
|
-
auth_mode,
|
|
485
|
-
mcp_config,
|
|
486
487
|
system_prompt,
|
|
487
488
|
model,
|
|
488
489
|
dangerously_skip_permissions,
|
|
489
490
|
)?),
|
|
490
491
|
Provider::CursorAgent => Ok(cursor_agent_base_command(
|
|
491
|
-
self,
|
|
492
|
-
auth_mode,
|
|
493
|
-
mcp_config,
|
|
494
|
-
system_prompt,
|
|
495
492
|
model,
|
|
496
493
|
dangerously_skip_permissions,
|
|
497
|
-
false,
|
|
498
|
-
None,
|
|
499
494
|
)?),
|
|
500
495
|
Provider::Pi => Err(ProviderError::Command(
|
|
501
496
|
"Pi commands require the shared lifecycle materializer".to_string(),
|
|
@@ -596,14 +591,9 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
596
591
|
// never written and restart could not resume.
|
|
597
592
|
let expected = next_session_token();
|
|
598
593
|
let mut argv = grok_base_command(
|
|
599
|
-
self,
|
|
600
|
-
ctx.auth_mode,
|
|
601
|
-
ctx.mcp_config,
|
|
602
594
|
ctx.system_prompt,
|
|
603
595
|
ctx.model,
|
|
604
596
|
ctx.dangerously_skip_permissions,
|
|
605
|
-
ctx.profile_launch
|
|
606
|
-
.is_some_and(|profile| profile.managed_mcp_config),
|
|
607
597
|
ctx.effort,
|
|
608
598
|
)?;
|
|
609
599
|
argv.push("--session-id".to_string());
|
|
@@ -637,15 +627,8 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
637
627
|
// U-01 create-chat 无登录超时;禁止发明 --session-id。
|
|
638
628
|
// pending 只能是 CLI chatId,本拍不造假 uuid。
|
|
639
629
|
let argv = cursor_agent_base_command(
|
|
640
|
-
self,
|
|
641
|
-
ctx.auth_mode,
|
|
642
|
-
ctx.mcp_config,
|
|
643
|
-
ctx.system_prompt,
|
|
644
630
|
ctx.model,
|
|
645
631
|
ctx.dangerously_skip_permissions,
|
|
646
|
-
ctx.profile_launch
|
|
647
|
-
.is_some_and(|profile| profile.managed_mcp_config),
|
|
648
|
-
ctx.effort,
|
|
649
632
|
)?;
|
|
650
633
|
Ok(CommandPlan {
|
|
651
634
|
argv,
|
|
@@ -816,7 +799,7 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
816
799
|
// copilot --resume 接受 id|name)。
|
|
817
800
|
Provider::Copilot => {
|
|
818
801
|
let mut argv =
|
|
819
|
-
|
|
802
|
+
copilot_base_command(auth_mode, mcp_config, system_prompt, model, dangerously_skip_permissions);
|
|
820
803
|
argv.push("--resume".to_string());
|
|
821
804
|
argv.push(session_id.as_str().to_string());
|
|
822
805
|
Ok(argv)
|
|
@@ -824,13 +807,9 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
824
807
|
// Grok resume: base + `--resume <id>` (grok -r/--resume 接受 id|title)。
|
|
825
808
|
Provider::Grok => {
|
|
826
809
|
let mut argv = grok_base_command(
|
|
827
|
-
self,
|
|
828
|
-
auth_mode,
|
|
829
|
-
mcp_config,
|
|
830
810
|
system_prompt,
|
|
831
811
|
model,
|
|
832
812
|
dangerously_skip_permissions,
|
|
833
|
-
false,
|
|
834
813
|
None,
|
|
835
814
|
)?;
|
|
836
815
|
argv.push("--resume".to_string());
|
|
@@ -839,16 +818,7 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
839
818
|
}
|
|
840
819
|
// Cursor resume: base + `--resume <chatId>`。
|
|
841
820
|
Provider::CursorAgent => {
|
|
842
|
-
let mut argv = cursor_agent_base_command(
|
|
843
|
-
self,
|
|
844
|
-
auth_mode,
|
|
845
|
-
mcp_config,
|
|
846
|
-
system_prompt,
|
|
847
|
-
model,
|
|
848
|
-
dangerously_skip_permissions,
|
|
849
|
-
false,
|
|
850
|
-
None,
|
|
851
|
-
)?;
|
|
821
|
+
let mut argv = cursor_agent_base_command(model, dangerously_skip_permissions)?;
|
|
852
822
|
// Put --resume immediately after `agent` so the CLI treats it as
|
|
853
823
|
// the session mode rather than a trailing flag after --workspace.
|
|
854
824
|
argv.insert(1, "--resume".to_string());
|
|
@@ -941,14 +911,9 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
941
911
|
));
|
|
942
912
|
};
|
|
943
913
|
let mut argv = grok_base_command(
|
|
944
|
-
self,
|
|
945
|
-
ctx.auth_mode,
|
|
946
|
-
ctx.mcp_config,
|
|
947
914
|
ctx.system_prompt,
|
|
948
915
|
ctx.model,
|
|
949
916
|
ctx.dangerously_skip_permissions,
|
|
950
|
-
ctx.profile_launch
|
|
951
|
-
.is_some_and(|profile| profile.managed_mcp_config),
|
|
952
917
|
ctx.effort,
|
|
953
918
|
)?;
|
|
954
919
|
argv.push("--resume".to_string());
|
|
@@ -968,15 +933,8 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
968
933
|
));
|
|
969
934
|
}
|
|
970
935
|
let mut argv = cursor_agent_base_command(
|
|
971
|
-
self,
|
|
972
|
-
ctx.auth_mode,
|
|
973
|
-
ctx.mcp_config,
|
|
974
|
-
ctx.system_prompt,
|
|
975
936
|
ctx.model,
|
|
976
937
|
ctx.dangerously_skip_permissions,
|
|
977
|
-
ctx.profile_launch
|
|
978
|
-
.is_some_and(|profile| profile.managed_mcp_config),
|
|
979
|
-
ctx.effort,
|
|
980
938
|
)?;
|
|
981
939
|
argv.push("--resume".to_string());
|
|
982
940
|
argv.push(raw.to_string());
|
|
@@ -1067,13 +1025,9 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
1067
1025
|
// (grok help: --fork-session 需配合 --resume/--continue)。
|
|
1068
1026
|
Provider::Grok => {
|
|
1069
1027
|
let mut argv = grok_base_command(
|
|
1070
|
-
self,
|
|
1071
|
-
auth_mode,
|
|
1072
|
-
mcp_config,
|
|
1073
1028
|
system_prompt,
|
|
1074
1029
|
model,
|
|
1075
1030
|
dangerously_skip_permissions,
|
|
1076
|
-
false,
|
|
1077
1031
|
None,
|
|
1078
1032
|
)?;
|
|
1079
1033
|
argv.push("--session-id".to_string());
|
|
@@ -1191,7 +1145,7 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
1191
1145
|
.ok_or_else(|| {
|
|
1192
1146
|
ProviderError::Command("copilot fork root was not materialized".to_string())
|
|
1193
1147
|
})?;
|
|
1194
|
-
let mut argv =
|
|
1148
|
+
let mut argv = copilot_base_command(
|
|
1195
1149
|
ctx.auth_mode,
|
|
1196
1150
|
ctx.mcp_config,
|
|
1197
1151
|
ctx.system_prompt,
|
|
@@ -1214,14 +1168,9 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
1214
1168
|
));
|
|
1215
1169
|
};
|
|
1216
1170
|
let mut argv = grok_base_command(
|
|
1217
|
-
self,
|
|
1218
|
-
ctx.auth_mode,
|
|
1219
|
-
ctx.mcp_config,
|
|
1220
1171
|
ctx.system_prompt,
|
|
1221
1172
|
ctx.model,
|
|
1222
1173
|
ctx.dangerously_skip_permissions,
|
|
1223
|
-
ctx.profile_launch
|
|
1224
|
-
.is_some_and(|profile| profile.managed_mcp_config),
|
|
1225
1174
|
ctx.effort,
|
|
1226
1175
|
)?;
|
|
1227
1176
|
argv.push("--session-id".to_string());
|
|
@@ -1405,7 +1354,7 @@ pub(crate) fn json_inline(value: &serde_json::Value) -> String {
|
|
|
1405
1354
|
// the extracted base-command functions, not directly by this file.
|
|
1406
1355
|
use super::adapters::claude::{claude_base_command, claude_launch_command};
|
|
1407
1356
|
use super::adapters::codex::codex_base_command;
|
|
1408
|
-
use super::adapters::copilot::
|
|
1357
|
+
use super::adapters::copilot::copilot_base_command;
|
|
1409
1358
|
use super::adapters::cursor_agent::cursor_agent_base_command;
|
|
1410
1359
|
use super::adapters::fake::fake_worker_command;
|
|
1411
1360
|
use super::adapters::grok::{grok_base_command, grok_launch_command};
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
use crate::model::enums::{AuthMode, Provider};
|
|
12
12
|
use crate::provider::adapter::{next_session_token, prompt_needs_native_mcp, BasicProviderAdapter};
|
|
13
13
|
use crate::provider::{McpConfig, ProviderAdapter, ProviderError};
|
|
14
|
+
use crate::provider::command_helpers::{append_opt_pair, append_pair};
|
|
14
15
|
|
|
15
16
|
pub(crate) fn claude_launch_command(
|
|
16
17
|
adapter: &BasicProviderAdapter,
|
|
@@ -30,8 +31,7 @@ pub(crate) fn claude_launch_command(
|
|
|
30
31
|
false,
|
|
31
32
|
None,
|
|
32
33
|
)?;
|
|
33
|
-
argv
|
|
34
|
-
argv.push(next_session_token());
|
|
34
|
+
append_pair(&mut argv, "--session-id", next_session_token());
|
|
35
35
|
Ok(argv)
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -54,21 +54,14 @@ pub(crate) fn claude_base_command(
|
|
|
54
54
|
.expect("claude provider must define a bypass flag");
|
|
55
55
|
argv.push(flag.to_string());
|
|
56
56
|
} else {
|
|
57
|
-
argv
|
|
58
|
-
argv.push("default".to_string());
|
|
59
|
-
}
|
|
60
|
-
if let Some(model) = model {
|
|
61
|
-
argv.push("--model".to_string());
|
|
62
|
-
argv.push(model.to_string());
|
|
57
|
+
append_pair(&mut argv, "--permission-mode", "default");
|
|
63
58
|
}
|
|
59
|
+
append_opt_pair(&mut argv, "--model", model);
|
|
64
60
|
if let Some(effort) = effort {
|
|
65
61
|
argv.push("--effort".to_string());
|
|
66
62
|
argv.push(effort.as_str().to_string());
|
|
67
63
|
}
|
|
68
|
-
|
|
69
|
-
argv.push("--append-system-prompt".to_string());
|
|
70
|
-
argv.push(prompt.to_string());
|
|
71
|
-
}
|
|
64
|
+
append_opt_pair(&mut argv, "--append-system-prompt", system_prompt);
|
|
72
65
|
if !managed_mcp_config
|
|
73
66
|
&& (mcp_config.is_some()
|
|
74
67
|
|| auth_mode == AuthMode::CompatibleApi
|
|
@@ -79,8 +72,7 @@ pub(crate) fn claude_base_command(
|
|
|
79
72
|
} else {
|
|
80
73
|
serde_json::json!({"mcpServers": adapter.mcp_config(auth_mode)?.raw})
|
|
81
74
|
};
|
|
82
|
-
argv
|
|
83
|
-
argv.push(raw.to_string());
|
|
75
|
+
append_pair(&mut argv, "--mcp-config", raw.to_string());
|
|
84
76
|
}
|
|
85
77
|
Ok(argv)
|
|
86
78
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
use crate::model::enums::AuthMode;
|
|
10
10
|
use crate::provider::adapter::json_inline;
|
|
11
|
+
use crate::provider::command_helpers::{append_opt_pair, append_pair};
|
|
11
12
|
use crate::provider::{McpConfig, ProviderCommandOverrides};
|
|
12
13
|
|
|
13
14
|
pub(crate) fn codex_base_command(
|
|
@@ -36,24 +37,17 @@ pub(crate) fn codex_base_command(
|
|
|
36
37
|
"--disable".to_string(),
|
|
37
38
|
"apps".to_string(),
|
|
38
39
|
]);
|
|
39
|
-
|
|
40
|
-
argv.push("--profile".to_string());
|
|
41
|
-
argv.push(profile.to_string());
|
|
42
|
-
}
|
|
40
|
+
append_opt_pair(&mut argv, "--profile", overrides.and_then(|o| o.codex_profile.as_deref()));
|
|
43
41
|
if dangerously_skip_permissions {
|
|
44
42
|
// 0.5.66 bypass 单源:flag 由 provider_bypass_flag 表供给。
|
|
45
43
|
let flag = crate::provider::bypass_flags::provider_bypass_flag(crate::model::enums::Provider::Codex)
|
|
46
44
|
.expect("codex provider must define a bypass flag");
|
|
47
45
|
argv.push(flag.to_string());
|
|
48
46
|
}
|
|
49
|
-
|
|
50
|
-
argv.push("--model".to_string());
|
|
51
|
-
argv.push(model.to_string());
|
|
52
|
-
}
|
|
47
|
+
append_opt_pair(&mut argv, "--model", model);
|
|
53
48
|
if let Some(overrides) = overrides {
|
|
54
49
|
for config in &overrides.codex_config {
|
|
55
|
-
argv
|
|
56
|
-
argv.push(config.clone());
|
|
50
|
+
append_pair(&mut argv, "-c", config.clone());
|
|
57
51
|
}
|
|
58
52
|
}
|
|
59
53
|
if let Some(effort) = effort {
|
|
@@ -66,8 +60,7 @@ pub(crate) fn codex_base_command(
|
|
|
66
60
|
.replace('\\', "\\\\")
|
|
67
61
|
.replace('"', "\\\"")
|
|
68
62
|
.replace('\n', "\\n");
|
|
69
|
-
argv
|
|
70
|
-
argv.push(format!("developer_instructions=\"{escaped}\""));
|
|
63
|
+
append_pair(&mut argv, "-c", format!("developer_instructions=\"{escaped}\""));
|
|
71
64
|
}
|
|
72
65
|
if let Some(config) = mcp_config {
|
|
73
66
|
append_codex_mcp_overrides(&mut argv, &config.raw);
|
|
@@ -91,8 +84,7 @@ pub(crate) fn append_codex_mcp_overrides(argv: &mut Vec<String>, raw: &serde_jso
|
|
|
91
84
|
if key == "env" {
|
|
92
85
|
if let Some(env) = value.as_object() {
|
|
93
86
|
for (env_key, env_value) in env {
|
|
94
|
-
argv
|
|
95
|
-
argv.push(format!(
|
|
87
|
+
append_pair(argv, "-c", format!(
|
|
96
88
|
"mcp_servers.{name}.env.{env_key}={}",
|
|
97
89
|
json_inline(env_value)
|
|
98
90
|
));
|
|
@@ -100,12 +92,10 @@ pub(crate) fn append_codex_mcp_overrides(argv: &mut Vec<String>, raw: &serde_jso
|
|
|
100
92
|
}
|
|
101
93
|
continue;
|
|
102
94
|
}
|
|
103
|
-
argv
|
|
104
|
-
argv.push(format!("mcp_servers.{name}.{key}={}", json_inline(value)));
|
|
95
|
+
append_pair(argv, "-c", format!("mcp_servers.{name}.{key}={}", json_inline(value)));
|
|
105
96
|
}
|
|
106
97
|
// Every MCP server gets a 600s tool timeout so long-running
|
|
107
98
|
// team_orchestrator calls (report_result etc.) survive the codex default.
|
|
108
|
-
argv
|
|
109
|
-
argv.push(format!("mcp_servers.{name}.tool_timeout_sec=600.0"));
|
|
99
|
+
append_pair(argv, "-c", format!("mcp_servers.{name}.tool_timeout_sec=600.0"));
|
|
110
100
|
}
|
|
111
101
|
}
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
//!
|
|
4
4
|
//! Extracted from `provider/adapter.rs` (0.4.x decoupling step 2). Pure
|
|
5
5
|
//! extraction — byte-identical to the original inline forms. Scope kept
|
|
6
|
-
//! small: base command +
|
|
6
|
+
//! small: base command + MCP type→transport
|
|
7
7
|
//! translation. Auth hint (`copilot_auth_hint`) stays in `adapter.rs`;
|
|
8
8
|
//! session-store scanning lives under `provider/session_scan/copilot.rs`.
|
|
9
9
|
|
|
10
10
|
use crate::model::enums::AuthMode;
|
|
11
11
|
use crate::provider::McpConfig;
|
|
12
|
+
use crate::provider::command_helpers::{append_opt_pair, append_pair};
|
|
12
13
|
|
|
13
14
|
pub(crate) fn copilot_base_command(
|
|
14
15
|
auth_mode: AuthMode,
|
|
@@ -35,18 +36,13 @@ pub(crate) fn copilot_base_command(
|
|
|
35
36
|
argv.push(flag.to_string());
|
|
36
37
|
}
|
|
37
38
|
// mcp_team ∈ canonical → approval-free (whole-server pattern).
|
|
38
|
-
argv
|
|
39
|
-
argv
|
|
40
|
-
if let Some(model) = model {
|
|
41
|
-
argv.push("--model".to_string());
|
|
42
|
-
argv.push(model.to_string());
|
|
43
|
-
}
|
|
39
|
+
append_pair(&mut argv, "--allow-tool", "team_orchestrator");
|
|
40
|
+
append_opt_pair(&mut argv, "--model", model);
|
|
44
41
|
if let Some(config) = mcp_config {
|
|
45
42
|
// Copilot mcp config schema field is `transport` (stdio|http|sse),
|
|
46
43
|
// not the canonical `type`. McpConfig.raw is canonical; only copilot
|
|
47
44
|
// translates type→transport for --additional-mcp-config.
|
|
48
|
-
argv
|
|
49
|
-
argv.push(copilot_translate_mcp_config(&config.raw).to_string());
|
|
45
|
+
append_pair(&mut argv, "--additional-mcp-config", copilot_translate_mcp_config(&config.raw).to_string());
|
|
50
46
|
}
|
|
51
47
|
argv
|
|
52
48
|
}
|
|
@@ -77,16 +73,3 @@ pub(crate) fn copilot_translate_mcp_config(raw: &serde_json::Value) -> serde_jso
|
|
|
77
73
|
}
|
|
78
74
|
serde_json::Value::Object(translated)
|
|
79
75
|
}
|
|
80
|
-
|
|
81
|
-
/// Resume path = base + `--resume <sid>` (drop --session-id); kept separate
|
|
82
|
-
/// to avoid the plan accidentally emitting --session-id and --resume in the
|
|
83
|
-
/// same frame.
|
|
84
|
-
pub(crate) fn copilot_base_command_resume(
|
|
85
|
-
auth_mode: AuthMode,
|
|
86
|
-
mcp_config: Option<&McpConfig>,
|
|
87
|
-
system_prompt: Option<&str>,
|
|
88
|
-
model: Option<&str>,
|
|
89
|
-
dangerously_skip_permissions: bool,
|
|
90
|
-
) -> Vec<String> {
|
|
91
|
-
copilot_base_command(auth_mode, mcp_config, system_prompt, model, dangerously_skip_permissions)
|
|
92
|
-
}
|