@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
|
@@ -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,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)。
|
|
@@ -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,22 @@ 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
|
+
},
|
|
345
361
|
Provider::Fake => ProviderCaps {
|
|
346
362
|
resume: false,
|
|
347
363
|
fork: false,
|
|
@@ -389,6 +405,9 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
389
405
|
// 标"weak / no auth-status command available";Compatible/Official 走 BYOK
|
|
390
406
|
// 路径,有 COPILOT_PROVIDER_BASE_URL 时已脱离 GitHub 登录通道。
|
|
391
407
|
Provider::Copilot => copilot_auth_hint(auth_mode),
|
|
408
|
+
// Cursor: 登录探测未接。U-17 前不得把未观测写成 Present(shim 对
|
|
409
|
+
// `cursor-agent status` 会假绿 Logged in)。缺键 ≠ 已知未登录,故不用 Missing。
|
|
410
|
+
Provider::CursorAgent => AuthHintStatus::Unknown,
|
|
392
411
|
_ => match auth_mode {
|
|
393
412
|
AuthMode::Subscription => AuthHintStatus::Present,
|
|
394
413
|
AuthMode::OfficialApi | AuthMode::CompatibleApi => AuthHintStatus::MissingOrUnknown,
|
|
@@ -454,6 +473,24 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
454
473
|
}
|
|
455
474
|
Ok(argv)
|
|
456
475
|
}
|
|
476
|
+
Provider::Grok => Ok(grok_launch_command(
|
|
477
|
+
self,
|
|
478
|
+
auth_mode,
|
|
479
|
+
mcp_config,
|
|
480
|
+
system_prompt,
|
|
481
|
+
model,
|
|
482
|
+
tools,
|
|
483
|
+
)?),
|
|
484
|
+
Provider::CursorAgent => Ok(cursor_agent_base_command(
|
|
485
|
+
self,
|
|
486
|
+
auth_mode,
|
|
487
|
+
mcp_config,
|
|
488
|
+
system_prompt,
|
|
489
|
+
model,
|
|
490
|
+
tools,
|
|
491
|
+
false,
|
|
492
|
+
None,
|
|
493
|
+
)?),
|
|
457
494
|
Provider::Fake => Ok(fake_worker_command()),
|
|
458
495
|
}
|
|
459
496
|
}
|
|
@@ -543,6 +580,32 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
543
580
|
// * `-C <workspace>`(双保险,即便 spawn cwd 漂移也能锚定)
|
|
544
581
|
// mcp_config inline 形态由 build_command 写入,launch 路径会用
|
|
545
582
|
// point_native_mcp_config_at_file 重写为 @<file> 形(§C1 note)。
|
|
583
|
+
Provider::Grok => {
|
|
584
|
+
// Align with Claude/Copilot: the UUID on `--session-id` is
|
|
585
|
+
// also the capture hint. `CommandPlan::argv_only` used to
|
|
586
|
+
// drop expected_session_id, so `_pending_session_id` was
|
|
587
|
+
// never written and restart could not resume.
|
|
588
|
+
let expected = next_session_token();
|
|
589
|
+
let mut argv = grok_base_command(
|
|
590
|
+
self,
|
|
591
|
+
ctx.auth_mode,
|
|
592
|
+
ctx.mcp_config,
|
|
593
|
+
ctx.system_prompt,
|
|
594
|
+
ctx.model,
|
|
595
|
+
ctx.tools,
|
|
596
|
+
ctx.profile_launch
|
|
597
|
+
.is_some_and(|profile| profile.managed_mcp_config),
|
|
598
|
+
ctx.effort,
|
|
599
|
+
)?;
|
|
600
|
+
argv.push("--session-id".to_string());
|
|
601
|
+
argv.push(expected.clone());
|
|
602
|
+
Ok(CommandPlan {
|
|
603
|
+
argv,
|
|
604
|
+
expected_session_id: Some(SessionId::new(expected)),
|
|
605
|
+
provider_projects_root: None,
|
|
606
|
+
managed_mcp_config: false,
|
|
607
|
+
})
|
|
608
|
+
}
|
|
546
609
|
Provider::Copilot => {
|
|
547
610
|
let expected = next_session_token();
|
|
548
611
|
let mut argv = copilot_base_command(
|
|
@@ -561,6 +624,27 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
561
624
|
managed_mcp_config: false,
|
|
562
625
|
})
|
|
563
626
|
}
|
|
627
|
+
Provider::CursorAgent => {
|
|
628
|
+
// U-01 create-chat 无登录超时;禁止发明 --session-id。
|
|
629
|
+
// pending 只能是 CLI chatId,本拍不造假 uuid。
|
|
630
|
+
let argv = cursor_agent_base_command(
|
|
631
|
+
self,
|
|
632
|
+
ctx.auth_mode,
|
|
633
|
+
ctx.mcp_config,
|
|
634
|
+
ctx.system_prompt,
|
|
635
|
+
ctx.model,
|
|
636
|
+
ctx.tools,
|
|
637
|
+
ctx.profile_launch
|
|
638
|
+
.is_some_and(|profile| profile.managed_mcp_config),
|
|
639
|
+
ctx.effort,
|
|
640
|
+
)?;
|
|
641
|
+
Ok(CommandPlan {
|
|
642
|
+
argv,
|
|
643
|
+
expected_session_id: None,
|
|
644
|
+
provider_projects_root: None,
|
|
645
|
+
managed_mcp_config: false,
|
|
646
|
+
})
|
|
647
|
+
}
|
|
564
648
|
_ => self
|
|
565
649
|
.build_command_with_tools(
|
|
566
650
|
ctx.auth_mode,
|
|
@@ -725,6 +809,40 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
725
809
|
argv.push(session_id.as_str().to_string());
|
|
726
810
|
Ok(argv)
|
|
727
811
|
}
|
|
812
|
+
// Grok resume: base + `--resume <id>` (grok -r/--resume 接受 id|title)。
|
|
813
|
+
Provider::Grok => {
|
|
814
|
+
let mut argv = grok_base_command(
|
|
815
|
+
self,
|
|
816
|
+
auth_mode,
|
|
817
|
+
mcp_config,
|
|
818
|
+
system_prompt,
|
|
819
|
+
model,
|
|
820
|
+
tools,
|
|
821
|
+
false,
|
|
822
|
+
None,
|
|
823
|
+
)?;
|
|
824
|
+
argv.push("--resume".to_string());
|
|
825
|
+
argv.push(session_id.as_str().to_string());
|
|
826
|
+
Ok(argv)
|
|
827
|
+
}
|
|
828
|
+
// Cursor resume: base + `--resume <chatId>`。
|
|
829
|
+
Provider::CursorAgent => {
|
|
830
|
+
let mut argv = cursor_agent_base_command(
|
|
831
|
+
self,
|
|
832
|
+
auth_mode,
|
|
833
|
+
mcp_config,
|
|
834
|
+
system_prompt,
|
|
835
|
+
model,
|
|
836
|
+
tools,
|
|
837
|
+
false,
|
|
838
|
+
None,
|
|
839
|
+
)?;
|
|
840
|
+
// Put --resume immediately after `agent` so the CLI treats it as
|
|
841
|
+
// the session mode rather than a trailing flag after --workspace.
|
|
842
|
+
argv.insert(1, "--resume".to_string());
|
|
843
|
+
argv.insert(2, session_id.as_str().to_string());
|
|
844
|
+
Ok(argv)
|
|
845
|
+
}
|
|
728
846
|
Provider::GeminiCli | Provider::Fake => Err(ProviderError::ResumeUnavailable(format!(
|
|
729
847
|
"{} resume requires session_id",
|
|
730
848
|
provider_wire(self.provider)
|
|
@@ -801,6 +919,54 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
801
919
|
argv.push(session_id.as_str().to_string());
|
|
802
920
|
Ok(CommandPlan::argv_only(argv))
|
|
803
921
|
}
|
|
922
|
+
Provider::Grok => {
|
|
923
|
+
let Some(session_id) = session_id else {
|
|
924
|
+
return Err(ProviderError::ResumeUnavailable(
|
|
925
|
+
"resume requires session_id".to_string(),
|
|
926
|
+
));
|
|
927
|
+
};
|
|
928
|
+
let mut argv = grok_base_command(
|
|
929
|
+
self,
|
|
930
|
+
ctx.auth_mode,
|
|
931
|
+
ctx.mcp_config,
|
|
932
|
+
ctx.system_prompt,
|
|
933
|
+
ctx.model,
|
|
934
|
+
ctx.tools,
|
|
935
|
+
ctx.profile_launch
|
|
936
|
+
.is_some_and(|profile| profile.managed_mcp_config),
|
|
937
|
+
ctx.effort,
|
|
938
|
+
)?;
|
|
939
|
+
argv.push("--resume".to_string());
|
|
940
|
+
argv.push(session_id.as_str().to_string());
|
|
941
|
+
Ok(CommandPlan::argv_only(argv))
|
|
942
|
+
}
|
|
943
|
+
Provider::CursorAgent => {
|
|
944
|
+
let Some(session_id) = session_id else {
|
|
945
|
+
return Err(ProviderError::ResumeUnavailable(
|
|
946
|
+
"cursor_agent resume requires chatId; empty --resume is forbidden (picker)".to_string(),
|
|
947
|
+
));
|
|
948
|
+
};
|
|
949
|
+
let raw = session_id.as_str();
|
|
950
|
+
if raw.is_empty() || raw.contains('/') || raw.contains('\\') || raw.contains('\0') {
|
|
951
|
+
return Err(ProviderError::ResumeUnavailable(
|
|
952
|
+
"cursor_agent resume rejected forged or empty chatId".to_string(),
|
|
953
|
+
));
|
|
954
|
+
}
|
|
955
|
+
let mut argv = cursor_agent_base_command(
|
|
956
|
+
self,
|
|
957
|
+
ctx.auth_mode,
|
|
958
|
+
ctx.mcp_config,
|
|
959
|
+
ctx.system_prompt,
|
|
960
|
+
ctx.model,
|
|
961
|
+
ctx.tools,
|
|
962
|
+
ctx.profile_launch
|
|
963
|
+
.is_some_and(|profile| profile.managed_mcp_config),
|
|
964
|
+
ctx.effort,
|
|
965
|
+
)?;
|
|
966
|
+
argv.push("--resume".to_string());
|
|
967
|
+
argv.push(raw.to_string());
|
|
968
|
+
Ok(CommandPlan::argv_only(argv))
|
|
969
|
+
}
|
|
804
970
|
_ => self
|
|
805
971
|
.build_resume_command_with_context(
|
|
806
972
|
session_id,
|
|
@@ -882,7 +1048,27 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
882
1048
|
Provider::Copilot => Err(ProviderError::CapabilityUnsupported(
|
|
883
1049
|
"copilot CLI 无 fork 旗标,session-store 不支持 branched continuation".to_string(),
|
|
884
1050
|
)),
|
|
885
|
-
|
|
1051
|
+
// Grok fork: base + 新 `--session-id` + `--resume <src>` + `--fork-session`
|
|
1052
|
+
// (grok help: --fork-session 需配合 --resume/--continue)。
|
|
1053
|
+
Provider::Grok => {
|
|
1054
|
+
let mut argv = grok_base_command(
|
|
1055
|
+
self,
|
|
1056
|
+
auth_mode,
|
|
1057
|
+
mcp_config,
|
|
1058
|
+
system_prompt,
|
|
1059
|
+
model,
|
|
1060
|
+
tools,
|
|
1061
|
+
false,
|
|
1062
|
+
None,
|
|
1063
|
+
)?;
|
|
1064
|
+
argv.push("--session-id".to_string());
|
|
1065
|
+
argv.push(next_session_token());
|
|
1066
|
+
argv.push("--resume".to_string());
|
|
1067
|
+
argv.push(session_id.as_str().to_string());
|
|
1068
|
+
argv.push("--fork-session".to_string());
|
|
1069
|
+
Ok(argv)
|
|
1070
|
+
}
|
|
1071
|
+
Provider::CursorAgent | Provider::GeminiCli | Provider::Fake => {
|
|
886
1072
|
Err(ProviderError::CapabilityUnsupported(format!(
|
|
887
1073
|
"{} does not support native session fork",
|
|
888
1074
|
provider_wire(self.provider)
|
|
@@ -1006,6 +1192,30 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
1006
1192
|
managed_mcp_config: profile.managed_mcp_config,
|
|
1007
1193
|
})
|
|
1008
1194
|
}
|
|
1195
|
+
Provider::Grok => {
|
|
1196
|
+
let Some(session_id) = session_id else {
|
|
1197
|
+
return Err(ProviderError::ResumeUnavailable(
|
|
1198
|
+
"fork requires session_id".to_string(),
|
|
1199
|
+
));
|
|
1200
|
+
};
|
|
1201
|
+
let mut argv = grok_base_command(
|
|
1202
|
+
self,
|
|
1203
|
+
ctx.auth_mode,
|
|
1204
|
+
ctx.mcp_config,
|
|
1205
|
+
ctx.system_prompt,
|
|
1206
|
+
ctx.model,
|
|
1207
|
+
ctx.tools,
|
|
1208
|
+
ctx.profile_launch
|
|
1209
|
+
.is_some_and(|profile| profile.managed_mcp_config),
|
|
1210
|
+
ctx.effort,
|
|
1211
|
+
)?;
|
|
1212
|
+
argv.push("--session-id".to_string());
|
|
1213
|
+
argv.push(next_session_token());
|
|
1214
|
+
argv.push("--resume".to_string());
|
|
1215
|
+
argv.push(session_id.as_str().to_string());
|
|
1216
|
+
argv.push("--fork-session".to_string());
|
|
1217
|
+
Ok(CommandPlan::argv_only(argv))
|
|
1218
|
+
}
|
|
1009
1219
|
_ => self
|
|
1010
1220
|
.fork_with_context(
|
|
1011
1221
|
session_id,
|
|
@@ -1061,9 +1271,17 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
1061
1271
|
r"working|processing",
|
|
1062
1272
|
r"Error|panic",
|
|
1063
1273
|
),
|
|
1064
|
-
|
|
1274
|
+
// Grok: 暂无专有 status 正则, 沿用 generic。
|
|
1275
|
+
Provider::Grok | Provider::GeminiCli | Provider::Fake => {
|
|
1065
1276
|
patterns(r">", r"working|processing", r"Error|Traceback")
|
|
1066
1277
|
}
|
|
1278
|
+
// Cursor: U-15 未过前不得用 generic `>` 单独判已证实 idle。
|
|
1279
|
+
// 字面量 `cursor-idle-unset-until-u15` 不会出现在屏上。
|
|
1280
|
+
Provider::CursorAgent => patterns(
|
|
1281
|
+
r"cursor-idle-unset-until-u15",
|
|
1282
|
+
r"cursor-busy-unset-until-u15",
|
|
1283
|
+
r"Error|Traceback",
|
|
1284
|
+
),
|
|
1067
1285
|
}
|
|
1068
1286
|
}
|
|
1069
1287
|
|
|
@@ -1164,7 +1382,9 @@ pub(crate) fn json_inline(value: &serde_json::Value) -> String {
|
|
|
1164
1382
|
use super::adapters::claude::{claude_base_command, claude_launch_command};
|
|
1165
1383
|
use super::adapters::codex::codex_base_command;
|
|
1166
1384
|
use super::adapters::copilot::{copilot_base_command, copilot_base_command_resume};
|
|
1385
|
+
use super::adapters::cursor_agent::cursor_agent_base_command;
|
|
1167
1386
|
use super::adapters::fake::fake_worker_command;
|
|
1387
|
+
use super::adapters::grok::{grok_base_command, grok_launch_command};
|
|
1168
1388
|
|
|
1169
1389
|
/// Contract C / MUST-8: the per-worker Team Agent MCP server config used by Claude
|
|
1170
1390
|
/// (`--mcp-config`) and Codex (`-c mcp_servers.*` injection). Placeholders
|
|
@@ -1249,9 +1469,16 @@ pub(crate) fn next_session_token() -> String {
|
|
|
1249
1469
|
#[cfg(test)]
|
|
1250
1470
|
mod fork_materialization_source_guard {
|
|
1251
1471
|
#[test]
|
|
1252
|
-
fn
|
|
1472
|
+
fn lifecycle_uses_in_window_fork_not_session_file_materialization() {
|
|
1253
1473
|
let source = include_str!("../lifecycle/launch/fork_agent.rs");
|
|
1254
|
-
assert!(
|
|
1474
|
+
assert!(
|
|
1475
|
+
source.contains("in_window_fork(") && source.contains("inject_clean_command"),
|
|
1476
|
+
"fork must stay on the shared in-window inject path"
|
|
1477
|
+
);
|
|
1478
|
+
assert!(
|
|
1479
|
+
!source.contains(".materialize_fork_backing("),
|
|
1480
|
+
"session-file fork materialization must stay gone"
|
|
1481
|
+
);
|
|
1255
1482
|
assert!(!source.contains("materialize_codex_fork"));
|
|
1256
1483
|
assert!(!source.contains("codex_fork"));
|
|
1257
1484
|
}
|