dsh-coding-sidebar 1.0.9 → 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 (39) hide show
  1. package/README.md +2 -2
  2. package/lib/client-editor.js +215 -184
  3. package/lib/client-registry.js +504 -309
  4. package/lib/client-terminal.js +210 -179
  5. package/lib/client.js +507 -312
  6. package/lib/index.js +63 -0
  7. package/lib/types/changes-ops.d.ts +31 -0
  8. package/lib/types/client/SessionLens.d.ts +4 -0
  9. package/lib/types/client/api.d.ts +10 -0
  10. package/lib/types/client/locales.d.ts +8 -0
  11. package/lib/types/client/redact.d.ts +14 -0
  12. package/package.json +1 -1
  13. package/src/changes-ops.ts +78 -0
  14. package/src/client/GitView.tsx +37 -0
  15. package/src/client/SessionLens.tsx +95 -0
  16. package/src/client/api.ts +4 -0
  17. package/src/client/locales-ar.ts +8 -0
  18. package/src/client/locales-de.ts +8 -0
  19. package/src/client/locales-fr.ts +8 -0
  20. package/src/client/locales-hi.ts +8 -0
  21. package/src/client/locales-id.ts +8 -0
  22. package/src/client/locales-it.ts +8 -0
  23. package/src/client/locales-ja.ts +8 -0
  24. package/src/client/locales-ko.ts +8 -0
  25. package/src/client/locales-nl.ts +8 -0
  26. package/src/client/locales-pl.ts +8 -0
  27. package/src/client/locales-pt.ts +8 -0
  28. package/src/client/locales-ru.ts +8 -0
  29. package/src/client/locales-sv.ts +8 -0
  30. package/src/client/locales-th.ts +8 -0
  31. package/src/client/locales-tr.ts +8 -0
  32. package/src/client/locales-vi.ts +8 -0
  33. package/src/client/locales-zh-HK.ts +8 -0
  34. package/src/client/locales-zh-MO.ts +8 -0
  35. package/src/client/locales-zh-TW.ts +8 -0
  36. package/src/client/locales.ts +16 -0
  37. package/src/client/redact.ts +39 -0
  38. package/src/client/sidebar.module.css +115 -0
  39. package/src/index.ts +13 -0
@@ -167,6 +167,14 @@ export const tr: Record<string, string> = {
167
167
  deleteDescFile: 'Bu dosya kalıcı olarak silinir. Bu işlem geri alınamaz.',
168
168
  deleteDescDir: 'Bu dizin ve içeriği kalıcı olarak silinir. Bu işlem geri alınamaz.',
169
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',
170
178
  exited: 'Terminal süreci sonlandı',
171
179
  noSession: 'Kenar çubuğunu kullanmak için bir oturum seçin',
172
180
  pluginNotLoaded: 'Eklenti yüklenmedi; sekme kullanılamıyor:',
@@ -167,6 +167,14 @@ export const vi: Record<string, string> = {
167
167
  deleteDescFile: 'Thao tác này sẽ xóa vĩnh viễn tệp. Không thể hoàn tác.',
168
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
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',
170
178
  exited: 'Tiến trình terminal đã thoát',
171
179
  noSession: 'Chọn một phiên để dùng thanh bên',
172
180
  pluginNotLoaded: 'Plugin chưa tải, tab tạm không khả dụng:',
@@ -182,6 +182,14 @@ export const zhHK: Record<string, string> = {
182
182
  deleteDescFile: '將永久刪除該檔案,此操作無法復原。',
183
183
  deleteDescDir: '將永久刪除該目錄及其全部內容,此操作無法復原。',
184
184
  dismiss: '關閉',
185
+ changesSessionGit: 'Git 變動',
186
+ changesSessionLens: '會話變動',
187
+ changesEmpty: '本會話尚無檔案操作',
188
+ changesCount: '{count} 個檔案操作',
189
+ changesRedacted: '[已脫敏]',
190
+ changesBinary: '二進位檔案,無法預覽',
191
+ changesPreviewError: '預覽讀取失敗:{message}',
192
+ changesLens: 'View',
185
193
  exited: '終端程序已退出',
186
194
  noSession: '選擇一個工作階段以使用側邊欄',
187
195
  pluginNotLoaded: '插件未載入,標籤暫不可用:',
@@ -182,6 +182,14 @@ export const zhMO: Record<string, string> = {
182
182
  deleteDescFile: '將永久刪除該檔案,此操作無法復原。',
183
183
  deleteDescDir: '將永久刪除該目錄及其全部內容,此操作無法復原。',
184
184
  dismiss: '關閉',
185
+ changesSessionGit: 'Git 變動',
186
+ changesSessionLens: '會話變動',
187
+ changesEmpty: '本會話尚無檔案操作',
188
+ changesCount: '{count} 個檔案操作',
189
+ changesRedacted: '[已脫敏]',
190
+ changesBinary: '二進位檔案,無法預覽',
191
+ changesPreviewError: '預覽讀取失敗:{message}',
192
+ changesLens: 'View',
185
193
  exited: '終端程序已退出',
186
194
  noSession: '選擇一個工作階段以使用側邊欄',
187
195
  pluginNotLoaded: '插件未載入,標籤暫不可用:',
@@ -182,6 +182,14 @@ export const zhTW: Record<string, string> = {
182
182
  deleteDescFile: '將永久刪除該檔案,此操作無法復原。',
183
183
  deleteDescDir: '將永久刪除該目錄及其全部內容,此操作無法復原。',
184
184
  dismiss: '關閉',
185
+ changesSessionGit: '偵測 Git 變動',
186
+ changesSessionLens: '會話變動',
187
+ changesEmpty: '本會話尚無檔案操作',
188
+ changesCount: '{count} 個檔案操作',
189
+ changesRedacted: '[已遮蔽]',
190
+ changesBinary: '二進位檔案,無法預覽',
191
+ changesPreviewError: '預覽讀取失敗:{message}',
192
+ changesLens: 'View',
185
193
  exited: '終端程序已退出',
186
194
  noSession: '選擇一個工作階段以使用側邊欄',
187
195
  pluginNotLoaded: '插件未載入,標籤暫不可用:',
@@ -172,6 +172,14 @@ export const zh = {
172
172
  deleteDescFile: '将永久删除该文件,此操作不可撤销。',
173
173
  deleteDescDir: '将永久删除该目录及其全部内容,此操作不可撤销。',
174
174
  dismiss: '关闭',
175
+ changesSessionGit: 'Git 变动',
176
+ changesSessionLens: '会话变动',
177
+ changesEmpty: '本会话尚无文件操作',
178
+ changesCount: '{count} 个文件操作',
179
+ changesRedacted: '[已脱敏]',
180
+ changesBinary: '二进制文件,无法预览',
181
+ changesPreviewError: '预览读取失败:{message}',
182
+ changesLens: '视角',
175
183
  exited: '终端进程已退出',
176
184
  noSession: '选择一个会话以使用侧边栏',
177
185
  pluginNotLoaded: '插件未加载,标签页暂不可用:',
@@ -521,6 +529,14 @@ export const en: Record<keyof typeof zh, string> = {
521
529
  deleteDescFile: 'This permanently deletes the file. This cannot be undone.',
522
530
  deleteDescDir: 'This permanently deletes the directory and everything inside it. This cannot be undone.',
523
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',
524
540
  exited: 'Terminal process exited',
525
541
  noSession: 'Select a conversation to use the sidebar',
526
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
+ }
@@ -1696,6 +1696,121 @@ body[data-dsh-sidebar-dragging] .panel {
1696
1696
  outline: none;
1697
1697
  }
1698
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
+
1699
1814
  /* The "Agent 正在等待 {needle}" wait banner: a warn-toned strip above the
1700
1815
  terminal surface while the model blocks in terminal_wait_for; the needle
1701
1816
  ellipsizes inline and the full text rides the title tooltip. */
package/src/index.ts CHANGED
@@ -31,6 +31,7 @@ import {
31
31
  } from './config.ts'
32
32
  import { parentOf, requireAbsolute, listDirectory, rootLabel } from './fs-tree.ts'
33
33
  import { removeWorkspaceEntry, renameWorkspaceEntry, writeWorkspaceUpload } from './fs-operations.ts'
34
+ import { sessionFileOps } from './changes-ops.ts'
34
35
  import { ensureWorkspacePath, ensureWorkspaceWritePath } from './path-security.ts'
35
36
  import { searchFiles } from './fs-search.ts'
36
37
  import { extractFrameAncestors } from './browser-probe.ts'
@@ -514,6 +515,18 @@ function buildApi(
514
515
  // exists. Kill is fenced to the owning session by the jobs registry.
515
516
  'jobs.output': (payload) => jobsApi.output(payload),
516
517
  'jobs.kill': (payload) => jobsApi.kill(payload),
518
+ // The session lens: file operations the model performed in this session,
519
+ // parsed from the session's own event log (read-only replay — the
520
+ // model's job_output cursor is never touched). Cold sessions degrade to
521
+ // an empty list (only live sessions have an in-memory log on this host).
522
+ 'changes.ops': async (payload) => {
523
+ const sessionId = requireString(payload, 'sessionId')
524
+ const stored = ctx.sessions.get(sessionId)
525
+ const events = stored?.snapshotEvents !== undefined
526
+ ? stored.snapshotEvents() as unknown as Parameters<typeof sessionFileOps>[0]
527
+ : []
528
+ return { ops: sessionFileOps(events) }
529
+ },
517
530
  // Subagent live previews: one batch request per refresh; the route folds
518
531
  // the newest text/tool activity of every running child in the tree.
519
532
  'subagents.live': (payload) => subagentLiveApi.live(payload),