dsh-task-dispatch-table 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.js +220 -49
- package/dist/client.js.map +1 -1
- package/dist/dispatch.js +4 -0
- package/dist/index.js +0 -0
- package/dist/receipt.js +67 -20
- package/dist/reconcile.d.ts +1 -1
- package/dist/reconcile.js +21 -10
- package/dist/runtime-index.d.ts +1 -0
- package/dist/scheduler.js +3 -0
- package/dist/store.d.ts +11 -4
- package/dist/store.js +10 -5
- package/dist/task-assets.d.ts +2 -0
- package/dist/tasks.d.ts +1 -0
- package/dist/tasks.js +2 -0
- package/package.json +1 -1
package/dist/dispatch.js
CHANGED
|
@@ -266,6 +266,10 @@ times) {
|
|
|
266
266
|
snapshot.prompt,
|
|
267
267
|
'',
|
|
268
268
|
`任务实例:${snapshot.title} · ${logicalDate}(目标工作区:${workspacePath})`,
|
|
269
|
+
'',
|
|
270
|
+
'【执行约束】本任务是定时自动派发、无人值守运行的:执行期间没有人会回复你。'
|
|
271
|
+
+ '因此:① 不得向用户提问或等待答复——信息不足就基于现有上下文自行决策并继续,必要时在回执 note 中如实记录;'
|
|
272
|
+
+ '② 不得申请、索取或等待任何额外权限或授权——严格在已授予的权限范围内执行,超范围的操作跳过并在回执中说明,不要停下来要许可。',
|
|
269
273
|
];
|
|
270
274
|
// 执行时间两条(用户 2026-10-05 拍板,U4 收口):模型对「现在几点」未必可靠(真机 once8 产出
|
|
271
275
|
// 文件日期差过一天)⇒ 把**原定时刻**与**实际派发时刻**原样下发(ISO 8601 / UTC,精确到秒),
|
package/dist/index.js
CHANGED
|
Binary file
|
package/dist/receipt.js
CHANGED
|
@@ -38,16 +38,34 @@ function normalizeStatus(raw, statuses) {
|
|
|
38
38
|
return statuses.find(status => status.toLowerCase() === wanted);
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* 产出归一:数组取字符串项;字符串按逗号切(模型偶尔传逗号串);其余忽略。
|
|
42
42
|
* 逐项去空白 / 零宽字符、反斜杠统一成 `/`、去掉 `./` 前缀(宿主对账按工作区相对路径 `resolve`)。
|
|
43
|
+
* 同一桶里的重复路径只留第一次(模型偶尔把同一文件报两遍 ⇒ 界面上会出重复卡片)。
|
|
43
44
|
*/
|
|
44
45
|
function normalizeOutputs(raw) {
|
|
45
46
|
const list = typeof raw === 'string' ? raw.split(',') : Array.isArray(raw) ? raw : [];
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const out = [];
|
|
48
|
+
const seen = new Set();
|
|
49
|
+
for (const item of list) {
|
|
50
|
+
if (typeof item !== 'string')
|
|
51
|
+
continue;
|
|
52
|
+
const path = normalizeText(item).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
53
|
+
if (path.length === 0 || seen.has(path))
|
|
54
|
+
continue;
|
|
55
|
+
seen.add(path);
|
|
56
|
+
out.push(path);
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
50
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* 产出清单上限(用户 2026-10-10 拍板):**主文件 / 过程文件各自**最多 10 项。
|
|
62
|
+
*
|
|
63
|
+
* 超限**截断**而非拒绝重报:无人值守链路里一次拒绝就多一轮对话(多烧一轮 token),还可能反复
|
|
64
|
+
* 卡住不收敛;截断最多丢展示项,从不失败。截断后由工具返回文案如实告知。
|
|
65
|
+
* 参数 schema 上的 `maxItems` 只是给模型的提示(宿主不据此硬拦参数,见 normalizeStatus 注释),
|
|
66
|
+
* 真正兜底的是这里。
|
|
67
|
+
*/
|
|
68
|
+
const MAX_OUTPUT_ITEMS = 10;
|
|
51
69
|
/** 合法 status 清单(空数组兜底成 `['ok']`:schema 允许显式传空,但工具的 enum 不能为空)。 */
|
|
52
70
|
function receiptStatuses(validStatuses) {
|
|
53
71
|
return validStatuses.length > 0 ? validStatuses : ['ok'];
|
|
@@ -67,11 +85,16 @@ function buildDefinition(deps) {
|
|
|
67
85
|
// outputs 的填写粒度放这里(属"怎么填",不再占用消息体那段的篇幅)。
|
|
68
86
|
description: `提交任务「${taskName}」的执行回执。**本轮回复结束前必须调用一次**(准备输出最后一句之前);`
|
|
69
87
|
+ `调度器以它判定本次任务成败,不调用等于失败——任务指令说「不要做其他操作」也不豁免。`
|
|
70
|
-
+ `参数:status(必填,如实)、outputs
|
|
71
|
-
+
|
|
72
|
-
+
|
|
73
|
-
+
|
|
74
|
-
+
|
|
88
|
+
+ `参数:status(必填,如实)、outputs(主文件)、processOutputs(过程文件)、note(可选备注);后三者都可省略。`
|
|
89
|
+
+ `**产出分两桶报,各桶最多 ${MAX_OUTPUT_ITEMS} 项**(超出只记前 ${MAX_OUTPUT_ITEMS} 项),同一路径只报一次、别重复:`
|
|
90
|
+
+ `· outputs = **主文件**:本次任务最终的交付物(任务报告、成果文件、成品目录),可以是一个、多个,文件或文件夹都行;`
|
|
91
|
+
+ `只是跑出了一堆过程、没有最终交付物 → 省略 outputs,不要把过程文件塞进来。`
|
|
92
|
+
+ `· processOutputs = **过程文件**:执行中产生的日志、中间产物、临时/工作目录、依赖资源(网页项目的 css/js/图片目录等)。`
|
|
93
|
+
+ `本身就是目录就直接报整个目录(如 "web-app/"),别把里面每个文件都拆开列——目录里同时含有主要工作和场务/过程内容是正常的,无需为此单独拆分、挪动文件再整理。`
|
|
94
|
+
+ `· 判断口径:一个文件夹里只有个别文件是最终交付、其余是过程 ⇒ 那几个文件报 outputs,整个文件夹报 processOutputs(含纳关系合法,不冲突)。`
|
|
95
|
+
+ `· 粒度:为本任务专门建的产物目录 → 报目录路径(如 "web-app/");只是写进既有或按日期规范的目录 → 逐个列文件(如 ["20260928/a.md"])。`
|
|
96
|
+
+ `**以最后一次提交为准**:可以重复调用,但每一次都会整体覆盖上一次——再次提交时,`
|
|
97
|
+
+ `两桶**先前报过的都必须一并带上**(不回带 = 视为放弃,它们会丢)。`,
|
|
75
98
|
parameters: {
|
|
76
99
|
type: 'object',
|
|
77
100
|
additionalProperties: false,
|
|
@@ -83,8 +106,15 @@ function buildDefinition(deps) {
|
|
|
83
106
|
},
|
|
84
107
|
outputs: {
|
|
85
108
|
type: 'array',
|
|
109
|
+
maxItems: MAX_OUTPUT_ITEMS,
|
|
110
|
+
items: { type: 'string' },
|
|
111
|
+
description: `主文件:本次任务最终的交付物,相对工作区根(目录以 "/" 结尾)。可为多个文件或多个文件夹;没有最终交付物就省略。最多 ${MAX_OUTPUT_ITEMS} 项。`,
|
|
112
|
+
},
|
|
113
|
+
processOutputs: {
|
|
114
|
+
type: 'array',
|
|
115
|
+
maxItems: MAX_OUTPUT_ITEMS,
|
|
86
116
|
items: { type: 'string' },
|
|
87
|
-
description:
|
|
117
|
+
description: `过程文件:日志、中间产物、临时/工作目录、依赖资源,相对工作区根(目录以 "/" 结尾)。最多 ${MAX_OUTPUT_ITEMS} 项;可与主文件并存(允许「某文件在 outputs、其父目录在 processOutputs」这种含纳关系),但同一路径不要两桶都报(都报了只算主文件)。`,
|
|
88
118
|
},
|
|
89
119
|
note: {
|
|
90
120
|
type: 'string',
|
|
@@ -118,7 +148,14 @@ function buildDefinition(deps) {
|
|
|
118
148
|
+ `大小写与首尾空白会自动归一,其余写法一律不认)。请修正参数后重试一次;这是参数错误,不必等待。`,
|
|
119
149
|
};
|
|
120
150
|
}
|
|
121
|
-
|
|
151
|
+
// 两桶各自归一 → 各自截断上限(满额即止)→ 跨桶去重(同一路径以主桶为准)。
|
|
152
|
+
// 截断与否要如实回报给模型(见返回文案),否则它会以为全都记上了。
|
|
153
|
+
const mainAll = normalizeOutputs(input.outputs);
|
|
154
|
+
const procAll = normalizeOutputs(input.processOutputs);
|
|
155
|
+
const outputs = mainAll.slice(0, MAX_OUTPUT_ITEMS);
|
|
156
|
+
const mainSet = new Set(outputs);
|
|
157
|
+
const processOutputs = procAll.filter(item => !mainSet.has(item)).slice(0, MAX_OUTPUT_ITEMS);
|
|
158
|
+
const overLimit = mainAll.length > MAX_OUTPUT_ITEMS || procAll.length > MAX_OUTPUT_ITEMS;
|
|
122
159
|
const rawNote = typeof input.note === 'string' ? normalizeText(input.note) : '';
|
|
123
160
|
const note = rawNote.length > 0 ? rawNote : undefined;
|
|
124
161
|
const instance = store.get(instanceId);
|
|
@@ -137,10 +174,15 @@ function buildDefinition(deps) {
|
|
|
137
174
|
message: `回执未提交:实例当前会话已是 ${instance.session_id ?? 'null'},本会话无权提交。不必重试。`,
|
|
138
175
|
};
|
|
139
176
|
}
|
|
140
|
-
// 与 submit.ts 写进 receipt
|
|
141
|
-
|
|
142
|
-
|
|
177
|
+
// 与 submit.ts 写进 receipt 事件的形状一致(对账逻辑零改动):status/outputs/processOutputs/note/session_id。
|
|
178
|
+
// processOutputs 是 2026-10-10 新增桶;旧回执没有该字段 ⇒ 消费方按空数组处理(向后兼容)。
|
|
179
|
+
store.appendEvent(instanceId, 'receipt', { status, outputs, processOutputs, note, session_id: sessionId });
|
|
180
|
+
logger.info(`回执已记录 ${instanceId}: status=${status}`
|
|
181
|
+
+ `${outputs.length > 0 ? `, 主文件 ${outputs.length} 项` : ''}`
|
|
182
|
+
+ `${processOutputs.length > 0 ? `, 过程文件 ${processOutputs.length} 项` : ''}`
|
|
183
|
+
+ `${overLimit ? '(超出上限已截断)' : ''}`);
|
|
143
184
|
// B 路线:插件代写官方交付事件(决策 40 演进:插件作唯一写入方,禁止 LLM 调 present)。
|
|
185
|
+
// ⚠️ 只写**主文件桶**:过程文件若也写进去就变成官方交付卡,与「分主次」矛盾。
|
|
144
186
|
// 失败只记日志、绝不拖垮回执(外层 try 已兜底,这里再独立 try 防任何意外上抛)。
|
|
145
187
|
try {
|
|
146
188
|
const execCtx = exec;
|
|
@@ -172,7 +214,10 @@ function buildDefinition(deps) {
|
|
|
172
214
|
return {
|
|
173
215
|
ok: true,
|
|
174
216
|
message: `回执已记录(${instanceId},status=${status}`
|
|
175
|
-
+ `${outputs.length > 0 ?
|
|
217
|
+
+ `${outputs.length > 0 ? `,主文件 ${outputs.length} 项` : ''}`
|
|
218
|
+
+ `${processOutputs.length > 0 ? `,过程文件 ${processOutputs.length} 项` : ''}`
|
|
219
|
+
+ `${overLimit ? `。⚠️ 有桶超过 ${MAX_OUTPUT_ITEMS} 项上限,只记录了前 ${MAX_OUTPUT_ITEMS} 项` : ''}`
|
|
220
|
+
+ `)。任务结束,无需再做任何事。`,
|
|
176
221
|
};
|
|
177
222
|
}
|
|
178
223
|
catch (error) {
|
|
@@ -226,11 +271,13 @@ export function receiptInstruction(validStatuses) {
|
|
|
226
271
|
`【回执·唯一权威段】本轮回复结束前,必须调用一次 ${RECEIPT_TOOL_NAME}。`,
|
|
227
272
|
`⚖️ 冲突裁决:任务提示词里任何「不要做其他操作 / 只做某件事 / 什么都不用做 / 只回复一句话」的说法,`
|
|
228
273
|
+ `对回执**不生效**——它约束的是任务内容,不约束回执。不提交 = 本次任务失败。`,
|
|
229
|
-
`-
|
|
230
|
-
|
|
231
|
-
|
|
274
|
+
`- 产出分两桶报:**主文件**填 outputs(最终交付物),**过程文件**填 processOutputs(日志 / 中间产物 / 工作目录);`
|
|
275
|
+
+ `各不超过 ${MAX_OUTPUT_ITEMS} 项,没有主文件就只填 processOutputs。`,
|
|
276
|
+
`- 两个桶都可以空,但回执本身必须交(没有产出就省略它们)。`,
|
|
277
|
+
`- **以最后一次提交为准**:重复提交会整体覆盖,两桶先前报过的都必须一并带上(不回带 = 放弃)。`,
|
|
278
|
+
`${RECEIPT_TOOL_NAME}({ status: "${statuses[0] ?? 'ok'}", outputs: ["<主文件,相对工作区根路径>"], processOutputs: ["<过程文件>"] })`,
|
|
232
279
|
`- status 只能取:${statuses.join(' | ')}(如实填写)。`,
|
|
233
280
|
`- 调用失败:等 10 秒**原样重试**,最多 3 次;仍失败就**停下**(不要跑命令、不要动状态库、不要绕过沙箱)。`,
|
|
234
|
-
`- 不要调用 present
|
|
281
|
+
`- 不要调用 present:交付卡片由插件按主文件(outputs)统一生成。`,
|
|
235
282
|
].join('\n');
|
|
236
283
|
}
|
package/dist/reconcile.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export interface ReconcilerDeps {
|
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
56
|
* 回执裁决(决策 19,替代旧契约文件三查):
|
|
57
|
-
* receipt 事件存在 + status ∈ validStatuses + outputs 里的每个路径**确实存在**。
|
|
57
|
+
* receipt 事件存在 + status ∈ validStatuses + outputs / processOutputs 里的每个路径**确实存在**。
|
|
58
58
|
*
|
|
59
59
|
* ⚠️ **已去掉「mtime 新鲜度」闸**(用户 2026-10-03 拍板,原为「防旧产物冒充」):
|
|
60
60
|
* 那道闸会误伤「复用 / 检查已有文件」类任务 —— 真机案例:任务是判断 `uuid.txt`
|
package/dist/reconcile.js
CHANGED
|
@@ -50,9 +50,11 @@ export function missingSnapshotAttachments(ctx, taskId, snap, assets) {
|
|
|
50
50
|
}
|
|
51
51
|
/** 跑完信号后允许补交回执的追问上限(写死不加配置,事件表可查次数)。 */
|
|
52
52
|
const NUDGE_LIMIT = 2;
|
|
53
|
+
/** 回执载荷里的路径数组(非数组 / 非字符串项一律剔除;缺字段 ⇒ 空数组)。 */
|
|
54
|
+
const receiptPaths = (raw) => Array.isArray(raw) ? raw.filter((item) => typeof item === 'string') : [];
|
|
53
55
|
/**
|
|
54
56
|
* 回执裁决(决策 19,替代旧契约文件三查):
|
|
55
|
-
* receipt 事件存在 + status ∈ validStatuses + outputs 里的每个路径**确实存在**。
|
|
57
|
+
* receipt 事件存在 + status ∈ validStatuses + outputs / processOutputs 里的每个路径**确实存在**。
|
|
56
58
|
*
|
|
57
59
|
* ⚠️ **已去掉「mtime 新鲜度」闸**(用户 2026-10-03 拍板,原为「防旧产物冒充」):
|
|
58
60
|
* 那道闸会误伤「复用 / 检查已有文件」类任务 —— 真机案例:任务是判断 `uuid.txt`
|
|
@@ -83,11 +85,19 @@ export function checkReceipt(workspacePath, validStatuses, receipt) {
|
|
|
83
85
|
detail: { status: payload.status, validStatuses: [...validStatuses] },
|
|
84
86
|
};
|
|
85
87
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
// 两桶同一道闸(2026-10-10):主文件与过程文件都按「确实存在」校验,防幽灵路径进界面与下游消息。
|
|
89
|
+
// ⚠️ 取舍:过程文件路径写错同样会让整次回执判失败并走重试(与主桶今天的行为一致);
|
|
90
|
+
// 若真机上因此频繁误失败,可降级为「只校验主桶」——改这一处即可。
|
|
91
|
+
const buckets = [
|
|
92
|
+
{ bucket: 'main', list: receiptPaths(payload.outputs) },
|
|
93
|
+
{ bucket: 'process', list: receiptPaths(payload.processOutputs) },
|
|
94
|
+
];
|
|
95
|
+
for (const { bucket, list } of buckets) {
|
|
96
|
+
for (const output of list) {
|
|
97
|
+
const outputPath = resolve(workspacePath, output);
|
|
98
|
+
if (!existsSync(outputPath))
|
|
99
|
+
return { ok: false, reason: 'output-missing', detail: { output, bucket } };
|
|
100
|
+
}
|
|
91
101
|
}
|
|
92
102
|
return { ok: true, detail: payload };
|
|
93
103
|
}
|
|
@@ -237,7 +247,7 @@ export function createReconciler({ ctx, logger, store, options, runtime, emit })
|
|
|
237
247
|
const notifyRow = (instance) => {
|
|
238
248
|
emit?.({ type: EventType.TASK_RUN_CHANGED, payload: { taskId: instance.task_id, instanceId: instance.id } });
|
|
239
249
|
};
|
|
240
|
-
function finishTerminal(instance, status, reason, detail, outputs) {
|
|
250
|
+
function finishTerminal(instance, status, reason, detail, outputs, processOutputs) {
|
|
241
251
|
const tk = tokenTotals.get(instance.id);
|
|
242
252
|
if (tokenTotals.has(instance.id))
|
|
243
253
|
tokenTotals.delete(instance.id);
|
|
@@ -247,8 +257,8 @@ export function createReconciler({ ctx, logger, store, options, runtime, emit })
|
|
|
247
257
|
runtime?.markTerminal(instance.task_id, status, instance.scheduled_at, finishedAt);
|
|
248
258
|
if (detail !== undefined)
|
|
249
259
|
store.appendEvent(instance.id, 'receipt_check', { reason, detail });
|
|
250
|
-
// 决策 32
|
|
251
|
-
store.recordCompletion(instance.id, outputs ?? null, tk?.in ?? null, tk?.out ?? null, tk?.cache ?? null);
|
|
260
|
+
// 决策 32 修订:完成瞬间写回两桶产出与 token 三拆列到总表(冗余,task_events 仍为真源)
|
|
261
|
+
store.recordCompletion(instance.id, outputs ?? null, processOutputs ?? null, tk?.in ?? null, tk?.out ?? null, tk?.cache ?? null);
|
|
252
262
|
forgetHandle(instance.session_id);
|
|
253
263
|
// 会话已结束(turn/end / disposed 触发的收敛)→ 归档;租约误判的回收不归档。
|
|
254
264
|
if (instance.session_id !== null)
|
|
@@ -300,7 +310,8 @@ export function createReconciler({ ctx, logger, store, options, runtime, emit })
|
|
|
300
310
|
if (verdict.ok) {
|
|
301
311
|
const payload = verdict.detail;
|
|
302
312
|
const outputsField = Array.isArray(payload?.outputs) ? JSON.stringify(payload.outputs) : null;
|
|
303
|
-
|
|
313
|
+
const processField = Array.isArray(payload?.processOutputs) ? JSON.stringify(payload.processOutputs) : null;
|
|
314
|
+
finishTerminal(instance, 'succeeded', 'receipt-pass', verdict.detail, outputsField, processField);
|
|
304
315
|
}
|
|
305
316
|
else {
|
|
306
317
|
store.appendEvent(instance.id, 'receipt_check', { reason: verdict.reason, detail: verdict.detail });
|
package/dist/runtime-index.d.ts
CHANGED
package/dist/scheduler.js
CHANGED
|
@@ -59,6 +59,9 @@ function stripJsoncComments(text) {
|
|
|
59
59
|
* 上游实例 → 已解析依赖(决策 43):固化 id / 计划时刻 / 会话 / 上游工作区 / 产出。
|
|
60
60
|
* 产出取上游实例的 `outputs` 列(回执声明并经 checkReceipt 校验的 JSON 数组,决策 32 修订);
|
|
61
61
|
* 旧行 / 坏 JSON / 未声明 ⇒ 空数组(消息里如实写「未声明产出」)。
|
|
62
|
+
*
|
|
63
|
+
* ⚠️ 2026-10-10:`outputs` 的语义已收窄为**主文件**(过程文件另存 `process_outputs` 列、**不下传下游**)
|
|
64
|
+
* ⇒ 「下游只收主文件」在这里天然成立,本函数无需改动。
|
|
62
65
|
*/
|
|
63
66
|
function resolvedOf(dep, upstream) {
|
|
64
67
|
let outputs = [];
|
package/dist/store.d.ts
CHANGED
|
@@ -45,7 +45,13 @@ export interface TaskInstance {
|
|
|
45
45
|
lease_until: string | null;
|
|
46
46
|
dispatched_at: string | null;
|
|
47
47
|
finished_at: string | null;
|
|
48
|
+
/**
|
|
49
|
+
* 回执声明的产出 JSON 数组,**主文件桶**(2026-10-10 语义收窄:核心交付物)。
|
|
50
|
+
* 旧行只有这一列 ⇒ 天然等价于「只有主文件」。
|
|
51
|
+
*/
|
|
48
52
|
outputs: string | null;
|
|
53
|
+
/** 回执声明的**过程文件桶**(2026-10-10 新增):日志 / 中间产物 / 工作目录的 JSON 数组;旧行为 null。 */
|
|
54
|
+
process_outputs: string | null;
|
|
49
55
|
token_in: number | null;
|
|
50
56
|
token_out: number | null;
|
|
51
57
|
token_in_cache: number | null;
|
|
@@ -159,8 +165,9 @@ export interface InstancePage {
|
|
|
159
165
|
*
|
|
160
166
|
* 为什么剔除:snapshot 是派发快照(提示词 / 附件 / 依赖的 JSON,单行 1.5–4 KB),
|
|
161
167
|
* 日历要一次取满整月(几百到上千行),带着它就是数 MB 的白给开销;日历只要状态 / 时刻 / 产出 / 原因。
|
|
168
|
+
* 2026-10-10 起同时剔除 `process_outputs`(过程文件只有记录页 / 查看档 / 会话弹窗展示,日历不展示它)。
|
|
162
169
|
*/
|
|
163
|
-
export type LiteTaskInstance = Omit<TaskInstance, 'snapshot'>;
|
|
170
|
+
export type LiteTaskInstance = Omit<TaskInstance, 'snapshot' | 'process_outputs'>;
|
|
164
171
|
export interface LiteInstancePage {
|
|
165
172
|
rows: LiteTaskInstance[];
|
|
166
173
|
/** 触及上限被截断(调用方必须提示用户收窄过滤,**不静默丢**)。 */
|
|
@@ -226,7 +233,7 @@ export declare class TaskStore {
|
|
|
226
233
|
* - 已迁移过的库:仅补决策 32 冗余字段(outputs / tokens 列),不重复去重。
|
|
227
234
|
*/
|
|
228
235
|
private migrate;
|
|
229
|
-
/** 决策 32
|
|
236
|
+
/** 决策 32(修订)+ 2026-10-10:兼容旧库(无 outputs / process_outputs / token 三拆列)。 */
|
|
230
237
|
private ensureInstanceColumns;
|
|
231
238
|
close(): void;
|
|
232
239
|
/**
|
|
@@ -364,8 +371,8 @@ export declare class TaskStore {
|
|
|
364
371
|
* ⚠️ **执行记录永久保留,没有清除方法**(用户 2026-10-08 拍板):历史是用来查的、不该被清,
|
|
365
372
|
* 原 `purgeHistory` 与 `historyRetentionDays` 配置一并删除。
|
|
366
373
|
*/
|
|
367
|
-
/**
|
|
368
|
-
recordCompletion(id: string, outputs: string | null, tokenIn: number | null, tokenOut: number | null, tokenInCache: number | null): void;
|
|
374
|
+
/** 完成瞬间写回两桶产出与 token 三拆列(决策 32 修订:总表冗余,task_events 仍为真源)。 */
|
|
375
|
+
recordCompletion(id: string, outputs: string | null, processOutputs: string | null, tokenIn: number | null, tokenOut: number | null, tokenInCache: number | null): void;
|
|
369
376
|
/** 按「任务 + 刻度」查实例(手动排查 / 备用回执通道用,不依赖 id 形态)。 */
|
|
370
377
|
findBySlot(taskId: string, scheduledAt: string): TaskInstance | undefined;
|
|
371
378
|
get(id: string): TaskInstance | undefined;
|
package/dist/store.js
CHANGED
|
@@ -90,7 +90,9 @@ CREATE TABLE IF NOT EXISTS task_instances (
|
|
|
90
90
|
lease_until TEXT,
|
|
91
91
|
dispatched_at TEXT,
|
|
92
92
|
finished_at TEXT,
|
|
93
|
+
-- 2026-10-10:outputs = 主文件(核心交付物);process_outputs = 过程文件。旧库补列(ensureInstanceColumns)。
|
|
93
94
|
outputs TEXT,
|
|
95
|
+
process_outputs TEXT,
|
|
94
96
|
token_in INTEGER,
|
|
95
97
|
token_out INTEGER,
|
|
96
98
|
token_in_cache INTEGER,
|
|
@@ -212,11 +214,14 @@ export class TaskStore {
|
|
|
212
214
|
this.db.exec('CREATE UNIQUE INDEX idx_instances_slot ON task_instances(task_id, scheduled_at)');
|
|
213
215
|
this.ensureInstanceColumns();
|
|
214
216
|
}
|
|
215
|
-
/** 决策 32
|
|
217
|
+
/** 决策 32(修订)+ 2026-10-10:兼容旧库(无 outputs / process_outputs / token 三拆列)。 */
|
|
216
218
|
ensureInstanceColumns() {
|
|
217
219
|
const cols = new Set(this.db.prepare('PRAGMA table_info(task_instances)').all().map(c => c.name));
|
|
218
220
|
if (!cols.has('outputs'))
|
|
219
221
|
this.db.exec('ALTER TABLE task_instances ADD COLUMN outputs TEXT');
|
|
222
|
+
// 过程文件桶(2026-10-10):旧库补列后为 NULL = 无过程文件 ⇒ 界面照旧只显示主文件。
|
|
223
|
+
if (!cols.has('process_outputs'))
|
|
224
|
+
this.db.exec('ALTER TABLE task_instances ADD COLUMN process_outputs TEXT');
|
|
220
225
|
// 旧版决策 32 曾用单个 tokens 列;拆成三列后卸下旧列(不支持 DROP COLUMN 的旧 SQLite 静默跳过)。
|
|
221
226
|
if (cols.has('tokens')) {
|
|
222
227
|
try {
|
|
@@ -482,11 +487,11 @@ export class TaskStore {
|
|
|
482
487
|
* ⚠️ **执行记录永久保留,没有清除方法**(用户 2026-10-08 拍板):历史是用来查的、不该被清,
|
|
483
488
|
* 原 `purgeHistory` 与 `historyRetentionDays` 配置一并删除。
|
|
484
489
|
*/
|
|
485
|
-
/**
|
|
486
|
-
recordCompletion(id, outputs, tokenIn, tokenOut, tokenInCache) {
|
|
490
|
+
/** 完成瞬间写回两桶产出与 token 三拆列(决策 32 修订:总表冗余,task_events 仍为真源)。 */
|
|
491
|
+
recordCompletion(id, outputs, processOutputs, tokenIn, tokenOut, tokenInCache) {
|
|
487
492
|
this.db
|
|
488
|
-
.prepare('UPDATE task_instances SET outputs = ?, token_in = ?, token_out = ?, token_in_cache = ?, updated_at = ? WHERE id = ?')
|
|
489
|
-
.run(outputs, tokenIn, tokenOut, tokenInCache, nowIso(), id);
|
|
493
|
+
.prepare('UPDATE task_instances SET outputs = ?, process_outputs = ?, token_in = ?, token_out = ?, token_in_cache = ?, updated_at = ? WHERE id = ?')
|
|
494
|
+
.run(outputs, processOutputs, tokenIn, tokenOut, tokenInCache, nowIso(), id);
|
|
490
495
|
}
|
|
491
496
|
/** 按「任务 + 刻度」查实例(手动排查 / 备用回执通道用,不依赖 id 形态)。 */
|
|
492
497
|
findBySlot(taskId, scheduledAt) {
|
package/dist/task-assets.d.ts
CHANGED
|
@@ -73,6 +73,8 @@ export interface AttachmentRef {
|
|
|
73
73
|
ref: string;
|
|
74
74
|
/** link 型:来源工作区 title(同一路径在不同工作区指向不同文件)。 */
|
|
75
75
|
workspace?: string;
|
|
76
|
+
/** 是否为目录(2026-10-09 起允许把整个文件夹当附件)。 */
|
|
77
|
+
isDir?: boolean;
|
|
76
78
|
}
|
|
77
79
|
export interface ReconcileResult {
|
|
78
80
|
/** 落定后的附件清单(upload 型的 ref 已改写成「相对任务目录」的路径)。 */
|
package/dist/tasks.d.ts
CHANGED
package/dist/tasks.js
CHANGED
|
@@ -90,6 +90,8 @@ export const taskDefinitionSchema = z.object({
|
|
|
90
90
|
ref: z.string().min(1),
|
|
91
91
|
/** link 型必带:来源工作区 title(同一路径在不同工作区指向不同文件)。 */
|
|
92
92
|
workspace: z.string().optional(),
|
|
93
|
+
/** 是否为目录(2026-10-09 起允许把整个文件夹当附件)。 */
|
|
94
|
+
isDir: z.boolean().optional(),
|
|
93
95
|
})
|
|
94
96
|
.refine(
|
|
95
97
|
// 唯一实现 = attachment-allowlist.isSafeAttachmentRef(客户端保存前校验 / 真删 / 上传定位同源)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-task-dispatch-table",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Scheduled task dispatcher: a dsh host plugin that drives recurring agent tasks from a single task table, dispatching each run as an independent dsh session (scheduling itself uses zero model calls)",
|
|
5
5
|
"icon": "./assets/icon-scheduler.svg",
|
|
6
6
|
"keywords": [
|