@migor/agentia 0.10.5 → 0.10.6
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 +128 -1
- package/README.md +1 -1
- package/dist/AGENTS.md +9 -1
- package/dist/container/container.js +3 -0
- package/dist/container/container.js.map +1 -1
- package/dist/core/abort.js +6 -1
- package/dist/core/abort.js.map +1 -1
- package/dist/core/json.js +4 -0
- package/dist/core/json.js.map +1 -1
- package/dist/core/limits.d.ts +5 -5
- package/dist/core/limits.js +5 -5
- package/dist/core/limits.js.map +1 -1
- package/dist/core/sse.d.ts +0 -13
- package/dist/core/sse.js +28 -2
- package/dist/core/sse.js.map +1 -1
- package/dist/core/timeout.d.ts +8 -0
- package/dist/core/timeout.js +19 -1
- package/dist/core/timeout.js.map +1 -1
- package/dist/core/trace.d.ts +7 -0
- package/dist/core/trace.js +7 -0
- package/dist/core/trace.js.map +1 -1
- package/dist/engine/loop.js +37 -11
- package/dist/engine/loop.js.map +1 -1
- package/dist/engine/mcp-server.d.ts +17 -1
- package/dist/engine/mcp-server.js +36 -2
- package/dist/engine/mcp-server.js.map +1 -1
- package/dist/engine/policy.js +5 -1
- package/dist/engine/policy.js.map +1 -1
- package/dist/engine/replay.js +63 -1
- package/dist/engine/replay.js.map +1 -1
- package/dist/engine/run-config.d.ts +6 -0
- package/dist/engine/run-config.js +15 -6
- package/dist/engine/run-config.js.map +1 -1
- package/dist/engine/spec.d.ts +6 -0
- package/dist/engine/spec.js.map +1 -1
- package/dist/engine/stop-reason.js +19 -1
- package/dist/engine/stop-reason.js.map +1 -1
- package/dist/engine/tool-events.d.ts +1 -0
- package/dist/engine/tool-events.js +7 -0
- package/dist/engine/tool-events.js.map +1 -1
- package/dist/engine/tracer.js +10 -3
- package/dist/engine/tracer.js.map +1 -1
- package/dist/engine/turn.d.ts +4 -12
- package/dist/engine/turn.js +89 -8
- package/dist/engine/turn.js.map +1 -1
- package/dist/engine/types.d.ts +10 -1
- package/dist/engine/types.js +1 -0
- package/dist/engine/types.js.map +1 -1
- package/dist/engine/usage.js +12 -4
- package/dist/engine/usage.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/integrations/anthropic.d.ts +9 -1
- package/dist/integrations/anthropic.js +42 -7
- package/dist/integrations/anthropic.js.map +1 -1
- package/dist/integrations/file-sink.d.ts +1 -0
- package/dist/integrations/file-sink.js +35 -32
- package/dist/integrations/file-sink.js.map +1 -1
- package/dist/integrations/mcp-http.js +33 -9
- package/dist/integrations/mcp-http.js.map +1 -1
- package/dist/integrations/mcp-protocol.js +33 -16
- package/dist/integrations/mcp-protocol.js.map +1 -1
- package/dist/integrations/metrics-otlp.js +4 -0
- package/dist/integrations/metrics-otlp.js.map +1 -1
- package/dist/integrations/metrics.js +17 -2
- package/dist/integrations/metrics.js.map +1 -1
- package/dist/integrations/openai.d.ts +6 -1
- package/dist/integrations/openai.js +45 -8
- package/dist/integrations/openai.js.map +1 -1
- package/dist/integrations/otlp.js +6 -0
- package/dist/integrations/otlp.js.map +1 -1
- package/dist/runtime/memory.d.ts +0 -8
- package/dist/runtime/memory.js +24 -2
- package/dist/runtime/memory.js.map +1 -1
- package/dist/runtime/run.js +34 -9
- package/dist/runtime/run.js.map +1 -1
- package/dist/store/fsStore.d.ts +2 -0
- package/dist/store/fsStore.js +38 -5
- package/dist/store/fsStore.js.map +1 -1
- package/dist/store/record.d.ts +40 -0
- package/dist/store/record.js +72 -0
- package/dist/store/record.js.map +1 -1
- package/dist/store/redisStore.js +15 -4
- package/dist/store/redisStore.js.map +1 -1
- package/dist/store/sqliteStore.js +39 -14
- package/dist/store/sqliteStore.js.map +1 -1
- package/dist/store/store.js +10 -4
- package/dist/store/store.js.map +1 -1
- package/dist/toolkit/module.js +1 -0
- package/dist/toolkit/module.js.map +1 -1
- package/dist/toolkit/prompt.js +12 -3
- package/dist/toolkit/prompt.js.map +1 -1
- package/dist/transport/approval-policy.d.ts +1 -1
- package/dist/transport/approval-policy.js +18 -3
- package/dist/transport/approval-policy.js.map +1 -1
- package/dist/transport/approval-supervisor.d.ts +15 -3
- package/dist/transport/approval-supervisor.js +37 -11
- package/dist/transport/approval-supervisor.js.map +1 -1
- package/dist/transport/async.d.ts +1 -0
- package/dist/transport/async.js +143 -23
- package/dist/transport/async.js.map +1 -1
- package/dist/transport/drain-gate.js +13 -1
- package/dist/transport/drain-gate.js.map +1 -1
- package/dist/transport/http-endpoints.js +7 -0
- package/dist/transport/http-endpoints.js.map +1 -1
- package/dist/transport/resume-scanner.d.ts +3 -0
- package/dist/transport/resume-scanner.js +10 -1
- package/dist/transport/resume-scanner.js.map +1 -1
- package/dist/transport/run-call-options.js +43 -1
- package/dist/transport/run-call-options.js.map +1 -1
- package/dist/transport/run-outcome.js +18 -1
- package/dist/transport/run-outcome.js.map +1 -1
- package/dist/transport/serve.js +10 -0
- package/dist/transport/serve.js.map +1 -1
- package/dist/transport/signal-supervisor.d.ts +3 -0
- package/dist/transport/signal-supervisor.js +5 -1
- package/dist/transport/signal-supervisor.js.map +1 -1
- package/dist/transport/stream-janitor.d.ts +45 -0
- package/dist/transport/stream-janitor.js +137 -0
- package/dist/transport/stream-janitor.js.map +1 -0
- package/dist/transport/task-wake-gate.d.ts +40 -0
- package/dist/transport/task-wake-gate.js +69 -0
- package/dist/transport/task-wake-gate.js.map +1 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/toolkit/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAkCtB,MAAM,WAAW,GAAG,IAAI,OAAO,EAAwB,CAAC;AAExD,MAAM,YAAY,GAAe;IAC/B,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE,EAAE;IACd,oBAAoB,EAAE,KAAK;CAC5B,CAAC;AAEF,oEAAoE;AACpE,MAAM,UAAU,MAAM,CAAC,IAAgB;IACrC,OAAO,CAAC,KAAe,EAAE,OAAmC,EAAQ,EAAE;QACpE,kBAAkB,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC,CAAC;AACJ,CAAC;AASD;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,0DAA0D;IAC1D,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAC/D,gDAAgD;IAChD,6CAA6C;IAC7C,MAAM,QAAQ,GAAG,CACf,IAAY,EACZ,MAAwC,EACxC,GAAoB,EACpB,IAAgB,EACV,EAAE;QACR,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC;QACvD,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;YACvB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,WAAW,EAAE,IAAI,CAAC,MAAM,IAAI,YAAY;YACxC,UAAU,EAAE,QAAQ;YACpB,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,OAAO,KAAK,SAAS;gBACxC,CAAC,CAAC;oBACE,QAAQ,EAAE;wBACR,GAAG,IAAI,CAAC,QAAQ;wBAChB,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBAC9C;iBACF;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAa,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;SACjF,CAAC,CAAC;QACH,
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/toolkit/prompt.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,4 +27,4 @@ export declare function fillTimeoutDenials(rec: TaskRecord, now: number): void;
|
|
|
27
27
|
* 决定齐了没:**每个**待决 id 都有决定(人工或超时兜底)。没有待决项时返回 true
|
|
28
28
|
* (空集真值)—— 与抽取前的行内写法逐字一致。
|
|
29
29
|
*/
|
|
30
|
-
export declare function approvalsComplete(rec: TaskRecord): boolean;
|
|
30
|
+
export declare function approvalsComplete(rec: TaskRecord, since?: number): boolean;
|
|
@@ -20,7 +20,14 @@ export function approvalExpired(rec, now, timeoutMs) {
|
|
|
20
20
|
export function fillTimeoutDenials(rec, now) {
|
|
21
21
|
rec.approvals ??= {};
|
|
22
22
|
for (const id of rec.pendingApprovals ?? []) {
|
|
23
|
-
|
|
23
|
+
const existing = rec.approvals[id];
|
|
24
|
+
// 旧挂起的决定(requestedAt 与本次不同)不挡本次超时兜底(2026-11-07 深评):
|
|
25
|
+
// 否则一条被复用的 tool_use_id 会让新一次等待永远补不出 deny。
|
|
26
|
+
const stale = existing !== undefined &&
|
|
27
|
+
rec.suspendedSince !== undefined &&
|
|
28
|
+
existing.requestedAt !== undefined &&
|
|
29
|
+
existing.requestedAt !== rec.suspendedSince;
|
|
30
|
+
if (existing !== undefined && !stale)
|
|
24
31
|
continue;
|
|
25
32
|
rec.approvals[id] = {
|
|
26
33
|
approved: false,
|
|
@@ -35,7 +42,15 @@ export function fillTimeoutDenials(rec, now) {
|
|
|
35
42
|
* 决定齐了没:**每个**待决 id 都有决定(人工或超时兜底)。没有待决项时返回 true
|
|
36
43
|
* (空集真值)—— 与抽取前的行内写法逐字一致。
|
|
37
44
|
*/
|
|
38
|
-
export function approvalsComplete(rec) {
|
|
39
|
-
return (rec.pendingApprovals ?? []).every((id) =>
|
|
45
|
+
export function approvalsComplete(rec, since) {
|
|
46
|
+
return (rec.pendingApprovals ?? []).every((id) => {
|
|
47
|
+
const d = rec.approvals?.[id];
|
|
48
|
+
if (d === undefined)
|
|
49
|
+
return false;
|
|
50
|
+
// 有本次挂起时刻时,旧挂起留下的决定不算数(2026-11-07 深评)。
|
|
51
|
+
if (since !== undefined && d.requestedAt !== undefined && d.requestedAt !== since)
|
|
52
|
+
return false;
|
|
53
|
+
return true;
|
|
54
|
+
});
|
|
40
55
|
}
|
|
41
56
|
//# sourceMappingURL=approval-policy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-policy.js","sourceRoot":"","sources":["../../src/transport/approval-policy.ts"],"names":[],"mappings":"AAcA;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,GAAe,EAAE,GAAW,EAAE,SAAiB;IAC7E,OAAO,CACL,SAAS,GAAG,CAAC;QACb,GAAG,CAAC,MAAM,KAAK,WAAW;QAC1B,mFAAmF;QACnF,4CAA4C;QAC5C,qDAAqD;QACrD,GAAG,CAAC,eAAe,KAAK,UAAU;QAClC,GAAG,GAAG,CAAC,GAAG,CAAC,cAAc,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,SAAS,CACzE,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAe,EAAE,GAAW;IAC7D,GAAG,CAAC,SAAS,KAAK,EAAE,CAAC;IACrB,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,gBAAgB,IAAI,EAAE,EAAE,CAAC;QAC5C,
|
|
1
|
+
{"version":3,"file":"approval-policy.js","sourceRoot":"","sources":["../../src/transport/approval-policy.ts"],"names":[],"mappings":"AAcA;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,GAAe,EAAE,GAAW,EAAE,SAAiB;IAC7E,OAAO,CACL,SAAS,GAAG,CAAC;QACb,GAAG,CAAC,MAAM,KAAK,WAAW;QAC1B,mFAAmF;QACnF,4CAA4C;QAC5C,qDAAqD;QACrD,GAAG,CAAC,eAAe,KAAK,UAAU;QAClC,GAAG,GAAG,CAAC,GAAG,CAAC,cAAc,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,SAAS,CACzE,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAe,EAAE,GAAW;IAC7D,GAAG,CAAC,SAAS,KAAK,EAAE,CAAC;IACrB,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,gBAAgB,IAAI,EAAE,EAAE,CAAC;QAC5C,MAAM,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACnC,oDAAoD;QACpD,0CAA0C;QAC1C,MAAM,KAAK,GACT,QAAQ,KAAK,SAAS;YACtB,GAAG,CAAC,cAAc,KAAK,SAAS;YAChC,QAAQ,CAAC,WAAW,KAAK,SAAS;YAClC,QAAQ,CAAC,WAAW,KAAK,GAAG,CAAC,cAAc,CAAC;QAC9C,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,KAAK;YAAE,SAAS;QAC/C,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG;YAClB,QAAQ,EAAE,KAAK;YACf,MAAM,EAAE,MAAM;YACd,SAAS,EAAE,QAAQ;YACnB,SAAS,EAAE,GAAG;YACd,GAAG,CAAC,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACjF,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAe,EAAE,KAAc;IAC/D,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE;QAC/C,MAAM,CAAC,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAClC,uCAAuC;QACvC,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,CAAC,WAAW,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QAChG,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SuspendedSummary } from './wake-policy.js';
|
|
2
2
|
import type { TaskRecord, TaskStore } from '../store/store.js';
|
|
3
|
+
import { TaskWakeGate } from './task-wake-gate.js';
|
|
3
4
|
/** `approve` 的入参:一批 tool_use_id → 批准/拒绝(理由可选) */
|
|
4
5
|
export type ApprovalDecisions = Record<string, {
|
|
5
6
|
approved: boolean;
|
|
@@ -24,6 +25,11 @@ export interface ApprovalSupervisorDeps {
|
|
|
24
25
|
approvalTimeoutMs: number;
|
|
25
26
|
/** 派发入口(`AsyncRunner.#dispatch`:停机闸 + 唯一派发点 + 停机窗口里的一次告警) */
|
|
26
27
|
dispatch(rec: TaskRecord): void;
|
|
28
|
+
/**
|
|
29
|
+
* 跨唤醒路径的 per-task 互斥闸(2026-11-07 深评 P1):approve / signalTask / 审批超时 /
|
|
30
|
+
* 到期唤醒共用它。两条路径并发读同一份 suspended 快照再各自派发,会让同一任务跑两遍。
|
|
31
|
+
*/
|
|
32
|
+
wakeGate?: TaskWakeGate;
|
|
27
33
|
/**
|
|
28
34
|
* cancel 的 per-task 在飞闸读数(2026-10-13 ref-run-root-close):approve 进入前等它收口再重判;
|
|
29
35
|
* 惰性超时恢复见它即**跳过本轮**(惰性路径下一轮还会再判,跳过不丢)。
|
|
@@ -47,12 +53,18 @@ export declare class ApprovalSupervisor {
|
|
|
47
53
|
*/
|
|
48
54
|
rebuild(recs: TaskRecord[]): void;
|
|
49
55
|
/**
|
|
50
|
-
* 惰性审批超时:`approvalExpired` 为真 ⇒ 进在飞闸 + 自动全拒 + 落库 +
|
|
56
|
+
* 惰性审批超时:`approvalExpired` 为真 ⇒ 进在飞闸 + 自动全拒 + 落库 + 派发。
|
|
57
|
+
*
|
|
58
|
+
* **返回是否真的起了这次恢复**(2026-11-09 深评 P2):「命中超时判定」不等于「已经恢复」
|
|
59
|
+
* —— cancel 在飞、同任务已有别的唤醒路径占着互斥闸时,这里只是**跳过本轮**。此前它在
|
|
60
|
+
* 两条跳过路径上都返回 true,`resumePending` 据此 `expired++` 会说「处理了 N 条」而实际
|
|
61
|
+
* 一条没动;更糟的是宿主若只在启动时调一次 `resumePending`,这次跳过就等于该任务的
|
|
62
|
+
* 审批超时兜底丢了(下一个重判机会只来自 `poll`)。
|
|
51
63
|
*
|
|
52
64
|
* 与原 `#lazyGates` / `#redispatch` 里那两句 `if (approvalExpired(...)) #expireAndResume(...)`
|
|
53
|
-
*
|
|
65
|
+
* 的**判定**逐字等价 —— `approvalExpired` 内部已判 `status === 'suspended'` 与
|
|
54
66
|
* `reason === 'approval'`(见 approval-policy.ts),所以 `#redispatch` 原写法里那句额外的
|
|
55
|
-
* `rec.status !== 'suspended'`
|
|
67
|
+
* `rec.status !== 'suspended'` 短路是冗余的。
|
|
56
68
|
*/
|
|
57
69
|
expireIfExpired(rec: TaskRecord, now: number): boolean;
|
|
58
70
|
/**
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { approvalExpired, approvalsComplete, fillTimeoutDenials } from './approval-policy.js';
|
|
21
21
|
import { summarizeSuspended } from './wake-policy.js';
|
|
22
22
|
import { isThenable } from '../store/store.js';
|
|
23
|
+
import { TaskWakeGate } from './task-wake-gate.js';
|
|
23
24
|
/**
|
|
24
25
|
* `approve` 的失败:带 HTTP 语义的状态码(404 = 任务不存在,409 = 任务当前不在
|
|
25
26
|
* `suspended` 状态),HTTP 宿主据此回对应响应。
|
|
@@ -56,8 +57,11 @@ export class ApprovalSupervisor {
|
|
|
56
57
|
* `approve` 与超时恢复都会「填决定 + 落库 + 派发」,不互斥就是同一任务跑两遍。 */
|
|
57
58
|
#inflight = new Map();
|
|
58
59
|
#deps;
|
|
60
|
+
/** 共享闸;单测可省略(省略时本件自建一把,跨路径互斥只在 AsyncRunner 注入时生效) */
|
|
61
|
+
#wakeGate;
|
|
59
62
|
constructor(deps) {
|
|
60
63
|
this.#deps = deps;
|
|
64
|
+
this.#wakeGate = deps.wakeGate ?? new TaskWakeGate();
|
|
61
65
|
}
|
|
62
66
|
/** 挂起读数:按原因分组的条数 + 最早的目标时刻(无时间挂起时为 `null`) */
|
|
63
67
|
get summary() {
|
|
@@ -87,12 +91,18 @@ export class ApprovalSupervisor {
|
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
93
|
/**
|
|
90
|
-
* 惰性审批超时:`approvalExpired` 为真 ⇒ 进在飞闸 + 自动全拒 + 落库 +
|
|
94
|
+
* 惰性审批超时:`approvalExpired` 为真 ⇒ 进在飞闸 + 自动全拒 + 落库 + 派发。
|
|
95
|
+
*
|
|
96
|
+
* **返回是否真的起了这次恢复**(2026-11-09 深评 P2):「命中超时判定」不等于「已经恢复」
|
|
97
|
+
* —— cancel 在飞、同任务已有别的唤醒路径占着互斥闸时,这里只是**跳过本轮**。此前它在
|
|
98
|
+
* 两条跳过路径上都返回 true,`resumePending` 据此 `expired++` 会说「处理了 N 条」而实际
|
|
99
|
+
* 一条没动;更糟的是宿主若只在启动时调一次 `resumePending`,这次跳过就等于该任务的
|
|
100
|
+
* 审批超时兜底丢了(下一个重判机会只来自 `poll`)。
|
|
91
101
|
*
|
|
92
102
|
* 与原 `#lazyGates` / `#redispatch` 里那两句 `if (approvalExpired(...)) #expireAndResume(...)`
|
|
93
|
-
*
|
|
103
|
+
* 的**判定**逐字等价 —— `approvalExpired` 内部已判 `status === 'suspended'` 与
|
|
94
104
|
* `reason === 'approval'`(见 approval-policy.ts),所以 `#redispatch` 原写法里那句额外的
|
|
95
|
-
* `rec.status !== 'suspended'`
|
|
105
|
+
* `rec.status !== 'suspended'` 短路是冗余的。
|
|
96
106
|
*/
|
|
97
107
|
expireIfExpired(rec, now) {
|
|
98
108
|
if (!approvalExpired(rec, now, this.#deps.approvalTimeoutMs))
|
|
@@ -101,8 +111,7 @@ export class ApprovalSupervisor {
|
|
|
101
111
|
// 就是「取消后照常执行」那条竞态。惰性路径不丢事:下一轮读还会再判(若取消没成)。
|
|
102
112
|
if (this.#deps.cancelInflight?.(rec.taskId))
|
|
103
113
|
return false;
|
|
104
|
-
this.#expireAndResume(rec, now);
|
|
105
|
-
return true;
|
|
114
|
+
return this.#expireAndResume(rec, now);
|
|
106
115
|
}
|
|
107
116
|
/**
|
|
108
117
|
* 审批一个处于 `suspended` 的任务(HITL)。
|
|
@@ -134,7 +143,9 @@ export class ApprovalSupervisor {
|
|
|
134
143
|
const inflight = this.#inflight.get(taskId);
|
|
135
144
|
if (inflight)
|
|
136
145
|
return inflight;
|
|
137
|
-
const run = this.#
|
|
146
|
+
const run = this.#wakeGate
|
|
147
|
+
.run(taskId, () => this.#approveInner(taskId, decisions, opts))
|
|
148
|
+
.finally(() => {
|
|
138
149
|
this.#inflight.delete(taskId);
|
|
139
150
|
});
|
|
140
151
|
this.#inflight.set(taskId, run);
|
|
@@ -173,8 +184,15 @@ export class ApprovalSupervisor {
|
|
|
173
184
|
}
|
|
174
185
|
rec.approvals ??= {};
|
|
175
186
|
for (const [id, d] of Object.entries(decisions)) {
|
|
176
|
-
|
|
177
|
-
|
|
187
|
+
const existing = rec.approvals[id];
|
|
188
|
+
// 逐 id 幂等,但**旧挂起的决定**允许被本次覆盖(2026-11-07 深评):
|
|
189
|
+
// OpenAI 兼容端点会复用 `call_1`,旧决定若不换新,新一次等待无法批准。
|
|
190
|
+
const stale = existing !== undefined &&
|
|
191
|
+
rec.suspendedSince !== undefined &&
|
|
192
|
+
existing.requestedAt !== undefined &&
|
|
193
|
+
existing.requestedAt !== rec.suspendedSince;
|
|
194
|
+
if (existing !== undefined && !stale)
|
|
195
|
+
continue;
|
|
178
196
|
rec.approvals[id] = {
|
|
179
197
|
approved: d.approved,
|
|
180
198
|
...(d.reason !== undefined ? { reason: d.reason } : {}),
|
|
@@ -186,7 +204,7 @@ export class ApprovalSupervisor {
|
|
|
186
204
|
// 惰性审批超时:人的决定先并入(先到先赢),仍空着的待决项由超时兜底成 deny
|
|
187
205
|
if (approvalExpired(rec, now, this.#deps.approvalTimeoutMs))
|
|
188
206
|
fillTimeoutDenials(rec, now);
|
|
189
|
-
const complete = approvalsComplete(rec);
|
|
207
|
+
const complete = approvalsComplete(rec, rec.suspendedSince);
|
|
190
208
|
if (complete) {
|
|
191
209
|
rec.status = 'running'; // 由 #executeInner 接管(acquireSlot → 恢复执行)
|
|
192
210
|
rec.ownerId = this.#deps.ownerId;
|
|
@@ -218,13 +236,21 @@ export class ApprovalSupervisor {
|
|
|
218
236
|
*/
|
|
219
237
|
#expireAndResume(rec, now) {
|
|
220
238
|
if (this.#inflight.has(rec.taskId))
|
|
221
|
-
return;
|
|
222
|
-
|
|
239
|
+
return false;
|
|
240
|
+
// 跨路径互斥(2026-11-07 深评 P1):其它唤醒路径在飞 ⇒ 跳过本轮。
|
|
241
|
+
// 惰性路径不丢事:下一次 poll / resumePending 还会再判。返回 false 让调用方知道
|
|
242
|
+
// 「这一轮什么都没做」(2026-11-09 深评 P2:此前无条件返回 true,计数与事实不符)。
|
|
243
|
+
if (this.#wakeGate.busy(rec.taskId))
|
|
244
|
+
return false;
|
|
245
|
+
const run = this.#wakeGate
|
|
246
|
+
.run(rec.taskId, () => this.#expireAndResumeInner(rec, now))
|
|
247
|
+
.finally(() => {
|
|
223
248
|
this.#inflight.delete(rec.taskId);
|
|
224
249
|
});
|
|
225
250
|
this.#inflight.set(rec.taskId, run);
|
|
226
251
|
// poll / resumePending 路径没有调用方接 reject —— 订阅掉,不得逃逸成 unhandled rejection
|
|
227
252
|
run.catch(() => undefined);
|
|
253
|
+
return true;
|
|
228
254
|
}
|
|
229
255
|
async #expireAndResumeInner(rec, now) {
|
|
230
256
|
// 进闸后**重读一遍**再判:闸只互斥「进入」,挡不住「进闸前已取到的旧副本」——
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-supervisor.js","sourceRoot":"","sources":["../../src/transport/approval-supervisor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC9F,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"approval-supervisor.js","sourceRoot":"","sources":["../../src/transport/approval-supervisor.ts"],"names":[],"mappings":"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"}
|
|
@@ -320,6 +320,7 @@ export declare class AsyncRunner {
|
|
|
320
320
|
*/
|
|
321
321
|
streamTask(taskId: string, listener: (frame: TaskStreamFrame) => void, opts?: {
|
|
322
322
|
from?: number;
|
|
323
|
+
pollMs?: number;
|
|
323
324
|
}): Promise<() => void>;
|
|
324
325
|
byIdempotency(key: string): MaybePromise<TaskRecord | undefined>;
|
|
325
326
|
list(): MaybePromise<TaskRecord[]>;
|
package/dist/transport/async.js
CHANGED
|
@@ -13,6 +13,8 @@ import { SlotPool } from './slot-pool.js';
|
|
|
13
13
|
import { ApprovalSupervisor } from './approval-supervisor.js';
|
|
14
14
|
import { SignalSupervisor } from './signal-supervisor.js';
|
|
15
15
|
import { ResumeScanner } from './resume-scanner.js';
|
|
16
|
+
import { TaskWakeGate } from './task-wake-gate.js';
|
|
17
|
+
import { createStreamJanitorGroup } from './stream-janitor.js';
|
|
16
18
|
import { timerDue } from './wake-policy.js';
|
|
17
19
|
import { DrainGate } from './drain-gate.js';
|
|
18
20
|
import { formatOwnerId } from './owner-id.js';
|
|
@@ -96,6 +98,12 @@ export class TaskQueueFullError extends Error {
|
|
|
96
98
|
* 如实抛 409(见 `#awaitCancelled`),不假装取消成功。
|
|
97
99
|
*/
|
|
98
100
|
const CANCEL_GRACE_MS = 2_000;
|
|
101
|
+
/**
|
|
102
|
+
* 跨进程流的兜底轮询间隔(2026-11-07 深评 F5):本进程的 `markDone` 只覆盖本进程写终态,
|
|
103
|
+
* 后续段在别的进程完成时只能靠读 store 收口。5s 是「连接不挂死」与「store 读放大」的折中;
|
|
104
|
+
* 0 可关闭(内部/测试用)。
|
|
105
|
+
*/
|
|
106
|
+
const STREAM_CROSS_PROCESS_POLL_MS = 5_000;
|
|
99
107
|
export class AsyncRunner {
|
|
100
108
|
app;
|
|
101
109
|
store;
|
|
@@ -157,6 +165,12 @@ export class AsyncRunner {
|
|
|
157
165
|
}
|
|
158
166
|
runTimeoutMs;
|
|
159
167
|
approvalTimeoutMs;
|
|
168
|
+
/**
|
|
169
|
+
* 跨唤醒路径的 per-task 互斥闸(2026-11-07 深评 P1):approve / signalTask / 审批超时 /
|
|
170
|
+
* 到期唤醒共用同一张表。缺了它,`approve × signalTask`(或 `wakeDue × signalTask`)
|
|
171
|
+
* 会各自凭同一份 suspended 快照派发一次 ⇒ 同一任务跑两遍、后写覆盖前写。
|
|
172
|
+
*/
|
|
173
|
+
#wakeGate = new TaskWakeGate();
|
|
160
174
|
/**
|
|
161
175
|
* 审批监督簇(挂起登记 + 在飞闸 + `approve` + 超时恢复)—— 实现见 approval-supervisor.ts。
|
|
162
176
|
* 依赖(store / ownerId / approvalTimeoutMs / dispatch 回调)在构造函数末尾注入。
|
|
@@ -200,6 +214,13 @@ export class AsyncRunner {
|
|
|
200
214
|
* 纯记账件,独立于 store / 槽位 —— 所以在 855 行的类里单独抽一个文件。
|
|
201
215
|
*/
|
|
202
216
|
#streams;
|
|
217
|
+
/**
|
|
218
|
+
* 跨进程流兜底的**单例扫描器**(2026-11-09 深评 P2-1):此前 `streamTask` 给每个订阅
|
|
219
|
+
* 各建一个 5s 定时器去 `store.get`,N 个并发连接就是 N 个定时器 + N/5 QPS,无合并无上限 ——
|
|
220
|
+
* 而跨进程收口是低频事件,不值得按连接付这份成本。现在一拍里每个**任务**只读一次 store,
|
|
221
|
+
* 再把结论分发到该任务的各个订阅(见 stream-janitor.ts)。
|
|
222
|
+
*/
|
|
223
|
+
#streamJanitors;
|
|
203
224
|
/**
|
|
204
225
|
* **同进程内的幂等键认领表**(idempotencyKey → 在飞记录)。
|
|
205
226
|
*
|
|
@@ -267,6 +288,15 @@ export class AsyncRunner {
|
|
|
267
288
|
throw new Error(`approvalTimeoutMs 必须为 ≥ 0 的有限数(${zeroClauseOf('AsyncRunner.approvalTimeoutMs')}),收到 ${opts.approvalTimeoutMs}`);
|
|
268
289
|
}
|
|
269
290
|
this.ownerId = formatOwnerId(process.pid, this.#host, randomUUID().slice(0, 8));
|
|
291
|
+
// 跨进程流兜底(2026-11-09 深评 P2-1):runner 级一个扫描器,一拍里每个任务只读一次 store。
|
|
292
|
+
// 用 `this.poll`(而不是裸 `store.get`)是为了保留惰性挂起闸 —— 审批超时 / 到期唤醒的
|
|
293
|
+
// 重判入口本来就搭在这条轮询上,换成裸 get 会让那两条兜底少一个触发点。
|
|
294
|
+
// 定时器**有订阅才存在**(见 stream-janitor.ts),空闲时零成本。
|
|
295
|
+
this.#streamJanitors = createStreamJanitorGroup({
|
|
296
|
+
poll: (taskId) => Promise.resolve(this.poll(taskId)),
|
|
297
|
+
ownerId: this.ownerId,
|
|
298
|
+
pollMs: STREAM_CROSS_PROCESS_POLL_MS,
|
|
299
|
+
});
|
|
270
300
|
// 审批监督簇在 ownerId 就位后构造(它要用 store 与 ownerId)。`dispatch` 用**回调**
|
|
271
301
|
// 注入而不是 import:派发入口 `#dispatch` 仍归本类(停机闸 + 唯一派发点 + 一次性告警),
|
|
272
302
|
// 依赖保持单向(审批 → 派发),也避免两个模块互相 import。
|
|
@@ -274,6 +304,7 @@ export class AsyncRunner {
|
|
|
274
304
|
store: this.store,
|
|
275
305
|
ownerId: this.ownerId,
|
|
276
306
|
approvalTimeoutMs: this.approvalTimeoutMs,
|
|
307
|
+
wakeGate: this.#wakeGate,
|
|
277
308
|
dispatch: (rec) => this.#dispatch(rec),
|
|
278
309
|
cancelInflight: (taskId) => this.#cancelInflight.get(taskId),
|
|
279
310
|
});
|
|
@@ -284,6 +315,7 @@ export class AsyncRunner {
|
|
|
284
315
|
store: this.store,
|
|
285
316
|
ownerId: this.ownerId,
|
|
286
317
|
approvals: this.#approvals,
|
|
318
|
+
wakeGate: this.#wakeGate,
|
|
287
319
|
dispatch: (rec) => this.#dispatch(rec),
|
|
288
320
|
cancelInflight: (taskId) => this.#cancelInflight.get(taskId),
|
|
289
321
|
});
|
|
@@ -292,6 +324,7 @@ export class AsyncRunner {
|
|
|
292
324
|
ownerId: this.ownerId,
|
|
293
325
|
host: this.#host,
|
|
294
326
|
approvals: this.#approvals,
|
|
327
|
+
wakeGate: this.#wakeGate,
|
|
295
328
|
dispatch: (rec) => this.#dispatch(rec),
|
|
296
329
|
isDraining: () => this.#drain.isDraining,
|
|
297
330
|
cancelInflight: (taskId) => this.#cancelInflight.get(taskId),
|
|
@@ -427,17 +460,54 @@ export class AsyncRunner {
|
|
|
427
460
|
const rec = await this.poll(taskId);
|
|
428
461
|
if (rec)
|
|
429
462
|
listener({ type: 'end', record: rec });
|
|
463
|
+
// 终态但记录被清(TTL / 外部删除)也不能静默:客户端需要一个收口帧。
|
|
464
|
+
else
|
|
465
|
+
listener({ type: 'closed', reason: 'not-in-this-process' });
|
|
430
466
|
return noop;
|
|
431
467
|
}
|
|
432
468
|
// 实时订阅:`onDone` 里补发 `end` 帧 —— 终态是流的一部分
|
|
433
469
|
//(没有它,这条 SSE 会在任务跑完之后一直挂着,客户端以为它还在跑)
|
|
434
|
-
|
|
435
|
-
// poll 可能是异步 store ⇒ 用 then
|
|
470
|
+
const off = this.#streams.subscribe(taskId, (item) => listener({ type: 'trace', index: item.index, event: item.event }), () => {
|
|
471
|
+
// poll 可能是异步 store ⇒ 用 then 补发(此刻任务已终态、记录已落库)。
|
|
472
|
+
// ⚠️ 必须有 rejection 处理(2026-11-07 深评):`poll` 在 Redis 抖动时 reject,
|
|
473
|
+
// 没有第二个参数的 `.then` 会产生 unhandledRejection ⇒ Node ≥15 终止宿主进程。
|
|
436
474
|
void Promise.resolve(this.poll(taskId)).then((rec) => {
|
|
437
|
-
|
|
438
|
-
|
|
475
|
+
try {
|
|
476
|
+
if (rec)
|
|
477
|
+
listener({ type: 'end', record: rec });
|
|
478
|
+
// 记录被清(TTL / 外部删除)时也不能一帧都不发:`end` 是唯一会
|
|
479
|
+
// closeStream 的帧,不发就是永远只有心跳。
|
|
480
|
+
else
|
|
481
|
+
listener({ type: 'closed', reason: 'not-in-this-process' });
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
/* listener 自己的错由宿主记账,不得再变成 unhandled */
|
|
485
|
+
}
|
|
486
|
+
}, () => {
|
|
487
|
+
try {
|
|
488
|
+
listener({ type: 'closed', reason: 'not-in-this-process' });
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
/* 同上 */
|
|
492
|
+
}
|
|
439
493
|
});
|
|
440
494
|
});
|
|
495
|
+
// 跨进程收口(2026-11-07 深评 F5;2026-11-09 P2-1 提到 runner 级):登记到单例扫描器,
|
|
496
|
+
// 本流关闭时注销。`pollMs <= 0` = 这条流不参与兜底(真正的时间间隔只有一个来源,
|
|
497
|
+
// 即 runner 上那个扫描器 —— 单连接不再各建定时器)。
|
|
498
|
+
let janitorCleanup = noop;
|
|
499
|
+
const cleanup = () => {
|
|
500
|
+
off();
|
|
501
|
+
janitorCleanup();
|
|
502
|
+
};
|
|
503
|
+
if ((opts.pollMs ?? STREAM_CROSS_PROCESS_POLL_MS) > 0) {
|
|
504
|
+
janitorCleanup = this.#streamJanitors.add(taskId, {
|
|
505
|
+
isTerminal: (rec) => isTerminalTask(rec),
|
|
506
|
+
onTerminal: () => this.#streams.markDone(taskId),
|
|
507
|
+
onClosed: () => listener({ type: 'closed', reason: 'not-in-this-process' }),
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
return cleanup;
|
|
441
511
|
}
|
|
442
512
|
const rec = await this.poll(taskId);
|
|
443
513
|
if (!rec)
|
|
@@ -514,11 +584,13 @@ export class AsyncRunner {
|
|
|
514
584
|
if (advance !== undefined || isThenable(found)) {
|
|
515
585
|
// 异步路径:先入闸(后续推进路径会看到它并等待/跳过),等在飞推进收口后**重读**再判
|
|
516
586
|
const run = (async () => {
|
|
517
|
-
if (advance !== undefined)
|
|
518
|
-
await advance.catch(() => undefined);
|
|
519
587
|
// 意图先立(等闸窗口里就要可见):等槽位那趟 #executeInner 可能正好放行 ——
|
|
520
|
-
// 意图在场它才不会起跑(见 #executeInner
|
|
588
|
+
// 意图在场它才不会起跑(见 #executeInner 的取消意图闸)。
|
|
589
|
+
// ⚠️ 必须在 `await advance` **之前**登记:此前写在等待之后,与注释相反,
|
|
590
|
+
// 让「等 approve 收口」的窗口里新起的 #execute 看不到意图(2026-11-07 深评)。
|
|
521
591
|
this.#cancels.add(taskId);
|
|
592
|
+
if (advance !== undefined)
|
|
593
|
+
await advance.catch(() => undefined);
|
|
522
594
|
try {
|
|
523
595
|
const rec = advance !== undefined ? await this.store.get(taskId) : await found;
|
|
524
596
|
return await this.#cancel(rec, taskId);
|
|
@@ -539,7 +611,10 @@ export class AsyncRunner {
|
|
|
539
611
|
}
|
|
540
612
|
/** 三条推进路径(审批 / 事件投递 / 到期唤醒)在该任务上的在飞 Promise(无则 undefined) */
|
|
541
613
|
#advanceInflight(taskId) {
|
|
542
|
-
|
|
614
|
+
// 跨路径闸的 tail 覆盖**全部**已登记的唤醒(含排队中的);先取它,再退回各监督件的
|
|
615
|
+
// 单件读数 —— tail 是 2026-11-07 深评 P1 的修法,单件读数只作防御性兜底。
|
|
616
|
+
return (this.#wakeGate.tail(taskId) ??
|
|
617
|
+
this.#approvals.inflightFor(taskId) ??
|
|
543
618
|
this.#signals.inflightFor(taskId) ??
|
|
544
619
|
this.#scanner.inflightWakeFor(taskId));
|
|
545
620
|
}
|
|
@@ -602,7 +677,10 @@ export class AsyncRunner {
|
|
|
602
677
|
this.#reportPersistError(rec, error, 'outcome');
|
|
603
678
|
saved = undefined;
|
|
604
679
|
}
|
|
605
|
-
|
|
680
|
+
// 只有「确实有一趟活着的 #execute 会消费这个标记」才登记;`queued` 但本进程没有
|
|
681
|
+
// 对应执行趟(resumePending 认领后被停机拒派 / 上一进程崩溃留下的记录)时登记
|
|
682
|
+
// 会永远没人摘(2026-11-07 深评 F8)。
|
|
683
|
+
if (wasQueued && this.#executions.has(taskId))
|
|
606
684
|
this.#cancelSettled.add(taskId);
|
|
607
685
|
const finish = () => {
|
|
608
686
|
// 取消意图的终点(2026-10-13 ref-run-root-close):还有活着的 #execute 时留给它消费 —— 它靠这个意图
|
|
@@ -818,11 +896,18 @@ export class AsyncRunner {
|
|
|
818
896
|
/** 等到任务终态;超时抛错。`suspended` 不是终态 —— 继续等(人在路上 / 时刻未到)。 */
|
|
819
897
|
async awaitTask(taskId, opts = {}) {
|
|
820
898
|
const timeoutMs = opts.timeoutMs ?? 30_000;
|
|
899
|
+
// 坏值防线(2026-11-07 深评 F6):`timeoutMs: NaN` 会让 `deadline` 变 NaN、
|
|
900
|
+
// `left <= 0` 恒假 ⇒ 永不超时;`Math.min(intervalMs, left)` 再把 NaN 交给
|
|
901
|
+
// setTimeout(Node 钳到 1ms)⇒ 每毫秒读一次 store。本仓其它定时旋钮全都挡了,这条漏了。
|
|
902
|
+
assertTimerDelay(timeoutMs, 'AsyncRunner.awaitTask 的 timeoutMs');
|
|
821
903
|
// intervalMs 现在只是**兜底轮询**间隔,不是主路径:本进程把任务写到终态会主动唤醒
|
|
822
904
|
// (见 task-waiters.ts 的 notify)。默认从 5ms 放宽到 250ms —— 旧实现每 5ms 读一次 store,
|
|
823
905
|
// 等 30s 就是约 6000 次读(SQLite/Redis 下是 6000 次往返)。用异步 store 且终态由
|
|
824
906
|
// **他进程**写入时唤不醒,才靠这个间隔兜底。
|
|
825
907
|
const intervalMs = opts.intervalMs ?? 250;
|
|
908
|
+
if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
|
|
909
|
+
throw new TypeError(`AsyncRunner.awaitTask 的 intervalMs 必须是正的有限数(毫秒),收到 ${String(opts.intervalMs)}`);
|
|
910
|
+
}
|
|
826
911
|
const deadline = Date.now() + timeoutMs;
|
|
827
912
|
for (;;) {
|
|
828
913
|
// 走 poll 而不是裸 store.get:惰性审批超时的判定挂在那里(HITL)
|
|
@@ -1054,20 +1139,51 @@ export class AsyncRunner {
|
|
|
1054
1139
|
rec.usageTotal = addUsage(rec.usageTotal, segmentUsage);
|
|
1055
1140
|
rec.error = out.result.error;
|
|
1056
1141
|
if (out.result.stopReason === 'suspended' && out.result.suspendedMessages) {
|
|
1057
|
-
//
|
|
1058
|
-
//
|
|
1059
|
-
//
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1142
|
+
// 取消意图优先于挂起(2026-11-07 深评 P1-1):引擎侧的 abort 守卫已覆盖
|
|
1143
|
+
// 「signal 已中止」的出口;这里再兜住「结果已返回、cancel 在随后一拍到达」的
|
|
1144
|
+
// 窗口 —— 否则记录会落成 suspended,cancel 等不到终态而 409。
|
|
1145
|
+
if (this.#cancels.has(rec.taskId)) {
|
|
1146
|
+
rec.status = 'cancelled';
|
|
1147
|
+
rec.error ??= abortedError();
|
|
1148
|
+
rec.pendingApprovals = undefined;
|
|
1149
|
+
rec.suspendedSince = undefined;
|
|
1150
|
+
rec.suspendedReason = undefined;
|
|
1151
|
+
rec.wakeAt = undefined;
|
|
1152
|
+
rec.pendingEvents = undefined;
|
|
1153
|
+
this.#approvals.unmarkSuspended(rec.taskId);
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
// HITL 挂起:记录怎么改、事件簿记按 `eventsDelivered` 分档、以及「用户输入快照
|
|
1157
|
+
// 必须在 `rec.spec` 被覆盖**之前**取」那条顺序事实 —— 全在 `run-outcome.ts`。
|
|
1158
|
+
// 槽位照常释放(finally)、onFinished 不触发(#execute 的出口判断)、
|
|
1159
|
+
// finishedAt 不置(下面 finally 里按状态跳过):它不是终态。
|
|
1160
|
+
const snapshot = applySuspendedOutcome(rec, out.result, out.result.suspendedMessages, {
|
|
1161
|
+
isResume,
|
|
1162
|
+
hasSessionStore: this.sessionStore !== undefined,
|
|
1163
|
+
now: Date.now(),
|
|
1164
|
+
});
|
|
1165
|
+
// 顺序承重:快照先落地,`markSuspended` 再登记(后者读 status / suspendedReason /
|
|
1166
|
+
// wakeAt —— 三样都刚被上一行写好;挂起读数纪律①:与落库同一个分支登记)
|
|
1167
|
+
if (snapshot !== undefined)
|
|
1168
|
+
this.sessionInputs.set(rec.taskId, snapshot);
|
|
1169
|
+
this.#approvals.markSuspended(rec);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
else if (out.result.stopReason === 'suspended') {
|
|
1173
|
+
// 自定义 AppCallable 返回 suspended 却没给 suspendedMessages:内建引擎不会
|
|
1174
|
+
// 产生这种形状,但薄宿主/JS 宿主可达。按终态 failed 收口,别落成
|
|
1175
|
+
// 「suspended 但挂起痕迹全清」的僵尸记录(approve / resume 两条闸都判不到)。
|
|
1176
|
+
rec.status = 'failed';
|
|
1177
|
+
rec.error = {
|
|
1178
|
+
type: 'agent_error',
|
|
1179
|
+
message: 'AppCallable 返回了 stopReason=suspended,但没有 suspendedMessages —— 无法恢复,按失败收口',
|
|
1180
|
+
retryable: false,
|
|
1181
|
+
};
|
|
1182
|
+
rec.pendingApprovals = undefined;
|
|
1183
|
+
rec.suspendedSince = undefined;
|
|
1184
|
+
rec.suspendedReason = undefined;
|
|
1185
|
+
rec.wakeAt = undefined;
|
|
1186
|
+
this.#approvals.unmarkSuspended(rec.taskId);
|
|
1071
1187
|
}
|
|
1072
1188
|
else {
|
|
1073
1189
|
// 取消:机制与 `runTimeoutMs` 一字不差(同一条 abort signal),**意图**不同 ——
|
|
@@ -1128,6 +1244,10 @@ export class AsyncRunner {
|
|
|
1128
1244
|
}
|
|
1129
1245
|
}
|
|
1130
1246
|
catch (e) {
|
|
1247
|
+
// 认领处的 #runAborts 登记在 save('running') **之前**,而 save 可能同步抛 /
|
|
1248
|
+
// 异步 reject —— 那条路径跳过了内层 finally 的 delete ⇒ 句柄永久滞留。
|
|
1249
|
+
// 这里兜底摘除(幂等),避免失败次数把 Map 撑成无界(2026-11-07 深评 F7)。
|
|
1250
|
+
this.#runAborts.delete(rec.taskId);
|
|
1131
1251
|
rec.status = 'failed';
|
|
1132
1252
|
rec.error = classifyError(e);
|
|
1133
1253
|
rec.finishedAt = Date.now();
|