claude-multiacc 2.0.31 → 2.0.32
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/CODEX.md +12 -9
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
- package/lib/__pycache__/keychain.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/codex_config_edit.py +46 -25
- package/lib/codex_settings.py +3 -1
- package/package.json +1 -1
- package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset.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/test_codex_config_edit.py +18 -2
- package/tests/test_codex_settings.py +19 -1
package/docs/CODEX.md
CHANGED
|
@@ -88,16 +88,19 @@ The addon does not choose delegation policy by default. To manage the concurrent
|
|
|
88
88
|
subagent ceiling across one Codex pool, explicitly configure it (Python 3.11+):
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
|
-
codex-accounts configure --max-subagents
|
|
92
|
-
codex-accounts configure --max-subagents
|
|
91
|
+
codex-accounts configure --max-subagents 7 --include-global --json
|
|
92
|
+
codex-accounts configure --max-subagents 7 --include-global --check --json
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
An explicitly selected modern Python is preserved. If the shell resolves an older
|
|
96
96
|
system Python, this command tries already installed Python 3.11+ executables on
|
|
97
97
|
PATH and in standard Homebrew locations, with bounded probes and no downloads.
|
|
98
98
|
|
|
99
|
-
The first command sets `
|
|
99
|
+
The first command sets `features.multi_agent_v2.max_concurrent_threads_per_session`
|
|
100
|
+
and the compatible `agents.max_concurrent_threads_per_session` in every manifest
|
|
100
101
|
account's `config.toml`, migrating its legacy `agents.max_threads` key when present.
|
|
102
|
+
An existing boolean `multi_agent_v2` becomes a table with the same `enabled` value;
|
|
103
|
+
an existing feature cap is updated along with the agents cap.
|
|
101
104
|
`--include-global` also updates the machine's `~/.codex/config.toml`; without it,
|
|
102
105
|
the global file is untouched. Unrelated models, instructions, MCP servers, project
|
|
103
106
|
trust and custom agents remain intact. Retired directories outside the manifest
|
|
@@ -112,16 +115,16 @@ fails sync instead of reporting success. Remote sync does not change the target'
|
|
|
112
115
|
global config. Without the policy file, existing settings keep their usual
|
|
113
116
|
copy-only-if-missing behavior and no new remote command runs.
|
|
114
117
|
|
|
115
|
-
`--check` never writes; it checks
|
|
116
|
-
and selected configs, returning exit 1 on drift. Without
|
|
117
|
-
command only reads the current pool policy and configuration. JSON reports contain
|
|
118
|
+
`--check` never writes; it checks both caps against the persisted policy
|
|
119
|
+
and requested value in selected configs, returning exit 1 on drift. Without
|
|
120
|
+
`--max-subagents`, the command only reads the current pool policy and configuration. JSON reports contain
|
|
118
121
|
the installed `plugin_version`, `max_subagents`, `policy_max_subagents`,
|
|
119
122
|
`accounts_total`, `accounts_configured`, `global_configured`, `mismatch_count` and
|
|
120
123
|
`verified`, without account identities, paths, credentials or arbitrary config.
|
|
121
124
|
|
|
122
|
-
Conflicting profile
|
|
125
|
+
Conflicting profile overrides are rejected.
|
|
123
126
|
Unusual inline-table layouts are also rejected before configuration changes;
|
|
124
|
-
use
|
|
125
|
-
validated before writes, each replacement is atomic, and repeating the command
|
|
127
|
+
use ordinary `[agents]` and `[features.multi_agent_v2]` tables for the managed settings.
|
|
128
|
+
All target configs are validated before writes, each replacement is atomic, and repeating the command
|
|
126
129
|
repairs an interrupted write. Existing running sessions retain the settings they
|
|
127
130
|
loaded; the policy applies to subsequent sessions.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/codex_config_edit.py
CHANGED
|
@@ -8,7 +8,6 @@ except ImportError:
|
|
|
8
8
|
raise SystemExit("Codex settings configuration requires Python 3.11 or newer") from None
|
|
9
9
|
|
|
10
10
|
KEY = "max_concurrent_threads_per_session"
|
|
11
|
-
ALIASES = (KEY, "max_threads")
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
def limits(document):
|
|
@@ -33,39 +32,61 @@ def validate_overrides(document, value):
|
|
|
33
32
|
raise ValueError(f"{name}: profile overrides the requested subagent limit")
|
|
34
33
|
|
|
35
34
|
|
|
35
|
+
def dotted_pattern(parts):
|
|
36
|
+
return r'\s*\.\s*'.join(r'(?:' + re.escape(part) + r'|"' + re.escape(part)
|
|
37
|
+
+ r'"|\x27' + re.escape(part) + r'\x27)' for part in parts)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def edit_setting(source, table, key, literal, aliases=()):
|
|
41
|
+
"""Edit table/dotted assignments, or remove one when literal is None."""
|
|
42
|
+
headers = [re.compile(r'^\s*\[\s*' + dotted_pattern(table[:depth]) + r'\s*\]\s*(?:#.*)?$')
|
|
43
|
+
for depth in range(1, len(table) + 1)]
|
|
44
|
+
assignments = [re.compile(r'^\s*(?:' + '|'.join(dotted_pattern((*table[depth:], name))
|
|
45
|
+
for name in (key, *aliases)) + r')\s*=')
|
|
46
|
+
for depth in range(len(table) + 1)]
|
|
47
|
+
section, output, inserted = 0, [], False
|
|
48
|
+
for line in source.splitlines(keepends=True):
|
|
49
|
+
if line.lstrip().startswith("["):
|
|
50
|
+
section = next((depth for depth, header in enumerate(headers, 1)
|
|
51
|
+
if header.match(line.rstrip("\r\n"))), None)
|
|
52
|
+
if section == len(table) and literal is not None and not inserted:
|
|
53
|
+
output.extend([line.rstrip("\r\n") + "\n", f"{key} = {literal}\n"])
|
|
54
|
+
inserted = True
|
|
55
|
+
continue
|
|
56
|
+
if section is not None and assignments[section].match(line):
|
|
57
|
+
if literal is not None and not inserted:
|
|
58
|
+
output.append(f"{'.'.join((*table[section:], key))} = {literal}\n")
|
|
59
|
+
inserted = True
|
|
60
|
+
continue
|
|
61
|
+
output.append(line)
|
|
62
|
+
if literal is not None and not inserted:
|
|
63
|
+
output.extend([f"\n[{'.'.join(table)}]\n", f"{key} = {literal}\n"])
|
|
64
|
+
return "".join(output)
|
|
65
|
+
|
|
66
|
+
|
|
36
67
|
def updated_config(source, value):
|
|
37
|
-
"""
|
|
68
|
+
"""Set the v2 feature cap and agents fallback, preserving enablement and unrelated values."""
|
|
38
69
|
original = tomllib.loads(source)
|
|
39
|
-
validate_overrides(original, value)
|
|
70
|
+
validate_overrides({"profiles": original.get("profiles", {})}, value)
|
|
40
71
|
expected = copy.deepcopy(original)
|
|
41
72
|
agents = expected.setdefault("agents", {})
|
|
42
73
|
agents.pop("max_threads", None)
|
|
43
74
|
agents[KEY] = value
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
section = "other"
|
|
56
|
-
if (section == "agents" and assignment.match(line)) or (not section and dotted.match(line)):
|
|
57
|
-
continue
|
|
58
|
-
output.append(line)
|
|
59
|
-
if not inserted:
|
|
60
|
-
if any(dotted.match(line) for line in lines):
|
|
61
|
-
output.insert(0, f"agents.{KEY} = {value}\n")
|
|
62
|
-
else:
|
|
63
|
-
output.extend(["\n[agents]\n", f"{KEY} = {value}\n"])
|
|
64
|
-
result = "".join(output)
|
|
75
|
+
features = expected.setdefault("features", {})
|
|
76
|
+
feature = features.get("multi_agent_v2")
|
|
77
|
+
result = edit_setting(source, ("agents",), KEY, value, ("max_threads",))
|
|
78
|
+
if isinstance(feature, bool):
|
|
79
|
+
result = edit_setting(result, ("features",), "multi_agent_v2", None)
|
|
80
|
+
result = edit_setting(result, ("features", "multi_agent_v2"), "enabled", str(feature).lower())
|
|
81
|
+
features["multi_agent_v2"] = {"enabled": feature}
|
|
82
|
+
elif feature is None:
|
|
83
|
+
features["multi_agent_v2"] = {}
|
|
84
|
+
features["multi_agent_v2"][KEY] = value
|
|
85
|
+
result = edit_setting(result, ("features", "multi_agent_v2"), KEY, value)
|
|
65
86
|
try:
|
|
66
87
|
unchanged = tomllib.loads(result) == expected
|
|
67
88
|
except tomllib.TOMLDecodeError:
|
|
68
89
|
unchanged = False
|
|
69
90
|
if not unchanged:
|
|
70
|
-
raise ValueError("unsupported
|
|
91
|
+
raise ValueError("unsupported Codex TOML layout; use normal [agents] and [features.multi_agent_v2] tables")
|
|
71
92
|
return result
|
package/lib/codex_settings.py
CHANGED
|
@@ -92,7 +92,9 @@ def readback(paths, value):
|
|
|
92
92
|
result = {}
|
|
93
93
|
for path in paths:
|
|
94
94
|
document = tomllib.loads(path.read_text()) if path.exists() else {}
|
|
95
|
-
|
|
95
|
+
current = limits(document)
|
|
96
|
+
matches = (all(current[key] == value for key in ("configured", "feature_override"))
|
|
97
|
+
if value is not None else True)
|
|
96
98
|
try:
|
|
97
99
|
validate_overrides(document, value)
|
|
98
100
|
except ValueError:
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -33,14 +33,30 @@ class CodexConfigEditTests(unittest.TestCase):
|
|
|
33
33
|
self.assertEqual(tomllib.loads(updated)["agents"][KEY], 8)
|
|
34
34
|
self.assertEqual(updated_config(updated, 8), updated)
|
|
35
35
|
|
|
36
|
-
def
|
|
37
|
-
for source in ('[features.multi_agent_v2]\nmax_concurrent_threads_per_session = 20\n',
|
|
36
|
+
def test_conflicting_profile_overrides_are_rejected(self):
|
|
37
|
+
for source in ('[profiles.quick.features.multi_agent_v2]\nmax_concurrent_threads_per_session = 20\n',
|
|
38
38
|
'[profiles.quick.agents]\nmax_threads = 20\n'):
|
|
39
39
|
with self.subTest(source=source), self.assertRaisesRegex(ValueError, "overrides"):
|
|
40
40
|
updated_config(source, 8)
|
|
41
41
|
source = '[features.multi_agent_v2]\nmax_concurrent_threads_per_session = 8\n'
|
|
42
42
|
self.assertEqual(tomllib.loads(updated_config(source, 8))["agents"][KEY], 8)
|
|
43
43
|
|
|
44
|
+
def test_existing_eight_thread_policies_and_feature_overrides_change_to_seven(self):
|
|
45
|
+
for feature in ('[features]\nmulti_agent_v2 = true\n',
|
|
46
|
+
'[features]\nmulti_agent_v2 = false\n',
|
|
47
|
+
'[features.multi_agent_v2]\nenabled = true\nmax_concurrent_threads_per_session = 20\n',
|
|
48
|
+
'features.multi_agent_v2.max_concurrent_threads_per_session = 8\n',
|
|
49
|
+
'[features]\nmulti_agent_v2.max_concurrent_threads_per_session = 8\n'):
|
|
50
|
+
source = feature + '[agents]\nmax_concurrent_threads_per_session = 8\n'
|
|
51
|
+
with self.subTest(feature=feature):
|
|
52
|
+
updated = updated_config(source, 7)
|
|
53
|
+
document = tomllib.loads(updated)
|
|
54
|
+
self.assertEqual(document["agents"][KEY], 7)
|
|
55
|
+
self.assertEqual(document["features"]["multi_agent_v2"][KEY], 7)
|
|
56
|
+
if 'false' in feature:
|
|
57
|
+
self.assertFalse(document["features"]["multi_agent_v2"]["enabled"])
|
|
58
|
+
self.assertEqual(updated_config(updated, 7), updated)
|
|
59
|
+
|
|
44
60
|
def test_unsupported_layouts_and_string_lookalikes_fail_safely(self):
|
|
45
61
|
for source in ('agents = { max_threads = 4 }\n',
|
|
46
62
|
'instructions = """\n[agents]\nmax_threads = 99\n"""\n'):
|
|
@@ -79,13 +79,31 @@ class CodexSettingsTests(unittest.TestCase):
|
|
|
79
79
|
|
|
80
80
|
def test_invalid_config_is_rejected_before_any_configuration_is_changed(self):
|
|
81
81
|
before = self.account.read_text()
|
|
82
|
-
self.global_config.write_text('[features.multi_agent_v2]\
|
|
82
|
+
self.global_config.write_text('[profiles.quick.features.multi_agent_v2]\n'
|
|
83
|
+
'max_concurrent_threads_per_session = 20\n')
|
|
83
84
|
result = self.command("configure", "--max-subagents", "8", "--include-global", "--json")
|
|
84
85
|
self.assertNotEqual(result.returncode, 0)
|
|
85
86
|
self.assertIn("overrides", result.stderr)
|
|
86
87
|
self.assertEqual(self.account.read_text(), before)
|
|
87
88
|
self.assertFalse((self.pool / "codex-settings-policy.json").exists())
|
|
88
89
|
|
|
90
|
+
def test_seven_thread_policy_requires_v2_setting_and_repairs_legacy_only_configs(self):
|
|
91
|
+
result = self.command("configure", "--max-subagents", "8", "--include-global", "--json")
|
|
92
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
93
|
+
result = self.command("configure", "--max-subagents", "7", "--include-global", "--json")
|
|
94
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
95
|
+
for path in (self.account, self.global_config):
|
|
96
|
+
config = tomllib.loads(path.read_text())
|
|
97
|
+
self.assertEqual(config["features"]["multi_agent_v2"][KEY], 7)
|
|
98
|
+
self.assertEqual(config["agents"][KEY], 7)
|
|
99
|
+
self.account.write_text('[agents]\nmax_concurrent_threads_per_session = 7\n')
|
|
100
|
+
result = self.command("configure", "--include-global", "--check", "--json")
|
|
101
|
+
self.assertEqual(result.returncode, 1, result.stderr)
|
|
102
|
+
self.assertEqual(json.loads(result.stdout)["mismatch_count"], 1)
|
|
103
|
+
result = self.command("configure", "--max-subagents", "7", "--include-global", "--json")
|
|
104
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
105
|
+
self.assertTrue(json.loads(result.stdout)["verified"])
|
|
106
|
+
|
|
89
107
|
def test_symlinked_global_config_remains_linked(self):
|
|
90
108
|
self.account.unlink()
|
|
91
109
|
self.account.symlink_to(self.global_config)
|