@actiondock/core 2.0.12 → 2.2.1

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 (192) hide show
  1. package/README.md +61 -30
  2. package/dist/app/app.d.ts +73 -0
  3. package/dist/app/app.js +700 -0
  4. package/dist/app/index.d.ts +2 -0
  5. package/dist/app/index.js +2 -0
  6. package/dist/app/types.d.ts +283 -0
  7. package/dist/app/types.js +1 -0
  8. package/dist/catalog/action-index.d.ts +1 -1
  9. package/dist/catalog/action-index.js +93 -0
  10. package/dist/catalog/action-resolver.d.ts +2 -2
  11. package/dist/catalog/action-resolver.js +95 -0
  12. package/dist/catalog/index.d.ts +5 -5
  13. package/dist/catalog/index.js +5 -0
  14. package/dist/catalog/location-registry.d.ts +5 -5
  15. package/dist/catalog/location-registry.js +138 -0
  16. package/dist/catalog/package-catalog.d.ts +2 -2
  17. package/dist/catalog/package-catalog.js +76 -0
  18. package/dist/catalog/types.d.ts +2 -2
  19. package/dist/catalog/types.js +1 -0
  20. package/dist/doctor/doctor.d.ts +2 -1
  21. package/dist/doctor/doctor.js +613 -0
  22. package/dist/doctor/index.d.ts +2 -2
  23. package/dist/doctor/index.js +2 -0
  24. package/dist/doctor/types.js +1 -0
  25. package/dist/errors.d.ts +122 -0
  26. package/dist/errors.js +123 -0
  27. package/dist/execution/index.d.ts +2 -2
  28. package/dist/execution/index.js +2 -0
  29. package/dist/execution/service.d.ts +46 -41
  30. package/dist/execution/service.js +633 -0
  31. package/dist/execution/types.d.ts +59 -2
  32. package/dist/execution/types.js +1 -0
  33. package/dist/export/index.d.ts +1 -1
  34. package/dist/export/index.js +1 -0
  35. package/dist/export/templates.d.ts +45 -7
  36. package/dist/export/templates.js +631 -0
  37. package/dist/filter/index.d.ts +1 -1
  38. package/dist/filter/index.js +1 -0
  39. package/dist/filter/intent.js +123 -0
  40. package/dist/host/host.d.ts +55 -0
  41. package/dist/host/host.js +740 -0
  42. package/dist/host/index.d.ts +2 -0
  43. package/dist/host/index.js +2 -0
  44. package/dist/host/types.d.ts +81 -0
  45. package/dist/host/types.js +1 -0
  46. package/dist/index.d.ts +21 -16
  47. package/dist/index.js +20 -8458
  48. package/dist/ipc/diagnostic.d.ts +65 -0
  49. package/dist/ipc/diagnostic.js +129 -0
  50. package/dist/ipc/host.d.ts +7 -0
  51. package/dist/ipc/host.js +93 -0
  52. package/dist/ipc/index.d.ts +4 -0
  53. package/dist/ipc/index.js +4 -0
  54. package/dist/ipc/target.d.ts +56 -0
  55. package/dist/ipc/target.js +297 -0
  56. package/dist/ipc/types.d.ts +42 -0
  57. package/dist/ipc/types.js +1 -0
  58. package/dist/platform/default.d.ts +32 -0
  59. package/dist/platform/default.js +40 -0
  60. package/dist/platform/index.d.ts +3 -0
  61. package/dist/platform/index.js +3 -0
  62. package/dist/platform/node-fs.d.ts +37 -0
  63. package/dist/platform/node-fs.js +81 -0
  64. package/dist/platform/types.d.ts +104 -0
  65. package/dist/platform/types.js +1 -0
  66. package/dist/profile/client.d.ts +58 -12
  67. package/dist/profile/client.js +467 -0
  68. package/dist/profile/index.d.ts +3 -3
  69. package/dist/profile/index.js +3 -0
  70. package/dist/profile/manager.d.ts +2 -2
  71. package/dist/profile/manager.js +294 -0
  72. package/dist/profile/types.d.ts +1 -1
  73. package/dist/profile/types.js +1 -0
  74. package/dist/project/closure.js +68 -0
  75. package/dist/project/digest.d.ts +29 -0
  76. package/dist/project/digest.js +324 -0
  77. package/dist/project/index.d.ts +10 -5
  78. package/dist/project/index.js +10 -0
  79. package/dist/project/init.d.ts +3 -4
  80. package/dist/project/init.js +175 -0
  81. package/dist/project/loader.d.ts +21 -27
  82. package/dist/project/loader.js +484 -0
  83. package/dist/project/lockfile.d.ts +55 -0
  84. package/dist/project/lockfile.js +175 -0
  85. package/dist/project/manifest.d.ts +6 -28
  86. package/dist/project/manifest.js +220 -0
  87. package/dist/project/resolver.d.ts +116 -0
  88. package/dist/project/resolver.js +376 -0
  89. package/dist/project/transactions.d.ts +103 -0
  90. package/dist/project/transactions.js +1203 -0
  91. package/dist/project/types-generator.d.ts +33 -0
  92. package/dist/project/types-generator.js +168 -0
  93. package/dist/project/types.d.ts +58 -82
  94. package/dist/project/types.js +1 -0
  95. package/dist/registry/index.d.ts +5 -2
  96. package/dist/registry/index.js +5 -0
  97. package/dist/registry/lock.d.ts +27 -0
  98. package/dist/registry/lock.js +93 -0
  99. package/dist/registry/registry.d.ts +32 -11
  100. package/dist/registry/registry.js +611 -0
  101. package/dist/registry/resolve.d.ts +62 -0
  102. package/dist/registry/resolve.js +195 -0
  103. package/dist/registry/scan.d.ts +38 -0
  104. package/dist/registry/scan.js +135 -0
  105. package/dist/registry/types.d.ts +1 -1
  106. package/dist/registry/types.js +1 -0
  107. package/dist/runtime/action-collection.d.ts +27 -0
  108. package/dist/runtime/action-collection.js +57 -0
  109. package/dist/runtime/clock.d.ts +0 -2
  110. package/dist/runtime/clock.js +17 -0
  111. package/dist/runtime/context.d.ts +16 -5
  112. package/dist/runtime/context.js +242 -0
  113. package/dist/runtime/env.d.ts +3 -1
  114. package/dist/runtime/env.js +159 -0
  115. package/dist/runtime/events.d.ts +46 -14
  116. package/dist/runtime/events.js +604 -0
  117. package/dist/runtime/execution-manager.d.ts +1 -1
  118. package/dist/runtime/execution-manager.js +66 -0
  119. package/dist/runtime/index.d.ts +10 -9
  120. package/dist/runtime/index.js +10 -0
  121. package/dist/runtime/module-loader.d.ts +0 -8
  122. package/dist/runtime/module-loader.js +16 -0
  123. package/dist/runtime/process.d.ts +2 -4
  124. package/dist/runtime/process.js +146 -0
  125. package/dist/runtime/runner.d.ts +117 -10
  126. package/dist/runtime/runner.js +984 -0
  127. package/dist/runtime/standalone.d.ts +68 -19
  128. package/dist/runtime/standalone.js +536 -0
  129. package/dist/schema/validator.js +122 -0
  130. package/dist/server/body.js +93 -0
  131. package/dist/server/index.d.ts +5 -6
  132. package/dist/server/index.js +5 -0
  133. package/dist/server/routes/actions.d.ts +2 -2
  134. package/dist/server/routes/actions.js +298 -0
  135. package/dist/server/routes/common.d.ts +28 -17
  136. package/dist/server/routes/common.js +104 -0
  137. package/dist/server/routes/config.d.ts +2 -1
  138. package/dist/server/routes/config.js +173 -0
  139. package/dist/server/routes/doctor.d.ts +2 -2
  140. package/dist/server/routes/doctor.js +63 -0
  141. package/dist/server/routes/health.d.ts +2 -2
  142. package/dist/server/routes/health.js +35 -0
  143. package/dist/server/routes/index.d.ts +9 -9
  144. package/dist/server/routes/index.js +9 -0
  145. package/dist/server/routes/info.d.ts +4 -2
  146. package/dist/server/routes/info.js +158 -0
  147. package/dist/server/routes/playbooks.d.ts +2 -2
  148. package/dist/server/routes/playbooks.js +134 -0
  149. package/dist/server/routes/runs.d.ts +2 -2
  150. package/dist/server/routes/runs.js +343 -0
  151. package/dist/server/routes/state.d.ts +2 -1
  152. package/dist/server/routes/state.js +169 -0
  153. package/dist/server/security.d.ts +11 -4
  154. package/dist/server/security.js +101 -0
  155. package/dist/server/server.d.ts +6 -6
  156. package/dist/server/server.js +355 -0
  157. package/dist/server/types.d.ts +27 -8
  158. package/dist/server/types.js +1 -0
  159. package/dist/storage/data-dir-lock.d.ts +119 -0
  160. package/dist/storage/data-dir-lock.js +1016 -0
  161. package/dist/storage/driver.d.ts +4 -10
  162. package/dist/storage/driver.js +59 -0
  163. package/dist/storage/index.d.ts +30 -10
  164. package/dist/storage/index.js +98 -0
  165. package/dist/storage/lazy.d.ts +8 -0
  166. package/dist/storage/lazy.js +44 -0
  167. package/dist/storage/mask.d.ts +12 -5
  168. package/dist/storage/mask.js +48 -0
  169. package/dist/storage/params.d.ts +13 -0
  170. package/dist/storage/params.js +32 -0
  171. package/dist/storage/sqlite.d.ts +19 -6
  172. package/dist/storage/sqlite.js +689 -0
  173. package/dist/storage/types.d.ts +57 -6
  174. package/dist/storage/types.js +14 -0
  175. package/dist/target/index.d.ts +4 -0
  176. package/dist/target/index.js +4 -0
  177. package/dist/target/local.d.ts +52 -0
  178. package/dist/target/local.js +343 -0
  179. package/dist/target/remote.d.ts +76 -0
  180. package/dist/target/remote.js +839 -0
  181. package/dist/target/target.d.ts +6 -0
  182. package/dist/target/target.js +35 -0
  183. package/dist/target/types.d.ts +246 -0
  184. package/dist/target/types.js +34 -0
  185. package/dist/utils/index.d.ts +22 -2
  186. package/dist/utils/index.js +180 -0
  187. package/dist/version.d.ts +1 -1
  188. package/dist/version.js +4 -0
  189. package/package.json +6 -7
  190. package/dist/build/index.d.ts +0 -1
  191. package/dist/build/templates.d.ts +0 -19
  192. package/dist/server/runtime-registry.d.ts +0 -78
@@ -0,0 +1,104 @@
1
+ import { assertValidPackageId } from "../../utils/index.js";
2
+ /**
3
+ * 构造带 CORS 头的标准 JSON HTTP 响应。
4
+ */
5
+ export function jsonResponse(data, status = 200, corsHeaders = {}) {
6
+ return new Response(JSON.stringify(data, null, 2), {
7
+ status,
8
+ headers: {
9
+ "Content-Type": "application/json",
10
+ ...corsHeaders,
11
+ },
12
+ });
13
+ }
14
+ /**
15
+ * 剥离标准版本前缀 (/api/v2/ 与兼容的 /api/v1/),获取相对路由子路径。
16
+ */
17
+ export function getSubPath(pathname) {
18
+ if (pathname.startsWith("/api/v2/")) {
19
+ return pathname.slice(7);
20
+ }
21
+ if (pathname.startsWith("/api/v1/")) {
22
+ return pathname.slice(7);
23
+ }
24
+ return pathname;
25
+ }
26
+ /**
27
+ * 包未列入白名单拒绝访问异常。
28
+ */
29
+ export class PackageNotAllowedError extends Error {
30
+ status = 403;
31
+ statusCode = 403;
32
+ code = "PACKAGE_NOT_ALLOWED";
33
+ constructor(packageId) {
34
+ super(`Package '${packageId}' is not in the allowed package list`);
35
+ this.name = "PackageNotAllowedError";
36
+ }
37
+ }
38
+ /**
39
+ * 校验目标 packageId 是否属于允许包白名单。
40
+ * 当 options.packageAllowlist 存在且目标 packageId 不在白名单中时抛出 403 PACKAGE_NOT_ALLOWED 异常。
41
+ */
42
+ export function assertPackageAllowed(packageId, options) {
43
+ if (options?.packageAllowlist &&
44
+ Array.isArray(options.packageAllowlist) &&
45
+ options.packageAllowlist.length > 0) {
46
+ if (!packageId || !options.packageAllowlist.includes(packageId)) {
47
+ throw new PackageNotAllowedError(packageId || "");
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * 从 Host 或 Target 中依据 packageId 解析对应的 ActionDockApp 实例。
53
+ * 若提供了 options,过滤或优先选择白名单内的 app。
54
+ */
55
+ export function resolveAppForPackage(packageIdOrPath, host, target, options) {
56
+ if (packageIdOrPath) {
57
+ assertPackageAllowed(packageIdOrPath, options);
58
+ assertValidPackageId(packageIdOrPath);
59
+ if (host) {
60
+ const app = host.getApp(packageIdOrPath);
61
+ if (app)
62
+ return app;
63
+ }
64
+ const innerTarget = target?.unwrap?.();
65
+ if (innerTarget && "packageId" in innerTarget && innerTarget.packageId === packageIdOrPath) {
66
+ return innerTarget;
67
+ }
68
+ if (innerTarget && "getApp" in innerTarget) {
69
+ const app = innerTarget.getApp(packageIdOrPath);
70
+ if (app)
71
+ return app;
72
+ }
73
+ throw new Error(`Unknown or unregistered package: '${packageIdOrPath}'`);
74
+ }
75
+ if (host) {
76
+ const apps = host.listApps();
77
+ if (options?.packageAllowlist &&
78
+ Array.isArray(options.packageAllowlist) &&
79
+ options.packageAllowlist.length > 0) {
80
+ const allowedApp = apps.find((a) => options.packageAllowlist.includes(a.packageId));
81
+ if (allowedApp)
82
+ return allowedApp;
83
+ }
84
+ if (apps.length > 0)
85
+ return apps[0];
86
+ }
87
+ const innerTarget = target?.unwrap?.();
88
+ if (innerTarget && "listApps" in innerTarget) {
89
+ const apps = innerTarget.listApps();
90
+ if (options?.packageAllowlist &&
91
+ Array.isArray(options.packageAllowlist) &&
92
+ options.packageAllowlist.length > 0) {
93
+ const allowedApp = apps.find((a) => options.packageAllowlist.includes(a.packageId));
94
+ if (allowedApp)
95
+ return allowedApp;
96
+ }
97
+ if (apps.length > 0)
98
+ return apps[0];
99
+ }
100
+ if (innerTarget && "packageId" in innerTarget) {
101
+ return innerTarget;
102
+ }
103
+ throw new Error("No registered package found in host or target");
104
+ }
@@ -1,5 +1,6 @@
1
- import { type RouteContext } from "./common";
1
+ import { type RouteContext } from "./common.js";
2
2
  /**
3
3
  * 处理配置元数据与当前值读取、更新及删除接口。
4
+ * 在未显式开启管理功能 (options.enableManagement !== true) 时返回 403 拒绝。
4
5
  */
5
6
  export declare function handleConfigRoutes(ctx: RouteContext): Promise<Response | null>;
@@ -0,0 +1,173 @@
1
+ import { CAPABILITY_UNAVAILABLE } from "../../errors.js";
2
+ import { resolveEnvValue } from "../../runtime/index.js";
3
+ import { isSecretConfigKey, maskSecretValue, sanitizeConfigDefinitions } from "../../storage/index.js";
4
+ import { readJsonBody } from "../body.js";
5
+ import { assertPackageAllowed, getSubPath, jsonResponse, resolveAppForPackage } from "./common.js";
6
+ /**
7
+ * 处理配置元数据与当前值读取、更新及删除接口。
8
+ * 在未显式开启管理功能 (options.enableManagement !== true) 时返回 403 拒绝。
9
+ */
10
+ export async function handleConfigRoutes(ctx) {
11
+ const { req, url, pathname, corsHeaders, options, host, target } = ctx;
12
+ const subpath = getSubPath(pathname);
13
+ const isConfigRoute = subpath === "/config" ||
14
+ subpath.startsWith("/config/") ||
15
+ subpath.startsWith("/management/config");
16
+ if (!isConfigRoute) {
17
+ return null;
18
+ }
19
+ // 校验管理能力开关
20
+ if (options.enableManagement !== true) {
21
+ return jsonResponse({
22
+ ok: false,
23
+ error: {
24
+ code: CAPABILITY_UNAVAILABLE,
25
+ message: "Management APIs are not enabled on this server. Set enableManagement: true to enable.",
26
+ },
27
+ }, 403, corsHeaders);
28
+ }
29
+ // 1. Config Env Check: GET /config/env
30
+ if (subpath === "/config/env" && req.method === "GET") {
31
+ try {
32
+ const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
33
+ if (pkgParam) {
34
+ assertPackageAllowed(pkgParam, options);
35
+ }
36
+ const app = resolveAppForPackage(pkgParam, host, target, options);
37
+ assertPackageAllowed(app.packageId, options);
38
+ const declared = app.projectConfig?.config || {};
39
+ const envChecks = [];
40
+ for (const [k, def] of Object.entries(declared)) {
41
+ const resolved = resolveEnvValue(k, def, app.packageId);
42
+ const matchedEnv = resolved?.envKey || null;
43
+ envChecks.push({
44
+ key: k,
45
+ required: def.default === undefined,
46
+ satisfied: Boolean(resolved !== undefined || def.default !== undefined),
47
+ matchedEnv,
48
+ hasDefault: def.default !== undefined,
49
+ secret: isSecretConfigKey(k, def),
50
+ });
51
+ }
52
+ return jsonResponse({ ok: true, packageId: app.packageId, envChecks }, 200, corsHeaders);
53
+ }
54
+ catch (err) {
55
+ if (err.code === "PACKAGE_NOT_ALLOWED" || err.status === 403) {
56
+ return jsonResponse({
57
+ ok: false,
58
+ error: {
59
+ code: "PACKAGE_NOT_ALLOWED",
60
+ message: err.message || "Package is not in the allowed package list",
61
+ },
62
+ }, 403, corsHeaders);
63
+ }
64
+ const isClient = err.message?.includes("Unknown or unregistered package") ||
65
+ err.message?.includes("Invalid packageId") ||
66
+ err.message?.includes("escapes boundary");
67
+ return jsonResponse({ ok: false, error: { code: isClient ? "INVALID_ARGUMENT" : "CONFIG_ENV_ERROR", message: err.message } }, isClient ? 400 : 500, corsHeaders);
68
+ }
69
+ }
70
+ // 2. Config Query: GET /config
71
+ if (subpath === "/config" && req.method === "GET") {
72
+ try {
73
+ const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
74
+ if (pkgParam) {
75
+ assertPackageAllowed(pkgParam, options);
76
+ }
77
+ const app = resolveAppForPackage(pkgParam, host, target, options);
78
+ assertPackageAllowed(app.packageId, options);
79
+ const stored = app.storage.listConfig();
80
+ const rawDeclared = app.projectConfig?.config || {};
81
+ const declared = sanitizeConfigDefinitions(rawDeclared) || {};
82
+ const maskedValues = {};
83
+ for (const [k, v] of Object.entries(stored)) {
84
+ if (isSecretConfigKey(k, rawDeclared[k])) {
85
+ maskedValues[k] = maskSecretValue(v);
86
+ }
87
+ else {
88
+ maskedValues[k] = v;
89
+ }
90
+ }
91
+ return jsonResponse({ ok: true, packageId: app.packageId, declared, values: maskedValues }, 200, corsHeaders);
92
+ }
93
+ catch (err) {
94
+ if (err.code === "PACKAGE_NOT_ALLOWED" || err.status === 403) {
95
+ return jsonResponse({
96
+ ok: false,
97
+ error: {
98
+ code: "PACKAGE_NOT_ALLOWED",
99
+ message: err.message || "Package is not in the allowed package list",
100
+ },
101
+ }, 403, corsHeaders);
102
+ }
103
+ const isClient = err.message?.includes("Unknown or unregistered package") ||
104
+ err.message?.includes("Invalid packageId") ||
105
+ err.message?.includes("escapes boundary");
106
+ return jsonResponse({ ok: false, error: { code: isClient ? "INVALID_ARGUMENT" : "CONFIG_LIST_ERROR", message: err.message } }, isClient ? 400 : 500, corsHeaders);
107
+ }
108
+ }
109
+ // 3. Config Update: PUT / POST /config
110
+ if (subpath === "/config" && (req.method === "PUT" || req.method === "POST")) {
111
+ try {
112
+ const body = await readJsonBody(req, { maxBytes: options.maxBodyBytes });
113
+ const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || body.package || undefined;
114
+ if (pkgParam) {
115
+ assertPackageAllowed(pkgParam, options);
116
+ }
117
+ const app = resolveAppForPackage(pkgParam, host, target, options);
118
+ assertPackageAllowed(app.packageId, options);
119
+ const key = body.key;
120
+ if (!key) {
121
+ return jsonResponse({ ok: false, error: { code: "INVALID_ARGUMENT", message: "Config 'key' is required" } }, 400, corsHeaders);
122
+ }
123
+ await app.setConfig(key, body.value);
124
+ return jsonResponse({ ok: true, packageId: app.packageId, key, message: "updated" }, 200, corsHeaders);
125
+ }
126
+ catch (err) {
127
+ if (err.code === "PACKAGE_NOT_ALLOWED" || err.status === 403) {
128
+ return jsonResponse({
129
+ ok: false,
130
+ error: {
131
+ code: "PACKAGE_NOT_ALLOWED",
132
+ message: err.message || "Package is not in the allowed package list",
133
+ },
134
+ }, 403, corsHeaders);
135
+ }
136
+ const isClient = err.message?.includes("Unknown or unregistered package") ||
137
+ err.message?.includes("Invalid packageId") ||
138
+ err.message?.includes("escapes boundary");
139
+ return jsonResponse({ ok: false, error: { code: isClient ? "INVALID_ARGUMENT" : "CONFIG_SET_ERROR", message: err.message } }, isClient ? 400 : 500, corsHeaders);
140
+ }
141
+ }
142
+ // 4. Config Delete: DELETE /config/:key
143
+ const configKeyMatch = subpath.match(/^\/config\/([^/]+)$/);
144
+ if (configKeyMatch && req.method === "DELETE") {
145
+ try {
146
+ const key = decodeURIComponent(configKeyMatch[1]);
147
+ const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
148
+ if (pkgParam) {
149
+ assertPackageAllowed(pkgParam, options);
150
+ }
151
+ const app = resolveAppForPackage(pkgParam, host, target, options);
152
+ assertPackageAllowed(app.packageId, options);
153
+ const deleted = app.storage.deleteConfig(key);
154
+ return jsonResponse({ ok: true, packageId: app.packageId, key, deleted }, 200, corsHeaders);
155
+ }
156
+ catch (err) {
157
+ if (err.code === "PACKAGE_NOT_ALLOWED" || err.status === 403) {
158
+ return jsonResponse({
159
+ ok: false,
160
+ error: {
161
+ code: "PACKAGE_NOT_ALLOWED",
162
+ message: err.message || "Package is not in the allowed package list",
163
+ },
164
+ }, 403, corsHeaders);
165
+ }
166
+ const isClient = err.message?.includes("Unknown or unregistered package") ||
167
+ err.message?.includes("Invalid packageId") ||
168
+ err.message?.includes("escapes boundary");
169
+ return jsonResponse({ ok: false, error: { code: isClient ? "INVALID_ARGUMENT" : "CONFIG_DELETE_ERROR", message: err.message } }, isClient ? 400 : 500, corsHeaders);
170
+ }
171
+ }
172
+ return null;
173
+ }
@@ -1,5 +1,5 @@
1
- import { type RouteContext } from "./common";
1
+ import { type RouteContext } from "./common.js";
2
2
  /**
3
- * 处理环境与依赖诊断接口(GET /api/v1/doctor)。
3
+ * 处理环境与依赖诊断接口(GET /api/v2/doctor、/doctor 与兼容别名 /api/v1/doctor)。
4
4
  */
5
5
  export declare function handleDoctorRoute(ctx: RouteContext): Promise<Response | null>;
@@ -0,0 +1,63 @@
1
+ import { runDoctorChecks } from "../../doctor/doctor.js";
2
+ import { assertPackageAllowed, getSubPath, jsonResponse } from "./common.js";
3
+ /**
4
+ * 处理环境与依赖诊断接口(GET /api/v2/doctor、/doctor 与兼容别名 /api/v1/doctor)。
5
+ */
6
+ export async function handleDoctorRoute(ctx) {
7
+ const { req, url, pathname, corsHeaders, projectRoot, customHome, host, target, options } = ctx;
8
+ const subpath = getSubPath(pathname);
9
+ if (subpath !== "/doctor" || req.method !== "GET") {
10
+ return null;
11
+ }
12
+ try {
13
+ const targetPkg = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
14
+ if (options.packageAllowlist && options.packageAllowlist.length > 0) {
15
+ if (!targetPkg) {
16
+ return jsonResponse({
17
+ ok: false,
18
+ error: {
19
+ code: "PACKAGE_NOT_ALLOWED",
20
+ message: "Global doctor check is forbidden when package allowlist is active. Please specify an allowed package.",
21
+ },
22
+ }, 403, corsHeaders);
23
+ }
24
+ }
25
+ if (targetPkg) {
26
+ assertPackageAllowed(targetPkg, options);
27
+ }
28
+ let packageRoot;
29
+ if (targetPkg) {
30
+ if (host) {
31
+ packageRoot = host.getApp(targetPkg)?.packageRoot;
32
+ }
33
+ if (!packageRoot && target?.unwrap) {
34
+ const inner = target.unwrap();
35
+ if (inner && "getApp" in inner && typeof inner.getApp === "function") {
36
+ packageRoot = inner.getApp(targetPkg)?.packageRoot;
37
+ }
38
+ else if (inner && "packageId" in inner && inner.packageId === targetPkg) {
39
+ packageRoot = inner.packageRoot;
40
+ }
41
+ }
42
+ }
43
+ const report = await runDoctorChecks({
44
+ cwd: packageRoot || projectRoot || process.cwd(),
45
+ packageIdOrPath: packageRoot || targetPkg,
46
+ customHome,
47
+ packageAllowlist: options.packageAllowlist,
48
+ });
49
+ return jsonResponse({ ok: true, report }, 200, corsHeaders);
50
+ }
51
+ catch (err) {
52
+ if (err.code === "PACKAGE_NOT_ALLOWED" || err.status === 403) {
53
+ return jsonResponse({
54
+ ok: false,
55
+ error: {
56
+ code: "PACKAGE_NOT_ALLOWED",
57
+ message: err.message || "Package is not in the allowed package list",
58
+ },
59
+ }, 403, corsHeaders);
60
+ }
61
+ return jsonResponse({ ok: false, error: { code: "DOCTOR_ERROR", message: err.message } }, 500, corsHeaders);
62
+ }
63
+ }
@@ -1,5 +1,5 @@
1
- import { type RouteContext } from "./common";
1
+ import { type RouteContext } from "./common.js";
2
2
  /**
3
- * 处理健康检查与就绪状态接口(支持 /api/v1/health /health)。
3
+ * 处理健康检查与就绪状态接口(支持 /api/v2/health、/health 与兼容别名 /api/v1/health)。
4
4
  */
5
5
  export declare function handleHealthRoute(ctx: RouteContext): Promise<Response | null>;
@@ -0,0 +1,35 @@
1
+ import { ACTIONDOCK_VERSION } from "../../version.js";
2
+ import { UNAUTHORIZED } from "../../errors.js";
3
+ import { verifyBearerToken } from "../security.js";
4
+ import { jsonResponse } from "./common.js";
5
+ /**
6
+ * 处理健康检查与就绪状态接口(支持 /api/v2/health、/health 与兼容别名 /api/v1/health)。
7
+ */
8
+ export async function handleHealthRoute(ctx) {
9
+ const { req, pathname, corsHeaders, options, projectRoot } = ctx;
10
+ if (pathname !== "/api/v2/health" &&
11
+ pathname !== "/health" &&
12
+ pathname !== "/api/v1/health") {
13
+ return null;
14
+ }
15
+ if (!verifyBearerToken(req, options.token, options)) {
16
+ return jsonResponse({
17
+ ok: false,
18
+ error: {
19
+ code: UNAUTHORIZED,
20
+ message: "Invalid or missing Bearer token",
21
+ },
22
+ }, 401, corsHeaders);
23
+ }
24
+ const healthData = {
25
+ ok: true,
26
+ status: "healthy",
27
+ version: ACTIONDOCK_VERSION,
28
+ timestamp: new Date().toISOString(),
29
+ uptime: process.uptime(),
30
+ };
31
+ if (options.exposeDebugInfo && projectRoot) {
32
+ healthData.projectRoot = projectRoot;
33
+ }
34
+ return jsonResponse(healthData, 200, corsHeaders);
35
+ }
@@ -1,9 +1,9 @@
1
- export * from "./common";
2
- export * from "./health";
3
- export * from "./info";
4
- export * from "./doctor";
5
- export * from "./actions";
6
- export * from "./playbooks";
7
- export * from "./runs";
8
- export * from "./state";
9
- export * from "./config";
1
+ export * from "./common.js";
2
+ export * from "./health.js";
3
+ export * from "./info.js";
4
+ export * from "./doctor.js";
5
+ export * from "./actions.js";
6
+ export * from "./playbooks.js";
7
+ export * from "./runs.js";
8
+ export * from "./state.js";
9
+ export * from "./config.js";
@@ -0,0 +1,9 @@
1
+ export * from "./common.js";
2
+ export * from "./health.js";
3
+ export * from "./info.js";
4
+ export * from "./doctor.js";
5
+ export * from "./actions.js";
6
+ export * from "./playbooks.js";
7
+ export * from "./runs.js";
8
+ export * from "./state.js";
9
+ export * from "./config.js";
@@ -1,5 +1,7 @@
1
- import { type RouteContext } from "./common";
1
+ import { type RouteContext } from "./common.js";
2
2
  /**
3
- * 处理系统探索与自省接口(GET /api/v1/info)。
3
+ * 处理系统自省与包信息接口:
4
+ * - GET /api/v2/info 与 GET /info -> 委托 target.info()
5
+ * - GET /api/v2/packages 与 GET /packages -> 委托 host.info() 或 target.info()
4
6
  */
5
7
  export declare function handleInfoRoute(ctx: RouteContext): Promise<Response | null>;
@@ -0,0 +1,158 @@
1
+ import { filterWithFallbackInfo } from "../../filter/index.js";
2
+ import { PACKAGE_NOT_FOUND } from "../../errors.js";
3
+ import { ACTIONDOCK_VERSION } from "../../version.js";
4
+ import { sanitizeConfigDefinitions } from "../../storage/index.js";
5
+ import { assertPackageAllowed, getSubPath, jsonResponse } from "./common.js";
6
+ function sanitizePackage(pkg, exposeDebugInfo) {
7
+ if (!pkg)
8
+ return pkg;
9
+ const sanitized = {
10
+ ...pkg,
11
+ ...(pkg.config ? { config: sanitizeConfigDefinitions(pkg.config) } : {}),
12
+ };
13
+ if (exposeDebugInfo !== true) {
14
+ delete sanitized.packageRoot;
15
+ delete sanitized.path;
16
+ }
17
+ return sanitized;
18
+ }
19
+ /**
20
+ * 处理系统自省与包信息接口:
21
+ * - GET /api/v2/info 与 GET /info -> 委托 target.info()
22
+ * - GET /api/v2/packages 与 GET /packages -> 委托 host.info() 或 target.info()
23
+ */
24
+ export async function handleInfoRoute(ctx) {
25
+ const { req, url, pathname, corsHeaders, options, target, host, projectRoot } = ctx;
26
+ const subpath = getSubPath(pathname);
27
+ // 1. Packages List: GET /api/v2/packages, /packages
28
+ if ((subpath === "/packages" || pathname === "/api/v2/packages" || pathname === "/packages") && req.method === "GET") {
29
+ try {
30
+ const targetPkg = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
31
+ if (targetPkg) {
32
+ assertPackageAllowed(targetPkg, options);
33
+ }
34
+ const rawPackages = target ? await target.listPackages() : (host ? await host.info() : []);
35
+ let packages = rawPackages.map((p) => sanitizePackage(p, options.exposeDebugInfo));
36
+ if (options.packageAllowlist && Array.isArray(options.packageAllowlist) && options.packageAllowlist.length > 0) {
37
+ packages = packages.filter((p) => p.id && options.packageAllowlist.includes(p.id));
38
+ }
39
+ return jsonResponse({
40
+ ok: true,
41
+ packages,
42
+ }, 200, corsHeaders);
43
+ }
44
+ catch (err) {
45
+ if (err.code === "PACKAGE_NOT_ALLOWED" || err.status === 403) {
46
+ return jsonResponse({
47
+ ok: false,
48
+ error: {
49
+ code: "PACKAGE_NOT_ALLOWED",
50
+ message: err.message || "Package is not in the allowed package list",
51
+ },
52
+ }, 403, corsHeaders);
53
+ }
54
+ return jsonResponse({
55
+ ok: false,
56
+ error: { code: "PACKAGES_INFO_ERROR", message: err.message },
57
+ }, 500, corsHeaders);
58
+ }
59
+ }
60
+ // 2. Info: GET /api/v2/info, /info
61
+ if ((subpath === "/info" || pathname === "/api/v2/info" || pathname === "/info") && req.method === "GET") {
62
+ try {
63
+ const isTree = url.searchParams.get("tree") === "true";
64
+ const intent = url.searchParams.get("intent") || undefined;
65
+ const targetPkg = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
66
+ if (targetPkg) {
67
+ assertPackageAllowed(targetPkg, options);
68
+ }
69
+ const targetInfo = await target.info();
70
+ const rawPackages = targetInfo.packages || [];
71
+ let packages = rawPackages.map((p) => sanitizePackage(p, options.exposeDebugInfo));
72
+ if (options.packageAllowlist && Array.isArray(options.packageAllowlist) && options.packageAllowlist.length > 0) {
73
+ packages = packages.filter((p) => p.id && options.packageAllowlist.includes(p.id));
74
+ }
75
+ if (isTree) {
76
+ return jsonResponse({ ok: true, type: "tree", packages }, 200, corsHeaders);
77
+ }
78
+ // 显式指定 package 详情下钻
79
+ if (targetPkg) {
80
+ const matched = packages.find((p) => p.id === targetPkg || p.packageRoot === targetPkg || p.path === targetPkg);
81
+ if (!matched) {
82
+ return jsonResponse({
83
+ ok: false,
84
+ error: {
85
+ code: PACKAGE_NOT_FOUND,
86
+ message: `Package '${targetPkg}' not found on remote server`,
87
+ },
88
+ }, 404, corsHeaders);
89
+ }
90
+ return jsonResponse({
91
+ ok: true,
92
+ type: "package_detail",
93
+ ...matched,
94
+ ...(options.exposeDebugInfo ? { projectRoot: matched.packageRoot || projectRoot } : {}),
95
+ }, 200, corsHeaders);
96
+ }
97
+ // 意图过滤
98
+ if (intent) {
99
+ const filterRes = filterWithFallbackInfo(packages, intent, [
100
+ (p) => p.id,
101
+ (p) => p.name,
102
+ (p) => p.description,
103
+ ], true);
104
+ if (filterRes.matchedCount === 1) {
105
+ const single = filterRes.items[0];
106
+ return jsonResponse({
107
+ ok: true,
108
+ type: "package_detail",
109
+ isSingleMatch: true,
110
+ ...single,
111
+ ...(options.exposeDebugInfo ? { projectRoot: single.packageRoot || projectRoot } : {}),
112
+ }, 200, corsHeaders);
113
+ }
114
+ return jsonResponse({
115
+ ok: true,
116
+ type: "package_list",
117
+ isFallback: filterRes.isFallback,
118
+ packages: filterRes.items,
119
+ }, 200, corsHeaders);
120
+ }
121
+ // 单包模式下展开详情
122
+ if (packages.length === 1) {
123
+ const p = packages[0];
124
+ return jsonResponse({
125
+ ok: true,
126
+ type: "package_detail",
127
+ ...p,
128
+ packages,
129
+ ...(options.exposeDebugInfo ? { projectRoot: p.packageRoot || projectRoot } : {}),
130
+ }, 200, corsHeaders);
131
+ }
132
+ // 多包列表概览
133
+ return jsonResponse({
134
+ ok: true,
135
+ type: "package_list",
136
+ version: ACTIONDOCK_VERSION,
137
+ packages,
138
+ ...(options.exposeDebugInfo ? { projectRoot } : {}),
139
+ }, 200, corsHeaders);
140
+ }
141
+ catch (err) {
142
+ if (err.code === "PACKAGE_NOT_ALLOWED" || err.status === 403) {
143
+ return jsonResponse({
144
+ ok: false,
145
+ error: {
146
+ code: "PACKAGE_NOT_ALLOWED",
147
+ message: err.message || "Package is not in the allowed package list",
148
+ },
149
+ }, 403, corsHeaders);
150
+ }
151
+ return jsonResponse({
152
+ ok: false,
153
+ error: { code: "INFO_ERROR", message: err.message },
154
+ }, 500, corsHeaders);
155
+ }
156
+ }
157
+ return null;
158
+ }
@@ -1,5 +1,5 @@
1
- import { type RouteContext } from "./common";
1
+ import { type RouteContext } from "./common.js";
2
2
  /**
3
- * 处理 Playbook 列表与 SOP 详情接口。
3
+ * 处理 Playbook 规程相关的 HTTP 路由(列表与 SOP 详情查询)。
4
4
  */
5
5
  export declare function handlePlaybooksRoutes(ctx: RouteContext): Promise<Response | null>;