relay-dsh-plugin-claude 0.1.2 → 0.1.4

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/lib/client.js CHANGED
@@ -50,6 +50,496 @@ window.__ModuleLoader__.load({
50
50
  return off;
51
51
  }
52
52
  //#endregion
53
+ //#region claude-import-contract.mjs
54
+ const CLAUDE_IMPORT_PATH = "/api/relay/claude/import";
55
+ //#endregion
56
+ //#region src/client/claude-session-import-client.mjs
57
+ function resolveImportWorkspace(workspaces, sessions) {
58
+ const current = sessions?.current;
59
+ if (current !== void 0) {
60
+ const owner = workspaces?.items?.find((workspace) => workspace.sessionIds?.includes(current));
61
+ if (owner) return owner;
62
+ }
63
+ const recent = workspaces?.recentWorkspaceId;
64
+ return workspaces?.items?.find((workspace) => workspace.workspaceId === recent) ?? null;
65
+ }
66
+ async function scanClaudeWorkspace(cwd, fetchImpl = fetch) {
67
+ const response = await fetchImpl(CLAUDE_IMPORT_PATH, requestInit("scan", cwd));
68
+ const body = await readJsonResponse(response);
69
+ if (!response.ok) throw new Error(body?.message ?? `Claude import scan failed with HTTP ${response.status}`);
70
+ return body;
71
+ }
72
+ async function importClaudeWorkspace(cwd, { sessionIds, onProgress } = {}, fetchImpl = fetch) {
73
+ const response = await fetchImpl(CLAUDE_IMPORT_PATH, requestInit("import", cwd, sessionIds));
74
+ if (!response.ok) {
75
+ const body = await readJsonResponse(response);
76
+ throw new Error(body?.message ?? `Claude import failed with HTTP ${response.status}`);
77
+ }
78
+ let completed = null;
79
+ for await (const frame of ndjsonFrames(response.body)) {
80
+ if (frame?.type === "progress") onProgress?.(frame);
81
+ if (frame?.type === "complete") completed = frame.result;
82
+ if (frame?.type === "error") throw new Error(frame.message ?? "Claude import failed");
83
+ }
84
+ if (completed === null) throw new Error("Claude import response ended before completion");
85
+ return completed;
86
+ }
87
+ async function refreshImportedWorkspace(sessions, workspaces) {
88
+ await sessions.refresh();
89
+ await workspaces.refresh();
90
+ }
91
+ async function* ndjsonFrames(body) {
92
+ if (!body || typeof body.getReader !== "function") throw new Error("Claude import response has no readable body");
93
+ const reader = body.getReader();
94
+ const decoder = new TextDecoder();
95
+ let buffer = "";
96
+ try {
97
+ while (true) {
98
+ const { done, value } = await reader.read();
99
+ buffer += decoder.decode(value, { stream: !done });
100
+ let newline = buffer.indexOf("\n");
101
+ while (newline !== -1) {
102
+ const line = buffer.slice(0, newline).trim();
103
+ buffer = buffer.slice(newline + 1);
104
+ if (line) yield parseFrame(line);
105
+ newline = buffer.indexOf("\n");
106
+ }
107
+ if (done) break;
108
+ }
109
+ const finalLine = buffer.trim();
110
+ if (finalLine) yield parseFrame(finalLine);
111
+ } finally {
112
+ reader.releaseLock();
113
+ }
114
+ }
115
+ function requestInit(action, cwd, sessionIds) {
116
+ return {
117
+ method: "POST",
118
+ headers: { "content-type": "application/json" },
119
+ body: JSON.stringify({
120
+ action,
121
+ cwd,
122
+ ...sessionIds === void 0 ? {} : { sessionIds }
123
+ })
124
+ };
125
+ }
126
+ async function readJsonResponse(response) {
127
+ try {
128
+ return await response.json();
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+ function parseFrame(line) {
134
+ try {
135
+ return JSON.parse(line);
136
+ } catch {
137
+ throw new Error("Claude import returned malformed progress data");
138
+ }
139
+ }
140
+ //#endregion
141
+ //#region src/client/claude-session-import-ui-policy.mjs
142
+ function claudeSessionImportUiPolicy(phase, selected = 0, failed = 0) {
143
+ if (phase === "importing") return Object.freeze({
144
+ canClose: false,
145
+ primary: "importing",
146
+ primaryDisabled: true
147
+ });
148
+ if (phase === "summary") return Object.freeze({
149
+ canClose: true,
150
+ secondary: "cancel",
151
+ primary: "import-selected",
152
+ primaryDisabled: selected === 0
153
+ });
154
+ if (phase === "error") return Object.freeze({
155
+ canClose: true,
156
+ secondary: "cancel",
157
+ primary: "retry",
158
+ primaryDisabled: false
159
+ });
160
+ if (phase === "complete" && failed > 0) return Object.freeze({
161
+ canClose: true,
162
+ secondary: "close",
163
+ primary: "retry",
164
+ primaryDisabled: false
165
+ });
166
+ return Object.freeze({
167
+ canClose: true,
168
+ primary: "close",
169
+ primaryDisabled: false
170
+ });
171
+ }
172
+ function claudeSessionImportUpdatedAtDate(value) {
173
+ if (value === null || value === void 0) return null;
174
+ const milliseconds = typeof value === "number" && Number.isFinite(value) && Math.abs(value) < 0xe8d4a51000 ? value * 1e3 : value;
175
+ const date = new Date(milliseconds);
176
+ return Number.isNaN(date.getTime()) ? null : date;
177
+ }
178
+ //#endregion
179
+ //#region \0relay-css-module:./src/client/ClaudeSessionImportAction.module.css.mjs
180
+ const css$1 = "._8iKb1W_trigger{width:100%;min-width:0;height:34px;color:var(--dsw-alias-label-secondary);cursor:pointer;font:inherit;letter-spacing:0;background:0 0;border:0;border-radius:6px;justify-content:flex-start;align-items:center;gap:10px;padding:0 12px;font-size:13px;display:flex}._8iKb1W_trigger:hover{background:var(--dsw-alias-fill-l2);color:var(--dsw-alias-label-primary)}._8iKb1W_trigger:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}._8iKb1W_trigger span{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}._8iKb1W_dialog{width:min(700px,100vw - 32px)}._8iKb1W_body{flex-direction:column;gap:18px;min-height:128px;display:flex}._8iKb1W_workspace{border-bottom:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:3px;min-width:0;padding-bottom:12px;display:flex}._8iKb1W_workspace strong{overflow-wrap:anywhere;color:var(--dsw-alias-label-primary);letter-spacing:0;font-size:14px;font-weight:500;line-height:20px}._8iKb1W_workspace span{color:var(--dsw-alias-label-tertiary);letter-spacing:0;overflow-wrap:anywhere;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:17px;overflow:hidden}._8iKb1W_message,._8iKb1W_error,._8iKb1W_success,._8iKb1W_partial{letter-spacing:0;margin:0;font-size:13px;line-height:20px}._8iKb1W_message{color:var(--dsw-alias-label-secondary)}._8iKb1W_error,._8iKb1W_dangerValue{color:var(--dsw-alias-state-error-primary)}._8iKb1W_success,._8iKb1W_accentValue{color:var(--dsw-alias-state-success-primary)}._8iKb1W_partial{color:var(--dsw-alias-state-error-secondary)}._8iKb1W_metrics{border-top:1px solid var(--dsw-alias-border-l2);border-left:1px solid var(--dsw-alias-border-l2);grid-template-columns:repeat(2,minmax(0,1fr));margin:0;display:grid}._8iKb1W_summary{flex-direction:column;gap:14px;min-width:0;display:flex}._8iKb1W_selectionToolbar{min-height:28px;color:var(--dsw-alias-label-secondary);letter-spacing:0;justify-content:space-between;align-items:center;gap:12px;font-size:12px;line-height:18px;display:flex}._8iKb1W_selectionToolbar>div{gap:4px;display:flex}._8iKb1W_selectionToolbar button{min-height:28px;color:var(--dsw-alias-state-business-primary);cursor:pointer;font:inherit;letter-spacing:0;background:0 0;border:0;border-radius:4px;padding:0 8px}._8iKb1W_selectionToolbar button:hover{background:var(--dsw-alias-fill-l2)}._8iKb1W_selectionToolbar button:focus-visible,._8iKb1W_candidates input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}._8iKb1W_candidates{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;max-height:min(390px,48vh);margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}._8iKb1W_candidates li{border-bottom:1px solid var(--dsw-alias-border-l2);min-width:0}._8iKb1W_candidates label{cursor:pointer;grid-template-columns:18px minmax(0,1fr);gap:10px;min-width:0;padding:11px 4px;display:grid}._8iKb1W_candidates input{width:16px;height:16px;accent-color:var(--dsw-alias-state-business-primary);margin:2px 0 0}._8iKb1W_candidateBody{flex-direction:column;gap:2px;min-width:0;display:flex}._8iKb1W_candidateHeading{justify-content:space-between;align-items:flex-start;gap:12px;min-width:0;display:flex}._8iKb1W_candidateHeading strong{overflow-wrap:anywhere;min-width:0;color:var(--dsw-alias-label-primary);letter-spacing:0;font-size:13px;font-weight:500;line-height:19px}._8iKb1W_candidateHeading>span{color:var(--dsw-alias-state-success-primary);letter-spacing:0;flex:none;font-size:11px;line-height:18px}._8iKb1W_candidateBody code,._8iKb1W_candidateBody>span,._8iKb1W_candidateBody time{overflow-wrap:anywhere;min-width:0;color:var(--dsw-alias-label-tertiary);letter-spacing:0;font-size:11px;line-height:17px}._8iKb1W_candidateBody code{color:var(--dsw-alias-label-secondary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}._8iKb1W_metric{border-right:1px solid var(--dsw-alias-border-l2);border-bottom:1px solid var(--dsw-alias-border-l2);min-width:0;padding:10px 12px}._8iKb1W_metric dt{color:var(--dsw-alias-label-tertiary);letter-spacing:0;font-size:11px;line-height:16px}._8iKb1W_metric dd{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;letter-spacing:0;margin:2px 0 0;font-size:18px;font-weight:500;line-height:24px}._8iKb1W_progress{flex-direction:column;gap:10px;display:flex}._8iKb1W_progressCopy{color:var(--dsw-alias-label-secondary);letter-spacing:0;justify-content:space-between;align-items:center;gap:16px;font-size:13px;line-height:20px;display:flex}._8iKb1W_progressCopy strong{color:var(--dsw-alias-label-primary);font-weight:500}._8iKb1W_progress progress{width:100%;height:6px;accent-color:var(--dsw-alias-state-business-primary)}._8iKb1W_failures{flex-direction:column;gap:8px;margin:14px 0 0;padding:0;list-style:none;display:flex}._8iKb1W_failures li{min-width:0;color:var(--dsw-alias-label-secondary);letter-spacing:0;grid-template-columns:minmax(88px,auto) minmax(0,1fr);gap:10px;font-size:12px;line-height:18px;display:grid}._8iKb1W_failures code,._8iKb1W_failures span{overflow-wrap:anywhere}._8iKb1W_failures code{color:var(--dsw-alias-state-error-primary)}@media (width<=520px){._8iKb1W_dialog{width:calc(100vw - 20px)}._8iKb1W_metrics{grid-template-columns:minmax(0,1fr)}._8iKb1W_selectionToolbar,._8iKb1W_candidateHeading{flex-direction:column;align-items:flex-start}._8iKb1W_candidateHeading{gap:2px}}";
181
+ const tagId$1 = "relay-dsh-plugin-claude/ClaudeSessionImportAction.module.css";
182
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
183
+ const tag = document.createElement("style");
184
+ tag.dataset.plugin = "relay-dsh-plugin-claude";
185
+ tag.dataset.pluginCss = tagId$1;
186
+ tag.textContent = css$1;
187
+ document.head.appendChild(tag);
188
+ }
189
+ var ClaudeSessionImportAction_module_css_default = {
190
+ "accentValue": "_8iKb1W_accentValue",
191
+ "body": "_8iKb1W_body",
192
+ "candidateBody": "_8iKb1W_candidateBody",
193
+ "candidateHeading": "_8iKb1W_candidateHeading",
194
+ "candidates": "_8iKb1W_candidates",
195
+ "dangerValue": "_8iKb1W_dangerValue",
196
+ "dialog": "_8iKb1W_dialog",
197
+ "error": "_8iKb1W_error",
198
+ "failures": "_8iKb1W_failures",
199
+ "message": "_8iKb1W_message",
200
+ "metric": "_8iKb1W_metric",
201
+ "metrics": "_8iKb1W_metrics",
202
+ "partial": "_8iKb1W_partial",
203
+ "progress": "_8iKb1W_progress",
204
+ "progressCopy": "_8iKb1W_progressCopy",
205
+ "selectionToolbar": "_8iKb1W_selectionToolbar",
206
+ "success": "_8iKb1W_success",
207
+ "summary": "_8iKb1W_summary",
208
+ "trigger": "_8iKb1W_trigger",
209
+ "workspace": "_8iKb1W_workspace"
210
+ };
211
+ //#endregion
212
+ //#region src/client/ClaudeSessionImportAction.tsx
213
+ function ClaudeSessionImportAction({ wide, useClaudeSessionImportWorkspaces, useClaudeSessionImportSessions, scanWorkspace, importWorkspace, refreshWorkspaceState, t }) {
214
+ const availableTarget = resolveImportWorkspace(useClaudeSessionImportWorkspaces((value) => value), useClaudeSessionImportSessions((value) => value));
215
+ const [open, setOpen] = (0, react.useState)(false);
216
+ const [target, setTarget] = (0, react.useState)(null);
217
+ const [phase, setPhase] = (0, react.useState)("idle");
218
+ const [summary, setSummary] = (0, react.useState)(null);
219
+ const [candidates, setCandidates] = (0, react.useState)([]);
220
+ const [selectedIds, setSelectedIds] = (0, react.useState)(/* @__PURE__ */ new Set());
221
+ const [progress, setProgress] = (0, react.useState)(null);
222
+ const [result, setResult] = (0, react.useState)(null);
223
+ const [error, setError] = (0, react.useState)("");
224
+ const request = (0, react.useRef)(0);
225
+ const close = () => {
226
+ if (!claudeSessionImportUiPolicy(phase, selectedIds.size, result?.failed).canClose) return;
227
+ request.current += 1;
228
+ setOpen(false);
229
+ };
230
+ const scan = (workspace) => {
231
+ const generation = ++request.current;
232
+ setPhase("scanning");
233
+ setSummary(null);
234
+ setCandidates([]);
235
+ setSelectedIds(/* @__PURE__ */ new Set());
236
+ setProgress(null);
237
+ setResult(null);
238
+ setError("");
239
+ scanWorkspace(workspace.path).then((response) => {
240
+ if (request.current !== generation) return;
241
+ setSummary(response.summary);
242
+ setCandidates(response.candidates);
243
+ setSelectedIds(new Set(response.candidates.map((candidate) => candidate.id)));
244
+ setPhase("summary");
245
+ }, (reason) => {
246
+ if (request.current !== generation) return;
247
+ setError(messageOf(reason));
248
+ setPhase("error");
249
+ });
250
+ };
251
+ const begin = () => {
252
+ const selected = availableTarget;
253
+ setTarget(selected);
254
+ setOpen(true);
255
+ if (selected === null) {
256
+ setPhase("no-workspace");
257
+ return;
258
+ }
259
+ scan(selected);
260
+ };
261
+ const importSelected = () => {
262
+ const sessionIds = candidates.filter((candidate) => selectedIds.has(candidate.id)).map((candidate) => candidate.id);
263
+ if (target === null || summary === null || sessionIds.length === 0 || phase === "importing") return;
264
+ const generation = ++request.current;
265
+ setPhase("importing");
266
+ setProgress({
267
+ completed: 0,
268
+ total: sessionIds.length,
269
+ found: sessionIds.length,
270
+ imported: 0,
271
+ existing: 0,
272
+ failed: 0,
273
+ failures: []
274
+ });
275
+ setError("");
276
+ (async () => {
277
+ try {
278
+ const completed = await importWorkspace(target.path, sessionIds, (update) => {
279
+ if (request.current === generation) setProgress(update);
280
+ });
281
+ await refreshWorkspaceState();
282
+ if (request.current !== generation) return;
283
+ setResult(completed);
284
+ setPhase("complete");
285
+ } catch (reason) {
286
+ if (request.current !== generation) return;
287
+ setError(messageOf(reason));
288
+ setPhase("error");
289
+ }
290
+ })();
291
+ };
292
+ const retry = () => {
293
+ if (target !== null) scan(target);
294
+ };
295
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
296
+ label: t("importAction"),
297
+ delayMs: 500,
298
+ disabled: wide,
299
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
300
+ type: "button",
301
+ className: ClaudeSessionImportAction_module_css_default.trigger,
302
+ "aria-label": t("importAction"),
303
+ onClick: begin,
304
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { size: wide ? 16 : 18 }), wide && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("importAction") })]
305
+ })
306
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
307
+ open,
308
+ onClose: close,
309
+ title: t("importTitle"),
310
+ closeLabel: t("close"),
311
+ description: t("importDescription"),
312
+ className: ClaudeSessionImportAction_module_css_default.dialog,
313
+ footer: modalFooter({
314
+ phase,
315
+ selectedCount: selectedIds.size,
316
+ result,
317
+ close,
318
+ retry,
319
+ importSelected,
320
+ t
321
+ }),
322
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
323
+ className: ClaudeSessionImportAction_module_css_default.body,
324
+ "aria-live": "polite",
325
+ children: [
326
+ target !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
327
+ className: ClaudeSessionImportAction_module_css_default.workspace,
328
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: target.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
329
+ title: target.path,
330
+ children: target.path
331
+ })]
332
+ }),
333
+ phase === "no-workspace" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
334
+ className: ClaudeSessionImportAction_module_css_default.message,
335
+ children: t("importNoWorkspace")
336
+ }),
337
+ phase === "scanning" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
338
+ className: ClaudeSessionImportAction_module_css_default.message,
339
+ children: t("importScanning")
340
+ }),
341
+ phase === "summary" && summary !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SummaryView, {
342
+ summary,
343
+ candidates,
344
+ selectedIds,
345
+ onSelectionChange: setSelectedIds,
346
+ t
347
+ }),
348
+ phase === "importing" && progress !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProgressView, {
349
+ progress,
350
+ t
351
+ }),
352
+ phase === "complete" && result !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResultView, {
353
+ result,
354
+ t
355
+ }),
356
+ phase === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
357
+ className: ClaudeSessionImportAction_module_css_default.error,
358
+ role: "alert",
359
+ children: error || t("importFailed")
360
+ })
361
+ ]
362
+ })
363
+ })] });
364
+ }
365
+ function SummaryView({ summary, candidates, selectedIds, onSelectionChange, t }) {
366
+ const select = (id, checked) => {
367
+ const next = new Set(selectedIds);
368
+ if (checked) next.add(id);
369
+ else next.delete(id);
370
+ onSelectionChange(next);
371
+ };
372
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
373
+ className: ClaudeSessionImportAction_module_css_default.summary,
374
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
375
+ className: ClaudeSessionImportAction_module_css_default.metrics,
376
+ children: [
377
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
378
+ label: t("importFound"),
379
+ value: summary.found
380
+ }),
381
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
382
+ label: t("importExisting"),
383
+ value: summary.existing
384
+ }),
385
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
386
+ label: t("importRecoverable"),
387
+ value: summary.recoverable
388
+ }),
389
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
390
+ label: t("importReady"),
391
+ value: summary.ready,
392
+ accent: true
393
+ })
394
+ ]
395
+ }), candidates.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
396
+ className: ClaudeSessionImportAction_module_css_default.message,
397
+ children: t("importEmpty")
398
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
399
+ className: ClaudeSessionImportAction_module_css_default.selectionToolbar,
400
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
401
+ t("importSelected"),
402
+ ": ",
403
+ selectedIds.size,
404
+ " / ",
405
+ candidates.length
406
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
407
+ type: "button",
408
+ onClick: () => onSelectionChange(new Set(candidates.map((candidate) => candidate.id))),
409
+ children: t("importSelectAll")
410
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
411
+ type: "button",
412
+ onClick: () => onSelectionChange(/* @__PURE__ */ new Set()),
413
+ children: t("importClearSelection")
414
+ })] })]
415
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
416
+ className: ClaudeSessionImportAction_module_css_default.candidates,
417
+ "aria-label": t("importCandidates"),
418
+ children: candidates.map((candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
419
+ type: "checkbox",
420
+ checked: selectedIds.has(candidate.id),
421
+ onChange: (event) => select(candidate.id, event.currentTarget.checked)
422
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
423
+ className: ClaudeSessionImportAction_module_css_default.candidateBody,
424
+ children: [
425
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
426
+ className: ClaudeSessionImportAction_module_css_default.candidateHeading,
427
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: candidate.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: candidate.status === "recoverable" ? t("importStatusRecoverable") : t("importStatusReady") })]
428
+ }),
429
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: candidate.id }),
430
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
431
+ title: candidate.cwd,
432
+ children: candidate.cwd
433
+ }),
434
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
435
+ dateTime: dateTimeValue(candidate.updatedAt),
436
+ children: formatUpdatedAt(candidate.updatedAt)
437
+ })
438
+ ]
439
+ })] }) }, candidate.id))
440
+ })] })]
441
+ });
442
+ }
443
+ function ProgressView({ progress, t }) {
444
+ const maximum = Math.max(1, progress.total);
445
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
446
+ className: ClaudeSessionImportAction_module_css_default.progress,
447
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
448
+ className: ClaudeSessionImportAction_module_css_default.progressCopy,
449
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("importImporting") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
450
+ progress.completed,
451
+ " / ",
452
+ progress.total
453
+ ] })]
454
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("progress", {
455
+ value: progress.completed,
456
+ max: maximum,
457
+ "aria-label": t("importImporting")
458
+ })]
459
+ });
460
+ }
461
+ function ResultView({ result, t }) {
462
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
463
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
464
+ className: result.failed > 0 ? ClaudeSessionImportAction_module_css_default.partial : ClaudeSessionImportAction_module_css_default.success,
465
+ children: result.failed > 0 ? t("importPartial") : t("importComplete")
466
+ }),
467
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
468
+ className: ClaudeSessionImportAction_module_css_default.metrics,
469
+ children: [
470
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
471
+ label: t("importImported"),
472
+ value: result.imported,
473
+ accent: true
474
+ }),
475
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
476
+ label: t("importExisting"),
477
+ value: result.existing
478
+ }),
479
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
480
+ label: t("importFailures"),
481
+ value: result.failed,
482
+ danger: result.failed > 0
483
+ })
484
+ ]
485
+ }),
486
+ result.failures.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
487
+ className: ClaudeSessionImportAction_module_css_default.failures,
488
+ "aria-label": t("importFailures"),
489
+ children: result.failures.map((failure) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: failure.session }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: failure.message })] }, failure.session))
490
+ })
491
+ ] });
492
+ }
493
+ function Metric({ label, value, accent = false, danger = false }) {
494
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
495
+ className: ClaudeSessionImportAction_module_css_default.metric,
496
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
497
+ className: danger ? ClaudeSessionImportAction_module_css_default.dangerValue : accent ? ClaudeSessionImportAction_module_css_default.accentValue : void 0,
498
+ children: value
499
+ })]
500
+ });
501
+ }
502
+ function modalFooter({ phase, selectedCount, result, close, retry, importSelected, t }) {
503
+ const policy = claudeSessionImportUiPolicy(phase, selectedCount, result?.failed);
504
+ const actions = {
505
+ cancel: close,
506
+ close,
507
+ "import-selected": importSelected,
508
+ importing: void 0,
509
+ retry
510
+ };
511
+ const labels = {
512
+ cancel: t("cancel"),
513
+ close: t("close"),
514
+ "import-selected": t("importSelectedAction"),
515
+ importing: t("importImporting"),
516
+ retry: t("retry")
517
+ };
518
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [policy.secondary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
519
+ variant: "outline",
520
+ onClick: actions[policy.secondary],
521
+ children: labels[policy.secondary]
522
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
523
+ variant: policy.primary === "close" ? "outline" : void 0,
524
+ disabled: policy.primaryDisabled,
525
+ onClick: actions[policy.primary],
526
+ children: labels[policy.primary]
527
+ })] });
528
+ }
529
+ function dateTimeValue(value) {
530
+ return claudeSessionImportUpdatedAtDate(value)?.toISOString();
531
+ }
532
+ function formatUpdatedAt(value) {
533
+ const date = claudeSessionImportUpdatedAtDate(value);
534
+ return date === null ? "-" : new Intl.DateTimeFormat(void 0, {
535
+ dateStyle: "medium",
536
+ timeStyle: "short"
537
+ }).format(date);
538
+ }
539
+ function messageOf(reason) {
540
+ return reason instanceof Error ? reason.message : String(reason);
541
+ }
542
+ //#endregion
53
543
  //#region \0relay-css-module:./src/client/ClaudeActivityView.module.css.mjs
54
544
  const css = ".ts8jCW_activity{width:min(100%,960px);color:var(--dsw-alias-label-secondary)}.ts8jCW_summary{min-width:0;color:var(--dsw-alias-label-tertiary);flex:1;align-items:center;gap:8px;font-size:13px;display:flex}.ts8jCW_summary :first-child{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.ts8jCW_detail{border-left:1px solid var(--dsw-alias-border-l2);margin:4px 0 8px 28px;padding-left:12px;overflow:hidden}.ts8jCW_detail pre{max-height:320px;color:var(--dsw-alias-label-secondary);font:var(--dsw-font-markdown-code-block-small);white-space:pre-wrap;word-break:break-word;margin:0 0 8px;overflow:auto}.ts8jCW_provenance{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;margin-bottom:8px;font-size:12px;line-height:18px;overflow:hidden}";
55
545
  const tagId = "relay-dsh-plugin-claude/ClaudeActivityView.module.css";
@@ -160,6 +650,64 @@ window.__ModuleLoader__.load({
160
650
  }
161
651
  };
162
652
  //#endregion
653
+ //#region src/client/locales.ts
654
+ const en = {
655
+ importAction: "Import Claude Sessions",
656
+ importTitle: "Import Claude Sessions",
657
+ importDescription: "Select existing Claude terminal Sessions from this Workspace to continue in DSH.",
658
+ importNoWorkspace: "Open or select a Workspace before importing Sessions.",
659
+ importScanning: "Scanning native Claude Sessions...",
660
+ importFound: "Found",
661
+ importExisting: "Already linked",
662
+ importRecoverable: "Recoverable",
663
+ importReady: "Available",
664
+ importEmpty: "No unlinked Claude Sessions are available in this Workspace.",
665
+ importSelected: "Selected",
666
+ importSelectAll: "Select all",
667
+ importClearSelection: "Clear",
668
+ importCandidates: "Claude Session candidates",
669
+ importStatusReady: "Ready",
670
+ importStatusRecoverable: "Retry import",
671
+ importSelectedAction: "Import selected",
672
+ importImporting: "Importing Sessions...",
673
+ importComplete: "Import completed.",
674
+ importPartial: "Some Sessions could not be imported.",
675
+ importImported: "Imported",
676
+ importFailures: "Failed",
677
+ importFailed: "Claude Session import failed.",
678
+ cancel: "Cancel",
679
+ close: "Close",
680
+ retry: "Retry"
681
+ };
682
+ const zh = {
683
+ importAction: "导入 Claude 会话",
684
+ importTitle: "导入 Claude 会话",
685
+ importDescription: "选择当前工作区中已有的 Claude 终端会话,并在 DSH 中继续。",
686
+ importNoWorkspace: "请先打开或选择一个工作区。",
687
+ importScanning: "正在扫描原生 Claude 会话...",
688
+ importFound: "发现",
689
+ importExisting: "已绑定",
690
+ importRecoverable: "可恢复",
691
+ importReady: "可导入",
692
+ importEmpty: "当前工作区没有未绑定的 Claude 会话。",
693
+ importSelected: "已选择",
694
+ importSelectAll: "全选",
695
+ importClearSelection: "清空",
696
+ importCandidates: "Claude 会话候选列表",
697
+ importStatusReady: "可导入",
698
+ importStatusRecoverable: "重试导入",
699
+ importSelectedAction: "导入所选会话",
700
+ importImporting: "正在导入会话...",
701
+ importComplete: "导入完成。",
702
+ importPartial: "部分会话未能导入。",
703
+ importImported: "已导入",
704
+ importFailures: "失败",
705
+ importFailed: "Claude 会话导入失败。",
706
+ cancel: "取消",
707
+ close: "关闭",
708
+ retry: "重试"
709
+ };
710
+ //#endregion
163
711
  //#region src/client/index.ts
164
712
  const inject = [
165
713
  "slots",
@@ -167,20 +715,47 @@ window.__ModuleLoader__.load({
167
715
  "locale",
168
716
  "remote",
169
717
  "sessions",
718
+ "workspaces",
170
719
  "connection",
171
720
  "conversationEvents"
172
721
  ];
173
722
  async function apply(ctx) {
723
+ ctx.effect(() => ctx.locale.register("relay.claude", {
724
+ zh,
725
+ en
726
+ }), "relay-claude: dictionaries");
174
727
  ctx.conversationEvents.register(claudeActivityDefinition);
175
728
  ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
176
729
  name: "conversation.chat.node",
177
730
  key: "relay-claude-activity"
178
731
  }, ClaudeActivityView));
732
+ applySessionImport(ctx);
179
733
  const unsubscribe = installModelSelection(ctx, "relay-claude", "relay-claude", "relay-codex");
180
734
  return async () => {
181
735
  unsubscribe();
182
736
  };
183
737
  }
738
+ function applySessionImport(ctx) {
739
+ const injected = () => ({
740
+ hooks: {
741
+ claudeSessionImportWorkspaces: ctx.workspaces.list,
742
+ claudeSessionImportSessions: ctx.sessions.list
743
+ },
744
+ scanWorkspace: (cwd) => scanClaudeWorkspace(cwd),
745
+ importWorkspace: (cwd, sessionIds, onProgress) => importClaudeWorkspace(cwd, {
746
+ sessionIds,
747
+ onProgress
748
+ }),
749
+ refreshWorkspaceState: () => refreshImportedWorkspace(ctx.sessions, ctx.workspaces)
750
+ });
751
+ ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
752
+ name: "sidebar.footer.action",
753
+ id: "relay-claude-session-import",
754
+ order: -9,
755
+ inject: injected,
756
+ locale: "relay.claude"
757
+ }, ClaudeSessionImportAction));
758
+ }
184
759
  //#endregion
185
760
  exports.apply = apply;
186
761
  exports.inject = inject;