@deepseek-harness-tui/dsh-tui 0.5.2 → 0.6.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.
Files changed (49) hide show
  1. package/README.md +7 -1
  2. package/lib/types/approvals.d.ts +80 -0
  3. package/lib/types/approvals.d.ts.map +1 -0
  4. package/lib/types/approvals.js +170 -0
  5. package/lib/types/channel.d.ts +5 -0
  6. package/lib/types/channel.d.ts.map +1 -1
  7. package/lib/types/channel.js +32 -25
  8. package/lib/types/commands.d.ts +14 -1
  9. package/lib/types/commands.d.ts.map +1 -1
  10. package/lib/types/commands.js +13 -0
  11. package/lib/types/compat/index.d.ts +1 -1
  12. package/lib/types/compat/index.d.ts.map +1 -1
  13. package/lib/types/compat/index.js +1 -1
  14. package/lib/types/compat/sessionLog.d.ts +21 -0
  15. package/lib/types/compat/sessionLog.d.ts.map +1 -1
  16. package/lib/types/compat/sessionLog.js +122 -37
  17. package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
  18. package/lib/types/components/CommandSuggestions.js +5 -3
  19. package/lib/types/components/HelpMenu.d.ts +1 -0
  20. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  21. package/lib/types/components/HelpMenu.js +4 -1
  22. package/lib/types/components/PromptInput.d.ts.map +1 -1
  23. package/lib/types/components/PromptInput.js +16 -15
  24. package/lib/types/components/approvals/ApprovalPanel.d.ts +21 -0
  25. package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -0
  26. package/lib/types/components/approvals/ApprovalPanel.js +48 -0
  27. package/lib/types/components/questions/AskUserQuestionPanel.d.ts +6 -0
  28. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  29. package/lib/types/components/questions/AskUserQuestionPanel.js +9 -2
  30. package/lib/types/components/questions/PlanReviewPanel.d.ts +39 -0
  31. package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -0
  32. package/lib/types/components/questions/PlanReviewPanel.js +168 -0
  33. package/lib/types/i18n.d.ts +189 -2
  34. package/lib/types/i18n.d.ts.map +1 -1
  35. package/lib/types/i18n.js +86 -1
  36. package/lib/types/ink/stringWidth.d.ts.map +1 -1
  37. package/lib/types/ink/stringWidth.js +61 -0
  38. package/lib/types/plugin.d.ts.map +1 -1
  39. package/lib/types/plugin.js +15 -0
  40. package/lib/types/questions.d.ts +3 -0
  41. package/lib/types/questions.d.ts.map +1 -1
  42. package/lib/types/questions.js +13 -1
  43. package/lib/types/screens/Chat.d.ts +8 -13
  44. package/lib/types/screens/Chat.d.ts.map +1 -1
  45. package/lib/types/screens/Chat.js +47 -18
  46. package/lib/types/utils/modifiers.d.ts +19 -0
  47. package/lib/types/utils/modifiers.d.ts.map +1 -0
  48. package/lib/types/utils/modifiers.js +17 -0
  49. package/package.json +2 -1
@@ -0,0 +1,168 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * The plan-review panel — Claude Code style exit-plan-mode decision card
4
+ * for the DSH user-interaction seam. plan-mode's `exit_plan_mode` tool asks
5
+ * through `ctx.userQuestions` with `intent: { kind: 'plan-review',
6
+ * approve }`: the plan markdown arrives in `detail`, the approve/decline
7
+ * choices in `options` (labels verbatim — the protocol answers with the
8
+ * asker's own labels).
9
+ *
10
+ * Protocol-exact answer mapping (dsh-plan-mode):
11
+ * - Approve: `{ selected: [intent.approve] }` — custom MUST be absent, or
12
+ * plan-mode treats it as keep-planning-with-feedback.
13
+ * - Keep planning / feedback: `{ selected: [declineLabel], custom? }` where
14
+ * declineLabel is the first option that is not the approve label.
15
+ * - Esc / Ctrl+C: the store rejects with ASK_CANCELLED, which plan-mode
16
+ * reads as "the user dismissed the review to speak instead".
17
+ */
18
+ import React from 'react';
19
+ import { t } from '../../i18n.js';
20
+ import { Box, Text, useInput } from '../../ui.js';
21
+ import { Divider } from '../design-system/Divider.js';
22
+ import { Markdown } from '../Markdown.js';
23
+ import { POINTER } from '../../cc/figures.js';
24
+ const PENCIL = '✎';
25
+ export function PlanReviewPanel({ question, onAnswer, onCancel, }) {
26
+ const options = question.options ?? [];
27
+ const approveLabel = question.intent?.approve ?? options[0]?.label;
28
+ const declineLabel = options.find(option => option.label !== approveLabel)?.label;
29
+ /** Rows: the asker's options plus the feedback input row at the tail. */
30
+ const rowCount = options.length + 1;
31
+ const [focusIndex, setFocusIndex] = React.useState(0);
32
+ const [feedback, setFeedback] = React.useState('');
33
+ const [cursor, setCursor] = React.useState(0);
34
+ const [error, setError] = React.useState(null);
35
+ const inputFocused = focusIndex === options.length;
36
+ const moveFocus = (delta) => {
37
+ setFocusIndex(index => (index + delta + rowCount) % rowCount);
38
+ setError(null);
39
+ };
40
+ /** Typing anywhere appends to the feedback buffer and focuses the input
41
+ * row — plan review has no "attach" semantics: approve must be clean. */
42
+ const appendFeedback = (text) => {
43
+ setFeedback(previous => previous + text);
44
+ setCursor(previous => previous + text.length);
45
+ setFocusIndex(options.length);
46
+ setError(null);
47
+ };
48
+ const backspaceFeedback = () => {
49
+ if (cursor <= 0)
50
+ return;
51
+ setFeedback(previous => previous.slice(0, cursor - 1) + previous.slice(cursor));
52
+ setCursor(previous => previous - 1);
53
+ };
54
+ /** The decline answer: the other option's label when the asker named one,
55
+ * else an empty selection (plan-mode reads any non-approve as decline). */
56
+ const declineSelected = () => declineLabel !== undefined ? [declineLabel] : [];
57
+ /** Enter on an option row. Approve with feedback in the buffer is an
58
+ * error — the protocol would silently read it as keep-planning. */
59
+ const submitOption = (index) => {
60
+ const label = options[index]?.label;
61
+ if (label === undefined)
62
+ return;
63
+ const text = feedback.trim();
64
+ if (label === approveLabel && text !== '') {
65
+ setError(t('plan-review-approve-needs-empty'));
66
+ return;
67
+ }
68
+ if (label === approveLabel) {
69
+ onAnswer({ selected: [label] });
70
+ return;
71
+ }
72
+ onAnswer({ selected: [label], ...(text !== '' ? { custom: text } : {}) });
73
+ };
74
+ /** Enter on the feedback row: text routes to keep-planning-with-feedback;
75
+ * empty is a plain keep-planning. */
76
+ const submitFeedback = () => {
77
+ const text = feedback.trim();
78
+ onAnswer({ selected: declineSelected(), ...(text !== '' ? { custom: text } : {}) });
79
+ };
80
+ useInput((input, key) => {
81
+ if (key.escape || (key.ctrl && input === 'c')) {
82
+ onCancel();
83
+ return;
84
+ }
85
+ if (inputFocused) {
86
+ if (key.upArrow) {
87
+ moveFocus(-1);
88
+ return;
89
+ }
90
+ if (key.downArrow) {
91
+ moveFocus(1);
92
+ return;
93
+ }
94
+ if (key.return) {
95
+ submitFeedback();
96
+ return;
97
+ }
98
+ if (key.backspace) {
99
+ backspaceFeedback();
100
+ return;
101
+ }
102
+ if (key.delete) {
103
+ if (cursor < feedback.length) {
104
+ setFeedback(text => text.slice(0, cursor) + text.slice(cursor + 1));
105
+ }
106
+ return;
107
+ }
108
+ if (key.leftArrow) {
109
+ setCursor(value => Math.max(0, value - 1));
110
+ return;
111
+ }
112
+ if (key.rightArrow) {
113
+ setCursor(value => Math.min(feedback.length, value + 1));
114
+ return;
115
+ }
116
+ if (key.home) {
117
+ setCursor(0);
118
+ return;
119
+ }
120
+ if (key.end) {
121
+ setCursor(feedback.length);
122
+ return;
123
+ }
124
+ if (!key.ctrl && !key.meta && input) {
125
+ setFeedback(text => text.slice(0, cursor) + input + text.slice(cursor));
126
+ setCursor(value => value + input.length);
127
+ setError(null);
128
+ }
129
+ return;
130
+ }
131
+ // An option row.
132
+ if (key.upArrow) {
133
+ moveFocus(-1);
134
+ return;
135
+ }
136
+ if (key.downArrow) {
137
+ moveFocus(1);
138
+ return;
139
+ }
140
+ if (key.return) {
141
+ submitOption(focusIndex);
142
+ return;
143
+ }
144
+ if (key.backspace) {
145
+ if (feedback !== '')
146
+ backspaceFeedback();
147
+ return;
148
+ }
149
+ if (!key.ctrl && !key.meta && input) {
150
+ // Number quick-pick submits the option outright — but only with an
151
+ // empty buffer; with feedback pending, digits are feedback chars.
152
+ const digit = /^[1-9]$/.test(input) ? Number(input) : 0;
153
+ if (feedback === '' && digit >= 1 && digit <= options.length) {
154
+ submitOption(digit - 1);
155
+ return;
156
+ }
157
+ appendFeedback(input);
158
+ }
159
+ }, { isActive: true });
160
+ const cursorChar = cursor < feedback.length ? feedback[cursor] : ' ';
161
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsx(Divider, { color: "permission", title: ` ${question.header ?? t('plan-review-fallback-header')} `, padding: 4 }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, wrap: "wrap", children: question.question }), question.detail !== undefined && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Markdown, { children: question.detail }) }))] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [options.map((option, index) => {
162
+ const focused = index === focusIndex;
163
+ const isApprove = option.label === approveLabel;
164
+ return (_jsxs(Box, { flexDirection: "row", marginTop: focused ? 1 : 0, children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { color: focused ? 'claude' : undefined, bold: focused, children: focused ? POINTER : ' ' }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsxs(Text, { bold: focused, color: focused || isApprove ? 'claude' : undefined, wrap: "wrap", children: [index + 1, ". ", option.label] }), option.description !== undefined && (_jsx(Text, { dimColor: true, wrap: "wrap", children: option.description }))] })] }, option.label));
165
+ }), _jsxs(Box, { flexDirection: "row", marginTop: inputFocused ? 1 : 0, children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { color: inputFocused ? 'claude' : undefined, bold: inputFocused, children: inputFocused ? POINTER : ' ' }) }), _jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { color: inputFocused ? 'claude' : 'suggestion', children: PENCIL }) }), _jsx(Box, { flexDirection: "row", marginLeft: 1, children: feedback === '' && !inputFocused ? (_jsx(Text, { dimColor: true, children: t('plan-review-feedback-placeholder') })) : (_jsxs(_Fragment, { children: [_jsx(Text, { wrap: "wrap", children: feedback.slice(0, cursor) }), inputFocused
166
+ ? _jsx(Text, { inverse: true, children: cursorChar })
167
+ : _jsx(Text, { color: "suggestion", children: "\u258F" }), _jsx(Text, { wrap: "wrap", children: feedback.slice(inputFocused ? cursor + 1 : cursor) })] })) })] })] }), error !== null && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "error", children: error }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: t('plan-review-hint') }) })] }));
168
+ }
@@ -584,8 +584,8 @@ declare const dict: {
584
584
  readonly en: "Recommended: Windows Terminal (≥110 columns, monospace, TrueColor).";
585
585
  };
586
586
  readonly 'terminal-paste-hint': {
587
- readonly zh: "Ctrl+V 粘贴文本/文件路径;Ctrl+Shift+V 终端原生粘贴;右键粘贴同样可用。";
588
- readonly en: "Ctrl+V pastes text/file paths; Ctrl+Shift+V is native terminal paste; right-click paste also works.";
587
+ readonly zh: "{{mod}}V 粘贴文本/文件路径;Ctrl+Shift+V 终端原生粘贴;右键粘贴同样可用。";
588
+ readonly en: "{{mod}}V pastes text/file paths; Ctrl+Shift+V is native terminal paste; right-click paste also works.";
589
589
  };
590
590
  readonly 'connect-none': {
591
591
  readonly zh: "DSH 暂无远程连接机制(CC 的 /connect 对应能力未适配)。";
@@ -751,6 +751,18 @@ declare const dict: {
751
751
  readonly zh: "当前目录没有可恢复的历史会话";
752
752
  readonly en: "No resumable sessions in the current directory";
753
753
  };
754
+ readonly 'rename-usage': {
755
+ readonly zh: "用法 /rename <新名称>";
756
+ readonly en: "Usage /rename <new title>";
757
+ };
758
+ readonly 'rename-current': {
759
+ readonly zh: "当前名称 {{title}}";
760
+ readonly en: "Current title {{title}}";
761
+ };
762
+ readonly 'rename-done': {
763
+ readonly zh: "已重命名为「{{title}}」";
764
+ readonly en: "Renamed to \"{{title}}\"";
765
+ };
754
766
  readonly 'compact-summary-folded': {
755
767
  readonly zh: "摘要已折叠";
756
768
  readonly en: "Summary folded";
@@ -855,6 +867,171 @@ declare const dict: {
855
867
  readonly zh: "直接输入…";
856
868
  readonly en: "Type directly…";
857
869
  };
870
+ readonly 'approval-waiting': {
871
+ readonly zh: " ⏳ 等待审批 · {{tool}} ";
872
+ readonly en: " Awaiting approval · {{tool}} ";
873
+ };
874
+ readonly 'approval-proceed': {
875
+ readonly zh: "要允许这次操作吗?";
876
+ readonly en: "Do you want to proceed?";
877
+ };
878
+ readonly 'approval-yes': {
879
+ readonly zh: "允许(仅本次)";
880
+ readonly en: "Yes, allow once";
881
+ };
882
+ readonly 'approval-no': {
883
+ readonly zh: "拒绝";
884
+ readonly en: "No";
885
+ };
886
+ readonly 'approval-hint': {
887
+ readonly zh: "↑/↓ 选择 · Enter 确认 · Esc 拒绝";
888
+ readonly en: "↑/↓ select · Enter confirm · Esc reject";
889
+ };
890
+ readonly 'plan-review-fallback-header': {
891
+ readonly zh: "计划评审";
892
+ readonly en: "Plan review";
893
+ };
894
+ readonly 'plan-review-feedback-placeholder': {
895
+ readonly zh: "输入反馈,告诉模型要改什么…";
896
+ readonly en: "Tell the model what to change…";
897
+ };
898
+ readonly 'plan-review-approve-needs-empty': {
899
+ readonly zh: "请先清空反馈再批准(或在输入行回车提交反馈)";
900
+ readonly en: "Clear the feedback to approve (or press Enter on the input row to send it)";
901
+ };
902
+ readonly 'plan-review-hint': {
903
+ readonly zh: "↑/↓ 选择 · 1/2 快选 · 打字输入反馈 · Enter 提交 · Esc 打断评审";
904
+ readonly en: "↑/↓ select · 1/2 quick-pick · type feedback · Enter submit · Esc dismiss";
905
+ };
906
+ readonly 'cmd-desc-new': {
907
+ readonly zh: "新开会话";
908
+ };
909
+ readonly 'cmd-desc-clear': {
910
+ readonly zh: "清空当前会话";
911
+ };
912
+ readonly 'cmd-desc-compact': {
913
+ readonly zh: "压缩会话历史";
914
+ };
915
+ readonly 'cmd-desc-resume': {
916
+ readonly zh: "恢复历史会话";
917
+ };
918
+ readonly 'cmd-desc-rename': {
919
+ readonly zh: "重命名当前会话";
920
+ };
921
+ readonly 'cmd-desc-rewind': {
922
+ readonly zh: "回退会话到历史消息";
923
+ };
924
+ readonly 'cmd-desc-export': {
925
+ readonly zh: "导出会话为 Markdown 文件";
926
+ };
927
+ readonly 'cmd-desc-status': {
928
+ readonly zh: "查看会话状态";
929
+ };
930
+ readonly 'cmd-desc-cost': {
931
+ readonly zh: "查看会话 token 用量";
932
+ };
933
+ readonly 'cmd-desc-config': {
934
+ readonly zh: "查看 dsh-cc 配置来源";
935
+ };
936
+ readonly 'cmd-desc-doctor': {
937
+ readonly zh: "运行环境检查";
938
+ };
939
+ readonly 'cmd-desc-init': {
940
+ readonly zh: "在工作目录创建 AGENTS.md";
941
+ };
942
+ readonly 'cmd-desc-agents': {
943
+ readonly zh: "查看本会话的子代理";
944
+ };
945
+ readonly 'cmd-desc-activity': {
946
+ readonly zh: "切换工作状态指示器预设";
947
+ };
948
+ readonly 'cmd-desc-preset': {
949
+ readonly zh: "切换 Agent 预设(standard/code/minimal/cordis)";
950
+ };
951
+ readonly 'cmd-desc-theme': {
952
+ readonly zh: "切换配色主题(内置或自定义)";
953
+ };
954
+ readonly 'cmd-desc-lang': {
955
+ readonly zh: "切换界面语言(en / zh)";
956
+ };
957
+ readonly 'cmd-desc-model': {
958
+ readonly zh: "查看当前模型";
959
+ };
960
+ readonly 'cmd-desc-thinking': {
961
+ readonly zh: "切换扩展思考显示";
962
+ };
963
+ readonly 'cmd-desc-tokens': {
964
+ readonly zh: "查看会话 token 用量";
965
+ };
966
+ readonly 'cmd-desc-login': {
967
+ readonly zh: "查看 API 凭证状态";
968
+ };
969
+ readonly 'cmd-desc-logout': {
970
+ readonly zh: "清除 API 凭证";
971
+ };
972
+ readonly 'cmd-desc-permissions': {
973
+ readonly zh: "查看权限策略状态";
974
+ };
975
+ readonly 'cmd-desc-add-dir': {
976
+ readonly zh: "查看文件系统策略范围";
977
+ };
978
+ readonly 'cmd-desc-hooks': {
979
+ readonly zh: "查看 hooks 状态";
980
+ };
981
+ readonly 'cmd-desc-mcp': {
982
+ readonly zh: "查看 MCP 状态";
983
+ };
984
+ readonly 'cmd-desc-memory': {
985
+ readonly zh: "查看记忆状态";
986
+ };
987
+ readonly 'cmd-desc-update': {
988
+ readonly zh: "更新 dsh-cc-tui 并重启";
989
+ };
990
+ readonly 'cmd-desc-audit': {
991
+ readonly zh: "对当前项目做全面代码审计";
992
+ };
993
+ readonly 'cmd-desc-bug': {
994
+ readonly zh: "记录一份 bug 报告";
995
+ };
996
+ readonly 'cmd-desc-practice': {
997
+ readonly zh: "与 dsh-cc 进行编程练习";
998
+ };
999
+ readonly 'cmd-desc-review': {
1000
+ readonly zh: "对当前项目做全面代码评审";
1001
+ };
1002
+ readonly 'cmd-desc-pr_comments': {
1003
+ readonly zh: "审查拉取请求评论";
1004
+ };
1005
+ readonly 'cmd-desc-release-notes': {
1006
+ readonly zh: "生成发布说明";
1007
+ };
1008
+ readonly 'cmd-desc-vuln-check': {
1009
+ readonly zh: "运行安全漏洞检查";
1010
+ };
1011
+ readonly 'cmd-desc-vim': {
1012
+ readonly zh: "切换 vim 模式";
1013
+ };
1014
+ readonly 'cmd-desc-terminal-setup': {
1015
+ readonly zh: "查看终端配置建议";
1016
+ };
1017
+ readonly 'cmd-desc-connect': {
1018
+ readonly zh: "连接远程机器";
1019
+ };
1020
+ readonly 'cmd-desc-help': {
1021
+ readonly zh: "查看快捷键与命令";
1022
+ };
1023
+ readonly 'cmd-desc-exit': {
1024
+ readonly zh: "退出 dsh-tui";
1025
+ };
1026
+ readonly 'cmd-desc-plan': {
1027
+ readonly zh: "切换计划模式(/plan off 退出)";
1028
+ };
1029
+ readonly 'cmd-desc-goal': {
1030
+ readonly zh: "设置或查看会话目标";
1031
+ };
1032
+ readonly 'cmd-desc-feedback': {
1033
+ readonly zh: "提交使用反馈";
1034
+ };
858
1035
  readonly 'lang-current': {
859
1036
  readonly zh: "当前语言 {{lang}}";
860
1037
  readonly en: "Current language {{lang}}";
@@ -900,6 +1077,16 @@ export declare function isLang(value: unknown): value is Lang;
900
1077
  * @param params - Placeholder values.
901
1078
  */
902
1079
  export declare function t(key: I18nKey, params?: I18nParams): string;
1080
+ /**
1081
+ * Translate a runtime-computed key (e.g. `cmd-desc-${name}`), falling back
1082
+ * to the given text when the key is missing or has no entry in the active
1083
+ * language — unlike {@link t}, which renders the key itself. Used where the
1084
+ * fallback holds the authoritative text (command descriptions: the en copy
1085
+ * lives in `LOCAL_COMMANDS` / the DSH registry, the dict carries zh only).
1086
+ * @param key - Dictionary key, computed at runtime so it is not type-checked.
1087
+ * @param fallback - Text used when no translation exists.
1088
+ */
1089
+ export declare function tOr(key: string, fallback: string): string;
903
1090
  /**
904
1091
  * Parse a persisted `{ lang }` value; anything else yields undefined.
905
1092
  * @param text - Raw file contents.
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,MAAM,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAI9B,+DAA+D;AAC/D,eAAO,MAAM,KAAK,uBAAwB,CAAA;AAE1C,QAAA,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyPA,CAAA;AAEV,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,IAAI,CAAA;AACvC,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;AAOxD,uEAAuE;AACvE,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAA;AAG1B,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI,CAG5D;AAED,qCAAqC;AACrC,wBAAgB,OAAO,IAAI,IAAI,CAE9B;AAED,yDAAyD;AACzD,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAGxC;AAED,iDAAiD;AACjD,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,IAAI,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,UAAe,GAAG,MAAM,CAM/D;AAID;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAS5D;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAkB,GAAG,IAAI,GAAG,SAAS,CAMtE;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,GAAE,MAAkB,GAAG,OAAO,CAQ1E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAUvC;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/i18n.ts"],"names":[],"mappings":"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"}
package/lib/types/i18n.js CHANGED
@@ -168,7 +168,7 @@ const dict = {
168
168
  'update-check-failed': { zh: '无法确认新版本(网络或 registry 不可达),已尝试直接更新……', en: 'Could not confirm a newer version (network or registry unreachable); attempting the update anyway…' },
169
169
  'vim-not-implemented': { zh: 'vim 模式暂未实现', en: 'vim mode not implemented yet' },
170
170
  'terminal-setup-hint': { zh: '推荐 Windows Terminal(≥110 列、等宽字体、TrueColor)。', en: 'Recommended: Windows Terminal (≥110 columns, monospace, TrueColor).' },
171
- 'terminal-paste-hint': { zh: 'Ctrl+V 粘贴文本/文件路径;Ctrl+Shift+V 终端原生粘贴;右键粘贴同样可用。', en: 'Ctrl+V pastes text/file paths; Ctrl+Shift+V is native terminal paste; right-click paste also works.' },
171
+ 'terminal-paste-hint': { zh: '{{mod}}V 粘贴文本/文件路径;Ctrl+Shift+V 终端原生粘贴;右键粘贴同样可用。', en: '{{mod}}V pastes text/file paths; Ctrl+Shift+V is native terminal paste; right-click paste also works.' },
172
172
  'connect-none': { zh: 'DSH 暂无远程连接机制(CC 的 /connect 对应能力未适配)。', en: 'DSH has no remote connection mechanism (CC\'s /connect equivalent is not adapted).' },
173
173
  'theme-switch-failed': { zh: '主题「{{name}}」切换失败(无法写入 ~/.dsh-cc/theme.json)', en: 'Theme "{{name}}" switch failed (cannot write ~/.dsh-cc/theme.json)' },
174
174
  'interrupt-delivered': { zh: '已打断当前回合,{{n}} 条消息立即处理', en: 'Interrupted current turn, {{n}} messages processed immediately' },
@@ -218,6 +218,9 @@ const dict = {
218
218
  // ── components/MessageList.tsx ──────────────────────────────────────
219
219
  'load-earlier': { zh: ' ↑ 加载更早消息(会话日志完整,/export 导出全文) ', en: ' ↑ load earlier messages (full session log; /export for full text) ' },
220
220
  'resume-none-in-cwd': { zh: '当前目录没有可恢复的历史会话', en: 'No resumable sessions in the current directory' },
221
+ 'rename-usage': { zh: '用法 /rename <新名称>', en: 'Usage /rename <new title>' },
222
+ 'rename-current': { zh: '当前名称 {{title}}', en: 'Current title {{title}}' },
223
+ 'rename-done': { zh: '已重命名为「{{title}}」', en: 'Renamed to "{{title}}"' },
221
224
  'compact-summary-folded': { zh: '摘要已折叠', en: 'Summary folded' },
222
225
  // ── components/ThemePicker.tsx ──────────────────────────────────────
223
226
  'theme-builtin-base': { zh: '内置 · {{name}} 基底', en: 'Built-in · {{name}} base' },
@@ -247,6 +250,75 @@ const dict = {
247
250
  'question-custom-tab': { zh: '自定义回答', en: 'Custom answer' },
248
251
  'question-attached-label': { zh: '(附加:{{label}})', en: '(attached: {{label}})' },
249
252
  'question-direct-input': { zh: '直接输入…', en: 'Type directly…' },
253
+ // ── components/approvals/ApprovalPanel.tsx ──────────────────────────
254
+ 'approval-waiting': { zh: ' ⏳ 等待审批 · {{tool}} ', en: ' Awaiting approval · {{tool}} ' },
255
+ 'approval-proceed': { zh: '要允许这次操作吗?', en: 'Do you want to proceed?' },
256
+ 'approval-yes': { zh: '允许(仅本次)', en: 'Yes, allow once' },
257
+ 'approval-no': { zh: '拒绝', en: 'No' },
258
+ 'approval-hint': { zh: '↑/↓ 选择 · Enter 确认 · Esc 拒绝', en: '↑/↓ select · Enter confirm · Esc reject' },
259
+ // ── components/questions/PlanReviewPanel.tsx ────────────────────────
260
+ 'plan-review-fallback-header': { zh: '计划评审', en: 'Plan review' },
261
+ 'plan-review-feedback-placeholder': { zh: '输入反馈,告诉模型要改什么…', en: 'Tell the model what to change…' },
262
+ 'plan-review-approve-needs-empty': { zh: '请先清空反馈再批准(或在输入行回车提交反馈)', en: 'Clear the feedback to approve (or press Enter on the input row to send it)' },
263
+ 'plan-review-hint': { zh: '↑/↓ 选择 · 1/2 快选 · 打字输入反馈 · Enter 提交 · Esc 打断评审', en: '↑/↓ select · 1/2 quick-pick · type feedback · Enter submit · Esc dismiss' },
264
+ // ── commands.ts — slash-command descriptions ─────────────────────────
265
+ // zh-only on purpose: the English text stays in `LOCAL_COMMANDS` (and in
266
+ // the DSH registry for external commands) as the single source of truth,
267
+ // so `localizedDescription` falls back to it whenever the active language
268
+ // has no entry here. `cmd-desc-<name>` keys are resolved at render time,
269
+ // so `/lang` switches apply on the next repaint.
270
+ // Conversation
271
+ 'cmd-desc-new': { zh: '新开会话' },
272
+ 'cmd-desc-clear': { zh: '清空当前会话' },
273
+ 'cmd-desc-compact': { zh: '压缩会话历史' },
274
+ 'cmd-desc-resume': { zh: '恢复历史会话' },
275
+ 'cmd-desc-rename': { zh: '重命名当前会话' },
276
+ 'cmd-desc-rewind': { zh: '回退会话到历史消息' },
277
+ 'cmd-desc-export': { zh: '导出会话为 Markdown 文件' },
278
+ // Session / environment
279
+ 'cmd-desc-status': { zh: '查看会话状态' },
280
+ 'cmd-desc-cost': { zh: '查看会话 token 用量' },
281
+ 'cmd-desc-config': { zh: '查看 dsh-cc 配置来源' },
282
+ 'cmd-desc-doctor': { zh: '运行环境检查' },
283
+ 'cmd-desc-init': { zh: '在工作目录创建 AGENTS.md' },
284
+ 'cmd-desc-agents': { zh: '查看本会话的子代理' },
285
+ // Model / display
286
+ 'cmd-desc-activity': { zh: '切换工作状态指示器预设' },
287
+ 'cmd-desc-preset': { zh: '切换 Agent 预设(standard/code/minimal/cordis)' },
288
+ 'cmd-desc-theme': { zh: '切换配色主题(内置或自定义)' },
289
+ 'cmd-desc-lang': { zh: '切换界面语言(en / zh)' },
290
+ 'cmd-desc-model': { zh: '查看当前模型' },
291
+ 'cmd-desc-thinking': { zh: '切换扩展思考显示' },
292
+ 'cmd-desc-tokens': { zh: '查看会话 token 用量' },
293
+ // Account / policy
294
+ 'cmd-desc-login': { zh: '查看 API 凭证状态' },
295
+ 'cmd-desc-logout': { zh: '清除 API 凭证' },
296
+ 'cmd-desc-permissions': { zh: '查看权限策略状态' },
297
+ 'cmd-desc-add-dir': { zh: '查看文件系统策略范围' },
298
+ 'cmd-desc-hooks': { zh: '查看 hooks 状态' },
299
+ 'cmd-desc-mcp': { zh: '查看 MCP 状态' },
300
+ 'cmd-desc-memory': { zh: '查看记忆状态' },
301
+ 'cmd-desc-update': { zh: '更新 dsh-cc-tui 并重启' },
302
+ // Built-in skills
303
+ 'cmd-desc-audit': { zh: '对当前项目做全面代码审计' },
304
+ 'cmd-desc-bug': { zh: '记录一份 bug 报告' },
305
+ 'cmd-desc-practice': { zh: '与 dsh-cc 进行编程练习' },
306
+ 'cmd-desc-review': { zh: '对当前项目做全面代码评审' },
307
+ 'cmd-desc-pr_comments': { zh: '审查拉取请求评论' },
308
+ 'cmd-desc-release-notes': { zh: '生成发布说明' },
309
+ 'cmd-desc-vuln-check': { zh: '运行安全漏洞检查' },
310
+ // Misc
311
+ 'cmd-desc-vim': { zh: '切换 vim 模式' },
312
+ 'cmd-desc-terminal-setup': { zh: '查看终端配置建议' },
313
+ 'cmd-desc-connect': { zh: '连接远程机器' },
314
+ // Help / exit
315
+ 'cmd-desc-help': { zh: '查看快捷键与命令' },
316
+ 'cmd-desc-exit': { zh: '退出 dsh-tui' },
317
+ // Registry-injected (external) commands — zh only; en falls back to the
318
+ // registry's own description, and unlisted externals always fall back.
319
+ 'cmd-desc-plan': { zh: '切换计划模式(/plan off 退出)' },
320
+ 'cmd-desc-goal': { zh: '设置或查看会话目标' },
321
+ 'cmd-desc-feedback': { zh: '提交使用反馈' },
250
322
  // ── /lang command ───────────────────────────────────────────────────
251
323
  'lang-current': { zh: '当前语言 {{lang}}', en: 'Current language {{lang}}' },
252
324
  'lang-switch-hint': { zh: '切换 /lang en | /lang zh', en: 'Switch /lang en | /lang zh' },
@@ -291,6 +363,19 @@ export function t(key, params = {}) {
291
363
  const template = entry?.[activeLang] ?? key;
292
364
  return template.replace(/\{\{(\w+)\}\}/g, (match, name) => name in params ? String(params[name]) : match);
293
365
  }
366
+ /**
367
+ * Translate a runtime-computed key (e.g. `cmd-desc-${name}`), falling back
368
+ * to the given text when the key is missing or has no entry in the active
369
+ * language — unlike {@link t}, which renders the key itself. Used where the
370
+ * fallback holds the authoritative text (command descriptions: the en copy
371
+ * lives in `LOCAL_COMMANDS` / the DSH registry, the dict carries zh only).
372
+ * @param key - Dictionary key, computed at runtime so it is not type-checked.
373
+ * @param fallback - Text used when no translation exists.
374
+ */
375
+ export function tOr(key, fallback) {
376
+ const entry = dict[key];
377
+ return entry?.[activeLang] ?? fallback;
378
+ }
294
379
  // ── persistence (~/.dsh-cc/lang.json) ──────────────────────────────────
295
380
  /**
296
381
  * Parse a persisted `{ lang }` value; anything else yields undefined.
@@ -1 +1 @@
1
- {"version":3,"file":"stringWidth.d.ts","sourceRoot":"","sources":["../../../src/ink/stringWidth.ts"],"names":[],"mappings":"AA2NA;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAElB,CAAA"}
1
+ {"version":3,"file":"stringWidth.d.ts","sourceRoot":"","sources":["../../../src/ink/stringWidth.ts"],"names":[],"mappings":"AAuRA;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAElB,CAAA"}
@@ -114,8 +114,69 @@ function getEmojiWidth(grapheme) {
114
114
  return 1;
115
115
  }
116
116
  }
117
+ // Single codepoint without VS16: only Emoji_Presentation=Yes characters
118
+ // actually render as wide emoji. Text-default emoji (✳ U+2733, ⚠ U+26A0,
119
+ // ❤ U+2764, ❄ U+2744, ✂ U+2702 …) render width 1 in terminals
120
+ // (ghostty/kitty/iTerm2/xterm.js all agree) unless VS16 forces emoji
121
+ // presentation. emoji-regex matches BOTH categories, so counting every
122
+ // match as 2 desyncs the layout from the terminal: the spinner glyph ✳
123
+ // measured 2 but painted 1, shifting the whole spinner row 1 column every
124
+ // time the animation cycle hit it — leaving stray cells ("tthinking",
125
+ // orphan "t" residue). VS16 sequences fall through to width 2 below.
126
+ if (!grapheme.includes('\ufe0f')) {
127
+ let codepoints = 0;
128
+ for (const _ of grapheme)
129
+ codepoints++;
130
+ if (codepoints === 1 && !hasEmojiPresentation(first)) {
131
+ return 1;
132
+ }
133
+ }
117
134
  return 2;
118
135
  }
136
+ /**
137
+ * Emoji_Presentation=Yes codepoints (Unicode emoji-data.txt, merged
138
+ * ranges). These render as wide emoji without VS16; everything else
139
+ * emoji-regex matches is text-default and renders narrow.
140
+ */
141
+ const EMOJI_PRESENTATION_RANGES = [
142
+ [0x231a, 0x231b], [0x23e9, 0x23ec], 0x23f0, 0x23f3, [0x25fd, 0x25fe],
143
+ [0x2614, 0x2615], [0x2648, 0x2653], 0x267f, 0x2693, 0x26a1,
144
+ [0x26aa, 0x26ab], [0x26bd, 0x26be], [0x26c4, 0x26c5], 0x26ce, 0x26d4,
145
+ 0x26ea, [0x26f2, 0x26f3], 0x26f5, 0x26fa, 0x26fd, 0x2705,
146
+ [0x270a, 0x270b], 0x2728, 0x274c, 0x274e, [0x2753, 0x2755], 0x2757,
147
+ [0x2795, 0x2797], 0x27b0, 0x27bf, [0x2b1b, 0x2b1c], 0x2b50, 0x2b55,
148
+ 0x1f004, 0x1f0cf, 0x1f18e, [0x1f191, 0x1f19a], [0x1f1e6, 0x1f1ff],
149
+ 0x1f201, 0x1f21a, 0x1f22f, [0x1f232, 0x1f236], [0x1f238, 0x1f23a],
150
+ [0x1f250, 0x1f251], [0x1f300, 0x1f320], [0x1f32d, 0x1f335],
151
+ [0x1f337, 0x1f37c], [0x1f37e, 0x1f393], [0x1f3a0, 0x1f3ca],
152
+ [0x1f3cf, 0x1f3d3], [0x1f3e0, 0x1f3f0], 0x1f3f4, [0x1f3f8, 0x1f43e],
153
+ 0x1f440, [0x1f442, 0x1f4fc], [0x1f4ff, 0x1f53d], [0x1f54b, 0x1f54e],
154
+ [0x1f550, 0x1f567], 0x1f57a, [0x1f595, 0x1f596], 0x1f5a4,
155
+ [0x1f5fb, 0x1f64f], [0x1f680, 0x1f6c5], 0x1f6cc, [0x1f6d0, 0x1f6d2],
156
+ [0x1f6d5, 0x1f6d8], [0x1f6dc, 0x1f6df], [0x1f6eb, 0x1f6ec],
157
+ [0x1f6f4, 0x1f6fc], [0x1f7e0, 0x1f7eb], 0x1f7f0, [0x1f90c, 0x1f93a],
158
+ [0x1f93c, 0x1f945], [0x1f947, 0x1f9ff], [0x1fa70, 0x1fa7c],
159
+ [0x1fa80, 0x1fa8a], [0x1fa8e, 0x1fac6], 0x1fac8, [0x1facd, 0x1fadc],
160
+ [0x1fadf, 0x1faea], [0x1faef, 0x1faf8],
161
+ ];
162
+ /** Binary search over the merged, sorted Emoji_Presentation ranges. */
163
+ function hasEmojiPresentation(cp) {
164
+ let lo = 0;
165
+ let hi = EMOJI_PRESENTATION_RANGES.length - 1;
166
+ while (lo <= hi) {
167
+ const mid = (lo + hi) >> 1;
168
+ const entry = EMOJI_PRESENTATION_RANGES[mid];
169
+ const start = typeof entry === 'number' ? entry : entry[0];
170
+ const end = typeof entry === 'number' ? entry : entry[1];
171
+ if (cp < start)
172
+ hi = mid - 1;
173
+ else if (cp > end)
174
+ lo = mid + 1;
175
+ else
176
+ return true;
177
+ }
178
+ return false;
179
+ }
119
180
  function isZeroWidth(codePoint) {
120
181
  // Fast path for common printable range
121
182
  if (codePoint >= 0x20 && codePoint < 0x7f)
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAkBnC;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwSvE;AAyGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG;IACjF,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,YAAY,EAAE,MAAM,IAAI,CAAA;CACzB,CAcA"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAmBnC;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuTvE;AAyGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG;IACjF,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,YAAY,EAAE,MAAM,IAAI,CAAA;CACzB,CAcA"}
@@ -7,6 +7,7 @@ import { createChannel } from './channel.js';
7
7
  import { createChildStderrReporter, installChildStderrGuard } from './childStderr.js';
8
8
  import { logForDebugging } from './utils/debug.js';
9
9
  import { QuestionStore } from './questions.js';
10
+ import { ApprovalStore } from './approvals.js';
10
11
  import { registerPackagedSkills } from './packaged-skills.js';
11
12
  import { readActivityFrames } from './activityPrefs.js';
12
13
  import { readModelPref } from './modelPrefs.js';
@@ -143,6 +144,19 @@ export async function apply(ctx, config) {
143
144
  agentPreset,
144
145
  handle,
145
146
  });
147
+ // DSH approval seam: the permission layer asks ApprovalService.request(),
148
+ // which dispatches an `approval/request` waterfall. With no answerer the
149
+ // chain falls through to the fail-closed 'unavailable', so register this
150
+ // TUI as the interactive answerer for the agent it owns; requests for
151
+ // other agents delegate down the chain (next()). Guarded on the service
152
+ // being mounted — a bare composition without the dsh-base approval row
153
+ // has nothing to answer into. channel.agentId tracks agent swaps
154
+ // (/new, /resume, rewind), so ownership is re-evaluated per request.
155
+ const approvalStore = new ApprovalStore();
156
+ if (ctx.get('approval') !== undefined) {
157
+ ctx.on('approval/request', (req, next) => String(req.agent.id) === channel.agentId ? approvalStore.park(req) : next());
158
+ ctx.effect(() => () => approvalStore.settleAll('cancelled'));
159
+ }
146
160
  // Attach the stderr reporter to the live channel and flush anything a
147
161
  // startup-spawned server produced while the channel didn't exist yet.
148
162
  notifyStderr = (text, options) => channel.notify(text, options);
@@ -241,6 +255,7 @@ export async function apply(ctx, config) {
241
255
  const chat = React.createElement(Chat, {
242
256
  channel,
243
257
  questionStore,
258
+ approvalStore,
244
259
  onExit: () => handleExit(),
245
260
  // Only a `dsh --profile <name>` launch has a profile installation for
246
261
  // `/update` to act on; source checkouts and `--config` overlays get the
@@ -92,6 +92,9 @@ export declare class QuestionStore {
92
92
  cancelCurrent(): void;
93
93
  /** Reject the active and all queued asks (plugin teardown). */
94
94
  rejectAll(): void;
95
+ /** User-initiated cancel — the asker learns the user wants to speak. */
96
+ private cancel;
97
+ /** Harness-side interruption — abort signal fired or plugin teardown. */
95
98
  private fail;
96
99
  }
97
100
  //# sourceMappingURL=questions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"questions.d.ts","sourceRoot":"","sources":["../../src/questions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAEL,KAAK,qBAAqB,EAE1B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAA;AAExC;mEACmE;AACnE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAgBD,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAA;IACtC,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,oCAAoC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;AA0BD;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAC9C,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,QAAQ,CAAI;IACpB,yEAAyE;IACzE,OAAO,CAAC,SAAS,CAAwB;IACzC;;;OAGG;IACH,OAAO,CAAC,aAAa,CAAgC;IAErD;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAO3C;;;OAGG;IACH,WAAW,IAAI,gBAAgB,GAAG,IAAI;IAItC;;;OAGG;IACH,aAAa,IAAI,eAAe,EAAE;IAMlC,OAAO,CAAC,IAAI;IAIZ,sEAAsE;IACtE,OAAO,CAAC,eAAe;IAgBvB;;;;;;OAMG;IACH,GAAG,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA6BpE,8CAA8C;IAC9C,OAAO,CAAC,SAAS;IAOjB;;;;OAIG;IACH,aAAa,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAwBjD,6DAA6D;IAC7D,aAAa,IAAI,IAAI;IAUrB,+DAA+D;IAC/D,SAAS,IAAI,IAAI;IASjB,OAAO,CAAC,IAAI;CAMb"}
1
+ {"version":3,"file":"questions.d.ts","sourceRoot":"","sources":["../../src/questions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAEL,KAAK,qBAAqB,EAE1B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAA;AAExC;mEACmE;AACnE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAgBD,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAA;IACtC,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,oCAAoC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;AAiCD;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAC9C,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,QAAQ,CAAI;IACpB,yEAAyE;IACzE,OAAO,CAAC,SAAS,CAAwB;IACzC;;;OAGG;IACH,OAAO,CAAC,aAAa,CAAgC;IAErD;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAO3C;;;OAGG;IACH,WAAW,IAAI,gBAAgB,GAAG,IAAI;IAItC;;;OAGG;IACH,aAAa,IAAI,eAAe,EAAE;IAMlC,OAAO,CAAC,IAAI;IAIZ,sEAAsE;IACtE,OAAO,CAAC,eAAe;IAgBvB;;;;;;OAMG;IACH,GAAG,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA6BpE,8CAA8C;IAC9C,OAAO,CAAC,SAAS;IAOjB;;;;OAIG;IACH,aAAa,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAwBjD,6DAA6D;IAC7D,aAAa,IAAI,IAAI;IAUrB,+DAA+D;IAC/D,SAAS,IAAI,IAAI;IASjB,wEAAwE;IACxE,OAAO,CAAC,MAAM;IAOd,yEAAyE;IACzE,OAAO,CAAC,IAAI;CAMb"}