@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,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);
|
|
@@ -243,6 +243,9 @@ pub fn register_binding_from_state_best_effort(
|
|
|
243
243
|
);
|
|
244
244
|
let event_log = crate::event_log::EventLog::new(workspace);
|
|
245
245
|
let write_result = write_entry_best_effort(&entry);
|
|
246
|
+
if write_result.is_some() {
|
|
247
|
+
let _ = mark_displaced_same_pane_entries_unbound(&entry);
|
|
248
|
+
}
|
|
246
249
|
let status = if let Some(path) = &write_result {
|
|
247
250
|
let _ = event_log.write(
|
|
248
251
|
EVENT_REGISTERED,
|
|
@@ -275,6 +278,68 @@ pub fn register_binding_from_state_best_effort(
|
|
|
275
278
|
})
|
|
276
279
|
}
|
|
277
280
|
|
|
281
|
+
/// Stored registry status after another workspace claimed the same pane.
|
|
282
|
+
pub const REGISTRY_STATUS_UNBOUND: &str = "unbound";
|
|
283
|
+
|
|
284
|
+
/// When workspace B claims a pane, every other attached registry row that
|
|
285
|
+
/// still names that pane is no longer deliverable. Mark those rows unbound.
|
|
286
|
+
/// Does not change pane-side authorization storage.
|
|
287
|
+
pub fn mark_displaced_same_pane_entries_unbound(winner: &LeaderRegistryEntry) -> Vec<PathBuf> {
|
|
288
|
+
let Some(winner_pane) = winner
|
|
289
|
+
.channel
|
|
290
|
+
.get("pane_id")
|
|
291
|
+
.and_then(Value::as_str)
|
|
292
|
+
.filter(|pane| !pane.is_empty())
|
|
293
|
+
else {
|
|
294
|
+
return Vec::new();
|
|
295
|
+
};
|
|
296
|
+
let winner_socket = winner
|
|
297
|
+
.channel
|
|
298
|
+
.get("tmux_socket")
|
|
299
|
+
.and_then(Value::as_str)
|
|
300
|
+
.filter(|socket| !socket.is_empty());
|
|
301
|
+
let mut displaced = Vec::new();
|
|
302
|
+
for (path, mut entry) in read_all_entries() {
|
|
303
|
+
if entry.workspace_hash == winner.workspace_hash && entry.team_key == winner.team_key {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if entry.status != "attached" {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
let Some(pane) = entry
|
|
310
|
+
.channel
|
|
311
|
+
.get("pane_id")
|
|
312
|
+
.and_then(Value::as_str)
|
|
313
|
+
.filter(|pane| !pane.is_empty())
|
|
314
|
+
else {
|
|
315
|
+
continue;
|
|
316
|
+
};
|
|
317
|
+
if pane != winner_pane {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
let Some(socket) = entry
|
|
321
|
+
.channel
|
|
322
|
+
.get("tmux_socket")
|
|
323
|
+
.and_then(Value::as_str)
|
|
324
|
+
.filter(|socket| !socket.is_empty())
|
|
325
|
+
else {
|
|
326
|
+
continue;
|
|
327
|
+
};
|
|
328
|
+
let Some(winner_socket) = winner_socket else {
|
|
329
|
+
continue;
|
|
330
|
+
};
|
|
331
|
+
if socket != winner_socket {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
entry.status = REGISTRY_STATUS_UNBOUND.to_string();
|
|
335
|
+
entry.updated_at = chrono::Utc::now().to_rfc3339();
|
|
336
|
+
if write_entry_best_effort(&entry).is_some() {
|
|
337
|
+
displaced.push(path);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
displaced
|
|
341
|
+
}
|
|
342
|
+
|
|
278
343
|
fn entry_filename(entry: &LeaderRegistryEntry) -> String {
|
|
279
344
|
format!("{}__{}.json", entry.workspace_hash, entry.team_key)
|
|
280
345
|
}
|