@team-agent/installer 0.5.85 → 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/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 +0 -58
- 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/restart/common.rs +4 -8
- 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 +30 -44
- 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/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 +8 -41
- 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/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
|
@@ -17,7 +17,6 @@ use std::process::Command;
|
|
|
17
17
|
|
|
18
18
|
use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
|
|
19
19
|
use crate::model::ids::AgentId;
|
|
20
|
-
use crate::model::permissions::{self, AgentPermissionInput};
|
|
21
20
|
use crate::model::yaml::{self, Value};
|
|
22
21
|
use crate::state::persist::load_runtime_state;
|
|
23
22
|
use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
|
|
@@ -133,13 +132,6 @@ pub fn launch_with_transport_in_workspace(
|
|
|
133
132
|
session_name.as_str()
|
|
134
133
|
)));
|
|
135
134
|
}
|
|
136
|
-
let permissions = spec_agents(&spec)
|
|
137
|
-
.into_iter()
|
|
138
|
-
.map(|agent| PermissionSummary {
|
|
139
|
-
agent_id: agent,
|
|
140
|
-
raw: serde_json::json!({"source": "compiled_spec"}),
|
|
141
|
-
})
|
|
142
|
-
.collect::<Vec<_>>();
|
|
143
135
|
let routes = spec_routes(&spec);
|
|
144
136
|
let started = if dry_run {
|
|
145
137
|
Vec::new()
|
|
@@ -208,7 +200,6 @@ pub fn launch_with_transport_in_workspace(
|
|
|
208
200
|
dry_run,
|
|
209
201
|
tmux_endpoint: transport.tmux_endpoint(),
|
|
210
202
|
routes,
|
|
211
|
-
permissions,
|
|
212
203
|
leader_receiver_attached: false,
|
|
213
204
|
leader_bind_stage: None,
|
|
214
205
|
leader_bind_reason: None,
|
|
@@ -301,11 +301,6 @@ pub(super) fn spawn_agent_window(
|
|
|
301
301
|
)?;
|
|
302
302
|
let system_prompt =
|
|
303
303
|
crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
|
|
304
|
-
let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
|
|
305
|
-
&command_agent,
|
|
306
|
-
provider,
|
|
307
|
-
)?;
|
|
308
|
-
let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
|
|
309
304
|
// owner_team_id resolution priority (Issue 2 fix):
|
|
310
305
|
// 1. caller's explicit override (restart paths pass `selected.team_key`)
|
|
311
306
|
// 2. agent row's persisted `owner_team_id` (set by prior launch/restart)
|
|
@@ -374,7 +369,10 @@ pub(super) fn spawn_agent_window(
|
|
|
374
369
|
mcp_config: Some(&mcp_config),
|
|
375
370
|
system_prompt: Some(system_prompt.as_str()),
|
|
376
371
|
model: command_model,
|
|
377
|
-
|
|
372
|
+
dangerously_skip_permissions: agent
|
|
373
|
+
.get("dangerously_skip_permissions")
|
|
374
|
+
.and_then(serde_json::Value::as_bool)
|
|
375
|
+
.unwrap_or(false),
|
|
378
376
|
profile_launch: Some(&profile_launch),
|
|
379
377
|
agent_id_hint: Some(agent_id.as_str()),
|
|
380
378
|
effort: restart_effort,
|
|
@@ -399,7 +397,6 @@ pub(super) fn spawn_agent_window(
|
|
|
399
397
|
model: command_model,
|
|
400
398
|
effort: restart_effort,
|
|
401
399
|
system_prompt: &system_prompt,
|
|
402
|
-
tool_categories: &resolved_tool_refs,
|
|
403
400
|
team_mcp_tools: &["send_message", "report_result"],
|
|
404
401
|
mcp_config: &mcp_config,
|
|
405
402
|
session_scope: crate::lifecycle::launch::pi_mcp::PiSessionScope::Isolated,
|
|
@@ -744,7 +741,6 @@ fn merge_command_context_fields(
|
|
|
744
741
|
};
|
|
745
742
|
for field in [
|
|
746
743
|
"role",
|
|
747
|
-
"tools",
|
|
748
744
|
"system_prompt",
|
|
749
745
|
"output_contract",
|
|
750
746
|
"provider",
|
|
@@ -97,15 +97,13 @@ fn managed_profile_command_plan_omits_mcp_config_even_when_mcp_config_is_availab
|
|
|
97
97
|
}
|
|
98
98
|
}),
|
|
99
99
|
};
|
|
100
|
-
let tools = ["mcp_team"];
|
|
101
|
-
|
|
102
100
|
let plan = get_adapter(Provider::Claude)
|
|
103
101
|
.build_command_plan(ProviderCommandContext {
|
|
104
102
|
auth_mode: AuthMode::CompatibleApi,
|
|
105
103
|
mcp_config: Some(&config),
|
|
106
104
|
system_prompt: Some("worker prompt requiring native MCP"),
|
|
107
105
|
model: Some("profile-effective-haiku"),
|
|
108
|
-
|
|
106
|
+
dangerously_skip_permissions: false,
|
|
109
107
|
profile_launch: Some(&profile),
|
|
110
108
|
agent_id_hint: None,
|
|
111
109
|
effort: None,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
//! ---
|
|
2
|
-
//! purpose: 回归——clone-agent
|
|
2
|
+
//! purpose: 回归——clone-agent 分身保留源席 tools,编译 spec 不投影 tools
|
|
3
3
|
//! contract:
|
|
4
4
|
//! provides:
|
|
5
5
|
//! - name: clone_agent_preserves_source_tools
|
|
6
|
-
//! what: 真跑 clone-agent 后,dynamic-role-files
|
|
6
|
+
//! what: 真跑 clone-agent 后,dynamic-role-files 保留源席 tools,runtime spec 不投影 tools
|
|
7
7
|
//! requires:
|
|
8
8
|
//! - name: source-six-set-vs-leader-three-set
|
|
9
9
|
//! what: 同一 team 里 leader 三件套 + 源席六件套的共享冲突面
|
|
@@ -275,6 +275,10 @@ fn role_tools(path: &Path) -> BTreeSet<String> {
|
|
|
275
275
|
.collect()
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
fn set_of(items: &[&str]) -> BTreeSet<String> {
|
|
279
|
+
items.iter().map(|s| (*s).to_string()).collect()
|
|
280
|
+
}
|
|
281
|
+
|
|
278
282
|
fn load_runtime_spec(workspace: &Path) -> Value {
|
|
279
283
|
let runtime = workspace.join(".team").join("runtime");
|
|
280
284
|
let spec = std::fs::read_dir(&runtime)
|
|
@@ -287,17 +291,7 @@ fn load_runtime_spec(workspace: &Path) -> Value {
|
|
|
287
291
|
.expect("parse spec")
|
|
288
292
|
}
|
|
289
293
|
|
|
290
|
-
fn
|
|
291
|
-
node.get("tools")
|
|
292
|
-
.and_then(Value::as_list)
|
|
293
|
-
.unwrap_or_else(|| panic!("tools list missing"))
|
|
294
|
-
.iter()
|
|
295
|
-
.filter_map(Value::as_str)
|
|
296
|
-
.map(str::to_string)
|
|
297
|
-
.collect()
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
fn spec_tools(workspace: &Path, agent: &str) -> BTreeSet<String> {
|
|
294
|
+
fn spec_projects_tools(workspace: &Path, agent: &str) -> bool {
|
|
301
295
|
let parsed = load_runtime_spec(workspace);
|
|
302
296
|
let agents = parsed
|
|
303
297
|
.get("agents")
|
|
@@ -305,22 +299,18 @@ fn spec_tools(workspace: &Path, agent: &str) -> BTreeSet<String> {
|
|
|
305
299
|
.expect("spec agents");
|
|
306
300
|
for row in agents {
|
|
307
301
|
if row.get("id").and_then(Value::as_str) == Some(agent) {
|
|
308
|
-
return
|
|
302
|
+
return row.get("tools").is_some();
|
|
309
303
|
}
|
|
310
304
|
}
|
|
311
305
|
panic!("agent {agent} missing from runtime spec");
|
|
312
306
|
}
|
|
313
307
|
|
|
314
|
-
fn
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
fn set_of(items: &[&str]) -> BTreeSet<String> {
|
|
323
|
-
items.iter().map(|s| (*s).to_string()).collect()
|
|
308
|
+
fn leader_spec_projects_tools(workspace: &Path) -> bool {
|
|
309
|
+
load_runtime_spec(workspace)
|
|
310
|
+
.get("leader")
|
|
311
|
+
.expect("spec leader")
|
|
312
|
+
.get("tools")
|
|
313
|
+
.is_some()
|
|
324
314
|
}
|
|
325
315
|
|
|
326
316
|
fn clone_ok(case: &Case, source: &str, dest: &str, phase: &str) {
|
|
@@ -347,7 +337,7 @@ fn clone_ok(case: &Case, source: &str, dest: &str, phase: &str) {
|
|
|
347
337
|
);
|
|
348
338
|
}
|
|
349
339
|
|
|
350
|
-
/// clone 后分身 role
|
|
340
|
+
/// clone 后分身 role 保留源席 tools;编译 spec 不投影 tools。
|
|
351
341
|
#[test]
|
|
352
342
|
fn clone_agent_preserves_source_tools() {
|
|
353
343
|
let case = Case::start();
|
|
@@ -380,21 +370,18 @@ fn clone_agent_preserves_source_tools() {
|
|
|
380
370
|
String::from_utf8_lossy(&qs.stdout)
|
|
381
371
|
);
|
|
382
372
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
set_of(SIX),
|
|
387
|
-
"fixture source must be the six-set"
|
|
373
|
+
assert!(
|
|
374
|
+
!spec_projects_tools(&case.workspace, SOURCE),
|
|
375
|
+
"compiled source spec must not project tools metadata"
|
|
388
376
|
);
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
set_of(THREE),
|
|
393
|
-
"leader must stay the three-set ceiling so the conflict surface exists; leader={leader_spec:?}"
|
|
377
|
+
assert!(
|
|
378
|
+
!leader_spec_projects_tools(&case.workspace),
|
|
379
|
+
"compiled leader spec must not project tools metadata"
|
|
394
380
|
);
|
|
395
381
|
|
|
396
382
|
clone_ok(&case, SOURCE, CLONE, "first_clone");
|
|
397
383
|
|
|
384
|
+
let source_role = role_tools(&case.workspace.join("agents").join(format!("{SOURCE}.md")));
|
|
398
385
|
let clone_role = role_tools(
|
|
399
386
|
&case
|
|
400
387
|
.workspace
|
|
@@ -402,23 +389,32 @@ fn clone_agent_preserves_source_tools() {
|
|
|
402
389
|
.join("dynamic-role-files")
|
|
403
390
|
.join(format!("{CLONE}.md")),
|
|
404
391
|
);
|
|
405
|
-
let clone_spec = spec_tools(&case.workspace, CLONE);
|
|
406
392
|
assert_eq!(
|
|
407
|
-
clone_role,
|
|
408
|
-
"clone role tools must equal source; source={
|
|
393
|
+
clone_role, source_role,
|
|
394
|
+
"clone role tools must equal source; source={source_role:?} clone={clone_role:?}"
|
|
409
395
|
);
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
"clone spec
|
|
396
|
+
assert!(
|
|
397
|
+
!spec_projects_tools(&case.workspace, CLONE),
|
|
398
|
+
"compiled clone spec must not project tools metadata"
|
|
413
399
|
);
|
|
414
400
|
|
|
415
|
-
let
|
|
416
|
-
assert_eq!(
|
|
401
|
+
let narrow_source_role = role_tools(&case.workspace.join("agents").join(format!("{NARROW}.md")));
|
|
402
|
+
assert_eq!(narrow_source_role, set_of(THREE), "narrow source fixture");
|
|
417
403
|
clone_ok(&case, NARROW, NARROW_CLONE, "second_clone");
|
|
418
|
-
let
|
|
404
|
+
let narrow_clone_role = role_tools(
|
|
405
|
+
&case
|
|
406
|
+
.workspace
|
|
407
|
+
.join(".team")
|
|
408
|
+
.join("dynamic-role-files")
|
|
409
|
+
.join(format!("{NARROW_CLONE}.md")),
|
|
410
|
+
);
|
|
419
411
|
assert_eq!(
|
|
420
|
-
|
|
421
|
-
"narrow clone must keep the three-set (preserve, do not widen); got={
|
|
412
|
+
narrow_clone_role, narrow_source_role,
|
|
413
|
+
"narrow clone role must keep the three-set (preserve, do not widen); got={narrow_clone_role:?}"
|
|
414
|
+
);
|
|
415
|
+
assert!(
|
|
416
|
+
!spec_projects_tools(&case.workspace, NARROW_CLONE),
|
|
417
|
+
"compiled narrow clone spec must not project tools metadata"
|
|
422
418
|
);
|
|
423
419
|
|
|
424
420
|
case.shutdown();
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
//! 1. worker argv golden (§C1 + C-5-1/3) — copilot_argv_* tests
|
|
18
18
|
//! 2. per-worker AGENTS.md == compiled prompt — copilot_agents_md_* tests (C-1-1/2,
|
|
19
19
|
//! identity-first per the 0.3.4 B2/D6 lock)
|
|
20
|
-
//! 3.
|
|
20
|
+
//! 3. tools metadata transparency (C-2-1, no prompt permission note or argv deny flags)
|
|
21
21
|
//! 4. fork = structured CapabilityUnsupported (C-4-2/3)
|
|
22
22
|
//! 5. startup recognizer supports copilot trust/ready; turn-state Unknown never idles
|
|
23
23
|
//! (N11, C-3-1/4)
|
|
@@ -62,12 +62,12 @@ const ANCESTRY_KEY: &str = "TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON";
|
|
|
62
62
|
const NEUTRAL_ANCESTRY: &str = "[\"/bin/zsh\"]";
|
|
63
63
|
const COPILOT_TMP_PREFIX: &str = "ta-rs-copilot";
|
|
64
64
|
|
|
65
|
-
/// Face 1+3 (§C1 golden + C-5-3, verdict
|
|
66
|
-
///
|
|
67
|
-
///
|
|
68
|
-
///
|
|
69
|
-
///
|
|
70
|
-
///
|
|
65
|
+
/// Face 1+3 (§C1 golden + C-5-3, verdict case `copilot_argv_golden_per_role_tools`):
|
|
66
|
+
/// a copilot worker (mcp_team only) gets the full §C1 argv — noise control,
|
|
67
|
+
/// inline/`@file` MCP config carrying team_orchestrator, a pre-assigned
|
|
68
|
+
/// `--session-id` equal to the persisted `_pending_session_id`, and `-C <workspace>`.
|
|
69
|
+
/// Tools metadata must not derive deny flags; non-dangerous workers still omit the
|
|
70
|
+
/// --allow-all family flag.
|
|
71
71
|
#[test]
|
|
72
72
|
#[serial(env)]
|
|
73
73
|
fn copilot_argv_golden_restricted_role() {
|
|
@@ -176,21 +176,11 @@ team_orchestrator or @<.team/runtime/mcp/worker_a.json>; got {value:?}"
|
|
|
176
176
|
flag_value(argv, "-C")
|
|
177
177
|
));
|
|
178
178
|
}
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
for denied in ["shell", "write"] {
|
|
183
|
-
if !deny_tools.iter().any(|tool| tool.contains(denied)) {
|
|
184
|
-
failures.push(format!(
|
|
185
|
-
"C-5-3: restricted copilot worker must carry --deny-tool '{denied}'; deny={deny_tools:?}"
|
|
186
|
-
));
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
// RC-15 (C-5-2 v2): network deny is `--deny-tool 'url'` (the help has only shell/
|
|
190
|
-
// write/mcp/url tool kinds; v1's --deny-url is not the v2 form).
|
|
191
|
-
if !deny_tools.iter().any(|tool| tool.contains("url")) {
|
|
179
|
+
// Tools metadata is transparent compatibility data; it must not derive
|
|
180
|
+
// provider-specific deny flags (shell/write/url or any other category).
|
|
181
|
+
if argv.iter().any(|arg| arg == "--deny-tool") {
|
|
192
182
|
failures.push(format!(
|
|
193
|
-
"
|
|
183
|
+
"C-5-3: tools metadata must not derive copilot --deny-tool flags; argv={argv:?}"
|
|
194
184
|
));
|
|
195
185
|
}
|
|
196
186
|
// RC-10 (C-3-5/C-5-2): mcp_team is allowlisted by server name (no approval prompt).
|
|
@@ -289,8 +279,8 @@ used for the dangerous tier; argv={argv:?}"
|
|
|
289
279
|
/// the spawn env must carry COPILOT_CUSTOM_INSTRUCTIONS_DIRS pointing at the
|
|
290
280
|
/// per-worker dir (path contains the agent id; works with NO profile configured), the
|
|
291
281
|
/// AGENTS.md there must BE the compiled worker system prompt (single source: identity
|
|
292
|
-
/// first, runtime contract, role body, output contract,
|
|
293
|
-
///
|
|
282
|
+
/// first, runtime contract, role body, and output contract), and no global
|
|
283
|
+
/// ~/.copilot/AGENTS.md may be written.
|
|
294
284
|
#[test]
|
|
295
285
|
#[serial(env)]
|
|
296
286
|
fn copilot_agents_md_is_the_compiled_prompt_and_env_points_at_it() {
|
|
@@ -362,7 +352,6 @@ with the identity section FIRST; head={:?}",
|
|
|
362
352
|
"# Team Agent Teammate Runtime Contract",
|
|
363
353
|
"COPILOT ROLE BODY SENTINEL",
|
|
364
354
|
"report_result exactly once",
|
|
365
|
-
"Permission note: these tools are prompt-only for this provider and not hard-enforced:",
|
|
366
355
|
] {
|
|
367
356
|
if !agents_md.contains(marker) {
|
|
368
357
|
failures.push(format!(
|
|
@@ -386,14 +375,11 @@ with the identity section FIRST; head={:?}",
|
|
|
386
375
|
);
|
|
387
376
|
}
|
|
388
377
|
|
|
389
|
-
/// Face 3
|
|
390
|
-
///
|
|
391
|
-
/// prompt-only. Observable single-source: the compiled prompt's Permission note (built
|
|
392
|
-
/// from PROVIDER_ENFORCEMENT) for a full-capability role must list exactly
|
|
393
|
-
/// fs_list/fs_read/git_diff/provider_builtin (sorted).
|
|
378
|
+
/// Face 3: tools metadata is transparent compatibility data and must not produce
|
|
379
|
+
/// a provider-specific Permission note in the compiled Copilot prompt.
|
|
394
380
|
#[test]
|
|
395
381
|
#[serial(env)]
|
|
396
|
-
fn
|
|
382
|
+
fn copilot_prompt_omits_removed_permission_note() {
|
|
397
383
|
let _hermetic = enter_hermetic("copilot-permission-note");
|
|
398
384
|
let _guard = EnvGuard::set(&[(ANCESTRY_KEY, NEUTRAL_ANCESTRY)]);
|
|
399
385
|
let ws = tmp_ws("enforcement");
|
|
@@ -428,21 +414,21 @@ fn copilot_permission_note_registers_fs_tools_prompt_only() {
|
|
|
428
414
|
.env
|
|
429
415
|
.get("COPILOT_CUSTOM_INSTRUCTIONS_DIRS")
|
|
430
416
|
.cloned()
|
|
431
|
-
.
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
.
|
|
438
|
-
|
|
417
|
+
.expect("Copilot spawn must carry COPILOT_CUSTOM_INSTRUCTIONS_DIRS");
|
|
418
|
+
assert!(
|
|
419
|
+
!dir.is_empty(),
|
|
420
|
+
"Copilot instructions directory must not be empty"
|
|
421
|
+
);
|
|
422
|
+
let agents_md = std::fs::read_to_string(Path::new(&dir).join("AGENTS.md"))
|
|
423
|
+
.expect("per-worker Copilot AGENTS.md must be readable");
|
|
424
|
+
let identity = "You are Team Agent worker `worker_a` with role `Copilot Worker`.";
|
|
425
|
+
assert!(
|
|
426
|
+
agents_md.starts_with(identity) && agents_md.contains("COPILOT ROLE BODY SENTINEL"),
|
|
427
|
+
"C-2-1: compiled Copilot prompt must include its identity and role body; content={agents_md:?}"
|
|
428
|
+
);
|
|
439
429
|
assert!(
|
|
440
|
-
|
|
441
|
-
"C-2-1:
|
|
442
|
-
fs_read/fs_list/git_diff/provider_builtin as prompt-only (sorted in the Permission \
|
|
443
|
-
note; execute_bash/fs_write/network/mcp_team are hard); note={note_line:?} \
|
|
444
|
-
agents_md_present={}",
|
|
445
|
-
!agents_md.is_empty()
|
|
430
|
+
!agents_md.contains("Permission note:"),
|
|
431
|
+
"C-2-1: removed provider tool enforcement must not emit a Permission note; content={agents_md:?}"
|
|
446
432
|
);
|
|
447
433
|
}
|
|
448
434
|
|
|
@@ -37,7 +37,7 @@ fn bin() -> &'static str {
|
|
|
37
37
|
|
|
38
38
|
#[test]
|
|
39
39
|
#[serial(env)]
|
|
40
|
-
fn
|
|
40
|
+
fn claude_worker_spawn_argv_uses_compiled_prompt_without_tool_restrictions() {
|
|
41
41
|
let _hermetic = enter_hermetic("core034-claude-argv");
|
|
42
42
|
let _ancestry = EnvGuard::set(
|
|
43
43
|
"TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON",
|
|
@@ -80,7 +80,6 @@ fn claude_worker_spawn_argv_uses_compiled_prompt_and_resolved_role_tools() {
|
|
|
80
80
|
"send_message(to='leader'",
|
|
81
81
|
"report_result exactly once",
|
|
82
82
|
"ROLE BODY SENTINEL",
|
|
83
|
-
"Permission note",
|
|
84
83
|
] {
|
|
85
84
|
if !prompt.contains(marker) {
|
|
86
85
|
failures.push(format!("B2 missing compiled prompt marker {marker:?}"));
|
|
@@ -90,31 +89,15 @@ fn claude_worker_spawn_argv_uses_compiled_prompt_and_resolved_role_tools() {
|
|
|
90
89
|
failures.push("B2 prompt collapsed to the bare role label".to_string());
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"Bash",
|
|
96
|
-
"Read",
|
|
97
|
-
"Edit",
|
|
98
|
-
"Write",
|
|
99
|
-
"MultiEdit",
|
|
100
|
-
"NotebookEdit",
|
|
101
|
-
"Glob",
|
|
102
|
-
"Grep",
|
|
103
|
-
] {
|
|
104
|
-
if disallowed.iter().any(|tool| tool == forbidden) {
|
|
105
|
-
failures.push(format!(
|
|
106
|
-
"B1 declared capability still disallowed via --disallowedTools {forbidden:?}"
|
|
107
|
-
));
|
|
108
|
-
}
|
|
92
|
+
if argv_has_flag(&spawn.argv, "--disallowedTools") {
|
|
93
|
+
failures.push("B1 Claude argv must not derive --disallowedTools from tools metadata".to_string());
|
|
109
94
|
}
|
|
110
95
|
if argv_has_flag(&spawn.argv, "--allowedTools") {
|
|
111
|
-
failures.push(
|
|
112
|
-
"B1 Python parity violated: Claude argv must not switch to --allowedTools".to_string(),
|
|
113
|
-
);
|
|
96
|
+
failures.push("B1 Claude argv must not derive --allowedTools from tools metadata".to_string());
|
|
114
97
|
}
|
|
115
98
|
assert!(
|
|
116
99
|
failures.is_empty(),
|
|
117
|
-
"B1/B2 worker argv contract failed:\n{}\nprompt={prompt:?}\
|
|
100
|
+
"B1/B2 worker argv contract failed:\n{}\nprompt={prompt:?}\nargv={:?}",
|
|
118
101
|
failures.join("\n"),
|
|
119
102
|
spawn.argv
|
|
120
103
|
);
|
|
@@ -122,7 +105,7 @@ fn claude_worker_spawn_argv_uses_compiled_prompt_and_resolved_role_tools() {
|
|
|
122
105
|
|
|
123
106
|
#[test]
|
|
124
107
|
#[serial(env)]
|
|
125
|
-
fn
|
|
108
|
+
fn claude_control_role_does_not_derive_native_tool_restrictions() {
|
|
126
109
|
let _hermetic = enter_hermetic("core034-claude-control");
|
|
127
110
|
let _ancestry = EnvGuard::set(
|
|
128
111
|
"TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON",
|
|
@@ -156,29 +139,21 @@ fn claude_control_role_without_native_tools_still_disallows_native_tools() {
|
|
|
156
139
|
.expect("quick-start should record the control worker command");
|
|
157
140
|
|
|
158
141
|
let spawn = transport.single_spawn();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
"
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
] {
|
|
170
|
-
assert!(
|
|
171
|
-
disallowed.iter().any(|tool| tool == expected),
|
|
172
|
-
"B1 negative case: mcp_team-only control roles must still disallow native Claude tools; \
|
|
173
|
-
missing={expected} disallowed={disallowed:?}\nargv={:?}",
|
|
174
|
-
spawn.argv
|
|
175
|
-
);
|
|
176
|
-
}
|
|
142
|
+
assert!(
|
|
143
|
+
!argv_has_flag(&spawn.argv, "--disallowedTools"),
|
|
144
|
+
"B1 control role must not derive native-tool restrictions from tools metadata; argv={:?}",
|
|
145
|
+
spawn.argv
|
|
146
|
+
);
|
|
147
|
+
assert!(
|
|
148
|
+
!argv_has_flag(&spawn.argv, "--allowedTools"),
|
|
149
|
+
"B1 control role must not derive an allowlist from tools metadata; argv={:?}",
|
|
150
|
+
spawn.argv
|
|
151
|
+
);
|
|
177
152
|
}
|
|
178
153
|
|
|
179
154
|
#[test]
|
|
180
155
|
#[serial(env)]
|
|
181
|
-
fn
|
|
156
|
+
fn restart_claude_worker_does_not_derive_tool_restrictions_when_runtime_state_has_no_raw_tools() {
|
|
182
157
|
let _hermetic = enter_hermetic("core034-restart-tools");
|
|
183
158
|
let _ancestry = EnvGuard::set(
|
|
184
159
|
"TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON",
|
|
@@ -215,34 +190,20 @@ fn restart_claude_worker_resolves_tools_from_spec_when_runtime_state_has_no_raw_
|
|
|
215
190
|
let report = restart_with_transport(&ws, true, Some("restarttools"), &restart_transport)
|
|
216
191
|
.expect("restart should reach worker command construction");
|
|
217
192
|
let spawn = restart_transport.single_spawn();
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
"Edit",
|
|
224
|
-
"Write",
|
|
225
|
-
"MultiEdit",
|
|
226
|
-
"NotebookEdit",
|
|
227
|
-
"Glob",
|
|
228
|
-
"Grep",
|
|
229
|
-
] {
|
|
230
|
-
assert!(
|
|
231
|
-
!disallowed.iter().any(|tool| tool == forbidden),
|
|
232
|
-
"B1 restart contract: when runtime state lacks raw tools, restart must resolve tools from the selected spec/role through resolve_permissions. \
|
|
233
|
-
{forbidden} must not be disallowed for this role; disallowed={disallowed:?}\nargv={:?}\nreport={report:?}",
|
|
234
|
-
spawn.argv
|
|
235
|
-
);
|
|
236
|
-
}
|
|
193
|
+
assert!(
|
|
194
|
+
!argv_has_flag(&spawn.argv, "--disallowedTools"),
|
|
195
|
+
"B1 restart must not derive native-tool restrictions from tools metadata; argv={:?} report={report:?}",
|
|
196
|
+
spawn.argv
|
|
197
|
+
);
|
|
237
198
|
assert!(
|
|
238
199
|
!argv_has_flag(&spawn.argv, "--allowedTools"),
|
|
239
|
-
"B1
|
|
200
|
+
"B1 restart must not derive an allowlist from tools metadata; argv={:?} report={report:?}",
|
|
240
201
|
spawn.argv
|
|
241
202
|
);
|
|
242
203
|
}
|
|
243
204
|
|
|
244
205
|
#[test]
|
|
245
|
-
fn
|
|
206
|
+
fn worker_command_context_uses_single_prompt_without_permission_sources() {
|
|
246
207
|
let crate_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
247
208
|
let src_root = crate_root.join("src");
|
|
248
209
|
let launch = composite_source::composite_source("src/lifecycle/launch.rs");
|
|
@@ -276,10 +237,9 @@ fn worker_command_context_uses_single_prompt_and_permission_sources() {
|
|
|
276
237
|
&& all_src.contains("runtime_contract_section")
|
|
277
238
|
&& all_src.contains("identity_section")
|
|
278
239
|
&& all_src.contains("role_body")
|
|
279
|
-
&& all_src.contains("output_contract")
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
(runtime_contract_section, identity_section, role_body, output_contract, permission_notes)."
|
|
240
|
+
&& all_src.contains("output_contract"),
|
|
241
|
+
"B2 grep guard: worker command construction needs one compiled prompt helper with the CR-approved four sections \
|
|
242
|
+
(runtime_contract_section, identity_section, role_body, output_contract)."
|
|
283
243
|
);
|
|
284
244
|
assert!(
|
|
285
245
|
!command_sources.contains("system_prompt: role"),
|
|
@@ -296,8 +256,9 @@ fn worker_command_context_uses_single_prompt_and_permission_sources() {
|
|
|
296
256
|
);
|
|
297
257
|
|
|
298
258
|
assert!(
|
|
299
|
-
|
|
300
|
-
|
|
259
|
+
!command_sources.contains("resolve_permissions")
|
|
260
|
+
&& !command_sources.contains("resolved_tool_strings"),
|
|
261
|
+
"B1 grep guard: command construction must not derive argv restrictions from tools metadata."
|
|
301
262
|
);
|
|
302
263
|
assert!(
|
|
303
264
|
!command_sources.contains("worker_tool_refs(agent_tool_strings")
|
|
@@ -305,8 +266,8 @@ fn worker_command_context_uses_single_prompt_and_permission_sources() {
|
|
|
305
266
|
"B1 grep guard: command construction must not raw-pass agent.tools/tool_refs into ProviderCommandContext."
|
|
306
267
|
);
|
|
307
268
|
assert!(
|
|
308
|
-
adapter.contains("--disallowedTools") && !adapter.contains("--allowedTools"),
|
|
309
|
-
"B1
|
|
269
|
+
!adapter.contains("--disallowedTools") && !adapter.contains("--allowedTools"),
|
|
270
|
+
"B1 provider adapter must not derive Claude tool restrictions from tools metadata."
|
|
310
271
|
);
|
|
311
272
|
assert!(
|
|
312
273
|
!adapter.contains("resolve_permissions"),
|
|
@@ -837,12 +798,6 @@ fn flag_value(argv: &[String], flag: &str) -> Option<String> {
|
|
|
837
798
|
.find_map(|pair| (pair[0] == flag).then(|| pair[1].clone()))
|
|
838
799
|
}
|
|
839
800
|
|
|
840
|
-
fn flag_values(argv: &[String], flag: &str) -> Vec<String> {
|
|
841
|
-
argv.windows(2)
|
|
842
|
-
.filter_map(|pair| (pair[0] == flag).then(|| pair[1].clone()))
|
|
843
|
-
.collect()
|
|
844
|
-
}
|
|
845
|
-
|
|
846
801
|
fn argv_has_flag(argv: &[String], flag: &str) -> bool {
|
|
847
802
|
argv.iter().any(|arg| arg == flag)
|
|
848
803
|
}
|
|
@@ -520,8 +520,8 @@ fn launch_dry_run_resolves_real_plan_not_stub_error() {
|
|
|
520
520
|
Ok(report) => {
|
|
521
521
|
assert!(report.dry_run, "dry_run launch must report dry_run=true");
|
|
522
522
|
assert!(
|
|
523
|
-
!report.routes.is_empty()
|
|
524
|
-
"dry_run launch must resolve a real route
|
|
523
|
+
!report.routes.is_empty(),
|
|
524
|
+
"dry_run launch must resolve a real route plan from the compiled spec"
|
|
525
525
|
);
|
|
526
526
|
}
|
|
527
527
|
// any OTHER Err (a real requirement/transport error) still proves the stub is gone.
|
|
@@ -3210,7 +3210,6 @@ fn quick_start_running_agent_state_shape_after_spawn_is_golden() {
|
|
|
3210
3210
|
"profile",
|
|
3211
3211
|
"window",
|
|
3212
3212
|
"mcp_config",
|
|
3213
|
-
"permissions",
|
|
3214
3213
|
"effective_approval_policy",
|
|
3215
3214
|
// 0.5.66 bypass 单源:fresh spawn 记录运行面 bypass 值(restart drift 挡板)。
|
|
3216
3215
|
"as_launched_dangerously_skip_permissions",
|
|
@@ -3239,16 +3238,6 @@ fn quick_start_running_agent_state_shape_after_spawn_is_golden() {
|
|
|
3239
3238
|
.join(".team/runtime/mcp/implementer.json")
|
|
3240
3239
|
.to_string_lossy())
|
|
3241
3240
|
);
|
|
3242
|
-
assert_eq!(
|
|
3243
|
-
agent["permissions"],
|
|
3244
|
-
json!({
|
|
3245
|
-
"agent_id": "implementer",
|
|
3246
|
-
"provider": "codex",
|
|
3247
|
-
"tools": ["mcp_team"],
|
|
3248
|
-
"resolved_tools": [{"tool": "mcp_team", "enforcement": "prompt_only"}],
|
|
3249
|
-
"has_prompt_only": true
|
|
3250
|
-
})
|
|
3251
|
-
);
|
|
3252
3241
|
assert!(agent["session_id"].is_null());
|
|
3253
3242
|
assert!(agent["rollout_path"].is_null());
|
|
3254
3243
|
assert!(agent["captured_at"].is_null());
|
|
@@ -91,7 +91,6 @@ fn command_for_model(model: &str) -> Vec<String> {
|
|
|
91
91
|
model: Some(model),
|
|
92
92
|
effort: Some(ProviderEffort::Medium),
|
|
93
93
|
system_prompt: "worker contract",
|
|
94
|
-
tool_categories: &["mcp_team"],
|
|
95
94
|
session_dir: Some(Path::new("/workspace/.team/runtime/pi/t1/w1/sessions")),
|
|
96
95
|
session: PiSessionSelector::Fresh {
|
|
97
96
|
session_id: "716ba0cb-c491-4471-b41a-43c8d5e1f05a",
|
|
@@ -10,7 +10,6 @@ fn request(session: PiSessionSelector<'static>) -> PiCommandRequest<'static> {
|
|
|
10
10
|
model: Some("team-agent/qwen3.8-27b"),
|
|
11
11
|
effort: Some(ProviderEffort::High),
|
|
12
12
|
system_prompt: "frozen worker prompt",
|
|
13
|
-
tool_categories: &["mcp_team", "fs_read", "fs_write", "execute_bash"],
|
|
14
13
|
session_dir: Some(Path::new(
|
|
15
14
|
"/workspace/.team/runtime/pi/team-a/worker-a/sessions",
|
|
16
15
|
)),
|
|
@@ -38,7 +37,6 @@ fn leader_request() -> PiCommandRequest<'static> {
|
|
|
38
37
|
model: None,
|
|
39
38
|
effort: None,
|
|
40
39
|
system_prompt: "frozen leader prompt",
|
|
41
|
-
tool_categories: &["mcp_team", "fs_read", "fs_write", "execute_bash"],
|
|
42
40
|
session_dir: None,
|
|
43
41
|
session: PiSessionSelector::Fresh {
|
|
44
42
|
session_id: "da8c3622-2378-4d05-a26c-e826a6ef6d63",
|