dsh-task-dispatch-table 0.0.0 → 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 (48) hide show
  1. package/README.md +95 -31
  2. package/README.zh.md +113 -0
  3. package/assets/icon-scheduler.svg +1 -0
  4. package/cordis.patch.yml +9 -0
  5. package/dist/attachment-allowlist.d.ts +24 -0
  6. package/dist/attachment-allowlist.js +41 -0
  7. package/dist/calendar-plan.d.ts +55 -0
  8. package/dist/calendar-plan.js +104 -0
  9. package/dist/client.js +72655 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/config.d.ts +79 -0
  12. package/dist/config.js +80 -0
  13. package/dist/deps.d.ts +25 -0
  14. package/dist/deps.js +54 -0
  15. package/dist/dispatch.d.ts +133 -0
  16. package/dist/dispatch.js +509 -0
  17. package/dist/event-bus.d.ts +16 -0
  18. package/dist/event-bus.js +67 -0
  19. package/dist/event-catalog.d.ts +42 -0
  20. package/dist/event-catalog.js +60 -0
  21. package/dist/host.d.ts +299 -0
  22. package/dist/host.js +1 -0
  23. package/dist/index.d.ts +25 -0
  24. package/dist/index.js +0 -0
  25. package/dist/receipt.d.ts +38 -0
  26. package/dist/receipt.js +236 -0
  27. package/dist/reconcile.d.ts +105 -0
  28. package/dist/reconcile.js +641 -0
  29. package/dist/runtime-index.d.ts +102 -0
  30. package/dist/runtime-index.js +340 -0
  31. package/dist/schedule-next.d.ts +44 -0
  32. package/dist/schedule-next.js +216 -0
  33. package/dist/scheduler.d.ts +82 -0
  34. package/dist/scheduler.js +639 -0
  35. package/dist/store.d.ts +461 -0
  36. package/dist/store.js +845 -0
  37. package/dist/submit.d.ts +1 -0
  38. package/dist/submit.js +60 -0
  39. package/dist/task-assets.d.ts +124 -0
  40. package/dist/task-assets.js +380 -0
  41. package/dist/task-sort.d.ts +27 -0
  42. package/dist/task-sort.js +59 -0
  43. package/dist/tasks.d.ts +191 -0
  44. package/dist/tasks.js +527 -0
  45. package/locale/en.json +6 -0
  46. package/locale/zh.json +6 -0
  47. package/package.json +67 -6
  48. package/index.js +0 -7
@@ -0,0 +1,461 @@
1
+ import type { ResolvedDependency } from './deps.js';
2
+ /** 单表查询支持的过滤运算符(白名单防注入)。 */
3
+ export type TableFilterOp = '=' | '!=' | '<' | '>' | '<=' | '>=' | 'LIKE';
4
+ export declare const TABLE_FILTER_OPS: readonly TableFilterOp[];
5
+ /** 单表查询的过滤条件(列名 / 运算符 / 值,全部经白名单 + 占位绑定)。 */
6
+ export interface TableFilter {
7
+ column: string;
8
+ op: TableFilterOp;
9
+ value: string;
10
+ }
11
+ /** 单表查询结果(设置页 Block 3 / Block 2 共用)。 */
12
+ export interface TableQueryResult {
13
+ name: string;
14
+ count: number;
15
+ columns: string[];
16
+ rows: Record<string, unknown>[];
17
+ truncated: boolean;
18
+ }
19
+ /**
20
+ * Agent 权限档位(决策 50):`default` = 会话默认(沿用宿主新建会话的权限设置,不加约束)。
21
+ * ⚠️ 与 src/client/task-editor.tsx 的同名类型**两处各写一份**(client bundle 不引 host 模块),
22
+ * 改枚举务必两边同步。
23
+ */
24
+ export type PermissionMode = 'default' | 'readOnly' | 'workspace' | 'full';
25
+ export declare const PERMISSION_MODES: readonly PermissionMode[];
26
+ export declare const TERMINAL_STATUSES: readonly ["succeeded", "failed", "skipped"];
27
+ export type InstanceStatus = 'pending' | 'dispatched' | 'running' | 'succeeded' | 'failed' | 'skipped' | 'unknown';
28
+ /**
29
+ * 执行记录的**触发来源**(2026-10-03 用户拍板新增,落地 U5 预留字段):
30
+ * - `scheduled` = 按排期自动调度(Loop A 建行,缺省值);
31
+ * - `manual` = 用户在卡片上点「立即执行」手动触发一次。
32
+ * 只落库记录,当前**没有任何读路径 / UI 展示消费它**(用户:读取的地方不用读、前端展示先不改)。
33
+ */
34
+ export type InstanceRunType = 'scheduled' | 'manual';
35
+ export interface TaskInstance {
36
+ id: string;
37
+ task_id: string;
38
+ logical_date: string;
39
+ scheduled_at: string;
40
+ status: InstanceStatus;
41
+ attempt: number;
42
+ /** 触发来源(2026-10-03):'scheduled' 自动调度 / 'manual' 手动立即执行;旧行为 null。 */
43
+ run_type: InstanceRunType | null;
44
+ session_id: string | null;
45
+ lease_until: string | null;
46
+ dispatched_at: string | null;
47
+ finished_at: string | null;
48
+ outputs: string | null;
49
+ token_in: number | null;
50
+ token_out: number | null;
51
+ token_in_cache: number | null;
52
+ /** 派发快照(决策 41):落库时固化的执行所需字段 JSON;旧行 / 异常为 null。 */
53
+ snapshot: string | null;
54
+ updated_at: string;
55
+ /** 备注(非表列):失败 / 跳过原因,由 task_events 最新原因事件推导(listInstancesByQuery 填充)。 */
56
+ note?: string | null;
57
+ }
58
+ /**
59
+ * 一条已解析的上游依赖(决策 43):Loop A 判定通过时固化,Loop B 只读不重判。
60
+ * 真源已上提到 `deps.ts`(零运行时依赖,客户端「接收」区共用同一份解析),此处只做转出。
61
+ */
62
+ export type { ResolvedDependency } from './deps.js';
63
+ /**
64
+ * 派发快照(决策 41):Loop A 落库时固化,Loop B(发动 / 重试 / 追问 / 回执裁决)**只读快照**,
65
+ * 与任务设置彻底解耦——中途改任务定义对已落库实例零影响。
66
+ */
67
+ export interface InstanceSnapshot {
68
+ /** /goal 多轮续跑(决策 48):undefined 视为 true(用户拍板「默认都是多轮会话」)。 */
69
+ goal?: boolean;
70
+ /** 多 Agent 协作(决策 49):undefined 视为 false;派发时探测宿主 ctx.agentTeams,缺则降级单轮。 */
71
+ agentTeam?: boolean;
72
+ /** Agent 权限档位(决策 50):undefined 视为 'default'(会话默认,不额外约束)。 */
73
+ permission?: PermissionMode;
74
+ /** 会话显示名(决策 42):title 回退 code,再回退短 id。 */
75
+ title: string;
76
+ prompt: string;
77
+ manual: string | null;
78
+ /** 解析后的工作区实体 path(cwd / attachSession / 回执 outputs 校验都用它)。 */
79
+ workspacePath: string;
80
+ /** 模型漏斗第①层提示(任务 target.provider/model;②③④层在发动时按插件配置 / 宿主现算)。 */
81
+ provider: string;
82
+ model: string;
83
+ validStatuses: string[];
84
+ maxAttempts: number;
85
+ /** ISO 时长串(超窗判定用,决策 41:快照管「已开工的」窗口边界)。 */
86
+ window: string;
87
+ /** 依赖快照(决策 43):判定通过那一刻命中的上游实例与产出;决策 41 旧行无此字段。 */
88
+ resolvedDeps?: ResolvedDependency[];
89
+ /**
90
+ * 附加文件清单(2026-09-30):Loop B 发动前要校验「附件还在不在」⇒ 必须随快照冻结,
91
+ * 否则 Loop B 只能回头读任务定义,违反决策 41「只读快照」。旧行无此字段 = 无附件。
92
+ */
93
+ attachments?: SnapshotAttachment[];
94
+ }
95
+ /** 快照里的附件引用(只记引用,不存内容)。 */
96
+ export interface SnapshotAttachment {
97
+ name: string;
98
+ kind: 'link' | 'upload';
99
+ ref: string;
100
+ /** link 型:来源工作区 title(派发注入 / 校验时按它把 ref 绝对化)。 */
101
+ workspace?: string;
102
+ }
103
+ /** 解析实例行的快照 JSON;空 / 坏 JSON / 形状不对返回 undefined(调用方走兜底)。 */
104
+ export declare function parseInstanceSnapshot(raw: string | null): InstanceSnapshot | undefined;
105
+ /** 调试快照事件行(detail 截断,临时调试面板用)。带 instance_id 供面板按实例过滤展开。 */
106
+ export interface SnapshotEvent {
107
+ seq: number;
108
+ instance_id: string;
109
+ ts: string;
110
+ kind: string;
111
+ detail: string | null;
112
+ }
113
+ /** 单实例状态转移 + task_events 追加的参数包。 */
114
+ export interface TransitionInput {
115
+ status: InstanceStatus;
116
+ attempt?: number;
117
+ session_id?: string | null;
118
+ lease_until?: string | null;
119
+ dispatched_at?: string | null;
120
+ finished_at?: string | null;
121
+ detail?: unknown;
122
+ }
123
+ /** 调试导出:一张表的原始行(面板「调试」页 / GET /db 的单元)。 */
124
+ export interface TableDump {
125
+ name: string;
126
+ /** 表内总行数(rows 可能只含最新一部分)。 */
127
+ count: number;
128
+ /** 列名,按建表顺序。 */
129
+ columns: string[];
130
+ /** 行原样(列 → TEXT/INTEGER/NULL 值)。 */
131
+ rows: Record<string, unknown>[];
132
+ /** true = 总行数超出 limit,rows 只含最新 limit 条。 */
133
+ truncated: boolean;
134
+ }
135
+ export interface InstanceQuery {
136
+ taskId?: string;
137
+ /**
138
+ * 按会话 id 精确取(2026-10-03):会话弹窗**只拿得到会话 id**(不再靠调用方传快照 / 产出)⇒
139
+ * 所有入口渲染必然一致。一个会话最多对应一条实例行,故配合 `limit: 1` 用。
140
+ */
141
+ sessionId?: string;
142
+ /** 已解析的工作区任务集合(workspace 过滤由调用方解析,store 不持有任务定义)。 */
143
+ taskIds?: readonly string[];
144
+ statuses?: readonly InstanceStatus[];
145
+ /** scheduled_at 区间(含端点,ISO 字符串)。 */
146
+ fromTs?: string;
147
+ toTs?: string;
148
+ /** 编码末行 `(scheduled_at, id)`。 */
149
+ cursor?: string;
150
+ limit?: number;
151
+ }
152
+ export interface InstancePage {
153
+ rows: TaskInstance[];
154
+ /** 还有下一页时为末行游标,否则 null。 */
155
+ nextCursor: string | null;
156
+ }
157
+ /**
158
+ * 轻量执行记录行(「任务日程」日历页按月取数用,2026-10-05)= 实例行**剔除 `snapshot` 列**。
159
+ *
160
+ * 为什么剔除:snapshot 是派发快照(提示词 / 附件 / 依赖的 JSON,单行 1.5–4 KB),
161
+ * 日历要一次取满整月(几百到上千行),带着它就是数 MB 的白给开销;日历只要状态 / 时刻 / 产出 / 原因。
162
+ */
163
+ export type LiteTaskInstance = Omit<TaskInstance, 'snapshot'>;
164
+ export interface LiteInstancePage {
165
+ rows: LiteTaskInstance[];
166
+ /** 触及上限被截断(调用方必须提示用户收窄过滤,**不静默丢**)。 */
167
+ truncated: boolean;
168
+ }
169
+ export interface LogQuery {
170
+ taskId?: string;
171
+ taskIds?: readonly string[];
172
+ levels?: readonly string[];
173
+ /** **message 与 kind 都做**子串匹配(LIKE %kw%);见 `listLogs` 处的说明。 */
174
+ keyword?: string;
175
+ fromTs?: string;
176
+ toTs?: string;
177
+ /** 编码末行 `(ts, seq)`。 */
178
+ cursor?: string;
179
+ limit?: number;
180
+ }
181
+ export interface LogRow {
182
+ seq: number;
183
+ ts: string;
184
+ task_id: string | null;
185
+ scheduled_at: string | null;
186
+ level: string;
187
+ kind: string;
188
+ message: string;
189
+ }
190
+ export interface LogPage {
191
+ rows: LogRow[];
192
+ nextCursor: string | null;
193
+ }
194
+ /** 某实例的事件时间线(执行记录下钻用),按 seq 升序(旧→新)。 */
195
+ export interface InstanceEventRow {
196
+ seq: number;
197
+ ts: string;
198
+ kind: string;
199
+ detail: string | null;
200
+ }
201
+ /**
202
+ * **插件整体日志的语义枚举**(`plugin_log.kind`)—— 2026-10-07。
203
+ *
204
+ * ⚠️ **新增 / 改名必须同步** `docs/design/data-model.md` §二 的表注释,否则文档与代码不一致
205
+ * (本仓已发生过:文档 §二 漏掉 `task_audit`,直到本次排查才发现)。
206
+ *
207
+ * 选用哪一类,按「**这件事挂不挂得上某个任务**」判断:
208
+ * - 挂得上 ⇒ 回 `task_log` / `task_events`,**不要扔这里**;
209
+ * - 挂不上(是插件进程自己的事)⇒ 才用本表。
210
+ */
211
+ export type PluginLogKind = 'startup' | 'shutdown' | 'degraded' | 'block' | 'slow_request' | 'route_error' | 'stream_open' | 'stream_close' | 'stream_limit' | 'stream_write_failed' | 'snapshot' | 'config' | 'tick_error' | 'dispatch' | 'error';
212
+ export declare class TaskStore {
213
+ private readonly db;
214
+ /** 事务嵌套深度(`transaction` 用):> 0 = 已在事务里 ⇒ 内层并入外层,不再 BEGIN。 */
215
+ private txDepth;
216
+ /**
217
+ * 迁移时因「同任务同刻度重复」被合并掉的行数。
218
+ * > 0 说明历史数据里存在重复(正常不该有),宿主会打告警——**绝不静默删数据**。
219
+ */
220
+ dupRowsRemoved: number;
221
+ constructor(statePath: string);
222
+ /**
223
+ * 旧库迁移(决策 25 + 决策 32):
224
+ * - 首次:去重(同一 task + 同一刻度只留 rowid 最小那条)后补建 `(task_id, scheduled_at)`
225
+ * 唯一索引作为防重闸门;
226
+ * - 已迁移过的库:仅补决策 32 冗余字段(outputs / tokens 列),不重复去重。
227
+ */
228
+ private migrate;
229
+ /** 决策 32(修订):兼容旧库(无 outputs / token 三拆列)。 */
230
+ private ensureInstanceColumns;
231
+ close(): void;
232
+ /**
233
+ * 把多次写库合成一个**原子单元**(2026-09-30 复核 P2):`ensureSkipped` → `appendEvent`(→ `appendLog`)
234
+ * 原先是两/三步独立写,中途被杀会留下**「执行记录里有这条未执行、展开却看不到为什么」**的半截记录。
235
+ *
236
+ * - `node:sqlite` 的 `DatabaseSync` 没有事务包装,但 `exec('BEGIN'/'COMMIT'/'ROLLBACK')` 与
237
+ * `prepare(...).run()` 走**同一条连接** ⇒ 天然同事务。用 `BEGIN IMMEDIATE` 立刻取写锁,
238
+ * 避免「先读后写」在事务中途升级锁失败(配合构造里的 `busy_timeout = 5000`)。
239
+ * - **嵌套守卫**:SQLite 不支持嵌套 BEGIN(会抛 `cannot start a transaction within a transaction`)
240
+ * ⇒ `txDepth > 0` 时内层直接并入外层,原子性由**最外层**统一提交 / 回滚。
241
+ * - 异常一律 `ROLLBACK` 后**原样抛出**;`fn` 内返回 `false` 也算**正常结束** ⇒ 走 COMMIT,
242
+ * 否则连接会留在事务里、后续写被隐式吞进这个永远不提交的事务。
243
+ */
244
+ transaction<T>(fn: () => T): T;
245
+ appendEvent(instanceId: string, kind: string, detail?: unknown): void;
246
+ /** 实例某类事件的最新一条(回执对账 / 追问判定用)。 */
247
+ latestEvent(instanceId: string, kind: string): {
248
+ ts: string;
249
+ detail: string | null;
250
+ } | undefined;
251
+ /** 实例某类事件计数(追问次数上限用)。 */
252
+ countEvents(instanceId: string, kind: string): number;
253
+ /**
254
+ * 调试面板快照(临时调试通道):全部实例 + 最近 N 条事件。
255
+ * 事件取 seq 倒序再反转 = 升序输出(旧→新,日志阅读顺序);detail 截断 200 字符防快照膨胀。
256
+ */
257
+ snapshot(limitEvents?: number): {
258
+ instances: TaskInstance[];
259
+ events: SnapshotEvent[];
260
+ };
261
+ /**
262
+ * 主界面运行态初始化(2026-09-30):**一条聚合 SQL** 取每任务最近一条**终态**实例。
263
+ * 只认终态(succeeded/failed/skipped/unknown)——「上次执行」= 最近一次**出了结果**的执行,
264
+ * 刚落库还在跑的不算(用户 2026-09-30:运行中不得覆盖上次执行)。
265
+ * SQLite 文档化行为:查询含 min/max 聚合时,其余裸列取**该聚合所在行**的值。
266
+ * 走 `idx_instances_slot(task_id, scheduled_at)`,绝不做「每任务一次查询」。
267
+ */
268
+ lastRunByTask(): Map<string, {
269
+ status: InstanceStatus;
270
+ scheduledAt: string;
271
+ finishedAt: string | null;
272
+ }>;
273
+ /** 在飞行(dispatched / running)的实例 ⇒ 主界面「运行中」。同样一条聚合 SQL。 */
274
+ inFlightByTask(): Map<string, string>;
275
+ /** 晚于某时刻的最新回执事件(决策 19:回执对账按次取新,防止上一轮 attempt 的旧回执冒充)。 */
276
+ latestReceipt(instanceId: string, afterIso: string | undefined): {
277
+ ts: string;
278
+ detail: string | null;
279
+ } | undefined;
280
+ /** 启动扫描(state-machine §3 机制 #5):已派发而未定态的实例置 unknown。 */
281
+ startupScan(): number;
282
+ /**
283
+ * 幂等建一条实例(决策 31:懒建行,调用方先生成 id 并判定预条件通过后才调用)。
284
+ * **身份 = 任务 + 计划刻度**——去重走 `UNIQUE(task_id, scheduled_at)`,
285
+ * 同一刻度重复 INSERT 一律 DO NOTHING ⇒ tick 幂等。状态由调用方给定(现仅 'dispatched')。
286
+ * `snapshot`(决策 41):派发快照,Loop A 落库时一并固化;缺省(旧测试 / 手动 SQL)为 NULL。
287
+ */
288
+ ensureInstance(id: string, taskId: string, logicalDate: string, scheduledAt: string, status: InstanceStatus, snapshot?: InstanceSnapshot,
289
+ /** 触发来源(2026-10-03):缺省 'scheduled'(自动调度不传);手动「立即执行」传 'manual'。 */
290
+ runType?: InstanceRunType): boolean;
291
+ /**
292
+ * 补记一条「未执行」记录(决策 54):某个该跑的刻度**最终没跑**时,用它留一条痕迹
293
+ * (用户:不能只在日志里,执行记录里必须看得见)。
294
+ *
295
+ * 身份同样是「任务 + 计划刻度」(唯一索引保证同一槽只写一条、天然幂等),但状态固定 `skipped`:
296
+ * - `skipped` 属**终态** ⇒ Loop B `sweep` 只遍历 pending/dispatched/running/unknown,**永不重试**;
297
+ * `startupScan` 只改 dispatched/running,也碰不到它。
298
+ * - **绝不能用 `unknown`/`pending`**:那会被重试逻辑真的拉起来执行,或悄悄变成 failed。
299
+ *
300
+ * ⚠️ `scheduledAt` 必须是被漏掉那一槽的**真实时刻**:若用「当前槽」会撞当前槽真实执行行的唯一键,
301
+ * `INSERT OR IGNORE` 静默丢弃 ⇒ **任务永久不再执行**。
302
+ * 原因(为什么没跑)走 `appendEvent` / `appendLog`——本表没有 message 列。
303
+ */
304
+ ensureSkipped(id: string, taskId: string, logicalDate: string, scheduledAt: string): boolean;
305
+ /** 旧实例补快照(决策 41 legacy 回退:首次被 Loop B 触到时按任务定义当场合成并固化)。 */
306
+ setSnapshot(id: string, snapshot: InstanceSnapshot): void;
307
+ /** 删除一条实例(决策 31.6:窗口外残留 pending 直接删,视为未执行)。 */
308
+ deleteInstance(id: string): void;
309
+ /** 写入一条诊断日志(决策 31/32:未推进到执行那一步的诊断进 task_log,不污染 task_instances)。 */
310
+ appendLog(entry: {
311
+ taskId?: string | null;
312
+ scheduledAt?: string | null;
313
+ level: 'info' | 'warn' | 'error';
314
+ kind: string;
315
+ message: string;
316
+ }): void;
317
+ /**
318
+ * **插件整体日志**(2026-10-07):进程级的运行 / 异常 / 性能观测进 `plugin_log`,
319
+ * **不进 `task_log`**(那张表的契约是「未推进到执行那一步的任务诊断」,见建表注释与
320
+ * design/data-model.md §二)。用途:事后回答「插件这个进程当时到底怎么了」。
321
+ *
322
+ * ⚠️ 下游**不要直接调这个方法**——请用下面三个语义化的 `logInfo` / `logWarn` / `logError`,
323
+ * 它们把 `level` 钉在方法名里,调用点只剩 `kind` 与文案,不会各处重复样板、也不会写错 level。
324
+ */
325
+ appendPluginLog(entry: {
326
+ level: 'info' | 'warn' | 'error';
327
+ kind: PluginLogKind;
328
+ message: string;
329
+ }): void;
330
+ /**
331
+ * ⚠️ **记日志绝不能影响主流程**(2026-10-07 审计 🔴):这三个方法是**唯一推荐给下游**的入口,
332
+ * 必须自带异常隔离。否则:
333
+ * - `startup` 写入抛错 ⇒ 插件半初始化(定时器已种、dispose 未注册);
334
+ * - `shutdown` 写入抛错 ⇒ **后面的清理动作(退订 / 停定时器 / 关连接 / close 库)全都不执行**。
335
+ * 一条日志没记上,远比整个插件生命周期被卡住划算。
336
+ */
337
+ logInfo(kind: PluginLogKind, message: string): void;
338
+ /** 记一条 warn 级插件日志(降级 / 观测告警:功能还在,但不如预期)。 */
339
+ logWarn(kind: PluginLogKind, message: string): void;
340
+ /** 记一条 error 级插件日志(真的出错了)。 */
341
+ logError(kind: PluginLogKind, message: string): void;
342
+ /** 落库并吞掉一切异常(见 `logInfo` 的注释)。 */
343
+ private safelyAppend;
344
+ /** 按保留期清除 `plugin_log`(与 `task_log` 同策略,默认 30 天)。返回删除条数。 */
345
+ purgePluginLog(retentionDays: number): number;
346
+ /** 按保留期清除 task_log(决策 32:独立表,可定时清)。返回删除条数。 */
347
+ purgeLog(retentionDays: number): number;
348
+ /**
349
+ * 操作审计留痕(2026-09-30):新增 / 修改 / 删除任务、版本留档 / 找回、附件增删都记一笔。
350
+ * 与 `task_log`(诊断)分开:审计**默认不清**,要能回答「这任务什么时候被改成什么样」。
351
+ */
352
+ appendAudit(entry: {
353
+ taskId?: string | null;
354
+ action: string;
355
+ detail?: unknown;
356
+ }): void;
357
+ /** 审计流水(某任务的最近 N 条,新的在前)。 */
358
+ listAudit(taskId: string, limit?: number): {
359
+ ts: string;
360
+ action: string;
361
+ detail: string | null;
362
+ }[];
363
+ /**
364
+ * ⚠️ **执行记录永久保留,没有清除方法**(用户 2026-10-08 拍板):历史是用来查的、不该被清,
365
+ * 原 `purgeHistory` 与 `historyRetentionDays` 配置一并删除。
366
+ */
367
+ /** 完成瞬间写回产出与 token 三拆列(决策 32 修订:总表冗余,task_events 仍为真源)。 */
368
+ recordCompletion(id: string, outputs: string | null, tokenIn: number | null, tokenOut: number | null, tokenInCache: number | null): void;
369
+ /** 按「任务 + 刻度」查实例(手动排查 / 备用回执通道用,不依赖 id 形态)。 */
370
+ findBySlot(taskId: string, scheduledAt: string): TaskInstance | undefined;
371
+ get(id: string): TaskInstance | undefined;
372
+ /** 读 meta 键值(无行返回 undefined——「从未写过」与「写过空串」借此区分)。 */
373
+ getMeta(key: string): string | undefined;
374
+ /** 写 meta 键值(upsert)。任务表 tasksInline 的持久化主通道走这里。 */
375
+ setMeta(key: string, value: string): void;
376
+ /** 调试 / 设置页查询允许的表名(SQLite 表名无法参数化,白名单防注入)。 */
377
+ static readonly DUMP_TABLES: readonly ["task_instances", "task_events", "task_log", "task_audit", "plugin_log", "meta"];
378
+ /** 各表的「最新在前」默认排序列(设置页 Block 3 从简:每表按各自最新字段 DESC,不做排序选择器)。 */
379
+ private static defaultOrder;
380
+ /** 表列缓存(白名单校验过滤列名,避免每请求打 PRAGMA)。 */
381
+ private columnCache;
382
+ /** 取表列(建表顺序),带缓存。 */
383
+ private columnsOf;
384
+ /**
385
+ * 单表通用查询(设置页 Block 3 / Block 2 共用):按白名单表 + 占位绑定过滤 + 默认最新字段 DESC + LIMIT。
386
+ * 不做服务端分页(用户 2026-10-08:top-N 自过滤、客户端自行筛选)。表名 / 列名 / 运算符全白名单,无注入面。
387
+ * @param name 表名(必须命中 `DUMP_TABLES`)。
388
+ * @param filters 过滤条件(列名须为该表真实列,运算符须为 `TABLE_FILTER_OPS`)。
389
+ * @param limit 最多返回行数(钳制 1..500)。
390
+ */
391
+ queryTable(name: (typeof TaskStore.DUMP_TABLES)[number], filters: TableFilter[], limit: number): TableQueryResult;
392
+ /**
393
+ * 调试导出:整表原样读出(旧调试页用,保留兼容)。
394
+ * @param name - 表名(必须命中白名单)。
395
+ * @param limit - 最多返回行数;超出时保留「最新」的 limit 条。
396
+ */
397
+ dumpTable(name: (typeof TaskStore.DUMP_TABLES)[number], limit: number): TableDump;
398
+ getBySession(sessionId: string): TaskInstance | undefined;
399
+ /**
400
+ * 计划重排(决策 20):计划时刻在「从未执行」前跟随配置 live 更新——仅 status=pending
401
+ * 且 attempt=0 可改,CAS 守卫防与派发竞态;执行一旦开始(attempt≥1)即冻结。
402
+ */
403
+ reschedule(id: string, scheduledAt: string): boolean;
404
+ listByStatus(statuses: readonly InstanceStatus[]): TaskInstance[];
405
+ /** 同任务非终态实例(排除某实例自身),用于同任务串行判定(state-machine §8)。 */
406
+ listNonTerminalOfTask(taskId: string, excludeId?: string): TaskInstance[];
407
+ /** CAS 领取(data-model 关键设计 3):仅 pending 可领取,受影响行数判定成败。 */
408
+ casClaim(id: string): boolean;
409
+ /** 通用状态转移:无条件写(调用方按状态机自查前置态)并留 state_change 事件。 */
410
+ transition(id: string, input: TransitionInput): void;
411
+ /** running 心跳续租(state-machine §4):收到该会话任何事件即续租。 */
412
+ renewLease(id: string, leaseMs: number): void;
413
+ /** 依赖判定 same_period(state-machine §9):同 logical_date 的上游实例;一天多刻度取最新一条(决策 33)。 */
414
+ getSamePeriod(upstreamTaskId: string, logicalDate: string): TaskInstance | undefined;
415
+ /**
416
+ * 某任务的**最近一条**实例(不分状态,按 `scheduled_at` 倒序)——决策 33 判定用:
417
+ * 上游最近一条必须**正好是 `succeeded`** 才放行;在跑 / 失败 / 无记录 ⇒ 阻塞。
418
+ * ⚠️ 排序必须按 `scheduled_at`:原按 `logical_date` 只到「日」,同日多次取哪条不确定。
419
+ */
420
+ getLatestInstance(taskId: string): TaskInstance | undefined;
421
+ /**
422
+ * 按任务 / 工作区 + 状态 / 时间过滤的执行记录(任务卡片「执行记录」面板 + 未来总查询页共用)。
423
+ * 排序 `scheduled_at DESC, id DESC`;`cursor` 编码末行 `(scheduled_at, id)`,`LIMIT limit+1` 判定是否还有下一页
424
+ * (limit+1 弹出一行 ⇒ 有剩余才给 cursor,恰好取尽时不会多翻一页)。全部条件走占位绑定,无注入面。
425
+ */
426
+ listInstancesByQuery(q: InstanceQuery): InstancePage;
427
+ /**
428
+ * 轻量版执行记录(「任务日程」日历页按月取数用,2026-10-05)。
429
+ *
430
+ * 与 `listInstancesByQuery` **同一套过滤条件**(都走 `instanceWhere`),区别只有两点:
431
+ * ① 列清单剔除 `snapshot`(见 `LiteTaskInstance`)—— 日历一次取满整月,不带几 KB 的大列;
432
+ * ② **不分页**,上限 3000,触及即 `truncated: true`(由页面提示收窄过滤,**不静默丢**)。
433
+ * ⚠️ 既有 `listInstancesByQuery` 的 500 上限与游标语义**一个字都没改**(记录页游标分页依赖它)。
434
+ */
435
+ listInstancesLite(q: InstanceQuery): LiteInstancePage;
436
+ /**
437
+ * 执行记录过滤条件的**唯一构造**(全量查询与轻量查询共用 ⇒ SQL 条件不写第二遍)。
438
+ * 全部条件走占位绑定,无注入面。
439
+ */
440
+ private instanceWhere;
441
+ /** 轻量查询的**显式列清单**:与 `SELECT *` 的唯一差别是剔除 `snapshot`(见 `LiteTaskInstance`)。 */
442
+ private static readonly LITE_INSTANCE_COLUMNS;
443
+ /** 轻量查询上限:比全量查询的 500 宽(轻量行约 200 B),够日历一次取满一个月的正常量级。 */
444
+ private static readonly LITE_INSTANCE_LIMIT;
445
+ /** 「原因类」事件 kind 白名单(写原因的只有这几类;receipt.note / *.reason)。 */
446
+ private static readonly NOTE_EVENT_KINDS;
447
+ /** detail JSON → 人话原因:receipt 取 note,其余取 reason;取不到回退原文。 */
448
+ private static noteOfEvent;
449
+ /**
450
+ * 给分页行就地填 note(只查 failed / skipped 行,一次 IN 查询取每实例最新原因事件)。
451
+ * 入参放宽成最小结构 ⇒ 全量行与轻量行(`LiteTaskInstance`,无 snapshot)都能走这一份实现。
452
+ */
453
+ private attachNotes;
454
+ /**
455
+ * 按任务 / 工作区 + 级别 / 关键字 / 时间过滤的诊断日志(任务卡片「日志」面板 + 未来总查询页共用)。
456
+ * 排序 `ts DESC, seq DESC`;`cursor` 编码末行 `(ts, seq)`。`keyword` 走 `LIKE %kw%`(参数化,不拼 SQL)。
457
+ */
458
+ listLogsByQuery(q: LogQuery): LogPage;
459
+ /** 某实例的事件时间线(执行记录下钻用):seq 升序 = 旧→新,日志阅读顺序。 */
460
+ listEventsByInstance(instanceId: string): InstanceEventRow[];
461
+ }