@openhands/extensions 0.29.0 → 0.31.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/.agents/skills/custom-codereview-guide.md +47 -5
- package/.claude-plugin/marketplace.json +20 -5
- package/.codex-plugin/marketplace.json +20 -5
- package/.github/workflows/build-vscode-app.yml +3 -3
- package/.github/workflows/check-extensions.yml +1 -1
- package/.github/workflows/pr-artifacts.yml +4 -83
- package/.github/workflows/tests.yml +14 -1
- package/.plugin/marketplace.json +20 -5
- package/.release-please-manifest.json +1 -1
- package/AGENTS.md +4 -3
- package/README.md +6 -5
- package/apps/vscode/README.md +8 -6
- package/apps/vscode/canvas-extension.json +11 -3
- package/apps/vscode/extension.js +60 -12
- package/apps/vscode/scripts/package.mjs +20 -74
- package/automations/bundle-index.js +10 -8
- package/automations/catalog/github-issue-to-pr/manifest.json +1 -1
- package/automations/catalog/github-issue-triage/manifest.json +4 -3
- package/automations/catalog/github-pr-reviewer/manifest.json +7 -6
- package/automations/catalog/gitlab-issue-to-mr/manifest.json +3 -3
- package/automations/catalog/jira-issue-to-bitbucket-pr/manifest.json +4 -4
- package/automations/catalog/jira-issue-to-gitlab-mr/manifest.json +5 -5
- package/automations/catalog/jira-issue-to-pr/manifest.json +1 -1
- package/integrations/catalog/atlassian-rovo.json +4 -3
- package/integrations/catalog/bitbucket.json +16 -1
- package/integrations/catalog/github.json +1 -0
- package/integrations/catalog/gitlab.json +1 -1
- package/marketplaces/openhands-extensions.json +20 -5
- package/package.json +1 -1
- package/plugins/pr-review/README.md +1 -0
- package/plugins/pr-review/scripts/prompt.py +44 -11
- package/plugins/runtime-api-configs/SKILL.md +1 -1
- package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +224 -8
- package/pyproject.toml +1 -1
- package/python/openhands_extensions/_version.py +1 -1
- package/skills/canvas-extension-api/references/backend-safety.md +3 -3
- package/skills/canvas-extension-api/references/v1-contract.md +7 -4
- package/skills/canvas-extension-api/scripts/validate-extension.mjs +17 -1
- package/skills/canvas-extension-api/scripts/validate-extension.test.mjs +49 -0
- package/skills/github/scripts/agent_conversation.py +224 -8
- package/skills/github-issue-to-pr/scripts/agent_conversation.py +224 -8
- package/skills/github-issue-triage/README.md +73 -14
- package/skills/github-issue-triage/SKILL.md +26 -46
- package/skills/github-issue-triage/scripts/agent_conversation.py +224 -8
- package/skills/github-issue-triage/scripts/triage_publication.py +293 -0
- package/skills/github-issue-triage/scripts/worker.py +153 -103
- package/skills/github-pr-reviewer/README.md +109 -1
- package/skills/github-pr-reviewer/SKILL.md +42 -8
- package/skills/github-pr-reviewer/references/state-schema.md +8 -0
- package/skills/github-pr-reviewer/scripts/agent_conversation.py +224 -8
- package/skills/github-pr-reviewer/scripts/main.py +163 -39
- package/skills/github-pr-reviewer/scripts/publication.py +193 -0
- package/skills/github-pr-reviewer/scripts/worker.py +297 -8
- package/skills/github-pr-reviewer/tests/test_main.py +73 -5
- package/skills/github-stale-ci-pr-closer/scripts/worker.py +8 -2
- package/skills/gitlab-issue-to-mr/README.md +10 -3
- package/skills/gitlab-issue-to-mr/SKILL.md +43 -9
- package/skills/gitlab-issue-to-mr/references/state-schema.md +4 -0
- package/skills/gitlab-issue-to-mr/scripts/main.py +359 -25
- package/skills/index.js +18 -8
- package/skills/jira-issue-to-pr/.claude-plugin/plugin.json +4 -1
- package/skills/jira-issue-to-pr/.codex-plugin/plugin.json +4 -1
- package/skills/jira-issue-to-pr/.plugin/plugin.json +4 -1
- package/skills/jira-issue-to-pr/README.md +18 -11
- package/skills/jira-issue-to-pr/SKILL.md +72 -23
- package/skills/jira-issue-to-pr/references/setup.md +43 -12
- package/skills/jira-issue-to-pr/scripts/main.py +136 -26
- package/skills/slack-channel-monitor/README.md +12 -3
- package/skills/slack-channel-monitor/SKILL.md +35 -20
- package/skills/slack-channel-monitor/references/windows.md +6 -2
- package/skills/slack-channel-monitor/scripts/main.py +189 -12
- package/skills/software-factory/.claude-plugin/plugin.json +19 -0
- package/skills/software-factory/.codex-plugin/plugin.json +19 -0
- package/skills/software-factory/.plugin/plugin.json +19 -0
- package/skills/software-factory/README.md +64 -0
- package/skills/software-factory/SKILL.md +229 -0
- package/skills/software-factory/commands/software-factory.md +8 -0
- package/skills/software-factory/scripts/factory.py +630 -0
- package/skills/ticket-to-code-change/.claude-plugin/plugin.json +1 -2
- package/skills/ticket-to-code-change/.codex-plugin/plugin.json +1 -2
- package/skills/ticket-to-code-change/.plugin/plugin.json +1 -2
- package/skills/ticket-to-code-change/SKILL.md +5 -3
- package/skills/ticket-to-code-change/commands/ticket-to-code-change-setup.md +1 -1
- package/tests/fixtures/automations/github-pr-reviewer.json +9 -9
- package/tests/test_agent_conversation_dispatch.py +173 -6
- package/tests/test_automation_llm_provenance.py +1209 -0
- package/tests/test_github_review_publication.py +422 -0
- package/tests/test_github_reviewer_delivery.py +174 -21
- package/tests/test_github_stale_ci_pr_closer.py +52 -1
- package/tests/test_github_triage_delivery.py +411 -238
- package/tests/test_gitlab_issue_to_mr.py +284 -0
- package/tests/test_jira_issue_to_pr.py +200 -6
- package/tests/test_pr_review_prompt_injection.py +353 -0
- package/tests/test_slack_channel_monitor.py +32 -1
- package/tests/test_software_factory_script.py +231 -0
- package/tests/test_vscode_app.py +8 -2
- package/tests/vscode-app.test.mjs +8 -2
package/apps/vscode/extension.js
CHANGED
|
@@ -5,14 +5,32 @@ function folderKey(host) {
|
|
|
5
5
|
return `${FOLDER_KEY_PREFIX}:${host.backend.id}:${host.backend.orgId || "local"}:${host.extension.name}`;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
function button(label, onClick) {
|
|
8
|
+
function button(label, onClick, variant = "secondary") {
|
|
9
9
|
const element = document.createElement("button");
|
|
10
10
|
element.type = "button";
|
|
11
11
|
element.textContent = label;
|
|
12
|
+
element.style.cssText = [
|
|
13
|
+
"min-height:40px",
|
|
14
|
+
"padding:0 18px",
|
|
15
|
+
"border-radius:8px",
|
|
16
|
+
"border:1px solid transparent",
|
|
17
|
+
"font:inherit",
|
|
18
|
+
"font-size:14px",
|
|
19
|
+
"font-weight:600",
|
|
20
|
+
"cursor:pointer",
|
|
21
|
+
"transition:opacity 120ms ease,background-color 120ms ease",
|
|
22
|
+
variant === "primary"
|
|
23
|
+
? "background:#f3ce49;color:#171717"
|
|
24
|
+
: "background:#262626;color:#fafafa;border-color:#525252",
|
|
25
|
+
].join(";");
|
|
12
26
|
element.addEventListener("click", onClick);
|
|
13
27
|
return element;
|
|
14
28
|
}
|
|
15
29
|
|
|
30
|
+
function statusLabel(state) {
|
|
31
|
+
return state.charAt(0).toUpperCase() + state.slice(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
export function activate(host) {
|
|
17
35
|
return host.registerPage("editor", async ({ container }) => {
|
|
18
36
|
container.style.minHeight = "100vh";
|
|
@@ -54,26 +72,55 @@ export function activate(host) {
|
|
|
54
72
|
return;
|
|
55
73
|
}
|
|
56
74
|
|
|
75
|
+
shell.style.justifyContent = "center";
|
|
76
|
+
shell.style.alignItems = "center";
|
|
77
|
+
shell.style.padding = "32px";
|
|
78
|
+
shell.style.boxSizing = "border-box";
|
|
79
|
+
|
|
57
80
|
const panel = document.createElement("section");
|
|
58
81
|
panel.setAttribute("aria-live", "polite");
|
|
82
|
+
panel.style.cssText = "width:min(100%,520px);padding:32px;border:1px solid #262626;border-radius:12px;background:#0a0a0a;color:#fafafa;box-sizing:border-box;text-align:center";
|
|
59
83
|
const heading = document.createElement("h2");
|
|
60
|
-
heading.textContent = "VS Code
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
heading.textContent = "Set up VS Code";
|
|
85
|
+
heading.style.cssText = "margin:0;font-size:24px;line-height:32px;font-weight:600";
|
|
86
|
+
const description = document.createElement("p");
|
|
87
|
+
description.textContent = "Start the secure VS Code backend for this Agent Canvas session. The first launch downloads and verifies the pinned editor package.";
|
|
88
|
+
description.style.cssText = "margin:12px auto 0;max-width:440px;color:#a3a3a3;font-size:14px;line-height:21px";
|
|
89
|
+
const statusRow = document.createElement("div");
|
|
90
|
+
statusRow.style.cssText = "display:flex;align-items:center;justify-content:center;gap:8px;margin:20px 0 24px;font-size:13px;color:#a3a3a3";
|
|
91
|
+
const statusDot = document.createElement("span");
|
|
92
|
+
statusDot.setAttribute("aria-hidden", "true");
|
|
93
|
+
statusDot.style.cssText = `width:8px;height:8px;border-radius:999px;background:${status.state === "unhealthy" ? "#fda4af" : "#737373"}`;
|
|
94
|
+
const detail = document.createElement("span");
|
|
95
|
+
detail.textContent = status.detail || `Backend status: ${statusLabel(status.state)}`;
|
|
96
|
+
statusRow.append(statusDot, detail);
|
|
97
|
+
panel.append(heading, description, statusRow);
|
|
98
|
+
|
|
99
|
+
const field = document.createElement("label");
|
|
100
|
+
field.style.cssText = "display:flex;flex-direction:column;gap:8px;text-align:left";
|
|
101
|
+
const fieldLabel = document.createElement("span");
|
|
102
|
+
fieldLabel.textContent = "Workspace folder";
|
|
103
|
+
fieldLabel.style.cssText = "font-size:13px;font-weight:500;color:#fafafa";
|
|
64
104
|
const folder = document.createElement("input");
|
|
65
105
|
folder.type = "text";
|
|
66
|
-
folder.placeholder = "
|
|
106
|
+
folder.placeholder = "Optional path to open in VS Code";
|
|
67
107
|
folder.value = localStorage.getItem(storageKey) || "";
|
|
68
108
|
folder.setAttribute("aria-label", "Workspace folder path");
|
|
69
|
-
|
|
70
|
-
const
|
|
109
|
+
folder.style.cssText = "width:100%;height:42px;padding:0 12px;border:1px solid #525252;border-radius:8px;background:#1a1a1a;color:#fafafa;font:inherit;font-size:14px;box-sizing:border-box;outline:none";
|
|
110
|
+
const hint = document.createElement("span");
|
|
111
|
+
hint.textContent = "Leave blank to open the default App workspace.";
|
|
112
|
+
hint.style.cssText = "font-size:12px;color:#8c8c8c";
|
|
113
|
+
field.append(fieldLabel, folder, hint);
|
|
114
|
+
panel.append(field);
|
|
115
|
+
|
|
116
|
+
const actions = document.createElement("div");
|
|
117
|
+
actions.style.cssText = "display:flex;justify-content:center;gap:10px;margin-top:24px;flex-wrap:wrap";
|
|
71
118
|
if (status.state === "missing" || status.state === "stopped" || status.state === "unhealthy") {
|
|
72
119
|
actions.append(button("Prepare and start", async () => {
|
|
73
120
|
const selectedFolder = folder.value.trim();
|
|
74
121
|
if (selectedFolder) localStorage.setItem(storageKey, selectedFolder);
|
|
75
122
|
else localStorage.removeItem(storageKey);
|
|
76
|
-
actions.replaceChildren(document.createTextNode("Preparing..."));
|
|
123
|
+
actions.replaceChildren(document.createTextNode("Preparing and verifying VS Code..."));
|
|
77
124
|
try {
|
|
78
125
|
let approval = status;
|
|
79
126
|
if (!approval.revision) approval = await request("GET");
|
|
@@ -90,14 +137,15 @@ export function activate(host) {
|
|
|
90
137
|
render(await request("GET"));
|
|
91
138
|
} catch (error) {
|
|
92
139
|
detail.textContent = `Setup failed: ${error.message}`;
|
|
93
|
-
|
|
140
|
+
statusDot.style.background = "#fda4af";
|
|
141
|
+
actions.replaceChildren(button("Try again", () => render(status), "primary"));
|
|
94
142
|
}
|
|
95
|
-
}));
|
|
143
|
+
}, "primary"));
|
|
96
144
|
}
|
|
97
145
|
if (status.state === "ready" || status.state === "starting") {
|
|
98
146
|
actions.append(button("Stop", async () => render(await request("POST", `${BACKEND}/stop`))));
|
|
99
147
|
}
|
|
100
|
-
actions.append(button("Refresh", async () => render(await request("GET"))));
|
|
148
|
+
actions.append(button("Refresh status", async () => render(await request("GET"))));
|
|
101
149
|
panel.append(actions);
|
|
102
150
|
shell.append(panel);
|
|
103
151
|
};
|
|
@@ -1,84 +1,30 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
1
|
+
import { cp, mkdir, readFile, rm } from "node:fs/promises";
|
|
5
2
|
import path from "node:path";
|
|
6
3
|
|
|
7
|
-
const exec = promisify(execFile);
|
|
8
4
|
const appRoot = path.resolve(new URL("..", import.meta.url).pathname);
|
|
9
5
|
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
6
|
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
7
|
const packageRoot = path.join(output, "apps", "vscode");
|
|
41
|
-
const manifest = JSON.parse(
|
|
42
|
-
await
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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`);
|
|
8
|
+
const manifest = JSON.parse(
|
|
9
|
+
await readFile(path.join(appRoot, "canvas-extension.json"), "utf8"),
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
for (const [platform, artifact] of Object.entries(manifest.backend.artifacts)) {
|
|
13
|
+
const url = new URL(artifact.url);
|
|
14
|
+
if (url.protocol !== "https:" || !url.pathname.endsWith(".tar.gz")) {
|
|
15
|
+
throw new Error(`${platform}: invalid HTTPS artifact URL`);
|
|
69
16
|
}
|
|
70
|
-
if (
|
|
71
|
-
throw new Error(`${platform}:
|
|
17
|
+
if (!/^[0-9a-f]{64}$/.test(artifact.sha256)) {
|
|
18
|
+
throw new Error(`${platform}: invalid SHA-256`);
|
|
72
19
|
}
|
|
73
|
-
if (
|
|
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
|
-
);
|
|
20
|
+
if (!Number.isInteger(artifact.strip_components) || artifact.strip_components < 0) {
|
|
21
|
+
throw new Error(`${platform}: invalid strip_components`);
|
|
77
22
|
}
|
|
78
|
-
console.log(`${platform}: ${produced}`);
|
|
79
23
|
}
|
|
80
|
-
|
|
81
|
-
await
|
|
82
|
-
await
|
|
83
|
-
|
|
84
|
-
|
|
24
|
+
|
|
25
|
+
await rm(packageRoot, { recursive: true, force: true });
|
|
26
|
+
await mkdir(packageRoot, { recursive: true });
|
|
27
|
+
for (const filename of ["canvas-extension.json", "extension.js", "README.md"]) {
|
|
28
|
+
await cp(path.join(appRoot, filename), path.join(packageRoot, filename));
|
|
29
|
+
}
|
|
30
|
+
console.log(`Wrote source-installable App package to ${packageRoot}`);
|