@migor/agentia 0.9.4 → 0.9.5
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/CHANGELOG.md +688 -4
- package/README.md +1 -1
- package/dist/AGENTS.md +313 -33
- package/dist/container/container.js +15 -0
- package/dist/container/container.js.map +1 -1
- package/dist/core/limits.d.ts +93 -13
- package/dist/core/limits.js +103 -13
- package/dist/core/limits.js.map +1 -1
- package/dist/core/line-framing.d.ts +36 -0
- package/dist/core/line-framing.js +39 -0
- package/dist/core/line-framing.js.map +1 -0
- package/dist/core/message.d.ts +11 -0
- package/dist/core/run.d.ts +68 -4
- package/dist/core/run.js +27 -1
- package/dist/core/run.js.map +1 -1
- package/dist/core/schema.js +59 -8
- package/dist/core/schema.js.map +1 -1
- package/dist/core/timeout.d.ts +43 -1
- package/dist/core/timeout.js +76 -3
- package/dist/core/timeout.js.map +1 -1
- package/dist/core/tool.d.ts +25 -1
- package/dist/core/trace.d.ts +40 -1
- package/dist/core/trace.js +26 -0
- package/dist/core/trace.js.map +1 -1
- package/dist/engine/defer.d.ts +49 -0
- package/dist/engine/defer.js +72 -0
- package/dist/engine/defer.js.map +1 -0
- package/dist/engine/forwarded.d.ts +14 -5
- package/dist/engine/forwarded.js +4 -1
- package/dist/engine/forwarded.js.map +1 -1
- package/dist/engine/loop-result.d.ts +65 -7
- package/dist/engine/loop-result.js +24 -5
- package/dist/engine/loop-result.js.map +1 -1
- package/dist/engine/loop.d.ts +2 -0
- package/dist/engine/loop.js +157 -17
- package/dist/engine/loop.js.map +1 -1
- package/dist/engine/mcp-server-http.d.ts +2 -0
- package/dist/engine/mcp-server-http.js +215 -0
- package/dist/engine/mcp-server-http.js.map +1 -0
- package/dist/engine/mcp-server-stdio.d.ts +15 -0
- package/dist/engine/mcp-server-stdio.js +106 -0
- package/dist/engine/mcp-server-stdio.js.map +1 -0
- package/dist/engine/mcp-server.d.ts +134 -0
- package/dist/engine/mcp-server.js +232 -0
- package/dist/engine/mcp-server.js.map +1 -0
- package/dist/engine/menu-drift.d.ts +34 -0
- package/dist/engine/menu-drift.js +81 -0
- package/dist/engine/menu-drift.js.map +1 -0
- package/dist/engine/policy.d.ts +2 -1
- package/dist/engine/policy.js +7 -1
- package/dist/engine/policy.js.map +1 -1
- package/dist/engine/replay.d.ts +2 -1
- package/dist/engine/replay.js +3 -2
- package/dist/engine/replay.js.map +1 -1
- package/dist/engine/resume-input.d.ts +10 -0
- package/dist/engine/resume-input.js +11 -0
- package/dist/engine/resume-input.js.map +1 -1
- package/dist/engine/run-config.d.ts +26 -2
- package/dist/engine/run-config.js +60 -0
- package/dist/engine/run-config.js.map +1 -1
- package/dist/engine/spec.d.ts +33 -4
- package/dist/engine/spec.js.map +1 -1
- package/dist/engine/tool-context.d.ts +10 -5
- package/dist/engine/tool-context.js +5 -1
- package/dist/engine/tool-context.js.map +1 -1
- package/dist/engine/tracer.d.ts +43 -5
- package/dist/engine/tracer.js +73 -10
- package/dist/engine/tracer.js.map +1 -1
- package/dist/engine/trimming.js +28 -2
- package/dist/engine/trimming.js.map +1 -1
- package/dist/engine/turn.d.ts +60 -3
- package/dist/engine/turn.js +180 -71
- package/dist/engine/turn.js.map +1 -1
- package/dist/engine/types.d.ts +89 -3
- package/dist/engine/types.js +38 -2
- package/dist/engine/types.js.map +1 -1
- package/dist/eval/export.d.ts +48 -0
- package/dist/eval/export.js +146 -0
- package/dist/eval/export.js.map +1 -0
- package/dist/eval/harvest.js +7 -2
- package/dist/eval/harvest.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/adapter-options.d.ts +22 -0
- package/dist/integrations/adapter-options.js +51 -0
- package/dist/integrations/adapter-options.js.map +1 -1
- package/dist/integrations/anthropic.d.ts +4 -2
- package/dist/integrations/anthropic.js +27 -27
- package/dist/integrations/anthropic.js.map +1 -1
- package/dist/integrations/file-sink.d.ts +20 -0
- package/dist/integrations/file-sink.js +27 -0
- package/dist/integrations/file-sink.js.map +1 -0
- package/dist/integrations/mcp-http.d.ts +3 -3
- package/dist/integrations/mcp-http.js +1 -1
- package/dist/integrations/mcp-http.js.map +1 -1
- package/dist/integrations/mcp-protocol.d.ts +115 -0
- package/dist/integrations/mcp-protocol.js +69 -0
- package/dist/integrations/mcp-protocol.js.map +1 -0
- package/dist/integrations/mcp-stdio.d.ts +3 -3
- package/dist/integrations/mcp-stdio.js +17 -3
- package/dist/integrations/mcp-stdio.js.map +1 -1
- package/dist/integrations/mcp.d.ts +6 -95
- package/dist/integrations/mcp.js +24 -66
- package/dist/integrations/mcp.js.map +1 -1
- package/dist/integrations/metrics-otlp.js +52 -7
- package/dist/integrations/metrics-otlp.js.map +1 -1
- package/dist/integrations/metrics-render.d.ts +15 -0
- package/dist/integrations/metrics-render.js +79 -2
- package/dist/integrations/metrics-render.js.map +1 -1
- package/dist/integrations/metrics-state.d.ts +99 -1
- package/dist/integrations/metrics-state.js +128 -4
- package/dist/integrations/metrics-state.js.map +1 -1
- package/dist/integrations/metrics.d.ts +62 -6
- package/dist/integrations/metrics.js +49 -8
- package/dist/integrations/metrics.js.map +1 -1
- package/dist/integrations/openai.js +9 -31
- package/dist/integrations/openai.js.map +1 -1
- package/dist/integrations/otlp.d.ts +3 -3
- package/dist/integrations/report.d.ts +7 -0
- package/dist/integrations/report.js +20 -1
- package/dist/integrations/report.js.map +1 -1
- package/dist/runtime/memory.d.ts +61 -1
- package/dist/runtime/memory.js +47 -7
- package/dist/runtime/memory.js.map +1 -1
- package/dist/runtime/run.d.ts +12 -2
- package/dist/runtime/run.js +112 -15
- package/dist/runtime/run.js.map +1 -1
- package/dist/store/fsStore.js +10 -4
- package/dist/store/fsStore.js.map +1 -1
- package/dist/store/record.d.ts +41 -0
- package/dist/store/record.js +54 -0
- package/dist/store/record.js.map +1 -0
- package/dist/store/redisStore.d.ts +3 -1
- package/dist/store/redisStore.js +21 -7
- package/dist/store/redisStore.js.map +1 -1
- package/dist/store/sqliteStore.d.ts +23 -0
- package/dist/store/sqliteStore.js +103 -8
- package/dist/store/sqliteStore.js.map +1 -1
- package/dist/store/store.d.ts +65 -2
- package/dist/store/store.js +5 -2
- package/dist/store/store.js.map +1 -1
- package/dist/toolkit/capability-cycles.d.ts +50 -0
- package/dist/toolkit/capability-cycles.js +88 -0
- package/dist/toolkit/capability-cycles.js.map +1 -0
- package/dist/toolkit/capability-slice.d.ts +86 -0
- package/dist/toolkit/capability-slice.js +98 -0
- package/dist/toolkit/capability-slice.js.map +1 -0
- package/dist/toolkit/module.d.ts +7 -1
- package/dist/toolkit/module.js +65 -32
- package/dist/toolkit/module.js.map +1 -1
- package/dist/transport/approval-policy.d.ts +1 -1
- package/dist/transport/approval-policy.js +8 -4
- package/dist/transport/approval-policy.js.map +1 -1
- package/dist/transport/async.d.ts +197 -18
- package/dist/transport/async.js +871 -85
- package/dist/transport/async.js.map +1 -1
- package/dist/transport/drain-gate.d.ts +6 -2
- package/dist/transport/drain-gate.js +14 -2
- package/dist/transport/drain-gate.js.map +1 -1
- package/dist/transport/http-endpoints.d.ts +151 -0
- package/dist/transport/http-endpoints.js +480 -0
- package/dist/transport/http-endpoints.js.map +1 -0
- package/dist/transport/http-io.d.ts +66 -0
- package/dist/transport/http-io.js +145 -0
- package/dist/transport/http-io.js.map +1 -0
- package/dist/transport/http-route.d.ts +10 -3
- package/dist/transport/http-route.js +18 -3
- package/dist/transport/http-route.js.map +1 -1
- package/dist/transport/http-shapes.d.ts +11 -0
- package/dist/transport/http-shapes.js +31 -1
- package/dist/transport/http-shapes.js.map +1 -1
- package/dist/transport/http.d.ts +22 -35
- package/dist/transport/http.js +98 -476
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/owner-id.d.ts +29 -0
- package/dist/transport/owner-id.js +46 -0
- package/dist/transport/owner-id.js.map +1 -0
- package/dist/transport/owner-liveness.d.ts +8 -0
- package/dist/transport/owner-liveness.js +56 -0
- package/dist/transport/owner-liveness.js.map +1 -0
- package/dist/transport/resume-policy.d.ts +48 -10
- package/dist/transport/resume-policy.js +25 -10
- package/dist/transport/resume-policy.js.map +1 -1
- package/dist/transport/scheduler.js +9 -23
- package/dist/transport/scheduler.js.map +1 -1
- package/dist/transport/slot-pool.d.ts +9 -1
- package/dist/transport/slot-pool.js +9 -1
- package/dist/transport/slot-pool.js.map +1 -1
- package/dist/transport/task-events.d.ts +34 -3
- package/dist/transport/task-events.js +86 -5
- package/dist/transport/task-events.js.map +1 -1
- package/dist/transport/wake-policy.d.ts +50 -0
- package/dist/transport/wake-policy.js +43 -0
- package/dist/transport/wake-policy.js.map +1 -0
- package/package.json +6 -4
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import type { MessageParam } from '../core/message.js';
|
|
2
2
|
import type { ModelClient } from '../core/tool.js';
|
|
3
3
|
import type { AgentRunResult } from '../engine/types.js';
|
|
4
|
-
import type { RunStatus } from '../core/run.js';
|
|
4
|
+
import type { RunStatus, TaskEvent } from '../core/run.js';
|
|
5
5
|
import type { RunInvocationOptions } from '../engine/spec.js';
|
|
6
6
|
import type { MaybePromise, TaskRecord, TaskStore } from '../store/store.js';
|
|
7
7
|
import type { TraceRecordEvent } from '../core/trace.js';
|
|
8
|
+
import type { SuspendedSummary } from './wake-policy.js';
|
|
8
9
|
/**
|
|
9
10
|
* Agentia —— 异步任务宿主(spec §6.3 异步 / §6.5 确定性 / §6.6 换宿主不换语义)。
|
|
10
11
|
*
|
|
11
12
|
* - submit 即回(queued),后台驱动状态机 queued → running → succeeded/failed;
|
|
12
|
-
* 工具标了 `approval: 'required'` 时 run 可在回合间挂起为 **
|
|
13
|
+
* 工具标了 `approval: 'required'` 时 run 可在回合间挂起为 **suspended**
|
|
13
14
|
* (HITL:非终态、不占并发槽、不触发 TaskSink.onFinished),`approve()` 给齐决定后恢复;
|
|
15
|
+
* 工具调 `ctx.deferUntil(t)` 时同样挂起(durable timer:`suspendedReason: 'timer'`),
|
|
16
|
+
* 到点由 `resumePending` / `poll` 的**惰性**扫描唤醒(不起定时器,见 wake-policy.ts);
|
|
17
|
+
* 两条挂起路径的**闸按原因分开**(等谁的决定 vs 等到点了没);
|
|
14
18
|
* - **at-least-once 去重**:同一 idempotencyKey 重复 submit,若上一任务仍在
|
|
15
19
|
* queued/running/succeeded 则直接返回既有记录,不重复执行(失败可重试新任务);
|
|
16
20
|
* - run 记录落 TaskStore(v1 InMemoryTaskStore),trace 随 result 一同保留;
|
|
@@ -108,13 +112,49 @@ export declare class TaskStreamError extends Error {
|
|
|
108
112
|
}
|
|
109
113
|
/**
|
|
110
114
|
* `approve` 的失败:带 HTTP 语义的状态码(404 = 任务不存在,409 = 任务当前不在
|
|
111
|
-
* `
|
|
115
|
+
* `suspended` 状态),HTTP 宿主据此回对应响应。
|
|
112
116
|
* module 级 export —— 不进公共导出面(纯宿主内部实现细节)。
|
|
113
117
|
*/
|
|
114
118
|
export declare class TaskApproveError extends Error {
|
|
119
|
+
/** 400 = 入参语义不合法(decisions 里有本任务待决之外的 id,2026-09-28 深评 P1-2) */
|
|
120
|
+
readonly status: 400 | 404 | 409;
|
|
121
|
+
constructor(status: 400 | 404 | 409, message: string);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* `cancel` 的失败:带 HTTP 语义的状态码(404 = 任务不存在,409 = 已终态 / 在飞 run 不在本进程)。
|
|
125
|
+
* 形状与 `TaskApproveError` 同款(module 级 export,不进公共导出面)。
|
|
126
|
+
*/
|
|
127
|
+
export declare class TaskCancelError extends Error {
|
|
115
128
|
readonly status: 404 | 409;
|
|
116
129
|
constructor(status: 404 | 409, message: string);
|
|
117
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* `signalTask` 的失败:带 HTTP 语义的状态码(404 = 任务不存在,409 = 任务当前不在
|
|
133
|
+
* `suspended` 状态 / 同 eventId 已投递)。形状与 `TaskCancelError` 同款
|
|
134
|
+
* (module 级 export,不进公共导出面)。
|
|
135
|
+
*/
|
|
136
|
+
export declare class TaskEventError extends Error {
|
|
137
|
+
readonly status: 404 | 409;
|
|
138
|
+
constructor(status: 404 | 409, message: string);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* `submit` 在**排队段已满**时的失败(`AsyncRunnerOptions.maxQueued`,2026-09-28 外部深评 T5)。
|
|
142
|
+
*
|
|
143
|
+
* 为什么单独一个类型、而不是复用 `TaskInputError`:两者在 HTTP 上的**含义不同** ——
|
|
144
|
+
* 入参不合法是 **400**(调用方改请求就能过),排队满**不是调用方的错**,是 **503
|
|
145
|
+
* + `Retry-After`**(同一次请求稍后重发就该过)。宿主若把它们混成一支,运维看到的就是
|
|
146
|
+
* 「你的请求有问题」,而实际是「服务该扩容 / 该退避」—— 与 `maxConcurrentRuns` 的 503
|
|
147
|
+
* 那支刻意分开的理由同款。
|
|
148
|
+
*
|
|
149
|
+
* `status` 是自陈字段(本仓 `TaskApproveError` 起的惯例):宿主按它选状态码,不靠
|
|
150
|
+
* `instanceof` 猜;本类目前只有 503 一种。
|
|
151
|
+
*
|
|
152
|
+
* module 级 export —— 不进公共导出面(与 TaskApproveError / TaskCancelError / TaskEventError 同档)。
|
|
153
|
+
*/
|
|
154
|
+
export declare class TaskQueueFullError extends Error {
|
|
155
|
+
readonly status = 503;
|
|
156
|
+
constructor(message: string);
|
|
157
|
+
}
|
|
118
158
|
export interface AsyncRunnerOptions {
|
|
119
159
|
client?: ModelClient;
|
|
120
160
|
store?: TaskStore;
|
|
@@ -133,6 +173,27 @@ export interface AsyncRunnerOptions {
|
|
|
133
173
|
};
|
|
134
174
|
/** 同时执行的任务上限;缺省不限。超出部分排队等槽位(状态保持 queued) */
|
|
135
175
|
concurrency?: number;
|
|
176
|
+
/**
|
|
177
|
+
* **在等槽位的任务数**上限(`concurrency` 之外的那一段);缺省 0 = 不限。
|
|
178
|
+
*
|
|
179
|
+
* 为什么需要它(2026-09-28 外部深评 T5):`concurrency` 只约束**同时在跑**几个,
|
|
180
|
+
* 而「跑不上、排在后面」的那一段**没有任何上限** —— `submit` 永远收单(POST /tasks
|
|
181
|
+
* 永远 202),排队段因此随调用方灌入无界增长:每条排队任务都是一条 `TaskRecord` +
|
|
182
|
+
* 一棵 `#execute` 的悬挂 promise + 一个 `#slots` 等待者,**全部在内存里**。
|
|
183
|
+
* `concurrency: 1` 挡不住它 —— 那只是让排队段更长。
|
|
184
|
+
*
|
|
185
|
+
* 语义:**「在等槽位」= 已受理(落库 + `#execute` 同步前段已计数)、尚未拿到槽位**。
|
|
186
|
+
* 拿到槽位即离开本计数(此后归 `concurrency` 管),所以两个旋钮管的是两段、可加:
|
|
187
|
+
* 进程内最多 `concurrency + maxQueued` 条任务同时在推进。
|
|
188
|
+
*
|
|
189
|
+
* ⚠️ 两个刻意的口径:
|
|
190
|
+
* - **只在 `submit`(新活入口)判**:恢复路径(approve / 到期唤醒 / 崩溃重投)**不受闸**
|
|
191
|
+
* —— 它们推进的是**已受理**的任务,拦下来等于把任务永久搁死在 store 里。
|
|
192
|
+
* - **超限是 503 不是 400**:这不是调用方把参数写错了,是「现在排不下、稍后再来」。
|
|
193
|
+
* HTTP 宿主回 503 + `Retry-After`(与 `maxConcurrentRuns` 同款),错误类型
|
|
194
|
+
* `TaskQueueFullError` 让宿主能把它与「入参不合法」分开。
|
|
195
|
+
*/
|
|
196
|
+
maxQueued?: number;
|
|
136
197
|
/** 任务完成回调(进程内);见 TaskSink */
|
|
137
198
|
taskSinks?: TaskSink[];
|
|
138
199
|
/**
|
|
@@ -166,7 +227,7 @@ export interface AsyncRunnerOptions {
|
|
|
166
227
|
* 审批等待超时(毫秒,HITL);缺省 0 = 不限(一直等人)。
|
|
167
228
|
*
|
|
168
229
|
* **惰性判定,不起定时器**:`approve` / `poll` / `resumePending` 读到一个
|
|
169
|
-
* `
|
|
230
|
+
* `suspended` 任务时,若它挂起已超过该值,框架自动把**全部待决项**写成
|
|
170
231
|
* 「denied,reason: '审批超时'」并恢复执行(模型收到拒绝理由,可自行换路)。
|
|
171
232
|
* 也就是说超时只在「有人读它」时生效 —— 没人读的任务不会自己动(进程里不养定时器,
|
|
172
233
|
* 崩溃/重启也不依赖任何在飞回调)。
|
|
@@ -180,17 +241,41 @@ export interface AsyncRunnerOptions {
|
|
|
180
241
|
* (既不是「关掉流」也不是「不限」),0 语义归类见 `core/limits.ts`。
|
|
181
242
|
*
|
|
182
243
|
* 内存量级:单条事件正文受 `maxEventChars` 约束(入参/成功出参缺省 2000 字符),
|
|
183
|
-
* 故 500 条 ≈ 1 MB/任务;终态流的保留条数是代码里的常量(最近 16
|
|
244
|
+
* 故 500 条 ≈ 1 MB/任务;终态流的保留条数是代码里的常量(最近 16 条),
|
|
245
|
+
* **非终态**流的缓冲配额见 `streamNonTerminalBuffers`(缺省 32 条流)。
|
|
184
246
|
* 超出条数上限时丢**最旧**的,并向订阅方发一帧 `stream.truncated`(**不静默**)。
|
|
185
247
|
*/
|
|
186
248
|
streamBufferEvents?: number;
|
|
249
|
+
/**
|
|
250
|
+
* **非终态**流的**缓冲**配额(条数,不是事件数):本进程最多为多少条**还没跑完**的流
|
|
251
|
+
* 保留「可回放的事件缓冲」;缺省 32。超出即从最旧、**无订阅者**的流开始回收缓冲。
|
|
252
|
+
*
|
|
253
|
+
* 为什么需要它:`streamBufferEvents` 只钉住**每任务**的量,而「挂着的任务」(等审批 /
|
|
254
|
+
* 等事件 / 等定时器)永远走不到终态 ⇒ 表项与缓冲只增不减(2026-09-28 外部深评 T4)。
|
|
255
|
+
* 回收的是**回放历史**,不是流本身:表项与序号都留着,连上来的读者先收到一帧
|
|
256
|
+
* `stream.truncated`(明示缺口)再转实时推送 —— 所以「回放看不全」是**看得见**的降级。
|
|
257
|
+
*
|
|
258
|
+
* `0` = 不为**没人读的**非终态流留回放缓冲(只有实时转发)。必须是非负安全整数,坏值构造期抛 TypeError。
|
|
259
|
+
* ⚠️ **有订阅者的流不受配额约束**(不抽走正在读的东西);终态流的**表项**保留数仍是
|
|
260
|
+
* 内部常量(最近 16 条)—— 它本来就是有界的,没走旋钮。
|
|
261
|
+
*/
|
|
262
|
+
streamNonTerminalBuffers?: number;
|
|
187
263
|
}
|
|
188
264
|
/** resumePending 的启动扫描选项 */
|
|
189
265
|
export interface ResumePendingOptions {
|
|
190
266
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
267
|
+
* 他进程任务的判定开关;缺省 0 = 不判断,一律重派(重启即续跑,单进程旧语义)。
|
|
268
|
+
*
|
|
269
|
+
* `> 0` 时按**两档**判他进程的记录(判据在 `resume-policy.ts`,这里只说取舍):
|
|
270
|
+
* ① 记录里的主机名是本机 ⇒ **问 pid 还在不在**(`owner-liveness.ts`)——
|
|
271
|
+
* 「主人在」的直接证据:**在的绝不抢**(不管它起跑多久),**不在的立刻可抢**
|
|
272
|
+
* (不等保鲜期,崩溃孤儿不会饿死);
|
|
273
|
+
* ② 判不了(异主机 / 升级前写下的旧格式 ownerId / 自定义串)⇒ 退回本参数名义上的
|
|
274
|
+
* 那件事:`startedAt`(退化到 `createdAt`)距今不足该值就当他还在跑、先别抢。
|
|
275
|
+
*
|
|
276
|
+
* ⚠️ ②只是**启发式**(「记录看起来还新」),不是租约:单进程部署建议 0;多进程共库
|
|
277
|
+
* 时它只是「问不到 pid」时的兜底,别再指望它单独把「重复执行」挡住。
|
|
278
|
+
* 阈值取值要大于「一个进程从启动到跑满一条记录」的合理时间,否则长跑会被抢。
|
|
194
279
|
*/
|
|
195
280
|
staleAfterMs?: number;
|
|
196
281
|
}
|
|
@@ -209,11 +294,18 @@ export declare class AsyncRunner {
|
|
|
209
294
|
#private;
|
|
210
295
|
private readonly app;
|
|
211
296
|
readonly store: TaskStore;
|
|
212
|
-
/**
|
|
297
|
+
/**
|
|
298
|
+
* 本进程标识:写进认领的 `TaskRecord.ownerId`,供 resumePending 区分他我。
|
|
299
|
+
*
|
|
300
|
+
* 形状 `p<pid>@<host>-<8位十六进制>`(真源见 `owner-id.ts`)。为什么带主机名与 pid:
|
|
301
|
+
* 光有时间判不出「主人还在不在」(`staleAfterMs` 是**新鲜度**不是**租约**),
|
|
302
|
+
* 而「同主机 + pid 还在」是能直接问操作系统的**直接证据**(见 `owner-liveness.ts`)。
|
|
303
|
+
*/
|
|
213
304
|
readonly ownerId: string;
|
|
214
305
|
private readonly client;
|
|
215
306
|
private readonly sessionStore;
|
|
216
307
|
private readonly concurrency;
|
|
308
|
+
private readonly maxQueued;
|
|
217
309
|
private readonly runTimeoutMs;
|
|
218
310
|
private readonly approvalTimeoutMs;
|
|
219
311
|
/** 已受理但未达终态的任务数(queued + running)—— /healthz 与 drain 共用 */
|
|
@@ -252,7 +344,7 @@ export declare class AsyncRunner {
|
|
|
252
344
|
* 1. **先重放、后实时**:`opts.from`(= SSE 的 `Last-Event-ID` / `?from=`)之后的事件先补发,
|
|
253
345
|
* 再挂实时订阅 —— 「连上时已经跑了一半」的客户端因此也能拿到完整前缀。
|
|
254
346
|
* 2. **终态收口**:任务已终态 ⇒ 补发完缓冲 + 一帧 `end`,**不留**一条永远不会再产出事件的流。
|
|
255
|
-
* 挂起(`
|
|
347
|
+
* 挂起(`suspended`)**不是**终态:批了(或睡到点了)会接着跑,流必须开着(见 isTerminalTask)。
|
|
256
348
|
* 3. **跨进程不假装**:本进程没有这条流时(别的进程在跑 / 任务早于本进程),发一帧
|
|
257
349
|
* `unavailable` 后**立即收口**:终态补 `end`,非终态补 `closed`(流级收尾,不是
|
|
258
350
|
* 伪造终态)—— 非终态只发 `unavailable` 就返回的话,这条 SSE 只剩心跳永远挂着。
|
|
@@ -268,13 +360,44 @@ export declare class AsyncRunner {
|
|
|
268
360
|
byIdempotency(key: string): MaybePromise<TaskRecord | undefined>;
|
|
269
361
|
list(): MaybePromise<TaskRecord[]>;
|
|
270
362
|
/**
|
|
271
|
-
*
|
|
363
|
+
* 取消一个任务(durable 配套 5 的另一半;设计稿 `docs/plans/2026-09-28-cancel-api.md`)。
|
|
364
|
+
*
|
|
365
|
+
* 三种「在服」状态各自的语义 —— 都落在这一处,不分散:
|
|
366
|
+
* - **running**:中止在飞 signal ⇒ 引擎以 `stopReason: 'aborted'` 收尾(尊重 signal 的
|
|
367
|
+
* client 是**真中断**,token 不再烧;不尊重者等价「放弃等待」—— 与 `runTimeoutMs`
|
|
368
|
+
* 同一份契约)。等它真落库再返回:不等的话调用方拿到的是 `status` 仍为 running 的快照,
|
|
369
|
+
* 「取消了但状态还在跑」比不返回更误导。
|
|
370
|
+
* - **queued**(还没起跑):落终态;`#executeInner` 拿到槽位后会**重读**再判,
|
|
371
|
+
* 所以被取消的排队任务**绝不起跑**(这条是本次实现顺带补上的洞)。记账由本方法
|
|
372
|
+
* 自己做;那一趟还活着的 `#execute`(在等槽位)靠 `#cancelSettled` 在 finally 里
|
|
373
|
+
* 跳过它那一份(否则 onFinished 两次)。
|
|
374
|
+
* - **suspended**(两种原因都算):落终态 —— 两条唤醒闸都 gate 在 `status === 'suspended'`
|
|
375
|
+
* 上,「不唤醒」因此是翻转状态的**推论**,不需要第二处标志。它没有活着的 `#execute`
|
|
376
|
+
* (挂起时那趟就走完了),所以记账(sinks / 事件流 / 认领释放 / 等待者 / 会话清理)
|
|
377
|
+
* 由本方法自己做 —— 也因此它**不进** `#cancelSettled`(没有消费者,加了就是泄漏)。
|
|
378
|
+
*
|
|
379
|
+
* 状态按**意图**落,不按机制落:超时的收尾语义**不变**(仍是 `failed` + `error.type` 为
|
|
380
|
+
* `'timeout'`),取消带 `error = abortedError()`(取消不是失败,但原因要可查)。
|
|
381
|
+
*
|
|
382
|
+
* 不做静默 no-op:已终态 ⇒ `TaskCancelError(409)`(状态不对要说出来),不存在 ⇒ 404。
|
|
383
|
+
* 边界(如实写在这里):`running` 的 run **不在本进程**(多进程部署 / 上一世留下的 running)
|
|
384
|
+
* 时抛 409 —— 本进程没有句柄可中断,改状态假装取消只会在那条 run 跑完时被覆盖回去。
|
|
385
|
+
*
|
|
386
|
+
* ⚠️ 同一个 409 判据下还有**第二种**情形(2026-09-28 外部复核):任务已在本进程受理、
|
|
387
|
+
* 但还没走到 `#runAborts` 登记(在等并发槽位,或恢复路径刚写完 `running` 尚未派发完)。
|
|
388
|
+
* 它**不是**「不在本进程」—— 原文案一律那么说,对这一类是说反话。两种情形分开措辞
|
|
389
|
+
* (见 `#cancel` 的判据),但**闸不放开**:放开会让等槽位那趟 `#execute` 的 finally
|
|
390
|
+
* 双发 `onFinished`(spec §10 的变异 M23)。
|
|
391
|
+
*/
|
|
392
|
+
cancel(taskId: string): MaybePromise<TaskRecord>;
|
|
393
|
+
/**
|
|
394
|
+
* 审批一个处于 `suspended` 的任务(HITL)。
|
|
272
395
|
*
|
|
273
396
|
* - **逐 tool_use_id 幂等**:已存在的决定不覆盖(第一次决定赢)—— 重复提交 /
|
|
274
397
|
* 并发点击不会推翻已有决定,也不会让恢复段重复执行;
|
|
275
398
|
* - **并发重入共享在飞那次**:同一任务的并发 approve(双击「批准」/两个审批人
|
|
276
399
|
* 同时批)返回同一个 Promise —— 否则两个调用都在对方落库前读到
|
|
277
|
-
* `
|
|
400
|
+
* `suspended`、各自判「决定齐了」、**各派发一次**(同一任务重复执行,
|
|
278
401
|
* 与 resumePending 的闸门同一 bug 类)。被共享的那次覆盖不到的决定不丢:
|
|
279
402
|
* 调用方从返回的记录看到任务仍在等待,重试即并入;
|
|
280
403
|
* - 决定齐了就恢复:`status` 回 `running`、**先落库再派发**(与 `#redispatch`
|
|
@@ -291,8 +414,40 @@ export declare class AsyncRunner {
|
|
|
291
414
|
/** 同一任务的在飞审批(approve 的重入闸,见上)。**与惰性超时恢复共用同一把闸** ——
|
|
292
415
|
* approve 与 #expireAndResume 都会「填决定 + 落库 + 派发」,不互斥就是同一任务跑两遍。 */
|
|
293
416
|
private readonly inflightApprovals;
|
|
417
|
+
/**
|
|
418
|
+
* 投递一个事件给**挂起**的任务(2026-09-28 ⑥,run 事件投入口;
|
|
419
|
+
* `POST /tasks/:id/events` 的宿主方法侧,与 approve/cancel 对称)。
|
|
420
|
+
*
|
|
421
|
+
* 语义(设计稿 §3 逐条的落点):
|
|
422
|
+
* - **只对 `suspended` 生效**:不存在 ⇒ 404;其余状态(含已终态)⇒ 409
|
|
423
|
+
* (「不唤醒已终态」是这条状态闸的推论,与 approve 同款「状态不对要说出来」);
|
|
424
|
+
* - **幂等**:`eventId` 给定时按 `rec.deliveredEventIds` 去重,重复 ⇒ 409
|
|
425
|
+
* (簿记随记录落库,重启不丢;有界 FIFO,见 MAX_DELIVERED_EVENT_IDS)。
|
|
426
|
+
* 不给 `eventId` 就**没有恰好一次**:重复投递 = 重复进历史(如实,不假装);
|
|
427
|
+
* - **投完即续跑**(两种挂起原因都算 —— 对 timer 挂起这就是「提前醒」):
|
|
428
|
+
* 事件进 `rec.pendingEvents`、状态回 `running`,**先落库再派发**
|
|
429
|
+
* (与 approve / #wakeDueInner 同一条纪律:崩在窗口里不能丢事件)。
|
|
430
|
+
* 事件**不**直接追加进 `rec.spec.messages` —— 续跑判定只认历史末尾一条
|
|
431
|
+
* (`tailToolUses`),追加 user 消息会把续跑判成新对话;注入由引擎在
|
|
432
|
+
* 未决 tool_use 解决之后做(见 engine/loop.ts 的 deliverTaskEvents);
|
|
433
|
+
* - **不沿旧 wakeAt**:记录上的旧目标时刻不动(闸都 gate 在 suspended 上,
|
|
434
|
+
* 状态一翻它就失效);醒来重跑那一批时工具若再次 `deferUntil`,
|
|
435
|
+
* `wakeAt` 由新回合重新落定;
|
|
436
|
+
* - drain 期间**仍允许**(与 approve 同理由:挂起的任务只有外部输入能推进)。
|
|
437
|
+
*
|
|
438
|
+
* 并发闸与 approve **不同款**:approve 共享在飞那次(决定是幂等合并),事件**串行成链**
|
|
439
|
+
* (两条并发事件是两条不同的输入,共享 = 静默丢一条)。与其它恢复路径
|
|
440
|
+
* (approve / 到期唤醒,各有自己的闸)的竞速:进闸后重读再判挡住绝大多数;
|
|
441
|
+
* 剩下的窄窗口与既有「approve × wakeDue 分闸」同类(那两条按挂起原因天然互斥,
|
|
442
|
+
* 事件两种原因都适用,是这个闸新盖的缝 —— 已记在 spec §10 2026-09-28 ⑥)。
|
|
443
|
+
*/
|
|
444
|
+
signalTask(taskId: string, event: TaskEvent): Promise<TaskRecord>;
|
|
445
|
+
/** 同一任务的在飞事件投递(signalTask 的重入闸,见上) */
|
|
446
|
+
private readonly inflightSignals;
|
|
294
447
|
/** 已受理但未达终态的任务数(queued + running)—— 健康检查与 drain 共用同一口径 */
|
|
295
448
|
get inFlight(): number;
|
|
449
|
+
/** 挂起读数:按原因分组的条数 + 最早的目标时刻(无时间挂起时为 `null`) */
|
|
450
|
+
get suspendedSummary(): SuspendedSummary;
|
|
296
451
|
/** 是否已进入优雅停机(drain 之后为 true)—— HTTP 宿主据此对新单回 503 */
|
|
297
452
|
get isDraining(): boolean;
|
|
298
453
|
/**
|
|
@@ -302,11 +457,31 @@ export declare class AsyncRunner {
|
|
|
302
457
|
* `resumePending` 续跑(所以 drain 不是「丢弃」,是「不再往前推」)。
|
|
303
458
|
* - `timeoutMs` 缺省 0 = 一直等。
|
|
304
459
|
* - 等待的是**所有已受理**的任务(queued 的也在内),不只是正在占槽位的那些。
|
|
460
|
+
*
|
|
461
|
+
* ⚠️ **这是一道单向闩:置位之后本进程不再推进任何任务,且没有复位路径**(`DrainGate` 只有
|
|
462
|
+
* 置位,没有复位)。所以 `false`(排空超时)的含义不只是「还有在飞的」——它同时给宿主
|
|
463
|
+
* 加了一条义务:**必须让进程退出**。理由(2026-09-28 PR #164 复核 §1,有探针实证):
|
|
464
|
+
* 停机窗口里被 `#dispatch` 拒掉的记录**已经落库**(`running`/`queued` + **本进程的 ownerId**),
|
|
465
|
+
* 而本进程的 `resumePending` 会按 `own-process` 跳过它们(见 resume-policy.ts),`submit` 又
|
|
466
|
+
* 已整体关闭 ⇒ 本进程内**没有任何自愈路径**。宿主若在 `false` 之后继续服务,这些记录会永远
|
|
467
|
+
* 停在 `running`:`awaitTask` 不返回,`GET /tasks/:id/stream` 还会告诉客户端
|
|
468
|
+
* `not-in-this-process`(说不对话:记录里的 ownerId 正是本进程)。只有**下一次进程启动**
|
|
469
|
+
* 才会有人认领它们(ownerId 含 pid)。参考宿主(`examples/` 各示例的 `src/main.ts`)都是
|
|
470
|
+
* `await drain(...)` 之后 `process.exit(...)`,与本条契约一致。
|
|
305
471
|
*/
|
|
306
472
|
drain(opts?: {
|
|
307
473
|
timeoutMs?: number;
|
|
308
474
|
}): Promise<boolean>;
|
|
309
|
-
/**
|
|
475
|
+
/** 在飞的到期唤醒(同一任务的重入闸,理由与 inflightApprovals 同款:两条路径都「落库 + 派发」)。 */
|
|
476
|
+
private readonly inflightWakes;
|
|
477
|
+
/**
|
|
478
|
+
* 当前**真正在排队**的任务数(`maxQueued` 的实时读数,诊断用)—— 与闸用的是**同一个**
|
|
479
|
+
* `#queueDepth`,所以「看到的数」就是「判据的数」,不会各说各话。
|
|
480
|
+
* 与 `inFlight` 的分工:`inFlight` 是「本进程已受理、未达终态」的全量(含在跑、含挂起),
|
|
481
|
+
* 本读数是其中「排不上、在等槽位」的那一段。
|
|
482
|
+
*/
|
|
483
|
+
get queued(): number;
|
|
484
|
+
/** 等到任务终态;超时抛错。`suspended` 不是终态 —— 继续等(人在路上 / 时刻未到)。 */
|
|
310
485
|
awaitTask(taskId: string, opts?: {
|
|
311
486
|
timeoutMs?: number;
|
|
312
487
|
intervalMs?: number;
|
|
@@ -316,17 +491,21 @@ export declare class AsyncRunner {
|
|
|
316
491
|
* (running 视为进程中断)。返回重派数量(异步 store 下返回 Promise<number>)。
|
|
317
492
|
* 幂等键去重照常生效。
|
|
318
493
|
*
|
|
319
|
-
* `
|
|
320
|
-
*
|
|
321
|
-
*
|
|
494
|
+
* `suspended`(HITL)**不捡**:它在等人、不是孤儿(进程没死也可能挂着)。
|
|
495
|
+
* 但读到它会做**惰性判定**,两条闸各管各的原因:
|
|
496
|
+
* - `approval`:配了 `approvalTimeoutMs` 且已超时的挂起任务自动全拒
|
|
497
|
+
* (`denied, reason: '审批超时'`)并重派;
|
|
498
|
+
* - `timer`:`wakeAt` 已到的睡着的任务**唤醒并重派**(醒来重跑那一批)。
|
|
499
|
+
* ⚠️ `drain()` 之后不唤醒(配套 5):停机窗口里天级的 run 被叫起来接着跑 = 部署卡死。
|
|
322
500
|
*
|
|
323
501
|
* **认领先落库、再派发**(见 `#redispatch`);异步 store 的认领落库失败会让本方法
|
|
324
502
|
* reject —— 宁可让调用方看见「续跑没做」,也不要静默放出一批会被重复执行的任务。
|
|
325
503
|
*
|
|
326
504
|
* 多进程共用一个 store 时靠 `ownerId` 区分他我:
|
|
327
505
|
* - 本进程的记录一律跳过(它还在本进程内存里跑,重派 = 跑两遍);
|
|
328
|
-
* - `staleAfterMs > 0`
|
|
329
|
-
*
|
|
506
|
+
* - `staleAfterMs > 0` 时启动他进程判定:**同主机就问 pid 还在不在**(在的不抢、
|
|
507
|
+
* 不在的立刻抢),问不到(异主机 / 旧格式 ownerId)才退回保鲜期启发式。
|
|
508
|
+
* 缺省 0 = 不判断、一律重派(单进程旧语义)。判定分级与边界见 `resume-policy.ts`。
|
|
330
509
|
*/
|
|
331
510
|
resumePending(opts?: ResumePendingOptions): number | Promise<number>;
|
|
332
511
|
/**
|