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.
- package/CHANGELOG.md +13 -1
- package/dist/auth-profiles-5CHn7vq1.js +85 -0
- package/dist/channels/plugins/actions/telegram.js +1 -0
- package/dist/compact-B247y5Qt.js +7 -1
- package/dist/config-koj5M_EO.js +84 -0
- package/dist/daemon-cli.js +84 -0
- package/dist/gateway-cli-B7fBU7gD.js +81 -1
- package/dist/gateway-cli-BCNMwzNq.js +81 -1
- package/dist/model-selection-BGlGpPgM.js +85 -1
- package/dist/model-selection-BU6wl1le.js +85 -1
- package/dist/model-selection-L7RMwsG-.js +86 -1
- package/dist/models-config-CAmcg63r.js +1 -1
- package/dist/models-config-DXA5BkaM.js +1 -1
- package/dist/pi-embedded-C6ITuRXf.js +7 -1
- package/dist/pi-embedded-DoQsYfIY.js +7 -1
- package/dist/plugin-sdk/bluebubbles.js +1 -0
- package/dist/plugin-sdk/config-4Fgm-yEH.js +84 -0
- package/dist/plugin-sdk/config-Di-lyCdh.js +84 -0
- package/dist/plugin-sdk/discord.js +84 -0
- package/dist/plugin-sdk/dispatch-BP0viZiL.js +7 -1
- package/dist/plugin-sdk/dispatch-CQsjmw7g.js +7 -1
- package/dist/plugin-sdk/dispatch-Cerq29sy.js +7 -1
- package/dist/plugin-sdk/dispatch-Cndjtt0g.js +7 -1
- package/dist/plugin-sdk/dispatch-UogiJYul.js +7 -1
- package/dist/plugin-sdk/feishu.js +1 -0
- package/dist/plugin-sdk/googlechat.js +1 -0
- package/dist/plugin-sdk/imessage.js +1 -0
- package/dist/plugin-sdk/mattermost.js +1 -0
- package/dist/plugin-sdk/model-auth-CX9cPHdC.js +84 -0
- package/dist/plugin-sdk/model-auth-Ciehz0x5.js +84 -0
- package/dist/plugin-sdk/model-auth-DVyo6JSX.js +84 -0
- package/dist/plugin-sdk/model-auth-kiHYHGrs.js +84 -0
- package/dist/plugin-sdk/model-selection-Dbj4jDYM.js +84 -0
- package/dist/plugin-sdk/reply-DbZnH8-h.js +7 -1
- package/dist/plugin-sdk/signal.js +1 -0
- package/dist/plugin-sdk/slack.js +1 -0
- package/dist/plugin-sdk/telegram.js +1 -0
- package/dist/plugin-sdk/thinking-B-rIrY_n.js +1 -0
- package/dist/plugin-sdk/thinking-CopbP_Cn.js +1 -0
- package/dist/plugin-sdk/thinking-WQ4Zq8Nl.js +1 -0
- package/dist/plugin-sdk/thinking-WXqkGljb.js +1 -0
- package/dist/plugin-sdk/thinking-_uXqFaFO.js +1 -0
- package/dist/plugin-sdk/thinking-pTdxk2dr.js +1 -0
- package/dist/plugin-sdk/tlon.js +1 -0
- package/dist/plugin-sdk/twitch.js +1 -0
- package/dist/plugin-sdk/zalo.js +1 -0
- package/dist/plugin-sdk/zalouser.js +1 -0
- package/dist/reply-C5LKjXcC.js +7 -1
- package/dist/thinking-B5B36ffe.js +1 -0
- package/dist/thinking-BYwvlJ3S.js +1 -0
- package/dist/thinking-CAzdgmNV.js +1 -0
- package/dist/thinking-DykY2Fzj.js +1 -0
- package/docs/PATCHES.md +16 -0
- package/docs/PROVIDER-AUDIT-2026-09-20.md +21 -4
- package/node_modules/@mariozechner/pi-ai/dist/models.generated.js +22 -0
- package/node_modules/@mariozechner/pi-ai/dist/models.js +1 -0
- package/node_modules/@mariozechner/pi-ai/dist/providers/anthropic.js +2 -2
- package/package.json +1 -1
- package/vendor/patched-deps/@mariozechner/pi-ai/dist/models.generated.js +22 -0
- package/vendor/patched-deps/@mariozechner/pi-ai/dist/models.js +1 -0
- 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.
|
|
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",
|
package/dist/compact-B247y5Qt.js
CHANGED
|
@@ -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 :
|
|
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, {}));
|
package/dist/config-koj5M_EO.js
CHANGED
|
@@ -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(),
|
package/dist/daemon-cli.js
CHANGED
|
@@ -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
|