@plainconceptsplatform/workflows 0.28.13 → 0.28.15
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/loops/actions/housekeeping/action.yml +52 -0
- package/loops/workflows/agent-apply-review.md +9 -6
- package/loops/workflows/agent-audit.md +2 -2
- package/loops/workflows/agent-implement.md +8 -5
- package/loops/workflows/agent-merge-gate.md +10 -6
- package/loops/workflows/agent-refine.md +15 -8
- package/loops/workflows/agent-triage.md +7 -5
- package/loops/workflows/agent-visual-verify.md +2 -1
- package/loops/workflows/work-router.yml +2 -0
- package/package.json +4 -4
|
@@ -66,6 +66,26 @@ runs:
|
|
|
66
66
|
const age = (iso) => Date.now() - new Date(iso).getTime();
|
|
67
67
|
const days = (ms) => Math.floor(ms / 86400000);
|
|
68
68
|
|
|
69
|
+
// 0. Cancel workflow runs stuck in "pending" for over an hour. The ARC fleet is two
|
|
70
|
+
// VMs, and when it saturates, jobs sit pending indefinitely — GitHub has no queue-time
|
|
71
|
+
// timeout. A stuck pending implement or refine run holds a fleet slot that the retry
|
|
72
|
+
// logic below can never free because the run never reaches a terminal state. This
|
|
73
|
+
// cancel runs on a GitHub-hosted runner, so it works even when the fleet is full.
|
|
74
|
+
const stuckCutoff = Date.now() - 3600 * 1000; // 1 hour ago
|
|
75
|
+
let cancelledRuns = 0;
|
|
76
|
+
for (const run of await github.paginate(github.rest.actions.listWorkflowRuns, {
|
|
77
|
+
owner: context.repo.owner, repo: context.repo.repo, workflow_id: 'work-router.yml',
|
|
78
|
+
status: 'pending', per_page: 50,
|
|
79
|
+
})) {
|
|
80
|
+
if (new Date(run.created_at).getTime() > stuckCutoff) continue;
|
|
81
|
+
await act(`cancelling stuck pending run #${run.id} (queued ${new Date(run.created_at).toISOString()})`, async () => {
|
|
82
|
+
await github.rest.actions.cancelWorkflowRun({
|
|
83
|
+
owner: context.repo.owner, repo: context.repo.repo, run_id: run.id,
|
|
84
|
+
});
|
|
85
|
+
cancelledRuns += 1;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
69
89
|
const issues = await github.paginate(github.rest.issues.listForRepo, {
|
|
70
90
|
...context.repo, state: 'open', per_page: 100,
|
|
71
91
|
});
|
|
@@ -74,6 +94,37 @@ runs:
|
|
|
74
94
|
});
|
|
75
95
|
const names = (issue) => issue.labels.map((l) => (typeof l === 'string' ? l : l.name));
|
|
76
96
|
|
|
97
|
+
// 0b. Clean orphaned bot-working labels. When a workflow run is cancelled (above) or
|
|
98
|
+
// dies without clearing bot-working, the issue sits with bot-working + a work label
|
|
99
|
+
// forever: no stalled marker, so the retry logic below never touches it. Find issues
|
|
100
|
+
// with bot-working, check whether the most recent run for the linked operation is
|
|
101
|
+
// still active, and if not, park the issue for retry.
|
|
102
|
+
for (const issue of issues) {
|
|
103
|
+
if (issue.pull_request) continue;
|
|
104
|
+
const labels = names(issue);
|
|
105
|
+
if (!labels.includes('bot-working')) continue;
|
|
106
|
+
// Don't touch issues that already have stalled or review — those are handled below.
|
|
107
|
+
if (labels.includes('stalled') || labels.includes('review')) continue;
|
|
108
|
+
const work = WORK_LABELS.find((l) => labels.includes(l));
|
|
109
|
+
if (!work) continue;
|
|
110
|
+
|
|
111
|
+
// An issue with bot-working should have a recent or active workflow run.
|
|
112
|
+
// If bot-working has been on for longer than the retry window, the run is dead.
|
|
113
|
+
const waited = age(issue.updated_at);
|
|
114
|
+
if (waited < retryAfterMs) continue;
|
|
115
|
+
|
|
116
|
+
await act(`#${issue.number}: releasing orphaned bot-working (parked ${Math.floor(waited / 3600000)}h with no active run)`, async () => {
|
|
117
|
+
// Park it for retry: add stalled + review, remove bot-working.
|
|
118
|
+
// The retry logic below will pick it up on the next sweep.
|
|
119
|
+
await github.rest.issues.addLabels({
|
|
120
|
+
...context.repo, issue_number: issue.number,
|
|
121
|
+
labels: ['stalled', 'review'],
|
|
122
|
+
});
|
|
123
|
+
await github.rest.issues.removeLabel({ ...context.repo, issue_number: issue.number, name: 'bot-working' })
|
|
124
|
+
.catch((error) => { if (error.status !== 404) throw error; });
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
77
128
|
const needsHuman = [];
|
|
78
129
|
// Gate telemetry. Every disposition the gate has ever posted is already on a pull
|
|
79
130
|
// request, marked, so the rate costs one extra listing rather than a store.
|
|
@@ -337,6 +388,7 @@ runs:
|
|
|
337
388
|
await core.summary
|
|
338
389
|
.addHeading('Housekeeping', 2)
|
|
339
390
|
.addList([
|
|
391
|
+
`Cancelled stuck pending runs: ${cancelledRuns}`,
|
|
340
392
|
`Retried: ${retried}`,
|
|
341
393
|
`Waiting on a person: ${needsHuman.length}`,
|
|
342
394
|
`Stalled, retrying by itself: ${retrying.length}`,
|
|
@@ -80,7 +80,8 @@ jobs:
|
|
|
80
80
|
# `reserve` thirteen minutes later and refined a closed issue to completion. Read now, once,
|
|
81
81
|
# and gate both the reservation and the agent on it.
|
|
82
82
|
still_open:
|
|
83
|
-
runs-on:
|
|
83
|
+
runs-on: RunnerLandingZone
|
|
84
|
+
timeout-minutes: 5
|
|
84
85
|
permissions:
|
|
85
86
|
contents: read
|
|
86
87
|
issues: read
|
|
@@ -98,7 +99,8 @@ jobs:
|
|
|
98
99
|
token: ${{ github.token }}
|
|
99
100
|
issue-number: ${{ inputs.issue-number }}
|
|
100
101
|
subject:
|
|
101
|
-
runs-on:
|
|
102
|
+
runs-on: RunnerLandingZone
|
|
103
|
+
timeout-minutes: 5
|
|
102
104
|
permissions:
|
|
103
105
|
contents: read
|
|
104
106
|
issues: read
|
|
@@ -167,7 +169,8 @@ jobs:
|
|
|
167
169
|
reserve:
|
|
168
170
|
needs: [subject, still_open]
|
|
169
171
|
if: needs.subject.outputs.found == 'true' && needs.subject.outputs.issue != '' && needs.still_open.outputs.open == 'true'
|
|
170
|
-
runs-on:
|
|
172
|
+
runs-on: RunnerLandingZone
|
|
173
|
+
timeout-minutes: 5
|
|
171
174
|
permissions:
|
|
172
175
|
contents: read
|
|
173
176
|
issues: write
|
|
@@ -199,7 +202,7 @@ jobs:
|
|
|
199
202
|
validate_output:
|
|
200
203
|
needs: [activation, subject, agent, safe_outputs]
|
|
201
204
|
if: always() && needs.agent.result == 'success' && needs.safe_outputs.result == 'success'
|
|
202
|
-
runs-on:
|
|
205
|
+
runs-on: RunnerLandingZone
|
|
203
206
|
permissions:
|
|
204
207
|
contents: read
|
|
205
208
|
pull-requests: read
|
|
@@ -242,7 +245,7 @@ jobs:
|
|
|
242
245
|
needs.agent.result == 'success' &&
|
|
243
246
|
needs.safe_outputs.result == 'success' &&
|
|
244
247
|
needs.validate_output.outputs.valid == 'true'
|
|
245
|
-
runs-on:
|
|
248
|
+
runs-on: RunnerLandingZone
|
|
246
249
|
permissions:
|
|
247
250
|
contents: write
|
|
248
251
|
issues: write
|
|
@@ -298,7 +301,7 @@ jobs:
|
|
|
298
301
|
always() &&
|
|
299
302
|
needs.subject.outputs.found == 'true' &&
|
|
300
303
|
(needs.agent.result != 'success' || needs.safe_outputs.result != 'success' || needs.validate_output.outputs.valid != 'true')
|
|
301
|
-
runs-on:
|
|
304
|
+
runs-on: RunnerLandingZone
|
|
302
305
|
permissions:
|
|
303
306
|
contents: read
|
|
304
307
|
issues: write
|
|
@@ -89,7 +89,7 @@ jobs:
|
|
|
89
89
|
needs.agent.result == 'success' &&
|
|
90
90
|
needs.safe_outputs.result == 'success' &&
|
|
91
91
|
needs.safe_outputs.outputs.process_safe_outputs_processed_count != '0'
|
|
92
|
-
runs-on:
|
|
92
|
+
runs-on: RunnerLandingZone
|
|
93
93
|
permissions:
|
|
94
94
|
contents: read
|
|
95
95
|
issues: write
|
|
@@ -143,7 +143,7 @@ jobs:
|
|
|
143
143
|
if: >
|
|
144
144
|
needs.agent.result == 'success' &&
|
|
145
145
|
needs.safe_outputs.outputs.process_safe_outputs_processed_count == '0'
|
|
146
|
-
runs-on:
|
|
146
|
+
runs-on: RunnerLandingZone
|
|
147
147
|
permissions:
|
|
148
148
|
contents: read
|
|
149
149
|
steps:
|
|
@@ -109,7 +109,8 @@ jobs:
|
|
|
109
109
|
# `reserve` thirteen minutes later and refined a closed issue to completion. Read now, once,
|
|
110
110
|
# and gate both the reservation and the agent on it.
|
|
111
111
|
still_open:
|
|
112
|
-
runs-on:
|
|
112
|
+
runs-on: RunnerLandingZone
|
|
113
|
+
timeout-minutes: 5
|
|
113
114
|
permissions:
|
|
114
115
|
contents: read
|
|
115
116
|
issues: read
|
|
@@ -127,7 +128,8 @@ jobs:
|
|
|
127
128
|
token: ${{ github.token }}
|
|
128
129
|
issue-number: ${{ inputs.issue-number }}
|
|
129
130
|
eligibility:
|
|
130
|
-
runs-on:
|
|
131
|
+
runs-on: RunnerLandingZone
|
|
132
|
+
timeout-minutes: 5
|
|
131
133
|
permissions:
|
|
132
134
|
issues: read
|
|
133
135
|
outputs:
|
|
@@ -174,7 +176,8 @@ jobs:
|
|
|
174
176
|
reserve:
|
|
175
177
|
needs: [eligibility, still_open]
|
|
176
178
|
if: needs.eligibility.outputs.eligible == 'true' && needs.still_open.outputs.open == 'true'
|
|
177
|
-
runs-on:
|
|
179
|
+
runs-on: RunnerLandingZone
|
|
180
|
+
timeout-minutes: 5
|
|
178
181
|
permissions:
|
|
179
182
|
contents: read
|
|
180
183
|
issues: write
|
|
@@ -214,7 +217,7 @@ jobs:
|
|
|
214
217
|
always() &&
|
|
215
218
|
needs.agent.result == 'success' &&
|
|
216
219
|
needs.safe_outputs.result == 'success'
|
|
217
|
-
runs-on:
|
|
220
|
+
runs-on: RunnerLandingZone
|
|
218
221
|
permissions:
|
|
219
222
|
contents: read
|
|
220
223
|
issues: write
|
|
@@ -430,7 +433,7 @@ jobs:
|
|
|
430
433
|
needs.agent.result != 'success' ||
|
|
431
434
|
needs.safe_outputs.result != 'success'
|
|
432
435
|
)
|
|
433
|
-
runs-on:
|
|
436
|
+
runs-on: RunnerLandingZone
|
|
434
437
|
permissions:
|
|
435
438
|
contents: read
|
|
436
439
|
issues: write
|
|
@@ -140,7 +140,8 @@ on:
|
|
|
140
140
|
# values and `on.steps` outputs do not reach the agent job.
|
|
141
141
|
jobs:
|
|
142
142
|
subject:
|
|
143
|
-
runs-on:
|
|
143
|
+
runs-on: RunnerLandingZone
|
|
144
|
+
timeout-minutes: 5
|
|
144
145
|
permissions:
|
|
145
146
|
contents: read
|
|
146
147
|
issues: read
|
|
@@ -195,7 +196,8 @@ jobs:
|
|
|
195
196
|
protected_changes:
|
|
196
197
|
needs: subject
|
|
197
198
|
if: needs.subject.outputs.found == 'true'
|
|
198
|
-
runs-on:
|
|
199
|
+
runs-on: RunnerLandingZone
|
|
200
|
+
timeout-minutes: 5
|
|
199
201
|
permissions:
|
|
200
202
|
contents: read
|
|
201
203
|
pull-requests: read
|
|
@@ -241,7 +243,8 @@ jobs:
|
|
|
241
243
|
# gh-aw makes the agent depend on custom jobs. Keep this job successful when
|
|
242
244
|
# there are no protected files instead of skipping it and blocking remediation.
|
|
243
245
|
if: always() && needs.subject.outputs.found == 'true'
|
|
244
|
-
runs-on:
|
|
246
|
+
runs-on: RunnerLandingZone
|
|
247
|
+
timeout-minutes: 5
|
|
245
248
|
permissions:
|
|
246
249
|
contents: read
|
|
247
250
|
issues: write
|
|
@@ -300,7 +303,8 @@ jobs:
|
|
|
300
303
|
reserve:
|
|
301
304
|
needs: subject
|
|
302
305
|
if: needs.subject.outputs.found == 'true'
|
|
303
|
-
runs-on:
|
|
306
|
+
runs-on: RunnerLandingZone
|
|
307
|
+
timeout-minutes: 5
|
|
304
308
|
permissions:
|
|
305
309
|
contents: read
|
|
306
310
|
issues: write
|
|
@@ -349,7 +353,7 @@ jobs:
|
|
|
349
353
|
always() &&
|
|
350
354
|
needs.agent.result == 'success' &&
|
|
351
355
|
needs.safe_outputs.result == 'success'
|
|
352
|
-
runs-on:
|
|
356
|
+
runs-on: RunnerLandingZone
|
|
353
357
|
permissions:
|
|
354
358
|
contents: read
|
|
355
359
|
outputs:
|
|
@@ -577,7 +581,7 @@ jobs:
|
|
|
577
581
|
needs.safe_outputs.result != 'success' ||
|
|
578
582
|
needs.validate_output.outputs.valid != 'true'
|
|
579
583
|
)
|
|
580
|
-
runs-on:
|
|
584
|
+
runs-on: RunnerLandingZone
|
|
581
585
|
permissions:
|
|
582
586
|
contents: read
|
|
583
587
|
issues: write
|
|
@@ -116,7 +116,8 @@ jobs:
|
|
|
116
116
|
# `reserve` thirteen minutes later and refined a closed issue to completion. Read now, once,
|
|
117
117
|
# and gate both the reservation and the agent on it.
|
|
118
118
|
still_open:
|
|
119
|
-
runs-on:
|
|
119
|
+
runs-on: RunnerLandingZone
|
|
120
|
+
timeout-minutes: 5
|
|
120
121
|
permissions:
|
|
121
122
|
contents: read
|
|
122
123
|
issues: read
|
|
@@ -140,7 +141,7 @@ jobs:
|
|
|
140
141
|
eligibility:
|
|
141
142
|
needs: [still_open]
|
|
142
143
|
if: needs.still_open.outputs.open == 'true'
|
|
143
|
-
runs-on:
|
|
144
|
+
runs-on: RunnerLandingZone
|
|
144
145
|
permissions:
|
|
145
146
|
issues: read
|
|
146
147
|
outputs:
|
|
@@ -170,7 +171,7 @@ jobs:
|
|
|
170
171
|
size_guard:
|
|
171
172
|
needs: [still_open, eligibility]
|
|
172
173
|
if: needs.still_open.outputs.open == 'true' && needs.eligibility.outputs.eligible == 'true'
|
|
173
|
-
runs-on:
|
|
174
|
+
runs-on: RunnerLandingZone
|
|
174
175
|
timeout-minutes: 5
|
|
175
176
|
permissions:
|
|
176
177
|
contents: read
|
|
@@ -213,7 +214,7 @@ jobs:
|
|
|
213
214
|
refuse_big_issue:
|
|
214
215
|
needs: [still_open, size_guard, eligibility]
|
|
215
216
|
if: needs.still_open.outputs.open == 'true' && needs.eligibility.outputs.eligible == 'true' && needs.size_guard.outputs.too_big == 'true'
|
|
216
|
-
runs-on:
|
|
217
|
+
runs-on: RunnerLandingZone
|
|
217
218
|
timeout-minutes: 5
|
|
218
219
|
permissions:
|
|
219
220
|
contents: read
|
|
@@ -266,7 +267,8 @@ jobs:
|
|
|
266
267
|
reserve:
|
|
267
268
|
needs: [still_open, size_guard, eligibility]
|
|
268
269
|
if: needs.still_open.outputs.open == 'true' && needs.eligibility.outputs.eligible == 'true' && needs.size_guard.outputs.too_big != 'true'
|
|
269
|
-
runs-on:
|
|
270
|
+
runs-on: RunnerLandingZone
|
|
271
|
+
timeout-minutes: 5
|
|
270
272
|
permissions:
|
|
271
273
|
contents: read
|
|
272
274
|
issues: write
|
|
@@ -306,7 +308,7 @@ jobs:
|
|
|
306
308
|
always() &&
|
|
307
309
|
needs.agent.result == 'success' &&
|
|
308
310
|
needs.safe_outputs.result == 'success'
|
|
309
|
-
runs-on:
|
|
311
|
+
runs-on: RunnerLandingZone
|
|
310
312
|
permissions:
|
|
311
313
|
contents: read
|
|
312
314
|
outputs:
|
|
@@ -343,7 +345,7 @@ jobs:
|
|
|
343
345
|
needs.agent.result == 'success' &&
|
|
344
346
|
needs.safe_outputs.result == 'success' &&
|
|
345
347
|
needs.validate_output.outputs.valid == 'true'
|
|
346
|
-
runs-on:
|
|
348
|
+
runs-on: RunnerLandingZone
|
|
347
349
|
permissions:
|
|
348
350
|
contents: read
|
|
349
351
|
issues: write
|
|
@@ -530,7 +532,7 @@ jobs:
|
|
|
530
532
|
needs.safe_outputs.result != 'success' ||
|
|
531
533
|
needs.validate_output.outputs.valid != 'true'
|
|
532
534
|
)
|
|
533
|
-
runs-on:
|
|
535
|
+
runs-on: RunnerLandingZone
|
|
534
536
|
permissions:
|
|
535
537
|
contents: read
|
|
536
538
|
issues: write
|
|
@@ -692,6 +694,11 @@ safe-outputs:
|
|
|
692
694
|
# under noise nobody closes.
|
|
693
695
|
report-failure-as-issue: false
|
|
694
696
|
threat-detection: false
|
|
697
|
+
# Stage the comment so it is only published after validate_output confirms the
|
|
698
|
+
# refined story is valid. Without staging, the agent's add_comment ("refinement
|
|
699
|
+
# complete") lands before validation, and a malformed output leaves the issue
|
|
700
|
+
# with a success comment and an incomplete status simultaneously (Pliny-Bot #383).
|
|
701
|
+
staged: true
|
|
695
702
|
# The refined story replaces the issue body, which is one call carrying the whole
|
|
696
703
|
# thing. The allowance is three rather than one because a single malformed call the
|
|
697
704
|
# bridge accepts as spent would otherwise end the run with the body unwritten and a
|
|
@@ -102,7 +102,8 @@ jobs:
|
|
|
102
102
|
# `reserve` thirteen minutes later and refined a closed issue to completion. Read now, once,
|
|
103
103
|
# and gate both the reservation and the agent on it.
|
|
104
104
|
still_open:
|
|
105
|
-
runs-on:
|
|
105
|
+
runs-on: RunnerLandingZone
|
|
106
|
+
timeout-minutes: 5
|
|
106
107
|
permissions:
|
|
107
108
|
contents: read
|
|
108
109
|
issues: read
|
|
@@ -122,7 +123,8 @@ jobs:
|
|
|
122
123
|
reserve:
|
|
123
124
|
needs: [still_open]
|
|
124
125
|
if: needs.still_open.outputs.open == 'true'
|
|
125
|
-
runs-on:
|
|
126
|
+
runs-on: RunnerLandingZone
|
|
127
|
+
timeout-minutes: 5
|
|
126
128
|
permissions:
|
|
127
129
|
contents: read
|
|
128
130
|
issues: write
|
|
@@ -168,7 +170,7 @@ jobs:
|
|
|
168
170
|
always() &&
|
|
169
171
|
needs.agent.result == 'success' &&
|
|
170
172
|
needs.safe_outputs.result == 'success'
|
|
171
|
-
runs-on:
|
|
173
|
+
runs-on: RunnerLandingZone
|
|
172
174
|
permissions:
|
|
173
175
|
contents: read
|
|
174
176
|
outputs:
|
|
@@ -196,7 +198,7 @@ jobs:
|
|
|
196
198
|
needs.agent.result == 'success' &&
|
|
197
199
|
needs.safe_outputs.result == 'success' &&
|
|
198
200
|
needs.validate_output.outputs.valid == 'true'
|
|
199
|
-
runs-on:
|
|
201
|
+
runs-on: RunnerLandingZone
|
|
200
202
|
permissions:
|
|
201
203
|
contents: read
|
|
202
204
|
issues: write
|
|
@@ -308,7 +310,7 @@ jobs:
|
|
|
308
310
|
needs.safe_outputs.result != 'success' ||
|
|
309
311
|
needs.validate_output.outputs.valid != 'true'
|
|
310
312
|
)
|
|
311
|
-
runs-on:
|
|
313
|
+
runs-on: RunnerLandingZone
|
|
312
314
|
permissions:
|
|
313
315
|
contents: read
|
|
314
316
|
issues: write
|
|
@@ -1204,6 +1204,8 @@ jobs:
|
|
|
1204
1204
|
contents: write
|
|
1205
1205
|
issues: write
|
|
1206
1206
|
pull-requests: read
|
|
1207
|
+
# actions: write cancels workflow runs stuck pending in the fleet queue.
|
|
1208
|
+
actions: write
|
|
1207
1209
|
env:
|
|
1208
1210
|
BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
|
|
1209
1211
|
BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plainconceptsplatform/workflows",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.15",
|
|
4
4
|
"description": "Install and update Platform GitHub agentic workflows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github-actions",
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
],
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/PlainConceptsPlatform/
|
|
12
|
+
"url": "git+https://github.com/PlainConceptsPlatform/agentic-workflows.git"
|
|
13
13
|
},
|
|
14
14
|
"bugs": {
|
|
15
|
-
"url": "https://github.com/PlainConceptsPlatform/
|
|
15
|
+
"url": "https://github.com/PlainConceptsPlatform/agentic-workflows/issues"
|
|
16
16
|
},
|
|
17
|
-
"homepage": "https://github.com/PlainConceptsPlatform/
|
|
17
|
+
"homepage": "https://github.com/PlainConceptsPlatform/agentic-workflows#readme",
|
|
18
18
|
"license": "UNLICENSED",
|
|
19
19
|
"type": "module",
|
|
20
20
|
"bin": {
|