Revert "feat(client): expose stable composer prefill API"

This commit is contained in:
imccyu
2026-10-30 13:59:08 +08:00
committed by GitHub
parent f77e9a2757
commit 5c2826eae5
25 changed files with 17 additions and 346 deletions
@@ -1,16 +0,0 @@
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.2-alpha.2/composer-prefill-method/guide.md
/:
en: a99282dc38a7dfeb
zh: 6f6ee2170907eb26
/composer-prefill-method-on-input-actions:
en: 976e77fa72fdfdd2
zh: dbaa50bd205dc1f3
/composer-prefill-method-on-input-actions/change:
en: 5186bc4ec9254340
zh: ff0e406a96d5c50b
/composer-prefill-method-on-input-actions/migration:
en: 21625abfcb53c507
zh: 4a6f0aaa04eb3a2c
@@ -1,18 +0,0 @@
---
kind: upgrade-guide
description: "Client code that builds InputActions or implements SessionInput must provide the required prefill method."
---
# Composer prefill method on input actions
English | [中文](guide.zh.md)
## Change
`InputActions` and `SessionInput` in `@deepseek-ai/dsh-client-ui-conversation/client` now require `prefill(text, options?)`, and the package exports `ComposerPrefillMode` and `ComposerPrefillOptions`. Slot components and Session-scope plugins read prefill from the harness, so their call sites compile unchanged. Code that constructs an `InputActions` value, or implements `SessionInput`, including test doubles passed to client components, no longer satisfies the interfaces until it adds the member. The method returns whether the draft accepted the write; returning `false` is valid for doubles that do not support prefill.
## Migration
1. Add `prefill` to every hand-written `InputActions` or `SessionInput` object. Forward to the harness-provided action, or return `false` when the double does not support writing.
2. Import `ComposerPrefillOptions` or `ComposerPrefillMode` from `@deepseek-ai/dsh-client-ui-conversation/client` when a signature needs them.
3. Rebuild custom Client plugins and their test suites, then run the type check. A passing check confirms the interface is satisfied; call `inputActions.prefill('text')` from a slot component to verify the harness path when the object forwards the operation.
@@ -1,18 +0,0 @@
---
kind: upgrade-guide
description: "手写 InputActions 或实现 SessionInput 的 Client 代码必须提供必选方法 prefill。"
---
# 输入操作上的 Composer 预填方法
[English](guide.md) | 中文
## 变更
`@deepseek-ai/dsh-client-ui-conversation/client` 中的 `InputActions` 和 `SessionInput` 现在要求提供 `prefill(text, options?)`,该包同时导出 `ComposerPrefillMode` 和 `ComposerPrefillOptions`。Slot 组件与 Session 作用域插件从 Harness 读取预填操作,调用方无需改动即可编译。构造 `InputActions` 值或实现 `SessionInput` 的代码(包括传给 Client 组件的测试替身)必须新增该成员才能满足接口。该方法返回草稿是否接受写入;不支持预填的替身可以返回 `false`。
## 迁移
1. 在所有手写的 `InputActions` 或 `SessionInput` 对象中加入 `prefill`。转发到 Harness 提供的操作,或在替身不支持写入时返回 `false`。
2. 签名需要时,从 `@deepseek-ai/dsh-client-ui-conversation/client` 导入 `ComposerPrefillOptions` 或 `ComposerPrefillMode`。
3. 重新构建自定义 Client 插件及其测试,然后运行类型检查。检查通过即表示接口已满足;当对象转发该操作时,可从 Slot 组件调用 `inputActions.prefill('text')` 验证 Harness 路径。
@@ -525,7 +525,6 @@ function makeHarness(
captureInsertion: () => ({ start: 0, end: 0, draftRev: 0 }),
insertText: () => false,
setDraft: () => {},
prefill: () => false,
persistDraft: () => {},
addAttachments: () => true,
removeAttachment: () => {},
@@ -12,8 +12,8 @@
en: 8811c28013fdc6dc
zh: 0a86edc422b38ba8
/deepseek-ai-dsh-client-ui-conversation/table-of-contents:
en: c1ca3d169bea2ccc
zh: 1892939a2ec76e1e
en: eef4f18c3972ccc4
zh: 6be65c6ac84ce497
/deepseek-ai-dsh-client-ui-conversation/conversation-assembly:
en: ef43c4f149195939
zh: 49fc6c83fd3a05ce
@@ -24,11 +24,8 @@
en: 99ec447f0a6bda48
zh: 922cf64666676530
/deepseek-ai-dsh-client-ui-conversation/temporary-composer-entries:
en: 9acb3b1d7f279d97
zh: 8544b7a140437553
/deepseek-ai-dsh-client-ui-conversation/plugin-prefill-api:
en: 0d16e9363a821bc6
zh: eca2ba2076a3ca9d
en: 59cd63057dfb6eeb
zh: b2e2c86e810b7bb8
/deepseek-ai-dsh-client-ui-conversation/model-experience:
en: e0a05b73ba3e4318
zh: dc85ab8433bb4456
-10
View File
@@ -19,7 +19,6 @@ Desktop product events use the optional [product analytics service](../product-a
- [Sent user text](#sent-user-text)
- [Shell and standard props](#shell-and-standard-props)
- [Temporary composer entries](#temporary-composer-entries)
- [Plugin prefill API](#plugin-prefill-api)
- [Model Experience](#model-experience)
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
- [Dev Note](#dev-note)
@@ -187,15 +186,6 @@ The selector must be a pure function of the owner currency. Its non-null return
Session title context gestures use the shared [context menu](../ui-context-menu/README.md) with the clicked breadcrumb’s Session. The reusable image gallery receives `getTriggerProps` from its Factory’s injected `useContextMenu` hook.
<a id="plugin-prefill-api"></a>
## Plugin prefill API
A plugin can write reviewable text into the current composer through the stable `prefill` action. Slot components receive it as `inputActions.prefill(text, options)`. A session-scope plugin resolves the same action with `ctx.conversation.input.for(actx).prefill(text, options)`.
The exported `ComposerPrefillOptions` type and `InputActions.prefill` signature are defined in the [input contracts](src/client/contract/input.ts).
An empty string is ignored and returns `false`. An empty or whitespace-only draft, or `mode: 'replace'`, replaces the whole draft only while the composer is in the plain phase and the session is live; that replacement is its own undo step. Otherwise the text replaces the current selection in one undo step, preceded by `separator` when the editor text before the selection ends in non-whitespace; the separator defaults to a newline and an empty separator inserts verbatim. A rejected write — a non-plain replacement, a changed draft revision, a locked submission phase, or a disposed session — returns `false` without changing the draft. `prefill` never sends the draft.
<a id="model-experience"></a>
## Model Experience
@@ -19,7 +19,6 @@ kind: "package-reference"
- [已发送用户文本](#sent-user-text)
- [Shell 与标准 props](#shell-and-standard-props)
- [临时 composer entry](#temporary-composer-entries)
- [插件预填 API](#plugin-prefill-api)
- [模型体验](#model-experience)
- [已知限制与暂缓事项](#known-limitations-and-deferred-work)
- [开发备注](#dev-note)
@@ -187,15 +186,6 @@ selector 必须是 owner currency 的纯函数。非 null 返回值作为 `match
会话标题右键使用共享[右键菜单](../ui-context-menu/README.zh.md),目标是被点击面包屑所属会话。可复用图片库从其 Factory 注入的 `useContextMenu` hook 获取 `getTriggerProps`。
<a id="plugin-prefill-api"></a>
## 插件预填 API
插件可以通过稳定的 `prefill` 操作把可检查的文本写入当前 Composer。Slot 组件收到的是 `inputActions.prefill(text, options)`;Session 作用域的插件通过 `ctx.conversation.input.for(actx).prefill(text, options)` 解析同一个操作。
导出的 `ComposerPrefillOptions` 类型和 `InputActions.prefill` 签名定义在[输入接口](src/client/contract/input.ts)中。
空字符串会被忽略并返回 `false`。草稿为空或只有空白,或者使用 `mode: 'replace'` 时,只有在 Composer 处于 plain 阶段且 Session 仍存活时才替换整个草稿;这次替换本身就是独立的撤销步骤。否则文本会替换当前选区,作为一次可撤销编辑;若选区前的编辑器文本末尾不是空白,则先插入 `separator`。`separator` 默认是换行,传空字符串时原样插入。非 plain 阶段的替换、草稿版本已变、提交阶段锁定或 Session 已释放时,写入会被拒绝并返回 `false`,草稿保持不变。`prefill` 不会发送草稿。
<a id="model-experience"></a>
## 模型体验
@@ -187,15 +187,6 @@ export interface SessionInput extends InputTarget {
/** Replace the whole draft with plain text, removing inline reference chips. */
setDraft(text: string): void
/**
* Write plugin-owned text into the draft in one undo step. This is the
* session-scope entry to the same prefill operation exposed as
* {@link InputActions.prefill} to slot components.
* @param text - text to write; an empty string is ignored.
* @param options - replacement mode and insertion separator.
* @returns whether the draft accepted the write.
*/
prefill(text: string, options?: ComposerPrefillOptions): boolean
/** Append ordered browser-owned attachment ids; busy admission phases refuse. */
addAttachments(ids: readonly DraftAttachmentId[]): boolean
/** Remove one browser-owned attachment id; busy admission phases refuse. @returns whether the id was removed. */
@@ -258,20 +249,6 @@ export interface DraftInitializationOptions {
/** An initialization request never waits for a React mount or a remote catalog. */
export type DraftInitializationResult = 'applied' | 'preserved' | 'blocked'
/** How a composer prefill treats existing draft text. */
export type ComposerPrefillMode = 'insert' | 'replace'
/** Options for a plugin-owned composer prefill. */
export interface ComposerPrefillOptions {
/** Replace the whole draft instead of inserting at the caret; defaults to `insert`. */
readonly mode?: ComposerPrefillMode
/**
* Text placed before an inserted prefill when the caret follows non-whitespace.
* Defaults to a newline; an empty string inserts verbatim. Ignored when replacing.
*/
readonly separator?: string
}
/**
* The public input action face provided to every session-scope slot
* component: stable-identity callbacks, mirroring the
@@ -292,19 +269,6 @@ export interface InputActions {
insertText(text: string, span: TokenSpan): boolean
/** Replace the whole draft with plain text, removing inline reference chips. */
setDraft(text: string): void
/**
* Write plugin-owned text into the draft in one undo step. An empty or
* whitespace-only draft, or `mode: 'replace'`, replaces the draft only while
* the session is live and the composer is in the plain phase; otherwise the
* current selection is replaced by an insertion at the selection, preceded
* by `separator` when the editor text before it ends in non-whitespace.
* Replacement edits are independent undo steps.
* @param text - text to write; an empty string is ignored.
* @param options - replacement mode and insertion separator.
* @returns whether the draft accepted the write; false for an empty string,
* a non-plain replacement, a changed draft revision, or a disposed session.
*/
prefill(text: string, options?: ComposerPrefillOptions): boolean
/** Write the current semantic document through the mounted persistence callback. */
persistDraft(): void
/** Append ordered browser-owned attachment ids; busy admission phases refuse. */
@@ -98,8 +98,7 @@ export type {
UseConversationViews,
} from './contract/slots.ts'
export type {
BeginCommandRequest, CommandClaim, ComposerPrefillMode, ComposerPrefillOptions,
ConsumeTokenRequest, DraftAttachmentId, DraftInitializationOptions,
BeginCommandRequest, CommandClaim, ConsumeTokenRequest, DraftAttachmentId, DraftInitializationOptions,
DraftInitializationResult, InputActions,
InputState, InsertReferenceRequest, InsertTextRequest, PickOutcome, SessionInput,
SessionInputResolver, SubmitAttachment, SubmitOutcome,
@@ -205,26 +205,6 @@ export class DraftEditorRuntime {
}, { discrete: true, tag: HISTORY_MERGE_TAG })
}
/**
* Replace the whole draft as one independent undo step at the top level.
* Unlike the seed {@link setDraft}, this edit carries a history boundary so
* the user can undo the plugin prefill without undoing an earlier seed or
* edit. Nested calls join their enclosing editor transaction.
* @param text - the full next draft.
*/
replaceDraft(text: string): void {
const clean = text.replace(REFERENCE_PLACEHOLDER_RE, '')
if (clean === this.projection.clipboardText && this.projection.occurrences.length === 0 && this.projection.quotes.length === 0) return
// An empty editor has no earlier entry to undo to; seed one before the first prefill.
if (this.editor.getEditorState().isEmpty()) {
this.applyEdit(() => { $getRoot().append($createParagraphNode()) }, HISTORY_MERGE_TAG)
}
this.applyEdit(() => {
$getRoot().clear()
$appendParagraphs(clean)
}, PASTE_TAG)
}
/**
* Add plain text after the current draft as new paragraphs, keeping its text
* and references; an empty draft takes the text as its whole content.
@@ -273,15 +253,9 @@ export class DraftEditorRuntime {
* @returns the ordered [start, end) span in detect coordinates.
*/
caretSpan(): { start: number; end: number } {
let span = { start: 0, end: 0 }
// A snapshot read misses uncommitted native selection. A discrete update
// synchronizes it; nested calls read the enclosing update's pending state.
this.applyEdit(() => {
const projection = $projectComposer(key => this.occurrenceIdOf(key))
const at = projection.detectText.length
span = projection.selection ?? { start: at, end: at }
})
return span
if (this.projection.selection !== null) return this.projection.selection
const at = this.projection.detectText.length
return { start: at, end: at }
}
/**
@@ -15,7 +15,7 @@ import {
} from '@deepseek-ai/dsh-client-store'
import type { LexicalEditor } from 'lexical'
import type {
CommandClaim, ComposerPrefillOptions, ConsumeTokenRequest, DraftAttachmentId, DraftInitializationOptions, DraftInitializationResult,
CommandClaim, ConsumeTokenRequest, DraftAttachmentId, DraftInitializationOptions, DraftInitializationResult,
InputActions, InputEffect, InputNotice, InputState, InputTriggerController, PickOutcome,
SessionInput, SubmitAttempt, SubmitAttachment, SubmitOutcome,
} from '../contract/input.ts'
@@ -148,7 +148,6 @@ export class SessionInputShell implements SessionInput {
return this.draftEditor.insertAsyncText(span, text)
},
setDraft: (text) => { this.setDraft(text) },
prefill: (text, options) => this.prefill(text, options),
persistDraft: () => { this.persistCurrentDraft() },
addAttachments: ids => this.addAttachments(ids),
removeAttachment: (id) => { this.removeAttachment(id) },
@@ -239,27 +238,6 @@ export class SessionInputShell implements SessionInput {
return inserted
}
/**
* Write plugin-owned text into the draft in one undo step.
* @param text - text to write; an empty string is ignored.
* @param options - replacement mode and insertion separator.
* @returns whether the draft accepted the write.
*/
prefill(text: string, options: ComposerPrefillOptions = {}): boolean {
if (text.length === 0) return false
if ((options.mode ?? 'insert') === 'replace' || this.snapshot.draft.trim() === '') {
if (this.snapshot.phase !== 'plain' || this.disposed) return false
this.cancelCommandRestore()
this.draftEditor.replaceDraft(text)
return true
}
const span = this.actions.captureInsertion()
const separator = options.separator ?? '\n'
const preceding = this.projection.detectText.slice(0, span.start)
const prefix = separator.length > 0 && span.start > 0 && !/\s$/.test(preceding) ? separator : ''
return this.actions.insertText(`${prefix}${text}`, span)
}
/**
* Reconnect optional catalog notifications and resume a pending command
* chip restore without waiting for another edit.
@@ -818,44 +818,6 @@ describe('draft documents in the input shell', () => {
)
})
describe('plugin prefill history', () => {
it.each(['insert', 'replace'] as const)('keeps the first %s prefill undoable before the editor mounts', async (mode) => {
const shell = makeShell()
expect(shell.editor.getRootElement()).toBeNull()
expect(shell.prefill('First plugin draft', { mode })).toBe(true)
shell.editor.dispatchCommand(UNDO_COMMAND, undefined)
await vi.waitFor(() => { expect(shell.draftSnapshot).toEqual({ text: '', references: [] }) })
shell.editor.dispatchCommand(REDO_COMMAND, undefined)
await vi.waitFor(() => { expect(shell.draftSnapshot).toEqual({ text: 'First plugin draft', references: [] }) })
})
it('keeps consecutive unmounted prefills as separate undo steps', async () => {
const shell = makeShell()
expect(shell.prefill('First')).toBe(true)
expect(shell.prefill('Second', { mode: 'replace' })).toBe(true)
shell.editor.dispatchCommand(UNDO_COMMAND, undefined)
await vi.waitFor(() => { expect(shell.draftSnapshot).toEqual({ text: 'First', references: [] }) })
shell.editor.dispatchCommand(UNDO_COMMAND, undefined)
await vi.waitFor(() => { expect(shell.draftSnapshot).toEqual({ text: '', references: [] }) })
shell.editor.dispatchCommand(REDO_COMMAND, undefined)
await vi.waitFor(() => { expect(shell.draftSnapshot).toEqual({ text: 'First', references: [] }) })
shell.editor.dispatchCommand(REDO_COMMAND, undefined)
await vi.waitFor(() => { expect(shell.draftSnapshot).toEqual({ text: 'Second', references: [] }) })
})
it('leaves an ordinary empty editor untouched until a prefill arrives', () => {
const shell = makeShell()
const root = document.createElement('div')
root.contentEditable = 'true'
document.body.append(root)
onTestFinished(() => { shell.editor.setRootElement(null); root.remove() })
shell.editor.setRootElement(root)
expect(shell.prefill('')).toBe(false)
expect(root.childElementCount).toBe(0)
expect(shell.draftSnapshot).toEqual({ text: '', references: [] })
})
})
describe('plain-text reference catalogs', () => {
it('leaves named tokens plain until their own catalog includes them', () => {
const unavailable = new Map<'/' | '@', readonly string[]>()
@@ -639,120 +639,3 @@ describe('scenario I: unknown /xyz + enter', () => {
expect(b.sink).not.toHaveBeenCalled()
})
})
describe('plugin composer prefill API', () => {
it('fills empty drafts, inserts at the caret, and honors replace and separator options', async () => {
const b = await bench()
expect(b.shell.prefill('')).toBe(false)
act(() => { expect(b.shell.prefill('Explain')).toBe(true) })
expect(b.shell.snapshot.draft).toBe('Explain')
act(() => { expect(b.shell.actions.prefill('tests')).toBe(true) })
expect(b.shell.snapshot.draft).toBe('Explain\ntests')
act(() => { expect(b.shell.prefill('next', { separator: ' ' })).toBe(true) })
expect(b.shell.snapshot.draft).toBe('Explain\ntests next')
act(() => { b.shell.setDraft('Keep ') })
act(() => { expect(b.shell.prefill('Next')).toBe(true) })
expect(b.shell.snapshot.draft).toBe('Keep Next')
act(() => { expect(b.shell.prefill('Only', { mode: 'replace' })).toBe(true) })
expect(b.shell.snapshot.draft).toBe('Only')
act(() => { expect(b.shell.prefill('Next', { separator: '' })).toBe(true) })
expect(b.shell.snapshot.draft).toBe('OnlyNext')
act(() => {
b.shell.editor.update(() => { $getRoot().selectStart() }, { discrete: true })
})
act(() => { expect(b.shell.prefill('Head')).toBe(true) })
expect(b.shell.snapshot.draft).toBe('HeadOnlyNext')
})
it('reports a rejected insertion without changing the draft', async () => {
const b = await bench()
act(() => { b.shell.setDraft('Busy') })
const insertText = vi.spyOn(b.shell.actions, 'insertText').mockReturnValue(false)
act(() => { expect(b.shell.prefill('Next')).toBe(false) })
expect(b.shell.snapshot.draft).toBe('Busy')
insertText.mockRestore()
})
})
describe('plugin composer prefill API guards and history', () => {
it('makes a replacement its own undo step', async () => {
const b = await bench()
act(() => { b.shell.setDraft('Old') })
act(() => { expect(b.shell.prefill('New', { mode: 'replace' })).toBe(true) })
expect(b.shell.snapshot.draft).toBe('New')
act(() => { b.shell.editor.dispatchCommand(UNDO_COMMAND, undefined) })
await act(async () => {})
expect(b.shell.snapshot.draft).toBe('Old')
})
it('refuses replacement while sending and after disposal', async () => {
const sending = await bench()
act(() => { sending.shell.setDraft('Sent') })
act(() => { sending.shell.submit() })
expect(sending.shell.snapshot.phase).toBe('sending')
act(() => { expect(sending.shell.prefill('New', { mode: 'replace' })).toBe(false) })
const disposed = await bench()
act(() => { disposed.shell.setDraft('Keep') })
disposed.shell.dispose()
act(() => { expect(disposed.shell.prefill('New', { mode: 'replace' })).toBe(false) })
act(() => { expect(disposed.shell.prefill('New')).toBe(false) })
})
it('checks the separator against detect coordinates around a reference chip', async () => {
const b = await bench()
act(() => { b.shell.setDraft('@res') })
const accepted = b.shell.insertReference({
source: 'reference',
ref: '@[Research](dsh-session:InNvdXJjZSI)',
label: 'Research',
clipboardText: '@[Research](dsh-session:InNvdXJjZSI)',
}, {
start: 0,
end: 4,
draftRev: b.shell.snapshot.draftRev,
})
expect(accepted).toBe(true)
const afterChip = b.shell.snapshot.draft
expect(afterChip.endsWith(' ')).toBe(true)
act(() => { expect(b.shell.prefill('Next')).toBe(true) })
expect(b.shell.snapshot.draft).toBe(`${afterChip}Next`)
})
it('replaces a whitespace-only draft', async () => {
const b = await bench()
act(() => { b.shell.setDraft(' \n ') })
act(() => { expect(b.shell.prefill('Text')).toBe(true) })
expect(b.shell.snapshot.draft).toBe('Text')
})
it.each(['captureInsertion', 'prefill'] as const)('reads the native caret through %s before its selection is projected', async (entry) => {
const b = await bench()
act(() => { b.shell.setDraft('Keep text') })
b.textarea.focus()
const text = b.textarea.firstChild?.firstChild
const selection = document.getSelection()
if (text === undefined || text === null || selection === null) throw new Error('missing native draft selection')
selection.setBaseAndExtent(text, 0, text, 0)
const before = b.shell.draftSnapshot
if (entry === 'captureInsertion') {
const revision = b.shell.snapshot.draftRev
const track = vi.spyOn(b.controller, 'track')
const expected = { start: 0, end: 0, draftRev: revision }
expect(b.shell.actions.captureInsertion()).toEqual(expected)
expect(track).toHaveBeenCalledOnce()
expect(b.shell.actions.captureInsertion()).toEqual(expected)
expect(track).toHaveBeenCalledOnce()
expect(b.shell.draftSnapshot).toBe(before)
expect(b.shell.snapshot.draftRev).toBe(revision)
expect(b.sink).not.toHaveBeenCalled()
return
}
act(() => { expect(b.shell.prefill('Head', { separator: '' })).toBe(true) })
expect(b.shell.snapshot.draft).toBe('HeadKeep text')
act(() => { b.shell.editor.dispatchCommand(UNDO_COMMAND, undefined) })
await act(async () => {})
expect(b.shell.draftSnapshot).toEqual(before)
expect(b.sink).not.toHaveBeenCalled()
})
})
@@ -242,7 +242,6 @@ const unusedFactoryProps = {
captureInsertion: unusedFrameworkProp,
insertText: unusedFrameworkProp,
setDraft: unusedFrameworkProp,
prefill: unusedFrameworkProp,
persistDraft: unusedFrameworkProp,
addAttachments: unusedFrameworkProp,
removeAttachment: unusedFrameworkProp,
@@ -29,8 +29,7 @@ const standard: GlobalStandardProps & SessionStandardProps = {
useSessionRetainInfo: unused, useResource: unused, useWorkspaces: unused,
...allowAllAccess,
useContextMenu: contextMenuHook(),
inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused,
setDraft: unused, prefill: unused, persistDraft: unused,
inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused, setDraft: unused, persistDraft: unused,
addAttachments: unused, removeAttachment: unused, pruneAttachments: unused, submit: unused },
}
@@ -36,8 +36,7 @@ const standard: GlobalStandardProps & SessionStandardProps = {
useSessionRetainInfo: unused, useResource: unused, useWorkspaces: unused,
...allowAllAccess,
useContextMenu: contextMenuHook(),
inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused,
setDraft: unused, prefill: unused, persistDraft: unused,
inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused, setDraft: unused, persistDraft: unused,
addAttachments: unused, removeAttachment: unused, pruneAttachments: unused, submit: unused },
}
@@ -25,8 +25,7 @@ const standard: GlobalStandardProps & SessionStandardProps = {
useSessionRetainInfo: unused, useResource: unused, useWorkspaces: unused,
...allowAllAccess,
useContextMenu: contextMenuHook(),
inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused,
setDraft: unused, prefill: unused, persistDraft: unused,
inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused, setDraft: unused, persistDraft: unused,
addAttachments: unused, removeAttachment: unused, pruneAttachments: unused, submit: unused },
}
const choices: TerminalLaunchShells = {
@@ -107,7 +107,6 @@ function props(
captureInsertion: unused,
insertText: unused,
setDraft: unused,
prefill: unused,
persistDraft: unused,
addAttachments: unused,
removeAttachment: unused,
@@ -220,7 +220,6 @@ function standaloneProps(
captureInsertion: () => ({ start: 0, end: 0, draftRev: 0 }),
insertText: () => false,
setDraft: () => {},
prefill: () => false,
persistDraft: () => {},
addAttachments: () => false,
removeAttachment: () => {},
@@ -359,7 +358,6 @@ function mount(fixture: Awaited<ReturnType<typeof bench>>) {
captureInsertion: () => ({ start: 0, end: 0, draftRev: 0 }),
insertText: () => false,
setDraft: vi.fn(),
prefill: vi.fn(() => false),
persistDraft: vi.fn(),
addAttachments: vi.fn(() => false),
removeAttachment: vi.fn(),
@@ -137,7 +137,6 @@ const kit: Omit<QuestionComposerProps, 'matched'> = {
captureInsertion: () => ({ start: 0, end: 0, draftRev: 0 }),
insertText: () => false,
setDraft: () => { throw new Error('unused') },
prefill: () => false,
persistDraft: () => { throw new Error('unused') },
addAttachments: () => { throw new Error('unused') },
removeAttachment: () => { throw new Error('unused') },
@@ -143,7 +143,6 @@ const kitBase: Omit<QuestionComposerProps, 'matched' | 'useStore' | 'useQuestion
captureInsertion: () => ({ start: 0, end: 0, draftRev: 0 }),
insertText: () => false,
setDraft: () => { throw new Error('unused') },
prefill: () => false,
persistDraft: () => { throw new Error('unused') },
addAttachments: () => { throw new Error('unused') },
removeAttachment: () => { throw new Error('unused') },
@@ -355,7 +355,6 @@ function panelProps(data: WorkflowRunChatData, sessions = listState(), openSessi
captureInsertion: () => ({ start: 0, end: 0, draftRev: 0 }),
insertText: () => false,
setDraft: () => {},
prefill: () => false,
persistDraft: () => {},
addAttachments: () => false,
removeAttachment: () => {},
@@ -24,7 +24,6 @@ function fixture(recording?: Recording, navigable = true) {
const inputActions = { notify: vi.fn(), insertQuote: () => false,
captureInsertion: vi.fn(() => ({ start: 3, end: 3, draftRev: 1 })), insertText: vi.fn(() => true),
setDraft: vi.fn(), persistDraft: vi.fn(), addAttachments: vi.fn(() => true), removeAttachment: vi.fn(),
prefill: vi.fn(() => false),
pruneAttachments: vi.fn(), submit: vi.fn() }
const readiness = createSnapshotStore<SpeechReadiness>({ connected: true, error: null, catalog: {
providers: [{ id, name: 'SenseVoiceSmall', location: 'host-local', languages: ['auto', 'zh', 'en', 'ja'], preparation: { phase: 'ready' } }],
@@ -182,12 +182,11 @@ describe('Node program process', () => {
})
it.each([{}, { timeoutMs: null }] as const)('cancels a live program and closes its managed process with %j', async (timing) => {
const { run, cwd } = await setup()
const { run } = await setup()
const entered = Promise.withResolvers<undefined>()
const controller = new AbortController()
// Synchronous file readiness does not depend on the busy child's control pipe draining.
const active = run({ ...timing, program: 'const fs = await import("node:fs"); fs.writeFileSync("entered", "ready"); for (;;) {}', signal: controller.signal, bindings: [] })
onTestFinished(async () => { controller.abort('test finished'); await active })
await expect.poll(() => readFile(join(cwd, 'entered'), 'utf8')).toBe('ready')
const active = run({ ...timing, program: 'void tools.enter({}); for (;;) {}', signal: controller.signal, bindings: bindings({ enter: async () => { entered.resolve(undefined); return null } }) })
await entered.promise
controller.abort('stop')
expect((await active).error).toEqual({ kind: 'abort', message: 'stop' })
})
@@ -27,8 +27,7 @@ const standard: GlobalStandardProps & SessionStandardProps = {
useSessionAccess: () => true, useRemoteAccess: () => true, useWorkspaceAccess: () => true,
useConversation: unused, useInput: unused, useChat: unused, useTrajectory: unused,
usePanelInfo: unused, useContextMenu: contextMenuHook(), useResource: unused, useWorkspaces: unused,
inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused,
setDraft: unused, prefill: unused, persistDraft: unused,
inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused, setDraft: unused, persistDraft: unused,
addAttachments: unused, removeAttachment: unused, pruneAttachments: unused, submit: unused },
}