

























@@ -1618,6 +1618,35 @@ describe("/acp command", () => {
16181618expect(hoisted.runTurnMock).not.toHaveBeenCalled();
16191619});
162016201621+it("falls through to thread-bound resolution when explicit session token is unresolvable", async () => {
1622+// callGateway returns null for sessions.resolve (unresolvable token)
1623+// but a thread-bound session exists — should use thread-bound, not error out
1624+hoisted.callGatewayMock.mockImplementation(async (request: { method?: string }) => {
1625+if (request.method === "sessions.resolve") {
1626+return null; // token lookup fails
1627+}
1628+return { ok: true };
1629+});
1630+mockBoundThreadSession();
1631+hoisted.readAcpSessionEntryMock.mockReturnValue(createAcpSessionEntry());
1632+hoisted.runTurnMock.mockImplementation(async function* () {
1633+yield { type: "text_delta", text: "Steered." };
1634+yield { type: "done" };
1635+});
1636+1637+const result = await runThreadAcpCommand(
1638+`/acp steer --session unresolvable-token-xyz tighten logging`,
1639+);
1640+1641+expect(hoisted.runTurnMock).toHaveBeenCalledWith(
1642+expect.objectContaining({
1643+mode: "steer",
1644+sessionKey: defaultAcpSessionKey,
1645+}),
1646+);
1647+expect(result?.reply?.text).toContain("Steered.");
1648+});
1649+16211650it("closes an ACP session, unbinds thread targets, and clears metadata", async () => {
16221651mockBoundThreadSession();
16231652hoisted.sessionBindingUnbindMock.mockResolvedValue([
@@ -1637,6 +1666,56 @@ describe("/acp command", () => {
16371666expect(result?.reply?.text).toContain("Removed 1 binding");
16381667});
163916681669+it("closes the bound thread ACP session when an explicit session token is unresolvable", async () => {
1670+hoisted.callGatewayMock.mockImplementation(async (request: { method?: string }) => {
1671+if (request.method === "sessions.resolve") {
1672+return null;
1673+}
1674+return { ok: true };
1675+});
1676+mockBoundThreadSession();
1677+hoisted.sessionBindingUnbindMock.mockResolvedValue([
1678+createBoundThreadSession() as SessionBindingRecord,
1679+]);
1680+1681+const result = await runThreadAcpCommand("/acp close not-a-session-target");
1682+1683+expect(hoisted.closeMock).toHaveBeenCalledWith({
1684+cfg: baseCfg,
1685+sessionKey: defaultAcpSessionKey,
1686+reason: "manual-close",
1687+allowBackendUnavailable: true,
1688+clearMeta: true,
1689+});
1690+expect(hoisted.sessionBindingUnbindMock).toHaveBeenCalledWith(
1691+expect.objectContaining({
1692+targetSessionKey: defaultAcpSessionKey,
1693+reason: "manual",
1694+}),
1695+);
1696+expect(result?.reply?.text).toContain(`Closed ACP session ${defaultAcpSessionKey}`);
1697+});
1698+1699+it("reports an explicit bad ACP session token before requester fallback", async () => {
1700+hoisted.callGatewayMock.mockImplementation(async (request: { method?: string }) => {
1701+if (request.method === "sessions.resolve") {
1702+return null;
1703+}
1704+return { ok: true };
1705+});
1706+const params = createConversationParams("/acp close not-a-session-target", {
1707+channel: "discord",
1708+originatingTo: "channel:parent-1",
1709+sessionKey: "requester-session",
1710+});
1711+1712+const result = await handleAcpCommand(params, true);
1713+1714+expect(result?.reply?.text).toContain("Unable to resolve session target: not-a-session-target");
1715+expect(hoisted.closeMock).not.toHaveBeenCalled();
1716+expect(hoisted.readAcpSessionEntryMock).not.toHaveBeenCalled();
1717+});
1718+16401719it("handles /acp close in a bound thread when text commands are disabled", async () => {
16411720mockBoundThreadSession();
16421721hoisted.sessionBindingUnbindMock.mockResolvedValue([
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。