@william2000/dsh-nova-ui-task-board 0.2.1 → 0.3.1

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 (51) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +34 -74
  3. package/cordis.patch.yml +0 -6
  4. package/lib/client.js +4 -5777
  5. package/lib/index.js +11 -4950
  6. package/lib/types/client/NovaPluginsSection.d.ts +0 -23
  7. package/lib/types/client/NovaTaskBoardSettingsCard.d.ts +0 -8
  8. package/lib/types/client/apply-guard.d.ts +0 -20
  9. package/lib/types/client/board/ConfirmDialog.d.ts +0 -3
  10. package/lib/types/client/board/ConfirmTaskDialog.d.ts +0 -1
  11. package/lib/types/client/board/ConvertTaskModal.d.ts +0 -4
  12. package/lib/types/client/board/NewTaskModal.d.ts +0 -1
  13. package/lib/types/client/board/RequirementSplitModal.d.ts +0 -4
  14. package/lib/types/client/board/SplitConfirmDialog.d.ts +0 -2
  15. package/lib/types/client/board/TaskBoard.d.ts +0 -5
  16. package/lib/types/client/board/TaskCard.d.ts +0 -23
  17. package/lib/types/client/board/TaskDetail.d.ts +0 -1
  18. package/lib/types/client/board/status-key.d.ts +0 -5
  19. package/lib/types/client/board-controller.d.ts +0 -159
  20. package/lib/types/client/board-mount.d.ts +0 -7
  21. package/lib/types/client/chat-integration.d.ts +0 -64
  22. package/lib/types/client/drag-utils.d.ts +0 -52
  23. package/lib/types/client/filter.d.ts +0 -22
  24. package/lib/types/client/grouping.d.ts +0 -39
  25. package/lib/types/client/host-api.d.ts +0 -44
  26. package/lib/types/client/index.d.ts +0 -21
  27. package/lib/types/client/legacy-store.d.ts +0 -22
  28. package/lib/types/client/locales.d.ts +4 -22
  29. package/lib/types/client/schedule-presets.d.ts +0 -9
  30. package/lib/types/client/sidebar-entry-core.d.ts +0 -33
  31. package/lib/types/client/sidebar-entry.d.ts +0 -16
  32. package/lib/types/core/context.d.ts +0 -77
  33. package/lib/types/core/cron.d.ts +0 -42
  34. package/lib/types/core/migrate.d.ts +0 -31
  35. package/lib/types/core/model.d.ts +0 -263
  36. package/lib/types/core/recovery.d.ts +0 -18
  37. package/lib/types/core/split.d.ts +0 -55
  38. package/lib/types/core/transitions.d.ts +0 -349
  39. package/lib/types/dsh-home.d.ts +0 -16
  40. package/lib/types/host-automation.d.ts +0 -40
  41. package/lib/types/host-ledger.d.ts +0 -104
  42. package/lib/types/host-routes.d.ts +0 -42
  43. package/lib/types/host-runner.d.ts +0 -85
  44. package/lib/types/host-service.d.ts +0 -219
  45. package/lib/types/index.d.ts +0 -61
  46. package/lib/types/loopback.d.ts +0 -18
  47. package/lib/types/mount-once.d.ts +0 -8
  48. package/lib/types/protocol.d.ts +0 -75
  49. package/package.json +16 -5
  50. package/lib/client.js.map +0 -1
  51. package/lib/types/client/PlaceholderBoard.d.ts +0 -9
@@ -1,67 +1,17 @@
1
- /**
2
- * Host 权威账本(DSH-REQ-001 §8 架构 / §9.1 / §17 可靠性)。
3
- *
4
- * 「Host 权威、浏览器只是异步视图」的数据底座:所有任务状态持久化到
5
- * `$DSH_HOME/nova-ui/task-board/ledger-v2.json`(0600,原子写),保证重启不丢、
6
- * 并发一致、损坏可隔离。T003(协议)/T005(执行)/T006(调度)等在此之上工作。
7
- *
8
- * 可靠性机制(§17 / §19-7 数据部分):
9
- * - **原子写**:临时文件 + fsync + 原子 rename + 目录 fsync;任何失败清理临时
10
- * 文件并重新抛出,绝不产生半写文件;
11
- * - **损坏隔离**:schema 不兼容/不可解析的账本 rename 为 `ledger-v2.json.corrupt-*`
12
- * (保留原字节)并以空账本重建,不崩溃;
13
- * - **单写者锁**:`ledger-v2.lock`(0600,`wx` 排他创建)记录 PID + token +
14
- * 进程启动时间探测;PID 复用通过启动时间不匹配识别为陈旧锁并接管(防误判),
15
- * 僵尸/死亡进程(Z/X)不阻塞启动;
16
- * - **幂等缓存**:`recentRequests`(最近 256 条 requestId→SHA-256 指纹)随账本
17
- * 原子持久化,Host 重启后浏览器重试仍安全(§11.3);
18
- * - **v1 迁移**:`importSource` 按 sourceId 一次性导入、按 id 合并,导入 marker
19
- * 随原子落账持久化(Host 确认后浏览器才清除 v1 原值,见 core/migrate.ts)。
20
- *
21
- * 串行性:本类全部方法为同步实现(无 await),单进程内天然串行;跨进程互斥
22
- * 由锁保证。`revision` 每次原子落账严格 +1(scheduler 心跳类补丁除外,见
23
- * `setScheduler`)。
24
- */
25
1
  import type { AutomationRule, LedgerDocument, LedgerScheduler, LedgerState, SchedulerSnapshot, TaskRecord } from './core/model.ts';
26
- /**
27
- * 尽力而为的单字母进程状态('R','S','D','Z',...),平台不支持时返回 undefined。
28
- * Linux 直接读 /proc(无子进程);其他 POSIX 用 `ps -o stat=`;Windows 无僵尸
29
- * 态,返回 undefined(此时 kill(0) 探测为唯一权威)。
30
- */
31
2
  export declare function processState(pid: number): string | undefined;
32
- /** PID 是否存活(僵尸/死亡态视为不存活)。 */
33
3
  export declare function processIsAlive(pid: number): boolean;
34
- /**
35
- * 尽力而为的进程启动时间(Unix epoch ms),用于证明锁记录里的 PID 是否真的是
36
- * 之前那个进程(PID 复用会被识别为陈旧锁而不是永久阻塞启动)。平台探测不可用
37
- * 时返回 undefined,调用方 fail closed。
38
- */
39
4
  export declare function processStartTimeMs(pid: number): number | undefined;
40
- /** 记录的启动时间是否证明记录的 PID 已是另一个进程(PID 复用探测核心)。 */
41
5
  export declare function startTimeMismatch(recorded: number, actual: number, exact: boolean): boolean;
42
- /**
43
- * 进程探测面(§17 可测试性:Host 编排用结构面注入 fake)。生产默认用真实
44
- * 平台探测;测试可注入假实现来覆盖 PID 复用接管等分支(真实探测在部分
45
- * 沙箱/系统上不可用——如 macOS 限制 ps——会 fail closed 而非误判)。
46
- */
47
6
  export interface ProcessProbes {
48
7
  isAlive(pid: number): boolean;
49
- /** 进程启动时间(Unix epoch ms);平台探测不可用时返回 undefined。 */
50
8
  startTimeMs(pid: number): number | undefined;
51
- /** 本进程启动时间(带缓存)。 */
52
9
  ownStartTimeMs(): number | undefined;
53
10
  }
54
- /**
55
- * Host 权威账本。构造即获取单写者锁并加载/恢复账本;进程内所有写入经
56
- * `applyRequest`/`importSource`/`mutate`/`setScheduler` 串行原子落账。
57
- */
58
11
  export declare class HostLedger {
59
12
  private readonly now;
60
- /** 账本文件绝对路径。 */
61
13
  readonly file: string;
62
- /** 锁文件绝对路径。 */
63
14
  readonly lockFile: string;
64
- /** 账本目录。 */
65
15
  readonly dir: string;
66
16
  private document;
67
17
  private readonly listeners;
@@ -69,78 +19,24 @@ export declare class HostLedger {
69
19
  private readonly lockToken;
70
20
  private lockFd;
71
21
  private readonly probes;
72
- /**
73
- * @param dir - 账本目录(默认 `$DSH_HOME/nova-ui/task-board/`,见 AGENTS.md D2)。
74
- * @param now - 时间源(测试可注入)。
75
- * @param probes - 进程探测面(默认真实探测;测试可注入 fake,见 §17 可测试性)。
76
- */
77
22
  constructor(dir?: string, now?: () => number, probes?: ProcessProbes);
78
- /** revision + scheduler(不克隆任务),供 SSE 事件帧使用。 */
79
23
  summary(): {
80
24
  revision: number;
81
25
  scheduler: SchedulerSnapshot;
82
26
  };
83
- /** 全量状态快照(任务/规则深拷贝,调用方修改不影响账本)。 */
84
27
  state(): LedgerState;
85
- /**
86
- * 只读任务数组(不克隆,O(1))。仅供服务层扫描/计数(如 power 快照的
87
- * runningSessions/armedSchedules);**绝不可修改**——任何变更必须走
88
- * `applyRequest`/`mutate` 的原子落账路径。
89
- */
90
28
  peekTasks(): readonly TaskRecord[];
91
- /** 只读自动化规则数组(不克隆;变更必须走 applyRequest/mutate 原子落账)。 */
92
29
  peekAutomationRules(): readonly AutomationRule[];
93
- /** 订阅账本变更(每次原子落账后触发)。 */
94
30
  subscribe(listener: () => void): () => void;
95
- /** 释放锁(仅当锁仍属本实例时删除锁文件,避免误删外部替换的锁)。 */
96
31
  dispose(): void;
97
- /**
98
- * 幂等 action 应用(§11.3)。调用方(T003 协议层)提供序列化的 action(仅
99
- * 用于 SHA-256 指纹)与变更闭包;闭包返回 true 表示文档有实际变更。
100
- *
101
- * - 重复 requestId + 相同指纹 → 直接返回当前 state(不重新应用、不 bump);
102
- * - 同 requestId + 不同动作 → 抛错(`request id was reused with a different action`);
103
- * - 首次请求 → 指纹先行入缓存,随后应用;变更闭包抛错则回滚缓存条目;
104
- * - 成功且 changed → 原子落账(revision+1),指纹随账本一并持久化——
105
- * Host 重启后浏览器重试仍安全。
106
- *
107
- * @param requestId - 浏览器侧 uuid。
108
- * @param action - 序列化 action(指纹源;必须可 JSON 序列化)。
109
- * @param apply - 变更闭包,就地修改文档;返回 true 表示发生变更(需落账)。
110
- */
111
32
  applyRequest(requestId: string, action: unknown, apply: (doc: LedgerDocument) => boolean): LedgerState;
112
- /**
113
- * v1 迁移导入(§11.2 `import` / §9.5):按 sourceId 一次性导入、按 id 合并、
114
- * 写入 importedSources marker(原子落账确认后才写入)。重复 sourceId 幂等
115
- * no-op(不落账不 bump)。浏览器以成功响应 + 记住 ledgerId 作为确认凭证
116
- * (T003/T004 接线;marker 不进 state/summary,见 `summary`)。
117
- */
118
33
  importSource(requestId: string, sourceId: string, tasks: readonly TaskRecord[]): LedgerState;
119
- /**
120
- * 通用原子变更(Host 编排用,T005/T006 的 runner/scheduler 也走这里)。
121
- * `apply` 返回 true 才落账(revision+1);返回 false 保持 revision 不变。
122
- */
123
34
  mutate(apply: (doc: LedgerDocument) => boolean): LedgerState;
124
- /**
125
- * 更新 scheduler 补丁(心跳 lastTickAt、错误可见性等,§9.1)。
126
- * 不 bump revision(避免 30s 心跳驱动客户端全量重拉);无实际变更不写盘。
127
- */
128
35
  setScheduler(patch: Partial<LedgerScheduler>): void;
129
36
  private syncRecentRequests;
130
- /**
131
- * 原子落账:临时文件 + fsync + 原子 rename + 目录 fsync(0600)。
132
- * 任一步失败清理临时文件并重新抛出——绝不产生半写文件。
133
- */
134
37
  private commit;
135
- /** 加载账本;损坏/schema 不兼容 → 隔离原文件并以空账本重建(§17 损坏隔离)。 */
136
38
  private load;
137
- /** 损坏隔离:原文件 rename 为 `ledger-v2.json.corrupt-<ts>-<pid>-<uuid>`,原字节保留。 */
138
39
  private quarantine;
139
40
  private notify;
140
- /**
141
- * 获取单写者锁:`wx` 排他创建(已存在则 EEXIST 走锁主判定)。
142
- * 锁记录 PID + token + 启动时间探测;PID 复用(启动时间不匹配)识别为陈旧锁
143
- * 并接管;锁不可读时 fail closed(提示人工清理)。
144
- */
145
41
  private acquireLock;
146
42
  }
@@ -1,46 +1,14 @@
1
- /**
2
- * 路由层(DSH-REQ-001 §11.1 三端点 + §16 安全栅栏)。
3
- *
4
- * 端点(同源,前缀 `/api/nova-task-board`):
5
- * - `GET /api/nova-task-board/state` → 全量快照 `{schemaVersion, revision,
6
- * tasks, scheduler, power, automationRules}`;
7
- * - `POST /api/nova-task-board/action` → 幂等信封 `{requestId, action}`,返回
8
- * 新 snapshot;
9
- * - `GET /api/nova-task-board/events` → SSE 推送 `{revision, scheduler,
10
- * power}`(15s 心跳注释帧),revision 变化时客户端重拉 state;
11
- * - `POST /api/nova-task-board/webhooks/github` → GitHub `issues` 事件接收
12
- * (T010 §14.1):HMAC-SHA256 签名校验(`X-Hub-Signature-256`,密钥来自
13
- * 环境变量,不落配置);未配置密钥 → 503;签名不匹配 → 401。该端点不走
14
- * 浏览器同源栅栏(GitHub 无法携带浏览器信号),HMAC 即权威边界。
15
- *
16
- * 安全栅栏(§16.1/16.2/16.3):
17
- * - 浏览器同源标记(sec-fetch-site/Origin)为触发器而非权威校验——裸命令行
18
- * 请求(curl 无任何浏览器信号)直接 403;权威边界是回环 socket + Host +
19
- * origin 等价校验(loopback.ts + isTrustedTaskBoardRequest);
20
- * - 同机认证反代必须白名单(trustedProxyHosts 规范化 authority)+ 服务端注入
21
- * token(`x-dsh-task-board-proxy-token`,timingSafeEqual 比较);
22
- * - POST 必须 application/json(否则 415);普通 action ≤ 64 KiB、import /
23
- * propose-batch / start-split ≤ 2 MiB(超限 413);非法信封/未知 action → 400
24
- * invalid-action;状态机守卫失败 → 400 + 错误信息。
25
- */
26
1
  import type { IncomingMessage } from 'node:http';
27
2
  import type { WebRoute } from '@deepseek-ai/dsh-host-webserver';
28
3
  import type { TaskBoardHostService } from './host-service.ts';
29
- /** 普通 action 载荷上限(§11.1)。 */
30
4
  export declare const ACTION_LIMIT: number;
31
- /** import(批量候选同限)载荷上限(§11.1)。 */
32
5
  export declare const IMPORT_LIMIT: number;
33
- /** SSE 心跳间隔(§17 性能:SSE 心跳 15s)。 */
34
6
  export declare const HEARTBEAT_MS = 15000;
35
- /** 同机认证反代在自身认证后替换注入的服务端 token 头(§16.1)。 */
36
7
  export declare const TASK_BOARD_PROXY_TOKEN_HEADER = "x-dsh-task-board-proxy-token";
37
- /** GitHub Webhook 签名头(HMAC-SHA256,`sha256=<hex>`,§14.1)。 */
38
8
  export declare const GITHUB_SIGNATURE_HEADER = "x-hub-signature-256";
39
- /** 路由访问面:默认仅回环 + 浏览器同源标记;反代须白名单 + token。 */
40
9
  export interface TaskBoardRouteAccess {
41
10
  trustedProxyHosts?: readonly string[];
42
11
  proxyToken?: string;
43
- /** GitHub Webhook HMAC 密钥(env 解析,不落配置;缺省 = Webhook 端点不可用)。 */
44
12
  webhookSecret?: string;
45
13
  }
46
14
  interface ResolvedRouteAccess {
@@ -48,18 +16,8 @@ interface ResolvedRouteAccess {
48
16
  proxyToken?: string;
49
17
  webhookSecret?: string;
50
18
  }
51
- /** 解析访问面并校验白名单条目的规范化形态(配置错误直接 throw,插件加载失败)。 */
52
19
  export declare function resolveAccess(access: TaskBoardRouteAccess): ResolvedRouteAccess;
53
- /**
54
- * GitHub Webhook 签名校验(§14.1):`X-Hub-Signature-256: sha256=<hex>`,
55
- * 对原始请求体做 HMAC-SHA256 并恒定时间比较。签名头缺失/畸形/不匹配 → false。
56
- */
57
20
  export declare function verifyGithubSignature(rawBody: string, signature: string | string[] | undefined, secret: string): boolean;
58
- /**
59
- * 请求级信任判定:浏览器同源标记 + (回环请求 | 白名单反代 + token)。
60
- * 回环判定包含 socket 地址、Host 头与 origin 等价校验(loopback.ts)。
61
- */
62
21
  export declare function isTrustedTaskBoardRequest(req: IncomingMessage, access: ResolvedRouteAccess): boolean;
63
- /** 组装三个端点路由(宿主的 webServer.register 对重复 (kind, path) 直接 throw,勿重复注册)。 */
64
22
  export declare function makeTaskBoardRoutes(service: TaskBoardHostService, access?: TaskBoardRouteAccess): WebRoute[];
65
23
  export {};
@@ -1,43 +1,13 @@
1
- /**
2
- * 真实执行与结算(DSH-REQ-001 §13,T005)。
3
- *
4
- * HostExecutionRunner 是「点击执行 → 独立 DSH 会话 → 按 turn 结算」的执行驱动:
5
- *
6
- * - **launch(§13.2)**:fail-closed 钉住目标——工作区必须存在于 workspace.list、
7
- * agent 预设必须存在且未损坏,任一失败直接抛错(服务层结算 failed,绝不发送
8
- * 任务 Prompt);随后创建独立会话(sessions.create,携带钉住的 workspaceId/
9
- * agentPreset)→ rename 为任务标题(**可失败但仅装饰**,§13.2)→ 钉住权限经
10
- * `/permission <id>` 命令应用(被拒/未匹配 → 失败)→ `sessions.prompt(mode:
11
- * 'queue', text = prompt || title)`。会话创建后的失败包装为
12
- * `SessionLaunchError`(携带 sessionId,服务层先回填再结算,保留跳转线索)。
13
- * - **listRunning(§13.3)**:轮询会话列表(会话列表 RPC 失败 → known:false,
14
- * 服务层据此标记 sessionStateKnown=false)。
15
- * - **inspect(§13.3)**:单条执行的结算判定——会话消失 → cancelled;仍在运行
16
- * → pending;已结束 → 翻页读历史,取时间 ≥ startedAt 的首条 `turn/end`
17
- * 判定:`reason.kind === 'error'` → failed,否则 → succeeded。
18
- *
19
- * 依赖面(§17 可测试性):本类只依赖 `ctx.apiProxy` 的窄子集(ExecutionApi)与
20
- * 命令调度器(ExecutionCommandDispatcher,`/permission` 走宿主 commands
21
- * 注册表、不经模型),测试注入 fake 即可脱离宿主单测;生产接线在 index.ts。
22
- *
23
- * 执行隔离(§16.4):任务 Prompt 只作为发给 agent 的数据;权限只能经
24
- * `/permission` 命令应用,Prompt 不能提权。
25
- */
26
1
  import type { HistoryEntry, PromptContentPart, RpcRequest, RpcResponse } from '@deepseek-ai/dsh-host-apiproxy/api';
27
2
  import type { SessionId } from '@deepseek-ai/dsh-session';
28
3
  import type { TaskRecord } from './core/model.ts';
29
- /** `/permission <id>` 命令的超时(对齐参考实现 30s)。 */
30
4
  export declare const PERMISSION_COMMAND_TIMEOUT_MS = 30000;
31
- /** inspect 历史翻页上限(对齐参考实现)。 */
32
5
  export declare const MAX_HISTORY_PAGES = 100;
33
- /** inspect 每页事件数(对齐参考实现)。 */
34
6
  export declare const HISTORY_PAGE_SIZE = 100;
35
- /** 会话列表行(inspect 所需的最小面;SDK SessionSummary 是其超集)。 */
36
7
  export interface ExecutionSessionRow {
37
8
  sessionId: string;
38
9
  running: boolean;
39
10
  }
40
- /** 5s 轮询的会话列表状态(§13.3):RPC 失败时 known:false(服务层据此降级)。 */
41
11
  export type ExecutionSessionState = {
42
12
  known: true;
43
13
  count: number;
@@ -45,20 +15,13 @@ export type ExecutionSessionState = {
45
15
  } | {
46
16
  known: false;
47
17
  };
48
- /** 单条执行的结算判定(§13.3)。 */
49
18
  export type ExecutionOutcome = 'pending' | 'cancelled' | 'failed' | 'succeeded';
50
19
  export interface ExecutionInspection {
51
20
  outcome: ExecutionOutcome;
52
21
  error?: string;
53
- /** 本次执行(startedAt 之后)最近的 assistant 消息文本(B5:Host 侧摘取摘要源)。 */
54
22
  lastAssistantText?: string;
55
23
  }
56
- /** `/permission <id>` 命令调度面(index.ts 接线到宿主 ctx.commands,见 §16.4)。 */
57
24
  export interface ExecutionCommandDispatcher {
58
- /**
59
- * 在会话上执行一条斜杠命令(不经模型)。undefined = 未被承认(命令名不匹配/
60
- * 语法未解析);kind:'error' = 被拒/未匹配。
61
- */
62
25
  execute(sessionId: string, line: string, signal: AbortSignal): Promise<{
63
26
  kind: 'success';
64
27
  text?: string;
@@ -67,10 +30,6 @@ export interface ExecutionCommandDispatcher {
67
30
  text: string;
68
31
  } | undefined>;
69
32
  }
70
- /**
71
- * HostExecutionRunner 依赖的 API 窄面(ctx.apiProxy 的结构子集)。只声明
72
- * launch/inspect 用到的域方法;生产传完整 ApiProxy(结构兼容),测试传 fake。
73
- */
74
33
  export interface ExecutionApi {
75
34
  workspace: {
76
35
  list(request: RpcRequest<{}>): Promise<RpcResponse<{
@@ -127,62 +86,18 @@ export interface ExecutionApi {
127
86
  }>>;
128
87
  };
129
88
  }
130
- /** 会话创建后的启动失败:仍标识会话(服务层先回填 sessionId 再结算 failed)。 */
131
89
  export declare class SessionLaunchError extends Error {
132
90
  readonly sessionId: string;
133
91
  constructor(sessionId: string, cause: unknown);
134
92
  }
135
- /** 会话 transcript 全文读取上限(T012/B8:拆分会话结果解析;防超大会话内存放大)。 */
136
93
  export declare const TRANSCRIPT_TEXT_LIMIT = 200000;
137
- /**
138
- * 真实执行与结算驱动(§13.2/§13.3)。无状态、无计时器——轮询调度由
139
- * TaskBoardHostService 持有(5s 间隔);本类只做单次 launch/inspect。
140
- */
141
94
  export declare class HostExecutionRunner {
142
95
  private readonly api;
143
96
  private readonly commands;
144
- /** @param api - apiProxy 窄面(workspace/agentPresets/sessions 的读写子集)。 */
145
97
  constructor(api: ExecutionApi, commands: ExecutionCommandDispatcher | undefined);
146
- /**
147
- * 启动一次执行(§13.2 流程):
148
- * 1. fail-closed 校验钉住目标(工作区存在、预设存在且未损坏)——任一失败抛
149
- * 错,调用方结算 failed,**不发送任务 Prompt**;
150
- * 2. `sessions.create` 创建独立会话(携带钉住的 workspaceId/agentPreset);
151
- * 3. rename 为任务标题(可失败但仅装饰);权限经 `/permission` 命令应用
152
- * (被拒/未匹配 → 失败);
153
- * 4. `sessions.prompt(mode:'queue', text = prompt || title)`。
154
- *
155
- * 会话创建之后的任何失败抛 `SessionLaunchError`(携带 sessionId)。
156
- * @returns 创建的会话 id。
157
- */
158
98
  launch(task: TaskRecord): Promise<string>;
159
- /**
160
- * 轮询会话列表(§13.3,5s 一次由服务层调度)。RPC 失败/不可用 → known:false
161
- * (服务层据此把 sessionStateKnown 置 false,不据此结算)。
162
- */
163
99
  listRunning(): Promise<ExecutionSessionState>;
164
- /**
165
- * 单条执行的结算判定(§13.3):
166
- * - 会话不在列表中 → cancelled(`execution session no longer exists`);
167
- * - 会话仍在运行 → pending;
168
- * - 已结束 → 翻页读历史直到越过 startedAt 边界,取时间 ≥ startedAt 的首条
169
- * `turn/end`:reason.kind==='error' → failed,否则 → succeeded;边界内无
170
- * turn/end(如 prompt 从未被消费)→ pending(保持观察)。
171
- * @param sessions - 本次轮询已取的会话列表(避免 1 + E 次 list RPC,对齐参考
172
- * 实现);缺省时自行拉取。
173
- */
174
100
  inspect(sessionId: string, startedAt: number, sessions?: readonly ExecutionSessionRow[]): Promise<ExecutionInspection>;
175
- /**
176
- * 停止底层会话(§13.3 用户取消执行):尽力而为——RPC 失败/被拒/会话不存在
177
- * 都只记日志,不抛给控制面(账本侧 cancelled 结算已权威,本调用只是避免
178
- * 孤儿 agent 继续空转/烧额度)。
179
- */
180
101
  cancelSession(sessionId: string): Promise<void>;
181
- /**
182
- * 读取会话 transcript 全文(T012/B8:拆分会话结果解析用)。翻页读取历史事件,
183
- * 拼接全部 assistant 消息文本(顺序拼接、换行分隔;总长封顶
184
- * TRANSCRIPT_TEXT_LIMIT)。会话不存在/历史不可读 → undefined(调用方按失败
185
- * 处理)。拆分会话是独立专用会话,全文即拆分结果(无需按 startedAt 截取)。
186
- */
187
102
  readTranscriptText(sessionId: string): Promise<string | undefined>;
188
103
  }
@@ -1,130 +1,34 @@
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
1
  import type { SplitRequestInput, TaskRecord } from './core/model.ts';
61
2
  import { HostLedger } from './host-ledger.ts';
62
3
  import type { TaskBoardAutomationDeps } from './host-automation.ts';
63
4
  import type { ExecutionInspection, ExecutionSessionRow, ExecutionSessionState } from './host-runner.ts';
64
5
  import type { TaskBoardAction, TaskBoardEventPayload, TaskBoardPowerSnapshot, TaskBoardSnapshot } from './protocol.ts';
65
- /** 会话轮询间隔(§17 性能:5s,对齐参考实现)。 */
66
6
  export declare const SESSION_POLL_MS = 5000;
67
- /** 调度 tick 间隔(§13.1/§17 性能:30s,对齐参考实现)。 */
68
7
  export declare const SCHEDULE_TICK_MS = 30000;
69
- /** 超过该间隔视为 Host 停机/睡眠/长暂停(§13.1 错过不补跑;= tick + 15s 容差)。 */
70
8
  export declare const RESUME_GAP_MS: number;
71
- /** 本服务可注入的依赖面(§17 可测试性:Host 编排用结构面注入 fake)。 */
72
9
  export interface TaskBoardHostServiceOptions {
73
- /** 账本(默认真实 HostLedger,测试注入临时目录账本)。 */
74
10
  ledger?: HostLedger;
75
- /** 时钟(测试可注入)。 */
76
11
  now?: () => number;
77
- /** 平台标识(测试可注入)。 */
78
12
  platform?: string;
79
- /** 执行驱动(T005;缺省时 run/rerun 被拒绝,测试可注入 fake)。 */
80
13
  runner?: TaskBoardExecutionRunner;
81
- /** 自动化收集依赖(T010;缺省 = 真实 fetch/文件读取,测试可注入 fake)。 */
82
14
  automation?: TaskBoardAutomationDeps;
83
15
  }
84
- /** 执行驱动面(§13):launch 启动会话、listRunning 轮询、inspect 结算判定。 */
85
16
  export interface TaskBoardExecutionRunner {
86
- /** 启动一次执行(fail-closed 钉住目标后创建会话并发送 Prompt),返回会话 id。 */
87
17
  launch(task: TaskRecord): Promise<string>;
88
- /** 拉取会话列表(失败 known:false,服务层降级 sessionStateKnown)。 */
89
18
  listRunning(): Promise<ExecutionSessionState>;
90
- /** 结算判定单条执行(pending/cancelled/failed/succeeded)。 */
91
19
  inspect(sessionId: string, startedAt: number, sessions?: readonly ExecutionSessionRow[]): Promise<ExecutionInspection>;
92
- /**
93
- * 读取会话 transcript 全文(T012/B8:需求拆分会话结果解析用);会话不存在/
94
- * 历史不可读 → undefined。拆分会话是独立专用会话,全文即拆分结果。
95
- */
96
20
  readTranscriptText(sessionId: string): Promise<string | undefined>;
97
- /**
98
- * 停止底层会话(用户取消执行,§13.3):尽力而为——缺省/失败不影响账本侧
99
- * cancelled 结算(cancel-execution 的 ledger 落账不依赖它)。可缺省(测试
100
- * fake 无需实现)。
101
- */
102
21
  cancelSession?(sessionId: string): Promise<void>;
103
22
  }
104
- /**
105
- * 需求拆分作业(T012/B8,§12.9/§14.5):一次 `start-split` 对应一个独立拆分
106
- * 会话(复用 T005 执行通道)。启动时注册(key = action.id,幂等重放不重复
107
- * 注册);会话结束(不再 running)后读取 transcript → 解析结构化标记 → 创建
108
- * 父需求任务 + `propose-batch` 原子落账(全部 proposed,人工确认闸门复用
109
- * T011)。失败写 scheduler.error(UI 可见)并移除作业。
110
- */
111
23
  export interface SplitJob {
112
- /** 拆分作业 id(= start-split action 的 id;幂等重放不重复注册)。 */
113
24
  id: string;
114
- /** 拆分请求(标题/需求来源/归属/递归父任务)。 */
115
25
  input: SplitRequestInput;
116
- /** 启动时刻(ms epoch)。 */
117
26
  startedAt: number;
118
- /** 父需求任务 id(结算时创建;children 的 parentId 指向它)。 */
119
27
  parentId: string;
120
- /** 拆分会话 id(launch 成功后回填)。 */
121
28
  sessionId?: string;
122
- /** 是否已调度启动(幂等重放不重复启动)。 */
123
29
  launched?: boolean;
124
30
  }
125
- /** 任务看板协议服务:串行 apply + 幂等 + snapshot/event/power + 执行轮询。 */
126
31
  export declare class TaskBoardHostService {
127
- /** 权威账本。 */
128
32
  readonly ledger: HostLedger;
129
33
  private readonly listeners;
130
34
  private readonly now;
@@ -140,164 +44,41 @@ export declare class TaskBoardHostService {
140
44
  private scheduleTimer;
141
45
  private tickInFlight;
142
46
  private lastScheduleTick;
143
- /** 需求拆分作业(T012/B8:独立拆分会话驱动;key = action.id)。 */
144
47
  private readonly splitJobs;
145
48
  constructor(options?: TaskBoardHostServiceOptions);
146
- /**
147
- * 应用配置(插件 enabled / preventIdleSleep)。enabled=false 时 apply 抛错
148
- * (路由层 400),SSE 订阅方通过事件帧感知 power 变化。从禁用恢复启用时
149
- * 立即补一轮会话轮询与调度 tick(首 tick 跳过停机期错过的触发点)。
150
- */
151
49
  setConfiguration(active: boolean, preventIdleSleep: boolean): void;
152
- /** 全量状态快照(§11.1 `GET /state`)。 */
153
50
  snapshot(): TaskBoardSnapshot;
154
- /** SSE 事件帧载荷(§11.1 `GET /events`):只推 revision/scheduler/power。 */
155
51
  eventPayload(): TaskBoardEventPayload;
156
- /** 订阅账本/配置变更(SSE 推送源;路由层在推送前先主动推一帧)。 */
157
52
  subscribe(listener: () => void): () => void;
158
- /**
159
- * 幂等应用一个已解析的 action(§11.3):账本层判定 requestId + SHA-256 指纹,
160
- * 命中缓存直接返回当前 state(不 bump);首次请求经状态机守卫原子落账。
161
- * 守卫/校验失败抛错(`TaskBoardTransitionError` 等),路由层转为 400。
162
- *
163
- * T005:`run/rerun` 在本次 apply 真实开启执行(revision +1)时,异步调度
164
- * launch(创建会话 + 发送 Prompt);幂等重放(revision 不变)不再重复启动。
165
- */
166
53
  apply(requestId: string, action: TaskBoardAction): TaskBoardSnapshot;
167
- /**
168
- * 启动执行轮询(§13.3):5s 间隔轮询会话列表结算 open 执行,启动时立即
169
- * 轮询一次(覆盖 Host 重启后带 sessionId 的 running 执行)。幂等;无 runner
170
- * 时 no-op(纯账本模式不轮询)。dispose 时停止。
171
- *
172
- * T006/T007 调度 tick(§13.1/§13.5):30s 间隔扫描到期 cron 与 one-shot,
173
- * 启动时立即 tick 一次——首个 tick 视为恢复(错过触发点跳过不补跑,见
174
- * tickSchedule)。
175
- */
176
54
  start(): void;
177
- /** 释放:轮询/调度定时器、账本锁、订阅。 */
178
55
  dispose(): void;
179
- /** 电源保护快照(§11.1 power 字段;保护实现随后续任务,本期为基础值 + 配置态)。 */
180
56
  powerSnapshot(): TaskBoardPowerSnapshot;
181
- /** 状态机分发:协议 action → transitions 纯函数(就地修改账本文档并返回是否变更)。 */
182
57
  private applyAction;
183
- /** 异步调度 launch(fire-and-forget;结算失败仅记录日志,不中断控制面)。 */
184
58
  private scheduleLaunch;
185
- /**
186
- * 异步停止底层会话(用户取消执行,§13.3):尽力而为——runner 未接线/
187
- * 不支持 cancelSession 时 no-op;RPC 失败仅记日志(账本侧 cancelled 结算
188
- * 已权威,不因会话停止失败而回滚)。
189
- */
190
59
  private scheduleSessionCancel;
191
- /**
192
- * launch 编排(§13.2 尾部):runner 创建会话成功 → 回填 sessionId(之后由
193
- * 轮询结算);任何失败 → 结算 failed 并保留错误文本(会话创建后的失败携带
194
- * sessionId,先回填再结算,详情页保留跳转线索)。
195
- */
196
60
  private launchExecution;
197
- /** 轮询调度(防重入):单轮在途时跳过,dispose 后不再启动。 */
198
61
  private schedulePoll;
199
- /**
200
- * 单轮结算轮询(§13.3):会话列表 → 逐条判定 open 执行;会话列表不可知时
201
- * 仅降级 sessionStateKnown(不结算,避免误判)。重启恢复语义:
202
- * - 有 sessionId 的 running 执行 → 继续观察结算(本方法覆盖);
203
- * - 无 sessionId 的启动中断 → HostLedger 加载时已标 cancelled(core/recovery.ts),
204
- * 本方法跳过(禁止自动重发)。
205
- * T012:同一轮次顺带结算需求拆分作业(复用会话列表,1 + E,B8)。
206
- */
207
62
  private pollSessions;
208
- /** 账本中全部未结算执行的最小投影(taskId/executionId/sessionId/startedAt)。 */
209
63
  private openExecutions;
210
64
  private setSessionStateKnown;
211
- /** 调度 tick 调度(防重入):单轮在途时跳过,dispose 后不再启动。 */
212
65
  private scheduleTick;
213
- /**
214
- * 单轮调度 tick(§13.1/§13.5):
215
- * - 记录 lastTickAt(setScheduler 不 bump revision,§17 性能);
216
- * - **恢复判定**:首个 tick(启动/从禁用恢复)或距上次 tick 超过
217
- * RESUME_GAP_MS(Host 停机/睡眠/长暂停)→ 跳过错过的触发点(skipMissed,
218
- * 不补跑),并把全部到期规则滚动到 now 之后的下一个匹配点(cron)或写
219
- * firedAt 消费(one-shot);
220
- * - 正常 tick:扫描到期 cron(enabled 且 nextRunAt ≤ now 且非归档)与到期
221
- * one-shot(firedAt 缺省且 runAt ≤ now 且非归档),逐条开启执行(复用 T005
222
- * launch 入口);同任务 running 中到期 → cron 只滚动不触发、one-shot 只写
223
- * firedAt 不触发(openScheduled/openScheduledOneShot 内部判定)。
224
- */
225
66
  private tickSchedule;
226
- /** 账本中当前到期(enabled 且 nextRunAt ≤ now)的自动化规则最小投影。 */
227
67
  private dueAutomationRules;
228
- /** 账本中当前到期(enabled 且 nextRunAt ≤ now)的 cron 规则最小投影。 */
229
68
  private dueSchedules;
230
- /**
231
- * 开启一次到期的调度执行(§13.1/§13.2 尾部):
232
- * - 任务不存在/已归档 → no-op;
233
- * - 任务 running 或带未结算执行 → **不并发、不排队**:本次只把 nextRunAt
234
- * 滚动到下一匹配点(lastTriggeredAt 保留现值,本次不记触发),返回 undefined;
235
- * - 否则开启执行(置 running + 追加 open 执行记录,startedAt=触发时刻)并把
236
- * 规则滚动到下一匹配点(lastTriggeredAt=本次触发点),返回开启的执行供
237
- * scheduleLaunch 启动会话。
238
- */
239
69
  private openScheduled;
240
- /**
241
- * 开启一次到期的 one-shot 执行(§13.5):
242
- * - 任务不存在/已归档 → no-op;
243
- * - 任务 running 或带未结算执行 → **不并发、不排队**:本次仍标记消费
244
- * (写 firedAt),但不触发执行,返回 undefined;
245
- * - 否则先标记消费(firedAt=now,触发即消费——防重启后重复触发),再开启
246
- * 执行(置 running + 追加 open 执行记录,startedAt=触发时刻),返回开启的
247
- * 执行供 scheduleLaunch 启动会话。
248
- */
249
70
  private openScheduledOneShot;
250
- /** 账本中当前到期(firedAt 缺省且 runAt ≤ now)的 one-shot 规则最小投影。 */
251
71
  private dueOneShots;
252
- /** 异步调度一次收集(fire-and-forget;失败记日志,不中断控制面)。 */
253
72
  private scheduleAutomationRun;
254
- /**
255
- * 执行一次收集(§14.2):按规则来源分发(书签收件箱文件 / GitHub API),产物
256
- * 经 applyAddCollectedTasks 去重落 backlog(同 (source, url) 幂等);收集失败
257
- * 写入可见错误(scheduler.error,UI 可见),不抛给控制面。
258
- */
259
73
  private runAutomationRule;
260
- /**
261
- * GitHub Webhook `issues` 事件摄入(§14.1):命令类字段递归拒绝(§14.3/§16.2,
262
- * 与 import 同一条红线);只处理 opened/reopened;按启用 github_issue 规则的
263
- * repo(配置了时须匹配)与标签过滤创建 backlog 任务(跨规则去重,同 url 只建
264
- * 一条)。返回本次实际创建数(含账本去重)。
265
- */
266
74
  ingestGithubWebhook(payload: unknown): {
267
75
  created: number;
268
76
  };
269
- /**
270
- * 注册拆分作业(start-split 的变更闭包):校验递归父任务引用(存在性 + 防环,
271
- * §16.8)后登记作业并返回 false(不产生账本变更、revision 不 bump——拆分
272
- * 结果在拆分会话结算时落账)。幂等重放命中缓存不会调用本闭包 → 不重复注册。
273
- */
274
77
  private registerSplitJob;
275
- /** 异步调度拆分会话启动(fire-and-forget;失败记 scheduler.error,不中断控制面)。 */
276
78
  private scheduleSplitLaunch;
277
- /**
278
- * 启动拆分会话(B8):合成拆分驱动任务(标题「需求拆分:<标题>」、Prompt =
279
- * 拆分指令模板)复用 runner.launch(fail-closed 校验 + 创建会话 + 发送 Prompt);
280
- * 会话 id 回填作业(供轮询结算)。启动失败 → 记 scheduler.error 并移除作业。
281
- */
282
79
  private launchSplit;
283
- /** 拆分作业失败:移除作业 + scheduler.error(UI 可见)。 */
284
80
  private failSplit;
285
- /**
286
- * 单轮拆分作业结算(B8,复用 pollSessions 已取的会话列表,1 + E):
287
- * - sessionId 未回填(launch 进行中)→ 跳过;
288
- * - 会话不在列表(被删/从未创建成功)→ 失败;
289
- * - 会话仍在运行 → 跳过(等待 agent 完成拆分);
290
- * - 会话已结束 → 读取 transcript、解析标记、原子落账(settleSplit)。
291
- */
292
81
  private pollSplitJobs;
293
- /**
294
- * 拆分结算(§12.9/§14.5):解析 transcript 的结构化标记(`⟦task-board:split⟧`
295
- * 逐条 + `⟦task-board:coverage⟧` 覆盖矩阵)→ 创建父需求任务 + `propose-batch`
296
- * 原子落账(同一 mutate 闭包,任一失败整体回滚、revision 不变——§16.8)。
297
- * 产物全部 proposed(source=requirement,人工确认闸门复用 T011);children
298
- * parentId 指向父需求任务;父任务递归拆分时挂到原任务下(§12.9);候选
299
- * metadata 携带拆分会话引用(可跳回追溯,§15)。解析不到任何条目 → 失败。
300
- */
301
82
  private settleSplit;
302
83
  private emit;
303
84
  }