dsh-plugin-standalone-session 0.0.0-stage → 0.1.1

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 ADDED
@@ -0,0 +1,1969 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-plugin-standalone-session",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ "use strict";
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
+
25
+ // src/client/index.ts
26
+ var index_exports = {};
27
+ __export(index_exports, {
28
+ NEW_PANEL_ID: () => NEW_PANEL_ID,
29
+ PANEL_ID: () => PANEL_ID,
30
+ apply: () => apply,
31
+ inject: () => inject
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+
35
+ // src/client/adapters.ts
36
+ var import_react = require("react");
37
+
38
+ // src/shared/standalone.ts
39
+ var STANDALONE_DIR_NAME = "dsh-standalone-sessions";
40
+ var SESSION_ID_PREFIX = "session-";
41
+ function pathSegments(path) {
42
+ return path.split(/[\\/]+/).filter((segment) => segment !== "");
43
+ }
44
+ function isStandaloneCwd(cwd) {
45
+ if (typeof cwd !== "string" || cwd.trim() === "") return false;
46
+ const segments = pathSegments(cwd);
47
+ for (let index = 0; index < segments.length - 1; index += 1) {
48
+ if (segments[index]?.toLowerCase() === STANDALONE_DIR_NAME) return true;
49
+ }
50
+ return false;
51
+ }
52
+ function joinPath(base, ...segments) {
53
+ const separator = base.includes("\\") && !base.includes("/") ? "\\" : "/";
54
+ const trimmed = base.replace(/[\\/]+$/, "");
55
+ const children = segments.filter((segment) => segment !== "");
56
+ for (const child of children) {
57
+ if (child === "." || child === ".." || child.includes("/") || child.includes("\\")) {
58
+ throw new Error(`invalid path segment: ${JSON.stringify(child)}`);
59
+ }
60
+ }
61
+ return children.length === 0 ? trimmed : `${trimmed}${separator}${children.join(separator)}`;
62
+ }
63
+ function defaultStandaloneRoot(home) {
64
+ return joinPath(home, ".dsh", STANDALONE_DIR_NAME);
65
+ }
66
+ function makeStandaloneCwd(home, sessionId) {
67
+ assertSessionId(sessionId);
68
+ return joinPath(defaultStandaloneRoot(home), sessionId);
69
+ }
70
+ function assertSessionId(sessionId) {
71
+ if (typeof sessionId !== "string" || sessionId === "" || sessionId.length > 200) {
72
+ throw new Error("invalid standalone session id");
73
+ }
74
+ if (!sessionId.startsWith(SESSION_ID_PREFIX)) {
75
+ throw new Error(`standalone session id must start with "${SESSION_ID_PREFIX}"`);
76
+ }
77
+ if (sessionId.includes("/") || sessionId.includes("\\") || sessionId === "." || sessionId === "..") {
78
+ throw new Error("standalone session id must not contain a path separator");
79
+ }
80
+ return sessionId;
81
+ }
82
+ function randomUuid() {
83
+ const source = globalThis.crypto;
84
+ if (source !== void 0 && typeof source.randomUUID === "function") return source.randomUUID();
85
+ const bytes = new Uint8Array(16);
86
+ if (source !== void 0 && typeof source.getRandomValues === "function") {
87
+ source.getRandomValues(bytes);
88
+ } else {
89
+ for (let index = 0; index < bytes.length; index += 1) {
90
+ bytes[index] = Math.floor(Math.random() * 256);
91
+ }
92
+ }
93
+ bytes[6] = (bytes[6] ?? 0) & 15 | 64;
94
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
95
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
96
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
97
+ }
98
+ function mintSessionId(uuidSource = randomUuid) {
99
+ const uuid = uuidSource();
100
+ return assertSessionId(`${SESSION_ID_PREFIX}${uuid}`);
101
+ }
102
+ function shouldUnlockComposer(sessionId, cwd, disabled) {
103
+ return disabled && sessionId !== void 0 && isStandaloneCwd(cwd);
104
+ }
105
+ function relativeTime(timestamp, now = Date.now()) {
106
+ if (!Number.isFinite(timestamp) || timestamp <= 0) return "";
107
+ const delta = Math.max(0, now - timestamp);
108
+ const minute = 6e4;
109
+ const hour = 60 * minute;
110
+ const day = 24 * hour;
111
+ if (delta < minute) return "now";
112
+ if (delta < hour) return `${Math.floor(delta / minute)}m`;
113
+ if (delta < day) return `${Math.floor(delta / hour)}h`;
114
+ if (delta < 30 * day) return `${Math.floor(delta / day)}d`;
115
+ return new Date(timestamp).toISOString().slice(0, 10);
116
+ }
117
+
118
+ // src/client/adapters.ts
119
+ var COMPOSER_SLOT = "conversation.composer.bar";
120
+ var RECONCILE_INTERVAL_MS = 750;
121
+ var LOG_PREFIX = "[standalone-session]";
122
+ function useSessionCwd(ctx, sessionId) {
123
+ const source = ctx.sessions.list;
124
+ const subscribe = (0, import_react.useMemo)(() => (listener) => source.subscribe(listener), [source]);
125
+ const getSnapshot = (0, import_react.useCallback)(
126
+ () => sessionId === void 0 ? void 0 : source.getSnapshot().byId[sessionId]?.cwd,
127
+ [sessionId, source]
128
+ );
129
+ return (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, () => void 0);
130
+ }
131
+ function patchComposer(ctx, t) {
132
+ const restorers = /* @__PURE__ */ new Map();
133
+ let disposed = false;
134
+ const restoreAll = () => {
135
+ for (const [entry, restore] of restorers) {
136
+ try {
137
+ restore();
138
+ } catch (error) {
139
+ console.warn(`${LOG_PREFIX} could not restore a composer entry`, error);
140
+ }
141
+ restorers.delete(entry);
142
+ }
143
+ };
144
+ const reconcile = () => {
145
+ if (disposed) return;
146
+ let entries;
147
+ try {
148
+ entries = ctx.slots.entries(COMPOSER_SLOT);
149
+ } catch (error) {
150
+ console.warn(`${LOG_PREFIX} could not read the composer slot`, error);
151
+ return;
152
+ }
153
+ const live = new Set(entries);
154
+ for (const [entry, restore] of [...restorers]) {
155
+ if (live.has(entry)) continue;
156
+ try {
157
+ restore();
158
+ } catch {
159
+ }
160
+ restorers.delete(entry);
161
+ }
162
+ for (const entry of entries) {
163
+ if (restorers.has(entry)) continue;
164
+ const priority = entry.options?.priority ?? 0;
165
+ if (priority !== 0) continue;
166
+ const original = entry.component;
167
+ if (original === void 0 || original === null) continue;
168
+ if (typeof original !== "function" && typeof original !== "object") continue;
169
+ const Wrapped = (props) => {
170
+ const sessionId = typeof props.sessionId === "string" ? props.sessionId : void 0;
171
+ const cwd = useSessionCwd(ctx, sessionId);
172
+ if (!shouldUnlockComposer(sessionId, cwd, props.disabled === true)) {
173
+ return (0, import_react.createElement)(original, props);
174
+ }
175
+ return (0, import_react.createElement)(original, {
176
+ ...props,
177
+ disabled: false,
178
+ workspacePickerOpen: false,
179
+ onRequestWorkspace: void 0,
180
+ placeholder: t("composer.placeholder")
181
+ });
182
+ };
183
+ Wrapped.displayName = `StandaloneComposer(${original.displayName ?? original.name ?? "native"})`;
184
+ try {
185
+ entry.component = Wrapped;
186
+ restorers.set(entry, () => {
187
+ if (entry.component === Wrapped) entry.component = original;
188
+ });
189
+ console.info(`${LOG_PREFIX} unlocked the native composer for standalone sessions`);
190
+ } catch (error) {
191
+ console.warn(`${LOG_PREFIX} could not wrap the native composer entry`, error);
192
+ }
193
+ }
194
+ };
195
+ reconcile();
196
+ let unsubscribe;
197
+ try {
198
+ unsubscribe = ctx.slots.subscribe?.(COMPOSER_SLOT, reconcile);
199
+ } catch {
200
+ unsubscribe = void 0;
201
+ }
202
+ const timer = setInterval(reconcile, RECONCILE_INTERVAL_MS);
203
+ return () => {
204
+ disposed = true;
205
+ clearInterval(timer);
206
+ try {
207
+ unsubscribe?.();
208
+ } catch {
209
+ }
210
+ restoreAll();
211
+ };
212
+ }
213
+
214
+ // src/client/components.tsx
215
+ var import_react3 = require("react");
216
+
217
+ // src/client/create.ts
218
+ function isAbsoluteHostPath(path) {
219
+ return /^(?:[A-Za-z]:[\\/]|\\\\|\/)/.test(path);
220
+ }
221
+ async function createStandaloneSession(deps) {
222
+ if (typeof deps.home !== "string" || deps.home.trim() === "") {
223
+ throw new Error(deps.noHomeMessage);
224
+ }
225
+ const home = deps.home.trim();
226
+ if (!isAbsoluteHostPath(home)) {
227
+ throw new Error(`host home is not absolute: ${home}`);
228
+ }
229
+ const sessionId = mintSessionId(deps.randomUuid);
230
+ const cwd = makeStandaloneCwd(home, sessionId);
231
+ const created = await deps.create({ cwd, sessionId });
232
+ deps.open?.(created);
233
+ return created;
234
+ }
235
+
236
+ // src/client/panels.ts
237
+ var PANEL_ID = "standalone-sessions";
238
+ var NEW_PANEL_ID = "standalone-sessions-new";
239
+
240
+ // src/client/notice.ts
241
+ var current = null;
242
+ var sequence = 0;
243
+ var listeners = /* @__PURE__ */ new Set();
244
+ function getNoticeSnapshot() {
245
+ return current;
246
+ }
247
+ function subscribeNotice(listener) {
248
+ listeners.add(listener);
249
+ return () => {
250
+ listeners.delete(listener);
251
+ };
252
+ }
253
+ function showNotice(kind, text) {
254
+ sequence += 1;
255
+ const notice = { id: sequence, kind, text };
256
+ current = notice;
257
+ for (const listener of listeners) listener();
258
+ const timer = setTimeout(() => {
259
+ if (current?.id !== notice.id) return;
260
+ current = null;
261
+ for (const listener of listeners) listener();
262
+ }, 6e3);
263
+ if (typeof timer === "object" && timer !== null && "unref" in timer) {
264
+ const unref = timer.unref;
265
+ if (typeof unref === "function") unref.call(timer);
266
+ }
267
+ }
268
+ function dismissNotice() {
269
+ if (current === null) return;
270
+ current = null;
271
+ for (const listener of listeners) listener();
272
+ }
273
+
274
+ // src/client/actions.ts
275
+ function hostHome(ctx) {
276
+ try {
277
+ return ctx.remote?.$host?.home;
278
+ } catch {
279
+ return void 0;
280
+ }
281
+ }
282
+ async function createStandaloneSessionId(ctx, t) {
283
+ return createStandaloneSession({
284
+ home: hostHome(ctx),
285
+ noHomeMessage: t("error.noHome"),
286
+ create: (options) => ctx.sessions.create(options)
287
+ });
288
+ }
289
+ async function startStandalone(ctx, t) {
290
+ const sessionId = await createStandaloneSessionId(ctx, t);
291
+ ctx.uiWorkspace.openSession(sessionId);
292
+ return sessionId;
293
+ }
294
+ function openManagementPanel(ctx, t) {
295
+ try {
296
+ ctx.layout.selectPanel(PANEL_ID);
297
+ } catch (error) {
298
+ showNotice("error", t("toast.manageFailed", { message: messageOf(error) }));
299
+ }
300
+ }
301
+ async function renameStandalone(ctx, sessionId, title) {
302
+ const trimmed = title.trim();
303
+ if (trimmed === "") throw new Error("empty title");
304
+ const result = await ctx.sessions.using(
305
+ sessionId,
306
+ { source: "standaloneSessionOperation" },
307
+ (reference) => reference.binding.session.rename(trimmed)
308
+ );
309
+ if (!result.ok) throw new Error(result.error.message);
310
+ }
311
+ async function archiveStandalone(ctx, sessionId) {
312
+ await ctx.workspaces.archiveSession(sessionId, { stopActivity: true });
313
+ }
314
+ function messageOf(error) {
315
+ return error instanceof Error ? error.message : String(error);
316
+ }
317
+
318
+ // src/client/hooks.ts
319
+ var import_react2 = require("react");
320
+ function useSnapshot(source) {
321
+ const subscribe = (0, import_react2.useMemo)(() => (listener) => source.subscribe(listener), [source]);
322
+ const getSnapshot = (0, import_react2.useMemo)(() => () => source.getSnapshot(), [source]);
323
+ return (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
324
+ }
325
+ function useSessionList(ctx) {
326
+ return useSnapshot(ctx.sessions.list);
327
+ }
328
+ function useWorkspaceSnapshot(ctx) {
329
+ return useSnapshot(ctx.workspaces.list);
330
+ }
331
+ function useSessionCwd2(ctx, sessionId) {
332
+ const source = ctx.sessions.list;
333
+ const subscribe = (0, import_react2.useMemo)(() => (listener) => source.subscribe(listener), [source]);
334
+ const getSnapshot = (0, import_react2.useCallback)(
335
+ () => sessionId === void 0 ? void 0 : source.getSnapshot().byId[sessionId]?.cwd,
336
+ [sessionId, source]
337
+ );
338
+ return (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, () => void 0);
339
+ }
340
+ function standaloneRows(sessions, archivedSessionIds) {
341
+ const archived = new Set(archivedSessionIds);
342
+ const rows = [];
343
+ for (const id of sessions.ids) {
344
+ const row = sessions.byId[id];
345
+ if (row === void 0) continue;
346
+ if (row.origin === "subagent") continue;
347
+ if (!isStandaloneCwd(row.cwd)) continue;
348
+ if (archived.has(row.id)) continue;
349
+ rows.push(row);
350
+ }
351
+ rows.sort((left, right) => {
352
+ if (left.blank !== right.blank) return left.blank ? -1 : 1;
353
+ if (right.updatedAt !== left.updatedAt) return right.updatedAt - left.updatedAt;
354
+ return left.id.localeCompare(right.id);
355
+ });
356
+ return rows;
357
+ }
358
+ function countStandalone(sessions, archivedSessionIds) {
359
+ const archived = new Set(archivedSessionIds);
360
+ let count = 0;
361
+ for (const id of sessions.ids) {
362
+ const row = sessions.byId[id];
363
+ if (row === void 0 || row.origin === "subagent") continue;
364
+ if (!isStandaloneCwd(row.cwd)) continue;
365
+ if (archived.has(row.id)) continue;
366
+ count += 1;
367
+ }
368
+ return count;
369
+ }
370
+
371
+ // src/client/launcher.ts
372
+ var LAUNCHER_ARM_WINDOW_MS = 3e3;
373
+ var armedAt = 0;
374
+ function armStandaloneLauncher(now = Date.now()) {
375
+ armedAt = now;
376
+ }
377
+ function isStandaloneLauncherArmed(now = Date.now()) {
378
+ if (armedAt === 0) return false;
379
+ if (now - armedAt > LAUNCHER_ARM_WINDOW_MS) {
380
+ armedAt = 0;
381
+ return false;
382
+ }
383
+ return true;
384
+ }
385
+ function clearStandaloneLauncherArm() {
386
+ armedAt = 0;
387
+ }
388
+ var StandaloneLaunchCoordinator = class {
389
+ constructor(deps) {
390
+ this.deps = deps;
391
+ }
392
+ deps;
393
+ lastLauncherSessionId;
394
+ inFlight;
395
+ /**
396
+ * Launch one conversation: reuse the last blank bridged session when it is
397
+ * still live, otherwise create a new one.
398
+ * @returns the session to open and whether it was reused.
399
+ */
400
+ launch() {
401
+ if (this.inFlight !== void 0) return this.inFlight;
402
+ const task = this.run();
403
+ this.inFlight = task;
404
+ void task.finally(() => {
405
+ if (this.inFlight === task) this.inFlight = void 0;
406
+ }).catch(() => {
407
+ });
408
+ return task;
409
+ }
410
+ async run() {
411
+ const reusable = this.reusableSession();
412
+ if (reusable !== void 0) {
413
+ return { sessionId: reusable.id, reused: true };
414
+ }
415
+ const sessionId = await this.deps.create();
416
+ this.lastLauncherSessionId = sessionId;
417
+ return { sessionId, reused: false };
418
+ }
419
+ reusableSession() {
420
+ const sessionId = this.lastLauncherSessionId;
421
+ if (sessionId === void 0) return void 0;
422
+ const row = this.deps.sessions().byId[sessionId];
423
+ if (row === void 0 || row.origin === "subagent" || !isStandaloneCwd(row.cwd) || !row.blank || this.deps.archivedSessionIds().includes(row.id)) {
424
+ this.lastLauncherSessionId = void 0;
425
+ return void 0;
426
+ }
427
+ return row;
428
+ }
429
+ };
430
+
431
+ // src/client/format.ts
432
+ function relativeTimeLabel(t, timestamp, now = Date.now()) {
433
+ const raw = relativeTime(timestamp, now);
434
+ if (raw === "") return "";
435
+ if (raw === "now") return t("time.now");
436
+ const match = /^(\d+)([mhd])$/.exec(raw);
437
+ if (match === null) return raw;
438
+ const n = Number(match[1]);
439
+ if (match[2] === "m") return t("time.minutes", { n });
440
+ if (match[2] === "h") return t("time.hours", { n });
441
+ return t("time.days", { n });
442
+ }
443
+
444
+ // src/client/locales.ts
445
+ var NS = "standaloneSession";
446
+ var zh = {
447
+ "panel.label": "\u72EC\u7ACB\u4F1A\u8BDD",
448
+ "panel.title": "\u72EC\u7ACB\u4F1A\u8BDD",
449
+ "panel.description": "\u4E0D\u96B6\u5C5E\u4E8E\u4EFB\u4F55\u5DE5\u4F5C\u533A\uFF0C\u4F7F\u7528 DSH \u539F\u751F\u8F93\u5165\u533A\u3002",
450
+ "panel.new": "\u65B0\u5EFA\u72EC\u7ACB\u4F1A\u8BDD",
451
+ "panel.empty": "\u8FD8\u6CA1\u6709\u72EC\u7ACB\u4F1A\u8BDD",
452
+ "panel.emptyHint": "\u70B9\u51FB\u4E0B\u65B9\u6309\u94AE\uFF0C\u521B\u5EFA\u4E00\u4E2A\u7A7A\u767D\u4F1A\u8BDD\u5F00\u59CB\u3002",
453
+ "panel.badge": "\u72EC\u7ACB",
454
+ "panel.open": "\u6253\u5F00",
455
+ "panel.rename": "\u91CD\u547D\u540D",
456
+ "panel.delete": "\u5220\u9664",
457
+ "panel.untitled": "\u672A\u547D\u540D\u4F1A\u8BDD",
458
+ "panel.blank": "\u7A7A\u767D",
459
+ "panel.running": "\u8FD0\u884C\u4E2D",
460
+ "panel.cwd": "\u5DE5\u4F5C\u76EE\u5F55",
461
+ "panel.count": "{n} \u4E2A\u4F1A\u8BDD",
462
+ "panel.deleteTitle": "\u5220\u9664\u72EC\u7ACB\u4F1A\u8BDD\uFF1F",
463
+ "panel.deleteBody": "\u4F1A\u8BDD\u5C06\u4ECE\u5217\u8868\u548C\u539F\u751F\u4F1A\u8BDD\u89C6\u56FE\u4E2D\u79FB\u9664\uFF08DSH \u4EE5\u5F52\u6863\u5B9E\u73B0\u5220\u9664\uFF09\uFF0C\u4F46\u4ECD\u53EF\u5728\u5F52\u6863\u89C6\u56FE\u4E2D\u6062\u590D\u3002\u7A7A\u767D\u4F1A\u8BDD\u7684\u4E34\u65F6\u76EE\u5F55\u4F1A\u88AB\u6E05\u7406\uFF1B\u5DF2\u7ECF\u4EA7\u751F\u8FC7\u6D88\u606F\u7684\u4F1A\u8BDD\u4F1A\u4FDD\u7559\u5176\u76EE\u5F55\u3002",
464
+ "panel.deleteConfirm": "\u5220\u9664",
465
+ "panel.deleteKeepDir": "\u8BE5\u4F1A\u8BDD\u5DF2\u6709\u6D88\u606F\uFF0C\u76EE\u5F55\u4F1A\u88AB\u4FDD\u7559\u3002",
466
+ "panel.cancel": "\u53D6\u6D88",
467
+ "panel.renameTitle": "\u91CD\u547D\u540D\u72EC\u7ACB\u4F1A\u8BDD",
468
+ "panel.renameLabel": "\u540D\u79F0",
469
+ "panel.more": "\u66F4\u591A\u64CD\u4F5C",
470
+ "panel.save": "\u4FDD\u5B58",
471
+ "panel.titleRequired": "\u6807\u9898\u4E0D\u80FD\u4E3A\u7A7A",
472
+ "panel.actionFailed": "\u64CD\u4F5C\u5931\u8D25\uFF1A{message}",
473
+ "panel.retry": "\u91CD\u8BD5",
474
+ "panel.archivedHint": "\u5DF2\u5220\u9664\u7684\u4F1A\u8BDD\u5728 DSH \u7684\u5F52\u6863\u89C6\u56FE\u4E2D\u4ECD\u53EF\u6062\u590D\u3002",
475
+ "panel.loading": "\u6B63\u5728\u52A0\u8F7D\u4F1A\u8BDD\u2026",
476
+ "entry.label": "\u65B0\u5EFA\u72EC\u7ACB\u4F1A\u8BDD",
477
+ "launcher.creating": "\u6B63\u5728\u521B\u5EFA\u72EC\u7ACB\u4F1A\u8BDD\u2026",
478
+ "launcher.failedTitle": "\u65E0\u6CD5\u521B\u5EFA\u72EC\u7ACB\u4F1A\u8BDD",
479
+ "launcher.retry": "\u91CD\u8BD5",
480
+ "launcher.manage": "\u7BA1\u7406\u5DF2\u6709\u4F1A\u8BDD",
481
+ "launcher.hint": "\u70B9\u51FB\u5DE6\u4FA7\u201C\u65B0\u5EFA\u72EC\u7ACB\u4F1A\u8BDD\u201D\u5373\u53EF\u76F4\u63A5\u5F00\u59CB\u3002",
482
+ "command.label": "\u72EC\u7ACB\u4F1A\u8BDD\uFF1A\u65B0\u5EFA",
483
+ "command.description": "\u521B\u5EFA\u4E00\u4E2A\u4E0D\u5C5E\u4E8E\u4EFB\u4F55\u5DE5\u4F5C\u533A\u7684\u72EC\u7ACB\u4F1A\u8BDD\u3002",
484
+ "command.manageLabel": "\u72EC\u7ACB\u4F1A\u8BDD\uFF1A\u7BA1\u7406",
485
+ "command.manageDescription": "\u6253\u5F00\u72EC\u7ACB\u4F1A\u8BDD\u5217\u8868\uFF0C\u652F\u6301\u6253\u5F00\u3001\u91CD\u547D\u540D\u548C\u5220\u9664\u3002",
486
+ "footer.manage": "\u7BA1\u7406\u72EC\u7ACB\u4F1A\u8BDD",
487
+ "footer.manageCount": "\u7BA1\u7406\u72EC\u7ACB\u4F1A\u8BDD\uFF08{n}\uFF09",
488
+ "footer.manageTooltip": "\u6253\u5F00\u72EC\u7ACB\u4F1A\u8BDD\u5217\u8868\uFF0C\u652F\u6301\u6253\u5F00\u3001\u91CD\u547D\u540D\u548C\u5220\u9664\u3002",
489
+ "time.now": "\u521A\u521A",
490
+ "time.minutes": "{n} \u5206\u949F\u524D",
491
+ "time.hours": "{n} \u5C0F\u65F6\u524D",
492
+ "time.days": "{n} \u5929\u524D",
493
+ "toast.manageFailed": "\u65E0\u6CD5\u6253\u5F00\u7BA1\u7406\u9762\u677F\uFF1A{message}",
494
+ "hero.badge": "\u72EC\u7ACB\u4F1A\u8BDD \xB7 \u65E0\u5DE5\u4F5C\u533A",
495
+ "hero.hint": "\u4E0D\u96B6\u5C5E\u4E8E\u4EFB\u4F55\u5DE5\u4F5C\u533A",
496
+ "row.mark": "\u72EC",
497
+ "row.hover": "\u72EC\u7ACB\u4F1A\u8BDD \xB7 \u4E0D\u5C5E\u4E8E\u4EFB\u4F55\u5DE5\u4F5C\u533A",
498
+ "composer.placeholder": "\u63CF\u8FF0\u4F60\u60F3\u6784\u5EFA\u7684\u5185\u5BB9\u2026",
499
+ "toast.created": "\u5DF2\u521B\u5EFA\u72EC\u7ACB\u4F1A\u8BDD",
500
+ "toast.createFailed": "\u65E0\u6CD5\u521B\u5EFA\u72EC\u7ACB\u4F1A\u8BDD\uFF1A{message}",
501
+ "toast.deleteFailed": "\u65E0\u6CD5\u5220\u9664\u72EC\u7ACB\u4F1A\u8BDD\uFF1A{message}",
502
+ "toast.deleted": "\u5DF2\u5220\u9664\u72EC\u7ACB\u4F1A\u8BDD",
503
+ "toast.renamed": "\u5DF2\u91CD\u547D\u540D\u72EC\u7ACB\u4F1A\u8BDD",
504
+ "toast.renameFailed": "\u65E0\u6CD5\u91CD\u547D\u540D\u72EC\u7ACB\u4F1A\u8BDD\uFF1A{message}",
505
+ "error.noHome": "\u65E0\u6CD5\u786E\u5B9A\u5BBF\u4E3B\u4E3B\u76EE\u5F55",
506
+ "error.noSession": "\u6CA1\u6709\u53EF\u7528\u7684\u4F1A\u8BDD"
507
+ };
508
+ var en = {
509
+ "panel.label": "Standalone",
510
+ "panel.title": "Standalone Sessions",
511
+ "panel.description": "Workspace-free sessions using the native DSH composer.",
512
+ "panel.new": "New Standalone Session",
513
+ "panel.empty": "No standalone sessions yet",
514
+ "panel.emptyHint": "Use the button below to create a blank session.",
515
+ "panel.badge": "Standalone",
516
+ "panel.open": "Open",
517
+ "panel.rename": "Rename",
518
+ "panel.delete": "Delete",
519
+ "panel.untitled": "Untitled session",
520
+ "panel.blank": "Blank",
521
+ "panel.running": "Running",
522
+ "panel.cwd": "Working directory",
523
+ "panel.count": "{n} session(s)",
524
+ "panel.deleteTitle": "Delete standalone session?",
525
+ "panel.deleteBody": "The session leaves the list and the native session view (DSH implements deletion as archive), and remains recoverable from the archived view. The directory of an unused session is removed; a session that already carried messages keeps its directory.",
526
+ "panel.deleteConfirm": "Delete",
527
+ "panel.deleteKeepDir": "This session already carried messages; its directory is retained.",
528
+ "panel.cancel": "Cancel",
529
+ "panel.renameTitle": "Rename standalone session",
530
+ "panel.renameLabel": "Name",
531
+ "panel.more": "More actions",
532
+ "panel.save": "Save",
533
+ "panel.titleRequired": "Title cannot be empty",
534
+ "panel.actionFailed": "Operation failed: {message}",
535
+ "panel.retry": "Retry",
536
+ "panel.archivedHint": "Deleted sessions remain recoverable from the native archived view.",
537
+ "panel.loading": "Loading sessions\u2026",
538
+ "entry.label": "New Standalone Session",
539
+ "launcher.creating": "Creating a standalone session\u2026",
540
+ "launcher.failedTitle": "Could not create a standalone session",
541
+ "launcher.retry": "Retry",
542
+ "launcher.manage": "Manage sessions",
543
+ "launcher.hint": "Choose New Standalone Session in the sidebar to start.",
544
+ "command.label": "Standalone Session: New",
545
+ "command.description": "Create a standalone session that belongs to no workspace.",
546
+ "command.manageLabel": "Standalone Session: Manage",
547
+ "command.manageDescription": "Open the standalone session list to open, rename or delete sessions.",
548
+ "footer.manage": "Manage standalone sessions",
549
+ "footer.manageCount": "Manage standalone sessions ({n})",
550
+ "footer.manageTooltip": "Open the standalone session list to open, rename or delete sessions.",
551
+ "time.now": "Just now",
552
+ "time.minutes": "{n} min ago",
553
+ "time.hours": "{n} hr ago",
554
+ "time.days": "{n} days ago",
555
+ "toast.manageFailed": "Could not open the manager: {message}",
556
+ "hero.badge": "Standalone \xB7 No workspace",
557
+ "hero.hint": "Not attached to any workspace",
558
+ "row.mark": "S",
559
+ "row.hover": "Standalone session \xB7 not attached to any workspace",
560
+ "composer.placeholder": "Describe what you want to build\u2026",
561
+ "toast.created": "Standalone session created",
562
+ "toast.createFailed": "Could not create the standalone session: {message}",
563
+ "toast.deleteFailed": "Could not delete the standalone session: {message}",
564
+ "toast.deleted": "Standalone session deleted",
565
+ "toast.renamed": "Standalone session renamed",
566
+ "toast.renameFailed": "Could not rename the standalone session: {message}",
567
+ "error.noHome": "Cannot determine the host home directory",
568
+ "error.noSession": "No session is available"
569
+ };
570
+
571
+ // src/client/components.tsx
572
+ var import_jsx_runtime = require("react/jsx-runtime");
573
+ function StandaloneIcon({
574
+ size = 16,
575
+ active = false
576
+ }) {
577
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
578
+ "svg",
579
+ {
580
+ width: size,
581
+ height: size,
582
+ viewBox: "0 0 16 16",
583
+ fill: "none",
584
+ "aria-hidden": "true",
585
+ focusable: "false",
586
+ style: { color: active ? "var(--dsw-alias-state-business-primary, #2563eb)" : "currentColor" },
587
+ children: [
588
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
589
+ "path",
590
+ {
591
+ d: "M2.5 3.25c0-.69.56-1.25 1.25-1.25h8.5c.69 0 1.25.56 1.25 1.25v6.5c0 .69-.56 1.25-1.25 1.25H7.4L4.6 13.2a.55.55 0 0 1-.9-.43V11H3.75c-.69 0-1.25-.56-1.25-1.25v-6.5Z",
592
+ stroke: "currentColor",
593
+ strokeWidth: "1.2",
594
+ strokeLinejoin: "round"
595
+ }
596
+ ),
597
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
598
+ "path",
599
+ {
600
+ d: "M8 3.4l.55 1.5 1.5.55-1.5.55L8 7.5l-.55-1.5-1.5-.55 1.5-.55L8 3.4Z",
601
+ fill: "currentColor"
602
+ }
603
+ )
604
+ ]
605
+ }
606
+ );
607
+ }
608
+ function StandaloneComposeIcon({
609
+ size = 16,
610
+ active = false
611
+ }) {
612
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
613
+ "svg",
614
+ {
615
+ width: size,
616
+ height: size,
617
+ viewBox: "0 0 16 16",
618
+ fill: "none",
619
+ "aria-hidden": "true",
620
+ focusable: "false",
621
+ style: { color: active ? "var(--dsw-alias-state-business-primary, #2563eb)" : "currentColor" },
622
+ children: [
623
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
624
+ "path",
625
+ {
626
+ d: "M2.5 3.25c0-.69.56-1.25 1.25-1.25h8.5c.69 0 1.25.56 1.25 1.25v6.5c0 .69-.56 1.25-1.25 1.25H7.4L4.6 13.2a.55.55 0 0 1-.9-.43V11H3.75c-.69 0-1.25-.56-1.25-1.25v-6.5Z",
627
+ stroke: "currentColor",
628
+ strokeWidth: "1.2",
629
+ strokeLinejoin: "round"
630
+ }
631
+ ),
632
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 4.3v3.4M6.3 6h3.4", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" })
633
+ ]
634
+ }
635
+ );
636
+ }
637
+ function StandaloneListIcon({ size = 16 }) {
638
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", focusable: "false", children: [
639
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.5 4h8M5.5 8h8M5.5 12h8", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }),
640
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "3", cy: "4", r: "0.8", fill: "currentColor" }),
641
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "3", cy: "8", r: "0.8", fill: "currentColor" }),
642
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "3", cy: "12", r: "0.8", fill: "currentColor" })
643
+ ] });
644
+ }
645
+ function useStandaloneRows(ctx) {
646
+ const sessions = useSessionList(ctx);
647
+ const workspaces = useWorkspaceSnapshot(ctx);
648
+ const rows = (0, import_react3.useMemo)(
649
+ () => standaloneRows(sessions, workspaces.archivedSessionIds),
650
+ [sessions, workspaces.archivedSessionIds]
651
+ );
652
+ return { rows, loading: sessions.phase === "pending" };
653
+ }
654
+ function createLauncherEntryIcon() {
655
+ return function StandaloneLauncherIcon({
656
+ size = 18,
657
+ active = false
658
+ }) {
659
+ const ref = (0, import_react3.useRef)(null);
660
+ (0, import_react3.useEffect)(() => {
661
+ const button = ref.current?.closest("button");
662
+ if (button == null) return;
663
+ const arm = () => armStandaloneLauncher();
664
+ button.addEventListener("click", arm, true);
665
+ return () => button.removeEventListener("click", arm, true);
666
+ }, []);
667
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ref, className: "ss-panelIcon", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StandaloneComposeIcon, { size, active }) });
668
+ };
669
+ }
670
+ function createManageFooterAction(ctx) {
671
+ const fallbackT = ctx.locale.bind(NS);
672
+ return function StandaloneManageFooterAction({
673
+ wide = true,
674
+ t
675
+ }) {
676
+ const translate = t ?? fallbackT;
677
+ const sessions = useSessionList(ctx);
678
+ const workspaces = useWorkspaceSnapshot(ctx);
679
+ const count = countStandalone(sessions, workspaces.archivedSessionIds);
680
+ const label = count > 0 ? translate("footer.manageCount", { n: count }) : translate("footer.manage");
681
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
682
+ "button",
683
+ {
684
+ type: "button",
685
+ className: `ss-footerAction${wide ? "" : " ss-footerRail"}`,
686
+ "aria-label": label,
687
+ title: translate("footer.manageTooltip"),
688
+ onClick: () => openManagementPanel(ctx, translate),
689
+ children: [
690
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-panelIcon", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StandaloneListIcon, { size: wide ? 15 : 18 }) }),
691
+ wide ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-footerLabel", children: label }) : null
692
+ ]
693
+ }
694
+ );
695
+ };
696
+ }
697
+ function createStandaloneLauncher(ctx) {
698
+ const fallbackT = ctx.locale.bind(NS);
699
+ const coordinator = new StandaloneLaunchCoordinator({
700
+ sessions: () => ctx.sessions.list.getSnapshot(),
701
+ archivedSessionIds: () => ctx.workspaces.list.getSnapshot().archivedSessionIds,
702
+ create: () => createStandaloneSessionId(ctx, fallbackT)
703
+ });
704
+ return function StandaloneLauncher(props) {
705
+ const t = props.t ?? fallbackT;
706
+ const [phase, setPhase] = (0, import_react3.useState)(
707
+ () => isStandaloneLauncherArmed() ? "creating" : "idle"
708
+ );
709
+ const [error, setError] = (0, import_react3.useState)(null);
710
+ const mounted = (0, import_react3.useRef)(true);
711
+ (0, import_react3.useEffect)(() => {
712
+ mounted.current = true;
713
+ return () => {
714
+ mounted.current = false;
715
+ };
716
+ }, []);
717
+ const run = async () => {
718
+ setPhase("creating");
719
+ setError(null);
720
+ try {
721
+ const result = await coordinator.launch();
722
+ if (!mounted.current) return;
723
+ ctx.uiWorkspace.openSession(result.sessionId);
724
+ setPhase("idle");
725
+ } catch (reason) {
726
+ if (!mounted.current) return;
727
+ setError(messageOf(reason));
728
+ setPhase("error");
729
+ } finally {
730
+ clearStandaloneLauncherArm();
731
+ }
732
+ };
733
+ (0, import_react3.useEffect)(() => {
734
+ if (!isStandaloneLauncherArmed()) {
735
+ setPhase((current2) => current2 === "creating" ? "idle" : current2);
736
+ return;
737
+ }
738
+ void run();
739
+ }, []);
740
+ if (phase === "creating") {
741
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-launcher", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-launcherCard", "aria-live": "polite", children: [
742
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-launcherSpinner", "aria-hidden": "true" }),
743
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-launcherTitle", children: t("launcher.creating") })
744
+ ] }) });
745
+ }
746
+ if (phase === "error") {
747
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-launcher", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-launcherCard", role: "alert", children: [
748
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-launcherTitle", children: t("launcher.failedTitle") }),
749
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-launcherHint", children: t("panel.actionFailed", { message: error ?? "" }) }),
750
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-launcherActions", children: [
751
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
752
+ "button",
753
+ {
754
+ type: "button",
755
+ className: "ss-button ss-buttonPrimary",
756
+ onClick: () => {
757
+ void run();
758
+ },
759
+ children: t("launcher.retry")
760
+ }
761
+ ),
762
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
763
+ "button",
764
+ {
765
+ type: "button",
766
+ className: "ss-button ss-buttonQuiet",
767
+ onClick: () => openManagementPanel(ctx, t),
768
+ children: t("launcher.manage")
769
+ }
770
+ )
771
+ ] })
772
+ ] }) });
773
+ }
774
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-launcher", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-launcherCard", children: [
775
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-launcherTitle", children: t("entry.label") }),
776
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-launcherHint", children: t("launcher.hint") }),
777
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-launcherActions", children: [
778
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
779
+ "button",
780
+ {
781
+ type: "button",
782
+ className: "ss-button ss-buttonPrimary",
783
+ onClick: () => {
784
+ void run();
785
+ },
786
+ children: t("entry.label")
787
+ }
788
+ ),
789
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
790
+ "button",
791
+ {
792
+ type: "button",
793
+ className: "ss-button ss-buttonQuiet",
794
+ onClick: () => openManagementPanel(ctx, t),
795
+ children: t("launcher.manage")
796
+ }
797
+ )
798
+ ] })
799
+ ] }) });
800
+ };
801
+ }
802
+ function createHeroBadge(ctx) {
803
+ const fallbackT = ctx.locale.bind(NS);
804
+ return function StandaloneHeroBadge({
805
+ sessionId,
806
+ t
807
+ }) {
808
+ const translate = t ?? fallbackT;
809
+ const cwd = useSessionCwd2(ctx, sessionId);
810
+ if (!isStandaloneCwd(cwd)) return null;
811
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-badge ss-heroBadge", title: translate("hero.hint"), children: translate("hero.badge") });
812
+ };
813
+ }
814
+ function createRowLeading(ctx) {
815
+ const fallbackT = ctx.locale.bind(NS);
816
+ return function StandaloneRowLeading({
817
+ sessionId,
818
+ t
819
+ }) {
820
+ const translate = t ?? fallbackT;
821
+ const cwd = useSessionCwd2(ctx, sessionId);
822
+ if (!isStandaloneCwd(cwd)) return null;
823
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-rowMark", title: translate("row.hover"), "aria-label": translate("row.hover"), children: translate("row.mark") });
824
+ };
825
+ }
826
+ function createRowHover(ctx) {
827
+ const fallbackT = ctx.locale.bind(NS);
828
+ return function StandaloneRowHover({
829
+ sessionId,
830
+ t
831
+ }) {
832
+ const translate = t ?? fallbackT;
833
+ const cwd = useSessionCwd2(ctx, sessionId);
834
+ if (!isStandaloneCwd(cwd)) return null;
835
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-rowHover", children: [
836
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-badge", children: translate("panel.badge") }),
837
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: translate("row.hover") })
838
+ ] });
839
+ };
840
+ }
841
+ function StandaloneToast() {
842
+ const notice = (0, import_react3.useSyncExternalStore)(subscribeNotice, getNoticeSnapshot, getNoticeSnapshot);
843
+ if (notice === null) return null;
844
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
845
+ "div",
846
+ {
847
+ className: `ss-toast${notice.kind === "error" ? " ss-toastError" : ""}`,
848
+ role: notice.kind === "error" ? "alert" : "status",
849
+ onClick: dismissNotice,
850
+ children: notice.text
851
+ }
852
+ );
853
+ }
854
+ function DialogFrame({ title, children, footer, onCancel, busy = false }) {
855
+ const ref = (0, import_react3.useRef)(null);
856
+ const onCancelRef = (0, import_react3.useRef)(onCancel);
857
+ const busyRef = (0, import_react3.useRef)(busy);
858
+ onCancelRef.current = onCancel;
859
+ busyRef.current = busy;
860
+ (0, import_react3.useEffect)(() => {
861
+ const dialog = ref.current;
862
+ if (dialog === null) return;
863
+ const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
864
+ const focusables = () => Array.from(
865
+ dialog.querySelectorAll(
866
+ 'button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])'
867
+ )
868
+ );
869
+ focusables()[0]?.focus();
870
+ const onKey = (event) => {
871
+ if (event.key === "Escape" && !busyRef.current) {
872
+ event.preventDefault();
873
+ onCancelRef.current();
874
+ return;
875
+ }
876
+ if (event.key !== "Tab") return;
877
+ const items = focusables();
878
+ if (items.length === 0) return;
879
+ const first = items[0];
880
+ const last = items[items.length - 1];
881
+ if (event.shiftKey && document.activeElement === first) {
882
+ event.preventDefault();
883
+ last.focus();
884
+ } else if (!event.shiftKey && document.activeElement === last) {
885
+ event.preventDefault();
886
+ first.focus();
887
+ }
888
+ };
889
+ document.addEventListener("keydown", onKey);
890
+ return () => {
891
+ document.removeEventListener("keydown", onKey);
892
+ previous?.focus();
893
+ };
894
+ }, []);
895
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
896
+ "div",
897
+ {
898
+ className: "ss-dialogBackdrop",
899
+ onMouseDown: (event) => {
900
+ if (event.target === event.currentTarget && !busy) onCancel();
901
+ },
902
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-dialog", role: "dialog", "aria-modal": "true", "aria-label": title, ref, children: [
903
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "ss-dialogTitle", children: title }),
904
+ children,
905
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-dialogFooter", children: footer })
906
+ ] })
907
+ }
908
+ );
909
+ }
910
+ function RenameDialog({
911
+ row,
912
+ t,
913
+ onCancel,
914
+ onSave
915
+ }) {
916
+ const [value, setValue] = (0, import_react3.useState)(row.title?.trim() ?? "");
917
+ const [busy, setBusy] = (0, import_react3.useState)(false);
918
+ const [error, setError] = (0, import_react3.useState)(null);
919
+ const inputRef = (0, import_react3.useRef)(null);
920
+ (0, import_react3.useEffect)(() => {
921
+ inputRef.current?.focus();
922
+ inputRef.current?.select();
923
+ }, []);
924
+ const submit = async () => {
925
+ const title = value.trim();
926
+ if (title === "") {
927
+ setError(t("panel.titleRequired"));
928
+ return;
929
+ }
930
+ setBusy(true);
931
+ setError(null);
932
+ try {
933
+ await onSave(title);
934
+ } catch (reason) {
935
+ setError(messageOf(reason));
936
+ } finally {
937
+ setBusy(false);
938
+ }
939
+ };
940
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
941
+ DialogFrame,
942
+ {
943
+ title: t("panel.renameTitle"),
944
+ busy,
945
+ onCancel,
946
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
947
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "ss-button ss-buttonQuiet", disabled: busy, onClick: onCancel, children: t("panel.cancel") }),
948
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
949
+ "button",
950
+ {
951
+ type: "button",
952
+ className: "ss-button ss-buttonPrimary",
953
+ disabled: busy,
954
+ onClick: () => {
955
+ void submit();
956
+ },
957
+ children: t("panel.save")
958
+ }
959
+ )
960
+ ] }),
961
+ children: [
962
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "ss-fieldLabel", htmlFor: "ss-rename-input", children: t("panel.renameLabel") }),
963
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
964
+ "input",
965
+ {
966
+ id: "ss-rename-input",
967
+ ref: inputRef,
968
+ className: "ss-input",
969
+ value,
970
+ maxLength: 200,
971
+ "aria-invalid": error !== null,
972
+ onChange: (event) => setValue(event.target.value),
973
+ onKeyDown: (event) => {
974
+ if (event.key === "Enter") {
975
+ event.preventDefault();
976
+ void submit();
977
+ }
978
+ }
979
+ }
980
+ ),
981
+ error !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-error", children: error }) : null
982
+ ]
983
+ }
984
+ );
985
+ }
986
+ function DeleteDialog({
987
+ row,
988
+ t,
989
+ onCancel,
990
+ onConfirm
991
+ }) {
992
+ const [busy, setBusy] = (0, import_react3.useState)(false);
993
+ const [error, setError] = (0, import_react3.useState)(null);
994
+ const confirm = async () => {
995
+ if (busy) return;
996
+ setBusy(true);
997
+ setError(null);
998
+ try {
999
+ await onConfirm();
1000
+ } catch (reason) {
1001
+ setError(messageOf(reason));
1002
+ setBusy(false);
1003
+ }
1004
+ };
1005
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1006
+ DialogFrame,
1007
+ {
1008
+ title: t("panel.deleteTitle"),
1009
+ busy,
1010
+ onCancel,
1011
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1012
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "ss-button ss-buttonQuiet", disabled: busy, onClick: onCancel, children: t("panel.cancel") }),
1013
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1014
+ "button",
1015
+ {
1016
+ type: "button",
1017
+ className: "ss-button ss-buttonDanger",
1018
+ disabled: busy,
1019
+ onClick: () => {
1020
+ void confirm();
1021
+ },
1022
+ children: t("panel.deleteConfirm")
1023
+ }
1024
+ )
1025
+ ] }),
1026
+ children: [
1027
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-dialogBody", children: t("panel.deleteBody") }),
1028
+ !row.blank ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-dialogBody", children: t("panel.deleteKeepDir") }) : null,
1029
+ error !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-error", children: error }) : null
1030
+ ]
1031
+ }
1032
+ );
1033
+ }
1034
+ function createStandalonePanel(ctx) {
1035
+ const fallbackT = ctx.locale.bind(NS);
1036
+ return function StandalonePanel(props) {
1037
+ const t = props.t ?? fallbackT;
1038
+ const { rows, loading } = useStandaloneRows(ctx);
1039
+ const [busy, setBusy] = (0, import_react3.useState)(null);
1040
+ const [error, setError] = (0, import_react3.useState)(null);
1041
+ const [renameTarget, setRenameTarget] = (0, import_react3.useState)(null);
1042
+ const [deleteTarget, setDeleteTarget] = (0, import_react3.useState)(null);
1043
+ const [menuTarget, setMenuTarget] = (0, import_react3.useState)(null);
1044
+ const menuRef = (0, import_react3.useRef)(null);
1045
+ const menuButtonRef = (0, import_react3.useRef)(null);
1046
+ (0, import_react3.useEffect)(() => {
1047
+ if (menuTarget === null) return;
1048
+ menuRef.current?.querySelector("button")?.focus();
1049
+ const onPointerDown = (event) => {
1050
+ if (menuRef.current?.contains(event.target)) return;
1051
+ setMenuTarget(null);
1052
+ };
1053
+ const onKeyDown = (event) => {
1054
+ if (event.key === "Escape") {
1055
+ setMenuTarget(null);
1056
+ menuButtonRef.current?.focus();
1057
+ }
1058
+ };
1059
+ document.addEventListener("pointerdown", onPointerDown);
1060
+ document.addEventListener("keydown", onKeyDown);
1061
+ return () => {
1062
+ document.removeEventListener("pointerdown", onPointerDown);
1063
+ document.removeEventListener("keydown", onKeyDown);
1064
+ };
1065
+ }, [menuTarget]);
1066
+ const create = async () => {
1067
+ if (busy !== null) return;
1068
+ setBusy("new");
1069
+ setError(null);
1070
+ try {
1071
+ await startStandalone(ctx, t);
1072
+ } catch (reason) {
1073
+ setError(t("panel.actionFailed", { message: messageOf(reason) }));
1074
+ } finally {
1075
+ setBusy(null);
1076
+ }
1077
+ };
1078
+ const open = (row) => {
1079
+ if (busy !== null) return;
1080
+ setMenuTarget(null);
1081
+ try {
1082
+ ctx.uiWorkspace.openSession(row.id);
1083
+ } catch (reason) {
1084
+ setError(t("panel.actionFailed", { message: messageOf(reason) }));
1085
+ }
1086
+ };
1087
+ const rename = async (row, title) => {
1088
+ setBusy(row.id);
1089
+ setError(null);
1090
+ try {
1091
+ await renameStandalone(ctx, row.id, title);
1092
+ setRenameTarget(null);
1093
+ } catch (reason) {
1094
+ throw new Error(messageOf(reason));
1095
+ } finally {
1096
+ setBusy(null);
1097
+ }
1098
+ };
1099
+ const remove = async (row) => {
1100
+ setBusy(row.id);
1101
+ setError(null);
1102
+ try {
1103
+ await archiveStandalone(ctx, row.id);
1104
+ openManagementPanel(ctx, t);
1105
+ setDeleteTarget(null);
1106
+ } catch (reason) {
1107
+ throw new Error(messageOf(reason));
1108
+ } finally {
1109
+ setBusy(null);
1110
+ }
1111
+ };
1112
+ const count = t("panel.count", { n: rows.length });
1113
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "ss-panel", "aria-label": t("panel.title"), children: [
1114
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-shell", children: [
1115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("header", { className: "ss-header", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-titleRow", children: [
1117
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "ss-title", children: t("panel.title") }),
1118
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-count ss-countInline", children: count })
1119
+ ] }),
1120
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-description", children: t("panel.description") })
1121
+ ] }) }),
1122
+ error !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-error", role: "alert", children: error }) : null,
1123
+ loading && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-empty", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-emptyHint", children: t("panel.loading") }) }) : rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-empty", children: [
1124
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-emptyTitle", children: t("panel.empty") }),
1125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-emptyHint", children: t("panel.emptyHint") }),
1126
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-emptyAction", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1127
+ "button",
1128
+ {
1129
+ type: "button",
1130
+ className: "ss-button ss-buttonPrimary",
1131
+ disabled: busy !== null,
1132
+ onClick: () => {
1133
+ void create();
1134
+ },
1135
+ children: [
1136
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StandaloneComposeIcon, { size: 14 }),
1137
+ t("panel.new")
1138
+ ]
1139
+ }
1140
+ ) })
1141
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "ss-list", children: rows.map((row) => {
1142
+ const title = row.title?.trim() || t("panel.untitled");
1143
+ const updated = relativeTimeLabel(t, row.updatedAt);
1144
+ const openMenu = menuTarget === row.id;
1145
+ const metaItems = [];
1146
+ if (updated !== "") metaItems.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: updated }, "time"));
1147
+ if (row.running) {
1148
+ metaItems.push(
1149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-stateRunning", children: t("panel.running") }, "running")
1150
+ );
1151
+ }
1152
+ if (row.blank) {
1153
+ metaItems.push(
1154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "ss-status", children: [
1155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-statusDot", "aria-hidden": "true" }),
1156
+ t("panel.blank")
1157
+ ] }, "blank")
1158
+ );
1159
+ }
1160
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1161
+ "li",
1162
+ {
1163
+ className: `ss-row${openMenu ? " ss-rowMenuOpen" : ""}`,
1164
+ role: "button",
1165
+ tabIndex: 0,
1166
+ "aria-disabled": busy !== null,
1167
+ onClick: () => open(row),
1168
+ onKeyDown: (event) => {
1169
+ if (event.target !== event.currentTarget) return;
1170
+ if (event.key === "Enter" || event.key === " ") {
1171
+ event.preventDefault();
1172
+ open(row);
1173
+ } else if (event.key === "ArrowDown") {
1174
+ event.preventDefault();
1175
+ setMenuTarget(row.id);
1176
+ }
1177
+ },
1178
+ children: [
1179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-rowMain", children: [
1180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-rowTitleRow", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-rowTitle", title, children: title }) }),
1181
+ metaItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-rowMeta", children: metaItems.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react3.Fragment, { children: [
1182
+ index > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-dotSep", children: "\xB7" }) : null,
1183
+ item
1184
+ ] }, index)) }) : null
1185
+ ] }),
1186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-rowActions", children: [
1187
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1188
+ "button",
1189
+ {
1190
+ type: "button",
1191
+ className: "ss-rowMenuButton",
1192
+ "aria-label": t("panel.more"),
1193
+ "aria-haspopup": "menu",
1194
+ "aria-expanded": openMenu,
1195
+ disabled: busy !== null,
1196
+ ref: openMenu ? menuButtonRef : void 0,
1197
+ onClick: (event) => {
1198
+ event.stopPropagation();
1199
+ setMenuTarget(openMenu ? null : row.id);
1200
+ },
1201
+ onKeyDown: (event) => event.stopPropagation(),
1202
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\u22EF" })
1203
+ }
1204
+ ),
1205
+ openMenu ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1206
+ "div",
1207
+ {
1208
+ className: "ss-rowMenu",
1209
+ role: "menu",
1210
+ ref: menuRef,
1211
+ onClick: (event) => event.stopPropagation(),
1212
+ children: [
1213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1214
+ "button",
1215
+ {
1216
+ type: "button",
1217
+ role: "menuitem",
1218
+ disabled: busy !== null,
1219
+ onClick: () => {
1220
+ setMenuTarget(null);
1221
+ setRenameTarget(row);
1222
+ },
1223
+ children: t("panel.rename")
1224
+ }
1225
+ ),
1226
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1227
+ "button",
1228
+ {
1229
+ type: "button",
1230
+ role: "menuitem",
1231
+ className: "ss-rowMenuDanger",
1232
+ disabled: busy !== null,
1233
+ onClick: () => {
1234
+ setMenuTarget(null);
1235
+ setDeleteTarget(row);
1236
+ },
1237
+ children: t("panel.delete")
1238
+ }
1239
+ )
1240
+ ]
1241
+ }
1242
+ ) : null
1243
+ ] })
1244
+ ]
1245
+ },
1246
+ row.id
1247
+ );
1248
+ }) }),
1249
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-panelHint", children: t("panel.archivedHint") })
1250
+ ] }),
1251
+ renameTarget !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1252
+ RenameDialog,
1253
+ {
1254
+ row: renameTarget,
1255
+ t,
1256
+ onCancel: () => setRenameTarget(null),
1257
+ onSave: (title) => rename(renameTarget, title)
1258
+ }
1259
+ ) : null,
1260
+ deleteTarget !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1261
+ DeleteDialog,
1262
+ {
1263
+ row: deleteTarget,
1264
+ t,
1265
+ onCancel: () => setDeleteTarget(null),
1266
+ onConfirm: () => remove(deleteTarget)
1267
+ }
1268
+ ) : null
1269
+ ] });
1270
+ };
1271
+ }
1272
+
1273
+ // src/client/styles.ts
1274
+ var CSS = `
1275
+ .ss-panel {
1276
+ box-sizing: border-box;
1277
+ height: 100%;
1278
+ min-height: 0;
1279
+ overflow: auto;
1280
+ padding: 32px clamp(20px, 5vw, 56px) 56px;
1281
+ color: var(--dsw-alias-label-primary, #1f2328);
1282
+ background: var(--dsw-specific-main-fill, transparent);
1283
+ }
1284
+ .ss-shell {
1285
+ width: 100%;
1286
+ max-width: 880px;
1287
+ margin: 0 auto;
1288
+ }
1289
+ .ss-header {
1290
+ display: flex;
1291
+ align-items: flex-start;
1292
+ gap: 16px;
1293
+ justify-content: space-between;
1294
+ flex-wrap: wrap;
1295
+ margin-bottom: 8px;
1296
+ }
1297
+ .ss-titleRow {
1298
+ display: flex;
1299
+ align-items: center;
1300
+ gap: 10px;
1301
+ min-width: 0;
1302
+ }
1303
+ .ss-title {
1304
+ font-size: 24px;
1305
+ line-height: 32px;
1306
+ font-weight: 600;
1307
+ margin: 0;
1308
+ }
1309
+ .ss-description {
1310
+ color: var(--dsw-alias-label-secondary, #59636e);
1311
+ font-size: 13px;
1312
+ line-height: 20px;
1313
+ margin: 6px 0 0;
1314
+ max-width: 560px;
1315
+ }
1316
+ .ss-count {
1317
+ color: var(--dsw-alias-label-tertiary, #818b98);
1318
+ font-size: 12px;
1319
+ line-height: 20px;
1320
+ margin-top: 6px;
1321
+ }
1322
+ .ss-countInline {
1323
+ margin-top: 0;
1324
+ align-self: center;
1325
+ }
1326
+ .ss-button {
1327
+ font: inherit;
1328
+ display: inline-flex;
1329
+ align-items: center;
1330
+ justify-content: center;
1331
+ gap: 6px;
1332
+ box-sizing: border-box;
1333
+ min-height: 32px;
1334
+ padding: 5px 12px;
1335
+ border-radius: var(--dsw-radius-md, 10px);
1336
+ border: 0.5px solid transparent;
1337
+ cursor: pointer;
1338
+ color: var(--dsw-alias-label-primary, #1f2328);
1339
+ background: var(--dsw-alias-button-elevated-fill, #f3f4f6);
1340
+ transition: background 120ms ease, border-color 120ms ease;
1341
+ }
1342
+ .ss-button:hover:not(:disabled) {
1343
+ background: var(--dsw-alias-interactive-bg-hover, #e9ebee);
1344
+ }
1345
+ .ss-button:focus-visible {
1346
+ outline: var(--dsw-focus-ring-width, 2px) solid
1347
+ var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));
1348
+ outline-offset: 1px;
1349
+ }
1350
+ .ss-button:disabled {
1351
+ cursor: default;
1352
+ opacity: 0.55;
1353
+ }
1354
+ .ss-buttonPrimary {
1355
+ color: #fff;
1356
+ background: var(--dsw-alias-state-business-primary, #2563eb);
1357
+ border-color: var(--dsw-alias-state-business-primary, #2563eb);
1358
+ }
1359
+ .ss-buttonPrimary:hover:not(:disabled) {
1360
+ background: var(--dsw-alias-state-business-primary, #2563eb);
1361
+ filter: brightness(1.05);
1362
+ }
1363
+ .ss-buttonQuiet {
1364
+ background: transparent;
1365
+ border-color: var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.22));
1366
+ }
1367
+ .ss-buttonDanger {
1368
+ color: var(--dsw-alias-state-error-primary, #d1242f);
1369
+ background: transparent;
1370
+ border-color: var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.22));
1371
+ }
1372
+ .ss-badge {
1373
+ display: inline-flex;
1374
+ align-items: center;
1375
+ gap: 4px;
1376
+ flex: none;
1377
+ border-radius: 999px;
1378
+ padding: 1px 8px 2px;
1379
+ font-size: 11px;
1380
+ line-height: 16px;
1381
+ font-weight: 600;
1382
+ letter-spacing: 0.01em;
1383
+ color: var(--dsw-alias-label-primary-bluish, #1f4f8f);
1384
+ background: var(--dsw-alias-state-business-tertiary, rgba(59, 130, 246, 0.12));
1385
+ border: 0.5px solid var(--dsw-alias-interactive-bg-hover, rgba(59, 130, 246, 0.18));
1386
+ }
1387
+ .ss-list {
1388
+ display: flex;
1389
+ flex-direction: column;
1390
+ gap: 8px;
1391
+ margin: 20px 0 0;
1392
+ padding: 0;
1393
+ list-style: none;
1394
+ }
1395
+ .ss-row {
1396
+ position: relative;
1397
+ display: flex;
1398
+ align-items: center;
1399
+ gap: 14px;
1400
+ box-sizing: border-box;
1401
+ padding: 12px 14px;
1402
+ border-radius: var(--dsw-radius-lg, 14px);
1403
+ border: 0.5px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.2));
1404
+ background: var(--dsw-alias-bg-elevated, rgba(255, 255, 255, 0.02));
1405
+ cursor: pointer;
1406
+ transition: background 120ms ease, border-color 120ms ease;
1407
+ }
1408
+ .ss-row:hover,
1409
+ .ss-row:focus-within {
1410
+ border-color: var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));
1411
+ background: var(--dsw-alias-interactive-bg-hover, #e9ebee);
1412
+ }
1413
+ .ss-row:focus-visible {
1414
+ outline: var(--dsw-focus-ring-width, 2px) solid
1415
+ var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));
1416
+ outline-offset: 1px;
1417
+ }
1418
+ .ss-row[aria-disabled="true"] {
1419
+ cursor: default;
1420
+ opacity: 0.7;
1421
+ }
1422
+ .ss-rowMain {
1423
+ min-width: 0;
1424
+ flex: 1;
1425
+ display: flex;
1426
+ flex-direction: column;
1427
+ gap: 3px;
1428
+ }
1429
+ .ss-rowTitleRow {
1430
+ display: flex;
1431
+ align-items: center;
1432
+ gap: 8px;
1433
+ min-width: 0;
1434
+ }
1435
+ .ss-rowTitle {
1436
+ font-size: 14px;
1437
+ line-height: 20px;
1438
+ font-weight: 600;
1439
+ min-width: 0;
1440
+ overflow: hidden;
1441
+ text-overflow: ellipsis;
1442
+ white-space: nowrap;
1443
+ }
1444
+ .ss-rowMeta {
1445
+ color: var(--dsw-alias-label-tertiary, #818b98);
1446
+ font-size: 12px;
1447
+ line-height: 18px;
1448
+ display: flex;
1449
+ align-items: center;
1450
+ gap: 8px;
1451
+ min-width: 0;
1452
+ flex-wrap: wrap;
1453
+ }
1454
+ .ss-dotSep {
1455
+ opacity: 0.5;
1456
+ }
1457
+ .ss-rowActions {
1458
+ position: relative;
1459
+ display: flex;
1460
+ align-items: center;
1461
+ gap: 6px;
1462
+ flex: none;
1463
+ }
1464
+ .ss-rowMenuButton {
1465
+ width: 28px;
1466
+ height: 28px;
1467
+ display: inline-flex;
1468
+ align-items: center;
1469
+ justify-content: center;
1470
+ padding: 0;
1471
+ border: 0;
1472
+ border-radius: var(--dsw-radius-md, 10px);
1473
+ background: transparent;
1474
+ color: var(--dsw-alias-label-tertiary, #818b98);
1475
+ font: inherit;
1476
+ font-size: 18px;
1477
+ line-height: 1;
1478
+ cursor: pointer;
1479
+ opacity: 0;
1480
+ transition: background 120ms ease, color 120ms ease, opacity 120ms ease;
1481
+ }
1482
+ .ss-row:hover .ss-rowMenuButton,
1483
+ .ss-row:focus-within .ss-rowMenuButton,
1484
+ .ss-rowMenuOpen .ss-rowMenuButton {
1485
+ opacity: 1;
1486
+ }
1487
+ .ss-rowMenuButton:hover:not(:disabled) {
1488
+ color: var(--dsw-alias-label-primary, #1f2328);
1489
+ background: var(--dsw-alias-interactive-bg-hover, #e9ebee);
1490
+ }
1491
+ .ss-rowMenuButton:focus-visible {
1492
+ opacity: 1;
1493
+ outline: var(--dsw-focus-ring-width, 2px) solid
1494
+ var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));
1495
+ outline-offset: 1px;
1496
+ }
1497
+ .ss-rowMenuButton:disabled {
1498
+ cursor: default;
1499
+ opacity: 0.35;
1500
+ }
1501
+ .ss-rowMenu {
1502
+ position: absolute;
1503
+ top: calc(100% + 4px);
1504
+ right: 0;
1505
+ z-index: 20;
1506
+ min-width: 136px;
1507
+ padding: 4px;
1508
+ display: flex;
1509
+ flex-direction: column;
1510
+ gap: 2px;
1511
+ border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));
1512
+ border-radius: var(--dsw-radius-md, 10px);
1513
+ background: var(--dsw-alias-bg-elevated, #fff);
1514
+ box-shadow: 0 10px 28px rgba(0, 0, 0, 0.16);
1515
+ }
1516
+ .ss-rowMenu button {
1517
+ box-sizing: border-box;
1518
+ width: 100%;
1519
+ padding: 6px 9px;
1520
+ border: 0;
1521
+ border-radius: 6px;
1522
+ background: transparent;
1523
+ color: var(--dsw-alias-label-primary, #1f2328);
1524
+ font: inherit;
1525
+ font-size: 13px;
1526
+ line-height: 20px;
1527
+ text-align: left;
1528
+ cursor: pointer;
1529
+ }
1530
+ .ss-rowMenu button:hover:not(:disabled),
1531
+ .ss-rowMenu button:focus-visible {
1532
+ background: var(--dsw-alias-interactive-bg-hover, #e9ebee);
1533
+ outline: none;
1534
+ }
1535
+ .ss-rowMenu button:disabled {
1536
+ cursor: default;
1537
+ opacity: 0.5;
1538
+ }
1539
+ .ss-rowMenuDanger {
1540
+ color: var(--dsw-alias-state-error-primary, #d1242f) !important;
1541
+ }
1542
+ .ss-status {
1543
+ display: inline-flex;
1544
+ align-items: center;
1545
+ gap: 5px;
1546
+ }
1547
+ .ss-statusDot {
1548
+ width: 5px;
1549
+ height: 5px;
1550
+ border-radius: 50%;
1551
+ background: currentColor;
1552
+ opacity: 0.55;
1553
+ }
1554
+ .ss-stateRunning {
1555
+ color: var(--dsw-alias-state-business-primary, #2563eb);
1556
+ font-weight: 600;
1557
+ }
1558
+ .ss-empty {
1559
+ margin-top: 28px;
1560
+ padding: 44px 24px;
1561
+ text-align: center;
1562
+ border: 0.5px dashed var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.3));
1563
+ border-radius: var(--dsw-radius-lg, 14px);
1564
+ }
1565
+ .ss-emptyTitle {
1566
+ font-size: 15px;
1567
+ font-weight: 600;
1568
+ line-height: 22px;
1569
+ margin: 0 0 6px;
1570
+ }
1571
+ .ss-emptyHint {
1572
+ color: var(--dsw-alias-label-secondary, #59636e);
1573
+ font-size: 13px;
1574
+ line-height: 20px;
1575
+ margin: 0 auto;
1576
+ max-width: 460px;
1577
+ }
1578
+ .ss-emptyAction {
1579
+ margin-top: 18px;
1580
+ }
1581
+ .ss-panelHint {
1582
+ margin: 18px 0 0;
1583
+ color: var(--dsw-alias-label-tertiary, #818b98);
1584
+ font-size: 12px;
1585
+ line-height: 20px;
1586
+ }
1587
+ .ss-fieldLabel {
1588
+ display: block;
1589
+ margin: 12px 0 6px;
1590
+ color: var(--dsw-alias-label-secondary, #59636e);
1591
+ font-size: 12px;
1592
+ line-height: 18px;
1593
+ }
1594
+ .ss-error {
1595
+ margin-top: 16px;
1596
+ padding: 8px 10px;
1597
+ border-radius: var(--dsw-radius-sm, 8px);
1598
+ color: var(--dsw-alias-state-error-primary, #d1242f);
1599
+ background: var(--dsw-alias-state-error-tertiary, rgba(209, 36, 47, 0.08));
1600
+ font-size: 12px;
1601
+ line-height: 18px;
1602
+ }
1603
+ .ss-dialogBackdrop {
1604
+ position: fixed;
1605
+ inset: 0;
1606
+ z-index: 80;
1607
+ display: flex;
1608
+ align-items: center;
1609
+ justify-content: center;
1610
+ background: rgba(0, 0, 0, 0.28);
1611
+ padding: 24px;
1612
+ }
1613
+ .ss-dialog {
1614
+ width: 100%;
1615
+ max-width: 440px;
1616
+ box-sizing: border-box;
1617
+ border-radius: var(--dsw-radius-lg, 14px);
1618
+ border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));
1619
+ background: var(--dsw-alias-bg-elevated, #fff);
1620
+ padding: 20px;
1621
+ box-shadow: 0 18px 48px rgba(0, 0, 0, 0.22);
1622
+ }
1623
+ .ss-dialogTitle {
1624
+ margin: 0 0 6px;
1625
+ font-size: 16px;
1626
+ line-height: 24px;
1627
+ font-weight: 600;
1628
+ }
1629
+ .ss-dialogBody {
1630
+ margin: 0 0 14px;
1631
+ color: var(--dsw-alias-label-secondary, #59636e);
1632
+ font-size: 13px;
1633
+ line-height: 20px;
1634
+ }
1635
+ .ss-input {
1636
+ box-sizing: border-box;
1637
+ width: 100%;
1638
+ height: 36px;
1639
+ border-radius: var(--dsw-radius-sm, 8px);
1640
+ border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));
1641
+ background: transparent;
1642
+ color: inherit;
1643
+ font: inherit;
1644
+ padding: 0 10px;
1645
+ }
1646
+ .ss-dialogFooter {
1647
+ display: flex;
1648
+ justify-content: flex-end;
1649
+ gap: 8px;
1650
+ margin-top: 18px;
1651
+ }
1652
+ .ss-footerAction {
1653
+ box-sizing: border-box;
1654
+ width: 100%;
1655
+ display: flex;
1656
+ align-items: center;
1657
+ gap: 8px;
1658
+ min-height: 30px;
1659
+ padding: 4px 8px;
1660
+ border: 0;
1661
+ border-radius: var(--dsw-radius-md, 10px);
1662
+ background: transparent;
1663
+ color: var(--dsw-alias-label-secondary, #59636e);
1664
+ font: inherit;
1665
+ cursor: pointer;
1666
+ text-align: left;
1667
+ }
1668
+ .ss-footerAction:hover {
1669
+ color: var(--dsw-alias-label-primary, #1f2328);
1670
+ background: var(--dsw-alias-interactive-bg-hover, #e9ebee);
1671
+ }
1672
+ .ss-footerAction:disabled {
1673
+ cursor: default;
1674
+ opacity: 0.55;
1675
+ }
1676
+ .ss-footerLabel {
1677
+ min-width: 0;
1678
+ overflow: hidden;
1679
+ text-overflow: ellipsis;
1680
+ white-space: nowrap;
1681
+ font-size: 13px;
1682
+ }
1683
+ .ss-footerRail {
1684
+ width: 36px;
1685
+ height: 36px;
1686
+ justify-content: center;
1687
+ padding: 0;
1688
+ }
1689
+ .ss-panelIcon {
1690
+ position: relative;
1691
+ display: inline-flex;
1692
+ align-items: center;
1693
+ justify-content: center;
1694
+ }
1695
+ .ss-launcher {
1696
+ box-sizing: border-box;
1697
+ min-height: 100%;
1698
+ display: flex;
1699
+ align-items: center;
1700
+ justify-content: center;
1701
+ padding: 32px;
1702
+ color: var(--dsw-alias-label-primary, #1f2328);
1703
+ }
1704
+ .ss-launcherCard {
1705
+ box-sizing: border-box;
1706
+ width: 100%;
1707
+ max-width: 430px;
1708
+ padding: 30px 28px;
1709
+ text-align: center;
1710
+ border: 0.5px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.22));
1711
+ border-radius: var(--dsw-radius-lg, 14px);
1712
+ background: var(--dsw-alias-bg-elevated, rgba(255, 255, 255, 0.02));
1713
+ }
1714
+ .ss-launcherSpinner {
1715
+ width: 22px;
1716
+ height: 22px;
1717
+ margin: 0 auto 14px;
1718
+ border-radius: 50%;
1719
+ border: 2px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));
1720
+ border-top-color: var(--dsw-alias-state-business-primary, #2563eb);
1721
+ animation: ss-spin 900ms linear infinite;
1722
+ }
1723
+ @keyframes ss-spin {
1724
+ to { transform: rotate(360deg); }
1725
+ }
1726
+ .ss-launcherTitle {
1727
+ margin: 0;
1728
+ font-size: 15px;
1729
+ line-height: 22px;
1730
+ font-weight: 600;
1731
+ }
1732
+ .ss-launcherHint {
1733
+ margin: 6px 0 0;
1734
+ color: var(--dsw-alias-label-secondary, #59636e);
1735
+ font-size: 13px;
1736
+ line-height: 20px;
1737
+ }
1738
+ .ss-launcherActions {
1739
+ display: flex;
1740
+ flex-wrap: wrap;
1741
+ justify-content: center;
1742
+ gap: 8px;
1743
+ margin-top: 18px;
1744
+ }
1745
+ .ss-heroBadge {
1746
+ margin-right: 4px;
1747
+ }
1748
+ .ss-rowMark {
1749
+ display: inline-flex;
1750
+ align-items: center;
1751
+ justify-content: center;
1752
+ width: 14px;
1753
+ height: 14px;
1754
+ border-radius: 4px;
1755
+ font-size: 10px;
1756
+ line-height: 1;
1757
+ font-weight: 700;
1758
+ color: var(--dsw-alias-label-primary-bluish, #1f4f8f);
1759
+ background: var(--dsw-alias-state-business-tertiary, rgba(59, 130, 246, 0.14));
1760
+ flex: none;
1761
+ }
1762
+ .ss-rowHover {
1763
+ display: flex;
1764
+ align-items: center;
1765
+ gap: 6px;
1766
+ color: #cfd3d6;
1767
+ font-size: 12px;
1768
+ line-height: 16px;
1769
+ }
1770
+ .ss-toast {
1771
+ position: fixed;
1772
+ left: 50%;
1773
+ bottom: 28px;
1774
+ transform: translateX(-50%);
1775
+ z-index: 90;
1776
+ max-width: min(520px, calc(100vw - 40px));
1777
+ box-sizing: border-box;
1778
+ padding: 9px 14px;
1779
+ border-radius: var(--dsw-radius-md, 10px);
1780
+ border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));
1781
+ background: var(--dsw-alias-bg-elevated, #fff);
1782
+ color: var(--dsw-alias-label-primary, #1f2328);
1783
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
1784
+ font-size: 13px;
1785
+ line-height: 19px;
1786
+ pointer-events: auto;
1787
+ }
1788
+ .ss-toastError {
1789
+ color: var(--dsw-alias-state-error-primary, #d1242f);
1790
+ }
1791
+ .ss-visuallyHidden {
1792
+ position: absolute;
1793
+ width: 1px;
1794
+ height: 1px;
1795
+ padding: 0;
1796
+ margin: -1px;
1797
+ overflow: hidden;
1798
+ clip: rect(0, 0, 0, 0);
1799
+ white-space: nowrap;
1800
+ border: 0;
1801
+ }
1802
+ @media (hover: none) {
1803
+ .ss-rowMenuButton { opacity: 1; }
1804
+ }
1805
+ @media (max-width: 640px) {
1806
+ .ss-panel { padding: 20px 14px 40px; }
1807
+ .ss-row { flex-wrap: wrap; }
1808
+ .ss-rowActions { width: 100%; justify-content: flex-end; }
1809
+ }
1810
+ `;
1811
+ function insertStyles() {
1812
+ if (typeof document === "undefined") return () => void 0;
1813
+ const existing = document.querySelector('style[data-plugin="dsh-plugin-standalone-session"]');
1814
+ if (existing !== null) {
1815
+ existing.textContent = CSS;
1816
+ return () => void 0;
1817
+ }
1818
+ const tag = document.createElement("style");
1819
+ tag.setAttribute("data-plugin", "dsh-plugin-standalone-session");
1820
+ tag.textContent = CSS;
1821
+ document.head.appendChild(tag);
1822
+ return () => tag.remove();
1823
+ }
1824
+
1825
+ // src/client/index.ts
1826
+ var inject = [
1827
+ "slots",
1828
+ "locale",
1829
+ "sessions",
1830
+ "workspaces",
1831
+ "uiWorkspace",
1832
+ "commandUi",
1833
+ "layout",
1834
+ "remote"
1835
+ ];
1836
+ function apply(ctx) {
1837
+ const boundT = ctx.locale.bind(NS);
1838
+ ctx.effect(
1839
+ () => ctx.locale.register(NS, { zh, en }),
1840
+ "standalone-session: dictionaries"
1841
+ );
1842
+ ctx.effect(() => insertStyles(), "standalone-session: styles");
1843
+ ctx.slots.inject("conversation.composer.bar", () => patchComposer(ctx, boundT));
1844
+ ctx.slots.inject(
1845
+ "main",
1846
+ () => ctx.slots.register(
1847
+ { name: "main", key: NEW_PANEL_ID, locale: NS },
1848
+ createStandaloneLauncher(ctx)
1849
+ )
1850
+ );
1851
+ ctx.slots.inject(
1852
+ "main",
1853
+ () => ctx.slots.register(
1854
+ { name: "main", key: PANEL_ID, locale: NS },
1855
+ createStandalonePanel(ctx)
1856
+ )
1857
+ );
1858
+ ctx.slots.inject(
1859
+ "sidebar.panellist",
1860
+ () => ctx.slots.register(
1861
+ {
1862
+ name: "sidebar.panellist",
1863
+ id: NEW_PANEL_ID,
1864
+ order: 20,
1865
+ locale: NS,
1866
+ label: () => boundT("entry.label")
1867
+ },
1868
+ createLauncherEntryIcon()
1869
+ )
1870
+ );
1871
+ ctx.slots.inject(
1872
+ "sidebar.footer.action",
1873
+ () => ctx.slots.register(
1874
+ {
1875
+ name: "sidebar.footer.action",
1876
+ id: "standalone-session-manage",
1877
+ order: 20,
1878
+ locale: NS
1879
+ },
1880
+ createManageFooterAction(ctx)
1881
+ )
1882
+ );
1883
+ ctx.slots.inject(
1884
+ "conversation.hero.options",
1885
+ () => ctx.slots.register(
1886
+ {
1887
+ name: "conversation.hero.options",
1888
+ id: "standalone-session-badge",
1889
+ order: 100,
1890
+ locale: NS
1891
+ },
1892
+ createHeroBadge(ctx)
1893
+ )
1894
+ );
1895
+ ctx.slots.inject(
1896
+ "sidebar.session.row.leading",
1897
+ () => ctx.slots.register(
1898
+ {
1899
+ name: "sidebar.session.row.leading",
1900
+ id: "standalone-session-mark",
1901
+ order: 100,
1902
+ locale: NS
1903
+ },
1904
+ createRowLeading(ctx)
1905
+ )
1906
+ );
1907
+ ctx.slots.inject(
1908
+ "sidebar.session.row.hover",
1909
+ () => ctx.slots.register(
1910
+ {
1911
+ name: "sidebar.session.row.hover",
1912
+ id: "standalone-session-hover",
1913
+ order: 100,
1914
+ locale: NS
1915
+ },
1916
+ createRowHover(ctx)
1917
+ )
1918
+ );
1919
+ ctx.slots.inject(
1920
+ "shell.overlay",
1921
+ () => ctx.slots.register(
1922
+ {
1923
+ name: "shell.overlay",
1924
+ id: "standalone-session-toast",
1925
+ order: 1e3
1926
+ },
1927
+ StandaloneToast
1928
+ )
1929
+ );
1930
+ ctx.effect(
1931
+ () => ctx.commandUi.register({
1932
+ name: "standalone-new",
1933
+ label: () => boundT("command.label"),
1934
+ description: () => boundT("command.description"),
1935
+ icon: StandaloneIcon,
1936
+ available: () => true,
1937
+ ui: {
1938
+ kind: "action",
1939
+ run: () => {
1940
+ void startStandalone(ctx, boundT).catch((error) => {
1941
+ showNotice(
1942
+ "error",
1943
+ boundT("toast.createFailed", { message: messageOf(error) })
1944
+ );
1945
+ });
1946
+ }
1947
+ }
1948
+ }),
1949
+ "standalone-session: new command"
1950
+ );
1951
+ ctx.effect(
1952
+ () => ctx.commandUi.register({
1953
+ name: "standalone-manage",
1954
+ label: () => boundT("command.manageLabel"),
1955
+ description: () => boundT("command.manageDescription"),
1956
+ icon: StandaloneIcon,
1957
+ available: () => true,
1958
+ ui: {
1959
+ kind: "action",
1960
+ run: () => openManagementPanel(ctx, boundT)
1961
+ }
1962
+ }),
1963
+ "standalone-session: manage command"
1964
+ );
1965
+ }
1966
+
1967
+ return module.exports;
1968
+ }
1969
+ });