@team-agent/installer 0.5.86 → 0.5.91

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.
Files changed (39) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +104 -164
  4. package/crates/team-agent/src/cli/emit.rs +68 -87
  5. package/crates/team-agent/src/cli/mod.rs +1 -1
  6. package/crates/team-agent/src/cli/spec.rs +1 -4
  7. package/crates/team-agent/src/cli/status_port/brief.rs +187 -7
  8. package/crates/team-agent/src/cli/status_port/inbox.rs +42 -17
  9. package/crates/team-agent/src/cli/tests/lane_c.rs +34 -26
  10. package/crates/team-agent/src/cli/tests/main_preserved.rs +4 -70
  11. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +0 -68
  12. package/crates/team-agent/src/cli/types.rs +3 -16
  13. package/crates/team-agent/src/compiler.rs +4 -7
  14. package/crates/team-agent/src/coordinator/tests/tick_core.rs +5 -6
  15. package/crates/team-agent/src/coordinator/tick.rs +1 -16
  16. package/crates/team-agent/src/coordinator/types.rs +1 -1
  17. package/crates/team-agent/src/db/message_store.rs +10 -5
  18. package/crates/team-agent/src/fake_worker.rs +4 -4
  19. package/crates/team-agent/src/lifecycle/launch/identity.rs +39 -21
  20. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -1
  21. package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
  22. package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +3 -3
  23. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +2 -2
  24. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +0 -10
  25. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +2 -9
  26. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -26
  27. package/crates/team-agent/src/main.rs +7 -2
  28. package/crates/team-agent/src/mcp_server/mod.rs +1 -1
  29. package/crates/team-agent/src/messaging/mod.rs +2 -2
  30. package/crates/team-agent/src/messaging/results.rs +234 -386
  31. package/crates/team-agent/src/messaging/tests/runtime.rs +91 -135
  32. package/crates/team-agent/src/messaging/watchers.rs +1 -1
  33. package/crates/team-agent/src/model/enums.rs +17 -3
  34. package/crates/team-agent/src/model/spec.rs +8 -7
  35. package/crates/team-agent/src/provider/wire.rs +1 -1
  36. package/package.json +4 -4
  37. package/skills/team-agent/SKILL.md +3 -3
  38. package/skills/team-agent/command-coverage.json +2 -9
  39. package/skills/team-agent/references/team-agent-operator.md +661 -0
@@ -595,36 +595,16 @@ fn report_result_valid_envelope_returns_ok_with_result_id() {
595
595
  }
596
596
 
597
597
  #[test]
598
- fn report_result_funnels_into_leader_delivery_primitive_not_queued_scheduled_event() {
599
- // #230 N31/N32 funnel (cr verdict §3 I-3 + MUST-8):
600
- //
601
- // [OLD assertion] report_result inserted a parallel `scheduled_events(kind='send',
602
- // target='leader', status='pending')` row + returned `notification_status="queued"`,
603
- // and the worker-facing tool body claimed success while leader had not yet seen the
604
- // result. That queued-only path was MUST-8 / I-3 violating — `notification_status=
605
- // queued` was returned as success but the leader pane never actually got the text.
606
- //
607
- // [NEW assertion] report_result now synchronously funnels through the shared leader-
608
- // delivery primitive (`send_to_leader_receiver`), creating a `messages` row that
609
- // `deliver_pending_messages` picks up on the same tick (NO `scheduled_events` row).
610
- // Without a bound leader pane (this fixture has no `leader_receiver.pane_id`), the
611
- // primitive returns I-4 `rebind_required` (Blocked / ok=false) — the row is persisted
612
- // as `failed` for audit and the tool body's `notification_status` is `rebind_required`,
613
- // NOT a misleading `queued` success. The contract grep that bans `queue_report_result_
614
- // notification` / `notification_status="queued[_only]"` literals in `results.rs` is the
615
- // direct mechanical counterpart of this assertion.
616
- let ws = tmp_ws("reportnotify");
598
+ fn report_result_auto_finalizes_and_queues_compact_leader_notification() {
599
+ let ws = tmp_ws("reportauto");
617
600
  crate::state::persist::save_runtime_state(
618
601
  &ws,
619
602
  &serde_json::json!({
620
- "session_name": null,
621
- "leader": {"id": "leader"},
622
603
  "agents": {"worker": {"status": "running"}},
623
604
  "tasks": [{"id": "task_1", "status": "running", "assignee": "worker"}]
624
605
  }),
625
606
  )
626
607
  .unwrap();
627
- let store = store_for(&ws);
628
608
  let envelope = json(serde_json::json!({
629
609
  "schema_version": "result_envelope_v1",
630
610
  "task_id": "task_1",
@@ -639,63 +619,97 @@ fn report_result_funnels_into_leader_delivery_primitive_not_queued_scheduled_eve
639
619
  }));
640
620
 
641
621
  let out = report_result(&ws, &envelope).unwrap();
642
- let result_id = out
643
- .get("result_id")
644
- .and_then(|v| v.as_str())
645
- .expect("report_result returns generated result_id");
646
- assert!(
647
- result_id.starts_with("res_"),
648
- "MessageStore.add_result generates res_* ids; got {result_id}"
649
- );
622
+ assert_eq!(out["finalization_status"], "auto_finalized");
623
+ assert_eq!(out["notification_status"], "queued");
624
+ assert_eq!(out["leader_notified"], false);
650
625
 
651
- // No `scheduled_events` rows: the queued parallel path is gone.
626
+ let store = store_for(&ws);
652
627
  let conn = seed_conn(&store);
653
- let scheduled_count: i64 = conn
654
- .query_row("select count(*) from scheduled_events", [], |row| {
655
- row.get(0)
656
- })
628
+ let result_status: String = conn
629
+ .query_row("select status from results", [], |row| row.get(0))
630
+ .unwrap();
631
+ let message_count: i64 = conn
632
+ .query_row("select count(*) from messages", [], |row| row.get(0))
633
+ .unwrap();
634
+ assert_eq!(result_status, "collected");
635
+ assert_eq!(message_count, 1);
636
+ let (presentation, content): (String, String) = conn
637
+ .query_row(
638
+ "select presentation, content from messages",
639
+ [],
640
+ |row| Ok((row.get(0)?, row.get(1)?)),
641
+ )
657
642
  .unwrap();
658
643
  assert_eq!(
659
- scheduled_count, 0,
660
- "N31/N32 funnel: report_result must NOT insert a parallel scheduled_events 'send' row; the leader-delivery primitive is the single funnel"
661
- );
662
-
663
- // Tool body: no `queued`/`queued_only` notification_status. Without a bound leader
664
- // pane this fixture surfaces I-4 `rebind_required` (ok=false on the leader delivery,
665
- // but the result row + audit trail are durable for rebind replay).
666
- assert_eq!(
667
- out.get("notification_status").and_then(|v| v.as_str()),
668
- Some("rebind_required"),
669
- "I-4: unbound leader pane → rebind_required, never queued/queued_only success"
644
+ serde_json::from_str::<serde_json::Value>(&presentation).unwrap()["class"],
645
+ "stage_result"
670
646
  );
647
+ assert!(content.len() < 320, "leader notification must stay compact");
648
+ let state = crate::state::persist::load_runtime_state(&ws).unwrap();
649
+ assert_eq!(state["tasks"][0]["status"], "done");
671
650
  assert_eq!(
672
- out.get("leader_notified").and_then(|v| v.as_bool()),
673
- Some(false),
674
- "I-4: leader_notified=false when no leader pane is bound"
651
+ state["tasks"][0]["accepted_result_id"].as_str().is_some(),
652
+ true
675
653
  );
676
654
  assert!(
677
- out.get("notification_event_id")
678
- .is_some_and(|v| v.is_null()),
679
- "no scheduled_events row → notification_event_id is null"
655
+ std::fs::read_to_string(ws.join(".team/logs/events.jsonl"))
656
+ .unwrap()
657
+ .contains("\"collect.result\"")
680
658
  );
659
+ }
681
660
 
682
- // Audit events: the funnel emits leader_receiver.delivery_blocked (I-4 rebind),
683
- // and the legacy mcp.report_result_notify_queued audit is gone.
684
- let events_path = ws.join(".team").join("logs").join("events.jsonl");
685
- let event_lines =
686
- std::fs::read_to_string(events_path).expect("report_result writes events.jsonl");
687
- assert!(
688
- event_lines.contains("\"leader_receiver.delivery_blocked\""),
689
- "I-4 rebind path must emit leader_receiver.delivery_blocked audit; got {event_lines}",
690
- );
691
- assert!(
692
- !event_lines.contains("mcp.report_result_notify_queued"),
693
- "legacy queued-notification audit must be gone; got {event_lines}",
694
- );
695
- assert!(
696
- event_lines.contains("\"mcp.report_result\""),
697
- "report_result still emits its own audit event; got {event_lines}",
698
- );
661
+ #[test]
662
+ fn report_result_message_scope_finalizes_latest_assigned_task() {
663
+ let ws = tmp_ws("reportauto-message");
664
+ crate::state::persist::save_runtime_state(
665
+ &ws,
666
+ &serde_json::json!({
667
+ "active_team_key": "team-a",
668
+ "teams": {
669
+ "team-a": {
670
+ "agents": {"worker": {"status": "running"}},
671
+ "tasks": [{"id": "task_initial", "status": "pending", "assignee": "worker"}]
672
+ }
673
+ }
674
+ }),
675
+ )
676
+ .unwrap();
677
+ let store = store_for(&ws);
678
+ let conn = seed_conn(&store);
679
+ conn.execute(
680
+ "insert into messages(message_id, owner_team_id, sender, recipient, status, content, created_at) values (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
681
+ rusqlite::params![
682
+ "msg_turn",
683
+ "team-a",
684
+ "leader",
685
+ "worker",
686
+ "delivered",
687
+ "complete the task",
688
+ "2026-09-19T00:00:00Z",
689
+ ],
690
+ )
691
+ .unwrap();
692
+ let envelope = json(serde_json::json!({
693
+ "schema_version": "result_envelope_v1",
694
+ "task_id": "msg_turn",
695
+ "agent_id": "worker",
696
+ "status": "success",
697
+ "summary": "done",
698
+ "changes": [],
699
+ "tests": [],
700
+ "risks": [],
701
+ "artifacts": [],
702
+ "next_actions": []
703
+ }));
704
+
705
+ let out = report_result(&ws, &envelope).unwrap();
706
+ assert_eq!(out["finalization_status"], "auto_finalized");
707
+ assert_eq!(out["notification_status"], "queued");
708
+ let state = crate::state::persist::load_runtime_state(&ws).unwrap();
709
+ assert_eq!(state["teams"]["team-a"]["tasks"][0]["status"], "done");
710
+ assert!(state["teams"]["team-a"]["tasks"][0]["accepted_result_id"]
711
+ .as_str()
712
+ .is_some());
699
713
  }
700
714
 
701
715
  #[test]
@@ -2423,56 +2437,6 @@ fn retry_result_deliveries_retries_notify_failed_watcher() {
2423
2437
  );
2424
2438
  }
2425
2439
 
2426
- // ════════════════════════════════════════════════════════════════════════
2427
- // GROUP W — collect_results_and_notify_watchers orchestration shape.
2428
- // results.py:430-447.
2429
- // ════════════════════════════════════════════════════════════════════════
2430
-
2431
- #[test]
2432
- fn collect_results_and_notify_watchers_returns_concrete_ok_shape() {
2433
- // SEEDED contract (results.py:430-447): with NO uncollected results, collect() is
2434
- // skipped (the `if store.results(uncollected_only=True)` guard is false), so the
2435
- // result stays {ok:true, collected_results:[]}; a seeded notify_failed watcher
2436
- // whose result_id has no matching results row is resolved to None by
2437
- // retry_result_deliveries → skipped → notified stays []. Probed golden (against
2438
- // exactly this fixture): {"ok": true, "collected": 0, "notified": []}.
2439
- // (The previous test asserted only out["ok"].is_some(), trivially passed by
2440
- // {"ok": false}.)
2441
- let ws = tmp_ws("collectnotify");
2442
- let store = store_for(&ws);
2443
- let log = EventLog::new(&ws);
2444
-
2445
- seed_watcher(
2446
- &store,
2447
- "w-orphan",
2448
- "team-a",
2449
- "t1",
2450
- "alice",
2451
- "notify_failed",
2452
- Some("res_missing"),
2453
- None,
2454
- );
2455
-
2456
- let out = collect_results_and_notify_watchers(&ws, &log).unwrap();
2457
- assert_eq!(
2458
- out.get("ok").and_then(|v| v.as_bool()),
2459
- Some(true),
2460
- "ok==true"
2461
- );
2462
- assert_eq!(
2463
- out.get("collected").and_then(|v| v.as_i64()),
2464
- Some(0),
2465
- "no uncollected results → collected==0"
2466
- );
2467
- assert_eq!(
2468
- out.get("notified")
2469
- .and_then(|v| v.as_array())
2470
- .map(|a| a.len()),
2471
- Some(0),
2472
- "orphan watcher (missing result) is skipped → notified empty"
2473
- );
2474
- }
2475
-
2476
2440
  // ════════════════════════════════════════════════════════════════════════
2477
2441
  // GROUP X — delivered_result_message content-level dedupe lookup +
2478
2442
  // result_id_from_text dual (scheduler send dedupe path). result_delivery.py:394.
@@ -2892,27 +2856,19 @@ fn r8_attach_requeue_exhausted_to_notify_failed_golden_attach_event() {
2892
2856
  );
2893
2857
  }
2894
2858
 
2895
- // E15/E23 双投守卫:report_result 的 pane fallback 必须被 `if !outcome.ok` 守为
2896
- // deliver 失败时的真兜底,绝不在 deliver 成功后无条件再投一次(=用户看到两条同内容回复)。
2859
+ // E15/E23 single-funnel guard: report_result presents a compact result through
2860
+ // the shared leader receiver and does not carry a private direct-inject retry.
2897
2861
  #[test]
2898
- fn e15_direct_inject_is_gated_by_deliver_failure_not_unconditional() {
2862
+ fn e15_report_result_uses_shared_leader_receiver_without_private_retry() {
2899
2863
  let src = include_str!("../results.rs");
2900
- // E23:旧 private direct inject 已合并到 shared deliver_to_leader_fallback_pane primitive。
2901
- let gate = "if !outcome.ok {";
2902
- let fallback_call = "deliver_to_leader_fallback_pane(";
2903
- let gate_pos = src.find(gate);
2904
- assert!(
2905
- gate_pos.is_some(),
2906
- "E15/E23: pane fallback must be gated by `if !outcome.ok` (deliver-fail fallback)"
2907
- );
2908
- let fallback_after_gate = src[gate_pos.unwrap()..].find(fallback_call);
2909
2864
  assert!(
2910
- fallback_after_gate.is_some(),
2911
- "E15/E23: report_result fallback must call the shared fallback pane primitive after the failure gate"
2865
+ src.contains("send_to_leader_receiver_with_presentation"),
2866
+ "E15/E23: report_result must use the shared leader receiver presentation funnel"
2912
2867
  );
2913
2868
  assert!(
2914
- !src.contains("inject_leader_notification_direct"),
2915
- "E23: private direct inject must stay deleted; all callers use deliver_to_leader_fallback_pane"
2869
+ !src.contains("inject_leader_notification_direct")
2870
+ && !src.contains("deliver_to_leader_fallback_pane"),
2871
+ "E23: private direct and removed fallback inject paths must stay deleted"
2916
2872
  );
2917
2873
  }
2918
2874
 
@@ -27,7 +27,7 @@ use super::{MessagingError, WatcherNotice, RESULT_DELIVERY_MAX_ATTEMPTS};
27
27
 
28
28
  /// `notify_result_watchers` (`result_delivery.py:38`):匹配 + 去重 + (有界) 投递 result 给 leader
29
29
  /// watcher。**恰好一次** (Gap 32/38):同 result_id 多 watcher → 1 次注入,余 `superseded`。
30
- /// 去重唯一原语 = [`MessageStore::claim_leader_notification_delivery`]。`collect`/coordinator tick 调。
30
+ /// 去重唯一原语 = [`MessageStore::claim_leader_notification_delivery`]。由 watcher/rebind recovery 调用。
31
31
  pub(crate) fn notify_result_watchers(
32
32
  workspace: &Path,
33
33
  result: &serde_json::Value,
@@ -86,9 +86,8 @@ impl ProviderEffort {
86
86
  matches!(self, Self::Max)
87
87
  }
88
88
 
89
- /// True when the given provider supports this effort level. `max` is
90
- /// Claude-only; other levels are supported by Claude, Codex, and Grok.
91
- /// Copilot/Gemini/CursorAgent/Fake ignore the field (warning at runtime).
89
+ /// Native command support. Claude/Pi accept max; Codex/Grok accept
90
+ /// the other levels. Use resolve_for_provider for admission/ignore policy.
92
91
  pub fn is_supported_by(self, provider: Provider) -> bool {
93
92
  match provider {
94
93
  Provider::Claude | Provider::ClaudeCode | Provider::Pi => true,
@@ -98,6 +97,21 @@ impl ProviderEffort {
98
97
  }
99
98
  }
100
99
  }
100
+
101
+ /// One admission policy for compiler, direct specs and runtime commands.
102
+ /// Ok(None) preserves the legacy warning-and-ignore providers.
103
+ pub fn resolve_for_provider(self, provider: Provider) -> Result<Option<Self>, &'static str> {
104
+ if provider == Provider::CursorAgent {
105
+ return Err("cursor_agent does not support effort; the Cursor CLI has no --effort flag");
106
+ }
107
+ if self.is_supported_by(provider) {
108
+ Ok(Some(self))
109
+ } else if self.is_claude_only() {
110
+ Err("effort 'max' is only supported by claude/claude_code/pi")
111
+ } else {
112
+ Ok(None)
113
+ }
114
+ }
101
115
  }
102
116
 
103
117
  /// auth 模式(`AUTH_MODES` `profiles/constants.py:6`)。
@@ -18,7 +18,7 @@ use crate::model::ids::TaskId;
18
18
  use crate::model::task_graph::{find_dependency_cycle, TaskNode};
19
19
  use crate::model::yaml::Value as Yaml;
20
20
  use crate::model::yaml;
21
- use crate::provider::wire::{is_claude_family, parse_canonical_provider};
21
+ use crate::provider::wire::parse_canonical_provider;
22
22
 
23
23
  /// result_envelope_v1 顶层 required(= allowed)。
24
24
  const RESULT_REQUIRED: &[&str] = &[
@@ -463,15 +463,16 @@ fn check_agent(agent: &Yaml, path: &str, errors: &mut Vec<String>) {
463
463
  "{path}/effort: unknown effort '{raw}' (allowed: low|medium|high|xhigh|max)"
464
464
  ));
465
465
  }
466
- Some(effort) if effort.is_claude_only() => {
466
+ Some(effort) => {
467
467
  let provider = agent.get("provider").and_then(Yaml::as_str).unwrap_or("");
468
- if !parse_canonical_provider(provider).is_some_and(is_claude_family) {
469
- errors.push(format!(
470
- "{path}/effort: effort '{raw}' is only supported by claude/claude_code (provider: {provider})"
471
- ));
468
+ if let Some(provider_enum) = parse_canonical_provider(provider) {
469
+ if let Err(reason) = effort.resolve_for_provider(provider_enum) {
470
+ errors.push(format!(
471
+ "{path}/effort: {reason} (effort: {raw}; provider: {provider})"
472
+ ));
473
+ }
472
474
  }
473
475
  }
474
- Some(_) => {}
475
476
  }
476
477
  }
477
478
  check_keys_y(
@@ -231,7 +231,7 @@ mod tests {
231
231
  );
232
232
  assert!(
233
233
  model_spec_rs.contains("parse_canonical_provider")
234
- && model_spec_rs.contains("is_claude_family"),
234
+ && model_spec_rs.contains("resolve_for_provider"),
235
235
  "model spec must validate providers through typed helpers"
236
236
  );
237
237
  assert!(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.5.86",
3
+ "version": "0.5.91",
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.86",
24
- "@team-agent/cli-darwin-x64": "0.5.86",
25
- "@team-agent/cli-linux-x64": "0.5.86"
23
+ "@team-agent/cli-darwin-arm64": "0.5.91",
24
+ "@team-agent/cli-darwin-x64": "0.5.91",
25
+ "@team-agent/cli-linux-x64": "0.5.91"
26
26
  },
27
27
  "scripts": {
28
28
  "postinstall": "node npm/bincheck.mjs",
@@ -7,7 +7,7 @@ last_verified_against: "0.5.66"
7
7
 
8
8
  # Team Agent
9
9
 
10
- Sealed appliance for someone who just got the CLI. Operator handbook (permissions, models, routing, recovery): `docs/reference/team-agent-operator.md`.
10
+ Sealed appliance for someone who just got the CLI. Operator handbook (permissions, models, routing, recovery): `references/team-agent-operator.md`.
11
11
 
12
12
  If `team-agent --version` does not match `last_verified_against`, do not copy examples as current truth. Learn from the live CLI:
13
13
 
@@ -29,11 +29,11 @@ team-agent doctor --help
29
29
 
30
30
  **On failure:** if the CLI prints a structured `action`, run that `action` first, then stop. Do not guess flags. `coordinator.session_missing` is a self-healing transient — re-check `status --json`; do not shutdown because of it.
31
31
 
32
- The current user-facing agent is the leader (orchestrate only). Workers call `report_result` exactly once. Nested teams: `skills/team-agent/references/team-in-team.md`.
32
+ The current user-facing agent is the leader (orchestrate only). Workers call `report_result` exactly once. Nested teams: `references/team-in-team.md`.
33
33
 
34
34
  ## Provider Capability Matrix
35
35
 
36
- Claude / Codex / Copilot / Gemini / fake: `docs/reference/team-agent-operator.md`. These two are in the runtime but were missing from that table:
36
+ Claude / Codex / Copilot / Gemini / fake: `references/team-agent-operator.md`. These two are in the runtime but were missing from that table:
37
37
  `quick-start` / `restart` / `claim-leader` JSON is compact by default (`ok`, `status`/`reason`, next action, attach/send, and `readiness.all_workers_spawned`); pass `--detail` only for internal diagnostics.
38
38
 
39
39
  | Provider | Resume | Turn-state detection | Per-worker model override | Native session fork |
@@ -96,13 +96,6 @@
96
96
  "owner": "runtime-owner:provider-launcher",
97
97
  "plan": "Use a per-call hermetic codex PATH shim and assert the exact dangerous flag argv log plus the bound CLI exit/output without invoking a real provider."
98
98
  },
99
- {
100
- "command": "team-agent collect",
101
- "bucket": "declared_gap",
102
- "covered": false,
103
- "owner": "runtime-owner:results",
104
- "plan": "Execute the exact human collect argv after a worker result and bind assertions to presentation and consumption of that same result."
105
- },
106
99
  {
107
100
  "command": "team-agent doctor",
108
101
  "bucket": "declared_gap",
@@ -118,14 +111,14 @@
118
111
  "plan": "Use an isolated provider/session fixture or authorized subscription gate and bind assertions to fork success or its exact rollback refusal."
119
112
  },
120
113
  {
121
- "command": "team-agent inbox",
114
+ "command": "team-agent inbox <agent_id> -n 3",
122
115
  "bucket": "declared_gap",
123
116
  "covered": false,
124
117
  "owner": "runtime-owner:messaging",
125
118
  "plan": "Execute the exact default inbox argv and bind assertions to its selected recipient and message or empty-state output."
126
119
  },
127
120
  {
128
- "command": "team-agent inbox coder",
121
+ "command": "team-agent inbox coder -n 3",
129
122
  "bucket": "declared_gap",
130
123
  "covered": false,
131
124
  "owner": "runtime-owner:messaging",