@actiondock/core 2.9.1 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/catalog/action-catalog.js +3 -33
- package/dist/catalog/discovery.d.ts +0 -3
- package/dist/catalog/discovery.js +4 -4
- package/dist/catalog/graph.d.ts +0 -4
- package/dist/catalog/graph.js +12 -15
- package/dist/{profile/client-actions.d.ts → client/actions.d.ts} +1 -1
- package/dist/{profile/client-actions.js → client/actions.js} +2 -8
- package/dist/{profile/client-config.d.ts → client/config.d.ts} +1 -1
- package/dist/{profile/client-config.js → client/config.js} +1 -1
- package/dist/client/dispatcher.d.ts +4 -0
- package/dist/client/dispatcher.js +21 -0
- package/dist/client/health.d.ts +38 -0
- package/dist/{profile/client-health.js → client/health.js} +9 -6
- package/dist/client/index.d.ts +16 -0
- package/dist/client/index.js +20 -0
- package/dist/{profile/client-playbooks.d.ts → client/playbooks.d.ts} +1 -1
- package/dist/{profile/client-playbooks.js → client/playbooks.js} +1 -1
- package/dist/{profile/client-runs.d.ts → client/runs.d.ts} +4 -2
- package/dist/{profile/client-runs.js → client/runs.js} +7 -1
- package/dist/{profile/client-state.d.ts → client/state.d.ts} +1 -1
- package/dist/{profile/client-state.js → client/state.js} +1 -1
- package/dist/{profile/client-transport.d.ts → client/transport.d.ts} +29 -10
- package/dist/{profile/client-transport.js → client/transport.js} +125 -35
- package/dist/doctor/checks/index.d.ts +1 -1
- package/dist/doctor/checks/index.js +1 -1
- package/dist/doctor/checks/project.js +24 -92
- package/dist/doctor/checks/registry.js +1 -1
- package/dist/doctor/checks/runtime.d.ts +0 -4
- package/dist/doctor/checks/runtime.js +0 -28
- package/dist/doctor/doctor.js +1 -2
- package/dist/errors.d.ts +85 -4
- package/dist/errors.js +114 -4
- package/dist/execution/service.d.ts +55 -37
- package/dist/execution/service.js +601 -212
- package/dist/execution/types.d.ts +24 -5
- package/dist/host/host.d.ts +42 -22
- package/dist/host/host.js +565 -150
- package/dist/host/routing.js +6 -6
- package/dist/host/types.d.ts +25 -13
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/input/advice.d.ts +0 -44
- package/dist/input/advice.js +7 -141
- package/dist/input/describe.d.ts +6 -0
- package/dist/input/describe.js +26 -1
- package/dist/input/flat-materializer.js +1 -1
- package/dist/input/flat-parser.js +1 -1
- package/dist/input/index.d.ts +0 -1
- package/dist/input/index.js +0 -1
- package/dist/input/input-resolver.js +2 -2
- package/dist/input/validation-mapper.d.ts +1 -1
- package/dist/invocation/policy.js +2 -0
- package/dist/ipc/diagnostic.d.ts +8 -21
- package/dist/ipc/diagnostic.js +24 -57
- package/dist/ipc/host.js +21 -7
- package/dist/ipc/service.d.ts +14 -0
- package/dist/ipc/service.js +132 -26
- package/dist/ipc/types.d.ts +0 -4
- package/dist/package/index.d.ts +4 -5
- package/dist/package/index.js +1 -2
- package/dist/package/runtime.d.ts +94 -23
- package/dist/package/runtime.js +628 -274
- package/dist/package/static-index.d.ts +3 -6
- package/dist/package/static-index.js +11 -40
- package/dist/package/types.d.ts +19 -4
- package/dist/platform/index.d.ts +2 -1
- package/dist/platform/index.js +2 -1
- package/dist/platform/loader-hook.d.ts +32 -0
- package/dist/platform/loader-hook.js +90 -0
- package/dist/{node → platform}/module-loader.d.ts +13 -0
- package/dist/{node → platform}/module-loader.js +51 -4
- package/dist/platform/node.d.ts +19 -8
- package/dist/platform/node.js +50 -20
- package/dist/platform/types.d.ts +7 -47
- package/dist/process/context-process.d.ts +3 -18
- package/dist/process/context-process.js +3 -44
- package/dist/process/control.d.ts +21 -0
- package/dist/process/control.js +34 -0
- package/dist/process/converters.d.ts +23 -0
- package/dist/process/converters.js +92 -0
- package/dist/process/cursor.js +2 -22
- package/dist/process/{diagnostics-sink.d.ts → diagnostics.d.ts} +0 -7
- package/dist/process/{diagnostics-sink.js → diagnostics.js} +6 -8
- package/dist/process/index.d.ts +1 -3
- package/dist/process/index.js +1 -3
- package/dist/process/input-dispatcher.d.ts +4 -13
- package/dist/process/input-dispatcher.js +48 -69
- package/dist/process/managed-record.d.ts +3 -31
- package/dist/process/metadata-store.d.ts +0 -41
- package/dist/process/metadata-store.js +2 -341
- package/dist/process/process-driver.js +2 -2
- package/dist/process/process-manager.d.ts +8 -50
- package/dist/process/process-manager.js +28 -417
- package/dist/process/quotas.d.ts +14 -0
- package/dist/process/quotas.js +29 -0
- package/dist/process/run-executor.js +0 -1
- package/dist/process/terminal-output-cache.d.ts +20 -34
- package/dist/process/terminal-output-cache.js +43 -64
- package/dist/profile/index.d.ts +1 -2
- package/dist/profile/index.js +1 -2
- package/dist/profile/manager.d.ts +2 -4
- package/dist/profile/manager.js +10 -40
- package/dist/profile/types.d.ts +1 -17
- package/dist/project/digest.d.ts +5 -1
- package/dist/project/digest.js +81 -219
- package/dist/project/index.d.ts +4 -5
- package/dist/project/index.js +4 -5
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +3 -3
- package/dist/project/loader.js +72 -50
- package/dist/project/lockfile.js +6 -5
- package/dist/project/manifest.js +5 -4
- package/dist/project/transactions.d.ts +4 -42
- package/dist/project/transactions.js +83 -138
- package/dist/project/types-generator.js +2 -1
- package/dist/project/types.d.ts +2 -0
- package/dist/registry/index.d.ts +0 -1
- package/dist/registry/index.js +0 -1
- package/dist/registry/lock.d.ts +2 -11
- package/dist/registry/lock.js +14 -183
- package/dist/registry/registry.d.ts +1 -6
- package/dist/registry/registry.js +5 -17
- package/dist/runtime/action-registry.d.ts +1 -5
- package/dist/runtime/action-registry.js +0 -12
- package/dist/runtime/context.js +2 -2
- package/dist/runtime/events.d.ts +11 -50
- package/dist/runtime/events.js +88 -368
- package/dist/runtime/index.d.ts +0 -4
- package/dist/runtime/index.js +0 -4
- package/dist/runtime/run-persistence.d.ts +2 -0
- package/dist/runtime/run-persistence.js +2 -1
- package/dist/runtime/standalone-commands.d.ts +18 -0
- package/dist/runtime/standalone-commands.js +466 -0
- package/dist/runtime/standalone-help.d.ts +20 -0
- package/dist/runtime/standalone-help.js +72 -0
- package/dist/runtime/standalone.d.ts +1 -44
- package/dist/runtime/standalone.js +31 -526
- package/dist/schema/validator.d.ts +2 -2
- package/dist/schema/validator.js +2 -9
- package/dist/server/index.d.ts +0 -2
- package/dist/server/index.js +0 -2
- package/dist/server/routes/actions.js +1 -1
- package/dist/server/routes/info.js +1 -1
- package/dist/server/routes/playbooks.js +1 -1
- package/dist/server/routes/runs.js +123 -16
- package/dist/server/routes/state.js +43 -18
- package/dist/server/security.d.ts +1 -1
- package/dist/server/security.js +2 -2
- package/dist/server/server.js +3 -7
- package/dist/service/factory.js +4 -3
- package/dist/service/index.d.ts +1 -1
- package/dist/service/index.js +1 -1
- package/dist/service/remote-errors.d.ts +1 -1
- package/dist/service/remote-errors.js +6 -6
- package/dist/service/remote-polling.d.ts +3 -0
- package/dist/service/remote-polling.js +10 -5
- package/dist/service/remote.d.ts +5 -1
- package/dist/service/remote.js +108 -17
- package/dist/service/sse-stream.js +2 -2
- package/dist/service/types.d.ts +22 -19
- package/dist/service/types.js +1 -24
- package/dist/storage/clock.d.ts +1 -1
- package/dist/storage/clock.js +1 -1
- package/dist/storage/data-dir-lock.d.ts +10 -22
- package/dist/storage/data-dir-lock.js +28 -56
- package/dist/storage/file-lock.d.ts +64 -0
- package/dist/storage/file-lock.js +447 -0
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.js +5 -4
- package/dist/storage/run-liveness.d.ts +48 -0
- package/dist/storage/run-liveness.js +44 -0
- package/dist/storage/sqlite-config.d.ts +16 -0
- package/dist/storage/sqlite-config.js +59 -0
- package/dist/storage/sqlite-driver.d.ts +3 -1
- package/dist/storage/sqlite-driver.js +14 -4
- package/dist/storage/sqlite-events.d.ts +13 -0
- package/dist/storage/sqlite-events.js +48 -0
- package/dist/storage/sqlite-runs.d.ts +107 -0
- package/dist/storage/sqlite-runs.js +622 -0
- package/dist/storage/sqlite-schema.d.ts +5 -0
- package/dist/storage/sqlite-schema.js +143 -0
- package/dist/storage/sqlite-state.d.ts +33 -0
- package/dist/storage/sqlite-state.js +270 -0
- package/dist/storage/sqlite.d.ts +30 -57
- package/dist/storage/sqlite.js +84 -741
- package/dist/storage/types.d.ts +35 -2
- package/dist/utils/index.d.ts +18 -14
- package/dist/utils/index.js +37 -29
- package/dist/utils/semver.d.ts +18 -0
- package/dist/utils/semver.js +19 -0
- package/dist/utils/warning.d.ts +1 -0
- package/dist/utils/warning.js +52 -0
- package/dist/{json/value-validator.d.ts → value-validator.d.ts} +9 -0
- package/dist/value-validator.js +330 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +10 -12
- package/dist/execution/index.d.ts +0 -2
- package/dist/execution/index.js +0 -2
- package/dist/filter/index.d.ts +0 -1
- package/dist/filter/index.js +0 -1
- package/dist/graph/index.d.ts +0 -4
- package/dist/graph/index.js +0 -4
- package/dist/input/metadata.d.ts +0 -99
- package/dist/input/metadata.js +0 -99
- package/dist/invocation/index.d.ts +0 -2
- package/dist/invocation/index.js +0 -2
- package/dist/ipc/index.d.ts +0 -4
- package/dist/ipc/index.js +0 -4
- package/dist/json/index.d.ts +0 -1
- package/dist/json/index.js +0 -1
- package/dist/json/value-validator.js +0 -330
- package/dist/node/index.d.ts +0 -1
- package/dist/node/index.js +0 -1
- package/dist/platform/node-fs.d.ts +0 -37
- package/dist/platform/node-fs.js +0 -81
- package/dist/process/control-arbiter.d.ts +0 -108
- package/dist/process/control-arbiter.js +0 -250
- package/dist/process/process-executor.d.ts +0 -47
- package/dist/process/process-executor.js +0 -296
- package/dist/profile/client-health.d.ts +0 -14
- package/dist/profile/client-query.d.ts +0 -30
- package/dist/profile/client-query.js +0 -69
- package/dist/profile/client.d.ts +0 -26
- package/dist/profile/client.js +0 -31
- package/dist/project/closure.d.ts +0 -25
- package/dist/project/closure.js +0 -74
- package/dist/registry/store.d.ts +0 -42
- package/dist/registry/store.js +0 -43
- package/dist/runtime/clock.d.ts +0 -7
- package/dist/runtime/clock.js +0 -7
- package/dist/runtime/module-loader.d.ts +0 -2
- package/dist/runtime/module-loader.js +0 -1
- package/dist/runtime/process.d.ts +0 -8
- package/dist/runtime/process.js +0 -1
- package/dist/runtime/runner.d.ts +0 -234
- package/dist/runtime/runner.js +0 -609
- package/dist/schema/index.d.ts +0 -1
- package/dist/schema/index.js +0 -1
- package/dist/server/dispatcher.d.ts +0 -25
- package/dist/server/dispatcher.js +0 -54
- package/dist/service/local.d.ts +0 -27
- package/dist/service/local.js +0 -141
- package/dist/storage/driver.d.ts +0 -10
- package/dist/storage/driver.js +0 -10
- package/dist/storage/lazy.d.ts +0 -8
- package/dist/storage/lazy.js +0 -45
- package/dist/storage/lock-core.d.ts +0 -158
- package/dist/storage/lock-core.js +0 -872
- package/dist/utils/net.d.ts +0 -10
- package/dist/utils/net.js +0 -16
- package/dist/utils/process.d.ts +0 -10
- package/dist/utils/process.js +0 -21
- /package/dist/{filter → utils}/intent.d.ts +0 -0
- /package/dist/{filter → utils}/intent.js +0 -0
package/dist/runtime/events.js
CHANGED
|
@@ -1,128 +1,61 @@
|
|
|
1
|
-
import { ActionDockError, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED } from "../errors.js";
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* - 接收实时推送事件并入队(超出背压上限时推送终止事件并切断通道)。
|
|
6
|
-
* - 维护 done 终止标记与唤醒回调,供生成器编排消费。
|
|
7
|
-
* - 暴露 inactive 注销钩子,供外部在通道切断时注销监听器。
|
|
2
|
+
* 订阅者通道:管理单个订阅者的事件缓冲队列与唤醒通知。
|
|
3
|
+
* 队列满时丢弃最旧事件并累计计数,不阻塞主流程,不切断通道。
|
|
8
4
|
*/
|
|
9
5
|
class SubscriberChannel {
|
|
10
|
-
/** 实时事件队列(含背压终止事件) */
|
|
11
6
|
liveQueue = [];
|
|
12
|
-
|
|
7
|
+
droppedCount = 0;
|
|
13
8
|
done = false;
|
|
14
|
-
/** 实时监听器(入队与背压终止逻辑入口) */
|
|
15
|
-
listener;
|
|
16
|
-
/** 通道被切断或订阅退出时的注销回调(由订阅方注入) */
|
|
17
|
-
onInactive = null;
|
|
18
|
-
/** 等待挂起期间的唤醒回调(由等待方注入) */
|
|
19
9
|
notify = null;
|
|
20
|
-
runId;
|
|
21
10
|
maxQueueSize;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
backpressureTerminated = false;
|
|
25
|
-
constructor(options) {
|
|
26
|
-
this.runId = options.runId;
|
|
27
|
-
this.maxQueueSize = options.maxQueueSize;
|
|
28
|
-
this.cursor = options.cursor;
|
|
29
|
-
this.allocateEventId = options.allocateEventId;
|
|
30
|
-
this.listener = (evt) => this.receive(evt);
|
|
11
|
+
constructor(maxQueueSize) {
|
|
12
|
+
this.maxQueueSize = maxQueueSize;
|
|
31
13
|
}
|
|
32
|
-
/**
|
|
33
|
-
* 接收单条实时事件:队列满时推送背压终止事件并切断通道。
|
|
34
|
-
*/
|
|
35
14
|
receive(evt) {
|
|
36
|
-
if (this.
|
|
15
|
+
if (this.done)
|
|
37
16
|
return;
|
|
38
|
-
}
|
|
39
17
|
if (this.liveQueue.length >= this.maxQueueSize) {
|
|
40
|
-
|
|
41
|
-
this.
|
|
42
|
-
this.onInactive?.();
|
|
43
|
-
const termEvt = {
|
|
44
|
-
runId: this.runId,
|
|
45
|
-
rootRunId: evt.rootRunId || this.runId,
|
|
46
|
-
sequence: evt.sequence + 1,
|
|
47
|
-
eventId: this.allocateEventId(),
|
|
48
|
-
timestamp: new Date().toISOString(),
|
|
49
|
-
type: "error",
|
|
50
|
-
error: {
|
|
51
|
-
code: EVENT_BACKPRESSURE_LIMIT,
|
|
52
|
-
message: `Event subscription queue exceeded limit of ${this.maxQueueSize} items due to slow subscriber`,
|
|
53
|
-
details: {
|
|
54
|
-
lastConfirmedCursor: this.cursor.lastConfirmedEventId ??
|
|
55
|
-
(this.cursor.lastConfirmedSequence >= 0 ? String(this.cursor.lastConfirmedSequence) : "0"),
|
|
56
|
-
lastConfirmedEventId: this.cursor.lastConfirmedEventId,
|
|
57
|
-
lastConfirmedSequence: this.cursor.lastConfirmedSequence,
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
this.liveQueue.length = 0;
|
|
62
|
-
this.liveQueue.push(termEvt);
|
|
63
|
-
if (this.notify) {
|
|
64
|
-
this.notify();
|
|
65
|
-
this.notify = null;
|
|
66
|
-
}
|
|
67
|
-
return;
|
|
18
|
+
this.liveQueue.shift();
|
|
19
|
+
this.droppedCount++;
|
|
68
20
|
}
|
|
69
21
|
this.liveQueue.push(evt);
|
|
70
22
|
if (this.notify) {
|
|
71
|
-
this.notify
|
|
23
|
+
const cb = this.notify;
|
|
72
24
|
this.notify = null;
|
|
73
|
-
|
|
74
|
-
if (evt.type === "finish") {
|
|
75
|
-
this.done = true;
|
|
25
|
+
cb();
|
|
76
26
|
}
|
|
77
27
|
}
|
|
78
|
-
/**
|
|
79
|
-
* 标记通道终止并唤醒挂起的等待方。
|
|
80
|
-
*/
|
|
81
28
|
terminate() {
|
|
82
29
|
this.done = true;
|
|
83
30
|
if (this.notify) {
|
|
84
|
-
this.notify
|
|
31
|
+
const cb = this.notify;
|
|
85
32
|
this.notify = null;
|
|
33
|
+
cb();
|
|
86
34
|
}
|
|
87
35
|
}
|
|
88
36
|
}
|
|
89
37
|
/**
|
|
90
|
-
*
|
|
91
|
-
*/
|
|
92
|
-
function estimateEventBytes(event) {
|
|
93
|
-
try {
|
|
94
|
-
return Buffer.byteLength(JSON.stringify(event), "utf8");
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
return 256;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* 进程内有界事件缓冲区实现。
|
|
38
|
+
* 进程内轻量发布订阅事件广播器。
|
|
102
39
|
* 遵循设计规范:
|
|
103
|
-
* -
|
|
40
|
+
* - 轻量发布订阅:保留订阅、退订、按 runId 过滤与事件分发。
|
|
41
|
+
* - 纯净广播:事件原样广播,绝不进行字节估算,绝不篡改用户执行结果。
|
|
42
|
+
* - 有界缓冲队列:简单条数上限(如 1000 条),队列满时丢弃最旧并计数,不切断通道。
|
|
104
43
|
* - 全局有界回收:限制最大运行数(默认 500),溢出时优先淘汰已完成运行。
|
|
105
|
-
* -
|
|
106
|
-
* - 严格生命周期清理:迭代退出或中断时彻底注销监听器与 AbortSignal 事件。
|
|
107
|
-
* - 慢订阅者背压保护:队列超出上限时推送终止事件并切断通道,主运行不受阻。
|
|
108
|
-
* - 游标断点续传:支持全局持久化 eventId 与序列号,过期游标返回 EVENT_CURSOR_EXPIRED。
|
|
44
|
+
* - 错误隔离保护:单个订阅者监听器异常不扩散,保障系统整体稳定性。
|
|
109
45
|
*/
|
|
110
46
|
export class InMemoryEventSink {
|
|
111
47
|
runs = new Map();
|
|
112
48
|
listeners = new Map();
|
|
113
|
-
wakeups = new Map();
|
|
114
49
|
evictions = new Map();
|
|
115
50
|
evictedRuns = new Set();
|
|
116
51
|
globalEventSeq = 0;
|
|
117
52
|
maxEventsPerRun;
|
|
118
|
-
maxBytesPerRun;
|
|
119
53
|
maxRuns;
|
|
120
54
|
maxSubscriberQueueSize;
|
|
121
55
|
constructor(options = {}) {
|
|
122
|
-
this.maxEventsPerRun = options.maxEventsPerRun ??
|
|
123
|
-
this.maxBytesPerRun = options.maxBytesPerRun ?? 1024 * 1024; // 1 MiB
|
|
56
|
+
this.maxEventsPerRun = options.maxEventsPerRun ?? 1000;
|
|
124
57
|
this.maxRuns = options.maxRuns ?? 500;
|
|
125
|
-
this.maxSubscriberQueueSize = options.maxSubscriberQueueSize ??
|
|
58
|
+
this.maxSubscriberQueueSize = options.maxSubscriberQueueSize ?? 1000;
|
|
126
59
|
}
|
|
127
60
|
emit(event) {
|
|
128
61
|
const runId = event.runId;
|
|
@@ -136,114 +69,33 @@ export class InMemoryEventSink {
|
|
|
136
69
|
};
|
|
137
70
|
}
|
|
138
71
|
if (!run) {
|
|
139
|
-
// 全局有界淘汰
|
|
140
72
|
if (this.runs.size >= this.maxRuns) {
|
|
141
73
|
this.evictOldestRun();
|
|
142
74
|
}
|
|
143
75
|
run = {
|
|
144
76
|
entries: [],
|
|
145
|
-
totalBytes: 0,
|
|
146
77
|
lastAccessedAt: now,
|
|
147
78
|
isTerminal: false,
|
|
148
|
-
|
|
149
|
-
earliestRetainedEventId: processedEvent.eventId,
|
|
79
|
+
droppedCount: 0,
|
|
150
80
|
};
|
|
151
81
|
this.runs.set(runId, run);
|
|
152
82
|
this.evictedRuns.delete(runId);
|
|
153
83
|
}
|
|
154
84
|
else {
|
|
155
85
|
run.lastAccessedAt = now;
|
|
156
|
-
if (run.earliestRetainedSequence === undefined) {
|
|
157
|
-
run.earliestRetainedSequence = processedEvent.sequence;
|
|
158
|
-
run.earliestRetainedEventId = processedEvent.eventId;
|
|
159
|
-
}
|
|
160
86
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (processedEvent.type === "log" && typeof processedEvent.message === "string") {
|
|
165
|
-
const keepChars = Math.max(0, Math.floor(this.maxBytesPerRun / 2));
|
|
166
|
-
processedEvent = {
|
|
167
|
-
...processedEvent,
|
|
168
|
-
message: `${processedEvent.message.slice(0, keepChars)}... [TRUNCATED]`,
|
|
169
|
-
data: undefined,
|
|
170
|
-
};
|
|
171
|
-
eventBytes = estimateEventBytes(processedEvent);
|
|
172
|
-
}
|
|
173
|
-
else if (processedEvent.type === "finish" && processedEvent.result) {
|
|
174
|
-
if (processedEvent.result.ok) {
|
|
175
|
-
processedEvent = {
|
|
176
|
-
...processedEvent,
|
|
177
|
-
result: {
|
|
178
|
-
ok: true,
|
|
179
|
-
runId: processedEvent.result.runId,
|
|
180
|
-
data: { _truncated: true, message: "Output exceeded maximum quota" },
|
|
181
|
-
},
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
const err = processedEvent.result.error;
|
|
186
|
-
const keepChars = Math.max(0, Math.min(200, Math.floor(this.maxBytesPerRun / 2)));
|
|
187
|
-
const truncatedMsg = typeof err?.message === "string"
|
|
188
|
-
? `${err.message.slice(0, keepChars)}... [TRUNCATED]`
|
|
189
|
-
: "Error exceeded maximum quota";
|
|
190
|
-
processedEvent = {
|
|
191
|
-
...processedEvent,
|
|
192
|
-
result: {
|
|
193
|
-
ok: false,
|
|
194
|
-
runId: processedEvent.result.runId,
|
|
195
|
-
error: {
|
|
196
|
-
code: err?.code || "EXECUTION_ERROR",
|
|
197
|
-
message: truncatedMsg,
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
eventBytes = estimateEventBytes(processedEvent);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
// 严格限制单运行缓存字节配额,超标事件不可入队,杜绝突破 maxBytesPerRun
|
|
206
|
-
if (eventBytes <= this.maxBytesPerRun) {
|
|
207
|
-
// 检查是否超出单运行条数或字节配额,按优先级淘汰
|
|
208
|
-
while (run.entries.length > 0 &&
|
|
209
|
-
(run.entries.length >= this.maxEventsPerRun ||
|
|
210
|
-
run.totalBytes + eventBytes > this.maxBytesPerRun)) {
|
|
211
|
-
// 优先淘汰非核心事件:log 或 progress
|
|
212
|
-
const nonEssentialIndex = run.entries.findIndex((e) => e.event.type === "log" || e.event.type === "progress");
|
|
213
|
-
if (nonEssentialIndex >= 0) {
|
|
214
|
-
const removed = run.entries.splice(nonEssentialIndex, 1)[0];
|
|
215
|
-
run.totalBytes -= removed.bytes;
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
// 无非核心事件时,淘汰最早的非 finish 事件
|
|
219
|
-
const nonTerminalIndex = run.entries.findIndex((e) => e.event.type !== "finish");
|
|
220
|
-
if (nonTerminalIndex >= 0) {
|
|
221
|
-
const removed = run.entries.splice(nonTerminalIndex, 1)[0];
|
|
222
|
-
run.totalBytes -= removed.bytes;
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
const removed = run.entries.shift();
|
|
226
|
-
run.totalBytes -= removed.bytes;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
if (run.entries.length > 0) {
|
|
231
|
-
run.earliestRetainedSequence = run.entries[0].event.sequence;
|
|
232
|
-
run.earliestRetainedEventId = run.entries[0].event.eventId;
|
|
233
|
-
}
|
|
234
|
-
if (run.totalBytes + eventBytes <= this.maxBytesPerRun) {
|
|
235
|
-
run.entries.push({ event: processedEvent, bytes: eventBytes });
|
|
236
|
-
run.totalBytes += eventBytes;
|
|
237
|
-
}
|
|
87
|
+
while (run.entries.length >= this.maxEventsPerRun) {
|
|
88
|
+
run.entries.shift();
|
|
89
|
+
run.droppedCount++;
|
|
238
90
|
}
|
|
239
|
-
|
|
91
|
+
run.entries.push(processedEvent);
|
|
240
92
|
if (processedEvent.type === "finish") {
|
|
241
93
|
run.isTerminal = true;
|
|
242
94
|
}
|
|
243
|
-
//
|
|
95
|
+
// 广播至当前活跃监听器,错误隔离保护
|
|
244
96
|
const subs = this.listeners.get(runId);
|
|
245
97
|
if (subs && subs.size > 0) {
|
|
246
|
-
for (const listener of subs) {
|
|
98
|
+
for (const listener of Array.from(subs)) {
|
|
247
99
|
try {
|
|
248
100
|
listener(processedEvent);
|
|
249
101
|
}
|
|
@@ -254,7 +106,10 @@ export class InMemoryEventSink {
|
|
|
254
106
|
}
|
|
255
107
|
}
|
|
256
108
|
async *subscribe(runId, options = {}) {
|
|
257
|
-
|
|
109
|
+
if (options.signal?.aborted || this.evictedRuns.has(runId)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
let afterSequence = -1;
|
|
258
113
|
let afterEventId;
|
|
259
114
|
if (options.after !== undefined) {
|
|
260
115
|
if (typeof options.after === "number") {
|
|
@@ -265,61 +120,37 @@ export class InMemoryEventSink {
|
|
|
265
120
|
if (/^-?\d+$/.test(trimmed)) {
|
|
266
121
|
afterSequence = parseInt(trimmed, 10);
|
|
267
122
|
}
|
|
268
|
-
|
|
123
|
+
else {
|
|
124
|
+
afterEventId = trimmed;
|
|
125
|
+
}
|
|
269
126
|
}
|
|
270
127
|
}
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
lastConfirmedSequence: effectiveAfter,
|
|
276
|
-
};
|
|
277
|
-
if (options.signal?.aborted || this.evictedRuns.has(runId)) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
// 检查游标是否在请求前已过期被清理
|
|
281
|
-
const run = this.runs.get(runId);
|
|
282
|
-
if (run && options.after !== undefined) {
|
|
283
|
-
this.assertCursorRetained(run, options.after, afterSequence);
|
|
284
|
-
}
|
|
285
|
-
const channel = new SubscriberChannel({
|
|
286
|
-
runId,
|
|
287
|
-
maxQueueSize: options.maxQueueSize ?? this.maxSubscriberQueueSize,
|
|
288
|
-
cursor,
|
|
289
|
-
allocateEventId: () => String(++this.globalEventSeq),
|
|
290
|
-
});
|
|
128
|
+
let lastYieldedSequence = afterSequence;
|
|
129
|
+
const maxQueueSize = options.maxQueueSize ?? this.maxSubscriberQueueSize;
|
|
130
|
+
const channel = new SubscriberChannel(maxQueueSize);
|
|
131
|
+
const listener = (evt) => channel.receive(evt);
|
|
291
132
|
let subs = this.listeners.get(runId);
|
|
292
133
|
if (!subs) {
|
|
293
134
|
subs = new Set();
|
|
294
135
|
this.listeners.set(runId, subs);
|
|
295
136
|
}
|
|
296
|
-
|
|
297
|
-
subs?.delete(channel.listener);
|
|
298
|
-
if (subs && subs.size === 0) {
|
|
299
|
-
this.listeners.delete(runId);
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
channel.onInactive = cleanupListener;
|
|
137
|
+
subs.add(listener);
|
|
303
138
|
let evictSet = this.evictions.get(runId);
|
|
304
139
|
if (!evictSet) {
|
|
305
140
|
evictSet = new Set();
|
|
306
141
|
this.evictions.set(runId, evictSet);
|
|
307
142
|
}
|
|
308
143
|
const onEvict = () => channel.terminate();
|
|
309
|
-
const onAbort = () => channel.terminate();
|
|
310
|
-
let currentWakeupCleanup = null;
|
|
311
|
-
// 先挂载实时监听器,防止历史回放与监听注册之间的竞态丢事件
|
|
312
|
-
subs.add(channel.listener);
|
|
313
144
|
evictSet.add(onEvict);
|
|
145
|
+
const onAbort = () => channel.terminate();
|
|
314
146
|
if (options.signal) {
|
|
315
147
|
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
316
148
|
}
|
|
317
149
|
const cleanup = () => {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
150
|
+
subs?.delete(listener);
|
|
151
|
+
if (subs && subs.size === 0) {
|
|
152
|
+
this.listeners.delete(runId);
|
|
321
153
|
}
|
|
322
|
-
cleanupListener();
|
|
323
154
|
evictSet?.delete(onEvict);
|
|
324
155
|
if (evictSet && evictSet.size === 0) {
|
|
325
156
|
this.evictions.delete(runId);
|
|
@@ -328,30 +159,42 @@ export class InMemoryEventSink {
|
|
|
328
159
|
options.signal.removeEventListener("abort", onAbort);
|
|
329
160
|
}
|
|
330
161
|
};
|
|
162
|
+
const isDeliverable = (evt) => {
|
|
163
|
+
if (afterEventId) {
|
|
164
|
+
if (evt.eventId === afterEventId) {
|
|
165
|
+
lastYieldedSequence = Math.max(lastYieldedSequence, evt.sequence);
|
|
166
|
+
afterEventId = undefined;
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return evt.sequence > lastYieldedSequence;
|
|
171
|
+
};
|
|
331
172
|
try {
|
|
332
|
-
// 回放历史快照
|
|
173
|
+
// 1. 回放历史快照
|
|
174
|
+
const run = this.runs.get(runId);
|
|
333
175
|
if (run) {
|
|
334
176
|
run.lastAccessedAt = Date.now();
|
|
335
|
-
const
|
|
336
|
-
for (const evt of
|
|
177
|
+
const snapshot = [...run.entries];
|
|
178
|
+
for (const evt of snapshot) {
|
|
337
179
|
if (options.signal?.aborted)
|
|
338
180
|
return;
|
|
339
|
-
if (evt
|
|
340
|
-
|
|
181
|
+
if (isDeliverable(evt)) {
|
|
182
|
+
lastYieldedSequence = evt.sequence;
|
|
341
183
|
yield evt;
|
|
342
|
-
if (
|
|
184
|
+
if (evt.type === "finish") {
|
|
343
185
|
channel.done = true;
|
|
344
186
|
return;
|
|
345
187
|
}
|
|
346
188
|
}
|
|
347
189
|
}
|
|
348
|
-
//
|
|
190
|
+
// 若历史记录已终态,排空实时队列后直接结束
|
|
349
191
|
if (run.isTerminal) {
|
|
350
192
|
while (channel.liveQueue.length > 0) {
|
|
351
|
-
const evt =
|
|
352
|
-
if (evt) {
|
|
193
|
+
const evt = channel.liveQueue.shift();
|
|
194
|
+
if (isDeliverable(evt)) {
|
|
195
|
+
lastYieldedSequence = evt.sequence;
|
|
353
196
|
yield evt;
|
|
354
|
-
if (
|
|
197
|
+
if (evt.type === "finish") {
|
|
355
198
|
channel.done = true;
|
|
356
199
|
return;
|
|
357
200
|
}
|
|
@@ -361,38 +204,38 @@ export class InMemoryEventSink {
|
|
|
361
204
|
return;
|
|
362
205
|
}
|
|
363
206
|
}
|
|
364
|
-
//
|
|
365
|
-
while (
|
|
207
|
+
// 2. 消费实时事件队列
|
|
208
|
+
while (!options.signal?.aborted) {
|
|
366
209
|
if (this.evictedRuns.has(runId)) {
|
|
367
|
-
channel.done = true;
|
|
368
210
|
break;
|
|
369
211
|
}
|
|
370
212
|
if (channel.liveQueue.length > 0) {
|
|
371
|
-
const evt =
|
|
372
|
-
if (evt) {
|
|
213
|
+
const evt = channel.liveQueue.shift();
|
|
214
|
+
if (isDeliverable(evt)) {
|
|
215
|
+
lastYieldedSequence = evt.sequence;
|
|
373
216
|
yield evt;
|
|
374
|
-
if (
|
|
217
|
+
if (evt.type === "finish") {
|
|
375
218
|
channel.done = true;
|
|
376
219
|
return;
|
|
377
220
|
}
|
|
378
221
|
}
|
|
379
222
|
}
|
|
380
223
|
else {
|
|
381
|
-
if (
|
|
382
|
-
channel.done = true;
|
|
224
|
+
if (channel.done) {
|
|
383
225
|
break;
|
|
384
226
|
}
|
|
385
|
-
await
|
|
386
|
-
|
|
227
|
+
await new Promise((resolve) => {
|
|
228
|
+
channel.notify = resolve;
|
|
387
229
|
});
|
|
388
230
|
}
|
|
389
231
|
}
|
|
390
|
-
//
|
|
232
|
+
// 3. 消费中止或完成瞬间积压的剩余事件
|
|
391
233
|
while (channel.liveQueue.length > 0) {
|
|
392
|
-
const evt =
|
|
393
|
-
if (evt) {
|
|
234
|
+
const evt = channel.liveQueue.shift();
|
|
235
|
+
if (isDeliverable(evt)) {
|
|
236
|
+
lastYieldedSequence = evt.sequence;
|
|
394
237
|
yield evt;
|
|
395
|
-
if (
|
|
238
|
+
if (evt.type === "finish") {
|
|
396
239
|
return;
|
|
397
240
|
}
|
|
398
241
|
}
|
|
@@ -402,108 +245,6 @@ export class InMemoryEventSink {
|
|
|
402
245
|
cleanup();
|
|
403
246
|
}
|
|
404
247
|
}
|
|
405
|
-
/**
|
|
406
|
-
* 断言请求游标仍处于保留窗口内(过期则抛出 EVENT_CURSOR_EXPIRED 错误)。
|
|
407
|
-
*/
|
|
408
|
-
assertCursorRetained(run, after, afterSequence) {
|
|
409
|
-
const earliestSeq = run.earliestRetainedSequence ?? 0;
|
|
410
|
-
if (afterSequence !== undefined && afterSequence >= 0 && afterSequence < earliestSeq - 1) {
|
|
411
|
-
const earliestCursor = run.earliestRetainedEventId ?? String(earliestSeq);
|
|
412
|
-
throw new ActionDockError(EVENT_CURSOR_EXPIRED, `Event cursor '${after}' has expired; earliest available cursor is '${earliestCursor}'`, {
|
|
413
|
-
cursor: after,
|
|
414
|
-
earliestCursor,
|
|
415
|
-
earliestRetainedCursor: earliestCursor,
|
|
416
|
-
earliestSequence: earliestSeq,
|
|
417
|
-
earliestEventId: run.earliestRetainedEventId,
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* 记录已交付事件并推进游标(已交付序号与最后确认游标)。
|
|
423
|
-
*/
|
|
424
|
-
trackDeliveredEvent(cursor, evt) {
|
|
425
|
-
cursor.lastYieldedSequence = evt.sequence;
|
|
426
|
-
cursor.lastConfirmedSequence = evt.sequence;
|
|
427
|
-
if (evt.eventId)
|
|
428
|
-
cursor.lastConfirmedEventId = evt.eventId;
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* 终止谓词:finish 事件或背压终止事件交付后订阅应立即结束。
|
|
432
|
-
*/
|
|
433
|
-
isTerminalDelivery(evt) {
|
|
434
|
-
return (evt.type === "finish" ||
|
|
435
|
-
(evt.type === "error" && evt.error?.code === EVENT_BACKPRESSURE_LIMIT));
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* 从实时队列取出下一条应交付的事件(跳过已交付序号),并同步推进游标。
|
|
439
|
-
*/
|
|
440
|
-
takeNextDeliverable(channel, cursor) {
|
|
441
|
-
const evt = channel.liveQueue.shift();
|
|
442
|
-
if (!evt)
|
|
443
|
-
return undefined;
|
|
444
|
-
if (evt.sequence > cursor.lastYieldedSequence || evt.type === "error") {
|
|
445
|
-
this.trackDeliveredEvent(cursor, evt);
|
|
446
|
-
return evt;
|
|
447
|
-
}
|
|
448
|
-
return undefined;
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* 挂起等待下一条实时事件、通道终止或运行淘汰唤醒(严格注销监听避免泄漏)。
|
|
452
|
-
*/
|
|
453
|
-
async waitForNextEvent(runId, channel, registerCleanup) {
|
|
454
|
-
await new Promise((resolve) => {
|
|
455
|
-
let runWakeups = this.wakeups.get(runId);
|
|
456
|
-
if (!runWakeups) {
|
|
457
|
-
runWakeups = new Set();
|
|
458
|
-
this.wakeups.set(runId, runWakeups);
|
|
459
|
-
}
|
|
460
|
-
const unregister = () => {
|
|
461
|
-
runWakeups?.delete(onWakeup);
|
|
462
|
-
if (runWakeups && runWakeups.size === 0) {
|
|
463
|
-
this.wakeups.delete(runId);
|
|
464
|
-
}
|
|
465
|
-
registerCleanup(null);
|
|
466
|
-
};
|
|
467
|
-
const onWakeup = () => {
|
|
468
|
-
channel.done = true;
|
|
469
|
-
unregister();
|
|
470
|
-
resolve();
|
|
471
|
-
};
|
|
472
|
-
runWakeups.add(onWakeup);
|
|
473
|
-
registerCleanup(unregister);
|
|
474
|
-
channel.notify = () => {
|
|
475
|
-
unregister();
|
|
476
|
-
resolve();
|
|
477
|
-
};
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* 手动修剪已终态运行中早于指定序列号的历史事件(用于模拟过期清理)。
|
|
482
|
-
*/
|
|
483
|
-
pruneEvents(runId, upToSequence) {
|
|
484
|
-
const run = this.runs.get(runId);
|
|
485
|
-
if (!run)
|
|
486
|
-
return;
|
|
487
|
-
const idx = run.entries.findIndex((e) => e.event.sequence > upToSequence);
|
|
488
|
-
if (idx > 0) {
|
|
489
|
-
const removed = run.entries.splice(0, idx);
|
|
490
|
-
for (const r of removed) {
|
|
491
|
-
run.totalBytes -= r.bytes;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
else if (idx === -1 && run.entries.length > 0) {
|
|
495
|
-
run.entries = [];
|
|
496
|
-
run.totalBytes = 0;
|
|
497
|
-
}
|
|
498
|
-
if (run.entries.length > 0) {
|
|
499
|
-
run.earliestRetainedSequence = run.entries[0].event.sequence;
|
|
500
|
-
run.earliestRetainedEventId = run.entries[0].event.eventId;
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
run.earliestRetainedSequence = upToSequence + 1;
|
|
504
|
-
run.earliestRetainedEventId = undefined;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
248
|
clear(runId) {
|
|
508
249
|
this.evictedRuns.add(runId);
|
|
509
250
|
if (this.evictedRuns.size > 2000) {
|
|
@@ -526,43 +267,20 @@ export class InMemoryEventSink {
|
|
|
526
267
|
}
|
|
527
268
|
}
|
|
528
269
|
}
|
|
529
|
-
const waiting = this.wakeups.get(runId);
|
|
530
|
-
if (waiting) {
|
|
531
|
-
const cbs = Array.from(waiting);
|
|
532
|
-
this.wakeups.delete(runId);
|
|
533
|
-
for (const wake of cbs) {
|
|
534
|
-
try {
|
|
535
|
-
wake();
|
|
536
|
-
}
|
|
537
|
-
catch {
|
|
538
|
-
// ignore
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
270
|
}
|
|
543
|
-
/**
|
|
544
|
-
* 获取指定运行的事件统计信息(调试与测试用)。
|
|
545
|
-
*/
|
|
546
271
|
getRunStats(runId) {
|
|
547
272
|
const run = this.runs.get(runId);
|
|
548
273
|
if (!run)
|
|
549
274
|
return undefined;
|
|
550
275
|
return {
|
|
551
276
|
count: run.entries.length,
|
|
552
|
-
bytes: run.totalBytes,
|
|
553
277
|
isTerminal: run.isTerminal,
|
|
278
|
+
droppedCount: run.droppedCount,
|
|
554
279
|
};
|
|
555
280
|
}
|
|
556
|
-
/**
|
|
557
|
-
* 获取全局缓存的运行数。
|
|
558
|
-
*/
|
|
559
281
|
getRunCount() {
|
|
560
282
|
return this.runs.size;
|
|
561
283
|
}
|
|
562
|
-
/**
|
|
563
|
-
* 淘汰最久未活跃的运行记录。
|
|
564
|
-
* 优先淘汰已终态运行;若无终态运行则淘汰最旧活动运行,严格保证 runs.size <= maxRuns。
|
|
565
|
-
*/
|
|
566
284
|
evictOldestRun() {
|
|
567
285
|
let candidateRunId = null;
|
|
568
286
|
let candidateAccess = Infinity;
|
|
@@ -586,16 +304,18 @@ export class InMemoryEventSink {
|
|
|
586
304
|
this.clear(candidateRunId);
|
|
587
305
|
}
|
|
588
306
|
}
|
|
589
|
-
/**
|
|
590
|
-
* 关闭事件接收器并唤醒所有活跃订阅者退出。
|
|
591
|
-
*/
|
|
592
307
|
close() {
|
|
593
|
-
for (const
|
|
594
|
-
for (const
|
|
595
|
-
|
|
308
|
+
for (const evictSet of this.evictions.values()) {
|
|
309
|
+
for (const onEvict of evictSet) {
|
|
310
|
+
try {
|
|
311
|
+
onEvict();
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
// ignore
|
|
315
|
+
}
|
|
596
316
|
}
|
|
597
317
|
}
|
|
598
|
-
this.
|
|
318
|
+
this.evictions.clear();
|
|
599
319
|
this.listeners.clear();
|
|
600
320
|
}
|
|
601
321
|
}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -3,10 +3,6 @@ export * from "./context.js";
|
|
|
3
3
|
export * from "./action-collection.js";
|
|
4
4
|
export * from "./action-registry.js";
|
|
5
5
|
export * from "./run-persistence.js";
|
|
6
|
-
export * from "./runner.js";
|
|
7
6
|
export * from "./standalone.js";
|
|
8
7
|
export * from "./env.js";
|
|
9
|
-
export * from "./clock.js";
|
|
10
|
-
export * from "./process.js";
|
|
11
8
|
export * from "./events.js";
|
|
12
|
-
export * from "./module-loader.js";
|
package/dist/runtime/index.js
CHANGED
|
@@ -3,10 +3,6 @@ export * from "./context.js";
|
|
|
3
3
|
export * from "./action-collection.js";
|
|
4
4
|
export * from "./action-registry.js";
|
|
5
5
|
export * from "./run-persistence.js";
|
|
6
|
-
export * from "./runner.js";
|
|
7
6
|
export * from "./standalone.js";
|
|
8
7
|
export * from "./env.js";
|
|
9
|
-
export * from "./clock.js";
|
|
10
|
-
export * from "./process.js";
|
|
11
8
|
export * from "./events.js";
|
|
12
|
-
export * from "./module-loader.js";
|