@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.
Files changed (97) hide show
  1. package/.agents/skills/custom-codereview-guide.md +47 -5
  2. package/.claude-plugin/marketplace.json +20 -5
  3. package/.codex-plugin/marketplace.json +20 -5
  4. package/.github/workflows/build-vscode-app.yml +3 -3
  5. package/.github/workflows/check-extensions.yml +1 -1
  6. package/.github/workflows/pr-artifacts.yml +4 -83
  7. package/.github/workflows/tests.yml +14 -1
  8. package/.plugin/marketplace.json +20 -5
  9. package/.release-please-manifest.json +1 -1
  10. package/AGENTS.md +4 -3
  11. package/README.md +6 -5
  12. package/apps/vscode/README.md +8 -6
  13. package/apps/vscode/canvas-extension.json +11 -3
  14. package/apps/vscode/extension.js +60 -12
  15. package/apps/vscode/scripts/package.mjs +20 -74
  16. package/automations/bundle-index.js +10 -8
  17. package/automations/catalog/github-issue-to-pr/manifest.json +1 -1
  18. package/automations/catalog/github-issue-triage/manifest.json +4 -3
  19. package/automations/catalog/github-pr-reviewer/manifest.json +7 -6
  20. package/automations/catalog/gitlab-issue-to-mr/manifest.json +3 -3
  21. package/automations/catalog/jira-issue-to-bitbucket-pr/manifest.json +4 -4
  22. package/automations/catalog/jira-issue-to-gitlab-mr/manifest.json +5 -5
  23. package/automations/catalog/jira-issue-to-pr/manifest.json +1 -1
  24. package/integrations/catalog/atlassian-rovo.json +4 -3
  25. package/integrations/catalog/bitbucket.json +16 -1
  26. package/integrations/catalog/github.json +1 -0
  27. package/integrations/catalog/gitlab.json +1 -1
  28. package/marketplaces/openhands-extensions.json +20 -5
  29. package/package.json +1 -1
  30. package/plugins/pr-review/README.md +1 -0
  31. package/plugins/pr-review/scripts/prompt.py +44 -11
  32. package/plugins/runtime-api-configs/SKILL.md +1 -1
  33. package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +224 -8
  34. package/pyproject.toml +1 -1
  35. package/python/openhands_extensions/_version.py +1 -1
  36. package/skills/canvas-extension-api/references/backend-safety.md +3 -3
  37. package/skills/canvas-extension-api/references/v1-contract.md +7 -4
  38. package/skills/canvas-extension-api/scripts/validate-extension.mjs +17 -1
  39. package/skills/canvas-extension-api/scripts/validate-extension.test.mjs +49 -0
  40. package/skills/github/scripts/agent_conversation.py +224 -8
  41. package/skills/github-issue-to-pr/scripts/agent_conversation.py +224 -8
  42. package/skills/github-issue-triage/README.md +73 -14
  43. package/skills/github-issue-triage/SKILL.md +26 -46
  44. package/skills/github-issue-triage/scripts/agent_conversation.py +224 -8
  45. package/skills/github-issue-triage/scripts/triage_publication.py +293 -0
  46. package/skills/github-issue-triage/scripts/worker.py +153 -103
  47. package/skills/github-pr-reviewer/README.md +109 -1
  48. package/skills/github-pr-reviewer/SKILL.md +42 -8
  49. package/skills/github-pr-reviewer/references/state-schema.md +8 -0
  50. package/skills/github-pr-reviewer/scripts/agent_conversation.py +224 -8
  51. package/skills/github-pr-reviewer/scripts/main.py +163 -39
  52. package/skills/github-pr-reviewer/scripts/publication.py +193 -0
  53. package/skills/github-pr-reviewer/scripts/worker.py +297 -8
  54. package/skills/github-pr-reviewer/tests/test_main.py +73 -5
  55. package/skills/github-stale-ci-pr-closer/scripts/worker.py +8 -2
  56. package/skills/gitlab-issue-to-mr/README.md +10 -3
  57. package/skills/gitlab-issue-to-mr/SKILL.md +43 -9
  58. package/skills/gitlab-issue-to-mr/references/state-schema.md +4 -0
  59. package/skills/gitlab-issue-to-mr/scripts/main.py +359 -25
  60. package/skills/index.js +18 -8
  61. package/skills/jira-issue-to-pr/.claude-plugin/plugin.json +4 -1
  62. package/skills/jira-issue-to-pr/.codex-plugin/plugin.json +4 -1
  63. package/skills/jira-issue-to-pr/.plugin/plugin.json +4 -1
  64. package/skills/jira-issue-to-pr/README.md +18 -11
  65. package/skills/jira-issue-to-pr/SKILL.md +72 -23
  66. package/skills/jira-issue-to-pr/references/setup.md +43 -12
  67. package/skills/jira-issue-to-pr/scripts/main.py +136 -26
  68. package/skills/slack-channel-monitor/README.md +12 -3
  69. package/skills/slack-channel-monitor/SKILL.md +35 -20
  70. package/skills/slack-channel-monitor/references/windows.md +6 -2
  71. package/skills/slack-channel-monitor/scripts/main.py +189 -12
  72. package/skills/software-factory/.claude-plugin/plugin.json +19 -0
  73. package/skills/software-factory/.codex-plugin/plugin.json +19 -0
  74. package/skills/software-factory/.plugin/plugin.json +19 -0
  75. package/skills/software-factory/README.md +64 -0
  76. package/skills/software-factory/SKILL.md +229 -0
  77. package/skills/software-factory/commands/software-factory.md +8 -0
  78. package/skills/software-factory/scripts/factory.py +630 -0
  79. package/skills/ticket-to-code-change/.claude-plugin/plugin.json +1 -2
  80. package/skills/ticket-to-code-change/.codex-plugin/plugin.json +1 -2
  81. package/skills/ticket-to-code-change/.plugin/plugin.json +1 -2
  82. package/skills/ticket-to-code-change/SKILL.md +5 -3
  83. package/skills/ticket-to-code-change/commands/ticket-to-code-change-setup.md +1 -1
  84. package/tests/fixtures/automations/github-pr-reviewer.json +9 -9
  85. package/tests/test_agent_conversation_dispatch.py +173 -6
  86. package/tests/test_automation_llm_provenance.py +1209 -0
  87. package/tests/test_github_review_publication.py +422 -0
  88. package/tests/test_github_reviewer_delivery.py +174 -21
  89. package/tests/test_github_stale_ci_pr_closer.py +52 -1
  90. package/tests/test_github_triage_delivery.py +411 -238
  91. package/tests/test_gitlab_issue_to_mr.py +284 -0
  92. package/tests/test_jira_issue_to_pr.py +200 -6
  93. package/tests/test_pr_review_prompt_injection.py +353 -0
  94. package/tests/test_slack_channel_monitor.py +32 -1
  95. package/tests/test_software_factory_script.py +231 -0
  96. package/tests/test_vscode_app.py +8 -2
  97. package/tests/vscode-app.test.mjs +8 -2
@@ -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 setup";
61
- const detail = document.createElement("p");
62
- detail.textContent = status.detail || `Backend status: ${status.state}.`;
63
- panel.append(heading, detail);
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 = "Workspace folder path (optional)";
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
- panel.append(folder);
70
- const actions = document.createElement("p");
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
- actions.replaceChildren(button("Prepare and start", () => render(status)));
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 { 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";
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(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`);
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 (path.basename(destination) !== path.basename(artifact.path)) {
71
- throw new Error(`${platform}: built ${path.basename(destination)} but canvas-extension.json declares ${artifact.path}`);
17
+ if (!/^[0-9a-f]{64}$/.test(artifact.sha256)) {
18
+ throw new Error(`${platform}: invalid SHA-256`);
72
19
  }
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
- );
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
- 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}`);
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}`);