dsh-coding-sidebar 1.0.8 → 1.0.10

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 (58) hide show
  1. package/README.md +2 -2
  2. package/lib/client-editor.js +257 -178
  3. package/lib/client-registry.js +959 -345
  4. package/lib/client-terminal.js +307 -172
  5. package/lib/client.js +952 -338
  6. package/lib/index.js +214 -2
  7. package/lib/types/changes-ops.d.ts +31 -0
  8. package/lib/types/client/DiffView.d.ts +33 -1
  9. package/lib/types/client/EditorHost.d.ts +3 -0
  10. package/lib/types/client/FileTree.d.ts +4 -0
  11. package/lib/types/client/SessionLens.d.ts +4 -0
  12. package/lib/types/client/TerminalWaitBanner.d.ts +6 -0
  13. package/lib/types/client/TreePanel.d.ts +3 -0
  14. package/lib/types/client/api.d.ts +36 -0
  15. package/lib/types/client/locales.d.ts +20 -0
  16. package/lib/types/client/redact.d.ts +14 -0
  17. package/lib/types/client/state.d.ts +15 -0
  18. package/lib/types/fs-operations.d.ts +42 -0
  19. package/lib/types/git.d.ts +15 -0
  20. package/package.json +1 -1
  21. package/src/changes-ops.ts +78 -0
  22. package/src/client/DiffTab.tsx +10 -1
  23. package/src/client/DiffView.tsx +174 -19
  24. package/src/client/EditorHost.tsx +8 -1
  25. package/src/client/FileTree.tsx +147 -4
  26. package/src/client/GitView.tsx +37 -0
  27. package/src/client/SessionLens.tsx +95 -0
  28. package/src/client/Sidebar.tsx +54 -3
  29. package/src/client/TerminalView.tsx +28 -0
  30. package/src/client/TerminalWaitBanner.tsx +32 -0
  31. package/src/client/TreePanel.tsx +6 -1
  32. package/src/client/api.ts +24 -0
  33. package/src/client/locales-ar.ts +20 -0
  34. package/src/client/locales-de.ts +20 -0
  35. package/src/client/locales-fr.ts +20 -0
  36. package/src/client/locales-hi.ts +20 -0
  37. package/src/client/locales-id.ts +20 -0
  38. package/src/client/locales-it.ts +20 -0
  39. package/src/client/locales-ja.ts +20 -0
  40. package/src/client/locales-ko.ts +20 -0
  41. package/src/client/locales-nl.ts +20 -0
  42. package/src/client/locales-pl.ts +20 -0
  43. package/src/client/locales-pt.ts +20 -0
  44. package/src/client/locales-ru.ts +20 -0
  45. package/src/client/locales-sv.ts +20 -0
  46. package/src/client/locales-th.ts +20 -0
  47. package/src/client/locales-tr.ts +20 -0
  48. package/src/client/locales-vi.ts +20 -0
  49. package/src/client/locales-zh-HK.ts +20 -0
  50. package/src/client/locales-zh-MO.ts +20 -0
  51. package/src/client/locales-zh-TW.ts +20 -0
  52. package/src/client/locales.ts +40 -0
  53. package/src/client/redact.ts +39 -0
  54. package/src/client/sidebar.module.css +183 -0
  55. package/src/client/state.ts +42 -3
  56. package/src/fs-operations.ts +126 -4
  57. package/src/git.ts +39 -2
  58. package/src/index.ts +56 -1
@@ -138,6 +138,26 @@ export const pt: Record<string, string> = {
138
138
  produced: 'Produzidos',
139
139
  producedOpen: 'Abrir na barra lateral',
140
140
  disconnected: 'Terminal desconectado, reconectando…',
141
+ terminalWaitBanner: 'O agente está aguardando {needle}',
142
+ terminalSkipWait: 'Pular espera',
143
+ gitFoldExpand: 'Mostrar as {count} linhas de contexto',
144
+ gitFoldLoading: 'A expandir…',
145
+ gitFoldFailed: 'Falha ao expandir',
146
+ rename: 'Renomear',
147
+ renameInvalid: 'O nome não pode estar vazio nem conter separadores de caminho.',
148
+ delete: 'Eliminar',
149
+ deleteTitle: 'Eliminar "{name}"?',
150
+ deleteDescFile: 'Isto elimina definitivamente o ficheiro. Não pode ser anulado.',
151
+ deleteDescDir: 'Isto elimina definitivamente o diretório e todo o seu conteúdo. Não pode ser anulado.',
152
+ dismiss: 'Fechar',
153
+ changesSessionGit: 'Alterações do Git',
154
+ changesSessionLens: 'Alterações da sessão',
155
+ changesEmpty: 'Sem operações de ficheiro nesta sessão',
156
+ changesCount: '{count} operações de ficheiro',
157
+ changesRedacted: '[MASCARADO]',
158
+ changesBinary: 'Ficheiro binário — sem pré-visualização',
159
+ changesPreviewError: 'Falha ao ler a pré-visualização: {message}',
160
+ changesLens: 'Vista',
141
161
  exited: 'O processo do terminal saiu',
142
162
  noSession: 'Selecione uma conversa para usar a barra lateral',
143
163
  pluginNotLoaded: 'Plugin não carregado; aba indisponível:',
@@ -151,6 +151,26 @@ export const ru: Record<string, string> = {
151
151
  produced: 'Результаты',
152
152
  producedOpen: 'Открыть в боковой панели',
153
153
  disconnected: 'Терминал отключён, переподключение…',
154
+ terminalWaitBanner: 'Агент ожидает {needle}',
155
+ terminalSkipWait: 'Пропустить ожидание',
156
+ gitFoldExpand: 'Показать {count} строк контекста',
157
+ gitFoldLoading: 'Развертывание…',
158
+ gitFoldFailed: 'Не удалось развернуть',
159
+ rename: 'Переименовать',
160
+ renameInvalid: 'Имя не может быть пустым или содержать разделители пути.',
161
+ delete: 'Удалить',
162
+ deleteTitle: 'Удалить «{name}»?',
163
+ deleteDescFile: 'Файл будет удалён безвозвратно. Действие необратимо.',
164
+ deleteDescDir: 'Каталог и всё его содержимое будут удалены безвозвратно. Действие необратимо.',
165
+ dismiss: 'Закрыть',
166
+ changesSessionGit: 'Изменения Git',
167
+ changesSessionLens: 'Изменения сессии',
168
+ changesEmpty: 'В этой сессии нет операций с файлами',
169
+ changesCount: 'Операций с файлами: {count}',
170
+ changesRedacted: '[СКРЫТО]',
171
+ changesBinary: 'Двоичный файл — предпросмотр недоступен',
172
+ changesPreviewError: 'Не удалось прочитать предпросмотр: {message}',
173
+ changesLens: 'Вид',
154
174
  exited: 'Процесс терминала завершился',
155
175
  noSession: 'Выберите сессию, чтобы использовать боковую панель',
156
176
  pluginNotLoaded: 'Плагин не загружен — вкладка недоступна:',
@@ -138,6 +138,26 @@ export const sv: Record<string, string> = {
138
138
  produced: 'Producerat',
139
139
  producedOpen: 'Öppna i sidopanelen',
140
140
  disconnected: 'Terminalen frånkopplad, ansluter igen…',
141
+ terminalWaitBanner: 'Agenten väntar på {needle}',
142
+ terminalSkipWait: 'Hoppa över väntan',
143
+ gitFoldExpand: 'Visa {count} sammanhangsrader',
144
+ gitFoldLoading: 'Expanderar…',
145
+ gitFoldFailed: 'Kunde inte expandera',
146
+ rename: 'Byt namn',
147
+ renameInvalid: 'Namnet får inte vara tomt eller innehålla sökvägsavgränsare.',
148
+ delete: 'Ta bort',
149
+ deleteTitle: 'Ta bort "{name}"?',
150
+ deleteDescFile: 'Filen tas bort permanent. Detta kan inte ångras.',
151
+ deleteDescDir: 'Katalogen och allt i den tas bort permanent. Detta kan inte ångras.',
152
+ dismiss: 'Stäng',
153
+ changesSessionGit: 'Git-ändringar',
154
+ changesSessionLens: 'Sessionsändringar',
155
+ changesEmpty: 'Inga filåtgärder i den här sessionen',
156
+ changesCount: '{count} filåtgärder',
157
+ changesRedacted: '[SMETAT]',
158
+ changesBinary: 'Binärfil — ingen förhandsvisning',
159
+ changesPreviewError: 'Kunde inte läsa förhandsvisning: {message}',
160
+ changesLens: 'Vy',
141
161
  exited: 'Terminalprocess avslutad',
142
162
  noSession: 'Välj en konversation för att använda sidopanelen',
143
163
  pluginNotLoaded: 'Plugin inte laddad; flik otillgänglig:',
@@ -155,6 +155,26 @@ export const th: Record<string, string> = {
155
155
  produced: 'ผลลัพธ์ที่สร้าง',
156
156
  producedOpen: 'เปิดในแถบด้านข้าง',
157
157
  disconnected: 'เทอร์มินัลถูกตัดการเชื่อมต่อ กำลังเชื่อมต่อใหม่…',
158
+ terminalWaitBanner: 'เอเจนต์กำลังรอ {needle}',
159
+ terminalSkipWait: 'ข้ามการรอ',
160
+ gitFoldExpand: 'แสดงบริบท {count} บรรทัด',
161
+ gitFoldLoading: 'กำลังขยาย…',
162
+ gitFoldFailed: 'ขยายไม่สำเร็จ',
163
+ rename: 'เปลี่ยนชื่อ',
164
+ renameInvalid: 'ชื่อต้องไม่ว่างและไม่มีอักขระคั่นพาธ',
165
+ delete: 'ลบ',
166
+ deleteTitle: 'ลบ "{name}"?',
167
+ deleteDescFile: 'ไฟล์นี้จะถูกลบอย่างถาวร ย้อนกลับไม่ได้',
168
+ deleteDescDir: 'ไดเรกทอรีนี้และเนื้อหาทั้งหมดจะถูกลบอย่างถาวร ย้อนกลับไม่ได้',
169
+ dismiss: 'ปิด',
170
+ changesSessionGit: 'การเปลี่ยนแปลง Git',
171
+ changesSessionLens: 'การเปลี่ยนแปลงในเซสชัน',
172
+ changesEmpty: 'ไม่มีการดำเนินการไฟล์ในเซสชันนี้',
173
+ changesCount: 'การดำเนินการไฟล์ {count} รายการ',
174
+ changesRedacted: '[ถูกปกปิด]',
175
+ changesBinary: 'ไฟล์ไบนารี — ไม่มีตัวอย่าง',
176
+ changesPreviewError: 'อ่านตัวอย่างไม่สำเร็จ: {message}',
177
+ changesLens: 'มุมมอง',
158
178
  exited: 'กระบวนการเทอร์มินัลออกแล้ว',
159
179
  noSession: 'เลือกแชทเพื่อใช้แถบด้านข้าง',
160
180
  pluginNotLoaded: 'ปลั๊กอินไม่ได้โหลด; tab ไม่พร้อมใช้งาน:',
@@ -155,6 +155,26 @@ export const tr: Record<string, string> = {
155
155
  produced: 'Üretilenler',
156
156
  producedOpen: 'Kenar çubuğunda aç',
157
157
  disconnected: 'Terminal bağlantısı kesildi, yeniden bağlanılıyor…',
158
+ terminalWaitBanner: 'Ajan {needle} için bekliyor',
159
+ terminalSkipWait: 'Beklemeyi atla',
160
+ gitFoldExpand: '{count} bağlam satırını göster',
161
+ gitFoldLoading: 'Genişletiliyor…',
162
+ gitFoldFailed: 'Genişletilemedi',
163
+ rename: 'Yeniden adlandır',
164
+ renameInvalid: 'Ad boş olamaz veya yol ayracı içeremez.',
165
+ delete: 'Sil',
166
+ deleteTitle: '"{name}" silinsin mi?',
167
+ deleteDescFile: 'Bu dosya kalıcı olarak silinir. Bu işlem geri alınamaz.',
168
+ deleteDescDir: 'Bu dizin ve içeriği kalıcı olarak silinir. Bu işlem geri alınamaz.',
169
+ dismiss: 'Kapat',
170
+ changesSessionGit: 'Git değişiklikleri',
171
+ changesSessionLens: 'Oturum değişiklikleri',
172
+ changesEmpty: 'Bu oturumda dosya işlemi yok',
173
+ changesCount: '{count} dosya işlemi',
174
+ changesRedacted: '[MASKELENDİ]',
175
+ changesBinary: 'İkili dosya — önizleme yok',
176
+ changesPreviewError: 'Önizleme okunamadı: {message}',
177
+ changesLens: 'Görünüm',
158
178
  exited: 'Terminal süreci sonlandı',
159
179
  noSession: 'Kenar çubuğunu kullanmak için bir oturum seçin',
160
180
  pluginNotLoaded: 'Eklenti yüklenmedi; sekme kullanılamıyor:',
@@ -155,6 +155,26 @@ export const vi: Record<string, string> = {
155
155
  produced: 'Đã tạo',
156
156
  producedOpen: 'Mở trong thanh bên',
157
157
  disconnected: 'Terminal mất kết nối, đang kết nối lại…',
158
+ terminalWaitBanner: 'Agent đang chờ {needle}',
159
+ terminalSkipWait: 'Bỏ qua chờ',
160
+ gitFoldExpand: 'Hiện {count} dòng ngữ cảnh',
161
+ gitFoldLoading: 'Đang mở…',
162
+ gitFoldFailed: 'Không thể mở',
163
+ rename: 'Đổi tên',
164
+ renameInvalid: 'Tên không được trống hoặc chứa dấu phân cách đường dẫn.',
165
+ delete: 'Xóa',
166
+ deleteTitle: 'Xóa "{name}"?',
167
+ deleteDescFile: 'Thao tác này sẽ xóa vĩnh viễn tệp. Không thể hoàn tác.',
168
+ deleteDescDir: 'Thao tác này sẽ xóa vĩnh viễn thư mục và mọi thứ bên trong. Không thể hoàn tác.',
169
+ dismiss: 'Đóng',
170
+ changesSessionGit: 'Thay đổi Git',
171
+ changesSessionLens: 'Thay đổi phiên',
172
+ changesEmpty: 'Không có thao tác tệp trong phiên này',
173
+ changesCount: '{count} thao tác tệp',
174
+ changesRedacted: '[ĐÃ CHE]',
175
+ changesBinary: 'Tệp nhị phân — không có xem trước',
176
+ changesPreviewError: 'Lỗi đọc xem trước: {message}',
177
+ changesLens: 'Chế độ xem',
158
178
  exited: 'Tiến trình terminal đã thoát',
159
179
  noSession: 'Chọn một phiên để dùng thanh bên',
160
180
  pluginNotLoaded: 'Plugin chưa tải, tab tạm không khả dụng:',
@@ -170,6 +170,26 @@ export const zhHK: Record<string, string> = {
170
170
  produced: '本次產出',
171
171
  producedOpen: '在側邊欄中開啟',
172
172
  disconnected: '終端連線斷開,重新連線中…',
173
+ terminalWaitBanner: 'Agent 正在等待 {needle}',
174
+ terminalSkipWait: '跳過等待',
175
+ gitFoldExpand: '展開 {count} 行上下文',
176
+ gitFoldLoading: '展開中…',
177
+ gitFoldFailed: '展開失敗',
178
+ rename: '重新命名',
179
+ renameInvalid: '名稱不能為空,且不能包含路徑分隔符。',
180
+ delete: '刪除',
181
+ deleteTitle: '刪除「{name}」?',
182
+ deleteDescFile: '將永久刪除該檔案,此操作無法復原。',
183
+ deleteDescDir: '將永久刪除該目錄及其全部內容,此操作無法復原。',
184
+ dismiss: '關閉',
185
+ changesSessionGit: 'Git 變動',
186
+ changesSessionLens: '會話變動',
187
+ changesEmpty: '本會話尚無檔案操作',
188
+ changesCount: '{count} 個檔案操作',
189
+ changesRedacted: '[已脫敏]',
190
+ changesBinary: '二進位檔案,無法預覽',
191
+ changesPreviewError: '預覽讀取失敗:{message}',
192
+ changesLens: 'View',
173
193
  exited: '終端程序已退出',
174
194
  noSession: '選擇一個工作階段以使用側邊欄',
175
195
  pluginNotLoaded: '插件未載入,標籤暫不可用:',
@@ -170,6 +170,26 @@ export const zhMO: Record<string, string> = {
170
170
  produced: '本次產出',
171
171
  producedOpen: '在側邊欄中開啟',
172
172
  disconnected: '終端連線斷開,重新連線中…',
173
+ terminalWaitBanner: 'Agent 正在等待 {needle}',
174
+ terminalSkipWait: '跳過等待',
175
+ gitFoldExpand: '展開 {count} 行上下文',
176
+ gitFoldLoading: '展開中…',
177
+ gitFoldFailed: '展開失敗',
178
+ rename: '重新命名',
179
+ renameInvalid: '名稱不能為空,且不能包含路徑分隔符。',
180
+ delete: '刪除',
181
+ deleteTitle: '刪除「{name}」?',
182
+ deleteDescFile: '將永久刪除該檔案,此操作無法復原。',
183
+ deleteDescDir: '將永久刪除該目錄及其全部內容,此操作無法復原。',
184
+ dismiss: '關閉',
185
+ changesSessionGit: 'Git 變動',
186
+ changesSessionLens: '會話變動',
187
+ changesEmpty: '本會話尚無檔案操作',
188
+ changesCount: '{count} 個檔案操作',
189
+ changesRedacted: '[已脫敏]',
190
+ changesBinary: '二進位檔案,無法預覽',
191
+ changesPreviewError: '預覽讀取失敗:{message}',
192
+ changesLens: 'View',
173
193
  exited: '終端程序已退出',
174
194
  noSession: '選擇一個工作階段以使用側邊欄',
175
195
  pluginNotLoaded: '插件未載入,標籤暫不可用:',
@@ -170,6 +170,26 @@ export const zhTW: Record<string, string> = {
170
170
  produced: '本次產出',
171
171
  producedOpen: '在側邊欄中開啟',
172
172
  disconnected: '終端連線斷開,重新連線中…',
173
+ terminalWaitBanner: 'Agent 正在等待 {needle}',
174
+ terminalSkipWait: '跳過等待',
175
+ gitFoldExpand: '展開 {count} 行上下文',
176
+ gitFoldLoading: '展開中…',
177
+ gitFoldFailed: '展開失敗',
178
+ rename: '重新命名',
179
+ renameInvalid: '名稱不能為空,且不能包含路徑分隔符。',
180
+ delete: '刪除',
181
+ deleteTitle: '刪除「{name}」?',
182
+ deleteDescFile: '將永久刪除該檔案,此操作無法復原。',
183
+ deleteDescDir: '將永久刪除該目錄及其全部內容,此操作無法復原。',
184
+ dismiss: '關閉',
185
+ changesSessionGit: '偵測 Git 變動',
186
+ changesSessionLens: '會話變動',
187
+ changesEmpty: '本會話尚無檔案操作',
188
+ changesCount: '{count} 個檔案操作',
189
+ changesRedacted: '[已遮蔽]',
190
+ changesBinary: '二進位檔案,無法預覽',
191
+ changesPreviewError: '預覽讀取失敗:{message}',
192
+ changesLens: 'View',
173
193
  exited: '終端程序已退出',
174
194
  noSession: '選擇一個工作階段以使用側邊欄',
175
195
  pluginNotLoaded: '插件未載入,標籤暫不可用:',
@@ -160,6 +160,26 @@ export const zh = {
160
160
  producedOpen: '在侧边栏中打开',
161
161
  showInFolder: '在文件夹中显示',
162
162
  disconnected: '终端连接断开,重连中…',
163
+ terminalWaitBanner: 'Agent 正在等待 {needle}',
164
+ terminalSkipWait: '跳过等待',
165
+ gitFoldExpand: '展开 {count} 行上下文',
166
+ gitFoldLoading: '展开中…',
167
+ gitFoldFailed: '展开失败',
168
+ rename: '重命名',
169
+ renameInvalid: '名称不能为空,且不能包含路径分隔符。',
170
+ delete: '删除',
171
+ deleteTitle: '删除「{name}」?',
172
+ deleteDescFile: '将永久删除该文件,此操作不可撤销。',
173
+ deleteDescDir: '将永久删除该目录及其全部内容,此操作不可撤销。',
174
+ dismiss: '关闭',
175
+ changesSessionGit: 'Git 变动',
176
+ changesSessionLens: '会话变动',
177
+ changesEmpty: '本会话尚无文件操作',
178
+ changesCount: '{count} 个文件操作',
179
+ changesRedacted: '[已脱敏]',
180
+ changesBinary: '二进制文件,无法预览',
181
+ changesPreviewError: '预览读取失败:{message}',
182
+ changesLens: '视角',
163
183
  exited: '终端进程已退出',
164
184
  noSession: '选择一个会话以使用侧边栏',
165
185
  pluginNotLoaded: '插件未加载,标签页暂不可用:',
@@ -497,6 +517,26 @@ export const en: Record<keyof typeof zh, string> = {
497
517
  producedOpen: 'Open in sidebar',
498
518
  showInFolder: 'Show in folder',
499
519
  disconnected: 'Terminal disconnected, reconnecting…',
520
+ terminalWaitBanner: 'Agent is waiting for {needle}',
521
+ terminalSkipWait: 'Skip wait',
522
+ gitFoldExpand: 'Expand {count} context lines',
523
+ gitFoldLoading: 'Expanding…',
524
+ gitFoldFailed: 'Failed to expand',
525
+ rename: 'Rename',
526
+ renameInvalid: 'The name cannot be empty or contain path separators.',
527
+ delete: 'Delete',
528
+ deleteTitle: 'Delete "{name}"?',
529
+ deleteDescFile: 'This permanently deletes the file. This cannot be undone.',
530
+ deleteDescDir: 'This permanently deletes the directory and everything inside it. This cannot be undone.',
531
+ dismiss: 'Dismiss',
532
+ changesSessionGit: 'Git changes',
533
+ changesSessionLens: 'Session changes',
534
+ changesEmpty: 'No file operations in this session',
535
+ changesCount: '{count} file operations',
536
+ changesRedacted: '[REDACTED]',
537
+ changesBinary: 'Binary file — no preview',
538
+ changesPreviewError: 'Preview read failed: {message}',
539
+ changesLens: 'Lens',
500
540
  exited: 'Terminal process exited',
501
541
  noSession: 'Select a conversation to use the sidebar',
502
542
  pluginNotLoaded: 'Plugin not loaded; tab unavailable:',
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Secret redaction for the session lens' file preview: heuristic masking of
3
+ * credential-shaped strings (API keys, bearer tokens, private key blocks,
4
+ * password assignments) before file content is shown in the sidebar. This
5
+ * layer applies ONLY to the session lens' preview pane — ordinary file reads
6
+ * (editor, untracked diff fallback) never pass through it, so ordinary
7
+ * files' content is untouched.
8
+ */
9
+
10
+ /** One redaction rule: a pattern plus the replacement it masks matches to. */
11
+ interface RedactRule {
12
+ pattern: RegExp
13
+ replacement: string
14
+ }
15
+
16
+ /** The heuristics, applied in order; `$1` keeps the leading keyword where the
17
+ * rule matches an assignment shape. */
18
+ const RULES: RedactRule[] = [
19
+ // PEM private key blocks (the whole block is one secret).
20
+ { pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, replacement: '[REDACTED PRIVATE KEY]' },
21
+ // Known token shapes (sk- keys, GitHub tokens, AWS ids, Slack, Google).
22
+ { pattern: /\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{16,}|AKIA[0-9A-Z]{16}|xox[abprs]-[A-Za-z0-9-]{10,}|AIza[0-9A-Za-z_-]{30,})\b/g, replacement: '[REDACTED KEY]' },
23
+ // Authorization headers and bearer tokens.
24
+ { pattern: /([Aa]uthorization\s*:\s*)[^\n"']{4,}/g, replacement: '$1[REDACTED]' },
25
+ { pattern: /\b([Bb]earer\s+)[A-Za-z0-9._-]{16,}\b/g, replacement: '$1[REDACTED]' },
26
+ // Credential assignment shapes (password/secret/token/api-key = value).
27
+ { pattern: /(\b[A-Za-z_-]*(?:password|passwd|secret|token|apikey|api_key)[A-Za-z_-]*(?:\s*[:=]\s*))['"]?[^\s"',;){]{3,}/gi, replacement: '$1[REDACTED]' },
28
+ ]
29
+
30
+ /**
31
+ * Mask credential-shaped strings in `text`. Best-effort by design: the goal
32
+ * is to keep the common accident (a key echoed into a file the model wrote)
33
+ * out of the sidebar, not to parse every secret format ever shipped.
34
+ */
35
+ export function redactSecrets(text: string): string {
36
+ let out = text
37
+ for (const rule of RULES) out = out.replace(rule.pattern, rule.replacement)
38
+ return out
39
+ }
@@ -1651,6 +1651,189 @@ body[data-dsh-sidebar-dragging] .panel {
1651
1651
  color: var(--dsw-alias-label-primary);
1652
1652
  }
1653
1653
 
1654
+ /* A hunk-gap fold in a diff: the "expand hidden context" chip between two
1655
+ hunks (loading is inert, failure degrades to a static marker). */
1656
+ .gitFoldRow {
1657
+ display: block;
1658
+ width: 100%;
1659
+ box-sizing: border-box;
1660
+ padding: 2px 12px;
1661
+ border: none;
1662
+ background: var(--dsw-alias-bg-layer-1);
1663
+ color: var(--dsw-alias-label-tertiary);
1664
+ font: var(--dsw-font-xxxs-11);
1665
+ text-align: center;
1666
+ cursor: pointer;
1667
+ outline: none;
1668
+ }
1669
+
1670
+ .gitFoldRow:hover {
1671
+ background: var(--dsw-alias-interactive-bg-hover);
1672
+ color: var(--dsw-alias-label-secondary);
1673
+ }
1674
+
1675
+ .gitFoldRow:disabled {
1676
+ cursor: default;
1677
+ }
1678
+
1679
+ .gitFoldRowFailed {
1680
+ color: var(--dsw-alias-state-error-primary);
1681
+ opacity: 0.7;
1682
+ }
1683
+
1684
+ /* The tree row's inline rename editor: fills the row's name slot, keeps the
1685
+ row's metrics so the tree does not jump when editing starts. */
1686
+ .explorerRenameInput {
1687
+ flex: 1;
1688
+ min-width: 0;
1689
+ height: 20px;
1690
+ padding: 0 4px;
1691
+ border: 1px solid var(--dsw-alias-border-l2);
1692
+ border-radius: 4px;
1693
+ background: var(--dsw-alias-bg-layer-2);
1694
+ color: var(--dsw-alias-label-primary);
1695
+ font: var(--dsw-font-xxs-12);
1696
+ outline: none;
1697
+ }
1698
+
1699
+ /* ── The unified changes tab's dual-lens toggle + session lens ─────────── */
1700
+
1701
+ .changesLensToggle {
1702
+ flex: none;
1703
+ display: flex;
1704
+ gap: 2px;
1705
+ padding: 2px;
1706
+ }
1707
+
1708
+ .changesLensTab {
1709
+ flex: 1;
1710
+ height: 24px;
1711
+ border: none;
1712
+ border-radius: 6px;
1713
+ background: transparent;
1714
+ color: var(--dsw-alias-label-secondary);
1715
+ font: var(--dsw-font-xxxs-strong-11);
1716
+ cursor: pointer;
1717
+ }
1718
+
1719
+ .changesLensTab:hover {
1720
+ background: var(--dsw-alias-interactive-bg-hover);
1721
+ }
1722
+
1723
+ .changesLensTabActive,
1724
+ .changesLensTabActive:hover {
1725
+ background: var(--dsw-alias-interactive-bg-active);
1726
+ color: var(--dsw-alias-label-primary);
1727
+ }
1728
+
1729
+ .sessionLens {
1730
+ flex: 1;
1731
+ min-height: 0;
1732
+ overflow-y: auto;
1733
+ }
1734
+
1735
+ .sessionLensBar {
1736
+ display: flex;
1737
+ align-items: center;
1738
+ gap: 6px;
1739
+ height: 26px;
1740
+ padding: 0 4px;
1741
+ }
1742
+
1743
+ .sessionLensCount {
1744
+ flex: 1;
1745
+ min-width: 0;
1746
+ font: var(--dsw-font-xxxs-11);
1747
+ color: var(--dsw-alias-label-tertiary);
1748
+ }
1749
+
1750
+ .sessionLensEmpty {
1751
+ padding: 16px;
1752
+ font: var(--dsw-font-xxs-12);
1753
+ color: var(--dsw-alias-label-tertiary);
1754
+ text-align: center;
1755
+ }
1756
+
1757
+ .sessionLensItem {
1758
+ border-bottom: 1px solid var(--dsw-alias-border-l1);
1759
+ }
1760
+
1761
+ .sessionLensRow {
1762
+ display: flex;
1763
+ align-items: center;
1764
+ gap: 8px;
1765
+ width: 100%;
1766
+ box-sizing: border-box;
1767
+ min-height: 30px;
1768
+ padding: 4px 10px;
1769
+ border: none;
1770
+ border-radius: 6px;
1771
+ background: transparent;
1772
+ color: var(--dsw-alias-label-primary);
1773
+ text-align: left;
1774
+ cursor: pointer;
1775
+ outline: none;
1776
+ }
1777
+
1778
+ .sessionLensRow:hover {
1779
+ background: var(--dsw-alias-interactive-bg-hover);
1780
+ }
1781
+
1782
+ .sessionLensPath {
1783
+ flex: 1;
1784
+ min-width: 0;
1785
+ overflow: hidden;
1786
+ text-overflow: ellipsis;
1787
+ white-space: nowrap;
1788
+ direction: rtl;
1789
+ text-align: left;
1790
+ }
1791
+
1792
+ .sessionLensMeta {
1793
+ flex: none;
1794
+ font: var(--dsw-font-xxxs-11);
1795
+ color: var(--dsw-alias-label-tertiary);
1796
+ }
1797
+
1798
+ .sessionLensPreview {
1799
+ max-height: 260px;
1800
+ overflow: auto;
1801
+ margin: 0 8px 6px;
1802
+ padding: 6px 8px;
1803
+ border: 1px solid var(--dsw-alias-border-l1);
1804
+ border-radius: 6px;
1805
+ background: var(--dsw-alias-bg-layer-1);
1806
+ font-family: var(--ds-font-family-code);
1807
+ font-size: var(--dsw-font-xxxs-11-font-size);
1808
+ line-height: 1.5;
1809
+ color: var(--dsw-alias-label-primary);
1810
+ white-space: pre-wrap;
1811
+ word-break: break-word;
1812
+ }
1813
+
1814
+ /* The "Agent 正在等待 {needle}" wait banner: a warn-toned strip above the
1815
+ terminal surface while the model blocks in terminal_wait_for; the needle
1816
+ ellipsizes inline and the full text rides the title tooltip. */
1817
+ .terminalWaitBanner {
1818
+ flex: none;
1819
+ display: flex;
1820
+ align-items: center;
1821
+ gap: 8px;
1822
+ padding: 3px 10px;
1823
+ font: var(--dsw-font-xxxs-11);
1824
+ color: var(--dsw-alias-state-warn-label);
1825
+ background: var(--dsw-alias-state-warn-tertiary);
1826
+ }
1827
+
1828
+ .terminalWaitNeedle {
1829
+ flex: 1;
1830
+ min-width: 0;
1831
+ overflow: hidden;
1832
+ text-overflow: ellipsis;
1833
+ white-space: nowrap;
1834
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1835
+ }
1836
+
1654
1837
  /* The node-pty dependency failure banner (issue #140): a taller warn block
1655
1838
  carrying the pasteable repair command (bash / cmd / PowerShell). */
1656
1839
  .terminalDepsBanner {
@@ -104,6 +104,14 @@ export interface SidebarState {
104
104
  splits: SplitNode
105
105
  /** Free windows (tabs dragged out onto the conversation area). */
106
106
  floats: FloatWindow[]
107
+ /**
108
+ * Live agent-terminal wait state (uuid → the wait the model currently
109
+ * blocks on in `terminal_wait_for`), mirrored from the host's
110
+ * agent-terminals push. Transient by design: sanitizeState never restores
111
+ * it, so a reload starts clean and the next push (sent immediately on WS
112
+ * attach) repopulates it.
113
+ */
114
+ agentWaits: Record<string, { needle: string; since: number }>
107
115
  }
108
116
 
109
117
  export const PANEL_MIN = 280
@@ -209,6 +217,7 @@ export function makeDefaultState(width = PANEL_DEFAULT, panelOpen = true, seed:
209
217
  revealed: [],
210
218
  splits: leaf,
211
219
  floats: [],
220
+ agentWaits: {},
212
221
  }
213
222
  }
214
223
 
@@ -854,6 +863,23 @@ export function agentTabId(uuid: string): string {
854
863
  return `${AGENT_TAB_PREFIX}${uuid}`
855
864
  }
856
865
 
866
+ /** Shallow equality of two agent-wait maps (same keys, same needle+since). */
867
+ function sameAgentWaits(
868
+ a: SidebarState['agentWaits'] | undefined,
869
+ b: Record<string, { needle: string; since: number }>,
870
+ ): boolean {
871
+ if (a === undefined) return Object.keys(b).length === 0
872
+ const aKeys = Object.keys(a)
873
+ if (aKeys.length !== Object.keys(b).length) return false
874
+ for (const key of aKeys) {
875
+ const av = a[key]
876
+ const bv = b[key]
877
+ if (av === undefined || bv === undefined) return false
878
+ if (av.needle !== bv.needle || av.since !== bv.since) return false
879
+ }
880
+ return true
881
+ }
882
+
857
883
  /**
858
884
  * Reconcile the sidebar's agent-terminal tabs with the host's live list.
859
885
  * The host pushes the current list of agent terminals (created by the model
@@ -868,7 +894,7 @@ export function agentTabId(uuid: string): string {
868
894
  */
869
895
  export function reconcileAgentTerminals(
870
896
  state: SidebarState,
871
- agentTerminals: ReadonlyArray<{ uuid: string; title: string }>,
897
+ agentTerminals: ReadonlyArray<{ uuid: string; title: string; waiting?: { needle: string; since: number } | null }>,
872
898
  ): SidebarState {
873
899
  const existingTabs = allLeaves(state.splits).flatMap(leaf => leaf.tabs)
874
900
  .concat(state.floats.map(float => float.tab))
@@ -883,7 +909,17 @@ export function reconcileAgentTerminals(
883
909
  // convergence: no title suffix, no meta write — the tab keeps its uuid
884
910
  // so a later reconcile push revives it if the agent reopens the same one).
885
911
  const toRemove = existingAgentTabs.filter(tab => !serverUuids.has(agentUuidOf(tab.id)) && tab.pin === undefined)
886
- if (toAdd.length === 0 && toRemove.length === 0) return state
912
+ // Mirror the live wait state from the push (authoritative: a vanished
913
+ // waiting field simply drops the entry). A waits-only change must still
914
+ // produce a new state — the tab add/remove no-change check alone would
915
+ // swallow banner updates.
916
+ const serverWaits: Record<string, { needle: string; since: number }> = {}
917
+ for (const terminal of agentTerminals) {
918
+ if (terminal.waiting !== undefined && terminal.waiting !== null) {
919
+ serverWaits[terminal.uuid] = { needle: terminal.waiting.needle, since: terminal.waiting.since }
920
+ }
921
+ }
922
+ if (toAdd.length === 0 && toRemove.length === 0 && sameAgentWaits(state.agentWaits, serverWaits)) return state
887
923
  // Remove tabs whose uuids vanished from the server list (the agent closed
888
924
  // them, or the pty exited and was reaped). Reuse closeTab's leaf cleanup;
889
925
  // a FLOATED agent terminal leaves with its window.
@@ -900,7 +936,7 @@ export function reconcileAgentTerminals(
900
936
  }
901
937
  // Add tabs for new uuids (the agent created a terminal). They land in the
902
938
  // active pane via openTabInActivePane; the next reconcile is a no-op for them.
903
- let next: SidebarState = { ...state, splits, floats }
939
+ let next: SidebarState = { ...state, splits, floats, agentWaits: serverWaits }
904
940
  for (const terminal of toAdd) {
905
941
  const tab: SidebarTab = {
906
942
  id: agentTabId(terminal.uuid),
@@ -1150,6 +1186,9 @@ export function sanitizeState(parsed: unknown): SidebarState | undefined {
1150
1186
  revealed: [],
1151
1187
  splits: migratedSplits,
1152
1188
  floats,
1189
+ // Live wait state is transient: a reload starts clean; the next
1190
+ // agent-terminals push (sent on WS attach) repopulates it.
1191
+ agentWaits: {},
1153
1192
  }
1154
1193
  }
1155
1194