dsh-coding-sidebar 1.0.7 → 1.0.9

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 (81) hide show
  1. package/README.md +2 -2
  2. package/lib/client-editor.js +485 -228
  3. package/lib/client-registry.js +1238 -7583
  4. package/lib/client-terminal.js +345 -189
  5. package/lib/client.js +1213 -7558
  6. package/lib/index.js +518 -69
  7. package/lib/types/agent-pty.d.ts +62 -4
  8. package/lib/types/bundle-route.d.ts +1 -1
  9. package/lib/types/client/DiffView.d.ts +33 -1
  10. package/lib/types/client/EditorHost.d.ts +4 -1
  11. package/lib/types/client/FileTree.d.ts +6 -2
  12. package/lib/types/client/TerminalWaitBanner.d.ts +6 -0
  13. package/lib/types/client/TreePanel.d.ts +4 -1
  14. package/lib/types/client/api.d.ts +26 -0
  15. package/lib/types/client/chunk-loader.d.ts +1 -1
  16. package/lib/types/client/chunks/locale.d.ts +3 -0
  17. package/lib/types/client/conversation-draft.d.ts +85 -4
  18. package/lib/types/client/locales.d.ts +13 -20
  19. package/lib/types/client/selection-popup.d.ts +58 -0
  20. package/lib/types/client/service.d.ts +1 -1
  21. package/lib/types/client/state.d.ts +15 -0
  22. package/lib/types/client/terminal-font.d.ts +25 -2
  23. package/lib/types/context-types.d.ts +3 -1
  24. package/lib/types/fs-operations.d.ts +42 -0
  25. package/lib/types/git.d.ts +15 -0
  26. package/lib/types/index.d.ts +9 -0
  27. package/lib/types/prefs-shared.d.ts +7 -5
  28. package/lib/types/pty-manager.d.ts +53 -0
  29. package/lib/types/wire.d.ts +6 -2
  30. package/package.json +1 -1
  31. package/src/agent-pty.ts +221 -33
  32. package/src/bundle-route.ts +1 -1
  33. package/src/client/DiffTab.tsx +10 -1
  34. package/src/client/DiffView.tsx +174 -19
  35. package/src/client/EditorHost.tsx +9 -2
  36. package/src/client/FileTree.tsx +151 -8
  37. package/src/client/Sidebar.tsx +95 -25
  38. package/src/client/TerminalView.tsx +41 -0
  39. package/src/client/TerminalWaitBanner.tsx +32 -0
  40. package/src/client/TextEditor.tsx +27 -45
  41. package/src/client/TreePanel.tsx +22 -2
  42. package/src/client/api.ts +20 -0
  43. package/src/client/chunk-loader.ts +1 -1
  44. package/src/client/chunks/locale.tsx +60 -0
  45. package/src/client/conversation-draft.ts +233 -7
  46. package/src/client/index.tsx +19 -8
  47. package/src/client/locales-ar.ts +17 -4
  48. package/src/client/locales-de.ts +17 -4
  49. package/src/client/locales-fr.ts +17 -4
  50. package/src/client/locales-hi.ts +17 -4
  51. package/src/client/locales-id.ts +17 -4
  52. package/src/client/locales-it.ts +17 -4
  53. package/src/client/locales-ja.ts +17 -4
  54. package/src/client/locales-ko.ts +17 -4
  55. package/src/client/locales-nl.ts +17 -4
  56. package/src/client/locales-pl.ts +17 -4
  57. package/src/client/locales-pt.ts +17 -4
  58. package/src/client/locales-ru.ts +17 -4
  59. package/src/client/locales-sv.ts +17 -4
  60. package/src/client/locales-th.ts +17 -4
  61. package/src/client/locales-tr.ts +17 -4
  62. package/src/client/locales-vi.ts +17 -4
  63. package/src/client/locales-zh-HK.ts +17 -4
  64. package/src/client/locales-zh-MO.ts +17 -4
  65. package/src/client/locales-zh-TW.ts +17 -4
  66. package/src/client/locales.ts +41 -51
  67. package/src/client/selection-popup.ts +155 -0
  68. package/src/client/service.ts +1 -1
  69. package/src/client/sidebar.module.css +68 -0
  70. package/src/client/state.ts +56 -10
  71. package/src/client/terminal-font.ts +34 -3
  72. package/src/context-types.ts +3 -2
  73. package/src/fs-operations.ts +126 -4
  74. package/src/git.ts +56 -3
  75. package/src/index.ts +82 -7
  76. package/src/open-external.ts +3 -4
  77. package/src/prefs-shared.ts +7 -5
  78. package/src/pty-manager.ts +176 -5
  79. package/src/sidechat-routes.ts +13 -1
  80. package/src/tools.ts +24 -6
  81. package/src/wire.ts +3 -0
@@ -15,6 +15,20 @@ export declare function clampDims(cols: number, rows: number): {
15
15
  cols: number;
16
16
  rows: number;
17
17
  };
18
+ /**
19
+ * Arm the Windows pre-ready resize gate for one freshly spawned pty.
20
+ * No-op on POSIX and for injected ptys without `onData`.
21
+ */
22
+ export declare function armPtyResizeGate(pty: IPty): void;
23
+ /**
24
+ * Best-effort resize for WebSocket-driven terminal views. Layout animation
25
+ * can briefly produce unusable dimensions, and node-pty can reject a resize
26
+ * after the socket setup's outer try/catch has returned. Ignore that one
27
+ * frame so the host stays alive and a later valid measurement can retry.
28
+ * Returns whether node-pty accepted the resize (or parked it for replay on
29
+ * the first output — the Windows pre-ready window).
30
+ */
31
+ export declare function tryResizePty(pty: Pick<IPty, 'resize'>, cols: number, rows: number): boolean;
18
32
  /**
19
33
  * Serializable snapshot of one agent terminal — the shape the model sees
20
34
  * through `terminal_list` and the sidebar sees through the push endpoint.
@@ -36,6 +50,15 @@ export interface AgentTerminalSnapshot {
36
50
  exitCode?: number | null;
37
51
  /** Exit signal name if the process was killed by a signal; null otherwise. */
38
52
  exitSignal?: string | null;
53
+ /**
54
+ * The model's active `terminal_wait_for` on this terminal (the sidebar
55
+ * renders the wait banner from it). Present only while a wait is
56
+ * registered; carries the LATEST wait when several overlap.
57
+ */
58
+ waiting?: {
59
+ needle: string;
60
+ since: number;
61
+ };
39
62
  }
40
63
  /** One live agent terminal. */
41
64
  export interface AgentTerminalHandle {
@@ -59,6 +82,17 @@ export interface AgentTerminalHandle {
59
82
  exitCode?: number | null;
60
83
  /** Exit signal number once known (POSIX only; undefined on Windows). */
61
84
  exitSignal?: number | null;
85
+ /** Active wait_for registrations (skip bookkeeping; empty while idle). */
86
+ waits: AgentTerminalActiveWait[];
87
+ }
88
+ /** One active wait_for registration on a handle (banner + skip bookkeeping). */
89
+ export interface AgentTerminalActiveWait {
90
+ /** The needle being awaited (shown on the sidebar wait banner). */
91
+ needle: string;
92
+ /** Epoch ms when the wait registered (age display / debugging). */
93
+ since: number;
94
+ /** Flipped by `skipWait()`; the waiting poll returns `skipped` within one tick. */
95
+ skipped: boolean;
62
96
  }
63
97
  /** Read result shape (mirrors the official tool-pty terminal_read contract). */
64
98
  export interface AgentTerminalReadResult {
@@ -73,14 +107,18 @@ export interface AgentTerminalReadResult {
73
107
  }
74
108
  /** Outcome of {@link AgentPtyRegistry.waitFor}. */
75
109
  export type AgentTerminalWaitResult = {
76
- /** The needle was found in the transcript. */
110
+ /** The pattern that was awaited. */
77
111
  kind: 'found';
78
- /** The matched substring. */
79
112
  needle: string;
80
113
  /** 0-based line index (in the retained transcript) where the needle first appeared. */
81
114
  line: number;
82
115
  /** 0-based column index within that line where the match starts. */
83
116
  column: number;
117
+ /**
118
+ * The text that actually matched — for multi-outcome patterns
119
+ * (e.g. `(BUILD_OK|BUILD_FAIL)`) this tells which alternative matched.
120
+ */
121
+ match: string;
84
122
  /** Elapsed wall-clock milliseconds from the wait start to the match. */
85
123
  elapsedMs: number;
86
124
  } | {
@@ -101,6 +139,11 @@ export type AgentTerminalWaitResult = {
101
139
  exitCode?: number | null;
102
140
  /** The exit signal name, if the process was killed by a signal. */
103
141
  exitSignal?: string | null;
142
+ } | {
143
+ /** The user skipped the wait from the sidebar banner. */
144
+ kind: 'skipped';
145
+ /** The needle that was awaited. */
146
+ needle: string;
104
147
  };
105
148
  /** Snapshot projection of a handle (drops the pty reference and transcript). */
106
149
  export declare function snapshotOf(handle: AgentTerminalHandle): AgentTerminalSnapshot;
@@ -181,12 +224,27 @@ export declare class AgentPtyRegistry {
181
224
  * make event-driven wakeups unreliable. A 50ms poll is fast enough for
182
225
  * interactive use and simple enough to be obviously correct.
183
226
  * @param uuid - terminal to watch.
184
- * @param needle - substring to search for (case-sensitive, verbatim).
227
+ * @param needle - JavaScript regular expression to search for
228
+ * (case-sensitive); a pattern that fails to compile falls back to
229
+ * verbatim substring matching. May cover several outcomes at once
230
+ * (e.g. `(BUILD_OK|BUILD_FAIL)` for build success vs failure) — the
231
+ * returned `match` reports the text that actually matched, so callers
232
+ * can tell which outcome hit.
185
233
  * @param timeoutMs - max wait; default 10000 (10s). Clamped to ≥100ms.
186
234
  * @param signal - caller-owned cancellation; aborts the wait re-throwing.
187
- * @returns one of `found` / `timeout` / `exited`.
235
+ * A wait can also be skipped by the user from the sidebar banner
236
+ * (`skipWait`), which resolves it with `{kind:'skipped'}`.
237
+ * @returns one of `found` / `timeout` / `exited` / `skipped`.
188
238
  */
189
239
  waitFor(uuid: string, needle: string, timeoutMs?: number, signal?: AbortSignal): Promise<AgentTerminalWaitResult>;
240
+ /**
241
+ * Mark every active wait on one terminal as skipped (the sidebar banner's
242
+ * skip button). Each waiting poll loop observes its record's flag within
243
+ * one 50ms tick and returns `{kind:'skipped'}`. Idempotent: 0 when nothing
244
+ * is waiting (a stale banner racing a wait that already resolved).
245
+ * @returns the number of waits that transitioned to skipped.
246
+ */
247
+ skipWait(uuid: string): number;
190
248
  /**
191
249
  * Send a POSIX signal to a terminal's foreground process.
192
250
  *
@@ -1,6 +1,6 @@
1
1
  import type { Context, SidebarHttpRequest, SidebarHttpResponse } from './context-types.ts';
2
2
  /** The chunk names the client may request (mirror of src/client/chunk-loader.ts). */
3
- export declare const CHUNK_NAMES: readonly ["terminal", "editor"];
3
+ export declare const CHUNK_NAMES: readonly ["terminal", "editor", "locale"];
4
4
  export type ChunkName = (typeof CHUNK_NAMES)[number];
5
5
  /**
6
6
  * Build the /sidebar/bundle route handler. `fence` is the shared browser-
@@ -1,3 +1,5 @@
1
+ import { type SessionScope } from './api.ts';
2
+ import type { SidebarDiffRef } from './state.ts';
1
3
  /** One rendered diff line. */
2
4
  export interface DiffLine {
3
5
  kind: 'ctx' | 'del' | 'add' | 'meta';
@@ -41,11 +43,41 @@ export interface ParsedDiff {
41
43
  * can still draw its path.
42
44
  */
43
45
  export declare function parseUnifiedDiff(text: string): ParsedDiff;
46
+ /** The old/new line range one hidden gap spans (both sides derive from the
47
+ * surrounding hunk headers and their counted rows). */
48
+ interface DiffFoldRange {
49
+ oldStart: number;
50
+ oldEnd: number;
51
+ newStart: number;
52
+ newEnd: number;
53
+ }
54
+ /**
55
+ * Materialize a git gap fold's hidden rows from the two sides' full file
56
+ * contents, by the fold's known line ranges: the old side drives context
57
+ * rows (each mapped onto the new side through the fold's offset — a gap is
58
+ * an unchanged run, so the sides align), and new-side lines the old range
59
+ * never reaches become pure additions. Line numbers clip to the actual
60
+ * content (a no-newline file's ranges can overrun by one); `\r` endings
61
+ * survive verbatim, like git's own context lines.
62
+ */
63
+ export declare function foldRowsFromContents(fold: DiffFoldRange, oldContent: string, newContent: string): DiffLine[];
44
64
  export interface DiffViewProps {
45
65
  /** Unified diff text (`git.diff` or `git.commit-diff` payloads). */
46
66
  diff: string;
47
67
  /** Untracked-file content: when present, renders as a full-file addition instead of parsing. */
48
68
  untrackedPath?: string;
49
69
  untrackedContent?: string;
70
+ /**
71
+ * When present (a worktree/commit diff ref plus its scope), hunk gaps
72
+ * render an expandable fold: clicking resolves both sides' full contents
73
+ * (`git.fold-contents`) and materializes the hidden context rows. Absent
74
+ * (or an untracked full-addition render) — no fold rows at all.
75
+ */
76
+ foldSource?: {
77
+ scope: SessionScope;
78
+ ref: SidebarDiffRef;
79
+ cwd: string | undefined;
80
+ };
50
81
  }
51
- export declare function DiffView({ diff, untrackedPath, untrackedContent }: DiffViewProps): import("react").JSX.Element | null;
82
+ export declare function DiffView({ diff, untrackedPath, untrackedContent, foldSource }: DiffViewProps): import("react").JSX.Element | null;
83
+ export {};
@@ -9,5 +9,8 @@ export declare function EditorHost(props: {
9
9
  expanded: string[];
10
10
  revealed: string[];
11
11
  onToggleDir: (path: string) => void;
12
- onReferenceFile: (path: string) => void;
12
+ onReferenceFile: (path: string, isDir: boolean) => void;
13
+ /** Tree-row mutations (passed through to the file tree; absent → hidden). */
14
+ onPathRenamed?: (oldPath: string, newPath: string) => void;
15
+ onPathRemoved?: (path: string) => void;
13
16
  }): import("react").JSX.Element;
@@ -27,12 +27,16 @@ export declare function FileTree(props: {
27
27
  onOpenWith?: (targetId: string, path: string) => void;
28
28
  /** Toggle one target's pinned state (the submenu row's pushpin). */
29
29
  onToggleOpenWithPin?: (targetId: string) => void;
30
- /** Insert `@<relative path>` into the composer draft. */
31
- onReferenceFile: (path: string) => void;
30
+ /** Insert `@<relative path>` into the composer draft (file vs directory). */
31
+ onReferenceFile: (path: string, isDir: boolean) => void;
32
32
  /** Bump to wipe the level cache and reload the visible set. */
33
33
  refreshTick: number;
34
34
  /** Upload into `dir` (absolute, inside the workspace); runs in the caller. */
35
35
  onUploadRequest: (dir: string, items: UploadItem[]) => void;
36
36
  /** True while an upload is in flight (drops are ignored). */
37
37
  busy: boolean;
38
+ /** A tree row was renamed (retarget open tabs; absent → no rename entry). */
39
+ onPathRenamed?: (oldPath: string, newPath: string) => void;
40
+ /** A tree row was removed (close affected tabs; absent → no delete entry). */
41
+ onPathRemoved?: (path: string) => void;
38
42
  }): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ /** Truncate one needle for inline display (title attr carries the full text). */
2
+ export declare function truncateNeedle(needle: string): string;
3
+ export declare function TerminalWaitBanner(props: {
4
+ needle: string;
5
+ onSkip: () => void;
6
+ }): import("react").JSX.Element;
@@ -17,7 +17,10 @@ export declare function TreePanel(props: {
17
17
  openWithSsh?: boolean;
18
18
  onOpenWith?: (targetId: string, path: string) => void;
19
19
  onToggleOpenWithPin?: (targetId: string) => void;
20
- onReferenceFile: (path: string) => void;
20
+ onReferenceFile: (path: string, isDir: boolean) => void;
21
+ /** Tree-row mutations (passed through to the file tree; absent → hidden). */
22
+ onPathRenamed?: (oldPath: string, newPath: string) => void;
23
+ onPathRemoved?: (path: string) => void;
21
24
  /** Full-window presentation: the panel fills its host instead of docking
22
25
  * at a fixed width. */
23
26
  full?: boolean;
@@ -138,6 +138,16 @@ export declare const api: {
138
138
  fsWrite: (scope: SessionScope, path: string, content: string) => Promise<{
139
139
  ok: true;
140
140
  }>;
141
+ /** Rename one tree row within its directory (single-segment name; a
142
+ * destination-existence clash is a 409; symlink rows rename the link). */
143
+ fsRename: (scope: SessionScope, path: string, name: string) => Promise<{
144
+ path: string;
145
+ }>;
146
+ /** Delete one tree row permanently (recursive for directories; a symlink
147
+ * row unlinks the link only). */
148
+ fsRemove: (scope: SessionScope, path: string) => Promise<{
149
+ path: string;
150
+ }>;
141
151
  /** Upload one file's raw bytes into `dir` (keeps the folder tree via
142
152
  * `relativePath`); the host streams it under the session workspace. */
143
153
  uploadFile: (scope: SessionScope, dir: string, relativePath: string, body: Blob, signal?: AbortSignal) => Promise<{
@@ -171,6 +181,16 @@ export declare const api: {
171
181
  gitCommitDiff: (scope: SessionScope, hash: string, worktree?: string, signal?: AbortSignal) => Promise<{
172
182
  diff: string;
173
183
  }>;
184
+ /** Both sides' full file contents for a diff-fold expansion; a missing
185
+ * side is null (untracked / deleted) and the view degrades the fold. */
186
+ gitFoldContents: (scope: SessionScope, opts: {
187
+ path: string;
188
+ staged?: boolean;
189
+ hash?: string;
190
+ }, worktree?: string, signal?: AbortSignal) => Promise<{
191
+ old: string | null;
192
+ new: string | null;
193
+ }>;
174
194
  /** Discard the worktree changes of one file (the index is untouched). */
175
195
  gitDiscard: (scope: SessionScope, path: string, worktree?: string) => Promise<{
176
196
  ok: true;
@@ -193,6 +213,12 @@ export declare const api: {
193
213
  agentPtyClose: (uuid: string) => Promise<{
194
214
  ok: true;
195
215
  }>;
216
+ /** Skip every active terminal_wait_for on one agent terminal (the wait
217
+ * banner's skip button). Idempotent: {skipped:0} when none is active. */
218
+ agentSkipWait: (uuid: string) => Promise<{
219
+ ok: true;
220
+ skipped: number;
221
+ }>;
196
222
  /** Terminal dependency status (issue #140): after a WS close 1011 with
197
223
  * reason `pty-deps-missing` the view fetches the full repair details here
198
224
  * (the close reason itself is capped at 123 bytes). */
@@ -48,7 +48,7 @@
48
48
  * client.js); an edit that does land while a core HMR happens is caught by
49
49
  * the ETag comparison on the next activation.
50
50
  */
51
- export type ChunkName = 'terminal' | 'editor';
51
+ export type ChunkName = 'terminal' | 'editor' | 'locale';
52
52
  /** The module exports a chunk factory provides (namespace-ish record). */
53
53
  export type ChunkExports = Record<string, unknown>;
54
54
  /**
@@ -0,0 +1,3 @@
1
+ /** Every non-zh/en dictionary keyed by its override id, ready for the
2
+ * better-locale store's `register(ns, dicts)`. */
3
+ export declare const localeDicts: Record<string, Record<string, string>>;
@@ -1,14 +1,95 @@
1
1
  /**
2
- * Append text to the current session's composer draft through the
2
+ * Insert text into the current session's composer draft through the
3
3
  * conversation service — the shared path behind the explorer's @-reference
4
4
  * button and the viewer selection popup. The service is resolved lazily
5
5
  * through `ctx.get` (the inject-free read the app's own plugins use); a
6
6
  * missing service or scope degrades to a logged no-op, never a crash.
7
+ *
8
+ * File references additionally use DSH's own structured insert event
9
+ * (`slash/input-insert-reference`, see `insertFileReference`) instead of
10
+ * plain draft text: the native `@file` picker emits this event, and the
11
+ * conversation input machine mints one occurrence whose chip covers the
12
+ * whole reference. Plain text `@folder/file.ts` only ever gets DSH's
13
+ * folder-ref decoration (`@folder/`) — the file name stays undecorated — so
14
+ * plain append is the fallback, not the primary path, for files.
15
+ *
16
+ * Insert position (upstream issue #425): the draft store only exposes the
17
+ * whole string (`getSnapshot().draft` + `setDraft(text)`) — there is no
18
+ * caret API on the conversation service. The composer's `<textarea>` keeps
19
+ * its last selection even while unfocused, so the live caret is probed from
20
+ * the DOM (guarded by a value-sync check), and the text is spliced at that
21
+ * position, replacing any live selection — with whitespace-aware joins, an
22
+ * insert into the middle of a sentence keeps single-space separation like
23
+ * the append path. An unknown/stale caret falls back to appending at the
24
+ * end (the pre-fix behavior).
25
+ *
26
+ * The caret is also *restored* after the insert: committing a programmatic
27
+ * draft change resets the controlled textarea's caret (observed landing at
28
+ * the start of the value), which would make every later insert probe the
29
+ * reset position and drift the stack (A|B + C + D ended up as |DACB). The
30
+ * placement (`placeComposerCaretAfterInsert`) puts the caret right after the
31
+ * inserted text once the value commit lands — the index accounts for the
32
+ * separating space on the left, so stacked inserts stay at their running
33
+ * position (A|B + C + D → ACD|B).
7
34
  */
8
35
  import type { Context } from '../context-types.ts';
36
+ /** A resolved composer caret/selection in draft coordinates. */
37
+ export interface DraftCaret {
38
+ start: number;
39
+ end: number;
40
+ }
9
41
  /**
10
- * Append `text` to the session's composer draft (space-separated, like the
11
- * @-mentions). Returns false — and logs — when the conversation service or
12
- * the session scope is unavailable.
42
+ * The spliced draft string (see {@link spliceInsert}); pure string math.
43
+ */
44
+ export declare function insertAtCaret(draft: string, text: string, caret: DraftCaret | null): string;
45
+ /**
46
+ * Resolve the composer's live caret from its DOM `<textarea>`. The draft
47
+ * store has no caret API, so the sidebar reads the composed input's selection
48
+ * directly; the value-sync check (`el.value === draft`) discards stale or
49
+ * wrong-composer reads — a caret must never be applied against a draft it
50
+ * was not measured on.
51
+ *
52
+ * Returns null when the composer is missing, disabled/read-only, out of
53
+ * sync with the store draft, or has no measurable selection (odd hosts
54
+ * report null selectionStart/End).
55
+ */
56
+ export declare function probeComposerCaret(draft: string): DraftCaret | null;
57
+ /**
58
+ * Restore the composer caret to `caretIndex` after a programmatic
59
+ * `setDraft` commit. A controlled textarea update resets the caret (React
60
+ * commits the value asynchronously and the browser moves the caret to the
61
+ * start/end), so the placement is scheduled and retried across at most two
62
+ * animation frames (setTimeout fallback), and only applied when the textarea
63
+ * still matches `expectedDraft` — a newer edit or a different composer wins
64
+ * the race untouched. The caret is clamped into the value bounds, mirroring
65
+ * how browsers clamp type-in positions.
66
+ */
67
+ export declare function placeComposerCaretAfterInsert(expectedDraft: string, caretIndex: number): void;
68
+ /**
69
+ * Insert `text` into the session's composer draft at the composer's live
70
+ * caret (see {@link probeComposerCaret}), falling back to appending at the
71
+ * end when the caret cannot be resolved. Returns false — and logs — when the
72
+ * conversation service or the session scope is unavailable.
13
73
  */
14
74
  export declare function appendToDraft(ctx: Context, sessionId: string, text: string): boolean;
75
+ /**
76
+ * The DSH `@file` spelling for one relative path, mirroring the host grammar
77
+ * (`formatFileMention` in `@deepseek-ai/dsh-file-reference`): plain when
78
+ * there is no whitespace, quoted when there is, and `undefined` when the
79
+ * path contains a control character or an embedded quote the editor grammar
80
+ * cannot represent.
81
+ */
82
+ export declare function fileMention(relativePath: string): {
83
+ mention: string;
84
+ label: string;
85
+ } | undefined;
86
+ /**
87
+ * Insert one FILE reference as a structured chip (like DSH's own `@` picker).
88
+ * The chip displays `@<basename>` but serializes to `@<relative path>` on
89
+ * send, so the reference stays a single link from trigger to basename.
90
+ *
91
+ * Directories are NOT handled here: DSH's folder grammar wants the trailing
92
+ * slash as plain text (`@dir/`) so completion can descend, which
93
+ * `appendToDraft` already covers.
94
+ */
95
+ export declare function insertFileReference(ctx: Context, sessionId: string, relativePath: string): boolean;
@@ -94,6 +94,7 @@ export declare const zh: {
94
94
  terminalDepsFailed: string;
95
95
  terminalDepsHint: string;
96
96
  terminalDepsProfile: string;
97
+ terminalShellNotFound: string;
97
98
  refresh: string;
98
99
  refreshUnsavedConfirm: string;
99
100
  save: string;
@@ -158,6 +159,18 @@ export declare const zh: {
158
159
  producedOpen: string;
159
160
  showInFolder: string;
160
161
  disconnected: string;
162
+ terminalWaitBanner: string;
163
+ terminalSkipWait: string;
164
+ gitFoldExpand: string;
165
+ gitFoldLoading: string;
166
+ gitFoldFailed: string;
167
+ rename: string;
168
+ renameInvalid: string;
169
+ delete: string;
170
+ deleteTitle: string;
171
+ deleteDescFile: string;
172
+ deleteDescDir: string;
173
+ dismiss: string;
161
174
  exited: string;
162
175
  noSession: string;
163
176
  pluginNotLoaded: string;
@@ -362,26 +375,6 @@ export declare const en: Record<keyof typeof zh, string>;
362
375
  * (`'sidebar'` is taken by DSH's own ui-sidebar, hence this distinct name).
363
376
  */
364
377
  export declare const LOCALE_NS = "betterSidebar";
365
- /** The ja dictionary (key-set-equal to zh, enforced by the type annotation). */
366
- export declare const ja: Record<keyof typeof zh, string>;
367
- export declare const de: Record<keyof typeof zh, string>;
368
- export declare const fr: Record<keyof typeof zh, string>;
369
- export declare const pt: Record<keyof typeof zh, string>;
370
- export declare const ko: Record<keyof typeof zh, string>;
371
- export declare const ar: Record<keyof typeof zh, string>;
372
- export declare const hi: Record<keyof typeof zh, string>;
373
- export declare const id: Record<keyof typeof zh, string>;
374
- export declare const tr: Record<keyof typeof zh, string>;
375
- export declare const vi: Record<keyof typeof zh, string>;
376
- export declare const th: Record<keyof typeof zh, string>;
377
- export declare const ru: Record<keyof typeof zh, string>;
378
- export declare const it: Record<keyof typeof zh, string>;
379
- export declare const nl: Record<keyof typeof zh, string>;
380
- export declare const sv: Record<keyof typeof zh, string>;
381
- export declare const pl: Record<keyof typeof zh, string>;
382
- export declare const zhHK: Record<keyof typeof zh, string>;
383
- export declare const zhTW: Record<keyof typeof zh, string>;
384
- export declare const zhMO: Record<keyof typeof zh, string>;
385
378
  /**
386
379
  * The better-locale override store attached by the client apply
387
380
  * (absent → no override; the zh/en chain runs). The store's `active`
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The floating "add selection to conversation" popup shared by the text
3
+ * viewer: a viewport-anchored button portaled to `document.body`, kept alive
4
+ * across the selection gesture and committed on click.
5
+ *
6
+ * Dismissal contract (upstream issue #425): the popup must never outlive its
7
+ * editor surface. The sidebar keeps every tab MOUNTED — switching tabs only
8
+ * flips the pane cell to `display:none` and collapsing the panel translates
9
+ * it off-screen — while the portaled `position:fixed` button stays pinned to
10
+ * its viewport anchor, ignoring both. The caller already hides on surface
11
+ * scrolls, selection collapse and content swaps; this hook adds the global
12
+ * dismissal that covers everything else:
13
+ *
14
+ * - any `mousedown` outside the button (tab bar, composer, another pane,
15
+ * the collapse toggle, …) closes it;
16
+ * - `Escape` closes it;
17
+ * - the document going hidden (`visibilitychange`) or the window losing
18
+ * focus closes it;
19
+ * - an `IntersectionObserver` on the editor surface closes it as soon as the
20
+ * surface leaves the viewport — the tab-switch (`display:none`) and
21
+ * panel-collapse (translated off-screen) paths have no DOM events of their
22
+ * own, so the geometry signal is the only reliable one.
23
+ *
24
+ * The button's own `mousedown` is never treated as an outside click: the
25
+ * caller preventDefaults it to keep the selection/caret alive until the
26
+ * click commits (the hook's capture-phase listener runs first and must not
27
+ * hide for it).
28
+ */
29
+ import { type RefObject } from 'react';
30
+ /** The floating "add to conversation" action: payload + viewport anchor. */
31
+ export interface SelectionPopup {
32
+ insert: string;
33
+ left: number;
34
+ top: number;
35
+ }
36
+ export interface SelectionPopupOptions {
37
+ /** Commit the payload into the composer draft (button click). */
38
+ onCommit(insert: string): void;
39
+ /**
40
+ * The DOM surface that must stay on screen for the popup to live: the
41
+ * CodeMirror host element. Called lazily (refs are null until the content
42
+ * loads).
43
+ */
44
+ getSurface(): HTMLElement | null;
45
+ }
46
+ export interface SelectionPopupControls {
47
+ /** The current popup (null = hidden). */
48
+ popup: SelectionPopup | null;
49
+ /** Attach to the portaled button element. */
50
+ buttonRef: RefObject<HTMLButtonElement>;
51
+ /** Anchor the popup above a selection (viewport-clamped). */
52
+ show(insert: string, left: number, top: number): void;
53
+ /** Hide the popup (idempotent). */
54
+ hide(): void;
55
+ /** The button's click: commit the stored payload, then hide. */
56
+ commit(): void;
57
+ }
58
+ export declare function useSelectionPopup(options: SelectionPopupOptions): SelectionPopupControls;
@@ -132,7 +132,7 @@ export interface TabComponentProps {
132
132
  /** The explorer's reveal-highlight set (ExplorerView; "Show in folder" targets). */
133
133
  revealed?: string[];
134
134
  onToggleDir?: (path: string) => void;
135
- onReferenceFile?: (path: string) => void;
135
+ onReferenceFile?: (path: string, isDir: boolean) => void;
136
136
  onOpenFile?: (path: string) => void;
137
137
  onOpenDiff?: (tab: SidebarTab) => void;
138
138
  onSubagentJump?: (childSessionId: string) => void;
@@ -110,6 +110,17 @@ export interface SidebarState {
110
110
  splits: SplitNode;
111
111
  /** Free windows (tabs dragged out onto the conversation area). */
112
112
  floats: FloatWindow[];
113
+ /**
114
+ * Live agent-terminal wait state (uuid → the wait the model currently
115
+ * blocks on in `terminal_wait_for`), mirrored from the host's
116
+ * agent-terminals push. Transient by design: sanitizeState never restores
117
+ * it, so a reload starts clean and the next push (sent immediately on WS
118
+ * attach) repopulates it.
119
+ */
120
+ agentWaits: Record<string, {
121
+ needle: string;
122
+ since: number;
123
+ }>;
113
124
  }
114
125
  export declare const PANEL_MIN = 280;
115
126
  export declare const PANEL_MAX = 640;
@@ -325,6 +336,10 @@ export declare function agentTabId(uuid: string): string;
325
336
  export declare function reconcileAgentTerminals(state: SidebarState, agentTerminals: ReadonlyArray<{
326
337
  uuid: string;
327
338
  title: string;
339
+ waiting?: {
340
+ needle: string;
341
+ since: number;
342
+ } | null;
328
343
  }>): SidebarState;
329
344
  /** Immutable snapshot handed to React (replaced only on real changes). */
330
345
  export interface SidebarSnapshot {
@@ -60,12 +60,35 @@ export declare const ICON_FONT_FALLBACKS: readonly string[];
60
60
  * @returns the stack with icon fallbacks merged in.
61
61
  */
62
62
  export declare function withIconFontFallbacks(stack: string): string;
63
+ /**
64
+ * Guarantee the stack ends in a generic family, so a font the browser cannot
65
+ * resolve degrades to a monospace one.
66
+ *
67
+ * Without this a stack of unresolvable names (a misspelled family, or one only
68
+ * installed on the machine running the shell rather than the one running the
69
+ * browser) falls through to the browser's *standard* font, which is
70
+ * proportional — xterm then measures its cell from a proportional advance and
71
+ * the whole grid breaks, rather than merely losing the requested typeface.
72
+ *
73
+ * This does not sniff whether the named families exist (an explicit non-goal
74
+ * of the terminal font design): it only terminates the stack. A stack that
75
+ * already names a generic family is returned untouched, so a user who
76
+ * deliberately wrote one keeps it. Generics are matched unquoted only — a
77
+ * quoted `"monospace"` is a family *name*, not the keyword.
78
+ *
79
+ * @param stack - the resolved font-family value.
80
+ * @returns the stack, ending in a generic family.
81
+ */
82
+ export declare function withMonospaceFallback(stack: string): string;
63
83
  /**
64
84
  * Resolve the xterm font options for the given prefs.
65
85
  *
66
86
  * The base family keeps its existing precedence — user pref > theme code
67
- * font > built-in stack — and then {@link withIconFontFallbacks} tops it up
68
- * so prompt icons resolve regardless of which base won.
87
+ * font > built-in stack. {@link withMonospaceFallback} then terminates the
88
+ * stack with a generic family so an unresolvable name degrades to a
89
+ * monospace instead of the proportional standard font, and finally
90
+ * {@link withIconFontFallbacks} tops it up so prompt icons resolve
91
+ * regardless of which base won.
69
92
  *
70
93
  * @param prefs - the current side card preferences.
71
94
  * @param themeFontFamily - the app's theme code font (`--ds-font-family-code`
@@ -430,10 +430,12 @@ export interface SidebarLocaleService {
430
430
  }
431
431
  /** The composer draft face the sidebar reaches through `ctx.conversation.input`. */
432
432
  export interface SidebarSessionInput {
433
- /** The live input store (draft read for append). */
433
+ /** The live input store (draft read for append). `draftRev` is the machine's
434
+ * span-CAS revision — required to mint a structured file-reference chip. */
434
435
  state: {
435
436
  getSnapshot(): {
436
437
  draft: string;
438
+ draftRev?: number;
437
439
  };
438
440
  };
439
441
  /** Replace the draft text (the input machine's single public write path). */
@@ -26,3 +26,45 @@ export declare function writeWorkspaceUpload(input: WorkspaceUploadInput): Promi
26
26
  path: string;
27
27
  size: number;
28
28
  }>;
29
+ /** Inputs of one tree-row rename. */
30
+ export interface WorkspaceRenameInput {
31
+ /** The session workspace root; the renamed entry must stay inside it. */
32
+ cwd: string;
33
+ /** Absolute path of the row as the tree displays it (may be a symlink). */
34
+ path: string;
35
+ /** The new base name (single segment — rename never moves across directories). */
36
+ name: string;
37
+ }
38
+ /**
39
+ * Rename one tree row within its directory: `path` → `<parent>/<name>`.
40
+ * The new name must be a single path segment (this is rename, not move);
41
+ * an existing destination is refused (POSIX rename would clobber it
42
+ * silently); the workspace root itself is never renamable; a symlink row
43
+ * renames the link, not its target. A no-op rename (same name) succeeds
44
+ * without touching the filesystem.
45
+ *
46
+ * @throws SidebarError with a wire code for shape, containment, existence
47
+ * and root failures.
48
+ */
49
+ export declare function renameWorkspaceEntry(input: WorkspaceRenameInput): Promise<{
50
+ path: string;
51
+ }>;
52
+ /** Inputs of one tree-row delete. */
53
+ export interface WorkspaceRemoveInput {
54
+ /** The session workspace root; the removed entry must stay inside it. */
55
+ cwd: string;
56
+ /** Absolute path of the row as the tree displays it (may be a symlink). */
57
+ path: string;
58
+ }
59
+ /**
60
+ * Delete one tree row permanently (there is no trash on the host): files are
61
+ * unlinked, directories removed recursively, a symlink row unlinks the LINK
62
+ * only (lstat decides, so a link to a directory does not recurse into its
63
+ * target). The workspace root itself is never removable.
64
+ *
65
+ * @throws SidebarError with a wire code for containment, existence and
66
+ * root failures.
67
+ */
68
+ export declare function removeWorkspaceEntry(input: WorkspaceRemoveInput): Promise<{
69
+ path: string;
70
+ }>;