@team-agent/installer 0.3.29 → 0.3.31

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 CHANGED
@@ -566,7 +566,7 @@ dependencies = [
566
566
 
567
567
  [[package]]
568
568
  name = "team-agent"
569
- version = "0.3.29"
569
+ version = "0.3.31"
570
570
  dependencies = [
571
571
  "anyhow",
572
572
  "chrono",
package/Cargo.toml CHANGED
@@ -9,7 +9,7 @@ members = ["crates/team-agent"]
9
9
 
10
10
  [workspace.package]
11
11
  edition = "2021"
12
- version = "0.3.29"
12
+ version = "0.3.31"
13
13
  license = "AGPL-3.0"
14
14
  rust-version = "1.95"
15
15
 
@@ -157,9 +157,7 @@ fn prepare_profile_launch(
157
157
  let mut claude_projects_root = None;
158
158
  let mut managed_mcp_config = false;
159
159
 
160
- if matches!(agent.provider, Provider::Claude | Provider::ClaudeCode)
161
- && agent.auth_mode == AuthMode::CompatibleApi
162
- {
160
+ if matches!(agent.provider, Provider::Claude | Provider::ClaudeCode) {
163
161
  let dir = compatible_claude_config_dir(workspace, &agent.id)?;
164
162
  if let Some(config) = mcp_config {
165
163
  ensure_compatible_claude_mcp_config(&dir, workspace, config)?;
@@ -382,13 +382,13 @@ pub fn deliver_pending_message(
382
382
  };
383
383
  let submit_verified = inject_submit_verified(&inject_report);
384
384
  let readback_verified = pane_readback_verified(&inject_report);
385
- // Worker panes run provider TUIs, so delivery needs both submit consumption
386
- // and pane readback. The leader pane is a human-facing receiver: no provider
387
- // consumes the token, so readback is the delivery proof.
385
+ // Leader pane: inject success is delivery proof. Worker pane: post-submit
386
+ // evidence is the delivery proof; stale Phase-1 readback must not veto it
387
+ // and cannot independently prove the current Enter submitted.
388
388
  let verified = if is_leader_recipient {
389
389
  true
390
390
  } else {
391
- submit_verified && readback_verified
391
+ submit_verified
392
392
  };
393
393
  if !verified {
394
394
  let reason = if !readback_verified {
@@ -507,13 +507,9 @@ pub(crate) fn inject_submit_verified(report: &InjectReport) -> bool {
507
507
  }
508
508
  }
509
509
 
510
- /// U1 #7 step-2: pane-readback gate. The submit may report verified (a key was sent)
511
- /// while the pasted token never actually landed in the pane — `inject()` step-1 now
512
- /// surfaces that as `InjectVerification::CaptureMissingToken`. A delivery is only truly
513
- /// verified when BOTH the submit succeeded AND the token was read back as visible.
514
- /// `CaptureMissingToken` → readback failed → not delivered (degraded/unverified). All
515
- /// other inject_verification variants (incl. token-less payloads, empty sends, new
516
- /// pasted-content prompt) are not readback-negative and pass this gate.
510
+ /// U1 #7 step-2: pane-readback gate. `CaptureMissingToken` is negative readback
511
+ /// evidence, but 0.3.30 submit evidence can supersede it: consumption or
512
+ /// post-submit token observation proves the message reached the pane.
517
513
  /// 0.3.27: promoted to pub(crate) for leader_receiver.rs verification gate.
518
514
  pub(crate) fn pane_readback_verified(report: &InjectReport) -> bool {
519
515
  !matches!(
@@ -141,7 +141,7 @@ pub(crate) fn non_provider_command(command: &str) -> Option<&str> {
141
141
 
142
142
  pub(crate) fn latest_prompt_signal(scrollback: &str) -> Option<AgentActivity> {
143
143
  // E47 ROOT-FIX#2 (0.3.24 P0, idle/busy 假阳): limit the scan to the
144
- // BOTTOM ACTIVE REGION (last 1-3 non-empty lines = composer / status
144
+ // BOTTOM ACTIVE REGION (last 1-5 non-empty lines = composer / status
145
145
  // line). The pre-fix `rfind` across the whole Tail(40) buffer let a
146
146
  // historical spinner/`Working` token out-position the live `❯`/`›`
147
147
  // composer (the rfind-recency family bug — same shape as the #320
@@ -155,7 +155,7 @@ pub(crate) fn latest_prompt_signal(scrollback: &str) -> Option<AgentActivity> {
155
155
  .lines()
156
156
  .rev()
157
157
  .filter(|line| !line.trim().is_empty())
158
- .take(3)
158
+ .take(5)
159
159
  .collect();
160
160
  if active_region.is_empty() {
161
161
  return None;
@@ -168,11 +168,13 @@ pub(crate) fn latest_prompt_signal(scrollback: &str) -> Option<AgentActivity> {
168
168
  has_idle_prompt = true;
169
169
  }
170
170
  // codex live spinner shapes (provider/adapter.rs:875-876 markers):
171
- // braille spinner, `• Working (`, `Thinking`; claude `✶`/`✢` per
172
- // adapter; we look for STRUCTURAL composer signals.
171
+ // braille spinner, `• Working (`, `Thinking`; claude `✶`/`✢`/`✻`
172
+ // and Claude Code tool-progress verbs. We look for STRUCTURAL
173
+ // composer/status signals in the active region only.
173
174
  if [
174
175
  "working (", "thinking", "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦",
175
- "⠧", "⠇", "⠏", "✶", "✢",
176
+ "⠧", "⠇", "⠏", "✶", "✢", "✻", "analyzing", "reading", "writing",
177
+ "searching", "running", "editing",
176
178
  ]
177
179
  .iter()
178
180
  .any(|needle| lower.contains(needle))
@@ -393,17 +393,12 @@ pub fn deliver_to_leader_fallback_pane(
393
393
 
394
394
  match inject_result {
395
395
  Ok(report) => {
396
- // 0.3.27 P0: leader_receiver verification gate. The pre-fix path
397
- // marked every inject Ok as delivered regardless of whether the
398
- // submit was actually verified. Apply the same two-gate check that
399
- // deliver_pending_message uses (delivery.rs:376-381):
400
- // (a) inject_submit_verified — Enter was accepted by the TUI
401
- // (b) pane_readback_verified — token was visible in the pane
402
- // Both must pass to mark delivered; otherwise degrade to
403
- // submitted_unverified (loud, not silent).
396
+ // 0.3.30: submit verification is enough for fallback-pane
397
+ // delivery. Readback is retained for diagnostics when submit
398
+ // itself is unverified.
404
399
  let submit_ok = super::delivery::inject_submit_verified(&report);
405
400
  let readback_ok = super::delivery::pane_readback_verified(&report);
406
- if submit_ok && readback_ok {
401
+ if submit_ok {
407
402
  store.mark(message_id, "delivered", None)?;
408
403
  event_log.write(
409
404
  "leader_receiver.fallback_pane_submitted",
@@ -6,6 +6,11 @@ fn e23_fallback_pane_has_single_shared_noisy_surface() {
6
6
  assert!(leader_receiver.contains("leader_receiver.fallback_pane_submitted"));
7
7
  assert!(leader_receiver.contains("leader_receiver.fallback_pane_failed"));
8
8
  assert!(leader_receiver.contains("delivered_via=fallback_pane"));
9
+ assert!(
10
+ leader_receiver.contains("if submit_ok {")
11
+ && !leader_receiver.contains("submit_ok && readback_ok"),
12
+ "fallback pane delivery must accept submit_ok alone; stale readback must not veto it"
13
+ );
9
14
  assert!(
10
15
  leader_receiver.find("if primary_ok").unwrap()
11
16
  < leader_receiver
@@ -1269,7 +1269,9 @@ fn fire_due_scheduled_events_fires_each_scheduled_kind() {
1269
1269
  );
1270
1270
  }
1271
1271
 
1272
- struct UnverifiedInjectTransport;
1272
+ struct UnverifiedInjectTransport {
1273
+ readback_visible: bool,
1274
+ }
1273
1275
  impl Transport for UnverifiedInjectTransport {
1274
1276
  fn kind(&self) -> BackendKind {
1275
1277
  BackendKind::Tmux
@@ -1303,7 +1305,11 @@ impl Transport for UnverifiedInjectTransport {
1303
1305
  ) -> Result<InjectReport, TransportError> {
1304
1306
  Ok(InjectReport {
1305
1307
  stage_reached: crate::transport::InjectStage::Submit,
1306
- inject_verification: crate::transport::InjectVerification::CaptureContainsToken,
1308
+ inject_verification: if self.readback_visible {
1309
+ crate::transport::InjectVerification::CaptureContainsToken
1310
+ } else {
1311
+ crate::transport::InjectVerification::CaptureMissingToken
1312
+ },
1307
1313
  submit_verification:
1308
1314
  crate::transport::SubmitVerification::PastedContentPromptStillPresentAfterSubmit,
1309
1315
  turn_verification: crate::transport::TurnVerification::NotYetObserved,
@@ -1441,12 +1447,8 @@ impl Transport for FailingInjectTransport {
1441
1447
  }
1442
1448
  }
1443
1449
 
1444
- // U1 #7 step-2 (RED→GREEN) pane-readback gate: a token payload whose token was NOT
1445
- // read back as visible in the pane (`InjectVerification::CaptureMissingToken`, surfaced by
1446
- // the step-1 tmux_backend readback) must NOT be counted delivered, even when the SUBMIT
1447
- // itself verified. Today the delivery gate only consulted submit_verification → a silent
1448
- // paste-drop reported delivered (false green). This transport returns a verified submit
1449
- // (KeySentAfterVisibleToken) but a missing-token readback.
1450
+ // 0.3.30: submit evidence supersedes stale readback. This transport returns a
1451
+ // verified submit but a missing-token readback.
1450
1452
  struct ReadbackMissingTransport;
1451
1453
  impl Transport for ReadbackMissingTransport {
1452
1454
  fn kind(&self) -> BackendKind {
@@ -1507,7 +1509,7 @@ impl Transport for ReadbackMissingTransport {
1507
1509
  }
1508
1510
 
1509
1511
  #[test]
1510
- fn deliver_pending_readback_missing_token_is_not_delivered() {
1512
+ fn deliver_pending_submit_verified_overrides_missing_readback() {
1511
1513
  let ws = tmp_ws("readbackmiss");
1512
1514
  let store = store_for(&ws);
1513
1515
  let log = EventLog::new(&ws);
@@ -1532,23 +1534,47 @@ fn deliver_pending_readback_missing_token_is_not_delivered() {
1532
1534
  .unwrap();
1533
1535
 
1534
1536
  assert!(
1535
- !out.ok,
1536
- "U1 #7: a submit-verified inject whose token was NOT read back (CaptureMissingToken) \
1537
- must NOT be delivered — readback gate catches the silent paste drop"
1537
+ out.ok,
1538
+ "0.3.30: submit-verified delivery must not be vetoed by stale CaptureMissingToken readback"
1538
1539
  );
1539
- assert_ne!(
1540
+ assert_eq!(
1540
1541
  out.message_status.0, "delivered",
1541
- "U1 #7: readback-missing must not mark the message delivered (false green)"
1542
+ "0.3.30: submit evidence is stronger than readback"
1542
1543
  );
1543
- let events = log.tail(0).unwrap();
1544
+ }
1545
+
1546
+ #[test]
1547
+ fn deliver_pending_submit_unverified_is_not_saved_by_readback() {
1548
+ let ws = tmp_ws("readbacksaves");
1549
+ let store = store_for(&ws);
1550
+ let log = EventLog::new(&ws);
1551
+ let state = serde_json::json!({
1552
+ "session_name": "team-readbacksaves",
1553
+ "leader_receiver": {"pane_id": "%leader"},
1554
+ "agents": {"w1": {"provider": "fake", "pane_id": "%1"}}
1555
+ });
1556
+ crate::state::persist::save_runtime_state(&ws, &state).unwrap();
1557
+ let message_id = store
1558
+ .create_message(None, "leader", "w1", "ping", None, false, None)
1559
+ .unwrap();
1560
+
1561
+ let out = deliver_pending_message(
1562
+ &ws,
1563
+ &store,
1564
+ &UnverifiedInjectTransport {
1565
+ readback_visible: true,
1566
+ },
1567
+ &message_id,
1568
+ &log,
1569
+ &state,
1570
+ )
1571
+ .unwrap();
1572
+
1544
1573
  assert!(
1545
- events.iter().any(|event| event
1546
- .get("reason")
1547
- .and_then(serde_json::Value::as_str)
1548
- .map(|r| r.contains("pane_readback_unverified"))
1549
- .unwrap_or(false)),
1550
- "U1 #7: the failure reason must name pane_readback_unverified (loud, not silent); got {events:?}"
1574
+ !out.ok,
1575
+ "0.3.30: positive readback alone must not mark delivered when submit is unverified"
1551
1576
  );
1577
+ assert_ne!(out.message_status.0, "delivered");
1552
1578
  }
1553
1579
 
1554
1580
  #[test]
@@ -1569,7 +1595,9 @@ fn deliver_pending_exhausted_unverified_send_emits_failed_event() {
1569
1595
  let out = deliver_pending_message(
1570
1596
  &ws,
1571
1597
  &store,
1572
- &UnverifiedInjectTransport,
1598
+ &UnverifiedInjectTransport {
1599
+ readback_visible: false,
1600
+ },
1573
1601
  &message_id,
1574
1602
  &log,
1575
1603
  &state,
@@ -622,6 +622,45 @@ fn e47_claude_live_working_in_bottom_active_region_classifies_working() {
622
622
  );
623
623
  }
624
624
 
625
+ #[test]
626
+ fn worker_state_claude_code_busy_signal_within_bottom_five_classifies_working() {
627
+ let scrollback = "older output\n\
628
+ ✻ Searching files\n\
629
+ Reading src/lib.rs\n\
630
+ tool progress\n\
631
+ status line\n\
632
+ › Continue?\n\
633
+ ❯ ";
634
+ let st = serde_json::json!({});
635
+ let a = classify_agent_activity(&st, scrollback, false, Some("claude"), None);
636
+ assert_eq!(
637
+ a.status,
638
+ ActivityStatus::Working,
639
+ "Claude Code busy signal inside the bottom five active lines must \
640
+ classify Working even when an idle prompt glyph is also visible. Got {a:?}"
641
+ );
642
+ }
643
+
644
+ #[test]
645
+ fn worker_state_historical_claude_code_busy_above_bottom_five_stays_idle() {
646
+ let scrollback = "✻ Searching old result\n\
647
+ historical tool output\n\
648
+ line 1\n\
649
+ line 2\n\
650
+ line 3\n\
651
+ line 4\n\
652
+ › Continue?\n\
653
+ ❯ ";
654
+ let st = serde_json::json!({});
655
+ let a = classify_agent_activity(&st, scrollback, false, Some("claude"), None);
656
+ assert_eq!(
657
+ a.status,
658
+ ActivityStatus::Idle,
659
+ "Claude Code busy signal above the bottom five active lines is \
660
+ scrollback history and must not override the live idle prompt. Got {a:?}"
661
+ );
662
+ }
663
+
625
664
  #[test]
626
665
  fn e47_iron_law_no_signal_in_bottom_region_stays_uncertain_not_idle() {
627
666
  // Defence: IRON LAW (activity.rs:3 / bug-071/077/085). When the bottom
@@ -623,21 +623,11 @@ CaptureMissingToken, not the static CaptureContainsToken false-positive"
623
623
  // no longer in the bottom 5 lines of the pane = composer cleared).
624
624
  // ═════════════════════════════════════════════════════════════════════════════
625
625
 
626
- /// **E46 RED-1 (核心假阳)**: token-bearing Text + Enter + bracketed paste.
627
- /// Token is visible in the pane after submit AND remains in the bottom of
628
- /// the pane (input region consumption was NOT observed). Submit must
629
- /// report `SubmitConsumptionUnverified`, NOT
630
- /// `EnterSentWithoutPlaceholderCheck`. Otherwise delivery emits delivered
631
- /// + message.delivered (假阳) even though the worker never consumed it.
632
- /// 0.3.28-final E55 truth source: grace fallback DELETED.
633
- /// `consumed=Some(false)` (token still in bottom 5 after all retries)
634
- /// MUST report SubmitConsumptionUnverified unconditionally. Pre-final
635
- /// returned EnterSentWithoutPlaceholderCheck when Phase-1 saw the token
636
- /// at all (token_visible_for_report=Some(true)) — but Phase-1 visibility
637
- /// only proves the paste LANDED, not that the provider CONSUMED it.
638
- /// That masked E55 busy-agent false positives as delivered=true.
626
+ /// 0.3.30 false-negative fix: token seen during post-submit consumption
627
+ /// polling proves the paste landed after Enter was sent. If it never
628
+ /// scrolls away, that is slow provider output, not transport failure.
639
629
  #[test]
640
- fn e46_inject_text_with_token_visible_but_unconsumed_reports_unverified() {
630
+ fn e46_post_submit_matched_token_without_scroll_is_verified() {
641
631
  let token_text = "Team Agent message from leader:\n\nhi\n\n[team-agent-token:msg_red1]";
642
632
  let (be, _rec) = backend_with(MockResp::Out(ok(token_text)), vec![]);
643
633
  let report = be
@@ -648,19 +638,19 @@ CaptureMissingToken, not the static CaptureContainsToken false-positive"
648
638
  true,
649
639
  )
650
640
  .expect("inject runs");
651
- // 0.3.28-final: no grace fallback. Token visible + not consumed →
652
- // SubmitConsumptionUnverified (delivery treats as not delivered).
653
641
  assert_eq!(
654
642
  report.submit_verification,
655
- SubmitVerification::SubmitConsumptionUnverified,
656
- "0.3.28-final: when token still in bottom 5 after all retries, \
657
- MUST be SubmitConsumptionUnverified grace fallback masking \
658
- this as EnterSentWithoutPlaceholderCheck caused E55 false \
659
- positives (paste landed but busy agent never consumed). \
660
- Got {:?}",
643
+ SubmitVerification::EnterSentWithoutPlaceholderCheck,
644
+ "0.3.30: post-submit matched=true is delivery proof even when \
645
+ the token stays in the bottom capture window. Got {:?}",
661
646
  report.submit_verification
662
647
  );
663
648
  assert_eq!(report.turn_verification, TurnVerification::NotYetObserved);
649
+ let diagnostics = report.submit_diagnostics.expect("diagnostics");
650
+ assert!(
651
+ diagnostics.attempts_detail.iter().any(|obs| obs.matched),
652
+ "the positive verdict must be backed by a post-submit matched observation"
653
+ );
664
654
  }
665
655
 
666
656
  #[test]
@@ -1492,6 +1492,7 @@ impl Transport for TmuxBackend {
1492
1492
  let mut consumption_attempts: u32 = 0;
1493
1493
  let mut consumed: Option<bool> = None;
1494
1494
  let mut attempts_detail: Vec<SubmitAttemptObservation> = Vec::new();
1495
+ let mut any_attempt_matched = false;
1495
1496
 
1496
1497
  let poll_consumption = !payload.skip_consumption_poll();
1497
1498
  if !poll_consumption {
@@ -1511,12 +1512,16 @@ impl Transport for TmuxBackend {
1511
1512
  if attempt > 0 {
1512
1513
  if let Some(m) = marker {
1513
1514
  if let Ok(cap) = self.capture(target, CaptureRange::Tail(40)) {
1514
- attempts_detail.push(submit_attempt_observation(
1515
+ let obs = submit_attempt_observation(
1515
1516
  attempt_index,
1516
1517
  &cap,
1517
1518
  marker,
1518
1519
  attempt_start.elapsed().as_millis() as u64,
1519
- ));
1520
+ );
1521
+ if obs.matched {
1522
+ any_attempt_matched = true;
1523
+ }
1524
+ attempts_detail.push(obs);
1520
1525
  if !token_in_bottom_n(&cap.text, m, 15) {
1521
1526
  consumed = Some(true);
1522
1527
  break;
@@ -1561,12 +1566,16 @@ impl Transport for TmuxBackend {
1561
1566
  std::thread::sleep(Duration::from_millis(100));
1562
1567
  match self.capture(target, CaptureRange::Tail(40)) {
1563
1568
  Ok(cap) => {
1564
- attempts_detail.push(submit_attempt_observation(
1569
+ let obs = submit_attempt_observation(
1565
1570
  attempt_index,
1566
1571
  &cap,
1567
1572
  marker,
1568
1573
  attempt_start.elapsed().as_millis() as u64,
1569
- ));
1574
+ );
1575
+ if obs.matched {
1576
+ any_attempt_matched = true;
1577
+ }
1578
+ attempts_detail.push(obs);
1570
1579
  if !token_in_bottom_n(&cap.text, m, 15) {
1571
1580
  found_consumed = true;
1572
1581
  break;
@@ -1592,45 +1601,40 @@ impl Transport for TmuxBackend {
1592
1601
 
1593
1602
  let submit_verification = match consumed {
1594
1603
  Some(true) => SubmitVerification::EnterSentWithoutPlaceholderCheck,
1595
- // 0.3.28-final (E55 truth source): consumed=Some(false)
1596
- // means we ran ALL retries and the token never left
1597
- // bottom 15 lines. That is a genuine consumption failure
1598
- // unless a provider busy marker proves the TUI is already
1599
- // processing the turn and simply has not scrolled yet;
1600
- // it MUST NOT be masked. Pre-final used a grace fallback
1601
- // that returned EnterSentWithoutPlaceholderCheck whenever
1602
- // token_visible_for_report=Some(true) — but Phase-1 token
1603
- // visibility only proves the paste landed, NOT that the
1604
- // provider consumed it. The fallback caused
1605
- // delivered=true under E55 (busy-agent paste-landed-not-
1606
- // consumed false positive). Do not restore that grace
1607
- // fallback; only a live busy-state signal upgrades this to
1608
- // EnterSentWithoutPlaceholderCheck.
1609
- Some(false) => match self.capture(target, CaptureRange::Tail(15)) {
1610
- Ok(cap) => {
1611
- attempts_detail.push(submit_attempt_observation(
1612
- consumption_attempts.max(1),
1613
- &cap,
1614
- marker,
1615
- inject_start.elapsed().as_millis() as u64,
1616
- ));
1617
- let busy = provider_busy_signal_in_tail(&cap.text);
1604
+ Some(false) => {
1605
+ if any_attempt_matched {
1618
1606
  eprintln!(
1619
- "team-agent submit consumption fallback: consumed=false busy_state={busy}"
1607
+ "team-agent submit consumption: consumed=false any_attempt_matched=true -> verified"
1620
1608
  );
1621
- if busy {
1622
- SubmitVerification::EnterSentWithoutPlaceholderCheck
1623
- } else {
1624
- SubmitVerification::SubmitConsumptionUnverified
1609
+ SubmitVerification::EnterSentWithoutPlaceholderCheck
1610
+ } else {
1611
+ match self.capture(target, CaptureRange::Tail(15)) {
1612
+ Ok(cap) => {
1613
+ attempts_detail.push(submit_attempt_observation(
1614
+ consumption_attempts.max(1),
1615
+ &cap,
1616
+ marker,
1617
+ inject_start.elapsed().as_millis() as u64,
1618
+ ));
1619
+ let busy = provider_busy_signal_in_tail(&cap.text);
1620
+ eprintln!(
1621
+ "team-agent submit consumption fallback: consumed=false any_attempt_matched=false busy_state={busy}"
1622
+ );
1623
+ if busy {
1624
+ SubmitVerification::EnterSentWithoutPlaceholderCheck
1625
+ } else {
1626
+ SubmitVerification::SubmitConsumptionUnverified
1627
+ }
1628
+ }
1629
+ Err(err) => {
1630
+ eprintln!(
1631
+ "team-agent submit consumption fallback: consumed=false busy_capture_error={err}"
1632
+ );
1633
+ SubmitVerification::SubmitConsumptionUnverified
1634
+ }
1625
1635
  }
1626
1636
  }
1627
- Err(err) => {
1628
- eprintln!(
1629
- "team-agent submit consumption fallback: consumed=false busy_capture_error={err}"
1630
- );
1631
- SubmitVerification::SubmitConsumptionUnverified
1632
- }
1633
- },
1637
+ }
1634
1638
  None => submit_verification_for_key(submit),
1635
1639
  };
1636
1640
  let total_elapsed_ms = inject_start.elapsed().as_millis() as u64;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.3.29",
3
+ "version": "0.3.31",
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.3.29",
24
- "@team-agent/cli-darwin-x64": "0.3.29",
25
- "@team-agent/cli-linux-x64": "0.3.29"
23
+ "@team-agent/cli-darwin-arm64": "0.3.31",
24
+ "@team-agent/cli-darwin-x64": "0.3.31",
25
+ "@team-agent/cli-linux-x64": "0.3.31"
26
26
  },
27
27
  "scripts": {
28
28
  "postinstall": "node npm/bincheck.mjs",