claude-multiacc 2.0.57 → 2.0.58
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/docs/AUTORESUME-HANDOFF.md +2 -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_usage_fetch.cpython-312.pyc +0 -0
- package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
- package/lib/__pycache__/mcp_registry.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__/shim_path.cpython-312.pyc +0 -0
- package/lib/autoresume_handoff.py +1 -1
- package/package.json +1 -1
- 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_codex_records.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_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_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux_modes.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/fixtures/cross_provider_cli.py +2 -1
- package/tests/test_autoresume_cross_origin.py +70 -0
- package/tests/test_autoresume_handoff_origins.py +53 -0
|
@@ -28,6 +28,8 @@ available; a Codex ID is never passed as a Claude native `--resume` ID, or vice
|
|
|
28
28
|
The watcher uses the history already bound to the source client. It does not guess from
|
|
29
29
|
the newest transcript or directory name. Invalid, ambiguous or unavailable source
|
|
30
30
|
history prevents the handoff before the source client is stopped.
|
|
31
|
+
Codex main conversations marked `source=cli` or `source=vscode` are supported when
|
|
32
|
+
resumed in a supervised TUI; their exact conversation and file identity still must match.
|
|
31
33
|
|
|
32
34
|
If another live client holds the same source conversation, the watcher holds to avoid
|
|
33
35
|
creating duplicate destination conversations. Stale process records do not count.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -52,7 +52,7 @@ def _codex_identity(data, sid, cwd):
|
|
|
52
52
|
meta = record.get("payload")
|
|
53
53
|
if not isinstance(meta, dict) or meta.get("id") != sid or meta.get("session_id") != sid:
|
|
54
54
|
return False
|
|
55
|
-
if meta.get("source") not in (None, "cli") or meta.get("thread_source") not in (None, "user"):
|
|
55
|
+
if meta.get("source") not in (None, "cli", "vscode") or meta.get("thread_source") not in (None, "user"):
|
|
56
56
|
return False
|
|
57
57
|
if meta.get("originator") not in (None, "codex-tui", "codex_cli_rs", "codex_cli"):
|
|
58
58
|
return False
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -75,7 +75,8 @@ def initialize_history(account, sid):
|
|
|
75
75
|
if PROVIDER == "codex":
|
|
76
76
|
history.write(json.dumps({"timestamp": timestamp(), "type": "session_meta", "payload": {
|
|
77
77
|
"id": sid, "session_id": sid, "timestamp": timestamp(),
|
|
78
|
-
"cwd": os.getcwd(), "source": "
|
|
78
|
+
"cwd": os.getcwd(), "source": os.environ.get("FAKE_CODEX_SOURCE", "cli"),
|
|
79
|
+
"thread_source": "user", "originator": "codex-tui"}}) + "\n")
|
|
79
80
|
history.write(json.dumps(user_record(sid, "Complete the synthetic acceptance task.")) + "\n")
|
|
80
81
|
prompt = sys.argv[-1] if len(sys.argv) > 1 and " " in sys.argv[-1] else ""
|
|
81
82
|
if prompt and os.environ.get("FAKE_DROP_PROMPT") != PROVIDER:
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Native TUI vscode rollout metadata survives account rotation and provider handoff."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
from autoresume_cross_tmux_support import CrossScenario, TMUX
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@unittest.skipUnless(TMUX, "real tmux is unavailable")
|
|
11
|
+
class CrossOriginTmuxTests(unittest.TestCase):
|
|
12
|
+
def scenario(self, provider):
|
|
13
|
+
case = CrossScenario(FAKE_CODEX_SOURCE="vscode")
|
|
14
|
+
self.addCleanup(case.close)
|
|
15
|
+
case.start(provider)
|
|
16
|
+
return case
|
|
17
|
+
|
|
18
|
+
def assert_vscode_main_history(self, ready):
|
|
19
|
+
metadata = json.loads(Path(ready["path"]).read_text().splitlines()[0])["payload"]
|
|
20
|
+
self.assertEqual(metadata["id"], ready["sid"])
|
|
21
|
+
self.assertEqual(metadata["session_id"], ready["sid"])
|
|
22
|
+
self.assertEqual(metadata["source"], "vscode")
|
|
23
|
+
self.assertEqual(metadata["thread_source"], "user")
|
|
24
|
+
self.assertEqual(metadata["originator"], "codex-tui")
|
|
25
|
+
|
|
26
|
+
def test_vscode_tui_rotates_codex_account_then_continues_exact_history_in_claude(self):
|
|
27
|
+
case = self.scenario("codex")
|
|
28
|
+
first = case.records("ready")[0]
|
|
29
|
+
self.assert_vscode_main_history(first)
|
|
30
|
+
case.quota()
|
|
31
|
+
case.wait(lambda: len(case.records("ready")) == 2, "same-provider account rotation")
|
|
32
|
+
case.wait(lambda: "autoresume switch" in case.logs("codex"), "same-provider continuation receipt")
|
|
33
|
+
second = case.records("ready")[1]
|
|
34
|
+
self.assertEqual((second["provider"], second["sid"]), ("codex", first["sid"]))
|
|
35
|
+
self.assertEqual(second["path"], first["path"])
|
|
36
|
+
self.assertEqual([row["account"] for row in case.records("launch")], ["acct-01", "acct-02"])
|
|
37
|
+
case.park("codex", ("acct-01",))
|
|
38
|
+
case.quota(second)
|
|
39
|
+
case.wait(lambda: len(case.records("ready")) == 3, "exhausted Codex pool transfers to Claude")
|
|
40
|
+
case.wait(lambda: case.logs("codex").count("autoresume switch") == 2, "cross-provider prompt receipt")
|
|
41
|
+
target = case.records("ready")[2]
|
|
42
|
+
prompt = case.records("prompt")[-1]
|
|
43
|
+
self.assertEqual((target["provider"], prompt["provider"]), ("claude", "claude"))
|
|
44
|
+
self.assertNotEqual(target["sid"], first["sid"])
|
|
45
|
+
self.assertIn(first["sid"], prompt["text"])
|
|
46
|
+
self.assertIn(first["path"], prompt["text"])
|
|
47
|
+
self.assertIn(str(case.work), prompt["text"])
|
|
48
|
+
self.assertEqual(prompt["sid"], target["sid"])
|
|
49
|
+
self.assertEqual(case.records("keys"), [])
|
|
50
|
+
|
|
51
|
+
def test_claude_handoff_confirms_new_codex_vscode_main_history(self):
|
|
52
|
+
case = self.scenario("claude")
|
|
53
|
+
source = case.records("ready")[0]
|
|
54
|
+
case.park("claude")
|
|
55
|
+
case.quota()
|
|
56
|
+
case.wait(lambda: len(case.records("ready")) == 2, "new Codex TUI starts")
|
|
57
|
+
target = case.records("ready")[1]
|
|
58
|
+
self.assert_vscode_main_history(target)
|
|
59
|
+
case.wait(lambda: "autoresume switch" in case.logs("claude"), "vscode target prompt receipt")
|
|
60
|
+
prompt = case.records("prompt")[-1]
|
|
61
|
+
self.assertEqual((target["provider"], prompt["provider"]), ("codex", "codex"))
|
|
62
|
+
self.assertNotEqual(target["sid"], source["sid"])
|
|
63
|
+
self.assertIn(source["sid"], prompt["text"])
|
|
64
|
+
self.assertIn(source["path"], prompt["text"])
|
|
65
|
+
self.assertEqual(prompt["sid"], target["sid"])
|
|
66
|
+
self.assertEqual(case.records("keys"), [])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
unittest.main()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Main Codex TUI histories retain their original transport when resumed elsewhere."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import tempfile
|
|
6
|
+
import unittest
|
|
7
|
+
|
|
8
|
+
import test_autoresume as fixtures
|
|
9
|
+
import autoresume_handoff as handoff
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HandoffOriginTests(unittest.TestCase):
|
|
13
|
+
def setUp(self):
|
|
14
|
+
temporary = tempfile.TemporaryDirectory(prefix="ar-handoff-origins-")
|
|
15
|
+
self.addCleanup(temporary.cleanup)
|
|
16
|
+
self.cwd = Path(temporary.name).resolve()
|
|
17
|
+
self.path = self.cwd / ("rollout-origin-" + fixtures.CX_SID + ".jsonl")
|
|
18
|
+
|
|
19
|
+
def history(self, **changes):
|
|
20
|
+
record = fixtures.cx_meta(1000, cwd=str(self.cwd))
|
|
21
|
+
record["payload"].update(source="vscode", thread_source="user", originator="codex-tui")
|
|
22
|
+
record["payload"].update(changes)
|
|
23
|
+
self.path.write_text(json.dumps(record) + "\n")
|
|
24
|
+
return handoff.validate_source("codex", fixtures.CX_SID, str(self.path), str(self.cwd))
|
|
25
|
+
|
|
26
|
+
def test_user_tui_history_accepts_cli_and_vscode_transports(self):
|
|
27
|
+
for source in (None, "cli", "vscode"):
|
|
28
|
+
with self.subTest(source=source):
|
|
29
|
+
history = self.history(source=source)
|
|
30
|
+
self.assertIsNotNone(history)
|
|
31
|
+
self.assertEqual(history["sid"], fixtures.CX_SID)
|
|
32
|
+
self.assertEqual(history["path"], str(self.path))
|
|
33
|
+
self.assertEqual(history["inode"], self.path.stat().st_ino)
|
|
34
|
+
argv = handoff.handoff_argv("codex", "claude", ["--yolo"], history)
|
|
35
|
+
self.assertEqual(argv[:-1], ["--dangerously-skip-permissions"])
|
|
36
|
+
self.assertIn(fixtures.CX_SID, argv[-1])
|
|
37
|
+
|
|
38
|
+
def test_exec_unknown_and_subagent_transports_remain_rejected(self):
|
|
39
|
+
for source in ("exec", "unknown", "app-server", {"subagent": "spawn"}):
|
|
40
|
+
with self.subTest(source=source):
|
|
41
|
+
self.assertIsNone(self.history(source=source))
|
|
42
|
+
|
|
43
|
+
def test_vscode_transport_does_not_bypass_main_conversation_checks(self):
|
|
44
|
+
changes = ({"thread_source": "subagent"}, {"originator": "codex_exec"},
|
|
45
|
+
{"originator": "unknown"}, {"session_id": fixtures.CX_SID2},
|
|
46
|
+
{"id": fixtures.CX_SID2}, {"cwd": str(self.cwd / "unrelated")})
|
|
47
|
+
for fields in changes:
|
|
48
|
+
with self.subTest(fields=fields):
|
|
49
|
+
self.assertIsNone(self.history(**fields))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
unittest.main()
|