@link-assistant/hive-mind 1.74.8 → 1.74.9

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.74.9
4
+
5
+ ### Patch Changes
6
+
7
+ - c4070e1: Fix incorrect usage-limit reset time for `--tool codex`. Codex reports weekly limits as a full calendar date (e.g. "try again at Jun 11th, 2026 12:27 AM"), but the reset-time parser dropped the month/day/year and kept only the time, making a multi-day weekly reset look like a same-day 5-hour reset. This both mis-informed users and made auto-resume fire far too early. `extractResetTime` now parses ordinal days and explicit years (keyword-independent), `parseResetTime` honors an explicit year, and Codex now traces the raw limit message and parsed reset under verbose mode.
8
+ </content>
9
+
3
10
  ## 1.74.8
4
11
 
5
12
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.74.8",
3
+ "version": "1.74.9",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
package/src/codex.lib.mjs CHANGED
@@ -1009,9 +1009,15 @@ export const executeCodexCommand = async params => {
1009
1009
  await log(`⚠️ Ignoring non-fatal Codex item error event(s): ${ignoredMessages}`, { level: 'warning', verbose: true });
1010
1010
  }
1011
1011
  if (codexErrorSummary.hasError) {
1012
- const limitInfo = detectUsageLimit(codexErrorSummary.message || lastMessage);
1013
- const retryableError = classifyRetryableError(codexErrorSummary.message || lastMessage);
1012
+ const limitSource = codexErrorSummary.message || lastMessage;
1013
+ const limitInfo = detectUsageLimit(limitSource);
1014
+ const retryableError = classifyRetryableError(limitSource);
1014
1015
  if (limitInfo.isUsageLimit) {
1016
+ // Issue #1869: Trace the raw limit text and what we parsed out of it so
1017
+ // a mis-parsed reset (e.g. a weekly reset read as a 5-hour reset) can be
1018
+ // diagnosed from the log without guessing at the original message.
1019
+ await log(`🔍 Codex usage limit detected. Raw message: ${JSON.stringify(limitSource)}`, { verbose: true });
1020
+ await log(`🔍 Parsed reset time: ${JSON.stringify(limitInfo.resetTime)}, timezone: ${JSON.stringify(limitInfo.timezone)}`, { verbose: true });
1015
1021
  limitReached = true;
1016
1022
  limitResetTime = limitInfo.resetTime;
1017
1023
 
@@ -1098,6 +1104,9 @@ export const executeCodexCommand = async params => {
1098
1104
  // Check for usage limit errors first (more specific)
1099
1105
  const limitInfo = detectUsageLimit(lastMessage);
1100
1106
  if (limitInfo.isUsageLimit) {
1107
+ // Issue #1869: Trace raw limit text + parsed reset for diagnosability.
1108
+ await log(`🔍 Codex usage limit detected (exit ${exitCode}). Raw message: ${JSON.stringify(lastMessage)}`, { verbose: true });
1109
+ await log(`🔍 Parsed reset time: ${JSON.stringify(limitInfo.resetTime)}, timezone: ${JSON.stringify(limitInfo.timezone)}`, { verbose: true });
1101
1110
  limitReached = true;
1102
1111
  limitResetTime = limitInfo.resetTime;
1103
1112
 
@@ -121,19 +121,33 @@ export function extractResetTime(message) {
121
121
  // Normalize whitespace for easier matching
122
122
  const normalized = message.replace(/\s+/g, ' ');
123
123
 
124
- // Pattern 0: Weekly limit with date - "resets Jan 15, 8am" or "resets January 15, 8:00am"
125
- // This pattern must come first to avoid partial matches by time-only patterns
124
+ // Pattern 0: Weekly/long-window limit with an explicit calendar date.
125
+ // This pattern must come FIRST so a date+time is never truncated to a bare
126
+ // time by the time-only patterns below (Issue #1869): Codex reports weekly
127
+ // limits as "try again at Jun 11th, 2026 12:27 AM" — keeping only "12:27 AM"
128
+ // makes a 2-days-out weekly reset look like a same-day 5-hour reset, which
129
+ // both mis-informs the user and triggers a far-too-early auto-resume.
130
+ //
131
+ // Handled shapes (keyword prefix like "resets"/"try again at" is optional —
132
+ // the month+day+time structure is specific enough on its own):
133
+ // - "resets Jan 15, 8am" (no year, Claude weekly)
134
+ // - "resets January 20, 10:30am"
135
+ // - "try again at Jun 11th, 2026 12:27 AM" (ordinal day + year, Codex weekly)
126
136
  const monthPattern = '(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)';
127
- const resetsWithDateRegex = new RegExp(`resets\\s+(${monthPattern})\\s+(\\d{1,2}),?\\s+([0-9]{1,2})(?::([0-9]{2}))?\\s*([ap]m)`, 'i');
128
- const resetsWithDate = normalized.match(resetsWithDateRegex);
129
- if (resetsWithDate) {
130
- const month = resetsWithDate[1];
131
- const day = resetsWithDate[2];
132
- const hour = resetsWithDate[3];
133
- const minute = resetsWithDate[4] || '00';
134
- const ampm = resetsWithDate[5].toUpperCase();
135
- // Return formatted date+time string for weekly limits
136
- return `${month} ${day}, ${hour}:${minute} ${ampm}`;
137
+ // (month) (day)[ordinal] [, year] [,] (hour)[:minute] (am|pm)
138
+ const dateWithTimeRegex = new RegExp(`(${monthPattern})\\s+(\\d{1,2})(?:st|nd|rd|th)?(?:,?\\s+(\\d{4}))?,?\\s+([0-9]{1,2})(?::([0-9]{2}))?\\s*([ap]m)`, 'i');
139
+ const dateWithTime = normalized.match(dateWithTimeRegex);
140
+ if (dateWithTime) {
141
+ const month = dateWithTime[1];
142
+ const day = dateWithTime[2];
143
+ const year = dateWithTime[3]; // optional
144
+ const hour = dateWithTime[4];
145
+ const minute = dateWithTime[5] || '00';
146
+ const ampm = dateWithTime[6].toUpperCase();
147
+ // Return formatted date+time string for weekly limits, preserving the year
148
+ // when present so the reset is anchored to the correct day rather than
149
+ // being assumed to be "this year / next occurrence".
150
+ return year ? `${month} ${day}, ${year}, ${hour}:${minute} ${ampm}` : `${month} ${day}, ${hour}:${minute} ${ampm}`;
137
151
  }
138
152
 
139
153
  // Pattern 1: "try again at 12:16 PM"
@@ -244,8 +258,10 @@ export function parseResetTime(timeStr, tz = null) {
244
258
  const normalized = timeStr.replace(/\bSept\b/gi, 'Sep');
245
259
 
246
260
  // Try date+time formats using dayjs custom parse
247
- // dayjs uses: MMM=Jan, MMMM=January, D=day, h=12-hour, mm=minutes, A=AM/PM
248
- const dateTimeFormats = ['MMM D, h:mm A', 'MMMM D, h:mm A'];
261
+ // dayjs uses: MMM=Jan, MMMM=January, D=day, YYYY=year, h=12-hour, mm=minutes, A=AM/PM
262
+ // Year-bearing formats are tried first so an explicit year (Codex weekly
263
+ // limits, Issue #1869) is honored instead of being dropped to the current year.
264
+ const dateTimeFormats = ['MMM D, YYYY, h:mm A', 'MMMM D, YYYY, h:mm A', 'MMM D, h:mm A', 'MMMM D, h:mm A'];
249
265
 
250
266
  for (const format of dateTimeFormats) {
251
267
  let parsed;
@@ -261,9 +277,11 @@ export function parseResetTime(timeStr, tz = null) {
261
277
  }
262
278
 
263
279
  if (parsed.isValid()) {
264
- // dayjs parses without year, so it defaults to current year
265
- // If the date is in the past, assume next year
266
- if (parsed.isBefore(now)) {
280
+ // When the format omits the year, dayjs defaults to the current year, so a
281
+ // past date should roll forward to next year. When an explicit year is
282
+ // present (Issue #1869), trust it verbatim and never bump it.
283
+ const hasExplicitYear = format.includes('YYYY');
284
+ if (!hasExplicitYear && parsed.isBefore(now)) {
267
285
  parsed = parsed.add(1, 'year');
268
286
  }
269
287
  return parsed;