@plainconceptsplatform/workflows 0.28.12 → 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}`,
@@ -442,53 +442,6 @@ The version bump, the tag and the Release are deterministic on purpose. The agen
442
442
  a file on disk, so a confused model cannot publish a release.
443
443
 
444
444
 
445
- ---
446
-
447
- ## agent-visual-verify.md
448
-
449
- Runs after the merge gate decides auto-merge and before the merge itself. The agent reads the
450
- change, writes a verification plan as JSON waypoints, and a post-agent shell step drives
451
- agent-browser to capture screenshots at each waypoint. A final step attaches the screenshots
452
- to the linked issue as a comment. Failures never block the merge.
453
-
454
- ```mermaid
455
- flowchart TD
456
- vvStart("Merge Gate<br/>auto-merge verdict") --> vvSubject
457
- vvSubject{"subject (rung 4)<br/>PR open? Visual<br/>verify enabled?"}
458
- vvSubject -.->|"no"| vvIdle
459
- vvSubject -->|"yes"| vvActivation
460
- vvActivation("Activation (rung 3)<br/>Checkout, load issue context,<br/>build the prompt") --> vvAgent
461
- vvAgent("Agent (rung 5)<br/>Explore the change, write<br/>plan.json with waypoints<br/>(url + selector per step)") --> vvBuild
462
- vvBuild("Build and start app<br/>VISUAL_VERIFY_BUILD_COMMAND,<br/>then VISUAL_VERIFY_START_COMMAND<br/>on the configured port") --> vvAppUp{"App up on<br/>the port?"}
463
- vvAppUp -.->|"no"| vvWarn
464
- vvAppUp -->|"yes"| vvCapture
465
- vvCapture["Capture screenshots<br/>agent-browser opens each waypoint,<br/>waits for selectors,<br/>screenshot --full --no-sandbox"] --> vvGotShots{"Any captured?"}
466
- vvGotShots -->|"yes"| vvAttach
467
- vvGotShots -.->|"no, all failed"| vvWarn
468
- vvAttach("Attach to issue<br/>post plan + screenshots<br/>as a single comment") --> vvDone
469
- vvDone(("Screenshots posted<br/>visual evidence on the issue"))
470
- vvWarn(("Warning posted<br/>no screenshots, merge proceeds"))
471
- vvIdle(("Idle<br/>not enabled or not our PR"))
472
-
473
- classDef start fill:#ffffff,stroke:#172033,stroke-width:2px,color:#172033
474
- classDef action fill:#eef0ff,stroke:#554cff,stroke-width:2px,color:#172033
475
- classDef decision fill:#fff8e8,stroke:#c75b00,stroke-width:2px,color:#172033
476
- classDef idle fill:#202c40,stroke:#738198,stroke-width:2px,color:#ffffff
477
- classDef failure fill:#fff0f0,stroke:#ef2929,stroke-width:2px,color:#8b1a1a
478
- classDef success fill:#e8f8ec,stroke:#18883c,stroke-width:2px,color:#145a32
479
- class vvStart start
480
- class vvActivation,vvAgent,vvBuild,vvCapture,vvAttach action
481
- class vvSubject,vvAppUp,vvGotShots decision
482
- class vvIdle idle
483
- class vvWarn failure
484
- class vvDone success
485
- ```
486
-
487
- The agent does not take screenshots itself. It writes a plan; a shell step runs agent-browser
488
- against the running app. Chromium needs --no-sandbox on CI runners (set via
489
- AGENT_BROWSER_ARGS), and the explicit apt-get install covers libraries gent-browser
490
- install --with-deps misses on ubuntu-24.04.
491
-
492
445
  ---
493
446
 
494
447
  ## housekeeping
@@ -657,3 +610,52 @@ The route matrix asserts the scanner exists, that both exit paths call `setFaile
657
610
  at least as many scans as upstream writes, that each individual check is still present, and that no
658
611
  model or `OPENAI_API_KEY` has appeared in the action. Every one of those was mutation-tested; three
659
612
  earlier versions of them passed against a deliberately broken guard and were rewritten.
613
+
614
+ ---
615
+
616
+ ## agent-visual-verify.md
617
+
618
+ Runs after merge-gate decides to auto-merge, before the actual merge, and produces **evidence, not
619
+ code changes**: it writes a verification plan, drives a real browser through the app, and attaches
620
+ screenshots to the linked issue. It is called directly by merge-gate's `conclude` job, so it is not
621
+ a router route. Failures never block the merge — a browser that will not start or an app that never
622
+ comes up emits a warning comment and the merge proceeds.
623
+
624
+ ```mermaid
625
+ flowchart TD
626
+ vvStart("merge-gate conclude<br/>calls agent-visual-verify") --> vvSubject
627
+ vvSubject["subject (rung 4)<br/>PR open? linked issue found?"] --> vvConfig
628
+ vvConfig{"config (rung 4)<br/>VISUAL_VERIFY_ENABLED and<br/>START_COMMAND set?"}
629
+ vvConfig -.->|"no"| vvIdle
630
+ vvConfig -->|"yes"| vvFacts
631
+ vvFacts["Facts (rung 3)<br/>PR branch checked out,<br/>issue context loaded"] --> vvPlan
632
+ vvPlan{"/repo-verify (rung 5)<br/>reachable UI surface?"}
633
+ vvPlan -.->|"not-applicable"| vvNoUi
634
+ vvPlan -->|"yes"| vvStartCmd
635
+ vvStartCmd{"start command set?<br/>VISUAL_VERIFY_START_COMMAND"}
636
+ vvStartCmd -.->|"empty"| vvNoStart
637
+ vvStartCmd -->|"yes"| vvAgent
638
+ vvAgent["Agent (rung 5)<br/>parse waypoints into plan.json,<br/>one add_comment on the issue"] --> vvCapture
639
+ vvCapture["post-agent (rung 6)<br/>build + start the app,<br/>agent-browser captures per waypoint"] --> vvAttach
640
+ vvAttach["attach-screenshots (rung 6)<br/>always(), subject found + enabled"] --> vvShutdown
641
+ vvShutdown["shut down the app"] --> vvDone
642
+ vvCapture -.->|"app never started,<br/>no plan, or all waypoints failed"| vvPartial
643
+ vvDone(("Verified<br/>screenshots attached,<br/>merge proceeds"))
644
+ vvPartial(("Attach what exists<br/>no-capture explanation,<br/>run stays green"))
645
+ vvNoUi(("No UI surface<br/>summary comment, stop"))
646
+ vvNoStart(("Not configured<br/>warning comment, stop"))
647
+ vvIdle(("Idle<br/>unconfigured or PR not open"))
648
+
649
+ classDef start fill:#ffffff,stroke:#172033,stroke-width:2px,color:#172033
650
+ classDef action fill:#eef0ff,stroke:#554cff,stroke-width:2px,color:#172033
651
+ classDef decision fill:#fff8e8,stroke:#c75b00,stroke-width:2px,color:#172033
652
+ classDef idle fill:#202c40,stroke:#738198,stroke-width:2px,color:#ffffff
653
+ classDef failure fill:#fff0f0,stroke:#ef2929,stroke-width:2px,color:#8b1a1a
654
+ classDef success fill:#e8f8ec,stroke:#18883c,stroke-width:2px,color:#145a32
655
+ class vvStart start
656
+ class vvSubject,vvFacts,vvCapture,vvAttach,vvShutdown action
657
+ class vvConfig,vvPlan,vvStartCmd decision
658
+ class vvIdle idle
659
+ class vvNoUi,vvNoStart,vvPartial failure
660
+ class vvDone success
661
+ ```