johns-harness 2026.9.13 → 2026.9.15

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 (71) hide show
  1. package/README.md +11 -6
  2. package/dist/auth-profiles-5CHn7vq1.js +28 -0
  3. package/dist/commands-registry-C78-tPS3.js +14 -0
  4. package/dist/commands-registry-D0Jkz-Sn.js +14 -0
  5. package/dist/commands-registry-DJWLO-6B.js +14 -0
  6. package/dist/commands-registry-l51VuG0m.js +14 -0
  7. package/dist/compact-B247y5Qt.js +20 -2
  8. package/dist/config-koj5M_EO.js +28 -0
  9. package/dist/daemon-cli.js +28 -0
  10. package/dist/image-generate.js +370 -0
  11. package/dist/model-selection-BGlGpPgM.js +28 -0
  12. package/dist/model-selection-BU6wl1le.js +28 -0
  13. package/dist/model-selection-L7RMwsG-.js +28 -0
  14. package/dist/pi-embedded-C6ITuRXf.js +20 -2
  15. package/dist/pi-embedded-DoQsYfIY.js +20 -2
  16. package/dist/pi-embedded-helpers-CkWXaNFn.js +9 -0
  17. package/dist/pi-embedded-helpers-DLLQ3MsR.js +9 -0
  18. package/dist/plugin-sdk/bluebubbles.js +12 -0
  19. package/dist/plugin-sdk/commands-registry-B4ZP4Y1B.js +14 -0
  20. package/dist/plugin-sdk/commands-registry-B8Og-fWU.js +14 -0
  21. package/dist/plugin-sdk/commands-registry-BoAXxFpH.js +14 -0
  22. package/dist/plugin-sdk/commands-registry-Ca1h6XvV.js +14 -0
  23. package/dist/plugin-sdk/commands-registry-Do-Y2EhN.js +14 -0
  24. package/dist/plugin-sdk/commands-registry-DpRs-E_M.js +14 -0
  25. package/dist/plugin-sdk/config-4Fgm-yEH.js +28 -0
  26. package/dist/plugin-sdk/config-Di-lyCdh.js +28 -0
  27. package/dist/plugin-sdk/discord.js +28 -0
  28. package/dist/plugin-sdk/dispatch-BP0viZiL.js +20 -2
  29. package/dist/plugin-sdk/dispatch-CQsjmw7g.js +20 -2
  30. package/dist/plugin-sdk/dispatch-Cerq29sy.js +20 -2
  31. package/dist/plugin-sdk/dispatch-Cndjtt0g.js +20 -2
  32. package/dist/plugin-sdk/dispatch-UogiJYul.js +20 -2
  33. package/dist/plugin-sdk/feishu.js +12 -0
  34. package/dist/plugin-sdk/googlechat.js +12 -0
  35. package/dist/plugin-sdk/imessage.js +12 -0
  36. package/dist/plugin-sdk/mattermost.js +14 -0
  37. package/dist/plugin-sdk/model-auth-CX9cPHdC.js +28 -0
  38. package/dist/plugin-sdk/model-auth-Ciehz0x5.js +28 -0
  39. package/dist/plugin-sdk/model-auth-DVyo6JSX.js +28 -0
  40. package/dist/plugin-sdk/model-auth-kiHYHGrs.js +28 -0
  41. package/dist/plugin-sdk/model-selection-Dbj4jDYM.js +28 -0
  42. package/dist/plugin-sdk/pi-embedded-helpers-B89Z8pGU.js +9 -0
  43. package/dist/plugin-sdk/pi-embedded-helpers-BDFUaaiY.js +9 -0
  44. package/dist/plugin-sdk/pi-embedded-helpers-BnFhV3or.js +9 -0
  45. package/dist/plugin-sdk/pi-embedded-helpers-CRUVFL1T.js +9 -0
  46. package/dist/plugin-sdk/pi-embedded-helpers-CY7PsNpl.js +9 -0
  47. package/dist/plugin-sdk/pi-embedded-helpers-Dira0ZRW.js +9 -0
  48. package/dist/plugin-sdk/reply-DbZnH8-h.js +20 -2
  49. package/dist/plugin-sdk/signal.js +12 -0
  50. package/dist/plugin-sdk/slack.js +12 -0
  51. package/dist/plugin-sdk/telegram.js +12 -0
  52. package/dist/plugin-sdk/tlon.js +12 -0
  53. package/dist/plugin-sdk/twitch.js +12 -0
  54. package/dist/plugin-sdk/zalo.js +12 -0
  55. package/dist/plugin-sdk/zalouser.js +12 -0
  56. package/dist/redact-snapshot-CjeFvdcl.js +5 -0
  57. package/dist/redact-snapshot-DuqfRMA9.js +5 -0
  58. package/dist/reply-C5LKjXcC.js +20 -2
  59. package/dist/runtime-path.js +58 -0
  60. package/dist/tool-catalog-CFg6jrp9.js +9 -0
  61. package/dist/tool-catalog-DE9Q8xiB.js +9 -0
  62. package/dist/update-cli-Cm_l3W29.js +1 -1
  63. package/dist/update-command.js +276 -0
  64. package/docs/PATCHES.md +28 -0
  65. package/package.json +1 -1
  66. package/skills/openai-image-gen/SKILL.md +3 -3
  67. package/skills/openai-image-gen/scripts/gen.py +1 -1
  68. package/vendor/patched-deps/@mariozechner/pi-ai/dist/models.generated.js +67 -0
  69. package/skills/nano-banana-pro/SKILL.md +0 -65
  70. package/skills/nano-banana-pro/scripts/generate_image.py +0 -235
  71. package/skills/nano-banana-pro/scripts/test_generate_image.py +0 -36
package/README.md CHANGED
@@ -55,7 +55,12 @@ Expired ChatGPT credentials trigger an owner notice with the `/login` recovery c
55
55
 
56
56
  <!-- JOHNNESS_PATCH_DEFAULTS_61 -->
57
57
  ### Batteries included
58
- The harness ships the current model catalog and the short aliases `opus`, `sonnet`, `fable`, `astra`, `sol`, `luna`, `terra`, and `gpt`, so `/model opus` works on a fresh install with no `models.providers` block. Channels you configure load without a `plugins.allow` list; only an explicit `enabled: false` or `plugins.deny` turns one off. Updating the harness updates the catalog and aliases for every agent.
58
+ The harness ships the current model catalog and the short aliases `opus`, `sonnet`, `fable`, `astra`, `sol`, `luna`, `terra`, `gpt`, `flash`, `banana`, and `image`, so `/model opus` works on a fresh install with no `models.providers` block. Channels you configure load without a `plugins.allow` list; only an explicit `enabled: false` or `plugins.deny` turns one off. Updating the harness updates the catalog and aliases for every agent. Image generation and editing use GPT Image 2 through the `image_generate` tool: `openai-codex/gpt-image-2` on the ChatGPT subscription first, `openai/gpt-image-2` on the API key second (`agents.defaults.imageGenerationModel`). Nano Banana Pro (`google/gemini-3-pro-image-preview`) ships in the catalog as a selectable image model, reachable with `model: "nano-banana"` and its own `aspectRatio` option; the default stays GPT Image 2 because the subscription path costs nothing.
59
+
60
+ **The alias contract.** Every shipped alias points at the newest model in its line, so an install or an `/update` moves every agent forward with zero per-instance config. Your own `agents.defaults.models[*].alias` entries are still consulted first and still win. Every catalog bump must move the matching alias; the verifier fails the release if it does not.
61
+
62
+ ### Updating from chat
63
+ Send `/update` to install the newest `johns-harness` on the machine the gateway runs on and restart into it; `/update 2026.9.14` pins an exact version. Owner only. The reply names the current and target versions, a second `/update` while one is running is refused, and the first message after the restart confirms the running version. On a writable host the package is reinstalled into the existing global prefix. On an immutable image with a persistent volume (Fly), it is installed into `$JOHNNESS_STATE_DIR/runtime/johnness` and the entrypoint prefers that runtime when it is newer than the image: `exec node "$(node /opt/johnness/lib/node_modules/johns-harness/dist/runtime-path.js)" gateway run`. `JOHNNESS_UPDATE_PREFIX` overrides the target. On failure the reply carries the last lines of npm output and the one command that fixes it.
59
64
 
60
65
  ### From source
61
66
 
@@ -68,8 +73,8 @@ node johnness.mjs --help
68
73
  ### Verify
69
74
 
70
75
  ```bash
71
- npm test # 701 tests against the installed dependency tree
72
- bash scripts/verify-patches.sh # 258 checks across every patch family
76
+ npm test # 767 tests against the installed dependency tree
77
+ bash scripts/verify-patches.sh # 360 checks across every patch family
73
78
  ```
74
79
 
75
80
  ## Launch a swarm with one command
@@ -115,7 +120,7 @@ Coding agents today run as isolated processes. Two agents on the same repository
115
120
  - **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.
116
121
  - **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.
117
122
  - **An append-only action log.** Agents record every consequential action to `ACTION_LOG.md` and read it before acting.
118
- - **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.
123
+ - **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: 62 patch families, each with a regression test and a rollback path.
119
124
  - **Reliability with a definition.** Bounded retries, explicit failure states, exact-once continuation, context-pressure recovery, durable delivery, and supervised gateway recovery.
120
125
  - **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.
121
126
  - **Local voice.** Optional native Whisper transcription of inbound audio, entirely on your machine. Off by default. [Setup](docs/NATIVE-WHISPER-MUSE.md).
@@ -182,7 +187,7 @@ The log is a [`RULES.md`](RULES.md) workspace convention enforced through the ac
182
187
 
183
188
  ## Patch families
184
189
 
185
- 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.
190
+ The runtime carries 62 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 360 checks against the package tree and installed dependencies, and 767 tests run against the installed dependency tree.
186
191
 
187
192
  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).
188
193
 
@@ -199,7 +204,7 @@ Every family follows the same six steps: reproduce the failure, trace the exact
199
204
  ## Documentation
200
205
 
201
206
  - [`docs/index.md`](docs/index.md): runtime docs, from gateway and channels to plugins, nodes, and CLI reference.
202
- - [`docs/PATCHES.md`](docs/PATCHES.md): the 59 patch families, one record each.
207
+ - [`docs/PATCHES.md`](docs/PATCHES.md): the 62 patch families, one record each.
203
208
  - [`docs/NATIVE-WHISPER-MUSE.md`](docs/NATIVE-WHISPER-MUSE.md): local Whisper transcription setup.
204
209
  - [`RULES.md`](RULES.md): the default operating rules, including Rule Zero on production authorization.
205
210
 
@@ -5111,6 +5111,22 @@ const DEFAULT_MODEL_ALIASES = {
5111
5111
  "gemini-flash": "google/gemini-3-flash-preview",
5112
5112
  "gemini-flash-lite": "google/gemini-3.1-flash-lite-preview"
5113
5113
  };
5114
+ // JOHNNESS_PATCH_NANO_BANANA_63: the shipped alias contract. Every alias resolves to the newest model
5115
+ // in its line that exists in the shipped catalog, so a fresh install or an /update moves
5116
+ // every agent forward with no per-instance config. This block carries only the aliases
5117
+ // family 63 moves or adds; the rest are already current in the literal above, and later
5118
+ // keys win. A user alias in agents.defaults.models[*].alias is consulted first and still
5119
+ // overrides. Adding a newer model to the catalog without moving its alias fails the
5120
+ // verifier (63.20, scripts/check-alias-contract.mjs).
5121
+ Object.assign(DEFAULT_MODEL_ALIASES, {
5122
+ gpt: "openai-codex/gpt-5.6-sol",
5123
+ flash: "google/gemini-3.8-flash",
5124
+ "gemini-flash": "google/gemini-3.8-flash",
5125
+ "gemini-flash-lite": "google/gemini-2.5-flash-lite",
5126
+ banana: "google/gemini-3-pro-image-preview",
5127
+ "nano-banana": "google/gemini-3-pro-image-preview",
5128
+ image: "openai-codex/gpt-image-2"
5129
+ });
5114
5130
  const DEFAULT_MODEL_COST = {
5115
5131
  input: 0,
5116
5132
  output: 0,
@@ -6443,6 +6459,17 @@ function applyLegacyAudioTranscriptionModel(params) {
6443
6459
  params.changes.push(params.alreadySetMessage);
6444
6460
  }
6445
6461
  const LEGACY_CONFIG_MIGRATIONS_PART_2 = [
6462
+ {
6463
+ id: "agent.image-generation-model-v1",
6464
+ describe: "Migrate agents.defaults.imageGenerationModel string to primary/fallbacks",
6465
+ apply: (raw, changes) => {
6466
+ const defaults = getRecord(getRecord(raw.agents)?.defaults);
6467
+ if (!defaults || typeof defaults.imageGenerationModel !== "string") return;
6468
+ const primary = defaults.imageGenerationModel.trim();
6469
+ defaults.imageGenerationModel = primary ? { primary, fallbacks: [] } : void 0;
6470
+ changes.push("Migrated agents.defaults.imageGenerationModel string to primary/fallbacks");
6471
+ }
6472
+ }, // JOHNNESS_PATCH_GPT_IMAGE_62
6446
6473
  {
6447
6474
  id: "agent.model-config-v2",
6448
6475
  describe: "Migrate legacy agent.model/allowedModels/modelAliases/modelFallbacks/imageModelFallbacks to agent.models + model lists",
@@ -10876,6 +10903,7 @@ function isValidNonNegativeByteSizeString(value) {
10876
10903
  const AgentDefaultsSchema = z.object({
10877
10904
  model: AgentModelSchema.optional(),
10878
10905
  imageModel: AgentModelSchema.optional(),
10906
+ imageGenerationModel: AgentModelSchema.optional(), // JOHNNESS_PATCH_GPT_IMAGE_62
10879
10907
  pdfModel: AgentModelSchema.optional(),
10880
10908
  pdfMaxBytesMb: z.number().positive().optional(),
10881
10909
  pdfMaxPages: z.number().int().positive().optional(),
@@ -1,3 +1,4 @@
1
+ // JOHNNESS_PATCH_UPDATE_COMMAND_62
1
2
  // JOHNNESS_PATCH_DEVICE_LOGIN_60
2
3
  // JOHNNESS_PATCH_DEVICE_LOGIN_60B
3
4
  import { l as escapeRegExp } from "./utils-DC4zYvW0.js";
@@ -181,6 +182,19 @@ function buildChatCommands() {
181
182
  captureRemaining: true
182
183
  }]
183
184
  }),
185
+ defineChatCommand({
186
+ key: "update",
187
+ nativeName: "update",
188
+ description: "Install the newest johns-harness and restart (owner only).",
189
+ textAlias: "/update",
190
+ category: "management",
191
+ args: [{
192
+ name: "version",
193
+ description: "Exact version to install (default latest)",
194
+ type: "string",
195
+ required: false
196
+ }]
197
+ }),
184
198
  defineChatCommand({
185
199
  key: "login",
186
200
  nativeName: "login",
@@ -1,3 +1,4 @@
1
+ // JOHNNESS_PATCH_UPDATE_COMMAND_62
1
2
  // JOHNNESS_PATCH_DEVICE_LOGIN_60
2
3
  // JOHNNESS_PATCH_DEVICE_LOGIN_60B
3
4
  import { D as isPlainObject, u as escapeRegExp } from "./logger-Bj0Xl6pn.js";
@@ -218,6 +219,19 @@ function buildChatCommands() {
218
219
  captureRemaining: true
219
220
  }]
220
221
  }),
222
+ defineChatCommand({
223
+ key: "update",
224
+ nativeName: "update",
225
+ description: "Install the newest johns-harness and restart (owner only).",
226
+ textAlias: "/update",
227
+ category: "management",
228
+ args: [{
229
+ name: "version",
230
+ description: "Exact version to install (default latest)",
231
+ type: "string",
232
+ required: false
233
+ }]
234
+ }),
221
235
  defineChatCommand({
222
236
  key: "login",
223
237
  nativeName: "login",
@@ -1,3 +1,4 @@
1
+ // JOHNNESS_PATCH_UPDATE_COMMAND_62
1
2
  // JOHNNESS_PATCH_DEVICE_LOGIN_60
2
3
  // JOHNNESS_PATCH_DEVICE_LOGIN_60B
3
4
  import { D as isPlainObject, u as escapeRegExp } from "./logger-BW8uLq6f.js";
@@ -218,6 +219,19 @@ function buildChatCommands() {
218
219
  captureRemaining: true
219
220
  }]
220
221
  }),
222
+ defineChatCommand({
223
+ key: "update",
224
+ nativeName: "update",
225
+ description: "Install the newest johns-harness and restart (owner only).",
226
+ textAlias: "/update",
227
+ category: "management",
228
+ args: [{
229
+ name: "version",
230
+ description: "Exact version to install (default latest)",
231
+ type: "string",
232
+ required: false
233
+ }]
234
+ }),
221
235
  defineChatCommand({
222
236
  key: "login",
223
237
  nativeName: "login",
@@ -1,3 +1,4 @@
1
+ // JOHNNESS_PATCH_UPDATE_COMMAND_62
1
2
  // JOHNNESS_PATCH_DEVICE_LOGIN_60
2
3
  // JOHNNESS_PATCH_DEVICE_LOGIN_60B
3
4
  import { Bi as DEFAULT_PROVIDER, Or as resolveConfiguredModelRef, zi as DEFAULT_MODEL } from "./auth-profiles-5CHn7vq1.js";
@@ -181,6 +182,19 @@ function buildChatCommands() {
181
182
  captureRemaining: true
182
183
  }]
183
184
  }),
185
+ defineChatCommand({
186
+ key: "update",
187
+ nativeName: "update",
188
+ description: "Install the newest johns-harness and restart (owner only).",
189
+ textAlias: "/update",
190
+ category: "management",
191
+ args: [{
192
+ name: "version",
193
+ description: "Exact version to install (default latest)",
194
+ type: "string",
195
+ required: false
196
+ }]
197
+ }),
184
198
  defineChatCommand({
185
199
  key: "login",
186
200
  nativeName: "login",
@@ -1,3 +1,5 @@
1
+ import { createImageGenerateTool } from "./image-generate.js";
2
+ import { handleUpdateCommand } from "./update-command.js";
1
3
  // JOHNNESS_PATCH_DEVICE_LOGIN_60
2
4
  // JOHNNESS_PATCH_DEVICE_LOGIN_60B
3
5
  import { handleDeviceLoginCommand } from "./codex-oauth-device.js";
@@ -11283,6 +11285,7 @@ const TRUSTED_TOOL_RESULT_MEDIA = new Set([
11283
11285
  "exec",
11284
11286
  "gateway",
11285
11287
  "image",
11288
+ "image_generate",
11286
11289
  "memory_get",
11287
11290
  "memory_search",
11288
11291
  "message",
@@ -15583,7 +15586,8 @@ function buildAgentSystemPrompt(params) {
15583
15586
  sessions_spawn: acpSpawnRuntimeEnabled ? "Spawn an isolated sub-agent or ACP coding session (runtime=\"acp\" requires `agentId` unless `acp.defaultAgent` is configured; ACP harness ids follow acp.allowedAgents, not agents_list)" : "Spawn an isolated sub-agent session",
15584
15587
  subagents: "List, steer, or kill sub-agent runs for this requester session",
15585
15588
  session_status: "Show a /status-equivalent status card (usage + time + Reasoning/Verbose/Elevated); use for model-use questions (📊 session_status); optional per-session model override",
15586
- image: "Analyze an image with the configured image model"
15589
+ image: "Analyze an image with the configured image model",
15590
+ image_generate: "Generate or edit images with GPT Image 2 (subscription first, API key second)"
15587
15591
  };
15588
15592
  const toolOrder = [
15589
15593
  "read",
@@ -15610,7 +15614,8 @@ function buildAgentSystemPrompt(params) {
15610
15614
  "sessions_send",
15611
15615
  "subagents",
15612
15616
  "session_status",
15613
- "image"
15617
+ "image",
15618
+ "image_generate"
15614
15619
  ];
15615
15620
  const canonicalToolNames = (params.toolNames ?? []).map((tool) => tool.trim()).filter(Boolean);
15616
15621
  const canonicalByNormalized = /* @__PURE__ */ new Map();
@@ -40979,6 +40984,8 @@ function resolveSessionEntryForHookSessionKey(sessionStore, sessionKey) {
40979
40984
  async function handleCommands(params) {
40980
40985
  const deviceLoginReply = await handleDeviceLoginCommand(params, shouldHandleTextCommands({ cfg: params.cfg, surface: params.command.surface, commandSource: params.ctx.CommandSource }), { routeReply, callGateway });
40981
40986
  if (deviceLoginReply) return deviceLoginReply;
40987
+ const updateReply = await handleUpdateCommand(params, shouldHandleTextCommands({ cfg: params.cfg, surface: params.command.surface, commandSource: params.ctx.CommandSource }), { routeReply, callGateway, writeRestartSentinel, extractDeliveryInfo, restart: () => scheduleGatewaySigusr1Restart({ reason: "/update" }) }); // JOHNNESS_PATCH_UPDATE_COMMAND_62
40988
+ if (updateReply) return updateReply;
40982
40989
  if (HANDLERS === null) HANDLERS = [
40983
40990
  handlePluginCommand,
40984
40991
  handleBashCommand,
@@ -55646,6 +55653,16 @@ function createJohnnessTools(options) {
55646
55653
  fsPolicy: options?.fsPolicy,
55647
55654
  modelHasVision: options?.modelHasVision
55648
55655
  }) : null;
55656
+ const imageGenerateTool = options?.agentDir?.trim() ? createImageGenerateTool({
55657
+ config: options?.config,
55658
+ agentDir: options.agentDir,
55659
+ workspaceDir,
55660
+ sandbox: options?.sandboxRoot && options?.sandboxFsBridge ? { root: options.sandboxRoot, bridge: options.sandboxFsBridge } : void 0
55661
+ }, {
55662
+ resolveApiKeyForProvider,
55663
+ loadWebMedia,
55664
+ localRoots: resolveMediaToolLocalRoots(workspaceDir, { workspaceOnly: options?.fsPolicy?.workspaceOnly === true })
55665
+ }) : null; // JOHNNESS_PATCH_GPT_IMAGE_62
55649
55666
  const pdfTool = options?.agentDir?.trim() ? createPdfTool({
55650
55667
  config: options?.config,
55651
55668
  agentDir: options.agentDir,
@@ -55765,6 +55782,7 @@ function createJohnnessTools(options) {
55765
55782
  ...webSearchTool ? [webSearchTool] : [],
55766
55783
  ...webFetchTool ? [webFetchTool] : [],
55767
55784
  ...imageTool ? [imageTool] : [],
55785
+ ...imageGenerateTool ? [imageGenerateTool] : [],
55768
55786
  ...pdfTool ? [pdfTool] : []
55769
55787
  ];
55770
55788
  const pluginTools = resolvePluginTools({
@@ -1808,6 +1808,22 @@ const DEFAULT_MODEL_ALIASES = {
1808
1808
  "gemini-flash": "google/gemini-3-flash-preview",
1809
1809
  "gemini-flash-lite": "google/gemini-3.1-flash-lite-preview"
1810
1810
  };
1811
+ // JOHNNESS_PATCH_NANO_BANANA_63: the shipped alias contract. Every alias resolves to the newest model
1812
+ // in its line that exists in the shipped catalog, so a fresh install or an /update moves
1813
+ // every agent forward with no per-instance config. This block carries only the aliases
1814
+ // family 63 moves or adds; the rest are already current in the literal above, and later
1815
+ // keys win. A user alias in agents.defaults.models[*].alias is consulted first and still
1816
+ // overrides. Adding a newer model to the catalog without moving its alias fails the
1817
+ // verifier (63.20, scripts/check-alias-contract.mjs).
1818
+ Object.assign(DEFAULT_MODEL_ALIASES, {
1819
+ gpt: "openai-codex/gpt-5.6-sol",
1820
+ flash: "google/gemini-3.8-flash",
1821
+ "gemini-flash": "google/gemini-3.8-flash",
1822
+ "gemini-flash-lite": "google/gemini-2.5-flash-lite",
1823
+ banana: "google/gemini-3-pro-image-preview",
1824
+ "nano-banana": "google/gemini-3-pro-image-preview",
1825
+ image: "openai-codex/gpt-image-2"
1826
+ });
1811
1827
  const DEFAULT_MODEL_COST = {
1812
1828
  input: 0,
1813
1829
  output: 0,
@@ -3132,6 +3148,17 @@ function applyLegacyAudioTranscriptionModel(params) {
3132
3148
  params.changes.push(params.alreadySetMessage);
3133
3149
  }
3134
3150
  const LEGACY_CONFIG_MIGRATIONS_PART_2 = [
3151
+ {
3152
+ id: "agent.image-generation-model-v1",
3153
+ describe: "Migrate agents.defaults.imageGenerationModel string to primary/fallbacks",
3154
+ apply: (raw, changes) => {
3155
+ const defaults = getRecord(getRecord(raw.agents)?.defaults);
3156
+ if (!defaults || typeof defaults.imageGenerationModel !== "string") return;
3157
+ const primary = defaults.imageGenerationModel.trim();
3158
+ defaults.imageGenerationModel = primary ? { primary, fallbacks: [] } : void 0;
3159
+ changes.push("Migrated agents.defaults.imageGenerationModel string to primary/fallbacks");
3160
+ }
3161
+ }, // JOHNNESS_PATCH_GPT_IMAGE_62
3135
3162
  {
3136
3163
  id: "agent.model-config-v2",
3137
3164
  describe: "Migrate legacy agent.model/allowedModels/modelAliases/modelFallbacks/imageModelFallbacks to agent.models + model lists",
@@ -6762,6 +6789,7 @@ function isValidNonNegativeByteSizeString(value) {
6762
6789
  const AgentDefaultsSchema = z.object({
6763
6790
  model: AgentModelSchema.optional(),
6764
6791
  imageModel: AgentModelSchema.optional(),
6792
+ imageGenerationModel: AgentModelSchema.optional(), // JOHNNESS_PATCH_GPT_IMAGE_62
6765
6793
  pdfModel: AgentModelSchema.optional(),
6766
6794
  pdfMaxBytesMb: z.number().positive().optional(),
6767
6795
  pdfMaxPages: z.number().int().positive().optional(),
@@ -4959,6 +4959,22 @@ const DEFAULT_MODEL_ALIASES = {
4959
4959
  "gemini-flash": "google/gemini-3-flash-preview",
4960
4960
  "gemini-flash-lite": "google/gemini-3.1-flash-lite-preview"
4961
4961
  };
4962
+ // JOHNNESS_PATCH_NANO_BANANA_63: the shipped alias contract. Every alias resolves to the newest model
4963
+ // in its line that exists in the shipped catalog, so a fresh install or an /update moves
4964
+ // every agent forward with no per-instance config. This block carries only the aliases
4965
+ // family 63 moves or adds; the rest are already current in the literal above, and later
4966
+ // keys win. A user alias in agents.defaults.models[*].alias is consulted first and still
4967
+ // overrides. Adding a newer model to the catalog without moving its alias fails the
4968
+ // verifier (63.20, scripts/check-alias-contract.mjs).
4969
+ Object.assign(DEFAULT_MODEL_ALIASES, {
4970
+ gpt: "openai-codex/gpt-5.6-sol",
4971
+ flash: "google/gemini-3.8-flash",
4972
+ "gemini-flash": "google/gemini-3.8-flash",
4973
+ "gemini-flash-lite": "google/gemini-2.5-flash-lite",
4974
+ banana: "google/gemini-3-pro-image-preview",
4975
+ "nano-banana": "google/gemini-3-pro-image-preview",
4976
+ image: "openai-codex/gpt-image-2"
4977
+ });
4962
4978
  const DEFAULT_MODEL_COST = {
4963
4979
  input: 0,
4964
4980
  output: 0,
@@ -6112,6 +6128,17 @@ function applyLegacyAudioTranscriptionModel(params) {
6112
6128
  params.changes.push(params.alreadySetMessage);
6113
6129
  }
6114
6130
  const LEGACY_CONFIG_MIGRATIONS_PART_2 = [
6131
+ {
6132
+ id: "agent.image-generation-model-v1",
6133
+ describe: "Migrate agents.defaults.imageGenerationModel string to primary/fallbacks",
6134
+ apply: (raw, changes) => {
6135
+ const defaults = getRecord(getRecord(raw.agents)?.defaults);
6136
+ if (!defaults || typeof defaults.imageGenerationModel !== "string") return;
6137
+ const primary = defaults.imageGenerationModel.trim();
6138
+ defaults.imageGenerationModel = primary ? { primary, fallbacks: [] } : void 0;
6139
+ changes.push("Migrated agents.defaults.imageGenerationModel string to primary/fallbacks");
6140
+ }
6141
+ }, // JOHNNESS_PATCH_GPT_IMAGE_62
6115
6142
  {
6116
6143
  id: "agent.model-config-v2",
6117
6144
  describe: "Migrate legacy agent.model/allowedModels/modelAliases/modelFallbacks/imageModelFallbacks to agent.models + model lists",
@@ -9694,6 +9721,7 @@ function isValidNonNegativeByteSizeString(value) {
9694
9721
  const AgentDefaultsSchema = z.object({
9695
9722
  model: AgentModelSchema.optional(),
9696
9723
  imageModel: AgentModelSchema.optional(),
9724
+ imageGenerationModel: AgentModelSchema.optional(), // JOHNNESS_PATCH_GPT_IMAGE_62
9697
9725
  pdfModel: AgentModelSchema.optional(),
9698
9726
  pdfMaxBytesMb: z.number().positive().optional(),
9699
9727
  pdfMaxPages: z.number().int().positive().optional(),