dsh-chat-manager-wide 1.4.0
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/AGENTS.md +127 -0
- package/CHANGELOG.md +48 -0
- package/LICENSE +21 -0
- package/PUBLISH.md +167 -0
- package/README.en.md +151 -0
- package/README.md +139 -0
- package/README.zh-CN.md +5 -0
- package/SECURITY.md +15 -0
- package/THIRD_PARTY_NOTICES.md +63 -0
- package/compatibility.json +31 -0
- package/cordis.patch.yml +7 -0
- package/lib/client.js +6209 -0
- package/package.json +159 -0
- package/scripts/build-client-local.mjs +38 -0
- package/scripts/build-client.mjs +450 -0
- package/scripts/smoke-ui.mjs +221 -0
- package/src/host/archive-manager.mjs +329 -0
- package/src/host/delete-session.mjs +430 -0
- package/src/index.js +97 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import { dirname, resolve } from 'node:path'
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url)
|
|
8
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
const output = process.env.DSH_CHAT_MANAGER_OUTPUT === undefined
|
|
10
|
+
? resolve(here, '../lib/client.js')
|
|
11
|
+
: resolve(process.env.DSH_CHAT_MANAGER_OUTPUT)
|
|
12
|
+
const compatibility = JSON.parse(readFileSync(resolve(here, '../compatibility.json'), 'utf8'))
|
|
13
|
+
const packageManifest = JSON.parse(readFileSync(resolve(here, '../package.json'), 'utf8'))
|
|
14
|
+
if (typeof packageManifest.name !== 'string' || packageManifest.name.length === 0) {
|
|
15
|
+
throw new Error('package.json must declare a non-empty package name')
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Browser module id of the composed bundle. The host keys its client-module
|
|
19
|
+
* graph by npm package name, so the bundle has to register exactly that id.
|
|
20
|
+
*/
|
|
21
|
+
const MODULE_ID = packageManifest.name
|
|
22
|
+
/**
|
|
23
|
+
* Attribution header for the bundled upstream work. The upstream artifact
|
|
24
|
+
* notice sits ahead of `factory: (require) => {` and is dropped by
|
|
25
|
+
* `extractClientFactoryBody`, so the composed bundle carries its own.
|
|
26
|
+
*/
|
|
27
|
+
const BUNDLE_NOTICE = `// ${MODULE_ID} — a modified build of @deepseek-ai/dsh-client-ui-workspace (MIT, Copyright (c) 2026 DeepSeek).\n`
|
|
28
|
+
const LATEST_UPSTREAM_VERSION = compatibility.latestTested
|
|
29
|
+
const SUPPORTED_UPSTREAM_VERSIONS = new Set([
|
|
30
|
+
...compatibility.supported,
|
|
31
|
+
...(compatibility.previews ?? []),
|
|
32
|
+
])
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
// A canceled event acknowledges the Portable bridge. Older hosts safely fall back.
|
|
36
|
+
export function openOfficialArchives(ctx, probe = false) {
|
|
37
|
+
try {
|
|
38
|
+
const entries = ctx.slots.entries('settings.section')
|
|
39
|
+
if (!entries.some(entry => (entry.options?.id ?? entry.id) === 'archived-sessions')) return false
|
|
40
|
+
return !window.dispatchEvent(new CustomEvent('dsh-portable/open-settings', {
|
|
41
|
+
cancelable: true, detail: { section: 'archived-sessions', probe },
|
|
42
|
+
}))
|
|
43
|
+
} catch { return false }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function unarchiveSession(sessionId) {
|
|
47
|
+
await this.workspaces.unarchiveSession(sessionId)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const resolveUpstreamClient = () => process.env.DSH_WORKSPACE_CLIENT_PATH === undefined
|
|
51
|
+
? require.resolve('@deepseek-ai/dsh-client-ui-workspace/client')
|
|
52
|
+
: resolve(process.env.DSH_WORKSPACE_CLIENT_PATH)
|
|
53
|
+
export const resolveUpstreamManifest = () => process.env.DSH_WORKSPACE_MANIFEST_PATH === undefined
|
|
54
|
+
? require.resolve('@deepseek-ai/dsh-client-ui-workspace/package.json')
|
|
55
|
+
: resolve(process.env.DSH_WORKSPACE_MANIFEST_PATH)
|
|
56
|
+
const resolvePreviewClient = () => process.env.DSH_WORKSPACE_CLIENT_PATH === undefined
|
|
57
|
+
? require.resolve(`${compatibility.previewWorkspaceFixture}/client`)
|
|
58
|
+
: resolve(process.env.DSH_WORKSPACE_CLIENT_PATH)
|
|
59
|
+
const resolvePreviewManifest = () => process.env.DSH_WORKSPACE_MANIFEST_PATH === undefined
|
|
60
|
+
? require.resolve(`${compatibility.previewWorkspaceFixture}/package.json`)
|
|
61
|
+
: resolve(process.env.DSH_WORKSPACE_MANIFEST_PATH)
|
|
62
|
+
const resolveLegacyClient = () => compatibility.legacyWorkspaceFixture === undefined
|
|
63
|
+
? resolveUpstreamClient()
|
|
64
|
+
: require.resolve(`${compatibility.legacyWorkspaceFixture}/client`)
|
|
65
|
+
const resolveLegacyManifest = () => compatibility.legacyWorkspaceFixture === undefined
|
|
66
|
+
? resolveUpstreamManifest()
|
|
67
|
+
: require.resolve(`${compatibility.legacyWorkspaceFixture}/package.json`)
|
|
68
|
+
|
|
69
|
+
const replaceOnce = (source, before, after, label) => {
|
|
70
|
+
const first = source.indexOf(before)
|
|
71
|
+
if (first === -1 || source.indexOf(before, first + before.length) !== -1) {
|
|
72
|
+
throw new Error(`upstream marker mismatch: ${label}`)
|
|
73
|
+
}
|
|
74
|
+
return `${source.slice(0, first)}${after}${source.slice(first + before.length)}`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const findFunctionSignature = (source, functionName) => {
|
|
78
|
+
const pattern = new RegExp(`function ${functionName}\\(\\{[^}]+\\}\\) \\{`, 'g')
|
|
79
|
+
const matches = [...source.matchAll(pattern)]
|
|
80
|
+
if (matches.length !== 1) throw new Error(`upstream marker mismatch: ${functionName} signature`)
|
|
81
|
+
return matches[0][0]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const findMarker = (source, candidates, label) => {
|
|
85
|
+
const matches = candidates.filter(candidate => source.includes(candidate))
|
|
86
|
+
if (matches.length !== 1) throw new Error(`upstream marker mismatch: ${label}`)
|
|
87
|
+
return matches[0]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const normalizeCssModulePrefix = (source, variableName, canonicalPrefix) => {
|
|
91
|
+
const marker = `var ${variableName}_module_css_default = {`
|
|
92
|
+
const start = source.indexOf(marker)
|
|
93
|
+
if (start < 0) throw new Error(`upstream marker mismatch: ${variableName} CSS module`)
|
|
94
|
+
const end = source.indexOf('\n\t\t};', start)
|
|
95
|
+
if (end < 0) throw new Error(`upstream marker mismatch: ${variableName} CSS module end`)
|
|
96
|
+
const prefixes = new Set(
|
|
97
|
+
[...source.slice(start, end).matchAll(/": "([^"]+?)_[^"]+"/g)]
|
|
98
|
+
.map(match => match[1]),
|
|
99
|
+
)
|
|
100
|
+
if (prefixes.size !== 1) throw new Error(`upstream marker mismatch: ${variableName} CSS prefix`)
|
|
101
|
+
const [generatedPrefix] = prefixes
|
|
102
|
+
return source.replaceAll(`${generatedPrefix}_`, `${canonicalPrefix}_`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const extractClientFactoryBody = (source, label) => {
|
|
106
|
+
const marker = 'factory: (require) => {'
|
|
107
|
+
const start = source.indexOf(marker)
|
|
108
|
+
const end = source.lastIndexOf('\n\t}\n});')
|
|
109
|
+
if (start < 0 || end < start || source.indexOf(marker, start + marker.length) >= 0) {
|
|
110
|
+
throw new Error(`client artifact marker mismatch: ${label}`)
|
|
111
|
+
}
|
|
112
|
+
return source.slice(start + marker.length, end)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function composeCompatibleClients(stableClient, previewClient, moduleId = MODULE_ID) {
|
|
116
|
+
const stableBody = extractClientFactoryBody(stableClient, 'stable factory')
|
|
117
|
+
const previewBody = extractClientFactoryBody(previewClient, 'preview factory')
|
|
118
|
+
return `${BUNDLE_NOTICE}// DSH Chat Manager runtime-compatible client: stable and preview implementations are selected by capability.\nwindow.__ModuleLoader__.load({\n\tid: ${JSON.stringify(moduleId)},\n\tfactory: (require) => {\n\t\tconst stableFactory = (require) => {${stableBody}\n\t\t};\n\t\tconst previewFactory = (require) => {${previewBody}\n\t\t};\n\t\tlet stableRuntimeAvailable = true;\n\t\ttry {\n\t\t\trequire("@deepseek-ai/dsh-client-runtime/client");\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tif (!/missed the module table|Cannot find module/u.test(message)) throw error;\n\t\t\tstableRuntimeAvailable = false;\n\t\t}\n\t\treturn stableRuntimeAvailable ? stableFactory(require) : previewFactory(require);\n\t}\n});\n`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Add one narrow feature to the shipped workspace client while preserving the
|
|
123
|
+
* rest of the official bundle byte-for-byte after a modification notice.
|
|
124
|
+
* Exact markers turn upstream UI drift into a build failure instead of a
|
|
125
|
+
* silently malformed client.
|
|
126
|
+
*/
|
|
127
|
+
export function patchWorkspaceClient(upstream, upstreamVersion = LATEST_UPSTREAM_VERSION) {
|
|
128
|
+
if (!SUPPORTED_UPSTREAM_VERSIONS.has(upstreamVersion)) {
|
|
129
|
+
throw new Error(`unsupported @deepseek-ai/dsh-client-ui-workspace version: ${upstreamVersion}`)
|
|
130
|
+
}
|
|
131
|
+
const sessionTreeSignature = findFunctionSignature(upstream, 'SessionTree')
|
|
132
|
+
const sessionRowSignature = findFunctionSignature(upstream, 'SessionNodeItem')
|
|
133
|
+
if (!sessionRowSignature.includes('onFork, onArchive, ')) throw new Error('upstream marker mismatch: session row props')
|
|
134
|
+
const flatListSignature = findFunctionSignature(upstream, 'FlatList')
|
|
135
|
+
const workspaceBrowserSignature = findFunctionSignature(upstream, 'WorkspaceBrowser')
|
|
136
|
+
if (!sessionTreeSignature.includes('onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore,')) {
|
|
137
|
+
throw new Error('upstream marker mismatch: SessionTree delete insertion point')
|
|
138
|
+
}
|
|
139
|
+
if (!workspaceBrowserSignature.includes('deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore,')) {
|
|
140
|
+
throw new Error('upstream marker mismatch: WorkspaceBrowser delete insertion point')
|
|
141
|
+
}
|
|
142
|
+
if (!flatListSignature.includes('onSessionRename, onSessionArchive, archivedSessionIds,')) {
|
|
143
|
+
throw new Error('upstream marker mismatch: FlatList delete insertion point')
|
|
144
|
+
}
|
|
145
|
+
let source = upstream
|
|
146
|
+
const stableArchiveAction = `\t\t\t\tarchiveSession: async (sessionId) => {\n\t\t\t\t\tawait ctx.workspaces.archiveSession(sessionId);\n\t\t\t\t},\n`
|
|
147
|
+
const controllerArchiveAction = `\t\t\t\tarchiveSession: async (sessionId) => {\n\t\t\t\t\tawait uiWorkspace.archiveSession(sessionId);\n\t\t\t\t},\n`
|
|
148
|
+
if (source.includes(controllerArchiveAction)) {
|
|
149
|
+
source = replaceOnce(source, controllerArchiveAction, stableArchiveAction, 'controller archive action')
|
|
150
|
+
}
|
|
151
|
+
const patch = (before, after, label) => {
|
|
152
|
+
source = replaceOnce(source, before, after, label)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Modern workspace bundles own this service; legacy hosts import theirs.
|
|
156
|
+
if (source.includes('super(ctx, "uiWorkspace")') && !source.includes('async unarchiveSession(sessionId)')) {
|
|
157
|
+
const archiveMethod = '\t\t\tasync archiveSession(sessionId) {\n\t\t\t\tawait this.workspaces.archiveSession(sessionId);\n\t\t\t}'
|
|
158
|
+
patch(archiveMethod, `${archiveMethod}\n\t\t\t${unarchiveSession.toString().replace('async function ', 'async ')}`, 'official unarchive service')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
patch(
|
|
162
|
+
'id: "@deepseek-ai/dsh-client-ui-workspace",',
|
|
163
|
+
`id: ${JSON.stringify(MODULE_ID)},`,
|
|
164
|
+
'client module id',
|
|
165
|
+
)
|
|
166
|
+
patch(
|
|
167
|
+
sessionRowSignature,
|
|
168
|
+
sessionRowSignature.replace('onFork, onArchive, ', 'onFork, onArchive, onDelete, '),
|
|
169
|
+
'session row props',
|
|
170
|
+
)
|
|
171
|
+
patch(
|
|
172
|
+
`\t\t\t\t{\n\t\t\t\t\tid: "archive",\n\t\t\t\t\tlabel: t("menu.archiveSession"),\n\t\t\t\t\ticon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })\n\t\t\t\t}\n`,
|
|
173
|
+
`\t\t\t\t{\n\t\t\t\t\tid: "archive",\n\t\t\t\t\tlabel: t("menu.archiveSession"),\n\t\t\t\t\ticon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: "delete-session",\n\t\t\t\t\tlabel: t("menu.deleteSession"),\n\t\t\t\t\ticon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}),\n\t\t\t\t\tdanger: true\n\t\t\t\t}\n`,
|
|
174
|
+
'session delete menu item',
|
|
175
|
+
)
|
|
176
|
+
patch(
|
|
177
|
+
'\t\t\t\t\t\t\t\t\tif (id === "archive") onArchive(node.id);\n',
|
|
178
|
+
'\t\t\t\t\t\t\t\t\tif (id === "archive") onArchive(node.id);\n\t\t\t\t\t\t\t\t\tif (id === "delete-session") onDelete(node.id, title);\n',
|
|
179
|
+
'session delete menu selection',
|
|
180
|
+
)
|
|
181
|
+
patch(
|
|
182
|
+
sessionTreeSignature,
|
|
183
|
+
sessionTreeSignature.replace(
|
|
184
|
+
'onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore,',
|
|
185
|
+
'onDeleteRequest, onSessionRename, onSessionArchive, onSessionDelete, insertWorkspaceBefore,',
|
|
186
|
+
),
|
|
187
|
+
'session tree props',
|
|
188
|
+
)
|
|
189
|
+
patch(
|
|
190
|
+
'\t\t\t\t\t\t\t\t\t\t\tonArchive: onSessionArchive,\n',
|
|
191
|
+
'\t\t\t\t\t\t\t\t\t\t\tonArchive: onSessionArchive,\n\t\t\t\t\t\t\t\t\t\t\tonDelete: onSessionDelete,\n',
|
|
192
|
+
'tree row delete prop',
|
|
193
|
+
)
|
|
194
|
+
patch(
|
|
195
|
+
flatListSignature,
|
|
196
|
+
flatListSignature.replace(
|
|
197
|
+
'onSessionRename, onSessionArchive, archivedSessionIds,',
|
|
198
|
+
'onSessionRename, onSessionArchive, onSessionDelete, archivedSessionIds,',
|
|
199
|
+
),
|
|
200
|
+
'flat list props',
|
|
201
|
+
)
|
|
202
|
+
patch(
|
|
203
|
+
'\t\t\t\t\t\t\tonFork: forkSession,\n\t\t\t\t\t\t\tonArchive: onSessionArchive,\n',
|
|
204
|
+
'\t\t\t\t\t\t\tonFork: forkSession,\n\t\t\t\t\t\t\tonArchive: onSessionArchive,\n\t\t\t\t\t\t\tonDelete: onSessionDelete,\n',
|
|
205
|
+
'flat row delete prop',
|
|
206
|
+
)
|
|
207
|
+
patch(
|
|
208
|
+
workspaceBrowserSignature,
|
|
209
|
+
workspaceBrowserSignature.replace(
|
|
210
|
+
'deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore,',
|
|
211
|
+
'deleteWorkspace, insertWorkspaceBefore, archiveSession, deleteSession, restoreSession, searchArchivedSessions, readArchivedSession, openArchivedSettings, insertSessionBefore,',
|
|
212
|
+
),
|
|
213
|
+
'workspace browser delete action prop',
|
|
214
|
+
)
|
|
215
|
+
patch(
|
|
216
|
+
`\t\t\tconst onSessionArchive = (sessionId) => {\n\t\t\t\tarchiveSession(sessionId).catch((reason) => {\n\t\t\t\t\tconsole.warn("session archive rejected:", reason);\n\t\t\t\t});\n\t\t\t};\n`,
|
|
217
|
+
`\t\t\tconst onSessionArchive = (sessionId) => {\n\t\t\t\tarchiveSession(sessionId).catch((reason) => {\n\t\t\t\t\tconsole.warn("session archive rejected:", reason);\n\t\t\t\t});\n\t\t\t};\n\t\t\tconst [sessionDeleteTarget, setSessionDeleteTarget] = (0, react.useState)(null);\n\t\t\tconst [sessionDeleting, setSessionDeleting] = (0, react.useState)(false);\n\t\t\tconst [sessionDeleteError, setSessionDeleteError] = (0, react.useState)(null);\n\t\t\tconst onSessionDelete = (sessionId, title) => {\n\t\t\t\tsetSessionDeleteTarget({ sessionId, title });\n\t\t\t\tsetSessionDeleteError(null);\n\t\t\t};\n\t\t\tconst closeSessionDelete = () => {\n\t\t\t\tif (sessionDeleting) return;\n\t\t\t\tsetSessionDeleteTarget(null);\n\t\t\t\tsetSessionDeleteError(null);\n\t\t\t};\n\t\t\tconst confirmSessionDelete = () => {\n\t\t\t\tif (sessionDeleting || sessionDeleteTarget === null) return;\n\t\t\t\tsetSessionDeleting(true);\n\t\t\t\tsetSessionDeleteError(null);\n\t\t\t\tdeleteSession(sessionDeleteTarget.sessionId).then(() => {\n\t\t\t\t\tclearArchiveDetailIf(sessionDeleteTarget.sessionId);\n\t\t\t\t\tsetSessionDeleting(false);\n\t\t\t\t\tsetSessionDeleteTarget(null);\n\t\t\t\t\tsetSessionDeleteError(null);\n\t\t\t\t}).catch((reason) => {\n\t\t\t\t\tsetSessionDeleting(false);\n\t\t\t\t\tsetSessionDeleteError(reason instanceof Error ? reason.message : String(reason));\n\t\t\t\t});\n\t\t\t};\n`,
|
|
218
|
+
'session delete dialog state',
|
|
219
|
+
)
|
|
220
|
+
patch(
|
|
221
|
+
'const [sessionDeleteTarget, setSessionDeleteTarget] = (0, react.useState)(null);',
|
|
222
|
+
`const archiveSessionList = useSessions((state) => state);
|
|
223
|
+
\t\t\tconst [archiveManagerOpen, setArchiveManagerOpen] = (0, react.useState)(false);
|
|
224
|
+
\t\t\tconst [archiveQuery, setArchiveQuery] = (0, react.useState)("");
|
|
225
|
+
\t\t\tconst [archiveSearch, setArchiveSearch] = (0, react.useState)({ query: "", status: "idle", items: [], hasMore: false });
|
|
226
|
+
\t\t\tconst [archiveBusyId, setArchiveBusyId] = (0, react.useState)(null);
|
|
227
|
+
\t\t\tconst [archiveError, setArchiveError] = (0, react.useState)(null);
|
|
228
|
+
\t\t\tconst normalizedArchiveQuery = archiveQuery.trim();
|
|
229
|
+
\t\t\t(0, react.useEffect)(() => {
|
|
230
|
+
\t\t\t\tif (!archiveManagerOpen || normalizedArchiveQuery === "") {
|
|
231
|
+
\t\t\t\t\tsetArchiveSearch({ query: "", status: "idle", items: [], hasMore: false });
|
|
232
|
+
\t\t\t\t\treturn;
|
|
233
|
+
\t\t\t\t}
|
|
234
|
+
\t\t\t\tconst controller = new AbortController();
|
|
235
|
+
\t\t\t\tsetArchiveSearch({ query: normalizedArchiveQuery, status: "loading", items: [], hasMore: false });
|
|
236
|
+
\t\t\t\tconst timer = window.setTimeout(() => {
|
|
237
|
+
\t\t\t\t\tsearchArchivedSessions(normalizedArchiveQuery, controller.signal).then((result) => {
|
|
238
|
+
\t\t\t\t\t\tif (!controller.signal.aborted) setArchiveSearch({ query: normalizedArchiveQuery, status: "ready", items: result.items, hasMore: result.hasMore });
|
|
239
|
+
\t\t\t\t\t}).catch(() => {
|
|
240
|
+
\t\t\t\t\t\tif (!controller.signal.aborted) setArchiveSearch({ query: normalizedArchiveQuery, status: "error", items: [], hasMore: false });
|
|
241
|
+
\t\t\t\t\t});
|
|
242
|
+
\t\t\t\t}, 250);
|
|
243
|
+
\t\t\t\treturn () => { window.clearTimeout(timer); controller.abort(); };
|
|
244
|
+
\t\t\t}, [archiveManagerOpen, normalizedArchiveQuery, searchArchivedSessions]);
|
|
245
|
+
\t\t\tconst archiveWorkspaceBySession = (0, react.useMemo)(() => {
|
|
246
|
+
\t\t\t\tconst result = /* @__PURE__ */ new Map();
|
|
247
|
+
\t\t\t\tfor (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!result.has(sessionId)) result.set(sessionId, workspace.title);
|
|
248
|
+
\t\t\t\treturn result;
|
|
249
|
+
\t\t\t}, [workspaces]);
|
|
250
|
+
\t\t\tconst archiveSnippets = (0, react.useMemo)(() => new Map(archiveSearch.items.map((item) => [item.sessionId, item.snippet])), [archiveSearch.items]);
|
|
251
|
+
\t\t\tconst archiveRows = (0, react.useMemo)(() => {
|
|
252
|
+
\t\t\t\tconst query = normalizedArchiveQuery.toLowerCase();
|
|
253
|
+
\t\t\t\tconst remoteIds = new Set(archiveSearch.items.map((item) => item.sessionId));
|
|
254
|
+
\t\t\t\tconst rows = archivedSessionIds.map((sessionId) => {
|
|
255
|
+
\t\t\t\t\tconst summary = archiveSessionList.byId[sessionId];
|
|
256
|
+
\t\t\t\t\treturn {
|
|
257
|
+
\t\t\t\t\t\tid: sessionId,
|
|
258
|
+
\t\t\t\t\t\ttitle: summary === void 0 ? sessionId : sessionTitle(summary),
|
|
259
|
+
\t\t\t\t\t\tworkspace: archiveWorkspaceBySession.get(sessionId) ?? t("group.ungrouped"),
|
|
260
|
+
\t\t\t\t\t\tupdatedAt: summary?.updatedAt ?? 0
|
|
261
|
+
\t\t\t\t\t};
|
|
262
|
+
\t\t\t\t});
|
|
263
|
+
\t\t\t\trows.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
264
|
+
\t\t\t\tif (query === "") return rows;
|
|
265
|
+
\t\t\t\treturn rows.filter((row) => row.title.toLowerCase().includes(query) || row.workspace.toLowerCase().includes(query) || remoteIds.has(row.id));
|
|
266
|
+
\t\t\t}, [archiveSessionList, archivedSessionIds, archiveSearch.items, archiveWorkspaceBySession, normalizedArchiveQuery, t]);
|
|
267
|
+
\t\t\tconst onArchiveRestore = (sessionId) => {
|
|
268
|
+
\t\t\t\tif (archiveBusyId !== null) return;
|
|
269
|
+
\t\t\t\tsetArchiveBusyId(sessionId);
|
|
270
|
+
\t\t\t\tsetArchiveError(null);
|
|
271
|
+
\t\t\t\trestoreSession(sessionId).then(() => { setArchiveBusyId(null); clearArchiveDetailIf(sessionId); }).catch((reason) => {
|
|
272
|
+
\t\t\t\t\tsetArchiveBusyId(null);
|
|
273
|
+
\t\t\t\t\tsetArchiveError(reason instanceof Error ? reason.message : String(reason));
|
|
274
|
+
\t\t\t\t});
|
|
275
|
+
\t\t\t};
|
|
276
|
+
\t\t\t\t\t\tconst [archiveDetail, setArchiveDetail] = (0, react.useState)({ sessionId: null, title: \"\", status: \"idle\", items: [], error: null, truncated: false, shown: 0, total: 0 });\n\t\t\tconst [archiveCopyState, setArchiveCopyState] = (0, react.useState)(\"idle\");\n\t\t\tconst archiveDetailAbort = (0, react.useRef)(null);\n\t\t\tconst archiveClipboardAvailable = typeof navigator !== \"undefined\" && navigator.clipboard !== void 0 && typeof navigator.clipboard.writeText === \"function\";\n\t\t\t(0, react.useEffect)(() => {\n\t\t\t\tif (typeof document === \"undefined\" || document.getElementById(\"dcm-archive-style\") !== null) return;\n\t\t\t\tconst style = document.createElement(\"style\");\n\t\t\t\tstyle.id = \"dcm-archive-style\";\n\t\t\t\tstyle.textContent = \".dcmArchiveDialog.dcmArchiveDialog{width:min(1120px,100%);max-width:min(1120px,100%)}.dcmArchiveLayout{display:flex;align-items:stretch;gap:12px;height:min(56vh,620px);min-height:240px;margin-top:12px}.dcmArchiveListPane{display:flex;flex-direction:column;gap:8px;width:340px;min-width:240px;flex:none;min-height:0}.dcmArchiveList{display:flex;flex-direction:column;gap:8px;flex:1;min-height:0;overflow-y:auto;padding-right:4px}.dcmArchiveRow{cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:10px;display:flex;flex-direction:column;gap:6px;background:transparent}.dcmArchiveRow:hover{background:var(--dsw-alias-bg-layer-1)}.dcmArchiveRowActive{border-color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-layer-1)}.dcmArchiveRowTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:18px;overflow-wrap:anywhere}.dcmArchiveRowMeta{color:var(--dsw-alias-label-secondary);font-size:12px;overflow-wrap:anywhere}.dcmArchiveRowSnippet{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;overflow-wrap:anywhere}.dcmArchiveRowActions{display:flex;justify-content:flex-end;gap:8px}.dcmArchiveRowAction{min-height:26px;height:26px;padding-inline:10px;font-size:12px}.dcmArchiveDetailPane{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;overflow:hidden}.dcmArchiveDetailHead{display:flex;flex-direction:column;gap:2px;padding:10px 12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dcmArchiveDetailTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;overflow-wrap:anywhere}.dcmArchiveDetailMeta{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:20px;color:var(--dsw-alias-label-secondary);font-size:12px}.dcmArchiveTranscript{flex:1;min-height:0;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:10px}.dcmArchiveMessage{display:flex;flex-direction:column;gap:4px;border:1px solid transparent;border-radius:10px;padding:8px 10px;background:var(--dsw-alias-bg-layer-1)}.dcmArchiveMessageAssistant{background:transparent;border-color:var(--dsw-alias-border-l2)}.dcmArchiveMessageHead{display:flex;align-items:center;justify-content:space-between;gap:8px;color:var(--dsw-alias-label-secondary);font-size:11px}.dcmArchiveMessageBody{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;white-space:pre-wrap;overflow-wrap:anywhere}.dcmArchivePlaceholder{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:8px;flex:1;min-height:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}.dcmArchiveNotice{color:var(--dsw-alias-label-secondary);font-size:12px}.dcmArchiveError{color:var(--dsw-alias-state-error-primary);font-size:12px;overflow-wrap:anywhere}\";\n\t\t\t\tdocument.head.appendChild(style);\n\t\t\t}, []);\n\t\t\t(0, react.useEffect)(() => {\n\t\t\t\tif (archiveManagerOpen) return;\n\t\t\t\tif (archiveDetailAbort.current !== null) {\n\t\t\t\t\tarchiveDetailAbort.current.abort();\n\t\t\t\t\tarchiveDetailAbort.current = null;\n\t\t\t\t}\n\t\t\t}, [archiveManagerOpen]);\n\t\t\tconst formatArchiveTime = (time) => {\n\t\t\t\tif (typeof time !== \"number\" || !Number.isFinite(time)) return \"\";\n\t\t\t\tconst date = new Date(time);\n\t\t\t\tconst pad = (value) => String(value).padStart(2, \"0\");\n\t\t\t\treturn pad(date.getMonth() + 1) + \"-\" + pad(date.getDate()) + \" \" + pad(date.getHours()) + \":\" + pad(date.getMinutes());\n\t\t\t};\n\t\t\tconst clearArchiveDetailIf = (sessionId) => {\n\t\t\t\tsetArchiveDetail((current) => current.sessionId === sessionId ? { sessionId: null, title: \"\", status: \"idle\", items: [], error: null, truncated: false, shown: 0, total: 0 } : current);\n\t\t\t};\n\t\t\tconst onArchiveSelect = (sessionId, title) => {\n\t\t\t\tif (archiveDetailAbort.current !== null) archiveDetailAbort.current.abort();\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tarchiveDetailAbort.current = controller;\n\t\t\t\tsetArchiveCopyState(\"idle\");\n\t\t\t\tsetArchiveDetail({ sessionId, title: title ?? \"\", status: \"loading\", items: [], error: null, truncated: false, shown: 0, total: 0 });\n\t\t\t\treadArchivedSession(sessionId, controller.signal).then((result) => {\n\t\t\t\t\tif (controller.signal.aborted) return;\n\t\t\t\t\tsetArchiveDetail({\n\t\t\t\t\t\tsessionId,\n\t\t\t\t\t\ttitle: title ?? \"\",\n\t\t\t\t\t\tstatus: \"ready\",\n\t\t\t\t\t\titems: Array.isArray(result?.items) ? result.items : [],\n\t\t\t\t\t\terror: null,\n\t\t\t\t\t\ttruncated: result?.truncated === true,\n\t\t\t\t\t\tshown: typeof result?.shown === \"number\" ? result.shown : 0,\n\t\t\t\t\t\ttotal: typeof result?.total === \"number\" ? result.total : 0\n\t\t\t\t\t});\n\t\t\t\t}).catch((reason) => {\n\t\t\t\t\tif (controller.signal.aborted) return;\n\t\t\t\t\tsetArchiveDetail({ sessionId, title: title ?? \"\", status: \"error\", items: [], error: reason instanceof Error ? reason.message : String(reason), truncated: false, shown: 0, total: 0 });\n\t\t\t\t});\n\t\t\t};\n\t\t\tconst onArchiveCopy = () => {\n\t\t\t\tif (!archiveClipboardAvailable || archiveDetail.items.length === 0) return;\n\t\t\t\tconst text = archiveDetail.items.map((item) => (item.role === \"user\" ? t(\"archive.manager.roleUser\") : t(\"archive.manager.roleAssistant\")) + \" \" + formatArchiveTime(item.time) + \"\\n\" + item.text).join(\"\\n\\n\");\n\t\t\t\tnavigator.clipboard.writeText(text).then(() => {\n\t\t\t\t\tsetArchiveCopyState(\"copied\");\n\t\t\t\t\twindow.setTimeout(() => setArchiveCopyState(\"idle\"), 1600);\n\t\t\t\t}).catch(() => setArchiveCopyState(\"idle\"));\n\t\t\t};\n\t\t\tconst renderArchiveTranscript = () => {\n\t\t\t\tif (archiveDetail.sessionId === null) return (0, react_jsx_runtime.jsx)(\"div\", { className: \"dcmArchivePlaceholder\", children: t(\"archive.manager.detail.empty\") });\n\t\t\t\tif (archiveDetail.status === \"loading\") return (0, react_jsx_runtime.jsx)(\"div\", { className: \"dcmArchivePlaceholder\", role: \"status\", children: t(\"archive.manager.detail.loading\") });\n\t\t\t\tif (archiveDetail.status === \"error\") return (0, react_jsx_runtime.jsxs)(\"div\", { className: \"dcmArchivePlaceholder\", role: \"alert\", children: [\n\t\t\t\t\t(0, react_jsx_runtime.jsx)(\"div\", { children: t(\"archive.manager.detail.error\", { message: archiveDetail.error }) }),\n\t\t\t\t\t(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: \"outline\", className: \"dcmArchiveRowAction\", onClick: () => onArchiveSelect(archiveDetail.sessionId, archiveDetail.title), children: t(\"archive.manager.retry\") })\n\t\t\t\t] });\n\t\t\t\tif (archiveDetail.items.length === 0) return (0, react_jsx_runtime.jsx)(\"div\", { className: \"dcmArchivePlaceholder\", children: t(\"archive.manager.detail.noMessages\") });\n\t\t\t\tconst messages = archiveDetail.items.map((item) => (0, react_jsx_runtime.jsxs)(\"div\", {\n\t\t\t\t\tkey: item.seq,\n\t\t\t\t\tclassName: item.role === \"user\" ? \"dcmArchiveMessage dcmArchiveMessageUser\" : \"dcmArchiveMessage dcmArchiveMessageAssistant\",\n\t\t\t\t\tchildren: [\n\t\t\t\t\t\t(0, react_jsx_runtime.jsxs)(\"div\", { className: \"dcmArchiveMessageHead\", children: [\n\t\t\t\t\t\t\t(0, react_jsx_runtime.jsx)(\"span\", { children: item.role === \"user\" ? t(\"archive.manager.roleUser\") : t(\"archive.manager.roleAssistant\") }),\n\t\t\t\t\t\t\t(0, react_jsx_runtime.jsx)(\"span\", { children: formatArchiveTime(item.time) })\n\t\t\t\t\t\t] }),\n\t\t\t\t\t\t(0, react_jsx_runtime.jsx)(\"div\", { className: \"dcmArchiveMessageBody\", children: item.text })\n\t\t\t\t\t]\n\t\t\t\t}, item.seq));\n\t\t\t\treturn (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [\n\t\t\t\t\tmessages,\n\t\t\t\t\tarchiveDetail.truncated && (0, react_jsx_runtime.jsx)(\"div\", { className: \"dcmArchiveNotice\", children: t(\"archive.manager.detail.truncated\", { n: archiveDetail.shown }) })\n\t\t\t\t] });\n\t\t\t};\n\t\t\tconst [sessionDeleteTarget, setSessionDeleteTarget] = (0, react.useState)(null);`,
|
|
277
|
+
'archive manager state',
|
|
278
|
+
)
|
|
279
|
+
patch(
|
|
280
|
+
'\t\t\t\t\t\t\tonSessionArchive,\n\t\t\t\t\t\t\tarchivedSessionIds,\n',
|
|
281
|
+
'\t\t\t\t\t\t\tonSessionArchive,\n\t\t\t\t\t\t\tonSessionDelete,\n\t\t\t\t\t\t\tarchivedSessionIds,\n',
|
|
282
|
+
'flat list delete handler',
|
|
283
|
+
)
|
|
284
|
+
patch(
|
|
285
|
+
'\t\t\t\t\t\t\tonSessionArchive,\n\t\t\t\t\t\t\tforkSession,\n',
|
|
286
|
+
'\t\t\t\t\t\t\tonSessionArchive,\n\t\t\t\t\t\t\tonSessionDelete,\n\t\t\t\t\t\t\tforkSession,\n',
|
|
287
|
+
'session tree delete handler',
|
|
288
|
+
)
|
|
289
|
+
patch(
|
|
290
|
+
'children: [wide && (0, react_jsx_runtime.jsx)(ViewOptionsMenu, {',
|
|
291
|
+
`children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
292
|
+
\t\t\t\t\t\t\t\t\tlabel: t("archive.manager.title"),
|
|
293
|
+
\t\t\t\t\t\t\t\t\tside: "bottom",
|
|
294
|
+
\t\t\t\t\t\t\t\t\tdelayMs: 500,
|
|
295
|
+
\t\t\t\t\t\t\t\t\tchildren: (0, react_jsx_runtime.jsx)("button", {
|
|
296
|
+
\t\t\t\t\t\t\t\t\t\tid: "archived-sessions",
|
|
297
|
+
\t\t\t\t\t\t\t\t\t\ttype: "button",
|
|
298
|
+
\t\t\t\t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.iconButton,
|
|
299
|
+
\t\t\t\t\t\t\t\t\t\t"aria-label": t("archive.manager.title"),
|
|
300
|
+
\t\t\t\t\t\t\t\t\t\tonClick: () => { if (openArchivedSettings()) return; setArchiveError(null); setArchiveManagerOpen(true); },
|
|
301
|
+
\t\t\t\t\t\t\t\t\t\tchildren: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: wide ? 16 : 18 })
|
|
302
|
+
\t\t\t\t\t\t\t\t\t})
|
|
303
|
+
\t\t\t\t\t\t\t\t}), openArchivedSettings(true) && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
304
|
+
label: t("archive.manager.advanced"), side: "bottom", delayMs: 500,
|
|
305
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
306
|
+
type: "button", className: WorkspaceBrowser_module_css_default.iconButton,
|
|
307
|
+
"aria-label": t("archive.manager.advanced"),
|
|
308
|
+
onClick: () => { setArchiveError(null); setArchiveManagerOpen(true); },
|
|
309
|
+
children: (0, react_jsx_runtime.jsx)("svg", {
|
|
310
|
+
width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", "aria-hidden": true,
|
|
311
|
+
children: (0, react_jsx_runtime.jsx)("path", { d: "M11 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0Zm-1 3 4 4" })
|
|
312
|
+
})
|
|
313
|
+
})
|
|
314
|
+
}), wide && (0, react_jsx_runtime.jsx)(ViewOptionsMenu, {`,
|
|
315
|
+
'archive manager header action',
|
|
316
|
+
)
|
|
317
|
+
patch(
|
|
318
|
+
'max-width:60px;transition:max-width .18s var(--ds-ease-in-out)',
|
|
319
|
+
'max-width:124px;transition:max-width .18s var(--ds-ease-in-out)',
|
|
320
|
+
'workspace header action capacity',
|
|
321
|
+
)
|
|
322
|
+
patch(
|
|
323
|
+
`\t\t\t\t\t(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {\n\t\t\t\t\t\topen: deleteTarget !== null,\n`,
|
|
324
|
+
`\t\t\t\t\t(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
325
|
+
\t\t\t\t\t\topen: archiveManagerOpen,\n\t\t\t\t\t\tonClose: () => { if (archiveBusyId === null) setArchiveManagerOpen(false); },\n\t\t\t\t\t\tcloseLabel: t(\"close\"),\n\t\t\t\t\t\ttitle: t(openArchivedSettings(true) ? \"archive.manager.advanced\" : \"archive.manager.title\"),\n\t\t\t\t\t\tdescription: t(\"archive.manager.description\", { n: archivedSessionIds.length }),\n\t\t\t\t\t\tclassName: \"dcmArchiveDialog\",\n\t\t\t\t\t\tfooter: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {\n\t\t\t\t\t\t\tvariant: \"outline\",\n\t\t\t\t\t\t\tdisabled: archiveBusyId !== null,\n\t\t\t\t\t\t\tonClick: () => setArchiveManagerOpen(false),\n\t\t\t\t\t\t\tchildren: t(\"close\")\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tchildren: [(0, react_jsx_runtime.jsx)(\"input\", {\n\t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.renameInput,\n\t\t\t\t\t\t\ttype: \"search\",\n\t\t\t\t\t\t\tvalue: archiveQuery,\n\t\t\t\t\t\t\tmaxLength: SEARCH_QUERY_MAX_CODE_UNITS,\n\t\t\t\t\t\t\tplaceholder: t(\"archive.manager.searchPlaceholder\"),\n\t\t\t\t\t\t\t\"aria-label\": t(\"archive.manager.searchPlaceholder\"),\n\t\t\t\t\t\t\tonChange: (event) => { setArchiveQuery(event.target.value); setArchiveError(null); }\n\t\t\t\t\t\t}), archiveSearch.status === \"loading\" && (0, react_jsx_runtime.jsx)(\"div\", {\n\t\t\t\t\t\t\tclassName: \"dcmArchiveNotice\",\n\t\t\t\t\t\t\trole: \"status\",\n\t\t\t\t\t\t\tchildren: t(\"archive.manager.searching\")\n\t\t\t\t\t\t}), archiveSearch.status === \"error\" && (0, react_jsx_runtime.jsx)(\"div\", {\n\t\t\t\t\t\t\tclassName: \"dcmArchiveError\",\n\t\t\t\t\t\t\trole: \"status\",\n\t\t\t\t\t\t\tchildren: t(\"archive.manager.searchUnavailable\")\n\t\t\t\t\t\t}), (0, react_jsx_runtime.jsxs)(\"div\", {\n\t\t\t\t\t\t\tclassName: \"dcmArchiveLayout\",\n\t\t\t\t\t\t\tchildren: [(0, react_jsx_runtime.jsxs)(\"div\", {\n\t\t\t\t\t\t\t\tclassName: \"dcmArchiveListPane\",\n\t\t\t\t\t\t\t\tchildren: [(0, react_jsx_runtime.jsx)(\"div\", {\n\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveList\",\n\t\t\t\t\t\t\t\t\tchildren: archiveRows.length === 0 ? (0, react_jsx_runtime.jsx)(\"div\", {\n\t\t\t\t\t\t\t\t\t\tclassName: \"dcmArchivePlaceholder\",\n\t\t\t\t\t\t\t\t\t\tchildren: normalizedArchiveQuery === \"\" ? t(\"archive.manager.empty\") : t(\"archive.manager.noMatches\")\n\t\t\t\t\t\t\t\t\t}) : archiveRows.map((row) => (0, react_jsx_runtime.jsxs)(\"div\", {\n\t\t\t\t\t\t\t\t\t\tkey: row.id,\n\t\t\t\t\t\t\t\t\t\tclassName: archiveDetail.sessionId === row.id ? \"dcmArchiveRow dcmArchiveRowActive\" : \"dcmArchiveRow\",\n\t\t\t\t\t\t\t\t\t\trole: \"button\",\n\t\t\t\t\t\t\t\t\t\ttabIndex: 0,\n\t\t\t\t\t\t\t\t\t\t\"aria-pressed\": archiveDetail.sessionId === row.id,\n\t\t\t\t\t\t\t\t\t\tonClick: () => onArchiveSelect(row.id, row.title),\n\t\t\t\t\t\t\t\t\t\tonKeyDown: (event) => {\n\t\t\t\t\t\t\t\t\t\t\tif (event.target !== event.currentTarget) return;\n\t\t\t\t\t\t\t\t\t\t\tif (event.key !== \"Enter\" && event.key !== \" \") return;\n\t\t\t\t\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\t\t\t\t\tonArchiveSelect(row.id, row.title);\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tchildren: [(0, react_jsx_runtime.jsx)(\"div\", {\n\t\t\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveRowTitle\",\n\t\t\t\t\t\t\t\t\t\t\tchildren: row.title\n\t\t\t\t\t\t\t\t\t\t}), (0, react_jsx_runtime.jsx)(\"div\", {\n\t\t\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveRowMeta\",\n\t\t\t\t\t\t\t\t\t\t\tchildren: row.workspace\n\t\t\t\t\t\t\t\t\t\t}), archiveSnippets.has(row.id) && (0, react_jsx_runtime.jsx)(\"div\", {\n\t\t\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveRowSnippet\",\n\t\t\t\t\t\t\t\t\t\t\tchildren: archiveSnippets.get(row.id)\n\t\t\t\t\t\t\t\t\t\t}), (0, react_jsx_runtime.jsxs)(\"div\", {\n\t\t\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveRowActions\",\n\t\t\t\t\t\t\t\t\t\t\tchildren: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {\n\t\t\t\t\t\t\t\t\t\t\t\tvariant: \"outline\",\n\t\t\t\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveRowAction\",\n\t\t\t\t\t\t\t\t\t\t\t\tdisabled: archiveBusyId !== null,\n\t\t\t\t\t\t\t\t\t\t\t\tonClick: (event) => { event.stopPropagation(); onArchiveRestore(row.id); },\n\t\t\t\t\t\t\t\t\t\t\t\tchildren: archiveBusyId === row.id ? t(\"archive.manager.restoring\") : t(\"archive.manager.restore\")\n\t\t\t\t\t\t\t\t\t\t\t}), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {\n\t\t\t\t\t\t\t\t\t\t\t\tvariant: \"outline\",\n\t\t\t\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveRowAction \" + WorkspaceBrowser_module_css_default.deleteAction,\n\t\t\t\t\t\t\t\t\t\t\t\tdisabled: archiveBusyId !== null,\n\t\t\t\t\t\t\t\t\t\t\t\tonClick: (event) => { event.stopPropagation(); onSessionDelete(row.id, row.title); },\n\t\t\t\t\t\t\t\t\t\t\t\tchildren: t(\"archive.manager.delete\")\n\t\t\t\t\t\t\t\t\t\t\t})]\n\t\t\t\t\t\t\t\t\t\t})]\n\t\t\t\t\t\t\t\t\t}, row.id))\n\t\t\t\t\t\t\t\t}), archiveSearch.hasMore && (0, react_jsx_runtime.jsx)(\"div\", {\n\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveNotice\",\n\t\t\t\t\t\t\t\t\tchildren: t(\"archive.manager.hasMore\")\n\t\t\t\t\t\t\t\t}), archiveError !== null && (0, react_jsx_runtime.jsx)(\"div\", {\n\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveError\",\n\t\t\t\t\t\t\t\t\trole: \"alert\",\n\t\t\t\t\t\t\t\t\tchildren: archiveError\n\t\t\t\t\t\t\t\t})]\n\t\t\t\t\t\t\t}), (0, react_jsx_runtime.jsxs)(\"div\", {\n\t\t\t\t\t\t\t\tclassName: \"dcmArchiveDetailPane\",\n\t\t\t\t\t\t\t\tchildren: [(0, react_jsx_runtime.jsxs)(\"div\", {\n\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveDetailHead\",\n\t\t\t\t\t\t\t\t\tchildren: [(0, react_jsx_runtime.jsx)(\"div\", {\n\t\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveDetailTitle\",\n\t\t\t\t\t\t\t\t\t\tchildren: archiveDetail.sessionId === null ? t(\"archive.manager.detail.title\") : archiveDetail.title\n\t\t\t\t\t\t\t\t\t}), (0, react_jsx_runtime.jsxs)(\"div\", {\n\t\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveDetailMeta\",\n\t\t\t\t\t\t\t\t\t\tchildren: [(0, react_jsx_runtime.jsx)(\"span\", {\n\t\t\t\t\t\t\t\t\t\t\tchildren: archiveDetail.status === \"ready\" ? t(\"archive.manager.detail.meta\", { n: archiveDetail.total }) : \"\"\n\t\t\t\t\t\t\t\t\t\t}), archiveDetail.status === \"ready\" && archiveClipboardAvailable && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {\n\t\t\t\t\t\t\t\t\t\t\tvariant: \"outline\",\n\t\t\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveRowAction\",\n\t\t\t\t\t\t\t\t\t\t\tonClick: onArchiveCopy,\n\t\t\t\t\t\t\t\t\t\t\tchildren: archiveCopyState === \"copied\" ? t(\"archive.manager.copied\") : t(\"archive.manager.copy\")\n\t\t\t\t\t\t\t\t\t\t})]\n\t\t\t\t\t\t\t\t\t})]\n\t\t\t\t\t\t\t\t}), (0, react_jsx_runtime.jsx)(\"div\", {\n\t\t\t\t\t\t\t\t\tclassName: \"dcmArchiveTranscript\",\n\t\t\t\t\t\t\t\t\tchildren: renderArchiveTranscript()\n\t\t\t\t\t\t\t\t})]\n\t\t\t\t\t\t\t})]\n\t\t\t\t\t\t})]\n\t\t\t\t\t}),\n\t\t\t\t\t(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {\n\t\t\t\t\t\topen: deleteTarget !== null,
|
|
326
|
+
`,
|
|
327
|
+
'archive manager modal',
|
|
328
|
+
)
|
|
329
|
+
patch(
|
|
330
|
+
`\t\t\t\t\t(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {\n\t\t\t\t\t\topen: deleteTarget !== null,\n`,
|
|
331
|
+
`\t\t\t\t\t(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {\n\t\t\t\t\t\topen: sessionDeleteTarget !== null,\n\t\t\t\t\t\tonClose: closeSessionDelete,\n\t\t\t\t\t\tcloseLabel: t("close"),\n\t\t\t\t\t\ttitle: t("delete.session.title"),\n\t\t\t\t\t\t...sessionDeleteTarget === null ? {} : { description: t("delete.session.desc", { name: sessionDeleteTarget.title }) },\n\t\t\t\t\t\tfooter: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {\n\t\t\t\t\t\t\tvariant: "outline",\n\t\t\t\t\t\t\tdisabled: sessionDeleting,\n\t\t\t\t\t\t\tonClick: closeSessionDelete,\n\t\t\t\t\t\t\tchildren: t("cancel")\n\t\t\t\t\t\t}), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {\n\t\t\t\t\t\t\tvariant: "outline",\n\t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.deleteAction,\n\t\t\t\t\t\t\tdisabled: sessionDeleting,\n\t\t\t\t\t\t\tonClick: confirmSessionDelete,\n\t\t\t\t\t\t\tchildren: t("delete.session.confirm")\n\t\t\t\t\t\t})] }),\n\t\t\t\t\t\tchildren: [sessionDeleting && (0, react_jsx_runtime.jsx)("div", {\n\t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.deleteStatus,\n\t\t\t\t\t\t\trole: "status",\n\t\t\t\t\t\t\tchildren: t("delete.session.pending")\n\t\t\t\t\t\t}), sessionDeleteError !== null && (0, react_jsx_runtime.jsx)("div", {\n\t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.renameError,\n\t\t\t\t\t\t\trole: "alert",\n\t\t\t\t\t\t\tchildren: sessionDeleteError\n\t\t\t\t\t\t})]\n\t\t\t\t\t}),\n\t\t\t\t\t(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {\n\t\t\t\t\t\topen: deleteTarget !== null,\n`,
|
|
332
|
+
'session delete confirmation modal',
|
|
333
|
+
)
|
|
334
|
+
patch(
|
|
335
|
+
'\t\t\t"menu.archiveSession": "归档会话",\n',
|
|
336
|
+
'\t\t\t"menu.archiveSession": "归档会话",\n\t\t\t"archive.manager.title": "归档会话",\n\t\t\t"archive.manager.advanced": "归档内容搜索与管理",\n\t\t\t"archive.manager.description": "共 {n} 个归档会话。可按名称、工作区或聊天内容搜索。",\n\t\t\t"archive.manager.searchPlaceholder": "搜索归档名称、工作区或聊天内容…",\n\t\t\t"archive.manager.searching": "正在搜索归档聊天记录…",\n\t\t\t"archive.manager.searchUnavailable": "内容搜索暂不可用,仅显示名称与工作区匹配。",\n\t\t\t"archive.manager.empty": "暂无归档会话",\n\t\t\t"archive.manager.noMatches": "没有匹配的归档会话",\n\t\t\t"archive.manager.hasMore": "仅显示前 20 条内容匹配,请缩小搜索范围。",\n\t\t\t"archive.manager.restore": "恢复",\n\t\t\t"archive.manager.restoring": "恢复中…",\n\t\t\t"archive.manager.delete": "永久删除",\n\t\t\t"menu.deleteSession": "删除会话",\n\t\t\t"delete.session.title": "永久删除会话?",\n\t\t\t"delete.session.desc": "“{name}”的会话记录将从本机永久删除,且无法恢复。正在运行的任务会先安全停止。",\n\t\t\t"delete.session.confirm": "永久删除",\n\t\t\t"delete.session.pending": "正在永久删除会话…",\n\t\t\t\t\t\t\"archive.manager.detail.title\": \"归档对话内容\",\n\t\t\t\"archive.manager.detail.empty\": \"在左侧选择一个归档会话,即可查看完整对话内容。\",\n\t\t\t\"archive.manager.detail.loading\": \"正在读取归档对话…\",\n\t\t\t\"archive.manager.detail.error\": \"读取归档对话失败:{message}\",\n\t\t\t\"archive.manager.detail.noMessages\": \"该归档会话没有可显示的对话消息。\",\n\t\t\t\"archive.manager.detail.meta\": \"共 {n} 条消息\",\n\t\t\t\"archive.manager.detail.truncated\": \"内容较长,仅显示前 {n} 条消息。\",\n\t\t\t\"archive.manager.roleUser\": \"我\",\n\t\t\t\"archive.manager.roleAssistant\": \"助手\",\n\t\t\t\"archive.manager.copy\": \"复制对话\",\n\t\t\t\"archive.manager.copied\": \"已复制\",\n\t\t\t\"archive.manager.retry\": \"重试\",\n',
|
|
337
|
+
'Chinese delete locale',
|
|
338
|
+
)
|
|
339
|
+
patch(
|
|
340
|
+
'\t\t\t"menu.archiveSession": "Archive session",\n',
|
|
341
|
+
'\t\t\t"menu.archiveSession": "Archive session",\n\t\t\t"archive.manager.title": "Archived sessions",\n\t\t\t"archive.manager.advanced": "Search and manage archived content",\n\t\t\t"archive.manager.description": "{n} archived sessions. Search by name, workspace, or conversation content.",\n\t\t\t"archive.manager.searchPlaceholder": "Search archived names, workspaces, or conversation content…",\n\t\t\t"archive.manager.searching": "Searching archived conversation history…",\n\t\t\t"archive.manager.searchUnavailable": "Content search is temporarily unavailable. Showing name and workspace matches.",\n\t\t\t"archive.manager.empty": "No archived sessions",\n\t\t\t"archive.manager.noMatches": "No matching archived sessions",\n\t\t\t"archive.manager.hasMore": "Showing the first 20 content matches. Narrow your search.",\n\t\t\t"archive.manager.restore": "Restore",\n\t\t\t"archive.manager.restoring": "Restoring…",\n\t\t\t"archive.manager.delete": "Delete permanently",\n\t\t\t"menu.deleteSession": "Delete session",\n\t\t\t"delete.session.title": "Permanently delete session?",\n\t\t\t"delete.session.desc": "The local record for “{name}” will be permanently deleted and cannot be recovered. Running work will be stopped safely before deletion.",\n\t\t\t"delete.session.confirm": "Delete permanently",\n\t\t\t"delete.session.pending": "Permanently deleting session…",\n\t\t\t\t\t\t\"archive.manager.detail.title\": \"Archived conversation\",\n\t\t\t\"archive.manager.detail.empty\": \"Select an archived session on the left to read its full conversation.\",\n\t\t\t\"archive.manager.detail.loading\": \"Reading the archived conversation…\",\n\t\t\t\"archive.manager.detail.error\": \"Reading the archived conversation failed: {message}\",\n\t\t\t\"archive.manager.detail.noMessages\": \"This archived session has no conversation messages to show.\",\n\t\t\t\"archive.manager.detail.meta\": \"{n} messages\",\n\t\t\t\"archive.manager.detail.truncated\": \"Only the first {n} messages are shown.\",\n\t\t\t\"archive.manager.roleUser\": \"You\",\n\t\t\t\"archive.manager.roleAssistant\": \"Assistant\",\n\t\t\t\"archive.manager.copy\": \"Copy conversation\",\n\t\t\t\"archive.manager.copied\": \"Copied\",\n\t\t\t\"archive.manager.retry\": \"Retry\",\n',
|
|
342
|
+
'English delete locale',
|
|
343
|
+
)
|
|
344
|
+
const archiveActionMarker = findMarker(source, [
|
|
345
|
+
`\t\t\t\tarchiveSession: async (sessionId) => {\n\t\t\t\t\tawait ctx.workspaces.archiveSession(sessionId);\n\t\t\t\t},\n`,
|
|
346
|
+
`\t\t\t\tarchiveSession: async (sessionId) => {\n\t\t\t\t\tawait uiWorkspace.archiveSession(sessionId);\n\t\t\t\t},\n`,
|
|
347
|
+
], 'browser archive action')
|
|
348
|
+
patch(
|
|
349
|
+
archiveActionMarker,
|
|
350
|
+
`\t\t\t\tarchiveSession: async (sessionId) => {\n\t\t\t\t\tawait ctx.workspaces.archiveSession(sessionId);\n\t\t\t\t},\n\t\t\t\tdeleteSession: async (sessionId) => {\n\t\t\t\t\tconst response = await fetch("/plugins/dsh-session-delete/delete", {\n\t\t\t\t\t\tmethod: "POST",\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t"content-type": "application/json",\n\t\t\t\t\t\t\t"x-dsh-session-delete-confirmation": "delete-session"\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: JSON.stringify({ sessionId })\n\t\t\t\t\t});\n\t\t\t\t\tconst payload = await response.json().catch(() => null);\n\t\t\t\t\tif (!response.ok || payload?.ok !== true) {\n\t\t\t\t\t\tthrow new Error(payload?.error?.message ?? \`Delete failed (HTTP \${response.status})\`);\n\t\t\t\t\t}\n\t\t\t\t\tif (ctx.sessions.list.getSnapshot().current === sessionId) ctx.sessions.clear();\n\t\t\t\t\tconst refreshes = await Promise.allSettled([\n\t\t\t\t\t\tctx.sessions.refresh(),\n\t\t\t\t\t\tctx.workspaces.refresh()\n\t\t\t\t\t]);\n\t\t\t\t\tfor (const refresh of refreshes) {\n\t\t\t\t\t\tif (refresh.status === "rejected") console.warn("session deletion succeeded but runtime refresh failed:", refresh.reason);\n\t\t\t\t\t}\n\t\t\t\t},\n`,
|
|
351
|
+
'browser delete request',
|
|
352
|
+
)
|
|
353
|
+
patch(
|
|
354
|
+
'\t\t\t\tinsertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {\n',
|
|
355
|
+
`\t\t\t\topenArchivedSettings: (probe = false) => (${openOfficialArchives.toString()})(ctx, probe),
|
|
356
|
+
\t\t\t\trestoreSession: async (sessionId) => {
|
|
357
|
+
\t\t\t\t\tconst response = await fetch("/plugins/dsh-session-delete/restore", {
|
|
358
|
+
\t\t\t\t\t\tmethod: "POST",
|
|
359
|
+
\t\t\t\t\t\theaders: {
|
|
360
|
+
\t\t\t\t\t\t\t"content-type": "application/json",
|
|
361
|
+
\t\t\t\t\t\t\t"x-dsh-session-manager-action": "restore-session"
|
|
362
|
+
\t\t\t\t\t\t},
|
|
363
|
+
\t\t\t\t\t\tbody: JSON.stringify({ sessionId })
|
|
364
|
+
\t\t\t\t\t});
|
|
365
|
+
\t\t\t\t\tconst payload = await response.json().catch(() => null);
|
|
366
|
+
\t\t\t\t\tif (!response.ok || payload?.ok !== true) throw new Error(payload?.error?.message ?? \`Restore failed (HTTP \${response.status})\`);
|
|
367
|
+
\t\t\t\t\tconst refreshes = await Promise.allSettled([ctx.sessions.refresh(), ctx.workspaces.refresh()]);
|
|
368
|
+
\t\t\t\t\tfor (const refresh of refreshes) if (refresh.status === "rejected") console.warn("session restore succeeded but runtime refresh failed:", refresh.reason);
|
|
369
|
+
\t\t\t\t},
|
|
370
|
+
\t\t\t\t\t\t\t\treadArchivedSession: async (sessionId, signal) => {\n\t\t\t\t\tconst response = await fetch(\"/plugins/dsh-session-delete/archive-detail\", {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\t\t\tbody: JSON.stringify({ sessionId }),\n\t\t\t\t\t\tsignal\n\t\t\t\t\t});\n\t\t\t\t\tconst payload = await response.json().catch(() => null);\n\t\t\t\t\tif (!response.ok || payload?.ok !== true) throw new Error(payload?.error?.message ?? \"Archived detail failed (HTTP \" + response.status + \")\");\n\t\t\t\t\treturn payload.value;\n\t\t\t\t},\n\t\t\t\tsearchArchivedSessions: async (query, signal) => {
|
|
371
|
+
\t\t\t\t\tconst response = await fetch("/plugins/dsh-session-delete/archive-search", {
|
|
372
|
+
\t\t\t\t\t\tmethod: "POST",
|
|
373
|
+
\t\t\t\t\t\theaders: { "content-type": "application/json" },
|
|
374
|
+
\t\t\t\t\t\tbody: JSON.stringify({ query }),
|
|
375
|
+
\t\t\t\t\t\tsignal
|
|
376
|
+
\t\t\t\t\t});
|
|
377
|
+
\t\t\t\t\tconst payload = await response.json().catch(() => null);
|
|
378
|
+
\t\t\t\t\tif (!response.ok || payload?.ok !== true) throw new Error(payload?.error?.message ?? \`Archived search failed (HTTP \${response.status})\`);
|
|
379
|
+
\t\t\t\t\treturn payload.value;
|
|
380
|
+
\t\t\t\t},
|
|
381
|
+
\t\t\t\tinsertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {
|
|
382
|
+
`,
|
|
383
|
+
'browser archive manager requests',
|
|
384
|
+
)
|
|
385
|
+
const workspaceRefreshCall = 'ctx.workspaces.refresh()'
|
|
386
|
+
if (source.split(workspaceRefreshCall).length !== 3) {
|
|
387
|
+
throw new Error('upstream marker mismatch: workspace refresh compatibility')
|
|
388
|
+
}
|
|
389
|
+
source = source.replaceAll(
|
|
390
|
+
workspaceRefreshCall,
|
|
391
|
+
'typeof ctx.workspaces.refresh === "function" ? ctx.workspaces.refresh() : Promise.resolve()',
|
|
392
|
+
)
|
|
393
|
+
const homePathCall = '(0, _deepseek_ai_dsh_client_runtime_client.abbreviateHomePath)(row.cwd, home)'
|
|
394
|
+
if (source.includes(homePathCall)) {
|
|
395
|
+
patch(
|
|
396
|
+
homePathCall,
|
|
397
|
+
'typeof _deepseek_ai_dsh_client_runtime_client.abbreviateHomePath === "function" ? (0, _deepseek_ai_dsh_client_runtime_client.abbreviateHomePath)(row.cwd, home) : row.cwd',
|
|
398
|
+
'home path compatibility fallback',
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
source = source.replace(
|
|
403
|
+
/\/\/#region \\0dsh-css:[^\r\n]*?packages[\\/]client[\\/]ui-workspace[\\/]/g,
|
|
404
|
+
'//#region \\0dsh-css:@deepseek-ai/dsh-client-ui-workspace/',
|
|
405
|
+
)
|
|
406
|
+
source = source.replace(
|
|
407
|
+
/(^\s*\/\/#region \\0dsh-css:@deepseek-ai\/dsh-client-ui-workspace\/)([^\r\n]+)/gm,
|
|
408
|
+
(_, prefix, modulePath) => `${prefix}${modulePath.replaceAll('\\', '/')}`,
|
|
409
|
+
)
|
|
410
|
+
source = normalizeCssModulePrefix(source, 'Rows', 'dcmRows')
|
|
411
|
+
source = normalizeCssModulePrefix(source, 'WorkspacePicker', 'dcmPicker')
|
|
412
|
+
source = normalizeCssModulePrefix(source, 'WorkspaceBrowser', 'dcmBrowser')
|
|
413
|
+
|
|
414
|
+
const notice = `// Modified from @deepseek-ai/dsh-client-ui-workspace ${upstreamVersion} by DSH Chat Manager. See THIRD_PARTY_NOTICES.md.\n`
|
|
415
|
+
return `${notice}${source}`
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export async function buildClient() {
|
|
419
|
+
const stableManifest = JSON.parse(await readFile(resolveLegacyManifest(), 'utf8'))
|
|
420
|
+
const expectedStableVersion = compatibility.legacyWorkspaceFixture === undefined
|
|
421
|
+
? LATEST_UPSTREAM_VERSION
|
|
422
|
+
: Object.entries(compatibility.workspaceFixtures).find(([, fixture]) => fixture === compatibility.legacyWorkspaceFixture)?.[0]
|
|
423
|
+
if (stableManifest.version !== expectedStableVersion) {
|
|
424
|
+
throw new Error(
|
|
425
|
+
`unsupported stable @deepseek-ai/dsh-client-ui-workspace version: ${stableManifest.version ?? 'unknown'}`,
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
const previewManifest = JSON.parse(await readFile(
|
|
429
|
+
compatibility.legacyWorkspaceFixture === undefined ? resolvePreviewManifest() : resolveUpstreamManifest(),
|
|
430
|
+
'utf8',
|
|
431
|
+
))
|
|
432
|
+
if (compatibility.legacyWorkspaceFixture === undefined
|
|
433
|
+
? !compatibility.previews.includes(previewManifest.version)
|
|
434
|
+
: previewManifest.version !== LATEST_UPSTREAM_VERSION) {
|
|
435
|
+
throw new Error(`unreviewed DSH preview ${String(previewManifest.version)}`)
|
|
436
|
+
}
|
|
437
|
+
const stable = patchWorkspaceClient(await readFile(resolveLegacyClient(), 'utf8'), stableManifest.version)
|
|
438
|
+
const preview = patchWorkspaceClient(await readFile(
|
|
439
|
+
compatibility.legacyWorkspaceFixture === undefined ? resolvePreviewClient() : resolveUpstreamClient(),
|
|
440
|
+
'utf8',
|
|
441
|
+
), previewManifest.version)
|
|
442
|
+
const patched = composeCompatibleClients(stable, preview)
|
|
443
|
+
await mkdir(dirname(output), { recursive: true })
|
|
444
|
+
await writeFile(output, patched, 'utf8')
|
|
445
|
+
return output
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (process.argv[1] !== undefined && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
|
|
449
|
+
process.stdout.write(`${await buildClient()}\n`)
|
|
450
|
+
}
|