


























@@ -125,6 +125,23 @@ describe("active-memory plugin", () => {
125125"utf8",
126126);
127127};
128+const makeMemoryToolAllowlistError = (
129+reason: string,
130+sources = "runtime toolsAllow: memory_recall, memory_search, memory_get",
131+) =>
132+new Error(
133+`No callable tools remain after resolving explicit tool allowlist ` +
134+`(${sources}); ${reason}. ` +
135+`Fix the allowlist or enable the plugin that registers the requested tool.`,
136+);
137+const hasDebugLine = (needle: string) =>
138+vi
139+.mocked(api.logger.debug)
140+.mock.calls.some((call: unknown[]) => String(call[0]).includes(needle));
141+const hasWarnLine = (needle: string) =>
142+vi
143+.mocked(api.logger.warn)
144+.mock.calls.some((call: unknown[]) => String(call[0]).includes(needle));
128145129146beforeEach(async () => {
130147vi.clearAllMocks();
@@ -1646,6 +1663,133 @@ describe("active-memory plugin", () => {
16461663expect(result).toBeUndefined();
16471664});
164816651666+it("skips the recall subagent when no registered memory tools match", async () => {
1667+const sessionKey = "agent:main:missing-memory-tools";
1668+hoisted.sessionStore[sessionKey] = {
1669+sessionId: "s-missing-memory-tools",
1670+updatedAt: 0,
1671+};
1672+const error = makeMemoryToolAllowlistError("no registered tools matched");
1673+expect(__testing.isMissingRegisteredMemoryToolsError(error)).toBe(true);
1674+runEmbeddedPiAgent.mockRejectedValueOnce(error);
1675+1676+const result = await hooks.before_prompt_build(
1677+{ prompt: "what wings should i order? missing memory tools", messages: [] },
1678+{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
1679+);
1680+1681+expect(result).toBeUndefined();
1682+expect(hasDebugLine("no memory tools registered")).toBe(true);
1683+expect(hasWarnLine("No callable tools remain")).toBe(false);
1684+const lines = getActiveMemoryLines(sessionKey);
1685+expect(lines).toEqual([expect.stringContaining("🧩 Active Memory: status=empty")]);
1686+expect(lines.join("\n")).not.toContain("status=unavailable");
1687+});
1688+1689+it("skips missing memory tools when the allowlist error includes inherited sources", async () => {
1690+const sessionKey = "agent:main:missing-memory-tools-with-policy-source";
1691+hoisted.sessionStore[sessionKey] = {
1692+sessionId: "s-missing-memory-tools-with-policy-source",
1693+updatedAt: 0,
1694+};
1695+const error = makeMemoryToolAllowlistError(
1696+"no registered tools matched",
1697+"tools.allow: *, lobster; runtime toolsAllow: memory_recall, memory_search, memory_get",
1698+);
1699+expect(__testing.isMissingRegisteredMemoryToolsError(error)).toBe(true);
1700+runEmbeddedPiAgent.mockRejectedValueOnce(error);
1701+1702+const result = await hooks.before_prompt_build(
1703+{ prompt: "what wings should i order? missing memory tools with policy", messages: [] },
1704+{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
1705+);
1706+1707+expect(result).toBeUndefined();
1708+expect(hasDebugLine("no memory tools registered")).toBe(true);
1709+expect(hasWarnLine("No callable tools remain")).toBe(false);
1710+expect(getActiveMemoryLines(sessionKey)).toEqual([
1711+expect.stringContaining("🧩 Active Memory: status=empty"),
1712+]);
1713+});
1714+1715+it("keeps memory-tool allowlist errors visible when upstream policy can filter memory tools", async () => {
1716+const sessionKey = "agent:main:memory-tools-filtered-by-policy";
1717+hoisted.sessionStore[sessionKey] = {
1718+sessionId: "s-memory-tools-filtered-by-policy",
1719+updatedAt: 0,
1720+};
1721+const error = makeMemoryToolAllowlistError(
1722+"no registered tools matched",
1723+"tools.allow: read, exec; runtime toolsAllow: memory_recall, memory_search, memory_get",
1724+);
1725+expect(__testing.isMissingRegisteredMemoryToolsError(error)).toBe(false);
1726+runEmbeddedPiAgent.mockRejectedValueOnce(error);
1727+1728+const result = await hooks.before_prompt_build(
1729+{ prompt: "what wings should i order? memory tools filtered by policy", messages: [] },
1730+{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
1731+);
1732+1733+expect(result).toBeUndefined();
1734+expect(hasDebugLine("no memory tools registered")).toBe(false);
1735+expect(hasWarnLine("No callable tools remain")).toBe(true);
1736+expect(getActiveMemoryLines(sessionKey)).toEqual([
1737+expect.stringContaining("🧩 Active Memory: status=unavailable"),
1738+]);
1739+});
1740+1741+it.each([
1742+["disabled tools", "tools are disabled for this run"],
1743+["models without tool support", "the selected model does not support tools"],
1744+])("keeps allowlist errors for %s visible", async (_label, reason) => {
1745+const sessionKey = `agent:main:${reason.replace(/\W+/g, "-")}`;
1746+hoisted.sessionStore[sessionKey] = {
1747+sessionId: `s-${reason.replace(/\W+/g, "-")}`,
1748+updatedAt: 0,
1749+};
1750+const error = makeMemoryToolAllowlistError(reason);
1751+expect(__testing.isMissingRegisteredMemoryToolsError(error)).toBe(false);
1752+runEmbeddedPiAgent.mockRejectedValueOnce(error);
1753+1754+const result = await hooks.before_prompt_build(
1755+{ prompt: `what wings should i order? ${reason}`, messages: [] },
1756+{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
1757+);
1758+1759+expect(result).toBeUndefined();
1760+expect(hasDebugLine("no memory tools registered")).toBe(false);
1761+expect(hasWarnLine(reason)).toBe(true);
1762+expect(getActiveMemoryLines(sessionKey)).toEqual([
1763+expect.stringContaining("🧩 Active Memory: status=unavailable"),
1764+]);
1765+});
1766+1767+it("does not skip missing memory-tool allowlist errors after abort", async () => {
1768+const sessionKey = "agent:main:missing-memory-tools-after-abort";
1769+hoisted.sessionStore[sessionKey] = {
1770+sessionId: "s-missing-memory-tools-after-abort",
1771+updatedAt: 0,
1772+};
1773+runEmbeddedPiAgent.mockImplementationOnce(async (params: { abortSignal?: AbortSignal }) => {
1774+Object.defineProperty(params.abortSignal as AbortSignal, "aborted", {
1775+configurable: true,
1776+value: true,
1777+});
1778+throw makeMemoryToolAllowlistError("no registered tools matched");
1779+});
1780+1781+const result = await hooks.before_prompt_build(
1782+{ prompt: "what wings should i order? missing memory tools after abort", messages: [] },
1783+{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
1784+);
1785+1786+expect(result).toBeUndefined();
1787+expect(hasDebugLine("no memory tools registered")).toBe(false);
1788+expect(getActiveMemoryLines(sessionKey)).toEqual([
1789+expect.stringContaining("🧩 Active Memory: status=timeout"),
1790+]);
1791+});
1792+16491793it("returns partial transcript text on timeout when the subagent has already written assistant output", async () => {
16501794__testing.setMinimumTimeoutMsForTests(1);
16511795__testing.setSetupGraceTimeoutMsForTests(0);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。