@xyz-agent/extension-protocol 0.3.1 → 0.5.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/dist/index.d.mts CHANGED
@@ -49,6 +49,14 @@ interface GuiComponentProps {
49
49
  /** 列表树——替代 TUI 的 ⎿ ├─ └─ 缩进 */
50
50
  'list-tree': {
51
51
  items: TreeItem[];
52
+ /** 行首显示弱化序号(1/2/3…,mono tabular-nums)。扁平有序清单用(todo);
53
+ * 自带编号的文本(goal criteria "1. xxx")不要开,避免双重编号 */
54
+ numbered?: boolean;
55
+ };
56
+ /** 垂直组合容器——无视觉样式的透明分组。宿主壳层(WidgetArea)承担卡壳/head/折叠
57
+ * 后,widget 内容需要多组件组合时的组合根(替代「无头 card」的语义滥用) */
58
+ 'group': {
59
+ children: GuiComponent[];
52
60
  };
53
61
  /** 双列网格——替代 TUI 的 │ 列分隔 */
54
62
  'columns': {
@@ -73,6 +81,31 @@ interface GuiRenderResult {
73
81
  /** 版本协商,前端检测,不认识降级 ansi-text */
74
82
  v: typeof PROTOCOL_VERSION;
75
83
  component: GuiComponent;
84
+ /**
85
+ * widget 宿主元数据(M17 对话流 widget 面板消费):标题/状态点/进度计数由
86
+ * 宿主壳层统一渲染成单一 head(含折叠交互),extension 不再用 card 原语
87
+ * 的 header 表达这些(壳层 head 与 payload card header 双头重复的根因修复)。
88
+ * 可选:不发时宿主 fallback 到 viewId 标题、无状态点/进度。
89
+ */
90
+ meta?: WidgetMeta;
91
+ }
92
+ /** widget 宿主元数据——head 渲染契约(title + 状态点 + 进度 + 折叠 chevron)。 */
93
+ interface WidgetMeta {
94
+ /** head 标题(todo → "Todo";goal → slug) */
95
+ title: string;
96
+ /** head 状态点语义:running=accent / done=success / failed=danger / idle=neutral 弱点 */
97
+ status?: 'running' | 'done' | 'failed' | 'idle';
98
+ /** head 进度(mini bar + 计数文本);progress-bar 原语从 body 移入 head 的承载 */
99
+ progress?: {
100
+ /** fill 比例 = current/total */
101
+ current: number;
102
+ total: number;
103
+ /** 计数显示文本(head 空间有限,extension 全权格式化:todo "2/5"、goal "42%")。
104
+ * 缺省 `${current}/${total}` */
105
+ label?: string;
106
+ /** fill 语义色(预算阈值映射);缺省按 meta.status(done→success,否则 accent) */
107
+ severity?: 'ok' | 'warn' | 'danger';
108
+ };
76
109
  }
77
110
  interface StatItem {
78
111
  label?: string;
@@ -135,23 +168,26 @@ interface GuiContext {
135
168
  */
136
169
  declare function isGuiCapable(ctx: GuiContext): boolean;
137
170
  /**
138
- * 构造 GuiRenderResult,放进 details.__gui__。
171
+ * 构造 GuiRenderResult。当前主用途是 guiSetWidget 的载荷(M17 对话流 widget 面板);
172
+ * details.__gui__(tool result 通道)为遗留兼容路径,todo/goal 已不再使用。
139
173
  * stripUndefined 确保序列化不含 undefined(JSON.stringify 会丢弃 undefined 字段)。
140
174
  */
141
- declare function guiResult(component: GuiComponent): GuiRenderResult;
175
+ declare function guiResult(component: GuiComponent, meta?: WidgetMeta): GuiRenderResult;
142
176
  /**
143
177
  * 构造 GuiComponent,带类型推断。
144
178
  * 类型参数 T 约束 props 到对应类型的 props 形状。
145
179
  */
146
180
  declare function guiComponent<T extends GuiComponentType>(type: T, props: GuiComponentProps[T]): GuiComponent<T>;
147
181
  /**
148
- * 设置 GUI widget。RPC 模式下用 marker 编码 GuiComponent JSON 进 string[],
149
- * runtime event-adapter 检测 marker 解码为结构化 WS 帧。
150
- * TUI 模式下此函数无操作(extension 应在 TUI 分支调原生 ctx.ui.setWidget 传 Component factory)。
182
+ * 设置 GUI widget。RPC 模式下用 marker 编码 GuiRenderResult JSON 进 string[],
183
+ * runtime event-adapter 检测 marker 解码为结构化 WS 帧(component + meta)。
184
+ * 本函数无 isGui 守卫(仅查 ctx.ui?.setWidget 存在性),调用方需先判定
185
+ * isGuiCapable(ctx)——TUI 模式误调会把 marker 编码行推给原生 widget 造成乱码。
151
186
  *
152
- * undefined 清除 widget。
187
+ * meta(标题/状态/进度)由宿主壳层渲染成统一 head;extension 不再用 card 原语
188
+ * header 表达这些。传 undefined 清除 widget。
153
189
  */
154
- declare function guiSetWidget(ctx: GuiContext, key: string, component: GuiComponent | undefined): void;
190
+ declare function guiSetWidget(ctx: GuiContext, key: string, result: GuiRenderResult | undefined): void;
155
191
  /**
156
192
  * 从 details 中提取 GuiRenderResult。前端统一用此函数读取 __gui__,
157
193
  * 集中校验版本号,避免散落的 as 断言。
@@ -163,6 +199,13 @@ declare function extractGui(details: Record<string, unknown> | undefined): GuiRe
163
199
  * 不校验 type 是否为已知值(GuiComponentRenderer 对未知 type 有 AnsiText 降级)。
164
200
  */
165
201
  declare function isGuiComponent(value: unknown): value is GuiComponent;
202
+ /**
203
+ * v1.1 wire 格式校验:判断 unknown 值是否为合法 GuiRenderResult 信封
204
+ * (v === PROTOCOL_VERSION + component 是合法 GuiComponent)。meta 可选不校验深度。
205
+ * event-adapter 据此区分 v1.1 信封(解包 component + meta)与 v1 裸 component
206
+ * (旧版 extension 发出的 wire 格式,兼容窗口内两种并存)。
207
+ */
208
+ declare function isGuiRenderResult(value: unknown): value is GuiRenderResult;
166
209
 
167
210
  /**
168
211
  * ask-user extension 的富交互类型定义。
@@ -195,14 +238,10 @@ interface AskUserQuestion {
195
238
  * - 有 options 时:默认 true,前端在选项末尾追加 Other 输入框;设 false 则不追加
196
239
  * - 无 options 时:整个问题就是自由输入,此字段被忽略 */
197
240
  allowOther?: boolean;
198
- /** 是否允许附加评论。选中后可追加短文本(4.0.1 restore:0.3.0 误删导致 pi-ask-user@4.0.0 ESM import 崩溃,见 .changeset/restore-ask-user-comment.md) */
199
- allowComment?: boolean;
200
241
  }
201
242
  interface AskUserOption {
202
- /** 显示标签 */
243
+ /** 显示标签,回传时作为选中值(D1:proto 无独立 value,选中值统一用 label) */
203
244
  label: string;
204
- /** 回传值。未提供时用 label */
205
- value?: string;
206
245
  /** 描述(可选)。显示在 label 下方,解释 tradeoff */
207
246
  description?: string;
208
247
  }
@@ -210,16 +249,14 @@ interface AskUserOption {
210
249
  * ask-user 富交互回传结果。key = question.header(header 缺失时用 question 文本)。
211
250
  *
212
251
  * 答案编码规则(避免逗号歧义):
213
- * - 单选:value = 选中项的 value string(或 label
214
- * - 多选:value = JSON.stringify(选中项 value 数组),如 '["pg","mysql"]'
215
- * (不用逗号 join——option value 可能含逗号导致 split 歧义)
252
+ * - 单选:value = 选中项的 label
253
+ * - 多选:value = JSON.stringify(选中项 label 数组),如 '["pg","mysql"]'
254
+ * (不用逗号 join——option label 可能含逗号导致 split 歧义)
216
255
  * - Other 文本:单独 key `${header}__other`,value = 自由文本(不混进选中项数组)
217
- * - comment:单独 key `${header}__comment`,value = 评论文本
218
256
  *
219
257
  * extension 解析示例:
220
258
  * const selected = JSON.parse(answers[header]) // 多选 → string[]
221
259
  * const other = answers[`${header}__other`] // Other 自由文本
222
- * const comment = answers[`${header}__comment`] // 评论
223
260
  */
224
261
  type AskUserAnswers = Record<string, string>;
225
262
 
@@ -266,12 +303,10 @@ declare function askUserInteract(ctx: GuiContext, questions: AskUserQuestion[],
266
303
  declare function getAskUserAnswer(answers: AskUserAnswers, question: AskUserQuestion): string | string[] | undefined;
267
304
  /** 从 answers 中提取 Other 自由文本 */
268
305
  declare function getAskUserOther(answers: AskUserAnswers, question: AskUserQuestion): string | undefined;
269
- /** 从 answers 中提取评论(4.0.1 restore:ask-user TUI 评论模式与 renderer AskUserOverlay 仍消费此 helper) */
270
- declare function getAskUserComment(answers: AskUserAnswers, question: AskUserQuestion): string | undefined;
271
306
  /**
272
307
  * 类型守卫:验证 unknown 是否为合法的 AskUserQuestion。
273
308
  * 用于前端从 runtime 透传的 askUserQuestions(unknown[])中安全收窄。
274
309
  */
275
310
  declare function isAskUserQuestion(value: unknown): value is AskUserQuestion;
276
311
 
277
- export { ASK_USER_MARKER, type AskUserAnswers, type AskUserOption, type AskUserQuestion, GUI_WIDGET_MARKER, type GuiComponent, type GuiComponentProps, type GuiComponentType, type GuiContext, type GuiRenderResult, PROTOCOL_VERSION, type StatItem, type TreeItem, type TreeItemIcon, askUserInteract, extractGui, getAskUserAnswer, getAskUserComment, getAskUserOther, guiComponent, guiResult, guiSetWidget, isAskUserQuestion, isGuiCapable, isGuiComponent };
312
+ export { ASK_USER_MARKER, type AskUserAnswers, type AskUserOption, type AskUserQuestion, GUI_WIDGET_MARKER, type GuiComponent, type GuiComponentProps, type GuiComponentType, type GuiContext, type GuiRenderResult, PROTOCOL_VERSION, type StatItem, type TreeItem, type TreeItemIcon, type WidgetMeta, askUserInteract, extractGui, getAskUserAnswer, getAskUserOther, guiComponent, guiResult, guiSetWidget, isAskUserQuestion, isGuiCapable, isGuiComponent, isGuiRenderResult };
package/dist/index.mjs CHANGED
@@ -8,19 +8,21 @@ var GUI_WIDGET_MARKER = "\0XYZ_GUI_WIDGET:";
8
8
  function isGuiCapable(ctx) {
9
9
  return ctx.mode === "rpc";
10
10
  }
11
- function guiResult(component) {
12
- return {
11
+ function guiResult(component, meta) {
12
+ const result = {
13
13
  v: PROTOCOL_VERSION,
14
14
  component: stripUndefined(component)
15
15
  };
16
+ if (meta !== void 0) result.meta = stripUndefined(meta);
17
+ return result;
16
18
  }
17
19
  function guiComponent(type, props) {
18
20
  return { type, props };
19
21
  }
20
- function guiSetWidget(ctx, key, component) {
22
+ function guiSetWidget(ctx, key, result) {
21
23
  if (!ctx.ui?.setWidget) return;
22
- if (component) {
23
- const encoded = [GUI_WIDGET_MARKER + JSON.stringify(stripUndefined(component))];
24
+ if (result) {
25
+ const encoded = [GUI_WIDGET_MARKER + JSON.stringify(stripUndefined(result))];
24
26
  ctx.ui.setWidget(key, encoded);
25
27
  } else {
26
28
  ctx.ui.setWidget(key, void 0);
@@ -38,6 +40,11 @@ function isGuiComponent(value) {
38
40
  const obj = value;
39
41
  return typeof obj.type === "string" && obj.props !== null && obj.props !== void 0 && typeof obj.props === "object";
40
42
  }
43
+ function isGuiRenderResult(value) {
44
+ if (value === null || typeof value !== "object") return false;
45
+ const obj = value;
46
+ return obj.v === PROTOCOL_VERSION && isGuiComponent(obj.component);
47
+ }
41
48
  function stripUndefined(obj) {
42
49
  if (obj === null || obj === void 0) return obj;
43
50
  if (typeof obj !== "object") return obj;
@@ -100,13 +107,10 @@ function getAskUserAnswer(answers, question) {
100
107
  function getAskUserOther(answers, question) {
101
108
  return answers[`${askUserKey(question)}__other`];
102
109
  }
103
- function getAskUserComment(answers, question) {
104
- return answers[`${askUserKey(question)}__comment`];
105
- }
106
110
  function isAskUserQuestion(value) {
107
111
  if (typeof value !== "object" || value === null) return false;
108
112
  const q = value;
109
- return typeof q.question === "string" && (q.header === void 0 || typeof q.header === "string") && (q.options === void 0 || Array.isArray(q.options)) && (q.multiSelect === void 0 || typeof q.multiSelect === "boolean") && (q.allowOther === void 0 || typeof q.allowOther === "boolean") && (q.allowComment === void 0 || typeof q.allowComment === "boolean");
113
+ return typeof q.question === "string" && (q.header === void 0 || typeof q.header === "string") && (q.options === void 0 || Array.isArray(q.options)) && (q.multiSelect === void 0 || typeof q.multiSelect === "boolean") && (q.allowOther === void 0 || typeof q.allowOther === "boolean");
110
114
  }
111
115
  export {
112
116
  ASK_USER_MARKER,
@@ -115,12 +119,12 @@ export {
115
119
  askUserInteract,
116
120
  extractGui,
117
121
  getAskUserAnswer,
118
- getAskUserComment,
119
122
  getAskUserOther,
120
123
  guiComponent,
121
124
  guiResult,
122
125
  guiSetWidget,
123
126
  isAskUserQuestion,
124
127
  isGuiCapable,
125
- isGuiComponent
128
+ isGuiComponent,
129
+ isGuiRenderResult
126
130
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xyz-agent/extension-protocol",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "Extension GUI rendering protocol: types and helpers for pi extension dual-mode (TUI/GUI) rendering",
5
5
  "main": "dist/index.mjs",
6
6
  "module": "dist/index.mjs",