@team-agent/installer 0.4.5 → 0.4.7

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.
Files changed (29) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +6 -1
  4. package/crates/team-agent/src/cli/status_port.rs +119 -66
  5. package/crates/team-agent/src/cli/tests/status_send.rs +74 -48
  6. package/crates/team-agent/src/coordinator/tests/health_sync.rs +6 -1
  7. package/crates/team-agent/src/coordinator/tick.rs +46 -21
  8. package/crates/team-agent/src/leader/helpers.rs +1 -22
  9. package/crates/team-agent/src/lifecycle/launch.rs +34 -17
  10. package/crates/team-agent/src/lifecycle/profile_launch.rs +1 -11
  11. package/crates/team-agent/src/lifecycle/restart/agent.rs +160 -0
  12. package/crates/team-agent/src/lifecycle/restart/common.rs +82 -22
  13. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +108 -50
  14. package/crates/team-agent/src/lifecycle/restart/selection.rs +23 -0
  15. package/crates/team-agent/src/lifecycle/tests/core.rs +19 -11
  16. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +23 -1
  17. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +163 -15
  18. package/crates/team-agent/src/provider/adapter.rs +217 -377
  19. package/crates/team-agent/src/provider/adapters/claude.rs +106 -0
  20. package/crates/team-agent/src/provider/adapters/codex.rs +117 -0
  21. package/crates/team-agent/src/provider/adapters/copilot.rs +124 -0
  22. package/crates/team-agent/src/provider/adapters/fake.rs +17 -0
  23. package/crates/team-agent/src/provider/adapters/mod.rs +27 -0
  24. package/crates/team-agent/src/provider/mod.rs +6 -0
  25. package/crates/team-agent/src/provider/session/capture.rs +593 -55
  26. package/crates/team-agent/src/provider/wire.rs +139 -0
  27. package/crates/team-agent/src/state/persist.rs +225 -10
  28. package/package.json +4 -4
  29. package/skills/team-agent/SKILL.md +6 -0
@@ -0,0 +1,106 @@
1
+ //! Claude / ClaudeCode provider-local command builders + permission helpers.
2
+ //!
3
+ //! Extracted from `provider/adapter.rs` (0.4.x decoupling step 2). Pure
4
+ //! extraction — byte-identical to the original inline forms. Scope kept
5
+ //! small on purpose: base command + permission/disallowed-tool mapping +
6
+ //! launch wrapper. Auth hints (`claude_auth_hint`), capture-related
7
+ //! helpers (`claude_projects_dir_for_cwd`, `encode_claude_projects_dir`,
8
+ //! `rollout_path_has_claude_leader_marker`,
9
+ //! `claude_records_have_leader_marker`), and the context-aware model
10
+ //! resolver (`claude_context_model`) stay in `adapter.rs` because they
11
+ //! depend on adapter-private utilities (capture scanning, `command_on_path`,
12
+ //! `ProfileLaunchContext`). A later step can move those into a session
13
+ //! store / auth descriptor hook.
14
+
15
+ use crate::model::enums::AuthMode;
16
+ use crate::provider::adapter::{
17
+ next_session_token, prompt_needs_native_mcp, BasicProviderAdapter,
18
+ };
19
+ use crate::provider::{McpConfig, ProviderAdapter, ProviderError};
20
+
21
+ pub(crate) fn claude_launch_command(
22
+ adapter: &BasicProviderAdapter,
23
+ auth_mode: AuthMode,
24
+ mcp_config: Option<&McpConfig>,
25
+ system_prompt: Option<&str>,
26
+ model: Option<&str>,
27
+ tools: &[&str],
28
+ ) -> Result<Vec<String>, ProviderError> {
29
+ let mut argv = claude_base_command(
30
+ adapter,
31
+ auth_mode,
32
+ mcp_config,
33
+ system_prompt,
34
+ model,
35
+ tools,
36
+ false,
37
+ )?;
38
+ argv.push("--session-id".to_string());
39
+ argv.push(next_session_token());
40
+ Ok(argv)
41
+ }
42
+
43
+ pub(crate) fn claude_base_command(
44
+ adapter: &BasicProviderAdapter,
45
+ auth_mode: AuthMode,
46
+ mcp_config: Option<&McpConfig>,
47
+ system_prompt: Option<&str>,
48
+ model: Option<&str>,
49
+ tools: &[&str],
50
+ managed_mcp_config: bool,
51
+ ) -> Result<Vec<String>, ProviderError> {
52
+ let mut argv = vec!["claude".to_string()];
53
+ if claude_dangerous_auto_approve(tools) {
54
+ argv.push("--dangerously-skip-permissions".to_string());
55
+ } else {
56
+ argv.push("--permission-mode".to_string());
57
+ argv.push("default".to_string());
58
+ }
59
+ if let Some(model) = model {
60
+ argv.push("--model".to_string());
61
+ argv.push(model.to_string());
62
+ }
63
+ if let Some(prompt) = system_prompt {
64
+ argv.push("--append-system-prompt".to_string());
65
+ argv.push(prompt.to_string());
66
+ }
67
+ if !managed_mcp_config
68
+ && (mcp_config.is_some()
69
+ || auth_mode == AuthMode::CompatibleApi
70
+ || system_prompt.is_some_and(prompt_needs_native_mcp))
71
+ {
72
+ let raw = if let Some(config) = mcp_config {
73
+ serde_json::json!({"mcpServers": config.raw.clone()})
74
+ } else {
75
+ serde_json::json!({"mcpServers": adapter.mcp_config(auth_mode)?.raw})
76
+ };
77
+ argv.push("--mcp-config".to_string());
78
+ argv.push(raw.to_string());
79
+ }
80
+ for tool in claude_disallowed_tools(tools) {
81
+ argv.push("--disallowedTools".to_string());
82
+ argv.push(tool.to_string());
83
+ }
84
+ Ok(argv)
85
+ }
86
+
87
+ pub(crate) fn claude_dangerous_auto_approve(tools: &[&str]) -> bool {
88
+ tools.contains(&"dangerous_auto_approve")
89
+ }
90
+
91
+ pub(crate) fn claude_disallowed_tools(tools: &[&str]) -> Vec<&'static str> {
92
+ let mut disallowed = Vec::new();
93
+ if !tools.contains(&"execute_bash") {
94
+ disallowed.push("Bash");
95
+ }
96
+ if !tools.contains(&"fs_read") {
97
+ disallowed.push("Read");
98
+ }
99
+ if !tools.contains(&"fs_write") {
100
+ disallowed.extend(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
101
+ }
102
+ if !tools.contains(&"fs_list") {
103
+ disallowed.extend(["Glob", "Grep"]);
104
+ }
105
+ disallowed
106
+ }
@@ -0,0 +1,117 @@
1
+ //! Codex provider-local command builder + permission/sandbox helpers.
2
+ //!
3
+ //! Extracted from `provider/adapter.rs` (0.4.x decoupling step 2). Pure
4
+ //! extraction — byte-identical to the original inline forms. Shared helper
5
+ //! `json_inline` stays in `adapter.rs` because it is also used by other
6
+ //! sites; this file reaches it via `super::*`.
7
+
8
+ use crate::model::enums::AuthMode;
9
+ use crate::provider::adapter::json_inline;
10
+ use crate::provider::{McpConfig, ProviderCommandOverrides};
11
+
12
+ pub(crate) fn codex_base_command(
13
+ subcommand: Option<&str>,
14
+ _auth_mode: AuthMode,
15
+ mcp_config: Option<&McpConfig>,
16
+ system_prompt: Option<&str>,
17
+ model: Option<&str>,
18
+ tools: &[&str],
19
+ overrides: Option<&ProviderCommandOverrides>,
20
+ ) -> Vec<String> {
21
+ let mut argv = vec!["codex".to_string()];
22
+ if let Some(subcommand) = subcommand {
23
+ argv.push(subcommand.to_string());
24
+ }
25
+ argv.extend([
26
+ "--no-alt-screen".to_string(),
27
+ "--disable".to_string(),
28
+ "shell_snapshot".to_string(),
29
+ "--disable".to_string(),
30
+ "apps".to_string(),
31
+ ]);
32
+ if let Some(profile) = overrides.and_then(|o| o.codex_profile.as_deref()) {
33
+ argv.push("--profile".to_string());
34
+ argv.push(profile.to_string());
35
+ }
36
+ if codex_dangerous_auto_approve(tools) {
37
+ argv.push("--dangerously-bypass-approvals-and-sandbox".to_string());
38
+ } else {
39
+ argv.push("--sandbox".to_string());
40
+ argv.push(codex_sandbox_mode(tools).to_string());
41
+ argv.push("--ask-for-approval".to_string());
42
+ argv.push("on-request".to_string());
43
+ }
44
+ if let Some(model) = model {
45
+ argv.push("--model".to_string());
46
+ argv.push(model.to_string());
47
+ }
48
+ if let Some(overrides) = overrides {
49
+ for config in &overrides.codex_config {
50
+ argv.push("-c".to_string());
51
+ argv.push(config.clone());
52
+ }
53
+ }
54
+ if let Some(prompt) = system_prompt {
55
+ // codex.py:120 — escape order matters: backslash first, then quote, then newline.
56
+ let escaped = prompt
57
+ .replace('\\', "\\\\")
58
+ .replace('"', "\\\"")
59
+ .replace('\n', "\\n");
60
+ argv.push("-c".to_string());
61
+ argv.push(format!("developer_instructions=\"{escaped}\""));
62
+ }
63
+ if let Some(config) = mcp_config {
64
+ append_codex_mcp_overrides(&mut argv, &config.raw);
65
+ }
66
+ argv
67
+ }
68
+
69
+ /// Render an `McpConfig::raw` ({ name: { type, command, args, env: {...} } }) into Codex
70
+ /// `-c mcp_servers.<name>.<field>=...` overrides. JSON values are stringified with serde
71
+ /// so arrays/objects survive (Codex parses the right-hand side as JSON; this is what the
72
+ /// Python golden + the live attached Codex panes do).
73
+ pub(crate) fn append_codex_mcp_overrides(argv: &mut Vec<String>, raw: &serde_json::Value) {
74
+ let Some(servers) = raw.as_object() else {
75
+ return;
76
+ };
77
+ for (name, server) in servers {
78
+ let Some(obj) = server.as_object() else {
79
+ continue;
80
+ };
81
+ for (key, value) in obj {
82
+ if key == "env" {
83
+ if let Some(env) = value.as_object() {
84
+ for (env_key, env_value) in env {
85
+ argv.push("-c".to_string());
86
+ argv.push(format!(
87
+ "mcp_servers.{name}.env.{env_key}={}",
88
+ json_inline(env_value)
89
+ ));
90
+ }
91
+ }
92
+ continue;
93
+ }
94
+ argv.push("-c".to_string());
95
+ argv.push(format!("mcp_servers.{name}.{key}={}", json_inline(value)));
96
+ }
97
+ // Every MCP server gets a 600s tool timeout so long-running
98
+ // team_orchestrator calls (report_result etc.) survive the codex default.
99
+ argv.push("-c".to_string());
100
+ argv.push(format!("mcp_servers.{name}.tool_timeout_sec=600.0"));
101
+ }
102
+ }
103
+
104
+ pub(crate) fn codex_dangerous_auto_approve(tools: &[&str]) -> bool {
105
+ tools.contains(&"dangerous_auto_approve")
106
+ }
107
+
108
+ pub(crate) fn codex_sandbox_mode(tools: &[&str]) -> &'static str {
109
+ if tools
110
+ .iter()
111
+ .any(|tool| matches!(*tool, "fs_write" | "execute_bash"))
112
+ {
113
+ "workspace-write"
114
+ } else {
115
+ "read-only"
116
+ }
117
+ }
@@ -0,0 +1,124 @@
1
+ //! Copilot provider-local command builders + permission helpers.
2
+ //!
3
+ //! Extracted from `provider/adapter.rs` (0.4.x decoupling step 2). Pure
4
+ //! extraction — byte-identical to the original inline forms. Scope kept
5
+ //! small: base command + resume + permission flags + MCP type→transport
6
+ //! translation. Auth hint (`copilot_auth_hint`) and session store scan
7
+ //! (`scan_copilot_session_store`, `copilot_candidate`) stay in
8
+ //! `adapter.rs` because they depend on `command_on_path` /
9
+ //! `CaptureSessionContext` / sqlite session-store helpers. Step 3 of the
10
+ //! decoupling plan can move those into provider session store hooks.
11
+
12
+ use crate::model::enums::AuthMode;
13
+ use crate::provider::McpConfig;
14
+
15
+ pub(crate) fn copilot_base_command(
16
+ auth_mode: AuthMode,
17
+ mcp_config: Option<&McpConfig>,
18
+ system_prompt: Option<&str>,
19
+ model: Option<&str>,
20
+ tools: &[&str],
21
+ ) -> Vec<String> {
22
+ let _ = (auth_mode, system_prompt);
23
+ let mut argv = vec![
24
+ "copilot".to_string(),
25
+ // Noise control trio + disable remote control (防 GitHub web 远控 worker)
26
+ "--no-color".to_string(),
27
+ "--no-auto-update".to_string(),
28
+ "--no-remote".to_string(),
29
+ // P0: disable built-in github-mcp-server. Residual risk covered by
30
+ // spawn-time `copilot mcp list` scan + per-name --disable-mcp-server.
31
+ "--disable-builtin-mcps".to_string(),
32
+ ];
33
+ if copilot_dangerous_auto_approve(tools) {
34
+ argv.push("--allow-all".to_string());
35
+ } else {
36
+ for flag in copilot_permission_flags(tools) {
37
+ argv.push(flag);
38
+ }
39
+ }
40
+ // mcp_team ∈ canonical → approval-free (whole-server pattern).
41
+ argv.push("--allow-tool".to_string());
42
+ argv.push("team_orchestrator".to_string());
43
+ if let Some(model) = model {
44
+ argv.push("--model".to_string());
45
+ argv.push(model.to_string());
46
+ }
47
+ if let Some(config) = mcp_config {
48
+ // Copilot mcp config schema field is `transport` (stdio|http|sse),
49
+ // not the canonical `type`. McpConfig.raw is canonical; only copilot
50
+ // translates type→transport for --additional-mcp-config.
51
+ argv.push("--additional-mcp-config".to_string());
52
+ argv.push(copilot_translate_mcp_config(&config.raw).to_string());
53
+ }
54
+ argv
55
+ }
56
+
57
+ /// Translate `McpConfig.raw` canonical schema (`type`) into the copilot
58
+ /// `mcp add`/`--additional-mcp-config` expected `transport` field
59
+ /// (stdio|http|sse). Only the copilot adapter walks this translation —
60
+ /// claude/codex paths leave canonical schema untouched.
61
+ pub(crate) fn copilot_translate_mcp_config(raw: &serde_json::Value) -> serde_json::Value {
62
+ let Some(servers) = raw.as_object() else {
63
+ return raw.clone();
64
+ };
65
+ let mut translated = serde_json::Map::new();
66
+ for (name, server) in servers {
67
+ let Some(obj) = server.as_object() else {
68
+ translated.insert(name.clone(), server.clone());
69
+ continue;
70
+ };
71
+ let mut out = serde_json::Map::new();
72
+ for (key, value) in obj {
73
+ if key == "type" {
74
+ out.insert("transport".to_string(), value.clone());
75
+ } else {
76
+ out.insert(key.clone(), value.clone());
77
+ }
78
+ }
79
+ translated.insert(name.clone(), serde_json::Value::Object(out));
80
+ }
81
+ serde_json::Value::Object(translated)
82
+ }
83
+
84
+ /// Resume path = base + `--resume <sid>` (drop --session-id); kept separate
85
+ /// to avoid the plan accidentally emitting --session-id and --resume in the
86
+ /// same frame.
87
+ pub(crate) fn copilot_base_command_resume(
88
+ auth_mode: AuthMode,
89
+ mcp_config: Option<&McpConfig>,
90
+ system_prompt: Option<&str>,
91
+ model: Option<&str>,
92
+ tools: &[&str],
93
+ ) -> Vec<String> {
94
+ copilot_base_command(auth_mode, mcp_config, system_prompt, model, tools)
95
+ }
96
+
97
+ pub(crate) fn copilot_dangerous_auto_approve(tools: &[&str]) -> bool {
98
+ tools.contains(&"dangerous_auto_approve")
99
+ }
100
+
101
+ /// Granular deny mapping (canonical tool → copilot flag, all via
102
+ /// `--deny-tool <kind>`; help-permissions Tool Permissions has four kinds:
103
+ /// shell/write/mcp/url):
104
+ /// execute_bash ∉ allowed → `--deny-tool 'shell'`
105
+ /// fs_write ∉ allowed → `--deny-tool 'write'`
106
+ /// network ∉ allowed → `--deny-tool 'url'`
107
+ /// fs_read/fs_list have no copilot deny kind (honestly prompt_only).
108
+ pub(crate) fn copilot_permission_flags(tools: &[&str]) -> Vec<String> {
109
+ let mut flags = Vec::new();
110
+ if !tools.contains(&"execute_bash") {
111
+ flags.push("--deny-tool".to_string());
112
+ flags.push("shell".to_string());
113
+ }
114
+ if !tools.contains(&"fs_write") {
115
+ flags.push("--deny-tool".to_string());
116
+ flags.push("write".to_string());
117
+ }
118
+ if !tools.contains(&"network") {
119
+ // `--deny-tool 'url'` (omit domain → match all URLs).
120
+ flags.push("--deny-tool".to_string());
121
+ flags.push("url".to_string());
122
+ }
123
+ flags
124
+ }
@@ -0,0 +1,17 @@
1
+ //! Fake provider — scripted worker built into the team-agent CLI itself.
2
+ //! Extracted from `provider/adapter.rs` (0.4.x decoupling step 2).
3
+
4
+ pub(crate) fn fake_worker_command() -> Vec<String> {
5
+ let exe = std::env::current_exe()
6
+ .ok()
7
+ .and_then(|p| p.into_os_string().into_string().ok())
8
+ .unwrap_or_else(|| "team-agent".to_string());
9
+ vec![
10
+ exe,
11
+ "fake-worker".to_string(),
12
+ "--workspace".to_string(),
13
+ "{workspace}".to_string(),
14
+ "--agent-id".to_string(),
15
+ "{agent_id}".to_string(),
16
+ ]
17
+ }
@@ -0,0 +1,27 @@
1
+ //! Provider-local adapter implementations. Split from the monolithic
2
+ //! `provider/adapter.rs` as 0.4.x decoupling step 2. Each file owns its
3
+ //! provider's command builders, permission/sandbox/auth helpers, and
4
+ //! anything else that varies by provider but doesn't need shared
5
+ //! capture/scan utilities. The `ProviderAdapter` trait and the
6
+ //! `BasicProviderAdapter` registry stay in `adapter.rs`; the trait impl
7
+ //! dispatches into these helpers exactly as the inline forms did.
8
+ //!
9
+ //! Per-file scope:
10
+ //! * `claude` — Claude/ClaudeCode argv, dangerous-skip flag, disallowed
11
+ //! tools mapping, auth hint, model passthrough.
12
+ //! * `codex` — Codex argv, profile/sandbox flags, MCP `-c` injection
13
+ //! with 600s tool_timeout, developer-instructions escaping.
14
+ //! * `copilot` — Copilot argv (no-color/no-remote/disable-builtin-mcps),
15
+ //! allow/deny flag matrix, MCP `type→transport` translation, resume
16
+ //! base, weak auth hint.
17
+ //! * `fake` — Built-in scripted worker exec path.
18
+ //!
19
+ //! Behavior is byte-identical to pre-split. Future steps may move
20
+ //! `pre_spawn_adjust_plan`, `profile_env`, `session_backing_probe`, and
21
+ //! `session_candidates` into provider-owned hooks following the same
22
+ //! per-file layout.
23
+
24
+ pub(crate) mod claude;
25
+ pub(crate) mod codex;
26
+ pub(crate) mod copilot;
27
+ pub(crate) mod fake;
@@ -32,6 +32,9 @@
32
32
  pub use crate::model::enums::{AuthMode, Provider};
33
33
 
34
34
  pub mod adapter;
35
+ /// 0.4.x decoupling step 2: per-provider command builders + permission/auth
36
+ /// helpers split out of `adapter.rs`. Pure extraction — behavior unchanged.
37
+ pub(crate) mod adapters;
35
38
  pub mod approvals;
36
39
  pub mod classify;
37
40
  pub mod command;
@@ -40,6 +43,9 @@ pub mod session;
40
43
  pub mod session_scan;
41
44
  pub mod startup_prompt;
42
45
  pub mod types;
46
+ /// 0.4.x: provider wire-format helpers — single source of truth for
47
+ /// `Provider` ↔ string conversions (replaces 7 hand-rolled match copies).
48
+ pub(crate) mod wire;
43
49
  // helpers 全部原为模块私有(JSONL 解析 / 正则编译),非根可见 → 私有 mod,不参与 re-export。
44
50
  mod helpers;
45
51