@team-agent/installer 0.5.65 → 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 +6 -0
- package/crates/team-agent/src/cli/adapters.rs +6 -1
- 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/compile.rs +1 -1
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
- package/crates/team-agent/src/cli/tests/mod.rs +2 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -1
- 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/cli/tests/verb_validate.rs +1 -1
- package/crates/team-agent/src/communication_mode/mod.rs +6 -4
- package/crates/team-agent/src/compiler/tests.rs +23 -20
- package/crates/team-agent/src/compiler.rs +84 -5
- package/crates/team-agent/src/conpty/backend.rs +16 -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 +1080 -58
- package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1 -1
- 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 -14
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +69 -2
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +196 -2
- 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 -457
- 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 +101 -4
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +244 -48
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -2
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
- package/crates/team-agent/src/lifecycle/launch.rs +209 -43
- 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 +213 -3
- package/crates/team-agent/src/lifecycle/restart/common.rs +373 -18
- 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 +165 -15
- 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/acceptance_b_batch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +3 -4
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +4 -4
- 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 +159 -174
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +15 -13
- package/crates/team-agent/src/lifecycle/tests/core.rs +4 -18
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +1 -1
- 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/display_adaptive_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +1 -1
- 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/harvest2_a_batch_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +3 -3
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +65 -40
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +20 -32
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +17 -9
- 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 +24 -23
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +98 -62
- package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -1
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +41 -8
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +3 -3
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +60 -2
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
- package/crates/team-agent/src/lifecycle/tests.rs +23 -15
- package/crates/team-agent/src/lifecycle/types.rs +61 -2
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +200 -33
- 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/main_preserved.rs +1 -1
- 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/model/spec.rs +129 -6
- package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +3 -1
- package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +3 -1
- package/crates/team-agent/src/model/testdata/team.spec.yaml +3 -1
- 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/claude.rs +5 -2
- package/crates/team-agent/src/provider/adapters/codex.rs +5 -2
- package/crates/team-agent/src/provider/adapters/copilot.rs +5 -2
- 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 +124 -0
- 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 +13 -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 +1083 -75
- package/crates/team-agent/src/transport/test_support.rs +20 -0
- package/crates/team-agent/src/transport/tests/wire.rs +28 -2
- package/crates/team-agent/src/transport.rs +265 -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/approval.rs +0 -134
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -483
- package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
- package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +0 -1187
- package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +0 -327
|
@@ -18,6 +18,15 @@ fn e23_fallback_pane_has_single_shared_noisy_surface() {
|
|
|
18
18
|
.unwrap(),
|
|
19
19
|
"primary-ok guard must run before any fallback audit/inject attempt"
|
|
20
20
|
);
|
|
21
|
+
let already = leader_receiver
|
|
22
|
+
.split("fn message_already_delivered")
|
|
23
|
+
.nth(1)
|
|
24
|
+
.unwrap_or("");
|
|
25
|
+
assert!(
|
|
26
|
+
already.contains("submitted_pending_acceptance")
|
|
27
|
+
&& already.contains("injected_awaiting_receipt"),
|
|
28
|
+
"fallback already-delivered set must include the live post-submit statuses, not only the dead string submitted"
|
|
29
|
+
);
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
#[test]
|
|
@@ -99,6 +99,72 @@ fn attached_leader_mailbox_is_rechecked_by_the_normal_delivery_tick() {
|
|
|
99
99
|
assert!(case.events().contains("leader_receiver.mailbox_requeued"));
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
#[test]
|
|
103
|
+
fn claimed_leader_delivery_revalidates_a_stale_unattached_snapshot() {
|
|
104
|
+
let workspace = tmp_ws("leader-inject-acceptance-stale-attach");
|
|
105
|
+
let rollout = workspace.join("leader.jsonl");
|
|
106
|
+
std::fs::write(&rollout, "").unwrap();
|
|
107
|
+
let current_state = serde_json::json!({
|
|
108
|
+
"active_team_key": "acceptance-team",
|
|
109
|
+
"session_name": "team-acceptance",
|
|
110
|
+
"leader_receiver": {
|
|
111
|
+
"pane_id": "%leader",
|
|
112
|
+
"status": "attached",
|
|
113
|
+
"provider": "claude",
|
|
114
|
+
"rollout_path": rollout,
|
|
115
|
+
},
|
|
116
|
+
"teams": {
|
|
117
|
+
"acceptance-team": {
|
|
118
|
+
"session_name": "team-acceptance",
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
crate::state::persist::save_runtime_state(&workspace, ¤t_state).unwrap();
|
|
123
|
+
let store = MessageStore::open(&workspace).unwrap();
|
|
124
|
+
let event_log = EventLog::new(&workspace);
|
|
125
|
+
let message_id = store
|
|
126
|
+
.create_message(
|
|
127
|
+
None,
|
|
128
|
+
"worker",
|
|
129
|
+
"leader",
|
|
130
|
+
"stale attach canary",
|
|
131
|
+
None,
|
|
132
|
+
false,
|
|
133
|
+
Some("acceptance-team"),
|
|
134
|
+
)
|
|
135
|
+
.unwrap();
|
|
136
|
+
let stale_state = serde_json::json!({
|
|
137
|
+
"active_team_key": "acceptance-team",
|
|
138
|
+
"teams": {
|
|
139
|
+
"acceptance-team": {
|
|
140
|
+
"session_name": "team-acceptance"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
let transport = RecordingTransport::new("");
|
|
145
|
+
|
|
146
|
+
let outcome = deliver_pending_message(
|
|
147
|
+
&workspace,
|
|
148
|
+
&store,
|
|
149
|
+
&transport,
|
|
150
|
+
&message_id,
|
|
151
|
+
&event_log,
|
|
152
|
+
&stale_state,
|
|
153
|
+
)
|
|
154
|
+
.expect("delivery from stale coordinator snapshot");
|
|
155
|
+
|
|
156
|
+
assert_ne!(
|
|
157
|
+
outcome.reason,
|
|
158
|
+
Some(DeliveryRefusal::LeaderNotAttached),
|
|
159
|
+
"a claim made after attach must revalidate durable state before writing leader_not_attached"
|
|
160
|
+
);
|
|
161
|
+
assert_ne!(
|
|
162
|
+
outcome.message_status.0, "failed",
|
|
163
|
+
"a stale pre-attach snapshot must not strand the requeued row"
|
|
164
|
+
);
|
|
165
|
+
assert_eq!(transport.inject_count(), 1);
|
|
166
|
+
}
|
|
167
|
+
|
|
102
168
|
#[test]
|
|
103
169
|
fn leader_submit_without_receipt_source_cannot_park_forever_without_a_completion_writer() {
|
|
104
170
|
let case = Case::new_without_rollout("missing-receipt-source");
|
|
@@ -94,7 +94,7 @@ fn collect_result_file_ingests_valid_known_task_envelope_into_results() {
|
|
|
94
94
|
.unwrap();
|
|
95
95
|
std::fs::write(
|
|
96
96
|
team.join("agents").join("w1.md"),
|
|
97
|
-
"---\nname: w1\nrole: Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nW1.\n",
|
|
97
|
+
"---\nname: w1\nrole: Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nW1.\n",
|
|
98
98
|
)
|
|
99
99
|
.unwrap();
|
|
100
100
|
let spec = crate::compiler::compile_team(&team).expect("compile collect team");
|
|
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
|
|
5
5
|
|
|
6
6
|
use crate::model::enums::Provider;
|
|
7
7
|
use crate::model::ids::{LeaderSessionUuid, OwnerEpoch, TaskId, TeamKey};
|
|
8
|
-
use crate::transport::PaneId;
|
|
8
|
+
use crate::transport::{PaneId, TurnVerification};
|
|
9
9
|
|
|
10
10
|
use super::helpers::MessageStatusShadow;
|
|
11
11
|
|
|
@@ -308,6 +308,9 @@ pub struct DeliveryOutcome {
|
|
|
308
308
|
pub reason: Option<DeliveryRefusal>,
|
|
309
309
|
/// fallback / broadcast / fanout 等通道标注 (`channel` 字段)。
|
|
310
310
|
pub channel: Option<String>,
|
|
311
|
+
/// 入箱 vs 开跑。None = 本路径没观测(JSON 必须落成 not_yet_observed,不得报开跑)。
|
|
312
|
+
/// Gap42:只是 metadata,不是投递闸门。
|
|
313
|
+
pub turn_verification: Option<TurnVerification>,
|
|
311
314
|
}
|
|
312
315
|
|
|
313
316
|
/// trust retry payload (card §45;`delivery.py:273` scheduled_events.payload_json 的 typed 视图)。
|
|
@@ -21,6 +21,12 @@ pub enum Provider {
|
|
|
21
21
|
/// AGENTS.md + `COPILOT_CUSTOM_INSTRUCTIONS_DIRS` env(B2 灵魂件降级,§C2)。
|
|
22
22
|
Copilot,
|
|
23
23
|
GeminiCli,
|
|
24
|
+
/// Grok CLI (0.5.67 provider-adapter). Append system prompt via `--rules`,
|
|
25
|
+
/// bypass `--always-approve`, session `--session-id`.
|
|
26
|
+
Grok,
|
|
27
|
+
/// Cursor `agent` CLI (0.5.67 provider-adapter). Append system prompt via
|
|
28
|
+
/// workspace `.cursor/rules/*.mdc` (方案 1 变体), bypass `--force`.
|
|
29
|
+
CursorAgent,
|
|
24
30
|
Fake,
|
|
25
31
|
}
|
|
26
32
|
|
|
@@ -33,7 +39,8 @@ pub enum Provider {
|
|
|
33
39
|
/// Provider support:
|
|
34
40
|
/// - claude / claude_code: low|medium|high|xhigh|max → `--effort <level>`
|
|
35
41
|
/// - codex: low|medium|high|xhigh (NOT max) → `-c model_reasoning_effort=<level>`
|
|
36
|
-
/// -
|
|
42
|
+
/// - grok: low|medium|high|xhigh (NOT max) → `--effort <level>`
|
|
43
|
+
/// - copilot / gemini_cli / cursor_agent / fake: unsupported — warning event, no flag
|
|
37
44
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
38
45
|
pub enum ProviderEffort {
|
|
39
46
|
#[serde(rename = "low")]
|
|
@@ -78,13 +85,15 @@ impl ProviderEffort {
|
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
/// True when the given provider supports this effort level. `max` is
|
|
81
|
-
/// Claude-only; other levels are supported by Claude
|
|
82
|
-
///
|
|
88
|
+
/// Claude-only; other levels are supported by Claude, Codex, and Grok.
|
|
89
|
+
/// Copilot/Gemini/CursorAgent/Fake ignore the field (warning at runtime).
|
|
83
90
|
pub fn is_supported_by(self, provider: Provider) -> bool {
|
|
84
91
|
match provider {
|
|
85
92
|
Provider::Claude | Provider::ClaudeCode => true,
|
|
86
|
-
Provider::Codex => !self.is_claude_only(),
|
|
87
|
-
Provider::Copilot | Provider::GeminiCli | Provider::Fake =>
|
|
93
|
+
Provider::Codex | Provider::Grok => !self.is_claude_only(),
|
|
94
|
+
Provider::Copilot | Provider::GeminiCli | Provider::CursorAgent | Provider::Fake => {
|
|
95
|
+
false
|
|
96
|
+
}
|
|
88
97
|
}
|
|
89
98
|
}
|
|
90
99
|
}
|
|
@@ -189,6 +189,9 @@ fn provider_enforcement(provider: Provider, tool: Tool) -> Enforcement {
|
|
|
189
189
|
FsRead | FsWrite | FsList | ExecuteBash | GitDiff | ProviderBuiltin => Hard,
|
|
190
190
|
},
|
|
191
191
|
Provider::Fake => Hard,
|
|
192
|
+
// 0.5.67: grok/cursor_agent 无 deny-kind 概念 (同 codex 精神), 诚实
|
|
193
|
+
// prompt_only, 不替 provider 决。
|
|
194
|
+
Provider::Grok | Provider::CursorAgent => PromptOnly,
|
|
192
195
|
// Copilot(C-2-1 cr verdict):execute_bash/fs_write/network/mcp_team = hard,
|
|
193
196
|
// fs_read/fs_list/git_diff/provider_builtin = prompt_only(诚实:copilot 无
|
|
194
197
|
// 对应 deny kind,framework 不替决,留给 provider prompt 控制;MUST-NOT-13)。
|
|
@@ -354,8 +354,31 @@ fn basic_schema_errors(spec: &Yaml) -> Vec<String> {
|
|
|
354
354
|
);
|
|
355
355
|
match spec.get("agents") {
|
|
356
356
|
Some(Yaml::List(agents)) if !agents.is_empty() => {
|
|
357
|
+
// 0.5.66 bypass 单源:收集缺/错 `dangerously_skip_permissions` 的角色清单,
|
|
358
|
+
// 循环后一次性报出,满足"缺字段错误消息含每个缺字段的角色 id 清单"硬要求。
|
|
359
|
+
let mut missing_bypass: Vec<String> = Vec::new();
|
|
360
|
+
let mut bad_type_bypass: Vec<String> = Vec::new();
|
|
357
361
|
for (idx, agent) in agents.iter().enumerate() {
|
|
358
362
|
check_agent(agent, &format!("/agents/{idx}"), &mut e);
|
|
363
|
+
match agent.get("dangerously_skip_permissions") {
|
|
364
|
+
None => missing_bypass.push(agent_label(agent, idx)),
|
|
365
|
+
Some(value) if !matches!(value, Yaml::Bool(_)) => {
|
|
366
|
+
bad_type_bypass.push(agent_label(agent, idx))
|
|
367
|
+
}
|
|
368
|
+
Some(_) => {}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if !missing_bypass.is_empty() {
|
|
372
|
+
e.push(format!(
|
|
373
|
+
"/agents/dangerously_skip_permissions: missing required field (role id(s): {}). This field must be declared explicitly; it controls whether the agent launches with permission prompts bypassed.",
|
|
374
|
+
missing_bypass.join(", ")
|
|
375
|
+
));
|
|
376
|
+
}
|
|
377
|
+
if !bad_type_bypass.is_empty() {
|
|
378
|
+
e.push(format!(
|
|
379
|
+
"/agents/dangerously_skip_permissions: must be a boolean (role id(s): {}). This field must be declared explicitly; it controls whether the agent launches with permission prompts bypassed.",
|
|
380
|
+
bad_type_bypass.join(", ")
|
|
381
|
+
));
|
|
359
382
|
}
|
|
360
383
|
}
|
|
361
384
|
_ => e.push("/agents: must be a non-empty list".to_string()),
|
|
@@ -384,7 +407,7 @@ fn check_agent(agent: &Yaml, path: &str, errors: &mut Vec<String>) {
|
|
|
384
407
|
"working_directory",
|
|
385
408
|
"system_prompt",
|
|
386
409
|
"tools",
|
|
387
|
-
"
|
|
410
|
+
"dangerously_skip_permissions",
|
|
388
411
|
"preferred_for",
|
|
389
412
|
"avoid_for",
|
|
390
413
|
"output_contract",
|
|
@@ -397,7 +420,7 @@ fn check_agent(agent: &Yaml, path: &str, errors: &mut Vec<String>) {
|
|
|
397
420
|
"working_directory",
|
|
398
421
|
"system_prompt",
|
|
399
422
|
"tools",
|
|
400
|
-
"
|
|
423
|
+
"dangerously_skip_permissions",
|
|
401
424
|
"preferred_for",
|
|
402
425
|
"avoid_for",
|
|
403
426
|
"output_contract",
|
|
@@ -407,6 +430,10 @@ fn check_agent(agent: &Yaml, path: &str, errors: &mut Vec<String>) {
|
|
|
407
430
|
"profile",
|
|
408
431
|
"credential_ref",
|
|
409
432
|
"forked_from",
|
|
433
|
+
// 存量角色 md 的 `permission_mode` 是历史无效值(compiler 曾恒发 restricted)。
|
|
434
|
+
// 0.5.66 起不再被 compiler 消费;保留在 allowed 以免存量文件 fail-loud
|
|
435
|
+
// (§3.1 迁移时顺手删除,0.6.0 可收紧)。
|
|
436
|
+
"permission_mode",
|
|
410
437
|
// 0.4.x provider effort MVP step 3: per-agent effort override (resolved at compile).
|
|
411
438
|
"effort",
|
|
412
439
|
];
|
|
@@ -481,6 +508,15 @@ fn check_agent(agent: &Yaml, path: &str, errors: &mut Vec<String>) {
|
|
|
481
508
|
}
|
|
482
509
|
}
|
|
483
510
|
|
|
511
|
+
/// 汇总错误里引用 agent 的可读标签:`<id>` 优先,退回 `<index> (role: <role>)`。
|
|
512
|
+
fn agent_label(agent: &Yaml, idx: usize) -> String {
|
|
513
|
+
agent
|
|
514
|
+
.get("id")
|
|
515
|
+
.and_then(Yaml::as_str)
|
|
516
|
+
.map(|id| format!("`{id}`"))
|
|
517
|
+
.unwrap_or_else(|| format!("agents[{idx}]"))
|
|
518
|
+
}
|
|
519
|
+
|
|
484
520
|
fn check_routing(routing: Option<&Yaml>, errors: &mut Vec<String>) {
|
|
485
521
|
check_keys_y(
|
|
486
522
|
routing,
|
|
@@ -564,7 +600,6 @@ fn check_runtime(runtime: Option<&Yaml>, errors: &mut Vec<String>) {
|
|
|
564
600
|
"max_active_agents",
|
|
565
601
|
"startup_order",
|
|
566
602
|
"display_backend",
|
|
567
|
-
"dangerous_auto_approve",
|
|
568
603
|
"auto_attach_leader",
|
|
569
604
|
"fast",
|
|
570
605
|
"tick_interval_sec",
|
|
@@ -588,9 +623,6 @@ fn check_runtime(runtime: Option<&Yaml>, errors: &mut Vec<String>) {
|
|
|
588
623
|
errors.push("/runtime/display_backend: invalid display backend".to_string());
|
|
589
624
|
}
|
|
590
625
|
}
|
|
591
|
-
if get("dangerous_auto_approve").is_some_and(|v| !matches!(v, Yaml::Bool(_))) {
|
|
592
|
-
errors.push("/runtime/dangerous_auto_approve: must be a boolean".to_string());
|
|
593
|
-
}
|
|
594
626
|
if get("auto_trust_own_workspace").is_some_and(|v| !matches!(v, Yaml::Bool(_))) {
|
|
595
627
|
errors.push("/runtime/auto_trust_own_workspace: must be a boolean".to_string());
|
|
596
628
|
}
|
|
@@ -1020,6 +1052,58 @@ mod tests {
|
|
|
1020
1052
|
);
|
|
1021
1053
|
}
|
|
1022
1054
|
|
|
1055
|
+
// 0.5.66 bypass 单源 §4.1:缺 `dangerously_skip_permissions` 字段 → 错误消息
|
|
1056
|
+
// 必须列出每个缺字段的角色 id + 用户可读的必填说明。
|
|
1057
|
+
#[test]
|
|
1058
|
+
fn test_missing_dangerously_field_reports_role_list() {
|
|
1059
|
+
// 从 fixture 文本里删掉第一个 agent 的字段,模拟该角色缺字段。
|
|
1060
|
+
let text = include_str!("testdata/team.spec.yaml");
|
|
1061
|
+
let without_first = text.replacen(" dangerously_skip_permissions: false\n", "", 1);
|
|
1062
|
+
assert_ne!(text, without_first, "fixture must contain the field");
|
|
1063
|
+
let spec = yaml::loads(&without_first).unwrap();
|
|
1064
|
+
let errors = all_spec_errors(&spec);
|
|
1065
|
+
let missing: Vec<&String> = errors
|
|
1066
|
+
.iter()
|
|
1067
|
+
.filter(|m| m.starts_with("/agents/dangerously_skip_permissions: missing"))
|
|
1068
|
+
.collect();
|
|
1069
|
+
assert_eq!(missing.len(), 1, "exactly one missing-field error, got: {errors:#?}");
|
|
1070
|
+
assert!(
|
|
1071
|
+
missing[0].contains("`codex_implementer`"),
|
|
1072
|
+
"must name the role id, got: {}",
|
|
1073
|
+
missing[0]
|
|
1074
|
+
);
|
|
1075
|
+
assert!(
|
|
1076
|
+
missing[0].contains("This field must be declared explicitly; it controls whether the agent launches with permission prompts bypassed."),
|
|
1077
|
+
"must carry the user-facing required-field message, got: {}",
|
|
1078
|
+
missing[0]
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// 0.5.66 bypass 单源 §4.1:非 bool 值 → fail-loud 类型错,同样带角色清单 + 硬串。
|
|
1083
|
+
#[test]
|
|
1084
|
+
fn test_invalid_dangerously_type_rejected() {
|
|
1085
|
+
let text = include_str!("testdata/team.spec.yaml");
|
|
1086
|
+
let bad = text.replacen(" dangerously_skip_permissions: false\n", " dangerously_skip_permissions: \"yes\"\n", 1);
|
|
1087
|
+
assert_ne!(text, bad, "fixture must contain the field");
|
|
1088
|
+
let spec = yaml::loads(&bad).unwrap();
|
|
1089
|
+
let errors = all_spec_errors(&spec);
|
|
1090
|
+
let type_errs: Vec<&String> = errors
|
|
1091
|
+
.iter()
|
|
1092
|
+
.filter(|m| m.starts_with("/agents/dangerously_skip_permissions: must be a boolean"))
|
|
1093
|
+
.collect();
|
|
1094
|
+
assert_eq!(type_errs.len(), 1, "exactly one type error, got: {errors:#?}");
|
|
1095
|
+
assert!(
|
|
1096
|
+
type_errs[0].contains("`codex_implementer`"),
|
|
1097
|
+
"must name the role id, got: {}",
|
|
1098
|
+
type_errs[0]
|
|
1099
|
+
);
|
|
1100
|
+
assert!(
|
|
1101
|
+
type_errs[0].contains("This field must be declared explicitly; it controls whether the agent launches with permission prompts bypassed."),
|
|
1102
|
+
"must carry the user-facing required-field message, got: {}",
|
|
1103
|
+
type_errs[0]
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1023
1107
|
#[test]
|
|
1024
1108
|
fn empty_spec_matches_python_golden() {
|
|
1025
1109
|
let spec = yaml::loads("{}").unwrap();
|
|
@@ -1050,4 +1134,43 @@ mod tests {
|
|
|
1050
1134
|
]
|
|
1051
1135
|
);
|
|
1052
1136
|
}
|
|
1137
|
+
|
|
1138
|
+
// 0.5.66 bypass 单源 §4.1:7 触发面共用同一 spec 校验层——compile_team 是它们的共同
|
|
1139
|
+
// 必经点(quick-start/restart/add/fork 都调用),缺字段一律 fail-loud。此处验证
|
|
1140
|
+
// compile 级校验确实 fail-loud,即各触发面(经 compile_team)都被覆盖。
|
|
1141
|
+
#[test]
|
|
1142
|
+
fn test_all_7_triggers_run_field_check() {
|
|
1143
|
+
// compile_team 是 7 触发面的 spec 校验必经点:
|
|
1144
|
+
// quick-start(quick_start.rs:233 compile_team)
|
|
1145
|
+
// restart(rebuild.rs:3245 rebuild_runtime_spec_from_roles → compile_team)
|
|
1146
|
+
// add-agent(add_agent.rs:331 compile_role_agent)
|
|
1147
|
+
// clone-agent(clone_agent.rs:62 → add_agent)
|
|
1148
|
+
// fork-agent(fork_agent.rs:96 validate_spec)
|
|
1149
|
+
// start-agent / reset-agent(操作已编译 spec,团队创建时经 compile_team 校验)
|
|
1150
|
+
// 验证 compile_team 对缺字段的角色文档 fail-loud。
|
|
1151
|
+
let team = std::env::temp_dir().join(format!(
|
|
1152
|
+
"ta-spec-7trigger-{}",
|
|
1153
|
+
std::process::id()
|
|
1154
|
+
));
|
|
1155
|
+
let _ = std::fs::remove_dir_all(&team);
|
|
1156
|
+
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
1157
|
+
std::fs::write(
|
|
1158
|
+
team.join("TEAM.md"),
|
|
1159
|
+
"---\nname: t7\nobjective: 7 trigger.\nprovider: codex\n---\n\nteam.\n",
|
|
1160
|
+
)
|
|
1161
|
+
.unwrap();
|
|
1162
|
+
// 角色文档故意缺 dangerously_skip_permissions
|
|
1163
|
+
std::fs::write(
|
|
1164
|
+
team.join("agents").join("w.md"),
|
|
1165
|
+
"---\nname: w\nrole: Worker\nprovider: codex\ntools:\n - mcp_team\n---\n\nw.\n",
|
|
1166
|
+
)
|
|
1167
|
+
.unwrap();
|
|
1168
|
+
let err = crate::compiler::compile_team(&team).unwrap_err().to_string();
|
|
1169
|
+
assert!(
|
|
1170
|
+
err.contains("missing front matter field dangerously_skip_permissions")
|
|
1171
|
+
&& err.contains("This field must be declared explicitly; it controls whether the agent launches with permission prompts bypassed."),
|
|
1172
|
+
"compile_team must fail-loud on missing field (shared by all 7 triggers); got {err}"
|
|
1173
|
+
);
|
|
1174
|
+
let _ = std::fs::remove_dir_all(&team);
|
|
1175
|
+
}
|
|
1053
1176
|
}
|
|
@@ -39,6 +39,7 @@ agents:
|
|
|
39
39
|
- "provider_builtin"
|
|
40
40
|
- "banana"
|
|
41
41
|
permission_mode: "restricted"
|
|
42
|
+
dangerously_skip_permissions: false
|
|
42
43
|
preferred_for:
|
|
43
44
|
- "implementation"
|
|
44
45
|
- "bug_fix"
|
|
@@ -69,6 +70,7 @@ agents:
|
|
|
69
70
|
- "mcp_team"
|
|
70
71
|
- "provider_builtin"
|
|
71
72
|
permission_mode: "restricted"
|
|
73
|
+
dangerously_skip_permissions: false
|
|
72
74
|
preferred_for:
|
|
73
75
|
- "research"
|
|
74
76
|
- "architecture"
|
|
@@ -99,6 +101,7 @@ agents:
|
|
|
99
101
|
- "mcp_team"
|
|
100
102
|
- "provider_builtin"
|
|
101
103
|
permission_mode: "restricted"
|
|
104
|
+
dangerously_skip_permissions: false
|
|
102
105
|
preferred_for:
|
|
103
106
|
- "review"
|
|
104
107
|
- "risk_check"
|
|
@@ -141,7 +144,6 @@ runtime:
|
|
|
141
144
|
session_name: "teamspec-full-example"
|
|
142
145
|
auto_launch: true
|
|
143
146
|
require_user_approval_before_launch: true
|
|
144
|
-
dangerous_auto_approve: false
|
|
145
147
|
max_active_agents: 3
|
|
146
148
|
startup_order:
|
|
147
149
|
- "codex_implementer"
|
|
@@ -38,6 +38,7 @@ agents:
|
|
|
38
38
|
- "mcp_team"
|
|
39
39
|
- "provider_builtin"
|
|
40
40
|
permission_mode: "restricted"
|
|
41
|
+
dangerously_skip_permissions: false
|
|
41
42
|
preferred_for:
|
|
42
43
|
- "implementation"
|
|
43
44
|
- "bug_fix"
|
|
@@ -68,6 +69,7 @@ agents:
|
|
|
68
69
|
- "mcp_team"
|
|
69
70
|
- "provider_builtin"
|
|
70
71
|
permission_mode: "restricted"
|
|
72
|
+
dangerously_skip_permissions: false
|
|
71
73
|
preferred_for:
|
|
72
74
|
- "research"
|
|
73
75
|
- "architecture"
|
|
@@ -98,6 +100,7 @@ agents:
|
|
|
98
100
|
- "mcp_team"
|
|
99
101
|
- "provider_builtin"
|
|
100
102
|
permission_mode: "restricted"
|
|
103
|
+
dangerously_skip_permissions: false
|
|
101
104
|
preferred_for:
|
|
102
105
|
- "review"
|
|
103
106
|
- "risk_check"
|
|
@@ -140,7 +143,6 @@ runtime:
|
|
|
140
143
|
session_name: "teamspec-full-example"
|
|
141
144
|
auto_launch: true
|
|
142
145
|
require_user_approval_before_launch: true
|
|
143
|
-
dangerous_auto_approve: false
|
|
144
146
|
max_active_agents: 3
|
|
145
147
|
startup_order:
|
|
146
148
|
- "codex_implementer"
|
|
@@ -38,6 +38,7 @@ agents:
|
|
|
38
38
|
- mcp_team
|
|
39
39
|
- provider_builtin
|
|
40
40
|
permission_mode: restricted
|
|
41
|
+
dangerously_skip_permissions: false
|
|
41
42
|
preferred_for:
|
|
42
43
|
- implementation
|
|
43
44
|
- bug_fix
|
|
@@ -68,6 +69,7 @@ agents:
|
|
|
68
69
|
- mcp_team
|
|
69
70
|
- provider_builtin
|
|
70
71
|
permission_mode: restricted
|
|
72
|
+
dangerously_skip_permissions: false
|
|
71
73
|
preferred_for:
|
|
72
74
|
- research
|
|
73
75
|
- architecture
|
|
@@ -98,6 +100,7 @@ agents:
|
|
|
98
100
|
- mcp_team
|
|
99
101
|
- provider_builtin
|
|
100
102
|
permission_mode: restricted
|
|
103
|
+
dangerously_skip_permissions: false
|
|
101
104
|
preferred_for:
|
|
102
105
|
- review
|
|
103
106
|
- risk_check
|
|
@@ -140,7 +143,6 @@ runtime:
|
|
|
140
143
|
session_name: teamspec-full-example
|
|
141
144
|
auto_launch: true
|
|
142
145
|
require_user_approval_before_launch: true
|
|
143
|
-
dangerous_auto_approve: false
|
|
144
146
|
max_active_agents: 3
|
|
145
147
|
startup_order:
|
|
146
148
|
- codex_implementer
|
|
@@ -1,10 +1,31 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 为运行时提供有界的进程输出读取与前台进程组探测
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: bounded_command_output_with_probe
|
|
6
|
+
//! what: 在固定超时内执行命令并读取 stdout,超时后终止子进程
|
|
7
|
+
//! - name: pane_foreground_and_root_pgrp
|
|
8
|
+
//! what: 读取 pane PID 的前台与根进程组 ID,无法判定时返回 None
|
|
9
|
+
//! depends:
|
|
10
|
+
//! - std
|
|
11
|
+
//! boundary:
|
|
12
|
+
//! - 只负责有界探测、临时 stdout 文件的独占创建与清理
|
|
13
|
+
//! - 不决定 shutdown 状态,不重试探测,不修改调用方行为
|
|
14
|
+
//! arch:
|
|
15
|
+
//! allowed_dependencies:
|
|
16
|
+
//! - std
|
|
17
|
+
//! maturity: wired
|
|
18
|
+
//! ---
|
|
19
|
+
|
|
1
20
|
use std::cell::RefCell;
|
|
2
21
|
use std::fs::OpenOptions;
|
|
3
22
|
use std::io::{self, Read};
|
|
4
23
|
use std::process::{Command, ExitStatus, Stdio};
|
|
24
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
5
25
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
|
6
26
|
|
|
7
27
|
const DEFAULT_TIMEOUT: Duration = Duration::from_millis(900);
|
|
28
|
+
static NEXT_TEMP_OUTPUT_ID: AtomicU64 = AtomicU64::new(0);
|
|
8
29
|
|
|
9
30
|
thread_local! {
|
|
10
31
|
static PROBE_TIMEOUT: RefCell<Option<ProbeTimeout>> = const { RefCell::new(None) };
|
|
@@ -23,19 +44,31 @@ pub(crate) struct BoundedCommandOutput {
|
|
|
23
44
|
pub(crate) stdout: Vec<u8>,
|
|
24
45
|
}
|
|
25
46
|
|
|
47
|
+
/// ---
|
|
48
|
+
/// purpose: 清除当前线程记录的探测超时
|
|
49
|
+
/// ---
|
|
26
50
|
pub(crate) fn clear_probe_timeout() {
|
|
27
51
|
PROBE_TIMEOUT.with(|timeout| *timeout.borrow_mut() = None);
|
|
28
52
|
}
|
|
29
53
|
|
|
54
|
+
/// ---
|
|
55
|
+
/// purpose: 判断当前线程是否记录了探测超时
|
|
56
|
+
/// ---
|
|
30
57
|
pub(crate) fn probe_timed_out() -> bool {
|
|
31
58
|
PROBE_TIMEOUT.with(|timeout| timeout.borrow().is_some())
|
|
32
59
|
}
|
|
33
60
|
|
|
61
|
+
/// ---
|
|
62
|
+
/// purpose: 获取当前线程记录的探测超时详情
|
|
63
|
+
/// ---
|
|
34
64
|
pub(crate) fn probe_timeout() -> Option<ProbeTimeout> {
|
|
35
65
|
PROBE_TIMEOUT.with(|timeout| timeout.borrow().clone())
|
|
36
66
|
}
|
|
37
67
|
|
|
38
68
|
#[cfg(test)]
|
|
69
|
+
/// ---
|
|
70
|
+
/// purpose: 为单元测试设置探测超时详情
|
|
71
|
+
/// ---
|
|
39
72
|
pub(crate) fn set_probe_timeout_for_test(probe: &'static str, pid: Option<u32>, timeout_ms: u64) {
|
|
40
73
|
PROBE_TIMEOUT.with(|current| {
|
|
41
74
|
*current.borrow_mut() = Some(ProbeTimeout {
|
|
@@ -46,6 +79,9 @@ pub(crate) fn set_probe_timeout_for_test(probe: &'static str, pid: Option<u32>,
|
|
|
46
79
|
});
|
|
47
80
|
}
|
|
48
81
|
|
|
82
|
+
/// ---
|
|
83
|
+
/// purpose: 执行一个带默认超时与探测标识的命令并读取 stdout
|
|
84
|
+
/// ---
|
|
49
85
|
pub(crate) fn bounded_command_output_with_probe(
|
|
50
86
|
command: &mut Command,
|
|
51
87
|
probe: &'static str,
|
|
@@ -114,9 +150,14 @@ fn temp_output_path(kind: &str) -> std::path::PathBuf {
|
|
|
114
150
|
.duration_since(UNIX_EPOCH)
|
|
115
151
|
.map(|duration| duration.as_nanos())
|
|
116
152
|
.unwrap_or(0);
|
|
153
|
+
temp_output_path_with_nanos(kind, nanos)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn temp_output_path_with_nanos(kind: &str, nanos: u128) -> std::path::PathBuf {
|
|
157
|
+
let unique_id = NEXT_TEMP_OUTPUT_ID.fetch_add(1, Ordering::Relaxed);
|
|
117
158
|
std::env::temp_dir().join(format!(
|
|
118
|
-
"team-agent-os-probe-{}-{nanos}.{kind}",
|
|
119
|
-
std::process::id()
|
|
159
|
+
"team-agent-os-probe-{}-{nanos}-{unique_id}.{kind}",
|
|
160
|
+
std::process::id(),
|
|
120
161
|
))
|
|
121
162
|
}
|
|
122
163
|
|
|
@@ -146,6 +187,9 @@ fn read_and_remove(path: &std::path::Path) -> Vec<u8> {
|
|
|
146
187
|
///
|
|
147
188
|
/// Bounded via [`run_bounded_command`] so a wedged ps cannot stall the
|
|
148
189
|
/// coordinator tick.
|
|
190
|
+
/// ---
|
|
191
|
+
/// purpose: 读取 pane PID 的前台与根进程组 ID,无法判定时返回 None
|
|
192
|
+
/// ---
|
|
149
193
|
pub(crate) fn pane_foreground_and_root_pgrp(pane_pid: u32) -> io::Result<Option<(u32, u32)>> {
|
|
150
194
|
let mut command = Command::new("ps");
|
|
151
195
|
command.args(["-o", "tpgid=,pgid=", "-p", &pane_pid.to_string()]);
|
|
@@ -206,4 +250,31 @@ mod fg_pgrp_tests {
|
|
|
206
250
|
pane_foreground_and_root_pgrp(0xFFFF_FFFE).expect("must not error on missing pid");
|
|
207
251
|
assert!(result.is_none(), "missing pid → None");
|
|
208
252
|
}
|
|
253
|
+
|
|
254
|
+
#[test]
|
|
255
|
+
fn temp_output_create_new_requires_atomic_discriminator() {
|
|
256
|
+
let fixed_nanos = 0x5eed_u128;
|
|
257
|
+
let first_path = temp_output_path_with_nanos("stdout", fixed_nanos);
|
|
258
|
+
let second_path = temp_output_path_with_nanos("stdout", fixed_nanos);
|
|
259
|
+
let _ = std::fs::remove_file(&first_path);
|
|
260
|
+
let _ = std::fs::remove_file(&second_path);
|
|
261
|
+
|
|
262
|
+
let first = OpenOptions::new()
|
|
263
|
+
.create_new(true)
|
|
264
|
+
.read(true)
|
|
265
|
+
.write(true)
|
|
266
|
+
.open(&first_path)
|
|
267
|
+
.expect("first atomic path create");
|
|
268
|
+
let second = OpenOptions::new()
|
|
269
|
+
.create_new(true)
|
|
270
|
+
.read(true)
|
|
271
|
+
.write(true)
|
|
272
|
+
.open(&second_path)
|
|
273
|
+
.expect("second atomic path create");
|
|
274
|
+
assert_ne!(first_path, second_path, "atomic discriminator must advance");
|
|
275
|
+
drop(first);
|
|
276
|
+
drop(second);
|
|
277
|
+
let _ = std::fs::remove_file(first_path);
|
|
278
|
+
let _ = std::fs::remove_file(second_path);
|
|
279
|
+
}
|
|
209
280
|
}
|