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
package/lib/index.js CHANGED
@@ -532,6 +532,64 @@ async function removeWorkspaceEntry(input) {
532
532
  return { path: absolute };
533
533
  }
534
534
  //#endregion
535
+ //#region src/changes-ops.ts
536
+ /** Argument keys a file-addressing tool may use for its target path. */
537
+ const PATH_KEYS = [
538
+ "path",
539
+ "file_path",
540
+ "filePath",
541
+ "notebook_path",
542
+ "filename"
543
+ ];
544
+ /**
545
+ * Whether a tool name looks like it MUTATES files. Deliberately coarse
546
+ * (substring match on the mutating verbs) so host-side and plugin-side
547
+ * file tools both qualify; read-only tools never match.
548
+ */
549
+ function isWriteTool(name) {
550
+ const lowered = name.toLowerCase();
551
+ return /write|edit|patch|apply|create_file|insert/.test(lowered);
552
+ }
553
+ /** Extract the addressed path from one tool call's arguments JSON. */
554
+ function argumentPath(args) {
555
+ if (args === "") return void 0;
556
+ try {
557
+ const parsed = JSON.parse(args);
558
+ for (const key of PATH_KEYS) {
559
+ const value = parsed[key];
560
+ if (typeof value === "string" && value !== "") return value;
561
+ }
562
+ } catch {}
563
+ }
564
+ /**
565
+ * Fold a session event log into the deduplicated file-operation list,
566
+ * newest first. `tool/call` events with a mutating tool name and an
567
+ * addressable path are collected; every path keeps only its latest call
568
+ * (plus a touch count). Rows outside a live sessions registry read come
569
+ * back as an empty list — the page degrades to the empty state.
570
+ * @param events - the session's append-only event log (oldest → newest).
571
+ */
572
+ function sessionFileOps(events) {
573
+ const byPath = /* @__PURE__ */ new Map();
574
+ for (let index = events.length - 1; index >= 0; index -= 1) {
575
+ const event = events[index];
576
+ if (event === void 0 || event.type !== "tool/call") continue;
577
+ const name = typeof event.data.name === "string" ? event.data.name : "";
578
+ if (name === "" || !isWriteTool(name)) continue;
579
+ const path = argumentPath(typeof event.data.arguments === "string" ? event.data.arguments : "");
580
+ if (path === void 0) continue;
581
+ const existing = byPath.get(path);
582
+ if (existing === void 0) byPath.set(path, {
583
+ path,
584
+ tool: name,
585
+ time: event.time,
586
+ count: 1
587
+ });
588
+ else existing.count += 1;
589
+ }
590
+ return [...byPath.values()].sort((left, right) => right.time - left.time);
591
+ }
592
+ //#endregion
535
593
  //#region src/fs-search.ts
536
594
  /**
537
595
  * Recursive file-name search for the editor's merged-mode side panel.
@@ -4366,6 +4424,11 @@ function buildApi(ctx, ptyManager, agentPtyRegistry, resolved, terminalShell, ge
4366
4424
  "terminal.deps": () => depsStatus(),
4367
4425
  "jobs.output": (payload) => jobsApi.output(payload),
4368
4426
  "jobs.kill": (payload) => jobsApi.kill(payload),
4427
+ "changes.ops": async (payload) => {
4428
+ const sessionId = requireString(payload, "sessionId");
4429
+ const stored = ctx.sessions.get(sessionId);
4430
+ return { ops: sessionFileOps(stored?.snapshotEvents !== void 0 ? stored.snapshotEvents() : []) };
4431
+ },
4369
4432
  "subagents.live": (payload) => subagentLiveApi.live(payload),
4370
4433
  "shell.get": () => ({
4371
4434
  shell: terminalShell,
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Pure derivation of the "session lens": the file operations the model
3
+ * performed in one session, parsed from the session's own event log (the
4
+ * same durable log the side chat reads — nothing here touches the host
5
+ * registry or the model's cursors). Kept framework-free so the parser is
6
+ * unit-testable in the node environment.
7
+ */
8
+ import type { SidebarSessionEvent } from './context-types.ts';
9
+ /**
10
+ * One deduplicated file operation: the LATEST write-shaped tool call that
11
+ * touched `path` (earlier calls to the same file fold into it).
12
+ */
13
+ export interface SessionFileOp {
14
+ /** The file path as the tool call addressed it (verbatim). */
15
+ path: string;
16
+ /** The tool that performed the latest operation (e.g. write_file). */
17
+ tool: string;
18
+ /** Epoch ms of the event. */
19
+ time: number;
20
+ /** How many write-shaped calls touched this path in total. */
21
+ count: number;
22
+ }
23
+ /**
24
+ * Fold a session event log into the deduplicated file-operation list,
25
+ * newest first. `tool/call` events with a mutating tool name and an
26
+ * addressable path are collected; every path keeps only its latest call
27
+ * (plus a touch count). Rows outside a live sessions registry read come
28
+ * back as an empty list — the page degrades to the empty state.
29
+ * @param events - the session's append-only event log (oldest → newest).
30
+ */
31
+ export declare function sessionFileOps(events: readonly SidebarSessionEvent[]): SessionFileOp[];
@@ -0,0 +1,4 @@
1
+ import { type SessionScope } from './api.ts';
2
+ export declare function SessionLens(props: {
3
+ scope: SessionScope;
4
+ }): import("react").JSX.Element;
@@ -219,6 +219,16 @@ export declare const api: {
219
219
  ok: true;
220
220
  skipped: number;
221
221
  }>;
222
+ /** The session lens: file operations the model performed in one session
223
+ * (parsed from the session's own event log; newest first). */
224
+ changesOps: (scope: SessionScope, signal?: AbortSignal) => Promise<{
225
+ ops: Array<{
226
+ path: string;
227
+ tool: string;
228
+ time: number;
229
+ count: number;
230
+ }>;
231
+ }>;
222
232
  /** Terminal dependency status (issue #140): after a WS close 1011 with
223
233
  * reason `pty-deps-missing` the view fetches the full repair details here
224
234
  * (the close reason itself is capped at 123 bytes). */
@@ -171,6 +171,14 @@ export declare const zh: {
171
171
  deleteDescFile: string;
172
172
  deleteDescDir: string;
173
173
  dismiss: string;
174
+ changesSessionGit: string;
175
+ changesSessionLens: string;
176
+ changesEmpty: string;
177
+ changesCount: string;
178
+ changesRedacted: string;
179
+ changesBinary: string;
180
+ changesPreviewError: string;
181
+ changesLens: string;
174
182
  exited: string;
175
183
  noSession: string;
176
184
  pluginNotLoaded: string;
@@ -0,0 +1,14 @@
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
+ * Mask credential-shaped strings in `text`. Best-effort by design: the goal
11
+ * is to keep the common accident (a key echoed into a file the model wrote)
12
+ * out of the sidebar, not to parse every secret format ever shipped.
13
+ */
14
+ export declare function redactSecrets(text: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-coding-sidebar",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "DSH web plugin: a VSCode-like right sidebar (explorer / editor / terminal / git / browser), isolated per conversation session. Exposes the betterSidebar service for other plugins to register sidebar tabs and file viewers. KCoder-maintained fork of DSH-better-sidebar 0.17.2 (bottom panel removed, upstream-decoupled release line).",
5
5
  "type": "module",
6
6
  "repository": {
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Pure derivation of the "session lens": the file operations the model
3
+ * performed in one session, parsed from the session's own event log (the
4
+ * same durable log the side chat reads — nothing here touches the host
5
+ * registry or the model's cursors). Kept framework-free so the parser is
6
+ * unit-testable in the node environment.
7
+ */
8
+ import type { SidebarSessionEvent } from './context-types.ts'
9
+
10
+ /**
11
+ * One deduplicated file operation: the LATEST write-shaped tool call that
12
+ * touched `path` (earlier calls to the same file fold into it).
13
+ */
14
+ export interface SessionFileOp {
15
+ /** The file path as the tool call addressed it (verbatim). */
16
+ path: string
17
+ /** The tool that performed the latest operation (e.g. write_file). */
18
+ tool: string
19
+ /** Epoch ms of the event. */
20
+ time: number
21
+ /** How many write-shaped calls touched this path in total. */
22
+ count: number
23
+ }
24
+
25
+ /** Argument keys a file-addressing tool may use for its target path. */
26
+ const PATH_KEYS = ['path', 'file_path', 'filePath', 'notebook_path', 'filename'] as const
27
+
28
+ /**
29
+ * Whether a tool name looks like it MUTATES files. Deliberately coarse
30
+ * (substring match on the mutating verbs) so host-side and plugin-side
31
+ * file tools both qualify; read-only tools never match.
32
+ */
33
+ function isWriteTool(name: string): boolean {
34
+ const lowered = name.toLowerCase()
35
+ return /write|edit|patch|apply|create_file|insert/.test(lowered)
36
+ }
37
+
38
+ /** Extract the addressed path from one tool call's arguments JSON. */
39
+ function argumentPath(args: string): string | undefined {
40
+ if (args === '') return undefined
41
+ try {
42
+ const parsed = JSON.parse(args) as Record<string, unknown>
43
+ for (const key of PATH_KEYS) {
44
+ const value = parsed[key]
45
+ if (typeof value === 'string' && value !== '') return value
46
+ }
47
+ } catch {
48
+ // Malformed arguments JSON: not a file op we can attribute.
49
+ }
50
+ return undefined
51
+ }
52
+
53
+ /**
54
+ * Fold a session event log into the deduplicated file-operation list,
55
+ * newest first. `tool/call` events with a mutating tool name and an
56
+ * addressable path are collected; every path keeps only its latest call
57
+ * (plus a touch count). Rows outside a live sessions registry read come
58
+ * back as an empty list — the page degrades to the empty state.
59
+ * @param events - the session's append-only event log (oldest → newest).
60
+ */
61
+ export function sessionFileOps(events: readonly SidebarSessionEvent[]): SessionFileOp[] {
62
+ const byPath = new Map<string, SessionFileOp>()
63
+ for (let index = events.length - 1; index >= 0; index -= 1) {
64
+ const event = events[index]
65
+ if (event === undefined || event.type !== 'tool/call') continue
66
+ const name = typeof event.data.name === 'string' ? event.data.name : ''
67
+ if (name === '' || !isWriteTool(name)) continue
68
+ const path = argumentPath(typeof event.data.arguments === 'string' ? event.data.arguments : '')
69
+ if (path === undefined) continue
70
+ const existing = byPath.get(path)
71
+ if (existing === undefined) {
72
+ byPath.set(path, { path, tool: name, time: event.time, count: 1 })
73
+ } else {
74
+ existing.count += 1
75
+ }
76
+ }
77
+ return [...byPath.values()].sort((left, right) => right.time - left.time)
78
+ }
@@ -10,6 +10,8 @@
10
10
  * file changes appear without a manual refresh.
11
11
  */
12
12
  import { useCallback, useEffect, useRef, useState, type MouseEvent, type ReactNode } from 'react'
13
+ import clsx from 'clsx'
14
+ import { SessionLens } from './SessionLens.tsx'
13
15
  import {
14
16
  Button, IconBranchOutline16, IconCodeOutline16, IconCopyOutline16, IconRefreshOutline16,
15
17
  IconTrashOutline16, Input, Menu, Modal, writeClipboard,
@@ -90,6 +92,31 @@ export function GitView(props: {
90
92
  visible: boolean
91
93
  }) {
92
94
  const { scope, onOpenFile, onOpenDiff, visible } = props
95
+ // The unified changes tab's dual lens: the git view (this component's
96
+ // classic body) and the session lens (what the model wrote this session).
97
+ // Session-selected by default? No — git is the everyday surface; the lens
98
+ // is one click away.
99
+ const [lens, setLens] = useState<'git' | 'session'>('git')
100
+ const lensToggle = (
101
+ <div className={css.changesLensToggle} role="tablist" aria-label={t('changesLens')}>
102
+ <button
103
+ type="button"
104
+ className={clsx(css.changesLensTab, lens === 'git' && css.changesLensTabActive)}
105
+ aria-pressed={lens === 'git'}
106
+ onClick={() => { setLens('git') }}
107
+ >
108
+ {t('changesSessionGit')}
109
+ </button>
110
+ <button
111
+ type="button"
112
+ className={clsx(css.changesLensTab, lens === 'session' && css.changesLensTabActive)}
113
+ aria-pressed={lens === 'session'}
114
+ onClick={() => { setLens('session') }}
115
+ >
116
+ {t('changesSessionLens')}
117
+ </button>
118
+ </div>
119
+ )
93
120
  const [status, setStatus] = useState<GitStatusResult | null>(null)
94
121
  const [worktrees, setWorktrees] = useState<GitWorktree[]>([])
95
122
  const [selectedWorktree, setSelectedWorktree] = useState<string | undefined>()
@@ -411,8 +438,18 @@ export function GitView(props: {
411
438
  )
412
439
  }
413
440
 
441
+ if (lens === 'session') {
442
+ return (
443
+ <div className={css.git}>
444
+ {lensToggle}
445
+ <SessionLens scope={scope} />
446
+ </div>
447
+ )
448
+ }
449
+
414
450
  return (
415
451
  <div className={css.git}>
452
+ {lensToggle}
416
453
  {worktrees.length > 1 && (
417
454
  <div className={css.gitWorktreeRow}>
418
455
  <span className={css.gitWorktreeLabel}>{t('worktree')}</span>
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The session lens: file operations the model performed in this session,
3
+ * parsed from the session's own event log (`changes.ops`). Clicking a row
4
+ * expands a best-effort text preview of the file (read through `fs.read`,
5
+ * passed through the secret-redaction layer). Kept a leaf component — the
6
+ * GitView hosts it as the "session changes" lens of the unified tab.
7
+ */
8
+ import { useCallback, useEffect, useState } from 'react'
9
+ import { IconRefreshOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
10
+ import { api, type SessionScope } from './api.ts'
11
+ import { redactSecrets } from './redact.ts'
12
+ import { t } from './locales.ts'
13
+ import css from './sidebar.module.css'
14
+
15
+ /** One deduplicated file operation row (the host's `changes.ops` payload). */
16
+ interface SessionFileOp {
17
+ path: string
18
+ tool: string
19
+ time: number
20
+ count: number
21
+ }
22
+
23
+ /** Preview cap: a long file shows its head only (the sidebar is not an editor). */
24
+ const PREVIEW_CHARS = 20_000
25
+
26
+ export function SessionLens(props: { scope: SessionScope }) {
27
+ const { scope } = props
28
+ const [ops, setOps] = useState<SessionFileOp[] | null>(null)
29
+ const [error, setError] = useState<string | null>(null)
30
+ const [openPath, setOpenPath] = useState<string | null>(null)
31
+ const [preview, setPreview] = useState<string | null>(null)
32
+ const [previewLoading, setPreviewLoading] = useState(false)
33
+
34
+ const load = useCallback(async (): Promise<void> => {
35
+ setError(null)
36
+ try {
37
+ const result = await api.changesOps(scope)
38
+ setOps(result.ops)
39
+ } catch (reason) {
40
+ setError(reason instanceof Error ? reason.message : String(reason))
41
+ }
42
+ }, [scope])
43
+
44
+ useEffect(() => { void load() }, [load])
45
+
46
+ /** Toggle one row's preview: fetch + redact on first open, cached after. */
47
+ const togglePreview = (path: string): void => {
48
+ if (openPath === path) {
49
+ setOpenPath(null)
50
+ return
51
+ }
52
+ setOpenPath(path)
53
+ setPreview(null)
54
+ setPreviewLoading(true)
55
+ api.fsRead(scope, path).then((result) => {
56
+ const text = result.kind === 'text' ? redactSecrets(result.content) : t('changesBinary')
57
+ setPreview(text.slice(0, PREVIEW_CHARS))
58
+ }).catch((reason: unknown) => {
59
+ setPreview(t('changesPreviewError', { message: reason instanceof Error ? reason.message : String(reason) }))
60
+ }).finally(() => { setPreviewLoading(false) })
61
+ }
62
+
63
+ return (
64
+ <div className={css.sessionLens}>
65
+ <div className={css.sessionLensBar}>
66
+ <span className={css.sessionLensCount}>{ops === null ? t('loading') : t('changesCount', { count: ops.length })}</span>
67
+ <button type="button" className={css.iconButton} aria-label={t('refresh')} title={t('refresh')} onClick={() => { void load() }}>
68
+ <IconRefreshOutline16 size={14} />
69
+ </button>
70
+ </div>
71
+ {error !== null && <div className={css.sessionLensEmpty}>{error}</div>}
72
+ {error === null && ops !== null && ops.length === 0 && (
73
+ <div className={css.sessionLensEmpty}>{t('changesEmpty')}</div>
74
+ )}
75
+ {ops !== null && ops.map(op => (
76
+ <div key={op.path} className={css.sessionLensItem}>
77
+ <button
78
+ type="button"
79
+ className={css.sessionLensRow}
80
+ aria-expanded={openPath === op.path}
81
+ onClick={() => { togglePreview(op.path) }}
82
+ >
83
+ <span className={css.sessionLensPath} title={op.path}>{op.path}</span>
84
+ <span className={css.sessionLensMeta}>{op.tool}{op.count > 1 ? ` ×${op.count}` : ''}</span>
85
+ </button>
86
+ {openPath === op.path && (
87
+ <div className={css.sessionLensPreview}>
88
+ {previewLoading ? t('loading') : preview ?? ''}
89
+ </div>
90
+ )}
91
+ </div>
92
+ ))}
93
+ </div>
94
+ )
95
+ }
package/src/client/api.ts CHANGED
@@ -278,6 +278,10 @@ export const api = {
278
278
  * banner's skip button). Idempotent: {skipped:0} when none is active. */
279
279
  agentSkipWait: (uuid: string) =>
280
280
  call<{ ok: true; skipped: number }>('agent-pty.skip-wait', { uuid }),
281
+ /** The session lens: file operations the model performed in one session
282
+ * (parsed from the session's own event log; newest first). */
283
+ changesOps: (scope: SessionScope, signal?: AbortSignal) =>
284
+ call<{ ops: Array<{ path: string; tool: string; time: number; count: number }> }>('changes.ops', scopePayload(scope, {}), signal),
281
285
  /** Terminal dependency status (issue #140): after a WS close 1011 with
282
286
  * reason `pty-deps-missing` the view fetches the full repair details here
283
287
  * (the close reason itself is capped at 123 bytes). */
@@ -168,6 +168,14 @@ export const ar: Record<string, string> = {
168
168
  deleteDescFile: 'سيتم حذف هذا الملف نهائيًا ولا يمكن التراجع.',
169
169
  deleteDescDir: 'سيتم حذف هذا الدليل ومحتواه نهائيًا ولا يمكن التراجع.',
170
170
  dismiss: 'إغلاق',
171
+ changesSessionGit: 'تغييرات Git',
172
+ changesSessionLens: 'تغييرات الجلسة',
173
+ changesEmpty: 'لا عمليات ملفات في هذه الجلسة',
174
+ changesCount: '{count} عملية ملف',
175
+ changesRedacted: '[مُنقَّح]',
176
+ changesBinary: 'ملف ثنائي — لا معاينة',
177
+ changesPreviewError: 'فشل قراءة المعاينة: {message}',
178
+ changesLens: 'عرض',
171
179
  exited: 'خرجت عملية الطرفية',
172
180
  noSession: 'اختر محادثة لاستخدام الشريط الجانبي',
173
181
  pluginNotLoaded: 'الإضافة غير محمّلة؛ التبويب غير متاح:',
@@ -153,6 +153,14 @@ export const de: Record<string, string> = {
153
153
  deleteDescFile: 'Diese Datei wird endgültig gelöscht. Dies kann nicht rückgängig gemacht werden.',
154
154
  deleteDescDir: 'Dieses Verzeichnis und sein gesamter Inhalt werden endgültig gelöscht. Dies kann nicht rückgängig gemacht werden.',
155
155
  dismiss: 'Schließen',
156
+ changesSessionGit: 'Git-Änderungen',
157
+ changesSessionLens: 'Sitzungsänderungen',
158
+ changesEmpty: 'Keine Dateioperationen in dieser Sitzung',
159
+ changesCount: '{count} Dateioperationen',
160
+ changesRedacted: '[GESCHWÄRZT]',
161
+ changesBinary: 'Binärdatei – keine Vorschau',
162
+ changesPreviewError: 'Vorschau konnte nicht gelesen werden: {message}',
163
+ changesLens: 'Ansicht',
156
164
  exited: 'Terminalprozess beendet',
157
165
  noSession: 'Wählen Sie eine Sitzung, um die Seitenleiste zu verwenden',
158
166
  pluginNotLoaded: 'Plugin nicht geladen; Tab vorübergehend nicht verfügbar:',
@@ -160,6 +160,14 @@ export const fr: Record<string, string> = {
160
160
  deleteDescFile: 'Ce fichier sera définitivement supprimé. Action irréversible.',
161
161
  deleteDescDir: 'Ce répertoire et tout son contenu seront définitivement supprimés. Action irréversible.',
162
162
  dismiss: 'Fermer',
163
+ changesSessionGit: 'Modifications Git',
164
+ changesSessionLens: 'Modifications de session',
165
+ changesEmpty: 'Aucune opération de fichier dans cette session',
166
+ changesCount: '{count} opérations de fichier',
167
+ changesRedacted: '[MASQUÉ]',
168
+ changesBinary: 'Fichier binaire — pas d’aperçu',
169
+ changesPreviewError: 'Échec de lecture de l’aperçu : {message}',
170
+ changesLens: 'Vue',
163
171
  exited: 'Le processus du terminal s’est terminé',
164
172
  noSession: 'Sélectionnez une session pour utiliser la barre latérale',
165
173
  pluginNotLoaded: 'Plugin non chargé, onglet indisponible pour le moment :',
@@ -167,6 +167,14 @@ export const hi: Record<string, string> = {
167
167
  deleteDescFile: 'यह फ़ाइल स्थायी रूप से हट जाएगी। इसे वापस नहीं किया जा सकता।',
168
168
  deleteDescDir: 'यह निर्देशिका और उसकी सामग्री स्थायी रूप से हट जाएगी। इसे वापस नहीं किया जा सकता।',
169
169
  dismiss: 'बंद करें',
170
+ changesSessionGit: 'Git बदलाव',
171
+ changesSessionLens: 'सेशन बदलाव',
172
+ changesEmpty: 'इस सेशन में कोई फ़ाइल ऑपरेशन नहीं',
173
+ changesCount: '{count} फ़ाइल ऑपरेशन',
174
+ changesRedacted: '[मास्क किया गया]',
175
+ changesBinary: 'बाइनरी फ़ाइल — कोई पूर्वावलोकन नहीं',
176
+ changesPreviewError: 'पूर्वावलोकन पढ़ने में विफल: {message}',
177
+ changesLens: 'दृश्य',
170
178
  exited: 'टर्मिनल प्रक्रिया बाहर निकली',
171
179
  noSession: 'साइडबार उपयोग करने के लिए एक वार्तालाप चुनें',
172
180
  pluginNotLoaded: 'प्लगइन लोड नहीं; टैब अनुपलब्ध:',
@@ -165,6 +165,14 @@ export const id: Record<string, string> = {
165
165
  deleteDescFile: 'File ini dihapus permanen dan tidak dapat dibatalkan.',
166
166
  deleteDescDir: 'Direktori ini dan seluruh isinya dihapus permanen dan tidak dapat dibatalkan.',
167
167
  dismiss: 'Tutup',
168
+ changesSessionGit: 'Perubahan Git',
169
+ changesSessionLens: 'Perubahan sesi',
170
+ changesEmpty: 'Tidak ada operasi file di sesi ini',
171
+ changesCount: '{count} operasi file',
172
+ changesRedacted: '[DISENSOR]',
173
+ changesBinary: 'File biner — tanpa pratinjau',
174
+ changesPreviewError: 'Gagal membaca pratinjau: {message}',
175
+ changesLens: 'Tampilan',
168
176
  exited: 'Proses terminal keluar',
169
177
  noSession: 'Pilih obrolan untuk menggunakan sidebar',
170
178
  pluginNotLoaded: 'Plugin tidak dimuat; tab tidak tersedia untuk sementara:',
@@ -158,6 +158,14 @@ export const it: Record<string, string> = {
158
158
  deleteDescFile: 'Il file verrà eliminato definitivamente. Operazione irreversibile.',
159
159
  deleteDescDir: 'La directory e tutto il suo contenuto verranno eliminati definitivamente. Operazione irreversibile.',
160
160
  dismiss: 'Chiudi',
161
+ changesSessionGit: 'Modifiche Git',
162
+ changesSessionLens: 'Modifiche di sessione',
163
+ changesEmpty: 'Nessuna operazione su file in questa sessione',
164
+ changesCount: '{count} operazioni su file',
165
+ changesRedacted: '[OSCURATO]',
166
+ changesBinary: 'File binario — nessuna anteprima',
167
+ changesPreviewError: 'Lettura anteprima non riuscita: {message}',
168
+ changesLens: 'Vista',
161
169
  exited: 'Il processo del terminale è terminato',
162
170
  noSession: 'Selezioni una conversazione per usare la barra laterale',
163
171
  pluginNotLoaded: 'Plugin non caricato; scheda non disponibile:',
@@ -167,6 +167,14 @@ export const ja: Record<string, string> = {
167
167
  deleteDescFile: 'このファイルは完全に削除されます。元に戻せません。',
168
168
  deleteDescDir: 'このディレクトリとその内容は完全に削除されます。元に戻せません。',
169
169
  dismiss: '閉じる',
170
+ changesSessionGit: 'Git 変更',
171
+ changesSessionLens: 'セッション変更',
172
+ changesEmpty: 'このセッションにはファイル操作がありません',
173
+ changesCount: '{count} 件のファイル操作',
174
+ changesRedacted: '[マスク済み]',
175
+ changesBinary: 'バイナリファイルのためプレビューできません',
176
+ changesPreviewError: 'プレビューの読み取りに失敗: {message}',
177
+ changesLens: 'ビュー',
170
178
  exited: 'ターミナルプロセスが終了しました',
171
179
  noSession: 'サイドバーを使うには会話を選択してください',
172
180
  pluginNotLoaded: 'プラグイン未読み込み、タブは一時的に利用不可:',
@@ -159,6 +159,14 @@ export const ko: Record<string, string> = {
159
159
  deleteDescFile: '이 파일은 영구 삭제되며 되돌릴 수 없습니다.',
160
160
  deleteDescDir: '이 디렉터리와 그 내용이 영구 삭제되며 되돌릴 수 없습니다.',
161
161
  dismiss: '닫기',
162
+ changesSessionGit: 'Git 변경',
163
+ changesSessionLens: '세션 변경',
164
+ changesEmpty: '이 세션에는 파일 작업이 없습니다',
165
+ changesCount: '파일 작업 {count}건',
166
+ changesRedacted: '[마스킹됨]',
167
+ changesBinary: '바이너리 파일 — 미리보기 없음',
168
+ changesPreviewError: '미리보기 읽기 실패: {message}',
169
+ changesLens: '보기',
162
170
  exited: '터미널 프로세스가 종료되었습니다',
163
171
  noSession: '사이드바를 사용하려면 대화를 선택하세요',
164
172
  pluginNotLoaded: '플러그인이 로드되지 않아 탭을 지금 사용할 수 없습니다:',
@@ -165,6 +165,14 @@ export const nl: Record<string, string> = {
165
165
  deleteDescFile: 'Dit verwijdert het bestand definitief. Dit kan niet ongedaan worden gemaakt.',
166
166
  deleteDescDir: 'Dit verwijdert de map en de volledige inhoud definitief. Dit kan niet ongedaan worden gemaakt.',
167
167
  dismiss: 'Sluiten',
168
+ changesSessionGit: 'Git-wijzigingen',
169
+ changesSessionLens: 'Sessiewijzigingen',
170
+ changesEmpty: 'Geen bestandsbewerkingen in deze sessie',
171
+ changesCount: '{count} bestandsbewerkingen',
172
+ changesRedacted: '[GEREDICEERD]',
173
+ changesBinary: 'Binair bestand — geen voorbeeldweergave',
174
+ changesPreviewError: 'Voorbeeldweergave lezen mislukt: {message}',
175
+ changesLens: 'Weergave',
168
176
  exited: 'Terminalproces beëindigd',
169
177
  noSession: 'Selecteer een conversatie om de zijbalk te gebruiken',
170
178
  pluginNotLoaded: 'Plugin niet geladen; tabblad niet beschikbaar:',
@@ -169,6 +169,14 @@ export const pl: Record<string, string> = {
169
169
  deleteDescFile: 'Ten plik zostanie trwale usunięty. Nie można tego cofnąć.',
170
170
  deleteDescDir: 'Ten katalog i cała jego zawartość zostaną trwale usunięte. Nie można tego cofnąć.',
171
171
  dismiss: 'Zamknij',
172
+ changesSessionGit: 'Zmiany Git',
173
+ changesSessionLens: 'Zmiany sesji',
174
+ changesEmpty: 'Brak operacji na plikach w tej sesji',
175
+ changesCount: 'Operacji na plikach: {count}',
176
+ changesRedacted: '[ZREDAKOWANO]',
177
+ changesBinary: 'Plik binarny — brak podglądu',
178
+ changesPreviewError: 'Nie udało się odczytać podglądu: {message}',
179
+ changesLens: 'Widok',
172
180
  exited: 'Proces terminala zakończony',
173
181
  noSession: 'Wybierz rozmowę, aby korzystać z panelu bocznego',
174
182
  pluginNotLoaded: 'Wtyczka niezaładowana; karta chwilowo niedostępna:',
@@ -150,6 +150,14 @@ export const pt: Record<string, string> = {
150
150
  deleteDescFile: 'Isto elimina definitivamente o ficheiro. Não pode ser anulado.',
151
151
  deleteDescDir: 'Isto elimina definitivamente o diretório e todo o seu conteúdo. Não pode ser anulado.',
152
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',
153
161
  exited: 'O processo do terminal saiu',
154
162
  noSession: 'Selecione uma conversa para usar a barra lateral',
155
163
  pluginNotLoaded: 'Plugin não carregado; aba indisponível:',
@@ -163,6 +163,14 @@ export const ru: Record<string, string> = {
163
163
  deleteDescFile: 'Файл будет удалён безвозвратно. Действие необратимо.',
164
164
  deleteDescDir: 'Каталог и всё его содержимое будут удалены безвозвратно. Действие необратимо.',
165
165
  dismiss: 'Закрыть',
166
+ changesSessionGit: 'Изменения Git',
167
+ changesSessionLens: 'Изменения сессии',
168
+ changesEmpty: 'В этой сессии нет операций с файлами',
169
+ changesCount: 'Операций с файлами: {count}',
170
+ changesRedacted: '[СКРЫТО]',
171
+ changesBinary: 'Двоичный файл — предпросмотр недоступен',
172
+ changesPreviewError: 'Не удалось прочитать предпросмотр: {message}',
173
+ changesLens: 'Вид',
166
174
  exited: 'Процесс терминала завершился',
167
175
  noSession: 'Выберите сессию, чтобы использовать боковую панель',
168
176
  pluginNotLoaded: 'Плагин не загружен — вкладка недоступна:',
@@ -150,6 +150,14 @@ export const sv: Record<string, string> = {
150
150
  deleteDescFile: 'Filen tas bort permanent. Detta kan inte ångras.',
151
151
  deleteDescDir: 'Katalogen och allt i den tas bort permanent. Detta kan inte ångras.',
152
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',
153
161
  exited: 'Terminalprocess avslutad',
154
162
  noSession: 'Välj en konversation för att använda sidopanelen',
155
163
  pluginNotLoaded: 'Plugin inte laddad; flik otillgänglig:',
@@ -167,6 +167,14 @@ export const th: Record<string, string> = {
167
167
  deleteDescFile: 'ไฟล์นี้จะถูกลบอย่างถาวร ย้อนกลับไม่ได้',
168
168
  deleteDescDir: 'ไดเรกทอรีนี้และเนื้อหาทั้งหมดจะถูกลบอย่างถาวร ย้อนกลับไม่ได้',
169
169
  dismiss: 'ปิด',
170
+ changesSessionGit: 'การเปลี่ยนแปลง Git',
171
+ changesSessionLens: 'การเปลี่ยนแปลงในเซสชัน',
172
+ changesEmpty: 'ไม่มีการดำเนินการไฟล์ในเซสชันนี้',
173
+ changesCount: 'การดำเนินการไฟล์ {count} รายการ',
174
+ changesRedacted: '[ถูกปกปิด]',
175
+ changesBinary: 'ไฟล์ไบนารี — ไม่มีตัวอย่าง',
176
+ changesPreviewError: 'อ่านตัวอย่างไม่สำเร็จ: {message}',
177
+ changesLens: 'มุมมอง',
170
178
  exited: 'กระบวนการเทอร์มินัลออกแล้ว',
171
179
  noSession: 'เลือกแชทเพื่อใช้แถบด้านข้าง',
172
180
  pluginNotLoaded: 'ปลั๊กอินไม่ได้โหลด; tab ไม่พร้อมใช้งาน:',