@openhands/extensions 0.24.0 → 0.26.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.
- package/.claude-plugin/marketplace.json +30 -0
- package/.codex-plugin/marketplace.json +30 -0
- package/.github/pull_request_template.md +17 -2
- package/.github/scripts/check_pr_description.py +64 -5
- package/.github/scripts/markdown_sections.py +95 -0
- package/.github/workflows/build-vscode-app.yml +30 -0
- package/.github/workflows/check-extensions.yml +3 -0
- package/.github/workflows/tests.yml +26 -0
- package/.plugin/marketplace.json +30 -0
- package/.release-please-manifest.json +1 -1
- package/AGENTS.md +3 -2
- package/README.md +10 -3
- package/apps/README.md +20 -0
- package/apps/catalog.json +10 -0
- package/apps/index.d.ts +11 -0
- package/apps/index.js +5 -0
- package/apps/vscode/README.md +13 -0
- package/apps/vscode/canvas-extension.json +19 -0
- package/apps/vscode/extension.js +114 -0
- package/apps/vscode/scripts/package.mjs +84 -0
- package/automations/bundle-index.js +18 -14
- package/automations/catalog/github-agents-md-maintainer/manifest.json +1 -1
- package/automations/catalog/github-pr-reviewer/manifest.json +19 -8
- package/automations/catalog/github-stale-ci-pr-closer/manifest.json +71 -0
- package/automations/catalog/gitlab-issue-to-mr/manifest.json +1 -1
- package/automations/catalog/news-digest/manifest.json +2 -2
- package/automations/catalog-index.js +2 -0
- package/index.d.ts +2 -0
- package/index.js +1 -0
- package/marketplaces/openhands-extensions.json +30 -0
- package/package.json +9 -1
- package/plugins/openhands/skills/openhands-api/README.md +1 -1
- package/plugins/openhands/skills/openhands-api/SKILL.md +3 -3
- package/plugins/openhands/skills/openhands-api/scripts/openhands_api.py +13 -0
- package/plugins/openhands/skills/openhands-automation/SKILL.md +66 -4
- package/plugins/openhands/skills/openhands-automation/references/custom-automation.md +21 -7
- package/plugins/openhands/skills/openhands-sdk/SKILL.md +1 -0
- package/plugins/pr-review/skills/github-pr-review/SKILL.md +12 -2
- package/plugins/qa-changes/skills/github-pr-review/SKILL.md +12 -2
- package/plugins/runtime-api-configs/.claude-plugin/plugin.json +20 -0
- package/plugins/runtime-api-configs/.codex-plugin/plugin.json +20 -0
- package/plugins/runtime-api-configs/.plugin/plugin.json +20 -0
- package/plugins/runtime-api-configs/README.md +59 -0
- package/plugins/runtime-api-configs/SKILL.md +240 -0
- package/plugins/runtime-api-configs/scripts/warm_runtime_configs.py +331 -0
- package/plugins/runtime-api-configs/tests/test_warm_runtime_configs.py +338 -0
- package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +223 -43
- package/plugins/vulnerability-remediation/skills/github/scripts/github_client.py +183 -15
- package/pyproject.toml +1 -1
- package/python/openhands_extensions/_version.py +1 -1
- package/scripts/check_plugin_resources.py +116 -0
- package/scripts/sync_extensions.py +32 -5
- package/skills/canvas-extension-api/SKILL.md +34 -3
- package/skills/canvas-extension-api/references/backend-safety.md +14 -0
- package/skills/canvas-extension-api/references/connections.md +24 -0
- package/skills/canvas-extension-api/references/sidecar-pattern.md +49 -10
- package/skills/canvas-extension-api/references/v1-contract.md +53 -3
- package/skills/canvas-extension-api/scripts/validate-extension.mjs +52 -0
- package/skills/canvas-extension-api/scripts/validate-extension.test.mjs +192 -1
- package/skills/github/scripts/agent_conversation.py +223 -43
- package/skills/github/scripts/github_client.py +183 -15
- package/skills/github-agents-md-maintainer/README.md +7 -0
- package/skills/github-agents-md-maintainer/SKILL.md +13 -6
- package/skills/github-agents-md-maintainer/scripts/github_client.py +183 -15
- package/skills/github-agents-md-maintainer/scripts/main.py +199 -29
- package/skills/github-delivery-watchdog/scripts/github_client.py +183 -15
- package/skills/github-issue-to-pr/scripts/agent_conversation.py +223 -43
- package/skills/github-issue-to-pr/scripts/github_client.py +183 -15
- package/skills/github-issue-to-pr/scripts/worker.py +10 -1
- package/skills/github-issue-triage/scripts/agent_conversation.py +223 -43
- package/skills/github-issue-triage/scripts/github_client.py +183 -15
- package/skills/github-pr-review/SKILL.md +12 -2
- package/skills/github-pr-reviewer/README.md +36 -0
- package/skills/github-pr-reviewer/SKILL.md +147 -4
- package/skills/github-pr-reviewer/scripts/agent_conversation.py +223 -43
- package/skills/github-pr-reviewer/scripts/github_client.py +183 -15
- package/skills/github-pr-reviewer/scripts/main.py +17 -1
- package/skills/github-pr-reviewer/scripts/worker.py +842 -57
- package/skills/github-repo-monitor/scripts/github_client.py +183 -15
- package/skills/github-repo-monitor/scripts/main.py +32 -1
- package/skills/github-stale-ci-pr-closer/.claude-plugin/plugin.json +9 -0
- package/skills/github-stale-ci-pr-closer/.codex-plugin/plugin.json +9 -0
- package/skills/github-stale-ci-pr-closer/.plugin/plugin.json +9 -0
- package/skills/github-stale-ci-pr-closer/README.md +9 -0
- package/skills/github-stale-ci-pr-closer/SKILL.md +24 -0
- package/skills/github-stale-ci-pr-closer/commands/github-stale-ci-pr-closer.md +8 -0
- package/skills/github-stale-ci-pr-closer/scripts/worker.py +508 -0
- package/skills/github-stale-ci-pr-closer/tests/test_worker.py +353 -0
- package/skills/gitlab-issue-to-mr/README.md +12 -0
- package/skills/gitlab-issue-to-mr/SKILL.md +13 -6
- package/skills/gitlab-issue-to-mr/scripts/main.py +217 -42
- package/skills/incident-retrospective/SKILL.md +5 -5
- package/skills/index.js +34 -16
- package/skills/jira-issue-to-pr/README.md +12 -4
- package/skills/jira-issue-to-pr/SKILL.md +129 -18
- package/skills/jira-issue-to-pr/references/setup.md +8 -0
- package/skills/jira-issue-to-pr/scripts/main.py +181 -54
- package/skills/jira-issue-to-pr/scripts/setup_webhook.py +290 -0
- package/skills/linear-triage/SKILL.md +5 -5
- package/skills/news-digest/README.md +6 -0
- package/skills/news-digest/SKILL.md +19 -9
- package/skills/news-digest/commands/news-digest-setup.md +1 -1
- package/skills/news-digest/scripts/main.py +133 -15
- package/skills/openhands-api/README.md +1 -1
- package/skills/openhands-api/SKILL.md +3 -3
- package/skills/openhands-api/scripts/openhands_api.py +13 -0
- package/skills/openhands-automation/SKILL.md +66 -4
- package/skills/openhands-automation/references/custom-automation.md +21 -7
- package/skills/openhands-sdk/SKILL.md +1 -0
- package/skills/research-brief/SKILL.md +5 -5
- package/skills/slack-standup-digest/SKILL.md +5 -5
- package/skills/ticket-to-code-change/SKILL.md +6 -3
- package/skills/use-jev/.claude-plugin/plugin.json +8 -0
- package/skills/use-jev/.codex-plugin/plugin.json +8 -0
- package/skills/use-jev/.plugin/plugin.json +8 -0
- package/skills/use-jev/README.md +66 -0
- package/skills/use-jev/SKILL.md +66 -0
- package/tests/fixtures/automations/github-pr-reviewer.json +39 -24
- package/tests/test_agent_conversation_dispatch.py +212 -0
- package/tests/test_apps_catalog.py +26 -0
- package/tests/test_automation_setup.py +44 -0
- package/tests/test_check_pr_description.py +89 -6
- package/tests/test_github_automation_foundation.py +158 -0
- package/tests/test_github_repo_monitor.py +107 -0
- package/tests/test_github_reviewer_delivery.py +2247 -6
- package/tests/test_github_stale_ci_pr_closer.py +56 -0
- package/tests/test_jira_issue_to_pr.py +312 -0
- package/tests/test_jira_setup_webhook.py +309 -0
- package/tests/test_openhands_api_python.py +37 -0
- package/tests/test_skill_plugin_loading.py +25 -15
- package/tests/test_sync_extensions.py +102 -0
- package/tests/test_vscode_app.py +33 -0
- package/tests/vscode-app.test.mjs +43 -0
|
@@ -12,24 +12,62 @@ from urllib.parse import parse_qsl, urlencode, urlsplit
|
|
|
12
12
|
from urllib.request import Request, urlopen
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
# The secret name of the GitHub token an OpenHands Cloud or Enterprise user's
|
|
16
|
+
# connected GitHub integration provides: how this run's sandbox serves it, and
|
|
17
|
+
# how a conversation sees it in its environment.
|
|
18
|
+
CLOUD_GITHUB_TOKEN_SECRET = "github_token"
|
|
19
|
+
CLOUD_GITHUB_TOKEN_ENV = "GITHUB_TOKEN"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def is_cloud_run() -> bool:
|
|
23
|
+
"""Whether this run is on OpenHands Cloud or Enterprise.
|
|
24
|
+
|
|
25
|
+
The automation service hands a run the Agent Server URL only on a local
|
|
26
|
+
Agent Canvas; elsewhere the run talks to the OpenHands API instead.
|
|
27
|
+
"""
|
|
28
|
+
return bool(os.environ.get("OPENHANDS_CLOUD_API_URL")) and not os.environ.get(
|
|
29
|
+
"AGENT_SERVER_URL"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _load_cloud_secret(name: str) -> str | None:
|
|
34
|
+
"""Read one named secret of this run's sandbox from the OpenHands API."""
|
|
35
|
+
api = os.environ["OPENHANDS_CLOUD_API_URL"].rstrip("/")
|
|
36
|
+
request = Request(
|
|
37
|
+
f"{api}/api/v1/sandboxes/{os.environ['SANDBOX_ID']}/settings/secrets/{name}",
|
|
38
|
+
headers={"X-Session-API-Key": os.environ["SESSION_API_KEY"]},
|
|
39
|
+
)
|
|
40
|
+
try:
|
|
41
|
+
with urlopen(request, timeout=90) as response:
|
|
42
|
+
return response.read().decode().strip()
|
|
43
|
+
except HTTPError as exc:
|
|
44
|
+
if exc.code == 404:
|
|
45
|
+
return None
|
|
46
|
+
raise
|
|
47
|
+
|
|
48
|
+
|
|
15
49
|
def _load_secret(name: str) -> str:
|
|
16
|
-
"""Read one named secret from the environment or configured Agent
|
|
50
|
+
"""Read one named secret from the environment, or from the configured Agent
|
|
51
|
+
Server on a local run and the OpenHands API on a cloud one."""
|
|
17
52
|
value = os.environ.get(name)
|
|
18
53
|
if value:
|
|
19
54
|
return value
|
|
20
55
|
|
|
21
|
-
|
|
56
|
+
if is_cloud_run():
|
|
57
|
+
value = _load_cloud_secret(name)
|
|
58
|
+
else:
|
|
59
|
+
from openhands.sdk.workspace import RemoteWorkspace
|
|
22
60
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
61
|
+
workspace = RemoteWorkspace(
|
|
62
|
+
host=os.environ["AGENT_SERVER_URL"],
|
|
63
|
+
api_key=os.environ["SESSION_API_KEY"],
|
|
64
|
+
working_dir=os.environ.get("WORKSPACE_BASE", "/workspace"),
|
|
65
|
+
)
|
|
66
|
+
try:
|
|
67
|
+
secret = workspace.get_secrets([name]).get(name)
|
|
68
|
+
value = secret.get_value() if secret else None
|
|
69
|
+
finally:
|
|
70
|
+
workspace.reset_client()
|
|
33
71
|
if not value:
|
|
34
72
|
raise ValueError(f"The GitHub credential {name} is unavailable")
|
|
35
73
|
return value
|
|
@@ -95,11 +133,22 @@ class GitHubRepository:
|
|
|
95
133
|
"Expected the environment variable containing the GitHub token"
|
|
96
134
|
)
|
|
97
135
|
self.token_name = github_token_secret
|
|
98
|
-
|
|
136
|
+
try:
|
|
137
|
+
self.token = _load_secret(github_token_secret)
|
|
138
|
+
except ValueError:
|
|
139
|
+
if not is_cloud_run():
|
|
140
|
+
raise
|
|
141
|
+
# No secret is saved under that name, so use the user's connected
|
|
142
|
+
# GitHub integration, under the name a conversation sees it by.
|
|
143
|
+
self.token = _load_cloud_secret(CLOUD_GITHUB_TOKEN_SECRET)
|
|
144
|
+
if not self.token:
|
|
145
|
+
raise
|
|
146
|
+
self.token_name = CLOUD_GITHUB_TOKEN_ENV
|
|
99
147
|
self.conversation = conversation
|
|
100
148
|
self.conversation_id = str(conversation.id) if conversation else None
|
|
101
149
|
self.dispatcher = dispatcher
|
|
102
|
-
|
|
150
|
+
# A cloud run is not given a workspace base; its sandbox has /workspace.
|
|
151
|
+
self.workspace = Path(os.environ.get("WORKSPACE_BASE", "/workspace"))
|
|
103
152
|
self.project = self.workspace
|
|
104
153
|
self.evidence = self.workspace / "evidence"
|
|
105
154
|
self.evidence.mkdir(exist_ok=True)
|
|
@@ -111,10 +160,13 @@ class GitHubRepository:
|
|
|
111
160
|
|
|
112
161
|
@property
|
|
113
162
|
def github_instructions(self):
|
|
163
|
+
# A cloud conversation runs in a sandbox of its own, so this run's
|
|
164
|
+
# workspace path means nothing to it.
|
|
165
|
+
where = "your working directory" if is_cloud_run() else self.project
|
|
114
166
|
return (
|
|
115
167
|
f"Use `GH_TOKEN=${self.token_name} gh api` for GitHub requests. "
|
|
116
168
|
"Never print the credential value. "
|
|
117
|
-
f"Only {self.repository} is in scope. Work in {
|
|
169
|
+
f"Only {self.repository} is in scope. Work in {where}. "
|
|
118
170
|
"Do not modify the automation bundle or its configuration."
|
|
119
171
|
)
|
|
120
172
|
|
|
@@ -165,6 +217,122 @@ class GitHubRepository:
|
|
|
165
217
|
result.setdefault(item["context"], item["state"])
|
|
166
218
|
return result
|
|
167
219
|
|
|
220
|
+
def check_runs(self, sha):
|
|
221
|
+
"""Return every check run GitHub reported for one commit SHA.
|
|
222
|
+
|
|
223
|
+
Reading check runs needs no branch-protection or ruleset access, so this
|
|
224
|
+
is the head-eligibility signal the automation's own token can always
|
|
225
|
+
see. The endpoint answers with an object rather than a list, so it
|
|
226
|
+
paginates through ``gh`` instead of ``gh_pages``.
|
|
227
|
+
"""
|
|
228
|
+
runs = []
|
|
229
|
+
for page in range(1, 101):
|
|
230
|
+
data = self.gh(
|
|
231
|
+
"GET", f"/commits/{sha}/check-runs?per_page=100&page={page}"
|
|
232
|
+
)
|
|
233
|
+
batch = data.get("check_runs") or []
|
|
234
|
+
runs.extend(batch)
|
|
235
|
+
if len(runs) >= int(data.get("total_count") or 0) or not batch:
|
|
236
|
+
return runs
|
|
237
|
+
raise RuntimeError("GitHub check-run pagination exceeded limit")
|
|
238
|
+
|
|
239
|
+
def required_check_contexts(self, number):
|
|
240
|
+
"""Return the status contexts GitHub marks required on one pull request.
|
|
241
|
+
|
|
242
|
+
`isRequired` is the merge-policy source of truth and is PR-scoped, so it
|
|
243
|
+
is correct for a stacked PR whose symbolic base branch carries no branch
|
|
244
|
+
rules of its own. It is a field on each context in the head's status
|
|
245
|
+
check rollup and takes the pull request number, so a single GraphQL query
|
|
246
|
+
answers with the required check runs and any required commit statuses,
|
|
247
|
+
each already attributed to the exact head. Only contexts GitHub itself
|
|
248
|
+
reports as required are returned, so an optional workflow that fails
|
|
249
|
+
before creating any check run is not present.
|
|
250
|
+
"""
|
|
251
|
+
owner, name = self.repository.split("/", 1)
|
|
252
|
+
query = (
|
|
253
|
+
"query($owner:String!,$name:String!,$number:Int!){"
|
|
254
|
+
"repository(owner:$owner,name:$name){"
|
|
255
|
+
"pullRequest(number:$number){"
|
|
256
|
+
"commits(last:1){"
|
|
257
|
+
"nodes{"
|
|
258
|
+
"commit{"
|
|
259
|
+
"statusCheckRollup{"
|
|
260
|
+
"contexts(first:100){"
|
|
261
|
+
"pageInfo{hasNextPage}"
|
|
262
|
+
"nodes{"
|
|
263
|
+
"__typename"
|
|
264
|
+
" ... on CheckRun{name isRequired(pullRequestNumber:$number)}"
|
|
265
|
+
" ... on StatusContext{context isRequired(pullRequestNumber:$number)}"
|
|
266
|
+
"}"
|
|
267
|
+
"}"
|
|
268
|
+
"}"
|
|
269
|
+
"}"
|
|
270
|
+
"}"
|
|
271
|
+
"}"
|
|
272
|
+
"}"
|
|
273
|
+
"}"
|
|
274
|
+
"}"
|
|
275
|
+
)
|
|
276
|
+
data = self.api(
|
|
277
|
+
"POST",
|
|
278
|
+
"/graphql",
|
|
279
|
+
body={
|
|
280
|
+
"query": query,
|
|
281
|
+
"variables": {"owner": owner, "name": name, "number": number},
|
|
282
|
+
},
|
|
283
|
+
)
|
|
284
|
+
if data.get("errors"):
|
|
285
|
+
raise RuntimeError(
|
|
286
|
+
f"GitHub required-check query failed: {data['errors'][0].get('message')}"
|
|
287
|
+
)
|
|
288
|
+
nodes = (
|
|
289
|
+
(((data.get("data") or {}).get("repository") or {}).get("pullRequest") or {})
|
|
290
|
+
.get("commits", {})
|
|
291
|
+
.get("nodes")
|
|
292
|
+
) or []
|
|
293
|
+
contexts = (
|
|
294
|
+
((nodes[0].get("commit") or {}).get("statusCheckRollup") or {}).get(
|
|
295
|
+
"contexts", {}
|
|
296
|
+
)
|
|
297
|
+
if nodes
|
|
298
|
+
else {}
|
|
299
|
+
)
|
|
300
|
+
if (contexts.get("pageInfo") or {}).get("hasNextPage"):
|
|
301
|
+
raise RuntimeError(
|
|
302
|
+
"GitHub required-check rollup exceeds one page; the required "
|
|
303
|
+
"set may be incomplete"
|
|
304
|
+
)
|
|
305
|
+
return [
|
|
306
|
+
{
|
|
307
|
+
"name": node.get("name") or node.get("context"),
|
|
308
|
+
"kind": node.get("__typename"),
|
|
309
|
+
}
|
|
310
|
+
for node in (contexts.get("nodes") or [])
|
|
311
|
+
if node.get("isRequired") and (node.get("name") or node.get("context"))
|
|
312
|
+
]
|
|
313
|
+
|
|
314
|
+
def workflow_runs(self, sha):
|
|
315
|
+
"""Return every Actions workflow run GitHub reported for one commit SHA.
|
|
316
|
+
|
|
317
|
+
A workflow that fails before any job starts - a workflow-level error, or
|
|
318
|
+
a `pull_request` run whose jobs never materialize - still records a
|
|
319
|
+
failed check suite, but it contributes no check runs, so the commit's
|
|
320
|
+
check-run rollup and `gh pr checks` both report success. Reading the
|
|
321
|
+
workflow runs directly is the only way the gate can see that failure.
|
|
322
|
+
Like the check-run endpoint this answers with an object, so it paginates
|
|
323
|
+
manually.
|
|
324
|
+
"""
|
|
325
|
+
runs = []
|
|
326
|
+
for page in range(1, 101):
|
|
327
|
+
data = self.gh(
|
|
328
|
+
"GET", f"/actions/runs?head_sha={sha}&per_page=100&page={page}"
|
|
329
|
+
)
|
|
330
|
+
batch = data.get("workflow_runs") or []
|
|
331
|
+
runs.extend(batch)
|
|
332
|
+
if len(runs) >= int(data.get("total_count") or 0) or not batch:
|
|
333
|
+
return runs
|
|
334
|
+
raise RuntimeError("GitHub workflow-run pagination exceeded limit")
|
|
335
|
+
|
|
168
336
|
def completed_dependency(self, number):
|
|
169
337
|
if number in self._completed_dependencies:
|
|
170
338
|
return self._completed_dependencies[number]
|
|
@@ -33,6 +33,7 @@ from pathlib import Path
|
|
|
33
33
|
import time
|
|
34
34
|
import urllib.error
|
|
35
35
|
import urllib.request
|
|
36
|
+
from urllib.parse import quote
|
|
36
37
|
from datetime import datetime, timedelta, timezone
|
|
37
38
|
|
|
38
39
|
from github_client import github_request as _github_request
|
|
@@ -394,11 +395,41 @@ def _fetch_settings(agent_url: str, api_key: str) -> dict:
|
|
|
394
395
|
raise RuntimeError(f"GET /api/settings failed: {exc.code}") from exc
|
|
395
396
|
|
|
396
397
|
|
|
398
|
+
def _fetch_profile_llm(agent_url: str, api_key: str, profile: str) -> dict | None:
|
|
399
|
+
"""Fetch one LLM profile's config, or None when it no longer exists.
|
|
400
|
+
|
|
401
|
+
Uses X-Expose-Secrets: plaintext so the config carries a real api_key,
|
|
402
|
+
mirroring _fetch_settings. Only 404 falls back; other errors raise.
|
|
403
|
+
"""
|
|
404
|
+
req = urllib.request.Request(
|
|
405
|
+
f"{agent_url}/api/profiles/{quote(profile, safe='')}",
|
|
406
|
+
headers={"X-Session-API-Key": api_key, "X-Expose-Secrets": "plaintext"},
|
|
407
|
+
)
|
|
408
|
+
try:
|
|
409
|
+
with urllib.request.urlopen(req) as r:
|
|
410
|
+
return json.loads(r.read())["config"]
|
|
411
|
+
except urllib.error.HTTPError as exc:
|
|
412
|
+
if exc.code == 404:
|
|
413
|
+
print(f"profile {profile!r} not found; falling back to active profile")
|
|
414
|
+
return None
|
|
415
|
+
raise
|
|
416
|
+
|
|
417
|
+
|
|
397
418
|
def _get_agent_dict(agent_url: str, api_key: str) -> dict:
|
|
398
|
-
"""Fetch configured agent settings for conversation creation.
|
|
419
|
+
"""Fetch configured agent settings for conversation creation.
|
|
420
|
+
|
|
421
|
+
When AUTOMATION_MODEL names an LLM profile, resolve that profile instead
|
|
422
|
+
of the active one so the automation runs on its configured model.
|
|
423
|
+
"""
|
|
399
424
|
data = _fetch_settings(agent_url, api_key)
|
|
400
425
|
agent_settings = data.get("agent_settings", {})
|
|
401
426
|
llm = agent_settings.get("llm", {})
|
|
427
|
+
profile = os.environ.get("AUTOMATION_MODEL")
|
|
428
|
+
if profile:
|
|
429
|
+
profile_llm = _fetch_profile_llm(agent_url, api_key, profile)
|
|
430
|
+
if profile_llm is not None:
|
|
431
|
+
llm = profile_llm
|
|
432
|
+
llm["usage_id"] = f"profile:{profile}"
|
|
402
433
|
# settings["agent_settings"]["agent"] reflects the full-app agent registry
|
|
403
434
|
# (e.g. "CodeActAgent", "BrowsingAgent"). The automation SDK is a separate
|
|
404
435
|
# runtime whose only valid kind is "Agent" — never forward that value.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# GitHub stale CI pull request closer
|
|
2
|
+
|
|
3
|
+
This extension runs a deterministic scheduled maintenance pass over configured
|
|
4
|
+
GitHub repositories. It warns after seven days of continuously failing required
|
|
5
|
+
CI and closes after another seven days without author follow-up. Optional checks
|
|
6
|
+
do not affect the decision, and no LLM slot is used.
|
|
7
|
+
|
|
8
|
+
Conversation and review automation state are unaffected. Warning and closure
|
|
9
|
+
comments carry stable markers so retries remain idempotent.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: github-stale-ci-pr-closer
|
|
3
|
+
description: Warn and close abandoned pull requests whose required CI remains failing.
|
|
4
|
+
triggers:
|
|
5
|
+
- /github-stale-ci-pr-closer
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# GitHub stale CI pull request closer
|
|
9
|
+
|
|
10
|
+
This deterministic scheduled automation scans every open, non-draft pull
|
|
11
|
+
request in its configured repositories. It reads the required status checks
|
|
12
|
+
from the GitHub rules that apply to the pull request's base branch.
|
|
13
|
+
|
|
14
|
+
- After required CI remains failing for seven days, it posts one marked warning.
|
|
15
|
+
- After seven more days, it closes the pull request only when required CI still
|
|
16
|
+
fails and the author has neither pushed nor commented since the warning.
|
|
17
|
+
- Passing or pending CI, a draft conversion, closure, merge, or author follow-up
|
|
18
|
+
cancels the pending close. Continued failure starts a fresh warning window.
|
|
19
|
+
|
|
20
|
+
The automation creates no agent or conversation. Configure a repository-scoped
|
|
21
|
+
fine-grained PAT with Metadata: Read, Actions: Read, Commit statuses: Read,
|
|
22
|
+
Issues: Read and Write, and Pull requests: Read and Write. The token also needs
|
|
23
|
+
read access to repository rules. Package `scripts/worker.py` as `worker.py` and
|
|
24
|
+
the shared `skills/github/scripts/github_client.py` as `github_client.py`.
|