@joekytc/dsh-swarm 0.1.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 (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +504 -0
  3. package/README.zh-CN.md +452 -0
  4. package/client/BoardCard.tsx +41 -0
  5. package/client/ConnectionBanner.tsx +16 -0
  6. package/client/KanbanBoard.tsx +168 -0
  7. package/client/KanbanTab.tsx +26 -0
  8. package/client/RenameModal.tsx +38 -0
  9. package/client/TaskDrawer.tsx +212 -0
  10. package/client/WorkflowRail.tsx +175 -0
  11. package/client/board-store.ts +196 -0
  12. package/client/css.d.ts +4 -0
  13. package/client/index.ts +31 -0
  14. package/client/kanban.css +653 -0
  15. package/client/useKanbanBoard.ts +7 -0
  16. package/client/workflow-model.ts +229 -0
  17. package/cordis.patch.yml +88 -0
  18. package/lib/client.js +1205 -0
  19. package/lib/config.d.ts +46 -0
  20. package/lib/config.js +43 -0
  21. package/lib/dispatcher/agent-runner.d.ts +23 -0
  22. package/lib/dispatcher/agent-runner.js +429 -0
  23. package/lib/dispatcher/chain-auditor.d.ts +47 -0
  24. package/lib/dispatcher/chain-auditor.js +194 -0
  25. package/lib/dispatcher/dispatcher.d.ts +50 -0
  26. package/lib/dispatcher/dispatcher.js +280 -0
  27. package/lib/dispatcher/event-waker.d.ts +13 -0
  28. package/lib/dispatcher/event-waker.js +24 -0
  29. package/lib/dispatcher/git-credentials.d.ts +33 -0
  30. package/lib/dispatcher/git-credentials.js +78 -0
  31. package/lib/dispatcher/merge-gate.d.ts +28 -0
  32. package/lib/dispatcher/merge-gate.js +74 -0
  33. package/lib/dispatcher/model-candidates.d.ts +13 -0
  34. package/lib/dispatcher/model-candidates.js +31 -0
  35. package/lib/dispatcher/session-events.d.ts +28 -0
  36. package/lib/dispatcher/session-events.js +33 -0
  37. package/lib/dispatcher/target-repo.d.ts +15 -0
  38. package/lib/dispatcher/target-repo.js +42 -0
  39. package/lib/dispatcher/v-orchestrator.d.ts +74 -0
  40. package/lib/dispatcher/v-orchestrator.js +452 -0
  41. package/lib/dispatcher/watchdog.d.ts +15 -0
  42. package/lib/dispatcher/watchdog.js +30 -0
  43. package/lib/dispatcher/workspace-attach.d.ts +40 -0
  44. package/lib/dispatcher/workspace-attach.js +112 -0
  45. package/lib/domain/delivery-contract.d.ts +18 -0
  46. package/lib/domain/delivery-contract.js +80 -0
  47. package/lib/domain/delivery-evidence.d.ts +12 -0
  48. package/lib/domain/delivery-evidence.js +41 -0
  49. package/lib/domain/event-store.d.ts +20 -0
  50. package/lib/domain/event-store.js +53 -0
  51. package/lib/domain/kanban-service.d.ts +94 -0
  52. package/lib/domain/kanban-service.js +430 -0
  53. package/lib/domain/permissions.d.ts +7 -0
  54. package/lib/domain/permissions.js +54 -0
  55. package/lib/domain/planning-checklist.d.ts +22 -0
  56. package/lib/domain/planning-checklist.js +81 -0
  57. package/lib/domain/prefetch-manifest.d.ts +21 -0
  58. package/lib/domain/prefetch-manifest.js +73 -0
  59. package/lib/domain/projection.d.ts +3 -0
  60. package/lib/domain/projection.js +165 -0
  61. package/lib/domain/review-evidence.d.ts +13 -0
  62. package/lib/domain/review-evidence.js +76 -0
  63. package/lib/domain/state-machine.d.ts +4 -0
  64. package/lib/domain/state-machine.js +32 -0
  65. package/lib/domain/task-parents.d.ts +17 -0
  66. package/lib/domain/task-parents.js +39 -0
  67. package/lib/domain/tdd-classify.d.ts +5 -0
  68. package/lib/domain/tdd-classify.js +20 -0
  69. package/lib/domain/types.d.ts +142 -0
  70. package/lib/domain/types.js +2 -0
  71. package/lib/index.d.ts +5 -0
  72. package/lib/index.js +56 -0
  73. package/lib/roles/preset-installer.d.ts +8 -0
  74. package/lib/roles/preset-installer.js +53 -0
  75. package/lib/roles/toolsets.d.ts +62 -0
  76. package/lib/roles/toolsets.js +332 -0
  77. package/lib/roles/wiki-worker.d.ts +20 -0
  78. package/lib/roles/wiki-worker.js +44 -0
  79. package/lib/routes/kanban-http.d.ts +6 -0
  80. package/lib/routes/kanban-http.js +159 -0
  81. package/lib/routes/kanban-sse.d.ts +8 -0
  82. package/lib/routes/kanban-sse.js +58 -0
  83. package/lib/routes/planning-driver.d.ts +16 -0
  84. package/lib/routes/planning-driver.js +52 -0
  85. package/lib/routes/prefix-router.d.ts +29 -0
  86. package/lib/routes/prefix-router.js +28 -0
  87. package/lib/services/kanban-provider.d.ts +16 -0
  88. package/lib/services/kanban-provider.js +14 -0
  89. package/lib/tools/kanban-tools.d.ts +11 -0
  90. package/lib/tools/kanban-tools.js +169 -0
  91. package/lib/tools/main-session-tools.d.ts +20 -0
  92. package/lib/tools/main-session-tools.js +170 -0
  93. package/lib/tools/planning-tools.d.ts +32 -0
  94. package/lib/tools/planning-tools.js +102 -0
  95. package/lib/tools/prefetch-tools.d.ts +5 -0
  96. package/lib/tools/prefetch-tools.js +59 -0
  97. package/lib/tools/spec-card-tools.d.ts +4 -0
  98. package/lib/tools/spec-card-tools.js +75 -0
  99. package/lib/tools/wiki-tools.d.ts +4 -0
  100. package/lib/tools/wiki-tools.js +57 -0
  101. package/lib/wiki/kb-linkage.d.ts +9 -0
  102. package/lib/wiki/kb-linkage.js +87 -0
  103. package/lib/wiki/page-path.d.ts +6 -0
  104. package/lib/wiki/page-path.js +28 -0
  105. package/lib/wiki/wiki-vault-client.d.ts +28 -0
  106. package/lib/wiki/wiki-vault-client.js +48 -0
  107. package/package.json +83 -0
  108. package/personas/kanban-d/agent.cordis.yml +157 -0
  109. package/personas/kanban-d/preset.yml +2 -0
  110. package/personas/kanban-dt/agent.cordis.yml +71 -0
  111. package/personas/kanban-dt/preset.yml +2 -0
  112. package/personas/kanban-p/agent.cordis.yml +66 -0
  113. package/personas/kanban-p/preset.yml +2 -0
  114. package/personas/kanban-pt/agent.cordis.yml +47 -0
  115. package/personas/kanban-pt/preset.yml +2 -0
  116. package/personas/kanban-v/agent.cordis.yml +47 -0
  117. package/personas/kanban-v/preset.yml +2 -0
  118. package/personas/kanban-w/agent.cordis.yml +47 -0
  119. package/personas/kanban-w/preset.yml +2 -0
  120. package/personas/persona-d.md +26 -0
  121. package/personas/persona-dt.md +18 -0
  122. package/personas/persona-p.md +13 -0
  123. package/personas/persona-pt.md +13 -0
  124. package/personas/persona-v.md +18 -0
  125. package/personas/persona-w.md +12 -0
@@ -0,0 +1,196 @@
1
+ import { applyTo } from '../src/domain/projection.js';
2
+ import type { BoardState, Chain, ChainAudit, Handoff, KanbanEvent, SpecCard, Task } from '../src/domain/types.js';
3
+
4
+ export interface BoardWire {
5
+ chains: Chain[];
6
+ tasks: Task[];
7
+ specCards: SpecCard[];
8
+ handoffs: Array<{ id: string } & Handoff>;
9
+ auditWarnings: Array<{ chainId: string } & ChainAudit>;
10
+ events: KanbanEvent[];
11
+ lastSeq: number;
12
+ }
13
+
14
+ export interface EventSourceLike {
15
+ onopen: ((event: Event) => void) | null;
16
+ onmessage: ((event: MessageEvent<string>) => void) | null;
17
+ onerror: ((event: Event) => void) | null;
18
+ close(): void;
19
+ }
20
+
21
+ export type BoardConnectionState = 'loading' | 'ready' | 'reconnecting' | 'error';
22
+
23
+ export interface ActionError {
24
+ taskId: string;
25
+ message: string;
26
+ }
27
+
28
+ export interface BoardClientSnapshot {
29
+ board: BoardState | null;
30
+ lastSeq: number;
31
+ connection: BoardConnectionState;
32
+ lastSuccessAt: number | null;
33
+ error: string | null;
34
+ actionError: ActionError | null;
35
+ }
36
+
37
+ export interface BoardStore {
38
+ start(): Promise<void>;
39
+ stop(): void;
40
+ retry(): Promise<void>;
41
+ subscribe(listener: () => void): () => void;
42
+ getSnapshot(): BoardClientSnapshot;
43
+ postAction(action: unknown): Promise<unknown>;
44
+ }
45
+
46
+ function hydrate(wire: BoardWire): BoardState {
47
+ return {
48
+ chains: new Map(wire.chains.map((value) => [value.id, value])),
49
+ tasks: new Map(wire.tasks.map((value) => [value.id, value])),
50
+ specCards: new Map(wire.specCards.map((value) => [value.id, value])),
51
+ handoffs: new Map(wire.handoffs.map(({ id, ...value }) => [id, value])),
52
+ auditWarnings: new Map(wire.auditWarnings.map(({ chainId, ...value }) => [chainId, value])),
53
+ events: wire.events,
54
+ };
55
+ }
56
+
57
+ export interface BoardStoreDeps {
58
+ fetchImpl?: typeof fetch;
59
+ eventSourceFactory?: (url: string) => EventSourceLike;
60
+ }
61
+
62
+ /** T32:乐观更新仅作用于状态类操作;comment 等无状态操作直接等待服务端事件收敛。 */
63
+ const OPTIMISTIC_STATUS: Partial<Record<string, Task['status']>> = {
64
+ block: 'blocked',
65
+ unblock: 'ready',
66
+ complete: 'done',
67
+ archive: 'archived',
68
+ retry: 'running',
69
+ };
70
+
71
+ function applyOptimistic(board: BoardState, taskId: string, status: Task['status']): BoardState {
72
+ const task = board.tasks.get(taskId);
73
+ if (!task || task.status === status) return board;
74
+ return { ...board, tasks: new Map(board.tasks).set(taskId, { ...task, status }) };
75
+ }
76
+
77
+ /** T24:浏览器外部 store。初始快照 + SSE 增量 + seq 去重/缺口重拉,无业务轮询。 */
78
+ export function createBoardStore(deps: BoardStoreDeps = {}): BoardStore {
79
+ const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
80
+ const eventSourceFactory = deps.eventSourceFactory ?? ((url: string) => new EventSource(url) as unknown as EventSourceLike);
81
+
82
+ let snapshot: BoardClientSnapshot = { board: null, lastSeq: -1, connection: 'loading', lastSuccessAt: null, error: null, actionError: null };
83
+ const listeners = new Set<() => void>();
84
+ let generation = 0;
85
+ let source: EventSourceLike | null = null;
86
+ let started = false;
87
+
88
+ const commit = (patch: Partial<BoardClientSnapshot>) => {
89
+ snapshot = { ...snapshot, ...patch };
90
+ for (const listener of [...listeners]) listener();
91
+ };
92
+
93
+ const resync = async (): Promise<void> => {
94
+ const gen = ++generation;
95
+ source?.close();
96
+ source = null;
97
+ commit({ connection: 'loading' });
98
+ try {
99
+ const res = await fetchImpl('/kanban/board');
100
+ if (!res.ok) throw new Error(`board snapshot failed: ${res.status}`);
101
+ const wire = (await res.json()) as BoardWire;
102
+ if (gen !== generation) return;
103
+ commit({
104
+ board: hydrate(wire),
105
+ lastSeq: wire.lastSeq,
106
+ connection: 'ready',
107
+ lastSuccessAt: Date.now(),
108
+ error: null,
109
+ });
110
+ installSource(gen, wire.lastSeq);
111
+ } catch (err) {
112
+ if (gen !== generation) return;
113
+ commit({ connection: 'error', error: String(err) });
114
+ }
115
+ };
116
+
117
+ const installSource = (gen: number, lastSeq: number): void => {
118
+ const next = eventSourceFactory(`/kanban/events?after=${lastSeq}`);
119
+ if (gen !== generation) {
120
+ next.close();
121
+ return;
122
+ }
123
+ source = next;
124
+ next.onopen = () => {
125
+ if (gen !== generation) return;
126
+ commit({ connection: 'ready' });
127
+ };
128
+ next.onerror = () => {
129
+ if (gen !== generation) return;
130
+ commit({ connection: 'reconnecting' });
131
+ };
132
+ next.onmessage = (message) => {
133
+ if (gen !== generation) return;
134
+ const event = JSON.parse(message.data) as KanbanEvent;
135
+ if (event.seq <= snapshot.lastSeq) return; // 去重
136
+ if (event.seq === snapshot.lastSeq + 1) {
137
+ const board = snapshot.board ? applyTo(snapshot.board, event) : snapshot.board;
138
+ if (board) commit({ board, lastSeq: event.seq });
139
+ return;
140
+ }
141
+ void resync(); // seq 缺口:关闭旧流并重拉完整快照
142
+ };
143
+ };
144
+
145
+ return {
146
+ async start() {
147
+ if (started) return;
148
+ started = true;
149
+ await resync();
150
+ },
151
+ stop() {
152
+ generation += 1;
153
+ source?.close();
154
+ source = null;
155
+ started = false;
156
+ },
157
+ async retry() {
158
+ if (!started) return;
159
+ await resync();
160
+ },
161
+ subscribe(listener) {
162
+ listeners.add(listener);
163
+ return () => { listeners.delete(listener); };
164
+ },
165
+ getSnapshot() {
166
+ return snapshot;
167
+ },
168
+ async postAction(action: unknown) {
169
+ const a = action as { type?: string; taskId?: string };
170
+ const type = a.type ?? '';
171
+ const taskId = a.taskId ?? '';
172
+ const optimistic = taskId ? OPTIMISTIC_STATUS[type] : undefined;
173
+ const before = optimistic ? snapshot.board : undefined;
174
+ const beforeSeq = snapshot.lastSeq;
175
+ if (before && optimistic) commit({ actionError: null, board: applyOptimistic(before, taskId, optimistic) });
176
+ try {
177
+ const res = await fetchImpl('/kanban/action', {
178
+ method: 'POST',
179
+ headers: { 'Content-Type': 'application/json' },
180
+ body: JSON.stringify(action),
181
+ });
182
+ const data = (await res.json()) as { ok?: boolean; error?: string };
183
+ if (!res.ok || data.ok === false) throw new Error(data.error ?? 'action failed');
184
+ commit({ actionError: null });
185
+ return data;
186
+ } catch (err) {
187
+ const patch: Partial<BoardClientSnapshot> = { actionError: { taskId, message: String(err) } };
188
+ if (before) patch.board = before; // T32:回滚到操作前快照切片
189
+ commit(patch);
190
+ // 操作在途期间到达的 SSE 事件不能因回滚丢失:seq 有推进则重拉权威快照
191
+ if (snapshot.lastSeq !== beforeSeq) void resync();
192
+ throw err;
193
+ }
194
+ },
195
+ };
196
+ }
@@ -0,0 +1,4 @@
1
+ declare module '*.css' {
2
+ const css: string;
3
+ export default css;
4
+ }
@@ -0,0 +1,31 @@
1
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
2
+ import { KanbanTab } from './KanbanTab.js';
3
+ import css from './kanban.css';
4
+
5
+ export const name = 'kanban-board';
6
+
7
+ /** 所需 client 服务(cordis fiber inject——loader 把模块导出当作对象插件传入)。 */
8
+ export const inject = ['slots'];
9
+
10
+ /** 浏览器半入口(roster 行 id: kanban-board):把看板挂到 conversation.view(会话中心 tab,additive)。
11
+ * 对齐 DSH 原生注册:对话(id=chat, order=0) → 轨迹(id=trajectory, order=10) → 看板(id=kanban, order=20)。
12
+ * 数据桥为节点端 /kanban HTTP 路由;ui-conversation 包仅运行时声明 slot(dsh.client.inject 排依赖序),浏览器半不直接 import。 */
13
+ export function apply(ctx: ClientContext): (() => void) | void {
14
+ let style: HTMLStyleElement | null = null;
15
+ if (typeof document !== 'undefined') {
16
+ style = document.head.querySelector<HTMLStyleElement>('style[data-dsh-swarm]');
17
+ if (!style) {
18
+ style = document.createElement('style');
19
+ style.setAttribute('data-dsh-swarm', '');
20
+ style.textContent = css;
21
+ document.head.appendChild(style);
22
+ }
23
+ }
24
+ ctx.slots.inject('conversation.view' as never, () =>
25
+ ctx.slots.register(
26
+ { name: 'conversation.view', id: 'kanban', order: 20, label: '看板' } as never,
27
+ KanbanTab as never,
28
+ ),
29
+ );
30
+ return () => { if (style) style.remove(); };
31
+ }