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
37 changes: 37 additions & 0 deletions apps/desktop/src/main/__tests__/workhub-anchor-rail.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import {
matchesWorkHubFilter,
MAX_WORKHUB_ANCHORS,
WorkHubNavigationRail,
WorkHubResultCard,
workHubLinkedWork,
workHubTurnResultPreview,
} from "../../renderer/features/workhub/index.js";
import { getWorkHubRailCopy } from "../../renderer/locales/workhub-copy.js";
import type { ToolCallMessage, ToolResultMessage } from '@maka/core/session';
Expand All @@ -48,6 +50,41 @@ test('durable task results restore Host-scoped work links without treating faile
], [], 'Work'), []);
});

test('a completed delegation returns its bounded result in the WorkHub conversation', () => {
const target = JSON.stringify(['host-a', 'task-a']);
const markup = renderToStaticMarkup(createElement(WorkHubResultCard, {
work: {
id: 'delegation-record',
coordinationTurnId: 'coordination-turn',
targetSessionId: target,
targetSessionName: 'Release checklist',
targetMessageId: 'delegated-message',
targetTurnId: 'target-turn',
state: 'completed',
resultPreview: 'All release checks passed. The report is ready.',
},
locale: 'en',
highlighted: false,
onHighlight: () => undefined,
onOpenWork: () => undefined,
}));

assert.match(markup, /Completed/u);
assert.match(markup, /All release checks passed\. The report is ready\./u);
assert.match(markup, /Open result/u);
});

test('delegated result previews select the exact Turn and stay character-bounded', () => {
const preview = workHubTurnResultPreview([
{ type: 'assistant', id: 'other-answer', turnId: 'other-turn', ts: 1, modelId: 'model', text: 'wrong result' },
{ type: 'assistant', id: 'target-answer', turnId: 'target-turn', ts: 2, modelId: 'model', text: ` ${'界'.repeat(700)} ` },
], 'target-turn');

assert.equal(Array.from(preview ?? '').length, 600);
assert.equal(preview?.endsWith('…'), true);
assert.doesNotMatch(preview ?? '', /wrong result/u);
});

function session(
sessionId: string,
state: WorkHubAnchorSession["state"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { createDesktopWorkHubServices } from '../../renderer/platform/desktop/cr
import { desktopSessionKey } from '../../shared/runtime-host-identity.js';
import { encodeDesktopTranscriptSnapshot } from '../desktop-transcript-ipc.js';
import type { AttachmentRef } from '@maka/core/events';
import { MESSAGE_QUEUE_MAX_ENTRIES } from '@maka/runtime-host/protocol';

test('WorkHub upload references round-trip through idle answers, both queue modes and attachment reads', async (t) => {
const owner = {
Expand Down Expand Up @@ -106,6 +107,208 @@ test('WorkHub upload references round-trip through idle answers, both queue mode
await assert.rejects(services.enqueueMessage(sessionId, 'foreign', 'read this', foreign, 'next_turn'), /another Host or Session/);
});

test('WorkHub projects the exact delegated Turn status and bounded assistant result', async (t) => {
const originalWindow = Object.getOwnPropertyDescriptor(globalThis, 'window');
Object.defineProperty(globalThis, 'window', { configurable: true, value: { location: { search: '?surface=workhub' } } });
t.after(() => {
if (originalWindow) Object.defineProperty(globalThis, 'window', originalWindow);
else Reflect.deleteProperty(globalThis, 'window');
});
const sessionId = desktopSessionKey({ hostId: 'owner-host', sessionId: 'target-session' });
const result: StoredMessage = {
type: 'assistant', id: 'answer', turnId: 'owned-turn', ts: 3,
modelId: 'model', text: 'The delegated task finished with this exact result.',
};
const services = createDesktopWorkHubServices({
attachments: {},
sessions: {
async list() {
return [{
id: sessionId, name: 'Target task', isFlagged: false, isArchived: false,
labels: [], hasUnread: false, status: 'active', runningTurnIds: [], revision: 1,
}];
},
async listTurns() {
return [{ turnId: 'owned-turn', firstSequence: 1, status: 'completed', statusSource: 'recorded' }];
},
async queryMessageExecutions() {
return { resolutions: [{ messageId: 'delegated-message', state: 'owned', turnId: 'owned-turn', runId: 'run' }] };
},
},
transcripts: {
async open(_sessionId: string, onBatch: (batch: DesktopTranscriptBatch) => void) {
const snapshot = {
sessionId: 'target-session', generation: 'generation-1', hostEpoch: 'epoch-1',
durableThrough: 1, overlay: [], hasOlder: false, hasNewer: false,
};
for (const batch of encodeDesktopTranscriptSnapshot({
...snapshot, navigationVersion: 0, durable: [{ sequence: 1, message: result }],
})) onBatch({ ...batch, deliverySequence: 1 });
return {
...snapshot, readThroughMessageId: result.id,
loadBefore: async () => undefined, loadAfter: async () => undefined,
loadAround: async () => undefined, close: async () => undefined,
};
},
},
} as unknown as Parameters<typeof createDesktopWorkHubServices>[0]);

assert.deepEqual(await services.delegationFeedback([{
id: 'delegation-record', targetSessionId: sessionId,
targetMessageId: 'delegated-message', targetTurnId: 'initial-turn',
}]), [{
id: 'delegation-record', state: 'completed',
resultPreview: 'The delegated task finished with this exact result.',
}]);
});

test('WorkHub proves a long historical Turn tail before caching its final result', async (t) => {
const originalWindow = Object.getOwnPropertyDescriptor(globalThis, 'window');
Object.defineProperty(globalThis, 'window', { configurable: true, value: { location: { search: '?surface=workhub' } } });
t.after(() => {
if (originalWindow) Object.defineProperty(globalThis, 'window', originalWindow);
else Reflect.deleteProperty(globalThis, 'window');
});
const sessionId = desktopSessionKey({ hostId: 'owner-host', sessionId: 'target-session' });
const intermediate: StoredMessage = {
type: 'assistant', id: 'intermediate', turnId: 'owned-turn', ts: 1,
modelId: 'model', text: 'Intermediate answer that must not be cached.',
};
const final: StoredMessage = {
type: 'assistant', id: 'final', turnId: 'owned-turn', ts: 2,
modelId: 'model', text: 'Final answer after the historical Turn boundary.',
};
const next: StoredMessage = {
type: 'user', id: 'next', turnId: 'next-turn', ts: 3, text: 'Later turn',
};
let deliverySequence = 0;
let opens = 0;
let loadAfters = 0;
const services = createDesktopWorkHubServices({
attachments: {},
sessions: {
async list() {
return [{
id: sessionId, name: 'Target task', isFlagged: false, isArchived: false,
labels: [], hasUnread: false, status: 'active', runningTurnIds: [], revision: 1,
}];
},
async listTurns() {
return [{ turnId: 'owned-turn', firstSequence: 1, status: 'completed', statusSource: 'recorded' }];
},
async queryMessageExecutions() {
return { resolutions: [{ messageId: 'delegated-message', state: 'owned', turnId: 'owned-turn', runId: 'run' }] };
},
},
transcripts: {
async open(_sessionId: string, onBatch: (batch: DesktopTranscriptBatch) => void) {
opens += 1;
const emit = (
navigationVersion: number,
durable: Array<{ sequence: number; message: StoredMessage }>,
hasOlder: boolean,
hasNewer: boolean,
) => {
for (const batch of encodeDesktopTranscriptSnapshot({
sessionId: 'target-session', generation: 'generation-1', hostEpoch: 'epoch-1',
durableThrough: 4, overlay: [], hasOlder, hasNewer, navigationVersion, durable,
})) onBatch({ ...batch, deliverySequence: ++deliverySequence });
};
emit(0, [{ sequence: 4, message: { ...next, id: 'tail', ts: 4 } }], true, false);
return {
sessionId: 'target-session', generation: 'generation-1', hostEpoch: 'epoch-1',
durableThrough: 4, hasOlder: true, hasNewer: false, readThroughMessageId: 'tail',
loadBefore: async () => undefined,
async loadAround(_sequence: number | null, _maxBytes: number | undefined, navigation: { navigationVersion: number }) {
emit(navigation.navigationVersion, [{ sequence: 1, message: intermediate }], false, true);
},
async loadAfter(anchor: number | null, _maxBytes: number | undefined, navigation: { navigationVersion: number }) {
loadAfters += 1;
assert.equal(anchor, 1);
emit(navigation.navigationVersion, [
{ sequence: 2, message: final },
{ sequence: 3, message: next },
], false, true);
},
close: async () => undefined,
};
},
},
} as unknown as Parameters<typeof createDesktopWorkHubServices>[0]);
const reference = {
id: 'delegation-record', targetSessionId: sessionId,
targetMessageId: 'delegated-message', targetTurnId: 'initial-turn',
};

assert.equal((await services.delegationFeedback([reference]))[0]?.resultPreview, final.text);
assert.equal((await services.delegationFeedback([reference]))[0]?.resultPreview, final.text);
assert.equal(opens, 1);
assert.equal(loadAfters, 1);
});

test('WorkHub batches more than the message execution query limit per target Session', async (t) => {
const originalWindow = Object.getOwnPropertyDescriptor(globalThis, 'window');
Object.defineProperty(globalThis, 'window', { configurable: true, value: { location: { search: '?surface=workhub' } } });
t.after(() => {
if (originalWindow) Object.defineProperty(globalThis, 'window', originalWindow);
else Reflect.deleteProperty(globalThis, 'window');
});
const sessionId = desktopSessionKey({ hostId: 'owner-host', sessionId: 'target-session' });
const querySizes: number[] = [];
const services = createDesktopWorkHubServices({
attachments: {},
sessions: {
async list() {
return [{
id: sessionId, name: 'Target task', isFlagged: false, isArchived: false,
labels: [], hasUnread: false, status: 'active', runningTurnIds: [], revision: 1,
}];
},
async listTurns() { return []; },
async queryMessageExecutions(_sessionId: string, messageIds: string[]) {
querySizes.push(messageIds.length);
if (messageIds.length > MESSAGE_QUEUE_MAX_ENTRIES) throw new Error('query limit exceeded');
return { resolutions: messageIds.map((messageId) => ({ messageId, state: 'pending' as const })) };
},
},
} as unknown as Parameters<typeof createDesktopWorkHubServices>[0]);
const references = Array.from({ length: MESSAGE_QUEUE_MAX_ENTRIES + 1 }, (_, index) => ({
id: `delegation-${index}`, targetSessionId: sessionId,
targetMessageId: `message-${index}`, targetTurnId: `turn-${index}`,
}));

assert.deepEqual((await services.delegationFeedback(references)).map(({ state }) => state),
references.map(() => 'accepted'));
assert.deepEqual(querySizes, [MESSAGE_QUEUE_MAX_ENTRIES, 1]);
});

test('WorkHub does not infer live running when the Session catalog is unavailable', async (t) => {
const originalWindow = Object.getOwnPropertyDescriptor(globalThis, 'window');
Object.defineProperty(globalThis, 'window', { configurable: true, value: { location: { search: '?surface=workhub' } } });
t.after(() => {
if (originalWindow) Object.defineProperty(globalThis, 'window', originalWindow);
else Reflect.deleteProperty(globalThis, 'window');
});
const sessionId = desktopSessionKey({ hostId: 'owner-host', sessionId: 'target-session' });
const services = createDesktopWorkHubServices({
attachments: {},
sessions: {
async list() { throw new Error('catalog unavailable'); },
async listTurns() {
return [{ turnId: 'owned-turn', firstSequence: 1, status: 'running', statusSource: 'recorded' }];
},
async queryMessageExecutions() {
return { resolutions: [{ messageId: 'delegated-message', state: 'owned', turnId: 'owned-turn', runId: 'run' }] };
},
},
} as unknown as Parameters<typeof createDesktopWorkHubServices>[0]);

assert.equal((await services.delegationFeedback([{
id: 'delegation-record', targetSessionId: sessionId,
targetMessageId: 'delegated-message', targetTurnId: 'initial-turn',
}]))[0]?.state, 'recovering');
});

// Keep the real preload's navigation defaults and filtering in this consumer
// regression; the IPC stub models the observer's authoritative reset reply.
test('WorkHub tail navigation converges through the preload with a fragmented sparse tail', { timeout: 5_000 }, async (t) => {
Expand Down
7 changes: 6 additions & 1 deletion apps/desktop/src/renderer/features/workhub/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@
*/

export * from './model/anchor-rail.js';
export { workHubLinkedWork } from './model/linked-work.js';
export {
workHubLinkedWork,
type WorkHubDelegationReference,
} from './model/linked-work.js';
export { projectWorkHubDelegationState, workHubTurnResultPreview } from './model/delegation-feedback.js';
export { WorkHubNavigationRail } from './ui/workhub-navigation-rail.js';
export { WorkHubResultCard } from './ui/workhub-conversation.js';
export type { WorkHubServices, WorkHubTranscriptSnapshot } from './ports.js';
export { WorkHubServicesProvider } from './services.js';
export { WorkHubRoot } from './ui/workhub-root.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export const workHubLiveCopy = {
hint: 'Ask a question, manage your tasks, or ask me to work in Maka.',
floating: 'WorkHub is in a floating window',
restore: 'Bring WorkHub back',
delegationAccepted: 'Accepted', delegationRunning: 'Running', delegationWaiting: 'Waiting for you',
delegationCompleted: 'Completed', delegationFailed: 'Failed', delegationAborted: 'Aborted', delegationRecovering: 'Recovering',
openWork: 'Open task', openResult: 'Open result',
},
'zh-CN': { attachmentLimit: '附件数量或大小超过限制', attachmentUploadFailed: '附件上传失败', reviewAttachments: '请查看附件。', sendFailed: '发送失败',
retrySteering: '请先保留原文和附件,用 Shift+Enter 重试并确认上次提交结果。',
Expand All @@ -66,6 +69,9 @@ export const workHubLiveCopy = {
hint: '问个问题、管理任务,或让我帮你操作 Maka。',
floating: '工作台已在浮窗中打开',
restore: '收回工作台',
delegationAccepted: '已接收', delegationRunning: '进行中', delegationWaiting: '等待你',
delegationCompleted: '已完成', delegationFailed: '失败', delegationAborted: '已中止', delegationRecovering: '正在恢复',
openWork: '打开任务', openResult: '打开结果',
},
'zh-TW': { attachmentLimit: '附件數量或大小超過限制', attachmentUploadFailed: '附件上傳失敗', reviewAttachments: '請查看附件。', sendFailed: '傳送失敗',
retrySteering: '請先保留原文和附件,用 Shift+Enter 重試並確認上次提交結果。',
Expand All @@ -90,5 +96,8 @@ export const workHubLiveCopy = {
hint: '問個問題、管理任務,或讓我幫你操作 Maka。',
floating: '工作台已在浮動視窗中開啟',
restore: '收回工作台',
delegationAccepted: '已接收', delegationRunning: '進行中', delegationWaiting: '等待你',
delegationCompleted: '已完成', delegationFailed: '失敗', delegationAborted: '已中止', delegationRecovering: '正在恢復',
openWork: '開啟任務', openResult: '開啟結果',
},
} satisfies UiCatalog<Record<string, string>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import type { SessionSummary, StoredMessage, TurnRecord } from '@maka/core/session';
import type { TurnMessageExecutionResolution } from '@maka/runtime-host/protocol';
import type { WorkHubDelegationState } from './linked-work.js';

export function projectWorkHubDelegationState(input: {
resolution: TurnMessageExecutionResolution | undefined;
session: SessionSummary | undefined;
turn: TurnRecord | undefined;
executionReadFailed?: boolean;
turnReadFailed?: boolean;
}): WorkHubDelegationState {
if (input.executionReadFailed || !input.resolution) return 'recovering';
if (input.resolution.state === 'cancelled') return 'aborted';
if (input.resolution.state === 'pending') return 'accepted';
if (input.turn?.statusSource === 'recorded' && input.turn.status !== 'running') {
return input.turn.status;
}
const ownsLiveTurn = input.session?.runningTurnIds?.includes(input.resolution.turnId) === true;
if (ownsLiveTurn && input.session?.status === 'waiting_for_user') return 'waiting_for_user';
if (ownsLiveTurn) return 'running';
if (input.turnReadFailed || !input.session || !input.turn) return 'recovering';
if (input.session?.runningTurnIds === undefined &&
Comment thread
Astro-Han marked this conversation as resolved.
input.turn?.statusSource === 'recorded' && input.turn.status === 'running') return 'running';
return 'accepted';
}

const WORKHUB_RESULT_PREVIEW_MAX_CHARACTERS = 600;

export function workHubTurnResultPreview(
messages: readonly StoredMessage[],
turnId: string,
): string | undefined {
for (let index = messages.length - 1; index >= 0; index -= 1) {
const message = messages[index];
if (message?.type !== 'assistant' || message.turnId !== turnId || !message.text.trim()) continue;
const normalized = message.text.trim().replace(/\s+/gu, ' ');
const characters = Array.from(normalized);
return characters.length <= WORKHUB_RESULT_PREVIEW_MAX_CHARACTERS
? normalized
: `${characters.slice(0, WORKHUB_RESULT_PREVIEW_MAX_CHARACTERS - 1).join('')}…`;
}
return undefined;
}
Loading