openWrittenDocument.test.ts4.3 KBView on GitHub
/**
 * The one rule for a document the agent just wrote: show it if the panel is IDLE, never if the
 * user already has something open there.
 *
 * These are mostly negative tests, and deliberately so. The behaviour this replaced was a
 * `display-document` tool the agent called by hand, which took the panel whenever it fired —
 * so the thing worth pinning is not that a doc can open, it is that a doc opening cannot take
 * away what the user was reading.
 */
import { openWrittenDocumentIfPanelIdle } from '@/modules/cedar-os/src/store/agentConnection/responseProcessors/openWrittenDocument';
import { DEFAULT_THREAD_ID } from '@/store/messages/MessageTypes';
import { useCedarStore } from '@/store/CedarStore';

const T1 = DEFAULT_THREAD_ID;
const T2 = 'thread-2';

function seed() {
  useCedarStore.setState((state) => ({
    ...state,
    threadMap: {
      [T1]: { id: T1, messages: [], selectedArtifact: null },
      [T2]: { id: T2, messages: [], selectedArtifact: null },
    },
    mainThreadId: T1,
    activeThreadId: T1,
    messages: [],
    conversationOpenFile: null,
  }));
}

const store = () => useCedarStore.getState();

describe('a written document opens only into an idle panel', () => {
  beforeEach(seed);

  it('opens a standalone doc when nothing is open', () => {
    openWrittenDocumentIfPanelIdle(store(), {
      documentId: 'doc_1',
      path: 'thread/thread-1/decagon-research',
      threadId: T1,
    });

    expect(store().getDisplayArtifact()).toEqual({ kind: 'file', id: 'doc_1' });
  });

  it('leaves an open conversation alone — the chip is how the user gets to the doc', () => {
    store().setSelectedArtifact({ kind: 'conversation', id: 'conv_a' });

    openWrittenDocumentIfPanelIdle(store(), {
      documentId: 'doc_1',
      path: 'thread/thread-1/decagon-research',
      threadId: T1,
    });

    expect(store().getDisplayArtifact()).toEqual({ kind: 'conversation', id: 'conv_a' });
  });

  it('leaves an open document alone too — a second write must not swap what is on screen', () => {
    store().setSelectedArtifact({ kind: 'file', id: 'doc_already_open' });

    openWrittenDocumentIfPanelIdle(store(), {
      documentId: 'doc_2',
      path: 'user/notes/scratch',
      threadId: T1,
    });

    expect(store().getDisplayArtifact()).toEqual({ kind: 'file', id: 'doc_already_open' });
  });

  it('a conversation-scoped doc opens inside its conversation, not as a bare file', () => {
    openWrittenDocumentIfPanelIdle(store(), {
      documentId: 'doc_3',
      path: 'conversation/conv_x/research/pricing',
      threadId: T1,
    });

    expect(store().activeConversationId).toBe('conv_x');
    expect(store().conversationOpenFile).toBe('doc_3');
  });

  it('ignores a buffer doc — a tool-output dump is not a deliverable', () => {
    openWrittenDocumentIfPanelIdle(store(), {
      documentId: 'doc_4',
      path: 'thread/thread-1/buffer/web-search',
      threadId: T1,
    });

    expect(store().getDisplayArtifact()).toBeNull();
  });

  it('a background thread cannot redraw the panel in front of the user', () => {
    // T2 is idle, but T1 is the chat being looked at and it has a conversation open.
    store().setSelectedArtifact({ kind: 'conversation', id: 'conv_a' });

    openWrittenDocumentIfPanelIdle(store(), {
      documentId: 'doc_5',
      path: 'user/notes/other',
      threadId: T1,
    });

    expect(store().getDisplayArtifact()).toEqual({ kind: 'conversation', id: 'conv_a' });
  });

  it("a background thread cannot fill the active thread's IDLE panel either", () => {
    // The case the check above misses: nothing is open, so "is the panel free" says yes — but
    // the write came from T2, and `setSelectedArtifact` writes the ACTIVE thread's slot, so
    // T2's document would land in front of a user who is reading T1.
    openWrittenDocumentIfPanelIdle(store(), {
      documentId: 'doc_6',
      path: 'user/notes/background',
      threadId: T2,
    });

    expect(store().getDisplayArtifact()).toBeNull();
  });

  it('a conversation-scoped write from a background thread does not move the user either', () => {
    openWrittenDocumentIfPanelIdle(store(), {
      documentId: 'doc_7',
      path: 'conversation/conv_y/research/pricing',
      threadId: T2,
    });

    expect(store().activeConversationId).not.toBe('conv_y');
    expect(store().conversationOpenFile).toBeNull();
  });
});