


























@@ -7,11 +7,12 @@ import { afterEach, describe, expect, it, vi } from "vitest";
77import { createBundleMcpJsonSchemaValidator } from "./agent-bundle-mcp-runtime.js";
88import { cleanupBundleMcpHarness } from "./agent-bundle-mcp-test-harness.js";
99import {
10-testing,
10+createSessionMcpRuntime,
1111getOrCreateSessionMcpRuntime,
1212materializeBundleMcpToolsForRun,
1313retireSessionMcpRuntime,
1414retireSessionMcpRuntimeForSessionKey,
15+testing,
1516} from "./agent-bundle-mcp-tools.js";
1617import type { SessionMcpRuntime } from "./agent-bundle-mcp-types.js";
1718import { writeExecutable } from "./bundle-mcp-shared.test-harness.js";
@@ -1591,7 +1592,6 @@ process.on("SIGINT", shutdown);`,
15911592activeLeases += 1;
15921593return () => {
15931594activeLeases -= 1;
1594-lastUsedAt = now;
15951595};
15961596},
15971597dispose: async () => {
@@ -1626,6 +1626,65 @@ process.on("SIGINT", shutdown);`,
16261626expect(manager.resolveSessionId("agent:test:session-idle")).toBeUndefined();
16271627});
162816281629+it("evicts immediately after release when TTL already elapsed during active lease", async () => {
1630+let now = 1_000;
1631+const disposed: string[] = [];
1632+const createRuntime: RuntimeFactory = (params) => {
1633+let lastUsedAt = now;
1634+let activeLeases = 0;
1635+return {
1636+ ...makeRuntime([{ toolName: "bundle_probe", description: "Bundle MCP probe" }]),
1637+sessionId: params.sessionId,
1638+sessionKey: params.sessionKey,
1639+workspaceDir: params.workspaceDir,
1640+configFingerprint: params.configFingerprint ?? "fingerprint",
1641+get lastUsedAt() {
1642+return lastUsedAt;
1643+},
1644+get activeLeases() {
1645+return activeLeases;
1646+},
1647+markUsed: () => {
1648+lastUsedAt = now;
1649+},
1650+acquireLease: () => {
1651+activeLeases += 1;
1652+return () => {
1653+activeLeases -= 1;
1654+};
1655+},
1656+dispose: async () => {
1657+disposed.push(params.sessionId);
1658+},
1659+};
1660+};
1661+const manager = testing.createSessionMcpRuntimeManager({
1662+ createRuntime,
1663+now: () => now,
1664+enableIdleSweepTimer: false,
1665+});
1666+1667+const runtime = await manager.getOrCreate({
1668+sessionId: "session-idle-post-release",
1669+sessionKey: "agent:test:session-idle-post-release",
1670+workspaceDir: "/workspace",
1671+cfg: { mcp: { servers: {}, sessionIdleTtlMs: 50 } },
1672+});
1673+const releaseLease = runtime.acquireLease?.();
1674+1675+// TTL elapses while the lease is still held, so sweep skips active runtimes.
1676+now += 60;
1677+await expect(manager.sweepIdleRuntimes()).resolves.toBe(0);
1678+1679+// Release must not reset lastUsedAt; the runtime is evictable on the very
1680+// next sweep without waiting another full TTL.
1681+releaseLease?.();
1682+await expect(manager.sweepIdleRuntimes()).resolves.toBe(1);
1683+1684+expect(disposed).toEqual(["session-idle-post-release"]);
1685+expect(manager.listSessionIds()).toStrictEqual([]);
1686+});
1687+16291688it("keeps idle runtime eviction disabled when the TTL is zero", async () => {
16301689let now = 1_000;
16311690const disposed: string[] = [];
@@ -1655,6 +1714,21 @@ process.on("SIGINT", shutdown);`,
16551714expect(manager.listSessionIds()).toEqual(["session-no-ttl"]);
16561715expect(disposed).toStrictEqual([]);
16571716});
1717+1718+it("production createSessionMcpRuntime acquireLease release does not refresh lastUsedAt", () => {
1719+const runtime = createSessionMcpRuntime({
1720+sessionId: "session-lease-timestamp-check",
1721+workspaceDir: "/workspace",
1722+cfg: { mcp: { servers: {} } },
1723+});
1724+const lastUsedBefore = runtime.lastUsedAt;
1725+if (!runtime.acquireLease) {
1726+throw new Error("Expected production session MCP runtime to expose acquireLease");
1727+}
1728+const release = runtime.acquireLease();
1729+release();
1730+expect(runtime.lastUsedAt).toBe(lastUsedBefore);
1731+});
16581732});
1659173316601734describe("disposeSession timeout", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。