Skip to content

Commit 6782703

Browse files
authored
Execute native agent tasks in the reusable workflow (#1751)
* Execute native agent tasks in reusable workflow * Enforce native agent task workflow contract * Expose native task output projections * Harden native agent task execution * Add reusable workflow interface contract * Harden native task artifact uploads * Remove stale bridge migration guidance
1 parent 028eacf commit 6782703

14 files changed

Lines changed: 915 additions & 266 deletions

.github/scripts/run-agent-task/build-codebox-task-request.mjs

Lines changed: 43 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ function requiredString(name) {
2020
return value
2121
}
2222

23-
function optionalString(name) {
24-
const value = process.env[name]?.trim() ?? ""
25-
return value || undefined
26-
}
27-
2823
function booleanEnv(name) {
2924
return process.env[name] === "true"
3025
}
@@ -37,22 +32,39 @@ function numberEnv(name) {
3732
return value
3833
}
3934

35+
const MAX_OUTPUT_CHARS = 8192
36+
4037
function output(name, value) {
41-
appendFileSync(process.env.GITHUB_OUTPUT, `${name}<<__WP_CODEBOX_OUTPUT__\n${String(value)}\n__WP_CODEBOX_OUTPUT__\n`)
38+
const rendered = String(value)
39+
if (rendered.length > MAX_OUTPUT_CHARS) throw new Error(`${name} exceeds the ${MAX_OUTPUT_CHARS}-character workflow output limit.`)
40+
appendFileSync(process.env.GITHUB_OUTPUT, `${name}<<__WP_CODEBOX_OUTPUT__\n${rendered}\n__WP_CODEBOX_OUTPUT__\n`)
4241
}
4342

44-
const artifactDeclarations = parseJson("ARTIFACT_DECLARATIONS", [], "array")
45-
const runnerRecipe = optionalString("RUNNER_RECIPE")
46-
const runAgent = booleanEnv("RUN_AGENT")
47-
const dryRun = booleanEnv("DRY_RUN")
43+
function repositoryList(name) {
44+
const repositories = (process.env[name] || "").split(",").map((value) => value.trim()).filter(Boolean)
45+
if (repositories.some((repository) => !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository))) {
46+
throw new Error(`${name} must contain comma-separated OWNER/REPO values.`)
47+
}
48+
return repositories
49+
}
4850

49-
if (!runnerRecipe && runAgent && !dryRun) {
50-
throw new Error("RUNNER_RECIPE may be omitted only when RUN_AGENT=false or DRY_RUN=true. The executable workflow in wp-codebox PR #1751 must land before a live agent run can omit it.")
51+
function commandList(name) {
52+
const value = parseJson(name, [], "array")
53+
return value.map((entry, index) => {
54+
if (typeof entry === "string" && entry.trim()) return { command: entry.trim(), description: entry.trim() }
55+
if (!entry || typeof entry !== "object" || Array.isArray(entry) || typeof entry.command !== "string" || !entry.command.trim()) {
56+
throw new Error(`${name}[${index}] must be a non-empty command string or an object with a non-empty command.`)
57+
}
58+
if (entry.description !== undefined && (typeof entry.description !== "string" || !entry.description.trim())) {
59+
throw new Error(`${name}[${index}].description must be a non-empty string when provided.`)
60+
}
61+
return { command: entry.command.trim(), description: typeof entry.description === "string" ? entry.description.trim() : entry.command.trim() }
62+
})
5163
}
5264

65+
const artifactDeclarations = parseJson("ARTIFACT_DECLARATIONS", [], "array")
5366
const request = {
5467
schema: "wp-codebox/agent-task-workflow-request/v1",
55-
...(runnerRecipe ? { runner_recipe: runnerRecipe } : {}),
5668
agent_bundle: requiredString("AGENT_BUNDLE"),
5769
workload: {
5870
id: process.env.WORKLOAD_ID || "agent-task",
@@ -68,23 +80,18 @@ const request = {
6880
},
6981
runner_workspace: parseJson("RUNNER_WORKSPACE_CONFIG", {}, "object"),
7082
validation_dependencies: process.env.VALIDATION_DEPENDENCIES || "",
71-
context_repositories: parseJson("CONTEXT_REPOSITORIES", [], "array"),
72-
verification_commands: parseJson("VERIFICATION_COMMANDS", [], "array"),
73-
drift_checks: parseJson("DRIFT_CHECKS", [], "array"),
74-
workspace_contract_checks: parseJson("WORKSPACE_CONTRACT_CHECKS", {}, "object"),
75-
actions_artifact_downloads: parseJson("ACTIONS_ARTIFACT_DOWNLOADS", [], "array"),
83+
verification_commands: commandList("VERIFICATION_COMMANDS"),
84+
drift_checks: commandList("DRIFT_CHECKS"),
7685
success: {
7786
requires_pr: booleanEnv("SUCCESS_REQUIRES_PR"),
78-
completion_outcomes: parseJson("SUCCESS_COMPLETION_OUTCOMES", [], "array"),
7987
},
8088
access: {
81-
access_token_repos: process.env.ACCESS_TOKEN_REPOS || process.env.TARGET_REPO || "",
89+
access_token_repos: repositoryList("ACCESS_TOKEN_REPOS"),
8290
require_access_token: booleanEnv("REQUIRE_ACCESS_TOKEN"),
8391
allowed_repos: parseJson("ALLOWED_REPOS", [], "array"),
8492
},
8593
limits: {
8694
max_turns: numberEnv("MAX_TURNS"),
87-
step_budget: numberEnv("STEP_BUDGET"),
8895
time_budget_ms: numberEnv("TIME_BUDGET_MS"),
8996
},
9097
artifacts: {
@@ -94,48 +101,29 @@ const request = {
94101
replay_bundle_name: process.env.REPLAY_BUNDLE_ARTIFACT_NAME || "",
95102
},
96103
outputs: {
97-
tool_results_key: process.env.TOOL_RESULTS_KEY || "tool_results",
98104
projections: parseJson("OUTPUT_PROJECTIONS", {}, "object"),
99105
},
100106
callback_data: parseJson("CALLBACK_DATA", {}, "object"),
101-
run_agent: runAgent,
102-
dry_run: dryRun,
107+
run_agent: booleanEnv("RUN_AGENT"),
108+
dry_run: booleanEnv("DRY_RUN"),
103109
}
104110

105-
const runId = `${request.workload.id}-${process.env.GITHUB_RUN_ID || "local"}`.replace(/[^A-Za-z0-9._-]+/g, "-")
106-
const status = !request.run_agent ? "skipped" : request.dry_run ? "dry-run" : "planned"
107-
const result = {
108-
schema: "wp-codebox/agent-task-workflow-result/v1",
109-
run_id: runId,
110-
status,
111-
request_path: ".codebox/agent-task-request.json",
112-
transcript: {
113-
artifact_name: request.artifacts.transcript_name,
114-
},
115-
artifacts: {
116-
declarations: artifactDeclarations,
117-
expected: request.artifacts.expected,
118-
replay_bundle_name: request.artifacts.replay_bundle_name,
119-
},
120-
outputs: {
121-
engine_data: {},
122-
projections: request.outputs.projections,
123-
},
124-
access: {
125-
credential_mode: request.access.require_access_token ? "app-token" : "default",
126-
},
111+
if (request.access.allowed_repos.some((repository) => typeof repository !== "string" || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository))) {
112+
throw new Error("ALLOWED_REPOS must be a JSON array of OWNER/REPO values.")
113+
}
114+
request.access.allowed_repos = [...new Set(request.access.allowed_repos.map((repository) => repository.toLowerCase()))]
115+
request.access.access_token_repos = [...new Set(request.access.access_token_repos.map((repository) => repository.toLowerCase()))]
116+
request.target_repo = request.target_repo.toLowerCase()
117+
if (!request.access.allowed_repos.includes(request.target_repo)) {
118+
throw new Error("ALLOWED_REPOS must explicitly include TARGET_REPO.")
119+
}
120+
if (!request.access.access_token_repos.includes(request.target_repo)) {
121+
throw new Error("ACCESS_TOKEN_REPOS must explicitly include TARGET_REPO.")
127122
}
128123

124+
const runId = `${request.workload.id}-${process.env.GITHUB_RUN_ID || "local"}`.replace(/[^A-Za-z0-9._-]+/g, "-")
129125
mkdirSync(".codebox", { recursive: true })
130126
writeFileSync(".codebox/agent-task-request.json", `${JSON.stringify(request, null, 2)}\n`)
131-
writeFileSync(".codebox/agent-task-workflow-result.json", `${JSON.stringify(result, null, 2)}\n`)
132127

133-
output("job_status", status)
134-
output("transcript_json", request.artifacts.transcript_name)
135-
output("transcript_summary", `${request.workload.label}: ${status}`)
136-
output("engine_data_json", JSON.stringify(result.outputs.engine_data))
137-
output("credential_mode", result.access.credential_mode)
138-
output("declared_artifacts_json", JSON.stringify(artifactDeclarations))
139-
output("request_path", result.request_path)
140-
output("result_path", ".codebox/agent-task-workflow-result.json")
128+
output("request_path", ".codebox/agent-task-request.json")
141129
output("run_id", runId)

0 commit comments

Comments
 (0)