@team-agent/installer 0.5.84 → 0.5.86
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/mod.rs +0 -1
- package/crates/team-agent/src/cli/profile.rs +4 -12
- package/crates/team-agent/src/cli/tests/verb_profile.rs +66 -0
- package/crates/team-agent/src/compiler/tests.rs +13 -24
- package/crates/team-agent/src/compiler.rs +1 -50
- package/crates/team-agent/src/leader/start.rs +0 -2
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +0 -6
- package/crates/team-agent/src/lifecycle/launch/identity.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/layout.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +1 -89
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +0 -3
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +14 -24
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +4 -8
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch.rs +0 -9
- package/crates/team-agent/src/lifecycle/profile_launch.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/common.rs +6 -9
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +1 -3
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +42 -46
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +209 -52
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +32 -77
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -13
- package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +0 -1
- package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +0 -2
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +14 -85
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +0 -5
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +0 -1
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +4 -4
- package/crates/team-agent/src/lifecycle/tests.rs +0 -1
- package/crates/team-agent/src/lifecycle/types.rs +0 -10
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +4 -248
- package/crates/team-agent/src/messaging/results/collection_tests.rs +719 -0
- package/crates/team-agent/src/messaging/results.rs +104 -27
- package/crates/team-agent/src/model/enums.rs +0 -35
- package/crates/team-agent/src/model/mod.rs +0 -1
- package/crates/team-agent/src/model/spec.rs +23 -84
- package/crates/team-agent/src/provider/adapter.rs +48 -50
- package/crates/team-agent/src/provider/adapters/claude.rs +5 -30
- package/crates/team-agent/src/provider/adapters/codex.rs +5 -25
- package/crates/team-agent/src/provider/adapters/copilot.rs +9 -42
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +4 -8
- package/crates/team-agent/src/provider/adapters/grok.rs +5 -58
- package/crates/team-agent/src/provider/adapters/mod.rs +5 -7
- package/crates/team-agent/src/provider/adapters/pi.rs +1 -56
- package/crates/team-agent/src/provider/bypass_flags.rs +1 -2
- package/crates/team-agent/src/provider/session/capture.rs +4 -4
- package/crates/team-agent/src/provider/tests/adapter.rs +7 -24
- package/crates/team-agent/src/provider/types.rs +1 -1
- package/crates/team-agent/src/state/persist.rs +15 -1
- package/package.json +4 -4
- package/schemas/team.schema.json +4 -9
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +0 -784
- package/crates/team-agent/src/model/permissions.rs +0 -721
|
@@ -6,9 +6,7 @@ use crate::lifecycle::launch::pi_mcp::{parse_pi_list_models_table, select_exact_
|
|
|
6
6
|
use crate::model::enums::ProviderEffort;
|
|
7
7
|
use crate::model::yaml::Value;
|
|
8
8
|
use crate::model::ModelError;
|
|
9
|
-
use crate::provider::adapters::pi::{
|
|
10
|
-
build_pi_command_argv, pi_tool_mapping, PiCommandRequest, PiSessionSelector, PiToolMapping,
|
|
11
|
-
};
|
|
9
|
+
use crate::provider::adapters::pi::{build_pi_command_argv, PiCommandRequest, PiSessionSelector};
|
|
12
10
|
|
|
13
11
|
const CATALOG: &[u8] = include_bytes!("fixtures/pi_list_models_g0.stdout.txt");
|
|
14
12
|
static NEXT_ROLE: AtomicU32 = AtomicU32::new(0);
|
|
@@ -51,13 +49,12 @@ fn compile_error(result: Result<CompiledRole, ModelError>) -> ModelError {
|
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
fn command(
|
|
52
|
+
fn command(_tools: &[&str], effort: ProviderEffort, model: &str) -> Result<Vec<String>, String> {
|
|
55
53
|
build_pi_command_argv(PiCommandRequest {
|
|
56
54
|
extension: Path::new("/workspace/.team/runtime/pi/t1/pi-worker/team-mcp.ts"),
|
|
57
55
|
model: Some(model),
|
|
58
56
|
effort: Some(effort),
|
|
59
57
|
system_prompt: "Pi worker contract.",
|
|
60
|
-
tool_categories: tools,
|
|
61
58
|
session_dir: Some(Path::new(
|
|
62
59
|
"/workspace/.team/runtime/pi/t1/pi-worker/sessions",
|
|
63
60
|
)),
|
|
@@ -70,24 +67,22 @@ fn command(tools: &[&str], effort: ProviderEffort, model: &str) -> Result<Vec<St
|
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
#[test]
|
|
73
|
-
fn
|
|
70
|
+
fn pi_role_ignores_tools_and_preserves_provider_defaults() {
|
|
74
71
|
let positive = compile_pi_role(&valid_role_with(""));
|
|
75
72
|
assert!(positive.is_ok(), "fully explicit Pi role must compile");
|
|
76
73
|
|
|
77
|
-
let
|
|
74
|
+
let missing_tools = valid_role_with("").replace(
|
|
75
|
+
"tools:\n - mcp_team\n - fs_read\n - fs_list\n - fs_write\n - execute_bash\n",
|
|
76
|
+
"",
|
|
77
|
+
);
|
|
78
|
+
compile_pi_role(&missing_tools).expect("omitted tools metadata must compile");
|
|
78
79
|
let unqualified =
|
|
79
80
|
valid_role_with("").replace("model: team-agent/qwen3.8-27b", "model: qwen3.8-27b");
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
("
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let error = compile_error(compile_pi_role(&role));
|
|
86
|
-
assert!(
|
|
87
|
-
error.to_string().to_ascii_lowercase().contains(label),
|
|
88
|
-
"error must name {label}; got {error}"
|
|
89
|
-
);
|
|
90
|
-
}
|
|
81
|
+
let error = compile_error(compile_pi_role(&unqualified));
|
|
82
|
+
assert!(
|
|
83
|
+
error.to_string().to_ascii_lowercase().contains("qualified exact model"),
|
|
84
|
+
"error must name qualified model; got {error}"
|
|
85
|
+
);
|
|
91
86
|
|
|
92
87
|
let defaults = valid_role_with("")
|
|
93
88
|
.replace("model: team-agent/qwen3.8-27b\n", "")
|
|
@@ -195,42 +190,7 @@ fn pi_max_effort_is_supported_without_model_suffix() {
|
|
|
195
190
|
}
|
|
196
191
|
|
|
197
192
|
#[test]
|
|
198
|
-
fn
|
|
199
|
-
assert_eq!(pi_tool_mapping("mcp_team"), PiToolMapping::Mcp);
|
|
200
|
-
assert_eq!(
|
|
201
|
-
pi_tool_mapping("fs_read"),
|
|
202
|
-
PiToolMapping::Builtin(&["read"])
|
|
203
|
-
);
|
|
204
|
-
assert_eq!(
|
|
205
|
-
pi_tool_mapping("fs_list"),
|
|
206
|
-
PiToolMapping::Builtin(&["grep", "find", "ls"])
|
|
207
|
-
);
|
|
208
|
-
assert_eq!(
|
|
209
|
-
pi_tool_mapping("fs_write"),
|
|
210
|
-
PiToolMapping::Builtin(&["edit", "write"])
|
|
211
|
-
);
|
|
212
|
-
assert_eq!(
|
|
213
|
-
pi_tool_mapping("execute_bash"),
|
|
214
|
-
PiToolMapping::Builtin(&["bash"])
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
for unsupported in ["git_diff", "network", "provider_builtin", "not_a_tool"] {
|
|
218
|
-
assert_eq!(
|
|
219
|
-
pi_tool_mapping(unsupported),
|
|
220
|
-
PiToolMapping::Unsupported,
|
|
221
|
-
"unknown categories must not silently broaden to bash"
|
|
222
|
-
);
|
|
223
|
-
assert!(
|
|
224
|
-
command(
|
|
225
|
-
&["mcp_team", unsupported],
|
|
226
|
-
ProviderEffort::Medium,
|
|
227
|
-
"team-agent/qwen3.8-27b"
|
|
228
|
-
)
|
|
229
|
-
.is_err(),
|
|
230
|
-
"unsupported tool must refuse the command: {unsupported}"
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
|
|
193
|
+
fn pi_role_command_ignores_legacy_tool_metadata() {
|
|
234
194
|
let argv = command(
|
|
235
195
|
&["mcp_team", "fs_write", "fs_read", "fs_list", "execute_bash"],
|
|
236
196
|
ProviderEffort::Medium,
|
|
@@ -242,34 +202,3 @@ fn pi_role_tools_validate_team_capabilities_without_replacing_direct_pi_tools()
|
|
|
242
202
|
"role tools validate Team Agent capabilities but must not replace direct Pi tools: {argv:?}"
|
|
243
203
|
);
|
|
244
204
|
}
|
|
245
|
-
|
|
246
|
-
#[test]
|
|
247
|
-
fn pi_role_rejects_unsupported_categories_during_compile() {
|
|
248
|
-
compile_pi_role(&valid_role_with("")).expect("supported Pi tools must compile");
|
|
249
|
-
|
|
250
|
-
for unsupported in ["git_diff", "network", "provider_builtin"] {
|
|
251
|
-
let role = valid_role_with("").replace(
|
|
252
|
-
" - execute_bash\n",
|
|
253
|
-
&format!(" - execute_bash\n - {unsupported}\n"),
|
|
254
|
-
);
|
|
255
|
-
let error = compile_error(compile_pi_role(&role)).to_string();
|
|
256
|
-
assert!(
|
|
257
|
-
error.contains(&format!("Pi does not support Team Agent tool category {unsupported:?}")),
|
|
258
|
-
"compile must reject {unsupported} before launch preparation: {error}"
|
|
259
|
-
);
|
|
260
|
-
assert!(
|
|
261
|
-
error.contains("remove it from the role's tools"),
|
|
262
|
-
"compile error must provide an actionable fix: {error}"
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
let alias = valid_role_with("").replace(
|
|
267
|
-
" - execute_bash\n",
|
|
268
|
-
" - execute_bash\n - \"@builtin\"\n",
|
|
269
|
-
);
|
|
270
|
-
let error = compile_error(compile_pi_role(&alias)).to_string();
|
|
271
|
-
assert!(
|
|
272
|
-
error.contains("Pi does not support Team Agent tool category \"provider_builtin\""),
|
|
273
|
-
"@builtin must use the same Pi capability contract: {error}"
|
|
274
|
-
);
|
|
275
|
-
}
|
|
@@ -572,7 +572,6 @@ fn pi_materializer_and_worker_routes_body(hermetic: &HermeticTestEnv) {
|
|
|
572
572
|
model: None,
|
|
573
573
|
effort: None,
|
|
574
574
|
system_prompt: "native leader fixture",
|
|
575
|
-
tool_categories: &["mcp_team"],
|
|
576
575
|
team_mcp_tools: &["send_message"],
|
|
577
576
|
mcp_config: &native_config,
|
|
578
577
|
session_scope: PiSessionScope::NativeDefault,
|
|
@@ -605,7 +604,6 @@ fn pi_materializer_and_worker_routes_body(hermetic: &HermeticTestEnv) {
|
|
|
605
604
|
model: Some("team-agent/qwen3.8-27b"),
|
|
606
605
|
effort: Some(ProviderEffort::Medium),
|
|
607
606
|
system_prompt: "isolated worker fixture",
|
|
608
|
-
tool_categories: &["mcp_team"],
|
|
609
607
|
team_mcp_tools: &["send_message"],
|
|
610
608
|
mcp_config: &isolated_config,
|
|
611
609
|
session_scope: PiSessionScope::Isolated,
|
|
@@ -633,7 +631,6 @@ fn pi_materializer_and_worker_routes_body(hermetic: &HermeticTestEnv) {
|
|
|
633
631
|
model: None,
|
|
634
632
|
effort: None,
|
|
635
633
|
system_prompt: "isolated resume fixture",
|
|
636
|
-
tool_categories: &["mcp_team"],
|
|
637
634
|
team_mcp_tools: &["send_message"],
|
|
638
635
|
mcp_config: &isolated_config,
|
|
639
636
|
session_scope: PiSessionScope::Isolated,
|
|
@@ -664,7 +661,6 @@ fn pi_materializer_and_worker_routes_body(hermetic: &HermeticTestEnv) {
|
|
|
664
661
|
model: None,
|
|
665
662
|
effort: None,
|
|
666
663
|
system_prompt: "native resume fixture",
|
|
667
|
-
tool_categories: &["mcp_team"],
|
|
668
664
|
team_mcp_tools: &["send_message"],
|
|
669
665
|
mcp_config: &native_config,
|
|
670
666
|
session_scope: PiSessionScope::NativeDefault,
|
|
@@ -699,7 +695,6 @@ fn pi_materializer_and_worker_routes_body(hermetic: &HermeticTestEnv) {
|
|
|
699
695
|
model: None,
|
|
700
696
|
effort: None,
|
|
701
697
|
system_prompt: "isolated negative resume fixture",
|
|
702
|
-
tool_categories: &["mcp_team"],
|
|
703
698
|
team_mcp_tools: &["send_message"],
|
|
704
699
|
mcp_config: &isolated_config,
|
|
705
700
|
session_scope: PiSessionScope::Isolated,
|
|
@@ -58,7 +58,6 @@ fn resume_argv(path: &Path) -> Result<Vec<String>, ProviderError> {
|
|
|
58
58
|
model: Some("team-agent/qwen3.8-27b"),
|
|
59
59
|
effort: Some(ProviderEffort::Medium),
|
|
60
60
|
system_prompt: "worker contract",
|
|
61
|
-
tool_categories: &["mcp_team"],
|
|
62
61
|
session_dir: Some(Path::new(
|
|
63
62
|
"/workspace/.team/runtime/pi/team-a/worker-a/sessions",
|
|
64
63
|
)),
|
|
@@ -901,13 +901,13 @@ impl ProviderAdapter for StaticCaptureAdapter {
|
|
|
901
901
|
Ok(vec!["fake".to_string()])
|
|
902
902
|
}
|
|
903
903
|
|
|
904
|
-
fn
|
|
904
|
+
fn build_command_with_permissions(
|
|
905
905
|
&self,
|
|
906
906
|
auth_mode: AuthMode,
|
|
907
907
|
mcp_config: Option<&McpConfig>,
|
|
908
908
|
system_prompt: Option<&str>,
|
|
909
909
|
model: Option<&str>,
|
|
910
|
-
|
|
910
|
+
_dangerously_skip_permissions: bool,
|
|
911
911
|
) -> Result<Vec<String>, ProviderError> {
|
|
912
912
|
self.build_command(auth_mode, mcp_config, system_prompt, model)
|
|
913
913
|
}
|
|
@@ -959,7 +959,7 @@ impl ProviderAdapter for StaticCaptureAdapter {
|
|
|
959
959
|
mcp_config: Option<&McpConfig>,
|
|
960
960
|
_system_prompt: Option<&str>,
|
|
961
961
|
_model: Option<&str>,
|
|
962
|
-
|
|
962
|
+
_dangerously_skip_permissions: bool,
|
|
963
963
|
) -> Result<Vec<String>, ProviderError> {
|
|
964
964
|
self.build_resume_command(session_id, auth_mode, mcp_config)
|
|
965
965
|
}
|
|
@@ -980,7 +980,7 @@ impl ProviderAdapter for StaticCaptureAdapter {
|
|
|
980
980
|
mcp_config: Option<&McpConfig>,
|
|
981
981
|
_system_prompt: Option<&str>,
|
|
982
982
|
_model: Option<&str>,
|
|
983
|
-
|
|
983
|
+
_dangerously_skip_permissions: bool,
|
|
984
984
|
) -> Result<Vec<String>, ProviderError> {
|
|
985
985
|
self.fork(session_id, auth_mode, mcp_config)
|
|
986
986
|
}
|
|
@@ -64,7 +64,6 @@ mod cursor_require_explicit_model_red;
|
|
|
64
64
|
mod cursor_restart_resume_red;
|
|
65
65
|
mod display_adaptive_red;
|
|
66
66
|
mod f032_startup_prompt_best_effort_red;
|
|
67
|
-
mod g1_silent_faces;
|
|
68
67
|
mod grok_effort_argv_red;
|
|
69
68
|
mod grok_mcp_overlay_red;
|
|
70
69
|
mod grok_require_explicit_model_red;
|
|
@@ -540,8 +540,6 @@ pub struct LaunchReport {
|
|
|
540
540
|
pub tmux_endpoint: Option<String>,
|
|
541
541
|
/// 路由决策(每 task 一条;`routing.decision` 事件)。
|
|
542
542
|
pub routes: Vec<RoutingDecision>,
|
|
543
|
-
/// 权限摘要(每 agent 一条)。
|
|
544
|
-
pub permissions: Vec<PermissionSummary>,
|
|
545
543
|
/// leader receiver(attach 成功时;经 step10 leader::attach_leader_to_state)。
|
|
546
544
|
pub leader_receiver_attached: bool,
|
|
547
545
|
pub leader_bind_stage: Option<String>,
|
|
@@ -577,14 +575,6 @@ pub struct RoutingDecision {
|
|
|
577
575
|
pub manual_override: bool,
|
|
578
576
|
}
|
|
579
577
|
|
|
580
|
-
/// 权限摘要(`resolve_permissions(agent)` 的 typed 版)。**PLACEHOLDER 字段**:
|
|
581
|
-
/// 实际形状由 step6 compiler 的 `resolve_permissions` 决定,集成时映射。
|
|
582
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
583
|
-
pub struct PermissionSummary {
|
|
584
|
-
pub agent_id: AgentId,
|
|
585
|
-
pub raw: serde_json::Value,
|
|
586
|
-
}
|
|
587
|
-
|
|
588
578
|
/// BUG-7 (0.3.1): quick-start cannot honestly report "ready" before the workers'
|
|
589
579
|
/// MCP tool sets have actually loaded — provider-side schema rejections (codex
|
|
590
580
|
/// invalid_function_parameters etc.) happen AFTER spawn and silently disable the
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
//! ---
|
|
2
|
-
//! purpose: 从 spec 里的一个 agent 定义,编译出 worker
|
|
2
|
+
//! purpose: 从 spec 里的一个 agent 定义,编译出 worker 启动所需的系统提示词
|
|
3
3
|
//! contract:
|
|
4
4
|
//! provides:
|
|
5
5
|
//! - name: WorkerCommandAgent
|
|
6
6
|
//! what: 从 YAML 或 JSON 读出的单 agent 命令上下文
|
|
7
7
|
//! - name: compile_worker_system_prompt
|
|
8
8
|
//! what: 按身份、runtime 契约、通信模式、角色正文、输出契约、权限说明拼系统提示词
|
|
9
|
-
//! - name: resolved_tool_strings_for_command
|
|
10
|
-
//! what: 解析出该 agent 的工具串,声明了 bypass 时追加 dangerous_auto_approve 哨兵
|
|
11
9
|
//! depends:
|
|
12
|
-
//! - crate::model::permissions
|
|
13
10
|
//! - crate::communication_mode
|
|
14
|
-
//! - crate::provider::bypass_flags
|
|
15
11
|
//! boundary:
|
|
16
12
|
//! - 只产出字符串,不 spawn 进程、不写盘
|
|
17
13
|
//! - bypass 只认 agent 自身声明,不从 team/runtime/leader argv 继承
|
|
@@ -22,9 +18,7 @@ use std::path::Path;
|
|
|
22
18
|
|
|
23
19
|
use crate::communication_mode::CommunicationMode;
|
|
24
20
|
use crate::lifecycle::types::LifecycleError;
|
|
25
|
-
use crate::model::enums::
|
|
26
|
-
use crate::model::ids::AgentId;
|
|
27
|
-
use crate::model::permissions::{resolve_permissions, AgentPermissionInput};
|
|
21
|
+
use crate::model::enums::Provider;
|
|
28
22
|
|
|
29
23
|
const RUNTIME_CONTRACT_SECTION: &str = r#"# Team Agent Teammate Runtime Contract
|
|
30
24
|
|
|
@@ -57,7 +51,6 @@ pub(crate) struct WorkerCommandAgent {
|
|
|
57
51
|
id: Option<String>,
|
|
58
52
|
provider: Provider,
|
|
59
53
|
role: Option<String>,
|
|
60
|
-
declared_tools: Option<Vec<String>>,
|
|
61
54
|
system_prompt_inline: Option<String>,
|
|
62
55
|
system_prompt_file: Option<String>,
|
|
63
56
|
output_contract_format: Option<String>,
|
|
@@ -94,16 +87,6 @@ impl WorkerCommandAgent {
|
|
|
94
87
|
.get("role")
|
|
95
88
|
.and_then(crate::model::yaml::Value::as_str)
|
|
96
89
|
.map(str::to_string),
|
|
97
|
-
declared_tools: agent
|
|
98
|
-
.get("tools")
|
|
99
|
-
.and_then(crate::model::yaml::Value::as_list)
|
|
100
|
-
.map(|items| {
|
|
101
|
-
items
|
|
102
|
-
.iter()
|
|
103
|
-
.filter_map(crate::model::yaml::Value::as_str)
|
|
104
|
-
.map(str::to_string)
|
|
105
|
-
.collect()
|
|
106
|
-
}),
|
|
107
90
|
system_prompt_inline: system_prompt
|
|
108
91
|
.and_then(|prompt| prompt.get("inline"))
|
|
109
92
|
.and_then(crate::model::yaml::Value::as_str)
|
|
@@ -158,16 +141,6 @@ impl WorkerCommandAgent {
|
|
|
158
141
|
.get("role")
|
|
159
142
|
.and_then(serde_json::Value::as_str)
|
|
160
143
|
.map(str::to_string),
|
|
161
|
-
declared_tools: agent
|
|
162
|
-
.get("tools")
|
|
163
|
-
.and_then(serde_json::Value::as_array)
|
|
164
|
-
.map(|items| {
|
|
165
|
-
items
|
|
166
|
-
.iter()
|
|
167
|
-
.filter_map(serde_json::Value::as_str)
|
|
168
|
-
.map(str::to_string)
|
|
169
|
-
.collect()
|
|
170
|
-
}),
|
|
171
144
|
system_prompt_inline: system_prompt
|
|
172
145
|
.and_then(|prompt| prompt.get("inline"))
|
|
173
146
|
.and_then(serde_json::Value::as_str)
|
|
@@ -233,9 +206,6 @@ pub(crate) fn compile_worker_system_prompt(
|
|
|
233
206
|
if let Some(contract) = output_contract(agent, &report_result) {
|
|
234
207
|
chunks.push(contract);
|
|
235
208
|
}
|
|
236
|
-
if let Some(notes) = permission_notes(agent)? {
|
|
237
|
-
chunks.push(notes);
|
|
238
|
-
}
|
|
239
209
|
Ok(chunks
|
|
240
210
|
.into_iter()
|
|
241
211
|
.filter(|chunk| !chunk.is_empty())
|
|
@@ -254,43 +224,6 @@ Do not invent a second team protocol or assume that a configured lazy MCP server
|
|
|
254
224
|
.to_string()
|
|
255
225
|
}
|
|
256
226
|
|
|
257
|
-
/// ---
|
|
258
|
-
/// purpose: 解析该 agent 最终生效的工具串
|
|
259
|
-
/// params:
|
|
260
|
-
/// provider: 用于查 bypass argv 定义的 provider
|
|
261
|
-
/// returns: 排序后的工具串;agent 声明 bypass 时末尾追加 dangerous_auto_approve
|
|
262
|
-
/// errors: 权限解析失败,或声明了 bypass 但该 provider 没有 bypass argv 定义时返回 RequirementUnmet
|
|
263
|
-
/// ---
|
|
264
|
-
pub(crate) fn resolved_tool_strings_for_command(
|
|
265
|
-
agent: &WorkerCommandAgent,
|
|
266
|
-
provider: Provider,
|
|
267
|
-
) -> Result<Vec<String>, LifecycleError> {
|
|
268
|
-
let mut tools: Vec<String> = resolve_agent_permissions(agent, provider)?
|
|
269
|
-
.sorted_tool_strings()
|
|
270
|
-
.into_iter()
|
|
271
|
-
.map(str::to_string)
|
|
272
|
-
.collect();
|
|
273
|
-
// 0.5.66 bypass 单源:读 agent 自己的 `dangerously_skip_permissions` bool,
|
|
274
|
-
// 不再收 team/runtime/leader argv 派生的 `DangerousApproval`。true → 加
|
|
275
|
-
// `dangerous_auto_approve` 哨兵(adapter 消费点不变);provider 无 bypass
|
|
276
|
-
// argv 定义 → fail-loud,不静默 fallback。
|
|
277
|
-
if agent.dangerously_skip_permissions && provider != Provider::Pi {
|
|
278
|
-
let flag = crate::provider::bypass_flags::provider_bypass_flag(provider).ok_or_else(
|
|
279
|
-
|| {
|
|
280
|
-
LifecycleError::RequirementUnmet(format!(
|
|
281
|
-
"provider {} has no bypass argv flag defined; dangerously_skip_permissions=true cannot be honored",
|
|
282
|
-
provider_display_name(provider)
|
|
283
|
-
))
|
|
284
|
-
},
|
|
285
|
-
)?;
|
|
286
|
-
debug_assert!(flag.starts_with("--"));
|
|
287
|
-
if !tools.iter().any(|tool| tool == "dangerous_auto_approve") {
|
|
288
|
-
tools.push("dangerous_auto_approve".to_string());
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
Ok(tools)
|
|
292
|
-
}
|
|
293
|
-
|
|
294
227
|
/// Provider-facing MCP tool name. Only verified call forms are filled in.
|
|
295
228
|
/// Unverified providers keep the historical dotted spelling.
|
|
296
229
|
fn mcp_tool_name(provider: Provider, server: &str, tool: &str) -> String {
|
|
@@ -308,33 +241,6 @@ fn mcp_tool_name(provider: Provider, server: &str, tool: &str) -> String {
|
|
|
308
241
|
}
|
|
309
242
|
}
|
|
310
243
|
|
|
311
|
-
fn provider_display_name(provider: Provider) -> &'static str {
|
|
312
|
-
match provider {
|
|
313
|
-
Provider::Claude => "claude",
|
|
314
|
-
Provider::ClaudeCode => "claude_code",
|
|
315
|
-
Provider::Codex => "codex",
|
|
316
|
-
Provider::Copilot => "copilot",
|
|
317
|
-
Provider::GeminiCli => "gemini_cli",
|
|
318
|
-
Provider::Grok => "grok",
|
|
319
|
-
Provider::CursorAgent => "cursor_agent",
|
|
320
|
-
Provider::Pi => "pi",
|
|
321
|
-
Provider::Fake => "fake",
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
fn resolve_agent_permissions(
|
|
326
|
-
agent: &WorkerCommandAgent,
|
|
327
|
-
provider: Provider,
|
|
328
|
-
) -> Result<crate::model::permissions::ResolvedPermissions, LifecycleError> {
|
|
329
|
-
resolve_permissions(&AgentPermissionInput {
|
|
330
|
-
id: agent.id.as_deref().map(AgentId::new),
|
|
331
|
-
provider,
|
|
332
|
-
role: agent.role.clone(),
|
|
333
|
-
tools: agent.declared_tools.clone(),
|
|
334
|
-
})
|
|
335
|
-
.map_err(|e| LifecycleError::Compile(e.to_string()))
|
|
336
|
-
}
|
|
337
|
-
|
|
338
244
|
fn runtime_contract_section(send_message: &str, report_result: &str) -> String {
|
|
339
245
|
RUNTIME_CONTRACT_SECTION
|
|
340
246
|
.replace("{send_message}", send_message)
|
|
@@ -405,161 +311,14 @@ fn output_contract(agent: &WorkerCommandAgent, report_result: &str) -> Option<St
|
|
|
405
311
|
.then(|| RESULT_ENVELOPE_OUTPUT_CONTRACT.replace("{report_result}", report_result))
|
|
406
312
|
}
|
|
407
313
|
|
|
408
|
-
fn permission_notes(agent: &WorkerCommandAgent) -> Result<Option<String>, LifecycleError> {
|
|
409
|
-
let permissions = resolve_agent_permissions(agent, agent.provider)?;
|
|
410
|
-
// C-2-1/C-2-2 cr verdict — Copilot 一期 framework 不替决 fs_read/fs_list/git_diff/
|
|
411
|
-
// provider_builtin(provider prompt 控);为诚实(MUST-NOT-13)在 system prompt 内
|
|
412
|
-
// 总是声明这些 provider-level prompt_only 工具,即便角色未显式声明。
|
|
413
|
-
let provider_prompt_only_extras = provider_default_prompt_only_tools(agent.provider);
|
|
414
|
-
let mut prompt_only: std::collections::BTreeSet<String> = permissions
|
|
415
|
-
.resolved_tools
|
|
416
|
-
.iter()
|
|
417
|
-
.filter(|tool| tool.enforcement == Enforcement::PromptOnly)
|
|
418
|
-
.filter_map(|tool| serde_json::to_value(tool.tool).ok())
|
|
419
|
-
.filter_map(|value| value.as_str().map(str::to_string))
|
|
420
|
-
.collect();
|
|
421
|
-
for tool in provider_prompt_only_extras {
|
|
422
|
-
prompt_only.insert((*tool).to_string());
|
|
423
|
-
}
|
|
424
|
-
if prompt_only.is_empty() {
|
|
425
|
-
return Ok(None);
|
|
426
|
-
}
|
|
427
|
-
let prompt_only: Vec<String> = prompt_only.into_iter().collect();
|
|
428
|
-
Ok(Some(format!(
|
|
429
|
-
"Permission note: these tools are prompt-only for this provider and not hard-enforced: {}",
|
|
430
|
-
prompt_only.join(", ")
|
|
431
|
-
)))
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/// C-2-1 cr verdict — provider-level prompt_only tools that the framework cannot
|
|
435
|
-
/// hard-enforce. The system prompt declares them so the worker is honest about
|
|
436
|
-
/// where consent gates actually live (provider prompt vs framework).
|
|
437
|
-
fn provider_default_prompt_only_tools(provider: Provider) -> &'static [&'static str] {
|
|
438
|
-
match provider {
|
|
439
|
-
// C-2-1: fs_read / fs_list / git_diff / provider_builtin 由 provider prompt
|
|
440
|
-
// 控制,framework 不替决(prompt_only 诚实声明)。
|
|
441
|
-
Provider::Copilot => &["fs_list", "fs_read", "git_diff", "provider_builtin"],
|
|
442
|
-
_ => &[],
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
314
|
#[cfg(test)]
|
|
447
315
|
mod tests {
|
|
448
316
|
use super::*;
|
|
449
317
|
|
|
450
|
-
#[test]
|
|
451
|
-
fn empty_tools_use_role_defaults_and_aliases_resolve_before_command() {
|
|
452
|
-
let agent = WorkerCommandAgent {
|
|
453
|
-
id: Some("dev".to_string()),
|
|
454
|
-
provider: Provider::ClaudeCode,
|
|
455
|
-
role: Some("developer".to_string()),
|
|
456
|
-
declared_tools: Some(Vec::new()),
|
|
457
|
-
system_prompt_inline: None,
|
|
458
|
-
system_prompt_file: None,
|
|
459
|
-
output_contract_format: None,
|
|
460
|
-
communication_mode: CommunicationMode::default(),
|
|
461
|
-
dangerously_skip_permissions: false,
|
|
462
|
-
};
|
|
463
|
-
let tools =
|
|
464
|
-
resolved_tool_strings_for_command(&agent, Provider::ClaudeCode).unwrap();
|
|
465
|
-
assert_eq!(
|
|
466
|
-
tools,
|
|
467
|
-
[
|
|
468
|
-
"execute_bash",
|
|
469
|
-
"fs_list",
|
|
470
|
-
"fs_read",
|
|
471
|
-
"fs_write",
|
|
472
|
-
"git_diff",
|
|
473
|
-
"mcp_team",
|
|
474
|
-
"provider_builtin"
|
|
475
|
-
]
|
|
476
|
-
);
|
|
477
|
-
|
|
478
|
-
let agent = WorkerCommandAgent {
|
|
479
|
-
declared_tools: Some(vec!["fs_*".to_string(), "@team-orchestrator".to_string()]),
|
|
480
|
-
..agent
|
|
481
|
-
};
|
|
482
|
-
let tools =
|
|
483
|
-
resolved_tool_strings_for_command(&agent, Provider::ClaudeCode).unwrap();
|
|
484
|
-
assert_eq!(tools, ["fs_list", "fs_read", "fs_write", "mcp_team"]);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// 0.5.66 bypass 单源 §4.1:true → tools 追加 `dangerous_auto_approve` 哨兵。
|
|
488
|
-
#[test]
|
|
489
|
-
fn test_true_field_produces_bypass_flag_argv() {
|
|
490
|
-
let agent = WorkerCommandAgent {
|
|
491
|
-
id: Some("dev".to_string()),
|
|
492
|
-
provider: Provider::ClaudeCode,
|
|
493
|
-
role: Some("developer".to_string()),
|
|
494
|
-
declared_tools: Some(vec!["mcp_team".to_string()]),
|
|
495
|
-
system_prompt_inline: None,
|
|
496
|
-
system_prompt_file: None,
|
|
497
|
-
output_contract_format: None,
|
|
498
|
-
communication_mode: CommunicationMode::default(),
|
|
499
|
-
dangerously_skip_permissions: true,
|
|
500
|
-
};
|
|
501
|
-
let tools = resolved_tool_strings_for_command(&agent, Provider::ClaudeCode).unwrap();
|
|
502
|
-
assert!(
|
|
503
|
-
tools.iter().any(|tool| tool == "dangerous_auto_approve"),
|
|
504
|
-
"true field must append the dangerous_auto_approve sentinel; tools={tools:?}"
|
|
505
|
-
);
|
|
506
|
-
assert!(
|
|
507
|
-
tools.iter().any(|tool| tool == "mcp_team"),
|
|
508
|
-
"normal tools must remain; tools={tools:?}"
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// 0.5.66 bypass 单源 §4.1:false → 不追加哨兵。
|
|
513
|
-
#[test]
|
|
514
|
-
fn test_false_field_produces_no_bypass_flag_argv() {
|
|
515
|
-
let agent = WorkerCommandAgent {
|
|
516
|
-
id: Some("dev".to_string()),
|
|
517
|
-
provider: Provider::ClaudeCode,
|
|
518
|
-
role: Some("developer".to_string()),
|
|
519
|
-
declared_tools: Some(vec!["mcp_team".to_string()]),
|
|
520
|
-
system_prompt_inline: None,
|
|
521
|
-
system_prompt_file: None,
|
|
522
|
-
output_contract_format: None,
|
|
523
|
-
communication_mode: CommunicationMode::default(),
|
|
524
|
-
dangerously_skip_permissions: false,
|
|
525
|
-
};
|
|
526
|
-
let tools = resolved_tool_strings_for_command(&agent, Provider::ClaudeCode).unwrap();
|
|
527
|
-
assert!(
|
|
528
|
-
!tools.iter().any(|tool| tool == "dangerous_auto_approve"),
|
|
529
|
-
"false field must not append the sentinel; tools={tools:?}"
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// 0.5.66 bypass 单源 §4.1:provider 未定义 bypass 参数 + true → fail-loud。
|
|
534
|
-
#[test]
|
|
535
|
-
fn test_provider_without_bypass_flag_definition_errors() {
|
|
536
|
-
let agent = WorkerCommandAgent {
|
|
537
|
-
id: Some("dev".to_string()),
|
|
538
|
-
provider: Provider::GeminiCli,
|
|
539
|
-
role: Some("developer".to_string()),
|
|
540
|
-
declared_tools: Some(vec!["mcp_team".to_string()]),
|
|
541
|
-
system_prompt_inline: None,
|
|
542
|
-
system_prompt_file: None,
|
|
543
|
-
output_contract_format: None,
|
|
544
|
-
communication_mode: CommunicationMode::default(),
|
|
545
|
-
dangerously_skip_permissions: true,
|
|
546
|
-
};
|
|
547
|
-
let err = resolved_tool_strings_for_command(&agent, Provider::GeminiCli).unwrap_err();
|
|
548
|
-
let msg = err.to_string();
|
|
549
|
-
assert!(
|
|
550
|
-
msg.contains("no bypass argv flag defined"),
|
|
551
|
-
"provider without bypass flag + true must fail-loud; got {msg}"
|
|
552
|
-
);
|
|
553
|
-
assert!(
|
|
554
|
-
msg.contains("gemini_cli"),
|
|
555
|
-
"error must name the provider; got {msg}"
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
318
|
#[test]
|
|
560
319
|
fn system_prompt_uses_identity_then_runtime_contract_python_order() {
|
|
561
320
|
// #264 D6: Python truth source (prompt.py:39, live ps confirmed) builds
|
|
562
|
-
// chunks = [identity, TEAMMATE_SYSTEM_PROMPT, role_body, output
|
|
321
|
+
// chunks = [identity, TEAMMATE_SYSTEM_PROMPT, role_body, output].
|
|
563
322
|
// The previous assertion locked the inverted contract-first order with no
|
|
564
323
|
// Python evidence; this is the corrected golden.
|
|
565
324
|
// 0.3.5 union (copilot v2 C-1 / B2 MUST-4 行为层守): identity 必须 FIRST —
|
|
@@ -569,7 +328,6 @@ mod tests {
|
|
|
569
328
|
id: Some("coder".to_string()),
|
|
570
329
|
provider: Provider::Codex,
|
|
571
330
|
role: Some("Runtime Developer".to_string()),
|
|
572
|
-
declared_tools: Some(vec!["mcp_team".to_string()]),
|
|
573
331
|
system_prompt_inline: Some("Implement the assigned slice.".to_string()),
|
|
574
332
|
system_prompt_file: None,
|
|
575
333
|
output_contract_format: Some("result_envelope_v1".to_string()),
|
|
@@ -590,8 +348,7 @@ mod tests {
|
|
|
590
348
|
let output = prompt
|
|
591
349
|
.find("Final completion must call team_orchestrator.report_result exactly once")
|
|
592
350
|
.unwrap();
|
|
593
|
-
|
|
594
|
-
assert!(identity < runtime && runtime < role && role < output && output < permissions);
|
|
351
|
+
assert!(identity < runtime && runtime < role && role < output);
|
|
595
352
|
let slowdown_phrase = format!("500/{}", 500 + 29);
|
|
596
353
|
assert!(prompt.contains(&slowdown_phrase));
|
|
597
354
|
assert!(prompt.contains("Runtime Developer"));
|
|
@@ -605,7 +362,6 @@ mod tests {
|
|
|
605
362
|
id: Some("worker".to_string()),
|
|
606
363
|
provider,
|
|
607
364
|
role: Some("developer".to_string()),
|
|
608
|
-
declared_tools: Some(vec!["mcp_team".to_string()]),
|
|
609
365
|
system_prompt_inline: Some("worker body".to_string()),
|
|
610
366
|
system_prompt_file: None,
|
|
611
367
|
output_contract_format: Some("result_envelope_v1".to_string()),
|