johns-harness 2026.9.30 → 2026.9.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/dist/auth-profiles-5CHn7vq1.js +85 -0
  3. package/dist/channels/plugins/actions/telegram.js +1 -0
  4. package/dist/compact-B247y5Qt.js +7 -1
  5. package/dist/config-koj5M_EO.js +84 -0
  6. package/dist/daemon-cli.js +84 -0
  7. package/dist/gateway-cli-B7fBU7gD.js +81 -1
  8. package/dist/gateway-cli-BCNMwzNq.js +81 -1
  9. package/dist/model-selection-BGlGpPgM.js +85 -1
  10. package/dist/model-selection-BU6wl1le.js +85 -1
  11. package/dist/model-selection-L7RMwsG-.js +86 -1
  12. package/dist/models-config-CAmcg63r.js +1 -1
  13. package/dist/models-config-DXA5BkaM.js +1 -1
  14. package/dist/pi-embedded-C6ITuRXf.js +7 -1
  15. package/dist/pi-embedded-DoQsYfIY.js +7 -1
  16. package/dist/plugin-sdk/bluebubbles.js +1 -0
  17. package/dist/plugin-sdk/config-4Fgm-yEH.js +84 -0
  18. package/dist/plugin-sdk/config-Di-lyCdh.js +84 -0
  19. package/dist/plugin-sdk/discord.js +84 -0
  20. package/dist/plugin-sdk/dispatch-BP0viZiL.js +7 -1
  21. package/dist/plugin-sdk/dispatch-CQsjmw7g.js +7 -1
  22. package/dist/plugin-sdk/dispatch-Cerq29sy.js +7 -1
  23. package/dist/plugin-sdk/dispatch-Cndjtt0g.js +7 -1
  24. package/dist/plugin-sdk/dispatch-UogiJYul.js +7 -1
  25. package/dist/plugin-sdk/feishu.js +1 -0
  26. package/dist/plugin-sdk/googlechat.js +1 -0
  27. package/dist/plugin-sdk/imessage.js +1 -0
  28. package/dist/plugin-sdk/mattermost.js +1 -0
  29. package/dist/plugin-sdk/model-auth-CX9cPHdC.js +84 -0
  30. package/dist/plugin-sdk/model-auth-Ciehz0x5.js +84 -0
  31. package/dist/plugin-sdk/model-auth-DVyo6JSX.js +84 -0
  32. package/dist/plugin-sdk/model-auth-kiHYHGrs.js +84 -0
  33. package/dist/plugin-sdk/model-selection-Dbj4jDYM.js +84 -0
  34. package/dist/plugin-sdk/reply-DbZnH8-h.js +7 -1
  35. package/dist/plugin-sdk/signal.js +1 -0
  36. package/dist/plugin-sdk/slack.js +1 -0
  37. package/dist/plugin-sdk/telegram.js +1 -0
  38. package/dist/plugin-sdk/thinking-B-rIrY_n.js +1 -0
  39. package/dist/plugin-sdk/thinking-CopbP_Cn.js +1 -0
  40. package/dist/plugin-sdk/thinking-WQ4Zq8Nl.js +1 -0
  41. package/dist/plugin-sdk/thinking-WXqkGljb.js +1 -0
  42. package/dist/plugin-sdk/thinking-_uXqFaFO.js +1 -0
  43. package/dist/plugin-sdk/thinking-pTdxk2dr.js +1 -0
  44. package/dist/plugin-sdk/tlon.js +1 -0
  45. package/dist/plugin-sdk/twitch.js +1 -0
  46. package/dist/plugin-sdk/zalo.js +1 -0
  47. package/dist/plugin-sdk/zalouser.js +1 -0
  48. package/dist/reply-C5LKjXcC.js +7 -1
  49. package/dist/thinking-B5B36ffe.js +1 -0
  50. package/dist/thinking-BYwvlJ3S.js +1 -0
  51. package/dist/thinking-CAzdgmNV.js +1 -0
  52. package/dist/thinking-DykY2Fzj.js +1 -0
  53. package/docs/PATCHES.md +16 -0
  54. package/docs/PROVIDER-AUDIT-2026-09-20.md +21 -4
  55. package/node_modules/@mariozechner/pi-ai/dist/models.generated.js +22 -0
  56. package/node_modules/@mariozechner/pi-ai/dist/models.js +1 -0
  57. package/node_modules/@mariozechner/pi-ai/dist/providers/anthropic.js +2 -2
  58. package/package.json +1 -1
  59. package/vendor/patched-deps/@mariozechner/pi-ai/dist/models.generated.js +22 -0
  60. package/vendor/patched-deps/@mariozechner/pi-ai/dist/models.js +1 -0
  61. package/vendor/patched-deps/@mariozechner/pi-ai/dist/providers/anthropic.js +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.9.32
4
+
5
+ - Family 79: recurring cron jobs retry transient errors (rate limit, overloaded, network, timeout, server error) at min(next natural slot, now + `cron.retry` backoff) up to `cron.retry.maxAttempts`, then return to the normal schedule, instead of burning the slot. Isolated cron runs always carry the default fallback chain, so a throttled job model falls through even when its provider differs from the default primary.
6
+ - New `cron.failureLog`: a file path that receives one line per cron failure, retries included: `- timestamp | cron-fail | job | error class | attempt n | next retry <time|none>`. Failures go to the file; Telegram stays reserved for each job's own announce.
7
+ - The `cron` tool waits 180s for the gateway (was 60s), and a cron gateway timeout no longer adds the automatic "⚠️ ⏰ Cron failed" line to the chat reply (the call usually completed server-side).
8
+ - Bare model shorthands always resolve to the newest shipped model. A user alias that pins an older model of the same line (for example `opus` on `claude-opus-5` after `opus` moved to `claude-opus-5-5`) yields to the shipped default, and the old model keeps a versioned alias (`opus5`, `sonnet46`, `gpt55`). Applied on every config load and written back to the config file once at gateway start, with a log line naming each move. Custom aliases and deliberate cross-provider pins are untouched.
9
+ - Exact replay `scripts/patch-cron-reliability.py` (37 files). Roll back with `npm i -g johns-harness@2026.9.31`.
10
+
11
+ ## 2026.9.31
12
+
13
+ - Family 78: Claude Opus 5.5 (`anthropic/claude-opus-5-5`) is a first-class model. Id, $4/$20 pricing, $0.20 cache reads, 1M native context (300K owner cap), 128K max output and always-on adaptive thinking confirmed against the Anthropic model docs on 2026-09-23. Effort maps 1:1 (xhigh and max are distinct), temperature is dropped, and `opus` now resolves to Opus 5.5 on every install; `opus5` keeps Claude Opus 5 reachable and the opus-5 catalog entry is unchanged. Exact replay via `scripts/patch-opus-5-5-model.py`; verifier checks 78.1-78.3. Roll back with `npm i -g johns-harness@2026.9.30`.
14
+
3
15
  ## 2026.9.30
4
16
 
5
17
  - Family 77: weekly official six-provider audit. DeepSeek V4 Flash/Pro now transmit native low for minimal/low instead of silently using high. Meta Standard Muse 1.2/1.3 preserve provider-default effort when the caller omits it instead of forcing minimal. Gemini 3.8 Flash and 3.5 Flash-Lite omit deprecated/ignored sampling temperature. Preserve all aliases, saved model IDs, owner context caps and default selections.
@@ -40,7 +52,7 @@
40
52
  - New packed-artifact gate `scripts/check-packed-artifact.mjs` inspects the real `npm pack` output (and registry tarballs) and fails closed unless all ten plugin-loader copies carry the Family 68 option. Releases 2026.9.22 and 2026.9.23 were cut from the pre-68 base, so their tarballs lacked it while main carried it; this gate makes that impossible to repeat.
41
53
  - Roll back with `npm i -g johns-harness@2026.9.23`.
42
54
 
43
- ## 2026.9.23
55
+ ## 2026.9.31
44
56
 
45
57
  - Family 70: ship the `orchestrator` skill as a bundled, always-eligible skill so every harness install loads the delegation and briefing protocol implicitly (pass the user's words through when they are clear, expand when they are vague, keep briefings brief). Workspace overrides, `enabled: false` and `allowBundled` still apply. `RULES.md` points at the bundled copy.
46
58
 
@@ -5134,6 +5134,11 @@ Object.assign(DEFAULT_MODEL_ALIASES, {
5134
5134
  "nano-banana": "google/gemini-3-pro-image-preview",
5135
5135
  image: "openai-codex/gpt-image-2"
5136
5136
  });
5137
+ // JOHNNESS_PATCH_OPUS_55_MODEL: Claude Opus 5.5 is the newest Opus; `opus` moves to it, opus-5 stays in the catalog.
5138
+ Object.assign(DEFAULT_MODEL_ALIASES, {
5139
+ opus: "anthropic/claude-opus-5-5",
5140
+ opus5: "anthropic/claude-opus-5"
5141
+ });
5137
5142
  const DEFAULT_MODEL_COST = {
5138
5143
  input: 0,
5139
5144
  output: 0,
@@ -5240,6 +5245,83 @@ function applyTalkApiKey(config) {
5240
5245
  function applyTalkConfigNormalization(config) {
5241
5246
  return normalizeTalkConfig(config);
5242
5247
  }
5248
+ /* JOHNNESS_PATCH_ALIAS_LATEST_79: bare shorthands (DEFAULT_MODEL_ALIASES keys without digits) always resolve
5249
+ * to the newest shipped model in their line. A user alias that claims one but pins an older model of the same
5250
+ * provider and line is moved: the shipped model takes the shorthand and the old model keeps a versioned alias
5251
+ * (opus5, sonnet46, gpt55). Custom aliases, newer pins and cross-provider or cross-line choices are untouched.
5252
+ * Replaces entries of the given models map in place (never mutates an entry); returns the moves. */
5253
+ function johnnessMigrateStaleShorthands79(models) {
5254
+ const moves = [];
5255
+ if (!models || typeof models !== "object" || Array.isArray(models)) return moves;
5256
+ const refOf = (raw) => {
5257
+ const text = String(raw ?? "").trim().toLowerCase();
5258
+ const slash = text.indexOf("/");
5259
+ return slash > 0 ? {
5260
+ provider: text.slice(0, slash),
5261
+ model: text.slice(slash + 1)
5262
+ } : null;
5263
+ };
5264
+ const versionOf = (model) => {
5265
+ const parts = [];
5266
+ for (const raw of model.match(/\d+/g) ?? []) {
5267
+ if (raw.length >= 4) break;
5268
+ parts.push(Number(raw));
5269
+ }
5270
+ return parts;
5271
+ };
5272
+ const isOlder = (alias, user, shipped) => {
5273
+ if (user.provider !== shipped.provider || user.model === shipped.model) return false;
5274
+ const tokens = new Set(user.model.split(/[^a-z0-9]+/).filter(Boolean));
5275
+ if (!alias.split("-").every((token) => tokens.has(token))) return false;
5276
+ const a = versionOf(user.model);
5277
+ const b = versionOf(shipped.model);
5278
+ if (a.length === 0 || b.length === 0) return false;
5279
+ for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
5280
+ const x = a[i] ?? 0;
5281
+ const y = b[i] ?? 0;
5282
+ if (x !== y) return x < y;
5283
+ }
5284
+ return false;
5285
+ };
5286
+ const versionedAliasFor = (alias, userKey, user) => {
5287
+ for (const [name, target] of Object.entries(DEFAULT_MODEL_ALIASES)) if (name !== alias && /\d/.test(name) && name.startsWith(alias) && String(target).toLowerCase() === userKey) return name;
5288
+ const digits = versionOf(user.model).join("");
5289
+ return digits ? `${alias}${digits}` : void 0;
5290
+ };
5291
+ for (const key of Object.keys(models)) {
5292
+ const entry = models[key];
5293
+ const alias = String(entry?.alias ?? "").trim().toLowerCase();
5294
+ if (!alias || /\d/.test(alias) || !Object.hasOwn(DEFAULT_MODEL_ALIASES, alias)) continue;
5295
+ const user = refOf(key);
5296
+ const shipped = refOf(DEFAULT_MODEL_ALIASES[alias]);
5297
+ if (!user || !shipped || !isOlder(alias, user, shipped)) continue;
5298
+ const userKey = `${user.provider}/${user.model}`;
5299
+ const taken = new Set(Object.entries(models).filter(([other]) => other !== key).map(([, value]) => String(value?.alias ?? "").trim().toLowerCase()).filter(Boolean));
5300
+ let versioned = versionedAliasFor(alias, userKey, user);
5301
+ if (versioned && (taken.has(versioned) || Object.hasOwn(DEFAULT_MODEL_ALIASES, versioned) && String(DEFAULT_MODEL_ALIASES[versioned]).toLowerCase() !== userKey)) versioned = void 0;
5302
+ const { alias: _previousAlias, ...rest } = entry;
5303
+ models[key] = versioned ? {
5304
+ ...rest,
5305
+ alias: versioned
5306
+ } : rest;
5307
+ const shippedKey = Object.keys(models).find((candidate) => {
5308
+ const ref = refOf(candidate);
5309
+ return ref !== null && ref.provider === shipped.provider && ref.model === shipped.model;
5310
+ });
5311
+ if (!shippedKey) models[DEFAULT_MODEL_ALIASES[alias]] = { alias };
5312
+ else if (!String(models[shippedKey]?.alias ?? "").trim()) models[shippedKey] = {
5313
+ ...models[shippedKey],
5314
+ alias
5315
+ };
5316
+ moves.push({
5317
+ alias,
5318
+ from: key,
5319
+ to: shippedKey ?? DEFAULT_MODEL_ALIASES[alias],
5320
+ versionedAlias: versioned
5321
+ });
5322
+ }
5323
+ return moves;
5324
+ }
5243
5325
  function applyModelDefaults(cfg) {
5244
5326
  let mutated = false;
5245
5327
  let nextCfg = cfg;
@@ -5311,6 +5393,7 @@ function applyModelDefaults(cfg) {
5311
5393
  const existingModels = existingAgent.models ?? {};
5312
5394
  if (Object.keys(existingModels).length === 0) return mutated ? nextCfg : cfg;
5313
5395
  const nextModels = { ...existingModels };
5396
+ if (johnnessMigrateStaleShorthands79(nextModels).length > 0) mutated = true; // JOHNNESS_PATCH_ALIAS_LATEST_79
5314
5397
  for (const [alias, target] of Object.entries(DEFAULT_MODEL_ALIASES)) {
5315
5398
  const entry = nextModels[target];
5316
5399
  if (!entry) continue;
@@ -13284,6 +13367,7 @@ const JohnnessSchema = z.object({
13284
13367
  cron: z.object({
13285
13368
  enabled: z.boolean().optional(),
13286
13369
  store: z.string().optional(),
13370
+ failureLog: z.string().optional(), // JOHNNESS_PATCH_CRON_RELIABILITY_79: file sink, one line per cron failure
13287
13371
  maxConcurrentRuns: z.number().int().positive().optional(),
13288
13372
  retry: z.object({
13289
13373
  maxAttempts: z.number().int().min(0).max(10).optional(),
@@ -16970,3 +17054,4 @@ var auth_profiles_exports = /* @__PURE__ */ __exportAll({
16970
17054
  });
16971
17055
  //#endregion
16972
17056
  export { normalizeTelegramCommandName as $, upsertAuthProfile as $n, retryAsync as $r, analyzeArgvCommand as $t, formatPermissionDetail as A, CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF as Ai, isSafeExecutableValue as An, resolveHooksGmailModel as Ar, AVATAR_MAX_BYTES as At, getRuntimeConfigSnapshot as B, DEFAULT_PROVIDER as Bi, collectConfigServiceEnvVars as Bn, buildKimiCodingProvider as Br, getConfigOverrides as Bt, isNonEmptyString as C, isNonSecretApiKeyMarker as Ci, splitShellArgs as Cn, normalizeModelSelection as Cr, isLegacyIpv4Literal as Ct, toDotPath as D, buildHuggingfaceModelDefinition as Di, applyMergePatch as Dn, resolveAllowlistModelKey as Dr, normalizeIpAddress as Dt, parseEnvValue as E, HUGGINGFACE_MODEL_CATALOG as Ei, resolveSafeBinProfiles as En, resolveAllowedModelRef as Er, isRfc1918Ipv4Address as Et, formatIcaclsResetCommand as F, KILOCODE_BASE_URL as Fi, resolveSlackNativeStreaming as Fn, resolveThinkingDefault as Fr, isSupportedLocalAvatarExtension as Ft, readConfigFileSnapshot as G, sanitizeTerminalText as Gn, resolveImplicitBedrockProvider as Gr, parseConfigPath as Gt, loadConfig as H, CLAUDE_CLI_PROFILE_ID as Hi, buildTalkConfigResponse as Hn, buildXiaomiProvider as Hr, setConfigOverride as Ht, config_exports as I, KILOCODE_DEFAULT_MODEL_REF as Ii, resolveSlackStreamingMode as In, QIANFAN_BASE_URL as Ir, isWorkspaceRelativeAvatarPath as It, setRuntimeConfigSnapshot as J, repairOAuthProfileIdMismatch as Jn, VENICE_BASE_URL as Jr, getTrustedSafeBinDirs as Jt, readConfigFileSnapshotForWrite as K, loadDotEnv as Kn, resolveImplicitCopilotProvider as Kr, setConfigValueAtPath as Kt, migrateLegacyConfig as L, splitTrailingAuthProfile as Li, resolveTelegramPreviewStreamMode as Ln, QIANFAN_DEFAULT_MODEL_ID as Lr, looksLikeAvatarPath as Lt, inspectPathPermissions as M, resolveCloudflareAiGatewayBaseUrl as Mi, formatSlackStreamingBooleanMigrationMessage as Mn, resolveReasoningDefault as Mr, isAvatarHttpUrl as Mt, safeStat as N, normalizeOptionalSecretInput as Ni, mapStreamingModeToSlackLegacyDraftStreamMode as Nn, resolveSubagentConfiguredModelSelection as Nr, isAvatarImageDataUrl as Nt, writeTextFileAtomic as O, discoverHuggingfaceModels as Oi, applyLegacyMigrations as On, resolveConfiguredModelRef as Or, parseCanonicalIpAddress as Ot, createIcaclsResetCommand as P, normalizeSecretInput as Pi, resolveDiscordPreviewStreamMode as Pn, resolveSubagentSpawnModelSelection as Pr, isPathWithinRoot as Pt, TELEGRAM_COMMAND_NAME_PATTERN as Q, setAuthProfileOrder as Qn, resolveRetryConfig as Qr, validateSafeBinArgv as Qt, clearRuntimeConfigSnapshot as R, DEFAULT_CONTEXT_TOKENS as Ri, INCLUDE_KEY as Rn, XIAOMI_DEFAULT_MODEL_ID as Rr, resolveAvatarMime as Rt, describeUnknownError as S, shouldEnableShellEnvFallback as Si, resolveInlineCommandMatch as Sn, normalizeModelRef as Sr, isIpv6Address as St, parseDotPath as T, HUGGINGFACE_BASE_URL as Ti, normalizeSafeBinProfileFixtures as Tn, parseModelRef as Tr, isPrivateOrLoopbackIpAddress as Tt, parseConfigJson5 as U, CODEX_CLI_PROFILE_ID as Ui, resolveAgentMaxConcurrent as Un, normalizeGoogleModelId as Ur, unsetConfigOverride as Ut, getRuntimeConfigSourceSnapshot as V, resolveAuthProfileDisplayLabel as Vi, containsEnvVarReference as Vn, buildQianfanProvider as Vr, resetConfigOverrides as Vt, readBestEffortConfig as W, resolveSubagentMaxConcurrent as Wn, normalizeProviders as Wr, getConfigValueAtPath as Wt, validateConfigObjectWithPlugins as X, listProfilesForProvider as Xn, VENICE_MODEL_CATALOG as Xr, listWritableExplicitTrustedSafeBinDirs as Xt, writeConfigFile as Y, dedupeProfileIds as Yn, VENICE_DEFAULT_MODEL_REF as Yr, isTrustedSafeBinPath as Yt, JohnnessSchema as Z, markAuthProfileGood as Zn, buildVeniceModelDefinition as Zr, normalizeTrustedSafeBinDirs as Zt, parseOAuthCallbackInput as _, resolveEnvApiKey as _i, unwrapDispatchWrappersForResolution as _n, findNormalizedProviderValue as _r, isBlockedSpecialUseIpv6Address as _t, getSoonestCooldownExpiry as a, SYNTHETIC_MODEL_CATALOG as ai, DEFAULT_SAFE_BINS as an, replaceRuntimeAuthProfileStoreSnapshots as ar, resolveIMessageRemoteAttachmentRoots as at, resolveSecretRefValue as b, getShellPathFromLoginShell as bi, POSIX_INLINE_COMMAND_FLAGS as bn, isCliProvider as br, isIpInCidr as bt, markAuthProfileUsed as c, buildAssistantMessage$1 as ci, resolveCommandResolutionFromArgv as cn, resolveAuthStorePathForDisplay as cr, SecretProviderSchema as ct, resolveApiKeyForProfile as d, buildUsageWithNoCost as di, extractShellWrapperCommand as dn, resolveProcessScopedMap as dr, isDangerousNetworkMode as dt, TOGETHER_BASE_URL as ei, analyzeShellCommand as en, upsertAuthProfileWithLock as er, resolveTelegramCustomCommands as et, evaluateStoredCredentialEligibility as f, getApiKeyForModel as fi, extractShellWrapperInlineCommand as fn, getProcessStartTime as fr, normalizeNetworkMode as ft, generateChutesPkce as g, resolveAwsSdkEnvVarName as gi, normalizeExecutableToken as gn, buildModelAliasIndex as gr, isBlockedSpecialUseIpv4Address as gt, exchangeChutesCodeForTokens as h, resolveApiKeyForProvider as hi, isShellWrapperExecutable as hn, buildConfiguredAllowlistKeys as hr, extractEmbeddedIpv4FromIpv6 as ht, clearExpiredCooldowns as i, SYNTHETIC_DEFAULT_MODEL_REF as ii, splitCommandChain as in, loadAuthProfileStoreForSecretsRuntime as ir, resolveIMessageAttachmentRoots as it, formatPermissionRemediation as j, buildCloudflareAiGatewayModelDefinition as ji, formatSlackStreamModeMigrationMessage as jn, resolveModelRefFromString as jr, isAvatarDataUrl as jt, encodeJsonPointerToken as k, isHuggingfacePolicyLocked as ki, ensureControlUiAllowedOriginsForNonLoopbackBind as kn, resolveDefaultModelForAgent as kr, parseLooseIpAddress as kt, resolveProfileUnusableUntilForDisplay as l, buildAssistantMessageWithZeroUsage as li, resolveExecutableFromPathEnv as ln, resolveJohnnessAgentDir as lr, sensitive as lt, CHUTES_AUTHORIZE_ENDPOINT as m, requireApiKey as mi, isDispatchWrapperExecutable as mn, buildAllowedModelSet as mr, parseByteSize as mt, resolveAuthProfileEligibility as n, buildTogetherModelDefinition as ni, isWindowsPlatform as nn, ensureAuthProfileStore as nr, isInboundPathAllowed as nt, isProfileInCooldown as o, buildSyntheticModelDefinition as oi, matchAllowlist as on, updateAuthProfileStoreWithLock as or, normalizeScpRemoteHost as ot, resolveTokenExpiryState as p, getCustomProviderApiKey as pi, hasEnvManipulationBeforeShellWrapper as pn, isPidAlive as pr, parseDurationMs as pt, resolveConfigSnapshotHash as q, resolveOwnerDisplaySetting as qn, resolveImplicitProviders as qr, unsetConfigValueAtPath as qt, resolveAuthProfileOrder as r, SYNTHETIC_BASE_URL as ri, resolvePlannedSegmentArgv as rn, loadAuthProfileStore as rr, mergeInboundPathRoots as rt, markAuthProfileFailure as s, createConfiguredOllamaStreamFn as si, resolveAllowlistCandidatePath as sn, resolveAuthStorePath as sr, parseNonNegativeByteSize as st, auth_profiles_exports as t, TOGETHER_MODEL_CATALOG as ti, buildEnforcedShellCommand as tn, clearRuntimeAuthProfileStoreSnapshots as tr, DEFAULT_IMESSAGE_ATTACHMENT_ROOTS as tt, resolveProfilesUnavailableReason as u, buildStreamErrorAssistantMessage as ui, POSIX_SHELL_WRAPPERS as un, withFileLock as ur, getBlockedNetworkModeReason as ut, isProviderScopedSecretResolutionError as v, resolveModelAuthMode as vi, unwrapKnownDispatchWrapperInvocation as vn, getModelRefStatus as vr, isCanonicalDottedDecimalIPv4 as vt, isRecord as w, isSecretRefHeaderValueMarker as wi, SAFE_BIN_PROFILES as wn, normalizeProviderId as wr, isLoopbackIpAddress as wt, resolveSecretRefValues as x, resolveShellEnvFallbackTimeoutMs as xi, POWERSHELL_INLINE_COMMAND_FLAGS as xn, modelKey as xr, isIpv4Address as xt, resolveSecretRefString as y, getShellEnvAppliedKeys as yi, unwrapKnownShellMultiplexerInvocation as yn, inferUniqueProviderFromConfiguredModels as yr, isCarrierGradeNatIpv4Address as yt, createConfigIO as z, DEFAULT_MODEL as zi, applyConfigEnvVars as zn, buildKilocodeProvider as zr, validateJsonSchemaValue as zt };
17057
+ export { johnnessMigrateStaleShorthands79 }; // JOHNNESS_PATCH_ALIAS_LATEST_79
@@ -2110,6 +2110,7 @@ const XHIGH_MODEL_REFS = [
2110
2110
  "anthropic/claude-fable-5-1",
2111
2111
  "meta/muse-spark-1.3", // JOHNNESS_PATCH_MUSE_SPARK
2112
2112
  "meta/muse-spark-1.2",
2113
+ "anthropic/claude-opus-5-5", // JOHNNESS_PATCH_OPUS_55_MODEL
2113
2114
  "xai/grok-4.6",
2114
2115
  "openai/gpt-6-astra",
2115
2116
  "openai-codex/gpt-6-astra",
@@ -18084,6 +18084,12 @@ function resolveToolErrorWarningPolicy(params) {
18084
18084
  showWarning: false,
18085
18085
  includeDetails
18086
18086
  };
18087
+ // JOHNNESS_PATCH_CRON_RELIABILITY_79: a cron.* gateway timeout usually completed server-side; the model still
18088
+ // sees the tool error, the chat reply gets no automatic warning.
18089
+ if (normalizedToolName === "cron" && /gateway (?:request )?timeout after \d+\s*ms/i.test(String(params.lastToolError.error ?? ""))) return {
18090
+ showWarning: false,
18091
+ includeDetails
18092
+ };
18087
18093
  if (params.lastToolError.mutatingAction ?? isLikelyMutatingToolName(params.lastToolError.toolName)) return {
18088
18094
  showWarning: true,
18089
18095
  includeDetails
@@ -46356,7 +46362,7 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Large l
46356
46362
  const action = readStringParam(params, "action", { required: true });
46357
46363
  const gatewayOpts = {
46358
46364
  ...readGatewayCallOptions(params),
46359
- timeoutMs: typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) ? params.timeoutMs : 6e4
46365
+ timeoutMs: typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) ? params.timeoutMs : 18e4 /* JOHNNESS_PATCH_CRON_RELIABILITY_79: cron.* calls queue behind busy lanes; 180s */
46360
46366
  };
46361
46367
  switch (action) {
46362
46368
  case "status": return jsonResult(await callGateway("cron.status", gatewayOpts, {}));
@@ -1829,6 +1829,11 @@ Object.assign(DEFAULT_MODEL_ALIASES, {
1829
1829
  "nano-banana": "google/gemini-3-pro-image-preview",
1830
1830
  image: "openai-codex/gpt-image-2"
1831
1831
  });
1832
+ // JOHNNESS_PATCH_OPUS_55_MODEL: Claude Opus 5.5 is the newest Opus; `opus` moves to it, opus-5 stays in the catalog.
1833
+ Object.assign(DEFAULT_MODEL_ALIASES, {
1834
+ opus: "anthropic/claude-opus-5-5",
1835
+ opus5: "anthropic/claude-opus-5"
1836
+ });
1832
1837
  const DEFAULT_MODEL_COST = {
1833
1838
  input: 0,
1834
1839
  output: 0,
@@ -1935,6 +1940,83 @@ function applyTalkApiKey(config) {
1935
1940
  function applyTalkConfigNormalization(config) {
1936
1941
  return normalizeTalkConfig(config);
1937
1942
  }
1943
+ /* JOHNNESS_PATCH_ALIAS_LATEST_79: bare shorthands (DEFAULT_MODEL_ALIASES keys without digits) always resolve
1944
+ * to the newest shipped model in their line. A user alias that claims one but pins an older model of the same
1945
+ * provider and line is moved: the shipped model takes the shorthand and the old model keeps a versioned alias
1946
+ * (opus5, sonnet46, gpt55). Custom aliases, newer pins and cross-provider or cross-line choices are untouched.
1947
+ * Replaces entries of the given models map in place (never mutates an entry); returns the moves. */
1948
+ function johnnessMigrateStaleShorthands79(models) {
1949
+ const moves = [];
1950
+ if (!models || typeof models !== "object" || Array.isArray(models)) return moves;
1951
+ const refOf = (raw) => {
1952
+ const text = String(raw ?? "").trim().toLowerCase();
1953
+ const slash = text.indexOf("/");
1954
+ return slash > 0 ? {
1955
+ provider: text.slice(0, slash),
1956
+ model: text.slice(slash + 1)
1957
+ } : null;
1958
+ };
1959
+ const versionOf = (model) => {
1960
+ const parts = [];
1961
+ for (const raw of model.match(/\d+/g) ?? []) {
1962
+ if (raw.length >= 4) break;
1963
+ parts.push(Number(raw));
1964
+ }
1965
+ return parts;
1966
+ };
1967
+ const isOlder = (alias, user, shipped) => {
1968
+ if (user.provider !== shipped.provider || user.model === shipped.model) return false;
1969
+ const tokens = new Set(user.model.split(/[^a-z0-9]+/).filter(Boolean));
1970
+ if (!alias.split("-").every((token) => tokens.has(token))) return false;
1971
+ const a = versionOf(user.model);
1972
+ const b = versionOf(shipped.model);
1973
+ if (a.length === 0 || b.length === 0) return false;
1974
+ for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
1975
+ const x = a[i] ?? 0;
1976
+ const y = b[i] ?? 0;
1977
+ if (x !== y) return x < y;
1978
+ }
1979
+ return false;
1980
+ };
1981
+ const versionedAliasFor = (alias, userKey, user) => {
1982
+ for (const [name, target] of Object.entries(DEFAULT_MODEL_ALIASES)) if (name !== alias && /\d/.test(name) && name.startsWith(alias) && String(target).toLowerCase() === userKey) return name;
1983
+ const digits = versionOf(user.model).join("");
1984
+ return digits ? `${alias}${digits}` : void 0;
1985
+ };
1986
+ for (const key of Object.keys(models)) {
1987
+ const entry = models[key];
1988
+ const alias = String(entry?.alias ?? "").trim().toLowerCase();
1989
+ if (!alias || /\d/.test(alias) || !Object.hasOwn(DEFAULT_MODEL_ALIASES, alias)) continue;
1990
+ const user = refOf(key);
1991
+ const shipped = refOf(DEFAULT_MODEL_ALIASES[alias]);
1992
+ if (!user || !shipped || !isOlder(alias, user, shipped)) continue;
1993
+ const userKey = `${user.provider}/${user.model}`;
1994
+ const taken = new Set(Object.entries(models).filter(([other]) => other !== key).map(([, value]) => String(value?.alias ?? "").trim().toLowerCase()).filter(Boolean));
1995
+ let versioned = versionedAliasFor(alias, userKey, user);
1996
+ if (versioned && (taken.has(versioned) || Object.hasOwn(DEFAULT_MODEL_ALIASES, versioned) && String(DEFAULT_MODEL_ALIASES[versioned]).toLowerCase() !== userKey)) versioned = void 0;
1997
+ const { alias: _previousAlias, ...rest } = entry;
1998
+ models[key] = versioned ? {
1999
+ ...rest,
2000
+ alias: versioned
2001
+ } : rest;
2002
+ const shippedKey = Object.keys(models).find((candidate) => {
2003
+ const ref = refOf(candidate);
2004
+ return ref !== null && ref.provider === shipped.provider && ref.model === shipped.model;
2005
+ });
2006
+ if (!shippedKey) models[DEFAULT_MODEL_ALIASES[alias]] = { alias };
2007
+ else if (!String(models[shippedKey]?.alias ?? "").trim()) models[shippedKey] = {
2008
+ ...models[shippedKey],
2009
+ alias
2010
+ };
2011
+ moves.push({
2012
+ alias,
2013
+ from: key,
2014
+ to: shippedKey ?? DEFAULT_MODEL_ALIASES[alias],
2015
+ versionedAlias: versioned
2016
+ });
2017
+ }
2018
+ return moves;
2019
+ }
1938
2020
  function applyModelDefaults(cfg) {
1939
2021
  let mutated = false;
1940
2022
  let nextCfg = cfg;
@@ -2006,6 +2088,7 @@ function applyModelDefaults(cfg) {
2006
2088
  const existingModels = existingAgent.models ?? {};
2007
2089
  if (Object.keys(existingModels).length === 0) return mutated ? nextCfg : cfg;
2008
2090
  const nextModels = { ...existingModels };
2091
+ if (johnnessMigrateStaleShorthands79(nextModels).length > 0) mutated = true; // JOHNNESS_PATCH_ALIAS_LATEST_79
2009
2092
  for (const [alias, target] of Object.entries(DEFAULT_MODEL_ALIASES)) {
2010
2093
  const entry = nextModels[target];
2011
2094
  if (!entry) continue;
@@ -9133,6 +9216,7 @@ const JohnnessSchema = z.object({
9133
9216
  cron: z.object({
9134
9217
  enabled: z.boolean().optional(),
9135
9218
  store: z.string().optional(),
9219
+ failureLog: z.string().optional(), // JOHNNESS_PATCH_CRON_RELIABILITY_79: file sink, one line per cron failure
9136
9220
  maxConcurrentRuns: z.number().int().positive().optional(),
9137
9221
  retry: z.object({
9138
9222
  maxAttempts: z.number().int().min(0).max(10).optional(),
@@ -4981,6 +4981,11 @@ Object.assign(DEFAULT_MODEL_ALIASES, {
4981
4981
  "nano-banana": "google/gemini-3-pro-image-preview",
4982
4982
  image: "openai-codex/gpt-image-2"
4983
4983
  });
4984
+ // JOHNNESS_PATCH_OPUS_55_MODEL: Claude Opus 5.5 is the newest Opus; `opus` moves to it, opus-5 stays in the catalog.
4985
+ Object.assign(DEFAULT_MODEL_ALIASES, {
4986
+ opus: "anthropic/claude-opus-5-5",
4987
+ opus5: "anthropic/claude-opus-5"
4988
+ });
4984
4989
  const DEFAULT_MODEL_COST = {
4985
4990
  input: 0,
4986
4991
  output: 0,
@@ -5087,6 +5092,83 @@ function applyTalkApiKey(config) {
5087
5092
  function applyTalkConfigNormalization(config) {
5088
5093
  return normalizeTalkConfig(config);
5089
5094
  }
5095
+ /* JOHNNESS_PATCH_ALIAS_LATEST_79: bare shorthands (DEFAULT_MODEL_ALIASES keys without digits) always resolve
5096
+ * to the newest shipped model in their line. A user alias that claims one but pins an older model of the same
5097
+ * provider and line is moved: the shipped model takes the shorthand and the old model keeps a versioned alias
5098
+ * (opus5, sonnet46, gpt55). Custom aliases, newer pins and cross-provider or cross-line choices are untouched.
5099
+ * Replaces entries of the given models map in place (never mutates an entry); returns the moves. */
5100
+ function johnnessMigrateStaleShorthands79(models) {
5101
+ const moves = [];
5102
+ if (!models || typeof models !== "object" || Array.isArray(models)) return moves;
5103
+ const refOf = (raw) => {
5104
+ const text = String(raw ?? "").trim().toLowerCase();
5105
+ const slash = text.indexOf("/");
5106
+ return slash > 0 ? {
5107
+ provider: text.slice(0, slash),
5108
+ model: text.slice(slash + 1)
5109
+ } : null;
5110
+ };
5111
+ const versionOf = (model) => {
5112
+ const parts = [];
5113
+ for (const raw of model.match(/\d+/g) ?? []) {
5114
+ if (raw.length >= 4) break;
5115
+ parts.push(Number(raw));
5116
+ }
5117
+ return parts;
5118
+ };
5119
+ const isOlder = (alias, user, shipped) => {
5120
+ if (user.provider !== shipped.provider || user.model === shipped.model) return false;
5121
+ const tokens = new Set(user.model.split(/[^a-z0-9]+/).filter(Boolean));
5122
+ if (!alias.split("-").every((token) => tokens.has(token))) return false;
5123
+ const a = versionOf(user.model);
5124
+ const b = versionOf(shipped.model);
5125
+ if (a.length === 0 || b.length === 0) return false;
5126
+ for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
5127
+ const x = a[i] ?? 0;
5128
+ const y = b[i] ?? 0;
5129
+ if (x !== y) return x < y;
5130
+ }
5131
+ return false;
5132
+ };
5133
+ const versionedAliasFor = (alias, userKey, user) => {
5134
+ for (const [name, target] of Object.entries(DEFAULT_MODEL_ALIASES)) if (name !== alias && /\d/.test(name) && name.startsWith(alias) && String(target).toLowerCase() === userKey) return name;
5135
+ const digits = versionOf(user.model).join("");
5136
+ return digits ? `${alias}${digits}` : void 0;
5137
+ };
5138
+ for (const key of Object.keys(models)) {
5139
+ const entry = models[key];
5140
+ const alias = String(entry?.alias ?? "").trim().toLowerCase();
5141
+ if (!alias || /\d/.test(alias) || !Object.hasOwn(DEFAULT_MODEL_ALIASES, alias)) continue;
5142
+ const user = refOf(key);
5143
+ const shipped = refOf(DEFAULT_MODEL_ALIASES[alias]);
5144
+ if (!user || !shipped || !isOlder(alias, user, shipped)) continue;
5145
+ const userKey = `${user.provider}/${user.model}`;
5146
+ const taken = new Set(Object.entries(models).filter(([other]) => other !== key).map(([, value]) => String(value?.alias ?? "").trim().toLowerCase()).filter(Boolean));
5147
+ let versioned = versionedAliasFor(alias, userKey, user);
5148
+ if (versioned && (taken.has(versioned) || Object.hasOwn(DEFAULT_MODEL_ALIASES, versioned) && String(DEFAULT_MODEL_ALIASES[versioned]).toLowerCase() !== userKey)) versioned = void 0;
5149
+ const { alias: _previousAlias, ...rest } = entry;
5150
+ models[key] = versioned ? {
5151
+ ...rest,
5152
+ alias: versioned
5153
+ } : rest;
5154
+ const shippedKey = Object.keys(models).find((candidate) => {
5155
+ const ref = refOf(candidate);
5156
+ return ref !== null && ref.provider === shipped.provider && ref.model === shipped.model;
5157
+ });
5158
+ if (!shippedKey) models[DEFAULT_MODEL_ALIASES[alias]] = { alias };
5159
+ else if (!String(models[shippedKey]?.alias ?? "").trim()) models[shippedKey] = {
5160
+ ...models[shippedKey],
5161
+ alias
5162
+ };
5163
+ moves.push({
5164
+ alias,
5165
+ from: key,
5166
+ to: shippedKey ?? DEFAULT_MODEL_ALIASES[alias],
5167
+ versionedAlias: versioned
5168
+ });
5169
+ }
5170
+ return moves;
5171
+ }
5090
5172
  function applyModelDefaults(cfg) {
5091
5173
  let mutated = false;
5092
5174
  let nextCfg = cfg;
@@ -5158,6 +5240,7 @@ function applyModelDefaults(cfg) {
5158
5240
  const existingModels = existingAgent.models ?? {};
5159
5241
  if (Object.keys(existingModels).length === 0) return mutated ? nextCfg : cfg;
5160
5242
  const nextModels = { ...existingModels };
5243
+ if (johnnessMigrateStaleShorthands79(nextModels).length > 0) mutated = true; // JOHNNESS_PATCH_ALIAS_LATEST_79
5161
5244
  for (const [alias, target] of Object.entries(DEFAULT_MODEL_ALIASES)) {
5162
5245
  const entry = nextModels[target];
5163
5246
  if (!entry) continue;
@@ -12039,6 +12122,7 @@ const JohnnessSchema = z.object({
12039
12122
  cron: z.object({
12040
12123
  enabled: z.boolean().optional(),
12041
12124
  store: z.string().optional(),
12125
+ failureLog: z.string().optional(), // JOHNNESS_PATCH_CRON_RELIABILITY_79: file sink, one line per cron failure
12042
12126
  maxConcurrentRuns: z.number().int().positive().optional(),
12043
12127
  retry: z.object({
12044
12128
  maxAttempts: z.number().int().min(0).max(10).optional(),
@@ -4417,7 +4417,7 @@ async function runCronIsolatedAgentTurn(params) {
4417
4417
  provider,
4418
4418
  model,
4419
4419
  agentDir,
4420
- fallbacksOverride: payloadFallbacks ?? resolveAgentModelFallbacksOverride(params.cfg, agentId),
4420
+ fallbacksOverride: payloadFallbacks ?? resolveAgentModelFallbacksOverride(params.cfg, agentId) ?? resolveCronRunFallbacks79(cfgWithAgentDefaults, resolvedDefault), // JOHNNESS_PATCH_CRON_RELIABILITY_79
4421
4421
  run: async (providerOverride, modelOverride, runOptions) => {
4422
4422
  if (abortSignal?.aborted) throw new Error(abortReason());
4423
4423
  const bootstrapPromptWarningSignature = bootstrapPromptWarningSignaturesSeen[bootstrapPromptWarningSignaturesSeen.length - 1];
@@ -6110,6 +6110,49 @@ const TRANSIENT_PATTERNS = {
6110
6110
  timeout: /(timeout|etimedout)/i,
6111
6111
  server_error: /\b5\d{2}\b/
6112
6112
  };
6113
+ /* JOHNNESS_PATCH_CRON_RELIABILITY_79: recurring transient retry, run fallbacks and the cron.failureLog sink (family 79). */
6114
+ function classifyCronError79(error) {
6115
+ const text = typeof error === "string" ? error : "";
6116
+ if (!text) return "error";
6117
+ for (const [kind, pattern] of Object.entries(TRANSIENT_PATTERNS)) if (pattern.test(text)) return kind;
6118
+ return /\btimed out\b/i.test(text) ? "timeout" : "error";
6119
+ }
6120
+ function isRecurringCronRetryable79(error, retryOn) {
6121
+ if (typeof error !== "string" || error.trim() === timeoutErrorMessage()) return false;
6122
+ if (isTransientCronError(error, retryOn)) return true;
6123
+ return (!retryOn?.length || retryOn.includes("timeout")) && /\btimed out\b/i.test(error);
6124
+ }
6125
+ function resolveCronRunFallbacks79(cfg, resolvedDefault) {
6126
+ const model = cfg?.agents?.defaults?.model;
6127
+ const configured = model && typeof model === "object" && Array.isArray(model.fallbacks) ? model.fallbacks : [];
6128
+ const primary = resolvedDefault?.provider && resolvedDefault?.model ? [`${resolvedDefault.provider}/${resolvedDefault.model}`] : [];
6129
+ return [...configured, ...primary];
6130
+ }
6131
+ function cronLogField79(value) {
6132
+ return String(value ?? "").replace(/[\r\n|]+/g, " ").replace(/\s+/g, " ").trim().slice(0, 160) || "-";
6133
+ }
6134
+ function cronLogTime79(ms) {
6135
+ if (typeof ms !== "number" || !Number.isFinite(ms)) return "-";
6136
+ const d = new Date(ms);
6137
+ const offset = -d.getTimezoneOffset();
6138
+ const pad = (n) => String(Math.trunc(Math.abs(n))).padStart(2, "0");
6139
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}${offset >= 0 ? "+" : "-"}${pad(offset / 60)}:${pad(offset % 60)}`;
6140
+ }
6141
+ function appendCronFailureLog79(state, job, result, retry) {
6142
+ const target = state.deps.cronConfig?.failureLog;
6143
+ if (typeof target !== "string" || !target.trim()) return;
6144
+ const file = resolveUserPath(target.trim());
6145
+ const next = retry?.retryAtMs !== void 0 ? cronLogTime79(retry.retryAtMs) : job.enabled && job.state.nextRunAtMs !== void 0 ? `none (next run ${cronLogTime79(job.state.nextRunAtMs)})` : job.enabled ? "none" : "none (disabled)";
6146
+ const line = `- ${cronLogTime79(result.endedAt)} | cron-fail | ${cronLogField79(job.name || job.id)} | ${classifyCronError79(result.error)} | attempt ${job.state.consecutiveErrors ?? 1} | next retry ${next}\n`;
6147
+ const write = fs$1.mkdir(path.dirname(file), { recursive: true }).then(() => fs$1.appendFile(file, line, "utf8")).catch((err) => {
6148
+ state.deps.log.warn({
6149
+ jobId: job.id,
6150
+ failureLog: file,
6151
+ err: String(err)
6152
+ }, "cron: failure log append failed");
6153
+ });
6154
+ state.failureLogWrites79 = write;
6155
+ }
6113
6156
  function isTransientCronError(error, retryOn) {
6114
6157
  if (!error || typeof error !== "string") return false;
6115
6158
  return (retryOn?.length ? retryOn : Object.keys(TRANSIENT_PATTERNS)).some((k) => TRANSIENT_PATTERNS[k]?.test(error));
@@ -6241,6 +6284,7 @@ function applyJobResult(state, job, result, opts) {
6241
6284
  job.state.consecutiveErrors = 0;
6242
6285
  job.state.lastFailureAlertAtMs = void 0;
6243
6286
  }
6287
+ let retry79; // JOHNNESS_PATCH_CRON_RELIABILITY_79
6244
6288
  const shouldDelete = job.schedule.kind === "at" && job.deleteAfterRun === true && result.status === "ok";
6245
6289
  if (!shouldDelete) if (job.schedule.kind === "at") {
6246
6290
  if (result.status === "ok" || result.status === "skipped") {
@@ -6285,6 +6329,24 @@ function applyJobResult(state, job, result, opts) {
6285
6329
  });
6286
6330
  }
6287
6331
  const backoffNext = result.endedAt + backoff;
6332
+ /* JOHNNESS_PATCH_CRON_RELIABILITY_79: a transient error on a recurring job retries at
6333
+ min(next natural slot, now + cron.retry backoff) up to cron.retry.maxAttempts; after that
6334
+ the normal schedule resumes with the error backoff below. */
6335
+ const retryConfig79 = resolveRetryConfig(state.deps.cronConfig);
6336
+ const consecutive79 = job.state.consecutiveErrors ?? 1;
6337
+ if (isRecurringCronRetryable79(result.error, retryConfig79.retryOn) && consecutive79 <= retryConfig79.maxAttempts) {
6338
+ const retryBackoff79 = Math.max(MIN_REFIRE_GAP_MS, errorBackoffMs(consecutive79, retryConfig79.backoffMs));
6339
+ const retryAt79 = result.endedAt + retryBackoff79;
6340
+ job.state.nextRunAtMs = normalNext !== void 0 ? Math.min(normalNext, retryAt79) : retryAt79;
6341
+ retry79 = { retryAtMs: job.state.nextRunAtMs };
6342
+ state.deps.log.info({
6343
+ jobId: job.id,
6344
+ jobName: job.name,
6345
+ consecutiveErrors: consecutive79,
6346
+ backoffMs: retryBackoff79,
6347
+ nextRunAtMs: job.state.nextRunAtMs
6348
+ }, "cron: scheduling recurring retry after transient error");
6349
+ } else {
6288
6350
  job.state.nextRunAtMs = normalNext !== void 0 ? Math.max(normalNext, backoffNext) : backoffNext;
6289
6351
  state.deps.log.info({
6290
6352
  jobId: job.id,
@@ -6292,6 +6354,7 @@ function applyJobResult(state, job, result, opts) {
6292
6354
  backoffMs: backoff,
6293
6355
  nextRunAtMs: job.state.nextRunAtMs
6294
6356
  }, "cron: applying error backoff");
6357
+ }
6295
6358
  } else if (job.enabled) {
6296
6359
  let naturalNext;
6297
6360
  try {
@@ -6308,6 +6371,7 @@ function applyJobResult(state, job, result, opts) {
6308
6371
  job.state.nextRunAtMs = naturalNext !== void 0 ? Math.max(naturalNext, minNext) : minNext;
6309
6372
  } else job.state.nextRunAtMs = naturalNext;
6310
6373
  } else job.state.nextRunAtMs = void 0;
6374
+ if (result.status === "error") appendCronFailureLog79(state, job, result, retry79 ?? (job.schedule.kind === "at" && job.enabled && job.state.nextRunAtMs !== void 0 ? { retryAtMs: job.state.nextRunAtMs } : void 0)); // JOHNNESS_PATCH_CRON_RELIABILITY_79
6311
6375
  return shouldDelete;
6312
6376
  }
6313
6377
  function applyOutcomeToStoredJob(state, result) {
@@ -23471,6 +23535,21 @@ async function startGatewayServer(port = 18789, opts = {}) {
23471
23535
  const issues = configSnapshot.issues.length > 0 ? formatConfigIssueLines(configSnapshot.issues, "", { normalizeRoot: true }).join("\n") : "Unknown validation issue.";
23472
23536
  throw new Error(`Invalid config at ${configSnapshot.path}.\n${issues}\nRun "${formatCliCommand("johnness doctor")}" to repair, then retry.`);
23473
23537
  }
23538
+ /* JOHNNESS_PATCH_ALIAS_LATEST_79: bare shorthands always point at the newest shipped model. A user
23539
+ alias that pins an older model of the same line moves to the shipped default; the old model keeps a
23540
+ versioned alias. Written back to the config file once, logged, idempotent. */
23541
+ try {
23542
+ const parsed79 = configSnapshot.parsed && typeof configSnapshot.parsed === "object" ? structuredClone(configSnapshot.parsed) : null;
23543
+ const models79 = parsed79?.agents?.defaults?.models;
23544
+ const moved79 = !isNixMode && models79 && typeof models79 === "object" && !Array.isArray(models79) ? johnnessMigrateStaleShorthands79(models79) : [];
23545
+ if (moved79.length > 0) {
23546
+ await writeConfigFile(parsed79);
23547
+ log.info(`gateway: model shorthands moved to the newest shipped models:\n${moved79.map((move) => `- ${move.alias}: ${move.from} -> ${move.to}${move.versionedAlias ? ` (${move.from} is now "${move.versionedAlias}")` : ""}`).join("\n")}`);
23548
+ configSnapshot = await readConfigFileSnapshot();
23549
+ }
23550
+ } catch (err) {
23551
+ log.warn(`gateway: model shorthand migration skipped: ${String(err)}`);
23552
+ }
23474
23553
  const autoEnable = applyPluginAutoEnable({
23475
23554
  config: configSnapshot.config,
23476
23555
  env: process.env
@@ -24774,3 +24853,4 @@ function registerGatewayCli(program) {
24774
24853
  }
24775
24854
  //#endregion
24776
24855
  export { registerGatewayCli };
24856
+ import { johnnessMigrateStaleShorthands79 } from "./model-selection-L7RMwsG-.js"; // JOHNNESS_PATCH_ALIAS_LATEST_79