dsh-coding-sidebar 1.0.6 → 1.0.8
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.
- package/README.md +2 -2
- package/lib/client-editor.js +436 -227
- package/lib/client-registry.js +639 -7397
- package/lib/client-terminal.js +239 -187
- package/lib/client.js +644 -7402
- package/lib/index.js +367 -67
- package/lib/types/agent-pty.d.ts +62 -4
- package/lib/types/bundle-route.d.ts +1 -1
- package/lib/types/client/EditorHost.d.ts +1 -1
- package/lib/types/client/FileTree.d.ts +2 -2
- package/lib/types/client/TreePanel.d.ts +1 -1
- package/lib/types/client/chunk-loader.d.ts +1 -1
- package/lib/types/client/chunks/locale.d.ts +3 -0
- package/lib/types/client/conversation-draft.d.ts +85 -4
- package/lib/types/client/locales.d.ts +1 -20
- package/lib/types/client/selection-popup.d.ts +58 -0
- package/lib/types/client/service.d.ts +1 -1
- package/lib/types/client/terminal-font.d.ts +25 -2
- package/lib/types/context-types.d.ts +3 -1
- package/lib/types/index.d.ts +9 -0
- package/lib/types/prefs-shared.d.ts +7 -5
- package/lib/types/pty-manager.d.ts +53 -0
- package/lib/types/wire.d.ts +6 -2
- package/package.json +1 -1
- package/src/agent-pty.ts +221 -33
- package/src/bundle-route.ts +1 -1
- package/src/client/BrowserView.tsx +17 -9
- package/src/client/EditorHost.tsx +1 -1
- package/src/client/FileTree.tsx +4 -4
- package/src/client/Sidebar.tsx +41 -22
- package/src/client/TerminalView.tsx +13 -0
- package/src/client/TextEditor.tsx +27 -45
- package/src/client/TreePanel.tsx +16 -1
- package/src/client/chunk-loader.ts +1 -1
- package/src/client/chunks/locale.tsx +60 -0
- package/src/client/conversation-draft.ts +233 -7
- package/src/client/index.tsx +19 -8
- package/src/client/locales-ar.ts +5 -4
- package/src/client/locales-de.ts +5 -4
- package/src/client/locales-fr.ts +5 -4
- package/src/client/locales-hi.ts +5 -4
- package/src/client/locales-id.ts +5 -4
- package/src/client/locales-it.ts +5 -4
- package/src/client/locales-ja.ts +5 -4
- package/src/client/locales-ko.ts +5 -4
- package/src/client/locales-nl.ts +5 -4
- package/src/client/locales-pl.ts +5 -4
- package/src/client/locales-pt.ts +5 -4
- package/src/client/locales-ru.ts +5 -4
- package/src/client/locales-sv.ts +5 -4
- package/src/client/locales-th.ts +5 -4
- package/src/client/locales-tr.ts +5 -4
- package/src/client/locales-vi.ts +5 -4
- package/src/client/locales-zh-HK.ts +5 -4
- package/src/client/locales-zh-MO.ts +5 -4
- package/src/client/locales-zh-TW.ts +5 -4
- package/src/client/locales.ts +17 -51
- package/src/client/selection-popup.ts +155 -0
- package/src/client/service.ts +1 -1
- package/src/client/state.ts +14 -7
- package/src/client/terminal-font.ts +34 -3
- package/src/context-types.ts +3 -2
- package/src/git.ts +17 -1
- package/src/index.ts +39 -6
- package/src/open-external.ts +3 -4
- package/src/prefs-shared.ts +7 -5
- package/src/pty-manager.ts +176 -5
- package/src/sidechat-routes.ts +13 -1
- package/src/tools.ts +24 -6
- package/src/wire.ts +3 -0
package/lib/types/agent-pty.d.ts
CHANGED
|
@@ -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
|
|
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 -
|
|
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
|
-
*
|
|
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-
|
|
@@ -27,8 +27,8 @@ 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. */
|
|
@@ -17,7 +17,7 @@ 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
21
|
/** Full-window presentation: the panel fills its host instead of docking
|
|
22
22
|
* at a fixed width. */
|
|
23
23
|
full?: boolean;
|
|
@@ -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
|
/**
|
|
@@ -1,14 +1,95 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
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;
|
|
@@ -362,26 +363,6 @@ export declare const en: Record<keyof typeof zh, string>;
|
|
|
362
363
|
* (`'sidebar'` is taken by DSH's own ui-sidebar, hence this distinct name).
|
|
363
364
|
*/
|
|
364
365
|
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
366
|
/**
|
|
386
367
|
* The better-locale override store attached by the client apply
|
|
387
368
|
* (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;
|
|
@@ -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
|
|
68
|
-
*
|
|
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). */
|
package/lib/types/index.d.ts
CHANGED
|
@@ -45,3 +45,12 @@ export interface SidebarSettingsFace {
|
|
|
45
45
|
* {@link resolveSidebarConfig}.
|
|
46
46
|
*/
|
|
47
47
|
export declare function apply(ctx: Context, config?: SidebarConfig): void;
|
|
48
|
+
/**
|
|
49
|
+
* The WS close reason for a failed terminal attach. A missing configured
|
|
50
|
+
* shell gets a SHORT machine-readable marker (`shell-not-found:<name>`,
|
|
51
|
+
* capped by BYTES — a WS close reason allows at most 123 bytes, which `ws`
|
|
52
|
+
* validates with `Buffer.byteLength`) that the client maps to a localized,
|
|
53
|
+
* actionable banner; every other failure keeps the raw message (the
|
|
54
|
+
* model-side tool errors read it verbatim).
|
|
55
|
+
*/
|
|
56
|
+
export declare function wsCloseReasonOf(error: unknown): string;
|
|
@@ -14,14 +14,16 @@ export interface SidebarPrefs {
|
|
|
14
14
|
/** Default panel width as a percent of the window width (20–60). */
|
|
15
15
|
defaultWidthPercent: number;
|
|
16
16
|
/**
|
|
17
|
-
* Whether the sidebar auto-activates
|
|
18
|
-
*
|
|
17
|
+
* Whether the sidebar auto-activates the Subagent page when the current
|
|
18
|
+
* conversation spawns a new subagent. Wide viewports also open the panel;
|
|
19
|
+
* narrow viewports prepare the tab without opening the full-screen drawer.
|
|
19
20
|
*/
|
|
20
21
|
autoOpenSubagent: boolean;
|
|
21
22
|
/**
|
|
22
|
-
* Whether the sidebar auto-activates
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* Whether the sidebar auto-activates the Jobs page when a NEW background
|
|
24
|
+
* job appears for the current conversation (any new job id, not just the
|
|
25
|
+
* first one). Wide viewports also open the panel; narrow viewports prepare
|
|
26
|
+
* the tab without opening the full-screen drawer.
|
|
25
27
|
*/
|
|
26
28
|
autoOpenJobs: boolean;
|
|
27
29
|
/**
|
|
@@ -134,6 +134,41 @@ export interface ShellResolutionOptions {
|
|
|
134
134
|
/** File-existence probe override (defaults to `existsSync`). */
|
|
135
135
|
exists?: (path: string) => boolean;
|
|
136
136
|
}
|
|
137
|
+
/** Inputs for resolving one configured shell into the executable path passed
|
|
138
|
+
* to node-pty. Injectable so the Windows-only search semantics stay covered
|
|
139
|
+
* on POSIX CI runners. */
|
|
140
|
+
export interface ShellExecutableResolutionOptions {
|
|
141
|
+
/** Platform override (defaults to `process.platform`). */
|
|
142
|
+
platform?: NodeJS.Platform;
|
|
143
|
+
/** Environment override; Windows reads PATH/PATHEXT/SystemRoot plus the
|
|
144
|
+
* PowerShell well-known-location variables. */
|
|
145
|
+
env?: NodeJS.ProcessEnv;
|
|
146
|
+
/** File-existence probe override (defaults to `existsSync`). */
|
|
147
|
+
exists?: (path: string) => boolean;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Resolve the configured shell executable before handing it to node-pty.
|
|
151
|
+
*
|
|
152
|
+
* Windows' native backend does not consistently apply the shell's PATHEXT
|
|
153
|
+
* lookup to a bare value (`pwsh` / `cmd` can fail with the opaque
|
|
154
|
+
* `File not found:` error), so perform the lookup ourselves: an explicit
|
|
155
|
+
* path is accepted as-is when it exists (or with a PATHEXT suffix when the
|
|
156
|
+
* user omitted `.exe`), and a bare name is searched through PATH, System32,
|
|
157
|
+
* and PowerShell's known install directories.
|
|
158
|
+
*
|
|
159
|
+
* POSIX node-pty uses `execvp`, so a bare name would already follow PATH —
|
|
160
|
+
* but a wrong name made the pty die with a bare
|
|
161
|
+
* `[process exited with code N]`, so probe like Windows anyway: a path with
|
|
162
|
+
* a separator must exist; a bare name is searched along PATH (the colon
|
|
163
|
+
* form is fixed by the platform). A miss is a clear, actionable
|
|
164
|
+
* `shell-not-found` error instead of a cryptic exit code.
|
|
165
|
+
*
|
|
166
|
+
* @param shell - the configured shell (settings page or yaml `config.shell`).
|
|
167
|
+
* @param options - platform/env/exists injection points for tests.
|
|
168
|
+
* @returns the executable path passed to node-pty.
|
|
169
|
+
* @throws {SidebarError} `shell-not-found` when no candidate exists.
|
|
170
|
+
*/
|
|
171
|
+
export declare function resolveShellExecutable(shell: string, options?: ShellExecutableResolutionOptions): string;
|
|
137
172
|
/**
|
|
138
173
|
* The interactive shell for this platform, resolved like a terminal
|
|
139
174
|
* emulator: an explicitly configured shell (the `shell` config field) wins,
|
|
@@ -166,3 +201,21 @@ export declare function shellDisplayName(shell: string): string;
|
|
|
166
201
|
* defaults entirely, giving deployments full control over shell startup.
|
|
167
202
|
*/
|
|
168
203
|
export declare function shellSpawnArgs(configured?: string[]): string[];
|
|
204
|
+
/**
|
|
205
|
+
* Strip ONE pair of surrounding quotes from a configured shell path. Users
|
|
206
|
+
* paste Windows paths with spaces pre-quoted (`"C:\Program Files\…"`); the
|
|
207
|
+
* quotes are shell-input syntax, not part of the path. Unpaired quotes and
|
|
208
|
+
* shorter values stay verbatim.
|
|
209
|
+
*/
|
|
210
|
+
export declare function unquotePath(value: string): string;
|
|
211
|
+
/**
|
|
212
|
+
* Split a settings-page shell-arguments string into argv with quote-aware
|
|
213
|
+
* grouping: `'…'` / `"…"` group whitespace, and characters inside quotes are
|
|
214
|
+
* LITERAL — a backslash is never an escape, so Windows paths survive intact
|
|
215
|
+
* (`-File "C:\my init\init.ps1"` → three tokens, the last containing spaces).
|
|
216
|
+
* The price is that an argument containing a literal quote character cannot
|
|
217
|
+
* be expressed; shell startup arguments never need one. An unclosed quote
|
|
218
|
+
* folds the remainder into the current token (settings input stays
|
|
219
|
+
* forgiving); an empty quote pair yields no argument.
|
|
220
|
+
*/
|
|
221
|
+
export declare function splitShellArgs(input: string): string[];
|
package/lib/types/wire.d.ts
CHANGED
|
@@ -6,12 +6,16 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { SidebarHttpRequest, SidebarHttpResponse } from './context-types.ts';
|
|
8
8
|
/** Machine-readable error codes of the sidebar API. */
|
|
9
|
-
export type SidebarErrorCode = 'bad-request' | 'not-found' | 'forbidden' | 'method-error' | 'too-large' | 'fs-error' | 'git-error' | 'pty-error' | 'pty-deps-missing' | 'job-error' | 'cdp-down' | 'sidechat-error' | 'subagents-unavailable' | 'settings-rejected' | 'settings-conflict' | 'internal';
|
|
9
|
+
export type SidebarErrorCode = 'bad-request' | 'not-found' | 'forbidden' | 'method-error' | 'too-large' | 'fs-error' | 'git-error' | 'pty-error' | 'pty-deps-missing' | 'shell-not-found' | 'job-error' | 'cdp-down' | 'sidechat-error' | 'subagents-unavailable' | 'settings-rejected' | 'settings-conflict' | 'internal';
|
|
10
10
|
/** One API failure with its wire code and HTTP status. */
|
|
11
11
|
export declare class SidebarError extends Error {
|
|
12
12
|
readonly code: SidebarErrorCode;
|
|
13
13
|
readonly status: number;
|
|
14
|
-
|
|
14
|
+
/** Optional structured context (e.g. `{ shell }` for shell-not-found). */
|
|
15
|
+
readonly meta?: Record<string, string> | undefined;
|
|
16
|
+
constructor(code: SidebarErrorCode, message: string, status?: number,
|
|
17
|
+
/** Optional structured context (e.g. `{ shell }` for shell-not-found). */
|
|
18
|
+
meta?: Record<string, string> | undefined);
|
|
15
19
|
}
|
|
16
20
|
/** Success envelope of one API method. */
|
|
17
21
|
export interface SidebarOk<T> {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-coding-sidebar",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
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": {
|