@joekytc/dsh-swarm 0.3.12 → 0.5.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/client/index.ts CHANGED
@@ -15,14 +15,15 @@ declare module '@deepseek-ai/cordis' {
15
15
  }
16
16
  import { ConfigSection } from './ConfigSection.js';
17
17
  import { SWARM_CONFIG_NS } from './config-store.js';
18
- import { setSessionsService } from './session-bridge.js';
18
+ import { setSessionsService, setSessionNavigator } from './session-bridge.js';
19
19
  import css from './kanban.css';
20
20
  import configCss from './config.css';
21
21
 
22
22
  export const name = 'kanban-board';
23
23
 
24
- /** 所需 client 服务(cordis fiber inject——loader 把模块导出当作对象插件传入)。 */
25
- export const inject = ['slots', 'sessions'];
24
+ /** 所需 client 服务(cordis fiber inject——loader 把模块导出当作对象插件传入)。
25
+ * uiWorkspace:0.1.7 起宿主内置的视图所有者导航(会话跳转新接缝),fiber inject 会等待其就绪。 */
26
+ export const inject = ['slots', 'sessions', 'uiWorkspace'];
26
27
 
27
28
  /** 浏览器半入口(roster 行 id: kanban-board):把看板挂到 conversation.view(会话中心 tab,additive)。
28
29
  * 对齐 DSH 原生注册:对话(id=chat, order=0) → 轨迹(id=trajectory, order=10) → 看板(id=kanban, order=20)。
@@ -32,6 +33,9 @@ export function apply(ctx: Context): (() => void) | void {
32
33
  // cast:@deepseek-ai/dsh-session(服务端包)也 merge 了 cordis Context.sessions: SessionStore,
33
34
  // 类型面被其覆盖;浏览器半运行时注入的实为 dsh-api-session-controller 的 ISessions。
34
35
  setSessionsService(ctx.sessions as unknown as ISessions);
36
+ // 会话跳转导航:0.2.0+ 宿主提供 uiWorkspace(视图所有者),但注册晚于 client apply——
37
+ // 只传惰性 getter(点击时解析+try/catch 降级),apply 期绝不触碰属性,防 cordis 未就绪 throw 炸掉入口注册。
38
+ setSessionNavigator(() => (ctx as { uiWorkspace?: { openSession(target: string): void } }).uiWorkspace ?? null);
35
39
  let style: HTMLStyleElement | null = null;
36
40
  let configStyle: HTMLStyleElement | null = null;
37
41
  if (typeof document !== 'undefined') {
@@ -62,5 +66,5 @@ export function apply(ctx: Context): (() => void) | void {
62
66
  ConfigSection as never,
63
67
  ),
64
68
  );
65
- return () => { if (style) style.remove(); if (configStyle) configStyle.remove(); setSessionsService(null); };
69
+ return () => { if (style) style.remove(); if (configStyle) configStyle.remove(); setSessionsService(null); setSessionNavigator(null); };
66
70
  }
@@ -32,8 +32,32 @@ export function useSessionIds(): ReadonlySet<string> {
32
32
  );
33
33
  }
34
34
 
35
- /** 应用内跳转到指定会话(宿主 ISessions.open 对不在列表的 id fail loud,调用方须先经 useSessionIds 门控)。 */
35
+ /** 新宿主导航接缝:dsh 0.2.0 起 ISessions.open 移除,会话导航归视图所有者(uiWorkspace.openSession)。
36
+ * 宿主以 getter 惰性供给(uiWorkspace 注册晚于 client apply,apply 期属性访问会在 cordis 上 throw);
37
+ * 0.1.7 底线宿主无此服务,getter 返回 null 走旧 open 转发。 */
38
+ let navigator: (() => { openSession(target: string): void } | null) | null = null;
39
+
40
+ export function setSessionNavigator(next: (() => { openSession(target: string): void } | null) | null): void {
41
+ navigator = next;
42
+ }
43
+
44
+ /** 应用内跳转到指定会话(调用方须先经 useSessionIds 门控,宿主对不在列表的 id fail loud)。 */
36
45
  export function openSession(id: string): void {
46
+ // 优先 0.2.0+ 视图所有者导航;解析失败视同服务缺席;旧宿主退回 ISessions.open 转发;两者皆缺则响亮报错。
47
+ if (navigator) {
48
+ let viaWorkspace: { openSession(target: string): void } | null = null;
49
+ try {
50
+ viaWorkspace = navigator();
51
+ } catch {
52
+ viaWorkspace = null;
53
+ }
54
+ if (viaWorkspace) {
55
+ viaWorkspace.openSession(id);
56
+ return;
57
+ }
58
+ }
37
59
  if (!service) throw new Error('sessions service unavailable');
38
- service.open(id as never);
60
+ const open = (service as { open?: (target: string) => void }).open;
61
+ if (!open) throw new Error('host exposes neither uiWorkspace nor sessions.open; session jump unavailable');
62
+ open(id);
39
63
  }
package/lib/client.js CHANGED
@@ -560,9 +560,27 @@ window.__ModuleLoader__.load({
560
560
  () => EMPTY
561
561
  );
562
562
  }
563
+ var navigator = null;
564
+ function setSessionNavigator(next) {
565
+ navigator = next;
566
+ }
563
567
  function openSession(id) {
568
+ if (navigator) {
569
+ let viaWorkspace = null;
570
+ try {
571
+ viaWorkspace = navigator();
572
+ } catch {
573
+ viaWorkspace = null;
574
+ }
575
+ if (viaWorkspace) {
576
+ viaWorkspace.openSession(id);
577
+ return;
578
+ }
579
+ }
564
580
  if (!service) throw new Error("sessions service unavailable");
565
- service.open(id);
581
+ const open = service.open;
582
+ if (!open) throw new Error("host exposes neither uiWorkspace nor sessions.open; session jump unavailable");
583
+ open(id);
566
584
  }
567
585
 
568
586
  // client/BoardCard.tsx
@@ -2897,9 +2915,10 @@ window.__ModuleLoader__.load({
2897
2915
 
2898
2916
  // client/index.ts
2899
2917
  var name = "kanban-board";
2900
- var inject = ["slots", "sessions"];
2918
+ var inject = ["slots", "sessions", "uiWorkspace"];
2901
2919
  function apply(ctx) {
2902
2920
  setSessionsService(ctx.sessions);
2921
+ setSessionNavigator(() => ctx.uiWorkspace ?? null);
2903
2922
  let style = null;
2904
2923
  let configStyle = null;
2905
2924
  if (typeof document !== "undefined") {
@@ -2936,6 +2955,7 @@ window.__ModuleLoader__.load({
2936
2955
  if (style) style.remove();
2937
2956
  if (configStyle) configStyle.remove();
2938
2957
  setSessionsService(null);
2958
+ setSessionNavigator(null);
2939
2959
  };
2940
2960
  }
2941
2961
 
@@ -1,5 +1,6 @@
1
1
  import { SessionId } from '@deepseek-ai/dsh-session';
2
2
  import { writeFileSync } from 'node:fs';
3
+ import { randomUUID } from 'node:crypto';
3
4
  import { join } from 'node:path';
4
5
  import { probeOcr } from '../services/ocr-cli.js';
5
6
  import { installRoleTools, buildReadOnlyWriteGuard, buildDTWriteGuard, buildPlanWriteGuard, buildKbWriteGuard, registerDtTaskChain, unregisterDtTaskChain } from '../roles/toolsets.js';
@@ -266,7 +267,7 @@ ${task.body}`);
266
267
  (task.attempts > 0 || state.events.some((e) => e.taskId === taskId && e.kind === 'task/claimed'));
267
268
  let agent;
268
269
  let context = '';
269
- const setup = async (agentCtx) => {
270
+ const setup = async (agentCtx, agent) => {
270
271
  // 思考等级强制(waterfall):宿主 selection 无 create-options 覆盖层(dsh-host-apiproxy 的
271
272
  // selectionFor 不消费 agentOptions),AgentOptions 也仅有 provider/model/maxTokens——agentOptions
272
273
  // 里的 reasoningEffort 不被宿主消费,新建角色会话思考等级会落回宿主默认。改走 DSH agent/request
@@ -283,7 +284,8 @@ ${task.body}`);
283
284
  // P/W/D = full access(跨目录读:P 读仓库/外部实证、W 读计划、D 执行);
284
285
  // 但 P 挂 plan 写护栏、W 挂只读护栏(写边界由工具级强制,防"改动源码",不靠 prompt 软约束);
285
286
  // PT/DT/V → workspace-write(评审/编排最小权限,PT/DT 只读护栏由 ToolGuard 独立保证)。
286
- const session = agentCtx.agent?.session;
287
+ // approval/sandbox 种子事件经 AgentSetup 第二参的 agent 显式直写(0.2.0 起 ctx.agent 挂载已移除)。
288
+ const session = agent?.session;
287
289
  session?.append?.('approval/policy', { policy: 'never', source: 'delegation' });
288
290
  const fullAccess = isDExecute || task.assignee === 'p' || task.assignee === 'w';
289
291
  session?.append?.('sandbox/mode', fullAccess ? { mode: 'danger-full-access', source: 'delegation' } : { mode: 'workspace-write', source: 'delegation' });
@@ -318,16 +320,13 @@ ${task.body}`);
318
320
  // PT/DT 是 workspace-write(sandbox_permissions 扩权到天花板是合法功能,剥了破坏官方能力);
319
321
  // W 虽 fullAccess 但只读护栏全拒 write,shadow 无意义(最小挂载面,V 无执行工具不涉及)。
320
322
  if (task.assignee === 'p' || isDExecute) {
321
- installCleanFsTools(agentCtx, agentCtx.agent);
323
+ installCleanFsTools(agentCtx, agent);
322
324
  }
323
325
  // 组合标记——角色工具面成功组合后,把 { role, taskId } 记到进程内 WeakMap(键=Agent 实例)。
324
- // 宿主探明:setup 收到的 agentCtx.agent 与发布后 agents.get(id) 返回的是同一 Agent 实例
325
- // (dsh-agent types/index.d.ts:38 `agent?: Agent` 安装为 Agent.ctx own property),
326
- // 故此处键入的实例即 resumeOrReuse 里 agents.get 命中的实例。agentCtx.agent 缺失(宿主变体)时
327
- // 不写标记 → live 复用走工具面校验路径(kanban_complete 可见性),能力无损。
328
- const liveAgent = agentCtx.agent;
329
- if (liveAgent)
330
- markRoleComposition(liveAgent, { role: task.assignee, taskId: task.id });
326
+ // AgentSetup 第二参收到的 Agent 与发布后 agents.get(id) 返回的是同一实例,故此处键入的实例
327
+ // 即 resumeOrReuse 里 agents.get 命中的实例(live 复用走标记匹配,能力无损)。
328
+ if (agent)
329
+ markRoleComposition(agent, { role: task.assignee, taskId: task.id });
331
330
  // 只读评审角色(PT/DT)注册 ToolGuard:拦截 tracked source 写入 / git mutation / 含写标记 bash。
332
331
  // 以 dsh-tools 类型为准:tools.guard(execution => reason|undefined),execution.name/arguments 为实际字段。
333
332
  if (task.assignee === 'pt' || task.assignee === 'dt') {
@@ -492,7 +491,7 @@ ${task.body}`);
492
491
  const attachId = task.resumeSessionId ?? `kbn-${task.id}`;
493
492
  await attachSessionToWorkspace(this.ctx, attachId, sessionCwd, 'task ' + task.id + ' ' + task.assignee + '/' + task.mode);
494
493
  try {
495
- agent.followup({ content: [{ type: 'text', text: context }], source: { kind: 'user' } });
494
+ agent.followup({ id: randomUUID(), content: [{ type: 'text', text: context }], source: { kind: 'user' } });
496
495
  console.error('[dsh-swarm][debug] runner followup sent ' + taskId);
497
496
  await agent.whenIdle();
498
497
  console.error('[dsh-swarm][debug] runner whenIdle resolved ' + taskId);
@@ -510,7 +509,7 @@ ${task.body}`);
510
509
  : (sessionLike.events ?? []);
511
510
  if (turnEvents.filter((e) => (Number(e?.seq) || 0) > eventsBase).some(isGuardSynthesizedReply)) {
512
511
  console.error('[dsh-swarm][debug] runner guard-synthesized reply detected (from-cache/from-security-guard) ' + taskId + ' — auto followup retry once');
513
- agent.followup({ content: [{ type: 'text', text: '继续未完成的任务:上一条回复是网关安全护栏/语义缓存误拦的合成拒答,并非你的真实结论。请忽略它,从中断处继续执行任务,直至按协议收敛终态(kanban_complete 或 kanban_block)。' }], source: { kind: 'user' } });
512
+ agent.followup({ id: randomUUID(), content: [{ type: 'text', text: '继续未完成的任务:上一条回复是网关安全护栏/语义缓存误拦的合成拒答,并非你的真实结论。请忽略它,从中断处继续执行任务,直至按协议收敛终态(kanban_complete 或 kanban_block)。' }], source: { kind: 'user' } });
514
513
  console.error('[dsh-swarm][debug] runner guard-retry followup sent ' + taskId);
515
514
  await agent.whenIdle();
516
515
  console.error('[dsh-swarm][debug] runner guard-retry whenIdle resolved ' + taskId);
@@ -650,7 +649,8 @@ ${task.body}`);
650
649
  // GUI 默认组合 incarnation:缺 kanban_complete → 重跑 setup 幂等补挂后复用。
651
650
  // setup 对 live ctx 的各步均可加:effort waterfall/approval+sandbox append(known 事件类型、
652
651
  // latest-wins)/preset mount(bindings 覆盖)/角色工具(此前为默认组合,无同名冲突)/护栏。
653
- await opts.setup(liveCtx);
652
+ // 双参签名(0.2.0):repair 重跑 setup 时显式传 live agent 实例(标记随修复自愈)。
653
+ await opts.setup(liveCtx, live);
654
654
  if (toolsSvc.get('kanban_complete', live))
655
655
  return live; // 修复后必须验证到位,防静默半修复
656
656
  throw new Error('live session composition repair failed: kanban_complete still missing after re-setup (session ' + sessionId + ')');
@@ -11,8 +11,9 @@ import type { AuditEvidence, Task } from '../domain/types.js';
11
11
  * a. 作用域收窄:仅扫描会话工作区(session.header.cwd)位于本链发起工作区
12
12
  * (Chain.workspaceDir = /plan: 主 agent 所在工作空间)内的会话,
13
13
  * 排除其他项目的主会话(如 评估 项目里调试 dsh-swarm 的会话);
14
- * b. 行为判定:run_code 按实际派发子调用(tool/code-dispatch-start / tool/code-dispatch,
15
- * 经 rootCallId 关联外层调用)判定是否真的发生写,而非把 run_code 一律视为写;
14
+ * b. 行为判定:run_code 按实际派发子调用(新旧宿主双名 tool/code-dispatch[-start] /
15
+ * tool/ptc-dispatch[-start],经 rootCallId 关联外层调用)判定是否真的发生写,而非把
16
+ * run_code 一律视为写;
16
17
  * c. 只读排除:bash 命令 / 兜底 code 字符串仅当含写操作标记(BASH_WRITE_RE)且
17
18
  * 含 workspacesRoot 路径时才计为写证据;纯只读排查(ls/cat/glob/read/grep)不产生证据。
18
19
  * 2. 产物归属核对(fallback,机械可测):枚举 workspaces/<chainId>/ 下条目;
@@ -1,7 +1,7 @@
1
1
  // src/dispatcher/chain-auditor.ts
2
2
  import { readdirSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
- import { eventType, toolArgs, toolName } from './session-events.js';
4
+ import { eventType, isRunCodeDispatchEvent, toolArgs, toolName } from './session-events.js';
5
5
  import { isPathInside } from './target-repo.js';
6
6
  import { headerPresetOf } from './session-preset.js';
7
7
  /** 直接写工具(无条件视为写能力;只读工具如 read/glob/grep 不算)。 */
@@ -113,7 +113,7 @@ function sessionWriteEvidence(id, workspacesRoot, events) {
113
113
  }
114
114
  }
115
115
  }
116
- else if (t === 'tool/code-dispatch-start' || t === 'tool/code-dispatch') {
116
+ else if (isRunCodeDispatchEvent(ev)) {
117
117
  const rootCallId = String(dataField(ev, 'rootCallId') ?? '');
118
118
  const name = String(dataField(ev, 'name') ?? '');
119
119
  if (!rootCallId || !name)
@@ -18,6 +18,13 @@ export declare function eventType(e: {
18
18
  export declare function toolName(e: unknown): string | undefined;
19
19
  /** 取工具调用参数对象(兼容 JSON 字符串 / 对象两种落盘形态)。 */
20
20
  export declare function toolArgs(e: unknown): Record<string, unknown>;
21
+ /** run_code 派发子调用事件名(start 与完成两种)新旧宿主双名集合:
22
+ * 旧宿主会话落盘为 tool/code-dispatch-start / tool/code-dispatch;
23
+ * 宿主 V3+ 会话格式改名为 tool/ptc-dispatch-start / tool/ptc-dispatch。
24
+ * 消费方(链审计器按派发子调用判定 run_code 实际写行为)双名等价命中,勿散落硬编码。 */
25
+ export declare const RUN_CODE_DISPATCH_EVENT_TYPES: ReadonlySet<string>;
26
+ /** 该事件是否为 run_code 派发子调用事件(start 或完成,新旧事件名皆认)。 */
27
+ export declare function isRunCodeDispatchEvent(e: unknown): boolean;
21
28
  /** 取 assistant 消息的网关缓存回放标记(replayState.response.responseModel,如 'from-cache')。
22
29
  * 非 assistant/message 或无标记返回 null。2026-09-04:远程网关回复缓存整包回放时由 SSE
23
30
  * id/model 字段透传(dsh 纯透传),插件侧据此识别缓存劫持零产出。兼容落盘(data.message)
@@ -35,6 +35,21 @@ export function toolArgs(e) {
35
35
  }
36
36
  return {};
37
37
  }
38
+ /** run_code 派发子调用事件名(start 与完成两种)新旧宿主双名集合:
39
+ * 旧宿主会话落盘为 tool/code-dispatch-start / tool/code-dispatch;
40
+ * 宿主 V3+ 会话格式改名为 tool/ptc-dispatch-start / tool/ptc-dispatch。
41
+ * 消费方(链审计器按派发子调用判定 run_code 实际写行为)双名等价命中,勿散落硬编码。 */
42
+ export const RUN_CODE_DISPATCH_EVENT_TYPES = new Set([
43
+ 'tool/code-dispatch-start',
44
+ 'tool/code-dispatch',
45
+ 'tool/ptc-dispatch-start',
46
+ 'tool/ptc-dispatch',
47
+ ]);
48
+ /** 该事件是否为 run_code 派发子调用事件(start 或完成,新旧事件名皆认)。 */
49
+ export function isRunCodeDispatchEvent(e) {
50
+ const t = eventType(e);
51
+ return t !== undefined && RUN_CODE_DISPATCH_EVENT_TYPES.has(t);
52
+ }
38
53
  /** 取 assistant 消息的网关缓存回放标记(replayState.response.responseModel,如 'from-cache')。
39
54
  * 非 assistant/message 或无标记返回 null。2026-09-04:远程网关回复缓存整包回放时由 SSE
40
55
  * id/model 字段透传(dsh 纯透传),插件侧据此识别缓存劫持零产出。兼容落盘(data.message)
@@ -34,7 +34,9 @@ export declare function buildPhaseInstruction(phase: VPhase, ctx: {
34
34
  taskId?: string;
35
35
  }, kbMode: 'remote' | 'local'): string;
36
36
  interface AgentLike {
37
+ /** 宿主 UserMessage 要求 identified(id 必填):缺 id 的消息会被 v4 会话校验拒读。 */
37
38
  followup(msg: {
39
+ id: string;
38
40
  content: {
39
41
  type: string;
40
42
  text: string;
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { installRoleTools } from '../roles/toolsets.js';
2
3
  import { mountOrRecompose } from '../roles/preset-installer.js';
3
4
  import { resolveTaskParents } from '../domain/task-parents.js';
@@ -266,7 +267,7 @@ export class VOrchestrator {
266
267
  'gave_up 任务说明链路已停止,建议查看对应 [blocked-final] 证据链(block 时间线 + 复核/评论时间线 + 最终原因),给出终态解释。',
267
268
  '规则:只评论、不建卡、不改任务状态;已有 [blocked-review] 评论的任务不要重复评论。',
268
269
  ].join('\n\n');
269
- agent.followup({ content: [{ type: 'text', text: context }], source: { kind: 'user' } });
270
+ agent.followup({ id: randomUUID(), content: [{ type: 'text', text: context }], source: { kind: 'user' } });
270
271
  await agent.whenIdle();
271
272
  return; // 本轮 V 唯一动作是阻塞复核,不再推进阶段
272
273
  }
@@ -411,7 +412,7 @@ export class VOrchestrator {
411
412
  let turnError = null;
412
413
  try {
413
414
  agent = await this.getVAgent(orch);
414
- agent.followup({ content: [{ type: 'text', text: context }], source: { kind: 'user' } });
415
+ agent.followup({ id: randomUUID(), content: [{ type: 'text', text: context }], source: { kind: 'user' } });
415
416
  await agent.whenIdle();
416
417
  }
417
418
  catch (err) {
@@ -619,7 +620,7 @@ export class VOrchestrator {
619
620
  // 与 agent-runner.ts 的 installRoleTools 用法一致。
620
621
  // V 后台编排会话与 P/W/D 一致,显式设置 approval=never + sandbox=workspace-write,
621
622
  // 避免在无 preset 装配时因默认审批策略在后台无应答者而挂起(卡死调度器首轮 tick)。
622
- const setup = async (agentCtx) => {
623
+ const setup = async (agentCtx, agent) => {
623
624
  // 思考等级强制(waterfall):与 agent-runner 同缺陷——宿主 selection 无 create-options 覆盖层,
624
625
  // agentOptions.reasoningEffort 不被消费,V 编排会话思考等级会落回宿主默认。走 DSH agent/request
625
626
  // waterfall 逐请求强制 'high'(宿主 installModelSelection 同机制),作用域仅本 V 会话。
@@ -629,7 +630,8 @@ export class VOrchestrator {
629
630
  const resolved = await next();
630
631
  return { ...resolved, reasoningEffort: 'high' };
631
632
  });
632
- const session = agentCtx.agent?.session;
633
+ // approval/sandbox 种子事件经 AgentSetup 第二参的 agent 显式直写(0.2.0 起 ctx.agent 挂载已移除)。
634
+ const session = agent?.session;
633
635
  session?.append?.('approval/policy', { policy: 'never', source: 'delegation' });
634
636
  session?.append?.('sandbox/mode', { mode: 'workspace-write', source: 'delegation' });
635
637
  // 对齐(2026-08-17):V=butler·orchestrator 零执行能力——先挂 kanban-v 裁剪 preset
@@ -653,9 +655,9 @@ export class VOrchestrator {
653
655
  }
654
656
  await installRoleTools(agentCtx, 'v', { kanban: this.kanban, wiki: this.wiki });
655
657
  // setup 完整成功(mount + 工具面)后写身份标记——live 复用校验依据(同 agent-runner 组合标记机制)。
656
- const vAgent = agentCtx.agent;
657
- if (vAgent && typeof vAgent === 'object')
658
- vSessionCompositions.set(vAgent, V_SESSION_PRESET_ID);
658
+ // 键 = AgentSetup 第二参的 Agent 实例(与发布后 agents.get(id) 返回同一实例)。
659
+ if (agent)
660
+ vSessionCompositions.set(agent, V_SESSION_PRESET_ID);
659
661
  };
660
662
  // 模型候选链:V 会话 create/resume 按 primary→fallbacks 静默切换;
661
663
  // V 无任务卡可 block——全候选不可用抛最后错误(wakeV 调用方按既有错误路径处理)。
package/lib/index.js CHANGED
@@ -36,10 +36,13 @@ export function apply(ctx, config) {
36
36
  const configProvider = new ConfigProvider(ctx, config, storageDir);
37
37
  // KanbanProvider 持 ConfigProvider 引用,kb_url base 经 getter 热读取(配置面板改后无需重建)。
38
38
  const provider = new KanbanProvider(ctx, config, configProvider);
39
- // 把包内角色裁剪 preset 组合安装到 $DSH_HOME/.agent-presets/(真实 API 下唯一可发现的自定义根)。
40
- const installed = installRolePresets();
39
+ // 双路安装角色裁剪 preset:目录写 $DSH_HOME/.agent-presets/(0.1.7-rc.x 宿主发现根)+
40
+ // 运行时注册 agentPresets.register(0.2.0 宿主唯一生效路径)。注册等服务就绪后完成,
41
+ // 由 installer 如实记录「目录写 / 运行时注册」各自清单,不假阳性。
42
+ void installRolePresets(ctx).catch((err) => {
43
+ console.warn('[dsh-swarm] role preset install error: ' + String(err));
44
+ });
41
45
  installLlWikiSkill(); // 尽力而为:失败仅告警不阻断
42
- console.info('[dsh-swarm] role presets installed: ' + (installed.length ? installed.join(',') : 'none'));
43
46
  // IM 投递(企微,2026-09-07 评审决议):W3 收尾/链阻塞时经 dsh-im 投群。
44
47
  // imDelivery.enabled=false(默认)时事件处理器零开销早退;dshIm 服务缺失显式降级留痕。
45
48
  const disposeImDelivery = wireImDelivery(ctx, provider.service, configProvider);
@@ -1,11 +1,56 @@
1
1
  /** 包内组合目录(随包分发)。src/roles/ 与 lib/roles/ 深度一致,均经 ../../ 回到包根。 */
2
2
  export declare function packagePresetsDir(): string;
3
- /** $DSH_HOME/.agent-presets(agent-presets 服务 includeUserRoot 派生根;DSH_HOME 缺省 ~/.dsh)。 */
3
+ /** $DSH_HOME/.agent-presets(0.1.7-rc.x 宿主用户预设根;DSH_HOME 缺省 ~/.dsh)。 */
4
4
  export declare function userPresetsRoot(): string;
5
- /** 安装角色裁剪 preset 到用户预设根;返回成功安装的 preset id 列表(空=无随包组合)。
6
- * 尽力而为:单 preset 写失败(用户预设根不可写等)仅告警不抛出,
7
- * 后续 agentPresets.mount('kanban-<role>') 失败由 runner 降级日志兜底。 */
8
- export declare function installRolePresets(): string[];
5
+ /** 单条 preset 组合行(官方 PresetDefinition.plugins 行的结构面:cordis entry list)。 */
6
+ export interface PresetPluginRow {
7
+ readonly id?: string;
8
+ readonly name: string;
9
+ readonly config?: unknown;
10
+ readonly disabled?: unknown;
11
+ }
12
+ /** 注册给宿主 registry 的 preset 定义(官方 PresetDefinition 的结构面)。 */
13
+ export interface PresetDefinitionLike {
14
+ readonly id: string;
15
+ readonly name?: string;
16
+ readonly description?: string;
17
+ readonly order?: number;
18
+ readonly plugins: readonly PresetPluginRow[];
19
+ }
20
+ /** 宿主 agentPresets registry 的结构面(本插件只消费 register)。 */
21
+ export interface PresetRegistryLike {
22
+ register(definition: PresetDefinitionLike): Promise<() => Promise<void>>;
23
+ }
24
+ /** 安装期 ctx 的最小面(只需可选 get)。 */
25
+ export interface PresetCtxLike {
26
+ get?(name: string): unknown;
27
+ }
28
+ /** 解析包内单个 preset 的组合与元数据为宿主 registry 定义;组合缺失返回 undefined。 */
29
+ export declare function readPresetDefinition(pkgDir: string, id: string): PresetDefinitionLike | undefined;
30
+ /** 路径二:运行时注册全部包内 preset;返回注册成功/失败 id 列表与成功项的撤销句柄(组合缺失的 id 不计)。 */
31
+ export declare function registerRolePresets(registry: PresetRegistryLike, ids: readonly string[], pkgDir?: string): Promise<{
32
+ registered: string[];
33
+ failed: string[];
34
+ disposers: Array<() => Promise<void>>;
35
+ }>;
36
+ /** 安装结果(如实区分两条路径,不假阳性)。 */
37
+ export interface RolePresetInstallReport {
38
+ /** 目录写成功的 preset id(路径一)。 */
39
+ dirWritten: string[];
40
+ /** 运行时注册成功的 preset id(路径二;服务不可用时为空)。 */
41
+ runtimeRegistered: string[];
42
+ /** 运行时注册失败的 preset id。 */
43
+ runtimeFailed: string[];
44
+ /** 路径二实际形态:runtime=已注册;unavailable=宿主无 agentPresets 服务(仅目录写生效)。 */
45
+ runtimeMode: 'runtime' | 'unavailable';
46
+ }
47
+ /**
48
+ * 双路安装入口。目录写同步完成并即时留痕;运行时注册等服务就绪后完成并留痕。
49
+ * 返回前目录写已落盘;注册部分异步收敛(fire-and-forget 调用方须 catch)。
50
+ */
51
+ export declare function installRolePresets(ctx: PresetCtxLike, opts?: {
52
+ registerWaitMs?: number;
53
+ }): Promise<RolePresetInstallReport>;
9
54
  /** 角色 preset 挂载的容器面(dsh-agent-presets roster 的最小结构类型)。 */
10
55
  export interface PresetMountLike {
11
56
  mount(ctx: unknown, id: string): Promise<unknown>;
@@ -1,36 +1,103 @@
1
1
  // src/roles/preset-installer.ts
2
- import { copyFileSync, existsSync, mkdirSync } from 'node:fs';
2
+ import { copyFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { JSON_SCHEMA, load as loadYaml, Type } from 'js-yaml';
6
7
  /**
7
- * 角色裁剪 preset 的运行时安装(2026-08-15 取舍记录):
8
- * - 组合文件随包分发(personas/kanban-{v,p,w,d}/agent.cordis.yml,随 npm 包分发)。
9
- * kanban-v 于 2026-08-17 补充:V=butler·orchestrator 零执行能力,组合仅 persona+instructions。
10
- * - 官方 dsh-agent-presets 的 roots 在 web 启动的 composeProfile 中被强制覆盖为
11
- * 官方 shipped root 单一路径(profile-boot 内 SHIPPED_PRESET_ROOT 硬编码),
12
- * 因此 cordis.patch.yml 追加 roots 指向包内目录在真实 API 下无效;
13
- * 唯一可发现的自定义根是 includeUserRoot 派生的 `$DSH_HOME/.agent-presets`。
14
- * - 故插件 apply 时把包内组合复制到 `$DSH_HOME/.agent-presets/<id>/`,
15
- * agentPresets.mount(agentCtx, 'kanban-v'|'kanban-p'|'kanban-w'|'kanban-d') 即可解析。
16
- * - 幂等:重复安装覆盖同名文件;缺组合文件(未随包)则跳过并让 runner 降级日志。
8
+ * 角色裁剪 preset 的双路安装:
9
+ *
10
+ * 路径一 · 目录写(0.1.7-rc.x 宿主的底线):把包内组合复制到 `$DSH_HOME/.agent-presets/<id>/`,
11
+ * 老宿主的 agent-presets 发现逻辑扫该目录完成装载。0.2.0 宿主不再扫描任何目录,此路不影响新宿主。
12
+ *
13
+ * 路径二 · 运行时注册(0.2.0 起的唯一生效路径):0.2.0 的 dsh-agent-preset-registry 是纯运行时
14
+ * 服务,preset 定义只经 `agentPresets.register(definition)` 进入(官方 dsh-agent-preset 插件
15
+ * 即在 Service.init 中 yield register(config)),无任何目录发现。本插件经 ctx 取 agentPresets
16
+ * 服务(未就绪则短轮询等待),把与目录写同一份 yml 解析出的定义注册进去,两个路径语义同源。
17
+ *
18
+ * 幂等:目录写覆盖同名文件;运行时注册遇宿主 "Duplicate agent preset" 视为已注册成功。
19
+ * 尽力而为:单 preset 目录写/注册失败仅告警不阻断插件启动,后续挂载失败由 runner 降级日志兜底。
17
20
  */
18
21
  const PRESET_IDS = ['kanban-v', 'kanban-p', 'kanban-w', 'kanban-d', 'kanban-pt', 'kanban-dt', 'swarm'];
22
+ /** 等待 agentPresets 服务就绪的超时(宿主组合启动期服务装配可能晚于插件 apply)。 */
23
+ const REGISTER_WAIT_MS = 60000;
19
24
  /** 包内组合目录(随包分发)。src/roles/ 与 lib/roles/ 深度一致,均经 ../../ 回到包根。 */
20
25
  export function packagePresetsDir() {
21
26
  return fileURLToPath(new URL('../../personas/', import.meta.url));
22
27
  }
23
- /** $DSH_HOME/.agent-presets(agent-presets 服务 includeUserRoot 派生根;DSH_HOME 缺省 ~/.dsh)。 */
28
+ /** $DSH_HOME/.agent-presets(0.1.7-rc.x 宿主用户预设根;DSH_HOME 缺省 ~/.dsh)。 */
24
29
  export function userPresetsRoot() {
25
30
  return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), '.agent-presets');
26
31
  }
27
- /** 安装角色裁剪 preset 到用户预设根;返回成功安装的 preset id 列表(空=无随包组合)。
28
- * 尽力而为:单 preset 写失败(用户预设根不可写等)仅告警不抛出,
29
- * 后续 agentPresets.mount('kanban-<role>') 失败由 runner 降级日志兜底。 */
30
- export function installRolePresets() {
32
+ // 宿主 entry-list YAML 方言的镜像(@deepseek-ai/cordis-plugin-include 的 entryListSchema):
33
+ // `!!js` 标量解析为 { __jsExpr } 表达式节点,由宿主 Loader 在行激活时求值(组合内
34
+ // `disabled: !!js process.platform === 'win32'` 等行依赖此节点形态,禁在解析期求值)。
35
+ const JS_EXPR_TAG = new Type('tag:yaml.org,2002:js', {
36
+ kind: 'scalar',
37
+ resolve: (data) => typeof data === 'string',
38
+ construct: (data) => ({ __jsExpr: data }),
39
+ });
40
+ const ENTRY_LIST_SCHEMA = JSON_SCHEMA.extend(JS_EXPR_TAG);
41
+ /** 镜像宿主 registry 的入口列表形状校验:顶层为带 name 的行数组(组行递归其 config)。 */
42
+ function entryListProblem(rows, at = '') {
43
+ if (!Array.isArray(rows))
44
+ return at === '' ? 'composition must be a top-level list of plugin rows' : `group ${at} must hold a list of plugin rows`;
45
+ for (const [index, row] of rows.entries()) {
46
+ const label = at === '' ? `row ${String(index + 1)}` : `${at} row ${String(index + 1)}`;
47
+ if (typeof row !== 'object' || row === null || Array.isArray(row))
48
+ return `${label} is not a plugin row`;
49
+ const name = row.name;
50
+ if (typeof name !== 'string' || name === '')
51
+ return `${label} names no plugin (a "name" string is required)`;
52
+ const config = row.config;
53
+ if (row.group === true && config !== undefined) {
54
+ const nested = entryListProblem(config, label);
55
+ if (nested !== undefined)
56
+ return nested;
57
+ }
58
+ }
59
+ return undefined;
60
+ }
61
+ /** 解析包内单个 preset 的组合与元数据为宿主 registry 定义;组合缺失返回 undefined。 */
62
+ export function readPresetDefinition(pkgDir, id) {
63
+ const srcFile = join(pkgDir, id, 'agent.cordis.yml');
64
+ if (!existsSync(srcFile))
65
+ return undefined;
66
+ let rows;
67
+ let meta;
68
+ try {
69
+ rows = loadYaml(readFileSync(srcFile, 'utf8'), { schema: ENTRY_LIST_SCHEMA });
70
+ const metaFile = join(pkgDir, id, 'preset.yml');
71
+ meta = existsSync(metaFile)
72
+ ? loadYaml(readFileSync(metaFile, 'utf8'), { schema: JSON_SCHEMA })
73
+ : {};
74
+ }
75
+ catch (err) {
76
+ console.warn('[dsh-swarm] role preset parse failed ' + id + ': ' + String(err));
77
+ return undefined;
78
+ }
79
+ const problem = entryListProblem(rows);
80
+ if (problem !== undefined) {
81
+ console.warn('[dsh-swarm] role preset composition invalid ' + id + ': ' + problem);
82
+ return undefined;
83
+ }
84
+ const definition = {
85
+ id,
86
+ plugins: rows,
87
+ };
88
+ if (typeof meta.name === 'string')
89
+ definition.name = meta.name;
90
+ if (typeof meta.description === 'string')
91
+ definition.description = meta.description;
92
+ if (typeof meta.order === 'number')
93
+ definition.order = meta.order;
94
+ return definition;
95
+ }
96
+ /** 路径一:目录写(0.1.7-rc.x 宿主发现根)。返回成功写入的 preset id 列表。 */
97
+ function writeUserDirPresets() {
31
98
  const src = packagePresetsDir();
32
99
  const dstRoot = userPresetsRoot();
33
- const installed = [];
100
+ const written = [];
34
101
  for (const id of PRESET_IDS) {
35
102
  const srcFile = join(src, id, 'agent.cordis.yml');
36
103
  if (!existsSync(srcFile))
@@ -42,14 +109,81 @@ export function installRolePresets() {
42
109
  const meta = join(src, id, 'preset.yml');
43
110
  if (existsSync(meta))
44
111
  copyFileSync(meta, join(dstDir, 'preset.yml'));
45
- installed.push(id);
112
+ written.push(id);
46
113
  }
47
114
  catch (err) {
48
115
  // 尽力而为:写入失败(如沙箱/权限拒绝)仅告警,不阻断插件启动
49
- console.warn('[dsh-swarm] role preset install skipped ' + id + ' -> ' + dstRoot + ': ' + String(err));
116
+ console.warn('[dsh-swarm] role preset dir write skipped ' + id + ' -> ' + dstRoot + ': ' + String(err));
50
117
  }
51
118
  }
52
- return installed;
119
+ return written;
120
+ }
121
+ /** 路径二:运行时注册全部包内 preset;返回注册成功/失败 id 列表与成功项的撤销句柄(组合缺失的 id 不计)。 */
122
+ export async function registerRolePresets(registry, ids, pkgDir = packagePresetsDir()) {
123
+ const registered = [];
124
+ const failed = [];
125
+ const disposers = [];
126
+ for (const id of ids) {
127
+ const definition = readPresetDefinition(pkgDir, id);
128
+ if (!definition)
129
+ continue;
130
+ try {
131
+ disposers.push(await registry.register(definition));
132
+ registered.push(id);
133
+ }
134
+ catch (err) {
135
+ // 插件重 apply 时定义已在 registry:与首次注册语义等价,按成功计
136
+ if (String(err).includes('Duplicate agent preset')) {
137
+ registered.push(id);
138
+ continue;
139
+ }
140
+ failed.push(id);
141
+ console.warn('[dsh-swarm] role preset runtime register failed ' + id + ': ' + String(err));
142
+ }
143
+ }
144
+ return { registered, failed, disposers };
145
+ }
146
+ /** 短轮询等待宿主 agentPresets 服务就绪;超时返回 undefined(降级仅目录写)。 */
147
+ async function waitForPresets(ctx, timeoutMs) {
148
+ const started = Date.now();
149
+ return new Promise((resolve) => {
150
+ const poll = setInterval(() => {
151
+ const found = ctx.get?.('agentPresets');
152
+ if (found) {
153
+ clearInterval(poll);
154
+ resolve(found);
155
+ }
156
+ else if (Date.now() - started >= timeoutMs) {
157
+ clearInterval(poll);
158
+ resolve(undefined);
159
+ }
160
+ }, 500);
161
+ });
162
+ }
163
+ /**
164
+ * 双路安装入口。目录写同步完成并即时留痕;运行时注册等服务就绪后完成并留痕。
165
+ * 返回前目录写已落盘;注册部分异步收敛(fire-and-forget 调用方须 catch)。
166
+ */
167
+ export async function installRolePresets(ctx, opts = {}) {
168
+ const dirWritten = writeUserDirPresets();
169
+ console.info('[dsh-swarm] role preset dir write: ' + (dirWritten.length ? dirWritten.join(',') : 'none'));
170
+ const registry = await waitForPresets(ctx, opts.registerWaitMs ?? REGISTER_WAIT_MS);
171
+ if (!registry) {
172
+ console.warn('[dsh-swarm] role presets installed: dir-write=' + (dirWritten.join(',') || 'none')
173
+ + ' runtime-register=none (agentPresets service unavailable; directory write only)');
174
+ return { dirWritten, runtimeRegistered: [], runtimeFailed: [], runtimeMode: 'unavailable' };
175
+ }
176
+ const { registered, failed, disposers } = await registerRolePresets(registry, PRESET_IDS);
177
+ console.info('[dsh-swarm] role presets installed: dir-write=' + (dirWritten.join(',') || 'none')
178
+ + ' runtime-register=' + (registered.join(',') || 'none')
179
+ + (failed.length ? ' failed=' + failed.join(',') : ''));
180
+ // 插件卸载/重载时撤销注册,避免宿主 registry 留下失效定义(官方 register 契约:声明方持有 disposer)。
181
+ ctx.on?.('dispose', () => {
182
+ for (const dispose of disposers.splice(0))
183
+ void dispose().catch(() => { });
184
+ });
185
+ const report = { dirWritten, runtimeRegistered: registered, runtimeFailed: failed, runtimeMode: 'runtime' };
186
+ return report;
53
187
  }
54
188
  /**
55
189
  * preset 挂载收敛入口(2026-09-21 事故修复):宿主 dsh-scope 的 scope key 绑定在进程
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joekytc/dsh-swarm",
3
- "version": "0.3.12",
3
+ "version": "0.5.0",
4
4
  "description": "A governed swarm of six specialist DSH agents (orchestrator, planner, knowledge-base bridge, developer and two reviewers) that turns a requirement into a strict phase pipeline with machine-verified delivery evidence, review-gated merges, a full audit-log event stream and a live kanban tab; design inspired by the Hermes Agent kanban",
5
5
  "license": "MIT",
6
6
  "author": "joekytc",
@@ -33,7 +33,8 @@
33
33
  "client": {
34
34
  "platform": "web",
35
35
  "inject": [
36
- "@deepseek-ai/dsh-client-ui-conversation"
36
+ "@deepseek-ai/dsh-client-ui-conversation",
37
+ "@deepseek-ai/dsh-client-ui-workspace"
37
38
  ]
38
39
  }
39
40
  },
@@ -58,27 +59,32 @@
58
59
  "test": "vitest run",
59
60
  "build:client": "node scripts/build-client.mjs"
60
61
  },
62
+ "dependencies": {
63
+ "js-yaml": "^4.3.2"
64
+ },
61
65
  "peerDependencies": {
62
66
  "@deepseek-ai/cordis": "^4.0.1",
63
- "@deepseek-ai/dsh-agent": "^0.1.2-rc.1",
64
- "@deepseek-ai/dsh-api-session-controller": "^0.1.2-rc.1",
65
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-rc.1",
66
- "@deepseek-ai/dsh-credentials": "^0.1.2-rc.1",
67
- "@deepseek-ai/dsh-persona": "^0.1.2-rc.1",
68
- "@deepseek-ai/dsh-session": "^0.1.2-rc.1",
69
- "@deepseek-ai/dsh-tool-bash": "^0.1.2-rc.1",
70
- "@deepseek-ai/dsh-tool-fs": "^0.1.2-rc.1",
71
- "@deepseek-ai/dsh-tool-fs-search": "^0.1.2-rc.1",
72
- "@deepseek-ai/dsh-tools": "^0.1.2-rc.1",
73
- "@deepseek-ai/dsh-util-values": "^0.1.2-rc.1",
67
+ "@deepseek-ai/dsh-agent": "~0.1.7-rc.2 || ^0.2.0-rc.2",
68
+ "@deepseek-ai/dsh-api-session-controller": "~0.1.7-rc.2 || ^0.2.0-rc.2",
69
+ "@deepseek-ai/dsh-client-ui-conversation": "~0.1.7-rc.2 || ^0.2.0-rc.2",
70
+ "@deepseek-ai/dsh-credentials": "~0.1.7-rc.2 || ^0.2.0-rc.2",
71
+ "@deepseek-ai/dsh-persona": "~0.1.7-rc.2 || ^0.2.0-rc.2",
72
+ "@deepseek-ai/dsh-session": "~0.1.7-rc.2 || ^0.2.0-rc.2",
73
+ "@deepseek-ai/dsh-tool-bash": "~0.1.7-rc.2 || ^0.2.0-rc.2",
74
+ "@deepseek-ai/dsh-tool-fs": "~0.1.7-rc.2 || ^0.2.0-rc.2",
75
+ "@deepseek-ai/dsh-tool-fs-search": "~0.1.7-rc.2 || ^0.2.0-rc.2",
76
+ "@deepseek-ai/dsh-tools": "~0.1.7-rc.2 || ^0.2.0-rc.2",
77
+ "@deepseek-ai/dsh-util-values": "~0.1.7-rc.2 || ^0.2.0-rc.2",
74
78
  "@deepseek-ai/schemastery": "^3.18.1"
75
79
  },
76
80
  "devDependencies": {
77
- "@deepseek-ai/dsh-client-store": "^0.1.2-rc.1",
78
- "@deepseek-ai/dsh-client-ui-layout": "^0.1.2-rc.1",
79
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-rc.1",
81
+ "@deepseek-ai/cordis-plugin-include": "^1.0.9",
82
+ "@deepseek-ai/dsh-client-store": "^0.2.0-rc.2",
83
+ "@deepseek-ai/dsh-client-ui-layout": "^0.2.0-rc.2",
84
+ "@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.2",
80
85
  "@testing-library/dom": "^10.4.0",
81
86
  "@testing-library/react": "^16.1.0",
87
+ "@types/js-yaml": "^4.0.9",
82
88
  "@types/node": "^22.0.0",
83
89
  "@types/react": "^18.3.0",
84
90
  "@types/react-dom": "^18.3.0",
@@ -12,7 +12,7 @@
12
12
  - id: persona
13
13
  name: '@deepseek-ai/dsh-persona'
14
14
  config:
15
- text: >-
15
+ prefix: >-
16
16
  You are the fullstack development agent (the ONLY executor) powered by the {{model}} model.
17
17
  Your working directory is {{cwd}}. You implement and test the aligned spec; you are
18
18
  the sole accountable executor and may delegate implementation subtasks. Rules:
@@ -134,12 +134,14 @@
134
134
  config:
135
135
  provider: spawn
136
136
  toolName: subagent
137
+ maxDepth: 3
137
138
 
138
139
  - id: tool-subagent-fork
139
140
  name: '@deepseek-ai/dsh-tool-subagent'
140
141
  config:
141
142
  provider: fork
142
143
  toolName: subagent_fork
144
+ maxDepth: 3
143
145
 
144
146
  - id: tool-subagent-control
145
147
  name: '@deepseek-ai/dsh-tool-subagent-control'
@@ -11,7 +11,7 @@
11
11
  - id: persona
12
12
  name: '@deepseek-ai/dsh-persona'
13
13
  config:
14
- text: >-
14
+ prefix: >-
15
15
  You are the implementation verifier + reviewer agent (read-only) powered by the {{model}}
16
16
  model. Your working directory is {{cwd}}. You empirically verify D's implementation and
17
17
  review it — you never modify source and you never execute writes. Rules:
@@ -95,3 +95,4 @@
95
95
  config:
96
96
  provider: spawn
97
97
  toolName: subagent
98
+ maxDepth: 3
@@ -15,7 +15,7 @@
15
15
  - id: persona
16
16
  name: '@deepseek-ai/dsh-persona'
17
17
  config:
18
- text: >-
18
+ prefix: >-
19
19
  You are the planning agent (planner) powered by the {{model}} model. Your working
20
20
  directory is {{cwd}}. You produce the implementation plan only — you are NOT the
21
21
  executor. When injected repo facts are insufficient, you may do READ-ONLY self-inspection
@@ -9,7 +9,7 @@
9
9
  - id: persona
10
10
  name: '@deepseek-ai/dsh-persona'
11
11
  config:
12
- text: >-
12
+ prefix: >-
13
13
  You are the plan-review agent (read-only) powered by the {{model}} model. Your working
14
14
  directory is {{cwd}}. You review P's plan deliverable only — you never modify any artifact
15
15
  and you never execute code. Rules:
@@ -12,7 +12,7 @@
12
12
  - id: persona
13
13
  name: '@deepseek-ai/dsh-persona'
14
14
  config:
15
- text: >-
15
+ prefix: >-
16
16
  You are the orchestration agent (butler) powered by the {{model}} model. Your working
17
17
  directory is {{cwd}}. You only decompose, dispatch and summarize — you are NEVER the executor.
18
18
  Rules:
@@ -12,7 +12,7 @@
12
12
  - id: persona
13
13
  name: '@deepseek-ai/dsh-persona'
14
14
  config:
15
- text: >-
15
+ prefix: >-
16
16
  You are the knowledge officer (Knowledge Officer) powered by the {{model}} model. Your
17
17
  working directory is {{cwd}}. You curate the requirement knowledge base (KB) in one of two
18
18
  modes (the active mode and the local KB root, when local, are stated in your task context);
@@ -6,7 +6,7 @@
6
6
  - id: persona
7
7
  name: '@deepseek-ai/dsh-persona'
8
8
  config:
9
- text: >-
9
+ prefix: >-
10
10
  你是蜂群模式的领队。本模式下你负责把「要动手开发的需求」转化为一条多角色工作流链并跟进到交付;其余对话正常应答。
11
11
 
12
12
  ## 分流判定(每轮先判)