Skip to content

Commit 39272ab

Browse files
author
clawsweeper-repair
committed
fix(acp): preserve bad-token diagnostics after thread fallback
1 parent 07d475e commit 39272ab

3 files changed

Lines changed: 59 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Docs: https://docs.openclaw.ai
8888
- Plugins/runtime-deps: add `openclaw plugins deps` inspection and repair with script-free package-manager defaults shared across plugin installers, so operators can repair missing bundled runtime deps without corrupting JSON output or blocking unrelated conflict-free deps. Thanks @vincentkoc.
8989
- Agents/output: strip internal `[tool calls omitted]` replay placeholders from user-facing replies while preserving visible reply whitespace. Fixes #74573. Thanks @blaspat.
9090
- Providers/Google Vertex: route authorized_user ADC credentials through OpenClaw's REST transport so Docker installs using gcloud application-default credentials no longer crash in the Google SDK before requests are sent. Fixes #74628. Thanks @frankhal2001-design.
91-
- ACP/resolver: fall through to thread-bound session resolution when an explicit `--session` token cannot be resolved, so Discord slash commands that auto-fill the current thread ID as the positional ACP target no longer return "Unable to resolve session target" errors. Fixes #66299. Thanks @martingarramon.
91+
- ACP/resolver: fall through to thread-bound session resolution when an explicit `--session` token cannot be resolved while preserving the bad-token diagnostic when no thread binding exists, so Discord slash commands that auto-fill the current thread ID as the positional ACP target no longer return "Unable to resolve session target" errors. Fixes #66299. Thanks @hclsys, @kindomLee, and @martingarramon.
9292
- Agents/sessions: emit a terminal lifecycle backstop when embedded timeout/error turns return without `agent_end`, so Gateway sessions no longer stay stuck in `running` after failover surfaces a timeout. Fixes #74607. Thanks @millerc79.
9393
- Gateway/diagnostics: include stuck-session reason hints and recovery skip causes in warnings, so operators can tell whether a lane is waiting on active work, queued work, or stale bookkeeping. Thanks @vincentkoc.
9494
- Agents/Codex: bound embedded-run cleanup, trajectory flushing, and command-lane task timeouts after runtime failures, so Discord and other chat sessions return to idle instead of staying stuck in processing. Thanks @vincentkoc.

src/auto-reply/reply/commands-acp.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,6 +1666,56 @@ describe("/acp command", () => {
16661666
expect(result?.reply?.text).toContain("Removed 1 binding");
16671667
});
16681668

1669+
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+
16691719
it("handles /acp close in a bound thread when text commands are disabled", async () => {
16701720
mockBoundThreadSession();
16711721
hoisted.sessionBindingUnbindMock.mockResolvedValue([

src/auto-reply/reply/commands-acp/targets.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ export async function resolveAcpTargetSessionKey(params: {
6565
// Token was supplied but could not be resolved as a session key/id/label.
6666
// Fall through to thread-bound resolution so that callers that auto-fill
6767
// the current thread ID as the token (e.g. Discord slash commands) still
68-
// reach the correct session via the binding context. Only return an error
69-
// if neither thread-bound nor requester-session fallbacks can produce a key.
68+
// reach the correct session via the binding context.
7069
}
7170

7271
const threadBound = resolveBoundAcpThreadSessionKey(params.commandParams);
@@ -77,6 +76,13 @@ export async function resolveAcpTargetSessionKey(params: {
7776
};
7877
}
7978

79+
if (token) {
80+
return {
81+
ok: false,
82+
error: `Unable to resolve session target: ${token}`,
83+
};
84+
}
85+
8086
const fallback = resolveRequesterSessionKey(params.commandParams, {
8187
preferCommandTarget: true,
8288
});

0 commit comments

Comments
 (0)