@team-agent/installer 0.5.112 → 0.5.114
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 +252 -84
- package/Cargo.toml +3 -1
- package/crates/team-agent/Cargo.toml +3 -0
- package/crates/team-agent/src/cli/adapters.rs +119 -50
- package/crates/team-agent/src/cli/diagnose.rs +36 -13
- package/crates/team-agent/src/cli/emit.rs +173 -109
- package/crates/team-agent/src/cli/leader.rs +16 -0
- package/crates/team-agent/src/cli/leader_prompt.rs +261 -0
- package/crates/team-agent/src/cli/leaders.rs +32 -32
- package/crates/team-agent/src/cli/mod.rs +158 -14
- package/crates/team-agent/src/cli/models.rs +68 -18
- package/crates/team-agent/src/cli/named_address.rs +17 -16
- package/crates/team-agent/src/cli/route.rs +4 -4
- package/crates/team-agent/src/cli/send/presentation.rs +19 -18
- package/crates/team-agent/src/cli/send/resolve.rs +11 -11
- package/crates/team-agent/src/cli/send.rs +4 -4
- package/crates/team-agent/src/cli/spec.rs +35 -29
- package/crates/team-agent/src/cli/status.rs +7 -7
- package/crates/team-agent/src/cli/status_port/brief.rs +268 -101
- package/crates/team-agent/src/cli/tests/base.rs +94 -6
- package/crates/team-agent/src/cli/tests/divergence.rs +3 -2
- package/crates/team-agent/src/cli/tests/lane_c.rs +1 -1
- package/crates/team-agent/src/cli/tests/named_address.rs +3 -3
- package/crates/team-agent/src/cli/tests/status_send.rs +9 -7
- package/crates/team-agent/src/cli/triage.rs +53 -2
- package/crates/team-agent/src/cli/types.rs +8 -8
- package/crates/team-agent/src/compiler/tests.rs +12 -0
- package/crates/team-agent/src/compiler.rs +65 -26
- package/crates/team-agent/src/contract_runtime/backend.rs +646 -0
- package/crates/team-agent/src/contract_runtime/config.rs +309 -0
- package/crates/team-agent/src/contract_runtime/discovery.rs +237 -0
- package/crates/team-agent/src/contract_runtime/forward.rs +229 -0
- package/crates/team-agent/src/contract_runtime/framework.rs +261 -0
- package/crates/team-agent/src/contract_runtime/mcp.rs +396 -0
- package/crates/team-agent/src/contract_runtime/mod.rs +14 -0
- package/crates/team-agent/src/contract_runtime/prompt.rs +216 -0
- package/crates/team-agent/src/contract_runtime/pump.rs +207 -0
- package/crates/team-agent/src/contract_runtime/registry.rs +30 -0
- package/crates/team-agent/src/coordinator/tick.rs +26 -8
- package/crates/team-agent/src/leader/mod.rs +1 -0
- package/crates/team-agent/src/leader/prompt.rs +906 -0
- package/crates/team-agent/src/leader/start.rs +265 -16
- package/crates/team-agent/src/leader/tests/identity.rs +21 -0
- package/crates/team-agent/src/leader/tests/mod.rs +2 -0
- package/crates/team-agent/src/leader/tests/prompt_contract.rs +268 -0
- package/crates/team-agent/src/leader/types.rs +6 -0
- package/crates/team-agent/src/lib.rs +4 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +172 -13
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +760 -365
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +38 -9
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +17 -0
- package/crates/team-agent/src/lifecycle/launch.rs +39 -28
- package/crates/team-agent/src/lifecycle/profile_launch.rs +58 -46
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +97 -52
- package/crates/team-agent/src/lifecycle/restart.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +3 -2
- package/crates/team-agent/src/lifecycle/types.rs +4 -1
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +37 -27
- package/crates/team-agent/src/main.rs +6 -0
- package/crates/team-agent/src/messaging/delivery.rs +50 -1
- package/crates/team-agent/src/messaging/send.rs +10 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +131 -0
- package/crates/team-agent/src/model/spec.rs +24 -12
- package/crates/team-agent/src/provider/adapter.rs +8 -59
- package/crates/team-agent/src/provider/adapters/claude.rs +6 -14
- package/crates/team-agent/src/provider/adapters/codex.rs +8 -18
- package/crates/team-agent/src/provider/adapters/copilot.rs +5 -22
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +8 -45
- package/crates/team-agent/src/provider/adapters/grok.rs +7 -31
- package/crates/team-agent/src/provider/adapters/pi.rs +7 -18
- package/crates/team-agent/src/provider/command_helpers.rs +45 -0
- package/crates/team-agent/src/provider/mod.rs +1 -0
- package/crates/team-agent/src/provider/model_catalog.rs +21 -28
- package/crates/team-agent/src/provider/tests/adapter.rs +70 -0
- package/crates/team-agent/src/provider/tests/command_helpers.rs +55 -0
- package/crates/team-agent/src/state/persist.rs +73 -0
- package/package.json +4 -4
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
//! Early, host-global command dispatch: never select or initialize a workspace.
|
|
2
|
+
use super::ExitCode;
|
|
3
|
+
use crate::leader::prompt::{self, Mutation, PromptError};
|
|
4
|
+
use serde_json::json;
|
|
5
|
+
use std::io::{self, Write};
|
|
6
|
+
use std::path::PathBuf;
|
|
7
|
+
|
|
8
|
+
pub(crate) const HELP: &str = "Purpose:\nPersist supplemental system instructions for future Team Agent Leader launches only.\nUsage: team-agent leader-prompt [show|set|append|clear] [--json] [--file PATH | -- TEXT]\n\nOptions:\nshow (default) prints the exact text; set replaces it; append joins old and new text with two newlines; clear removes it.\nset/append require exactly one TEXT token after --, or --file PATH containing UTF-8 text. No implicit stdin.\nEverything after -- is literal text, including --json and --help. Empty text and NUL are rejected.\nConfiguration: ~/.team-agent/leader-prompt.txt, independent of the workspace. Mutations do not echo the text.\nOnly new Pi, Claude, Codex and Grok Leaders apply it; Worker launches never read it. Cursor/Copilot are refused when configured.\nCodex merges explicit argv developer_instructions, but overrides that user/profile configuration slot; migrate its existing text here.\nNative core prompts and unrelated launch arguments remain unchanged. Attach does not update an existing session.\nExamples:\nteam-agent leader-prompt show\nteam-agent leader-prompt set -- 'Keep replies concise.'\nteam-agent leader-prompt clear\n\nNext Action:\nUse show to inspect the text, then start a new Leader with team-agent pi (or claude/codex/grok).\nset/append/clear affect the next new process, not running Leaders or their conversation history.";
|
|
9
|
+
|
|
10
|
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
11
|
+
enum Operation {
|
|
12
|
+
Show,
|
|
13
|
+
Set,
|
|
14
|
+
Append,
|
|
15
|
+
Clear,
|
|
16
|
+
}
|
|
17
|
+
impl Operation {
|
|
18
|
+
fn name(self) -> &'static str {
|
|
19
|
+
match self {
|
|
20
|
+
Self::Show => "show",
|
|
21
|
+
Self::Set => "set",
|
|
22
|
+
Self::Append => "append",
|
|
23
|
+
Self::Clear => "clear",
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
#[derive(Debug, Eq, PartialEq)]
|
|
28
|
+
enum Input {
|
|
29
|
+
Text(String),
|
|
30
|
+
File(PathBuf),
|
|
31
|
+
}
|
|
32
|
+
#[derive(Debug)]
|
|
33
|
+
struct Request {
|
|
34
|
+
operation: Operation,
|
|
35
|
+
input: Option<Input>,
|
|
36
|
+
json: bool,
|
|
37
|
+
help: bool,
|
|
38
|
+
}
|
|
39
|
+
fn usage() -> PromptError {
|
|
40
|
+
PromptError::new(
|
|
41
|
+
"leader_prompt_invalid_input",
|
|
42
|
+
"Invalid or incomplete leader-prompt arguments; use one input for set/append",
|
|
43
|
+
None,
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
fn parse(args: &[String]) -> Result<Request, PromptError> {
|
|
47
|
+
let end = args
|
|
48
|
+
.iter()
|
|
49
|
+
.position(|arg| arg == "--")
|
|
50
|
+
.unwrap_or(args.len());
|
|
51
|
+
let mut json = false;
|
|
52
|
+
let mut help = false;
|
|
53
|
+
let mut file = None;
|
|
54
|
+
let mut positional = None;
|
|
55
|
+
let mut i = 0;
|
|
56
|
+
while i < end {
|
|
57
|
+
match args[i].as_str() {
|
|
58
|
+
"--json" if !json => json = true,
|
|
59
|
+
"--help" | "-h" if !help => help = true,
|
|
60
|
+
"--stdin" => {
|
|
61
|
+
return Err(PromptError::new(
|
|
62
|
+
"leader_prompt_invalid_input",
|
|
63
|
+
"--stdin is not supported for leader-prompt; use -- TEXT or --file PATH instead",
|
|
64
|
+
None,
|
|
65
|
+
));
|
|
66
|
+
}
|
|
67
|
+
"--file" if file.is_none() && i + 1 < end => {
|
|
68
|
+
i += 1;
|
|
69
|
+
if matches!(args[i].as_str(), "--json" | "--help" | "-h" | "--file") {
|
|
70
|
+
return Err(usage());
|
|
71
|
+
}
|
|
72
|
+
file = Some(PathBuf::from(&args[i]));
|
|
73
|
+
}
|
|
74
|
+
token if !token.starts_with('-') && positional.is_none() => positional = Some(token),
|
|
75
|
+
_ => return Err(usage()),
|
|
76
|
+
}
|
|
77
|
+
i += 1;
|
|
78
|
+
}
|
|
79
|
+
let operation = match positional.unwrap_or("show") {
|
|
80
|
+
"show" => Operation::Show,
|
|
81
|
+
"set" => Operation::Set,
|
|
82
|
+
"append" => Operation::Append,
|
|
83
|
+
"clear" => Operation::Clear,
|
|
84
|
+
_ => return Err(usage()),
|
|
85
|
+
};
|
|
86
|
+
let text = if end < args.len() {
|
|
87
|
+
if args.len() != end + 2 {
|
|
88
|
+
return Err(usage());
|
|
89
|
+
}
|
|
90
|
+
Some(args[end + 1].clone())
|
|
91
|
+
} else {
|
|
92
|
+
None
|
|
93
|
+
};
|
|
94
|
+
if (file.is_some() && text.is_some()) || (help && (file.is_some() || text.is_some())) {
|
|
95
|
+
return Err(usage());
|
|
96
|
+
}
|
|
97
|
+
let input = file.map(Input::File).or_else(|| text.map(Input::Text));
|
|
98
|
+
if !help && (matches!(operation, Operation::Set | Operation::Append) != input.is_some()) {
|
|
99
|
+
return Err(usage());
|
|
100
|
+
}
|
|
101
|
+
Ok(Request {
|
|
102
|
+
operation,
|
|
103
|
+
input,
|
|
104
|
+
json,
|
|
105
|
+
help,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
fn execute(request: Request) -> Result<(), PromptError> {
|
|
109
|
+
let path = prompt::config_path()?;
|
|
110
|
+
if request.operation == Operation::Show {
|
|
111
|
+
let text = prompt::read(&path)?.unwrap_or_default();
|
|
112
|
+
if request.json {
|
|
113
|
+
println!(
|
|
114
|
+
"{}",
|
|
115
|
+
json!({"configured": !text.is_empty(), "config_path": path, "prompt": text})
|
|
116
|
+
);
|
|
117
|
+
} else {
|
|
118
|
+
// Unlike mutation diagnostics, explicit show must preserve every byte.
|
|
119
|
+
io::stdout()
|
|
120
|
+
.write_all(text.as_bytes())
|
|
121
|
+
.and_then(|_| io::stdout().flush())
|
|
122
|
+
.map_err(|error| PromptError::io("leader_prompt_write_failed", &path, error))?;
|
|
123
|
+
}
|
|
124
|
+
return Ok(());
|
|
125
|
+
}
|
|
126
|
+
let mutation = match request.operation {
|
|
127
|
+
Operation::Clear => Mutation::Clear,
|
|
128
|
+
_ => {
|
|
129
|
+
let text = match request.input {
|
|
130
|
+
Some(Input::Text(text)) => text,
|
|
131
|
+
Some(Input::File(input)) => std::fs::read_to_string(&input).map_err(|error| {
|
|
132
|
+
PromptError::io("leader_prompt_unreadable", &input, error).at_config(&path)
|
|
133
|
+
})?,
|
|
134
|
+
None => return Err(usage()),
|
|
135
|
+
};
|
|
136
|
+
if request.operation == Operation::Set {
|
|
137
|
+
Mutation::Set(text)
|
|
138
|
+
} else {
|
|
139
|
+
Mutation::Append(text)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
let configured = prompt::mutate(&path, mutation)?;
|
|
144
|
+
if request.json {
|
|
145
|
+
println!(
|
|
146
|
+
"{}",
|
|
147
|
+
json!({"operation": request.operation.name(), "configured": configured, "config_path": path})
|
|
148
|
+
);
|
|
149
|
+
} else {
|
|
150
|
+
println!(
|
|
151
|
+
"Leader prompt {}: configured={configured}; config={}",
|
|
152
|
+
request.operation.name(),
|
|
153
|
+
path.display()
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
Ok(())
|
|
157
|
+
}
|
|
158
|
+
pub(crate) fn run(args: &[String]) -> ExitCode {
|
|
159
|
+
let end = args
|
|
160
|
+
.iter()
|
|
161
|
+
.position(|arg| arg == "--")
|
|
162
|
+
.unwrap_or(args.len());
|
|
163
|
+
let as_json = args[..end].iter().any(|arg| arg == "--json");
|
|
164
|
+
let result = match parse(args) {
|
|
165
|
+
Ok(request) if request.help => {
|
|
166
|
+
println!("{HELP}");
|
|
167
|
+
return ExitCode::Ok;
|
|
168
|
+
}
|
|
169
|
+
Ok(request) => execute(request),
|
|
170
|
+
Err(error) => Err(error),
|
|
171
|
+
};
|
|
172
|
+
match result {
|
|
173
|
+
Ok(()) => ExitCode::Ok,
|
|
174
|
+
Err(error) => {
|
|
175
|
+
if as_json {
|
|
176
|
+
println!("{}", error.report());
|
|
177
|
+
} else {
|
|
178
|
+
eprintln!("{error}");
|
|
179
|
+
}
|
|
180
|
+
if error.reason == "leader_prompt_invalid_input" {
|
|
181
|
+
ExitCode::Usage
|
|
182
|
+
} else {
|
|
183
|
+
ExitCode::Error
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#[cfg(test)]
|
|
190
|
+
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
191
|
+
mod tests {
|
|
192
|
+
use super::*;
|
|
193
|
+
fn args(tokens: &[&str]) -> Vec<String> {
|
|
194
|
+
tokens.iter().map(|text| text.to_string()).collect()
|
|
195
|
+
}
|
|
196
|
+
#[test]
|
|
197
|
+
fn leader_prompt_controls_stop_at_delimiter() {
|
|
198
|
+
for text in ["--json", "--help", "--file", "--stdin", "multi\nline🙂", ""] {
|
|
199
|
+
let request = parse(&args(&["set", "--", text])).unwrap();
|
|
200
|
+
assert!(!request.json && !request.help);
|
|
201
|
+
assert_eq!(request.input, Some(Input::Text(text.into())));
|
|
202
|
+
}
|
|
203
|
+
assert!(parse(&args(&[])).unwrap().operation == Operation::Show);
|
|
204
|
+
let request = parse(&args(&["--json", "append", "--file", "path with 中文"])).unwrap();
|
|
205
|
+
assert_eq!(
|
|
206
|
+
request.input,
|
|
207
|
+
Some(Input::File(PathBuf::from("path with 中文")))
|
|
208
|
+
);
|
|
209
|
+
assert!(request.json);
|
|
210
|
+
}
|
|
211
|
+
#[test]
|
|
212
|
+
fn leader_prompt_stdin_control_reports_unsupported_input_with_usage_exit() {
|
|
213
|
+
for tokens in [
|
|
214
|
+
vec!["set", "--stdin"],
|
|
215
|
+
vec!["set", "--json", "--stdin"],
|
|
216
|
+
vec!["append", "--json", "--stdin"],
|
|
217
|
+
] {
|
|
218
|
+
let tokens = args(&tokens);
|
|
219
|
+
let error = parse(&tokens).unwrap_err();
|
|
220
|
+
assert_eq!(error.reason, "leader_prompt_invalid_input");
|
|
221
|
+
assert_eq!(
|
|
222
|
+
error.error,
|
|
223
|
+
"--stdin is not supported for leader-prompt; use -- TEXT or --file PATH instead"
|
|
224
|
+
);
|
|
225
|
+
assert!(error.config_path.is_none());
|
|
226
|
+
assert_eq!(error.report()["reason"], "leader_prompt_invalid_input");
|
|
227
|
+
assert_eq!(error.report()["error"], error.error);
|
|
228
|
+
assert!(error.to_string().contains("--stdin is not supported"));
|
|
229
|
+
assert_eq!(run(&tokens).code(), 2);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
#[test]
|
|
233
|
+
fn leader_prompt_stdin_file_value_is_not_reclassified_as_a_control() {
|
|
234
|
+
for operation in ["set", "append"] {
|
|
235
|
+
let request = parse(&args(&[operation, "--file", "--stdin"])).unwrap();
|
|
236
|
+
assert_eq!(request.input, Some(Input::File(PathBuf::from("--stdin"))));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
#[test]
|
|
240
|
+
fn leader_prompt_rejects_duplicate_missing_unknown_or_extra_controls() {
|
|
241
|
+
for tokens in [
|
|
242
|
+
vec!["set"],
|
|
243
|
+
vec!["append", "--"],
|
|
244
|
+
vec!["set", "--", "one", "two"],
|
|
245
|
+
vec!["set", "--file"],
|
|
246
|
+
vec!["set", "--file", "input", "--", "text"],
|
|
247
|
+
vec!["show", "--", "text"],
|
|
248
|
+
vec!["clear", "--file", "input"],
|
|
249
|
+
vec!["show", "--json", "--json"],
|
|
250
|
+
vec!["set", "--file", "one", "--file", "two"],
|
|
251
|
+
vec!["show", "--unknown"],
|
|
252
|
+
vec!["show", "clear"],
|
|
253
|
+
vec!["unknown", "--help"],
|
|
254
|
+
] {
|
|
255
|
+
assert_eq!(
|
|
256
|
+
parse(&args(&tokens)).unwrap_err().reason,
|
|
257
|
+
"leader_prompt_invalid_input"
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -124,7 +124,7 @@ fn send_hint(entry: &LeaderRegistryEntry) -> String {
|
|
|
124
124
|
format!(
|
|
125
125
|
"team-agent send {} {}",
|
|
126
126
|
quote_cli_arg(&to),
|
|
127
|
-
quote_cli_arg("
|
|
127
|
+
quote_cli_arg("message")
|
|
128
128
|
)
|
|
129
129
|
}
|
|
130
130
|
|
|
@@ -183,19 +183,19 @@ fn format_leaders_human(rows: &[LeaderRow], view: LeadersView) -> String {
|
|
|
183
183
|
if rows.is_empty() {
|
|
184
184
|
return match view {
|
|
185
185
|
LeadersView::Live => {
|
|
186
|
-
"
|
|
186
|
+
"No live leaders. Run team-agent doctor --workspace . for the current team.\n".to_string()
|
|
187
187
|
}
|
|
188
|
-
LeadersView::All => "
|
|
189
|
-
LeadersView::Stale => "
|
|
188
|
+
LeadersView::All => "No registered leaders. Open a leader with team-agent pi for first-time collaboration.\n".to_string(),
|
|
189
|
+
LeadersView::Stale => "No stale leader registrations.\n".to_string(),
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
|
-
let mut out = String::from("
|
|
192
|
+
let mut out = String::from("leader\tstatus\tworkspace\tteam\tsend command\n");
|
|
193
193
|
for row in rows {
|
|
194
194
|
out.push_str("leader\t");
|
|
195
195
|
out.push_str(match row.status {
|
|
196
|
-
"LIVE" => "
|
|
197
|
-
"STALE" => "
|
|
198
|
-
"AMBIGUOUS" => "
|
|
196
|
+
"LIVE" => "LIVE",
|
|
197
|
+
"STALE" => "STALE",
|
|
198
|
+
"AMBIGUOUS" => "AMBIGUOUS",
|
|
199
199
|
other => other,
|
|
200
200
|
});
|
|
201
201
|
out.push('\t');
|
|
@@ -212,7 +212,7 @@ fn format_leaders_human(rows: &[LeaderRow], view: LeadersView) -> String {
|
|
|
212
212
|
&row.entry.workspace,
|
|
213
213
|
Some(&row.entry.team_key),
|
|
214
214
|
)
|
|
215
|
-
.unwrap_or_else(|| "
|
|
215
|
+
.unwrap_or_else(|| "Verify the workspace path".to_string()),
|
|
216
216
|
);
|
|
217
217
|
}
|
|
218
218
|
out.push('\n');
|
|
@@ -240,33 +240,33 @@ fn prune_json(report: &RegistryPruneReport) -> Value {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
fn format_prune_human(report: &RegistryPruneReport) -> String {
|
|
243
|
-
let mut out = format!("
|
|
243
|
+
let mut out = format!("dry_run: {}\n", report.dry_run);
|
|
244
244
|
for (label, items) in [
|
|
245
|
-
("
|
|
246
|
-
("
|
|
247
|
-
("
|
|
248
|
-
("
|
|
249
|
-
("
|
|
245
|
+
("candidates", &report.candidates),
|
|
246
|
+
("removed", &report.removed),
|
|
247
|
+
("kept", &report.kept),
|
|
248
|
+
("skipped", &report.skipped),
|
|
249
|
+
("errors", &report.errors),
|
|
250
250
|
] {
|
|
251
251
|
out.push_str(&format!("{label}: {}\n", items.len()));
|
|
252
252
|
for item in items {
|
|
253
253
|
out.push_str(" ");
|
|
254
254
|
out.push_str(&item.workspace.display().to_string());
|
|
255
|
-
out.push_str("
|
|
255
|
+
out.push_str(", team: ");
|
|
256
256
|
out.push_str(&item.team_key);
|
|
257
257
|
out.push_str(" [");
|
|
258
258
|
out.push_str(match item.reason.as_str() {
|
|
259
|
-
"LIVE" => "
|
|
260
|
-
"team_key_not_found" => "
|
|
261
|
-
"team_not_alive" => "
|
|
262
|
-
"registry_entry_missing" => "
|
|
259
|
+
"LIVE" => "still live; registration retained",
|
|
260
|
+
"team_key_not_found" => "team no longer exists in the workspace",
|
|
261
|
+
"team_not_alive" => "team is stopped",
|
|
262
|
+
"registry_entry_missing" => "registration no longer exists",
|
|
263
263
|
"registry_entry_changed" | "canonical_state_changed" => {
|
|
264
|
-
"
|
|
264
|
+
"registration or team changed; cleanup refused"
|
|
265
265
|
}
|
|
266
|
-
"canonical_state_unavailable" => "
|
|
267
|
-
"registry_lock_unavailable" => "
|
|
268
|
-
"remove_failed" => "
|
|
269
|
-
_ => "
|
|
266
|
+
"canonical_state_unavailable" => "team state unavailable; registration retained",
|
|
267
|
+
"registry_lock_unavailable" => "registry lock unavailable; cleanup refused",
|
|
268
|
+
"remove_failed" => "registration removal failed; inspect file permissions",
|
|
269
|
+
_ => "registration could not be verified safely; diagnose the selected workspace",
|
|
270
270
|
});
|
|
271
271
|
out.push_str("]\n");
|
|
272
272
|
}
|
|
@@ -337,13 +337,13 @@ mod tests {
|
|
|
337
337
|
assert_eq!(lines.len(), 3, "one header and both selected rows: {out}");
|
|
338
338
|
let header = lines[0];
|
|
339
339
|
assert_eq!(header.split('\t').count(), 5);
|
|
340
|
-
assert!(header.contains("
|
|
340
|
+
assert!(header.contains("status") && header.contains("workspace") && header.contains("team"));
|
|
341
341
|
let live: Vec<_> = lines[1].split('\t').collect();
|
|
342
342
|
assert_eq!(
|
|
343
343
|
&live[..4],
|
|
344
344
|
&[
|
|
345
345
|
"leader",
|
|
346
|
-
"
|
|
346
|
+
"LIVE",
|
|
347
347
|
"/Volumes/nvme/Projects/讨论team-agent",
|
|
348
348
|
"wiki-team"
|
|
349
349
|
]
|
|
@@ -354,7 +354,7 @@ mod tests {
|
|
|
354
354
|
"team-agent",
|
|
355
355
|
"send",
|
|
356
356
|
"leader",
|
|
357
|
-
"
|
|
357
|
+
"Complete the task and reply to the leader.",
|
|
358
358
|
"--workspace",
|
|
359
359
|
"/Volumes/nvme/Projects/讨论team-agent",
|
|
360
360
|
"--team",
|
|
@@ -364,7 +364,7 @@ mod tests {
|
|
|
364
364
|
let stale: Vec<_> = lines[2].split('\t').collect();
|
|
365
365
|
assert_eq!(
|
|
366
366
|
&stale[..4],
|
|
367
|
-
&["leader", "
|
|
367
|
+
&["leader", "STALE", "/Users/alauda/stale", "old-team"]
|
|
368
368
|
);
|
|
369
369
|
assert_eq!(stale[4], "-", "stale rows must never suggest a send: {out}");
|
|
370
370
|
}
|
|
@@ -404,9 +404,9 @@ mod tests {
|
|
|
404
404
|
#[test]
|
|
405
405
|
fn empty_view_names_are_explicit() {
|
|
406
406
|
for (view, empty, next) in [
|
|
407
|
-
(LeadersView::Live, "
|
|
408
|
-
(LeadersView::Stale, "
|
|
409
|
-
(LeadersView::All, "
|
|
407
|
+
(LeadersView::Live, "No live leaders", Some("team-agent doctor")),
|
|
408
|
+
(LeadersView::Stale, "No stale leader registrations", None),
|
|
409
|
+
(LeadersView::All, "No registered leaders", Some("team-agent pi")),
|
|
410
410
|
] {
|
|
411
411
|
let out = format_leaders_human(&[], view);
|
|
412
412
|
assert!(out.contains(empty) && out.ends_with('\n'), "{out}");
|