@team-agent/installer 0.5.61 → 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 +5 -2
- package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
- package/crates/team-agent/src/cli/diagnose.rs +1 -0
- package/crates/team-agent/src/cli/emit.rs +41 -7
- package/crates/team-agent/src/cli/helpers.rs +1 -0
- package/crates/team-agent/src/cli/leader.rs +6 -1
- package/crates/team-agent/src/cli/leaders.rs +2 -0
- package/crates/team-agent/src/cli/mod.rs +33 -3
- package/crates/team-agent/src/cli/named_address.rs +8 -1
- 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 +1 -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 +1 -0
- 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/tests/verb_profile.rs +38 -0
- package/crates/team-agent/src/cli/types.rs +3 -2
- package/crates/team-agent/src/communication_mode/mod.rs +1 -0
- package/crates/team-agent/src/compiler.rs +1 -0
- 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 +2 -1
- 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/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 +373 -125
- 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 +7 -2
- 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 +3 -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_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 +1 -27
- 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 +3 -3
- 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/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 +4 -3
- package/crates/team-agent/src/mcp_server/tools.rs +1 -0
- package/crates/team-agent/src/mcp_server/types.rs +1 -18
- package/crates/team-agent/src/mcp_server/wire.rs +5 -5
- 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 +4 -3
- package/crates/team-agent/src/messaging/helpers.rs +2 -1
- package/crates/team-agent/src/messaging/leader_channel.rs +1 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +2 -1
- 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 +2 -1
- package/crates/team-agent/src/messaging/results.rs +1 -0
- 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 +41 -8
- package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
- package/crates/team-agent/src/messaging/types.rs +2 -2
- package/crates/team-agent/src/messaging/watchers.rs +19 -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 +1 -0
- package/crates/team-agent/src/model/name_similarity.rs +1 -0
- package/crates/team-agent/src/model/pane_authority_refusal.rs +1 -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 +1 -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 +1 -4
- 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 +1 -0
- package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
- package/crates/team-agent/src/provider/session_scan.rs +1 -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/transport.rs +15 -0
- package/package.json +4 -4
- package/crates/team-agent/src/leader/inject.rs +0 -33
- package/crates/team-agent/src/provider/command.rs +0 -80
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
use super::mailbox::maybe_enqueue_offline_leader_mailbox;
|
|
2
3
|
use super::persist::persist_resolved_target;
|
|
3
4
|
use crate::cli::{CliError, SendArgs};
|
|
@@ -207,7 +208,7 @@ pub(super) fn send_to_logical_to(
|
|
|
207
208
|
&args.workspace,
|
|
208
209
|
name,
|
|
209
210
|
content,
|
|
210
|
-
args.sender.
|
|
211
|
+
args.sender.display_name(),
|
|
211
212
|
args.task.as_deref(),
|
|
212
213
|
&error,
|
|
213
214
|
)? {
|
|
@@ -359,3 +360,207 @@ pub(super) fn send_to_resolved_name(
|
|
|
359
360
|
}
|
|
360
361
|
Ok(value)
|
|
361
362
|
}
|
|
363
|
+
|
|
364
|
+
#[cfg(test)]
|
|
365
|
+
mod a13_red_tests {
|
|
366
|
+
#![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
|
|
367
|
+
|
|
368
|
+
use super::send_to_resolved_name;
|
|
369
|
+
use crate::cli::named_address::{NamedTargetKind, ResolvedNamedAddress};
|
|
370
|
+
use crate::cli::SendArgs;
|
|
371
|
+
use crate::message_store::MessageStore;
|
|
372
|
+
use crate::messaging::TrustedSender;
|
|
373
|
+
use crate::model::ids::AgentId;
|
|
374
|
+
use serde_json::{json, Value};
|
|
375
|
+
use std::path::{Path, PathBuf};
|
|
376
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
377
|
+
|
|
378
|
+
fn fresh_workspace(tag: &str) -> PathBuf {
|
|
379
|
+
static NEXT: AtomicU64 = AtomicU64::new(0);
|
|
380
|
+
let workspace = std::env::temp_dir().join(format!(
|
|
381
|
+
"team-agent-a13-{tag}-{}-{}",
|
|
382
|
+
std::process::id(),
|
|
383
|
+
NEXT.fetch_add(1, Ordering::Relaxed)
|
|
384
|
+
));
|
|
385
|
+
let _ = std::fs::remove_dir_all(&workspace);
|
|
386
|
+
std::fs::create_dir_all(workspace.join(".team/runtime")).expect("create fixture");
|
|
387
|
+
workspace
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
fn seed_team(workspace: &Path, team: &str) {
|
|
391
|
+
let session = format!("team-{team}");
|
|
392
|
+
let state = json!({
|
|
393
|
+
"active_team_key": team,
|
|
394
|
+
"status": "alive",
|
|
395
|
+
"session_name": session,
|
|
396
|
+
"agents": {},
|
|
397
|
+
"teams": {
|
|
398
|
+
team: {
|
|
399
|
+
"status": "alive",
|
|
400
|
+
"session_name": session,
|
|
401
|
+
"agents": {},
|
|
402
|
+
"leader_receiver": {"status": "unbound"}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
crate::state::persist::save_runtime_state(workspace, &state).expect("save fixture state");
|
|
407
|
+
MessageStore::open(workspace).expect("create fixture store");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
fn send_args(workspace: &Path) -> SendArgs {
|
|
411
|
+
SendArgs {
|
|
412
|
+
target: None,
|
|
413
|
+
message: vec!["payload".to_string()],
|
|
414
|
+
targets: None,
|
|
415
|
+
workspace: workspace.to_path_buf(),
|
|
416
|
+
team: None,
|
|
417
|
+
task: None,
|
|
418
|
+
sender: TrustedSender::from_runtime_identity_with_source(
|
|
419
|
+
AgentId::new("worker-1"),
|
|
420
|
+
workspace.display(),
|
|
421
|
+
"sender-team",
|
|
422
|
+
),
|
|
423
|
+
no_ack: false,
|
|
424
|
+
no_wait: true,
|
|
425
|
+
watch_result: false,
|
|
426
|
+
timeout: 0.0,
|
|
427
|
+
confirm_human: false,
|
|
428
|
+
json: true,
|
|
429
|
+
message_id: None,
|
|
430
|
+
presentation: crate::messaging::presentation::PresentationRequest::default(),
|
|
431
|
+
pane: None,
|
|
432
|
+
to_name: Some("other-team/leader".to_string()),
|
|
433
|
+
to_leader: None,
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
fn leader_address(sender_workspace: &Path, target_workspace: &Path) -> ResolvedNamedAddress {
|
|
438
|
+
ResolvedNamedAddress {
|
|
439
|
+
raw_name: format!("{}::other-team/leader", target_workspace.display()),
|
|
440
|
+
target_kind: NamedTargetKind::Leader,
|
|
441
|
+
sender_workspace: sender_workspace.to_path_buf(),
|
|
442
|
+
target_workspace: target_workspace.to_path_buf(),
|
|
443
|
+
team_key: Some("other-team".to_string()),
|
|
444
|
+
agent_id: None,
|
|
445
|
+
pane_id: String::new(),
|
|
446
|
+
session_name: Some("team-other-team".to_string()),
|
|
447
|
+
window_name: None,
|
|
448
|
+
tmux_endpoint: None,
|
|
449
|
+
transport_kind: Some("direct_tmux".to_string()),
|
|
450
|
+
app_server: None,
|
|
451
|
+
state_pane_id: None,
|
|
452
|
+
state_pane_stale: false,
|
|
453
|
+
agent_status: None,
|
|
454
|
+
warning: None,
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
fn message_count(workspace: &Path) -> i64 {
|
|
459
|
+
let store = MessageStore::open(workspace).expect("open fixture store");
|
|
460
|
+
crate::db::schema::open_db(store.db_path())
|
|
461
|
+
.expect("open fixture database")
|
|
462
|
+
.query_row("select count(*) from messages", [], |row| row.get(0))
|
|
463
|
+
.expect("count fixture messages")
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
#[test]
|
|
467
|
+
fn a13_worker_cross_workspace_leader_send_preserves_target_scope() {
|
|
468
|
+
let sender_workspace = fresh_workspace("sender");
|
|
469
|
+
let target_workspace = fresh_workspace("target");
|
|
470
|
+
seed_team(&target_workspace, "other-team");
|
|
471
|
+
let mut args = send_args(&sender_workspace);
|
|
472
|
+
let to_name = format!("{}::other-team/leader", target_workspace.display());
|
|
473
|
+
args.to_name = Some(to_name.clone());
|
|
474
|
+
let value = super::send_to_logical_to(&args, &to_name, "payload")
|
|
475
|
+
.expect("cross-workspace leader send must preserve a structured outcome");
|
|
476
|
+
assert_eq!(
|
|
477
|
+
value["to_name"],
|
|
478
|
+
json!(to_name),
|
|
479
|
+
"cross-workspace receipt must retain the original target: {value}"
|
|
480
|
+
);
|
|
481
|
+
assert_eq!(
|
|
482
|
+
value["sender"],
|
|
483
|
+
json!(format!(
|
|
484
|
+
"{}::sender-team/worker-1",
|
|
485
|
+
sender_workspace.display()
|
|
486
|
+
)),
|
|
487
|
+
"cross-workspace receipt must retain the full sender: {value}"
|
|
488
|
+
);
|
|
489
|
+
assert_eq!(
|
|
490
|
+
message_count(&sender_workspace),
|
|
491
|
+
0,
|
|
492
|
+
"cross-workspace send must not fold into the sender workspace"
|
|
493
|
+
);
|
|
494
|
+
assert_eq!(
|
|
495
|
+
message_count(&target_workspace),
|
|
496
|
+
1,
|
|
497
|
+
"cross-workspace send must persist in the target workspace"
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
let _ = std::fs::remove_dir_all(sender_workspace);
|
|
501
|
+
let _ = std::fs::remove_dir_all(target_workspace);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
#[test]
|
|
505
|
+
fn a13_worker_cross_workspace_identity_is_preserved_in_row_and_render() {
|
|
506
|
+
let sender_workspace = fresh_workspace("identity-sender");
|
|
507
|
+
let target_workspace = fresh_workspace("identity-target");
|
|
508
|
+
seed_team(&target_workspace, "other-team");
|
|
509
|
+
let mut args = send_args(&sender_workspace);
|
|
510
|
+
args.to_name = Some(format!("{}::other-team/leader", target_workspace.display()));
|
|
511
|
+
let resolved = leader_address(&sender_workspace, &target_workspace);
|
|
512
|
+
|
|
513
|
+
send_to_resolved_name(&args, &resolved, "identity payload")
|
|
514
|
+
.expect("cross-workspace identity send must persist");
|
|
515
|
+
let store = MessageStore::open(&target_workspace).expect("open target store");
|
|
516
|
+
let connection = crate::db::schema::open_db(store.db_path()).expect("open target db");
|
|
517
|
+
let sender: String = connection
|
|
518
|
+
.query_row("select sender from messages limit 1", [], |row| row.get(0))
|
|
519
|
+
.expect("read sender");
|
|
520
|
+
let expected = format!("{}::sender-team/worker-1", sender_workspace.display());
|
|
521
|
+
assert_eq!(
|
|
522
|
+
sender, expected,
|
|
523
|
+
"durable row must retain the full sender identity"
|
|
524
|
+
);
|
|
525
|
+
let rendered = crate::messaging::delivery::render_message(
|
|
526
|
+
&sender,
|
|
527
|
+
None,
|
|
528
|
+
"identity payload",
|
|
529
|
+
"msg-a13",
|
|
530
|
+
);
|
|
531
|
+
assert!(
|
|
532
|
+
rendered.contains(&expected),
|
|
533
|
+
"rendered pane must show full sender: {rendered}"
|
|
534
|
+
);
|
|
535
|
+
assert!(
|
|
536
|
+
!rendered.contains("from leader"),
|
|
537
|
+
"worker must not render as leader: {rendered}"
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
let _ = std::fs::remove_dir_all(sender_workspace);
|
|
541
|
+
let _ = std::fs::remove_dir_all(target_workspace);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
#[test]
|
|
545
|
+
fn a13_worker_in_workspace_leader_send_remains_allowed() {
|
|
546
|
+
let workspace = fresh_workspace("in-workspace");
|
|
547
|
+
seed_team(&workspace, "other-team");
|
|
548
|
+
let args = send_args(&workspace);
|
|
549
|
+
let resolved = leader_address(&workspace, &workspace);
|
|
550
|
+
|
|
551
|
+
let value = send_to_resolved_name(&args, &resolved, "payload")
|
|
552
|
+
.expect("in-workspace leader send should preserve the existing path");
|
|
553
|
+
assert_ne!(
|
|
554
|
+
value["reason"],
|
|
555
|
+
Value::String("target_out_of_scope".to_string()),
|
|
556
|
+
"in-workspace send must not be classified as cross-workspace: {value}"
|
|
557
|
+
);
|
|
558
|
+
assert_eq!(
|
|
559
|
+
message_count(&workspace),
|
|
560
|
+
1,
|
|
561
|
+
"allowed send must persist once"
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
let _ = std::fs::remove_dir_all(workspace);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! cli · send — `cmd_send` + target 解析(`_send_target`)+ `SendArgs`→`SendOptions` 翻译
|
|
2
3
|
//! (`send_options_from_args`,旗标取反语义)。
|
|
3
4
|
|
|
@@ -180,7 +181,7 @@ pub fn send_to_canonical_leader_target(
|
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
/// `_send_target`(`commands.py:181-184`):`--to` comma-split fanout / `target` 单值 / None。
|
|
183
|
-
pub fn send_target(targets: Option<&str>, target: Option<&str>) -> MessageTarget {
|
|
184
|
+
pub(super) fn send_target(targets: Option<&str>, target: Option<&str>) -> MessageTarget {
|
|
184
185
|
if let Some(targets) = targets.filter(|s| !s.is_empty()) {
|
|
185
186
|
let recipients: Vec<String> = targets
|
|
186
187
|
.split(',')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! cli · status — 五行 triage summary 渲染 + agent 分类计数(commands.py
|
|
2
3
|
//! `_format_status_summary` / `_agent_summary_counts` / `_interaction_counts` /
|
|
3
4
|
//! `_latest_result_line` + `cmd_status` 的 compact 不变量)。
|
|
@@ -100,7 +101,7 @@ fn stale_agent_bucket(agent: &Value) -> Option<SummaryBucket> {
|
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
/// `_interaction_counts`(`commands.py:292-306`):遍历 agents 的 `interacted` 字段。
|
|
103
|
-
pub fn interaction_counts(agents: &Value) -> InteractionCounts {
|
|
104
|
+
pub(super) fn interaction_counts(agents: &Value) -> InteractionCounts {
|
|
104
105
|
let mut counts = InteractionCounts::default();
|
|
105
106
|
let Some(agent_map) = agents.as_object() else {
|
|
106
107
|
return counts;
|
|
@@ -122,6 +122,44 @@ fn profile_init_routes_and_creates_secret_boundary_files() {
|
|
|
122
122
|
let _ = std::fs::remove_dir_all(&ws);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
#[test]
|
|
126
|
+
fn compatible_api_profile_init_template_contains_common_five_fields() {
|
|
127
|
+
let ws = tmp_workspace();
|
|
128
|
+
let code = run(
|
|
129
|
+
&profile_argv(&[
|
|
130
|
+
"profile",
|
|
131
|
+
"init",
|
|
132
|
+
"compat",
|
|
133
|
+
"--workspace",
|
|
134
|
+
".",
|
|
135
|
+
"--auth-mode",
|
|
136
|
+
"compatible_api",
|
|
137
|
+
"--json",
|
|
138
|
+
]),
|
|
139
|
+
&ws,
|
|
140
|
+
);
|
|
141
|
+
assert_eq!(
|
|
142
|
+
code,
|
|
143
|
+
ExitCode::Ok,
|
|
144
|
+
"compatible_api profile init must route and exit 0"
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
let expected =
|
|
148
|
+
"AUTH_MODE=compatible_api\nPROFILE_NAME=compat\nBASE_URL=\nAPI_KEY=\nMODEL=\n";
|
|
149
|
+
let dir = profiles_dir(&ws);
|
|
150
|
+
assert_eq!(
|
|
151
|
+
std::fs::read_to_string(dir.join("compat.env")).unwrap(),
|
|
152
|
+
expected,
|
|
153
|
+
"compatible_api profile must expose the documented five-field template"
|
|
154
|
+
);
|
|
155
|
+
assert_eq!(
|
|
156
|
+
std::fs::read_to_string(dir.join("compat.example.env")).unwrap(),
|
|
157
|
+
expected,
|
|
158
|
+
"compatible_api example must expose the documented five-field template"
|
|
159
|
+
);
|
|
160
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
161
|
+
}
|
|
162
|
+
|
|
125
163
|
// Golden source:
|
|
126
164
|
// - profiles/core.py:95-119 doctor_profile returns ok=true for existing profiles and
|
|
127
165
|
// ok=false for missing profiles; parser.py:506-508 maps result.ok false to exit 1.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! cli · types — 错误信封 / 退出码 / 命令结果载体 / leader passthrough 参数 /
|
|
2
3
|
//! 每子命令的 clap-style arg 结构 / 五行 summary 计数桶。
|
|
3
4
|
|
|
@@ -13,7 +14,7 @@ use crate::transport::PaneId;
|
|
|
13
14
|
/// `{ok:false, error, action, log, reason?, session_name?, next_actions?}`(字节级保留)。
|
|
14
15
|
/// 人读时打 `error:`/`action:`/`log:` 三行到 stderr(`helpers.py:132-134`)。
|
|
15
16
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
16
|
-
pub struct CliErrorPayload {
|
|
17
|
+
pub(crate) struct CliErrorPayload {
|
|
17
18
|
/// 恒 `false`(错误信封)。
|
|
18
19
|
pub ok: bool,
|
|
19
20
|
pub error: String,
|
|
@@ -70,7 +71,7 @@ impl From<crate::messaging::MessagingError> for CliError {
|
|
|
70
71
|
impl CliError {
|
|
71
72
|
/// `_cli_error_payload`(`helpers.py:137-155`):落盘日志后构造稳定信封。tmux session
|
|
72
73
|
/// 冲突时按 command 富化 reason/session_name/next_actions(`helpers.py:158-187`)。
|
|
73
|
-
pub fn to_payload(&self, log_path: &Path, command: &str) -> CliErrorPayload {
|
|
74
|
+
pub(crate) fn to_payload(&self, log_path: &Path, command: &str) -> CliErrorPayload {
|
|
74
75
|
let error = self.to_string();
|
|
75
76
|
let mut payload = CliErrorPayload {
|
|
76
77
|
ok: false,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! daemon 主循环面(`__main__.py`)—— 退避序列 + tick 间隔解析 + 子进程入口。
|
|
2
3
|
|
|
3
4
|
use thiserror::Error;
|
|
@@ -10,7 +11,7 @@ use crate::provider::ProviderAdapter;
|
|
|
10
11
|
use super::health::{coordinator_pid_path, write_coordinator_metadata};
|
|
11
12
|
use super::tick::{write_coordinator_heartbeat, TickError, TickReport, HEARTBEAT_STATUS_PANIC};
|
|
12
13
|
use super::types::{
|
|
13
|
-
|
|
14
|
+
MetadataSource, Pid, ProviderRegistry, WorkspacePath, BACKOFF_MAX_SEC,
|
|
14
15
|
DEFAULT_TICK_INTERVAL_SEC,
|
|
15
16
|
};
|
|
16
17
|
use super::Coordinator;
|
|
@@ -122,7 +123,7 @@ pub fn run_daemon(args: DaemonArgs) -> Result<(), DaemonError> {
|
|
|
122
123
|
run_daemon_with_coordinator_and_boot_tmux(&args, &coordinator, Some(tmux_metadata))
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
pub fn run_daemon_with_coordinator(
|
|
126
|
+
pub(crate) fn run_daemon_with_coordinator(
|
|
126
127
|
args: &DaemonArgs,
|
|
127
128
|
coordinator: &Coordinator,
|
|
128
129
|
) -> Result<(), DaemonError> {
|
|
@@ -519,14 +520,14 @@ fn current_ppid() -> u32 {
|
|
|
519
520
|
|
|
520
521
|
/// 计算 tick 间隔(`_tick_interval`,`__main__.py:104-115`)。读 spec `runtime.tick_interval_sec`,
|
|
521
522
|
/// 缺失/出错 → `DEFAULT_TICK_INTERVAL_SEC`;并确保 schema 存在(`MessageStore(workspace)`)。
|
|
522
|
-
pub fn resolve_tick_interval(workspace: &WorkspacePath) -> Result<f64, TickError> {
|
|
523
|
+
pub(super) fn resolve_tick_interval(workspace: &WorkspacePath) -> Result<f64, TickError> {
|
|
523
524
|
let _ = MessageStore::open(workspace.as_path())?;
|
|
524
525
|
Ok(DEFAULT_TICK_INTERVAL_SEC)
|
|
525
526
|
}
|
|
526
527
|
|
|
527
528
|
/// 退避序列(`__main__.py:65`):`min(interval * 2^min(failures-1, 5), 60.0)` → 5→10→20→40→60→60s。
|
|
528
529
|
/// unit test 锁死本序列(card §85)。**纯函数,无 I/O,可直接 impl 钉死**(但 ROUND-0 仍占位)。
|
|
529
|
-
pub fn backoff_sleep_sec(interval: f64, consecutive_failures: u32) -> f64 {
|
|
530
|
+
pub(super) fn backoff_sleep_sec(interval: f64, consecutive_failures: u32) -> f64 {
|
|
530
531
|
let failures = consecutive_failures.saturating_sub(1).min(5);
|
|
531
532
|
let exp = i32::try_from(failures).unwrap_or(5);
|
|
532
533
|
(interval * 2f64.powi(exp)).min(BACKOFF_MAX_SEC)
|
|
@@ -538,10 +539,6 @@ impl ProviderRegistry for RealProviderRegistry {
|
|
|
538
539
|
fn adapter_for(&self, provider: Provider) -> Box<dyn ProviderAdapter> {
|
|
539
540
|
crate::provider::get_adapter(provider)
|
|
540
541
|
}
|
|
541
|
-
|
|
542
|
-
fn error_lists(&self, _provider: Provider) -> ErrorLists {
|
|
543
|
-
ErrorLists::default()
|
|
544
|
-
}
|
|
545
542
|
}
|
|
546
543
|
|
|
547
544
|
fn sleep_seconds(seconds: f64) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! Windows ConPTY shim lifecycle manager.
|
|
2
3
|
//!
|
|
3
4
|
//! 0.5.x Windows portability Batch 6 Option A. The design's
|
|
@@ -160,7 +161,7 @@ pub enum ShimError {
|
|
|
160
161
|
/// installations.
|
|
161
162
|
/// 2. Env override `TEAM_AGENT_WINDOWS_SHIM_PATH` — tests + CI
|
|
162
163
|
/// harness (Batch 6 SSH real-machine harness sets this).
|
|
163
|
-
|
|
164
|
+
fn locate_shim_binary() -> Result<PathBuf, ShimError> {
|
|
164
165
|
if let Ok(explicit) = std::env::var("TEAM_AGENT_WINDOWS_SHIM_PATH") {
|
|
165
166
|
let p = PathBuf::from(&explicit);
|
|
166
167
|
if p.exists() {
|
|
@@ -218,7 +219,7 @@ fn fresh_pipe_token() -> String {
|
|
|
218
219
|
/// downstream `transport_factory::conpty_pipe_ready(...)` opens on
|
|
219
220
|
/// the next factory resolve. The `pipe_token` is NOT persisted (CR
|
|
220
221
|
/// C-1); it stays in `ShimHandle` memory only.
|
|
221
|
-
|
|
222
|
+
fn spawn_shim_and_handshake(
|
|
222
223
|
workspace: &Path,
|
|
223
224
|
team_key: &str,
|
|
224
225
|
workspace_hash: &str,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! step 12 · coordinator — daemon lifecycle / single-tick orchestration SKELETON (ROUND-0).
|
|
2
3
|
//!
|
|
3
4
|
//! Card: `docs/phase0/subsystems/12-coordinator.md`.
|
|
@@ -10,8 +11,7 @@
|
|
|
10
11
|
//! - `coordinator/metadata.py` (COORDINATOR_PROTOCOL_VERSION=2, pid_is_running, read/write/ok meta)
|
|
11
12
|
//! - `coordinator/paths.py` (coordinator.pid / coordinator.json / coordinator.log paths)
|
|
12
13
|
//! - `watch/__init__.py` (run_watch / collect_watch_lines / render_event_line / WatchCursor)
|
|
13
|
-
//! - `abnormal_track.py` (Gap 32 §4 provider-neutral
|
|
14
|
-
//! process_abnormal_records / detect_whole_team_gone)
|
|
14
|
+
//! - `abnormal_track.py` (Gap 32 §4 provider-neutral whole-team-gone detection)
|
|
15
15
|
//!
|
|
16
16
|
//! 职责(card §职责):per-workspace daemon 生命周期 + 单次 tick 编排。tick 按固定顺序把
|
|
17
17
|
//! step 8-11 的原子操作串成一个只读 + 投递既定 obligation 的回路 —— **绝不**在无 pending
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
//!
|
|
2
|
-
|
|
3
|
-
use std::hash::{Hash, Hasher};
|
|
4
|
-
|
|
5
|
-
use serde_json::Value;
|
|
6
|
-
|
|
7
|
-
use crate::model::enums::Provider;
|
|
8
|
-
use crate::provider::{ProcessLiveness, Signature, TurnId, TurnState};
|
|
1
|
+
//!
|
|
2
|
+
//! 孤儿自终止判定(Gap 37b)+ provider-neutral 整队消失检测。
|
|
9
3
|
|
|
10
4
|
use super::types::{
|
|
11
|
-
|
|
12
|
-
AbnormalProcessOutput, MarkerStore, ProviderRegistry, TeamPresenceSnapshot, WholeTeamGoneClass,
|
|
13
|
-
WholeTeamGoneReport, WorkspacePath,
|
|
5
|
+
MarkerStore, TeamPresenceSnapshot, WholeTeamGoneClass, WholeTeamGoneReport, WorkspacePath,
|
|
14
6
|
};
|
|
15
7
|
|
|
8
|
+
///
|
|
16
9
|
/// 孤儿自终止判定(`__main__.py:51-59`,Gap 37b)。仅当
|
|
17
10
|
/// `current_ppid != initial_ppid ∧ current_ppid == 1 ∧ !workspace.exists()` 三者**同时**成立 → true。
|
|
18
11
|
/// 少一个条件都不能误杀正常 daemon(card §91)。
|
|
@@ -28,104 +21,7 @@ pub fn should_orphan_self_terminate(
|
|
|
28
21
|
// abnormal_track 公共面(abnormal_track.py)—— Gap 32 §4 provider-neutral
|
|
29
22
|
// ===========================================================================
|
|
30
23
|
|
|
31
|
-
/// #236 dedicated `worker.abnormal_exit` surface.
|
|
32
24
|
///
|
|
33
|
-
/// This is intentionally separate from `process_abnormal_records`: the generic
|
|
34
|
-
/// abnormal path is transcript-only, while `worker.abnormal_exit` requires a fresh
|
|
35
|
-
/// latest explicit provider error before leader notification.
|
|
36
|
-
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
37
|
-
#[serde(tag = "event")]
|
|
38
|
-
pub enum WorkerAbnormalExitSurface {
|
|
39
|
-
#[serde(rename = "worker.abnormal_exit")]
|
|
40
|
-
WorkerAbnormalExit {
|
|
41
|
-
agent_id: String,
|
|
42
|
-
provider: Provider,
|
|
43
|
-
path: String,
|
|
44
|
-
provider_process_dead: bool,
|
|
45
|
-
latest_explicit_error: bool,
|
|
46
|
-
signature: Signature,
|
|
47
|
-
turn_id: Option<TurnId>,
|
|
48
|
-
process_liveness: ProcessLiveness,
|
|
49
|
-
},
|
|
50
|
-
#[serde(rename = "abnormal_exit.single_signal_suppressed")]
|
|
51
|
-
SingleSignalSuppressed {
|
|
52
|
-
agent_id: String,
|
|
53
|
-
provider: Provider,
|
|
54
|
-
path: String,
|
|
55
|
-
provider_process_dead: bool,
|
|
56
|
-
latest_explicit_error: bool,
|
|
57
|
-
reason: String,
|
|
58
|
-
},
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/// `process_abnormal_records`(`abnormal_track.py:14`)。把原始 provider session records 经
|
|
62
|
-
/// provider reader 翻译成结构化 fault facts(本 module **不命名 provider、不读屏**),catch-bias
|
|
63
|
-
/// (C9)+ `(signature, turn_id|fingerprint)` 去重(C8)。`registry` 携带 provider + 黑白名单。
|
|
64
|
-
pub fn process_abnormal_records(
|
|
65
|
-
records: &[Value],
|
|
66
|
-
registry: &dyn ProviderRegistry,
|
|
67
|
-
provider: Provider,
|
|
68
|
-
notification_state: &AbnormalNotificationState,
|
|
69
|
-
) -> Result<AbnormalProcessOutput, AbnormalError> {
|
|
70
|
-
let lists = registry.error_lists(provider);
|
|
71
|
-
let mut state = notification_state.clone();
|
|
72
|
-
let mut notifications = Vec::new();
|
|
73
|
-
for record in records {
|
|
74
|
-
let raw = record
|
|
75
|
-
.get("raw")
|
|
76
|
-
.and_then(Value::as_str)
|
|
77
|
-
.unwrap_or_else(|| record.as_str().unwrap_or(""))
|
|
78
|
-
.to_string();
|
|
79
|
-
let signature = record
|
|
80
|
-
.get("signature")
|
|
81
|
-
.and_then(Value::as_str)
|
|
82
|
-
.unwrap_or("unknown")
|
|
83
|
-
.to_string();
|
|
84
|
-
let signature_lower = signature.to_lowercase();
|
|
85
|
-
let turn_id = record
|
|
86
|
-
.get("turn_id")
|
|
87
|
-
.and_then(Value::as_str)
|
|
88
|
-
.map(TurnId::new);
|
|
89
|
-
let bucket = match &turn_id {
|
|
90
|
-
Some(id) => id.as_str().to_string(),
|
|
91
|
-
None => stable_fingerprint(record),
|
|
92
|
-
};
|
|
93
|
-
let key = format!("{signature}\0{bucket}");
|
|
94
|
-
if state.seen.contains(&key) {
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
state.seen.insert(key);
|
|
98
|
-
if lists.whitelist.iter().any(|needle| raw.contains(needle)) {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
let decision =
|
|
102
|
-
if lists.blacklist.iter().any(|needle| {
|
|
103
|
-
raw.contains(needle) || signature_lower.contains(&needle.to_lowercase())
|
|
104
|
-
}) {
|
|
105
|
-
AbnormalDecision::NotifyBlacklist
|
|
106
|
-
} else {
|
|
107
|
-
AbnormalDecision::NotifyDefault
|
|
108
|
-
};
|
|
109
|
-
let kind = record.get("kind").and_then(Value::as_str);
|
|
110
|
-
notifications.push(AbnormalNotification {
|
|
111
|
-
signature,
|
|
112
|
-
turn_id,
|
|
113
|
-
state: if kind == Some("approval") {
|
|
114
|
-
TurnState::BlockedOnHuman
|
|
115
|
-
} else {
|
|
116
|
-
TurnState::Abnormal
|
|
117
|
-
},
|
|
118
|
-
decision,
|
|
119
|
-
provider: Some(provider),
|
|
120
|
-
raw: record.clone(),
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
Ok(AbnormalProcessOutput {
|
|
124
|
-
notifications,
|
|
125
|
-
notification_state: state,
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
|
|
129
25
|
/// `detect_whole_team_gone`(`abnormal_track.py:91`,C10/C13)。coordinator-independent 整队消失检测:
|
|
130
26
|
/// 全死(coordinator + leader + 所有 worker + 所有 session)且非 clean_shutdown/restart →
|
|
131
27
|
/// 写 durable marker + 延迟到下条 leader 命令再 escalate。clean/restart 静默。
|
|
@@ -166,14 +62,6 @@ pub fn detect_whole_team_gone(
|
|
|
166
62
|
)
|
|
167
63
|
}
|
|
168
64
|
|
|
169
|
-
fn stable_fingerprint(value: &Value) -> String {
|
|
170
|
-
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
171
|
-
serde_json::to_string(value)
|
|
172
|
-
.unwrap_or_else(|_| value.to_string())
|
|
173
|
-
.hash(&mut hasher);
|
|
174
|
-
format!("{:016x}", hasher.finish())
|
|
175
|
-
}
|
|
176
|
-
|
|
177
65
|
fn whole_team_report(
|
|
178
66
|
whole_team_gone: bool,
|
|
179
67
|
classification: WholeTeamGoneClass,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! unit-11 (Stage 4) — coordinator tick `abnormal` step group.
|
|
2
3
|
//!
|
|
3
4
|
//! Abnormal-exit detection + classification step extracted from
|
|
@@ -15,6 +16,7 @@ use super::super::health::pid_is_running;
|
|
|
15
16
|
use super::super::tick::TickError;
|
|
16
17
|
use super::super::types::Pid;
|
|
17
18
|
|
|
19
|
+
///
|
|
18
20
|
/// #236 `worker.abnormal_exit` watcher.
|
|
19
21
|
///
|
|
20
22
|
/// Notify only when the bounded transcript/rollout tail contains a latest explicit
|
|
@@ -967,6 +969,7 @@ fn abnormal_last_error_observation_cohort(state: &Value, agent_id: &str) -> Opti
|
|
|
967
969
|
/// P1: Python `_TAIL_BYTES` parity (idle_takeover_wiring.py:13) — RS must not read less.
|
|
968
970
|
const ABNORMAL_TAIL_BYTES: u64 = 131_072;
|
|
969
971
|
|
|
972
|
+
///
|
|
970
973
|
/// P1: bounded tail read; a partial first line is harmless (the consumer only parses
|
|
971
974
|
/// the latest complete JSONL record) and lossy UTF-8 keeps a mid-codepoint seek safe.
|
|
972
975
|
pub(crate) fn read_tail_text(path: &Path, max_bytes: u64) -> std::io::Result<String> {
|
|
@@ -1347,6 +1350,7 @@ impl ApiErrorRecoveryClass {
|
|
|
1347
1350
|
}
|
|
1348
1351
|
}
|
|
1349
1352
|
|
|
1353
|
+
///
|
|
1350
1354
|
/// 0.5.36 §7.1: rules keyed on status/error text.
|
|
1351
1355
|
pub(crate) fn classify_api_error_recovery(
|
|
1352
1356
|
signature: &str,
|
|
@@ -1871,6 +1875,7 @@ fn process_api_error_recovery_intent(
|
|
|
1871
1875
|
// path (which owns its own save).
|
|
1872
1876
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1873
1877
|
|
|
1878
|
+
///
|
|
1874
1879
|
/// 0.5.36 §7.3: process all due recovery intents. Called from tick.rs
|
|
1875
1880
|
/// AFTER atomic_save has flushed the detector-written intent. Reloads a
|
|
1876
1881
|
/// fresh state each call so the caller's stale in-memory state cannot
|
|
@@ -2168,7 +2173,7 @@ fn write_recovery_intent_result(workspace: &Path, agent_id: &str, update: Recove
|
|
|
2168
2173
|
mod tests {
|
|
2169
2174
|
use super::*;
|
|
2170
2175
|
use crate::coordinator::tick::Coordinator;
|
|
2171
|
-
use crate::coordinator::types::{
|
|
2176
|
+
use crate::coordinator::types::{ProviderRegistry, WorkspacePath};
|
|
2172
2177
|
use std::io::Write as _;
|
|
2173
2178
|
|
|
2174
2179
|
struct NormalRegistry;
|
|
@@ -2180,10 +2185,6 @@ mod tests {
|
|
|
2180
2185
|
) -> Box<dyn crate::provider::ProviderAdapter> {
|
|
2181
2186
|
crate::provider::get_adapter(provider)
|
|
2182
2187
|
}
|
|
2183
|
-
|
|
2184
|
-
fn error_lists(&self, _provider: crate::provider::Provider) -> ErrorLists {
|
|
2185
|
-
ErrorLists::default()
|
|
2186
|
-
}
|
|
2187
2188
|
}
|
|
2188
2189
|
#[test]
|
|
2189
2190
|
fn abnormal_stale_error_baselines_then_fresh_alive_error_notifies() {
|