dsh-monkeyai-sessions 0.1.0 → 0.1.2

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.
@@ -91,7 +91,7 @@ const PANEL_CSS = `
91
91
  `
92
92
 
93
93
  function errorText(error) {
94
- return typeof error?.message === 'string' ? error.message : typeof error === 'string' ? error : 'Remote operation failed'
94
+ return typeof error?.message === 'string' ? error.message : typeof error === 'string' ? error : '远程操作失败'
95
95
  }
96
96
 
97
97
  // Lightweight inline markdown: fenced code blocks, `code`, **bold**,
@@ -221,8 +221,8 @@ function ApprovalCard({ request, hasControl, onRespond, answerText, setAnswerTex
221
221
  const disabled = !hasControl
222
222
  return h('div', { style: style.approval, role: 'alert' },
223
223
  h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
224
- h('span', { style: { fontSize: 12.5, fontWeight: 650 } }, isPermission ? 'Permission requested' : 'MonkeyAI asks for input'),
225
- h('span', { style: { fontSize: 11, color: t.text3 } }, 'responding requires session:control')),
224
+ h('span', { style: { fontSize: 12.5, fontWeight: 650 } }, isPermission ? '请求授权' : 'MonkeyAI 需要你的输入'),
225
+ h('span', { style: { fontSize: 11, color: t.text3 } }, '回复需要 session:control 权限')),
226
226
  questionText !== '' ? h('div', { style: { fontSize: 12.5, color: t.text2, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, questionText) : null,
227
227
  options.length > 0
228
228
  ? h('div', { style: style.row },
@@ -230,26 +230,26 @@ function ApprovalCard({ request, hasControl, onRespond, answerText, setAnswerTex
230
230
  type: 'button', style: option.kind === 'allow_once' || option.kind === 'allow_always' ? style.primaryButton : style.button,
231
231
  key: option.optionId, disabled, onClick: () => onRespond(request, { outcome: 'selected', optionId: option.optionId }),
232
232
  }, option.name || option.optionId)),
233
- h('button', { type: 'button', style: style.button, disabled, onClick: () => onRespond(request, { outcome: 'cancelled' }) }, 'Decline'))
233
+ h('button', { type: 'button', style: style.button, disabled, onClick: () => onRespond(request, { outcome: 'cancelled' }) }, '拒绝'))
234
234
  : null,
235
235
  options.length === 0 && !isPermission
236
236
  ? h(React.Fragment, null,
237
237
  h('textarea', {
238
238
  style: { ...style.textarea, minHeight: 44 },
239
- placeholder: disabled ? 'Read-only: session:control is required to answer' : 'Type your answer',
239
+ placeholder: disabled ? '只读模式:回复需要 session:control 权限' : '输入你的回答',
240
240
  value: answerText, disabled,
241
241
  onChange: event => setAnswerText(event.target.value),
242
242
  }),
243
243
  h('div', { style: style.row },
244
- h('button', { type: 'button', style: style.primaryButton, disabled: disabled || answerText.trim() === '', onClick: () => onAnswer(request) }, 'Reply'),
245
- h('button', { type: 'button', style: style.button, disabled, onClick: () => onRespond(request, { action: 'decline' }) }, 'Decline')))
244
+ h('button', { type: 'button', style: style.primaryButton, disabled: disabled || answerText.trim() === '', onClick: () => onAnswer(request) }, '回复'),
245
+ h('button', { type: 'button', style: style.button, disabled, onClick: () => onRespond(request, { action: 'decline' }) }, '拒绝')))
246
246
  : null,
247
247
  options.length === 0 && isPermission
248
248
  ? h('div', { style: style.row },
249
- h('button', { type: 'button', style: style.button, disabled, onClick: () => onRespond(request, { outcome: 'cancelled' }) }, 'Decline'))
249
+ h('button', { type: 'button', style: style.button, disabled, onClick: () => onRespond(request, { outcome: 'cancelled' }) }, '拒绝'))
250
250
  : null,
251
251
  h('details', { style: style.details },
252
- h('summary', { style: style.summary }, 'Request details'),
252
+ h('summary', { style: style.summary }, '请求详情'),
253
253
  h('pre', { style: { margin: '6px 0 0', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', fontSize: 11.5 } }, JSON.stringify(request.data, null, 2))),
254
254
  )
255
255
  }
@@ -271,7 +271,7 @@ function RemoteSessionPage({ api }) {
271
271
  const [pendingRequests, setPendingRequests] = useState([])
272
272
  const [prompt, setPrompt] = useState('')
273
273
  const [answerText, setAnswerText] = useState('')
274
- const [status, setStatus] = useState('Disconnected')
274
+ const [status, setStatus] = useState('未连接')
275
275
  const [error, setError] = useState('')
276
276
  const [loading, setLoading] = useState(false)
277
277
  const [lastDelivery, setLastDelivery] = useState(undefined)
@@ -353,7 +353,7 @@ function RemoteSessionPage({ api }) {
353
353
  if (loading || !serverAddress || !signInEmail.trim() || signInPassword === '') return
354
354
  setError('')
355
355
  setLoading(true)
356
- setStatus('Signing in to MonkeyAI')
356
+ setStatus('正在登录 MonkeyAI')
357
357
  try {
358
358
  const account = await unwrap(api.signIn({
359
359
  serverAddress,
@@ -364,7 +364,7 @@ function RemoteSessionPage({ api }) {
364
364
  adoptAccount(account)
365
365
  } catch (reason) {
366
366
  setError(errorText(reason))
367
- setStatus('Sign-in failed')
367
+ setStatus('登录失败')
368
368
  } finally {
369
369
  setLoading(false)
370
370
  }
@@ -380,9 +380,9 @@ function RemoteSessionPage({ api }) {
380
380
  setAuthorizationMode(mode)
381
381
  setCopied(false)
382
382
  if (mode === 'loopback') {
383
- setStatus('Waiting for approval')
383
+ setStatus('等待批准')
384
384
  } else {
385
- setStatus('Approve access, then paste the callback URL')
385
+ setStatus('批准授权后,粘贴回调 URL')
386
386
  }
387
387
  // Keep the opener so the loopback page can post the callback back.
388
388
  // Without it the browser cannot return the code automatically.
@@ -433,12 +433,12 @@ function RemoteSessionPage({ api }) {
433
433
  setAccounts(current => [result.account, ...current.filter(item => item.accountId !== result.account.accountId)])
434
434
  setAccountId(result.account.accountId)
435
435
  setServerAddress(result.account.serverOrigin)
436
- setStatus('MonkeyAI account connected')
436
+ setStatus('MonkeyAI 账号已连接')
437
437
  } else if (result.status === 'failed') {
438
- setError(result.error || 'Automatic authorization failed; paste the callback URL instead.')
439
- setStatus('Automatic authorization failed')
438
+ setError(result.error || '自动授权失败,请改为粘贴回调 URL。')
439
+ setStatus('自动授权失败')
440
440
  } else {
441
- setStatus('Authorization request expired')
441
+ setStatus('授权请求已过期')
442
442
  }
443
443
  setAuthorizationRequestId('')
444
444
  setAuthorizationUrl('')
@@ -452,7 +452,7 @@ function RemoteSessionPage({ api }) {
452
452
  setAccounts(current => [account, ...current.filter(item => item.accountId !== account.accountId)])
453
453
  setAccountId(account.accountId)
454
454
  setServerAddress(account.serverOrigin)
455
- setStatus('MonkeyAI account connected')
455
+ setStatus('MonkeyAI 账号已连接')
456
456
  setAddingServer(false)
457
457
  }
458
458
 
@@ -477,9 +477,9 @@ function RemoteSessionPage({ api }) {
477
477
  try {
478
478
  await navigator.clipboard.writeText(authorizationUrl)
479
479
  setCopied(true)
480
- setStatus('Authorization link copied')
480
+ setStatus('授权链接已复制')
481
481
  } catch {
482
- setStatus('Copy failed - select the link text manually')
482
+ setStatus('复制失败,请手动选中链接文本')
483
483
  }
484
484
  }
485
485
 
@@ -497,7 +497,7 @@ function RemoteSessionPage({ api }) {
497
497
  setSessionId('')
498
498
  streamRef.current?.dispose()
499
499
  streamRef.current = null
500
- setStatus(result.items.length > 0 ? `${result.items.length} device(s) found` : 'No devices registered')
500
+ setStatus(result.items.length > 0 ? `${result.items.length} device(s) found` : '暂无已注册设备')
501
501
  if (first) await loadSessions(first)
502
502
  } catch (reason) {
503
503
  setError(errorText(reason))
@@ -522,10 +522,10 @@ function RemoteSessionPage({ api }) {
522
522
  ])
523
523
  const errors = []
524
524
  if (results[0].status === 'fulfilled') setSummarySessions(results[0].value.items)
525
- else errors.push(`Summary index: ${errorText(results[0].reason)}`)
525
+ else errors.push(`会话摘要索引:${errorText(results[0].reason)}`)
526
526
  if (results[1].status === 'fulfilled') setRuntimeSessions(results[1].value.sessions)
527
- else errors.push(`ACP session list: ${errorText(results[1].reason)}`)
528
- setStatus(errors.length === 0 ? 'Sessions refreshed' : 'Some session sources are unavailable')
527
+ else errors.push(`ACP 会话列表:${errorText(results[1].reason)}`)
528
+ setStatus(errors.length === 0 ? '会话已刷新' : '部分会话来源不可用')
529
529
  setError(errors.join('\n'))
530
530
  setLoading(false)
531
531
  }
@@ -545,38 +545,38 @@ function RemoteSessionPage({ api }) {
545
545
  setCurrentModel('')
546
546
  setVisibleCount(RECENT_LIMIT)
547
547
  setHistoryLoading(true)
548
- setStatus('Connecting to remote session')
548
+ setStatus('正在连接远程会话')
549
549
  try {
550
550
  const stream = api.followSession({ accountId, machineId, sessionId: session.id, cwd: session.cwd })
551
551
  streamRef.current = stream
552
- setStatus('Remote session connected')
552
+ setStatus('远程会话已连接')
553
553
  for await (const frame of stream) {
554
554
  setFrames(current => [...current, frame])
555
- if (frame.type === 'ready' && !historyLoadedRef.current) setStatus('Replaying session history')
556
- if (frame.type === 'reconnecting') setStatus(frame.message || 'Reconnecting to remote runtime')
555
+ if (frame.type === 'ready' && !historyLoadedRef.current) setStatus('正在回放会话历史')
556
+ if (frame.type === 'reconnecting') setStatus(frame.message || '正在重连远程 runtime')
557
557
  if (frame.type === 'history_loaded') {
558
558
  historyLoadedRef.current = true
559
- setStatus('History loaded')
559
+ setStatus('历史已加载')
560
560
  setHistoryLoading(false)
561
561
  if (frame.model) setCurrentModel(frame.model)
562
562
  }
563
563
  if (frame.type === 'history_incomplete') {
564
- setStatus('History incomplete: ' + (frame.message || 'replay was cut off'))
564
+ setStatus('历史不完整:' + (frame.message || '回放被中断'))
565
565
  setHistoryLoading(false)
566
566
  }
567
567
  if (frame.type === 'model_switched') {
568
568
  if (frame.data?.model) setCurrentModel(frame.data.model)
569
569
  setSwitchingModel(false)
570
- setStatus('Model switched')
570
+ setStatus('模型已切换')
571
571
  }
572
572
  if (frame.type === 'model_switch_failed') {
573
573
  setSwitchingModel(false)
574
- setError(frame.message || 'Model switch was rejected')
575
- setStatus('Model switch failed')
574
+ setError(frame.message || '模型切换被拒绝')
575
+ setStatus('模型切换失败')
576
576
  }
577
577
  if (frame.type === 'session_missing') {
578
- setError(frame.message || 'This session no longer exists on the selected device.')
579
- setStatus('Session not found on device')
578
+ setError(frame.message || '该会话在所选设备上已不存在。')
579
+ setStatus('设备上找不到该会话')
580
580
  setHistoryLoading(false)
581
581
  }
582
582
  if (frame.type === 'request') {
@@ -589,31 +589,31 @@ function RemoteSessionPage({ api }) {
589
589
  setStopping(false)
590
590
  if (accepted) {
591
591
  const cancelled = frame.data?.result?.stopReason === 'cancelled'
592
- setStatus(cancelled ? 'Turn stopped' : 'Turn completed')
592
+ setStatus(cancelled ? '回合已停止' : '回合已完成')
593
593
  } else {
594
594
  setPrompt(current => current === '' ? (lastDraftRef.current || '') : current)
595
- setStatus('MonkeyAI did not accept the instruction; draft preserved')
595
+ setStatus('MonkeyAI 未接受该指令,草稿已保留')
596
596
  if (frame.message) setError(frame.message)
597
597
  }
598
598
  }
599
599
  if (frame.type === 'cancel_sent') {
600
600
  setStopping(false)
601
- setStatus('Stop requested - waiting for the runtime to cancel')
601
+ setStatus('已请求停止,等待 runtime 取消')
602
602
  }
603
603
  if (frame.type === 'response_sent') {
604
604
  setPendingRequests(current => current.filter(item => item.requestId !== frame.requestId))
605
605
  }
606
606
  if (frame.type === 'response_error') {
607
- setError(`Remote response was rejected: ${frame.code || 'unknown'}`)
607
+ setError(`远程回复被拒绝:${frame.code || '未知'}`)
608
608
  }
609
- if (frame.type === 'error') setError(frame.message || frame.code || 'Remote session error')
609
+ if (frame.type === 'error') setError(frame.message || frame.code || '远程会话错误')
610
610
  }
611
- setStatus('Remote session disconnected')
611
+ setStatus('远程会话已断开')
612
612
  setHistoryLoading(false)
613
613
  setTurnActive(false)
614
614
  setStopping(false)
615
615
  } catch (reason) {
616
- setStatus('Remote session unavailable')
616
+ setStatus('远程会话不可用')
617
617
  setError(errorText(reason))
618
618
  setHistoryLoading(false)
619
619
  setTurnActive(false)
@@ -632,7 +632,7 @@ function RemoteSessionPage({ api }) {
632
632
  setPrompt('')
633
633
  setTurnActive(true)
634
634
  setStopping(false)
635
- setStatus('Instruction sent - MonkeyAI is working')
635
+ setStatus('指令已发送,MonkeyAI 正在处理')
636
636
  } catch (reason) {
637
637
  setError(errorText(reason))
638
638
  }
@@ -644,7 +644,7 @@ function RemoteSessionPage({ api }) {
644
644
  try {
645
645
  stream.send({ type: 'cancel' })
646
646
  setStopping(true)
647
- setStatus('Stopping…')
647
+ setStatus('停止中…')
648
648
  } catch (reason) {
649
649
  setError(errorText(reason))
650
650
  }
@@ -652,7 +652,7 @@ function RemoteSessionPage({ api }) {
652
652
 
653
653
  function respond(request, result) {
654
654
  if (!hasControl) {
655
- setError('MonkeyAI response requires session:control permission')
655
+ setError('回复 MonkeyAI 需要 session:control 权限')
656
656
  return
657
657
  }
658
658
  try {
@@ -684,7 +684,7 @@ function RemoteSessionPage({ api }) {
684
684
  setCallbackUrl('')
685
685
  setAuthorizationUrl('')
686
686
  setAuthorizationMode('')
687
- setStatus('Disconnected')
687
+ setStatus('未连接')
688
688
  setAddingServer(false)
689
689
  } catch (reason) {
690
690
  setError(errorText(reason))
@@ -739,7 +739,7 @@ function RemoteSessionPage({ api }) {
739
739
  setDevices(current => [entry, ...current.filter(item => item.machineId !== machineId)])
740
740
  setMachineId(current => current || machineId)
741
741
  }
742
- setStatus(`Added device ${entry.displayName}`)
742
+ setStatus(`已添加设备 ${entry.displayName}`)
743
743
  }
744
744
 
745
745
  function removeManualDevice(machineId) {
@@ -757,14 +757,14 @@ function RemoteSessionPage({ api }) {
757
757
  if (created.model) setCurrentModel(created.model)
758
758
  setRuntimeSessions(current => {
759
759
  if (current.some(item => item.id === created.sessionId)) return current
760
- return [{ id: created.sessionId, title: created.title || 'New remote session', cwd: created.cwd }, ...current]
760
+ return [{ id: created.sessionId, title: created.title || '新建远程会话', cwd: created.cwd }, ...current]
761
761
  })
762
762
  setSummarySessions(current => current.filter(item => item.id !== created.sessionId))
763
- setStatus('Session created')
764
- await openSession({ id: created.sessionId, title: created.title || 'New remote session', cwd: created.cwd })
763
+ setStatus('会话已创建')
764
+ await openSession({ id: created.sessionId, title: created.title || '新建远程会话', cwd: created.cwd })
765
765
  } catch (reason) {
766
766
  setError(errorText(reason))
767
- setStatus('Could not create a remote session')
767
+ setStatus('无法创建远程会话')
768
768
  } finally {
769
769
  setNewSessionBusy(false)
770
770
  }
@@ -780,10 +780,10 @@ function RemoteSessionPage({ api }) {
780
780
  // Switch on the live session connection: a separate connection cannot
781
781
  // see the mounted session and the runtime rejects the switch.
782
782
  stream.send({ type: 'switch_model', model: modelId })
783
- setStatus('Switching model…')
783
+ setStatus('正在切换模型…')
784
784
  } catch (reason) {
785
785
  setError(errorText(reason))
786
- setStatus('Model switch failed')
786
+ setStatus('模型切换失败')
787
787
  setSwitchingModel(false)
788
788
  }
789
789
  }
@@ -841,32 +841,32 @@ function RemoteSessionPage({ api }) {
841
841
  h('header', { style: style.header },
842
842
  h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
843
843
  h(MonkeyIcon, { size: 22 }),
844
- h('h1', { style: style.title }, 'MonkeyAI Sessions')),
844
+ h('h1', { style: style.title }, 'MonkeyAI 会话')),
845
845
  h(StatusBadge, { status, live: isLive })),
846
846
  error ? h('div', { role: 'alert', style: style.error },
847
847
  h('span', { style: { flex: 1 } }, error),
848
848
  h('button', {
849
- type: 'button', 'aria-label': 'Dismiss error', onClick: () => setError(''),
849
+ type: 'button', 'aria-label': '关闭错误提示', onClick: () => setError(''),
850
850
  style: { border: 0, background: 'transparent', cursor: 'pointer', color: t.danger, fontWeight: 700, padding: '0 2px', fontSize: 13 },
851
851
  }, '×')) : null,
852
852
  showConnectCard ? h('section', { style: style.card },
853
853
  addingServer && !authorizing ? h('div', { style: style.row },
854
- h('h2', { style: { ...style.sectionTitle, flex: 1 } }, 'Add MonkeyAI server'),
855
- h('button', { type: 'button', style: style.button, onClick: () => { setAddingServer(false); setServerAddress(currentAccount?.serverOrigin || ''); setStatus('MonkeyAI account connected') } }, 'Cancel')) : null,
854
+ h('h2', { style: { ...style.sectionTitle, flex: 1 } }, '添加 MonkeyAI 服务器'),
855
+ h('button', { type: 'button', style: style.button, onClick: () => { setAddingServer(false); setServerAddress(currentAccount?.serverOrigin || ''); setStatus('MonkeyAI 账号已连接') } }, '取消')) : null,
856
856
  authorizing ? h(React.Fragment, null,
857
857
  h('div', { style: style.row },
858
- h('h2', { style: { ...style.sectionTitle, flex: 1 } }, addingServer ? 'Authorizing a new server' : 'Waiting for authorization'),
859
- addingServer ? h('button', { type: 'button', style: style.button, onClick: () => { setAuthorizationRequestId(''); setAuthorizationUrl(''); setAuthorizationMode(''); setCallbackUrl(''); setAddingServer(false); setStatus('MonkeyAI account connected') } }, 'Cancel') : null),
858
+ h('h2', { style: { ...style.sectionTitle, flex: 1 } }, addingServer ? '正在授权新服务器' : '等待授权'),
859
+ addingServer ? h('button', { type: 'button', style: style.button, onClick: () => { setAuthorizationRequestId(''); setAuthorizationUrl(''); setAuthorizationMode(''); setCallbackUrl(''); setAddingServer(false); setStatus('MonkeyAI 账号已连接') } }, '取消') : null),
860
860
  h('ol', { style: { margin: 0, paddingLeft: 18, display: 'grid', gap: 6, fontSize: 12.5, color: t.text2 } },
861
- h('li', null, 'Open the authorization page and approve access in MonkeyAI.',
861
+ h('li', null, '打开授权页并在 MonkeyAI 中批准访问。',
862
862
  h('span', { style: style.row, title: authorizationUrl },
863
- h('a', { href: authorizationUrl, target: '_blank', rel: 'noopener noreferrer', style: { color: t.accent, fontWeight: 600, fontSize: 12 } }, 'Open page'),
864
- h('button', { type: 'button', style: style.button, onClick: copyAuthorizationUrl }, copied ? 'Copied' : 'Copy link'))),
863
+ h('a', { href: authorizationUrl, target: '_blank', rel: 'noopener noreferrer', style: { color: t.accent, fontWeight: 600, fontSize: 12 } }, '打开授权页'),
864
+ h('button', { type: 'button', style: style.button, onClick: copyAuthorizationUrl }, copied ? '已复制' : '复制链接'))),
865
865
  authorizationMode === 'loopback'
866
- ? h('li', null, 'Approve in the browser - this panel completes automatically. If the browser cannot return to DSH, copy the callback URL below.')
867
- : h('li', null, 'After approving, the browser shows the monkeyai-desktop:// callback - copy its full address-bar URL and paste it below.')),
866
+ ? h('li', null, '在浏览器中批准后面板会自动完成。若浏览器无法回传到 DSH,请复制回调 URL 粘贴到下方。')
867
+ : h('li', null, '批准后浏览器会显示 monkeyai-desktop:// 回调,复制地址栏完整 URL 粘贴到下方。')),
868
868
  h('details', { style: style.details },
869
- h('summary', { style: style.summary }, 'Automatic completion did not finish? Paste the callback'),
869
+ h('summary', { style: style.summary }, '没有自动完成?手动粘贴回调'),
870
870
  h('div', { style: { ...style.row, paddingTop: 8 } },
871
871
  h('input', {
872
872
  type: 'url',
@@ -877,14 +877,14 @@ function RemoteSessionPage({ api }) {
877
877
  style: { ...style.input, flex: '1 1 260px' },
878
878
  onChange: event => setCallbackUrl(event.target.value),
879
879
  }),
880
- h('button', { type: 'button', style: style.primaryButton, disabled: loading || !callbackUrl, onClick: completeAuthorization }, 'Complete authorization'))))
880
+ h('button', { type: 'button', style: style.primaryButton, disabled: loading || !callbackUrl, onClick: completeAuthorization }, '完成授权'))))
881
881
  : h(React.Fragment, null,
882
- h('h2', { style: style.sectionTitle }, 'Connect MonkeyAI'),
882
+ h('h2', { style: style.sectionTitle }, '连接 MonkeyAI'),
883
883
  h('form', {
884
884
  style: { display: 'grid', gap: 10 },
885
885
  onSubmit: event => { event.preventDefault(); signIn() },
886
886
  },
887
- h(Field, { labelText: 'MonkeyAI server origin' },
887
+ h(Field, { labelText: 'MonkeyAI 服务器地址' },
888
888
  h('input', {
889
889
  type: 'url',
890
890
  value: serverAddress,
@@ -893,7 +893,7 @@ function RemoteSessionPage({ api }) {
893
893
  autoComplete: 'url',
894
894
  onChange: event => setServerAddress(event.target.value),
895
895
  })),
896
- h(Field, { labelText: 'Account email' },
896
+ h(Field, { labelText: '账号邮箱' },
897
897
  h('input', {
898
898
  type: 'email',
899
899
  value: signInEmail,
@@ -902,11 +902,11 @@ function RemoteSessionPage({ api }) {
902
902
  autoComplete: 'username',
903
903
  onChange: event => setSignInEmail(event.target.value),
904
904
  })),
905
- h(Field, { labelText: 'Password' },
905
+ h(Field, { labelText: '密码' },
906
906
  h('input', {
907
907
  type: 'password',
908
908
  value: signInPassword,
909
- placeholder: 'MonkeyAI account password',
909
+ placeholder: 'MonkeyAI 账号密码',
910
910
  style: style.input,
911
911
  autoComplete: 'current-password',
912
912
  onChange: event => setSignInPassword(event.target.value),
@@ -914,70 +914,70 @@ function RemoteSessionPage({ api }) {
914
914
  h('button', {
915
915
  type: 'submit', style: style.primaryButton,
916
916
  disabled: loading || !serverAddress || !signInEmail.trim() || signInPassword === '',
917
- }, loading ? 'Signing in…' : 'Sign in'),
917
+ }, loading ? '登录中…' : '登录'),
918
918
  h('p', { style: { margin: 0, fontSize: 11.5, color: t.text3 } },
919
- 'Sign-in runs on the DSH host and works on phones. Your password is used once to authorize this plugin and is never stored.')),
919
+ '登录在 DSH Host 上完成,手机同样可用。密码仅用于本次授权,不会被存储。')),
920
920
  h('details', { style: style.details },
921
- h('summary', { style: style.summary }, 'Use browser authorization instead'),
921
+ h('summary', { style: style.summary }, '改用浏览器授权'),
922
922
  h('div', { style: { display: 'grid', gap: 8, paddingTop: 8 } },
923
- h('button', { type: 'button', style: style.button, disabled: loading || !serverAddress, onClick: () => beginAuthorization('loopback') }, 'Authorize in browser'),
924
- h('button', { type: 'button', style: style.button, disabled: loading || !serverAddress, onClick: () => beginAuthorization('scheme') }, 'Authorize (paste callback)'),
925
- h('p', { style: { margin: 0, fontSize: 12, color: t.text3 } }, 'Browser authorization needs the callback to reach this host. On a phone, use sign-in above instead.'))),
926
- accounts.length > 0 ? h('p', { style: { margin: 0, fontSize: 12, color: t.text3 } }, 'Connected accounts appear in the account selector above the device list.') : null))
923
+ h('button', { type: 'button', style: style.button, disabled: loading || !serverAddress, onClick: () => beginAuthorization('loopback') }, '在浏览器中授权'),
924
+ h('button', { type: 'button', style: style.button, disabled: loading || !serverAddress, onClick: () => beginAuthorization('scheme') }, '授权(粘贴回调)'),
925
+ h('p', { style: { margin: 0, fontSize: 12, color: t.text3 } }, '浏览器授权需要回调能访问到本机 Host。手机上请改用上方账号登录。'))),
926
+ accounts.length > 0 ? h('p', { style: { margin: 0, fontSize: 12, color: t.text3 } }, '已连接的账号会出现在设备列表上方的账号选择器中。') : null))
927
927
  : null,
928
928
  currentAccount ? h('section', { style: style.card },
929
929
  h('div', { style: style.row },
930
- h('h2', { style: { ...style.sectionTitle, flex: 1 } }, 'Remote sessions'),
930
+ h('h2', { style: { ...style.sectionTitle, flex: 1 } }, '远程会话'),
931
931
  h('span', { style: { fontSize: 11, color: t.text3 } }, currentAccount.scopes.filter(s => s !== 'device:connect').join(' · '))),
932
932
  h('div', { style: style.row },
933
- h('select', { style: { ...style.input, flex: '1 1 200px' }, value: accountId, onChange: event => setAccountId(event.target.value), 'aria-label': 'Authorized account' },
933
+ h('select', { style: { ...style.input, flex: '1 1 200px' }, value: accountId, onChange: event => setAccountId(event.target.value), 'aria-label': '已授权账号' },
934
934
  accounts.map(account => h('option', { key: account.accountId, value: account.accountId }, account.serverOrigin))),
935
- h('button', { type: 'button', style: { ...style.button, flex: '0 0 auto', padding: '6px 10px', fontWeight: 700 }, disabled: loading || addingServer, onClick: () => { setAddingServer(true); setServerAddress(''); setAuthorizationRequestId(''); setAuthorizationUrl(''); setAuthorizationMode(''); setCallbackUrl(''); setStatus('Enter a MonkeyAI server origin') }, title: 'Authorize an additional MonkeyAI server' }, '+ Add server'),
935
+ h('button', { type: 'button', style: { ...style.button, flex: '0 0 auto', padding: '6px 10px', fontWeight: 700 }, disabled: loading || addingServer, onClick: () => { setAddingServer(true); setServerAddress(''); setAuthorizationRequestId(''); setAuthorizationUrl(''); setAuthorizationMode(''); setCallbackUrl(''); setStatus('请输入 MonkeyAI 服务器地址') }, title: '授权另一个 MonkeyAI 服务器' }, '+ 添加服务器'),
936
936
  h('select', { style: { ...style.input, flex: '1 1 220px' }, value: machineId, onChange: event => {
937
937
  const next = event.target.value
938
938
  setMachineId(next)
939
939
  if (next) void loadSessions(next)
940
- }, 'aria-label': 'Remote device' },
941
- h('option', { value: '' }, 'Select a device'),
942
- allDevices.map(device => h('option', { key: device.machineId, value: device.machineId }, `${device.displayName}${device.status === 'online' ? '' : ` · ${device.status}`}${device.stale ? ' · stale' : ''}`))),
943
- h('button', { type: 'button', style: style.button, disabled: loading, onClick: machineId ? () => loadSessions() : loadDevices, title: machineId ? 'Refresh sessions' : 'Refresh devices' }, 'Refresh'),
944
- h('button', { type: 'button', style: style.dangerButton, disabled: !accountId, onClick: disconnectAccount, title: 'Revoke this account authorization' }, 'Disconnect')),
945
- !hasRead ? h('p', { style: { margin: 0, fontSize: 12, color: t.text3 } }, 'session:read is required to list and read remote sessions.') : null,
940
+ }, 'aria-label': '远程设备' },
941
+ h('option', { value: '' }, '选择设备'),
942
+ allDevices.map(device => h('option', { key: device.machineId, value: device.machineId }, `${device.displayName}${device.status === 'online' ? '' : ` · ${device.status === 'offline' ? '离线' : device.status}`}${device.stale ? ' · 已过期' : ''}`))),
943
+ h('button', { type: 'button', style: style.button, disabled: loading, onClick: machineId ? () => loadSessions() : loadDevices, title: machineId ? '刷新会话' : '刷新设备' }, '刷新'),
944
+ h('button', { type: 'button', style: style.dangerButton, disabled: !accountId, onClick: disconnectAccount, title: '撤销该账号授权' }, '断开连接')),
945
+ !hasRead ? h('p', { style: { margin: 0, fontSize: 12, color: t.text3 } }, '列出和读取远程会话需要 session:read 权限。') : null,
946
946
  manualDevices.length > 0
947
947
  ? h('div', { style: style.row },
948
948
  manualDevices.map(device => h('span', { key: device.machineId, style: style.chip },
949
949
  device.displayName,
950
950
  h('button', {
951
951
  type: 'button', style: { border: 0, background: 'transparent', cursor: 'pointer', padding: 0, color: t.danger, fontWeight: 700, fontSize: 12 },
952
- 'aria-label': `Remove ${device.displayName}`,
952
+ 'aria-label': `移除 ${device.displayName}`,
953
953
  onClick: () => removeManualDevice(device.machineId),
954
954
  }, '×'))))
955
955
  : null,
956
956
  h('details', { style: style.details },
957
- h('summary', { style: style.summary }, 'Device missing? Add it by machine ID'),
957
+ h('summary', { style: style.summary }, '找不到设备?按 machine ID 添加'),
958
958
  h('div', { style: { display: 'grid', gap: 8, paddingTop: 8 } },
959
959
  h('div', { style: style.row },
960
960
  h('input', {
961
961
  style: { ...style.input, flex: '2 1 200px' },
962
962
  value: manualDeviceId,
963
- placeholder: 'Machine ID, e.g. 6f1c2a34-... or headless-host-01',
963
+ placeholder: 'Machine ID,例如 6f1c2a34-... 或 headless-host-01',
964
964
  onChange: event => setManualDeviceId(event.target.value),
965
965
  }),
966
966
  h('input', {
967
967
  style: { ...style.input, flex: '1 1 140px' },
968
968
  value: manualDeviceName,
969
- placeholder: 'Display name (optional)',
969
+ placeholder: '显示名称(可选)',
970
970
  onChange: event => setManualDeviceName(event.target.value),
971
971
  }),
972
- h('button', { type: 'button', style: style.button, disabled: !manualDeviceId.trim(), onClick: addManualDevice }, 'Add')))),
972
+ h('button', { type: 'button', style: style.button, disabled: !manualDeviceId.trim(), onClick: addManualDevice }, '添加')))),
973
973
  h('details', { style: style.details },
974
- h('summary', { style: style.summary }, 'New remote session'),
974
+ h('summary', { style: style.summary }, '新建远程会话'),
975
975
  h('div', { style: { display: 'grid', gap: 8, paddingTop: 8 } },
976
976
  h('div', { style: style.row },
977
977
  h('input', {
978
978
  style: { ...style.input, flex: '2 1 200px' },
979
979
  value: newSessionCwd,
980
- placeholder: 'Working directory, e.g. /root',
980
+ placeholder: '工作目录,例如 /root',
981
981
  onChange: event => setNewSessionCwd(event.target.value),
982
982
  }),
983
983
  h('select', {
@@ -985,16 +985,16 @@ function RemoteSessionPage({ api }) {
985
985
  value: selectedModel,
986
986
  disabled: models.length === 0,
987
987
  onChange: event => setSelectedModel(event.target.value),
988
- 'aria-label': 'Model for the new session',
988
+ 'aria-label': '新会话使用的模型',
989
989
  }, models.map(m => h('option', { key: m.id, value: m.id }, m.displayName))),
990
990
  h('button', {
991
991
  type: 'button', style: style.primaryButton, flex: '0 0 auto',
992
992
  disabled: loading || !machineId || !newSessionCwd.trim() || newSessionBusy,
993
993
  onClick: createRemoteSession,
994
- }, newSessionBusy ? 'Creating...' : 'Create')))),
994
+ }, newSessionBusy ? '创建中…' : '创建')))),
995
995
  sessionOptions.length > 0
996
996
  ? h('div', { style: style.sessions }, sessionOptions.map(session => {
997
- const flag = session.stale ? 'stale' : session.deviceStatus && session.deviceStatus !== 'online' ? session.deviceStatus : ''
997
+ const flag = session.stale ? '已过期' : session.deviceStatus && session.deviceStatus !== 'online' ? (session.deviceStatus === 'offline' ? '离线' : session.deviceStatus) : ''
998
998
  return h('button', {
999
999
  style: { ...style.session, ...(session.id === sessionId ? style.selected : {}) },
1000
1000
  className: 'mai-card-hover',
@@ -1002,26 +1002,26 @@ function RemoteSessionPage({ api }) {
1002
1002
  type: 'button',
1003
1003
  disabled: !session.cwd,
1004
1004
  onClick: () => openSession(session),
1005
- title: session.cwd ? `Open ${session.cwd}` : 'ACP session list did not provide a working directory',
1005
+ title: session.cwd ? `打开 ${session.cwd}` : 'ACP 会话列表未提供工作目录',
1006
1006
  },
1007
1007
  h('span', { style: style.sessionTitle }, session.title || session.id),
1008
- h('span', { style: style.sessionMeta }, session.cwd || 'History requires a working directory from the runtime'),
1008
+ h('span', { style: style.sessionMeta }, session.cwd || '加载历史需要 runtime 提供工作目录'),
1009
1009
  flag !== '' ? h('span', { style: { fontSize: 10.5, fontWeight: 650, color: session.stale ? t.text3 : '#8a6d1d', textTransform: 'uppercase', letterSpacing: '0.04em' } }, flag) : null)
1010
1010
  }))
1011
- : h('p', { style: style.subtitle }, machineId ? 'No sessions on this device yet. Create one above or refresh.' : 'Select a device to list its sessions.'))
1011
+ : h('p', { style: style.subtitle }, machineId ? '该设备暂无会话。可在上方新建,或点击刷新。' : '请选择设备以列出其会话。'))
1012
1012
  : null,
1013
1013
  sessionId ? h('section', { style: style.card },
1014
1014
  h('div', { style: style.row },
1015
1015
  h('button', {
1016
- type: 'button', style: { ...style.button, flexShrink: 0 }, title: 'Close session and go back to the list',
1017
- onClick: () => { streamRef.current?.dispose(); streamRef.current = null; setSessionId(''); setTurnActive(false); setStopping(false); setStatus('Remote session closed') },
1016
+ type: 'button', style: { ...style.button, flexShrink: 0 }, title: '关闭会话并返回列表',
1017
+ onClick: () => { streamRef.current?.dispose(); streamRef.current = null; setSessionId(''); setTurnActive(false); setStopping(false); setStatus('远程会话已关闭') },
1018
1018
  }, '←'),
1019
1019
  h('strong', { style: { flex: 1, fontSize: 13.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title }, title),
1020
1020
  h('select', {
1021
1021
  style: { ...style.input, flex: '0 1 190px', minWidth: 130 },
1022
1022
  value: currentModel,
1023
1023
  disabled: !hasControl || switchingModel || models.length === 0,
1024
- title: 'Switch model for this session',
1024
+ title: '切换该会话的模型',
1025
1025
  onChange: event => {
1026
1026
  const next = event.target.value
1027
1027
  if (next && next !== currentModel) switchSessionModel(next)
@@ -1041,17 +1041,17 @@ function RemoteSessionPage({ api }) {
1041
1041
  } },
1042
1042
  historyLoading
1043
1043
  ? h('div', { style: { fontSize: 12.5, color: t.text3, textAlign: 'center', padding: '32px 12px' } },
1044
- displayEntries.length > 0 ? `Loading session history… (${displayEntries.length} entries loaded)` : 'Loading session history…')
1044
+ displayEntries.length > 0 ? `正在加载会话历史…(已加载 ${displayEntries.length} 条)` : '正在加载会话历史…')
1045
1045
  : h(React.Fragment, null,
1046
1046
  displayEntries.length > visibleCount
1047
- ? h('div', { style: { fontSize: 11, color: t.text3, textAlign: 'center', padding: '6px 0' } }, `${displayEntries.length - visibleCount} earlier messages — scroll up to load more`)
1047
+ ? h('div', { style: { fontSize: 11, color: t.text3, textAlign: 'center', padding: '6px 0' } }, `还有 ${displayEntries.length - visibleCount} 条更早的消息 — 向上滚动加载`)
1048
1048
  : null,
1049
1049
  displayEntries.slice(-visibleCount).map(entry => {
1050
1050
  if (entry.kind === 'thought') {
1051
1051
  return h('div', { style: { display: 'flex', gap: 8, alignItems: 'flex-start', justifyContent: 'flex-start' }, key: entry.key },
1052
1052
  h('div', { style: { ...style.avatar, background: '#8a5fd0' } }, '···'),
1053
1053
  h('details', { style: style.thought },
1054
- h('summary', { style: style.thoughtSummary }, 'Thinking'),
1054
+ h('summary', { style: style.thoughtSummary }, '思考过程'),
1055
1055
  h('p', { style: { margin: '6px 0 0', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', color: '#5b4a72', fontStyle: 'italic' } }, entry.text || '')))
1056
1056
  }
1057
1057
  if (entry.kind === 'message') {
@@ -1066,7 +1066,7 @@ function RemoteSessionPage({ api }) {
1066
1066
  isUser
1067
1067
  ? h('p', { style: { margin: 0, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, entry.text || '')
1068
1068
  : h('div', { className: 'mai-md' }, renderMarkdown(entry.text || ''))),
1069
- isUser ? h('div', { style: { ...style.avatar, background: avatarColor } }, 'You') : null)
1069
+ isUser ? h('div', { style: { ...style.avatar, background: avatarColor } }, '我') : null)
1070
1070
  }
1071
1071
  if (entry.kind === 'tool') {
1072
1072
  const statusLabel = entry.status ? (TOOL_STATUS_LABEL[entry.status] ?? entry.status) : 'running'
@@ -1075,15 +1075,15 @@ function RemoteSessionPage({ api }) {
1075
1075
  const statusBg = entry.status === 'completed' ? '#e9f5ee' : entry.status === 'failed' ? t.dangerBg : '#fdf6e3'
1076
1076
  return h('details', { style: { ...style.event, padding: '6px 11px', background: t.surface }, key: entry.key },
1077
1077
  h('summary', { style: { ...style.row, cursor: 'pointer', userSelect: 'none', listStyle: 'none' } },
1078
- h('span', { style: { fontSize: 10, fontWeight: 700, color: t.text3, textTransform: 'uppercase', letterSpacing: '0.05em', flexShrink: 0 } }, 'Tool'),
1079
- h('span', { style: { fontSize: 12.5, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1, minWidth: 0 } }, entry.title || 'Working'),
1078
+ h('span', { style: { fontSize: 10, fontWeight: 700, color: t.text3, textTransform: 'uppercase', letterSpacing: '0.05em', flexShrink: 0 } }, '工具'),
1079
+ h('span', { style: { fontSize: 12.5, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1, minWidth: 0 } }, entry.title || '执行中'),
1080
1080
  running ? h('span', { className: 'mai-pulse', style: { ...style.dot, width: 6, height: 6, background: '#d4a72c' } }) : null,
1081
1081
  h('span', { style: { fontSize: 10.5, padding: '1px 8px', borderRadius: 999, background: statusBg, color: statusColor, fontWeight: 650, flexShrink: 0 } }, statusLabel)),
1082
1082
  entry.detail ? h('pre', { style: { margin: '6px 0 0', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', fontSize: 11.5, maxHeight: 320, overflowY: 'auto', background: t.surfaceAlt, borderRadius: 6, padding: '8px 10px' } }, entry.detail) : null)
1083
1083
  }
1084
1084
  if (entry.kind === 'plan') {
1085
1085
  return h('article', { style: style.event, key: entry.key },
1086
- h('div', { style: { fontSize: 10.5, fontWeight: 700, color: t.text3, marginBottom: 3, textTransform: 'uppercase', letterSpacing: '0.04em' } }, 'Plan'),
1086
+ h('div', { style: { fontSize: 10.5, fontWeight: 700, color: t.text3, marginBottom: 3, textTransform: 'uppercase', letterSpacing: '0.04em' } }, '计划'),
1087
1087
  ...entry.entries.map((item, index) => h('div', { style: { display: 'flex', gap: 6, fontSize: 12.5, margin: '2px 0' }, key: index },
1088
1088
  h('span', { style: { opacity: 0.55 } }, index + 1 + '.'),
1089
1089
  h('span', { style: { textDecoration: item.status === 'completed' ? 'line-through' : 'none', opacity: item.status === 'completed' ? 0.55 : 1 } },
@@ -1095,7 +1095,7 @@ function RemoteSessionPage({ api }) {
1095
1095
  return h('article', { style: style.event, key: entry.key },
1096
1096
  h('div', { style: { opacity: 0.6, fontSize: 11, marginBottom: 4 } }, entry.label),
1097
1097
  h('details', null,
1098
- h('summary', { style: style.summary }, 'Show details'),
1098
+ h('summary', { style: style.summary }, '查看详情'),
1099
1099
  h('pre', { style: { margin: '5px 0 0', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', fontSize: 11.5 } }, JSON.stringify(entry.data ?? {}, null, 2))))
1100
1100
  })
1101
1101
  )
@@ -1115,26 +1115,26 @@ function RemoteSessionPage({ api }) {
1115
1115
  sendPrompt()
1116
1116
  }
1117
1117
  },
1118
- placeholder: hasControl ? 'Send an instruction to the remote session (Enter to send, Shift+Enter for a new line)' : 'Read-only: session:control is required to send instructions',
1118
+ placeholder: hasControl ? '向远程会话下发指令(回车发送,Shift+回车换行)' : '只读模式:下发指令需要 session:control 权限',
1119
1119
  disabled: !hasControl,
1120
1120
  }),
1121
1121
  turnActive
1122
1122
  ? h('button', {
1123
1123
  type: 'button', style: style.stopButton, disabled: !hasControl || stopping,
1124
- onClick: stopTurn, title: stopping ? 'Stopping…' : 'Stop the current turn',
1125
- 'aria-label': 'Stop the current turn',
1124
+ onClick: stopTurn, title: stopping ? '停止中…' : '停止当前回合',
1125
+ 'aria-label': '停止当前回合',
1126
1126
  },
1127
1127
  h('span', { style: style.stopIcon }))
1128
1128
  : h('button', {
1129
1129
  type: 'submit', style: style.sendButton,
1130
1130
  disabled: !streamRef.current || !hasControl || prompt.trim() === '',
1131
- title: 'Send (Enter)', 'aria-label': 'Send instruction',
1131
+ title: '发送(回车)', 'aria-label': '发送指令',
1132
1132
  },
1133
1133
  h('svg', { width: 16, height: 16, viewBox: '0 0 16 16', fill: 'none', 'aria-hidden': 'true', style: { display: 'block' } },
1134
1134
  h('path', { d: 'M8 12.5V3.5M8 3.5L4 7.5M8 3.5L12 7.5', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round' })))),
1135
1135
  lastDelivery || turnActive ? h('div', { style: { fontSize: 11.5, color: turnActive ? t.accent : lastDelivery?.accepted ? t.success : lastDelivery?.frame === 'pending' ? t.text3 : t.danger, textAlign: 'right', display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 6 } },
1136
1136
  turnActive ? h('span', { className: 'mai-pulse', style: { ...style.dot, background: t.accent } }) : null,
1137
- turnActive ? (stopping ? 'Stopping the current turn…' : 'MonkeyAI is working…') : lastDelivery?.accepted ? 'Turn finished' : lastDelivery?.frame === 'pending' ? 'Delivery pending' : 'Delivery rejected') : null)
1137
+ turnActive ? (stopping ? '正在停止当前回合…' : 'MonkeyAI 正在处理…') : lastDelivery?.accepted ? '回合已结束' : lastDelivery?.frame === 'pending' ? '等待投递' : '投递被拒绝') : null)
1138
1138
  : null)
1139
1139
  }
1140
1140
 
@@ -1166,8 +1166,8 @@ export async function apply(ctx) {
1166
1166
  type: 'button',
1167
1167
  style: { width: '100%', height: '100%', padding: 0, border: 0, background: 'transparent', color: 'inherit', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' },
1168
1168
  onClick: () => owner.layout.selectPanel(PANEL_ID),
1169
- 'aria-label': 'MonkeyAI sessions',
1170
- title: 'MonkeyAI Sessions',
1169
+ 'aria-label': 'MonkeyAI 会话',
1170
+ title: 'MonkeyAI 会话',
1171
1171
  }, h(MonkeyIcon, { size: 18 }))
1172
1172
  owner.slots.inject('main', () => owner.slots.register({
1173
1173
  name: 'main', key: PANEL_ID, inject: () => ({ api }),