@roaming-ai/dsh-group-chat 0.3.0 → 0.3.2

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 (80) hide show
  1. package/README.md +93 -28
  2. package/lib/client.js +295 -326
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +234 -221
  5. package/lib/types/client/components/AsidePanel.d.ts +3 -19
  6. package/lib/types/client/components/NavPanel.d.ts +9 -11
  7. package/lib/types/client/components/RoleDrawer.d.ts +3 -2
  8. package/lib/types/client/components/ToolRow.d.ts +2 -2
  9. package/lib/types/client/hooks/useGroupChatState.d.ts +0 -16
  10. package/lib/types/client/lib/model.d.ts +1 -7
  11. package/lib/types/client/utils/utils.d.ts +4 -4
  12. package/lib/types/core/json.d.ts +6 -0
  13. package/lib/types/core/types.d.ts +59 -64
  14. package/lib/types/host/engine/defaults.d.ts +13 -0
  15. package/lib/types/host/persistence/persistence.d.ts +2 -4
  16. package/lib/types/host/persistence/store.d.ts +5 -1
  17. package/lib/types/host/service.d.ts +2 -2
  18. package/lib/types/host/state.d.ts +1 -0
  19. package/lib/types/host/tools/tools.d.ts +2 -1
  20. package/lib/types/index.d.ts +3 -0
  21. package/lib/types/shared/file-mention-grammar.d.ts +3 -12
  22. package/package.json +4 -1
  23. package/src/client/GroupChatPanel.tsx +9 -56
  24. package/src/client/components/AsidePanel.tsx +40 -15
  25. package/src/client/components/ChatPanel.tsx +1 -1
  26. package/src/client/components/Composer.tsx +2 -2
  27. package/src/client/components/ConstraintList.tsx +1 -6
  28. package/src/client/components/MessageFlow.tsx +2 -1
  29. package/src/client/components/NavPanel.tsx +96 -78
  30. package/src/client/components/RoleDrawer.tsx +29 -7
  31. package/src/client/components/ToolRow.tsx +2 -2
  32. package/src/client/hooks/useComposer.ts +14 -53
  33. package/src/client/hooks/useGroupChatState.ts +2 -13
  34. package/src/client/lib/model.ts +1 -14
  35. package/src/client/lib/styles.ts +6 -2
  36. package/src/client/utils/utils.ts +6 -6
  37. package/src/core/constraints.ts +6 -12
  38. package/src/core/errors.ts +2 -12
  39. package/src/core/json.ts +17 -0
  40. package/src/core/types.ts +29 -20
  41. package/src/host/api/actions.ts +14 -13
  42. package/src/host/engine/conversation.ts +50 -51
  43. package/src/host/engine/defaults.ts +28 -0
  44. package/src/host/engine/fold.ts +2 -17
  45. package/src/host/engine/retitle.ts +9 -31
  46. package/src/host/materials/materials.ts +2 -0
  47. package/src/host/persistence/persistence.ts +57 -34
  48. package/src/host/persistence/store.ts +5 -1
  49. package/src/host/service.ts +4 -4
  50. package/src/host/state.ts +5 -2
  51. package/src/host/tools/tools.ts +42 -48
  52. package/src/index.ts +7 -5
  53. package/src/shared/file-mention-grammar.test.ts +1 -38
  54. package/src/shared/file-mention-grammar.ts +7 -33
  55. package/lib/types/client/Bubble.d.ts +0 -10
  56. package/lib/types/client/Glyph.d.ts +0 -12
  57. package/lib/types/client/GroupChatPanel.old.d.ts +0 -10
  58. package/lib/types/client/GroupChatSettingsSection.d.ts +0 -15
  59. package/lib/types/client/PermissionSelect.d.ts +0 -13
  60. package/lib/types/client/RoleDrawer.d.ts +0 -17
  61. package/lib/types/client/ThinkRow.d.ts +0 -9
  62. package/lib/types/client/ToolRow.d.ts +0 -9
  63. package/lib/types/client/api.d.ts +0 -8
  64. package/lib/types/client/components.d.ts +0 -22
  65. package/lib/types/client/drawer.d.ts +0 -17
  66. package/lib/types/client/glyph.d.ts +0 -12
  67. package/lib/types/client/model.d.ts +0 -78
  68. package/lib/types/client/panel.d.ts +0 -10
  69. package/lib/types/client/settings.d.ts +0 -15
  70. package/lib/types/client/styles.d.ts +0 -10
  71. package/lib/types/client/ui.d.ts +0 -17
  72. package/lib/types/client/utils.d.ts +0 -22
  73. package/lib/types/host/actions.d.ts +0 -34
  74. package/lib/types/host/conversation.d.ts +0 -25
  75. package/lib/types/host/http.d.ts +0 -16
  76. package/lib/types/host/materials.d.ts +0 -32
  77. package/lib/types/host/persistence.d.ts +0 -31
  78. package/lib/types/host/routes.d.ts +0 -11
  79. package/lib/types/host/store.d.ts +0 -65
  80. package/lib/types/host/tools.d.ts +0 -28
@@ -1,31 +1,15 @@
1
1
  /**
2
- * 右侧成员与工作区栏组件
2
+ * 右侧成员与工作区栏组件。工作区目录编辑态(草稿、文件浏览器)为栏内自有状态。
3
3
  * @module dsh-group-chat/client/components
4
4
  */
5
- import type { ReactNode } from 'react';
5
+ import { type ReactNode } from 'react';
6
6
  import { type ClientSnapshot, type SnapshotRole } from '../lib/model.ts';
7
7
  interface AsidePanelProps {
8
8
  snap: ClientSnapshot;
9
9
  group: ClientSnapshot['groups'][number];
10
10
  asideOpen: boolean;
11
- wsDraft: string | null;
12
- setWsDraft: (val: string | null) => void;
13
- fileBrowser: {
14
- open: boolean;
15
- loading: boolean;
16
- list: import('../../core/types.ts').BrowseResult | null;
17
- error: string;
18
- } | null;
19
- setFileBrowser: (val: {
20
- open: boolean;
21
- loading: boolean;
22
- list: import('../../core/types.ts').BrowseResult | null;
23
- error: string;
24
- } | null) => void;
25
11
  mutate: (args: Record<string, unknown>) => Promise<unknown>;
26
- openRoleEditor: (role: SnapshotRole | null) => Promise<void>;
27
- openBrowser: (path: string | undefined) => Promise<void>;
28
- selectCurrentDir: () => void;
12
+ openRoleEditor: (role: SnapshotRole | null) => void;
29
13
  }
30
14
  export declare function AsidePanel(props: AsidePanelProps): ReactNode;
31
15
  export {};
@@ -1,9 +1,15 @@
1
1
  /**
2
- * 左侧导航栏组件
2
+ * 左侧导航栏组件(群组 → 会话目录树;群组行与会话行共用重命名/删除三件套)。
3
3
  * @module dsh-group-chat/client/components
4
4
  */
5
5
  import type { ReactNode } from 'react';
6
6
  import { type ClientSnapshot } from '../lib/model.ts';
7
+ /** 目录树节点编辑态形状(群组/会话共用)。 */
8
+ type NodeEdit = {
9
+ kind: 'group' | 'session';
10
+ id: string;
11
+ value: string;
12
+ };
7
13
  interface NavPanelProps {
8
14
  snap: ClientSnapshot;
9
15
  search: string;
@@ -15,16 +21,8 @@ interface NavPanelProps {
15
21
  setGid: (val: string) => void;
16
22
  setSid: (val: string) => void;
17
23
  setPartsSel: (val: string[] | null) => void;
18
- renameDraft: {
19
- kind: 'group' | 'session';
20
- id: string;
21
- value: string;
22
- } | null;
23
- setRenameDraft: (val: {
24
- kind: 'group' | 'session';
25
- id: string;
26
- value: string;
27
- } | null) => void;
24
+ renameDraft: NodeEdit | null;
25
+ setRenameDraft: (val: NodeEdit | null) => void;
28
26
  confirmDel: {
29
27
  kind: 'group' | 'session';
30
28
  id: string;
@@ -7,11 +7,12 @@ import { type ModelsResponse, type RoleDraft } from '../lib/model.ts';
7
7
  export interface RoleDrawerProps {
8
8
  draft: RoleDraft;
9
9
  set: (draft: RoleDraft) => void;
10
+ /** 保存目标群组(upsertRole 落库)。 */
11
+ groupId: string;
10
12
  models: ModelsResponse | null;
11
13
  modelsError: string | null;
12
14
  onRetryModels: () => void;
13
- onSave: () => void;
15
+ mutate: (args: Record<string, unknown>) => Promise<unknown>;
14
16
  onCancel: () => void;
15
- formError: string;
16
17
  }
17
18
  export declare function RoleDrawer(props: RoleDrawerProps): ReactNode;
@@ -3,7 +3,7 @@
3
3
  * @module dsh-group-chat/client/ToolRow
4
4
  */
5
5
  import { type ReactNode } from 'react';
6
- import type { ToolCallView } from '../lib/model.ts';
6
+ import type { ToolCallRecord } from '../../core/types.ts';
7
7
  export declare function ToolRow(props: {
8
- c: ToolCallView;
8
+ c: ToolCallRecord;
9
9
  }): ReactNode;
@@ -47,26 +47,10 @@ export declare function useGroupChatState(): {
47
47
  setConfirmClear: import("react").Dispatch<import("react").SetStateAction<boolean>>;
48
48
  roleDraft: RoleDraft | null;
49
49
  setRoleDraft: import("react").Dispatch<import("react").SetStateAction<RoleDraft | null>>;
50
- roleFormError: string;
51
- setRoleFormError: import("react").Dispatch<import("react").SetStateAction<string>>;
52
50
  models: ModelsResponse | null;
53
51
  setModels: import("react").Dispatch<import("react").SetStateAction<ModelsResponse | null>>;
54
52
  modelsError: string | null;
55
53
  setModelsError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
56
- fileBrowser: {
57
- open: boolean;
58
- loading: boolean;
59
- list: import("../../core/types.ts").BrowseResult | null;
60
- error: string;
61
- } | null;
62
- setFileBrowser: import("react").Dispatch<import("react").SetStateAction<{
63
- open: boolean;
64
- loading: boolean;
65
- list: import("../../core/types.ts").BrowseResult | null;
66
- error: string;
67
- } | null>>;
68
- wsDraft: string | null;
69
- setWsDraft: import("react").Dispatch<import("react").SetStateAction<string | null>>;
70
54
  partsSel: string[] | null;
71
55
  setPartsSel: import("react").Dispatch<import("react").SetStateAction<string[] | null>>;
72
56
  rounds: number;
@@ -2,7 +2,7 @@
2
2
  * 浏览器半共享常量与小工具(纯函数,无 React 依赖)。
3
3
  * @module dsh-group-chat/client/model
4
4
  */
5
- import type { RoleRecord, Snapshot, ToolCallRecord } from '../../core/types.ts';
5
+ import type { Snapshot } from '../../core/types.ts';
6
6
  /** 会话列表状态派生与文案(core 纯函数,供 NavPanel 渲染)。 */
7
7
  export { sessStatus, SESS_STATUS_LABEL, type SessStatus } from '../../core/status.ts';
8
8
  /** 角色标识色调色板(与宿主半一致)。 */
@@ -72,9 +72,3 @@ export interface RoleDraft {
72
72
  }
73
73
  export declare function draftFromRole(role: SnapshotRole): RoleDraft;
74
74
  export declare function blankDraft(): RoleDraft;
75
- /** 工具调用行的展示参数。 */
76
- export type ToolCallView = ToolCallRecord;
77
- /** 草稿是否可作为角色落库(宿主侧再校验一次)。 */
78
- export declare function draftMissing(draft: RoleDraft): string;
79
- /** RoleRecord 兼容视图(快照角色行即其展示子集)。 */
80
- export type RoleView = RoleRecord;
@@ -13,14 +13,14 @@ export declare function execCommand(commandId: 'delete' | 'insertHTML' | 'insert
13
13
  * DIV/P 块前补换行(防粘贴残留的块级包裹)。
14
14
  */
15
15
  export declare function serializeInput(root: HTMLElement): string;
16
- /** @角色芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删)。 */
16
+ /** @角色芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删;fresh=插入后需营救选区,加 data-new 标记)。 */
17
17
  export declare function chipHtml(role: {
18
18
  id: string;
19
19
  name: string;
20
20
  color?: string;
21
- }): string;
22
- /** @文件芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删)。 */
23
- export declare function fileChipHtml(path: string, kind: 'file' | 'directory'): string;
21
+ }, fresh?: boolean): string;
22
+ /** @文件芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删;fresh 同 chipHtml)。 */
23
+ export declare function fileChipHtml(path: string, kind: 'file' | 'directory', fresh?: boolean): string;
24
24
  /**
25
25
  * 光标前的活跃 @token(角色或文件弹层触发判定)。
26
26
  *
@@ -7,3 +7,9 @@ import type { MessageRecord, RoleRecord } from './types.ts';
7
7
  export declare function messageJson(m: Partial<MessageRecord> | undefined): Record<string, unknown> | null;
8
8
  /** 角色 → 落盘 JSON(groupId 由目录归属,不再写入)。 */
9
9
  export declare function roleJson(r: Partial<RoleRecord>): Record<string, unknown>;
10
+ /**
11
+ * 宽容 JSON 提取(模型输出/供应商错误原文的统一解析入口):
12
+ * 取首个 { 至末个 } 的片段解析,仅接受普通对象;失败返回 null。
13
+ * 代码围栏剥离由调用方按需先行处理(供应商错误原文不剥)。
14
+ */
15
+ export declare function looseJson(raw: string): Record<string, unknown> | null;
@@ -128,7 +128,8 @@ export interface RunState {
128
128
  confirmSignal: {
129
129
  resolve: (allowed: boolean) => void;
130
130
  } | null;
131
- childProc: import('node:child_process').ChildProcess | null;
131
+ /** 正在执行的 run_command 的中止句柄(stop/dispose 时 abort,执行器 kill 进程)。 */
132
+ commandAbort: AbortController | null;
132
133
  /** 最近一次 run 的结束标记:会话列表「已完成/已出错」状态的数据源。 */
133
134
  finished: RunFinished | null;
134
135
  /** 原地重试时被覆盖的失败消息 id;普通 send 为 null。 */
@@ -193,78 +194,72 @@ export interface FileSearchResult {
193
194
  }[];
194
195
  error?: string;
195
196
  }
196
- /** 发到客户端的全量快照(wire 形态)。 */
197
+ /** 快照内的会话行(wire 形态:不含 hydrate 用的固定标记与折入水位)。 */
198
+ export type SnapshotSession = Omit<SessionRecord, 'namePinned' | 'topicPinned' | 'constraintsUpToSeq'>;
199
+ /** 快照内的消息行(wire 形态:思考全文与摘要不外发)。 */
200
+ export type SnapshotMessage = Omit<MessageRecord, 'reasoningFull' | 'thinkingSummary'>;
201
+ /** 发到客户端的全量快照(wire 形态;各表行由领域记录派生,字段增删由编译器同步)。 */
197
202
  export interface Snapshot {
198
203
  revision: number;
199
- run: {
200
- running: boolean;
201
- sessionId: string | null;
202
- currentRoleId: string | null;
203
- partial: string;
204
- partialReasoning: string;
205
- pendingConfirm: PendingConfirm | null;
206
- finished: RunFinished | null;
207
- replaceMessageId: string | null;
208
- };
204
+ run: Omit<RunState, 'stopping' | 'queue' | 'confirmSignal' | 'commandAbort'>;
209
205
  lastCreated: LastCreated | null;
210
- groups: {
211
- id: string;
212
- name: string;
213
- workspaceDir: string;
214
- permissionTier: PermissionTier;
215
- roleIds: string[];
216
- sessionIds: string[];
217
- }[];
218
- sessions: {
219
- id: string;
220
- groupId: string;
221
- name: string;
222
- topic: string;
223
- constraints?: SessionConstraint[];
224
- messageIds: string[];
225
- createdAt: number;
226
- }[];
227
- roles: {
228
- id: string;
229
- groupId: string;
230
- name: string;
231
- color?: string;
232
- persona: string;
233
- provider: string;
234
- model: string;
235
- temperature?: number;
236
- reasoningEffort?: string;
237
- enabled: boolean;
238
- thinking: boolean;
239
- }[];
240
- messages: {
241
- id: string;
242
- sessionId: string;
243
- seq: number;
244
- speaker: string;
245
- text: string;
246
- reasoning?: string;
247
- model?: string;
248
- error?: boolean;
249
- failedRoleId?: string;
250
- toolCalls?: ToolCallRecord[];
251
- ts: number;
252
- }[];
206
+ groups: GroupRecord[];
207
+ sessions: SnapshotSession[];
208
+ roles: RoleRecord[];
209
+ messages: SnapshotMessage[];
253
210
  error?: string;
254
211
  }
255
- /** mutate 动作的参数形态(op 分发见 host/service)。 */
256
- export interface MutateArgs {
257
- op: string;
258
- groupId?: string;
259
- sessionId?: string;
260
- roleId?: string;
261
- role?: Partial<RoleRecord>;
212
+ /** mutate 动作的参数形态(判别联合:按 op 收窄各分支字段;wire 上字段可缺,消费方各自守卫)。 */
213
+ export type MutateArgs = {
214
+ op: 'createGroup';
262
215
  name?: string;
216
+ } | {
217
+ op: 'renameGroup';
218
+ groupId: string;
219
+ name?: string;
220
+ } | {
221
+ op: 'deleteGroup';
222
+ groupId: string;
223
+ } | {
224
+ op: 'createSession';
225
+ groupId: string;
226
+ name?: string;
227
+ } | {
228
+ op: 'renameSession';
229
+ sessionId: string;
230
+ name?: string;
231
+ } | {
232
+ op: 'deleteSession';
233
+ sessionId: string;
234
+ } | {
235
+ op: 'setTopic';
236
+ sessionId: string;
263
237
  topic?: string;
238
+ } | {
239
+ op: 'upsertRole';
240
+ groupId: string;
241
+ role?: Partial<RoleRecord>;
242
+ } | {
243
+ op: 'deleteRole';
244
+ roleId: string;
245
+ } | {
246
+ op: 'setRoleEnabled';
247
+ roleId: string;
248
+ enabled?: boolean;
249
+ } | {
250
+ op: 'setWorkspaceDir';
251
+ groupId: string;
264
252
  path?: string;
253
+ } | {
254
+ op: 'setPermissionTier';
255
+ groupId: string;
265
256
  tier?: string;
266
- enabled?: boolean;
267
- }
257
+ } | {
258
+ op: 'ackFinish';
259
+ } | {
260
+ op: 'clearMessages';
261
+ sessionId: string;
262
+ };
268
263
  /** send 动作的参数形态。 */
269
264
  export interface SendArgs {
270
265
  sessionId: string;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 引擎共享小件:DSH 默认模型读取 + 发言人展示名(retitle / fold / conversation 共用)。
3
+ * @module dsh-group-chat/host/engine/defaults
4
+ */
5
+ import type { RoleRecord } from '../../core/types.ts';
6
+ import type { HostState } from '../state.ts';
7
+ /** DSH 默认模型(agentDefaultModel 服务缺位或未配置时返回 null,调用方静默跳过)。 */
8
+ export declare const defaultModel: (core: HostState) => {
9
+ provider: string;
10
+ model: string;
11
+ } | null;
12
+ /** 发言人展示名(roles 表内查角色名;user/system 固定文案,见 speakerLabel)。 */
13
+ export declare const speakerNameOf: (roles: Map<string, RoleRecord>, speaker: string) => string;
@@ -13,16 +13,14 @@ export interface Persistence {
13
13
  roles?: string | null;
14
14
  workspace?: string | null;
15
15
  }) => void;
16
- /** 同步 flush:写全部脏文件(dispose 最终落盘用)。 */
17
- flushNow: () => void;
18
16
  /** 删除群组/会话后摘除脏标记(对应文件已删/将删,flush 跳过)。 */
19
17
  dropDirty: (targets: {
20
18
  session?: string | null;
21
19
  roles?: string | null;
22
20
  workspace?: string | null;
23
21
  }) => void;
24
- /** dispose:同步最终 flush → 释放锁摘除句柄。 */
25
- release: () => void;
22
+ /** dispose:停止新调度 → 等待挂起 flush → 最终 flush 释放锁(异步)。 */
23
+ release: () => Promise<void>;
26
24
  }
27
25
  /**
28
26
  * 创建持久化面:构造即完成 store 初始化 + hydrate(v1 迁移 → 残留清理 →
@@ -32,7 +32,11 @@ export declare class Store {
32
32
  private acquireLock;
33
33
  release(): void;
34
34
  fsyncDir(dir: string): void;
35
- /** 原子写:tmp+fsync+rename;目录 fsync 由调用方按 flush 批量执行(每目录一次)。 */
35
+ /**
36
+ * 同步原子写(tmp+fsync+rename):仅供构造期 v1 迁移(migrateV1)与测试
37
+ * 直用;运行时 flush 走 persistence.ts 的 writeFileAtomic(异步、wx 独占
38
+ * 创建 + 随机后缀 + 符号链接安全,无 per-file fsync,对齐 DSH 基座标准)。
39
+ */
36
40
  atomicWrite(file: string, text: string): void;
37
41
  quarantine(file: string): void;
38
42
  /** 读 JSON;缺失返回 null;损坏则隔离后返回 null。 */
@@ -19,8 +19,8 @@ export interface GroupChatService {
19
19
  subscribePush(push: () => void): () => void;
20
20
  /** 设置停用时中止正在进行的群聊。 */
21
21
  stopAll(): void;
22
- /** 卸载/热重载:唤醒确认等待 + kill 子进程 → 同步最终 flush → 释放锁。 */
23
- dispose(): void;
22
+ /** 卸载/热重载:唤醒确认等待 + kill 子进程 → 异步最终 flush → 释放锁(cordis 会 await)。 */
23
+ dispose(): Promise<void>;
24
24
  }
25
25
  /**
26
26
  * 创建群聊宿主服务(装配各模块;持久化锁失败时降级内存态运行)。
@@ -27,6 +27,7 @@ export interface HostState {
27
27
  ctx: Context;
28
28
  llm: Context['llm'];
29
29
  fs: Context['fs'];
30
+ shell: Context['shell'];
30
31
  groups: Map<string, GroupRecord>;
31
32
  sessions: Map<string, SessionRecord>;
32
33
  roles: Map<string, RoleRecord>;
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * 工具执行(TOOLS.md §2:沙箱 / §3:确认闸门):read_file / list_dir /
3
3
  * run_command 三件套;realpath 硬边界 + 分隔符比较;run_command 按群组
4
- * 权限档位走逐条确认或直接执行。
4
+ * 权限档位走逐条确认或直接执行,经 `shell` 服务(ctx.shell 沙箱执行器)
5
+ * 以 per-call sandboxPolicy 收紧到群工作区。
5
6
  * @module dsh-group-chat/host/tools
6
7
  */
7
8
  import { TOOL_SCHEMAS } from '../../core/tools.ts';
@@ -14,6 +14,9 @@
14
14
  * (engine/fold.ts;立刻 idle、fire-and-forget,不产生消息)
15
15
  * - 群组工作区目录经 `fs` 服务读取(根下一层文本文件,最多 20 个),
16
16
  * 以「共享资料」块注入每个角色的 system 提示词;无独立笔记/文件清单
17
+ * - 工具执行(read_file/list_dir/run_command)见 docs/TOOLS.md:run_command
18
+ * 经 `shell` 服务(ctx.shell 沙箱执行器)执行,per-call sandboxPolicy
19
+ * 收紧到群工作区(workspace_write 档)或免受限(full_access 档)
17
20
  * - 经 `webServer` 暴露 HTTP API:
18
21
  * GET /api/group-chat/state 全量快照
19
22
  * POST /api/group-chat/action { kind: mutate|send|stop|confirmCommand|models|efforts|browse|fileSearch, ... }
@@ -4,6 +4,9 @@
4
4
  * Sourced from @deepseek-ai/dsh-file-reference/lib/types/grammar.js
5
5
  * Cannot import directly due to client bundle purity gate (dsh-client-bundle-purity).
6
6
  *
7
+ * 只保留输入侧的 @token 识别(activeAtToken);候选的文本化(formatFileMention)
8
+ * 未被本插件采用——目录钻入走「插入整颗芯片」语义,见 docs/DESIGN.md Composer 契约。
9
+ *
7
10
  * @module dsh-group-chat/shared
8
11
  */
9
12
  export interface AtToken {
@@ -14,10 +17,6 @@ export interface AtToken {
14
17
  /** Whether the token uses quoted form @"..." */
15
18
  quoted: boolean;
16
19
  }
17
- export interface FileCandidate {
18
- kind: 'file' | 'directory';
19
- path: string;
20
- }
21
20
  /**
22
21
  * Extract active @token at cursor position.
23
22
  * Matches @ at word boundary (line start or after whitespace).
@@ -27,11 +26,3 @@ export interface FileCandidate {
27
26
  * @returns Token or undefined if no active @token at cursor
28
27
  */
29
28
  export declare function activeAtToken(line: string, cursor: number): AtToken | undefined;
30
- /**
31
- * Format file/directory candidate as @mention text.
32
- *
33
- * @param candidate - File or directory to format
34
- * @param preserveQuote - Keep quotes even when unnecessary (for drill-down continuity)
35
- * @returns Formatted @path or undefined for invalid paths
36
- */
37
- export declare function formatFileMention(candidate: FileCandidate, preserveQuote: boolean): string | undefined;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@roaming-ai/dsh-group-chat",
3
3
  "description": "DSH 模型群聊:多模型角色群组对话面板。角色绑定不同 provider/model,群内共享对话记录;设置页支持启用/停用。",
4
- "version": "0.3.0",
4
+ "version": "0.3.2",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -30,6 +30,7 @@
30
30
  "README.md"
31
31
  ],
32
32
  "license": "MIT",
33
+ "author": "localSummer (https://github.com/localSummer)",
33
34
  "repository": {
34
35
  "type": "git",
35
36
  "url": "https://github.com/localSummer/dsh-group-chat"
@@ -55,6 +56,7 @@
55
56
  }
56
57
  },
57
58
  "dependencies": {
59
+ "@deepseek-ai/dsh-atomic-write": "0.1.5-rc.2",
58
60
  "@deepseek-ai/dsh-file-reference": "0.1.5-rc.2",
59
61
  "@deepseek-ai/dsh-file-reference-local": "0.1.5-rc.2",
60
62
  "schemastery": "^3.18.0"
@@ -78,6 +80,7 @@
78
80
  "@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.1",
79
81
  "@deepseek-ai/dsh-llm": "^0.1.5-rc.1",
80
82
  "@deepseek-ai/dsh-settings": "^0.1.5-rc.1",
83
+ "@deepseek-ai/dsh-shell": "^0.1.5-rc.1",
81
84
  "@deepseek-ai/dsh-workspace": "^0.1.5-rc.1",
82
85
  "@types/node": "^22.20.0",
83
86
  "@types/react": "~18.3.1",
@@ -41,16 +41,10 @@ export function GroupChatPanel(): ReactNode {
41
41
  setConfirmClear,
42
42
  roleDraft,
43
43
  setRoleDraft,
44
- roleFormError,
45
- setRoleFormError,
46
44
  models,
47
45
  setModels,
48
46
  modelsError,
49
47
  setModelsError,
50
- fileBrowser,
51
- setFileBrowser,
52
- wsDraft,
53
- setWsDraft,
54
48
  partsSel,
55
49
  setPartsSel,
56
50
  rounds,
@@ -127,13 +121,15 @@ export function GroupChatPanel(): ReactNode {
127
121
 
128
122
  const sendMsg = useCallback(async (): Promise<void> => {
129
123
  if (busyNow || !sess || sendingRef.current) return
124
+ // 空内容不可发送(按钮 disabled 之外,Enter 路径在此静默拦截)
125
+ if (!input.trim()) return
130
126
  sendingRef.current = true
131
127
  try {
132
- const parts = mentionedRoles.length ? mentionedRoles.map((r) => r.id) : participants
133
- if (!parts.length) {
134
- setErr(mentionedRoles.length ? '' : '请至少选择一个参与角色(或在消息中 @成员)')
135
- if (!mentionedRoles.length) return
128
+ if (!participants.length && !mentionedRoles.length) {
129
+ setErr('请至少选择一个参与角色(或在消息中 @成员)')
130
+ return
136
131
  }
132
+ const parts = mentionedRoles.length ? mentionedRoles.map((r) => r.id) : participants
137
133
  const res = await action({ kind: 'send', sessionId: sess.id, text: input, participantRoleIds: parts, rounds }) as ActionOk | null
138
134
  if (res && !res.ok && res.error) setErr(res.error)
139
135
  else if (res && res.ok) {
@@ -220,51 +216,14 @@ export function GroupChatPanel(): ReactNode {
220
216
 
221
217
  const openRoleEditor = async (role: SnapshotRole | null): Promise<void> => {
222
218
  setErr('')
223
- setRoleFormError('')
224
219
  if (!models || modelsError) await fetchModels()
225
220
  setRoleDraft(role ? draftFromRole(role) : blankDraft())
226
221
  }
227
222
 
228
- const saveRole = async (): Promise<void> => {
229
- if (!roleDraft) return
230
- if (!roleDraft.name.trim()) {
231
- setRoleFormError('角色名称不能为空')
232
- return
233
- }
234
- if (!roleDraft.provider || !roleDraft.model) {
235
- setRoleFormError('请选择角色绑定的模型')
236
- return
237
- }
238
- const res = await mutate({ op: 'upsertRole', groupId: group.id, role: roleDraft })
239
- if (res && res.ok && res.snapshot && !res.snapshot.error) {
240
- setRoleDraft(null)
241
- setRoleFormError('')
242
- }
243
- }
244
-
245
223
  const stopRun = async (): Promise<void> => {
246
224
  await action({ kind: 'stop', sessionId: sess!.id })
247
225
  }
248
226
 
249
- const openBrowser = async (path: string | undefined): Promise<void> => {
250
- setFileBrowser({ open: true, loading: true, list: null, error: '' })
251
- try {
252
- const res = await api.action({ kind: 'browse', path: path || '' }) as import('../core/types.ts').BrowseResult
253
- if (res && res.ok) setFileBrowser({ open: true, loading: false, list: res, error: '' })
254
- else setFileBrowser({ open: true, loading: false, list: null, error: (res && res.error) || '浏览失败' })
255
- } catch (e) {
256
- setFileBrowser({ open: true, loading: false, list: null, error: String((e && (e as Error).message) || e) })
257
- }
258
- }
259
-
260
- const selectCurrentDir = (): void => {
261
- if (!fileBrowser || !fileBrowser.list || !group) return
262
- const path = fileBrowser.list.path!
263
- void mutate({ op: 'setWorkspaceDir', groupId: group.id, path })
264
- setWsDraft(null)
265
- setFileBrowser(null)
266
- }
267
-
268
227
  return (
269
228
  <div className="dsgc-root">
270
229
  <NavPanel
@@ -338,14 +297,8 @@ export function GroupChatPanel(): ReactNode {
338
297
  snap={snap}
339
298
  group={group}
340
299
  asideOpen={asideOpen}
341
- wsDraft={wsDraft}
342
- setWsDraft={setWsDraft}
343
- fileBrowser={fileBrowser}
344
- setFileBrowser={setFileBrowser}
345
300
  mutate={mutate}
346
301
  openRoleEditor={openRoleEditor}
347
- openBrowser={openBrowser}
348
- selectCurrentDir={selectCurrentDir}
349
302
  />
350
303
  {sess && confirmClear
351
304
  ? (
@@ -386,12 +339,12 @@ export function GroupChatPanel(): ReactNode {
386
339
  <RoleDrawer
387
340
  draft={roleDraft}
388
341
  set={setRoleDraft}
342
+ groupId={group.id}
389
343
  models={models}
390
344
  modelsError={modelsError}
391
345
  onRetryModels={() => { void fetchModels() }}
392
- onSave={() => { void saveRole() }}
393
- onCancel={() => { setRoleDraft(null); setRoleFormError('') }}
394
- formError={roleFormError}
346
+ mutate={mutate}
347
+ onCancel={() => { setRoleDraft(null) }}
395
348
  />
396
349
  )
397
350
  : null}