@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.
Files changed (136) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/cordis.patch.yml +12 -0
  4. package/lib/client.js +3308 -0
  5. package/lib/client.js.map +1 -0
  6. package/lib/index.js +2583 -0
  7. package/lib/types/client/Bubble.d.ts +10 -0
  8. package/lib/types/client/Glyph.d.ts +12 -0
  9. package/lib/types/client/GroupChatPanel.d.ts +10 -0
  10. package/lib/types/client/GroupChatPanel.old.d.ts +10 -0
  11. package/lib/types/client/GroupChatSettingsSection.d.ts +15 -0
  12. package/lib/types/client/PermissionSelect.d.ts +13 -0
  13. package/lib/types/client/RoleDrawer.d.ts +17 -0
  14. package/lib/types/client/ThinkRow.d.ts +9 -0
  15. package/lib/types/client/ToolRow.d.ts +9 -0
  16. package/lib/types/client/api.d.ts +8 -0
  17. package/lib/types/client/components/AsidePanel.d.ts +31 -0
  18. package/lib/types/client/components/Bubble.d.ts +19 -0
  19. package/lib/types/client/components/ChatPanel.d.ts +57 -0
  20. package/lib/types/client/components/Composer.d.ts +48 -0
  21. package/lib/types/client/components/Glyph.d.ts +12 -0
  22. package/lib/types/client/components/GroupChatSettingsSection.d.ts +15 -0
  23. package/lib/types/client/components/MessageFlow.d.ts +15 -0
  24. package/lib/types/client/components/NavPanel.d.ts +40 -0
  25. package/lib/types/client/components/PermissionSelect.d.ts +13 -0
  26. package/lib/types/client/components/RoleDrawer.d.ts +17 -0
  27. package/lib/types/client/components/ThinkRow.d.ts +9 -0
  28. package/lib/types/client/components/ToolRow.d.ts +9 -0
  29. package/lib/types/client/components/index.d.ts +9 -0
  30. package/lib/types/client/components.d.ts +22 -0
  31. package/lib/types/client/drawer.d.ts +17 -0
  32. package/lib/types/client/glyph.d.ts +12 -0
  33. package/lib/types/client/hooks/index.d.ts +6 -0
  34. package/lib/types/client/hooks/useComposer.d.ts +27 -0
  35. package/lib/types/client/hooks/useFileSearch.d.ts +19 -0
  36. package/lib/types/client/hooks/useGroupChatState.d.ts +95 -0
  37. package/lib/types/client/hooks/useMentionState.d.ts +27 -0
  38. package/lib/types/client/index.d.ts +52 -0
  39. package/lib/types/client/lib/api.d.ts +8 -0
  40. package/lib/types/client/lib/model.d.ts +80 -0
  41. package/lib/types/client/lib/styles.d.ts +10 -0
  42. package/lib/types/client/lib/ui.d.ts +17 -0
  43. package/lib/types/client/model.d.ts +78 -0
  44. package/lib/types/client/panel.d.ts +10 -0
  45. package/lib/types/client/settings.d.ts +15 -0
  46. package/lib/types/client/styles.d.ts +10 -0
  47. package/lib/types/client/ui.d.ts +17 -0
  48. package/lib/types/client/utils/utils.d.ts +29 -0
  49. package/lib/types/client/utils.d.ts +22 -0
  50. package/lib/types/core/json.d.ts +9 -0
  51. package/lib/types/core/status.d.ts +18 -0
  52. package/lib/types/core/tools.d.ts +59 -0
  53. package/lib/types/core/types.d.ts +256 -0
  54. package/lib/types/host/actions.d.ts +34 -0
  55. package/lib/types/host/api/actions.d.ts +36 -0
  56. package/lib/types/host/api/http.d.ts +16 -0
  57. package/lib/types/host/api/index.d.ts +7 -0
  58. package/lib/types/host/api/routes.d.ts +11 -0
  59. package/lib/types/host/broadcast.d.ts +20 -0
  60. package/lib/types/host/conversation.d.ts +25 -0
  61. package/lib/types/host/engine/conversation.d.ts +25 -0
  62. package/lib/types/host/engine/index.d.ts +7 -0
  63. package/lib/types/host/engine/retitle.d.ts +20 -0
  64. package/lib/types/host/http.d.ts +16 -0
  65. package/lib/types/host/index.d.ts +8 -0
  66. package/lib/types/host/materials/index.d.ts +6 -0
  67. package/lib/types/host/materials/materials.d.ts +40 -0
  68. package/lib/types/host/materials.d.ts +32 -0
  69. package/lib/types/host/persistence/index.d.ts +6 -0
  70. package/lib/types/host/persistence/persistence.d.ts +31 -0
  71. package/lib/types/host/persistence/store.d.ts +65 -0
  72. package/lib/types/host/persistence.d.ts +31 -0
  73. package/lib/types/host/routes.d.ts +11 -0
  74. package/lib/types/host/service.d.ts +28 -0
  75. package/lib/types/host/state.d.ts +45 -0
  76. package/lib/types/host/store.d.ts +65 -0
  77. package/lib/types/host/tools/index.d.ts +6 -0
  78. package/lib/types/host/tools/tools.d.ts +28 -0
  79. package/lib/types/host/tools.d.ts +28 -0
  80. package/lib/types/index.d.ts +41 -0
  81. package/lib/types/mount-once.d.ts +9 -0
  82. package/lib/types/shared/file-mention-grammar.d.ts +37 -0
  83. package/lib/types/shared/file-mention-grammar.test.d.ts +5 -0
  84. package/package.json +98 -0
  85. package/src/client/GroupChatPanel.tsx +385 -0
  86. package/src/client/components/AsidePanel.tsx +188 -0
  87. package/src/client/components/Bubble.tsx +76 -0
  88. package/src/client/components/ChatPanel.tsx +214 -0
  89. package/src/client/components/Composer.tsx +238 -0
  90. package/src/client/components/Glyph.tsx +27 -0
  91. package/src/client/components/GroupChatSettingsSection.tsx +74 -0
  92. package/src/client/components/MessageFlow.tsx +97 -0
  93. package/src/client/components/NavPanel.tsx +249 -0
  94. package/src/client/components/PermissionSelect.tsx +159 -0
  95. package/src/client/components/RoleDrawer.tsx +186 -0
  96. package/src/client/components/ThinkRow.tsx +32 -0
  97. package/src/client/components/ToolRow.tsx +51 -0
  98. package/src/client/components/index.ts +10 -0
  99. package/src/client/hooks/index.ts +7 -0
  100. package/src/client/hooks/useComposer.ts +272 -0
  101. package/src/client/hooks/useFileSearch.ts +115 -0
  102. package/src/client/hooks/useGroupChatState.ts +214 -0
  103. package/src/client/hooks/useMentionState.ts +56 -0
  104. package/src/client/index.ts +110 -0
  105. package/src/client/lib/api.ts +23 -0
  106. package/src/client/lib/model.ts +119 -0
  107. package/src/client/lib/styles.ts +317 -0
  108. package/src/client/lib/ui.ts +27 -0
  109. package/src/client/react-dom-shim.d.ts +13 -0
  110. package/src/client/utils/utils.ts +104 -0
  111. package/src/core/json.ts +28 -0
  112. package/src/core/status.ts +33 -0
  113. package/src/core/tools.ts +41 -0
  114. package/src/core/types.ts +222 -0
  115. package/src/host/api/actions.ts +335 -0
  116. package/src/host/api/http.ts +80 -0
  117. package/src/host/api/index.ts +8 -0
  118. package/src/host/api/routes.ts +95 -0
  119. package/src/host/broadcast.ts +65 -0
  120. package/src/host/engine/conversation.ts +331 -0
  121. package/src/host/engine/index.ts +8 -0
  122. package/src/host/engine/retitle.ts +135 -0
  123. package/src/host/index.ts +9 -0
  124. package/src/host/materials/index.ts +7 -0
  125. package/src/host/materials/materials.ts +193 -0
  126. package/src/host/persistence/index.ts +7 -0
  127. package/src/host/persistence/persistence.ts +311 -0
  128. package/src/host/persistence/store.ts +375 -0
  129. package/src/host/service.ts +78 -0
  130. package/src/host/state.ts +85 -0
  131. package/src/host/tools/index.ts +7 -0
  132. package/src/host/tools/tools.ts +194 -0
  133. package/src/index.ts +99 -0
  134. package/src/mount-once.ts +49 -0
  135. package/src/shared/file-mention-grammar.test.ts +77 -0
  136. 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
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
+ }
@@ -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
+ }