@team-agent/installer 0.5.66 → 0.5.69
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 +8 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +1 -0
- package/crates/team-agent/src/cli/adapters.rs +5 -0
- package/crates/team-agent/src/cli/diagnose.rs +156 -0
- package/crates/team-agent/src/cli/emit.rs +6 -5
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +22 -7
- package/crates/team-agent/src/cli/leaders.rs +125 -1
- package/crates/team-agent/src/cli/mod.rs +20 -0
- package/crates/team-agent/src/cli/send/presentation.rs +7 -1
- package/crates/team-agent/src/cli/spec.rs +3 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
- package/crates/team-agent/src/cli/tests/base.rs +26 -3
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
- package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
- package/crates/team-agent/src/communication_mode/mod.rs +6 -4
- package/crates/team-agent/src/compiler.rs +121 -5
- package/crates/team-agent/src/coordinator/backoff.rs +55 -0
- package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
- package/crates/team-agent/src/coordinator/health.rs +173 -0
- package/crates/team-agent/src/coordinator/mod.rs +31 -0
- package/crates/team-agent/src/coordinator/orphan.rs +31 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +31 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
- package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
- package/crates/team-agent/src/coordinator/tick.rs +133 -22
- package/crates/team-agent/src/coordinator/types.rs +49 -0
- package/crates/team-agent/src/db/message_store.rs +39 -5
- package/crates/team-agent/src/layout/worker_env.rs +20 -3
- package/crates/team-agent/src/layout/worker_window_helpers.rs +3 -0
- package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
- package/crates/team-agent/src/leader/registry.rs +65 -0
- package/crates/team-agent/src/leader/start.rs +973 -75
- package/crates/team-agent/src/lifecycle/display.rs +56 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +398 -26
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +80 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +60 -0
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
- package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +548 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +120 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +208 -456
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
- package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +482 -1
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +830 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +87 -3
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +127 -25
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +140 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
- package/crates/team-agent/src/lifecycle/launch.rs +107 -29
- package/crates/team-agent/src/lifecycle/lock.rs +42 -1
- package/crates/team-agent/src/lifecycle/mod.rs +11 -0
- package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +99 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +52 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +214 -160
- package/crates/team-agent/src/lifecycle/restart/common.rs +513 -37
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +233 -6
- package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
- package/crates/team-agent/src/lifecycle/restart.rs +29 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +24 -3
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +464 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +247 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_g0.stdout.txt +222 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +787 -0
- package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +485 -0
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
- package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
- package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +97 -0
- package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +128 -0
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +207 -0
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +838 -0
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +320 -0
- package/crates/team-agent/src/lifecycle/tests/pi_lifecycle_doctor_red.rs +98 -0
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +216 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
- package/crates/team-agent/src/lifecycle/tests.rs +30 -13
- package/crates/team-agent/src/lifecycle/types.rs +61 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +143 -12
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
- package/crates/team-agent/src/mcp_server/tests.rs +196 -2
- package/crates/team-agent/src/messaging/delivery.rs +430 -41
- package/crates/team-agent/src/messaging/helpers.rs +2 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
- package/crates/team-agent/src/messaging/results.rs +4 -0
- package/crates/team-agent/src/messaging/send.rs +34 -0
- package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
- package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/types.rs +4 -1
- package/crates/team-agent/src/model/enums.rs +17 -6
- package/crates/team-agent/src/model/permissions.rs +3 -0
- package/crates/team-agent/src/os_probe.rs +73 -2
- package/crates/team-agent/src/provider/adapter.rs +259 -6
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +89 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +3 -0
- package/crates/team-agent/src/provider/adapters/pi.rs +152 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +47 -2
- package/crates/team-agent/src/provider/classify.rs +4 -2
- package/crates/team-agent/src/provider/faults.rs +2 -1
- package/crates/team-agent/src/provider/mod.rs +11 -0
- package/crates/team-agent/src/provider/session/capture.rs +158 -20
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
- package/crates/team-agent/src/provider/session/context_fork.rs +78 -3
- package/crates/team-agent/src/provider/session/mod.rs +18 -0
- package/crates/team-agent/src/provider/session/resume.rs +57 -0
- package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
- package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
- package/crates/team-agent/src/provider/session_scan/common.rs +205 -2
- package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
- package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
- package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
- package/crates/team-agent/src/provider/session_scan/pi.rs +172 -0
- package/crates/team-agent/src/provider/session_scan.rs +12 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +384 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +29 -1
- package/crates/team-agent/src/state/persist.rs +301 -8
- package/crates/team-agent/src/state/repository.rs +5 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
- package/crates/team-agent/src/tmux_backend.rs +1116 -75
- package/crates/team-agent/src/transport/tests/wire.rs +28 -2
- package/crates/team-agent/src/transport.rs +248 -1
- package/npm/install.mjs +129 -73
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +32 -239
- package/skills/team-agent/command-coverage.json +31 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
- package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 冷启、add/clone/fork 入口拼装
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: fork_agent
|
|
6
|
+
//! what: 成员分身入口,grok 走窗口内命令
|
|
7
|
+
//! boundary:
|
|
8
|
+
//! - 不读 provider 会话落盘
|
|
9
|
+
//! maturity: wired
|
|
10
|
+
//! ---
|
|
1
11
|
//!
|
|
2
12
|
//! lifecycle::launch —— 冷启 / quick-start / 危险审批探测 + add/fork / plan 起步与推进。
|
|
3
13
|
|
|
@@ -18,6 +28,16 @@ use super::*;
|
|
|
18
28
|
|
|
19
29
|
// ── lifecycle::launch —— 冷启 / quick-start / 危险审批探测 ──────────────────
|
|
20
30
|
|
|
31
|
+
/// ---
|
|
32
|
+
/// purpose: 冷启全队的默认入口,按 spec 所在目录绑定 per-team tmux socket 后转 launch_with_transport
|
|
33
|
+
/// params:
|
|
34
|
+
/// spec_path: 已编译 spec 的路径,其父目录即 team 目录
|
|
35
|
+
/// dry_run: 只出路由与权限,不 spawn 也不落盘
|
|
36
|
+
/// auto_approve: 当前为 no-op
|
|
37
|
+
/// skip_profile_smoke: 当前为 no-op
|
|
38
|
+
/// returns: LaunchReport
|
|
39
|
+
/// errors: 透传 launch_with_transport 的错误
|
|
40
|
+
/// ---
|
|
21
41
|
///
|
|
22
42
|
/// `launch(spec_path, dry_run, auto_approve, skip_profile_smoke)`(`launch/core.py:29`)。
|
|
23
43
|
/// 冷启全队:路由 tasks、resolve 权限/危险审批门、session 冲突检查(冲突 →
|
|
@@ -42,6 +62,13 @@ pub fn launch(
|
|
|
42
62
|
)
|
|
43
63
|
}
|
|
44
64
|
|
|
65
|
+
/// ---
|
|
66
|
+
/// purpose: 由 spec 路径推出 workspace,再转 launch_with_transport_in_workspace
|
|
67
|
+
/// params:
|
|
68
|
+
/// transport: 已绑定 team socket 的 transport
|
|
69
|
+
/// returns: LaunchReport
|
|
70
|
+
/// errors: 透传 launch_with_transport_in_workspace 的错误
|
|
71
|
+
/// ---
|
|
45
72
|
pub(crate) fn launch_with_transport(
|
|
46
73
|
spec_path: &Path,
|
|
47
74
|
dry_run: bool,
|
|
@@ -61,6 +88,17 @@ pub(crate) fn launch_with_transport(
|
|
|
61
88
|
)
|
|
62
89
|
}
|
|
63
90
|
|
|
91
|
+
/// ---
|
|
92
|
+
/// purpose: 冷启全队的实体实现,编译 spec、检查 session 冲突、按序 spawn、落 state、出报告
|
|
93
|
+
/// params:
|
|
94
|
+
/// workspace: run workspace 根,事件与 state 都落在这里
|
|
95
|
+
/// spec_path: 已编译 spec 的路径
|
|
96
|
+
/// dry_run: 为真时不 spawn 不落盘,started 为空
|
|
97
|
+
/// auto_approve: 未使用
|
|
98
|
+
/// skip_profile_smoke: 未使用
|
|
99
|
+
/// returns: LaunchReport,其中 leader_receiver_attached 恒 false、session_capture_incomplete_agents 恒空
|
|
100
|
+
/// errors: spec 不存在或解析失败返回 Compile,tmux session 已存在返回 SessionConflict,spawn 与落 state 的错误透传
|
|
101
|
+
/// ---
|
|
64
102
|
pub fn launch_with_transport_in_workspace(
|
|
65
103
|
workspace: &Path,
|
|
66
104
|
spec_path: &Path,
|
|
@@ -71,8 +109,8 @@ pub fn launch_with_transport_in_workspace(
|
|
|
71
109
|
) -> Result<LaunchReport, LifecycleError> {
|
|
72
110
|
let _ = skip_profile_smoke;
|
|
73
111
|
let _ = auto_approve; // 0.5.66: bypass 单源后无 runtime.dangerous_auto_approve,`--yes` 为 no-op。
|
|
74
|
-
|
|
75
|
-
|
|
112
|
+
// 0.5.38 (`.team/artifacts/startup-latency-locate.md` §5): launch.phase
|
|
113
|
+
// timer with monotonic `elapsed_ms` for latency triage.
|
|
76
114
|
let phase_timer = crate::lifecycle::restart::RestartPhaseTimer::start();
|
|
77
115
|
if !spec_path.exists() {
|
|
78
116
|
return Err(LifecycleError::Compile(format!(
|
|
@@ -107,13 +145,7 @@ pub fn launch_with_transport_in_workspace(
|
|
|
107
145
|
Vec::new()
|
|
108
146
|
} else {
|
|
109
147
|
phase_timer.emit(workspace, "launch.phase", "spawn_all");
|
|
110
|
-
let started = spawn_agents(
|
|
111
|
-
workspace,
|
|
112
|
-
spec_path,
|
|
113
|
-
&spec,
|
|
114
|
-
&session_name,
|
|
115
|
-
transport,
|
|
116
|
-
)?;
|
|
148
|
+
let started = spawn_agents(workspace, spec_path, &spec, &session_name, transport)?;
|
|
117
149
|
persist_spawn_agent_state(
|
|
118
150
|
workspace,
|
|
119
151
|
spec_path,
|
|
@@ -224,8 +256,8 @@ mod plan;
|
|
|
224
256
|
pub(crate) use plan::{handle_report_result, start_plan};
|
|
225
257
|
|
|
226
258
|
pub mod spawn;
|
|
227
|
-
pub(super) use spawn::*;
|
|
228
259
|
pub(crate) use spawn::agent_id_spec_source_path;
|
|
260
|
+
pub(super) use spawn::*;
|
|
229
261
|
|
|
230
262
|
mod layout;
|
|
231
263
|
pub(super) use layout::*;
|
|
@@ -260,20 +292,46 @@ pub(crate) use agent_state::{
|
|
|
260
292
|
persist_effective_approval_policy_from_agent,
|
|
261
293
|
};
|
|
262
294
|
|
|
295
|
+
mod grok_per_seat;
|
|
296
|
+
pub(crate) use grok_per_seat::{
|
|
297
|
+
is_per_seat_env_key, non_per_seat_env_in_tables, per_seat_keys_in_toml,
|
|
298
|
+
strip_per_seat_keys_from_toml,
|
|
299
|
+
};
|
|
300
|
+
|
|
263
301
|
mod mcp_config;
|
|
302
|
+
pub use mcp_config::apply_grok_mcp_overlay;
|
|
264
303
|
pub(super) use mcp_config::*;
|
|
265
304
|
pub(crate) use mcp_config::{
|
|
266
|
-
|
|
305
|
+
ensure_grok_login_and_folder_trust, grok_shared_cwd_error, point_native_mcp_config_at_file,
|
|
306
|
+
reconcile_grok_toml_per_seat_keys, resolve_mcp_config, write_worker_mcp_config,
|
|
267
307
|
write_worker_mcp_config_for_provider,
|
|
268
308
|
};
|
|
269
309
|
|
|
310
|
+
pub(crate) mod pi_mcp;
|
|
311
|
+
|
|
312
|
+
mod cursor_mcp_iso;
|
|
313
|
+
pub use cursor_mcp_iso::{
|
|
314
|
+
cursor_mcp_isolation_enabled, cursor_mcp_json_path, cursor_mcp_project_dir,
|
|
315
|
+
materialize_cursor_mcp_project,
|
|
316
|
+
};
|
|
317
|
+
mod cursor_mcp;
|
|
318
|
+
pub use cursor_mcp::{
|
|
319
|
+
apply_cursor_mcp_overlay, apply_cursor_spawn_workspace_pointers,
|
|
320
|
+
apply_cursor_workspace_physical_path, cursor_mcp_enable_argv, enable_cursor_workspace_mcp,
|
|
321
|
+
physical_workspace_path, refuse_second_cursor_occupant,
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
mod cursor_create_chat;
|
|
325
|
+
|
|
270
326
|
mod worker_env;
|
|
271
327
|
pub(super) use worker_env::*;
|
|
272
328
|
pub(crate) use worker_env::{
|
|
273
|
-
apply_copilot_instructions_overlay,
|
|
329
|
+
apply_copilot_instructions_overlay, apply_cursor_agent_rules_overlay,
|
|
330
|
+
apply_mcp_auto_approval_env, apply_profile_launch_env, auth_mode_env_value,
|
|
274
331
|
fill_spawn_placeholders, fill_spawn_placeholders_full, inherited_env_with_team_overrides,
|
|
275
332
|
persist_command_plan_state, spawn_timestamp,
|
|
276
333
|
};
|
|
334
|
+
pub use worker_env::{apply_cursor_subscription_proxy_env, CursorProxyPresence};
|
|
277
335
|
|
|
278
336
|
mod identity;
|
|
279
337
|
pub(super) use identity::*;
|
|
@@ -300,7 +358,7 @@ pub(crate) use quick_start_transport::{
|
|
|
300
358
|
};
|
|
301
359
|
|
|
302
360
|
pub mod readiness;
|
|
303
|
-
pub
|
|
361
|
+
pub use readiness::launched_team_receiver_is_attached;
|
|
304
362
|
pub(super) use readiness::*;
|
|
305
363
|
|
|
306
364
|
mod add_agent;
|
|
@@ -313,19 +371,12 @@ pub(crate) use add_agent_state::inject_agent_into_spec;
|
|
|
313
371
|
pub(super) use add_agent_state::*;
|
|
314
372
|
|
|
315
373
|
mod fork_agent;
|
|
316
|
-
pub use fork_agent::fork_agent_with_transport;
|
|
317
374
|
pub(super) use fork_agent::*;
|
|
375
|
+
pub use fork_agent::{fork_agent_with_transport, in_window_fork, in_window_fork_command};
|
|
318
376
|
|
|
319
377
|
mod fork_entry;
|
|
320
378
|
pub use fork_entry::fork_agent;
|
|
321
379
|
|
|
322
|
-
mod fork_state;
|
|
323
|
-
pub(super) use fork_state::*;
|
|
324
|
-
|
|
325
|
-
mod fork_finalize;
|
|
326
|
-
pub(super) use fork_finalize::*;
|
|
327
|
-
pub(crate) use fork_finalize::{finalize_pending_fork_capture, ContextForkFinalized};
|
|
328
|
-
|
|
329
380
|
mod role_source;
|
|
330
381
|
pub(super) use role_source::*;
|
|
331
382
|
|
|
@@ -337,9 +388,7 @@ pub(super) use ownership::*;
|
|
|
337
388
|
pub(crate) use ownership::{ensure_owner_allowed, ensure_owner_allowed_for_state, state_path};
|
|
338
389
|
|
|
339
390
|
pub mod spec_state;
|
|
340
|
-
pub
|
|
341
|
-
override_spec_session_name, override_spec_workspace, spec_agent_id_set, write_spec_atomic,
|
|
342
|
-
};
|
|
391
|
+
pub use spec_state::worker_session_name_pub;
|
|
343
392
|
pub(crate) use spec_state::{
|
|
344
393
|
effective_runtime_config_for_worker_spawn, effective_runtime_config_for_worker_spawn_json,
|
|
345
394
|
};
|
|
@@ -350,7 +399,9 @@ use spec_state::{
|
|
|
350
399
|
spec_agent_values, spec_agents, spec_default_assignee, spec_routes, spec_session_name,
|
|
351
400
|
spec_tasks_json, team_workspace, yaml_value_to_json,
|
|
352
401
|
};
|
|
353
|
-
pub use spec_state::
|
|
402
|
+
pub(crate) use spec_state::{
|
|
403
|
+
override_spec_session_name, override_spec_workspace, spec_agent_id_set, write_spec_atomic,
|
|
404
|
+
};
|
|
354
405
|
|
|
355
406
|
#[cfg(test)]
|
|
356
407
|
mod tests {
|
|
@@ -361,10 +412,8 @@ mod tests {
|
|
|
361
412
|
// leader argv 带 bypass flag + 无角色声明 true → 写 warning 事件。
|
|
362
413
|
#[test]
|
|
363
414
|
fn test_leader_flag_present_but_no_role_true_writes_warning_event() {
|
|
364
|
-
let ws =
|
|
365
|
-
"ta-bypass-warning-test-{}",
|
|
366
|
-
std::process::id()
|
|
367
|
-
));
|
|
415
|
+
let ws =
|
|
416
|
+
std::env::temp_dir().join(format!("ta-bypass-warning-test-{}", std::process::id()));
|
|
368
417
|
let _ = std::fs::remove_dir_all(&ws);
|
|
369
418
|
std::fs::create_dir_all(&ws).unwrap();
|
|
370
419
|
let spec = yaml::loads(
|
|
@@ -415,4 +464,33 @@ mod tests {
|
|
|
415
464
|
);
|
|
416
465
|
let _ = std::fs::remove_dir_all(&ws);
|
|
417
466
|
}
|
|
467
|
+
|
|
468
|
+
#[test]
|
|
469
|
+
fn team_agent_force_flag_does_not_look_like_bypass() {
|
|
470
|
+
let ws =
|
|
471
|
+
std::env::temp_dir().join(format!("ta-bypass-force-not-bypass-{}", std::process::id()));
|
|
472
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
473
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
474
|
+
let spec = yaml::loads(
|
|
475
|
+
"version: 1\nteam: {}\nleader: {}\nagents:\n - id: a\n role: r\n provider: fake\n dangerously_skip_permissions: false\n",
|
|
476
|
+
)
|
|
477
|
+
.unwrap();
|
|
478
|
+
let argv = vec![
|
|
479
|
+
"team-agent".to_string(),
|
|
480
|
+
"remove-agent".to_string(),
|
|
481
|
+
"x".to_string(),
|
|
482
|
+
"--confirm".to_string(),
|
|
483
|
+
"--force".to_string(),
|
|
484
|
+
];
|
|
485
|
+
emit_dangerous_flag_no_role_declared_warning_with_argv(&ws, &spec, &argv);
|
|
486
|
+
let events = crate::event_log::EventLog::new(&ws).tail(0).unwrap();
|
|
487
|
+
assert!(
|
|
488
|
+
!events.iter().any(|event| {
|
|
489
|
+
event.get("event").and_then(serde_json::Value::as_str)
|
|
490
|
+
== Some("dangerously_flag_present_but_no_role_declared")
|
|
491
|
+
}),
|
|
492
|
+
"team-agent --force must not emit bypass warning; events={events:?}"
|
|
493
|
+
);
|
|
494
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
495
|
+
}
|
|
418
496
|
}
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: workspace 级 agent-lifecycle 互斥锁,让同一 workspace 的席位增删起停串行
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: acquire_agent_lifecycle_lock
|
|
6
|
+
//! what: 轮询抢锁,成功返回持有到 Drop 的 guard,超时返回 LifecycleLockTimeout
|
|
7
|
+
//! - name: agent_lifecycle_lock_path
|
|
8
|
+
//! what: 给出该 workspace 的锁文件路径
|
|
9
|
+
//! depends:
|
|
10
|
+
//! - crate::platform::file_lock
|
|
11
|
+
//! - crate::event_log::EventLog
|
|
12
|
+
//! - crate::model::paths
|
|
13
|
+
//! boundary:
|
|
14
|
+
//! - 不锁 pane 输入通道,那是 pane_input_lock
|
|
15
|
+
//! - 不做跨 workspace 互斥,锁作用域就是一个 workspace
|
|
16
|
+
//! - 拿不到锁只超时报错,不强抢也不清理他人锁文件
|
|
17
|
+
//! - 锁文件内容只作观测用,不作为持有权判据
|
|
18
|
+
//! maturity: wired
|
|
19
|
+
//! ---
|
|
1
20
|
use std::io::{Seek, Write};
|
|
2
21
|
use std::path::{Path, PathBuf};
|
|
3
22
|
use std::time::{Duration, Instant};
|
|
@@ -17,10 +36,16 @@ pub(crate) struct LifecycleLockRequest<'a> {
|
|
|
17
36
|
}
|
|
18
37
|
|
|
19
38
|
pub(crate) struct LifecycleLockGuard {
|
|
20
|
-
#[allow(dead_code)]
|
|
21
39
|
file: std::fs::File,
|
|
22
40
|
}
|
|
23
41
|
|
|
42
|
+
/// ---
|
|
43
|
+
/// purpose: 抢 workspace 级 agent-lifecycle 锁,默认 30 秒超时、5 秒起写 lock_held_long 事件
|
|
44
|
+
/// params:
|
|
45
|
+
/// request: 携带 workspace、操作名与可选 team/agent,仅用于锁元数据与事件
|
|
46
|
+
/// returns: 持有锁的 guard,Drop 时解锁
|
|
47
|
+
/// errors: 建目录或打开锁文件失败返回 StatePersist,等待超时返回 LifecycleLockTimeout
|
|
48
|
+
/// ---
|
|
24
49
|
pub(crate) fn acquire_agent_lifecycle_lock(
|
|
25
50
|
request: LifecycleLockRequest<'_>,
|
|
26
51
|
) -> Result<LifecycleLockGuard, LifecycleError> {
|
|
@@ -35,6 +60,14 @@ pub(crate) fn acquire_agent_lifecycle_lock(
|
|
|
35
60
|
)
|
|
36
61
|
}
|
|
37
62
|
|
|
63
|
+
/// ---
|
|
64
|
+
/// purpose: 以显式超时与告警阈值抢同一把锁,仅测试构建可见
|
|
65
|
+
/// params:
|
|
66
|
+
/// timeout: 放弃等待的上限
|
|
67
|
+
/// held_long: 超过它写一次 lock_held_long 事件
|
|
68
|
+
/// returns: 持有锁的 guard
|
|
69
|
+
/// errors: 同 acquire_agent_lifecycle_lock
|
|
70
|
+
/// ---
|
|
38
71
|
#[cfg(test)]
|
|
39
72
|
pub(crate) fn acquire_agent_lifecycle_lock_for_test(
|
|
40
73
|
request: LifecycleLockRequest<'_>,
|
|
@@ -55,6 +88,10 @@ pub(crate) struct LifecycleLockDeadlineOverrideGuard {
|
|
|
55
88
|
previous: Option<(Duration, Duration)>,
|
|
56
89
|
}
|
|
57
90
|
|
|
91
|
+
/// ---
|
|
92
|
+
/// purpose: 线程内改写默认锁超时与告警阈值,仅测试构建可见
|
|
93
|
+
/// returns: guard,Drop 时恢复上一次的取值
|
|
94
|
+
/// ---
|
|
58
95
|
#[cfg(test)]
|
|
59
96
|
pub(crate) fn override_agent_lifecycle_lock_deadlines_for_test(
|
|
60
97
|
timeout: Duration,
|
|
@@ -145,6 +182,10 @@ fn acquire_agent_lifecycle_lock_with_deadlines(
|
|
|
145
182
|
}
|
|
146
183
|
}
|
|
147
184
|
|
|
185
|
+
/// ---
|
|
186
|
+
/// purpose: 给出该 workspace 的 agent-lifecycle 锁文件路径
|
|
187
|
+
/// returns: runtime 目录下的 agent-lifecycle.lock
|
|
188
|
+
/// ---
|
|
148
189
|
pub(crate) fn agent_lifecycle_lock_path(workspace: &Path) -> PathBuf {
|
|
149
190
|
crate::model::paths::runtime_dir(workspace).join(format!("{AGENT_LIFECYCLE_LOCK_NAME}.lock"))
|
|
150
191
|
}
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: team 进程级生命周期编排(起停加减 clone/fork)
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: launch
|
|
6
|
+
//! what: 冷启与成员生命周期入口
|
|
7
|
+
//! boundary:
|
|
8
|
+
//! - 不拥有物理注入,只调用 transport
|
|
9
|
+
//! maturity: wired
|
|
10
|
+
//! ---
|
|
1
11
|
//!
|
|
2
12
|
//! step 13 · lifecycle — team 进程级生命周期编排器(ROUND-0.5 骨架)。
|
|
3
13
|
//!
|
|
@@ -46,6 +56,7 @@ pub mod display;
|
|
|
46
56
|
pub mod helpers;
|
|
47
57
|
pub mod launch;
|
|
48
58
|
pub(crate) mod lock;
|
|
59
|
+
pub(crate) mod pane_input_lock;
|
|
49
60
|
pub(crate) mod profile_launch;
|
|
50
61
|
pub(crate) mod profile_smoke;
|
|
51
62
|
pub mod restart;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: pane 输入通道互斥锁,让同一 pane 上的写入串行化
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: acquire_or_proceed
|
|
6
|
+
//! what: 取到锁则持有到 Drop;超时或锁不可用则告警后放行,不无限等待
|
|
7
|
+
//! depends:
|
|
8
|
+
//! - crate::platform::file_lock
|
|
9
|
+
//! - crate::event_log::EventLog
|
|
10
|
+
//! boundary:
|
|
11
|
+
//! - 不锁席位生命周期(那是 lock.rs)
|
|
12
|
+
//! - 不重粘文本、不加自动兜底重投;重试只重按回车由既有 inject 路径负责
|
|
13
|
+
//! - 不在投递层发明临时持有态
|
|
14
|
+
//! maturity: wired
|
|
15
|
+
//! ---
|
|
16
|
+
|
|
17
|
+
use std::path::{Path, PathBuf};
|
|
18
|
+
use std::time::{Duration, Instant};
|
|
19
|
+
|
|
20
|
+
use crate::event_log::EventLog;
|
|
21
|
+
use crate::model::paths::runtime_dir;
|
|
22
|
+
|
|
23
|
+
/// Hard upper bound for waiting on the pane input lock. Constant, not a magic number in a loop.
|
|
24
|
+
pub(crate) const PANE_INPUT_LOCK_TIMEOUT: Duration = Duration::from_millis(200);
|
|
25
|
+
|
|
26
|
+
pub(crate) const PANE_INPUT_LOCK_NAME: &str = "pane-input";
|
|
27
|
+
|
|
28
|
+
/// Event / stderr token. A5 greps this exact string.
|
|
29
|
+
pub(crate) const PANE_INPUT_LOCK_TIMEOUT_EVENT: &str = "pane_input_lock.timeout";
|
|
30
|
+
|
|
31
|
+
pub(crate) struct PaneInputLockRequest<'a> {
|
|
32
|
+
pub workspace: Option<&'a Path>,
|
|
33
|
+
pub target_key: &'a str,
|
|
34
|
+
pub operation: &'static str,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub(crate) struct PaneInputLockGuard {
|
|
38
|
+
file: Option<std::fs::File>,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// ---
|
|
42
|
+
/// purpose: 取 pane 输入锁;拿不到或超时则告警后放行,绝不无限等待
|
|
43
|
+
/// params:
|
|
44
|
+
/// request: 携带可选 workspace、目标 pane 键与操作名
|
|
45
|
+
/// returns: Some 表示已持锁,None 表示无 workspace 可锁或已超时放行
|
|
46
|
+
/// ---
|
|
47
|
+
/// Acquire the pane lock or proceed after the hard timeout.
|
|
48
|
+
///
|
|
49
|
+
/// On timeout / lock I/O failure: emit [`PANE_INPUT_LOCK_TIMEOUT_EVENT`] (stderr + events.jsonl)
|
|
50
|
+
/// and return `None` so the caller still writes. Relief messages must not wait forever.
|
|
51
|
+
pub(crate) fn acquire_or_proceed(request: PaneInputLockRequest<'_>) -> Option<PaneInputLockGuard> {
|
|
52
|
+
let timeout = PANE_INPUT_LOCK_TIMEOUT;
|
|
53
|
+
let Some(workspace) = request.workspace else {
|
|
54
|
+
// No workspace (some unit backends): nothing to lock against. Not an alarm.
|
|
55
|
+
return None;
|
|
56
|
+
};
|
|
57
|
+
acquire_file_lock(workspace, &request, timeout)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn acquire_file_lock(
|
|
61
|
+
workspace: &Path,
|
|
62
|
+
request: &PaneInputLockRequest<'_>,
|
|
63
|
+
timeout: Duration,
|
|
64
|
+
) -> Option<PaneInputLockGuard> {
|
|
65
|
+
let path = pane_input_lock_path(workspace, request.target_key);
|
|
66
|
+
if let Some(parent) = path.parent() {
|
|
67
|
+
if std::fs::create_dir_all(parent).is_err() {
|
|
68
|
+
emit_timeout_alarm(request, timeout, Duration::ZERO, "lock_dir");
|
|
69
|
+
return None;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let file = match std::fs::OpenOptions::new()
|
|
73
|
+
.create(true)
|
|
74
|
+
.read(true)
|
|
75
|
+
.write(true)
|
|
76
|
+
.truncate(false)
|
|
77
|
+
.open(&path)
|
|
78
|
+
{
|
|
79
|
+
Ok(file) => file,
|
|
80
|
+
Err(_) => {
|
|
81
|
+
emit_timeout_alarm(request, timeout, Duration::ZERO, "lock_open");
|
|
82
|
+
return None;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
let started = Instant::now();
|
|
86
|
+
loop {
|
|
87
|
+
match crate::platform::file_lock::try_lock_once_nonblocking(&file) {
|
|
88
|
+
Ok(true) => {
|
|
89
|
+
return Some(PaneInputLockGuard { file: Some(file) });
|
|
90
|
+
}
|
|
91
|
+
Ok(false) => {}
|
|
92
|
+
Err(_) => {
|
|
93
|
+
emit_timeout_alarm(request, timeout, started.elapsed(), "lock_io");
|
|
94
|
+
return None;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
let elapsed = started.elapsed();
|
|
98
|
+
if elapsed >= timeout {
|
|
99
|
+
emit_timeout_alarm(request, timeout, elapsed, "timeout");
|
|
100
|
+
return None;
|
|
101
|
+
}
|
|
102
|
+
std::thread::sleep(std::cmp::min(
|
|
103
|
+
Duration::from_millis(10),
|
|
104
|
+
timeout.saturating_sub(elapsed),
|
|
105
|
+
));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fn pane_input_lock_path(workspace: &Path, target_key: &str) -> PathBuf {
|
|
110
|
+
let safe: String = target_key
|
|
111
|
+
.chars()
|
|
112
|
+
.map(|c| {
|
|
113
|
+
if c.is_ascii_alphanumeric() {
|
|
114
|
+
c
|
|
115
|
+
} else {
|
|
116
|
+
'_'
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.collect();
|
|
120
|
+
runtime_dir(workspace)
|
|
121
|
+
.join(PANE_INPUT_LOCK_NAME)
|
|
122
|
+
.join(format!("{safe}.lock"))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fn emit_timeout_alarm(
|
|
126
|
+
request: &PaneInputLockRequest<'_>,
|
|
127
|
+
timeout: Duration,
|
|
128
|
+
waited: Duration,
|
|
129
|
+
reason: &str,
|
|
130
|
+
) {
|
|
131
|
+
// A5 watches this exact token on stderr. Commenting this line must turn A5 red.
|
|
132
|
+
eprintln!(
|
|
133
|
+
"{PANE_INPUT_LOCK_TIMEOUT_EVENT} target={} operation={} waited_ms={} timeout_ms={} reason={reason} proceeding",
|
|
134
|
+
request.target_key,
|
|
135
|
+
request.operation,
|
|
136
|
+
waited.as_millis(),
|
|
137
|
+
timeout.as_millis()
|
|
138
|
+
);
|
|
139
|
+
if let Some(workspace) = request.workspace {
|
|
140
|
+
let _ = EventLog::new(workspace).write(
|
|
141
|
+
PANE_INPUT_LOCK_TIMEOUT_EVENT,
|
|
142
|
+
serde_json::json!({
|
|
143
|
+
"lock_name": PANE_INPUT_LOCK_NAME,
|
|
144
|
+
"target": request.target_key,
|
|
145
|
+
"operation": request.operation,
|
|
146
|
+
"waited_ms": waited.as_millis() as u64,
|
|
147
|
+
"timeout_ms": timeout.as_millis() as u64,
|
|
148
|
+
"reason": reason,
|
|
149
|
+
"disposition": "proceed",
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
impl Drop for PaneInputLockGuard {
|
|
156
|
+
fn drop(&mut self) {
|
|
157
|
+
if let Some(file) = self.file.take() {
|
|
158
|
+
let _ = crate::platform::file_lock::unlock(&file);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 由 agent 的 profile 声明推出 provider 启动所需的环境覆盖、环境清除与命令覆写
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: prepare_provider_profile_launch
|
|
6
|
+
//! what: 从 YAML agent 节点算出 ProviderProfileLaunch
|
|
7
|
+
//! - name: prepare_provider_profile_launch_from_json
|
|
8
|
+
//! what: 从 runtime state 的 JSON agent 节点算出同一结果
|
|
9
|
+
//! - name: load_profile
|
|
10
|
+
//! what: 按查找顺序找到 profile env 文件并解析成键值
|
|
11
|
+
//! - name: provider_env_unsets
|
|
12
|
+
//! what: 该 provider 与 auth_mode 下必须清除的环境变量集,worker 与 leader 共用这一份
|
|
13
|
+
//! - name: profile_proxy_mode
|
|
14
|
+
//! what: 读出 profile 的代理模式,缺省 inherit
|
|
15
|
+
//! - name: parse_auth_mode
|
|
16
|
+
//! what: 把 auth_mode 字符串解析成枚举,未知值返回 None
|
|
17
|
+
//! depends:
|
|
18
|
+
//! - crate::provider
|
|
19
|
+
//! - crate::model::enums
|
|
20
|
+
//! - std::fs
|
|
21
|
+
//! boundary:
|
|
22
|
+
//! - 只读 profile 文件,不改写也不创建 profile
|
|
23
|
+
//! - 不把密钥值写进事件或错误消息
|
|
24
|
+
//! - provider 的环境清除集只此一份,leader 侧不得另建
|
|
25
|
+
//! maturity: wired
|
|
26
|
+
//! ---
|
|
1
27
|
use std::collections::{BTreeMap, BTreeSet};
|
|
2
28
|
use std::path::{Path, PathBuf};
|
|
3
29
|
|
|
@@ -37,6 +63,15 @@ struct AgentProfileInput {
|
|
|
37
63
|
model_source: Option<String>,
|
|
38
64
|
}
|
|
39
65
|
|
|
66
|
+
/// ---
|
|
67
|
+
/// purpose: 从 YAML agent 节点准备 provider 启动参数,profile 目录用默认查找顺序
|
|
68
|
+
/// params:
|
|
69
|
+
/// agent_id: agent 节点没写 id 时的兜底 id
|
|
70
|
+
/// mcp_config: 需要托管 MCP 配置时传入,否则 None
|
|
71
|
+
/// returns: 环境覆盖、环境清除、命令覆写与 claude 配置目录
|
|
72
|
+
/// errors: profile 找不到、必填键缺失或写 runtime env 文件失败时返回 LifecycleError
|
|
73
|
+
/// contract_id: lifecycle.profile_launch.prepare
|
|
74
|
+
/// ---
|
|
40
75
|
pub(crate) fn prepare_provider_profile_launch(
|
|
41
76
|
workspace: &Path,
|
|
42
77
|
agent_id: &str,
|
|
@@ -46,6 +81,14 @@ pub(crate) fn prepare_provider_profile_launch(
|
|
|
46
81
|
prepare_provider_profile_launch_with_profile_dir(workspace, agent_id, agent, None, mcp_config)
|
|
47
82
|
}
|
|
48
83
|
|
|
84
|
+
/// ---
|
|
85
|
+
/// purpose: 同上,但允许指定优先查找的 profile 目录
|
|
86
|
+
/// params:
|
|
87
|
+
/// profile_dir: 优先查找目录;agent 节点里的 _profile_dir 优先于它
|
|
88
|
+
/// returns: 环境覆盖、环境清除、命令覆写与 claude 配置目录
|
|
89
|
+
/// errors: 同 prepare_provider_profile_launch
|
|
90
|
+
/// contract_id: lifecycle.profile_launch.prepare
|
|
91
|
+
/// ---
|
|
49
92
|
pub(crate) fn prepare_provider_profile_launch_with_profile_dir(
|
|
50
93
|
workspace: &Path,
|
|
51
94
|
agent_id: &str,
|
|
@@ -94,6 +137,12 @@ pub(crate) fn prepare_provider_profile_launch_with_profile_dir(
|
|
|
94
137
|
prepare_profile_launch(workspace, input, mcp_config)
|
|
95
138
|
}
|
|
96
139
|
|
|
140
|
+
/// ---
|
|
141
|
+
/// purpose: 从 runtime state 的 JSON agent 节点准备 provider 启动参数
|
|
142
|
+
/// returns: 环境覆盖、环境清除、命令覆写与 claude 配置目录
|
|
143
|
+
/// errors: 同 prepare_provider_profile_launch
|
|
144
|
+
/// contract_id: lifecycle.profile_launch.prepare
|
|
145
|
+
/// ---
|
|
97
146
|
pub(crate) fn prepare_provider_profile_launch_from_json(
|
|
98
147
|
workspace: &Path,
|
|
99
148
|
agent_id: &str,
|
|
@@ -199,6 +248,14 @@ fn prepare_profile_launch(
|
|
|
199
248
|
})
|
|
200
249
|
}
|
|
201
250
|
|
|
251
|
+
/// ---
|
|
252
|
+
/// purpose: 按查找目录顺序定位 profile env 文件并解析成键值表
|
|
253
|
+
/// params:
|
|
254
|
+
/// name: profile 名,先找 name.env 再找 name.example.env
|
|
255
|
+
/// profile_dir: 优先查找目录,None 表示只用默认顺序
|
|
256
|
+
/// returns: 命中的文件路径与解析出的键值
|
|
257
|
+
/// errors: 所有目录都没命中返回 RequirementUnmet,读文件失败返回 StatePersist
|
|
258
|
+
/// ---
|
|
202
259
|
pub(crate) fn load_profile(
|
|
203
260
|
workspace: &Path,
|
|
204
261
|
name: &str,
|
|
@@ -424,11 +481,19 @@ fn provider_env_exports(
|
|
|
424
481
|
}
|
|
425
482
|
}
|
|
426
483
|
}
|
|
484
|
+
// grok/cursor_agent 一期无 profile exports(subscription 已登录态)。
|
|
485
|
+
// Cursor 订阅席的代理透传不在这里:profile 只服务 compatible-api。
|
|
486
|
+
// 订阅路径见 apply_cursor_subscription_proxy_env(只记有无,不记值)。
|
|
487
|
+
Provider::Grok | Provider::CursorAgent | Provider::Pi => {}
|
|
427
488
|
Provider::Fake => {}
|
|
428
489
|
}
|
|
429
490
|
exports
|
|
430
491
|
}
|
|
431
492
|
|
|
493
|
+
/// ---
|
|
494
|
+
/// purpose: 给出该 provider 与 auth_mode 下必须从子进程环境里清掉的变量名集
|
|
495
|
+
/// returns: 变量名集合;claude 系清掉会让子进程并入父会话的那组标识
|
|
496
|
+
/// ---
|
|
432
497
|
/// 0.4.x (CR C-1): exposed as `pub(crate)` so the leader launcher
|
|
433
498
|
/// (leader/start.rs) can reuse the SAME Claude/Codex/Copilot env-unset set
|
|
434
499
|
/// used by worker spawn. Single source of truth — adding a new provider env
|
|
@@ -497,6 +562,12 @@ pub(crate) fn provider_env_unsets(provider: Provider, auth_mode: AuthMode) -> BT
|
|
|
497
562
|
// C-7-1 cr verdict: 一期 subscription-only(已登录态),exports/unsets 空;
|
|
498
563
|
// BYOK(COPILOT_PROVIDER_*)二期立项时单独 cr verdict 再开。
|
|
499
564
|
Provider::Copilot => {}
|
|
565
|
+
// grok 无 env 需 unset。cursor 订阅席必须保留代理键(TUI 连云要用);
|
|
566
|
+
// `--print` 路径会因 https:// 方案撞 GLOBAL_AGENT,但主路径是 TUI。
|
|
567
|
+
// 禁止在这里 unset 代理。
|
|
568
|
+
Provider::Grok => {}
|
|
569
|
+
Provider::CursorAgent => {}
|
|
570
|
+
Provider::Pi => {}
|
|
500
571
|
Provider::Fake => {}
|
|
501
572
|
}
|
|
502
573
|
unsets
|
|
@@ -893,6 +964,12 @@ fn compatible_api_network_exports(
|
|
|
893
964
|
.collect()
|
|
894
965
|
}
|
|
895
966
|
|
|
967
|
+
/// ---
|
|
968
|
+
/// purpose: 读出 profile 声明的代理模式
|
|
969
|
+
/// params:
|
|
970
|
+
/// values: 已解析的 profile 键值,先看 PROXY_MODE 再看 NETWORK_MODE
|
|
971
|
+
/// returns: 小写去空白后的模式串,两个键都没有时为 inherit
|
|
972
|
+
/// ---
|
|
896
973
|
pub(crate) fn profile_proxy_mode(values: &BTreeMap<String, String>) -> String {
|
|
897
974
|
values
|
|
898
975
|
.get("PROXY_MODE")
|
|
@@ -915,10 +992,18 @@ fn required_profile_keys(provider: Provider, auth_mode: AuthMode) -> &'static [&
|
|
|
915
992
|
// C-7-1 cr verdict: copilot 一期 subscription-only,无 BYOK required key 集合;
|
|
916
993
|
// 二期 BYOK 立项时填(向后兼容字段保留)。
|
|
917
994
|
Provider::Copilot => &[],
|
|
995
|
+
// 0.5.67: grok/cursor_agent 一期无 BYOK required key 集合 (同 copilot 精神)。
|
|
996
|
+
Provider::Grok | Provider::CursorAgent | Provider::Pi => &[],
|
|
918
997
|
Provider::Fake => &[],
|
|
919
998
|
}
|
|
920
999
|
}
|
|
921
1000
|
|
|
1001
|
+
/// ---
|
|
1002
|
+
/// purpose: 取 profile 里某个规范键,取不到时按该键的已知别名回退
|
|
1003
|
+
/// params:
|
|
1004
|
+
/// key: 规范键名,API_KEY BASE_URL MODEL 各有一组别名,其余键不回退
|
|
1005
|
+
/// returns: 命中的非空值
|
|
1006
|
+
/// ---
|
|
922
1007
|
pub(crate) fn profile_value_or_alternate<'a>(
|
|
923
1008
|
values: &'a BTreeMap<String, String>,
|
|
924
1009
|
key: &str,
|
|
@@ -949,6 +1034,10 @@ pub(crate) fn profile_value_or_alternate<'a>(
|
|
|
949
1034
|
})
|
|
950
1035
|
}
|
|
951
1036
|
|
|
1037
|
+
/// ---
|
|
1038
|
+
/// purpose: 在两个显式给定的键之间取第一个非空值
|
|
1039
|
+
/// returns: 命中的非空值,两个都空则 None
|
|
1040
|
+
/// ---
|
|
952
1041
|
pub(crate) fn value_or_alternate<'a>(
|
|
953
1042
|
values: &'a BTreeMap<String, String>,
|
|
954
1043
|
primary: &str,
|
|
@@ -961,6 +1050,10 @@ pub(crate) fn value_or_alternate<'a>(
|
|
|
961
1050
|
.map(String::as_str)
|
|
962
1051
|
}
|
|
963
1052
|
|
|
1053
|
+
/// ---
|
|
1054
|
+
/// purpose: 取 profile 里声明的模型名
|
|
1055
|
+
/// returns: MODEL 的非空值,没有时取 ANTHROPIC_MODEL
|
|
1056
|
+
/// ---
|
|
964
1057
|
pub(crate) fn profile_model(values: &BTreeMap<String, String>) -> Option<&str> {
|
|
965
1058
|
values
|
|
966
1059
|
.get("MODEL")
|
|
@@ -982,6 +1075,12 @@ fn effective_profile_or_agent_model<'a>(
|
|
|
982
1075
|
|
|
983
1076
|
pub(crate) use crate::provider::wire::parse_provider;
|
|
984
1077
|
|
|
1078
|
+
/// ---
|
|
1079
|
+
/// purpose: 把 auth_mode 字符串解析成枚举
|
|
1080
|
+
/// params:
|
|
1081
|
+
/// raw: 只接受 subscription、official_api、compatible_api
|
|
1082
|
+
/// returns: 对应枚举;未知取值返回 None,由调用方决定兜底
|
|
1083
|
+
/// ---
|
|
985
1084
|
pub(crate) fn parse_auth_mode(raw: &str) -> Option<AuthMode> {
|
|
986
1085
|
match raw {
|
|
987
1086
|
"subscription" => Some(AuthMode::Subscription),
|