@x-otto/schedule 0.0.1-alpha.2 → 0.0.1-alpha.4
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/README.md +15 -6
- package/dist/index.d.ts +479 -265
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { Migration, Persistence } from "@x-otto/persistence";
|
|
2
2
|
import { AgentJobRegistry } from "@x-otto/runtime";
|
|
3
3
|
|
|
4
|
+
//#region ../interchange/dist/index.d.ts
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/model.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* 推理投入档(reasoning effort)——RFC-235 对齐 Claude Code 词表:`low/medium/high/xhigh/max`。
|
|
9
|
+
* 去除历史遗留的 `minimal`(无对应厂商语义,存量配置载入时迁移为 `low`),新增 `max`(Claude
|
|
10
|
+
* Code 最高档 + DeepSeek `reasoning_effort:'max'` 顶档)。各模型真实支持的子集由 manifest 的
|
|
11
|
+
* `Model.thinkingLevels` 声明,provider 组装请求体时按"≤该档最高支持档"降级(见 RFC-235 §3.3)。
|
|
12
|
+
*/
|
|
13
|
+
type ThinkingLevel = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
14
|
+
/**
|
|
15
|
+
* 模型能力优势标签(供大模型为 subagent 选型)。非 /models API 返回——由
|
|
16
|
+
* model-capabilities 据「精选家族图 + 启发式」推导。
|
|
17
|
+
* planning=方案/架构 · knowledge=知识/通识 · coding=编码 · reasoning=深度推理
|
|
18
|
+
* · vision=视觉 · speed=低延迟 · long-context=长上下文
|
|
19
|
+
*/
|
|
20
|
+
//#endregion
|
|
4
21
|
//#region src/types.d.ts
|
|
5
22
|
/** A scheduled task persisted to ${OTTO_HOME}/schedules/<id>.json */
|
|
6
23
|
interface ScheduledTask {
|
|
@@ -49,6 +66,37 @@ interface ScheduledTask {
|
|
|
49
66
|
* 时不使用。
|
|
50
67
|
*/
|
|
51
68
|
boundSessionId?: string;
|
|
69
|
+
/**
|
|
70
|
+
* RFC-423 D2:事件触发描述。存在时任务由进程内事件驱动而非 cron 驱动
|
|
71
|
+
* (cronExpression 被忽略,任务不进 tick 的 findDue)。缺省 = cron 驱动(既有语义,零变化)。
|
|
72
|
+
*/
|
|
73
|
+
eventTrigger?: EventTriggerSpec;
|
|
74
|
+
/**
|
|
75
|
+
* RFC-438:到点执行时使用的模型 id(缺省 = 当前会话模型/默认)。
|
|
76
|
+
* 只存 id 字符串——modelRegistry 是唯一真源,schedule 不持有 Model 对象。
|
|
77
|
+
*/
|
|
78
|
+
modelId?: string;
|
|
79
|
+
/** RFC-438:思考档位(配 modelId 使用)。缺省 = provider 默认(detached-run 实际 ?? 'medium')。 */
|
|
80
|
+
thinkingLevel?: ThinkingLevel;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* RFC-423 D2:事件触发描述。
|
|
84
|
+
*
|
|
85
|
+
* MVP 只支持进程内事件源(source: 'job',监听 AgentJobRegistry 终态)。
|
|
86
|
+
* git commit / file watch 等进程外源列为后续 RFC,架构预留 source 开放为 string。
|
|
87
|
+
*/
|
|
88
|
+
interface EventTriggerSpec {
|
|
89
|
+
/** 事件源类型(MVP:'job'——AgentJobRegistry 终态事件) */
|
|
90
|
+
source: string;
|
|
91
|
+
/** 事件名(source='job' 时为 AgentJobStatus:'applied' | 'failed' | 'canceled') */
|
|
92
|
+
event: string;
|
|
93
|
+
/**
|
|
94
|
+
* 可选过滤器(如只对特定 sessionId 的 job 触发)。死循环闸门之一(D4):
|
|
95
|
+
* 缺省不过滤 sessionId 时,闸门2(默认拒绝 job 投递)+ debounce(闸门3)承担防护。
|
|
96
|
+
*/
|
|
97
|
+
filter?: {
|
|
98
|
+
sessionId?: string;
|
|
99
|
+
};
|
|
52
100
|
}
|
|
53
101
|
/** RFC-229 M1: structured result from SchedulerService.cancel() */
|
|
54
102
|
interface CancelResult {
|
|
@@ -67,7 +115,7 @@ interface CancelResult {
|
|
|
67
115
|
* 整套调度设施,正是 RFC 列为"放弃方案"的路径。
|
|
68
116
|
*/
|
|
69
117
|
interface ScheduleSubscriber {
|
|
70
|
-
kind:
|
|
118
|
+
kind: string;
|
|
71
119
|
pluginId: string;
|
|
72
120
|
serviceId: string;
|
|
73
121
|
}
|
|
@@ -86,6 +134,12 @@ interface ScheduledTaskInput {
|
|
|
86
134
|
target?: 'job' | 'session';
|
|
87
135
|
/** RFC-357 D2:`target==='session'` 时必填,调用方(scheduler.add())从当前 sessionRef 取值填充。 */
|
|
88
136
|
boundSessionId?: string;
|
|
137
|
+
/** RFC-423 D2:事件触发描述。存在时任务由进程内事件驱动,cronExpression 仅作占位。 */
|
|
138
|
+
eventTrigger?: EventTriggerSpec;
|
|
139
|
+
/** RFC-438:到点执行时使用的模型 id(缺省 = 当前会话模型/默认)。 */
|
|
140
|
+
modelId?: string;
|
|
141
|
+
/** RFC-438:思考档位(配 modelId 使用)。 */
|
|
142
|
+
thinkingLevel?: ThinkingLevel;
|
|
89
143
|
}
|
|
90
144
|
//#endregion
|
|
91
145
|
//#region src/registry.d.ts
|
|
@@ -101,35 +155,16 @@ declare class ScheduleRegistry {
|
|
|
101
155
|
* Find tasks that are due for execution.
|
|
102
156
|
* Returns all enabled tasks (recurring or one-shot) whose nextFireAt <= now.
|
|
103
157
|
* The 'firing' guard is a scheduler concern (managed via a separate Set).
|
|
158
|
+
*
|
|
159
|
+
* RFC-423:eventTrigger 任务永不由时间"到点"——它们由进程内事件驱动(InProcessEventTrigger
|
|
160
|
+
* arm 监听),不进 cron 轮询。此处排除,使 cron 轮询(tick)与事件监听(arm)
|
|
161
|
+
* 两条路径互斥(D5)。
|
|
104
162
|
*/
|
|
105
163
|
findDue(now: number): ScheduledTask[];
|
|
106
164
|
count(): number;
|
|
107
165
|
has(id: string): boolean;
|
|
108
166
|
}
|
|
109
167
|
//#endregion
|
|
110
|
-
//#region src/cron-parser.d.ts
|
|
111
|
-
/**
|
|
112
|
-
* Parse a 5-field cron expression (minute hour dom month dow) and return the next
|
|
113
|
-
* fire time >= from (inclusive). All times in local timezone.
|
|
114
|
-
*
|
|
115
|
-
* Throws on invalid expressions.
|
|
116
|
-
*
|
|
117
|
-
* Field syntax: wildcard (*), single value (5), step (e.g. \*\/15 or 5\/15),
|
|
118
|
-
* range (1-5), list (1,15,30), range with step (1-10\/2).
|
|
119
|
-
*
|
|
120
|
-
* Unsupported: L, W, ?, #, name aliases (MON, JAN).
|
|
121
|
-
*
|
|
122
|
-
* ## DoM∧DoW semantics
|
|
123
|
-
*
|
|
124
|
-
* If both day-of-month and day-of-week are constrained (not a bare *),
|
|
125
|
-
* a date matches if **EITHER** field matches (standard vixie-cron OR behaviour).
|
|
126
|
-
*
|
|
127
|
-
* ## Day-of-week mapping
|
|
128
|
-
*
|
|
129
|
-
* 0 = Sunday, 1–6 = Monday–Saturday, 7 = also Sunday.
|
|
130
|
-
*/
|
|
131
|
-
declare function parseCron(expr: string, from?: Date): Date;
|
|
132
|
-
//#endregion
|
|
133
168
|
//#region src/schedule-store.d.ts
|
|
134
169
|
interface ScheduleStore {
|
|
135
170
|
saveTask(task: ScheduledTask): Promise<void>;
|
|
@@ -159,6 +194,18 @@ interface FireOwnership {
|
|
|
159
194
|
renew(): Promise<void>;
|
|
160
195
|
release(): Promise<void>;
|
|
161
196
|
lastFailureReason?(): 'occupied' | 'network-error' | undefined;
|
|
197
|
+
/**
|
|
198
|
+
* 当前锁持有者的可读描述(诊断用,非语义判定)。返回 `undefined` 表示无法确定
|
|
199
|
+
* (旧格式锁文件、锁已消失、或远程实现——服务端仲裁没有本地 pid 概念)。
|
|
200
|
+
*
|
|
201
|
+
* 为什么是可选端口:`FireOwnership` 是本地 O_EXCL 与远程 HTTP 双实现的公共接口
|
|
202
|
+
* (RFC-169 D3/D3b),"谁持有"只在本地实现里有确切答案。消费方必须按可选处理,
|
|
203
|
+
* 拿不到就退回不带主体的提示文案,不得据此做任何准入判定。
|
|
204
|
+
*/
|
|
205
|
+
describeOwner?(): {
|
|
206
|
+
pid?: number;
|
|
207
|
+
host?: string;
|
|
208
|
+
} | undefined;
|
|
162
209
|
/** Whether this process currently holds the lock (verified against the token). */
|
|
163
210
|
isOwner(): boolean;
|
|
164
211
|
}
|
|
@@ -168,327 +215,474 @@ interface FireOwnership {
|
|
|
168
215
|
*/
|
|
169
216
|
declare function createLocalFireOwnership(workspaceKey: string): FireOwnership;
|
|
170
217
|
//#endregion
|
|
171
|
-
//#region src/schedule-
|
|
172
|
-
|
|
218
|
+
//#region src/schedule-config.d.ts
|
|
219
|
+
/**
|
|
220
|
+
* schedule-config.ts —— RFC-406 D4:常量配置化。
|
|
221
|
+
*
|
|
222
|
+
* 之前散落在 scheduler.ts 顶部的硬编码常量,提取为可注入的配置接口。
|
|
223
|
+
* 不同部署场景(单用户 vs 团队共享、高频 vs 低频)可调整参数,不需改源码。
|
|
224
|
+
*
|
|
225
|
+
* **不纳入本接口的常量**(刻意游离,非遗漏):
|
|
226
|
+
* - `fire-ownership.ts` 的 `STALE_MS`(5 分钟)——本地锁过期阈值,有不变量约束
|
|
227
|
+
* (必须超过 tick renew 间隔,否则锁在 renew 周期间被误判过期),调错会致
|
|
228
|
+
* 多进程重复 fire。它是 fire-ownership 的领域常量,不是可调运维参数。
|
|
229
|
+
* - `remote-fire-ownership.ts` 的 `HEARTBEAT_MS`(30s)/ `STALE_MS`(180s)——
|
|
230
|
+
* 远程租约心跳与过期,有 1:6 比例约束(过期 > 6 倍心跳,防网络抖动误夺锁)。
|
|
231
|
+
* 调错同样致多终端重复 fire 或锁饥饿。两者已通过模块 export 暴露
|
|
232
|
+
* (`REMOTE_FIRE_OWNERSHIP_HEARTBEAT_MS` / `REMOTE_FIRE_OWNERSHIP_STALE_MS`),
|
|
233
|
+
* 不进 ScheduleConfig 以免暗示可随意调。
|
|
234
|
+
*/
|
|
235
|
+
interface ScheduleConfig {
|
|
236
|
+
/** 最大任务数 */
|
|
237
|
+
maxTasks: number;
|
|
238
|
+
/** recurring 任务过期阈值(超过此时间未触发则清除) */
|
|
239
|
+
recurringEvictionMs: number;
|
|
240
|
+
/** recurring 任务 jitter 窗口 */
|
|
241
|
+
recurringJitterWindowMs: number;
|
|
242
|
+
/** one-shot 任务 jitter 窗口 */
|
|
243
|
+
oneShotJitterWindowMs: number;
|
|
244
|
+
/** 默认单次执行超时 */
|
|
245
|
+
defaultMaxDurationMs: number;
|
|
246
|
+
/** 默认重试次数 */
|
|
247
|
+
defaultMaxRetries: number;
|
|
248
|
+
/** ticker 间隔 */
|
|
249
|
+
tickIntervalMs: number;
|
|
250
|
+
/** needs_input 硬上限倍数 */
|
|
251
|
+
needsInputMaxExtensionFactor: number;
|
|
252
|
+
/** 每任务最大 fire log 条数 */
|
|
253
|
+
maxFireLogsPerTask: number;
|
|
254
|
+
/** owner 从 store 重新同步的间隔(tick 数) */
|
|
255
|
+
reloadIntervalTicks: number;
|
|
256
|
+
/** recovery 模式重试间隔 */
|
|
257
|
+
recoveryRetryIntervalMs: number;
|
|
258
|
+
}
|
|
259
|
+
declare const DEFAULT_SCHEDULE_CONFIG: ScheduleConfig;
|
|
173
260
|
//#endregion
|
|
174
|
-
//#region src/
|
|
175
|
-
interface
|
|
261
|
+
//#region src/fire-target.d.ts
|
|
262
|
+
interface FireTarget {
|
|
263
|
+
/** 唯一标识('job' | 'session' | 'plugin-service') */
|
|
264
|
+
readonly type: string;
|
|
265
|
+
/** 判断此 handler 是否能处理该 task。注册顺序决定优先级——先注册的先匹配。 */
|
|
266
|
+
canHandle(task: ScheduledTask): boolean;
|
|
176
267
|
/**
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* `new URL(sessionUrl).origin` 提取协议+主机+端口,再拼接 `/api/storage/schedules`——
|
|
180
|
-
* 与 `remote-fire-ownership.ts` 的同一提取逻辑保持一致,不能直接拿 sessionUrl 当前缀用。
|
|
268
|
+
* 执行投递。返回 FireResult——scheduler 不关心 handler 内部如何实现,
|
|
269
|
+
* 只根据 outcome 决定后续行为(one-shot 清理 / recurring 记账)。
|
|
181
270
|
*/
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
271
|
+
fire(task: ScheduledTask, ctx: FireContext): Promise<FireResult>;
|
|
272
|
+
/**
|
|
273
|
+
* fire() 退出保障钩子(M1 修复①):无论 fire 正常返回、抛错还是早退,executor 的
|
|
274
|
+
* finally 块都会调用。handler 必须在此清理本次 fire 的运行态记账
|
|
275
|
+
* (runningJobs / cancelRequested 等)——漏清会导致 cancel 意图残留、
|
|
276
|
+
* recurring 任务被永久跳过、运行态 Map 无界泄漏。
|
|
277
|
+
*/
|
|
278
|
+
cleanup?(taskId: string): void;
|
|
279
|
+
/**
|
|
280
|
+
* 可选能力:支持取消正在运行的投递(job 路径实现)。
|
|
281
|
+
* SchedulerService.cancel() 路由到当前 fire 该任务的 handler。
|
|
282
|
+
*/
|
|
283
|
+
requestCancel?(taskId: string): CancelResult;
|
|
284
|
+
/** 可选能力:当前有并发运行态的 handler(job 路径)暴露运行中的 task id 集合。 */
|
|
285
|
+
getRunningTaskIds?(): Set<string>;
|
|
286
|
+
/**
|
|
287
|
+
* 可选能力:session 投递 handler 暴露"创建任务时绑定的当前 sessionId"端口,
|
|
288
|
+
* scheduler.add() 经此填充 boundSessionId(RFC-406 D6)。
|
|
289
|
+
*/
|
|
290
|
+
getSessionId?(): string;
|
|
291
|
+
}
|
|
292
|
+
interface FireContext {
|
|
293
|
+
/** 本次计划触发时刻(ticker 路径传入,runNow 用实际时刻)。用于 occurrenceId 派生。 */
|
|
294
|
+
scheduledFor?: number;
|
|
295
|
+
now: () => number;
|
|
296
|
+
logger?: {
|
|
297
|
+
error: (msg: string | Error, ...args: unknown[]) => void;
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
interface FireResult {
|
|
301
|
+
outcome: 'success' | 'failed';
|
|
302
|
+
/** cancel 信号——JobFireTarget 在 cancel() 路径设置 */
|
|
303
|
+
cancelled?: boolean;
|
|
304
|
+
error?: string;
|
|
305
|
+
/** job-specific 信号(JobFireTarget 附加,用于 runningJobsByTaskId 记账) */
|
|
306
|
+
jobId?: string;
|
|
189
307
|
}
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/trigger.d.ts
|
|
190
310
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* arbitration only.
|
|
311
|
+
* 事件监听型触发器。`arm()` 建立订阅、返回退订函数——退订由
|
|
312
|
+
* `SchedulerService.terminate()` 咽喉点统一调用(→ RFC-433 §4 R2),
|
|
313
|
+
* 实现方不必自行管理生命周期。
|
|
195
314
|
*/
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
315
|
+
interface EventDrivenTrigger {
|
|
316
|
+
/** 唯一标识(当前仅 `'in-process-event'`)。 */
|
|
317
|
+
readonly type: string;
|
|
318
|
+
/** 判断此 Trigger 是否负责该任务的触发。 */
|
|
319
|
+
canHandle(task: ScheduledTask): boolean;
|
|
200
320
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* 产品线对照表),本构造函数内部用 `new URL(sessionUrl).origin` 提取协议+主机+端口,
|
|
204
|
-
* 再拼接 `/api/schedules/...` lease 路径——不能直接拿 `sessionUrl` 当 baseUrl 使用。
|
|
321
|
+
* 为一个任务建立事件订阅。触发条件满足时调用 `fire(task)`。
|
|
322
|
+
* 返回退订函数——`SchedulerService` 在任务终结、stop、重新 arm 时调用,防监听泄漏。
|
|
205
323
|
*/
|
|
206
|
-
|
|
207
|
-
wsKey: string;
|
|
208
|
-
getAuth: () => Promise<{
|
|
209
|
-
token: string;
|
|
210
|
-
}>;
|
|
211
|
-
fetch?: typeof globalThis.fetch;
|
|
212
|
-
timeoutMs?: number;
|
|
213
|
-
now?: () => number;
|
|
324
|
+
arm(task: ScheduledTask, fire: (task: ScheduledTask) => void): () => void;
|
|
214
325
|
}
|
|
215
|
-
/** Create a server-arbitrated FireOwnership backed by persistenced's lease endpoints. */
|
|
216
|
-
declare function createRemoteFireOwnership(options: RemoteFireOwnershipOptions): FireOwnership;
|
|
217
326
|
//#endregion
|
|
218
327
|
//#region src/scheduler.d.ts
|
|
328
|
+
/**
|
|
329
|
+
* RFC-433 D1:任务终结的来源。仅用于诊断/日志语义——终结动作本身对三者一致
|
|
330
|
+
* (退订 → 删快照 → 删 registry),区分来源是为了让调用点自我说明。
|
|
331
|
+
*/
|
|
332
|
+
type TerminationReason = /** 用户经 `/schedule remove`、面板或 `schedule_delete` 工具删除。 */'user-removed' /** one-shot 任务成功执行完毕(FireExecutor 终态处理)。 */ | 'one-shot-complete' /** recurring cron 任务 7 天未触发,被 TickLoop 判为滞留清除。 */ | 'stale-evicted';
|
|
219
333
|
interface SchedulerDeps {
|
|
220
334
|
registry: ScheduleRegistry;
|
|
221
335
|
store: ScheduleStore;
|
|
222
336
|
ownership: FireOwnership;
|
|
223
|
-
jobRegistry: AgentJobRegistry;
|
|
224
337
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*/
|
|
228
|
-
startJob: (input: {
|
|
229
|
-
title: string;
|
|
230
|
-
prompt: string;
|
|
231
|
-
sessionId: string;
|
|
232
|
-
origin: 'user' | 'main';
|
|
233
|
-
}) => {
|
|
234
|
-
id: string;
|
|
235
|
-
};
|
|
236
|
-
getSessionId: () => string;
|
|
237
|
-
/**
|
|
238
|
-
* RFC-327 D2:向订阅者投递到点通知的窄端口(缺省 = 不支持订阅者,此类任务 fire 时
|
|
239
|
-
* 记日志跳过而非崩溃)。
|
|
338
|
+
* FireTarget handler 列表。内置 handler(job/session/subscriber)由消费方构造并传入,
|
|
339
|
+
* SchedulerService 不自动注册——消费方完全控制 handler 集合和优先级。
|
|
240
340
|
*
|
|
241
|
-
*
|
|
242
|
-
* 但**不重试、不报错**:定时通知是尽力而为的旁路信号(at-least-once 的"至少"由
|
|
243
|
-
* 下一次 cron 周期保证,不是靠即时重试)。
|
|
244
|
-
*
|
|
245
|
-
* 端口刻意只接受纯数据标识:scheduler 不知道插件服务的存在形式,也不持有其句柄。
|
|
341
|
+
* 如果传入空数组,fire() 会找不到 handler 并记日志跳过——不会崩溃。
|
|
246
342
|
*/
|
|
247
|
-
|
|
248
|
-
scheduleId: string;
|
|
249
|
-
occurrenceId: string;
|
|
250
|
-
occurredAt: number;
|
|
251
|
-
}) => boolean;
|
|
343
|
+
fireTargets: FireTarget[];
|
|
252
344
|
/**
|
|
253
|
-
* RFC-
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* 跳过而非崩溃,同 `notifySubscriber` 缺省行为的纹理)。
|
|
257
|
-
*
|
|
258
|
-
* 返回值语义(调用方——CLI wiring——必须自行捕获全部失效原因,统一收敛为 boolean):
|
|
259
|
-
* `true` = 已成功注入;`false` = 失效(会话身份不匹配/已销毁/archived/paused 等),
|
|
260
|
-
* scheduler 不重试、不报错——同 notifySubscriber 一样是尽力而为的旁路信号。
|
|
345
|
+
* RFC-423:事件驱动 Trigger 列表(如 InProcessEventTrigger)。缺省空数组——只有 cron
|
|
346
|
+
* 触发(cron 任务由 TickLoop 轮询驱动,无需消费方传入)。消费方传入事件 trigger
|
|
347
|
+
* 即启用事件触发能力。
|
|
261
348
|
*/
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
name: string;
|
|
266
|
-
origin: 'user' | 'model';
|
|
267
|
-
}) => boolean;
|
|
349
|
+
triggers?: EventDrivenTrigger[];
|
|
350
|
+
/** 可选配置覆盖(常量配置化,RFC-406 D4) */
|
|
351
|
+
config?: Partial<ScheduleConfig>;
|
|
268
352
|
now?: () => number;
|
|
353
|
+
/**
|
|
354
|
+
* `debug` 可选——生产装配(`interactive-schedule.ts`)注入的 logger 只有 `error`;
|
|
355
|
+
* 有 `debug` 的实现能拿到任务终结的来源(user-removed / one-shot-complete /
|
|
356
|
+
* stale-evicted),排查"任务怎么没了"时不必猜。
|
|
357
|
+
*/
|
|
269
358
|
logger?: {
|
|
270
359
|
error: (msg: string | Error, ...args: unknown[]) => void;
|
|
360
|
+
debug?: (msg: string, ...args: unknown[]) => void;
|
|
271
361
|
};
|
|
272
|
-
/**
|
|
273
|
-
* RFC-230:任务失败重试之间的固定延迟(ms)注入端口。缺省真实 `setTimeout`,测试可注入假
|
|
274
|
-
* 实现避免真实等待。`retryBackoffMs`(见下)为 0 时仍会调用(`sleep(0)`,一次微任务调度
|
|
275
|
-
* 点而非跳过——与当前"循环内无 await 直接进下一轮"存在细微时序差异,但不影响任何可观察
|
|
276
|
-
* 行为,见 RFC-230 §3 D6 Scheduler 小节)。
|
|
277
|
-
*/
|
|
278
|
-
sleep?: (ms: number) => Promise<void>;
|
|
279
|
-
/**
|
|
280
|
-
* RFC-230:每次重试之间的固定延迟(ms)。默认 0(保持现状:不改变既有 per-task 重试
|
|
281
|
-
* 节奏,除非显式配置非零值)。真实值来自 `app.getResilienceConfig().schedule.retryBackoffMs`,
|
|
282
|
-
* 由真实构造点(`packages/cli/src/commands/interactive-schedule.ts`)注入——不是
|
|
283
|
-
* `packages/coding` App 内部构造 `SchedulerService`(二轮评审 P2 修正的装配点误解)。
|
|
284
|
-
*/
|
|
285
|
-
retryBackoffMs?: number;
|
|
286
362
|
}
|
|
287
363
|
declare class SchedulerService {
|
|
288
364
|
private readonly deps;
|
|
289
365
|
private readonly registry;
|
|
290
|
-
private
|
|
366
|
+
private readonly persistence;
|
|
367
|
+
private readonly fireExecutor;
|
|
368
|
+
private readonly fireLogger;
|
|
369
|
+
private readonly tickLoop;
|
|
370
|
+
private readonly config;
|
|
291
371
|
private recoveryTimer;
|
|
292
|
-
|
|
293
|
-
private
|
|
372
|
+
/** RFC-435 D4:从未当过 owner 的非 owner 进程的 acquire 重试循环(TUI 释放锁后本进程可接管)。 */
|
|
373
|
+
private ensureOwnershipTimer;
|
|
294
374
|
private running;
|
|
295
|
-
|
|
296
|
-
private readonly
|
|
297
|
-
|
|
298
|
-
private
|
|
375
|
+
/** RFC-423:事件驱动 trigger 列表(消费方传入)。 */
|
|
376
|
+
private readonly triggers;
|
|
377
|
+
/** RFC-423:已 arm 的事件任务 → 退订函数(remove/stop 时调用,防监听泄漏)。 */
|
|
378
|
+
private readonly armedTriggers;
|
|
299
379
|
constructor(deps: SchedulerDeps);
|
|
300
|
-
/**
|
|
301
|
-
* RFC-169 D3d: whether this process currently holds fire ownership (ticker actively
|
|
302
|
-
* fires due tasks) vs read-only (loaded the task list but a different live process —
|
|
303
|
-
* local: another workspace-scoped otto instance; remote: another terminal sharing the
|
|
304
|
-
* same wsKey — is the one that will actually fire). Consumed by `/schedule list` to show
|
|
305
|
-
* a "read-only, another terminal holds fire ownership" hint (RFC-169 D3d TUI indicator).
|
|
306
|
-
*/
|
|
307
380
|
isFireOwner(): boolean;
|
|
308
|
-
/**
|
|
309
|
-
* F2 (RFC-169 终局 review 2026-07-15): when `isFireOwner()` is false, why. `undefined`
|
|
310
|
-
* from the underlying `FireOwnership` (e.g. the local O_EXCL implementation, which never
|
|
311
|
-
* has a "can't reach the lock" failure mode — fs ops either succeed or throw) means the
|
|
312
|
-
* distinction genuinely doesn't apply; callers should fall back to the generic
|
|
313
|
-
* "another terminal holds it" wording. Only the remote implementation can return
|
|
314
|
-
* `'network-error'`.
|
|
315
|
-
*/
|
|
316
381
|
fireOwnerFailureReason(): 'occupied' | 'network-error' | undefined;
|
|
317
382
|
/** Load persisted tasks and start the ticker (if fire owner). */
|
|
318
383
|
start(): Promise<void>;
|
|
319
384
|
/**
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
* (
|
|
385
|
+
* 任务终结的**唯一入口**(RFC-433 D1 / R2)。三条删除路径——用户 `remove()`、
|
|
386
|
+
* one-shot 完成、7 天滞留驱逐——全部经此,否则事件监听会泄漏:任务从 registry
|
|
387
|
+
* 消失后,`armedTriggers` 里的退订函数仍在,而 arm 回调闭包捕获的是 **task 对象
|
|
388
|
+
* 引用本身**(见 `armIfEventDriven`),registry 里有没有它根本不影响 fire。
|
|
389
|
+
*
|
|
390
|
+
* **三步顺序不可调换(R2b 不变量)**:`persistence.deleteOne()` 内部要
|
|
391
|
+
* `registry.get(id)` 判 session-local(`schedule-persistence.ts:37`)——registry
|
|
392
|
+
* 先删会让它读不到 task、判不出 session-local,对本就不落盘的任务白发一次 store IO。
|
|
326
393
|
*/
|
|
327
|
-
private
|
|
328
|
-
/** Clear ticker, release ownership, and persist current state. */
|
|
329
|
-
stop(): Promise<void>;
|
|
394
|
+
private terminate;
|
|
330
395
|
/**
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
* competing process exits. Without this, a single transient heartbeat failure in
|
|
335
|
-
* remote mode permanently degrades the process to read-only (contradicting D3d's
|
|
336
|
-
* auto-recovery promise) and local suspend-then-resume recovery requires a full
|
|
337
|
-
* process restart.
|
|
396
|
+
* 只从内存移除、**保留** store 快照——`reloadFromStore` 的差量同步用(store 里
|
|
397
|
+
* 本来就已经没有它了,是别的进程删的;再发一次 delete 是多余 IO)。
|
|
398
|
+
* 同样必须退订,理由同 `terminate()`。
|
|
338
399
|
*/
|
|
400
|
+
private evictFromMemory;
|
|
401
|
+
/** Clear ticker, release ownership, and persist current state. */
|
|
402
|
+
stop(): Promise<void>;
|
|
339
403
|
private startRecovery;
|
|
340
404
|
private recoveryAttempt;
|
|
341
405
|
private stopRecovery;
|
|
342
406
|
/**
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
* - Parses the cron expression to compute nextFireAt + jitter
|
|
347
|
-
* - Enforces a max of 50 tasks (throws if exceeded)
|
|
407
|
+
* 非 owner 进程的低频 acquire 重试。TUI 先开持锁 → daemon 后起(acquire 失败,
|
|
408
|
+
* 本循环待机)→ TUI 关闭释放锁 → 本循环在下一次 tick 接管。锁的原子性
|
|
409
|
+
* (O_EXCL / 服务端 CAS)保证任一时刻只有一个 owner——重试不会造成双主。
|
|
348
410
|
*/
|
|
411
|
+
private startEnsureOwnership;
|
|
412
|
+
private ensureOwnershipAttempt;
|
|
413
|
+
private stopEnsureOwnership;
|
|
349
414
|
add(input: ScheduledTaskInput): ScheduledTask;
|
|
350
|
-
/** Remove a task by id. Returns true if the task existed and was removed. */
|
|
351
415
|
remove(id: string): boolean;
|
|
352
|
-
/**
|
|
353
|
-
* Update mutable fields of a task.
|
|
354
|
-
*
|
|
355
|
-
* If cronExpression changes, nextFireAt is recalculated with jitter.
|
|
356
|
-
* Fields not in the patch are left unchanged.
|
|
357
|
-
*/
|
|
358
416
|
update(id: string, patch: Partial<Pick<ScheduledTask, 'name' | 'prompt' | 'cronExpression' | 'enabled'>>): boolean;
|
|
359
|
-
/** Return all tasks, ordered by nextFireAt ascending. */
|
|
360
417
|
list(): ScheduledTask[];
|
|
361
|
-
/**
|
|
362
|
-
* RFC-229 M2: return the set of task IDs that currently have a running job.
|
|
363
|
-
* Used by the plugin panel to show [R] status indicators.
|
|
364
|
-
*/
|
|
365
418
|
getRunningTaskIds(): Set<string>;
|
|
366
|
-
/**
|
|
367
|
-
* RFC-229 M3: read the most recent fire log entries for a task.
|
|
368
|
-
* Returns up to `maxEntries` lines, newest first. Each entry is { timestamp, status }.
|
|
369
|
-
*/
|
|
370
419
|
readFireLogs(taskId: string, maxEntries?: number): Array<{
|
|
371
420
|
timestamp: string;
|
|
372
421
|
status: string;
|
|
373
422
|
}>;
|
|
423
|
+
runNow(id: string): void;
|
|
424
|
+
cancel(taskId: string): CancelResult;
|
|
374
425
|
/**
|
|
375
|
-
*
|
|
376
|
-
*
|
|
426
|
+
* 若任务是事件驱动(eventTrigger 存在),用匹配的 EventDrivenTrigger arm 监听。
|
|
427
|
+
* fire 回调复用既有 fireExecutor 路径——Trigger 只决定"何时",FireTarget 决定"投哪"。
|
|
377
428
|
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
* `firing` synchronously before awaiting startJob) could dispatch two jobs for one task.
|
|
429
|
+
* 死循环闸门2(D4):事件驱动任务默认拒绝 JobFireTarget 投递——只有 subscriber/session
|
|
430
|
+
* 目标(不产生新 job)才放行。配 job 会产生新 job → 高死循环风险,在此拒绝而非静默。
|
|
381
431
|
*/
|
|
382
|
-
|
|
432
|
+
private armIfEventDriven;
|
|
433
|
+
private disarmIfArmed;
|
|
434
|
+
private disarmAll;
|
|
383
435
|
/**
|
|
384
|
-
*
|
|
436
|
+
* 非 owner 进程拒绝创建 session-local 任务时的诊断文案(RFC-357 D2 R3 的执行面)。
|
|
385
437
|
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
438
|
+
* **拒绝本身不放宽**——它防的是"任务出现在列表里却永远不触发"的静默失效:非 owner
|
|
439
|
+
* 进程不跑 ticker,任务又 durable:false 不落盘,owner 进程 loadAllTasks 也看不见它。
|
|
440
|
+
* 本方法只解决"用户知道被拒了,但不知道该怎么办":补上**谁持有**(本地实现可从锁
|
|
441
|
+
* 文件解出 pid/host)与**多久能好**(RFC-435 D4 的非 owner acquire 重试会在锁释放后
|
|
442
|
+
* 自动接管,无需重启进程)。
|
|
388
443
|
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
* Sets a cancel intent in `cancelRequested` so the in-flight `fire()` loop stops
|
|
393
|
-
* retrying — it does NOT prematurely remove the task from `firing` (that belongs to
|
|
394
|
-
* `fire().finally`), avoiding a race window where the task appears idle but `fire()` is
|
|
395
|
-
* still running (RFC-229 R10).
|
|
444
|
+
* 三态退化:远程实现无本地 pid 概念 → 无主体文案;`network-error` 时不能说"另一终端
|
|
445
|
+
* 持有"(可能压根没连上服务端,让用户去找一个不存在的第二终端是更糟的误导)——这条
|
|
446
|
+
* 三态区分沿用 `handlers/schedule.ts:105-109` 既有 readOnlyHint 范式,不另造一套判据。
|
|
396
447
|
*/
|
|
397
|
-
|
|
448
|
+
private describeSessionOwnershipRefusal;
|
|
398
449
|
/**
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
|
|
402
|
-
private tick;
|
|
403
|
-
/**
|
|
404
|
-
* Execute a task: start a job, monitor completion via registry events,
|
|
405
|
-
* retry on failure up to maxRetries. Handles one-shot cleanup.
|
|
450
|
+
* session-local 任务创建时绑定的 sessionId。
|
|
451
|
+
* RFC-406 D6:从 SessionFireTarget handler 的 getSessionId() 端口读取(旧 SchedulerDeps.getSessionId
|
|
452
|
+
* 语义迁入此 handler)。add() 用它来设置 boundSessionId。
|
|
406
453
|
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
* tick, but does not abort in-flight fire() calls), this in-flight call can still resolve
|
|
410
|
-
* and write a stale lastFiredAt/nextFireAt back to the store after a successor process
|
|
411
|
-
* has taken over. Harmless one-time overwrite (this process's ticker is already stopped,
|
|
412
|
-
* so no repeated corruption) — needs 5-min suspend + in-flight fire + a same-window write
|
|
413
|
-
* race with the new owner to manifest. Revisit if it proves to matter in practice.
|
|
454
|
+
* 找不到 SessionFireTarget(消费方未装配 session 投递)时回退空字符串——此时 add() 的
|
|
455
|
+
* wantsSession 分支不会命中(session 任务无 handler 可 fire),boundSessionId 也无意义。
|
|
414
456
|
*/
|
|
457
|
+
private getSessionIdForTask;
|
|
458
|
+
}
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region src/cron-parser.d.ts
|
|
461
|
+
/**
|
|
462
|
+
* Parse a 5-field cron expression (minute hour dom month dow) and return the next
|
|
463
|
+
* fire time >= from (inclusive). All times in local timezone.
|
|
464
|
+
*
|
|
465
|
+
* Throws on invalid expressions.
|
|
466
|
+
*
|
|
467
|
+
* Field syntax: wildcard (*), single value (5), step (e.g. \*\/15 or 5\/15),
|
|
468
|
+
* range (1-5), list (1,15,30), range with step (1-10\/2).
|
|
469
|
+
*
|
|
470
|
+
* Unsupported: L, W, ?, #, name aliases (MON, JAN).
|
|
471
|
+
*
|
|
472
|
+
* ## DoM∧DoW semantics
|
|
473
|
+
*
|
|
474
|
+
* If both day-of-month and day-of-week are constrained (not a bare *),
|
|
475
|
+
* a date matches if **EITHER** field matches (standard vixie-cron OR behaviour).
|
|
476
|
+
*
|
|
477
|
+
* ## Day-of-week mapping
|
|
478
|
+
*
|
|
479
|
+
* 0 = Sunday, 1–6 = Monday–Saturday, 7 = also Sunday.
|
|
480
|
+
*/
|
|
481
|
+
declare function parseCron(expr: string, from?: Date): Date;
|
|
482
|
+
//#endregion
|
|
483
|
+
//#region src/remote-schedule-store.d.ts
|
|
484
|
+
interface RemoteScheduleStoreOptions {
|
|
415
485
|
/**
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
* `
|
|
419
|
-
*
|
|
420
|
-
* 若用随机 id,重复投递会被当成两次不同触发,幂等失效。
|
|
421
|
-
*
|
|
422
|
-
* 返回是否送达;未送达(服务未运行/宿主未接线)只记日志,不抛错、不重试。
|
|
486
|
+
* `--session-url` 的原始值(如 `http://host:3001/api/storage/sessions`)——**不是**裸
|
|
487
|
+
* origin,该值本身是完整资源路径(对齐 `otto serve` 后端约定)。内部用
|
|
488
|
+
* `new URL(sessionUrl).origin` 提取协议+主机+端口,再拼接 `/api/storage/schedules`——
|
|
489
|
+
* 与 `remote-fire-ownership.ts` 的同一提取逻辑保持一致,不能直接拿 sessionUrl 当前缀用。
|
|
423
490
|
*/
|
|
424
|
-
|
|
491
|
+
sessionUrl: string;
|
|
492
|
+
getAuth: () => Promise<{
|
|
493
|
+
token: string;
|
|
494
|
+
}>;
|
|
495
|
+
fetch?: typeof globalThis.fetch;
|
|
496
|
+
timeoutMs?: number;
|
|
497
|
+
wsKey?: string | (() => string | undefined);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Create a ScheduleStore backed by the persistenced `/api/storage/schedules` namespace's
|
|
501
|
+
* generic storage route (`storage.ts`, `namespace='schedules'`) — distinct from the
|
|
502
|
+
* lease sub-route (`schedule-lease.ts`, `/api/schedules/...`) which handles fire-ownership
|
|
503
|
+
* arbitration only.
|
|
504
|
+
*/
|
|
505
|
+
declare function createRemoteScheduleStore(options: RemoteScheduleStoreOptions): ScheduleStore;
|
|
506
|
+
//#endregion
|
|
507
|
+
//#region src/schedule-lease-migrations.d.ts
|
|
508
|
+
declare const SCHEDULE_LEASE_MIGRATIONS: Migration[];
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/remote-fire-ownership.d.ts
|
|
511
|
+
interface RemoteFireOwnershipOptions {
|
|
425
512
|
/**
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
* 返回是否注入成功;失败(无注入端口/会话身份不匹配/会话已销毁等——由端口内部判断并
|
|
431
|
-
* 折叠为 boolean)只记日志,不抛错、不重试——同 `fireSubscriber` 的旁路信号纪律。
|
|
513
|
+
* `--session-url` 的原始值(如 `http://host:3001/api/storage/sessions`)——**不是**裸
|
|
514
|
+
* origin。该值本身是完整资源路径(对齐 `otto serve` 后端约定,见 RFC-146 §1.3 两条远程
|
|
515
|
+
* 产品线对照表),本构造函数内部用 `new URL(sessionUrl).origin` 提取协议+主机+端口,
|
|
516
|
+
* 再拼接 `/api/schedules/...` lease 路径——不能直接拿 `sessionUrl` 当 baseUrl 使用。
|
|
432
517
|
*/
|
|
433
|
-
|
|
434
|
-
|
|
518
|
+
sessionUrl: string;
|
|
519
|
+
wsKey: string;
|
|
520
|
+
getAuth: () => Promise<{
|
|
521
|
+
token: string;
|
|
522
|
+
}>;
|
|
523
|
+
fetch?: typeof globalThis.fetch;
|
|
524
|
+
timeoutMs?: number;
|
|
525
|
+
now?: () => number;
|
|
526
|
+
}
|
|
527
|
+
/** Create a server-arbitrated FireOwnership backed by persistenced's lease endpoints. */
|
|
528
|
+
declare function createRemoteFireOwnership(options: RemoteFireOwnershipOptions): FireOwnership;
|
|
529
|
+
//#endregion
|
|
530
|
+
//#region src/fire-logger.d.ts
|
|
531
|
+
declare class FireLogger {
|
|
532
|
+
private config;
|
|
533
|
+
constructor(config: ScheduleConfig);
|
|
435
534
|
/**
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
* 'success': ready (diff produced) or applied
|
|
440
|
-
* 'failed': failed
|
|
441
|
-
* 'timeout': maxDurationMs elapsed while still running (triggers cancel)
|
|
442
|
-
* 'canceled': the job was canceled (deliberately, by user via cancel(), not a failure)
|
|
443
|
-
*
|
|
444
|
-
* ready is NOT terminal in the engine, but for the schedule it's a success
|
|
445
|
-
* (the diff exists, the user can /job apply later).
|
|
535
|
+
* 写一行 fire log 到 ${OTTO_HOME}/schedule-logs/<taskId>-<epoch>.log,然后清理超出
|
|
536
|
+
* maxFireLogsPerTask 的旧日志。best-effort——错误静默吞。
|
|
446
537
|
*
|
|
447
|
-
*
|
|
448
|
-
* and should not be killed while waiting.
|
|
538
|
+
* RFC-345:影子态零磁盘写入——fire log 是纯本机持久化产物,影子进程跳过。
|
|
449
539
|
*/
|
|
450
|
-
|
|
540
|
+
write(taskId: string, status: string): void;
|
|
541
|
+
/** 保留最新 maxFireLogsPerTask 条日志文件。 */
|
|
542
|
+
private prune;
|
|
451
543
|
/**
|
|
452
|
-
*
|
|
453
|
-
* 30-min window (spreads periodic load); one-shot tasks use a 90s window (review S2 —
|
|
454
|
-
* RFC-087 §D3 intends one-shot "in ~30s/~90s", not "delayed by up to 30 min"; a 30-min
|
|
455
|
-
* window made "remind me in 30 minutes" arrive up to ~61 minutes later).
|
|
456
|
-
* Uses djb2 hash of the task id for reproducibility across restarts.
|
|
544
|
+
* 读取最近的 fire log 条目。返回最新 maxEntries 条,每条 { timestamp, status }。
|
|
457
545
|
*/
|
|
458
|
-
|
|
546
|
+
read(taskId: string, maxEntries?: number): Array<{
|
|
547
|
+
timestamp: string;
|
|
548
|
+
status: string;
|
|
549
|
+
}>;
|
|
550
|
+
}
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region src/fire-targets/job-fire-target.d.ts
|
|
553
|
+
interface JobFireTargetDeps {
|
|
554
|
+
jobRegistry: AgentJobRegistry;
|
|
459
555
|
/**
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
* process delete another process's schedules (A2).
|
|
556
|
+
* Dispatch an agent job. Mirrors AgentJobService.start() shape:
|
|
557
|
+
* synchronously returns a record with at least { id }.
|
|
463
558
|
*/
|
|
559
|
+
startJob: (input: {
|
|
560
|
+
title: string;
|
|
561
|
+
prompt: string;
|
|
562
|
+
sessionId: string;
|
|
563
|
+
origin: 'user' | 'main'; /** RFC-438:per-job 指定模型与思考档位(task.modelId/thinkingLevel 透传)。 */
|
|
564
|
+
model?: {
|
|
565
|
+
id: string;
|
|
566
|
+
thinkingLevel?: ThinkingLevel;
|
|
567
|
+
};
|
|
568
|
+
}) => {
|
|
569
|
+
id: string;
|
|
570
|
+
};
|
|
464
571
|
/**
|
|
465
|
-
* RFC-
|
|
466
|
-
*
|
|
467
|
-
* 全部调用点共享此拦截,不逐点打补丁(否则新增调用点时会漏防,正是原设计的漏洞)。
|
|
572
|
+
* RFC-435 D1:绑定会话 id 端口。serve/daemon 无人值守装配可缺省——
|
|
573
|
+
* job-only 模式无会话,fire 时 sessionId 用空串(无会话绑定,notify 不投递)。
|
|
468
574
|
*/
|
|
469
|
-
|
|
575
|
+
getSessionId?: () => string;
|
|
576
|
+
/** RFC-230:重试之间的固定延迟(ms)。默认 0。 */
|
|
577
|
+
retryBackoffMs?: number;
|
|
578
|
+
/** 测试可注入假 sleep 避免真实等待。 */
|
|
579
|
+
sleep?: (ms: number) => Promise<void>;
|
|
580
|
+
/** FireLogger 实例(写 fire 日志) */
|
|
581
|
+
fireLogger: FireLogger;
|
|
582
|
+
/** needs_input 硬上限倍数(对齐 ScheduleConfig.needsInputMaxExtensionFactor,默认 3)。 */
|
|
583
|
+
needsInputMaxExtensionFactor?: number;
|
|
584
|
+
}
|
|
585
|
+
declare class JobFireTarget implements FireTarget {
|
|
586
|
+
private deps;
|
|
587
|
+
readonly type = "job";
|
|
588
|
+
private readonly runningJobsByTaskId;
|
|
589
|
+
private readonly cancelRequested;
|
|
590
|
+
private _nextFireToken;
|
|
591
|
+
constructor(deps: JobFireTargetDeps);
|
|
592
|
+
canHandle(task: ScheduledTask): boolean;
|
|
593
|
+
fire(task: ScheduledTask, ctx: FireContext): Promise<FireResult>;
|
|
594
|
+
/**
|
|
595
|
+
* 设置 cancel 意图——fire() 循环在下次 attempt 前检查并停止。
|
|
596
|
+
* 同时调用 jobRegistry.cancel() 终止当前正在运行的 job。
|
|
597
|
+
* 返回 CancelResult(与 SchedulerService.cancel() 的返回类型对齐)。
|
|
598
|
+
*/
|
|
599
|
+
requestCancel(taskId: string): CancelResult;
|
|
600
|
+
/** 清理 fire 完成后的记账状态。由 FireExecutor 在 fire().finally 中调用。 */
|
|
601
|
+
cleanup(taskId: string): void;
|
|
602
|
+
/** 当前有正在运行的 job 的 task id 集合。 */
|
|
603
|
+
getRunningTaskIds(): Set<string>;
|
|
604
|
+
private waitForCompletion;
|
|
605
|
+
}
|
|
606
|
+
//#endregion
|
|
607
|
+
//#region src/fire-targets/session-fire-target.d.ts
|
|
608
|
+
interface SessionFireTargetDeps {
|
|
470
609
|
/**
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
* 调用 store——它本就不在 store 里),避免对不存在的记录发起一次多余的 IO。
|
|
610
|
+
* 把 prompt 注入 boundSessionId 对应的当前会话。
|
|
611
|
+
* 返回 true=已成功注入;false=失效(会话身份不匹配/已销毁/archived/paused 等)。
|
|
474
612
|
*/
|
|
475
|
-
|
|
613
|
+
followUp: (task: {
|
|
614
|
+
prompt: string;
|
|
615
|
+
boundSessionId: string;
|
|
616
|
+
name: string;
|
|
617
|
+
origin: 'user' | 'model';
|
|
618
|
+
}) => boolean;
|
|
476
619
|
/**
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
* sessionRef** 上"复活",直接击穿"不落盘"这条防线(本 RFC 最初设计遗漏的关键路径)。
|
|
620
|
+
* RFC-406 D6:session-local 任务创建时绑定的当前 sessionId 端口。
|
|
621
|
+
* scheduler.add() 经 SchedulerService.getSessionIdForTask() 读取此值填充 boundSessionId。
|
|
622
|
+
* (旧 SchedulerDeps.getSessionId 的语义迁入此 handler。)
|
|
481
623
|
*/
|
|
482
|
-
|
|
624
|
+
getSessionId: () => string;
|
|
625
|
+
/** FireLogger 实例(写 fire 日志) */
|
|
626
|
+
fireLogger: FireLogger;
|
|
627
|
+
}
|
|
628
|
+
declare class SessionFireTarget implements FireTarget {
|
|
629
|
+
private deps;
|
|
630
|
+
readonly type = "session";
|
|
631
|
+
constructor(deps: SessionFireTargetDeps);
|
|
632
|
+
canHandle(task: ScheduledTask): boolean;
|
|
633
|
+
/** RFC-406 D6:暴露当前 sessionId 供 scheduler.add() 绑定 boundSessionId。 */
|
|
634
|
+
getSessionId(): string;
|
|
635
|
+
fire(task: ScheduledTask, _ctx: FireContext): Promise<FireResult>;
|
|
636
|
+
}
|
|
637
|
+
//#endregion
|
|
638
|
+
//#region src/fire-targets/subscriber-fire-target.d.ts
|
|
639
|
+
interface SubscriberFireTargetDeps {
|
|
483
640
|
/**
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
* fires 1440 times/day with no prior retention — the directory grew unbounded). Best-effort
|
|
487
|
-
* throughout — swallows errors silently (logging is non-critical).
|
|
641
|
+
* 向订阅者投递到点通知的窄端口。返回是否真的送达——false 表示服务未运行/已停机。
|
|
642
|
+
* 端口刻意只接受纯数据标识:handler 不知道插件服务的存在形式,也不持有其句柄。
|
|
488
643
|
*/
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
644
|
+
notify: (subscriber: ScheduleSubscriber, tick: {
|
|
645
|
+
scheduleId: string;
|
|
646
|
+
occurrenceId: string;
|
|
647
|
+
occurredAt: number;
|
|
648
|
+
}) => boolean;
|
|
649
|
+
/** FireLogger 实例(写 fire 日志) */
|
|
650
|
+
fireLogger: FireLogger;
|
|
651
|
+
}
|
|
652
|
+
declare class SubscriberFireTarget implements FireTarget {
|
|
653
|
+
private deps;
|
|
654
|
+
readonly type = "plugin-service";
|
|
655
|
+
constructor(deps: SubscriberFireTargetDeps);
|
|
656
|
+
canHandle(task: ScheduledTask): boolean;
|
|
657
|
+
fire(task: ScheduledTask, ctx: FireContext): Promise<FireResult>;
|
|
658
|
+
}
|
|
659
|
+
//#endregion
|
|
660
|
+
//#region src/triggers/in-process-event-trigger.d.ts
|
|
661
|
+
interface InProcessEventTriggerDeps {
|
|
662
|
+
jobRegistry: AgentJobRegistry;
|
|
663
|
+
now?: () => number;
|
|
664
|
+
logger?: {
|
|
665
|
+
error: (msg: string | Error, ...args: unknown[]) => void;
|
|
666
|
+
};
|
|
667
|
+
/** 闸门B:debounce 窗口(ms)。缺省 60s。 */
|
|
668
|
+
debounceWindowMs?: number;
|
|
669
|
+
/** 闸门B:窗口内最大触发次数。缺省 5。 */
|
|
670
|
+
maxFiresPerWindow?: number;
|
|
671
|
+
}
|
|
672
|
+
declare class InProcessEventTrigger implements EventDrivenTrigger {
|
|
673
|
+
private deps;
|
|
674
|
+
readonly type = "in-process-event";
|
|
675
|
+
/** 闸门B:每任务的触发时刻队列(滑动窗口)。 */
|
|
676
|
+
private readonly fireTimestamps;
|
|
677
|
+
private readonly now;
|
|
678
|
+
private readonly debounceWindowMs;
|
|
679
|
+
private readonly maxFiresPerWindow;
|
|
680
|
+
constructor(deps: InProcessEventTriggerDeps);
|
|
681
|
+
canHandle(task: ScheduledTask): boolean;
|
|
682
|
+
arm(task: ScheduledTask, fire: (task: ScheduledTask) => void): () => void;
|
|
683
|
+
private matches;
|
|
684
|
+
/** 闸门B:滑动窗口频率检查。返回 true=放行,false=超限拦截。 */
|
|
685
|
+
private passesDebounce;
|
|
492
686
|
}
|
|
493
687
|
//#endregion
|
|
494
688
|
//#region src/schedule-capability.d.ts
|
|
@@ -506,6 +700,19 @@ interface ScheduleCapability {
|
|
|
506
700
|
* the model-facing guidance on when to pick which.
|
|
507
701
|
*/
|
|
508
702
|
target?: 'job' | 'session';
|
|
703
|
+
/**
|
|
704
|
+
* RFC-423:进程内事件触发。存在时任务由事件驱动(如 job diff applied)而非 cron。
|
|
705
|
+
* 必须配 target:'session'(或 subscriber)——事件触发不允许 job 投递(死循环闸门)。
|
|
706
|
+
*/
|
|
707
|
+
eventTrigger?: {
|
|
708
|
+
source: string;
|
|
709
|
+
event: string;
|
|
710
|
+
filter?: {
|
|
711
|
+
sessionId?: string;
|
|
712
|
+
};
|
|
713
|
+
}; /** RFC-438:到点执行时使用的模型 id(如 "deepseek/deepseek-v4-flash")。缺省 = 当前会话模型。 */
|
|
714
|
+
modelId?: string; /** RFC-438:思考档位(配 modelId 使用)。 */
|
|
715
|
+
thinkingLevel?: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
509
716
|
}) => {
|
|
510
717
|
taskId: string;
|
|
511
718
|
nextFireAt: number;
|
|
@@ -531,7 +738,14 @@ interface ScheduleCapability {
|
|
|
531
738
|
reason?: string;
|
|
532
739
|
};
|
|
533
740
|
}
|
|
534
|
-
|
|
741
|
+
interface ScheduleCapabilityDeps {
|
|
742
|
+
/**
|
|
743
|
+
* RFC-438 D3b:create 时校验模型 id。返回错误信息或 undefined(合法)。
|
|
744
|
+
* 缺省 = 不校验(运行时 fail-closed 兜底——fire 时 resolveJobModel 抛错)。
|
|
745
|
+
*/
|
|
746
|
+
validateModelId?: (id: string) => string | undefined;
|
|
747
|
+
}
|
|
748
|
+
declare function createScheduleCapability(getScheduler: () => SchedulerService | undefined, deps?: ScheduleCapabilityDeps): ScheduleCapability;
|
|
535
749
|
//#endregion
|
|
536
|
-
export { type
|
|
750
|
+
export { DEFAULT_SCHEDULE_CONFIG, type EventDrivenTrigger, type EventTriggerSpec, FireLogger, type FireOwnership, type FireTarget, InProcessEventTrigger, JobFireTarget, SCHEDULE_LEASE_MIGRATIONS, ScheduleRegistry, type ScheduleStore, type ScheduleSubscriber, type ScheduledTask, type ScheduledTaskInput, type SchedulerDeps, SchedulerService, SessionFireTarget, SubscriberFireTarget, type TerminationReason, createLocalFireOwnership, createRemoteFireOwnership, createRemoteScheduleStore, createScheduleCapability, createScheduleStore, parseCron };
|
|
537
751
|
//# sourceMappingURL=index.d.ts.map
|