@magnusekdahl/parallix 1.2.0 → 1.3.0

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 (127) hide show
  1. package/index.js +190 -185
  2. package/lib/agents/agents.js +737 -838
  3. package/lib/agents/agents.ts +930 -0
  4. package/lib/agents/claude-telemetry.js +149 -154
  5. package/lib/agents/claude-telemetry.ts +233 -0
  6. package/lib/agents/claude.js +121 -131
  7. package/lib/agents/claude.ts +156 -0
  8. package/lib/agents/codex-telemetry.js +176 -164
  9. package/lib/agents/codex-telemetry.ts +205 -0
  10. package/lib/agents/codex.js +168 -201
  11. package/lib/agents/codex.ts +236 -0
  12. package/lib/agents/limit-hit.js +216 -226
  13. package/lib/agents/limit-hit.ts +262 -0
  14. package/lib/agents/mistral-telemetry.js +7 -10
  15. package/lib/agents/mistral-telemetry.ts +44 -0
  16. package/lib/agents/mistral.js +40 -58
  17. package/lib/agents/mistral.ts +84 -0
  18. package/lib/agents/opencode-export.js +126 -127
  19. package/lib/agents/opencode-export.ts +160 -0
  20. package/lib/agents/opencode-telemetry.js +282 -261
  21. package/lib/agents/opencode-telemetry.ts +350 -0
  22. package/lib/agents/opencode.js +263 -305
  23. package/lib/agents/opencode.ts +370 -0
  24. package/lib/agents/stage-telemetry.js +23 -24
  25. package/lib/agents/stage-telemetry.ts +47 -0
  26. package/lib/commands/active.js +502 -579
  27. package/lib/commands/active.ts +665 -0
  28. package/lib/commands/checkpoint.js +98 -74
  29. package/lib/commands/checkpoint.ts +79 -0
  30. package/lib/commands/config.js +61 -31
  31. package/lib/commands/config.ts +47 -0
  32. package/lib/commands/coverage-gate.js +271 -246
  33. package/lib/commands/coverage-gate.ts +362 -0
  34. package/lib/commands/diff.js +140 -119
  35. package/lib/commands/diff.ts +125 -0
  36. package/lib/commands/draft.js +770 -923
  37. package/lib/commands/draft.ts +1021 -0
  38. package/lib/commands/handoff.js +588 -608
  39. package/lib/commands/handoff.ts +694 -0
  40. package/lib/commands/integrate.js +1332 -1463
  41. package/lib/commands/integrate.ts +1688 -0
  42. package/lib/commands/mission-start.js +279 -237
  43. package/lib/commands/mission-start.ts +263 -0
  44. package/lib/commands/rebase.js +606 -582
  45. package/lib/commands/rebase.ts +635 -0
  46. package/lib/commands/repair-handoff.js +181 -179
  47. package/lib/commands/repair-handoff.ts +228 -0
  48. package/lib/commands/resolve-conflict.js +121 -102
  49. package/lib/commands/resolve-conflict.ts +112 -0
  50. package/lib/commands/review.js +3 -4
  51. package/lib/commands/review.ts +14 -0
  52. package/lib/commands/setup-review.js +4 -5
  53. package/lib/commands/setup-review.ts +14 -0
  54. package/lib/commands/setup.js +3 -3
  55. package/lib/commands/setup.ts +2 -0
  56. package/lib/commands/stats-backfill.js +394 -359
  57. package/lib/commands/stats-backfill.ts +418 -0
  58. package/lib/commands/stats.js +1424 -1665
  59. package/lib/commands/stats.ts +2186 -0
  60. package/lib/commands/status.js +219 -200
  61. package/lib/commands/status.ts +219 -0
  62. package/lib/commands/verify.js +3 -1
  63. package/lib/commands/verify.ts +2 -0
  64. package/lib/core/fmt.js +140 -211
  65. package/lib/core/fmt.ts +189 -0
  66. package/lib/core/git.js +121 -102
  67. package/lib/core/git.ts +146 -0
  68. package/lib/core/gitignore.js +59 -106
  69. package/lib/core/gitignore.ts +103 -0
  70. package/lib/core/mission-utils.js +833 -875
  71. package/lib/core/mission-utils.ts +998 -0
  72. package/lib/core/nels.js +112 -127
  73. package/lib/core/nels.ts +189 -0
  74. package/lib/core/persistent-data-migration.js +247 -205
  75. package/lib/core/persistent-data-migration.ts +255 -0
  76. package/lib/core/product-config.js +400 -476
  77. package/lib/core/product-config.ts +518 -0
  78. package/lib/core/runtime-matrix.js +41 -87
  79. package/lib/core/runtime-matrix.ts +84 -0
  80. package/lib/core/spawn-tee.js +152 -192
  81. package/lib/core/spawn-tee.ts +202 -0
  82. package/lib/core/state-map.js +92 -92
  83. package/lib/core/state-map.ts +115 -0
  84. package/lib/core/storage.js +101 -129
  85. package/lib/core/storage.ts +182 -0
  86. package/lib/core/subagent-limit.js +15 -16
  87. package/lib/core/subagent-limit.ts +25 -0
  88. package/lib/core/verification.js +148 -138
  89. package/lib/core/verification.ts +200 -0
  90. package/lib/index.js +142 -59
  91. package/lib/index.ts +134 -0
  92. package/lib/review/rebase.js +142 -145
  93. package/lib/review/rebase.ts +188 -0
  94. package/lib/review/review-adapter.js +99 -87
  95. package/lib/review/review-adapter.ts +198 -0
  96. package/lib/review/review-artifacts.js +486 -629
  97. package/lib/review/review-artifacts.ts +622 -0
  98. package/lib/review/review-commands.js +1220 -1373
  99. package/lib/review/review-commands.ts +1579 -0
  100. package/lib/review/review-events.js +544 -886
  101. package/lib/review/review-events.ts +954 -0
  102. package/lib/review/review-loop.js +945 -1020
  103. package/lib/review/review-loop.ts +1174 -0
  104. package/lib/review/review-polling.js +86 -115
  105. package/lib/review/review-polling.ts +194 -0
  106. package/lib/review/review-prompts.js +154 -189
  107. package/lib/review/review-prompts.ts +182 -0
  108. package/lib/review/review-state.js +246 -225
  109. package/lib/review/review-state.ts +353 -0
  110. package/lib/review/review.js +63 -84
  111. package/lib/review/review.ts +138 -0
  112. package/lib/tools/backlog.js +694 -680
  113. package/lib/tools/backlog.ts +835 -0
  114. package/lib/tools/forgejo.js +1270 -1405
  115. package/lib/tools/forgejo.ts +1793 -0
  116. package/lib/tools/gatekeeper.js +106 -96
  117. package/lib/tools/gatekeeper.ts +124 -0
  118. package/lib/tools/redgreen.js +200 -181
  119. package/lib/tools/redgreen.ts +221 -0
  120. package/lib/tools/sessions.js +58 -52
  121. package/lib/tools/sessions.ts +81 -0
  122. package/lib/tools/setup-review.js +902 -975
  123. package/lib/tools/setup-review.ts +1152 -0
  124. package/package.json +7 -5
  125. package/prompts/draft.md +1 -0
  126. package/prompts/portfolio.md +1 -1
  127. package/px.js +226 -210
@@ -1,269 +1,259 @@
1
1
  'use strict';
2
-
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SIGINT_SHORT_BLOCK_MINUTES = exports.DEFAULT_FALLBACK_HOURS = exports.PATTERN_SETS = void 0;
4
+ exports.detectLimitHit = detectLimitHit;
5
+ exports.parseResetTime = parseResetTime;
6
+ exports.formatBlockUntil = formatBlockUntil;
7
+ exports.ceilToNextHour = ceilToNextHour;
8
+ exports.findLimitHitMatch = findLimitHitMatch;
9
+ exports.clipContext = clipContext;
10
+ exports.parseIsoOffset = parseIsoOffset;
11
+ exports.parseIso = parseIso;
12
+ exports.parseTwelveHour = parseTwelveHour;
13
+ exports.parseTwentyFourHour = parseTwentyFourHour;
14
+ exports.parseRelative = parseRelative;
15
+ exports.parseRetryAfter = parseRetryAfter;
16
+ exports.parseRetryAtDate = parseRetryAtDate;
17
+ exports.projectClockTime = projectClockTime;
18
+ exports.getPatternsForAgent = getPatternsForAgent;
3
19
  const DEFAULT_FALLBACK_HOURS = 1;
20
+ exports.DEFAULT_FALLBACK_HOURS = DEFAULT_FALLBACK_HOURS;
4
21
  const SIGINT_SHORT_BLOCK_MINUTES = 15;
5
-
22
+ exports.SIGINT_SHORT_BLOCK_MINUTES = SIGINT_SHORT_BLOCK_MINUTES;
6
23
  const PATTERN_SETS = Object.freeze({
7
- claude: [
8
- /claude (?:ai )?usage limit reached/i,
9
- /\b5-hour limit (?:reached|exceeded)\b/i,
10
- /you(?:'|')?ve hit your limit(?:\b|$)/i,
11
- /you(?:'|')?ve hit your (?:weekly|daily|monthly|usage) limit/i,
12
- /(?:usage|rate)\s*limit (?:has been )?(?:reached|exceeded)/i,
13
- /\b429\b[^\n]*?\b(?:rate|usage|quota)\b/i
14
- ],
15
- codex: [
16
- /you(?:'|’)?ve hit your (?:weekly|daily|monthly|usage) limit/i,
17
- /\brate[_ ]?limit(?:_| )?(?:exceeded|reached)\b/i,
18
- /\bweekly (?:codex )?usage limit\b/i,
19
- /\b429\b[^\n]*?\b(?:rate|quota|usage)\b/i,
20
- /resource_exhausted/i
21
- ],
22
-
23
- custom: [
24
- /\b(?:rate|usage|quota)\s*limit\s*(?:reached|exceeded)\b/i,
25
- /\b429\b[^\n]*?\b(?:rate|quota|usage)\b/i,
26
- /\b429\b/i,
27
- /\binsufficient[_ ]quota\b/i
28
- ],
29
- mistral: [
30
- /\bmistral (?:ai )?usage limit reached/i,
31
- /\bmistral (?:ai )?quota exceeded/i,
32
- /\bmistral (?:ai )?rate limit(?: reached| exceeded)/i,
33
- /you(?:'|')?ve hit your (?:mistral|vibe) (?:weekly|daily|monthly|usage) limit/i,
34
- /\b429\b[^\n]*?\b(?:rate|quota|usage)\b/i,
35
- /\bvibe\b[^\n]*?\b(?:quota exceeded|rate limit exceeded)\b/i,
36
- /\bmistral\b[^\n]*?\b(?:quota exceeded|rate limit exceeded)\b/i,
37
- /\bresource[_ ]has[_ ]been[_ ]exhausted\b/i,
38
- /\bresource_exhausted\b/i
39
- ]
24
+ claude: [
25
+ /claude (?:ai )?usage limit reached/i,
26
+ /\b5-hour limit (?:reached|exceeded)\b/i,
27
+ /you(?:'|')?ve hit your limit(?:\b|$)/i,
28
+ /you(?:'|')?ve hit your (?:weekly|daily|monthly|usage) limit/i,
29
+ /(?:usage|rate)\s*limit (?:has been )?(?:reached|exceeded)/i,
30
+ /\b429\b[^\n]*?\b(?:rate|usage|quota)\b/i
31
+ ],
32
+ codex: [
33
+ /you(?:'|’)?ve hit your (?:weekly|daily|monthly|usage) limit/i,
34
+ /\brate[_ ]?limit(?:_| )?(?:exceeded|reached)\b/i,
35
+ /\bweekly (?:codex )?usage limit\b/i,
36
+ /\b429\b[^\n]*?\b(?:rate|quota|usage)\b/i,
37
+ /resource_exhausted/i
38
+ ],
39
+ custom: [
40
+ /\b(?:rate|usage|quota)\s*limit\s*(?:reached|exceeded)\b/i,
41
+ /\b429\b[^\n]*?\b(?:rate|quota|usage)\b/i,
42
+ /\b429\b/i,
43
+ /\binsufficient[_ ]quota\b/i
44
+ ],
45
+ mistral: [
46
+ /\bmistral (?:ai )?usage limit reached/i,
47
+ /\bmistral (?:ai )?quota exceeded/i,
48
+ /\bmistral (?:ai )?rate limit(?: reached| exceeded)/i,
49
+ /you(?:'|')?ve hit your (?:mistral|vibe) (?:weekly|daily|monthly|usage) limit/i,
50
+ /\b429\b[^\n]*?\b(?:rate|quota|usage)\b/i,
51
+ /\bvibe\b[^\n]*?\b(?:quota exceeded|rate limit exceeded)\b/i,
52
+ /\bmistral\b[^\n]*?\b(?:quota exceeded|rate limit exceeded)\b/i,
53
+ /\bresource[_ ]has[_ ]been[_ ]exhausted\b/i,
54
+ /\bresource_exhausted\b/i
55
+ ]
40
56
  });
41
-
42
- /** @param {string} agent */
57
+ exports.PATTERN_SETS = PATTERN_SETS;
43
58
  function getPatternsForAgent(agent) {
44
- return PATTERN_SETS[/** @type {keyof typeof PATTERN_SETS} */(agent)] || [];
59
+ return PATTERN_SETS[agent] || [];
45
60
  }
46
-
47
- /** @param {string} agent @param {string} text */
48
61
  function findLimitHitMatch(agent, text) {
49
- const patterns = getPatternsForAgent(agent);
50
- for (const pattern of patterns) {
51
- const match = pattern.exec(text);
52
- if (match) {
53
- return { pattern, index: match.index, length: match[0].length };
62
+ const patterns = getPatternsForAgent(agent);
63
+ for (const pattern of patterns) {
64
+ const match = pattern.exec(text);
65
+ if (match) {
66
+ return { pattern, index: match.index, length: match[0].length };
67
+ }
54
68
  }
55
- }
56
- return null;
69
+ return null;
57
70
  }
58
-
59
- /** @param {string} text @param {number} index @param {number} length */
60
71
  function clipContext(text, index, length) {
61
- const before = Math.max(0, index - 200);
62
- const after = Math.min(text.length, index + length + 200);
63
- return text.slice(before, after);
72
+ const before = Math.max(0, index - 200);
73
+ const after = Math.min(text.length, index + length + 200);
74
+ return text.slice(before, after);
64
75
  }
65
-
66
76
  const ISO_PATTERN = /(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?(?:\.\d+)?(Z|[+-]\d{2}:?\d{2})?/;
67
77
  const TWELVE_HOUR_PATTERN = /\b(\d{1,2})(?::(\d{2}))?\s*(am|pm|AM|PM)\b/;
68
78
  const TWENTY_FOUR_HOUR_PATTERN = /\b([01]?\d|2[0-3]):([0-5]\d)\b/;
69
79
  const RELATIVE_PATTERN = /\bin\s+(\d+)\s+(hour|hours|minute|minutes|second|seconds)\b/i;
70
80
  const RETRY_AFTER_PATTERN = /retry[- ]?after[:\s]+(\d+)\s*(seconds?|s|minutes?|m)?/i;
71
81
  const RETRY_AT_DATE_PATTERN = /(?:retry|reset|try again|reset(?:s)? at)[^\n]{0,40}?(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)/i;
72
-
73
- /** @param {string | undefined} value */
74
82
  function parseIsoOffset(value) {
75
- if (!value) {return null;}
76
- if (value === 'Z') {return 0;}
77
- const m = /([+-])(\d{2}):?(\d{2})/.exec(value);
78
- if (!m) {return null;}
79
- const sign = m[1] === '-' ? -1 : 1;
80
- return sign * (Number(m[2]) * 60 + Number(m[3]));
83
+ if (!value) {
84
+ return null;
85
+ }
86
+ if (value === 'Z') {
87
+ return 0;
88
+ }
89
+ const m = /([+-])(\d{2}):?(\d{2})/.exec(value);
90
+ if (!m) {
91
+ return null;
92
+ }
93
+ const sign = m[1] === '-' ? -1 : 1;
94
+ return sign * (Number(m[2]) * 60 + Number(m[3]));
81
95
  }
82
-
83
- /** @param {string} text */
84
96
  function parseIso(text) {
85
- const m = ISO_PATTERN.exec(text);
86
- if (!m) {return null;}
87
- const [, year, month, day, hour, minute, second, offset] = m;
88
- const offsetMinutes = parseIsoOffset(offset);
89
- if (offsetMinutes === null) {
90
- return new Date(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second || 0), 0);
91
- }
92
- const utcMs = Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second || 0), 0);
93
- return new Date(utcMs - offsetMinutes * 60 * 1000);
97
+ const m = ISO_PATTERN.exec(text);
98
+ if (!m) {
99
+ return null;
100
+ }
101
+ const [, year, month, day, hour, minute, second, offset] = m;
102
+ const offsetMinutes = parseIsoOffset(offset);
103
+ if (offsetMinutes === null) {
104
+ return new Date(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second || 0), 0);
105
+ }
106
+ const utcMs = Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second || 0), 0);
107
+ return new Date(utcMs - offsetMinutes * 60 * 1000);
94
108
  }
95
-
96
- /** @param {string} text @param {Date} now */
97
109
  function parseTwelveHour(text, now) {
98
- const m = TWELVE_HOUR_PATTERN.exec(text);
99
- if (!m) {return null;}
100
- let hour = Number(m[1]);
101
- const minute = Number(m[2] || 0);
102
- const meridiem = m[3].toLowerCase();
103
- if (hour < 1 || hour > 12) {return null;}
104
- if (meridiem === 'pm' && hour !== 12) {hour += 12;}
105
- if (meridiem === 'am' && hour === 12) {hour = 0;}
106
- return projectClockTime(now, hour, minute);
110
+ const m = TWELVE_HOUR_PATTERN.exec(text);
111
+ if (!m) {
112
+ return null;
113
+ }
114
+ let hour = Number(m[1]);
115
+ const minute = Number(m[2] || 0);
116
+ const meridiem = m[3].toLowerCase();
117
+ if (hour < 1 || hour > 12) {
118
+ return null;
119
+ }
120
+ if (meridiem === 'pm' && hour !== 12) {
121
+ hour += 12;
122
+ }
123
+ if (meridiem === 'am' && hour === 12) {
124
+ hour = 0;
125
+ }
126
+ return projectClockTime(now, hour, minute);
107
127
  }
108
-
109
- /** @param {string} text @param {Date} now */
110
128
  function parseTwentyFourHour(text, now) {
111
- const m = TWENTY_FOUR_HOUR_PATTERN.exec(text);
112
- if (!m) {return null;}
113
- const hour = Number(m[1]);
114
- const minute = Number(m[2]);
115
- return projectClockTime(now, hour, minute);
129
+ const m = TWENTY_FOUR_HOUR_PATTERN.exec(text);
130
+ if (!m) {
131
+ return null;
132
+ }
133
+ const hour = Number(m[1]);
134
+ const minute = Number(m[2]);
135
+ return projectClockTime(now, hour, minute);
116
136
  }
117
-
118
- /** @param {string} text @param {Date} now */
119
137
  function parseRelative(text, now) {
120
- const m = RELATIVE_PATTERN.exec(text);
121
- if (!m) {return null;}
122
- const amount = Number(m[1]);
123
- const unit = m[2].toLowerCase();
124
- let ms;
125
- if (unit.startsWith('hour')) {ms = amount * 60 * 60 * 1000;}
126
- else if (unit.startsWith('minute')) {ms = amount * 60 * 1000;}
127
- else {ms = amount * 1000;}
128
- return new Date(now.getTime() + ms);
138
+ const m = RELATIVE_PATTERN.exec(text);
139
+ if (!m) {
140
+ return null;
141
+ }
142
+ const amount = Number(m[1]);
143
+ const unit = m[2].toLowerCase();
144
+ let ms;
145
+ if (unit.startsWith('hour')) {
146
+ ms = amount * 60 * 60 * 1000;
147
+ }
148
+ else if (unit.startsWith('minute')) {
149
+ ms = amount * 60 * 1000;
150
+ }
151
+ else {
152
+ ms = amount * 1000;
153
+ }
154
+ return new Date(now.getTime() + ms);
129
155
  }
130
-
131
- /** @param {string} text @param {Date} now */
132
156
  function parseRetryAfter(text, now) {
133
- const m = RETRY_AFTER_PATTERN.exec(text);
134
- if (!m) {return null;}
135
- const amount = Number(m[1]);
136
- const unit = (m[2] || 's').toLowerCase();
137
- let ms;
138
- if (unit.startsWith('m') && unit !== 's') {ms = amount * 60 * 1000;}
139
- else {ms = amount * 1000;}
140
- return new Date(now.getTime() + ms);
157
+ const m = RETRY_AFTER_PATTERN.exec(text);
158
+ if (!m) {
159
+ return null;
160
+ }
161
+ const amount = Number(m[1]);
162
+ const unit = (m[2] || 's').toLowerCase();
163
+ let ms;
164
+ if (unit.startsWith('m') && unit !== 's') {
165
+ ms = amount * 60 * 1000;
166
+ }
167
+ else {
168
+ ms = amount * 1000;
169
+ }
170
+ return new Date(now.getTime() + ms);
141
171
  }
142
-
143
- /** @param {string} text */
144
172
  function parseRetryAtDate(text) {
145
- const m = RETRY_AT_DATE_PATTERN.exec(text);
146
- if (!m) {return null;}
147
- return parseIso(m[1]);
173
+ const m = RETRY_AT_DATE_PATTERN.exec(text);
174
+ if (!m) {
175
+ return null;
176
+ }
177
+ return parseIso(m[1]);
148
178
  }
149
-
150
- /** @param {Date} now @param {number} hour @param {number} minute */
151
179
  function projectClockTime(now, hour, minute) {
152
- const candidate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute, 0, 0);
153
- if (candidate.getTime() <= now.getTime()) {
154
- candidate.setDate(candidate.getDate() + 1);
155
- }
156
- return candidate;
180
+ const candidate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute, 0, 0);
181
+ if (candidate.getTime() <= now.getTime()) {
182
+ candidate.setDate(candidate.getDate() + 1);
183
+ }
184
+ return candidate;
157
185
  }
158
-
159
- /** @param {string} text @param {Date} [now] */
160
186
  function parseResetTime(text, now = new Date()) {
161
- if (!text) {return null;}
162
- return (
163
- parseRetryAtDate(text) ||
164
- parseIso(text) ||
165
- parseTwelveHour(text, now) ||
166
- parseTwentyFourHour(text, now) ||
167
- parseRelative(text, now) ||
168
- parseRetryAfter(text, now)
169
- );
187
+ if (!text) {
188
+ return null;
189
+ }
190
+ return (parseRetryAtDate(text) ||
191
+ parseIso(text) ||
192
+ parseTwelveHour(text, now) ||
193
+ parseTwentyFourHour(text, now) ||
194
+ parseRelative(text, now) ||
195
+ parseRetryAfter(text, now));
170
196
  }
171
-
172
- /** @param {Date} date */
173
197
  function formatBlockUntil(date) {
174
- const year = date.getFullYear();
175
- const month = String(date.getMonth() + 1).padStart(2, '0');
176
- const day = String(date.getDate()).padStart(2, '0');
177
- const hour = String(date.getHours()).padStart(2, '0');
178
- return `${year}-${month}-${day} ${hour}`;
198
+ const year = date.getFullYear();
199
+ const month = String(date.getMonth() + 1).padStart(2, '0');
200
+ const day = String(date.getDate()).padStart(2, '0');
201
+ const hour = String(date.getHours()).padStart(2, '0');
202
+ return `${year}-${month}-${day} ${hour}`;
179
203
  }
180
-
181
- /** @param {Date} date */
182
204
  function ceilToNextHour(date) {
183
- const ceil = new Date(date.getTime());
184
- if (ceil.getMinutes() === 0 && ceil.getSeconds() === 0 && ceil.getMilliseconds() === 0) {
205
+ const ceil = new Date(date.getTime());
206
+ if (ceil.getMinutes() === 0 && ceil.getSeconds() === 0 && ceil.getMilliseconds() === 0) {
207
+ return ceil;
208
+ }
209
+ ceil.setMinutes(0, 0, 0);
210
+ ceil.setHours(ceil.getHours() + 1);
185
211
  return ceil;
186
- }
187
- ceil.setMinutes(0, 0, 0);
188
- ceil.setHours(ceil.getHours() + 1);
189
- return ceil;
190
212
  }
191
-
192
- /**
193
- * @param {{ agent: string, stdout?: string, stderr?: string, status?: number, signal?: string | null, error?: { code?: string } | null, now?: Date }} [opts]
194
- */
195
- function detectLimitHit({
196
- agent,
197
- stdout = '',
198
- stderr = '',
199
- status,
200
- signal,
201
- error,
202
- now = new Date()
203
- } = /** @type {{ agent: string, stdout?: string, stderr?: string, status?: number, signal?: string | null, error?: { code?: string } | null, now?: Date }} */({})) {
204
- if (!agent) {return null;}
205
- // Gate detection on a failed launcher invocation. A successful child (exit 0,
206
- // no signal, no spawn error) means any limit-hit phrases in the transcript
207
- // are quoted text — typically when the agent itself reviewed code, tests, or
208
- // logs containing those strings. Treating that as a real limit hit would
209
- // wrongly block a healthy agent in agents.local.json.
210
- // `status === undefined` means the caller did not pass exit metadata; preserve
211
- // the legacy behavior so callers that haven't been updated still get detection.
212
- const launcherFailed =
213
- error !== null && error !== undefined ||
214
- signal !== null && signal !== undefined ||
215
- (typeof status === 'number' && status !== 0) ||
216
- typeof status === 'undefined';
217
- if (!launcherFailed) {return null;}
218
-
219
- const combined = `${stdout || ''}\n${stderr || ''}`;
220
- const match = findLimitHitMatch(agent, combined);
221
-
222
- let target;
223
- let source;
224
-
225
- if (match) {
226
- const context = clipContext(combined, match.index, match.length);
227
- const parsed = parseResetTime(context, now);
228
-
229
- if (parsed && !Number.isNaN(parsed.getTime()) && parsed.getTime() > now.getTime()) {
230
- target = parsed;
231
- source = 'parsed';
232
- } else {
233
- target = new Date(now.getTime() + DEFAULT_FALLBACK_HOURS * 60 * 60 * 1000);
234
- source = 'fallback';
213
+ function detectLimitHit({ agent, stdout = '', stderr = '', status, signal, error, now = new Date() } = {}) {
214
+ if (!agent) {
215
+ return null;
235
216
  }
236
- } else if (signal !== null && signal !== undefined) {
237
- // Agent was killed by a signal (e.g. SIGINT/Ctrl-C) but no limit-hit
238
- // pattern matched. Apply a short-term block so the next invocation
239
- // selects a different agent instead of re-picking the same one.
240
- target = new Date(now.getTime() + SIGINT_SHORT_BLOCK_MINUTES * 60 * 1000);
241
- source = 'sigint';
242
- } else {
243
- return null;
244
- }
245
-
246
- const ceiled = ceilToNextHour(target);
247
- return { until: formatBlockUntil(ceiled), source };
217
+ // Gate detection on a failed launcher invocation. A successful child (exit 0,
218
+ // no signal, no spawn error) means any limit-hit phrases in the transcript
219
+ // are quoted text — typically when the agent itself reviewed code, tests, or
220
+ // logs containing those strings. Treating that as a real limit hit would
221
+ // wrongly block a healthy agent in agents.local.json.
222
+ // `status === undefined` means the caller did not pass exit metadata; preserve
223
+ // the legacy behavior so callers that haven't been updated still get detection.
224
+ const launcherFailed = error !== null && error !== undefined ||
225
+ signal !== null && signal !== undefined ||
226
+ (typeof status === 'number' && status !== 0) ||
227
+ typeof status === 'undefined';
228
+ if (!launcherFailed) {
229
+ return null;
230
+ }
231
+ const combined = `${stdout || ''}\n${stderr || ''}`;
232
+ const match = findLimitHitMatch(agent, combined);
233
+ let target;
234
+ let source;
235
+ if (match) {
236
+ const context = clipContext(combined, match.index, match.length);
237
+ const parsed = parseResetTime(context, now);
238
+ if (parsed && !Number.isNaN(parsed.getTime()) && parsed.getTime() > now.getTime()) {
239
+ target = parsed;
240
+ source = 'parsed';
241
+ }
242
+ else {
243
+ target = new Date(now.getTime() + DEFAULT_FALLBACK_HOURS * 60 * 60 * 1000);
244
+ source = 'fallback';
245
+ }
246
+ }
247
+ else if (signal !== null && signal !== undefined) {
248
+ // Agent was killed by a signal (e.g. SIGINT/Ctrl-C) but no limit-hit
249
+ // pattern matched. Apply a short-term block so the next invocation
250
+ // selects a different agent instead of re-picking the same one.
251
+ target = new Date(now.getTime() + SIGINT_SHORT_BLOCK_MINUTES * 60 * 1000);
252
+ source = 'sigint';
253
+ }
254
+ else {
255
+ return null;
256
+ }
257
+ const ceiled = ceilToNextHour(target);
258
+ return { until: formatBlockUntil(ceiled), source };
248
259
  }
249
-
250
- module.exports = {
251
- detectLimitHit,
252
- parseResetTime,
253
- formatBlockUntil,
254
- ceilToNextHour,
255
- findLimitHitMatch,
256
- clipContext,
257
- parseIsoOffset,
258
- parseIso,
259
- parseTwelveHour,
260
- parseTwentyFourHour,
261
- parseRelative,
262
- parseRetryAfter,
263
- parseRetryAtDate,
264
- projectClockTime,
265
- getPatternsForAgent,
266
- PATTERN_SETS,
267
- DEFAULT_FALLBACK_HOURS,
268
- SIGINT_SHORT_BLOCK_MINUTES
269
- };