@plainconceptsplatform/workflows 1.3.1 → 1.5.0

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.
Files changed (48) hide show
  1. package/dist/catalog-installation.d.ts +2 -0
  2. package/dist/catalog-installation.js +44 -2
  3. package/dist/index.js +3 -0
  4. package/dist/stack-defaults.d.ts +1 -0
  5. package/dist/stack-defaults.js +13 -1
  6. package/dist/worker-env.js +2 -3
  7. package/loops/actions/add-issue-labels/action.yml +50 -50
  8. package/loops/actions/agent-output.cjs +17 -17
  9. package/loops/actions/apply-agent-bundle/action.yml +24 -24
  10. package/loops/actions/apply-agent-comments/action.yml +42 -42
  11. package/loops/actions/apply-agent-labels/action.yml +55 -55
  12. package/loops/actions/apply-agent-output/action.yml +5 -0
  13. package/loops/actions/cleanup-artifacts/action.yml +91 -91
  14. package/loops/actions/close-agent-issues/action.yml +43 -43
  15. package/loops/actions/create-agent-issues/action.yml +64 -3
  16. package/loops/actions/create-issue-comment/action.yml +29 -29
  17. package/loops/actions/download-agent-output/action.yml +53 -53
  18. package/loops/actions/housekeeping/action.yml +95 -1
  19. package/loops/actions/link-pr-to-issue/action.yml +40 -40
  20. package/loops/actions/list-open-issues/action.yml +33 -33
  21. package/loops/actions/load-issue-context/action.yml +45 -45
  22. package/loops/actions/merge-agent-pr/action.yml +49 -49
  23. package/loops/actions/push-agent-branch/action.yml +45 -45
  24. package/loops/actions/remove-issue-labels/action.yml +37 -37
  25. package/loops/actions/update-agent-issues/action.yml +58 -58
  26. package/loops/actions/validate-merge-gate-output/action.yml +5 -0
  27. package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +8 -2
  28. package/loops/actions/validate-refine-output/validate-refine-output.sh +30 -1
  29. package/loops/actions/validate-review-output/action.yml +35 -35
  30. package/loops/actions/validate-triage-output/action.yml +36 -36
  31. package/loops/actions/verify-composite-actions/action.yml +9 -9
  32. package/loops/actions/verify-refine-output/action.yml +9 -9
  33. package/loops/actions/verify-refine-output/verify-refine-output.sh +17 -0
  34. package/loops/actions/verify-route-matrix/action.yml +9 -9
  35. package/loops/actions/verify-route-matrix/verify-route-matrix.sh +221 -7
  36. package/loops/docs/diagrams.md +90 -12
  37. package/loops/scripts/compile-agent-workflows.mjs +76 -2
  38. package/loops/templates/agentics/agentics-maintenance.yml +121 -121
  39. package/loops/templates/issues/bug_report.yml +109 -109
  40. package/loops/templates/issues/feature_request.yml +75 -75
  41. package/loops/templates/release/github-release.yml +30 -30
  42. package/loops/workflows/agent-implement.md +43 -8
  43. package/loops/workflows/agent-merge-gate.md +102 -6
  44. package/loops/workflows/agent-refine.md +152 -5
  45. package/loops/workflows/authorize-bot-work.yml +105 -105
  46. package/loops/workflows/shared/platform-defaults.md +23 -23
  47. package/loops/workflows/work-router.yml +345 -2
  48. package/package.json +1 -1
@@ -1,91 +1,91 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/cleanup-artifacts/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Clean up artifacts
3
- description: Delete workflow artifacts older than the configured retention period.
4
- inputs:
5
- token:
6
- description: GitHub token with actions write access.
7
- required: true
8
- artifact-retention-days:
9
- description: Retention period in days. Invalid or empty values use 3.
10
- required: false
11
- default: ''
12
- runs:
13
- using: composite
14
- steps:
15
- - name: Delete expired artifacts
16
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
17
- env:
18
- ARTIFACT_RETENTION_DAYS: ${{ inputs.artifact-retention-days }}
19
- with:
20
- github-token: ${{ inputs.token }}
21
- script: |
22
- const parsedDays = Number.parseInt(process.env.ARTIFACT_RETENTION_DAYS, 10);
23
- const days = Number.isInteger(parsedDays) && parsedDays > 0 ? parsedDays : 3;
24
- const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
25
-
26
- core.info(`Retention: ${days} days (cutoff ${cutoff.toISOString()})`);
27
-
28
- const artifacts = await github.paginate(github.rest.actions.listArtifactsForRepo, {
29
- ...context.repo,
30
- per_page: 100,
31
- });
32
-
33
- const expired = artifacts.filter((artifact) => new Date(artifact.created_at) < cutoff);
34
-
35
- let deleted = 0;
36
- const failed = [];
37
-
38
- for (const artifact of expired) {
39
- // GitHub answers a delete with a 5xx now and then. Three tries, because the
40
- // alternative is a red daily run over one artifact that tomorrow's run would
41
- // have removed anyway.
42
- let lastError;
43
- for (let attempt = 1; attempt <= 3; attempt += 1) {
44
- try {
45
- await github.rest.actions.deleteArtifact({
46
- ...context.repo,
47
- artifact_id: artifact.id,
48
- });
49
-
50
- core.info(`Deleted ${artifact.name} (created ${artifact.created_at})`);
51
- deleted += 1;
52
- lastError = undefined;
53
- break;
54
- } catch (error) {
55
- lastError = error;
56
- if (attempt < 3) await new Promise((wait) => setTimeout(wait, attempt * 2000));
57
- }
58
- }
59
-
60
- if (lastError) {
61
- core.warning(
62
- `Could not delete ${artifact.name} (${artifact.id}) after 3 tries: ${lastError.message}`,
63
- );
64
- failed.push({ name: artifact.name, status: lastError.status });
65
- }
66
- }
67
-
68
- await core.summary
69
- .addHeading('Artifact cleanup', 2)
70
- .addList([
71
- `Retention: ${days} days (cutoff ${cutoff.toISOString()})`,
72
- `Deleted: ${deleted}`,
73
- `Failed to delete: ${failed.length}`,
74
- `Within retention: ${artifacts.length - expired.length}`,
75
- ])
76
- .write();
77
-
78
- // Deleting nothing at all while expired artifacts exist is a permission or
79
- // configuration problem, and has to be loud. One stubborn artifact among many is
80
- // the API having a bad minute: tomorrow's run picks it up, and failing here only
81
- // teaches people to ignore a red cleanup. A 4xx is never transient, so it fails
82
- // whatever else succeeded.
83
- const names = failed.map((entry) => entry.name).join(', ');
84
- const structural = failed.some((entry) => entry.status && entry.status < 500);
85
- if (failed.length > 0 && (deleted === 0 || structural)) {
86
- core.setFailed(`${failed.length} artifact(s) could not be deleted: ${names}`);
87
- } else if (failed.length > 0) {
88
- core.warning(
89
- `${failed.length} artifact(s) survived this run and will be retried tomorrow: ${names}`,
90
- );
91
- }
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/cleanup-artifacts/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Clean up artifacts
3
+ description: Delete workflow artifacts older than the configured retention period.
4
+ inputs:
5
+ token:
6
+ description: GitHub token with actions write access.
7
+ required: true
8
+ artifact-retention-days:
9
+ description: Retention period in days. Invalid or empty values use 3.
10
+ required: false
11
+ default: ''
12
+ runs:
13
+ using: composite
14
+ steps:
15
+ - name: Delete expired artifacts
16
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
17
+ env:
18
+ ARTIFACT_RETENTION_DAYS: ${{ inputs.artifact-retention-days }}
19
+ with:
20
+ github-token: ${{ inputs.token }}
21
+ script: |
22
+ const parsedDays = Number.parseInt(process.env.ARTIFACT_RETENTION_DAYS, 10);
23
+ const days = Number.isInteger(parsedDays) && parsedDays > 0 ? parsedDays : 3;
24
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
25
+
26
+ core.info(`Retention: ${days} days (cutoff ${cutoff.toISOString()})`);
27
+
28
+ const artifacts = await github.paginate(github.rest.actions.listArtifactsForRepo, {
29
+ ...context.repo,
30
+ per_page: 100,
31
+ });
32
+
33
+ const expired = artifacts.filter((artifact) => new Date(artifact.created_at) < cutoff);
34
+
35
+ let deleted = 0;
36
+ const failed = [];
37
+
38
+ for (const artifact of expired) {
39
+ // GitHub answers a delete with a 5xx now and then. Three tries, because the
40
+ // alternative is a red daily run over one artifact that tomorrow's run would
41
+ // have removed anyway.
42
+ let lastError;
43
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
44
+ try {
45
+ await github.rest.actions.deleteArtifact({
46
+ ...context.repo,
47
+ artifact_id: artifact.id,
48
+ });
49
+
50
+ core.info(`Deleted ${artifact.name} (created ${artifact.created_at})`);
51
+ deleted += 1;
52
+ lastError = undefined;
53
+ break;
54
+ } catch (error) {
55
+ lastError = error;
56
+ if (attempt < 3) await new Promise((wait) => setTimeout(wait, attempt * 2000));
57
+ }
58
+ }
59
+
60
+ if (lastError) {
61
+ core.warning(
62
+ `Could not delete ${artifact.name} (${artifact.id}) after 3 tries: ${lastError.message}`,
63
+ );
64
+ failed.push({ name: artifact.name, status: lastError.status });
65
+ }
66
+ }
67
+
68
+ await core.summary
69
+ .addHeading('Artifact cleanup', 2)
70
+ .addList([
71
+ `Retention: ${days} days (cutoff ${cutoff.toISOString()})`,
72
+ `Deleted: ${deleted}`,
73
+ `Failed to delete: ${failed.length}`,
74
+ `Within retention: ${artifacts.length - expired.length}`,
75
+ ])
76
+ .write();
77
+
78
+ // Deleting nothing at all while expired artifacts exist is a permission or
79
+ // configuration problem, and has to be loud. One stubborn artifact among many is
80
+ // the API having a bad minute: tomorrow's run picks it up, and failing here only
81
+ // teaches people to ignore a red cleanup. A 4xx is never transient, so it fails
82
+ // whatever else succeeded.
83
+ const names = failed.map((entry) => entry.name).join(', ');
84
+ const structural = failed.some((entry) => entry.status && entry.status < 500);
85
+ if (failed.length > 0 && (deleted === 0 || structural)) {
86
+ core.setFailed(`${failed.length} artifact(s) could not be deleted: ${names}`);
87
+ } else if (failed.length > 0) {
88
+ core.warning(
89
+ `${failed.length} artifact(s) survived this run and will be retried tomorrow: ${names}`,
90
+ );
91
+ }
@@ -1,43 +1,43 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/close-agent-issues/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Close agent issues
3
- description: Close every close_issue item from agent_output.json.
4
- inputs:
5
- output-file:
6
- description: Path to agent_output.json.
7
- required: true
8
- token:
9
- description: GitHub token with issues:write.
10
- required: true
11
- runs:
12
- using: composite
13
- steps:
14
- - name: Close issues
15
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
16
- env:
17
- AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
18
- OUTPUT_FILE: ${{ inputs.output-file }}
19
- with:
20
- github-token: ${{ inputs.token }}
21
- script: |
22
- const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
23
- const items = readAgentItems(process.env.OUTPUT_FILE, 'close_issue');
24
-
25
- let closed = 0;
26
-
27
- for (const item of items) {
28
- if (!item.item_number) {
29
- core.warning(`close_issue item has no item_number, skipping: ${JSON.stringify(item)}`);
30
- continue;
31
- }
32
-
33
- await github.rest.issues.update({
34
- ...context.repo,
35
- issue_number: Number(item.item_number),
36
- state: 'closed',
37
- state_reason: item.state_reason === 'not_planned' ? 'not_planned' : 'completed',
38
- });
39
-
40
- closed += 1;
41
- }
42
-
43
- core.info(`Closed ${closed} issue(s).`);
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/close-agent-issues/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Close agent issues
3
+ description: Close every close_issue item from agent_output.json.
4
+ inputs:
5
+ output-file:
6
+ description: Path to agent_output.json.
7
+ required: true
8
+ token:
9
+ description: GitHub token with issues:write.
10
+ required: true
11
+ runs:
12
+ using: composite
13
+ steps:
14
+ - name: Close issues
15
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
16
+ env:
17
+ AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
18
+ OUTPUT_FILE: ${{ inputs.output-file }}
19
+ with:
20
+ github-token: ${{ inputs.token }}
21
+ script: |
22
+ const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
23
+ const items = readAgentItems(process.env.OUTPUT_FILE, 'close_issue');
24
+
25
+ let closed = 0;
26
+
27
+ for (const item of items) {
28
+ if (!item.item_number) {
29
+ core.warning(`close_issue item has no item_number, skipping: ${JSON.stringify(item)}`);
30
+ continue;
31
+ }
32
+
33
+ await github.rest.issues.update({
34
+ ...context.repo,
35
+ issue_number: Number(item.item_number),
36
+ state: 'closed',
37
+ state_reason: item.state_reason === 'not_planned' ? 'not_planned' : 'completed',
38
+ });
39
+
40
+ closed += 1;
41
+ }
42
+
43
+ core.info(`Closed ${closed} issue(s).`);
@@ -8,6 +8,12 @@ inputs:
8
8
  token:
9
9
  description: GitHub token with issues:write.
10
10
  required: true
11
+ parent-issue:
12
+ description: >-
13
+ The split parent, set only when the run is a split. Each created issue is stamped with the
14
+ split-parent marker and linked as a sub-issue of it, and temporary ids are resolved.
15
+ required: false
16
+ default: ''
11
17
  outputs:
12
18
  created-count:
13
19
  description: Number of issues created.
@@ -15,6 +21,9 @@ outputs:
15
21
  first-issue-number:
16
22
  description: Issue number of the first issue created, or empty.
17
23
  value: ${{ steps.create.outputs.first-issue-number }}
24
+ created-numbers:
25
+ description: Space-separated numbers of every issue created, in order.
26
+ value: ${{ steps.create.outputs.created-numbers }}
18
27
  runs:
19
28
  using: composite
20
29
  steps:
@@ -24,13 +33,23 @@ runs:
24
33
  env:
25
34
  AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
26
35
  OUTPUT_FILE: ${{ inputs.output-file }}
36
+ PARENT_ISSUE: ${{ inputs.parent-issue }}
27
37
  with:
28
38
  github-token: ${{ inputs.token }}
29
39
  script: |
30
40
  const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
31
41
  const items = readAgentItems(process.env.OUTPUT_FILE, 'create_issue');
42
+ // Set only for a split. The agent writes `<!-- split-parent: N -->` in every child, but
43
+ // gh-aw's sanitizer strips HTML comments before agent_output.json is written, so the
44
+ // marker never reaches this file (Lyceum #45, run 38747143251: three markers in
45
+ // safeoutputs.jsonl, none in agent_output.json). gh-aw used to link the children as
46
+ // sub-issues itself; staged, nothing does, and the hand-over found no children. So the
47
+ // workflow stamps the marker and links the sub-issue here, from a value it owns.
48
+ const parent = /^[0-9]+$/.test(process.env.PARENT_ISSUE || '') ? Number(process.env.PARENT_ISSUE) : null;
49
+ const marker = parent ? `<!-- split-parent: ${parent} -->` : '';
32
50
 
33
51
  const created = [];
52
+ const temporaryIds = {};
34
53
 
35
54
  for (const item of items) {
36
55
  if (!item.title) {
@@ -38,15 +57,57 @@ runs:
38
57
  continue;
39
58
  }
40
59
 
60
+ let body = item.body ?? '';
61
+ if (parent && !body.includes(marker)) body = `${body}\n\n${marker}`;
62
+
41
63
  const { data } = await github.rest.issues.create({
42
64
  ...context.repo,
43
65
  title: item.title,
44
- body: item.body ?? '',
66
+ body,
45
67
  });
46
68
 
47
- created.push(data.number);
69
+ created.push({ number: data.number, body });
70
+ const tid = String(item.temporary_id ?? '').replace(/^#/, '');
71
+ if (tid) temporaryIds[tid] = data.number;
48
72
  core.info(`Created #${data.number}: ${item.title}`);
73
+
74
+ if (parent) {
75
+ try {
76
+ await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/sub_issues', {
77
+ ...context.repo,
78
+ issue_number: parent,
79
+ sub_issue_id: data.id,
80
+ });
81
+ core.info(`Linked #${data.number} under #${parent}`);
82
+ } catch (error) {
83
+ // The marker above is the fallback the hand-over searches, so a failed link is
84
+ // degraded, not lost.
85
+ core.warning(`Could not link #${data.number} under #${parent}: ${error.message}`);
86
+ }
87
+ }
88
+ }
89
+
90
+ // The agent references siblings by the temporary id it gave them, because their numbers
91
+ // do not exist until now: "Blocked by #aw_struct_modules". gh-aw resolves these when it
92
+ // creates the issues; staged, they reached the issues unresolved. Rewrite them in the
93
+ // children and in the parent's checklist, which was updated before this step ran.
94
+ const resolve = text => text.replace(/#(aw_[A-Za-z0-9_]+)/g, (whole, id) => (temporaryIds[id] ? `#${temporaryIds[id]}` : whole));
95
+ if (Object.keys(temporaryIds).length > 0) {
96
+ for (const issue of created) {
97
+ const next = resolve(issue.body);
98
+ if (next !== issue.body) {
99
+ await github.rest.issues.update({ ...context.repo, issue_number: issue.number, body: next });
100
+ }
101
+ }
102
+ if (parent) {
103
+ const { data: current } = await github.rest.issues.get({ ...context.repo, issue_number: parent });
104
+ const next = resolve(current.body ?? '');
105
+ if (next !== (current.body ?? '')) {
106
+ await github.rest.issues.update({ ...context.repo, issue_number: parent, body: next });
107
+ }
108
+ }
49
109
  }
50
110
 
51
111
  core.setOutput('created-count', String(created.length));
52
- core.setOutput('first-issue-number', created.length > 0 ? String(created[0]) : '');
112
+ core.setOutput('first-issue-number', created.length > 0 ? String(created[0].number) : '');
113
+ core.setOutput('created-numbers', created.map(issue => issue.number).join(' '));
@@ -1,29 +1,29 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/create-issue-comment/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Create issue comment
3
- description: Create a comment on an issue or pull request.
4
- inputs:
5
- token:
6
- description: GitHub token used by github-script.
7
- required: true
8
- issue-number:
9
- description: Issue or pull request number.
10
- required: true
11
- body:
12
- description: Complete Markdown comment body.
13
- required: true
14
- runs:
15
- using: composite
16
- steps:
17
- - name: Create comment
18
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
19
- env:
20
- ISSUE_NUMBER: ${{ inputs.issue-number }}
21
- BODY: ${{ inputs.body }}
22
- with:
23
- github-token: ${{ inputs.token }}
24
- script: |
25
- await github.rest.issues.createComment({
26
- ...context.repo,
27
- issue_number: Number(process.env.ISSUE_NUMBER),
28
- body: process.env.BODY,
29
- });
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/create-issue-comment/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Create issue comment
3
+ description: Create a comment on an issue or pull request.
4
+ inputs:
5
+ token:
6
+ description: GitHub token used by github-script.
7
+ required: true
8
+ issue-number:
9
+ description: Issue or pull request number.
10
+ required: true
11
+ body:
12
+ description: Complete Markdown comment body.
13
+ required: true
14
+ runs:
15
+ using: composite
16
+ steps:
17
+ - name: Create comment
18
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
19
+ env:
20
+ ISSUE_NUMBER: ${{ inputs.issue-number }}
21
+ BODY: ${{ inputs.body }}
22
+ with:
23
+ github-token: ${{ inputs.token }}
24
+ script: |
25
+ await github.rest.issues.createComment({
26
+ ...context.repo,
27
+ issue_number: Number(process.env.ISSUE_NUMBER),
28
+ body: process.env.BODY,
29
+ });
@@ -1,53 +1,53 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/download-agent-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Download agent output
3
- description: Download the agent artifact and expose the output JSON and bundle paths.
4
- inputs:
5
- artifact-name:
6
- description: >-
7
- Artifact name to download. gh-aw prefixes it with the value it derives from the
8
- workflow's inputs, so a workflow_call worker must pass the activation job's
9
- artifact_prefix output followed by "agent", not the bare name.
10
- required: true
11
- outputs:
12
- output-file:
13
- description: Path to agent_output.json, or empty if not found.
14
- value: ${{ steps.download.outputs.output-file }}
15
- bundle-file:
16
- description: Path to the git bundle, or empty if not found.
17
- value: ${{ steps.download.outputs.bundle-file }}
18
- item-count:
19
- description: Number of items in the agent output.
20
- value: ${{ steps.download.outputs.item-count }}
21
- runs:
22
- using: composite
23
- steps:
24
- - name: Download agent artifact
25
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
26
- with:
27
- name: ${{ inputs.artifact-name }}
28
- path: agent-artifact
29
- - name: Locate output and bundle
30
- id: download
31
- shell: bash
32
- env:
33
- ARTIFACT_NAME: ${{ inputs.artifact-name }}
34
- run: |
35
- set -euo pipefail
36
- OUTPUT_FILE=""
37
- BUNDLE_FILE=""
38
- ITEM_COUNT="0"
39
-
40
- if [ -f "agent-artifact/agent_output.json" ]; then
41
- OUTPUT_FILE="agent-artifact/agent_output.json"
42
- ITEM_COUNT=$(jq '.items | length' "$OUTPUT_FILE")
43
- BUNDLE_FILE=$(ls agent-artifact/aw-*.bundle 2>/dev/null | head -1 || true)
44
- else
45
- echo "::error::Artifact '$ARTIFACT_NAME' contained no agent_output.json. Nothing the agent proposed will be applied."
46
- exit 1
47
- fi
48
-
49
- echo "Artifact '$ARTIFACT_NAME' carries $ITEM_COUNT item(s)."
50
-
51
- echo "output-file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
52
- echo "bundle-file=$BUNDLE_FILE" >> "$GITHUB_OUTPUT"
53
- echo "item-count=$ITEM_COUNT" >> "$GITHUB_OUTPUT"
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/download-agent-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Download agent output
3
+ description: Download the agent artifact and expose the output JSON and bundle paths.
4
+ inputs:
5
+ artifact-name:
6
+ description: >-
7
+ Artifact name to download. gh-aw prefixes it with the value it derives from the
8
+ workflow's inputs, so a workflow_call worker must pass the activation job's
9
+ artifact_prefix output followed by "agent", not the bare name.
10
+ required: true
11
+ outputs:
12
+ output-file:
13
+ description: Path to agent_output.json, or empty if not found.
14
+ value: ${{ steps.download.outputs.output-file }}
15
+ bundle-file:
16
+ description: Path to the git bundle, or empty if not found.
17
+ value: ${{ steps.download.outputs.bundle-file }}
18
+ item-count:
19
+ description: Number of items in the agent output.
20
+ value: ${{ steps.download.outputs.item-count }}
21
+ runs:
22
+ using: composite
23
+ steps:
24
+ - name: Download agent artifact
25
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
26
+ with:
27
+ name: ${{ inputs.artifact-name }}
28
+ path: agent-artifact
29
+ - name: Locate output and bundle
30
+ id: download
31
+ shell: bash
32
+ env:
33
+ ARTIFACT_NAME: ${{ inputs.artifact-name }}
34
+ run: |
35
+ set -euo pipefail
36
+ OUTPUT_FILE=""
37
+ BUNDLE_FILE=""
38
+ ITEM_COUNT="0"
39
+
40
+ if [ -f "agent-artifact/agent_output.json" ]; then
41
+ OUTPUT_FILE="agent-artifact/agent_output.json"
42
+ ITEM_COUNT=$(jq '.items | length' "$OUTPUT_FILE")
43
+ BUNDLE_FILE=$(ls agent-artifact/aw-*.bundle 2>/dev/null | head -1 || true)
44
+ else
45
+ echo "::error::Artifact '$ARTIFACT_NAME' contained no agent_output.json. Nothing the agent proposed will be applied."
46
+ exit 1
47
+ fi
48
+
49
+ echo "Artifact '$ARTIFACT_NAME' carries $ITEM_COUNT item(s)."
50
+
51
+ echo "output-file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
52
+ echo "bundle-file=$BUNDLE_FILE" >> "$GITHUB_OUTPUT"
53
+ echo "item-count=$ITEM_COUNT" >> "$GITHUB_OUTPUT"