@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
package/src/tools.ts DELETED
@@ -1,257 +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
-
9
- import type { Context } from '@deepseek-ai/cordis'
10
- import { defineTool } from '@deepseek-ai/dsh-tools'
11
- import type { ContentBlock } from '@deepseek-ai/dsh-llm'
12
- import type { DeepSeekWebEngine } from './engine/engine.ts'
13
- import type { TranscriptStore } from './store.ts'
14
- import { renderTranscriptMarkdown, transferToHarnessSession } from './transfer.ts'
15
- import type { DistillConfig } from './transfer.ts'
16
- import type { WebChatErrorCode } from './protocol.ts'
17
-
18
- /** Actionable hint for a structured engine error code (surfaced to the agent). */
19
- function errorCodeHint(code: WebChatErrorCode | undefined): string {
20
- switch (code) {
21
- case 'NEED_LOGIN': return '需要先登录:请在插件面板点击「打开登录窗口」完成 DeepSeek 网页登录。'
22
- case 'PAGE_CHANGED': return '页面/协议疑似改版:请升级 dsh-webchat 插件。'
23
- case 'TIMEOUT': return '生成超时:可稍后重试。'
24
- case 'NETWORK': return '网络/浏览器错误:请检查网络或浏览器是否可用。'
25
- default: return ''
26
- }
27
- }
28
-
29
- /** One text content block (the only render shape these tools emit). */
30
- function text(value: string): ContentBlock[] {
31
- return [{ type: 'text', text: value }]
32
- }
33
-
34
- /** Minimal workspace projection surfaced to the agent (id/path/title only). */
35
- export interface WorkspaceRef {
36
- id: string
37
- path: string
38
- title: string
39
- }
40
-
41
- /** Render the chat list compactly. */
42
- function renderChats(store: TranscriptStore): string {
43
- const chats = store.list()
44
- if (chats.length === 0) return '还没有任何网页端对话记录'
45
- return chats.map(chat => {
46
- const messages = chat.messages.length
47
- const last = chat.messages.at(-1)
48
- const preview = last === undefined ? '' : ` · 最后: ${last.content.replace(/\s+/g, ' ').slice(0, 60)}`
49
- return `${chat.id} | ${chat.title} | ${chat.model} | ${messages} 条消息 | ${new Date(chat.updatedAt).toLocaleString()}${preview}`
50
- }).join('\n')
51
- }
52
-
53
- /** The engine-status tool. */
54
- export function webChatStatusTool(engine: DeepSeekWebEngine, store: TranscriptStore, listWorkspaces?: () => WorkspaceRef[] | undefined) {
55
- return defineTool({
56
- name: 'webchat_status',
57
- description: 'Report the DeepSeek 网页端 (chat.deepseek.com) web-chat state: engine status, login state, active chat, stored transcripts, and the harness workspaces available as webchat_transfer targets. Triggers: webchat, deepseek 网页端, 网页聊天. Use before webchat_send to confirm login.',
58
- parameters: {},
59
- output: {
60
- schema: {
61
- type: 'object',
62
- additionalProperties: false,
63
- properties: {
64
- report: { type: 'string', required: true },
65
- },
66
- },
67
- render: (_args, value: { report?: string }) => text(value.report ?? ''),
68
- },
69
- async execute(): Promise<{ report: string }> {
70
- const status = await engine.status()
71
- const active = store.activeChat()
72
- const lines = [
73
- `engine: ${status.engine}${status.engineError !== undefined ? ` (${status.engineError})` : ''}`,
74
- `loggedIn: ${String(status.loggedIn)}`,
75
- `pageUrl: ${status.pageUrl ?? '-'}`,
76
- `deepThink: ${String(status.deepThink)}`,
77
- `search: ${String(status.search)}`,
78
- `busy: ${String(status.busy)}`,
79
- `activeChat: ${active === undefined ? '-' : `${active.id} (${active.title})`}`,
80
- `chats:\n${renderChats(store)}`,
81
- ]
82
- const web = await engine.listWebConversations().catch(() => [] as Array<{ title: string }>)
83
- if (web.length > 0) {
84
- const localTitles = new Set(store.list().map(chat => chat.title))
85
- const missing = web.filter(item => !localTitles.has(item.title)).map(item => item.title)
86
- lines.push(`webChats:\n${web.map(item => ` - ${item.title}`).join('\n')}`)
87
- if (missing.length > 0) lines.push(`webChatsNotImported (use webchat_recover):\n${missing.map(title => ` - ${title}`).join('\n')}`)
88
- }
89
- const workspaces = listWorkspaces?.()
90
- if (workspaces !== undefined) {
91
- lines.push('workspaces:')
92
- if (workspaces.length === 0) lines.push(' (none)')
93
- else for (const ws of workspaces) lines.push(` ${ws.id} | ${ws.title} | ${ws.path}`)
94
- }
95
- return { report: lines.join('\n') }
96
- },
97
- })
98
- }
99
-
100
- /** The send-via-web tool. */
101
- export function webChatSendTool(engine: DeepSeekWebEngine) {
102
- return defineTool({
103
- name: 'webchat_send',
104
- description: 'Send one message through the DeepSeek 网页端 (chat.deepseek.com) using the web model — your web session, no API billing. The assistant reply streams until complete and returns as markdown. Optionally attach local image files (absolute paths) for multimodal prompts. Requires the user to have logged into the web chat once (webchat_status → loggedIn true). Best for asking the web model to explain/design/review; do not use for file operations. Triggers: 网页端提问, deepseek web, chatgpt mode.',
105
- parameters: {
106
- text: { type: 'string', required: true, description: 'The message to send to deepseek-chat on the web.' },
107
- images: { type: 'array', items: { type: 'string' }, description: 'Optional local absolute paths of image files to attach (multimodal prompt).' },
108
- },
109
- output: {
110
- schema: {
111
- type: 'object',
112
- additionalProperties: false,
113
- properties: {
114
- reply: { type: 'string', required: true },
115
- error: { type: 'string' },
116
- code: { type: 'string' },
117
- partial: { type: 'boolean' },
118
- },
119
- },
120
- render: (_args, value: { reply?: string; error?: string; code?: string; partial?: boolean }) => {
121
- const partial = value.partial === true
122
- const hint = errorCodeHint(value.code as WebChatErrorCode | undefined)
123
- return text([
124
- `webchat_send: 已通过 DeepSeek 网页端发送并收到回复${partial ? '(生成可能不完整)' : ''}`,
125
- value.error !== undefined ? `(注意:${value.error})` : '',
126
- hint !== '' ? `(${hint})` : '',
127
- '',
128
- '--- 网页端回复 ---',
129
- (value.reply ?? '').trim() === '' ? '(空回复)' : (value.reply ?? '').trim(),
130
- '--- 回复结束 ---',
131
- '',
132
- '会话已保存,可用 webchat_transfer 将整段对话转移到 harness 会话。',
133
- ].join('\n'))
134
- },
135
- },
136
- async execute(args: { text?: string; images?: unknown }): Promise<{ reply: string; error?: string; code?: string; partial: boolean }> {
137
- const textValue = typeof args?.text === 'string' ? args.text.trim() : ''
138
- if (textValue === '') return { reply: '', error: '缺少 text 参数', partial: false }
139
- const images = Array.isArray(args?.images)
140
- ? args.images.filter(value => typeof value === 'string').map(value => value as string)
141
- : undefined
142
- // wait=true so the tool returns the completed reply (the GUI path is fire-and-forget).
143
- const result = await engine.send(textValue, true, images)
144
- return {
145
- reply: result.reply ?? '',
146
- error: result.error,
147
- code: result.code,
148
- partial: result.error !== undefined,
149
- }
150
- },
151
- })
152
- }
153
-
154
- /** The web-conversation recover tool (sync web sidebar → local store). */
155
- export function webChatRecoverTool(engine: DeepSeekWebEngine) {
156
- return defineTool({
157
- name: 'webchat_recover',
158
- description: 'Recover a DeepSeek 网页端 conversation into the local store so it can be imported/transferred. With no title, lists the web-side conversations. Triggers: 同步网页会话, 恢复网页对话, sync webchat.',
159
- parameters: {
160
- title: { type: 'string', description: 'Conversation title (from the web sidebar or webchat_status webChats list). Omit to list web conversations.' },
161
- },
162
- output: {
163
- schema: { type: 'object', additionalProperties: false, properties: { report: { type: 'string', required: true } } },
164
- render: (_args, value: { report?: string }) => text(value.report ?? ''),
165
- },
166
- async execute(args: { title?: string }): Promise<{ report: string }> {
167
- if (typeof args?.title === 'string' && args.title.trim() !== '') {
168
- const result = await engine.recoverWebConversation(args.title.trim())
169
- if (!result.ok) return { report: `webchat_recover: 恢复失败 — ${result.error ?? ''}` }
170
- const dedup = result.created === false ? '(本地已存在,未重复导入)' : ''
171
- return { report: `webchat_recover: 已恢复「${result.title ?? ''}」为本地对话 ${result.chatId ?? ''}${dedup}。可用 webchat_transfer 转移。` }
172
- }
173
- const web = await engine.listWebConversations().catch(() => [] as Array<{ title: string }>)
174
- if (web.length === 0) return { report: 'webchat_recover: 未在网页端读到会话(可能未登录或页面已改版)' }
175
- return { report: web.map(item => `- ${item.title}`).join('\n') }
176
- },
177
- })
178
- }
179
-
180
- /** The transcript import tool. */
181
- export function webChatImportTool(store: TranscriptStore) {
182
- return defineTool({
183
- name: 'webchat_import',
184
- description: 'Import one stored DeepSeek 网页端 transcript as markdown so the agent can continue the discussion itself. Triggers: 读取网页对话, import webchat, 把网页聊天作为上下文. Use webchat_status to list chat ids first.',
185
- parameters: {
186
- chatId: { type: 'string', description: 'Transcript id (from webchat_status). Omit for the active chat.' },
187
- },
188
- output: {
189
- schema: {
190
- type: 'object',
191
- additionalProperties: false,
192
- properties: {
193
- transcript: { type: 'string', required: true },
194
- error: { type: 'string' },
195
- },
196
- },
197
- render: (_args, value: { transcript?: string; error?: string }) => {
198
- if (value.error !== undefined) return text(value.error)
199
- return text(value.transcript ?? '')
200
- },
201
- },
202
- async execute(args: { chatId?: string }): Promise<{ transcript: string; error?: string }> {
203
- const chat = typeof args?.chatId === 'string' ? store.getChat(args.chatId) : store.activeChat()
204
- if (chat === undefined) return { transcript: '', error: 'webchat_import: 找不到对话记录(用 webchat_status 查看列表)' }
205
- return { transcript: renderTranscriptMarkdown(chat) }
206
- },
207
- })
208
- }
209
-
210
- /** The transfer tool (closes over the host context so it can create sessions). */
211
- export function webChatTransferTool(hostCtx: Context, store: TranscriptStore, distill: DistillConfig) {
212
- return defineTool({
213
- name: 'webchat_transfer',
214
- description: 'Transfer a stored DeepSeek 网页端 transcript into harness mode: distills the web conversation into an executable task brief (goal, established context, current state, next steps) and creates a NEW harness session whose first message is that brief (not the raw chat log), OR appends it as a fresh user message to an EXISTING session via targetSessionId (continue the same task). Optionally target a workspace (workspaceId from webchat_status workspaces list) so the new session is grouped under it. Returns the (new or target) session id. Triggers: 转移到 harness, 转成开发会话, transfer webchat.',
215
- parameters: {
216
- chatId: { type: 'string', description: 'Transcript id (from webchat_status). Omit for the active chat.' },
217
- targetSessionId: { type: 'string', description: 'Optional existing harness session id to CONTINUE (append the brief as a new user message) instead of creating a new session. Omit to create a new session.' },
218
- workspaceId: { type: 'string', description: 'Optional target workspace id (from the workspaces list in webchat_status). Omit to leave the new session ungrouped. Ignored when targetSessionId is given.' },
219
- cwd: { type: 'string', description: 'Optional absolute working directory for the new session; ignored when workspaceId or targetSessionId is given.' },
220
- },
221
- output: {
222
- schema: {
223
- type: 'object',
224
- additionalProperties: false,
225
- properties: {
226
- sessionId: { type: 'string', required: true },
227
- distilled: { type: 'boolean' },
228
- attached: { type: 'boolean' },
229
- continued: { type: 'boolean' },
230
- workspaceId: { type: 'string' },
231
- error: { type: 'string' },
232
- },
233
- },
234
- render: (_args, value: { sessionId?: string; distilled?: boolean; attached?: boolean; continued?: boolean; workspaceId?: string; error?: string }) => {
235
- if (value.error !== undefined) return text(value.error)
236
- const note = value.distilled === true ? '(已蒸馏为任务简报)' : '(蒸馏不可用,已回退为原始对话记录)'
237
- if (value.continued === true) {
238
- return text(`webchat_transfer: 已把网页对话作为新的用户消息延续到 harness 会话 ${value.sessionId ?? ''}${note}。请告知用户打开该会话继续开发。`)
239
- }
240
- const where = value.workspaceId !== undefined ? `已归入工作区 ${value.workspaceId}` : '未分组'
241
- return text(`webchat_transfer: 已创建新 harness 会话 ${value.sessionId ?? ''}${note}(${where})。请告知用户从侧边栏打开该会话继续开发。`)
242
- },
243
- },
244
- async execute(args: { chatId?: string; targetSessionId?: string; workspaceId?: string; cwd?: string }): Promise<{ sessionId: string; distilled: boolean; attached: boolean; continued?: boolean; workspaceId?: string; error?: string }> {
245
- const chat = typeof args?.chatId === 'string' ? store.getChat(args.chatId) : store.activeChat()
246
- if (chat === undefined) return { sessionId: '', distilled: false, attached: false, error: 'webchat_transfer: 找不到对话记录(用 webchat_status 查看列表)' }
247
- const targetSessionId = typeof args?.targetSessionId === 'string' && args.targetSessionId !== '' ? args.targetSessionId : undefined
248
- const workspace = targetSessionId === undefined && typeof args?.workspaceId === 'string' && args.workspaceId !== '' ? { workspaceId: args.workspaceId } : undefined
249
- try {
250
- const { sessionId, distilled, attached, workspaceId } = await transferToHarnessSession(hostCtx, { transcript: chat, cwd: args?.cwd, workspace, targetSessionId }, distill)
251
- return { sessionId, distilled, attached, continued: targetSessionId !== undefined, workspaceId }
252
- } catch (error) {
253
- return { sessionId: '', distilled: false, attached: false, continued: targetSessionId !== undefined, error: `webchat_transfer: 转移失败 — ${String(error)}` }
254
- }
255
- },
256
- })
257
- }