@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.
Files changed (243) hide show
  1. package/README.md +77 -64
  2. package/dist/catalog/action-catalog.d.ts +56 -0
  3. package/dist/catalog/action-catalog.js +202 -0
  4. package/dist/catalog/discovery.d.ts +60 -0
  5. package/dist/catalog/discovery.js +119 -0
  6. package/dist/catalog/graph.d.ts +190 -0
  7. package/dist/catalog/graph.js +589 -0
  8. package/dist/catalog/index.d.ts +5 -5
  9. package/dist/catalog/index.js +5 -5
  10. package/dist/catalog/resolve-action.d.ts +43 -0
  11. package/dist/catalog/resolve-action.js +119 -0
  12. package/dist/catalog/resolve-playbook.d.ts +26 -0
  13. package/dist/catalog/resolve-playbook.js +114 -0
  14. package/dist/doctor/checks/project.js +27 -8
  15. package/dist/doctor/checks/registry.js +2 -2
  16. package/dist/doctor/checks/runtime.js +34 -1
  17. package/dist/errors.d.ts +106 -10
  18. package/dist/errors.js +105 -10
  19. package/dist/execution/service.d.ts +18 -16
  20. package/dist/execution/service.js +183 -252
  21. package/dist/execution/types.d.ts +25 -69
  22. package/dist/execution/types.js +2 -1
  23. package/dist/filter/intent.d.ts +8 -9
  24. package/dist/filter/intent.js +49 -10
  25. package/dist/graph/index.d.ts +4 -0
  26. package/dist/graph/index.js +4 -0
  27. package/dist/host/host.d.ts +74 -32
  28. package/dist/host/host.js +585 -336
  29. package/dist/host/routing.d.ts +15 -48
  30. package/dist/host/routing.js +90 -153
  31. package/dist/host/types.d.ts +50 -17
  32. package/dist/index.d.ts +21 -24
  33. package/dist/index.js +19 -23
  34. package/dist/input/advice.d.ts +5 -1
  35. package/dist/input/advice.js +163 -214
  36. package/dist/input/describe.d.ts +1 -1
  37. package/dist/input/flat-errors.d.ts +5 -6
  38. package/dist/input/flat-errors.js +6 -6
  39. package/dist/input/flat-materializer.js +42 -44
  40. package/dist/invocation/index.d.ts +2 -0
  41. package/dist/invocation/index.js +2 -0
  42. package/dist/invocation/policy.d.ts +157 -0
  43. package/dist/invocation/policy.js +231 -0
  44. package/dist/invocation/types.d.ts +172 -0
  45. package/dist/invocation/types.js +97 -0
  46. package/dist/ipc/diagnostic.d.ts +0 -12
  47. package/dist/ipc/diagnostic.js +0 -21
  48. package/dist/ipc/host.d.ts +4 -4
  49. package/dist/ipc/host.js +44 -47
  50. package/dist/ipc/index.d.ts +1 -1
  51. package/dist/ipc/index.js +1 -1
  52. package/dist/ipc/service.d.ts +40 -0
  53. package/dist/ipc/service.js +349 -0
  54. package/dist/ipc/types.d.ts +21 -15
  55. package/dist/node/index.d.ts +1 -0
  56. package/dist/node/index.js +1 -0
  57. package/dist/node/module-loader.d.ts +63 -0
  58. package/dist/node/module-loader.js +112 -0
  59. package/dist/package/index.d.ts +23 -0
  60. package/dist/package/index.js +18 -0
  61. package/dist/{app/app.d.ts → package/runtime.d.ts} +41 -20
  62. package/dist/{app/app.js → package/runtime.js} +140 -95
  63. package/dist/{app → package}/static-index.js +58 -41
  64. package/dist/package/storage-view.d.ts +13 -0
  65. package/dist/package/storage-view.js +22 -0
  66. package/dist/{app → package}/types.d.ts +76 -57
  67. package/dist/package/types.js +22 -0
  68. package/dist/platform/index.d.ts +1 -1
  69. package/dist/platform/index.js +1 -1
  70. package/dist/platform/node.d.ts +48 -0
  71. package/dist/platform/node.js +96 -0
  72. package/dist/platform/types.d.ts +0 -9
  73. package/dist/process/control-arbiter.d.ts +2 -1
  74. package/dist/process/control-arbiter.js +1 -2
  75. package/dist/process/driver.d.ts +9 -8
  76. package/dist/process/driver.js +11 -22
  77. package/dist/process/index.d.ts +2 -0
  78. package/dist/process/index.js +2 -0
  79. package/dist/process/managed-record.d.ts +2 -2
  80. package/dist/process/process-driver.d.ts +109 -0
  81. package/dist/process/process-driver.js +650 -0
  82. package/dist/process/process-executor.d.ts +47 -0
  83. package/dist/process/process-executor.js +296 -0
  84. package/dist/process/process-manager.js +48 -26
  85. package/dist/process/run-executor.js +12 -12
  86. package/dist/profile/client-actions.js +2 -2
  87. package/dist/profile/client-health.js +2 -2
  88. package/dist/profile/client-query.js +5 -6
  89. package/dist/profile/client-transport.d.ts +19 -17
  90. package/dist/profile/client-transport.js +47 -80
  91. package/dist/profile/client.d.ts +2 -2
  92. package/dist/profile/client.js +1 -1
  93. package/dist/profile/index.d.ts +1 -0
  94. package/dist/profile/index.js +1 -0
  95. package/dist/profile/manager.d.ts +5 -0
  96. package/dist/profile/manager.js +41 -6
  97. package/dist/project/closure.js +2 -2
  98. package/dist/project/index.d.ts +8 -1
  99. package/dist/project/index.js +10 -1
  100. package/dist/project/init.js +2 -2
  101. package/dist/project/loader.d.ts +1 -1
  102. package/dist/project/loader.js +26 -29
  103. package/dist/project/transactions.js +5 -10
  104. package/dist/project/types.d.ts +5 -0
  105. package/dist/registry/index.d.ts +3 -1
  106. package/dist/registry/index.js +2 -1
  107. package/dist/registry/lock.d.ts +28 -7
  108. package/dist/registry/lock.js +136 -16
  109. package/dist/registry/registry.d.ts +1 -5
  110. package/dist/registry/registry.js +1 -53
  111. package/dist/registry/scan.d.ts +0 -15
  112. package/dist/registry/scan.js +0 -59
  113. package/dist/registry/store.d.ts +42 -0
  114. package/dist/registry/store.js +43 -0
  115. package/dist/registry/types.d.ts +0 -24
  116. package/dist/runtime/action-collection.d.ts +1 -1
  117. package/dist/runtime/action-registry.d.ts +0 -4
  118. package/dist/runtime/action-registry.js +0 -7
  119. package/dist/runtime/context.js +5 -11
  120. package/dist/runtime/events.js +3 -6
  121. package/dist/runtime/identity.d.ts +22 -0
  122. package/dist/runtime/identity.js +10 -0
  123. package/dist/runtime/index.d.ts +1 -2
  124. package/dist/runtime/index.js +1 -2
  125. package/dist/runtime/module-loader.d.ts +2 -34
  126. package/dist/runtime/module-loader.js +1 -16
  127. package/dist/runtime/process.d.ts +6 -24
  128. package/dist/runtime/process.js +1 -118
  129. package/dist/runtime/run-persistence.js +2 -5
  130. package/dist/runtime/runner.d.ts +33 -93
  131. package/dist/runtime/runner.js +132 -364
  132. package/dist/runtime/standalone.d.ts +23 -8
  133. package/dist/runtime/standalone.js +100 -147
  134. package/dist/server/body.d.ts +16 -8
  135. package/dist/server/body.js +33 -16
  136. package/dist/server/dispatcher.d.ts +6 -10
  137. package/dist/server/dispatcher.js +24 -32
  138. package/dist/server/http-server.d.ts +87 -0
  139. package/dist/server/http-server.js +304 -0
  140. package/dist/server/index.d.ts +12 -0
  141. package/dist/server/index.js +11 -0
  142. package/dist/server/mcp-endpoint.d.ts +61 -0
  143. package/dist/server/mcp-endpoint.js +118 -0
  144. package/dist/server/policy.d.ts +138 -0
  145. package/dist/server/policy.js +368 -0
  146. package/dist/server/routes/actions.js +114 -64
  147. package/dist/server/routes/common.d.ts +18 -33
  148. package/dist/server/routes/common.js +45 -65
  149. package/dist/server/routes/config.d.ts +1 -1
  150. package/dist/server/routes/config.js +55 -65
  151. package/dist/server/routes/doctor.d.ts +1 -1
  152. package/dist/server/routes/doctor.js +13 -21
  153. package/dist/server/routes/health.d.ts +1 -1
  154. package/dist/server/routes/health.js +4 -5
  155. package/dist/server/routes/info.d.ts +2 -2
  156. package/dist/server/routes/info.js +17 -24
  157. package/dist/server/routes/playbooks.js +23 -36
  158. package/dist/server/routes/runs.js +151 -141
  159. package/dist/server/routes/state.d.ts +1 -1
  160. package/dist/server/routes/state.js +64 -63
  161. package/dist/server/security.d.ts +11 -0
  162. package/dist/server/security.js +36 -19
  163. package/dist/server/server.d.ts +2 -2
  164. package/dist/server/server.js +224 -238
  165. package/dist/server/types.d.ts +68 -21
  166. package/dist/service/factory.d.ts +12 -0
  167. package/dist/service/factory.js +85 -0
  168. package/dist/{target → service}/index.d.ts +1 -1
  169. package/dist/{target → service}/index.js +1 -1
  170. package/dist/service/local.d.ts +27 -0
  171. package/dist/service/local.js +138 -0
  172. package/dist/{target → service}/remote-errors.d.ts +3 -6
  173. package/dist/service/remote-errors.js +38 -0
  174. package/dist/{target → service}/remote-polling.d.ts +2 -2
  175. package/dist/{target → service}/remote-polling.js +8 -8
  176. package/dist/service/remote.d.ts +34 -0
  177. package/dist/service/remote.js +634 -0
  178. package/dist/service/sse-stream.d.ts +24 -0
  179. package/dist/service/sse-stream.js +139 -0
  180. package/dist/service/types.d.ts +272 -0
  181. package/dist/service/types.js +32 -0
  182. package/dist/storage/data-dir-lock.js +10 -5
  183. package/dist/storage/driver.d.ts +2 -1
  184. package/dist/storage/driver.js +3 -52
  185. package/dist/storage/index.d.ts +1 -0
  186. package/dist/storage/index.js +1 -0
  187. package/dist/storage/lock-core.d.ts +2 -6
  188. package/dist/storage/lock-core.js +4 -17
  189. package/dist/storage/params.d.ts +2 -8
  190. package/dist/storage/params.js +2 -8
  191. package/dist/storage/sqlite-driver.d.ts +40 -0
  192. package/dist/storage/sqlite-driver.js +116 -0
  193. package/dist/storage/sqlite.js +21 -6
  194. package/dist/storage/types.d.ts +4 -4
  195. package/dist/storage/types.js +2 -2
  196. package/dist/utils/index.d.ts +53 -0
  197. package/dist/utils/index.js +99 -4
  198. package/dist/utils/process.d.ts +10 -0
  199. package/dist/utils/process.js +21 -0
  200. package/dist/version.d.ts +1 -1
  201. package/dist/version.js +1 -1
  202. package/package.json +41 -3
  203. package/dist/app/index.d.ts +0 -2
  204. package/dist/app/index.js +0 -2
  205. package/dist/app/types.js +0 -1
  206. package/dist/catalog/action-index.d.ts +0 -9
  207. package/dist/catalog/action-index.js +0 -95
  208. package/dist/catalog/action-resolver.d.ts +0 -20
  209. package/dist/catalog/action-resolver.js +0 -95
  210. package/dist/catalog/location-registry.d.ts +0 -15
  211. package/dist/catalog/location-registry.js +0 -138
  212. package/dist/catalog/package-catalog.d.ts +0 -7
  213. package/dist/catalog/package-catalog.js +0 -77
  214. package/dist/catalog/types.d.ts +0 -68
  215. package/dist/catalog/types.js +0 -1
  216. package/dist/export/index.d.ts +0 -1
  217. package/dist/export/index.js +0 -1
  218. package/dist/export/templates.d.ts +0 -71
  219. package/dist/export/templates.js +0 -635
  220. package/dist/ipc/target.d.ts +0 -74
  221. package/dist/ipc/target.js +0 -365
  222. package/dist/platform/default.d.ts +0 -38
  223. package/dist/platform/default.js +0 -78
  224. package/dist/project/resolver.d.ts +0 -116
  225. package/dist/project/resolver.js +0 -376
  226. package/dist/registry/resolve.d.ts +0 -62
  227. package/dist/registry/resolve.js +0 -195
  228. package/dist/runtime/execution-manager.d.ts +0 -48
  229. package/dist/runtime/execution-manager.js +0 -66
  230. package/dist/runtime/package-runner-factory.d.ts +0 -82
  231. package/dist/runtime/package-runner-factory.js +0 -186
  232. package/dist/target/local.d.ts +0 -53
  233. package/dist/target/local.js +0 -349
  234. package/dist/target/remote-errors.js +0 -51
  235. package/dist/target/remote.d.ts +0 -86
  236. package/dist/target/remote.js +0 -818
  237. package/dist/target/target.d.ts +0 -6
  238. package/dist/target/target.js +0 -37
  239. package/dist/target/types.d.ts +0 -261
  240. package/dist/target/types.js +0 -35
  241. /package/dist/{app → package}/static-index.d.ts +0 -0
  242. /package/dist/{target → service}/sse-parser.d.ts +0 -0
  243. /package/dist/{target → service}/sse-parser.js +0 -0
@@ -1,13 +1,12 @@
1
- import { filterByIntent } from "../../filter/index.js";
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, target, host } = ctx;
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
- const limit = parseInt(url.searchParams.get("limit") || "50", 10);
20
- if (packageId &&
21
- options.packageAllowlist &&
22
- options.packageAllowlist.length > 0 &&
23
- !options.packageAllowlist.includes(packageId)) {
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: "PACKAGE_FORBIDDEN",
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
- const allRuns = [];
33
- const seenRunIds = new Set();
34
- const apps = host
35
- ? host.listApps()
36
- : (() => {
37
- const inner = target?.unwrap?.();
38
- return inner && "listApps" in inner ? inner.listApps() : [inner].filter(Boolean);
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.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || ""));
74
- const filtered = intent
75
- ? filterByIntent(allRuns, intent, [(r) => r.id, (r) => r.actionId, (r) => r.status, (r) => r.packageId], false)
76
- : allRuns;
77
- const sliced = filtered.slice(0, limit);
78
- return jsonResponse({ ok: true, total: filtered.length, items: sliced }, 200, corsHeaders);
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: "RUNS_LIST_ERROR", message: err.message } }, 500, corsHeaders);
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: "PACKAGE_FORBIDDEN",
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
- const apps = host
109
- ? host.listApps()
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: "RUNS_CLEAR_ERROR", message: err.message } }, 500, corsHeaders);
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 target.getRun(runId);
95
+ const run = await service.runs.get(runId);
139
96
  if (!run) {
140
- return jsonResponse({ ok: false, error: { code: "RUN_NOT_FOUND", message: `Run '${runId}' not found` } }, 404, corsHeaders);
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 (options.packageAllowlist &&
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: "PACKAGE_FORBIDDEN",
149
- message: `Package '${run.packageId}' is not in the allowed package list`,
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
- const eventStream = target.events(runId, { after: afterCursor, signal: req.signal });
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
- // 检查游标是否在建流前已过期:拉取首个事件,若抛出 EVENT_CURSOR_EXPIRED 直接返回 410
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 iterator.next();
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
- if (err?.code === "EVENT_CURSOR_EXPIRED") {
177
- return jsonResponse({
178
- ok: false,
179
- error: {
180
- code: "EVENT_CURSOR_EXPIRED",
181
- message: err.message || "Event cursor has expired",
182
- details: err.details,
183
- },
184
- }, 410, corsHeaders);
185
- }
186
- throw err;
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
- if (!firstResult.done) {
196
- eventsCount++;
197
- const evt = firstResult.value;
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
- const eventType = evt.type || "message";
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 === "EVENT_BACKPRESSURE_LIMIT") {
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
- const eventType = evt.type || "message";
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 === "EVENT_BACKPRESSURE_LIMIT") {
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" ? "RUN_INTERRUPTED" : EXECUTION_FAILED,
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 target.getRun(runId);
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: "RUN_NOT_FOUND",
269
+ code: RUN_NOT_FOUND,
279
270
  message: `Run '${runId}' not found`,
280
271
  },
281
272
  }, 404, corsHeaders);
282
273
  }
283
- if (options.packageAllowlist &&
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: "PACKAGE_FORBIDDEN",
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 (options.packageAllowlist && options.packageAllowlist.length > 0) {
301
- const run = await target.getRun(runId);
302
- if (run && (!run.packageId || !options.packageAllowlist.includes(run.packageId))) {
303
- return jsonResponse({
304
- ok: false,
305
- error: {
306
- code: "PACKAGE_FORBIDDEN",
307
- message: `Package '${run.packageId}' is not in the allowed package list`,
308
- },
309
- }, 403, corsHeaders);
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 target.cancelRun(runId, reason);
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: "RUN_NOT_FOUND",
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: "RUN_ALREADY_FINISHED",
342
+ code: RUN_ALREADY_FINISHED,
333
343
  message: `Run '${runId}' has already finished with status '${cancelResult.status}'`,
334
344
  status: cancelResult.status,
335
345
  },
@@ -1,6 +1,6 @@
1
1
  import { type RouteContext } from "./common.js";
2
2
  /**
3
3
  * 处理状态键名列表、读取、写入、删除及清空接口。
4
- * 在未显式开启管理功能 (options.enableManagement !== true) 时返回 403 拒绝。
4
+ * 在未显式开启管理功能时返回 403 拒绝。
5
5
  */
6
6
  export declare function handleStateRoutes(ctx: RouteContext): Promise<Response | null>;