johns-harness 2026.9.8 → 2026.9.10

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 (114) hide show
  1. package/README.md +57 -10
  2. package/dist/auth-choice-Ck7FJ_PD.js +1 -1
  3. package/dist/auth-choice-CtvmIf50.js +2 -2
  4. package/dist/auth-choice-DYEtgk1X.js +2 -2
  5. package/dist/auth-choice-DtQ1-QBl.js +1 -1
  6. package/dist/auth-profiles-5CHn7vq1.js +101 -171
  7. package/dist/auth-store-transactions.js +135 -0
  8. package/dist/auth-token-DXCF3dFL.js +28 -28
  9. package/dist/auth-token-f950j5qR.js +28 -28
  10. package/dist/codex-oauth-owner.js +227 -0
  11. package/dist/codex-oauth-token.js +53 -0
  12. package/dist/command-registry-ZarkU2_Y.js +5 -1
  13. package/dist/compact-B247y5Qt.js +35 -4
  14. package/dist/config-koj5M_EO.js +10 -9
  15. package/dist/coord-swarm-board.js +30 -4
  16. package/dist/daemon-cli.js +12 -48
  17. package/dist/doctor-config-flow-B1i686kY.js +10 -201
  18. package/dist/doctor-config-flow-BjBa3YNF.js +10 -201
  19. package/dist/entry.js +4 -0
  20. package/dist/exec-approvals-1t-rSzbk.js +4 -5
  21. package/dist/exec-approvals-oKbYU_E8.js +4 -5
  22. package/dist/globals-BM8hKFm0.js +1 -0
  23. package/dist/globals-Bv4ZcVWM.js +1 -0
  24. package/dist/index.js +5 -0
  25. package/dist/installs-DGqxcU3J.js +2 -9
  26. package/dist/manifest-registry-BMEqbkWA.js +8 -7
  27. package/dist/manifest-registry-Ba187z7Z.js +8 -7
  28. package/dist/memory-cli-DkhQZF9L.js +29 -2
  29. package/dist/migrate-inspect.js +215 -0
  30. package/dist/migrate-plan.js +30 -0
  31. package/dist/migrate-plugin-adapters.js +31 -0
  32. package/dist/migrate-service.js +42 -0
  33. package/dist/migrate-transaction.js +89 -0
  34. package/dist/migrate.js +28 -0
  35. package/dist/model-selection-BGlGpPgM.js +102 -143
  36. package/dist/model-selection-BU6wl1le.js +102 -143
  37. package/dist/model-selection-L7RMwsG-.js +101 -171
  38. package/dist/models-CxHdes0w.js +3 -3
  39. package/dist/models-cli-DZnoM8Ln.js +3 -3
  40. package/dist/mr-memory-setup.js +64 -0
  41. package/dist/onboard-C_T1ajyP.js +2 -0
  42. package/dist/onboard-CjFEsMQX.js +2 -0
  43. package/dist/onboarding-DYOenUs-.js +2 -0
  44. package/dist/onboarding-VbQraljl.js +2 -0
  45. package/dist/paths-BJV7vkaX.js +3 -39
  46. package/dist/paths-BfR2LXbA.js +3 -39
  47. package/dist/paths-GBpjI3o0.js +3 -39
  48. package/dist/paths-hfkBoC7i.js +3 -39
  49. package/dist/pi-embedded-C6ITuRXf.js +38 -8
  50. package/dist/pi-embedded-DoQsYfIY.js +38 -8
  51. package/dist/plugin-sdk/agents/auth-profiles/profiles.d.ts +1 -1
  52. package/dist/plugin-sdk/agents/auth-profiles/store.d.ts +2 -2
  53. package/dist/plugin-sdk/bluebubbles.js +4 -40
  54. package/dist/plugin-sdk/commands/onboard-auth.credentials.d.ts +2 -2
  55. package/dist/plugin-sdk/config/agent-limits.d.ts +1 -1
  56. package/dist/plugin-sdk/config/types.agent-defaults.d.ts +2 -2
  57. package/dist/plugin-sdk/config-4Fgm-yEH.js +102 -143
  58. package/dist/plugin-sdk/config-Di-lyCdh.js +102 -143
  59. package/dist/plugin-sdk/core.js +3 -26
  60. package/dist/plugin-sdk/device-pair.js +3 -26
  61. package/dist/plugin-sdk/diagnostics-otel.js +3 -26
  62. package/dist/plugin-sdk/discord.js +12 -48
  63. package/dist/plugin-sdk/dispatch-BP0viZiL.js +38 -8
  64. package/dist/plugin-sdk/dispatch-CQsjmw7g.js +38 -8
  65. package/dist/plugin-sdk/dispatch-Cerq29sy.js +38 -8
  66. package/dist/plugin-sdk/dispatch-Cndjtt0g.js +38 -8
  67. package/dist/plugin-sdk/dispatch-UogiJYul.js +38 -8
  68. package/dist/plugin-sdk/feishu.js +4 -40
  69. package/dist/plugin-sdk/google-gemini-cli-auth.js +3 -39
  70. package/dist/plugin-sdk/googlechat.js +4 -40
  71. package/dist/plugin-sdk/imessage.js +4 -40
  72. package/dist/plugin-sdk/model-auth-CX9cPHdC.js +102 -143
  73. package/dist/plugin-sdk/model-auth-Ciehz0x5.js +102 -143
  74. package/dist/plugin-sdk/model-auth-DVyo6JSX.js +102 -143
  75. package/dist/plugin-sdk/model-auth-kiHYHGrs.js +102 -143
  76. package/dist/plugin-sdk/model-selection-Dbj4jDYM.js +40 -96
  77. package/dist/plugin-sdk/nostr.js +3 -26
  78. package/dist/plugin-sdk/paths-B9fwHuf0.js +3 -39
  79. package/dist/plugin-sdk/paths-D6tDENa_.js +3 -39
  80. package/dist/plugin-sdk/paths-Dmn791zP.js +3 -39
  81. package/dist/plugin-sdk/paths-akVZbnot.js +3 -39
  82. package/dist/plugin-sdk/paths-eFexkPEh.js +3 -39
  83. package/dist/plugin-sdk/reply-DbZnH8-h.js +38 -8
  84. package/dist/plugin-sdk/signal.js +4 -40
  85. package/dist/plugin-sdk/slack.js +4 -40
  86. package/dist/plugin-sdk/synology-chat.js +3 -39
  87. package/dist/plugin-sdk/telegram.js +4 -40
  88. package/dist/plugin-sdk/tlon.js +4 -40
  89. package/dist/plugin-sdk/twitch.js +4 -40
  90. package/dist/plugin-sdk/voice-call.js +3 -39
  91. package/dist/plugin-sdk/zalo.js +4 -40
  92. package/dist/plugin-sdk/zalouser.js +4 -40
  93. package/dist/program-Csx0T2gv.js +1 -0
  94. package/dist/program-context-CKpVgs9O.js +4 -0
  95. package/dist/prompt-select-styled-C_vP6os-.js +2 -4
  96. package/dist/prompt-select-styled-ClBOfLjF.js +2 -4
  97. package/dist/provider-auth-helpers-BKS7ln76.js +1 -1
  98. package/dist/provider-auth-helpers-D5_z82-N.js +1 -1
  99. package/dist/register.migrate.js +12 -0
  100. package/dist/register.onboard-3v4vug5B.js +3 -1
  101. package/dist/register.onboard-Cwc9htf5.js +3 -1
  102. package/dist/reply-C5LKjXcC.js +35 -4
  103. package/dist/status-Cz7cjn88.js +1 -1
  104. package/dist/status-DH0rKO8q.js +1 -1
  105. package/dist/swarm-primitive.js +1563 -0
  106. package/dist/utils-CUUJ4XQX.js +3 -39
  107. package/docs/PATCHES.md +165 -113
  108. package/docs/SWARM.md +174 -0
  109. package/docs/images/swarm-spawn-alt.png +0 -0
  110. package/docs/images/swarm-spawn.png +0 -0
  111. package/docs/tools/subagents.md +5 -5
  112. package/johnness.mjs +5 -0
  113. package/package.json +1 -1
  114. package/scripts/postinstall.mjs +3 -6
package/README.md CHANGED
@@ -8,7 +8,17 @@
8
8
 
9
9
  ![A collective of AI agents working one codebase together](docs/images/swarm-collective.png)
10
10
 
11
- John's Harness is a production agent harness for engineers who run more than one coding agent at a time. Agents work the same repositories at once, coordinate through a durable board keyed by git origin, message each other while they run, and ship only through an exact-head push gate with per-commit production authority. Memory persists across sessions and compaction and is shared by every agent in the swarm.
11
+ John's Harness is a production agent harness for engineers who run more than one coding agent at a time. Agents work the same repositories at once, coordinate through a durable board keyed by git origin, message each other while they run, and ship only through an exact-head push gate with per-commit production authority. Memory persists across sessions and compaction and is shared by every agent in the swarm. Launching a swarm takes one command and a number; the members divide the work among themselves.
12
+
13
+ ## Migrating from OpenClaw
14
+ Bring your agents, memory, credentials, and scheduled jobs into John's Harness.
15
+ ```sh
16
+ npm i -g johns-harness && johnness migrate
17
+ ```
18
+ The command backs up your state, creates `~/.johnness`, rewrites settings, and checks the result.
19
+ If your gateway is running, it shows the exact stop command before making changes.
20
+ Review any script updates, then follow the printed commands to start the new gateway.
21
+ Your original installation stays intact. Preview with `johnness migrate --dry-run`; undo with `johnness migrate --rollback`.
12
22
 
13
23
  ## Quick start
14
24
 
@@ -17,12 +27,12 @@ Requires Node 22.12 or newer.
17
27
  ```bash
18
28
  npm install -g johns-harness
19
29
  johnness onboard
20
- johnness config set plugins.entries.mr-memory.config.key "mk_your_key"
30
+ johnness memory mk_your_key
21
31
  ```
22
32
 
23
33
  The package is `johns-harness`; the command it installs is `johnness`.
24
34
 
25
- The wizard sets up credentials, workspace, gateway, and channels. The third line stores a MemoryRouter API key from [memoryrouter.ai](https://memoryrouter.ai); until it is set, the harness still runs and prints one setup line saying where to put it.
35
+ The wizard sets up credentials, workspace, gateway, and channels. It asks for your MemoryRouter API key and links to [signup](https://app.memoryrouter.ai/signup), free to start. If you skip memory in the wizard, the third line stores the key later.
26
36
 
27
37
  ### First run
28
38
 
@@ -30,7 +40,7 @@ The wizard sets up credentials, workspace, gateway, and channels. The third line
30
40
  | --- | --- | --- |
31
41
  | Install | `npm install -g johns-harness` | Installs the CLI, the runtime, and the default memory plugin. A postinstall step applies the patched dependency files. |
32
42
  | Configure | `johnness onboard` | Interactive wizard for gateway, workspace, and skills. `johnness configure` reopens it later. |
33
- | Memory | `johnness config set plugins.entries.mr-memory.config.key "mk_..."` | Turns on persistent memory. The plugin is already installed and enabled. |
43
+ | Memory | `johnness memory mk_your_key` | Turns on persistent memory. The plugin is already installed and enabled. |
34
44
  | Start | `johnness gateway start` | Starts the gateway as a background service. `johnness gateway run` keeps it in the foreground. |
35
45
  | Connect | `johnness channels add --channel telegram --token "<bot token>"` | Connects a chat channel. Discord, Slack, WhatsApp, Signal, iMessage, Matrix, and more are supported. |
36
46
  | Check | `johnness doctor` | Health checks and quick fixes for the gateway and channels. |
@@ -46,21 +56,54 @@ node johnness.mjs --help
46
56
  ### Verify
47
57
 
48
58
  ```bash
49
- npm test # 529 tests against the installed dependency tree
50
- bash scripts/verify-patches.sh # 182 checks across every patch family
59
+ npm test # 701 tests against the installed dependency tree
60
+ bash scripts/verify-patches.sh # 258 checks across every patch family
61
+ ```
62
+
63
+ ## Launch a swarm with one command
64
+
65
+ One call, one number. The orchestrator writes a single directive and chooses how many agents should work it. The members decide everything else.
66
+
67
+ ```
68
+ swarm_spawn({
69
+ directive: "<the whole briefing: goal, scope, forbidden files, push rules, author identity>",
70
+ count: 12,
71
+ coord: { root: "/abs/path/to/repo", branch: "main", prodAuthorized: false },
72
+ model: "anthropic/claude-fable-5-1" // optional, applies to every member
73
+ })
51
74
  ```
52
75
 
76
+ ![One command launches a hundred agents that fan out over a codebase and pick up work from a shared board](docs/images/swarm-spawn.png)
77
+
78
+ Every member receives the same directive plus a short runtime preamble that carries the protocol. Nobody hands out work.
79
+
80
+ The lowest-numbered live member decomposes the directive into work items on the shared board. The post is a compare-and-swap, so exactly one plan exists no matter how many members arrive at the same moment; the rest wait for it rather than poll.
81
+
82
+ Each member claims one open item atomically, works it, pushes, marks it done or failed, and claims the next. A claim is exactly one winner, even under concurrent attempts.
83
+
84
+ Claims carry a 15 minute lease that heartbeats extend. A lease that lapses reopens the item the moment anyone looks at the board, so a dead member never strands its work.
85
+
86
+ When work reveals more items, any member appends them to the plan and the addition wakes the others through their inboxes. Members message each other directly only for a real dependency, and each records how the swarm coordinated so the next one starts smarter.
87
+
88
+ The preamble also carries the operating protocol as defaults with named exceptions: the planner declares the shape of the work, one integrator merges parallel branches and builds and pushes once so parallel work flows into a single sequential step, one gateway or a shared budget fronts each rate-limited host, and done means done. Members ask a service holder with a durable request and wait on the answer without polling.
89
+
90
+ Per-member announces are suppressed. When the last member finishes, one `swarm finished` rollup arrives with every item, its owner, its pushed head, and its summary.
91
+
92
+ `count` runs from 2 to 100. Members beyond the concurrency limit are queued and start as slots free, and `swarm_status` reads a swarm's progress without interrupting anyone. Members are ordinary agents: same depth rules, same push preflight, same per-commit production authority, no force pushes.
93
+
94
+ Full guide, including the preamble members receive and the work item state machine: [`docs/SWARM.md`](docs/SWARM.md).
95
+
53
96
  ## The problem
54
97
 
55
98
  Coding agents today run as isolated processes. Two agents on the same repository have no shared state, no way to talk to each other mid-task, and no gate between a dirty local branch and production. Once a context window compacts, whatever the agent knew is gone.
56
99
 
57
100
  ## What makes it different
58
101
 
59
- - **A swarm, not a pile of agents.** One coordination board per repository, direct agent-to-agent messages delivered inside a busy agent's run, exclusive claims on shared areas, and a push preflight that blocks stale, forced, broad, and unauthorized pushes.
102
+ - **A swarm, not a pile of agents.** One coordination board per repository, direct agent-to-agent messages delivered inside a busy agent's run, exclusive claims on shared areas, a push preflight that blocks stale, forced, broad, and unauthorized pushes, and `swarm_spawn`, which launches up to 100 agents from one directive in a single call.
60
103
  - **Memory built for production.** Tiered recall that traces from consolidated reflections down to the verbatim record, surviving compaction and shared across the swarm. Installed and enabled by default.
61
104
  - **Production authority per commit.** Promotion is granted to an exact SHA, never to a branch name, so one worker's authorization never covers another worker's commits.
62
105
  - **An append-only action log.** Agents record every consequential action to `ACTION_LOG.md` and read it before acting.
63
- - **A fixed runtime surface.** The core does not grow on a schedule. New mechanisms enter only to solve a demonstrated production failure, one narrow, tested, reversible patch at a time: 54 patch families, each with a regression test and a rollback path.
106
+ - **A fixed runtime surface.** The core does not grow on a schedule. New mechanisms enter only to solve a demonstrated production failure, one narrow, tested, reversible patch at a time: 59 patch families, each with a regression test and a rollback path.
64
107
  - **Reliability with a definition.** Bounded retries, explicit failure states, exact-once continuation, context-pressure recovery, durable delivery, and supervised gateway recovery.
65
108
  - **Coding first, phone optional.** Direct the work at a high level from a chat channel while agents plan, implement, test, coordinate, and stage releases unattended.
66
109
  - **Local voice.** Optional native Whisper transcription of inbound audio, entirely on your machine. Off by default. [Setup](docs/NATIVE-WHISPER-MUSE.md).
@@ -92,8 +135,12 @@ Agents sharing a filesystem are not a swarm. A swarm knows who owns which work,
92
135
 
93
136
  **Production authority belongs to a commit.** Never to a branch name or a timestamp.
94
137
 
138
+ **One directive, one number.** `swarm_spawn` launches up to 100 members from the same briefing and they divide the work themselves. See the launch section near the top of this page and the [swarm guide](docs/SWARM.md).
139
+
95
140
  ## Memory
96
141
 
142
+ Run `johnness memory mk_your_key` to set your MemoryRouter API key and start persistent memory, or enter it during onboarding.
143
+
97
144
  The future of AI is not smarter models. It is models with the right context: what is happening, what happened before, and what matters now.
98
145
 
99
146
  An agent in this harness remembers what it was told yesterday, the decision it made last Tuesday, the fix it tried that did not work, and why. That context persists across sessions and survives compaction. It does not live and die inside one context window.
@@ -123,7 +170,7 @@ The log is a [`RULES.md`](RULES.md) workspace convention enforced through the ac
123
170
 
124
171
  ## Patch families
125
172
 
126
- The runtime carries 54 patch families. Each is a production fix applied at the source with a regression test, a durable patch marker, and a rollback path. A verifier runs 182 checks against the package tree and installed dependencies, and 529 tests run against the installed dependency tree.
173
+ The runtime carries 59 patch families. Each is a production fix applied at the source with a regression test, a durable patch marker, and a rollback path. A verifier runs 258 checks against the package tree and installed dependencies, and 701 tests run against the installed dependency tree.
127
174
 
128
175
  Every family follows the same six steps: reproduce the failure, trace the exact runtime path, make the smallest source-level change that restores the invariant, add a regression test and a patch marker, run the verifier, and retain rollback artifacts. The full index: [`docs/PATCHES.md`](docs/PATCHES.md).
129
176
 
@@ -140,7 +187,7 @@ Every family follows the same six steps: reproduce the failure, trace the exact
140
187
  ## Documentation
141
188
 
142
189
  - [`docs/index.md`](docs/index.md): runtime docs, from gateway and channels to plugins, nodes, and CLI reference.
143
- - [`docs/PATCHES.md`](docs/PATCHES.md): the 54 patch families, one record each.
190
+ - [`docs/PATCHES.md`](docs/PATCHES.md): the 59 patch families, one record each.
144
191
  - [`docs/NATIVE-WHISPER-MUSE.md`](docs/NATIVE-WHISPER-MUSE.md): local Whisper transcription setup.
145
192
  - [`RULES.md`](RULES.md): the default operating rules, including Rule Zero on production authorization.
146
193
 
@@ -206,7 +206,7 @@ async function applyNonInteractiveAuthChoice(params) {
206
206
  provider,
207
207
  name: ""
208
208
  });
209
- upsertAuthProfile({
209
+ await upsertAuthProfile({
210
210
  profileId,
211
211
  credential: {
212
212
  type: "token",
@@ -62,7 +62,7 @@ async function applyAuthChoiceAnthropic(params) {
62
62
  provider,
63
63
  name: String(profileNameRaw ?? "")
64
64
  });
65
- upsertAuthProfile({
65
+ await upsertAuthProfile({
66
66
  profileId: namedProfileId,
67
67
  agentDir: params.agentDir,
68
68
  credential: {
@@ -813,7 +813,7 @@ async function applyAuthChoicePluginProvider(params, options) {
813
813
  });
814
814
  if (result.configPatch) nextConfig = mergeConfigPatch(nextConfig, result.configPatch);
815
815
  for (const profile of result.profiles) {
816
- upsertAuthProfile({
816
+ await upsertAuthProfile({
817
817
  profileId: profile.profileId,
818
818
  credential: profile.credential,
819
819
  agentDir
@@ -62,7 +62,7 @@ async function applyAuthChoiceAnthropic(params) {
62
62
  provider,
63
63
  name: String(profileNameRaw ?? "")
64
64
  });
65
- upsertAuthProfile({
65
+ await upsertAuthProfile({
66
66
  profileId: namedProfileId,
67
67
  agentDir: params.agentDir,
68
68
  credential: {
@@ -813,7 +813,7 @@ async function applyAuthChoicePluginProvider(params, options) {
813
813
  });
814
814
  if (result.configPatch) nextConfig = mergeConfigPatch(nextConfig, result.configPatch);
815
815
  for (const profile of result.profiles) {
816
- upsertAuthProfile({
816
+ await upsertAuthProfile({
817
817
  profileId: profile.profileId,
818
818
  credential: profile.credential,
819
819
  agentDir
@@ -206,7 +206,7 @@ async function applyNonInteractiveAuthChoice(params) {
206
206
  provider,
207
207
  name: ""
208
208
  });
209
- upsertAuthProfile({
209
+ await upsertAuthProfile({
210
210
  profileId,
211
211
  credential: {
212
212
  type: "token",
@@ -1,3 +1,4 @@
1
+ import { refreshManagedCodexProfile, readManagedCodexStore, mutateAuthProfile, updateAuthStoreMetadata, saveAuthStoreSnapshot } from "./codex-oauth-owner.js";
1
2
  import { t as __exportAll } from "./rolldown-runtime-DUslC3ob.js";
2
3
  import { _ as expandHomePrefix, a as resolveCanonicalConfigPath, c as resolveDefaultConfigCandidates, d as resolveIsNixMode, f as resolveLegacyStateDirs, g as resolveStateDir, h as resolveOAuthPath, i as isNixMode, l as resolveGatewayLockDir, m as resolveOAuthDir, n as DEFAULT_GATEWAY_PORT, o as resolveConfigPath, p as resolveNewStateDir, r as STATE_DIR, s as resolveConfigPathCandidate, t as CONFIG_PATH, u as resolveGatewayPort, y as resolveRequiredHomeDir } from "./paths-BfR2LXbA.js";
3
4
  import { c as sanitizeForLog, l as stripAnsi, t as createSubsystemLogger } from "./subsystem-Cf9yS0UI.js";
@@ -4199,13 +4200,7 @@ function resolveAuthStorePathForDisplay(agentDir) {
4199
4200
  const pathname = resolveAuthStorePath(agentDir);
4200
4201
  return pathname.startsWith("~") ? pathname : resolveUserPath(pathname);
4201
4202
  }
4202
- function ensureAuthStoreFile(pathname) {
4203
- if (fs.existsSync(pathname)) return;
4204
- saveJsonFile(pathname, {
4205
- version: 1,
4206
- profiles: {}
4207
- });
4208
- }
4203
+
4209
4204
  //#endregion
4210
4205
  //#region src/agents/auth-profiles/store.ts
4211
4206
  const AUTH_PROFILE_TYPES = new Set([
@@ -4213,6 +4208,24 @@ const AUTH_PROFILE_TYPES = new Set([
4213
4208
  "oauth",
4214
4209
  "token"
4215
4210
  ]);
4211
+ // JOHNNESS_PATCH_CODEX_OAUTH_OWNERSHIP_58: all compiled adapters share one owner.
4212
+ function codexOwnershipContext(agentDir) {
4213
+ return {
4214
+ stateDir: resolveStateDir(),
4215
+ mainAgentDir: resolveJohnnessAgentDir(),
4216
+ agentDir: agentDir ? resolveUserPath(agentDir) : void 0,
4217
+ log: (event) => log$8.info("Codex OAuth ownership", event)
4218
+ };
4219
+ }
4220
+ function readCodexOwnedAuthStore(agentDir, store) {
4221
+ return readManagedCodexStore({ ...codexOwnershipContext(agentDir), store });
4222
+ }
4223
+ function sanitizeAuthProfileCredential(credential) {
4224
+ const sanitized = { ...credential };
4225
+ if (sanitized.type === "api_key" && sanitized.keyRef) delete sanitized.key;
4226
+ if (sanitized.type === "token" && sanitized.tokenRef) delete sanitized.token;
4227
+ return sanitized;
4228
+ }
4216
4229
  const runtimeAuthStoreSnapshots = /* @__PURE__ */ new Map();
4217
4230
  function resolveRuntimeStoreKey(agentDir) {
4218
4231
  return resolveAuthStorePath(agentDir);
@@ -4226,34 +4239,34 @@ function resolveRuntimeAuthProfileStore(agentDir) {
4226
4239
  const requestedKey = resolveRuntimeStoreKey(agentDir);
4227
4240
  const mainStore = runtimeAuthStoreSnapshots.get(mainKey);
4228
4241
  const requestedStore = runtimeAuthStoreSnapshots.get(requestedKey);
4229
- if (!agentDir || requestedKey === mainKey) {
4230
- if (!mainStore) return null;
4231
- return cloneAuthProfileStore(mainStore);
4232
- }
4233
- if (mainStore && requestedStore) return mergeAuthProfileStores(cloneAuthProfileStore(mainStore), cloneAuthProfileStore(requestedStore));
4234
- if (requestedStore) return cloneAuthProfileStore(requestedStore);
4235
- if (mainStore) return cloneAuthProfileStore(mainStore);
4236
- return null;
4242
+ let store;
4243
+ if (!agentDir || requestedKey === mainKey) store = mainStore;
4244
+ else if (mainStore && requestedStore) store = mergeAuthProfileStores(cloneAuthProfileStore(mainStore), cloneAuthProfileStore(requestedStore));
4245
+ else store = requestedStore ?? mainStore;
4246
+ return store ? readCodexOwnedAuthStore(agentDir, cloneAuthProfileStore(store)) : null;
4237
4247
  }
4238
4248
  function replaceRuntimeAuthProfileStoreSnapshots(entries) {
4239
4249
  runtimeAuthStoreSnapshots.clear();
4240
- for (const entry of entries) runtimeAuthStoreSnapshots.set(resolveRuntimeStoreKey(entry.agentDir), cloneAuthProfileStore(entry.store));
4250
+ for (const entry of entries) runtimeAuthStoreSnapshots.set(resolveRuntimeStoreKey(entry.agentDir), readCodexOwnedAuthStore(entry.agentDir, cloneAuthProfileStore(entry.store)));
4241
4251
  }
4242
4252
  function clearRuntimeAuthProfileStoreSnapshots() {
4243
4253
  runtimeAuthStoreSnapshots.clear();
4244
4254
  }
4245
4255
  async function updateAuthProfileStoreWithLock(params) {
4246
- const authPath = resolveAuthStorePath(params.agentDir);
4247
- ensureAuthStoreFile(authPath);
4248
- try {
4249
- return await withFileLock(authPath, AUTH_STORE_LOCK_OPTIONS, async () => {
4250
- const store = ensureAuthProfileStore(params.agentDir);
4251
- if (params.updater(store)) saveAuthProfileStore(store, params.agentDir);
4252
- return store;
4253
- });
4254
- } catch {
4255
- return null;
4256
- }
4256
+ return await updateAuthStoreMetadata({
4257
+ ...codexOwnershipContext(params.agentDir),
4258
+ update: (metadata) => {
4259
+ const effective = readCodexOwnedAuthStore(params.agentDir);
4260
+ const profiles = Object.freeze(Object.fromEntries(Object.entries(effective.profiles).map(([id, credential]) => [id, Object.freeze({ ...credential })])));
4261
+ const view = { ...metadata };
4262
+ Object.defineProperty(view, "profiles", {
4263
+ enumerable: true,
4264
+ get: () => profiles,
4265
+ set: () => { throw new Error("Credential mutations require upsertAuthProfileWithLock"); }
4266
+ });
4267
+ if (params.updater(view)) for (const key of ["usageStats", "lastGood", "order"]) metadata[key] = view[key];
4268
+ }
4269
+ });
4257
4270
  }
4258
4271
  /**
4259
4272
  * Normalise a raw auth-profiles.json credential entry.
@@ -4439,68 +4452,18 @@ function loadCoercedStore(authPath) {
4439
4452
  return coerceAuthStore(loadJsonFile(authPath));
4440
4453
  }
4441
4454
  function loadAuthProfileStore() {
4442
- const authPath = resolveAuthStorePath();
4443
- const asStore = loadCoercedStore(authPath);
4444
- if (asStore) {
4445
- if (syncExternalCliCredentials(asStore)) saveJsonFile(authPath, asStore);
4446
- return asStore;
4447
- }
4448
- const legacy = coerceLegacyStore(loadJsonFile(resolveLegacyAuthStorePath()));
4449
- if (legacy) {
4450
- const store = {
4451
- version: 1,
4452
- profiles: {}
4453
- };
4454
- applyLegacyStore(store, legacy);
4455
- syncExternalCliCredentials(store);
4456
- return store;
4457
- }
4458
- const store = {
4459
- version: 1,
4460
- profiles: {}
4461
- };
4462
- syncExternalCliCredentials(store);
4463
- return store;
4455
+ return loadAuthProfileStoreForAgent();
4464
4456
  }
4465
4457
  function loadAuthProfileStoreForAgent(agentDir, options) {
4466
- const readOnly = options?.readOnly === true;
4467
- const authPath = resolveAuthStorePath(agentDir);
4468
- const asStore = loadCoercedStore(authPath);
4469
- if (asStore) {
4470
- if (syncExternalCliCredentials(asStore) && !readOnly) saveJsonFile(authPath, asStore);
4471
- return asStore;
4472
- }
4473
- if (agentDir && !readOnly) {
4474
- const mainStore = coerceAuthStore(loadJsonFile(resolveAuthStorePath()));
4475
- if (mainStore && Object.keys(mainStore.profiles).length > 0) {
4476
- saveJsonFile(authPath, mainStore);
4477
- log$8.info("inherited auth-profiles from main agent", { agentDir });
4478
- return mainStore;
4479
- }
4458
+ // Loading never copies rotating credentials, migrates files, or imports external CLI state.
4459
+ let store = loadCoercedStore(resolveAuthStorePath(agentDir));
4460
+ if (!store) {
4461
+ store = { version: 1, profiles: {} };
4462
+ const legacy = coerceLegacyStore(loadJsonFile(resolveLegacyAuthStorePath(agentDir)));
4463
+ if (legacy) applyLegacyStore(store, legacy);
4464
+ mergeOAuthFileIntoStore(store);
4480
4465
  }
4481
- const legacy = coerceLegacyStore(loadJsonFile(resolveLegacyAuthStorePath(agentDir)));
4482
- const store = {
4483
- version: 1,
4484
- profiles: {}
4485
- };
4486
- if (legacy) applyLegacyStore(store, legacy);
4487
- const mergedOAuth = mergeOAuthFileIntoStore(store);
4488
- const syncedCli = syncExternalCliCredentials(store);
4489
- const forceReadOnly = process.env.JOHNNESS_AUTH_STORE_READONLY === "1";
4490
- const shouldWrite = !readOnly && !forceReadOnly && (legacy !== null || mergedOAuth || syncedCli);
4491
- if (shouldWrite) saveJsonFile(authPath, store);
4492
- if (shouldWrite && legacy !== null) {
4493
- const legacyPath = resolveLegacyAuthStorePath(agentDir);
4494
- try {
4495
- fs.unlinkSync(legacyPath);
4496
- } catch (err) {
4497
- if (err?.code !== "ENOENT") log$8.warn("failed to delete legacy auth.json after migration", {
4498
- err,
4499
- legacyPath
4500
- });
4501
- }
4502
- }
4503
- return store;
4466
+ return readCodexOwnedAuthStore(agentDir, store);
4504
4467
  }
4505
4468
  function loadAuthProfileStoreForRuntime(agentDir, options) {
4506
4469
  const store = loadAuthProfileStoreForAgent(agentDir, options);
@@ -4519,13 +4482,12 @@ function ensureAuthProfileStore(agentDir, options) {
4519
4482
  const runtimeStore = resolveRuntimeAuthProfileStore(agentDir);
4520
4483
  if (runtimeStore) return runtimeStore;
4521
4484
  const store = loadAuthProfileStoreForAgent(agentDir, options);
4522
- const authPath = resolveAuthStorePath(agentDir);
4523
4485
  const mainAuthPath = resolveAuthStorePath();
4524
- if (!agentDir || authPath === mainAuthPath) return store;
4525
- return mergeAuthProfileStores(loadAuthProfileStoreForAgent(void 0, options), store);
4486
+ const effective = !agentDir || resolveAuthStorePath(agentDir) === mainAuthPath ? store : mergeAuthProfileStores(loadAuthProfileStoreForAgent(void 0, options), store);
4487
+ return readCodexOwnedAuthStore(agentDir, effective);
4526
4488
  }
4527
- function saveAuthProfileStore(store, agentDir) {
4528
- saveJsonFile(resolveAuthStorePath(agentDir), {
4489
+ async function saveAuthProfileStore(store, agentDir) {
4490
+ await saveAuthStoreSnapshot({ ...codexOwnershipContext(agentDir), store: {
4529
4491
  version: 1,
4530
4492
  profiles: Object.fromEntries(Object.entries(store.profiles).map(([profileId, credential]) => {
4531
4493
  if (credential.type === "api_key" && credential.keyRef && credential.key !== void 0) {
@@ -4543,7 +4505,7 @@ function saveAuthProfileStore(store, agentDir) {
4543
4505
  order: store.order ?? void 0,
4544
4506
  lastGood: store.lastGood ?? void 0,
4545
4507
  usageStats: store.usageStats ?? void 0
4546
- });
4508
+ } });
4547
4509
  }
4548
4510
  //#endregion
4549
4511
  //#region src/agents/auth-profiles/profiles.ts
@@ -4568,7 +4530,7 @@ async function setAuthProfileOrder(params) {
4568
4530
  }
4569
4531
  });
4570
4532
  }
4571
- function upsertAuthProfile(params) {
4533
+ async function upsertAuthProfile(params) {
4572
4534
  const credential = params.credential.type === "api_key" ? {
4573
4535
  ...params.credential,
4574
4536
  ...typeof params.credential.key === "string" ? { key: normalizeSecretInput(params.credential.key) } : {}
@@ -4576,18 +4538,11 @@ function upsertAuthProfile(params) {
4576
4538
  ...params.credential,
4577
4539
  token: normalizeSecretInput(params.credential.token)
4578
4540
  } : params.credential;
4579
- const store = ensureAuthProfileStore(params.agentDir);
4580
- store.profiles[params.profileId] = credential;
4581
- saveAuthProfileStore(store, params.agentDir);
4541
+ await mutateAuthProfile({ ...codexOwnershipContext(params.agentDir), profileId: params.profileId, credential: sanitizeAuthProfileCredential(credential) });
4582
4542
  }
4583
4543
  async function upsertAuthProfileWithLock(params) {
4584
- return await updateAuthProfileStoreWithLock({
4585
- agentDir: params.agentDir,
4586
- updater: (store) => {
4587
- store.profiles[params.profileId] = params.credential;
4588
- return true;
4589
- }
4590
- });
4544
+ await mutateAuthProfile({ ...codexOwnershipContext(params.agentDir), profileId: params.profileId, credential: sanitizeAuthProfileCredential(params.credential) });
4545
+ return readCodexOwnedAuthStore(params.agentDir);
4591
4546
  }
4592
4547
  function listProfilesForProvider(store, provider) {
4593
4548
  const providerKey = normalizeProviderIdForAuth(provider);
@@ -4607,17 +4562,7 @@ async function markAuthProfileGood(params) {
4607
4562
  return true;
4608
4563
  }
4609
4564
  });
4610
- if (updated) {
4611
- store.lastGood = updated.lastGood;
4612
- return;
4613
- }
4614
- const profile = store.profiles[profileId];
4615
- if (!profile || profile.provider !== provider) return;
4616
- store.lastGood = {
4617
- ...store.lastGood,
4618
- [provider]: profileId
4619
- };
4620
- saveAuthProfileStore(store, agentDir);
4565
+ if (updated) store.lastGood = updated.lastGood;
4621
4566
  }
4622
4567
  //#endregion
4623
4568
  //#region src/agents/auth-profiles/repair.ts
@@ -4939,7 +4884,7 @@ function resolveAgentMaxConcurrent(cfg) {
4939
4884
  function resolveSubagentMaxConcurrent(cfg) {
4940
4885
  const raw = cfg?.agents?.defaults?.subagents?.maxConcurrent;
4941
4886
  if (typeof raw === "number" && Number.isFinite(raw)) return Math.max(1, Math.floor(raw));
4942
- return 12;
4887
+ return 100;
4943
4888
  }
4944
4889
  //#endregion
4945
4890
  //#region src/config/talk.ts
@@ -5357,7 +5302,7 @@ function applyAgentDefaults(cfg) {
5357
5302
  }
5358
5303
  const nextSubagents = defaults?.subagents ? { ...defaults.subagents } : {};
5359
5304
  if (!hasSubMax) {
5360
- nextSubagents.maxConcurrent = 12;
5305
+ nextSubagents.maxConcurrent = 100;
5361
5306
  mutated = true;
5362
5307
  }
5363
5308
  if (!mutated) return cfg;
@@ -11026,7 +10971,7 @@ const AgentDefaultsSchema = z.object({
11026
10971
  subagents: z.object({
11027
10972
  maxConcurrent: z.number().int().positive().optional(),
11028
10973
  maxSpawnDepth: z.number().int().min(1).max(5).optional().describe("Maximum nesting depth for sub-agent spawning. 1 = no nesting (default), 2 = sub-agents can spawn sub-sub-agents."),
11029
- maxChildrenPerAgent: z.number().int().min(1).max(20).optional().describe("Maximum number of active children a single agent session can spawn (default: 12)."),
10974
+ maxChildrenPerAgent: z.number().int().min(1).max(100).optional().describe("Maximum number of active children a single agent session can spawn (default: 100)."),
11030
10975
  archiveAfterMinutes: z.number().int().positive().optional(),
11031
10976
  model: AgentModelSchema.optional(),
11032
10977
  thinking: z.string().optional(),
@@ -16298,19 +16243,15 @@ function buildOAuthProfileResult(params) {
16298
16243
  function extractErrorMessage(error) {
16299
16244
  return error instanceof Error ? error.message : String(error);
16300
16245
  }
16301
- function shouldUseOpenaiCodexRefreshFallback(params) {
16302
- if (normalizeProviderId(params.provider) !== "openai-codex") return false;
16303
- const message = extractErrorMessage(params.error);
16304
- if (!/extract\s+accountid\s+from\s+token/i.test(message)) return false;
16305
- return typeof params.credentials.access === "string" && params.credentials.access.trim().length > 0;
16306
- }
16307
- function adoptNewerMainOAuthCredential(params) {
16246
+
16247
+ async function adoptNewerMainOAuthCredential(params) {
16248
+ if (params.cred.provider === "openai-codex") return readCodexOwnedAuthStore(params.agentDir, params.store).profiles[params.profileId] ?? null;
16308
16249
  if (!params.agentDir) return null;
16309
16250
  try {
16310
16251
  const mainCred = ensureAuthProfileStore(void 0).profiles[params.profileId];
16311
16252
  if (mainCred?.type === "oauth" && mainCred.provider === params.cred.provider && Number.isFinite(mainCred.expires) && (!Number.isFinite(params.cred.expires) || mainCred.expires > params.cred.expires)) {
16312
16253
  params.store.profiles[params.profileId] = { ...mainCred };
16313
- saveAuthProfileStore(params.store, params.agentDir);
16254
+ await saveAuthProfileStore(params.store, params.agentDir);
16314
16255
  log$8.info("adopted newer OAuth credentials from main agent", {
16315
16256
  profileId: params.profileId,
16316
16257
  agentDir: params.agentDir,
@@ -16327,11 +16268,22 @@ function adoptNewerMainOAuthCredential(params) {
16327
16268
  return null;
16328
16269
  }
16329
16270
  async function refreshOAuthTokenWithLock(params) {
16271
+ const store = readCodexOwnedAuthStore(params.agentDir, params.store);
16272
+ const cred = store.profiles[params.profileId];
16273
+ if (cred?.provider === "openai-codex" || params.store?.profiles[params.profileId]?.provider === "openai-codex") return await refreshManagedCodexProfile({
16274
+ ...codexOwnershipContext(params.agentDir),
16275
+ profileId: params.profileId,
16276
+ store: params.store
16277
+ });
16278
+ return await refreshUnmanagedOAuthTokenWithLock(params);
16279
+ }
16280
+ async function refreshUnmanagedOAuthTokenWithLock(params) {
16330
16281
  const authPath = resolveAuthStorePath(params.agentDir);
16331
- ensureAuthStoreFile(authPath);
16282
+ fs.mkdirSync(path.dirname(authPath), { recursive: true, mode: 448 });
16332
16283
  return await withFileLock(authPath, AUTH_STORE_LOCK_OPTIONS, async () => {
16333
16284
  const store = ensureAuthProfileStore(params.agentDir);
16334
16285
  const cred = store.profiles[params.profileId];
16286
+ if (cred?.provider === "openai-codex") throw new Error("Auth profile changed to Codex; resolve again through its owner");
16335
16287
  if (!cred || cred.type !== "oauth") return null;
16336
16288
  if (Date.now() < cred.expires) return {
16337
16289
  apiKey: buildOAuthApiKey(cred.provider, cred),
@@ -16361,13 +16313,15 @@ async function refreshOAuthTokenWithLock(params) {
16361
16313
  ...result.newCredentials,
16362
16314
  type: "oauth"
16363
16315
  };
16364
- saveAuthProfileStore(store, params.agentDir);
16316
+ await saveAuthProfileStore(store, params.agentDir);
16365
16317
  return result;
16366
16318
  });
16367
16319
  }
16368
16320
  async function tryResolveOAuthProfile(params) {
16369
16321
  const { cfg, store, profileId } = params;
16370
- const cred = store.profiles[profileId];
16322
+ const effective = readCodexOwnedAuthStore(params.agentDir, store);
16323
+ store.profiles = effective.profiles;
16324
+ const cred = effective.profiles[profileId];
16371
16325
  if (!cred || cred.type !== "oauth") return null;
16372
16326
  if (!isProfileConfigCompatible({
16373
16327
  cfg,
@@ -16375,6 +16329,11 @@ async function tryResolveOAuthProfile(params) {
16375
16329
  provider: cred.provider,
16376
16330
  mode: cred.type
16377
16331
  })) return null;
16332
+ if (cred.type === "oauth" && cred.provider === "openai-codex") {
16333
+ const result = await refreshManagedCodexProfile({ ...codexOwnershipContext(params.agentDir), profileId, store });
16334
+ if (!result) return null;
16335
+ return buildApiKeyProfileResult({ apiKey: result.apiKey, provider: "openai-codex", email: result.newCredentials?.email ?? cred.email });
16336
+ }
16378
16337
  if (Date.now() < cred.expires) return buildOAuthProfileResult({
16379
16338
  provider: cred.provider,
16380
16339
  credentials: cred,
@@ -16427,7 +16386,9 @@ async function resolveProfileSecretString(params) {
16427
16386
  }
16428
16387
  async function resolveApiKeyForProfile(params) {
16429
16388
  const { cfg, store, profileId } = params;
16430
- const cred = store.profiles[profileId];
16389
+ const effective = readCodexOwnedAuthStore(params.agentDir, store);
16390
+ store.profiles = effective.profiles;
16391
+ const cred = effective.profiles[profileId];
16431
16392
  if (!cred) return null;
16432
16393
  if (!isProfileConfigCompatible({
16433
16394
  cfg,
@@ -16436,6 +16397,11 @@ async function resolveApiKeyForProfile(params) {
16436
16397
  mode: cred.type,
16437
16398
  allowOAuthTokenCompatibility: true
16438
16399
  })) return null;
16400
+ if (cred.type === "oauth" && cred.provider === "openai-codex") {
16401
+ const result = await refreshManagedCodexProfile({ ...codexOwnershipContext(params.agentDir), profileId, store });
16402
+ if (!result) return null;
16403
+ return buildApiKeyProfileResult({ apiKey: result.apiKey, provider: "openai-codex", email: result.newCredentials?.email ?? cred.email });
16404
+ }
16439
16405
  const refResolveCache = {};
16440
16406
  const configForRefResolution = cfg ?? loadConfig();
16441
16407
  const refDefaults = configForRefResolution.secrets?.defaults;
@@ -16479,7 +16445,7 @@ async function resolveApiKeyForProfile(params) {
16479
16445
  email: cred.email
16480
16446
  });
16481
16447
  }
16482
- const oauthCred = adoptNewerMainOAuthCredential({
16448
+ const oauthCred = await adoptNewerMainOAuthCredential({
16483
16449
  store,
16484
16450
  profileId,
16485
16451
  agentDir: params.agentDir,
@@ -16504,6 +16470,7 @@ async function resolveApiKeyForProfile(params) {
16504
16470
  } catch (error) {
16505
16471
  const refreshedStore = ensureAuthProfileStore(params.agentDir);
16506
16472
  const refreshed = refreshedStore.profiles[profileId];
16473
+ if (refreshed?.type === "oauth" && refreshed.provider === "openai-codex") return await resolveApiKeyForProfile({ ...params, store: refreshedStore });
16507
16474
  if (refreshed?.type === "oauth" && Date.now() < refreshed.expires) return buildOAuthProfileResult({
16508
16475
  provider: refreshed.provider,
16509
16476
  credentials: refreshed,
@@ -16526,9 +16493,9 @@ async function resolveApiKeyForProfile(params) {
16526
16493
  } catch {}
16527
16494
  if (params.agentDir) try {
16528
16495
  const mainCred = ensureAuthProfileStore(void 0).profiles[profileId];
16529
- if (mainCred?.type === "oauth" && Date.now() < mainCred.expires) {
16496
+ if (mainCred?.type === "oauth" && mainCred.provider === cred.provider && Date.now() < mainCred.expires) {
16530
16497
  refreshedStore.profiles[profileId] = { ...mainCred };
16531
- saveAuthProfileStore(refreshedStore, params.agentDir);
16498
+ await saveAuthProfileStore(refreshedStore, params.agentDir);
16532
16499
  log$8.info("inherited fresh OAuth credentials from main agent", {
16533
16500
  profileId,
16534
16501
  agentDir: params.agentDir,
@@ -16541,21 +16508,6 @@ async function resolveApiKeyForProfile(params) {
16541
16508
  });
16542
16509
  }
16543
16510
  } catch {}
16544
- if (shouldUseOpenaiCodexRefreshFallback({
16545
- provider: cred.provider,
16546
- credentials: cred,
16547
- error
16548
- })) {
16549
- log$8.warn("openai-codex oauth refresh failed; using cached access token fallback", {
16550
- profileId,
16551
- provider: cred.provider
16552
- });
16553
- return buildApiKeyProfileResult({
16554
- apiKey: cred.access,
16555
- provider: cred.provider,
16556
- email: cred.email
16557
- });
16558
- }
16559
16511
  const message = extractErrorMessage(error);
16560
16512
  const hint = formatAuthDoctorHint({
16561
16513
  cfg,
@@ -16728,13 +16680,7 @@ async function markAuthProfileUsed(params) {
16728
16680
  return true;
16729
16681
  }
16730
16682
  });
16731
- if (updated) {
16732
- store.usageStats = updated.usageStats;
16733
- return;
16734
- }
16735
- if (!store.profiles[profileId]) return;
16736
- updateUsageStatsEntry(store, profileId, (existing) => resetUsageStats(existing, { lastUsed: Date.now() }));
16737
- saveAuthProfileStore(store, agentDir);
16683
+ if (updated) store.usageStats = updated.usageStats;
16738
16684
  }
16739
16685
  function calculateAuthProfileCooldownMs(errorCount) {
16740
16686
  const normalized = Math.max(1, errorCount);
@@ -16855,23 +16801,7 @@ async function markAuthProfileFailure(params) {
16855
16801
  return true;
16856
16802
  }
16857
16803
  });
16858
- if (updated) {
16859
- store.usageStats = updated.usageStats;
16860
- return;
16861
- }
16862
- if (!store.profiles[profileId]) return;
16863
- const now = Date.now();
16864
- const cfgResolved = resolveAuthCooldownConfig({
16865
- cfg,
16866
- providerId: normalizeProviderId(store.profiles[profileId]?.provider ?? "")
16867
- });
16868
- updateUsageStatsEntry(store, profileId, (existing) => computeNextProfileUsageStats({
16869
- existing: existing ?? {},
16870
- now,
16871
- reason,
16872
- cfgResolved
16873
- }));
16874
- saveAuthProfileStore(store, agentDir);
16804
+ if (updated) store.usageStats = updated.usageStats;
16875
16805
  }
16876
16806
  //#endregion
16877
16807
  //#region src/agents/auth-profiles/order.ts