@deepseek-ai/dsh-client-ui-workspace 0.2.0-rc.2 → 0.2.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +6 -6
- package/README.md +3 -3
- package/README.zh.md +3 -3
- package/lib/client.js +656 -2
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/client/navigation.d.ts +8 -2
- package/package.json +27 -27
package/README.i18n.yaml
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
en: b164014ee565be16
|
|
19
19
|
zh: 6a741647932b93e6
|
|
20
20
|
/deepseek-ai-dsh-client-ui-workspace/use-this-package/reordering-and-view-options:
|
|
21
|
-
en:
|
|
22
|
-
zh:
|
|
21
|
+
en: 8673afc1d72ad077
|
|
22
|
+
zh: 9fbb83a4029c5d10
|
|
23
23
|
/deepseek-ai-dsh-client-ui-workspace/use-this-package/workspace-hierarchy:
|
|
24
24
|
en: 5e049b90c55eddb3
|
|
25
25
|
zh: e06dc8d0d25c013f
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
en: dd0a7799c76c0f94
|
|
37
37
|
zh: 48d1cdacc9168aa2
|
|
38
38
|
/deepseek-ai-dsh-client-ui-workspace/understand-the-implementation:
|
|
39
|
-
en:
|
|
40
|
-
zh:
|
|
39
|
+
en: 0cc718002a249cc9
|
|
40
|
+
zh: 969b2a034331ab56
|
|
41
41
|
/deepseek-ai-dsh-client-ui-workspace/understand-the-implementation/the-directory-flow-hole:
|
|
42
42
|
en: 978415ec6b2e0cc0
|
|
43
43
|
zh: 0d5c705ff34fb1da
|
|
@@ -69,5 +69,5 @@
|
|
|
69
69
|
en: 00d7b1ddda7390f5
|
|
70
70
|
zh: 1e5a1055d1be4d1d
|
|
71
71
|
/deepseek-ai-dsh-client-ui-workspace/known-limitations-and-deferred-work/dev-note:
|
|
72
|
-
en:
|
|
73
|
-
zh:
|
|
72
|
+
en: e67f633dc6fe7773
|
|
73
|
+
zh: 3bb0c20c93f16f98
|
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ Pinned Sessions lead ordinary Sessions in both grouped and flat views. **Last up
|
|
|
37
37
|
|
|
38
38
|
Pinning moves the Session to the front of its complete saved sequence without changing the selected mode. It leads the pinned partition in Manual but need not lead it in Last updated. Unpinning leaves the saved position unchanged. Dragging either a pinned or ordinary row edits that same complete sequence and selects Manual; hidden archived members are retained. Missing pinned members prepend in pin-array order. New ordinary forks precede their sources in the complete sequence without inheriting pin membership; visible pins still lead ordinary rows. Other missing members append by recency with missing archives last. Supplementation stays in memory until a Session-order write records the complete result. Archive filtering alone changes neither saved positions nor account membership.
|
|
39
39
|
|
|
40
|
-
The selected blank **New Session** retains its provisional first slot and cannot be dragged; after its first prompt it becomes an ordinary draggable row, retaining that position in Manual or following its current timestamp in Last updated. A collapsed-group drag uses the target Session identity and keeps the source visible. Session display orders for real Workspaces, Ungrouped, and the flat list are browser-local; Workspace group drag order remains Host-durable.
|
|
40
|
+
The selected blank **New Session** retains its provisional first slot and cannot be dragged; after its first prompt it becomes an ordinary draggable row, retaining that position in Manual or following its current timestamp in Last updated. A collapsed-group drag uses the target Session identity and keeps the source visible. Session display orders for real Workspaces, Ungrouped, and the flat list are browser-local; Workspace group drag order remains Host-durable.
|
|
41
41
|
|
|
42
42
|
### Workspace hierarchy
|
|
43
43
|
|
|
@@ -89,6 +89,8 @@ The package is one composition: both target slots are declared by other plugins,
|
|
|
89
89
|
|
|
90
90
|
The browser entry also declares two root-scoped `list` child seats on each Session row: `sidebar.session.row.leading`, rendered only while that row's primary status is idle and left blank on an archived row, and `sidebar.session.row.hover`, mounted only while that row's hover card is open. Both take the row's Session identity and nothing else, so an occupant reads its own data by that id; a Session-scoped seat would force a Session binding, which would activate and retain every listed Session.
|
|
91
91
|
|
|
92
|
+
Client callers can pass `prompt` and `clearPreviousDraft` in the second argument to `uiWorkspace.startSession`. A prompt is plain text; structured drafts are handled internally during restoration and Workspace transfer. Without clearing, existing text or attachments are preserved; explicit clearing replaces only the target Session's text and inline references, leaving attachments intact. Option values are captured before asynchronous creation and applied to the exact retained binding before navigation commits. Superseded navigation does not write content; a submission-blocked input refuses preparation and keeps the previous selection. Calls without options retain the normal New Session flow. The [draft initialization decision](../../../.agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.md) describes the input owner's data flow.
|
|
93
|
+
|
|
92
94
|
### The directory-flow hole
|
|
93
95
|
|
|
94
96
|
Each registration declares a **directory-flow child hole** (`single` kind: `conversation.hero.workspace.directoryFlow` / `sidebar.workspaces.directoryFlow`) that the composed picker package's client half fills with its picking interaction — the `-native` backend's renderless OS-chooser driver, an in-app browsing dialog under a `-browse` composition. The flat **Add workspace...** action renders only while the surface's hole is occupied; an empty hole means the composition has no picking affordance. This package owns the trigger and the adoption: the occupant reports one picked path per open through the hole's owner conversation (`open`/`busy`/`onPicked`/`onCancel`/`onError`), and the owner adopts it through the object layer, selecting the committed Workspace only after its list projection has refreshed.
|
|
@@ -224,5 +226,3 @@ These limits define the search depth, the archive surface, and the picking carri
|
|
|
224
226
|
None.
|
|
225
227
|
|
|
226
228
|
</details>
|
|
227
|
-
|
|
228
|
-
**Runtime invariant:** No companion is published. This is a pure-consumer plugin that registers presentational components into two host-declared slots and registers its locale dictionaries; its inject face consists of stateless RPC wrappers plus a create-and-open call. It emits no Cordis events and owns no cross-plugin mutable state.
|
package/README.zh.md
CHANGED
|
@@ -37,7 +37,7 @@ kind: "package-reference"
|
|
|
37
37
|
|
|
38
38
|
置顶把 Session 移到完整保存序列的首位,但不切换所选模式。它在手动模式下排在置顶区最前,在最近更新模式下则不一定。取消置顶不改变保存的位置。拖拽置顶行或普通行都修改同一条完整序列并选择手动模式,隐藏的归档成员始终保留。缺失的置顶成员按置顶数组顺序补到头部。新增的普通 fork 在完整序列中插在来源之前,不继承置顶成员关系;可见置顶行仍排在普通行之前。其他缺失成员按最近更新时间追加,缺失归档项放在最后。补齐仅在内存中完成,直到一次 Session 顺序写入保存完整结果。单纯切换归档筛选既不改变保存的位置,也不改变成员关系。
|
|
39
39
|
|
|
40
|
-
当前选中的空白**新会话**保留临时首位且无法拖拽;首条提示词落地后,它成为可拖拽的普通行,在手动排序中保留该位置,在最近更新中按当前时间戳排列。折叠分组的拖拽使用目标 Session 身份,并保持来源行可见。真实 Workspace、Ungrouped 与单列表的 Session 显示顺序都保留在浏览器本地;Workspace 分组的拖拽顺序仍由 Host 持久化。
|
|
40
|
+
当前选中的空白**新会话**保留临时首位且无法拖拽;首条提示词落地后,它成为可拖拽的普通行,在手动排序中保留该位置,在最近更新中按当前时间戳排列。折叠分组的拖拽使用目标 Session 身份,并保持来源行可见。真实 Workspace、Ungrouped 与单列表的 Session 显示顺序都保留在浏览器本地;Workspace 分组的拖拽顺序仍由 Host 持久化。
|
|
41
41
|
|
|
42
42
|
### 工作区层级
|
|
43
43
|
|
|
@@ -89,6 +89,8 @@ Workspace 和 Session 的启动基线均就绪后,空安装环境调用 `works
|
|
|
89
89
|
|
|
90
90
|
浏览器入口还为每个 Session 行声明两个 root 作用域的 `list` 子 slot:`sidebar.session.row.leading` 仅在该行主状态为 idle 时渲染、归档行留空,`sidebar.session.row.hover` 仅在该行的悬浮卡片打开时挂载。两者只接收行的 Session 身份,占用方据此读取自己的数据;Session 作用域的 slot 会强制建立 Session 绑定,从而激活并保留列表中每个 Session。
|
|
91
91
|
|
|
92
|
+
客户端调用方可以在 `uiWorkspace.startSession` 的第二参数中传入 `prompt` 与 `clearPreviousDraft`。提示词仅接受普通文字;结构化草稿由内部恢复和工作区携带流程处理。不清空时保留已有文字或附件;显式清空只替换目标 Session 的文字和内联引用,不删除附件。在异步创建前捕获选项值,在导航提交前应用到准确保留的 binding。已被后续操作取代的导航不写内容;输入处于提交阻塞状态时拒绝准备并保留原选择。不传选项时保持正常新会话流程。[草稿初始化决策](../../../.agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.zh.md)说明输入 owner 的数据流。
|
|
93
|
+
|
|
92
94
|
### 目录流子 slot
|
|
93
95
|
|
|
94
96
|
每个注册各自声明一个**目录流子 slot**(`single` kind:`conversation.hero.workspace.directoryFlow`/`sidebar.workspaces.directoryFlow`),由组合的选择器包 client half 填入其选取交互——`-native` 后端的无渲染 OS 选择器驱动,`-browse` 组合下则是应用内浏览对话框。平铺显示的**添加工作区…** 操作仅在当前界面的 slot 被占用时渲染;slot 为空意味着该组合没有目录选择能力。本包持有触发与接纳:占用方通过 slot 的属主交互约定(`open`/`busy`/`onPicked`/`onCancel`/`onError`)每次打开上报一个所选路径,owner 通过对象层接纳它,并等待 Workspace 列表投影刷新后才选中已提交的 Workspace。
|
|
@@ -224,5 +226,3 @@ Workspace 与 Session 悬浮卡片会复制对应行被截断的值:激活 Wor
|
|
|
224
226
|
无。
|
|
225
227
|
|
|
226
228
|
</details>
|
|
227
|
-
|
|
228
|
-
**运行时不变式:** 不发布伴生入口。这是一个纯消费方插件,只向两个由宿主声明的 slot 注册展示组件,并注册自身的 locale dictionaries;inject face 由无状态 RPC 包装层和一次 create-and-open 调用组成;本插件不发出 Cordis 事件,也不持有跨插件可变状态。
|
package/lib/client.js
CHANGED
|
@@ -204,6 +204,630 @@ window.__ModuleLoader__.load({
|
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
206
|
//#endregion
|
|
207
|
+
//#region ../../util/values/src/partial-json.ts
|
|
208
|
+
/**
|
|
209
|
+
* Lazily scanned view of one JSON object's top-level fields, built from text
|
|
210
|
+
* that may still be streaming or from an already parsed object. Nothing is
|
|
211
|
+
* scanned until a reader asks; the view remembers every question it answered
|
|
212
|
+
* and reports changed answers when the owner refreshes for publication.
|
|
213
|
+
* Used for model tool-call arguments: a row reads the fields it
|
|
214
|
+
* cares about at whatever granularity it displays, at every stage of the call.
|
|
215
|
+
* @module @deepseek-ai/dsh-util-values/src/partial-json
|
|
216
|
+
*/
|
|
217
|
+
const SIMPLE_ESCAPES = {
|
|
218
|
+
"\"": "\"",
|
|
219
|
+
"\\": "\\",
|
|
220
|
+
"/": "/",
|
|
221
|
+
b: "\b",
|
|
222
|
+
f: "\f",
|
|
223
|
+
n: "\n",
|
|
224
|
+
r: "\r",
|
|
225
|
+
t: " "
|
|
226
|
+
};
|
|
227
|
+
const CONTENT_ESCAPE = /[\\\u0000-\u001f]/u;
|
|
228
|
+
function isWhitespace(c) {
|
|
229
|
+
return c === " " || c === "\n" || c === "\r" || c === " ";
|
|
230
|
+
}
|
|
231
|
+
function isHex(c) {
|
|
232
|
+
return c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F";
|
|
233
|
+
}
|
|
234
|
+
(class PartialArguments {
|
|
235
|
+
/** The view of a call with no arguments available. */
|
|
236
|
+
static EMPTY = PartialArguments.fromObject({});
|
|
237
|
+
/**
|
|
238
|
+
* View finished argument text without scanning it until a reader asks.
|
|
239
|
+
* @param text - the complete argument JSON text.
|
|
240
|
+
* @returns a sealed view.
|
|
241
|
+
*/
|
|
242
|
+
static fromText(text) {
|
|
243
|
+
const view = new PartialArguments();
|
|
244
|
+
view.append(text);
|
|
245
|
+
view.sealed = true;
|
|
246
|
+
return view;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* View an already parsed argument payload, such as a PTC dispatch object.
|
|
250
|
+
* @param value - the parsed argument value.
|
|
251
|
+
* @returns a sealed view; a non-object payload has no fields.
|
|
252
|
+
*/
|
|
253
|
+
static fromObject(value) {
|
|
254
|
+
const view = new PartialArguments();
|
|
255
|
+
view.object = typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
256
|
+
view.sealed = true;
|
|
257
|
+
return view;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* The source: text so far or a parsed object, plus whether it can still grow.
|
|
261
|
+
* These are the only enumerable fields, so two views over the same source
|
|
262
|
+
* compare equal structurally however far each has been read.
|
|
263
|
+
*/
|
|
264
|
+
chunks = [];
|
|
265
|
+
object;
|
|
266
|
+
sealed = false;
|
|
267
|
+
#ends = [];
|
|
268
|
+
#size = 0;
|
|
269
|
+
#consumed = 0;
|
|
270
|
+
#mode = "root";
|
|
271
|
+
#escape = false;
|
|
272
|
+
#keyStart = 0;
|
|
273
|
+
#keyEscaped = false;
|
|
274
|
+
#key = "";
|
|
275
|
+
#current = null;
|
|
276
|
+
#nestedEnds = [];
|
|
277
|
+
#nestedInString = false;
|
|
278
|
+
#invalidAt;
|
|
279
|
+
#invalidValue = false;
|
|
280
|
+
#entries = /* @__PURE__ */ new Map();
|
|
281
|
+
#order = [];
|
|
282
|
+
#reads = /* @__PURE__ */ new Map();
|
|
283
|
+
/** Whether this view rejects further appends; does not scan text or register reads. */
|
|
284
|
+
get isSealed() {
|
|
285
|
+
return this.sealed;
|
|
286
|
+
}
|
|
287
|
+
/** Whether indexing or a content read found invalid JSON; unread value contents are not validated. */
|
|
288
|
+
get invalid() {
|
|
289
|
+
this.scan();
|
|
290
|
+
return this.#mode === "invalid" || this.#invalidValue;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Retain streamed argument text without scanning or comparing observed answers.
|
|
294
|
+
* @param fragment - the text following every fragment appended before.
|
|
295
|
+
*/
|
|
296
|
+
append(fragment) {
|
|
297
|
+
if (this.sealed) throw new Error("PartialArguments: cannot append to a sealed view");
|
|
298
|
+
if (fragment.length === 0) return;
|
|
299
|
+
this.chunks.push(fragment);
|
|
300
|
+
this.#size += fragment.length;
|
|
301
|
+
this.#ends.push(this.#size);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Reconcile a streamed prefix with authoritative complete text without joining the fragments.
|
|
305
|
+
* @param text - the final argument text, which replaces missing or conflicting deltas.
|
|
306
|
+
* @returns this view sealed with its caches retained when every character matches; otherwise a new sealed view.
|
|
307
|
+
*/
|
|
308
|
+
settle(text) {
|
|
309
|
+
if (this.object !== void 0 || text.length !== this.#size) return PartialArguments.fromText(text);
|
|
310
|
+
let offset = 0;
|
|
311
|
+
for (const chunk of this.chunks) {
|
|
312
|
+
if (!text.startsWith(chunk, offset)) return PartialArguments.fromText(text);
|
|
313
|
+
offset += chunk.length;
|
|
314
|
+
}
|
|
315
|
+
this.chunks = text.length === 0 ? [] : [text];
|
|
316
|
+
this.#ends = text.length === 0 ? [] : [text.length];
|
|
317
|
+
this.sealed = true;
|
|
318
|
+
return this;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Compare observed answers and advance their publication baseline. Unread views remain unscanned.
|
|
322
|
+
* @returns whether any observed answer changed since its first read or the preceding refresh.
|
|
323
|
+
*/
|
|
324
|
+
refresh() {
|
|
325
|
+
if (this.#reads.size === 0) return false;
|
|
326
|
+
this.scan();
|
|
327
|
+
let changed = false;
|
|
328
|
+
let completions = false;
|
|
329
|
+
for (const read of this.#reads.values()) {
|
|
330
|
+
if (read.completion) {
|
|
331
|
+
completions = true;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
changed = this.refreshRead(read) || changed;
|
|
335
|
+
}
|
|
336
|
+
if (completions) {
|
|
337
|
+
for (const read of this.#reads.values()) if (read.completion) changed = this.refreshRead(read) || changed;
|
|
338
|
+
}
|
|
339
|
+
if (this.sealed) this.#reads.clear();
|
|
340
|
+
return changed;
|
|
341
|
+
}
|
|
342
|
+
refreshRead(read) {
|
|
343
|
+
const now = read.answer();
|
|
344
|
+
if (Object.is(now, read.last)) return false;
|
|
345
|
+
read.last = now;
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Check whether no further fields can arrive.
|
|
350
|
+
* @returns whether the outer object closed, indexing failed, or the view is sealed; unread values are not validated.
|
|
351
|
+
*/
|
|
352
|
+
closed() {
|
|
353
|
+
return this.remember("closed", "", () => this.closedNow());
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* List discovered fields in first-appearance order.
|
|
357
|
+
* @returns top-level keys seen so far, in first-appearance order.
|
|
358
|
+
*/
|
|
359
|
+
keys() {
|
|
360
|
+
return this.remember("keys", "", () => this.keysNow(), (keys) => keys.length);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Check whether a top-level field has appeared.
|
|
364
|
+
* @param key - argument name.
|
|
365
|
+
* @returns whether the field has appeared (a string opened or another value began).
|
|
366
|
+
*/
|
|
367
|
+
has(key) {
|
|
368
|
+
return this.remember("has", key, () => this.hasNow(key));
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Check whether a field's closing delimiter has arrived, without validating its contents.
|
|
372
|
+
* @param key - argument name.
|
|
373
|
+
* @returns whether its delimiter arrived and no content reader has reported an error for this value.
|
|
374
|
+
*/
|
|
375
|
+
complete(key) {
|
|
376
|
+
return this.remember("complete", key, () => this.completeNow(key));
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Read string length without materializing its text.
|
|
380
|
+
* @param key - argument name.
|
|
381
|
+
* @param options - change granularity for a streaming string.
|
|
382
|
+
* @returns decoded UTF-16 length of the string field so far; undefined when absent or not a string.
|
|
383
|
+
*/
|
|
384
|
+
stringLength(key, options) {
|
|
385
|
+
const step = Math.max(1, Math.floor(options?.step ?? 1));
|
|
386
|
+
const offset = options?.offset ?? 0;
|
|
387
|
+
return this.remember(`length:${step}:${offset}`, key, () => this.lengthNow(key), (length) => length === void 0 ? void 0 : Math.ceil((length + offset) / step));
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Check a string against a decoded UTF-16 length limit without materializing it.
|
|
391
|
+
* @param key - argument name.
|
|
392
|
+
* @param maxLength - decoded UTF-16 limit, floored to at least zero.
|
|
393
|
+
* @returns whether the string is longer than the limit; false when absent or not a string.
|
|
394
|
+
*/
|
|
395
|
+
stringExceeds(key, maxLength) {
|
|
396
|
+
const limit = Math.max(0, Math.floor(maxLength));
|
|
397
|
+
return this.remember(`exceeds:${limit}`, key, () => (this.lengthNow(key, limit + 1) ?? 0) > limit);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Read a decoded string, including a streaming prefix.
|
|
401
|
+
* @param key - argument name.
|
|
402
|
+
* @returns the string field's decoded text so far; undefined when absent or not a string.
|
|
403
|
+
*/
|
|
404
|
+
text(key) {
|
|
405
|
+
return this.remember("text", key, () => this.textNow(key));
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Read at most the first decoded UTF-16 units of a string.
|
|
409
|
+
* @param key - argument name.
|
|
410
|
+
* @param maxLength - maximum decoded UTF-16 length, floored to at least one.
|
|
411
|
+
* @returns the bounded string prefix; undefined when absent or not a string.
|
|
412
|
+
*/
|
|
413
|
+
textPrefix(key, maxLength) {
|
|
414
|
+
const limit = Math.max(1, Math.floor(maxLength));
|
|
415
|
+
return this.remember(`prefix:${limit}`, key, () => this.textPrefixNow(key, limit));
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Read a completed non-string argument.
|
|
419
|
+
* @param key - argument name.
|
|
420
|
+
* @returns the parsed non-string value once it closed; undefined while open, absent, or a string.
|
|
421
|
+
*/
|
|
422
|
+
value(key) {
|
|
423
|
+
return this.remember("value", key, () => this.valueNow(key));
|
|
424
|
+
}
|
|
425
|
+
/** Answer a question and, on a streaming view, remember it for change detection. */
|
|
426
|
+
remember(kind, key, read, comparison) {
|
|
427
|
+
this.scan();
|
|
428
|
+
const result = read();
|
|
429
|
+
if (!this.sealed) {
|
|
430
|
+
const id = `${kind}/${key}`;
|
|
431
|
+
if (!this.#reads.has(id)) this.#reads.set(id, {
|
|
432
|
+
completion: kind === "complete",
|
|
433
|
+
answer: comparison === void 0 ? read : () => comparison(read()),
|
|
434
|
+
last: comparison === void 0 ? result : comparison(result)
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
439
|
+
closedNow() {
|
|
440
|
+
return this.sealed || this.#mode === "closed" || this.#mode === "invalid";
|
|
441
|
+
}
|
|
442
|
+
keysNow() {
|
|
443
|
+
return this.object === void 0 ? this.#order : Object.keys(this.object);
|
|
444
|
+
}
|
|
445
|
+
hasNow(key) {
|
|
446
|
+
return this.object === void 0 ? this.#entries.has(key) : Object.hasOwn(this.object, key);
|
|
447
|
+
}
|
|
448
|
+
completeNow(key) {
|
|
449
|
+
if (this.object !== void 0) return Object.hasOwn(this.object, key);
|
|
450
|
+
const entry = this.#entries.get(key);
|
|
451
|
+
return entry !== void 0 && entry.end >= 0 && (entry.kind === "string" ? entry.invalidAt === void 0 : !entry.invalid);
|
|
452
|
+
}
|
|
453
|
+
lengthNow(key, limit = Number.POSITIVE_INFINITY) {
|
|
454
|
+
if (this.object !== void 0) {
|
|
455
|
+
const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
|
|
456
|
+
return typeof field === "string" ? field.length : void 0;
|
|
457
|
+
}
|
|
458
|
+
const entry = this.#entries.get(key);
|
|
459
|
+
if (entry?.kind !== "string") return void 0;
|
|
460
|
+
if (entry.text !== void 0 && entry.text.at === entry.end) return entry.text.length;
|
|
461
|
+
const read = entry.length ??= {
|
|
462
|
+
at: entry.start,
|
|
463
|
+
length: 0,
|
|
464
|
+
text: ""
|
|
465
|
+
};
|
|
466
|
+
this.readString(entry, read, limit, false);
|
|
467
|
+
return read.length;
|
|
468
|
+
}
|
|
469
|
+
textNow(key) {
|
|
470
|
+
if (this.object !== void 0) {
|
|
471
|
+
const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
|
|
472
|
+
return typeof field === "string" ? field : void 0;
|
|
473
|
+
}
|
|
474
|
+
const entry = this.#entries.get(key);
|
|
475
|
+
if (entry?.kind !== "string") return void 0;
|
|
476
|
+
if (entry.text === void 0 && entry.end >= 0 && entry.needsDecoding && entry.invalidAt === void 0) {
|
|
477
|
+
let text;
|
|
478
|
+
try {
|
|
479
|
+
text = JSON.parse(`"${this.slice(entry.start, entry.end)}"`);
|
|
480
|
+
} catch (_error) {}
|
|
481
|
+
if (text !== void 0) entry.text = {
|
|
482
|
+
at: entry.end,
|
|
483
|
+
length: text.length,
|
|
484
|
+
text
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const read = entry.text ??= {
|
|
488
|
+
at: entry.start,
|
|
489
|
+
length: 0,
|
|
490
|
+
text: ""
|
|
491
|
+
};
|
|
492
|
+
this.readString(entry, read, Number.POSITIVE_INFINITY, true);
|
|
493
|
+
return read.text;
|
|
494
|
+
}
|
|
495
|
+
textPrefixNow(key, maxLength) {
|
|
496
|
+
if (this.object !== void 0) {
|
|
497
|
+
const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
|
|
498
|
+
return typeof field === "string" ? field.slice(0, maxLength) : void 0;
|
|
499
|
+
}
|
|
500
|
+
const entry = this.#entries.get(key);
|
|
501
|
+
if (entry?.kind !== "string") return void 0;
|
|
502
|
+
const prefixes = entry.prefixes ??= /* @__PURE__ */ new Map();
|
|
503
|
+
let read = prefixes.get(maxLength);
|
|
504
|
+
if (read === void 0) {
|
|
505
|
+
read = {
|
|
506
|
+
at: entry.start,
|
|
507
|
+
length: 0,
|
|
508
|
+
text: ""
|
|
509
|
+
};
|
|
510
|
+
prefixes.set(maxLength, read);
|
|
511
|
+
}
|
|
512
|
+
this.readString(entry, read, maxLength, true);
|
|
513
|
+
return read.text;
|
|
514
|
+
}
|
|
515
|
+
valueNow(key) {
|
|
516
|
+
if (this.object !== void 0) {
|
|
517
|
+
if (!Object.hasOwn(this.object, key)) return void 0;
|
|
518
|
+
const field = this.object[key];
|
|
519
|
+
return typeof field === "string" ? void 0 : field;
|
|
520
|
+
}
|
|
521
|
+
const entry = this.#entries.get(key);
|
|
522
|
+
if (entry?.kind !== "value" || entry.end < 0 || entry.invalid) return void 0;
|
|
523
|
+
if (entry.parsed === void 0) try {
|
|
524
|
+
entry.parsed = JSON.parse(this.slice(entry.start, entry.end));
|
|
525
|
+
} catch (_error) {
|
|
526
|
+
entry.invalid = true;
|
|
527
|
+
this.#invalidValue = true;
|
|
528
|
+
}
|
|
529
|
+
return entry.parsed;
|
|
530
|
+
}
|
|
531
|
+
chunkAt(at) {
|
|
532
|
+
let low = 0;
|
|
533
|
+
let high = this.#ends.length;
|
|
534
|
+
while (low < high) {
|
|
535
|
+
const mid = low + high >>> 1;
|
|
536
|
+
if (this.#ends[mid] <= at) low = mid + 1;
|
|
537
|
+
else high = mid;
|
|
538
|
+
}
|
|
539
|
+
return low;
|
|
540
|
+
}
|
|
541
|
+
/** Materialize only a requested range, never the cumulative source. */
|
|
542
|
+
slice(start, end) {
|
|
543
|
+
if (start >= end) return "";
|
|
544
|
+
const first = this.chunkAt(start);
|
|
545
|
+
const last = this.chunkAt(end - 1);
|
|
546
|
+
const base = first === 0 ? 0 : this.#ends[first - 1];
|
|
547
|
+
if (first === last) return this.chunks[first].slice(start - base, end - base);
|
|
548
|
+
const parts = [this.chunks[first].slice(start - base)];
|
|
549
|
+
for (let i = first + 1; i < last; i++) parts.push(this.chunks[i]);
|
|
550
|
+
parts.push(this.chunks[last].slice(0, end - this.#ends[last - 1]));
|
|
551
|
+
return parts.join("");
|
|
552
|
+
}
|
|
553
|
+
readString(entry, read, limit, materialize) {
|
|
554
|
+
const end = Math.min(entry.end < 0 ? this.#consumed : entry.end, entry.invalidAt ?? Number.POSITIVE_INFINITY, this.#invalidAt ?? Number.POSITIVE_INFINITY);
|
|
555
|
+
if (!entry.needsDecoding) {
|
|
556
|
+
const length = Math.min(end - read.at, limit - read.length);
|
|
557
|
+
if (length <= 0) return;
|
|
558
|
+
if (materialize) read.text += this.slice(read.at, read.at + length);
|
|
559
|
+
read.at += length;
|
|
560
|
+
read.length += length;
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
let chunkIndex = this.chunkAt(read.at);
|
|
564
|
+
while (read.at < end && read.length < limit) {
|
|
565
|
+
const base = chunkIndex === 0 ? 0 : this.#ends[chunkIndex - 1];
|
|
566
|
+
const chunk = this.chunks[chunkIndex];
|
|
567
|
+
const remaining = chunk.slice(read.at - base, Math.min(chunk.length, end - base));
|
|
568
|
+
const boundary = remaining.search(CONTENT_ESCAPE);
|
|
569
|
+
const length = Math.min(boundary < 0 ? remaining.length : boundary, limit - read.length);
|
|
570
|
+
if (length > 0) {
|
|
571
|
+
if (materialize) read.text += remaining.slice(0, length);
|
|
572
|
+
read.at += length;
|
|
573
|
+
read.length += length;
|
|
574
|
+
if (read.at === base + chunk.length) chunkIndex++;
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const type = remaining.length > 1 ? remaining[1] : read.at + 1 < end ? this.chunks[chunkIndex + 1][0] : void 0;
|
|
578
|
+
let decoded;
|
|
579
|
+
let width = 2;
|
|
580
|
+
if (remaining[0] === "\\" && type === void 0 && entry.end < 0) return;
|
|
581
|
+
if (remaining[0] === "\\" && type === "u") {
|
|
582
|
+
const hex = this.slice(read.at + 2, Math.min(end, read.at + 6));
|
|
583
|
+
let valid = true;
|
|
584
|
+
for (let i = 0; i < hex.length; i++) if (!isHex(hex[i])) valid = false;
|
|
585
|
+
if (valid) {
|
|
586
|
+
if (hex.length < 4 && entry.end < 0) return;
|
|
587
|
+
if (hex.length === 4) decoded = String.fromCharCode(Number.parseInt(hex, 16));
|
|
588
|
+
}
|
|
589
|
+
width = 6;
|
|
590
|
+
} else if (remaining[0] === "\\" && type !== void 0) decoded = SIMPLE_ESCAPES[type];
|
|
591
|
+
if (decoded === void 0) {
|
|
592
|
+
entry.invalidAt = read.at;
|
|
593
|
+
this.#invalidValue = true;
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
if (materialize) read.text += decoded;
|
|
597
|
+
read.length++;
|
|
598
|
+
read.at += width;
|
|
599
|
+
while (chunkIndex < this.chunks.length && read.at >= this.#ends[chunkIndex]) chunkIndex++;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/** Locate new field ranges without decoding or parsing their contents. */
|
|
603
|
+
scan() {
|
|
604
|
+
if (this.object !== void 0 || this.#consumed === this.#size) return;
|
|
605
|
+
for (let i = this.chunkAt(this.#consumed); i < this.chunks.length && this.#invalidAt === void 0; i++) {
|
|
606
|
+
const pending = this.chunks[i];
|
|
607
|
+
const base = i === 0 ? 0 : this.#ends[i - 1];
|
|
608
|
+
for (let index = this.#consumed - base; index < pending.length && this.#mode !== "invalid"; index++) {
|
|
609
|
+
if (this.#mode === "string" || this.#mode === "nested" && this.#nestedInString) {
|
|
610
|
+
const end = this.stringBoundary(pending, index);
|
|
611
|
+
this.#consumed += end - index;
|
|
612
|
+
index = end;
|
|
613
|
+
if (index === pending.length) break;
|
|
614
|
+
}
|
|
615
|
+
this.step(pending[index], this.#consumed);
|
|
616
|
+
this.#consumed++;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/** Only raw quotes and their preceding backslash runs can terminate a string. */
|
|
621
|
+
stringBoundary(fragment, start) {
|
|
622
|
+
let at = start;
|
|
623
|
+
while (true) {
|
|
624
|
+
const quote = fragment.indexOf("\"", at);
|
|
625
|
+
const end = quote < 0 ? fragment.length : quote;
|
|
626
|
+
if (this.#mode === "string") {
|
|
627
|
+
const entry = this.#current;
|
|
628
|
+
if (!entry.needsDecoding && CONTENT_ESCAPE.test(fragment.slice(at, end))) entry.needsDecoding = true;
|
|
629
|
+
}
|
|
630
|
+
let slashStart = end;
|
|
631
|
+
while (slashStart > at && fragment[slashStart - 1] === "\\") slashStart--;
|
|
632
|
+
const escaped = (end - slashStart) % 2 === 1 !== (slashStart === at && this.#escape);
|
|
633
|
+
this.#escape = quote < 0 && escaped;
|
|
634
|
+
if (quote < 0 || !escaped) return end;
|
|
635
|
+
at = quote + 1;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
step(c, at) {
|
|
639
|
+
switch (this.#mode) {
|
|
640
|
+
case "root":
|
|
641
|
+
if (isWhitespace(c)) return;
|
|
642
|
+
if (c === "{") {
|
|
643
|
+
this.#mode = "key-or-end";
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
this.fail();
|
|
647
|
+
return;
|
|
648
|
+
case "key-or-end":
|
|
649
|
+
if (isWhitespace(c)) return;
|
|
650
|
+
if (c === "}") {
|
|
651
|
+
this.#mode = "closed";
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (c === "\"") {
|
|
655
|
+
this.beginKey(at);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
this.fail();
|
|
659
|
+
return;
|
|
660
|
+
case "key-only":
|
|
661
|
+
if (isWhitespace(c)) return;
|
|
662
|
+
if (c === "\"") {
|
|
663
|
+
this.beginKey(at);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
this.fail();
|
|
667
|
+
return;
|
|
668
|
+
case "key":
|
|
669
|
+
this.stepKey(c, at);
|
|
670
|
+
return;
|
|
671
|
+
case "colon":
|
|
672
|
+
if (isWhitespace(c)) return;
|
|
673
|
+
if (c === ":") {
|
|
674
|
+
this.#mode = "value";
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
this.fail();
|
|
678
|
+
return;
|
|
679
|
+
case "value":
|
|
680
|
+
this.beginValue(c, at);
|
|
681
|
+
return;
|
|
682
|
+
case "string": {
|
|
683
|
+
const entry = this.#current;
|
|
684
|
+
entry.end = at;
|
|
685
|
+
this.#current = null;
|
|
686
|
+
this.#mode = "comma-or-end";
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
case "scalar":
|
|
690
|
+
this.stepScalar(c, at);
|
|
691
|
+
return;
|
|
692
|
+
case "nested":
|
|
693
|
+
this.stepNested(c, at);
|
|
694
|
+
return;
|
|
695
|
+
case "comma-or-end":
|
|
696
|
+
if (isWhitespace(c)) return;
|
|
697
|
+
if (c === ",") {
|
|
698
|
+
this.#mode = "key-only";
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (c === "}") {
|
|
702
|
+
this.#mode = "closed";
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
this.fail();
|
|
706
|
+
return;
|
|
707
|
+
case "closed":
|
|
708
|
+
if (isWhitespace(c)) return;
|
|
709
|
+
this.fail();
|
|
710
|
+
return;
|
|
711
|
+
/* v8 ignore next 2 -- scan() stops stepping once the view is invalid. */
|
|
712
|
+
case "invalid": return;
|
|
713
|
+
/* v8 ignore next 2 -- Every scanner mode has a handler above. */
|
|
714
|
+
default: assertNever$1(this.#mode);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
fail() {
|
|
718
|
+
this.#invalidAt = this.#consumed;
|
|
719
|
+
this.#mode = "invalid";
|
|
720
|
+
this.#current = null;
|
|
721
|
+
}
|
|
722
|
+
beginKey(at) {
|
|
723
|
+
this.#mode = "key";
|
|
724
|
+
this.#keyStart = at + 1;
|
|
725
|
+
this.#keyEscaped = false;
|
|
726
|
+
this.#escape = false;
|
|
727
|
+
}
|
|
728
|
+
stepKey(c, at) {
|
|
729
|
+
if (c < " ") {
|
|
730
|
+
this.fail();
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (this.#escape) {
|
|
734
|
+
this.#escape = false;
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (c === "\\") {
|
|
738
|
+
this.#escape = true;
|
|
739
|
+
this.#keyEscaped = true;
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (c !== "\"") return;
|
|
743
|
+
const raw = this.slice(this.#keyStart, at);
|
|
744
|
+
if (this.#keyEscaped) try {
|
|
745
|
+
this.#key = JSON.parse(`"${raw}"`);
|
|
746
|
+
} catch (_error) {
|
|
747
|
+
this.fail();
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
else this.#key = raw;
|
|
751
|
+
this.#mode = "colon";
|
|
752
|
+
}
|
|
753
|
+
open(entry) {
|
|
754
|
+
if (!this.#entries.has(this.#key)) this.#order.push(this.#key);
|
|
755
|
+
this.#entries.set(this.#key, entry);
|
|
756
|
+
this.#current = entry;
|
|
757
|
+
}
|
|
758
|
+
beginValue(c, at) {
|
|
759
|
+
if (isWhitespace(c)) return;
|
|
760
|
+
if (c === "\"") {
|
|
761
|
+
this.open({
|
|
762
|
+
kind: "string",
|
|
763
|
+
start: at + 1,
|
|
764
|
+
end: -1,
|
|
765
|
+
needsDecoding: false,
|
|
766
|
+
invalidAt: void 0,
|
|
767
|
+
length: void 0,
|
|
768
|
+
text: void 0,
|
|
769
|
+
prefixes: void 0
|
|
770
|
+
});
|
|
771
|
+
this.#escape = false;
|
|
772
|
+
this.#mode = "string";
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (c === "}" || c === "," || c === ":" || c === "]") {
|
|
776
|
+
this.fail();
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
this.open({
|
|
780
|
+
kind: "value",
|
|
781
|
+
start: at,
|
|
782
|
+
end: -1,
|
|
783
|
+
parsed: void 0,
|
|
784
|
+
invalid: false
|
|
785
|
+
});
|
|
786
|
+
if (c === "{" || c === "[") {
|
|
787
|
+
this.#mode = "nested";
|
|
788
|
+
this.#nestedEnds = [c === "{" ? "}" : "]"];
|
|
789
|
+
this.#nestedInString = false;
|
|
790
|
+
this.#escape = false;
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
this.#mode = "scalar";
|
|
794
|
+
}
|
|
795
|
+
stepScalar(c, at) {
|
|
796
|
+
if (c !== "," && c !== "}" && !isWhitespace(c)) return;
|
|
797
|
+
this.closeValue(at);
|
|
798
|
+
this.#mode = c === "," ? "key-only" : c === "}" ? "closed" : "comma-or-end";
|
|
799
|
+
}
|
|
800
|
+
stepNested(c, at) {
|
|
801
|
+
if (this.#nestedInString) {
|
|
802
|
+
this.#nestedInString = false;
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
if (c === "\"") {
|
|
806
|
+
this.#nestedInString = true;
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
if (c === "{" || c === "[") {
|
|
810
|
+
this.#nestedEnds.push(c === "{" ? "}" : "]");
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (c === "}" || c === "]") {
|
|
814
|
+
if (this.#nestedEnds.pop() !== c) {
|
|
815
|
+
this.fail();
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (this.#nestedEnds.length === 0) {
|
|
819
|
+
this.closeValue(at + 1);
|
|
820
|
+
this.#mode = "comma-or-end";
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
closeValue(end) {
|
|
825
|
+
const entry = this.#current;
|
|
826
|
+
entry.end = end;
|
|
827
|
+
this.#current = null;
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
//#endregion
|
|
207
831
|
//#region ../../util/values/src/index.ts
|
|
208
832
|
/**
|
|
209
833
|
* Mark an unreachable closed-union branch.
|
|
@@ -843,7 +1467,9 @@ window.__ModuleLoader__.load({
|
|
|
843
1467
|
...onCreated === void 0 ? {} : { onCreated }
|
|
844
1468
|
});
|
|
845
1469
|
}
|
|
846
|
-
startSession(workspaceId) {
|
|
1470
|
+
startSession(workspaceId, options) {
|
|
1471
|
+
const draftOptions = options === void 0 ? void 0 : { ...options };
|
|
1472
|
+
const initializeDraft = draftOptions !== void 0 && (draftOptions.prompt !== void 0 || draftOptions.clearPreviousDraft === true);
|
|
847
1473
|
const workspace = this.workspaces.list.getSnapshot();
|
|
848
1474
|
const sessions = this.sessions.list.getSnapshot();
|
|
849
1475
|
const current = this.mainReference?.sessionId;
|
|
@@ -851,13 +1477,37 @@ window.__ModuleLoader__.load({
|
|
|
851
1477
|
const recent = workspace.phase === "ready" && sessions.phase === "ready" ? recentWorkspace(workspace.items, sessions.byId) : void 0;
|
|
852
1478
|
const target = workspaceId ?? currentWorkspaceId ?? recent;
|
|
853
1479
|
if (target === void 0) {
|
|
1480
|
+
if (initializeDraft) {
|
|
1481
|
+
this.notify({
|
|
1482
|
+
kind: "createFailed",
|
|
1483
|
+
message: this.ctx.locale.bind("workspace")("draft.workspaceRequired")
|
|
1484
|
+
});
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
854
1487
|
this.clearMain();
|
|
855
1488
|
return;
|
|
856
1489
|
}
|
|
857
|
-
this.openWorkspace(target
|
|
1490
|
+
this.openWorkspace(target, initializeDraft ? (id) => {
|
|
1491
|
+
const binding = this.sessions.binding(id);
|
|
1492
|
+
if (binding === void 0) this.draftPreparationFailed();
|
|
1493
|
+
this.prepareDraft(binding, draftOptions);
|
|
1494
|
+
} : void 0).catch((reason) => {
|
|
858
1495
|
console.warn("new session failed:", reason);
|
|
859
1496
|
});
|
|
860
1497
|
}
|
|
1498
|
+
prepareDraft(binding, options) {
|
|
1499
|
+
const conversation = this.ctx.get("conversation");
|
|
1500
|
+
if (conversation === void 0) this.draftPreparationFailed();
|
|
1501
|
+
if (conversation.input.requestDraftInitialization(binding, options) === "blocked") this.draftPreparationFailed();
|
|
1502
|
+
}
|
|
1503
|
+
draftPreparationFailed() {
|
|
1504
|
+
const message = this.ctx.locale.bind("workspace")("draft.initializationFailed");
|
|
1505
|
+
this.notify({
|
|
1506
|
+
kind: "createFailed",
|
|
1507
|
+
message
|
|
1508
|
+
});
|
|
1509
|
+
throw new Error(message);
|
|
1510
|
+
}
|
|
861
1511
|
async archiveSession(sessionId, options = {}) {
|
|
862
1512
|
await this.workspaces.archiveSession(sessionId, options);
|
|
863
1513
|
if (this.mainReference?.sessionId === sessionId) this.clearMain();
|
|
@@ -3866,6 +4516,8 @@ window.__ModuleLoader__.load({
|
|
|
3866
4516
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
3867
4517
|
const zh = {
|
|
3868
4518
|
"defaultWorkspace.failed": "无法创建默认工作区,请通过“选择工作区”选择文件夹",
|
|
4519
|
+
"draft.workspaceRequired": "请先选择工作区",
|
|
4520
|
+
"draft.initializationFailed": "暂时无法填入草稿,请稍后重试",
|
|
3869
4521
|
"group.ungrouped": "未分组",
|
|
3870
4522
|
"session.new": "新会话",
|
|
3871
4523
|
"session.untitled": "未命名",
|
|
@@ -3981,6 +4633,8 @@ window.__ModuleLoader__.load({
|
|
|
3981
4633
|
/** English dictionary, checked complete against the zh key set. */
|
|
3982
4634
|
const en = {
|
|
3983
4635
|
"defaultWorkspace.failed": "Unable to create default workspace. Use Choose workspace to select a folder.",
|
|
4636
|
+
"draft.workspaceRequired": "Choose a workspace first.",
|
|
4637
|
+
"draft.initializationFailed": "Could not fill the draft. Please try again shortly.",
|
|
3984
4638
|
"group.ungrouped": "Ungrouped",
|
|
3985
4639
|
"session.new": "New Session",
|
|
3986
4640
|
"session.untitled": "Untitled",
|
|
@@ -2,7 +2,7 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
2
2
|
import type { WorkspaceSnapshot } from '@deepseek-ai/dsh-api-workspace-controller/client';
|
|
3
3
|
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
|
|
4
4
|
import { type WorkspaceKey } from './locales.ts';
|
|
5
|
-
export type { UiWorkspace } from './navigation.ts';
|
|
5
|
+
export type { StartSessionOptions, UiWorkspace } from './navigation.ts';
|
|
6
6
|
export type { DirectoryFlowOwnerProps, DirectoryFlowSlotName, DirectoryPickingHooks, DirectoryPickingInjected, MenuOpenState, RowToast, SessionRenameTarget, SessionRowOwnerProps, UseMenuOpenState, WorkspaceBrowserInjected, SessionRowScheduleOwnerProps, WorkspaceBrowserProps, WorkspacePickerInjected, WorkspacePickerProps, } from './contract/slots.ts';
|
|
7
7
|
export type { WorkspaceKey } from './locales.ts';
|
|
8
8
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
7
7
|
export declare const zh: {
|
|
8
8
|
'defaultWorkspace.failed': string;
|
|
9
|
+
'draft.workspaceRequired': string;
|
|
10
|
+
'draft.initializationFailed': string;
|
|
9
11
|
'group.ungrouped': string;
|
|
10
12
|
'session.new': string;
|
|
11
13
|
'session.untitled': string;
|
|
@@ -123,6 +125,8 @@ export type WorkspaceKey = keyof typeof zh;
|
|
|
123
125
|
/** English dictionary, checked complete against the zh key set. */
|
|
124
126
|
export declare const en: {
|
|
125
127
|
'defaultWorkspace.failed': string;
|
|
128
|
+
'draft.workspaceRequired': string;
|
|
129
|
+
'draft.initializationFailed': string;
|
|
126
130
|
'group.ungrouped': string;
|
|
127
131
|
'session.new': string;
|
|
128
132
|
'session.untitled': string;
|
|
@@ -4,8 +4,11 @@ import type { ClientRemote, DirectoryListing, RemoteFailure } from '@deepseek-ai
|
|
|
4
4
|
import type { ISessions, SessionTarget } from '@deepseek-ai/dsh-api-session-controller/client';
|
|
5
5
|
import type { IWorkspaces, WorkspaceId } from '@deepseek-ai/dsh-api-workspace-controller/client';
|
|
6
6
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
7
|
+
import type { DraftInitializationOptions } from '@deepseek-ai/dsh-client-ui-conversation/client';
|
|
7
8
|
import type { RowToast } from './contract/slots.ts';
|
|
8
9
|
import type { WorkspaceViewStoreActions } from './stores.ts';
|
|
10
|
+
/** Optional content preparation for the resolved target Session. */
|
|
11
|
+
export type StartSessionOptions = DraftInitializationOptions;
|
|
9
12
|
/** Workspace archive and directory operations consumed by Client UI domains. */
|
|
10
13
|
export interface UiWorkspace {
|
|
11
14
|
/**
|
|
@@ -40,8 +43,9 @@ export interface UiWorkspace {
|
|
|
40
43
|
* Start a New Session flow and navigate to its Session; a creation the Host
|
|
41
44
|
* refuses is shown through the Workspace notice and leaves the selection as it was.
|
|
42
45
|
* @param workspaceId - explicit target; absent inherits the current or most recent Workspace.
|
|
46
|
+
* @param options - initial content; existing text or attachments are preserved unless clearPreviousDraft is true.
|
|
43
47
|
*/
|
|
44
|
-
startSession(workspaceId?: WorkspaceId): void;
|
|
48
|
+
startSession(workspaceId?: WorkspaceId, options?: StartSessionOptions): void;
|
|
45
49
|
/**
|
|
46
50
|
* Archive a Session and clear it when it is the current selection.
|
|
47
51
|
* @param sessionId - Session to archive.
|
|
@@ -127,7 +131,9 @@ declare class UiWorkspaceService extends Service implements UiWorkspace {
|
|
|
127
131
|
openSession(target: SessionTarget): void;
|
|
128
132
|
openWorkspace(workspaceId: WorkspaceId, beforeOpen?: (sessionId: SessionId) => void): Promise<void>;
|
|
129
133
|
forkSession(sessionId: SessionId, onCreated?: (childId: SessionId) => void): Promise<SessionId>;
|
|
130
|
-
startSession(workspaceId?: WorkspaceId): void;
|
|
134
|
+
startSession(workspaceId?: WorkspaceId, options?: StartSessionOptions): void;
|
|
135
|
+
private prepareDraft;
|
|
136
|
+
private draftPreparationFailed;
|
|
131
137
|
archiveSession(sessionId: SessionId, options?: {
|
|
132
138
|
readonly stopActivity?: boolean;
|
|
133
139
|
}): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-ui-workspace",
|
|
3
3
|
"description": "Workspace picker plugin: one WorkspacePicker registered into the sidebar and empty-state workspace slots",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.1-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -45,37 +45,37 @@
|
|
|
45
45
|
},
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@deepseek-ai/cordis": "~4.0.
|
|
48
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/react": "~18.3.1",
|
|
52
52
|
"clsx": "^2.0.0",
|
|
53
53
|
"react": "^18.2.0",
|
|
54
|
-
"@deepseek-ai/dsh-
|
|
55
|
-
"@deepseek-ai/
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
58
|
-
"@deepseek-ai/
|
|
59
|
-
"@deepseek-ai/dsh-client-
|
|
60
|
-
"@deepseek-ai/dsh-client-
|
|
61
|
-
"@deepseek-ai/dsh-client-
|
|
62
|
-
"@deepseek-ai/dsh-client-
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-
|
|
65
|
-
"@deepseek-ai/dsh-client-ui-
|
|
66
|
-
"@deepseek-ai/dsh-client-ui-
|
|
67
|
-
"@deepseek-ai/dsh-client-ui-
|
|
68
|
-
"@deepseek-ai/dsh-client-ui-
|
|
69
|
-
"@deepseek-ai/dsh-
|
|
70
|
-
"@deepseek-ai/dsh-
|
|
71
|
-
"@deepseek-ai/dsh-
|
|
72
|
-
"@deepseek-ai/dsh-
|
|
73
|
-
"@deepseek-ai/dsh-
|
|
74
|
-
"@deepseek-ai/dsh-
|
|
75
|
-
"@deepseek-ai/dsh-
|
|
76
|
-
"@deepseek-ai/dsh-
|
|
77
|
-
"@deepseek-ai/dsh-client-product-analytics": "0.2.
|
|
78
|
-
"@deepseek-ai/dsh-
|
|
54
|
+
"@deepseek-ai/dsh-api-remotes": "0.2.1-alpha.1",
|
|
55
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.1",
|
|
56
|
+
"@deepseek-ai/dsh-agent": "0.2.1-alpha.1",
|
|
57
|
+
"@deepseek-ai/dsh-api-workspace-controller": "0.2.1-alpha.1",
|
|
58
|
+
"@deepseek-ai/dsh-api-session-controller": "0.2.1-alpha.1",
|
|
59
|
+
"@deepseek-ai/dsh-client-connection": "0.2.1-alpha.1",
|
|
60
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-alpha.1",
|
|
61
|
+
"@deepseek-ai/dsh-client-store": "0.2.1-alpha.1",
|
|
62
|
+
"@deepseek-ai/dsh-client-test-runtime": "0.2.1-alpha.1",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.2.1-alpha.1",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-alpha.1",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-alpha.1",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-alpha.1",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-session": "0.2.1-alpha.1",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-alpha.1",
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-alpha.1",
|
|
70
|
+
"@deepseek-ai/dsh-jobs": "0.2.1-alpha.1",
|
|
71
|
+
"@deepseek-ai/dsh-session": "0.2.1-alpha.1",
|
|
72
|
+
"@deepseek-ai/dsh-subagent": "0.2.1-alpha.1",
|
|
73
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.1",
|
|
74
|
+
"@deepseek-ai/dsh-util-values": "0.2.1-alpha.1",
|
|
75
|
+
"@deepseek-ai/dsh-schedule": "0.2.1-alpha.1",
|
|
76
|
+
"@deepseek-ai/dsh-util-workspace-path": "0.2.1-alpha.1",
|
|
77
|
+
"@deepseek-ai/dsh-client-product-analytics": "0.2.1-alpha.1",
|
|
78
|
+
"@deepseek-ai/dsh-client-shortcuts": "0.2.1-alpha.1"
|
|
79
79
|
},
|
|
80
80
|
"files": [
|
|
81
81
|
"lib/index.js",
|