@openhands/extensions 0.29.0 → 0.30.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 +44 -1
- package/.claude-plugin/marketplace.json +7 -5
- package/.codex-plugin/marketplace.json +7 -5
- package/.github/workflows/build-vscode-app.yml +3 -3
- package/.github/workflows/check-extensions.yml +1 -1
- package/.github/workflows/tests.yml +1 -1
- package/.plugin/marketplace.json +7 -5
- package/.release-please-manifest.json +1 -1
- package/README.md +3 -3
- 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 +9 -7
- 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 +5 -4
- 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 +7 -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 +103 -1
- package/skills/github-pr-reviewer/SKILL.md +35 -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/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 +8 -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 +6 -1
- package/skills/slack-channel-monitor/SKILL.md +6 -3
- package/skills/slack-channel-monitor/scripts/main.py +36 -4
- 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_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_vscode_app.py +8 -2
- package/tests/vscode-app.test.mjs +8 -2
|
@@ -27,7 +27,32 @@ Cross-repository work is acceptable when this PR contains only the
|
|
|
27
27
|
extensions-owned portion and relies on public interfaces from the owning
|
|
28
28
|
repository.
|
|
29
29
|
|
|
30
|
-
##
|
|
30
|
+
## Issue triage
|
|
31
|
+
|
|
32
|
+
Issue readiness means the work is clear enough to start—not that the fix is
|
|
33
|
+
complete. Do not require a PR, passing implementation tests, or before-and-after
|
|
34
|
+
fix evidence. Reconsider readiness when new information leaves scope or expected
|
|
35
|
+
behavior unresolved. Read the issue, linked discussion, applicable `AGENTS.md`,
|
|
36
|
+
and adjacent source before asking questions.
|
|
37
|
+
|
|
38
|
+
For API failures, compare the extension request with the provider's reported or
|
|
39
|
+
supported version, not upstream main. Use dependency pins or deployment evidence.
|
|
40
|
+
Instructions, request arguments, response parsing, and scanner policy belong
|
|
41
|
+
here; use the ownership boundaries above for other failures. State required
|
|
42
|
+
cross-repository changes and their order. An independent upstream defect need
|
|
43
|
+
not block this work. Naming an owner does not authorize edits or duplicate tickets.
|
|
44
|
+
|
|
45
|
+
Define the smallest useful outcome and explicit non-goals. Cover only affected
|
|
46
|
+
entrypoints, environments, and permissions. For scanners, state whether discovery,
|
|
47
|
+
commenting, and dispatch are allowed for ineligible candidates or after a cap.
|
|
48
|
+
Discovery alone does not authorize unsolicited comments.
|
|
49
|
+
|
|
50
|
+
Request visual evidence only when needed; otherwise use logs, minimal
|
|
51
|
+
reproductions, or failing tests. Do not repeat answered questions. Match managed
|
|
52
|
+
criteria to current human decisions and authoritative readiness policy. Report
|
|
53
|
+
conflicts instead of repeatedly adding a label that policy removes.
|
|
54
|
+
|
|
55
|
+
## Implementation review checkpoints
|
|
31
56
|
|
|
32
57
|
### Executable instructions and runtime parity
|
|
33
58
|
|
|
@@ -42,6 +67,16 @@ in a clean environment far enough to exercise the changed behavior. Keep local
|
|
|
42
67
|
and cloud behavior identical unless the documentation names and explains a real
|
|
43
68
|
platform capability difference.
|
|
44
69
|
|
|
70
|
+
For provider changes, inspect the supported implementation; mocks and local types
|
|
71
|
+
alone do not establish an API contract. Separate static/helper tests, tests with
|
|
72
|
+
simulated services, and real integration evidence. If the version or runtime is
|
|
73
|
+
unavailable, report that limit, not an assumed defect. For packaging changes,
|
|
74
|
+
check source, shipped bundle, and deployed files separately.
|
|
75
|
+
|
|
76
|
+
For scanners, check eligibility, permissions, caps, retries, unchanged runs, and
|
|
77
|
+
recovery for each action listed under triage. A launch cap does not permit
|
|
78
|
+
unlimited gate comments.
|
|
79
|
+
|
|
45
80
|
### Untrusted triggers and credentials
|
|
46
81
|
|
|
47
82
|
Treat repository events, issue and PR comments, chat messages, catalog fields,
|
|
@@ -130,3 +165,11 @@ Do not comment on punctuation, preferred prose tone, optional DRY cleanup, or
|
|
|
130
165
|
hypothetical non-standard configurations without a supported failure mode. Before
|
|
131
166
|
raising a finding, verify that the referenced file and behavior are present in
|
|
132
167
|
the current PR head and have not already been addressed in review history.
|
|
168
|
+
|
|
169
|
+
## Review verdict
|
|
170
|
+
|
|
171
|
+
Do not approve material code defects or unmet blocking merge/evidence checks.
|
|
172
|
+
Distinguish code defects from missing merge requirements and optional follow-up.
|
|
173
|
+
If GitHub forbids self-approval of a clean PR, use COMMENT and explain why.
|
|
174
|
+
Respect accepted scope and non-goals: block on a pre-existing limitation only if
|
|
175
|
+
this change exposes it.
|
|
@@ -752,7 +752,7 @@
|
|
|
752
752
|
{
|
|
753
753
|
"name": "gitlab-issue-to-mr",
|
|
754
754
|
"source": "./skills/gitlab-issue-to-mr",
|
|
755
|
-
"description": "Create an automation that implements GitLab issues when a configurable trigger label is applied.
|
|
755
|
+
"description": "Create an automation that implements GitLab issues when a configurable trigger label is applied. Polls the projects deterministically and starts one conversation per label event; on OpenHands Cloud and Enterprise it reaches GitLab through the connected MCP server or integration, with no token to set up.",
|
|
756
756
|
"category": "automations",
|
|
757
757
|
"keywords": [
|
|
758
758
|
"gitlab",
|
|
@@ -851,11 +851,14 @@
|
|
|
851
851
|
{
|
|
852
852
|
"name": "jira-issue-to-pr",
|
|
853
853
|
"source": "./skills/jira-issue-to-pr",
|
|
854
|
-
"description": "Deploy a cron-based OpenHands automation that
|
|
854
|
+
"description": "Deploy a cron-based OpenHands automation that opens a GitHub pull request, a GitLab merge request or a Bitbucket pull request for each Jira issue carrying a configurable label (default: create-pr). The target repository is read from the ticket body - no repo parameter required at deploy time.",
|
|
855
855
|
"category": "automations",
|
|
856
856
|
"keywords": [
|
|
857
857
|
"jira",
|
|
858
|
-
"atlassian"
|
|
858
|
+
"atlassian",
|
|
859
|
+
"github",
|
|
860
|
+
"gitlab",
|
|
861
|
+
"bitbucket"
|
|
859
862
|
]
|
|
860
863
|
},
|
|
861
864
|
{
|
|
@@ -873,10 +876,9 @@
|
|
|
873
876
|
{
|
|
874
877
|
"name": "ticket-to-code-change",
|
|
875
878
|
"source": "./skills/ticket-to-code-change",
|
|
876
|
-
"description": "Set up
|
|
879
|
+
"description": "Set up Linear ticket-to-code-change automations for GitHub, GitLab, and Bitbucket.",
|
|
877
880
|
"category": "automations",
|
|
878
881
|
"keywords": [
|
|
879
|
-
"jira",
|
|
880
882
|
"linear",
|
|
881
883
|
"github",
|
|
882
884
|
"gitlab",
|
|
@@ -752,7 +752,7 @@
|
|
|
752
752
|
{
|
|
753
753
|
"name": "gitlab-issue-to-mr",
|
|
754
754
|
"source": "./skills/gitlab-issue-to-mr",
|
|
755
|
-
"description": "Create an automation that implements GitLab issues when a configurable trigger label is applied.
|
|
755
|
+
"description": "Create an automation that implements GitLab issues when a configurable trigger label is applied. Polls the projects deterministically and starts one conversation per label event; on OpenHands Cloud and Enterprise it reaches GitLab through the connected MCP server or integration, with no token to set up.",
|
|
756
756
|
"category": "automations",
|
|
757
757
|
"keywords": [
|
|
758
758
|
"gitlab",
|
|
@@ -851,11 +851,14 @@
|
|
|
851
851
|
{
|
|
852
852
|
"name": "jira-issue-to-pr",
|
|
853
853
|
"source": "./skills/jira-issue-to-pr",
|
|
854
|
-
"description": "Deploy a cron-based OpenHands automation that
|
|
854
|
+
"description": "Deploy a cron-based OpenHands automation that opens a GitHub pull request, a GitLab merge request or a Bitbucket pull request for each Jira issue carrying a configurable label (default: create-pr). The target repository is read from the ticket body - no repo parameter required at deploy time.",
|
|
855
855
|
"category": "automations",
|
|
856
856
|
"keywords": [
|
|
857
857
|
"jira",
|
|
858
|
-
"atlassian"
|
|
858
|
+
"atlassian",
|
|
859
|
+
"github",
|
|
860
|
+
"gitlab",
|
|
861
|
+
"bitbucket"
|
|
859
862
|
]
|
|
860
863
|
},
|
|
861
864
|
{
|
|
@@ -873,10 +876,9 @@
|
|
|
873
876
|
{
|
|
874
877
|
"name": "ticket-to-code-change",
|
|
875
878
|
"source": "./skills/ticket-to-code-change",
|
|
876
|
-
"description": "Set up
|
|
879
|
+
"description": "Set up Linear ticket-to-code-change automations for GitHub, GitLab, and Bitbucket.",
|
|
877
880
|
"category": "automations",
|
|
878
881
|
"keywords": [
|
|
879
|
-
"jira",
|
|
880
882
|
"linear",
|
|
881
883
|
"github",
|
|
882
884
|
"gitlab",
|
|
@@ -14,15 +14,15 @@ jobs:
|
|
|
14
14
|
- uses: actions/checkout@v7
|
|
15
15
|
- uses: actions/setup-node@v7
|
|
16
16
|
with:
|
|
17
|
-
node-version: "
|
|
17
|
+
node-version: "24"
|
|
18
18
|
- name: Build installable App package
|
|
19
19
|
run: node apps/vscode/scripts/package.mjs
|
|
20
20
|
- name: Validate package files
|
|
21
21
|
run: |
|
|
22
22
|
test -f dist/apps/vscode/canvas-extension.json
|
|
23
23
|
test -f dist/apps/vscode/extension.js
|
|
24
|
-
test -f dist/apps/vscode/
|
|
25
|
-
test -
|
|
24
|
+
test -f dist/apps/vscode/README.md
|
|
25
|
+
test -z "$(find dist/apps/vscode -type f -name '*.tar.gz' -print -quit)"
|
|
26
26
|
- uses: actions/upload-artifact@v7
|
|
27
27
|
with:
|
|
28
28
|
name: vscode-app-${{ github.sha }}
|
package/.plugin/marketplace.json
CHANGED
|
@@ -752,7 +752,7 @@
|
|
|
752
752
|
{
|
|
753
753
|
"name": "gitlab-issue-to-mr",
|
|
754
754
|
"source": "./skills/gitlab-issue-to-mr",
|
|
755
|
-
"description": "Create an automation that implements GitLab issues when a configurable trigger label is applied.
|
|
755
|
+
"description": "Create an automation that implements GitLab issues when a configurable trigger label is applied. Polls the projects deterministically and starts one conversation per label event; on OpenHands Cloud and Enterprise it reaches GitLab through the connected MCP server or integration, with no token to set up.",
|
|
756
756
|
"category": "automations",
|
|
757
757
|
"keywords": [
|
|
758
758
|
"gitlab",
|
|
@@ -851,11 +851,14 @@
|
|
|
851
851
|
{
|
|
852
852
|
"name": "jira-issue-to-pr",
|
|
853
853
|
"source": "./skills/jira-issue-to-pr",
|
|
854
|
-
"description": "Deploy a cron-based OpenHands automation that
|
|
854
|
+
"description": "Deploy a cron-based OpenHands automation that opens a GitHub pull request, a GitLab merge request or a Bitbucket pull request for each Jira issue carrying a configurable label (default: create-pr). The target repository is read from the ticket body - no repo parameter required at deploy time.",
|
|
855
855
|
"category": "automations",
|
|
856
856
|
"keywords": [
|
|
857
857
|
"jira",
|
|
858
|
-
"atlassian"
|
|
858
|
+
"atlassian",
|
|
859
|
+
"github",
|
|
860
|
+
"gitlab",
|
|
861
|
+
"bitbucket"
|
|
859
862
|
]
|
|
860
863
|
},
|
|
861
864
|
{
|
|
@@ -873,10 +876,9 @@
|
|
|
873
876
|
{
|
|
874
877
|
"name": "ticket-to-code-change",
|
|
875
878
|
"source": "./skills/ticket-to-code-change",
|
|
876
|
-
"description": "Set up
|
|
879
|
+
"description": "Set up Linear ticket-to-code-change automations for GitHub, GitLab, and Bitbucket.",
|
|
877
880
|
"category": "automations",
|
|
878
881
|
"keywords": [
|
|
879
|
-
"jira",
|
|
880
882
|
"linear",
|
|
881
883
|
"github",
|
|
882
884
|
"gitlab",
|
package/README.md
CHANGED
|
@@ -154,10 +154,10 @@ Official skills and plugins for OpenHands — the open-source AI software engine
|
|
|
154
154
|
| github-repo-monitor | skill | Create a cron automation that polls a GitHub repository for issue and PR comments containing a configurable trigger p... | `/github-monitor:poll` |
|
|
155
155
|
| github-stale-ci-pr-closer | skill | Warn and close abandoned pull requests whose required CI remains failing. | `/github-stale-ci-pr-closer` |
|
|
156
156
|
| gitlab | skill | Interact with GitLab repositories, merge requests, and APIs using the GITLAB_TOKEN environment variable. Use when wor... | — |
|
|
157
|
-
| gitlab-issue-to-mr | skill | Create an automation that implements GitLab issues when a configurable trigger label is applied.
|
|
157
|
+
| gitlab-issue-to-mr | skill | Create an automation that implements GitLab issues when a configurable trigger label is applied. Polls the projects d... | `/issue-to-mr:setup` |
|
|
158
158
|
| incident-retrospective | skill | Create an automation that drafts incident retrospectives by gathering incident-channel messages from Slack, collectin... | `/incident-retro:setup` |
|
|
159
159
|
| iterate | skill | Iterate on a GitHub pull request — drive it through CI, code review, and QA until merge-ready. Monitors state, fixes ... | `/iterate`, `/verify`, `/babysit` |
|
|
160
|
-
| jira-issue-to-pr | skill | Deploy a cron-based OpenHands automation that
|
|
160
|
+
| jira-issue-to-pr | skill | Deploy a cron-based OpenHands automation that opens a GitHub pull request, a GitLab merge request or a Bitbucket pull... | — |
|
|
161
161
|
| jupyter | skill | Read, modify, execute, and convert Jupyter notebooks programmatically. Use when working with .ipynb files for data sc... | — |
|
|
162
162
|
| kubernetes | skill | Set up and manage local Kubernetes clusters using KIND (Kubernetes IN Docker). Use when testing Kubernetes applicatio... | — |
|
|
163
163
|
| learn-from-code-review | skill | Distill code review feedback from GitHub PRs into reusable skills and guidelines. Use when users ask to learn from co... | `/learn-from-reviews` |
|
|
@@ -190,7 +190,7 @@ Official skills and plugins for OpenHands — the open-source AI software engine
|
|
|
190
190
|
| swift-linux | skill | Install and configure Swift programming language on Debian Linux for server-side development. Use when building Swift... | — |
|
|
191
191
|
| technical-writing | skill | Write and revise technical explanations in flowing, direct, conversational prose that stays concise without becoming ... | — |
|
|
192
192
|
| theme-factory | skill | Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc.... | — |
|
|
193
|
-
| ticket-to-code-change | skill | Set up
|
|
193
|
+
| ticket-to-code-change | skill | Set up Linear ticket-to-code-change automations for GitHub, GitLab, and Bitbucket. | `/ticket-to-code-change:setup` |
|
|
194
194
|
| upstream-fork-sync | skill | Keep a long-lived fork in sync with its upstream. Creates a cron automation that fetches upstream changes, rebases lo... | `/upstream-fork-sync:setup` |
|
|
195
195
|
| use-jev | skill | Use Jev, a general classifier, for typed choices, scores, and yes/no probabilities in software. | — |
|
|
196
196
|
| uv | skill | Common project, dependency, and environment operations using uv. | — |
|
package/apps/vscode/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# VS Code App
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Install this App in Agent Canvas by opening **Apps → Add App** and entering:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```text
|
|
6
|
+
https://github.com/OpenHands/extensions/tree/main/apps/vscode
|
|
7
|
+
```
|
|
6
8
|
|
|
7
|
-
The
|
|
9
|
+
The App provides a full-height OpenVSCode editor. Before the backend is ready, it shows backend status, an optional workspace-folder input, and revision-bound prepare/start/stop/refresh actions. Once ready, the page contains only the editor iframe.
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
## Artifact delivery
|
|
10
12
|
|
|
11
|
-
The
|
|
13
|
+
The repository contains no OpenVSCode binaries. During the explicit **Prepare and start** step, Agent Server downloads only the current platform's pinned OpenVSCode Server `v1.109.5` archive from its public GitHub Release, verifies the manifest-pinned SHA-256 digest, safely extracts it into an App-owned content-addressed cache, and starts it with App-owned home, user-data, and extensions directories. It does not send Agent Server or repository credentials to the artifact host.
|
|
12
14
|
|
|
13
|
-
The
|
|
15
|
+
The optional folder is persisted in browser storage and forwarded as the editor folder. It is a user convenience, not OS sandboxing.
|
|
@@ -9,10 +9,18 @@
|
|
|
9
9
|
"backend": {
|
|
10
10
|
"schema_version": 1,
|
|
11
11
|
"artifacts": {
|
|
12
|
-
"linux-amd64": {
|
|
13
|
-
|
|
12
|
+
"linux-amd64": {
|
|
13
|
+
"url": "https://github.com/gitpod-io/openvscode-server/releases/download/openvscode-server-v1.109.5/openvscode-server-v1.109.5-linux-x64.tar.gz",
|
|
14
|
+
"sha256": "b433bf4f0227321a7014d8460d10a8f958adc0f45aa79bd889e84e65e8f88363",
|
|
15
|
+
"strip_components": 1
|
|
16
|
+
},
|
|
17
|
+
"linux-arm64": {
|
|
18
|
+
"url": "https://github.com/gitpod-io/openvscode-server/releases/download/openvscode-server-v1.109.5/openvscode-server-v1.109.5-linux-arm64.tar.gz",
|
|
19
|
+
"sha256": "36d9c14036489b63de84ebace837fcacf7e60e669a0dc715802c5443684ea4dc",
|
|
20
|
+
"strip_components": 1
|
|
21
|
+
}
|
|
14
22
|
},
|
|
15
|
-
"argv": ["{artifact_dir}/bin/openvscode-
|
|
23
|
+
"argv": ["{artifact_dir}/bin/openvscode-server", "--host", "127.0.0.1", "--port", "{port}", "--without-connection-token", "--accept-server-license-terms", "--telemetry-level", "off", "--user-data-dir", "{data_dir}/user", "--extensions-dir", "{data_dir}/extensions", "--server-base-path", "/app-backends/vscode", "{data_dir}"],
|
|
16
24
|
"health": {"path": "/", "timeout_seconds": 90, "interval_seconds": 0.5},
|
|
17
25
|
"inherit_environment": ["PATH", "LANG", "LC_ALL", "TMPDIR"]
|
|
18
26
|
}
|
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}`);
|