@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.
@@ -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
@@ -53,6 +53,7 @@ on:
53
53
  jobs:
54
54
  subject:
55
55
  runs-on: agents-arc
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.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/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": {