@team-agent/installer 0.5.65 → 0.5.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +8 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +6 -0
- package/crates/team-agent/src/cli/adapters.rs +6 -1
- package/crates/team-agent/src/cli/diagnose.rs +82 -0
- package/crates/team-agent/src/cli/emit.rs +3 -3
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +21 -7
- package/crates/team-agent/src/cli/leaders.rs +125 -1
- package/crates/team-agent/src/cli/mod.rs +20 -0
- package/crates/team-agent/src/cli/send/presentation.rs +7 -1
- package/crates/team-agent/src/cli/spec.rs +2 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
- package/crates/team-agent/src/cli/tests/base.rs +26 -3
- package/crates/team-agent/src/cli/tests/compile.rs +1 -1
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
- package/crates/team-agent/src/cli/tests/mod.rs +2 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -1
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
- package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
- package/crates/team-agent/src/cli/tests/verb_validate.rs +1 -1
- package/crates/team-agent/src/communication_mode/mod.rs +6 -4
- package/crates/team-agent/src/compiler/tests.rs +23 -20
- package/crates/team-agent/src/compiler.rs +84 -5
- package/crates/team-agent/src/conpty/backend.rs +16 -0
- package/crates/team-agent/src/coordinator/backoff.rs +55 -0
- package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
- package/crates/team-agent/src/coordinator/health.rs +173 -0
- package/crates/team-agent/src/coordinator/mod.rs +31 -0
- package/crates/team-agent/src/coordinator/orphan.rs +31 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
- package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
- package/crates/team-agent/src/coordinator/tick.rs +133 -22
- package/crates/team-agent/src/coordinator/types.rs +49 -0
- package/crates/team-agent/src/db/message_store.rs +39 -5
- package/crates/team-agent/src/layout/worker_env.rs +20 -3
- package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
- package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
- package/crates/team-agent/src/leader/registry.rs +65 -0
- package/crates/team-agent/src/leader/start.rs +1080 -58
- package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/display.rs +56 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -14
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +69 -2
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +196 -2
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
- package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -457
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
- package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +101 -4
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +244 -48
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -2
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
- package/crates/team-agent/src/lifecycle/launch.rs +209 -43
- package/crates/team-agent/src/lifecycle/lock.rs +42 -1
- package/crates/team-agent/src/lifecycle/mod.rs +11 -0
- package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +213 -3
- package/crates/team-agent/src/lifecycle/restart/common.rs +373 -18
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +165 -15
- package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
- package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
- package/crates/team-agent/src/lifecycle/restart.rs +29 -0
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +3 -4
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +4 -4
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +159 -174
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +15 -13
- package/crates/team-agent/src/lifecycle/tests/core.rs +4 -18
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
- package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +3 -3
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +65 -40
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +20 -32
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +17 -9
- package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +24 -23
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +98 -62
- package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -1
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +41 -8
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +3 -3
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +60 -2
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
- package/crates/team-agent/src/lifecycle/tests.rs +23 -15
- package/crates/team-agent/src/lifecycle/types.rs +61 -2
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +200 -33
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
- package/crates/team-agent/src/mcp_server/tests.rs +196 -2
- package/crates/team-agent/src/messaging/delivery.rs +405 -40
- package/crates/team-agent/src/messaging/helpers.rs +2 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
- package/crates/team-agent/src/messaging/results.rs +4 -0
- package/crates/team-agent/src/messaging/send.rs +34 -0
- package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
- package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +1 -1
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/types.rs +4 -1
- package/crates/team-agent/src/model/enums.rs +14 -5
- package/crates/team-agent/src/model/permissions.rs +3 -0
- package/crates/team-agent/src/model/spec.rs +129 -6
- package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +3 -1
- package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +3 -1
- package/crates/team-agent/src/model/testdata/team.spec.yaml +3 -1
- package/crates/team-agent/src/os_probe.rs +73 -2
- package/crates/team-agent/src/provider/adapter.rs +231 -4
- package/crates/team-agent/src/provider/adapters/claude.rs +5 -2
- package/crates/team-agent/src/provider/adapters/codex.rs +5 -2
- package/crates/team-agent/src/provider/adapters/copilot.rs +5 -2
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +124 -0
- package/crates/team-agent/src/provider/classify.rs +4 -2
- package/crates/team-agent/src/provider/faults.rs +2 -1
- package/crates/team-agent/src/provider/mod.rs +13 -0
- package/crates/team-agent/src/provider/session/capture.rs +158 -20
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
- package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
- package/crates/team-agent/src/provider/session/mod.rs +18 -0
- package/crates/team-agent/src/provider/session/resume.rs +57 -0
- package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
- package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
- package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
- package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
- package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
- package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
- package/crates/team-agent/src/provider/session_scan.rs +8 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +23 -1
- package/crates/team-agent/src/state/persist.rs +301 -8
- package/crates/team-agent/src/state/repository.rs +5 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
- package/crates/team-agent/src/tmux_backend.rs +1083 -75
- package/crates/team-agent/src/transport/test_support.rs +20 -0
- package/crates/team-agent/src/transport/tests/wire.rs +28 -2
- package/crates/team-agent/src/transport.rs +265 -1
- package/npm/install.mjs +129 -73
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +33 -238
- package/skills/team-agent/command-coverage.json +31 -0
- package/crates/team-agent/src/lifecycle/launch/approval.rs +0 -134
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -483
- package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
- package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +0 -1187
- package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +0 -327
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: coordinator tick session_gate 步骤组的占位命名空间——实现仍在 tick.rs
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides: []
|
|
5
|
+
//! depends: []
|
|
6
|
+
//! boundary:
|
|
7
|
+
//! - 本文件当前不含任何 item:provider session 捕获与就绪门步骤未来迁入处
|
|
8
|
+
//! - 迁移落地前不要在此新增逻辑,否则步骤顺序会分裂成两处
|
|
9
|
+
//! maturity: signature_only
|
|
10
|
+
//! ---
|
|
1
11
|
//!
|
|
2
12
|
//! unit-11 (Stage 4) — coordinator tick `session_gate` step group.
|
|
3
13
|
//!
|
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: coordinator 的核心——Coordinator 本体与单次 tick 的固定顺序编排,外加心跳 sidecar 与 worker 运行态解析
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: tick
|
|
6
|
+
//! what: 跑一轮固定顺序的编排:会话门 → 捕获缺失 session → 启动/审批提示 → 健康对账 → 投递与到期事件 → 只读探测 → 原子持久化 → 回收结果
|
|
7
|
+
//! - name: write_coordinator_heartbeat
|
|
8
|
+
//! what: 写 runtime/coordinator_tick.json 心跳 sidecar(阶段、状态、boot id、迭代计数)
|
|
9
|
+
//! - name: read_coordinator_heartbeat
|
|
10
|
+
//! what: 只读取出最近一次心跳,供 status/diagnose 消费
|
|
11
|
+
//! - name: resolve_worker_runtime_state_with_fg_pgrp
|
|
12
|
+
//! what: 由 agent JSON 与活动分类结果解析 worker 运行态,缺信号时判 Unknown 而非 Idle
|
|
13
|
+
//! depends:
|
|
14
|
+
//! - super::health
|
|
15
|
+
//! - super::types
|
|
16
|
+
//! - super::steps
|
|
17
|
+
//! - super::steps::abnormal
|
|
18
|
+
//! - super::runtime_observation
|
|
19
|
+
//! - crate::state
|
|
20
|
+
//! - crate::message_store
|
|
21
|
+
//! - crate::messaging
|
|
22
|
+
//! - crate::leader
|
|
23
|
+
//! - crate::provider
|
|
24
|
+
//! - crate::transport
|
|
25
|
+
//! - crate::event_log
|
|
26
|
+
//! - crate::os_probe
|
|
27
|
+
//! boundary:
|
|
28
|
+
//! - 不实现投递、到期事件与结果回收的本体,那些在 crate::messaging,本模块只按序调用
|
|
29
|
+
//! - 无 pending obligation 时不注入任何探索性 prompt;无既定义务时唯一会投递的是 should_ping 成立时的一条中性提示。例外:屏上出现审批提示且策略允许时,runtime_approval 会经 transport.send_keys 注入应答按键(不是 prompt,是对已观测提示的应答)
|
|
30
|
+
//! - 监测步骤失败降级并继续,TickReport.ok=true 不代表每一步都成功——各步失败只在事件里
|
|
31
|
+
//! - 持久化失败走 degraded 报告而不是 panic,也不是 Err
|
|
32
|
+
//! - 不直接依赖 provider client crate,provider 一律经注入的 ProviderAdapter
|
|
33
|
+
//! maturity: wired
|
|
34
|
+
//! ---
|
|
1
35
|
//!
|
|
2
36
|
//! Coordinator core:daemon lifecycle 宿主 + 单次 tick 编排(19 步固定顺序)+ health/start/stop。
|
|
3
37
|
|
|
@@ -166,6 +200,14 @@ pub struct Coordinator {
|
|
|
166
200
|
|
|
167
201
|
impl Coordinator {
|
|
168
202
|
/// 构造(注入 provider registry + transport)。spawn 出的 daemon 在 `run` 前装配它。
|
|
203
|
+
/// ---
|
|
204
|
+
/// purpose: 装配一个 Coordinator——注入 provider registry 与 transport,两者都是 trait object 以便替换与 mock
|
|
205
|
+
/// params:
|
|
206
|
+
/// workspace: 本实例服务的 workspace 根
|
|
207
|
+
/// provider_registry: provider adapter 解析器;provider 一律经它取,绝不直连 provider client crate
|
|
208
|
+
/// transport: 会话/pane 控制面,用于存活探测与注入
|
|
209
|
+
/// returns: 未绑定 team_key、无 save 钩子、无顺序探针的实例
|
|
210
|
+
/// ---
|
|
169
211
|
pub fn new(
|
|
170
212
|
workspace: WorkspacePath,
|
|
171
213
|
provider_registry: Box<dyn ProviderRegistry>,
|
|
@@ -181,6 +223,12 @@ impl Coordinator {
|
|
|
181
223
|
}
|
|
182
224
|
}
|
|
183
225
|
|
|
226
|
+
/// ---
|
|
227
|
+
/// purpose: 绑定 daemon 侧选定的 team_key,让 tick 的状态投影用它而不是可能过期的根 active_team_key
|
|
228
|
+
/// params:
|
|
229
|
+
/// team_key: 目标 team;None 或空串都视为未绑定,tick 会回落到 state 推导
|
|
230
|
+
/// returns: 绑定后的自身
|
|
231
|
+
/// ---
|
|
184
232
|
pub(crate) fn with_team_key(mut self, team_key: Option<String>) -> Self {
|
|
185
233
|
self.daemon_team_key = team_key.filter(|key| !key.is_empty());
|
|
186
234
|
self
|
|
@@ -190,6 +238,17 @@ impl Coordinator {
|
|
|
190
238
|
/// transport + mock provider registry + 可选 save 注入钩 + ORDER 探针。**纯 test-support
|
|
191
239
|
/// 脚手架**(真实 impl,非 `unimplemented!()`):它只装配字段,不执行任何 daemon 逻辑;
|
|
192
240
|
/// tick/health/start/stop 仍是 `unimplemented!()` 生产体,因此调它们的契约仍 RED。
|
|
241
|
+
/// ---
|
|
242
|
+
/// purpose: 测试装配口——直接构出实例并注入 save 钩子与步骤顺序探针
|
|
243
|
+
/// params:
|
|
244
|
+
/// workspace: workspace 根
|
|
245
|
+
/// provider_registry: 可 mock 的 provider adapter 解析器
|
|
246
|
+
/// transport: 可 mock 的传输控制面
|
|
247
|
+
/// save_hook: 替换真实持久化,用于制造持久化失败
|
|
248
|
+
/// order_recorder: 记录 tick 内各步骤名,用于断言固定顺序
|
|
249
|
+
/// returns: 只装配字段的实例,本函数自身不执行任何 daemon 逻辑
|
|
250
|
+
/// cfg: test
|
|
251
|
+
/// ---
|
|
193
252
|
#[cfg(test)]
|
|
194
253
|
pub(crate) fn for_test(
|
|
195
254
|
workspace: WorkspacePath,
|
|
@@ -226,6 +285,11 @@ impl Coordinator {
|
|
|
226
285
|
/// `state::save_runtime_state`,bug-084 测试注入失败);在每个 step8-11 原子调用点
|
|
227
286
|
/// `if let Some(rec) = &self.order_recorder { rec.lock()...push(STEP_NAME) }`(tick
|
|
228
287
|
/// 副作用 ORDER 测试断言固定序列)。生产两者均 `None`,零开销。
|
|
288
|
+
/// ---
|
|
289
|
+
/// purpose: 跑一轮 tick——按固定顺序把各步骤串起来,只投递既定义务,绝不凭空注入
|
|
290
|
+
/// returns: TickReport。传输会话不在了 → stop=true 且 reason=TmuxSessionMissing,主循环据此退出;持久化失败 → ok=false、reason=PersistenceDegraded、persisted=Some(false),但仍是 Ok;正常 → ok=true 并带本轮投递、到期事件、回收结果等清单。注意 ok=true 只说明主干走完,监测类步骤失败已被降级吞掉,真相只在事件日志里
|
|
291
|
+
/// errors: 加载状态、打开 message store、传输探测或事件写入这些主干操作失败时返回 TickError,由主循环 catch 后退避
|
|
292
|
+
/// ---
|
|
229
293
|
pub fn tick(&self) -> Result<TickReport, TickError> {
|
|
230
294
|
self.record_step(TickStepGroup::SessionGate, "load_state");
|
|
231
295
|
let raw_state = crate::state::persist::load_runtime_state(self.workspace.as_path())?;
|
|
@@ -271,17 +335,12 @@ impl Coordinator {
|
|
|
271
335
|
}
|
|
272
336
|
|
|
273
337
|
self.record_step(TickStepGroup::SessionGate, "capture_missing");
|
|
274
|
-
let
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
serde_json::json!({"error": error.to_string()}),
|
|
281
|
-
);
|
|
282
|
-
Vec::new()
|
|
283
|
-
}
|
|
284
|
-
};
|
|
338
|
+
if let Err(error) = self.capture_missing_sessions(&mut state, &event_log) {
|
|
339
|
+
let _ = event_log.write(
|
|
340
|
+
"coordinator.tick.capture_missing_failed",
|
|
341
|
+
serde_json::json!({"error": error.to_string()}),
|
|
342
|
+
);
|
|
343
|
+
}
|
|
285
344
|
|
|
286
345
|
// Slice 1 energy gate: one pane snapshot per tick feeds probe eligibility,
|
|
287
346
|
// health sync, and abnormal-exit detection. Missing panes are filtered
|
|
@@ -461,15 +520,6 @@ impl Coordinator {
|
|
|
461
520
|
collections,
|
|
462
521
|
));
|
|
463
522
|
}
|
|
464
|
-
for context_fork in &pending_context_fork_audits {
|
|
465
|
-
context_fork.write_audit(&event_log).map_err(|error| {
|
|
466
|
-
eprintln!(
|
|
467
|
-
"[coordinator] context_fork audit publish failed after state commit: {error}"
|
|
468
|
-
);
|
|
469
|
-
TickError::EventLog(error)
|
|
470
|
-
})?;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
523
|
self.record_step(TickStepGroup::Delivery, "collect_results");
|
|
474
524
|
collections.results =
|
|
475
525
|
collect_results(crate::messaging::collect_results_and_notify_watchers(
|
|
@@ -489,7 +539,7 @@ impl Coordinator {
|
|
|
489
539
|
&self,
|
|
490
540
|
state: &mut Value,
|
|
491
541
|
event_log: &EventLog,
|
|
492
|
-
) -> Result<
|
|
542
|
+
) -> Result<(), TickError> {
|
|
493
543
|
let report = crate::session_capture::capture_missing_provider_sessions_once(
|
|
494
544
|
state,
|
|
495
545
|
&mut |provider| self.provider_registry.adapter_for(provider),
|
|
@@ -653,7 +703,7 @@ impl Coordinator {
|
|
|
653
703
|
}),
|
|
654
704
|
)?;
|
|
655
705
|
}
|
|
656
|
-
Ok(
|
|
706
|
+
Ok(())
|
|
657
707
|
}
|
|
658
708
|
|
|
659
709
|
fn sync_agent_health(
|
|
@@ -1371,6 +1421,11 @@ impl Coordinator {
|
|
|
1371
1421
|
|
|
1372
1422
|
/// `coordinator_health`(`lifecycle.py:26`)。pid + meta + schema 三合一健康。
|
|
1373
1423
|
/// doctor / start 前置调它。`ok = running ∧ metadata_ok ∧ schema_ok`。
|
|
1424
|
+
/// ---
|
|
1425
|
+
/// purpose: 取本 workspace 的 coordinator 健康报告
|
|
1426
|
+
/// returns: 与 health::coordinator_health 完全一致的报告
|
|
1427
|
+
/// errors: 当前实现恒为 Ok;返回类型保留 Result 以便未来健康判定引入可失败路径
|
|
1428
|
+
/// ---
|
|
1374
1429
|
pub fn health(&self) -> Result<HealthReport, TickError> {
|
|
1375
1430
|
Ok(super::health::coordinator_health(&self.workspace))
|
|
1376
1431
|
}
|
|
@@ -1379,11 +1434,23 @@ impl Coordinator {
|
|
|
1379
1434
|
/// schema 不兼容拒启给 hint;否则 spawn 自身二进制子命令(`team-agent coordinator --workspace ..`,
|
|
1380
1435
|
/// Python 是 `python -m team_agent.coordinator`,`lifecycle.py:108`)。
|
|
1381
1436
|
/// **schema 兼容门**:三元任一不匹配 → restart_incompatible,**不可静默继续**(card §89)。
|
|
1437
|
+
/// ---
|
|
1438
|
+
/// purpose: 幂等启动本 workspace 的 daemon 子进程
|
|
1439
|
+
/// returns: 与 health::start_coordinator 完全一致的报告;「拒启」体现为 ok=false 而非 Err
|
|
1440
|
+
/// errors: 建目录、开日志、spawn 或写 pid/metadata 失败时返回 StartError
|
|
1441
|
+
/// ---
|
|
1382
1442
|
pub fn start(&self) -> Result<StartReport, StartError> {
|
|
1383
1443
|
super::health::start_coordinator(&self.workspace)
|
|
1384
1444
|
}
|
|
1385
1445
|
|
|
1386
1446
|
/// `stop_coordinator`(`lifecycle.py:229`)。SIGTERM + 清 pid/meta。pid 非整数 → 清文件返回。
|
|
1447
|
+
/// ---
|
|
1448
|
+
/// purpose: 清掉本 workspace 的 coordinator pid/meta 文件
|
|
1449
|
+
/// returns: pid 文件不存在 → Missing;文件在且内容可解析 → Stopped 并带该 pid;文件在但内容非法 → InvalidPidRemoved
|
|
1450
|
+
/// errors: 删文件失败时返回 StopError
|
|
1451
|
+
/// ---
|
|
1452
|
+
/// 注意:本方法只删文件,**不向 daemon 发任何信号**——与 health::stop_coordinator
|
|
1453
|
+
/// 的「真终止进程」语义不同。调它之后 daemon 仍会继续 tick,而健康探测会报 Missing。
|
|
1387
1454
|
pub fn stop(&self) -> Result<StopReport, StopError> {
|
|
1388
1455
|
let pid_path = coordinator_pid_path(&self.workspace);
|
|
1389
1456
|
if !pid_path.exists() {
|
|
@@ -1412,6 +1479,10 @@ impl Coordinator {
|
|
|
1412
1479
|
|
|
1413
1480
|
/// `message_store_schema_health`(`lifecycle.py:197`)。DB 列兼容门:区分 pre-init 必需列缺失
|
|
1414
1481
|
/// (拒启)vs migratable 列缺失(可迁移)。`doctor --fix-schema` 用其 action hint。
|
|
1482
|
+
/// ---
|
|
1483
|
+
/// purpose: 查本队 message store 的 schema 兼容门
|
|
1484
|
+
/// returns: 与 health::message_store_schema_health 一致;打不开库时 ok=false 并带原文与修复 hint
|
|
1485
|
+
/// ---
|
|
1415
1486
|
pub fn schema_health(&self) -> SchemaHealth {
|
|
1416
1487
|
// A-8: the gate must inspect the REAL team.db (Python lifecycle.py:197+
|
|
1417
1488
|
// message_store_schema_health); a hardcoded ok:true left the card §89
|
|
@@ -1525,6 +1596,21 @@ fn increment_coordinator_tick_iteration_count(workspace: &WorkspacePath) {
|
|
|
1525
1596
|
);
|
|
1526
1597
|
}
|
|
1527
1598
|
|
|
1599
|
+
/// ---
|
|
1600
|
+
/// purpose: 写心跳 sidecar runtime/coordinator_tick.json,让外部不必进程内也能看到 daemon 走到哪一步
|
|
1601
|
+
/// params:
|
|
1602
|
+
/// workspace: workspace 根
|
|
1603
|
+
/// pid: 当前 daemon pid
|
|
1604
|
+
/// boot_id: daemon 世代 id;None 时沿用文件里已有值,都没有则用 coord_unknown_<pid>
|
|
1605
|
+
/// last_phase: 本次写入代表的阶段名;值为 tick_running 时刷新 last_tick_started_at,否则刷新 last_tick_finished_at
|
|
1606
|
+
/// last_tick_status: 上一轮 tick 的状态串
|
|
1607
|
+
/// last_error: 上一轮的错误串
|
|
1608
|
+
/// increment_count: 是否把迭代计数加一
|
|
1609
|
+
/// returns: 写成功返回 ();先写临时文件再 rename,读方不会看到半份
|
|
1610
|
+
/// errors: 建目录、写临时文件或 rename 失败时返回 io::Error
|
|
1611
|
+
/// ---
|
|
1612
|
+
/// ⚠ 字段 `host_boot_time` 写的是**本次写入时刻的墙钟**,不是主机启动时间——
|
|
1613
|
+
/// 每次心跳都会变。主机启动身份在 `host_boot_id`,按名消费 host_boot_time 会拿到假事实。
|
|
1528
1614
|
pub(crate) fn write_coordinator_heartbeat(
|
|
1529
1615
|
workspace: &WorkspacePath,
|
|
1530
1616
|
pid: Pid,
|
|
@@ -1597,6 +1683,10 @@ fn coordinator_heartbeat_path(workspace: &WorkspacePath) -> PathBuf {
|
|
|
1597
1683
|
/// falls back to a formatted timestamp. Returns None when the platform
|
|
1598
1684
|
/// signal cannot be read — the caller MUST NOT guess; downstream
|
|
1599
1685
|
/// treats a missing/mismatched host boot separately from a matched one.
|
|
1686
|
+
/// ---
|
|
1687
|
+
/// purpose: 探测当前主机的启动身份,用来识别「所有 pane/pid/会话绑定都早于本次开机」
|
|
1688
|
+
/// returns: Linux 读 boot_id 得 linux-<uuid>;macOS 读 kern.boottime 得 macos-<...>;测试可用 TEAM_AGENT_TEST_HOST_BOOT_ID 覆盖。平台信号读不到时为 None——调用方必须把「读不到」与「不匹配」分开处理,不许猜
|
|
1689
|
+
/// ---
|
|
1600
1690
|
pub(crate) fn probe_host_boot_id() -> Option<String> {
|
|
1601
1691
|
if let Ok(override_id) = std::env::var("TEAM_AGENT_TEST_HOST_BOOT_ID") {
|
|
1602
1692
|
if !override_id.is_empty() {
|
|
@@ -1634,6 +1724,12 @@ pub(crate) fn probe_host_boot_id() -> Option<String> {
|
|
|
1634
1724
|
/// error (missing file, malformed JSON) returns None — callers treat
|
|
1635
1725
|
/// that as "unknown, fall back to existing facts" per locate §8 risk
|
|
1636
1726
|
/// note, they must never guess a stale/fresh verdict from absence.
|
|
1727
|
+
/// ---
|
|
1728
|
+
/// purpose: 只读取出最近一次写入的心跳 sidecar
|
|
1729
|
+
/// params:
|
|
1730
|
+
/// workspace: workspace 根
|
|
1731
|
+
/// returns: 解析成功才有值;文件缺失或 JSON 损坏一律 None。调用方须把 None 当作「未知」回落到既有事实,不得据此断定新鲜或陈旧
|
|
1732
|
+
/// ---
|
|
1637
1733
|
pub fn read_coordinator_heartbeat(workspace: &WorkspacePath) -> Option<Value> {
|
|
1638
1734
|
let path = coordinator_heartbeat_path(workspace);
|
|
1639
1735
|
let text = std::fs::read_to_string(path).ok()?;
|
|
@@ -2529,6 +2625,13 @@ fn write_activity(
|
|
|
2529
2625
|
/// conflict (or probe unavailable + activity idle).
|
|
2530
2626
|
/// 5. Unknown — missing pane_pid, probe error, or no decisive
|
|
2531
2627
|
/// signal. Iron law: never silently Idle.
|
|
2628
|
+
/// ---
|
|
2629
|
+
/// purpose: 由 agent 状态与活动分类结果解析出 worker 的运行态
|
|
2630
|
+
/// params:
|
|
2631
|
+
/// agent: agent 状态对象;读 pane_pid、awaiting_human_confirm 与审批/信任标志
|
|
2632
|
+
/// activity: JSONL 活动分类结果;None 表示本轮没有分类信号
|
|
2633
|
+
/// returns: 按优先级判定——先 Blocked(等人确认/等信任提示),再用前台进程组探测判 Busy,再由活动分类给 Busy/ProbablyIdle;任何一步缺信号都落到 Unknown。铁律是绝不在信息不足时静默判 Idle
|
|
2634
|
+
/// ---
|
|
2532
2635
|
pub(crate) fn resolve_worker_runtime_state_with_fg_pgrp(
|
|
2533
2636
|
agent: &Value,
|
|
2534
2637
|
activity: Option<&crate::messaging::AgentActivity>,
|
|
@@ -2612,6 +2715,14 @@ fn agent_health_status_wire(status: crate::messaging::ActivityStatus) -> &'stati
|
|
|
2612
2715
|
/// 0.4.x Phase 1: read the worker_state wire string that `write_activity`
|
|
2613
2716
|
/// persisted alongside the legacy activity. Returns None when no worker_state
|
|
2614
2717
|
/// has been written yet (older state row pre-upgrade).
|
|
2718
|
+
/// ---
|
|
2719
|
+
/// purpose: 读回 write_activity 与活动一起持久化的 worker_state 线字符串
|
|
2720
|
+
/// params:
|
|
2721
|
+
/// agent: agent 状态对象
|
|
2722
|
+
/// returns: 已写过 worker_state 时解析出运行态;升级前的旧状态行没有该字段,返回 None
|
|
2723
|
+
/// ---
|
|
2724
|
+
/// 现状:本仓内没有任何调用点(仅定义),与其他 pub(crate) 项不同——按机器事实记录,
|
|
2725
|
+
/// 不据此推断它「已接线」。
|
|
2615
2726
|
pub(crate) fn agent_worker_state(agent: &Value) -> Option<crate::messaging::WorkerRuntimeState> {
|
|
2616
2727
|
agent
|
|
2617
2728
|
.get("worker_state")
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: coordinator 的共享数据面——协议常量、Pid/WorkspacePath newtype、健康与启停的穷尽状态 enum、metadata/schema/report 结构、以及跨子系统注入用的 trait 占位
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: as_str
|
|
6
|
+
//! what: 把 binary-identity relation 与 metadata mismatch reason 映射成稳定 wire 字符串
|
|
7
|
+
//! - name: as_path
|
|
8
|
+
//! what: 从 WorkspacePath newtype 取回底层路径
|
|
9
|
+
//! depends:
|
|
10
|
+
//! - crate::model::enums
|
|
11
|
+
//! - crate::model::ids
|
|
12
|
+
//! - crate::provider
|
|
13
|
+
//! - crate::message_store
|
|
14
|
+
//! - crate::event_log
|
|
15
|
+
//! boundary:
|
|
16
|
+
//! - 只定义形状,不含任何判定、I/O 或事件写入
|
|
17
|
+
//! - as_str 的返回值是对外稳定契约:已发布的字符串只可新增不可改写
|
|
18
|
+
//! - ProviderRegistry / MarkerStore 只是注入点,实现体不在本模块
|
|
19
|
+
//! maturity: wired
|
|
20
|
+
//! ---
|
|
21
|
+
//!
|
|
1
22
|
//! coordinator 共享数据面:常量 / newtype / 穷尽 enum / metadata / schema health /
|
|
2
23
|
//! report 结构 / abnormal-track 数据型 / WatchCursor / cross-dep 占位。
|
|
3
24
|
|
|
@@ -42,9 +63,19 @@ pub const ROTATION_MARKER: &str =
|
|
|
42
63
|
pub struct Pid(pub u32);
|
|
43
64
|
|
|
44
65
|
impl Pid {
|
|
66
|
+
/// ---
|
|
67
|
+
/// purpose: 把裸 OS pid 包成 newtype,杜绝与其他 int id 混传
|
|
68
|
+
/// params:
|
|
69
|
+
/// pid: OS 进程号,不做存活或有效性校验
|
|
70
|
+
/// returns: 承载该 pid 的 newtype
|
|
71
|
+
/// ---
|
|
45
72
|
pub fn new(pid: u32) -> Self {
|
|
46
73
|
Self(pid)
|
|
47
74
|
}
|
|
75
|
+
/// ---
|
|
76
|
+
/// purpose: 取回底层裸 pid,供需要 OS 接口的调用方使用
|
|
77
|
+
/// returns: 构造时传入的 pid 原值
|
|
78
|
+
/// ---
|
|
48
79
|
pub fn get(self) -> u32 {
|
|
49
80
|
self.0
|
|
50
81
|
}
|
|
@@ -62,9 +93,19 @@ impl std::fmt::Display for Pid {
|
|
|
62
93
|
pub struct WorkspacePath(pub PathBuf);
|
|
63
94
|
|
|
64
95
|
impl WorkspacePath {
|
|
96
|
+
/// ---
|
|
97
|
+
/// purpose: 把一个路径包成 workspace newtype,作为 tick/health/start/stop 的统一 key
|
|
98
|
+
/// params:
|
|
99
|
+
/// p: workspace 根路径。调用方负责先 resolve;本构造不做规范化也不校验存在性
|
|
100
|
+
/// returns: 承载该路径的 newtype
|
|
101
|
+
/// ---
|
|
65
102
|
pub fn new(p: impl Into<PathBuf>) -> Self {
|
|
66
103
|
Self(p.into())
|
|
67
104
|
}
|
|
105
|
+
/// ---
|
|
106
|
+
/// purpose: 借出底层路径,供文件系统与子进程参数使用
|
|
107
|
+
/// returns: 构造时传入的路径原值的借用
|
|
108
|
+
/// ---
|
|
68
109
|
pub fn as_path(&self) -> &Path {
|
|
69
110
|
&self.0
|
|
70
111
|
}
|
|
@@ -200,6 +241,10 @@ pub enum CoordinatorBinaryIdentityRelation {
|
|
|
200
241
|
}
|
|
201
242
|
|
|
202
243
|
impl CoordinatorBinaryIdentityRelation {
|
|
244
|
+
/// ---
|
|
245
|
+
/// purpose: 把「调用方二进制相对 daemon 二进制」的关系映射成稳定 wire 字符串
|
|
246
|
+
/// returns: 五个稳定串之一(same / caller_newer_than_daemon / daemon_newer_than_caller / same_version_path_mismatch / unknown);这些值进事件与 status 输出,已发布的不可改写
|
|
247
|
+
/// ---
|
|
203
248
|
pub fn as_str(self) -> &'static str {
|
|
204
249
|
match self {
|
|
205
250
|
Self::Same => "same",
|
|
@@ -225,6 +270,10 @@ pub enum CoordinatorMetadataMismatchReason {
|
|
|
225
270
|
}
|
|
226
271
|
|
|
227
272
|
impl CoordinatorMetadataMismatchReason {
|
|
273
|
+
/// ---
|
|
274
|
+
/// purpose: 把 coordinator metadata 被拒的原因映射成稳定 wire 字符串
|
|
275
|
+
/// returns: 七个稳定串之一(metadata_missing / pid_mismatch / protocol_version_mismatch / message_store_schema_version_mismatch / binary_identity_missing / binary_version_mismatch / binary_path_mismatch);进 HealthReport 与事件,已发布的不可改写
|
|
276
|
+
/// ---
|
|
228
277
|
pub fn as_str(self) -> &'static str {
|
|
229
278
|
match self {
|
|
230
279
|
Self::MetadataMissing => "metadata_missing",
|
|
@@ -548,19 +548,26 @@ impl MessageStore {
|
|
|
548
548
|
|
|
549
549
|
/// `claim_for_delivery` (`core.py:190-205`): atomic single-winner claim. Flips an
|
|
550
550
|
/// eligible row (status ∈ pending/accepted/queued_until_idle/queued_until_start/
|
|
551
|
-
/// queued_stopped/queued_pane_missing
|
|
552
|
-
///
|
|
551
|
+
/// queued_stopped/queued_pane_missing, **or** `target_resolved` with a non-null
|
|
552
|
+
/// error — the retry/error-bypass path) to `target_resolved`, clears `error`,
|
|
553
|
+
/// `delivery_attempts += 1`. Returns `true` iff THIS update matched exactly one
|
|
554
|
+
/// row. Clearing `error` is the CAS: a concurrent retry cannot also match
|
|
555
|
+
/// `error is not null`.
|
|
553
556
|
pub fn claim_for_delivery(&self, message_id: &str) -> Result<bool, MessageStoreError> {
|
|
554
557
|
let conn = crate::db::schema::open_db(&self.path)?;
|
|
555
558
|
let rows = conn.execute(
|
|
556
559
|
"update messages
|
|
557
560
|
set status = 'target_resolved',
|
|
558
561
|
delivery_attempts = delivery_attempts + 1,
|
|
562
|
+
error = null,
|
|
559
563
|
updated_at = ?2
|
|
560
564
|
where message_id = ?1
|
|
561
|
-
and
|
|
562
|
-
|
|
563
|
-
|
|
565
|
+
and (
|
|
566
|
+
status in (
|
|
567
|
+
'pending', 'accepted', 'queued_until_idle', 'queued_until_start',
|
|
568
|
+
'queued_stopped', 'queued_pane_missing', ?3
|
|
569
|
+
)
|
|
570
|
+
or (status = 'target_resolved' and error is not null)
|
|
564
571
|
)",
|
|
565
572
|
params![
|
|
566
573
|
message_id,
|
|
@@ -1138,6 +1145,33 @@ mod tests {
|
|
|
1138
1145
|
assert_eq!(status_of(&read(&s), &mid), "failed");
|
|
1139
1146
|
}
|
|
1140
1147
|
|
|
1148
|
+
#[test]
|
|
1149
|
+
fn claim_for_delivery_error_retry_is_atomic_single_winner() {
|
|
1150
|
+
let s = store();
|
|
1151
|
+
let mid = s
|
|
1152
|
+
.create_message(Some("t"), "s", "r", "c", None, true, None)
|
|
1153
|
+
.unwrap();
|
|
1154
|
+
s.mark(&mid, "target_resolved", Some("probe_failed"))
|
|
1155
|
+
.unwrap();
|
|
1156
|
+
assert_eq!(
|
|
1157
|
+
col_str(&read(&s), &mid, "error").as_deref(),
|
|
1158
|
+
Some("probe_failed")
|
|
1159
|
+
);
|
|
1160
|
+
assert!(
|
|
1161
|
+
s.claim_for_delivery(&mid).unwrap(),
|
|
1162
|
+
"target_resolved+error must enter the claim gate, not a non-CAS bypass"
|
|
1163
|
+
);
|
|
1164
|
+
let c = read(&s);
|
|
1165
|
+
assert_eq!(status_of(&c, &mid), "target_resolved");
|
|
1166
|
+
assert_eq!(col_str(&c, &mid, "error"), None);
|
|
1167
|
+
assert_eq!(col_i64(&c, &mid, "delivery_attempts"), 1);
|
|
1168
|
+
assert!(
|
|
1169
|
+
!s.claim_for_delivery(&mid).unwrap(),
|
|
1170
|
+
"cleared error must lose the second claim"
|
|
1171
|
+
);
|
|
1172
|
+
assert_eq!(col_i64(&read(&s), &mid, "delivery_attempts"), 1);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1141
1175
|
#[test]
|
|
1142
1176
|
fn claim_for_delivery_never_claims_stored_only_presentation() {
|
|
1143
1177
|
let s = store();
|
|
@@ -72,7 +72,8 @@ const WORKER_IDENTITY_PREFIXES: &[&str] = &["CLAUDE_CODE_"];
|
|
|
72
72
|
/// * All POSIX-valid shell identifier keys from `parent_env` EXCEPT
|
|
73
73
|
/// those matching `STRIP_PREFIXES` / `STRIP_EXACT`.
|
|
74
74
|
/// * Overlay: `TEAM_AGENT_WORKSPACE`, `TEAM_AGENT_ID`,
|
|
75
|
-
/// `TEAM_AGENT_AGENT_ID`, `TEAM_AGENT_OWNER_TEAM_ID` (when present)
|
|
75
|
+
/// `TEAM_AGENT_AGENT_ID`, `TEAM_AGENT_OWNER_TEAM_ID` (when present),
|
|
76
|
+
/// `TEAM_AGENT_AUTH_MODE` (when present).
|
|
76
77
|
///
|
|
77
78
|
/// POSIX-validity filter: shell variable names must match
|
|
78
79
|
/// `[A-Za-z_][A-Za-z0-9_]*`. Cargo's integration-test runner exports keys
|
|
@@ -83,6 +84,7 @@ pub fn worker_spawn_env<I>(
|
|
|
83
84
|
workspace: &Path,
|
|
84
85
|
agent_id: &str,
|
|
85
86
|
team_id: Option<&str>,
|
|
87
|
+
auth_mode: Option<&str>,
|
|
86
88
|
) -> BTreeMap<String, String>
|
|
87
89
|
where
|
|
88
90
|
I: IntoIterator<Item = (String, String)>,
|
|
@@ -106,6 +108,9 @@ where
|
|
|
106
108
|
if let Some(tid) = team_id.filter(|s| !s.is_empty()) {
|
|
107
109
|
env.insert("TEAM_AGENT_OWNER_TEAM_ID".to_string(), tid.to_string());
|
|
108
110
|
}
|
|
111
|
+
if let Some(auth) = auth_mode.filter(|s| !s.is_empty()) {
|
|
112
|
+
env.insert("TEAM_AGENT_AUTH_MODE".to_string(), auth.to_string());
|
|
113
|
+
}
|
|
109
114
|
env
|
|
110
115
|
}
|
|
111
116
|
|
|
@@ -223,6 +228,7 @@ mod tests {
|
|
|
223
228
|
Path::new("/ws"),
|
|
224
229
|
"developer",
|
|
225
230
|
Some("alpha"),
|
|
231
|
+
None,
|
|
226
232
|
);
|
|
227
233
|
for stripped in [
|
|
228
234
|
"TEAM_AGENT_LEADER_PROVIDER",
|
|
@@ -248,6 +254,7 @@ mod tests {
|
|
|
248
254
|
Path::new("/ws"),
|
|
249
255
|
"developer",
|
|
250
256
|
Some("alpha"),
|
|
257
|
+
Some("subscription"),
|
|
251
258
|
);
|
|
252
259
|
assert_eq!(
|
|
253
260
|
env.get("TEAM_AGENT_ID").map(String::as_str),
|
|
@@ -265,11 +272,21 @@ mod tests {
|
|
|
265
272
|
env.get("TEAM_AGENT_OWNER_TEAM_ID").map(String::as_str),
|
|
266
273
|
Some("alpha")
|
|
267
274
|
);
|
|
275
|
+
assert_eq!(
|
|
276
|
+
env.get("TEAM_AGENT_AUTH_MODE").map(String::as_str),
|
|
277
|
+
Some("subscription")
|
|
278
|
+
);
|
|
268
279
|
}
|
|
269
280
|
|
|
270
281
|
#[test]
|
|
271
282
|
fn worker_spawn_env_inherits_parent_env_minus_strip_list() {
|
|
272
|
-
let env = worker_spawn_env(
|
|
283
|
+
let env = worker_spawn_env(
|
|
284
|
+
make_parent_env(&[]),
|
|
285
|
+
Path::new("/ws"),
|
|
286
|
+
"developer",
|
|
287
|
+
None,
|
|
288
|
+
None,
|
|
289
|
+
);
|
|
273
290
|
// Inherit-then-strip: every parent key passes through EXCEPT the
|
|
274
291
|
// strip list. PATH-like + provider creds + random user vars all
|
|
275
292
|
// present.
|
|
@@ -292,7 +309,7 @@ mod tests {
|
|
|
292
309
|
#[test]
|
|
293
310
|
fn worker_spawn_env_drops_posix_invalid_keys() {
|
|
294
311
|
let parent = make_parent_env(&[("CARGO_BIN_EXE_team-agent", "/bin/x")]);
|
|
295
|
-
let env = worker_spawn_env(parent, Path::new("/ws"), "developer", None);
|
|
312
|
+
let env = worker_spawn_env(parent, Path::new("/ws"), "developer", None, None);
|
|
296
313
|
assert!(!env.contains_key("CARGO_BIN_EXE_team-agent"));
|
|
297
314
|
}
|
|
298
315
|
|
|
@@ -17,6 +17,8 @@ pub fn provider_window_name(provider: Provider) -> &'static str {
|
|
|
17
17
|
Provider::Codex => "codex",
|
|
18
18
|
Provider::Copilot => "copilot",
|
|
19
19
|
Provider::GeminiCli => "gemini",
|
|
20
|
+
Provider::Grok => "grok",
|
|
21
|
+
Provider::CursorAgent => "agent",
|
|
20
22
|
Provider::Fake => "fake",
|
|
21
23
|
}
|
|
22
24
|
}
|
|
@@ -17,6 +17,11 @@ const COMMAND_GRAMMAR_PROVIDER_COMMANDS: &[(&str, Provider)] = &[
|
|
|
17
17
|
("codex", Provider::Codex),
|
|
18
18
|
("copilot", Provider::Copilot),
|
|
19
19
|
("gemini", Provider::GeminiCli),
|
|
20
|
+
("grok", Provider::Grok),
|
|
21
|
+
// NOTE: Cursor `agent` binary is NOT added here — command-text attribution
|
|
22
|
+
// is a substring contains() match in table order; `"agent"` would also
|
|
23
|
+
// match the framework's own `team-agent` binary and mis-attribute leader
|
|
24
|
+
// panes. CursorAgent panes need env/argv-based attribution (TODO 0.5.67).
|
|
20
25
|
("fake", Provider::Fake),
|
|
21
26
|
];
|
|
22
27
|
|
|
@@ -91,11 +96,37 @@ fn provider_from_pid_env(pid: u32) -> Option<Provider> {
|
|
|
91
96
|
|
|
92
97
|
fn provider_from_command_text(text: &str) -> Option<Provider> {
|
|
93
98
|
let lower = text.to_ascii_lowercase();
|
|
99
|
+
if cursor_agent_command_text(&lower) {
|
|
100
|
+
return Some(Provider::CursorAgent);
|
|
101
|
+
}
|
|
94
102
|
COMMAND_GRAMMAR_PROVIDER_COMMANDS
|
|
95
103
|
.iter()
|
|
96
104
|
.find_map(|(needle, provider)| lower.contains(needle).then_some(*provider))
|
|
97
105
|
}
|
|
98
106
|
|
|
107
|
+
/// Cursor 二进制名为 `agent`,不能做 substring contains(会误伤 `team-agent`)。
|
|
108
|
+
/// 认精确 token `cursor-agent` / `…/agent`,且必须带 `--sandbox` 或 `--trust`。
|
|
109
|
+
fn cursor_agent_command_text(lower: &str) -> bool {
|
|
110
|
+
let tokens: Vec<&str> = lower.split_whitespace().collect();
|
|
111
|
+
if tokens.iter().any(|tok| {
|
|
112
|
+
*tok == "team-agent"
|
|
113
|
+
|| tok.ends_with("/team-agent")
|
|
114
|
+
|| tok.ends_with("\\team-agent")
|
|
115
|
+
}) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
let is_cursor_bin = tokens.iter().any(|tok| {
|
|
119
|
+
*tok == "cursor-agent"
|
|
120
|
+
|| *tok == "agent"
|
|
121
|
+
|| tok.ends_with("/cursor-agent")
|
|
122
|
+
|| tok.ends_with("/agent")
|
|
123
|
+
|| tok.ends_with("\\cursor-agent")
|
|
124
|
+
|| tok.ends_with("\\agent")
|
|
125
|
+
});
|
|
126
|
+
let has_cursor_flag = tokens.iter().any(|tok| *tok == "--sandbox" || *tok == "--trust");
|
|
127
|
+
is_cursor_bin && has_cursor_flag
|
|
128
|
+
}
|
|
129
|
+
|
|
99
130
|
fn provider_from_env_text(text: &str) -> Option<Provider> {
|
|
100
131
|
text.split(|ch: char| ch == '\0' || ch.is_ascii_whitespace())
|
|
101
132
|
.find_map(|part| part.strip_prefix("TEAM_AGENT_LEADER_PROVIDER="))
|
|
@@ -180,6 +211,28 @@ mod tests {
|
|
|
180
211
|
);
|
|
181
212
|
}
|
|
182
213
|
|
|
214
|
+
#[test]
|
|
215
|
+
fn cursor_agent_basename_with_trust_is_attributed() {
|
|
216
|
+
assert_eq!(
|
|
217
|
+
attribute_command_provider("cursor-agent --trust --sandbox disabled --workspace /ws"),
|
|
218
|
+
Some(Provider::CursorAgent)
|
|
219
|
+
);
|
|
220
|
+
assert_eq!(
|
|
221
|
+
attribute_command_provider("agent --trust --sandbox disabled --workspace /ws"),
|
|
222
|
+
Some(Provider::CursorAgent)
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#[test]
|
|
227
|
+
fn team_agent_binary_is_not_cursor() {
|
|
228
|
+
assert_ne!(
|
|
229
|
+
attribute_command_provider("team-agent --trust claude"),
|
|
230
|
+
Some(Provider::CursorAgent)
|
|
231
|
+
);
|
|
232
|
+
assert_eq!(attribute_command_provider("team-agent status"), None);
|
|
233
|
+
assert_eq!(attribute_command_provider("agent"), None);
|
|
234
|
+
}
|
|
235
|
+
|
|
183
236
|
#[test]
|
|
184
237
|
fn pane_env_provider_wins_over_command() {
|
|
185
238
|
let mut pane = pane("codex", None);
|