This commit is contained in:
Oleg Maslov
2026-09-02 10:14:22 +02:00
parent 0c3e2ead3b
commit b20b138fe4
771 changed files with 161561 additions and 9027 deletions

View File

@@ -5,6 +5,31 @@ import path from 'node:path';
import { describe, expect, it } from 'vitest';
const ROOT = path.resolve(import.meta.dirname, '..', '..', '..');
const SOURCE_RESOURCES = path.join(ROOT, 'app', 'src-tauri', 'resources');
const SOURCE_BUNDLED_NODE = path.join(
SOURCE_RESOURCES,
process.platform === 'win32' ? 'node.exe' : 'node',
);
const SOURCE_STAGED_NODE_MODULES = path.join(SOURCE_RESOURCES, 'node_modules');
const SOURCE_STAGED_SERVICE = path.join(SOURCE_RESOURCES, 'service.js');
const SOURCE_STAGED_MEMORY_MCP = path.join(
SOURCE_STAGED_NODE_MODULES,
'waggle-memory-mcp',
'dist',
'index.js',
);
const REQUIRE_STAGED_RUNTIME = process.env.WAGGLE_VERIFY_STAGED_HOOK_RUNTIME === '1';
const ANY_STAGED_RUNTIME = [
SOURCE_BUNDLED_NODE,
SOURCE_STAGED_NODE_MODULES,
SOURCE_STAGED_SERVICE,
].some((entry) => fs.existsSync(entry));
const COMPLETE_STAGED_RUNTIME = [
SOURCE_BUNDLED_NODE,
SOURCE_STAGED_NODE_MODULES,
SOURCE_STAGED_SERVICE,
SOURCE_STAGED_MEMORY_MCP,
].every((entry) => fs.existsSync(entry));
function makeTempRoot(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-hook-packages-'));
@@ -23,19 +48,29 @@ async function runInCwd(
cwd: string,
home: string,
stripPath = false,
hookNodePath = process.execPath,
): Promise<CommandResult> {
return new Promise((resolve, reject) => {
const env: NodeJS.ProcessEnv = {
...process.env,
HOME: home,
USERPROFILE: home,
APPDATA: path.join(home, 'AppData', 'Roaming'),
HERMES_HOME: path.join(home, '.hermes'),
NO_COLOR: '1',
...(stripPath ? {
PATH: '',
Path: '',
WAGGLE_HOOK_NODE_PATH: hookNodePath,
} : {}),
};
delete env.NODE_PATH;
delete env.NODE_OPTIONS;
delete env.WAGGLE_MEMORY_MCP_ENTRY;
delete env.WAGGLE_CLAUDE_DESKTOP_CONFIG_DIR;
const child = spawn(command, args, {
cwd,
env: {
...process.env,
HOME: home,
USERPROFILE: home,
NO_COLOR: '1',
...(stripPath ? {
PATH: '', Path: '', WAGGLE_HOOK_NODE_PATH: process.execPath,
} : {}),
},
env,
shell: process.platform === 'win32' && command.endsWith('.cmd'),
stdio: ['ignore', 'pipe', 'pipe'],
});
@@ -51,48 +86,85 @@ async function runInCwd(
interface HookPackageCase {
id: string;
packageName: string;
configDir: string;
configFile: string;
layout: (home: string) => HookLayout;
precreateConfig?: string;
}
interface HookLayout {
configDir: string;
configPath: string;
pointerPath: string;
}
function standardLayout(configDirName: string, configFile: string) {
return (home: string): HookLayout => {
const configDir = path.join(home, configDirName);
return {
configDir,
configPath: path.join(configDir, configFile),
pointerPath: path.join(configDir, 'hive-mind-install.json'),
};
};
}
function claudeDesktopLayout(home: string): HookLayout {
let configDir: string;
if (process.platform === 'win32') {
configDir = path.join(home, 'AppData', 'Roaming', 'Claude');
} else if (process.platform === 'darwin') {
configDir = path.join(home, 'Library', 'Application Support', 'Claude');
} else {
configDir = path.join(home, '.config', 'Claude');
}
return {
configDir,
configPath: path.join(configDir, 'claude_desktop_config.json'),
pointerPath: path.join(home, '.waggle', 'claude-desktop', 'hive-mind-install.json'),
};
}
const HOOK_PACKAGE_CASES: HookPackageCase[] = [
{
id: 'claude-code',
packageName: '@waggle/hive-mind-hooks-claude-code',
configDir: '.claude',
configFile: 'settings.json',
layout: standardLayout('.claude', 'settings.json'),
precreateConfig: '{}\n',
},
{
id: 'claude-desktop',
packageName: '@waggle/hive-mind-hooks-claude-desktop',
layout: claudeDesktopLayout,
precreateConfig: '{}\n',
},
{
id: 'codex',
packageName: '@waggle/hive-mind-hooks-codex',
configDir: '.codex',
configFile: 'hooks.json',
layout: standardLayout('.codex', 'hooks.json'),
precreateConfig: '{ "custom": "preserve-codex", "hooks": {} }\n',
},
{
id: 'codex-desktop',
packageName: '@waggle/hive-mind-hooks-codex-desktop',
configDir: '.codex',
configFile: 'hooks.json',
layout: standardLayout('.codex', 'hooks.json'),
precreateConfig: '{ "custom": "preserve-codex-desktop", "hooks": {} }\n',
},
{
id: 'cursor',
packageName: '@waggle/hive-mind-hooks-cursor',
configDir: '.cursor',
configFile: 'hooks.json',
layout: standardLayout('.cursor', 'hooks.json'),
precreateConfig: '{ "version": 1, "custom": "preserve-cursor", "hooks": {} }\n',
},
{
id: 'hermes',
packageName: '@waggle/hive-mind-hooks-hermes',
configDir: '.hermes',
configFile: 'config.yaml',
layout: standardLayout('.hermes', 'config.yaml'),
precreateConfig: '# preserve Hermes comment\nmodel: existing\nhooks: {}\n',
},
{
id: 'openclaw',
packageName: '@waggle/hive-mind-hooks-openclaw',
configDir: '.openclaw',
configFile: 'openclaw.json',
layout: standardLayout('.openclaw', 'openclaw.json'),
precreateConfig: '{\n // preserve OpenClaw comment\n model: "existing",\n}\n',
},
];
@@ -130,6 +202,45 @@ function expectCommandOk(
).toBe(0);
}
function expectNoSourceRuntimePaths(contents: string, label: string): void {
for (const sourcePath of [ROOT, SOURCE_RESOURCES, SOURCE_BUNDLED_NODE, process.execPath]) {
expect(contents, `${label} leaked source runtime path ${sourcePath}`).not.toContain(sourcePath);
if (sourcePath.includes('\\')) {
expect(contents, `${label} leaked JSON-escaped source runtime path ${sourcePath}`).not.toContain(
sourcePath.replace(/\\/g, '\\\\'),
);
expect(contents, `${label} leaked slash-normalized source runtime path ${sourcePath}`).not.toContain(
sourcePath.replace(/\\/g, '/'),
);
}
}
}
describe('hook runtime clean-build contract', () => {
it('orders workspace declaration prerequisites before packaged runtimes', () => {
const buildScript = fs.readFileSync(
path.join(ROOT, 'scripts', 'build-hook-runtime.mjs'),
'utf8',
);
const projectOrder = Array.from(
buildScript.matchAll(/['"](packages\/[^'"]+\/tsconfig\.json)['"]/g),
(match) => match[1],
);
const requiredOrder = [
'packages/shared/tsconfig.json',
'packages/hive-mind-core/tsconfig.json',
'packages/core/tsconfig.json',
'packages/wiki-compiler/tsconfig.json',
'packages/memory-mcp/tsconfig.json',
];
expect(projectOrder.filter((project) => requiredOrder.includes(project))).toEqual(
requiredOrder,
);
});
});
describe('hook package installed lifecycle UX', () => {
it('runs the packaged CLI and hook lifecycles through Node with npm and npx absent', async () => {
const tempRoot = makeTempRoot();
@@ -147,10 +258,8 @@ describe('hook package installed lifecycle UX', () => {
for (const hookPackage of HOOK_PACKAGE_CASES) {
const home = path.join(tempRoot, `home-${hookPackage.id}`);
const toolDir = path.join(home, hookPackage.configDir);
const configPath = path.join(toolDir, hookPackage.configFile);
const pointerPath = path.join(toolDir, 'hive-mind-install.json');
fs.mkdirSync(toolDir, { recursive: true });
const { configDir, configPath, pointerPath } = hookPackage.layout(home);
fs.mkdirSync(configDir, { recursive: true });
if (hookPackage.precreateConfig !== undefined) {
fs.writeFileSync(configPath, hookPackage.precreateConfig, 'utf8');
}
@@ -160,15 +269,18 @@ describe('hook package installed lifecycle UX', () => {
bin: Record<string, string>;
};
const hookEntry = path.join(packageDir, Object.values(manifest.bin)[0]);
const runHook = (action: 'install' | 'verify' | 'uninstall') => runInCwd(
process.execPath,
action === 'install'
? [hookEntry, action, '--cli-path', fakeCliPath]
: [hookEntry, action],
projectDir,
home,
true,
);
const runHook = (action: 'install' | 'verify' | 'uninstall') => {
const args = [hookEntry, action];
if (action === 'install' || (action === 'verify' && hookPackage.id === 'openclaw')) {
args.push('--cli-path', fakeCliPath);
}
if (action === 'install') {
if (hookPackage.id === 'claude-desktop') {
args.push('--mcp-entry', fakeCliPath);
}
}
return runInCwd(process.execPath, args, projectDir, home, true);
};
const installResult = await runHook('install');
expectCommandOk(installResult, `${hookPackage.id} install`);
@@ -177,9 +289,15 @@ describe('hook package installed lifecycle UX', () => {
expect(fs.existsSync(pointerPath)).toBe(true);
if (hookPackage.id !== 'openclaw') {
const installedConfig = fs.readFileSync(configPath, 'utf8');
const nodePathHaystack = process.platform === 'win32'
&& (hookPackage.id === 'codex' || hookPackage.id === 'codex-desktop')
? [...installedConfig.matchAll(/-EncodedCommand ([A-Za-z0-9+/=]+)/g)]
.map(match => Buffer.from(match[1], 'base64').toString('utf16le'))
.join('\n')
: installedConfig;
expect(
installedConfig.includes(process.execPath)
|| installedConfig.includes(process.execPath.replace(/\\/g, '\\\\')),
nodePathHaystack.includes(process.execPath)
|| nodePathHaystack.includes(process.execPath.replace(/\\/g, '\\\\')),
`${hookPackage.id} did not pin the bundled Node path`,
).toBe(true);
}
@@ -203,4 +321,202 @@ describe('hook package installed lifecycle UX', () => {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
}, 300_000);
it.runIf(ANY_STAGED_RUNTIME || REQUIRE_STAGED_RUNTIME)(
'runs staged Tauri hook lifecycles from a physical runtime copy',
async () => {
expect(
COMPLETE_STAGED_RUNTIME,
'staged hook verification requires service.js, bundled Node, node_modules, and memory MCP',
).toBe(true);
const tempRoot = makeTempRoot();
try {
const isolatedResources = path.join(tempRoot, 'isolated-resources');
const isolatedNodeModules = path.join(isolatedResources, 'node_modules');
const bundledNode = path.join(
isolatedResources,
process.platform === 'win32' ? 'node.exe' : 'node',
);
fs.mkdirSync(isolatedResources, { recursive: true });
fs.copyFileSync(SOURCE_BUNDLED_NODE, bundledNode);
fs.chmodSync(bundledNode, fs.statSync(SOURCE_BUNDLED_NODE).mode);
await fs.promises.cp(SOURCE_STAGED_NODE_MODULES, isolatedNodeModules, {
recursive: true,
dereference: true,
});
const relativeToRepo = path.relative(ROOT, isolatedResources);
const copiedInsideRepo = relativeToRepo === ''
|| (!relativeToRepo.startsWith(`..${path.sep}`) && !path.isAbsolute(relativeToRepo));
expect(copiedInsideRepo).toBe(false);
const projectDir = path.join(tempRoot, 'project');
fs.mkdirSync(projectDir, { recursive: true });
const stagedCli = path.join(
isolatedNodeModules,
'@waggle',
'hive-mind-cli',
'dist',
'index.js',
);
const stagedMemoryMcp = path.join(
isolatedNodeModules,
'waggle-memory-mcp',
'dist',
'index.js',
);
expect(fs.existsSync(stagedMemoryMcp)).toBe(true);
const cliHelp = await runInCwd(
bundledNode,
[stagedCli, '--help'],
projectDir,
tempRoot,
true,
bundledNode,
);
expectCommandOk(cliHelp, 'isolated staged hive-mind-cli');
for (const hookPackage of HOOK_PACKAGE_CASES) {
const home = path.join(tempRoot, `staged-home-${hookPackage.id}`);
const { configDir, configPath, pointerPath } = hookPackage.layout(home);
fs.mkdirSync(configDir, { recursive: true });
if (hookPackage.precreateConfig !== undefined) {
fs.writeFileSync(configPath, hookPackage.precreateConfig, 'utf8');
}
const packageDir = path.join(
isolatedNodeModules,
...hookPackage.packageName.split('/'),
);
const manifest = JSON.parse(
fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8'),
) as { bin: Record<string, string> };
const hookEntry = path.join(packageDir, Object.values(manifest.bin)[0]);
const runHook = (action: 'install' | 'verify' | 'uninstall') => {
const shouldPinCli = action === 'install'
|| (action === 'verify' && hookPackage.id === 'openclaw');
const args = shouldPinCli
? [hookEntry, action, '--cli-path', stagedCli]
: [hookEntry, action];
return runInCwd(
bundledNode,
args,
projectDir,
home,
true,
bundledNode,
);
};
const installResult = await runHook('install');
expectCommandOk(installResult, `isolated staged ${hookPackage.id} install`);
expect(fs.existsSync(configPath)).toBe(true);
expect(fs.existsSync(pointerPath)).toBe(true);
const installedConfig = fs.readFileSync(configPath, 'utf8');
const installedPointer = fs.readFileSync(pointerPath, 'utf8');
expectNoSourceRuntimePaths(installedConfig, `${hookPackage.id} config`);
expectNoSourceRuntimePaths(installedPointer, `${hookPackage.id} pointer`);
if (hookPackage.id === 'openclaw') {
const installedHandler = path.join(
home,
'.openclaw',
'hooks',
'hive-mind',
'handler.js',
);
const installedBundle = path.join(
home,
'.openclaw',
'hooks',
'hive-mind',
'handler.cjs',
);
expect(fs.existsSync(installedHandler)).toBe(true);
expect(fs.readFileSync(installedHandler, 'utf8')).toBe(
[
"'use strict';",
"const handler = require('./handler.cjs');",
`module.exports = (event) => handler(event, ${JSON.stringify({
cliPath: stagedCli,
nodePath: bundledNode,
})});`,
'',
].join('\n'),
);
expect(fs.readFileSync(installedBundle)).toEqual(
fs.readFileSync(path.join(packageDir, 'dist', 'handler.bundle.cjs')),
);
expect(installedConfig).toContain(stagedCli.replace(/\\/g, '\\\\'));
expect(installedConfig).toContain(bundledNode.replace(/\\/g, '\\\\'));
expect(JSON.parse(installedPointer)).toMatchObject({
cli_path: stagedCli,
extra: {
runtime_binding: {
version: 1,
cli_path: stagedCli,
node_path: bundledNode,
},
},
});
expectNoSourceRuntimePaths(
fs.readFileSync(installedHandler, 'utf8'),
'openclaw installed handler loader',
);
expectNoSourceRuntimePaths(
fs.readFileSync(installedBundle, 'utf8'),
'openclaw installed handler bundle',
);
} else if (hookPackage.id === 'claude-desktop') {
const config = JSON.parse(installedConfig) as {
mcpServers: Record<string, { command: string; args: string[] }>;
};
expect(config.mcpServers['waggle-memory']).toEqual({
command: bundledNode,
args: [stagedMemoryMcp],
});
} else {
const nodePathHaystacks = process.platform === 'win32'
&& (hookPackage.id === 'codex' || hookPackage.id === 'codex-desktop')
? [...installedConfig.matchAll(/-EncodedCommand ([A-Za-z0-9+/=]+)/g)]
.map(match => Buffer.from(match[1], 'base64').toString('utf16le'))
: [installedConfig];
if (process.platform === 'win32'
&& (hookPackage.id === 'codex' || hookPackage.id === 'codex-desktop')) {
expect(nodePathHaystacks).toHaveLength(4);
}
for (const nodePathHaystack of nodePathHaystacks) {
expect(
nodePathHaystack.includes(bundledNode)
|| nodePathHaystack.includes(bundledNode.replace(/\\/g, '\\\\')),
`${hookPackage.id} did not pin every command to the copied bundled Node path`,
).toBe(true);
expectNoSourceRuntimePaths(
nodePathHaystack,
`${hookPackage.id} decoded runtime command`,
);
}
}
const verifyResult = await runHook('verify');
expectCommandOk(verifyResult, `isolated staged ${hookPackage.id} verify`);
expect(verifyResult.stdout).toContain('All checks passed.');
const uninstallResult = await runHook('uninstall');
expectCommandOk(uninstallResult, `isolated staged ${hookPackage.id} uninstall`);
expect(fs.existsSync(pointerPath)).toBe(false);
if (hookPackage.precreateConfig !== undefined) {
expect(fs.readFileSync(configPath, 'utf8')).toBe(hookPackage.precreateConfig);
} else {
expect(fs.existsSync(configPath)).toBe(false);
}
}
} finally {
await fs.promises.rm(tempRoot, { recursive: true, force: true });
}
},
600_000,
);
});