feat(design): render assistant activity progress
This commit is contained in:
1 parent
da29294979
commit
81b524a02c
9 files changed
+2066
-80
No files matched your search
@@ -390,6 +390,198 @@ describe('Works Square V2 Design Workspace adapter', () => {
|
||||
subscription.close();
|
||||
});
|
||||
|
||||
it('normalizes bounded public AI activity without exposing provider details', async () => {
|
||||
const socket = createSocket();
|
||||
const fetchMock = vi.fn(async (input: string | URL) => {
|
||||
const url = String(input);
|
||||
if (url.endsWith('/stream-tickets')) {
|
||||
return jsonResponse({ stream_url: 'https://works.example/api/agents/stream/ticket-1' });
|
||||
}
|
||||
throw new Error(`Unexpected URL: ${url}`);
|
||||
});
|
||||
const module = new WorksSquareDesignWorkspace({
|
||||
apiBaseUrl: 'https://works.example',
|
||||
fetchImpl: fetchMock as unknown as typeof fetch,
|
||||
webSocketFactory: async () => {
|
||||
setTimeout(() => socket.onopen?.(), 0);
|
||||
return { socket };
|
||||
},
|
||||
});
|
||||
const subscription = await module.openWorkspaceEvents({
|
||||
workspaceId: 'workspace-1',
|
||||
sessionId: 'session-1',
|
||||
});
|
||||
const nextEvent = subscription.events[Symbol.asyncIterator]().next();
|
||||
|
||||
socket.onmessage?.({
|
||||
data: JSON.stringify({
|
||||
type: 'event',
|
||||
event: {
|
||||
session_id: 'session-1',
|
||||
sequence: 8,
|
||||
runtime: 'design',
|
||||
type: 'design.assistant.progress',
|
||||
schema_version: 1,
|
||||
payload: {
|
||||
workspace_id: 'workspace-1',
|
||||
direction_id: 'direction-1',
|
||||
client_operation_id: 'operation-chat-1',
|
||||
stage: 'reviewing_context',
|
||||
message: 'RAW provider chain-of-thought must not cross Main',
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await expect(nextEvent).resolves.toEqual({
|
||||
done: false,
|
||||
value: {
|
||||
id: 'session-1:8',
|
||||
type: 'design.assistant.progress',
|
||||
workspaceId: 'workspace-1',
|
||||
directionId: 'direction-1',
|
||||
clientOperationId: 'operation-chat-1',
|
||||
stage: 'reviewing_context',
|
||||
message: '正在把它和前面的设计想法放在一起看…',
|
||||
},
|
||||
});
|
||||
subscription.close();
|
||||
});
|
||||
|
||||
it('carries the Gateway operation identity with the canonical Design turn', async () => {
|
||||
const socket = createSocket();
|
||||
const fetchMock = vi.fn(async (input: string | URL) => {
|
||||
const url = String(input);
|
||||
if (url.endsWith('/stream-tickets')) {
|
||||
return jsonResponse({ stream_url: 'https://works.example/api/agents/stream/ticket-1' });
|
||||
}
|
||||
throw new Error(`Unexpected URL: ${url}`);
|
||||
});
|
||||
const module = new WorksSquareDesignWorkspace({
|
||||
apiBaseUrl: 'https://works.example',
|
||||
fetchImpl: fetchMock as unknown as typeof fetch,
|
||||
webSocketFactory: async () => {
|
||||
setTimeout(() => socket.onopen?.(), 0);
|
||||
return { socket };
|
||||
},
|
||||
});
|
||||
const subscription = await module.openWorkspaceEvents({
|
||||
workspaceId: 'workspace-1',
|
||||
sessionId: 'session-1',
|
||||
});
|
||||
const nextEvent = subscription.events[Symbol.asyncIterator]().next();
|
||||
|
||||
socket.onmessage?.({
|
||||
data: JSON.stringify({
|
||||
type: 'event',
|
||||
event: {
|
||||
session_id: 'session-1',
|
||||
sequence: 9,
|
||||
runtime: 'design',
|
||||
type: 'design.direction.updated',
|
||||
client_command_id: 'operation-chat-1',
|
||||
schema_version: 1,
|
||||
payload: {
|
||||
replayed: false,
|
||||
operation: {
|
||||
operation_kind: 'chat',
|
||||
interaction_id: 'interaction-2',
|
||||
base_direction_revision: 4,
|
||||
new_direction_revision: 5,
|
||||
raw_turn_sequence: 2,
|
||||
specification_revision: 3,
|
||||
specification_revision_id: 'specification-revision-3',
|
||||
workspace_view_revision: 7,
|
||||
specification_revision_created: false,
|
||||
meaning_changed: false,
|
||||
change_set: { interaction_id: 'interaction-2', changes: [] },
|
||||
turn_id: 'turn-2',
|
||||
assistant_message: '你希望它更像漫画还是电影?',
|
||||
created_decision_prompt_ids: [],
|
||||
resolved_decision_prompt_id: null,
|
||||
superseded_decision_prompt_ids: [],
|
||||
superseded_quote_count: 0,
|
||||
quote_id: null,
|
||||
generation_task_id: null,
|
||||
},
|
||||
form: { ...serverForm, raw_turn_sequence: 2, direction_revision: 5 },
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await expect(nextEvent).resolves.toMatchObject({
|
||||
done: false,
|
||||
value: {
|
||||
id: 'session-1:9',
|
||||
type: 'design.direction.updated',
|
||||
clientOperationId: 'operation-chat-1',
|
||||
operation: { turnId: 'turn-2' },
|
||||
},
|
||||
});
|
||||
subscription.close();
|
||||
});
|
||||
|
||||
it('normalizes Gateway command terminal events by their stable operation identity', async () => {
|
||||
const socket = createSocket();
|
||||
const fetchMock = vi.fn(async (input: string | URL) => {
|
||||
const url = String(input);
|
||||
if (url.endsWith('/stream-tickets')) {
|
||||
return jsonResponse({ stream_url: 'https://works.example/api/agents/stream/ticket-1' });
|
||||
}
|
||||
throw new Error(`Unexpected URL: ${url}`);
|
||||
});
|
||||
const module = new WorksSquareDesignWorkspace({
|
||||
apiBaseUrl: 'https://works.example',
|
||||
fetchImpl: fetchMock as unknown as typeof fetch,
|
||||
webSocketFactory: async () => {
|
||||
setTimeout(() => socket.onopen?.(), 0);
|
||||
return { socket };
|
||||
},
|
||||
});
|
||||
const subscription = await module.openWorkspaceEvents({
|
||||
workspaceId: 'workspace-1',
|
||||
sessionId: 'session-1',
|
||||
});
|
||||
const iterator = subscription.events[Symbol.asyncIterator]();
|
||||
const nextEvent = iterator.next();
|
||||
|
||||
socket.onmessage?.({
|
||||
data: JSON.stringify({
|
||||
type: 'event',
|
||||
event: {
|
||||
session_id: 'session-1',
|
||||
sequence: 9,
|
||||
runtime: 'design',
|
||||
type: 'command.failed',
|
||||
client_command_id: 'operation-chat-1',
|
||||
schema_version: 1,
|
||||
terminal: true,
|
||||
payload: {
|
||||
error: {
|
||||
code: 'design_reasoner_invalid',
|
||||
message: 'private technical detail',
|
||||
retryable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await expect(nextEvent).resolves.toEqual({
|
||||
done: false,
|
||||
value: {
|
||||
id: 'session-1:9',
|
||||
type: 'command.failed',
|
||||
workspaceId: 'workspace-1',
|
||||
clientOperationId: 'operation-chat-1',
|
||||
outcome: 'failed',
|
||||
errorCode: 'design_reasoner_invalid',
|
||||
},
|
||||
});
|
||||
subscription.close();
|
||||
});
|
||||
|
||||
it('refreshes authentication once after a 401', async () => {
|
||||
getTokenMock.mockResolvedValueOnce('expired-token').mockResolvedValueOnce('fresh-token');
|
||||
const fetchMock = vi.fn(async (_input: string | URL, init?: RequestInit) => {
|
||||
|
||||
Reference in new issue
Block a user