@toddzheng024/dscode-bundle 0.7.11 → 0.7.13

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 (154) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -3
  2. package/cordis.patch.yml +1 -1
  3. package/package.json +5 -4
  4. package/plugins/i18n/messages.mjs +6 -6
  5. package/plugins/session-metrics/view.mjs +16 -5
  6. package/plugins/tui-tools/index.mjs +14 -1
  7. package/vendor/tui/lib/app.mjs +6674 -0
  8. package/vendor/tui/lib/approval.mjs +122 -0
  9. package/vendor/tui/lib/attachments.mjs +218 -0
  10. package/vendor/tui/lib/authorization-panel.mjs +242 -0
  11. package/vendor/tui/lib/authorization.mjs +111 -0
  12. package/vendor/tui/lib/commands.mjs +123 -0
  13. package/vendor/tui/lib/dscode/chat.mjs +64 -0
  14. package/vendor/tui/lib/dscode/flags.mjs +32 -0
  15. package/vendor/tui/lib/dscode/model-search.mjs +68 -0
  16. package/vendor/tui/lib/dscode/paste.mjs +87 -0
  17. package/vendor/tui/lib/dscode/telemetry.mjs +82 -0
  18. package/vendor/tui/lib/dscode/welcome.mjs +100 -0
  19. package/vendor/tui/lib/editor-keys.mjs +346 -0
  20. package/vendor/tui/lib/editor.mjs +50 -0
  21. package/vendor/tui/lib/fork.mjs +24 -0
  22. package/vendor/tui/lib/git-workflow.mjs +278 -0
  23. package/vendor/tui/{types/history.d.ts → lib/history.mjs} +69 -26
  24. package/vendor/tui/{types/i18n.d.ts → lib/i18n.mjs} +23 -15
  25. package/vendor/tui/lib/index.mjs +2198 -0
  26. package/vendor/tui/lib/input-split.mjs +170 -0
  27. package/vendor/tui/lib/internals.mjs +90 -0
  28. package/vendor/tui/lib/invariant.mjs +22 -0
  29. package/vendor/tui/lib/kernel-panels.mjs +1265 -0
  30. package/vendor/tui/lib/keyboard.mjs +283 -0
  31. package/vendor/tui/lib/language-panel.mjs +41 -0
  32. package/vendor/tui/lib/locales/en.mjs +495 -0
  33. package/vendor/tui/lib/locales/zh.mjs +495 -0
  34. package/vendor/tui/lib/mentions.mjs +150 -0
  35. package/vendor/tui/lib/model-capabilities.mjs +245 -0
  36. package/vendor/tui/lib/models.mjs +215 -0
  37. package/vendor/tui/{types/panel-accent.d.ts → lib/panel-accent.mjs} +11 -7
  38. package/vendor/tui/lib/permissions.mjs +45 -0
  39. package/vendor/tui/lib/plugin-inventory.mjs +27 -0
  40. package/vendor/tui/lib/presets.mjs +46 -0
  41. package/vendor/tui/lib/provider-settings.mjs +600 -0
  42. package/vendor/tui/lib/questions.mjs +116 -0
  43. package/vendor/tui/lib/rainbow.mjs +181 -0
  44. package/vendor/tui/lib/render/animations.mjs +665 -0
  45. package/vendor/tui/lib/render/editor.mjs +437 -0
  46. package/vendor/tui/lib/render/export.mjs +119 -0
  47. package/vendor/tui/lib/render/fuzzy.mjs +79 -0
  48. package/vendor/tui/lib/render/ime-cursor.mjs +135 -0
  49. package/vendor/tui/lib/render/inspector.mjs +93 -0
  50. package/vendor/tui/lib/render/lines.mjs +538 -0
  51. package/vendor/tui/lib/render/markdown.mjs +592 -0
  52. package/vendor/tui/lib/render/projection.mjs +1862 -0
  53. package/vendor/tui/lib/render/status.mjs +591 -0
  54. package/vendor/tui/lib/render/text.mjs +157 -0
  55. package/vendor/tui/lib/render/tool-detail.mjs +187 -0
  56. package/vendor/tui/lib/render/tool-preview.mjs +79 -0
  57. package/vendor/tui/lib/render/usage.mjs +336 -0
  58. package/vendor/tui/lib/render/width.mjs +191 -0
  59. package/vendor/tui/lib/session-directory.mjs +329 -0
  60. package/vendor/tui/lib/session-query.mjs +184 -0
  61. package/vendor/tui/lib/session-switch.mjs +51 -0
  62. package/vendor/tui/lib/settings-file.mjs +75 -0
  63. package/vendor/tui/lib/skills.mjs +119 -0
  64. package/vendor/tui/lib/startup.mjs +111 -0
  65. package/vendor/tui/lib/store.mjs +129 -0
  66. package/vendor/tui/lib/subagents.mjs +200 -0
  67. package/vendor/tui/lib/terminal-title.mjs +186 -0
  68. package/vendor/tui/lib/theme-panel.mjs +60 -0
  69. package/vendor/tui/lib/theme.mjs +379 -0
  70. package/vendor/tui/lib/update-panel.mjs +239 -0
  71. package/vendor/tui/lib/update.mjs +101 -0
  72. package/vendor/tui/lib/version.mjs +105 -0
  73. package/vendor/tui/lib/whale-glyph.mjs +20 -0
  74. package/vendor/tui/LICENSE +0 -21
  75. package/vendor/tui/devtools-CdTl3MNy.mjs +0 -3643
  76. package/vendor/tui/dscode-email/cli.mjs +0 -32
  77. package/vendor/tui/dscode-email/contacts.mjs +0 -36
  78. package/vendor/tui/dscode-email/gmail-oauth.mjs +0 -69
  79. package/vendor/tui/dscode-email/gmail-store.mjs +0 -2
  80. package/vendor/tui/dscode-email/gmail.mjs +0 -194
  81. package/vendor/tui/dscode-email/imap.mjs +0 -136
  82. package/vendor/tui/dscode-email/inbox.d.mts +0 -25
  83. package/vendor/tui/dscode-email/inbox.mjs +0 -76
  84. package/vendor/tui/dscode-email/smtp.mjs +0 -69
  85. package/vendor/tui/dscode-email/store.mjs +0 -32
  86. package/vendor/tui/dscode-providers/catalog.mjs +0 -136
  87. package/vendor/tui/dscode-providers/effort.mjs +0 -35
  88. package/vendor/tui/dscode-providers/openrouter-account.mjs +0 -171
  89. package/vendor/tui/index.mjs +0 -44483
  90. package/vendor/tui/invariant.mjs +0 -21
  91. package/vendor/tui/rolldown-runtime-CMFfr-1z.mjs +0 -26
  92. package/vendor/tui/session-query.mjs +0 -150
  93. package/vendor/tui/startup.mjs +0 -109
  94. package/vendor/tui/theme-7u5Qo3dF.mjs +0 -1265
  95. package/vendor/tui/types/app.d.ts +0 -348
  96. package/vendor/tui/types/approval.d.ts +0 -59
  97. package/vendor/tui/types/attachments.d.ts +0 -52
  98. package/vendor/tui/types/authorization-panel.d.ts +0 -22
  99. package/vendor/tui/types/authorization.d.ts +0 -36
  100. package/vendor/tui/types/commands.d.ts +0 -52
  101. package/vendor/tui/types/editor-keys.d.ts +0 -105
  102. package/vendor/tui/types/editor.d.ts +0 -6
  103. package/vendor/tui/types/fork.d.ts +0 -8
  104. package/vendor/tui/types/git-workflow.d.ts +0 -121
  105. package/vendor/tui/types/index.d.ts +0 -223
  106. package/vendor/tui/types/input-split.d.ts +0 -54
  107. package/vendor/tui/types/internals.d.ts +0 -26
  108. package/vendor/tui/types/invariant.d.ts +0 -15
  109. package/vendor/tui/types/kernel-panels.d.ts +0 -245
  110. package/vendor/tui/types/keyboard.d.ts +0 -80
  111. package/vendor/tui/types/language-panel.d.ts +0 -12
  112. package/vendor/tui/types/locales/en.d.ts +0 -450
  113. package/vendor/tui/types/locales/zh.d.ts +0 -9
  114. package/vendor/tui/types/mentions.d.ts +0 -85
  115. package/vendor/tui/types/model-capabilities.d.ts +0 -82
  116. package/vendor/tui/types/models.d.ts +0 -133
  117. package/vendor/tui/types/permissions.d.ts +0 -27
  118. package/vendor/tui/types/plugin-inventory.d.ts +0 -11
  119. package/vendor/tui/types/presets.d.ts +0 -22
  120. package/vendor/tui/types/provider-settings.d.ts +0 -239
  121. package/vendor/tui/types/questions.d.ts +0 -54
  122. package/vendor/tui/types/rainbow.d.ts +0 -69
  123. package/vendor/tui/types/render/animations.d.ts +0 -307
  124. package/vendor/tui/types/render/editor.d.ts +0 -163
  125. package/vendor/tui/types/render/export.d.ts +0 -9
  126. package/vendor/tui/types/render/fuzzy.d.ts +0 -21
  127. package/vendor/tui/types/render/ime-cursor.d.ts +0 -60
  128. package/vendor/tui/types/render/inspector.d.ts +0 -62
  129. package/vendor/tui/types/render/lines.d.ts +0 -86
  130. package/vendor/tui/types/render/markdown.d.ts +0 -29
  131. package/vendor/tui/types/render/projection.d.ts +0 -587
  132. package/vendor/tui/types/render/status.d.ts +0 -196
  133. package/vendor/tui/types/render/text.d.ts +0 -64
  134. package/vendor/tui/types/render/tool-detail.d.ts +0 -94
  135. package/vendor/tui/types/render/tool-preview.d.ts +0 -28
  136. package/vendor/tui/types/render/usage.d.ts +0 -113
  137. package/vendor/tui/types/render/width.d.ts +0 -29
  138. package/vendor/tui/types/session-directory.d.ts +0 -190
  139. package/vendor/tui/types/session-query.d.ts +0 -92
  140. package/vendor/tui/types/session-switch.d.ts +0 -17
  141. package/vendor/tui/types/settings-file.d.ts +0 -41
  142. package/vendor/tui/types/skills.d.ts +0 -47
  143. package/vendor/tui/types/startup.d.ts +0 -65
  144. package/vendor/tui/types/store.d.ts +0 -58
  145. package/vendor/tui/types/subagents.d.ts +0 -70
  146. package/vendor/tui/types/terminal-title.d.ts +0 -66
  147. package/vendor/tui/types/theme-panel.d.ts +0 -24
  148. package/vendor/tui/types/theme.d.ts +0 -434
  149. package/vendor/tui/types/update-panel.d.ts +0 -49
  150. package/vendor/tui/types/update.d.ts +0 -75
  151. package/vendor/tui/types/version.d.ts +0 -19
  152. package/vendor/tui/types/whale-glyph.d.ts +0 -6
  153. /package/vendor/tui/{dscode-clipboard-image → lib/dscode/clipboard-image}/clipboard-image.swift +0 -0
  154. /package/vendor/tui/{dscode-clipboard-image → lib/dscode/clipboard-image}/index.mjs +0 -0
@@ -0,0 +1,346 @@
1
+ /**
2
+ * VS Code-family terminal keybinding repair. VS Code hands Ctrl+R to the
3
+ * workbench (Open Recent) even while an integrated terminal owns focus, so
4
+ * the reasoning-fold key never reaches the TUI. Workspace-scoped keybindings
5
+ * do not exist, so the fix is one user-level keybindings.json rule that
6
+ * forwards the raw Ctrl byte via sendSequence under terminalFocus. This
7
+ * module detects the hosting editor variant, resolves its user
8
+ * keybindings.json, and merges the rule idempotently; pure merge/detect
9
+ * helpers are separated from the fs orchestration so both stay testable.
10
+ * @module @deepseek-ai/dsh-code/editor-keys
11
+ */
12
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
13
+ import { basename, dirname, join } from 'node:path';
14
+ /** Install directory names per family ('vscode' covers stable and Insiders). */
15
+ const FAMILY_DIRS = {
16
+ vscode: ['Code', 'Code - Insiders'],
17
+ cursor: ['Cursor'],
18
+ vscodium: ['VSCodium'],
19
+ windsurf: ['Windsurf'],
20
+ };
21
+ /**
22
+ * Detect the editor hosting this integrated terminal.
23
+ * @param env - process environment (TERM_PROGRAM decides; case/whitespace tolerant).
24
+ * @returns the family, or undefined outside VS Code-family terminals.
25
+ */
26
+ export function detectEditorTerminalFamily(env = process.env) {
27
+ const program = env.TERM_PROGRAM?.trim().toLowerCase();
28
+ if (program === 'vscode' || program === 'cursor' || program === 'vscodium' || program === 'windsurf')
29
+ return program;
30
+ return undefined;
31
+ }
32
+ /**
33
+ * Whether the pty is hosted away from the editor UI (ssh/container/tunnel).
34
+ * Keybindings live on the client machine, so a remote session must never
35
+ * write them server-side.
36
+ */
37
+ export function isRemoteTerminalEnv(env = process.env) {
38
+ return (env.VSCODE_IPC_HOOK_CLI ?? '').trim() !== '';
39
+ }
40
+ /**
41
+ * Resolve the user keybindings.json candidates for one family, most likely
42
+ * install first. Only paths that exist on disk are repaired.
43
+ */
44
+ export function editorKeybindingCandidates(family, context) {
45
+ return FAMILY_DIRS[family].map(dir => {
46
+ if (context.platform === 'win32') {
47
+ return join(context.appdata ?? join(context.homedir, 'AppData', 'Roaming'), dir, 'User', 'keybindings.json');
48
+ }
49
+ if (context.platform === 'darwin') {
50
+ return join(context.homedir, 'Library', 'Application Support', dir, 'User', 'keybindings.json');
51
+ }
52
+ return join(context.homedir, '.config', dir, 'User', 'keybindings.json');
53
+ });
54
+ }
55
+ /** The one workbench rule that hands Ctrl+R to the focused terminal. */
56
+ export const CTRL_R_PASSTHROUGH_RULE = {
57
+ key: 'ctrl+r',
58
+ command: 'workbench.action.terminal.sendSequence',
59
+ args: { text: '\u0012' },
60
+ when: 'terminalFocus',
61
+ };
62
+ /** Serialized rule block (4-space indent, the editors' default style). */
63
+ const RULE_BLOCK = [
64
+ ' {',
65
+ ' "key": "ctrl+r",',
66
+ ' "command": "workbench.action.terminal.sendSequence",',
67
+ ' "args": { "text": "\\u0012" },',
68
+ ' "when": "terminalFocus"',
69
+ ' }',
70
+ ].join('\n');
71
+ /** Fresh-file template carrying the editors' standard header comment. */
72
+ const KEYBINDINGS_TEMPLATE = '// Place your key bindings in this file to override the defaults\n[\n' + RULE_BLOCK + '\n]\n';
73
+ /** Whether one parsed keybindings entry already forwards Ctrl+R to the terminal. */
74
+ function isCtrlRPassthroughEntry(entry) {
75
+ if (typeof entry !== 'object' || entry === null)
76
+ return false;
77
+ const record = entry;
78
+ if (record.command !== CTRL_R_PASSTHROUGH_RULE.command)
79
+ return false;
80
+ if (typeof record.key !== 'string' || record.key.trim().toLowerCase() !== CTRL_R_PASSTHROUGH_RULE.key)
81
+ return false;
82
+ const args = record.args;
83
+ if (typeof args !== 'object' || args === null)
84
+ return false;
85
+ const text = args.text;
86
+ if (typeof text !== 'string' || !text.includes('\u0012'))
87
+ return false;
88
+ return typeof record.when === 'string' && /\bterminalFocus\b/u.test(record.when);
89
+ }
90
+ /**
91
+ * Remove // and block comments from one JSONC document. Double-quoted strings
92
+ * survive untouched, so comment markers inside string values are preserved.
93
+ */
94
+ export function stripJsoncComments(text) {
95
+ let out = '';
96
+ let index = 0;
97
+ let inString = false;
98
+ while (index < text.length) {
99
+ const char = text[index];
100
+ if (inString) {
101
+ out += char;
102
+ if (char === '\\' && index + 1 < text.length) {
103
+ out += text[index + 1];
104
+ index += 2;
105
+ continue;
106
+ }
107
+ if (char === '"')
108
+ inString = false;
109
+ index += 1;
110
+ continue;
111
+ }
112
+ if (char === '"') {
113
+ inString = true;
114
+ out += char;
115
+ index += 1;
116
+ continue;
117
+ }
118
+ if (char === '/' && text[index + 1] === '/') {
119
+ while (index < text.length && text[index] !== '\n')
120
+ index += 1;
121
+ continue;
122
+ }
123
+ if (char === '/' && text[index + 1] === '*') {
124
+ index += 2;
125
+ while (index < text.length && !(text[index] === '*' && text[index + 1] === '/')) {
126
+ if (text[index] === '\n')
127
+ out += '\n';
128
+ index += 1;
129
+ }
130
+ index += 2;
131
+ continue;
132
+ }
133
+ out += char;
134
+ index += 1;
135
+ }
136
+ return out;
137
+ }
138
+ /** Drop commas directly before a closing bracket (string-aware). */
139
+ function removeTrailingCommas(text) {
140
+ let out = '';
141
+ let index = 0;
142
+ let inString = false;
143
+ while (index < text.length) {
144
+ const char = text[index];
145
+ if (inString) {
146
+ out += char;
147
+ if (char === '\\' && index + 1 < text.length) {
148
+ out += text[index + 1];
149
+ index += 2;
150
+ continue;
151
+ }
152
+ if (char === '"')
153
+ inString = false;
154
+ index += 1;
155
+ continue;
156
+ }
157
+ if (char === '"') {
158
+ inString = true;
159
+ out += char;
160
+ index += 1;
161
+ continue;
162
+ }
163
+ if (char === ',') {
164
+ let peek = index + 1;
165
+ while (peek < text.length && (text[peek] === ' ' || text[peek] === '\t' || text[peek] === '\n' || text[peek] === '\r'))
166
+ peek += 1;
167
+ const next = text[peek];
168
+ if (next === '}' || next === ']') {
169
+ index += 1;
170
+ continue;
171
+ }
172
+ }
173
+ out += char;
174
+ index += 1;
175
+ }
176
+ return out;
177
+ }
178
+ /** Parse one JSONC document; trailing commas are tolerated. */
179
+ export function parseJsonc(text) {
180
+ return JSON.parse(removeTrailingCommas(stripJsoncComments(text)));
181
+ }
182
+ /** Raw index of the top-level rule array's opening bracket, or -1 when absent. */
183
+ function rawOpenBracketIndex(text) {
184
+ let index = 0;
185
+ let inString = false;
186
+ while (index < text.length) {
187
+ const char = text[index];
188
+ if (inString) {
189
+ if (char === '\\') {
190
+ index += 2;
191
+ continue;
192
+ }
193
+ if (char === '"')
194
+ inString = false;
195
+ index += 1;
196
+ continue;
197
+ }
198
+ if (char === '"') {
199
+ inString = true;
200
+ index += 1;
201
+ continue;
202
+ }
203
+ if (char === '/' && text[index + 1] === '/') {
204
+ while (index < text.length && text[index] !== '\n')
205
+ index += 1;
206
+ continue;
207
+ }
208
+ if (char === '/' && text[index + 1] === '*') {
209
+ index += 2;
210
+ while (index < text.length && !(text[index] === '*' && text[index + 1] === '/'))
211
+ index += 1;
212
+ index += 2;
213
+ continue;
214
+ }
215
+ if (char === '[')
216
+ return index;
217
+ index += 1;
218
+ }
219
+ return -1;
220
+ }
221
+ /**
222
+ * Merge the Ctrl+R passthrough into one keybindings.json document. The raw
223
+ * text is preserved verbatim (comments included); the rule is inserted right
224
+ * after the array opener so it cannot be shadowed by later conflicting user
225
+ * rules. Missing files resolve to a fresh template.
226
+ * @throws when the document does not carry a rule array.
227
+ */
228
+ export function mergeCtrlRPassthrough(raw) {
229
+ if (raw === undefined)
230
+ return { status: 'created', text: KEYBINDINGS_TEMPLATE };
231
+ const parsed = parseJsonc(raw);
232
+ if (!Array.isArray(parsed))
233
+ throw new Error('keybindings.json does not contain a rule array');
234
+ if (parsed.some(isCtrlRPassthroughEntry))
235
+ return { status: 'present' };
236
+ const open = rawOpenBracketIndex(raw);
237
+ if (open === -1)
238
+ throw new Error('keybindings.json does not contain a rule array');
239
+ const insert = parsed.length > 0 ? '\n' + RULE_BLOCK + ',' : '\n' + RULE_BLOCK;
240
+ return { status: 'updated', text: raw.slice(0, open + 1) + insert + raw.slice(open + 1) };
241
+ }
242
+ /** Parse one flag file snapshot; missing or corrupt content degrades to unshown. */
243
+ export function parseEditorKeysFlag(raw) {
244
+ if (raw === undefined)
245
+ return {};
246
+ try {
247
+ const parsed = JSON.parse(raw);
248
+ if (typeof parsed !== 'object' || parsed === null)
249
+ return {};
250
+ const shown = parsed.hintShownAt;
251
+ return typeof shown === 'string' ? { hintShownAt: shown } : {};
252
+ }
253
+ catch {
254
+ return {};
255
+ }
256
+ }
257
+ /** Persist the shown marker; best-effort, the hint is cosmetic and never a gate. */
258
+ export async function markEditorKeysHintShown(path) {
259
+ await mkdir(dirname(path), { recursive: true });
260
+ await writeFile(path, JSON.stringify({ hintShownAt: new Date().toISOString() }, null, 2) + '\n', 'utf8');
261
+ }
262
+ /** Read one file if it exists; undefined otherwise (ENOENT and unreadable both). */
263
+ async function readIfPresent(path) {
264
+ try {
265
+ return await readFile(path, 'utf8');
266
+ }
267
+ catch {
268
+ return undefined;
269
+ }
270
+ }
271
+ /**
272
+ * Apply the Ctrl+R passthrough to every local keybindings.json of the hosting
273
+ * editor and mark the startup hint shown. Existing files get a .dsh-bak
274
+ * backup before the first write.
275
+ * @returns a one-line user-facing summary.
276
+ * @throws with an actionable message when the environment cannot be repaired.
277
+ */
278
+ export async function applyCtrlRPassthrough({ env, paths, flagPath }) {
279
+ const family = detectEditorTerminalFamily(env);
280
+ if (family === undefined) {
281
+ throw new Error('not a VS Code-family terminal (TERM_PROGRAM=' + (env.TERM_PROGRAM?.trim() || 'unset') + '); add the ctrl+r rule to keybindings.json manually');
282
+ }
283
+ if (isRemoteTerminalEnv(env)) {
284
+ throw new Error('remote terminal detected; apply the keybindings rule on the local machine instead');
285
+ }
286
+ const candidates = editorKeybindingCandidates(family, paths);
287
+ const targets = [];
288
+ for (const candidate of candidates) {
289
+ if (await readIfPresent(candidate) !== undefined)
290
+ targets.push(candidate);
291
+ }
292
+ if (targets.length === 0)
293
+ targets.push(candidates[0]);
294
+ const updated = [];
295
+ const present = [];
296
+ for (const target of targets) {
297
+ const raw = await readIfPresent(target);
298
+ const merge = mergeCtrlRPassthrough(raw);
299
+ if (merge.status === 'present') {
300
+ present.push(target);
301
+ continue;
302
+ }
303
+ await mkdir(dirname(target), { recursive: true });
304
+ if (raw !== undefined)
305
+ await writeFile(target + '.dsh-bak', raw, 'utf8');
306
+ await writeFile(target, merge.text, 'utf8');
307
+ updated.push(target);
308
+ }
309
+ await markEditorKeysHintShown(flagPath).catch(() => { });
310
+ const label = (target) => basename(dirname(dirname(target)));
311
+ if (updated.length === 0) {
312
+ return 'ctrl+r passthrough already configured in ' + present.map(label).join(', ');
313
+ }
314
+ return 'ctrl+r passthrough written to ' + updated.map(label).join(', ') + ' — effective immediately';
315
+ }
316
+ /**
317
+ * Resolve the one-shot startup hint for VS Code-family terminals. Fires at
318
+ * most once per install (flag file), never when the passthrough rule is
319
+ * already present, and never in remote ptys where the repair cannot run.
320
+ * @returns the hint line, or undefined to stay silent.
321
+ */
322
+ export async function resolveEditorKeysStartupHint({ env, paths, flagPath }) {
323
+ const flag = parseEditorKeysFlag(await readIfPresent(flagPath));
324
+ if (flag.hintShownAt !== undefined)
325
+ return undefined;
326
+ const family = detectEditorTerminalFamily(env);
327
+ if (family === undefined || isRemoteTerminalEnv(env))
328
+ return undefined;
329
+ for (const candidate of editorKeybindingCandidates(family, paths)) {
330
+ const raw = await readIfPresent(candidate);
331
+ if (raw === undefined)
332
+ continue;
333
+ try {
334
+ const parsed = parseJsonc(raw);
335
+ if (Array.isArray(parsed) && parsed.some(isCtrlRPassthroughEntry)) {
336
+ await markEditorKeysHintShown(flagPath).catch(() => { });
337
+ return undefined;
338
+ }
339
+ }
340
+ catch {
341
+ // An unparseable config still deserves the hint; apply reports the error.
342
+ }
343
+ }
344
+ await markEditorKeysHintShown(flagPath).catch(() => { });
345
+ return 'run /vscode-keys to pass ctrl+r through this editor (alt+r works meanwhile)';
346
+ }
@@ -0,0 +1,50 @@
1
+ /** Host editor and clipboard adapters used by the terminal surface. */
2
+ import { spawn } from 'node:child_process';
3
+ function waitForProcess(command, args, input) {
4
+ return new Promise((resolve, reject) => {
5
+ const child = spawn(command, [...args], {
6
+ stdio: input === undefined ? 'inherit' : ['pipe', 'ignore', 'pipe'],
7
+ windowsHide: true,
8
+ });
9
+ let stderr = '';
10
+ child.stderr?.on('data', chunk => { stderr += String(chunk); });
11
+ child.once('error', reject);
12
+ child.once('exit', code => {
13
+ if (code === 0)
14
+ resolve();
15
+ else
16
+ reject(new Error(`${command} exited with code ${String(code)}${stderr.trim() === '' ? '' : `: ${stderr.trim()}`}`));
17
+ });
18
+ if (input !== undefined)
19
+ child.stdin?.end(input);
20
+ });
21
+ }
22
+ /** Copy UTF-8 text through the platform clipboard command. */
23
+ export async function copyText(text) {
24
+ if (process.platform === 'win32') {
25
+ // Windows PowerShell 5 reads redirected stdin using the active console
26
+ // code page by default. Node writes UTF-8, so CJK copied through `$input`
27
+ // became mojibake. Set the pipe encoding before reading it.
28
+ await waitForProcess('powershell.exe', [
29
+ '-NoProfile',
30
+ '-NonInteractive',
31
+ '-Command',
32
+ '[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false); Set-Clipboard -Value ([Console]::In.ReadToEnd())',
33
+ ], text);
34
+ return;
35
+ }
36
+ if (process.platform === 'darwin') {
37
+ await waitForProcess('pbcopy', [], text);
38
+ return;
39
+ }
40
+ await waitForProcess('xclip', ['-selection', 'clipboard'], text);
41
+ }
42
+ /** Latest complete assistant text, excluding streaming and reasoning. */
43
+ export function latestAssistantText(view) {
44
+ for (let index = view.entries.length - 1; index >= 0; index -= 1) {
45
+ const entry = view.entries[index];
46
+ if (entry?.kind === 'assistant' && entry.text !== '')
47
+ return entry.text;
48
+ }
49
+ return undefined;
50
+ }
@@ -0,0 +1,24 @@
1
+ /** Pure session-fork boundary policy shared by the TUI command and tests. */
2
+ /** Select a completed turn and trailing between-turn metadata. */
3
+ export function selectForkSeed(events, atSeq) {
4
+ if (atSeq !== undefined && (!Number.isSafeInteger(atSeq) || atSeq < 0)) {
5
+ throw new Error('fork event sequence must be a non-negative integer');
6
+ }
7
+ const lastSeq = events.at(-1)?.seq ?? -1;
8
+ const anchored = atSeq === undefined
9
+ ? undefined
10
+ : events.find(event => event.type === 'turn/end' && event.seq >= atSeq);
11
+ const boundary = anchored
12
+ ?? (atSeq === undefined || atSeq > lastSeq
13
+ ? events.findLast(event => event.type === 'turn/end')
14
+ : undefined);
15
+ if (boundary === undefined) {
16
+ throw new Error(atSeq !== undefined && atSeq <= lastSeq
17
+ ? `the turn containing event ${atSeq} has not completed`
18
+ : 'this session has no completed turn to fork from');
19
+ }
20
+ let cut = events.indexOf(boundary) + 1;
21
+ while (cut < events.length && events[cut]?.type !== 'turn/start')
22
+ cut += 1;
23
+ return { boundarySeq: boundary.seq, events: events.slice(0, cut) };
24
+ }