@zhushanwen/pi-ask-user 7.3.2 → 7.3.4
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/package.json +2 -2
- package/src/__tests__/channel-handler.test.ts +62 -0
- package/src/__tests__/form-adapter.test.ts +0 -44
- package/src/__tests__/index.test.ts +82 -1
- package/src/channel-handler.ts +12 -15
- package/src/channel-registry-register.ts +1 -1
- package/src/form-adapter.ts +0 -24
- package/src/index.ts +6 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-ask-user",
|
|
3
|
-
"version": "7.3.
|
|
3
|
+
"version": "7.3.4",
|
|
4
4
|
"description": "Inline adaptive ask_user tool for Pi — single/multi-question structured input with split-pane preview and an inline free-text editor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"ARCHITECTURE.md"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@zhushanwen/extension-protocol": "0.
|
|
32
|
+
"@zhushanwen/extension-protocol": "0.17.0",
|
|
33
33
|
"@zhushanwen/pi-ext-guards": "0.4.2",
|
|
34
34
|
"@zhushanwen/pi-extension-logger": "0.6.1"
|
|
35
35
|
},
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import type { AskUserQuestion, FormQuestion } from "@zhushanwen/extension-protocol";
|
|
19
19
|
import { describe, expect, it } from "vitest";
|
|
20
20
|
|
|
21
|
+
import { encodeAnswer } from "../answer-codec";
|
|
21
22
|
import { createAskUserChannelHandler } from "../channel-handler";
|
|
22
23
|
import type { Result } from "../types";
|
|
23
24
|
|
|
@@ -332,3 +333,64 @@ describe("createAskUserChannelHandler — input 校验", () => {
|
|
|
332
333
|
expect(resp).toEqual({ cancelled: true });
|
|
333
334
|
});
|
|
334
335
|
});
|
|
336
|
+
|
|
337
|
+
// ── 输出等价锚(plan-mode-audit-remediation 批次 3 活路径去重)──
|
|
338
|
+
// TUI 三格式往返收敛为单次转换:handler 编码直接迭代 internal Question[](formToInternalQuestions
|
|
339
|
+
// 单次转换产物),不再构造 legacy AskUserQuestion[] 中转视图(formToAskUserQuestions 已删)。
|
|
340
|
+
// 本组断言钉死:新路径产出 ≡ 旧「proto 中转视图 + encodeAnswer」链路的产出——proto 视图以
|
|
341
|
+
// 字面量充当被删转换的参考实现(key = header ?? question、multiSelect === true 语义不变)。
|
|
342
|
+
describe("TUI encode 输出等价锚(internal 单次转换 ≡ 旧 proto 中转视图链路)", () => {
|
|
343
|
+
it("handler 编码结果 ≡ 旧链路参考实现(single/multi/Other 三形态对拍)", async () => {
|
|
344
|
+
const formQuestions: FormQuestion[] = [
|
|
345
|
+
{ type: "choice", question: "Which DB?", options: [{ label: "Postgres" }, { label: "SQLite" }] },
|
|
346
|
+
{
|
|
347
|
+
type: "choice",
|
|
348
|
+
question: "Which tools?",
|
|
349
|
+
header: "Tools",
|
|
350
|
+
multi: true,
|
|
351
|
+
options: [{ label: "A" }, { label: "B" }],
|
|
352
|
+
},
|
|
353
|
+
];
|
|
354
|
+
// 被删 formToAskUserQuestions 的逐字产出形态(参考实现字面量)
|
|
355
|
+
const deletedProtoView: AskUserQuestion[] = [
|
|
356
|
+
{
|
|
357
|
+
question: "Which DB?",
|
|
358
|
+
options: [{ label: "Postgres" }, { label: "SQLite" }],
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
question: "Which tools?",
|
|
362
|
+
header: "Tools",
|
|
363
|
+
multiSelect: true,
|
|
364
|
+
options: [{ label: "A" }, { label: "B" }],
|
|
365
|
+
},
|
|
366
|
+
];
|
|
367
|
+
const internalResult: Result = {
|
|
368
|
+
questions: [],
|
|
369
|
+
answers: {
|
|
370
|
+
"Which DB?": { selected: ["Postgres"], other: "Custom DB" },
|
|
371
|
+
"Which tools?": { selected: ["B", "A"], other: null },
|
|
372
|
+
},
|
|
373
|
+
cancelled: false,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const handler = createAskUserChannelHandler(
|
|
377
|
+
makeCtx({ mode: "tui", customResult: internalResult }) as never,
|
|
378
|
+
);
|
|
379
|
+
const resp = await handler({ channelPayload: { formQuestions } });
|
|
380
|
+
|
|
381
|
+
// 旧链路参考实现:proto 中转视图 + encodeAnswer(encode SSOT 未删未改,唯一变量是视图)
|
|
382
|
+
const expected: Record<string, string> = {};
|
|
383
|
+
for (const pq of deletedProtoView) {
|
|
384
|
+
const av = internalResult.answers[pq.question];
|
|
385
|
+
if (av === undefined) continue;
|
|
386
|
+
Object.assign(expected, encodeAnswer(av, { key: pq.header ?? pq.question, multiSelect: pq.multiSelect === true }));
|
|
387
|
+
}
|
|
388
|
+
expect(resp).toEqual({ value: JSON.stringify(expected) });
|
|
389
|
+
// 主 key/Other 键逐字形态(防 expected 构造自身退化成恒真对拍)
|
|
390
|
+
expect(JSON.parse((resp as { value: string }).value)).toEqual({
|
|
391
|
+
"Which DB?": "Postgres",
|
|
392
|
+
"Which DB?__other": "Custom DB",
|
|
393
|
+
"Tools": JSON.stringify(["B", "A"]),
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
});
|
|
@@ -8,7 +8,6 @@ import { describe, expect, it } from "vitest";
|
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
askUserToFormQuestions,
|
|
11
|
-
formToAskUserQuestions,
|
|
12
11
|
formToInternalQuestions,
|
|
13
12
|
internalToFormQuestions,
|
|
14
13
|
} from "../form-adapter";
|
|
@@ -79,49 +78,6 @@ describe("askUserToFormQuestions(D9 旧 payload 归一)", () => {
|
|
|
79
78
|
});
|
|
80
79
|
});
|
|
81
80
|
|
|
82
|
-
describe("formToAskUserQuestions(解码视图)", () => {
|
|
83
|
-
it("choice:multi 还原为 multiSelect,保持解码 helper 契约", () => {
|
|
84
|
-
const [q] = formToAskUserQuestions([
|
|
85
|
-
{ type: "choice", question: "q", options: [{ label: "A" }], multi: true },
|
|
86
|
-
]);
|
|
87
|
-
expect(q).toEqual({ question: "q", options: [{ label: "A" }], multiSelect: true });
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("text:无 options 字段(答案经 __other 键解码)", () => {
|
|
91
|
-
const [q] = formToAskUserQuestions([{ type: "text", question: "q" }]);
|
|
92
|
-
expect(q).toEqual({ question: "q" });
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("schedule:跳过(ask-user 链路不产生)", () => {
|
|
96
|
-
const out = formToAskUserQuestions([
|
|
97
|
-
{ type: "schedule", question: "when?" },
|
|
98
|
-
{ type: "text", question: "q" },
|
|
99
|
-
]);
|
|
100
|
-
expect(out).toEqual([{ question: "q" }]);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("round trip:internal → Form → AskUser 与旧 toProtoQuestions 语义等价", () => {
|
|
104
|
-
const form = internalToFormQuestions([internalSingle, internalMulti]);
|
|
105
|
-
const decodeView = formToAskUserQuestions(form);
|
|
106
|
-
expect(decodeView[0]).toEqual({
|
|
107
|
-
question: "Which DB?",
|
|
108
|
-
options: [
|
|
109
|
-
{ label: "Postgres", description: "Mature" },
|
|
110
|
-
{ label: "SQLite" },
|
|
111
|
-
],
|
|
112
|
-
allowOther: true,
|
|
113
|
-
});
|
|
114
|
-
expect(decodeView[1]).toEqual({
|
|
115
|
-
question: "Which tools?",
|
|
116
|
-
header: "Tools",
|
|
117
|
-
context: "pick many",
|
|
118
|
-
options: [{ label: "A" }, { label: "B" }],
|
|
119
|
-
multiSelect: true,
|
|
120
|
-
allowOther: true,
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
81
|
describe("formToInternalQuestions(TUI 组件形态)", () => {
|
|
126
82
|
it("choice → options 原样 + multiSelect 还原", () => {
|
|
127
83
|
const [q] = formToInternalQuestions([
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/__tests__/index.test.ts
|
|
2
2
|
// Tests the factory + execute orchestration (FR-1/7/8/9/10/13) with mocked ctx/pi.
|
|
3
|
+
import { getAskUserAnswer, getAskUserOther } from "@zhushanwen/extension-protocol";
|
|
3
4
|
import { describe, expect, it } from "vitest";
|
|
4
5
|
|
|
5
6
|
import factory from "../index";
|
|
@@ -493,7 +494,12 @@ describe("factory registration (FR-1)", () => {
|
|
|
493
494
|
expect(tool.name).toBe("ask_user");
|
|
494
495
|
expect(tool.label).toBe("Ask User");
|
|
495
496
|
expect(tool.description).toBeTruthy();
|
|
496
|
-
|
|
497
|
+
// 结构锚定:parameters 须为 { type: 'object', properties: { questions } }(InputSchema 投影),
|
|
498
|
+
// 防 parameters 被替换成任意 truthy 值仍绿的弱断言
|
|
499
|
+
expect(tool.parameters).toMatchObject({
|
|
500
|
+
type: "object",
|
|
501
|
+
properties: expect.objectContaining({ questions: expect.anything() }),
|
|
502
|
+
});
|
|
497
503
|
expect(typeof tool.execute).toBe("function");
|
|
498
504
|
expect(typeof tool.renderCall).toBe("function");
|
|
499
505
|
expect(typeof tool.renderResult).toBe("function");
|
|
@@ -849,3 +855,78 @@ describe("execute — RPC mode (uiFormInteract via select channel)", () => {
|
|
|
849
855
|
});
|
|
850
856
|
|
|
851
857
|
});
|
|
858
|
+
|
|
859
|
+
// ── 输出等价锚(plan-mode-audit-remediation 批次 3 活路径去重)──
|
|
860
|
+
// protoQuestions 副本对齐:index.ts 的 toProtoQuestions 副本已删,RPC 解码直接以
|
|
861
|
+
// internal Question 调协议 helper。本组断言钉死两种视图在解码 SSOT(getAskUserAnswer /
|
|
862
|
+
// getAskUserOther)下输出逐字一致——proto 视图以字面量充当被删副本的参考实现。
|
|
863
|
+
describe("protoAnswersToResult 解码视图等价锚(internal Question ≡ 旧 protoQuestions 副本)", () => {
|
|
864
|
+
// internal Question(validateInput 通过后的形态)
|
|
865
|
+
const internal = [
|
|
866
|
+
{
|
|
867
|
+
question: "Which DB?",
|
|
868
|
+
options: [{ label: "Postgres" }, { label: "SQLite" }],
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
question: "Which tools?",
|
|
872
|
+
header: "Tools",
|
|
873
|
+
context: "pick many",
|
|
874
|
+
options: [{ label: "A" }, { label: "B" }],
|
|
875
|
+
multiSelect: true,
|
|
876
|
+
},
|
|
877
|
+
] as const;
|
|
878
|
+
|
|
879
|
+
// 被删 toProtoQuestions 的逐字产出形态(副本的参考实现字面量)
|
|
880
|
+
const protoView = [
|
|
881
|
+
{
|
|
882
|
+
header: undefined,
|
|
883
|
+
question: "Which DB?",
|
|
884
|
+
context: undefined,
|
|
885
|
+
options: [
|
|
886
|
+
{ label: "Postgres", description: undefined },
|
|
887
|
+
{ label: "SQLite", description: undefined },
|
|
888
|
+
],
|
|
889
|
+
multiSelect: undefined,
|
|
890
|
+
allowOther: true,
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
header: "Tools",
|
|
894
|
+
question: "Which tools?",
|
|
895
|
+
context: "pick many",
|
|
896
|
+
options: [
|
|
897
|
+
{ label: "A", description: undefined },
|
|
898
|
+
{ label: "B", description: undefined },
|
|
899
|
+
],
|
|
900
|
+
multiSelect: true,
|
|
901
|
+
allowOther: true,
|
|
902
|
+
},
|
|
903
|
+
] as const;
|
|
904
|
+
|
|
905
|
+
const answerShapes: Record<string, Record<string, string>> = {
|
|
906
|
+
"single selection": { "Which DB?": "Postgres" },
|
|
907
|
+
"multi selection (JSON array)": { Tools: JSON.stringify(["B", "A"]) },
|
|
908
|
+
"multi + other": {
|
|
909
|
+
Tools: JSON.stringify(["A"]),
|
|
910
|
+
"Tools__other": "Custom",
|
|
911
|
+
},
|
|
912
|
+
"unanswered": {},
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
it.each(Object.entries(answerShapes))(
|
|
916
|
+
"%s: decode via internal Question ≡ decode via deleted proto copy",
|
|
917
|
+
(_name, answers) => {
|
|
918
|
+
internal.forEach((q, i) => {
|
|
919
|
+
const pq = protoView[i]!;
|
|
920
|
+
expect(getAskUserAnswer(answers, q)).toEqual(getAskUserAnswer(answers, pq));
|
|
921
|
+
expect(getAskUserOther(answers, q) ?? null).toEqual(getAskUserOther(answers, pq) ?? null);
|
|
922
|
+
});
|
|
923
|
+
},
|
|
924
|
+
);
|
|
925
|
+
|
|
926
|
+
it("decoded values pinned(防双向同错空转)", () => {
|
|
927
|
+
expect(getAskUserAnswer({ "Which DB?": "Postgres" }, internal[0]!)).toBe("Postgres");
|
|
928
|
+
expect(getAskUserAnswer({ Tools: JSON.stringify(["B", "A"]) }, internal[1]!)).toEqual(["B", "A"]);
|
|
929
|
+
expect(getAskUserOther({ "Tools__other": "Custom" }, internal[1]!)).toBe("Custom");
|
|
930
|
+
expect(getAskUserAnswer({}, internal[0]!)).toBeUndefined();
|
|
931
|
+
});
|
|
932
|
+
});
|
package/src/channel-handler.ts
CHANGED
|
@@ -35,12 +35,8 @@ import { getLogger } from "@zhushanwen/pi-extension-logger";
|
|
|
35
35
|
|
|
36
36
|
import { AskUserComponent } from "./component";
|
|
37
37
|
import { encodeAnswer } from "./answer-codec";
|
|
38
|
-
import {
|
|
39
|
-
|
|
40
|
-
formToAskUserQuestions,
|
|
41
|
-
formToInternalQuestions,
|
|
42
|
-
} from "./form-adapter";
|
|
43
|
-
import { type AnswerValue, type Result, type ThemeLike } from "./types";
|
|
38
|
+
import { askUserToFormQuestions, formToInternalQuestions } from "./form-adapter";
|
|
39
|
+
import { type AnswerValue, type Question, type Result, type ThemeLike } from "./types";
|
|
44
40
|
|
|
45
41
|
const logger = getLogger("ask-user");
|
|
46
42
|
|
|
@@ -94,6 +90,10 @@ function payloadToFormQuestions(payload: ChannelRequest["channelPayload"]): Form
|
|
|
94
90
|
* 内部 Result.answers:key = question 全文,value = 结构化 AnswerValue
|
|
95
91
|
* (selected = option label 数组,other = Other 自由文本)。
|
|
96
92
|
*
|
|
93
|
+
* 迭代直接走 internal Question[](formToInternalQuestions 的单次转换产物)——
|
|
94
|
+
* internal Question 自带编码所需 key(header ?? question)与 multiSelect 语义,
|
|
95
|
+
* 无需再经 legacy AskUserQuestion[] 中转视图。
|
|
96
|
+
*
|
|
97
97
|
* proto FormAnswers 契约(@zhushanwen/extension-protocol,choice/text 部分与旧
|
|
98
98
|
* AskUserAnswers 逐字兼容):
|
|
99
99
|
* - key = question.header ?? question 全文
|
|
@@ -104,19 +104,16 @@ function payloadToFormQuestions(payload: ChannelRequest["channelPayload"]): Form
|
|
|
104
104
|
* 序列化走 encodeAnswer(answer-codec.ts 是本扩展内的唯一 encode 实现,与协议包解码
|
|
105
105
|
* helper 对齐;renderer 前端组件无法 import extension 包,独立实现对齐同一解码契约)。
|
|
106
106
|
*/
|
|
107
|
-
function encodeTuiResultToProto(
|
|
108
|
-
protoQuestions: AskUserQuestion[],
|
|
109
|
-
result: Result,
|
|
110
|
-
): FormAnswers {
|
|
107
|
+
function encodeTuiResultToProto(questions: Question[], result: Result): FormAnswers {
|
|
111
108
|
const answers: FormAnswers = {};
|
|
112
|
-
for (const
|
|
113
|
-
const av: AnswerValue | undefined = result.answers[
|
|
109
|
+
for (const q of questions) {
|
|
110
|
+
const av: AnswerValue | undefined = result.answers[q.question];
|
|
114
111
|
if (av === undefined) continue; // 该问题未答(buildResult 跳过未答)
|
|
115
112
|
Object.assign(
|
|
116
113
|
answers,
|
|
117
114
|
encodeAnswer(av, {
|
|
118
|
-
key:
|
|
119
|
-
multiSelect:
|
|
115
|
+
key: q.header ?? q.question,
|
|
116
|
+
multiSelect: q.multiSelect === true,
|
|
120
117
|
}),
|
|
121
118
|
);
|
|
122
119
|
}
|
|
@@ -152,7 +149,7 @@ async function runTuiProtoInteraction(
|
|
|
152
149
|
// 显式 undefined 守卫:裸 result.cancelled 对 undefined 会抛 TypeError(W4 修复前
|
|
153
150
|
// 靠 dialog-queue 兜底为 {cancelled:true},现源头短路,语义等价且不再依赖兜底)。
|
|
154
151
|
if (result === null || result === undefined || result.cancelled) return null;
|
|
155
|
-
return encodeTuiResultToProto(
|
|
152
|
+
return encodeTuiResultToProto(questions, result);
|
|
156
153
|
}
|
|
157
154
|
|
|
158
155
|
/**
|
|
@@ -27,7 +27,7 @@ const logger = getLogger("ask-user");
|
|
|
27
27
|
* 完全一致——两边用同一字符串确保拿到同一 slot 实例。改名必须两侧同步。
|
|
28
28
|
*/
|
|
29
29
|
export const CHANNEL_HANDSHAKE_KEY = Symbol.for(
|
|
30
|
-
"@zhushanwen/
|
|
30
|
+
"@zhushanwen/subagent-core.channelHandshake",
|
|
31
31
|
);
|
|
32
32
|
|
|
33
33
|
/** 握手协议版本号。读写 slot 时校验 version !== 1 视为不兼容(warn + 重建 slot)。 */
|
package/src/form-adapter.ts
CHANGED
|
@@ -68,30 +68,6 @@ export function askUserToFormQuestions(legacy: AskUserQuestion[]): FormQuestion[
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
/** FormQuestion[] → legacy AskUserQuestion[](解码视图:复用协议包
|
|
72
|
-
* getAskUserAnswer/getAskUserOther 的解码 SSOT,multi 还原为 multiSelect)。
|
|
73
|
-
* 保持顺序、跳过 schedule(ask-user 链路不产生;与 formToInternalQuestions 同规则)。 */
|
|
74
|
-
export function formToAskUserQuestions(form: FormQuestion[]): AskUserQuestion[] {
|
|
75
|
-
return form.flatMap((q: FormQuestion): AskUserQuestion[] => {
|
|
76
|
-
if (q.type === "schedule") return [];
|
|
77
|
-
const common = {
|
|
78
|
-
header: q.header,
|
|
79
|
-
question: q.question,
|
|
80
|
-
context: q.context,
|
|
81
|
-
};
|
|
82
|
-
return [
|
|
83
|
-
q.type === "choice"
|
|
84
|
-
? {
|
|
85
|
-
...common,
|
|
86
|
-
options: q.options,
|
|
87
|
-
multiSelect: q.multi,
|
|
88
|
-
allowOther: q.allowOther,
|
|
89
|
-
}
|
|
90
|
-
: common,
|
|
91
|
-
];
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
71
|
/** FormQuestion[] → internal Question[](TUI 组件渲染形态)。
|
|
96
72
|
* choice → options 原样(multiSelect 还原);text → 空 options(组件渲染为仅 Other
|
|
97
73
|
* 行的自由输入,与旧 payload 无 options 问题的现状处理一致);schedule 跳过
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type { AgentToolResult, AgentToolUpdateCallback, ExtensionAPI, ExtensionC
|
|
|
4
4
|
import { type Static } from "typebox";
|
|
5
5
|
import {
|
|
6
6
|
type AskUserAnswers,
|
|
7
|
-
type AskUserQuestion,
|
|
8
7
|
getAskUserAnswer,
|
|
9
8
|
getAskUserOther,
|
|
10
9
|
uiFormInteract,
|
|
@@ -109,47 +108,26 @@ function renderExpandedOptions(
|
|
|
109
108
|
return out;
|
|
110
109
|
}
|
|
111
110
|
|
|
112
|
-
/**
|
|
113
|
-
* 把 ask-user 内部 Question[] 映射为协议包 AskUserQuestion[](RPC 交互声明)。
|
|
114
|
-
*
|
|
115
|
-
* ask-user 的 Question.options 只有 label/description(协议 AskUserOption 也无 value
|
|
116
|
-
* 字段——回传值统一用 label,与 ask-user 语义一致)。
|
|
117
|
-
* allowOther 固定 true:ask-user 无条件自动追加 Other(schema 不暴露此字段)。
|
|
118
|
-
*/
|
|
119
|
-
function toProtoQuestions(questions: Question[]): AskUserQuestion[] {
|
|
120
|
-
return questions.map((q: Question) => ({
|
|
121
|
-
header: q.header,
|
|
122
|
-
question: q.question,
|
|
123
|
-
context: q.context,
|
|
124
|
-
options: q.options.map((o: Option) => ({
|
|
125
|
-
label: o.label,
|
|
126
|
-
description: o.description,
|
|
127
|
-
})),
|
|
128
|
-
multiSelect: q.multiSelect,
|
|
129
|
-
allowOther: true,
|
|
130
|
-
}));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
111
|
/**
|
|
134
112
|
* 把协议包 AskUserAnswers 转换为 ask-user 内部 Result.answers(结构化 AnswerValue)。
|
|
135
113
|
*
|
|
136
114
|
* 协议格式:key=header/question, 单选=string, 多选=JSON数组, Other=__other
|
|
137
115
|
* ask-user 格式:key=question 全文, value=AnswerValue(selected=label 数组、other=自由文本)
|
|
138
116
|
*
|
|
139
|
-
*
|
|
117
|
+
* 解码直接以内部 Question 调协议包 helper(getAskUserAnswer/getAskUserOther,proto
|
|
118
|
+
* 格式的唯一解码 SSOT)——internal Question 结构上就是 proto 视图(header/question/
|
|
119
|
+
* multiSelect 同名同义),无需再构造一份 AskUserQuestion[] 副本;
|
|
140
120
|
* selected 空 && other 无 → 该问题未答,跳过不写入。
|
|
141
121
|
*/
|
|
142
122
|
function protoAnswersToResult(
|
|
143
123
|
questions: Question[],
|
|
144
|
-
protoQuestions: AskUserQuestion[],
|
|
145
124
|
answers: AskUserAnswers,
|
|
146
125
|
): Record<string, AnswerValue> {
|
|
147
126
|
const out: Record<string, AnswerValue> = {};
|
|
148
127
|
for (let i = 0; i < questions.length; i++) {
|
|
149
128
|
const q = questions[i]!;
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
const other = getAskUserOther(answers, iq) ?? null;
|
|
129
|
+
const selected = getAskUserAnswer(answers, q);
|
|
130
|
+
const other = getAskUserOther(answers, q) ?? null;
|
|
153
131
|
|
|
154
132
|
let selectedArr: string[];
|
|
155
133
|
if (Array.isArray(selected)) {
|
|
@@ -194,7 +172,6 @@ async function runRpcInteraction(
|
|
|
194
172
|
signal: AbortSignal | undefined,
|
|
195
173
|
ctx: ExtensionContext,
|
|
196
174
|
): Promise<Result> {
|
|
197
|
-
const protoQuestions = toProtoQuestions(questions);
|
|
198
175
|
const formQuestions = internalToFormQuestions(questions);
|
|
199
176
|
const guiCtx = {
|
|
200
177
|
mode: ctx.mode,
|
|
@@ -226,7 +203,7 @@ async function runRpcInteraction(
|
|
|
226
203
|
}
|
|
227
204
|
return {
|
|
228
205
|
questions,
|
|
229
|
-
answers: protoAnswersToResult(questions,
|
|
206
|
+
answers: protoAnswersToResult(questions, interact.answers),
|
|
230
207
|
cancelled: false,
|
|
231
208
|
};
|
|
232
209
|
}
|