@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.
Files changed (164) hide show
  1. package/Cargo.lock +8 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +1 -0
  4. package/crates/team-agent/src/cli/adapters.rs +5 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +82 -0
  6. package/crates/team-agent/src/cli/emit.rs +3 -3
  7. package/crates/team-agent/src/cli/grok_slot.rs +299 -0
  8. package/crates/team-agent/src/cli/leader.rs +21 -7
  9. package/crates/team-agent/src/cli/leaders.rs +125 -1
  10. package/crates/team-agent/src/cli/mod.rs +20 -0
  11. package/crates/team-agent/src/cli/send/presentation.rs +7 -1
  12. package/crates/team-agent/src/cli/spec.rs +2 -0
  13. package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
  14. package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
  15. package/crates/team-agent/src/cli/tests/base.rs +26 -3
  16. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
  17. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
  18. package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
  19. package/crates/team-agent/src/communication_mode/mod.rs +6 -4
  20. package/crates/team-agent/src/compiler.rs +59 -0
  21. package/crates/team-agent/src/coordinator/backoff.rs +55 -0
  22. package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
  23. package/crates/team-agent/src/coordinator/health.rs +173 -0
  24. package/crates/team-agent/src/coordinator/mod.rs +31 -0
  25. package/crates/team-agent/src/coordinator/orphan.rs +31 -0
  26. package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
  27. package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
  28. package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
  29. package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
  30. package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
  31. package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
  32. package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
  33. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
  34. package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
  35. package/crates/team-agent/src/coordinator/tick.rs +133 -22
  36. package/crates/team-agent/src/coordinator/types.rs +49 -0
  37. package/crates/team-agent/src/db/message_store.rs +39 -5
  38. package/crates/team-agent/src/layout/worker_env.rs +20 -3
  39. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  40. package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
  41. package/crates/team-agent/src/leader/registry.rs +65 -0
  42. package/crates/team-agent/src/leader/start.rs +920 -63
  43. package/crates/team-agent/src/lifecycle/display.rs +56 -0
  44. package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
  45. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -12
  46. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +62 -0
  47. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +59 -0
  48. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
  49. package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
  50. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
  51. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -456
  52. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
  53. package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
  54. package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
  55. package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
  56. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
  57. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
  58. package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
  59. package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
  60. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
  61. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
  62. package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
  63. package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
  64. package/crates/team-agent/src/lifecycle/launch/spawn.rs +62 -2
  65. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +139 -0
  66. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
  67. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
  68. package/crates/team-agent/src/lifecycle/launch.rs +99 -29
  69. package/crates/team-agent/src/lifecycle/lock.rs +42 -1
  70. package/crates/team-agent/src/lifecycle/mod.rs +11 -0
  71. package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
  72. package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
  73. package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
  74. package/crates/team-agent/src/lifecycle/restart/agent.rs +124 -0
  75. package/crates/team-agent/src/lifecycle/restart/common.rs +369 -11
  76. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
  77. package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
  78. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
  79. package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
  80. package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
  81. package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
  82. package/crates/team-agent/src/lifecycle/restart.rs +29 -0
  83. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
  84. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
  85. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
  86. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
  87. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
  88. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
  89. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
  90. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
  91. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
  92. package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
  93. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
  94. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
  95. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
  96. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
  97. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
  98. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
  99. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
  100. package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
  101. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
  102. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
  103. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +2 -0
  104. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
  105. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
  106. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
  107. package/crates/team-agent/src/lifecycle/tests.rs +23 -13
  108. package/crates/team-agent/src/lifecycle/types.rs +61 -0
  109. package/crates/team-agent/src/lifecycle/worker_command_context.rs +85 -11
  110. package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
  111. package/crates/team-agent/src/mcp_server/tests.rs +196 -2
  112. package/crates/team-agent/src/messaging/delivery.rs +405 -40
  113. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  114. package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
  115. package/crates/team-agent/src/messaging/results.rs +4 -0
  116. package/crates/team-agent/src/messaging/send.rs +34 -0
  117. package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
  118. package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
  119. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
  120. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  121. package/crates/team-agent/src/messaging/types.rs +4 -1
  122. package/crates/team-agent/src/model/enums.rs +14 -5
  123. package/crates/team-agent/src/model/permissions.rs +3 -0
  124. package/crates/team-agent/src/os_probe.rs +73 -2
  125. package/crates/team-agent/src/provider/adapter.rs +231 -4
  126. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
  127. package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
  128. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  129. package/crates/team-agent/src/provider/bypass_flags.rs +46 -2
  130. package/crates/team-agent/src/provider/classify.rs +4 -2
  131. package/crates/team-agent/src/provider/faults.rs +2 -1
  132. package/crates/team-agent/src/provider/mod.rs +11 -0
  133. package/crates/team-agent/src/provider/session/capture.rs +158 -20
  134. package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
  135. package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
  136. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
  137. package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
  138. package/crates/team-agent/src/provider/session/mod.rs +18 -0
  139. package/crates/team-agent/src/provider/session/resume.rs +57 -0
  140. package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
  141. package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
  142. package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
  143. package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
  144. package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
  145. package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
  146. package/crates/team-agent/src/provider/session_scan.rs +8 -0
  147. package/crates/team-agent/src/provider/submit_now.rs +94 -0
  148. package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
  149. package/crates/team-agent/src/provider/types.rs +4 -2
  150. package/crates/team-agent/src/provider/wire.rs +23 -1
  151. package/crates/team-agent/src/state/persist.rs +301 -8
  152. package/crates/team-agent/src/state/repository.rs +5 -0
  153. package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
  154. package/crates/team-agent/src/tmux_backend.rs +1064 -75
  155. package/crates/team-agent/src/transport/tests/wire.rs +28 -2
  156. package/crates/team-agent/src/transport.rs +248 -1
  157. package/npm/install.mjs +129 -73
  158. package/package.json +4 -4
  159. package/skills/team-agent/SKILL.md +33 -238
  160. package/skills/team-agent/command-coverage.json +31 -0
  161. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
  162. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
  163. package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
  164. 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 | Provider::GeminiCli | Provider::Fake => {}
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();