@deepseek-ai/dsh-api-session-controller 0.1.2-alpha.2 → 0.1.2-alpha.3
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 +2 -2
- package/README.zh.md +2 -2
- package/lib/client.js +42 -1
- package/lib/index.js +2 -17
- package/lib/typert.host.js +1 -1
- package/lib/types/client/contract/session.d.ts +11 -0
- package/lib/types/client/contract/snapshot.d.ts +4 -0
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/sessions/queue-mirror.js +3 -0
- package/lib/types/client/sessions/session.d.ts +8 -0
- package/lib/types/client/sessions/session.js +61 -0
- package/lib/types/commands.js +2 -13
- package/package.json +59 -59
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: 94e8697cd7f5bf75746edd5d691956e680d9b261
|
|
6
|
+
README.zh.md: 0228ca15624413fcd5cb6e17afb8aaf0a28f780a
|
package/README.md
CHANGED
|
@@ -27,9 +27,9 @@ History pages and follow opening snapshots carry a discriminated `SessionHistory
|
|
|
27
27
|
|
|
28
28
|
Each endpoint states its activation policy. List, 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. Create and fork are the only operations that create a new Agent directly. The skill catalog instead uses a live Agent when present or the recorded preset's standing scope when cold, so listing never starts an Agent.
|
|
29
29
|
|
|
30
|
-
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`, and `append` changes, and repairs reconnect or sequence gaps through a tail page. Ordinary records cover `[event.seq, event.seq]`; packed rows cover `[event.seq, event.seq + memberCount - 1]`. 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.
|
|
30
|
+
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`, and `append` 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. Ordinary records cover `[event.seq, event.seq]`; packed rows cover `[event.seq, event.seq + memberCount - 1]`. 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.
|
|
31
31
|
|
|
32
|
-
The Session object also carries local submission echoes: `session.beginSubmission` inserts one into `SessionSnapshot.pendingSubmissions` synchronously, before the caller serializes and prompts, so a conversation UI can show the message on the submit click's own frame. The prompt's `requestId` is the correlation identity
|
|
32
|
+
The Session object also carries local submission echoes: `session.beginSubmission` inserts one into `SessionSnapshot.pendingSubmissions` synchronously, before the caller serializes and prompts, so a conversation UI can show the message on the submit click's own frame. Session derives each echo's `transcript`, `queued`, or `steering` placement from its current running state and the requested delivery mode, then retains that placement while serialization is in flight. The prompt's `requestId` is the correlation identity: the Host echoes it as the durable user source's `rpcId`, and queue occurrences project it as `SessionQueuedItem.rpcId`. An echo retires one animation frame after its durable event or queue occurrence is observed (the delay keeps it renderable until the replacement is ready), immediately when its identified prompt fails or is abandoned, and as failed on disposal; each retirement fires the registered `onRetire` callback exactly once. Echoes are Client memory only; reload and reconnect rebuild the conversation from durable events alone.
|
|
33
33
|
|
|
34
34
|
-----
|
|
35
35
|
|
package/README.zh.md
CHANGED
|
@@ -27,9 +27,9 @@ kind: "package-reference"
|
|
|
27
27
|
|
|
28
28
|
每个 endpoint 都声明自己的激活策略。列表、搜索、附件、历史页、日志跟随、skill 发现和工作区路径打开可以在不激活 Agent 的情况下检查 persistence;`canOpenWorkspacePath()` 无需指定 Session 即可报告原生打开能力。queue 变更与取消要求 live 状态;模型、重命名、prompt 和文件引用操作可以解析或恢复普通 Session。只有 create 与 fork 会直接创建新 Agent。skill 目录则优先使用已有 live Agent,否则使用所记录 preset 的常驻 scope,因此列表查询绝不会启动 Agent。
|
|
29
29
|
|
|
30
|
-
Client adapter 提供 `SessionEventStream`,即绑定到一个普通 Session 或 direct subagent address 的 Gateway `RemoteJournalStream`。它在读取首个 page 前打开 follow,只发布连续的 `replace`、`prepend` 和 `append` 变更,并通过 tail page 修复重连或 seq
|
|
30
|
+
Client adapter 提供 `SessionEventStream`,即绑定到一个普通 Session 或 direct subagent address 的 Gateway `RemoteJournalStream`。它在读取首个 page 前打开 follow,只发布连续的 `replace`、`prepend` 和 `append` 变更,并通过 tail page 修复重连或 seq 缺口。向后分页有两个动词:`loadOlder()` 拉一页 50 条 message,而 `loadThrough(seq)`——轮次跳转加载器——按 200 条 message 一页循环拉取直到窗口覆盖目标 seq,重复调用会下调共享目标,遇到无进展的页即停止,忙碌状态复用同一个 `loadingOlder` 快照位。普通 record 覆盖 `[event.seq, event.seq]`,packed row 覆盖 `[event.seq, event.seq + memberCount - 1]`。业务、persistence 或无法恢复的连续性错误会终止 stream,只有物理载体断开才触发自动恢复。`SessionControlStream` 是 Gateway `RemoteSnapshotStream`;每代都以完整的进程本地 baseline 开始,因此重连会替换 queue、jobs 和 projection 状态,而不会把瞬态值当作 durable event。
|
|
31
31
|
|
|
32
|
-
Session 对象还承载本地提交回显:`session.beginSubmission` 在调用方序列化与 prompt 之前,同步把一条回显写入 `SessionSnapshot.pendingSubmissions`,会话 UI 因此能在点击提交的当帧显示消息。prompt 的 `requestId`
|
|
32
|
+
Session 对象还承载本地提交回显:`session.beginSubmission` 在调用方序列化与 prompt 之前,同步把一条回显写入 `SessionSnapshot.pendingSubmissions`,会话 UI 因此能在点击提交的当帧显示消息。Session 根据当前运行状态与请求的投递模式推导每条回显的 `transcript`、`queued` 或 `steering` 位置,并在序列化期间保留该位置。prompt 的 `requestId` 是关联标识:Host 把它回显为 durable user source 的 `rpcId`,queue occurrence 也把它投影为 `SessionQueuedItem.rpcId`。回显在观察到其 durable event 或 queue occurrence 后延迟一个动画帧退休,该延迟保证替代内容就绪前回显仍可渲染;带标识的 prompt 失败或被放弃时立即退休,销毁时按 failed 退休;每次退休恰好触发一次注册的 `onRetire` 回调。回显只存在于 Client 内存;刷新与重连只从 durable event 重建会话。
|
|
33
33
|
|
|
34
34
|
-----
|
|
35
35
|
|
package/lib/client.js
CHANGED
|
@@ -671,7 +671,7 @@ window.__ModuleLoader__.load({
|
|
|
671
671
|
//#region lib/types/client/sessions/queue-mirror.js
|
|
672
672
|
const QUEUE_PREVIEW_CHARS = 200;
|
|
673
673
|
function previewOf(content) {
|
|
674
|
-
const flat = content.map((block) => block.type === "text" ? block.text : `[${block.type}]`).join(" ").replace(/\s+/g, " ").trim();
|
|
674
|
+
const flat = content.filter((block) => block.type !== "image").map((block) => block.type === "text" ? block.text : `[${block.type}]`).join(" ").replace(/\s+/g, " ").trim();
|
|
675
675
|
const chars = Array.from(flat);
|
|
676
676
|
return chars.length > QUEUE_PREVIEW_CHARS ? `${chars.slice(0, QUEUE_PREVIEW_CHARS).join("")}…` : flat;
|
|
677
677
|
}
|
|
@@ -740,6 +740,10 @@ window.__ModuleLoader__.load({
|
|
|
740
740
|
* passes drop all writes once the generation moves on. */
|
|
741
741
|
openGeneration = 0;
|
|
742
742
|
loadingOlder = false;
|
|
743
|
+
/** Shared low-water target of the running jump loop; null when no jump is paging. */
|
|
744
|
+
jumpTargetSeq = null;
|
|
745
|
+
/** The running jump loop's completion, shared by retargeting callers. */
|
|
746
|
+
jumpPromise = null;
|
|
743
747
|
/** Authoritative stream-only inbox snapshot; pending work never hits history. */
|
|
744
748
|
queueMirror = new SessionQueueMirror();
|
|
745
749
|
running = false;
|
|
@@ -834,6 +838,7 @@ window.__ModuleLoader__.load({
|
|
|
834
838
|
const requestId = randomUUID();
|
|
835
839
|
this.pendingSubmissions = [...this.pendingSubmissions, {
|
|
836
840
|
requestId,
|
|
841
|
+
placement: this.running ? input.mode === "steer" ? "steering" : "queued" : "transcript",
|
|
837
842
|
time: Date.now(),
|
|
838
843
|
text: input.text,
|
|
839
844
|
images: input.images
|
|
@@ -1014,6 +1019,42 @@ window.__ModuleLoader__.load({
|
|
|
1014
1019
|
this.notifier.markDirty();
|
|
1015
1020
|
}
|
|
1016
1021
|
}
|
|
1022
|
+
/** Jump loader: page backwards until the window covers seq (see ISession.loadThrough). */
|
|
1023
|
+
loadThrough(seq) {
|
|
1024
|
+
if (this.openState !== "open" || !this.hasMore || this.baseSeq <= seq) return Promise.resolve();
|
|
1025
|
+
if (this.jumpPromise !== null) {
|
|
1026
|
+
this.jumpTargetSeq = Math.min(this.jumpTargetSeq ?? seq, seq);
|
|
1027
|
+
return this.jumpPromise;
|
|
1028
|
+
}
|
|
1029
|
+
if (this.loadingOlder) return Promise.resolve();
|
|
1030
|
+
this.jumpTargetSeq = seq;
|
|
1031
|
+
this.loadingOlder = true;
|
|
1032
|
+
this.notifier.markDirty();
|
|
1033
|
+
const generation = this.openGeneration;
|
|
1034
|
+
this.jumpPromise = (async () => {
|
|
1035
|
+
try {
|
|
1036
|
+
while (this.hasMore && this.jumpTargetSeq !== null && this.baseSeq > this.jumpTargetSeq) {
|
|
1037
|
+
if (generation !== this.openGeneration) return;
|
|
1038
|
+
const events = this.events;
|
|
1039
|
+
if (events === void 0) return;
|
|
1040
|
+
const before = this.baseSeq;
|
|
1041
|
+
await events.prepend({
|
|
1042
|
+
beforeSeq: this.baseSeq,
|
|
1043
|
+
maxMessages: 200
|
|
1044
|
+
});
|
|
1045
|
+
if (this.baseSeq >= before) return;
|
|
1046
|
+
}
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
if (!(0, _deepseek_ai_dsh_api_gateway_client.isRemoteFailure)(error)) console.error("[session-controller] loadThrough failed:", error);
|
|
1049
|
+
} finally {
|
|
1050
|
+
this.jumpTargetSeq = null;
|
|
1051
|
+
this.jumpPromise = null;
|
|
1052
|
+
this.loadingOlder = false;
|
|
1053
|
+
this.notifier.markDirty();
|
|
1054
|
+
}
|
|
1055
|
+
})();
|
|
1056
|
+
return this.jumpPromise;
|
|
1057
|
+
}
|
|
1017
1058
|
/** Rebuild an opened history source after address replacement.
|
|
1018
1059
|
* Invalidates any in-flight open first; queue state belongs to the independently
|
|
1019
1060
|
* reconnecting control stream and remains untouched. */
|
package/lib/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
|
7
7
|
import { SessionQueryError } from "@deepseek-ai/dsh-session-query";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { brandString } from "@deepseek-ai/dsh-brand";
|
|
10
|
-
import { AttachmentError,
|
|
10
|
+
import { AttachmentError, admitPromptContent } from "@deepseek-ai/dsh-attachment";
|
|
11
11
|
import { SessionTitleInvalidError } from "@deepseek-ai/dsh-session-title";
|
|
12
12
|
import { canonicalClientTimeZone } from "@deepseek-ai/dsh-util-time";
|
|
13
13
|
import { Deque } from "@deepseek-ai/dsh-deque";
|
|
@@ -745,7 +745,7 @@ var SessionCommandController = class {
|
|
|
745
745
|
if (model.inputModalities !== void 0 && !model.inputModalities.includes("image")) throw new RemoteError("session/attachment-invalid", `Model "${current.model}" does not support image input.`, { reason: "MODEL_DOES_NOT_SUPPORT_IMAGES" });
|
|
746
746
|
}
|
|
747
747
|
const message = createUserMessage({
|
|
748
|
-
content: await
|
|
748
|
+
content: await admitPromptContent(this.ctx.attachments, request.content),
|
|
749
749
|
source
|
|
750
750
|
});
|
|
751
751
|
if (request.mode === "steer") agent.steer(message);
|
|
@@ -874,21 +874,6 @@ var SessionCommandController = class {
|
|
|
874
874
|
}
|
|
875
875
|
}
|
|
876
876
|
};
|
|
877
|
-
async function durablePromptContent(ctx, content) {
|
|
878
|
-
if (content.every((part) => part.type === "text")) return content.map((part) => ({
|
|
879
|
-
type: "text",
|
|
880
|
-
text: part.text
|
|
881
|
-
}));
|
|
882
|
-
const refs = await admitEncodedImages(ctx.attachments, content.filter((part) => part.type === "image"));
|
|
883
|
-
let next = 0;
|
|
884
|
-
return content.map((part) => part.type === "text" ? {
|
|
885
|
-
type: "text",
|
|
886
|
-
text: part.text
|
|
887
|
-
} : {
|
|
888
|
-
type: "image",
|
|
889
|
-
attachment: refs[next++]
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
877
|
function imageBlockIn(content, match) {
|
|
893
878
|
if (!Array.isArray(content)) return void 0;
|
|
894
879
|
for (const value of content) {
|
package/lib/typert.host.js
CHANGED
|
@@ -1557,7 +1557,7 @@ export const TYPERT = {
|
|
|
1557
1557
|
},
|
|
1558
1558
|
{
|
|
1559
1559
|
"name": "RemoteErrorDetailsMap",
|
|
1560
|
-
"declaration": "export interface RemoteErrorDetailsMap {\n 'gateway/bad-request': { readonly issues?: readonly object[]; };\n 'gateway/cancelled': {};\n 'gateway/internal': {};\n 'llm/model-discovery-rejected': { readonly settingsNs: string; readonly baseURL?: string; };\n 'session/not-found': { readonly sessionId: SessionId; };\n 'agent-preset/not-found': { readonly agentPreset: string; readonly available: readonly string[]; };\n 'agent-preset/invalid': { readonly agentPreset: string; readonly reason: string; };\n 'agent-preset/read-only': { readonly agentPreset: string; readonly reason: string; };\n 'agent-preset/locked': { readonly sessionId: SessionId; readonly agentPreset: string; };\n 'workspace/not-found': { readonly workspaceId: WorkspaceId; };\n 'session/model-unavailable': { readonly provider: string; readonly model: string; };\n 'session/conflict': { readonly sessionId: SessionId; readonly requestedCwd: string; readonly existingCwd?: string; };\n 'session/agent-busy': { readonly reason: string; };\n 'session/invalid-time-zone': { readonly value: string; };\n 'session/workspace-attach-failed': { readonly sessionId: SessionId; readonly workspaceId: string; };\n 'agent-preset/conflict': { readonly sessionId: SessionId; readonly requestedPreset: string; readonly existingPreset?: string; };\n 'session/attachment-invalid': { readonly reason: string; };\n 'session/queue-item-not-found': { readonly itemId: MessageId; };\n 'session/steer-unavailable': { readonly itemId: MessageId; };\n 'session/title-invalid': { readonly sessionId: SessionId; };\n 'session/fork-unavailable': { readonly sessionId: SessionId; };\n 'subagent/not-found': { readonly parentSessionId: SessionId; readonly childSessionId: SessionId; };\n 'subagent/catalog-diagnostic': { readonly parentSessionId: SessionId; readonly childSessionId: SessionId; readonly reason: 'corrupt' | 'unsupported' | 'unavailable'; };\n 'subagent/invalid-time-zone': { readonly value: string; };\n 'subagent/parent-unavailable': { readonly parentSessionId: SessionId; };\n 'subagent/not-resumable': { readonly childSessionId: SessionId; };\n 'subagent/unauthorized': { readonly childSessionId: SessionId; };\n 'subagent/attachment-
|
|
1560
|
+
"declaration": "export interface RemoteErrorDetailsMap {\n 'gateway/bad-request': { readonly issues?: readonly object[]; };\n 'gateway/cancelled': {};\n 'gateway/internal': {};\n 'llm/model-discovery-rejected': { readonly settingsNs: string; readonly baseURL?: string; };\n 'session/not-found': { readonly sessionId: SessionId; };\n 'agent-preset/not-found': { readonly agentPreset: string; readonly available: readonly string[]; };\n 'agent-preset/invalid': { readonly agentPreset: string; readonly reason: string; };\n 'agent-preset/read-only': { readonly agentPreset: string; readonly reason: string; };\n 'agent-preset/locked': { readonly sessionId: SessionId; readonly agentPreset: string; };\n 'workspace/not-found': { readonly workspaceId: WorkspaceId; };\n 'session/model-unavailable': { readonly provider: string; readonly model: string; };\n 'session/conflict': { readonly sessionId: SessionId; readonly requestedCwd: string; readonly existingCwd?: string; };\n 'session/agent-busy': { readonly reason: string; };\n 'session/invalid-time-zone': { readonly value: string; };\n 'session/workspace-attach-failed': { readonly sessionId: SessionId; readonly workspaceId: string; };\n 'agent-preset/conflict': { readonly sessionId: SessionId; readonly requestedPreset: string; readonly existingPreset?: string; };\n 'session/attachment-invalid': { readonly reason: string; };\n 'session/queue-item-not-found': { readonly itemId: MessageId; };\n 'session/steer-unavailable': { readonly itemId: MessageId; };\n 'session/title-invalid': { readonly sessionId: SessionId; };\n 'session/fork-unavailable': { readonly sessionId: SessionId; };\n 'subagent/not-found': { readonly parentSessionId: SessionId; readonly childSessionId: SessionId; };\n 'subagent/catalog-diagnostic': { readonly parentSessionId: SessionId; readonly childSessionId: SessionId; readonly reason: 'corrupt' | 'unsupported' | 'unavailable'; };\n 'subagent/invalid-time-zone': { readonly value: string; };\n 'subagent/parent-unavailable': { readonly parentSessionId: SessionId; };\n 'subagent/not-resumable': { readonly childSessionId: SessionId; };\n 'subagent/unauthorized': { readonly childSessionId: SessionId; };\n 'subagent/attachment-invalid': { readonly reason: string; };\n 'subagent/delivery-unavailable': { readonly childSessionId: SessionId; };\n 'subagent/projections-unavailable': {};\n 'settings/rejected': { readonly ns: string; };\n 'settings/conflict': { readonly ns: string; readonly expected: number; readonly actual: number; };\n 'credential/rejected': { readonly ref: string; };\n 'workspace/invalid-path': { readonly path: string; };\n 'workspace/name-conflict': { readonly name: string; };\n 'workspace/move-invalid': { readonly workspaceId: WorkspaceId; readonly sessionId: SessionId; readonly beforeSessionId?: SessionId; };\n 'directory-picker/unavailable': { readonly capability: string; };\n 'directory-picker/unreadable': { readonly path: string; };\n 'directory-picker/exists': { readonly path: string; };\n 'directory-picker/create-failed': { readonly path: string; };\n}"
|
|
1561
1561
|
},
|
|
1562
1562
|
{
|
|
1563
1563
|
"name": "ReplayEnvelope",
|
|
@@ -28,6 +28,8 @@ export type PendingSubmissionRetirement = {
|
|
|
28
28
|
};
|
|
29
29
|
/** Input registering one local submission echo ahead of its prompt call. */
|
|
30
30
|
export interface BeginSubmissionInput {
|
|
31
|
+
/** Delivery mode used with the upcoming prompt. */
|
|
32
|
+
readonly mode: 'queue' | 'steer';
|
|
31
33
|
/** Prompt text exactly as the upcoming prompt will send it. */
|
|
32
34
|
readonly text: string;
|
|
33
35
|
/** Ordered image previews matching the upcoming prompt's image parts. */
|
|
@@ -120,6 +122,15 @@ export interface ISession {
|
|
|
120
122
|
* @returns completion; failures land in snapshot.openState/loadingOlder.
|
|
121
123
|
*/
|
|
122
124
|
loadOlder(): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Page history backwards until the window covers `seq` (inclusive) — the
|
|
127
|
+
* turn-jump loader. Repeated calls while a jump is paging lower its shared
|
|
128
|
+
* target and return the in-flight completion; `snapshot.loadingOlder` is
|
|
129
|
+
* the busy signal for the whole jump.
|
|
130
|
+
* @param seq - durable event seq the window must reach (a turn's `turn/start` seq).
|
|
131
|
+
* @returns completion once covered, exhausted, superseded, or failed soft.
|
|
132
|
+
*/
|
|
133
|
+
loadThrough(seq: number): Promise<void>;
|
|
123
134
|
/**
|
|
124
135
|
* Execute one slash-command line against this session's agent — pure
|
|
125
136
|
* admission semantics (the host executor durably logs the lifecycle).
|
|
@@ -27,6 +27,8 @@ export interface PendingSubmissionImage {
|
|
|
27
27
|
/** Intrinsic pixel height, when the submitter has probed it. */
|
|
28
28
|
readonly height?: number;
|
|
29
29
|
}
|
|
30
|
+
/** Client surface selected when a local submission begins. */
|
|
31
|
+
export type PendingSubmissionPlacement = 'transcript' | 'queued' | 'steering';
|
|
30
32
|
/**
|
|
31
33
|
* One local prompt-submission echo: inserted synchronously when a submission
|
|
32
34
|
* begins, so the conversation can show the message before serialization,
|
|
@@ -36,6 +38,8 @@ export interface PendingSubmissionImage {
|
|
|
36
38
|
export interface PendingSubmission {
|
|
37
39
|
/** The prompt RPC identity; the durable `user/message` source echoes it as `rpcId`. */
|
|
38
40
|
readonly requestId: SessionRequestId;
|
|
41
|
+
/** Expected surface until the Host reports the admitted queue or durable occurrence. */
|
|
42
|
+
readonly placement: PendingSubmissionPlacement;
|
|
39
43
|
/** Client wall-clock ms when the submission began. */
|
|
40
44
|
readonly time: number;
|
|
41
45
|
/** Prompt text exactly as it will be sent (one text block). */
|
|
@@ -13,7 +13,7 @@ export type { BeginSubmissionInput, ISession, PendingSubmissionRetirement, Proje
|
|
|
13
13
|
export type { ISessions } from './contract/sessions.ts';
|
|
14
14
|
export { MutableSessionEventSource } from './contract/events.ts';
|
|
15
15
|
export type { SessionEventChange, SessionEventLike, SessionEventLikeEntry, SessionEventSource, SessionEventWindow, SessionLiveEventEntry, } from './contract/events.ts';
|
|
16
|
-
export type { OpenState, PendingSubmission, PendingSubmissionImage, PromptError, QueuedMessage, SessionSnapshot, } from './contract/snapshot.ts';
|
|
16
|
+
export type { OpenState, PendingSubmission, PendingSubmissionImage, PendingSubmissionPlacement, PromptError, QueuedMessage, SessionSnapshot, } from './contract/snapshot.ts';
|
|
17
17
|
declare module '@deepseek-ai/cordis' {
|
|
18
18
|
interface Context {
|
|
19
19
|
/** Client Session object layer and Agent scope owner. */
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
const QUEUE_PREVIEW_CHARS = 200;
|
|
2
|
+
// Image blocks are excluded: queue presentation renders them as thumbnails
|
|
3
|
+
// from `content`, so the text preview covers only what has no visual form.
|
|
2
4
|
function previewOf(content) {
|
|
3
5
|
const flat = content
|
|
6
|
+
.filter(block => block.type !== 'image')
|
|
4
7
|
.map(block => (block.type === 'text' ? block.text : `[${block.type}]`))
|
|
5
8
|
.join(' ').replace(/\s+/g, ' ').trim();
|
|
6
9
|
const chars = Array.from(flat);
|
|
@@ -12,6 +12,8 @@ import type { SessionRemotes } from './remotes.ts';
|
|
|
12
12
|
import { ProjectionValueStore } from './projection-store.ts';
|
|
13
13
|
/** Messages requested per history page. */
|
|
14
14
|
export declare const PAGE_MESSAGES = 50;
|
|
15
|
+
/** Messages requested per page while a turn jump loops backwards (fewer, larger round trips). */
|
|
16
|
+
export declare const JUMP_PAGE_MESSAGES = 200;
|
|
15
17
|
/** Manager-owned observers of a Session object's local state edges. */
|
|
16
18
|
export interface SessionOptions {
|
|
17
19
|
/** Catalog-discovered address selecting non-activating subagent transport. */
|
|
@@ -53,6 +55,10 @@ export declare class Session implements SessionFace {
|
|
|
53
55
|
* passes drop all writes once the generation moves on. */
|
|
54
56
|
private openGeneration;
|
|
55
57
|
private loadingOlder;
|
|
58
|
+
/** Shared low-water target of the running jump loop; null when no jump is paging. */
|
|
59
|
+
private jumpTargetSeq;
|
|
60
|
+
/** The running jump loop's completion, shared by retargeting callers. */
|
|
61
|
+
private jumpPromise;
|
|
56
62
|
/** Authoritative stream-only inbox snapshot; pending work never hits history. */
|
|
57
63
|
private readonly queueMirror;
|
|
58
64
|
private running;
|
|
@@ -189,6 +195,8 @@ export declare class Session implements SessionFace {
|
|
|
189
195
|
open(): Promise<void>;
|
|
190
196
|
/** Page up: pull one earlier page with the window's first seq as beforeSeq and prepend. */
|
|
191
197
|
loadOlder(): Promise<void>;
|
|
198
|
+
/** Jump loader: page backwards until the window covers seq (see ISession.loadThrough). */
|
|
199
|
+
loadThrough(seq: number): Promise<void>;
|
|
192
200
|
/** Rebuild an opened history source after address replacement.
|
|
193
201
|
* Invalidates any in-flight open first; queue state belongs to the independently
|
|
194
202
|
* reconnecting control stream and remains untouched. */
|
|
@@ -9,6 +9,8 @@ import { resolvedClientTimeZone } from "../time-zone.js";
|
|
|
9
9
|
import { SessionQueueMirror } from "./queue-mirror.js";
|
|
10
10
|
/** Messages requested per history page. */
|
|
11
11
|
export const PAGE_MESSAGES = 50;
|
|
12
|
+
/** Messages requested per page while a turn jump loops backwards (fewer, larger round trips). */
|
|
13
|
+
export const JUMP_PAGE_MESSAGES = 200;
|
|
12
14
|
/**
|
|
13
15
|
* Owns a session's event window, lifecycle state, and observable
|
|
14
16
|
* snapshot. React bindings remain outside this data layer. Features see only
|
|
@@ -29,6 +31,10 @@ export class Session {
|
|
|
29
31
|
* passes drop all writes once the generation moves on. */
|
|
30
32
|
openGeneration = 0;
|
|
31
33
|
loadingOlder = false;
|
|
34
|
+
/** Shared low-water target of the running jump loop; null when no jump is paging. */
|
|
35
|
+
jumpTargetSeq = null;
|
|
36
|
+
/** The running jump loop's completion, shared by retargeting callers. */
|
|
37
|
+
jumpPromise = null;
|
|
32
38
|
/** Authoritative stream-only inbox snapshot; pending work never hits history. */
|
|
33
39
|
queueMirror = new SessionQueueMirror();
|
|
34
40
|
running = false;
|
|
@@ -125,6 +131,9 @@ export class Session {
|
|
|
125
131
|
const requestId = randomUUID();
|
|
126
132
|
this.pendingSubmissions = [...this.pendingSubmissions, {
|
|
127
133
|
requestId,
|
|
134
|
+
placement: this.running
|
|
135
|
+
? input.mode === 'steer' ? 'steering' : 'queued'
|
|
136
|
+
: 'transcript',
|
|
128
137
|
time: Date.now(),
|
|
129
138
|
text: input.text,
|
|
130
139
|
images: input.images,
|
|
@@ -300,6 +309,58 @@ export class Session {
|
|
|
300
309
|
this.notifier.markDirty();
|
|
301
310
|
}
|
|
302
311
|
}
|
|
312
|
+
/** Jump loader: page backwards until the window covers seq (see ISession.loadThrough). */
|
|
313
|
+
loadThrough(seq) {
|
|
314
|
+
if (this.openState !== 'open' || !this.hasMore || this.baseSeq <= seq)
|
|
315
|
+
return Promise.resolve();
|
|
316
|
+
if (this.jumpPromise !== null) {
|
|
317
|
+
// Retarget the running loop to the lowest requested seq.
|
|
318
|
+
this.jumpTargetSeq = Math.min(this.jumpTargetSeq ?? seq, seq);
|
|
319
|
+
return this.jumpPromise;
|
|
320
|
+
}
|
|
321
|
+
// A plain single-page pull owns the busy flag; the jump does not queue
|
|
322
|
+
// behind it (the caller retries once it settles) and must leave no
|
|
323
|
+
// target behind — only the loop's finally clears that field, and no
|
|
324
|
+
// loop starts here.
|
|
325
|
+
if (this.loadingOlder)
|
|
326
|
+
return Promise.resolve();
|
|
327
|
+
this.jumpTargetSeq = seq;
|
|
328
|
+
this.loadingOlder = true;
|
|
329
|
+
this.notifier.markDirty();
|
|
330
|
+
// Stale-pass guard (the doOpen pattern): a resync mid-loop replaces the
|
|
331
|
+
// stream generation; this pass then stops instead of paging the new
|
|
332
|
+
// generation toward its old target.
|
|
333
|
+
const generation = this.openGeneration;
|
|
334
|
+
this.jumpPromise = (async () => {
|
|
335
|
+
try {
|
|
336
|
+
while (this.hasMore && this.jumpTargetSeq !== null && this.baseSeq > this.jumpTargetSeq) {
|
|
337
|
+
if (generation !== this.openGeneration)
|
|
338
|
+
return;
|
|
339
|
+
const events = this.events;
|
|
340
|
+
if (events === undefined)
|
|
341
|
+
return;
|
|
342
|
+
const before = this.baseSeq;
|
|
343
|
+
await events.prepend({ beforeSeq: this.baseSeq, maxMessages: JUMP_PAGE_MESSAGES });
|
|
344
|
+
// No-progress guard: an empty or dropped page that still claims more
|
|
345
|
+
// history must end the loop, not spin it.
|
|
346
|
+
if (this.baseSeq >= before)
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
if (!isRemoteFailure(error)) {
|
|
352
|
+
console.error('[session-controller] loadThrough failed:', error);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
finally {
|
|
356
|
+
this.jumpTargetSeq = null;
|
|
357
|
+
this.jumpPromise = null;
|
|
358
|
+
this.loadingOlder = false;
|
|
359
|
+
this.notifier.markDirty();
|
|
360
|
+
}
|
|
361
|
+
})();
|
|
362
|
+
return this.jumpPromise;
|
|
363
|
+
}
|
|
303
364
|
/** Rebuild an opened history source after address replacement.
|
|
304
365
|
* Invalidates any in-flight open first; queue state belongs to the independently
|
|
305
366
|
* reconnecting control stream and remains untouched. */
|
package/lib/types/commands.js
CHANGED
|
@@ -53,7 +53,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
53
53
|
});
|
|
54
54
|
import { randomUUID } from 'node:crypto';
|
|
55
55
|
import { brandString } from '@deepseek-ai/dsh-brand';
|
|
56
|
-
import { AttachmentError,
|
|
56
|
+
import { AttachmentError, admitPromptContent } from '@deepseek-ai/dsh-attachment';
|
|
57
57
|
import { ReasoningEffortId, createUserMessage, freezeMessage, } from '@deepseek-ai/dsh-llm';
|
|
58
58
|
import { SessionQueryError } from '@deepseek-ai/dsh-session-query';
|
|
59
59
|
import { SessionTitleInvalidError } from '@deepseek-ai/dsh-session-title';
|
|
@@ -296,7 +296,7 @@ export class SessionCommandController {
|
|
|
296
296
|
throw new RemoteError('session/attachment-invalid', `Model "${current.model}" does not support image input.`, { reason: 'MODEL_DOES_NOT_SUPPORT_IMAGES' });
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
|
-
const content = await
|
|
299
|
+
const content = await admitPromptContent(this.ctx.attachments, request.content);
|
|
300
300
|
const message = createUserMessage({ content, source });
|
|
301
301
|
if (request.mode === 'steer')
|
|
302
302
|
agent.steer(message);
|
|
@@ -457,17 +457,6 @@ export class SessionCommandController {
|
|
|
457
457
|
return undefined;
|
|
458
458
|
}
|
|
459
459
|
}
|
|
460
|
-
async function durablePromptContent(ctx, content) {
|
|
461
|
-
if (content.every(part => part.type === 'text')) {
|
|
462
|
-
return content.map(part => ({ type: 'text', text: part.text }));
|
|
463
|
-
}
|
|
464
|
-
const refs = await admitEncodedImages(ctx.attachments, content.filter(part => part.type === 'image'));
|
|
465
|
-
let next = 0;
|
|
466
|
-
return content.map(part => part.type === 'text'
|
|
467
|
-
? { type: 'text', text: part.text }
|
|
468
|
-
// admitEncodedImages returns one reference per image part in order.
|
|
469
|
-
: { type: 'image', attachment: refs[next++] });
|
|
470
|
-
}
|
|
471
460
|
function imageBlockIn(content, match) {
|
|
472
461
|
if (!Array.isArray(content))
|
|
473
462
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-api-session-controller",
|
|
3
3
|
"description": "Session Remote commands, cold reads, and live control transport",
|
|
4
|
-
"version": "0.1.2-alpha.
|
|
4
|
+
"version": "0.1.2-alpha.3",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -70,37 +70,37 @@
|
|
|
70
70
|
"license": "MIT",
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"zod": "^4.4.3",
|
|
73
|
-
"@deepseek-ai/dsh-brand": "^0.1.2-alpha.
|
|
74
|
-
"@deepseek-ai/dsh-deque": "^0.1.2-alpha.
|
|
73
|
+
"@deepseek-ai/dsh-brand": "^0.1.2-alpha.3",
|
|
74
|
+
"@deepseek-ai/dsh-deque": "^0.1.2-alpha.3",
|
|
75
75
|
"@deepseek-ai/schemastery": "^3.18.2"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
78
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
79
|
-
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.
|
|
80
|
-
"@deepseek-ai/dsh-agent-default-model": "^0.1.2-alpha.
|
|
81
|
-
"@deepseek-ai/dsh-agent-presets": "^0.1.2-alpha.
|
|
82
|
-
"@deepseek-ai/dsh-api-gateway": "^0.1.2-alpha.
|
|
83
|
-
"@deepseek-ai/dsh-attachment": "^0.1.2-alpha.
|
|
84
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.
|
|
85
|
-
"@deepseek-ai/dsh-file-reference": "^0.1.2-alpha.
|
|
86
|
-
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.
|
|
87
|
-
"@deepseek-ai/dsh-jobs": "^0.1.2-alpha.
|
|
88
|
-
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.
|
|
89
|
-
"@deepseek-ai/dsh-native-command": "^0.1.2-alpha.
|
|
90
|
-
"@deepseek-ai/dsh-scope": "^0.1.2-alpha.
|
|
91
|
-
"@deepseek-ai/dsh-session-
|
|
92
|
-
"@deepseek-ai/dsh-session-
|
|
93
|
-
"@deepseek-ai/dsh-
|
|
94
|
-
"@deepseek-ai/dsh-
|
|
95
|
-
"@deepseek-ai/dsh-subagent": "^0.1.2-alpha.
|
|
96
|
-
"@deepseek-ai/dsh-typert-protocol": "^0.1.2-alpha.
|
|
97
|
-
"@deepseek-ai/dsh-
|
|
98
|
-
"@deepseek-ai/dsh-
|
|
99
|
-
"@deepseek-ai/dsh-
|
|
100
|
-
"@deepseek-ai/dsh-util-workspace-path": "^0.1.2-alpha.
|
|
101
|
-
"@deepseek-ai/dsh-workspace": "^0.1.2-alpha.
|
|
102
|
-
"@deepseek-ai/dsh-session": "^0.1.2-alpha.
|
|
103
|
-
"@deepseek-ai/dsh-session-
|
|
79
|
+
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.3",
|
|
80
|
+
"@deepseek-ai/dsh-agent-default-model": "^0.1.2-alpha.3",
|
|
81
|
+
"@deepseek-ai/dsh-agent-presets": "^0.1.2-alpha.3",
|
|
82
|
+
"@deepseek-ai/dsh-api-gateway": "^0.1.2-alpha.3",
|
|
83
|
+
"@deepseek-ai/dsh-attachment": "^0.1.2-alpha.3",
|
|
84
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.3",
|
|
85
|
+
"@deepseek-ai/dsh-file-reference": "^0.1.2-alpha.3",
|
|
86
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
|
|
87
|
+
"@deepseek-ai/dsh-jobs": "^0.1.2-alpha.3",
|
|
88
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.3",
|
|
89
|
+
"@deepseek-ai/dsh-native-command": "^0.1.2-alpha.3",
|
|
90
|
+
"@deepseek-ai/dsh-scope": "^0.1.2-alpha.3",
|
|
91
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.2-alpha.3",
|
|
92
|
+
"@deepseek-ai/dsh-session-projection-cache": "^0.1.2-alpha.3",
|
|
93
|
+
"@deepseek-ai/dsh-session-query": "^0.1.2-alpha.3",
|
|
94
|
+
"@deepseek-ai/dsh-skill": "^0.1.2-alpha.3",
|
|
95
|
+
"@deepseek-ai/dsh-subagent": "^0.1.2-alpha.3",
|
|
96
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.2-alpha.3",
|
|
97
|
+
"@deepseek-ai/dsh-typert-registry": "^0.1.2-alpha.3",
|
|
98
|
+
"@deepseek-ai/dsh-util-time": "^0.1.2-alpha.3",
|
|
99
|
+
"@deepseek-ai/dsh-session-title": "^0.1.2-alpha.3",
|
|
100
|
+
"@deepseek-ai/dsh-util-workspace-path": "^0.1.2-alpha.3",
|
|
101
|
+
"@deepseek-ai/dsh-workspace": "^0.1.2-alpha.3",
|
|
102
|
+
"@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
|
|
103
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.3"
|
|
104
104
|
},
|
|
105
105
|
"peerDependenciesMeta": {
|
|
106
106
|
"@deepseek-ai/dsh-jobs": {
|
|
@@ -115,37 +115,37 @@
|
|
|
115
115
|
},
|
|
116
116
|
"devDependencies": {
|
|
117
117
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
118
|
-
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.
|
|
119
|
-
"@deepseek-ai/dsh-agent-default-model": "^0.1.2-alpha.
|
|
120
|
-
"@deepseek-ai/dsh-agent-presets": "^0.1.2-alpha.
|
|
121
|
-
"@deepseek-ai/dsh-
|
|
122
|
-
"@deepseek-ai/dsh-
|
|
123
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.
|
|
124
|
-
"@deepseek-ai/dsh-client-store": "^0.1.2-alpha.
|
|
125
|
-
"@deepseek-ai/dsh-file-reference": "^0.1.2-alpha.
|
|
126
|
-
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.
|
|
127
|
-
"@deepseek-ai/dsh-jobs": "^0.1.2-alpha.
|
|
128
|
-
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.
|
|
129
|
-
"@deepseek-ai/dsh-
|
|
130
|
-
"@deepseek-ai/dsh-
|
|
131
|
-
"@deepseek-ai/dsh-
|
|
132
|
-
"@deepseek-ai/dsh-session": "^0.1.2-alpha.
|
|
133
|
-
"@deepseek-ai/dsh-session-persistence": "^0.1.2-alpha.
|
|
134
|
-
"@deepseek-ai/dsh-session-
|
|
135
|
-
"@deepseek-ai/dsh-session-
|
|
136
|
-
"@deepseek-ai/dsh-
|
|
137
|
-
"@deepseek-ai/dsh-
|
|
138
|
-
"@deepseek-ai/dsh-
|
|
139
|
-
"@deepseek-ai/dsh-storage": "^0.1.2-alpha.
|
|
140
|
-
"@deepseek-ai/dsh-
|
|
141
|
-
"@deepseek-ai/dsh-
|
|
142
|
-
"@deepseek-ai/dsh-
|
|
143
|
-
"@deepseek-ai/dsh-
|
|
144
|
-
"@deepseek-ai/dsh-
|
|
145
|
-
"@deepseek-ai/dsh-
|
|
146
|
-
"@deepseek-ai/dsh-util-
|
|
147
|
-
"@deepseek-ai/dsh-
|
|
148
|
-
"@deepseek-ai/dsh-
|
|
118
|
+
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.3",
|
|
119
|
+
"@deepseek-ai/dsh-agent-default-model": "^0.1.2-alpha.3",
|
|
120
|
+
"@deepseek-ai/dsh-agent-presets": "^0.1.2-alpha.3",
|
|
121
|
+
"@deepseek-ai/dsh-api-gateway": "^0.1.2-alpha.3",
|
|
122
|
+
"@deepseek-ai/dsh-attachment": "^0.1.2-alpha.3",
|
|
123
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.3",
|
|
124
|
+
"@deepseek-ai/dsh-client-store": "^0.1.2-alpha.3",
|
|
125
|
+
"@deepseek-ai/dsh-file-reference": "^0.1.2-alpha.3",
|
|
126
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
|
|
127
|
+
"@deepseek-ai/dsh-jobs": "^0.1.2-alpha.3",
|
|
128
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.3",
|
|
129
|
+
"@deepseek-ai/dsh-scope": "^0.1.2-alpha.3",
|
|
130
|
+
"@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
|
|
131
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.3",
|
|
132
|
+
"@deepseek-ai/dsh-session-projection-cache": "^0.1.2-alpha.3",
|
|
133
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.2-alpha.3",
|
|
134
|
+
"@deepseek-ai/dsh-session-query": "^0.1.2-alpha.3",
|
|
135
|
+
"@deepseek-ai/dsh-session-title": "^0.1.2-alpha.3",
|
|
136
|
+
"@deepseek-ai/dsh-skill": "^0.1.2-alpha.3",
|
|
137
|
+
"@deepseek-ai/dsh-storage": "^0.1.2-alpha.3",
|
|
138
|
+
"@deepseek-ai/dsh-storage-domain": "^0.1.2-alpha.3",
|
|
139
|
+
"@deepseek-ai/dsh-storage-json": "^0.1.2-alpha.3",
|
|
140
|
+
"@deepseek-ai/dsh-subagent": "^0.1.2-alpha.3",
|
|
141
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.2-alpha.3",
|
|
142
|
+
"@deepseek-ai/dsh-typert-registry": "^0.1.2-alpha.3",
|
|
143
|
+
"@deepseek-ai/dsh-util-time": "^0.1.2-alpha.3",
|
|
144
|
+
"@deepseek-ai/dsh-permission-presets": "^0.1.2-alpha.3",
|
|
145
|
+
"@deepseek-ai/dsh-native-command": "^0.1.2-alpha.3",
|
|
146
|
+
"@deepseek-ai/dsh-util-workspace-path": "^0.1.2-alpha.3",
|
|
147
|
+
"@deepseek-ai/dsh-workspace": "^0.1.2-alpha.3",
|
|
148
|
+
"@deepseek-ai/dsh-util-crypto": "^0.1.2-alpha.3"
|
|
149
149
|
},
|
|
150
150
|
"scripts": {
|
|
151
151
|
"bundle": "tsdown",
|