dsh-group-status 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -0
- package/README.en.md +12 -10
- package/README.md +14 -10
- package/lib/client.js +1294 -281
- package/lib/client.js.map +1 -1
- package/lib/index.js +7 -4
- package/lib/types/client/BadgeSettingsCard.d.ts.map +1 -1
- package/lib/types/client/BadgeSettingsCard.js +6 -6
- package/lib/types/client/BadgeSettingsCard.js.map +1 -1
- package/lib/types/client/badge-field.d.ts +33 -10
- package/lib/types/client/badge-field.d.ts.map +1 -1
- package/lib/types/client/badge-field.js +35 -16
- package/lib/types/client/badge-field.js.map +1 -1
- package/lib/types/client/badge-labels.d.ts +3 -3
- package/lib/types/client/badge-labels.js +3 -3
- package/lib/types/client/badge-settings.d.ts +0 -25
- package/lib/types/client/badge-settings.d.ts.map +1 -1
- package/lib/types/client/badge-settings.js +0 -18
- package/lib/types/client/badge-settings.js.map +1 -1
- package/lib/types/client/contract/slots.d.ts +39 -21
- package/lib/types/client/contract/slots.d.ts.map +1 -1
- package/lib/types/client/contract/slots.js.map +1 -1
- package/lib/types/client/contract/toast.d.ts +67 -0
- package/lib/types/client/contract/toast.d.ts.map +1 -0
- package/lib/types/client/contract/toast.js +2 -0
- package/lib/types/client/contract/toast.js.map +1 -0
- package/lib/types/client/facets.d.ts +39 -5
- package/lib/types/client/facets.d.ts.map +1 -1
- package/lib/types/client/facets.js +48 -4
- package/lib/types/client/facets.js.map +1 -1
- package/lib/types/client/index.d.ts +6 -3
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +48 -13
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/locales.d.ts +14 -8
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +14 -8
- package/lib/types/client/locales.js.map +1 -1
- package/lib/types/client/navigation.d.ts +45 -12
- package/lib/types/client/navigation.d.ts.map +1 -1
- package/lib/types/client/navigation.js +102 -26
- package/lib/types/client/navigation.js.map +1 -1
- package/lib/types/client/rows/GroupBadge.d.ts +4 -4
- package/lib/types/client/rows/GroupBadge.d.ts.map +1 -1
- package/lib/types/client/rows/GroupBadge.js +10 -7
- package/lib/types/client/rows/GroupBadge.js.map +1 -1
- package/lib/types/client/rows/Rows.d.ts.map +1 -1
- package/lib/types/client/rows/Rows.js +4 -4
- package/lib/types/client/rows/Rows.js.map +1 -1
- package/lib/types/client/rows/WorkspaceBrowser.d.ts +1 -1
- package/lib/types/client/rows/WorkspaceBrowser.d.ts.map +1 -1
- package/lib/types/client/rows/WorkspaceBrowser.js +66 -61
- package/lib/types/client/rows/WorkspaceBrowser.js.map +1 -1
- package/lib/types/client/session-actions/ArchiveSession.js +1 -1
- package/lib/types/client/session-actions/ArchiveSession.js.map +1 -1
- package/lib/types/client/session-actions/ForkSession.d.ts +5 -4
- package/lib/types/client/session-actions/ForkSession.d.ts.map +1 -1
- package/lib/types/client/session-actions/ForkSession.js +7 -4
- package/lib/types/client/session-actions/ForkSession.js.map +1 -1
- package/lib/types/client/session-actions/PinSession.js +1 -1
- package/lib/types/client/session-actions/PinSession.js.map +1 -1
- package/lib/types/client/session-actions/RowActionToast.d.ts +5 -8
- package/lib/types/client/session-actions/RowActionToast.d.ts.map +1 -1
- package/lib/types/client/session-actions/RowActionToast.js +10 -49
- package/lib/types/client/session-actions/RowActionToast.js.map +1 -1
- package/lib/types/client/session-actions/SessionId.d.ts +8 -0
- package/lib/types/client/session-actions/SessionId.d.ts.map +1 -0
- package/lib/types/client/session-actions/SessionId.js +16 -0
- package/lib/types/client/session-actions/SessionId.js.map +1 -0
- package/lib/types/client/session-watermarks.d.ts +30 -0
- package/lib/types/client/session-watermarks.d.ts.map +1 -0
- package/lib/types/client/session-watermarks.js +54 -0
- package/lib/types/client/session-watermarks.js.map +1 -0
- package/lib/types/client/shortcuts.d.ts +2 -7
- package/lib/types/client/shortcuts.d.ts.map +1 -1
- package/lib/types/client/shortcuts.js +7 -11
- package/lib/types/client/shortcuts.js.map +1 -1
- package/lib/types/client/toast/Toast.d.ts +41 -0
- package/lib/types/client/toast/Toast.d.ts.map +1 -0
- package/lib/types/client/toast/Toast.js +44 -0
- package/lib/types/client/toast/Toast.js.map +1 -0
- package/lib/types/client/toast/register.d.ts +11 -0
- package/lib/types/client/toast/register.d.ts.map +1 -0
- package/lib/types/client/toast/register.js +162 -0
- package/lib/types/client/toast/register.js.map +1 -0
- package/lib/types/client/tree.d.ts +16 -1
- package/lib/types/client/tree.d.ts.map +1 -1
- package/lib/types/client/tree.js +20 -8
- package/lib/types/client/tree.js.map +1 -1
- package/lib/types/client/use-registered-facets.d.ts +11 -0
- package/lib/types/client/use-registered-facets.d.ts.map +1 -0
- package/lib/types/client/use-registered-facets.js +48 -0
- package/lib/types/client/use-registered-facets.js.map +1 -0
- package/lib/types/host-contract.d.ts +57 -0
- package/lib/types/host-contract.d.ts.map +1 -0
- package/lib/types/host-contract.js +84 -0
- package/lib/types/host-contract.js.map +1 -0
- package/lib/types/index.d.ts +7 -10
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +7 -4
- package/lib/types/index.js.map +1 -1
- package/package.json +34 -30
- package/lib/types/client/facet-visibility.d.ts +0 -17
- package/lib/types/client/facet-visibility.d.ts.map +0 -1
- package/lib/types/client/facet-visibility.js +0 -18
- package/lib/types/client/facet-visibility.js.map +0 -1
- package/lib/types/client/legacy/bridge.d.ts +0 -30
- package/lib/types/client/legacy/bridge.d.ts.map +0 -1
- package/lib/types/client/legacy/bridge.js +0 -58
- package/lib/types/client/legacy/bridge.js.map +0 -1
- package/lib/types/client/legacy/register-legacy-facets.d.ts +0 -14
- package/lib/types/client/legacy/register-legacy-facets.d.ts.map +0 -1
- package/lib/types/client/legacy/register-legacy-facets.js +0 -77
- package/lib/types/client/legacy/register-legacy-facets.js.map +0 -1
- package/lib/types/client/legacy/schedule.d.ts +0 -49
- package/lib/types/client/legacy/schedule.d.ts.map +0 -1
- package/lib/types/client/legacy/schedule.js +0 -73
- package/lib/types/client/legacy/schedule.js.map +0 -1
- package/lib/types/client/legacy/store-source.d.ts +0 -78
- package/lib/types/client/legacy/store-source.d.ts.map +0 -1
- package/lib/types/client/legacy/store-source.js +0 -82
- package/lib/types/client/legacy/store-source.js.map +0 -1
- package/lib/types/client/legacy/waiting-review.d.ts +0 -85
- package/lib/types/client/legacy/waiting-review.d.ts.map +0 -1
- package/lib/types/client/legacy/waiting-review.js +0 -179
- package/lib/types/client/legacy/waiting-review.js.map +0 -1
package/lib/client.js
CHANGED
|
@@ -5,8 +5,8 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
8
|
-
let react = require("react");
|
|
9
8
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
|
+
let react = require("react");
|
|
10
10
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
11
|
let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
|
|
12
12
|
//#region src/client/contract/slots.ts
|
|
@@ -30,10 +30,8 @@ window.__ModuleLoader__.load({
|
|
|
30
30
|
searchRequest: 0,
|
|
31
31
|
addRequested: false,
|
|
32
32
|
directoryBusy: false,
|
|
33
|
-
renameTarget: null
|
|
34
|
-
forkError: null
|
|
33
|
+
renameTarget: null
|
|
35
34
|
});
|
|
36
|
-
let forkErrorSeq = 0;
|
|
37
35
|
return {
|
|
38
36
|
state,
|
|
39
37
|
search: () => {
|
|
@@ -74,22 +72,6 @@ window.__ModuleLoader__.load({
|
|
|
74
72
|
...state.getSnapshot(),
|
|
75
73
|
renameTarget: null
|
|
76
74
|
});
|
|
77
|
-
},
|
|
78
|
-
forkFailed: (reason) => {
|
|
79
|
-
forkErrorSeq += 1;
|
|
80
|
-
state.set({
|
|
81
|
-
...state.getSnapshot(),
|
|
82
|
-
forkError: {
|
|
83
|
-
reason,
|
|
84
|
-
seq: forkErrorSeq
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
},
|
|
88
|
-
dismissForkError: () => {
|
|
89
|
-
state.set({
|
|
90
|
-
...state.getSnapshot(),
|
|
91
|
-
forkError: null
|
|
92
|
-
});
|
|
93
75
|
}
|
|
94
76
|
};
|
|
95
77
|
}
|
|
@@ -100,8 +82,9 @@ window.__ModuleLoader__.load({
|
|
|
100
82
|
* @param navigation - session creation and forking from the pointer controls' navigation service.
|
|
101
83
|
* @param controls - browser-owned opening requests.
|
|
102
84
|
* @param archiveSession - shared archive action, including running-work confirmation and notices.
|
|
85
|
+
* @param reportForkFailure - publish a classified failure through the frame-wide notice channel.
|
|
103
86
|
*/
|
|
104
|
-
function installWorkspaceShortcuts(ctx, navigation, controls, archiveSession) {
|
|
87
|
+
function installWorkspaceShortcuts(ctx, navigation, controls, archiveSession, reportForkFailure) {
|
|
105
88
|
const t = ctx.locale.bind("workspace");
|
|
106
89
|
const current = () => Object.values(ctx.sessions.list.getSnapshot().byId).find((row) => (row.retainedBy.mainView ?? 0) > 0);
|
|
107
90
|
const addReason = () => ctx.slots.entries("sidebar.workspaces.directoryFlow").length === 0 ? t("shortcut.noPicker") : controls.state.getSnapshot().directoryBusy ? t("shortcut.directoryBusy") : null;
|
|
@@ -183,8 +166,8 @@ window.__ModuleLoader__.load({
|
|
|
183
166
|
status: "handled",
|
|
184
167
|
run: () => {
|
|
185
168
|
navigation.forkSession(target.id).catch((error) => {
|
|
186
|
-
const unavailable = error instanceof Error && error.name === "SessionForkError"
|
|
187
|
-
|
|
169
|
+
const unavailable = (error instanceof Error && error.name === "SessionForkError" ? error : void 0)?.rpcError.code === "session/fork-unavailable";
|
|
170
|
+
reportForkFailure(unavailable ? "unavailable" : "failed");
|
|
188
171
|
if (!unavailable) console.warn("session fork rejected:", error);
|
|
189
172
|
});
|
|
190
173
|
}
|
|
@@ -228,30 +211,17 @@ window.__ModuleLoader__.load({
|
|
|
228
211
|
*/
|
|
229
212
|
/** Loader 条目 id —— 设置页的寻址键,也是宿主 `describe()` 的 ns。 */
|
|
230
213
|
const ENTRY_ID = "dsh-group-status";
|
|
231
|
-
/**
|
|
232
|
-
* 一个 facet 是否被设置关掉。
|
|
233
|
-
*
|
|
234
|
-
* - 没有 `settingKey` → 始终参与(缺省是"显示",与其余开关的默认值同源)。
|
|
235
|
-
* - 声明了但不是布尔开关键 → 同样按显示处理:拼错的键不该让一个状态整块消失。
|
|
236
|
-
*/
|
|
237
|
-
function facetVisibleBySettings(settings, settingKey) {
|
|
238
|
-
if (settingKey === void 0) return true;
|
|
239
|
-
const value = settings[settingKey];
|
|
240
|
-
return typeof value === "boolean" ? value : true;
|
|
241
|
-
}
|
|
242
214
|
/** 默认值:与 `Config` 各字段的 schema 默认同源(全部显示,数字阈值 2 即 count=1 时省略)。 */
|
|
243
215
|
const DEFAULT_BADGE_SETTINGS = Object.freeze({
|
|
244
216
|
showCompletedInBadge: true,
|
|
245
217
|
showRunningInBadge: true,
|
|
246
218
|
showPendingInBadge: true,
|
|
247
|
-
showScheduledInBadge: true,
|
|
248
|
-
showWaitingReviewInBadge: true,
|
|
249
219
|
showHoverDetails: true,
|
|
250
220
|
countThreshold: 2
|
|
251
221
|
});
|
|
252
222
|
/** 两个设置对象是否逐字段相等(用于发布前比较,避免无谓通知)。 */
|
|
253
223
|
function settingsEqual(a, b) {
|
|
254
|
-
return a.showCompletedInBadge === b.showCompletedInBadge && a.showRunningInBadge === b.showRunningInBadge && a.showPendingInBadge === b.showPendingInBadge && a.
|
|
224
|
+
return a.showCompletedInBadge === b.showCompletedInBadge && a.showRunningInBadge === b.showRunningInBadge && a.showPendingInBadge === b.showPendingInBadge && a.showHoverDetails === b.showHoverDetails && a.countThreshold === b.countThreshold;
|
|
255
225
|
}
|
|
256
226
|
/** 从原始快照派生:只有**显式 false** 才隐藏,缺失/非法一律显示(countThreshold 默认 2)。 */
|
|
257
227
|
function resolveFromSnapshot(snap) {
|
|
@@ -270,8 +240,6 @@ window.__ModuleLoader__.load({
|
|
|
270
240
|
showCompletedInBadge: getBool("showCompletedInBadge", true),
|
|
271
241
|
showRunningInBadge: getBool("showRunningInBadge", true),
|
|
272
242
|
showPendingInBadge: getBool("showPendingInBadge", true),
|
|
273
|
-
showScheduledInBadge: getBool("showScheduledInBadge", true),
|
|
274
|
-
showWaitingReviewInBadge: getBool("showWaitingReviewInBadge", true),
|
|
275
243
|
showHoverDetails: getBool("showHoverDetails", true),
|
|
276
244
|
countThreshold: getNumber("countThreshold", 2)
|
|
277
245
|
};
|
|
@@ -451,12 +419,52 @@ window.__ModuleLoader__.load({
|
|
|
451
419
|
};
|
|
452
420
|
}
|
|
453
421
|
/**
|
|
454
|
-
*
|
|
422
|
+
* 挂在 `globalThis` 上的登记表面的键名。
|
|
423
|
+
*
|
|
424
|
+
* 它同时是**现场诊断入口**:宿主里可以直接
|
|
425
|
+
* `globalThis.__dshGroupStatusFacetRegistry__.list().map(e => e.facet.id)` 读出"此刻到底
|
|
426
|
+
* 有哪些状态源登记上了"——「我注册了为什么没显示」不该只能靠猜(协议文档点名的那类
|
|
427
|
+
* 不可诊断失败)。
|
|
428
|
+
*/
|
|
429
|
+
const SHARED_REGISTRY_KEY = "__dshGroupStatusFacetRegistry__";
|
|
430
|
+
/**
|
|
431
|
+
* 取(并首次创建)**进程级**共享的登记表面。
|
|
432
|
+
*
|
|
433
|
+
* ## 为什么不能是模块级
|
|
434
|
+
*
|
|
435
|
+
* 提供方把 facet 注册进来是一次性的:它在自己的 `apply` 里 `require` 本插件的 client
|
|
436
|
+
* 入口、登记完就把注销函数挂在 effect 上。而宿主的热替换(HMR、`reload(id, rev)`)
|
|
437
|
+
* **只换代被替换的那个模块**——`dsh-client-modules` 的 `invalidate()` 只删自己那一行的
|
|
438
|
+
* factory 与 loadCache,不级联到"谁 require 过它"(依赖方不会重跑 apply)。
|
|
439
|
+
*
|
|
440
|
+
* 于是模块级单例在这里必然失效:本插件被换成新实例 → 新实例建一张**空表** → 提供方
|
|
441
|
+
* 那份注册留在被丢弃的旧表上 → 徽标从此少一家,且**只有刷新页面才好**。开发环境天天
|
|
442
|
+
* 重建 `lib/`,这条几乎必然发生。
|
|
443
|
+
*
|
|
444
|
+
* 挂到 `globalThis` 上之后,换代只是"再拿一次同一张表":注册跨换代存活,`facetStore()`
|
|
445
|
+
* 在新实例里 `rewire()` 时照样订阅到那些来源。提供方被换代时按同 id 覆盖登记(见
|
|
446
|
+
* {@link FacetRegistry.register} 的来源决胜),不会重复计数。
|
|
447
|
+
*
|
|
448
|
+
* @returns 全进程唯一的登记表面。
|
|
449
|
+
*/
|
|
450
|
+
function sharedFacetRegistry() {
|
|
451
|
+
const host = globalThis;
|
|
452
|
+
const existing = host[SHARED_REGISTRY_KEY];
|
|
453
|
+
if (existing !== void 0) return existing;
|
|
454
|
+
const created = createFacetRegistry();
|
|
455
|
+
host[SHARED_REGISTRY_KEY] = created;
|
|
456
|
+
return created;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* 生产用的单例登记表面。
|
|
455
460
|
*
|
|
456
461
|
* 与 {@link createFacetRegistry} 分开,是为了让测试能对着一张干净的表断言协议行为,
|
|
457
462
|
* 而不会被别的用例或别的插件登记的 facet 污染。
|
|
463
|
+
*
|
|
464
|
+
* ⚠️ 它只是 {@link sharedFacetRegistry} 在**本模块首次加载时**取到的那份引用;跨模块实例
|
|
465
|
+
* 存活的是 globalThis 上那一张,所以运行期一律经 {@link sharedFacetRegistry} 现取。
|
|
458
466
|
*/
|
|
459
|
-
const registry =
|
|
467
|
+
const registry = sharedFacetRegistry();
|
|
460
468
|
/**
|
|
461
469
|
* 本插件 client 入口对外的登记函数(provider 侧唯一需要认识的符号)。
|
|
462
470
|
*
|
|
@@ -465,13 +473,14 @@ window.__ModuleLoader__.load({
|
|
|
465
473
|
* 返回值形状保持稳定,provider 的 dispose 路径不必区分成败。
|
|
466
474
|
*/
|
|
467
475
|
function registerGroupStatusFacet(facet) {
|
|
468
|
-
const
|
|
476
|
+
const target = sharedFacetRegistry();
|
|
477
|
+
const result = target.register(facet);
|
|
469
478
|
if (!result.ok) {
|
|
470
479
|
if (!(result.reason === "superseded" && facetOrigin(facet) === "legacy")) reportRejection(result);
|
|
471
480
|
return () => void 0;
|
|
472
481
|
}
|
|
473
482
|
return () => {
|
|
474
|
-
|
|
483
|
+
target.remove(result.id, facet);
|
|
475
484
|
};
|
|
476
485
|
}
|
|
477
486
|
/**
|
|
@@ -592,19 +601,39 @@ window.__ModuleLoader__.load({
|
|
|
592
601
|
}
|
|
593
602
|
//#endregion
|
|
594
603
|
//#region src/client/badge-field.ts
|
|
595
|
-
/**
|
|
604
|
+
/**
|
|
605
|
+
* 每个开关控制的是徽标上的哪一枚点。
|
|
606
|
+
*
|
|
607
|
+
* 单一事实来源:设置卡片里给每行画的**预览点**与折叠徽标实际画的那三枚点,都从这里取。
|
|
608
|
+
* 两处各写一套的话迟早会漂,而漂了的后果不是"多一个点少一个点",是**卡片预览与徽标说的
|
|
609
|
+
* 不是一回事**(同一族毛病:#25① 修过点行与明细的顺序,#37 又发现配置面里混着别人家的语义)。
|
|
610
|
+
*
|
|
611
|
+
* 对应关系与 `rows/GroupBadge.tsx` 一致:待审批 = warning 黄点、进行中 = ongoing 转圈、
|
|
612
|
+
* 已完成 = done 绿点。外部来源的点**不在这里** —— 它们由来源自己画(`renderIcon`),
|
|
613
|
+
* 也不在本插件的卡片上(显隐归来源自己)。
|
|
614
|
+
*/
|
|
615
|
+
const BADGE_FIELD_DOT_STATE = {
|
|
616
|
+
showPendingInBadge: "warning",
|
|
617
|
+
showRunningInBadge: "ongoing",
|
|
618
|
+
showCompletedInBadge: "done"
|
|
619
|
+
};
|
|
620
|
+
/**
|
|
621
|
+
* 卡片字段名(与 `src/index.ts` 的 `Config` 同名同义)。
|
|
622
|
+
*
|
|
623
|
+
* ⚠️ 只有本插件自己的语义。曾有 `showScheduledInBadge` / `showWaitingReviewInBadge`
|
|
624
|
+
* 两个字段替 dsh-later / dsh-gitea-dispatch 写死名字与图标形状,现已删除:外部来源的
|
|
625
|
+
* 显隐归来源自己(不注册就是不显示)。
|
|
626
|
+
*/
|
|
596
627
|
const BADGE_FIELDS = {
|
|
597
628
|
showCompletedInBadge: "showCompletedInBadge",
|
|
598
629
|
showRunningInBadge: "showRunningInBadge",
|
|
599
630
|
showPendingInBadge: "showPendingInBadge",
|
|
600
|
-
showScheduledInBadge: "showScheduledInBadge",
|
|
601
|
-
showWaitingReviewInBadge: "showWaitingReviewInBadge",
|
|
602
631
|
showHoverDetails: "showHoverDetails",
|
|
603
632
|
countThreshold: "countThreshold"
|
|
604
633
|
};
|
|
605
634
|
/**
|
|
606
|
-
*
|
|
607
|
-
*
|
|
635
|
+
* 草稿文本解析成开关状态:比较的是模型给的**文本**,不是存储值 ——
|
|
636
|
+
* 别把字符串 `'false'` 当成布尔 `false`(`'false' !== false` 恒真)。
|
|
608
637
|
*/
|
|
609
638
|
function isDraftEnabled(text) {
|
|
610
639
|
return text !== "false";
|
|
@@ -633,13 +662,21 @@ window.__ModuleLoader__.load({
|
|
|
633
662
|
} : void 0
|
|
634
663
|
};
|
|
635
664
|
}
|
|
636
|
-
/**
|
|
665
|
+
/** 各字段的字段转换规格。 */
|
|
666
|
+
const showCompletedField = makeBooleanField(BADGE_FIELDS.showCompletedInBadge, true);
|
|
667
|
+
const showRunningField = makeBooleanField(BADGE_FIELDS.showRunningInBadge, true);
|
|
668
|
+
/**
|
|
669
|
+
* 所有字段规格。
|
|
670
|
+
*
|
|
671
|
+
* ⚠️ 顺序**必须**与徽标画点的顺序一致(`rows/GroupBadge.tsx`:待审批 → 进行中 → 已完成;
|
|
672
|
+
* 外部来源排在已完成之前,但它们不在本插件的卡片上)。用户操作场景就是"我看到徽标第 N
|
|
673
|
+
* 个图标,想关掉它",卡片顺序错位会让人怀疑两处是两套事实 —— 同一族毛病 #25① 已经在
|
|
674
|
+
* 点行与明细之间修过一次。回归用例:`tests/badge-settings-card.client.spec.tsx`。
|
|
675
|
+
*/
|
|
637
676
|
const ALL_BADGE_FIELDS = [
|
|
638
|
-
makeBooleanField(BADGE_FIELDS.showCompletedInBadge, true),
|
|
639
|
-
makeBooleanField(BADGE_FIELDS.showRunningInBadge, true),
|
|
640
677
|
makeBooleanField(BADGE_FIELDS.showPendingInBadge, true),
|
|
641
|
-
|
|
642
|
-
|
|
678
|
+
showRunningField,
|
|
679
|
+
showCompletedField,
|
|
643
680
|
makeBooleanField(BADGE_FIELDS.showHoverDetails, true),
|
|
644
681
|
{
|
|
645
682
|
field: BADGE_FIELDS.countThreshold,
|
|
@@ -659,7 +696,7 @@ window.__ModuleLoader__.load({
|
|
|
659
696
|
];
|
|
660
697
|
//#endregion
|
|
661
698
|
//#region \0dsh-css:src/client/BadgeSettingsCard.module.css.mjs
|
|
662
|
-
const css$4 = ".dhp54G_section{min-width:0;padding:16px 0}.dhp54G_section:last-child{padding-bottom:0}.dhp54G_heading{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:600;line-height:1.5}.dhp54G_badgeField{gap:6px;padding:12px 0;display:grid}.dhp54G_badgeToggleRow{color:var(--dsw-alias-label-primary);justify-content:space-between;align-items:flex-start;gap:16px;font-size:13px;line-height:1.5;display:flex}.dhp54G_badgeToggleLabel{flex:1;min-width:0}.dhp54G_badgeNumberControl{align-items:center;gap:4px;display:flex}.dhp54G_badgeNumberButton{width:28px;height:28px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-elevated,#2a2a2a);border:1px solid var(--dsw-alias-border-primary,#555);cursor:pointer;user-select:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:16px;font-weight:600;line-height:1;display:flex}.dhp54G_badgeNumberButton:hover:not(:disabled){border-color:var(--dsw-alias-border-focus,#0078d4);background:var(--dsw-alias-bg-hover,#3a3a3a)}.dhp54G_badgeNumberButton:active:not(:disabled){background:var(--dsw-alias-bg-active,#4a4a4a)}.dhp54G_badgeNumberButton:disabled{opacity:.3;cursor:not-allowed}.dhp54G_badgeNumberValue{text-align:center;min-width:24px;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}.dhp54G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}";
|
|
699
|
+
const css$4 = ".dhp54G_section{min-width:0;padding:16px 0}.dhp54G_section:last-child{padding-bottom:0}.dhp54G_heading{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:600;line-height:1.5}.dhp54G_badgeField{gap:6px;padding:12px 0;display:grid}.dhp54G_badgeToggleRow{color:var(--dsw-alias-label-primary);justify-content:space-between;align-items:flex-start;gap:16px;font-size:13px;line-height:1.5;display:flex}.dhp54G_badgeSpecimen{vertical-align:-2px;align-items:center;margin-right:6px;display:inline-flex}.dhp54G_badgeToggleLabel{flex:1;min-width:0}.dhp54G_badgeNumberControl{align-items:center;gap:4px;display:flex}.dhp54G_badgeNumberButton{width:28px;height:28px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-elevated,#2a2a2a);border:1px solid var(--dsw-alias-border-primary,#555);cursor:pointer;user-select:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:16px;font-weight:600;line-height:1;display:flex}.dhp54G_badgeNumberButton:hover:not(:disabled){border-color:var(--dsw-alias-border-focus,#0078d4);background:var(--dsw-alias-bg-hover,#3a3a3a)}.dhp54G_badgeNumberButton:active:not(:disabled){background:var(--dsw-alias-bg-active,#4a4a4a)}.dhp54G_badgeNumberButton:disabled{opacity:.3;cursor:not-allowed}.dhp54G_badgeNumberValue{text-align:center;min-width:24px;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}.dhp54G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}";
|
|
663
700
|
const tagId$4 = "dsh-group-status/BadgeSettingsCard.module.css";
|
|
664
701
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
|
|
665
702
|
const tag = document.createElement("style");
|
|
@@ -673,6 +710,7 @@ window.__ModuleLoader__.load({
|
|
|
673
710
|
"badgeNumberButton": "dhp54G_badgeNumberButton",
|
|
674
711
|
"badgeNumberControl": "dhp54G_badgeNumberControl",
|
|
675
712
|
"badgeNumberValue": "dhp54G_badgeNumberValue",
|
|
713
|
+
"badgeSpecimen": "dhp54G_badgeSpecimen",
|
|
676
714
|
"badgeToggleLabel": "dhp54G_badgeToggleLabel",
|
|
677
715
|
"badgeToggleRow": "dhp54G_badgeToggleRow",
|
|
678
716
|
"heading": "dhp54G_heading",
|
|
@@ -753,21 +791,29 @@ window.__ModuleLoader__.load({
|
|
|
753
791
|
id: badgeSectionId,
|
|
754
792
|
children: t("settings.badge.section")
|
|
755
793
|
}),
|
|
756
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
757
|
-
field: BADGE_FIELDS.showRunningInBadge,
|
|
758
|
-
text: fieldText(BADGE_FIELDS.showRunningInBadge),
|
|
759
|
-
label: t("settings.badge.showRunning"),
|
|
760
|
-
hint: t("settings.badge.showRunningHint"),
|
|
761
|
-
state,
|
|
762
|
-
actions
|
|
763
|
-
}),
|
|
764
794
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
765
795
|
field: BADGE_FIELDS.showPendingInBadge,
|
|
766
796
|
text: fieldText(BADGE_FIELDS.showPendingInBadge),
|
|
767
797
|
label: t("settings.badge.showPending"),
|
|
768
798
|
hint: t("settings.badge.showPendingHint"),
|
|
769
799
|
state,
|
|
770
|
-
actions
|
|
800
|
+
actions,
|
|
801
|
+
specimen: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
|
|
802
|
+
state: BADGE_FIELD_DOT_STATE.showPendingInBadge,
|
|
803
|
+
size: 12
|
|
804
|
+
})
|
|
805
|
+
}),
|
|
806
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
807
|
+
field: BADGE_FIELDS.showRunningInBadge,
|
|
808
|
+
text: fieldText(BADGE_FIELDS.showRunningInBadge),
|
|
809
|
+
label: t("settings.badge.showRunning"),
|
|
810
|
+
hint: t("settings.badge.showRunningHint"),
|
|
811
|
+
state,
|
|
812
|
+
actions,
|
|
813
|
+
specimen: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
|
|
814
|
+
state: BADGE_FIELD_DOT_STATE.showRunningInBadge,
|
|
815
|
+
size: 12
|
|
816
|
+
})
|
|
771
817
|
}),
|
|
772
818
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
773
819
|
field: BADGE_FIELDS.showCompletedInBadge,
|
|
@@ -775,23 +821,11 @@ window.__ModuleLoader__.load({
|
|
|
775
821
|
label: t("settings.badge.showCompleted"),
|
|
776
822
|
hint: t("settings.badge.showCompletedHint"),
|
|
777
823
|
state,
|
|
778
|
-
actions
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
label: t("settings.badge.showScheduled"),
|
|
784
|
-
hint: t("settings.badge.showScheduledHint"),
|
|
785
|
-
state,
|
|
786
|
-
actions
|
|
787
|
-
}),
|
|
788
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
789
|
-
field: BADGE_FIELDS.showWaitingReviewInBadge,
|
|
790
|
-
text: fieldText(BADGE_FIELDS.showWaitingReviewInBadge),
|
|
791
|
-
label: t("settings.badge.showWaitingReview"),
|
|
792
|
-
hint: t("settings.badge.showWaitingReviewHint"),
|
|
793
|
-
state,
|
|
794
|
-
actions
|
|
824
|
+
actions,
|
|
825
|
+
specimen: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
|
|
826
|
+
state: BADGE_FIELD_DOT_STATE.showCompletedInBadge,
|
|
827
|
+
size: 12
|
|
828
|
+
})
|
|
795
829
|
})
|
|
796
830
|
]
|
|
797
831
|
}),
|
|
@@ -830,15 +864,18 @@ window.__ModuleLoader__.load({
|
|
|
830
864
|
]
|
|
831
865
|
});
|
|
832
866
|
}
|
|
833
|
-
/** 单行开关:label + Switch + hint
|
|
834
|
-
function ToggleRow({ field, text, label, hint, state, actions }) {
|
|
867
|
+
/** 单行开关:label + Switch + hint。`specimen` 是行首那枚预览点(可选)。 */
|
|
868
|
+
function ToggleRow({ field, text, label, hint, state, actions, specimen }) {
|
|
835
869
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
836
870
|
className: BadgeSettingsCard_module_css_default.badgeField,
|
|
837
871
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
838
872
|
className: BadgeSettingsCard_module_css_default.badgeToggleRow,
|
|
839
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
873
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
840
874
|
className: BadgeSettingsCard_module_css_default.badgeToggleLabel,
|
|
841
|
-
children:
|
|
875
|
+
children: [specimen !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
876
|
+
className: BadgeSettingsCard_module_css_default.badgeSpecimen,
|
|
877
|
+
children: specimen
|
|
878
|
+
}), label]
|
|
842
879
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
843
880
|
checked: isDraftEnabled(text),
|
|
844
881
|
label,
|
|
@@ -902,7 +939,838 @@ window.__ModuleLoader__.load({
|
|
|
902
939
|
});
|
|
903
940
|
}
|
|
904
941
|
//#endregion
|
|
905
|
-
//#region
|
|
942
|
+
//#region src/client/toast/Toast.tsx
|
|
943
|
+
/** Shared Workspace notice presentation and keyed business adapters. */
|
|
944
|
+
/**
|
|
945
|
+
* Render one notice using the shared Factory.
|
|
946
|
+
* @param props - keyed payload, injected display mapping, locale, and Factory renderer.
|
|
947
|
+
* @returns the shared Toast occurrence.
|
|
948
|
+
*/
|
|
949
|
+
function ToastEntry(props) {
|
|
950
|
+
const { dismissToast, display, t, renderFactorySlot, seq } = props;
|
|
951
|
+
return renderFactorySlot("workspace.toast.content", {
|
|
952
|
+
...display({ ...props }, t, dismissToast),
|
|
953
|
+
seq,
|
|
954
|
+
onDone: dismissToast
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Keep the archive filter action reactive without subscribing unrelated notices.
|
|
959
|
+
* @param props - archive notice, display mapping, and the existing viewing store.
|
|
960
|
+
* @returns the shared Toast occurrence with its current actions.
|
|
961
|
+
*/
|
|
962
|
+
function ArchiveToastEntry(props) {
|
|
963
|
+
const { dismissToast, display, filterAction, useStore, t, renderFactorySlot, seq } = props;
|
|
964
|
+
const archivedVisible = useStore((state) => (state.archivedFilter ?? "default") !== "default");
|
|
965
|
+
const fields = display({ ...props }, t, dismissToast);
|
|
966
|
+
return renderFactorySlot("workspace.toast.content", {
|
|
967
|
+
...fields,
|
|
968
|
+
actions: [...fields.actions ?? [], ...archivedVisible ? [] : [filterAction(t, dismissToast)]],
|
|
969
|
+
seq,
|
|
970
|
+
onDone: dismissToast
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Present plain display data with the shared primitive's timing and portal.
|
|
975
|
+
* @param props - localized display fields, display identity, and dismissal.
|
|
976
|
+
* @returns one Toast, remounted only for a new display identity.
|
|
977
|
+
*/
|
|
978
|
+
function WorkspaceToast({ seq, text, tone, holdMs, actions, onDone }) {
|
|
979
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
980
|
+
text,
|
|
981
|
+
onDone,
|
|
982
|
+
...tone === "success" ? { tone: "success" } : {},
|
|
983
|
+
...tone === "warning" ? { icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}) } : {},
|
|
984
|
+
...holdMs === void 0 ? {} : { holdMs },
|
|
985
|
+
...actions === void 0 ? {} : { actions }
|
|
986
|
+
}, seq);
|
|
987
|
+
}
|
|
988
|
+
//#endregion
|
|
989
|
+
//#region src/client/toast/register.tsx
|
|
990
|
+
/** Wrap a business display mapping without making the slot component generic. */
|
|
991
|
+
function plainToast(display) {
|
|
992
|
+
return () => ({ display });
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Register notice presentations with their business-owned display fields and actions.
|
|
996
|
+
* @param ctx - workspace plugin context.
|
|
997
|
+
* @param viewStore - viewing store, read only by archive notices.
|
|
998
|
+
* @param actions - archive and source-opening callbacks.
|
|
999
|
+
*/
|
|
1000
|
+
function registerToasts(ctx, viewStore, actions) {
|
|
1001
|
+
const toastEntry = ToastEntry;
|
|
1002
|
+
const archiveToastEntry = ArchiveToastEntry;
|
|
1003
|
+
const archiveFilterAction = (t, dismiss) => ({
|
|
1004
|
+
prefix: t("toast.archivedOr"),
|
|
1005
|
+
label: t("toast.archivedFilter"),
|
|
1006
|
+
onClick: () => {
|
|
1007
|
+
dismiss();
|
|
1008
|
+
actions.showArchived();
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
ctx.slots.registerFactory({
|
|
1012
|
+
name: "workspace.toast.content",
|
|
1013
|
+
scope: "root"
|
|
1014
|
+
}, WorkspaceToast);
|
|
1015
|
+
ctx.slots.inject("workspace.toast", function* () {
|
|
1016
|
+
yield ctx.slots.register({
|
|
1017
|
+
name: "workspace.toast",
|
|
1018
|
+
key: "sessionIdCopied",
|
|
1019
|
+
locale: "workspace",
|
|
1020
|
+
inject: plainToast((_notice, t) => ({
|
|
1021
|
+
text: t("toast.sessionIdCopied"),
|
|
1022
|
+
tone: "success"
|
|
1023
|
+
}))
|
|
1024
|
+
}, toastEntry);
|
|
1025
|
+
yield ctx.slots.register({
|
|
1026
|
+
name: "workspace.toast",
|
|
1027
|
+
key: "sessionIdCopyFailed",
|
|
1028
|
+
locale: "workspace",
|
|
1029
|
+
inject: plainToast((_notice, t) => ({
|
|
1030
|
+
text: t("toast.sessionIdCopyFailed"),
|
|
1031
|
+
tone: "warning"
|
|
1032
|
+
}))
|
|
1033
|
+
}, toastEntry);
|
|
1034
|
+
yield ctx.slots.register({
|
|
1035
|
+
name: "workspace.toast",
|
|
1036
|
+
key: "pinFailed",
|
|
1037
|
+
locale: "workspace",
|
|
1038
|
+
inject: plainToast((_notice, t) => ({
|
|
1039
|
+
text: t("toast.pinFailed"),
|
|
1040
|
+
tone: "warning"
|
|
1041
|
+
}))
|
|
1042
|
+
}, toastEntry);
|
|
1043
|
+
yield ctx.slots.register({
|
|
1044
|
+
name: "workspace.toast",
|
|
1045
|
+
key: "unpinFailed",
|
|
1046
|
+
locale: "workspace",
|
|
1047
|
+
inject: plainToast((_notice, t) => ({
|
|
1048
|
+
text: t("toast.unpinFailed"),
|
|
1049
|
+
tone: "warning"
|
|
1050
|
+
}))
|
|
1051
|
+
}, toastEntry);
|
|
1052
|
+
yield ctx.slots.register({
|
|
1053
|
+
name: "workspace.toast",
|
|
1054
|
+
key: "archivedNotOpenable",
|
|
1055
|
+
locale: "workspace",
|
|
1056
|
+
inject: plainToast((_notice, t) => ({
|
|
1057
|
+
text: t("toast.archivedNotOpenable"),
|
|
1058
|
+
tone: "warning"
|
|
1059
|
+
}))
|
|
1060
|
+
}, toastEntry);
|
|
1061
|
+
yield ctx.slots.register({
|
|
1062
|
+
name: "workspace.toast",
|
|
1063
|
+
key: "defaultWorkspaceFailed",
|
|
1064
|
+
locale: "workspace",
|
|
1065
|
+
inject: plainToast((_notice, t) => ({
|
|
1066
|
+
text: t("defaultWorkspace.failed"),
|
|
1067
|
+
tone: "warning"
|
|
1068
|
+
}))
|
|
1069
|
+
}, toastEntry);
|
|
1070
|
+
yield ctx.slots.register({
|
|
1071
|
+
name: "workspace.toast",
|
|
1072
|
+
key: "shortcutForkFailed",
|
|
1073
|
+
locale: "workspace",
|
|
1074
|
+
inject: plainToast((notice, t) => ({
|
|
1075
|
+
text: t(notice.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
|
|
1076
|
+
tone: "plain"
|
|
1077
|
+
}))
|
|
1078
|
+
}, toastEntry);
|
|
1079
|
+
yield ctx.slots.register({
|
|
1080
|
+
name: "workspace.toast",
|
|
1081
|
+
key: "createFailed",
|
|
1082
|
+
locale: "workspace",
|
|
1083
|
+
inject: plainToast((notice, t) => ({
|
|
1084
|
+
text: t("toast.createFailed", { message: notice.message }),
|
|
1085
|
+
tone: "warning",
|
|
1086
|
+
holdMs: 6e3
|
|
1087
|
+
}))
|
|
1088
|
+
}, toastEntry);
|
|
1089
|
+
yield ctx.slots.register({
|
|
1090
|
+
name: "workspace.toast",
|
|
1091
|
+
key: "forkRequiresOpen",
|
|
1092
|
+
locale: "workspace",
|
|
1093
|
+
inject: plainToast((notice, t, dismiss) => ({
|
|
1094
|
+
text: t("toast.forkRequiresOpen"),
|
|
1095
|
+
tone: "warning",
|
|
1096
|
+
holdMs: 6e3,
|
|
1097
|
+
actions: [{
|
|
1098
|
+
label: t("toast.openForkSource"),
|
|
1099
|
+
onClick: () => {
|
|
1100
|
+
dismiss();
|
|
1101
|
+
actions.openForkSource(notice.sessionId);
|
|
1102
|
+
}
|
|
1103
|
+
}]
|
|
1104
|
+
}))
|
|
1105
|
+
}, toastEntry);
|
|
1106
|
+
yield ctx.slots.register({
|
|
1107
|
+
name: "workspace.toast",
|
|
1108
|
+
key: "archived",
|
|
1109
|
+
locale: "workspace",
|
|
1110
|
+
store: viewStore,
|
|
1111
|
+
inject: () => ({
|
|
1112
|
+
display: (notice, t, dismiss) => ({
|
|
1113
|
+
text: t("toast.archived"),
|
|
1114
|
+
tone: "success",
|
|
1115
|
+
holdMs: 6e3,
|
|
1116
|
+
actions: [{
|
|
1117
|
+
label: t("toast.archivedUndo"),
|
|
1118
|
+
onClick: () => {
|
|
1119
|
+
dismiss();
|
|
1120
|
+
actions.undoArchive(notice.sessionId);
|
|
1121
|
+
}
|
|
1122
|
+
}]
|
|
1123
|
+
}),
|
|
1124
|
+
filterAction: archiveFilterAction
|
|
1125
|
+
})
|
|
1126
|
+
}, archiveToastEntry);
|
|
1127
|
+
yield ctx.slots.register({
|
|
1128
|
+
name: "workspace.toast",
|
|
1129
|
+
key: "stoppedAndArchived",
|
|
1130
|
+
locale: "workspace",
|
|
1131
|
+
store: viewStore,
|
|
1132
|
+
inject: () => ({
|
|
1133
|
+
display: (notice, t, dismiss) => ({
|
|
1134
|
+
text: t("toast.stoppedAndArchived"),
|
|
1135
|
+
tone: "success",
|
|
1136
|
+
holdMs: 6e3,
|
|
1137
|
+
actions: [{
|
|
1138
|
+
label: t("toast.archivedUndo"),
|
|
1139
|
+
onClick: () => {
|
|
1140
|
+
dismiss();
|
|
1141
|
+
actions.undoArchive(notice.sessionId);
|
|
1142
|
+
}
|
|
1143
|
+
}]
|
|
1144
|
+
}),
|
|
1145
|
+
filterAction: archiveFilterAction
|
|
1146
|
+
})
|
|
1147
|
+
}, archiveToastEntry);
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
//#endregion
|
|
1151
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-values@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
|
|
1152
|
+
/**
|
|
1153
|
+
* Lazily scanned view of one JSON object's top-level fields, built from text
|
|
1154
|
+
* that may still be streaming or from an already parsed object. Nothing is
|
|
1155
|
+
* scanned until a reader asks; the view remembers every question it answered
|
|
1156
|
+
* and reports changed answers when the owner refreshes for publication.
|
|
1157
|
+
* Used for model tool-call arguments: a row reads the fields it
|
|
1158
|
+
* cares about at whatever granularity it displays, at every stage of the call.
|
|
1159
|
+
* @module @deepseek-ai/dsh-util-values/src/partial-json
|
|
1160
|
+
*/
|
|
1161
|
+
const SIMPLE_ESCAPES = {
|
|
1162
|
+
"\"": "\"",
|
|
1163
|
+
"\\": "\\",
|
|
1164
|
+
"/": "/",
|
|
1165
|
+
b: "\b",
|
|
1166
|
+
f: "\f",
|
|
1167
|
+
n: "\n",
|
|
1168
|
+
r: "\r",
|
|
1169
|
+
t: " "
|
|
1170
|
+
};
|
|
1171
|
+
const CONTENT_ESCAPE = /[\\\u0000-\u001f]/u;
|
|
1172
|
+
function isWhitespace(c) {
|
|
1173
|
+
return c === " " || c === "\n" || c === "\r" || c === " ";
|
|
1174
|
+
}
|
|
1175
|
+
function isHex(c) {
|
|
1176
|
+
return c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F";
|
|
1177
|
+
}
|
|
1178
|
+
(class PartialArguments {
|
|
1179
|
+
/** The view of a call with no arguments available. */
|
|
1180
|
+
static EMPTY = PartialArguments.fromObject({});
|
|
1181
|
+
/**
|
|
1182
|
+
* View finished argument text without scanning it until a reader asks.
|
|
1183
|
+
* @param text - the complete argument JSON text.
|
|
1184
|
+
* @returns a sealed view.
|
|
1185
|
+
*/
|
|
1186
|
+
static fromText(text) {
|
|
1187
|
+
const view = new PartialArguments();
|
|
1188
|
+
view.append(text);
|
|
1189
|
+
view.sealed = true;
|
|
1190
|
+
return view;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* View an already parsed argument payload, such as a PTC dispatch object.
|
|
1194
|
+
* @param value - the parsed argument value.
|
|
1195
|
+
* @returns a sealed view; a non-object payload has no fields.
|
|
1196
|
+
*/
|
|
1197
|
+
static fromObject(value) {
|
|
1198
|
+
const view = new PartialArguments();
|
|
1199
|
+
view.object = typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
1200
|
+
view.sealed = true;
|
|
1201
|
+
return view;
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* The source: text so far or a parsed object, plus whether it can still grow.
|
|
1205
|
+
* These are the only enumerable fields, so two views over the same source
|
|
1206
|
+
* compare equal structurally however far each has been read.
|
|
1207
|
+
*/
|
|
1208
|
+
chunks = [];
|
|
1209
|
+
object;
|
|
1210
|
+
sealed = false;
|
|
1211
|
+
#ends = [];
|
|
1212
|
+
#size = 0;
|
|
1213
|
+
#consumed = 0;
|
|
1214
|
+
#mode = "root";
|
|
1215
|
+
#escape = false;
|
|
1216
|
+
#keyStart = 0;
|
|
1217
|
+
#keyEscaped = false;
|
|
1218
|
+
#key = "";
|
|
1219
|
+
#current = null;
|
|
1220
|
+
#nestedEnds = [];
|
|
1221
|
+
#nestedInString = false;
|
|
1222
|
+
#invalidAt;
|
|
1223
|
+
#invalidValue = false;
|
|
1224
|
+
#entries = /* @__PURE__ */ new Map();
|
|
1225
|
+
#order = [];
|
|
1226
|
+
#reads = /* @__PURE__ */ new Map();
|
|
1227
|
+
/** Whether this view rejects further appends; does not scan text or register reads. */
|
|
1228
|
+
get isSealed() {
|
|
1229
|
+
return this.sealed;
|
|
1230
|
+
}
|
|
1231
|
+
/** Whether indexing or a content read found invalid JSON; unread value contents are not validated. */
|
|
1232
|
+
get invalid() {
|
|
1233
|
+
this.scan();
|
|
1234
|
+
return this.#mode === "invalid" || this.#invalidValue;
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Retain streamed argument text without scanning or comparing observed answers.
|
|
1238
|
+
* @param fragment - the text following every fragment appended before.
|
|
1239
|
+
*/
|
|
1240
|
+
append(fragment) {
|
|
1241
|
+
if (this.sealed) throw new Error("PartialArguments: cannot append to a sealed view");
|
|
1242
|
+
if (fragment.length === 0) return;
|
|
1243
|
+
this.chunks.push(fragment);
|
|
1244
|
+
this.#size += fragment.length;
|
|
1245
|
+
this.#ends.push(this.#size);
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Reconcile a streamed prefix with authoritative complete text without joining the fragments.
|
|
1249
|
+
* @param text - the final argument text, which replaces missing or conflicting deltas.
|
|
1250
|
+
* @returns this view sealed with its caches retained when every character matches; otherwise a new sealed view.
|
|
1251
|
+
*/
|
|
1252
|
+
settle(text) {
|
|
1253
|
+
if (this.object !== void 0 || text.length !== this.#size) return PartialArguments.fromText(text);
|
|
1254
|
+
let offset = 0;
|
|
1255
|
+
for (const chunk of this.chunks) {
|
|
1256
|
+
if (!text.startsWith(chunk, offset)) return PartialArguments.fromText(text);
|
|
1257
|
+
offset += chunk.length;
|
|
1258
|
+
}
|
|
1259
|
+
this.chunks = text.length === 0 ? [] : [text];
|
|
1260
|
+
this.#ends = text.length === 0 ? [] : [text.length];
|
|
1261
|
+
this.sealed = true;
|
|
1262
|
+
return this;
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Compare observed answers and advance their publication baseline. Unread views remain unscanned.
|
|
1266
|
+
* @returns whether any observed answer changed since its first read or the preceding refresh.
|
|
1267
|
+
*/
|
|
1268
|
+
refresh() {
|
|
1269
|
+
if (this.#reads.size === 0) return false;
|
|
1270
|
+
this.scan();
|
|
1271
|
+
let changed = false;
|
|
1272
|
+
let completions = false;
|
|
1273
|
+
for (const read of this.#reads.values()) {
|
|
1274
|
+
if (read.completion) {
|
|
1275
|
+
completions = true;
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
changed = this.refreshRead(read) || changed;
|
|
1279
|
+
}
|
|
1280
|
+
if (completions) {
|
|
1281
|
+
for (const read of this.#reads.values()) if (read.completion) changed = this.refreshRead(read) || changed;
|
|
1282
|
+
}
|
|
1283
|
+
if (this.sealed) this.#reads.clear();
|
|
1284
|
+
return changed;
|
|
1285
|
+
}
|
|
1286
|
+
refreshRead(read) {
|
|
1287
|
+
const now = read.answer();
|
|
1288
|
+
if (Object.is(now, read.last)) return false;
|
|
1289
|
+
read.last = now;
|
|
1290
|
+
return true;
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Check whether no further fields can arrive.
|
|
1294
|
+
* @returns whether the outer object closed, indexing failed, or the view is sealed; unread values are not validated.
|
|
1295
|
+
*/
|
|
1296
|
+
closed() {
|
|
1297
|
+
return this.remember("closed", "", () => this.closedNow());
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* List discovered fields in first-appearance order.
|
|
1301
|
+
* @returns top-level keys seen so far, in first-appearance order.
|
|
1302
|
+
*/
|
|
1303
|
+
keys() {
|
|
1304
|
+
return this.remember("keys", "", () => this.keysNow(), (keys) => keys.length);
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Check whether a top-level field has appeared.
|
|
1308
|
+
* @param key - argument name.
|
|
1309
|
+
* @returns whether the field has appeared (a string opened or another value began).
|
|
1310
|
+
*/
|
|
1311
|
+
has(key) {
|
|
1312
|
+
return this.remember("has", key, () => this.hasNow(key));
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Check whether a field's closing delimiter has arrived, without validating its contents.
|
|
1316
|
+
* @param key - argument name.
|
|
1317
|
+
* @returns whether its delimiter arrived and no content reader has reported an error for this value.
|
|
1318
|
+
*/
|
|
1319
|
+
complete(key) {
|
|
1320
|
+
return this.remember("complete", key, () => this.completeNow(key));
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Read string length without materializing its text.
|
|
1324
|
+
* @param key - argument name.
|
|
1325
|
+
* @param options - change granularity for a streaming string.
|
|
1326
|
+
* @returns decoded UTF-16 length of the string field so far; undefined when absent or not a string.
|
|
1327
|
+
*/
|
|
1328
|
+
stringLength(key, options) {
|
|
1329
|
+
const step = Math.max(1, Math.floor(options?.step ?? 1));
|
|
1330
|
+
const offset = options?.offset ?? 0;
|
|
1331
|
+
return this.remember(`length:${step}:${offset}`, key, () => this.lengthNow(key), (length) => length === void 0 ? void 0 : Math.ceil((length + offset) / step));
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Check a string against a decoded UTF-16 length limit without materializing it.
|
|
1335
|
+
* @param key - argument name.
|
|
1336
|
+
* @param maxLength - decoded UTF-16 limit, floored to at least zero.
|
|
1337
|
+
* @returns whether the string is longer than the limit; false when absent or not a string.
|
|
1338
|
+
*/
|
|
1339
|
+
stringExceeds(key, maxLength) {
|
|
1340
|
+
const limit = Math.max(0, Math.floor(maxLength));
|
|
1341
|
+
return this.remember(`exceeds:${limit}`, key, () => (this.lengthNow(key, limit + 1) ?? 0) > limit);
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Read a decoded string, including a streaming prefix.
|
|
1345
|
+
* @param key - argument name.
|
|
1346
|
+
* @returns the string field's decoded text so far; undefined when absent or not a string.
|
|
1347
|
+
*/
|
|
1348
|
+
text(key) {
|
|
1349
|
+
return this.remember("text", key, () => this.textNow(key));
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Read at most the first decoded UTF-16 units of a string.
|
|
1353
|
+
* @param key - argument name.
|
|
1354
|
+
* @param maxLength - maximum decoded UTF-16 length, floored to at least one.
|
|
1355
|
+
* @returns the bounded string prefix; undefined when absent or not a string.
|
|
1356
|
+
*/
|
|
1357
|
+
textPrefix(key, maxLength) {
|
|
1358
|
+
const limit = Math.max(1, Math.floor(maxLength));
|
|
1359
|
+
return this.remember(`prefix:${limit}`, key, () => this.textPrefixNow(key, limit));
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Read a completed non-string argument.
|
|
1363
|
+
* @param key - argument name.
|
|
1364
|
+
* @returns the parsed non-string value once it closed; undefined while open, absent, or a string.
|
|
1365
|
+
*/
|
|
1366
|
+
value(key) {
|
|
1367
|
+
return this.remember("value", key, () => this.valueNow(key));
|
|
1368
|
+
}
|
|
1369
|
+
/** Answer a question and, on a streaming view, remember it for change detection. */
|
|
1370
|
+
remember(kind, key, read, comparison) {
|
|
1371
|
+
this.scan();
|
|
1372
|
+
const result = read();
|
|
1373
|
+
if (!this.sealed) {
|
|
1374
|
+
const id = `${kind}/${key}`;
|
|
1375
|
+
if (!this.#reads.has(id)) this.#reads.set(id, {
|
|
1376
|
+
completion: kind === "complete",
|
|
1377
|
+
answer: comparison === void 0 ? read : () => comparison(read()),
|
|
1378
|
+
last: comparison === void 0 ? result : comparison(result)
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
return result;
|
|
1382
|
+
}
|
|
1383
|
+
closedNow() {
|
|
1384
|
+
return this.sealed || this.#mode === "closed" || this.#mode === "invalid";
|
|
1385
|
+
}
|
|
1386
|
+
keysNow() {
|
|
1387
|
+
return this.object === void 0 ? this.#order : Object.keys(this.object);
|
|
1388
|
+
}
|
|
1389
|
+
hasNow(key) {
|
|
1390
|
+
return this.object === void 0 ? this.#entries.has(key) : Object.hasOwn(this.object, key);
|
|
1391
|
+
}
|
|
1392
|
+
completeNow(key) {
|
|
1393
|
+
if (this.object !== void 0) return Object.hasOwn(this.object, key);
|
|
1394
|
+
const entry = this.#entries.get(key);
|
|
1395
|
+
return entry !== void 0 && entry.end >= 0 && (entry.kind === "string" ? entry.invalidAt === void 0 : !entry.invalid);
|
|
1396
|
+
}
|
|
1397
|
+
lengthNow(key, limit = Number.POSITIVE_INFINITY) {
|
|
1398
|
+
if (this.object !== void 0) {
|
|
1399
|
+
const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
|
|
1400
|
+
return typeof field === "string" ? field.length : void 0;
|
|
1401
|
+
}
|
|
1402
|
+
const entry = this.#entries.get(key);
|
|
1403
|
+
if (entry?.kind !== "string") return void 0;
|
|
1404
|
+
if (entry.text !== void 0 && entry.text.at === entry.end) return entry.text.length;
|
|
1405
|
+
const read = entry.length ??= {
|
|
1406
|
+
at: entry.start,
|
|
1407
|
+
length: 0,
|
|
1408
|
+
text: ""
|
|
1409
|
+
};
|
|
1410
|
+
this.readString(entry, read, limit, false);
|
|
1411
|
+
return read.length;
|
|
1412
|
+
}
|
|
1413
|
+
textNow(key) {
|
|
1414
|
+
if (this.object !== void 0) {
|
|
1415
|
+
const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
|
|
1416
|
+
return typeof field === "string" ? field : void 0;
|
|
1417
|
+
}
|
|
1418
|
+
const entry = this.#entries.get(key);
|
|
1419
|
+
if (entry?.kind !== "string") return void 0;
|
|
1420
|
+
if (entry.text === void 0 && entry.end >= 0 && entry.needsDecoding && entry.invalidAt === void 0) {
|
|
1421
|
+
let text;
|
|
1422
|
+
try {
|
|
1423
|
+
text = JSON.parse(`"${this.slice(entry.start, entry.end)}"`);
|
|
1424
|
+
} catch (_error) {}
|
|
1425
|
+
if (text !== void 0) entry.text = {
|
|
1426
|
+
at: entry.end,
|
|
1427
|
+
length: text.length,
|
|
1428
|
+
text
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
const read = entry.text ??= {
|
|
1432
|
+
at: entry.start,
|
|
1433
|
+
length: 0,
|
|
1434
|
+
text: ""
|
|
1435
|
+
};
|
|
1436
|
+
this.readString(entry, read, Number.POSITIVE_INFINITY, true);
|
|
1437
|
+
return read.text;
|
|
1438
|
+
}
|
|
1439
|
+
textPrefixNow(key, maxLength) {
|
|
1440
|
+
if (this.object !== void 0) {
|
|
1441
|
+
const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
|
|
1442
|
+
return typeof field === "string" ? field.slice(0, maxLength) : void 0;
|
|
1443
|
+
}
|
|
1444
|
+
const entry = this.#entries.get(key);
|
|
1445
|
+
if (entry?.kind !== "string") return void 0;
|
|
1446
|
+
const prefixes = entry.prefixes ??= /* @__PURE__ */ new Map();
|
|
1447
|
+
let read = prefixes.get(maxLength);
|
|
1448
|
+
if (read === void 0) {
|
|
1449
|
+
read = {
|
|
1450
|
+
at: entry.start,
|
|
1451
|
+
length: 0,
|
|
1452
|
+
text: ""
|
|
1453
|
+
};
|
|
1454
|
+
prefixes.set(maxLength, read);
|
|
1455
|
+
}
|
|
1456
|
+
this.readString(entry, read, maxLength, true);
|
|
1457
|
+
return read.text;
|
|
1458
|
+
}
|
|
1459
|
+
valueNow(key) {
|
|
1460
|
+
if (this.object !== void 0) {
|
|
1461
|
+
if (!Object.hasOwn(this.object, key)) return void 0;
|
|
1462
|
+
const field = this.object[key];
|
|
1463
|
+
return typeof field === "string" ? void 0 : field;
|
|
1464
|
+
}
|
|
1465
|
+
const entry = this.#entries.get(key);
|
|
1466
|
+
if (entry?.kind !== "value" || entry.end < 0 || entry.invalid) return void 0;
|
|
1467
|
+
if (entry.parsed === void 0) try {
|
|
1468
|
+
entry.parsed = JSON.parse(this.slice(entry.start, entry.end));
|
|
1469
|
+
} catch (_error) {
|
|
1470
|
+
entry.invalid = true;
|
|
1471
|
+
this.#invalidValue = true;
|
|
1472
|
+
}
|
|
1473
|
+
return entry.parsed;
|
|
1474
|
+
}
|
|
1475
|
+
chunkAt(at) {
|
|
1476
|
+
let low = 0;
|
|
1477
|
+
let high = this.#ends.length;
|
|
1478
|
+
while (low < high) {
|
|
1479
|
+
const mid = low + high >>> 1;
|
|
1480
|
+
if (this.#ends[mid] <= at) low = mid + 1;
|
|
1481
|
+
else high = mid;
|
|
1482
|
+
}
|
|
1483
|
+
return low;
|
|
1484
|
+
}
|
|
1485
|
+
/** Materialize only a requested range, never the cumulative source. */
|
|
1486
|
+
slice(start, end) {
|
|
1487
|
+
if (start >= end) return "";
|
|
1488
|
+
const first = this.chunkAt(start);
|
|
1489
|
+
const last = this.chunkAt(end - 1);
|
|
1490
|
+
const base = first === 0 ? 0 : this.#ends[first - 1];
|
|
1491
|
+
if (first === last) return this.chunks[first].slice(start - base, end - base);
|
|
1492
|
+
const parts = [this.chunks[first].slice(start - base)];
|
|
1493
|
+
for (let i = first + 1; i < last; i++) parts.push(this.chunks[i]);
|
|
1494
|
+
parts.push(this.chunks[last].slice(0, end - this.#ends[last - 1]));
|
|
1495
|
+
return parts.join("");
|
|
1496
|
+
}
|
|
1497
|
+
readString(entry, read, limit, materialize) {
|
|
1498
|
+
const end = Math.min(entry.end < 0 ? this.#consumed : entry.end, entry.invalidAt ?? Number.POSITIVE_INFINITY, this.#invalidAt ?? Number.POSITIVE_INFINITY);
|
|
1499
|
+
if (!entry.needsDecoding) {
|
|
1500
|
+
const length = Math.min(end - read.at, limit - read.length);
|
|
1501
|
+
if (length <= 0) return;
|
|
1502
|
+
if (materialize) read.text += this.slice(read.at, read.at + length);
|
|
1503
|
+
read.at += length;
|
|
1504
|
+
read.length += length;
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
let chunkIndex = this.chunkAt(read.at);
|
|
1508
|
+
while (read.at < end && read.length < limit) {
|
|
1509
|
+
const base = chunkIndex === 0 ? 0 : this.#ends[chunkIndex - 1];
|
|
1510
|
+
const chunk = this.chunks[chunkIndex];
|
|
1511
|
+
const remaining = chunk.slice(read.at - base, Math.min(chunk.length, end - base));
|
|
1512
|
+
const boundary = remaining.search(CONTENT_ESCAPE);
|
|
1513
|
+
const length = Math.min(boundary < 0 ? remaining.length : boundary, limit - read.length);
|
|
1514
|
+
if (length > 0) {
|
|
1515
|
+
if (materialize) read.text += remaining.slice(0, length);
|
|
1516
|
+
read.at += length;
|
|
1517
|
+
read.length += length;
|
|
1518
|
+
if (read.at === base + chunk.length) chunkIndex++;
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
const type = remaining.length > 1 ? remaining[1] : read.at + 1 < end ? this.chunks[chunkIndex + 1][0] : void 0;
|
|
1522
|
+
let decoded;
|
|
1523
|
+
let width = 2;
|
|
1524
|
+
if (remaining[0] === "\\" && type === void 0 && entry.end < 0) return;
|
|
1525
|
+
if (remaining[0] === "\\" && type === "u") {
|
|
1526
|
+
const hex = this.slice(read.at + 2, Math.min(end, read.at + 6));
|
|
1527
|
+
let valid = true;
|
|
1528
|
+
for (let i = 0; i < hex.length; i++) if (!isHex(hex[i])) valid = false;
|
|
1529
|
+
if (valid) {
|
|
1530
|
+
if (hex.length < 4 && entry.end < 0) return;
|
|
1531
|
+
if (hex.length === 4) decoded = String.fromCharCode(Number.parseInt(hex, 16));
|
|
1532
|
+
}
|
|
1533
|
+
width = 6;
|
|
1534
|
+
} else if (remaining[0] === "\\" && type !== void 0) decoded = SIMPLE_ESCAPES[type];
|
|
1535
|
+
if (decoded === void 0) {
|
|
1536
|
+
entry.invalidAt = read.at;
|
|
1537
|
+
this.#invalidValue = true;
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
if (materialize) read.text += decoded;
|
|
1541
|
+
read.length++;
|
|
1542
|
+
read.at += width;
|
|
1543
|
+
while (chunkIndex < this.chunks.length && read.at >= this.#ends[chunkIndex]) chunkIndex++;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
/** Locate new field ranges without decoding or parsing their contents. */
|
|
1547
|
+
scan() {
|
|
1548
|
+
if (this.object !== void 0 || this.#consumed === this.#size) return;
|
|
1549
|
+
for (let i = this.chunkAt(this.#consumed); i < this.chunks.length && this.#invalidAt === void 0; i++) {
|
|
1550
|
+
const pending = this.chunks[i];
|
|
1551
|
+
const base = i === 0 ? 0 : this.#ends[i - 1];
|
|
1552
|
+
for (let index = this.#consumed - base; index < pending.length && this.#mode !== "invalid"; index++) {
|
|
1553
|
+
if (this.#mode === "string" || this.#mode === "nested" && this.#nestedInString) {
|
|
1554
|
+
const end = this.stringBoundary(pending, index);
|
|
1555
|
+
this.#consumed += end - index;
|
|
1556
|
+
index = end;
|
|
1557
|
+
if (index === pending.length) break;
|
|
1558
|
+
}
|
|
1559
|
+
this.step(pending[index], this.#consumed);
|
|
1560
|
+
this.#consumed++;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
/** Only raw quotes and their preceding backslash runs can terminate a string. */
|
|
1565
|
+
stringBoundary(fragment, start) {
|
|
1566
|
+
let at = start;
|
|
1567
|
+
while (true) {
|
|
1568
|
+
const quote = fragment.indexOf("\"", at);
|
|
1569
|
+
const end = quote < 0 ? fragment.length : quote;
|
|
1570
|
+
if (this.#mode === "string") {
|
|
1571
|
+
const entry = this.#current;
|
|
1572
|
+
if (!entry.needsDecoding && CONTENT_ESCAPE.test(fragment.slice(at, end))) entry.needsDecoding = true;
|
|
1573
|
+
}
|
|
1574
|
+
let slashStart = end;
|
|
1575
|
+
while (slashStart > at && fragment[slashStart - 1] === "\\") slashStart--;
|
|
1576
|
+
const escaped = (end - slashStart) % 2 === 1 !== (slashStart === at && this.#escape);
|
|
1577
|
+
this.#escape = quote < 0 && escaped;
|
|
1578
|
+
if (quote < 0 || !escaped) return end;
|
|
1579
|
+
at = quote + 1;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
step(c, at) {
|
|
1583
|
+
switch (this.#mode) {
|
|
1584
|
+
case "root":
|
|
1585
|
+
if (isWhitespace(c)) return;
|
|
1586
|
+
if (c === "{") {
|
|
1587
|
+
this.#mode = "key-or-end";
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
this.fail();
|
|
1591
|
+
return;
|
|
1592
|
+
case "key-or-end":
|
|
1593
|
+
if (isWhitespace(c)) return;
|
|
1594
|
+
if (c === "}") {
|
|
1595
|
+
this.#mode = "closed";
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
if (c === "\"") {
|
|
1599
|
+
this.beginKey(at);
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
this.fail();
|
|
1603
|
+
return;
|
|
1604
|
+
case "key-only":
|
|
1605
|
+
if (isWhitespace(c)) return;
|
|
1606
|
+
if (c === "\"") {
|
|
1607
|
+
this.beginKey(at);
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
this.fail();
|
|
1611
|
+
return;
|
|
1612
|
+
case "key":
|
|
1613
|
+
this.stepKey(c, at);
|
|
1614
|
+
return;
|
|
1615
|
+
case "colon":
|
|
1616
|
+
if (isWhitespace(c)) return;
|
|
1617
|
+
if (c === ":") {
|
|
1618
|
+
this.#mode = "value";
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
this.fail();
|
|
1622
|
+
return;
|
|
1623
|
+
case "value":
|
|
1624
|
+
this.beginValue(c, at);
|
|
1625
|
+
return;
|
|
1626
|
+
case "string": {
|
|
1627
|
+
const entry = this.#current;
|
|
1628
|
+
entry.end = at;
|
|
1629
|
+
this.#current = null;
|
|
1630
|
+
this.#mode = "comma-or-end";
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
case "scalar":
|
|
1634
|
+
this.stepScalar(c, at);
|
|
1635
|
+
return;
|
|
1636
|
+
case "nested":
|
|
1637
|
+
this.stepNested(c, at);
|
|
1638
|
+
return;
|
|
1639
|
+
case "comma-or-end":
|
|
1640
|
+
if (isWhitespace(c)) return;
|
|
1641
|
+
if (c === ",") {
|
|
1642
|
+
this.#mode = "key-only";
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (c === "}") {
|
|
1646
|
+
this.#mode = "closed";
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
this.fail();
|
|
1650
|
+
return;
|
|
1651
|
+
case "closed":
|
|
1652
|
+
if (isWhitespace(c)) return;
|
|
1653
|
+
this.fail();
|
|
1654
|
+
return;
|
|
1655
|
+
/* v8 ignore next 2 -- scan() stops stepping once the view is invalid. */
|
|
1656
|
+
case "invalid": return;
|
|
1657
|
+
/* v8 ignore next 2 -- Every scanner mode has a handler above. */
|
|
1658
|
+
default: assertNever$1(this.#mode);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
fail() {
|
|
1662
|
+
this.#invalidAt = this.#consumed;
|
|
1663
|
+
this.#mode = "invalid";
|
|
1664
|
+
this.#current = null;
|
|
1665
|
+
}
|
|
1666
|
+
beginKey(at) {
|
|
1667
|
+
this.#mode = "key";
|
|
1668
|
+
this.#keyStart = at + 1;
|
|
1669
|
+
this.#keyEscaped = false;
|
|
1670
|
+
this.#escape = false;
|
|
1671
|
+
}
|
|
1672
|
+
stepKey(c, at) {
|
|
1673
|
+
if (c < " ") {
|
|
1674
|
+
this.fail();
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
if (this.#escape) {
|
|
1678
|
+
this.#escape = false;
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
if (c === "\\") {
|
|
1682
|
+
this.#escape = true;
|
|
1683
|
+
this.#keyEscaped = true;
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
if (c !== "\"") return;
|
|
1687
|
+
const raw = this.slice(this.#keyStart, at);
|
|
1688
|
+
if (this.#keyEscaped) try {
|
|
1689
|
+
this.#key = JSON.parse(`"${raw}"`);
|
|
1690
|
+
} catch (_error) {
|
|
1691
|
+
this.fail();
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
else this.#key = raw;
|
|
1695
|
+
this.#mode = "colon";
|
|
1696
|
+
}
|
|
1697
|
+
open(entry) {
|
|
1698
|
+
if (!this.#entries.has(this.#key)) this.#order.push(this.#key);
|
|
1699
|
+
this.#entries.set(this.#key, entry);
|
|
1700
|
+
this.#current = entry;
|
|
1701
|
+
}
|
|
1702
|
+
beginValue(c, at) {
|
|
1703
|
+
if (isWhitespace(c)) return;
|
|
1704
|
+
if (c === "\"") {
|
|
1705
|
+
this.open({
|
|
1706
|
+
kind: "string",
|
|
1707
|
+
start: at + 1,
|
|
1708
|
+
end: -1,
|
|
1709
|
+
needsDecoding: false,
|
|
1710
|
+
invalidAt: void 0,
|
|
1711
|
+
length: void 0,
|
|
1712
|
+
text: void 0,
|
|
1713
|
+
prefixes: void 0
|
|
1714
|
+
});
|
|
1715
|
+
this.#escape = false;
|
|
1716
|
+
this.#mode = "string";
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
if (c === "}" || c === "," || c === ":" || c === "]") {
|
|
1720
|
+
this.fail();
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
this.open({
|
|
1724
|
+
kind: "value",
|
|
1725
|
+
start: at,
|
|
1726
|
+
end: -1,
|
|
1727
|
+
parsed: void 0,
|
|
1728
|
+
invalid: false
|
|
1729
|
+
});
|
|
1730
|
+
if (c === "{" || c === "[") {
|
|
1731
|
+
this.#mode = "nested";
|
|
1732
|
+
this.#nestedEnds = [c === "{" ? "}" : "]"];
|
|
1733
|
+
this.#nestedInString = false;
|
|
1734
|
+
this.#escape = false;
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
this.#mode = "scalar";
|
|
1738
|
+
}
|
|
1739
|
+
stepScalar(c, at) {
|
|
1740
|
+
if (c !== "," && c !== "}" && !isWhitespace(c)) return;
|
|
1741
|
+
this.closeValue(at);
|
|
1742
|
+
this.#mode = c === "," ? "key-only" : c === "}" ? "closed" : "comma-or-end";
|
|
1743
|
+
}
|
|
1744
|
+
stepNested(c, at) {
|
|
1745
|
+
if (this.#nestedInString) {
|
|
1746
|
+
this.#nestedInString = false;
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
if (c === "\"") {
|
|
1750
|
+
this.#nestedInString = true;
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
if (c === "{" || c === "[") {
|
|
1754
|
+
this.#nestedEnds.push(c === "{" ? "}" : "]");
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (c === "}" || c === "]") {
|
|
1758
|
+
if (this.#nestedEnds.pop() !== c) {
|
|
1759
|
+
this.fail();
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
if (this.#nestedEnds.length === 0) {
|
|
1763
|
+
this.closeValue(at + 1);
|
|
1764
|
+
this.#mode = "comma-or-end";
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
closeValue(end) {
|
|
1769
|
+
const entry = this.#current;
|
|
1770
|
+
entry.end = end;
|
|
1771
|
+
this.#current = null;
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
906
1774
|
/** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
|
|
907
1775
|
/**
|
|
908
1776
|
* Mark an unreachable closed-union branch.
|
|
@@ -915,7 +1783,7 @@ window.__ModuleLoader__.load({
|
|
|
915
1783
|
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
916
1784
|
}
|
|
917
1785
|
//#endregion
|
|
918
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.2.
|
|
1786
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
|
|
919
1787
|
/**
|
|
920
1788
|
* Browser-safe Workspace path and display helpers.
|
|
921
1789
|
* @module @deepseek-ai/dsh-util-workspace-path
|
|
@@ -959,6 +1827,12 @@ window.__ModuleLoader__.load({
|
|
|
959
1827
|
function owningGroupKey(workspaces, sessionId) {
|
|
960
1828
|
return workspaces.find((workspace) => workspace.sessionIds.includes(sessionId))?.workspaceId ?? "";
|
|
961
1829
|
}
|
|
1830
|
+
/** 宿主认的 kind 集合(查表用)。 */
|
|
1831
|
+
const PENDING_INTERACTION_KIND_SET = /* @__PURE__ */ new Set([
|
|
1832
|
+
"approval",
|
|
1833
|
+
"plan-review",
|
|
1834
|
+
"question"
|
|
1835
|
+
]);
|
|
962
1836
|
function mainSessionId(list) {
|
|
963
1837
|
return Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
964
1838
|
}
|
|
@@ -1142,12 +2016,7 @@ window.__ModuleLoader__.load({
|
|
|
1142
2016
|
}
|
|
1143
2017
|
/** Keep navigation presentation independent from domain-owned interaction objects. */
|
|
1144
2018
|
function visiblePendingKind(kind) {
|
|
1145
|
-
|
|
1146
|
-
case "approval":
|
|
1147
|
-
case "plan-review":
|
|
1148
|
-
case "question": return kind;
|
|
1149
|
-
default: return;
|
|
1150
|
-
}
|
|
2019
|
+
return kind !== void 0 && PENDING_INTERACTION_KIND_SET.has(kind) ? kind : void 0;
|
|
1151
2020
|
}
|
|
1152
2021
|
function runningChildCount(list, parentId, statuses) {
|
|
1153
2022
|
return list.projectionsBySession[parentId]?.values.subagentCatalog?.reduce((count, child) => count + ((statuses.get(child.id)?.running ?? list.byId[child.id]?.running) === true ? 1 : 0), 0) ?? 0;
|
|
@@ -1490,46 +2359,90 @@ window.__ModuleLoader__.load({
|
|
|
1490
2359
|
};
|
|
1491
2360
|
}, "ui-workspace: Workspace navigation policy");
|
|
1492
2361
|
}
|
|
1493
|
-
|
|
2362
|
+
connectWorkspace(workspaceId, options = {}) {
|
|
2363
|
+
return this.connectBlank(workspaceId, options);
|
|
2364
|
+
}
|
|
2365
|
+
/**
|
|
2366
|
+
* @param reuse - preset for the reused or created blank; absent creates a fresh Session.
|
|
2367
|
+
*/
|
|
2368
|
+
async connectBlank(workspaceId, reuse) {
|
|
1494
2369
|
const workspace = this.workspaces.list.getSnapshot().items.find((item) => item.workspaceId === workspaceId);
|
|
1495
2370
|
if (workspace === void 0) throw new Error(`uiWorkspace.connectWorkspace: unknown workspace ${workspaceId}`);
|
|
2371
|
+
if (reuse === void 0) return this.sessions.create({ workspaceId });
|
|
1496
2372
|
const inflight = this.connecting.get(workspaceId);
|
|
1497
|
-
if (inflight !== void 0) return inflight;
|
|
1498
|
-
const attempt = this.
|
|
1499
|
-
this.connecting.delete(workspaceId);
|
|
2373
|
+
if (inflight !== void 0 && inflight.agentPreset === reuse.agentPreset) return inflight.attempt;
|
|
2374
|
+
const attempt = this.createInWorkspace(workspace, reuse).finally(() => {
|
|
2375
|
+
if (this.connecting.get(workspaceId)?.attempt === attempt) this.connecting.delete(workspaceId);
|
|
2376
|
+
});
|
|
2377
|
+
this.connecting.set(workspaceId, {
|
|
2378
|
+
agentPreset: reuse.agentPreset,
|
|
2379
|
+
attempt
|
|
1500
2380
|
});
|
|
1501
|
-
this.connecting.set(workspaceId, attempt);
|
|
1502
2381
|
return attempt;
|
|
1503
2382
|
}
|
|
1504
|
-
|
|
2383
|
+
createInWorkspace(workspace, options) {
|
|
1505
2384
|
const archived = this.workspaces.list.getSnapshot().archivedSessionIds;
|
|
1506
2385
|
const sessions = this.sessions.list.getSnapshot();
|
|
1507
|
-
|
|
2386
|
+
const blank = sessions.ids.find((id) => {
|
|
1508
2387
|
const summary = sessions.byId[id];
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
2388
|
+
return summary !== void 0 && summary.blank && summary.cwd === workspace.path && workspace.sessionIds.includes(id) && !archived.includes(id);
|
|
2389
|
+
});
|
|
2390
|
+
return this.createNamed({
|
|
2391
|
+
workspaceId: workspace.workspaceId,
|
|
2392
|
+
...blank === void 0 ? {} : { sessionId: blank },
|
|
2393
|
+
...options.agentPreset === void 0 ? {} : { agentPreset: options.agentPreset }
|
|
2394
|
+
});
|
|
1513
2395
|
}
|
|
1514
|
-
|
|
2396
|
+
/**
|
|
2397
|
+
* Create through the Host. A named blank the Host declined may still read as
|
|
2398
|
+
* blank from stale cached list metadata, which hides it from the Session tree;
|
|
2399
|
+
* its projections are reloaded so the row reflects its log.
|
|
2400
|
+
*/
|
|
2401
|
+
async createNamed(request) {
|
|
2402
|
+
const sessionId = await this.sessions.create(request);
|
|
2403
|
+
const named = request.sessionId;
|
|
2404
|
+
if (named !== void 0 && named !== sessionId) this.sessions.refreshProjections(named).catch((reason) => {
|
|
2405
|
+
console.warn("declined blank Session projection reload failed:", reason);
|
|
2406
|
+
});
|
|
2407
|
+
return sessionId;
|
|
2408
|
+
}
|
|
2409
|
+
/**
|
|
2410
|
+
* Reopen a saved blank Session under the preset its log recorded, or under
|
|
2411
|
+
* the Host default once that preset no longer activates. Listed values are
|
|
2412
|
+
* cached hints that can lag the log after an unclean Host exit, so the
|
|
2413
|
+
* Session's projections are reloaded before the preset is read; a Session
|
|
2414
|
+
* that still awaits a format migration reloads only those cached hints.
|
|
2415
|
+
*/
|
|
2416
|
+
async restoreBlank(workspaceId, sessionId) {
|
|
2417
|
+
await this.sessions.refreshProjections(sessionId);
|
|
2418
|
+
const recorded = this.sessions.list.getSnapshot().byId[sessionId]?.projectionValues?.agentPreset;
|
|
2419
|
+
const request = {
|
|
2420
|
+
workspaceId,
|
|
2421
|
+
sessionId
|
|
2422
|
+
};
|
|
2423
|
+
if (typeof recorded !== "string") return this.createNamed(request);
|
|
1515
2424
|
try {
|
|
1516
|
-
return await this.
|
|
1517
|
-
|
|
1518
|
-
|
|
2425
|
+
return await this.createNamed({
|
|
2426
|
+
...request,
|
|
2427
|
+
agentPreset: recorded
|
|
1519
2428
|
});
|
|
1520
2429
|
} catch (error) {
|
|
1521
|
-
|
|
1522
|
-
|
|
2430
|
+
const code = sessionCreateErrorOf(error)?.rpcError.code;
|
|
2431
|
+
if (code !== "agent-preset/not-found" && code !== "agent-preset/invalid") throw error;
|
|
2432
|
+
return this.createNamed(request);
|
|
1523
2433
|
}
|
|
1524
2434
|
}
|
|
1525
2435
|
openSession(target) {
|
|
1526
2436
|
this.replaceMain(target, this.lifetime.signal, "reveal");
|
|
1527
2437
|
}
|
|
1528
|
-
|
|
2438
|
+
openWorkspace(workspaceId, beforeOpen, options = {}) {
|
|
2439
|
+
return this.openWorkspaceSession(workspaceId, beforeOpen, options);
|
|
2440
|
+
}
|
|
2441
|
+
async openWorkspaceSession(workspaceId, beforeOpen, reuse) {
|
|
1529
2442
|
const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
|
|
1530
2443
|
let sessionId;
|
|
1531
2444
|
try {
|
|
1532
|
-
sessionId = await this.
|
|
2445
|
+
sessionId = await this.connectBlank(workspaceId, reuse);
|
|
1533
2446
|
} catch (error) {
|
|
1534
2447
|
if (!navigation.aborted) this.notify({
|
|
1535
2448
|
kind: "createFailed",
|
|
@@ -1540,14 +2453,18 @@ window.__ModuleLoader__.load({
|
|
|
1540
2453
|
if (navigation.aborted) return;
|
|
1541
2454
|
this.replaceMain(sessionId, navigation, "reveal", beforeOpen);
|
|
1542
2455
|
}
|
|
1543
|
-
|
|
2456
|
+
forkSession(sessionId, onCreated, options) {
|
|
1544
2457
|
return this.sessions.fork({
|
|
1545
2458
|
sessionId,
|
|
1546
2459
|
increaseTitle: true,
|
|
1547
|
-
...onCreated === void 0 ? {} : { onCreated }
|
|
2460
|
+
...onCreated === void 0 ? {} : { onCreated },
|
|
2461
|
+
...options?.allowMigration === void 0 ? {} : { allowMigration: options.allowMigration }
|
|
1548
2462
|
});
|
|
1549
2463
|
}
|
|
1550
|
-
startSession(workspaceId) {
|
|
2464
|
+
startSession(workspaceId, options) {
|
|
2465
|
+
const { agentPreset, ...draft } = options ?? {};
|
|
2466
|
+
const draftOptions = options === void 0 ? void 0 : draft;
|
|
2467
|
+
const initializeDraft = draftOptions !== void 0 && (draftOptions.prompt !== void 0 || draftOptions.clearPreviousDraft === true);
|
|
1551
2468
|
const workspace = this.workspaces.list.getSnapshot();
|
|
1552
2469
|
const sessions = this.sessions.list.getSnapshot();
|
|
1553
2470
|
const current = this.mainReference?.sessionId;
|
|
@@ -1555,13 +2472,34 @@ window.__ModuleLoader__.load({
|
|
|
1555
2472
|
const recent = workspace.phase === "ready" && sessions.phase === "ready" ? recentWorkspace(workspace.items, sessions.byId) : void 0;
|
|
1556
2473
|
const target = workspaceId ?? currentWorkspaceId ?? recent;
|
|
1557
2474
|
if (target === void 0) {
|
|
2475
|
+
if (initializeDraft) {
|
|
2476
|
+
this.notify({
|
|
2477
|
+
kind: "createFailed",
|
|
2478
|
+
message: this.ctx.locale.bind("workspace")("draft.workspaceRequired")
|
|
2479
|
+
});
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
1558
2482
|
this.clearMain();
|
|
1559
2483
|
return;
|
|
1560
2484
|
}
|
|
1561
|
-
this.
|
|
2485
|
+
(draftOptions === void 0 ? this.openWorkspaceSession(target, void 0, void 0) : this.openWorkspace(target, initializeDraft ? (sessionId) => {
|
|
2486
|
+
const binding = this.sessions.binding(sessionId) ?? this.draftPreparationFailed();
|
|
2487
|
+
this.prepareDraft(binding, draftOptions);
|
|
2488
|
+
} : void 0, agentPreset === void 0 ? {} : { agentPreset })).catch((reason) => {
|
|
1562
2489
|
console.warn("new session failed:", reason);
|
|
1563
2490
|
});
|
|
1564
2491
|
}
|
|
2492
|
+
prepareDraft(binding, options) {
|
|
2493
|
+
if ((this.ctx.get("conversation") ?? this.draftPreparationFailed()).input.requestDraftInitialization(binding, options) === "blocked") this.draftPreparationFailed();
|
|
2494
|
+
}
|
|
2495
|
+
draftPreparationFailed() {
|
|
2496
|
+
const message = this.ctx.locale.bind("workspace")("draft.initializationFailed");
|
|
2497
|
+
this.notify({
|
|
2498
|
+
kind: "createFailed",
|
|
2499
|
+
message
|
|
2500
|
+
});
|
|
2501
|
+
throw new Error(message);
|
|
2502
|
+
}
|
|
1565
2503
|
async archiveSession(sessionId, options = {}) {
|
|
1566
2504
|
await this.workspaces.archiveSession(sessionId, options);
|
|
1567
2505
|
if (this.mainReference?.sessionId === sessionId) this.clearMain();
|
|
@@ -1640,7 +2578,7 @@ window.__ModuleLoader__.load({
|
|
|
1640
2578
|
}
|
|
1641
2579
|
const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
|
|
1642
2580
|
let sessionId;
|
|
1643
|
-
if (summary !== void 0 && workspace !== void 0 && summary.cwd === workspace.path && !workspaces.archivedSessionIds.includes(summary.id)) sessionId = await this.
|
|
2581
|
+
if (summary !== void 0 && workspace !== void 0 && summary.cwd === workspace.path && !workspaces.archivedSessionIds.includes(summary.id)) sessionId = await this.restoreBlank(workspace.workspaceId, summary.id);
|
|
1644
2582
|
let target = workspace?.workspaceId ?? recentWorkspace(workspaces.items, sessions.byId);
|
|
1645
2583
|
if (target === void 0 && workspaces.items.length === 0 && sessions.ids.length === 0) {
|
|
1646
2584
|
const prepared = await this.initializeDefaultWorkspace(navigation);
|
|
@@ -1946,23 +2884,96 @@ window.__ModuleLoader__.load({
|
|
|
1946
2884
|
liveStatus: aggregated.get(group.key) ?? EMPTY_LIVE_STATUS
|
|
1947
2885
|
}));
|
|
1948
2886
|
}
|
|
2887
|
+
/** The blank row plus the first `watermark` non-blank rows, in list order. */
|
|
2888
|
+
function watermarkRows(sessions, watermark) {
|
|
2889
|
+
let count = 0;
|
|
2890
|
+
const rows = sessions.filter((session) => {
|
|
2891
|
+
if (session.blank) return true;
|
|
2892
|
+
count += 1;
|
|
2893
|
+
return count <= watermark;
|
|
2894
|
+
});
|
|
2895
|
+
return {
|
|
2896
|
+
rows,
|
|
2897
|
+
hiddenCount: sessions.length - rows.length
|
|
2898
|
+
};
|
|
2899
|
+
}
|
|
2900
|
+
/**
|
|
2901
|
+
* Smallest watermark that covers every non-blank row within the automatic maximum that is running, has a running
|
|
2902
|
+
* child, awaits the user, carries an unread completion, or is current, and the reveal target at any position.
|
|
2903
|
+
* @param sessions - one group's ordered Session rows.
|
|
2904
|
+
* @param current - Session retained by the main view, if the sidebar is the active surface.
|
|
2905
|
+
* @param revealSessionId - Session selected from search and required to be visible.
|
|
2906
|
+
* @returns the group's baseline non-blank row count.
|
|
2907
|
+
*/
|
|
2908
|
+
function requiredWatermark(sessions, current, revealSessionId) {
|
|
2909
|
+
let index = 0;
|
|
2910
|
+
let required = 0;
|
|
2911
|
+
for (const session of sessions) {
|
|
2912
|
+
if (session.blank) continue;
|
|
2913
|
+
index += 1;
|
|
2914
|
+
if (index <= 50 && (session.running || session.runningSubagentCount > 0 || session.pendingInteraction !== void 0 || session.completed || session.id === current) || session.id === revealSessionId) required = index;
|
|
2915
|
+
}
|
|
2916
|
+
return required;
|
|
2917
|
+
}
|
|
2918
|
+
/**
|
|
2919
|
+
* Raise each listed group's watermark to its requirement; returns `marks` itself when nothing rises.
|
|
2920
|
+
* @param marks - watermarks stored by the current browser tree.
|
|
2921
|
+
* @param required - required baselines, in group order.
|
|
2922
|
+
* @returns either the unchanged input or a new object containing only raised groups.
|
|
2923
|
+
*/
|
|
2924
|
+
function raiseWatermarks(marks, required) {
|
|
2925
|
+
let raised = marks;
|
|
2926
|
+
for (const [key, watermark] of required) if (watermark > (raised[key] ?? 5)) raised = {
|
|
2927
|
+
...raised,
|
|
2928
|
+
[key]: watermark
|
|
2929
|
+
};
|
|
2930
|
+
return raised;
|
|
2931
|
+
}
|
|
1949
2932
|
//#endregion
|
|
1950
|
-
//#region src/client/
|
|
2933
|
+
//#region src/client/use-registered-facets.ts
|
|
1951
2934
|
/**
|
|
1952
|
-
*
|
|
2935
|
+
* 把登记面读成「这一轮渲染该折哪些来源」的**唯一**一处接线。
|
|
2936
|
+
*
|
|
2937
|
+
* ## 为什么单独成文
|
|
2938
|
+
*
|
|
2939
|
+
* 这一步此前直接写在 `rows/WorkspaceBrowser.tsx` 里,两行:
|
|
2940
|
+
*
|
|
2941
|
+
* ```ts
|
|
2942
|
+
* useSyncExternalStore(facetsStore.subscribe, facetsStore.getVersion, …) // 返回值丢掉
|
|
2943
|
+
* const facets = useMemo(() => visibleFacets(facetsStore.list(), settings), [settings])
|
|
2944
|
+
* ```
|
|
2945
|
+
*
|
|
2946
|
+
* 看着没问题——订阅了、也 memo 了。实际是**订阅通知只重渲染、不重算**:
|
|
2947
|
+
* `useSyncExternalStore` 的返回值被丢掉,`useMemo` 的依赖里就没有任何东西随登记面变化,
|
|
2948
|
+
* 于是那次重渲染原样拿回**上一次**的登记面。提供方在自己的 `apply` 里注册,而宿主不保证
|
|
2949
|
+
* `apply` 与本组件首渲染的先后:注册**晚于**首渲染时,facet 明明登记上了,却永远进不了
|
|
2950
|
+
* 徽标——不报错、不红测试(能渲染这条路径的那份上游 spec 在本仓库是跳过的,见
|
|
2951
|
+
* `vitest.config.ts` 的 `MONOREPO_ONLY_SPECS`),只能靠刷新页面碰运气。
|
|
1953
2952
|
*
|
|
1954
|
-
*
|
|
1955
|
-
* `
|
|
2953
|
+
* 版本号必须**同时**当两件事用:① 触发重渲染的信号,② memo 的依赖。这里把它收进一个
|
|
2954
|
+
* hook,让「只用①、丢掉②」在结构上写不出来,并且让这条接缝能被 `@testing-library/react`
|
|
2955
|
+
* 直接覆盖(本文件零宿主 import,宿主依赖闭包进不来)。
|
|
1956
2956
|
*
|
|
1957
|
-
*
|
|
1958
|
-
* "要不要画徽标"因此天然同源。放到渲染阶段会出现"label 里有、计数里没有",而那正是
|
|
1959
|
-
* 徽标最容易被怀疑撒谎的地方。
|
|
2957
|
+
* ## 为什么现在不再按设置筛
|
|
1960
2958
|
*
|
|
1961
|
-
*
|
|
2959
|
+
* 旧版本这里会过一道 `visibleFacets(list, settings)`,按 `facet.settingKey` 查本插件的
|
|
2960
|
+
* 布尔开关。那意味着**本插件的配置面里写着别人家的语义**(`showScheduledInBadge` /
|
|
2961
|
+
* `showWaitingReviewInBadge`,连图标形状都替对方写死),与"本插件不认识任何来源"直接
|
|
2962
|
+
* 矛盾。现在外部来源的显隐归**来源自己**:它想被关掉就不注册 facet,登记面即所见。
|
|
2963
|
+
* 本插件的开关只管自己那三档会话状态(见 `badge-settings.ts`)。
|
|
2964
|
+
*
|
|
2965
|
+
* @module dsh-group-status/client/use-registered-facets
|
|
2966
|
+
*/
|
|
2967
|
+
/**
|
|
2968
|
+
* 当前登记的来源,按登记面顺序(已按 `order` 排好)。
|
|
2969
|
+
*
|
|
2970
|
+
* 引用在版本号不变时保持稳定:登记面是低频事件,重算成本 = O(facet × 组内会话)。
|
|
2971
|
+
*
|
|
2972
|
+
* @param store - facet 消费侧存储(`facetStore()` 单例)。
|
|
1962
2973
|
*/
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
return
|
|
2974
|
+
function useRegisteredFacets(store) {
|
|
2975
|
+
const version = (0, react.useSyncExternalStore)(store.subscribe, store.getVersion, store.getServerVersion);
|
|
2976
|
+
return (0, react.useMemo)(() => [...store.list()], [version]);
|
|
1966
2977
|
}
|
|
1967
2978
|
//#endregion
|
|
1968
2979
|
//#region src/client/badge-labels.ts
|
|
@@ -2005,9 +3016,9 @@ window.__ModuleLoader__.load({
|
|
|
2005
3016
|
* Accessible label for a folded group's aggregated status, reusing the
|
|
2006
3017
|
* session-row locale keys so both surfaces read identically.
|
|
2007
3018
|
*
|
|
2008
|
-
*
|
|
2009
|
-
* `
|
|
2010
|
-
*
|
|
3019
|
+
* 按设置过滤:本插件那三档会话状态按设置逐项过滤;**外部来源不参与过滤** —— 登记了就
|
|
3020
|
+
* 一定出现在这里,没登记就不在折果里,所以 `aria-label` 与徽标视觉天然一致,不必在这里
|
|
3021
|
+
* 再判一次设置(本插件也没有替别人家存的开关)。
|
|
2011
3022
|
*/
|
|
2012
3023
|
function groupLiveLabel(s, t, settings, now = Date.now()) {
|
|
2013
3024
|
const parts = [];
|
|
@@ -2187,10 +3198,10 @@ window.__ModuleLoader__.load({
|
|
|
2187
3198
|
*
|
|
2188
3199
|
* Count matches the dots: host-side sessions plus every registered facet's own count.
|
|
2189
3200
|
*
|
|
2190
|
-
* `settings`
|
|
2191
|
-
*
|
|
2192
|
-
*
|
|
2193
|
-
*
|
|
3201
|
+
* `settings` 只过滤**本插件自己的**三档会话状态:关掉的档位不画点、不计数、不进
|
|
3202
|
+
* aria-label、不参与「是否显示徽标」的判断。会话行自身的状态点不受影响:那是行级状态,
|
|
3203
|
+
* 不是分组提示。**外部来源在这里没有开关可关** —— 它们要么注册了(一定显示),要么没
|
|
3204
|
+
* 注册(根本不在登记面上)。要关某个来源,去那个插件自己的设置卡片:显隐是来源的决定。
|
|
2194
3205
|
*
|
|
2195
3206
|
* `now` 缺省取渲染时刻(与 `WorkspaceBrowser` 为相对时间读的时钟同一姿态):文案里的
|
|
2196
3207
|
* 剩余时长以此计算,只在提供方 store 通知时随渲染更新。
|
|
@@ -2205,8 +3216,8 @@ window.__ModuleLoader__.load({
|
|
|
2205
3216
|
if (pending === 0 && active === 0 && completed === 0 && facetTotal === 0) return null;
|
|
2206
3217
|
const label = groupLiveLabel(s, t, settings, now);
|
|
2207
3218
|
const dots = [];
|
|
2208
|
-
if (pending > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state:
|
|
2209
|
-
if (active > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state:
|
|
3219
|
+
if (pending > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: BADGE_FIELD_DOT_STATE.showPendingInBadge }, "warn"));
|
|
3220
|
+
if (active > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: BADGE_FIELD_DOT_STATE.showRunningInBadge }, "run"));
|
|
2210
3221
|
const urgentClass = Rows_module_css_default.groupBadgeUrgent ?? "";
|
|
2211
3222
|
for (const entry of s.facets.values()) {
|
|
2212
3223
|
const glyph = facetGlyph(entry, t, now);
|
|
@@ -2216,7 +3227,7 @@ window.__ModuleLoader__.load({
|
|
|
2216
3227
|
children: glyph
|
|
2217
3228
|
}, entry.facet.id));
|
|
2218
3229
|
}
|
|
2219
|
-
if (completed > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state:
|
|
3230
|
+
if (completed > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: BADGE_FIELD_DOT_STATE.showCompletedInBadge }, "done"));
|
|
2220
3231
|
const count = pending + active + completed + facetTotal;
|
|
2221
3232
|
const countLabel = count > 99 ? "99+" : count >= settings.countThreshold ? String(count) : "";
|
|
2222
3233
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -2476,6 +3487,7 @@ window.__ModuleLoader__.load({
|
|
|
2476
3487
|
side: "bottom",
|
|
2477
3488
|
align: "end",
|
|
2478
3489
|
delayMs: 500,
|
|
3490
|
+
portal: true,
|
|
2479
3491
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2480
3492
|
type: "button",
|
|
2481
3493
|
className: Rows_module_css_default.iconButton,
|
|
@@ -2504,6 +3516,7 @@ window.__ModuleLoader__.load({
|
|
|
2504
3516
|
t
|
|
2505
3517
|
}),
|
|
2506
3518
|
openDelayMs: 800,
|
|
3519
|
+
grouped: true,
|
|
2507
3520
|
disabled: menuOpen,
|
|
2508
3521
|
copyText: row.cwd,
|
|
2509
3522
|
copyLabel: t("copy"),
|
|
@@ -2658,6 +3671,7 @@ window.__ModuleLoader__.load({
|
|
|
2658
3671
|
side: "bottom",
|
|
2659
3672
|
align: "end",
|
|
2660
3673
|
delayMs: 500,
|
|
3674
|
+
portal: true,
|
|
2661
3675
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2662
3676
|
type: "button",
|
|
2663
3677
|
className: Rows_module_css_default.iconButton,
|
|
@@ -2812,6 +3826,7 @@ window.__ModuleLoader__.load({
|
|
|
2812
3826
|
t
|
|
2813
3827
|
}),
|
|
2814
3828
|
openDelayMs: 800,
|
|
3829
|
+
grouped: true,
|
|
2815
3830
|
disabled: menuOpen || drag?.active === true,
|
|
2816
3831
|
copyText: row.blank || row.title === "" ? void 0 : row.title,
|
|
2817
3832
|
copyLabel: t("copy"),
|
|
@@ -3253,22 +4268,6 @@ window.__ModuleLoader__.load({
|
|
|
3253
4268
|
const SEARCH_DEBOUNCE_MS = 250;
|
|
3254
4269
|
/** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
|
|
3255
4270
|
const SEARCH_QUERY_MAX_CODE_UNITS = 500;
|
|
3256
|
-
/** Idle Session rows visible per Workspace before the local overflow control. */
|
|
3257
|
-
const COLLAPSED_SESSION_LIMIT = 5;
|
|
3258
|
-
/** Keep provisional and running rows outside the idle-session quota, including parents with running children. */
|
|
3259
|
-
function collapsedSessionRows(sessions, limit = COLLAPSED_SESSION_LIMIT) {
|
|
3260
|
-
let idleCount = 0;
|
|
3261
|
-
const rows = sessions.filter((session) => {
|
|
3262
|
-
if (session.blank || session.running || session.runningSubagentCount > 0) return true;
|
|
3263
|
-
if (idleCount >= limit) return false;
|
|
3264
|
-
idleCount += 1;
|
|
3265
|
-
return true;
|
|
3266
|
-
});
|
|
3267
|
-
return {
|
|
3268
|
-
rows,
|
|
3269
|
-
hiddenCount: sessions.length - rows.length
|
|
3270
|
-
};
|
|
3271
|
-
}
|
|
3272
4271
|
/** Keep controlled input and RPC payload inside the session.search wire contract. */
|
|
3273
4272
|
function sanitizeSearchQuery(value) {
|
|
3274
4273
|
const withoutNul = value.replaceAll("\0", "");
|
|
@@ -3454,11 +4453,11 @@ window.__ModuleLoader__.load({
|
|
|
3454
4453
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
3455
4454
|
const badgeSettings = useBadgeSettings((settings) => settings);
|
|
3456
4455
|
const statuses = useSessionStatus((s) => s);
|
|
3457
|
-
|
|
3458
|
-
const facets = (0, react.useMemo)(() => visibleFacets(facetsStore.list(), badgeSettings), [badgeSettings]);
|
|
4456
|
+
const facets = useRegisteredFacets(facetsStore);
|
|
3459
4457
|
const current = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
3460
4458
|
const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
|
|
3461
|
-
const [
|
|
4459
|
+
const [storedWatermarks, setStoredWatermarks] = (0, react.useState)({});
|
|
4460
|
+
const [watermarkMotionKey, setWatermarkMotionKey] = (0, react.useState)(0);
|
|
3462
4461
|
const [drag, setDrag] = (0, react.useState)(null);
|
|
3463
4462
|
const sessionDropCommitted = (0, react.useRef)(false);
|
|
3464
4463
|
const [workspaceDrag, setWorkspaceDrag] = (0, react.useState)(null);
|
|
@@ -3516,20 +4515,24 @@ window.__ModuleLoader__.load({
|
|
|
3516
4515
|
revealGroup,
|
|
3517
4516
|
setGroupExpanded
|
|
3518
4517
|
]);
|
|
3519
|
-
(
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
4518
|
+
const baselines = new Map(groups.map((group) => [group.key, Math.max(5, requiredWatermark(group.expanded ? group.sessions : [], current, group.key === revealGroup ? revealSessionId : void 0))]));
|
|
4519
|
+
const foldedKeys = new Set(groups.filter((group) => !group.expanded).map((group) => group.key));
|
|
4520
|
+
const underFoldedAncestor = (key) => {
|
|
4521
|
+
for (let owner = parents.get(key); owner !== void 0; owner = parents.get(owner)) if (foldedKeys.has(owner)) return true;
|
|
4522
|
+
return false;
|
|
4523
|
+
};
|
|
4524
|
+
const requiredWatermarks = new Map([...baselines].filter(([key]) => !underFoldedAncestor(key)));
|
|
4525
|
+
const sessionWatermarks = raiseWatermarks(storedWatermarks, requiredWatermarks);
|
|
4526
|
+
if (sessionWatermarks !== storedWatermarks) setStoredWatermarks((marks) => raiseWatermarks(marks, requiredWatermarks));
|
|
4527
|
+
const dropWatermarks = (drop) => {
|
|
4528
|
+
if (!Object.keys(storedWatermarks).some(drop)) return;
|
|
4529
|
+
setStoredWatermarks((marks) => Object.fromEntries(Object.entries(marks).filter(([key]) => !drop(key))));
|
|
4530
|
+
setWatermarkMotionKey((key) => key + 1);
|
|
4531
|
+
};
|
|
4532
|
+
const withinGroup = (key, root) => {
|
|
4533
|
+
for (let owner = key; owner !== void 0; owner = parents.get(owner)) if (owner === root) return true;
|
|
4534
|
+
return false;
|
|
4535
|
+
};
|
|
3533
4536
|
const now = Date.now();
|
|
3534
4537
|
const commitSessionDrag = (activeDrag, over) => {
|
|
3535
4538
|
if (sessionDropCommitted.current) return;
|
|
@@ -3540,7 +4543,7 @@ window.__ModuleLoader__.load({
|
|
|
3540
4543
|
if (over.id === activeDrag.sessionId) return;
|
|
3541
4544
|
const accountSessionIds = activeDrag.accountKey === "" ? ungroupedSessionIds : workspaces.find((workspace) => workspace.workspaceId === activeDrag.accountKey)?.sessionIds;
|
|
3542
4545
|
if (accountSessionIds === void 0) return;
|
|
3543
|
-
const renderedSessions =
|
|
4546
|
+
const renderedSessions = watermarkRows(group.sessions, sessionWatermarks[group.key] ?? 5).rows;
|
|
3544
4547
|
const nextOrder = sessionDragOrder(accountSessionIds, renderedSessions, activeDrag, over);
|
|
3545
4548
|
if (nextOrder !== void 0) setSessionOrder(activeDrag.accountKey, nextOrder);
|
|
3546
4549
|
};
|
|
@@ -3598,8 +4601,9 @@ window.__ModuleLoader__.load({
|
|
|
3598
4601
|
const workspaceId = group.workspaceId;
|
|
3599
4602
|
const children = childrenByParent.get(group.key) ?? [];
|
|
3600
4603
|
const compatibleDrag = workspaceDrag !== null && parents.get(workspaceDrag.workspaceId) === parents.get(group.key);
|
|
3601
|
-
const
|
|
3602
|
-
const
|
|
4604
|
+
const watermark = sessionWatermarks[group.key] ?? 5;
|
|
4605
|
+
const collapsed = watermarkRows(group.sessions, baselines.get(group.key) ?? 5);
|
|
4606
|
+
const visible = watermarkRows(group.sessions, watermark);
|
|
3603
4607
|
const sessionsExpanded = visible.hiddenCount === 0;
|
|
3604
4608
|
rowKeys.push(`workspace:${group.key}`);
|
|
3605
4609
|
const childRows = group.expanded ? children.map((child) => renderGroup(child, depth + 1)) : [];
|
|
@@ -3673,10 +4677,7 @@ window.__ModuleLoader__.load({
|
|
|
3673
4677
|
now,
|
|
3674
4678
|
t,
|
|
3675
4679
|
onToggle: () => {
|
|
3676
|
-
if (group.expanded)
|
|
3677
|
-
...limits,
|
|
3678
|
-
[group.key]: COLLAPSED_SESSION_LIMIT
|
|
3679
|
-
}));
|
|
4680
|
+
if (group.expanded) dropWatermarks((key) => withinGroup(key, group.key));
|
|
3680
4681
|
setGroupExpanded(group.key, !group.expanded);
|
|
3681
4682
|
},
|
|
3682
4683
|
onCreate: () => {
|
|
@@ -3761,10 +4762,15 @@ window.__ModuleLoader__.load({
|
|
|
3761
4762
|
"data-row-key": `overflow:${group.key}`,
|
|
3762
4763
|
"aria-expanded": sessionsExpanded,
|
|
3763
4764
|
onClick: () => {
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
4765
|
+
if (sessionsExpanded) {
|
|
4766
|
+
dropWatermarks((key) => key === group.key);
|
|
4767
|
+
return;
|
|
4768
|
+
}
|
|
4769
|
+
setStoredWatermarks((marks) => ({
|
|
4770
|
+
...marks,
|
|
4771
|
+
[group.key]: visible.hiddenCount <= 5 ? Infinity : watermark + 5
|
|
3767
4772
|
}));
|
|
4773
|
+
setWatermarkMotionKey((key) => key + 1);
|
|
3768
4774
|
},
|
|
3769
4775
|
children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: visible.hiddenCount })
|
|
3770
4776
|
})
|
|
@@ -3772,7 +4778,7 @@ window.__ModuleLoader__.load({
|
|
|
3772
4778
|
}, group.key);
|
|
3773
4779
|
};
|
|
3774
4780
|
const groupRows = rootGroups.map((group) => renderGroup(group, 0));
|
|
3775
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
4781
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.HoverCardGroup, {
|
|
3776
4782
|
className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
|
|
3777
4783
|
children: [
|
|
3778
4784
|
workspaceDropAtListStart && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -3784,7 +4790,7 @@ window.__ModuleLoader__.load({
|
|
|
3784
4790
|
label: t("section.sessions"),
|
|
3785
4791
|
rowKeys,
|
|
3786
4792
|
ready: list.phase === "ready" && workspaceReady && !nativeDragActive,
|
|
3787
|
-
resetKey: animationResetKey,
|
|
4793
|
+
resetKey: JSON.stringify([animationResetKey, watermarkMotionKey]),
|
|
3788
4794
|
children: [groupsWithAggregatedStatus.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptySessions, {
|
|
3789
4795
|
rowState,
|
|
3790
4796
|
onLeaveArchivedOnly,
|
|
@@ -3817,7 +4823,7 @@ window.__ModuleLoader__.load({
|
|
|
3817
4823
|
if (nextOrder !== void 0) setSessionOrder(FLAT_SESSION_ORDER_KEY, nextOrder);
|
|
3818
4824
|
};
|
|
3819
4825
|
const now = Date.now();
|
|
3820
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
4826
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.HoverCardGroup, {
|
|
3821
4827
|
className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
|
|
3822
4828
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(AnimatedRows, {
|
|
3823
4829
|
className: clsx(WorkspaceBrowser_module_css_default.list, WorkspaceBrowser_module_css_default.flatList),
|
|
@@ -3950,7 +4956,7 @@ window.__ModuleLoader__.load({
|
|
|
3950
4956
|
* @param props - composed slot props (shell owner share + store + injected actions).
|
|
3951
4957
|
* @returns the region element tree.
|
|
3952
4958
|
*/
|
|
3953
|
-
function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, requestSessionRename, notifyArchivedNotOpenable, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, unarchiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, useShortcuts, useWorkspaceShortcuts, useBadgeSettings, requestSearch, requestAddWorkspace, closeAddWorkspace, setDirectoryBusy,
|
|
4959
|
+
function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, requestSessionRename, notifyArchivedNotOpenable, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, unarchiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, useShortcuts, useWorkspaceShortcuts, useBadgeSettings, requestSearch, requestAddWorkspace, closeAddWorkspace, setDirectoryBusy, renderSlot, t }) {
|
|
3954
4960
|
const home = useHostInfo((info) => info.home);
|
|
3955
4961
|
const shortcuts = useShortcuts((rows) => rows);
|
|
3956
4962
|
const searchShortcut = shortcuts.find((row) => row.id === "session.search");
|
|
@@ -4379,7 +5385,7 @@ window.__ModuleLoader__.load({
|
|
|
4379
5385
|
side: "right",
|
|
4380
5386
|
onPick: (workspaceId) => {
|
|
4381
5387
|
closeAddWorkspace();
|
|
4382
|
-
startSession(workspaceId);
|
|
5388
|
+
startSession(workspaceId, { clearPreviousDraft: false });
|
|
4383
5389
|
},
|
|
4384
5390
|
onClose: () => {
|
|
4385
5391
|
closeAddWorkspace();
|
|
@@ -4559,11 +5565,7 @@ window.__ModuleLoader__.load({
|
|
|
4559
5565
|
role: "alert",
|
|
4560
5566
|
children: deleteError
|
|
4561
5567
|
})]
|
|
4562
|
-
})
|
|
4563
|
-
shortcutState.forkError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
4564
|
-
text: t(shortcutState.forkError.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
|
|
4565
|
-
onDone: dismissForkError
|
|
4566
|
-
}, shortcutState.forkError.seq)
|
|
5568
|
+
})
|
|
4567
5569
|
]
|
|
4568
5570
|
});
|
|
4569
5571
|
}
|
|
@@ -4608,6 +5610,7 @@ window.__ModuleLoader__.load({
|
|
|
4608
5610
|
side: "bottom",
|
|
4609
5611
|
align: "end",
|
|
4610
5612
|
delayMs: 500,
|
|
5613
|
+
portal: true,
|
|
4611
5614
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4612
5615
|
type: "button",
|
|
4613
5616
|
className: Rows_module_css_default.iconButton,
|
|
@@ -4753,13 +5756,15 @@ window.__ModuleLoader__.load({
|
|
|
4753
5756
|
/** The fork action: one `sidebar.workspaces.session.menu.item` row. */
|
|
4754
5757
|
/**
|
|
4755
5758
|
* Menu row (order 300): fork at the Session's last completed turn; the child
|
|
4756
|
-
* arrives through the Host list beside its source.
|
|
4757
|
-
*
|
|
4758
|
-
* @
|
|
5759
|
+
* arrives through the Host list beside its source. Absent when the access
|
|
5760
|
+
* snapshot excludes `session/fork` for this Session.
|
|
5761
|
+
* @param props - owner share, menu open state, access Hook, and the fork share.
|
|
5762
|
+
* @returns the row, or nothing when the caller may not fork this Session.
|
|
4759
5763
|
*/
|
|
4760
|
-
function ForkSessionMenuItem({ sessionId, useMenuOpenState, useShortcuts, forkSession, t }) {
|
|
5764
|
+
function ForkSessionMenuItem({ sessionId, useMenuOpenState, useShortcuts, useSessionAccess, forkSession, t }) {
|
|
4761
5765
|
const [, setMenuOpen] = useMenuOpenState();
|
|
4762
5766
|
const shortcut = useShortcuts((rows) => rows.find((row) => row.id === "session.fork"));
|
|
5767
|
+
if (!useSessionAccess(sessionId, "session/fork")) return null;
|
|
4763
5768
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
|
|
4764
5769
|
shortcut,
|
|
4765
5770
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
|
|
@@ -4820,6 +5825,7 @@ window.__ModuleLoader__.load({
|
|
|
4820
5825
|
side: "bottom",
|
|
4821
5826
|
align: "end",
|
|
4822
5827
|
delayMs: 500,
|
|
5828
|
+
portal: true,
|
|
4823
5829
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4824
5830
|
type: "button",
|
|
4825
5831
|
className: Rows_module_css_default.iconButton,
|
|
@@ -4949,75 +5955,38 @@ window.__ModuleLoader__.load({
|
|
|
4949
5955
|
});
|
|
4950
5956
|
}
|
|
4951
5957
|
//#endregion
|
|
4952
|
-
//#region src/client/session-actions/
|
|
4953
|
-
/**
|
|
4954
|
-
* The `shell.overlay` entry for Workspace and Session notices.
|
|
4955
|
-
* One notice is visible at a time; a parent rerender does not extend its hold.
|
|
4956
|
-
*/
|
|
5958
|
+
//#region src/client/session-actions/SessionId.tsx
|
|
5959
|
+
/** The copy Session ID action: one `sidebar.workspaces.session.menu.item` row. */
|
|
4957
5960
|
/**
|
|
4958
|
-
*
|
|
4959
|
-
*
|
|
4960
|
-
*
|
|
5961
|
+
* Copy the complete ID after dismissing the row menu.
|
|
5962
|
+
* @param props - row identity, menu dismissal, clipboard action, and locale.
|
|
5963
|
+
* @returns the final built-in menu item.
|
|
4961
5964
|
*/
|
|
4962
|
-
|
|
5965
|
+
function SessionIdMenuItem({ sessionId, useMenuOpenState, copySessionId, t }) {
|
|
5966
|
+
const [, setMenuOpen] = useMenuOpenState();
|
|
5967
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
|
|
5968
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular, {}),
|
|
5969
|
+
onSelect: () => {
|
|
5970
|
+
setMenuOpen(false);
|
|
5971
|
+
copySessionId(sessionId);
|
|
5972
|
+
},
|
|
5973
|
+
children: t("menu.sessionId")
|
|
5974
|
+
});
|
|
5975
|
+
}
|
|
5976
|
+
//#endregion
|
|
5977
|
+
//#region src/client/session-actions/RowActionToast.tsx
|
|
4963
5978
|
/**
|
|
4964
|
-
*
|
|
4965
|
-
*
|
|
4966
|
-
*
|
|
4967
|
-
* reason on the same hold, or a plain warning for a failed pin, an archived
|
|
4968
|
-
* row that was clicked, or default Workspace creation.
|
|
4969
|
-
* @param props - the notice hook, the shared viewing store, the notice dismissal, the two archived-notice actions, and the locale seat.
|
|
4970
|
-
* @returns the notice on display, or null.
|
|
5979
|
+
* Dispatch the current notice without interpreting its business payload.
|
|
5980
|
+
* @param props - Current notice, dismissal, and the declared keyed renderer.
|
|
5981
|
+
* @returns the selected notice presentation, or nothing.
|
|
4971
5982
|
*/
|
|
4972
|
-
function RowActionToast({ useToast,
|
|
4973
|
-
const
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
text: t(toast.kind === "archived" ? "toast.archived" : "toast.stoppedAndArchived"),
|
|
4980
|
-
tone: "success",
|
|
4981
|
-
holdMs: LONG_TOAST_HOLD_MS,
|
|
4982
|
-
actions: [{
|
|
4983
|
-
label: t("toast.archivedUndo"),
|
|
4984
|
-
onClick: () => {
|
|
4985
|
-
dismissToast();
|
|
4986
|
-
undoArchive(sessionId);
|
|
4987
|
-
}
|
|
4988
|
-
}, ...archivedRowsVisible ? [] : [{
|
|
4989
|
-
prefix: t("toast.archivedOr"),
|
|
4990
|
-
label: t("toast.archivedFilter"),
|
|
4991
|
-
onClick: () => {
|
|
4992
|
-
dismissToast();
|
|
4993
|
-
showArchived();
|
|
4994
|
-
}
|
|
4995
|
-
}]],
|
|
4996
|
-
onDone: dismissToast
|
|
4997
|
-
}, `toast-${String(toast.seq)}`);
|
|
4998
|
-
}
|
|
4999
|
-
if (toast.kind === "createFailed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
5000
|
-
text: t("toast.createFailed", { message: toast.message }),
|
|
5001
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
|
|
5002
|
-
holdMs: LONG_TOAST_HOLD_MS,
|
|
5003
|
-
onDone: dismissToast
|
|
5004
|
-
}, `toast-${String(toast.seq)}`);
|
|
5005
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
5006
|
-
text: plainNoticeText(toast, t),
|
|
5007
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
|
|
5008
|
-
onDone: dismissToast
|
|
5009
|
-
}, `toast-${String(toast.seq)}`);
|
|
5010
|
-
}
|
|
5011
|
-
/** The copy of one plain warning, keyed by the notice kind the union closes over. */
|
|
5012
|
-
function plainNoticeText(toast, t) {
|
|
5013
|
-
switch (toast.kind) {
|
|
5014
|
-
case "pinFailed": return t("toast.pinFailed");
|
|
5015
|
-
case "unpinFailed": return t("toast.unpinFailed");
|
|
5016
|
-
case "defaultWorkspaceFailed": return t("defaultWorkspace.failed");
|
|
5017
|
-
case "archivedNotOpenable": return t("toast.archivedNotOpenable");
|
|
5018
|
-
/* v8 ignore next 2 -- closed-union backstop; only reached if a notice kind is forged */
|
|
5019
|
-
default: return assertNever$1(toast);
|
|
5020
|
-
}
|
|
5983
|
+
function RowActionToast({ useToast, dismissToast, renderSlot }) {
|
|
5984
|
+
const notice = useToast((current) => current);
|
|
5985
|
+
if (notice === null) return null;
|
|
5986
|
+
return renderSlot("workspace.toast", {
|
|
5987
|
+
...notice,
|
|
5988
|
+
dismissToast
|
|
5989
|
+
}, { entryKey: notice.kind });
|
|
5021
5990
|
}
|
|
5022
5991
|
//#endregion
|
|
5023
5992
|
//#region src/client/locales.ts
|
|
@@ -5029,6 +5998,8 @@ window.__ModuleLoader__.load({
|
|
|
5029
5998
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
5030
5999
|
const zh = {
|
|
5031
6000
|
"defaultWorkspace.failed": "无法创建默认工作区,请通过“选择工作区”选择文件夹",
|
|
6001
|
+
"draft.workspaceRequired": "请先选择工作区",
|
|
6002
|
+
"draft.initializationFailed": "暂时无法填入草稿,请稍后重试",
|
|
5032
6003
|
"group.ungrouped": "未分组",
|
|
5033
6004
|
"session.new": "新会话",
|
|
5034
6005
|
"session.untitled": "未命名",
|
|
@@ -5083,6 +6054,9 @@ window.__ModuleLoader__.load({
|
|
|
5083
6054
|
"menu.unarchiveSession": "取消归档",
|
|
5084
6055
|
"menu.pinSession": "置顶会话",
|
|
5085
6056
|
"menu.unpinSession": "取消置顶",
|
|
6057
|
+
"menu.sessionId": "复制 Session ID",
|
|
6058
|
+
"toast.sessionIdCopied": "Session ID 已复制",
|
|
6059
|
+
"toast.sessionIdCopyFailed": "复制失败,请重试",
|
|
5086
6060
|
"row.archived": "已归档",
|
|
5087
6061
|
"row.pinned": "已置顶",
|
|
5088
6062
|
"toast.archivedNotOpenable": "已归档对话暂时无法查看,请取消归档后查看",
|
|
@@ -5109,6 +6083,8 @@ window.__ModuleLoader__.load({
|
|
|
5109
6083
|
"toast.pinFailed": "置顶失败,请稍后重试",
|
|
5110
6084
|
"toast.unpinFailed": "取消置顶失败,请稍后重试",
|
|
5111
6085
|
"toast.createFailed": "新建会话失败:{message}",
|
|
6086
|
+
"toast.forkRequiresOpen": "请先打开原会话完成迁移,再创建分支",
|
|
6087
|
+
"toast.openForkSource": "打开原会话",
|
|
5112
6088
|
"sessions.count.one": "{n} 个会话",
|
|
5113
6089
|
"sessions.count.other": "{n} 个会话",
|
|
5114
6090
|
"actions.workspace.aria": "工作区“{name}”的操作",
|
|
@@ -5148,10 +6124,6 @@ window.__ModuleLoader__.load({
|
|
|
5148
6124
|
"settings.badge.showPendingHint": "关闭后,等待审批 / 计划评审 / 等待回答的会话不再画黄点,也不再计入折叠徽标的数字与悬停明细。",
|
|
5149
6125
|
"settings.badge.showCompleted": "显示「已完成」绿点",
|
|
5150
6126
|
"settings.badge.showCompletedHint": "关闭后,「已完成」的会话不再画绿点,也不再计入折叠徽标的数字与悬停明细。会话行自身的绿点不受影响。",
|
|
5151
|
-
"settings.badge.showScheduled": "显示「定时任务」时钟",
|
|
5152
|
-
"settings.badge.showScheduledHint": "关闭后,带定时提醒的会话(来自 dsh-later)不再画时钟图标,也不再计入折叠徽标的数字与悬停明细。",
|
|
5153
|
-
"settings.badge.showWaitingReview": "显示「等待评审」沙漏",
|
|
5154
|
-
"settings.badge.showWaitingReviewHint": "关闭后,等待代码评审的会话(来自 dsh-gitea-dispatch)不再画沙漏图标,也不再计入折叠徽标的数字与悬停明细。",
|
|
5155
6127
|
"settings.hover.section": "悬停明细",
|
|
5156
6128
|
"settings.hover.showDetails": "显示状态明细行",
|
|
5157
6129
|
"settings.hover.showDetailsHint": "关闭后,悬停卡只显示工作区标题、路径和创建时间——不再显示聚合状态明细行。",
|
|
@@ -5167,6 +6139,8 @@ window.__ModuleLoader__.load({
|
|
|
5167
6139
|
/** English dictionary, checked complete against the zh key set. */
|
|
5168
6140
|
const en = {
|
|
5169
6141
|
"defaultWorkspace.failed": "Unable to create default workspace. Use Choose workspace to select a folder.",
|
|
6142
|
+
"draft.workspaceRequired": "Choose a workspace first.",
|
|
6143
|
+
"draft.initializationFailed": "Could not fill the draft. Please try again shortly.",
|
|
5170
6144
|
"group.ungrouped": "Ungrouped",
|
|
5171
6145
|
"session.new": "New Session",
|
|
5172
6146
|
"session.untitled": "Untitled",
|
|
@@ -5221,6 +6195,9 @@ window.__ModuleLoader__.load({
|
|
|
5221
6195
|
"menu.unarchiveSession": "Unarchive session",
|
|
5222
6196
|
"menu.pinSession": "Pin session",
|
|
5223
6197
|
"menu.unpinSession": "Unpin session",
|
|
6198
|
+
"menu.sessionId": "Copy Session ID",
|
|
6199
|
+
"toast.sessionIdCopied": "Session ID copied",
|
|
6200
|
+
"toast.sessionIdCopyFailed": "Copy failed. Try again.",
|
|
5224
6201
|
"row.archived": "Archived",
|
|
5225
6202
|
"row.pinned": "Pinned",
|
|
5226
6203
|
"toast.archivedNotOpenable": "Archived sessions cannot be opened. Unarchive it to view.",
|
|
@@ -5247,6 +6224,8 @@ window.__ModuleLoader__.load({
|
|
|
5247
6224
|
"toast.pinFailed": "Pin failed. Try again later.",
|
|
5248
6225
|
"toast.unpinFailed": "Unpin failed. Try again later.",
|
|
5249
6226
|
"toast.createFailed": "New session failed: {message}",
|
|
6227
|
+
"toast.forkRequiresOpen": "Open the original session to complete migration before creating a branch.",
|
|
6228
|
+
"toast.openForkSource": "Open original session",
|
|
5250
6229
|
"sessions.count.one": "{n} session",
|
|
5251
6230
|
"sessions.count.other": "{n} sessions",
|
|
5252
6231
|
"actions.workspace.aria": "Workspace actions for {name}",
|
|
@@ -5286,10 +6265,6 @@ window.__ModuleLoader__.load({
|
|
|
5286
6265
|
"settings.badge.showPendingHint": "When off, Sessions waiting for approval / plan review / answer stop drawing the yellow dot and drop out of the collapsed badge count and its hover details.",
|
|
5287
6266
|
"settings.badge.showCompleted": "Show the completed dot",
|
|
5288
6267
|
"settings.badge.showCompletedHint": "When off, completed Sessions stop drawing the dot and drop out of the collapsed badge count and its hover details. The Session row's own dot is unaffected.",
|
|
5289
|
-
"settings.badge.showScheduled": "Show the scheduled icon",
|
|
5290
|
-
"settings.badge.showScheduledHint": "When off, Sessions with scheduled reminders (from dsh-later) stop drawing the clock icon and drop out of the collapsed badge count and its hover details.",
|
|
5291
|
-
"settings.badge.showWaitingReview": "Show the waiting-for-review icon",
|
|
5292
|
-
"settings.badge.showWaitingReviewHint": "When off, Sessions waiting for code review (from dsh-gitea-dispatch) stop drawing the hourglass icon and drop out of the collapsed badge count and its hover details.",
|
|
5293
6268
|
"settings.hover.section": "Hover details",
|
|
5294
6269
|
"settings.hover.showDetails": "Show status detail lines",
|
|
5295
6270
|
"settings.hover.showDetailsHint": "When off, the hover card shows only the workspace title, path, and creation time — no aggregated status lines.",
|
|
@@ -5321,7 +6296,8 @@ window.__ModuleLoader__.load({
|
|
|
5321
6296
|
"remote",
|
|
5322
6297
|
"remote.directoryPicker",
|
|
5323
6298
|
"layout",
|
|
5324
|
-
"shortcuts"
|
|
6299
|
+
"shortcuts",
|
|
6300
|
+
"remoteAccess"
|
|
5325
6301
|
];
|
|
5326
6302
|
/**
|
|
5327
6303
|
* Register the browser and picker once their slot declarations are on the
|
|
@@ -5347,7 +6323,14 @@ window.__ModuleLoader__.load({
|
|
|
5347
6323
|
});
|
|
5348
6324
|
};
|
|
5349
6325
|
const uiWorkspace = new UiWorkspaceService(ctx, ctx.remote.directoryPicker, workspaces, sessions, viewInstance.actions, notify);
|
|
5350
|
-
|
|
6326
|
+
const denyWorkspaceAccess = {
|
|
6327
|
+
getSnapshot: () => false,
|
|
6328
|
+
subscribe: () => () => {}
|
|
6329
|
+
};
|
|
6330
|
+
ctx.slots.provideRoot({
|
|
6331
|
+
hooks: { workspaces: workspaces.list },
|
|
6332
|
+
keyedHooks: { workspaceAccess: (workspaceId, action) => action === void 0 ? denyWorkspaceAccess : ctx.remoteAccess.source("workspace", workspaceId, action) }
|
|
6333
|
+
});
|
|
5351
6334
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
5352
6335
|
zh,
|
|
5353
6336
|
en
|
|
@@ -5446,7 +6429,12 @@ window.__ModuleLoader__.load({
|
|
|
5446
6429
|
},
|
|
5447
6430
|
unarchiveSession
|
|
5448
6431
|
});
|
|
5449
|
-
installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, archiveInjected().archiveSession)
|
|
6432
|
+
installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, archiveInjected().archiveSession, (reason) => {
|
|
6433
|
+
notify({
|
|
6434
|
+
kind: "shortcutForkFailed",
|
|
6435
|
+
reason
|
|
6436
|
+
});
|
|
6437
|
+
});
|
|
5450
6438
|
const archiveConfirmInjected = () => ({
|
|
5451
6439
|
hooks: { archiveRequest },
|
|
5452
6440
|
settleSessionArchive: () => {
|
|
@@ -5467,7 +6455,12 @@ window.__ModuleLoader__.load({
|
|
|
5467
6455
|
parent_session_id: sessionId,
|
|
5468
6456
|
click_position: "sidebar"
|
|
5469
6457
|
});
|
|
5470
|
-
}).catch(() => {
|
|
6458
|
+
}, { allowMigration: false }).catch((reason) => {
|
|
6459
|
+
if ((reason instanceof Error && reason.name === "SessionForkError" ? reason : void 0)?.rpcError?.code === "session/migration-required") notify({
|
|
6460
|
+
kind: "forkRequiresOpen",
|
|
6461
|
+
sessionId
|
|
6462
|
+
});
|
|
6463
|
+
});
|
|
5471
6464
|
} });
|
|
5472
6465
|
const renameInjected = () => ({ requestSessionRename });
|
|
5473
6466
|
const renameDialogInjected = () => ({
|
|
@@ -5475,19 +6468,31 @@ window.__ModuleLoader__.load({
|
|
|
5475
6468
|
settleSessionRename: shortcutControls.closeRename,
|
|
5476
6469
|
renameSession
|
|
5477
6470
|
});
|
|
6471
|
+
const sessionIdInjected = () => ({ copySessionId: async (id) => {
|
|
6472
|
+
notify({ kind: await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(id) ? "sessionIdCopied" : "sessionIdCopyFailed" });
|
|
6473
|
+
} });
|
|
6474
|
+
registerToasts(ctx, viewStore, {
|
|
6475
|
+
undoArchive: unarchiveSession,
|
|
6476
|
+
showArchived: () => {
|
|
6477
|
+
viewInstance.actions.setArchivedFilter("show");
|
|
6478
|
+
},
|
|
6479
|
+
openForkSource: (id) => {
|
|
6480
|
+
if (workspaces.list.getSnapshot().archivedSessionIds.includes(id)) {
|
|
6481
|
+
notify({ kind: "archivedNotOpenable" });
|
|
6482
|
+
return;
|
|
6483
|
+
}
|
|
6484
|
+
openSession(id);
|
|
6485
|
+
}
|
|
6486
|
+
});
|
|
5478
6487
|
const rowToastInjected = () => ({
|
|
5479
6488
|
hooks: { toast: rowToast },
|
|
5480
6489
|
dismissToast: () => {
|
|
5481
6490
|
rowToast.set(null);
|
|
5482
|
-
},
|
|
5483
|
-
undoArchive: unarchiveSession,
|
|
5484
|
-
showArchived: () => {
|
|
5485
|
-
viewInstance.actions.setArchivedFilter("show");
|
|
5486
6491
|
}
|
|
5487
6492
|
});
|
|
5488
6493
|
const browserInjected = () => ({
|
|
5489
|
-
startSession: (workspaceId) => {
|
|
5490
|
-
uiWorkspace.startSession(workspaceId);
|
|
6494
|
+
startSession: (workspaceId, options) => {
|
|
6495
|
+
uiWorkspace.startSession(workspaceId, options);
|
|
5491
6496
|
},
|
|
5492
6497
|
open: openSession,
|
|
5493
6498
|
searchSessions,
|
|
@@ -5513,7 +6518,6 @@ window.__ModuleLoader__.load({
|
|
|
5513
6518
|
requestAddWorkspace: shortcutControls.add,
|
|
5514
6519
|
closeAddWorkspace: shortcutControls.closeAdd,
|
|
5515
6520
|
setDirectoryBusy: shortcutControls.directoryBusy,
|
|
5516
|
-
dismissForkError: shortcutControls.dismissForkError,
|
|
5517
6521
|
hooks: {
|
|
5518
6522
|
directoryFlow: browserFlowSource,
|
|
5519
6523
|
hostInfo,
|
|
@@ -5586,6 +6590,13 @@ window.__ModuleLoader__.load({
|
|
|
5586
6590
|
locale: NS,
|
|
5587
6591
|
inject: archiveInjected
|
|
5588
6592
|
}, ArchiveSessionMenuItem);
|
|
6593
|
+
yield ctx.slots.register({
|
|
6594
|
+
name: "sidebar.workspaces.session.menu.item",
|
|
6595
|
+
id: "session-id",
|
|
6596
|
+
order: 450,
|
|
6597
|
+
locale: NS,
|
|
6598
|
+
inject: sessionIdInjected
|
|
6599
|
+
}, SessionIdMenuItem);
|
|
5589
6600
|
});
|
|
5590
6601
|
ctx.slots.inject("sidebar.workspaces.session.row.action", function* () {
|
|
5591
6602
|
yield ctx.slots.register({
|
|
@@ -5619,9 +6630,11 @@ window.__ModuleLoader__.load({
|
|
|
5619
6630
|
yield ctx.slots.register({
|
|
5620
6631
|
name: "shell.overlay",
|
|
5621
6632
|
id: "workspace.row-toast",
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
6633
|
+
inject: rowToastInjected,
|
|
6634
|
+
children: { "workspace.toast": {
|
|
6635
|
+
kind: "keyed",
|
|
6636
|
+
scope: "root"
|
|
6637
|
+
} }
|
|
5625
6638
|
}, RowActionToast);
|
|
5626
6639
|
});
|
|
5627
6640
|
ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({
|