

























@@ -252,11 +252,15 @@ function rateLimitsUpdated(resetsAt: number): ProjectorNotification {
252252}
253253254254function turnCompleted(items: unknown[] = []): ProjectorNotification {
255+return turnWithStatus("completed", items);
256+}
257+258+function turnWithStatus(status: string, items: unknown[] = []): ProjectorNotification {
255259return {
256260method: "turn/completed",
257261params: {
258262threadId: THREAD_ID,
259-turn: { id: TURN_ID, status: "completed", items },
263+turn: { id: TURN_ID, status, items },
260264},
261265} as ProjectorNotification;
262266}
@@ -588,6 +592,79 @@ describe("CodexAppServerEventProjector", () => {
588592expect(result.lastAssistant).toBeUndefined();
589593});
590594595+it("does not treat app-server interrupted status as a user cancellation by itself", async () => {
596+const projector = await createProjector();
597+598+await projector.handleNotification(turnWithStatus("interrupted"));
599+600+const result = projector.buildResult(buildEmptyToolTelemetry());
601+602+expect(result.aborted).toBe(false);
603+expect(result.externalAbort).toBe(false);
604+expect(result.timedOut).toBe(false);
605+expect(result.promptError).toBeNull();
606+expect(result.assistantTexts).toEqual([]);
607+expect(result.lastAssistant).toBeUndefined();
608+});
609+610+it("keeps sparse successful bash output eligible for the no-visible-answer guard", async () => {
611+const projector = await createProjector();
612+613+await projector.handleNotification(
614+turnWithStatus("interrupted", [
615+{
616+type: "commandExecution",
617+id: "cmd-empty-output",
618+command:
619+"ps -eo pid,ppid,stat,cmd | rg 'venv-roadmap|pytest|run_security_contract_validation|validate_public_install|git push|apply_patch' || true",
620+cwd: "/workspace",
621+processId: null,
622+source: "agent",
623+status: "completed",
624+commandActions: [],
625+aggregatedOutput: "",
626+exitCode: 0,
627+durationMs: 42,
628+},
629+]),
630+);
631+632+const result = projector.buildResult(buildEmptyToolTelemetry());
633+634+expect(result.aborted).toBe(false);
635+expect(result.assistantTexts).toEqual([]);
636+expect(result.toolMetas).toEqual([
637+expect.objectContaining({ toolName: "bash", meta: expect.stringContaining("workspace") }),
638+]);
639+});
640+641+it("keeps explicit cancellation marked aborted for interrupted tool-only turns", async () => {
642+const projector = await createProjector();
643+projector.markAborted();
644+645+await projector.handleNotification(
646+turnWithStatus("interrupted", [
647+{
648+type: "commandExecution",
649+id: "cmd-cancelled",
650+command: "/bin/bash -lc true",
651+cwd: "/workspace",
652+processId: null,
653+source: "agent",
654+status: "completed",
655+commandActions: [],
656+aggregatedOutput: "",
657+exitCode: 0,
658+durationMs: 12,
659+},
660+]),
661+);
662+663+const result = projector.buildResult(buildEmptyToolTelemetry());
664+expect(result.aborted).toBe(true);
665+expect(result.assistantTexts).toEqual([]);
666+});
667+591668it("does not fail a completed reply after a retryable app-server error notification", async () => {
592669const projector = await createProjector();
593670此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。