@yadsh/dsh-draft-sessions 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ //#region src/shared/constants.ts
2
+ /** On-disk format version. Bump only with an explicit migration. */
3
+ const DRAFT_FILE_VERSION = 1;
4
+ /** Current draft record format. */
5
+ const DRAFT_SESSION_VERSION = 1;
6
+ /** Default maximum number of drafts in a workspace. */
7
+ const DEFAULT_MAX_DRAFTS_PER_WORKSPACE = 50;
8
+ /** Default maximum length of a title derived by consumers. */
9
+ const DEFAULT_TITLE_MAX_LENGTH = 80;
10
+ //#endregion
11
+ export { DRAFT_SESSION_VERSION as i, DEFAULT_TITLE_MAX_LENGTH as n, DRAFT_FILE_VERSION as r, DEFAULT_MAX_DRAFTS_PER_WORKSPACE as t };
package/lib/index.js ADDED
@@ -0,0 +1,292 @@
1
+ import { i as DRAFT_SESSION_VERSION, n as DEFAULT_TITLE_MAX_LENGTH, r as DRAFT_FILE_VERSION, t as DEFAULT_MAX_DRAFTS_PER_WORKSPACE } from "./constants-vAKitj5i.js";
2
+ import { t as draftFileSchema } from "./schema-BkmWuQBa.js";
3
+ import { deriveDraftTitle, displayDraftTitle } from "./shared/types.js";
4
+ import z from "@deepseek-ai/schemastery";
5
+ import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
6
+ import { randomUUID } from "node:crypto";
7
+ import { homedir } from "node:os";
8
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
9
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
10
+ import { ZodError } from "zod";
11
+ //#region src/host/errors.ts
12
+ /** Stable domain failure raised by DraftStore and exposed by the Host service. */
13
+ var DraftStoreError = class extends Error {
14
+ code;
15
+ constructor(message, code) {
16
+ super(message);
17
+ this.code = code;
18
+ this.name = "DraftStoreError";
19
+ }
20
+ };
21
+ //#endregion
22
+ //#region src/host/store.ts
23
+ function defaultStoragePath() {
24
+ const configuredHome = process.env.DSH_HOME?.trim();
25
+ const dshHome = configuredHome === void 0 || configuredHome === "" ? join(homedir(), ".dsh") : resolve(configuredHome);
26
+ return join(dshHome, "storages", "dsh-draft-sessions", "drafts.json");
27
+ }
28
+ function storagePath(value) {
29
+ if (value === void 0 || value.trim() === "") return defaultStoragePath();
30
+ return isAbsolute(value) ? value : resolve(value);
31
+ }
32
+ function positiveLimit(value) {
33
+ const resolvedValue = value ?? 50;
34
+ if (!Number.isSafeInteger(resolvedValue) || resolvedValue < 1) throw new DraftStoreError("maxDraftsPerWorkspace must be a positive safe integer", "DRAFT_INVALID_INPUT");
35
+ return resolvedValue;
36
+ }
37
+ function requiredText(value, field) {
38
+ if (typeof value !== "string" || value.trim() === "") throw new DraftStoreError(`${field} must be a non-empty string`, "DRAFT_INVALID_INPUT");
39
+ return value;
40
+ }
41
+ function optionalNonBlank(value, field) {
42
+ if (value === void 0) return void 0;
43
+ return requiredText(value, field);
44
+ }
45
+ function orderValue(value, field = "order") {
46
+ if (!Number.isSafeInteger(value) || value < 0) throw new DraftStoreError(`${field} must be a non-negative safe integer`, "DRAFT_INVALID_INPUT");
47
+ return value;
48
+ }
49
+ function revisionValue(value) {
50
+ if (!Number.isSafeInteger(value) || value < 1) throw new DraftStoreError("expectedRevision must be a positive safe integer", "DRAFT_INVALID_INPUT");
51
+ return value;
52
+ }
53
+ function cloneDraft(draft) {
54
+ return structuredClone(draft);
55
+ }
56
+ function sorted(drafts) {
57
+ return [...drafts].sort((left, right) => Number(right.pinned === true) - Number(left.pinned === true) || left.order - right.order || left.createdAt - right.createdAt || left.id.localeCompare(right.id));
58
+ }
59
+ /** Serialized, atomically persisted authority for unsent draft sessions. */
60
+ var DraftStore = class {
61
+ path;
62
+ maxDraftsPerWorkspace;
63
+ now;
64
+ id;
65
+ drafts;
66
+ queue = Promise.resolve();
67
+ constructor(options = {}) {
68
+ this.path = storagePath(options.storagePath);
69
+ this.maxDraftsPerWorkspace = positiveLimit(options.maxDraftsPerWorkspace);
70
+ this.now = options.now ?? Date.now;
71
+ this.id = options.id ?? randomUUID;
72
+ }
73
+ list(request = {}) {
74
+ return this.enqueue(async () => {
75
+ const drafts = await this.loaded();
76
+ return sorted(request.workspaceId === void 0 ? drafts : drafts.filter((draft) => draft.workspaceId === request.workspaceId)).map(cloneDraft);
77
+ });
78
+ }
79
+ create(request) {
80
+ return this.enqueue(async () => {
81
+ const current = await this.loaded();
82
+ const workspaceId = requiredText(request.workspaceId, "workspaceId");
83
+ const workspaceDrafts = current.filter((draft) => draft.workspaceId === workspaceId);
84
+ if (workspaceDrafts.length >= this.maxDraftsPerWorkspace) throw new DraftStoreError(`workspace already has ${this.maxDraftsPerWorkspace} drafts`, "DRAFT_LIMIT_REACHED");
85
+ const now = this.now();
86
+ const sessionId = request.sessionId ?? null;
87
+ if (sessionId !== null) requiredText(sessionId, "sessionId");
88
+ const draft = {
89
+ version: 1,
90
+ id: requiredText(this.id(), "generated id"),
91
+ sessionId,
92
+ workspaceId,
93
+ ...optionalNonBlank(request.workspacePath, "workspacePath") === void 0 ? {} : { workspacePath: request.workspacePath },
94
+ text: request.text ?? "",
95
+ ...optionalNonBlank(request.title, "title") === void 0 ? {} : { title: request.title },
96
+ createdAt: now,
97
+ updatedAt: now,
98
+ order: request.order === void 0 ? Math.max(-1, ...workspaceDrafts.map((item) => item.order)) + 1 : orderValue(request.order),
99
+ ...request.pinned === void 0 ? {} : { pinned: request.pinned },
100
+ ...optionalNonBlank(request.agentPresetId, "agentPresetId") === void 0 ? {} : { agentPresetId: request.agentPresetId },
101
+ state: sessionId === null ? "draft" : "ready",
102
+ revision: 1
103
+ };
104
+ await this.commit([...current, draft]);
105
+ return cloneDraft(draft);
106
+ });
107
+ }
108
+ update(request) {
109
+ return this.enqueue(async () => {
110
+ const current = await this.loaded();
111
+ const index = this.find(current, request.id);
112
+ const previous = current[index];
113
+ this.expectRevision(previous, request.expectedRevision);
114
+ const now = Math.max(this.now(), previous.updatedAt);
115
+ const mutable = {
116
+ ...previous,
117
+ ...request.text === void 0 ? {} : { text: request.text },
118
+ ...request.order === void 0 ? {} : { order: orderValue(request.order) },
119
+ ...request.pinned === void 0 ? {} : { pinned: request.pinned },
120
+ ...request.state === void 0 ? {} : { state: request.state },
121
+ updatedAt: now,
122
+ revision: previous.revision + 1
123
+ };
124
+ if (request.title === null) delete mutable.title;
125
+ else if (request.title !== void 0) mutable.title = requiredText(request.title, "title");
126
+ if (request.agentPresetId === null) delete mutable.agentPresetId;
127
+ else if (request.agentPresetId !== void 0) mutable.agentPresetId = requiredText(request.agentPresetId, "agentPresetId");
128
+ if (request.lastError === null) delete mutable.lastError;
129
+ else if (request.lastError !== void 0) mutable.lastError = requiredText(request.lastError, "lastError");
130
+ const changed = [...current];
131
+ changed[index] = mutable;
132
+ await this.commit(changed);
133
+ return cloneDraft(mutable);
134
+ });
135
+ }
136
+ delete(request) {
137
+ return this.enqueue(async () => {
138
+ const current = await this.loaded();
139
+ const index = current.findIndex((draft) => draft.id === request.id);
140
+ if (index < 0) return false;
141
+ const previous = current[index];
142
+ if (request.expectedRevision !== void 0) this.expectRevision(previous, request.expectedRevision);
143
+ const changed = current.filter((_, draftIndex) => draftIndex !== index);
144
+ await this.commit(changed);
145
+ return true;
146
+ });
147
+ }
148
+ rebind(request) {
149
+ return this.enqueue(async () => {
150
+ const current = await this.loaded();
151
+ const index = this.find(current, request.id);
152
+ const previous = current[index];
153
+ this.expectRevision(previous, request.expectedRevision);
154
+ if (request.sessionId !== null) requiredText(request.sessionId, "sessionId");
155
+ const rebound = {
156
+ ...previous,
157
+ sessionId: request.sessionId,
158
+ state: request.sessionId === null ? "draft" : "ready",
159
+ updatedAt: Math.max(this.now(), previous.updatedAt),
160
+ revision: previous.revision + 1
161
+ };
162
+ delete rebound.lastError;
163
+ const changed = [...current];
164
+ changed[index] = rebound;
165
+ await this.commit(changed);
166
+ return cloneDraft(rebound);
167
+ });
168
+ }
169
+ enqueue(operation) {
170
+ const result = this.queue.then(operation, operation);
171
+ this.queue = result.then(() => void 0, () => void 0);
172
+ return result;
173
+ }
174
+ async loaded() {
175
+ if (this.drafts !== void 0) return this.drafts;
176
+ let contents;
177
+ try {
178
+ contents = await readFile(this.path, "utf8");
179
+ } catch (error) {
180
+ if (error.code === "ENOENT") {
181
+ this.drafts = [];
182
+ return this.drafts;
183
+ }
184
+ throw error;
185
+ }
186
+ try {
187
+ const parsed = draftFileSchema.parse(JSON.parse(contents));
188
+ const seen = /* @__PURE__ */ new Set();
189
+ for (const draft of parsed.drafts) {
190
+ if (seen.has(draft.id)) throw new DraftStoreError(`storage contains duplicate draft id ${JSON.stringify(draft.id)}`, "DRAFT_STORAGE_INVALID");
191
+ seen.add(draft.id);
192
+ }
193
+ this.drafts = parsed.drafts.map(cloneDraft);
194
+ return this.drafts;
195
+ } catch (error) {
196
+ if (error instanceof DraftStoreError) throw error;
197
+ throw new DraftStoreError(`invalid draft storage: ${error instanceof ZodError ? error.issues.map((issue) => issue.message).join("; ") : error instanceof Error ? error.message : String(error)}`, "DRAFT_STORAGE_INVALID");
198
+ }
199
+ }
200
+ find(drafts, id) {
201
+ requiredText(id, "id");
202
+ const index = drafts.findIndex((draft) => draft.id === id);
203
+ if (index < 0) throw new DraftStoreError(`draft ${JSON.stringify(id)} was not found`, "DRAFT_NOT_FOUND");
204
+ return index;
205
+ }
206
+ expectRevision(draft, expectedRevision) {
207
+ revisionValue(expectedRevision);
208
+ if (draft.revision !== expectedRevision) throw new DraftStoreError(`draft ${JSON.stringify(draft.id)} is at revision ${draft.revision}, not ${expectedRevision}`, "DRAFT_STALE_REVISION");
209
+ }
210
+ async commit(drafts) {
211
+ const document = {
212
+ version: 1,
213
+ drafts
214
+ };
215
+ const directory = dirname(this.path);
216
+ await mkdir(directory, { recursive: true });
217
+ const temporary = join(directory, `.${basename(this.path)}.${process.pid}.${randomUUID()}.tmp`);
218
+ try {
219
+ await writeFile(temporary, `${JSON.stringify(document, null, 2)}\n`, {
220
+ encoding: "utf8",
221
+ flag: "wx"
222
+ });
223
+ await rename(temporary, this.path);
224
+ } finally {
225
+ await unlink(temporary).catch((error) => {
226
+ if (error.code !== "ENOENT") throw error;
227
+ });
228
+ }
229
+ this.drafts = drafts;
230
+ }
231
+ };
232
+ //#endregion
233
+ //#region src/index.ts
234
+ /** Host service and Typert Remote boundary for persistent draft records. */
235
+ var DraftSessionsService = class extends TypertRemoteService {
236
+ static Config = z.object({
237
+ storagePath: z.string().default(""),
238
+ maxDraftsPerWorkspace: z.number().default(50)
239
+ });
240
+ store;
241
+ constructor(ctx, config = {}) {
242
+ super(ctx, "draftSessions");
243
+ this.store = new DraftStore({
244
+ ...config.storagePath === void 0 || config.storagePath.trim() === "" ? {} : { storagePath: config.storagePath },
245
+ maxDraftsPerWorkspace: config.maxDraftsPerWorkspace ?? 50
246
+ });
247
+ }
248
+ list(request) {
249
+ return this.store.list(request);
250
+ }
251
+ create(request) {
252
+ return this.store.create(request);
253
+ }
254
+ update(request) {
255
+ return this.store.update(request);
256
+ }
257
+ async delete(request) {
258
+ return { deleted: await this.store.delete(request) };
259
+ }
260
+ rebind(request) {
261
+ return this.store.rebind(request);
262
+ }
263
+ };
264
+ /**
265
+ * Apply the standard Remote decorator without shipping decorator syntax.
266
+ *
267
+ * Out-of-tree tsdown builds currently preserve standard decorators even when
268
+ * targeting Node 22. Invoking the public decorator protocol here records the
269
+ * same class markers while keeping the published JavaScript executable.
270
+ */
271
+ function registerRemoteMethod(method) {
272
+ const initializers = [];
273
+ Remote(DraftSessionsService.prototype[method], {
274
+ name: method,
275
+ private: false,
276
+ static: false,
277
+ addInitializer(initializer) {
278
+ initializers.push(initializer);
279
+ }
280
+ });
281
+ const markerReceiver = Object.create(DraftSessionsService.prototype);
282
+ for (const initializer of initializers) initializer.call(markerReceiver);
283
+ }
284
+ for (const method of [
285
+ "list",
286
+ "create",
287
+ "update",
288
+ "delete",
289
+ "rebind"
290
+ ]) registerRemoteMethod(method);
291
+ //#endregion
292
+ export { DEFAULT_MAX_DRAFTS_PER_WORKSPACE, DEFAULT_TITLE_MAX_LENGTH, DRAFT_FILE_VERSION, DRAFT_SESSION_VERSION, DraftSessionsService, DraftSessionsService as default, DraftStore, DraftStoreError, deriveDraftTitle, displayDraftTitle };
package/lib/remote.js ADDED
@@ -0,0 +1,71 @@
1
+ import { n as draftSessionSchema, r as draftStateSchema } from "./schema-BkmWuQBa.js";
2
+ import { z } from "zod";
3
+ //#region src/remote.ts
4
+ const listRequestSchema = z.strictObject({ workspaceId: z.string().min(1).optional() });
5
+ const createRequestSchema = z.strictObject({
6
+ workspaceId: z.string().min(1),
7
+ sessionId: z.string().min(1).nullable().optional(),
8
+ workspacePath: z.string().min(1).optional(),
9
+ text: z.string().optional(),
10
+ title: z.string().min(1).optional(),
11
+ order: z.number().int().nonnegative().optional(),
12
+ pinned: z.boolean().optional(),
13
+ agentPresetId: z.string().min(1).optional()
14
+ });
15
+ const updateRequestSchema = z.strictObject({
16
+ id: z.string().min(1),
17
+ expectedRevision: z.number().int().positive(),
18
+ text: z.string().optional(),
19
+ title: z.string().min(1).nullable().optional(),
20
+ order: z.number().int().nonnegative().optional(),
21
+ pinned: z.boolean().optional(),
22
+ agentPresetId: z.string().min(1).nullable().optional(),
23
+ state: draftStateSchema.optional(),
24
+ lastError: z.string().min(1).nullable().optional()
25
+ });
26
+ const deleteRequestSchema = z.strictObject({
27
+ id: z.string().min(1),
28
+ expectedRevision: z.number().int().positive().optional()
29
+ });
30
+ const deleteResultSchema = z.strictObject({ deleted: z.boolean() });
31
+ const rebindRequestSchema = z.strictObject({
32
+ id: z.string().min(1),
33
+ expectedRevision: z.number().int().positive(),
34
+ sessionId: z.string().min(1).nullable()
35
+ });
36
+ function descriptor(method, requestType, requestSchema, resultType, resultSchema) {
37
+ return {
38
+ id: `dsh-draft-sessions#draftSessions/${method}`,
39
+ service: "draftSessions",
40
+ namespace: "draftSessions",
41
+ method,
42
+ invocation: { kind: "direct" },
43
+ parameters: [{
44
+ name: "request",
45
+ wire: "request",
46
+ source: "json",
47
+ codec: {
48
+ mode: "strict",
49
+ typeSymbol: requestType,
50
+ schema: requestSchema
51
+ }
52
+ }],
53
+ result: {
54
+ mode: "strict",
55
+ typeSymbol: resultType,
56
+ schema: resultSchema
57
+ }
58
+ };
59
+ }
60
+ const draftSessionsRemote = {
61
+ package: "dsh-draft-sessions",
62
+ descriptors: [
63
+ descriptor("list", "dsh-draft-sessions/types#ListDraftsRequest", listRequestSchema, "dsh-draft-sessions/types#DraftSession[]", z.array(draftSessionSchema)),
64
+ descriptor("create", "dsh-draft-sessions/types#CreateDraftRequest", createRequestSchema, "dsh-draft-sessions/types#DraftSession", draftSessionSchema),
65
+ descriptor("update", "dsh-draft-sessions/types#UpdateDraftRequest", updateRequestSchema, "dsh-draft-sessions/types#DraftSession", draftSessionSchema),
66
+ descriptor("delete", "dsh-draft-sessions/types#DeleteDraftRequest", deleteRequestSchema, "dsh-draft-sessions/types#DeleteDraftResult", deleteResultSchema),
67
+ descriptor("rebind", "dsh-draft-sessions/types#RebindDraftRequest", rebindRequestSchema, "dsh-draft-sessions/types#DraftSession", draftSessionSchema)
68
+ ]
69
+ };
70
+ //#endregion
71
+ export { draftSessionsRemote as default };
@@ -0,0 +1,33 @@
1
+ import "./constants-vAKitj5i.js";
2
+ import { z } from "zod";
3
+ //#region src/host/schema.ts
4
+ const draftStateSchema = z.union([
5
+ z.literal("draft"),
6
+ z.literal("materializing"),
7
+ z.literal("ready"),
8
+ z.literal("converting"),
9
+ z.literal("error")
10
+ ]);
11
+ const draftSessionSchema = z.strictObject({
12
+ version: z.literal(1),
13
+ id: z.string().min(1),
14
+ sessionId: z.string().min(1).nullable(),
15
+ workspaceId: z.string().min(1),
16
+ workspacePath: z.string().min(1).optional(),
17
+ text: z.string(),
18
+ title: z.string().min(1).optional(),
19
+ createdAt: z.number().int().nonnegative(),
20
+ updatedAt: z.number().int().nonnegative(),
21
+ order: z.number().int().nonnegative(),
22
+ pinned: z.boolean().optional(),
23
+ agentPresetId: z.string().min(1).optional(),
24
+ state: draftStateSchema,
25
+ lastError: z.string().min(1).optional(),
26
+ revision: z.number().int().positive()
27
+ });
28
+ const draftFileSchema = z.strictObject({
29
+ version: z.literal(1),
30
+ drafts: z.array(draftSessionSchema)
31
+ });
32
+ //#endregion
33
+ export { draftSessionSchema as n, draftStateSchema as r, draftFileSchema as t };
@@ -0,0 +1,13 @@
1
+ import "../constants-vAKitj5i.js";
2
+ //#region src/shared/types.ts
3
+ /** First non-empty line, compacted for a session-like sidebar row. */
4
+ function deriveDraftTitle(text, maxLength = 80) {
5
+ if (!Number.isSafeInteger(maxLength) || maxLength < 1) throw new RangeError("maxLength must be a positive safe integer");
6
+ return (text.trim().split(/\r?\n/u, 1)[0] ?? "").slice(0, maxLength);
7
+ }
8
+ /** Explicit title when present, otherwise a stable first-line projection. */
9
+ function displayDraftTitle(draft, maxLength = 80) {
10
+ return draft.title ?? deriveDraftTitle(draft.text, maxLength);
11
+ }
12
+ //#endregion
13
+ export { deriveDraftTitle, displayDraftTitle };
@@ -0,0 +1,46 @@
1
+ import { Service, type Context } from "@deepseek-ai/cordis";
2
+ import type { ISessions } from "@deepseek-ai/dsh-client-runtime/client";
3
+ import type { IConversation } from "@deepseek-ai/dsh-client-ui-conversation/client";
4
+ import type { RemoteFailure, TypertRemoteNamespace } from "@deepseek-ai/dsh-typert-protocol";
5
+ import type { DraftSession } from "../shared/types.js";
6
+ import type { DraftSessionLifecycle } from "./lifecycle.js";
7
+ import type { DraftSidebarSource } from "./sidebar.js";
8
+ type DraftSessionsRemote = TypertRemoteNamespace<"draftSessions">;
9
+ export interface DraftComposerBridgeOptions {
10
+ readonly lifecycle: Pick<DraftSessionLifecycle, "ensureShell" | "onBeforeFinalize">;
11
+ readonly drafts: DraftSessionsRemote;
12
+ readonly sessions: Pick<ISessions, "open" | "scope">;
13
+ readonly conversation: Pick<IConversation, "input">;
14
+ readonly sidebar?: Pick<DraftSidebarSource, "accept">;
15
+ readonly debounceMs?: number;
16
+ }
17
+ export declare class DraftAutosaveError extends Error {
18
+ readonly code: string;
19
+ readonly draft: DraftSession;
20
+ readonly localText: string;
21
+ constructor(failure: Pick<RemoteFailure, "code" | "message">, draft: DraftSession, localText: string);
22
+ }
23
+ /** Official InputHub bridge plus serialized optimistic Host autosave. */
24
+ export declare class DraftComposerBridge extends Service {
25
+ private readonly lifecycle;
26
+ private readonly drafts;
27
+ private readonly sessions;
28
+ private readonly conversation;
29
+ private readonly debounceMs;
30
+ private readonly sidebar;
31
+ private active;
32
+ private saveQueue;
33
+ constructor(ctx: Context, options?: DraftComposerBridgeOptions);
34
+ /** Flush the previous draft, open this Session, and restore exact text. */
35
+ open(draft: DraftSession): Promise<DraftSession>;
36
+ /** Persist every pending edit before navigation continues. */
37
+ flush(): Promise<DraftSession | undefined>;
38
+ /** Flush and stop mirroring the current composer. */
39
+ close(): Promise<DraftSession | undefined>;
40
+ private inputChanged;
41
+ private drain;
42
+ private clearTimer;
43
+ private detach;
44
+ }
45
+ export {};
46
+ //# sourceMappingURL=composer.d.ts.map
@@ -0,0 +1,22 @@
1
+ import { type DraftSession } from "../shared/types.js";
2
+ export interface DraftDropTarget {
3
+ readonly workspaceId: string;
4
+ readonly beforeDraftId?: string;
5
+ }
6
+ export interface DraftSidebarViewProps {
7
+ readonly surface?: "inline" | "tab" | "popover";
8
+ readonly drafts: readonly DraftSession[];
9
+ readonly currentSessionId?: string;
10
+ readonly workspaceNames?: Readonly<Record<string, string>>;
11
+ readonly onCreate: () => Promise<void>;
12
+ readonly onOpen: (draft: DraftSession) => void;
13
+ readonly onRename: (draft: DraftSession, title: string) => Promise<void>;
14
+ readonly onDuplicate: (draft: DraftSession) => Promise<void>;
15
+ readonly onDelete: (draft: DraftSession) => Promise<void>;
16
+ readonly onReorder: (workspaceId: string, draftId: string, beforeDraftId?: string) => Promise<void>;
17
+ }
18
+ export declare function resolveDraftDropTarget(drafts: readonly DraftSession[], sourceId: string, targetId: string, half: "before" | "after"): DraftDropTarget | undefined;
19
+ export declare function DraftSidebarView({ surface, drafts, currentSessionId, workspaceNames, onCreate, onOpen, onRename, onDuplicate, onDelete, onReorder, }: DraftSidebarViewProps): import("react").FunctionComponentElement<{
20
+ children?: import("react").ReactNode | undefined;
21
+ }>;
22
+ //# sourceMappingURL=draft-sidebar-view.d.ts.map
@@ -0,0 +1,32 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import type { ConnectionHandle, IApiClient } from "@deepseek-ai/dsh-client-connection/client";
3
+ import type { ISessions, IWorkspaces } from "@deepseek-ai/dsh-client-runtime/client";
4
+ import type { IConversation } from "@deepseek-ai/dsh-client-ui-conversation/client";
5
+ import { DraftComposerBridge } from "./composer.js";
6
+ import { DraftSessionLifecycle } from "./lifecycle.js";
7
+ import { DraftSidebarSource } from "./sidebar.js";
8
+ import { DraftShortcutController } from "./shortcut.js";
9
+ export type * from "../shared/types.js";
10
+ export * from "./composer.js";
11
+ export * from "./lifecycle.js";
12
+ export * from "./sidebar.js";
13
+ export * from "./shortcut.js";
14
+ export * from "./workspace-contribution.js";
15
+ declare module "@deepseek-ai/cordis" {
16
+ interface Context {
17
+ connection: ConnectionHandle & {
18
+ readonly api: IApiClient;
19
+ };
20
+ sessions: ISessions;
21
+ workspaces: IWorkspaces;
22
+ conversation: IConversation;
23
+ draftSessionLifecycle: DraftSessionLifecycle;
24
+ draftComposerBridge: DraftComposerBridge;
25
+ draftShortcutController: DraftShortcutController;
26
+ draftSidebarSource: DraftSidebarSource;
27
+ }
28
+ }
29
+ export declare const inject: string[];
30
+ /** Mount the strict Remote namespace and its blank-Session lifecycle bridge. */
31
+ export declare function apply(ctx: Context): Promise<() => Promise<void>>;
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,69 @@
1
+ import { Service, type Context } from "@deepseek-ai/cordis";
2
+ import type { IApiClient, RpcMessage } from "@deepseek-ai/dsh-client-connection/client";
3
+ import type { RemoteFailure, TypertRemoteNamespace } from "@deepseek-ai/dsh-typert-protocol";
4
+ import type { CreateDraftRequest, DraftSession } from "../shared/types.js";
5
+ import type { DraftSidebarSource } from "./sidebar.js";
6
+ type DraftSessionsRemote = TypertRemoteNamespace<"draftSessions">;
7
+ type SessionsApi = Pick<IApiClient["sessions"], "create" | "list">;
8
+ export interface ApiEnvelopeSource {
9
+ subscribeEnvelopes(listener: (batch: readonly RpcMessage[]) => void): () => void;
10
+ }
11
+ export type CreateManagedDraftRequest = Omit<CreateDraftRequest, "sessionId">;
12
+ export type DraftLifecycleStage = "draft-create" | "draft-list" | "draft-update" | "draft-delete" | "session-list" | "session-create" | "draft-rebind";
13
+ /** A lifecycle failure never implies that the durable DraftRecord was removed. */
14
+ export declare class DraftLifecycleError extends Error {
15
+ readonly stage: DraftLifecycleStage;
16
+ readonly code: string;
17
+ readonly draft: DraftSession | undefined;
18
+ readonly sessionId: string | undefined;
19
+ constructor(stage: DraftLifecycleStage, failure: Pick<RemoteFailure, "code" | "message">, options?: {
20
+ readonly draft?: DraftSession;
21
+ readonly sessionId?: string;
22
+ readonly cause?: unknown;
23
+ });
24
+ }
25
+ export interface DraftSessionLifecycleOptions {
26
+ readonly drafts: DraftSessionsRemote;
27
+ readonly sessions: SessionsApi;
28
+ readonly envelopes?: ApiEnvelopeSource;
29
+ readonly sidebar?: Pick<DraftSidebarSource, "accept" | "remove">;
30
+ }
31
+ export type BeforeDraftFinalizeListener = (sessionId: string) => void | Promise<void>;
32
+ export declare function envelopeSource(api: IApiClient): ApiEnvelopeSource | undefined;
33
+ /**
34
+ * Client-side bridge between durable DraftRecords and real blank DSH Sessions.
35
+ *
36
+ * The DraftRecord is created first with no Session id. A Session id enters the
37
+ * durable record only after `sessions.create` has returned a successful result.
38
+ */
39
+ export declare class DraftSessionLifecycle extends Service {
40
+ private readonly drafts;
41
+ private readonly sessions;
42
+ private readonly sidebar;
43
+ private readonly pendingPrompts;
44
+ private readonly beforeFinalizeListeners;
45
+ private observationQueue;
46
+ constructor(ctx: Context, options?: DraftSessionLifecycleOptions);
47
+ /** Create a durable draft and give it a distinct blank Session shell. */
48
+ create(request: CreateManagedDraftRequest): Promise<DraftSession>;
49
+ /** Return the draft unchanged when its Session exists, otherwise rebind it. */
50
+ ensureShell(draft: DraftSession): Promise<DraftSession>;
51
+ /** Recover every missing Session shell in one Workspace from one list cut. */
52
+ reconcileWorkspace(workspaceId: string): Promise<DraftSession[]>;
53
+ /** Run cleanup hooks before an accepted Session's durable draft is removed. */
54
+ onBeforeFinalize(listener: BeforeDraftFinalizeListener): () => void;
55
+ /**
56
+ * Finalize drafts for an accepted prompt only after DSH reports the Session
57
+ * as nonblank. Returns false while the transition is not yet observable.
58
+ */
59
+ finalizeAcceptedSession(sessionId: string): Promise<boolean>;
60
+ private materialize;
61
+ private markFailed;
62
+ private deleteDraft;
63
+ private observeEnvelopes;
64
+ private enqueueObservation;
65
+ private remoteValue;
66
+ private apiError;
67
+ }
68
+ export {};
69
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1,40 @@
1
+ import { Service, type Context } from "@deepseek-ai/cordis";
2
+ import type { ISessions, IWorkspaces, SessionListState, WorkspaceListState } from "@deepseek-ai/dsh-client-runtime/client";
3
+ import type { DraftSession } from "../shared/types.js";
4
+ import type { DraftComposerBridge } from "./composer.js";
5
+ import type { DraftSessionLifecycle } from "./lifecycle.js";
6
+ interface ShortcutEvent {
7
+ readonly key: string;
8
+ readonly ctrlKey: boolean;
9
+ readonly metaKey: boolean;
10
+ readonly shiftKey: boolean;
11
+ readonly altKey: boolean;
12
+ readonly repeat: boolean;
13
+ preventDefault(): void;
14
+ }
15
+ interface ShortcutSource {
16
+ subscribe(listener: (event: ShortcutEvent) => void): () => void;
17
+ }
18
+ export interface DraftShortcutControllerOptions {
19
+ readonly lifecycle: Pick<DraftSessionLifecycle, "create">;
20
+ readonly composer: Pick<DraftComposerBridge, "flush" | "open">;
21
+ readonly sessions: Pick<ISessions, "list">;
22
+ readonly workspaces: Pick<IWorkspaces, "list">;
23
+ readonly shortcuts?: ShortcutSource;
24
+ }
25
+ /** Resolve the same current/recent Workspace axis used by New Session. */
26
+ export declare function resolveDraftWorkspace(sessions: Pick<SessionListState, "current">, workspaces: Pick<WorkspaceListState, "items" | "recentWorkspaceId">): string | undefined;
27
+ /** Global Ctrl/Cmd+Shift+N action for a distinct draft Session. */
28
+ export declare class DraftShortcutController extends Service {
29
+ private readonly lifecycle;
30
+ private readonly composer;
31
+ private readonly sessions;
32
+ private readonly workspaces;
33
+ private creating;
34
+ constructor(ctx: Context, options?: DraftShortcutControllerOptions);
35
+ /** Flush the current draft, create another, and open its composer. */
36
+ create(workspaceId?: string): Promise<DraftSession>;
37
+ private onShortcut;
38
+ }
39
+ export {};
40
+ //# sourceMappingURL=shortcut.d.ts.map