|
| 1 | +import { mkdtemp, readFile } from "node:fs/promises"; |
| 2 | +import { tmpdir } from "node:os"; |
| 3 | +import { join } from "node:path"; |
1 | 4 | import { afterEach, describe, expect, it, vi } from "vitest"; |
| 5 | +import { writeBundleProbeMcpServer } from "./bundle-mcp-shared.test-harness.js"; |
2 | 6 | import { createBundleMcpJsonSchemaValidator } from "./pi-bundle-mcp-runtime.js"; |
3 | 7 | import { cleanupBundleMcpHarness } from "./pi-bundle-mcp-test-harness.js"; |
4 | 8 | import { |
5 | 9 | __testing, |
| 10 | + createSessionMcpRuntime, |
6 | 11 | getOrCreateSessionMcpRuntime, |
7 | 12 | materializeBundleMcpToolsForRun, |
8 | 13 | retireSessionMcpRuntime, |
@@ -141,6 +146,64 @@ describe("session MCP runtime", () => { |
141 | 146 | ]); |
142 | 147 | }); |
143 | 148 |
|
| 149 | + it("emits diagnostics timeline spans for bundle MCP server catalog discovery", async () => { |
| 150 | + const dir = await mkdtemp(join(tmpdir(), "openclaw-bundle-mcp-timeline-")); |
| 151 | + const serverScriptPath = join(dir, "bundle-probe-server.mjs"); |
| 152 | + const timelinePath = join(dir, "timeline.jsonl"); |
| 153 | + await writeBundleProbeMcpServer(serverScriptPath); |
| 154 | + const previousDiagnostics = process.env.OPENCLAW_DIAGNOSTICS; |
| 155 | + const previousTimelinePath = process.env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH; |
| 156 | + process.env.OPENCLAW_DIAGNOSTICS = "timeline"; |
| 157 | + process.env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH = timelinePath; |
| 158 | + try { |
| 159 | + const runtime = createSessionMcpRuntime({ |
| 160 | + sessionId: "session-timeline", |
| 161 | + workspaceDir: dir, |
| 162 | + cfg: { |
| 163 | + mcp: { |
| 164 | + servers: { |
| 165 | + bundleProbe: { |
| 166 | + command: "node", |
| 167 | + args: [serverScriptPath], |
| 168 | + }, |
| 169 | + }, |
| 170 | + }, |
| 171 | + }, |
| 172 | + }); |
| 173 | + const materialized = await materializeBundleMcpToolsForRun({ runtime }); |
| 174 | + await materialized.dispose(); |
| 175 | + await runtime.dispose(); |
| 176 | + |
| 177 | + const events = (await readFile(timelinePath, "utf-8")) |
| 178 | + .trim() |
| 179 | + .split("\n") |
| 180 | + .filter(Boolean) |
| 181 | + .map((line) => JSON.parse(line) as { name: string; type: string; durationMs?: number }); |
| 182 | + expect(events).toEqual( |
| 183 | + expect.arrayContaining([ |
| 184 | + expect.objectContaining({ name: "bundle-mcp.server", type: "span.end" }), |
| 185 | + expect.objectContaining({ name: "bundle-mcp.connect", type: "span.end" }), |
| 186 | + expect.objectContaining({ name: "bundle-mcp.tools-list", type: "span.end" }), |
| 187 | + ]), |
| 188 | + ); |
| 189 | + const toolsListSpan = events.find( |
| 190 | + (event) => event.name === "bundle-mcp.tools-list" && event.type === "span.end", |
| 191 | + ); |
| 192 | + expect(toolsListSpan?.durationMs).toEqual(expect.any(Number)); |
| 193 | + } finally { |
| 194 | + if (previousDiagnostics === undefined) { |
| 195 | + delete process.env.OPENCLAW_DIAGNOSTICS; |
| 196 | + } else { |
| 197 | + process.env.OPENCLAW_DIAGNOSTICS = previousDiagnostics; |
| 198 | + } |
| 199 | + if (previousTimelinePath === undefined) { |
| 200 | + delete process.env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH; |
| 201 | + } else { |
| 202 | + process.env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH = previousTimelinePath; |
| 203 | + } |
| 204 | + } |
| 205 | + }); |
| 206 | + |
144 | 207 | it("holds a runtime lease until the materialized tool runtime is disposed", async () => { |
145 | 208 | let activeLeases = 0; |
146 | 209 | const runtime = { |
|
0 commit comments