@alecsibilia/luca 14.0.0-alpha.30 → 14.0.0-alpha.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 +8 -0
- package/board/package.json +1 -1
- package/board/server/board-vocabulary-engine.ts +15 -0
- package/board/server/board-vocabulary.ts +2 -0
- package/board/server/describe-record.ts +6 -1
- package/board/server/journal-rebuild-helpers.ts +6 -1
- package/board/server/luca-version.ts +1 -1
- package/board/server/reduce-board-create.ts +1 -0
- package/board/server/reduce-board-final-review.ts +3 -2
- package/board/server/reduce-board-needs-you.ts +108 -36
- package/board/server/reduce-board-update.ts +42 -0
- package/board/shared/board-state.ts +7 -0
- package/engine/cli/config-reload-text.ts +3 -1
- package/engine/config/config-fields.ts +10 -0
- package/engine/config/config-reload.ts +18 -0
- package/engine/config/engine-config-schema.ts +7 -0
- package/engine/config/engine-config.ts +1 -0
- package/engine/core/auto-retry.ts +206 -0
- package/engine/core/decide-build-view.ts +7 -0
- package/engine/core/decide-run-budget.ts +6 -1
- package/engine/core/decide-stuck.ts +22 -1
- package/engine/core/execute-action.ts +1 -0
- package/engine/core/execute-build.ts +1 -0
- package/engine/core/execute-stuck.ts +21 -1
- package/engine/core/execute.ts +7 -1
- package/engine/core/run-ended.ts +88 -0
- package/engine/journal/final-review-replay.ts +6 -0
- package/engine/journal/final-review-state.ts +3 -0
- package/engine/journal/journal-entry-schema.ts +4 -0
- package/engine/journal/journal-kinds.ts +2 -0
- package/engine/journal/journal-record-schema.ts +4 -0
- package/engine/journal/journal-record.ts +2 -0
- package/engine/journal/replay.ts +6 -0
- package/engine/journal/reply-replay.ts +44 -0
- package/engine/journal/run-entries.ts +30 -0
- package/engine/journal/run-state.ts +6 -0
- package/engine/journal/stuck-entries.ts +24 -0
- package/engine/journal/ticket-progress.ts +7 -0
- package/engine/journal/ticket-replay.ts +7 -1
- package/engine/journal/ticket-result-replay.ts +13 -3
- package/engine/journal/ticket-retry-replay.ts +1 -0
- package/engine/testing/auto-retry-fixtures.ts +226 -0
- package/engine/testing/run-fixtures.ts +6 -0
- package/engine/tracker/tracker.ts +9 -3
- package/package.json +1 -1
- package/skills/luca-retro/SKILL.md +1 -1
- package/skills/luca-unstick/scripts/stuck-lines.ts +234 -0
- package/skills/luca-unstick/scripts/stuck-records.ts +494 -0
- package/skills/luca-unstick/scripts/stuck-summary.ts +49 -564
- package/skills/luca-watch/SKILL.md +131 -0
- package/skills/luca-watch/scripts/wait-for-change.ts +378 -0
package/CHANGELOG.md
CHANGED
package/board/package.json
CHANGED
|
@@ -75,6 +75,21 @@ export const ENGINE_VOCABULARY = {
|
|
|
75
75
|
reason: z.string(),
|
|
76
76
|
detail: z.string().catch(''),
|
|
77
77
|
}),
|
|
78
|
+
/**
|
|
79
|
+
* The engine told the owner a ticket (`ticket` set), the run, or the
|
|
80
|
+
* final review is stuck (#705): its Needs-you item shows from here.
|
|
81
|
+
*/
|
|
82
|
+
stuck_reported: z.looseObject({}),
|
|
83
|
+
/**
|
|
84
|
+
* The engine retried a stuck ticket, the final review, or the run by
|
|
85
|
+
* itself (#705): `item` is `ticket`, `final_review`, or `run`, and
|
|
86
|
+
* `body` its comment on the spec issue, word for word.
|
|
87
|
+
*/
|
|
88
|
+
auto_retried: z.looseObject({
|
|
89
|
+
item: z.string(),
|
|
90
|
+
reason: z.string().catch(''),
|
|
91
|
+
body: z.string().catch(''),
|
|
92
|
+
}),
|
|
78
93
|
pull_request_opened: z.looseObject({
|
|
79
94
|
number: z.number().int(),
|
|
80
95
|
url: z.string(),
|
|
@@ -65,6 +65,8 @@ const BoardEntrySchema = z.discriminatedUnion('kind', [
|
|
|
65
65
|
entry({ kind: 'run_branch_pushed' }),
|
|
66
66
|
entry({ kind: 'ticket_stuck' }),
|
|
67
67
|
entry({ kind: 'run_stuck' }),
|
|
68
|
+
entry({ kind: 'stuck_reported' }),
|
|
69
|
+
entry({ kind: 'auto_retried' }),
|
|
68
70
|
entry({ kind: 'pull_request_opened' }),
|
|
69
71
|
entry({ kind: 'worktrees_removed' }),
|
|
70
72
|
entry({ kind: 'jev_asked' }),
|
|
@@ -278,6 +278,9 @@ export const describeRecord = ({
|
|
|
278
278
|
text: `The run is stuck. ${runReasonText({ reason: record.content.reason })}`,
|
|
279
279
|
tone: 'danger',
|
|
280
280
|
})
|
|
281
|
+
// The engine's own retry (#705): its comment on the spec issue.
|
|
282
|
+
case 'auto_retried':
|
|
283
|
+
return event({ text: record.content.body, tone: 'info' })
|
|
281
284
|
case 'pull_request_opened':
|
|
282
285
|
return event({
|
|
283
286
|
text: `Pull request #${record.content.number} opened: ${record.content.url}`,
|
|
@@ -377,11 +380,13 @@ export const describeRecord = ({
|
|
|
377
380
|
tone: 'info',
|
|
378
381
|
})
|
|
379
382
|
// The repo wait shows as a banner on the panel (#632), the network
|
|
380
|
-
// wait as the run's status (#701)
|
|
383
|
+
// wait as the run's status (#701), and a stuck report as the stuck
|
|
384
|
+
// row it brings (#705).
|
|
381
385
|
case 'repo_wait_started':
|
|
382
386
|
case 'repo_wait_ended':
|
|
383
387
|
case 'network_wait_started':
|
|
384
388
|
case 'network_wait_ended':
|
|
389
|
+
case 'stuck_reported':
|
|
385
390
|
case 'intake_read':
|
|
386
391
|
case 'ticket_snapshot':
|
|
387
392
|
case 'baseline_tests':
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
pullRequestOpened,
|
|
10
10
|
runStarted,
|
|
11
11
|
stamp,
|
|
12
|
+
stuckReported,
|
|
12
13
|
ticketStuck,
|
|
13
14
|
ticketWorktreeCreated,
|
|
14
15
|
wholeTicket,
|
|
@@ -133,11 +134,15 @@ export const finishedRun = (): Entry[] => [
|
|
|
133
134
|
}),
|
|
134
135
|
]
|
|
135
136
|
|
|
136
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* Spec 10 with ticket 13 stuck, told to the owner (so it needs you, #705),
|
|
139
|
+
* and nothing else running.
|
|
140
|
+
*/
|
|
137
141
|
export const stuckRun = (): Entry[] => [
|
|
138
142
|
...intakeOfThree(),
|
|
139
143
|
ticketWorktreeCreated({ ticket: 13 }),
|
|
140
144
|
ticketStuck({ ticket: 13, reason: 'red_check_failed', detail: 'x' }),
|
|
145
|
+
stuckReported({ ticket: 13 }),
|
|
141
146
|
]
|
|
142
147
|
|
|
143
148
|
export const nothingToDoRun = (): Entry[] => [
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The Luca version of this board, stamped when the package was packed. */
|
|
2
|
-
export const LUCA_VERSION: string = "14.0.0-alpha.
|
|
2
|
+
export const LUCA_VERSION: string = "14.0.0-alpha.32"
|
|
@@ -7,7 +7,7 @@ import { finalReasonText } from './reduce-board-reasons'
|
|
|
7
7
|
import { clip } from './reduce-board-text'
|
|
8
8
|
import {
|
|
9
9
|
DETAIL_MAX,
|
|
10
|
-
|
|
10
|
+
addUnreported,
|
|
11
11
|
resolveNeedsYou,
|
|
12
12
|
updateFinal,
|
|
13
13
|
withTried,
|
|
@@ -125,8 +125,9 @@ export const applyFinalReviewKind = ({
|
|
|
125
125
|
}),
|
|
126
126
|
}),
|
|
127
127
|
})
|
|
128
|
+
// Needs you once the engine tells the owner (#705).
|
|
128
129
|
case 'final_review_stuck':
|
|
129
|
-
return
|
|
130
|
+
return addUnreported({
|
|
130
131
|
state: updateFinal({
|
|
131
132
|
state,
|
|
132
133
|
update: (review) => ({ ...review, state: 'stuck' }),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What needs a person: a stuck ticket or run
|
|
3
|
-
* and the replies
|
|
2
|
+
* What needs a person: a stuck ticket or run (once the engine told the
|
|
3
|
+
* owner, #705), a skipped or retried ticket, and the replies and the
|
|
4
|
+
* engine's own retries that resolve them.
|
|
4
5
|
*/
|
|
5
6
|
import type { BoardRecord } from './board-vocabulary'
|
|
6
7
|
import {
|
|
@@ -12,6 +13,8 @@ import { clip } from './reduce-board-text'
|
|
|
12
13
|
import {
|
|
13
14
|
DETAIL_MAX,
|
|
14
15
|
addNeedsYou,
|
|
16
|
+
addUnreported,
|
|
17
|
+
reportNeedsYou,
|
|
15
18
|
resolveNeedsYou,
|
|
16
19
|
updateFinal,
|
|
17
20
|
updateTicket,
|
|
@@ -126,6 +129,55 @@ const ticketRetried = ({
|
|
|
126
129
|
}
|
|
127
130
|
}
|
|
128
131
|
|
|
132
|
+
/**
|
|
133
|
+
* A retry of a ticket, the run, or the final review, the owner's or the
|
|
134
|
+
* engine's own (#705): it carries on, and its item is resolved.
|
|
135
|
+
*/
|
|
136
|
+
const retriedTicket = ({
|
|
137
|
+
state,
|
|
138
|
+
ticket,
|
|
139
|
+
}: {
|
|
140
|
+
state: BoardState
|
|
141
|
+
ticket: number
|
|
142
|
+
}): BoardState =>
|
|
143
|
+
resolveNeedsYou({
|
|
144
|
+
state: updateTicket({
|
|
145
|
+
state,
|
|
146
|
+
number: ticket,
|
|
147
|
+
update: (card) => ({
|
|
148
|
+
...card,
|
|
149
|
+
stage: 'building',
|
|
150
|
+
activity: 'retrying',
|
|
151
|
+
}),
|
|
152
|
+
}),
|
|
153
|
+
key: ticketKey({ ticket }),
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
const retriedRun = ({
|
|
157
|
+
state,
|
|
158
|
+
top_up,
|
|
159
|
+
}: {
|
|
160
|
+
state: BoardState
|
|
161
|
+
top_up: number
|
|
162
|
+
}): BoardState =>
|
|
163
|
+
resolveNeedsYou({
|
|
164
|
+
state: {
|
|
165
|
+
...state,
|
|
166
|
+
run_budget_tokens: state.run_budget_tokens + top_up,
|
|
167
|
+
run_stuck_reason: null,
|
|
168
|
+
},
|
|
169
|
+
key: RUN_KEY,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
const retriedFinal = ({ state }: { state: BoardState }): BoardState =>
|
|
173
|
+
resolveNeedsYou({
|
|
174
|
+
state: updateFinal({
|
|
175
|
+
state,
|
|
176
|
+
update: (review) => ({ ...review, state: 'reviewing' }),
|
|
177
|
+
}),
|
|
178
|
+
key: FINAL_KEY,
|
|
179
|
+
})
|
|
180
|
+
|
|
129
181
|
const replyReceived = ({
|
|
130
182
|
state,
|
|
131
183
|
record,
|
|
@@ -137,45 +189,21 @@ const replyReceived = ({
|
|
|
137
189
|
const ticket = record.content.ticket ?? record.ticket
|
|
138
190
|
switch (word) {
|
|
139
191
|
case 'retry':
|
|
140
|
-
if (ticket !== null) {
|
|
141
|
-
return resolveNeedsYou({
|
|
142
|
-
state: updateTicket({
|
|
143
|
-
state,
|
|
144
|
-
number: ticket,
|
|
145
|
-
update: (card) => ({
|
|
146
|
-
...card,
|
|
147
|
-
stage: 'building',
|
|
148
|
-
activity: 'retrying',
|
|
149
|
-
}),
|
|
150
|
-
}),
|
|
151
|
-
key: ticketKey({ ticket }),
|
|
152
|
-
})
|
|
153
|
-
}
|
|
192
|
+
if (ticket !== null) return retriedTicket({ state, ticket })
|
|
154
193
|
// A bare `retry` while the run is stuck on its budget adds its
|
|
155
194
|
// top-up (one whole budget for a retry journaled with none,
|
|
156
195
|
// #543); stuck for another reason, no budget. The final review
|
|
157
196
|
// is left as it is.
|
|
158
197
|
if (state.needs_you.some((item) => item.key === RUN_KEY)) {
|
|
159
|
-
|
|
160
|
-
state
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
...state,
|
|
166
|
-
run_budget_tokens: state.run_budget_tokens + top_up,
|
|
167
|
-
run_stuck_reason: null,
|
|
168
|
-
},
|
|
169
|
-
key: RUN_KEY,
|
|
198
|
+
return retriedRun({
|
|
199
|
+
state,
|
|
200
|
+
top_up:
|
|
201
|
+
state.run_stuck_reason === 'run_budget'
|
|
202
|
+
? (record.content.top_up ?? state.run_budget_each)
|
|
203
|
+
: 0,
|
|
170
204
|
})
|
|
171
205
|
}
|
|
172
|
-
return
|
|
173
|
-
state: updateFinal({
|
|
174
|
-
state,
|
|
175
|
-
update: (review) => ({ ...review, state: 'reviewing' }),
|
|
176
|
-
}),
|
|
177
|
-
key: FINAL_KEY,
|
|
178
|
-
})
|
|
206
|
+
return retriedFinal({ state })
|
|
179
207
|
case 'skip':
|
|
180
208
|
return skipTicket({ state, ticket, because: null })
|
|
181
209
|
case 'ship':
|
|
@@ -191,10 +219,39 @@ const replyReceived = ({
|
|
|
191
219
|
}
|
|
192
220
|
}
|
|
193
221
|
|
|
222
|
+
/**
|
|
223
|
+
* The engine retried a stuck item by itself (#705): nothing to reply to,
|
|
224
|
+
* so its item is resolved, told or not, and it carries on as after the
|
|
225
|
+
* owner's `retry`.
|
|
226
|
+
*/
|
|
227
|
+
const autoRetried = ({
|
|
228
|
+
state,
|
|
229
|
+
record,
|
|
230
|
+
}: {
|
|
231
|
+
state: BoardState
|
|
232
|
+
record: Extract<BoardRecord, { kind: 'auto_retried' }>
|
|
233
|
+
}): BoardState => {
|
|
234
|
+
const { ticket } = record
|
|
235
|
+
switch (record.content.item) {
|
|
236
|
+
case 'ticket':
|
|
237
|
+
return ticket === null ? state : retriedTicket({ state, ticket })
|
|
238
|
+
// The engine retries the run only after `pull_request_failed`: no
|
|
239
|
+
// budget to add.
|
|
240
|
+
case 'run':
|
|
241
|
+
return retriedRun({ state, top_up: 0 })
|
|
242
|
+
case 'final_review':
|
|
243
|
+
return retriedFinal({ state })
|
|
244
|
+
default:
|
|
245
|
+
return state
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
194
249
|
/** The record kinds of what needs a person. */
|
|
195
250
|
export const NEEDS_YOU_KINDS = [
|
|
196
251
|
'ticket_stuck',
|
|
197
252
|
'run_stuck',
|
|
253
|
+
'stuck_reported',
|
|
254
|
+
'auto_retried',
|
|
198
255
|
'reply_received',
|
|
199
256
|
'ticket_skipped',
|
|
200
257
|
'ticket_retried',
|
|
@@ -229,7 +286,8 @@ export const applyNeedsYouKind = ({
|
|
|
229
286
|
activity: 'stuck',
|
|
230
287
|
}),
|
|
231
288
|
})
|
|
232
|
-
|
|
289
|
+
// Needs you once the engine tells the owner (#705).
|
|
290
|
+
return addUnreported({
|
|
233
291
|
state: stuck,
|
|
234
292
|
item: {
|
|
235
293
|
key: ticketKey({ ticket }),
|
|
@@ -250,7 +308,7 @@ export const applyNeedsYouKind = ({
|
|
|
250
308
|
})
|
|
251
309
|
}
|
|
252
310
|
case 'run_stuck':
|
|
253
|
-
return
|
|
311
|
+
return addUnreported({
|
|
254
312
|
state: { ...state, run_stuck_reason: record.content.reason },
|
|
255
313
|
item: {
|
|
256
314
|
key: RUN_KEY,
|
|
@@ -266,6 +324,20 @@ export const applyNeedsYouKind = ({
|
|
|
266
324
|
since: record.time,
|
|
267
325
|
},
|
|
268
326
|
})
|
|
327
|
+
// A report with no ticket is the run's while it waits to be told,
|
|
328
|
+
// else the final review's.
|
|
329
|
+
case 'stuck_reported':
|
|
330
|
+
return reportNeedsYou({
|
|
331
|
+
state,
|
|
332
|
+
key:
|
|
333
|
+
ticket !== null
|
|
334
|
+
? ticketKey({ ticket })
|
|
335
|
+
: state.unreported.some((item) => item.key === RUN_KEY)
|
|
336
|
+
? RUN_KEY
|
|
337
|
+
: FINAL_KEY,
|
|
338
|
+
})
|
|
339
|
+
case 'auto_retried':
|
|
340
|
+
return autoRetried({ state, record })
|
|
269
341
|
case 'reply_received':
|
|
270
342
|
return replyReceived({ state, record })
|
|
271
343
|
case 'ticket_skipped':
|
|
@@ -77,6 +77,44 @@ export const addNeedsYou = ({
|
|
|
77
77
|
],
|
|
78
78
|
})
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* A stuck item the engine hasn't told the owner of yet (#705): it waits in
|
|
82
|
+
* `unreported` until `stuck_reported` (`reportNeedsYou`).
|
|
83
|
+
*/
|
|
84
|
+
export const addUnreported = ({
|
|
85
|
+
state,
|
|
86
|
+
item,
|
|
87
|
+
}: {
|
|
88
|
+
state: BoardState
|
|
89
|
+
item: NeedsYou
|
|
90
|
+
}): BoardState => ({
|
|
91
|
+
...state,
|
|
92
|
+
unreported: [
|
|
93
|
+
...state.unreported.filter((entry) => entry.key !== item.key),
|
|
94
|
+
item,
|
|
95
|
+
],
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
/** The engine told the owner: the item `key` waiting to be told needs you now. */
|
|
99
|
+
export const reportNeedsYou = ({
|
|
100
|
+
state,
|
|
101
|
+
key,
|
|
102
|
+
}: {
|
|
103
|
+
state: BoardState
|
|
104
|
+
key: string
|
|
105
|
+
}): BoardState => {
|
|
106
|
+
const item = state.unreported.find((entry) => entry.key === key)
|
|
107
|
+
if (item === undefined) return state
|
|
108
|
+
return addNeedsYou({
|
|
109
|
+
state: {
|
|
110
|
+
...state,
|
|
111
|
+
unreported: state.unreported.filter((entry) => entry.key !== key),
|
|
112
|
+
},
|
|
113
|
+
item,
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Resolves item `key`, told or not yet told, or every item for `null`. */
|
|
80
118
|
export const resolveNeedsYou = ({
|
|
81
119
|
state,
|
|
82
120
|
key,
|
|
@@ -89,6 +127,10 @@ export const resolveNeedsYou = ({
|
|
|
89
127
|
key === null
|
|
90
128
|
? []
|
|
91
129
|
: state.needs_you.filter((entry) => entry.key !== key),
|
|
130
|
+
unreported:
|
|
131
|
+
key === null
|
|
132
|
+
? []
|
|
133
|
+
: state.unreported.filter((entry) => entry.key !== key),
|
|
92
134
|
})
|
|
93
135
|
|
|
94
136
|
/** The final review with one more "tried" line. */
|
|
@@ -144,6 +144,13 @@ export const BoardStateSchema = z.object({
|
|
|
144
144
|
/** How much of the plan the whole run used, per window. */
|
|
145
145
|
run_plan_used: z.array(PlanUsedSchema),
|
|
146
146
|
needs_you: z.array(NeedsYouSchema),
|
|
147
|
+
/**
|
|
148
|
+
* A stuck ticket's or run's item before the engine told the owner
|
|
149
|
+
* (#705): it moves to `needs_you` on the engine's `stuck_reported`, and
|
|
150
|
+
* is dropped when the engine retries the item by itself. Defaulted, so
|
|
151
|
+
* a board state from before it still parses.
|
|
152
|
+
*/
|
|
153
|
+
unreported: z.array(NeedsYouSchema).default([]),
|
|
147
154
|
tickets: z.array(TicketCardSchema),
|
|
148
155
|
final_review: FinalReviewSchema,
|
|
149
156
|
jev: JevCountsSchema,
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
import type { JournalRecord } from '../journal/journal-record'
|
|
6
6
|
|
|
7
7
|
/** A value of a reloaded config field in a log line; `null` is "none". */
|
|
8
|
-
const fieldValue = (
|
|
8
|
+
const fieldValue = (
|
|
9
|
+
value: string | number | boolean | string[] | null
|
|
10
|
+
): string => {
|
|
9
11
|
if (value === null) return 'none'
|
|
10
12
|
if (Array.isArray(value)) return `[${value.join(', ')}]`
|
|
11
13
|
return String(value)
|
|
@@ -94,6 +94,16 @@ export const decisionModelOf = ({
|
|
|
94
94
|
export const modelsOf = ({ config }: { config: EngineConfig }): ModelsConfig =>
|
|
95
95
|
config.models ?? ModelsConfigSchema.parse({})
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Whether the engine retries a routine stuck item once by itself (#705):
|
|
99
|
+
* the config's `auto_retry`, else `true`.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* autoRetryOf({ config: { ...config, auto_retry: false } }) // false
|
|
103
|
+
*/
|
|
104
|
+
export const autoRetryOf = ({ config }: { config: EngineConfig }): boolean =>
|
|
105
|
+
config.auto_retry ?? true
|
|
106
|
+
|
|
97
107
|
/**
|
|
98
108
|
* The feature map's path (#583), in the form a worktree-relative path takes
|
|
99
109
|
* everywhere else (`normalize`, no leading `./`), or `null` with no map.
|
|
@@ -3,6 +3,7 @@ import omit from 'lodash/omit'
|
|
|
3
3
|
import reduce from 'lodash/reduce'
|
|
4
4
|
import { z } from 'zod'
|
|
5
5
|
|
|
6
|
+
import { autoRetryOf } from './config-fields'
|
|
6
7
|
import type { EngineConfig } from './engine-config'
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -28,6 +29,9 @@ import type { EngineConfig } from './engine-config'
|
|
|
28
29
|
* - `leftover_allow`: more new markdown the leftover scan lets through
|
|
29
30
|
* (#520). Each scan reads it then, so a run stuck on a leftover can be
|
|
30
31
|
* unstuck by allowing the file and resuming.
|
|
32
|
+
* - `auto_retry`: whether the engine retries a routine stuck item once by
|
|
33
|
+
* itself (#705). Only an item stuck after the resume is retried, so
|
|
34
|
+
* turning it on leaves an item already told to the owner waiting.
|
|
31
35
|
*/
|
|
32
36
|
export const RELOADED_FIELDS = [
|
|
33
37
|
'prepare',
|
|
@@ -37,6 +41,7 @@ export const RELOADED_FIELDS = [
|
|
|
37
41
|
'test_timeout_ms',
|
|
38
42
|
'test_case_timeout_ms',
|
|
39
43
|
'leftover_allow',
|
|
44
|
+
'auto_retry',
|
|
40
45
|
] as const
|
|
41
46
|
|
|
42
47
|
/**
|
|
@@ -130,6 +135,12 @@ export const ConfigChangeSchema = z.discriminatedUnion('field', [
|
|
|
130
135
|
from: patterns,
|
|
131
136
|
to: patterns,
|
|
132
137
|
}),
|
|
138
|
+
// Never left out: a config without it reads as `true`.
|
|
139
|
+
z.object({
|
|
140
|
+
field: z.literal('auto_retry'),
|
|
141
|
+
from: z.boolean(),
|
|
142
|
+
to: z.boolean(),
|
|
143
|
+
}),
|
|
133
144
|
])
|
|
134
145
|
|
|
135
146
|
export type ConfigChange = z.infer<typeof ConfigChangeSchema>
|
|
@@ -186,6 +197,11 @@ export const configChanges = ({
|
|
|
186
197
|
from: current.leftover_allow ?? null,
|
|
187
198
|
to: next.leftover_allow ?? null,
|
|
188
199
|
},
|
|
200
|
+
{
|
|
201
|
+
field: 'auto_retry',
|
|
202
|
+
from: autoRetryOf({ config: current }),
|
|
203
|
+
to: autoRetryOf({ config: next }),
|
|
204
|
+
},
|
|
189
205
|
]
|
|
190
206
|
return all.filter(({ from, to }) => !isEqual(from, to))
|
|
191
207
|
}
|
|
@@ -226,6 +242,8 @@ const applyChange = ({
|
|
|
226
242
|
return change.to === null
|
|
227
243
|
? omit(config, 'leftover_allow')
|
|
228
244
|
: { ...config, leftover_allow: change.to }
|
|
245
|
+
case 'auto_retry':
|
|
246
|
+
return { ...config, auto_retry: change.to }
|
|
229
247
|
}
|
|
230
248
|
}
|
|
231
249
|
|
|
@@ -344,6 +344,13 @@ export const EngineConfigSchema = z.object({
|
|
|
344
344
|
* (`DEFAULT_RUN_BUDGET_TOKENS`).
|
|
345
345
|
*/
|
|
346
346
|
run_budget_tokens: z.number().int().positive().optional(),
|
|
347
|
+
/**
|
|
348
|
+
* Whether the engine retries a routine stuck item once by itself
|
|
349
|
+
* (#705), with a comment on the spec issue, before it asks the owner.
|
|
350
|
+
* Left out, `true`; a config written before it may lack it, and reads
|
|
351
|
+
* as `true` too (`autoRetryOf`).
|
|
352
|
+
*/
|
|
353
|
+
auto_retry: z.boolean().default(true).optional(),
|
|
347
354
|
})
|
|
348
355
|
|
|
349
356
|
export type EngineConfig = z.infer<typeof EngineConfigSchema>
|