@team-agent/installer 0.5.59 → 0.5.61
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 +44 -1
- package/crates/team-agent/src/cli/diagnose.rs +183 -0
- package/crates/team-agent/src/cli/emit.rs +63 -14
- package/crates/team-agent/src/cli/leader.rs +8 -1
- package/crates/team-agent/src/cli/mod.rs +263 -5
- package/crates/team-agent/src/cli/named_address.rs +18 -1
- package/crates/team-agent/src/cli/send/presentation.rs +2 -0
- package/crates/team-agent/src/cli/spec.rs +4 -1
- package/crates/team-agent/src/cli/status_port/store.rs +7 -3
- package/crates/team-agent/src/cli/tests/named_address.rs +65 -0
- package/crates/team-agent/src/cli/types.rs +33 -0
- package/crates/team-agent/src/communication_mode/mod.rs +53 -0
- package/crates/team-agent/src/compiler/tests.rs +5 -5
- package/crates/team-agent/src/compiler.rs +53 -1
- package/crates/team-agent/src/coordinator/tick.rs +21 -8
- package/crates/team-agent/src/db/message_store.rs +45 -1
- package/crates/team-agent/src/fake_worker.rs +21 -1
- package/crates/team-agent/src/leader/start.rs +732 -94
- package/crates/team-agent/src/leader/tests/identity.rs +64 -57
- package/crates/team-agent/src/leader/tests/identity_session_names.rs +42 -0
- package/crates/team-agent/src/lib.rs +4 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +58 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +57 -51
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +148 -8
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +5 -0
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +35 -1
- package/crates/team-agent/src/lifecycle/types.rs +1 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +29 -11
- package/crates/team-agent/src/mcp_server/normalize.rs +1 -0
- package/crates/team-agent/src/mcp_server/tests/send.rs +26 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +1 -1
- package/crates/team-agent/src/mcp_server/tools.rs +100 -66
- package/crates/team-agent/src/mcp_server/types.rs +5 -0
- package/crates/team-agent/src/mcp_server/wire.rs +27 -21
- package/crates/team-agent/src/messaging/delivery.rs +105 -38
- package/crates/team-agent/src/messaging/leader_channel.rs +33 -10
- package/crates/team-agent/src/messaging/leader_receiver.rs +40 -23
- package/crates/team-agent/src/messaging/presentation.rs +109 -0
- package/crates/team-agent/src/messaging/results.rs +165 -17
- package/crates/team-agent/src/messaging/send.rs +94 -81
- package/crates/team-agent/src/messaging/tests/leader_channel.rs +6 -6
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +187 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +13 -6
- package/crates/team-agent/src/messaging/types.rs +5 -0
- package/crates/team-agent/src/messaging/watchers.rs +5 -0
- package/crates/team-agent/src/model/mod.rs +1 -0
- package/crates/team-agent/src/model/pane_authority_refusal.rs +358 -0
- package/crates/team-agent/src/model/spec.rs +16 -0
- package/crates/team-agent/src/provider/adapter.rs +57 -0
- package/crates/team-agent/src/provider/adapters/claude_fork.rs +10 -2
- package/crates/team-agent/src/provider/session/capture.rs +69 -34
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +386 -9
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +3 -14
- package/crates/team-agent/src/provider/session/context_fork.rs +7 -2
- package/crates/team-agent/src/provider/session/mod.rs +3 -2
- package/crates/team-agent/src/provider/session_scan/claude.rs +92 -3
- package/crates/team-agent/src/provider/session_scan/codex.rs +28 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +34 -8
- package/crates/team-agent/src/provider/session_scan.rs +8 -0
- package/crates/team-agent/src/topology.rs +3 -0
- package/package.json +4 -4
- package/skills/team-agent/command-coverage.json +379 -0
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
|
@@ -483,6 +483,43 @@ pub fn cmd_collect_for_team(args: &CollectArgs, team: Option<&str>) -> Result<Cm
|
|
|
483
483
|
))
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
+
pub fn cmd_results(args: &ResultsArgs) -> Result<CmdResult, CliError> {
|
|
487
|
+
let selected = match crate::state::selector::resolve_active_team(
|
|
488
|
+
&args.workspace,
|
|
489
|
+
args.team.as_deref(),
|
|
490
|
+
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
491
|
+
) {
|
|
492
|
+
Ok(selected) => selected,
|
|
493
|
+
Err(error) => {
|
|
494
|
+
return Ok(CmdResult::from_json(
|
|
495
|
+
json!({
|
|
496
|
+
"ok": false,
|
|
497
|
+
"error": error.to_string(),
|
|
498
|
+
"workspace": args.workspace.to_string_lossy().to_string(),
|
|
499
|
+
}),
|
|
500
|
+
args.json,
|
|
501
|
+
));
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
let results = messaging::results::results_for_case(
|
|
505
|
+
&selected.run_workspace,
|
|
506
|
+
&args.case_id,
|
|
507
|
+
Some(selected.team_key.as_str()),
|
|
508
|
+
args.team.as_deref(),
|
|
509
|
+
)?;
|
|
510
|
+
let value = json!({
|
|
511
|
+
"ok": true,
|
|
512
|
+
"case_id": args.case_id,
|
|
513
|
+
"results": results,
|
|
514
|
+
"workspace": selected.run_workspace.to_string_lossy().to_string(),
|
|
515
|
+
});
|
|
516
|
+
Ok(if args.json {
|
|
517
|
+
CmdResult::from_ordered_json(value)
|
|
518
|
+
} else {
|
|
519
|
+
CmdResult::from_json(value, false)
|
|
520
|
+
})
|
|
521
|
+
}
|
|
522
|
+
|
|
486
523
|
/// `cmd_allow_peer_talk`(`parser.py allow-peer-talk`).
|
|
487
524
|
pub fn cmd_allow_peer_talk(args: &AllowPeerTalkArgs) -> Result<CmdResult, CliError> {
|
|
488
525
|
if args.team.is_some() {
|
|
@@ -1485,7 +1522,13 @@ pub fn cmd_doctor(args: &DoctorArgs) -> Result<CmdResult, CliError> {
|
|
|
1485
1522
|
} else if args.fix_schema {
|
|
1486
1523
|
diagnose_port::fix_schema(&args.workspace)?
|
|
1487
1524
|
} else {
|
|
1488
|
-
diagnose_port::doctor(&args.workspace, args.spec.as_deref())
|
|
1525
|
+
let mut value = diagnose_port::doctor(&args.workspace, args.spec.as_deref())?;
|
|
1526
|
+
crate::cli::diagnose::append_selected_live_leader_workspace_mismatch(
|
|
1527
|
+
&args.workspace,
|
|
1528
|
+
args.team.as_deref(),
|
|
1529
|
+
&mut value,
|
|
1530
|
+
);
|
|
1531
|
+
value
|
|
1489
1532
|
};
|
|
1490
1533
|
Ok(CmdResult::from_json(value, args.json))
|
|
1491
1534
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
//! diagnose/preflight/wait-ready CLI helpers.
|
|
2
2
|
use super::*;
|
|
3
|
+
use crate::model::pane_authority_refusal::{
|
|
4
|
+
PaneAuthorityRecoveryAction, PaneAuthorityRecoveryHint, PaneAuthorityRefusal,
|
|
5
|
+
PaneAuthorityRefusalFacts, PaneAuthorityRefusalField, ACTION_FIELD, ACTION_REQUIRED_FIELD,
|
|
6
|
+
HINT_ACTION_FIELD, REASON_FIELD,
|
|
7
|
+
};
|
|
3
8
|
use crate::provider::wire::{command_name, parse_provider, provider_wire};
|
|
4
9
|
use crate::transport::Transport;
|
|
5
10
|
|
|
@@ -195,12 +200,190 @@ pub(crate) fn diagnose_runtime_for_workspace(
|
|
|
195
200
|
backend: &dyn Transport,
|
|
196
201
|
) -> (Value, Value) {
|
|
197
202
|
let (mut issues, mut repairs) = diagnose_runtime(state, backend);
|
|
203
|
+
append_live_leader_workspace_mismatch_issue(
|
|
204
|
+
workspace,
|
|
205
|
+
state,
|
|
206
|
+
backend,
|
|
207
|
+
&mut issues,
|
|
208
|
+
&mut repairs,
|
|
209
|
+
);
|
|
198
210
|
append_legacy_snapshot_issue(workspace, state, &mut issues);
|
|
199
211
|
append_coordinator_health_issue(workspace, state, &mut issues, &mut repairs);
|
|
200
212
|
append_runtime_bindings_stale_after_boot_issue(workspace, state, &mut issues, &mut repairs);
|
|
201
213
|
(issues, repairs)
|
|
202
214
|
}
|
|
203
215
|
|
|
216
|
+
fn append_live_leader_workspace_mismatch_issue(
|
|
217
|
+
workspace: &std::path::Path,
|
|
218
|
+
state: &Value,
|
|
219
|
+
backend: &dyn Transport,
|
|
220
|
+
issues: &mut Value,
|
|
221
|
+
repairs: &mut Value,
|
|
222
|
+
) {
|
|
223
|
+
let Some((issue, repair)) = live_leader_workspace_mismatch(workspace, state, backend) else {
|
|
224
|
+
return;
|
|
225
|
+
};
|
|
226
|
+
if let Some(items) = issues.as_array_mut() {
|
|
227
|
+
items.push(issue);
|
|
228
|
+
}
|
|
229
|
+
if let Some(items) = repairs.as_array_mut() {
|
|
230
|
+
items.push(repair);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fn live_leader_workspace_mismatch(
|
|
235
|
+
workspace: &std::path::Path,
|
|
236
|
+
state: &Value,
|
|
237
|
+
backend: &dyn Transport,
|
|
238
|
+
) -> Option<(Value, Value)> {
|
|
239
|
+
let receiver = state.get("leader_receiver")?;
|
|
240
|
+
let facts = match crate::messaging::resolve_live_leader_channel(workspace, receiver, backend) {
|
|
241
|
+
crate::messaging::LeaderChannelResolution::Unbound(
|
|
242
|
+
crate::messaging::LeaderChannelUnbound::PaneWorkspaceMismatch(facts),
|
|
243
|
+
) => facts,
|
|
244
|
+
_ => return None,
|
|
245
|
+
};
|
|
246
|
+
let team = state
|
|
247
|
+
.get("team_key")
|
|
248
|
+
.and_then(Value::as_str)
|
|
249
|
+
.filter(|team| !team.is_empty())
|
|
250
|
+
.unwrap_or("current");
|
|
251
|
+
let provider = receiver
|
|
252
|
+
.get("provider")
|
|
253
|
+
.and_then(Value::as_str)
|
|
254
|
+
.filter(|provider| !provider.is_empty())
|
|
255
|
+
.unwrap_or("claude");
|
|
256
|
+
let refusal =
|
|
257
|
+
PaneAuthorityRefusal::new(PaneAuthorityRefusalFacts::PaneWorkspaceMismatch(facts));
|
|
258
|
+
let PaneAuthorityRefusalFacts::PaneWorkspaceMismatch(facts) = &refusal.facts else {
|
|
259
|
+
return None;
|
|
260
|
+
};
|
|
261
|
+
let mut fields = serde_json::Map::new();
|
|
262
|
+
fields.insert(
|
|
263
|
+
REASON_FIELD.to_string(),
|
|
264
|
+
Value::String(refusal.reason().as_str().to_string()),
|
|
265
|
+
);
|
|
266
|
+
fields.insert(
|
|
267
|
+
PaneAuthorityRefusalField::RequestedWorkspace
|
|
268
|
+
.as_str()
|
|
269
|
+
.to_string(),
|
|
270
|
+
Value::String(facts.requested_workspace.to_string_lossy().into_owned()),
|
|
271
|
+
);
|
|
272
|
+
fields.insert(
|
|
273
|
+
PaneAuthorityRefusalField::ObservedPaneId
|
|
274
|
+
.as_str()
|
|
275
|
+
.to_string(),
|
|
276
|
+
Value::String(facts.observed_pane_id.clone()),
|
|
277
|
+
);
|
|
278
|
+
fields.insert(
|
|
279
|
+
PaneAuthorityRefusalField::ObservedPaneWorkspace
|
|
280
|
+
.as_str()
|
|
281
|
+
.to_string(),
|
|
282
|
+
Value::String(facts.observed_pane_workspace.to_string_lossy().into_owned()),
|
|
283
|
+
);
|
|
284
|
+
fields.insert(
|
|
285
|
+
PaneAuthorityRefusalField::Endpoint.as_str().to_string(),
|
|
286
|
+
Value::String(facts.endpoint.clone()),
|
|
287
|
+
);
|
|
288
|
+
let mut issue = Value::Object(fields.clone());
|
|
289
|
+
if let Some(object) = issue.as_object_mut() {
|
|
290
|
+
object.insert(
|
|
291
|
+
"id".to_string(),
|
|
292
|
+
Value::String("leader_channel_workspace_mismatch".to_string()),
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
let mut repair = Value::Object(fields);
|
|
296
|
+
if let Some(object) = repair.as_object_mut() {
|
|
297
|
+
object.insert(
|
|
298
|
+
"issue".to_string(),
|
|
299
|
+
Value::String("leader_channel_workspace_mismatch".to_string()),
|
|
300
|
+
);
|
|
301
|
+
object.insert(
|
|
302
|
+
ACTION_REQUIRED_FIELD.to_string(),
|
|
303
|
+
Value::Bool(refusal.recovery.action_required),
|
|
304
|
+
);
|
|
305
|
+
object.insert("advisory".to_string(), Value::Bool(true));
|
|
306
|
+
object.insert(
|
|
307
|
+
"broken_class".to_string(),
|
|
308
|
+
Value::String("leader_channel_workspace_mismatch".to_string()),
|
|
309
|
+
);
|
|
310
|
+
object.insert(
|
|
311
|
+
HINT_ACTION_FIELD.to_string(),
|
|
312
|
+
Value::String(
|
|
313
|
+
match refusal.recovery.hint_action {
|
|
314
|
+
PaneAuthorityRecoveryHint::AttachLeader => "team-agent attach-leader",
|
|
315
|
+
}
|
|
316
|
+
.to_string(),
|
|
317
|
+
),
|
|
318
|
+
);
|
|
319
|
+
object.insert(
|
|
320
|
+
"dedupe_key".to_string(),
|
|
321
|
+
Value::String(format!("{team}:leader_channel_workspace_mismatch")),
|
|
322
|
+
);
|
|
323
|
+
object.insert(
|
|
324
|
+
ACTION_FIELD.to_string(),
|
|
325
|
+
Value::String(match refusal.recovery.action {
|
|
326
|
+
PaneAuthorityRecoveryAction::OpenTerminalOutsideCurrentTmuxPaneOrAttachFromMatchingPane => {
|
|
327
|
+
format!(
|
|
328
|
+
"open a new terminal window outside the current tmux/pane, change \
|
|
329
|
+
directory to the requested workspace, then run \
|
|
330
|
+
`team-agent attach-leader --team {team} --provider {provider} \
|
|
331
|
+
--confirm --json`; then rerun \
|
|
332
|
+
`team-agent diagnose --team {team} --json`"
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
}),
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
Some((issue, repair))
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
pub(crate) fn append_selected_live_leader_workspace_mismatch(
|
|
342
|
+
workspace: &std::path::Path,
|
|
343
|
+
team: Option<&str>,
|
|
344
|
+
report: &mut Value,
|
|
345
|
+
) {
|
|
346
|
+
let Some((issue, repair)) = selected_live_leader_workspace_mismatch(workspace, team) else {
|
|
347
|
+
return;
|
|
348
|
+
};
|
|
349
|
+
let Some(object) = report.as_object_mut() else {
|
|
350
|
+
return;
|
|
351
|
+
};
|
|
352
|
+
object.insert("issues".to_string(), Value::Array(vec![issue]));
|
|
353
|
+
object.insert("suggested_repairs".to_string(), Value::Array(vec![repair]));
|
|
354
|
+
object.insert("ok".to_string(), Value::Bool(false));
|
|
355
|
+
if object.get("error").is_none_or(Value::is_null) {
|
|
356
|
+
object.insert(
|
|
357
|
+
"error".to_string(),
|
|
358
|
+
Value::String("PaneWorkspaceMismatch".to_string()),
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
fn selected_live_leader_workspace_mismatch(
|
|
364
|
+
workspace: &std::path::Path,
|
|
365
|
+
team: Option<&str>,
|
|
366
|
+
) -> Option<(Value, Value)> {
|
|
367
|
+
let selected = crate::state::selector::resolve_active_team(
|
|
368
|
+
workspace,
|
|
369
|
+
team,
|
|
370
|
+
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
371
|
+
)
|
|
372
|
+
.ok()?;
|
|
373
|
+
let backend: Box<dyn crate::transport::Transport> =
|
|
374
|
+
match crate::transport_factory::resolve_read_only_transport(
|
|
375
|
+
&selected.run_workspace,
|
|
376
|
+
Some(&selected.state),
|
|
377
|
+
crate::transport_factory::TransportPurpose::Diagnose,
|
|
378
|
+
) {
|
|
379
|
+
Ok(resolved) => resolved.backend,
|
|
380
|
+
Err(_) => Box::new(crate::tmux_backend::TmuxBackend::for_workspace(
|
|
381
|
+
&selected.run_workspace,
|
|
382
|
+
)),
|
|
383
|
+
};
|
|
384
|
+
live_leader_workspace_mismatch(&selected.run_workspace, &selected.state, backend.as_ref())
|
|
385
|
+
}
|
|
386
|
+
|
|
204
387
|
/// 0.5.41 Slice 1 (fault-invisibility-locate.md §5/§6.2): read the
|
|
205
388
|
/// coordinator heartbeat sidecar for the last-observed host boot
|
|
206
389
|
/// identity and compare it against the current host boot. If they
|
|
@@ -9,13 +9,25 @@ use std::io::{ErrorKind, Write as _};
|
|
|
9
9
|
/// 否则 dict 逐键 `key: value`(嵌套 dict/list 内联 compact json,`ensure_ascii=False`)、非 dict 直接 print。
|
|
10
10
|
/// 返回应打印到 stdout 的字符串(bin main 负责实际 println)。
|
|
11
11
|
pub fn emit(output: &CmdOutput, as_json: bool) -> Option<String> {
|
|
12
|
+
emit_with_json_order(output, as_json, false)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
fn emit_with_json_order(
|
|
16
|
+
output: &CmdOutput,
|
|
17
|
+
as_json: bool,
|
|
18
|
+
preserve_json_order: bool,
|
|
19
|
+
) -> Option<String> {
|
|
12
20
|
match output {
|
|
13
21
|
CmdOutput::None => None,
|
|
14
22
|
CmdOutput::Human(text) => Some(crate::redaction::redact_external_text(text)),
|
|
15
23
|
CmdOutput::Json(value) => {
|
|
16
24
|
let value = crate::redaction::redact_external_value(value);
|
|
17
25
|
if as_json {
|
|
18
|
-
return
|
|
26
|
+
return if preserve_json_order {
|
|
27
|
+
serde_json::to_string_pretty(&value).ok()
|
|
28
|
+
} else {
|
|
29
|
+
serde_json::to_string_pretty(&sort_json(&value)).ok()
|
|
30
|
+
};
|
|
19
31
|
}
|
|
20
32
|
if let Value::Object(obj) = value {
|
|
21
33
|
let lines: Vec<String> = obj
|
|
@@ -84,7 +96,7 @@ fn emit_result(r: CmdResult) -> ExitCode {
|
|
|
84
96
|
.map(ToString::to_string),
|
|
85
97
|
_ => None,
|
|
86
98
|
};
|
|
87
|
-
if let Some(text) =
|
|
99
|
+
if let Some(text) = emit_with_json_order(&r.output, r.as_json, r.preserve_json_order) {
|
|
88
100
|
if let Err(error) = write_stdout_line(&text) {
|
|
89
101
|
if let Some(message_id) = persisted_message_id {
|
|
90
102
|
let stderr = std::io::stderr();
|
|
@@ -180,6 +192,7 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
|
|
|
180
192
|
cmd_collect_for_team(&collect_args(args, cwd)?, parse_args(args).team.as_deref())
|
|
181
193
|
.map(emit_result)
|
|
182
194
|
}
|
|
195
|
+
"results" => cmd_results(&results_args(args, cwd)?).map(emit_result),
|
|
183
196
|
"diagnose" => cmd_diagnose(&diagnose_args(args, cwd)).map(emit_result),
|
|
184
197
|
"preflight" => cmd_preflight(&preflight_args(args, cwd)).map(emit_result),
|
|
185
198
|
"wait-ready" => cmd_wait_ready(&wait_ready_args(args, cwd)).map(emit_result),
|
|
@@ -226,6 +239,7 @@ const DISPATCH_COMMANDS: &[&str] = &[
|
|
|
226
239
|
"install-skill",
|
|
227
240
|
"profile",
|
|
228
241
|
"collect",
|
|
242
|
+
"results",
|
|
229
243
|
"diagnose",
|
|
230
244
|
"preflight",
|
|
231
245
|
"wait-ready",
|
|
@@ -271,7 +285,7 @@ fn default_help() -> String {
|
|
|
271
285
|
append_help_section(
|
|
272
286
|
&mut out,
|
|
273
287
|
"Core",
|
|
274
|
-
&["quick-start", "send", "status", "collect"],
|
|
288
|
+
&["quick-start", "send", "status", "collect", "results"],
|
|
275
289
|
);
|
|
276
290
|
append_help_section(
|
|
277
291
|
&mut out,
|
|
@@ -346,10 +360,10 @@ fn command_help(command: Option<&str>) -> String {
|
|
|
346
360
|
Some("send") => concat!(
|
|
347
361
|
"usage: team-agent send TO MESSAGE... ",
|
|
348
362
|
"[--workspace WORKSPACE] [--team TEAM] ",
|
|
349
|
-
"[--
|
|
363
|
+
"[--mailbox] ",
|
|
350
364
|
"[--json]\n\n",
|
|
351
365
|
"TO is a logical recipient; send returns after the message is persisted. ",
|
|
352
|
-
"
|
|
366
|
+
"--mailbox stores durably without live injection; omitted sends to the live conversation."
|
|
353
367
|
)
|
|
354
368
|
.to_string(),
|
|
355
369
|
Some("allow-peer-talk") => "usage: team-agent allow-peer-talk A B [--workspace WORKSPACE] [--json]".to_string(),
|
|
@@ -383,6 +397,7 @@ fn command_help(command: Option<&str>) -> String {
|
|
|
383
397
|
Some("install-skill") => "usage: team-agent install-skill (--source DIR | --uninstall) [--target codex|claude|copilot|all] [--dest DIR] [--dry-run] [--json]".to_string(),
|
|
384
398
|
Some("profile") => "usage: team-agent profile COMMAND NAME [--workspace WORKSPACE] [--team TEAM] [--auth-mode MODE] [--json]".to_string(),
|
|
385
399
|
Some("collect") => "usage: team-agent collect [--workspace WORKSPACE] [--team TEAM] [--result-file FILE] [--json]".to_string(),
|
|
400
|
+
Some("results") => "usage: team-agent results --case CASE_ID [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
|
|
386
401
|
Some("diagnose") => "usage: team-agent diagnose [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
|
|
387
402
|
Some("preflight") => "usage: team-agent preflight [TEAMDIR] [--json]".to_string(),
|
|
388
403
|
Some("wait-ready") => "usage: team-agent wait-ready [--workspace WORKSPACE] [--team TEAM] [--timeout SECONDS] [--json]".to_string(),
|
|
@@ -806,6 +821,7 @@ struct ParsedArgs {
|
|
|
806
821
|
socket: Option<String>,
|
|
807
822
|
thread_id: Option<String>,
|
|
808
823
|
message_id: Option<String>,
|
|
824
|
+
mailbox: bool,
|
|
809
825
|
presentation_sink: Option<String>,
|
|
810
826
|
message_class: Option<String>,
|
|
811
827
|
case_id: Option<String>,
|
|
@@ -899,6 +915,7 @@ fn parse_args(args: &[String]) -> ParsedArgs {
|
|
|
899
915
|
"--socket" => parsed.socket = next_arg(args, &mut i),
|
|
900
916
|
"--thread-id" => parsed.thread_id = next_arg(args, &mut i),
|
|
901
917
|
"--message-id" => parsed.message_id = next_arg(args, &mut i),
|
|
918
|
+
"--mailbox" => parsed.mailbox = true,
|
|
902
919
|
"--presentation-sink" => parsed.presentation_sink = next_arg(args, &mut i),
|
|
903
920
|
"--message-class" => parsed.message_class = next_arg(args, &mut i),
|
|
904
921
|
"--case-id" => parsed.case_id = next_arg(args, &mut i),
|
|
@@ -1089,10 +1106,14 @@ fn send_args(args: &[String], cwd: &Path) -> Result<SendArgs, CliError> {
|
|
|
1089
1106
|
} else {
|
|
1090
1107
|
None
|
|
1091
1108
|
};
|
|
1092
|
-
let (
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1109
|
+
let mailbox_value = parsed.mailbox.then(|| serde_json::json!(true));
|
|
1110
|
+
let normalized = crate::messaging::presentation::normalize_send_presentation(
|
|
1111
|
+
mailbox_value.as_ref(),
|
|
1112
|
+
presentation_value.as_ref(),
|
|
1113
|
+
)
|
|
1114
|
+
.map_err(|error| CliError::Usage(format!("invalid send routing: {error}")))?;
|
|
1115
|
+
if let Some(deprecation) = normalized.deprecation {
|
|
1116
|
+
eprintln!("warning: {deprecation}");
|
|
1096
1117
|
}
|
|
1097
1118
|
Ok(SendArgs {
|
|
1098
1119
|
target,
|
|
@@ -1114,7 +1135,7 @@ fn send_args(args: &[String], cwd: &Path) -> Result<SendArgs, CliError> {
|
|
|
1114
1135
|
confirm_human: false,
|
|
1115
1136
|
json: parsed.json,
|
|
1116
1137
|
message_id: None,
|
|
1117
|
-
presentation,
|
|
1138
|
+
presentation: normalized.request,
|
|
1118
1139
|
pane: parsed.pane.clone(),
|
|
1119
1140
|
to_name: parsed.to_name.clone(),
|
|
1120
1141
|
to_leader: parsed.to_leader.clone(),
|
|
@@ -1177,6 +1198,7 @@ fn validate_send_flags(args: &[String]) -> Result<(), CliError> {
|
|
|
1177
1198
|
"--timeout",
|
|
1178
1199
|
"--confirm-human",
|
|
1179
1200
|
"--message-id",
|
|
1201
|
+
"--mailbox",
|
|
1180
1202
|
"--presentation-sink",
|
|
1181
1203
|
"--message-class",
|
|
1182
1204
|
"--case-id",
|
|
@@ -1593,6 +1615,27 @@ fn collect_args(args: &[String], cwd: &Path) -> Result<CollectArgs, CliError> {
|
|
|
1593
1615
|
})
|
|
1594
1616
|
}
|
|
1595
1617
|
|
|
1618
|
+
fn results_args(args: &[String], cwd: &Path) -> Result<ResultsArgs, CliError> {
|
|
1619
|
+
if args
|
|
1620
|
+
.iter()
|
|
1621
|
+
.any(|arg| arg == "--to" || arg.starts_with("--to="))
|
|
1622
|
+
{
|
|
1623
|
+
return Err(CliError::Usage(
|
|
1624
|
+
"results does not accept --to; it is a read-only command".to_string(),
|
|
1625
|
+
));
|
|
1626
|
+
}
|
|
1627
|
+
let parsed = parse_args(args);
|
|
1628
|
+
let case_id = option_value(args, "--case")
|
|
1629
|
+
.filter(|value| !value.is_empty())
|
|
1630
|
+
.ok_or_else(|| CliError::Usage("missing --case".to_string()))?;
|
|
1631
|
+
Ok(ResultsArgs {
|
|
1632
|
+
case_id,
|
|
1633
|
+
workspace: workspace(&parsed, cwd),
|
|
1634
|
+
team: parsed.team,
|
|
1635
|
+
json: parsed.json,
|
|
1636
|
+
})
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1596
1639
|
fn option_value(args: &[String], flag: &str) -> Option<String> {
|
|
1597
1640
|
let prefix = format!("{flag}=");
|
|
1598
1641
|
let mut i = 0usize;
|
|
@@ -2093,8 +2136,14 @@ mod tests {
|
|
|
2093
2136
|
}
|
|
2094
2137
|
|
|
2095
2138
|
#[test]
|
|
2096
|
-
fn
|
|
2139
|
+
fn send_mailbox_and_legacy_progress_map_to_the_one_bit_request() {
|
|
2097
2140
|
let cwd = tmp_workspace();
|
|
2141
|
+
let mailbox = send_args(&cli_argv(&["leader", "progress", "--mailbox"]), &cwd).unwrap();
|
|
2142
|
+
assert_eq!(
|
|
2143
|
+
mailbox.presentation.sink,
|
|
2144
|
+
crate::messaging::presentation::PresentationSink::Casefile
|
|
2145
|
+
);
|
|
2146
|
+
|
|
2098
2147
|
let args = send_args(
|
|
2099
2148
|
&cli_argv(&[
|
|
2100
2149
|
"leader",
|
|
@@ -2115,9 +2164,9 @@ mod tests {
|
|
|
2115
2164
|
);
|
|
2116
2165
|
assert_eq!(
|
|
2117
2166
|
args.presentation.class,
|
|
2118
|
-
crate::messaging::presentation::PresentationClass::
|
|
2167
|
+
crate::messaging::presentation::PresentationClass::Message
|
|
2119
2168
|
);
|
|
2120
|
-
assert_eq!(args.presentation.case_id
|
|
2169
|
+
assert_eq!(args.presentation.case_id, None);
|
|
2121
2170
|
}
|
|
2122
2171
|
|
|
2123
2172
|
#[test]
|
|
@@ -2130,7 +2179,7 @@ mod tests {
|
|
|
2130
2179
|
.unwrap_err();
|
|
2131
2180
|
assert!(matches!(
|
|
2132
2181
|
error,
|
|
2133
|
-
CliError::Usage(message) if message == "invalid
|
|
2182
|
+
CliError::Usage(message) if message == "invalid send routing: missing_class"
|
|
2134
2183
|
));
|
|
2135
2184
|
}
|
|
2136
2185
|
|
|
@@ -39,6 +39,8 @@ pub fn leader_launcher_args(values: &[String]) -> Result<LeaderLauncherArgs, Cli
|
|
|
39
39
|
out.confirm_attach = true;
|
|
40
40
|
} else if token == "--external-leader" {
|
|
41
41
|
out.external_leader = true;
|
|
42
|
+
} else if token == "--allow-nested-attach" {
|
|
43
|
+
out.allow_nested_attach = true;
|
|
42
44
|
} else if token == "--attach-session" {
|
|
43
45
|
let Some(value) = values.get(idx + 1) else {
|
|
44
46
|
return Err(CliError::Runtime(
|
|
@@ -60,7 +62,12 @@ pub fn leader_launcher_args(values: &[String]) -> Result<LeaderLauncherArgs, Cli
|
|
|
60
62
|
fn is_leader_launcher_flag(value: &str) -> bool {
|
|
61
63
|
matches!(
|
|
62
64
|
value,
|
|
63
|
-
"--external-leader"
|
|
65
|
+
"--external-leader"
|
|
66
|
+
| "--allow-nested-attach"
|
|
67
|
+
| "--attach"
|
|
68
|
+
| "--attach-existing"
|
|
69
|
+
| "--confirm"
|
|
70
|
+
| "--attach-session"
|
|
64
71
|
) || value.starts_with("--attach-session=")
|
|
65
72
|
}
|
|
66
73
|
|