@deepseek-ai/dsh-client-ui-workspace 0.2.0-rc.2 → 0.2.1-alpha.2
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 +12 -12
- package/README.md +8 -8
- package/README.zh.md +8 -8
- package/lib/client.js +706 -20
- package/lib/types/client/contract/slots.d.ts +11 -5
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/locales.d.ts +8 -0
- package/lib/types/client/navigation.d.ts +14 -5
- package/lib/types/client/session-actions/RowActionToast.d.ts +3 -7
- 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
|
|
@@ -27,20 +27,20 @@
|
|
|
27
27
|
en: 9eab3bfbaf98c987
|
|
28
28
|
zh: 54e7e141dae5c9e8
|
|
29
29
|
/deepseek-ai-dsh-client-ui-workspace/use-this-package/managing-sessions:
|
|
30
|
-
en:
|
|
31
|
-
zh:
|
|
30
|
+
en: cafc87e8e0ac4058
|
|
31
|
+
zh: 4c35bc5bfa24ba6c
|
|
32
32
|
/deepseek-ai-dsh-client-ui-workspace/use-this-package/pending-interactions:
|
|
33
33
|
en: 9fb88260380bf093
|
|
34
34
|
zh: 3bace2b96ecd4a82
|
|
35
35
|
/deepseek-ai-dsh-client-ui-workspace/use-this-package/active-schedule-markers:
|
|
36
|
-
en:
|
|
37
|
-
zh:
|
|
36
|
+
en: 04956ad6ba31cc86
|
|
37
|
+
zh: 0e5fb72cfa932d25
|
|
38
38
|
/deepseek-ai-dsh-client-ui-workspace/understand-the-implementation:
|
|
39
|
-
en:
|
|
40
|
-
zh:
|
|
39
|
+
en: 721d5a49fe231191
|
|
40
|
+
zh: 3ef1dd720219e00e
|
|
41
41
|
/deepseek-ai-dsh-client-ui-workspace/understand-the-implementation/the-directory-flow-hole:
|
|
42
|
-
en:
|
|
43
|
-
zh:
|
|
42
|
+
en: 868904dbfb5713f8
|
|
43
|
+
zh: 225906bfef7398bf
|
|
44
44
|
/deepseek-ai-dsh-client-ui-workspace/understand-the-implementation/session-row-actions:
|
|
45
45
|
en: a3b80d0e844d2929
|
|
46
46
|
zh: 85fe5dfb2968b491
|
|
@@ -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
|
|
|
@@ -51,13 +51,13 @@ Collapsed search is one header action beside the view and add actions: activatin
|
|
|
51
51
|
|
|
52
52
|
### Managing sessions
|
|
53
53
|
|
|
54
|
-
The Session row's Rename action opens a dialog prefilled with the row's display title; confirming an unchanged title is deliberately allowed — it pins the current automatic title against regeneration. Double-clicking a title also opens Rename; for an unarchived Session, the preceding clicks open its conversation. Rename uses a temporary `workspaceOperation` reference and awaits its initial history opening. The row's Fork action
|
|
54
|
+
The Session row's Rename action opens a dialog prefilled with the row's display title; confirming an unchanged title is deliberately allowed — it pins the current automatic title against regeneration. Double-clicking a title also opens Rename; for an unarchived Session, the preceding clicks open its conversation. Rename uses a temporary `workspaceOperation` reference and awaits its initial history opening. The row's Fork action reads ordinary cold history normally. When the Host reports that source migration is required, an app-wide Toast offers to open that Session; the action only navigates and does not retry fork. A successful fork uses the source's last completed turn and increments the inherited persisted title through Session Controller without retaining the child, opening its history, or changing selection. Workspace Delete opens a confirmation that states the retention boundary; success removes the group while its Sessions remain under Ungrouped. Pin, Rename, Fork, and Archive are themselves entries of the `sidebar.workspaces.session.menu.item` list (pin and archive also of `sidebar.workspaces.session.row.action`), so a client plugin's action takes whatever position its `order` gives it.
|
|
55
55
|
|
|
56
56
|
Archive commits without a confirmation dialog for a quiet Session and retains the Session's account position. A Session with running work is the one case that asks first: the Host refuses the plain archive and names that work, and the sidebar opens a stop-and-archive dialog listing it by family — the turn in progress, running subagents, background jobs, scheduled reminders, each with its labels — with the restore path stated; confirming asks the Host to stop the work the way the stop button does and archives once the archive set is durable, while the stops settle in the background, and cancelling leaves the Session running and visible. View options control visibility through one explicit three-way choice: Hide archived (the default) hides archived Sessions, All conversations (show archived) includes them, and Archived only hides ordinary Sessions and drops Workspaces without an archived Session; in tree grouping, children of a dropped Workspace nest under the nearest shown ancestor. Visible archived rows are grayed and carry an accessible explanation that they cannot be opened until restored; Rename, Fork, and Unarchive remain available. A successful archive shows a notice with an undo action and — while archived rows are hidden — a "filter archived sessions" action that switches the filter to All conversations (show archived); a stop-and-archive shows the same notice under its own wording, and undo restores the Session without resuming the stopped work. Unarchive removes the archive mark without restoring a pin or changing the saved position. An empty list shows a centered glyph-over-text placeholder; the Archived-only view names its own emptiness (No archived sessions yet) and adds a View other sessions text action that switches the filter back to Hide archived.
|
|
57
57
|
|
|
58
58
|
Session update times use the tertiary label color, including on archived rows. A Session title wider than its row is clipped with an ellipsis at rest. Hovering the row scrolls the title to its far edge — the incremented title of a fork, for example — and reveals it without the ellipsis; leaving the row returns the title to its start.
|
|
59
59
|
|
|
60
|
-
The keyboard reference exposes New Session, Search sessions, Add workspace, Rename session, Fork session, and Archive session. Desktop defaults use the platform's primary modifier, with Mod+K for search; Windows and macOS Web use the [shortcut service’s platform defaults](../shortcuts/README.md); Linux Web leaves these commands unbound until configured. Button tooltips and Session row menus display the effective bindings. Clicking a menu item targets that row; pressing its shortcut targets the main Session. Desktop and Windows/macOS Web rename use Mod+Alt+G, leaving Mod+Shift+R to browser reload. Rename requires a nonblank main Conversation with no modal obscuring it; a retained Session behind another main panel is unavailable. Other Windows and macOS Desktop bindings also execute from terminal input and modal dialogs; other environments follow the command's region and modal restrictions. Search and rename requests belong to this package; input drafts remain in the browser. The directory picker rejects another open while selection or Workspace adoption is pending. Fork captures the
|
|
60
|
+
The keyboard reference exposes New Session, Search sessions, Add workspace, Rename session, Fork session, and Archive session. Desktop defaults use the platform's primary modifier, with Mod+K for search; Windows and macOS Web use the [shortcut service’s platform defaults](../shortcuts/README.md); Linux Web leaves these commands unbound until configured. Button tooltips and Session row menus display the effective bindings. Clicking a menu item targets that row; pressing its shortcut targets the main Session. Desktop and Windows/macOS Web rename use Mod+Alt+G, leaving Mod+Shift+R to browser reload. Rename requires a nonblank main Conversation with no modal obscuring it; a retained Session behind another main panel is unavailable. Other Windows and macOS Desktop bindings also execute from terminal input and modal dialogs; other environments follow the command's region and modal restrictions. Search and rename requests belong to this package; input drafts remain in the browser. The directory picker rejects another open while selection or Workspace adoption is pending. Fork captures the retained main-view Session, for which navigation has requested history opening, and keeps automatic migration. It uses the row action's Host operation to select its last completed turn, without loading older Client history. Missing or blank Sessions are unavailable; the Host rejects a source with no completed turn. A rejected shortcut fork preserves the current selection, displays a localized notification, and can be retried; unexpected failures also retain diagnostic logging.
|
|
61
61
|
|
|
62
62
|
### Pending interactions
|
|
63
63
|
|
|
@@ -69,11 +69,11 @@ Grouped and flat Session rows show a clock mark when the Session has active sche
|
|
|
69
69
|
|
|
70
70
|
-----
|
|
71
71
|
|
|
72
|
-
`ctx.uiWorkspace.openSession(target)` synchronously replaces the owned `mainView` reference and returns the main area to Conversation without waiting for `reference.ready`, so history loading renders inside the selected Session view. The target may be a known Session id or a durable direct-parent subagent address; an explicit address does not require a preloaded parent catalog. `openWorkspace(id, beforeOpen?)` opens
|
|
72
|
+
`ctx.uiWorkspace.openSession(target)` synchronously replaces the owned `mainView` reference and returns the main area to Conversation without waiting for `reference.ready`, so history loading renders inside the selected Session view. The target may be a known Session id or a durable direct-parent subagent address; an explicit address does not require a preloaded parent catalog. `openWorkspace(id, beforeOpen?)` opens a Workspace blank only if no later navigation has superseded the request; an explicit New Session creates a fresh Session. `forkSession(id)` creates the child without navigating or superseding a pending navigation. The optional synchronous preparation callback runs after the target is retained and only for a current Workspace request, so superseded requests do not move composer drafts. Navigation or owner disposal suppresses the late UI commit, not the underlying Session creation. Startup restoration retains its main reference without changing the selected panel or cancelling a later navigation. Archiving the main Session releases its reference and clears the main selection. Selection failure leaves a global panel visible. Session rows read `usePanelInfo` to suppress their selected appearance while a global panel is active; search and directory-picker focus alone do not leave that panel.
|
|
73
73
|
|
|
74
74
|
Navigation and startup restoration obtain the selected Session's projections from its `follow`; neither issues a separate projection refresh for that Session or its parent.
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
Workspace reopening and explicit draft preparation try to acquire the first eligible blank in catalog order; startup restoration tries the saved blank. If that writer is held, navigation creates a new Session without trying other blanks. Other acquisition failures abort the request: an explicit New Session or hero Workspace pick shows the failure as a transient notice quoting the Host's code and message (for example a preset that fails to mount) or, for a failure that is not a Host refusal, that failure's own message; a request a later navigation or owner disposal superseded raises no notice, and startup restoration reports only to the console. Released blanks retain their slash-command state when reused. Later navigation cancels a pending startup selection.
|
|
77
77
|
|
|
78
78
|
Once both Workspace and Session startup baselines are ready, an empty installation calls `workspaces.initializeDefault` and creates or reuses its blank Session. The composer becomes editable when that Session is selected; no message is submitted automatically. Later navigation or owner disposal prevents startup from selecting its result. Ineligible first use leaves the folder picker available without an error. Default Workspace creation failure shows a transient notice directing the user to Choose workspace, and is not retried until the next startup. Session creation failures use the ordinary restoration error handling. The registered Workspace remains available if Session creation or later submission fails.
|
|
79
79
|
|
|
@@ -89,9 +89,11 @@ 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 create a fresh Session using the current Agent preset definition. Workspace reconnection and explicit draft preparation may reuse an existing blank Session. 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
|
-
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.
|
|
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. The sidebar Add workspace action preserves an existing blank Session and its draft. 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.
|
|
95
97
|
|
|
96
98
|
### Session row actions
|
|
97
99
|
|
|
@@ -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
|
|
|
@@ -51,13 +51,13 @@ kind: "package-reference"
|
|
|
51
51
|
|
|
52
52
|
### 管理会话
|
|
53
53
|
|
|
54
|
-
Session 行内的 Rename 操作打开一个以该行显示标题预填的对话框;确认未修改的标题是有意允许的——这正是把当前自动标题钉住、不再被重新生成覆盖的手势。双击标题也会打开 Rename;对于未归档 Session,先发生的点击会打开其对话。Rename 使用临时 `workspaceOperation` reference,并等待首次历史打开。行内 Fork
|
|
54
|
+
Session 行内的 Rename 操作打开一个以该行显示标题预填的对话框;确认未修改的标题是有意允许的——这正是把当前自动标题钉住、不再被重新生成覆盖的手势。双击标题也会打开 Rename;对于未归档 Session,先发生的点击会打开其对话。Rename 使用临时 `workspaceOperation` reference,并等待首次历史打开。行内 Fork 正常读取普通冷历史。Host 报告源会话需要迁移时,应用级 Toast 提供打开该会话的操作;该操作仅导航,不重试 fork。成功的 fork 在源会话最后一个已完成轮次处创建分支,通过 Session Controller 递增继承的持久化标题,不 retain 子会话、不打开其历史,也不改变选择。Workspace 行内的 Delete 操作会打开确认框,说明保留边界;成功后该分组被移除,其 Session 则留在 Ungrouped 下。Pin、Rename、Fork、Archive 本身就是 `sidebar.workspaces.session.menu.item` 列表的条目(pin 与 archive 同时也是 `sidebar.workspaces.session.row.action` 的条目),因此客户端插件的 action 由其 `order` 决定落在哪个位置。
|
|
55
55
|
|
|
56
56
|
对静止的 Session,Archive 不经确认对话框直接提交,并保留 Session 的记账位置。仍有工作在跑的 Session 是唯一会先询问的情形:Host 拒绝普通归档并列出这些工作,侧栏随即打开"停止并归档"对话框,按族列出——进行中的回合、运行中的子代理、后台任务、定时提醒,各带名称——并写明恢复路径;确认后请 Host 按停止按钮同样的方式停止这些工作,归档集合持久化后即完成归档,停止在后台收敛;取消则让 Session 继续运行并保持可见。视图选项以一组显式三选一控制显隐:隐藏已归档(默认项)隐藏已归档 Session,全部对话(显示已归档)将其纳入列表,仅显示已归档则隐藏普通 Session,并丢弃没有归档 Session 的 Workspace;树形分组下,被丢弃 Workspace 的子级挂到最近一个仍显示的祖先下。可见的归档行置灰,并提供无障碍说明,告知取消归档后才能打开;Rename、Fork 与取消归档仍然可用。归档成功后的提示提供"撤销"动作,并在归档行仍被隐藏时附带"筛选已归档会话"动作,后者直接把筛选切到全部对话(显示已归档);停止并归档显示同样的提示但措辞不同,撤销只恢复 Session,不会让被停止的工作继续。取消归档移除归档标记,但不恢复置顶,也不改变保存的位置。列表为空时显示居中的"图标在上、文字在下"占位;仅显示已归档视图用自己的文案(暂无已归档会话),并附"查看其他会话"文字按钮,点击把筛选切回隐藏已归档。
|
|
57
57
|
|
|
58
58
|
会话更新时间使用 tertiary 文本色,包括已归档行。标题宽于所在行时,静止状态以省略号裁切。把指针停在行上,标题会滚动到远端——例如 fork 递增后的标题——并在揭示时不显示省略号;指针离开后标题回到开头。
|
|
59
59
|
|
|
60
|
-
快捷键速查提供新建会话、搜索会话、添加工作区、重命名会话、分叉会话和归档会话。桌面默认使用平台的主修饰键,搜索为 Mod+K;Windows 和 macOS Web 使用[快捷键服务的平台默认值](../shortcuts/README.zh.md);Linux Web 在用户配置前不绑定这些命令。按钮提示和会话行菜单显示当前有效绑定。点击菜单项操作该行,按快捷键操作主会话。Desktop 和 Windows/macOS Web 的重命名均使用 Mod+Alt+G,将 Mod+Shift+R 留给浏览器刷新。重命名要求主区域正在显示非空会话,且没有模态对话框遮挡;切换到其他主面板后,后台保留的会话不可作为重命名目标。其他 Windows 和 macOS Desktop
|
|
60
|
+
快捷键速查提供新建会话、搜索会话、添加工作区、重命名会话、分叉会话和归档会话。桌面默认使用平台的主修饰键,搜索为 Mod+K;Windows 和 macOS Web 使用[快捷键服务的平台默认值](../shortcuts/README.zh.md);Linux Web 在用户配置前不绑定这些命令。按钮提示和会话行菜单显示当前有效绑定。点击菜单项操作该行,按快捷键操作主会话。Desktop 和 Windows/macOS Web 的重命名均使用 Mod+Alt+G,将 Mod+Shift+R 留给浏览器刷新。重命名要求主区域正在显示非空会话,且没有模态对话框遮挡;切换到其他主面板后,后台保留的会话不可作为重命名目标。其他 Windows 和 macOS Desktop 绑定也可从终端输入区域和模态对话框中执行;其他环境遵循命令的区域和模态限制。搜索和重命名的打开请求归本包管理,输入草稿保留在浏览器中。目录选择或工作区接纳尚未结束时,目录选择器拒绝重复打开。分叉捕获主视图已持有并发起历史打开的会话,保留自动迁移;它使用与行菜单相同的 Host 操作选择最近已完成轮次,不读取更早的 Client 历史。没有会话或会话为空时不可用;没有已完成轮次的源会话由 Host 拒绝。快捷键分叉被拒绝时保留当前选择、显示本地化提示,并允许重试;非预期失败还会保留诊断日志。
|
|
61
61
|
|
|
62
62
|
### 待处理交互
|
|
63
63
|
|
|
@@ -69,11 +69,11 @@ Session 行渲染运行时的实时 `pendingInteraction` 分类:审批显示**
|
|
|
69
69
|
|
|
70
70
|
-----
|
|
71
71
|
|
|
72
|
-
`ctx.uiWorkspace.openSession(target)` 会同步替换其拥有的 `mainView` reference,并让主区域返回 Conversation,而不等待 `reference.ready`,因此历史加载会显示在已经选中的 Session 视图内。目标可以是已知 Session id,也可以是持久的直接父子 subagent 地址;显式地址不要求预先加载 parent catalog。`openWorkspace(id, beforeOpen?)`
|
|
72
|
+
`ctx.uiWorkspace.openSession(target)` 会同步替换其拥有的 `mainView` reference,并让主区域返回 Conversation,而不等待 `reference.ready`,因此历史加载会显示在已经选中的 Session 视图内。目标可以是已知 Session id,也可以是持久的直接父子 subagent 地址;显式地址不要求预先加载 parent catalog。`openWorkspace(id, beforeOpen?)` 仅在请求未被后续导航替代时打开工作区空白 Session;显式新会话操作会创建新的 Session。`forkSession(id)` 创建子会话,不导航,也不替代尚未完成的导航。可选的同步准备回调在目标被 retain 后执行,并且仅对仍有效的 Workspace 请求执行,因此过期请求不会搬移 composer 草稿。后续导航或 owner 释放会阻止晚到的 UI 提交,但不取消底层 Session 创建。启动恢复会 retain 主 reference,不改变已选面板,也不取消后续导航。归档主 Session 会释放其 reference 并清除主选择。选择失败时保留当前全局面板。Session 行读取 `usePanelInfo`,在全局面板活跃时不显示 Session 选中样式;仅把焦点移到搜索框或目录选择器不会离开该面板。
|
|
73
73
|
|
|
74
74
|
导航和启动恢复通过所选 Session 的 `follow` 获取投影,不会另行刷新该 Session 或其父会话的投影。
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
重新打开工作区和显式准备草稿尝试获取列表中第一个符合条件的空白会话;启动恢复尝试已保存的空白会话。若该写锁被占用,导航直接新建 Session,不再尝试其他空白会话。其他获取错误会中止请求:显式新建会话或在 hero 中选择工作区时,失败以短暂提示展示,引用 Host 的错误码和消息(例如 preset 挂载失败),非 Host 拒绝的失败则显示其自身消息;已被后续导航或 owner 销毁取代的请求不弹提示,启动恢复仍只报告到控制台。已释放的空白会话被复用时保留 slash 命令状态。后续导航会取消尚未完成的启动选择。
|
|
77
77
|
|
|
78
78
|
Workspace 和 Session 的启动基线均就绪后,空安装环境调用 `workspaces.initializeDefault`,创建或复用其空白 Session。选中该 Session 后输入框才可编辑,不会自动提交消息。后续导航或所属上下文销毁会阻止启动流程选中其结果。不符合首次使用条件时仍可选择文件夹,不显示错误。默认工作区创建失败时显示短暂提示,引导用户通过“选择工作区”选择文件夹,直到下次启动才重试。Session 创建失败沿用普通的恢复错误处理。登记成功的工作区在 Session 创建或后续提交失败时仍然保留。
|
|
79
79
|
|
|
@@ -89,9 +89,11 @@ 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。已被后续操作取代的导航不写内容;输入处于提交阻塞状态时拒绝准备并保留原选择。不传选项时按当前 Agent 预设定义创建新的 Session。工作区重连和显式草稿准备仍可复用已有空 Session。[草稿初始化决策](../../../.agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.zh.md)说明输入 owner 的数据流。
|
|
93
|
+
|
|
92
94
|
### 目录流子 slot
|
|
93
95
|
|
|
94
|
-
每个注册各自声明一个**目录流子 slot**(`single` kind:`conversation.hero.workspace.directoryFlow`/`sidebar.workspaces.directoryFlow`),由组合的选择器包 client half 填入其选取交互——`-native` 后端的无渲染 OS 选择器驱动,`-browse` 组合下则是应用内浏览对话框。平铺显示的**添加工作区…** 操作仅在当前界面的 slot 被占用时渲染;slot
|
|
96
|
+
每个注册各自声明一个**目录流子 slot**(`single` kind:`conversation.hero.workspace.directoryFlow`/`sidebar.workspaces.directoryFlow`),由组合的选择器包 client half 填入其选取交互——`-native` 后端的无渲染 OS 选择器驱动,`-browse` 组合下则是应用内浏览对话框。平铺显示的**添加工作区…** 操作仅在当前界面的 slot 被占用时渲染;slot 为空意味着该组合没有目录选择能力。侧栏的添加工作区操作保留已有空白 Session 及其草稿。本包持有触发与接纳:占用方通过 slot 的属主交互约定(`open`/`busy`/`onPicked`/`onCancel`/`onError`)每次打开上报一个所选路径,owner 通过对象层接纳它,并等待 Workspace 列表投影刷新后才选中已提交的 Workspace。
|
|
95
97
|
|
|
96
98
|
### Session 行 action
|
|
97
99
|
|
|
@@ -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.
|
|
@@ -786,9 +1410,13 @@ window.__ModuleLoader__.load({
|
|
|
786
1410
|
};
|
|
787
1411
|
}, "ui-workspace: Workspace navigation policy");
|
|
788
1412
|
}
|
|
789
|
-
|
|
1413
|
+
connectWorkspace(workspaceId) {
|
|
1414
|
+
return this.connectBlank(workspaceId, true);
|
|
1415
|
+
}
|
|
1416
|
+
async connectBlank(workspaceId, reuse) {
|
|
790
1417
|
const workspace = this.workspaces.list.getSnapshot().items.find((item) => item.workspaceId === workspaceId);
|
|
791
1418
|
if (workspace === void 0) throw new Error(`uiWorkspace.connectWorkspace: unknown workspace ${workspaceId}`);
|
|
1419
|
+
if (!reuse) return this.sessions.create({ workspaceId });
|
|
792
1420
|
const inflight = this.connecting.get(workspaceId);
|
|
793
1421
|
if (inflight !== void 0) return inflight;
|
|
794
1422
|
const attempt = this.reuseOrCreateBlank(workspace).finally(() => {
|
|
@@ -821,11 +1449,14 @@ window.__ModuleLoader__.load({
|
|
|
821
1449
|
openSession(target) {
|
|
822
1450
|
this.replaceMain(target, this.lifetime.signal, "reveal");
|
|
823
1451
|
}
|
|
824
|
-
|
|
1452
|
+
openWorkspace(workspaceId, beforeOpen) {
|
|
1453
|
+
return this.openWorkspaceSession(workspaceId, beforeOpen, true);
|
|
1454
|
+
}
|
|
1455
|
+
async openWorkspaceSession(workspaceId, beforeOpen, reuse) {
|
|
825
1456
|
const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
|
|
826
1457
|
let sessionId;
|
|
827
1458
|
try {
|
|
828
|
-
sessionId = await this.
|
|
1459
|
+
sessionId = await this.connectBlank(workspaceId, reuse);
|
|
829
1460
|
} catch (error) {
|
|
830
1461
|
if (!navigation.aborted) this.notify({
|
|
831
1462
|
kind: "createFailed",
|
|
@@ -836,14 +1467,17 @@ window.__ModuleLoader__.load({
|
|
|
836
1467
|
if (navigation.aborted) return;
|
|
837
1468
|
this.replaceMain(sessionId, navigation, "reveal", beforeOpen);
|
|
838
1469
|
}
|
|
839
|
-
async forkSession(sessionId, onCreated) {
|
|
1470
|
+
async forkSession(sessionId, onCreated, options) {
|
|
840
1471
|
return this.sessions.fork({
|
|
841
1472
|
sessionId,
|
|
842
1473
|
increaseTitle: true,
|
|
843
|
-
...onCreated === void 0 ? {} : { onCreated }
|
|
1474
|
+
...onCreated === void 0 ? {} : { onCreated },
|
|
1475
|
+
...options?.allowMigration === void 0 ? {} : { allowMigration: options.allowMigration }
|
|
844
1476
|
});
|
|
845
1477
|
}
|
|
846
|
-
startSession(workspaceId) {
|
|
1478
|
+
startSession(workspaceId, options) {
|
|
1479
|
+
const draftOptions = options === void 0 ? void 0 : { ...options };
|
|
1480
|
+
const initializeDraft = draftOptions !== void 0 && (draftOptions.prompt !== void 0 || draftOptions.clearPreviousDraft === true);
|
|
847
1481
|
const workspace = this.workspaces.list.getSnapshot();
|
|
848
1482
|
const sessions = this.sessions.list.getSnapshot();
|
|
849
1483
|
const current = this.mainReference?.sessionId;
|
|
@@ -851,13 +1485,37 @@ window.__ModuleLoader__.load({
|
|
|
851
1485
|
const recent = workspace.phase === "ready" && sessions.phase === "ready" ? recentWorkspace(workspace.items, sessions.byId) : void 0;
|
|
852
1486
|
const target = workspaceId ?? currentWorkspaceId ?? recent;
|
|
853
1487
|
if (target === void 0) {
|
|
1488
|
+
if (initializeDraft) {
|
|
1489
|
+
this.notify({
|
|
1490
|
+
kind: "createFailed",
|
|
1491
|
+
message: this.ctx.locale.bind("workspace")("draft.workspaceRequired")
|
|
1492
|
+
});
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
854
1495
|
this.clearMain();
|
|
855
1496
|
return;
|
|
856
1497
|
}
|
|
857
|
-
this.
|
|
1498
|
+
(draftOptions === void 0 ? this.openWorkspaceSession(target, void 0, false) : this.openWorkspace(target, initializeDraft ? (id) => {
|
|
1499
|
+
const binding = this.sessions.binding(id);
|
|
1500
|
+
if (binding === void 0) this.draftPreparationFailed();
|
|
1501
|
+
this.prepareDraft(binding, draftOptions);
|
|
1502
|
+
} : void 0)).catch((reason) => {
|
|
858
1503
|
console.warn("new session failed:", reason);
|
|
859
1504
|
});
|
|
860
1505
|
}
|
|
1506
|
+
prepareDraft(binding, options) {
|
|
1507
|
+
const conversation = this.ctx.get("conversation");
|
|
1508
|
+
if (conversation === void 0) this.draftPreparationFailed();
|
|
1509
|
+
if (conversation.input.requestDraftInitialization(binding, options) === "blocked") this.draftPreparationFailed();
|
|
1510
|
+
}
|
|
1511
|
+
draftPreparationFailed() {
|
|
1512
|
+
const message = this.ctx.locale.bind("workspace")("draft.initializationFailed");
|
|
1513
|
+
this.notify({
|
|
1514
|
+
kind: "createFailed",
|
|
1515
|
+
message
|
|
1516
|
+
});
|
|
1517
|
+
throw new Error(message);
|
|
1518
|
+
}
|
|
861
1519
|
async archiveSession(sessionId, options = {}) {
|
|
862
1520
|
await this.workspaces.archiveSession(sessionId, options);
|
|
863
1521
|
if (this.mainReference?.sessionId === sessionId) this.clearMain();
|
|
@@ -3219,7 +3877,7 @@ window.__ModuleLoader__.load({
|
|
|
3219
3877
|
side: "right",
|
|
3220
3878
|
onPick: (workspaceId) => {
|
|
3221
3879
|
closeAddWorkspace();
|
|
3222
|
-
startSession(workspaceId);
|
|
3880
|
+
startSession(workspaceId, { clearPreviousDraft: false });
|
|
3223
3881
|
},
|
|
3224
3882
|
onClose: () => {
|
|
3225
3883
|
closeAddWorkspace();
|
|
@@ -3793,23 +4451,31 @@ window.__ModuleLoader__.load({
|
|
|
3793
4451
|
*/
|
|
3794
4452
|
/**
|
|
3795
4453
|
* Hold for the notices that take longer to read than a one-line warning: the
|
|
3796
|
-
*
|
|
3797
|
-
* creation, which quotes the Host's reason.
|
|
4454
|
+
* archive and fork recovery actions, and a refused Session creation quoting the Host's reason.
|
|
3798
4455
|
*/
|
|
3799
4456
|
const LONG_TOAST_HOLD_MS = 6e3;
|
|
3800
4457
|
/**
|
|
3801
|
-
* Render
|
|
3802
|
-
*
|
|
3803
|
-
* are hidden — on a 6 s hold, a refused Session creation with the Host's
|
|
3804
|
-
* reason on the same hold, or a plain warning for a failed pin, an archived
|
|
3805
|
-
* row that was clicked, or default Workspace creation.
|
|
3806
|
-
* @param props - the notice hook, the shared viewing store, the notice dismissal, the two archived-notice actions, and the locale seat.
|
|
4458
|
+
* Render an operation notice, retaining recovery actions for six seconds.
|
|
4459
|
+
* @param props - notice and viewing sources, dismissal and recovery callbacks, and localized copy.
|
|
3807
4460
|
* @returns the notice on display, or null.
|
|
3808
4461
|
*/
|
|
3809
|
-
function RowActionToast({ useToast, useStore, dismissToast, undoArchive, showArchived, t }) {
|
|
4462
|
+
function RowActionToast({ useToast, useStore, dismissToast, undoArchive, showArchived, openForkSource, t }) {
|
|
3810
4463
|
const toast = useToast((current) => current);
|
|
3811
4464
|
const archivedRowsVisible = useStore((state) => (state.archivedFilter ?? "default") !== "default");
|
|
3812
4465
|
if (toast === null) return null;
|
|
4466
|
+
if (toast.kind === "forkRequiresOpen") return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
4467
|
+
text: t("toast.forkRequiresOpen"),
|
|
4468
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
|
|
4469
|
+
holdMs: LONG_TOAST_HOLD_MS,
|
|
4470
|
+
actions: [{
|
|
4471
|
+
label: t("toast.openForkSource"),
|
|
4472
|
+
onClick: () => {
|
|
4473
|
+
dismissToast();
|
|
4474
|
+
openForkSource(toast.sessionId);
|
|
4475
|
+
}
|
|
4476
|
+
}],
|
|
4477
|
+
onDone: dismissToast
|
|
4478
|
+
}, `toast-${String(toast.seq)}`);
|
|
3813
4479
|
if (toast.kind === "archived" || toast.kind === "stoppedAndArchived") {
|
|
3814
4480
|
const { sessionId } = toast;
|
|
3815
4481
|
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
@@ -3866,6 +4532,8 @@ window.__ModuleLoader__.load({
|
|
|
3866
4532
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
3867
4533
|
const zh = {
|
|
3868
4534
|
"defaultWorkspace.failed": "无法创建默认工作区,请通过“选择工作区”选择文件夹",
|
|
4535
|
+
"draft.workspaceRequired": "请先选择工作区",
|
|
4536
|
+
"draft.initializationFailed": "暂时无法填入草稿,请稍后重试",
|
|
3869
4537
|
"group.ungrouped": "未分组",
|
|
3870
4538
|
"session.new": "新会话",
|
|
3871
4539
|
"session.untitled": "未命名",
|
|
@@ -3946,6 +4614,8 @@ window.__ModuleLoader__.load({
|
|
|
3946
4614
|
"toast.pinFailed": "置顶失败,请稍后重试",
|
|
3947
4615
|
"toast.unpinFailed": "取消置顶失败,请稍后重试",
|
|
3948
4616
|
"toast.createFailed": "新建会话失败:{message}",
|
|
4617
|
+
"toast.forkRequiresOpen": "请先打开原会话完成迁移,再创建分支",
|
|
4618
|
+
"toast.openForkSource": "打开原会话",
|
|
3949
4619
|
"sessions.count.one": "{n} 个会话",
|
|
3950
4620
|
"sessions.count.other": "{n} 个会话",
|
|
3951
4621
|
"actions.workspace.aria": "工作区“{name}”的操作",
|
|
@@ -3981,6 +4651,8 @@ window.__ModuleLoader__.load({
|
|
|
3981
4651
|
/** English dictionary, checked complete against the zh key set. */
|
|
3982
4652
|
const en = {
|
|
3983
4653
|
"defaultWorkspace.failed": "Unable to create default workspace. Use Choose workspace to select a folder.",
|
|
4654
|
+
"draft.workspaceRequired": "Choose a workspace first.",
|
|
4655
|
+
"draft.initializationFailed": "Could not fill the draft. Please try again shortly.",
|
|
3984
4656
|
"group.ungrouped": "Ungrouped",
|
|
3985
4657
|
"session.new": "New Session",
|
|
3986
4658
|
"session.untitled": "Untitled",
|
|
@@ -4061,6 +4733,8 @@ window.__ModuleLoader__.load({
|
|
|
4061
4733
|
"toast.pinFailed": "Pin failed. Try again later.",
|
|
4062
4734
|
"toast.unpinFailed": "Unpin failed. Try again later.",
|
|
4063
4735
|
"toast.createFailed": "New session failed: {message}",
|
|
4736
|
+
"toast.forkRequiresOpen": "Open the original session to complete migration before creating a branch.",
|
|
4737
|
+
"toast.openForkSource": "Open original session",
|
|
4064
4738
|
"sessions.count.one": "{n} session",
|
|
4065
4739
|
"sessions.count.other": "{n} sessions",
|
|
4066
4740
|
"actions.workspace.aria": "Workspace actions for {name}",
|
|
@@ -4238,7 +4912,12 @@ window.__ModuleLoader__.load({
|
|
|
4238
4912
|
parent_session_id: sessionId,
|
|
4239
4913
|
click_position: "sidebar"
|
|
4240
4914
|
});
|
|
4241
|
-
}).catch(() => {
|
|
4915
|
+
}, { allowMigration: false }).catch((error) => {
|
|
4916
|
+
if (error instanceof Error && error.name === "SessionForkError" && error.rpcError.code === "session/migration-required") notify({
|
|
4917
|
+
kind: "forkRequiresOpen",
|
|
4918
|
+
sessionId
|
|
4919
|
+
});
|
|
4920
|
+
});
|
|
4242
4921
|
} });
|
|
4243
4922
|
const renameInjected = () => ({ requestSessionRename });
|
|
4244
4923
|
const renameDialogInjected = () => ({
|
|
@@ -4254,11 +4933,18 @@ window.__ModuleLoader__.load({
|
|
|
4254
4933
|
undoArchive: unarchiveSession,
|
|
4255
4934
|
showArchived: () => {
|
|
4256
4935
|
viewInstance.actions.setArchivedFilter("show");
|
|
4936
|
+
},
|
|
4937
|
+
openForkSource: (sessionId) => {
|
|
4938
|
+
if (workspaces.list.getSnapshot().archivedSessionIds.includes(sessionId)) {
|
|
4939
|
+
notify({ kind: "archivedNotOpenable" });
|
|
4940
|
+
return;
|
|
4941
|
+
}
|
|
4942
|
+
openSession(sessionId);
|
|
4257
4943
|
}
|
|
4258
4944
|
});
|
|
4259
4945
|
const browserInjected = () => ({
|
|
4260
|
-
startSession: (workspaceId) => {
|
|
4261
|
-
uiWorkspace.startSession(workspaceId);
|
|
4946
|
+
startSession: (workspaceId, options) => {
|
|
4947
|
+
uiWorkspace.startSession(workspaceId, options);
|
|
4262
4948
|
},
|
|
4263
4949
|
open: openSession,
|
|
4264
4950
|
searchSessions,
|
|
@@ -45,6 +45,7 @@ import type { SessionActivity, WorkspaceId, WorkspaceView } from '@deepseek-ai/d
|
|
|
45
45
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
46
46
|
import type { ShortcutCatalogEntry } from '@deepseek-ai/dsh-client-shortcuts/client';
|
|
47
47
|
import type { WorkspaceShortcutState } from '../shortcuts.ts';
|
|
48
|
+
import type { StartSessionOptions } from '../navigation.ts';
|
|
48
49
|
import type { createWorkspaceViewStore } from '../stores.ts';
|
|
49
50
|
/**
|
|
50
51
|
* Owner share of the directory-flow holes: the complete conversation between
|
|
@@ -236,11 +237,11 @@ export type WorkspaceBrowserInjected = {
|
|
|
236
237
|
/** Dismiss the shortcut's fork-failure notification. */
|
|
237
238
|
dismissForkError: () => void;
|
|
238
239
|
/**
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
240
|
+
* Create a fresh Session, or preserve a reusable blank with explicit draft options.
|
|
241
|
+
* Without an explicit Workspace, inherit the current Session Workspace,
|
|
242
|
+
* then the recent Workspace, or clear into the New Session view.
|
|
242
243
|
*/
|
|
243
|
-
startSession: (workspaceId?: WorkspaceId) => void;
|
|
244
|
+
startSession: (workspaceId?: WorkspaceId, options?: StartSessionOptions) => void;
|
|
244
245
|
/** Open a real Session. */
|
|
245
246
|
open: (sessionId: SessionId) => void;
|
|
246
247
|
/**
|
|
@@ -294,6 +295,9 @@ export type RowToast = {
|
|
|
294
295
|
kind: 'archivedNotOpenable';
|
|
295
296
|
} | {
|
|
296
297
|
kind: 'defaultWorkspaceFailed';
|
|
298
|
+
} | {
|
|
299
|
+
kind: 'forkRequiresOpen';
|
|
300
|
+
sessionId: SessionId;
|
|
297
301
|
}
|
|
298
302
|
/**
|
|
299
303
|
* An explicit New Session request that failed. `message` is untranslated:
|
|
@@ -406,7 +410,7 @@ export interface SessionRenameDialogInjected {
|
|
|
406
410
|
/** Rename a Session (explicit user title; resolves on host acceptance). */
|
|
407
411
|
renameSession: (sessionId: SessionId, title: string) => Promise<void>;
|
|
408
412
|
}
|
|
409
|
-
/** Row toast share: the notice
|
|
413
|
+
/** Row toast share: the notice, dismissal, archive recovery, and explicit source-history opening. */
|
|
410
414
|
export interface RowToastInjected {
|
|
411
415
|
hooks: {
|
|
412
416
|
/** The notice on display, or none. */
|
|
@@ -418,6 +422,8 @@ export interface RowToastInjected {
|
|
|
418
422
|
undoArchive: (sessionId: SessionId) => void;
|
|
419
423
|
/** Switch the archived filter to "show" so the archived row is back in view. */
|
|
420
424
|
showArchived: () => void;
|
|
425
|
+
/** Open the fork source without retrying the fork. */
|
|
426
|
+
openForkSource: (sessionId: SessionId) => void;
|
|
421
427
|
}
|
|
422
428
|
/** Props of the rename dialog entry in `shell.overlay`. */
|
|
423
429
|
export type SessionRenameDialogProps = PropsRuntime<'shell.overlay'> & PropsLocale<'workspace'> & Omit<SessionRenameDialogInjected, 'hooks'> & PropsHooks<SessionRenameDialogInjected['hooks']>;
|
|
@@ -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;
|
|
@@ -86,6 +88,8 @@ export declare const zh: {
|
|
|
86
88
|
'toast.pinFailed': string;
|
|
87
89
|
'toast.unpinFailed': string;
|
|
88
90
|
'toast.createFailed': string;
|
|
91
|
+
'toast.forkRequiresOpen': string;
|
|
92
|
+
'toast.openForkSource': string;
|
|
89
93
|
'sessions.count.one': string;
|
|
90
94
|
'sessions.count.other': string;
|
|
91
95
|
'actions.workspace.aria': string;
|
|
@@ -123,6 +127,8 @@ export type WorkspaceKey = keyof typeof zh;
|
|
|
123
127
|
/** English dictionary, checked complete against the zh key set. */
|
|
124
128
|
export declare const en: {
|
|
125
129
|
'defaultWorkspace.failed': string;
|
|
130
|
+
'draft.workspaceRequired': string;
|
|
131
|
+
'draft.initializationFailed': string;
|
|
126
132
|
'group.ungrouped': string;
|
|
127
133
|
'session.new': string;
|
|
128
134
|
'session.untitled': string;
|
|
@@ -203,6 +209,8 @@ export declare const en: {
|
|
|
203
209
|
'toast.pinFailed': string;
|
|
204
210
|
'toast.unpinFailed': string;
|
|
205
211
|
'toast.createFailed': string;
|
|
212
|
+
'toast.forkRequiresOpen': string;
|
|
213
|
+
'toast.openForkSource': string;
|
|
206
214
|
'sessions.count.one': string;
|
|
207
215
|
'sessions.count.other': string;
|
|
208
216
|
'actions.workspace.aria': 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
|
/**
|
|
@@ -27,9 +30,10 @@ export interface UiWorkspace {
|
|
|
27
30
|
* Fork a Session without changing the current selection.
|
|
28
31
|
* @param sessionId - source Session.
|
|
29
32
|
* @param onCreated - observer before the optional child-title update.
|
|
33
|
+
* @param options - optional permission to start source migration; omission preserves the Host default.
|
|
30
34
|
* @returns the child SessionId after creation and inherited-title increment.
|
|
31
35
|
*/
|
|
32
|
-
forkSession(sessionId: SessionId, onCreated?: (childId: SessionId) => void): Promise<SessionId>;
|
|
36
|
+
forkSession(sessionId: SessionId, onCreated?: (childId: SessionId) => void, options?: Pick<Parameters<ISessions['fork']>[0], 'allowMigration'>): Promise<SessionId>;
|
|
33
37
|
/**
|
|
34
38
|
* Resolve the reusable or newly created blank Session for a Workspace.
|
|
35
39
|
* @param workspaceId - target Workspace.
|
|
@@ -37,11 +41,12 @@ export interface UiWorkspace {
|
|
|
37
41
|
*/
|
|
38
42
|
connectWorkspace(workspaceId: WorkspaceId): Promise<SessionId>;
|
|
39
43
|
/**
|
|
40
|
-
*
|
|
44
|
+
* Create a fresh Session, or reuse a blank when preparing an explicit draft. A creation the Host
|
|
41
45
|
* refuses is shown through the Workspace notice and leaves the selection as it was.
|
|
42
46
|
* @param workspaceId - explicit target; absent inherits the current or most recent Workspace.
|
|
47
|
+
* @param options - initial content; existing text or attachments are preserved unless clearPreviousDraft is true.
|
|
43
48
|
*/
|
|
44
|
-
startSession(workspaceId?: WorkspaceId): void;
|
|
49
|
+
startSession(workspaceId?: WorkspaceId, options?: StartSessionOptions): void;
|
|
45
50
|
/**
|
|
46
51
|
* Archive a Session and clear it when it is the current selection.
|
|
47
52
|
* @param sessionId - Session to archive.
|
|
@@ -122,12 +127,16 @@ declare class UiWorkspaceService extends Service implements UiWorkspace {
|
|
|
122
127
|
*/
|
|
123
128
|
constructor(ctx: Context, directoryPicker: ClientRemote['directoryPicker'], workspaces: IWorkspaces, sessions: ISessions, view: Pick<WorkspaceViewStoreActions, 'pinSessionOrder'>, notify: (toast: RowToast) => void);
|
|
124
129
|
connectWorkspace(workspaceId: WorkspaceId): Promise<SessionId>;
|
|
130
|
+
private connectBlank;
|
|
125
131
|
private reuseOrCreateBlank;
|
|
126
132
|
private reuseBlank;
|
|
127
133
|
openSession(target: SessionTarget): void;
|
|
128
134
|
openWorkspace(workspaceId: WorkspaceId, beforeOpen?: (sessionId: SessionId) => void): Promise<void>;
|
|
129
|
-
|
|
130
|
-
|
|
135
|
+
private openWorkspaceSession;
|
|
136
|
+
forkSession(sessionId: SessionId, onCreated?: (childId: SessionId) => void, options?: Pick<Parameters<ISessions['fork']>[0], 'allowMigration'>): Promise<SessionId>;
|
|
137
|
+
startSession(workspaceId?: WorkspaceId, options?: StartSessionOptions): void;
|
|
138
|
+
private prepareDraft;
|
|
139
|
+
private draftPreparationFailed;
|
|
131
140
|
archiveSession(sessionId: SessionId, options?: {
|
|
132
141
|
readonly stopActivity?: boolean;
|
|
133
142
|
}): Promise<void>;
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { RowToastProps } from '../contract/slots.ts';
|
|
2
2
|
/**
|
|
3
|
-
* Render
|
|
4
|
-
*
|
|
5
|
-
* are hidden — on a 6 s hold, a refused Session creation with the Host's
|
|
6
|
-
* reason on the same hold, or a plain warning for a failed pin, an archived
|
|
7
|
-
* row that was clicked, or default Workspace creation.
|
|
8
|
-
* @param props - the notice hook, the shared viewing store, the notice dismissal, the two archived-notice actions, and the locale seat.
|
|
3
|
+
* Render an operation notice, retaining recovery actions for six seconds.
|
|
4
|
+
* @param props - notice and viewing sources, dismissal and recovery callbacks, and localized copy.
|
|
9
5
|
* @returns the notice on display, or null.
|
|
10
6
|
*/
|
|
11
|
-
export declare function RowActionToast({ useToast, useStore, dismissToast, undoArchive, showArchived, t }: RowToastProps): import("react").JSX.Element | null;
|
|
7
|
+
export declare function RowActionToast({ useToast, useStore, dismissToast, undoArchive, showArchived, openForkSource, t }: RowToastProps): import("react").JSX.Element | null;
|
|
12
8
|
//# sourceMappingURL=RowActionToast.d.ts.map
|
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.2",
|
|
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
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.1",
|
|
52
|
+
"@deepseek-ai/dsh-api-remotes": "0.2.1-alpha.2",
|
|
53
|
+
"@deepseek-ai/dsh-agent": "0.2.1-alpha.2",
|
|
54
|
+
"@deepseek-ai/dsh-api-session-controller": "0.2.1-alpha.2",
|
|
55
|
+
"@deepseek-ai/dsh-api-workspace-controller": "0.2.1-alpha.2",
|
|
56
|
+
"@deepseek-ai/dsh-client-connection": "0.2.1-alpha.2",
|
|
57
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-alpha.2",
|
|
58
|
+
"@deepseek-ai/dsh-client-store": "0.2.1-alpha.2",
|
|
59
|
+
"@deepseek-ai/dsh-client-test-runtime": "0.2.1-alpha.2",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-alpha.2",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.2.1-alpha.2",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-alpha.2",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-alpha.2",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-session": "0.2.1-alpha.2",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-alpha.2",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-alpha.2",
|
|
67
|
+
"@deepseek-ai/dsh-jobs": "0.2.1-alpha.2",
|
|
68
|
+
"@deepseek-ai/dsh-schedule": "0.2.1-alpha.2",
|
|
69
|
+
"@deepseek-ai/dsh-session": "0.2.1-alpha.2",
|
|
70
|
+
"@deepseek-ai/dsh-subagent": "0.2.1-alpha.2",
|
|
71
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.2",
|
|
72
|
+
"@deepseek-ai/dsh-util-values": "0.2.1-alpha.2",
|
|
73
|
+
"@deepseek-ai/dsh-util-workspace-path": "0.2.1-alpha.2",
|
|
51
74
|
"@types/react": "~18.3.1",
|
|
52
75
|
"clsx": "^2.0.0",
|
|
53
76
|
"react": "^18.2.0",
|
|
54
|
-
"@deepseek-ai/dsh-
|
|
55
|
-
"@deepseek-ai/dsh-
|
|
56
|
-
"@deepseek-ai/dsh-api-workspace-controller": "0.2.0-rc.2",
|
|
57
|
-
"@deepseek-ai/dsh-client-connection": "0.2.0-rc.2",
|
|
58
|
-
"@deepseek-ai/cordis": "~4.0.4",
|
|
59
|
-
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
|
|
60
|
-
"@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
|
|
61
|
-
"@deepseek-ai/dsh-client-test-runtime": "0.2.0-rc.2",
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-layout": "0.2.0-rc.2",
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-session": "0.2.0-rc.2",
|
|
65
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
|
|
66
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.2",
|
|
67
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "0.2.0-rc.2",
|
|
68
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
|
|
69
|
-
"@deepseek-ai/dsh-session": "0.2.0-rc.2",
|
|
70
|
-
"@deepseek-ai/dsh-subagent": "0.2.0-rc.2",
|
|
71
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
|
|
72
|
-
"@deepseek-ai/dsh-schedule": "0.2.0-rc.2",
|
|
73
|
-
"@deepseek-ai/dsh-util-values": "0.2.0-rc.2",
|
|
74
|
-
"@deepseek-ai/dsh-jobs": "0.2.0-rc.2",
|
|
75
|
-
"@deepseek-ai/dsh-util-workspace-path": "0.2.0-rc.2",
|
|
76
|
-
"@deepseek-ai/dsh-client-shortcuts": "0.2.0-rc.2",
|
|
77
|
-
"@deepseek-ai/dsh-client-product-analytics": "0.2.0-rc.2",
|
|
78
|
-
"@deepseek-ai/dsh-api-remotes": "0.2.0-rc.2"
|
|
77
|
+
"@deepseek-ai/dsh-client-shortcuts": "0.2.1-alpha.2",
|
|
78
|
+
"@deepseek-ai/dsh-client-product-analytics": "0.2.1-alpha.2"
|
|
79
79
|
},
|
|
80
80
|
"files": [
|
|
81
81
|
"lib/index.js",
|