@team-agent/installer 0.4.6 → 0.4.8
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 +33 -1
- package/crates/team-agent/src/cli/status_port.rs +119 -66
- package/crates/team-agent/src/cli/tests/status_send.rs +74 -48
- package/crates/team-agent/src/coordinator/tick.rs +2 -21
- package/crates/team-agent/src/leader/helpers.rs +1 -22
- package/crates/team-agent/src/lifecycle/launch.rs +1 -11
- package/crates/team-agent/src/lifecycle/profile_launch.rs +25 -11
- package/crates/team-agent/src/lifecycle/restart/agent.rs +27 -1
- package/crates/team-agent/src/lifecycle/restart/common.rs +20 -26
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +30 -8
- package/crates/team-agent/src/lifecycle/restart/selection.rs +67 -16
- package/crates/team-agent/src/lifecycle/restart.rs +1 -0
- package/crates/team-agent/src/lifecycle/tests/core.rs +99 -0
- package/crates/team-agent/src/messaging/delivery.rs +113 -0
- package/crates/team-agent/src/provider/adapter.rs +18 -372
- package/crates/team-agent/src/provider/adapters/claude.rs +106 -0
- package/crates/team-agent/src/provider/adapters/codex.rs +117 -0
- package/crates/team-agent/src/provider/adapters/copilot.rs +124 -0
- package/crates/team-agent/src/provider/adapters/fake.rs +17 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +27 -0
- package/crates/team-agent/src/provider/mod.rs +6 -0
- package/crates/team-agent/src/provider/session/capture.rs +131 -1
- package/crates/team-agent/src/provider/wire.rs +139 -0
- package/crates/team-agent/src/state/paths.rs +8 -4
- package/crates/team-agent/src/state/persist.rs +17 -0
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +6 -0
|
@@ -6,7 +6,7 @@ use std::process::Command;
|
|
|
6
6
|
use super::helpers::{find_session_id, parse_jsonl_records, patterns};
|
|
7
7
|
use super::types::{
|
|
8
8
|
AuthHintStatus, CaptureVia, CapturedSession, CommandPlan, Confidence, McpConfig,
|
|
9
|
-
ProviderCaps, ProviderCommandContext,
|
|
9
|
+
ProviderCaps, ProviderCommandContext, ProviderError, RolloutPath,
|
|
10
10
|
SessionId, StatusPatterns,
|
|
11
11
|
};
|
|
12
12
|
use super::{AuthMode, Provider};
|
|
@@ -296,7 +296,7 @@ pub fn get_adapter(p: Provider) -> Box<dyn ProviderAdapter> {
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
#[derive(Debug, Clone, Copy)]
|
|
299
|
-
struct BasicProviderAdapter {
|
|
299
|
+
pub(crate) struct BasicProviderAdapter {
|
|
300
300
|
provider: Provider,
|
|
301
301
|
}
|
|
302
302
|
|
|
@@ -928,26 +928,7 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
928
928
|
}
|
|
929
929
|
}
|
|
930
930
|
|
|
931
|
-
|
|
932
|
-
match provider {
|
|
933
|
-
Provider::Claude | Provider::ClaudeCode => "claude",
|
|
934
|
-
Provider::Codex => "codex",
|
|
935
|
-
Provider::Copilot => "copilot",
|
|
936
|
-
Provider::GeminiCli => "gemini",
|
|
937
|
-
Provider::Fake => "team-agent",
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
fn provider_wire(provider: Provider) -> &'static str {
|
|
942
|
-
match provider {
|
|
943
|
-
Provider::Claude => "claude",
|
|
944
|
-
Provider::ClaudeCode => "claude_code",
|
|
945
|
-
Provider::Codex => "codex",
|
|
946
|
-
Provider::Copilot => "copilot",
|
|
947
|
-
Provider::GeminiCli => "gemini_cli",
|
|
948
|
-
Provider::Fake => "fake",
|
|
949
|
-
}
|
|
950
|
-
}
|
|
931
|
+
use crate::provider::wire::{command_name, provider_wire};
|
|
951
932
|
|
|
952
933
|
fn auth_mode_wire(auth_mode: AuthMode) -> &'static str {
|
|
953
934
|
match auth_mode {
|
|
@@ -1559,364 +1540,29 @@ fn path_is_under_team_runtime(path: &Path) -> bool {
|
|
|
1559
1540
|
.any(|c| c.as_os_str() == std::ffi::OsStr::new(".team"))
|
|
1560
1541
|
}
|
|
1561
1542
|
|
|
1562
|
-
fn
|
|
1563
|
-
let exe = std::env::current_exe()
|
|
1564
|
-
.ok()
|
|
1565
|
-
.and_then(|p| p.into_os_string().into_string().ok())
|
|
1566
|
-
.unwrap_or_else(|| "team-agent".to_string());
|
|
1567
|
-
vec![
|
|
1568
|
-
exe,
|
|
1569
|
-
"fake-worker".to_string(),
|
|
1570
|
-
"--workspace".to_string(),
|
|
1571
|
-
"{workspace}".to_string(),
|
|
1572
|
-
"--agent-id".to_string(),
|
|
1573
|
-
"{agent_id}".to_string(),
|
|
1574
|
-
]
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
fn claude_launch_command(
|
|
1578
|
-
adapter: &BasicProviderAdapter,
|
|
1579
|
-
auth_mode: AuthMode,
|
|
1580
|
-
mcp_config: Option<&McpConfig>,
|
|
1581
|
-
system_prompt: Option<&str>,
|
|
1582
|
-
model: Option<&str>,
|
|
1583
|
-
tools: &[&str],
|
|
1584
|
-
) -> Result<Vec<String>, ProviderError> {
|
|
1585
|
-
let mut argv = claude_base_command(adapter, auth_mode, mcp_config, system_prompt, model, tools, false)?;
|
|
1586
|
-
argv.push("--session-id".to_string());
|
|
1587
|
-
argv.push(next_session_token());
|
|
1588
|
-
Ok(argv)
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
fn claude_base_command(
|
|
1592
|
-
adapter: &BasicProviderAdapter,
|
|
1593
|
-
auth_mode: AuthMode,
|
|
1594
|
-
mcp_config: Option<&McpConfig>,
|
|
1595
|
-
system_prompt: Option<&str>,
|
|
1596
|
-
model: Option<&str>,
|
|
1597
|
-
tools: &[&str],
|
|
1598
|
-
managed_mcp_config: bool,
|
|
1599
|
-
) -> Result<Vec<String>, ProviderError> {
|
|
1600
|
-
let mut argv = vec!["claude".to_string()];
|
|
1601
|
-
if claude_dangerous_auto_approve(tools) {
|
|
1602
|
-
argv.push("--dangerously-skip-permissions".to_string());
|
|
1603
|
-
} else {
|
|
1604
|
-
argv.push("--permission-mode".to_string());
|
|
1605
|
-
argv.push("default".to_string());
|
|
1606
|
-
}
|
|
1607
|
-
if let Some(model) = model {
|
|
1608
|
-
argv.push("--model".to_string());
|
|
1609
|
-
argv.push(model.to_string());
|
|
1610
|
-
}
|
|
1611
|
-
if let Some(prompt) = system_prompt {
|
|
1612
|
-
argv.push("--append-system-prompt".to_string());
|
|
1613
|
-
argv.push(prompt.to_string());
|
|
1614
|
-
}
|
|
1615
|
-
if !managed_mcp_config
|
|
1616
|
-
&& (mcp_config.is_some()
|
|
1617
|
-
|| auth_mode == AuthMode::CompatibleApi
|
|
1618
|
-
|| system_prompt.is_some_and(prompt_needs_native_mcp))
|
|
1619
|
-
{
|
|
1620
|
-
let raw = if let Some(config) = mcp_config {
|
|
1621
|
-
serde_json::json!({"mcpServers": config.raw.clone()})
|
|
1622
|
-
} else {
|
|
1623
|
-
serde_json::json!({"mcpServers": adapter.mcp_config(auth_mode)?.raw})
|
|
1624
|
-
};
|
|
1625
|
-
argv.push("--mcp-config".to_string());
|
|
1626
|
-
argv.push(raw.to_string());
|
|
1627
|
-
}
|
|
1628
|
-
for tool in claude_disallowed_tools(tools) {
|
|
1629
|
-
argv.push("--disallowedTools".to_string());
|
|
1630
|
-
argv.push(tool.to_string());
|
|
1631
|
-
}
|
|
1632
|
-
Ok(argv)
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
fn prompt_needs_native_mcp(prompt: &str) -> bool {
|
|
1543
|
+
pub(crate) fn prompt_needs_native_mcp(prompt: &str) -> bool {
|
|
1636
1544
|
prompt.contains('\n') || prompt.contains('"')
|
|
1637
1545
|
}
|
|
1638
1546
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
system_prompt: Option<&str>,
|
|
1644
|
-
model: Option<&str>,
|
|
1645
|
-
tools: &[&str],
|
|
1646
|
-
overrides: Option<&ProviderCommandOverrides>,
|
|
1647
|
-
) -> Vec<String> {
|
|
1648
|
-
let mut argv = vec![
|
|
1649
|
-
"codex".to_string(),
|
|
1650
|
-
];
|
|
1651
|
-
if let Some(subcommand) = subcommand {
|
|
1652
|
-
argv.push(subcommand.to_string());
|
|
1653
|
-
}
|
|
1654
|
-
argv.extend([
|
|
1655
|
-
"--no-alt-screen".to_string(),
|
|
1656
|
-
"--disable".to_string(),
|
|
1657
|
-
"shell_snapshot".to_string(),
|
|
1658
|
-
"--disable".to_string(),
|
|
1659
|
-
"apps".to_string(),
|
|
1660
|
-
]);
|
|
1661
|
-
// codex.py:105-107 — profile CODEX_PROFILE before the sandbox/approval flags.
|
|
1662
|
-
if let Some(profile) = overrides.and_then(|o| o.codex_profile.as_deref()) {
|
|
1663
|
-
argv.push("--profile".to_string());
|
|
1664
|
-
argv.push(profile.to_string());
|
|
1665
|
-
}
|
|
1666
|
-
if codex_dangerous_auto_approve(tools) {
|
|
1667
|
-
argv.push("--dangerously-bypass-approvals-and-sandbox".to_string());
|
|
1668
|
-
} else {
|
|
1669
|
-
argv.push("--sandbox".to_string());
|
|
1670
|
-
argv.push(codex_sandbox_mode(tools).to_string());
|
|
1671
|
-
argv.push("--ask-for-approval".to_string());
|
|
1672
|
-
argv.push("on-request".to_string());
|
|
1673
|
-
}
|
|
1674
|
-
if let Some(model) = model {
|
|
1675
|
-
argv.push("--model".to_string());
|
|
1676
|
-
argv.push(model.to_string());
|
|
1677
|
-
}
|
|
1678
|
-
// codex.py:117-118 — profile codex_config `-c` items before developer_instructions.
|
|
1679
|
-
if let Some(overrides) = overrides {
|
|
1680
|
-
for config in &overrides.codex_config {
|
|
1681
|
-
argv.push("-c".to_string());
|
|
1682
|
-
argv.push(config.clone());
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
if let Some(prompt) = system_prompt {
|
|
1686
|
-
// codex.py:120 — escape order matters: backslash first, then quote, then newline.
|
|
1687
|
-
let escaped = prompt
|
|
1688
|
-
.replace('\\', "\\\\")
|
|
1689
|
-
.replace('"', "\\\"")
|
|
1690
|
-
.replace('\n', "\\n");
|
|
1691
|
-
argv.push("-c".to_string());
|
|
1692
|
-
argv.push(format!("developer_instructions=\"{escaped}\""));
|
|
1693
|
-
}
|
|
1694
|
-
// Contract C / MUST-8: Codex CLI (2026-06) does NOT take Claude's `--mcp-config <json>` flag;
|
|
1695
|
-
// instead it uses `-c mcp_servers.<name>.<field>=...` overrides, the same pattern used by
|
|
1696
|
-
// the live Team Agent workers in this very session (attach-leader codex panes spawn with
|
|
1697
|
-
// `-c mcp_servers.team_orchestrator.command="..."`, ` ...args=[...]`, `...env.TEAM_AGENT_ID=...`).
|
|
1698
|
-
// Inject the resolved MCP config that way so the Codex worker has a real callable
|
|
1699
|
-
// `team_orchestrator` server (not prompt-only metadata).
|
|
1700
|
-
if let Some(config) = mcp_config {
|
|
1701
|
-
append_codex_mcp_overrides(&mut argv, &config.raw);
|
|
1702
|
-
}
|
|
1703
|
-
argv
|
|
1704
|
-
}
|
|
1705
|
-
|
|
1706
|
-
/// Render an `McpConfig::raw` ({ name: { type, command, args, env: {...} } }) into Codex
|
|
1707
|
-
/// `-c mcp_servers.<name>.<field>=...` overrides. JSON values are stringified with serde
|
|
1708
|
-
/// so arrays/objects survive (Codex parses the right-hand side as JSON; this is what the
|
|
1709
|
-
/// Python golden + the live attached Codex panes do).
|
|
1710
|
-
fn append_codex_mcp_overrides(argv: &mut Vec<String>, raw: &serde_json::Value) {
|
|
1711
|
-
let Some(servers) = raw.as_object() else {
|
|
1712
|
-
return;
|
|
1713
|
-
};
|
|
1714
|
-
for (name, server) in servers {
|
|
1715
|
-
let Some(obj) = server.as_object() else {
|
|
1716
|
-
continue;
|
|
1717
|
-
};
|
|
1718
|
-
for (key, value) in obj {
|
|
1719
|
-
if key == "env" {
|
|
1720
|
-
if let Some(env) = value.as_object() {
|
|
1721
|
-
for (env_key, env_value) in env {
|
|
1722
|
-
argv.push("-c".to_string());
|
|
1723
|
-
argv.push(format!(
|
|
1724
|
-
"mcp_servers.{name}.env.{env_key}={}",
|
|
1725
|
-
json_inline(env_value)
|
|
1726
|
-
));
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
continue;
|
|
1730
|
-
}
|
|
1731
|
-
argv.push("-c".to_string());
|
|
1732
|
-
argv.push(format!("mcp_servers.{name}.{key}={}", json_inline(value)));
|
|
1733
|
-
}
|
|
1734
|
-
// codex.py:129 — every MCP server gets a 600s tool timeout so long-running
|
|
1735
|
-
// team_orchestrator calls (report_result etc.) survive the codex default.
|
|
1736
|
-
argv.push("-c".to_string());
|
|
1737
|
-
argv.push(format!("mcp_servers.{name}.tool_timeout_sec=600.0"));
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
fn json_inline(value: &serde_json::Value) -> String {
|
|
1547
|
+
/// Shared JSON renderer used by the codex `-c mcp_servers.*=...` overrides
|
|
1548
|
+
/// and any other adapter that wants inline JSON. String values are quoted
|
|
1549
|
+
/// (with quote escape); other values use serde's default Display.
|
|
1550
|
+
pub(crate) fn json_inline(value: &serde_json::Value) -> String {
|
|
1742
1551
|
match value {
|
|
1743
1552
|
serde_json::Value::String(s) => format!("\"{}\"", s.replace('"', "\\\"")),
|
|
1744
1553
|
other => other.to_string(),
|
|
1745
1554
|
}
|
|
1746
1555
|
}
|
|
1747
1556
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
//
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
// copilot --no-color --no-auto-update --no-remote # C-1-2 噪音 + 禁远控
|
|
1758
|
-
// --disable-builtin-mcps # C-3-1 P0 禁内建 github-mcp-server
|
|
1759
|
-
// --additional-mcp-config @<file> # C-3-4 用 @file 形,避 wrapper 语义
|
|
1760
|
-
// --allow-tool 'team_orchestrator' # C-3-5 mcp_team 免审批 (server 级)
|
|
1761
|
-
// --session-id <uuid> -n <agent_id> # C-7-1 plan/launch 加
|
|
1762
|
-
// -C <workspace> # 双保险,launch 加
|
|
1763
|
-
// [--allow-all | <granular deny>] # C-5-1/C-5-2
|
|
1764
|
-
// [--model <m>]
|
|
1765
|
-
// [--log-dir <dir> --log-level info] # C-6-2 launch 加
|
|
1766
|
-
// env: COPILOT_CUSTOM_INSTRUCTIONS_DIRS=<ws>/.../<agent_id>/ # C-2-1 launch 注入
|
|
1767
|
-
// COPILOT_DISABLE_TERMINAL_TITLE=1 # C-4 P0 N39 红线,launch 注入
|
|
1768
|
-
// banner 不入 argv(v1 错的 --banner=never 删除;banner 走 config 文件,非 CLI flag)。
|
|
1769
|
-
// `-i`/`-p`/`--share*`/`--no-ask-user` **绝不**入 argv(RC-1/RC-14/RC-16)。
|
|
1770
|
-
//
|
|
1771
|
-
// system_prompt(B2 灵魂件)**不进 argv**:走 spawn env COPILOT_CUSTOM_INSTRUCTIONS_DIRS
|
|
1772
|
-
// + per-worker AGENTS.md(B2 单源,不另拼);本函数 system_prompt 参数静默忽略。
|
|
1773
|
-
fn copilot_base_command(
|
|
1774
|
-
auth_mode: AuthMode,
|
|
1775
|
-
mcp_config: Option<&McpConfig>,
|
|
1776
|
-
system_prompt: Option<&str>,
|
|
1777
|
-
model: Option<&str>,
|
|
1778
|
-
tools: &[&str],
|
|
1779
|
-
) -> Vec<String> {
|
|
1780
|
-
let _ = (auth_mode, system_prompt);
|
|
1781
|
-
let mut argv = vec![
|
|
1782
|
-
"copilot".to_string(),
|
|
1783
|
-
// C-1-2 v2:噪音控制三件 + 禁远控(防 GitHub web 远控 worker)
|
|
1784
|
-
"--no-color".to_string(),
|
|
1785
|
-
"--no-auto-update".to_string(),
|
|
1786
|
-
"--no-remote".to_string(),
|
|
1787
|
-
// C-3-1 v2 (P0):禁内建 github-mcp-server(main-help:70-71);残留风险
|
|
1788
|
-
// 通过 spawn 前 `copilot mcp list` 扫描 + 按名 `--disable-mcp-server <n>` 补
|
|
1789
|
-
// (那一段在 launch 路径加,因为需要 spawn-time 探测)。
|
|
1790
|
-
"--disable-builtin-mcps".to_string(),
|
|
1791
|
-
];
|
|
1792
|
-
if copilot_dangerous_auto_approve(tools) {
|
|
1793
|
-
// C-5-1 v2 实证:--allow-all == --yolo == 三件套(tools+paths+urls 等价),
|
|
1794
|
-
// help-permissions Enabling All Permissions 节原文。**禁** --allow-all-tools
|
|
1795
|
-
// (仅 tools 一档,语义不全,RC-13)。
|
|
1796
|
-
argv.push("--allow-all".to_string());
|
|
1797
|
-
} else {
|
|
1798
|
-
// C-5-2 v2:角色缺某 canonical 能力 → 精细 deny(deny 恒优先,即便
|
|
1799
|
-
// --allow-all-tools,help-permissions 原文);**禁** --allow-all/--yolo(RC-14)。
|
|
1800
|
-
for flag in copilot_permission_flags(tools) {
|
|
1801
|
-
argv.push(flag);
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
// C-3-5 v2:mcp_team ∈ canonical(team_orchestrator 是我们的 server)→ 免审批
|
|
1805
|
-
// (模式 `<mcp-server-name>(tool-name?)` 省略 tool = 该 server 全工具集)。
|
|
1806
|
-
argv.push("--allow-tool".to_string());
|
|
1807
|
-
argv.push("team_orchestrator".to_string());
|
|
1808
|
-
if let Some(model) = model {
|
|
1809
|
-
argv.push("--model".to_string());
|
|
1810
|
-
argv.push(model.to_string());
|
|
1811
|
-
}
|
|
1812
|
-
if let Some(config) = mcp_config {
|
|
1813
|
-
// §C1 v2 + C-3-4 cr verdict v2(te 真机实证 cmd-mcp-add schema):
|
|
1814
|
-
// copilot 的 mcp 配置 schema 字段名是 `transport`(取值 stdio|http|sse),
|
|
1815
|
-
// 不是 codex/claude 的 `type`。McpConfig.raw 是 canonical(type),写
|
|
1816
|
-
// --additional-mcp-config 时必须翻译 type→transport(仅 copilot 走此分支)。
|
|
1817
|
-
argv.push("--additional-mcp-config".to_string());
|
|
1818
|
-
argv.push(copilot_translate_mcp_config(&config.raw).to_string());
|
|
1819
|
-
}
|
|
1820
|
-
argv
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
/// C-3-4 cr verdict v2 — 把 McpConfig.raw 的 canonical schema(`type`)翻译成
|
|
1824
|
-
/// copilot mcp add/--additional-mcp-config 期望的 `transport` 字段(stdio|http|sse)。
|
|
1825
|
-
/// 仅 Copilot 适配走此翻译,claude/codex 路径不动。
|
|
1826
|
-
fn copilot_translate_mcp_config(raw: &serde_json::Value) -> serde_json::Value {
|
|
1827
|
-
let Some(servers) = raw.as_object() else {
|
|
1828
|
-
return raw.clone();
|
|
1829
|
-
};
|
|
1830
|
-
let mut translated = serde_json::Map::new();
|
|
1831
|
-
for (name, server) in servers {
|
|
1832
|
-
let Some(obj) = server.as_object() else {
|
|
1833
|
-
translated.insert(name.clone(), server.clone());
|
|
1834
|
-
continue;
|
|
1835
|
-
};
|
|
1836
|
-
let mut out = serde_json::Map::new();
|
|
1837
|
-
for (key, value) in obj {
|
|
1838
|
-
if key == "type" {
|
|
1839
|
-
out.insert("transport".to_string(), value.clone());
|
|
1840
|
-
} else {
|
|
1841
|
-
out.insert(key.clone(), value.clone());
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
translated.insert(name.clone(), serde_json::Value::Object(out));
|
|
1845
|
-
}
|
|
1846
|
-
serde_json::Value::Object(translated)
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
/// resume 路径同 base + `--resume <sid>`(去 --session-id);单列出
|
|
1850
|
-
/// 避免 plan 端误 push --session-id 与 --resume 同帧。
|
|
1851
|
-
fn copilot_base_command_resume(
|
|
1852
|
-
auth_mode: AuthMode,
|
|
1853
|
-
mcp_config: Option<&McpConfig>,
|
|
1854
|
-
system_prompt: Option<&str>,
|
|
1855
|
-
model: Option<&str>,
|
|
1856
|
-
tools: &[&str],
|
|
1857
|
-
) -> Vec<String> {
|
|
1858
|
-
copilot_base_command(auth_mode, mcp_config, system_prompt, model, tools)
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
fn copilot_dangerous_auto_approve(tools: &[&str]) -> bool {
|
|
1862
|
-
tools.contains(&"dangerous_auto_approve")
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
/// C-5-2 v2 verdict — copilot 细粒度 deny 映射(canonical tool → copilot flag,
|
|
1866
|
-
/// 全部走 `--deny-tool <kind>`,help-permissions Tool Permissions 节四 kind:
|
|
1867
|
-
/// shell/write/mcp/url):
|
|
1868
|
-
/// execute_bash ∉ allowed → `--deny-tool 'shell'`
|
|
1869
|
-
/// fs_write ∉ allowed → `--deny-tool 'write'`
|
|
1870
|
-
/// network ∉ allowed → `--deny-tool 'url'`(help-permissions: "url(domain-or-url?)
|
|
1871
|
-
/// … If omitted, matches all URLs")
|
|
1872
|
-
/// fs_read/fs_list 在 copilot 上无对应 deny kind(C-5-3 prompt_only 诚实)。
|
|
1873
|
-
fn copilot_permission_flags(tools: &[&str]) -> Vec<String> {
|
|
1874
|
-
let mut flags = Vec::new();
|
|
1875
|
-
if !tools.contains(&"execute_bash") {
|
|
1876
|
-
flags.push("--deny-tool".to_string());
|
|
1877
|
-
flags.push("shell".to_string());
|
|
1878
|
-
}
|
|
1879
|
-
if !tools.contains(&"fs_write") {
|
|
1880
|
-
flags.push("--deny-tool".to_string());
|
|
1881
|
-
flags.push("write".to_string());
|
|
1882
|
-
}
|
|
1883
|
-
if !tools.contains(&"network") {
|
|
1884
|
-
// v2 修正:`--deny-tool 'url'`(省略 domain 匹配全 URL),不是 `--deny-url '*'`
|
|
1885
|
-
// (RC-19 反向 case 守 — 全 URL 拒绝走 deny-tool kind,不走 deny-url path)。
|
|
1886
|
-
flags.push("--deny-tool".to_string());
|
|
1887
|
-
flags.push("url".to_string());
|
|
1888
|
-
}
|
|
1889
|
-
flags
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
fn claude_dangerous_auto_approve(tools: &[&str]) -> bool {
|
|
1893
|
-
tools.contains(&"dangerous_auto_approve")
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
fn claude_disallowed_tools(tools: &[&str]) -> Vec<&'static str> {
|
|
1897
|
-
let mut disallowed = Vec::new();
|
|
1898
|
-
if !tools.contains(&"execute_bash") {
|
|
1899
|
-
disallowed.push("Bash");
|
|
1900
|
-
}
|
|
1901
|
-
if !tools.contains(&"fs_read") {
|
|
1902
|
-
disallowed.push("Read");
|
|
1903
|
-
}
|
|
1904
|
-
if !tools.contains(&"fs_write") {
|
|
1905
|
-
disallowed.extend(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
1906
|
-
}
|
|
1907
|
-
if !tools.contains(&"fs_list") {
|
|
1908
|
-
disallowed.extend(["Glob", "Grep"]);
|
|
1909
|
-
}
|
|
1910
|
-
disallowed
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
fn codex_sandbox_mode(tools: &[&str]) -> &'static str {
|
|
1914
|
-
if tools.iter().any(|tool| matches!(*tool, "fs_write" | "execute_bash")) {
|
|
1915
|
-
"workspace-write"
|
|
1916
|
-
} else {
|
|
1917
|
-
"read-only"
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1557
|
+
// 0.4.x decoupling step 2: provider-local command builders moved to provider/adapters/.
|
|
1558
|
+
// Only the entry points the trait impl actually calls are re-imported here;
|
|
1559
|
+
// the per-provider helper fns (dangerous_auto_approve, permission flags,
|
|
1560
|
+
// disallowed_tools, sandbox_mode, mcp_overrides) are called from within the
|
|
1561
|
+
// extracted base_command fns, not directly by this file.
|
|
1562
|
+
use super::adapters::claude::{claude_base_command, claude_launch_command};
|
|
1563
|
+
use super::adapters::codex::codex_base_command;
|
|
1564
|
+
use super::adapters::copilot::{copilot_base_command, copilot_base_command_resume};
|
|
1565
|
+
use super::adapters::fake::fake_worker_command;
|
|
1920
1566
|
|
|
1921
1567
|
/// Contract C / MUST-8: the per-worker Team Agent MCP server config used by Claude
|
|
1922
1568
|
/// (`--mcp-config`) and Codex (`-c mcp_servers.*` injection). Placeholders
|
|
@@ -2010,7 +1656,7 @@ fn has_cwd_field(record: &serde_json::Value) -> bool {
|
|
|
2010
1656
|
record_cwd(record).is_some()
|
|
2011
1657
|
}
|
|
2012
1658
|
|
|
2013
|
-
fn next_session_token() -> String {
|
|
1659
|
+
pub(crate) fn next_session_token() -> String {
|
|
2014
1660
|
use sha2::Digest;
|
|
2015
1661
|
|
|
2016
1662
|
static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(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
|
+
}
|