1999 lines · 62.9 KB · TypeScript
RawDownload
import { describe, expect, it } from "vite-plus/test";import { codexFeedbackMessage } from "@t3tools/client-runtime/state/threads"; import {  EventId,  MessageId,  ProjectId,  ProviderInstanceId,  ThreadId,  TurnId,  type OrchestrationThread,  type OrchestrationThreadActivity,} from "@t3tools/contracts"; import {  buildPendingUserInputAnswers,  buildThreadFeed,  derivePendingApprovals,  deriveThreadFeedPresentation,  isPendingUserInputOptionSelected,  setPendingUserInputCustomAnswer,  togglePendingUserInputOptionSelection,  type ThreadFeedActivity,  type ThreadFeedEntry,} from "./threadActivity"; describe("Codex feedback pseudo-messages", () => {  it("keeps pending and completed feedback messages in the mobile thread body", () => {    const pending = {      id: MessageId.make("feedback-command"),      command: "/feedback The agent stopped early.",      createdAt: "2026-08-23T00:00:00.000Z",      status: "uploading" as const,    };    const entries = [codexFeedbackMessage(pending), codexFeedbackMessage(pending, "assistant")].map(      (message) => ({        type: "message" as const,        id: message.id,        createdAt: message.createdAt,        message,      }),    );     expect(deriveThreadFeedPresentation(entries, null, new Set())).toEqual(entries);    expect(entries[1]?.message.text).toBe("Sending feedback to OpenAI...");     const completed = codexFeedbackMessage(      { ...pending, status: "sent", feedbackId: "codex-thread-1" },      "assistant",    );    expect(completed.text).toContain("codex-thread-1");  });}); const singleSelectQuestion = {  id: "runtime",  header: "Runtime",  question: "Which runtime should be used?",  options: [    { label: "Go", description: "One binary" },    { label: "Node.js", description: "Reuse TypeScript" },  ],  multiSelect: false,} as const; const multiSelectQuestion = {  id: "scope",  header: "Scope",  question: "Which data should be collected?",  options: [    { label: "Orders", description: "Receipts" },    { label: "Listings", description: "Inventory" },  ],  multiSelect: true,} as const; describe("pending user input answers", () => {  it("replaces single-select options and toggles multi-select options", () => {    expect(      togglePendingUserInputOptionSelection(        singleSelectQuestion,        { selectedOptionLabels: ["Go"] },        "Node.js",      ),    ).toEqual({ customAnswer: "", selectedOptionLabels: ["Node.js"] });     const orders = togglePendingUserInputOptionSelection(multiSelectQuestion, undefined, "Orders");    const ordersAndListings = togglePendingUserInputOptionSelection(      multiSelectQuestion,      orders,      "Listings",    );    expect(ordersAndListings).toEqual({      customAnswer: "",      selectedOptionLabels: ["Orders", "Listings"],    });    expect(      togglePendingUserInputOptionSelection(multiSelectQuestion, ordersAndListings, "Orders"),    ).toEqual({ customAnswer: "", selectedOptionLabels: ["Listings"] });     const paddedOrders = togglePendingUserInputOptionSelection(      multiSelectQuestion,      undefined,      "  Orders  ",    );    expect(paddedOrders).toEqual({ customAnswer: "", selectedOptionLabels: ["Orders"] });    expect(      togglePendingUserInputOptionSelection(multiSelectQuestion, paddedOrders, "  Orders  "),    ).toEqual({ customAnswer: "" });  });   it("builds array answers for multi-select questions", () => {    expect(      buildPendingUserInputAnswers([singleSelectQuestion, multiSelectQuestion], {        runtime: { selectedOptionLabels: ["Go"] },        scope: { selectedOptionLabels: ["Orders", "Listings"] },      }),    ).toEqual({      runtime: "Go",      scope: ["Orders", "Listings"],    });  });   it("clears selected options while a custom answer is active", () => {    expect(      setPendingUserInputCustomAnswer(        { selectedOptionLabels: ["Orders", "Listings"] },        "Orders first",      ),    ).toEqual({ customAnswer: "Orders first" });  });   it("matches selected chips against normalized option labels", () => {    expect(      isPendingUserInputOptionSelected({ selectedOptionLabels: ["Orders"] }, "  Orders  "),    ).toBe(true);    expect(      isPendingUserInputOptionSelected(        { selectedOptionLabels: ["Orders"], customAnswer: "Orders first" },        "  Orders  ",      ),    ).toBe(false);  });}); describe("pending approvals", () => {  it("keeps app access approvals and persistence choices from remote environments", () => {    const options = [      { decision: "decline", label: "Decline" },      { decision: "acceptAlways", label: "Always allow Safari" },      { decision: "accept", label: "Approve" },    ];    const activity = makeActivity({      id: EventId.make("approval-safari"),      kind: "approval.requested",      summary: "App access approval requested",      createdAt: "2026-08-24T00:00:00.000Z",      payload: {        requestId: "req-safari",        requestType: "mcp_elicitation_approval",        detail: "Allow ChatGPT to use Safari?",        appName: "Safari",        options,      },    });     expect(derivePendingApprovals([activity])).toEqual([      {        requestId: "req-safari",        requestKind: "mcp-elicitation",        createdAt: "2026-08-24T00:00:00.000Z",        detail: "Allow ChatGPT to use Safari?",        appName: "Safari",        options,      },    ]);  });   it("removes an app access approval after a remote client rejects it", () => {    const requested = makeActivity({      id: EventId.make("approval-safari-open"),      kind: "approval.requested",      summary: "App access approval requested",      createdAt: "2026-08-24T00:00:00.000Z",      payload: { requestId: "req-safari", requestKind: "mcp-elicitation" },    });    const resolved = makeActivity({      id: EventId.make("approval-safari-resolved"),      kind: "approval.resolved",      summary: "Approval resolved",      createdAt: "2026-08-24T00:00:01.000Z",      payload: { requestId: "req-safari", decision: "decline" },    });     expect(derivePendingApprovals([requested, resolved])).toEqual([]);  });}); function makeActivity(  input: Partial<OrchestrationThreadActivity> &    Pick<OrchestrationThreadActivity, "id" | "kind" | "summary" | "createdAt">,): OrchestrationThreadActivity {  return {    tone: "info",    payload: {},    turnId: null,    ...input,  };} function makeThread(  input: Partial<OrchestrationThread> & Pick<OrchestrationThread, "id" | "projectId" | "title">,): OrchestrationThread {  return {    modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" },    runtimeMode: "full-access",    interactionMode: "default",    branch: null,    worktreePath: null,    latestTurn: null,    createdAt: "2026-04-01T00:00:00.000Z",    updatedAt: "2026-04-01T00:00:00.000Z",    archivedAt: null,    deletedAt: null,    messages: [],    proposedPlans: [],    activities: [],    checkpoints: [],    session: null,    ...input,    settledOverride: input.settledOverride ?? null,    settledAt: input.settledAt ?? null,  };} describe("buildThreadFeed", () => {  it("keeps long Claude commands expandable without repeating them in full detail", () => {    const command = `printf 'first line\nsecond line'\n&& printf done`;    const thread = makeThread({      id: ThreadId.make("thread-long-command"),      projectId: ProjectId.make("project-1"),      title: "Long command",      activities: [        makeActivity({          id: EventId.make("long-command"),          kind: "tool.completed",          tone: "tool",          summary: "Command run",          createdAt: "2026-09-01T00:00:00.000Z",          payload: {            itemType: "command_execution",            title: "Command run",            detail: `Bash: ${command}`,            data: { toolName: "Bash", command },          },        }),      ],    });     const [group] = buildThreadFeed(thread);    expect(group?.type).toBe("activity-group");    if (group?.type !== "activity-group") return;    const [row] = group.activities;    expect(row).toMatchObject({ detail: command, canExpand: true });    expect(row?.getFullDetail()).toBe(command);    expect(row?.getCopyText()).toBe(`Command run\n${command}`);  });   it("keeps command output when it equals the displayed command", () => {    const command = "printf hello";    const thread = makeThread({      id: ThreadId.make("thread-matching-command-output"),      projectId: ProjectId.make("project-1"),      title: "Matching output",      activities: [        makeActivity({          id: EventId.make("matching-command-output"),          kind: "tool.completed",          tone: "tool",          summary: "Command run",          createdAt: "2026-09-01T00:00:00.000Z",          payload: {            itemType: "command_execution",            title: "Command run",            detail: `Bash: ${command}`,            data: { toolName: "Bash", command, rawOutput: { content: command } },          },        }),      ],    });     const [group] = buildThreadFeed(thread);    expect(group?.type).toBe("activity-group");    if (group?.type !== "activity-group") return;    const [row] = group.activities;    expect(row?.detail).toBe(command);    expect(row?.getFullDetail()).toBe(`${command}\n\n${command}`);    expect(row?.getCopyText()).toBe(`Command run\n${command}\n\n${command}`);  });   it("keeps OpenCode detail-only output when it equals the command", () => {    const command = "printf hello";    const thread = makeThread({      id: ThreadId.make("thread-opencode-detail-output"),      projectId: ProjectId.make("project-1"),      title: "OpenCode detail output",      activities: [        makeActivity({          id: EventId.make("opencode-detail-output"),          kind: "tool.completed",          tone: "tool",          summary: "bash",          createdAt: "2026-09-01T00:00:00.000Z",          payload: {            itemType: "command_execution",            title: "bash",            detail: command,            data: { command },          },        }),      ],    });     const [group] = buildThreadFeed(thread);    expect(group?.type).toBe("activity-group");    if (group?.type !== "activity-group") return;    const [row] = group.activities;    expect(row?.workEntry.detail).toBe(command);    expect(row?.getFullDetail()).toBe(`${command}\n\n${command}`);  });   it("drops a truncated Claude echo of a long command", () => {    const command = `git add -A && git commit -m "${"x".repeat(200)}"`;    const thread = makeThread({      id: ThreadId.make("thread-truncated-echo"),      projectId: ProjectId.make("project-1"),      title: "Truncated echo",      activities: [        makeActivity({          id: EventId.make("truncated-echo"),          kind: "tool.completed",          tone: "tool",          summary: "Command run",          createdAt: "2026-09-01T00:00:00.000Z",          payload: {            itemType: "command_execution",            title: "Command run",            detail: `Bash: ${command}`.slice(0, 177) + "...",            data: { toolName: "Bash", command },          },        }),      ],    });     const [group] = buildThreadFeed(thread);    expect(group?.type).toBe("activity-group");    if (group?.type !== "activity-group") return;    const [row] = group.activities;    expect(row?.workEntry.detail).toBeUndefined();    expect(row?.getFullDetail()).toBe(command);  });   it("drops an ACP command echo when the update omits the tool kind", () => {    const command = "pnpm test";    const thread = makeThread({      id: ThreadId.make("thread-acp-no-kind"),      projectId: ProjectId.make("project-1"),      title: "ACP no kind",      activities: [        makeActivity({          id: EventId.make("acp-no-kind"),          kind: "tool.completed",          tone: "tool",          summary: "Terminal",          createdAt: "2026-09-01T00:00:00.000Z",          payload: {            itemType: "command_execution",            title: "Terminal",            detail: command,            data: { toolCallId: "tool-1", command },          },        }),      ],    });     const [group] = buildThreadFeed(thread);    expect(group?.type).toBe("activity-group");    if (group?.type !== "activity-group") return;    const [row] = group.activities;    expect(row?.workEntry.detail).toBeUndefined();    expect(row?.getFullDetail()).toBe(command);  });   it("drops ACP command metadata when detail only repeats the command", () => {    const command = "printf hello";    const thread = makeThread({      id: ThreadId.make("thread-acp-command-detail"),      projectId: ProjectId.make("project-1"),      title: "ACP command detail",      activities: [        makeActivity({          id: EventId.make("acp-command-detail"),          kind: "tool.completed",          tone: "tool",          summary: "Terminal",          createdAt: "2026-09-01T00:00:00.000Z",          payload: {            itemType: "command_execution",            title: "Terminal",            detail: command,            data: { kind: "execute", command },          },        }),      ],    });     const [group] = buildThreadFeed(thread);    expect(group?.type).toBe("activity-group");    if (group?.type !== "activity-group") return;    const [row] = group.activities;    expect(row?.workEntry.detail).toBeUndefined();    expect(row?.getFullDetail()).toBe(command);  });   it("does not show command output when the command input is missing", () => {    const thread = makeThread({      id: ThreadId.make("thread-command-without-input"),      projectId: ProjectId.make("project-1"),      title: "Missing command input",      activities: [        makeActivity({          id: EventId.make("command-without-input"),          kind: "tool.completed",          tone: "tool",          summary: "Command run",          createdAt: "2026-09-01T00:00:00.000Z",          payload: {            itemType: "command_execution",            title: "Command run",            data: { rawOutput: { content: "output without command metadata" } },          },        }),      ],    });     const [group] = buildThreadFeed(thread);    expect(group?.type).toBe("activity-group");    if (group?.type !== "activity-group") return;    expect(group.activities[0]?.detail).toBeNull();    expect(group.activities[0]?.getFullDetail()).toBeNull();  });   it("keeps setup failures visible without routine setup notices before or after a turn", () => {    const thread = makeThread({      id: ThreadId.make("thread-worktree-setup"),      projectId: ProjectId.make("project-1"),      title: "Worktree setup",      activities: [        makeActivity({          id: EventId.make("setup-requested"),          kind: "setup-script.requested",          summary: "Starting setup script",          createdAt: "2026-08-30T00:00:00.000Z",        }),        makeActivity({          id: EventId.make("setup-started"),          kind: "setup-script.started",          summary: "Setup script started",          createdAt: "2026-08-30T00:00:01.000Z",        }),        makeActivity({          id: EventId.make("setup-failed"),          kind: "setup-script.failed",          summary: "Setup script failed to start",          createdAt: "2026-08-30T00:00:02.000Z",          tone: "error",          payload: { detail: "Setup command was not found" },        }),      ],    });    const latestTurn = {      turnId: TurnId.make("turn-after-setup"),      state: "running" as const,      requestedAt: "2026-08-30T00:00:03.000Z",      startedAt: "2026-08-30T00:00:04.000Z",      completedAt: null,      assistantMessageId: null,    };     for (const currentTurn of [null, latestTurn]) {      const feed = buildThreadFeed({ ...thread, latestTurn: currentTurn });      expect(feed).toMatchObject([        {          type: "activity-group",          activities: [{ id: "setup-failed", status: "failure" }],        },      ]);      const group = feed[0];      if (group?.type !== "activity-group") throw new Error("Expected the setup failure group");      expect(group.activities[0]?.getCopyText()).toContain("Setup command was not found");

Lines 1–500 of 1999.