@plainconceptsplatform/workflows 0.28.13 → 0.28.14
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 +3 -0
- package/loops/workflows/agent-implement.md +3 -0
- package/loops/workflows/agent-merge-gate.md +4 -0
- package/loops/workflows/agent-refine.md +7 -0
- package/loops/workflows/agent-triage.md +2 -0
- package/loops/workflows/agent-visual-verify.md +1 -0
- 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}`,
|
|
@@ -81,6 +81,7 @@ jobs:
|
|
|
81
81
|
# and gate both the reservation and the agent on it.
|
|
82
82
|
still_open:
|
|
83
83
|
runs-on: agents-arc
|
|
84
|
+
timeout-minutes: 5
|
|
84
85
|
permissions:
|
|
85
86
|
contents: read
|
|
86
87
|
issues: read
|
|
@@ -99,6 +100,7 @@ jobs:
|
|
|
99
100
|
issue-number: ${{ inputs.issue-number }}
|
|
100
101
|
subject:
|
|
101
102
|
runs-on: agents-arc
|
|
103
|
+
timeout-minutes: 5
|
|
102
104
|
permissions:
|
|
103
105
|
contents: read
|
|
104
106
|
issues: read
|
|
@@ -168,6 +170,7 @@ jobs:
|
|
|
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
172
|
runs-on: agents-arc
|
|
173
|
+
timeout-minutes: 5
|
|
171
174
|
permissions:
|
|
172
175
|
contents: read
|
|
173
176
|
issues: write
|
|
@@ -110,6 +110,7 @@ jobs:
|
|
|
110
110
|
# and gate both the reservation and the agent on it.
|
|
111
111
|
still_open:
|
|
112
112
|
runs-on: agents-arc
|
|
113
|
+
timeout-minutes: 5
|
|
113
114
|
permissions:
|
|
114
115
|
contents: read
|
|
115
116
|
issues: read
|
|
@@ -128,6 +129,7 @@ jobs:
|
|
|
128
129
|
issue-number: ${{ inputs.issue-number }}
|
|
129
130
|
eligibility:
|
|
130
131
|
runs-on: agents-arc
|
|
132
|
+
timeout-minutes: 5
|
|
131
133
|
permissions:
|
|
132
134
|
issues: read
|
|
133
135
|
outputs:
|
|
@@ -175,6 +177,7 @@ jobs:
|
|
|
175
177
|
needs: [eligibility, still_open]
|
|
176
178
|
if: needs.eligibility.outputs.eligible == 'true' && needs.still_open.outputs.open == 'true'
|
|
177
179
|
runs-on: agents-arc
|
|
180
|
+
timeout-minutes: 5
|
|
178
181
|
permissions:
|
|
179
182
|
contents: read
|
|
180
183
|
issues: write
|
|
@@ -141,6 +141,7 @@ on:
|
|
|
141
141
|
jobs:
|
|
142
142
|
subject:
|
|
143
143
|
runs-on: agents-arc
|
|
144
|
+
timeout-minutes: 5
|
|
144
145
|
permissions:
|
|
145
146
|
contents: read
|
|
146
147
|
issues: read
|
|
@@ -196,6 +197,7 @@ jobs:
|
|
|
196
197
|
needs: subject
|
|
197
198
|
if: needs.subject.outputs.found == 'true'
|
|
198
199
|
runs-on: agents-arc
|
|
200
|
+
timeout-minutes: 5
|
|
199
201
|
permissions:
|
|
200
202
|
contents: read
|
|
201
203
|
pull-requests: read
|
|
@@ -242,6 +244,7 @@ jobs:
|
|
|
242
244
|
# there are no protected files instead of skipping it and blocking remediation.
|
|
243
245
|
if: always() && needs.subject.outputs.found == 'true'
|
|
244
246
|
runs-on: agents-arc
|
|
247
|
+
timeout-minutes: 5
|
|
245
248
|
permissions:
|
|
246
249
|
contents: read
|
|
247
250
|
issues: write
|
|
@@ -301,6 +304,7 @@ jobs:
|
|
|
301
304
|
needs: subject
|
|
302
305
|
if: needs.subject.outputs.found == 'true'
|
|
303
306
|
runs-on: agents-arc
|
|
307
|
+
timeout-minutes: 5
|
|
304
308
|
permissions:
|
|
305
309
|
contents: read
|
|
306
310
|
issues: write
|
|
@@ -117,6 +117,7 @@ jobs:
|
|
|
117
117
|
# and gate both the reservation and the agent on it.
|
|
118
118
|
still_open:
|
|
119
119
|
runs-on: agents-arc
|
|
120
|
+
timeout-minutes: 5
|
|
120
121
|
permissions:
|
|
121
122
|
contents: read
|
|
122
123
|
issues: read
|
|
@@ -267,6 +268,7 @@ jobs:
|
|
|
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
270
|
runs-on: agents-arc
|
|
271
|
+
timeout-minutes: 5
|
|
270
272
|
permissions:
|
|
271
273
|
contents: read
|
|
272
274
|
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
|
|
@@ -103,6 +103,7 @@ jobs:
|
|
|
103
103
|
# and gate both the reservation and the agent on it.
|
|
104
104
|
still_open:
|
|
105
105
|
runs-on: agents-arc
|
|
106
|
+
timeout-minutes: 5
|
|
106
107
|
permissions:
|
|
107
108
|
contents: read
|
|
108
109
|
issues: read
|
|
@@ -123,6 +124,7 @@ jobs:
|
|
|
123
124
|
needs: [still_open]
|
|
124
125
|
if: needs.still_open.outputs.open == 'true'
|
|
125
126
|
runs-on: agents-arc
|
|
127
|
+
timeout-minutes: 5
|
|
126
128
|
permissions:
|
|
127
129
|
contents: read
|
|
128
130
|
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.14",
|
|
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": {
|