From c74dd1a374e5492583e1523ada641fc3be68d4cd Mon Sep 17 00:00:00 2001 From: xjx <2869418079@qq.com> Date: Sat, 24 Oct 2026 15:49:19 +0800 Subject: [PATCH 1/2] test: trim low-value checks and stabilize Windows MCP fixture --- .github/scripts/quality-check-scope.mjs | 1 - .release-notes/codex-trim-redundant-tests.md | 7 ++++++ .../scripts/check-source-entrypoints.test.mjs | 17 -------------- .../scripts/workflow-mcp-package.test.mjs | 20 ---------------- .../src/automation/engine/mcp/server.test.ts | 4 ++++ scripts/generate-star-history.test.mjs | 6 ----- scripts/monorepo-layout.test.mjs | 17 +------------- scripts/release-notes.test.mjs | 16 ------------- scripts/session-subagent-layout.test.mjs | 23 ------------------- scripts/session-toolbar-layout.test.mjs | 19 --------------- 10 files changed, 12 insertions(+), 118 deletions(-) create mode 100644 .release-notes/codex-trim-redundant-tests.md delete mode 100644 apps/main-2.0/scripts/check-source-entrypoints.test.mjs delete mode 100644 apps/main-2.0/scripts/workflow-mcp-package.test.mjs delete mode 100644 scripts/session-subagent-layout.test.mjs delete mode 100644 scripts/session-toolbar-layout.test.mjs diff --git a/.github/scripts/quality-check-scope.mjs b/.github/scripts/quality-check-scope.mjs index 35f27021b..8a760b26a 100644 --- a/.github/scripts/quality-check-scope.mjs +++ b/.github/scripts/quality-check-scope.mjs @@ -37,7 +37,6 @@ const REPOSITORY_ONLY_FILES = new Set([ "scripts/monorepo-layout.test.mjs", "scripts/release-notes.mjs", "scripts/release-notes.test.mjs", - "scripts/session-toolbar-layout.test.mjs", ]); function applicationForPath(file) { diff --git a/.release-notes/codex-trim-redundant-tests.md b/.release-notes/codex-trim-redundant-tests.md new file mode 100644 index 000000000..0fe5a88e0 --- /dev/null +++ b/.release-notes/codex-trim-redundant-tests.md @@ -0,0 +1,7 @@ +# 改善 Windows 下的 Workflow MCP 验证稳定性 + + + +## Bug 修复 + +- 修复 Windows 环境中验证 Workflow MCP 退出行为时,连接正常关闭仍被误报为失败的问题。 diff --git a/apps/main-2.0/scripts/check-source-entrypoints.test.mjs b/apps/main-2.0/scripts/check-source-entrypoints.test.mjs deleted file mode 100644 index c1b5f8096..000000000 --- a/apps/main-2.0/scripts/check-source-entrypoints.test.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import assert from "node:assert/strict"; -import { spawnSync } from "node:child_process"; -import path from "node:path"; -import test from "node:test"; -import { fileURLToPath } from "node:url"; - -const appRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); - -test("production source modules are reachable from declared entry points", () => { - const result = spawnSync(process.execPath, ["scripts/check-source-entrypoints.mjs"], { - cwd: appRoot, - encoding: "utf8", - }); - - assert.equal(result.status, 0, result.stderr || result.stdout); - assert.match(result.stdout, /Source entrypoint check passed/); -}); diff --git a/apps/main-2.0/scripts/workflow-mcp-package.test.mjs b/apps/main-2.0/scripts/workflow-mcp-package.test.mjs deleted file mode 100644 index 9893d0468..000000000 --- a/apps/main-2.0/scripts/workflow-mcp-package.test.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import assert from "node:assert/strict"; -import { readFile } from "node:fs/promises"; -import test from "node:test"; - -test("the package exposes an agent-recall-v2 Workflow MCP executable", async () => { - const [manifest, launcher, buildScript, entrySource, serverSource] = await Promise.all([ - readFile(new URL("../package.json", import.meta.url), "utf8").then(JSON.parse), - readFile(new URL("../bin/agent-recall-workflow-mcp.mjs", import.meta.url), "utf8"), - readFile(new URL("./build-mcp-bundle.mjs", import.meta.url), "utf8"), - readFile(new URL("../src/mcp/workflow-entry.ts", import.meta.url), "utf8"), - readFile(new URL("../src/automation/engine/mcp/server.ts", import.meta.url), "utf8"), - ]); - - assert.equal(manifest.bin["agent-recall-v2-workflow-mcp"], "bin/agent-recall-workflow-mcp.mjs"); - assert.match(launcher, /path\.join\(binDir,\s*"\.\.",\s*"out",\s*"mcp",\s*"workflow-entry\.js"\)/); - assert.match(buildScript, /path\.join\(root,\s*"src",\s*"mcp",\s*"workflow-entry\.ts"\)/); - assert.match(entrySource, /startStdioMcpServer\(\)/); - assert.doesNotMatch(serverSource, /import\.meta\.url\s*===/); - assert.doesNotMatch(launcher, /multi-agent-chat|\/Users\//); -}); diff --git a/apps/main-2.0/src/automation/engine/mcp/server.test.ts b/apps/main-2.0/src/automation/engine/mcp/server.test.ts index f9378c617..eadaeab6a 100644 --- a/apps/main-2.0/src/automation/engine/mcp/server.test.ts +++ b/apps/main-2.0/src/automation/engine/mcp/server.test.ts @@ -557,6 +557,10 @@ describe("MCP server tools", () => { // Accept the TCP connection but never respond, so the tool call hangs // the same way a gateway request in flight while its host dies does. blackholeSockets.add(socket); + socket.on("error", (error: NodeJS.ErrnoException) => { + // Windows may reset this fixture connection when the child exits mid-request. + if (error.code !== "ECONNRESET") throw error; + }); socket.once("close", () => blackholeSockets.delete(socket)); }); await new Promise((resolve, reject) => { diff --git a/scripts/generate-star-history.test.mjs b/scripts/generate-star-history.test.mjs index 3683acc45..fc7d99bbb 100644 --- a/scripts/generate-star-history.test.mjs +++ b/scripts/generate-star-history.test.mjs @@ -18,12 +18,6 @@ after(async () => { await Promise.all([...temporaryDirectories].map((directory) => rm(directory, { recursive: true, force: true }))) }) -test('committed Star History data matches the renamed repository', async () => { - const data = JSON.parse(await readFile('assets/star-history-data.json', 'utf8')) - - assert.doesNotThrow(() => parseStarHistoryData(data, 'zszz3/AgentRecall')) -}) - test('fetchStarCount reads only repository metadata', async () => { const requests = [] const fetchImpl = async (url, options) => { diff --git a/scripts/monorepo-layout.test.mjs b/scripts/monorepo-layout.test.mjs index 0667c8fbd..a01b01dcb 100644 --- a/scripts/monorepo-layout.test.mjs +++ b/scripts/monorepo-layout.test.mjs @@ -1,9 +1,7 @@ import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; -import { createRequire } from "node:module"; import test from "node:test"; -const require = createRequire(import.meta.url); const root = JSON.parse(await readFile("package.json", "utf8")); const v1 = JSON.parse(await readFile("apps/main-1.0/package.json", "utf8")); const v2 = JSON.parse(await readFile("apps/main-2.0/package.json", "utf8")); @@ -17,6 +15,7 @@ test("keeps V1 and V2 as independent app packages", () => { assert.equal(v1.productName, "AgentRecall"); assert.equal(v2.productName, "AgentRecall"); assert.ok(Object.keys(v2.bin).every(command => !(command in v1.bin))); + assert.equal(v2.bin["agent-recall-v2-workflow-mcp"], "bin/agent-recall-workflow-mcp.mjs"); assert.equal(v2.scripts.postinstall, "node bin/embedded-postgres-runtime.cjs"); }); @@ -31,20 +30,6 @@ test("exposes explicit root commands for both apps", () => { assert.match(root.scripts["package:smoke:all"], /run-package-smokes\.mjs/); }); -test("keeps PostCSS discovery inside each app", () => { - for (const appDirectory of ["main-1.0", "main-2.0"]) { - const config = require(`../apps/${appDirectory}/postcss.config.cjs`); - assert.deepEqual(config, { plugins: {} }); - } -}); - -test("runs independent V1 and V2 package smokes concurrently", async () => { - const smokeRunner = await readFile("scripts/run-package-smokes.mjs", "utf8"); - assert.match(smokeRunner, /directory:\s*"apps\/main-1\.0"/); - assert.match(smokeRunner, /directory:\s*"apps\/main-2\.0"/); - assert.match(smokeRunner, /Promise\.allSettled\(applications\.map\(runPackageSmoke\)\)/); -}); - test("installs app dependencies without changing the user's Claude statusline", async () => { const setupScript = await readFile("scripts/setup-app.mjs", "utf8"); assert.match(setupScript, /AGENT_RECALL_SKIP_STATUSLINE_INSTALL:\s*"1"/); diff --git a/scripts/release-notes.test.mjs b/scripts/release-notes.test.mjs index d0fbbd370..241e93be0 100644 --- a/scripts/release-notes.test.mjs +++ b/scripts/release-notes.test.mjs @@ -82,22 +82,6 @@ test("renders each product's release notes independently", () => { assert.doesNotMatch(renderedV2, /Stable feature\./); }); -test("repository guidance treats release notes as sanitized product copy", async () => { - const instructions = await readFile("AGENTS.md", "utf8"); - const templateGuidance = await readFile(".release-notes/README.md", "utf8"); - const pullRequestTemplate = await readFile(".github/pull_request_template.md", "utf8"); - const claudeGuidance = await readFile("CLAUDE.md", "utf8"); - assert.match(instructions, /\[release-note rules\]\(\.release-notes\/README\.md\)/); - assert.match(templateGuidance, /Write this as product copy for users, not as an engineering log/); - assert.match(templateGuidance, /Remove internal-only changes/); - assert.match(templateGuidance, /omit private identifiers, hosts, paths, table names, credentials/); - assert.match(templateGuidance, /Release routing is explicit for every new note/); - assert.match(templateGuidance, /release-target: v1.*release-target: v2.*release-target: both/); - assert.match(templateGuidance, /does not force a dual release/); - assert.match(pullRequestTemplate, /release-target: v1.*v2.*both/); - assert.match(claudeGuidance, /an explicit `` marker/); -}); - test("bumps minor for features and patch for fix-only releases", () => { const feature = { title: "Feature", features: ["New behavior"], fixes: [] }; const fix = { title: "Fix", features: [], fixes: ["Fixed behavior"] }; diff --git a/scripts/session-subagent-layout.test.mjs b/scripts/session-subagent-layout.test.mjs deleted file mode 100644 index 3bfbd7b37..000000000 --- a/scripts/session-subagent-layout.test.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import assert from "node:assert/strict"; -import { readFileSync } from "node:fs"; -import test from "node:test"; - -const styleFiles = [ - ["V1", new URL("../apps/main-1.0/src/renderer/src/styles.css", import.meta.url)], - ["V2", new URL("../apps/main-2.0/src/renderer/src/styles.css", import.meta.url)], -]; - -for (const [appName, styleFile] of styleFiles) { - test(`${appName} aligns related sessions with the conversation content`, () => { - const styles = readFileSync(styleFile, "utf8"); - const rules = [...styles.matchAll(/\.subagent-session-tree\s*\{([^}]*)\}/g)]; - const rule = rules.at(-1)?.[1]; - - assert.ok(rule, ".subagent-session-tree styles should exist"); - assert.match( - rule, - /padding:\s*14px 18px 0;/, - "the related-session area should keep the same horizontal gutter as the conversation", - ); - }); -} diff --git a/scripts/session-toolbar-layout.test.mjs b/scripts/session-toolbar-layout.test.mjs deleted file mode 100644 index b242d7ad0..000000000 --- a/scripts/session-toolbar-layout.test.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import assert from "node:assert/strict"; -import { readFileSync } from "node:fs"; -import test from "node:test"; - -const styleFiles = [ - ["V1", new URL("../apps/main-1.0/src/renderer/src/styles.css", import.meta.url)], - ["V2", new URL("../apps/main-2.0/src/renderer/src/styles/sessions.css", import.meta.url)], -]; - -for (const [appName, styleFile] of styleFiles) { - test(`${appName} aligns the session result toolbar with the filters`, () => { - const styles = readFileSync(styleFile, "utf8"); - const rule = styles.match(/\.result-count\s*\{([^}]*)\}/)?.[1]; - - assert.ok(rule, ".result-count styles should exist"); - assert.match(rule, /margin:\s*8px 0;/, "the result toolbar should keep equal spacing from the filters and session list"); - assert.match(rule, /padding:\s*0;/, "the result toolbar should share the filters' left edge"); - }); -} From f1d3b1d3f413d99249d9718482657df400e2da68 Mon Sep 17 00:00:00 2001 From: xjx <2869418079@qq.com> Date: Sat, 24 Oct 2026 16:00:33 +0800 Subject: [PATCH 2/2] test: retire source-presence checks and consolidate related cases --- .../core/claude-statusline-install.test.ts | 43 +++++++------------ .../src/core/summary-endpoint.test.ts | 23 +++++----- .../main/live-session-worker-wiring.test.ts | 26 ----------- .../src/main/main-startup-wiring.test.ts | 20 --------- .../src/main/session-query-wiring.test.ts | 36 ---------------- .../agent-hub-persisted-migrations.test.ts | 17 -------- .../persisted/agent-hub-persistence.test.ts | 16 ++++++- .../src/core/summary-endpoint.test.ts | 23 +++++----- .../main/live-session-worker-wiring.test.ts | 32 -------------- .../src/main/main-startup-wiring.test.ts | 24 ----------- .../src/main/openviking-main-wiring.test.ts | 35 +-------------- scripts/generate-star-history.test.mjs | 17 +++----- 12 files changed, 63 insertions(+), 249 deletions(-) delete mode 100644 apps/main-1.0/src/main/live-session-worker-wiring.test.ts delete mode 100644 apps/main-2.0/src/automation/engine/main/hub/persisted/agent-hub-persisted-migrations.test.ts delete mode 100644 apps/main-2.0/src/main/live-session-worker-wiring.test.ts delete mode 100644 apps/main-2.0/src/main/main-startup-wiring.test.ts diff --git a/apps/main-1.0/src/core/claude-statusline-install.test.ts b/apps/main-1.0/src/core/claude-statusline-install.test.ts index 3943aa254..e7bf947a5 100644 --- a/apps/main-1.0/src/core/claude-statusline-install.test.ts +++ b/apps/main-1.0/src/core/claude-statusline-install.test.ts @@ -63,33 +63,22 @@ describe("installClaudeStatuslineBridge", () => { } }); - it("is idempotent when our bridge is already configured", () => { - const homeDir = makeHome(); - try { - const settingsPath = writeSettings(homeDir, { - statusLine: { type: "command", command: `node "${SCRIPT}"` }, - }); - const before = readFileSync(settingsPath, "utf8"); - - const result = installClaudeStatuslineBridge({ homeDir, scriptPath: SCRIPT }); - - expect(result.status).toBe("already"); - expect(readFileSync(settingsPath, "utf8")).toBe(before); - } finally { - rmSync(homeDir, { recursive: true, force: true }); - } - }); - - it("recognizes the global bin name as already installed", () => { - const homeDir = makeHome(); - try { - writeSettings(homeDir, { - statusLine: { type: "command", command: "agent-recall-claude-statusline" }, - }); - const result = installClaudeStatuslineBridge({ homeDir, scriptPath: SCRIPT }); - expect(result.status).toBe("already"); - } finally { - rmSync(homeDir, { recursive: true, force: true }); + it("preserves existing bridge commands, including the global executable", () => { + for (const command of [`node "${SCRIPT}"`, "agent-recall-claude-statusline"]) { + const homeDir = makeHome(); + try { + const settingsPath = writeSettings(homeDir, { + statusLine: { type: "command", command }, + }); + const before = readFileSync(settingsPath, "utf8"); + + const result = installClaudeStatuslineBridge({ homeDir, scriptPath: SCRIPT }); + + expect(result.status).toBe("already"); + expect(readFileSync(settingsPath, "utf8")).toBe(before); + } finally { + rmSync(homeDir, { recursive: true, force: true }); + } } }); diff --git a/apps/main-1.0/src/core/summary-endpoint.test.ts b/apps/main-1.0/src/core/summary-endpoint.test.ts index 3a6de10a5..3d3227368 100644 --- a/apps/main-1.0/src/core/summary-endpoint.test.ts +++ b/apps/main-1.0/src/core/summary-endpoint.test.ts @@ -8,6 +8,9 @@ import { resolveSummaryEndpointFromSettings, } from "./summary-endpoint"; +const codexConfigDir = path.resolve("fixtures", "summary-codex"); +const claudeConfigDir = path.resolve("fixtures", "summary-claude"); + function settingsWith(overrides: Partial): AppSettings { return { ...structuredClone(defaultSettings), ...overrides }; } @@ -27,12 +30,12 @@ describe("summary exec endpoints", () => { it("points each CLI at its own directory without touching the other", () => { const settings = settingsWith({ - summaryCodexConfigDir: "/tmp/summary-codex", - summaryClaudeConfigDir: "/tmp/summary-claude", + summaryCodexConfigDir: codexConfigDir, + summaryClaudeConfigDir: claudeConfigDir, }); - expect(buildCodexExecEndpoint(settings).env).toEqual({ CODEX_HOME: "/tmp/summary-codex" }); - expect(buildClaudeExecEndpoint(settings).env).toEqual({ CLAUDE_CONFIG_DIR: "/tmp/summary-claude" }); + expect(buildCodexExecEndpoint(settings).env).toEqual({ CODEX_HOME: codexConfigDir }); + expect(buildClaudeExecEndpoint(settings).env).toEqual({ CLAUDE_CONFIG_DIR: claudeConfigDir }); }); it("expands a leading ~ so a hand-typed path reaches the right directory", () => { @@ -73,25 +76,25 @@ describe("summary exec endpoints", () => { describe("resolveSummaryEndpointFromSettings", () => { it("routes each source to its own CLI and directory", () => { const settings = settingsWith({ - summaryCodexConfigDir: "/tmp/summary-codex", - summaryClaudeConfigDir: "/tmp/summary-claude", + summaryCodexConfigDir: codexConfigDir, + summaryClaudeConfigDir: claudeConfigDir, }); const codex = resolveSummaryEndpointFromSettings({ ...settings, summarySource: "codex" }); expect(codex?.apiFormat).toBe("codex_exec"); - expect(codex?.env).toEqual({ CODEX_HOME: "/tmp/summary-codex" }); + expect(codex?.env).toEqual({ CODEX_HOME: codexConfigDir }); const claude = resolveSummaryEndpointFromSettings({ ...settings, summarySource: "claude" }); expect(claude?.apiFormat).toBe("claude_exec"); - expect(claude?.env).toEqual({ CLAUDE_CONFIG_DIR: "/tmp/summary-claude" }); + expect(claude?.env).toEqual({ CLAUDE_CONFIG_DIR: claudeConfigDir }); }); it("keeps a custom route on HTTP and out of both config directories", () => { const endpoint = resolveSummaryEndpointFromSettings(settingsWith({ summarySource: "custom", summaryApiConfigMode: "custom", - summaryCodexConfigDir: "/tmp/summary-codex", - summaryClaudeConfigDir: "/tmp/summary-claude", + summaryCodexConfigDir: codexConfigDir, + summaryClaudeConfigDir: claudeConfigDir, summaryReasoningEffort: "low", summaryApiConfig: { ...defaultSettings.summaryApiConfig, diff --git a/apps/main-1.0/src/main/live-session-worker-wiring.test.ts b/apps/main-1.0/src/main/live-session-worker-wiring.test.ts deleted file mode 100644 index 7cd5c9191..000000000 --- a/apps/main-1.0/src/main/live-session-worker-wiring.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { readFileSync } from "node:fs"; -import { describe, expect, it } from "vitest"; - -const source = readFileSync(new URL("./index.ts", import.meta.url), "utf8"); -const buildConfig = readFileSync(new URL("../../electron.vite.config.ts", import.meta.url), "utf8"); - -describe("main process live session worker wiring", () => { - it("loads local live sessions through the persistent worker without changing cache or remote merging", () => { - expect(buildConfig).toContain( - '"live-session-worker": resolve("src/main/live-session-worker.ts")', - ); - expect(source).toContain( - 'new LocalLiveSessionService(\n path.join(__dirname, "live-session-worker.js"),', - ); - expect(source).toContain("load: (options) => localLiveSessions().load(options)"); - expect(source).toContain("loadCachedLocalLiveSessionSnapshot(options)"); - expect(source).toContain("loadRemoteLiveSessions("); - }); - - it("stops the live session worker during application shutdown", () => { - expect(source).toContain("localLiveSessionService?.stop()"); - expect(source.indexOf("localSessionWorkersStopping = true")).toBeLessThan( - source.indexOf("localLiveSessionService?.stop()"), - ); - }); -}); diff --git a/apps/main-1.0/src/main/main-startup-wiring.test.ts b/apps/main-1.0/src/main/main-startup-wiring.test.ts index 84486d346..731578f38 100644 --- a/apps/main-1.0/src/main/main-startup-wiring.test.ts +++ b/apps/main-1.0/src/main/main-startup-wiring.test.ts @@ -46,26 +46,6 @@ describe("main process startup wiring", () => { expect(toggleBlock).toContain("showWindow({ focusSearch: true })"); }); - it("migrates SSH sessions through remote writeback and SSH Resume", () => { - const source = readFileSync(new URL("./index.ts", import.meta.url), "utf8"); - - expect(source).toContain('session.environmentKind === "wsl" || session.environmentKind === "ssh"'); - expect(source).toContain('target !== sshMigrationTarget(session.source)'); - expect(source).toContain('{ allowSsh: session.environmentKind === "ssh" }'); - expect(source).toContain("createSourceRemoteRestoreDependencies(environment, progress)"); - expect(source).toContain('environment.kind === "ssh" ? inspectSshMigrationCli(environment, target)'); - expect(source).toContain("getRemoteMigrationCliVersionCommand(command, args)"); - expect(source).toContain('const sshArgs = buildRemoteInteractiveSshArgs(environment, "").slice(0, -1)'); - expect(source).toContain("await openResumeInTerminal(session, getSettings(), { sshArgs })"); - }); - - it("uses Electron networking for direct summary providers", () => { - const source = readFileSync(new URL("./index.ts", import.meta.url), "utf8"); - - expect(source).toContain("fetch: electronSummaryFetch"); - expect(source).toContain("net.fetch(input, init)"); - }); - it("checks summary source support before reading messages or invoking the provider", () => { const source = readFileSync(new URL("./index.ts", import.meta.url), "utf8"); const summarizeOneSession = source.match( diff --git a/apps/main-1.0/src/main/session-query-wiring.test.ts b/apps/main-1.0/src/main/session-query-wiring.test.ts index 791ab0e0e..db17445bf 100644 --- a/apps/main-1.0/src/main/session-query-wiring.test.ts +++ b/apps/main-1.0/src/main/session-query-wiring.test.ts @@ -4,42 +4,6 @@ import { describe, expect, it } from "vitest"; const source = readFileSync(new URL("./index.ts", import.meta.url), "utf8"); describe("main process session query wiring", () => { - it("keeps heavy catalog and usage reads off the Electron main thread", () => { - expect(source).toContain( - 'new LocalSessionQueryService(path.join(__dirname, "session-query-worker.js")', - ); - expect(source).toContain( - 'sessionCatalogQueries().searchSessionPage(visibleSearchOptions(options))', - ); - expect(source).toContain( - 'sessionStatsQueries().getStats(visibleStatsOptions(options))', - ); - expect(source).toContain( - 'sessionStatsQueries().getStatsTrend(visibleStatsOptions(options))', - ); - expect(source).toContain( - 'sessionCatalogQueries().listProjects({ ...options, ...visibleProjectOptions() })', - ); - - expect(source).not.toContain( - 'ipcMain.handle("search:session-page", (_event, options: SearchOptions) => store.searchSessionPage', - ); - expect(source).not.toContain( - 'ipcMain.handle("stats:get", (_event, options?: SessionStatsOptions) => store.getStats', - ); - expect(source).not.toContain( - 'ipcMain.handle("stats:trend", (_event, options?: SessionStatsOptions) => store.getStatsTrend', - ); - }); - - it("isolates usage scans from interactive catalog queries and stops both workers", () => { - expect(source).toContain( - "localSessionStatsQueryService ??= createLocalSessionQueryService()", - ); - expect(source).toContain("localSessionQueryService?.stop()"); - expect(source).toContain("localSessionStatsQueryService?.stop()"); - }); - it("does not lazily create query workers after shutdown has started", () => { expect(source).toContain("if (quitStarted || localSessionWorkersStopping)"); expect(source).toMatch( diff --git a/apps/main-2.0/src/automation/engine/main/hub/persisted/agent-hub-persisted-migrations.test.ts b/apps/main-2.0/src/automation/engine/main/hub/persisted/agent-hub-persisted-migrations.test.ts deleted file mode 100644 index cebd00166..000000000 --- a/apps/main-2.0/src/automation/engine/main/hub/persisted/agent-hub-persisted-migrations.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, expect, test } from "vitest"; -import { isPersistedAppStateV5 } from "./agent-hub-persisted-migrations"; - -function state(version: number) { - return { version, workDir: "C:/repo", sessions: [], messages: [], events: [], tasks: [], taskMessages: [], taskEvents: [], teams: [], teamRuns: [] }; -} - -describe("persisted app state validation", () => { - test("accepts only the current V5 state", () => { - expect(isPersistedAppStateV5(state(5))).toBe(true); - expect(isPersistedAppStateV5(state(4))).toBe(false); - }); - - test("rejects a non-array workflow conversation collection", () => { - expect(isPersistedAppStateV5({ ...state(5), workflowNodeConversations: {} })).toBe(false); - }); -}); diff --git a/apps/main-2.0/src/automation/engine/main/hub/persisted/agent-hub-persistence.test.ts b/apps/main-2.0/src/automation/engine/main/hub/persisted/agent-hub-persistence.test.ts index 091e682f9..5f9480f9f 100644 --- a/apps/main-2.0/src/automation/engine/main/hub/persisted/agent-hub-persistence.test.ts +++ b/apps/main-2.0/src/automation/engine/main/hub/persisted/agent-hub-persistence.test.ts @@ -1,8 +1,22 @@ import { describe, expect, test } from "vitest"; import { isWorkflowRunNodeStatus } from "./agent-hub-persistence"; +import { isPersistedAppStateV5 } from "./agent-hub-persisted-migrations"; -describe("AgentHub Workflow persistence", () => { +function state(version: number) { + return { version, workDir: "C:/repo", sessions: [], messages: [], events: [], tasks: [], taskMessages: [], taskEvents: [], teams: [], teamRuns: [] }; +} + +describe("AgentHub persistence validation", () => { test("accepts the durable status for a human-approved review result", () => { expect(isWorkflowRunNodeStatus("completed_with_override")).toBe(true); }); + + test("accepts only the current V5 state", () => { + expect(isPersistedAppStateV5(state(5))).toBe(true); + expect(isPersistedAppStateV5(state(4))).toBe(false); + }); + + test("rejects a non-array workflow conversation collection", () => { + expect(isPersistedAppStateV5({ ...state(5), workflowNodeConversations: {} })).toBe(false); + }); }); diff --git a/apps/main-2.0/src/core/summary-endpoint.test.ts b/apps/main-2.0/src/core/summary-endpoint.test.ts index 3a6de10a5..3d3227368 100644 --- a/apps/main-2.0/src/core/summary-endpoint.test.ts +++ b/apps/main-2.0/src/core/summary-endpoint.test.ts @@ -8,6 +8,9 @@ import { resolveSummaryEndpointFromSettings, } from "./summary-endpoint"; +const codexConfigDir = path.resolve("fixtures", "summary-codex"); +const claudeConfigDir = path.resolve("fixtures", "summary-claude"); + function settingsWith(overrides: Partial): AppSettings { return { ...structuredClone(defaultSettings), ...overrides }; } @@ -27,12 +30,12 @@ describe("summary exec endpoints", () => { it("points each CLI at its own directory without touching the other", () => { const settings = settingsWith({ - summaryCodexConfigDir: "/tmp/summary-codex", - summaryClaudeConfigDir: "/tmp/summary-claude", + summaryCodexConfigDir: codexConfigDir, + summaryClaudeConfigDir: claudeConfigDir, }); - expect(buildCodexExecEndpoint(settings).env).toEqual({ CODEX_HOME: "/tmp/summary-codex" }); - expect(buildClaudeExecEndpoint(settings).env).toEqual({ CLAUDE_CONFIG_DIR: "/tmp/summary-claude" }); + expect(buildCodexExecEndpoint(settings).env).toEqual({ CODEX_HOME: codexConfigDir }); + expect(buildClaudeExecEndpoint(settings).env).toEqual({ CLAUDE_CONFIG_DIR: claudeConfigDir }); }); it("expands a leading ~ so a hand-typed path reaches the right directory", () => { @@ -73,25 +76,25 @@ describe("summary exec endpoints", () => { describe("resolveSummaryEndpointFromSettings", () => { it("routes each source to its own CLI and directory", () => { const settings = settingsWith({ - summaryCodexConfigDir: "/tmp/summary-codex", - summaryClaudeConfigDir: "/tmp/summary-claude", + summaryCodexConfigDir: codexConfigDir, + summaryClaudeConfigDir: claudeConfigDir, }); const codex = resolveSummaryEndpointFromSettings({ ...settings, summarySource: "codex" }); expect(codex?.apiFormat).toBe("codex_exec"); - expect(codex?.env).toEqual({ CODEX_HOME: "/tmp/summary-codex" }); + expect(codex?.env).toEqual({ CODEX_HOME: codexConfigDir }); const claude = resolveSummaryEndpointFromSettings({ ...settings, summarySource: "claude" }); expect(claude?.apiFormat).toBe("claude_exec"); - expect(claude?.env).toEqual({ CLAUDE_CONFIG_DIR: "/tmp/summary-claude" }); + expect(claude?.env).toEqual({ CLAUDE_CONFIG_DIR: claudeConfigDir }); }); it("keeps a custom route on HTTP and out of both config directories", () => { const endpoint = resolveSummaryEndpointFromSettings(settingsWith({ summarySource: "custom", summaryApiConfigMode: "custom", - summaryCodexConfigDir: "/tmp/summary-codex", - summaryClaudeConfigDir: "/tmp/summary-claude", + summaryCodexConfigDir: codexConfigDir, + summaryClaudeConfigDir: claudeConfigDir, summaryReasoningEffort: "low", summaryApiConfig: { ...defaultSettings.summaryApiConfig, diff --git a/apps/main-2.0/src/main/live-session-worker-wiring.test.ts b/apps/main-2.0/src/main/live-session-worker-wiring.test.ts deleted file mode 100644 index a24385eb4..000000000 --- a/apps/main-2.0/src/main/live-session-worker-wiring.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { readFileSync } from "node:fs"; -import { describe, expect, it } from "vitest"; - -const mainSource = readFileSync(new URL("./index.ts", import.meta.url), "utf8"); -const viteConfigSource = readFileSync(new URL("../../electron.vite.config.ts", import.meta.url), "utf8"); - -describe("main process live-session worker wiring", () => { - it("builds and uses the local live-session worker behind the existing cache", () => { - expect(viteConfigSource).toContain( - '"live-session-worker": resolve("src/main/live-session-worker.ts")', - ); - expect(mainSource).toContain( - 'new LocalLiveSessionService(\n path.join(__dirname, "live-session-worker.js"),', - ); - expect(mainSource).toContain( - "const loadCachedLocalLiveSessionSnapshot = createCachedLiveSessionSnapshotLoader({\n" - + " load: (options) => localLiveSessionService.load(options),\n" - + "});", - ); - }); - - it("keeps remote merging in the main process and stops the worker during quit", () => { - const localLoad = mainSource.indexOf("loadCachedLocalLiveSessionSnapshot(options)"); - const remoteLoad = mainSource.indexOf("loadRemoteLiveSessions(", localLoad); - const quitStarted = mainSource.indexOf("automationQuitStarted = true;"); - const stopWorker = mainSource.indexOf("localLiveSessionService.stop();", quitStarted); - - expect(localLoad).toBeGreaterThan(-1); - expect(remoteLoad).toBeGreaterThan(localLoad); - expect(stopWorker).toBeGreaterThan(quitStarted); - }); -}); diff --git a/apps/main-2.0/src/main/main-startup-wiring.test.ts b/apps/main-2.0/src/main/main-startup-wiring.test.ts deleted file mode 100644 index d209e09bd..000000000 --- a/apps/main-2.0/src/main/main-startup-wiring.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { readFileSync } from "node:fs"; -import { describe, expect, it } from "vitest"; - -describe("main process wiring", () => { - it("migrates SSH sessions through remote writeback and SSH Resume", () => { - const source = readFileSync(new URL("./index.ts", import.meta.url), "utf8"); - - expect(source).toContain('source?.environmentKind === "wsl" || source?.environmentKind === "ssh"'); - expect(source).toContain('request.target !== sshMigrationTarget(migrationSource.source.source)'); - expect(source).toContain('allowSsh: migrationSource.source.environmentKind === "ssh"'); - expect(source).toContain("createSourceRemoteRestoreDependencies(environment, progress)"); - expect(source).toContain('environment.kind === "ssh" ? inspectSshMigrationCli(environment, target)'); - expect(source).toContain("getRemoteMigrationCliVersionCommand(command, args)"); - expect(source).toContain('const sshArgs = buildRemoteInteractiveSshArgs(environment, "").slice(0, -1)'); - expect(source).toContain("await openResumeInTerminal(session, getSettings(), { sshArgs })"); - }); - - it("uses Electron networking for direct summary providers", () => { - const source = readFileSync(new URL("./index.ts", import.meta.url), "utf8"); - - expect(source).toContain("fetch: electronSummaryFetch"); - expect(source).toContain("net.fetch(input, init)"); - }); -}); diff --git a/apps/main-2.0/src/main/openviking-main-wiring.test.ts b/apps/main-2.0/src/main/openviking-main-wiring.test.ts index e0bd4c68c..2774f4e3a 100644 --- a/apps/main-2.0/src/main/openviking-main-wiring.test.ts +++ b/apps/main-2.0/src/main/openviking-main-wiring.test.ts @@ -3,38 +3,11 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; describe("OpenViking main-process wiring", () => { - it("replaces the rules-file memory IPC with the directory memory control plane", async () => { + it("keeps startup ordering, hook cleanup, and configured extraction routes", async () => { const mainSource = await readFile(path.join(process.cwd(), "src/main/index.ts"), "utf8"); - const preloadSource = await readFile(path.join(process.cwd(), "src/preload/index.ts"), "utf8"); - - expect(mainSource).toContain("registerOpenVikingMemoryIpc"); - expect(mainSource).toContain("initializeOpenVikingMemory"); - expect(mainSource).toContain("OpenVikingHookManifestService"); - expect(mainSource).toContain("OpenVikingHookManifestPublisher"); - expect(mainSource).toContain("OpenVikingHookStateFlusher"); expect(mainSource).toContain("openVikingHookStateFlusher.start()"); expect(mainSource).toContain("openVikingHookStateFlusher?.stop()"); expect(mainSource).toContain("openVikingHookManifestService?.clear()"); - expect(mainSource).toContain("reconcileOpenVikingMemoryHooks"); - expect(mainSource).toContain("platform: process.platform"); - expect(mainSource).toContain("refreshOpenVikingHookManifest"); - expect(mainSource).toContain("refreshManagedCredentials"); - expect(mainSource).toContain("withWorkspaceAuth"); - expect(mainSource).toContain('if ((await runtime.getStatus()).state !== "running")'); - expect(mainSource).toContain( - 'process.env.AGENT_RECALL_NODE_PATH || process.env.npm_node_execpath || "node"', - ); - expect(mainSource).toContain("snapshot.workspaces.some((workspace) => workspace.managed)"); - expect(mainSource).not.toContain("syncManagedWorkspaces"); - expect(mainSource).toContain("build-openviking-runtime.mjs"); - expect(mainSource).toContain("developmentFallback"); - expect(mainSource).toContain("allowLocalRuntime: !releaseUpdateRuntime"); - expect(mainSource).toContain( - 'const releaseUpdateRuntime = process.env.AGENT_RECALL_RELEASE_BUILD === "1"', - ); - expect(mainSource).toContain( - "developmentFallback: releaseUpdateRuntime", - ); expect(mainSource).toContain( 'const codexAuthBootstrapPath = codexAuthPath(process.env, app.getPath("home"))', ); @@ -62,11 +35,5 @@ describe("OpenViking main-process wiring", () => { expect(mainSource.indexOf("reconcileOpenVikingMemoryHooks(getSettings());")).toBeLessThan( mainSource.indexOf("const initialIndexSettled"), ); - expect(mainSource).not.toContain("registerAgentMemoryIpc"); - expect(mainSource).not.toContain("new AgentMemoryService"); - expect(preloadSource).not.toContain("createAgentMemoryApi"); - expect(preloadSource).toContain("createOpenVikingMemoryApi"); - expect(preloadSource).not.toContain("importOpenVikingWorkspace"); - expect(preloadSource).not.toContain("listOpenVikingImportSessions"); }); }); diff --git a/scripts/generate-star-history.test.mjs b/scripts/generate-star-history.test.mjs index fc7d99bbb..bf4e79ec4 100644 --- a/scripts/generate-star-history.test.mjs +++ b/scripts/generate-star-history.test.mjs @@ -136,22 +136,13 @@ test('generateStarHistory is idempotent when today is unchanged', async () => { assert.equal(await readFile(outputPath, 'utf8'), svg) }) -test('rendered description uses star count history rather than cumulative wording', () => { - const svg = renderStarHistorySvg({ - repository: 'zszz3/AgentRecall', - series: [{ date: '2026-07-13', count: 152 }, { date: '2026-07-14', count: 151 }] - }) - assert.match(svg, /GitHub star count history/) - assert.doesNotMatch(svg, /Cumulative GitHub stars/) -}) - test('renderStarHistorySvg is deterministic and describes the final star count', () => { const input = { repository: 'zszz3/AgentRecall', series: [ { date: '2026-06-01', count: 1 }, - { date: '2026-06-02', count: 1 }, - { date: '2026-06-03', count: 3 } + { date: '2026-06-02', count: 3 }, + { date: '2026-06-03', count: 2 } ] } @@ -160,7 +151,9 @@ test('renderStarHistorySvg is deterministic and describes the final star count', assert.equal(first, second) assert.match(first, /AgentRecall Star History/) - assert.match(first, /reaching 3 stars/) + assert.match(first, /reaching 2 stars/) + assert.match(first, /GitHub star count history/) + assert.doesNotMatch(first, /Cumulative GitHub stars/) assert.match(first, /Updated Jun 3, 2026/) assert.doesNotMatch(first, /NaN|undefined/) })