@team-agent/installer 0.5.60 → 0.5.62
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 +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +49 -3
- package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
- package/crates/team-agent/src/cli/diagnose.rs +184 -0
- package/crates/team-agent/src/cli/emit.rs +104 -21
- package/crates/team-agent/src/cli/helpers.rs +1 -0
- package/crates/team-agent/src/cli/leader.rs +14 -2
- package/crates/team-agent/src/cli/leaders.rs +2 -0
- package/crates/team-agent/src/cli/mod.rs +296 -8
- package/crates/team-agent/src/cli/named_address.rs +26 -2
- package/crates/team-agent/src/cli/profile.rs +2 -1
- package/crates/team-agent/src/cli/send/coordinator.rs +1 -0
- package/crates/team-agent/src/cli/send/mailbox.rs +3 -0
- package/crates/team-agent/src/cli/send/persist.rs +1 -0
- package/crates/team-agent/src/cli/send/presentation.rs +3 -0
- package/crates/team-agent/src/cli/send/resolve.rs +206 -1
- package/crates/team-agent/src/cli/send.rs +2 -1
- package/crates/team-agent/src/cli/spec.rs +5 -1
- package/crates/team-agent/src/cli/status.rs +2 -1
- package/crates/team-agent/src/cli/status_port/compact.rs +1 -0
- package/crates/team-agent/src/cli/status_port/format.rs +1 -0
- package/crates/team-agent/src/cli/status_port/inbox.rs +1 -0
- package/crates/team-agent/src/cli/status_port/store.rs +7 -3
- package/crates/team-agent/src/cli/tests/named_address.rs +65 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
- package/crates/team-agent/src/cli/types.rs +36 -2
- package/crates/team-agent/src/communication_mode/mod.rs +54 -0
- package/crates/team-agent/src/compiler/tests.rs +5 -5
- package/crates/team-agent/src/compiler.rs +54 -1
- package/crates/team-agent/src/conpty/backend.rs +1 -0
- package/crates/team-agent/src/conpty/mod.rs +1 -0
- package/crates/team-agent/src/coordinator/backoff.rs +5 -8
- package/crates/team-agent/src/coordinator/conpty_shim.rs +3 -2
- package/crates/team-agent/src/coordinator/health.rs +1 -0
- package/crates/team-agent/src/coordinator/mod.rs +2 -2
- package/crates/team-agent/src/coordinator/orphan.rs +4 -116
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +6 -5
- package/crates/team-agent/src/coordinator/steps/delivery.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +2 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +1 -0
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +0 -174
- package/crates/team-agent/src/coordinator/tests/api_error_recovery.rs +0 -9
- package/crates/team-agent/src/coordinator/tests/basics.rs +0 -184
- package/crates/team-agent/src/coordinator/tests/mod.rs +2 -17
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -42
- package/crates/team-agent/src/coordinator/tick.rs +24 -28
- package/crates/team-agent/src/coordinator/types.rs +3 -176
- package/crates/team-agent/src/db/agent_health_capture.rs +1 -0
- package/crates/team-agent/src/db/message_store.rs +47 -2
- package/crates/team-agent/src/db/migration.rs +5 -4
- package/crates/team-agent/src/db/mod.rs +1 -0
- package/crates/team-agent/src/db/schema.rs +1 -0
- package/crates/team-agent/src/diagnose/comms.rs +2 -1
- package/crates/team-agent/src/diagnose/mod.rs +1 -0
- package/crates/team-agent/src/diagnose/orphans.rs +1 -0
- package/crates/team-agent/src/fake_worker.rs +21 -1
- package/crates/team-agent/src/layout/manager.rs +3 -7
- package/crates/team-agent/src/layout/mod.rs +2 -2
- package/crates/team-agent/src/layout/overlay.rs +6 -1
- package/crates/team-agent/src/layout/placement.rs +1 -0
- package/crates/team-agent/src/layout/recovery.rs +3 -0
- package/crates/team-agent/src/layout/runtime_sessions.rs +8 -3
- package/crates/team-agent/src/layout/sessions.rs +8 -1
- package/crates/team-agent/src/layout/tmux_endpoint.rs +3 -0
- package/crates/team-agent/src/layout/worker_env.rs +3 -0
- package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
- package/crates/team-agent/src/leader/helpers.rs +1 -0
- package/crates/team-agent/src/leader/incident.rs +1 -0
- package/crates/team-agent/src/leader/lease.rs +27 -22
- package/crates/team-agent/src/leader/mod.rs +2 -3
- package/crates/team-agent/src/leader/owner_bind.rs +13 -3
- package/crates/team-agent/src/leader/provider_attribution.rs +1 -0
- package/crates/team-agent/src/leader/rediscover.rs +4 -17
- package/crates/team-agent/src/leader/registry.rs +3 -15
- package/crates/team-agent/src/leader/start.rs +1072 -186
- package/crates/team-agent/src/leader/takeover.rs +10 -232
- package/crates/team-agent/src/leader/tests/claim_leader_mailbox_flush_contract.rs +544 -0
- package/crates/team-agent/src/leader/tests/historical_inventory_upgrade_gate_red.rs +624 -0
- package/crates/team-agent/src/leader/tests/identity.rs +71 -59
- package/crates/team-agent/src/leader/tests/identity_session_names.rs +42 -0
- package/crates/team-agent/src/leader/tests/idle.rs +0 -193
- package/crates/team-agent/src/leader/tests/lease_api.rs +0 -45
- package/crates/team-agent/src/leader/tests/mod.rs +4 -41
- package/crates/team-agent/src/leader/tests/rehomed_env.rs +83 -0
- package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1273 -0
- package/crates/team-agent/src/leader/tests/terminal_and_replay_vs_rerender_invariants_red.rs +417 -0
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +22 -103
- package/crates/team-agent/src/leader/types.rs +1 -28
- package/crates/team-agent/src/lib.rs +7 -0
- package/crates/team-agent/src/lifecycle/display.rs +7 -1
- package/crates/team-agent/src/lifecycle/helpers.rs +3 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +4 -26
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -27
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +2 -28
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +4 -32
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch.rs +11 -12
- package/crates/team-agent/src/lifecycle/mod.rs +2 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +4 -4
- package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +2 -2
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -5
- package/crates/team-agent/src/lifecycle/restart/remove.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/selection.rs +3 -3
- package/crates/team-agent/src/lifecycle/restart.rs +13 -11
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1007 -0
- package/crates/team-agent/src/lifecycle/tests/b0_legacy_snapshot_nonauthority_contract.rs +265 -0
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +459 -0
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +849 -0
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +323 -0
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +423 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +737 -0
- package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +1187 -0
- package/crates/team-agent/src/lifecycle/tests/codex_weak_window_attribution_red.rs +683 -0
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +394 -0
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +1856 -0
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +917 -0
- package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +481 -0
- package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +301 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +516 -0
- package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +986 -0
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +651 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -0
- package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +332 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +846 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_residual_g1_g4_red.rs +648 -0
- package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +910 -0
- package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +630 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1083 -0
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +1677 -0
- package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +410 -0
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +428 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1032 -0
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +869 -0
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1249 -0
- package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +342 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +542 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1103 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1276 -0
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_roster_red.rs +145 -0
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +603 -0
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +837 -0
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +928 -0
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +951 -0
- package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +327 -0
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +760 -0
- package/crates/team-agent/src/lifecycle/tests.rs +59 -0
- package/crates/team-agent/src/lifecycle/types.rs +0 -9
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +29 -11
- package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -0
- package/crates/team-agent/src/mcp_server/mod.rs +1 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +5 -3
- package/crates/team-agent/src/mcp_server/tests/send.rs +26 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +1 -1
- package/crates/team-agent/src/mcp_server/tools.rs +101 -66
- package/crates/team-agent/src/mcp_server/types.rs +6 -18
- package/crates/team-agent/src/mcp_server/wire.rs +32 -26
- package/crates/team-agent/src/messaging/activity.rs +1 -0
- package/crates/team-agent/src/messaging/address.rs +1 -0
- package/crates/team-agent/src/messaging/delivery.rs +109 -41
- package/crates/team-agent/src/messaging/helpers.rs +2 -1
- package/crates/team-agent/src/messaging/leader_channel.rs +34 -10
- package/crates/team-agent/src/messaging/leader_receiver.rs +42 -24
- package/crates/team-agent/src/messaging/mod.rs +10 -14
- package/crates/team-agent/src/messaging/peers.rs +2 -0
- package/crates/team-agent/src/messaging/persist.rs +3 -1
- package/crates/team-agent/src/messaging/presentation.rs +111 -1
- package/crates/team-agent/src/messaging/results.rs +166 -17
- package/crates/team-agent/src/messaging/scheduler.rs +1 -0
- package/crates/team-agent/src/messaging/selftest.rs +1 -1
- package/crates/team-agent/src/messaging/send.rs +135 -89
- package/crates/team-agent/src/messaging/tests/leader_channel.rs +6 -6
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +187 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +13 -6
- package/crates/team-agent/src/messaging/types.rs +7 -2
- package/crates/team-agent/src/messaging/watchers.rs +24 -13
- package/crates/team-agent/src/model/enums.rs +1 -0
- package/crates/team-agent/src/model/errors.rs +1 -0
- package/crates/team-agent/src/model/ids.rs +1 -0
- package/crates/team-agent/src/model/mod.rs +2 -0
- package/crates/team-agent/src/model/name_similarity.rs +1 -0
- package/crates/team-agent/src/model/pane_authority_refusal.rs +359 -0
- package/crates/team-agent/src/model/paths.rs +1 -0
- package/crates/team-agent/src/model/permissions.rs +3 -2
- package/crates/team-agent/src/model/routing.rs +1 -0
- package/crates/team-agent/src/model/spec.rs +17 -0
- package/crates/team-agent/src/model/task_graph.rs +1 -0
- package/crates/team-agent/src/model/yaml/tests.rs +1 -0
- package/crates/team-agent/src/model/yaml.rs +1 -0
- package/crates/team-agent/src/packaging/install.rs +9 -147
- package/crates/team-agent/src/packaging/migrate.rs +2 -0
- package/crates/team-agent/src/packaging/mod.rs +1 -0
- package/crates/team-agent/src/packaging/repair.rs +2 -0
- package/crates/team-agent/src/packaging/tests.rs +29 -218
- package/crates/team-agent/src/packaging/types.rs +8 -15
- package/crates/team-agent/src/platform/argv.rs +4 -0
- package/crates/team-agent/src/platform/errors.rs +10 -1
- package/crates/team-agent/src/platform/file_lock.rs +4 -157
- package/crates/team-agent/src/platform/mod.rs +0 -61
- package/crates/team-agent/src/platform/process.rs +1 -0
- package/crates/team-agent/src/provider/adapter.rs +1 -0
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -0
- package/crates/team-agent/src/provider/adapters/claude_fork.rs +1 -0
- package/crates/team-agent/src/provider/adapters/codex.rs +1 -0
- package/crates/team-agent/src/provider/adapters/copilot.rs +1 -0
- package/crates/team-agent/src/provider/adapters/copilot_fork.rs +1 -0
- package/crates/team-agent/src/provider/adapters/fake.rs +1 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
- package/crates/team-agent/src/provider/approvals/mod.rs +1 -0
- package/crates/team-agent/src/provider/approvals/parsing.rs +2 -5
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +6 -5
- package/crates/team-agent/src/provider/classify.rs +1 -0
- package/crates/team-agent/src/provider/faults.rs +1 -26
- package/crates/team-agent/src/provider/helpers.rs +1 -0
- package/crates/team-agent/src/provider/mod.rs +1 -1
- package/crates/team-agent/src/provider/session/capture.rs +41 -22
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork.rs +1 -0
- package/crates/team-agent/src/provider/session/mod.rs +2 -4
- package/crates/team-agent/src/provider/session/resume.rs +2 -209
- package/crates/team-agent/src/provider/session_scan/claude.rs +93 -3
- package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +24 -7
- package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
- package/crates/team-agent/src/provider/session_scan.rs +6 -42
- package/crates/team-agent/src/provider/startup_prompt.rs +1 -0
- package/crates/team-agent/src/provider/types.rs +1 -0
- package/crates/team-agent/src/provider/wire.rs +1 -0
- package/crates/team-agent/src/state/identity.rs +16 -5
- package/crates/team-agent/src/state/identity_keys.rs +1 -0
- package/crates/team-agent/src/state/mod.rs +2 -0
- package/crates/team-agent/src/state/owner_gate.rs +5 -0
- package/crates/team-agent/src/state/ownership.rs +10 -4
- package/crates/team-agent/src/state/paths.rs +12 -0
- package/crates/team-agent/src/state/persist.rs +9 -3
- package/crates/team-agent/src/state/projection.rs +15 -2
- package/crates/team-agent/src/state/repository/intent.rs +1 -0
- package/crates/team-agent/src/state/repository/tests.rs +1 -0
- package/crates/team-agent/src/state/repository.rs +7 -0
- package/crates/team-agent/src/state/selector.rs +1 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +39 -0
- package/crates/team-agent/src/tmux_backend.rs +63 -0
- package/crates/team-agent/src/topology.rs +3 -0
- package/crates/team-agent/src/transport.rs +15 -0
- package/package.json +4 -4
- package/skills/team-agent/command-coverage.json +379 -0
- package/crates/team-agent/src/leader/inject.rs +0 -33
- package/crates/team-agent/src/provider/command.rs +0 -80
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
//!
|
|
1
|
+
//!
|
|
2
|
+
//! unit-5 (Stage 2) — closed `ResumeRefusalReason` enum and recovery hints.
|
|
2
3
|
//!
|
|
3
4
|
//! Today the restart resume gate flattens every refusal into one of two
|
|
4
5
|
//! opaque strings:
|
|
@@ -159,110 +160,6 @@ impl ResumeRefusalReason {
|
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
/// Outcome of `ResumePreflight::check`.
|
|
163
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
164
|
-
pub enum ResumePreflightOutcome {
|
|
165
|
-
/// Worker can resume from the named session.
|
|
166
|
-
Resume { session_id: String },
|
|
167
|
-
/// `--allow-fresh` was set; worker will start fresh.
|
|
168
|
-
FreshStart,
|
|
169
|
-
/// Resume refused. Caller MUST NOT proceed with teardown/spawn.
|
|
170
|
-
Refuse { reason: ResumeRefusalReason },
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/// Information about whether a provider backing file is present on disk.
|
|
174
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
175
|
-
pub struct ProviderBackingCheck {
|
|
176
|
-
pub paths: Vec<PathBuf>,
|
|
177
|
-
pub exists: bool,
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/// Per-decision detail emitted to events / JSON.
|
|
181
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
182
|
-
pub struct ResumeDecisionDetail {
|
|
183
|
-
pub agent_id: String,
|
|
184
|
-
pub session_id: Option<String>,
|
|
185
|
-
pub provider: String,
|
|
186
|
-
pub backing: Option<ProviderBackingCheck>,
|
|
187
|
-
pub outcome: ResumePreflightOutcome,
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/// Pure resume preflight evaluator. No I/O — callers pass facts in.
|
|
191
|
-
pub struct ResumePreflight;
|
|
192
|
-
|
|
193
|
-
impl ResumePreflight {
|
|
194
|
-
/// Decide a single worker's resume outcome from the facts the
|
|
195
|
-
/// restart code already collected. Mirrors the existing
|
|
196
|
-
/// `classify_restart_plan_with_resume_validation` decision tree:
|
|
197
|
-
///
|
|
198
|
-
/// * session_id present + provider can resume + backing exists -> Resume
|
|
199
|
-
/// * session_id present + (no resume OR no backing) + allow_fresh -> FreshStart
|
|
200
|
-
/// * session_id present + (no resume OR no backing) + !allow_fresh -> Refuse
|
|
201
|
-
/// * session_id absent + allow_fresh -> FreshStart
|
|
202
|
-
/// * session_id absent + !allow_fresh -> Refuse(NoSessionId)
|
|
203
|
-
pub fn check(
|
|
204
|
-
session_id: Option<&str>,
|
|
205
|
-
provider_can_resume: bool,
|
|
206
|
-
backing: Option<&ProviderBackingCheck>,
|
|
207
|
-
provider_name: &str,
|
|
208
|
-
allow_fresh: bool,
|
|
209
|
-
) -> ResumePreflightOutcome {
|
|
210
|
-
Self::check_with_hint(
|
|
211
|
-
session_id,
|
|
212
|
-
provider_can_resume,
|
|
213
|
-
backing,
|
|
214
|
-
provider_name,
|
|
215
|
-
allow_fresh,
|
|
216
|
-
None,
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/// Layer 2 self-healing variant: same decision tree as
|
|
221
|
-
/// [`Self::check`], but propagates an optional `RecoveryHint` into
|
|
222
|
-
/// the `SessionBackingStoreMissing` refusal so the CLI / event log
|
|
223
|
-
/// can surface a "look for session named X under cwd Y" pointer to
|
|
224
|
-
/// the operator. The hint is for HUMAN consumption only — no
|
|
225
|
-
/// auto-resume happens off it (Layer 3 follow-up).
|
|
226
|
-
pub fn check_with_hint(
|
|
227
|
-
session_id: Option<&str>,
|
|
228
|
-
provider_can_resume: bool,
|
|
229
|
-
backing: Option<&ProviderBackingCheck>,
|
|
230
|
-
provider_name: &str,
|
|
231
|
-
allow_fresh: bool,
|
|
232
|
-
recovery_hint: Option<RecoveryHint>,
|
|
233
|
-
) -> ResumePreflightOutcome {
|
|
234
|
-
match session_id {
|
|
235
|
-
Some(sid) if provider_can_resume => {
|
|
236
|
-
let backing_present = backing.map(|b| b.exists).unwrap_or(true);
|
|
237
|
-
if backing_present {
|
|
238
|
-
ResumePreflightOutcome::Resume {
|
|
239
|
-
session_id: sid.to_string(),
|
|
240
|
-
}
|
|
241
|
-
} else if allow_fresh {
|
|
242
|
-
ResumePreflightOutcome::FreshStart
|
|
243
|
-
} else {
|
|
244
|
-
ResumePreflightOutcome::Refuse {
|
|
245
|
-
reason: ResumeRefusalReason::SessionBackingStoreMissing {
|
|
246
|
-
checked_paths: backing.map(|b| b.paths.clone()).unwrap_or_default(),
|
|
247
|
-
recovery_hint,
|
|
248
|
-
},
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
Some(_) if allow_fresh => ResumePreflightOutcome::FreshStart,
|
|
253
|
-
Some(_) => ResumePreflightOutcome::Refuse {
|
|
254
|
-
reason: ResumeRefusalReason::ProviderResumeUnsupported {
|
|
255
|
-
provider: provider_name.to_string(),
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
None if allow_fresh => ResumePreflightOutcome::FreshStart,
|
|
259
|
-
None => ResumePreflightOutcome::Refuse {
|
|
260
|
-
reason: ResumeRefusalReason::NoSessionId,
|
|
261
|
-
},
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
163
|
#[cfg(test)]
|
|
267
164
|
mod tests {
|
|
268
165
|
use super::*;
|
|
@@ -287,58 +184,6 @@ mod tests {
|
|
|
287
184
|
assert!(matches!(r, ResumeRefusalReason::Other { .. }));
|
|
288
185
|
}
|
|
289
186
|
|
|
290
|
-
#[test]
|
|
291
|
-
fn preflight_resume_when_session_and_backing_present() {
|
|
292
|
-
let backing = ProviderBackingCheck {
|
|
293
|
-
paths: vec![PathBuf::from("/tmp/x.jsonl")],
|
|
294
|
-
exists: true,
|
|
295
|
-
};
|
|
296
|
-
let out = ResumePreflight::check(Some("sess-1"), true, Some(&backing), "codex", false);
|
|
297
|
-
assert!(matches!(out, ResumePreflightOutcome::Resume { .. }));
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
#[test]
|
|
301
|
-
fn preflight_refuses_when_session_id_missing_without_allow_fresh() {
|
|
302
|
-
let out = ResumePreflight::check(None, true, None, "codex", false);
|
|
303
|
-
assert_eq!(
|
|
304
|
-
out,
|
|
305
|
-
ResumePreflightOutcome::Refuse {
|
|
306
|
-
reason: ResumeRefusalReason::NoSessionId
|
|
307
|
-
}
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
#[test]
|
|
312
|
-
fn preflight_distinguishes_backing_missing_from_no_session_id() {
|
|
313
|
-
let backing = ProviderBackingCheck {
|
|
314
|
-
paths: vec![PathBuf::from("/missing.jsonl")],
|
|
315
|
-
exists: false,
|
|
316
|
-
};
|
|
317
|
-
let out = ResumePreflight::check(Some("sess-x"), true, Some(&backing), "codex", false);
|
|
318
|
-
match out {
|
|
319
|
-
ResumePreflightOutcome::Refuse { reason } => {
|
|
320
|
-
assert_eq!(reason.wire(), "session_backing_store_missing");
|
|
321
|
-
}
|
|
322
|
-
other => panic!("expected backing-missing refusal; got {other:?}"),
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
#[test]
|
|
327
|
-
fn preflight_allow_fresh_converts_refusals_to_fresh() {
|
|
328
|
-
let backing = ProviderBackingCheck {
|
|
329
|
-
paths: vec![PathBuf::from("/missing.jsonl")],
|
|
330
|
-
exists: false,
|
|
331
|
-
};
|
|
332
|
-
assert_eq!(
|
|
333
|
-
ResumePreflight::check(None, true, None, "codex", true),
|
|
334
|
-
ResumePreflightOutcome::FreshStart
|
|
335
|
-
);
|
|
336
|
-
assert_eq!(
|
|
337
|
-
ResumePreflight::check(Some("sess-x"), true, Some(&backing), "codex", true),
|
|
338
|
-
ResumePreflightOutcome::FreshStart
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
187
|
// Layer 2 self-healing tests ────────────────────────────────────────────
|
|
343
188
|
|
|
344
189
|
#[test]
|
|
@@ -364,56 +209,4 @@ mod tests {
|
|
|
364
209
|
};
|
|
365
210
|
assert_eq!(hint.picker_hint(), "codex session");
|
|
366
211
|
}
|
|
367
|
-
|
|
368
|
-
#[test]
|
|
369
|
-
fn preflight_with_hint_attaches_to_backing_missing_refusal() {
|
|
370
|
-
let backing = ProviderBackingCheck {
|
|
371
|
-
paths: vec![PathBuf::from("/missing.jsonl")],
|
|
372
|
-
exists: false,
|
|
373
|
-
};
|
|
374
|
-
let hint = RecoveryHint {
|
|
375
|
-
provider_session_name_hint: Some("coder".to_string()),
|
|
376
|
-
spawn_cwd: Some(PathBuf::from("/repo")),
|
|
377
|
-
provider: "claude".to_string(),
|
|
378
|
-
};
|
|
379
|
-
let out = ResumePreflight::check_with_hint(
|
|
380
|
-
Some("sess-1"),
|
|
381
|
-
true,
|
|
382
|
-
Some(&backing),
|
|
383
|
-
"claude",
|
|
384
|
-
false,
|
|
385
|
-
Some(hint.clone()),
|
|
386
|
-
);
|
|
387
|
-
match out {
|
|
388
|
-
ResumePreflightOutcome::Refuse {
|
|
389
|
-
reason:
|
|
390
|
-
ResumeRefusalReason::SessionBackingStoreMissing {
|
|
391
|
-
recovery_hint: Some(h),
|
|
392
|
-
..
|
|
393
|
-
},
|
|
394
|
-
} => {
|
|
395
|
-
assert_eq!(h, hint);
|
|
396
|
-
}
|
|
397
|
-
other => panic!("expected SessionBackingStoreMissing with hint; got {other:?}"),
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
#[test]
|
|
402
|
-
fn preflight_no_hint_legacy_behavior_unchanged() {
|
|
403
|
-
// Default check() (no hint) still produces None recovery_hint —
|
|
404
|
-
// wire-string-compatible with pre-Layer-2 callers.
|
|
405
|
-
let backing = ProviderBackingCheck {
|
|
406
|
-
paths: vec![PathBuf::from("/missing.jsonl")],
|
|
407
|
-
exists: false,
|
|
408
|
-
};
|
|
409
|
-
let out = ResumePreflight::check(Some("sess-x"), true, Some(&backing), "codex", false);
|
|
410
|
-
match out {
|
|
411
|
-
ResumePreflightOutcome::Refuse {
|
|
412
|
-
reason: ResumeRefusalReason::SessionBackingStoreMissing { recovery_hint, .. },
|
|
413
|
-
} => {
|
|
414
|
-
assert!(recovery_hint.is_none());
|
|
415
|
-
}
|
|
416
|
-
other => panic!("expected backing-missing refusal; got {other:?}"),
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
212
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
//!
|
|
1
2
|
use std::path::{Path, PathBuf};
|
|
2
3
|
|
|
3
|
-
use crate::provider::
|
|
4
|
+
use crate::provider::helpers::find_session_id;
|
|
5
|
+
use crate::provider::types::{CaptureVia, CapturedSession, Confidence, RolloutPath, SessionId};
|
|
4
6
|
use crate::provider::Provider;
|
|
5
7
|
|
|
6
8
|
use super::{CaptureSessionContext, CapturedSessionCandidate};
|
|
@@ -14,7 +16,7 @@ pub(crate) fn projects_dir_for_cwd(home: &Path, spawn_cwd: &Path) -> Option<Path
|
|
|
14
16
|
Some(home.join(".claude").join("projects").join(encoded))
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
fn encode_projects_dir(path: &str) -> String {
|
|
19
|
+
pub(super) fn encode_projects_dir(path: &str) -> String {
|
|
18
20
|
let mut out = String::with_capacity(path.len());
|
|
19
21
|
for c in path.chars() {
|
|
20
22
|
if c.is_ascii_alphanumeric() {
|
|
@@ -26,6 +28,65 @@ fn encode_projects_dir(path: &str) -> String {
|
|
|
26
28
|
out
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
pub(super) fn scan_expected_session(
|
|
32
|
+
context: &CaptureSessionContext,
|
|
33
|
+
) -> Vec<CapturedSessionCandidate> {
|
|
34
|
+
let Some(expected) = context.expected_session_id.as_ref() else {
|
|
35
|
+
return Vec::new();
|
|
36
|
+
};
|
|
37
|
+
let Some(projects_root) = context.provider_projects_root.clone().or_else(|| {
|
|
38
|
+
std::env::var_os("HOME")
|
|
39
|
+
.map(PathBuf::from)
|
|
40
|
+
.map(|home| home.join(".claude").join("projects"))
|
|
41
|
+
}) else {
|
|
42
|
+
return Vec::new();
|
|
43
|
+
};
|
|
44
|
+
let canonical =
|
|
45
|
+
std::fs::canonicalize(&context.spawn_cwd).unwrap_or_else(|_| context.spawn_cwd.clone());
|
|
46
|
+
let encoded = encode_projects_dir(&canonical.to_string_lossy());
|
|
47
|
+
if encoded.is_empty() {
|
|
48
|
+
return Vec::new();
|
|
49
|
+
}
|
|
50
|
+
let path = projects_root
|
|
51
|
+
.join(encoded)
|
|
52
|
+
.join(format!("{}.jsonl", expected.as_str()));
|
|
53
|
+
let Ok(text) = super::common::read_head_text(&path, super::common::CAPTURE_HEAD_BYTES) else {
|
|
54
|
+
return Vec::new();
|
|
55
|
+
};
|
|
56
|
+
let records = super::common::parse_session_records(&text);
|
|
57
|
+
let session_matches = records
|
|
58
|
+
.iter()
|
|
59
|
+
.find_map(find_session_id)
|
|
60
|
+
.is_some_and(|session_id| session_id == expected.as_str());
|
|
61
|
+
let has_lifecycle_record = records.iter().any(|record| {
|
|
62
|
+
matches!(
|
|
63
|
+
record.get("type").and_then(serde_json::Value::as_str),
|
|
64
|
+
Some("user" | "assistant")
|
|
65
|
+
)
|
|
66
|
+
});
|
|
67
|
+
if !session_matches
|
|
68
|
+
|| !has_lifecycle_record
|
|
69
|
+
|| records_have_leader_marker(&records)
|
|
70
|
+
|| !records.iter().any(has_cwd_field)
|
|
71
|
+
{
|
|
72
|
+
return Vec::new();
|
|
73
|
+
}
|
|
74
|
+
let embedded_agent_id = super::common::embedded_team_agent_worker_id_from_text(&text);
|
|
75
|
+
let positive_agent_id_match = embedded_agent_id.as_deref() == Some(context.agent_id.as_str());
|
|
76
|
+
vec![CapturedSessionCandidate {
|
|
77
|
+
captured: CapturedSession {
|
|
78
|
+
session_id: Some(expected.clone()),
|
|
79
|
+
rollout_path: Some(RolloutPath::new(path)),
|
|
80
|
+
captured_via: CaptureVia::FsWatch,
|
|
81
|
+
attribution_confidence: Confidence::High,
|
|
82
|
+
spawn_cwd: context.spawn_cwd.clone(),
|
|
83
|
+
},
|
|
84
|
+
embedded_agent_id,
|
|
85
|
+
positive_agent_id_match,
|
|
86
|
+
agent_path_match: false,
|
|
87
|
+
}]
|
|
88
|
+
}
|
|
89
|
+
|
|
29
90
|
pub(crate) fn rollout_path_has_leader_marker(provider: Provider, rollout_path: &Path) -> bool {
|
|
30
91
|
if !matches!(provider, Provider::Claude | Provider::ClaudeCode) {
|
|
31
92
|
return false;
|
|
@@ -112,6 +173,7 @@ mod tests {
|
|
|
112
173
|
std::fs::create_dir_all(dir).unwrap();
|
|
113
174
|
let path = dir.join(format!("{uuid}.jsonl"));
|
|
114
175
|
let line = serde_json::json!({
|
|
176
|
+
"type": "user",
|
|
115
177
|
"sessionId": uuid,
|
|
116
178
|
"cwd": cwd.to_string_lossy(),
|
|
117
179
|
});
|
|
@@ -119,6 +181,11 @@ mod tests {
|
|
|
119
181
|
path
|
|
120
182
|
}
|
|
121
183
|
|
|
184
|
+
fn expected_transcript_dir(projects_root: &Path, cwd: &Path) -> PathBuf {
|
|
185
|
+
let canonical = std::fs::canonicalize(cwd).unwrap_or_else(|_| cwd.to_path_buf());
|
|
186
|
+
projects_root.join(encode_projects_dir(&canonical.to_string_lossy()))
|
|
187
|
+
}
|
|
188
|
+
|
|
122
189
|
#[test]
|
|
123
190
|
fn claude_leader_marker_in_custom_title_is_detected() {
|
|
124
191
|
let records = vec![serde_json::json!({
|
|
@@ -205,7 +272,11 @@ mod tests {
|
|
|
205
272
|
std::fs::create_dir_all(&cwd).unwrap();
|
|
206
273
|
let proj = base.join("projects");
|
|
207
274
|
write_transcript(&proj, "11111111-1111-4111-8111-111111111111", &cwd);
|
|
208
|
-
write_transcript(
|
|
275
|
+
let expected = write_transcript(
|
|
276
|
+
&expected_transcript_dir(&proj, &cwd),
|
|
277
|
+
"22222222-2222-4222-8222-222222222222",
|
|
278
|
+
&cwd,
|
|
279
|
+
);
|
|
209
280
|
let ctx = CaptureSessionContext {
|
|
210
281
|
agent_id: "w1".to_string(),
|
|
211
282
|
spawn_cwd: cwd.clone(),
|
|
@@ -221,6 +292,10 @@ mod tests {
|
|
|
221
292
|
out[0].captured.session_id.as_ref().unwrap().as_str(),
|
|
222
293
|
"22222222-2222-4222-8222-222222222222"
|
|
223
294
|
);
|
|
295
|
+
assert_eq!(
|
|
296
|
+
out[0].captured.rollout_path.as_ref().unwrap().as_path(),
|
|
297
|
+
expected
|
|
298
|
+
);
|
|
224
299
|
let _ = std::fs::remove_dir_all(&base);
|
|
225
300
|
}
|
|
226
301
|
|
|
@@ -284,6 +359,21 @@ mod tests {
|
|
|
284
359
|
let proj = base.join("projects");
|
|
285
360
|
let leader = write_transcript(&proj, "11111111-1111-4111-8111-111111111111", &cwd);
|
|
286
361
|
let stale = write_transcript(&proj, "22222222-2222-4222-8222-222222222222", &cwd);
|
|
362
|
+
let addressed =
|
|
363
|
+
expected_transcript_dir(&proj, &cwd).join("99999999-9999-4999-8999-999999999999.jsonl");
|
|
364
|
+
std::fs::create_dir_all(addressed.parent().unwrap()).unwrap();
|
|
365
|
+
std::fs::write(
|
|
366
|
+
&addressed,
|
|
367
|
+
format!(
|
|
368
|
+
"{}\n",
|
|
369
|
+
serde_json::json!({
|
|
370
|
+
"type": "user",
|
|
371
|
+
"sessionId": "22222222-2222-4222-8222-222222222222",
|
|
372
|
+
"cwd": cwd.to_string_lossy(),
|
|
373
|
+
})
|
|
374
|
+
),
|
|
375
|
+
)
|
|
376
|
+
.unwrap();
|
|
287
377
|
let _ = (leader, stale);
|
|
288
378
|
let ctx = CaptureSessionContext {
|
|
289
379
|
agent_id: "claude-worker".to_string(),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
use std::path::{Path, PathBuf};
|
|
2
3
|
|
|
3
4
|
use crate::provider::helpers::{find_session_id, parse_jsonl_records};
|
|
@@ -26,25 +27,36 @@ pub(super) fn candidate_session_files(
|
|
|
26
27
|
) -> Result<Vec<SessionCandidate>, ProviderError> {
|
|
27
28
|
let mut out = Vec::new();
|
|
28
29
|
if let Some(root) = context.provider_projects_root.as_ref() {
|
|
29
|
-
|
|
30
|
+
let allowed_team_root =
|
|
31
|
+
matches!(provider, Provider::Claude | Provider::ClaudeCode).then_some(root.as_path());
|
|
32
|
+
collect_optional_candidate_files(root, &context.agent_id, allowed_team_root, &mut out)?;
|
|
30
33
|
}
|
|
31
|
-
collect_candidate_files(
|
|
34
|
+
collect_candidate_files(
|
|
35
|
+
&context.spawn_cwd,
|
|
36
|
+
&context.agent_id,
|
|
37
|
+
0,
|
|
38
|
+
false,
|
|
39
|
+
None,
|
|
40
|
+
&mut out,
|
|
41
|
+
)?;
|
|
32
42
|
if let Some(home) = std::env::var_os("HOME").map(PathBuf::from) {
|
|
33
43
|
match provider {
|
|
34
44
|
Provider::Codex => {
|
|
35
45
|
collect_optional_candidate_files(
|
|
36
46
|
&home.join(".codex").join("sessions"),
|
|
37
47
|
&context.agent_id,
|
|
48
|
+
None,
|
|
38
49
|
&mut out,
|
|
39
50
|
)?;
|
|
40
51
|
}
|
|
41
52
|
Provider::Claude | Provider::ClaudeCode => {
|
|
42
53
|
if let Some(dir) = super::claude::projects_dir_for_cwd(&home, &context.spawn_cwd) {
|
|
43
|
-
collect_optional_candidate_files(&dir, &context.agent_id, &mut out)?;
|
|
54
|
+
collect_optional_candidate_files(&dir, &context.agent_id, None, &mut out)?;
|
|
44
55
|
}
|
|
45
56
|
collect_optional_candidate_files(
|
|
46
57
|
&home.join(".claude").join("projects"),
|
|
47
58
|
&context.agent_id,
|
|
59
|
+
None,
|
|
48
60
|
&mut out,
|
|
49
61
|
)?;
|
|
50
62
|
}
|
|
@@ -275,10 +287,11 @@ fn complete_head_text(bytes: &[u8]) -> String {
|
|
|
275
287
|
fn collect_optional_candidate_files(
|
|
276
288
|
dir: &Path,
|
|
277
289
|
agent_id: &str,
|
|
290
|
+
allowed_team_root: Option<&Path>,
|
|
278
291
|
out: &mut Vec<SessionCandidate>,
|
|
279
292
|
) -> Result<(), ProviderError> {
|
|
280
293
|
if dir.exists() {
|
|
281
|
-
let _ = collect_candidate_files(dir, agent_id, 0, true, out);
|
|
294
|
+
let _ = collect_candidate_files(dir, agent_id, 0, true, allowed_team_root, out);
|
|
282
295
|
}
|
|
283
296
|
Ok(())
|
|
284
297
|
}
|
|
@@ -288,6 +301,7 @@ fn collect_candidate_files(
|
|
|
288
301
|
agent_id: &str,
|
|
289
302
|
depth: usize,
|
|
290
303
|
requires_cwd_match: bool,
|
|
304
|
+
allowed_team_root: Option<&Path>,
|
|
291
305
|
out: &mut Vec<SessionCandidate>,
|
|
292
306
|
) -> Result<(), ProviderError> {
|
|
293
307
|
if depth > 4 {
|
|
@@ -309,9 +323,10 @@ fn collect_candidate_files(
|
|
|
309
323
|
agent_id,
|
|
310
324
|
depth.saturating_add(1),
|
|
311
325
|
requires_cwd_match,
|
|
326
|
+
allowed_team_root,
|
|
312
327
|
out,
|
|
313
328
|
)?;
|
|
314
|
-
} else if looks_like_session_file(&path, agent_id) {
|
|
329
|
+
} else if looks_like_session_file(&path, agent_id, allowed_team_root) {
|
|
315
330
|
out.push(SessionCandidate {
|
|
316
331
|
path,
|
|
317
332
|
requires_cwd_match,
|
|
@@ -321,8 +336,10 @@ fn collect_candidate_files(
|
|
|
321
336
|
Ok(())
|
|
322
337
|
}
|
|
323
338
|
|
|
324
|
-
fn looks_like_session_file(path: &Path, agent_id: &str) -> bool {
|
|
325
|
-
if path_is_under_team_runtime(path)
|
|
339
|
+
fn looks_like_session_file(path: &Path, agent_id: &str, allowed_team_root: Option<&Path>) -> bool {
|
|
340
|
+
if path_is_under_team_runtime(path)
|
|
341
|
+
&& !allowed_team_root.is_some_and(|root| path.starts_with(root))
|
|
342
|
+
{
|
|
326
343
|
return false;
|
|
327
344
|
}
|
|
328
345
|
let Some(name) = path.file_name().and_then(|s| s.to_str()) else {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! Provider session-scan boundary.
|
|
2
3
|
//!
|
|
3
4
|
//! `ProviderAdapter` keeps the public polling facade in `adapter.rs`; this
|
|
@@ -14,27 +15,6 @@ pub(crate) mod codex;
|
|
|
14
15
|
pub(crate) mod common;
|
|
15
16
|
pub(crate) mod copilot;
|
|
16
17
|
|
|
17
|
-
/// Marker label for which scan strategy a provider uses.
|
|
18
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
19
|
-
pub enum SessionScanStrategy {
|
|
20
|
-
CodexJsonl,
|
|
21
|
-
ClaudeTranscript,
|
|
22
|
-
CopilotHome,
|
|
23
|
-
NotRequired,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
impl SessionScanStrategy {
|
|
27
|
-
pub fn for_provider(provider: Provider) -> Self {
|
|
28
|
-
match provider {
|
|
29
|
-
Provider::Codex => Self::CodexJsonl,
|
|
30
|
-
Provider::Claude | Provider::ClaudeCode => Self::ClaudeTranscript,
|
|
31
|
-
Provider::Copilot => Self::CopilotHome,
|
|
32
|
-
Provider::GeminiCli => Self::NotRequired,
|
|
33
|
-
Provider::Fake => Self::NotRequired,
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
18
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
39
19
|
pub struct CaptureSessionContext {
|
|
40
20
|
pub agent_id: String,
|
|
@@ -61,6 +41,11 @@ pub(crate) fn scan_session_candidates_once(
|
|
|
61
41
|
if matches!(provider, Provider::Copilot) {
|
|
62
42
|
return Ok(copilot::scan_session_store(context));
|
|
63
43
|
}
|
|
44
|
+
if matches!(provider, Provider::Claude | Provider::ClaudeCode)
|
|
45
|
+
&& context.expected_session_id.is_some()
|
|
46
|
+
{
|
|
47
|
+
return Ok(claude::scan_expected_session(context));
|
|
48
|
+
}
|
|
64
49
|
|
|
65
50
|
let candidates = common::candidate_session_files(provider, context)?;
|
|
66
51
|
let mut out = common::parse_candidate_files(provider, context, candidates);
|
|
@@ -82,24 +67,3 @@ pub(crate) fn scan_session_candidates_once(
|
|
|
82
67
|
}
|
|
83
68
|
|
|
84
69
|
pub(crate) use claude::rollout_path_has_leader_marker as rollout_path_has_claude_leader_marker;
|
|
85
|
-
|
|
86
|
-
#[cfg(test)]
|
|
87
|
-
mod tests {
|
|
88
|
-
use super::*;
|
|
89
|
-
|
|
90
|
-
#[test]
|
|
91
|
-
fn strategy_per_provider() {
|
|
92
|
-
assert_eq!(
|
|
93
|
-
SessionScanStrategy::for_provider(Provider::Codex),
|
|
94
|
-
SessionScanStrategy::CodexJsonl
|
|
95
|
-
);
|
|
96
|
-
assert_eq!(
|
|
97
|
-
SessionScanStrategy::for_provider(Provider::Claude),
|
|
98
|
-
SessionScanStrategy::ClaudeTranscript
|
|
99
|
-
);
|
|
100
|
-
assert_eq!(
|
|
101
|
-
SessionScanStrategy::for_provider(Provider::Fake),
|
|
102
|
-
SessionScanStrategy::NotRequired
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! identity:leader receiver identity 派生 / 迁移 / first-time binding(真相源 `state.py`)。
|
|
2
3
|
//!
|
|
3
4
|
//! Python 直接读 `os.environ`/`os.getcwd()`/`Path.resolve()`;此处把**环境变量**经
|
|
@@ -44,8 +45,9 @@ fn path_str(p: &Path) -> String {
|
|
|
44
45
|
p.to_string_lossy().into_owned()
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
///
|
|
47
49
|
/// `_identity_os_user`(`state.py:275`):`USER` or `USERNAME` or `""`。
|
|
48
|
-
|
|
50
|
+
fn identity_os_user(env: &dyn IdentityEnv) -> String {
|
|
49
51
|
let user = env_str(env, "USER");
|
|
50
52
|
if !user.is_empty() {
|
|
51
53
|
return user;
|
|
@@ -57,9 +59,10 @@ pub fn identity_os_user(env: &dyn IdentityEnv) -> String {
|
|
|
57
59
|
String::new()
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
///
|
|
60
63
|
/// `_identity_machine_fingerprint`(`state.py:279`):team_owner/leader_receiver 的非空
|
|
61
64
|
/// machine_fingerprint 优先,否则 env `TEAM_AGENT_MACHINE_FINGERPRINT` or `""`。
|
|
62
|
-
|
|
65
|
+
fn identity_machine_fingerprint(state: &Value, env: &dyn IdentityEnv) -> String {
|
|
63
66
|
for key in ["team_owner", "leader_receiver"] {
|
|
64
67
|
if let Some(fp) = state
|
|
65
68
|
.get(key)
|
|
@@ -74,9 +77,10 @@ pub fn identity_machine_fingerprint(state: &Value, env: &dyn IdentityEnv) -> Str
|
|
|
74
77
|
env_str(env, "TEAM_AGENT_MACHINE_FINGERPRINT")
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
///
|
|
77
81
|
/// `_identity_workspace_abspath`(`state.py:264`):按 workspace→team_dir→spec_path→fallback
|
|
78
82
|
/// 顺序派生 workspace 绝对路径(各分支先 `resolve()` 再做 parent 导航)。
|
|
79
|
-
|
|
83
|
+
fn identity_workspace_abspath(
|
|
80
84
|
state: &Value,
|
|
81
85
|
env: &dyn IdentityEnv,
|
|
82
86
|
workspace: Option<&Path>,
|
|
@@ -157,9 +161,10 @@ fn leader_session_uuid_for_state(
|
|
|
157
161
|
Ok(uuid.as_str().to_string())
|
|
158
162
|
}
|
|
159
163
|
|
|
164
|
+
///
|
|
160
165
|
/// `_migrate_team_identity`(`state.py:295`):为缺 `leader_session_uuid` 的 team_owner /
|
|
161
166
|
/// leader_receiver 填入派生 uuid。返回是否有改动。
|
|
162
|
-
|
|
167
|
+
fn migrate_team_identity(
|
|
163
168
|
state: &mut Value,
|
|
164
169
|
env: &dyn IdentityEnv,
|
|
165
170
|
workspace: Option<&Path>,
|
|
@@ -189,6 +194,7 @@ pub fn migrate_team_identity(
|
|
|
189
194
|
Ok(changed)
|
|
190
195
|
}
|
|
191
196
|
|
|
197
|
+
///
|
|
192
198
|
/// `_migrate_state_identity`(`state.py:306`):顶层(有 session_name 时)+ 每个 team 子状态迁移。
|
|
193
199
|
pub fn migrate_state_identity(
|
|
194
200
|
state: &mut Value,
|
|
@@ -220,6 +226,7 @@ pub fn migrate_state_identity(
|
|
|
220
226
|
Ok(changed)
|
|
221
227
|
}
|
|
222
228
|
|
|
229
|
+
///
|
|
223
230
|
/// `_caller_identity_from_env`(`state.py:316`):env override/env/derived → 5 字段 caller 身份。
|
|
224
231
|
pub fn caller_identity_from_env(
|
|
225
232
|
state: Option<&Value>,
|
|
@@ -282,9 +289,10 @@ pub fn caller_identity_from_env(
|
|
|
282
289
|
})
|
|
283
290
|
}
|
|
284
291
|
|
|
292
|
+
///
|
|
285
293
|
/// `populate_team_owner_from_env`(`state.py:414`):已有 team_owner → 仅补迁移 uuid 后返回;
|
|
286
294
|
/// 否则从 env caller 身份 seed team_owner(无 pane_id → `None`)。`now_iso` = claimed_at。
|
|
287
|
-
|
|
295
|
+
fn populate_team_owner_from_env(
|
|
288
296
|
state: &mut Value,
|
|
289
297
|
source: &str,
|
|
290
298
|
env: &dyn IdentityEnv,
|
|
@@ -320,6 +328,7 @@ pub fn populate_team_owner_from_env(
|
|
|
320
328
|
Ok(Some(owner))
|
|
321
329
|
}
|
|
322
330
|
|
|
331
|
+
///
|
|
323
332
|
/// `apply_first_time_leader_binding`(`state.py:434`):命令 + workspace realpath 双门后,
|
|
324
333
|
/// 写入 team_owner / leader_receiver(owner_epoch=0)。拒绝 dict 字节对齐 Python(含 `pane` +
|
|
325
334
|
/// `repr()` 错误串)。`command_looks_usable`(`_leader_command_looks_usable`,step 11)经闭包注入。
|
|
@@ -411,6 +420,7 @@ pub fn apply_first_time_leader_binding(
|
|
|
411
420
|
json!({ "ok": true, "pane": pane_info, "warning": null, "first_time": true })
|
|
412
421
|
}
|
|
413
422
|
|
|
423
|
+
///
|
|
414
424
|
/// `leader_env_exports`(`state.py:469`):receiver + identity → 6 个 leader env 导出(插入序)。
|
|
415
425
|
pub fn leader_env_exports(receiver: &Value, identity: &Value) -> Vec<(String, String)> {
|
|
416
426
|
vec![
|
|
@@ -441,6 +451,7 @@ pub fn leader_env_exports(receiver: &Value, identity: &Value) -> Vec<(String, St
|
|
|
441
451
|
]
|
|
442
452
|
}
|
|
443
453
|
|
|
454
|
+
///
|
|
444
455
|
/// `validate_leader_uuid_from_targets`(`state.py:480`):fake provider 直过;否则核对 targets
|
|
445
456
|
/// 扫描成功 + receiver.pane_id 在 targets 中存在。
|
|
446
457
|
pub fn validate_leader_uuid_from_targets(receiver: &Value, targets: &Value) -> Value {
|