@actiondock/core 2.7.0 → 2.9.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 +77 -64
- package/dist/catalog/action-catalog.d.ts +56 -0
- package/dist/catalog/action-catalog.js +202 -0
- package/dist/catalog/discovery.d.ts +60 -0
- package/dist/catalog/discovery.js +119 -0
- package/dist/catalog/graph.d.ts +190 -0
- package/dist/catalog/graph.js +589 -0
- package/dist/catalog/index.d.ts +5 -5
- package/dist/catalog/index.js +5 -5
- package/dist/catalog/resolve-action.d.ts +43 -0
- package/dist/catalog/resolve-action.js +119 -0
- package/dist/catalog/resolve-playbook.d.ts +26 -0
- package/dist/catalog/resolve-playbook.js +114 -0
- package/dist/doctor/checks/project.js +27 -8
- package/dist/doctor/checks/registry.js +2 -2
- package/dist/doctor/checks/runtime.js +34 -1
- package/dist/errors.d.ts +106 -10
- package/dist/errors.js +105 -10
- package/dist/execution/service.d.ts +18 -16
- package/dist/execution/service.js +183 -252
- package/dist/execution/types.d.ts +25 -69
- package/dist/execution/types.js +2 -1
- package/dist/filter/intent.d.ts +8 -9
- package/dist/filter/intent.js +49 -10
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.js +4 -0
- package/dist/host/host.d.ts +74 -32
- package/dist/host/host.js +585 -336
- package/dist/host/routing.d.ts +15 -48
- package/dist/host/routing.js +90 -153
- package/dist/host/types.d.ts +50 -17
- package/dist/index.d.ts +21 -24
- package/dist/index.js +19 -23
- package/dist/input/advice.d.ts +5 -1
- package/dist/input/advice.js +163 -214
- package/dist/input/describe.d.ts +1 -1
- package/dist/input/flat-errors.d.ts +5 -6
- package/dist/input/flat-errors.js +6 -6
- package/dist/input/flat-materializer.js +42 -44
- package/dist/invocation/index.d.ts +2 -0
- package/dist/invocation/index.js +2 -0
- package/dist/invocation/policy.d.ts +157 -0
- package/dist/invocation/policy.js +231 -0
- package/dist/invocation/types.d.ts +172 -0
- package/dist/invocation/types.js +97 -0
- package/dist/ipc/diagnostic.d.ts +0 -12
- package/dist/ipc/diagnostic.js +0 -21
- package/dist/ipc/host.d.ts +4 -4
- package/dist/ipc/host.js +44 -47
- package/dist/ipc/index.d.ts +1 -1
- package/dist/ipc/index.js +1 -1
- package/dist/ipc/service.d.ts +40 -0
- package/dist/ipc/service.js +349 -0
- package/dist/ipc/types.d.ts +21 -15
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +1 -0
- package/dist/node/module-loader.d.ts +63 -0
- package/dist/node/module-loader.js +112 -0
- package/dist/package/index.d.ts +23 -0
- package/dist/package/index.js +18 -0
- package/dist/{app/app.d.ts → package/runtime.d.ts} +41 -20
- package/dist/{app/app.js → package/runtime.js} +140 -95
- package/dist/{app → package}/static-index.js +58 -41
- package/dist/package/storage-view.d.ts +13 -0
- package/dist/package/storage-view.js +22 -0
- package/dist/{app → package}/types.d.ts +76 -57
- package/dist/package/types.js +22 -0
- package/dist/platform/index.d.ts +1 -1
- package/dist/platform/index.js +1 -1
- package/dist/platform/node.d.ts +48 -0
- package/dist/platform/node.js +96 -0
- package/dist/platform/types.d.ts +0 -9
- package/dist/process/control-arbiter.d.ts +2 -1
- package/dist/process/control-arbiter.js +1 -2
- package/dist/process/driver.d.ts +9 -8
- package/dist/process/driver.js +11 -22
- package/dist/process/index.d.ts +2 -0
- package/dist/process/index.js +2 -0
- package/dist/process/managed-record.d.ts +2 -2
- package/dist/process/process-driver.d.ts +109 -0
- package/dist/process/process-driver.js +650 -0
- package/dist/process/process-executor.d.ts +47 -0
- package/dist/process/process-executor.js +296 -0
- package/dist/process/process-manager.js +48 -26
- package/dist/process/run-executor.js +12 -12
- package/dist/profile/client-actions.js +2 -2
- package/dist/profile/client-health.js +2 -2
- package/dist/profile/client-query.js +5 -6
- package/dist/profile/client-transport.d.ts +19 -17
- package/dist/profile/client-transport.js +47 -80
- package/dist/profile/client.d.ts +2 -2
- package/dist/profile/client.js +1 -1
- package/dist/profile/index.d.ts +1 -0
- package/dist/profile/index.js +1 -0
- package/dist/profile/manager.d.ts +5 -0
- package/dist/profile/manager.js +41 -6
- package/dist/project/closure.js +2 -2
- package/dist/project/index.d.ts +8 -1
- package/dist/project/index.js +10 -1
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +1 -1
- package/dist/project/loader.js +26 -29
- package/dist/project/transactions.js +5 -10
- package/dist/project/types.d.ts +5 -0
- package/dist/registry/index.d.ts +3 -1
- package/dist/registry/index.js +2 -1
- package/dist/registry/lock.d.ts +28 -7
- package/dist/registry/lock.js +136 -16
- package/dist/registry/registry.d.ts +1 -5
- package/dist/registry/registry.js +1 -53
- package/dist/registry/scan.d.ts +0 -15
- package/dist/registry/scan.js +0 -59
- package/dist/registry/store.d.ts +42 -0
- package/dist/registry/store.js +43 -0
- package/dist/registry/types.d.ts +0 -24
- package/dist/runtime/action-collection.d.ts +1 -1
- package/dist/runtime/action-registry.d.ts +0 -4
- package/dist/runtime/action-registry.js +0 -7
- package/dist/runtime/context.js +5 -11
- package/dist/runtime/events.js +3 -6
- package/dist/runtime/identity.d.ts +22 -0
- package/dist/runtime/identity.js +10 -0
- package/dist/runtime/index.d.ts +1 -2
- package/dist/runtime/index.js +1 -2
- package/dist/runtime/module-loader.d.ts +2 -34
- package/dist/runtime/module-loader.js +1 -16
- package/dist/runtime/process.d.ts +6 -24
- package/dist/runtime/process.js +1 -118
- package/dist/runtime/run-persistence.js +2 -5
- package/dist/runtime/runner.d.ts +33 -93
- package/dist/runtime/runner.js +132 -364
- package/dist/runtime/standalone.d.ts +23 -8
- package/dist/runtime/standalone.js +100 -147
- package/dist/server/body.d.ts +16 -8
- package/dist/server/body.js +33 -16
- package/dist/server/dispatcher.d.ts +6 -10
- package/dist/server/dispatcher.js +24 -32
- package/dist/server/http-server.d.ts +87 -0
- package/dist/server/http-server.js +304 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.js +11 -0
- package/dist/server/mcp-endpoint.d.ts +61 -0
- package/dist/server/mcp-endpoint.js +118 -0
- package/dist/server/policy.d.ts +138 -0
- package/dist/server/policy.js +368 -0
- package/dist/server/routes/actions.js +114 -64
- package/dist/server/routes/common.d.ts +18 -33
- package/dist/server/routes/common.js +45 -65
- package/dist/server/routes/config.d.ts +1 -1
- package/dist/server/routes/config.js +55 -65
- package/dist/server/routes/doctor.d.ts +1 -1
- package/dist/server/routes/doctor.js +13 -21
- package/dist/server/routes/health.d.ts +1 -1
- package/dist/server/routes/health.js +4 -5
- package/dist/server/routes/info.d.ts +2 -2
- package/dist/server/routes/info.js +17 -24
- package/dist/server/routes/playbooks.js +23 -36
- package/dist/server/routes/runs.js +151 -141
- package/dist/server/routes/state.d.ts +1 -1
- package/dist/server/routes/state.js +64 -63
- package/dist/server/security.d.ts +11 -0
- package/dist/server/security.js +36 -19
- package/dist/server/server.d.ts +2 -2
- package/dist/server/server.js +224 -238
- package/dist/server/types.d.ts +68 -21
- package/dist/service/factory.d.ts +12 -0
- package/dist/service/factory.js +85 -0
- package/dist/{target → service}/index.d.ts +1 -1
- package/dist/{target → service}/index.js +1 -1
- package/dist/service/local.d.ts +27 -0
- package/dist/service/local.js +138 -0
- package/dist/{target → service}/remote-errors.d.ts +3 -6
- package/dist/service/remote-errors.js +38 -0
- package/dist/{target → service}/remote-polling.d.ts +2 -2
- package/dist/{target → service}/remote-polling.js +8 -8
- package/dist/service/remote.d.ts +34 -0
- package/dist/service/remote.js +634 -0
- package/dist/service/sse-stream.d.ts +24 -0
- package/dist/service/sse-stream.js +139 -0
- package/dist/service/types.d.ts +272 -0
- package/dist/service/types.js +32 -0
- package/dist/storage/data-dir-lock.js +10 -5
- package/dist/storage/driver.d.ts +2 -1
- package/dist/storage/driver.js +3 -52
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/lock-core.d.ts +2 -6
- package/dist/storage/lock-core.js +4 -17
- package/dist/storage/params.d.ts +2 -8
- package/dist/storage/params.js +2 -8
- package/dist/storage/sqlite-driver.d.ts +40 -0
- package/dist/storage/sqlite-driver.js +116 -0
- package/dist/storage/sqlite.js +21 -6
- package/dist/storage/types.d.ts +4 -4
- package/dist/storage/types.js +2 -2
- package/dist/utils/index.d.ts +53 -0
- package/dist/utils/index.js +99 -4
- package/dist/utils/process.d.ts +10 -0
- package/dist/utils/process.js +21 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +41 -3
- package/dist/app/index.d.ts +0 -2
- package/dist/app/index.js +0 -2
- package/dist/app/types.js +0 -1
- package/dist/catalog/action-index.d.ts +0 -9
- package/dist/catalog/action-index.js +0 -95
- package/dist/catalog/action-resolver.d.ts +0 -20
- package/dist/catalog/action-resolver.js +0 -95
- package/dist/catalog/location-registry.d.ts +0 -15
- package/dist/catalog/location-registry.js +0 -138
- package/dist/catalog/package-catalog.d.ts +0 -7
- package/dist/catalog/package-catalog.js +0 -77
- package/dist/catalog/types.d.ts +0 -68
- package/dist/catalog/types.js +0 -1
- package/dist/export/index.d.ts +0 -1
- package/dist/export/index.js +0 -1
- package/dist/export/templates.d.ts +0 -71
- package/dist/export/templates.js +0 -635
- package/dist/ipc/target.d.ts +0 -74
- package/dist/ipc/target.js +0 -365
- package/dist/platform/default.d.ts +0 -38
- package/dist/platform/default.js +0 -78
- package/dist/project/resolver.d.ts +0 -116
- package/dist/project/resolver.js +0 -376
- package/dist/registry/resolve.d.ts +0 -62
- package/dist/registry/resolve.js +0 -195
- package/dist/runtime/execution-manager.d.ts +0 -48
- package/dist/runtime/execution-manager.js +0 -66
- package/dist/runtime/package-runner-factory.d.ts +0 -82
- package/dist/runtime/package-runner-factory.js +0 -186
- package/dist/target/local.d.ts +0 -53
- package/dist/target/local.js +0 -349
- package/dist/target/remote-errors.js +0 -51
- package/dist/target/remote.d.ts +0 -86
- package/dist/target/remote.js +0 -818
- package/dist/target/target.d.ts +0 -6
- package/dist/target/target.js +0 -37
- package/dist/target/types.d.ts +0 -261
- package/dist/target/types.js +0 -35
- /package/dist/{app → package}/static-index.d.ts +0 -0
- /package/dist/{target → service}/sse-parser.d.ts +0 -0
- /package/dist/{target → service}/sse-parser.js +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { EXECUTION_FAILED } from "../../errors.js";
|
|
1
|
+
import { ACTION_FORBIDDEN, CAPABILITY_UNAVAILABLE, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, EXECUTION_FAILED, PACKAGE_FORBIDDEN, RUN_ALREADY_FINISHED, RUN_INTERRUPTED, RUN_NOT_FOUND, RUNS_CLEAR_ERROR, RUNS_LIST_ERROR, } from "../../errors.js";
|
|
3
2
|
import { isTerminalRunStatus } from "../../storage/types.js";
|
|
4
3
|
import { readJsonBody } from "../body.js";
|
|
5
|
-
import { getSubPath, jsonResponse } from "./common.js";
|
|
4
|
+
import { getSubPath, isActionAllowedByPolicy, isPackageAllowedByPolicy, jsonResponse } from "./common.js";
|
|
6
5
|
/**
|
|
7
6
|
* 处理历史运行记录查询、清理、详情及 SSE 流式日志接口。
|
|
8
7
|
*/
|
|
9
8
|
export async function handleRunsRoutes(ctx) {
|
|
10
|
-
const { req, url, pathname, corsHeaders, options,
|
|
9
|
+
const { req, url, pathname, corsHeaders, options, service, activePolicy: policy } = ctx;
|
|
11
10
|
const subpath = getSubPath(pathname);
|
|
12
11
|
// 1. Runs List: GET /api/v2/runs, GET /runs
|
|
13
12
|
if (subpath === "/runs" && req.method === "GET") {
|
|
@@ -16,69 +15,47 @@ export async function handleRunsRoutes(ctx) {
|
|
|
16
15
|
const actionId = url.searchParams.get("actionId") || undefined;
|
|
17
16
|
const packageId = url.searchParams.get("packageId") || undefined;
|
|
18
17
|
const intent = url.searchParams.get("intent") || undefined;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
// limit 边界防护:非法或超界值回退默认 50,并夹紧到 1 至 500 区间
|
|
19
|
+
const parsedLimit = parseInt(url.searchParams.get("limit") || "50", 10);
|
|
20
|
+
const limit = Number.isFinite(parsedLimit) && parsedLimit > 0
|
|
21
|
+
? Math.min(parsedLimit, 500)
|
|
22
|
+
: 50;
|
|
23
|
+
if (packageId && !isPackageAllowedByPolicy(packageId, policy)) {
|
|
24
24
|
return jsonResponse({
|
|
25
25
|
ok: false,
|
|
26
26
|
error: {
|
|
27
|
-
code:
|
|
27
|
+
code: PACKAGE_FORBIDDEN,
|
|
28
28
|
message: `Package '${packageId}' is not in the allowed package list`,
|
|
29
29
|
},
|
|
30
30
|
}, 403, corsHeaders);
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
})
|
|
40
|
-
for (const app of apps) {
|
|
41
|
-
if (!app)
|
|
42
|
-
continue;
|
|
43
|
-
if (packageId && app.packageId !== packageId)
|
|
44
|
-
continue;
|
|
45
|
-
if (options.packageAllowlist && options.packageAllowlist.length > 0) {
|
|
46
|
-
if (!options.packageAllowlist.includes(app.packageId)) {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (app.storage && typeof app.storage.listRuns === "function") {
|
|
51
|
-
try {
|
|
52
|
-
// 状态过滤下推到存储层 SQL,避免先分页后过滤导致某包前 N 条非目标状态时贡献 0 条
|
|
53
|
-
const records = app.storage.listRuns({ actionId, status, limit });
|
|
54
|
-
for (const r of records) {
|
|
55
|
-
if (!seenRunIds.has(r.id)) {
|
|
56
|
-
seenRunIds.add(r.id);
|
|
57
|
-
if (options.packageAllowlist &&
|
|
58
|
-
options.packageAllowlist.length > 0 &&
|
|
59
|
-
r.packageId &&
|
|
60
|
-
!options.packageAllowlist.includes(r.packageId)) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
allRuns.push(r);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
// 单包查询失败不阻断整体列表响应,但必须可观测
|
|
69
|
-
console.warn(`[actiondock] listRuns failed for package '${app.packageId}': ${err instanceof Error ? err.message : String(err)}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
32
|
+
if (actionId && !isActionAllowedByPolicy({ packageId, actionId }, policy)) {
|
|
33
|
+
return jsonResponse({
|
|
34
|
+
ok: false,
|
|
35
|
+
error: {
|
|
36
|
+
code: ACTION_FORBIDDEN,
|
|
37
|
+
message: `Action '${packageId ? `${packageId}/${actionId}` : actionId}' is not in the allowed action list`,
|
|
38
|
+
},
|
|
39
|
+
}, 403, corsHeaders);
|
|
72
40
|
}
|
|
73
|
-
allRuns
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
41
|
+
let allRuns = await service.runs.list({
|
|
42
|
+
actionId,
|
|
43
|
+
status,
|
|
44
|
+
packageId,
|
|
45
|
+
intent,
|
|
46
|
+
limit,
|
|
47
|
+
});
|
|
48
|
+
if (policy.packageAllowlist && policy.packageAllowlist.length > 0) {
|
|
49
|
+
allRuns = allRuns.filter((r) => isPackageAllowedByPolicy(r.packageId, policy));
|
|
50
|
+
}
|
|
51
|
+
if (policy.actionAllowlist && policy.actionAllowlist.length > 0) {
|
|
52
|
+
allRuns = allRuns.filter((r) => isActionAllowedByPolicy({ packageId: r.packageId, actionId: r.actionId }, policy));
|
|
53
|
+
}
|
|
54
|
+
const sliced = allRuns.slice(0, limit);
|
|
55
|
+
return jsonResponse({ ok: true, total: allRuns.length, items: sliced }, 200, corsHeaders);
|
|
79
56
|
}
|
|
80
57
|
catch (err) {
|
|
81
|
-
return jsonResponse({ ok: false, error: { code:
|
|
58
|
+
return jsonResponse({ ok: false, error: { code: RUNS_LIST_ERROR, message: err.message } }, 500, corsHeaders);
|
|
82
59
|
}
|
|
83
60
|
}
|
|
84
61
|
// 2. Runs Clear: POST /api/v2/runs/clear, DELETE /api/v2/runs, etc.
|
|
@@ -92,61 +69,48 @@ export async function handleRunsRoutes(ctx) {
|
|
|
92
69
|
const packageId = url.searchParams.get("packageId") || body.packageId || undefined;
|
|
93
70
|
const actionId = url.searchParams.get("actionId") || body.actionId || undefined;
|
|
94
71
|
const status = url.searchParams.get("status") || body.status || undefined;
|
|
95
|
-
if (packageId &&
|
|
96
|
-
options.packageAllowlist &&
|
|
97
|
-
options.packageAllowlist.length > 0 &&
|
|
98
|
-
!options.packageAllowlist.includes(packageId)) {
|
|
72
|
+
if (packageId && !isPackageAllowedByPolicy(packageId, policy)) {
|
|
99
73
|
return jsonResponse({
|
|
100
74
|
ok: false,
|
|
101
75
|
error: {
|
|
102
|
-
code:
|
|
76
|
+
code: PACKAGE_FORBIDDEN,
|
|
103
77
|
message: `Package '${packageId}' is not in the allowed package list`,
|
|
104
78
|
},
|
|
105
79
|
}, 403, corsHeaders);
|
|
106
80
|
}
|
|
107
81
|
let clearedCount = 0;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
: (() => {
|
|
111
|
-
const inner = target?.unwrap?.();
|
|
112
|
-
return inner && "listApps" in inner ? inner.listApps() : [inner].filter(Boolean);
|
|
113
|
-
})();
|
|
114
|
-
for (const app of apps) {
|
|
115
|
-
if (!app)
|
|
116
|
-
continue;
|
|
117
|
-
if (packageId && app.packageId !== packageId)
|
|
118
|
-
continue;
|
|
119
|
-
if (options.packageAllowlist && options.packageAllowlist.length > 0) {
|
|
120
|
-
if (!options.packageAllowlist.includes(app.packageId)) {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (app.storage && typeof app.storage.clearRuns === "function") {
|
|
125
|
-
clearedCount += app.storage.clearRuns({ actionId, status });
|
|
126
|
-
}
|
|
82
|
+
if (service.runs.clear) {
|
|
83
|
+
clearedCount = await service.runs.clear({ packageId, actionId, status });
|
|
127
84
|
}
|
|
128
85
|
return jsonResponse({ ok: true, clearedCount }, 200, corsHeaders);
|
|
129
86
|
}
|
|
130
87
|
catch (err) {
|
|
131
|
-
return jsonResponse({ ok: false, error: { code:
|
|
88
|
+
return jsonResponse({ ok: false, error: { code: RUNS_CLEAR_ERROR, message: err.message } }, 500, corsHeaders);
|
|
132
89
|
}
|
|
133
90
|
}
|
|
134
91
|
// 3. Run Events SSE: GET /api/v2/runs/:runId/events (and stream alias)
|
|
135
92
|
const runEventsMatch = subpath.match(/^\/runs\/([^/]+)\/(events|stream)$/);
|
|
136
93
|
if (runEventsMatch && req.method === "GET") {
|
|
137
94
|
const runId = decodeURIComponent(runEventsMatch[1]);
|
|
138
|
-
const run = await
|
|
95
|
+
const run = await service.runs.get(runId);
|
|
139
96
|
if (!run) {
|
|
140
|
-
return jsonResponse({ ok: false, error: { code:
|
|
97
|
+
return jsonResponse({ ok: false, error: { code: RUN_NOT_FOUND, message: `Run '${runId}' not found` } }, 404, corsHeaders);
|
|
98
|
+
}
|
|
99
|
+
if (!isPackageAllowedByPolicy(run.packageId, policy)) {
|
|
100
|
+
return jsonResponse({
|
|
101
|
+
ok: false,
|
|
102
|
+
error: {
|
|
103
|
+
code: PACKAGE_FORBIDDEN,
|
|
104
|
+
message: `Package '${run.packageId || "unknown"}' is not in the allowed package list`,
|
|
105
|
+
},
|
|
106
|
+
}, 403, corsHeaders);
|
|
141
107
|
}
|
|
142
|
-
if (
|
|
143
|
-
options.packageAllowlist.length > 0 &&
|
|
144
|
-
(!run.packageId || !options.packageAllowlist.includes(run.packageId))) {
|
|
108
|
+
if (!isActionAllowedByPolicy({ packageId: run.packageId, actionId: run.actionId }, policy)) {
|
|
145
109
|
return jsonResponse({
|
|
146
110
|
ok: false,
|
|
147
111
|
error: {
|
|
148
|
-
code:
|
|
149
|
-
message: `
|
|
112
|
+
code: ACTION_FORBIDDEN,
|
|
113
|
+
message: `Action '${run.packageId ? `${run.packageId}/${run.actionId}` : run.actionId}' is not in the allowed action list`,
|
|
150
114
|
},
|
|
151
115
|
}, 403, corsHeaders);
|
|
152
116
|
}
|
|
@@ -165,44 +129,62 @@ export async function handleRunsRoutes(ctx) {
|
|
|
165
129
|
afterCursor = trimmed;
|
|
166
130
|
}
|
|
167
131
|
}
|
|
168
|
-
|
|
132
|
+
if (!service.events) {
|
|
133
|
+
return jsonResponse({
|
|
134
|
+
ok: false,
|
|
135
|
+
error: {
|
|
136
|
+
code: CAPABILITY_UNAVAILABLE,
|
|
137
|
+
message: "EventsPort is not supported by this ActionDock service",
|
|
138
|
+
},
|
|
139
|
+
}, 501);
|
|
140
|
+
}
|
|
141
|
+
const eventStream = service.events.events(runId, { after: afterCursor, signal: req.signal });
|
|
169
142
|
const iterator = eventStream[Symbol.asyncIterator]();
|
|
170
|
-
|
|
143
|
+
const encoder = new TextEncoder();
|
|
144
|
+
const encodeEvent = (evt) => {
|
|
145
|
+
const eventType = evt.type || "message";
|
|
146
|
+
const idField = evt.eventId ? `id: ${evt.eventId}\n` : `id: ${evt.sequence}\n`;
|
|
147
|
+
return encoder.encode(`${idField}event: ${eventType}\ndata: ${JSON.stringify(evt)}\n\n`);
|
|
148
|
+
};
|
|
149
|
+
// 游标过期检测:仅等待首事件一小段时间(过期错误通常在拉取瞬间抛出);
|
|
150
|
+
// 超时后立即开流,保证响应头及时下发,不再等首事件到达
|
|
151
|
+
const CURSOR_PROBE_MS = 250;
|
|
171
152
|
let firstResult;
|
|
153
|
+
let cursorExpired = null;
|
|
154
|
+
const firstPull = iterator.next();
|
|
172
155
|
try {
|
|
173
|
-
firstResult = await
|
|
156
|
+
firstResult = await Promise.race([
|
|
157
|
+
firstPull,
|
|
158
|
+
new Promise((resolve) => setTimeout(() => resolve(undefined), CURSOR_PROBE_MS)),
|
|
159
|
+
]);
|
|
174
160
|
}
|
|
175
161
|
catch (err) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
162
|
+
cursorExpired = err;
|
|
163
|
+
}
|
|
164
|
+
if (cursorExpired?.code === EVENT_CURSOR_EXPIRED) {
|
|
165
|
+
return jsonResponse({
|
|
166
|
+
ok: false,
|
|
167
|
+
error: {
|
|
168
|
+
code: EVENT_CURSOR_EXPIRED,
|
|
169
|
+
message: cursorExpired.message || "Event cursor has expired",
|
|
170
|
+
details: cursorExpired.details,
|
|
171
|
+
},
|
|
172
|
+
}, 410, corsHeaders);
|
|
187
173
|
}
|
|
188
174
|
const stream = new ReadableStream({
|
|
189
175
|
async start(controller) {
|
|
190
|
-
const encoder = new TextEncoder();
|
|
191
176
|
let sawFinish = false;
|
|
192
|
-
let eventsCount = 0;
|
|
193
177
|
try {
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const evt =
|
|
178
|
+
// 先消费首个事件结果(探测期内未完成则继续等待)
|
|
179
|
+
const first = firstResult ?? (await firstPull);
|
|
180
|
+
if (!first.done) {
|
|
181
|
+
const evt = first.value;
|
|
198
182
|
if (evt.type === "finish") {
|
|
199
183
|
sawFinish = true;
|
|
200
184
|
}
|
|
201
|
-
|
|
202
|
-
const idField = evt.eventId ? `id: ${evt.eventId}\n` : `id: ${evt.sequence}\n`;
|
|
203
|
-
controller.enqueue(encoder.encode(`${idField}event: ${eventType}\ndata: ${JSON.stringify(evt)}\n\n`));
|
|
185
|
+
controller.enqueue(encodeEvent(evt));
|
|
204
186
|
// 若首条事件为背压终止事件,立即关闭通道
|
|
205
|
-
if (evt.type === "error" && evt.error?.code ===
|
|
187
|
+
if (evt.type === "error" && evt.error?.code === EVENT_BACKPRESSURE_LIMIT) {
|
|
206
188
|
controller.close();
|
|
207
189
|
return;
|
|
208
190
|
}
|
|
@@ -212,16 +194,13 @@ export async function handleRunsRoutes(ctx) {
|
|
|
212
194
|
const nextResult = await iterator.next();
|
|
213
195
|
if (nextResult.done)
|
|
214
196
|
break;
|
|
215
|
-
eventsCount++;
|
|
216
197
|
const evt = nextResult.value;
|
|
217
198
|
if (evt.type === "finish") {
|
|
218
199
|
sawFinish = true;
|
|
219
200
|
}
|
|
220
|
-
|
|
221
|
-
const idField = evt.eventId ? `id: ${evt.eventId}\n` : `id: ${evt.sequence}\n`;
|
|
222
|
-
controller.enqueue(encoder.encode(`${idField}event: ${eventType}\ndata: ${JSON.stringify(evt)}\n\n`));
|
|
201
|
+
controller.enqueue(encodeEvent(evt));
|
|
223
202
|
// 若收到背压截断终止事件,正常关闭流通道
|
|
224
|
-
if (evt.type === "error" && evt.error?.code ===
|
|
203
|
+
if (evt.type === "error" && evt.error?.code === EVENT_BACKPRESSURE_LIMIT) {
|
|
225
204
|
break;
|
|
226
205
|
}
|
|
227
206
|
}
|
|
@@ -236,7 +215,7 @@ export async function handleRunsRoutes(ctx) {
|
|
|
236
215
|
ok: false,
|
|
237
216
|
runId,
|
|
238
217
|
error: run.error || {
|
|
239
|
-
code: run.status === "interrupted" ?
|
|
218
|
+
code: run.status === "interrupted" ? RUN_INTERRUPTED : EXECUTION_FAILED,
|
|
240
219
|
message: `Run finished with status ${run.status}`,
|
|
241
220
|
},
|
|
242
221
|
},
|
|
@@ -244,8 +223,11 @@ export async function handleRunsRoutes(ctx) {
|
|
|
244
223
|
controller.enqueue(encoder.encode(`event: finish\ndata: ${JSON.stringify(finishEvt)}\n\n`));
|
|
245
224
|
}
|
|
246
225
|
}
|
|
247
|
-
catch {
|
|
248
|
-
//
|
|
226
|
+
catch (err) {
|
|
227
|
+
// 流中断不静默:保留诊断线索(客户端断连属正常路径,仅记录非预期异常)
|
|
228
|
+
if (!(err instanceof Error) || err.name !== "AbortError") {
|
|
229
|
+
console.warn(`[RunsRoute] SSE stream for run '${runId}' interrupted: ${err instanceof Error ? err.message : String(err)}`);
|
|
230
|
+
}
|
|
249
231
|
}
|
|
250
232
|
finally {
|
|
251
233
|
try {
|
|
@@ -254,7 +236,16 @@ export async function handleRunsRoutes(ctx) {
|
|
|
254
236
|
catch { }
|
|
255
237
|
}
|
|
256
238
|
},
|
|
257
|
-
cancel() {
|
|
239
|
+
cancel() {
|
|
240
|
+
// 客户端断开时释放底层事件迭代器,避免资源悬挂
|
|
241
|
+
try {
|
|
242
|
+
const ret = iterator.return?.();
|
|
243
|
+
if (ret && typeof ret.catch === "function") {
|
|
244
|
+
ret.catch(() => { });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch { }
|
|
248
|
+
},
|
|
258
249
|
});
|
|
259
250
|
return new Response(stream, {
|
|
260
251
|
status: 200,
|
|
@@ -270,43 +261,62 @@ export async function handleRunsRoutes(ctx) {
|
|
|
270
261
|
const runShowMatch = subpath.match(/^\/runs\/([^/]+)$/);
|
|
271
262
|
if (runShowMatch && req.method === "GET") {
|
|
272
263
|
const runId = decodeURIComponent(runShowMatch[1]);
|
|
273
|
-
const run = await
|
|
264
|
+
const run = await service.runs.get(runId);
|
|
274
265
|
if (!run) {
|
|
275
266
|
return jsonResponse({
|
|
276
267
|
ok: false,
|
|
277
268
|
error: {
|
|
278
|
-
code:
|
|
269
|
+
code: RUN_NOT_FOUND,
|
|
279
270
|
message: `Run '${runId}' not found`,
|
|
280
271
|
},
|
|
281
272
|
}, 404, corsHeaders);
|
|
282
273
|
}
|
|
283
|
-
if (
|
|
284
|
-
options.packageAllowlist.length > 0 &&
|
|
285
|
-
(!run.packageId || !options.packageAllowlist.includes(run.packageId))) {
|
|
274
|
+
if (!isPackageAllowedByPolicy(run.packageId, policy)) {
|
|
286
275
|
return jsonResponse({
|
|
287
276
|
ok: false,
|
|
288
277
|
error: {
|
|
289
|
-
code:
|
|
278
|
+
code: PACKAGE_FORBIDDEN,
|
|
290
279
|
message: `Package '${run.packageId || "unknown"}' is not in the allowed package list`,
|
|
291
280
|
},
|
|
292
281
|
}, 403, corsHeaders);
|
|
293
282
|
}
|
|
283
|
+
if (!isActionAllowedByPolicy({ packageId: run.packageId, actionId: run.actionId }, policy)) {
|
|
284
|
+
return jsonResponse({
|
|
285
|
+
ok: false,
|
|
286
|
+
error: {
|
|
287
|
+
code: ACTION_FORBIDDEN,
|
|
288
|
+
message: `Action '${run.packageId ? `${run.packageId}/${run.actionId}` : run.actionId}' is not in the allowed action list`,
|
|
289
|
+
},
|
|
290
|
+
}, 403, corsHeaders);
|
|
291
|
+
}
|
|
294
292
|
return jsonResponse(run, 200, corsHeaders);
|
|
295
293
|
}
|
|
296
294
|
// 5. Run Cancel: POST /api/v2/runs/:runId/cancel, POST /runs/:runId/cancel
|
|
297
295
|
const runCancelMatch = subpath.match(/^\/runs\/([^/]+)\/cancel$/);
|
|
298
296
|
if (runCancelMatch && req.method === "POST") {
|
|
299
297
|
const runId = decodeURIComponent(runCancelMatch[1]);
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
298
|
+
if ((policy.packageAllowlist && policy.packageAllowlist.length > 0) ||
|
|
299
|
+
(policy.actionAllowlist && policy.actionAllowlist.length > 0)) {
|
|
300
|
+
const run = await service.runs.get(runId);
|
|
301
|
+
if (run) {
|
|
302
|
+
if (!isPackageAllowedByPolicy(run.packageId, policy)) {
|
|
303
|
+
return jsonResponse({
|
|
304
|
+
ok: false,
|
|
305
|
+
error: {
|
|
306
|
+
code: PACKAGE_FORBIDDEN,
|
|
307
|
+
message: `Package '${run.packageId || "unknown"}' is not in the allowed package list`,
|
|
308
|
+
},
|
|
309
|
+
}, 403, corsHeaders);
|
|
310
|
+
}
|
|
311
|
+
if (!isActionAllowedByPolicy({ packageId: run.packageId, actionId: run.actionId }, policy)) {
|
|
312
|
+
return jsonResponse({
|
|
313
|
+
ok: false,
|
|
314
|
+
error: {
|
|
315
|
+
code: ACTION_FORBIDDEN,
|
|
316
|
+
message: `Action '${run.packageId ? `${run.packageId}/${run.actionId}` : run.actionId}' is not in the allowed action list`,
|
|
317
|
+
},
|
|
318
|
+
}, 403, corsHeaders);
|
|
319
|
+
}
|
|
310
320
|
}
|
|
311
321
|
}
|
|
312
322
|
let body = {};
|
|
@@ -315,12 +325,12 @@ export async function handleRunsRoutes(ctx) {
|
|
|
315
325
|
}
|
|
316
326
|
catch { }
|
|
317
327
|
const reason = body?.reason || "Cancelled by client request";
|
|
318
|
-
const cancelResult = await
|
|
328
|
+
const cancelResult = await service.runs.cancel(runId, reason);
|
|
319
329
|
if (cancelResult.outcome === "not_found") {
|
|
320
330
|
return jsonResponse({
|
|
321
331
|
ok: false,
|
|
322
332
|
error: {
|
|
323
|
-
code:
|
|
333
|
+
code: RUN_NOT_FOUND,
|
|
324
334
|
message: `Run '${runId}' not found`,
|
|
325
335
|
},
|
|
326
336
|
}, 404, corsHeaders);
|
|
@@ -329,7 +339,7 @@ export async function handleRunsRoutes(ctx) {
|
|
|
329
339
|
return jsonResponse({
|
|
330
340
|
ok: false,
|
|
331
341
|
error: {
|
|
332
|
-
code:
|
|
342
|
+
code: RUN_ALREADY_FINISHED,
|
|
333
343
|
message: `Run '${runId}' has already finished with status '${cancelResult.status}'`,
|
|
334
344
|
status: cancelResult.status,
|
|
335
345
|
},
|