mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 16:24:46 +00:00
test: clear acp manager broad matchers
This commit is contained in:
@@ -97,6 +97,60 @@ function createDeferred(): { promise: Promise<void>; resolve: () => void } {
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
function expectRecordFields(record: unknown, expected: Record<string, unknown>) {
|
||||
expect(record).toBeDefined();
|
||||
const actual = record as Record<string, unknown>;
|
||||
for (const [key, value] of Object.entries(expected)) {
|
||||
expect(actual[key]).toEqual(value);
|
||||
}
|
||||
return actual;
|
||||
}
|
||||
|
||||
async function expectRejectedRecord(promise: Promise<unknown>, expected: Record<string, unknown>) {
|
||||
await promise.then(
|
||||
() => {
|
||||
throw new Error("Expected promise to reject.");
|
||||
},
|
||||
(error) => {
|
||||
expectRecordFields(error, expected);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function mockCallArg(mock: ReturnType<typeof vi.fn>, callIndex = 0): Record<string, unknown> {
|
||||
const call = mock.mock.calls[callIndex];
|
||||
if (!call) {
|
||||
throw new Error(`Expected mock call ${callIndex}`);
|
||||
}
|
||||
return call[0] as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function mockCallArgs(mock: ReturnType<typeof vi.fn>): Array<Record<string, unknown>> {
|
||||
return mock.mock.calls.map((call) => call[0] as Record<string, unknown>);
|
||||
}
|
||||
|
||||
function findMockCallFields(mock: ReturnType<typeof vi.fn>, expected: Record<string, unknown>) {
|
||||
return mockCallArgs(mock).find((actual) =>
|
||||
Object.entries(expected).every(([key, value]) => Object.is(actual[key], value)),
|
||||
);
|
||||
}
|
||||
|
||||
function expectMockCallFields(mock: ReturnType<typeof vi.fn>, expected: Record<string, unknown>) {
|
||||
expect(findMockCallFields(mock, expected)).toBeDefined();
|
||||
}
|
||||
|
||||
function expectNoMockCallFields(mock: ReturnType<typeof vi.fn>, expected: Record<string, unknown>) {
|
||||
expect(findMockCallFields(mock, expected)).toBeUndefined();
|
||||
}
|
||||
|
||||
function requireTaskByRunId(runId: string) {
|
||||
const task = findTaskByRunId(runId);
|
||||
if (!task) {
|
||||
throw new Error(`Expected task for run ${runId}`);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
function createRuntime(): {
|
||||
runtime: AcpRuntime;
|
||||
ensureSession: ReturnType<typeof vi.fn>;
|
||||
@@ -295,18 +349,14 @@ describe("AcpSessionManager", () => {
|
||||
requestId: "r-main",
|
||||
});
|
||||
|
||||
expect(hoisted.readAcpSessionEntryMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
cfg,
|
||||
sessionKey: "agent:main:main",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
agent: "main",
|
||||
sessionKey: "agent:main:main",
|
||||
}),
|
||||
);
|
||||
expectRecordFields(mockCallArg(hoisted.readAcpSessionEntryMock), {
|
||||
cfg,
|
||||
sessionKey: "agent:main:main",
|
||||
});
|
||||
expectRecordFields(mockCallArg(runtimeState.ensureSession), {
|
||||
agent: "main",
|
||||
sessionKey: "agent:main:main",
|
||||
});
|
||||
});
|
||||
|
||||
it("tracks parented direct ACP turns in the task registry", async () => {
|
||||
@@ -387,7 +437,7 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(findTaskByRunId("direct-parented-run")).toMatchObject({
|
||||
expectRecordFields(requireTaskByRunId("direct-parented-run"), {
|
||||
runtime: "acp",
|
||||
ownerKey: "agent:quant:telegram:quant:direct:822430204",
|
||||
scopeKind: "session",
|
||||
@@ -473,7 +523,7 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(findTaskByRunId("direct-parented-korean-path-run")).toMatchObject({
|
||||
expectRecordFields(requireTaskByRunId("direct-parented-korean-path-run"), {
|
||||
runtime: "acp",
|
||||
ownerKey: "agent:quant:telegram:quant:direct:822430204",
|
||||
scopeKind: "session",
|
||||
@@ -618,7 +668,7 @@ describe("AcpSessionManager", () => {
|
||||
return;
|
||||
}
|
||||
expect(secondOutcome.error).toBeInstanceOf(AcpRuntimeError);
|
||||
expect(secondOutcome.error).toMatchObject({
|
||||
expectRecordFields(secondOutcome.error, {
|
||||
code: "ACP_TURN_FAILED",
|
||||
message: "ACP operation aborted.",
|
||||
});
|
||||
@@ -683,7 +733,7 @@ describe("AcpSessionManager", () => {
|
||||
|
||||
await vi.advanceTimersByTimeAsync(3_500);
|
||||
|
||||
await expect(first).rejects.toMatchObject({
|
||||
await expectRejectedRecord(first, {
|
||||
code: "ACP_TURN_FAILED",
|
||||
message: "ACP turn timed out after 1s.",
|
||||
});
|
||||
@@ -691,22 +741,17 @@ describe("AcpSessionManager", () => {
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledTimes(1);
|
||||
expect(runtimeState.runTurn).toHaveBeenCalledTimes(2);
|
||||
expect(runtimeState.cancel).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: "turn-timeout",
|
||||
}),
|
||||
);
|
||||
expectRecordFields(mockCallArg(runtimeState.cancel), {
|
||||
reason: "turn-timeout",
|
||||
});
|
||||
expect(runtimeState.close).not.toHaveBeenCalled();
|
||||
expect(manager.getObservabilitySnapshot(cfg)).toMatchObject({
|
||||
runtimeCache: {
|
||||
activeSessions: 1,
|
||||
},
|
||||
turns: {
|
||||
active: 0,
|
||||
queueDepth: 0,
|
||||
completed: 1,
|
||||
failed: 1,
|
||||
},
|
||||
const snapshot = manager.getObservabilitySnapshot(cfg);
|
||||
expect(snapshot.runtimeCache.activeSessions).toBe(1);
|
||||
expectRecordFields(snapshot.turns, {
|
||||
active: 0,
|
||||
queueDepth: 0,
|
||||
completed: 1,
|
||||
failed: 1,
|
||||
});
|
||||
|
||||
const states = extractStatesFromUpserts();
|
||||
@@ -778,13 +823,13 @@ describe("AcpSessionManager", () => {
|
||||
|
||||
await vi.advanceTimersByTimeAsync(4_500);
|
||||
|
||||
await expect(first).rejects.toMatchObject({
|
||||
await expectRejectedRecord(first, {
|
||||
code: "ACP_TURN_FAILED",
|
||||
message: "ACP turn timed out after 1s.",
|
||||
});
|
||||
expect(manager.getObservabilitySnapshot(cfg).runtimeCache.activeSessions).toBe(1);
|
||||
|
||||
await expect(
|
||||
await expectRejectedRecord(
|
||||
manager.runTurn({
|
||||
cfg,
|
||||
sessionKey: "agent:codex:acp:session-b",
|
||||
@@ -792,10 +837,11 @@ describe("AcpSessionManager", () => {
|
||||
mode: "prompt",
|
||||
requestId: "r2",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "ACP_SESSION_INIT_FAILED",
|
||||
message: expect.stringContaining("max concurrent sessions"),
|
||||
});
|
||||
{
|
||||
code: "ACP_SESSION_INIT_FAILED",
|
||||
message: "ACP max concurrent sessions reached (1/1).",
|
||||
},
|
||||
);
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
@@ -1080,13 +1126,11 @@ describe("AcpSessionManager", () => {
|
||||
requestId: "r-binding-restart",
|
||||
});
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey,
|
||||
agent: "codex",
|
||||
resumeSessionId: "acpx-sid-1",
|
||||
}),
|
||||
);
|
||||
expectRecordFields(mockCallArg(runtimeState.ensureSession), {
|
||||
sessionKey,
|
||||
agent: "codex",
|
||||
resumeSessionId: "acpx-sid-1",
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers the persisted agent session id when reopening an ACP runtime after restart", async () => {
|
||||
@@ -1125,13 +1169,11 @@ describe("AcpSessionManager", () => {
|
||||
requestId: "r-binding-restart-gemini",
|
||||
});
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey,
|
||||
agent: "gemini",
|
||||
resumeSessionId: "gemini-sid-1",
|
||||
}),
|
||||
);
|
||||
expectRecordFields(mockCallArg(runtimeState.ensureSession), {
|
||||
sessionKey,
|
||||
agent: "gemini",
|
||||
resumeSessionId: "gemini-sid-1",
|
||||
});
|
||||
});
|
||||
|
||||
it("passes persisted cwd runtime options into ensureSession after restart", async () => {
|
||||
@@ -1165,12 +1207,10 @@ describe("AcpSessionManager", () => {
|
||||
requestId: "r-binding-restart-cwd",
|
||||
});
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey,
|
||||
cwd: "/workspace/project",
|
||||
}),
|
||||
);
|
||||
expectRecordFields(mockCallArg(runtimeState.ensureSession), {
|
||||
sessionKey,
|
||||
cwd: "/workspace/project",
|
||||
});
|
||||
});
|
||||
|
||||
it("passes persisted model runtime options into ensureSession after restart", async () => {
|
||||
@@ -1203,12 +1243,10 @@ describe("AcpSessionManager", () => {
|
||||
requestId: "r-binding-restart-model",
|
||||
});
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey,
|
||||
model: "openai-codex/gpt-5.4",
|
||||
}),
|
||||
);
|
||||
expectRecordFields(mockCallArg(runtimeState.ensureSession), {
|
||||
sessionKey,
|
||||
model: "openai-codex/gpt-5.4",
|
||||
});
|
||||
});
|
||||
|
||||
it("passes persisted thinking runtime options into ensureSession after restart", async () => {
|
||||
@@ -1241,12 +1279,10 @@ describe("AcpSessionManager", () => {
|
||||
requestId: "r-binding-restart-thinking",
|
||||
});
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey,
|
||||
thinking: "high",
|
||||
}),
|
||||
);
|
||||
expectRecordFields(mockCallArg(runtimeState.ensureSession), {
|
||||
sessionKey,
|
||||
thinking: "high",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not resume persisted ACP identity for oneshot sessions after restart", async () => {
|
||||
@@ -1288,7 +1324,7 @@ describe("AcpSessionManager", () => {
|
||||
const ensureInput = runtimeState.ensureSession.mock.calls[0]?.[0] as
|
||||
| { resumeSessionId?: string; mode?: string }
|
||||
| undefined;
|
||||
expect(ensureInput).toMatchObject({
|
||||
expectRecordFields(ensureInput, {
|
||||
sessionKey,
|
||||
agent: "codex",
|
||||
mode: "oneshot",
|
||||
@@ -1375,7 +1411,7 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledTimes(2);
|
||||
expect(runtimeState.ensureSession.mock.calls[0]?.[0]).toMatchObject({
|
||||
expectRecordFields(mockCallArg(runtimeState.ensureSession), {
|
||||
sessionKey,
|
||||
agent: "codex",
|
||||
resumeSessionId: "agent-sid-stale",
|
||||
@@ -1426,7 +1462,7 @@ describe("AcpSessionManager", () => {
|
||||
requestId: "r1",
|
||||
});
|
||||
|
||||
await expect(
|
||||
await expectRejectedRecord(
|
||||
manager.runTurn({
|
||||
cfg: limitedCfg,
|
||||
sessionKey: "agent:codex:acp:session-b",
|
||||
@@ -1434,10 +1470,11 @@ describe("AcpSessionManager", () => {
|
||||
mode: "prompt",
|
||||
requestId: "r2",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "ACP_SESSION_INIT_FAILED",
|
||||
message: expect.stringContaining("max concurrent sessions"),
|
||||
});
|
||||
{
|
||||
code: "ACP_SESSION_INIT_FAILED",
|
||||
message: "ACP max concurrent sessions reached (1/1).",
|
||||
},
|
||||
);
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -1467,17 +1504,18 @@ describe("AcpSessionManager", () => {
|
||||
mode: "persistent",
|
||||
});
|
||||
|
||||
await expect(
|
||||
await expectRejectedRecord(
|
||||
manager.initializeSession({
|
||||
cfg: limitedCfg,
|
||||
sessionKey: "agent:codex:acp:session-b",
|
||||
agent: "codex",
|
||||
mode: "persistent",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "ACP_SESSION_INIT_FAILED",
|
||||
message: expect.stringContaining("max concurrent sessions"),
|
||||
});
|
||||
{
|
||||
code: "ACP_SESSION_INIT_FAILED",
|
||||
message: "ACP max concurrent sessions reached (1/1).",
|
||||
},
|
||||
);
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -1514,13 +1552,11 @@ describe("AcpSessionManager", () => {
|
||||
model: "openai-codex/gpt-5.4",
|
||||
thinking: "high",
|
||||
});
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey: "agent:codex:acp:session-a",
|
||||
model: "openai-codex/gpt-5.4",
|
||||
thinking: "high",
|
||||
}),
|
||||
);
|
||||
expectRecordFields(mockCallArg(runtimeState.ensureSession), {
|
||||
sessionKey: "agent:codex:acp:session-a",
|
||||
model: "openai-codex/gpt-5.4",
|
||||
thinking: "high",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves runtimeOptions cwd when initializeSession cwd is omitted", async () => {
|
||||
@@ -1551,12 +1587,10 @@ describe("AcpSessionManager", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey: "agent:codex:acp:session-cwd-runtime-options",
|
||||
cwd: "/workspace/from-runtime-options",
|
||||
}),
|
||||
);
|
||||
expectRecordFields(mockCallArg(runtimeState.ensureSession), {
|
||||
sessionKey: "agent:codex:acp:session-cwd-runtime-options",
|
||||
cwd: "/workspace/from-runtime-options",
|
||||
});
|
||||
expect(extractRuntimeOptionsFromUpserts()).toContainEqual({
|
||||
cwd: "/workspace/from-runtime-options",
|
||||
});
|
||||
@@ -1760,7 +1794,7 @@ describe("AcpSessionManager", () => {
|
||||
expect(result.runtimeClosed).toBe(true);
|
||||
expect(entry.acp?.state).toBe("idle");
|
||||
expect(entry.acp?.lastError).toBeUndefined();
|
||||
expect(entry.acp?.identity).toMatchObject({
|
||||
expectRecordFields(entry.acp?.identity, {
|
||||
state: "pending",
|
||||
acpxRecordId: sessionKey,
|
||||
source: "status",
|
||||
@@ -1833,11 +1867,9 @@ describe("AcpSessionManager", () => {
|
||||
expect(runtimeState.prepareFreshSession).toHaveBeenCalledWith({
|
||||
sessionKey,
|
||||
});
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey,
|
||||
}),
|
||||
);
|
||||
expectRecordFields(mockCallArg(runtimeState.ensureSession), {
|
||||
sessionKey,
|
||||
});
|
||||
expect(runtimeState.prepareFreshSession.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
runtimeState.ensureSession.mock.invocationCallOrder[0],
|
||||
);
|
||||
@@ -1898,7 +1930,7 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
expect(runtimeState.ensureSession).not.toHaveBeenCalled();
|
||||
expect(runtimeState.close).not.toHaveBeenCalled();
|
||||
expect(entry.acp?.identity).toMatchObject({
|
||||
expectRecordFields(entry.acp?.identity, {
|
||||
state: "pending",
|
||||
acpxRecordId: sessionKey,
|
||||
source: "ensure",
|
||||
@@ -1956,14 +1988,13 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledTimes(2);
|
||||
expect(runtimeState.close).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: "idle-evicted",
|
||||
handle: expect.objectContaining({
|
||||
sessionKey: "agent:codex:acp:session-a",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
const closeInput = mockCallArg(runtimeState.close);
|
||||
expectRecordFields(closeInput, {
|
||||
reason: "idle-evicted",
|
||||
});
|
||||
expectRecordFields(closeInput.handle, {
|
||||
sessionKey: "agent:codex:acp:session-a",
|
||||
});
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
@@ -2001,7 +2032,7 @@ describe("AcpSessionManager", () => {
|
||||
mode: "prompt",
|
||||
requestId: "ok",
|
||||
});
|
||||
await expect(
|
||||
await expectRejectedRecord(
|
||||
manager.runTurn({
|
||||
cfg: baseCfg,
|
||||
sessionKey: "agent:codex:acp:session-1",
|
||||
@@ -2009,9 +2040,8 @@ describe("AcpSessionManager", () => {
|
||||
mode: "prompt",
|
||||
requestId: "fail",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "ACP_TURN_FAILED",
|
||||
});
|
||||
{ code: "ACP_TURN_FAILED" },
|
||||
);
|
||||
|
||||
const snapshot = manager.getObservabilitySnapshot(baseCfg);
|
||||
expect(snapshot.turns.completed).toBe(1);
|
||||
@@ -2038,14 +2068,13 @@ describe("AcpSessionManager", () => {
|
||||
mode: "persistent",
|
||||
}),
|
||||
).rejects.toThrow("disk full");
|
||||
expect(runtimeState.close).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: "init-meta-failed",
|
||||
handle: expect.objectContaining({
|
||||
sessionKey: "agent:codex:acp:session-1",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
const closeInput = mockCallArg(runtimeState.close);
|
||||
expectRecordFields(closeInput, {
|
||||
reason: "init-meta-failed",
|
||||
});
|
||||
expectRecordFields(closeInput.handle, {
|
||||
sessionKey: "agent:codex:acp:session-1",
|
||||
});
|
||||
});
|
||||
|
||||
it("preempts an active turn on cancel and returns to idle state", async () => {
|
||||
@@ -2096,11 +2125,9 @@ describe("AcpSessionManager", () => {
|
||||
await runPromise;
|
||||
|
||||
expect(runtimeState.cancel).toHaveBeenCalledTimes(1);
|
||||
expect(runtimeState.cancel).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: "manual-cancel",
|
||||
}),
|
||||
);
|
||||
expectRecordFields(mockCallArg(runtimeState.cancel), {
|
||||
reason: "manual-cancel",
|
||||
});
|
||||
const states = extractStatesFromUpserts();
|
||||
expect(states).toContain("running");
|
||||
expect(states).toContain("idle");
|
||||
@@ -2167,7 +2194,7 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
|
||||
const manager = new AcpSessionManager();
|
||||
await expect(
|
||||
await expectRejectedRecord(
|
||||
manager.runTurn({
|
||||
cfg: baseCfg,
|
||||
sessionKey: "agent:codex:acp:session-1",
|
||||
@@ -2175,10 +2202,11 @@ describe("AcpSessionManager", () => {
|
||||
mode: "prompt",
|
||||
requestId: "run-1",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "ACP_TURN_FAILED",
|
||||
message: "acpx exited with code 1",
|
||||
});
|
||||
{
|
||||
code: "ACP_TURN_FAILED",
|
||||
message: "acpx exited with code 1",
|
||||
},
|
||||
);
|
||||
|
||||
const states = extractStatesFromUpserts();
|
||||
expect(states).toContain("running");
|
||||
@@ -2206,7 +2234,7 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
|
||||
const manager = new AcpSessionManager();
|
||||
await expect(
|
||||
await expectRejectedRecord(
|
||||
manager.runTurn({
|
||||
cfg: baseCfg,
|
||||
sessionKey: "agent:codex:acp:session-1",
|
||||
@@ -2214,10 +2242,11 @@ describe("AcpSessionManager", () => {
|
||||
mode: "prompt",
|
||||
requestId: "run-1",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "ACP_TURN_FAILED",
|
||||
message: "ACP turn ended without a terminal done event.",
|
||||
});
|
||||
{
|
||||
code: "ACP_TURN_FAILED",
|
||||
message: "ACP turn ended without a terminal done event.",
|
||||
},
|
||||
);
|
||||
|
||||
const states = extractStatesFromUpserts();
|
||||
expect(states).toContain("running");
|
||||
@@ -2241,7 +2270,7 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
|
||||
const manager = new AcpSessionManager();
|
||||
await expect(
|
||||
await expectRejectedRecord(
|
||||
manager.runTurn({
|
||||
cfg: baseCfg,
|
||||
sessionKey: "agent:codex:acp:session-1",
|
||||
@@ -2249,10 +2278,11 @@ describe("AcpSessionManager", () => {
|
||||
mode: "prompt",
|
||||
requestId: "run-1",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "ACP_SESSION_INIT_FAILED",
|
||||
message: "acpx exited with code 1",
|
||||
});
|
||||
{
|
||||
code: "ACP_SESSION_INIT_FAILED",
|
||||
message: "acpx exited with code 1",
|
||||
},
|
||||
);
|
||||
|
||||
const states = extractStatesFromUpserts();
|
||||
expect(states).not.toContain("running");
|
||||
@@ -2395,7 +2425,7 @@ describe("AcpSessionManager", () => {
|
||||
sessionKey,
|
||||
});
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledTimes(2);
|
||||
expect(runtimeState.ensureSession.mock.calls[0]?.[0]).toMatchObject({
|
||||
expectRecordFields(mockCallArg(runtimeState.ensureSession), {
|
||||
sessionKey,
|
||||
resumeSessionId: "acpx-sid-stale",
|
||||
});
|
||||
@@ -2430,11 +2460,9 @@ describe("AcpSessionManager", () => {
|
||||
runtimeMode: "plan",
|
||||
});
|
||||
|
||||
expect(runtimeState.setMode).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mode: "plan",
|
||||
}),
|
||||
);
|
||||
expectMockCallFields(runtimeState.setMode, {
|
||||
mode: "plan",
|
||||
});
|
||||
expect(options.runtimeMode).toBe("plan");
|
||||
const persistedRuntimeModes = extractRuntimeOptionsFromUpserts().map(
|
||||
(entry) => entry?.runtimeMode,
|
||||
@@ -2499,11 +2527,9 @@ describe("AcpSessionManager", () => {
|
||||
requestId: "run-1",
|
||||
});
|
||||
|
||||
expect(runtimeState.setMode).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mode: "plan",
|
||||
}),
|
||||
);
|
||||
expectMockCallFields(runtimeState.setMode, {
|
||||
mode: "plan",
|
||||
});
|
||||
});
|
||||
|
||||
it("reconciles persisted ACP session identifiers from runtime status after a turn", async () => {
|
||||
@@ -2632,7 +2658,7 @@ describe("AcpSessionManager", () => {
|
||||
mode: "oneshot",
|
||||
});
|
||||
|
||||
expect(currentMeta?.identity).toMatchObject({
|
||||
expectRecordFields(currentMeta?.identity, {
|
||||
state: "pending",
|
||||
acpxSessionId: "acpx-oneshot",
|
||||
source: "ensure",
|
||||
@@ -2647,14 +2673,15 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
|
||||
expect(runtimeState.getStatus).toHaveBeenCalledTimes(2);
|
||||
expect(runtimeState.close).toHaveBeenCalledWith({
|
||||
handle: expect.objectContaining({
|
||||
backendSessionId: "acpx-oneshot",
|
||||
agentSessionId: "agent-oneshot",
|
||||
}),
|
||||
const closeInput = mockCallArg(runtimeState.close);
|
||||
expectRecordFields(closeInput, {
|
||||
reason: "oneshot-complete",
|
||||
});
|
||||
expect(currentMeta?.identity).toMatchObject({
|
||||
expectRecordFields(closeInput.handle, {
|
||||
backendSessionId: "acpx-oneshot",
|
||||
agentSessionId: "agent-oneshot",
|
||||
});
|
||||
expectRecordFields(currentMeta?.identity, {
|
||||
state: "resolved",
|
||||
acpxSessionId: "acpx-oneshot",
|
||||
agentSessionId: "agent-oneshot",
|
||||
@@ -2974,35 +3001,25 @@ describe("AcpSessionManager", () => {
|
||||
requestId: "run-1",
|
||||
});
|
||||
|
||||
expect(runtimeState.setMode).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mode: "plan",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "model",
|
||||
value: "openai-codex/gpt-5.4",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "thinking",
|
||||
value: "high",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "approval_policy",
|
||||
value: "strict",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "timeout",
|
||||
value: "120",
|
||||
}),
|
||||
);
|
||||
expectMockCallFields(runtimeState.setMode, {
|
||||
mode: "plan",
|
||||
});
|
||||
expectMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "model",
|
||||
value: "openai-codex/gpt-5.4",
|
||||
});
|
||||
expectMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "thinking",
|
||||
value: "high",
|
||||
});
|
||||
expectMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "approval_policy",
|
||||
value: "strict",
|
||||
});
|
||||
expectMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "timeout",
|
||||
value: "120",
|
||||
});
|
||||
});
|
||||
|
||||
it("maps persisted thinking runtime options to advertised effort config keys before running turns", async () => {
|
||||
@@ -3035,17 +3052,13 @@ describe("AcpSessionManager", () => {
|
||||
requestId: "run-1",
|
||||
});
|
||||
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "effort",
|
||||
value: "high",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.setConfigOption).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "thinking",
|
||||
}),
|
||||
);
|
||||
expectMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "effort",
|
||||
value: "high",
|
||||
});
|
||||
expectNoMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "thinking",
|
||||
});
|
||||
});
|
||||
|
||||
it("maps persisted runtime options to backend-advertised aliases before running turns", async () => {
|
||||
@@ -3081,39 +3094,27 @@ describe("AcpSessionManager", () => {
|
||||
requestId: "run-1",
|
||||
});
|
||||
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "thought_level",
|
||||
value: "high",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "permissions",
|
||||
value: "strict",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "timeout_seconds",
|
||||
value: "120",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.setConfigOption).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "thinking",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.setConfigOption).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "approval_policy",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.setConfigOption).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "timeout",
|
||||
}),
|
||||
);
|
||||
expectMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "thought_level",
|
||||
value: "high",
|
||||
});
|
||||
expectMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "permissions",
|
||||
value: "strict",
|
||||
});
|
||||
expectMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "timeout_seconds",
|
||||
value: "120",
|
||||
});
|
||||
expectNoMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "thinking",
|
||||
});
|
||||
expectNoMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "approval_policy",
|
||||
});
|
||||
expectNoMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "timeout",
|
||||
});
|
||||
});
|
||||
|
||||
it("re-ensures runtime handles after cwd runtime option updates", async () => {
|
||||
@@ -3180,13 +3181,10 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledTimes(2);
|
||||
expect(runtimeState.ensureSession).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
sessionKey,
|
||||
cwd: "/workspace/next",
|
||||
}),
|
||||
);
|
||||
expectRecordFields(mockCallArg(runtimeState.ensureSession, 1), {
|
||||
sessionKey,
|
||||
cwd: "/workspace/next",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns unsupported-control error when backend does not support set_config_option", async () => {
|
||||
@@ -3209,16 +3207,15 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
|
||||
const manager = new AcpSessionManager();
|
||||
await expect(
|
||||
await expectRejectedRecord(
|
||||
manager.setSessionConfigOption({
|
||||
cfg: baseCfg,
|
||||
sessionKey: "agent:codex:acp:session-1",
|
||||
key: "model",
|
||||
value: "gpt-5.4",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "ACP_BACKEND_UNSUPPORTED_CONTROL",
|
||||
});
|
||||
{ code: "ACP_BACKEND_UNSUPPORTED_CONTROL" },
|
||||
);
|
||||
});
|
||||
|
||||
it("maps explicit thinking config updates to advertised effort keys", async () => {
|
||||
@@ -3245,12 +3242,10 @@ describe("AcpSessionManager", () => {
|
||||
value: "high",
|
||||
});
|
||||
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "effort",
|
||||
value: "high",
|
||||
}),
|
||||
);
|
||||
expectMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "effort",
|
||||
value: "high",
|
||||
});
|
||||
expect(nextOptions).toEqual({ thinking: "high" });
|
||||
});
|
||||
|
||||
@@ -3284,12 +3279,10 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
|
||||
expect(runtimeState.getStatus).toHaveBeenCalled();
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "effort",
|
||||
value: "high",
|
||||
}),
|
||||
);
|
||||
expectMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "effort",
|
||||
value: "high",
|
||||
});
|
||||
expect(nextOptions).toEqual({ thinking: "high" });
|
||||
});
|
||||
|
||||
@@ -3317,12 +3310,10 @@ describe("AcpSessionManager", () => {
|
||||
value: "high",
|
||||
});
|
||||
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "effort",
|
||||
value: "high",
|
||||
}),
|
||||
);
|
||||
expectMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "effort",
|
||||
value: "high",
|
||||
});
|
||||
expect(nextOptions).toEqual({ thinking: "high" });
|
||||
});
|
||||
|
||||
@@ -3350,12 +3341,10 @@ describe("AcpSessionManager", () => {
|
||||
value: "strict",
|
||||
});
|
||||
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "permission_mode",
|
||||
value: "strict",
|
||||
}),
|
||||
);
|
||||
expectMockCallFields(runtimeState.setConfigOption, {
|
||||
key: "permission_mode",
|
||||
value: "strict",
|
||||
});
|
||||
expect(nextOptions).toEqual({ permissionProfile: "strict" });
|
||||
});
|
||||
|
||||
@@ -3372,27 +3361,25 @@ describe("AcpSessionManager", () => {
|
||||
});
|
||||
|
||||
const manager = new AcpSessionManager();
|
||||
await expect(
|
||||
await expectRejectedRecord(
|
||||
manager.setSessionConfigOption({
|
||||
cfg: baseCfg,
|
||||
sessionKey: "agent:codex:acp:session-1",
|
||||
key: "timeout",
|
||||
value: "not-a-number",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "ACP_INVALID_RUNTIME_OPTION",
|
||||
});
|
||||
{ code: "ACP_INVALID_RUNTIME_OPTION" },
|
||||
);
|
||||
expect(runtimeState.setConfigOption).not.toHaveBeenCalled();
|
||||
|
||||
await expect(
|
||||
await expectRejectedRecord(
|
||||
manager.updateSessionRuntimeOptions({
|
||||
cfg: baseCfg,
|
||||
sessionKey: "agent:codex:acp:session-1",
|
||||
patch: { cwd: "relative/path" },
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "ACP_INVALID_RUNTIME_OPTION",
|
||||
});
|
||||
{ code: "ACP_INVALID_RUNTIME_OPTION" },
|
||||
);
|
||||
});
|
||||
|
||||
it("can close and clear metadata when backend is unavailable", async () => {
|
||||
|
||||
Reference in New Issue
Block a user