Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions apps/desktop/e2e/workhub-reconstruction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ test('WorkHub rebuilds Session conversation after navigating away and back', asy
await window.maka.settings.updateClient({ workHub: { enabled: true } });
});
await expect(page.getByRole('main', { name: 'WorkHub' })).toBeVisible();
await expect(
page.locator('.workhub-projected-turn .workhub-user-bubble > p', {
hasText: initialPrompt,
}),
).toBeVisible();
// The conversation is the Coordination Session transcript. An ordinary
// Session is a routing target and a status row, never a turn in WorkHub.
await expect(page.getByText('1 项工作', { exact: true })).toBeVisible();
await expect(page.locator('.workhub-turn')).toHaveCount(0);
await expect(page.locator('.workhub-empty h2')).toHaveText('从这里继续所有工作');

const routedPrompt = '继续这个工作,补充重复投递测试点。';
const workHubComposer = page.locator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,39 @@ test('restarts a paginated catalog read instead of mixing revisions', async () =
test('resolves WorkHub coordination through the dedicated Host operation', async () => {
const { client, requests } = clientWithResponses([
{ sessionId: 'maka_workhub_coordination' },
{ turnId: 'answer-turn' },
{ turnId: 'summary-turn' },
]);

assert.deepEqual(await client.resolveWorkHubCoordinationSession(), {
sessionId: 'maka_workhub_coordination',
});
assert.deepEqual(
await client.answerWorkHubCoordination({ turnId: 'answer-turn', text: 'Question' }),
{ turnId: 'answer-turn' },
);
assert.deepEqual(
await client.recordWorkHubCoordination({
turnId: 'summary-turn',
userText: 'Request',
assistantText: 'Summary',
}),
{ turnId: 'summary-turn' },
);
assert.deepEqual(requests, [
{ operation: 'workhub.coordination.resolve', input: {} },
{
operation: 'workhub.coordination.answer',
input: { turnId: 'answer-turn', text: 'Question' },
},
{
operation: 'workhub.coordination.record',
input: {
turnId: 'summary-turn',
userText: 'Request',
assistantText: 'Summary',
},
},
]);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,26 @@ import { registerRuntimeHostWorkHubIpc } from '../runtime-host-workhub-ipc-main.
test('projects WorkHub coordination resolution through its dedicated IPC domain', async () => {
const handlers = new Map<string, (...args: unknown[]) => unknown>();
let resolveCalls = 0;
const answers: unknown[] = [];
const records: unknown[] = [];
registerRuntimeHostWorkHubIpc(
{
resolveWorkHubCoordinationSession: async () => {
resolveCalls += 1;
return { sessionId: 'maka_workhub_coordination' };
},
answerWorkHubCoordination: async (input: { turnId: string; text: string }) => {
answers.push(input);
return { turnId: input.turnId };
},
recordWorkHubCoordination: async (input: {
turnId: string;
userText: string;
assistantText: string;
}) => {
records.push(input);
return { turnId: input.turnId };
},
} as never,
{
handle: (channel: string, handler: (...args: unknown[]) => unknown) => {
Expand All @@ -42,4 +56,22 @@ test('projects WorkHub coordination resolution through its dedicated IPC domain'
assert.ok(handler);
assert.deepEqual(await handler({}), { sessionId: 'maka_workhub_coordination' });
assert.equal(resolveCalls, 1);
assert.deepEqual(
await handlers.get('workhub:answer')?.({}, { turnId: 'answer', text: 'Question' }),
{ turnId: 'answer' },
);
assert.deepEqual(
await handlers.get('workhub:record')?.({}, {
turnId: 'record',
userText: 'Request',
assistantText: 'Summary',
}),
{ turnId: 'record' },
);
assert.deepEqual(answers, [{ turnId: 'answer', text: 'Question' }]);
assert.deepEqual(records, [{
turnId: 'record',
userText: 'Request',
assistantText: 'Summary',
}]);
});
30 changes: 18 additions & 12 deletions apps/desktop/src/main/__tests__/workhub-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ test('read exposes existing ordinary Sessions as factual Work summaries', async
assert.deepEqual(projection.turns, []);
});

test('read rebuilds a bounded conversation projection from ordinary Session turns', async () => {
test('read does not rebuild WorkHub conversation from ordinary Session turns', async () => {
const sessions = port([
session('login', { sessionName: '登录刷新令牌', updatedAt: 30 }),
session('internal', { kind: 'internal', updatedAt: 40 }),
Expand All @@ -156,16 +156,8 @@ test('read rebuilds a bounded conversation projection from ordinary Session turn

const projection = await createWorkHubController({ sessions }).read();

assert.deepEqual(requestedTargets, [['login']]);
assert.deepEqual(projection.turns, [{
messageId: 'user-1',
target: { sessionId: 'login' },
turnId: 'turn-login',
text: '检查刷新令牌竞争条件',
state: 'completed',
result: '已定位到并发刷新窗口',
updatedAt: 20,
}]);
assert.deepEqual(requestedTargets, []);
assert.deepEqual(projection.turns, []);
});

test('archived Sessions stay inspectable but are excluded from routing targets', async () => {
Expand Down Expand Up @@ -2370,12 +2362,23 @@ test('submit lets strong foreign core evidence override a vague focus word', asy

test('submit keeps unmatched non-executable conversation in WorkHub', async () => {
let created = false;
const answered: Array<{ turnId: string; text: string }> = [];
const sessions = port([]);
sessions.create = async () => {
created = true;
return session('unexpected');
};
const controller = createWorkHubController({ sessions });
const controller = createWorkHubController({
sessions,
coordination: {
open: async () => ({ close: async () => undefined }),
answer: async (input) => {
answered.push(input);
return { turnId: input.turnId };
},
record: async (input) => ({ turnId: input.turnId }),
},
});

const result = await controller.submit({
requestId: 'request-discussion',
Expand All @@ -2389,6 +2392,9 @@ test('submit keeps unmatched non-executable conversation in WorkHub', async () =
text: '你觉得统一入口最重要的价值是什么?',
});
assert.equal(created, false);
assert.deepEqual(answered, [
{ turnId: 'request-discussion', text: '你觉得统一入口最重要的价值是什么?' },
]);
});

test('submit treats a design question containing an action word as discussion', async () => {
Expand Down
77 changes: 77 additions & 0 deletions apps/desktop/src/main/__tests__/workhub-session-port.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import {
projectWorkHubSessionTurns,
type WorkHubDesktopSession,
} from '../../renderer/workhub-session-port.js';
import {
createDesktopWorkHubCoordinationPort,
projectWorkHubCoordinationTurns,
} from '../../renderer/workhub-coordination-port.js';
import { WorkHubSessionSubmitError } from '../../renderer/workhub-controller.js';

function desktopSession(
Expand Down Expand Up @@ -94,6 +98,79 @@ function transcriptsWith(messages: readonly StoredMessage[]) {
};
}

test('projects the durable Coordination transcript into the WorkHub conversation', () => {
assert.deepEqual(projectWorkHubCoordinationTurns([
{ type: 'user', id: 'user-1', turnId: 'turn-1', ts: 10, text: 'What is next?' },
{
type: 'assistant',
id: 'assistant-1',
turnId: 'turn-1',
ts: 11,
text: 'Slice 3 is next.',
modelId: 'test-model',
},
{
type: 'turn_state',
id: 'state-1',
turnId: 'turn-1',
ts: 12,
status: 'completed',
partialOutputRetained: true,
},
]), [{
messageId: 'user-1',
turnId: 'turn-1',
text: 'What is next?',
result: 'Slice 3 is next.',
state: 'completed',
updatedAt: 11,
}]);
});

test('Coordination transcript adapter emits an initial empty ready snapshot and closes cleanly', async () => {
const sessionId = desktopSessionKey({ hostId: 'local-host', sessionId: 'coordination' });
const snapshots: unknown[] = [];
let closes = 0;
const adapter = createDesktopWorkHubCoordinationPort({
sessionId,
transcripts: {
open: async (requestedSessionId, handler) => {
assert.equal(requestedSessionId, sessionId);
handler({
sessionId: 'coordination',
deliverySequence: 1,
generation: 'generation-1',
hostEpoch: 'epoch-1',
durableThrough: null,
fragments: [],
evictedDurableSequences: [],
completedOverlayMessageIds: [],
hasOlder: false,
hasNewer: false,
reset: true,
ready: true,
});
return {
sessionId,
generation: 'generation-1',
hostEpoch: 'epoch-1',
readThroughMessageId: null,
loadBefore: async () => {},
loadAround: async () => {},
close: async () => { closes += 1; },
};
},
},
answer: async (input) => ({ turnId: input.turnId }),
record: async (input) => ({ turnId: input.turnId }),
});

const handle = await adapter.open((turns) => snapshots.push(turns), () => {});
assert.deepEqual(snapshots, [[]]);
await handle.close();
assert.equal(closes, 1);
});

test('projects durable Session messages into an ordered WorkHub conversation', () => {
const turns = projectWorkHubSessionTurns({
target: { sessionId: 'payment' },
Expand Down
Loading