@changfenhuang/dsh-genui 0.11.3 → 0.12.0
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.
- package/CHANGELOG.md +44 -0
- package/README.md +21 -6
- package/README.zh-CN.md +21 -6
- package/SKILL.md +18 -6
- package/lib/assets/mermaid.js +325 -325
- package/lib/assets/standalone-runtime.js +10 -9
- package/lib/client.js +31 -33
- package/lib/index.js +1060 -371
- package/lib/types/client/blocks/forms.d.ts +5 -6
- package/lib/types/client/blocks/state.d.ts +2 -11
- package/lib/types/client/dom-fence.d.ts +2 -1
- package/lib/types/client/file-address.d.ts +4 -0
- package/lib/types/client/file-link-context.d.ts +41 -0
- package/lib/types/client/file-link.d.ts +17 -0
- package/lib/types/client/i18n/en.d.ts +5 -1
- package/lib/types/client/inline.d.ts +1 -1
- package/lib/types/client/mermaid-colors.d.ts +2 -0
- package/lib/types/client/panel.d.ts +6 -1
- package/lib/types/client/spec.d.ts +8 -11
- package/lib/types/client/submission-registry.d.ts +51 -0
- package/lib/types/client/table-details.d.ts +20 -0
- package/lib/types/client/walk-spec.d.ts +3 -0
- package/lib/types/plugin/fence-feedback.d.ts +55 -12
- package/lib/types/plugin/index.d.ts +7 -3
- package/lib/types/shared/fence-repair.d.ts +19 -7
- package/package.json +1 -12
- package/src/client/GenuiBlock.module.css +100 -4
- package/src/client/GenuiBlock.tsx +7 -14
- package/src/client/artifact/ArtifactExport.module.css +34 -3
- package/src/client/artifact/ExportableGenuiBlock.tsx +23 -2
- package/src/client/blocks/charts.tsx +63 -14
- package/src/client/blocks/forms.tsx +26 -48
- package/src/client/blocks/render-node.tsx +11 -8
- package/src/client/blocks/state.ts +2 -12
- package/src/client/dom-fence.tsx +190 -50
- package/src/client/file-address.ts +37 -0
- package/src/client/file-link-context.tsx +114 -0
- package/src/client/file-link.ts +46 -0
- package/src/client/genui-runtime/diagnostics.ts +20 -0
- package/src/client/genui-runtime/normalize.ts +28 -9
- package/src/client/guard.ts +98 -153
- package/src/client/i18n/en.ts +5 -1
- package/src/client/i18n/zh.ts +5 -1
- package/src/client/index.tsx +23 -5
- package/src/client/inline.ts +28 -10
- package/src/client/mermaid-colors.ts +39 -0
- package/src/client/mermaid-core.ts +7 -3
- package/src/client/panel.tsx +18 -5
- package/src/client/spec.ts +8 -11
- package/src/client/submission-registry.ts +150 -0
- package/src/client/table-details.ts +151 -0
- package/src/client/walk-spec.ts +43 -0
- package/src/plugin/fence-feedback.ts +384 -53
- package/src/plugin/index.ts +20 -9
- package/src/plugin/tool.ts +8 -2
- package/src/shared/fence-repair.ts +345 -128
- package/tsdown.config.ts +3 -7
- package/lib/invariant.js +0 -19
- package/lib/types/plugin/invariant.d.ts +0 -15
- package/src/plugin/invariant.ts +0 -30
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RadioSubmissionMember } from '../submission-registry.ts';
|
|
2
|
+
import type { AnswersState, GenuiBlockProps } from './state.ts';
|
|
2
3
|
import type { GenuiInput, GenuiRadio, GenuiSelect, GenuiSlider, GenuiSubmit, GenuiSwitch, GenuiTextarea } from '../spec.ts';
|
|
3
4
|
export declare function RadioNode({ node, onAction, answers }: {
|
|
4
5
|
node: GenuiRadio;
|
|
5
6
|
onAction?: GenuiBlockProps['onAction'];
|
|
6
7
|
answers?: AnswersState | undefined;
|
|
7
8
|
}): import("react").JSX.Element;
|
|
8
|
-
/**
|
|
9
|
-
export declare function correctLabelOf(m:
|
|
10
|
-
/**
|
|
11
|
-
* LOCAL-FIRST grading; when checkbox selections participate, the click falls
|
|
12
|
-
* back to the aggregation action so multi-select data is never discarded. */
|
|
9
|
+
/** 根据静态 radio member 获取正确选项的标签。 */
|
|
10
|
+
export declare function correctLabelOf(m: RadioSubmissionMember): string | undefined;
|
|
11
|
+
/** 汇总当前 block 的表单状态,并根据 resolver 的结果提交或本地判卷。 */
|
|
13
12
|
export declare function SubmitNode({ node, onAction, answers }: {
|
|
14
13
|
node: GenuiSubmit;
|
|
15
14
|
onAction?: GenuiBlockProps['onAction'];
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { GenuiSpec } from '../spec.ts';
|
|
9
9
|
import type { BlockInteractionState } from '../interaction-store.ts';
|
|
10
|
+
import type { SubmissionRegistry } from '../submission-registry.ts';
|
|
10
11
|
export interface GenuiBlockProps {
|
|
11
12
|
/** Parsed spec to render. */
|
|
12
13
|
spec: GenuiSpec;
|
|
@@ -33,15 +34,6 @@ export interface GenuiBlockProps {
|
|
|
33
34
|
/** Observe the current secret-free durable state without changing persistence ownership. */
|
|
34
35
|
onStateSnapshot?: ((state: BlockInteractionState) => void) | undefined;
|
|
35
36
|
}
|
|
36
|
-
/** Per-question metadata registered by grouped radios for local grading. */
|
|
37
|
-
export interface QuestionMeta {
|
|
38
|
-
label: string;
|
|
39
|
-
options: string[];
|
|
40
|
-
/** Correct option: index (number) or label (string); absent = no local grading. */
|
|
41
|
-
answer?: number | string | undefined;
|
|
42
|
-
/** Shown after local grading. */
|
|
43
|
-
explanation?: string | undefined;
|
|
44
|
-
}
|
|
45
37
|
/** Block-wide aggregation state. Grouped radios store one selected label in
|
|
46
38
|
* `answers`; grouped checkboxes store a selected-label array in
|
|
47
39
|
* `multiAnswers`. Keeping the registries separate preserves the existing
|
|
@@ -57,7 +49,7 @@ export interface AnswersState {
|
|
|
57
49
|
fields: Record<string, string>;
|
|
58
50
|
/** Field ids whose value must never be persisted or collected (secrets). */
|
|
59
51
|
secretFields: ReadonlySet<string>;
|
|
60
|
-
|
|
52
|
+
registry: SubmissionRegistry;
|
|
61
53
|
/** True after a local grading: questions are locked until 重新作答. */
|
|
62
54
|
locked: boolean;
|
|
63
55
|
/** Bumped by every reset; radios use it as their remount key. */
|
|
@@ -66,7 +58,6 @@ export interface AnswersState {
|
|
|
66
58
|
setMultiAnswer: (group: string, choice: string, checked: boolean) => void;
|
|
67
59
|
setField: (id: string, value: string) => void;
|
|
68
60
|
registerSecretField: (id: string) => void;
|
|
69
|
-
registerMeta: (group: string, meta: QuestionMeta) => void;
|
|
70
61
|
clear: () => void;
|
|
71
62
|
setLocked: (locked: boolean) => void;
|
|
72
63
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Root } from 'react-dom/client';
|
|
2
2
|
import type { Context } from '@deepseek-ai/cordis';
|
|
3
3
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
4
|
+
import { type FileLinkAvailability } from './file-link-context.tsx';
|
|
4
5
|
/** Override the React root factory (tests / tuning). */
|
|
5
6
|
export declare function setDomRootFactory(factory: (container: HTMLElement) => Root): void;
|
|
6
7
|
/**
|
|
@@ -27,4 +28,4 @@ export declare function hostFenceIndexOf(row: Element, block: Element): number;
|
|
|
27
28
|
* @param sendAction - plugin-owned relay: (sessionId, action, payload) → the
|
|
28
29
|
* scoped conversation send carrying the `[genui-action]` prompt.
|
|
29
30
|
*/
|
|
30
|
-
export declare function installDomFenceRenderer(ctx: Context, sendAction: (sessionId: SessionId, action: string, payload: Record<string, unknown>) => void): () => void;
|
|
31
|
+
export declare function installDomFenceRenderer(ctx: Context, sendAction: (sessionId: SessionId, action: string, payload: Record<string, unknown>) => void, fileLinkAvailability?: FileLinkAvailability): () => void;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Build a session resource address while preserving encoded path segments. */
|
|
2
|
+
export declare function sessionFileAddress(sessionId: string, path: string): string;
|
|
3
|
+
/** Resolve workspace absolute paths to session-relative resource addresses. */
|
|
4
|
+
export declare function fileAddressFor(sessionId: string, cwd: string | undefined, path: string): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
interface SidebarRightService {
|
|
3
|
+
openResource(address: string, options?: {
|
|
4
|
+
params: {
|
|
5
|
+
line: number;
|
|
6
|
+
};
|
|
7
|
+
}): void;
|
|
8
|
+
}
|
|
9
|
+
interface FileLinkHostContext {
|
|
10
|
+
get(name: 'sidebarRight', strict?: false): SidebarRightService | undefined;
|
|
11
|
+
sessions: {
|
|
12
|
+
list: {
|
|
13
|
+
getSnapshot(): unknown;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
interface FileLinkScope {
|
|
18
|
+
get(name: 'sidebarRight'): SidebarRightService | undefined;
|
|
19
|
+
effect(callback: () => void | (() => void), name?: string): unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface FileLinkCordisContext extends FileLinkHostContext {
|
|
22
|
+
inject(services: ['sidebarRight'], callback: (scope: FileLinkScope) => void): unknown;
|
|
23
|
+
}
|
|
24
|
+
export interface FileLinkAvailability {
|
|
25
|
+
subscribe(listener: () => void): () => void;
|
|
26
|
+
getSnapshot(): boolean;
|
|
27
|
+
}
|
|
28
|
+
/** Track an optional host service through arrival, removal, and plugin unload. */
|
|
29
|
+
export declare function createFileLinkAvailability(ctx: FileLinkCordisContext): FileLinkAvailability;
|
|
30
|
+
/** Render a file link as an accessible host navigation button when available. */
|
|
31
|
+
export declare function FileLink({ path, line, label, children }: {
|
|
32
|
+
path: string;
|
|
33
|
+
line?: number;
|
|
34
|
+
label: string;
|
|
35
|
+
children?: ReactNode;
|
|
36
|
+
}): import("react").DetailedReactHTMLElement<{
|
|
37
|
+
title: string;
|
|
38
|
+
}, HTMLElement>;
|
|
39
|
+
/** Add session-scoped file navigation to one fence render tree. */
|
|
40
|
+
export declare function withFileLinkContext(ctx: FileLinkHostContext, availability: FileLinkAvailability, sessionId: string | undefined, streaming: boolean, children: ReactNode): ReactNode;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ParsedFileLink {
|
|
2
|
+
path: string;
|
|
3
|
+
line?: number;
|
|
4
|
+
}
|
|
5
|
+
export type LinkTarget = {
|
|
6
|
+
type: 'external';
|
|
7
|
+
href: string;
|
|
8
|
+
} | {
|
|
9
|
+
type: 'file';
|
|
10
|
+
file: ParsedFileLink;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'invalid';
|
|
13
|
+
};
|
|
14
|
+
/** Decode a local file destination and its optional first line fragment. */
|
|
15
|
+
export declare function parseFileLink(value: string): ParsedFileLink | undefined;
|
|
16
|
+
/** Classify a link destination while preserving the existing external URL policy. */
|
|
17
|
+
export declare function classifyLinkTarget(value: unknown): LinkTarget;
|
|
@@ -262,6 +262,10 @@ export declare const EN: {
|
|
|
262
262
|
readonly 'label.copyButtonTitle': "{action}; right-click for copy options";
|
|
263
263
|
readonly 'block.copyMarkdown': "Copy Markdown";
|
|
264
264
|
readonly 'block.copyCsv': "Copy CSV";
|
|
265
|
+
readonly 'block.table.expandDetails': "Expand details for {label}";
|
|
266
|
+
readonly 'block.table.collapseDetails': "Collapse details for {label}";
|
|
267
|
+
readonly 'block.table.expandGroup': "Expand group {label}";
|
|
268
|
+
readonly 'block.table.collapseGroup': "Collapse group {label}";
|
|
265
269
|
readonly 'block.total': "Total";
|
|
266
270
|
readonly 'block.filteredRows': "{visible} / {total} rows after filtering";
|
|
267
271
|
readonly 'block.filterMatch': "{shown} / {total} matched";
|
|
@@ -277,7 +281,7 @@ export declare const EN: {
|
|
|
277
281
|
readonly 'block.svgPreview': "Preview";
|
|
278
282
|
readonly 'block.svgSource': "Source";
|
|
279
283
|
readonly 'block.svgError': "SVG could not be displayed — showing the source instead";
|
|
280
|
-
readonly 'block.mermaidError': "
|
|
284
|
+
readonly 'block.mermaidError': "Diagram rendering failed — showing the source instead";
|
|
281
285
|
readonly 'block.mermaidLoading': "Rendering…";
|
|
282
286
|
readonly 'block.scene3dLoading': "Loading 3D scene…";
|
|
283
287
|
readonly 'block.scene3dError': "3D rendering failed";
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
import { type ReactNode } from 'react';
|
|
3
3
|
export declare function hasInlineMarkup(text: string): boolean;
|
|
4
4
|
/** Render safe phrasing content, usable in headings, buttons and labels too. */
|
|
5
|
-
export declare function renderInline(text: string, allowLinks?: boolean, depth?: number): ReactNode;
|
|
5
|
+
export declare function renderInline(text: string, allowLinks?: boolean | 'file', depth?: number): ReactNode;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { type GenuiActionHandler } from './action-context.ts';
|
|
2
|
+
import { type FileLinkAvailability, type FileLinkCordisContext } from './file-link-context.tsx';
|
|
2
3
|
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
4
|
/** Injection face built per session in apply (scoped conversation send). */
|
|
4
5
|
export interface GenuiPanelInjected {
|
|
5
6
|
sessionId: string;
|
|
6
7
|
sendGenuiAction: GenuiActionHandler;
|
|
8
|
+
fileLinkContext?: {
|
|
9
|
+
ctx: FileLinkCordisContext;
|
|
10
|
+
availability: FileLinkAvailability;
|
|
11
|
+
};
|
|
7
12
|
/** Template center "try it": insert the template instruction into the
|
|
8
13
|
* current composer draft (standard conversation.input.for channel). */
|
|
9
14
|
insertTemplate: (text: string) => void;
|
|
@@ -15,4 +20,4 @@ export type GenuiPanelProps = PropsRuntime<'conversation.input.dock'> & GenuiPan
|
|
|
15
20
|
* default so the dock never steals the message flow's scroll room; the
|
|
16
21
|
* header always shows the current panel title.
|
|
17
22
|
*/
|
|
18
|
-
export declare function GenuiPanel({ sessionId, sendGenuiAction, insertTemplate }: GenuiPanelProps): import("react").JSX.Element | null;
|
|
23
|
+
export declare function GenuiPanel({ sessionId, sendGenuiAction, insertTemplate, fileLinkContext }: GenuiPanelProps): import("react").JSX.Element | null;
|
|
@@ -372,17 +372,13 @@ export interface GenuiRadio {
|
|
|
372
372
|
/** v2.6: per-question explanation shown after local grading. */
|
|
373
373
|
explanation?: string;
|
|
374
374
|
}
|
|
375
|
-
/**
|
|
376
|
-
* this block. LOCAL-FIRST grading still applies to radio-only question scopes;
|
|
377
|
-
* aggregation scopes can emit strings for radio groups and string arrays for
|
|
378
|
-
* checkbox groups in the same `answers` object. */
|
|
375
|
+
/** 提交节点收集当前 block 的答案和字段值;纯 radio 范围可在本地判卷。 */
|
|
379
376
|
export interface GenuiSubmit {
|
|
380
377
|
type: 'submit';
|
|
381
378
|
label: string;
|
|
382
379
|
/**
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
* an action and emit `{type:'submit', answers, total, answered}`.
|
|
380
|
+
* 可选 action 名称。纯 radio 范围满足本地判卷条件时无需 action;其他提交
|
|
381
|
+
* 通过 action 发送 `answers`、可选的 `fields`、`total` 和 `answered`。
|
|
386
382
|
*/
|
|
387
383
|
action?: string;
|
|
388
384
|
/**
|
|
@@ -392,10 +388,11 @@ export interface GenuiSubmit {
|
|
|
392
388
|
*/
|
|
393
389
|
resetAction?: string;
|
|
394
390
|
/**
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
391
|
+
* `groups` 引用当前 block 中的 submission member key,来源为 radio.group、
|
|
392
|
+
* checkbox.group 以及 input/textarea/select/slider.id。radio 需已选择,
|
|
393
|
+
* checkbox 需至少选择一项,普通 field 需在 trim 后非空。`groups` 控制提交
|
|
394
|
+
* 所需成员与完成进度;payload 仍收集当前 block 中已填写的表单状态。
|
|
395
|
+
* 未设置时,只要存在至少一个已完成成员即可提交。
|
|
399
396
|
*/
|
|
400
397
|
groups?: string[];
|
|
401
398
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { GenuiSpec } from './spec.ts';
|
|
2
|
+
export type SubmissionMember = RadioSubmissionMember | CheckboxSubmissionMember | FieldSubmissionMember;
|
|
3
|
+
export interface RadioSubmissionMember {
|
|
4
|
+
kind: 'radio';
|
|
5
|
+
key: string;
|
|
6
|
+
label: string;
|
|
7
|
+
options: string[];
|
|
8
|
+
answer?: number | string;
|
|
9
|
+
explanation?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CheckboxSubmissionMember {
|
|
12
|
+
kind: 'checkbox';
|
|
13
|
+
key: string;
|
|
14
|
+
}
|
|
15
|
+
export interface FieldSubmissionMember {
|
|
16
|
+
kind: 'field';
|
|
17
|
+
key: string;
|
|
18
|
+
fieldType: 'input' | 'textarea' | 'select' | 'slider';
|
|
19
|
+
secret: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface SubmissionRegistry {
|
|
22
|
+
members: ReadonlyMap<string, SubmissionMember>;
|
|
23
|
+
}
|
|
24
|
+
export interface SubmitInteractionState {
|
|
25
|
+
answers: Record<string, string>;
|
|
26
|
+
multiAnswers: Record<string, string[]>;
|
|
27
|
+
fields: Record<string, string>;
|
|
28
|
+
secretFields: ReadonlySet<string>;
|
|
29
|
+
}
|
|
30
|
+
export interface ResolvedSubmitState {
|
|
31
|
+
scope: SubmissionMember[];
|
|
32
|
+
answered: number;
|
|
33
|
+
total: number;
|
|
34
|
+
localGradeEligible: boolean;
|
|
35
|
+
hasOutOfScopePayload: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** 从 spec 建立成员表,并诊断 submission key 和 submit.groups。 */
|
|
38
|
+
export declare function analyzeSubmissionRegistry(spec: GenuiSpec): {
|
|
39
|
+
registry: SubmissionRegistry;
|
|
40
|
+
diagnostics: string[];
|
|
41
|
+
};
|
|
42
|
+
/** 编译供运行时读取的静态 submission 成员表。 */
|
|
43
|
+
export declare function compileSubmissionRegistry(spec: GenuiSpec): SubmissionRegistry;
|
|
44
|
+
/** 根据当前交互状态判断一个 submission member 是否完成。 */
|
|
45
|
+
export declare function isSubmissionMemberAnswered(member: SubmissionMember, state: SubmitInteractionState): boolean;
|
|
46
|
+
/** 计算 submit 进度和纯本地判卷条件。 */
|
|
47
|
+
export declare function resolveSubmitState({ registry, groups, state }: {
|
|
48
|
+
registry: SubmissionRegistry;
|
|
49
|
+
groups?: string[];
|
|
50
|
+
state: SubmitInteractionState;
|
|
51
|
+
}): ResolvedSubmitState;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** 保存 repair 所需的原始 table 字段。 */
|
|
2
|
+
export interface TableDetailSource {
|
|
3
|
+
columns?: unknown;
|
|
4
|
+
rows?: unknown;
|
|
5
|
+
data?: unknown;
|
|
6
|
+
types?: unknown;
|
|
7
|
+
}
|
|
8
|
+
/** 保存 repair 后的 table 列和行。 */
|
|
9
|
+
export interface PreparedTableRows {
|
|
10
|
+
columns: string[];
|
|
11
|
+
rows: Array<Array<string | number>>;
|
|
12
|
+
}
|
|
13
|
+
/** 使用 table repair 相同的转换规则处理列和行。 */
|
|
14
|
+
export declare function prepareTableRows(table: TableDetailSource): PreparedTableRows | null;
|
|
15
|
+
/** 返回 repair 后可显示详情的 table 行。 */
|
|
16
|
+
export declare function tableRowsForDetails<Row = unknown>(table: TableDetailSource): Row[];
|
|
17
|
+
/** 按照 table renderer 的规则识别分组标题行。 */
|
|
18
|
+
export declare function isTableGroupHeaderRow(row: unknown, types: unknown): boolean;
|
|
19
|
+
/** 判断详情索引对应的行是否可展开并显示。 */
|
|
20
|
+
export declare function isTableDetailReachable(table: TableDetailSource, rowIndex: number): boolean;
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
*
|
|
9
9
|
* The loop is deliberately narrow, matching the contract agreed on the issue:
|
|
10
10
|
* - **默认开启。** 插件配置中的 `fenceFeedback: false` 可以关闭回合转向。
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
11
|
+
* - **修正上限。** 每个 turn 最多发送两条 correction;render failure 和 delivery reminder 共用上限,
|
|
12
|
+
* 每个 fence body 在当前回合中最多修正一次,新回合可重新修正。
|
|
13
|
+
* - **仅含 reasoning 的恢复。** 已验证的 GenUI 回合若只以 reasoning block 结束,使用宿主的
|
|
14
|
+
* `EMPTY_RESPONSE` retry policy。
|
|
13
15
|
* - **Never for subagents.** A child session's fence belongs to a parent reply.
|
|
14
16
|
* - **Exact fence matching.** Only an info string of exactly `dsh-ui` opens a
|
|
15
17
|
* fence, so ` ```dsh-ui-dark `, indented prose, or a mention of the name is
|
|
@@ -28,6 +30,11 @@ import type { UserMessage } from '@deepseek-ai/dsh-session';
|
|
|
28
30
|
export declare const FEEDBACK_PLUGIN_NAME = "@changfenhuang/dsh-genui";
|
|
29
31
|
/** Source kind persisted by this plugin in Session format v4. */
|
|
30
32
|
export declare const FEEDBACK_SOURCE_KIND: "plugin:@changfenhuang/dsh-genui";
|
|
33
|
+
/**
|
|
34
|
+
* 同一个 turn 内 fence-feedback correction 的共享上限。
|
|
35
|
+
* Render failure 和 nothing-delivered reminder 共用该 budget;各自的 ledger 独立阻止重复修正。
|
|
36
|
+
*/
|
|
37
|
+
export declare const MAX_CORRECTIONS_PER_TURN = 2;
|
|
31
38
|
/** One ```dsh-ui fence found in an assistant reply. */
|
|
32
39
|
export interface ExtractedFence {
|
|
33
40
|
/** Raw body between the fences (no delimiters). */
|
|
@@ -82,31 +89,67 @@ export declare function fenceCorrectionText(failures: readonly FenceFailure[]):
|
|
|
82
89
|
export declare function createFeedbackMessage(text: string, sessionFormatVersion: number): UserMessage;
|
|
83
90
|
/** What the pure planner needs to decide whether a correction may be sent. */
|
|
84
91
|
export interface FenceFeedbackPlanInput {
|
|
92
|
+
/** Latest assistant reply text of the current turn. */
|
|
85
93
|
readonly text: string;
|
|
86
94
|
readonly turn: number;
|
|
87
|
-
|
|
88
|
-
readonly
|
|
95
|
+
/** Fence bodies already corrected for a render failure in the current turn. */
|
|
96
|
+
readonly correctedSpec: ReadonlySet<string>;
|
|
97
|
+
/** Turns already given the delivery reminder. */
|
|
98
|
+
readonly deliveryRemindedTurns?: ReadonlySet<number> | undefined;
|
|
99
|
+
/** A `validate_dsh_ui` call happened this turn (formal GenUI signal). */
|
|
100
|
+
readonly validatedThisTurn?: boolean | undefined;
|
|
101
|
+
/** The turn already delivered a body or a successful `render_ui` result. */
|
|
102
|
+
readonly deliveredThisTurn?: boolean | undefined;
|
|
89
103
|
readonly aborted: boolean;
|
|
104
|
+
/** Corrections already steered in this turn (shared hard cap). */
|
|
105
|
+
readonly correctionsThisTurn?: number | undefined;
|
|
106
|
+
/** Turn {@link correctionsThisTurn} counts (stale counts are ignored). */
|
|
107
|
+
readonly correctionsTurn?: number | undefined;
|
|
108
|
+
/** Whether the host has registered the `render_ui` tool. Defaults to enabled. */
|
|
109
|
+
readonly renderUiToolEnabled?: boolean | undefined;
|
|
90
110
|
}
|
|
91
111
|
/** A correction the caller must account for before steering. */
|
|
92
112
|
export interface FenceFeedbackPlan {
|
|
93
113
|
readonly text: string;
|
|
94
114
|
readonly fingerprints: readonly string[];
|
|
95
115
|
readonly turn: number;
|
|
116
|
+
/**
|
|
117
|
+
* `render` corrects a fence that failed to resolve; `delivery` reminds the
|
|
118
|
+
* model that the turn produced nothing formal. They share the per-turn budget
|
|
119
|
+
* but keep separate ledgers.
|
|
120
|
+
*/
|
|
121
|
+
readonly kind: 'render' | 'delivery';
|
|
96
122
|
}
|
|
97
123
|
/**
|
|
98
|
-
* Decide whether this turn boundary should steer a
|
|
99
|
-
*
|
|
100
|
-
*
|
|
124
|
+
* Decide whether this turn boundary should steer a correction — the pure core of
|
|
125
|
+
* the loop, so every bound (per-turn cap, per-fence ledger, cancellation) is
|
|
126
|
+
* testable without a host.
|
|
101
127
|
*
|
|
102
|
-
*
|
|
128
|
+
* The decision is driven by FORMAL events only: fences in the reply body, a
|
|
129
|
+
* `validate_dsh_ui` call, a delivered body text or a successful `render_ui`
|
|
130
|
+
* result. The reasoning block is
|
|
131
|
+
* never read here — a draft inside the thinking block is not proof that the model
|
|
132
|
+
* chose to deliver it, so it must not change any decision.
|
|
133
|
+
*
|
|
134
|
+
* @param input - reply text, turn identity, formal signals, and the accounting.
|
|
103
135
|
* @returns the correction to send, or null when the loop must stay silent.
|
|
104
136
|
*/
|
|
105
137
|
export declare function planFenceFeedback(input: FenceFeedbackPlanInput): FenceFeedbackPlan | null;
|
|
106
138
|
/**
|
|
107
|
-
*
|
|
139
|
+
* Correction for a validated GenUI turn that reached the boundary without any
|
|
140
|
+
* formal delivery.
|
|
141
|
+
*
|
|
142
|
+
* @param turn - turn that reached the boundary.
|
|
143
|
+
* @param attempt - correction number within the shared turn budget.
|
|
144
|
+
* @param renderUiToolEnabled - whether the correction may recommend `render_ui`.
|
|
145
|
+
* @returns the message text to steer into the running turn.
|
|
146
|
+
*/
|
|
147
|
+
export declare function missingBodyCorrectionText(turn: number, attempt?: number, renderUiToolEnabled?: boolean): string;
|
|
148
|
+
/**
|
|
149
|
+
* 注册 GenUI 回合跟踪、宿主重试判定和可选的围栏修正流程。
|
|
108
150
|
*
|
|
109
|
-
* @param ctx -
|
|
110
|
-
* @param enabled -
|
|
151
|
+
* @param ctx - 宿主 Context。
|
|
152
|
+
* @param enabled - 是否启用同回合围栏修正。
|
|
153
|
+
* @param renderUiToolEnabled - 是否已注册 `render_ui` 工具。
|
|
111
154
|
*/
|
|
112
|
-
export declare function installFenceFeedback(ctx: Context, enabled: boolean): void;
|
|
155
|
+
export declare function installFenceFeedback(ctx: Context, enabled: boolean, renderUiToolEnabled?: boolean): void;
|
|
@@ -13,7 +13,7 @@ import { Context } from '@deepseek-ai/cordis';
|
|
|
13
13
|
* Deliberately slim: the `genui` skill carries the full component→field
|
|
14
14
|
* mapping; this section keeps only the contract that must always be
|
|
15
15
|
* present (fence syntax, type whitelist, and critical behavioral rules). */
|
|
16
|
-
export declare const GENUI_SECTION_TEXT = "You can render interactive UI components INSIDE your reply \u2014 between paragraphs \u2014 by emitting a fenced block with the language tag `dsh-ui` containing a JSON spec:\n\n```dsh-ui\n{\"title\":\"<user-language text>\",\"gap\":14,\"items\":[...]}\n```\n\nAllowed `type` values; the `genui` skill, when available, carries the full content\u2192component mapping and per-component field details:\n\n- \u5E03\u5C40: text \u00B7 row \u00B7 col \u00B7 grid \u00B7 card \u00B7 divider \u00B7 spacer \u00B7 hero\uFF08\u5C01\u9762\u5757\uFF0C\u4E00\u6761\u56DE\u7B54\u6700\u591A\u4E00\u4E2A\uFF09\n- \u5C55\u793A: badge \u00B7 stat \u00B7 progress \u00B7 list \u00B7 table \u00B7 keyvalue \u00B7 timeline \u00B7 file-tree \u00B7 breadcrumb \u00B7 callout \u00B7 steps \u00B7 diff \u00B7 json \u00B7 code \u00B7 copy \u00B7 avatar \u00B7 audio \u00B7 video\n- \u56FE\u8868: chart {\"kind\":\"bars|line|donut\",\"data\":[{\"label\":\"...\",\"value\":n}],\"series\":[{\"label\":\"...\",\"data\":[...]}]?,\"horizontal\":true?,\"stacked\":true?}\uFF08series\uFF1Abars \u5206\u7EC4/\u5806\u53E0 / line \u591A\u5E8F\u5217\uFF1Bhorizontal \u6A2A\u5411\u67F1\uFF09 \u00B7 echart (preset \u540D\u6216 option \u76F4\u901A\uFF0C\u9884\u8BBE\u6E05\u5355\u89C1 skill) \u00B7 plot (\u51FD\u6570\u56FE)\n- \u4EA4\u4E92: button \u00B7 input \u00B7 textarea \u00B7 select \u00B7 checkbox \u00B7 switch \u00B7 slider \u00B7 radio \u00B7 submit \u00B7 quiz \u00B7 link \u00B7 tabs \u00B7 accordion\n- \u9AD8\u7EA7: mermaid (\u6D41\u7A0B\u56FE/\u65F6\u5E8F/\u7518\u7279/ER \u7B49\uFF0C\u5173\u952E\u5B57\u89C1 skill) \u00B7 diagram (\u67B6\u6784/\u6D41\u7A0B\u56FE\uFF0C27 \u79CD kind) \u00B7 scene3d (3D WebGL)\n\n**\u9ED8\u8BA4\u5C31\u8BE5\u51FA UI**\uFF1A\u51FA\u73B0\u4E0B\u5217\u60C5\u51B5\u81F3\u5C11\u51FA\u4E00\u4E2A\u56F4\u680F\uFF1A\n- \u22653 \u6761\u5E76\u5217\u8981\u70B9 \u2192 `list`\uFF1B\u6570\u5B57\u5BF9\u6BD4 \u2192 `table`\uFF1B\u6307\u6807/\u8FDB\u5EA6/\u72B6\u6001 \u2192 `stat`/`progress`/`badge`\n- \u6B65\u9AA4/\u65F6\u95F4\u7EBF \u2192 `steps`/`timeline`/`mermaid`\uFF1B\u67B6\u6784/\u6D41\u7A0B \u2192 `diagram` \u6216 `mermaid`\uFF1B\u98CE\u9669/\u7ED3\u8BBA \u2192 `callout`\uFF1B\u4EE3\u7801/\u6539\u52A8 \u2192 `code`/`diff`/`json`\n- \u884C\u5185\u5BCC\u6587\u672C\uFF1A\u652F\u6301\u516C\u5F0F\u3001`code`\u3001\u52A0\u7C97\u3001\u9AD8\u4EAE\u3001\u94FE\u63A5\uFF1B\u7981\u7528 Markdown table / fenced code\uFF0C\u6539\u7528 table / code / diff / json\u3002\n- \u9ED8\u8BA4\u65E0\u5361\uFF1A\u6309\u89E6\u53D1\u6761\u4EF6\u4F7F\u7528\u7EC4\u4EF6\uFF0C\u6BCF\u4E2A\u7EC4\u4EF6\u627F\u8F7D\u4E0D\u540C\u4FE1\u606F\uFF1B\u5361\u7247\u53EA\u7528\u4E8E\u5E76\u6392\u9879\u4E0E\u6570\u636E\u5BF9\u8C61\uFF0C\u5355\u6BB5\u6587\u5B57\u7528\u6807\u9898\u3001\u6B63\u6587\u4E0E\u95F4\u8DDD\u3002\n\n**\u53D1\u56DE\u7B54\u524D\u6700\u540E\u81EA\u68C0\u4E00\u6B21**\uFF1A\u8FD9\u6BB5\u5185\u5BB9\u91CC\u6709\u6CA1\u6709 \u22653 \u6761\u5E76\u5217\u8981\u70B9\u3001\u4EFB\u4F55\u5BF9\u6BD4\u3001\u4EFB\u4F55\u6570\u5B57/\u6307\u6807\u3001\u4EFB\u4F55\u6B65\u9AA4\u6216\u6D41\u7A0B\uFF1F\u6709\u5C31\u5148\u8F6C\u6210\u7EC4\u4EF6\u518D\u5F00\u53E3\u3002**\u72B6\u6001\u6C47\u62A5\u3001\u8FDB\u5EA6\u8BF4\u660E\u3001\u63D0\u4EA4\u4E0E\u6539\u52A8\u6E05\u5355\u540C\u6837\u7B97**\u3002\n- \u8D8B\u52BF/\u5360\u6BD4 \u2192 `chart`\uFF08\u22648 \u70B9\uFF09\u6216 `echart`\uFF08\u591A\u5E8F\u5217/\u8981\u4EA4\u4E92\u65F6\uFF09\uFF1B\u914D\u8272\u9ED8\u8BA4\u8DDF\u968F\u4E3B\u9898\uFF0C\u53EA\u6709\u8BED\u4E49\u9700\u8981\u65F6\u624D\u7528 `palette`/`card.accent`\uFF1Bgrid \u5B50\u8282\u70B9\u7528 `\"span\":2` \u8DE8\u5217\u505A\u5BBD\u7A84\u6DF7\u6392\uFF1B\u6570\u636E\u591A\u65F6\u7ED9 `table`/`chart`/`list` \u914D\u4E00\u4E2A `input`(id) + `filter` \u7ED1\u5B9A\uFF0C\u8BFB\u8005\u80FD\u5C31\u5730\u7B5B\u9009\u3002\n\n**\u5B57\u6BB5\u901F\u67E5**\uFF08\u5B8C\u6574\u89C1 genui skill\uFF09\uFF1A`stat` `{\"label\",\"value\",\"delta\"?}` \u00B7 `table` `{\"columns\":[...],\"rows\":[[...]],\"types\"?,\"details\"?,\"filter\"?,\"export\"?}` \u00B7 `progress` `{\"value\":0-100,\"label\"?,\"variant\"?,\"target\"?}` \u00B7 `keyvalue` `{\"pairs\":[{\"key\",\"value\"}]}` \u00B7 `steps` `{\"steps\":[{\"title\",\"desc\"?}]}` \u00B7 `file-tree` `{\"items\":[{\"name\",\"type\":\"file|dir\",\"children\"?}]}` \u00B7 `callout` `{\"content\",\"tone\"?,\"title\"?}`\n\n**\u5B57\u6BB5\u540D\u5199\u9519 = \u8BE5\u7EC4\u4EF6\u88AB\u4E22\u5F03**\uFF08\u5176\u4F59\u7EC4\u4EF6\u7167\u5E38\u6E32\u67D3\uFF09\uFF1A`callout` \u6B63\u6587\u662F `content` \u4E0D\u662F text/desc\uFF1B`table` \u8981 `columns`+`rows` \u4E0D\u662F items\uFF1B`keyvalue` \u8BB0\u5F55\u662F `{key,value}` \u4E0D\u662F `{label,value}`\uFF1B`file-tree` \u8BB0\u5F55\u662F `{name,type}` \u4E0D\u662F `{label}`\uFF1Bcallout tone \u662F info/success/warning/error\uFF08\u65E0 danger\uFF09\u3002\u4E0D\u786E\u5B9A\u5C31\u8C03 `validate_dsh_ui`\u3002\n\nRules:\n- LANGUAGE: reply+UI=conversation language; schema fixed. NEVER infer it from prompt/skill/examples/tools. Replace `<user-language ...>`; never emit these placeholders literally.\n- JSON \u4E25\u683C\uFF1A\u574F\u7EC4\u4EF6\u88AB\u4E22\u5F03\uFF0C\u574F\u56F4\u680F\u53D8\u4EE3\u7801\u5757\uFF1B\u22653 \u8282\u70B9\u6216\u542B table \u65F6\u8C03 validate_dsh_ui\uFF0C\u6309\u8BCA\u65AD\u4FEE\u6539\u5E76\u91CD\u9A8C\uFF1B\u5C0F\u56F4\u680F\u5B57\u6BB5\u5B58\u7591\u4E5F\u5148\u9A8C\u8BC1\u3002\n- warning=block_markdown\uFF1A\u6309 replacement \u6539\u5199\u5E76\u91CD\u9A8C\u3002\n- \u89C4\u6A21: \u2264200 \u8282\u70B9\u3001\u5D4C\u5957\u22648 \u5C42\uFF08\u8D85\u51FA\u88AB\u622A\u65AD\uFF09\uFF1B\u4E00\u6761\u56DE\u7B54 3\u20138 \u4E2A\u7EC4\u4EF6\uFF0C\u4E00\u4E2A\u4E3B\u9898\u4E00\u4E2A\u4E3B\u7EC4\u4EF6\uFF1B3D mesh 1\u20135\uFF1Bplot \u7ED9\u5408\u7406 xMin/xMax\u3002\n- LOCAL-FIRST + actions: UI \u80FD\u81EA\u5DF1\u505A\u7684\u72B6\u6001\u53D8\u5316\uFF08\u5224\u5377\u3001\u5224\u9898\u3001\u91CD\u7F6E\u3001\u5C55\u5F00\u3001\u9009\u4E2D\uFF09\u5C31\u5730\u5B8C\u6210\uFF0C\u96F6\u5F80\u8FD4\uFF1Baction \u53EA\u7528\u4E8E\u5FC5\u987B\u6A21\u578B\u53C2\u4E0E\u7684\u4E8B\u3002\u4EA4\u4E92\
|
|
16
|
+
export declare const GENUI_SECTION_TEXT = "You can render interactive UI components INSIDE your reply \u2014 between paragraphs \u2014 by emitting a fenced block with the language tag `dsh-ui` containing a JSON spec:\n\n```dsh-ui\n{\"title\":\"<user-language text>\",\"gap\":14,\"items\":[...]}\n```\n\nAllowed `type` values; the `genui` skill, when available, carries the full content\u2192component mapping and per-component field details:\n\n- \u5E03\u5C40: text \u00B7 row \u00B7 col \u00B7 grid \u00B7 card \u00B7 divider \u00B7 spacer \u00B7 hero\uFF08\u5C01\u9762\u5757\uFF0C\u4E00\u6761\u56DE\u7B54\u6700\u591A\u4E00\u4E2A\uFF09\n- \u5C55\u793A: badge \u00B7 stat \u00B7 progress \u00B7 list \u00B7 table \u00B7 keyvalue \u00B7 timeline \u00B7 file-tree \u00B7 breadcrumb \u00B7 callout \u00B7 steps \u00B7 diff \u00B7 json \u00B7 code \u00B7 copy \u00B7 avatar \u00B7 audio \u00B7 video\n- \u56FE\u8868: chart {\"kind\":\"bars|line|donut\",\"data\":[{\"label\":\"...\",\"value\":n}],\"series\":[{\"label\":\"...\",\"data\":[...]}]?,\"horizontal\":true?,\"stacked\":true?}\uFF08series\uFF1Abars \u5206\u7EC4/\u5806\u53E0 / line \u591A\u5E8F\u5217\uFF1Bhorizontal \u6A2A\u5411\u67F1\uFF09 \u00B7 echart (preset \u540D\u6216 option \u76F4\u901A\uFF0C\u9884\u8BBE\u6E05\u5355\u89C1 skill) \u00B7 plot (\u51FD\u6570\u56FE)\n- \u4EA4\u4E92: button \u00B7 input \u00B7 textarea \u00B7 select \u00B7 checkbox \u00B7 switch \u00B7 slider \u00B7 radio \u00B7 submit \u00B7 quiz \u00B7 link \u00B7 tabs \u00B7 accordion\n- \u9AD8\u7EA7: mermaid (\u6D41\u7A0B\u56FE/\u65F6\u5E8F/\u7518\u7279/ER \u7B49\uFF0C\u5173\u952E\u5B57\u89C1 skill) \u00B7 diagram (\u67B6\u6784/\u6D41\u7A0B\u56FE\uFF0C27 \u79CD kind) \u00B7 scene3d (3D WebGL)\n\n**\u9ED8\u8BA4\u5C31\u8BE5\u51FA UI**\uFF1A\u51FA\u73B0\u4E0B\u5217\u60C5\u51B5\u81F3\u5C11\u51FA\u4E00\u4E2A\u56F4\u680F\uFF1A\n- \u22653 \u6761\u5E76\u5217\u8981\u70B9 \u2192 `list`\uFF1B\u6570\u5B57\u5BF9\u6BD4 \u2192 `table`\uFF1B\u6307\u6807/\u8FDB\u5EA6/\u72B6\u6001 \u2192 `stat`/`progress`/`badge`\n- \u6B65\u9AA4/\u65F6\u95F4\u7EBF \u2192 `steps`/`timeline`/`mermaid`\uFF1B\u67B6\u6784/\u6D41\u7A0B \u2192 `diagram` \u6216 `mermaid`\uFF1B\u98CE\u9669/\u7ED3\u8BBA \u2192 `callout`\uFF1B\u4EE3\u7801/\u6539\u52A8 \u2192 `code`/`diff`/`json`\n- \u884C\u5185\u5BCC\u6587\u672C\uFF1A\u652F\u6301\u516C\u5F0F\u3001`code`\u3001\u52A0\u7C97\u3001\u9AD8\u4EAE\u3001\u94FE\u63A5\uFF1B\u7981\u7528 Markdown table / fenced code\uFF0C\u6539\u7528 table / code / diff / json\u3002\n- \u9ED8\u8BA4\u65E0\u5361\uFF1A\u6309\u89E6\u53D1\u6761\u4EF6\u4F7F\u7528\u7EC4\u4EF6\uFF0C\u6BCF\u4E2A\u7EC4\u4EF6\u627F\u8F7D\u4E0D\u540C\u4FE1\u606F\uFF1B\u5361\u7247\u53EA\u7528\u4E8E\u5E76\u6392\u9879\u4E0E\u6570\u636E\u5BF9\u8C61\uFF0C\u5355\u6BB5\u6587\u5B57\u7528\u6807\u9898\u3001\u6B63\u6587\u4E0E\u95F4\u8DDD\u3002\n\n**\u53D1\u56DE\u7B54\u524D\u6700\u540E\u81EA\u68C0\u4E00\u6B21**\uFF1A\u8FD9\u6BB5\u5185\u5BB9\u91CC\u6709\u6CA1\u6709 \u22653 \u6761\u5E76\u5217\u8981\u70B9\u3001\u4EFB\u4F55\u5BF9\u6BD4\u3001\u4EFB\u4F55\u6570\u5B57/\u6307\u6807\u3001\u4EFB\u4F55\u6B65\u9AA4\u6216\u6D41\u7A0B\uFF1F\u6709\u5C31\u5148\u8F6C\u6210\u7EC4\u4EF6\u518D\u5F00\u53E3\u3002**\u72B6\u6001\u6C47\u62A5\u3001\u8FDB\u5EA6\u8BF4\u660E\u3001\u63D0\u4EA4\u4E0E\u6539\u52A8\u6E05\u5355\u540C\u6837\u7B97**\u3002\n- \u8D8B\u52BF/\u5360\u6BD4 \u2192 `chart`\uFF08\u22648 \u70B9\uFF09\u6216 `echart`\uFF08\u591A\u5E8F\u5217/\u8981\u4EA4\u4E92\u65F6\uFF09\uFF1B\u914D\u8272\u9ED8\u8BA4\u8DDF\u968F\u4E3B\u9898\uFF0C\u53EA\u6709\u8BED\u4E49\u9700\u8981\u65F6\u624D\u7528 `palette`/`card.accent`\uFF1Bgrid \u5B50\u8282\u70B9\u7528 `\"span\":2` \u8DE8\u5217\u505A\u5BBD\u7A84\u6DF7\u6392\uFF1B\u6570\u636E\u591A\u65F6\u7ED9 `table`/`chart`/`list` \u914D\u4E00\u4E2A `input`(id) + `filter` \u7ED1\u5B9A\uFF0C\u8BFB\u8005\u80FD\u5C31\u5730\u7B5B\u9009\u3002\n\n**\u5B57\u6BB5\u901F\u67E5**\uFF08\u5B8C\u6574\u89C1 genui skill\uFF09\uFF1A`stat` `{\"label\",\"value\",\"delta\"?}` \u00B7 `table` `{\"columns\":[...],\"rows\":[[...]],\"types\"?,\"details\"?,\"filter\"?,\"export\"?}` \u00B7 `progress` `{\"value\":0-100,\"label\"?,\"variant\"?,\"target\"?}` \u00B7 `keyvalue` `{\"pairs\":[{\"key\",\"value\"}]}` \u00B7 `steps` `{\"steps\":[{\"title\",\"desc\"?}]}` \u00B7 `file-tree` `{\"items\":[{\"name\",\"type\":\"file|dir\",\"children\"?}]}` \u00B7 `callout` `{\"content\",\"tone\"?,\"title\"?}`\n\n**\u5B57\u6BB5\u540D\u5199\u9519 = \u8BE5\u7EC4\u4EF6\u88AB\u4E22\u5F03**\uFF08\u5176\u4F59\u7EC4\u4EF6\u7167\u5E38\u6E32\u67D3\uFF09\uFF1A`callout` \u6B63\u6587\u662F `content` \u4E0D\u662F text/desc\uFF1B`table` \u8981 `columns`+`rows` \u4E0D\u662F items\uFF1B`keyvalue` \u8BB0\u5F55\u662F `{key,value}` \u4E0D\u662F `{label,value}`\uFF1B`file-tree` \u8BB0\u5F55\u662F `{name,type}` \u4E0D\u662F `{label}`\uFF1Bcallout tone \u662F info/success/warning/error\uFF08\u65E0 danger\uFF09\u3002\u4E0D\u786E\u5B9A\u5C31\u8C03 `validate_dsh_ui`\u3002\n\nRules:\n- LANGUAGE: reply+UI=conversation language; schema fixed. NEVER infer it from prompt/skill/examples/tools. Replace `<user-language ...>`; never emit these placeholders literally.\n- JSON \u4E25\u683C\uFF1A\u574F\u7EC4\u4EF6\u88AB\u4E22\u5F03\uFF0C\u574F\u56F4\u680F\u53D8\u4EE3\u7801\u5757\uFF1B\u22653 \u8282\u70B9\u6216\u542B table \u65F6\u8C03 validate_dsh_ui\uFF0C\u6309\u8BCA\u65AD\u4FEE\u6539\u5E76\u91CD\u9A8C\uFF1B\u5C0F\u56F4\u680F\u5B57\u6BB5\u5B58\u7591\u4E5F\u5148\u9A8C\u8BC1\u3002\n- warning=block_markdown\uFF1A\u6309 replacement \u6539\u5199\u5E76\u91CD\u9A8C\u3002\n- \u89C4\u6A21: \u2264200 \u8282\u70B9\u3001\u5D4C\u5957\u22648 \u5C42\uFF08\u8D85\u51FA\u88AB\u622A\u65AD\uFF09\uFF1B\u4E00\u6761\u56DE\u7B54 3\u20138 \u4E2A\u7EC4\u4EF6\uFF0C\u4E00\u4E2A\u4E3B\u9898\u4E00\u4E2A\u4E3B\u7EC4\u4EF6\uFF1B3D mesh 1\u20135\uFF1Bplot \u7ED9\u5408\u7406 xMin/xMax\u3002\n- LOCAL-FIRST + actions: UI \u80FD\u81EA\u5DF1\u505A\u7684\u72B6\u6001\u53D8\u5316\uFF08\u5224\u5377\u3001\u5224\u9898\u3001\u91CD\u7F6E\u3001\u5C55\u5F00\u3001\u9009\u4E2D\uFF09\u5C31\u5730\u5B8C\u6210\uFF0C\u96F6\u5F80\u8FD4\uFF1Baction \u53EA\u7528\u4E8E\u5FC5\u987B\u6A21\u578B\u53C2\u4E0E\u7684\u4E8B\u3002\u4EA4\u4E92\u4EE5 [genui-action] name + \u7EC4\u4EF6\u6570\u636E\u56DE\u4F20\uFF0C\u5C4A\u65F6\u91CD\u6E32\u67D3\u66F4\u65B0 UI\uFF1B\u65E0 action \u7684\u6309\u94AE\u7981\u7528\u3002\n- Durable state: \u4EA4\u4E92\u72B6\u6001\u6309\u300C\u4F1A\u8BDD+\u5185\u5BB9\u6307\u7EB9\u300D\u6301\u4E45\u5316\u2014\u2014\u5237\u65B0/\u91CD\u653E\u6062\u590D\uFF1B\u76F8\u540C\u5185\u5BB9\u4FDD\u7559\uFF0C\u65B0\u5185\u5BB9\u91CD\u7F6E\u3002\n- \u5377\u5B50\u6A21\u5F0F: \u6BCF\u9898\u4E00\u4E2A radio\uFF08group+answer+explanation\uFF09+ \u4E00\u4E2A submit\uFF08groups \u5168\u5217\uFF09\uFF0C\u672C\u5730\u5224\u5206\u3002\n- Secrets ban: \u4E0D\u7D22\u53D6\u5BC6\u7801\u3001API Key\u3001Token\u3001\u6062\u590D\u7801\uFF1B\u9700\u8981\u65F6\u62D2\u7EDD\u5E76\u89E3\u91CA\u3002\n- Tool channel: render_ui \u5DE5\u5177\u628A\u540C\u4E00 spec \u6E32\u67D3\u4E3A\u5DE5\u5177\u884C\u5361\u7247\uFF1B\u56F4\u680F\u7528\u4E8E\u56DE\u7B54\u5185\u8054 UI\u3002\n- \u56F4\u680F\u4F4D\u7F6E\uFF1A`dsh-ui` \u53EA\u5199\u5728**\u56DE\u7B54\u6B63\u6587**\uFF1B\u5199\u5728 reasoning/\u601D\u8003\u5757\u91CC\u4E0D\u6E32\u67D3\u3001\u7528\u6237\u770B\u4E0D\u5230\u2014\u2014\u601D\u8003\u91CC\u9A8C\u8BC1\u597D spec\uFF0C\u6B63\u6587\u518D\u8F93\u51FA\u540C\u4E00\u4EFD\u3002\n- Panel: \"panel\":true \u53EA\u6E32\u67D3\u8FDB\u4F1A\u8BDD\u9762\u677F dock \u5E76\u539F\u5730\u66F4\u65B0\uFF1B\"append\":true \u8FFD\u52A0\u5408\u5E76\uFF1B\u4E0A\u9650 200 \u8282\u70B9/200 \u6B21\u8FFD\u52A0\uFF0C\u6EE1\u4E86\u53D1 replace \u91CD\u5EFA\u3002\u9762\u677F\u6765\u7684 [genui-action] \u53EA\u56DE\u4E00\u4E2A panel:true \u56F4\u680F + \u81F3\u591A\u4E00\u884C 10 \u5B57\u5185\u786E\u8BA4\u3002";
|
|
17
17
|
/**
|
|
18
18
|
* Register the GenUI output-language section and the render_ui tool.
|
|
19
19
|
* @param ctx - cordis context.
|
|
@@ -27,9 +27,13 @@ export declare const inject: string[];
|
|
|
27
27
|
export interface GenuiPluginConfig {
|
|
28
28
|
/**
|
|
29
29
|
* 在最终 dsh-ui 围栏无法渲染的回合中请求模型发送一次修正版(issue #160)。
|
|
30
|
-
* 默认开启,设置为 false
|
|
31
|
-
*
|
|
30
|
+
* 默认开启,设置为 false 可以关闭同回合围栏修正。GenUI 回合的 reasoning-only 响应仍会交给宿主重试策略处理。
|
|
31
|
+
* 每回合和每个围栏正文最多请求一次,子代理不触发,每次修正请求会消耗模型步数。
|
|
32
32
|
*/
|
|
33
33
|
fenceFeedback?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* 是否注册 render_ui 模型工具。默认开启,设置为 false 时关闭;不影响 validate_dsh_ui 和 dsh-ui 围栏渲染。
|
|
36
|
+
*/
|
|
37
|
+
renderUiTool?: boolean;
|
|
34
38
|
}
|
|
35
39
|
export declare function apply(ctx: Context, config?: GenuiPluginConfig): void;
|
|
@@ -31,12 +31,12 @@ export declare function describeJsonFailure(raw: string): string | null;
|
|
|
31
31
|
* with ASCII quotes (e.g. `对"别名路径"判定失败`), which makes JSON.parse
|
|
32
32
|
* fail near that quote with "Expected ',' or ']'...".
|
|
33
33
|
* 2. Trailing commas before `}` / `]` or at end of input.
|
|
34
|
+
* 3. Valueless object keys immediately before a complete key-and-colon pair.
|
|
34
35
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* comma → drop it.
|
|
36
|
+
* A shared grammar-aware scan distinguishes object keys from values and
|
|
37
|
+
* checks the continuation after a potential string terminator. Ambiguous
|
|
38
|
+
* value quotes use bounded backtracking; trailing commas are dropped only
|
|
39
|
+
* outside strings.
|
|
40
40
|
*
|
|
41
41
|
* Returns `{ text, repairs }` on success, or null when nothing needed fixing
|
|
42
42
|
* or the body still does not parse (callers fall through to tier-2 / banner).
|
|
@@ -45,6 +45,18 @@ export declare function repairFenceJson(raw: string): {
|
|
|
45
45
|
text: string;
|
|
46
46
|
repairs: number;
|
|
47
47
|
} | null;
|
|
48
|
+
/**
|
|
49
|
+
* 取**第一个平衡根值**的文本(丢弃其后的杂字符);没有平衡根时返回 null。
|
|
50
|
+
*
|
|
51
|
+
* 与 {@link completeFenceJson} 里的前缀回退同源,但**只做裁剪、不做结构补全**:
|
|
52
|
+
* 内容识别用它来容忍「合法 JSON + 尾部泄漏文本」(真实样本:模型把自己的工具调用
|
|
53
|
+
* 模板泄漏在 JSON 之后,且围栏没闭合)。根值正好结束在末尾时返回 null —— 那种情况
|
|
54
|
+
* `JSON.parse` 本来就会成功。
|
|
55
|
+
*
|
|
56
|
+
* @param text - 候选正文。
|
|
57
|
+
* @returns 平衡根前缀;无可裁剪内容时 null。
|
|
58
|
+
*/
|
|
59
|
+
export declare function trimToBalancedRoot(text: string): string | null;
|
|
48
60
|
/**
|
|
49
61
|
* Tier-2 repair — SETTLED MESSAGES ONLY (never while streaming): heals
|
|
50
62
|
* structural incompleteness — missing closing quotes/brackets — by appending
|
|
@@ -54,8 +66,8 @@ export declare function repairFenceJson(raw: string): {
|
|
|
54
66
|
* client uses the host-provided fence source; the validate tool is by
|
|
55
67
|
* definition pre-emission), so a streaming half can never flash premature UI.
|
|
56
68
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
69
|
+
* One shared scan implementation folds the tier-1 fixes (quote escaping +
|
|
70
|
+
* trailing-comma drops) into structural completion, so bodies with BOTH defects
|
|
59
71
|
* (a trailing comma AND a missing closer) heal in one shot — the old
|
|
60
72
|
* two-phase chain lost tier-1's partial work when its whole-body parse
|
|
61
73
|
* failed, and re-scanning the raw text could not compose the repairs.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@changfenhuang/dsh-genui",
|
|
3
3
|
"description": "GenUI for DeepSeek Harness: interactive UI components rendered inline in assistant replies via the ```dsh-ui fence — layout, charts, plots, forms, quizzes, mermaid, 3D scenes, and an action event loop back to the model. Ships the fence-teaching host plugin, the browser renderer (client half), and the genui skill.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/plugin/index.d.ts",
|
|
@@ -10,10 +10,6 @@
|
|
|
10
10
|
"types": "./lib/types/plugin/index.d.ts",
|
|
11
11
|
"default": "./lib/index.js"
|
|
12
12
|
},
|
|
13
|
-
"./invariant": {
|
|
14
|
-
"types": "./lib/types/plugin/invariant.d.ts",
|
|
15
|
-
"default": "./lib/invariant.js"
|
|
16
|
-
},
|
|
17
13
|
"./client": {
|
|
18
14
|
"types": "./lib/types/client/index.d.ts",
|
|
19
15
|
"default": "./lib/client.js"
|
|
@@ -29,12 +25,10 @@
|
|
|
29
25
|
},
|
|
30
26
|
"files": [
|
|
31
27
|
"lib/index.js",
|
|
32
|
-
"lib/invariant.js",
|
|
33
28
|
"lib/client.js",
|
|
34
29
|
"lib/assets/mermaid.js",
|
|
35
30
|
"lib/assets/three.js",
|
|
36
31
|
"lib/types/plugin/index.d.ts",
|
|
37
|
-
"lib/types/plugin/invariant.d.ts",
|
|
38
32
|
"lib/types/client/index.d.ts",
|
|
39
33
|
"SKILL.md",
|
|
40
34
|
"README.md",
|
|
@@ -86,7 +80,6 @@
|
|
|
86
80
|
"@deepseek-ai/dsh-client-ui-session": "^0.1.2-rc.1 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || >=0.2.0-rc.1 <0.3.0-0",
|
|
87
81
|
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-rc.1 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || >=0.2.0-rc.1 <0.3.0-0",
|
|
88
82
|
"@deepseek-ai/dsh-client-ui-tool": "^0.1.2-rc.1 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || >=0.2.0-rc.1 <0.3.0-0",
|
|
89
|
-
"@deepseek-ai/dsh-invariants": "^0.1.2-rc.1 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || >=0.2.0-rc.1 <0.3.0-0",
|
|
90
83
|
"@deepseek-ai/dsh-llm": "^0.1.2-rc.1 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || >=0.2.0-rc.1 <0.3.0-0",
|
|
91
84
|
"@deepseek-ai/dsh-session": "^0.1.2-rc.1 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || >=0.2.0-rc.1 <0.3.0-0",
|
|
92
85
|
"@deepseek-ai/dsh-skill": "^0.1.2-rc.1 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || >=0.2.0-rc.1 <0.3.0-0",
|
|
@@ -122,7 +115,6 @@
|
|
|
122
115
|
"@deepseek-ai/dsh-deque": "0.1.2-rc.1",
|
|
123
116
|
"@deepseek-ai/dsh-file-reference": "0.1.2-rc.1",
|
|
124
117
|
"@deepseek-ai/dsh-home-paths": "0.1.2-rc.1",
|
|
125
|
-
"@deepseek-ai/dsh-invariants": "0.1.2-rc.1",
|
|
126
118
|
"@deepseek-ai/dsh-jobs": "0.1.2-rc.1",
|
|
127
119
|
"@deepseek-ai/dsh-llm": "0.1.2-rc.1",
|
|
128
120
|
"@deepseek-ai/dsh-native-command": "0.1.2-rc.1",
|
|
@@ -226,9 +218,6 @@
|
|
|
226
218
|
"@deepseek-ai/dsh-client-ui-tool": {
|
|
227
219
|
"optional": true
|
|
228
220
|
},
|
|
229
|
-
"@deepseek-ai/dsh-invariants": {
|
|
230
|
-
"optional": true
|
|
231
|
-
},
|
|
232
221
|
"@deepseek-ai/dsh-llm": {
|
|
233
222
|
"optional": true
|
|
234
223
|
},
|