@jetcrab/pi-desk 1.1.31 → 1.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +24 -10
  2. package/README.zh-CN.md +24 -10
  3. package/package.json +1 -1
  4. package/src/client/l2_biz/workbench/hooks/l2-workbench-model-context.ts +39 -7
  5. package/src/client/l2_biz/workbench/l2-workbench-model-context-dialog.tsx +7 -8
  6. package/src/client/l2_biz/workbench/native-ui/l2-native-ui-mcp.tsx +66 -99
  7. package/src/client/l2_biz/workbench/native-ui/l2-native-ui-notices.tsx +2 -2
  8. package/src/client/l2_biz/workbench/native-ui/l2-native-ui-view.tsx +5 -2
  9. package/src/client/l4_foundation/desktop-window/l4-desktop-titlebar.tsx +8 -15
  10. package/src/client/l4_foundation/desktop-window/l4-desktop-window.css +0 -4
  11. package/src/common/l2_biz/workbench/l2-workbench-locale-messages.ts +12 -16
  12. package/src/common/l4_foundation/locale/l4-host-error-messages.ts +0 -2
  13. package/src/server/l1_entry/api/l1-chat-context-get-route.ts +0 -4
  14. package/src/server/l4_foundation/pi/l4-pi-chat-worker.ts +0 -19
  15. package/src/server/l4_foundation/pi/l4-pi-mcp-command.ts +48 -0
  16. package/src/server/l4_foundation/pi/l4-pi-session-resources.ts +11 -2
  17. package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
  18. package/temp/build/pi-desk/release/.next/build-manifest.json +2 -2
  19. package/temp/build/pi-desk/release/.next/prerender-manifest.json +3 -3
  20. package/temp/build/pi-desk/release/.next/required-server-files.js +2 -2
  21. package/temp/build/pi-desk/release/.next/required-server-files.json +2 -2
  22. package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  23. package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
  24. package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +1 -1
  25. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  26. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  27. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  28. package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  29. package/temp/build/pi-desk/release/.next/server/app/api/chat/context/get/route.js +1 -1
  30. package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
  31. package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
  32. package/temp/build/pi-desk/release/.next/server/app/page.js +2 -2
  33. package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
  34. package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -1
  35. package/temp/build/pi-desk/release/.next/server/chunks/25406.js +11 -11
  36. package/temp/build/pi-desk/release/.next/server/chunks/35985.js +1 -1
  37. package/temp/build/pi-desk/release/.next/server/chunks/92386.js +10 -10
  38. package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
  39. package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
  40. package/temp/build/pi-desk/release/.next/server/server-reference-manifest.json +1 -1
  41. package/temp/build/pi-desk/release/.next/static/chunks/11937-d5d4d2199356c3ed.js +1 -0
  42. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-fdea504082053156.js +1 -0
  43. package/temp/build/pi-desk/release/.next/static/chunks/app/{page-c27bac30ceaea98b.js → page-4f02cd8374a5ea39.js} +6 -6
  44. package/temp/build/pi-desk/release/.next/static/css/e44d06d81c9535c0.css +1 -0
  45. package/temp/build/pi-desk/release/.next/trace-build +1 -1
  46. package/temp/build/pi-desk/release/.next/static/chunks/11937-ac71fb0f7cef482a.js +0 -1
  47. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-ca5ef5994165eb2e.js +0 -1
  48. package/temp/build/pi-desk/release/.next/static/css/3c59ac068ae404da.css +0 -1
  49. /package/temp/build/pi-desk/release/.next/static/{n_zNpuntCtgSLQXHItXgr → YNnS2PkK7QVGmBEX0iXCM}/_buildManifest.js +0 -0
  50. /package/temp/build/pi-desk/release/.next/static/{n_zNpuntCtgSLQXHItXgr → YNnS2PkK7QVGmBEX0iXCM}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -20,16 +20,30 @@ Pi powers the agent. Pi Desk provides the interface, with a lightweight core and
20
20
  - **Tasks and results in view.** Follow background commands and subagents in the task center, and preview deliverables with the corresponding plugins.
21
21
  - **Extend it your way.** Add tools, panels, applications, and custom message views through Pi Desk plugins and the public SDK.
22
22
 
23
- ## Interface demo
24
-
25
- <p>
26
- <a href="./docs/doc_asset/client-overview.webp">
27
- <img src="./docs/doc_asset/client-overview.webp" alt="File preview" width="300" />
28
- </a>
29
- <a href="./docs/doc_asset/multi-window.webp">
30
- <img src="./docs/doc_asset/multi-window.webp" alt="Side-by-side conversations" width="300" />
31
- </a>
32
- </p>
23
+ ## Interface preview
24
+
25
+ Explore common Pi Desk views below; appearance varies by version and theme, and you can click any screenshot to view it full size.
26
+
27
+ <table>
28
+ <tr>
29
+ <th width="50%">File preview</th>
30
+ <th width="50%">Side-by-side conversations</th>
31
+ </tr>
32
+ <tr>
33
+ <td>
34
+ <a href="./docs/doc_asset/client-overview.webp">
35
+ <img src="./docs/doc_asset/client-overview.webp" alt="File preview" width="100%" />
36
+ </a>
37
+ </td>
38
+ <td>
39
+ <a href="./docs/doc_asset/multi-window.webp">
40
+ <img src="./docs/doc_asset/multi-window.webp" alt="Side-by-side conversations" width="100%" />
41
+ </a>
42
+ </td>
43
+ </tr>
44
+ </table>
45
+
46
+ <!-- Add more screenshots here: copy the table above for each pair, replacing titles, image paths, and links. Repeat for additional groups. -->
33
47
 
34
48
  Visit [pidesk.dev](https://pidesk.dev) for product demos and [the documentation](https://pidesk.dev/docs/) for usage guides.
35
49
 
package/README.zh-CN.md CHANGED
@@ -20,16 +20,30 @@ Pi 负责代理运行,Pi Desk 提供围绕它的工作界面。核心保持轻
20
20
  - **任务与结果集中呈现。** 通过相应插件,在任务中心跟踪后台命令和子代理,并预览交付物。
21
21
  - **按自己的需要扩展。** 使用 Pi Desk 插件和公共 SDK 添加工具、面板、应用及自定义消息视图。
22
22
 
23
- ## 界面演示
24
-
25
- <p>
26
- <a href="./docs/doc_asset/client-overview.webp">
27
- <img src="./docs/doc_asset/client-overview.webp" alt="文件预览" width="300" />
28
- </a>
29
- <a href="./docs/doc_asset/multi-window.webp">
30
- <img src="./docs/doc_asset/multi-window.webp" alt="多窗口会话" width="300" />
31
- </a>
32
- </p>
23
+ ## 界面预览
24
+
25
+ 以下展示 Pi Desk 的常用界面;具体外观随版本与主题变化,点击截图可查看大图。
26
+
27
+ <table>
28
+ <tr>
29
+ <th width="50%">文件预览</th>
30
+ <th width="50%">多会话并排</th>
31
+ </tr>
32
+ <tr>
33
+ <td>
34
+ <a href="./docs/doc_asset/client-overview.webp">
35
+ <img src="./docs/doc_asset/client-overview.webp" alt="文件预览" width="100%" />
36
+ </a>
37
+ </td>
38
+ <td>
39
+ <a href="./docs/doc_asset/multi-window.webp">
40
+ <img src="./docs/doc_asset/multi-window.webp" alt="多会话并排" width="100%" />
41
+ </a>
42
+ </td>
43
+ </tr>
44
+ </table>
45
+
46
+ <!-- 后续截图追加位置:每组两张,复制上方表格并替换图名、图片路径和链接;可继续追加多组。 -->
33
47
 
34
48
  在 [pidesk.dev](https://pidesk.dev) 查看产品演示,在[使用文档](https://pidesk.dev/docs/)中了解具体操作。
35
49
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetcrab/pi-desk",
3
- "version": "1.1.31",
3
+ "version": "1.1.32",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { useCallback, useEffect, useState } from 'react'
3
+ import { useCallback, useEffect, useRef, useState } from 'react'
4
4
  import type { L2ChatSource } from '@common/l2_biz/chat/l2-chat-contract'
5
5
  import type { L2ChatModelContextGetResponse } from '@common/l2_biz/chat/l2-chat-context-contract'
6
6
  import type {
@@ -9,6 +9,7 @@ import type {
9
9
  } from '@common/l3_modules/plugin-host/l3-plugin-native-pi-contract'
10
10
  import type { L2WorkbenchBiz } from '../l2-workbench-biz'
11
11
  import type { L2WorkbenchChatInputRuntime } from '../l2-workbench-chat-input-runtime'
12
+ import type { L2NativeUiBiz, L2NativeUiState } from '../native-ui/l2-native-ui-biz'
12
13
 
13
14
  export type L2WorkbenchContextTab = 'tools' | 'system' | 'mcp'
14
15
 
@@ -58,18 +59,49 @@ export function useL2WorkbenchModelContext(input: {
58
59
  runtime: L2WorkbenchChatInputRuntime
59
60
  getContext: L2WorkbenchBiz['getChatModelContext']
60
61
  listCommands: L2WorkbenchBiz['listPiCommands']
62
+ nativeBiz: L2NativeUiBiz
63
+ nativeState: L2NativeUiState
61
64
  }): {
62
65
  context: PanelData<L2ChatModelContextGetResponse>
63
66
  tools: PanelData<L3PiNativeToolsListResponse>
64
67
  commands: PanelData<L3PiNativeCommandsListResponse>
65
68
  } {
66
- const { source, runtime, getContext, listCommands, open, tab } = input
69
+ const { source, runtime, getContext, listCommands, open, tab, nativeBiz, nativeState } = input
67
70
  const loadContext = useCallback(() => getContext({ source }), [getContext, source])
68
71
  const loadTools = useCallback(() => runtime.listTools(source), [runtime, source])
69
72
  const loadCommands = useCallback(() => listCommands({ source }), [listCommands, source])
70
- return {
71
- context: usePanelData(open && tab === 'system', loadContext),
72
- tools: usePanelData(open && tab === 'tools', loadTools),
73
- commands: usePanelData(open && tab === 'mcp', loadCommands)
74
- }
73
+ const context = usePanelData(open && tab === 'system', loadContext)
74
+ const tools = usePanelData(open && tab === 'tools', loadTools)
75
+ const commands = usePanelData(open && tab === 'mcp', loadCommands)
76
+ const queriedMcp = useRef(false)
77
+ useEffect(() => {
78
+ if (!open || tab !== 'mcp') {
79
+ queriedMcp.current = false
80
+ return
81
+ }
82
+ if (
83
+ queriedMcp.current ||
84
+ commands.loading ||
85
+ !commands.value?.commands.some(
86
+ (command) => command.name === 'mcp' && command.source === 'extension'
87
+ ) ||
88
+ !nativeState.ready ||
89
+ nativeState.commandPending ||
90
+ nativeState.snapshot.requests.length > 0
91
+ )
92
+ return
93
+ queriedMcp.current = true
94
+ void nativeBiz.executeCommand(source, 'mcp', '')
95
+ }, [
96
+ open,
97
+ tab,
98
+ commands.loading,
99
+ commands.value,
100
+ nativeState.ready,
101
+ nativeState.commandPending,
102
+ nativeState.snapshot.requests.length,
103
+ nativeBiz,
104
+ source
105
+ ])
106
+ return { context, tools, commands }
75
107
  }
@@ -54,7 +54,7 @@ export function L2WorkbenchModelContextDialog({
54
54
  }: L2WorkbenchModelContextDialogProps): React.JSX.Element {
55
55
  const { t, i18n } = useTranslation('workbench')
56
56
  const [copied, setCopied] = useState(false)
57
- const [view, setView] = useState<L2WorkbenchContextTab>('tools')
57
+ const [view, setView] = useState<L2WorkbenchContextTab>('system')
58
58
  const toast = useL4AppToast()
59
59
  const { context, tools, commands } = useL2WorkbenchModelContext({
60
60
  source,
@@ -62,7 +62,9 @@ export function L2WorkbenchModelContextDialog({
62
62
  tab: view,
63
63
  runtime,
64
64
  getContext,
65
- listCommands
65
+ listCommands,
66
+ nativeBiz,
67
+ nativeState: nativeUi.state
66
68
  })
67
69
  const data = view === 'system' ? context : view === 'tools' ? tools : commands
68
70
  const question = nativeUi.state.snapshot.requests[0]?.id
@@ -101,8 +103,8 @@ export function L2WorkbenchModelContextDialog({
101
103
  value={view}
102
104
  onChange={setView}
103
105
  options={[
104
- { value: 'tools', label: t('tools') },
105
106
  { value: 'system', label: t('systemPrompt') },
107
+ { value: 'tools', label: t('tools') },
106
108
  { value: 'mcp', label: t('mcpSession', { defaultValue: '会话 MCP' }) }
107
109
  ]}
108
110
  />
@@ -172,13 +174,10 @@ export function L2WorkbenchModelContextDialog({
172
174
  )}
173
175
  state={nativeUi.state}
174
176
  disabled={commandsDisabled}
175
- onExecute={(args, login) => {
177
+ onExecute={(args) => {
176
178
  void nativeBiz.executeCommand(source, 'mcp', args)
177
- if (login) {
178
- nativeUi.setCollapsed(false)
179
- onOpenChange(false)
180
- }
181
179
  }}
180
+ onRetryConnection={nativeUi.refresh}
182
181
  />
183
182
  ) : null}
184
183
  </div>
@@ -1,10 +1,8 @@
1
1
  'use client'
2
2
 
3
- import { useState } from 'react'
3
+ import { LoaderCircleIcon, RefreshCwIcon } from 'lucide-react'
4
4
  import { useTranslation } from 'react-i18next'
5
5
  import { Button } from '@client/l4_foundation/ui/shadcn/button'
6
- import { Input } from '@client/l4_foundation/ui/shadcn/input'
7
- import { L4ChoiceGroup } from '@client/l4_foundation/ui/l4-choice-group'
8
6
  import type { L2NativeUiState } from './l2-native-ui-biz'
9
7
  import { L2NativeUiNotices } from './l2-native-ui-notices'
10
8
 
@@ -12,119 +10,88 @@ export function L2NativeUiMcp({
12
10
  available,
13
11
  state,
14
12
  disabled,
15
- onExecute
13
+ onExecute,
14
+ onRetryConnection
16
15
  }: {
17
16
  available: boolean
18
17
  state: L2NativeUiState
19
18
  disabled: boolean
20
- onExecute: (args: string, login: boolean) => void
19
+ onExecute: (args: string) => void
20
+ onRetryConnection: () => void
21
21
  }): React.JSX.Element {
22
22
  const { t } = useTranslation('workbench')
23
- const [action, setAction] = useState<'status' | 'login' | 'logout' | 'reconnect'>('status')
24
- const [name, setName] = useState('')
25
- const [confirmLogout, setConfirmLogout] = useState(false)
26
- const needsName = action !== 'status'
27
- const target = name.trim()
28
- const invalidName = needsName && !/^[a-zA-Z0-9_-]+$/.test(target)
23
+ const commandDisabled =
24
+ !state.ready || state.commandPending !== null || state.snapshot.requests.length > 0
29
25
  return (
30
26
  <div className="space-y-4">
31
27
  {available ? (
32
- <form
33
- className="space-y-3"
34
- onSubmit={(event) => {
35
- event.preventDefault()
36
- if (
37
- disabled ||
38
- !state.ready ||
39
- state.commandPending ||
40
- invalidName ||
41
- (action === 'logout' && !confirmLogout)
42
- )
43
- return
44
- onExecute(action === 'status' ? '' : `${action} ${target}`, action === 'login')
45
- }}
46
- >
47
- <L4ChoiceGroup
48
- label={t('mcpSessionActions', { defaultValue: 'MCP 会话操作' })}
49
- layout="segmented"
50
- value={action}
51
- onChange={(value) => {
52
- setAction(value)
53
- setConfirmLogout(false)
54
- }}
55
- options={[
56
- { value: 'status', label: t('mcpStatus', { defaultValue: '状态' }) },
57
- { value: 'login', label: t('mcpLogin', { defaultValue: '登录' }) },
58
- { value: 'reconnect', label: t('mcpReconnect', { defaultValue: '重连' }) },
59
- { value: 'logout', label: t('mcpLogout', { defaultValue: '退出登录' }) }
60
- ]}
61
- />
62
- {needsName ? (
63
- <label className="block space-y-1 text-sm font-medium">
64
- <span>{t('mcpServerName', { defaultValue: '服务名称' })}</span>
65
- <Input
66
- value={name}
67
- onChange={(event) => setName(event.target.value)}
68
- placeholder={t('mcpServerNamePlaceholder', { defaultValue: '输入真实服务名称' })}
69
- autoComplete="off"
70
- maxLength={128}
71
- />
72
- </label>
73
- ) : null}
74
- {needsName && target && invalidName ? (
75
- <p className="text-sm text-destructive" role="alert">
76
- {t('mcpServerNameInvalid', {
77
- defaultValue: '服务名称只能包含字母、数字、下划线和连字符。'
78
- })}
79
- </p>
80
- ) : null}
81
- {action === 'logout' ? (
82
- <label className="flex min-h-8 cursor-pointer items-start gap-2 text-sm">
83
- <input
84
- type="checkbox"
85
- className="mt-1 size-4 accent-primary"
86
- checked={confirmLogout}
87
- onChange={(event) => setConfirmLogout(event.target.checked)}
88
- />
89
- <span>
90
- {t('mcpLogoutConfirm', {
91
- defaultValue: '删除共享登录凭据,其他会话下次访问也可能需要重新登录。'
92
- })}
93
- </span>
94
- </label>
95
- ) : null}
28
+ <section aria-label={t('mcpSessionActions')} className="space-y-3">
96
29
  <div className="flex flex-wrap items-center gap-2">
97
30
  <Button
98
- type="submit"
31
+ type="button"
32
+ variant="outline"
99
33
  size="sm"
100
- disabled={
101
- disabled ||
102
- !state.ready ||
103
- state.commandPending !== null ||
104
- invalidName ||
105
- (action === 'logout' && !confirmLogout)
106
- }
34
+ disabled={commandDisabled}
35
+ onClick={() => onExecute('')}
107
36
  >
108
- {t('mcpExecute', { defaultValue: '执行' })}
37
+ <RefreshCwIcon />
38
+ {t('mcpRefreshStatus')}
109
39
  </Button>
110
- {state.commandPending ? (
111
- <span role="status" className="wrap-anywhere text-sm text-muted-foreground">
112
- {t('mcpExecuting', { defaultValue: '正在执行' })} {state.commandPending}
113
- </span>
114
- ) : null}
40
+ {(['login', 'reconnect', 'logout'] as const).map((action) => (
41
+ <Button
42
+ key={action}
43
+ type="button"
44
+ variant="ghost"
45
+ size="sm"
46
+ disabled={commandDisabled || disabled}
47
+ onClick={() => onExecute(action)}
48
+ >
49
+ {t(
50
+ action === 'login'
51
+ ? 'mcpLogin'
52
+ : action === 'reconnect'
53
+ ? 'mcpReconnect'
54
+ : 'mcpLogout'
55
+ )}
56
+ </Button>
57
+ ))}
115
58
  </div>
116
- </form>
59
+ {state.commandPending ? (
60
+ <p
61
+ role="status"
62
+ className="flex items-center gap-2 wrap-anywhere text-sm text-muted-foreground"
63
+ >
64
+ <LoaderCircleIcon className="size-4 shrink-0 animate-spin" />
65
+ {t('mcpExecuting')} {state.commandPending}
66
+ </p>
67
+ ) : disabled ? (
68
+ <p className="text-sm text-muted-foreground">{t('mcpChangesWhenIdle')}</p>
69
+ ) : null}
70
+ {state.error || !state.ready ? (
71
+ <div role="status" className="flex flex-wrap items-center gap-2 text-sm">
72
+ <span className="min-w-0 flex-1 wrap-anywhere">
73
+ {state.error ?? t('nativeUiDisconnected')}
74
+ </span>
75
+ <Button
76
+ type="button"
77
+ variant="outline"
78
+ size="sm"
79
+ disabled={state.refreshing}
80
+ onClick={onRetryConnection}
81
+ >
82
+ {t('nativeUiRetry')}
83
+ </Button>
84
+ </div>
85
+ ) : null}
86
+ </section>
117
87
  ) : (
118
- <p className="text-sm text-muted-foreground">
119
- {t('mcpCommandUnavailable', { defaultValue: '当前会话未注册 MCP 命令。' })}
120
- </p>
88
+ <p className="text-sm text-muted-foreground">{t('mcpCommandUnavailable')}</p>
121
89
  )}
122
- <div className="border-t pt-4">
123
- <h3 className="mb-3 text-sm font-medium">
124
- {t('nativeCommandOutput', { defaultValue: '会话命令输出' })}
125
- </h3>
126
- <L2NativeUiNotices notices={state.notices} />
127
- </div>
90
+ <section className="border-t pt-4">
91
+ <h3 className="mb-1 text-sm font-medium">{t('nativeCommandOutput')}</h3>
92
+ <p className="mb-3 text-sm text-muted-foreground">{t('mcpNoticesDescription')}</p>
93
+ <L2NativeUiNotices notices={[...state.notices].reverse()} />
94
+ </section>
128
95
  </div>
129
96
  )
130
97
  }
@@ -37,7 +37,7 @@ export function L2NativeUiNotices({
37
37
  const { t } = useTranslation('workbench')
38
38
  return (
39
39
  <section
40
- aria-label={t('nativeCommandOutput', { defaultValue: '会话命令输出' })}
40
+ aria-label={t('nativeCommandOutput', { defaultValue: '会话通知' })}
41
41
  className="space-y-3"
42
42
  >
43
43
  {notices.length ? (
@@ -51,7 +51,7 @@ export function L2NativeUiNotices({
51
51
  ))
52
52
  ) : (
53
53
  <p className="py-6 text-sm text-muted-foreground">
54
- {t('nativeCommandOutputEmpty', { defaultValue: '暂无会话命令输出' })}
54
+ {t('nativeCommandOutputEmpty', { defaultValue: '暂无会话通知' })}
55
55
  </p>
56
56
  )}
57
57
  </section>
@@ -236,6 +236,8 @@ export function L2NativeUiQuestions({
236
236
  const { t } = useTranslation('workbench')
237
237
  const { state } = model
238
238
  const request = state.snapshot.requests[0]
239
+ const signingIn =
240
+ state.commandPending === '/mcp login' || state.commandPending?.startsWith('/mcp login ')
239
241
  return (
240
242
  <>
241
243
  {state.error || (!state.ready && request) ? (
@@ -286,14 +288,15 @@ export function L2NativeUiQuestions({
286
288
  ) : null}
287
289
  {state.notices.length > 0 ? (
288
290
  <Collapsible
289
- defaultOpen={Boolean(request || state.commandPending?.startsWith('/mcp login '))}
291
+ key={request ? `notices:${request.id}` : signingIn ? 'mcp-login' : 'notices'}
292
+ defaultOpen={Boolean(request || signingIn)}
290
293
  className="mb-2 rounded-lg border px-3 py-2"
291
294
  data-testid="native-ui-output"
292
295
  >
293
296
  <div className="flex items-center gap-2">
294
297
  <CollapsibleTrigger className="group flex min-h-7 min-w-0 flex-1 cursor-pointer items-center gap-2 text-left text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring">
295
298
  <ChevronDownIcon className="size-3.5 shrink-0 transition-transform group-data-panel-open:rotate-180 motion-reduce:transition-none" />
296
- {t('nativeCommandOutput', { defaultValue: '会话命令输出' })}
299
+ {t('nativeCommandOutput', { defaultValue: '会话通知' })}
297
300
  </CollapsibleTrigger>
298
301
  <Button
299
302
  type="button"
@@ -31,7 +31,6 @@ export function L4DesktopTitlebar(): React.JSX.Element {
31
31
  const [state, setState] = useState<L4DesktopWindowState | null>(null)
32
32
  const [title, setTitle] = useState('Pi Desk')
33
33
  const [locale, setLocale] = useState<'zh-CN' | 'en'>('zh-CN')
34
- const [focused, setFocused] = useState(true)
35
34
 
36
35
  useEffect(() => {
37
36
  const desktop = getL4DesktopWindow()
@@ -50,25 +49,21 @@ export function L4DesktopTitlebar(): React.JSX.Element {
50
49
  void desktop
51
50
  .invoke('state')
52
51
  .then((next) => {
53
- if (!disposed) setState((current) => (current ? next : null))
52
+ if (!disposed) {
53
+ setState((current) =>
54
+ current && current.maximized !== next.maximized ? next : current
55
+ )
56
+ }
54
57
  })
55
58
  .catch((error: unknown) => {
56
59
  console.error('[desktop-window] 读取窗口状态失败', error)
57
60
  })
58
61
  })
59
62
  }
60
- const onFocus = (): void => {
61
- setFocused(true)
62
- }
63
- const onBlur = (): void => {
64
- setFocused(false)
65
- }
66
63
  const observer = new MutationObserver(readTitle)
67
64
  observer.observe(document.head, { childList: true, subtree: true, characterData: true })
68
65
  observer.observe(root, { attributes: true, attributeFilter: ['lang'] })
69
66
  window.addEventListener('resize', refreshState)
70
- window.addEventListener('focus', onFocus)
71
- window.addEventListener('blur', onBlur)
72
67
 
73
68
  // 按钮已挂载并绑定事件后才关闭系统装饰;旧页面不会调用此能力。
74
69
  void desktop
@@ -76,7 +71,6 @@ export function L4DesktopTitlebar(): React.JSX.Element {
76
71
  .then((next) => {
77
72
  if (disposed) return
78
73
  readTitle()
79
- setFocused(document.hasFocus())
80
74
  root.dataset.piDeskWindow = ''
81
75
  setState(next)
82
76
  })
@@ -90,8 +84,6 @@ export function L4DesktopTitlebar(): React.JSX.Element {
90
84
  observer.disconnect()
91
85
  cancelAnimationFrame(frame)
92
86
  window.removeEventListener('resize', refreshState)
93
- window.removeEventListener('focus', onFocus)
94
- window.removeEventListener('blur', onBlur)
95
87
  delete root.dataset.piDeskWindow
96
88
  void desktop.invoke('detach').catch((error: unknown) => {
97
89
  console.error('[desktop-window] 恢复系统标题栏失败', error)
@@ -104,7 +96,9 @@ export function L4DesktopTitlebar(): React.JSX.Element {
104
96
  if (!desktop) return
105
97
  try {
106
98
  const next = await desktop.invoke(action)
107
- if (adapter.current === desktop) setState(next)
99
+ if (adapter.current === desktop) {
100
+ setState((current) => (current?.maximized === next.maximized ? current : next))
101
+ }
108
102
  } catch (error) {
109
103
  console.error(`[desktop-window] 窗口操作失败 action=${action}`, error)
110
104
  try {
@@ -129,7 +123,6 @@ export function L4DesktopTitlebar(): React.JSX.Element {
129
123
  <header
130
124
  className="pi-desk-titlebar"
131
125
  data-testid="desktop-titlebar"
132
- data-focused={focused}
133
126
  hidden={!state}
134
127
  aria-label={text.window}
135
128
  onMouseDown={onDrag}
@@ -40,10 +40,6 @@
40
40
  white-space: nowrap;
41
41
  }
42
42
 
43
- .pi-desk-titlebar[data-focused='false'] .pi-desk-titlebar-title {
44
- color: var(--muted-foreground);
45
- }
46
-
47
43
  .pi-desk-titlebar-controls {
48
44
  display: flex;
49
45
  align-self: stretch;
@@ -17,19 +17,18 @@ export const L2_WORKBENCH_LOCALE_MESSAGES = {
17
17
  modelChanging: 'Changing model…',
18
18
  modelContextUnknown: 'Context unknown',
19
19
  mcpSessionActions: 'Session MCP actions',
20
- mcpStatus: 'Status',
20
+ mcpRefreshStatus: 'Refresh status',
21
21
  mcpLogin: 'Sign in',
22
22
  mcpReconnect: 'Reconnect',
23
23
  mcpLogout: 'Sign out',
24
- mcpServerName: 'Server name',
25
- mcpServerNamePlaceholder: 'Enter the configured server name',
26
- mcpServerNameInvalid: 'Use letters, numbers, underscores and hyphens.',
27
- mcpLogoutConfirm: 'Delete shared credentials. Other sessions may need to sign in again.',
28
- mcpExecute: 'Run',
24
+ mcpChangesWhenIdle:
25
+ 'Status is available while running. Sign in, reconnect or sign out when the task finishes.',
26
+ mcpNoticesDescription:
27
+ 'MCP status and authorization links appear here, together with other plugin notifications.',
29
28
  mcpExecuting: 'Running',
30
29
  mcpCommandUnavailable: 'No MCP command is registered in this session.',
31
- nativeCommandOutput: 'Session command output',
32
- nativeCommandOutputEmpty: 'No session command output yet',
30
+ nativeCommandOutput: 'Session notifications',
31
+ nativeCommandOutputEmpty: 'No session notifications yet',
33
32
  searchToolsSources: 'Search names, sources or descriptions',
34
33
  toolFilter: 'Tool usage',
35
34
  allTools: 'All tools',
@@ -472,19 +471,16 @@ export const L2_WORKBENCH_LOCALE_MESSAGES = {
472
471
  modelChanging: '正在切换模型…',
473
472
  modelContextUnknown: '上下文未知',
474
473
  mcpSessionActions: 'MCP 会话操作',
475
- mcpStatus: '状态',
474
+ mcpRefreshStatus: '刷新状态',
476
475
  mcpLogin: '登录',
477
476
  mcpReconnect: '重连',
478
477
  mcpLogout: '退出登录',
479
- mcpServerName: '服务名称',
480
- mcpServerNamePlaceholder: '输入已配置的服务名称',
481
- mcpServerNameInvalid: '服务名称只能包含字母、数字、下划线和连字符。',
482
- mcpLogoutConfirm: '删除共享登录凭据,其他会话下次访问也可能需要重新登录。',
483
- mcpExecute: '执行',
478
+ mcpChangesWhenIdle: '运行中可查看状态;登录、重连和退出登录需等任务结束。',
479
+ mcpNoticesDescription: '这里显示 MCP 状态、授权链接及其他插件的会话通知。',
484
480
  mcpExecuting: '正在执行',
485
481
  mcpCommandUnavailable: '当前会话未注册 MCP 命令。',
486
- nativeCommandOutput: '会话命令输出',
487
- nativeCommandOutputEmpty: '暂无会话命令输出',
482
+ nativeCommandOutput: '会话通知',
483
+ nativeCommandOutputEmpty: '暂无会话通知',
488
484
  searchToolsSources: '搜索名称、来源或描述',
489
485
  toolFilter: '工具使用方式',
490
486
  allTools: '全部工具',
@@ -55,7 +55,6 @@ export const L4_HOST_ERROR_MESSAGES = {
55
55
  capabilityRuleInvalid: 'Check the capability mode rules',
56
56
  chatContextJsonInvalid: 'Invalid JSON for system context request',
57
57
  chatContextInvalid: 'Invalid system context request',
58
- chatContextBusy: 'Session is running. System context is temporarily unavailable',
59
58
  chatContextFailed: 'Could not read system context',
60
59
  chatMessageInvalid: 'Invalid message detail request',
61
60
  chatMessageMissing: 'Chat message not found',
@@ -215,7 +214,6 @@ export const L4_HOST_ERROR_MESSAGES = {
215
214
  capabilityRuleInvalid: '请检查模式规则',
216
215
  chatContextJsonInvalid: '系统上下文请求 JSON 格式无效',
217
216
  chatContextInvalid: '系统上下文请求参数无效',
218
- chatContextBusy: '当前会话正在运行,暂时无法查看系统上下文',
219
217
  chatContextFailed: '读取系统上下文失败',
220
218
  chatMessageInvalid: '消息详情参数无效',
221
219
  chatMessageMissing: '聊天消息不存在',
@@ -6,7 +6,6 @@ import {
6
6
  L2ChatSourceBindingError
7
7
  } from '@server/l2_biz/work-session/l2-work-session-chat-runtime'
8
8
  import { getL2WorkSessionManage } from '@server/l2_biz/work-session/l2-work-session-manage'
9
- import { L4PiModelContextBusyError } from '@server/l4_foundation/pi/l4-pi-chat-worker'
10
9
  import {
11
10
  createL4ApiErrorResponse,
12
11
  createL4ApiSuccessResponse
@@ -41,9 +40,6 @@ export async function POST(request: Request): Promise<Response> {
41
40
  key: 'errors:chatSourceBusy'
42
41
  })
43
42
  }
44
- if (error instanceof L4PiModelContextBusyError) {
45
- return createL4ApiErrorResponse(409, error.message, { key: 'errors:chatContextBusy' })
46
- }
47
43
 
48
44
  console.error('[Pi Desk][ChatModelContextApi] 读取系统上下文失败', {
49
45
  workId: input.data.source.workId,
@@ -127,13 +127,6 @@ export class L4PiDirectBashRunningError extends Error {
127
127
  }
128
128
  }
129
129
 
130
- export class L4PiModelContextBusyError extends Error {
131
- constructor() {
132
- super('当前会话正在运行,暂时无法查看系统上下文')
133
- this.name = 'L4PiModelContextBusyError'
134
- }
135
- }
136
-
137
130
  export interface L4PiChatContextUsage {
138
131
  tokens: number | null
139
132
  contextWindow: number
@@ -803,19 +796,7 @@ export class L4PiChatWorker {
803
796
  }
804
797
 
805
798
  async readModelContext(): Promise<L4PiModelContext> {
806
- if (
807
- this.startingAcceptedInput ||
808
- this.acceptedInputs.length > 0 ||
809
- this.activeValidation ||
810
- this.pendingPrompt ||
811
- this.activeCompactionTempId ||
812
- this.observedRunPromises.size > 0
813
- ) {
814
- throw new L4PiModelContextBusyError()
815
- }
816
-
817
799
  const session = await this.ensureSession()
818
- if (!session.isIdle || session.isBashRunning) throw new L4PiModelContextBusyError()
819
800
 
820
801
  const activeToolNames = new Set(session.getActiveToolNames())
821
802
  const tools = session