Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/installers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
- run: npm ci
- run: npm run audit:public
- run: npm run desktop:installer -- --mac dmg --arm64
- run: node scripts/verify-packaged-runtime.mjs "out/installers/mac-arm64/劳博士.app/Contents/Resources/app"
- run: test "$(find out/installers -maxdepth 1 -name 'laobos-studio-*-macos-arm64.dmg' | wc -l | tr -d ' ')" = "1"
- uses: actions/upload-artifact@v7
with:
Expand All @@ -40,6 +41,7 @@ jobs:
- run: npm ci
- run: npm run audit:public
- run: npm run desktop:installer -- --win nsis --x64
- run: node scripts/verify-packaged-runtime.mjs "out/installers/win-unpacked/resources/app"
- shell: pwsh
run: |
$installers = @(Get-ChildItem "out/installers/laobos-studio-*-windows-x64-setup.exe")
Expand Down
42 changes: 21 additions & 21 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 20 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "laobos-agent-studio",
"version": "0.2.1",
"version": "0.2.2",
"description": "劳博士:基于 DeepSeek Harness 的本地 Agent 桌面客户端",
"author": "Modole contributors",
"license": "SEE LICENSE IN LICENSE",
Expand Down Expand Up @@ -43,7 +43,26 @@
},
"dependencies": {
"@browserops/bridge": "^0.0.11",
"@deepseek-ai/cordis-plugin-group": "1.0.1",
"@deepseek-ai/dsh": "0.1.0-rc.6",
"@deepseek-ai/dsh-anonymous-user-id": "0.1.0-rc.6",
"@deepseek-ai/dsh-atomic-write": "0.1.0-rc.6",
"@deepseek-ai/dsh-bash-local": "0.1.0-rc.6",
"@deepseek-ai/dsh-code-runtime": "0.1.0-rc.6",
"@deepseek-ai/dsh-compaction": "0.1.0-rc.6",
"@deepseek-ai/dsh-fs": "0.1.0-rc.6",
"@deepseek-ai/dsh-invariants": "0.1.0-rc.6",
"@deepseek-ai/dsh-output-retention": "0.1.0-rc.6",
"@deepseek-ai/dsh-sandbox": "0.1.0-rc.6",
"@deepseek-ai/dsh-scope": "0.1.0-rc.6",
"@deepseek-ai/dsh-session-telemetry": "0.1.0-rc.6",
"@deepseek-ai/dsh-session-title-llm": "0.1.0-rc.6",
"@deepseek-ai/dsh-shell": "0.1.0-rc.6",
"@deepseek-ai/dsh-spill": "0.1.0-rc.6",
"@deepseek-ai/dsh-subagent-in-process-driver": "0.1.0-rc.6",
"@deepseek-ai/dsh-subprocess": "0.1.0-rc.6",
"@deepseek-ai/dsh-timeout": "0.1.0-rc.6",
"@deepseek-ai/dsh-workflow": "0.1.0-rc.6",
"@laobos/dsh-system-tools": "file:packages/laobos-system-tools",
"@xterm/addon-fit": "^0.11.0",
"@xterm/xterm": "^6.0.0",
Expand Down
111 changes: 111 additions & 0 deletions scripts/verify-packaged-runtime.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env node

import { access, readdir, readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

const platformProvidedPackages = new Set(["electron"]);

async function exists(target) {
try {
await access(target);
return true;
} catch {
return false;
}
}

async function packageDirectories(nodeModulesDirectory, result, visited) {
if (visited.has(nodeModulesDirectory)) return;
visited.add(nodeModulesDirectory);

const entries = await readdir(nodeModulesDirectory, { withFileTypes: true }).catch((error) => {
if (error?.code === "ENOENT") return [];
throw error;
});

for (const entry of entries) {
if (entry.name.startsWith(".")) continue;
const entryPath = path.join(nodeModulesDirectory, entry.name);
if (entry.name.startsWith("@")) {
const scopedEntries = await readdir(entryPath, { withFileTypes: true });
for (const scopedEntry of scopedEntries) {
if (!scopedEntry.isDirectory() && !scopedEntry.isSymbolicLink()) continue;
const packageDirectory = path.join(entryPath, scopedEntry.name);
result.add(packageDirectory);
await packageDirectories(path.join(packageDirectory, "node_modules"), result, visited);
}
continue;
}

if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
result.add(entryPath);
await packageDirectories(path.join(entryPath, "node_modules"), result, visited);
}
}

function requiredDependencies(manifest) {
const optional = new Set(Object.keys(manifest.optionalDependencies || {}));
const required = new Set(
Object.keys(manifest.dependencies || {}).filter((name) => !optional.has(name)),
);

for (const name of Object.keys(manifest.peerDependencies || {})) {
if (manifest.peerDependenciesMeta?.[name]?.optional !== true) required.add(name);
}
return [...required].sort();
}

async function resolvesInsideApp(appDirectory, packageDirectory, dependency) {
if (platformProvidedPackages.has(dependency)) return true;
let current = packageDirectory;
while (current === appDirectory || current.startsWith(`${appDirectory}${path.sep}`)) {
if (await exists(path.join(current, "node_modules", dependency, "package.json"))) return true;
if (current === appDirectory) break;
current = path.dirname(current);
}
return false;
}

export async function verifyPackagedRuntime(appDirectoryInput) {
const appDirectory = path.resolve(appDirectoryInput);
const rootManifestPath = path.join(appDirectory, "package.json");
if (!(await exists(rootManifestPath))) {
throw new Error(`安装包应用目录无效,缺少 package.json:${appDirectory}`);
}

const packageRoots = new Set([appDirectory]);
await packageDirectories(path.join(appDirectory, "node_modules"), packageRoots, new Set());
const missing = [];

for (const packageDirectory of [...packageRoots].sort()) {
const manifestPath = path.join(packageDirectory, "package.json");
if (!(await exists(manifestPath))) continue;
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
const owner = manifest.name || path.relative(appDirectory, packageDirectory) || "<app>";
for (const dependency of requiredDependencies(manifest)) {
if (!(await resolvesInsideApp(appDirectory, packageDirectory, dependency))) {
missing.push({ dependency, owner });
}
}
}

if (missing.length > 0) {
const rows = missing
.map(({ dependency, owner }) => `- ${owner} -> ${dependency}`)
.join("\n");
throw new Error(`安装包缺少 ${missing.length} 项必需运行时依赖:\n${rows}`);
}

return { packageCount: packageRoots.size };
}

const isCommandLineEntry = process.argv[1]
&& path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);

if (isCommandLineEntry) {
const appDirectory = process.argv[2];
if (!appDirectory) throw new Error("用法:node scripts/verify-packaged-runtime.mjs <resources/app>");
const result = await verifyPackagedRuntime(appDirectory);
console.log(`安装包运行时依赖完整:已检查 ${result.packageCount} 个包。`);
}
Loading
Loading