@team-agent/installer 0.5.66 → 0.5.69
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 +156 -0
- package/crates/team-agent/src/cli/emit.rs +6 -5
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +22 -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 +3 -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 +121 -5
- 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 +31 -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 +3 -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 +973 -75
- 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 +398 -26
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +80 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +60 -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 +548 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +120 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +208 -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 +482 -1
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +830 -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 +87 -3
- 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 +127 -25
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +140 -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 +107 -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 +99 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +52 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +214 -160
- package/crates/team-agent/src/lifecycle/restart/common.rs +513 -37
- 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 +233 -6
- 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/claude_profile_launch_red.rs +24 -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 +464 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +247 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_g0.stdout.txt +222 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +787 -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 +485 -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 +1 -1
- 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/pi_catalog_red.rs +97 -0
- package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +128 -0
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +207 -0
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +838 -0
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +320 -0
- package/crates/team-agent/src/lifecycle/tests/pi_lifecycle_doctor_red.rs +98 -0
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +216 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -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 +30 -13
- package/crates/team-agent/src/lifecycle/types.rs +61 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +143 -12
- 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 +430 -41
- 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 +17 -6
- 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 +259 -6
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +89 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +3 -0
- package/crates/team-agent/src/provider/adapters/pi.rs +152 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +47 -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 +78 -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 +205 -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/pi.rs +172 -0
- package/crates/team-agent/src/provider/session_scan.rs +12 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +384 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +29 -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 +1116 -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 +32 -239
- 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
|
@@ -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");
|
|
@@ -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,14 @@ 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,
|
|
30
|
+
/// Pi coding agent CLI. Models are selected from its live catalog.
|
|
31
|
+
Pi,
|
|
24
32
|
Fake,
|
|
25
33
|
}
|
|
26
34
|
|
|
@@ -33,7 +41,8 @@ pub enum Provider {
|
|
|
33
41
|
/// Provider support:
|
|
34
42
|
/// - claude / claude_code: low|medium|high|xhigh|max → `--effort <level>`
|
|
35
43
|
/// - codex: low|medium|high|xhigh (NOT max) → `-c model_reasoning_effort=<level>`
|
|
36
|
-
/// -
|
|
44
|
+
/// - grok: low|medium|high|xhigh (NOT max) → `--effort <level>`
|
|
45
|
+
/// - copilot / gemini_cli / cursor_agent / fake: unsupported — warning event, no flag
|
|
37
46
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
38
47
|
pub enum ProviderEffort {
|
|
39
48
|
#[serde(rename = "low")]
|
|
@@ -78,13 +87,15 @@ impl ProviderEffort {
|
|
|
78
87
|
}
|
|
79
88
|
|
|
80
89
|
/// True when the given provider supports this effort level. `max` is
|
|
81
|
-
/// Claude-only; other levels are supported by Claude
|
|
82
|
-
///
|
|
90
|
+
/// Claude-only; other levels are supported by Claude, Codex, and Grok.
|
|
91
|
+
/// Copilot/Gemini/CursorAgent/Fake ignore the field (warning at runtime).
|
|
83
92
|
pub fn is_supported_by(self, provider: Provider) -> bool {
|
|
84
93
|
match provider {
|
|
85
|
-
Provider::Claude | Provider::ClaudeCode => true,
|
|
86
|
-
Provider::Codex => !self.is_claude_only(),
|
|
87
|
-
Provider::Copilot | Provider::GeminiCli | Provider::Fake =>
|
|
94
|
+
Provider::Claude | Provider::ClaudeCode | Provider::Pi => true,
|
|
95
|
+
Provider::Codex | Provider::Grok => !self.is_claude_only(),
|
|
96
|
+
Provider::Copilot | Provider::GeminiCli | Provider::CursorAgent | Provider::Fake => {
|
|
97
|
+
false
|
|
98
|
+
}
|
|
88
99
|
}
|
|
89
100
|
}
|
|
90
101
|
}
|
|
@@ -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 | Provider::Pi => 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)。
|
|
@@ -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
|
}
|
|
@@ -342,6 +342,28 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
342
342
|
native_mcp_config: false,
|
|
343
343
|
writes_global_settings: true,
|
|
344
344
|
},
|
|
345
|
+
// Grok: --resume/--fork-session 齐全 → resume/fork true; 无原生
|
|
346
|
+
// --mcp-config flag → native_mcp_config false (MCP 走 .grok/config.toml)。
|
|
347
|
+
Provider::Grok => ProviderCaps {
|
|
348
|
+
resume: true,
|
|
349
|
+
fork: true,
|
|
350
|
+
native_mcp_config: false,
|
|
351
|
+
writes_global_settings: false,
|
|
352
|
+
},
|
|
353
|
+
// Cursor agent: --resume [chatId] 有 → resume true; 无 fork flag →
|
|
354
|
+
// fork false; 无 --mcp-config → native_mcp_config false。
|
|
355
|
+
Provider::CursorAgent => ProviderCaps {
|
|
356
|
+
resume: true,
|
|
357
|
+
fork: false,
|
|
358
|
+
native_mcp_config: false,
|
|
359
|
+
writes_global_settings: false,
|
|
360
|
+
},
|
|
361
|
+
Provider::Pi => ProviderCaps {
|
|
362
|
+
resume: true,
|
|
363
|
+
fork: false,
|
|
364
|
+
native_mcp_config: false,
|
|
365
|
+
writes_global_settings: false,
|
|
366
|
+
},
|
|
345
367
|
Provider::Fake => ProviderCaps {
|
|
346
368
|
resume: false,
|
|
347
369
|
fork: false,
|
|
@@ -389,6 +411,10 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
389
411
|
// 标"weak / no auth-status command available";Compatible/Official 走 BYOK
|
|
390
412
|
// 路径,有 COPILOT_PROVIDER_BASE_URL 时已脱离 GitHub 登录通道。
|
|
391
413
|
Provider::Copilot => copilot_auth_hint(auth_mode),
|
|
414
|
+
// Cursor: 登录探测未接。U-17 前不得把未观测写成 Present(shim 对
|
|
415
|
+
// `cursor-agent status` 会假绿 Logged in)。缺键 ≠ 已知未登录,故不用 Missing。
|
|
416
|
+
Provider::CursorAgent => AuthHintStatus::Unknown,
|
|
417
|
+
Provider::Pi => AuthHintStatus::Unknown,
|
|
392
418
|
_ => match auth_mode {
|
|
393
419
|
AuthMode::Subscription => AuthHintStatus::Present,
|
|
394
420
|
AuthMode::OfficialApi | AuthMode::CompatibleApi => AuthHintStatus::MissingOrUnknown,
|
|
@@ -454,6 +480,27 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
454
480
|
}
|
|
455
481
|
Ok(argv)
|
|
456
482
|
}
|
|
483
|
+
Provider::Grok => Ok(grok_launch_command(
|
|
484
|
+
self,
|
|
485
|
+
auth_mode,
|
|
486
|
+
mcp_config,
|
|
487
|
+
system_prompt,
|
|
488
|
+
model,
|
|
489
|
+
tools,
|
|
490
|
+
)?),
|
|
491
|
+
Provider::CursorAgent => Ok(cursor_agent_base_command(
|
|
492
|
+
self,
|
|
493
|
+
auth_mode,
|
|
494
|
+
mcp_config,
|
|
495
|
+
system_prompt,
|
|
496
|
+
model,
|
|
497
|
+
tools,
|
|
498
|
+
false,
|
|
499
|
+
None,
|
|
500
|
+
)?),
|
|
501
|
+
Provider::Pi => Err(ProviderError::Command(
|
|
502
|
+
"Pi commands require the shared lifecycle materializer".to_string(),
|
|
503
|
+
)),
|
|
457
504
|
Provider::Fake => Ok(fake_worker_command()),
|
|
458
505
|
}
|
|
459
506
|
}
|
|
@@ -543,6 +590,32 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
543
590
|
// * `-C <workspace>`(双保险,即便 spawn cwd 漂移也能锚定)
|
|
544
591
|
// mcp_config inline 形态由 build_command 写入,launch 路径会用
|
|
545
592
|
// point_native_mcp_config_at_file 重写为 @<file> 形(§C1 note)。
|
|
593
|
+
Provider::Grok => {
|
|
594
|
+
// Align with Claude/Copilot: the UUID on `--session-id` is
|
|
595
|
+
// also the capture hint. `CommandPlan::argv_only` used to
|
|
596
|
+
// drop expected_session_id, so `_pending_session_id` was
|
|
597
|
+
// never written and restart could not resume.
|
|
598
|
+
let expected = next_session_token();
|
|
599
|
+
let mut argv = grok_base_command(
|
|
600
|
+
self,
|
|
601
|
+
ctx.auth_mode,
|
|
602
|
+
ctx.mcp_config,
|
|
603
|
+
ctx.system_prompt,
|
|
604
|
+
ctx.model,
|
|
605
|
+
ctx.tools,
|
|
606
|
+
ctx.profile_launch
|
|
607
|
+
.is_some_and(|profile| profile.managed_mcp_config),
|
|
608
|
+
ctx.effort,
|
|
609
|
+
)?;
|
|
610
|
+
argv.push("--session-id".to_string());
|
|
611
|
+
argv.push(expected.clone());
|
|
612
|
+
Ok(CommandPlan {
|
|
613
|
+
argv,
|
|
614
|
+
expected_session_id: Some(SessionId::new(expected)),
|
|
615
|
+
provider_projects_root: None,
|
|
616
|
+
managed_mcp_config: false,
|
|
617
|
+
})
|
|
618
|
+
}
|
|
546
619
|
Provider::Copilot => {
|
|
547
620
|
let expected = next_session_token();
|
|
548
621
|
let mut argv = copilot_base_command(
|
|
@@ -561,6 +634,30 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
561
634
|
managed_mcp_config: false,
|
|
562
635
|
})
|
|
563
636
|
}
|
|
637
|
+
Provider::CursorAgent => {
|
|
638
|
+
// U-01 create-chat 无登录超时;禁止发明 --session-id。
|
|
639
|
+
// pending 只能是 CLI chatId,本拍不造假 uuid。
|
|
640
|
+
let argv = cursor_agent_base_command(
|
|
641
|
+
self,
|
|
642
|
+
ctx.auth_mode,
|
|
643
|
+
ctx.mcp_config,
|
|
644
|
+
ctx.system_prompt,
|
|
645
|
+
ctx.model,
|
|
646
|
+
ctx.tools,
|
|
647
|
+
ctx.profile_launch
|
|
648
|
+
.is_some_and(|profile| profile.managed_mcp_config),
|
|
649
|
+
ctx.effort,
|
|
650
|
+
)?;
|
|
651
|
+
Ok(CommandPlan {
|
|
652
|
+
argv,
|
|
653
|
+
expected_session_id: None,
|
|
654
|
+
provider_projects_root: None,
|
|
655
|
+
managed_mcp_config: false,
|
|
656
|
+
})
|
|
657
|
+
}
|
|
658
|
+
Provider::Pi => Err(ProviderError::Command(
|
|
659
|
+
"Pi commands require the shared lifecycle materializer".to_string(),
|
|
660
|
+
)),
|
|
564
661
|
_ => self
|
|
565
662
|
.build_command_with_tools(
|
|
566
663
|
ctx.auth_mode,
|
|
@@ -725,6 +822,43 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
725
822
|
argv.push(session_id.as_str().to_string());
|
|
726
823
|
Ok(argv)
|
|
727
824
|
}
|
|
825
|
+
// Grok resume: base + `--resume <id>` (grok -r/--resume 接受 id|title)。
|
|
826
|
+
Provider::Grok => {
|
|
827
|
+
let mut argv = grok_base_command(
|
|
828
|
+
self,
|
|
829
|
+
auth_mode,
|
|
830
|
+
mcp_config,
|
|
831
|
+
system_prompt,
|
|
832
|
+
model,
|
|
833
|
+
tools,
|
|
834
|
+
false,
|
|
835
|
+
None,
|
|
836
|
+
)?;
|
|
837
|
+
argv.push("--resume".to_string());
|
|
838
|
+
argv.push(session_id.as_str().to_string());
|
|
839
|
+
Ok(argv)
|
|
840
|
+
}
|
|
841
|
+
// Cursor resume: base + `--resume <chatId>`。
|
|
842
|
+
Provider::CursorAgent => {
|
|
843
|
+
let mut argv = cursor_agent_base_command(
|
|
844
|
+
self,
|
|
845
|
+
auth_mode,
|
|
846
|
+
mcp_config,
|
|
847
|
+
system_prompt,
|
|
848
|
+
model,
|
|
849
|
+
tools,
|
|
850
|
+
false,
|
|
851
|
+
None,
|
|
852
|
+
)?;
|
|
853
|
+
// Put --resume immediately after `agent` so the CLI treats it as
|
|
854
|
+
// the session mode rather than a trailing flag after --workspace.
|
|
855
|
+
argv.insert(1, "--resume".to_string());
|
|
856
|
+
argv.insert(2, session_id.as_str().to_string());
|
|
857
|
+
Ok(argv)
|
|
858
|
+
}
|
|
859
|
+
Provider::Pi => Err(ProviderError::ResumeUnavailable(
|
|
860
|
+
"pi resume requires the captured exact session path".to_string(),
|
|
861
|
+
)),
|
|
728
862
|
Provider::GeminiCli | Provider::Fake => Err(ProviderError::ResumeUnavailable(format!(
|
|
729
863
|
"{} resume requires session_id",
|
|
730
864
|
provider_wire(self.provider)
|
|
@@ -801,6 +935,54 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
801
935
|
argv.push(session_id.as_str().to_string());
|
|
802
936
|
Ok(CommandPlan::argv_only(argv))
|
|
803
937
|
}
|
|
938
|
+
Provider::Grok => {
|
|
939
|
+
let Some(session_id) = session_id else {
|
|
940
|
+
return Err(ProviderError::ResumeUnavailable(
|
|
941
|
+
"resume requires session_id".to_string(),
|
|
942
|
+
));
|
|
943
|
+
};
|
|
944
|
+
let mut argv = grok_base_command(
|
|
945
|
+
self,
|
|
946
|
+
ctx.auth_mode,
|
|
947
|
+
ctx.mcp_config,
|
|
948
|
+
ctx.system_prompt,
|
|
949
|
+
ctx.model,
|
|
950
|
+
ctx.tools,
|
|
951
|
+
ctx.profile_launch
|
|
952
|
+
.is_some_and(|profile| profile.managed_mcp_config),
|
|
953
|
+
ctx.effort,
|
|
954
|
+
)?;
|
|
955
|
+
argv.push("--resume".to_string());
|
|
956
|
+
argv.push(session_id.as_str().to_string());
|
|
957
|
+
Ok(CommandPlan::argv_only(argv))
|
|
958
|
+
}
|
|
959
|
+
Provider::CursorAgent => {
|
|
960
|
+
let Some(session_id) = session_id else {
|
|
961
|
+
return Err(ProviderError::ResumeUnavailable(
|
|
962
|
+
"cursor_agent resume requires chatId; empty --resume is forbidden (picker)".to_string(),
|
|
963
|
+
));
|
|
964
|
+
};
|
|
965
|
+
let raw = session_id.as_str();
|
|
966
|
+
if raw.is_empty() || raw.contains('/') || raw.contains('\\') || raw.contains('\0') {
|
|
967
|
+
return Err(ProviderError::ResumeUnavailable(
|
|
968
|
+
"cursor_agent resume rejected forged or empty chatId".to_string(),
|
|
969
|
+
));
|
|
970
|
+
}
|
|
971
|
+
let mut argv = cursor_agent_base_command(
|
|
972
|
+
self,
|
|
973
|
+
ctx.auth_mode,
|
|
974
|
+
ctx.mcp_config,
|
|
975
|
+
ctx.system_prompt,
|
|
976
|
+
ctx.model,
|
|
977
|
+
ctx.tools,
|
|
978
|
+
ctx.profile_launch
|
|
979
|
+
.is_some_and(|profile| profile.managed_mcp_config),
|
|
980
|
+
ctx.effort,
|
|
981
|
+
)?;
|
|
982
|
+
argv.push("--resume".to_string());
|
|
983
|
+
argv.push(raw.to_string());
|
|
984
|
+
Ok(CommandPlan::argv_only(argv))
|
|
985
|
+
}
|
|
804
986
|
_ => self
|
|
805
987
|
.build_resume_command_with_context(
|
|
806
988
|
session_id,
|
|
@@ -882,7 +1064,27 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
882
1064
|
Provider::Copilot => Err(ProviderError::CapabilityUnsupported(
|
|
883
1065
|
"copilot CLI 无 fork 旗标,session-store 不支持 branched continuation".to_string(),
|
|
884
1066
|
)),
|
|
885
|
-
|
|
1067
|
+
// Grok fork: base + 新 `--session-id` + `--resume <src>` + `--fork-session`
|
|
1068
|
+
// (grok help: --fork-session 需配合 --resume/--continue)。
|
|
1069
|
+
Provider::Grok => {
|
|
1070
|
+
let mut argv = grok_base_command(
|
|
1071
|
+
self,
|
|
1072
|
+
auth_mode,
|
|
1073
|
+
mcp_config,
|
|
1074
|
+
system_prompt,
|
|
1075
|
+
model,
|
|
1076
|
+
tools,
|
|
1077
|
+
false,
|
|
1078
|
+
None,
|
|
1079
|
+
)?;
|
|
1080
|
+
argv.push("--session-id".to_string());
|
|
1081
|
+
argv.push(next_session_token());
|
|
1082
|
+
argv.push("--resume".to_string());
|
|
1083
|
+
argv.push(session_id.as_str().to_string());
|
|
1084
|
+
argv.push("--fork-session".to_string());
|
|
1085
|
+
Ok(argv)
|
|
1086
|
+
}
|
|
1087
|
+
Provider::Pi | Provider::CursorAgent | Provider::GeminiCli | Provider::Fake => {
|
|
886
1088
|
Err(ProviderError::CapabilityUnsupported(format!(
|
|
887
1089
|
"{} does not support native session fork",
|
|
888
1090
|
provider_wire(self.provider)
|
|
@@ -1006,6 +1208,30 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
1006
1208
|
managed_mcp_config: profile.managed_mcp_config,
|
|
1007
1209
|
})
|
|
1008
1210
|
}
|
|
1211
|
+
Provider::Grok => {
|
|
1212
|
+
let Some(session_id) = session_id else {
|
|
1213
|
+
return Err(ProviderError::ResumeUnavailable(
|
|
1214
|
+
"fork requires session_id".to_string(),
|
|
1215
|
+
));
|
|
1216
|
+
};
|
|
1217
|
+
let mut argv = grok_base_command(
|
|
1218
|
+
self,
|
|
1219
|
+
ctx.auth_mode,
|
|
1220
|
+
ctx.mcp_config,
|
|
1221
|
+
ctx.system_prompt,
|
|
1222
|
+
ctx.model,
|
|
1223
|
+
ctx.tools,
|
|
1224
|
+
ctx.profile_launch
|
|
1225
|
+
.is_some_and(|profile| profile.managed_mcp_config),
|
|
1226
|
+
ctx.effort,
|
|
1227
|
+
)?;
|
|
1228
|
+
argv.push("--session-id".to_string());
|
|
1229
|
+
argv.push(next_session_token());
|
|
1230
|
+
argv.push("--resume".to_string());
|
|
1231
|
+
argv.push(session_id.as_str().to_string());
|
|
1232
|
+
argv.push("--fork-session".to_string());
|
|
1233
|
+
Ok(CommandPlan::argv_only(argv))
|
|
1234
|
+
}
|
|
1009
1235
|
_ => self
|
|
1010
1236
|
.fork_with_context(
|
|
1011
1237
|
session_id,
|
|
@@ -1061,14 +1287,32 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
1061
1287
|
r"working|processing",
|
|
1062
1288
|
r"Error|panic",
|
|
1063
1289
|
),
|
|
1064
|
-
|
|
1290
|
+
// Pi: 无已验证 turn-state 检测;占位串不会匹配真实 pane chrome。
|
|
1291
|
+
Provider::Pi => patterns(
|
|
1292
|
+
r"pi-idle-unset-until-turn-state",
|
|
1293
|
+
r"pi-busy-unset-until-turn-state",
|
|
1294
|
+
r"pi-error-unset-until-turn-state",
|
|
1295
|
+
),
|
|
1296
|
+
// Grok: 暂无专有 status 正则, 沿用 generic。
|
|
1297
|
+
Provider::Grok | Provider::GeminiCli | Provider::Fake => {
|
|
1065
1298
|
patterns(r">", r"working|processing", r"Error|Traceback")
|
|
1066
1299
|
}
|
|
1300
|
+
// Cursor: U-15 未过前不得用 generic `>` 单独判已证实 idle。
|
|
1301
|
+
// 字面量 `cursor-idle-unset-until-u15` 不会出现在屏上。
|
|
1302
|
+
Provider::CursorAgent => patterns(
|
|
1303
|
+
r"cursor-idle-unset-until-u15",
|
|
1304
|
+
r"cursor-busy-unset-until-u15",
|
|
1305
|
+
r"Error|Traceback",
|
|
1306
|
+
),
|
|
1067
1307
|
}
|
|
1068
1308
|
}
|
|
1069
1309
|
|
|
1070
|
-
fn validate_model(&self,
|
|
1071
|
-
|
|
1310
|
+
fn validate_model(&self, model: &str) -> Result<bool, ProviderError> {
|
|
1311
|
+
if self.provider != Provider::Pi {
|
|
1312
|
+
return Ok(true);
|
|
1313
|
+
}
|
|
1314
|
+
let (_, catalog) = crate::lifecycle::launch::pi_mcp::resolve_pi_executable_chain()?;
|
|
1315
|
+
Ok(crate::lifecycle::launch::pi_mcp::select_exact_pi_model(&catalog, model).is_ok())
|
|
1072
1316
|
}
|
|
1073
1317
|
}
|
|
1074
1318
|
|
|
@@ -1164,7 +1408,9 @@ pub(crate) fn json_inline(value: &serde_json::Value) -> String {
|
|
|
1164
1408
|
use super::adapters::claude::{claude_base_command, claude_launch_command};
|
|
1165
1409
|
use super::adapters::codex::codex_base_command;
|
|
1166
1410
|
use super::adapters::copilot::{copilot_base_command, copilot_base_command_resume};
|
|
1411
|
+
use super::adapters::cursor_agent::cursor_agent_base_command;
|
|
1167
1412
|
use super::adapters::fake::fake_worker_command;
|
|
1413
|
+
use super::adapters::grok::{grok_base_command, grok_launch_command};
|
|
1168
1414
|
|
|
1169
1415
|
/// Contract C / MUST-8: the per-worker Team Agent MCP server config used by Claude
|
|
1170
1416
|
/// (`--mcp-config`) and Codex (`-c mcp_servers.*` injection). Placeholders
|
|
@@ -1249,9 +1495,16 @@ pub(crate) fn next_session_token() -> String {
|
|
|
1249
1495
|
#[cfg(test)]
|
|
1250
1496
|
mod fork_materialization_source_guard {
|
|
1251
1497
|
#[test]
|
|
1252
|
-
fn
|
|
1498
|
+
fn lifecycle_uses_in_window_fork_not_session_file_materialization() {
|
|
1253
1499
|
let source = include_str!("../lifecycle/launch/fork_agent.rs");
|
|
1254
|
-
assert!(
|
|
1500
|
+
assert!(
|
|
1501
|
+
source.contains("in_window_fork(") && source.contains("inject_clean_command"),
|
|
1502
|
+
"fork must stay on the shared in-window inject path"
|
|
1503
|
+
);
|
|
1504
|
+
assert!(
|
|
1505
|
+
!source.contains(".materialize_fork_backing("),
|
|
1506
|
+
"session-file fork materialization must stay gone"
|
|
1507
|
+
);
|
|
1255
1508
|
assert!(!source.contains("materialize_codex_fork"));
|
|
1256
1509
|
assert!(!source.contains("codex_fork"));
|
|
1257
1510
|
}
|