@openhands/extensions 0.25.0 → 0.27.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 +24 -0
- package/.codex-plugin/marketplace.json +24 -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 +24 -0
- package/.release-please-manifest.json +1 -1
- package/AGENTS.md +3 -2
- package/README.md +9 -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/README.md +7 -6
- package/automations/bundle-index.js +15 -15
- package/automations/catalog/github-agents-md-maintainer/manifest.json +1 -1
- package/automations/catalog/github-pr-reviewer/manifest.json +5 -5
- package/automations/catalog/github-stale-ci-pr-closer/manifest.json +3 -3
- package/automations/catalog/gitlab-issue-to-mr/manifest.json +1 -1
- package/automations/catalog/news-digest/manifest.json +2 -2
- package/automations/index.d.ts +9 -1
- package/automations/interface.json +9 -0
- package/automations/interface.schema.json +37 -2
- package/index.d.ts +2 -0
- package/index.js +1 -0
- package/marketplaces/openhands-extensions.json +24 -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/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 +67 -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 +67 -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 +67 -15
- package/skills/github-agents-md-maintainer/scripts/main.py +199 -29
- package/skills/github-delivery-watchdog/scripts/github_client.py +67 -15
- package/skills/github-issue-to-pr/scripts/agent_conversation.py +223 -43
- package/skills/github-issue-to-pr/scripts/github_client.py +67 -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 +67 -15
- package/skills/github-pr-reviewer/README.md +5 -4
- package/skills/github-pr-reviewer/SKILL.md +8 -17
- package/skills/github-pr-reviewer/references/state-schema.md +0 -15
- package/skills/github-pr-reviewer/scripts/agent_conversation.py +223 -43
- package/skills/github-pr-reviewer/scripts/github_client.py +67 -15
- package/skills/github-pr-reviewer/scripts/worker.py +131 -108
- package/skills/github-repo-monitor/scripts/github_client.py +67 -15
- package/skills/github-stale-ci-pr-closer/scripts/worker.py +223 -50
- package/skills/github-stale-ci-pr-closer/tests/test_worker.py +155 -15
- 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 +23 -14
- 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/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 +9 -9
- 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_reviewer_delivery.py +254 -118
- package/tests/test_github_stale_ci_pr_closer.py +56 -0
- package/tests/test_interface_manifest.py +23 -1
- 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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const BACKEND = "/api/canvas-extensions/installed/vscode/backend";
|
|
2
|
+
const FOLDER_KEY_PREFIX = "openhands.apps.vscode.folder";
|
|
3
|
+
|
|
4
|
+
function folderKey(host) {
|
|
5
|
+
return `${FOLDER_KEY_PREFIX}:${host.backend.id}:${host.backend.orgId || "local"}:${host.extension.name}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function button(label, onClick) {
|
|
9
|
+
const element = document.createElement("button");
|
|
10
|
+
element.type = "button";
|
|
11
|
+
element.textContent = label;
|
|
12
|
+
element.addEventListener("click", onClick);
|
|
13
|
+
return element;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function activate(host) {
|
|
17
|
+
return host.registerPage("editor", async ({ container }) => {
|
|
18
|
+
container.style.minHeight = "100vh";
|
|
19
|
+
const shell = document.createElement("div");
|
|
20
|
+
shell.style.cssText = "height:100vh;min-height:100vh;width:100%;display:flex;flex-direction:column";
|
|
21
|
+
container.replaceChildren(shell);
|
|
22
|
+
|
|
23
|
+
if (!host.appBackendView) {
|
|
24
|
+
const error = document.createElement("p");
|
|
25
|
+
error.setAttribute("role", "alert");
|
|
26
|
+
error.textContent = "VS Code is unavailable because this Agent Server does not support managed App backends.";
|
|
27
|
+
shell.append(error);
|
|
28
|
+
return () => container.replaceChildren();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let disposed = false;
|
|
32
|
+
let mounted;
|
|
33
|
+
const storageKey = folderKey(host);
|
|
34
|
+
const request = (method, path = BACKEND, body) => host.agentServer.request({ method, path, ...(body ? { body } : {}) });
|
|
35
|
+
const cleanup = () => {
|
|
36
|
+
disposed = true;
|
|
37
|
+
mounted?.();
|
|
38
|
+
mounted = undefined;
|
|
39
|
+
container.replaceChildren();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const render = (status) => {
|
|
43
|
+
if (disposed) return;
|
|
44
|
+
shell.replaceChildren();
|
|
45
|
+
if (status.state === "ready") {
|
|
46
|
+
const editor = document.createElement("div");
|
|
47
|
+
editor.style.cssText = "flex:1;min-height:100vh;width:100%;overflow:hidden";
|
|
48
|
+
shell.append(editor);
|
|
49
|
+
const folder = localStorage.getItem(storageKey)?.trim();
|
|
50
|
+
mounted = host.appBackendView.mount({
|
|
51
|
+
container: editor,
|
|
52
|
+
...(folder ? { query: { folder } } : {}),
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const panel = document.createElement("section");
|
|
58
|
+
panel.setAttribute("aria-live", "polite");
|
|
59
|
+
const heading = document.createElement("h2");
|
|
60
|
+
heading.textContent = "VS Code setup";
|
|
61
|
+
const detail = document.createElement("p");
|
|
62
|
+
detail.textContent = status.detail || `Backend status: ${status.state}.`;
|
|
63
|
+
panel.append(heading, detail);
|
|
64
|
+
const folder = document.createElement("input");
|
|
65
|
+
folder.type = "text";
|
|
66
|
+
folder.placeholder = "Workspace folder path (optional)";
|
|
67
|
+
folder.value = localStorage.getItem(storageKey) || "";
|
|
68
|
+
folder.setAttribute("aria-label", "Workspace folder path");
|
|
69
|
+
panel.append(folder);
|
|
70
|
+
const actions = document.createElement("p");
|
|
71
|
+
if (status.state === "missing" || status.state === "stopped" || status.state === "unhealthy") {
|
|
72
|
+
actions.append(button("Prepare and start", async () => {
|
|
73
|
+
const selectedFolder = folder.value.trim();
|
|
74
|
+
if (selectedFolder) localStorage.setItem(storageKey, selectedFolder);
|
|
75
|
+
else localStorage.removeItem(storageKey);
|
|
76
|
+
actions.replaceChildren(document.createTextNode("Preparing..."));
|
|
77
|
+
try {
|
|
78
|
+
let approval = status;
|
|
79
|
+
if (!approval.revision) approval = await request("GET");
|
|
80
|
+
if (!approval.revision) throw new Error("The backend did not report an immutable revision.");
|
|
81
|
+
try {
|
|
82
|
+
await request("POST", `${BACKEND}/prepare`, { revision: approval.revision });
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error?.status !== 409) throw error;
|
|
85
|
+
approval = await request("GET");
|
|
86
|
+
if (!approval.revision) throw error;
|
|
87
|
+
await request("POST", `${BACKEND}/prepare`, { revision: approval.revision });
|
|
88
|
+
}
|
|
89
|
+
await request("POST", `${BACKEND}/start`, { revision: approval.revision });
|
|
90
|
+
render(await request("GET"));
|
|
91
|
+
} catch (error) {
|
|
92
|
+
detail.textContent = `Setup failed: ${error.message}`;
|
|
93
|
+
actions.replaceChildren(button("Prepare and start", () => render(status)));
|
|
94
|
+
}
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
if (status.state === "ready" || status.state === "starting") {
|
|
98
|
+
actions.append(button("Stop", async () => render(await request("POST", `${BACKEND}/stop`))));
|
|
99
|
+
}
|
|
100
|
+
actions.append(button("Refresh", async () => render(await request("GET"))));
|
|
101
|
+
panel.append(actions);
|
|
102
|
+
shell.append(panel);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
try { render(await request("GET")); }
|
|
106
|
+
catch (error) {
|
|
107
|
+
const failure = document.createElement("p");
|
|
108
|
+
failure.setAttribute("role", "alert");
|
|
109
|
+
failure.textContent = `Unable to read VS Code backend status: ${error.message}`;
|
|
110
|
+
shell.replaceChildren(failure);
|
|
111
|
+
}
|
|
112
|
+
return cleanup;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readdir, readFile, mkdir, rm, writeFile, chmod, stat } from "node:fs/promises";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
const exec = promisify(execFile);
|
|
8
|
+
const appRoot = path.resolve(new URL("..", import.meta.url).pathname);
|
|
9
|
+
const root = path.resolve(appRoot, "../..");
|
|
10
|
+
if (process.platform !== "linux") throw new Error("VS Code App packaging currently supports Linux build runners only");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Force a umask-independent mode set on the staged tree.
|
|
14
|
+
*
|
|
15
|
+
* `cp -a` preserves whatever modes tar materialized, and the build host's umask
|
|
16
|
+
* narrows them (0664/0775 under umask 0002, 0644/0755 under umask 022). Those
|
|
17
|
+
* bits end up in the final archive's tar headers, so the digest — and with it
|
|
18
|
+
* the `sha256` declared in canvas-extension.json — used to vary per build host.
|
|
19
|
+
*/
|
|
20
|
+
async function normalizeModes(dir) {
|
|
21
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
22
|
+
const target = path.join(dir, entry.name);
|
|
23
|
+
if (entry.isSymbolicLink()) continue;
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
await chmod(target, 0o755);
|
|
26
|
+
await normalizeModes(target);
|
|
27
|
+
} else if (entry.isFile()) {
|
|
28
|
+
const { mode } = await stat(target);
|
|
29
|
+
await chmod(target, mode & 0o111 ? 0o755 : 0o644);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const output = path.resolve(process.env.OUTPUT_DIR ?? path.join(root, "dist"));
|
|
34
|
+
const version = "1.109.5";
|
|
35
|
+
const releases = {
|
|
36
|
+
"linux-amd64": ["x64", "b433bf4f0227321a7014d8460d10a8f958adc0f45aa79bd889e84e65e8f88363"],
|
|
37
|
+
"linux-arm64": ["arm64", "36d9c14036489b63de84ebace837fcacf7e60e669a0dc715802c5443684ea4dc"],
|
|
38
|
+
};
|
|
39
|
+
const work = path.join(output, ".download");
|
|
40
|
+
const packageRoot = path.join(output, "apps", "vscode");
|
|
41
|
+
const manifest = JSON.parse(await readFile(path.join(appRoot, "canvas-extension.json"), "utf8"));
|
|
42
|
+
await rm(work, { recursive: true, force: true });
|
|
43
|
+
await rm(packageRoot, { recursive: true, force: true });
|
|
44
|
+
await mkdir(work, { recursive: true });
|
|
45
|
+
await mkdir(path.join(packageRoot, "backend"), { recursive: true });
|
|
46
|
+
for (const [platform, [upstreamArch, expected]] of Object.entries(releases)) {
|
|
47
|
+
const source = path.join(work, `${platform}.tar.gz`);
|
|
48
|
+
const archive = `openvscode-server-v${version}-linux-${upstreamArch}.tar.gz`;
|
|
49
|
+
const url = `https://github.com/gitpod-io/openvscode-server/releases/download/openvscode-server-v${version}/${archive}`;
|
|
50
|
+
await exec("curl", ["--fail", "--location", "--retry", "3", "--output", source, url]);
|
|
51
|
+
const actual = createHash("sha256").update(await readFile(source)).digest("hex");
|
|
52
|
+
if (actual !== expected) throw new Error(`${platform}: upstream checksum mismatch (${actual})`);
|
|
53
|
+
const unpacked = path.join(work, `${platform}-unpacked`);
|
|
54
|
+
const normalized = path.join(work, `${platform}-normalized`);
|
|
55
|
+
await mkdir(unpacked, { recursive: true });
|
|
56
|
+
await exec("tar", ["-xzf", source, "-C", unpacked]);
|
|
57
|
+
await mkdir(normalized, { recursive: true });
|
|
58
|
+
const [top] = await readdir(unpacked);
|
|
59
|
+
await exec("cp", ["-a", `${path.join(unpacked, top)}/.`, normalized]);
|
|
60
|
+
await writeFile(path.join(normalized, "bin", "openvscode-bootstrap"), "#!/bin/sh\nset -eu\ndata_dir=\"\"\nfor candidate do data_dir=\"$candidate\"; done\nmkdir -p \"$data_dir\" \"$data_dir/home\" \"$data_dir/workspace\"\nexport HOME=\"$data_dir/home\"\nexec \"$(dirname \"$0\")/openvscode-server\" \"$@\"\n");
|
|
61
|
+
await exec("chmod", ["0755", path.join(normalized, "bin", "openvscode-bootstrap")]);
|
|
62
|
+
await normalizeModes(normalized);
|
|
63
|
+
const destination = path.join(packageRoot, "backend", `openvscode-${platform}.tar.gz`);
|
|
64
|
+
await exec("tar", ["--sort=name", "--mtime=UTC 1970-01-01", "--owner=0", "--group=0", "--numeric-owner", "-czf", destination, "-C", normalized, "."]);
|
|
65
|
+
const produced = createHash("sha256").update(await readFile(destination)).digest("hex");
|
|
66
|
+
const artifact = manifest?.backend?.artifacts?.[platform];
|
|
67
|
+
if (!artifact?.sha256) {
|
|
68
|
+
throw new Error(`canvas-extension.json declares no backend.artifacts.${platform}.sha256`);
|
|
69
|
+
}
|
|
70
|
+
if (path.basename(destination) !== path.basename(artifact.path)) {
|
|
71
|
+
throw new Error(`${platform}: built ${path.basename(destination)} but canvas-extension.json declares ${artifact.path}`);
|
|
72
|
+
}
|
|
73
|
+
if (produced !== artifact.sha256) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`${platform}: built archive digest ${produced} does not match the sha256 declared for backend.artifacts.${platform} in canvas-extension.json (${artifact.sha256}); update the manifest with the reproducible digest`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
console.log(`${platform}: ${produced}`);
|
|
79
|
+
}
|
|
80
|
+
await exec("cp", [path.join(appRoot, "canvas-extension.json"), path.join(packageRoot, "canvas-extension.json")]);
|
|
81
|
+
await exec("cp", [path.join(appRoot, "extension.js"), path.join(packageRoot, "extension.js")]);
|
|
82
|
+
await exec("cp", [path.join(appRoot, "README.md"), path.join(packageRoot, "README.md")]);
|
|
83
|
+
await rm(work, { recursive: true, force: true });
|
|
84
|
+
console.log(`Wrote ${packageRoot}`);
|
package/automations/README.md
CHANGED
|
@@ -270,12 +270,13 @@ falling back to its built-in defaults when the manifest is absent or fails admis
|
|
|
270
270
|
captions it: `overview.tiles[].metric` names a host-computed value (`automations`, `needs-attention`,
|
|
271
271
|
`total-runs`, `average-duration`), filter option values name host predicates (`status`: enabled /
|
|
272
272
|
latest-run-failed / disabled; `trigger`: `event` matches event-triggered automations, `schedule`
|
|
273
|
-
everything else
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
273
|
+
everything else; `created_by`: `me` matches automations the signed-in user created, `others` everything
|
|
274
|
+
else, and the host decides where it is shown), `sort` values name host comparators, and `insights`
|
|
275
|
+
captions the host's run-health states and per-automation stats. The health precedence, the run
|
|
276
|
+
sampling, the value formatting, the relative-time rendering, and the filtered-empty state with its reset
|
|
277
|
+
button are the host's - a manifest cannot redefine them, only relabel what appears. Tile `detail` copy is
|
|
278
|
+
plain substitution over the metric's placeholder namespace (only the `automations` metric exposes
|
|
279
|
+
`{{active}}`); `zeroDetail` replaces `detail` while the value is zero.
|
|
279
280
|
- **`docsUrl`** - the automations documentation link, prefix-pinned to docs.openhands.dev by schema.
|
|
280
281
|
- **`attributes`** - the input surface of an existing Automation: which attributes can be set after
|
|
281
282
|
creation, keyed by the runtime-model property the host sends (`name`, `prompt`, `model`, `timeout`,
|