@roaming-ai/dsh-group-chat 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.
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/cordis.patch.yml +12 -0
- package/lib/client.js +3308 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +2583 -0
- package/lib/types/client/Bubble.d.ts +10 -0
- package/lib/types/client/Glyph.d.ts +12 -0
- package/lib/types/client/GroupChatPanel.d.ts +10 -0
- package/lib/types/client/GroupChatPanel.old.d.ts +10 -0
- package/lib/types/client/GroupChatSettingsSection.d.ts +15 -0
- package/lib/types/client/PermissionSelect.d.ts +13 -0
- package/lib/types/client/RoleDrawer.d.ts +17 -0
- package/lib/types/client/ThinkRow.d.ts +9 -0
- package/lib/types/client/ToolRow.d.ts +9 -0
- package/lib/types/client/api.d.ts +8 -0
- package/lib/types/client/components/AsidePanel.d.ts +31 -0
- package/lib/types/client/components/Bubble.d.ts +19 -0
- package/lib/types/client/components/ChatPanel.d.ts +57 -0
- package/lib/types/client/components/Composer.d.ts +48 -0
- package/lib/types/client/components/Glyph.d.ts +12 -0
- package/lib/types/client/components/GroupChatSettingsSection.d.ts +15 -0
- package/lib/types/client/components/MessageFlow.d.ts +15 -0
- package/lib/types/client/components/NavPanel.d.ts +40 -0
- package/lib/types/client/components/PermissionSelect.d.ts +13 -0
- package/lib/types/client/components/RoleDrawer.d.ts +17 -0
- package/lib/types/client/components/ThinkRow.d.ts +9 -0
- package/lib/types/client/components/ToolRow.d.ts +9 -0
- package/lib/types/client/components/index.d.ts +9 -0
- package/lib/types/client/components.d.ts +22 -0
- package/lib/types/client/drawer.d.ts +17 -0
- package/lib/types/client/glyph.d.ts +12 -0
- package/lib/types/client/hooks/index.d.ts +6 -0
- package/lib/types/client/hooks/useComposer.d.ts +27 -0
- package/lib/types/client/hooks/useFileSearch.d.ts +19 -0
- package/lib/types/client/hooks/useGroupChatState.d.ts +95 -0
- package/lib/types/client/hooks/useMentionState.d.ts +27 -0
- package/lib/types/client/index.d.ts +52 -0
- package/lib/types/client/lib/api.d.ts +8 -0
- package/lib/types/client/lib/model.d.ts +80 -0
- package/lib/types/client/lib/styles.d.ts +10 -0
- package/lib/types/client/lib/ui.d.ts +17 -0
- package/lib/types/client/model.d.ts +78 -0
- package/lib/types/client/panel.d.ts +10 -0
- package/lib/types/client/settings.d.ts +15 -0
- package/lib/types/client/styles.d.ts +10 -0
- package/lib/types/client/ui.d.ts +17 -0
- package/lib/types/client/utils/utils.d.ts +29 -0
- package/lib/types/client/utils.d.ts +22 -0
- package/lib/types/core/json.d.ts +9 -0
- package/lib/types/core/status.d.ts +18 -0
- package/lib/types/core/tools.d.ts +59 -0
- package/lib/types/core/types.d.ts +256 -0
- package/lib/types/host/actions.d.ts +34 -0
- package/lib/types/host/api/actions.d.ts +36 -0
- package/lib/types/host/api/http.d.ts +16 -0
- package/lib/types/host/api/index.d.ts +7 -0
- package/lib/types/host/api/routes.d.ts +11 -0
- package/lib/types/host/broadcast.d.ts +20 -0
- package/lib/types/host/conversation.d.ts +25 -0
- package/lib/types/host/engine/conversation.d.ts +25 -0
- package/lib/types/host/engine/index.d.ts +7 -0
- package/lib/types/host/engine/retitle.d.ts +20 -0
- package/lib/types/host/http.d.ts +16 -0
- package/lib/types/host/index.d.ts +8 -0
- package/lib/types/host/materials/index.d.ts +6 -0
- package/lib/types/host/materials/materials.d.ts +40 -0
- package/lib/types/host/materials.d.ts +32 -0
- package/lib/types/host/persistence/index.d.ts +6 -0
- package/lib/types/host/persistence/persistence.d.ts +31 -0
- package/lib/types/host/persistence/store.d.ts +65 -0
- package/lib/types/host/persistence.d.ts +31 -0
- package/lib/types/host/routes.d.ts +11 -0
- package/lib/types/host/service.d.ts +28 -0
- package/lib/types/host/state.d.ts +45 -0
- package/lib/types/host/store.d.ts +65 -0
- package/lib/types/host/tools/index.d.ts +6 -0
- package/lib/types/host/tools/tools.d.ts +28 -0
- package/lib/types/host/tools.d.ts +28 -0
- package/lib/types/index.d.ts +41 -0
- package/lib/types/mount-once.d.ts +9 -0
- package/lib/types/shared/file-mention-grammar.d.ts +37 -0
- package/lib/types/shared/file-mention-grammar.test.d.ts +5 -0
- package/package.json +98 -0
- package/src/client/GroupChatPanel.tsx +385 -0
- package/src/client/components/AsidePanel.tsx +188 -0
- package/src/client/components/Bubble.tsx +76 -0
- package/src/client/components/ChatPanel.tsx +214 -0
- package/src/client/components/Composer.tsx +238 -0
- package/src/client/components/Glyph.tsx +27 -0
- package/src/client/components/GroupChatSettingsSection.tsx +74 -0
- package/src/client/components/MessageFlow.tsx +97 -0
- package/src/client/components/NavPanel.tsx +249 -0
- package/src/client/components/PermissionSelect.tsx +159 -0
- package/src/client/components/RoleDrawer.tsx +186 -0
- package/src/client/components/ThinkRow.tsx +32 -0
- package/src/client/components/ToolRow.tsx +51 -0
- package/src/client/components/index.ts +10 -0
- package/src/client/hooks/index.ts +7 -0
- package/src/client/hooks/useComposer.ts +272 -0
- package/src/client/hooks/useFileSearch.ts +115 -0
- package/src/client/hooks/useGroupChatState.ts +214 -0
- package/src/client/hooks/useMentionState.ts +56 -0
- package/src/client/index.ts +110 -0
- package/src/client/lib/api.ts +23 -0
- package/src/client/lib/model.ts +119 -0
- package/src/client/lib/styles.ts +317 -0
- package/src/client/lib/ui.ts +27 -0
- package/src/client/react-dom-shim.d.ts +13 -0
- package/src/client/utils/utils.ts +104 -0
- package/src/core/json.ts +28 -0
- package/src/core/status.ts +33 -0
- package/src/core/tools.ts +41 -0
- package/src/core/types.ts +222 -0
- package/src/host/api/actions.ts +335 -0
- package/src/host/api/http.ts +80 -0
- package/src/host/api/index.ts +8 -0
- package/src/host/api/routes.ts +95 -0
- package/src/host/broadcast.ts +65 -0
- package/src/host/engine/conversation.ts +331 -0
- package/src/host/engine/index.ts +8 -0
- package/src/host/engine/retitle.ts +135 -0
- package/src/host/index.ts +9 -0
- package/src/host/materials/index.ts +7 -0
- package/src/host/materials/materials.ts +193 -0
- package/src/host/persistence/index.ts +7 -0
- package/src/host/persistence/persistence.ts +311 -0
- package/src/host/persistence/store.ts +375 -0
- package/src/host/service.ts +78 -0
- package/src/host/state.ts +85 -0
- package/src/host/tools/index.ts +7 -0
- package/src/host/tools/tools.ts +194 -0
- package/src/index.ts +99 -0
- package/src/mount-once.ts +49 -0
- package/src/shared/file-mention-grammar.test.ts +77 -0
- package/src/shared/file-mention-grammar.ts +82 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Platform modules (react-dom / client) have no @types in this plugin. */
|
|
2
|
+
declare module 'react-dom' {
|
|
3
|
+
export function flushSync(fn: () => void): void
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare module 'react-dom/client' {
|
|
7
|
+
import type { ReactNode } from 'react'
|
|
8
|
+
export interface Root {
|
|
9
|
+
render(children: ReactNode): void
|
|
10
|
+
unmount(): void
|
|
11
|
+
}
|
|
12
|
+
export function createRoot(container: Element | DocumentFragment): Root
|
|
13
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用工具函数
|
|
3
|
+
* @module dsh-group-chat/client/utils
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createElement } from 'react'
|
|
7
|
+
import { flushSync } from 'react-dom'
|
|
8
|
+
import { createRoot } from 'react-dom/client'
|
|
9
|
+
import { FileTypeIcon } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
10
|
+
import { activeAtToken, type AtToken } from '../../shared/file-mention-grammar.ts'
|
|
11
|
+
|
|
12
|
+
/** HTML 转义(芯片以 execCommand('insertHTML') 注入,角色名需转义)。 */
|
|
13
|
+
export function escapeHtml(v: string): string {
|
|
14
|
+
return String(v).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* execCommand 集中封装。它已被 Web 平台标记 @deprecated(ts 6387 编辑器提示),
|
|
19
|
+
* 但仍是唯一能让 contenteditable 的 DOM 编辑进入浏览器原生 undo 栈的同步 API
|
|
20
|
+
* (自研 undo 栈或引入 Lexical/ProseMirror 类框架代价更大;取舍见 DESIGN.md
|
|
21
|
+
* Composer 契约)。经中间别名擦除废弃标记,全部调用走本封装,不在调用处散布提示。
|
|
22
|
+
*/
|
|
23
|
+
type ExecCommandFn = (commandId: string, showUI?: boolean, value?: string) => boolean
|
|
24
|
+
let execCommandFn: ExecCommandFn | undefined
|
|
25
|
+
export function execCommand(commandId: 'delete' | 'insertHTML' | 'insertText' | 'insertLineBreak', value?: string): boolean {
|
|
26
|
+
execCommandFn ??= (document as unknown as { execCommand: ExecCommandFn }).execCommand
|
|
27
|
+
return execCommandFn.call(document, commandId, false, value)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* contenteditable 输入区 → 纯文本序列化(发送/参与判定的唯一事实源):
|
|
32
|
+
* 文本节点原样;<br> → 换行;角色芯片(.dsgc-chipin[data-role-id])展开回「@名字␠」;
|
|
33
|
+
* 文件芯片(.dsgc-chipin[data-kind=file])展开回「@path」或「@"path with spaces"」;
|
|
34
|
+
* DIV/P 块前补换行(防粘贴残留的块级包裹)。
|
|
35
|
+
*/
|
|
36
|
+
export function serializeInput(root: HTMLElement): string {
|
|
37
|
+
const walk = (node: Node): string => {
|
|
38
|
+
if (node.nodeType === Node.TEXT_NODE) return node.nodeValue || ''
|
|
39
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return ''
|
|
40
|
+
const el = node as HTMLElement
|
|
41
|
+
if (el.tagName === 'BR') return '\n'
|
|
42
|
+
if (el.classList.contains('dsgc-chipin')) {
|
|
43
|
+
// 角色芯片
|
|
44
|
+
if (el.dataset.roleId) {
|
|
45
|
+
return '@' + (el.dataset.name || '') + ' '
|
|
46
|
+
}
|
|
47
|
+
// 文件芯片
|
|
48
|
+
if (el.dataset.kind === 'file') {
|
|
49
|
+
const path = el.dataset.path || ''
|
|
50
|
+
const needsQuote = /\s/.test(path)
|
|
51
|
+
return needsQuote ? `@"${path}"` : `@${path}`
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const sep = /^(DIV|P)$/.test(el.tagName) ? '\n' : ''
|
|
55
|
+
return sep + Array.from(el.childNodes).map(walk).join('')
|
|
56
|
+
}
|
|
57
|
+
return Array.from(root.childNodes).map(walk).join('')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** @角色芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删)。 */
|
|
61
|
+
export function chipHtml(role: { id: string, name: string, color?: string }): string {
|
|
62
|
+
const c = escapeHtml(role.color || '#888')
|
|
63
|
+
return '<span class="dsgc-chipin" data-role-id="' + escapeHtml(role.id) + '" data-name="' + escapeHtml(role.name) + '" style="--role-color:' + c + '" contenteditable="false" draggable="true">' +
|
|
64
|
+
'<span class="dsgc-chipdot" style="background:' + c + '"></span>' + escapeHtml(role.name) + '</span>'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 把宿主 FileTypeIcon 渲成静态 SVG,再塞进 insertHTML(与检索列表同一套字形)。 */
|
|
68
|
+
function fileTypeIconMarkup(path: string, kind: 'file' | 'directory'): string {
|
|
69
|
+
if (typeof document === 'undefined') return ''
|
|
70
|
+
const host = document.createElement('span')
|
|
71
|
+
const root = createRoot(host)
|
|
72
|
+
try {
|
|
73
|
+
flushSync(() => {
|
|
74
|
+
root.render(kind === 'directory'
|
|
75
|
+
? createElement(FileTypeIcon, { kind: 'folder', size: 14 })
|
|
76
|
+
: createElement(FileTypeIcon, { path, size: 14 }))
|
|
77
|
+
})
|
|
78
|
+
return host.innerHTML
|
|
79
|
+
} finally {
|
|
80
|
+
root.unmount()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** @文件芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删)。 */
|
|
85
|
+
export function fileChipHtml(path: string, kind: 'file' | 'directory'): string {
|
|
86
|
+
const basename = path.split('/').pop() || path
|
|
87
|
+
const dir = kind === 'directory'
|
|
88
|
+
return '<span class="dsgc-chipin dsgc-chipin-file" data-kind="file"' + (dir ? ' data-dir="1"' : '') + ' data-path="' + escapeHtml(path) + '" contenteditable="false" draggable="true">' +
|
|
89
|
+
'<span class="dsgc-chipglyph" aria-hidden="true">' + fileTypeIconMarkup(path, kind) + '</span>' + escapeHtml(basename) + '</span>'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 光标前的活跃 @token(角色或文件弹层触发判定)。
|
|
94
|
+
*
|
|
95
|
+
* @returns AtToken 对象,包含 prefix/query/quoted;无返回 null
|
|
96
|
+
*/
|
|
97
|
+
export function queryAtCaret(): AtToken | null {
|
|
98
|
+
const sel = window.getSelection()
|
|
99
|
+
if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return null
|
|
100
|
+
const node = sel.anchorNode
|
|
101
|
+
if (!node || node.nodeType !== Node.TEXT_NODE) return null
|
|
102
|
+
const before = (node.nodeValue || '').slice(0, sel.anchorOffset)
|
|
103
|
+
return activeAtToken(before, before.length) || null
|
|
104
|
+
}
|
package/src/core/json.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 记录 → 落盘 JSON 的纯序列化(core 层;可选字段无则省略)。
|
|
3
|
+
* @module dsh-group-chat/core/json
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { MessageRecord, RoleRecord } from './types.ts'
|
|
7
|
+
|
|
8
|
+
/** 消息 → 落盘 JSON(可选字段无则省略;兼容保留 error)。 */
|
|
9
|
+
export function messageJson(m: Partial<MessageRecord> | undefined): Record<string, unknown> | null {
|
|
10
|
+
if (!m || typeof m.id !== 'string') return null
|
|
11
|
+
const o: Record<string, unknown> = { id: m.id, speaker: m.speaker, text: String(m.text || ''), ts: typeof m.ts === 'number' ? m.ts : Date.now(), seq: typeof m.seq === 'number' ? m.seq : 0 }
|
|
12
|
+
if (m.model !== undefined) o.model = m.model
|
|
13
|
+
if (m.reasoning !== undefined) o.reasoning = m.reasoning
|
|
14
|
+
if (m.reasoningFull !== undefined) o.reasoningFull = m.reasoningFull
|
|
15
|
+
if (m.thinkingSummary !== undefined) o.thinkingSummary = m.thinkingSummary
|
|
16
|
+
if (m.error !== undefined) o.error = m.error
|
|
17
|
+
if (Array.isArray(m.toolCalls) && m.toolCalls.length > 0) o.toolCalls = m.toolCalls
|
|
18
|
+
return o
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 角色 → 落盘 JSON(groupId 由目录归属,不再写入)。 */
|
|
22
|
+
export function roleJson(r: Partial<RoleRecord>): Record<string, unknown> {
|
|
23
|
+
const o: Record<string, unknown> = { id: r.id, name: String(r.name || '成员'), persona: String(r.persona || ''), provider: String(r.provider || ''), model: String(r.model || ''), enabled: r.enabled !== false, thinking: r.thinking === true }
|
|
24
|
+
if (r.color) o.color = r.color
|
|
25
|
+
if (typeof r.temperature === 'number' && !Number.isNaN(r.temperature)) o.temperature = r.temperature
|
|
26
|
+
if (typeof r.reasoningEffort === 'string' && r.reasoningEffort && r.reasoningEffort !== 'default') o.reasoningEffort = r.reasoningEffort
|
|
27
|
+
return o
|
|
28
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话状态派生(纯函数):从快照 run 派生单个会话的列表状态,
|
|
3
|
+
* 语义对齐 DSH 主会话列表(StateDot 五态取四)。
|
|
4
|
+
*
|
|
5
|
+
* 优先级:等待确认 > 进行中 > 已完成/已出错 > 默认(无点)。
|
|
6
|
+
* - ongoing:run 进行中(含用户已点停止但未停完的 stopping 窗口)
|
|
7
|
+
* - warning:run_command 确认闸门挂起(仅工作区内修改档)
|
|
8
|
+
* - done/error:run 结束后的「输出完毕」标记(正常/停止 → done,发言失败 → error)
|
|
9
|
+
* - idle:无任何活动(不渲染状态点,对齐主 GUI idle 隐藏)
|
|
10
|
+
* @module dsh-group-chat/core/status
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Snapshot } from './types.ts'
|
|
14
|
+
|
|
15
|
+
/** 会话列表状态(取值即 StateDotState 的渲染态)。 */
|
|
16
|
+
export type SessStatus = 'idle' | 'ongoing' | 'warning' | 'done' | 'error'
|
|
17
|
+
|
|
18
|
+
/** 状态点悬停/读屏文案(idle 不渲染点,无文案)。 */
|
|
19
|
+
export const SESS_STATUS_LABEL: Record<SessStatus, string> = {
|
|
20
|
+
idle: '',
|
|
21
|
+
ongoing: '进行中',
|
|
22
|
+
warning: '等待确认',
|
|
23
|
+
done: '已完成',
|
|
24
|
+
error: '已出错',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 派生单个会话的列表状态(run 为快照的 run 视图)。 */
|
|
28
|
+
export function sessStatus(run: Snapshot['run'], sessionId: string): SessStatus {
|
|
29
|
+
if (run.running && run.sessionId === sessionId) return run.pendingConfirm ? 'warning' : 'ongoing'
|
|
30
|
+
const f = run.finished
|
|
31
|
+
if (f && f.sessionId === sessionId) return f.reason === 'error' ? 'error' : 'done'
|
|
32
|
+
return 'idle'
|
|
33
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具执行护栏常量与 schema(TOOLS.md §1/§5;core 层纯数据)。
|
|
3
|
+
* @module dsh-group-chat/core/tools
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ---------- 工具执行护栏常量(TOOLS.md §5) ----------
|
|
7
|
+
/** run_command 超时。 */
|
|
8
|
+
export const RUN_CMD_TIMEOUT_MS = 120e3
|
|
9
|
+
/** read_file 单文件上限(超出截断)。 */
|
|
10
|
+
export const READ_FILE_MAX_BYTES = 100 * 1024
|
|
11
|
+
/** 单条工具输出回注截断。 */
|
|
12
|
+
export const CMD_OUTPUT_MAX_CHARS = 8e3
|
|
13
|
+
/** 命令输出采集内存上限(1MB,超出弃置仅计数)。 */
|
|
14
|
+
export const CMD_CAPTURE_MAX_BYTES = 1 << 20
|
|
15
|
+
/** 单条发言工具结果累计预算(含地板成本)。 */
|
|
16
|
+
export const TOOL_RESULTS_TOTAL_MAX = 32e3
|
|
17
|
+
/** 每次调用地板成本(防空输出零累积)。 */
|
|
18
|
+
export const TOOL_FLOOR_COST_CHARS = 100
|
|
19
|
+
/** 连续空输出/相同调用次数 → 强制收尾。 */
|
|
20
|
+
export const TOOL_REPEAT_LIMIT = 6
|
|
21
|
+
/** transcript 中工具输出摘要截断。 */
|
|
22
|
+
export const TRANSCRIPT_TOOL_SUMMARY = 200
|
|
23
|
+
|
|
24
|
+
/** 工具 schema(TOOLS.md §1)。 */
|
|
25
|
+
export const TOOL_SCHEMAS = [
|
|
26
|
+
{
|
|
27
|
+
name: 'read_file',
|
|
28
|
+
description: '读取群组工作区目录内的一个文本文件内容(≤100KB,超出截断)',
|
|
29
|
+
parameters: { type: 'object', properties: { path: { type: 'string', description: '相对工作区根的路径' } }, required: ['path'] },
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'list_dir',
|
|
33
|
+
description: '列出群组工作区目录内一个子目录的条目(名称/类型/大小)',
|
|
34
|
+
parameters: { type: 'object', properties: { path: { type: 'string', description: '相对工作区根的路径,默认 "."' } } },
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'run_command',
|
|
38
|
+
description: '在群组工作区目录内执行 shell 命令(如运行测试、git 操作;超时 120 秒;是否需要确认取决于群组权限档位)',
|
|
39
|
+
parameters: { type: 'object', properties: { command: { type: 'string' } }, required: ['command'] },
|
|
40
|
+
},
|
|
41
|
+
] as const
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 群聊数据模型(core 层,无 cordis 依赖)。
|
|
3
|
+
* 数据模型:Group 1..N Session,消息挂在会话上;角色与工作区目录挂在群组上。
|
|
4
|
+
* @module dsh-group-chat/core/types
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 群组权限档位(对齐主会话 /permission 三档):
|
|
9
|
+
* - view_only 仅可查看:只保留 read_file/list_dir,run_command 从 schema 剔除
|
|
10
|
+
* - workspace_write 工作区内修改:run_command 可用,每条命令逐条确认
|
|
11
|
+
* - full_access 完全权限:run_command 免确认直接执行
|
|
12
|
+
*/
|
|
13
|
+
export type PermissionTier = 'view_only' | 'workspace_write' | 'full_access'
|
|
14
|
+
|
|
15
|
+
/** 全部合法档位(展示顺序)。 */
|
|
16
|
+
export const PERMISSION_TIERS: readonly PermissionTier[] = ['view_only', 'workspace_write', 'full_access']
|
|
17
|
+
|
|
18
|
+
/** 档位安全化:合法字符串原样,其余 undefined。 */
|
|
19
|
+
export function asPermissionTier(value: unknown): PermissionTier | undefined {
|
|
20
|
+
return typeof value === 'string' && (PERMISSION_TIERS as readonly string[]).includes(value) ? value as PermissionTier : undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* ledger 行档位归一(v2→v3 兼容):显式 permissionTier 优先;
|
|
25
|
+
* 缺失时按旧布尔迁移——allowCommands=true → workspace_write(今日语义即
|
|
26
|
+
* 「可执行命令但逐条确认」),false/无字段 → view_only。
|
|
27
|
+
*/
|
|
28
|
+
export function migrateTier(permissionTier: unknown, allowCommands: unknown): PermissionTier {
|
|
29
|
+
const tier = asPermissionTier(permissionTier)
|
|
30
|
+
if (tier !== undefined) return tier
|
|
31
|
+
return allowCommands === true ? 'workspace_write' : 'view_only'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 群组:角色与工作区目录的宿主。 */
|
|
35
|
+
export interface GroupRecord {
|
|
36
|
+
id: string
|
|
37
|
+
name: string
|
|
38
|
+
workspaceDir: string
|
|
39
|
+
permissionTier: PermissionTier
|
|
40
|
+
roleIds: string[]
|
|
41
|
+
sessionIds: string[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 会话:消息挂在会话上。 */
|
|
45
|
+
export interface SessionRecord {
|
|
46
|
+
id: string
|
|
47
|
+
groupId: string
|
|
48
|
+
name: string
|
|
49
|
+
topic: string
|
|
50
|
+
/** 名称已被手动编辑:自动命名永久跳过(隐式固定)。 */
|
|
51
|
+
namePinned?: boolean
|
|
52
|
+
/** 主题已被手动编辑:自动整理永久跳过(隐式固定)。 */
|
|
53
|
+
topicPinned?: boolean
|
|
54
|
+
messageIds: string[]
|
|
55
|
+
createdAt: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 角色:绑定 provider/model 的群成员身份。 */
|
|
59
|
+
export interface RoleRecord {
|
|
60
|
+
id: string
|
|
61
|
+
groupId: string
|
|
62
|
+
name: string
|
|
63
|
+
color?: string
|
|
64
|
+
persona: string
|
|
65
|
+
provider: string
|
|
66
|
+
model: string
|
|
67
|
+
temperature?: number
|
|
68
|
+
reasoningEffort?: string
|
|
69
|
+
enabled: boolean
|
|
70
|
+
thinking: boolean
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 一次工具调用的落盘/快照形态(TOOLS.md §3.3 回注契约的展示面)。 */
|
|
74
|
+
export interface ToolCallRecord {
|
|
75
|
+
tool: string
|
|
76
|
+
args: unknown
|
|
77
|
+
status: string
|
|
78
|
+
output: string
|
|
79
|
+
durationMs?: number
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 消息(user / system / 角色发言共用一条记录)。 */
|
|
83
|
+
export interface MessageRecord {
|
|
84
|
+
id: string
|
|
85
|
+
sessionId: string
|
|
86
|
+
seq: number
|
|
87
|
+
speaker: string
|
|
88
|
+
text: string
|
|
89
|
+
reasoning?: string
|
|
90
|
+
reasoningFull?: string
|
|
91
|
+
thinkingSummary?: string
|
|
92
|
+
model?: string
|
|
93
|
+
error?: boolean
|
|
94
|
+
toolCalls?: ToolCallRecord[]
|
|
95
|
+
ts: number
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** run_command 确认闸门的待确认载荷。 */
|
|
99
|
+
export interface PendingConfirm {
|
|
100
|
+
toolCallId: string
|
|
101
|
+
tool: string
|
|
102
|
+
args: { command?: string } & Record<string, unknown>
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 最近一次创建的群组/会话,供客户端定位选中项。 */
|
|
106
|
+
export interface LastCreated {
|
|
107
|
+
kind: 'group' | 'session'
|
|
108
|
+
groupId: string
|
|
109
|
+
sessionId: string
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** run 结束后的「输出完毕」标记(内存态不持久化,重启即清;reason 取 ok/error)。 */
|
|
113
|
+
export interface RunFinished {
|
|
114
|
+
sessionId: string
|
|
115
|
+
reason: 'ok' | 'error'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** 对话进行时状态(run)。 */
|
|
119
|
+
export interface RunState {
|
|
120
|
+
running: boolean
|
|
121
|
+
sessionId: string | null
|
|
122
|
+
currentRoleId: string | null
|
|
123
|
+
partial: string
|
|
124
|
+
partialReasoning: string
|
|
125
|
+
stopping: boolean
|
|
126
|
+
queue: string[]
|
|
127
|
+
pendingConfirm: PendingConfirm | null
|
|
128
|
+
confirmSignal: { resolve: (allowed: boolean) => void } | null
|
|
129
|
+
childProc: import('node:child_process').ChildProcess | null
|
|
130
|
+
/** 最近一次 run 的结束标记:会话列表「已完成/已出错」状态的数据源。 */
|
|
131
|
+
finished: RunFinished | null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 角色发言的引擎产物。 */
|
|
135
|
+
export interface SpeakResult {
|
|
136
|
+
text: string
|
|
137
|
+
reasoning: string | undefined
|
|
138
|
+
toolCalls: ToolCallRecord[]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** 工具执行结果(host 内部约定)。 */
|
|
142
|
+
export interface ToolExecution {
|
|
143
|
+
status: 'ok' | 'error' | 'denied'
|
|
144
|
+
output: string
|
|
145
|
+
args: Record<string, unknown>
|
|
146
|
+
durationMs: number
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** 模型目录(models action 的缓存载荷)。 */
|
|
150
|
+
export interface ModelCatalog {
|
|
151
|
+
at: number
|
|
152
|
+
providers: { id: string, name: string }[]
|
|
153
|
+
modelsByProvider: Record<string, { id: string, name?: string }[]>
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** 推理级别选项(efforts action 的载荷)。 */
|
|
157
|
+
export interface EffortOptions {
|
|
158
|
+
efforts: { id: string, name: string, description?: string }[]
|
|
159
|
+
defaultEffort: string | undefined
|
|
160
|
+
error?: string
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** 目录浏览结果(browse action 的载荷)。 */
|
|
164
|
+
export interface BrowseResult {
|
|
165
|
+
ok: boolean
|
|
166
|
+
path?: string
|
|
167
|
+
home?: string
|
|
168
|
+
parent?: string
|
|
169
|
+
entries?: { name: string, type: string, path: string, size?: number, hidden: boolean }[]
|
|
170
|
+
error?: string
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** 群工作区 @ 文件检索结果(fileSearch action 的载荷)。 */
|
|
174
|
+
export interface FileSearchResult {
|
|
175
|
+
ok: boolean
|
|
176
|
+
candidates?: { path: string, isDir: boolean }[]
|
|
177
|
+
error?: string
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** 发到客户端的全量快照(wire 形态)。 */
|
|
181
|
+
export interface Snapshot {
|
|
182
|
+
revision: number
|
|
183
|
+
run: { running: boolean, sessionId: string | null, currentRoleId: string | null, partial: string, partialReasoning: string, pendingConfirm: PendingConfirm | null, finished: RunFinished | null }
|
|
184
|
+
lastCreated: LastCreated | null
|
|
185
|
+
groups: { id: string, name: string, workspaceDir: string, permissionTier: PermissionTier, roleIds: string[], sessionIds: string[] }[]
|
|
186
|
+
sessions: { id: string, groupId: string, name: string, topic: string, messageIds: string[], createdAt: number }[]
|
|
187
|
+
roles: { id: string, groupId: string, name: string, color?: string, persona: string, provider: string, model: string, temperature?: number, reasoningEffort?: string, enabled: boolean, thinking: boolean }[]
|
|
188
|
+
messages: { id: string, sessionId: string, seq: number, speaker: string, text: string, reasoning?: string, model?: string, error?: boolean, toolCalls?: ToolCallRecord[], ts: number }[]
|
|
189
|
+
error?: string
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** mutate 动作的参数形态(op 分发见 host/service)。 */
|
|
193
|
+
export interface MutateArgs {
|
|
194
|
+
op: string
|
|
195
|
+
groupId?: string
|
|
196
|
+
sessionId?: string
|
|
197
|
+
roleId?: string
|
|
198
|
+
role?: Partial<RoleRecord>
|
|
199
|
+
name?: string
|
|
200
|
+
topic?: string
|
|
201
|
+
path?: string
|
|
202
|
+
tier?: string
|
|
203
|
+
enabled?: boolean
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** send 动作的参数形态。 */
|
|
207
|
+
export interface SendArgs {
|
|
208
|
+
sessionId: string
|
|
209
|
+
text?: string
|
|
210
|
+
participantRoleIds?: string[]
|
|
211
|
+
rounds?: number
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** 可选数值参数安全化:数字则原样,否则 undefined。 */
|
|
215
|
+
export function asNumber(value: unknown): number | undefined {
|
|
216
|
+
return typeof value === 'number' && !Number.isNaN(value) ? value : undefined
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** 可选字符串参数安全化:非 default 的非空字符串则原样,否则 undefined。 */
|
|
220
|
+
export function asEffort(value: unknown): string | undefined {
|
|
221
|
+
return typeof value === 'string' && value && value !== 'default' ? value : undefined
|
|
222
|
+
}
|