@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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: Cursor agent CLI argv;role 走 .cursor/rules,MCP 走 mcp.json overlay
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: cursor_agent_base_command
|
|
6
|
+
//! what: 只组已实测文档化 flag;mcp_config 不入 argv,由 launch overlay 写盘
|
|
7
|
+
//! boundary:
|
|
8
|
+
//! - 不调未文档化 flag(--system-prompt / --allowed-tools)
|
|
9
|
+
//! - 不写 grok 那种 cwd 独占闸
|
|
10
|
+
//! - 不打印代理值
|
|
11
|
+
//! maturity: wired
|
|
12
|
+
//! ---
|
|
13
|
+
//!
|
|
14
|
+
//! Cursor `agent` CLI(与 `cursor-agent` 同二进制)。主路径与
|
|
15
|
+
//! `.team/scripts/cursor_seat.sh` 实测一致:
|
|
16
|
+
//! `--trust --sandbox disabled --workspace <物理路径> [--force] [--model]`
|
|
17
|
+
//! Role 不入 argv,写 `<workspace>/.cursor/rules/*.mdc` + `alwaysApply: true`。
|
|
18
|
+
//! MCP 无 `--mcp-config`;身份必须写进 `.cursor/mcp.json` 的 env 表
|
|
19
|
+
//! (cursor 不把父进程 TEAM_AGENT_* 传给 MCP 子进程)。
|
|
20
|
+
//!
|
|
21
|
+
//! 隐藏 flag `--system-prompt` / `--allowed-tools` 实测存在但 help 未列,
|
|
22
|
+
//! 随时可能消失。可以在注释里记录,代码里不许调。
|
|
23
|
+
//! `--allowed-tools` 正反行为未验证,不写「支持工具白名单」。
|
|
24
|
+
|
|
25
|
+
use crate::model::enums::AuthMode;
|
|
26
|
+
use crate::provider::adapter::BasicProviderAdapter;
|
|
27
|
+
use crate::provider::{McpConfig, ProviderError};
|
|
28
|
+
|
|
29
|
+
pub(crate) fn cursor_agent_launch_command(
|
|
30
|
+
adapter: &BasicProviderAdapter,
|
|
31
|
+
auth_mode: AuthMode,
|
|
32
|
+
mcp_config: Option<&McpConfig>,
|
|
33
|
+
system_prompt: Option<&str>,
|
|
34
|
+
model: Option<&str>,
|
|
35
|
+
tools: &[&str],
|
|
36
|
+
) -> Result<Vec<String>, ProviderError> {
|
|
37
|
+
cursor_agent_base_command(
|
|
38
|
+
adapter,
|
|
39
|
+
auth_mode,
|
|
40
|
+
mcp_config,
|
|
41
|
+
system_prompt,
|
|
42
|
+
model,
|
|
43
|
+
tools,
|
|
44
|
+
false,
|
|
45
|
+
None,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub(crate) fn cursor_agent_base_command(
|
|
50
|
+
adapter: &BasicProviderAdapter,
|
|
51
|
+
auth_mode: AuthMode,
|
|
52
|
+
mcp_config: Option<&McpConfig>,
|
|
53
|
+
system_prompt: Option<&str>,
|
|
54
|
+
model: Option<&str>,
|
|
55
|
+
tools: &[&str],
|
|
56
|
+
managed_mcp_config: bool,
|
|
57
|
+
effort: Option<crate::model::enums::ProviderEffort>,
|
|
58
|
+
) -> Result<Vec<String>, ProviderError> {
|
|
59
|
+
let mut argv = vec!["agent".to_string()];
|
|
60
|
+
// --trust 跳过 Workspace Trust 闸(已实测)。无此 flag 会停在 Do you trust。
|
|
61
|
+
argv.push("--trust".to_string());
|
|
62
|
+
if cursor_agent_dangerous_auto_approve(tools) {
|
|
63
|
+
argv.push("--force".to_string());
|
|
64
|
+
}
|
|
65
|
+
// 与 cursor_seat.sh 主路径一致。沙箱实际隔离面未再拆,但 flag 本身已实测。
|
|
66
|
+
argv.push("--sandbox".to_string());
|
|
67
|
+
argv.push("disabled".to_string());
|
|
68
|
+
if let Some(model) = model {
|
|
69
|
+
argv.push("--model".to_string());
|
|
70
|
+
argv.push(model.to_string());
|
|
71
|
+
}
|
|
72
|
+
// Cursor `--effort` flag 不存在;框架在调用方丢掉 effort。绝不发明 flag。
|
|
73
|
+
let _ = effort;
|
|
74
|
+
// system_prompt 不入 argv(help 无 --rules / --append-system-prompt)。
|
|
75
|
+
// launch 写 `<workspace>/.cursor/rules/team-agent-role-<agent_id>.mdc`。
|
|
76
|
+
// mcp_config 也不入 argv(无 --mcp-config)。launch 写 `.cursor/mcp.json`
|
|
77
|
+
// 并 `agent mcp enable team_orchestrator`。这里收下以免静默丢弃。
|
|
78
|
+
let _ = (adapter, auth_mode, mcp_config, managed_mcp_config, system_prompt);
|
|
79
|
+
argv.push("--workspace".to_string());
|
|
80
|
+
argv.push("{workspace}".to_string());
|
|
81
|
+
Ok(argv)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
pub(crate) fn cursor_agent_dangerous_auto_approve(tools: &[&str]) -> bool {
|
|
85
|
+
tools.contains(&"dangerous_auto_approve")
|
|
86
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
//!
|
|
2
|
+
//! purpose: Grok CLI argv + permission deny 映射;MCP 不走 CLI flag
|
|
3
|
+
//! contract: launch 写 `<cwd>/.grok/config.toml`;未登录 / 目录未信任必须拒绝启动。
|
|
4
|
+
//! 角色缺 model 必须拒绝启动(禁止内建 / team / CLI 任何隐式默认)。
|
|
5
|
+
//! 未知工具映射记成 Unsupported,不发明 deny 名
|
|
6
|
+
//! mcp_injection: dir_scoped —— 每席必须独立工作目录;同 cwd 多席会静默串台,故拒绝。
|
|
7
|
+
//! 对照 claude/codex 是 argv 作用域(`--mcp-config`),同目录多席没有这个问题。
|
|
8
|
+
//! 本版 exclusive 检查不读 per-agent cwd,一个 workspace 只支持一个 grok 席。
|
|
9
|
+
//! 这是 Provider::Grok 的能力边界,不是框架对所有 provider 的限制。
|
|
10
|
+
//! boundary: 只服务 Provider::Grok。不改 claude/codex/copilot 路径
|
|
11
|
+
//!
|
|
12
|
+
//! Grok CLI provider-local command builders + permission helpers.
|
|
13
|
+
//!
|
|
14
|
+
//! Mirrors `adapters/claude.rs` (0.5.67 provider-adapter step). Pure
|
|
15
|
+
//! flag-name adaptation over the claude skeleton — no new abstraction
|
|
16
|
+
//! layers, no public-API changes outside the provider dispatch.
|
|
17
|
+
//!
|
|
18
|
+
//! Grok CLI flag map (亲核 attest 0.5.67 + grok-full-help.txt):
|
|
19
|
+
//! bypass → `--always-approve` ("Auto-approve all tool executions")
|
|
20
|
+
//! system → `--rules <RULES>` ("Extra rules to append to the system prompt")
|
|
21
|
+
//! model → `-m, --model <MODEL>`
|
|
22
|
+
//! session → `-s, --session-id <SESSION_ID>` (fresh spawn)
|
|
23
|
+
//! resume → `-r, --resume [<ID_OR_TITLE>]` / `-c, --continue`
|
|
24
|
+
//! fork → `--fork-session` (with --resume)
|
|
25
|
+
//! effort → `--effort <level>` (alias of `--reasoning-effort`)
|
|
26
|
+
//! grok accepts low|medium|high|xhigh; CLI rejects `max`
|
|
27
|
+
//! deny → `--disallowed-tools` (compat alias `--disallowedTools`)
|
|
28
|
+
//! cwd → `--cwd <CWD>` / `-w, --worktree [<WORKTREE>]`
|
|
29
|
+
//!
|
|
30
|
+
//! No native `--mcp-config` flag on the Grok CLI (`grok mcp` is a subcommand)
|
|
31
|
+
//! → `native_mcp_config: false`; MCP reaches the worker via launch-path
|
|
32
|
+
//! `<cwd>/.grok/config.toml` (`apply_grok_mcp_overlay`).
|
|
33
|
+
|
|
34
|
+
use crate::model::enums::AuthMode;
|
|
35
|
+
use crate::provider::adapter::{next_session_token, BasicProviderAdapter};
|
|
36
|
+
use crate::provider::{McpConfig, ProviderError};
|
|
37
|
+
|
|
38
|
+
pub(crate) fn grok_launch_command(
|
|
39
|
+
adapter: &BasicProviderAdapter,
|
|
40
|
+
auth_mode: AuthMode,
|
|
41
|
+
mcp_config: Option<&McpConfig>,
|
|
42
|
+
system_prompt: Option<&str>,
|
|
43
|
+
model: Option<&str>,
|
|
44
|
+
tools: &[&str],
|
|
45
|
+
) -> Result<Vec<String>, ProviderError> {
|
|
46
|
+
let mut argv = grok_base_command(
|
|
47
|
+
adapter,
|
|
48
|
+
auth_mode,
|
|
49
|
+
mcp_config,
|
|
50
|
+
system_prompt,
|
|
51
|
+
model,
|
|
52
|
+
tools,
|
|
53
|
+
false,
|
|
54
|
+
None,
|
|
55
|
+
)?;
|
|
56
|
+
argv.push("--session-id".to_string());
|
|
57
|
+
argv.push(next_session_token());
|
|
58
|
+
Ok(argv)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pub(crate) fn grok_base_command(
|
|
62
|
+
adapter: &BasicProviderAdapter,
|
|
63
|
+
auth_mode: AuthMode,
|
|
64
|
+
mcp_config: Option<&McpConfig>,
|
|
65
|
+
system_prompt: Option<&str>,
|
|
66
|
+
model: Option<&str>,
|
|
67
|
+
tools: &[&str],
|
|
68
|
+
managed_mcp_config: bool,
|
|
69
|
+
effort: Option<crate::model::enums::ProviderEffort>,
|
|
70
|
+
) -> Result<Vec<String>, ProviderError> {
|
|
71
|
+
let mut argv = vec!["grok".to_string()];
|
|
72
|
+
if grok_dangerous_auto_approve(tools) {
|
|
73
|
+
argv.push("--always-approve".to_string());
|
|
74
|
+
}
|
|
75
|
+
let model = match model.map(str::trim).filter(|value| !value.is_empty()) {
|
|
76
|
+
Some(model) => model,
|
|
77
|
+
None => {
|
|
78
|
+
return Err(ProviderError::Command(
|
|
79
|
+
"grok launch requires an explicit model. \
|
|
80
|
+
Without an explicit model the framework fills a built-in default (grok-4 for grok), \
|
|
81
|
+
so the seat silently gets a model and context window that are not in the role file, \
|
|
82
|
+
while argv still looks normal. The role file must name the model itself; \
|
|
83
|
+
built-in defaults, team-level defaults, and the Grok CLI global default are all implicit sources. \
|
|
84
|
+
Set the role front-matter field, for example:\n\
|
|
85
|
+
model: grok-4.6"
|
|
86
|
+
.to_string(),
|
|
87
|
+
));
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
argv.push("--model".to_string());
|
|
91
|
+
argv.push(model.to_string());
|
|
92
|
+
if let Some(effort) = effort {
|
|
93
|
+
// Grok accepts `--effort` as alias for `--reasoning-effort`.
|
|
94
|
+
argv.push("--effort".to_string());
|
|
95
|
+
argv.push(effort.as_str().to_string());
|
|
96
|
+
}
|
|
97
|
+
if let Some(prompt) = system_prompt {
|
|
98
|
+
argv.push("--rules".to_string());
|
|
99
|
+
argv.push(prompt.to_string());
|
|
100
|
+
}
|
|
101
|
+
// Grok CLI has no `--mcp-config` flag — the claude inline-MCP block is
|
|
102
|
+
// intentionally absent. Launch writes `<cwd>/.grok/config.toml`.
|
|
103
|
+
let _ = (adapter, auth_mode, mcp_config, managed_mcp_config);
|
|
104
|
+
for tool in grok_disallowed_tools(tools) {
|
|
105
|
+
argv.push("--disallowedTools".to_string());
|
|
106
|
+
argv.push(tool.to_string());
|
|
107
|
+
}
|
|
108
|
+
Ok(argv)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
pub(crate) fn grok_dangerous_auto_approve(tools: &[&str]) -> bool {
|
|
112
|
+
tools.contains(&"dangerous_auto_approve")
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
pub(crate) fn grok_disallowed_tools(tools: &[&str]) -> Vec<&'static str> {
|
|
116
|
+
let mut disallowed = Vec::new();
|
|
117
|
+
for tool in [
|
|
118
|
+
"execute_bash",
|
|
119
|
+
"fs_read",
|
|
120
|
+
"fs_write",
|
|
121
|
+
"fs_list",
|
|
122
|
+
"network",
|
|
123
|
+
"git_diff",
|
|
124
|
+
"mcp_team",
|
|
125
|
+
"provider_builtin",
|
|
126
|
+
] {
|
|
127
|
+
if tools.contains(&tool) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
match grok_tool_mapping(tool) {
|
|
131
|
+
GrokToolMapping::Deny(names) => disallowed.extend(names),
|
|
132
|
+
GrokToolMapping::Unsupported | GrokToolMapping::Bypass => {}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
disallowed
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Canonical tool → grok CLI deny names. Unknown tools stay Unsupported
|
|
139
|
+
/// (no invented `--disallowedTools` token).
|
|
140
|
+
pub(crate) fn grok_tool_mapping(tool: &str) -> GrokToolMapping {
|
|
141
|
+
match tool {
|
|
142
|
+
"execute_bash" => GrokToolMapping::Deny(&["Bash"]),
|
|
143
|
+
"fs_read" => GrokToolMapping::Deny(&["Read"]),
|
|
144
|
+
"fs_write" => GrokToolMapping::Deny(&["Edit", "Write", "MultiEdit", "NotebookEdit"]),
|
|
145
|
+
"fs_list" => GrokToolMapping::Deny(&["Glob", "Grep"]),
|
|
146
|
+
"dangerous_auto_approve" => GrokToolMapping::Bypass,
|
|
147
|
+
"network" | "git_diff" | "mcp_team" | "provider_builtin" => GrokToolMapping::Unsupported,
|
|
148
|
+
_ => GrokToolMapping::Unsupported,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
153
|
+
pub(crate) enum GrokToolMapping {
|
|
154
|
+
Deny(&'static [&'static str]),
|
|
155
|
+
Bypass,
|
|
156
|
+
Unsupported,
|
|
157
|
+
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
//! - claude/claude_code → `--dangerously-skip-permissions`
|
|
9
9
|
//! - codex → `--dangerously-bypass-approvals-and-sandbox`
|
|
10
10
|
//! - copilot → `--allow-all`(0.3.27 P1 E54 symptom 2 起)
|
|
11
|
+
//! - grok → `--always-approve`
|
|
12
|
+
//! - cursor_agent → `--force`
|
|
11
13
|
//!
|
|
12
14
|
//! 未定义(返回 None)的 provider:调用侧必须 fail-loud(见
|
|
13
15
|
//! `resolved_tool_strings_for_command`),不得静默 fallback。
|
|
@@ -20,14 +22,23 @@ pub(crate) fn provider_bypass_flag(provider: Provider) -> Option<&'static str> {
|
|
|
20
22
|
Provider::Claude | Provider::ClaudeCode => Some("--dangerously-skip-permissions"),
|
|
21
23
|
Provider::Codex => Some("--dangerously-bypass-approvals-and-sandbox"),
|
|
22
24
|
Provider::Copilot => Some("--allow-all"),
|
|
25
|
+
Provider::Grok => Some("--always-approve"),
|
|
26
|
+
Provider::CursorAgent => Some("--force"),
|
|
23
27
|
// TODO: 查 gemini_cli 的等价 bypass 参数;未定义前 fail-loud。
|
|
24
28
|
Provider::GeminiCli => None,
|
|
25
29
|
Provider::Fake => None,
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
/// 0.5.66 §3.2 跨 workspace 兼容警示用:扫 argv tokens
|
|
30
|
-
///
|
|
33
|
+
/// 0.5.66 §3.2 跨 workspace 兼容警示用:扫 **team-agent 进程自己的** argv tokens
|
|
34
|
+
/// 找已知 bypass flag。**只做检测,不做行为决策**(0.6.0 删)。
|
|
35
|
+
///
|
|
36
|
+
/// 不认 `--force`:那是 team-agent 自己的 CLI 旗(`init --force` /
|
|
37
|
+
/// `remove-agent --confirm --force` / `restart --force`),与 cursor worker
|
|
38
|
+
/// 的 `--force` 撞名。本函数看不到「当前在看哪个 provider 的 argv」,
|
|
39
|
+
/// 认 `--force` 会把既有非 grok/cursor 路径误判成 bypass。
|
|
40
|
+
/// cursor worker 的 bypass 仍走 [`provider_bypass_flag`],不走这条检测面。
|
|
41
|
+
/// `--always-approve` 可以认:team-agent 自己没有这个旗。
|
|
31
42
|
///
|
|
32
43
|
/// TODO(0.6.0): remove
|
|
33
44
|
pub(crate) fn detect_bypass_flag_in_argv(argv_tokens: &[String]) -> Option<&'static str> {
|
|
@@ -43,6 +54,9 @@ pub(crate) fn detect_bypass_flag_in_argv(argv_tokens: &[String]) -> Option<&'sta
|
|
|
43
54
|
if token == "--allow-all" || token == "--yolo" {
|
|
44
55
|
return Some("--allow-all");
|
|
45
56
|
}
|
|
57
|
+
if token == "--always-approve" {
|
|
58
|
+
return Some("--always-approve");
|
|
59
|
+
}
|
|
46
60
|
}
|
|
47
61
|
None
|
|
48
62
|
}
|
|
@@ -77,4 +91,34 @@ mod tests {
|
|
|
77
91
|
assert_eq!(provider_bypass_flag(Provider::GeminiCli), None);
|
|
78
92
|
assert_eq!(provider_bypass_flag(Provider::Fake), None);
|
|
79
93
|
}
|
|
94
|
+
|
|
95
|
+
#[test]
|
|
96
|
+
fn grok_and_cursor_bypass_flags() {
|
|
97
|
+
assert_eq!(
|
|
98
|
+
provider_bypass_flag(Provider::Grok),
|
|
99
|
+
Some("--always-approve")
|
|
100
|
+
);
|
|
101
|
+
assert_eq!(
|
|
102
|
+
provider_bypass_flag(Provider::CursorAgent),
|
|
103
|
+
Some("--force")
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
#[test]
|
|
108
|
+
fn detect_bypass_flag_ignores_team_agent_force() {
|
|
109
|
+
let argv = [
|
|
110
|
+
"team-agent".to_string(),
|
|
111
|
+
"remove-agent".to_string(),
|
|
112
|
+
"x".to_string(),
|
|
113
|
+
"--confirm".to_string(),
|
|
114
|
+
"--force".to_string(),
|
|
115
|
+
];
|
|
116
|
+
assert_eq!(
|
|
117
|
+
detect_bypass_flag_in_argv(&argv),
|
|
118
|
+
None,
|
|
119
|
+
"detect_bypass_flag_in_argv scans team-agent process argv, not worker argv; \
|
|
120
|
+
`remove-agent --force` must not look like a provider bypass flag; got {:?}",
|
|
121
|
+
detect_bypass_flag_in_argv(&argv)
|
|
122
|
+
);
|
|
123
|
+
}
|
|
80
124
|
}
|
|
@@ -215,7 +215,8 @@ fn latest_explicit_error_fact_for_record(
|
|
|
215
215
|
Provider::Claude | Provider::ClaudeCode => claude_latest_explicit_error_fact(record),
|
|
216
216
|
// C-3-5 cr verdict: copilot N35 通知不依赖 turn-state 分类;一期 Unknown,
|
|
217
217
|
// 与 GeminiCli/Fake 同精神。二期接 sqlite turns 表后再回填。
|
|
218
|
-
Provider::Copilot | Provider::
|
|
218
|
+
Provider::Copilot | Provider::Grok | Provider::CursorAgent | Provider::GeminiCli
|
|
219
|
+
| Provider::Fake => None,
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
222
|
|
|
@@ -378,7 +379,8 @@ fn extract_lifecycle_facts(
|
|
|
378
379
|
Provider::Codex => codex_lifecycle_fact(record),
|
|
379
380
|
// C-3-1 cr verdict: copilot lifecycle facts 一期不导出(classify→None,
|
|
380
381
|
// Unknown);二期读 sqlite turns 表(turn_index/assistant_response)。
|
|
381
|
-
Provider::Copilot | Provider::
|
|
382
|
+
Provider::Copilot | Provider::Grok | Provider::CursorAgent | Provider::GeminiCli
|
|
383
|
+
| Provider::Fake => None,
|
|
382
384
|
})
|
|
383
385
|
.collect()
|
|
384
386
|
}
|
|
@@ -46,7 +46,8 @@ fn fault_fact(provider: Provider, record: &serde_json::Value) -> Option<FaultFac
|
|
|
46
46
|
match provider {
|
|
47
47
|
Provider::Claude | Provider::ClaudeCode => claude_fault_fact(record),
|
|
48
48
|
Provider::Codex => codex_fault_fact(record),
|
|
49
|
-
Provider::Copilot | Provider::
|
|
49
|
+
Provider::Copilot | Provider::Grok | Provider::CursorAgent | Provider::GeminiCli
|
|
50
|
+
| Provider::Fake => None,
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: provider 命令构造与会话观察入口
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: adapter
|
|
6
|
+
//! what: 按 Provider 分发 launch/capability
|
|
7
|
+
//! boundary:
|
|
8
|
+
//! - 不把 observation 写成 ownership
|
|
9
|
+
//! maturity: wired
|
|
10
|
+
//! ---
|
|
1
11
|
//!
|
|
2
12
|
//! step 8 · provider — type + trait SKELETON (ROUND-0).
|
|
3
13
|
//!
|
|
@@ -44,6 +54,7 @@ pub mod faults;
|
|
|
44
54
|
pub mod session;
|
|
45
55
|
pub mod session_scan;
|
|
46
56
|
pub mod startup_prompt;
|
|
57
|
+
pub mod submit_now;
|
|
47
58
|
pub mod types;
|
|
48
59
|
/// 0.4.x: provider wire-format helpers — single source of truth for
|
|
49
60
|
/// `Provider` ↔ string conversions (replaces 7 hand-rolled match copies).
|
|
@@ -1,4 +1,42 @@
|
|
|
1
|
-
//!
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 会话捕获通道——把「席位有 pending id 但还没有权威会话」这件事,经扫描候选 + 分配器,收敛成 state 上的原子四元组(session_id + rollout_path + captured_at + captured_via),收敛不了就写明确的诊断态
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: capture_missing_provider_sessions_once
|
|
6
|
+
//! what: 单次捕获通道:挑 pending 席位 → adapter 扫候选 → 分配 → 写四元组或写 ambiguous/identity_mismatch/transcript_missing
|
|
7
|
+
//! - name: converge_missing_provider_sessions
|
|
8
|
+
//! what: 破坏性生命周期门用的有界收敛屏障:显式 deadline + poll_interval,反复调上一函数直到无缺口或超预算
|
|
9
|
+
//! - name: recover_resume_session_from_events
|
|
10
|
+
//! what: 从 event log 的 session.captured 记录补回缺失的四元组,captured_via=event_log_repair
|
|
11
|
+
//! - name: incomplete_resumable_agent_ids
|
|
12
|
+
//! what: 列出「provider 支持 resume 但四元组不完整」的席位 id,已排序
|
|
13
|
+
//! - name: incomplete_interacted_resumable_agent_ids
|
|
14
|
+
//! what: 上一条再筛「有过 first_send_at」的子集
|
|
15
|
+
//! - name: CapturePassReport
|
|
16
|
+
//! what: 单次捕获的结果面:changed/pending/assigned/ambiguous/identity_mismatches/capture_failures/candidate_count_by_agent/transcript_missing
|
|
17
|
+
//! - name: SessionConvergence
|
|
18
|
+
//! what: 收敛结果:converged 是否达成、changed、剩余 missing、预算与实耗
|
|
19
|
+
//! - name: SessionConvergenceProgress
|
|
20
|
+
//! what: 每轮回调给调用方的进度快照
|
|
21
|
+
//! - name: SESSION_CAPTURE_CONVERGENCE_DEADLINE_MS
|
|
22
|
+
//! what: 默认收敛预算 12s(restart 复用同一常量)
|
|
23
|
+
//! - name: SESSION_CAPTURE_CONVERGENCE_POLL_MS
|
|
24
|
+
//! what: 默认轮询间隔 250ms
|
|
25
|
+
//! requires:
|
|
26
|
+
//! - name: crate::provider::ProviderAdapter
|
|
27
|
+
//! what: 候选扫描一律经 adapter.capture_session_candidates,本文件不直接读 provider 存档
|
|
28
|
+
//! - name: crate::state::identity_keys::SessionAttributionKey
|
|
29
|
+
//! what: 分配器的 claimed 键空间(Session/Transcript × team-scoped/global)
|
|
30
|
+
//! - name: crate::event_log::EventLog
|
|
31
|
+
//! what: event-log 修复通道的事实源
|
|
32
|
+
//! boundary:
|
|
33
|
+
//! - 四元组是原子的:缺 session_id 或 rollout_path 就整条不写,宁可留 pending 也不写半条
|
|
34
|
+
//! - 不发事件——transcript_missing 只进报告,事件由 coordinator tick 节流发出
|
|
35
|
+
//! - 不启动/销毁席位,不决定 restart 走向;只提供事实与诊断态
|
|
36
|
+
//! - capture_state 只是诊断字段,写它不等于写权威四元组
|
|
37
|
+
//! - 歧义只在 finalize_ambiguous=true 的那一趟才落盘;收敛超时的那趟不 finalize
|
|
38
|
+
//! maturity: wired
|
|
39
|
+
//! ---
|
|
2
40
|
use std::collections::{BTreeMap, BTreeSet};
|
|
3
41
|
use std::path::{Path, PathBuf};
|
|
4
42
|
|
|
@@ -30,7 +68,6 @@ pub struct CapturePassReport {
|
|
|
30
68
|
/// (coordinator tick) emits a throttled
|
|
31
69
|
/// `provider.session.transcript_missing` event for each entry.
|
|
32
70
|
pub transcript_missing: Vec<TranscriptMissing>,
|
|
33
|
-
pub(crate) context_forks: Vec<crate::lifecycle::launch::ContextForkFinalized>,
|
|
34
71
|
}
|
|
35
72
|
|
|
36
73
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
@@ -97,6 +134,26 @@ pub struct SessionConvergenceProgress {
|
|
|
97
134
|
/// unbounded wait: callers must pass an explicit `deadline` and `poll_interval`.
|
|
98
135
|
/// Each poll runs the shared allocator once, reports progress, and sleeps until
|
|
99
136
|
/// either all required agents have provider sessions or the deadline expires.
|
|
137
|
+
/// ---
|
|
138
|
+
/// purpose: 有界收敛屏障——在显式预算内反复跑单次捕获,直到调用方指定的必需席位都拿到会话,或预算耗尽
|
|
139
|
+
/// params:
|
|
140
|
+
/// state: 就地修改的 state JSON;每轮的写入都直接落在它上面
|
|
141
|
+
/// adapter_for: provider → adapter 的工厂
|
|
142
|
+
/// deadline: 总预算;绝不无限等待
|
|
143
|
+
/// poll_interval: 轮询间隔;同时用作单次捕获的 timeout(取秒数,至少 1)
|
|
144
|
+
/// missing_agent_ids: 由调用方定义「谁必须有会话」——本函数不自己判定必需集
|
|
145
|
+
/// progress: 每轮回调;返回 Err 即整体中止并把该错误上抛
|
|
146
|
+
/// returns: converged=true 表示 missing 已空;converged=false 表示预算到期仍有缺口,两者都是正常返回而非错误
|
|
147
|
+
/// errors: 单次捕获的 ProviderError 与 progress 回调的错误都被转成 String 上抛
|
|
148
|
+
/// contract:
|
|
149
|
+
/// provides:
|
|
150
|
+
/// - name: converge_missing_provider_sessions
|
|
151
|
+
/// what: 只有 missing 清空后才会补跑一趟 finalize_ambiguous=true;超时路径不补跑
|
|
152
|
+
/// boundary:
|
|
153
|
+
/// - 不是机会式单次捕获,也不是无界等待——预算必须由调用方显式给出
|
|
154
|
+
/// - 超时返回时歧义态未被 finalize:行上不会出现 attribution_ambiguous 标记;capture_state 保持前值(可能缺失)——非歧义的 pending 席位仍会被单次捕获写成 pending_first_turn / pending_context_fork / transcript_missing,这不受 finalize 开关控制
|
|
155
|
+
/// - progress 只报告不决策;是否继续由预算决定
|
|
156
|
+
/// ---
|
|
100
157
|
pub fn converge_missing_provider_sessions<F, M, P>(
|
|
101
158
|
state: &mut Value,
|
|
102
159
|
adapter_for: &mut F,
|
|
@@ -168,6 +225,25 @@ where
|
|
|
168
225
|
}
|
|
169
226
|
}
|
|
170
227
|
|
|
228
|
+
/// ---
|
|
229
|
+
/// purpose: 跑一趟捕获——为每个还缺权威会话的席位扫候选、经分配器定归属,把能确定的写成原子四元组,把不能确定的写成诊断态
|
|
230
|
+
/// params:
|
|
231
|
+
/// state: 就地修改的 state JSON;无 agents 对象时直接返回空报告
|
|
232
|
+
/// adapter_for: provider → adapter 的工厂;扫描一律经 adapter
|
|
233
|
+
/// finalize_ambiguous: false 时歧义只进报告不落盘;true 时才把 attribution_ambiguous / capture_state 写进 agent 行
|
|
234
|
+
/// timeout_s: 传给 adapter.capture_session_candidates 的单席位扫描预算
|
|
235
|
+
/// returns: CapturePassReport;changed 表示 state 被改过,调用方据此决定是否持久化
|
|
236
|
+
/// errors: 当前实现不产生 Err(Result 签名为与调用方同形);取不到 agents 视图返回空报告;单席位扫描失败进 capture_failures 且该席位保持 pending,不中断整趟
|
|
237
|
+
/// contract:
|
|
238
|
+
/// provides:
|
|
239
|
+
/// - name: capture_missing_provider_sessions_once
|
|
240
|
+
/// what: 写权威四元组的唯一常规通道(另一条是 event-log 修复)
|
|
241
|
+
/// boundary:
|
|
242
|
+
/// - 候选的 embedded worker id 与本席位不符 → 记 identity_mismatch 并跳过,绝不写会话
|
|
243
|
+
/// - 候选 session id 与 pending expected id 不符 → 记 ambiguous 并跳过,绝不写会话
|
|
244
|
+
/// - _pending_session_id 与行上 session_id 不一致时不 stamp captured——重开在途,旧会话不得被宣告为新席位的权威
|
|
245
|
+
/// - 不发事件;transcript_missing 只进报告
|
|
246
|
+
/// ---
|
|
171
247
|
pub fn capture_missing_provider_sessions_once<F>(
|
|
172
248
|
state: &mut Value,
|
|
173
249
|
adapter_for: &mut F,
|
|
@@ -317,12 +393,9 @@ where
|
|
|
317
393
|
// partial candidates (no session_id or no rollout_path). When
|
|
318
394
|
// it returns false, the row stays unchanged; capture re-runs
|
|
319
395
|
// on the next coordinator tick.
|
|
320
|
-
if
|
|
396
|
+
if apply_captured_session(agent_obj, &candidate.captured) {
|
|
321
397
|
report.changed = true;
|
|
322
398
|
report.assigned.push(item.agent_id);
|
|
323
|
-
if let AppliedCapture::ContextFork(context_fork) = applied {
|
|
324
|
-
report.context_forks.push(context_fork);
|
|
325
|
-
}
|
|
326
399
|
}
|
|
327
400
|
continue;
|
|
328
401
|
}
|
|
@@ -517,6 +590,19 @@ fn classify_pending_capture_state(
|
|
|
517
590
|
}
|
|
518
591
|
}
|
|
519
592
|
|
|
593
|
+
/// ---
|
|
594
|
+
/// purpose: 列出「provider 本可 resume、但会话事实还不完整」的席位——即捕获通道该继续照看的那批
|
|
595
|
+
/// params:
|
|
596
|
+
/// state: 只读的 state JSON;无 agents 对象时返回空
|
|
597
|
+
/// returns: 按 agent id 升序的清单
|
|
598
|
+
/// contract:
|
|
599
|
+
/// provides:
|
|
600
|
+
/// - name: incomplete_resumable_agent_ids
|
|
601
|
+
/// what: 只读判定,不修改 state
|
|
602
|
+
/// boundary:
|
|
603
|
+
/// - 用全局 get_adapter 取 adapter,不接受注入——测试要替身请走 capture 通道那两个函数
|
|
604
|
+
/// - 不区分「从没捕获过」与「捕获过但四元组残缺」,两者都在清单里
|
|
605
|
+
/// ---
|
|
520
606
|
pub fn incomplete_resumable_agent_ids(state: &Value) -> Vec<String> {
|
|
521
607
|
let Some(agents) = state.get("agents").and_then(Value::as_object) else {
|
|
522
608
|
return Vec::new();
|
|
@@ -535,6 +621,28 @@ pub fn incomplete_resumable_agent_ids(state: &Value) -> Vec<String> {
|
|
|
535
621
|
out
|
|
536
622
|
}
|
|
537
623
|
|
|
624
|
+
/// ---
|
|
625
|
+
/// purpose: 会话四元组缺失时的第二条修复通道——倒序翻 event log,用该席位最近一条可信的 session.captured 记录把行补全
|
|
626
|
+
/// params:
|
|
627
|
+
/// workspace: event log 所在工作区
|
|
628
|
+
/// agent_id: 目标席位;事件的 agent_id 或 worker_id 命中即算该席位
|
|
629
|
+
/// previous: 修复前的 agent 行;provider 字段决定要不要套 claude leader-marker 过滤
|
|
630
|
+
/// adapter: 用于二次确认候选会话确实可 resume
|
|
631
|
+
/// auth_mode: 传给 session_is_resumable 的鉴权模式
|
|
632
|
+
/// exclude_session_ids: 调用方给出的黑名单(例如刚被判为不可用的会话)
|
|
633
|
+
/// returns: Some(补好四元组的新行) 表示找到可用记录;None 表示没找到,或先撞上 discard.session_tombstone 而主动放弃
|
|
634
|
+
/// errors: 读 event log 的 Io 错误、以及 session_is_resumable 的 ProviderError
|
|
635
|
+
/// contract:
|
|
636
|
+
/// provides:
|
|
637
|
+
/// - name: recover_resume_session_from_events
|
|
638
|
+
/// what: 修复结果一定是完整四元组;captured_at 缺事件 ts 时退到当前时间,captured_via 固定 event_log_repair
|
|
639
|
+
/// boundary:
|
|
640
|
+
/// - 只读 event log 与 rollout 路径存在性,不扫 provider 存档、不调分配器
|
|
641
|
+
/// - 不走分配器的 claimed-key 冲突检查,只看 exclude_session_ids 与 tombstone
|
|
642
|
+
/// - rollout_path 存在性用 exists()——对目录型 backing(grok/cursor)只要目录在就为真
|
|
643
|
+
/// - claude 家族额外过滤 leader-marker transcript;其它 provider 无等价归属过滤
|
|
644
|
+
/// - 修复成功会清掉 attribution_ambiguous 标记
|
|
645
|
+
/// ---
|
|
538
646
|
pub fn recover_resume_session_from_events(
|
|
539
647
|
workspace: &Path,
|
|
540
648
|
agent_id: &str,
|
|
@@ -651,6 +759,19 @@ fn event_rollout_path(event: &Value) -> Option<PathBuf> {
|
|
|
651
759
|
.map(PathBuf::from)
|
|
652
760
|
}
|
|
653
761
|
|
|
762
|
+
/// ---
|
|
763
|
+
/// purpose: 在「会话不完整」清单上再筛出真正被用过的席位——有 first_send_at 才算交互过
|
|
764
|
+
/// params:
|
|
765
|
+
/// state: 只读的 state JSON
|
|
766
|
+
/// returns: 按 agent id 升序的子集
|
|
767
|
+
/// contract:
|
|
768
|
+
/// provides:
|
|
769
|
+
/// - name: incomplete_interacted_resumable_agent_ids
|
|
770
|
+
/// what: incomplete_resumable_agent_ids 的过滤视图,只读
|
|
771
|
+
/// boundary:
|
|
772
|
+
/// - 交互判据只看 first_send_at 非空,不看 last_result_at / pane 输出
|
|
773
|
+
/// - 从没派过活的席位不进清单——沉默席位不算捕获失败
|
|
774
|
+
/// ---
|
|
654
775
|
pub fn incomplete_interacted_resumable_agent_ids(state: &Value) -> Vec<String> {
|
|
655
776
|
let mut out = incomplete_resumable_agent_ids(state)
|
|
656
777
|
.into_iter()
|
|
@@ -1170,11 +1291,6 @@ fn candidate_key(owner: &PendingSessionCapture, candidate: &CapturedSessionCandi
|
|
|
1170
1291
|
.join("|")
|
|
1171
1292
|
}
|
|
1172
1293
|
|
|
1173
|
-
enum AppliedCapture {
|
|
1174
|
-
Session,
|
|
1175
|
-
ContextFork(crate::lifecycle::launch::ContextForkFinalized),
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
1294
|
/// 0.4.6 tuple-atomic contract (audit §Capture 修改清单, line 111): writes
|
|
1179
1295
|
/// the authoritative session tuple if and only if the candidate carries
|
|
1180
1296
|
/// BOTH `session_id` and `rollout_path`. A partial candidate (e.g.
|
|
@@ -1185,16 +1301,12 @@ enum AppliedCapture {
|
|
|
1185
1301
|
fn apply_captured_session(
|
|
1186
1302
|
agent_obj: &mut serde_json::Map<String, Value>,
|
|
1187
1303
|
captured: &CapturedSession,
|
|
1188
|
-
) ->
|
|
1189
|
-
if agent_obj.get("capture_state").and_then(Value::as_str) == Some("pending_context_fork") {
|
|
1190
|
-
return crate::lifecycle::finalize_pending_fork_capture(agent_obj, captured)
|
|
1191
|
-
.map(AppliedCapture::ContextFork);
|
|
1192
|
-
}
|
|
1304
|
+
) -> bool {
|
|
1193
1305
|
let Some(session_id) = captured.session_id.as_ref() else {
|
|
1194
|
-
return
|
|
1306
|
+
return false;
|
|
1195
1307
|
};
|
|
1196
1308
|
let Some(rollout_path) = captured.rollout_path.as_ref() else {
|
|
1197
|
-
return
|
|
1309
|
+
return false;
|
|
1198
1310
|
};
|
|
1199
1311
|
agent_obj.insert(
|
|
1200
1312
|
"session_id".to_string(),
|
|
@@ -1224,7 +1336,7 @@ fn apply_captured_session(
|
|
|
1224
1336
|
// diagnose/status observability.
|
|
1225
1337
|
agent_obj.remove("_pending_session_id");
|
|
1226
1338
|
agent_obj.insert("capture_state".to_string(), serde_json::json!("captured"));
|
|
1227
|
-
|
|
1339
|
+
true
|
|
1228
1340
|
}
|
|
1229
1341
|
|
|
1230
1342
|
fn claimed_provider_session_keys(
|
|
@@ -1410,6 +1522,20 @@ pub(crate) mod test_support {
|
|
|
1410
1522
|
}
|
|
1411
1523
|
|
|
1412
1524
|
impl CaptureCandidatesAdapter {
|
|
1525
|
+
/// ---
|
|
1526
|
+
/// purpose: 造一个可控的测试替身 adapter:指定 provider,并可让某个席位的候选扫描必定失败
|
|
1527
|
+
/// params:
|
|
1528
|
+
/// provider: 替身自称的 provider
|
|
1529
|
+
/// fail_agent_id: 该席位的 capture_session_candidates 返回错误;None 表示都不失败
|
|
1530
|
+
/// error: 失败时携带的错误文本
|
|
1531
|
+
/// returns: 未挂候选表的替身;候选表另经 with_candidates 注入
|
|
1532
|
+
/// contract:
|
|
1533
|
+
/// provides:
|
|
1534
|
+
/// - name: new
|
|
1535
|
+
/// what: 纯构造,无 I/O
|
|
1536
|
+
/// boundary:
|
|
1537
|
+
/// - 仅 cfg(test) 存在,不进产品二进制
|
|
1538
|
+
/// ---
|
|
1413
1539
|
pub(crate) fn new(provider: Provider, fail_agent_id: Option<&str>, error: &str) -> Self {
|
|
1414
1540
|
Self {
|
|
1415
1541
|
provider,
|
|
@@ -1419,6 +1545,18 @@ pub(crate) mod test_support {
|
|
|
1419
1545
|
}
|
|
1420
1546
|
}
|
|
1421
1547
|
|
|
1548
|
+
/// ---
|
|
1549
|
+
/// purpose: 给测试替身挂一张 agent_id → 候选列表的表,用来构造分配器层面的交叉归属回归
|
|
1550
|
+
/// params:
|
|
1551
|
+
/// candidates_by_agent: 按席位 id 索引的候选;未列入的席位仍返回空列表
|
|
1552
|
+
/// returns: 挂好表的自身(builder 风格,消费 self)
|
|
1553
|
+
/// contract:
|
|
1554
|
+
/// provides:
|
|
1555
|
+
/// - name: with_candidates
|
|
1556
|
+
/// what: 只改替身内部状态,不触发任何扫描
|
|
1557
|
+
/// boundary:
|
|
1558
|
+
/// - 仅 cfg(test) 存在,不进产品二进制
|
|
1559
|
+
/// ---
|
|
1422
1560
|
pub(crate) fn with_candidates(
|
|
1423
1561
|
mut self,
|
|
1424
1562
|
candidates_by_agent: BTreeMap<String, Vec<CapturedSessionCandidate>>,
|
|
@@ -2653,7 +2791,7 @@ mod u1_tests {
|
|
|
2653
2791
|
spawn_cwd: PathBuf::from("/tmp/cwd"),
|
|
2654
2792
|
};
|
|
2655
2793
|
let written = super::apply_captured_session(&mut agent, &captured);
|
|
2656
|
-
assert!(written
|
|
2794
|
+
assert!(written, "apply must accept valid captured");
|
|
2657
2795
|
assert!(
|
|
2658
2796
|
agent.get("_pending_session_id").is_none(),
|
|
2659
2797
|
"S1-CAPTURE-001: _pending_session_id must be removed after capture \
|