@jaychang1989/dsh-webchat 0.3.0 → 0.4.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.
Files changed (44) hide show
  1. package/NOTICE +10 -3
  2. package/README.md +44 -39
  3. package/README.zh.md +43 -43
  4. package/cordis.patch.yml +8 -7
  5. package/lib/client.js +203 -1703
  6. package/lib/index.js +246 -3884
  7. package/package.json +3 -35
  8. package/lib/client.js.map +0 -1
  9. package/lib/types/client/api.d.ts +0 -74
  10. package/lib/types/client/controller.d.ts +0 -19
  11. package/lib/types/client/index.d.ts +0 -30
  12. package/lib/types/client/locales.d.ts +0 -169
  13. package/lib/types/client/mount.d.ts +0 -23
  14. package/lib/types/client/panel/Markdown.d.ts +0 -15
  15. package/lib/types/client/panel/WebChatPanel.d.ts +0 -28
  16. package/lib/types/client/sidebar-entry-core.d.ts +0 -60
  17. package/lib/types/client/sidebar-entry.d.ts +0 -22
  18. package/lib/types/engine/engine.d.ts +0 -247
  19. package/lib/types/engine/html-md.d.ts +0 -23
  20. package/lib/types/index.d.ts +0 -63
  21. package/lib/types/protocol.d.ts +0 -107
  22. package/lib/types/routes.d.ts +0 -22
  23. package/lib/types/store.d.ts +0 -69
  24. package/lib/types/tools.d.ts +0 -27
  25. package/lib/types/transfer.d.ts +0 -138
  26. package/src/client/api.ts +0 -90
  27. package/src/client/controller.ts +0 -43
  28. package/src/client/css-modules.d.ts +0 -5
  29. package/src/client/index.ts +0 -75
  30. package/src/client/locales.ts +0 -172
  31. package/src/client/mount.tsx +0 -125
  32. package/src/client/panel/Markdown.tsx +0 -251
  33. package/src/client/panel/WebChatPanel.tsx +0 -760
  34. package/src/client/panel/panel.module.css +0 -936
  35. package/src/client/sidebar-entry-core.ts +0 -207
  36. package/src/client/sidebar-entry.ts +0 -49
  37. package/src/engine/engine.ts +0 -1385
  38. package/src/engine/html-md.ts +0 -126
  39. package/src/index.ts +0 -212
  40. package/src/protocol.ts +0 -116
  41. package/src/routes.ts +0 -307
  42. package/src/store.ts +0 -217
  43. package/src/tools.ts +0 -257
  44. package/src/transfer.ts +0 -602
@@ -1,107 +0,0 @@
1
- /**
2
- * Shared wire surface of the dsh-webchat plugin: API paths, transcript data
3
- * model, and engine status views. Imported by both the host routes and the
4
- * browser panel (the client bundle inlines it — no shared runtime identity).
5
- */
6
- /** One transcript message (markdown text). */
7
- export interface WebChatMessage {
8
- /** Local stable id. */
9
- readonly id: string;
10
- readonly role: 'user' | 'assistant';
11
- /** Markdown body. */
12
- readonly content: string;
13
- /** Unix epoch ms. */
14
- readonly ts: number;
15
- /** True while the engine is still streaming this message. */
16
- readonly streaming?: boolean;
17
- /** Set when the exchange failed (content may be partial). */
18
- readonly error?: string;
19
- /** Local absolute paths of images attached to a user message. */
20
- readonly attachments?: string[];
21
- }
22
- /** One web chat session transcript (persisted locally). */
23
- export interface WebChatTranscript {
24
- readonly id: string;
25
- title: string;
26
- readonly createdAt: number;
27
- updatedAt: number;
28
- /** Model the chat currently runs on the web (deepseek-chat / deepseek-reasoner). */
29
- model: string;
30
- readonly messages: WebChatMessage[];
31
- /** True while an assistant reply is streaming. */
32
- streaming: boolean;
33
- }
34
- /**
35
- * Structured engine error codes so callers (panel / agent tools) can act on a
36
- * failure instead of only printing it: NEED_LOGIN → prompt the login window;
37
- * PAGE_CHANGED → the DeepSeek page/stream protocol moved and the plugin likely
38
- * needs an upgrade; TIMEOUT → generation exceeded the reply window; NETWORK →
39
- * browser launch / HTTP / connectivity failure.
40
- */
41
- export type WebChatErrorCode = 'NEED_LOGIN' | 'PAGE_CHANGED' | 'TIMEOUT' | 'NETWORK';
42
- /** Coarse engine state for the panel. */
43
- export type EngineState = 'stopped' | 'launching' | 'ready' | 'error';
44
- /** Snapshot the panel polls. */
45
- export interface WebChatState {
46
- readonly engine: EngineState;
47
- readonly engineError?: string;
48
- /** null = unknown yet; false = browser up but not logged in; true = chat page ready. */
49
- readonly loggedIn: boolean | null;
50
- readonly pageUrl?: string;
51
- /** Deep-think (R1) toggle state read from the page. */
52
- readonly deepThink: boolean;
53
- /** Internet-search toggle state read from the page. */
54
- readonly search: boolean;
55
- readonly activeChatId?: string;
56
- readonly chats: WebChatTranscript[];
57
- /** True while a message is being sent / replied (serialized engine busy). */
58
- readonly busy: boolean;
59
- /** Last send error, transient for the panel to display. */
60
- readonly lastError?: string;
61
- /** Structured code for the last error (so the panel can show a targeted action). */
62
- readonly lastErrorCode?: WebChatErrorCode;
63
- }
64
- /** Engine-level operation results surfaced to agent tools. */
65
- export interface SendResult {
66
- readonly ok: boolean;
67
- readonly chatId?: string;
68
- readonly reply?: string;
69
- readonly error?: string;
70
- /** Structured error code (when ok is false) for actionable handling. */
71
- readonly code?: WebChatErrorCode;
72
- }
73
- /** Result of transferring a transcript into harness mode. */
74
- export interface TransferResult {
75
- readonly ok: boolean;
76
- readonly sessionId?: string;
77
- readonly filePath?: string;
78
- /** True when the session was attached to a workspace (grouped); false = ungrouped. */
79
- readonly attached?: boolean;
80
- /** Workspace id the session landed in, when attached. */
81
- readonly workspaceId?: string;
82
- readonly error?: string;
83
- }
84
- /**
85
- * How a transfer seeds the new harness session: 'distill' condenses the web
86
- * conversation into an executable task brief via the harness LLM; 'raw'
87
- * replays the full transcript verbatim. The web panel lets the user choose.
88
- */
89
- export type TransferMode = 'distill' | 'raw';
90
- /** API path constants shared by host routes and the browser panel. */
91
- export declare const WEBChat_API: {
92
- readonly state: "/api/dsh-webchat/state";
93
- readonly openLogin: "/api/dsh-webchat/open-login";
94
- readonly closeBrowser: "/api/dsh-webchat/close-browser";
95
- readonly newChat: "/api/dsh-webchat/new-chat";
96
- readonly send: "/api/dsh-webchat/send";
97
- readonly stop: "/api/dsh-webchat/stop";
98
- readonly deepThink: "/api/dsh-webchat/deep-think";
99
- readonly search: "/api/dsh-webchat/search";
100
- readonly transfer: "/api/dsh-webchat/transfer";
101
- readonly exportFile: "/api/dsh-webchat/export";
102
- readonly renameChat: "/api/dsh-webchat/rename";
103
- readonly deleteChat: "/api/dsh-webchat/delete";
104
- readonly clearChats: "/api/dsh-webchat/clear";
105
- readonly webChats: "/api/dsh-webchat/web-chats";
106
- readonly recover: "/api/dsh-webchat/recover";
107
- };
@@ -1,22 +0,0 @@
1
- /**
2
- * The /api/dsh-webchat route family: engine state, browser login control,
3
- * chat operations (new chat / send / stop / switch model), transcript
4
- * export, and the harness transfer that seeds a new session with a web
5
- * transcript. Every route carries the same loopback-only trust fence the
6
- * dsh-ssh plugin uses — these endpoints drive a browser and create sessions,
7
- * so LAN-exposed deployments must not serve them.
8
- */
9
- import type { Context } from '@deepseek-ai/cordis';
10
- import type { WebRoute } from '@deepseek-ai/dsh-host-webserver';
11
- import type { DeepSeekWebEngine } from './engine/engine.ts';
12
- import type { TranscriptStore } from './store.ts';
13
- import type { DistillConfig } from './transfer.ts';
14
- /** Route family dependencies. */
15
- export interface WebChatRoutesDeps {
16
- ctx: Context;
17
- engine: DeepSeekWebEngine;
18
- store: TranscriptStore;
19
- distill: DistillConfig;
20
- }
21
- /** Build every /api/dsh-webchat route. */
22
- export declare function makeRoutes(deps: WebChatRoutesDeps): WebRoute[];
@@ -1,69 +0,0 @@
1
- /**
2
- * Local transcript store. Chats are persisted as one JSON file under the
3
- * plugin data dir (~/.dsh/dsh-webchat/transcripts.json by default) so web
4
- * conversations survive restarts and can be transferred into harness mode
5
- * at any time. Atomic writes (tmp + rename) keep a crash from corrupting
6
- * history.
7
- */
8
- import type { WebChatMessage, WebChatTranscript } from './protocol.ts';
9
- /** Default plugin data directory (tests inject a sandbox root). */
10
- export declare function defaultDataDir(): string;
11
- export interface TranscriptStoreOptions {
12
- /** Root data dir; the transcripts file lives directly under it. */
13
- dataDir?: string;
14
- }
15
- /**
16
- * JSON-file transcript store. All mutations are synchronous and persisted
17
- * immediately (chats are small); the engine and routes use this single
18
- * instance so GUI and agent tools always see the same history.
19
- */
20
- export declare class TranscriptStore {
21
- readonly dataDir: string;
22
- private readonly file;
23
- private chats;
24
- private activeChatId;
25
- constructor(options?: TranscriptStoreOptions);
26
- private read;
27
- private persist;
28
- /** Create a fresh chat and make it active. */
29
- createChat(model: string): WebChatTranscript;
30
- /** All chats, newest first. */
31
- list(): WebChatTranscript[];
32
- /** Local transcript titles (for matching against the web sidebar). */
33
- titles(): string[];
34
- /**
35
- * Import a recovered web conversation as a local transcript. Dedups by exact
36
- * title (web conversations with the same title map to the existing chat), so
37
- * re-syncing is idempotent. Returns the chat plus whether it was newly created.
38
- */
39
- importTranscript(input: {
40
- title: string;
41
- model: string;
42
- messages: WebChatMessage[];
43
- }): {
44
- chat: WebChatTranscript;
45
- created: boolean;
46
- };
47
- /** The active chat, or undefined when none exists yet. */
48
- activeChat(): WebChatTranscript | undefined;
49
- /** Read one chat by id. */
50
- getChat(id: string): WebChatTranscript | undefined;
51
- /** Pick the active chat, creating one if none exists. */
52
- ensureActiveChat(model: string): WebChatTranscript;
53
- /** Set which chat is active. */
54
- setActiveChat(id: string): boolean;
55
- /** Mutate the active (or named) chat and persist. */
56
- private update;
57
- /** Append a message to a chat. */
58
- appendMessage(id: string, message: WebChatMessage): WebChatTranscript | undefined;
59
- /** Replace (or insert) one message by id — used for streaming updates. */
60
- upsertMessage(id: string, message: WebChatMessage): WebChatTranscript | undefined;
61
- /** Mark the chat's streaming flag (assistant reply started/stopped). */
62
- setStreaming(id: string, streaming: boolean, model?: string): WebChatTranscript | undefined;
63
- /** Rename a chat (used to pin a meaningful title after the first exchange). */
64
- renameChat(id: string, title: string): WebChatTranscript | undefined;
65
- /** Delete one chat; a deleted active chat falls back to the newest remaining. */
66
- deleteChat(id: string): boolean;
67
- /** Delete every chat; returns the number removed. */
68
- clearAllChats(): number;
69
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Agent tools: the DSH-native counterpart of the web-chat panel. The harness
3
- * agent can chat through the DeepSeek web session (webchat_send), inspect
4
- * stored transcripts (webchat_status / webchat_import), and hand a web
5
- * conversation into a new harness session (webchat_transfer) — mirroring how
6
- * Codex's chatgpt mode lets the agent itself use the web subscription.
7
- */
8
- import type { Context } from '@deepseek-ai/cordis';
9
- import type { DeepSeekWebEngine } from './engine/engine.ts';
10
- import type { TranscriptStore } from './store.ts';
11
- import type { DistillConfig } from './transfer.ts';
12
- /** Minimal workspace projection surfaced to the agent (id/path/title only). */
13
- export interface WorkspaceRef {
14
- id: string;
15
- path: string;
16
- title: string;
17
- }
18
- /** The engine-status tool. */
19
- export declare function webChatStatusTool(engine: DeepSeekWebEngine, store: TranscriptStore, listWorkspaces?: () => WorkspaceRef[] | undefined): import("@deepseek-ai/dsh-tools").ToolDefinition;
20
- /** The send-via-web tool. */
21
- export declare function webChatSendTool(engine: DeepSeekWebEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
22
- /** The web-conversation recover tool (sync web sidebar → local store). */
23
- export declare function webChatRecoverTool(engine: DeepSeekWebEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
24
- /** The transcript import tool. */
25
- export declare function webChatImportTool(store: TranscriptStore): import("@deepseek-ai/dsh-tools").ToolDefinition;
26
- /** The transfer tool (closes over the host context so it can create sessions). */
27
- export declare function webChatTransferTool(hostCtx: Context, store: TranscriptStore, distill: DistillConfig): import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -1,138 +0,0 @@
1
- /**
2
- * Harness-mode transfer: turn a web-chat transcript into development context.
3
- *
4
- * This is the "Continue in Codex" / ChatGPT-mode analog, and like Codex it is
5
- * a CONTEXT HANDOFF rather than a raw replay: the exploration-phase web
6
- * conversation is distilled (via the harness LLM) into an executable task
7
- * brief — the execution-phase state representation the agent actually needs —
8
- * and that brief seeds a fresh harness session. The raw transcript is kept as
9
- * the fallback when distillation is unavailable.
10
- *
11
- * Two targets:
12
- * - new harness session — a COLD persisted session seeded with the distilled
13
- * brief (or raw transcript), so it shows up in the GUI list and resumes;
14
- * - workspace file — the raw transcript rendered to markdown in the target
15
- * project directory, so any agent can read it with file tools.
16
- *
17
- * Session creation writes directly through the session-persistence backend
18
- * (`sessionPersistence.create` + `append`), NOT `ctx.sessions.create()`:
19
- * the store's `create` produces a LIVE session owned by the calling fiber,
20
- * which the GUI then refuses to resume ("cannot prepare … while it is live").
21
- * A cold persisted session is exactly what the GUI's resume path expects.
22
- */
23
- import type { Context } from '@deepseek-ai/cordis';
24
- import type { SessionEvent } from '@deepseek-ai/dsh-session';
25
- import type { Workspace } from '@deepseek-ai/dsh-workspace';
26
- import type { TransferMode, WebChatMessage, WebChatTranscript } from './protocol.ts';
27
- /** Render the message list of a transcript (no header) to markdown. */
28
- export declare function renderMessagesMarkdown(messages: WebChatMessage[], options?: {
29
- excludeThinking?: boolean;
30
- }): string;
31
- /** Render one transcript to markdown for harness consumption. */
32
- export declare function renderTranscriptMarkdown(transcript: WebChatTranscript, options?: {
33
- excludeThinking?: boolean;
34
- }): string;
35
- /**
36
- * Framing that turns the distilled brief into established context for the
37
- * agent — the analog of Codex's "another model started to solve this problem
38
- * and produced a summary; use it to build on the work already done".
39
- */
40
- export declare const HANDOFF_PREAMBLE = "\u8FD9\u662F\u4E00\u6B21\u4ECE DeepSeek \u7F51\u9875\u7AEF\u4F1A\u8BDD\uFF08chat.deepseek.com\uFF09\u8F6C\u6765\u7684\u4E0A\u4E0B\u6587\u4EA4\u63A5\u3002\u4E0B\u9762\u7684\u4EFB\u52A1\u7B80\u62A5\u5DF2\u628A\u8BE5\u5BF9\u8BDD\u63D0\u70BC\u4E3A\u53EF\u6267\u884C\u7684\u4EFB\u52A1\u4E0A\u4E0B\u6587\u2014\u2014\u628A\u5B83\u5F53\u4F5C\u65E2\u5B9A\u76EE\u6807\u4E0E\u80CC\u666F\uFF0C\u76F4\u63A5\u5728\u5176\u57FA\u7840\u4E0A\u7EE7\u7EED\uFF0C\u4E0D\u8981\u590D\u8FF0\u3002";
41
- /** Transfer distillation settings (resolved from the plugin config surface). */
42
- export interface DistillConfig {
43
- /** When true (default), distill the transcript into a task brief via ctx.llm. */
44
- distill: boolean;
45
- /** Provider route for the distillation call; empty = auto-detect. */
46
- provider: string;
47
- /** Model id for the distillation call; empty = auto-detect. */
48
- model: string;
49
- /** Output-token cap for the final brief (single-shot / reduce). Default 4096. */
50
- maxTokens?: number;
51
- /** Output-token cap for each per-chunk map summary. Default 1024. */
52
- chunkTokens?: number;
53
- }
54
- /** Default output-token cap for the final distillation brief. */
55
- export declare const DEFAULT_TRANSFER_MAX_TOKENS = 4096;
56
- /** Default output-token cap for one chunk summary in the map phase. */
57
- export declare const DEFAULT_TRANSFER_CHUNK_TOKENS = 1024;
58
- /** Character budget per chunk when splitting a long transcript for map-reduce. */
59
- export declare const CHUNK_CHAR_BUDGET = 12000;
60
- /** A successful distillation result. */
61
- export interface DistillResult {
62
- brief: string;
63
- provider: string;
64
- model: string;
65
- }
66
- /**
67
- * Split a transcript's messages into chunks of at most `budget` characters
68
- * (sum of `message.content.length`), never splitting a single message: a
69
- * message larger than the budget becomes its own (oversized) chunk. Streaming
70
- * assistant messages are skipped (they were never completed).
71
- */
72
- export declare function chunkTranscript(transcript: WebChatTranscript, budget?: number): WebChatMessage[][];
73
- /**
74
- * Distill a web transcript into an executable task brief via the harness LLM.
75
- * Long transcripts are distilled with map-reduce: each chunk is summarized
76
- * (map, capped at `chunkTokens`), then the summaries are merged into the final
77
- * brief (reduce, capped at `maxTokens`). Short transcripts take a single shot.
78
- * Returns undefined (so callers fall back to the raw transcript) when the LLM
79
- * service, a provider/model, or a clean completion is unavailable.
80
- */
81
- export declare function distillTranscriptToBrief(ctx: Context, transcript: WebChatTranscript, config: DistillConfig): Promise<DistillResult | undefined>;
82
- /** Build a user-message surface event carrying the handoff text at a given seq. */
83
- export declare function transcriptUserMessageEvent(markdown: string, seq: number): SessionEvent<'user/message'>;
84
- /** Build the seed user-message event carrying the handoff text (seq 0). */
85
- export declare function transcriptSeedEvent(markdown: string): SessionEvent<'user/message'>;
86
- export interface TransferWorkspaceTarget {
87
- /** Stable workspace id (from the registry / GUI picker); wins over `path`. */
88
- workspaceId?: string;
89
- /** Directory path to use as the session cwd (optionally resolves to a workspace). */
90
- path?: string;
91
- }
92
- export interface TransferToSessionInput {
93
- transcript: WebChatTranscript;
94
- cwd?: string;
95
- /** Target workspace; when set, the session is grouped under it (attached). */
96
- workspace?: TransferWorkspaceTarget;
97
- /**
98
- * Existing harness session to CONTINUE instead of creating a new one. When
99
- * set, the distilled brief (or raw transcript) is appended as a fresh user
100
- * message to that session rather than seeding a new session.
101
- */
102
- targetSessionId?: string;
103
- }
104
- /** Resolved transfer destination: the session cwd plus an optional owning workspace. */
105
- export interface ResolvedTransferTarget {
106
- cwd: string;
107
- workspace?: Workspace;
108
- }
109
- /** The result of creating a transferred harness session. */
110
- export interface TransferToSessionResult {
111
- sessionId: string;
112
- distilled: boolean;
113
- /** True when the session was attached to a workspace; false = ungrouped. */
114
- attached: boolean;
115
- /** Workspace id the session landed in, when attached. */
116
- workspaceId?: string;
117
- }
118
- /**
119
- * Create a new COLD harness session seeded with a distilled task brief (or the
120
- * raw transcript when the user chooses 'raw' / distillation is unavailable),
121
- * written straight through the session-persistence backend so the GUI lists it
122
- * and can resume it later (no live-store ownership). When `input.workspace`
123
- * names a registered workspace, the session's cwd is set to that workspace's
124
- * canonical path and the session is attached to the workspace's account, so
125
- * the GUI groups it under that workspace instead of "ungrouped".
126
- *
127
- * `mode` is the user's explicit choice; when undefined the plugin config
128
- * default (`transferDistill`) applies.
129
- */
130
- export declare function transferToHarnessSession(ctx: Context, input: TransferToSessionInput, config: DistillConfig, mode?: TransferMode): Promise<TransferToSessionResult>;
131
- export interface ExportTranscriptInput {
132
- transcript: WebChatTranscript;
133
- cwd?: string;
134
- }
135
- /** Write the transcript markdown into the target directory; returns the path. */
136
- export declare function exportTranscriptFile(input: ExportTranscriptInput): {
137
- filePath: string;
138
- };
package/src/client/api.ts DELETED
@@ -1,90 +0,0 @@
1
- /**
2
- * Browser-side API client for the /api/dsh-webchat route family. The only
3
- * data access path the panel components use — plain fetch, same origin.
4
- */
5
-
6
- import { WEBChat_API, type TransferMode, type WebChatState } from '../protocol.ts'
7
-
8
- /** Shape every /api/dsh-webchat response carries: ok plus optional error. */
9
- interface ApiResult {
10
- ok: boolean
11
- error?: string
12
- }
13
-
14
- /** Endpoint payloads always extend ApiResult. */
15
- type EndpointResult<T> = T & ApiResult
16
-
17
- async function request<T>(path: string, body?: unknown): Promise<EndpointResult<T>> {
18
- const response = await fetch(path, {
19
- method: body === undefined ? 'GET' : 'POST',
20
- headers: body === undefined ? undefined : { 'content-type': 'application/json' },
21
- body: body === undefined ? undefined : JSON.stringify(body),
22
- })
23
- const payload = (await response.json().catch(() => ({}))) as EndpointResult<T>
24
- if (!response.ok && payload.ok !== true) {
25
- return { ...payload, ok: false, error: payload.error ?? `HTTP ${response.status}` }
26
- }
27
- return payload
28
- }
29
-
30
- export class WebChatApi {
31
- state(): Promise<EndpointResult<WebChatState>> {
32
- return request<WebChatState>(WEBChat_API.state)
33
- }
34
-
35
- openLogin(): Promise<EndpointResult<{ ok: boolean }>> {
36
- return request<{ ok: boolean }>(WEBChat_API.openLogin)
37
- }
38
-
39
- closeBrowser(): Promise<EndpointResult<{ ok: boolean }>> {
40
- return request<{ ok: boolean }>(WEBChat_API.closeBrowser)
41
- }
42
-
43
- newChat(): Promise<EndpointResult<{ ok: boolean; chatId?: string }>> {
44
- return request<{ ok: boolean; chatId?: string }>(WEBChat_API.newChat)
45
- }
46
-
47
- send(text: string, images?: string[]): Promise<EndpointResult<{ ok: boolean; chatId?: string }>> {
48
- return request<{ ok: boolean; chatId?: string }>(WEBChat_API.send, { text, images })
49
- }
50
-
51
- stop(): Promise<EndpointResult<{ ok: boolean }>> {
52
- return request<{ ok: boolean }>(WEBChat_API.stop)
53
- }
54
-
55
- setDeepThink(enabled: boolean): Promise<EndpointResult<{ ok: boolean }>> {
56
- return request<{ ok: boolean }>(WEBChat_API.deepThink, { enabled })
57
- }
58
-
59
- setSearch(enabled: boolean): Promise<EndpointResult<{ ok: boolean }>> {
60
- return request<{ ok: boolean }>(WEBChat_API.search, { enabled })
61
- }
62
-
63
- transfer(chatId: string, cwd?: string, mode?: TransferMode, workspaceId?: string, targetSessionId?: string): Promise<EndpointResult<{ ok: boolean; sessionId?: string; distilled?: boolean; attached?: boolean; continued?: boolean; workspaceId?: string }>> {
64
- return request<{ ok: boolean; sessionId?: string; distilled?: boolean; attached?: boolean; continued?: boolean; workspaceId?: string }>(WEBChat_API.transfer, { chatId, cwd, mode, workspaceId, targetSessionId })
65
- }
66
-
67
- exportFile(chatId: string, cwd?: string): Promise<EndpointResult<{ ok: boolean; filePath?: string }>> {
68
- return request<{ ok: boolean; filePath?: string }>(WEBChat_API.exportFile, { chatId, cwd })
69
- }
70
-
71
- renameChat(chatId: string, title: string): Promise<EndpointResult<{ ok: boolean }>> {
72
- return request<{ ok: boolean }>(WEBChat_API.renameChat, { chatId, title })
73
- }
74
-
75
- deleteChat(chatId: string): Promise<EndpointResult<{ ok: boolean }>> {
76
- return request<{ ok: boolean }>(WEBChat_API.deleteChat, { chatId })
77
- }
78
-
79
- clearChats(): Promise<EndpointResult<{ ok: boolean; count?: number }>> {
80
- return request<{ ok: boolean; count?: number }>(WEBChat_API.clearChats)
81
- }
82
-
83
- webChats(): Promise<EndpointResult<{ ok: boolean; web: Array<{ title: string }>; missing: string[] }>> {
84
- return request<{ ok: boolean; web: Array<{ title: string }>; missing: string[] }>(WEBChat_API.webChats)
85
- }
86
-
87
- recover(title: string): Promise<EndpointResult<{ ok: boolean; chatId?: string; title?: string; created?: boolean }>> {
88
- return request<{ ok: boolean; chatId?: string; title?: string; created?: boolean }>(WEBChat_API.recover, { title })
89
- }
90
- }
@@ -1,43 +0,0 @@
1
- /**
2
- * Panel controller: minimal external state the sidebar entry and the panel
3
- * share (open/closed), plus a tiny pub/sub. The chat data itself lives in the
4
- * panel component's polling loop.
5
- */
6
-
7
- export interface PanelSnapshot {
8
- readonly panelOpen: boolean
9
- }
10
-
11
- const INITIAL: PanelSnapshot = { panelOpen: false }
12
-
13
- /** Plain pub/sub store (no React dependency — used by the DOM sidebar entry). */
14
- export class PanelController {
15
- private snapshot: PanelSnapshot = INITIAL
16
- private readonly listeners = new Set<() => void>()
17
-
18
- getSnapshot(): PanelSnapshot {
19
- return this.snapshot
20
- }
21
-
22
- subscribe(listener: () => void): () => void {
23
- this.listeners.add(listener)
24
- return () => { this.listeners.delete(listener) }
25
- }
26
-
27
- private set(patch: Partial<PanelSnapshot>): void {
28
- this.snapshot = { ...this.snapshot, ...patch }
29
- for (const listener of [...this.listeners]) listener()
30
- }
31
-
32
- open(): void {
33
- if (!this.snapshot.panelOpen) this.set({ panelOpen: true })
34
- }
35
-
36
- close(): void {
37
- if (this.snapshot.panelOpen) this.set({ panelOpen: false })
38
- }
39
-
40
- toggle(): void {
41
- this.set({ panelOpen: !this.snapshot.panelOpen })
42
- }
43
- }
@@ -1,5 +0,0 @@
1
- /** CSS Modules ambient declaration for the tsdown virtual loader. */
2
- declare module '*.module.css' {
3
- const classes: Record<string, string>
4
- export default classes
5
- }
@@ -1,75 +0,0 @@
1
- /**
2
- * Browser-half entry for the dsh-webchat plugin — runs inside the dsh web GUI.
3
- *
4
- * Registers the locale dictionaries and mounts the two DOM surfaces: the
5
- * sidebar entry row (toggles the panel) and the web-chat panel in the center
6
- * column. Failure policy: DOM mounting problems are logged, never thrown —
7
- * the web shell fails the whole boot when a plugin apply throws, and an
8
- * external plugin must not take the GUI down.
9
- *
10
- * Export discipline (packages/client rule): the /client surface carries what
11
- * cordis loading needs plus types only — all value exports stay internal.
12
- */
13
- import type { ClientContext, ISessions, IWorkspaces } from '@deepseek-ai/dsh-client-runtime/client'
14
- // Type-only: pulls the locale plugin's Context merge (ctx.locale).
15
- import type {} from '@deepseek-ai/dsh-client-locale/client'
16
- // Type-only: pulls the LocaleNamespaceMap merge table.
17
- import type {} from '@deepseek-ai/dsh-client-ui-slots'
18
- import { WebChatApi } from './api.ts'
19
- import { PanelController } from './controller.ts'
20
- import { en, zh, type WebChatKey } from './locales.ts'
21
- import { mountPanel } from './mount.tsx'
22
- import { mountSidebarEntry } from './sidebar-entry.ts'
23
-
24
- /** Locale namespace this plugin owns. */
25
- const NS = 'dsh-webchat'
26
-
27
- declare module '@deepseek-ai/dsh-client-ui-slots' {
28
- interface LocaleNamespaceMap {
29
- /** dsh-webchat surface copy. */
30
- 'dsh-webchat': WebChatKey
31
- }
32
- }
33
-
34
- /** Required services (fiber inject waiting — the runtime must be up first). */
35
- export const inject = ['slots', 'locale', 'sessions', 'workspaces']
36
-
37
- /** Type-only surface (export discipline: no value exports beyond the plugin contract). */
38
- export type { WebChatPanelProps } from './panel/WebChatPanel.tsx'
39
- export type { WebChatKey } from './locales.ts'
40
-
41
- /**
42
- * Mount the web-chat panel.
43
- * @param ctx - client root context (locale + sessions services).
44
- */
45
- export function apply(ctx: ClientContext): void {
46
- ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-webchat: dictionaries')
47
-
48
- const controller = new PanelController()
49
- const api = new WebChatApi()
50
- // The host-side dsh-session package also merges `Context.sessions` into
51
- // cordis types; the client runtime's ISessions is the authoritative shape
52
- // here, so read it through a narrow cast rather than trusting the merged
53
- // type (which the host package wins during a shared typecheck).
54
- const sessions = (ctx as unknown as { sessions: ISessions }).sessions
55
- const workspaces = (ctx as unknown as { workspaces: IWorkspaces }).workspaces
56
- const tt = ctx.locale.bind(NS)
57
- const currentCwd = (): string | undefined => {
58
- const snapshot = sessions.list.getSnapshot()
59
- const current = snapshot.current
60
- if (current === undefined) return undefined
61
- return snapshot.byId[current]?.cwd
62
- }
63
-
64
- const disposers: Array<() => void> = []
65
- try {
66
- disposers.push(mountSidebarEntry({ controller, tt }))
67
- disposers.push(mountPanel({ sessions, workspaces, api, controller, tt, currentCwd }))
68
- } catch (error) {
69
- // DOM failures degrade the panel, never the GUI.
70
- console.warn('[dsh-webchat] mount failed:', error)
71
- }
72
- ctx.effect(() => () => {
73
- for (const dispose of disposers.splice(0)) dispose()
74
- }, 'dsh-webchat: ui mounts')
75
- }