@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.
@@ -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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
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: agents-arc
313
+ runs-on: RunnerLandingZone
312
314
  permissions:
313
315
  contents: read
314
316
  issues: write
@@ -52,7 +52,8 @@ on:
52
52
  # shell step runs agent-browser to capture screenshots outside the awf sandbox.
53
53
  jobs:
54
54
  subject:
55
- runs-on: agents-arc
55
+ runs-on: RunnerLandingZone
56
+ timeout-minutes: 5
56
57
  permissions:
57
58
  contents: read
58
59
  issues: read
@@ -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.13",
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/Agentic-Workflows.git"
12
+ "url": "git+https://github.com/PlainConceptsPlatform/agentic-workflows.git"
13
13
  },
14
14
  "bugs": {
15
- "url": "https://github.com/PlainConceptsPlatform/Agentic-Workflows/issues"
15
+ "url": "https://github.com/PlainConceptsPlatform/agentic-workflows/issues"
16
16
  },
17
- "homepage": "https://github.com/PlainConceptsPlatform/Agentic-Workflows#readme",
17
+ "homepage": "https://github.com/PlainConceptsPlatform/agentic-workflows#readme",
18
18
  "license": "UNLICENSED",
19
19
  "type": "module",
20
20
  "bin": {