@keith9681/dsh-sidebar 0.15.2
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/LICENSE +21 -0
- package/README.md +0 -0
- package/README_EN.md +0 -0
- package/cordis.patch.yml +49 -0
- package/lib/client-editor.js +34651 -0
- package/lib/client-mermaid.js +200629 -0
- package/lib/client-registry.js +13388 -0
- package/lib/client-terminal.js +8904 -0
- package/lib/client.js +13388 -0
- package/lib/index.js +3959 -0
- package/lib/invariant.js +22 -0
- package/lib/types/agent-pty.d.ts +226 -0
- package/lib/types/browser-probe.d.ts +15 -0
- package/lib/types/bundle-route.d.ts +12 -0
- package/lib/types/client/BrowserView.d.ts +22 -0
- package/lib/types/client/DiffTab.d.ts +6 -0
- package/lib/types/client/DiffView.d.ts +51 -0
- package/lib/types/client/EditorHost.d.ts +12 -0
- package/lib/types/client/FileTree.d.ts +36 -0
- package/lib/types/client/GitView.d.ts +8 -0
- package/lib/types/client/OrphanedTab.d.ts +11 -0
- package/lib/types/client/PdfView.d.ts +6 -0
- package/lib/types/client/RenderBoundary.d.ts +32 -0
- package/lib/types/client/SandboxStatusBar.d.ts +10 -0
- package/lib/types/client/SideCardSection.d.ts +74 -0
- package/lib/types/client/SideChatView.d.ts +16 -0
- package/lib/types/client/Sidebar.d.ts +6 -0
- package/lib/types/client/SubagentView.d.ts +16 -0
- package/lib/types/client/TabBar.d.ts +43 -0
- package/lib/types/client/TerminalView.d.ts +24 -0
- package/lib/types/client/TextEditor.d.ts +10 -0
- package/lib/types/client/TreePanel.d.ts +23 -0
- package/lib/types/client/UploadOverlay.d.ts +20 -0
- package/lib/types/client/add-plugin-modal.d.ts +15 -0
- package/lib/types/client/api.d.ts +268 -0
- package/lib/types/client/binary-download.d.ts +12 -0
- package/lib/types/client/breakpoints.d.ts +12 -0
- package/lib/types/client/browser.d.ts +50 -0
- package/lib/types/client/builtins/index.d.ts +18 -0
- package/lib/types/client/builtins/tabs.d.ts +11 -0
- package/lib/types/client/builtins/viewers.d.ts +3 -0
- package/lib/types/client/chunk-loader.d.ts +124 -0
- package/lib/types/client/chunks/editor.d.ts +9 -0
- package/lib/types/client/chunks/mermaid.d.ts +10 -0
- package/lib/types/client/chunks/terminal.d.ts +9 -0
- package/lib/types/client/cm-themes.d.ts +24 -0
- package/lib/types/client/conversation-draft.d.ts +14 -0
- package/lib/types/client/desktop-env.d.ts +35 -0
- package/lib/types/client/editable-state.d.ts +27 -0
- package/lib/types/client/editor-load.d.ts +66 -0
- package/lib/types/client/frame-batcher.d.ts +24 -0
- package/lib/types/client/icons.d.ts +57 -0
- package/lib/types/client/image-types.d.ts +3 -0
- package/lib/types/client/ime-guard.d.ts +36 -0
- package/lib/types/client/index.d.ts +19 -0
- package/lib/types/client/intercept.d.ts +33 -0
- package/lib/types/client/lang.d.ts +12 -0
- package/lib/types/client/lazy-chunk.d.ts +25 -0
- package/lib/types/client/link-intercept.d.ts +42 -0
- package/lib/types/client/locales.d.ts +366 -0
- package/lib/types/client/mermaid-blocks.d.ts +41 -0
- package/lib/types/client/mermaid-sanitize.d.ts +2 -0
- package/lib/types/client/mermaid.d.ts +12 -0
- package/lib/types/client/open-when-sized.d.ts +20 -0
- package/lib/types/client/open-with-settings.d.ts +4 -0
- package/lib/types/client/open-with.d.ts +87 -0
- package/lib/types/client/openpath-intercept.d.ts +41 -0
- package/lib/types/client/paths.d.ts +35 -0
- package/lib/types/client/pdf-types.d.ts +2 -0
- package/lib/types/client/plugin-settings.d.ts +8 -0
- package/lib/types/client/plugins-shared.d.ts +27 -0
- package/lib/types/client/plugins-tabs.d.ts +3 -0
- package/lib/types/client/plugins-viewers.d.ts +3 -0
- package/lib/types/client/prefs.d.ts +41 -0
- package/lib/types/client/produced-files.d.ts +23 -0
- package/lib/types/client/selection-payload.d.ts +27 -0
- package/lib/types/client/service.d.ts +440 -0
- package/lib/types/client/settings-nav-icon.d.ts +19 -0
- package/lib/types/client/shell-presets.d.ts +48 -0
- package/lib/types/client/sidechat-transcript.d.ts +103 -0
- package/lib/types/client/split-pane.d.ts +30 -0
- package/lib/types/client/state.d.ts +359 -0
- package/lib/types/client/subagent-detect.d.ts +54 -0
- package/lib/types/client/subagent-jobs.d.ts +63 -0
- package/lib/types/client/tab-content-memo.d.ts +33 -0
- package/lib/types/client/terminal-font.d.ts +20 -0
- package/lib/types/client/theme.d.ts +45 -0
- package/lib/types/client/titlebar-strip.d.ts +23 -0
- package/lib/types/client/upload.d.ts +69 -0
- package/lib/types/client/wco.d.ts +47 -0
- package/lib/types/config.d.ts +64 -0
- package/lib/types/context-types.d.ts +603 -0
- package/lib/types/fs-operations.d.ts +28 -0
- package/lib/types/fs-search.d.ts +24 -0
- package/lib/types/fs-tree.d.ts +51 -0
- package/lib/types/git.d.ts +82 -0
- package/lib/types/html-route.d.ts +59 -0
- package/lib/types/index.d.ts +47 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/jobs-routes.d.ts +45 -0
- package/lib/types/open-external.d.ts +24 -0
- package/lib/types/prefs-shared.d.ts +225 -0
- package/lib/types/pty-deps.d.ts +78 -0
- package/lib/types/pty-manager.d.ts +168 -0
- package/lib/types/sidechat-core.d.ts +172 -0
- package/lib/types/sidechat-routes.d.ts +30 -0
- package/lib/types/subagent-activity.d.ts +44 -0
- package/lib/types/subagent-live-route.d.ts +44 -0
- package/lib/types/tools.d.ts +31 -0
- package/lib/types/trust-fence.d.ts +24 -0
- package/lib/types/wire.d.ts +38 -0
- package/package.json +173 -0
- package/scripts/install.ps1 +289 -0
- package/scripts/install.sh +287 -0
- package/src/agent-pty.ts +527 -0
- package/src/browser-probe.ts +26 -0
- package/src/bundle-route.ts +129 -0
- package/src/client/BrowserView.tsx +253 -0
- package/src/client/DiffTab.tsx +110 -0
- package/src/client/DiffView.tsx +305 -0
- package/src/client/EditorHost.tsx +525 -0
- package/src/client/FileTree.tsx +653 -0
- package/src/client/GitView.tsx +558 -0
- package/src/client/OrphanedTab.tsx +27 -0
- package/src/client/PdfView.tsx +110 -0
- package/src/client/RenderBoundary.tsx +49 -0
- package/src/client/SandboxStatusBar.tsx +60 -0
- package/src/client/SideCardSection.module.css +960 -0
- package/src/client/SideCardSection.tsx +1152 -0
- package/src/client/SideChatView.module.css +437 -0
- package/src/client/SideChatView.tsx +644 -0
- package/src/client/Sidebar.tsx +1148 -0
- package/src/client/SubagentView.module.css +547 -0
- package/src/client/SubagentView.tsx +883 -0
- package/src/client/TabBar.tsx +305 -0
- package/src/client/TerminalView.tsx +383 -0
- package/src/client/TextEditor.tsx +500 -0
- package/src/client/TreePanel.tsx +273 -0
- package/src/client/UploadOverlay.tsx +62 -0
- package/src/client/add-plugin-modal.tsx +199 -0
- package/src/client/api.ts +335 -0
- package/src/client/binary-download.tsx +23 -0
- package/src/client/breakpoints.ts +51 -0
- package/src/client/browser.ts +119 -0
- package/src/client/builtins/index.ts +37 -0
- package/src/client/builtins/tabs.tsx +331 -0
- package/src/client/builtins/viewers.tsx +125 -0
- package/src/client/chunk-loader.ts +351 -0
- package/src/client/chunks/editor.tsx +9 -0
- package/src/client/chunks/mermaid.tsx +10 -0
- package/src/client/chunks/terminal.tsx +9 -0
- package/src/client/cm-themes.ts +129 -0
- package/src/client/conversation-draft.ts +29 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/desktop-env.ts +72 -0
- package/src/client/editable-state.ts +70 -0
- package/src/client/editor-load.ts +92 -0
- package/src/client/frame-batcher.ts +56 -0
- package/src/client/icons.tsx +161 -0
- package/src/client/image-types.ts +8 -0
- package/src/client/ime-guard.ts +47 -0
- package/src/client/index.tsx +369 -0
- package/src/client/intercept.tsx +110 -0
- package/src/client/lang.ts +103 -0
- package/src/client/layout.css +94 -0
- package/src/client/lazy-chunk.tsx +89 -0
- package/src/client/link-intercept.ts +73 -0
- package/src/client/locales.ts +734 -0
- package/src/client/mermaid-blocks.ts +110 -0
- package/src/client/mermaid-sanitize.ts +93 -0
- package/src/client/mermaid.tsx +401 -0
- package/src/client/open-when-sized.ts +43 -0
- package/src/client/open-with-settings.tsx +135 -0
- package/src/client/open-with.ts +221 -0
- package/src/client/openpath-intercept.ts +62 -0
- package/src/client/paths.ts +47 -0
- package/src/client/pdf-types.ts +4 -0
- package/src/client/plugin-settings.ts +43 -0
- package/src/client/plugins-shared.ts +29 -0
- package/src/client/plugins-tabs.ts +87 -0
- package/src/client/plugins-viewers.ts +30 -0
- package/src/client/prefs.ts +207 -0
- package/src/client/produced-files.ts +91 -0
- package/src/client/selection-payload.ts +86 -0
- package/src/client/service.ts +838 -0
- package/src/client/settings-nav-icon.ts +45 -0
- package/src/client/shell-presets.ts +83 -0
- package/src/client/sidebar.module.css +2577 -0
- package/src/client/sidechat-transcript.ts +321 -0
- package/src/client/split-pane.tsx +322 -0
- package/src/client/state.ts +1175 -0
- package/src/client/subagent-detect.ts +137 -0
- package/src/client/subagent-jobs.ts +161 -0
- package/src/client/tab-content-memo.ts +46 -0
- package/src/client/terminal-font.ts +28 -0
- package/src/client/theme.ts +110 -0
- package/src/client/titlebar-strip.ts +37 -0
- package/src/client/upload.ts +187 -0
- package/src/client/wco.ts +120 -0
- package/src/config.ts +155 -0
- package/src/context-types.ts +610 -0
- package/src/fs-operations.ts +99 -0
- package/src/fs-search.ts +86 -0
- package/src/fs-tree.ts +158 -0
- package/src/git.ts +244 -0
- package/src/html-route.ts +106 -0
- package/src/index.ts +1132 -0
- package/src/invariant.ts +32 -0
- package/src/jobs-routes.ts +257 -0
- package/src/open-external.ts +90 -0
- package/src/prefs-shared.ts +268 -0
- package/src/pty-deps.ts +240 -0
- package/src/pty-manager.ts +376 -0
- package/src/sidechat-core.ts +468 -0
- package/src/sidechat-routes.ts +344 -0
- package/src/subagent-activity.ts +91 -0
- package/src/subagent-live-route.ts +93 -0
- package/src/tools.ts +479 -0
- package/src/trust-fence.ts +77 -0
- package/src/wire.ts +100 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side read of the user-facing "Side card" preferences. The host owns
|
|
3
|
+
* the namespace through the settings seam (in-process); the DSH settings RPC
|
|
4
|
+
* domain only serves allowlisted namespaces to configuration clients, so the
|
|
5
|
+
* client reads and writes THIS namespace through the plugin's own fenced
|
|
6
|
+
* /sidebar routes instead (api.settingsGet/settingsUpdate). Any failure
|
|
7
|
+
* (route rejected, namespace absent, a field of the wrong type, a value out
|
|
8
|
+
* of the contract range) falls back to the schema defaults — the side card
|
|
9
|
+
* must keep working exactly as composed when the settings surface is missing.
|
|
10
|
+
*/
|
|
11
|
+
import type { api } from './api.ts'
|
|
12
|
+
import {
|
|
13
|
+
clampTerminalFontSize,
|
|
14
|
+
clampTitleBarStrip,
|
|
15
|
+
clampWidthPercent,
|
|
16
|
+
SIDEBAR_PREFS_DEFAULTS,
|
|
17
|
+
TITLE_BAR_SCHEMES,
|
|
18
|
+
TITLE_BAR_STRIP_DEFAULT,
|
|
19
|
+
type SidebarPrefs,
|
|
20
|
+
type TitleBarScheme,
|
|
21
|
+
} from '../prefs-shared.ts'
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
SIDEBAR_PREFS_DEFAULTS,
|
|
25
|
+
TITLE_BAR_SCHEMES,
|
|
26
|
+
TITLE_BAR_STRIP_DEFAULT,
|
|
27
|
+
clampTerminalFontSize,
|
|
28
|
+
clampTitleBarStrip,
|
|
29
|
+
clampWidthPercent,
|
|
30
|
+
}
|
|
31
|
+
export type { SidebarPrefs, TitleBarScheme }
|
|
32
|
+
|
|
33
|
+
/** The settings wire face the preferences need (a subset of the plugin api). */
|
|
34
|
+
export type SidebarSettingsClient = Pick<typeof api, 'settingsGet' | 'settingsUpdate'>
|
|
35
|
+
|
|
36
|
+
/** Validate one raw resolved value into {@link SidebarPrefs}. Used for the
|
|
37
|
+
* settings.get payload AND the settings.update response (both carry the
|
|
38
|
+
* layered resolved value); any malformed field falls back to its default.
|
|
39
|
+
* @param value - the raw resolved section from the settings wire.
|
|
40
|
+
* @returns validated prefs (always well-formed).
|
|
41
|
+
*/
|
|
42
|
+
export function parsePrefs(value: unknown): SidebarPrefs {
|
|
43
|
+
if (value === null || typeof value !== 'object') return { ...SIDEBAR_PREFS_DEFAULTS }
|
|
44
|
+
const record = value as Record<string, unknown>
|
|
45
|
+
return {
|
|
46
|
+
openByDefault: typeof record.openByDefault === 'boolean'
|
|
47
|
+
? record.openByDefault
|
|
48
|
+
: SIDEBAR_PREFS_DEFAULTS.openByDefault,
|
|
49
|
+
defaultWidthPercent: typeof record.defaultWidthPercent === 'number' && Number.isFinite(record.defaultWidthPercent)
|
|
50
|
+
? clampWidthPercent(record.defaultWidthPercent)
|
|
51
|
+
: SIDEBAR_PREFS_DEFAULTS.defaultWidthPercent,
|
|
52
|
+
autoOpenSubagent: typeof record.autoOpenSubagent === 'boolean'
|
|
53
|
+
? record.autoOpenSubagent
|
|
54
|
+
: SIDEBAR_PREFS_DEFAULTS.autoOpenSubagent,
|
|
55
|
+
autoOpenJobs: typeof record.autoOpenJobs === 'boolean'
|
|
56
|
+
? record.autoOpenJobs
|
|
57
|
+
: SIDEBAR_PREFS_DEFAULTS.autoOpenJobs,
|
|
58
|
+
agentTerminalTools: typeof record.agentTerminalTools === 'boolean'
|
|
59
|
+
? record.agentTerminalTools
|
|
60
|
+
: SIDEBAR_PREFS_DEFAULTS.agentTerminalTools,
|
|
61
|
+
bottomPanelAutoTerminal: typeof record.bottomPanelAutoTerminal === 'boolean'
|
|
62
|
+
? record.bottomPanelAutoTerminal
|
|
63
|
+
: SIDEBAR_PREFS_DEFAULTS.bottomPanelAutoTerminal,
|
|
64
|
+
terminalFontFamily: typeof record.terminalFontFamily === 'string'
|
|
65
|
+
? record.terminalFontFamily
|
|
66
|
+
: SIDEBAR_PREFS_DEFAULTS.terminalFontFamily,
|
|
67
|
+
terminalShell: typeof record.terminalShell === 'string'
|
|
68
|
+
? record.terminalShell
|
|
69
|
+
: SIDEBAR_PREFS_DEFAULTS.terminalShell,
|
|
70
|
+
terminalShellArgs: typeof record.terminalShellArgs === 'string'
|
|
71
|
+
? record.terminalShellArgs
|
|
72
|
+
: SIDEBAR_PREFS_DEFAULTS.terminalShellArgs,
|
|
73
|
+
terminalFontSize: typeof record.terminalFontSize === 'number' && Number.isFinite(record.terminalFontSize)
|
|
74
|
+
? clampTerminalFontSize(record.terminalFontSize)
|
|
75
|
+
: SIDEBAR_PREFS_DEFAULTS.terminalFontSize,
|
|
76
|
+
interceptOpenPath: typeof record.interceptOpenPath === 'boolean'
|
|
77
|
+
? record.interceptOpenPath
|
|
78
|
+
: SIDEBAR_PREFS_DEFAULTS.interceptOpenPath,
|
|
79
|
+
editorExplorer: typeof record.editorExplorer === 'boolean'
|
|
80
|
+
? record.editorExplorer
|
|
81
|
+
: SIDEBAR_PREFS_DEFAULTS.editorExplorer,
|
|
82
|
+
// The title-bar scheme (auto | web | preset | custom). The schema
|
|
83
|
+
// declares the field WITHOUT a default, so documents written by older
|
|
84
|
+
// plugin versions resolve without it — migrate from the legacy fields:
|
|
85
|
+
// a document that ALREADY HAS VALUES (the manual compat flag on, or a
|
|
86
|
+
// non-default strip px — both only reachable through the old gear
|
|
87
|
+
// popup) maps to the `custom` scheme so the user's numbers keep
|
|
88
|
+
// working; a pristine document keeps the conservative `auto` scheme.
|
|
89
|
+
titleBarScheme: isTitleBarScheme(record.titleBarScheme)
|
|
90
|
+
? record.titleBarScheme
|
|
91
|
+
: (record.titleBarCompat === true || hasLegacyStripValue(record.titleBarStripPx) ? 'custom' : 'auto'),
|
|
92
|
+
titleBarPresetId: typeof record.titleBarPresetId === 'string'
|
|
93
|
+
? record.titleBarPresetId
|
|
94
|
+
: SIDEBAR_PREFS_DEFAULTS.titleBarPresetId,
|
|
95
|
+
customCss: typeof record.customCss === 'string'
|
|
96
|
+
? record.customCss
|
|
97
|
+
: SIDEBAR_PREFS_DEFAULTS.customCss,
|
|
98
|
+
titleBarCompat: typeof record.titleBarCompat === 'boolean'
|
|
99
|
+
? record.titleBarCompat
|
|
100
|
+
: SIDEBAR_PREFS_DEFAULTS.titleBarCompat,
|
|
101
|
+
titleBarStripPx: typeof record.titleBarStripPx === 'number' && Number.isFinite(record.titleBarStripPx)
|
|
102
|
+
? clampTitleBarStrip(record.titleBarStripPx)
|
|
103
|
+
: SIDEBAR_PREFS_DEFAULTS.titleBarStripPx,
|
|
104
|
+
htmlViewerNoSandbox: typeof record.htmlViewerNoSandbox === 'boolean'
|
|
105
|
+
? record.htmlViewerNoSandbox
|
|
106
|
+
: SIDEBAR_PREFS_DEFAULTS.htmlViewerNoSandbox,
|
|
107
|
+
htmlViewerDefaultUnsafe: typeof record.htmlViewerDefaultUnsafe === 'boolean'
|
|
108
|
+
? record.htmlViewerDefaultUnsafe
|
|
109
|
+
: SIDEBAR_PREFS_DEFAULTS.htmlViewerDefaultUnsafe,
|
|
110
|
+
browserNoSandbox: typeof record.browserNoSandbox === 'boolean'
|
|
111
|
+
? record.browserNoSandbox
|
|
112
|
+
: SIDEBAR_PREFS_DEFAULTS.browserNoSandbox,
|
|
113
|
+
browserInterceptLinks: typeof record.browserInterceptLinks === 'boolean'
|
|
114
|
+
? record.browserInterceptLinks
|
|
115
|
+
: SIDEBAR_PREFS_DEFAULTS.browserInterceptLinks,
|
|
116
|
+
browserInterceptHttp: typeof record.browserInterceptHttp === 'boolean'
|
|
117
|
+
? record.browserInterceptHttp
|
|
118
|
+
: SIDEBAR_PREFS_DEFAULTS.browserInterceptHttp,
|
|
119
|
+
browserInterceptHttps: typeof record.browserInterceptHttps === 'boolean'
|
|
120
|
+
? record.browserInterceptHttps
|
|
121
|
+
: SIDEBAR_PREFS_DEFAULTS.browserInterceptHttps,
|
|
122
|
+
tabsEnabled: booleanMapOf(record.tabsEnabled),
|
|
123
|
+
viewersEnabled: booleanMapOf(record.viewersEnabled),
|
|
124
|
+
pluginSettings: pluginSettingsMapOf(record.pluginSettings),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Validate the plugin-owned settings map (v0.12.0+): `{ descriptorId: { key:
|
|
130
|
+
* value } }`, nested open maps. Any non-object value (or a malformed whole)
|
|
131
|
+
* falls back to the empty map — the schema defaults already guard the wire
|
|
132
|
+
* shape, this is the client's second line.
|
|
133
|
+
*/
|
|
134
|
+
function pluginSettingsMapOf(value: unknown): Record<string, Record<string, unknown>> {
|
|
135
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return {}
|
|
136
|
+
const out: Record<string, Record<string, unknown>> = {}
|
|
137
|
+
for (const [id, blob] of Object.entries(value as Record<string, unknown>)) {
|
|
138
|
+
if (blob !== null && typeof blob === 'object' && !Array.isArray(blob)) {
|
|
139
|
+
out[id] = blob as Record<string, unknown>
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return out
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Validate one enable-switch map (per-tab / per-viewer). Only boolean values
|
|
147
|
+
* survive; a non-object or a non-boolean entry falls back to the empty map /
|
|
148
|
+
* drops the entry — an absent key means the feature stays enabled.
|
|
149
|
+
*/
|
|
150
|
+
function booleanMapOf(value: unknown): Record<string, boolean> {
|
|
151
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return {}
|
|
152
|
+
const out: Record<string, boolean> = {}
|
|
153
|
+
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
|
|
154
|
+
if (typeof item === 'boolean') out[key] = item
|
|
155
|
+
}
|
|
156
|
+
return out
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Type guard for the title-bar scheme union (anything else falls back). */
|
|
160
|
+
function isTitleBarScheme(value: unknown): value is TitleBarScheme {
|
|
161
|
+
return typeof value === 'string' && (TITLE_BAR_SCHEMES as readonly string[]).includes(value)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Whether the legacy document carries an explicit strip value (only
|
|
166
|
+
* reachable through the old gear popup): a stored number different from the
|
|
167
|
+
* default counts as "the user already configured something" and migrates to
|
|
168
|
+
* the `custom` scheme.
|
|
169
|
+
*/
|
|
170
|
+
function hasLegacyStripValue(value: unknown): boolean {
|
|
171
|
+
return typeof value === 'number' && Number.isFinite(value) && value !== TITLE_BAR_STRIP_DEFAULT
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Read the resolved side card preferences through the plugin's settings route.
|
|
176
|
+
* @param settings - the settings wire face (the plugin api by default).
|
|
177
|
+
* @returns validated prefs, or the schema defaults when the route rejects,
|
|
178
|
+
* the namespace is absent, or a stored value violates the contract.
|
|
179
|
+
*/
|
|
180
|
+
export async function loadPrefs(settings: SidebarSettingsClient): Promise<SidebarPrefs> {
|
|
181
|
+
try {
|
|
182
|
+
const view = await settings.settingsGet()
|
|
183
|
+
return parsePrefs(view.value)
|
|
184
|
+
} catch {
|
|
185
|
+
// Transport/fence rejection or a malformed response: keep the defaults.
|
|
186
|
+
return { ...SIDEBAR_PREFS_DEFAULTS }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Read the external-disable flag from the same settings route: the
|
|
192
|
+
* dsh-web-ui family's aionui-panel provider choice. True only when the host
|
|
193
|
+
* resolved `aionui-panel.rightPanel` to 'aionui-panel' — while true the
|
|
194
|
+
* sidebar must not mount (the two right panels are mutually exclusive). Any
|
|
195
|
+
* failure (route rejected, aionui absent, malformed response) reads false,
|
|
196
|
+
* so a missing family never hides the sidebar.
|
|
197
|
+
* @param settings - the settings wire face (the plugin api by default).
|
|
198
|
+
* @returns the external-disable flag (false on any failure).
|
|
199
|
+
*/
|
|
200
|
+
export async function loadExternalDisable(settings: SidebarSettingsClient): Promise<boolean> {
|
|
201
|
+
try {
|
|
202
|
+
const view = await settings.settingsGet()
|
|
203
|
+
return view.externalDisable === true
|
|
204
|
+
} catch {
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure derivation of one turn's produced files from finalized conversation
|
|
3
|
+
* nodes — a structural replica of ui-deliverables' `producedForClosing`
|
|
4
|
+
* (the mutation tools' follow-along `locations`, by render intent: a diff
|
|
5
|
+
* card or a generic edit card; reads/deletes/failures produce nothing).
|
|
6
|
+
* Kept dependency-free so the takeover logic is unit-testable and the
|
|
7
|
+
* replica is easy to diff against upstream when it drifts.
|
|
8
|
+
*/
|
|
9
|
+
import { isAbsolutePath } from './paths.ts'
|
|
10
|
+
|
|
11
|
+
/** Paths a tool-result view reports as produced, by render intent. */
|
|
12
|
+
export function producedPaths(view: unknown): readonly string[] {
|
|
13
|
+
if (view === null || typeof view !== 'object') return []
|
|
14
|
+
const record = view as { card?: unknown; kind?: unknown; locations?: unknown }
|
|
15
|
+
const isEdit = record.card === 'diff' || (record.card === 'generic' && record.kind === 'edit')
|
|
16
|
+
if (!isEdit) return []
|
|
17
|
+
if (!Array.isArray(record.locations)) return []
|
|
18
|
+
const paths: string[] = []
|
|
19
|
+
for (const location of record.locations) {
|
|
20
|
+
if (location !== null && typeof location === 'object' && typeof (location as { path?: unknown }).path === 'string') {
|
|
21
|
+
paths.push((location as { path: string }).path)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return paths
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Files produced by the turn the assistant at `seq` closes. Accumulation
|
|
29
|
+
* resets on turn boundaries (a user message, or a node reporting a different
|
|
30
|
+
* turn number); paths keep first-seen order and appear once.
|
|
31
|
+
* @param nodes - snapshot nodes in surface order (structural, unknown-safe).
|
|
32
|
+
* @param seq - the closing assistant's seq (the render site's anchor).
|
|
33
|
+
* @returns produced paths; empty when the turn wrote nothing.
|
|
34
|
+
*/
|
|
35
|
+
export function producedForClosing(nodes: readonly unknown[], seq: number): readonly string[] {
|
|
36
|
+
let pending: string[] = []
|
|
37
|
+
let seen = new Set<string>()
|
|
38
|
+
let turn: number | undefined
|
|
39
|
+
for (const node of nodes) {
|
|
40
|
+
if (node === null || typeof node !== 'object') continue
|
|
41
|
+
const record = node as { kind?: unknown; isError?: unknown; callView?: unknown; turn?: unknown; seq?: unknown }
|
|
42
|
+
if (record.kind === 'tool-result') {
|
|
43
|
+
if (record.isError === true) continue
|
|
44
|
+
for (const path of producedPaths(record.callView)) {
|
|
45
|
+
if (seen.has(path)) continue
|
|
46
|
+
seen.add(path)
|
|
47
|
+
pending.push(path)
|
|
48
|
+
}
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
if (record.kind === 'user') {
|
|
52
|
+
turn = undefined
|
|
53
|
+
pending = []
|
|
54
|
+
seen = new Set()
|
|
55
|
+
} else if (typeof record.turn === 'number') {
|
|
56
|
+
if (turn !== undefined && record.turn !== turn) {
|
|
57
|
+
pending = []
|
|
58
|
+
seen = new Set()
|
|
59
|
+
}
|
|
60
|
+
turn = record.turn
|
|
61
|
+
}
|
|
62
|
+
if (record.kind === 'assistant' && record.seq === seq) return pending
|
|
63
|
+
}
|
|
64
|
+
return []
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Claim the turn-tail chain only when the closing turn produced files.
|
|
69
|
+
* @param owner - the turn-tail owner currency ({nodes, seq}).
|
|
70
|
+
* @returns produced paths as the matched value, or null to decline.
|
|
71
|
+
*/
|
|
72
|
+
export function selectProducedFiles(owner: unknown): readonly string[] | null {
|
|
73
|
+
const record = owner as { nodes?: unknown; seq?: unknown } | null
|
|
74
|
+
if (record === null || typeof record !== 'object') return null
|
|
75
|
+
if (!Array.isArray(record.nodes) || typeof record.seq !== 'number') return null
|
|
76
|
+
const paths = producedForClosing(record.nodes, record.seq)
|
|
77
|
+
return paths.length === 0 ? null : paths
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolve a (possibly relative) path against the session cwd for the sidebar.
|
|
82
|
+
* Absolute detection mirrors the host (see client/paths.isAbsolutePath):
|
|
83
|
+
* POSIX roots, drive letters and UNC shares must not be joined onto the cwd.
|
|
84
|
+
*/
|
|
85
|
+
export function resolveSidebarPath(cwd: string | undefined, path: string): string {
|
|
86
|
+
if (isAbsolutePath(path)) return path
|
|
87
|
+
const base = cwd ?? ''
|
|
88
|
+
if (base === '') return path
|
|
89
|
+
const separator = base.includes('\\') ? '\\' : '/'
|
|
90
|
+
return `${base.replace(/[\\/]+$/, '')}${separator}${path}`
|
|
91
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure payload builders for the "add selection to conversation" popup in the
|
|
3
|
+
* text viewers (markdown preview + the catch-all code viewer). Everything
|
|
4
|
+
* here is string math — no React, no ctx — so the unit tests cover it
|
|
5
|
+
* directly.
|
|
6
|
+
*
|
|
7
|
+
* Insert shape (agreed with the product owner):
|
|
8
|
+
* - Selection ≤ SELECTION_LIMIT characters: a fenced code block whose info
|
|
9
|
+
* line is `相对路径:起止行` and whose body is the selected text.
|
|
10
|
+
* - Selection over the limit: a single plain-text line `相对路径:起止行`
|
|
11
|
+
* (no fence, no content).
|
|
12
|
+
* - The path is relative to the session cwd (the same projection the
|
|
13
|
+
* explorer's @ button uses); an unknown cwd falls back to the absolute
|
|
14
|
+
* path.
|
|
15
|
+
* - Line numbers: single-line selections write `path:12`, multi-line write
|
|
16
|
+
* `path:12-15`. The markdown preview cannot map rendered DOM back to
|
|
17
|
+
* source lines directly, so it reverse-searches the selected text in the
|
|
18
|
+
* source and only reports lines on an unambiguous hit (see
|
|
19
|
+
* {@link linesOfSelection}).
|
|
20
|
+
*/
|
|
21
|
+
import { relativeTo } from './paths.ts'
|
|
22
|
+
|
|
23
|
+
/** Max inserted selection length (UTF-16 code units, i.e. JS `.length`). */
|
|
24
|
+
export const SELECTION_LIMIT = 500
|
|
25
|
+
|
|
26
|
+
/** The source line span a selection maps to (1-based, inclusive). */
|
|
27
|
+
export interface SelectionLines {
|
|
28
|
+
start: number
|
|
29
|
+
end: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The fence info line: `rel[:start[-end]]` — lines are omitted entirely
|
|
34
|
+
* when unknown (the preview reverse-search missed).
|
|
35
|
+
*/
|
|
36
|
+
export function headerOf(path: string, cwd: string | undefined, lines?: SelectionLines): string {
|
|
37
|
+
const rel = cwd !== undefined ? relativeTo(cwd, path) : path
|
|
38
|
+
if (lines === undefined) return rel
|
|
39
|
+
if (lines.end > lines.start) return `${rel}:${lines.start}-${lines.end}`
|
|
40
|
+
return `${rel}:${lines.start}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The full text appended to the composer draft for one selection.
|
|
45
|
+
* Over the limit the content is dropped: the plain path line is the whole
|
|
46
|
+
* payload (an empty fenced block would just occupy the draft).
|
|
47
|
+
*/
|
|
48
|
+
export function buildSelectionInsert(
|
|
49
|
+
path: string,
|
|
50
|
+
cwd: string | undefined,
|
|
51
|
+
lines: SelectionLines | undefined,
|
|
52
|
+
selected: string,
|
|
53
|
+
): string {
|
|
54
|
+
const header = headerOf(path, cwd, lines)
|
|
55
|
+
if (selected.length > SELECTION_LIMIT) return header
|
|
56
|
+
return `\`\`\`${header}\n${selected}\n\`\`\``
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 1-based line number of a character index in a text. */
|
|
60
|
+
function lineAt(source: string, index: number): number {
|
|
61
|
+
let line = 1
|
|
62
|
+
for (let i = 0; i < index && i < source.length; i++) {
|
|
63
|
+
if (source[i] === '\n') line++
|
|
64
|
+
}
|
|
65
|
+
return line
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Reverse-map a rendered-DOM selection back to source line numbers. The
|
|
70
|
+
* preview selection is plain text (block boundaries come out as `\n`), so
|
|
71
|
+
* this is a best-effort substring search: a single trailing newline is
|
|
72
|
+
* stripped first (DOM block selections tend to carry one), and only an
|
|
73
|
+
* EXACTLY-ONE occurrence yields lines — an ambiguous or missing match
|
|
74
|
+
* returns null (the header then carries the path without line numbers).
|
|
75
|
+
*/
|
|
76
|
+
export function linesOfSelection(source: string, selected: string): SelectionLines | null {
|
|
77
|
+
const text = selected.endsWith('\n') ? selected.slice(0, -1) : selected
|
|
78
|
+
if (text === '') return null
|
|
79
|
+
const at = source.indexOf(text)
|
|
80
|
+
if (at === -1) return null
|
|
81
|
+
if (source.indexOf(text, at + 1) !== -1) return null
|
|
82
|
+
return {
|
|
83
|
+
start: lineAt(source, at),
|
|
84
|
+
end: lineAt(source, at + Math.max(text.length - 1, 0)),
|
|
85
|
+
}
|
|
86
|
+
}
|