@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
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 从 spec 里的一个 agent 定义,编译出 worker 启动所需的系统提示词与工具串
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: WorkerCommandAgent
|
|
6
|
+
//! what: 从 YAML 或 JSON 读出的单 agent 命令上下文
|
|
7
|
+
//! - name: compile_worker_system_prompt
|
|
8
|
+
//! what: 按身份、runtime 契约、通信模式、角色正文、输出契约、权限说明拼系统提示词
|
|
9
|
+
//! - name: resolved_tool_strings_for_command
|
|
10
|
+
//! what: 解析出该 agent 的工具串,声明了 bypass 时追加 dangerous_auto_approve 哨兵
|
|
11
|
+
//! depends:
|
|
12
|
+
//! - crate::model::permissions
|
|
13
|
+
//! - crate::communication_mode
|
|
14
|
+
//! - crate::provider::bypass_flags
|
|
15
|
+
//! boundary:
|
|
16
|
+
//! - 只产出字符串,不 spawn 进程、不写盘
|
|
17
|
+
//! - bypass 只认 agent 自身声明,不从 team/runtime/leader argv 继承
|
|
18
|
+
//! - provider 没有 bypass argv 定义时报错,不静默降级
|
|
19
|
+
//! maturity: wired
|
|
20
|
+
//! ---
|
|
1
21
|
use std::path::Path;
|
|
2
22
|
|
|
3
23
|
use crate::communication_mode::CommunicationMode;
|
|
@@ -13,9 +33,9 @@ output. All communication must go through Team Agent MCP tools.
|
|
|
13
33
|
|
|
14
34
|
## Communication (mandatory)
|
|
15
35
|
|
|
16
|
-
- Coordinate with teammate:
|
|
17
|
-
- Broadcast to all teammates:
|
|
18
|
-
- Task complete:
|
|
36
|
+
- Coordinate with teammate: {send_message}(to='<agent_id>', content='...')
|
|
37
|
+
- Broadcast to all teammates: {send_message}(to='*', content='...')
|
|
38
|
+
- Task complete: {report_result}(summary='...') — call exactly once
|
|
19
39
|
|
|
20
40
|
## Rules
|
|
21
41
|
|
|
@@ -29,7 +49,7 @@ output. All communication must go through Team Agent MCP tools.
|
|
|
29
49
|
// semantics (leader-attach dependence + fallback status) that the
|
|
30
50
|
// generic runtime section deliberately leaves out.
|
|
31
51
|
const RESULT_ENVELOPE_OUTPUT_CONTRACT: &str =
|
|
32
|
-
"Final completion must call
|
|
52
|
+
"Final completion must call {report_result} exactly once with a short summary \
|
|
33
53
|
and optional status/changes/tests; the MCP runtime injects the result into the attached leader pane. \
|
|
34
54
|
If no leader is attached, the tool returns a fallback/failed result instead of completion.";
|
|
35
55
|
|
|
@@ -47,6 +67,16 @@ pub(crate) struct WorkerCommandAgent {
|
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
impl WorkerCommandAgent {
|
|
70
|
+
/// ---
|
|
71
|
+
/// purpose: 从 spec 的 YAML agent 节点读出命令上下文
|
|
72
|
+
/// params:
|
|
73
|
+
/// agent: 单个 agent 的 YAML 节点
|
|
74
|
+
/// fallback_id: agent 节点没写 id 时的兜底 id
|
|
75
|
+
/// provider: 已解析的 provider
|
|
76
|
+
/// returns: 填好的 WorkerCommandAgent
|
|
77
|
+
/// errors: communication_mode 取值非法时返回 LifecycleError
|
|
78
|
+
/// contract_id: lifecycle.worker_command_agent.from_source
|
|
79
|
+
/// ---
|
|
50
80
|
pub(crate) fn from_yaml(
|
|
51
81
|
agent: &crate::model::yaml::Value,
|
|
52
82
|
fallback_id: Option<&str>,
|
|
@@ -101,6 +131,16 @@ impl WorkerCommandAgent {
|
|
|
101
131
|
})
|
|
102
132
|
}
|
|
103
133
|
|
|
134
|
+
/// ---
|
|
135
|
+
/// purpose: 从 runtime state 的 JSON agent 节点读出命令上下文
|
|
136
|
+
/// params:
|
|
137
|
+
/// agent: 单个 agent 的 JSON 节点
|
|
138
|
+
/// fallback_id: agent 节点没写 id 时的兜底 id
|
|
139
|
+
/// provider: 已解析的 provider
|
|
140
|
+
/// returns: 填好的 WorkerCommandAgent
|
|
141
|
+
/// errors: communication_mode 取值非法时返回 LifecycleError
|
|
142
|
+
/// contract_id: lifecycle.worker_command_agent.from_source
|
|
143
|
+
/// ---
|
|
104
144
|
pub(crate) fn from_json(
|
|
105
145
|
agent: &serde_json::Value,
|
|
106
146
|
fallback_id: Option<&str>,
|
|
@@ -156,6 +196,11 @@ impl WorkerCommandAgent {
|
|
|
156
196
|
}
|
|
157
197
|
}
|
|
158
198
|
|
|
199
|
+
/// ---
|
|
200
|
+
/// purpose: 拼出 worker 的系统提示词,身份段必须排在最前
|
|
201
|
+
/// returns: 各段以空行分隔的提示词,空段被丢弃
|
|
202
|
+
/// errors: 角色正文读取失败或权限解析失败时返回 LifecycleError
|
|
203
|
+
/// ---
|
|
159
204
|
pub(crate) fn compile_worker_system_prompt(
|
|
160
205
|
agent: &WorkerCommandAgent,
|
|
161
206
|
) -> Result<String, LifecycleError> {
|
|
@@ -163,13 +208,15 @@ pub(crate) fn compile_worker_system_prompt(
|
|
|
163
208
|
// identity line anchors the very first section (live Python worker argv confirms).
|
|
164
209
|
// C-1 cr verdict / B2 灵魂件 — identity 必须 FIRST(MUST-4 行为层守:空白上下文问
|
|
165
210
|
// "你是谁"必须先答 Team Agent worker 身份)。runtime contract 跟后。
|
|
211
|
+
let send_message = mcp_tool_name(agent.provider, "team_orchestrator", "send_message");
|
|
212
|
+
let report_result = mcp_tool_name(agent.provider, "team_orchestrator", "report_result");
|
|
166
213
|
let mut chunks = vec![
|
|
167
214
|
identity_section(agent),
|
|
168
|
-
runtime_contract_section(),
|
|
169
|
-
agent.communication_mode.runtime_contract()
|
|
215
|
+
runtime_contract_section(&send_message, &report_result),
|
|
216
|
+
agent.communication_mode.runtime_contract(&send_message),
|
|
170
217
|
role_body(agent)?,
|
|
171
218
|
];
|
|
172
|
-
if let Some(contract) = output_contract(agent) {
|
|
219
|
+
if let Some(contract) = output_contract(agent, &report_result) {
|
|
173
220
|
chunks.push(contract);
|
|
174
221
|
}
|
|
175
222
|
if let Some(notes) = permission_notes(agent)? {
|
|
@@ -182,6 +229,13 @@ pub(crate) fn compile_worker_system_prompt(
|
|
|
182
229
|
.join("\n\n"))
|
|
183
230
|
}
|
|
184
231
|
|
|
232
|
+
/// ---
|
|
233
|
+
/// purpose: 解析该 agent 最终生效的工具串
|
|
234
|
+
/// params:
|
|
235
|
+
/// provider: 用于查 bypass argv 定义的 provider
|
|
236
|
+
/// returns: 排序后的工具串;agent 声明 bypass 时末尾追加 dangerous_auto_approve
|
|
237
|
+
/// errors: 权限解析失败,或声明了 bypass 但该 provider 没有 bypass argv 定义时返回 RequirementUnmet
|
|
238
|
+
/// ---
|
|
185
239
|
pub(crate) fn resolved_tool_strings_for_command(
|
|
186
240
|
agent: &WorkerCommandAgent,
|
|
187
241
|
provider: Provider,
|
|
@@ -212,6 +266,22 @@ pub(crate) fn resolved_tool_strings_for_command(
|
|
|
212
266
|
Ok(tools)
|
|
213
267
|
}
|
|
214
268
|
|
|
269
|
+
/// Provider-facing MCP tool name. Only verified call forms are filled in.
|
|
270
|
+
/// Unverified providers keep the historical dotted spelling.
|
|
271
|
+
fn mcp_tool_name(provider: Provider, server: &str, tool: &str) -> String {
|
|
272
|
+
match provider {
|
|
273
|
+
Provider::Claude | Provider::ClaudeCode => format!("mcp__{server}__{tool}"),
|
|
274
|
+
Provider::Grok => format!("{server}__{tool}"),
|
|
275
|
+
// CursorAgent / Codex / Copilot / GeminiCli / Fake: 未验证,沿用现状点号。
|
|
276
|
+
// CursorAgent 不可与 grok 同臂:仓库里没有活转录,`{server}__{tool}` 是推断。
|
|
277
|
+
Provider::CursorAgent
|
|
278
|
+
| Provider::Codex
|
|
279
|
+
| Provider::Copilot
|
|
280
|
+
| Provider::GeminiCli
|
|
281
|
+
| Provider::Fake => format!("{server}.{tool}"),
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
215
285
|
fn provider_display_name(provider: Provider) -> &'static str {
|
|
216
286
|
match provider {
|
|
217
287
|
Provider::Claude => "claude",
|
|
@@ -219,6 +289,8 @@ fn provider_display_name(provider: Provider) -> &'static str {
|
|
|
219
289
|
Provider::Codex => "codex",
|
|
220
290
|
Provider::Copilot => "copilot",
|
|
221
291
|
Provider::GeminiCli => "gemini_cli",
|
|
292
|
+
Provider::Grok => "grok",
|
|
293
|
+
Provider::CursorAgent => "cursor_agent",
|
|
222
294
|
Provider::Fake => "fake",
|
|
223
295
|
}
|
|
224
296
|
}
|
|
@@ -236,8 +308,10 @@ fn resolve_agent_permissions(
|
|
|
236
308
|
.map_err(|e| LifecycleError::Compile(e.to_string()))
|
|
237
309
|
}
|
|
238
310
|
|
|
239
|
-
fn runtime_contract_section() -> String {
|
|
240
|
-
RUNTIME_CONTRACT_SECTION
|
|
311
|
+
fn runtime_contract_section(send_message: &str, report_result: &str) -> String {
|
|
312
|
+
RUNTIME_CONTRACT_SECTION
|
|
313
|
+
.replace("{send_message}", send_message)
|
|
314
|
+
.replace("{report_result}", report_result)
|
|
241
315
|
}
|
|
242
316
|
|
|
243
317
|
fn communication_mode(value: Option<&str>) -> Result<CommunicationMode, LifecycleError> {
|
|
@@ -271,9 +345,9 @@ fn role_body(agent: &WorkerCommandAgent) -> Result<String, LifecycleError> {
|
|
|
271
345
|
Ok(chunks.join("\n\n"))
|
|
272
346
|
}
|
|
273
347
|
|
|
274
|
-
fn output_contract(agent: &WorkerCommandAgent) -> Option<String> {
|
|
348
|
+
fn output_contract(agent: &WorkerCommandAgent, report_result: &str) -> Option<String> {
|
|
275
349
|
(agent.output_contract_format.as_deref() == Some("result_envelope_v1"))
|
|
276
|
-
.then(|| RESULT_ENVELOPE_OUTPUT_CONTRACT.
|
|
350
|
+
.then(|| RESULT_ENVELOPE_OUTPUT_CONTRACT.replace("{report_result}", report_result))
|
|
277
351
|
}
|
|
278
352
|
|
|
279
353
|
fn permission_notes(agent: &WorkerCommandAgent) -> Result<Option<String>, LifecycleError> {
|
|
@@ -1,3 +1,73 @@
|
|
|
1
|
+
/// ---
|
|
2
|
+
/// purpose: scoped MCP no-compaction update_state filesystem contract
|
|
3
|
+
/// contract:
|
|
4
|
+
/// provides:
|
|
5
|
+
/// - name: update_state_path_contracts
|
|
6
|
+
/// what: proves the raw no-compaction response preserves its state-file path in an owned fixture
|
|
7
|
+
/// depends:
|
|
8
|
+
/// - name: mcp_state_fixture
|
|
9
|
+
/// what: fixture-owned root and provenance from tests.rs
|
|
10
|
+
/// boundary:
|
|
11
|
+
/// - retain the raw {ok,state_file} result shape
|
|
12
|
+
/// - the owned selector checks workspace, state-file, and runtime paths remain fixture-owned
|
|
13
|
+
/// maturity: wired
|
|
14
|
+
/// ---
|
|
15
|
+
|
|
16
|
+
fn verify_cleanup_surviving_receipt(
|
|
17
|
+
receipt_path: &std::path::Path,
|
|
18
|
+
raw_workspace: &std::path::Path,
|
|
19
|
+
canonical_workspace: &std::path::Path,
|
|
20
|
+
state_file: &std::path::Path,
|
|
21
|
+
runtime_state: &std::path::Path,
|
|
22
|
+
) {
|
|
23
|
+
let bytes = std::fs::read(receipt_path)
|
|
24
|
+
.expect("provenance receipt must survive fixture Drop");
|
|
25
|
+
let receipt: Value = serde_json::from_slice(&bytes).expect("provenance receipt must be JSON");
|
|
26
|
+
assert_eq!(receipt["schema"], json!("mcp-state-provenance-v1"));
|
|
27
|
+
let payload = &receipt["payload"];
|
|
28
|
+
let payload_bytes = serde_json::to_vec(payload).unwrap();
|
|
29
|
+
assert_eq!(
|
|
30
|
+
receipt["payload_sha256"],
|
|
31
|
+
json!(format!("{:x}", sha2::Sha256::digest(&payload_bytes)))
|
|
32
|
+
);
|
|
33
|
+
assert_eq!(
|
|
34
|
+
payload["raw_workspace"],
|
|
35
|
+
json!(raw_workspace.display().to_string())
|
|
36
|
+
);
|
|
37
|
+
assert_eq!(
|
|
38
|
+
payload["canonical_workspace"],
|
|
39
|
+
json!(canonical_workspace.display().to_string())
|
|
40
|
+
);
|
|
41
|
+
assert_eq!(
|
|
42
|
+
payload["resolved_state_file"],
|
|
43
|
+
json!(state_file.display().to_string())
|
|
44
|
+
);
|
|
45
|
+
assert_eq!(
|
|
46
|
+
payload["runtime_state_path"],
|
|
47
|
+
json!(runtime_state.display().to_string())
|
|
48
|
+
);
|
|
49
|
+
let facts = payload["facts"].as_array().unwrap();
|
|
50
|
+
for name in [
|
|
51
|
+
"root",
|
|
52
|
+
"raw_workspace",
|
|
53
|
+
"canonical_workspace",
|
|
54
|
+
"team_dir",
|
|
55
|
+
"runtime_dir",
|
|
56
|
+
"state_parent",
|
|
57
|
+
"runtime_state",
|
|
58
|
+
"state_file",
|
|
59
|
+
] {
|
|
60
|
+
let fact = facts
|
|
61
|
+
.iter()
|
|
62
|
+
.find(|fact| fact["name"] == json!(name))
|
|
63
|
+
.unwrap_or_else(|| panic!("provenance fact missing: {name}"));
|
|
64
|
+
assert!(fact["path"].as_str().is_some());
|
|
65
|
+
assert!(fact["uid"].as_u64().is_some());
|
|
66
|
+
assert!(fact["mode"].as_u64().is_some());
|
|
67
|
+
assert!(fact["device"].as_u64().is_some());
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
1
71
|
#[test]
|
|
2
72
|
fn dispatch_send_message_worker_accepted_returned_verbatim() {
|
|
3
73
|
// A-7: accepted requires a REAL stored message_id (no fabricated ids), so the
|
|
@@ -52,8 +122,10 @@
|
|
|
52
122
|
// state_file (not a golden whitelist key), so the key vanishes.
|
|
53
123
|
#[test]
|
|
54
124
|
fn update_state_state_file_survives_no_compaction() {
|
|
125
|
+
let fixture = McpStateFixture::new("writable");
|
|
126
|
+
let expected = fixture.state_file("team_state.md");
|
|
55
127
|
let tools = TeamOrchestratorTools::with_identity(
|
|
56
|
-
&
|
|
128
|
+
&fixture.workspace,
|
|
57
129
|
Some(AgentId::new("leader")),
|
|
58
130
|
None,
|
|
59
131
|
);
|
|
@@ -62,6 +134,33 @@
|
|
|
62
134
|
assert!(v.get("state_file").and_then(Value::as_str).is_some(),
|
|
63
135
|
"state_file must survive (update_state is not _compact_tool_result'd)");
|
|
64
136
|
assert_eq!(keys(&v), vec!["ok", "state_file"]);
|
|
137
|
+
let returned_state_file = PathBuf::from(v["state_file"].as_str().unwrap());
|
|
138
|
+
assert_eq!(returned_state_file, expected);
|
|
139
|
+
let runtime_state = crate::state::persist::runtime_state_path(&fixture.workspace);
|
|
140
|
+
assert!(fixture.under_root(&returned_state_file));
|
|
141
|
+
assert!(fixture.under_root(&runtime_state));
|
|
142
|
+
let canonical_workspace =
|
|
143
|
+
crate::model::paths::canonical_run_workspace(&fixture.workspace).unwrap();
|
|
144
|
+
let receipt = fixture.record_provenance(&fixture.workspace, &returned_state_file);
|
|
145
|
+
let root = fixture.root.clone();
|
|
146
|
+
drop(fixture);
|
|
147
|
+
assert!(!root.exists(), "fixture root must be removed before receipt verification");
|
|
148
|
+
verify_cleanup_surviving_receipt(
|
|
149
|
+
&receipt,
|
|
150
|
+
&root.join("workspace"),
|
|
151
|
+
&canonical_workspace,
|
|
152
|
+
&returned_state_file,
|
|
153
|
+
&runtime_state,
|
|
154
|
+
);
|
|
155
|
+
let receipt_bytes = std::fs::read(&receipt).unwrap();
|
|
156
|
+
println!(
|
|
157
|
+
"F10_PROVENANCE_RECEIPT path={} sha256={:x} payload={}",
|
|
158
|
+
receipt.display(),
|
|
159
|
+
sha2::Sha256::digest(&receipt_bytes),
|
|
160
|
+
String::from_utf8_lossy(&receipt_bytes)
|
|
161
|
+
);
|
|
162
|
+
std::fs::remove_file(&receipt).unwrap();
|
|
163
|
+
assert!(!receipt.exists(), "cleanup must remove only the exact receipt path");
|
|
65
164
|
}
|
|
66
165
|
|
|
67
166
|
// ── #36 report_result setdefault: populated envelope keys WIN over args ─────
|
|
@@ -1,8 +1,26 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: MCP no-compaction contract test with fixture-owned workspace and path provenance
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: mcp_state_fixture
|
|
6
|
+
//! what: supplies explicit process-owned workspace and state-file paths without mutating process-global environment
|
|
7
|
+
//! - name: mcp_state_path_provenance
|
|
8
|
+
//! what: records raw/canonical workspace, resolved state path, and filesystem ownership facts
|
|
9
|
+
//! depends:
|
|
10
|
+
//! - crate::mcp_server::lifecycle_tools::state_status
|
|
11
|
+
//! - crate::state::selector
|
|
12
|
+
//! boundary:
|
|
13
|
+
//! - the owned no-compaction test writes only within its fixture root
|
|
14
|
+
//! - this suite does not alter persistence semantics
|
|
15
|
+
//! maturity: wired
|
|
16
|
+
//! ---
|
|
1
17
|
//! step 14a · mcp_server::tests — WAVE-2 RED contracts (Python v0.2.11 golden).
|
|
2
18
|
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
3
19
|
|
|
4
20
|
use super::*;
|
|
5
21
|
use serde_json::json;
|
|
22
|
+
use sha2::{Digest, Sha256};
|
|
23
|
+
use std::path::{Path, PathBuf};
|
|
6
24
|
|
|
7
25
|
// ── helpers ──────────────────────────────────────────────────────────────
|
|
8
26
|
|
|
@@ -27,8 +45,7 @@ fn unique_ws(tag: &str) -> std::path::PathBuf {
|
|
|
27
45
|
static N: AtomicU64 = AtomicU64::new(0);
|
|
28
46
|
loop {
|
|
29
47
|
let n = N.fetch_add(1, Ordering::Relaxed);
|
|
30
|
-
let p =
|
|
31
|
-
std::env::temp_dir().join(format!("ta-rs-mcp-{tag}-{}-{n}", std::process::id()));
|
|
48
|
+
let p = std::env::temp_dir().join(format!("ta-rs-mcp-{tag}-{}-{n}", std::process::id()));
|
|
32
49
|
match std::fs::create_dir(&p) {
|
|
33
50
|
Ok(()) => return p,
|
|
34
51
|
Err(error) if error.kind() == ErrorKind::AlreadyExists => {}
|
|
@@ -37,6 +54,183 @@ fn unique_ws(tag: &str) -> std::path::PathBuf {
|
|
|
37
54
|
}
|
|
38
55
|
}
|
|
39
56
|
|
|
57
|
+
static MCP_FIXTURE_SEQ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
|
58
|
+
|
|
59
|
+
/// One process-owned root for update_state tests. All paths are passed explicitly
|
|
60
|
+
/// to the code under test; the fixture never changes process-global environment.
|
|
61
|
+
struct McpStateFixture {
|
|
62
|
+
root: PathBuf,
|
|
63
|
+
workspace: PathBuf,
|
|
64
|
+
receipt: PathBuf,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
impl McpStateFixture {
|
|
68
|
+
fn new(_tag: &str) -> Self {
|
|
69
|
+
let seq = MCP_FIXTURE_SEQ.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
|
70
|
+
let base = if cfg!(target_os = "macos") {
|
|
71
|
+
PathBuf::from("/private/tmp")
|
|
72
|
+
} else {
|
|
73
|
+
PathBuf::from("/tmp")
|
|
74
|
+
};
|
|
75
|
+
let raw_root = base.join(format!("ta-mcp-{}-{seq}", std::process::id()));
|
|
76
|
+
std::fs::create_dir(&raw_root).unwrap();
|
|
77
|
+
let root = std::fs::canonicalize(raw_root).unwrap();
|
|
78
|
+
let workspace = root.join("workspace");
|
|
79
|
+
let receipt = root
|
|
80
|
+
.parent()
|
|
81
|
+
.unwrap()
|
|
82
|
+
.join(format!("ta-mcp-receipt-{}-{seq}.json", std::process::id()));
|
|
83
|
+
std::fs::create_dir(&workspace).unwrap();
|
|
84
|
+
|
|
85
|
+
let fixture = Self {
|
|
86
|
+
root,
|
|
87
|
+
workspace,
|
|
88
|
+
receipt,
|
|
89
|
+
};
|
|
90
|
+
fixture.seed_spec("team_state.md");
|
|
91
|
+
crate::state::repository::StateRepository::new(&fixture.workspace)
|
|
92
|
+
.save(
|
|
93
|
+
crate::state::repository::StateWriteIntent::FakeE2eSeed,
|
|
94
|
+
&json!({
|
|
95
|
+
"session_name": "mcp-fixture",
|
|
96
|
+
"active_team_key": "current",
|
|
97
|
+
"agents": {},
|
|
98
|
+
"tasks": []
|
|
99
|
+
}),
|
|
100
|
+
)
|
|
101
|
+
.unwrap();
|
|
102
|
+
fixture
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fn seed_spec(&self, state_file: &str) {
|
|
106
|
+
std::fs::write(
|
|
107
|
+
self.workspace.join("team.spec.yaml"),
|
|
108
|
+
format!(
|
|
109
|
+
"team:\n name: mcp-fixture\n objective: path contract\nagents: []\ntasks: []\ncontext:\n state_file: {state_file}\n"
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
.unwrap();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fn state_file(&self, relative: &str) -> PathBuf {
|
|
116
|
+
self.workspace.join(relative)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn record_provenance(&self, raw_workspace: &Path, state_file: &Path) -> PathBuf {
|
|
120
|
+
let canonical_workspace =
|
|
121
|
+
crate::model::paths::canonical_run_workspace(raw_workspace).unwrap();
|
|
122
|
+
assert!(self.under_root(raw_workspace));
|
|
123
|
+
assert!(self.under_root(&canonical_workspace));
|
|
124
|
+
assert!(self.under_root(state_file));
|
|
125
|
+
let team_dir = self.workspace.join(".team");
|
|
126
|
+
let runtime_dir = team_dir.join("runtime");
|
|
127
|
+
let runtime_state = crate::state::persist::runtime_state_path(&self.workspace);
|
|
128
|
+
let paths = [
|
|
129
|
+
("root", self.root.as_path()),
|
|
130
|
+
("raw_workspace", raw_workspace),
|
|
131
|
+
("canonical_workspace", canonical_workspace.as_path()),
|
|
132
|
+
("team_dir", team_dir.as_path()),
|
|
133
|
+
("runtime_dir", runtime_dir.as_path()),
|
|
134
|
+
("state_parent", state_file.parent().unwrap()),
|
|
135
|
+
("runtime_state", runtime_state.as_path()),
|
|
136
|
+
];
|
|
137
|
+
let mut facts = paths
|
|
138
|
+
.into_iter()
|
|
139
|
+
.map(|(name, path)| {
|
|
140
|
+
let metadata = std::fs::symlink_metadata(path).unwrap();
|
|
141
|
+
json!({
|
|
142
|
+
"name": name,
|
|
143
|
+
"path": path.display().to_string(),
|
|
144
|
+
"uid": metadata_uid(&metadata),
|
|
145
|
+
"mode": metadata_mode(&metadata),
|
|
146
|
+
"device": metadata_device(&metadata)
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
.collect::<Vec<_>>();
|
|
150
|
+
if let Ok(metadata) = std::fs::symlink_metadata(state_file) {
|
|
151
|
+
facts.push(json!({
|
|
152
|
+
"name": "state_file",
|
|
153
|
+
"path": state_file.display().to_string(),
|
|
154
|
+
"uid": metadata_uid(&metadata),
|
|
155
|
+
"mode": metadata_mode(&metadata),
|
|
156
|
+
"device": metadata_device(&metadata)
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
let payload = json!({
|
|
160
|
+
"raw_workspace": raw_workspace.display().to_string(),
|
|
161
|
+
"canonical_workspace": canonical_workspace.display().to_string(),
|
|
162
|
+
"resolved_state_file": state_file.display().to_string(),
|
|
163
|
+
"runtime_state_path": runtime_state.display().to_string(),
|
|
164
|
+
"facts": facts
|
|
165
|
+
});
|
|
166
|
+
let payload_bytes = serde_json::to_vec(&payload).unwrap();
|
|
167
|
+
let payload_sha256 = format!("{:x}", Sha256::digest(&payload_bytes));
|
|
168
|
+
let receipt = json!({
|
|
169
|
+
"schema": "mcp-state-provenance-v1",
|
|
170
|
+
"payload": payload,
|
|
171
|
+
"payload_sha256": payload_sha256
|
|
172
|
+
});
|
|
173
|
+
let receipt_bytes = serde_json::to_vec_pretty(&receipt).unwrap();
|
|
174
|
+
use std::io::Write;
|
|
175
|
+
let mut file = std::fs::OpenOptions::new()
|
|
176
|
+
.write(true)
|
|
177
|
+
.create_new(true)
|
|
178
|
+
.open(&self.receipt)
|
|
179
|
+
.unwrap();
|
|
180
|
+
file.write_all(&receipt_bytes).unwrap();
|
|
181
|
+
file.sync_all().unwrap();
|
|
182
|
+
self.receipt.clone()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
fn under_root(&self, path: &Path) -> bool {
|
|
186
|
+
let candidate = if path.exists() {
|
|
187
|
+
std::fs::canonicalize(path).unwrap()
|
|
188
|
+
} else {
|
|
189
|
+
path.to_path_buf()
|
|
190
|
+
};
|
|
191
|
+
candidate.starts_with(&self.root)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
impl Drop for McpStateFixture {
|
|
196
|
+
fn drop(&mut self) {
|
|
197
|
+
let _ = std::fs::remove_dir_all(&self.root);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[cfg(unix)]
|
|
202
|
+
fn metadata_uid(metadata: &std::fs::Metadata) -> u32 {
|
|
203
|
+
use std::os::unix::fs::MetadataExt;
|
|
204
|
+
metadata.uid()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#[cfg(not(unix))]
|
|
208
|
+
fn metadata_uid(_: &std::fs::Metadata) -> u32 {
|
|
209
|
+
0
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#[cfg(unix)]
|
|
213
|
+
fn metadata_mode(metadata: &std::fs::Metadata) -> u32 {
|
|
214
|
+
use std::os::unix::fs::MetadataExt;
|
|
215
|
+
metadata.mode()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#[cfg(not(unix))]
|
|
219
|
+
fn metadata_mode(_: &std::fs::Metadata) -> u32 {
|
|
220
|
+
0
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#[cfg(unix)]
|
|
224
|
+
fn metadata_device(metadata: &std::fs::Metadata) -> u64 {
|
|
225
|
+
use std::os::unix::fs::MetadataExt;
|
|
226
|
+
metadata.dev()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
#[cfg(not(unix))]
|
|
230
|
+
fn metadata_device(_: &std::fs::Metadata) -> u64 {
|
|
231
|
+
0
|
|
232
|
+
}
|
|
233
|
+
|
|
40
234
|
include!("tests/normalize.rs");
|
|
41
235
|
include!("tests/wire.rs");
|
|
42
236
|
include!("tests/send.rs");
|