mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
fix(client): localize terminal send presentation
This commit is contained in:
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.md
|
||||
2026-08-23-client-derived-tool-presentation.md: 957d2e6c1a79cb0b0a246066463e6ac960fb3d5b
|
||||
2026-08-23-client-derived-tool-presentation.zh.md: 5fa1a350963ff74306520bad6a4fc89765c592a7
|
||||
2026-08-23-client-derived-tool-presentation.md: 77fb48552625c1b22908225b0116eeba14c6046c
|
||||
2026-08-23-client-derived-tool-presentation.zh.md: 50cc447f9f4ca2e946de10d2df31ca18382d95ef
|
||||
|
||||
@@ -312,6 +312,8 @@ Standard shell results continue parsing trailing `[exit code: N]` and `[killed b
|
||||
|
||||
Call `description` remains above the card and overrides the collapsed summary. Workdir continues handling absolute, relative, and missing values. Relative paths resolve against the Session cwd while preserving normalization for `.`, `..`, drive letters, and UNC roots.
|
||||
|
||||
For `terminal_send`, non-empty input and the session id remain verbatim tool data; the empty-input fallback and session label resolve through the render site's conversation locale.
|
||||
|
||||
Standard and persistent providers sharing the same tool name are a special compatibility point. The Client uses currently valid argument and result features to preserve their delivered differences. Input that cannot be identified unambiguously uses a Generic settled result rather than gaining new presentation.
|
||||
|
||||
`TerminalBlock` ANSI handling, cursor replay, wide characters, line limits, expansion, copying, and assistive text remain unchanged.
|
||||
|
||||
+2
@@ -312,6 +312,8 @@ Client terminal model 从工具名称、调用参数、结果 content、error、
|
||||
|
||||
调用 `description` 继续显示在 card 上方并覆盖折叠摘要。workdir 继续按绝对、相对和缺失三种情况处理;相对路径基于 Session cwd,且保留 `.`、`..`、盘符与 UNC root 的归一化。
|
||||
|
||||
对于 `terminal_send`,非空 input 与 session id 保持为逐字工具数据;空 input fallback 与 session label 通过 render site 的 conversation locale 解析。
|
||||
|
||||
同名普通与 persistent provider 是特殊兼容点。Client 使用当前有效参数与结果特征保留已交付差异;不足以无歧义识别的输入选择 Generic settled 结果,不增加新表现。
|
||||
|
||||
TerminalBlock 的 ANSI、光标重放、宽字符、行数上限、展开、复制与辅助技术文本完全不变。
|
||||
|
||||
@@ -145,6 +145,8 @@ export const zh = {
|
||||
'terminal.collapseAria': '收起输出',
|
||||
'terminal.expandAria': '展开其余 {n} 行输出',
|
||||
'terminal.expandRest': '… 其余 {n} 行',
|
||||
'terminal.sendInput': '(发送输入)',
|
||||
'terminal.session': '终端 {sessionId}',
|
||||
} satisfies Record<string, string>
|
||||
|
||||
/** The conversation namespace key union. */
|
||||
@@ -288,4 +290,8 @@ export const en = {
|
||||
'terminal.collapseAria': 'Collapse output',
|
||||
'terminal.expandAria': 'Expand the remaining {n} output lines',
|
||||
'terminal.expandRest': '… {n} more lines',
|
||||
// The Host terminal_send presenter has no locale seat; keep its fallbacks
|
||||
// aligned with these English values.
|
||||
'terminal.sendInput': '(send input)',
|
||||
'terminal.session': 'Terminal {sessionId}',
|
||||
} satisfies Record<ConversationKey, string>
|
||||
|
||||
@@ -4,7 +4,9 @@ import type { ToolDetailsProps } from '../contract/slots.ts'
|
||||
import { diffCardModel } from './models/diff-card-model.ts'
|
||||
import { readCardModel } from './models/read-card-model.ts'
|
||||
import { searchCardModel } from './models/search-card-model.ts'
|
||||
import { terminalBlockLabels, terminalCardModel } from './models/terminal-card-model.ts'
|
||||
import {
|
||||
localizeTerminalCardModel, terminalBlockLabels, terminalCardModel,
|
||||
} from './models/terminal-card-model.ts'
|
||||
import {
|
||||
diffBlockLabels, readBlockLabels, searchBlockLabels, webBlockLabels,
|
||||
} from './models/primitive-labels.ts'
|
||||
@@ -22,8 +24,9 @@ export function ToolDetails({
|
||||
block, cwd, useHostDescription, t,
|
||||
}: Pick<ToolDetailsProps, 'block' | 'cwd' | 'useHostDescription' | 't'>) {
|
||||
const home = useHostDescription(description => description?.home)
|
||||
const terminal = terminalCardModel(block, cwd)
|
||||
if (terminal !== null) {
|
||||
const terminalModel = terminalCardModel(block, cwd)
|
||||
if (terminalModel !== null) {
|
||||
const terminal = localizeTerminalCardModel(terminalModel, t)
|
||||
return (
|
||||
<>
|
||||
{terminal.description !== undefined ? (
|
||||
|
||||
@@ -7,7 +7,9 @@ import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { CHAT_DIFF_MAX_LINES, type DiffCardModel } from '../models/diff-card-model.ts'
|
||||
import { CHAT_READ_MAX_LINES, type ReadCardModel } from '../models/read-card-model.ts'
|
||||
import { CHAT_SEARCH_MAX_LINES, type SearchCardModel } from '../models/search-card-model.ts'
|
||||
import { terminalBlockLabels, type TerminalCardModel } from '../models/terminal-card-model.ts'
|
||||
import {
|
||||
localizeTerminalCardModel, terminalBlockLabels, type TerminalCardModel,
|
||||
} from '../models/terminal-card-model.ts'
|
||||
import {
|
||||
diffBlockLabels, readBlockLabels, searchBlockLabels, webBlockLabels,
|
||||
} from '../models/primitive-labels.ts'
|
||||
@@ -106,7 +108,9 @@ export function ToolRow({
|
||||
const readLabels = useMemo(() => readBlockLabels(t), [t])
|
||||
const searchLabels = useMemo(() => searchBlockLabels(t), [t])
|
||||
const webLabels = useMemo(() => webBlockLabels(t), [t])
|
||||
const terminalBody = terminal ?? null
|
||||
const terminalBody = terminal === undefined || terminal === null
|
||||
? null
|
||||
: localizeTerminalCardModel(terminal, t)
|
||||
const diffBody = diff ?? null
|
||||
const readBody = read ?? null
|
||||
const searchBody = search ?? null
|
||||
@@ -118,7 +122,7 @@ export function ToolRow({
|
||||
const status = stateStatus(state, t)
|
||||
// A failure must replace, not supplement, the normal summary.
|
||||
const failureLine = state === 'error' ? errorSummary ?? null : null
|
||||
const summaryText = failureLine ?? summary
|
||||
const summaryText = failureLine ?? terminalBody?.description ?? summary
|
||||
const suffix = failureLine === null ? summarySuffix ?? null : null
|
||||
const fileLink = filePath !== undefined && onOpenFile !== undefined && failureLine === null
|
||||
const toggleExpand = () => {
|
||||
|
||||
@@ -37,16 +37,48 @@ export function terminalBlockLabels(t: TranslateNS<'conversation'>): TerminalBlo
|
||||
*/
|
||||
export interface TerminalCardModel {
|
||||
/**
|
||||
* The props {@link TerminalBlock} draws. Held as a nested object so a render
|
||||
* site spreads exactly the primitive's own surface and can never leak a
|
||||
* neighbouring field into it.
|
||||
* The locale-neutral props {@link TerminalBlock} draws. The render site adds
|
||||
* `command` after resolving {@link copy} through its locale seat.
|
||||
*/
|
||||
card: Pick<TerminalBlockProps, 'command' | 'cwd' | 'output' | 'exitCode' | 'signal' | 'running'>
|
||||
card: Pick<TerminalBlockProps, 'cwd' | 'output' | 'exitCode' | 'signal' | 'running'>
|
||||
/**
|
||||
* The model-authored call description rendered above the card. Absent for
|
||||
* persistent shells, whose parameter set has no description.
|
||||
* Verbatim Tool data or semantic `terminal_send` data. Product copy stays
|
||||
* unresolved until a render site supplies its locale seat.
|
||||
*/
|
||||
description: string | undefined
|
||||
copy:
|
||||
| { readonly kind: 'shell'; readonly command: string; readonly description: string | undefined }
|
||||
| { readonly kind: 'terminal-send'; readonly text: string; readonly sessionId: string }
|
||||
}
|
||||
|
||||
interface LocalizedTerminalCardModel {
|
||||
readonly card: Pick<TerminalBlockProps, 'command' | 'cwd' | 'output' | 'exitCode' | 'signal' | 'running'>
|
||||
readonly description: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve locale-owned `terminal_send` copy while preserving Tool-authored
|
||||
* shell commands and descriptions verbatim.
|
||||
* @param model - locale-neutral terminal card data.
|
||||
* @param t - the render site's conversation locale seat.
|
||||
* @returns terminal props and description ready for rendering.
|
||||
*/
|
||||
export function localizeTerminalCardModel(
|
||||
model: TerminalCardModel,
|
||||
t: TranslateNS<'conversation'>,
|
||||
): LocalizedTerminalCardModel {
|
||||
if (model.copy.kind === 'shell') {
|
||||
return {
|
||||
card: { command: model.copy.command, ...model.card },
|
||||
description: model.copy.description,
|
||||
}
|
||||
}
|
||||
return {
|
||||
card: {
|
||||
command: model.copy.text === '' ? t('terminal.sendInput') : model.copy.text,
|
||||
...model.card,
|
||||
},
|
||||
description: t('terminal.session', { sessionId: model.copy.sessionId }),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,6 +172,7 @@ function collapse(body: string, rooted: boolean, separator = '/'): string {
|
||||
}
|
||||
|
||||
interface ShellCall {
|
||||
kind: 'shell'
|
||||
command: string
|
||||
description: string | undefined
|
||||
workdir: string | undefined
|
||||
@@ -159,10 +192,11 @@ function shellCall(name: string, args: Record<string, unknown>): ShellCall | nul
|
||||
// Standard dsh-tool-bash and dsh-tool-pwsh schemas require `description`;
|
||||
// persistent shell providers omit it. Their parameter roots stay open, so
|
||||
// unrelated fields do not change their running-card behavior.
|
||||
return { command, description: undefined, workdir: undefined, persistent: true, background: false }
|
||||
return { kind: 'shell', command, description: undefined, workdir: undefined, persistent: true, background: false }
|
||||
}
|
||||
if (typeof description !== 'string' || description.trim() === '') return null
|
||||
return {
|
||||
kind: 'shell',
|
||||
command,
|
||||
description,
|
||||
workdir,
|
||||
@@ -172,8 +206,9 @@ function shellCall(name: string, args: Record<string, unknown>): ShellCall | nul
|
||||
}
|
||||
|
||||
interface TerminalSendCall {
|
||||
command: string
|
||||
description: string
|
||||
kind: 'terminal-send'
|
||||
text: string
|
||||
sessionId: string
|
||||
background: boolean
|
||||
}
|
||||
|
||||
@@ -184,10 +219,9 @@ function terminalSendCall(name: string, args: Record<string, unknown>): Terminal
|
||||
if (submit !== undefined && typeof submit !== 'boolean') return null
|
||||
if (background !== undefined && typeof background !== 'boolean') return null
|
||||
return {
|
||||
// Keep this visible fallback aligned with dsh-tool-terminal's
|
||||
// `terminal_send.presentCall` implementation.
|
||||
command: text || '(send input)',
|
||||
description: `Terminal ${sessionId}`,
|
||||
kind: 'terminal-send',
|
||||
text,
|
||||
sessionId,
|
||||
background: background === true,
|
||||
}
|
||||
}
|
||||
@@ -213,7 +247,7 @@ function parseExitStatus(text: string): { output: string; exitCode?: number; sig
|
||||
* the generic path.
|
||||
* @param block - running or settled Tool block.
|
||||
* @param sessionCwd - session workspace root used to resolve workdir.
|
||||
* @returns the terminal-card props, or null for the generic path.
|
||||
* @returns locale-neutral terminal-card data, or null for the generic path.
|
||||
*/
|
||||
export function terminalCardModel(
|
||||
block: ToolCallBlock,
|
||||
@@ -222,19 +256,17 @@ export function terminalCardModel(
|
||||
if (block.parentCallId !== undefined) return null
|
||||
const parsed = parsedToolCall(block)
|
||||
if (parsed === null) return null
|
||||
const shell = shellCall(parsed.name, parsed.args)
|
||||
const send = terminalSendCall(parsed.name, parsed.args)
|
||||
if (shell === null && send === null) return null
|
||||
if (shell?.background === true || send?.background === true) return null
|
||||
const call = shellCall(parsed.name, parsed.args) ?? terminalSendCall(parsed.name, parsed.args)
|
||||
if (call === null || call.background) return null
|
||||
|
||||
const command = shell?.command ?? send?.command ?? ''
|
||||
const description = shell?.description ?? send?.description
|
||||
const cwd = resolveTerminalCwd(shell?.workdir, sessionCwd)
|
||||
const copy: TerminalCardModel['copy'] = call.kind === 'shell'
|
||||
? { kind: 'shell', command: call.command, description: call.description }
|
||||
: { kind: 'terminal-send', text: call.text, sessionId: call.sessionId }
|
||||
const cwd = resolveTerminalCwd(call.kind === 'shell' ? call.workdir : undefined, sessionCwd)
|
||||
if (!('kind' in block)) {
|
||||
return {
|
||||
description,
|
||||
copy,
|
||||
card: {
|
||||
command,
|
||||
cwd,
|
||||
output: undefined,
|
||||
exitCode: undefined,
|
||||
@@ -243,14 +275,13 @@ export function terminalCardModel(
|
||||
},
|
||||
}
|
||||
}
|
||||
if (block.isError || shell?.persistent === true) return null
|
||||
if (block.isError || (call.kind === 'shell' && call.persistent)) return null
|
||||
const output = singleResultText(block)
|
||||
if (output === undefined) return null
|
||||
const status = shell === null ? { output } : parseExitStatus(output)
|
||||
const status = call.kind === 'terminal-send' ? { output } : parseExitStatus(output)
|
||||
return {
|
||||
description,
|
||||
copy,
|
||||
card: {
|
||||
command,
|
||||
cwd,
|
||||
output: status.output,
|
||||
exitCode: status.exitCode,
|
||||
|
||||
@@ -47,7 +47,7 @@ export function GenericToolCard({ toolName, block, cwd, home, openFile, inspect,
|
||||
toolName={toolName}
|
||||
icon={VARIANT_ICONS[model.variant]}
|
||||
title={t(model.titleKey)}
|
||||
summary={terminal?.description ?? model.summary}
|
||||
summary={model.summary}
|
||||
// Single-file tools never expose an args body — the path link is the only
|
||||
// args interaction. A card is not an args body: a read/write/edit row is
|
||||
// single-file AND carries a card, so the card expands under the path link.
|
||||
|
||||
@@ -6,7 +6,9 @@ import {
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import type { ToolCallViewProps } from '../../contract/slots.ts'
|
||||
import { terminalBlockLabels, terminalCardModel, terminalFailed } from '../models/terminal-card-model.ts'
|
||||
import {
|
||||
localizeTerminalCardModel, terminalBlockLabels, terminalCardModel, terminalFailed,
|
||||
} from '../models/terminal-card-model.ts'
|
||||
import { toolRowModel, type ToolRowState } from '../models/tool-call-model.ts'
|
||||
import { CONVERSATION_NS as NS } from '../../locale.ts'
|
||||
import css from './bash-sample.module.css'
|
||||
@@ -38,10 +40,11 @@ export function BashRow({ toolName, block, sessionId, useSessions, inspect, t }:
|
||||
// An omitted shell workdir is the session workspace; relative values resolve
|
||||
// against it before reaching the terminal primitive.
|
||||
const cwd = useSessions(list => list.byId[sessionId]?.cwd)
|
||||
const terminal = terminalCardModel(block, cwd)
|
||||
const terminalModel = terminalCardModel(block, cwd)
|
||||
const terminal = terminalModel === null ? null : localizeTerminalCardModel(terminalModel, t)
|
||||
// A failing exit status is the terminal card's own error signal (the call
|
||||
// itself settles isError:false), surfaced as the row's red state dot.
|
||||
const state = model.state === 'ok' && terminal !== null && terminalFailed(terminal)
|
||||
const state = model.state === 'ok' && terminalModel !== null && terminalFailed(terminalModel)
|
||||
? 'error'
|
||||
: model.state
|
||||
const status = stateStatus(state, t)
|
||||
|
||||
@@ -12,19 +12,23 @@ import type {
|
||||
import type { SessionListState } from '@deepseek-ai/dsh-api-session-controller/client'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { en as commonEn } from '@deepseek-ai/dsh-client-locale/src/locales/en.ts'
|
||||
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
||||
import { terminalCardModel, terminalFailed } from '../src/client/tool/models/terminal-card-model.ts'
|
||||
import {
|
||||
localizeTerminalCardModel, terminalCardModel, terminalFailed,
|
||||
} from '../src/client/tool/models/terminal-card-model.ts'
|
||||
import { createChatStore } from '@deepseek-ai/dsh-client-ui-chat/src/client/stores.ts'
|
||||
import { GenericToolCard, type GenericToolCardProps } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
||||
import { DetailsPanel } from '@deepseek-ai/dsh-client-ui-chat/src/client/details/DetailsPanel.tsx'
|
||||
import { BashRow } from '../src/client/tool/toolviews/bash-sample.tsx'
|
||||
import { renderToolDetails, toolChatSnapshot, useEmptyTrajectory } from './tool-details-render.client.tsx'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
import { en, zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
import { zh as chatZh } from '@deepseek-ai/dsh-client-ui-chat/src/client/locale.ts'
|
||||
|
||||
type BashRowProps = Parameters<typeof BashRow>[0]
|
||||
|
||||
const t: GenericToolCardProps['t'] = makeTranslate(zh, commonZh)
|
||||
const enT: GenericToolCardProps['t'] = makeTranslate(en, commonEn)
|
||||
const chatT = makeTranslate(chatZh, commonZh)
|
||||
|
||||
afterEach(cleanup)
|
||||
@@ -65,9 +69,9 @@ const settled = (over?: Partial<ToolResultNode>): ToolResultNode => ({
|
||||
describe('terminalCardModel', () => {
|
||||
it('derives a running standard-shell card from raw arguments', () => {
|
||||
expect(terminalCardModel(running({ argsRaw: shellArgs({ workdir: '/projects/app' }) }))).toEqual({
|
||||
description: 'List files',
|
||||
copy: { kind: 'shell', command: 'ls -la', description: 'List files' },
|
||||
card: {
|
||||
command: 'ls -la', cwd: '/projects/app', output: undefined,
|
||||
cwd: '/projects/app', output: undefined,
|
||||
exitCode: undefined, signal: undefined, running: true,
|
||||
},
|
||||
})
|
||||
@@ -78,9 +82,9 @@ describe('terminalCardModel', () => {
|
||||
call: { name: 'bash', argsRaw: shellArgs({ workdir: '/projects/app' }) },
|
||||
content: [{ type: 'text', text: 'boom\n[exit code: 2]' }],
|
||||
}))).toEqual({
|
||||
description: 'List files',
|
||||
copy: { kind: 'shell', command: 'ls -la', description: 'List files' },
|
||||
card: {
|
||||
command: 'ls -la', cwd: '/projects/app', output: 'boom',
|
||||
cwd: '/projects/app', output: 'boom',
|
||||
exitCode: 2, signal: undefined, running: false,
|
||||
},
|
||||
})
|
||||
@@ -163,10 +167,13 @@ describe('terminalCardModel', () => {
|
||||
const argsRaw = JSON.stringify({ sessionId: 'pty-3', text: 'make' })
|
||||
const run = running({ name: 'terminal_send', argsRaw })
|
||||
expect(terminalCardModel(run, '/w/app')).toMatchObject({
|
||||
description: 'Terminal pty-3', card: { command: 'make', cwd: '/w/app', running: true },
|
||||
copy: { kind: 'terminal-send', text: 'make', sessionId: 'pty-3' },
|
||||
card: { cwd: '/w/app', running: true },
|
||||
})
|
||||
const done = settled({ call: { name: 'terminal_send', argsRaw }, content: [{ type: 'text', text: 'ok' }] })
|
||||
expect(terminalCardModel(done)?.card).toMatchObject({ command: 'make', output: 'ok', running: false })
|
||||
expect(localizeTerminalCardModel(terminalCardModel(done)!, enT)).toMatchObject({
|
||||
description: 'Terminal pty-3', card: { command: 'make', output: 'ok', running: false },
|
||||
})
|
||||
expect(terminalCardModel(settled({
|
||||
call: { name: 'terminal_send', argsRaw: JSON.stringify({ sessionId: 'pty-3', text: 'make', run_in_background: true }) },
|
||||
}))).toBeNull()
|
||||
@@ -176,10 +183,10 @@ describe('terminalCardModel', () => {
|
||||
it('preserves persistent-shell running cards and settled generic output', () => {
|
||||
const persistent = JSON.stringify({ command: 'pwd' })
|
||||
expect(terminalCardModel(running({ argsRaw: persistent }))).toMatchObject({
|
||||
description: undefined, card: { command: 'pwd', running: true },
|
||||
copy: { kind: 'shell', command: 'pwd', description: undefined }, card: { running: true },
|
||||
})
|
||||
expect(terminalCardModel(running({ name: 'pwsh', argsRaw: persistent }))).toMatchObject({
|
||||
description: undefined, card: { command: 'pwd', running: true },
|
||||
copy: { kind: 'shell', command: 'pwd', description: undefined }, card: { running: true },
|
||||
})
|
||||
expect(terminalCardModel(settled({ call: { name: 'bash', argsRaw: persistent } }))).toBeNull()
|
||||
expect(terminalCardModel(settled({ call: { name: 'pwsh', argsRaw: persistent } }))).toBeNull()
|
||||
@@ -190,8 +197,22 @@ describe('terminalCardModel', () => {
|
||||
call: { name: 'pwsh', argsRaw: ARGS },
|
||||
content: [{ type: 'text', text: 'failed\n[exit code: 3]' }],
|
||||
}))).toMatchObject({
|
||||
description: 'List files',
|
||||
card: { command: 'ls -la', output: 'failed', exitCode: 3, running: false },
|
||||
copy: { kind: 'shell', command: 'ls -la', description: 'List files' },
|
||||
card: { output: 'failed', exitCode: 3, running: false },
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps terminal_send copy semantic until the render locale is known', () => {
|
||||
const model = terminalCardModel(running({
|
||||
name: 'terminal_send',
|
||||
argsRaw: JSON.stringify({ sessionId: 'pty-3', text: '' }),
|
||||
}))!
|
||||
expect(model.copy).toEqual({ kind: 'terminal-send', text: '', sessionId: 'pty-3' })
|
||||
expect(localizeTerminalCardModel(model, t)).toMatchObject({
|
||||
description: '终端 pty-3', card: { command: '(发送输入)' },
|
||||
})
|
||||
expect(localizeTerminalCardModel(model, enT)).toMatchObject({
|
||||
description: 'Terminal pty-3', card: { command: '(send input)' },
|
||||
})
|
||||
})
|
||||
|
||||
@@ -323,6 +344,20 @@ describe('chat row terminal body', () => {
|
||||
expect(runStateOf(view.container)).toBe('ongoing')
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ locale: 'zh', translate: t, description: '终端 pty-3', command: '(发送输入)' },
|
||||
{ locale: 'en', translate: enT, description: 'Terminal pty-3', command: '(send input)' },
|
||||
])('renders terminal_send copy through the $locale locale', ({ translate, description, command }) => {
|
||||
const block = running({
|
||||
name: 'terminal_send',
|
||||
argsRaw: JSON.stringify({ sessionId: 'pty-3', text: '' }),
|
||||
})
|
||||
const view = render(<GenericToolCard {...ownerProps(block)} toolName="terminal_send" t={translate} />)
|
||||
expect(view.getByText(description)).toBeTruthy()
|
||||
toggleRow(view)
|
||||
expect(view.getByText(command)).toBeTruthy()
|
||||
})
|
||||
|
||||
it('a non-terminal call keeps the args-JSON text body', () => {
|
||||
const view = render(<GenericToolCard {...ownerProps(settled({
|
||||
call: { name: 'bash', argsRaw: shellArgs({ run_in_background: true }) },
|
||||
@@ -527,6 +562,18 @@ describe('DetailsPanel Output section', () => {
|
||||
expect(description.compareDocumentPosition(card!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
|
||||
})
|
||||
|
||||
it('localizes terminal_send copy in Details', () => {
|
||||
const argsRaw = JSON.stringify({ sessionId: 'pty-3', text: '' })
|
||||
const view = mount(snapshot({
|
||||
nodes: [settled({
|
||||
call: { name: 'terminal_send', argsRaw },
|
||||
content: [{ type: 'text', text: 'ok' }],
|
||||
})],
|
||||
}), { ...target, toolName: 'terminal_send' })
|
||||
expect(view.getByText('终端 pty-3')).toBeTruthy()
|
||||
expect(view.getByText('(发送输入)')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('resolves the prompt cwd against the session workspace', () => {
|
||||
const view = mount(snapshot({ nodes: [settled()] }), target, '/w/app')
|
||||
// No workdir in the call args: the prompt label is the workspace basename.
|
||||
|
||||
@@ -284,8 +284,8 @@ export function apply(ctx: Context, config: Config = {}): void {
|
||||
if (parsed.run_in_background === true) {
|
||||
return { card: 'generic', title: `Send to terminal ${parsed.sessionId as string} in background`, kind: 'execute', rawInput: parsed.text }
|
||||
}
|
||||
// Keep this visible fallback aligned with dsh-client-ui-tool's
|
||||
// terminal-card model, which cannot import this Host package.
|
||||
// Keep these Host-only fallbacks aligned with the conversation locale
|
||||
// keys `terminal.sendInput` and `terminal.session` used by Web.
|
||||
return { card: 'terminal', title: parsed.text || '(send input)', description: `Terminal ${parsed.sessionId as string}` }
|
||||
},
|
||||
presentResult(args, result) {
|
||||
|
||||
Reference in New Issue
Block a user