@team-agent/installer 0.5.66 → 0.5.69
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 +156 -0
- package/crates/team-agent/src/cli/emit.rs +6 -5
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +22 -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 +3 -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 +121 -5
- 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 +31 -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 +3 -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 +973 -75
- 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 +398 -26
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +80 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +60 -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 +548 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +120 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +208 -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 +482 -1
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +830 -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 +87 -3
- 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 +127 -25
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +140 -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 +107 -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 +99 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +52 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +214 -160
- package/crates/team-agent/src/lifecycle/restart/common.rs +513 -37
- 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 +233 -6
- 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/claude_profile_launch_red.rs +24 -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 +464 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +247 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_g0.stdout.txt +222 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +787 -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 +485 -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/harvest2_a_batch_red.rs +1 -1
- 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/pi_catalog_red.rs +97 -0
- package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +128 -0
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +207 -0
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +838 -0
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +320 -0
- package/crates/team-agent/src/lifecycle/tests/pi_lifecycle_doctor_red.rs +98 -0
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +216 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -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 +30 -13
- package/crates/team-agent/src/lifecycle/types.rs +61 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +143 -12
- 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 +430 -41
- 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 +17 -6
- 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 +259 -6
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +89 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +3 -0
- package/crates/team-agent/src/provider/adapters/pi.rs +152 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +47 -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 +78 -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 +205 -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/pi.rs +172 -0
- package/crates/team-agent/src/provider/session_scan.rs +12 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +384 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +29 -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 +1116 -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 +32 -239
- 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
|
@@ -390,10 +390,19 @@ fn compile_role_agent_with_mode(
|
|
|
390
390
|
let id = required_string(&meta, role_path, "name")?;
|
|
391
391
|
let role = required_string(&meta, role_path, "role")?;
|
|
392
392
|
let provider = required_string(&meta, role_path, "provider")?;
|
|
393
|
+
require_explicit_grok_role_model(&meta, role_path, &provider)?;
|
|
394
|
+
require_explicit_cursor_role_model(&meta, role_path, &provider)?;
|
|
395
|
+
require_explicit_pi_role_fields(&meta, role_path, &provider)?;
|
|
393
396
|
let model = resolve_model(&meta, team_meta, &provider);
|
|
394
397
|
let auth_mode = string_field(&meta, "auth_mode")
|
|
395
398
|
.or_else(|| string_field(team_meta, "default_auth_mode"))
|
|
396
399
|
.unwrap_or_else(|| "subscription".to_string());
|
|
400
|
+
if parse_canonical_provider(&provider) == Some(Provider::Pi) && auth_mode != "subscription" {
|
|
401
|
+
return Err(ModelError::Validation(format!(
|
|
402
|
+
"{}: Pi roles support subscription auth_mode only",
|
|
403
|
+
role_path.display()
|
|
404
|
+
)));
|
|
405
|
+
}
|
|
397
406
|
if auth_mode != "subscription" && meta.get("profile").is_none() {
|
|
398
407
|
return Err(ModelError::Validation(format!(
|
|
399
408
|
"{}: profile is required when auth_mode is '{auth_mode}'",
|
|
@@ -476,13 +485,25 @@ fn compile_role_agent_with_mode(
|
|
|
476
485
|
})
|
|
477
486
|
.unwrap_or("");
|
|
478
487
|
let provider_enum = parse_canonical_provider(provider_str).unwrap_or(Provider::Codex);
|
|
479
|
-
if effort.is_claude_only()
|
|
488
|
+
if effort.is_claude_only()
|
|
489
|
+
&& !is_claude_family(provider_enum)
|
|
490
|
+
&& provider_enum != Provider::Pi
|
|
491
|
+
{
|
|
480
492
|
return Err(ModelError::Validation(format!(
|
|
481
493
|
"{}: effort '{}' is only supported by claude/claude_code (provider: {provider_str})",
|
|
482
494
|
role_path.display(),
|
|
483
495
|
effort.as_str()
|
|
484
496
|
)));
|
|
485
497
|
}
|
|
498
|
+
if provider_enum == Provider::CursorAgent {
|
|
499
|
+
return Err(ModelError::Validation(format!(
|
|
500
|
+
"{}: cursor_agent does not support effort '{}'. \
|
|
501
|
+
The Cursor CLI has no `--effort` flag; the framework must not drop the field and still launch. \
|
|
502
|
+
Remove effort from the role file (do not map it into `--model[effort=]` until that form is measured).",
|
|
503
|
+
role_path.display(),
|
|
504
|
+
effort.as_str()
|
|
505
|
+
)));
|
|
506
|
+
}
|
|
486
507
|
agent_items.push(("effort", Value::Str(effort.as_str().to_string())));
|
|
487
508
|
}
|
|
488
509
|
Ok(CompiledRole {
|
|
@@ -550,12 +571,107 @@ fn required_string(meta: &Value, path: &Path, key: &str) -> Result<String, Model
|
|
|
550
571
|
})
|
|
551
572
|
}
|
|
552
573
|
|
|
553
|
-
///
|
|
554
|
-
///
|
|
555
|
-
|
|
574
|
+
/// 缺 model 时框架会填内建默认(grok 上是 grok-4),席位因此静默拿到
|
|
575
|
+
/// 与角色文件无关的模型和上下文窗口,而 argv 看起来完全正常。
|
|
576
|
+
/// 角色文件必须自己写死;内建默认、team 级默认、CLI 全局默认都是隐式来源。
|
|
577
|
+
fn require_explicit_grok_role_model(
|
|
578
|
+
meta: &Value,
|
|
579
|
+
path: &Path,
|
|
580
|
+
provider: &str,
|
|
581
|
+
) -> Result<(), ModelError> {
|
|
582
|
+
if parse_canonical_provider(provider) != Some(Provider::Grok) {
|
|
583
|
+
return Ok(());
|
|
584
|
+
}
|
|
585
|
+
if string_field(meta, "model").is_some_and(|value| !value.trim().is_empty()) {
|
|
586
|
+
return Ok(());
|
|
587
|
+
}
|
|
588
|
+
Err(ModelError::Validation(format!(
|
|
589
|
+
"{}: missing front matter field model. \
|
|
590
|
+
Without an explicit model the framework fills a built-in default (grok-4 for grok), \
|
|
591
|
+
so the seat silently gets a model and context window that are not in the role file, \
|
|
592
|
+
while argv still looks normal. The role file must name the model itself; \
|
|
593
|
+
built-in defaults, team-level defaults, and the Grok CLI global default are all implicit sources. Example:\n\
|
|
594
|
+
model: grok-4.6",
|
|
595
|
+
path.display()
|
|
596
|
+
)))
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/// 缺 model 时框架曾填 builtin `sonnet-4-thinking`,本机 shim 还会剥掉该默认。
|
|
600
|
+
/// 角色必须自己写死;内建默认、team 级默认、CLI 全局默认都是隐式来源。
|
|
601
|
+
fn require_explicit_cursor_role_model(
|
|
602
|
+
meta: &Value,
|
|
603
|
+
path: &Path,
|
|
604
|
+
provider: &str,
|
|
605
|
+
) -> Result<(), ModelError> {
|
|
606
|
+
if parse_canonical_provider(provider) != Some(Provider::CursorAgent) {
|
|
607
|
+
return Ok(());
|
|
608
|
+
}
|
|
609
|
+
if string_field(meta, "model").is_some_and(|value| !value.trim().is_empty()) {
|
|
610
|
+
return Ok(());
|
|
611
|
+
}
|
|
612
|
+
Err(ModelError::Validation(format!(
|
|
613
|
+
"{}: missing front matter field model. \
|
|
614
|
+
Without an explicit model the framework fills a built-in default (sonnet-4-thinking for cursor_agent), \
|
|
615
|
+
so the seat silently gets a model that is not in the role file, while argv still looks normal. \
|
|
616
|
+
The role file must name the model itself; built-in defaults, team-level defaults, and the Cursor CLI default are all implicit sources. Example:\n\
|
|
617
|
+
model: sonnet-4-thinking",
|
|
618
|
+
path.display()
|
|
619
|
+
)))
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
fn require_explicit_pi_role_fields(
|
|
556
623
|
meta: &Value,
|
|
557
624
|
path: &Path,
|
|
558
|
-
|
|
625
|
+
provider: &str,
|
|
626
|
+
) -> Result<(), ModelError> {
|
|
627
|
+
if parse_canonical_provider(provider) != Some(Provider::Pi) {
|
|
628
|
+
return Ok(());
|
|
629
|
+
}
|
|
630
|
+
let model = string_field(meta, "model")
|
|
631
|
+
.filter(|value| !value.trim().is_empty())
|
|
632
|
+
.ok_or_else(|| {
|
|
633
|
+
ModelError::Validation(format!(
|
|
634
|
+
"{}: Pi roles require an explicit qualified exact model",
|
|
635
|
+
path.display()
|
|
636
|
+
))
|
|
637
|
+
})?;
|
|
638
|
+
let (catalog, name) = model.trim().split_once('/').ok_or_else(|| {
|
|
639
|
+
ModelError::Validation(format!(
|
|
640
|
+
"{}: Pi roles require a qualified exact model such as provider/model",
|
|
641
|
+
path.display()
|
|
642
|
+
))
|
|
643
|
+
})?;
|
|
644
|
+
if catalog.is_empty() || name.is_empty() || model.contains('*') {
|
|
645
|
+
return Err(ModelError::Validation(format!(
|
|
646
|
+
"{}: Pi roles require a qualified exact model",
|
|
647
|
+
path.display()
|
|
648
|
+
)));
|
|
649
|
+
}
|
|
650
|
+
if !string_field(meta, "effort").is_some_and(|value| !value.trim().is_empty()) {
|
|
651
|
+
return Err(ModelError::Validation(format!(
|
|
652
|
+
"{}: Pi roles require explicit effort",
|
|
653
|
+
path.display()
|
|
654
|
+
)));
|
|
655
|
+
}
|
|
656
|
+
let tools = required_tools(meta, path)?;
|
|
657
|
+
if !tools.iter().any(|tool| tool == "mcp_team") {
|
|
658
|
+
return Err(ModelError::Validation(format!(
|
|
659
|
+
"{}: Pi roles require mcp_team",
|
|
660
|
+
path.display()
|
|
661
|
+
)));
|
|
662
|
+
}
|
|
663
|
+
match meta.get("dangerously_skip_permissions") {
|
|
664
|
+
Some(Value::Bool(true)) => Ok(()),
|
|
665
|
+
_ => Err(ModelError::Validation(format!(
|
|
666
|
+
"{}: Pi roles require dangerously_skip_permissions: true",
|
|
667
|
+
path.display()
|
|
668
|
+
))),
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/// 0.5.66 bypass 单源:角色 md 的 `dangerously_skip_permissions` 必填 bool。
|
|
673
|
+
/// 缺 = 编译失败(消息含硬串);非 bool = 同 fail-loud。
|
|
674
|
+
fn required_dangerously_skip_permissions(meta: &Value, path: &Path) -> Result<bool, ModelError> {
|
|
559
675
|
match meta.get("dangerously_skip_permissions") {
|
|
560
676
|
Some(Value::Bool(value)) => Ok(*value),
|
|
561
677
|
Some(_) => Err(ModelError::Validation(format!(
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: coordinator daemon 进程的宿主——装配 transport/registry,跑主循环,把 tick 的崩溃收进退避与事件
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: run_daemon
|
|
6
|
+
//! what: `team-agent coordinator --workspace ..` 子进程入口:解析 transport、构造 Coordinator、进主循环
|
|
7
|
+
//! - name: backoff_sleep_sec
|
|
8
|
+
//! what: 连续失败次数到睡眠秒数的纯函数(5→10→20→40→60→60,封顶 BACKOFF_MAX_SEC)
|
|
9
|
+
//! - name: resolve_tick_interval
|
|
10
|
+
//! what: 解析 tick 间隔并顺带确保 message store schema 可打开
|
|
11
|
+
//! depends:
|
|
12
|
+
//! - super::health
|
|
13
|
+
//! - super::tick
|
|
14
|
+
//! - super::types
|
|
15
|
+
//! - super::conpty_shim
|
|
16
|
+
//! - crate::state::persist
|
|
17
|
+
//! - crate::transport_factory
|
|
18
|
+
//! - crate::tmux_backend
|
|
19
|
+
//! - crate::message_store
|
|
20
|
+
//! - crate::event_log
|
|
21
|
+
//! boundary:
|
|
22
|
+
//! - 不做单次 tick 的编排内容,tick 体在 tick.rs
|
|
23
|
+
//! - 不决定健康与否,只消费 health.rs 的判定与路径
|
|
24
|
+
//! - transport 解析失败时降级为 tmux workspace 后端保活,但不静默——降级原因写进 boot metadata
|
|
25
|
+
//! maturity: wired
|
|
26
|
+
//! ---
|
|
1
27
|
//!
|
|
2
28
|
//! daemon 主循环面(`__main__.py`)—— 退避序列 + tick 间隔解析 + 子进程入口。
|
|
3
29
|
|
|
@@ -41,6 +67,13 @@ pub struct DaemonArgs {
|
|
|
41
67
|
/// daemon 主循环(`main`,`__main__.py:25-98`)。写 pid/meta(source=boot)、装信号→STOP、孤儿自检、
|
|
42
68
|
/// catch-all + 指数退避 + tick_error 去重/抑制、tick_recovered 重置、`result.stop || once` → break。
|
|
43
69
|
/// §10:返 `Result`(顶层 bin 用 anyhow 收;§12 边界)。
|
|
70
|
+
/// ---
|
|
71
|
+
/// purpose: daemon 进程入口——解析 transport 后端、构造 Coordinator、跑主循环直到 stop 或 --once
|
|
72
|
+
/// params:
|
|
73
|
+
/// args: workspace(已 resolve)、once、tick_interval_sec、可选 team_key;team_key 为空时回落到 state.active_team_key
|
|
74
|
+
/// returns: 主循环正常退出(收到 STOP、tick 报 stop、或 --once 跑完一轮)时为 Ok
|
|
75
|
+
/// errors: 写 pid/metadata、事件日志或 tick 间隔解析失败时返回 DaemonError
|
|
76
|
+
/// ---
|
|
44
77
|
pub fn run_daemon(args: DaemonArgs) -> Result<(), DaemonError> {
|
|
45
78
|
// CP-1: the daemon's whole tick surface (has_session / capture / inject / list_windows / kill)
|
|
46
79
|
// runs through this backend. Prefer the persisted runtime endpoint so attached explicit-socket
|
|
@@ -123,6 +156,14 @@ pub fn run_daemon(args: DaemonArgs) -> Result<(), DaemonError> {
|
|
|
123
156
|
run_daemon_with_coordinator_and_boot_tmux(&args, &coordinator, Some(tmux_metadata))
|
|
124
157
|
}
|
|
125
158
|
|
|
159
|
+
/// ---
|
|
160
|
+
/// purpose: 用调用方已装配好的 Coordinator 跑主循环(测试与内部复用入口,跳过 transport 解析)
|
|
161
|
+
/// params:
|
|
162
|
+
/// args: 与 run_daemon 同一份参数
|
|
163
|
+
/// coordinator: 已注入 provider registry 与 transport 的实例
|
|
164
|
+
/// returns: 与 run_daemon 相同的主循环退出语义
|
|
165
|
+
/// errors: 同 run_daemon
|
|
166
|
+
/// ---
|
|
126
167
|
pub(crate) fn run_daemon_with_coordinator(
|
|
127
168
|
args: &DaemonArgs,
|
|
128
169
|
coordinator: &Coordinator,
|
|
@@ -520,6 +561,13 @@ fn current_ppid() -> u32 {
|
|
|
520
561
|
|
|
521
562
|
/// 计算 tick 间隔(`_tick_interval`,`__main__.py:104-115`)。读 spec `runtime.tick_interval_sec`,
|
|
522
563
|
/// 缺失/出错 → `DEFAULT_TICK_INTERVAL_SEC`;并确保 schema 存在(`MessageStore(workspace)`)。
|
|
564
|
+
/// ---
|
|
565
|
+
/// purpose: 取本 workspace 的 tick 间隔秒数,并顺带确保 message store schema 可打开
|
|
566
|
+
/// params:
|
|
567
|
+
/// workspace: 已 resolve 的 workspace 根
|
|
568
|
+
/// returns: tick 间隔秒数;当前实现恒为 DEFAULT_TICK_INTERVAL_SEC(spec 覆盖尚未接线)
|
|
569
|
+
/// errors: MessageStore::open 失败时返回 TickError
|
|
570
|
+
/// ---
|
|
523
571
|
pub(super) fn resolve_tick_interval(workspace: &WorkspacePath) -> Result<f64, TickError> {
|
|
524
572
|
let _ = MessageStore::open(workspace.as_path())?;
|
|
525
573
|
Ok(DEFAULT_TICK_INTERVAL_SEC)
|
|
@@ -527,6 +575,13 @@ pub(super) fn resolve_tick_interval(workspace: &WorkspacePath) -> Result<f64, Ti
|
|
|
527
575
|
|
|
528
576
|
/// 退避序列(`__main__.py:65`):`min(interval * 2^min(failures-1, 5), 60.0)` → 5→10→20→40→60→60s。
|
|
529
577
|
/// unit test 锁死本序列(card §85)。**纯函数,无 I/O,可直接 impl 钉死**(但 ROUND-0 仍占位)。
|
|
578
|
+
/// ---
|
|
579
|
+
/// purpose: 由连续失败次数算出本轮该睡多少秒的纯函数
|
|
580
|
+
/// params:
|
|
581
|
+
/// interval: 基准 tick 间隔秒数
|
|
582
|
+
/// consecutive_failures: 至今连续失败次数;0 与 1 同视为首次失败
|
|
583
|
+
/// returns: interval * 2^min(failures-1, 5),上限 BACKOFF_MAX_SEC
|
|
584
|
+
/// ---
|
|
530
585
|
pub(super) fn backoff_sleep_sec(interval: f64, consecutive_failures: u32) -> f64 {
|
|
531
586
|
let failures = consecutive_failures.saturating_sub(1).min(5);
|
|
532
587
|
let exp = i32::try_from(failures).unwrap_or(5);
|
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: Windows ConPTY shim 的生命周期主人——定位并启动 windows-shim.exe、做 Hello 握手、把可复用的连接与 pid/pipe 名记进 state
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: ensure_shim_running
|
|
6
|
+
//! what: 幂等入口:state 里记的 shim 还活着就重连,否则新起一个并握手
|
|
7
|
+
//! - name: reconnect_recorded_shim
|
|
8
|
+
//! what: 只按 state 记的 pipe 名重连并重做 Hello,绝不新起进程
|
|
9
|
+
//! - name: recorded_shim_pid
|
|
10
|
+
//! what: 读 state.transport.shim.pid,供停机路由
|
|
11
|
+
//! - name: recorded_shim_pipe_name
|
|
12
|
+
//! what: 读 state.transport.shim.pipe_name,供重连路由
|
|
13
|
+
//! - name: mark_transport_unavailable
|
|
14
|
+
//! what: shim 不可达时发 stale 家族事件并清掉 state 里的 pipe_ready
|
|
15
|
+
//! depends:
|
|
16
|
+
//! - conpty_transport
|
|
17
|
+
//! - crate::state::repository
|
|
18
|
+
//! - crate::state::persist
|
|
19
|
+
//! - crate::event_log
|
|
20
|
+
//! - crate::platform::process
|
|
21
|
+
//! boundary:
|
|
22
|
+
//! - pipe_token 绝不落 state:只经子进程环境变量传递并留在本进程内存里,state 只记 pid/pipe_name/pipe_ready
|
|
23
|
+
//! - 终态失败不静默回落 tmux,交由调用方按传输不可用处理
|
|
24
|
+
//! - 重连只证明线协议可用,不做新的令牌认证
|
|
25
|
+
//! - 整个文件仅在 Windows 编译;非 Windows 调用方必须自己 cfg 门控
|
|
26
|
+
//! maturity: wired
|
|
27
|
+
//! ---
|
|
1
28
|
//!
|
|
2
29
|
//! Windows ConPTY shim lifecycle manager.
|
|
3
30
|
//!
|
|
@@ -75,16 +102,28 @@ pub struct ShimHandle {
|
|
|
75
102
|
impl ShimHandle {
|
|
76
103
|
/// The connected pipe client, ready for the factory to wire into
|
|
77
104
|
/// `ConPtyBackend`. Callable exactly once — takes ownership.
|
|
105
|
+
/// ---
|
|
106
|
+
/// purpose: 取走已完成 Hello 握手的管道客户端,交给传输后端接线
|
|
107
|
+
/// returns: 首次调用返回客户端并转移所有权;再次调用为 None
|
|
108
|
+
/// ---
|
|
78
109
|
pub fn take_client(&mut self) -> Option<NamedPipeClient> {
|
|
79
110
|
self.client.take()
|
|
80
111
|
}
|
|
81
112
|
|
|
82
113
|
/// The shim pid (for status + shutdown routing).
|
|
114
|
+
/// ---
|
|
115
|
+
/// purpose: 给出 shim 进程号,用于状态展示与停机路由
|
|
116
|
+
/// returns: 启动时记下的 pid;子进程句柄已被 detach 或 drop 后该值仍可读
|
|
117
|
+
/// ---
|
|
83
118
|
pub fn pid(&self) -> u32 {
|
|
84
119
|
self.pid
|
|
85
120
|
}
|
|
86
121
|
|
|
87
122
|
/// The pipe name the shim listens on.
|
|
123
|
+
/// ---
|
|
124
|
+
/// purpose: 给出 shim 正在监听的命名管道名
|
|
125
|
+
/// returns: 形如 team-agent-conpty-<hash>-<team> 的管道全名
|
|
126
|
+
/// ---
|
|
88
127
|
pub fn pipe_name(&self) -> &str {
|
|
89
128
|
&self.pipe_name
|
|
90
129
|
}
|
|
@@ -95,6 +134,11 @@ impl ShimHandle {
|
|
|
95
134
|
/// an explicit `platform::process::terminate_pid` via
|
|
96
135
|
/// `recorded_shim_pid`. Without this call, going out of scope
|
|
97
136
|
/// would terminate the shim and orphan every worker.
|
|
137
|
+
/// ---
|
|
138
|
+
/// purpose: 放弃对 shim 子进程的所有权,使本句柄析构时不再杀它
|
|
139
|
+
/// returns: shim 的 pid,供调用方日后按精确 pid 显式终止
|
|
140
|
+
/// ---
|
|
141
|
+
/// 不调用它就让句柄离开作用域,会连带杀掉 shim 并使全部 worker 变成孤儿。
|
|
98
142
|
pub fn detach(mut self) -> u32 {
|
|
99
143
|
// Forget the child so its own Drop doesn't run either. The
|
|
100
144
|
// shim survives until an explicit kill.
|
|
@@ -453,6 +497,12 @@ fn finalize(
|
|
|
453
497
|
/// `None` when no shim is currently registered (Unix / never-launched
|
|
454
498
|
/// Windows worker). Callers use `platform::process::terminate_pid`
|
|
455
499
|
/// on the returned pid.
|
|
500
|
+
/// ---
|
|
501
|
+
/// purpose: 从 state 读出已登记的 shim 进程号
|
|
502
|
+
/// params:
|
|
503
|
+
/// workspace: workspace 根
|
|
504
|
+
/// returns: state.transport.shim.pid 存在且能转成 u32 时给值;没有登记过 shim(未起过或非 Windows 队)时为 None。只读状态,不做存活探测
|
|
505
|
+
/// ---
|
|
456
506
|
pub fn recorded_shim_pid(workspace: &Path) -> Option<u32> {
|
|
457
507
|
let state = crate::state::repository::StateRepository::new(workspace)
|
|
458
508
|
.load_workspace_if_exists_without_migrations()
|
|
@@ -466,6 +516,12 @@ pub fn recorded_shim_pid(workspace: &Path) -> Option<u32> {
|
|
|
466
516
|
}
|
|
467
517
|
|
|
468
518
|
/// Read `state.transport.shim.pipe_name` for reconnect routing.
|
|
519
|
+
/// ---
|
|
520
|
+
/// purpose: 从 state 读出已登记的 shim 管道名
|
|
521
|
+
/// params:
|
|
522
|
+
/// workspace: workspace 根
|
|
523
|
+
/// returns: state.transport.shim.pipe_name 的字符串值;未登记时为 None
|
|
524
|
+
/// ---
|
|
469
525
|
pub fn recorded_shim_pipe_name(workspace: &Path) -> Option<String> {
|
|
470
526
|
let state = crate::state::repository::StateRepository::new(workspace)
|
|
471
527
|
.load_workspace_if_exists_without_migrations()
|
|
@@ -497,6 +553,15 @@ pub fn recorded_shim_pipe_name(workspace: &Path) -> Option<String> {
|
|
|
497
553
|
/// This gives the "coord can die, shim survives" invariant: a fresh
|
|
498
554
|
/// coord that finds a live shim just reconnects; a fresh coord that
|
|
499
555
|
/// finds no shim spawns one.
|
|
556
|
+
/// ---
|
|
557
|
+
/// purpose: 保证本 (workspace, team) 有一个可用的 shim——这是 coordinator 侧唯一的 shim 所有权入口
|
|
558
|
+
/// params:
|
|
559
|
+
/// workspace: workspace 根
|
|
560
|
+
/// team_key: 目标 team,进 Hello 请求的作用域
|
|
561
|
+
/// workspace_hash: 管道名里用的 workspace 摘要
|
|
562
|
+
/// returns: 已登记 pid 还活着且重连成功 → 复用原进程的句柄(不拥有子进程);否则新起一个并握手,返回拥有子进程的句柄
|
|
563
|
+
/// errors: 找不到 shim 可执行文件、spawn 失败、连接超时、Hello 失败或落盘 state 失败时返回对应 ShimError。重连失败不算错误,会直接落到新起那条路
|
|
564
|
+
/// ---
|
|
500
565
|
pub fn ensure_shim_running(
|
|
501
566
|
workspace: &Path,
|
|
502
567
|
team_key: &str,
|
|
@@ -541,6 +606,15 @@ pub fn ensure_shim_running(
|
|
|
541
606
|
/// Handle returned from this fn has no owned child (the shim is
|
|
542
607
|
/// somebody else's — usually a previous coord instance). `detach()`
|
|
543
608
|
/// on the handle is a no-op for the child.
|
|
609
|
+
/// ---
|
|
610
|
+
/// purpose: 只重连 state 里已登记的 shim 并重做 Hello,用来证明线协议仍然可用
|
|
611
|
+
/// params:
|
|
612
|
+
/// workspace: workspace 根
|
|
613
|
+
/// team_key: 目标 team,进 Hello 请求的作用域
|
|
614
|
+
/// _workspace_hash: 未使用;管道名直接取自 state,不再重算
|
|
615
|
+
/// returns: 连上且 Hello 回 ok 时给出不拥有子进程的句柄,对它调 detach 对子进程是空操作
|
|
616
|
+
/// errors: state 里没有管道名或多次连接均失败 → ConnectTimeout;连上但 Hello 回 ok=false → HelloFailed。本函数绝不新起进程
|
|
617
|
+
/// ---
|
|
544
618
|
pub fn reconnect_recorded_shim(
|
|
545
619
|
workspace: &Path,
|
|
546
620
|
team_key: &str,
|
|
@@ -630,6 +704,14 @@ fn reconnect_hello(client: &mut NamedPipeClient, team_key: &str) -> Result<(), S
|
|
|
630
704
|
/// The Windows-only `#[cfg]` gate is at the mod level (see
|
|
631
705
|
/// `coordinator/mod.rs`); on Unix this file isn't compiled, so
|
|
632
706
|
/// downstream callers must cfg-gate their reference themselves.
|
|
707
|
+
/// ---
|
|
708
|
+
/// purpose: 把「shim 不可达」这件事诚实地写出去——发 stale 家族事件,并清掉 state 里的就绪位
|
|
709
|
+
/// params:
|
|
710
|
+
/// workspace: workspace 根
|
|
711
|
+
/// reason: 不可达原因,同时进事件与 state.transport.shim.unavailable_reason
|
|
712
|
+
/// returns: 状态里本来就没有 shim 块时也返回 Ok。事件写入是尽力而为,写不出去不影响返回;清状态才是权威动作
|
|
713
|
+
/// errors: 读写 state 失败时返回 StateError;state JSON 损坏时按空状态继续
|
|
714
|
+
/// ---
|
|
633
715
|
pub fn mark_transport_unavailable(workspace: &Path, reason: &str) -> Result<(), StateError> {
|
|
634
716
|
// Best-effort event emission — a failed event write should not
|
|
635
717
|
// fail the caller. The state-clearing step below is authoritative.
|