@actiondock/core 2.0.12 → 2.2.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 +61 -30
- package/dist/app/app.d.ts +73 -0
- package/dist/app/app.js +700 -0
- package/dist/app/index.d.ts +2 -0
- package/dist/app/index.js +2 -0
- package/dist/app/types.d.ts +283 -0
- package/dist/app/types.js +1 -0
- package/dist/catalog/action-index.d.ts +1 -1
- package/dist/catalog/action-index.js +93 -0
- package/dist/catalog/action-resolver.d.ts +2 -2
- package/dist/catalog/action-resolver.js +95 -0
- package/dist/catalog/index.d.ts +5 -5
- package/dist/catalog/index.js +5 -0
- package/dist/catalog/location-registry.d.ts +5 -5
- package/dist/catalog/location-registry.js +138 -0
- package/dist/catalog/package-catalog.d.ts +2 -2
- package/dist/catalog/package-catalog.js +76 -0
- package/dist/catalog/types.d.ts +2 -2
- package/dist/catalog/types.js +1 -0
- package/dist/doctor/doctor.d.ts +2 -1
- package/dist/doctor/doctor.js +613 -0
- package/dist/doctor/index.d.ts +2 -2
- package/dist/doctor/index.js +2 -0
- package/dist/doctor/types.js +1 -0
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +123 -0
- package/dist/execution/index.d.ts +2 -2
- package/dist/execution/index.js +2 -0
- package/dist/execution/service.d.ts +46 -41
- package/dist/execution/service.js +633 -0
- package/dist/execution/types.d.ts +59 -2
- package/dist/execution/types.js +1 -0
- package/dist/export/index.d.ts +1 -1
- package/dist/export/index.js +1 -0
- package/dist/export/templates.d.ts +45 -7
- package/dist/export/templates.js +621 -0
- package/dist/filter/index.d.ts +1 -1
- package/dist/filter/index.js +1 -0
- package/dist/filter/intent.js +123 -0
- package/dist/host/host.d.ts +55 -0
- package/dist/host/host.js +740 -0
- package/dist/host/index.d.ts +2 -0
- package/dist/host/index.js +2 -0
- package/dist/host/types.d.ts +81 -0
- package/dist/host/types.js +1 -0
- package/dist/index.d.ts +21 -16
- package/dist/index.js +20 -8458
- package/dist/ipc/diagnostic.d.ts +65 -0
- package/dist/ipc/diagnostic.js +129 -0
- package/dist/ipc/host.d.ts +7 -0
- package/dist/ipc/host.js +93 -0
- package/dist/ipc/index.d.ts +4 -0
- package/dist/ipc/index.js +4 -0
- package/dist/ipc/target.d.ts +56 -0
- package/dist/ipc/target.js +297 -0
- package/dist/ipc/types.d.ts +42 -0
- package/dist/ipc/types.js +1 -0
- package/dist/platform/default.d.ts +32 -0
- package/dist/platform/default.js +40 -0
- package/dist/platform/index.d.ts +3 -0
- package/dist/platform/index.js +3 -0
- package/dist/platform/node-fs.d.ts +37 -0
- package/dist/platform/node-fs.js +81 -0
- package/dist/platform/types.d.ts +104 -0
- package/dist/platform/types.js +1 -0
- package/dist/profile/client.d.ts +58 -12
- package/dist/profile/client.js +467 -0
- package/dist/profile/index.d.ts +3 -3
- package/dist/profile/index.js +3 -0
- package/dist/profile/manager.d.ts +2 -2
- package/dist/profile/manager.js +294 -0
- package/dist/profile/types.d.ts +1 -1
- package/dist/profile/types.js +1 -0
- package/dist/project/closure.js +68 -0
- package/dist/project/digest.d.ts +29 -0
- package/dist/project/digest.js +324 -0
- package/dist/project/index.d.ts +10 -5
- package/dist/project/index.js +10 -0
- package/dist/project/init.d.ts +3 -4
- package/dist/project/init.js +175 -0
- package/dist/project/loader.d.ts +21 -27
- package/dist/project/loader.js +484 -0
- package/dist/project/lockfile.d.ts +55 -0
- package/dist/project/lockfile.js +175 -0
- package/dist/project/manifest.d.ts +6 -28
- package/dist/project/manifest.js +220 -0
- package/dist/project/resolver.d.ts +116 -0
- package/dist/project/resolver.js +376 -0
- package/dist/project/transactions.d.ts +103 -0
- package/dist/project/transactions.js +1203 -0
- package/dist/project/types-generator.d.ts +33 -0
- package/dist/project/types-generator.js +168 -0
- package/dist/project/types.d.ts +58 -82
- package/dist/project/types.js +1 -0
- package/dist/registry/index.d.ts +5 -2
- package/dist/registry/index.js +5 -0
- package/dist/registry/lock.d.ts +27 -0
- package/dist/registry/lock.js +93 -0
- package/dist/registry/registry.d.ts +32 -11
- package/dist/registry/registry.js +611 -0
- package/dist/registry/resolve.d.ts +62 -0
- package/dist/registry/resolve.js +195 -0
- package/dist/registry/scan.d.ts +38 -0
- package/dist/registry/scan.js +135 -0
- package/dist/registry/types.d.ts +1 -1
- package/dist/registry/types.js +1 -0
- package/dist/runtime/action-collection.d.ts +27 -0
- package/dist/runtime/action-collection.js +57 -0
- package/dist/runtime/clock.d.ts +0 -2
- package/dist/runtime/clock.js +17 -0
- package/dist/runtime/context.d.ts +16 -5
- package/dist/runtime/context.js +242 -0
- package/dist/runtime/env.d.ts +3 -1
- package/dist/runtime/env.js +159 -0
- package/dist/runtime/events.d.ts +46 -14
- package/dist/runtime/events.js +604 -0
- package/dist/runtime/execution-manager.d.ts +1 -1
- package/dist/runtime/execution-manager.js +66 -0
- package/dist/runtime/index.d.ts +10 -9
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/module-loader.d.ts +0 -8
- package/dist/runtime/module-loader.js +16 -0
- package/dist/runtime/process.d.ts +2 -4
- package/dist/runtime/process.js +146 -0
- package/dist/runtime/runner.d.ts +117 -10
- package/dist/runtime/runner.js +984 -0
- package/dist/runtime/standalone.d.ts +68 -19
- package/dist/runtime/standalone.js +536 -0
- package/dist/schema/validator.js +116 -0
- package/dist/server/body.js +93 -0
- package/dist/server/index.d.ts +5 -6
- package/dist/server/index.js +5 -0
- package/dist/server/routes/actions.d.ts +2 -2
- package/dist/server/routes/actions.js +298 -0
- package/dist/server/routes/common.d.ts +28 -17
- package/dist/server/routes/common.js +104 -0
- package/dist/server/routes/config.d.ts +2 -1
- package/dist/server/routes/config.js +173 -0
- package/dist/server/routes/doctor.d.ts +2 -2
- package/dist/server/routes/doctor.js +63 -0
- package/dist/server/routes/health.d.ts +2 -2
- package/dist/server/routes/health.js +35 -0
- package/dist/server/routes/index.d.ts +9 -9
- package/dist/server/routes/index.js +9 -0
- package/dist/server/routes/info.d.ts +4 -2
- package/dist/server/routes/info.js +158 -0
- package/dist/server/routes/playbooks.d.ts +2 -2
- package/dist/server/routes/playbooks.js +134 -0
- package/dist/server/routes/runs.d.ts +2 -2
- package/dist/server/routes/runs.js +343 -0
- package/dist/server/routes/state.d.ts +2 -1
- package/dist/server/routes/state.js +169 -0
- package/dist/server/security.d.ts +11 -4
- package/dist/server/security.js +101 -0
- package/dist/server/server.d.ts +6 -6
- package/dist/server/server.js +355 -0
- package/dist/server/types.d.ts +27 -8
- package/dist/server/types.js +1 -0
- package/dist/storage/data-dir-lock.d.ts +119 -0
- package/dist/storage/data-dir-lock.js +1016 -0
- package/dist/storage/driver.d.ts +4 -10
- package/dist/storage/driver.js +59 -0
- package/dist/storage/index.d.ts +30 -10
- package/dist/storage/index.js +98 -0
- package/dist/storage/lazy.d.ts +8 -0
- package/dist/storage/lazy.js +44 -0
- package/dist/storage/mask.d.ts +12 -5
- package/dist/storage/mask.js +48 -0
- package/dist/storage/params.d.ts +13 -0
- package/dist/storage/params.js +32 -0
- package/dist/storage/sqlite.d.ts +19 -6
- package/dist/storage/sqlite.js +689 -0
- package/dist/storage/types.d.ts +57 -6
- package/dist/storage/types.js +14 -0
- package/dist/target/index.d.ts +4 -0
- package/dist/target/index.js +4 -0
- package/dist/target/local.d.ts +52 -0
- package/dist/target/local.js +343 -0
- package/dist/target/remote.d.ts +76 -0
- package/dist/target/remote.js +839 -0
- package/dist/target/target.d.ts +6 -0
- package/dist/target/target.js +35 -0
- package/dist/target/types.d.ts +246 -0
- package/dist/target/types.js +34 -0
- package/dist/utils/index.d.ts +22 -2
- package/dist/utils/index.js +180 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +4 -0
- package/package.json +6 -7
- package/dist/build/index.d.ts +0 -1
- package/dist/build/templates.d.ts +0 -19
- package/dist/server/runtime-registry.d.ts +0 -78
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { filterByIntent } from "../../filter/index.js";
|
|
2
|
+
import { assertPackageAllowed, getSubPath, jsonResponse } from "./common.js";
|
|
3
|
+
/**
|
|
4
|
+
* 处理 Playbook 规程相关的 HTTP 路由(列表与 SOP 详情查询)。
|
|
5
|
+
*/
|
|
6
|
+
export async function handlePlaybooksRoutes(ctx) {
|
|
7
|
+
const { req, url, pathname, corsHeaders, target, host, options } = ctx;
|
|
8
|
+
const subpath = getSubPath(pathname);
|
|
9
|
+
// 1. Playbooks List: GET /api/v2/playbooks, GET /playbooks
|
|
10
|
+
if (subpath === "/playbooks" && req.method === "GET") {
|
|
11
|
+
try {
|
|
12
|
+
const intent = url.searchParams.get("intent") || undefined;
|
|
13
|
+
const targetPkg = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
|
|
14
|
+
if (targetPkg) {
|
|
15
|
+
assertPackageAllowed(targetPkg, options);
|
|
16
|
+
}
|
|
17
|
+
let pbs = await target.listPlaybooks({ intent, package: targetPkg });
|
|
18
|
+
if (targetPkg) {
|
|
19
|
+
pbs = pbs.filter((p) => p.packageId === targetPkg);
|
|
20
|
+
}
|
|
21
|
+
if (options.packageAllowlist && Array.isArray(options.packageAllowlist) && options.packageAllowlist.length > 0) {
|
|
22
|
+
pbs = pbs.filter((p) => p.packageId && options.packageAllowlist.includes(p.packageId));
|
|
23
|
+
}
|
|
24
|
+
if (intent) {
|
|
25
|
+
pbs = filterByIntent(pbs, intent, [(p) => p.id, (p) => p.description || "", (p) => p.packageId || "", (p) => (p.actions || []).join(" ")], false);
|
|
26
|
+
}
|
|
27
|
+
return jsonResponse(pbs, 200, corsHeaders);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
if (err.code === "PACKAGE_NOT_ALLOWED" || err.status === 403) {
|
|
31
|
+
return jsonResponse({
|
|
32
|
+
ok: false,
|
|
33
|
+
error: {
|
|
34
|
+
code: "PACKAGE_NOT_ALLOWED",
|
|
35
|
+
message: err.message || "Package is not in the allowed package list",
|
|
36
|
+
},
|
|
37
|
+
}, 403, corsHeaders);
|
|
38
|
+
}
|
|
39
|
+
return jsonResponse({ ok: false, error: { code: "PLAYBOOKS_LIST_ERROR", message: err.message } }, 500, corsHeaders);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// 2. Multi-package Playbook Show: GET /api/v2/packages/:packageId/playbooks/:playbookId
|
|
43
|
+
const pkgPlaybookMatch = subpath.match(/^\/packages\/([^/]+)\/playbooks\/([^/]+)$/);
|
|
44
|
+
if (pkgPlaybookMatch && req.method === "GET") {
|
|
45
|
+
const packageId = decodeURIComponent(pkgPlaybookMatch[1]);
|
|
46
|
+
const playbookId = decodeURIComponent(pkgPlaybookMatch[2]);
|
|
47
|
+
try {
|
|
48
|
+
assertPackageAllowed(packageId, options);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
return jsonResponse({
|
|
52
|
+
ok: false,
|
|
53
|
+
error: {
|
|
54
|
+
code: "PACKAGE_NOT_ALLOWED",
|
|
55
|
+
message: err.message || `Package '${packageId}' is not in the allowed package list`,
|
|
56
|
+
},
|
|
57
|
+
}, 403, corsHeaders);
|
|
58
|
+
}
|
|
59
|
+
const ref = `${packageId}/${playbookId}`;
|
|
60
|
+
try {
|
|
61
|
+
const pb = await target.describePlaybook(ref);
|
|
62
|
+
return jsonResponse(pb, 200, corsHeaders);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
return jsonResponse({
|
|
66
|
+
ok: false,
|
|
67
|
+
error: {
|
|
68
|
+
code: "PLAYBOOK_NOT_FOUND",
|
|
69
|
+
message: err.message || `Playbook '${playbookId}' not found in package '${packageId}'`,
|
|
70
|
+
},
|
|
71
|
+
}, 404, corsHeaders);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// 3. Short Playbook Show: GET /api/v2/playbooks/:id, GET /playbooks/:id
|
|
75
|
+
const playbookMatch = subpath.match(/^\/playbooks\/(.+)$/);
|
|
76
|
+
if (playbookMatch && req.method === "GET") {
|
|
77
|
+
const playbookId = decodeURIComponent(playbookMatch[1]);
|
|
78
|
+
if (playbookId.includes("/")) {
|
|
79
|
+
const pkgFromRef = playbookId.slice(0, playbookId.lastIndexOf("/"));
|
|
80
|
+
try {
|
|
81
|
+
assertPackageAllowed(pkgFromRef, options);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
return jsonResponse({
|
|
85
|
+
ok: false,
|
|
86
|
+
error: {
|
|
87
|
+
code: "PACKAGE_NOT_ALLOWED",
|
|
88
|
+
message: err.message || `Package '${pkgFromRef}' is not in the allowed package list`,
|
|
89
|
+
},
|
|
90
|
+
}, 403, corsHeaders);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const pb = await target.describePlaybook(playbookId);
|
|
95
|
+
const unwrapped = target.unwrap?.();
|
|
96
|
+
let pkgId = pb.packageId || (unwrapped && "packageId" in unwrapped ? unwrapped.packageId : undefined);
|
|
97
|
+
if (!pkgId && host) {
|
|
98
|
+
for (const app of host.listApps()) {
|
|
99
|
+
try {
|
|
100
|
+
const map = app.getStaticPlaybookMap?.();
|
|
101
|
+
const cleanId = playbookId.replace(/\.md$/, "");
|
|
102
|
+
if (map?.has(cleanId) || map?.has(playbookId)) {
|
|
103
|
+
pkgId = app.packageId;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch { }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!pkgId) {
|
|
111
|
+
const allPbs = await target.listPlaybooks().catch(() => []);
|
|
112
|
+
const matched = allPbs.find((p) => p.id === playbookId || p.id.endsWith(`/${playbookId}`));
|
|
113
|
+
if (matched?.packageId) {
|
|
114
|
+
pkgId = matched.packageId;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
assertPackageAllowed(pkgId, options);
|
|
118
|
+
return jsonResponse(pb, 200, corsHeaders);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
if (err.code === "PACKAGE_NOT_ALLOWED" || err.status === 403) {
|
|
122
|
+
return jsonResponse({
|
|
123
|
+
ok: false,
|
|
124
|
+
error: {
|
|
125
|
+
code: "PACKAGE_NOT_ALLOWED",
|
|
126
|
+
message: err.message || "Package is not in the allowed package list",
|
|
127
|
+
},
|
|
128
|
+
}, 403, corsHeaders);
|
|
129
|
+
}
|
|
130
|
+
return jsonResponse({ ok: false, error: { code: "PLAYBOOK_NOT_FOUND", message: err.message } }, 404, corsHeaders);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { filterByIntent } from "../../filter/index.js";
|
|
2
|
+
import { EXECUTION_FAILED } from "../../errors.js";
|
|
3
|
+
import { isTerminalRunStatus } from "../../storage/types.js";
|
|
4
|
+
import { readJsonBody } from "../body.js";
|
|
5
|
+
import { getSubPath, jsonResponse } from "./common.js";
|
|
6
|
+
/**
|
|
7
|
+
* 处理历史运行记录查询、清理、详情及 SSE 流式日志接口。
|
|
8
|
+
*/
|
|
9
|
+
export async function handleRunsRoutes(ctx) {
|
|
10
|
+
const { req, url, pathname, corsHeaders, options, target, host } = ctx;
|
|
11
|
+
const subpath = getSubPath(pathname);
|
|
12
|
+
// 1. Runs List: GET /api/v2/runs, GET /runs
|
|
13
|
+
if (subpath === "/runs" && req.method === "GET") {
|
|
14
|
+
try {
|
|
15
|
+
const status = url.searchParams.get("status") || undefined;
|
|
16
|
+
const actionId = url.searchParams.get("actionId") || undefined;
|
|
17
|
+
const packageId = url.searchParams.get("packageId") || undefined;
|
|
18
|
+
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)) {
|
|
24
|
+
return jsonResponse({
|
|
25
|
+
ok: false,
|
|
26
|
+
error: {
|
|
27
|
+
code: "PACKAGE_FORBIDDEN",
|
|
28
|
+
message: `Package '${packageId}' is not in the allowed package list`,
|
|
29
|
+
},
|
|
30
|
+
}, 403, corsHeaders);
|
|
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
|
+
const records = app.storage.listRuns({ actionId, limit });
|
|
53
|
+
for (const r of records) {
|
|
54
|
+
if (!seenRunIds.has(r.id)) {
|
|
55
|
+
seenRunIds.add(r.id);
|
|
56
|
+
if (status && r.status !== status)
|
|
57
|
+
continue;
|
|
58
|
+
if (options.packageAllowlist &&
|
|
59
|
+
options.packageAllowlist.length > 0 &&
|
|
60
|
+
r.packageId &&
|
|
61
|
+
!options.packageAllowlist.includes(r.packageId)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
allRuns.push(r);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch { }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
allRuns.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || ""));
|
|
72
|
+
const filtered = intent
|
|
73
|
+
? filterByIntent(allRuns, intent, [(r) => r.id, (r) => r.actionId, (r) => r.status, (r) => r.packageId], false)
|
|
74
|
+
: allRuns;
|
|
75
|
+
const sliced = filtered.slice(0, limit);
|
|
76
|
+
return jsonResponse({ ok: true, total: filtered.length, items: sliced }, 200, corsHeaders);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
return jsonResponse({ ok: false, error: { code: "RUNS_LIST_ERROR", message: err.message } }, 500, corsHeaders);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// 2. Runs Clear: POST /api/v2/runs/clear, DELETE /api/v2/runs, etc.
|
|
83
|
+
if ((subpath === "/runs/clear" && req.method === "POST") ||
|
|
84
|
+
(subpath === "/runs" && req.method === "DELETE")) {
|
|
85
|
+
try {
|
|
86
|
+
let body = {};
|
|
87
|
+
if (req.method === "POST" || req.headers.get("content-type")?.includes("json")) {
|
|
88
|
+
body = await readJsonBody(req, { maxBytes: options.maxBodyBytes }).catch(() => ({}));
|
|
89
|
+
}
|
|
90
|
+
const packageId = url.searchParams.get("packageId") || body.packageId || undefined;
|
|
91
|
+
const actionId = url.searchParams.get("actionId") || body.actionId || undefined;
|
|
92
|
+
const status = url.searchParams.get("status") || body.status || undefined;
|
|
93
|
+
if (packageId &&
|
|
94
|
+
options.packageAllowlist &&
|
|
95
|
+
options.packageAllowlist.length > 0 &&
|
|
96
|
+
!options.packageAllowlist.includes(packageId)) {
|
|
97
|
+
return jsonResponse({
|
|
98
|
+
ok: false,
|
|
99
|
+
error: {
|
|
100
|
+
code: "PACKAGE_FORBIDDEN",
|
|
101
|
+
message: `Package '${packageId}' is not in the allowed package list`,
|
|
102
|
+
},
|
|
103
|
+
}, 403, corsHeaders);
|
|
104
|
+
}
|
|
105
|
+
let clearedCount = 0;
|
|
106
|
+
const apps = host
|
|
107
|
+
? host.listApps()
|
|
108
|
+
: (() => {
|
|
109
|
+
const inner = target?.unwrap?.();
|
|
110
|
+
return inner && "listApps" in inner ? inner.listApps() : [inner].filter(Boolean);
|
|
111
|
+
})();
|
|
112
|
+
for (const app of apps) {
|
|
113
|
+
if (!app)
|
|
114
|
+
continue;
|
|
115
|
+
if (packageId && app.packageId !== packageId)
|
|
116
|
+
continue;
|
|
117
|
+
if (options.packageAllowlist && options.packageAllowlist.length > 0) {
|
|
118
|
+
if (!options.packageAllowlist.includes(app.packageId)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (app.storage && typeof app.storage.clearRuns === "function") {
|
|
123
|
+
clearedCount += app.storage.clearRuns({ actionId, status });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return jsonResponse({ ok: true, clearedCount }, 200, corsHeaders);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
return jsonResponse({ ok: false, error: { code: "RUNS_CLEAR_ERROR", message: err.message } }, 500, corsHeaders);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// 3. Run Events SSE: GET /api/v2/runs/:runId/events (and stream alias)
|
|
133
|
+
const runEventsMatch = subpath.match(/^\/runs\/([^/]+)\/(events|stream)$/);
|
|
134
|
+
if (runEventsMatch && req.method === "GET") {
|
|
135
|
+
const runId = decodeURIComponent(runEventsMatch[1]);
|
|
136
|
+
const run = await target.getRun(runId);
|
|
137
|
+
if (!run) {
|
|
138
|
+
return jsonResponse({ ok: false, error: { code: "RUN_NOT_FOUND", message: `Run '${runId}' not found` } }, 404, corsHeaders);
|
|
139
|
+
}
|
|
140
|
+
if (options.packageAllowlist &&
|
|
141
|
+
options.packageAllowlist.length > 0 &&
|
|
142
|
+
(!run.packageId || !options.packageAllowlist.includes(run.packageId))) {
|
|
143
|
+
return jsonResponse({
|
|
144
|
+
ok: false,
|
|
145
|
+
error: {
|
|
146
|
+
code: "PACKAGE_FORBIDDEN",
|
|
147
|
+
message: `Package '${run.packageId}' is not in the allowed package list`,
|
|
148
|
+
},
|
|
149
|
+
}, 403, corsHeaders);
|
|
150
|
+
}
|
|
151
|
+
// 解析 Last-Event-ID 请求头或 query 参数 after 游标
|
|
152
|
+
const lastEventIdHeader = req.headers.get("Last-Event-ID") ||
|
|
153
|
+
req.headers.get("last-event-id") ||
|
|
154
|
+
url.searchParams.get("after") ||
|
|
155
|
+
undefined;
|
|
156
|
+
let afterCursor = undefined;
|
|
157
|
+
if (lastEventIdHeader !== undefined && lastEventIdHeader.trim() !== "") {
|
|
158
|
+
const trimmed = lastEventIdHeader.trim();
|
|
159
|
+
if (/^-?\d+$/.test(trimmed)) {
|
|
160
|
+
afterCursor = parseInt(trimmed, 10);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
afterCursor = trimmed;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const eventStream = target.events(runId, { after: afterCursor, signal: req.signal });
|
|
167
|
+
const iterator = eventStream[Symbol.asyncIterator]();
|
|
168
|
+
// 检查游标是否在建流前已过期:拉取首个事件,若抛出 EVENT_CURSOR_EXPIRED 直接返回 410
|
|
169
|
+
let firstResult;
|
|
170
|
+
try {
|
|
171
|
+
firstResult = await iterator.next();
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
if (err?.code === "EVENT_CURSOR_EXPIRED") {
|
|
175
|
+
return jsonResponse({
|
|
176
|
+
ok: false,
|
|
177
|
+
error: {
|
|
178
|
+
code: "EVENT_CURSOR_EXPIRED",
|
|
179
|
+
message: err.message || "Event cursor has expired",
|
|
180
|
+
details: err.details,
|
|
181
|
+
},
|
|
182
|
+
}, 410, corsHeaders);
|
|
183
|
+
}
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
const stream = new ReadableStream({
|
|
187
|
+
async start(controller) {
|
|
188
|
+
const encoder = new TextEncoder();
|
|
189
|
+
let sawFinish = false;
|
|
190
|
+
let eventsCount = 0;
|
|
191
|
+
try {
|
|
192
|
+
// 先消费首个事件结果
|
|
193
|
+
if (!firstResult.done) {
|
|
194
|
+
eventsCount++;
|
|
195
|
+
const evt = firstResult.value;
|
|
196
|
+
if (evt.type === "finish") {
|
|
197
|
+
sawFinish = true;
|
|
198
|
+
}
|
|
199
|
+
const eventType = evt.type || "message";
|
|
200
|
+
const idField = evt.eventId ? `id: ${evt.eventId}\n` : `id: ${evt.sequence}\n`;
|
|
201
|
+
controller.enqueue(encoder.encode(`${idField}event: ${eventType}\ndata: ${JSON.stringify(evt)}\n\n`));
|
|
202
|
+
// 若首条事件为背压终止事件,立即关闭通道
|
|
203
|
+
if (evt.type === "error" && evt.error?.code === "EVENT_BACKPRESSURE_LIMIT") {
|
|
204
|
+
controller.close();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// 循环消费后续实时或队列事件
|
|
209
|
+
while (!req.signal.aborted) {
|
|
210
|
+
const nextResult = await iterator.next();
|
|
211
|
+
if (nextResult.done)
|
|
212
|
+
break;
|
|
213
|
+
eventsCount++;
|
|
214
|
+
const evt = nextResult.value;
|
|
215
|
+
if (evt.type === "finish") {
|
|
216
|
+
sawFinish = true;
|
|
217
|
+
}
|
|
218
|
+
const eventType = evt.type || "message";
|
|
219
|
+
const idField = evt.eventId ? `id: ${evt.eventId}\n` : `id: ${evt.sequence}\n`;
|
|
220
|
+
controller.enqueue(encoder.encode(`${idField}event: ${eventType}\ndata: ${JSON.stringify(evt)}\n\n`));
|
|
221
|
+
// 若收到背压截断终止事件,正常关闭流通道
|
|
222
|
+
if (evt.type === "error" && evt.error?.code === "EVENT_BACKPRESSURE_LIMIT") {
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (!sawFinish && run && isTerminalRunStatus(run.status)) {
|
|
227
|
+
const finishEvt = {
|
|
228
|
+
type: "finish",
|
|
229
|
+
runId,
|
|
230
|
+
timestamp: run.finishedAt || new Date().toISOString(),
|
|
231
|
+
result: run.status === "success"
|
|
232
|
+
? { ok: true, runId, data: run.output ?? null }
|
|
233
|
+
: {
|
|
234
|
+
ok: false,
|
|
235
|
+
runId,
|
|
236
|
+
error: run.error || {
|
|
237
|
+
code: run.status === "interrupted" ? "RUN_INTERRUPTED" : EXECUTION_FAILED,
|
|
238
|
+
message: `Run finished with status ${run.status}`,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
controller.enqueue(encoder.encode(`event: finish\ndata: ${JSON.stringify(finishEvt)}\n\n`));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// 忽略中断
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
try {
|
|
250
|
+
controller.close();
|
|
251
|
+
}
|
|
252
|
+
catch { }
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
cancel() { },
|
|
256
|
+
});
|
|
257
|
+
return new Response(stream, {
|
|
258
|
+
status: 200,
|
|
259
|
+
headers: {
|
|
260
|
+
"Content-Type": "text/event-stream",
|
|
261
|
+
"Cache-Control": "no-cache",
|
|
262
|
+
"Connection": "keep-alive",
|
|
263
|
+
...corsHeaders,
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
// 4. Run Show: GET /api/v2/runs/:runId, GET /runs/:runId
|
|
268
|
+
const runShowMatch = subpath.match(/^\/runs\/([^/]+)$/);
|
|
269
|
+
if (runShowMatch && req.method === "GET") {
|
|
270
|
+
const runId = decodeURIComponent(runShowMatch[1]);
|
|
271
|
+
const run = await target.getRun(runId);
|
|
272
|
+
if (!run) {
|
|
273
|
+
return jsonResponse({
|
|
274
|
+
ok: false,
|
|
275
|
+
error: {
|
|
276
|
+
code: "RUN_NOT_FOUND",
|
|
277
|
+
message: `Run '${runId}' not found`,
|
|
278
|
+
},
|
|
279
|
+
}, 404, corsHeaders);
|
|
280
|
+
}
|
|
281
|
+
if (options.packageAllowlist &&
|
|
282
|
+
options.packageAllowlist.length > 0 &&
|
|
283
|
+
(!run.packageId || !options.packageAllowlist.includes(run.packageId))) {
|
|
284
|
+
return jsonResponse({
|
|
285
|
+
ok: false,
|
|
286
|
+
error: {
|
|
287
|
+
code: "PACKAGE_FORBIDDEN",
|
|
288
|
+
message: `Package '${run.packageId || "unknown"}' is not in the allowed package list`,
|
|
289
|
+
},
|
|
290
|
+
}, 403, corsHeaders);
|
|
291
|
+
}
|
|
292
|
+
return jsonResponse(run, 200, corsHeaders);
|
|
293
|
+
}
|
|
294
|
+
// 5. Run Cancel: POST /api/v2/runs/:runId/cancel, POST /runs/:runId/cancel
|
|
295
|
+
const runCancelMatch = subpath.match(/^\/runs\/([^/]+)\/cancel$/);
|
|
296
|
+
if (runCancelMatch && req.method === "POST") {
|
|
297
|
+
const runId = decodeURIComponent(runCancelMatch[1]);
|
|
298
|
+
if (options.packageAllowlist && options.packageAllowlist.length > 0) {
|
|
299
|
+
const run = await target.getRun(runId);
|
|
300
|
+
if (run && (!run.packageId || !options.packageAllowlist.includes(run.packageId))) {
|
|
301
|
+
return jsonResponse({
|
|
302
|
+
ok: false,
|
|
303
|
+
error: {
|
|
304
|
+
code: "PACKAGE_FORBIDDEN",
|
|
305
|
+
message: `Package '${run.packageId}' is not in the allowed package list`,
|
|
306
|
+
},
|
|
307
|
+
}, 403, corsHeaders);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
let body = {};
|
|
311
|
+
try {
|
|
312
|
+
body = await readJsonBody(req, { maxBytes: options.maxBodyBytes });
|
|
313
|
+
}
|
|
314
|
+
catch { }
|
|
315
|
+
const reason = body?.reason || "Cancelled by client request";
|
|
316
|
+
const cancelResult = await target.cancelRun(runId, reason);
|
|
317
|
+
if (cancelResult.outcome === "not_found") {
|
|
318
|
+
return jsonResponse({
|
|
319
|
+
ok: false,
|
|
320
|
+
error: {
|
|
321
|
+
code: "RUN_NOT_FOUND",
|
|
322
|
+
message: `Run '${runId}' not found`,
|
|
323
|
+
},
|
|
324
|
+
}, 404, corsHeaders);
|
|
325
|
+
}
|
|
326
|
+
if (cancelResult.outcome === "already_terminal") {
|
|
327
|
+
return jsonResponse({
|
|
328
|
+
ok: false,
|
|
329
|
+
error: {
|
|
330
|
+
code: "RUN_ALREADY_FINISHED",
|
|
331
|
+
message: `Run '${runId}' has already finished with status '${cancelResult.status}'`,
|
|
332
|
+
status: cancelResult.status,
|
|
333
|
+
},
|
|
334
|
+
}, 409, corsHeaders);
|
|
335
|
+
}
|
|
336
|
+
return jsonResponse({
|
|
337
|
+
ok: true,
|
|
338
|
+
runId,
|
|
339
|
+
status: "cancelled",
|
|
340
|
+
}, 200, corsHeaders);
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
@@ -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 handleStateRoutes(ctx: RouteContext): Promise<Response | null>;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { decodeStateKey } from "../../storage/index.js";
|
|
2
|
+
import { CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND } from "../../errors.js";
|
|
3
|
+
import { readJsonBody } from "../body.js";
|
|
4
|
+
import { assertPackageAllowed, getSubPath, jsonResponse, resolveAppForPackage } from "./common.js";
|
|
5
|
+
/**
|
|
6
|
+
* 处理状态键名列表、读取、写入、删除及清空接口。
|
|
7
|
+
* 在未显式开启管理功能 (options.enableManagement !== true) 时返回 403 拒绝。
|
|
8
|
+
*/
|
|
9
|
+
export async function handleStateRoutes(ctx) {
|
|
10
|
+
const { req, url, pathname, corsHeaders, options, host, target } = ctx;
|
|
11
|
+
const subpath = getSubPath(pathname);
|
|
12
|
+
const isStateRoute = subpath === "/state" ||
|
|
13
|
+
subpath.startsWith("/state/") ||
|
|
14
|
+
subpath.startsWith("/management/state");
|
|
15
|
+
if (!isStateRoute) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
// 校验管理能力开关
|
|
19
|
+
if (options.enableManagement !== true) {
|
|
20
|
+
return jsonResponse({
|
|
21
|
+
ok: false,
|
|
22
|
+
error: {
|
|
23
|
+
code: CAPABILITY_UNAVAILABLE,
|
|
24
|
+
message: "Management APIs are not enabled on this server. Set enableManagement: true to enable.",
|
|
25
|
+
},
|
|
26
|
+
}, 403, corsHeaders);
|
|
27
|
+
}
|
|
28
|
+
// 1. State List: GET /state
|
|
29
|
+
if (subpath === "/state" && req.method === "GET") {
|
|
30
|
+
try {
|
|
31
|
+
const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
|
|
32
|
+
const actionParam = url.searchParams.get("action") || url.searchParams.get("actionId") || "";
|
|
33
|
+
const nsParam = url.searchParams.get("namespace") ?? undefined;
|
|
34
|
+
const prefix = url.searchParams.get("prefix") || "";
|
|
35
|
+
if (pkgParam) {
|
|
36
|
+
assertPackageAllowed(pkgParam, options);
|
|
37
|
+
}
|
|
38
|
+
const app = resolveAppForPackage(pkgParam, host, target, options);
|
|
39
|
+
assertPackageAllowed(app.packageId, options);
|
|
40
|
+
const effectiveNs = actionParam
|
|
41
|
+
? (nsParam !== undefined ? `${actionParam}:${nsParam}` : actionParam)
|
|
42
|
+
: (nsParam !== undefined ? nsParam : null);
|
|
43
|
+
const keys = await app.storage.listStateKeys(effectiveNs, prefix);
|
|
44
|
+
return jsonResponse({ ok: true, packageId: app.packageId, keys }, 200, corsHeaders);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (err.code === "PACKAGE_NOT_ALLOWED" || err.status === 403) {
|
|
48
|
+
return jsonResponse({
|
|
49
|
+
ok: false,
|
|
50
|
+
error: {
|
|
51
|
+
code: "PACKAGE_NOT_ALLOWED",
|
|
52
|
+
message: err.message || "Package is not in the allowed package list",
|
|
53
|
+
},
|
|
54
|
+
}, 403, corsHeaders);
|
|
55
|
+
}
|
|
56
|
+
const isClient = err.message?.includes("Unknown or unregistered package") ||
|
|
57
|
+
err.message?.includes("Invalid packageId") ||
|
|
58
|
+
err.message?.includes("escapes boundary");
|
|
59
|
+
return jsonResponse({ ok: false, error: { code: isClient ? "INVALID_ARGUMENT" : "STATE_LIST_ERROR", message: err.message } }, isClient ? 400 : 500, corsHeaders);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// 2. State Clear: POST /state/clear
|
|
63
|
+
if (subpath === "/state/clear" && req.method === "POST") {
|
|
64
|
+
try {
|
|
65
|
+
const body = await readJsonBody(req, { maxBytes: options.maxBodyBytes }).catch(() => ({}));
|
|
66
|
+
const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || body.package || undefined;
|
|
67
|
+
const actionParam = url.searchParams.get("action") || url.searchParams.get("actionId") || body.action || body.actionId || "";
|
|
68
|
+
const baseNs = body.namespace ?? (url.searchParams.get("namespace") || undefined);
|
|
69
|
+
const effectiveNs = actionParam ? (baseNs ? `${actionParam}:${baseNs}` : actionParam) : baseNs;
|
|
70
|
+
if (pkgParam) {
|
|
71
|
+
assertPackageAllowed(pkgParam, options);
|
|
72
|
+
}
|
|
73
|
+
const app = resolveAppForPackage(pkgParam, host, target, options);
|
|
74
|
+
assertPackageAllowed(app.packageId, options);
|
|
75
|
+
const clearedCount = await app.storage.clearState({
|
|
76
|
+
namespace: effectiveNs,
|
|
77
|
+
all: Boolean(body.all ?? url.searchParams.get("all") === "true"),
|
|
78
|
+
prefix: body.prefix ?? (url.searchParams.get("prefix") || undefined),
|
|
79
|
+
});
|
|
80
|
+
return jsonResponse({ ok: true, packageId: app.packageId, clearedCount }, 200, corsHeaders);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
if (err.code === "PACKAGE_NOT_ALLOWED" || err.status === 403) {
|
|
84
|
+
return jsonResponse({
|
|
85
|
+
ok: false,
|
|
86
|
+
error: {
|
|
87
|
+
code: "PACKAGE_NOT_ALLOWED",
|
|
88
|
+
message: err.message || "Package is not in the allowed package list",
|
|
89
|
+
},
|
|
90
|
+
}, 403, corsHeaders);
|
|
91
|
+
}
|
|
92
|
+
const isClient = err.message?.includes("Unknown or unregistered package") ||
|
|
93
|
+
err.message?.includes("Invalid packageId") ||
|
|
94
|
+
err.message?.includes("escapes boundary");
|
|
95
|
+
return jsonResponse({ ok: false, error: { code: isClient ? "INVALID_ARGUMENT" : "STATE_CLEAR_ERROR", message: err.message } }, isClient ? 400 : 500, corsHeaders);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// 3. State Key CRUD: GET / PUT / POST / DELETE /state/:key
|
|
99
|
+
const stateKeyMatch = subpath.match(/^\/state\/([^/]+)$/);
|
|
100
|
+
if (stateKeyMatch) {
|
|
101
|
+
try {
|
|
102
|
+
const key = decodeURIComponent(stateKeyMatch[1]);
|
|
103
|
+
const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
|
|
104
|
+
const actionParam = url.searchParams.get("action") || url.searchParams.get("actionId") || "";
|
|
105
|
+
const nsParam = url.searchParams.get("namespace") || undefined;
|
|
106
|
+
const effectiveNs = actionParam ? (nsParam ? `${actionParam}:${nsParam}` : actionParam) : nsParam;
|
|
107
|
+
if (pkgParam) {
|
|
108
|
+
assertPackageAllowed(pkgParam, options);
|
|
109
|
+
}
|
|
110
|
+
const app = resolveAppForPackage(pkgParam, host, target, options);
|
|
111
|
+
assertPackageAllowed(app.packageId, options);
|
|
112
|
+
if (req.method === "GET") {
|
|
113
|
+
const entry = await app.storage.findState(key, effectiveNs);
|
|
114
|
+
if (!entry || entry.value === undefined) {
|
|
115
|
+
return jsonResponse({ ok: false, error: { code: STATE_KEY_NOT_FOUND, message: `State key '${key}' not found` } }, 404, corsHeaders);
|
|
116
|
+
}
|
|
117
|
+
return jsonResponse({
|
|
118
|
+
ok: true,
|
|
119
|
+
packageId: app.packageId,
|
|
120
|
+
key: entry.key,
|
|
121
|
+
namespace: entry.namespace,
|
|
122
|
+
value: entry.value,
|
|
123
|
+
expiresAt: entry.expiresAt,
|
|
124
|
+
}, 200, corsHeaders);
|
|
125
|
+
}
|
|
126
|
+
if (req.method === "PUT" || req.method === "POST") {
|
|
127
|
+
const body = await readJsonBody(req, { maxBytes: options.maxBodyBytes });
|
|
128
|
+
const val = body.value !== undefined ? body.value : body;
|
|
129
|
+
const ttl = typeof body.ttl === "number" ? body.ttl : undefined;
|
|
130
|
+
const bodyAction = body.action || body.actionId || actionParam;
|
|
131
|
+
const explicitNs = body.namespace || nsParam;
|
|
132
|
+
const combinedNs = bodyAction ? (explicitNs ? `${bodyAction}:${explicitNs}` : bodyAction) : explicitNs;
|
|
133
|
+
let actualKey = key;
|
|
134
|
+
let ns = combinedNs || "";
|
|
135
|
+
if (!combinedNs) {
|
|
136
|
+
const decoded = decodeStateKey(key);
|
|
137
|
+
ns = decoded.namespace;
|
|
138
|
+
actualKey = decoded.key;
|
|
139
|
+
}
|
|
140
|
+
await app.storage.setState(ns, actualKey, val, ttl);
|
|
141
|
+
return jsonResponse({ ok: true, packageId: app.packageId, key: actualKey, namespace: ns, message: "updated" }, 200, corsHeaders);
|
|
142
|
+
}
|
|
143
|
+
if (req.method === "DELETE") {
|
|
144
|
+
const deleted = await app.storage.deleteStateSmart(key, effectiveNs);
|
|
145
|
+
if (!deleted) {
|
|
146
|
+
return jsonResponse({ ok: false, error: { code: STATE_KEY_NOT_FOUND, message: `State key '${key}' not found` } }, 404, corsHeaders);
|
|
147
|
+
}
|
|
148
|
+
return jsonResponse({ ok: true, packageId: app.packageId, key, deleted: true }, 200, corsHeaders);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
if (err.code === "PACKAGE_NOT_ALLOWED" || err.status === 403) {
|
|
153
|
+
return jsonResponse({
|
|
154
|
+
ok: false,
|
|
155
|
+
error: {
|
|
156
|
+
code: "PACKAGE_NOT_ALLOWED",
|
|
157
|
+
message: err.message || "Package is not in the allowed package list",
|
|
158
|
+
},
|
|
159
|
+
}, 403, corsHeaders);
|
|
160
|
+
}
|
|
161
|
+
const isClient = err.message?.includes("Unknown or unregistered package") ||
|
|
162
|
+
err.message?.includes("Invalid packageId") ||
|
|
163
|
+
err.message?.includes("escapes boundary") ||
|
|
164
|
+
err.message?.includes("Ambiguous state key");
|
|
165
|
+
return jsonResponse({ ok: false, error: { code: isClient ? "INVALID_ARGUMENT" : "STATE_KEY_ERROR", message: err.message } }, isClient ? 400 : 500, corsHeaders);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|