@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,10 +1,61 @@
|
|
|
1
|
-
//!
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: codex rollout 的时间与归属过滤——用「创建时间」而非 mtime 判新旧,用 rollout 头里的 cwd 判是不是本席位的
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: parse_spawned_at
|
|
6
|
+
//! what: RFC3339 文本 → SystemTime,解析不了给 None(调用方据此 fail-closed 清空候选)
|
|
7
|
+
//! - name: truncate_to_uuid_precision
|
|
8
|
+
//! what: 把时间截到毫秒,与 uuid-v7 里能表达的精度对齐,避免亚毫秒误差把自己的会话判成"太旧"
|
|
9
|
+
//! - name: rollout_created_at
|
|
10
|
+
//! what: 三级取创建时间:uuid-v7 时间戳 → 头记录 created_at → 文件名里的时间戳
|
|
11
|
+
//! - name: apply_expected_session_filter
|
|
12
|
+
//! what: 有 pending id 时只留 session id 精确相等的候选
|
|
13
|
+
//! - name: retain_spawn_cwd
|
|
14
|
+
//! what: 只留 rollout 头记录里 cwd 与席位 spawn_cwd 等价的候选
|
|
15
|
+
//! requires:
|
|
16
|
+
//! - name: super::common
|
|
17
|
+
//! what: 读头、解析记录、record_cwd、paths_equivalent 均复用 common
|
|
18
|
+
//! - name: chrono
|
|
19
|
+
//! what: RFC3339 解析
|
|
20
|
+
//! boundary:
|
|
21
|
+
//! - 只服务 Provider::Codex
|
|
22
|
+
//! - 一律只读:不写盘、不改 plan、不改 state
|
|
23
|
+
//! - 创建时间刻意不取文件 mtime——邻席活动会抬升 mtime,把别人的会话冒充成新的
|
|
24
|
+
//! - 头窗口之外的记录读不到;三级取值全失败时 rollout_created_at 给 None,调用方按"不通过"处理
|
|
25
|
+
//! maturity: wired
|
|
26
|
+
//! ---
|
|
27
|
+
/// ---
|
|
28
|
+
/// purpose: 把持久化在 state 上的 spawned_at 文本解析成时间点
|
|
29
|
+
/// params:
|
|
30
|
+
/// raw: RFC3339 时间串
|
|
31
|
+
/// returns: 解析成功给 UTC SystemTime;失败给 None
|
|
32
|
+
/// contract:
|
|
33
|
+
/// provides:
|
|
34
|
+
/// - name: parse_spawned_at
|
|
35
|
+
/// what: 纯解析,无 I/O、不读系统时钟
|
|
36
|
+
/// boundary:
|
|
37
|
+
/// - 只认 RFC3339;不接受 epoch 秒、不做宽松格式回落
|
|
38
|
+
/// - 返回 None 的处置由调用方定:common::apply_spawned_at_filter 选择清空候选(fail-closed)
|
|
39
|
+
/// ---
|
|
2
40
|
pub(super) fn parse_spawned_at(raw: &str) -> Option<std::time::SystemTime> {
|
|
3
41
|
chrono::DateTime::parse_from_rfc3339(raw)
|
|
4
42
|
.ok()
|
|
5
43
|
.map(|dt| std::time::SystemTime::from(dt.with_timezone(&chrono::Utc)))
|
|
6
44
|
}
|
|
7
45
|
|
|
46
|
+
/// ---
|
|
47
|
+
/// purpose: 把时间点截到毫秒,与 uuid-v7 能表达的精度对齐
|
|
48
|
+
/// params:
|
|
49
|
+
/// timestamp: 待截断的时间点
|
|
50
|
+
/// returns: 同一毫秒的起点;早于 UNIX 纪元或毫秒数溢出 u64 时 None
|
|
51
|
+
/// contract:
|
|
52
|
+
/// provides:
|
|
53
|
+
/// - name: truncate_to_uuid_precision
|
|
54
|
+
/// what: 纯算术,无 I/O
|
|
55
|
+
/// boundary:
|
|
56
|
+
/// - 只向下截断,绝不向上取整——否则会把自己刚开的会话判成"早于 spawn"
|
|
57
|
+
/// - 只在「Codex 且有 expected id」的比较里使用,不是通用时间归一
|
|
58
|
+
/// ---
|
|
8
59
|
pub(super) fn truncate_to_uuid_precision(
|
|
9
60
|
timestamp: std::time::SystemTime,
|
|
10
61
|
) -> Option<std::time::SystemTime> {
|
|
@@ -16,12 +67,40 @@ pub(super) fn truncate_to_uuid_precision(
|
|
|
16
67
|
))
|
|
17
68
|
}
|
|
18
69
|
|
|
70
|
+
/// ---
|
|
71
|
+
/// purpose: 取 codex rollout 的创建时间,作为「这条存档是不是本次 spawn 之后产生的」的判据
|
|
72
|
+
/// params:
|
|
73
|
+
/// path: rollout 文件路径
|
|
74
|
+
/// returns: 三级依次尝试:文件名尾部 uuid-v7 的时间戳 → 头记录里的 created_at → 文件名中的时间戳;全失败给 None
|
|
75
|
+
/// contract:
|
|
76
|
+
/// provides:
|
|
77
|
+
/// - name: rollout_created_at
|
|
78
|
+
/// what: 只读文件名与头 64KB
|
|
79
|
+
/// boundary:
|
|
80
|
+
/// - 刻意不取文件 mtime:mtime 会被后续追加与邻席活动抬升,不是创建时间
|
|
81
|
+
/// - uuid 分支要求版本位为 '7';非 v7 的 uuid 直接落到下一级
|
|
82
|
+
/// - 返回 None 时调用方按"不满足时间窗"处理,不做放行
|
|
83
|
+
/// ---
|
|
19
84
|
pub(super) fn rollout_created_at(path: &std::path::Path) -> Option<std::time::SystemTime> {
|
|
20
85
|
created_at_from_rollout_uuid(path)
|
|
21
86
|
.or_else(|| created_at_from_rollout_head(path))
|
|
22
87
|
.or_else(|| created_at_from_rollout_filename(path))
|
|
23
88
|
}
|
|
24
89
|
|
|
90
|
+
/// ---
|
|
91
|
+
/// purpose: 有 pending id 时把候选收窄到 session id 精确相等的那些
|
|
92
|
+
/// params:
|
|
93
|
+
/// context: 无 expected_session_id 时原样返回,不做任何过滤
|
|
94
|
+
/// out: 候选列表,按值传入并就地 retain
|
|
95
|
+
/// returns: 过滤后的候选;无一条命中则空向量
|
|
96
|
+
/// contract:
|
|
97
|
+
/// provides:
|
|
98
|
+
/// - name: apply_expected_session_filter
|
|
99
|
+
/// what: 纯内存过滤,无 I/O
|
|
100
|
+
/// boundary:
|
|
101
|
+
/// - 判据是 session id 全等;session_id 为 None 的候选一律剔除
|
|
102
|
+
/// - 与 claude 同名函数语义不同:此处未命中即空,不退到"身份阳性子集"
|
|
103
|
+
/// ---
|
|
25
104
|
pub(super) fn apply_expected_session_filter(
|
|
26
105
|
context: &super::CaptureSessionContext,
|
|
27
106
|
mut out: Vec<super::CapturedSessionCandidate>,
|
|
@@ -39,6 +118,20 @@ pub(super) fn apply_expected_session_filter(
|
|
|
39
118
|
out
|
|
40
119
|
}
|
|
41
120
|
|
|
121
|
+
/// ---
|
|
122
|
+
/// purpose: 只保留 rollout 头里记着的 cwd 与本席位 spawn_cwd 等价的候选,挡掉其它工作目录的会话
|
|
123
|
+
/// params:
|
|
124
|
+
/// context: 提供 spawn_cwd 基准
|
|
125
|
+
/// out: 就地过滤的候选列表
|
|
126
|
+
/// contract:
|
|
127
|
+
/// provides:
|
|
128
|
+
/// - name: retain_spawn_cwd
|
|
129
|
+
/// what: 逐个候选读头 64KB 后比对 cwd
|
|
130
|
+
/// boundary:
|
|
131
|
+
/// - 无 rollout_path、读不出头、头里一条 cwd 都没有 → 剔除(fail-closed)
|
|
132
|
+
/// - 等价判定走 common::paths_equivalent,它把「记录 cwd 的父目录等于 spawn_cwd」也算等价
|
|
133
|
+
/// - 只看头窗口内的记录;窗口之外的 cwd 记录看不见
|
|
134
|
+
/// ---
|
|
42
135
|
pub(super) fn retain_spawn_cwd(
|
|
43
136
|
context: &super::CaptureSessionContext,
|
|
44
137
|
out: &mut Vec<super::CapturedSessionCandidate>,
|
|
@@ -1,4 +1,46 @@
|
|
|
1
|
-
//!
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 各 provider 共用的通用候选扫描与解析底座——枚举可能的会话文件、读头解析、按进程世代与身份 marker 过滤
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: candidate_session_files
|
|
6
|
+
//! what: 按 provider 枚举候选文件(隔离根 + spawn_cwd 递归 + provider HOME 目录),去重排序并按 mtime 截到 300 条
|
|
7
|
+
//! - name: parse_candidate_files
|
|
8
|
+
//! what: 逐个读头 64KB 解析成 CapturedSessionCandidate,途中套 cwd 匹配、embedded 身份、claude cwd 字段与 leader marker 四道排除
|
|
9
|
+
//! - name: apply_spawned_at_filter
|
|
10
|
+
//! what: 剔除早于席位进程世代边界(spawned_at)的存档;spawned_at 存在但解析不了则清空候选
|
|
11
|
+
//! - name: apply_spawn_time_window_if_unique
|
|
12
|
+
//! what: 只有当"落在 spawn 之后"的候选恰好剩一条时才据此收窄,否则不动
|
|
13
|
+
//! - name: sort_expected_first_if_needed
|
|
14
|
+
//! what: 有 pending id 时把命中的候选排到最前
|
|
15
|
+
//! - name: read_head_text
|
|
16
|
+
//! what: 读文件头若干字节并截到最后一个完整行
|
|
17
|
+
//! - name: parse_session_records
|
|
18
|
+
//! what: 先按整体 JSON 解析(数组/单值),失败再按 JSONL 逐行解析
|
|
19
|
+
//! - name: embedded_team_agent_worker_id_from_text
|
|
20
|
+
//! what: 从正文里抽出 "You are Team Agent worker `<id>`" 的 id,字符集受限校验
|
|
21
|
+
//! - name: rollout_path_embedded_team_agent_worker_id
|
|
22
|
+
//! what: 上一条的按路径版本(读头后再抽)
|
|
23
|
+
//! - name: record_cwd
|
|
24
|
+
//! what: 从记录里取 cwd(顶层 cwd 或 session_meta.payload.cwd / payload.cwd)
|
|
25
|
+
//! - name: paths_equivalent
|
|
26
|
+
//! what: 路径等价判定:相等、canonicalize 后相等、或左的父目录等于右
|
|
27
|
+
//! - name: CAPTURE_HEAD_BYTES
|
|
28
|
+
//! what: 统一的读头窗口 64KB
|
|
29
|
+
//! requires:
|
|
30
|
+
//! - name: crate::provider::helpers
|
|
31
|
+
//! what: find_session_id 与 parse_jsonl_records
|
|
32
|
+
//! - name: super::claude
|
|
33
|
+
//! what: claude 家族的 projects 目录推导、cwd 字段判据与 leader marker 判据
|
|
34
|
+
//! - name: super::codex
|
|
35
|
+
//! what: spawned_at 解析、uuid 精度截断与 rollout 创建时间
|
|
36
|
+
//! boundary:
|
|
37
|
+
//! - 一律只读:不写盘、不改 state、不发事件
|
|
38
|
+
//! - 只产出候选与事实;归属最终由上游分配器裁定,本文件不写会话
|
|
39
|
+
//! - spawn_cwd 递归最深 4 层,且跳过 .team 运行时目录(claude 的隔离根例外)
|
|
40
|
+
//! - 候选上限 300 条,超出按 mtime 由新到旧截断——截掉的部分不留痕迹
|
|
41
|
+
//! - "看起来像会话文件"的判据很宽(.json/.jsonl 后缀,或名字含 session/rollout/席位 id),收窄靠后续 provider 专属过滤
|
|
42
|
+
//! maturity: wired
|
|
43
|
+
//! ---
|
|
2
44
|
use std::path::{Path, PathBuf};
|
|
3
45
|
|
|
4
46
|
use crate::provider::helpers::{find_session_id, parse_jsonl_records};
|
|
@@ -21,6 +63,23 @@ const CAPTURE_CANDIDATE_CAP: usize = 300;
|
|
|
21
63
|
/// P2 (C-P2-1): head window >= Python's 200-line read.
|
|
22
64
|
pub(super) const CAPTURE_HEAD_BYTES: u64 = 65_536;
|
|
23
65
|
|
|
66
|
+
/// ---
|
|
67
|
+
/// purpose: 枚举本席位可能对应的会话文件,产出待解析的候选路径表
|
|
68
|
+
/// params:
|
|
69
|
+
/// provider: 决定要不要去 ~/.codex/sessions 或 ~/.claude/projects 找
|
|
70
|
+
/// context: provider_projects_root(隔离根)、spawn_cwd(必扫)、agent_id(参与文件名匹配)
|
|
71
|
+
/// returns: 去重并排序后的候选;requires_cwd_match=false 的排前面,同组按路径字典序
|
|
72
|
+
/// errors: 只有 spawn_cwd 这一层根目录读不开才上抛 Io;provider HOME 侧目录读失败静默跳过
|
|
73
|
+
/// contract:
|
|
74
|
+
/// provides:
|
|
75
|
+
/// - name: candidate_session_files
|
|
76
|
+
/// what: 只列路径,不打开文件内容(截断时会取 mtime)
|
|
77
|
+
/// boundary:
|
|
78
|
+
/// - spawn_cwd 递归最深 4 层;工作区是用户代码仓,任何名含 session 的 json 都会被列进来
|
|
79
|
+
/// - .team 运行时目录一律排除,除非它就在 claude 的隔离根之下
|
|
80
|
+
/// - 超过 300 条按 mtime 由新到旧截断;被截掉的候选不产生任何信号
|
|
81
|
+
/// - Copilot / Grok / CursorAgent / GeminiCli / Fake 不走 HOME 分支——它们各有专属扫描
|
|
82
|
+
/// ---
|
|
24
83
|
pub(super) fn candidate_session_files(
|
|
25
84
|
provider: Provider,
|
|
26
85
|
context: &CaptureSessionContext,
|
|
@@ -60,7 +119,11 @@ pub(super) fn candidate_session_files(
|
|
|
60
119
|
&mut out,
|
|
61
120
|
)?;
|
|
62
121
|
}
|
|
63
|
-
Provider::Copilot
|
|
122
|
+
Provider::Copilot
|
|
123
|
+
| Provider::Grok
|
|
124
|
+
| Provider::CursorAgent
|
|
125
|
+
| Provider::GeminiCli
|
|
126
|
+
| Provider::Fake => {}
|
|
64
127
|
}
|
|
65
128
|
}
|
|
66
129
|
out.sort_by(|a, b| {
|
|
@@ -73,6 +136,22 @@ pub(super) fn candidate_session_files(
|
|
|
73
136
|
Ok(out)
|
|
74
137
|
}
|
|
75
138
|
|
|
139
|
+
/// ---
|
|
140
|
+
/// purpose: 把候选路径读头解析成结构化候选,并在解析途中就滤掉明显不属于本席位的
|
|
141
|
+
/// params:
|
|
142
|
+
/// provider: 决定是否套用 claude 专属的两道排除
|
|
143
|
+
/// context: 提供 spawn_cwd 与 agent_id 两个比对基准
|
|
144
|
+
/// candidates: candidate_session_files 的产物
|
|
145
|
+
/// returns: 通过全部排除的候选;每条带 embedded 身份、positive_agent_id_match 与 agent_path_match
|
|
146
|
+
/// contract:
|
|
147
|
+
/// provides:
|
|
148
|
+
/// - name: parse_candidate_files
|
|
149
|
+
/// what: 每个文件只读头 64KB,且只取到最后一个完整行
|
|
150
|
+
/// boundary:
|
|
151
|
+
/// - 四道排除:读不出头 / 解析不出记录 → 跳过;requires_cwd_match 的候选 cwd 对不上 → 跳过;embedded 身份存在且不等于本席位 → 跳过;claude 家族有 session id 却无 cwd 字段、或含 leader marker → 跳过
|
|
152
|
+
/// - 无 session id 的候选不丢弃,降级为 FsMtimeFallback + Confidence::Low
|
|
153
|
+
/// - 身份判定用两套文本:正文 text 判 TEAM_AGENT_ID,未截行的原始字节判 embedded worker id
|
|
154
|
+
/// ---
|
|
76
155
|
pub(super) fn parse_candidate_files(
|
|
77
156
|
provider: Provider,
|
|
78
157
|
context: &CaptureSessionContext,
|
|
@@ -144,6 +223,21 @@ pub(super) fn parse_candidate_files(
|
|
|
144
223
|
}
|
|
145
224
|
|
|
146
225
|
/// Reject provider files older than the persisted process-cohort boundary.
|
|
226
|
+
/// ---
|
|
227
|
+
/// purpose: 用席位的进程世代边界剔除上一代/别人留下的旧存档
|
|
228
|
+
/// params:
|
|
229
|
+
/// provider: Codex 走 rollout 创建时间,其余走文件 mtime
|
|
230
|
+
/// context: spawned_at 缺失则不过滤(legacy 行);expected id 存在且是 Codex 时把边界截到毫秒
|
|
231
|
+
/// out: 就地过滤的候选列表
|
|
232
|
+
/// contract:
|
|
233
|
+
/// provides:
|
|
234
|
+
/// - name: apply_spawned_at_filter
|
|
235
|
+
/// what: fail-closed —— spawned_at 存在但解析不了(或截断失败)时清空候选,绝不放行
|
|
236
|
+
/// boundary:
|
|
237
|
+
/// - 无 spawned_at 时整体跳过过滤,把守卫让给分配器的身份与唯一性判据
|
|
238
|
+
/// - 无 rollout_path、取不到时间的候选一律剔除
|
|
239
|
+
/// - Codex 刻意不用 mtime:追加写与邻席活动都会抬升 mtime
|
|
240
|
+
/// ---
|
|
147
241
|
pub(super) fn apply_spawned_at_filter(
|
|
148
242
|
provider: Provider,
|
|
149
243
|
context: &CaptureSessionContext,
|
|
@@ -186,6 +280,20 @@ fn candidate_mtime(path: &Path) -> Option<std::time::SystemTime> {
|
|
|
186
280
|
.ok()
|
|
187
281
|
}
|
|
188
282
|
|
|
283
|
+
/// ---
|
|
284
|
+
/// purpose: 在时间窗只能唯一确定一条候选时才据此收窄,避免用弱判据在多条候选间乱挑
|
|
285
|
+
/// params:
|
|
286
|
+
/// provider: Codex 用 rollout 创建时间,其余用 mtime
|
|
287
|
+
/// context: 需要 spawned_at;有 expected id 且候选不超过 1 条时直接不动
|
|
288
|
+
/// out: 就地可能被替换的候选列表
|
|
289
|
+
/// contract:
|
|
290
|
+
/// provides:
|
|
291
|
+
/// - name: apply_spawn_time_window_if_unique
|
|
292
|
+
/// what: 只在窗内恰好剩 1 条时替换整表;0 条或 ≥2 条一律保持原样
|
|
293
|
+
/// boundary:
|
|
294
|
+
/// - 无 spawned_at 或解析失败则不过滤(与 apply_spawned_at_filter 的 fail-closed 相反,此处是不作为)
|
|
295
|
+
/// - 不排序、不去重;只做"唯一即取"的收窄
|
|
296
|
+
/// ---
|
|
189
297
|
pub(super) fn apply_spawn_time_window_if_unique(
|
|
190
298
|
provider: Provider,
|
|
191
299
|
context: &CaptureSessionContext,
|
|
@@ -221,6 +329,18 @@ pub(super) fn apply_spawn_time_window_if_unique(
|
|
|
221
329
|
}
|
|
222
330
|
}
|
|
223
331
|
|
|
332
|
+
/// ---
|
|
333
|
+
/// purpose: 有 pending id 时把命中的候选排到最前,让下游取首条即取到期望的那个
|
|
334
|
+
/// params:
|
|
335
|
+
/// context: 无 expected_session_id 则不动
|
|
336
|
+
/// out: 就地排序的候选切片
|
|
337
|
+
/// contract:
|
|
338
|
+
/// provides:
|
|
339
|
+
/// - name: sort_expected_first_if_needed
|
|
340
|
+
/// what: 稳定排序,只按"是否命中 expected"分两档,不改组内相对次序
|
|
341
|
+
/// boundary:
|
|
342
|
+
/// - 只排序不过滤——未命中的候选仍在表里
|
|
343
|
+
/// ---
|
|
224
344
|
pub(super) fn sort_expected_first_if_needed(
|
|
225
345
|
context: &CaptureSessionContext,
|
|
226
346
|
out: &mut [CapturedSessionCandidate],
|
|
@@ -264,6 +384,22 @@ fn cap_candidates_by_mtime(out: &mut Vec<SessionCandidate>, cap: usize) {
|
|
|
264
384
|
});
|
|
265
385
|
}
|
|
266
386
|
|
|
387
|
+
/// ---
|
|
388
|
+
/// purpose: 读文件头若干字节并截到最后一个完整行,供各 provider 解析记录
|
|
389
|
+
/// params:
|
|
390
|
+
/// path: 待读文件
|
|
391
|
+
/// max_bytes: 读取上限;全模块统一用 CAPTURE_HEAD_BYTES(64KB)
|
|
392
|
+
/// returns: 到最后一个换行为止的文本;整段无换行时返回读到的全部
|
|
393
|
+
/// errors: 打不开或读失败时上抛 io::Error
|
|
394
|
+
/// contract:
|
|
395
|
+
/// provides:
|
|
396
|
+
/// - name: read_head_text
|
|
397
|
+
/// what: 只读、不加锁、不改 mtime
|
|
398
|
+
/// boundary:
|
|
399
|
+
/// - 刻意丢弃末尾不完整的一行——半条 JSON 会让解析器把整段判废
|
|
400
|
+
/// - 非 UTF-8 字节按 lossy 转换,不报错
|
|
401
|
+
/// - 只看头部;窗口之外的内容对所有基于本函数的判据都不可见
|
|
402
|
+
/// ---
|
|
267
403
|
pub(super) fn read_head_text(path: &Path, max_bytes: u64) -> std::io::Result<String> {
|
|
268
404
|
read_head_bytes(path, max_bytes).map(|bytes| complete_head_text(&bytes))
|
|
269
405
|
}
|
|
@@ -352,6 +488,18 @@ fn looks_like_session_file(path: &Path, agent_id: &str, allowed_team_root: Optio
|
|
|
352
488
|
|| (!agent_id.is_empty() && name.contains(agent_id))
|
|
353
489
|
}
|
|
354
490
|
|
|
491
|
+
/// ---
|
|
492
|
+
/// purpose: 把会话文本解析成记录数组,兼容整体 JSON 与 JSONL 两种存档形态
|
|
493
|
+
/// params:
|
|
494
|
+
/// text: 已读入的文本(通常是 read_head_text 的产物)
|
|
495
|
+
/// returns: JSON 数组则展开;JSON 单值则包成一条;都不是则按 JSONL 逐行解析
|
|
496
|
+
/// contract:
|
|
497
|
+
/// provides:
|
|
498
|
+
/// - name: parse_session_records
|
|
499
|
+
/// what: 纯解析,无 I/O
|
|
500
|
+
/// boundary:
|
|
501
|
+
/// - JSONL 分支跳过解析失败的行,不报错——因此"记录为空"不区分"文件空"与"全是坏行"
|
|
502
|
+
/// ---
|
|
355
503
|
pub(super) fn parse_session_records(text: &str) -> Vec<serde_json::Value> {
|
|
356
504
|
match serde_json::from_str::<serde_json::Value>(text) {
|
|
357
505
|
Ok(serde_json::Value::Array(items)) => items,
|
|
@@ -387,6 +535,20 @@ fn candidate_text_has_team_agent_id(text: &str, context: &CaptureSessionContext)
|
|
|
387
535
|
.any(|needle| text.contains(needle))
|
|
388
536
|
}
|
|
389
537
|
|
|
538
|
+
/// ---
|
|
539
|
+
/// purpose: 从会话正文里抽出 team-agent 写进 prompt 的席位身份 marker,作为归属的强证据
|
|
540
|
+
/// params:
|
|
541
|
+
/// text: 待搜索的文本
|
|
542
|
+
/// returns: marker 后反引号之间的 id;未找到、为空或含非法字符时 None
|
|
543
|
+
/// contract:
|
|
544
|
+
/// provides:
|
|
545
|
+
/// - name: embedded_team_agent_worker_id_from_text
|
|
546
|
+
/// what: 纯文本查找,无 I/O
|
|
547
|
+
/// boundary:
|
|
548
|
+
/// - 只取第一处 marker;后续出现的一律忽略
|
|
549
|
+
/// - id 字符集限定为 ASCII 字母数字与 _ - .;越界即判无
|
|
550
|
+
/// - 找不到只说明"这段文本里没有",不等于该会话不属于任何席位
|
|
551
|
+
/// ---
|
|
390
552
|
pub(crate) fn embedded_team_agent_worker_id_from_text(text: &str) -> Option<String> {
|
|
391
553
|
const PREFIX: &str = "You are Team Agent worker `";
|
|
392
554
|
let start = text.find(PREFIX)? + PREFIX.len();
|
|
@@ -403,6 +565,19 @@ pub(crate) fn embedded_team_agent_worker_id_from_text(text: &str) -> Option<Stri
|
|
|
403
565
|
Some(id.to_string())
|
|
404
566
|
}
|
|
405
567
|
|
|
568
|
+
/// ---
|
|
569
|
+
/// purpose: 按路径取该存档的 embedded 席位身份,供捕获链外的调用方直接查归属
|
|
570
|
+
/// params:
|
|
571
|
+
/// path: 存档文件路径
|
|
572
|
+
/// returns: 读头 64KB 后抽到的席位 id;读失败或没抽到给 None
|
|
573
|
+
/// contract:
|
|
574
|
+
/// provides:
|
|
575
|
+
/// - name: rollout_path_embedded_team_agent_worker_id
|
|
576
|
+
/// what: read_head_text + embedded_team_agent_worker_id_from_text 的组合,只读
|
|
577
|
+
/// boundary:
|
|
578
|
+
/// - 读失败与"文件里确实没有 marker"都返回 None,两者不可区分
|
|
579
|
+
/// - marker 若出现在 64KB 之后则看不见
|
|
580
|
+
/// ---
|
|
406
581
|
pub(crate) fn rollout_path_embedded_team_agent_worker_id(path: &Path) -> Option<String> {
|
|
407
582
|
read_head_text(path, CAPTURE_HEAD_BYTES)
|
|
408
583
|
.ok()
|
|
@@ -421,6 +596,18 @@ fn candidate_path_matches_agent_id(path: &Path, context: &CaptureSessionContext)
|
|
|
421
596
|
name.contains(id) || name.contains(&dashed)
|
|
422
597
|
}
|
|
423
598
|
|
|
599
|
+
/// ---
|
|
600
|
+
/// purpose: 从一条会话记录里取工作目录,兼容 claude 顶层 cwd 与 codex session_meta.payload.cwd 两种形状
|
|
601
|
+
/// params:
|
|
602
|
+
/// record: 单条已解析记录
|
|
603
|
+
/// returns: 顶层 cwd 优先;否则取 session_meta.payload.cwd 或 payload.cwd;都没有则 None
|
|
604
|
+
/// contract:
|
|
605
|
+
/// provides:
|
|
606
|
+
/// - name: record_cwd
|
|
607
|
+
/// what: 纯取值,不做路径归一或存在性检查
|
|
608
|
+
/// boundary:
|
|
609
|
+
/// - 只认这三个位置;其它自定义字段一概不看
|
|
610
|
+
/// ---
|
|
424
611
|
pub(super) fn record_cwd(record: &serde_json::Value) -> Option<String> {
|
|
425
612
|
record
|
|
426
613
|
.get("cwd")
|
|
@@ -436,6 +623,21 @@ pub(super) fn record_cwd(record: &serde_json::Value) -> Option<String> {
|
|
|
436
623
|
.map(ToString::to_string)
|
|
437
624
|
}
|
|
438
625
|
|
|
626
|
+
/// ---
|
|
627
|
+
/// purpose: 判断记录里的 cwd 与席位 spawn_cwd 是不是同一个工作目录
|
|
628
|
+
/// params:
|
|
629
|
+
/// left: 记录里的 cwd
|
|
630
|
+
/// right: 席位 spawn_cwd
|
|
631
|
+
/// returns: 字面相等、canonicalize 后相等、或 left 的父目录等于 right 时为 true
|
|
632
|
+
/// contract:
|
|
633
|
+
/// provides:
|
|
634
|
+
/// - name: paths_equivalent
|
|
635
|
+
/// what: 会触发文件系统 canonicalize;失败则退回原路径继续比
|
|
636
|
+
/// boundary:
|
|
637
|
+
/// - 父目录等价是刻意放宽的一档:记录 cwd 是 spawn_cwd 的直接子目录也算同 cwd,嵌套 workspace 下会把子目录的存档收进来
|
|
638
|
+
/// - 不对称:只查 left 的父等于 right,反向不查
|
|
639
|
+
/// - 不解析符号链接之外的等价(如大小写不敏感文件系统的同名不同写)
|
|
640
|
+
/// ---
|
|
439
641
|
pub(super) fn paths_equivalent(left: &Path, right: &Path) -> bool {
|
|
440
642
|
if left == right {
|
|
441
643
|
return true;
|
|
@@ -1,10 +1,42 @@
|
|
|
1
|
-
//!
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 只按 pending session id 到 copilot 的 session-store.db 里查一行,查得到才算捕获
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: scan_session_store
|
|
6
|
+
//! what: 有 expected_session_id 且 sessions 表里存在该 id 时返回唯一候选,否则空
|
|
7
|
+
//! requires:
|
|
8
|
+
//! - name: crate::provider::adapters::copilot_fork::copilot_home
|
|
9
|
+
//! what: session-store.db 的位置由 copilot adapter 决定,本文件不自行推导 HOME
|
|
10
|
+
//! - name: rusqlite
|
|
11
|
+
//! what: 只读打开 sqlite,不建表、不写入
|
|
12
|
+
//! boundary:
|
|
13
|
+
//! - 只服务 Provider::Copilot
|
|
14
|
+
//! - 无 pending id 一律返回空——绝不按 cwd/时间挑「最近那个会话」
|
|
15
|
+
//! - 候选的 rollout_path 是共享的 session-store.db 本身,不是每席位一份的文件
|
|
16
|
+
//! - embedded_agent_id 恒 None:copilot 存档里没有 team-agent 身份 marker 可读
|
|
17
|
+
//! - 打不开 HOME / 库不存在 / SQL 失败一律退成空向量,不上抛错误
|
|
18
|
+
//! maturity: wired
|
|
19
|
+
//! ---
|
|
2
20
|
use std::path::Path;
|
|
3
21
|
|
|
4
22
|
use crate::provider::types::{CaptureVia, CapturedSession, Confidence, RolloutPath, SessionId};
|
|
5
23
|
|
|
6
24
|
use super::{CaptureSessionContext, CapturedSessionCandidate};
|
|
7
25
|
|
|
26
|
+
/// ---
|
|
27
|
+
/// purpose: 按 pending id 在 copilot session-store.db 的 sessions 表里做一次存在性查询
|
|
28
|
+
/// params:
|
|
29
|
+
/// context: 只用到 expected_session_id 与 spawn_cwd(后者仅原样写进候选)
|
|
30
|
+
/// returns: 命中给一条 FsWatch/High 候选,rollout_path 指向 session-store.db;未命中或无 pending id 给空向量
|
|
31
|
+
/// contract:
|
|
32
|
+
/// provides:
|
|
33
|
+
/// - name: scan_session_store
|
|
34
|
+
/// what: 只读 sqlite 一行 id,不读会话正文、不按 cwd 过滤
|
|
35
|
+
/// boundary:
|
|
36
|
+
/// - 无 expected_session_id 直接返回空,不做任何回落挑选
|
|
37
|
+
/// - 不校验该会话的 cwd 是否等于 spawn_cwd——库里 cwd 列存在但本函数不用
|
|
38
|
+
/// - 所有失败路径都退成空向量,调用方无法区分「库不存在」与「查无此行」
|
|
39
|
+
/// ---
|
|
8
40
|
pub(super) fn scan_session_store(context: &CaptureSessionContext) -> Vec<CapturedSessionCandidate> {
|
|
9
41
|
let Ok(home) = crate::provider::adapters::copilot_fork::copilot_home() else {
|
|
10
42
|
return Vec::new();
|