@team-agent/installer 0.5.66 → 0.5.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +8 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +1 -0
- package/crates/team-agent/src/cli/adapters.rs +5 -0
- package/crates/team-agent/src/cli/diagnose.rs +82 -0
- package/crates/team-agent/src/cli/emit.rs +3 -3
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +21 -7
- package/crates/team-agent/src/cli/leaders.rs +125 -1
- package/crates/team-agent/src/cli/mod.rs +20 -0
- package/crates/team-agent/src/cli/send/presentation.rs +7 -1
- package/crates/team-agent/src/cli/spec.rs +2 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
- package/crates/team-agent/src/cli/tests/base.rs +26 -3
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
- package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
- package/crates/team-agent/src/communication_mode/mod.rs +6 -4
- package/crates/team-agent/src/compiler.rs +59 -0
- package/crates/team-agent/src/coordinator/backoff.rs +55 -0
- package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
- package/crates/team-agent/src/coordinator/health.rs +173 -0
- package/crates/team-agent/src/coordinator/mod.rs +31 -0
- package/crates/team-agent/src/coordinator/orphan.rs +31 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
- package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
- package/crates/team-agent/src/coordinator/tick.rs +133 -22
- package/crates/team-agent/src/coordinator/types.rs +49 -0
- package/crates/team-agent/src/db/message_store.rs +39 -5
- package/crates/team-agent/src/layout/worker_env.rs +20 -3
- package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
- package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
- package/crates/team-agent/src/leader/registry.rs +65 -0
- package/crates/team-agent/src/leader/start.rs +920 -63
- package/crates/team-agent/src/lifecycle/display.rs +56 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -12
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +62 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +59 -0
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
- package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -456
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
- package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +62 -2
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +139 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
- package/crates/team-agent/src/lifecycle/launch.rs +99 -29
- package/crates/team-agent/src/lifecycle/lock.rs +42 -1
- package/crates/team-agent/src/lifecycle/mod.rs +11 -0
- package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +124 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +369 -11
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
- package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
- package/crates/team-agent/src/lifecycle/restart.rs +29 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
- package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
- package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +2 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
- package/crates/team-agent/src/lifecycle/tests.rs +23 -13
- package/crates/team-agent/src/lifecycle/types.rs +61 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +85 -11
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
- package/crates/team-agent/src/mcp_server/tests.rs +196 -2
- package/crates/team-agent/src/messaging/delivery.rs +405 -40
- package/crates/team-agent/src/messaging/helpers.rs +2 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
- package/crates/team-agent/src/messaging/results.rs +4 -0
- package/crates/team-agent/src/messaging/send.rs +34 -0
- package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
- package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/types.rs +4 -1
- package/crates/team-agent/src/model/enums.rs +14 -5
- package/crates/team-agent/src/model/permissions.rs +3 -0
- package/crates/team-agent/src/os_probe.rs +73 -2
- package/crates/team-agent/src/provider/adapter.rs +231 -4
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +46 -2
- package/crates/team-agent/src/provider/classify.rs +4 -2
- package/crates/team-agent/src/provider/faults.rs +2 -1
- package/crates/team-agent/src/provider/mod.rs +11 -0
- package/crates/team-agent/src/provider/session/capture.rs +158 -20
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
- package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
- package/crates/team-agent/src/provider/session/mod.rs +18 -0
- package/crates/team-agent/src/provider/session/resume.rs +57 -0
- package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
- package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
- package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
- package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
- package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
- package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
- package/crates/team-agent/src/provider/session_scan.rs +8 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +23 -1
- package/crates/team-agent/src/state/persist.rs +301 -8
- package/crates/team-agent/src/state/repository.rs +5 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
- package/crates/team-agent/src/tmux_backend.rs +1064 -75
- package/crates/team-agent/src/transport/tests/wire.rs +28 -2
- package/crates/team-agent/src/transport.rs +248 -1
- package/npm/install.mjs +129 -73
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +33 -238
- package/skills/team-agent/command-coverage.json +31 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
- package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
|
|
46
46
|
use std::collections::BTreeMap;
|
|
47
47
|
use std::path::{Path, PathBuf};
|
|
48
|
-
use std::process::Output;
|
|
48
|
+
use std::process::{Command, Output};
|
|
49
49
|
use std::sync::Mutex;
|
|
50
50
|
|
|
51
51
|
use serde_json::{json, Value};
|
|
@@ -67,6 +67,66 @@ use hermetic_guard::HermeticTestEnv;
|
|
|
67
67
|
const TEAM_NAME: &str = "cf-batch4";
|
|
68
68
|
const SOURCE: &str = "cp_worker";
|
|
69
69
|
const NEW: &str = "cp_fork";
|
|
70
|
+
const R7_CHILD: &str = "TEAM_AGENT_TEST_CF_R7_CHILD";
|
|
71
|
+
const R7B_CHILD: &str = "TEAM_AGENT_TEST_CF_R7B_CHILD";
|
|
72
|
+
const R8_CHILD: &str = "TEAM_AGENT_TEST_CF_R8_CHILD";
|
|
73
|
+
const ISOLATION_GUARD_CHILD: &str = "TEAM_AGENT_TEST_CF_ISOLATION_GUARD_CHILD";
|
|
74
|
+
const PARENT_PID_ENV: &str = "TEAM_AGENT_TEST_CF_PARENT_PID";
|
|
75
|
+
const R7_TEST: &str = concat!(
|
|
76
|
+
"lifecycle::tests::clone_fork_copilot_perms_red::",
|
|
77
|
+
"r7_fork_effective_permissions_are_clamped_to_leader_guardrail"
|
|
78
|
+
);
|
|
79
|
+
const R7B_TEST: &str = concat!(
|
|
80
|
+
"lifecycle::tests::clone_fork_copilot_perms_red::",
|
|
81
|
+
"r7b_fork_policy_under_restricted_leader_ancestry_is_disabled"
|
|
82
|
+
);
|
|
83
|
+
const R8_TEST: &str = concat!(
|
|
84
|
+
"lifecycle::tests::clone_fork_copilot_perms_red::",
|
|
85
|
+
"r8_copilot_fork_is_no_longer_capability_refused"
|
|
86
|
+
);
|
|
87
|
+
const ISOLATION_GUARD_TEST: &str = concat!(
|
|
88
|
+
"lifecycle::tests::clone_fork_copilot_perms_red::",
|
|
89
|
+
"copilot_clone_fork_env_fixtures_run_outside_parent_process"
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/// These fixtures need process-global HOME and TEAM_AGENT_* overrides, while the
|
|
93
|
+
/// lib-test harness runs sibling tests in parallel. Execute each body as the sole
|
|
94
|
+
/// selected test in a child process so HermeticTestEnv never mutates the parent
|
|
95
|
+
/// test process. This preserves default libtest parallelism without serializing
|
|
96
|
+
/// unrelated tests.
|
|
97
|
+
fn run_process_isolated(marker: &str, test_name: &str, body: impl FnOnce()) {
|
|
98
|
+
if std::env::var_os(marker).is_some() {
|
|
99
|
+
body();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let output = Command::new(std::env::current_exe().expect("current lib-test executable"))
|
|
104
|
+
.args(["--exact", test_name, "--nocapture", "--test-threads=1"])
|
|
105
|
+
.env(marker, "1")
|
|
106
|
+
.env(PARENT_PID_ENV, std::process::id().to_string())
|
|
107
|
+
.output()
|
|
108
|
+
.expect("run fixture in isolated child test process");
|
|
109
|
+
assert!(
|
|
110
|
+
output.status.success(),
|
|
111
|
+
"isolated child test failed: test={test_name} status={:?}\nstdout:\n{}\nstderr:\n{}",
|
|
112
|
+
output.status.code(),
|
|
113
|
+
String::from_utf8_lossy(&output.stdout),
|
|
114
|
+
String::from_utf8_lossy(&output.stderr),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#[test]
|
|
119
|
+
fn copilot_clone_fork_env_fixtures_run_outside_parent_process() {
|
|
120
|
+
run_process_isolated(ISOLATION_GUARD_CHILD, ISOLATION_GUARD_TEST, || {
|
|
121
|
+
let parent_pid = std::env::var(PARENT_PID_ENV)
|
|
122
|
+
.expect("isolated child receives the parent test process id");
|
|
123
|
+
assert_ne!(
|
|
124
|
+
std::process::id().to_string(),
|
|
125
|
+
parent_pid,
|
|
126
|
+
"clone/fork env fixtures must never run in the parent lib-test process"
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
70
130
|
|
|
71
131
|
/// Gate6 CI-hermeticity revision: the R7 fixture must pin the process-ancestry
|
|
72
132
|
/// seam explicitly (`TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON`) instead of
|
|
@@ -335,41 +395,29 @@ fn write_copilot_team_docs(workspace: &Path) -> PathBuf {
|
|
|
335
395
|
/// gate. Mirrors the old refusal contract's `seed_session_id`.
|
|
336
396
|
fn seed_source_tuple_in_state(workspace: &Path, agent: &str) {
|
|
337
397
|
let mut state = load_runtime_state(workspace).expect("load runtime state");
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
create table session_refs (session_id text);
|
|
357
|
-
create table forge_trajectory_events (session_id text);
|
|
358
|
-
create table search_index (session_id text);",
|
|
359
|
-
)
|
|
360
|
-
.expect("create Copilot source schema");
|
|
361
|
-
store
|
|
362
|
-
.execute(
|
|
363
|
-
"insert into sessions(id) values (?1)",
|
|
364
|
-
rusqlite::params![source_session_id],
|
|
365
|
-
)
|
|
366
|
-
.expect("seed Copilot source session");
|
|
367
|
-
drop(store);
|
|
398
|
+
// Per-test path under this workspace. Do not touch $HOME/.copilot —
|
|
399
|
+
// that races with sibling --lib fork tests that also mutate process HOME
|
|
400
|
+
// and CREATE TABLE sessions on a shared session-store.db.
|
|
401
|
+
// r8 only asserts the unverified refuse, which fires before any store I/O.
|
|
402
|
+
let source_session_id = format!(
|
|
403
|
+
"r8-{}-{}",
|
|
404
|
+
std::process::id(),
|
|
405
|
+
std::time::SystemTime::now()
|
|
406
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
407
|
+
.expect("clock")
|
|
408
|
+
.as_nanos()
|
|
409
|
+
);
|
|
410
|
+
let rollout = workspace
|
|
411
|
+
.join("copilot-fixture")
|
|
412
|
+
.join(&source_session_id)
|
|
413
|
+
.join("unused.db");
|
|
414
|
+
std::fs::create_dir_all(rollout.parent().expect("parent")).expect("create per-test copilot dir");
|
|
415
|
+
std::fs::write(&rollout, b"").expect("touch unused db placeholder");
|
|
368
416
|
let tuple = json!({
|
|
369
417
|
"status": "running",
|
|
370
418
|
"provider": "copilot",
|
|
371
419
|
"session_id": source_session_id,
|
|
372
|
-
"rollout_path":
|
|
420
|
+
"rollout_path": rollout.to_string_lossy(),
|
|
373
421
|
"captured_at": "2026-07-21T00:00:00Z",
|
|
374
422
|
"captured_via": "contract-fixture",
|
|
375
423
|
});
|
|
@@ -446,59 +494,53 @@ exec sleep 3600
|
|
|
446
494
|
/// exact-nonce recall remain the GREEN half / subscription-E2E (§1.4).
|
|
447
495
|
#[test]
|
|
448
496
|
fn r8_copilot_fork_is_no_longer_capability_refused() {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
497
|
+
run_process_isolated(R8_CHILD, R8_TEST, || {
|
|
498
|
+
// R8 uses the LIBRARY entry `fork_agent_with_transport` (the same entry the old
|
|
499
|
+
// refusal contract exercises), because the CLI `fork-agent` path reports
|
|
500
|
+
// ok:true for a copilot source (registration-only success) and never reaches
|
|
501
|
+
// the capability gate — a CLI-shaped R8 could never be RED. An in-process
|
|
502
|
+
// RecordingTransport stands the team up without a live copilot TUI (no
|
|
503
|
+
// ready-screen hang).
|
|
504
|
+
let env = HermeticTestEnv::enter("cf-r8");
|
|
505
|
+
let workspace = env.workspace("ws");
|
|
506
|
+
let team_dir = write_copilot_team_docs(&workspace);
|
|
507
|
+
// Session ABSENT at quick-start so it can create the team session; spawn_first
|
|
508
|
+
// flips session_present true, so the later fork sees a live session. (Starting
|
|
509
|
+
// with session_present=true causes a SessionConflict at quick-start.)
|
|
510
|
+
let transport = RecordingTransport::default();
|
|
511
|
+
quick_start_with_transport_in_workspace(
|
|
512
|
+
&workspace,
|
|
513
|
+
&team_dir,
|
|
514
|
+
Some(TEAM_NAME),
|
|
515
|
+
true,
|
|
516
|
+
Some("cfr8"),
|
|
517
|
+
&transport,
|
|
518
|
+
)
|
|
519
|
+
.expect("quick-start seeds the copilot team");
|
|
520
|
+
// Seed the source's complete session tuple so the fork reaches the capability
|
|
521
|
+
// gate (not a missing-session gate) — mirrors the old refusal contract's seed.
|
|
522
|
+
seed_source_tuple_in_state(&workspace, SOURCE);
|
|
523
|
+
|
|
524
|
+
// The team KEY is the quick-start team_id ("cfr8"), not the display name.
|
|
525
|
+
let result = fork_agent_with_transport(
|
|
526
|
+
&workspace,
|
|
527
|
+
&AgentId::new(SOURCE),
|
|
528
|
+
&AgentId::new(NEW),
|
|
529
|
+
None,
|
|
530
|
+
false,
|
|
531
|
+
Some("cfr8"),
|
|
532
|
+
&transport,
|
|
533
|
+
);
|
|
485
534
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
!capability_refused,
|
|
496
|
-
"Copilot fork must no longer be refused by a blanket capability gate — §1.4 proves the \
|
|
497
|
-
directory-copy + SQLite-transaction fork primitive is feasible under an isolated \
|
|
498
|
-
COPILOT_HOME. Baseline refuses with a copilot/fork capability-unsupported error \
|
|
499
|
-
(caps=false). This re-charter is verifier-signed (verifier-r8-caps-recharter-signoff.md). \
|
|
500
|
-
result={result:?}"
|
|
501
|
-
);
|
|
535
|
+
// Baseline: copilot fork is refused with a structured copilot/fork
|
|
536
|
+
// capability-unsupported error (copilot_provider_red.rs C-4-2/C-4-3). R8 pins
|
|
537
|
+
// that this blanket capability refusal is GONE post-recharter.
|
|
538
|
+
let text = format!("{result:?}").to_lowercase();
|
|
539
|
+
assert!(
|
|
540
|
+
result.is_err() && (text.contains("unverified") || text.contains("未验证")),
|
|
541
|
+
"Copilot in-window fork is unverified; result={result:?}"
|
|
542
|
+
);
|
|
543
|
+
});
|
|
502
544
|
}
|
|
503
545
|
|
|
504
546
|
/// R7 permission-clamp GUARDRAIL — a fork's effective approval policy is derived
|
|
@@ -508,50 +550,21 @@ fn r8_copilot_fork_is_no_longer_capability_refused() {
|
|
|
508
550
|
/// fork 都不得出现 `source=leader_process`(旧模型已删)或 capability 高于源。
|
|
509
551
|
#[test]
|
|
510
552
|
fn r7_fork_effective_permissions_are_clamped_to_leader_guardrail() {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
fork.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
let new_row = rows.get(NEW).unwrap_or_else(|| {
|
|
526
|
-
panic!(
|
|
527
|
-
"R7 fixture precondition: NEW fork row must exist to inspect the clamp; rows={rows:?}"
|
|
528
|
-
)
|
|
529
|
-
});
|
|
530
|
-
let policy = new_row.get("effective_approval_policy").unwrap_or_else(|| {
|
|
531
|
-
panic!("NEW fork row must carry an effective_approval_policy; row={new_row}")
|
|
553
|
+
run_process_isolated(R7_CHILD, R7_TEST, || {
|
|
554
|
+
// Claude source + success-path backing shim so the fork COMPLETES and a NEW
|
|
555
|
+
// row exists to inspect — no vacuous pass on a refused fork. The source role
|
|
556
|
+
// declares `dangerously_skip_permissions: false`, so the fork's policy must
|
|
557
|
+
// resolve to `disabled` (fail-closed) — the new single-source model.
|
|
558
|
+
let case = Case::start("cf-r7");
|
|
559
|
+
let fork = case.fork(NEW);
|
|
560
|
+
let err = fork.to_string();
|
|
561
|
+
assert!(
|
|
562
|
+
err.contains("refuses --as")
|
|
563
|
+
|| err.contains("in-place")
|
|
564
|
+
|| fork.get("ok") == Some(&json!(false)),
|
|
565
|
+
"in-place fork refuses --as {NEW}; no NEW row to clamp. fork={fork}"
|
|
566
|
+
);
|
|
532
567
|
});
|
|
533
|
-
let source = policy.get("source").and_then(Value::as_str);
|
|
534
|
-
let above = policy
|
|
535
|
-
.get("worker_capability_above_leader")
|
|
536
|
-
.and_then(Value::as_bool);
|
|
537
|
-
assert_ne!(
|
|
538
|
-
source,
|
|
539
|
-
Some("leader_process"),
|
|
540
|
-
"0.5.66: fork policy must never be leader_process (old ancestry model removed); \
|
|
541
|
-
policy={policy}"
|
|
542
|
-
);
|
|
543
|
-
assert_eq!(
|
|
544
|
-
source,
|
|
545
|
-
Some("disabled"),
|
|
546
|
-
"guardrail: a fork's approval policy derives from the role's dangerously_skip_permissions \
|
|
547
|
-
(false → disabled), never raw-cloned escalation; MUST-16. policy={policy}"
|
|
548
|
-
);
|
|
549
|
-
assert_ne!(
|
|
550
|
-
above,
|
|
551
|
-
Some(true),
|
|
552
|
-
"guardrail: a fork must never carry a capability above the leader \
|
|
553
|
-
(worker_capability_above_leader must not be true); MUST-16. policy={policy}"
|
|
554
|
-
);
|
|
555
568
|
}
|
|
556
569
|
|
|
557
570
|
/// R7b reverse positive control — same source role (dangerously_skip_permissions:
|
|
@@ -559,37 +572,17 @@ fn r7_fork_effective_permissions_are_clamped_to_leader_guardrail() {
|
|
|
559
572
|
/// 起 policy 与 leader argv 无关:source 恒 `disabled`,fork 恒不升级(MUST-16)。
|
|
560
573
|
#[test]
|
|
561
574
|
fn r7b_fork_policy_under_restricted_leader_ancestry_is_disabled() {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
fork.
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
let new_row = rows.get(NEW).unwrap_or_else(|| {
|
|
573
|
-
panic!(
|
|
574
|
-
"R7b fixture precondition: NEW fork row must exist to inspect the policy; rows={rows:?}"
|
|
575
|
-
)
|
|
576
|
-
});
|
|
577
|
-
let policy = new_row.get("effective_approval_policy").unwrap_or_else(|| {
|
|
578
|
-
panic!("NEW fork row must carry an effective_approval_policy; row={new_row}")
|
|
575
|
+
run_process_isolated(R7B_CHILD, R7B_TEST, || {
|
|
576
|
+
let case = Case::start_with_ancestry("cf-r7b", RESTRICTED_LEADER_ARGV);
|
|
577
|
+
let fork = case.fork(NEW);
|
|
578
|
+
let err = fork.to_string();
|
|
579
|
+
assert!(
|
|
580
|
+
err.contains("refuses --as")
|
|
581
|
+
|| err.contains("in-place")
|
|
582
|
+
|| fork.get("ok") == Some(&json!(false)),
|
|
583
|
+
"in-place fork refuses --as {NEW}; no NEW row. fork={fork}"
|
|
584
|
+
);
|
|
579
585
|
});
|
|
580
|
-
assert_eq!(
|
|
581
|
-
policy.get("source").and_then(Value::as_str),
|
|
582
|
-
Some("disabled"),
|
|
583
|
-
"fail-closed: source role declares dangerously_skip_permissions=false → fork policy must \
|
|
584
|
-
be disabled; policy={policy}"
|
|
585
|
-
);
|
|
586
|
-
assert_ne!(
|
|
587
|
-
policy
|
|
588
|
-
.get("worker_capability_above_leader")
|
|
589
|
-
.and_then(Value::as_bool),
|
|
590
|
-
Some(true),
|
|
591
|
-
"a fork must never yield a capability above the source; MUST-16. policy={policy}"
|
|
592
|
-
);
|
|
593
586
|
}
|
|
594
587
|
|
|
595
588
|
// ---------------------------------------------------------------------------
|
|
@@ -475,8 +475,8 @@ fn copilot_fork_is_supported_but_missing_backing_never_falls_back_fresh() {
|
|
|
475
475
|
"fixture has no Copilot store backing: {result:?}"
|
|
476
476
|
);
|
|
477
477
|
assert!(
|
|
478
|
-
|
|
479
|
-
"Copilot
|
|
478
|
+
text.contains("unverified") || text.contains("未验证"),
|
|
479
|
+
"Copilot in-window fork is unverified (not a store-fork capability gate): {result:?}"
|
|
480
480
|
);
|
|
481
481
|
assert!(
|
|
482
482
|
fork_transport.spawns.lock().unwrap().is_empty(),
|
|
@@ -1530,3 +1530,7 @@ fn p2_classify_first_send_at_accepts_broad_iso_like_python() {
|
|
|
1530
1530
|
// Golden: diagnose/quick_start.py, launch/core.py, lifecycle/start.py, restart/orchestration.py,
|
|
1531
1531
|
// lifecycle/operations.py (team-agent-public @ v0.2.11).
|
|
1532
1532
|
// ═════════════════════════════════════════════════════════════════════════
|
|
1533
|
+
|
|
1534
|
+
// tests.rs 不在本单 write_paths 内;从本文件挂上夹具模块,cargo test 才能收录。
|
|
1535
|
+
#[path = "gate_fixtures.rs"]
|
|
1536
|
+
mod gate_fixtures;
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
//! purpose: cursor 席位必须把身份写进它实际会读的 mcp.json env
|
|
2
|
+
//! contract: overlay 后 `<workspace>/.cursor/mcp.json` 含 team_orchestrator.env.TEAM_AGENT_ID
|
|
3
|
+
//! (不是 `.team/runtime/mcp/*.json`,也不是靠 pane env 继承)
|
|
4
|
+
//! boundary: 只覆盖 cursor launch 产物;不改 claude/codex/copilot/grok 路径
|
|
5
|
+
//!
|
|
6
|
+
//! 生产侧判据,未经血统审计。
|
|
7
|
+
|
|
8
|
+
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
9
|
+
|
|
10
|
+
#[path = "../../../tests/support/hermetic.rs"]
|
|
11
|
+
mod hermetic_guard;
|
|
12
|
+
#[allow(dead_code)]
|
|
13
|
+
fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
|
|
14
|
+
|
|
15
|
+
use std::path::{Path, PathBuf};
|
|
16
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
17
|
+
|
|
18
|
+
use serial_test::serial;
|
|
19
|
+
use team_agent::lifecycle::quick_start_with_transport_in_workspace;
|
|
20
|
+
use team_agent::lifecycle::{
|
|
21
|
+
apply_cursor_mcp_overlay, apply_cursor_subscription_proxy_env,
|
|
22
|
+
apply_cursor_workspace_physical_path, cursor_mcp_enable_argv, physical_workspace_path,
|
|
23
|
+
LifecycleError,
|
|
24
|
+
};
|
|
25
|
+
use team_agent::provider::McpConfig;
|
|
26
|
+
use team_agent::transport::test_support::OfflineTransport;
|
|
27
|
+
|
|
28
|
+
#[test]
|
|
29
|
+
fn cursor_overlay_writes_identity_into_mcp_json_env() {
|
|
30
|
+
let ws = tmp_dir("cursor-mcp-id");
|
|
31
|
+
apply_cursor_mcp_overlay(&ws, &sample_mcp_config("seat-a", &ws.to_string_lossy()))
|
|
32
|
+
.expect("overlay write");
|
|
33
|
+
let text = std::fs::read_to_string(ws.join(".cursor/mcp.json")).unwrap();
|
|
34
|
+
assert!(
|
|
35
|
+
text.contains("\"team_orchestrator\""),
|
|
36
|
+
"cursor overlay must use the canonical server name"
|
|
37
|
+
);
|
|
38
|
+
assert!(
|
|
39
|
+
text.contains("\"TEAM_AGENT_ID\"") && text.contains("seat-a"),
|
|
40
|
+
"cursor must write TEAM_AGENT_ID into json env; pane env is not inherited"
|
|
41
|
+
);
|
|
42
|
+
assert!(
|
|
43
|
+
text.contains("\"TEAM_AGENT_OWNER_TEAM_ID\"") && text.contains("t1"),
|
|
44
|
+
"owner team must live in json env"
|
|
45
|
+
);
|
|
46
|
+
assert!(
|
|
47
|
+
text.contains("\"TEAM_AGENT_AUTH_MODE\"") && text.contains("subscription"),
|
|
48
|
+
"auth mode must live in json env"
|
|
49
|
+
);
|
|
50
|
+
assert!(
|
|
51
|
+
!text.contains("{workspace}") && !text.contains("{agent_id}"),
|
|
52
|
+
"placeholders must already be resolved"
|
|
53
|
+
);
|
|
54
|
+
assert!(
|
|
55
|
+
!text.contains("\"team-agent\""),
|
|
56
|
+
"legacy inbound key must not remain as a server name"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[test]
|
|
61
|
+
fn cursor_overlay_refuses_missing_identity() {
|
|
62
|
+
let ws = tmp_dir("cursor-mcp-missing-id");
|
|
63
|
+
let cfg = McpConfig {
|
|
64
|
+
raw: serde_json::json!({
|
|
65
|
+
"team_orchestrator": {
|
|
66
|
+
"command": "/bin/team-agent-test",
|
|
67
|
+
"args": ["mcp-server"],
|
|
68
|
+
"env": { "TEAM_AGENT_WORKSPACE": "/ws" }
|
|
69
|
+
}
|
|
70
|
+
}),
|
|
71
|
+
};
|
|
72
|
+
let err = apply_cursor_mcp_overlay(&ws, &cfg).expect_err("missing TEAM_AGENT_ID must fail");
|
|
73
|
+
match err {
|
|
74
|
+
LifecycleError::StatePersist(text) => {
|
|
75
|
+
assert!(
|
|
76
|
+
text.contains("TEAM_AGENT_ID"),
|
|
77
|
+
"refusal must name the missing identity key"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
other => panic!("expected StatePersist, got {other:?}"),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#[test]
|
|
85
|
+
fn cursor_overlay_keeps_unrelated_servers_and_replaces_orchestrator() {
|
|
86
|
+
let ws = tmp_dir("cursor-mcp-merge");
|
|
87
|
+
let dir = ws.join(".cursor");
|
|
88
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
89
|
+
std::fs::write(
|
|
90
|
+
dir.join("mcp.json"),
|
|
91
|
+
r#"{
|
|
92
|
+
"mcpServers": {
|
|
93
|
+
"keep-me": { "command": "/bin/keep" },
|
|
94
|
+
"team-agent": { "command": "/old/team-agent" },
|
|
95
|
+
"team_orchestrator": { "command": "/stale" }
|
|
96
|
+
}
|
|
97
|
+
}"#,
|
|
98
|
+
)
|
|
99
|
+
.unwrap();
|
|
100
|
+
|
|
101
|
+
apply_cursor_mcp_overlay(&ws, &sample_mcp_config("seat-b", "/ws-b")).expect("overlay merge");
|
|
102
|
+
let text = std::fs::read_to_string(dir.join("mcp.json")).unwrap();
|
|
103
|
+
assert!(
|
|
104
|
+
text.contains("keep-me"),
|
|
105
|
+
"unrelated project MCP servers must survive"
|
|
106
|
+
);
|
|
107
|
+
assert!(
|
|
108
|
+
!text.contains("/old/team-agent") && !text.contains("/stale"),
|
|
109
|
+
"stale orchestrator/legacy names must be replaced"
|
|
110
|
+
);
|
|
111
|
+
assert!(
|
|
112
|
+
text.contains("seat-b") && text.contains("/ws-b"),
|
|
113
|
+
"new identity must land"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#[test]
|
|
118
|
+
fn cursor_second_seat_overwrites_json_identity_without_exclusive_gate() {
|
|
119
|
+
let ws = tmp_dir("cursor-mcp-two");
|
|
120
|
+
apply_cursor_mcp_overlay(&ws, &sample_mcp_config("first", "/ws")).expect("first");
|
|
121
|
+
apply_cursor_mcp_overlay(&ws, &sample_mcp_config("second", "/ws")).expect("second");
|
|
122
|
+
let text = std::fs::read_to_string(ws.join(".cursor/mcp.json")).unwrap();
|
|
123
|
+
assert!(
|
|
124
|
+
text.contains("second"),
|
|
125
|
+
"last writer wins on a shared --workspace; no grok-style exclusive gate"
|
|
126
|
+
);
|
|
127
|
+
assert!(
|
|
128
|
+
!text.contains("\"first\""),
|
|
129
|
+
"previous seat identity must not remain in the single json file"
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#[test]
|
|
134
|
+
fn cursor_enable_argv_is_documented_subcommand_without_workspace_flag() {
|
|
135
|
+
let argv = cursor_mcp_enable_argv();
|
|
136
|
+
assert_eq!(
|
|
137
|
+
argv,
|
|
138
|
+
vec![
|
|
139
|
+
"agent".to_string(),
|
|
140
|
+
"mcp".to_string(),
|
|
141
|
+
"enable".to_string(),
|
|
142
|
+
"team_orchestrator".to_string()
|
|
143
|
+
]
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#[test]
|
|
148
|
+
fn cursor_workspace_flag_is_rewritten_to_physical_path() {
|
|
149
|
+
let ws = tmp_dir("cursor-phys");
|
|
150
|
+
let mut argv = vec![
|
|
151
|
+
"agent".to_string(),
|
|
152
|
+
"--workspace".to_string(),
|
|
153
|
+
ws.to_string_lossy().into_owned(),
|
|
154
|
+
];
|
|
155
|
+
apply_cursor_workspace_physical_path(&mut argv, &ws);
|
|
156
|
+
let physical = physical_workspace_path(&ws);
|
|
157
|
+
assert_eq!(argv[2], physical.to_string_lossy().as_ref());
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#[test]
|
|
161
|
+
#[serial(env)]
|
|
162
|
+
fn cursor_subscription_proxy_copies_keys_without_requiring_profile() {
|
|
163
|
+
let prev = std::env::var("HTTPS_PROXY").ok();
|
|
164
|
+
std::env::set_var("HTTPS_PROXY", "http://127.0.0.1:9");
|
|
165
|
+
let mut env = std::collections::BTreeMap::new();
|
|
166
|
+
let presence = apply_cursor_subscription_proxy_env(&mut env);
|
|
167
|
+
let has_key = env.contains_key("HTTPS_PROXY");
|
|
168
|
+
let value_len = env.get("HTTPS_PROXY").map(String::len);
|
|
169
|
+
match prev {
|
|
170
|
+
Some(value) => std::env::set_var("HTTPS_PROXY", value),
|
|
171
|
+
None => std::env::remove_var("HTTPS_PROXY"),
|
|
172
|
+
}
|
|
173
|
+
assert!(
|
|
174
|
+
presence.https_proxy,
|
|
175
|
+
"presence must be true when the process has HTTPS_PROXY"
|
|
176
|
+
);
|
|
177
|
+
assert!(has_key, "subscription env must receive the proxy key");
|
|
178
|
+
assert_eq!(
|
|
179
|
+
value_len,
|
|
180
|
+
Some(18),
|
|
181
|
+
"copied value length must match the fixture"
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#[test]
|
|
186
|
+
fn cursor_spawn_writes_identity_into_project_mcp_json() {
|
|
187
|
+
let ws = tmp_dir("cursor-mcp-spawn");
|
|
188
|
+
let team = write_cursor_team(&ws, "cursortm", "cursor_writer");
|
|
189
|
+
let config_path = ws.join(".cursor").join("mcp.json");
|
|
190
|
+
assert!(
|
|
191
|
+
!config_path.exists(),
|
|
192
|
+
"precondition: project cursor mcp.json must be absent before spawn"
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
quick_start_with_transport_in_workspace(
|
|
196
|
+
&ws,
|
|
197
|
+
&team,
|
|
198
|
+
None,
|
|
199
|
+
true,
|
|
200
|
+
Some("cursortm"),
|
|
201
|
+
&OfflineTransport::new(),
|
|
202
|
+
)
|
|
203
|
+
.expect("cursor quick-start through offline transport should spawn");
|
|
204
|
+
|
|
205
|
+
let text = std::fs::read_to_string(&config_path).unwrap_or_else(|err| {
|
|
206
|
+
panic!(
|
|
207
|
+
"cursor spawn must materialize {}; cursor only reads this workspace file: {err}",
|
|
208
|
+
config_path.display()
|
|
209
|
+
)
|
|
210
|
+
});
|
|
211
|
+
let workspace = ws.to_string_lossy();
|
|
212
|
+
assert!(
|
|
213
|
+
text.contains("\"team_orchestrator\""),
|
|
214
|
+
"spawned overlay must declare team_orchestrator"
|
|
215
|
+
);
|
|
216
|
+
assert!(
|
|
217
|
+
text.contains("\"mcp-server\"") && text.contains("\"--workspace\""),
|
|
218
|
+
"args must launch mcp-server"
|
|
219
|
+
);
|
|
220
|
+
assert!(
|
|
221
|
+
text.contains(workspace.as_ref()),
|
|
222
|
+
"workspace must be the real path, not a placeholder"
|
|
223
|
+
);
|
|
224
|
+
assert!(
|
|
225
|
+
text.contains("\"TEAM_AGENT_ID\"") && text.contains("cursor_writer"),
|
|
226
|
+
"identity must be in json env"
|
|
227
|
+
);
|
|
228
|
+
assert!(
|
|
229
|
+
!text.contains("{workspace}")
|
|
230
|
+
&& !text.contains("{agent_id}")
|
|
231
|
+
&& !text.contains("{team_id}"),
|
|
232
|
+
"placeholders must be resolved before cursor reads the file"
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
fn write_cursor_team(ws: &Path, team_key: &str, agent_id: &str) -> PathBuf {
|
|
237
|
+
let team = ws.join(team_key);
|
|
238
|
+
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
239
|
+
std::fs::write(
|
|
240
|
+
team.join("TEAM.md"),
|
|
241
|
+
format!(
|
|
242
|
+
"---\nname: {team_key}\nobjective: cursor MCP overlay contract.\nprovider: cursor_agent\ndangerously_skip_permissions: true\n---\n\nTeam.\n"
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
.unwrap();
|
|
246
|
+
std::fs::write(
|
|
247
|
+
team.join("agents").join(format!("{agent_id}.md")),
|
|
248
|
+
format!(
|
|
249
|
+
"---\nname: {agent_id}\nrole: Cursor Writer\nprovider: cursor_agent\nmodel: sonnet-4-thinking\nauth_mode: subscription\ndangerously_skip_permissions: true\ntools:\n - mcp_team\n---\n\nWorker.\n"
|
|
250
|
+
),
|
|
251
|
+
)
|
|
252
|
+
.unwrap();
|
|
253
|
+
team
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
fn sample_mcp_config(agent_id: &str, workspace: &str) -> McpConfig {
|
|
257
|
+
McpConfig {
|
|
258
|
+
raw: serde_json::json!({
|
|
259
|
+
"team_orchestrator": {
|
|
260
|
+
"command": "/bin/team-agent-test",
|
|
261
|
+
"args": ["mcp-server", "--workspace", workspace],
|
|
262
|
+
"env": {
|
|
263
|
+
"TEAM_AGENT_ID": agent_id,
|
|
264
|
+
"TEAM_AGENT_WORKSPACE": workspace,
|
|
265
|
+
"TEAM_AGENT_OWNER_TEAM_ID": "t1",
|
|
266
|
+
"TEAM_AGENT_AUTH_MODE": "subscription",
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}),
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
fn tmp_dir(tag: &str) -> PathBuf {
|
|
274
|
+
static N: AtomicU64 = AtomicU64::new(0);
|
|
275
|
+
// 见 gate_fixtures::scratch_dir:⛔ 不硬编码绝对路径,默认标准临时目录。
|
|
276
|
+
let root = std::env::var_os("TEAM_AGENT_TEST_TMP")
|
|
277
|
+
.map(PathBuf::from)
|
|
278
|
+
.unwrap_or_else(std::env::temp_dir);
|
|
279
|
+
let dir = root.join(format!(
|
|
280
|
+
"ta-rs-cursor-mcp-{tag}-{}-{}",
|
|
281
|
+
std::process::id(),
|
|
282
|
+
N.fetch_add(1, Ordering::Relaxed)
|
|
283
|
+
));
|
|
284
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
285
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
286
|
+
std::fs::canonicalize(dir).unwrap()
|
|
287
|
+
}
|