@plainconceptsplatform/workflows 1.6.30 → 1.6.42

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/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { formatCatalog, listCatalog, searchCatalog } from "./catalog-listing.js"
5
5
  import { packageVersion } from "./package-baseline.js";
6
6
  import { routeNames, templateNames } from "./workflow-catalog.js";
7
7
  import { runInteractive } from "./tui.js";
8
+ import { realpathSync } from "node:fs";
8
9
  import { resolve } from "node:path";
9
10
  import { fileURLToPath } from "node:url";
10
11
  const HELP_TEXT = `Workflows CLI install and update Plain Concepts Platform agentic workflows.
@@ -269,7 +270,17 @@ function fail(message) {
269
270
  console.error(message);
270
271
  return 1;
271
272
  }
272
- if (process.argv[1] !== undefined && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
273
+ // pnpm installs the package behind a symlink: argv[1] is the link and import.meta.url the real file,
274
+ // so a plain comparison never matched and the installed CLI exited silently, --version included.
275
+ function realPath(path) {
276
+ try {
277
+ return realpathSync(path);
278
+ }
279
+ catch {
280
+ return resolve(path);
281
+ }
282
+ }
283
+ if (process.argv[1] !== undefined && realPath(process.argv[1]) === realPath(fileURLToPath(import.meta.url))) {
273
284
  void run(process.argv.slice(2)).then((exitCode) => {
274
285
  process.exitCode = exitCode;
275
286
  });
@@ -311,7 +311,17 @@ runs:
311
311
  });
312
312
  const told = comments.find((c) => (c.body ?? '').includes('<!-- stack-complete -->'));
313
313
  if (!told) {
314
- needsHuman.push({ kind: 'stack-ready', issue, pull: root, waited: age(issue.updated_at) });
314
+ // The router announces a finished stack when a child's pull request merges into it.
315
+ // A child closed any other way (by hand, or by this sweep) never raised that event,
316
+ // and Pliny-Bot #781 sat finished with no announcement, which is what the merge train
317
+ // waits for before it lets the root merge. Announce it here; the root's own gate and
318
+ // CI still decide whether it merges.
319
+ await act(`#${rootPr}: announcing the finished stack of #${issue.number}`, async () => {
320
+ await github.rest.issues.createComment({
321
+ ...context.repo, issue_number: rootPr,
322
+ body: `Stack complete: every child of #${issue.number} has landed on the feature branch. This root pull request is ready to merge into ${context.payload.repository?.default_branch ?? 'main'}: the merge gate decides it next, and a person is asked only when its blast radius or a finding says so. <!-- stack-complete -->`,
323
+ });
324
+ });
315
325
  } else if (age(told.created_at) > stalePrMs) {
316
326
  needsHuman.push({ kind: 'stack-held', issue, pull: root, waited: age(told.created_at) });
317
327
  }
@@ -444,7 +444,14 @@ for line in "${candidates[@]}"; do
444
444
  --jq '[.check_runs[] | select(.conclusion == "failure") | .completed_at] | min // empty' 2>/dev/null || true)
445
445
  red_for_hours=0
446
446
  if [ -n "$red_since" ]; then
447
- red_for_hours=$(( (now_epoch - $(iso_epoch "$red_since")) / 3600 ))
447
+ # A date that does not parse reads as epoch 0, which made main "red for 497899h" and ended the
448
+ # wait at once (Pliny-Bot repaired five pull requests for failures main had too). No date, no claim.
449
+ red_epoch=$(iso_epoch "$red_since")
450
+ if [ "$red_epoch" -gt 0 ]; then
451
+ red_for_hours=$(( (now_epoch - red_epoch) / 3600 ))
452
+ else
453
+ echo "::warning::Could not read when $base_ref went red ('$red_since'); waiting for it as if it just did."
454
+ fi
448
455
  fi
449
456
  if [ -n "$failed_here" ] && [ -n "$failed_base" ] &&
450
457
  [ -z "$(comm -23 <(sort <<<"$failed_here") <(sort <<<"$failed_base"))" ]; then
@@ -0,0 +1,41 @@
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/rebase-stale-patch/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Rebase a stale agent patch
3
+ description: When an agent's patch no longer applies because its base branch moved, rebase the run's bundle onto the base and open the pull request the agent asked for. A real conflict is left for a person.
4
+ inputs:
5
+ token:
6
+ description: App token that can push a branch, open a pull request and close an issue.
7
+ required: true
8
+ run-id:
9
+ description: The workflow run whose agent artifact holds the bundle.
10
+ required: true
11
+ artifact:
12
+ description: Name of the agent artifact, with the run's artifact prefix.
13
+ required: true
14
+ issue-number:
15
+ description: The issue the agent worked on.
16
+ required: true
17
+ default-branch:
18
+ description: Base used when the issue is not part of a stack.
19
+ required: true
20
+ outputs:
21
+ rescued:
22
+ description: '"true" when a pull request was opened from the rebased bundle.'
23
+ value: ${{ steps.rebase.outputs.rescued }}
24
+ pr-number:
25
+ description: The pull request opened, or empty.
26
+ value: ${{ steps.rebase.outputs.pr_number }}
27
+ runs:
28
+ using: composite
29
+ steps:
30
+ - name: Rebase the bundle and open its pull request
31
+ id: rebase
32
+ shell: bash
33
+ env:
34
+ GH_TOKEN: ${{ inputs.token }}
35
+ DOWNLOAD_TOKEN: ${{ github.token }}
36
+ REPO: ${{ github.repository }}
37
+ RUN_ID: ${{ inputs.run-id }}
38
+ ARTIFACT: ${{ inputs.artifact }}
39
+ ISSUE: ${{ inputs.issue-number }}
40
+ DEFAULT_BRANCH: ${{ inputs.default-branch }}
41
+ run: bash "${{ github.action_path }}/rebase-stale-patch.sh"
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env bash
2
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/rebase-stale-patch/rebase-stale-patch.sh. Update with `workflows update --force`; consumer edits may be overwritten.
3
+ # gh-aw pushes an agent's commits onto the base as it is when the run ends. When the base moved
4
+ # under a long run and the commits no longer apply, gh-aw files the patch as an issue and the work
5
+ # waits for a person, although most of the time a plain rebase applies it: Pliny-Bot #787 and #794
6
+ # were both rebased by hand in minutes. This does that rebase here and opens the pull request the
7
+ # agent asked for. A conflict a rebase cannot settle stays with a person, exactly as before.
8
+ #
9
+ # Needs: GH_TOKEN (App), DOWNLOAD_TOKEN (the run's own token, for its artifacts), REPO, RUN_ID, ARTIFACT, ISSUE, DEFAULT_BRANCH, GITHUB_OUTPUT.
10
+
11
+ set -euo pipefail
12
+
13
+ say() { echo "$1"; }
14
+ done_with() {
15
+ echo "rescued=$1" >> "$GITHUB_OUTPUT"
16
+ echo "pr_number=${2:-}" >> "$GITHUB_OUTPUT"
17
+ exit 0
18
+ }
19
+
20
+ work="$(mktemp -d)"
21
+ if ! GH_TOKEN="${DOWNLOAD_TOKEN:-$GH_TOKEN}" gh run download "$RUN_ID" --repo "$REPO" --name "$ARTIFACT" --dir "$work" >/dev/null 2>&1; then
22
+ say "No agent artifact to rebase from."
23
+ done_with false
24
+ fi
25
+
26
+ bundle="$(find "$work" -maxdepth 1 -name '*.bundle' | head -1)"
27
+ output="$work/agent_output.json"
28
+ if [ -z "$bundle" ] || [ ! -f "$output" ]; then
29
+ say "The artifact holds no bundle or no agent output."
30
+ done_with false
31
+ fi
32
+
33
+ request="$(jq -c '[.items[]? | select(.type == "create_pull_request")] | first // empty' "$output")"
34
+ if [ -z "$request" ]; then
35
+ say "The agent did not ask for a pull request."
36
+ done_with false
37
+ fi
38
+ branch="$(jq -r '.branch // empty' <<<"$request")"
39
+ title="$(jq -r '.title // empty' <<<"$request")"
40
+ jq -r '.body // ""' <<<"$request" > "$work/body.md"
41
+ if [ -z "$branch" ] || [ -z "$title" ]; then
42
+ say "The pull request request has no branch or title."
43
+ done_with false
44
+ fi
45
+
46
+ # A stack child targets its feature branch while that branch exists; otherwise the default branch.
47
+ base="$DEFAULT_BRANCH"
48
+ stack="$(gh issue view "$ISSUE" --repo "$REPO" --json body --jq '.body // ""' |
49
+ sed -nE 's/.*<!-- stack-root-branch: ([^ ]+) -->.*/\1/p' | head -1)"
50
+ if [ -n "$stack" ] && gh api "repos/$REPO/branches/$stack" >/dev/null 2>&1; then
51
+ base="$stack"
52
+ fi
53
+
54
+ remote="https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git"
55
+ # Full history: the bundle's prerequisites are commits of the base, and a rebase needs their blobs.
56
+ if git rev-parse --is-shallow-repository | grep -q true; then
57
+ git fetch --quiet --unshallow "$remote" "+refs/heads/${base}:refs/remotes/rescue-base/${base}"
58
+ else
59
+ git fetch --quiet "$remote" "+refs/heads/${base}:refs/remotes/rescue-base/${base}"
60
+ fi
61
+ git fetch --quiet "$bundle" "+refs/heads/*:refs/remotes/rescue-bundle/*"
62
+ head="refs/remotes/rescue-bundle/${branch}"
63
+ if ! git rev-parse --verify --quiet "$head" >/dev/null; then
64
+ head="$(git for-each-ref --format='%(refname)' refs/remotes/rescue-bundle | head -1)"
65
+ fi
66
+
67
+ rescued_branch="${branch}-rebased-${RUN_ID}"
68
+ git checkout --quiet -B "$rescued_branch" "$head"
69
+ if ! git -c user.name="github-actions[bot]" -c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
70
+ rebase --quiet "refs/remotes/rescue-base/${base}"; then
71
+ git rebase --abort || true
72
+ say "The rebase onto ${base} conflicts: left for a person."
73
+ done_with false
74
+ fi
75
+
76
+ git push --quiet "$remote" "HEAD:refs/heads/${rescued_branch}"
77
+
78
+ grep -qiE "(closes|fixes|resolves) #${ISSUE}\b" "$work/body.md" || printf '\n\nCloses #%s\n' "$ISSUE" >> "$work/body.md"
79
+ printf '\n\nRebased onto `%s` by the workflow: the agent'"'"'s commits no longer applied after `%s` moved during the run.\n' \
80
+ "$base" "$base" >> "$work/body.md"
81
+ [[ "$title" == "[bot] "* ]] || title="[bot] $title"
82
+ url="$(gh pr create --repo "$REPO" --base "$base" --head "$rescued_branch" --title "$title" --body-file "$work/body.md")"
83
+ number="${url##*/}"
84
+
85
+ # The issue gh-aw filed with the patch is superseded by the pull request.
86
+ stray="$(gh issue list --repo "$REPO" --state open --limit 30 --json number,title 2>/dev/null |
87
+ jq -r --arg t "$title" '[.[] | select(.title == $t)] | first | .number // empty' || true)"
88
+ if [ -n "$stray" ]; then
89
+ gh issue close "$stray" --repo "$REPO" --reason "not planned" \
90
+ --comment "Superseded by #${number}: the patch was rebased onto ${base} and opened as a pull request." >/dev/null
91
+ fi
92
+
93
+ say "Rebased onto ${base} and opened #${number}."
94
+ done_with true "$number"
@@ -1792,11 +1792,11 @@ fi
1792
1792
  # full verification at minute forty-one after six scoped runs, and #751 spent fifteen of twenty-two
1793
1793
  # minutes in twelve tiny worker tasks. These sentences are what keep a run near twenty minutes.
1794
1794
  if worker_installed implement; then
1795
- if grep -qF 'Run them once, on the final tree.' "$IMPLEMENT_WORKER_MD" && grep -qF 'on both paths, before the commands above.' "$IMPLEMENT_WORKER_MD" && grep -qF 'Ask it for few, coarse tasks' "$IMPLEMENT_WORKER_MD" && grep -qF 'WORK IN FEW TURNS' "$IMPLEMENT_WORKER_MD"; then
1795
+ if grep -qF 'Run them once, on the final tree.' "$IMPLEMENT_WORKER_MD" && grep -qF 'on both paths, before the commands above.' "$IMPLEMENT_WORKER_MD" && grep -qF 'tasks that do not depend on each' "$IMPLEMENT_WORKER_MD" && grep -qF 'WORK IN FEW TURNS' "$IMPLEMENT_WORKER_MD"; then
1796
1796
  PASS=$((PASS + 1))
1797
1797
  else
1798
1798
  FAIL=$((FAIL + 1))
1799
- echo "FAIL: the implement prompt must verify once on the final tree, ask for coarse tasks and work in few turns" >&2
1799
+ echo "FAIL: the implement prompt must verify once on the final tree, let independent tasks run in parallel and work in few turns" >&2
1800
1800
  fi
1801
1801
  fi
1802
1802
  # gh-aw leaves OPENCODE_MODEL empty, so every lead ran on opencode.ci.json's one model. Each worker
@@ -1824,6 +1824,9 @@ fi
1824
1824
  if ! grep -qF 'jq --arg model "$AGENT_LEAD_MODEL"' "${WORKFLOWS_DIR}/shared/opencode-ci.md"; then
1825
1825
  LEAD_OK=0; echo "FAIL: the CI config merge must write AGENT_LEAD_MODEL onto ci-workflow-agent" >&2
1826
1826
  fi
1827
+ if ! grep -qF 'contents/${FRAGMENT}?ref=${DEFAULT_BRANCH}' "${WORKFLOWS_DIR}/shared/opencode-ci.md"; then
1828
+ LEAD_OK=0; echo "FAIL: the CI config merge must read opencode.ci.json from the default branch" >&2
1829
+ fi
1827
1830
  if worker_installed implement && ! grep -qF 'subagent_type: planner' "$IMPLEMENT_WORKER_MD"; then
1828
1831
  LEAD_OK=0; echo "FAIL: the implement prompt must run propose and archive in the planner agent" >&2
1829
1832
  fi
@@ -2784,12 +2787,69 @@ fi
2784
2787
  # No prompt rule stopped the model ending a turn in text; the compiled engine command continues the
2785
2788
  # session when no safe output exists. Pliny-Bot #771 passed verify and exited with nothing delivered.
2786
2789
  COMPILE_MJS="${HERE}/../../scripts/compile-agent-workflows.mjs"
2790
+ # In a consumer the action sits under .github/ and the script at the repository root.
2791
+ [ -f "$COMPILE_MJS" ] || COMPILE_MJS="${HERE}/../../../scripts/compile-agent-workflows.mjs"
2787
2792
  if [ -f "$COMPILE_MJS" ] && grep -qF 'opencode run --continue' "$COMPILE_MJS" && grep -qF 'gh-aw/safeoutputs/outputs.jsonl' "$COMPILE_MJS"; then
2788
2793
  PASS=$((PASS + 1))
2789
2794
  else
2790
2795
  FAIL=$((FAIL + 1))
2791
2796
  echo "FAIL: the compiled engine command does not continue a session that delivered nothing" >&2
2792
2797
  fi
2798
+ # A patch the base moved under is rebased and opened as a pull request before anyone is asked: Pliny-Bot
2799
+ # #787 and #794 sat as issues until rebased by hand. The rescue must run before the pull request steps,
2800
+ # and the push-failure flags must stay quiet when it worked.
2801
+ IMPL_MD="${WORKFLOWS_DIR}/agent-implement.md"
2802
+ if worker_installed implement; then
2803
+ if grep -qF 'uses: ./.github/actions/rebase-stale-patch' "$IMPL_MD" && grep -qF "steps.rescue.outputs.rescued == 'true'" "$IMPL_MD" && grep -qF "&& steps.rescue.outputs.rescued != 'true'" "$IMPL_MD" && [ -f "${HERE}/../rebase-stale-patch/rebase-stale-patch.sh" ]; then
2804
+ PASS=$((PASS + 1))
2805
+ else
2806
+ FAIL=$((FAIL + 1))
2807
+ echo "FAIL: implement does not rebase a patch its base moved under before parking it" >&2
2808
+ fi
2809
+ fi
2810
+ # The subagent monitor never sees a CI child finish and denies every spawn after the cap.
2811
+ if grep -qF 'for plugin in pc-system-reminders pc-subagent-monitor' "${WORKFLOWS_DIR}/shared/opencode-ci.md"; then
2812
+ PASS=$((PASS + 1))
2813
+ else
2814
+ FAIL=$((FAIL + 1))
2815
+ echo "FAIL: the CI setup must remove pc-subagent-monitor in CI" >&2
2816
+ fi
2817
+ # a published pull request is never flagged stalled, whatever the agent job's own result.
2818
+ if grep -A8 '^ incomplete:' "$IMPLEMENT_WORKER_MD" | grep -qF "needs.safe_outputs.outputs.created_pr_number == ''"; then
2819
+ PASS=$((PASS + 1))
2820
+ else
2821
+ FAIL=$((FAIL + 1))
2822
+ echo "FAIL: implement flags an issue stalled next to the pull request it just opened" >&2
2823
+ fi
2824
+ # A date the train cannot parse must not read as "main has been red since 1970".
2825
+ if grep -qF 'Could not read when $base_ref went red' "${HERE}/../merge-train/merge-train.sh"; then
2826
+ PASS=$((PASS + 1))
2827
+ else
2828
+ FAIL=$((FAIL + 1))
2829
+ echo "FAIL: the merge train treats an unparsable red-since date as an endless red main" >&2
2830
+ fi
2831
+ # An empty AGENT_TIER_MODEL must remove an override a reused runner kept from an earlier job.
2832
+ if grep -qF 'rm -f .opencode/harness.user.json' "${WORKFLOWS_DIR}/shared/opencode-ci.md"; then
2833
+ PASS=$((PASS + 1))
2834
+ else
2835
+ FAIL=$((FAIL + 1))
2836
+ echo "FAIL: a stale harness.user.json on a reused runner keeps every tier pinned" >&2
2837
+ fi
2838
+ # A stack whose last child closed outside the router is announced by housekeeping, or its root waits.
2839
+ if grep -qF "announcing the finished stack" "${HERE}/../housekeeping/action.yml"; then
2840
+ PASS=$((PASS + 1))
2841
+ else
2842
+ FAIL=$((FAIL + 1))
2843
+ echo "FAIL: housekeeping does not announce a stack whose children all closed outside the router" >&2
2844
+ fi
2845
+ # A stack child runs the feature branch's copy of the workflows, so the router brings the default
2846
+ # branch in before it dispatches one.
2847
+ if grep -qF 'bring $DEFAULT_BRANCH into $STACK_BRANCH before a child runs' "${WORKFLOWS_DIR}/work-router.yml"; then
2848
+ PASS=$((PASS + 1))
2849
+ else
2850
+ FAIL=$((FAIL + 1))
2851
+ echo "FAIL: stack children run the feature branch's stale workflows" >&2
2852
+ fi
2793
2853
  # A stack root merges itself once every child has landed, through the same gate as any pull request.
2794
2854
  # The people it still stops are the gate's own: a blast radius over the limit, a protected path, a
2795
2855
  # finding. The gate must read the router's completion announcement, and the train must give the root
@@ -6,9 +6,12 @@ env:
6
6
  # so it names its model here. `model:` below only satisfies the compiler. A repository may
7
7
  # change it to any model its opencode.ci.json declares.
8
8
  AGENT_LEAD_MODEL: "forge/glm-5-2"
9
- # The model every harness tier (plan, build, fast) runs on in CI, planner included. Unset,
10
- # the tiers take harness.json, whose models the AWF proxy may not price.
9
+ # Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
10
+ # harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
11
+ # #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
11
12
  AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
13
+ # The fast tier alone, while the CI's Forge account is not granted carina-60b.
14
+ AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
12
15
  # Printed by the prompt as the verification block. Consumers set their own commands.
13
16
  VERIFY_COMMANDS: ""
14
17
  REPO_RULES: "Apply only actionable outstanding reviewer feedback to the selected bot pull request. Make minimal changes that address each comment. Preserve architecture and do not weaken tests. Run full verification after changes."
@@ -499,8 +502,8 @@ safe-outputs:
499
502
  # 240 went on to every worker at once when the provider was slow, which fixed the deaths and
500
503
  # made every worker equally expensive to hang. These numbers are per worker: enough headroom
501
504
  # for a slow gateway on the work it actually does, and not four hours for a run that reads one
502
- # issue. Turns remain the guard against a confused agent looping; for a custom model the credit
503
- # ceiling is models.dev fallback pricing and guards nothing.
505
+ # issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
506
+ # Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
504
507
  #
505
508
  # Applies review comments to an existing branch and verifies the result.
506
509
  timeout-minutes: 90
@@ -6,9 +6,12 @@ env:
6
6
  # so it names its model here. `model:` below only satisfies the compiler. A repository may
7
7
  # change it to any model its opencode.ci.json declares.
8
8
  AGENT_LEAD_MODEL: "forge/glm-5-2"
9
- # The model every harness tier (plan, build, fast) runs on in CI, planner included. Unset,
10
- # the tiers take harness.json, whose models the AWF proxy may not price.
9
+ # Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
10
+ # harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
11
+ # #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
11
12
  AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
13
+ # The fast tier alone, while the CI's Forge account is not granted carina-60b.
14
+ AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
12
15
  REPO_RULES: "Read-only repository audit. Report only reproducible, actionable defects with evidence. Do not modify files, commit, push, or run write operations."
13
16
  # Split out of REPO_RULES, which carried both the read-only discipline above and the list
14
17
  # below. All four consuming repositories had customised the list and none could touch it
@@ -175,8 +178,8 @@ safe-outputs:
175
178
  # 240 went on to every worker at once when the provider was slow, which fixed the deaths and
176
179
  # made every worker equally expensive to hang. These numbers are per worker: enough headroom
177
180
  # for a slow gateway on the work it actually does, and not four hours for a run that reads one
178
- # issue. Turns remain the guard against a confused agent looping; for a custom model the credit
179
- # ceiling is models.dev fallback pricing and guards nothing.
181
+ # issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
182
+ # Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
180
183
  #
181
184
  # Sweeps the repository read-only and writes one findings issue. Observed around 45 minutes.
182
185
  timeout-minutes: 90
@@ -6,9 +6,12 @@ env:
6
6
  # so it names its model here. `model:` below only satisfies the compiler. A repository may
7
7
  # change it to any model its opencode.ci.json declares.
8
8
  AGENT_LEAD_MODEL: "forge/glm-5-2"
9
- # The model every harness tier (plan, build, fast) runs on in CI, planner included. Unset,
10
- # the tiers take harness.json, whose models the AWF proxy may not price.
9
+ # Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
10
+ # harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
11
+ # #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
11
12
  AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
13
+ # The fast tier alone, while the CI's Forge account is not granted carina-60b.
14
+ AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
12
15
  VERIFY_COMMANDS: ""
13
16
  REPO_RULES: "Implement only the selected issue. Follow repository documentation and existing conventions. Do not weaken tests, lower coverage thresholds, or bypass checks."
14
17
  # Split out of REPO_RULES because one field asked to carry architecture, testing, coverage and
@@ -243,7 +246,7 @@ jobs:
243
246
  ${{ env.REVIEW_LABEL }}
244
247
  ${{ env.STALLED_LABEL }}
245
248
  conclude:
246
- needs: [agent, safe_outputs]
249
+ needs: [activation, agent, safe_outputs]
247
250
  if: >
248
251
  always() &&
249
252
  needs.agent.result == 'success' &&
@@ -276,13 +279,27 @@ jobs:
276
279
  token: ${{ steps.app-token.outputs.token }}
277
280
  issue-number: ${{ inputs.issue-number }}
278
281
  labels: ${{ env.WORKING_LABEL }}
282
+ # gh-aw pushes onto the base as it is when the run ends; a base that moved under a long run
283
+ # makes the commits not apply and the work is filed as an issue. A plain rebase applies most
284
+ # of them (Pliny-Bot #787 and #794 were rebased by hand in minutes), so it is tried here, and
285
+ # every step below treats the rescued pull request as the one the agent opened.
286
+ - name: Rebase a patch the base moved under
287
+ id: rescue
288
+ if: needs.safe_outputs.outputs.created_pr_number == '' && contains(needs.agent.outputs.output_types, 'create_pull_request')
289
+ uses: ./.github/actions/rebase-stale-patch
290
+ with:
291
+ token: ${{ steps.app-token.outputs.token }}
292
+ run-id: ${{ github.run_id }}
293
+ artifact: ${{ needs.activation.outputs.artifact_prefix }}agent
294
+ issue-number: ${{ inputs.issue-number }}
295
+ default-branch: ${{ github.event.repository.default_branch }}
279
296
  - name: Verify PR closes the source issue
280
- if: needs.safe_outputs.outputs.created_pr_number != ''
297
+ if: needs.safe_outputs.outputs.created_pr_number != '' || steps.rescue.outputs.rescued == 'true'
281
298
  continue-on-error: true
282
299
  uses: ./.github/actions/link-pr-to-issue
283
300
  with:
284
301
  token: ${{ steps.app-token.outputs.token }}
285
- pr-number: ${{ needs.safe_outputs.outputs.created_pr_number }}
302
+ pr-number: ${{ needs.safe_outputs.outputs.created_pr_number || steps.rescue.outputs.pr-number }}
286
303
  issue-number: ${{ inputs.issue-number }}
287
304
  # GitHub only stores the PR-to-issue direction (Closes #N); the reverse lookup is a
288
305
  # body-text search. Stamping hidden markers on the issue makes issue-to-branch exact:
@@ -290,13 +307,13 @@ jobs:
290
307
  # the branch without guessing. Old markers are replaced, so a re-implement after a
291
308
  # closed pull request re-stamps cleanly.
292
309
  - name: Record the pull request and branch on the issue
293
- if: needs.safe_outputs.outputs.created_pr_number != ''
310
+ if: needs.safe_outputs.outputs.created_pr_number != '' || steps.rescue.outputs.rescued == 'true'
294
311
  continue-on-error: true
295
312
  env:
296
313
  GH_TOKEN: ${{ steps.app-token.outputs.token }}
297
314
  REPO: ${{ github.repository }}
298
315
  ISSUE: ${{ inputs.issue-number }}
299
- PR_NUMBER: ${{ needs.safe_outputs.outputs.created_pr_number }}
316
+ PR_NUMBER: ${{ needs.safe_outputs.outputs.created_pr_number || steps.rescue.outputs.pr-number }}
300
317
  run: |
301
318
  set -euo pipefail
302
319
  branch=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRefName --jq '.headRefName')
@@ -306,14 +323,14 @@ jobs:
306
323
  gh issue edit "$ISSUE" --repo "$REPO" --body-file /tmp/issue-body.md
307
324
  echo "Stamped PR #$PR_NUMBER and branch $branch on issue #$ISSUE"
308
325
  - name: Mark issue as having a pull request pending
309
- if: needs.safe_outputs.outputs.created_pr_number != ''
326
+ if: needs.safe_outputs.outputs.created_pr_number != '' || steps.rescue.outputs.rescued == 'true'
310
327
  uses: ./.github/actions/add-issue-labels
311
328
  with:
312
329
  token: ${{ steps.app-token.outputs.token }}
313
330
  issue-number: ${{ inputs.issue-number }}
314
331
  labels: ${{ env.PR_PENDING_LABEL }}
315
332
  - name: Reconcile the new bot pull request
316
- if: needs.safe_outputs.outputs.created_pr_number != ''
333
+ if: needs.safe_outputs.outputs.created_pr_number != '' || steps.rescue.outputs.rescued == 'true'
317
334
  env:
318
335
  GH_TOKEN: ${{ steps.app-token.outputs.token }}
319
336
  REPO: ${{ github.repository }}
@@ -354,7 +371,7 @@ jobs:
354
371
  # comment exists to prevent (Odyssey #226). Distinguish them the same way the steps below
355
372
  # do: gate on whether the agent actually asked for a pull request.
356
373
  - name: Flag a patch that could not be pushed
357
- if: needs.safe_outputs.outputs.created_pr_number == '' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '0' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '' && contains(needs.agent.outputs.output_types, 'create_pull_request')
374
+ if: needs.safe_outputs.outputs.created_pr_number == '' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '0' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '' && contains(needs.agent.outputs.output_types, 'create_pull_request') && steps.rescue.outputs.rescued != 'true'
358
375
  uses: ./.github/actions/add-issue-labels
359
376
  with:
360
377
  token: ${{ steps.app-token.outputs.token }}
@@ -363,7 +380,7 @@ jobs:
363
380
  ${{ env.REVIEW_LABEL }}
364
381
  ${{ env.STALLED_LABEL }}
365
382
  - name: Say where the patch went
366
- if: needs.safe_outputs.outputs.created_pr_number == '' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '0' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '' && contains(needs.agent.outputs.output_types, 'create_pull_request')
383
+ if: needs.safe_outputs.outputs.created_pr_number == '' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '0' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '' && contains(needs.agent.outputs.output_types, 'create_pull_request') && steps.rescue.outputs.rescued != 'true'
367
384
  uses: ./.github/actions/create-issue-comment
368
385
  with:
369
386
  token: ${{ steps.app-token.outputs.token }}
@@ -385,7 +402,7 @@ jobs:
385
402
  # said "nothing landed" and neither could be acted on, while #175's run had produced more
386
403
  # output than the run that succeeded in the same hour.
387
404
  - name: Flag a request that was not applied
388
- if: needs.safe_outputs.outputs.created_pr_number == '' && (needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '0' || needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '') && contains(needs.agent.outputs.output_types, 'create_pull_request')
405
+ if: needs.safe_outputs.outputs.created_pr_number == '' && (needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '0' || needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '') && contains(needs.agent.outputs.output_types, 'create_pull_request') && steps.rescue.outputs.rescued != 'true'
389
406
  uses: ./.github/actions/add-issue-labels
390
407
  with:
391
408
  token: ${{ steps.app-token.outputs.token }}
@@ -394,7 +411,7 @@ jobs:
394
411
  ${{ env.REVIEW_LABEL }}
395
412
  ${{ env.STALLED_LABEL }}
396
413
  - name: Say the request was not applied
397
- if: needs.safe_outputs.outputs.created_pr_number == '' && (needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '0' || needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '') && contains(needs.agent.outputs.output_types, 'create_pull_request')
414
+ if: needs.safe_outputs.outputs.created_pr_number == '' && (needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '0' || needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '') && contains(needs.agent.outputs.output_types, 'create_pull_request') && steps.rescue.outputs.rescued != 'true'
398
415
  uses: ./.github/actions/create-issue-comment
399
416
  with:
400
417
  token: ${{ steps.app-token.outputs.token }}
@@ -508,9 +525,13 @@ jobs:
508
525
  [View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
509
526
  incomplete:
510
527
  needs: [agent, safe_outputs, eligibility]
528
+ # A published pull request is an outcome whatever the agent job's own result: Pliny-Bot #765
529
+ # opened #844 after 64 minutes, the agent job read as failed, and this job released the issue
530
+ # and flagged it stalled next to its open pull request.
511
531
  if: >
512
532
  always() &&
513
533
  needs.eligibility.outputs.eligible == 'true' &&
534
+ needs.safe_outputs.outputs.created_pr_number == '' &&
514
535
  (
515
536
  needs.agent.result != 'success' ||
516
537
  needs.safe_outputs.result != 'success'
@@ -692,8 +713,8 @@ safe-outputs:
692
713
  # 240 went on to every worker at once when the provider was slow, which fixed the deaths and
693
714
  # made every worker equally expensive to hang. These numbers are per worker: enough headroom
694
715
  # for a slow gateway on the work it actually does, and not four hours for a run that reads one
695
- # issue. Turns remain the guard against a confused agent looping; for a custom model the credit
696
- # ceiling is models.dev fallback pricing and guards nothing.
716
+ # issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
717
+ # Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
697
718
  #
698
719
  # Writes code, builds, runs a test suite and pushes a branch: the longest real work in the fleet.
699
720
  timeout-minutes: 180
@@ -763,12 +784,12 @@ timeout-minutes: 180
763
784
  `proposal.md` or `tasks.md` yourself.** A hand-written `tasks.md` carries no annotations,
764
785
  and without them `pc-plan-apply` has no workers to spawn, so the whole change is done by
765
786
  you alone, in sequence, on one model. That is the failure this step exists to prevent.
766
- Ask it for few, coarse tasks: one task per area of files, the tests for that code in the
767
- same task, and no task whose only job is to repair what an earlier task broke. Five tasks
768
- in three waves cover most issues. Every task is a fresh worker that rereads the code it
769
- needs before it edits anything, one to three minutes each: Pliny-Bot #751 split a small
770
- web feature into twelve tasks and spent fifteen of its twenty-two minutes in them, and
771
- #696 ended with a twelve-minute task that fixed the test doubles the earlier tasks broke.
787
+ Let it plan the way it plans on a developer's machine: tasks that do not depend on each
788
+ other run in the same wave, in parallel, each on its tier's model. Keep the tests for code
789
+ in the same task as the code, and no task whose only job is to repair what an earlier task
790
+ broke (#696 ended with a twelve-minute task that fixed the test doubles the earlier ones
791
+ broke). A chain of tasks that each wait for the last is the slow shape: Pliny-Bot #794 ran
792
+ four workers in three waves, one at a time, for twenty-six of its fifty-nine minutes.
772
793
  3. `pc-plan-apply`. The tasks are implemented by the subagents it spawns, in waves, each on
773
794
  the model tier its annotation names. **In this phase you do not edit source files
774
795
  yourself.** If it stops because a worker cannot be resolved, report that with
@@ -6,9 +6,12 @@ env:
6
6
  # so it names its model here. `model:` below only satisfies the compiler. A repository may
7
7
  # change it to any model its opencode.ci.json declares.
8
8
  AGENT_LEAD_MODEL: "forge/glm-5-2"
9
- # The model every harness tier (plan, build, fast) runs on in CI, planner included. Unset,
10
- # the tiers take harness.json, whose models the AWF proxy may not price.
9
+ # Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
10
+ # harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
11
+ # #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
11
12
  AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
13
+ # The fast tier alone, while the CI's Forge account is not granted carina-60b.
14
+ AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
12
15
  VERIFY_COMMANDS: ""
13
16
  REPO_RULES: "Review the selected pull request for defects and report what you verified. Do not decide the outcome: the workflow computes it from your report and from facts it measured before you ran."
14
17
  # Where to look first, not what to escalate on. This list used to be the check that decided
@@ -1002,8 +1005,8 @@ safe-outputs:
1002
1005
  # 240 went on to every worker at once when the provider was slow, which fixed the deaths and
1003
1006
  # made every worker equally expensive to hang. These numbers are per worker: enough headroom
1004
1007
  # for a slow gateway on the work it actually does, and not four hours for a run that reads one
1005
- # issue. Turns remain the guard against a confused agent looping; for a custom model the credit
1006
- # ceiling is models.dev fallback pricing and guards nothing.
1008
+ # issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
1009
+ # Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
1007
1010
  #
1008
1011
  # Reads CI failure evidence and may fix, verify and re-push, so it can do implement's work on a smaller diff.
1009
1012
  timeout-minutes: 120
@@ -5,10 +5,13 @@ env:
5
5
  # every agent without its own model falls back to; the lead is where a wrong call costs a run,
6
6
  # so it names its model here. `model:` below only satisfies the compiler. A repository may
7
7
  # change it to any model its opencode.ci.json declares.
8
- AGENT_LEAD_MODEL: "forge/glm-5-2"
9
- # The model every harness tier (plan, build, fast) runs on in CI, planner included. Unset,
10
- # the tiers take harness.json, whose models the AWF proxy may not price.
8
+ AGENT_LEAD_MODEL: "forge/glm-5-3"
9
+ # Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
10
+ # harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
11
+ # #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
11
12
  AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
13
+ # The fast tier alone, while the CI's Forge account is not granted carina-60b.
14
+ AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
12
15
  REPO_RULES: "Refine only the selected issue into a grounded, implementation-ready user story. Read repository documentation for domain context. Write acceptance criteria that match existing patterns. Where a story or its open questions decide how business data (named people, roles, addresses) is stored, resolve it toward the repository's convention for that data -- a database entity or a settings entry -- and record that choice in the acceptance criteria; do not carry a person's name or email address into the story as something the code reads from a constant. Do not implement code. Do not use web search or web fetch tools. The issue body already contains the research that motivated it; derive your domain context from the repository's own code and documentation only."
13
16
  # The estimate decides whether a story gets split, and the prompt tells the agent these bands
14
17
  # come from this repository's own merged pull requests. They have to actually come from it, or
@@ -882,8 +885,8 @@ safe-outputs:
882
885
  # 240 went on to every worker at once when the provider was slow, which fixed the deaths and
883
886
  # made every worker equally expensive to hang. These numbers are per worker: enough headroom
884
887
  # for a slow gateway on the work it actually does, and not four hours for a run that reads one
885
- # issue. Turns remain the guard against a confused agent looping; for a custom model the credit
886
- # ceiling is models.dev fallback pricing and guards nothing.
888
+ # issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
889
+ # Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
887
890
  #
888
891
  # Explores the repository per work unit, then rewrites one issue body. Measured: a run needing 36 turns died at 40 minutes with the story finished.
889
892
  timeout-minutes: 90
@@ -6,9 +6,12 @@ env:
6
6
  # so it names its model here. `model:` below only satisfies the compiler. A repository may
7
7
  # change it to any model its opencode.ci.json declares.
8
8
  AGENT_LEAD_MODEL: "forge/glm-5-2"
9
- # The model every harness tier (plan, build, fast) runs on in CI, planner included. Unset,
10
- # the tiers take harness.json, whose models the AWF proxy may not price.
9
+ # Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
10
+ # harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
11
+ # #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
11
12
  AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
13
+ # The fast tier alone, while the CI's Forge account is not granted carina-60b.
14
+ AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
12
15
  GIT_AUTHOR_NAME: "github-actions[bot]"
13
16
  GIT_AUTHOR_EMAIL: "github-actions[bot]@users.noreply.github.com"
14
17
  GIT_COMMITTER_NAME: "github-actions[bot]"
@@ -94,8 +97,8 @@ safe-outputs:
94
97
  # 240 went on to every worker at once when the provider was slow, which fixed the deaths and
95
98
  # made every worker equally expensive to hang. These numbers are per worker: enough headroom
96
99
  # for a slow gateway on the work it actually does, and not four hours for a run that reads one
97
- # issue. Turns remain the guard against a confused agent looping; for a custom model the credit
98
- # ceiling is models.dev fallback pricing and guards nothing.
100
+ # issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
101
+ # Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
99
102
  #
100
103
  # Reads a commit log already on disk and writes release notes. Nothing else.
101
104
  timeout-minutes: 30
@@ -6,9 +6,12 @@ env:
6
6
  # so it names its model here. `model:` below only satisfies the compiler. A repository may
7
7
  # change it to any model its opencode.ci.json declares.
8
8
  AGENT_LEAD_MODEL: "forge/glm-5-2"
9
- # The model every harness tier (plan, build, fast) runs on in CI, planner included. Unset,
10
- # the tiers take harness.json, whose models the AWF proxy may not price.
9
+ # Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
10
+ # harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
11
+ # #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
11
12
  AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
13
+ # The fast tier alone, while the CI's Forge account is not granted carina-60b.
14
+ AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
12
15
  REPO_RULES: "Triage issues opened by outside collaborators. Assess template completeness, security risk, change size, danger level, duplicates, clarity, reproducibility, acceptance criteria, and cross-cutting impact. When the request names specific people, email addresses, or business entities as the thing the code acts on, judge where that data is meant to live: business data that belongs in the database or a settings page is a design decision, not a wording detail, and it must not be left for the implementer to decide by hardcoding it. Do not implement code. Do not modify the issue body."
13
16
  # What a product owner may ask for, and what has to become a maintainer-owned technical
14
17
  # proposal instead. This is business policy, so it belongs to the repository rather than to
@@ -454,8 +457,8 @@ safe-outputs:
454
457
  # 240 went on to every worker at once when the provider was slow, which fixed the deaths and
455
458
  # made every worker equally expensive to hang. These numbers are per worker: enough headroom
456
459
  # for a slow gateway on the work it actually does, and not four hours for a run that reads one
457
- # issue. Turns remain the guard against a confused agent looping; for a custom model the credit
458
- # ceiling is models.dev fallback pricing and guards nothing.
460
+ # issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
461
+ # Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
459
462
  #
460
463
  # Reads one issue and the open-issue list, runs ten checks, writes one comment. No repository exploration, no build.
461
464
  timeout-minutes: 45
@@ -13,6 +13,33 @@ description: |
13
13
  shared baseline in the consumer copy. The merge step below is package-owned and required
14
14
  for the agent to resolve its provider and its models.
15
15
 
16
+ # AI credits prices for the Forge models, so the AWF proxy can meter them against max-ai-credits
17
+ # instead of refusing them ("Model ... has no AI credits pricing", Atlas #2016). gh-aw maps this to
18
+ # apiProxy.providers in the AWF config, the operator overlay the proxy checks before its curated
19
+ # table and its models.dev snapshot. Without it glm-5-2 was metered at models.dev's glm-5 rate by
20
+ # prefix match, and carina-60b, quasar-438b and qwen-3-8-27b had no price at all.
21
+ #
22
+ # Rates are Multiverse's public list prices for the CompactifAI API, the provider Forge serves these
23
+ # from: https://docs.compactif.ai/pricing/ (also on https://multiversecomputing.com/compactifai/api),
24
+ # read 2026-10-18. Values are USD per token (the page's $/1M divided by 1e6); the proxy bills one AI
25
+ # credit per $0.01. Where the page shows no cached rate, prompt caching is off for that model and
26
+ # every input token is billed at the input rate, so cache_read equals input instead of the proxy's
27
+ # 10%-of-input default. The key is `openai` because Forge is the proxy's openai target.
28
+ models:
29
+ providers:
30
+ openai:
31
+ models:
32
+ carina-60b:
33
+ cost: { input: "0.00000005", cache_read: "0.00000005", output: "0.0000002" }
34
+ qwen-3-8-27b:
35
+ cost: { input: "0.0000004", cache_read: "0.0000004", output: "0.000002" }
36
+ glm-5-2:
37
+ cost: { input: "0.0000011", cache_read: "0.00000022", output: "0.0000035" }
38
+ glm-5-3:
39
+ cost: { input: "0.0000011", cache_read: "0.00000022", output: "0.0000035" }
40
+ quasar-438b:
41
+ cost: { input: "0.0000006", cache_read: "0.0000006", output: "0.0000018" }
42
+
16
43
  # Consumer repositories should add stack-specific steps (NuGet cache, dotnet restore,
17
44
  # OpenSpec, Playwright, etc.) after the shared baseline. The merge step at the end is
18
45
  # package-owned and required for the agent to resolve its provider and models. Do not
@@ -182,9 +209,10 @@ pre-agent-steps:
182
209
  # worker's prompt does not load, and has never run in CI; it stays off until it does.
183
210
  #
184
211
  # The tier models come from AGENT_TIER_MODEL when a worker sets it. The AWF proxy refuses
185
- # a model it has no AI credits price for, and harness.json names carina-60b and
186
- # quasar-438b, which it has none for (Atlas #2016), so CI pins every tier to one priced
187
- # model through the gitignored per-user override the plugin reads first.
212
+ # a model it has no AI credits price for; harness.json names carina-60b and quasar-438b,
213
+ # which had none until the `models.providers` prices at the top of this file (Atlas #2016).
214
+ # CI still pins every tier to one model, through the gitignored per-user override the
215
+ # plugin reads first, until a run on the newly priced models has passed.
188
216
  run: |
189
217
  set -euo pipefail
190
218
  if [ ! -f .opencode/package.json ]; then
@@ -193,24 +221,51 @@ pre-agent-steps:
193
221
  if [ ! -d .opencode/node_modules/@opencode/plugin ]; then
194
222
  (cd .opencode && npm install --no-audit --no-fund --no-package-lock --ignore-scripts --loglevel=error)
195
223
  fi
196
- if [ -f .opencode/plugins/pc-system-reminders.js ]; then
197
- rm .opencode/plugins/pc-system-reminders.js
198
- # Tracked, so hide the deletion: an agent that sees it in `git status` restores it.
199
- git update-index --skip-worktree .opencode/plugins/pc-system-reminders.js 2>/dev/null || true
200
- fi
224
+ # pc-subagent-monitor frees a slot only on a child's idle or completed event, which a CI
225
+ # run never emits: after agents.maxConcurrent tasks it denied every spawn and Pliny-Bot
226
+ # #823 stopped half done. Off in CI until the plugin sees a child finish.
227
+ for plugin in pc-system-reminders pc-subagent-monitor; do
228
+ if [ -f ".opencode/plugins/${plugin}.js" ]; then
229
+ rm ".opencode/plugins/${plugin}.js"
230
+ # Tracked, so hide the deletion: an agent that sees it in `git status` restores it.
231
+ git update-index --skip-worktree ".opencode/plugins/${plugin}.js" 2>/dev/null || true
232
+ fi
233
+ done
234
+ # The runners are reused VMs and keep the workspace's ignored files between jobs, so an
235
+ # override an earlier run wrote is still there; an empty AGENT_TIER_MODEL has to remove it
236
+ # or the tiers stay pinned (Pliny-Bot #774 ran on glm-5-2 after the pin was dropped).
201
237
  if [ -n "${AGENT_TIER_MODEL:-}" ]; then
202
238
  jq -n --arg m "$AGENT_TIER_MODEL" '{models: {plan: $m, build: $m, fast: $m}}' > .opencode/harness.user.json
239
+ elif [ -n "${AGENT_FAST_MODEL:-}" ]; then
240
+ # One tier only: the CI's Forge account is not granted carina-60b, which harness.json
241
+ # names for fast work, so that tier alone moves to a model the account has.
242
+ jq -n --arg m "$AGENT_FAST_MODEL" '{models: {fast: $m}}' > .opencode/harness.user.json
243
+ else
244
+ rm -f .opencode/harness.user.json
203
245
  fi
204
246
  echo "Harness plugins: $(ls .opencode/plugins | tr '\n' ' ')"
205
247
  echo "Tier models: $(jq -c '.models' .opencode/harness.user.json 2>/dev/null || jq -c '.models' .opencode/harness.json)"
206
248
 
207
249
  - name: Merge the CI-only OpenCode provider into opencode.jsonc
250
+ env:
251
+ GH_TOKEN: ${{ github.token }}
252
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch || 'main' }}
208
253
  run: |
209
254
  set -euo pipefail
210
255
 
211
256
  CONFIG=opencode.jsonc
212
257
  FRAGMENT=opencode.ci.json
213
258
 
259
+ # The default branch's copy wins over the checkout's. A pull request branch cut before a
260
+ # model change keeps the old default: Pliny-Bot's gate on #836 ran forge/carina-60b,
261
+ # which the CI account is not granted, hours after main had moved off it.
262
+ if curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw" \
263
+ "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/contents/${FRAGMENT}?ref=${DEFAULT_BRANCH}" \
264
+ -o "${RUNNER_TEMP}/opencode.ci.json" && jq -e . "${RUNNER_TEMP}/opencode.ci.json" > /dev/null; then
265
+ FRAGMENT="${RUNNER_TEMP}/opencode.ci.json"
266
+ echo "Using $DEFAULT_BRANCH's opencode.ci.json"
267
+ fi
268
+
214
269
  [ -f "$FRAGMENT" ] || { echo "::error::$FRAGMENT is missing from the checkout"; exit 1; }
215
270
 
216
271
  # Pure JSON on purpose, not JSONC: jq cannot parse `//` comments, and a naive
@@ -134,7 +134,7 @@ env:
134
134
  # times it tries before leaving the issue to a person. Only machine failures are retried; a
135
135
  # verdict the agent reached on purpose is reported and never re-run, because re-running a
136
136
  # decision reproduces it.
137
- HOUSEKEEPING_RETRY_AFTER_HOURS: "6"
137
+ HOUSEKEEPING_RETRY_AFTER_HOURS: "1"
138
138
  HOUSEKEEPING_MAX_RETRIES: "3"
139
139
  # A bot pull request older than this with no merge-gate verdict at all is reported, never
140
140
  # closed: the janitor deletes branches and labels, never someone's work.
@@ -756,10 +756,17 @@ jobs:
756
756
  GH_TOKEN: ${{ steps.app-token.outputs.token }}
757
757
  REPO: ${{ github.repository }}
758
758
  STACK_BRANCH: ${{ needs.check-stack-branch.outputs.stack-branch }}
759
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch || 'main' }}
759
760
  ISSUE_NUMBER: ${{ needs.classify.outputs.issue-number }}
760
761
  ATTEMPTS: ${{ needs.classify.outputs.attempts }}
761
762
  run: |
762
763
  set -euo pipefail
764
+ # A dispatch on the feature ref runs the feature branch's own copy of the workflows, so a
765
+ # branch cut before a workflow fix keeps running the old one: Pliny-Bot #774 ran every
766
+ # tier on glm-5-2 from feat/767 after main had dropped the pin. Bring the default branch
767
+ # in first. A conflict (409) leaves the branch as it is; the run still goes ahead.
768
+ code=$(gh api -X POST "repos/$REPO/merges" -f base="$STACK_BRANCH" -f head="$DEFAULT_BRANCH" -f commit_message="chore: bring $DEFAULT_BRANCH into $STACK_BRANCH before a child runs" --silent -i 2>/dev/null | head -1 | awk '{print $2}' || true)
769
+ echo "Merged $DEFAULT_BRANCH into $STACK_BRANCH: HTTP ${code:-unknown} (201 merged, 204 up to date, 409 conflict)"
763
770
  gh workflow run agent-implement.lock.yml --repo "$REPO" --ref "$STACK_BRANCH" \
764
771
  -f issue-number="$ISSUE_NUMBER" \
765
772
  -f attempts_so_far="$ATTEMPTS"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plainconceptsplatform/workflows",
3
- "version": "1.6.30",
3
+ "version": "1.6.42",
4
4
  "description": "Install and update Platform GitHub agentic workflows.",
5
5
  "keywords": [
6
6
  "github-actions",