dsh-crate-web 0.1.2-preview.3 → 0.1.2-preview.5
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/README.md +4 -2
- package/core/dsh_pack/_version.py +1 -1
- package/core/dsh_pack/cli.py +2 -2
- package/core/dsh_pack/desktop_verify.py +6 -0
- package/core/dsh_pack/errors.py +14 -0
- package/core/dsh_pack/export.py +58 -15
- package/lib/index.js +6 -29
- package/package.json +3 -2
- package/runtime-inventory.mjs +31 -0
- package/skills/dsh-crate-troubleshooting/compatibility.json +1 -1
- package/skills/dsh-crate-troubleshooting/reference/diagnostics.generated.md +1 -1
- package/verify-desktop.mjs +6 -2
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# DSH Crate — Preview
|
|
2
2
|
|
|
3
|
-
DSH Crate 为 DeepSeek Harness 的设置页提供环境包导出、检查、恢复、验证和历史记录。正式 npm 包名是 `dsh-crate-web`;本候选为 `0.1.2-preview.
|
|
3
|
+
DSH Crate 为 DeepSeek Harness 的设置页提供环境包导出、检查、恢复、验证和历史记录。正式 npm 包名是 `dsh-crate-web`;本候选为 `0.1.2-preview.5`,使用 `preview` 标签,保留既有 `latest`。
|
|
4
4
|
|
|
5
5
|
可通过官方插件入口安装明确版本:
|
|
6
6
|
|
|
7
7
|
```powershell
|
|
8
|
-
dsh plugin --profile web add dsh-crate-web@0.1.2-preview.
|
|
8
|
+
dsh plugin --profile web add dsh-crate-web@0.1.2-preview.5
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
重启后打开 Settings → DSH Crate。离线环境可从同版本 tgz 安装。Windows x64 随包保留 Python 3.14.6,无需另外安装系统 Python。其他系统未完成本轮桌面验收。
|
|
@@ -23,3 +23,5 @@ Import 默认恢复到新 Profile,先执行 Preflight 和计划检查。覆盖
|
|
|
23
23
|
工作流先扫描提交与实际 tgz,再让发布任务复用同一 tgz 并核对 SHA-256。新代码必须升 prerelease 版本;同版本不同内容会停止。现有 npm Trusted Publisher 对应本仓库和 `publish-dsh-crate-web.yml`,无需把长期 npm Token 写入仓库。
|
|
24
24
|
|
|
25
25
|
宿主依赖:DSH 模块与 React 由目标 DSH 宿主提供,npm 将这些 peer 标记为 optional,避免在空 Profile 中自动安装另一套旧 DSH。这不表示插件可以脱离 DSH 运行,也不跳过实际宿主服务检查;客户端 UI 组件仍须由 DSH 模块加载器提供。发布流程对最终 tgz 执行普通干净安装,不使用 legacy-peer-deps 或 force。官方 CLI 与真实桌面的覆盖以对应版本的验证报告为准。
|
|
26
|
+
|
|
27
|
+
本候选修复了两个实际入口问题:无系统 npm 时,Export 可以复用桌面提供的 pnpm,禁用打包脚本与钩子并保留源清单;默认 Verify 根据完整的运行插件清单判断 Inspector 端点是否适用,清单不完整或已启用的 Inspector 缺监听时仍失败。Windows x64 / 原装 DSH 0.2.1-rc.5 下,前一候选的页面导出、下载、检查和新 Profile 恢复真实通过;本候选使用同一配置、仅更新 Crate 制品的诊断包,真实通过恢复、默认 Verify 的组合、启动、页面就绪、空白会话动作和清理。会话动作遵循官方行为,复用了已有空白会话,没有制造新的会话文件。默认 Verify 总结果仍为 UNTESTED:零模型,没有覆盖模型、原生工具、计划重启和其后的插件测试;本轮也没有覆盖启用 Inspector 的真实运行或历史 Worker 取消故障。诊断包不是本候选重新完成的页面 Export,不能将两轮证据合称为完整候选验收。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.2-preview.
|
|
1
|
+
__version__ = "0.1.2-preview.5"
|
package/core/dsh_pack/cli.py
CHANGED
|
@@ -8,7 +8,7 @@ import os
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Sequence
|
|
10
10
|
|
|
11
|
-
from .errors import DshPackError, PackImportError
|
|
11
|
+
from .errors import DshPackError, ExportError, PackImportError
|
|
12
12
|
from .export import EMBEDDED, REFERENCE_ONLY, ExportOptions, PluginExportOptions, export_profile
|
|
13
13
|
from .importer import ImportOptions, create_import_plan, delete_profile, import_pack
|
|
14
14
|
from .preflight import PreflightContext, inspect_pack, render_text
|
|
@@ -250,7 +250,7 @@ def _run_export(args: argparse.Namespace) -> int:
|
|
|
250
250
|
return 0
|
|
251
251
|
result = export_profile(args.profile, args.output, options=options)
|
|
252
252
|
except (DshPackError, OSError, ValueError, json.JSONDecodeError) as error:
|
|
253
|
-
payload = {"status": "failed", "error": {"message": str(error)}}
|
|
253
|
+
payload = {"status": "failed", "error": error.as_dict() if isinstance(error, ExportError) else {"message": str(error)}}
|
|
254
254
|
if args.json:
|
|
255
255
|
_emit(payload, args.command)
|
|
256
256
|
else:
|
|
@@ -154,6 +154,9 @@ class DesktopVerifyAdapter:
|
|
|
154
154
|
|
|
155
155
|
def _surface(self):
|
|
156
156
|
value = self._rpc('surface')
|
|
157
|
+
inspector = value.get('inspector')
|
|
158
|
+
if not isinstance(inspector, dict) or inspector.get('inventoryComplete') is not True or type(inspector.get('applicable')) is not bool:
|
|
159
|
+
raise RuntimeError('Inspector applicability not confirmed: runtime inventory missing or incomplete')
|
|
157
160
|
self.endpoints[f'web-{self.generation}'] = value['webPort']
|
|
158
161
|
table = tcp_rows()
|
|
159
162
|
with self.lock:
|
|
@@ -164,6 +167,9 @@ class DesktopVerifyAdapter:
|
|
|
164
167
|
for name, port in self.endpoints.items():
|
|
165
168
|
if not name.endswith('-' + str(self.generation)):
|
|
166
169
|
continue
|
|
170
|
+
if name.startswith('inspector-') and not inspector['applicable']:
|
|
171
|
+
evidence.append({'name': name, 'port': port, 'status': 'NOT_APPLICABLE', 'applicability': inspector})
|
|
172
|
+
continue
|
|
167
173
|
listeners = [row for row in table if row['localPort'] == port and row['state'] == 'LISTEN']
|
|
168
174
|
if not listeners or not all(row['pid'] in pids for row in listeners):
|
|
169
175
|
raise RuntimeError('Owned endpoint listener not confirmed: ' + name)
|
package/core/dsh_pack/errors.py
CHANGED
|
@@ -28,6 +28,20 @@ class IntegrityError(DshPackError):
|
|
|
28
28
|
class ExportError(DshPackError):
|
|
29
29
|
"""A Profile cannot be exported into a valid DSH Pack."""
|
|
30
30
|
|
|
31
|
+
def __init__(self, message: str, *, stage: str | None = None, code: str | None = None, details: dict | None = None) -> None:
|
|
32
|
+
super().__init__(message)
|
|
33
|
+
self.stage, self.code, self.details = stage, code, details
|
|
34
|
+
|
|
35
|
+
def as_dict(self) -> dict:
|
|
36
|
+
result = {"message": str(self)}
|
|
37
|
+
if self.stage is not None:
|
|
38
|
+
result["stage"] = self.stage
|
|
39
|
+
if self.code is not None:
|
|
40
|
+
result["code"] = self.code
|
|
41
|
+
if self.details is not None:
|
|
42
|
+
result["details"] = self.details
|
|
43
|
+
return result
|
|
44
|
+
|
|
31
45
|
|
|
32
46
|
class PackImportError(DshPackError):
|
|
33
47
|
"""A Pack cannot be prepared as a new Profile."""
|
package/core/dsh_pack/export.py
CHANGED
|
@@ -492,12 +492,22 @@ def _with_bundle_composition(profile_files: dict[str, bytes], bundles: Sequence[
|
|
|
492
492
|
profile_files["package.json"] = (json.dumps(manifest, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
|
|
493
493
|
|
|
494
494
|
|
|
495
|
-
def _npm_command(configured: str | None) -> str:
|
|
495
|
+
def _npm_command(configured: str | None) -> str | tuple[str, ...]:
|
|
496
496
|
if configured:
|
|
497
497
|
return configured
|
|
498
|
-
if os.name == "nt"
|
|
499
|
-
|
|
500
|
-
|
|
498
|
+
available = (shutil.which("npm.cmd") if os.name == "nt" else None) or shutil.which("npm")
|
|
499
|
+
if available:
|
|
500
|
+
return available
|
|
501
|
+
value = os.environ.get("DSH_PACK_PNPM_COMMAND")
|
|
502
|
+
if value:
|
|
503
|
+
try:
|
|
504
|
+
argv = json.loads(value)
|
|
505
|
+
except ValueError:
|
|
506
|
+
argv = None
|
|
507
|
+
if not isinstance(argv, list) or not argv or not all(isinstance(item, str) and item and "\x00" not in item for item in argv):
|
|
508
|
+
raise ExportError("DSH_PACK_PNPM_COMMAND must be a nonempty JSON argv array", stage="package-artifact", code="EXPORT_PACK_COMMAND_INVALID")
|
|
509
|
+
return tuple(argv)
|
|
510
|
+
return "npm.cmd" if os.name == "nt" else "npm"
|
|
501
511
|
|
|
502
512
|
|
|
503
513
|
def _bundle_patch_path(package_dir: Path, package_name: str, patch: str) -> str:
|
|
@@ -613,13 +623,14 @@ def _validate_tarball(content: bytes, package_name: str, required_file: str | Se
|
|
|
613
623
|
def _npm_pack(
|
|
614
624
|
package_dir: Path,
|
|
615
625
|
package_name: str,
|
|
616
|
-
npm_command: str,
|
|
626
|
+
npm_command: str | tuple[str, ...],
|
|
617
627
|
*,
|
|
618
628
|
required_file: str | Sequence[str] | None = None,
|
|
619
629
|
) -> bytes:
|
|
620
630
|
with tempfile.TemporaryDirectory(prefix="dsh-pack-npm-") as directory:
|
|
621
631
|
cache_directory = Path(directory) / "npm-cache"
|
|
622
632
|
cache_directory.mkdir()
|
|
633
|
+
is_pnpm = isinstance(npm_command, tuple)
|
|
623
634
|
command = [
|
|
624
635
|
npm_command,
|
|
625
636
|
"pack",
|
|
@@ -630,24 +641,56 @@ def _npm_pack(
|
|
|
630
641
|
"--cache",
|
|
631
642
|
str(cache_directory),
|
|
632
643
|
]
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
644
|
+
environment = None
|
|
645
|
+
if is_pnpm:
|
|
646
|
+
command = [*npm_command, "pack", "--pack-destination", directory,
|
|
647
|
+
"--skip-manifest-obfuscation", "--config.ignore-scripts=true",
|
|
648
|
+
"--config.ignore-pnpmfile=true", "--config.offline=true",
|
|
649
|
+
"--config.pm-on-fail=ignore", "--config.runtime-on-fail=ignore"]
|
|
650
|
+
environment = dict(os.environ, ELECTRON_RUN_AS_NODE="1")
|
|
651
|
+
try:
|
|
652
|
+
completed = subprocess.run(
|
|
653
|
+
command, cwd=package_dir, capture_output=True, text=True,
|
|
654
|
+
check=False, timeout=120, **({"env": environment} if environment is not None else {}),
|
|
655
|
+
)
|
|
656
|
+
except (OSError, subprocess.TimeoutExpired) as error:
|
|
657
|
+
raise ExportError(
|
|
658
|
+
f"Cannot pack {package_name}: {error}. Install npm or use the desktop-provided pnpm entry.",
|
|
659
|
+
stage="package-artifact", code="EXPORT_PACK_START_FAILED",
|
|
660
|
+
details={"package": package_name, "command": command, "cwd": str(package_dir), "originalError": str(error)},
|
|
661
|
+
) from error
|
|
641
662
|
if completed.returncode != 0:
|
|
642
663
|
raise ExportError(
|
|
643
664
|
f"npm pack failed for {package_name} with exit code {completed.returncode}: "
|
|
644
|
-
f"{completed.stderr.strip() or completed.stdout.strip()}"
|
|
665
|
+
f"{completed.stderr.strip() or completed.stdout.strip()}",
|
|
666
|
+
stage="package-artifact", code="EXPORT_PACK_FAILED",
|
|
667
|
+
details={"package": package_name, "command": command, "cwd": str(package_dir),
|
|
668
|
+
"exitCode": completed.returncode, "stdout": completed.stdout, "stderr": completed.stderr},
|
|
645
669
|
)
|
|
646
670
|
tarballs = sorted(Path(directory).glob("*.tgz"))
|
|
647
671
|
if len(tarballs) != 1:
|
|
648
672
|
raise ExportError(f"npm pack produced {len(tarballs)} tarballs for {package_name}")
|
|
649
673
|
content = tarballs[0].read_bytes()
|
|
650
674
|
_validate_tarball(content, package_name, required_file)
|
|
675
|
+
if is_pnpm:
|
|
676
|
+
# pnpm strips fields even with --skip-manifest-obfuscation. Preserve the
|
|
677
|
+
# installed manifest exactly; the artifact is a snapshot, not a publish.
|
|
678
|
+
import io
|
|
679
|
+
manifest = (package_dir / "package.json").read_bytes()
|
|
680
|
+
output = io.BytesIO()
|
|
681
|
+
with tarfile.open(fileobj=io.BytesIO(content), mode="r:gz") as source:
|
|
682
|
+
manifests = [member for member in source.getmembers() if member.name == "package/package.json"]
|
|
683
|
+
if len(manifests) != 1 or not manifests[0].isfile():
|
|
684
|
+
raise ExportError(f"pnpm pack artifact has no unique regular package manifest: {package_name}")
|
|
685
|
+
with tarfile.open(fileobj=output, mode="w:gz") as target:
|
|
686
|
+
for member in source.getmembers():
|
|
687
|
+
if member.name == "package/package.json":
|
|
688
|
+
member.size = len(manifest)
|
|
689
|
+
target.addfile(member, io.BytesIO(manifest))
|
|
690
|
+
else:
|
|
691
|
+
target.addfile(member, source.extractfile(member) if member.isfile() else None)
|
|
692
|
+
content = output.getvalue()
|
|
693
|
+
_validate_tarball(content, package_name, required_file)
|
|
651
694
|
return content
|
|
652
695
|
|
|
653
696
|
|
|
@@ -663,7 +706,7 @@ def _plugin_lock_entry(
|
|
|
663
706
|
specifier: str,
|
|
664
707
|
bundle_order: int | None,
|
|
665
708
|
options: PluginExportOptions,
|
|
666
|
-
npm_command: str,
|
|
709
|
+
npm_command: str | tuple[str, ...],
|
|
667
710
|
) -> tuple[dict[str, Any], dict[str, bytes]]:
|
|
668
711
|
package_dir, package_manifest = _installed_package(profile_dir, package_name, bundle=bundle_order is not None)
|
|
669
712
|
installed_name = _string(package_manifest.get("name", package_name), f"{package_name}.package.json.name")
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readRuntimeInventory } from "../runtime-inventory.mjs";
|
|
1
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
3
|
import { access, lstat, mkdir, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
4
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
@@ -951,33 +952,7 @@ const SAFE_PLUGIN_NAME = /^@?[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)?$/;
|
|
|
951
952
|
const SAFE_OPERATION_ID = /^[0-9a-f-]{36}$/i;
|
|
952
953
|
const CRATE_PACKAGE_NAME = "dsh-crate-web";
|
|
953
954
|
/** Cordis FiberState enum values; mirrors dsh-host-plugin-inventory phases. */
|
|
954
|
-
|
|
955
|
-
0: "pending",
|
|
956
|
-
1: "loading",
|
|
957
|
-
2: "active",
|
|
958
|
-
3: "failed",
|
|
959
|
-
4: null,
|
|
960
|
-
5: "unloading"
|
|
961
|
-
};
|
|
962
|
-
/** Read-only projection of the currently running Loader's non-group entries. */
|
|
963
|
-
function listRuntimePlugins(ctx) {
|
|
964
|
-
const loader = ctx.loader;
|
|
965
|
-
if (!loader || typeof loader.entries !== "function") return [];
|
|
966
|
-
const rows = [];
|
|
967
|
-
try {
|
|
968
|
-
for (const entry of loader.entries()) {
|
|
969
|
-
if (entry.options.group) continue;
|
|
970
|
-
if (entry.options.name.startsWith("cordis:")) continue;
|
|
971
|
-
rows.push({
|
|
972
|
-
id: entry.id,
|
|
973
|
-
name: entry.options.name,
|
|
974
|
-
enabled: entry.disabled !== true,
|
|
975
|
-
phase: entry.fiber ? RUNTIME_FIBER_PHASE[entry.fiber.state] ?? null : null
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
} catch {}
|
|
979
|
-
return rows;
|
|
980
|
-
}
|
|
955
|
+
|
|
981
956
|
function json(res, status, body) {
|
|
982
957
|
const payload = JSON.stringify(body);
|
|
983
958
|
res.writeHead(status, {
|
|
@@ -1594,7 +1569,7 @@ async function createProfile(home, body, workspace) {
|
|
|
1594
1569
|
history: item
|
|
1595
1570
|
};
|
|
1596
1571
|
}
|
|
1597
|
-
async function handleApi(home, body, runtimePlugins = []) {
|
|
1572
|
+
async function handleApi(home, body, runtimePlugins = [], runtimeInventoryComplete = false) {
|
|
1598
1573
|
const action = body.action;
|
|
1599
1574
|
const workspace = await ensureWorkspace(home);
|
|
1600
1575
|
if (action === "profiles") {
|
|
@@ -1608,6 +1583,7 @@ async function handleApi(home, body, runtimePlugins = []) {
|
|
|
1608
1583
|
status: "ok",
|
|
1609
1584
|
profiles: await listProfiles(home),
|
|
1610
1585
|
runtimePlugins: classifiedPlugins,
|
|
1586
|
+
runtimeInventoryComplete,
|
|
1611
1587
|
runtime
|
|
1612
1588
|
};
|
|
1613
1589
|
}
|
|
@@ -2137,7 +2113,8 @@ function apply(ctx, config = {}) {
|
|
|
2137
2113
|
return;
|
|
2138
2114
|
}
|
|
2139
2115
|
try {
|
|
2140
|
-
|
|
2116
|
+
const inventory = readRuntimeInventory(ctx);
|
|
2117
|
+
json(res, 200, await handleApi(home, await readRequest(req), inventory.rows, inventory.complete));
|
|
2141
2118
|
} catch (error) {
|
|
2142
2119
|
json(res, 400, {
|
|
2143
2120
|
status: "failed",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-crate-web",
|
|
3
3
|
"description": "DSH Web Settings surface for DSH Crate export, import, inspect, and history",
|
|
4
|
-
"version": "0.1.2-preview.
|
|
4
|
+
"version": "0.1.2-preview.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"skills/dsh-crate-troubleshooting/SKILL.md",
|
|
30
30
|
"skills/dsh-crate-troubleshooting/compatibility.json",
|
|
31
31
|
"skills/dsh-crate-troubleshooting/reference/*.md",
|
|
32
|
-
"THIRD-PARTY-LICENSE.txt"
|
|
32
|
+
"THIRD-PARTY-LICENSE.txt",
|
|
33
|
+
"runtime-inventory.mjs"
|
|
33
34
|
],
|
|
34
35
|
"publishConfig": {
|
|
35
36
|
"access": "public",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Read Loader metadata only; a partial enumeration never proves absence. */
|
|
2
|
+
const phases = { 0: 'pending', 1: 'loading', 2: 'active', 3: 'failed', 4: null, 5: 'unloading' }
|
|
3
|
+
export function readRuntimeInventory(ctx) {
|
|
4
|
+
const rows = []
|
|
5
|
+
const loader = ctx?.loader
|
|
6
|
+
if (!loader || typeof loader.entries !== 'function') return { rows, complete: false }
|
|
7
|
+
try {
|
|
8
|
+
for (const entry of loader.entries()) {
|
|
9
|
+
if (entry.options.group) continue
|
|
10
|
+
if (typeof entry.options.name !== 'string' || typeof entry.id !== 'string') throw new Error('invalid Loader row')
|
|
11
|
+
if (entry.options.name.startsWith('cordis:')) continue
|
|
12
|
+
rows.push({ id: entry.id, name: entry.options.name, enabled: entry.disabled !== true,
|
|
13
|
+
phase: entry.fiber ? (phases[entry.fiber.state] ?? null) : null })
|
|
14
|
+
}
|
|
15
|
+
return { rows, complete: true }
|
|
16
|
+
} catch {
|
|
17
|
+
return { rows, complete: false }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function inspectorApplicability(rows, complete) {
|
|
22
|
+
if (complete !== true || !Array.isArray(rows) || !rows.length || rows.some(row => !row ||
|
|
23
|
+
typeof row.id !== 'string' || typeof row.name !== 'string' || typeof row.enabled !== 'boolean')) {
|
|
24
|
+
throw new Error('Inspector applicability not confirmed: runtime inventory missing or incomplete')
|
|
25
|
+
}
|
|
26
|
+
const matches = rows.filter(row => row.name === '@deepseek-ai/dsh-experimental-inspector')
|
|
27
|
+
const applicable = matches.some(row => row.enabled)
|
|
28
|
+
return { inventoryComplete: true, inventoryCount: rows.length, applicable,
|
|
29
|
+
reason: applicable ? 'enabled' : matches.length ? 'disabled' : 'not-declared',
|
|
30
|
+
plugins: matches.map(({ id, name, enabled, phase }) => ({ id, name, enabled, phase })) }
|
|
31
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DSH Crate Diagnostic Codes
|
|
2
2
|
|
|
3
|
-
> Generated from `core/dsh_pack/diagnostics.py` (schema v1, producer `dsh-crate`, crate 0.1.2-preview.
|
|
3
|
+
> Generated from `core/dsh_pack/diagnostics.py` (schema v1, producer `dsh-crate`, crate 0.1.2-preview.5). Do not edit by hand.
|
|
4
4
|
|
|
5
5
|
Total codes: **79**. Stages: **16**.
|
|
6
6
|
|
package/verify-desktop.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { open, mkdir, readdir, lstat, readFile, writeFile } from 'node:fs/promis
|
|
|
7
7
|
import { createServer } from 'node:net'
|
|
8
8
|
import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
|
|
9
9
|
import { pathToFileURL } from 'node:url'
|
|
10
|
+
import { inspectorApplicability } from './runtime-inventory.mjs'
|
|
10
11
|
|
|
11
12
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
12
13
|
const inside = (root, path) => { const part = relative(resolve(root), resolve(path)); return part === '' || (!part.startsWith('..') && !isAbsolute(part)) }
|
|
@@ -61,7 +62,9 @@ export function validateSurface(value, home, webPort) {
|
|
|
61
62
|
throw new Error('Verification backend identity does not match its isolated desktop')
|
|
62
63
|
}
|
|
63
64
|
if (!Number.isInteger(webPort) || webPort < 1 || webPort > 65535) throw new Error('Owned backend Web port missing from startup log')
|
|
64
|
-
|
|
65
|
+
const inspector = inspectorApplicability(value.runtimePlugins, value.runtimeInventoryComplete)
|
|
66
|
+
const { runtimePlugins, runtimeInventoryComplete, ...surface } = value
|
|
67
|
+
return { ...surface, inspector, webPort, authenticationContext: 'official owned desktop protocol and authenticated profiles route; credentials not exported' }
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
class Cdp {
|
|
@@ -247,7 +250,8 @@ export class DesktopController {
|
|
|
247
250
|
let result; try { result = await backend.json() } catch { result = {} }
|
|
248
251
|
return { accountMenu, httpStatus: response.status, origin: location.origin, protocol: location.protocol, hostname: location.hostname,
|
|
249
252
|
backendHttpStatus: backend.status, backendStatus: result.status, runtimePid: result.runtime?.pid,
|
|
250
|
-
|
|
253
|
+
runtimeHome: result.runtime?.dshHome, runtimeProfile: result.runtime?.currentProfile,
|
|
254
|
+
runtimePlugins: result.runtimePlugins, runtimeInventoryComplete: result.runtimeInventoryComplete };
|
|
251
255
|
})()`)
|
|
252
256
|
const log = join(this.config.job, `desktop-${this.generation}.stdout.log`)
|
|
253
257
|
if ((await lstat(log)).size > 1024 * 1024) throw new Error('Owned desktop startup log exceeded observation limit')
|