@link-assistant/hive-mind 1.74.9 → 1.74.11
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 +12 -0
- package/package.json +1 -1
- package/src/config.lib.mjs +59 -10
- package/src/models/index.mjs +24 -2
- package/src/solve.auto-continue.lib.mjs +1 -1
- package/src/solve.auto-merge.lib.mjs +1 -1
- package/src/solve.config.lib.mjs +2 -2
- package/src/solve.mjs +1 -1
- package/src/solve.validation.lib.mjs +48 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.74.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- faa10c5: Add support for Claude Fable 5 (`claude-fable-5`) and its un-classified sibling Claude Mythos 5 (`claude-mythos-5`) as selectable models for `--tool claude` (Issue #1875). New aliases `fable`, `fable-5`, `claude-fable-5`, `mythos-5`, and `claude-mythos-5` resolve in the centralized model registry, support the `[1m]` 1M-context suffix, the full effort ladder including `xhigh` and `max` (default `high`), and 128K max output tokens. Both models are adaptive-thinking-only, so `getClaudeEnv` removes `MAX_THINKING_TOKENS` for them (the API rejects disabled thinking), mirroring Opus 4.7/4.8. Documented default fallbacks are registered (`claude-fable-5 -> opus` reflecting Fable 5's safety-classifier hand-off to Opus 4.8; `claude-mythos-5 -> fable`). Existing defaults (`opus`, `sonnet`, `haiku`, `opusplan`) are unchanged — this adds Fable 5 as an option without altering current behavior. `fable` is surfaced in `--model` help. Includes `tests/test-fable-5-model-support.mjs` (127 tests) and a full case study under `docs/case-studies/issue-1875/`.
|
|
8
|
+
|
|
9
|
+
## 1.74.10
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 88adc75: Fix the auto-resume wait calculation for weekly `--tool codex` usage limits (Issue #1869, phase 2). After the display parser was fixed to keep the full reset date, the separate auto-resume parser in `solve.validation.lib.mjs` still crashed with `Invalid time format: Jun 11, 2026, 12:27 AM` and, even when it parsed, discarded the date and scheduled for today/tomorrow — so auto-resume woke up far too early. `calculateWaitTime` now delegates to the robust date-aware `parseResetTime` from `usage-limit.lib.mjs` (honoring explicit year, weekly date, and timezone) and returns the real time-until-reset, and all three call sites now forward the timezone. This consolidates onto a single reset-time parser.
|
|
14
|
+
|
|
3
15
|
## 1.74.9
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
package/src/config.lib.mjs
CHANGED
|
@@ -270,6 +270,48 @@ const isMythosPreview = model => {
|
|
|
270
270
|
return model.toLowerCase().includes('mythos');
|
|
271
271
|
};
|
|
272
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Check if a model is Claude Fable 5 (Issue #1875)
|
|
275
|
+
* Fable 5 (`claude-fable-5`) is Anthropic's most capable widely released model
|
|
276
|
+
* (generally available June 9, 2026). It is a Mythos-class model wrapped in safety
|
|
277
|
+
* classifiers that can refuse high-risk requests (returning stop_reason "refusal")
|
|
278
|
+
* and fall back to Claude Opus 4.8.
|
|
279
|
+
* @param {string} model - The model name or ID
|
|
280
|
+
* @returns {boolean} True if the model is Claude Fable 5
|
|
281
|
+
*/
|
|
282
|
+
export const isFable5 = model => {
|
|
283
|
+
if (!model) return false;
|
|
284
|
+
const m = model.toLowerCase();
|
|
285
|
+
return m === 'fable' || m === 'fable-5' || m.includes('fable-5') || m.includes('fable5');
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Check if a model is Claude Mythos 5 (Issue #1875)
|
|
290
|
+
* Mythos 5 (`claude-mythos-5`) shares Fable 5's capabilities without the safety
|
|
291
|
+
* classifiers and is offered in limited availability via Project Glasswing.
|
|
292
|
+
* Distinct from Claude Mythos Preview (see isMythosPreview): Mythos 5 additionally
|
|
293
|
+
* supports the `xhigh` effort level.
|
|
294
|
+
* @param {string} model - The model name or ID
|
|
295
|
+
* @returns {boolean} True if the model is Claude Mythos 5
|
|
296
|
+
*/
|
|
297
|
+
export const isMythos5 = model => {
|
|
298
|
+
if (!model) return false;
|
|
299
|
+
const m = model.toLowerCase();
|
|
300
|
+
return m === 'mythos-5' || m.includes('mythos-5') || m.includes('mythos5');
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Check if a model is Claude Fable 5 or Claude Mythos 5 (Issue #1875)
|
|
305
|
+
* Both share the same Messages API constraints: the effort parameter is supported
|
|
306
|
+
* across low/medium/high/xhigh/max (default high), adaptive thinking is always on
|
|
307
|
+
* (extended/manual thinking is unavailable and `thinking: {type: "disabled"}` is
|
|
308
|
+
* rejected), the context window is 1M tokens, and max output is 128k tokens.
|
|
309
|
+
* See: https://platform.claude.com/docs/en/about-claude/models/introducing-claude-fable-5-and-claude-mythos-5
|
|
310
|
+
* @param {string} model - The model name or ID
|
|
311
|
+
* @returns {boolean} True if the model is Claude Fable 5 or Claude Mythos 5
|
|
312
|
+
*/
|
|
313
|
+
export const isFable5OrMythos5 = model => isFable5(model) || isMythos5(model);
|
|
314
|
+
|
|
273
315
|
/**
|
|
274
316
|
* Check if a model supports CLAUDE_CODE_EFFORT_LEVEL (Issue #1238, Issue #1620)
|
|
275
317
|
* Official effort support: Claude Mythos Preview, Opus 4.7, Opus 4.6, Sonnet 4.6, and Opus 4.5.
|
|
@@ -279,32 +321,36 @@ const isMythosPreview = model => {
|
|
|
279
321
|
*/
|
|
280
322
|
export const supportsEffortLevel = model => {
|
|
281
323
|
if (!model) return false;
|
|
282
|
-
return isMythosPreview(model) || isOpus47OrLater(model) || isOpus46(model) || isSonnet46OrLater(model) || isOpus45(model);
|
|
324
|
+
return isFable5OrMythos5(model) || isMythosPreview(model) || isOpus47OrLater(model) || isOpus46(model) || isSonnet46OrLater(model) || isOpus45(model);
|
|
283
325
|
};
|
|
284
326
|
|
|
285
327
|
/**
|
|
286
328
|
* Check if a model supports the xhigh effort level.
|
|
287
|
-
* Official docs list xhigh for Claude
|
|
329
|
+
* Official docs list xhigh for Claude Fable 5, Claude Mythos 5, Claude Opus 4.7,
|
|
330
|
+
* and Opus 4.8 (Issue #1832, Issue #1875).
|
|
288
331
|
* @param {string} model - The model name or ID
|
|
289
332
|
* @returns {boolean} True if the model supports xhigh effort
|
|
290
333
|
*/
|
|
291
|
-
export const supportsXHighEffortLevel = model => isOpus47(model);
|
|
334
|
+
export const supportsXHighEffortLevel = model => isFable5OrMythos5(model) || isOpus47(model);
|
|
292
335
|
|
|
293
336
|
/**
|
|
294
337
|
* Check if a model supports the max effort level.
|
|
295
|
-
* Official docs list max for Claude
|
|
338
|
+
* Official docs list max for Claude Fable 5, Claude Mythos 5, Claude Mythos Preview,
|
|
339
|
+
* Opus 4.7, Opus 4.6, and Sonnet 4.6 (Issue #1875).
|
|
296
340
|
* @param {string} model - The model name or ID
|
|
297
341
|
* @returns {boolean} True if the model supports max effort
|
|
298
342
|
*/
|
|
299
|
-
export const supportsMaxEffortLevel = model => isMythosPreview(model) || isOpus47OrLater(model) || isOpus46(model) || isSonnet46OrLater(model);
|
|
343
|
+
export const supportsMaxEffortLevel = model => isFable5OrMythos5(model) || isMythosPreview(model) || isOpus47OrLater(model) || isOpus46(model) || isSonnet46OrLater(model);
|
|
300
344
|
|
|
301
345
|
/**
|
|
302
|
-
* Get the max output tokens for a specific model (Issue #1221)
|
|
346
|
+
* Get the max output tokens for a specific model (Issue #1221, Issue #1875)
|
|
347
|
+
* Claude Fable 5 and Claude Mythos 5 support up to 128k output tokens, matching
|
|
348
|
+
* the Opus 4.6+ ceiling.
|
|
303
349
|
* @param {string} model - The model name or ID
|
|
304
350
|
* @returns {number} The max output tokens for the model
|
|
305
351
|
*/
|
|
306
352
|
export const getMaxOutputTokensForModel = model => {
|
|
307
|
-
if (isOpus46OrLater(model)) {
|
|
353
|
+
if (isOpus46OrLater(model) || isFable5OrMythos5(model)) {
|
|
308
354
|
return claudeCode.maxOutputTokensOpus46;
|
|
309
355
|
}
|
|
310
356
|
return claudeCode.maxOutputTokens;
|
|
@@ -479,11 +525,14 @@ export const getClaudeEnv = (options = {}) => {
|
|
|
479
525
|
|
|
480
526
|
// Opus 4.7+ always uses adaptive thinking — MAX_THINKING_TOKENS has no effect (Issue #1620, Issue #1832)
|
|
481
527
|
// Opus 4.8 inherits this constraint: adaptive thinking is the only thinking mode.
|
|
528
|
+
// Claude Fable 5 and Claude Mythos 5 are adaptive-thinking-only too: extended/manual
|
|
529
|
+
// thinking is unavailable and `thinking: {type: "disabled"}` is rejected, so a
|
|
530
|
+
// MAX_THINKING_TOKENS=0 would be invalid for them (Issue #1875).
|
|
482
531
|
// For Opus 4.6 and earlier, MAX_THINKING_TOKENS controls extended thinking (Claude Code >= 2.1.12)
|
|
483
532
|
// Default is 0 (thinking disabled) per Issue #1238.
|
|
484
|
-
const
|
|
485
|
-
if (
|
|
486
|
-
// Remove any inherited MAX_THINKING_TOKENS from process.env —
|
|
533
|
+
const adaptiveThinkingOnly = options.model && (isOpus47OrLater(options.model) || isFable5OrMythos5(options.model));
|
|
534
|
+
if (adaptiveThinkingOnly) {
|
|
535
|
+
// Remove any inherited MAX_THINKING_TOKENS from process.env — these models ignore it
|
|
487
536
|
delete env.MAX_THINKING_TOKENS;
|
|
488
537
|
} else {
|
|
489
538
|
env.MAX_THINKING_TOKENS = String(options.thinkingBudget ?? 0);
|
package/src/models/index.mjs
CHANGED
|
@@ -28,7 +28,8 @@ const execFileAsync = promisify(execFile);
|
|
|
28
28
|
// ─── MODEL DATA ──────────────────────────────────────────────────────────────
|
|
29
29
|
|
|
30
30
|
// Claude models (Anthropic API)
|
|
31
|
-
// Updated for Opus 4.5/4.6/4.7/4.8
|
|
31
|
+
// Updated for Opus 4.5/4.6/4.7/4.8, Sonnet 4.6, and Fable 5 / Mythos 5 support
|
|
32
|
+
// (Issue #1221, Issue #1238, Issue #1329, Issue #1433, Issue #1620, Issue #1832, Issue #1875)
|
|
32
33
|
export const claudeModels = {
|
|
33
34
|
sonnet: 'claude-sonnet-4-6', // Sonnet 4.6 (default, Issue #1329)
|
|
34
35
|
opus: 'claude-opus-4-8', // Opus 4.8 (Issue #1832)
|
|
@@ -36,6 +37,13 @@ export const claudeModels = {
|
|
|
36
37
|
'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
|
|
37
38
|
'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
|
|
38
39
|
opusplan: 'opusplan', // Special mode: Opus for planning, Sonnet for execution (Issue #1223)
|
|
40
|
+
// Claude Fable 5 — Anthropic's most capable widely released (Mythos-class) model, GA 2026-06-09 (Issue #1875)
|
|
41
|
+
fable: 'claude-fable-5', // Fable 5 alias
|
|
42
|
+
'fable-5': 'claude-fable-5', // Fable 5 short alias
|
|
43
|
+
'claude-fable-5': 'claude-fable-5', // Fable 5 full ID
|
|
44
|
+
// Claude Mythos 5 — shares Fable 5's capabilities without safety classifiers; limited availability (Project Glasswing) (Issue #1875)
|
|
45
|
+
'mythos-5': 'claude-mythos-5', // Mythos 5 short alias
|
|
46
|
+
'claude-mythos-5': 'claude-mythos-5', // Mythos 5 full ID
|
|
39
47
|
// Shorter version aliases (Issue #1221, Issue #1329 - PR comment feedback)
|
|
40
48
|
'sonnet-4-6': 'claude-sonnet-4-6', // Sonnet 4.6 short alias (Issue #1329)
|
|
41
49
|
'opus-4-8': 'claude-opus-4-8', // Opus 4.8 short alias (Issue #1832)
|
|
@@ -180,6 +188,11 @@ export const defaultModels = {
|
|
|
180
188
|
// Models that support 1M token context window via [1m] suffix (Issue #1221, Issue #1238, Issue #1329, Issue #1832)
|
|
181
189
|
// See: https://code.claude.com/docs/en/model-config
|
|
182
190
|
export const MODELS_SUPPORTING_1M_CONTEXT = [
|
|
191
|
+
'claude-fable-5', // Fable 5 — 1M context by default (Issue #1875)
|
|
192
|
+
'claude-mythos-5', // Mythos 5 — 1M context by default (Issue #1875)
|
|
193
|
+
'fable', // Fable 5 alias (Issue #1875)
|
|
194
|
+
'fable-5', // Fable 5 short alias (Issue #1875)
|
|
195
|
+
'mythos-5', // Mythos 5 short alias (Issue #1875)
|
|
183
196
|
'claude-opus-4-8', // Opus 4.8 (Issue #1832)
|
|
184
197
|
'claude-opus-4-7', // Opus 4.7 (Issue #1620)
|
|
185
198
|
'claude-opus-4-6',
|
|
@@ -220,6 +233,8 @@ export const freeToBaseModelMap = {
|
|
|
220
233
|
|
|
221
234
|
export const CLAUDE_MODELS = {
|
|
222
235
|
...claudeModels,
|
|
236
|
+
'claude-fable-5': 'claude-fable-5', // Fable 5 full ID (Issue #1875)
|
|
237
|
+
'claude-mythos-5': 'claude-mythos-5', // Mythos 5 full ID (Issue #1875)
|
|
223
238
|
'claude-opus-4-8': 'claude-opus-4-8', // Opus 4.8 full ID (Issue #1832)
|
|
224
239
|
'claude-opus-4-7': 'claude-opus-4-7', // Opus 4.7 full ID (Issue #1620)
|
|
225
240
|
'claude-sonnet-4-5-20250929': 'claude-sonnet-4-5-20250929',
|
|
@@ -450,7 +465,7 @@ export const getValidModelsForTool = tool => {
|
|
|
450
465
|
// Primary (non-alias, non-deprecated) short names shown in CLI help descriptions
|
|
451
466
|
// These are the recommended model names users should see in --model help text
|
|
452
467
|
export const primaryModelNames = {
|
|
453
|
-
claude: ['opus', 'sonnet', 'haiku', 'opusplan'],
|
|
468
|
+
claude: ['opus', 'sonnet', 'haiku', 'opusplan', 'fable'],
|
|
454
469
|
opencode: ['grok', 'gpt4o'],
|
|
455
470
|
codex: ['gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.3-codex-spark'],
|
|
456
471
|
agent: ['nemotron-3-super-free', 'minimax-m2.5-free', 'big-pickle', 'gpt-5-nano', 'glm-5-free', 'deepseek-r1-free'],
|
|
@@ -994,6 +1009,13 @@ export const resolveModelId = (requestedModel, tool) => {
|
|
|
994
1009
|
|
|
995
1010
|
export const defaultFallbackModels = {
|
|
996
1011
|
claude: {
|
|
1012
|
+
// Claude Fable 5's safety classifiers can refuse high-risk requests and hand them
|
|
1013
|
+
// off to Claude Opus 4.8; mirror that documented fallback here (Issue #1875).
|
|
1014
|
+
// See: https://platform.claude.com/docs/en/about-claude/models/introducing-claude-fable-5-and-claude-mythos-5
|
|
1015
|
+
'claude-fable-5': 'opus',
|
|
1016
|
+
// Claude Mythos 5 (limited availability) falls back to the generally available
|
|
1017
|
+
// Mythos-class model, Claude Fable 5 (Issue #1875).
|
|
1018
|
+
'claude-mythos-5': 'fable',
|
|
997
1019
|
'claude-opus-4-8': 'opus-4-7',
|
|
998
1020
|
'claude-opus-4-7': 'opus-4-6',
|
|
999
1021
|
},
|
|
@@ -92,7 +92,7 @@ export const autoContinueWhenLimitResets = async (issueUrl, sessionId, argv, sho
|
|
|
92
92
|
const nextAutoResumeIteration = currentAutoResumeIteration + 1;
|
|
93
93
|
const resetTime = global.limitResetTime;
|
|
94
94
|
const timezone = global.limitTimezone || null;
|
|
95
|
-
const baseWaitMs = calculateWaitTime(resetTime);
|
|
95
|
+
const baseWaitMs = calculateWaitTime(resetTime, timezone);
|
|
96
96
|
|
|
97
97
|
// Add buffer time after limit reset to account for server time differences
|
|
98
98
|
// Default: 10 minutes (configurable via HIVE_MIND_LIMIT_RESET_BUFFER_MS)
|
|
@@ -735,7 +735,7 @@ No further AI sessions will be started automatically for this run. Please review
|
|
|
735
735
|
limitResumeCount++;
|
|
736
736
|
const resumeSessionId = toolResult.sessionId;
|
|
737
737
|
const resetTime = toolResult.limitResetTime;
|
|
738
|
-
const baseWaitMs = resetTime ? calculateWaitTime(resetTime) : 0;
|
|
738
|
+
const baseWaitMs = resetTime ? calculateWaitTime(resetTime, toolResult.limitTimezone || null) : 0;
|
|
739
739
|
const bufferMs = limitReset.bufferMs;
|
|
740
740
|
const jitterMs = Math.floor(Math.random() * limitReset.jitterMs);
|
|
741
741
|
const waitMs = baseWaitMs + bufferMs + jitterMs;
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -285,7 +285,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
285
285
|
},
|
|
286
286
|
think: {
|
|
287
287
|
type: 'string',
|
|
288
|
-
description: 'Thinking level hint. For Claude, translated to --thinking-budget for Claude Code >= 2.1.12 (off=0, low=~8000, medium=~16000, high=~24000, xhigh/max=31999) and to CLAUDE_CODE_EFFORT_LEVEL when supported. Opus 4.8/4.7 support xhigh and max; Opus 4.6/Sonnet 4.6/Mythos support max; Opus 4.5 uses high for xhigh/max. For Codex, mapped to reasoning effort (off=none, low=low, medium=medium, high=high, xhigh/max=xhigh).',
|
|
288
|
+
description: 'Thinking level hint. For Claude, translated to --thinking-budget for Claude Code >= 2.1.12 (off=0, low=~8000, medium=~16000, high=~24000, xhigh/max=31999) and to CLAUDE_CODE_EFFORT_LEVEL when supported. Fable 5/Mythos 5/Opus 4.8/4.7 support xhigh and max; Opus 4.6/Sonnet 4.6/Mythos Preview support max; Opus 4.5 uses high for xhigh/max. For Codex, mapped to reasoning effort (off=none, low=low, medium=medium, high=high, xhigh/max=xhigh).',
|
|
289
289
|
choices: ['off', 'low', 'medium', 'high', 'xhigh', 'max'],
|
|
290
290
|
default: undefined,
|
|
291
291
|
},
|
|
@@ -316,7 +316,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
316
316
|
},
|
|
317
317
|
'fallback-model': {
|
|
318
318
|
type: 'string',
|
|
319
|
-
description: 'Fallback model to switch to on model capacity/overload errors. When supported, retries resume the same session with this model. Defaults: claude opus/opus-4-8 -> opus-4-7; claude opus-4-7 -> opus-4-6; codex gpt-5.5 -> gpt-5.4; all others unset.',
|
|
319
|
+
description: 'Fallback model to switch to on model capacity/overload errors (and, for Fable 5, on safety-classifier refusals). When supported, retries resume the same session with this model. Defaults: claude fable/claude-fable-5 -> opus (Opus 4.8); claude mythos-5/claude-mythos-5 -> fable; claude opus/opus-4-8 -> opus-4-7; claude opus-4-7 -> opus-4-6; codex gpt-5.5 -> gpt-5.4; all others unset.',
|
|
320
320
|
default: undefined,
|
|
321
321
|
},
|
|
322
322
|
'show-thinking-content': {
|
package/src/solve.mjs
CHANGED
|
@@ -1029,7 +1029,7 @@ try {
|
|
|
1029
1029
|
// Calculate wait time in d:h:m:s format
|
|
1030
1030
|
const validation = await import('./solve.validation.lib.mjs');
|
|
1031
1031
|
const { calculateWaitTime } = validation;
|
|
1032
|
-
const waitMs = calculateWaitTime(global.limitResetTime);
|
|
1032
|
+
const waitMs = calculateWaitTime(global.limitResetTime, global.limitTimezone || null);
|
|
1033
1033
|
|
|
1034
1034
|
const formatWaitTime = ms => {
|
|
1035
1035
|
const seconds = Math.floor(ms / 1000);
|
|
@@ -43,6 +43,13 @@ const claudeLib = await import('./claude.lib.mjs');
|
|
|
43
43
|
const sentryLib = await import('./sentry.lib.mjs');
|
|
44
44
|
const { reportError } = sentryLib;
|
|
45
45
|
|
|
46
|
+
// Import the robust usage-limit reset-time parser.
|
|
47
|
+
// This returns a full dayjs date (honoring an explicit year and timezone) so the
|
|
48
|
+
// auto-resume wait calculation can respect weekly limits that are days out, rather
|
|
49
|
+
// than collapsing every reset to "today/tomorrow at HH:MM" (Issue #1869).
|
|
50
|
+
const usageLimitLib = await import('./usage-limit.lib.mjs');
|
|
51
|
+
const { parseResetTime: parseResetTimeToDate } = usageLimitLib;
|
|
52
|
+
|
|
46
53
|
const { validateClaudeConnection } = claudeLib;
|
|
47
54
|
|
|
48
55
|
// Wrapper function for disk space check using imported module
|
|
@@ -394,13 +401,23 @@ export const parseUrlComponents = issueUrl => {
|
|
|
394
401
|
};
|
|
395
402
|
};
|
|
396
403
|
|
|
397
|
-
// Helper function to parse time string
|
|
404
|
+
// Helper function to parse a reset time string into hour/minute components.
|
|
405
|
+
//
|
|
406
|
+
// Accepts:
|
|
407
|
+
// - Time-only forms: "5:30am", "11:45pm", "12:16 PM", "07:05 Am", "5am", "5 AM"
|
|
408
|
+
// - Date+time forms: "Apr 17, 4:00 AM" (date portion ignored)
|
|
409
|
+
// - Date+year+time forms: "Jun 11, 2026, 12:27 AM" (date+year ignored — Issue #1869)
|
|
410
|
+
//
|
|
411
|
+
// NOTE: This helper only extracts the time-of-day. For computing the actual wait
|
|
412
|
+
// duration use calculateWaitTime(), which preserves the full date so weekly limits
|
|
413
|
+
// (which can be days away) are honored instead of being collapsed to today/tomorrow.
|
|
398
414
|
export const parseResetTime = timeStr => {
|
|
399
|
-
// Normalize and parse time formats like:
|
|
400
|
-
// "5:30am", "11:45pm", "12:16 PM", "07:05 Am", "5am", "5 AM"
|
|
401
|
-
// Also accepts date+time forms like "Apr 17, 4:00 AM" and ignores the date portion.
|
|
402
415
|
const normalized = (timeStr || '').toString().trim();
|
|
403
|
-
|
|
416
|
+
// Strip an optional leading "Month Day," and an optional "Year," so the
|
|
417
|
+
// remaining string is just the time-of-day. The year group (Issue #1869) makes
|
|
418
|
+
// Codex weekly-limit strings like "Jun 11, 2026, 12:27 AM" parse instead of
|
|
419
|
+
// throwing "Invalid time format".
|
|
420
|
+
const timePortion = normalized.replace(/^(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\s+\d{1,2}(?:st|nd|rd|th)?,\s+(?:\d{4},\s+)?/i, '');
|
|
404
421
|
|
|
405
422
|
// Accept both HH:MM am/pm and HH am/pm
|
|
406
423
|
let match = timePortion.match(/^(\d{1,2})(?::(\d{2}))?\s*([ap]m)$/i);
|
|
@@ -423,8 +440,32 @@ export const parseResetTime = timeStr => {
|
|
|
423
440
|
return { hour, minute };
|
|
424
441
|
};
|
|
425
442
|
|
|
426
|
-
// Calculate milliseconds until the
|
|
427
|
-
|
|
443
|
+
// Calculate milliseconds until the limit reset.
|
|
444
|
+
//
|
|
445
|
+
// Issue #1869: This MUST respect the full reset date (including an explicit year),
|
|
446
|
+
// not just the time-of-day. A weekly Codex limit reports "Jun 11th, 2026 12:27 AM"
|
|
447
|
+
// which can be days in the future; the previous implementation only looked at the
|
|
448
|
+
// hour/minute and scheduled for today/tomorrow, so auto-resume woke up far too early
|
|
449
|
+
// and burned an auto-resume iteration without the limit actually having reset.
|
|
450
|
+
//
|
|
451
|
+
// We delegate to the robust usage-limit parser, which returns a full dayjs date that
|
|
452
|
+
// already handles: explicit year, weekly date+time, time-only (rolls forward to the
|
|
453
|
+
// next occurrence), and an optional IANA timezone. We then return the real diff.
|
|
454
|
+
//
|
|
455
|
+
// @param {string} resetTime - Reset time string (time-only, date+time, or date+year+time)
|
|
456
|
+
// @param {string|null} timezone - Optional IANA timezone (e.g. "Europe/Berlin")
|
|
457
|
+
// @returns {number} - Milliseconds until reset (never negative)
|
|
458
|
+
export const calculateWaitTime = (resetTime, timezone = null) => {
|
|
459
|
+
const resetDate = parseResetTimeToDate(resetTime, timezone);
|
|
460
|
+
|
|
461
|
+
if (resetDate && resetDate.isValid()) {
|
|
462
|
+
const diffMs = resetDate.valueOf() - Date.now();
|
|
463
|
+
return diffMs > 0 ? diffMs : 0;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Fallback: the robust parser could not interpret the string. Fall back to the
|
|
467
|
+
// legacy time-of-day behavior (today/tomorrow) so we still wait a sensible amount
|
|
468
|
+
// rather than throwing — parseResetTime throws for genuinely unparseable input.
|
|
428
469
|
const { hour, minute } = parseResetTime(resetTime);
|
|
429
470
|
|
|
430
471
|
const now = new Date();
|