@openhands/extensions 0.25.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 +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/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/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_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
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""Test that skills listed in marketplaces can be loaded as Codex/Claude plugins.
|
|
2
2
|
|
|
3
3
|
Every marketplace entry that references a ``skills/`` directory needs a
|
|
4
|
-
``.plugin/plugin.json`` manifest and vendor
|
|
4
|
+
``.plugin/plugin.json`` manifest and vendor manifests (``.codex-plugin``,
|
|
5
5
|
``.claude-plugin``) so that Codex and Claude Code can discover and load them.
|
|
6
|
+
A vendor manifest is either a symlink to ``.plugin/`` or a real directory
|
|
7
|
+
mirroring ``plugin.json`` (Codex install drops symlinked dirs; see issue #257).
|
|
6
8
|
|
|
7
9
|
Regression test for: https://github.com/OpenHands/extensions/issues/201
|
|
8
10
|
"""
|
|
@@ -61,18 +63,17 @@ class TestAllMarketplaceSkillsHaveManifests:
|
|
|
61
63
|
f"{', '.join(missing)}"
|
|
62
64
|
)
|
|
63
65
|
|
|
64
|
-
def
|
|
65
|
-
"""Every marketplace skill with a manifest must have vendor
|
|
66
|
+
def test_all_marketplace_skills_have_vendor_manifests(self):
|
|
67
|
+
"""Every marketplace skill with a manifest must have vendor manifests."""
|
|
66
68
|
problems = []
|
|
67
69
|
for name, path in _marketplace_skill_entries():
|
|
68
70
|
if not (path / ".plugin" / "plugin.json").exists():
|
|
69
71
|
continue
|
|
70
72
|
for vendor in VENDOR_SYMLINKS:
|
|
71
|
-
|
|
72
|
-
if not link.is_symlink():
|
|
73
|
+
if not _vendor_manifest_ok(path, vendor):
|
|
73
74
|
problems.append(f"{name}/{vendor}")
|
|
74
75
|
assert not problems, (
|
|
75
|
-
f"Missing vendor
|
|
76
|
+
f"Missing vendor manifests: {', '.join(problems)}"
|
|
76
77
|
)
|
|
77
78
|
|
|
78
79
|
def test_all_manifests_have_required_fields(self):
|
|
@@ -127,8 +128,20 @@ class TestIteratePluginLoading:
|
|
|
127
128
|
assert "verify" in command_names
|
|
128
129
|
|
|
129
130
|
|
|
131
|
+
def _vendor_manifest_ok(directory: Path, vendor: str) -> bool:
|
|
132
|
+
"""A vendor manifest is a symlink to .plugin/ or a real dir mirror."""
|
|
133
|
+
link = directory / vendor
|
|
134
|
+
if link.is_symlink():
|
|
135
|
+
return link.resolve() == (directory / ".plugin").resolve()
|
|
136
|
+
if link.is_dir():
|
|
137
|
+
src = directory / ".plugin" / "plugin.json"
|
|
138
|
+
dst = link / "plugin.json"
|
|
139
|
+
return src.is_file() and dst.is_file() and src.read_bytes() == dst.read_bytes()
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
|
|
130
143
|
class TestVendorSymlinksForManifests:
|
|
131
|
-
"""Every directory with .plugin/ must have vendor
|
|
144
|
+
"""Every directory with .plugin/ must have vendor manifests."""
|
|
132
145
|
|
|
133
146
|
@pytest.fixture(
|
|
134
147
|
params=list(_all_dirs_with_plugin_manifest()),
|
|
@@ -137,13 +150,10 @@ class TestVendorSymlinksForManifests:
|
|
|
137
150
|
def dir_with_manifest(self, request):
|
|
138
151
|
return request.param
|
|
139
152
|
|
|
140
|
-
def
|
|
141
|
-
"""Directories with .plugin/ must have .claude-plugin and .codex-plugin
|
|
153
|
+
def test_has_vendor_manifests(self, dir_with_manifest):
|
|
154
|
+
"""Directories with .plugin/ must have .claude-plugin and .codex-plugin manifests."""
|
|
142
155
|
for vendor in VENDOR_SYMLINKS:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
)
|
|
147
|
-
assert link.resolve() == (dir_with_manifest / ".plugin").resolve(), (
|
|
148
|
-
f"{link.relative_to(REPO_ROOT)} must point to .plugin"
|
|
156
|
+
assert _vendor_manifest_ok(dir_with_manifest, vendor), (
|
|
157
|
+
f"{dir_with_manifest / vendor} must be a symlink to .plugin "
|
|
158
|
+
"or a real directory mirroring plugin.json"
|
|
149
159
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Tests for scripts/sync_extensions.py core functions."""
|
|
2
2
|
|
|
3
|
+
import shutil
|
|
3
4
|
import sys
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
@@ -18,6 +19,7 @@ from sync_extensions import (
|
|
|
18
19
|
parse_frontmatter,
|
|
19
20
|
slash_triggers,
|
|
20
21
|
sync_commands,
|
|
22
|
+
sync_symlinks,
|
|
21
23
|
)
|
|
22
24
|
|
|
23
25
|
|
|
@@ -288,6 +290,106 @@ class TestSyncCommands:
|
|
|
288
290
|
assert any("manually-edited" in p for p in problems)
|
|
289
291
|
|
|
290
292
|
|
|
293
|
+
# ── vendor manifests ─────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
def _make_plugin_skill(root: Path, name: str = "demo") -> Path:
|
|
296
|
+
"""Create a fake skill dir with a canonical .plugin/plugin.json."""
|
|
297
|
+
skill = root / "skills" / name
|
|
298
|
+
(skill / ".plugin").mkdir(parents=True)
|
|
299
|
+
(skill / ".plugin" / "plugin.json").write_text('{"name": "demo"}\n')
|
|
300
|
+
return skill
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _make_real_vendor_mirror(skill: Path, vendor: str = ".codex-plugin") -> Path:
|
|
304
|
+
path = skill / vendor
|
|
305
|
+
path.mkdir(exist_ok=True)
|
|
306
|
+
(path / "plugin.json").write_text('{"name": "demo"}\n')
|
|
307
|
+
return path
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _point_sync_at(tmp_path, monkeypatch):
|
|
311
|
+
monkeypatch.setattr("sync_extensions.SKILL_DIRS", [tmp_path / "skills"])
|
|
312
|
+
monkeypatch.setattr("sync_extensions.REPO_ROOT", tmp_path)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class TestVendorManifests:
|
|
316
|
+
def test_iterate_vendor_dirs_are_real_and_mirror_canonical(self):
|
|
317
|
+
"""Codex drops symlinked dirs on install, so iterate ships real ones."""
|
|
318
|
+
iterate = REPO_ROOT / "skills" / "iterate"
|
|
319
|
+
canon = (iterate / ".plugin" / "plugin.json").read_bytes()
|
|
320
|
+
for vendor in (".codex-plugin", ".claude-plugin"):
|
|
321
|
+
path = iterate / vendor
|
|
322
|
+
assert not path.is_symlink(), f"{vendor} must not be a symlink"
|
|
323
|
+
assert path.is_dir(), f"{vendor} must be a real directory"
|
|
324
|
+
assert (path / "plugin.json").read_bytes() == canon
|
|
325
|
+
|
|
326
|
+
def test_symlink_to_canonical_passes(self, tmp_path, monkeypatch):
|
|
327
|
+
skill = _make_plugin_skill(tmp_path)
|
|
328
|
+
try:
|
|
329
|
+
for vendor in (".codex-plugin", ".claude-plugin"):
|
|
330
|
+
(skill / vendor).symlink_to(".plugin", target_is_directory=True)
|
|
331
|
+
except OSError:
|
|
332
|
+
pytest.skip("symlinks need privileges on this platform")
|
|
333
|
+
_point_sync_at(tmp_path, monkeypatch)
|
|
334
|
+
|
|
335
|
+
assert sync_symlinks(check=True) == []
|
|
336
|
+
|
|
337
|
+
def test_real_dir_mirror_passes_and_stale_copy_is_flagged(self, tmp_path, monkeypatch):
|
|
338
|
+
skill = _make_plugin_skill(tmp_path)
|
|
339
|
+
vendor = _make_real_vendor_mirror(skill)
|
|
340
|
+
_make_real_vendor_mirror(skill, ".claude-plugin")
|
|
341
|
+
_point_sync_at(tmp_path, monkeypatch)
|
|
342
|
+
|
|
343
|
+
assert sync_symlinks(check=True) == []
|
|
344
|
+
|
|
345
|
+
(vendor / "plugin.json").write_text('{"name": "stale"}\n')
|
|
346
|
+
problems = sync_symlinks(check=True)
|
|
347
|
+
assert any("stale manifest copy" in p for p in problems)
|
|
348
|
+
|
|
349
|
+
def test_fix_mode_refreshes_stale_copy(self, tmp_path, monkeypatch):
|
|
350
|
+
skill = _make_plugin_skill(tmp_path)
|
|
351
|
+
vendor = _make_real_vendor_mirror(skill)
|
|
352
|
+
_make_real_vendor_mirror(skill, ".claude-plugin")
|
|
353
|
+
(vendor / "plugin.json").write_text('{"name": "stale"}\n')
|
|
354
|
+
_point_sync_at(tmp_path, monkeypatch)
|
|
355
|
+
|
|
356
|
+
problems = sync_symlinks(check=False)
|
|
357
|
+
assert any("stale manifest copy" in p for p in problems)
|
|
358
|
+
assert (vendor / "plugin.json").read_text() == '{"name": "demo"}\n'
|
|
359
|
+
assert sync_symlinks(check=True) == []
|
|
360
|
+
|
|
361
|
+
def test_lossy_install_copy_keeps_codex_manifest(self, tmp_path):
|
|
362
|
+
"""Mimic Codex `plugin add`, which drops symlinked directories.
|
|
363
|
+
|
|
364
|
+
The installed cache must still contain
|
|
365
|
+
`.codex-plugin/plugin.json` for the plugin to load.
|
|
366
|
+
"""
|
|
367
|
+
src = REPO_ROOT / "skills" / "iterate"
|
|
368
|
+
dst = tmp_path / "iterate"
|
|
369
|
+
shutil.copytree(
|
|
370
|
+
src,
|
|
371
|
+
dst,
|
|
372
|
+
ignore=lambda d, names: [
|
|
373
|
+
n for n in names if (Path(d) / n).is_symlink()
|
|
374
|
+
],
|
|
375
|
+
)
|
|
376
|
+
manifest = dst / ".codex-plugin" / "plugin.json"
|
|
377
|
+
assert manifest.is_file(), "Codex install lost .codex-plugin/plugin.json"
|
|
378
|
+
assert manifest.read_bytes() == (src / ".plugin" / "plugin.json").read_bytes()
|
|
379
|
+
|
|
380
|
+
def test_missing_vendor_still_gets_symlink(self, tmp_path, monkeypatch):
|
|
381
|
+
skill = _make_plugin_skill(tmp_path)
|
|
382
|
+
_point_sync_at(tmp_path, monkeypatch)
|
|
383
|
+
try:
|
|
384
|
+
sync_symlinks(check=False)
|
|
385
|
+
except OSError:
|
|
386
|
+
pytest.skip("symlinks need privileges on this platform")
|
|
387
|
+
|
|
388
|
+
assert (skill / ".codex-plugin").is_symlink()
|
|
389
|
+
assert (skill / ".claude-plugin").is_symlink()
|
|
390
|
+
assert sync_symlinks(check=True) == []
|
|
391
|
+
|
|
392
|
+
|
|
291
393
|
# ── marketplace source paths ─────────────────────────────────────────
|
|
292
394
|
|
|
293
395
|
class TestMarketplaceSourcePaths:
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import subprocess
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
6
|
+
APP = ROOT / "apps" / "vscode"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_vscode_app_manifest_has_contained_pinned_dual_arch_artifacts():
|
|
10
|
+
manifest = json.loads((APP / "canvas-extension.json").read_text())
|
|
11
|
+
backend = manifest["backend"]
|
|
12
|
+
assert set(backend["artifacts"]) == {"linux-amd64", "linux-arm64"}
|
|
13
|
+
for artifact in backend["artifacts"].values():
|
|
14
|
+
assert artifact["path"].startswith("backend/")
|
|
15
|
+
assert len(artifact["sha256"]) == 64
|
|
16
|
+
assert "HOME" not in backend["inherit_environment"]
|
|
17
|
+
assert manifest["entrypoint"] == "extension.js"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_ready_entrypoint_is_self_contained_and_full_height():
|
|
21
|
+
source = (APP / "extension.js").read_text()
|
|
22
|
+
assert "export function activate" in source
|
|
23
|
+
assert "host.appBackendView.mount" in source
|
|
24
|
+
assert "query: { folder }" in source
|
|
25
|
+
assert "localStorage" in source
|
|
26
|
+
assert "height:100vh" in source
|
|
27
|
+
assert "<iframe" not in source
|
|
28
|
+
assert "import " not in source
|
|
29
|
+
subprocess.run(["node", "--check", str(APP / "extension.js")], check=True)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_release_binaries_are_not_committed():
|
|
33
|
+
assert not list(APP.glob("backend/*"))
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { activate } from "../apps/vscode/extension.js";
|
|
3
|
+
|
|
4
|
+
class Element {
|
|
5
|
+
constructor(tag) { this.tagName = tag; this.children = []; this.listeners = {}; this.style = {}; }
|
|
6
|
+
append(...children) { this.children.push(...children); }
|
|
7
|
+
replaceChildren(...children) { this.children = children; }
|
|
8
|
+
setAttribute() {}
|
|
9
|
+
addEventListener(name, fn) { this.listeners[name] = fn; }
|
|
10
|
+
async click() { await this.listeners.click(); }
|
|
11
|
+
}
|
|
12
|
+
class Document { createElement(tag) { return new Element(tag); } createTextNode(text) { return { textContent: text }; } }
|
|
13
|
+
const document = new Document();
|
|
14
|
+
globalThis.document = document;
|
|
15
|
+
globalThis.localStorage = { values: new Map(), getItem(k) { return this.values.get(k) ?? null; }, setItem(k, v) { this.values.set(k, v); }, removeItem(k) { this.values.delete(k); } };
|
|
16
|
+
|
|
17
|
+
const requests = [];
|
|
18
|
+
let status = { name: "vscode", state: "stopped", revision: "artifact-revision" };
|
|
19
|
+
const host = {
|
|
20
|
+
backend: { id: "backend-a", orgId: "org-a" }, extension: { name: "vscode" },
|
|
21
|
+
agentServer: { request: async ({ method, path, body }) => {
|
|
22
|
+
requests.push({ method, path, body });
|
|
23
|
+
if (method === "GET") return status;
|
|
24
|
+
if (path.endsWith("/prepare")) return status;
|
|
25
|
+
if (path.endsWith("/start")) { status = { ...status, state: "ready" }; return status; }
|
|
26
|
+
return status;
|
|
27
|
+
} },
|
|
28
|
+
appBackendView: { mount: ({ container, query }) => { container.mountedQuery = query; return () => { container.disposed = true; }; } },
|
|
29
|
+
registerPage: (_id, mount) => { host.mount = mount; return () => {}; },
|
|
30
|
+
};
|
|
31
|
+
await activate(host);
|
|
32
|
+
const container = new Element("main");
|
|
33
|
+
const dispose = await host.mount({ container });
|
|
34
|
+
assert.equal(container.children[0].children[0].children[0].textContent, "VS Code setup");
|
|
35
|
+
await container.children[0].children[0].children[3].children[0].click();
|
|
36
|
+
assert.deepEqual(requests.slice(0, 3).map(({ method, path, body }) => [method, path, body]), [
|
|
37
|
+
["GET", "/api/canvas-extensions/installed/vscode/backend", undefined],
|
|
38
|
+
["POST", "/api/canvas-extensions/installed/vscode/backend/prepare", { revision: "artifact-revision" }],
|
|
39
|
+
["POST", "/api/canvas-extensions/installed/vscode/backend/start", { revision: "artifact-revision" }],
|
|
40
|
+
]);
|
|
41
|
+
assert.equal(container.children[0].children[0].mountedQuery, undefined);
|
|
42
|
+
dispose();
|
|
43
|
+
console.log("VS Code App lifecycle behavior passed");
|