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: 8 additions & 2 deletions docs/en/wework/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ tool's CLI protocol and runs the process in the current workspace through a loca
interactive terminal appears in the center while the title bar, right workspace, and bottom panel
remain available. The right and bottom panels can create additional harness sessions for the same
workspace through a picker instead of exposing every installed tool inline. The primary session
can be switched but not closed from a panel; additional sessions can be closed explicitly.
can be switched but not closed from a panel; additional sessions can be closed explicitly. A
session created from the right workspace opens directly as a right-side tab without replacing the
central primary session. A session created from the bottom panel keeps the central additional-session
behavior. The creation dialog selects both the harness and the model for that session and starts it
only after **Create session** is confirmed.

Kimi Code starts as an interactive TUI. When the terminal attaches, Wework injects the first task
through bracketed paste instead of using the one-shot `--prompt` mode, so the same terminal remains
Expand All @@ -77,7 +81,9 @@ the harness model picker. Its default is **Don't specify a model**: Wework does
configuration. You can instead explicitly select a local model interface from
**Settings → Models** or a public, personal, or group model available through the connected Wegent
account. That choice is persisted for the harness and replaces any `--model` or `-m` value from its
default arguments.
default arguments. When an additional session is created from the right or bottom panel, the dialog
starts with that persisted default, but changing the model there applies only to the new session and
does not overwrite the harness default.

Only an explicitly selected Wework model connects the harness to the Anthropic
Messages-compatible loopback route exposed by the executor. The child process then receives a
Expand Down
4 changes: 4 additions & 0 deletions docs/zh/wework/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ Windows 和 Linux 使用界面中显示的对应组合键。
的命令行协议传入完整提示词,并在当前工作区中通过本地 PTY 运行进程。终端显示在工作台中央,
支持键盘输入和窗口尺寸同步;标题栏、右侧工作区和底部面板仍可使用。可以从右侧或底部面板的
新增入口创建同一工作区的更多运行工具会话,新增入口先打开工具选择器,不会直接铺开所有工具。
从右侧工作区创建的会话会直接成为右侧标签页,不会替换中央主会话;从底部面板创建时仍按中央
附加会话处理。创建弹窗会同时选择运行工具和本次会话使用的模型,确认“创建会话”后才启动。
主会话只能切换,不能从面板关闭;附加会话可以显式关闭。

Kimi Code 以交互 TUI 启动。Wework 在终端附着时通过 bracketed paste 注入首条任务,不使用
Expand All @@ -76,6 +78,8 @@ Wework 不会改用其他目录启动会话,而会明确提示原工作区不
默认项是“不指定模型”:Wework 不传入 `--model`、模型代理地址或模型凭证,由工具读取
自己的原生配置。也可以显式选择“设置 → 模型”中的本地模型接口,或当前已连接 Wegent 账号可访问的
公共、个人和群组模型;该选择会持久化到对应运行工具,并替换默认参数里的 `--model` 或 `-m`。
从右侧或底部面板新建附加会话时,弹窗会带出该默认值,但弹窗中的模型调整只作用于本次新会话,
不会改写运行工具的持久化默认模型。

只有显式选择 Wework 模型时,运行工具才连接 executor 暴露在回环地址上的 Anthropic Messages
兼容路由。此时工具进程只收到固定的本地模型别名和无权限的占位凭证;真实 provider 凭证、云端
Expand Down
49 changes: 46 additions & 3 deletions wework/e2e/desktop/scenarios/local-terminal.scenario.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -689,13 +689,23 @@ export async function createDesktopScenario({
timeoutMs: uiTimeoutMs,
})
await control.command('click', '[data-testid="harness-session-picker-option-opencode"]')
await control.command('waitFor', '[data-testid="harness-session-picker-model-selector"]', {
text: 'Desktop E2E Vision',
timeoutMs: uiTimeoutMs,
})
await control.command('click', '[data-testid="harness-session-picker-create-button"]')
await waitForFile(
join(homePath, OPEN_CODE_ARGS_FILE),
'--model\nwework-messages/wework-selected\n',
uiTimeoutMs
)
await control.command(
'waitFor',
'[data-testid^="right-workspace-harness-tab-local-harness-"]',
{ timeoutMs: uiTimeoutMs }
)
await control.command('waitFor', '[data-testid="workbench-pane-task-title"]', {
text: 'OpenCode',
text: 'Inspect the project with Kimi',
timeoutMs: uiTimeoutMs,
})
const multiSessionSnapshot = JSON.parse(await control.command('snapshot', 'body'))
Expand All @@ -705,13 +715,46 @@ export async function createDesktopScenario({
).length >= 2,
'The workspace did not retain multiple harness sessions'
)
assert.ok(
multiSessionSnapshot.testIds.some(testId =>
testId.startsWith('right-workspace-harness-tab-local-harness-')
),
'The additional harness session was not created in the right sidebar'
)
assert.ok(
!multiSessionSnapshot.testIds.includes('central-harness-close-button'),
'The right-sidebar harness session replaced the active primary session'
)
await captureWorkbench(control, 'local-harness-20-multiple-sessions.png')
await control.command('click', '[data-testid="central-harness-close-button"]')
await control.command('waitFor', '[data-testid="desktop-empty-composer-frame"]', {
await control.command(
'click',
'[data-testid^="right-workspace-harness-tab-local-harness-"][data-testid$="-close-button"]'
)
await control.command('click', '[data-testid="toggle-right-workspace-panel-button"]')
await control.command('waitFor', '[data-testid="right-workspace-launcher"]', {
timeoutMs: uiTimeoutMs,
})
await control.command('waitFor', '[data-testid="workbench-pane-task-title"]', {
text: 'Inspect the project with Kimi',
timeoutMs: uiTimeoutMs,
})
const closedSidebarSessionSnapshot = JSON.parse(await control.command('snapshot', 'body'))
assert.ok(
!closedSidebarSessionSnapshot.testIds.some(testId =>
testId.startsWith('right-workspace-harness-tab-local-harness-')
),
'The closed right-sidebar harness tab remained mounted'
)
assert.ok(
!closedSidebarSessionSnapshot.testIds.includes('desktop-empty-composer-frame'),
'Closing the right-sidebar harness session replaced the active primary session'
)
await captureWorkbench(control, 'local-harness-21-session-closed.png')

await control.command('click', '[data-testid="new-chat-button"]')
await control.command('waitFor', '[data-testid="desktop-empty-composer-frame"]', {
timeoutMs: uiTimeoutMs,
})
await control.command(
'click',
`${ACTIVE_WORKBENCH_SELECTOR} [data-testid="workbench-harness-selector"]`
Expand Down
43 changes: 38 additions & 5 deletions wework/src/components/layout/DesktopWorkbenchLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3339,6 +3339,18 @@ describe('DesktopWorkbenchLayout', () => {
await userEvent.click(screen.getByTestId('workspace-add-harness-option'))
expect(screen.getByTestId('harness-session-picker')).toBeInTheDocument()
await userEvent.click(screen.getByTestId('harness-session-picker-option-opencode'))
expect(screen.getByTestId('harness-session-picker-model-selector')).toHaveTextContent(
'Second Test Model'
)
await userEvent.click(screen.getByTestId('harness-session-picker-model-selector'))
await userEvent.click(screen.getByTestId('harness-session-picker-model-option-opencode-0'))
await userEvent.click(screen.getByTestId('harness-session-picker-create-button'))
await waitFor(() =>
expect(resolveLocalHarnessLaunchMock).toHaveBeenLastCalledWith(
'opencode',
expect.objectContaining({ label: 'Test Model' })
)
)
await waitFor(() =>
expect(startLocalHarnessMock).toHaveBeenLastCalledWith(
expect.objectContaining({
Expand All @@ -3363,21 +3375,42 @@ describe('DesktopWorkbenchLayout', () => {
screen.getByTestId('local-harness-session-row-local-harness-1')
)
expect(
screen.getAllByTestId('embedded-local-terminal').find(element => !element.hidden)
within(screen.getByTestId('desktop-workbench-content')).getByTestId('embedded-local-terminal')
).toHaveAttribute('data-session-id', 'local-harness-1')
expect(
within(screen.getByTestId('right-workspace-panel-shell')).getByTestId(
'embedded-local-terminal'
)
).toHaveAttribute('data-session-id', 'local-harness-2')
expect(screen.getByTestId('central-harness-close-button')).toHaveAccessibleName('归档编码会话')
expect(screen.getByTestId('right-workspace-harness-tab-local-harness-2')).toHaveAttribute(
'aria-selected',
'true'
)
expect(screen.queryByTestId('central-harness-close-button')).not.toBeInTheDocument()
expect(screen.getByTestId('close-local-harness-session-local-harness-2')).toHaveAccessibleName(
'归档编码会话'
)

await userEvent.click(screen.getByTestId('central-harness-close-button'))
await userEvent.click(
screen.getByTestId('right-workspace-harness-tab-local-harness-2-close-button')
)

await waitFor(() =>
expect(archiveLocalHarnessSessionMock).toHaveBeenCalledWith('local-harness-2')
)
expect(screen.getByTestId('local-harness-session-row-local-harness-1')).toBeInTheDocument()
expect(screen.getByTestId('desktop-empty-composer-frame')).toBeInTheDocument()
expect(screen.getByTestId('right-workspace-chat-option')).toBeInTheDocument()
expect(
within(screen.getByTestId('desktop-workbench-content')).getByTestId('embedded-local-terminal')
).toHaveAttribute('data-session-id', 'local-harness-1')
expect(screen.queryByTestId('desktop-empty-composer-frame')).not.toBeInTheDocument()

startLocalHarnessMock.mockRejectedValueOnce(new Error('Right session failed'))
await userEvent.click(screen.getByTestId('toggle-right-workspace-panel-button'))
expect(screen.getByTestId('right-workspace-launcher')).toBeInTheDocument()
await userEvent.click(screen.getByTestId('workspace-add-harness-option'))
await userEvent.click(screen.getByTestId('harness-session-picker-create-button'))
expect(await screen.findByTestId('transient-notice')).toHaveTextContent('Right session failed')
expect(screen.getByTestId('central-harness-terminal')).toBeInTheDocument()
})

test('prepares a project worktree before starting the primary OpenCode session', async () => {
Expand Down
24 changes: 16 additions & 8 deletions wework/src/components/layout/DesktopWorkbenchLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ import {
updateLocalHarnessSessionTitle,
WEWORK_LOCAL_HARNESS_SESSIONS_CHANGED_EVENT,
} from '@/lib/local-terminal'
import type { LocalHarnessWorkbenchSession } from './localHarnessWorkbench'
import type {
LocalHarnessSessionRegistrationOptions,
LocalHarnessWorkbenchSession,
} from './localHarnessWorkbench'

type ImNotificationDialogMode = { type: 'global' } | { type: 'task'; address: RuntimeTaskAddress }

Expand Down Expand Up @@ -235,13 +238,18 @@ export function DesktopWorkbenchLayout({ routeActive = true }: DesktopWorkbenchL
},
[currentPath, onOpenRuntimeTask, state.currentRuntimeTask]
)
const registerLocalHarnessSession = useCallback((session: LocalHarnessWorkbenchSession) => {
setLocalHarnessSessions(current => [
session,
...current.filter(candidate => candidate.sessionId !== session.sessionId),
])
setActiveLocalHarnessSessionId(session.sessionId)
}, [])
const registerLocalHarnessSession = useCallback(
(session: LocalHarnessWorkbenchSession, options?: LocalHarnessSessionRegistrationOptions) => {
setLocalHarnessSessions(current => [
session,
...current.filter(candidate => candidate.sessionId !== session.sessionId),
])
if (options?.activate !== false) {
setActiveLocalHarnessSessionId(session.sessionId)
}
},
[]
)
const updateHarnessSessionTitle = useCallback((sessionId: string, title: string) => {
const normalized = title.trim().replace(/\s+/g, ' ').slice(0, 80)
if (!normalized) return
Expand Down
Loading
Loading