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.
Files changed (82) hide show
  1. package/docs/AUTORESUME-HANDOFF.md +2 -0
  2. package/lib/__pycache__/audit.cpython-312.pyc +0 -0
  3. package/lib/__pycache__/autoresume.cpython-312.pyc +0 -0
  4. package/lib/__pycache__/autoresume_cross.cpython-312.pyc +0 -0
  5. package/lib/__pycache__/autoresume_cross_dispatch.cpython-312.pyc +0 -0
  6. package/lib/__pycache__/autoresume_cross_receipt.cpython-312.pyc +0 -0
  7. package/lib/__pycache__/autoresume_handoff.cpython-312.pyc +0 -0
  8. package/lib/__pycache__/autoresume_probe.cpython-312.pyc +0 -0
  9. package/lib/__pycache__/autoresume_receipt.cpython-312.pyc +0 -0
  10. package/lib/__pycache__/claude_client_error.cpython-312.pyc +0 -0
  11. package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
  12. package/lib/__pycache__/claude_token_identity.cpython-312.pyc +0 -0
  13. package/lib/__pycache__/codex_autoresume_mode.cpython-312.pyc +0 -0
  14. package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
  15. package/lib/__pycache__/codex_credit_controls.cpython-312.pyc +0 -0
  16. package/lib/__pycache__/codex_credit_local_scope.cpython-312.pyc +0 -0
  17. package/lib/__pycache__/codex_credit_selection.cpython-312.pyc +0 -0
  18. package/lib/__pycache__/codex_credits.cpython-312.pyc +0 -0
  19. package/lib/__pycache__/codex_limits_lock.cpython-312.pyc +0 -0
  20. package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
  21. package/lib/__pycache__/codex_usage_fetch.cpython-312.pyc +0 -0
  22. package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
  23. package/lib/__pycache__/mcp_registry.cpython-312.pyc +0 -0
  24. package/lib/__pycache__/report.cpython-312.pyc +0 -0
  25. package/lib/__pycache__/selector_credit_scope.cpython-312.pyc +0 -0
  26. package/lib/__pycache__/selector_credits.cpython-312.pyc +0 -0
  27. package/lib/__pycache__/selector_funding.cpython-312.pyc +0 -0
  28. package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
  29. package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
  30. package/lib/__pycache__/shim_path.cpython-312.pyc +0 -0
  31. package/lib/autoresume_handoff.py +1 -1
  32. package/package.json +1 -1
  33. package/tests/__pycache__/autoresume_cross_receipt_support.cpython-312.pyc +0 -0
  34. package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-312.pyc +0 -0
  35. package/tests/__pycache__/autoresume_cross_tmux_support.cpython-312.pyc +0 -0
  36. package/tests/__pycache__/autoresume_cross_unit_support.cpython-312.pyc +0 -0
  37. package/tests/__pycache__/autoresume_tmux_support.cpython-312.pyc +0 -0
  38. package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
  39. package/tests/__pycache__/test_autoresume.cpython-312.pyc +0 -0
  40. package/tests/__pycache__/test_autoresume_codex_records.cpython-312.pyc +0 -0
  41. package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-312.pyc +0 -0
  42. package/tests/__pycache__/test_autoresume_cross_guards.cpython-312.pyc +0 -0
  43. package/tests/__pycache__/test_autoresume_cross_holders.cpython-312.pyc +0 -0
  44. package/tests/__pycache__/test_autoresume_cross_origin.cpython-312.pyc +0 -0
  45. package/tests/__pycache__/test_autoresume_cross_policy.cpython-312.pyc +0 -0
  46. package/tests/__pycache__/test_autoresume_cross_receipt.cpython-312.pyc +0 -0
  47. package/tests/__pycache__/test_autoresume_cross_tmux.cpython-312.pyc +0 -0
  48. package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-312.pyc +0 -0
  49. package/tests/__pycache__/test_autoresume_cross_tokens.cpython-312.pyc +0 -0
  50. package/tests/__pycache__/test_autoresume_cursor.cpython-312.pyc +0 -0
  51. package/tests/__pycache__/test_autoresume_fallback.cpython-312.pyc +0 -0
  52. package/tests/__pycache__/test_autoresume_handoff_argv.cpython-312.pyc +0 -0
  53. package/tests/__pycache__/test_autoresume_handoff_history.cpython-312.pyc +0 -0
  54. package/tests/__pycache__/test_autoresume_handoff_origins.cpython-312.pyc +0 -0
  55. package/tests/__pycache__/test_autoresume_mode.cpython-312.pyc +0 -0
  56. package/tests/__pycache__/test_autoresume_probe.cpython-312.pyc +0 -0
  57. package/tests/__pycache__/test_autoresume_receipt.cpython-312.pyc +0 -0
  58. package/tests/__pycache__/test_autoresume_tmux.cpython-312.pyc +0 -0
  59. package/tests/__pycache__/test_autoresume_tmux_modes.cpython-312.pyc +0 -0
  60. package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
  61. package/tests/__pycache__/test_claude_token_identity.cpython-312.pyc +0 -0
  62. package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
  63. package/tests/__pycache__/test_codex_reset_credits.cpython-312.pyc +0 -0
  64. package/tests/__pycache__/test_codex_reset_credits_polling.cpython-312.pyc +0 -0
  65. package/tests/__pycache__/test_codex_reset_credits_report.cpython-312.pyc +0 -0
  66. package/tests/__pycache__/test_codex_reset_fetch.cpython-312.pyc +0 -0
  67. package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
  68. package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
  69. package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
  70. package/tests/__pycache__/test_selector.cpython-312.pyc +0 -0
  71. package/tests/__pycache__/test_selector_cached_credits.cpython-312.pyc +0 -0
  72. package/tests/__pycache__/test_selector_credit_scope.cpython-312.pyc +0 -0
  73. package/tests/__pycache__/test_selector_credit_shim.cpython-312.pyc +0 -0
  74. package/tests/__pycache__/test_selector_credits.cpython-312.pyc +0 -0
  75. package/tests/__pycache__/test_selector_spread.cpython-312.pyc +0 -0
  76. package/tests/__pycache__/test_token_identity_ceremony.cpython-312.pyc +0 -0
  77. package/tests/__pycache__/test_token_identity_integration.cpython-312.pyc +0 -0
  78. package/tests/__pycache__/test_token_identity_retry.cpython-312.pyc +0 -0
  79. package/tests/__pycache__/test_token_identity_sync.cpython-312.pyc +0 -0
  80. package/tests/fixtures/cross_provider_cli.py +2 -1
  81. package/tests/test_autoresume_cross_origin.py +70 -0
  82. 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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-multiacc",
3
- "version": "2.0.57",
3
+ "version": "2.0.58",
4
4
  "description": "Unified Claude Code and OpenAI Codex subscription pooling with quota-aware selection.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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": "cli"}}) + "\n")
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()