@team-agent/installer 0.5.110 → 0.5.112
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 +231 -812
- package/crates/team-agent/src/cli/diagnose.rs +2 -424
- package/crates/team-agent/src/cli/emit.rs +493 -451
- package/crates/team-agent/src/cli/leader.rs +4 -9
- package/crates/team-agent/src/cli/leaders.rs +90 -29
- package/crates/team-agent/src/cli/mod.rs +348 -169
- package/crates/team-agent/src/cli/models.rs +80 -24
- package/crates/team-agent/src/cli/named_address.rs +114 -25
- package/crates/team-agent/src/cli/route.rs +10 -5
- package/crates/team-agent/src/cli/send/presentation.rs +60 -41
- package/crates/team-agent/src/cli/send/resolve.rs +25 -18
- package/crates/team-agent/src/cli/send.rs +4 -4
- package/crates/team-agent/src/cli/spec.rs +53 -109
- package/crates/team-agent/src/cli/tests/base.rs +189 -82
- package/crates/team-agent/src/cli/tests/lane_c.rs +1 -2
- package/crates/team-agent/src/cli/tests/leader_watch.rs +2 -86
- package/crates/team-agent/src/cli/tests/main_preserved.rs +15 -140
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +1 -258
- package/crates/team-agent/src/cli/tests/mod.rs +0 -2
- package/crates/team-agent/src/cli/tests/named_address.rs +19 -22
- package/crates/team-agent/src/cli/tests/status_send.rs +15 -10
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +2 -2
- package/crates/team-agent/src/cli/types.rs +26 -121
- package/crates/team-agent/src/coordinator/health.rs +3 -283
- package/crates/team-agent/src/coordinator/mod.rs +0 -5
- package/crates/team-agent/src/coordinator/tests/basics.rs +0 -9
- package/crates/team-agent/src/coordinator/tests/mod.rs +0 -1
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -17
- package/crates/team-agent/src/coordinator/types.rs +1 -21
- package/crates/team-agent/src/db/message_store.rs +0 -42
- package/crates/team-agent/src/db/migration.rs +49 -9
- package/crates/team-agent/src/db/schema.rs +27 -20
- package/crates/team-agent/src/leader/owner_bind.rs +0 -24
- package/crates/team-agent/src/leader/registry.rs +3 -3
- package/crates/team-agent/src/leader/tests/byte_findings.rs +0 -20
- package/crates/team-agent/src/leader/tests/identity.rs +0 -32
- package/crates/team-agent/src/leader/tests/lease_claim.rs +0 -55
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +4 -9
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -51
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +3 -1
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +23 -23
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +29 -55
- package/crates/team-agent/src/messaging/mod.rs +0 -2
- package/crates/team-agent/src/messaging/results.rs +0 -58
- package/crates/team-agent/src/messaging/tests/runtime.rs +0 -46
- package/crates/team-agent/src/state/repository.rs +1 -5
- package/package.json +4 -4
- package/templates/team_state.md +1 -1
- package/crates/team-agent/src/cli/tests/peer_allow.rs +0 -164
- package/crates/team-agent/src/cli/tests/verb_validate.rs +0 -195
- package/crates/team-agent/src/coordinator/tests/watch.rs +0 -223
- package/crates/team-agent/src/messaging/peers.rs +0 -34
|
@@ -144,155 +144,12 @@ fn seed_leader_registry_entry(ws: &std::path::Path) {
|
|
|
144
144
|
);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
//
|
|
148
|
-
// WAVE-2 NON-SUB CHECKPOINT — missing CLI subcommands (ABSENT from cli/emit.rs dispatch).
|
|
149
|
-
// emit.rs `dispatch` has NO arm for sessions, peek, collect, e2e, diagnose,
|
|
150
|
-
// preflight, wait-ready -> they fall to `_ => Ok(ExitCode::Error)`. These REDs
|
|
151
|
-
// assert the dispatch ROUTES each subcommand: `run([sub,...]) == ExitCode::Ok` for a golden
|
|
152
|
-
// EXIT-0 scenario (today unrouted -> ExitCode::Error -> RED; green once the porter adds the
|
|
153
|
-
// dispatch arm + handler). Golden exit codes + JSON shapes probed via `python3 -m team_agent <sub>`.
|
|
154
|
-
//
|
|
155
|
-
// OBSERVABILITY NOTE: `run()` exposes only ExitCode (Ok=0/Error=1); it prints via println!, which
|
|
156
|
-
// libtest intercepts (thread-local capture), so an fd-level stdout byte-capture is unreliable
|
|
157
|
-
// under `cargo test`. The exact golden --json byte-shape is therefore LOCKED in each doc-comment
|
|
158
|
-
// as the porter's parity obligation; the in-process assertion is the routing (exit code). A
|
|
159
|
-
// follow-up can byte-lock output once each handler exists as a callable `cmd_*`/`*_port` symbol.
|
|
160
|
-
//
|
|
161
|
-
// Only golden-EXIT-0, tmux-SAFE scenarios make a clean RED (an exit-1 scenario's Error is
|
|
162
|
-
// indistinguishable from the unknown-subcommand Error -> false-green, forbidden). preflight/
|
|
163
|
-
// wait-ready/e2e/peek cannot reach golden-exit-0 on CI without real tmux/providers/a live team,
|
|
164
|
-
// so they are #[ignore] real-effect seams (documented shape), NOT false-green exit-1 asserts.
|
|
165
|
-
// =========================================================================
|
|
147
|
+
// Retained doctor dispatch and shared status-readiness contracts.
|
|
166
148
|
|
|
167
149
|
fn cli_argv(items: &[&str]) -> Vec<String> {
|
|
168
150
|
items.iter().map(|s| (*s).to_string()).collect()
|
|
169
151
|
}
|
|
170
152
|
|
|
171
|
-
/// The exact golden fake spec (cli/e2e.py `_fake_spec`, dumped via simple_yaml) with the literal
|
|
172
|
-
/// `/WS` placeholder substituted to the real workspace — the minimal VALID team.spec.yaml that
|
|
173
|
-
/// `collect` accepts. (load_spec rejects partial specs: requires
|
|
174
|
-
/// communication/context/leader/routing/runtime/tasks.)
|
|
175
|
-
const FAKE_SPEC_YAML: &str = r#"version: 1
|
|
176
|
-
team:
|
|
177
|
-
name: "fake-e2e"
|
|
178
|
-
mode: "supervisor_worker"
|
|
179
|
-
objective: "Exercise fake provider orchestration."
|
|
180
|
-
workspace: "/WS"
|
|
181
|
-
leader:
|
|
182
|
-
id: "leader"
|
|
183
|
-
role: "leader"
|
|
184
|
-
provider: "fake"
|
|
185
|
-
model: null
|
|
186
|
-
tools:
|
|
187
|
-
- "fs_read"
|
|
188
|
-
- "fs_list"
|
|
189
|
-
- "mcp_team"
|
|
190
|
-
context_policy:
|
|
191
|
-
keep_user_thread: true
|
|
192
|
-
receive_worker_outputs: "structured_only"
|
|
193
|
-
max_worker_result_tokens: 2000
|
|
194
|
-
agents:
|
|
195
|
-
- id: "fake_impl"
|
|
196
|
-
role: "implementation_engineer"
|
|
197
|
-
provider: "fake"
|
|
198
|
-
model: null
|
|
199
|
-
working_directory: "/WS"
|
|
200
|
-
system_prompt:
|
|
201
|
-
inline: "Handle fake implementation tasks."
|
|
202
|
-
file: null
|
|
203
|
-
tools:
|
|
204
|
-
- "fs_read"
|
|
205
|
-
- "fs_write"
|
|
206
|
-
- "fs_list"
|
|
207
|
-
- "execute_bash"
|
|
208
|
-
- "git_diff"
|
|
209
|
-
- "mcp_team"
|
|
210
|
-
- "provider_builtin"
|
|
211
|
-
permission_mode: "restricted"
|
|
212
|
-
preferred_for:
|
|
213
|
-
- "implementation"
|
|
214
|
-
avoid_for: []
|
|
215
|
-
output_contract:
|
|
216
|
-
format: "result_envelope_v1"
|
|
217
|
-
required_fields:
|
|
218
|
-
- "task_id"
|
|
219
|
-
- "status"
|
|
220
|
-
- "summary"
|
|
221
|
-
- "artifacts"
|
|
222
|
-
routing:
|
|
223
|
-
default_assignee: "leader"
|
|
224
|
-
rules:
|
|
225
|
-
- id: "implementation-to-fake"
|
|
226
|
-
match:
|
|
227
|
-
type:
|
|
228
|
-
- "implementation"
|
|
229
|
-
assign_to: "fake_impl"
|
|
230
|
-
priority: 10
|
|
231
|
-
communication:
|
|
232
|
-
protocol: "mcp_inbox"
|
|
233
|
-
topology: "leader_centered"
|
|
234
|
-
worker_to_worker: true
|
|
235
|
-
ack_timeout_sec: 2
|
|
236
|
-
result_format: "result_envelope_v1"
|
|
237
|
-
message_store:
|
|
238
|
-
sqlite: ".team/runtime/team.db"
|
|
239
|
-
mirror_files: ".team/messages"
|
|
240
|
-
runtime:
|
|
241
|
-
backend: "tmux"
|
|
242
|
-
session_name: "team-agent-fake-e2e"
|
|
243
|
-
auto_launch: true
|
|
244
|
-
require_user_approval_before_launch: false
|
|
245
|
-
max_active_agents: 1
|
|
246
|
-
startup_order:
|
|
247
|
-
- "fake_impl"
|
|
248
|
-
context:
|
|
249
|
-
state_file: "team_state.md"
|
|
250
|
-
artifact_dir: ".team/artifacts"
|
|
251
|
-
log_dir: ".team/logs"
|
|
252
|
-
summarization:
|
|
253
|
-
worker_full_logs: "retain_outside_leader_context"
|
|
254
|
-
state_update: "after_each_result"
|
|
255
|
-
tasks:
|
|
256
|
-
- id: "task_impl"
|
|
257
|
-
title: "Fake implementation"
|
|
258
|
-
type: "implementation"
|
|
259
|
-
assignee: null
|
|
260
|
-
deps: []
|
|
261
|
-
acceptance:
|
|
262
|
-
- "fake result collected"
|
|
263
|
-
status: "pending"
|
|
264
|
-
requires_tools:
|
|
265
|
-
- "fs_write"
|
|
266
|
-
- "execute_bash"
|
|
267
|
-
files:
|
|
268
|
-
- "src/example.py"
|
|
269
|
-
risk: "low"
|
|
270
|
-
"#;
|
|
271
|
-
|
|
272
|
-
fn seed_team_spec(ws: &std::path::Path) {
|
|
273
|
-
let spec = FAKE_SPEC_YAML.replace("/WS", &ws.to_string_lossy());
|
|
274
|
-
std::fs::write(ws.join("team.spec.yaml"), spec).unwrap();
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// ── sessions ── golden cli/parser.py:230 `cmd_sessions` -> runtime.sessions(ws). EXIT 0.
|
|
278
|
-
// `team-agent sessions --workspace <ws> --json` on an empty ws ->
|
|
279
|
-
// {"ok":true,"sessions":[],"workspace":"<ws>"} (--json sort_keys). RED: unrouted -> Error.
|
|
280
|
-
#[test]
|
|
281
|
-
fn dispatch_routes_sessions_subcommand() {
|
|
282
|
-
let ws = tmp_workspace();
|
|
283
|
-
let code = run(
|
|
284
|
-
&cli_argv(&["sessions", "--workspace", &ws.to_string_lossy(), "--json"]),
|
|
285
|
-
&ws,
|
|
286
|
-
);
|
|
287
|
-
assert_eq!(
|
|
288
|
-
code,
|
|
289
|
-
ExitCode::Ok,
|
|
290
|
-
"`sessions` must ROUTE to cmd_sessions (golden parser.py:230, exit 0 {{ok,sessions,workspace}}); \
|
|
291
|
-
today it falls to the unknown-subcommand arm (emit.rs:77) -> Error"
|
|
292
|
-
);
|
|
293
|
-
let _ = std::fs::remove_dir_all(&ws);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
153
|
// ── diagnose ── attached is host-registry authority, not state.json self-sign.
|
|
297
154
|
// Seed leader_receiver=attached + a ~/.team-agent/leaders/ row for this workspace
|
|
298
155
|
// + NO session_name + NO agents -> issues=[] -> EXIT 0.
|
|
@@ -389,120 +246,6 @@ fn dispatch_routes_diagnose_self_signed_attached_without_registry_is_not_ok() {
|
|
|
389
246
|
let _ = std::fs::remove_dir_all(&ws);
|
|
390
247
|
}
|
|
391
248
|
|
|
392
|
-
// ── preflight (#[ignore] real-machine) ── golden parser.py:160 `cmd_preflight` -> runtime.preflight
|
|
393
|
-
// (Path(args.team)). Validates a team role-doc dir: checks compile(TEAM.md)/tmux/ghostty/rust_core/
|
|
394
|
-
// profile_dir; golden -> {"blockers":[...],"checks":[...],"details_log":...,"next_actions":[...],
|
|
395
|
-
// "ok":bool,"summary":...}. Reaches ok:true (exit 0) ONLY with a valid TEAM.md + ghostty installed,
|
|
396
|
-
// which CI cannot supply (ghostty absent -> blocker) -> not a clean in-process exit-0 RED.
|
|
397
|
-
#[test]
|
|
398
|
-
#[ignore = "real-machine: `preflight --team <dir>` needs a valid TEAM.md role-doc dir + ghostty to \
|
|
399
|
-
reach ok:true; on CI it golden-exits 1 (compile/ghostty blockers) which is \
|
|
400
|
-
indistinguishable from the unknown-subcommand Error (false-green). Routes to cmd_preflight \
|
|
401
|
-
(parser.py:160); shape {blockers,checks,details_log,next_actions,ok,summary}"]
|
|
402
|
-
fn dispatch_routes_preflight_real_machine() {
|
|
403
|
-
let ws = tmp_workspace();
|
|
404
|
-
let code = run(
|
|
405
|
-
&cli_argv(&["preflight", "--team", &ws.to_string_lossy(), "--json"]),
|
|
406
|
-
&ws,
|
|
407
|
-
);
|
|
408
|
-
assert_eq!(
|
|
409
|
-
code,
|
|
410
|
-
ExitCode::Ok,
|
|
411
|
-
"preflight must ROUTE + exit 0 on a valid team dir"
|
|
412
|
-
);
|
|
413
|
-
let _ = std::fs::remove_dir_all(&ws);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// ── wait-ready (#[ignore] real-machine) ── golden parser.py:171 `cmd_wait_ready` -> runtime.wait_ready
|
|
417
|
-
// (ws, timeout). Polls worker readiness; golden -> {"details_log":...,"next_actions":[...],"ok":bool,
|
|
418
|
-
// "readiness":{cli_prompt_ready,mcp_ready,process_started,task_prompt_delivered},"summary":...}.
|
|
419
|
-
// ok:true (exit 0) needs a LIVE ready team; CI has none -> golden-exits 1 (false-green vs unknown).
|
|
420
|
-
#[test]
|
|
421
|
-
#[ignore = "real-machine: `wait-ready` polls a LIVE team's readiness; with no workers golden-exits 1 \
|
|
422
|
-
(false-green vs unknown-subcommand Error). Routes to cmd_wait_ready (parser.py:171); shape \
|
|
423
|
-
{details_log,next_actions,ok,readiness{cli_prompt_ready,mcp_ready,process_started,task_prompt_delivered},summary}"]
|
|
424
|
-
fn dispatch_routes_wait_ready_real_machine() {
|
|
425
|
-
let ws = tmp_workspace();
|
|
426
|
-
let code = run(
|
|
427
|
-
&cli_argv(&[
|
|
428
|
-
"wait-ready",
|
|
429
|
-
"--workspace",
|
|
430
|
-
&ws.to_string_lossy(),
|
|
431
|
-
"--timeout",
|
|
432
|
-
"1",
|
|
433
|
-
"--json",
|
|
434
|
-
]),
|
|
435
|
-
&ws,
|
|
436
|
-
);
|
|
437
|
-
assert_eq!(
|
|
438
|
-
code,
|
|
439
|
-
ExitCode::Ok,
|
|
440
|
-
"wait-ready must ROUTE + exit 0 once the team is ready"
|
|
441
|
-
);
|
|
442
|
-
let _ = std::fs::remove_dir_all(&ws);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// ── e2e --providers fake (#[ignore] real-machine) ── golden parser.py:449 `cmd_e2e` (cli/e2e.py:12).
|
|
446
|
-
// `--providers fake` runs a REAL end-to-end: runtime.launch(spec, auto_approve=True) (spawns a tmux
|
|
447
|
-
// team) -> send_message -> sleep -> collect -> shutdown. Result envelope (e2e.py:16,48-56):
|
|
448
|
-
// {"workspace":str,"providers":{"fake":{"ok":bool,"launch":{...},"send":{...},"collect":{...},
|
|
449
|
-
// "shutdown":{...}}},"ok":bool}. Real tmux spawn -> #[ignore] (NOT runnable in-process).
|
|
450
|
-
#[test]
|
|
451
|
-
#[ignore = "real-machine: `e2e --providers fake` calls runtime.launch -> spawns a REAL tmux team \
|
|
452
|
-
(send/collect/shutdown). Routes to cmd_e2e (parser.py:449 / e2e.py:12); envelope \
|
|
453
|
-
{workspace,providers:{fake:{ok,launch,send,collect,shutdown}},ok}"]
|
|
454
|
-
fn dispatch_routes_e2e_fake_real_machine() {
|
|
455
|
-
let ws = tmp_workspace();
|
|
456
|
-
let code = run(
|
|
457
|
-
&cli_argv(&[
|
|
458
|
-
"e2e",
|
|
459
|
-
"--providers",
|
|
460
|
-
"fake",
|
|
461
|
-
"--workspace",
|
|
462
|
-
&ws.to_string_lossy(),
|
|
463
|
-
"--json",
|
|
464
|
-
]),
|
|
465
|
-
&ws,
|
|
466
|
-
);
|
|
467
|
-
assert_eq!(
|
|
468
|
-
code,
|
|
469
|
-
ExitCode::Ok,
|
|
470
|
-
"e2e --providers fake must ROUTE + run the fake end-to-end (ok:true)"
|
|
471
|
-
);
|
|
472
|
-
let _ = std::fs::remove_dir_all(&ws);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// ── peek (#[ignore] real-machine) ── golden parser.py:201 `cmd_peek` (commands.py:118). Requires
|
|
476
|
-
// `--allow-raw-screen` (else TeamAgentError) + a mutually-exclusive --head/--tail/--search; then
|
|
477
|
-
// runtime.peek captures a LIVE tmux pane (tmux capture-pane). Real tmux -> #[ignore]. Routes to
|
|
478
|
-
// cmd_peek; --json returns the peek dict (text + capture metadata).
|
|
479
|
-
#[test]
|
|
480
|
-
#[ignore = "real-machine: `peek <agent> --tail N --allow-raw-screen` captures a LIVE tmux pane \
|
|
481
|
-
(tmux capture-pane); needs a running worker pane. Routes to cmd_peek (parser.py:201); \
|
|
482
|
-
--json returns the peek dict (raw screen text + metadata)"]
|
|
483
|
-
fn dispatch_routes_peek_real_machine() {
|
|
484
|
-
let ws = tmp_workspace();
|
|
485
|
-
let code = run(
|
|
486
|
-
&cli_argv(&[
|
|
487
|
-
"peek",
|
|
488
|
-
"fake_impl",
|
|
489
|
-
"--workspace",
|
|
490
|
-
&ws.to_string_lossy(),
|
|
491
|
-
"--tail",
|
|
492
|
-
"20",
|
|
493
|
-
"--allow-raw-screen",
|
|
494
|
-
"--json",
|
|
495
|
-
]),
|
|
496
|
-
&ws,
|
|
497
|
-
);
|
|
498
|
-
assert_eq!(
|
|
499
|
-
code,
|
|
500
|
-
ExitCode::Ok,
|
|
501
|
-
"peek must ROUTE + capture the live pane (real machine)"
|
|
502
|
-
);
|
|
503
|
-
let _ = std::fs::remove_dir_all(&ws);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
249
|
// CONTRACT (shared-root, real-machine-driven; golden = correct-behavior baseline): wait_readiness
|
|
507
250
|
// derives cli_prompt_ready from the LIFECYCLE status — an alive worker (status="running") IS
|
|
508
251
|
// cli_prompt_ready (golden quick_start.py:173 `status ∈ {running, busy}`). Rust (diagnose.rs:280)
|
|
@@ -129,11 +129,9 @@ mod leader_watch;
|
|
|
129
129
|
mod main_preserved;
|
|
130
130
|
mod missing_subcommands;
|
|
131
131
|
mod named_address;
|
|
132
|
-
mod peer_allow;
|
|
133
132
|
mod run_delegation;
|
|
134
133
|
mod shutdown_kill_plan;
|
|
135
134
|
mod status_brief;
|
|
136
135
|
mod status_send;
|
|
137
136
|
mod verb_install_skill;
|
|
138
137
|
mod verb_profile;
|
|
139
|
-
mod verb_validate;
|
|
@@ -248,12 +248,9 @@ fn resolve_qualified_name_prefers_unique_state_pane_over_same_window_idle_pane()
|
|
|
248
248
|
pane("team-a", "qa", "%idle"),
|
|
249
249
|
]);
|
|
250
250
|
|
|
251
|
-
let resolved =
|
|
252
|
-
&ws,
|
|
253
|
-
|
|
254
|
-
&transport,
|
|
255
|
-
)
|
|
256
|
-
.expect("a qualified worker address should use its unique state pane");
|
|
251
|
+
let resolved =
|
|
252
|
+
resolve_name_with_transport(&ws, &format!("{}::team-a/qa", ws.display()), &transport)
|
|
253
|
+
.expect("a qualified worker address should use its unique state pane");
|
|
257
254
|
assert_eq!(resolved.pane_id, "%worker");
|
|
258
255
|
assert_eq!(resolved.state_pane_id.as_deref(), Some("%worker"));
|
|
259
256
|
assert!(!resolved.state_pane_stale);
|
|
@@ -271,11 +268,17 @@ fn resolve_qualified_name_state_pane_must_uniquely_match_scoped_candidates() {
|
|
|
271
268
|
.as_object_mut()
|
|
272
269
|
.unwrap()
|
|
273
270
|
.remove("pane_id");
|
|
274
|
-
vec![
|
|
271
|
+
vec![
|
|
272
|
+
pane("team-a", "qa", "%worker"),
|
|
273
|
+
pane("team-a", "qa", "%idle"),
|
|
274
|
+
]
|
|
275
275
|
}
|
|
276
276
|
"stale" => {
|
|
277
277
|
team["agents"]["qa"]["pane_id"] = json!("%stale");
|
|
278
|
-
vec![
|
|
278
|
+
vec![
|
|
279
|
+
pane("team-a", "qa", "%worker"),
|
|
280
|
+
pane("team-a", "qa", "%idle"),
|
|
281
|
+
]
|
|
279
282
|
}
|
|
280
283
|
"cross-session" => vec![
|
|
281
284
|
pane("old-team-a", "qa", "%worker"),
|
|
@@ -293,12 +296,9 @@ fn resolve_qualified_name_state_pane_must_uniquely_match_scoped_candidates() {
|
|
|
293
296
|
};
|
|
294
297
|
seed_state(&ws, state_with_teams(json!({"team-a": team})));
|
|
295
298
|
let transport = OfflineTransport::new().with_targets(targets);
|
|
296
|
-
let err =
|
|
297
|
-
&ws,
|
|
298
|
-
|
|
299
|
-
&transport,
|
|
300
|
-
)
|
|
301
|
-
.expect_err("non-unique state pane evidence must remain fail-closed");
|
|
299
|
+
let err =
|
|
300
|
+
resolve_name_with_transport(&ws, &format!("{}::team-a/qa", ws.display()), &transport)
|
|
301
|
+
.expect_err("non-unique state pane evidence must remain fail-closed");
|
|
302
302
|
assert_eq!(err.kind, NamedAddressErrorKind::NameAmbiguous, "{scenario}");
|
|
303
303
|
let _ = std::fs::remove_dir_all(&ws);
|
|
304
304
|
}
|
|
@@ -313,11 +313,8 @@ fn resolve_qualified_name_canonicalizes_legacy_session_alias() {
|
|
|
313
313
|
"teamdir": worker_team("team-displayname", "qa", "%1", "qa")
|
|
314
314
|
})),
|
|
315
315
|
);
|
|
316
|
-
let transport =
|
|
317
|
-
"team-displayname",
|
|
318
|
-
"qa",
|
|
319
|
-
"%1",
|
|
320
|
-
)]);
|
|
316
|
+
let transport =
|
|
317
|
+
OfflineTransport::new().with_targets(vec![pane("team-displayname", "qa", "%1")]);
|
|
321
318
|
|
|
322
319
|
let resolved = resolve_name_with_transport(&ws, "displayname/qa", &transport)
|
|
323
320
|
.expect("qualified legacy alias must resolve through the canonical selector");
|
|
@@ -380,21 +377,21 @@ fn send_to_name_mutual_exclusion() {
|
|
|
380
377
|
let args = named_send_args(&cwd, Some("team-a/qa"), Some("%1"), None, &["hello"]);
|
|
381
378
|
let err = cmd_send(&args).unwrap_err();
|
|
382
379
|
assert!(
|
|
383
|
-
matches!(err, CliError::Usage(ref message) if message.contains("--to-name
|
|
380
|
+
matches!(err, CliError::Usage(ref message) if message.contains("--to-name/--pane/--to") && message.contains("不能同时")),
|
|
384
381
|
"expected --to-name mutual exclusion, got {err:?}"
|
|
385
382
|
);
|
|
386
383
|
|
|
387
384
|
let args = named_send_args(&cwd, Some("team-a/qa"), None, Some("worker"), &["hello"]);
|
|
388
385
|
let err = cmd_send(&args).unwrap_err();
|
|
389
386
|
assert!(
|
|
390
|
-
matches!(err, CliError::Usage(ref message) if message.contains("--to-name
|
|
387
|
+
matches!(err, CliError::Usage(ref message) if message.contains("--to-name/--pane/--to") && message.contains("不能同时")),
|
|
391
388
|
"expected --to-name/--to mutual exclusion, got {err:?}"
|
|
392
389
|
);
|
|
393
390
|
|
|
394
391
|
let args = named_send_args(&cwd, Some("team-a/qa"), None, None, &[]);
|
|
395
392
|
let err = cmd_send(&args).unwrap_err();
|
|
396
393
|
assert!(
|
|
397
|
-
matches!(err, CliError::Usage(ref message) if message == "
|
|
394
|
+
matches!(err, CliError::Usage(ref message) if message == "请填写任务内容;--to-name 不能发送空消息"),
|
|
398
395
|
"expected empty-message usage error, got {err:?}"
|
|
399
396
|
);
|
|
400
397
|
let _ = std::fs::remove_dir_all(&cwd);
|
|
@@ -645,7 +645,7 @@ fn cmd_send_joins_message_with_single_space() {
|
|
|
645
645
|
);
|
|
646
646
|
assert_eq!(
|
|
647
647
|
v.get("reminder").and_then(|reminder| reminder.as_str()),
|
|
648
|
-
Some("
|
|
648
|
+
Some("任务已收下,但还未确认送到队友对话。查看 team-agent status/doctor,等真实回复,不反复重发。")
|
|
649
649
|
);
|
|
650
650
|
}
|
|
651
651
|
}
|
|
@@ -654,22 +654,27 @@ fn cmd_send_joins_message_with_single_space() {
|
|
|
654
654
|
}
|
|
655
655
|
|
|
656
656
|
#[test]
|
|
657
|
-
fn
|
|
657
|
+
fn cmd_send_default_human_output_is_brief_without_false_delivered() {
|
|
658
658
|
let r = cmd_send(&queued_send_args_fixture(false)).expect("cmd_send returns CmdResult");
|
|
659
659
|
assert!(!r.as_json);
|
|
660
660
|
let text = emit(&r.output, r.as_json).expect("send should render human text");
|
|
661
661
|
let lines: Vec<_> = text.lines().collect();
|
|
662
662
|
assert_eq!(
|
|
663
663
|
lines.len(),
|
|
664
|
-
|
|
665
|
-
"
|
|
664
|
+
2,
|
|
665
|
+
"send must separate outcome and next action: {text}"
|
|
666
666
|
);
|
|
667
667
|
assert!(
|
|
668
|
-
lines[0].contains("
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
"
|
|
668
|
+
lines[0].contains("任务已收下") && lines[0].contains("还未送到队友的对话"),
|
|
669
|
+
"queued send must report its true pending outcome: {text}"
|
|
670
|
+
);
|
|
671
|
+
assert!(
|
|
672
|
+
lines[1].contains("下一步")
|
|
673
|
+
&& lines[1].contains("team-agent status")
|
|
674
|
+
&& lines[1].contains("doctor")
|
|
675
|
+
&& lines[1].contains("等真实回复")
|
|
676
|
+
&& lines[1].contains("不反复重发"),
|
|
677
|
+
"queued send must give honest next steps: {text}"
|
|
673
678
|
);
|
|
674
679
|
assert!(
|
|
675
680
|
!text.contains("delivered"),
|
|
@@ -946,7 +951,7 @@ fn cmd_send_unknown_task_surfaces_golden_error_envelope_not_silent() {
|
|
|
946
951
|
);
|
|
947
952
|
assert_eq!(
|
|
948
953
|
payload.action,
|
|
949
|
-
"
|
|
954
|
+
"先运行 team-agent doctor --workspace . 检查所选队伍,或查看此处列出的错误日志。"
|
|
950
955
|
);
|
|
951
956
|
let _ = std::fs::remove_dir_all(&ws);
|
|
952
957
|
}
|
|
@@ -62,8 +62,8 @@ fn install_skill_missing_source_is_usage_error() {
|
|
|
62
62
|
);
|
|
63
63
|
assert_eq!(
|
|
64
64
|
code,
|
|
65
|
-
ExitCode::
|
|
66
|
-
"
|
|
65
|
+
ExitCode::Usage,
|
|
66
|
+
"known missing --source must give pure Usage2 guidance"
|
|
67
67
|
);
|
|
68
68
|
let _ = std::fs::remove_dir_all(&ws);
|
|
69
69
|
}
|