@william2000/dsh-nova-ui-task-board 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 (50) hide show
  1. package/README.md +86 -0
  2. package/cordis.patch.yml +13 -0
  3. package/lib/client.js +5641 -0
  4. package/lib/client.js.map +1 -0
  5. package/lib/index.js +4873 -0
  6. package/lib/types/client/NovaTaskBoardSettingsCard.d.ts +17 -0
  7. package/lib/types/client/PlaceholderBoard.d.ts +9 -0
  8. package/lib/types/client/apply-guard.d.ts +25 -0
  9. package/lib/types/client/board/ConfirmDialog.d.ts +12 -0
  10. package/lib/types/client/board/ConfirmTaskDialog.d.ts +8 -0
  11. package/lib/types/client/board/ConvertTaskModal.d.ts +17 -0
  12. package/lib/types/client/board/NewTaskModal.d.ts +6 -0
  13. package/lib/types/client/board/RequirementSplitModal.d.ts +14 -0
  14. package/lib/types/client/board/SplitConfirmDialog.d.ts +9 -0
  15. package/lib/types/client/board/TagEditor.d.ts +6 -0
  16. package/lib/types/client/board/TaskBoard.d.ts +9 -0
  17. package/lib/types/client/board/TaskCard.d.ts +52 -0
  18. package/lib/types/client/board/TaskDetail.d.ts +7 -0
  19. package/lib/types/client/board/status-key.d.ts +8 -0
  20. package/lib/types/client/board-controller.d.ts +274 -0
  21. package/lib/types/client/board-mount.d.ts +10 -0
  22. package/lib/types/client/chat-integration.d.ts +99 -0
  23. package/lib/types/client/drag-utils.d.ts +78 -0
  24. package/lib/types/client/filter.d.ts +42 -0
  25. package/lib/types/client/grouping.d.ts +54 -0
  26. package/lib/types/client/host-api.d.ts +62 -0
  27. package/lib/types/client/index.d.ts +46 -0
  28. package/lib/types/client/legacy-store.d.ts +28 -0
  29. package/lib/types/client/locales.d.ts +202 -0
  30. package/lib/types/client/schedule-presets.d.ts +20 -0
  31. package/lib/types/client/sidebar-entry-core.d.ts +54 -0
  32. package/lib/types/client/sidebar-entry.d.ts +19 -0
  33. package/lib/types/core/context.d.ts +139 -0
  34. package/lib/types/core/cron.d.ts +54 -0
  35. package/lib/types/core/migrate.d.ts +39 -0
  36. package/lib/types/core/model.d.ts +446 -0
  37. package/lib/types/core/recovery.d.ts +25 -0
  38. package/lib/types/core/split.d.ts +81 -0
  39. package/lib/types/core/transitions.d.ts +436 -0
  40. package/lib/types/dsh-home.d.ts +21 -0
  41. package/lib/types/host-automation.d.ts +59 -0
  42. package/lib/types/host-ledger.d.ts +146 -0
  43. package/lib/types/host-routes.d.ts +65 -0
  44. package/lib/types/host-runner.d.ts +177 -0
  45. package/lib/types/host-service.d.ts +291 -0
  46. package/lib/types/index.d.ts +90 -0
  47. package/lib/types/loopback.d.ts +23 -0
  48. package/lib/types/mount-once.d.ts +9 -0
  49. package/lib/types/protocol.d.ts +210 -0
  50. package/package.json +89 -0
@@ -0,0 +1,291 @@
1
+ /**
2
+ * 任务看板协议服务层(DSH-REQ-001 §8 架构「Host(控制面)」)。
3
+ *
4
+ * T003 交付控制面的串行应用骨架:浏览器提交 `{requestId, action}` → 幂等判定
5
+ * (复用 T002 HostLedger 的 applyRequest + recentRequests 持久化)→ 状态机
6
+ * 守卫(core/transitions.ts)→ 原子落账(revision+1)→ 返回全量 snapshot。
7
+ * T005 接入真实执行(§13):`run/rerun` action 经状态机开执行记录后由
8
+ * HostExecutionRunner 启动独立 DSH 会话;5s 轮询会话列表按 turn 结算
9
+ * (§13.3);Host 重启后带 sessionId 的 running 执行由轮询继续观察结算。
10
+ * T006 接入 cron 周期定时(§9.4/§13.1):30s tick 扫描到期任务并触发执行
11
+ * (浏览器关闭也生效)——启动/恢复首个 tick 跳过错过的触发点(不补跑)、
12
+ * 到期且 enabled → 复用 T005 的 launch 入口开启执行、同任务 running 中到期
13
+ * 只滚动到下一匹配点不并发;`set-schedule` action(§11.2)设定/修改/关闭
14
+ * cron(nextRunAt/lastTriggeredAt 为 Host 独占字段)。
15
+ * T007 接入一次性计划(§9.4/§11.2 set-one-shot/§13.5):同一 30s tick 同时
16
+ * 扫描 one-shot——`runAt ≤ now && firedAt 缺省` 即触发一次并写 firedAt(触发
17
+ * 即消费,绝不重复);触发时任务 running → 不并发不排队、本次仍标记消费;
18
+ * 错过(停机/睡眠/长暂停,恢复首 tick)→ 跳过不补跑、写 firedAt 展示「已过期
19
+ * /已跳过」;`set-one-shot` 设定/修改/清除(runAt 缺省或 0 = 取消),与 cron
20
+ * 互斥(D6)。
21
+ * T009 接入列内排序与跨列拖拽(§9.2/§10.2/§11.2/§12.10):`reorder` 经状态机
22
+ * 重算列内 order(同列唯一、整列重编号;status 须与当前状态一致防竞态;可携带
23
+ * project/tags 归属变更原子落账)、`move.order` 可选落位(缺省追加目标列末尾,
24
+ * done/failed 拖回 backlog/todo 为重开不触发执行)。分组视图模式由浏览器端持有,
25
+ * Host 不感知(§12.10)。
26
+ * T010 接入自动化收集与上下文闭环(§9.5/§14,P3):
27
+ * - 自动化规则(§9.5 automation_rules):`upsert-automation`/`delete-automation`
28
+ * 落账;30s tick 与任务 schedule 同轮扫描规则 cron——到期滚动
29
+ * (applyCollectorRoll)后异步执行收集(收藏收件箱文件 / GitHub API 轮询,
30
+ * host-automation.ts),恢复期首 tick 跳过(applySkipMissedAutomationRules,
31
+ * 错过不补跑);`run-automation` 手动触发一次收集(只滚 lastTriggeredAt,
32
+ * 不消费计划触发点);收集产物经 applyAddCollectedTasks 去重落 backlog
33
+ * (§14.3:只进 backlog,人工 promote 前不可执行/不可定时,守卫在状态机);
34
+ * - GitHub Webhook(§14.1):`ingestGithubWebhook` 把 `issues` 事件按启用规则
35
+ * 的标签过滤创建 backlog 任务(命令类字段递归拒绝,§14.3/§16.2);HMAC
36
+ * 校验在路由层(host-routes.ts);
37
+ * - 评论/产物/上下文快照(§9.5):`add-comment`/`add-artifact`/`update-context`
38
+ * 经状态机落账;每次执行结算后 Host 自动更新——成功时记录会话产物
39
+ * (type=session,transcript 即默认产物)并摘取最近 assistant 文本写入
40
+ * `contextSnapshot.lastAiSummary`(B5:Host 侧摘取 + token 预算),
41
+ * latestUserFeedback/relatedLinks 同步刷新;后续调用只注入摘要与必要片段
42
+ * (runner.launch 经 composePrompt,见 core/context.ts)。
43
+ * T011 接入对话候选流转(§11.2/§14.4,P3.1):`propose`(对话提取/手动转任务
44
+ * → proposed 候选,source=conversation,输入清洗与命令字段拒绝在状态机/协议
45
+ * 层)、`confirm`(确认候选 → 落 backlog/todo,确认是唯一 promote 路径——
46
+ * 防注入闸门)、`dismiss`(拒绝/忽略删除)三个 action 经状态机落账。
47
+ * T012 接入需求拆分(§11.2/§12.9/§14.5,P3.2,见 B8):`start-split` 提交需求
48
+ * (粘贴文本/工作区文件路径)→ 注册拆分作业 → 启动独立拆分会话(复用 T005
49
+ * 执行通道,指令模板 = 粒度启发式 + 覆盖自检 + 输出 schema)→ 会话结束后读取
50
+ * transcript、解析结构化标记 → 创建父需求任务「需求:<标题>」+ `propose-batch`
51
+ * 原子落账(全部 proposed、source=requirement、children parentId 指向父任务、
52
+ * 递归拆分挂到原任务下;人工确认闸门复用 T011);`propose-batch` 亦可独立提交
53
+ * (批量候选,parentId 引用校验与防环在状态机层,§16.8)。
54
+ *
55
+ * 分层:路由层(host-routes.ts)负责安全栅栏与载荷校验;本服务只认已解析的
56
+ * 判别联合(protocol.ts 的输出),内部不含 HTTP 语义。调度(cron/one-shot/
57
+ * 自动化规则,T006/T007/T010)、电源保护实现(后续任务)不在本基线内;
58
+ * power 快照仅提供稳定的类型契约与基础值(sessionStateKnown 由轮询驱动)。
59
+ */
60
+ import type { SplitRequestInput, TaskRecord } from './core/model.ts';
61
+ import { HostLedger } from './host-ledger.ts';
62
+ import type { TaskBoardAutomationDeps } from './host-automation.ts';
63
+ import type { ExecutionInspection, ExecutionSessionRow, ExecutionSessionState } from './host-runner.ts';
64
+ import type { TaskBoardAction, TaskBoardEventPayload, TaskBoardPowerSnapshot, TaskBoardSnapshot } from './protocol.ts';
65
+ /** 会话轮询间隔(§17 性能:5s,对齐参考实现)。 */
66
+ export declare const SESSION_POLL_MS = 5000;
67
+ /** 调度 tick 间隔(§13.1/§17 性能:30s,对齐参考实现)。 */
68
+ export declare const SCHEDULE_TICK_MS = 30000;
69
+ /** 超过该间隔视为 Host 停机/睡眠/长暂停(§13.1 错过不补跑;= tick + 15s 容差)。 */
70
+ export declare const RESUME_GAP_MS: number;
71
+ /** 本服务可注入的依赖面(§17 可测试性:Host 编排用结构面注入 fake)。 */
72
+ export interface TaskBoardHostServiceOptions {
73
+ /** 账本(默认真实 HostLedger,测试注入临时目录账本)。 */
74
+ ledger?: HostLedger;
75
+ /** 时钟(测试可注入)。 */
76
+ now?: () => number;
77
+ /** 平台标识(测试可注入)。 */
78
+ platform?: string;
79
+ /** 执行驱动(T005;缺省时 run/rerun 被拒绝,测试可注入 fake)。 */
80
+ runner?: TaskBoardExecutionRunner;
81
+ /** 自动化收集依赖(T010;缺省 = 真实 fetch/文件读取,测试可注入 fake)。 */
82
+ automation?: TaskBoardAutomationDeps;
83
+ }
84
+ /** 执行驱动面(§13):launch 启动会话、listRunning 轮询、inspect 结算判定。 */
85
+ export interface TaskBoardExecutionRunner {
86
+ /** 启动一次执行(fail-closed 钉住目标后创建会话并发送 Prompt),返回会话 id。 */
87
+ launch(task: TaskRecord): Promise<string>;
88
+ /** 拉取会话列表(失败 known:false,服务层降级 sessionStateKnown)。 */
89
+ listRunning(): Promise<ExecutionSessionState>;
90
+ /** 结算判定单条执行(pending/cancelled/failed/succeeded)。 */
91
+ inspect(sessionId: string, startedAt: number, sessions?: readonly ExecutionSessionRow[]): Promise<ExecutionInspection>;
92
+ /**
93
+ * 读取会话 transcript 全文(T012/B8:需求拆分会话结果解析用);会话不存在/
94
+ * 历史不可读 → undefined。拆分会话是独立专用会话,全文即拆分结果。
95
+ */
96
+ readTranscriptText(sessionId: string): Promise<string | undefined>;
97
+ }
98
+ /**
99
+ * 需求拆分作业(T012/B8,§12.9/§14.5):一次 `start-split` 对应一个独立拆分
100
+ * 会话(复用 T005 执行通道)。启动时注册(key = action.id,幂等重放不重复
101
+ * 注册);会话结束(不再 running)后读取 transcript → 解析结构化标记 → 创建
102
+ * 父需求任务 + `propose-batch` 原子落账(全部 proposed,人工确认闸门复用
103
+ * T011)。失败写 scheduler.error(UI 可见)并移除作业。
104
+ */
105
+ export interface SplitJob {
106
+ /** 拆分作业 id(= start-split action 的 id;幂等重放不重复注册)。 */
107
+ id: string;
108
+ /** 拆分请求(标题/需求来源/归属/递归父任务)。 */
109
+ input: SplitRequestInput;
110
+ /** 启动时刻(ms epoch)。 */
111
+ startedAt: number;
112
+ /** 父需求任务 id(结算时创建;children 的 parentId 指向它)。 */
113
+ parentId: string;
114
+ /** 拆分会话 id(launch 成功后回填)。 */
115
+ sessionId?: string;
116
+ /** 是否已调度启动(幂等重放不重复启动)。 */
117
+ launched?: boolean;
118
+ }
119
+ /** 任务看板协议服务:串行 apply + 幂等 + snapshot/event/power + 执行轮询。 */
120
+ export declare class TaskBoardHostService {
121
+ /** 权威账本。 */
122
+ readonly ledger: HostLedger;
123
+ private readonly listeners;
124
+ private readonly now;
125
+ private readonly platform;
126
+ private readonly runner;
127
+ private readonly automation;
128
+ private active;
129
+ private preventIdleSleep;
130
+ private disposed;
131
+ private pollTimer;
132
+ private pollInFlight;
133
+ private sessionStateKnown;
134
+ private scheduleTimer;
135
+ private tickInFlight;
136
+ private lastScheduleTick;
137
+ /** 需求拆分作业(T012/B8:独立拆分会话驱动;key = action.id)。 */
138
+ private readonly splitJobs;
139
+ constructor(options?: TaskBoardHostServiceOptions);
140
+ /**
141
+ * 应用配置(插件 enabled / preventIdleSleep)。enabled=false 时 apply 抛错
142
+ * (路由层 400),SSE 订阅方通过事件帧感知 power 变化。从禁用恢复启用时
143
+ * 立即补一轮会话轮询与调度 tick(首 tick 跳过停机期错过的触发点)。
144
+ */
145
+ setConfiguration(active: boolean, preventIdleSleep: boolean): void;
146
+ /** 全量状态快照(§11.1 `GET /state`)。 */
147
+ snapshot(): TaskBoardSnapshot;
148
+ /** SSE 事件帧载荷(§11.1 `GET /events`):只推 revision/scheduler/power。 */
149
+ eventPayload(): TaskBoardEventPayload;
150
+ /** 订阅账本/配置变更(SSE 推送源;路由层在推送前先主动推一帧)。 */
151
+ subscribe(listener: () => void): () => void;
152
+ /**
153
+ * 幂等应用一个已解析的 action(§11.3):账本层判定 requestId + SHA-256 指纹,
154
+ * 命中缓存直接返回当前 state(不 bump);首次请求经状态机守卫原子落账。
155
+ * 守卫/校验失败抛错(`TaskBoardTransitionError` 等),路由层转为 400。
156
+ *
157
+ * T005:`run/rerun` 在本次 apply 真实开启执行(revision +1)时,异步调度
158
+ * launch(创建会话 + 发送 Prompt);幂等重放(revision 不变)不再重复启动。
159
+ */
160
+ apply(requestId: string, action: TaskBoardAction): TaskBoardSnapshot;
161
+ /**
162
+ * 启动执行轮询(§13.3):5s 间隔轮询会话列表结算 open 执行,启动时立即
163
+ * 轮询一次(覆盖 Host 重启后带 sessionId 的 running 执行)。幂等;无 runner
164
+ * 时 no-op(纯账本模式不轮询)。dispose 时停止。
165
+ *
166
+ * T006/T007 调度 tick(§13.1/§13.5):30s 间隔扫描到期 cron 与 one-shot,
167
+ * 启动时立即 tick 一次——首个 tick 视为恢复(错过触发点跳过不补跑,见
168
+ * tickSchedule)。
169
+ */
170
+ start(): void;
171
+ /** 释放:轮询/调度定时器、账本锁、订阅。 */
172
+ dispose(): void;
173
+ /** 电源保护快照(§11.1 power 字段;保护实现随后续任务,本期为基础值 + 配置态)。 */
174
+ powerSnapshot(): TaskBoardPowerSnapshot;
175
+ /** 状态机分发:协议 action → transitions 纯函数(就地修改账本文档并返回是否变更)。 */
176
+ private applyAction;
177
+ /** 异步调度 launch(fire-and-forget;结算失败仅记录日志,不中断控制面)。 */
178
+ private scheduleLaunch;
179
+ /**
180
+ * launch 编排(§13.2 尾部):runner 创建会话成功 → 回填 sessionId(之后由
181
+ * 轮询结算);任何失败 → 结算 failed 并保留错误文本(会话创建后的失败携带
182
+ * sessionId,先回填再结算,详情页保留跳转线索)。
183
+ */
184
+ private launchExecution;
185
+ /** 轮询调度(防重入):单轮在途时跳过,dispose 后不再启动。 */
186
+ private schedulePoll;
187
+ /**
188
+ * 单轮结算轮询(§13.3):会话列表 → 逐条判定 open 执行;会话列表不可知时
189
+ * 仅降级 sessionStateKnown(不结算,避免误判)。重启恢复语义:
190
+ * - 有 sessionId 的 running 执行 → 继续观察结算(本方法覆盖);
191
+ * - 无 sessionId 的启动中断 → HostLedger 加载时已标 cancelled(core/recovery.ts),
192
+ * 本方法跳过(禁止自动重发)。
193
+ * T012:同一轮次顺带结算需求拆分作业(复用会话列表,1 + E,B8)。
194
+ */
195
+ private pollSessions;
196
+ /** 账本中全部未结算执行的最小投影(taskId/executionId/sessionId/startedAt)。 */
197
+ private openExecutions;
198
+ private setSessionStateKnown;
199
+ /** 调度 tick 调度(防重入):单轮在途时跳过,dispose 后不再启动。 */
200
+ private scheduleTick;
201
+ /**
202
+ * 单轮调度 tick(§13.1/§13.5):
203
+ * - 记录 lastTickAt(setScheduler 不 bump revision,§17 性能);
204
+ * - **恢复判定**:首个 tick(启动/从禁用恢复)或距上次 tick 超过
205
+ * RESUME_GAP_MS(Host 停机/睡眠/长暂停)→ 跳过错过的触发点(skipMissed,
206
+ * 不补跑),并把全部到期规则滚动到 now 之后的下一个匹配点(cron)或写
207
+ * firedAt 消费(one-shot);
208
+ * - 正常 tick:扫描到期 cron(enabled 且 nextRunAt ≤ now 且非归档)与到期
209
+ * one-shot(firedAt 缺省且 runAt ≤ now 且非归档),逐条开启执行(复用 T005
210
+ * launch 入口);同任务 running 中到期 → cron 只滚动不触发、one-shot 只写
211
+ * firedAt 不触发(openScheduled/openScheduledOneShot 内部判定)。
212
+ */
213
+ private tickSchedule;
214
+ /** 账本中当前到期(enabled 且 nextRunAt ≤ now)的自动化规则最小投影。 */
215
+ private dueAutomationRules;
216
+ /** 账本中当前到期(enabled 且 nextRunAt ≤ now)的 cron 规则最小投影。 */
217
+ private dueSchedules;
218
+ /**
219
+ * 开启一次到期的调度执行(§13.1/§13.2 尾部):
220
+ * - 任务不存在/已归档 → no-op;
221
+ * - 任务 running 或带未结算执行 → **不并发、不排队**:本次只把 nextRunAt
222
+ * 滚动到下一匹配点(lastTriggeredAt 保留现值,本次不记触发),返回 undefined;
223
+ * - 否则开启执行(置 running + 追加 open 执行记录,startedAt=触发时刻)并把
224
+ * 规则滚动到下一匹配点(lastTriggeredAt=本次触发点),返回开启的执行供
225
+ * scheduleLaunch 启动会话。
226
+ */
227
+ private openScheduled;
228
+ /**
229
+ * 开启一次到期的 one-shot 执行(§13.5):
230
+ * - 任务不存在/已归档 → no-op;
231
+ * - 任务 running 或带未结算执行 → **不并发、不排队**:本次仍标记消费
232
+ * (写 firedAt),但不触发执行,返回 undefined;
233
+ * - 否则先标记消费(firedAt=now,触发即消费——防重启后重复触发),再开启
234
+ * 执行(置 running + 追加 open 执行记录,startedAt=触发时刻),返回开启的
235
+ * 执行供 scheduleLaunch 启动会话。
236
+ */
237
+ private openScheduledOneShot;
238
+ /** 账本中当前到期(firedAt 缺省且 runAt ≤ now)的 one-shot 规则最小投影。 */
239
+ private dueOneShots;
240
+ /** 异步调度一次收集(fire-and-forget;失败记日志,不中断控制面)。 */
241
+ private scheduleAutomationRun;
242
+ /**
243
+ * 执行一次收集(§14.2):按规则来源分发(书签收件箱文件 / GitHub API),产物
244
+ * 经 applyAddCollectedTasks 去重落 backlog(同 (source, url) 幂等);收集失败
245
+ * 写入可见错误(scheduler.error,UI 可见),不抛给控制面。
246
+ */
247
+ private runAutomationRule;
248
+ /**
249
+ * GitHub Webhook `issues` 事件摄入(§14.1):命令类字段递归拒绝(§14.3/§16.2,
250
+ * 与 import 同一条红线);只处理 opened/reopened;按启用 github_issue 规则的
251
+ * repo(配置了时须匹配)与标签过滤创建 backlog 任务(跨规则去重,同 url 只建
252
+ * 一条)。返回本次实际创建数(含账本去重)。
253
+ */
254
+ ingestGithubWebhook(payload: unknown): {
255
+ created: number;
256
+ };
257
+ /**
258
+ * 注册拆分作业(start-split 的变更闭包):校验递归父任务引用(存在性 + 防环,
259
+ * §16.8)后登记作业并返回 false(不产生账本变更、revision 不 bump——拆分
260
+ * 结果在拆分会话结算时落账)。幂等重放命中缓存不会调用本闭包 → 不重复注册。
261
+ */
262
+ private registerSplitJob;
263
+ /** 异步调度拆分会话启动(fire-and-forget;失败记 scheduler.error,不中断控制面)。 */
264
+ private scheduleSplitLaunch;
265
+ /**
266
+ * 启动拆分会话(B8):合成拆分驱动任务(标题「需求拆分:<标题>」、Prompt =
267
+ * 拆分指令模板)复用 runner.launch(fail-closed 校验 + 创建会话 + 发送 Prompt);
268
+ * 会话 id 回填作业(供轮询结算)。启动失败 → 记 scheduler.error 并移除作业。
269
+ */
270
+ private launchSplit;
271
+ /** 拆分作业失败:移除作业 + scheduler.error(UI 可见)。 */
272
+ private failSplit;
273
+ /**
274
+ * 单轮拆分作业结算(B8,复用 pollSessions 已取的会话列表,1 + E):
275
+ * - sessionId 未回填(launch 进行中)→ 跳过;
276
+ * - 会话不在列表(被删/从未创建成功)→ 失败;
277
+ * - 会话仍在运行 → 跳过(等待 agent 完成拆分);
278
+ * - 会话已结束 → 读取 transcript、解析标记、原子落账(settleSplit)。
279
+ */
280
+ private pollSplitJobs;
281
+ /**
282
+ * 拆分结算(§12.9/§14.5):解析 transcript 的结构化标记(`⟦task-board:split⟧`
283
+ * 逐条 + `⟦task-board:coverage⟧` 覆盖矩阵)→ 创建父需求任务 + `propose-batch`
284
+ * 原子落账(同一 mutate 闭包,任一失败整体回滚、revision 不变——§16.8)。
285
+ * 产物全部 proposed(source=requirement,人工确认闸门复用 T011);children
286
+ * parentId 指向父需求任务;父任务递归拆分时挂到原任务下(§12.9);候选
287
+ * metadata 携带拆分会话引用(可跳回追溯,§15)。解析不到任何条目 → 失败。
288
+ */
289
+ private settleSplit;
290
+ private emit;
291
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Host loader entry for the dsh-nova-ui-task-board plugin.
3
+ *
4
+ * T001 脚手架:插件配置项、设置命名空间(nova-task-board)、agent 播报;
5
+ * T003 起加载协议控制面:Host 权威账本(T002 HostLedger)+ 状态机 +
6
+ * `/api/nova-task-board/{state,action,events}` 三端点(安全栅栏 + 幂等);
7
+ * T005 接入真实执行:HostExecutionRunner 经 `ctx.apiProxy` 创建独立 DSH 会话
8
+ * (钉住工作区/agent 预设,权限经宿主 commands 注册表的 `/permission` 命令
9
+ * 应用),5s 轮询结算;调度(T006/T007)后续接入同一账本。
10
+ *
11
+ * 命名空间取 `nova-task-board` 而非参考实现的 `task-board`:参考实现
12
+ * @linxin666/dsh-client-ui-task-board 已在本机同一宿主注册 `task-board`
13
+ * 命名空间(dsh-settings 对重复注册直接 throw,插件加载失败),本项目与
14
+ * 其共存(AGENTS.md D2/D3),因此所有宿主侧标识均带 nova- 前缀;API 前缀
15
+ * 同理取 `/api/nova-task-board`(参考实现已占用 `/api/task-board`)。
16
+ */
17
+ import type { Context } from '@deepseek-ai/cordis';
18
+ import z from 'schemastery';
19
+ /** Default environment variable holding the authenticated proxy token. */
20
+ export declare const DEFAULT_PROXY_TOKEN_ENV = "DSH_TASK_BOARD_PROXY_TOKEN";
21
+ /** Default environment variable holding the GitHub Webhook HMAC secret. */
22
+ export declare const DEFAULT_GITHUB_WEBHOOK_SECRET_ENV = "DSH_TASK_BOARD_GITHUB_WEBHOOK_SECRET";
23
+ /** Default environment variable holding the GitHub API token (issue polling). */
24
+ export declare const DEFAULT_GITHUB_TOKEN_ENV = "DSH_TASK_BOARD_GITHUB_TOKEN";
25
+ /** Required services (the announcement section needs the system-prompt seam; the control plane registers on the webServer; execution uses the apiProxy + agent/command registries). */
26
+ export declare const inject: readonly ["systemPrompt", "webServer", "apiProxy", "agents", "commands"];
27
+ /**
28
+ * Settings namespace of the board — the section the web settings surface
29
+ * edits. Spelled here rather than imported: the browser half spells the same
30
+ * value and must not depend on a Host package.
31
+ */
32
+ export declare const NOVA_TASK_BOARD_SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
33
+ /** Model-facing announcement: plugin presence, capabilities, and limits. */
34
+ export declare const TASK_BOARD_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-nova-ui-task-board \u63D2\u4EF6\uFF08DSH Web GUI \u7684\u4EFB\u52A1\u770B\u677F\uFF0CNova \u7CFB\u5217\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300C\u4EFB\u52A1\u770B\u677F\u300D\u5165\u53E3\u3002\u80FD\u529B\uFF1A\u591A\u5217\u770B\u677F\u7BA1\u7406\u4EFB\u52A1\uFF1BHost \u6743\u5A01\u8D26\u672C\uFF1B\u5173\u95ED\u6D4F\u89C8\u5668\u540E\u4ECD\u7531 Host \u6267\u884C\u548C\u7ED3\u7B97\uFF1B\u4EFB\u52A1\u53EF\u9489\u4F4F\u5DE5\u4F5C\u533A\u3001agent \u9884\u8BBE\u548C\u6743\u9650\uFF1B\u652F\u6301 Host \u672C\u5730\u65F6\u533A\u7684 5 \u6BB5 cron\uFF0C\u9519\u8FC7\u7684\u89E6\u53D1\u70B9\u4E0D\u8865\u8DD1\uFF1B\u53EF\u9009\u4E14\u9ED8\u8BA4\u5173\u95ED\u7684\u7A7A\u95F2\u7CFB\u7EDF\u7761\u7720\u4FDD\u62A4\u3002\u5BF9\u8BDD\u5019\u9009\u4EFB\u52A1\u6D41\u8F6C\uFF08P3.1\uFF09\uFF1A\u7528\u6237\u8981\u6C42\u300C\u505A\u67D0\u4E8B\u300D\u300C\u8BB0\u4E0B\u6765\u300D\u300C\u540E\u7EED\u8981\u5904\u7406\u300D\u7B49\u660E\u786E\u4EFB\u52A1\u610F\u56FE\u65F6\uFF0C\u5728\u56DE\u590D\u672B\u5C3E\u8F93\u51FA\u4E00\u884C\u7ED3\u6784\u5316\u5019\u9009\u6807\u8BB0 `\u27E6task-board:propose\u27E7 \u6807\u9898 | \u63CF\u8FF0 | prompt`\uFF08\u63CF\u8FF0\u53EF\u4E3A\u7A7A\uFF0Cprompt \u4E3A\u6267\u884C\u65F6\u53D1\u9001\u7ED9 agent \u7684\u5B8C\u6574\u6307\u4EE4\u3001\u53EF\u7701\u7565\uFF09\uFF0C\u524D\u7AEF\u89E3\u6790\u540E\u8FDB\u5165\u770B\u677F\u300C\u5F85\u786E\u8BA4\u300D\u5217\uFF0C\u7ECF\u7528\u6237\u786E\u8BA4\u540E\u65B9\u53EF\u6267\u884C\u3002\u7528\u6237\u63D0\u5230\u300C\u4EFB\u52A1\u770B\u677F / \u770B\u677F / \u5B9A\u65F6\u4EFB\u52A1 / \u5019\u9009\u4EFB\u52A1\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
35
+ /** Plugin config, validated by the same-named schemastery schema. */
36
+ export interface Config {
37
+ /**
38
+ * When true (default), a system-prompt section announces the board to every
39
+ * agent. Set false to keep the board silent in prompts; agents then learn
40
+ * about it only when the user mentions it.
41
+ */
42
+ announceToAgent?: boolean;
43
+ /** Master switch for the plugin (browser half + host announcement). */
44
+ enabled?: boolean;
45
+ /** Prevent idle system sleep while sessions run or schedules are armed. */
46
+ preventIdleSleep?: boolean;
47
+ /** Canonical reverse-proxy Host authorities admitted with a server-side token. */
48
+ trustedProxyHosts?: string[];
49
+ /** Environment variable whose value the authenticated proxy injects upstream. */
50
+ proxyTokenEnv?: string;
51
+ /**
52
+ * Environment variable holding the GitHub Webhook HMAC secret
53
+ * (`DSH_TASK_BOARD_GITHUB_WEBHOOK_SECRET`); when unset the webhook endpoint
54
+ * answers 503 (T010 §14.1).
55
+ */
56
+ githubWebhookSecretEnv?: string;
57
+ /**
58
+ * Environment variable holding the GitHub API token used by the issue
59
+ * polling collector (`DSH_TASK_BOARD_GITHUB_TOKEN`); when unset the poller
60
+ * runs anonymously (rate-limited).
61
+ */
62
+ githubTokenEnv?: string;
63
+ }
64
+ export declare const Config: z<Config>;
65
+ /** Resolve proxy access without ever placing the token value in plugin config. */
66
+ export declare function resolveProxyAccess(config: Config | undefined, env?: NodeJS.ProcessEnv): {
67
+ trustedProxyHosts: string[];
68
+ proxyToken?: string;
69
+ };
70
+ /**
71
+ * Resolve T010 automation secrets from environment variables (never placed in
72
+ * plugin config or the ledger). Empty/absent env values resolve to undefined:
73
+ * - webhookSecret 缺省 → GitHub Webhook 端点 503(未配置);
74
+ * - githubToken 缺省 → Issue 轮询走匿名(GitHub 限速)。
75
+ */
76
+ export declare function resolveAutomationSecrets(config: Config | undefined, env?: NodeJS.ProcessEnv): {
77
+ webhookSecret?: string;
78
+ githubToken?: string;
79
+ };
80
+ /**
81
+ * Register the board's announcement section, gated on the composition entry's
82
+ * `announceToAgent` (and the live settings value once the web settings
83
+ * surface is served). The section is re-registered whenever the source
84
+ * changes, so a settings edit takes effect without a restart.
85
+ * @param ctx - the plugin context (systemPrompt injected).
86
+ * @param config - resolved plugin config (schema defaults applied by the loader).
87
+ */
88
+ export declare const apply: typeof applyImpl;
89
+ declare function applyImpl(ctx: Context, config?: Config): void;
90
+ export {};
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 回环信任栅栏(DSH-REQ-001 §16.1):socket 地址、Host 头、浏览器同源标记。
3
+ *
4
+ * 语义(对齐参考实现的共享 loopback 模块,本插件自持一份):
5
+ * - RFC 5735 IPv4 127/8、`::1`、IPv4-mapped `::ffff:127/8` 视为回环地址;
6
+ * - `localhost` / `[::1]` / 127/8 主机名视为回环 authority;
7
+ * - 请求级信任 = 回环 socket 地址 AND 回环 Host 头 AND 浏览器同源标记
8
+ * (`sec-fetch-site` 与 `Origin`);socket 地址为权威依据,
9
+ * `X-Forwarded-For` 永不信任。
10
+ */
11
+ import type { IncomingMessage } from 'node:http';
12
+ /** IPv4 127/8 谓词(四个十进制八位组、首段 == 127)。 */
13
+ export declare function isIPv4Loopback(v4: string): boolean;
14
+ /** socket 远端地址是否属于回环范围(127/8、::1、IPv4-mapped)。 */
15
+ export declare function isLoopbackAddress(address: string | undefined): boolean;
16
+ /** 归一化 URL 主机名是否为回环 authority(localhost、[::1]、127/8)。 */
17
+ export declare function isLoopbackHostname(hostname: string): boolean;
18
+ /**
19
+ * 请求级信任栅栏:回环 socket 地址 AND 回环 Host 头,加上浏览器同源标记。
20
+ * socket 地址权威;`X-Forwarded-For` 永不信任。Origin 缺省时仅接受
21
+ * `sec-fetch-site: same-origin` 的显式标记。
22
+ */
23
+ export declare function isLoopbackRequest(request: IncomingMessage): boolean;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Wrap a cordis plugin apply so the package runs at most once per process.
3
+ * The first mount registers normally and unmarks when its fiber disposes;
4
+ * any later mount of the same package name is a no-op.
5
+ * @param packageName - npm package identity shared by every install source.
6
+ * @param fn - the original plugin apply.
7
+ * @returns an apply of the same shape.
8
+ */
9
+ export declare function mountOnce<T extends (...args: any[]) => unknown>(packageName: string, fn: T): T;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * 协议层(DSH-REQ-001 §11):严格版本化 Action 判别联合、全量快照与 SSE 事件
3
+ * 帧类型、幂等信封解析。T003 交付协议基线:
4
+ *
5
+ * - 端点前缀 `/api/nova-task-board`(AGENTS.md D2:参考实现已占用
6
+ * `/api/task-board`,本插件必须使用 nova- 前缀);
7
+ * - Action 判别联合基线:`import / create / update / delete / move / archive /
8
+ * restore`;T005 扩展 `run / rerun`(真实执行,§13.2);T006 扩展
9
+ * `set-schedule`(cron 周期定时,§9.4/§11.2:enabled/cron patch,nextRunAt/
10
+ * lastTriggeredAt 浏览器不可写);T007 扩展 `set-one-shot`(一次性计划,
11
+ * §11.2:runAt 缺省或 0 = 取消,与 set-schedule 互斥 D6);T008 扩展
12
+ * `create.input`/`update.patch` 支持 project/tags(§9.2/§12.8);T009 扩展
13
+ * `reorder`(列内排序/分组落位,§11.2:status 须与当前状态一致、order 有限
14
+ * 数字、可携带 project/tags 归属变更)与 `move.order`(跨列拖拽落位,缺省
15
+ * 追加目标列末尾);T011 扩展 `propose`(对话提取/手动转任务 → proposed
16
+ * 候选,§11.2/§14.4:输入沿用 NewTaskInput 校验 + sourceConversationId/
17
+ * sourceMessageId 来源引用,命令类字段递归拒绝 §16.7)、`confirm`(确认候选
18
+ * → 落 backlog/todo,确认是唯一 promote 路径)、`dismiss`(拒绝/忽略删除);
19
+ * T012 扩展 `propose-batch`(批量候选,§11.2/§14.5:items 逐条沿用 propose
20
+ * 校验 + parentId 引用校验,整批原子落账、单 revision、幂等指纹按整批计算、
21
+ * 载荷上限对齐 import 2 MiB)与 `start-split`(需求拆分驱动,§11.2/§12.9/
22
+ * §14.5:提交需求文本/文件路径,Host 启动独立拆分会话并在结算时落账);
23
+ * - 严格校验:exact keys、类型、枚举、cron 合法性、有限时间戳;未知字段/未知
24
+ * action 一律拒绝;import 载荷递归拒绝 `args/command/executable/powershell/
25
+ * shell` 字段(防注入,§16.2/§19-9);
26
+ * - 幂等(§11.3):requestId + SHA-256 action 指纹,缓存判定复用 T002
27
+ * HostLedger 的持久化 recentRequests(本层负责信封解析,指纹由账本层计算)。
28
+ *
29
+ * T010 扩展(§9.5/§11.2/§14,P3 自动化与闭环):
30
+ * - `add-comment`(评论,author/body/type)、`add-artifact`(产物,
31
+ * type/title/url/contentRef)、`update-context`(上下文快照部分补丁,空串/
32
+ * 空数组 = 清除,updatedAt 浏览器不可写);
33
+ * - `upsert-automation`(自动化规则创建/替换;nextRunAt/lastTriggeredAt 为
34
+ * Host 独占字段,浏览器不可写)、`delete-automation`、`run-automation`
35
+ * (手动触发一次收集);
36
+ * - snapshot 新增 `automationRules`(§9.5 automation_rules 集合)。
37
+ *
38
+ * 分层说明:import 的任务行做「字段级严格校验 + 未知字段剥离」——v1 迁移数据
39
+ * 可能携带 `priority` 等历史字段(§9.5 列入 P4 metadata),整行拒绝会阻断迁移,
40
+ * 但命令类字段(禁止集)与类型/枚举/时间戳错误整行拒绝;envelope/action 层则
41
+ * 是 exact-keys 严格拒绝。
42
+ */
43
+ import type { AutomationRule, ArtifactType, BatchProposeItem, CommentType, NewTaskInput, ProposeTaskInput, SchedulerSnapshot, SplitRequestInput, TaskRecord, TaskStatus, TaskUpdatePatch } from './core/model.ts';
44
+ import { LEDGER_SCHEMA_VERSION } from './core/model.ts';
45
+ import type { AutomationRuleInput, ContextSnapshotPatch } from './core/transitions.ts';
46
+ /** 本项目 API 前缀(AGENTS.md D2,2026-08-22 定值;参考实现占用 /api/task-board)。 */
47
+ export declare const TASK_BOARD_API_PREFIX = "/api/nova-task-board";
48
+ /** 电源保护阶段(§11.1 snapshot 的 power 字段;保护实现随后续任务,本期仅类型+基础值)。 */
49
+ export type PowerPhase = 'disabled' | 'idle' | 'acquiring' | 'active' | 'error' | 'unsupported';
50
+ /** 电源保护对外快照(对齐参考实现形状,保证 T004 UI 的类型契约稳定)。 */
51
+ export interface TaskBoardPowerSnapshot {
52
+ /** 宿主平台。 */
53
+ platform: string;
54
+ /** 电源保护阶段。 */
55
+ phase: PowerPhase;
56
+ /** 保护是否启用(enabled 配置 + 平台支持)。 */
57
+ enabled: boolean;
58
+ /** 正在运行的执行数。 */
59
+ runningSessions: number;
60
+ /** 已武装的计划数。 */
61
+ armedSchedules: number;
62
+ /** 会话状态是否已知(T005 起由 5s 会话轮询驱动;轮询失败降级 false)。 */
63
+ sessionStateKnown: boolean;
64
+ /** 最近可见错误。 */
65
+ lastError?: string;
66
+ }
67
+ /** 全量状态快照(§11.1 `GET /state`)。 */
68
+ export interface TaskBoardSnapshot {
69
+ schemaVersion: typeof LEDGER_SCHEMA_VERSION;
70
+ revision: number;
71
+ tasks: TaskRecord[];
72
+ scheduler: SchedulerSnapshot;
73
+ power: TaskBoardPowerSnapshot;
74
+ /** 自动化收集规则(T010,§9.5 automation_rules)。 */
75
+ automationRules: AutomationRule[];
76
+ }
77
+ /** SSE 事件帧(§11.1 `GET /events`):只推 revision/scheduler/power,绝不携带任务列表。 */
78
+ export interface TaskBoardEventPayload {
79
+ revision: number;
80
+ scheduler: SchedulerSnapshot;
81
+ power: TaskBoardPowerSnapshot;
82
+ }
83
+ /** Action 判别联合(§11.2 基线 + T005 执行扩展 + T006 定时扩展 + T009 排序/拖拽扩展 + T011 候选流转扩展)。 */
84
+ export type TaskBoardAction = {
85
+ kind: 'import';
86
+ sourceId: string;
87
+ tasks: TaskRecord[];
88
+ } | {
89
+ kind: 'create';
90
+ id: string;
91
+ input: NewTaskInput;
92
+ } | {
93
+ kind: 'update';
94
+ taskId: string;
95
+ patch: TaskUpdatePatch;
96
+ } | {
97
+ kind: 'delete';
98
+ taskId: string;
99
+ } | {
100
+ kind: 'move';
101
+ taskId: string;
102
+ status: TaskStatus;
103
+ order?: number;
104
+ } | {
105
+ kind: 'reorder';
106
+ taskId: string;
107
+ status: TaskStatus;
108
+ order: number;
109
+ project?: string;
110
+ tags?: string[];
111
+ } | {
112
+ kind: 'archive';
113
+ taskId: string;
114
+ } | {
115
+ kind: 'restore';
116
+ taskId: string;
117
+ } | {
118
+ kind: 'set-schedule';
119
+ taskId: string;
120
+ patch: {
121
+ enabled?: boolean;
122
+ cron?: string;
123
+ };
124
+ } | {
125
+ kind: 'set-one-shot';
126
+ taskId: string;
127
+ patch: {
128
+ runAt?: number;
129
+ };
130
+ } | {
131
+ kind: 'run';
132
+ taskId: string;
133
+ } | {
134
+ kind: 'rerun';
135
+ taskId: string;
136
+ } | {
137
+ kind: 'propose';
138
+ id: string;
139
+ input: ProposeTaskInput;
140
+ } | {
141
+ kind: 'confirm';
142
+ taskId: string;
143
+ target: 'backlog' | 'todo';
144
+ } | {
145
+ kind: 'dismiss';
146
+ taskId: string;
147
+ } | {
148
+ kind: 'propose-batch';
149
+ id: string;
150
+ items: BatchProposeItem[];
151
+ } | {
152
+ kind: 'start-split';
153
+ id: string;
154
+ input: SplitRequestInput;
155
+ } | {
156
+ kind: 'add-comment';
157
+ taskId: string;
158
+ comment: {
159
+ author?: string;
160
+ body: string;
161
+ type?: CommentType;
162
+ };
163
+ } | {
164
+ kind: 'add-artifact';
165
+ taskId: string;
166
+ artifact: {
167
+ type: ArtifactType;
168
+ title: string;
169
+ url?: string;
170
+ contentRef?: string;
171
+ };
172
+ } | {
173
+ kind: 'update-context';
174
+ taskId: string;
175
+ patch: ContextSnapshotPatch;
176
+ } | {
177
+ kind: 'upsert-automation';
178
+ rule: AutomationRuleInput;
179
+ } | {
180
+ kind: 'delete-automation';
181
+ ruleId: string;
182
+ } | {
183
+ kind: 'run-automation';
184
+ ruleId: string;
185
+ };
186
+ /** 幂等 action 信封(§11.3):requestId 浏览器 uuid。 */
187
+ export interface TaskBoardActionEnvelope {
188
+ requestId: string;
189
+ action: TaskBoardAction;
190
+ }
191
+ /**
192
+ * 载荷是否含命令类字段(§16.2/§19-9):对键名递归检查
193
+ * `args/command/executable/powershell/shell`(大小写不敏感)。import 与
194
+ * GitHub Webhook 外部载荷共用(T010 §14.3:自动收集内容不携带命令结构)。
195
+ */
196
+ export declare function hasForbiddenCommandFields(value: unknown): boolean;
197
+ /**
198
+ * 解析幂等 action 信封(§11.2/§11.3)。任何结构/类型/枚举/时间戳/cron/未知
199
+ * 字段/未知 action 问题 → undefined(路由层 400 invalid-action)。返回的 action
200
+ * 为规范化对象(import 任务行经严格校验与字段剥离)。
201
+ *
202
+ * T009(§11.2/D10):
203
+ * - `reorder`:`order` 必填有限数字(浏览器提交的落位下标,0-based;Host 重算
204
+ * 同列唯一,不信任浏览器序号——见 transitions.applyReorderTask);`status` 须
205
+ * 为合法状态且与任务当前状态一致(防竞态,状态机层校验);`project` 可选字符串
206
+ * (空串 = 清除归属,沿用 update 语义);`tags` 可选字符串数组(整体替换,
207
+ * 沿用 update 的 normalizeTags 语义)。
208
+ * - `move`:新增可选 `order`(有限数字 = 目标列落位下标,缺省追加目标列末尾)。
209
+ */
210
+ export declare function parseActionEnvelope(value: unknown): TaskBoardActionEnvelope | undefined;