@team-agent/installer 0.5.75 → 0.5.77
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/README.md +9 -0
- package/README.zh.md +8 -0
- package/crates/team-agent/src/cli/adapters.rs +23 -96
- package/crates/team-agent/src/cli/emit.rs +35 -7
- package/crates/team-agent/src/cli/mod.rs +63 -42
- package/crates/team-agent/src/cli/models.rs +138 -27
- package/crates/team-agent/src/cli/status_port/brief.rs +1137 -0
- package/crates/team-agent/src/cli/status_port.rs +8 -0
- package/crates/team-agent/src/cli/tests/mod.rs +1 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +29 -24
- package/crates/team-agent/src/cli/tests/status_brief.rs +691 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +8 -2
- package/crates/team-agent/src/leader/lease.rs +46 -8
- package/crates/team-agent/src/leader/types.rs +4 -0
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +81 -4
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +11 -4
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +45 -14
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +1 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +24 -28
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +22 -10
- package/crates/team-agent/src/lifecycle/types.rs +4 -0
- package/crates/team-agent/src/messaging/watchers.rs +48 -4
- package/crates/team-agent/src/state/paths.rs +17 -2
- package/crates/team-agent/src/state/projection.rs +18 -2
- package/crates/team-agent/src/state/selector.rs +23 -3
- package/crates/team-agent/src/test_support/models_timeout_fixture.rs +122 -0
- package/package.json +4 -4
|
@@ -653,12 +653,30 @@ pub fn recover_watchers_for_incident(
|
|
|
653
653
|
/// row for watcher-backed messages. A `submitted_pending_acceptance` row has
|
|
654
654
|
/// already crossed the transport boundary and is claim-immutable. Any future
|
|
655
655
|
/// retry belongs to a separate typed recovery arm, not attach/claim convergence.
|
|
656
|
+
/// Compatibility helper retained for callers that only need the total.
|
|
657
|
+
/// The typed recovery path below is the single writer; this wrapper performs
|
|
658
|
+
/// no second requeue and only maps the first result to `total()`.
|
|
656
659
|
pub(crate) fn requeue_blocked_leader_messages(
|
|
657
660
|
store: &MessageStore,
|
|
658
661
|
event_log: &EventLog,
|
|
659
662
|
owner_team_id: &TeamKey,
|
|
660
663
|
claimed_pane_id: &PaneId,
|
|
661
664
|
) -> Result<usize, MessagingError> {
|
|
665
|
+
requeue_blocked_leader_messages_with_counts(
|
|
666
|
+
store,
|
|
667
|
+
event_log,
|
|
668
|
+
owner_team_id,
|
|
669
|
+
claimed_pane_id,
|
|
670
|
+
)
|
|
671
|
+
.map(crate::message_store::BlockedLeaderRequeueCounts::total)
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
pub(crate) fn requeue_blocked_leader_messages_with_counts(
|
|
675
|
+
store: &MessageStore,
|
|
676
|
+
event_log: &EventLog,
|
|
677
|
+
owner_team_id: &TeamKey,
|
|
678
|
+
claimed_pane_id: &PaneId,
|
|
679
|
+
) -> Result<crate::message_store::BlockedLeaderRequeueCounts, MessagingError> {
|
|
662
680
|
// E6 (0.5.9 offline-mailbox §6.5): also requeue rows that a third-party
|
|
663
681
|
// sender left in `queued_until_leader_attach` via the leader mailbox.
|
|
664
682
|
// Same idempotent requeue funnel (row/message_id/leader_notification_log
|
|
@@ -690,7 +708,7 @@ pub(crate) fn requeue_blocked_leader_messages(
|
|
|
690
708
|
}),
|
|
691
709
|
)?;
|
|
692
710
|
}
|
|
693
|
-
Ok(
|
|
711
|
+
Ok(counts)
|
|
694
712
|
}
|
|
695
713
|
|
|
696
714
|
/// `requeue_delivery_exhausted_watchers`: attach-leader 成功后把已经耗尽投递
|
|
@@ -702,12 +720,33 @@ pub(crate) fn requeue_blocked_leader_messages(
|
|
|
702
720
|
/// `deliver_pending_messages` never re-picks — the new leader would never see the
|
|
703
721
|
/// pending notification.
|
|
704
722
|
pub fn requeue_delivery_exhausted_watchers(
|
|
705
|
-
|
|
723
|
+
workspace: &Path,
|
|
706
724
|
store: &MessageStore,
|
|
707
725
|
event_log: &EventLog,
|
|
708
726
|
owner_team_id: &TeamKey,
|
|
709
727
|
claimed_pane_id: &PaneId,
|
|
710
728
|
) -> Result<Vec<WatcherNotice>, MessagingError> {
|
|
729
|
+
requeue_delivery_exhausted_watchers_with_counts(
|
|
730
|
+
workspace,
|
|
731
|
+
store,
|
|
732
|
+
event_log,
|
|
733
|
+
owner_team_id,
|
|
734
|
+
claimed_pane_id,
|
|
735
|
+
)
|
|
736
|
+
.map(|(notices, _)| notices)
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/// Attach-leader's typed recovery result. The counts come from the same
|
|
740
|
+
/// owner-team scoped attach-recovery call (the watcher and message updates
|
|
741
|
+
/// remain their existing transactions), so an attach caller can expose only
|
|
742
|
+
/// this operation's debt rather than scanning arbitrary historical events.
|
|
743
|
+
pub(crate) fn requeue_delivery_exhausted_watchers_with_counts(
|
|
744
|
+
_workspace: &Path,
|
|
745
|
+
store: &MessageStore,
|
|
746
|
+
event_log: &EventLog,
|
|
747
|
+
owner_team_id: &TeamKey,
|
|
748
|
+
claimed_pane_id: &PaneId,
|
|
749
|
+
) -> Result<(Vec<WatcherNotice>, crate::message_store::BlockedLeaderRequeueCounts), MessagingError> {
|
|
711
750
|
let conn = crate::db::schema::open_db(store.db_path())?;
|
|
712
751
|
let mut stmt = conn.prepare(
|
|
713
752
|
"select watcher_id, result_id, status from result_watchers
|
|
@@ -748,8 +787,13 @@ pub fn requeue_delivery_exhausted_watchers(
|
|
|
748
787
|
)?;
|
|
749
788
|
}
|
|
750
789
|
drop(stmt);
|
|
751
|
-
let
|
|
752
|
-
|
|
790
|
+
let counts = requeue_blocked_leader_messages_with_counts(
|
|
791
|
+
store,
|
|
792
|
+
event_log,
|
|
793
|
+
owner_team_id,
|
|
794
|
+
claimed_pane_id,
|
|
795
|
+
)?;
|
|
796
|
+
Ok((out, counts))
|
|
753
797
|
}
|
|
754
798
|
|
|
755
799
|
/// `delivered_result_message` (`result_delivery.py:394`):内容级去重 —— 查某 result_id 是否已有
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
use std::path::{Path, PathBuf};
|
|
28
28
|
|
|
29
29
|
use crate::model::paths::{runtime_dir, runtime_spec_path};
|
|
30
|
-
use crate::state::persist::load_runtime_state;
|
|
30
|
+
use crate::state::persist::{load_runtime_state, load_runtime_state_without_migrations};
|
|
31
31
|
use crate::state::projection::team_state_candidates;
|
|
32
32
|
|
|
33
33
|
/// The identity of a team within a workspace. Carries the *workspace root*
|
|
@@ -193,10 +193,25 @@ impl CommandScope {
|
|
|
193
193
|
/// On any I/O error the empty case is returned — destructive commands
|
|
194
194
|
/// will run their own selector and surface the real error.
|
|
195
195
|
pub fn resolve(workspace: &Path, requested_team: Option<&str>) -> Self {
|
|
196
|
+
Self::resolve_from_state(requested_team, load_runtime_state(workspace))
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// Ambiguity gate for read-only commands. Must not migrate or persist state.
|
|
200
|
+
pub fn resolve_readonly(workspace: &Path, requested_team: Option<&str>) -> Self {
|
|
201
|
+
Self::resolve_from_state(
|
|
202
|
+
requested_team,
|
|
203
|
+
load_runtime_state_without_migrations(workspace),
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
fn resolve_from_state(
|
|
208
|
+
requested_team: Option<&str>,
|
|
209
|
+
loaded: Result<serde_json::Value, crate::state::StateError>,
|
|
210
|
+
) -> Self {
|
|
196
211
|
if let Some(team) = requested_team.filter(|t| !t.is_empty()) {
|
|
197
212
|
return Self::Resolved(team.to_string());
|
|
198
213
|
}
|
|
199
|
-
let Ok(state) =
|
|
214
|
+
let Ok(state) = loaded else {
|
|
200
215
|
return Self::EmptyWorkspace;
|
|
201
216
|
};
|
|
202
217
|
let alive = team_state_candidates(&state);
|
|
@@ -16,7 +16,8 @@ use serde_json::{json, Map, Value};
|
|
|
16
16
|
|
|
17
17
|
use super::StateError;
|
|
18
18
|
use crate::state::persist::{
|
|
19
|
-
load_runtime_state,
|
|
19
|
+
load_runtime_state, load_runtime_state_without_migrations,
|
|
20
|
+
save_runtime_state_with_deleted_agents,
|
|
20
21
|
save_runtime_state_with_lifecycle_topology_authority,
|
|
21
22
|
save_runtime_state_with_lifecycle_topology_authority_and_capture_backfill_skip,
|
|
22
23
|
save_runtime_state_with_team_tombstone_lifecycle_topology_authority,
|
|
@@ -504,7 +505,22 @@ pub fn resolve_runtime_team_scope(
|
|
|
504
505
|
workspace: &Path,
|
|
505
506
|
team: Option<&str>,
|
|
506
507
|
) -> Result<TeamScopeResolution, StateError> {
|
|
507
|
-
|
|
508
|
+
resolve_runtime_team_scope_loaded(load_runtime_state(workspace)?, team)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/// Same team selection as [`resolve_runtime_team_scope`], but never migrates or
|
|
512
|
+
/// writes runtime state. Status and other read-only CLI surfaces must use this.
|
|
513
|
+
pub fn resolve_runtime_team_scope_readonly(
|
|
514
|
+
workspace: &Path,
|
|
515
|
+
team: Option<&str>,
|
|
516
|
+
) -> Result<TeamScopeResolution, StateError> {
|
|
517
|
+
resolve_runtime_team_scope_loaded(load_runtime_state_without_migrations(workspace)?, team)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
fn resolve_runtime_team_scope_loaded(
|
|
521
|
+
state: Value,
|
|
522
|
+
team: Option<&str>,
|
|
523
|
+
) -> Result<TeamScopeResolution, StateError> {
|
|
508
524
|
let alive = team_state_candidates(&state);
|
|
509
525
|
// Python `if team:` —— 空串 falsy,等同无 team(对抗 P1:此前 Some("") 误入 team 分支,空
|
|
510
526
|
// team_dir 匹配致歧义/未找到错误串漂移)。
|
|
@@ -7,7 +7,9 @@ use serde_json::Value;
|
|
|
7
7
|
|
|
8
8
|
use crate::model::paths::{canonical_run_workspace, runtime_spec_path, team_workspace};
|
|
9
9
|
use crate::state::persist::runtime_state_path;
|
|
10
|
-
use crate::state::projection::
|
|
10
|
+
use crate::state::projection::{
|
|
11
|
+
resolve_runtime_team_scope, resolve_runtime_team_scope_readonly, TeamScopeResolution,
|
|
12
|
+
};
|
|
11
13
|
use crate::state::StateError;
|
|
12
14
|
|
|
13
15
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
@@ -35,6 +37,24 @@ pub fn resolve_active_team(
|
|
|
35
37
|
input: &Path,
|
|
36
38
|
team: Option<&str>,
|
|
37
39
|
mode: SelectorMode,
|
|
40
|
+
) -> Result<SelectedTeam, StateError> {
|
|
41
|
+
resolve_active_team_with(input, team, mode, resolve_runtime_team_scope)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Status-path selector: in-memory projection only, no runtime-state writeback.
|
|
45
|
+
pub fn resolve_active_team_readonly(
|
|
46
|
+
input: &Path,
|
|
47
|
+
team: Option<&str>,
|
|
48
|
+
mode: SelectorMode,
|
|
49
|
+
) -> Result<SelectedTeam, StateError> {
|
|
50
|
+
resolve_active_team_with(input, team, mode, resolve_runtime_team_scope_readonly)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn resolve_active_team_with(
|
|
54
|
+
input: &Path,
|
|
55
|
+
team: Option<&str>,
|
|
56
|
+
mode: SelectorMode,
|
|
57
|
+
resolve_scope: fn(&Path, Option<&str>) -> Result<TeamScopeResolution, StateError>,
|
|
38
58
|
) -> Result<SelectedTeam, StateError> {
|
|
39
59
|
let explicit_spec = input.join("team.spec.yaml");
|
|
40
60
|
let (run_workspace, resolved) = if explicit_spec.exists() {
|
|
@@ -44,7 +64,7 @@ pub fn resolve_active_team(
|
|
|
44
64
|
} else {
|
|
45
65
|
team_run
|
|
46
66
|
};
|
|
47
|
-
let resolved =
|
|
67
|
+
let resolved = resolve_scope(&run, team)?;
|
|
48
68
|
(run, resolved)
|
|
49
69
|
} else {
|
|
50
70
|
let run =
|
|
@@ -59,7 +79,7 @@ pub fn resolve_active_team(
|
|
|
59
79
|
input.display()
|
|
60
80
|
)));
|
|
61
81
|
}
|
|
62
|
-
let resolved =
|
|
82
|
+
let resolved = resolve_scope(&run, team)?;
|
|
63
83
|
(run, resolved)
|
|
64
84
|
};
|
|
65
85
|
let state = resolved.state;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
use std::fs::OpenOptions;
|
|
2
|
+
use std::io::{Read, Write};
|
|
3
|
+
use std::os::unix::net::{UnixListener, UnixStream};
|
|
4
|
+
use std::path::{Path, PathBuf};
|
|
5
|
+
use std::process::{Command, Stdio};
|
|
6
|
+
use std::time::Duration;
|
|
7
|
+
|
|
8
|
+
const CHILD_FLAG: &str = "--team-agent-models-timeout-child";
|
|
9
|
+
const LIST_MODELS_FLAG: &str = "--list-models";
|
|
10
|
+
const RECEIPT_ENV: &str = "TEAM_AGENT_MODELS_TIMEOUT_RECEIPT";
|
|
11
|
+
const MODE_ENV: &str = "TEAM_AGENT_MODELS_TIMEOUT_MODE";
|
|
12
|
+
const READY_SOCKET_ENV: &str = "TEAM_AGENT_MODELS_TIMEOUT_READY_SOCKET";
|
|
13
|
+
const STDOUT_MARKER: &[u8] = b"__team_agent_models_timeout_descendant_stdout_v1__\n";
|
|
14
|
+
|
|
15
|
+
fn receipt_path() -> PathBuf {
|
|
16
|
+
std::env::var_os(RECEIPT_ENV)
|
|
17
|
+
.map(PathBuf::from)
|
|
18
|
+
.unwrap_or_else(|| std::process::exit(4))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fn append_receipt(path: &Path, key: &str, value: impl std::fmt::Display) -> Result<(), ()> {
|
|
22
|
+
let mut receipt = OpenOptions::new()
|
|
23
|
+
.create(true)
|
|
24
|
+
.append(true)
|
|
25
|
+
.open(path)
|
|
26
|
+
.map_err(|_| ())?;
|
|
27
|
+
writeln!(receipt, "{key}={value}").map_err(|_| ())
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn child_main(args: &[String]) {
|
|
31
|
+
if args.len() != 1 || args[0] != CHILD_FLAG {
|
|
32
|
+
std::process::exit(5);
|
|
33
|
+
}
|
|
34
|
+
let receipt = receipt_path();
|
|
35
|
+
let socket = std::env::var_os(READY_SOCKET_ENV)
|
|
36
|
+
.map(PathBuf::from)
|
|
37
|
+
.unwrap_or_else(|| std::process::exit(5));
|
|
38
|
+
|
|
39
|
+
let mut stdout = std::io::stdout();
|
|
40
|
+
if stdout.write_all(STDOUT_MARKER).is_err() || stdout.flush().is_err() {
|
|
41
|
+
std::process::exit(5);
|
|
42
|
+
}
|
|
43
|
+
if append_receipt(&receipt, "child_argv_exact", 1).is_err()
|
|
44
|
+
|| append_receipt(&receipt, "descendant_pid", std::process::id()).is_err()
|
|
45
|
+
|| append_receipt(&receipt, "descendant_started", 1).is_err()
|
|
46
|
+
|| append_receipt(&receipt, "descendant_stdout_inherited", 1).is_err()
|
|
47
|
+
{
|
|
48
|
+
std::process::exit(5);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let mut ready = UnixStream::connect(socket).unwrap_or_else(|_| std::process::exit(5));
|
|
52
|
+
if ready.write_all(b"1").is_err() {
|
|
53
|
+
std::process::exit(5);
|
|
54
|
+
}
|
|
55
|
+
std::thread::sleep(Duration::from_secs(2));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn main() {
|
|
59
|
+
let args: Vec<String> = std::env::args().skip(1).collect();
|
|
60
|
+
if args.first().is_some_and(|arg| arg == CHILD_FLAG) {
|
|
61
|
+
child_main(&args);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if args.len() != 1 || args[0] != LIST_MODELS_FLAG {
|
|
65
|
+
std::process::exit(2);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
match std::env::var(MODE_ENV) {
|
|
69
|
+
Ok(mode) if mode == "exit7" => {
|
|
70
|
+
eprintln!("sensitive-token");
|
|
71
|
+
std::process::exit(7);
|
|
72
|
+
}
|
|
73
|
+
Ok(mode) if mode == "oversize" => {
|
|
74
|
+
let mut stdout = std::io::stdout();
|
|
75
|
+
let bytes = [b'x'; 64];
|
|
76
|
+
let _ = stdout.write_all(&bytes);
|
|
77
|
+
let _ = stdout.flush();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
Ok(mode) if mode == "sleep" => {
|
|
81
|
+
std::thread::sleep(Duration::from_secs(2));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
Ok(mode) if mode == "descendant" => {}
|
|
85
|
+
Err(std::env::VarError::NotPresent) => {}
|
|
86
|
+
Ok(_) | Err(std::env::VarError::NotUnicode(_)) => std::process::exit(2),
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let receipt = receipt_path();
|
|
90
|
+
let socket = PathBuf::from(format!("{}.sock", receipt.display()));
|
|
91
|
+
let _ = std::fs::remove_file(&socket);
|
|
92
|
+
let listener = UnixListener::bind(&socket).unwrap_or_else(|_| std::process::exit(4));
|
|
93
|
+
if append_receipt(&receipt, "parent_started_once", 1).is_err()
|
|
94
|
+
|| append_receipt(&receipt, "parent_argv_exact", 1).is_err()
|
|
95
|
+
|| append_receipt(&receipt, "parent_pid", std::process::id()).is_err()
|
|
96
|
+
|| append_receipt(&receipt, "child_spawn_attempts", 1).is_err()
|
|
97
|
+
{
|
|
98
|
+
std::process::exit(4);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let executable = std::env::current_exe().unwrap_or_else(|_| std::process::exit(4));
|
|
102
|
+
let child = Command::new(executable)
|
|
103
|
+
.arg(CHILD_FLAG)
|
|
104
|
+
.env(RECEIPT_ENV, &receipt)
|
|
105
|
+
.env(READY_SOCKET_ENV, &socket)
|
|
106
|
+
.stdin(Stdio::null())
|
|
107
|
+
.stdout(Stdio::inherit())
|
|
108
|
+
.stderr(Stdio::null())
|
|
109
|
+
.spawn()
|
|
110
|
+
.unwrap_or_else(|_| std::process::exit(4));
|
|
111
|
+
let (mut ready, _) = listener.accept().unwrap_or_else(|_| std::process::exit(4));
|
|
112
|
+
let mut byte = [0_u8; 1];
|
|
113
|
+
if ready.read_exact(&mut byte).is_err() || byte != [b'1'] {
|
|
114
|
+
std::process::exit(4);
|
|
115
|
+
}
|
|
116
|
+
if append_receipt(&receipt, "child_pid", child.id()).is_err() {
|
|
117
|
+
std::process::exit(4);
|
|
118
|
+
}
|
|
119
|
+
let _ = std::fs::remove_file(&socket);
|
|
120
|
+
// The parent exits successfully without waiting; the child retains the
|
|
121
|
+
// inherited stdout pipe and self-exits after the real reader deadline.
|
|
122
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.77",
|
|
4
4
|
"description": "npx installer for Team Agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codex",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"team-agent-installer": "npm/install.mjs"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@team-agent/cli-darwin-arm64": "0.5.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.5.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.5.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.5.77",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.77",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.77"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|