mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
fix: resolve oneshot ACP identities before close
This commit is contained in:
@@ -87,6 +87,9 @@ Docs: https://docs.openclaw.ai
|
||||
- ACP/sessions_spawn: apply `runTimeoutSeconds` to ACP child turns and dispatch
|
||||
those turns on the background subagent lane, so quota-stalled ACP harnesses do
|
||||
not occupy the main agent lane indefinitely. Fixes #68823.
|
||||
- ACP/oneshot: reconcile runtime session identity before closing completed
|
||||
oneshot ACP runs, so finished `sessions.json` entries do not stay stuck with
|
||||
`acp.identity.state="pending"`.
|
||||
- ACP/models: document that non-Codex ACP model overrides require adapter
|
||||
support for ACP `models` plus `session/set_model`, so unsupported harnesses
|
||||
fail clearly instead of silently falling back to their defaults.
|
||||
|
||||
@@ -929,15 +929,8 @@ export class AcpSessionManager {
|
||||
if (activeTurn && this.activeTurnBySession.get(actorKey) === activeTurn) {
|
||||
this.activeTurnBySession.delete(actorKey);
|
||||
}
|
||||
if (
|
||||
!retryFreshHandle &&
|
||||
!skipPostTurnCleanup &&
|
||||
runtime &&
|
||||
handle &&
|
||||
meta &&
|
||||
meta.mode !== "oneshot"
|
||||
) {
|
||||
({ handle } = await this.reconcileRuntimeSessionIdentifiers({
|
||||
if (!retryFreshHandle && !skipPostTurnCleanup && runtime && handle && meta) {
|
||||
({ handle, meta } = await this.reconcileRuntimeSessionIdentifiers({
|
||||
cfg: input.cfg,
|
||||
sessionKey,
|
||||
runtime,
|
||||
|
||||
@@ -2399,6 +2399,91 @@ describe("AcpSessionManager", () => {
|
||||
expect(currentMeta.identity?.agentSessionId).toBe("agent-fresh");
|
||||
});
|
||||
|
||||
it("reconciles oneshot ACP identity from runtime status before closing after a turn", async () => {
|
||||
const runtimeState = createRuntime();
|
||||
runtimeState.ensureSession.mockResolvedValue({
|
||||
sessionKey: "agent:codex:acp:session-1",
|
||||
backend: "acpx",
|
||||
runtimeSessionName: "runtime-1",
|
||||
backendSessionId: "acpx-oneshot",
|
||||
});
|
||||
runtimeState.getStatus.mockResolvedValue({
|
||||
summary: "status=done",
|
||||
backendSessionId: "acpx-oneshot",
|
||||
agentSessionId: "agent-oneshot",
|
||||
details: { status: "done" },
|
||||
});
|
||||
hoisted.requireAcpRuntimeBackendMock.mockReturnValue({
|
||||
id: "acpx",
|
||||
runtime: runtimeState.runtime,
|
||||
});
|
||||
|
||||
let currentMeta: SessionAcpMeta | undefined;
|
||||
hoisted.readAcpSessionEntryMock.mockImplementation((paramsUnknown: unknown) => {
|
||||
const sessionKey =
|
||||
(paramsUnknown as { sessionKey?: string }).sessionKey ?? "agent:codex:acp:session-1";
|
||||
return {
|
||||
sessionKey,
|
||||
storeSessionKey: sessionKey,
|
||||
acp: currentMeta,
|
||||
};
|
||||
});
|
||||
hoisted.upsertAcpSessionMetaMock.mockImplementation(async (paramsUnknown: unknown) => {
|
||||
const params = paramsUnknown as {
|
||||
mutate: (
|
||||
current: SessionAcpMeta | undefined,
|
||||
entry: { acp?: SessionAcpMeta } | undefined,
|
||||
) => SessionAcpMeta | null | undefined;
|
||||
};
|
||||
const next = params.mutate(currentMeta, { acp: currentMeta });
|
||||
if (next) {
|
||||
currentMeta = next;
|
||||
}
|
||||
return {
|
||||
sessionId: "session-1",
|
||||
updatedAt: Date.now(),
|
||||
acp: currentMeta,
|
||||
};
|
||||
});
|
||||
|
||||
const manager = new AcpSessionManager();
|
||||
await manager.initializeSession({
|
||||
cfg: baseCfg,
|
||||
sessionKey: "agent:codex:acp:session-1",
|
||||
agent: "codex",
|
||||
mode: "oneshot",
|
||||
});
|
||||
|
||||
expect(currentMeta?.identity).toMatchObject({
|
||||
state: "pending",
|
||||
acpxSessionId: "acpx-oneshot",
|
||||
source: "ensure",
|
||||
});
|
||||
|
||||
await manager.runTurn({
|
||||
cfg: baseCfg,
|
||||
sessionKey: "agent:codex:acp:session-1",
|
||||
text: "do work",
|
||||
mode: "prompt",
|
||||
requestId: "run-1",
|
||||
});
|
||||
|
||||
expect(runtimeState.getStatus).toHaveBeenCalledTimes(2);
|
||||
expect(runtimeState.close).toHaveBeenCalledWith({
|
||||
handle: expect.objectContaining({
|
||||
backendSessionId: "acpx-oneshot",
|
||||
agentSessionId: "agent-oneshot",
|
||||
}),
|
||||
reason: "oneshot-complete",
|
||||
});
|
||||
expect(currentMeta?.identity).toMatchObject({
|
||||
state: "resolved",
|
||||
acpxSessionId: "acpx-oneshot",
|
||||
agentSessionId: "agent-oneshot",
|
||||
source: "status",
|
||||
});
|
||||
});
|
||||
|
||||
it("reconciles pending ACP identities during startup scan", async () => {
|
||||
const runtimeState = createRuntime();
|
||||
runtimeState.getStatus.mockResolvedValue({
|
||||
|
||||
Reference in New Issue
Block a user