claude-multiacc 2.0.58 → 2.0.59

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 (149) hide show
  1. package/README.md +111 -3
  2. package/bin/claude +323 -84
  3. package/bin/claude-accounts +273 -42
  4. package/bin/codex +15 -0
  5. package/bin/codex-accounts +31 -4
  6. package/docs/ACCOUNT_OPERATIONS.md +62 -2
  7. package/docs/AUTORESUME-HANDOFF.md +28 -3
  8. package/docs/AUTORESUME.md +275 -18
  9. package/docs/CODEX.md +1 -0
  10. package/docs/INSTALLATION.md +7 -1
  11. package/docs/VERIFICATION.md +4 -0
  12. package/lib/__pycache__/audit.cpython-312.pyc +0 -0
  13. package/lib/__pycache__/autoresume.cpython-312.pyc +0 -0
  14. package/lib/__pycache__/autoresume_cross.cpython-312.pyc +0 -0
  15. package/lib/__pycache__/autoresume_cross_dispatch.cpython-312.pyc +0 -0
  16. package/lib/__pycache__/autoresume_cross_receipt.cpython-312.pyc +0 -0
  17. package/lib/__pycache__/autoresume_handoff.cpython-312.pyc +0 -0
  18. package/lib/__pycache__/autoresume_probe.cpython-312.pyc +0 -0
  19. package/lib/__pycache__/autoresume_receipt.cpython-312.pyc +0 -0
  20. package/lib/__pycache__/claude_client_error.cpython-312.pyc +0 -0
  21. package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
  22. package/lib/__pycache__/claude_token_identity.cpython-312.pyc +0 -0
  23. package/lib/__pycache__/codex_autoresume_mode.cpython-312.pyc +0 -0
  24. package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
  25. package/lib/__pycache__/codex_credit_controls.cpython-312.pyc +0 -0
  26. package/lib/__pycache__/codex_credit_local_scope.cpython-312.pyc +0 -0
  27. package/lib/__pycache__/codex_credit_selection.cpython-312.pyc +0 -0
  28. package/lib/__pycache__/codex_credits.cpython-312.pyc +0 -0
  29. package/lib/__pycache__/codex_limits_lock.cpython-312.pyc +0 -0
  30. package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
  31. package/lib/__pycache__/codex_trust.cpython-312.pyc +0 -0
  32. package/lib/__pycache__/codex_usage_fetch.cpython-312.pyc +0 -0
  33. package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
  34. package/lib/__pycache__/mcp_oauth.cpython-312.pyc +0 -0
  35. package/lib/__pycache__/mcp_registry.cpython-312.pyc +0 -0
  36. package/lib/__pycache__/model_catalog.cpython-312.pyc +0 -0
  37. package/lib/__pycache__/model_match.cpython-312.pyc +0 -0
  38. package/lib/__pycache__/report.cpython-312.pyc +0 -0
  39. package/lib/__pycache__/selector_credit_scope.cpython-312.pyc +0 -0
  40. package/lib/__pycache__/selector_credits.cpython-312.pyc +0 -0
  41. package/lib/__pycache__/selector_funding.cpython-312.pyc +0 -0
  42. package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
  43. package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
  44. package/lib/__pycache__/session_settings.cpython-312.pyc +0 -0
  45. package/lib/__pycache__/shim_path.cpython-312.pyc +0 -0
  46. package/lib/audit.py +88 -22
  47. package/lib/autoresume.py +1197 -82
  48. package/lib/autoresume_cross.py +3 -1
  49. package/lib/autoresume_handoff.py +81 -9
  50. package/lib/autoresume_probe.py +2 -2
  51. package/lib/claude_client_error.py +9 -4
  52. package/lib/codex_trust.py +263 -0
  53. package/lib/common.sh +131 -3
  54. package/lib/credential.py +5 -2
  55. package/lib/install_actions.sh +19 -0
  56. package/lib/keychain.py +180 -36
  57. package/lib/mcp_oauth.py +1748 -0
  58. package/lib/model_catalog.json +404 -0
  59. package/lib/model_catalog.py +201 -0
  60. package/lib/model_match.py +104 -0
  61. package/lib/report.py +8 -5
  62. package/lib/session_settings.py +123 -0
  63. package/package.json +2 -2
  64. package/tests/__pycache__/autoresume_cross_receipt_support.cpython-312.pyc +0 -0
  65. package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-312.pyc +0 -0
  66. package/tests/__pycache__/autoresume_cross_tmux_support.cpython-312.pyc +0 -0
  67. package/tests/__pycache__/autoresume_cross_unit_support.cpython-312.pyc +0 -0
  68. package/tests/__pycache__/autoresume_tmux_support.cpython-312.pyc +0 -0
  69. package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
  70. package/tests/__pycache__/test_autoresume.cpython-312.pyc +0 -0
  71. package/tests/__pycache__/test_autoresume_catalog.cpython-312.pyc +0 -0
  72. package/tests/__pycache__/test_autoresume_catalog_cli.cpython-312.pyc +0 -0
  73. package/tests/__pycache__/test_autoresume_codex_records.cpython-312.pyc +0 -0
  74. package/tests/__pycache__/test_autoresume_codex_trust.cpython-312.pyc +0 -0
  75. package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-312.pyc +0 -0
  76. package/tests/__pycache__/test_autoresume_cross_guards.cpython-312.pyc +0 -0
  77. package/tests/__pycache__/test_autoresume_cross_holders.cpython-312.pyc +0 -0
  78. package/tests/__pycache__/test_autoresume_cross_origin.cpython-312.pyc +0 -0
  79. package/tests/__pycache__/test_autoresume_cross_policy.cpython-312.pyc +0 -0
  80. package/tests/__pycache__/test_autoresume_cross_receipt.cpython-312.pyc +0 -0
  81. package/tests/__pycache__/test_autoresume_cross_tmux.cpython-312.pyc +0 -0
  82. package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-312.pyc +0 -0
  83. package/tests/__pycache__/test_autoresume_cross_tokens.cpython-312.pyc +0 -0
  84. package/tests/__pycache__/test_autoresume_cursor.cpython-312.pyc +0 -0
  85. package/tests/__pycache__/test_autoresume_fallback.cpython-312.pyc +0 -0
  86. package/tests/__pycache__/test_autoresume_handoff_argv.cpython-312.pyc +0 -0
  87. package/tests/__pycache__/test_autoresume_handoff_history.cpython-312.pyc +0 -0
  88. package/tests/__pycache__/test_autoresume_handoff_origins.cpython-312.pyc +0 -0
  89. package/tests/__pycache__/test_autoresume_memory.cpython-312.pyc +0 -0
  90. package/tests/__pycache__/test_autoresume_memory_tmux.cpython-312.pyc +0 -0
  91. package/tests/__pycache__/test_autoresume_mode.cpython-312.pyc +0 -0
  92. package/tests/__pycache__/test_autoresume_probe.cpython-312.pyc +0 -0
  93. package/tests/__pycache__/test_autoresume_receipt.cpython-312.pyc +0 -0
  94. package/tests/__pycache__/test_autoresume_relaunch_settings.cpython-312.pyc +0 -0
  95. package/tests/__pycache__/test_autoresume_settings.cpython-312.pyc +0 -0
  96. package/tests/__pycache__/test_autoresume_sidechain.cpython-312.pyc +0 -0
  97. package/tests/__pycache__/test_autoresume_sidechain_tmux.cpython-312.pyc +0 -0
  98. package/tests/__pycache__/test_autoresume_tmux.cpython-312.pyc +0 -0
  99. package/tests/__pycache__/test_autoresume_tmux_modes.cpython-312.pyc +0 -0
  100. package/tests/__pycache__/test_autoresume_trust_tmux.cpython-312.pyc +0 -0
  101. package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
  102. package/tests/__pycache__/test_claude_token_identity.cpython-312.pyc +0 -0
  103. package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
  104. package/tests/__pycache__/test_codex_reset_credits.cpython-312.pyc +0 -0
  105. package/tests/__pycache__/test_codex_reset_credits_polling.cpython-312.pyc +0 -0
  106. package/tests/__pycache__/test_codex_reset_credits_report.cpython-312.pyc +0 -0
  107. package/tests/__pycache__/test_codex_reset_fetch.cpython-312.pyc +0 -0
  108. package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
  109. package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
  110. package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
  111. package/tests/__pycache__/test_selector.cpython-312.pyc +0 -0
  112. package/tests/__pycache__/test_selector_cached_credits.cpython-312.pyc +0 -0
  113. package/tests/__pycache__/test_selector_credit_scope.cpython-312.pyc +0 -0
  114. package/tests/__pycache__/test_selector_credit_shim.cpython-312.pyc +0 -0
  115. package/tests/__pycache__/test_selector_credits.cpython-312.pyc +0 -0
  116. package/tests/__pycache__/test_selector_spread.cpython-312.pyc +0 -0
  117. package/tests/__pycache__/test_token_identity_ceremony.cpython-312.pyc +0 -0
  118. package/tests/__pycache__/test_token_identity_integration.cpython-312.pyc +0 -0
  119. package/tests/__pycache__/test_token_identity_retry.cpython-312.pyc +0 -0
  120. package/tests/__pycache__/test_token_identity_sync.cpython-312.pyc +0 -0
  121. package/tests/fake_codex_tui.py +25 -0
  122. package/tests/fake_security.sh +24 -2
  123. package/tests/fixtures/cross_provider_cli.py +109 -0
  124. package/tests/run-tests.sh +1028 -23
  125. package/tests/test_autoresume.py +215 -8
  126. package/tests/test_autoresume_catalog.py +226 -0
  127. package/tests/test_autoresume_catalog_cli.py +185 -0
  128. package/tests/test_autoresume_codex_records.py +16 -0
  129. package/tests/test_autoresume_codex_trust.py +247 -0
  130. package/tests/test_autoresume_cross_guards.py +28 -0
  131. package/tests/test_autoresume_cross_tmux.py +27 -2
  132. package/tests/test_autoresume_cross_tokens.py +32 -2
  133. package/tests/test_autoresume_handoff_argv.py +120 -16
  134. package/tests/test_autoresume_handoff_origins.py +14 -0
  135. package/tests/test_autoresume_memory.py +1141 -0
  136. package/tests/test_autoresume_memory_tmux.py +94 -0
  137. package/tests/test_autoresume_probe.py +2 -1
  138. package/tests/test_autoresume_relaunch_settings.py +317 -0
  139. package/tests/test_autoresume_settings.py +135 -0
  140. package/tests/test_autoresume_sidechain.py +1518 -0
  141. package/tests/test_autoresume_sidechain_tmux.py +79 -0
  142. package/tests/test_autoresume_trust_tmux.py +63 -0
  143. package/tests/test_claude_client_error.py +10 -7
  144. package/tests/test_claude_org_block.py +1 -0
  145. package/tests/test_claude_pool_prepare.py +4 -1
  146. package/tests/test_claude_reset.py +3 -0
  147. package/tests/test_keychain_nologin.py +340 -0
  148. package/tests/test_mcp_oauth.py +1414 -0
  149. package/tests/test_packaged_commands.py +41 -0
@@ -1,12 +1,24 @@
1
- """Fresh provider conversations retain source history and conservative permission intent."""
1
+ """Fresh provider conversations retain source history, conservative permission intent and the
2
+ same-level model and effort the source history recorded."""
2
3
 
3
4
  import json
4
5
  from pathlib import Path
5
6
  import tempfile
6
7
  import unittest
8
+ from unittest import mock
7
9
 
8
10
  import test_autoresume as fixtures
9
11
  import autoresume_handoff as handoff
12
+ import model_catalog
13
+ import model_match
14
+ import session_settings
15
+
16
+ READ_ONLY = ["--sandbox", "read-only", "--ask-for-approval", "on-request"]
17
+ # Codex gpt-6-astra at ultra -> Claude opus at max; Claude opus 5.5 [1m] at xhigh -> Codex astra
18
+ # at xhigh (the operator's map). The shipped catalog decides the newest listed model of a family.
19
+ CLAUDE_FLAGS = ["--model", "claude-opus-5-5", "--effort", "max"]
20
+ CODEX_FLAGS = ["-m", "gpt-6-astra", "-c", "model_reasoning_effort=xhigh"]
21
+ SOURCE = {"codex": ("gpt-6-astra", "ultra"), "claude": ("claude-opus-5-5[1m]", "xhigh")}
10
22
 
11
23
 
12
24
  class HandoffArgvTests(unittest.TestCase):
@@ -16,31 +28,122 @@ class HandoffArgvTests(unittest.TestCase):
16
28
  self.home = Path(temporary.name).resolve()
17
29
  self.cwd = self.home / 'workspace "quoted" $literal'
18
30
  self.cwd.mkdir()
31
+ # Empty pool roots: the shipped catalog alone, never this machine's live CLI caches.
32
+ self.roots = {"claude": str(self.home / "claude-pool"), "codex": str(self.home / "codex-pool")}
33
+
34
+ def settings_record(self, provider, model, effort):
35
+ """The session's own model record: Codex's turn context, Claude's main-thread reply."""
36
+ if provider == "codex":
37
+ return {"timestamp": fixtures.iso(1001), "type": "turn_context",
38
+ "payload": {"cwd": str(self.cwd), "approval_policy": "on-request", "model": model,
39
+ "effort": effort}}
40
+ return fixtures.cc_reply(1001, cwd=str(self.cwd), requestedModel=model, effort=effort)
19
41
 
20
- def history(self, provider):
42
+ def history(self, provider, settings=None):
21
43
  sid = fixtures.SID if provider == "claude" else fixtures.CX_SID
22
44
  name = sid + ".jsonl" if provider == "claude" else "rollout-fixture-" + sid + ".jsonl"
23
45
  path = self.home / name
24
46
  record = fixtures.cc_user(1000, "PRIVATE_SOURCE_TEXT_SENTINEL", cwd=str(self.cwd)) \
25
47
  if provider == "claude" else fixtures.cx_meta(1000, cwd=str(self.cwd))
26
- path.write_text(json.dumps(record) + "\n")
48
+ records = [record] + ([self.settings_record(provider, *settings)] if settings else [])
49
+ path.write_text(fixtures.jl(records))
27
50
  return handoff.validate_source(provider, sid, str(path), str(self.cwd))
28
51
 
29
- def test_fresh_target_drops_source_session_model_effort_and_original_prompt(self):
52
+ def test_history_without_model_records_keeps_the_exact_unpinned_argv(self):
30
53
  cases = (("claude", "codex", ["--model", "source-only-model", "--effort", "high",
31
- "--resume", fixtures.SID, "Original user request"]),
54
+ "--resume", fixtures.SID, "Original user request"], READ_ONLY),
32
55
  ("codex", "claude", ["resume", "--no-daemon", "--model", "source-only-model",
33
- fixtures.CX_SID, "Original user request"]))
34
- for source, target, original in cases:
56
+ fixtures.CX_SID, "Original user request"], ["--permission-mode", "default"]))
57
+ for source, target, original, expected in cases:
35
58
  with self.subTest(source=source):
36
59
  history = self.history(source)
37
- argv = handoff.handoff_argv(source, target, original, history)
60
+ argv = handoff.handoff_argv(source, target, original, history, roots=self.roots)
38
61
  self.assertIsNotNone(argv)
39
- for value in ("resume", "--resume", "--last", "--continue", "--model", "--effort",
40
- "source-only-model", "Original user request", history["sid"]):
41
- self.assertNotIn(value, argv[:-1])
62
+ self.assertEqual(argv[:-1], expected)
63
+ self.assertFalse(any("source-only-model" in arg or "Original user request" in arg for arg in argv))
64
+ self.assertIn(history["sid"], argv[-1])
65
+ self.assertNotIn("Source model:", argv[-1])
66
+
67
+ def test_recorded_model_and_effort_pin_the_same_level_target_before_the_prompt(self):
68
+ cases = (("codex", "claude", [], ["--permission-mode", "default"] + CLAUDE_FLAGS),
69
+ ("codex", "claude", ["--yolo", "-m", "source-only-model"],
70
+ ["--dangerously-skip-permissions"] + CLAUDE_FLAGS),
71
+ ("claude", "codex", [], READ_ONLY + CODEX_FLAGS),
72
+ ("claude", "codex", ["--dangerously-skip-permissions", "--model", "source-only-model"],
73
+ ["--dangerously-bypass-approvals-and-sandbox"] + CODEX_FLAGS))
74
+ for source, target, original, expected in cases:
75
+ with self.subTest(source=source, original=original):
76
+ history = self.history(source, SOURCE[source])
77
+ argv = handoff.handoff_argv(source, target, original, history, roots=self.roots)
78
+ self.assertEqual(argv[:-1], expected)
42
79
  self.assertIn(history["sid"], argv[-1])
43
- self.assertNotIn("Original user request", argv[-1])
80
+ self.assertTrue(argv[-1].startswith(handoff.PROMPT_PREFIX))
81
+
82
+ def test_prompt_names_source_and_matched_settings_before_the_operator_instruction(self):
83
+ sentences = {"codex": "Source model: gpt-6-astra (effort ultra). This conversation runs the matching "
84
+ "claude model claude-opus-5-5 (effort max).",
85
+ "claude": "Source model: claude-opus-5-5[1m] (effort xhigh). This conversation runs the "
86
+ "matching codex model gpt-6-astra (effort xhigh)."}
87
+ for source, target in (("codex", "claude"), ("claude", "codex")):
88
+ with self.subTest(source=source):
89
+ history = self.history(source, SOURCE[source])
90
+ prompt = handoff.handoff_argv(source, target, [], history, prompt_override="Keep the plan.",
91
+ roots=self.roots)[-1]
92
+ self.assertTrue(prompt.endswith(
93
+ "completed work. " + sentences[source] + " Additional continuation instruction: Keep the plan."))
94
+ self.assertEqual(handoff.handoff_argv(source, target, [], history, roots=self.roots)[-1],
95
+ prompt[:prompt.index(" Additional continuation instruction:")])
96
+
97
+ def test_settings_come_only_from_the_validated_snapshot(self):
98
+ history = self.history("codex", SOURCE["codex"])
99
+ with open(history["path"], "a") as stream:
100
+ stream.write(json.dumps(self.settings_record("codex", "gpt-6.1-sol", "low")) + "\n")
101
+ argv = handoff.handoff_argv("codex", "claude", [], history, roots=self.roots)
102
+ self.assertEqual(argv[:-1], ["--permission-mode", "default"] + CLAUDE_FLAGS)
103
+ later = handoff.validate_source("codex", history["sid"], history["path"], history["cwd"])
104
+ self.assertGreater(later["offset"], history["offset"])
105
+ argv = handoff.handoff_argv("codex", "claude", [], later, roots=self.roots)
106
+ self.assertEqual(argv[:-1], ["--permission-mode", "default", "--model", "claude-sonnet-5-5", "--effort", "low"])
107
+
108
+ def test_unsafe_unknown_or_failing_settings_never_block_the_handoff(self):
109
+ unpinned = ["--permission-mode", "default"]
110
+ for settings, expected in ((("--dangerously-skip-permissions", "max"), unpinned),
111
+ (("gpt 6 astra", "max"), unpinned), (("gpt-6-astra\nx", "max"), unpinned),
112
+ (("mystery-model", "turbo"), unpinned),
113
+ (("gpt-6-astra", "--effort"), unpinned + CLAUDE_FLAGS[:2]),
114
+ (("gpt-6-astra", "hyper"), unpinned + CLAUDE_FLAGS[:2])):
115
+ with self.subTest(settings=settings):
116
+ argv = handoff.handoff_argv("codex", "claude", [], self.history("codex", settings), roots=self.roots)
117
+ self.assertEqual(argv[:-1], expected)
118
+ history = self.history("codex", SOURCE["codex"])
119
+ for module, name in ((session_settings, "session_settings"), (model_catalog, "load"),
120
+ (model_match, "resume_settings"), (model_match, "settings_flags")):
121
+ with self.subTest(failing=name), mock.patch.object(module, name, side_effect=RuntimeError("boom")):
122
+ result = handoff.build_handoff("codex", "claude", [], history["sid"], history["path"],
123
+ history["cwd"], roots=self.roots)
124
+ self.assertEqual(result["argv"][:-1], unpinned)
125
+ self.assertEqual(result["models"]["flags"], [])
126
+ self.assertNotIn("Source model:", result["argv"][-1])
127
+
128
+ def test_build_handoff_threads_pool_roots_into_the_catalog(self):
129
+ history = self.history("codex", SOURCE["codex"])
130
+ result = handoff.build_handoff("codex", "claude", [], history["sid"], history["path"], history["cwd"],
131
+ roots=self.roots)
132
+ self.assertEqual(result["argv"][:-1], ["--permission-mode", "default"] + CLAUDE_FLAGS)
133
+ overlay = Path(self.roots["claude"])
134
+ overlay.mkdir()
135
+ efforts = ["low", "medium", "high", "xhigh", "max"]
136
+ (overlay / model_catalog.OVERLAY).write_text(json.dumps({"schema": 1, "claude": {
137
+ "fetched_at": "2999-01-01T00:00:00Z", "models": {"claude-opus-6": {
138
+ "family": "opus", "version": [6], "efforts": efforts, "default_effort": "high",
139
+ "listed": True, "upgrade": None}}}}))
140
+ result = handoff.build_handoff("codex", "claude", [], history["sid"], history["path"], history["cwd"],
141
+ roots=self.roots)
142
+ self.assertEqual(result["argv"][:-1], ["--permission-mode", "default", "--model", "claude-opus-6",
143
+ "--effort", "max"])
144
+ self.assertEqual(result["models"], {"source": {"model": "gpt-6-astra", "effort": "ultra"},
145
+ "target": {"model": "claude-opus-6", "effort": "max"},
146
+ "flags": ["--model", "claude-opus-6", "--effort", "max"]})
44
147
 
45
148
  def test_default_and_allow_bypass_only_keep_conservative_target_permissions(self):
46
149
  cases = (("claude", "codex", [], ["--sandbox", "read-only", "--ask-for-approval", "on-request"]),
@@ -82,11 +185,12 @@ class HandoffArgvTests(unittest.TestCase):
82
185
  self.assertIsNone(handoff.handoff_argv("codex", "claude", original, self.history("codex")))
83
186
 
84
187
  def test_custom_prompt_cannot_remove_exact_history_and_continuation_instructions(self):
85
- for source, target in (("claude", "codex"), ("codex", "claude")):
86
- with self.subTest(source=source):
87
- history = self.history(source)
188
+ for source, target, settings in (("claude", "codex", None), ("codex", "claude", None),
189
+ ("claude", "codex", SOURCE["claude"]), ("codex", "claude", SOURCE["codex"])):
190
+ with self.subTest(source=source, settings=settings):
191
+ history = self.history(source, settings)
88
192
  original = Path(history["path"]).read_bytes()
89
- argv = handoff.handoff_argv(source, target, [], history,
193
+ argv = handoff.handoff_argv(source, target, [], history, roots=self.roots,
90
194
  prompt_override="Preserve the operator's extra instruction.")
91
195
  prompt = argv[-1]
92
196
  for value in (source, history["sid"], json.dumps(history["path"]), json.dumps(history["cwd"]),
@@ -35,6 +35,20 @@ class HandoffOriginTests(unittest.TestCase):
35
35
  self.assertEqual(argv[:-1], ["--dangerously-skip-permissions"])
36
36
  self.assertIn(fixtures.CX_SID, argv[-1])
37
37
 
38
+ def test_vscode_history_maps_only_its_own_thread_settings(self):
39
+ roots = {"claude": str(self.cwd / "claude-pool"), "codex": str(self.cwd / "codex-pool")}
40
+ for thread, expected in ((fixtures.CX_SID, ["--model", "claude-opus-5-5", "--effort", "max"]),
41
+ (fixtures.CX_SID2, [])):
42
+ with self.subTest(thread=thread):
43
+ self.history(source="vscode")
44
+ settings = {"model": "gpt-6-astra", "reasoning_effort": "ultra"}
45
+ event = fixtures.cx_event(1001, {"type": "thread_settings_applied", "thread_id": thread,
46
+ "thread_settings": settings})
47
+ fixtures.append(str(self.path), [event])
48
+ history = handoff.validate_source("codex", fixtures.CX_SID, str(self.path), str(self.cwd))
49
+ argv = handoff.handoff_argv("codex", "claude", ["--yolo"], history, roots=roots)
50
+ self.assertEqual(argv[:-1], ["--dangerously-skip-permissions"] + expected)
51
+
38
52
  def test_exec_unknown_and_subagent_transports_remain_rejected(self):
39
53
  for source in ("exec", "unknown", "app-server", {"subagent": "spawn"}):
40
54
  with self.subTest(source=source):