@crazx/dsh-api-session-controller 0.1.5-alpha.1.zw.3 → 0.1.5-rc.1.zw.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 +2 -2
- package/README.md +1 -1
- package/README.zh.md +10 -10
- package/lib/client.js +2 -166
- package/lib/index.js +18 -2
- package/lib/typert.host.js +97 -22
- package/lib/typert.remote-client.js +56 -16
- package/lib/types/client/contract/sessions.d.ts +0 -12
- package/lib/types/client/sessions/manager.d.ts +0 -23
- package/lib/types/client/sessions/manager.js +0 -82
- package/lib/types/client/sessions/service.d.ts +0 -8
- package/lib/types/client/sessions/service.js +0 -16
- package/lib/types/index.d.ts +12 -0
- package/lib/types/index.js +16 -2
- package/lib/types/types.d.ts +2 -0
- package/package.json +68 -68
- package/lib/types/client/sessions/selection-history.d.ts +0 -55
- package/lib/types/client/sessions/selection-history.js +0 -91
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/api/session-controller/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 24cfbba626ddec510452e3b6c8bc4333c03876bd
|
|
6
|
+
README.zh.md: 883fa2343693e6512638fa437a3f8bd0db09310c
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ History pages and follow opening snapshots carry one `{ type: 'event', event: Se
|
|
|
28
28
|
|
|
29
29
|
The Client journal validates exact V3 event envelopes before publishing follow snapshots, live entries, or history pages. It reuses the browser-safe Session validators for required surface markers, exact replacement endpoints, earlier unique source seqs, embedded Assistant provenance, request-header omissions, and tool-error consistency. Invalid records fail without field stripping or normalization; range membership and source existence remain durable-log checks on the Host.
|
|
30
30
|
|
|
31
|
-
Each endpoint states its activation policy. List reads only stored headers and projection-cache rows: it never calls per-session stat or opens a cold Session body. A current-format cache identity may supply every list hint; a lifecycle-matching predecessor cache may supply only its version-compatible title as a stale display fact, never as an authoritative fold seed. Search, attachment, history pages, log following, skill discovery, and workspace-path opening can inspect persistence without activating an Agent; `canOpenWorkspacePath()` reports native-opening availability without addressing a Session. Queue mutation and cancellation require live state; model, rename, prompt, and file-reference operations may resolve or resume an ordinary Session. Prompt rejects content with neither non-whitespace text nor an attachment before resolving the Agent or appending Session events; queue edits accept only non-empty text content. Prompt admission consumes opaque receipts from the injected [`fileUploads`](../../client/file-upload/README.md) Host service and resolves every same-Agent receipt before sending the complete ordered content list through `ctx.attachments`. Prompt retries whose `requestId` is already queued or logged return the original acceptance without inserting another message. Create and fork are the only operations that create a new Agent directly. The service applies one preset-aware resume policy and subagent ownership fence to its own methods and to the Typert Agent and Session lookups used by other Remote namespaces. Queue mutation has one narrow exception: a live child whose current projected identity is continuable and comes from its own non-seed suffix accepts the ordinary Edit, Remove, and QueueDock Steer actions across both inbox destinations. One-shot, missing, unknown, corrupt, seed-only, or cold children remain rejected without resume. The skill catalog uses a live Agent when present or the recorded preset's standing scope when cold, so listing never starts an Agent.
|
|
31
|
+
Each endpoint states its activation policy. List reads only stored headers and projection-cache rows: it never calls per-session stat or opens a cold Session body. A current-format cache identity may supply every list hint; a lifecycle-matching predecessor cache may supply only its version-compatible title as a stale display fact, never as an authoritative fold seed. Search, attachment, history pages, log following, skill discovery, and workspace-path opening can inspect persistence without activating an Agent; `canOpenWorkspacePath()` reports native-opening availability without addressing a Session. Queue mutation and cancellation require live state; model, rename, prompt, and file-reference operations may resolve or resume an ordinary Session. Prompt rejects content with neither non-whitespace text nor an attachment before resolving the Agent or appending Session events; queue edits accept only non-empty text content. Prompt admission consumes opaque receipts from the injected [`fileUploads`](../../client/file-upload/README.md) Host service and resolves every same-Agent receipt before sending the complete ordered content list through `ctx.attachments`. Prompt retries whose `requestId` is already queued or logged return the original acceptance without inserting another message. Create and fork are the only operations that create a new Agent directly. The service applies one preset-aware resume policy and subagent ownership fence to its own methods and to the Typert Agent and Session lookups used by other Remote namespaces. Queue mutation has one narrow exception: a live child whose current projected identity is continuable and comes from its own non-seed suffix accepts the ordinary Edit, Remove, and QueueDock Steer actions across both inbox destinations. One-shot, missing, unknown, corrupt, seed-only, or cold children remain rejected without resume. The skill catalog uses a live Agent when present or the recorded preset's standing scope when cold, so listing never starts an Agent. The authenticated delivery routes use `workspaceDesktop()` for the serving Host name and file-manager behavior. `openWorkspacePath({ path, action: "reveal" })` delegates file-manager navigation to the native adapter; omitting `action` opens the default application.
|
|
32
32
|
|
|
33
33
|
The Client adapter exposes `SessionEventStream`, a Gateway `RemoteJournalStream` bound to one ordinary or direct-subagent address. It opens follow before the initial page, publishes only contiguous `replace`, `prepend`, `append`, and `settle-assistant` changes, and repairs reconnect or sequence gaps through a tail page. Backwards paging has two verbs: `loadOlder()` pulls one 50-message page, and `loadThrough(seq)` — the turn-jump loader — loops 200-message pages until the window covers the target seq, lowering a shared target on repeated calls, stopping on a page that makes no progress, and reporting busy through the same `loadingOlder` snapshot bit. The Web adapter explicitly opts into cursorless Assistant frames: each opening carries the active attempt's `startedAfterSeq`, `nextIndex`, and compact stream, and every stream member becomes a Client-only `assistant/live-chunk` entry ordered between durable cursors. The Host captures a follower-local arrival ordinal with that baseline and suppresses buffered frames at or before the cut; a replacement Agent may restart frame revision at one. A durable `assistant/message` or `assistant/attempt` arriving after an active opening stays staged only when its seq follows `startedAfterSeq` and its Turn and Step match; the matching end type, seq, and index publishes one named settlement delta that retires the attempt's transient rows and adds the durable entry while earlier same-step retries remain visible. Revision, dense-index, or settlement gaps for a known attempt reopen follow, while a controller that missed the start ignores unknown-attempt frames and publishes their durable settlement normally. An abandoned end publishes a settlement delta without a durable entry so its transient rows retire immediately. A durable gap-repair page has no Assistant baseline, so its held notification reopens follow once for a paired page and baseline. Every history record covers exactly its event seq. A business, persistence, or unresolved continuity failure terminates the stream, while only physical carrier loss selects automatic resumption. `SessionControlStream` is a Gateway `RemoteSnapshotStream`; every generation opens with a complete process-local baseline, so reconnect replaces queue, jobs, and projection state instead of treating transient values as durable events. For each inbox change, the Host publishes the projection frame first and derives the queue replacement from that same validated post-fold value, so listener registration order cannot produce a stale queue frame.Client Agent contexts provide the identity used by the independent [`fileUpload`](../../client/file-upload/README.md) service; Session objects expose lifecycle, prompt, queue, and history operations rather than file transfer.
|
|
34
34
|
|
package/README.zh.md
CHANGED
|
@@ -8,7 +8,7 @@ kind: "package-reference"
|
|
|
8
8
|
|
|
9
9
|
## 概述
|
|
10
10
|
|
|
11
|
-
`@deepseek-ai/dsh-api-session-controller` 拥有 Host 的 `ctx.sessionController` 服务,以及生成的 Client `session`、`skills` 和 `fileReferences` Remote namespace。它提供 Session 生命周期与历史、Host generation 模型目录、工作区路径打开、用户可调用 skill
|
|
11
|
+
`@deepseek-ai/dsh-api-session-controller` 拥有 Host 的 `ctx.sessionController` 服务,以及生成的 Client `session`、`skills` 和 `fileReferences` Remote namespace。它提供 Session 生命周期与历史、Host generation 模型目录、工作区路径打开、用户可调用 skill(技能)发现和 Agent(智能体)范围的文件引用。当 Client 需要按 Session 寻址的操作时,请通过 API Gateway 使用它。
|
|
12
12
|
|
|
13
13
|
## 目录
|
|
14
14
|
|
|
@@ -24,21 +24,21 @@ kind: "package-reference"
|
|
|
24
24
|
<a id="use-this-package"></a>
|
|
25
25
|
## 使用本包
|
|
26
26
|
|
|
27
|
-
历史页与 follow opening
|
|
27
|
+
历史页与 follow opening 快照为每个持久 Session 事件携带一条 `{ type: 'event', event: SessionWireEvent }` record。Client 把每条已接受 record 保留为一个持久 `SessionEventLikeEntry`;Assistant token 边界保留在 `assistant/message` 或 `assistant/attempt` 的紧凑流内。工具参数、结果内容、失败信息和 `tool/result.data.meta` 原样通过;控制器不解析工具定义、不运行展示转换器,也不附加 UI 数据。
|
|
28
28
|
|
|
29
|
-
Client journal 在发布 follow
|
|
29
|
+
Client journal 在发布 follow 快照、live entry 或历史页之前验证精确的 V3 事件 envelope。它复用浏览器安全的 Session validator,检查必需的 surface marker、精确的 replacement endpoint、更早且唯一的 source seq、内嵌 Assistant 来源、request header 可选字段的省略规则以及工具错误一致性。无效 record 直接失败,不删除字段或归一化;范围成员与来源存在性仍由 Host 的持久日志检查。
|
|
30
30
|
|
|
31
|
-
每个 endpoint 都声明自己的激活策略。列表只读取持久化 header 与 projection cache row,绝不调用逐 Session stat 或打开冷 Session body。当前格式 cache identity 可以提供全部列表 hint;生命周期匹配的 predecessor cache 只能提供版本兼容的 title,作为可能过时的展示事实,绝不能作为权威 fold seed。搜索、附件、历史页、日志跟随、skill 发现和工作区路径打开可以在不激活 Agent 的情况下检查 persistence;`canOpenWorkspacePath()` 无需指定 Session 即可报告原生打开能力。queue 变更与取消要求 live 状态;模型、重命名、prompt 和文件引用操作可以解析或恢复普通 Session
|
|
31
|
+
每个 endpoint 都声明自己的激活策略。列表只读取持久化 header 与 projection cache row,绝不调用逐 Session stat 或打开冷 Session body。当前格式 cache identity 可以提供全部列表 hint;生命周期匹配的 predecessor cache 只能提供版本兼容的 title,作为可能过时的展示事实,绝不能作为权威 fold seed。搜索、附件、历史页、日志跟随、skill 发现和工作区路径打开可以在不激活 Agent 的情况下检查 persistence;`canOpenWorkspacePath()` 无需指定 Session 即可报告原生打开能力。queue 变更与取消要求 live 状态;模型、重命名、prompt 和文件引用操作可以解析或恢复普通 Session。提示词会在解析 Agent 或追加 Session 事件前,拒绝既没有非空白文本也没有附件的 content;queue edit 只接受非空文本 content。prompt 准入从注入的 [`fileUploads`](../../client/file-upload/README.zh.md) Host 服务取得不透明凭证,在把完整有序内容列表交给 `ctx.attachments` 前解析每个属于同一 Agent 的凭证。`requestId` 已进入 queue 或日志时,prompt 重试直接返回原来的接受结果,不会重复插入消息。只有 create 与 fork 会直接创建新 Agent。该服务把同一套感知 preset 的恢复策略和 subagent ownership fence 同时用于自身方法,以及其他 Remote namespace 使用的 Typert Agent 与 Session lookup。Queue 变更只有一个狭窄例外:当前 projection identity 为 continuable 且来自自身非 seed suffix 的在线 child,可以在两个 inbox 目标上使用普通 Edit、Remove 与 QueueDock Steer action。One-shot、缺失、未知、损坏、仅含 seed identity 或冷 child 继续被拒绝,且不会恢复。skill 目录优先使用已有 live Agent,否则使用所记录 preset 的常驻 scope,因此列表查询绝不会启动 Agent。经过鉴权的文件交付路由通过 `workspaceDesktop()` 获取提供服务的 Host 名称和文件管理器行为。`openWorkspacePath({ path, action: "reveal" })` 将文件管理器导航委托给原生适配器;省略 `action` 时打开默认应用。
|
|
32
32
|
|
|
33
|
-
Client
|
|
33
|
+
Client 适配器提供 `SessionEventStream`,即绑定到一个普通 Session 或 direct subagent address 的 Gateway `RemoteJournalStream`。它在读取首个 page 前打开 follow,只发布连续的 `replace`、`prepend`、`append` 与 `settle-assistant` 变更,并通过 tail page 修复重连或 seq 缺口。向后分页有两个动词:`loadOlder()` 拉一页 50 条消息,而 `loadThrough(seq)`——轮次跳转加载器——按每页 200 条消息循环拉取直到窗口覆盖目标 seq,重复调用会下调共享目标,遇到无进展的页即停止,忙碌状态复用同一个 `loadingOlder` 快照位。Web 适配器显式选择接收无 cursor 的 Assistant frame:每个 opening 携带活跃 attempt 的 `startedAfterSeq`、`nextIndex` 与紧凑 stream,每个 stream member 都成为排在持久 cursor 之间的 Client-only `assistant/live-chunk` 条目。Host 会随该 baseline 捕获 follower 本地到达序号,并抑制该 cut 及之前的 buffered frame;replacement Agent 可以从 revision 一重新开始。活跃 opening 之后到达的持久 `assistant/message` 或 `assistant/attempt` 只有在其 seq 晚于 `startedAfterSeq` 且轮次与步骤匹配时才会保持暂存;匹配的 end type、seq 与 index 会发布一个具名 settlement delta,删除该 attempt 的瞬态 row、加入持久条目,并保留同一步骤中更早的 retry。已知 attempt 的 revision、密集 index 或 settlement 缺口会重新打开 follow;若 controller 错过 start,则忽略 unknown-attempt frame,并正常发布其持久 settlement。Abandoned end 会发布不含持久条目的 settlement delta,使瞬态 row 立即退出。持久缺口修复 page 不携带 Assistant baseline,因此 held notification 会重新打开 follow 一次,以取得配对的 page 与 baseline。每条历史 record 只覆盖自身的事件 seq。业务、persistence 或无法恢复的连续性错误会终止 stream,只有物理载体断开才触发自动恢复。`SessionControlStream` 是 Gateway `RemoteSnapshotStream`;每代都以完整的进程本地 baseline 开始,因此重连会替换 queue、jobs 和 projection 状态,而不会把瞬态值当作持久事件。每次 inbox 变更时,Host 会先发布 projection frame,再从同一份已校验的折叠后值派生 queue replacement,因此监听器注册顺序不会产生陈旧的 queue frame。Client Agent 上下文提供独立 [`fileUpload`](../../client/file-upload/README.zh.md) 服务使用的身份;Session 对象提供生命周期、prompt、queue 与历史操作,不提供文件传输。
|
|
34
34
|
|
|
35
|
-
Session 对象还承载本地提交回显:`session.beginSubmission`
|
|
35
|
+
Session 对象还承载本地提交回显:`session.beginSubmission` 在调用方序列化与提示词之前,同步把一条回显写入 `SessionSnapshot.pendingSubmissions`,会话 UI 因此能在点击提交的当帧显示消息。回显按顺序存放图片预览与持久文件引用。Session 根据当前运行状态与请求的投递模式推导其 `transcript`、`queued` 或 `steering` 位置,并在序列化期间保留该位置。提示词的 `requestId` 是关联标识:Host 把它回显为 durable user source 的 `rpcId`,queue occurrence 也把它投影为 `SessionQueuedItem.rpcId`。回显在观察到其 durable event 或 queue occurrence 后延迟一个动画帧退休,带标识的提示词失败或被放弃时立即退休,销毁时按 failed 退休。每次退休恰好触发一次 `onRetire`;observed 退休还会携带有序的持久附件引用,让 composer 释放成功卡片并保留失败草稿。回显只存在于 Client 内存;刷新与重连只从持久事件重建会话。
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
<a id="session-media-references"></a>
|
|
39
39
|
## 会话媒体引用
|
|
40
40
|
|
|
41
|
-
当 `connection`、`fs` 与 `attachments` 均被组合时,`SessionMediaReferences` 在鉴权 `connection.fetch` 通道上挂载 `GET|HEAD /api/file?path=<绝对路径>`。它通过 `ctx.fs` 读取普通文件,包括已注册工作区之外的临时路径与远程提供方中的文件。目录包含关系与 MIME 类别均不限制访问;`mime-types` 提供响应类型,未知扩展名使用 `application/octet-stream`。GET 复用 `readBytes` 执行读取前及读取中的字节限制;HEAD 只读取元数据。所有文件均使用 `ctx.attachments.imageLimits.maxImageBytes`(通常为 20 MiB);超过此上限返回 413。响应包含完整文件,忽略 Range,并携带 `private, no-store`、`nosniff`
|
|
41
|
+
当 `connection`、`fs` 与 `attachments` 均被组合时,`SessionMediaReferences` 在鉴权 `connection.fetch` 通道上挂载 `GET|HEAD /api/file?path=<绝对路径>`。它通过 `ctx.fs` 读取普通文件,包括已注册工作区之外的临时路径与远程提供方中的文件。目录包含关系与 MIME 类别均不限制访问;`mime-types` 提供响应类型,未知扩展名使用 `application/octet-stream`。GET 复用 `readBytes` 执行读取前及读取中的字节限制;HEAD 只读取元数据。所有文件均使用 `ctx.attachments.imageLimits.maxImageBytes`(通常为 20 MiB);超过此上限返回 413。响应包含完整文件,忽略 Range,并携带 `private, no-store`、`nosniff` 与沙箱 CSP,使直接打开的 HTML/SVG 无法以 API 源身份执行脚本。客户端重写位于 `ui-chat`(`AssistantMarkdown`);音视频文件响应已可用,Markdown 音视频播放器节点仍是独立工作。
|
|
42
42
|
|
|
43
43
|
-----
|
|
44
44
|
|
|
@@ -56,11 +56,11 @@ Session 对象还承载本地提交回显:`session.beginSubmission` 在调用
|
|
|
56
56
|
<a id="model-experience"></a>
|
|
57
57
|
## 模型体验
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
无;任何模型可见效果都由被调用的 Agent 命令负责。
|
|
60
60
|
|
|
61
61
|
#### KV Cache 影响
|
|
62
62
|
|
|
63
|
-
无直接影响;模型请求仍由 Agent 和 LLM
|
|
63
|
+
无直接影响;模型请求仍由 Agent 和 LLM(大语言模型)包拥有。
|
|
64
64
|
|
|
65
65
|
## 已知限制与延期工作
|
|
66
66
|
|
|
@@ -69,7 +69,7 @@ Session 对象还承载本地提交回显:`session.beginSubmission` 在调用
|
|
|
69
69
|
- 图片字节上限不校验解码后的尺寸或像素数。
|
|
70
70
|
- Control baseline 表示进程本地状态,因此 Host 重启后无法重建 jobs。
|
|
71
71
|
- follow 恢复失败会对调用方可见,而不会无限重试。
|
|
72
|
-
- 浏览器原始字节上传使用一次不带断点续传偏移的流式 HTTP
|
|
72
|
+
- 浏览器原始字节上传使用一次不带断点续传偏移的流式 HTTP 请求;重试会从第零字节重新传输整个文件。
|
|
73
73
|
- 文件引用补全使用共享 Agent lookup,因此可能恢复冷 Session;`skills/list` 目录是不激活 Agent 的 skill 元数据读取路径。
|
|
74
74
|
|
|
75
75
|
|
package/lib/client.js
CHANGED
|
@@ -127,6 +127,7 @@ window.__ModuleLoader__.load({
|
|
|
127
127
|
"compaction/prune",
|
|
128
128
|
"compaction/start",
|
|
129
129
|
"compaction/summary",
|
|
130
|
+
"deliverables/presented",
|
|
130
131
|
"feedback/message-delete",
|
|
131
132
|
"feedback/message-put",
|
|
132
133
|
"feedback/record",
|
|
@@ -148,6 +149,7 @@ window.__ModuleLoader__.load({
|
|
|
148
149
|
"session/title-llm-request",
|
|
149
150
|
"step/end",
|
|
150
151
|
"step/start",
|
|
152
|
+
"subagent/catalog",
|
|
151
153
|
"subagent/descriptor",
|
|
152
154
|
"subagent/model-selection-policy",
|
|
153
155
|
"system/message",
|
|
@@ -2179,87 +2181,6 @@ window.__ModuleLoader__.load({
|
|
|
2179
2181
|
return refs;
|
|
2180
2182
|
}
|
|
2181
2183
|
//#endregion
|
|
2182
|
-
//#region lib/types/client/sessions/selection-history.js
|
|
2183
|
-
function sameSelection(left, right) {
|
|
2184
|
-
return left.sessionId === right.sessionId && left.address?.parentSessionId === right.address?.parentSessionId && left.address?.childSessionId === right.address?.childSessionId;
|
|
2185
|
-
}
|
|
2186
|
-
/**
|
|
2187
|
-
* Bounded LIFO-with-cursor history. Recording a selection made outside
|
|
2188
|
-
* navigation collapses consecutive repeats, truncates the forward tail after
|
|
2189
|
-
* a backtrack, and drops the oldest entry past the bound. Pruning removes
|
|
2190
|
-
* deleted sessions wherever they sit and re-points the cursor onto the same
|
|
2191
|
-
* logical position.
|
|
2192
|
-
*/
|
|
2193
|
-
var SelectionHistory = class {
|
|
2194
|
-
limit;
|
|
2195
|
-
/** Oldest-first; the cursor sits ON the current entry (-1 while empty). */
|
|
2196
|
-
#entries = [];
|
|
2197
|
-
#cursor = -1;
|
|
2198
|
-
constructor(limit = 50) {
|
|
2199
|
-
this.limit = limit;
|
|
2200
|
-
}
|
|
2201
|
-
get entries() {
|
|
2202
|
-
return this.#entries;
|
|
2203
|
-
}
|
|
2204
|
-
get cursor() {
|
|
2205
|
-
return this.#cursor;
|
|
2206
|
-
}
|
|
2207
|
-
/** Whether an older entry exists to go back to. */
|
|
2208
|
-
canBack() {
|
|
2209
|
-
return this.#cursor > 0;
|
|
2210
|
-
}
|
|
2211
|
-
/** Whether a newer entry exists to go forward to. */
|
|
2212
|
-
canForward() {
|
|
2213
|
-
return this.#cursor < this.#entries.length - 1;
|
|
2214
|
-
}
|
|
2215
|
-
/**
|
|
2216
|
-
* Record one selection made outside history navigation.
|
|
2217
|
-
* @param entry - the selection (session, addressed subagent, or clear).
|
|
2218
|
-
*/
|
|
2219
|
-
record(entry) {
|
|
2220
|
-
const current = this.#entries[this.#cursor];
|
|
2221
|
-
if (current !== void 0 && sameSelection(current, entry)) return;
|
|
2222
|
-
this.#entries.length = this.#cursor + 1;
|
|
2223
|
-
this.#entries.push(entry);
|
|
2224
|
-
if (this.#entries.length > this.limit) this.#entries.shift();
|
|
2225
|
-
this.#cursor = this.#entries.length - 1;
|
|
2226
|
-
}
|
|
2227
|
-
/**
|
|
2228
|
-
* Step one entry back.
|
|
2229
|
-
* @returns the entry to replay, or undefined at the oldest bound.
|
|
2230
|
-
*/
|
|
2231
|
-
back() {
|
|
2232
|
-
if (!this.canBack()) return void 0;
|
|
2233
|
-
this.#cursor -= 1;
|
|
2234
|
-
return this.#entries[this.#cursor];
|
|
2235
|
-
}
|
|
2236
|
-
/**
|
|
2237
|
-
* Step one entry forward.
|
|
2238
|
-
* @returns the entry to replay, or undefined at the newest bound.
|
|
2239
|
-
*/
|
|
2240
|
-
forward() {
|
|
2241
|
-
if (!this.canForward()) return void 0;
|
|
2242
|
-
this.#cursor += 1;
|
|
2243
|
-
return this.#entries[this.#cursor];
|
|
2244
|
-
}
|
|
2245
|
-
/**
|
|
2246
|
-
* Drop every entry referencing the removed session wherever it sits,
|
|
2247
|
-
* keeping the cursor on the same logical position.
|
|
2248
|
-
* @param sessionId - the removed session.
|
|
2249
|
-
*/
|
|
2250
|
-
prune(sessionId) {
|
|
2251
|
-
let removedBeforeCursor = 0;
|
|
2252
|
-
for (let index = this.#entries.length - 1; index >= 0; index -= 1) {
|
|
2253
|
-
const entry = this.#entries[index];
|
|
2254
|
-
if (entry === void 0 || entry.sessionId !== sessionId) continue;
|
|
2255
|
-
this.#entries.splice(index, 1);
|
|
2256
|
-
if (index <= this.#cursor) removedBeforeCursor += 1;
|
|
2257
|
-
}
|
|
2258
|
-
this.#cursor -= removedBeforeCursor;
|
|
2259
|
-
if (this.#cursor > this.#entries.length - 1) this.#cursor = this.#entries.length - 1;
|
|
2260
|
-
}
|
|
2261
|
-
};
|
|
2262
|
-
//#endregion
|
|
2263
2184
|
//#region lib/types/client/sessions/manager.js
|
|
2264
2185
|
function sessionSeqCursor(value) {
|
|
2265
2186
|
return value === -1 ? -1 : SessionSeq(value);
|
|
@@ -2310,10 +2231,6 @@ window.__ModuleLoader__.load({
|
|
|
2310
2231
|
*/
|
|
2311
2232
|
jobsBySession = /* @__PURE__ */ new Map();
|
|
2312
2233
|
selected;
|
|
2313
|
-
/** Toolbar back/forward history over selections (session, addressed child, or clear). */
|
|
2314
|
-
selectionHistory = new SelectionHistory();
|
|
2315
|
-
/** While replaying a history entry, the selections it makes must not re-record. */
|
|
2316
|
-
navigatingHistory = false;
|
|
2317
2234
|
listSnapshotCache;
|
|
2318
2235
|
/** Entry-identity cache (reference stability): list rebuilds reuse the previous entry
|
|
2319
2236
|
* object when every field matches — wire refreshes mint all-new summary objects, so identity
|
|
@@ -2331,10 +2248,6 @@ window.__ModuleLoader__.load({
|
|
|
2331
2248
|
this.remote = remote;
|
|
2332
2249
|
this.selected = restoredSelection;
|
|
2333
2250
|
if (restoredAddress !== void 0) this.addresses.set(restoredAddress.childSessionId, restoredAddress);
|
|
2334
|
-
this.selectionHistory.record(restoredSelection === void 0 ? {} : {
|
|
2335
|
-
sessionId: restoredSelection,
|
|
2336
|
-
...restoredAddress !== void 0 ? { address: restoredAddress } : {}
|
|
2337
|
-
});
|
|
2338
2251
|
this.listSnapshotCache = this.buildListSnapshot();
|
|
2339
2252
|
}
|
|
2340
2253
|
/**
|
|
@@ -2348,10 +2261,6 @@ window.__ModuleLoader__.load({
|
|
|
2348
2261
|
this.sessions.get(sessionId)?.configureSubagent(address, address === void 0 ? void 0 : this.catalogs.get(address.parentSessionId)?.parentAvailable);
|
|
2349
2262
|
this.selected = sessionId;
|
|
2350
2263
|
this.completedNotifications.delete(sessionId);
|
|
2351
|
-
if (!this.navigatingHistory) this.selectionHistory.record(address === void 0 ? { sessionId } : {
|
|
2352
|
-
sessionId,
|
|
2353
|
-
address
|
|
2354
|
-
});
|
|
2355
2264
|
this.refreshSubagents(sessionId);
|
|
2356
2265
|
this.notifier.notifyNow();
|
|
2357
2266
|
}
|
|
@@ -2367,69 +2276,14 @@ window.__ModuleLoader__.load({
|
|
|
2367
2276
|
this.sessions.get(address.childSessionId)?.configureSubagent(address, catalog?.parentAvailable);
|
|
2368
2277
|
this.selected = address.childSessionId;
|
|
2369
2278
|
this.completedNotifications.delete(address.childSessionId);
|
|
2370
|
-
if (!this.navigatingHistory) this.selectionHistory.record({
|
|
2371
|
-
sessionId: address.childSessionId,
|
|
2372
|
-
address
|
|
2373
|
-
});
|
|
2374
2279
|
this.refreshSubagents(address.childSessionId);
|
|
2375
2280
|
this.notifier.notifyNow();
|
|
2376
2281
|
}
|
|
2377
2282
|
/** Clear the selection (the layout falls to the no-session view state). */
|
|
2378
2283
|
clearSelection() {
|
|
2379
2284
|
this.selected = void 0;
|
|
2380
|
-
if (!this.navigatingHistory) this.selectionHistory.record({});
|
|
2381
2285
|
this.notifier.notifyNow();
|
|
2382
2286
|
}
|
|
2383
|
-
/** Whether an older selection exists to go back to. */
|
|
2384
|
-
canBack() {
|
|
2385
|
-
return this.selectionHistory.canBack();
|
|
2386
|
-
}
|
|
2387
|
-
/** Whether a newer selection exists to go forward to. */
|
|
2388
|
-
canForward() {
|
|
2389
|
-
return this.selectionHistory.canForward();
|
|
2390
|
-
}
|
|
2391
|
-
/**
|
|
2392
|
-
* Select the previous history entry. Unreachable entries (sessions removed
|
|
2393
|
-
* before their prune landed) are pruned and the walk continues to older
|
|
2394
|
-
* entries; the walk stops silently at the oldest bound.
|
|
2395
|
-
*/
|
|
2396
|
-
back() {
|
|
2397
|
-
while (this.selectionHistory.canBack()) {
|
|
2398
|
-
const entry = this.selectionHistory.back();
|
|
2399
|
-
if (entry === void 0) return;
|
|
2400
|
-
if (!this.canReplay(entry)) {
|
|
2401
|
-
if (entry.sessionId !== void 0) this.selectionHistory.prune(entry.sessionId);
|
|
2402
|
-
continue;
|
|
2403
|
-
}
|
|
2404
|
-
this.replaySelection(entry);
|
|
2405
|
-
return;
|
|
2406
|
-
}
|
|
2407
|
-
}
|
|
2408
|
-
/**
|
|
2409
|
-
* Select the next history entry after a back navigation; silently a
|
|
2410
|
-
* no-op at the newest bound.
|
|
2411
|
-
*/
|
|
2412
|
-
forward() {
|
|
2413
|
-
const entry = this.selectionHistory.forward();
|
|
2414
|
-
if (entry === void 0) return;
|
|
2415
|
-
if (this.canReplay(entry)) this.replaySelection(entry);
|
|
2416
|
-
else if (entry.sessionId !== void 0) this.selectionHistory.prune(entry.sessionId);
|
|
2417
|
-
}
|
|
2418
|
-
/** Whether a history entry names a session that is still listed or addressed. */
|
|
2419
|
-
canReplay(entry) {
|
|
2420
|
-
if (entry.sessionId === void 0) return true;
|
|
2421
|
-
return this.summaries.some((summary) => summary.sessionId === entry.sessionId) || this.addresses.has(entry.sessionId);
|
|
2422
|
-
}
|
|
2423
|
-
/** Replay one entry without recording it back into the history. */
|
|
2424
|
-
replaySelection(entry) {
|
|
2425
|
-
this.navigatingHistory = true;
|
|
2426
|
-
try {
|
|
2427
|
-
if (entry.sessionId === void 0) this.clearSelection();
|
|
2428
|
-
else this.select(entry.sessionId);
|
|
2429
|
-
} finally {
|
|
2430
|
-
this.navigatingHistory = false;
|
|
2431
|
-
}
|
|
2432
|
-
}
|
|
2433
2287
|
/**
|
|
2434
2288
|
* Return the durable catalog address retained for one child.
|
|
2435
2289
|
* @param sessionId - possible addressed child id.
|
|
@@ -2646,7 +2500,6 @@ window.__ModuleLoader__.load({
|
|
|
2646
2500
|
let summaries = baseline;
|
|
2647
2501
|
for (const mutation of mutations) {
|
|
2648
2502
|
summaries = applyMutation(summaries, mutation);
|
|
2649
|
-
if (mutation.kind === "remove") this.selectionHistory.prune(mutation.sessionId);
|
|
2650
2503
|
this.summaries = summaries;
|
|
2651
2504
|
this.syncCompletedNotifications();
|
|
2652
2505
|
}
|
|
@@ -2787,7 +2640,6 @@ window.__ModuleLoader__.load({
|
|
|
2787
2640
|
recordMutation(mutation) {
|
|
2788
2641
|
this.listMutations?.push(mutation);
|
|
2789
2642
|
this.summaries = applyMutation(this.summaries, mutation);
|
|
2790
|
-
if (mutation.kind === "remove") this.selectionHistory.prune(mutation.sessionId);
|
|
2791
2643
|
this.syncCompletedNotifications();
|
|
2792
2644
|
this.notifier.markDirty();
|
|
2793
2645
|
}
|
|
@@ -3282,22 +3134,6 @@ window.__ModuleLoader__.load({
|
|
|
3282
3134
|
clear() {
|
|
3283
3135
|
this.manager.clearSelection();
|
|
3284
3136
|
}
|
|
3285
|
-
/** Whether the transient selection history has an older entry to go back to. */
|
|
3286
|
-
canBack() {
|
|
3287
|
-
return this.manager.canBack();
|
|
3288
|
-
}
|
|
3289
|
-
/** Whether the transient selection history has a newer entry to go forward to. */
|
|
3290
|
-
canForward() {
|
|
3291
|
-
return this.manager.canForward();
|
|
3292
|
-
}
|
|
3293
|
-
/** Select the previous history entry (prunes unreachable entries on the way). */
|
|
3294
|
-
back() {
|
|
3295
|
-
this.manager.back();
|
|
3296
|
-
}
|
|
3297
|
-
/** Select the next history entry after a back navigation. */
|
|
3298
|
-
forward() {
|
|
3299
|
-
this.manager.forward();
|
|
3300
|
-
}
|
|
3301
3137
|
/**
|
|
3302
3138
|
* Refresh the real Session baseline, reusing an in-flight pull.
|
|
3303
3139
|
* @returns completion of the current or newly started baseline pull.
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { hostname } from "node:os";
|
|
1
2
|
import z from "@deepseek-ai/schemastery";
|
|
2
3
|
import { AssistantStreamAccumulator, ReasoningEffortId, assistantStreamChunks, createUserMessage, errorChain, freezeMessage } from "@deepseek-ai/dsh-llm";
|
|
3
|
-
import { canOpenNativePath, openNativePath } from "@deepseek-ai/dsh-native-command";
|
|
4
|
+
import { canOpenNativePath, nativeFileManager, openNativePath, revealNativePath } from "@deepseek-ai/dsh-native-command";
|
|
4
5
|
import { Remote, RemoteError, TypertRemoteService, remoteErrorOf } from "@deepseek-ai/dsh-typert-protocol";
|
|
5
6
|
import { mkdir } from "node:fs/promises";
|
|
6
7
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
@@ -2730,6 +2731,7 @@ let SessionController = (() => {
|
|
|
2730
2731
|
history;
|
|
2731
2732
|
listState;
|
|
2732
2733
|
openPath;
|
|
2734
|
+
revealPath;
|
|
2733
2735
|
canOpenPath;
|
|
2734
2736
|
promotions = /* @__PURE__ */ new Set();
|
|
2735
2737
|
/**
|
|
@@ -2756,6 +2758,7 @@ let SessionController = (() => {
|
|
|
2756
2758
|
});
|
|
2757
2759
|
this.listState = new ApiSessionList(ctx);
|
|
2758
2760
|
this.openPath = internals.openPath ?? openNativePath;
|
|
2761
|
+
this.revealPath = internals.revealPath ?? revealNativePath;
|
|
2759
2762
|
this.canOpenPath = internals.canOpenPath ?? (() => config.nativeOpen ?? (internals.openPath !== void 0 || canOpenNativePath()));
|
|
2760
2763
|
ctx.plugin(SessionFileReferences);
|
|
2761
2764
|
ctx.plugin(SessionMediaReferences);
|
|
@@ -2879,6 +2882,18 @@ let SessionController = (() => {
|
|
|
2879
2882
|
return this.canOpenPath();
|
|
2880
2883
|
}
|
|
2881
2884
|
/**
|
|
2885
|
+
* Describe the serving desktop for authenticated file-action routes.
|
|
2886
|
+
* @returns Host name, configured availability, and platform-specific file-manager behavior.
|
|
2887
|
+
*/
|
|
2888
|
+
workspaceDesktop() {
|
|
2889
|
+
const fileManager = nativeFileManager();
|
|
2890
|
+
return {
|
|
2891
|
+
name: hostname(),
|
|
2892
|
+
available: fileManager !== null && this.canOpenPath(),
|
|
2893
|
+
fileManager
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
/**
|
|
2882
2897
|
* Open one path prepared by a Session-aware caller on the Host desktop.
|
|
2883
2898
|
* @param request - path after best-effort Session workspace resolution.
|
|
2884
2899
|
* @param signal - caller lifetime; abort terminates the native command.
|
|
@@ -2889,7 +2904,8 @@ let SessionController = (() => {
|
|
|
2889
2904
|
if (request.path.length === 0) throw new RemoteError("gateway/bad-request", "session.openWorkspacePath requires a non-empty path", {});
|
|
2890
2905
|
signal.throwIfAborted();
|
|
2891
2906
|
try {
|
|
2892
|
-
await this.
|
|
2907
|
+
if (request.action === "reveal") await this.revealPath(request.path, signal);
|
|
2908
|
+
else await this.openPath(request.path, signal);
|
|
2893
2909
|
return { opened: true };
|
|
2894
2910
|
} catch (error) {
|
|
2895
2911
|
if (signal.aborted) throw new RemoteError("gateway/cancelled", "path open was aborted", {});
|