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.
- package/README.md +111 -3
- package/bin/claude +323 -84
- package/bin/claude-accounts +273 -42
- package/bin/codex +15 -0
- package/bin/codex-accounts +31 -4
- package/docs/ACCOUNT_OPERATIONS.md +62 -2
- package/docs/AUTORESUME-HANDOFF.md +28 -3
- package/docs/AUTORESUME.md +275 -18
- package/docs/CODEX.md +1 -0
- package/docs/INSTALLATION.md +7 -1
- package/docs/VERIFICATION.md +4 -0
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross_dispatch.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross_receipt.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_handoff.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_probe.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_receipt.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_client_error.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_token_identity.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_autoresume_mode.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_controls.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_local_scope.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_selection.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credits.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_limits_lock.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_trust.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_usage_fetch.cpython-312.pyc +0 -0
- package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
- package/lib/__pycache__/mcp_oauth.cpython-312.pyc +0 -0
- package/lib/__pycache__/mcp_registry.cpython-312.pyc +0 -0
- package/lib/__pycache__/model_catalog.cpython-312.pyc +0 -0
- package/lib/__pycache__/model_match.cpython-312.pyc +0 -0
- package/lib/__pycache__/report.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_credit_scope.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_credits.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_funding.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
- package/lib/__pycache__/session_settings.cpython-312.pyc +0 -0
- package/lib/__pycache__/shim_path.cpython-312.pyc +0 -0
- package/lib/audit.py +88 -22
- package/lib/autoresume.py +1197 -82
- package/lib/autoresume_cross.py +3 -1
- package/lib/autoresume_handoff.py +81 -9
- package/lib/autoresume_probe.py +2 -2
- package/lib/claude_client_error.py +9 -4
- package/lib/codex_trust.py +263 -0
- package/lib/common.sh +131 -3
- package/lib/credential.py +5 -2
- package/lib/install_actions.sh +19 -0
- package/lib/keychain.py +180 -36
- package/lib/mcp_oauth.py +1748 -0
- package/lib/model_catalog.json +404 -0
- package/lib/model_catalog.py +201 -0
- package/lib/model_match.py +104 -0
- package/lib/report.py +8 -5
- package/lib/session_settings.py +123 -0
- package/package.json +2 -2
- package/tests/__pycache__/autoresume_cross_receipt_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_tmux_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_unit_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_tmux_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_catalog.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_catalog_cli.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_codex_records.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_codex_trust.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_guards.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_holders.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_origin.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_policy.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_receipt.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tokens.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cursor.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_fallback.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_argv.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_history.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_origins.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_memory.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_memory_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_mode.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_probe.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_receipt.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_relaunch_settings.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_settings.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_sidechain.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_sidechain_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux_modes.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_trust_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_token_identity.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits_report.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_fetch.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_cached_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credit_scope.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credit_shim.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_spread.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_ceremony.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_integration.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_retry.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_sync.cpython-312.pyc +0 -0
- package/tests/fake_codex_tui.py +25 -0
- package/tests/fake_security.sh +24 -2
- package/tests/fixtures/cross_provider_cli.py +109 -0
- package/tests/run-tests.sh +1028 -23
- package/tests/test_autoresume.py +215 -8
- package/tests/test_autoresume_catalog.py +226 -0
- package/tests/test_autoresume_catalog_cli.py +185 -0
- package/tests/test_autoresume_codex_records.py +16 -0
- package/tests/test_autoresume_codex_trust.py +247 -0
- package/tests/test_autoresume_cross_guards.py +28 -0
- package/tests/test_autoresume_cross_tmux.py +27 -2
- package/tests/test_autoresume_cross_tokens.py +32 -2
- package/tests/test_autoresume_handoff_argv.py +120 -16
- package/tests/test_autoresume_handoff_origins.py +14 -0
- package/tests/test_autoresume_memory.py +1141 -0
- package/tests/test_autoresume_memory_tmux.py +94 -0
- package/tests/test_autoresume_probe.py +2 -1
- package/tests/test_autoresume_relaunch_settings.py +317 -0
- package/tests/test_autoresume_settings.py +135 -0
- package/tests/test_autoresume_sidechain.py +1518 -0
- package/tests/test_autoresume_sidechain_tmux.py +79 -0
- package/tests/test_autoresume_trust_tmux.py +63 -0
- package/tests/test_claude_client_error.py +10 -7
- package/tests/test_claude_org_block.py +1 -0
- package/tests/test_claude_pool_prepare.py +4 -1
- package/tests/test_claude_reset.py +3 -0
- package/tests/test_keychain_nologin.py +340 -0
- package/tests/test_mcp_oauth.py +1414 -0
- package/tests/test_packaged_commands.py +41 -0
|
@@ -1,12 +1,24 @@
|
|
|
1
|
-
"""Fresh provider conversations retain source history
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
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
|
-
|
|
87
|
-
|
|
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):
|