@actiondock/core 2.9.0 → 2.9.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.
- package/README.md +1 -1
- package/dist/host/host.d.ts +2 -0
- package/dist/host/host.js +10 -0
- package/dist/host/types.d.ts +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/ipc/host.js +1 -0
- package/dist/ipc/service.js +3 -0
- package/dist/package/runtime.d.ts +3 -0
- package/dist/package/runtime.js +8 -0
- package/dist/package/types.d.ts +4 -0
- package/dist/platform/node.js +1 -0
- package/dist/platform/types.d.ts +3 -1
- package/dist/profile/client-runs.d.ts +10 -1
- package/dist/profile/client-runs.js +14 -1
- package/dist/profile/client.d.ts +1 -1
- package/dist/profile/client.js +1 -1
- package/dist/project/init.js +2 -2
- package/dist/server/routes/runs.js +50 -1
- package/dist/server/server.js +15 -0
- package/dist/service/local.js +3 -0
- package/dist/service/remote.js +16 -1
- package/dist/service/types.d.ts +4 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +6 -1
- package/dist/storage/sqlite.d.ts +18 -1
- package/dist/storage/sqlite.js +159 -2
- package/dist/storage/types.d.ts +29 -0
- package/dist/storage/types.js +12 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ Service -> Resolution -> PackageRuntime -> ExecutionService -> ActionRunner -> A
|
|
|
56
56
|
|
|
57
57
|
- 发现端口 `DiscoveryPort`:负责包与动作的元数据发现、清单检索、全文过滤与规程查询。
|
|
58
58
|
- 执行端口 `ExecutionPort`:负责动作的同步阻塞执行(`run`)与异步启动执行(`start`)。
|
|
59
|
-
- 运行端口 `RunsPort
|
|
59
|
+
- 运行端口 `RunsPort`:负责任务运行历史列表、单次详情查询、协同取消(`cancel`)以及历史记录清理与保留策略淘汰(`clearRuns`、`cleanExpiredRuns`)。
|
|
60
60
|
- 事件端口 `EventsPort`:负责任务执行实时事件流订阅。
|
|
61
61
|
- 配置端口 `ConfigPort`:负责运行时分层配置读取、持久化配置管理与环境变量满足度体检。
|
|
62
62
|
- 状态端口 `StatePort`:负责包级与动作级持久化键值存取、前缀列举与过期清理。
|
package/dist/host/host.d.ts
CHANGED
|
@@ -91,7 +91,9 @@ export declare class DefaultActionDockHost implements ActionDockHost {
|
|
|
91
91
|
actionId?: string;
|
|
92
92
|
status?: string;
|
|
93
93
|
olderThanMs?: number;
|
|
94
|
+
keep?: number;
|
|
94
95
|
}): Promise<number>;
|
|
96
|
+
cleanExpiredRuns(policy?: import("../storage/types.js").RunsRetentionPolicy): Promise<number>;
|
|
95
97
|
cancelRun(runId: string, reason?: string): Promise<CancelResult>;
|
|
96
98
|
events(runId: string, options?: {
|
|
97
99
|
after?: number | string;
|
package/dist/host/host.js
CHANGED
|
@@ -684,6 +684,16 @@ export class DefaultActionDockHost {
|
|
|
684
684
|
}
|
|
685
685
|
return total;
|
|
686
686
|
}
|
|
687
|
+
async cleanExpiredRuns(policy) {
|
|
688
|
+
let total = 0;
|
|
689
|
+
for (const runtime of this.listRuntimes()) {
|
|
690
|
+
if (typeof runtime.cleanExpiredRuns === "function") {
|
|
691
|
+
const res = await runtime.cleanExpiredRuns(policy);
|
|
692
|
+
total += res;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return total;
|
|
696
|
+
}
|
|
687
697
|
async cancelRun(runId, reason) {
|
|
688
698
|
for (const runtime of this.listRuntimes()) {
|
|
689
699
|
const res = await runtime.cancelRun(runId, reason);
|
package/dist/host/types.d.ts
CHANGED
|
@@ -80,7 +80,10 @@ export interface ActionDockHost {
|
|
|
80
80
|
actionId?: string;
|
|
81
81
|
status?: string;
|
|
82
82
|
olderThanMs?: number;
|
|
83
|
+
keep?: number;
|
|
83
84
|
}): Promise<number>;
|
|
85
|
+
/** 按保留策略清理所有运行时中的过期及超额终态运行记录 */
|
|
86
|
+
cleanExpiredRuns(policy?: import("../storage/types.js").RunsRetentionPolicy): Promise<number>;
|
|
84
87
|
/** 取消指定在运行的任务 */
|
|
85
88
|
cancelRun(runId: string, reason?: string): Promise<CancelResult>;
|
|
86
89
|
/** 订阅指定运行的事件流 */
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
export { ACTIONDOCK_VERSION } from "./version.js";
|
|
9
9
|
export { createActionDock, connectActionDock, } from "./service/factory.js";
|
|
10
10
|
export type { ActionDockService, ActionDockService as ActionDock, DiscoveryPort, ExecutionPort, RunsPort, EventsPort, RunEventSubscriptionOptions, ConfigPort, StatePort, CreateActionDockOptions, ConnectActionDockOptions, RemoteServiceOptions, ConfigValueView, ListRunsOptions, StateScopeOptions, } from "./service/types.js";
|
|
11
|
+
export { DEFAULT_RUNS_RETENTION_MS, DEFAULT_MAX_RUNS_PER_PACKAGE, DEFAULT_MIN_RETAIN_RUNS, type RunsRetentionPolicy, } from "./storage/types.js";
|
|
12
|
+
export { parseDuration } from "./utils/index.js";
|
|
11
13
|
export type { ActionRef, ResolvedActionRef, ExecutionResult, RunRecord, ExecutionEvent, } from "@actiondock/sdk";
|
|
12
14
|
export type { RunOptions, ExecutionTicket, CancelResult, } from "./execution/types.js";
|
|
13
15
|
export type { ActionSpec, ActionSummary, PlaybookSpec, PlaybookSummary, PackageInfo, ListActionsOptions, } from "./package/types.js";
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
export { ACTIONDOCK_VERSION } from "./version.js";
|
|
10
10
|
// 2. 统一服务门面与聚合契约接口
|
|
11
11
|
export { createActionDock, connectActionDock, } from "./service/factory.js";
|
|
12
|
+
export { DEFAULT_RUNS_RETENTION_MS, DEFAULT_MAX_RUNS_PER_PACKAGE, DEFAULT_MIN_RETAIN_RUNS, } from "./storage/types.js";
|
|
13
|
+
export { parseDuration } from "./utils/index.js";
|
|
12
14
|
// 5. 原生平台装配与服务启动
|
|
13
15
|
export { createNodePlatform, } from "./platform/node.js";
|
|
14
16
|
export { startActionDockServer, } from "./server/server.js";
|
package/dist/ipc/host.js
CHANGED
|
@@ -20,6 +20,7 @@ const IPC_METHOD_HANDLERS = {
|
|
|
20
20
|
getRun: async (service, a) => service.runs.get(a[0]),
|
|
21
21
|
cancelRun: async (service, a) => service.runs.cancel(a[0], a[1]),
|
|
22
22
|
clearRuns: async (service, a) => service.runs.clear ? await service.runs.clear(a[0]) : 0,
|
|
23
|
+
cleanExpiredRuns: async (service, a) => service.runs.cleanExpired ? await service.runs.cleanExpired(a[0]) : 0,
|
|
23
24
|
getConfig: async (service, a) => service.management?.config.get(a[0], a[1]),
|
|
24
25
|
setConfig: async (service, a) => service.management?.config.set(a[0], a[1], a[2]),
|
|
25
26
|
deleteConfig: async (service, a) => service.management?.config.delete(a[0], a[1]),
|
package/dist/ipc/service.js
CHANGED
|
@@ -184,6 +184,9 @@ export class IpcActionDockService {
|
|
|
184
184
|
async clear(opts) {
|
|
185
185
|
return forward("clearRuns", [opts]);
|
|
186
186
|
},
|
|
187
|
+
async cleanExpired(policy) {
|
|
188
|
+
return forward("cleanExpiredRuns", [policy]);
|
|
189
|
+
},
|
|
187
190
|
};
|
|
188
191
|
if (options.enableManagement !== false) {
|
|
189
192
|
this.management = {
|
|
@@ -97,7 +97,10 @@ export declare class DefaultPackageRuntime implements HostManagedPackageRuntime
|
|
|
97
97
|
clearRuns(options?: {
|
|
98
98
|
actionId?: string;
|
|
99
99
|
status?: string;
|
|
100
|
+
olderThanMs?: number;
|
|
101
|
+
keep?: number;
|
|
100
102
|
}): Promise<number>;
|
|
103
|
+
cleanExpiredRuns(policy?: import("../storage/types.js").RunsRetentionPolicy): Promise<number>;
|
|
101
104
|
listStateEntries(options?: any): Promise<import("../storage/types.js").StateEntry[]>;
|
|
102
105
|
close(options?: {
|
|
103
106
|
graceMs?: number;
|
package/dist/package/runtime.js
CHANGED
|
@@ -581,8 +581,16 @@ export class DefaultPackageRuntime {
|
|
|
581
581
|
return this.storage.clearRuns({
|
|
582
582
|
actionId: options?.actionId,
|
|
583
583
|
status: options?.status,
|
|
584
|
+
olderThanMs: options?.olderThanMs,
|
|
585
|
+
keep: options?.keep,
|
|
584
586
|
});
|
|
585
587
|
}
|
|
588
|
+
async cleanExpiredRuns(policy) {
|
|
589
|
+
if (typeof this.storage.cleanExpiredRuns === "function") {
|
|
590
|
+
return this.storage.cleanExpiredRuns(policy);
|
|
591
|
+
}
|
|
592
|
+
return 0;
|
|
593
|
+
}
|
|
586
594
|
async listStateEntries(options) {
|
|
587
595
|
if (typeof this.storage.listStateEntries === "function") {
|
|
588
596
|
return this.storage.listStateEntries(options);
|
package/dist/package/types.d.ts
CHANGED
|
@@ -268,7 +268,11 @@ export interface PackageRuntime {
|
|
|
268
268
|
clearRuns(options?: {
|
|
269
269
|
actionId?: string;
|
|
270
270
|
status?: string;
|
|
271
|
+
olderThanMs?: number;
|
|
272
|
+
keep?: number;
|
|
271
273
|
}): Promise<number>;
|
|
274
|
+
/** 按保留策略清理过期及超额的终态运行记录(基于时间与数量策略) */
|
|
275
|
+
cleanExpiredRuns?(policy?: import("../storage/types.js").RunsRetentionPolicy): Promise<number>;
|
|
272
276
|
/** 订阅指定运行的事件流 */
|
|
273
277
|
events(runId: string, options?: {
|
|
274
278
|
after?: number | string;
|
package/dist/platform/node.js
CHANGED
package/dist/platform/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ProcessAPI } from "@actiondock/sdk";
|
|
2
2
|
import type { Clock } from "../runtime/clock.js";
|
|
3
3
|
import type { ModuleLoader } from "../runtime/module-loader.js";
|
|
4
|
-
import type { RuntimeStorage } from "../storage/types.js";
|
|
4
|
+
import type { RunsRetentionPolicy, RuntimeStorage } from "../storage/types.js";
|
|
5
5
|
/**
|
|
6
6
|
* 文件元数据信息契约。
|
|
7
7
|
*/
|
|
@@ -58,6 +58,8 @@ export interface StorageFactoryOptions {
|
|
|
58
58
|
* 默认 false(旁观查询打开,不触碰在途记录),供 CLI 查询命令与执行宿主并发共存。
|
|
59
59
|
*/
|
|
60
60
|
recoverOrphans?: boolean;
|
|
61
|
+
/** 运行记录保留策略配置 */
|
|
62
|
+
retentionPolicy?: RunsRetentionPolicy;
|
|
61
63
|
}
|
|
62
64
|
/**
|
|
63
65
|
* 跨运行时全局存储工厂配置选项。
|
|
@@ -30,13 +30,22 @@ export declare function fetchRemoteRuns(serverUrl: string, token?: string, optio
|
|
|
30
30
|
items: RunRecord[];
|
|
31
31
|
}>;
|
|
32
32
|
/**
|
|
33
|
-
* 清空远端运行记录(可按包、Action
|
|
33
|
+
* 清空远端运行记录(可按包、Action、状态、保留时长、保留数量过滤清除范围)。
|
|
34
34
|
*/
|
|
35
35
|
export declare function clearRemoteRuns(serverUrl: string, token?: string, options?: {
|
|
36
36
|
packageId?: string;
|
|
37
37
|
actionId?: string;
|
|
38
38
|
status?: string;
|
|
39
|
+
olderThanMs?: number;
|
|
40
|
+
keep?: number;
|
|
39
41
|
} & RemoteClientRequestOptions): Promise<{
|
|
40
42
|
ok: boolean;
|
|
41
43
|
clearedCount: number;
|
|
42
44
|
}>;
|
|
45
|
+
/**
|
|
46
|
+
* 按保留策略清理远端过期及超额运行记录。
|
|
47
|
+
*/
|
|
48
|
+
export declare function cleanExpiredRemoteRuns(serverUrl: string, token?: string, options?: import("../storage/types.js").RunsRetentionPolicy & RemoteClientRequestOptions): Promise<{
|
|
49
|
+
ok: boolean;
|
|
50
|
+
cleanedCount: number;
|
|
51
|
+
}>;
|
|
@@ -44,7 +44,7 @@ export async function fetchRemoteRuns(serverUrl, token, options) {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
* 清空远端运行记录(可按包、Action
|
|
47
|
+
* 清空远端运行记录(可按包、Action、状态、保留时长、保留数量过滤清除范围)。
|
|
48
48
|
*/
|
|
49
49
|
export async function clearRemoteRuns(serverUrl, token, options) {
|
|
50
50
|
return fetchRemoteJson(serverUrl, "/api/v2/runs/clear", token, {
|
|
@@ -56,3 +56,16 @@ export async function clearRemoteRuns(serverUrl, token, options) {
|
|
|
56
56
|
dispatcher: options?.dispatcher,
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* 按保留策略清理远端过期及超额运行记录。
|
|
61
|
+
*/
|
|
62
|
+
export async function cleanExpiredRemoteRuns(serverUrl, token, options) {
|
|
63
|
+
return fetchRemoteJson(serverUrl, "/api/v2/runs/clean-expired", token, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
body: options || {},
|
|
66
|
+
errorPrefix: "Failed to clean expired remote runs",
|
|
67
|
+
allowInsecureHttp: options?.allowInsecureHttp,
|
|
68
|
+
insecure: options?.insecure,
|
|
69
|
+
dispatcher: options?.dispatcher,
|
|
70
|
+
});
|
|
71
|
+
}
|
package/dist/profile/client.d.ts
CHANGED
|
@@ -21,6 +21,6 @@ export { checkRemoteHealth } from "./client-health.js";
|
|
|
21
21
|
export { executeRemoteAction, fetchRemoteActionShow, fetchRemoteActions, fetchRemoteDoctor, fetchRemoteInfo, } from "./client-actions.js";
|
|
22
22
|
export type { RemoteExecuteOptions, RemoteExecutionResult } from "./client-actions.js";
|
|
23
23
|
export { fetchRemotePlaybookShow, fetchRemotePlaybooks } from "./client-playbooks.js";
|
|
24
|
-
export { cancelRemoteRun, clearRemoteRuns, fetchRemoteRun, fetchRemoteRuns, } from "./client-runs.js";
|
|
24
|
+
export { cancelRemoteRun, clearRemoteRuns, cleanExpiredRemoteRuns, fetchRemoteRun, fetchRemoteRuns, } from "./client-runs.js";
|
|
25
25
|
export { clearRemoteState, deleteRemoteStateKey, fetchRemoteStateList, getRemoteStateKey, setRemoteStateKey, } from "./client-state.js";
|
|
26
26
|
export { deleteRemoteConfig, fetchRemoteConfig, fetchRemoteConfigEnv, setRemoteConfig, } from "./client-config.js";
|
package/dist/profile/client.js
CHANGED
|
@@ -24,7 +24,7 @@ export { executeRemoteAction, fetchRemoteActionShow, fetchRemoteActions, fetchRe
|
|
|
24
24
|
// Playbook 规程查询
|
|
25
25
|
export { fetchRemotePlaybookShow, fetchRemotePlaybooks } from "./client-playbooks.js";
|
|
26
26
|
// 运行记录管理
|
|
27
|
-
export { cancelRemoteRun, clearRemoteRuns, fetchRemoteRun, fetchRemoteRuns, } from "./client-runs.js";
|
|
27
|
+
export { cancelRemoteRun, clearRemoteRuns, cleanExpiredRemoteRuns, fetchRemoteRun, fetchRemoteRuns, } from "./client-runs.js";
|
|
28
28
|
// 状态存储管理
|
|
29
29
|
export { clearRemoteState, deleteRemoteStateKey, fetchRemoteStateList, getRemoteStateKey, setRemoteStateKey, } from "./client-state.js";
|
|
30
30
|
// 配置管理
|
package/dist/project/init.js
CHANGED
|
@@ -86,10 +86,10 @@ export function initProject(targetDir, options = {}) {
|
|
|
86
86
|
node: ">=24.12.0",
|
|
87
87
|
},
|
|
88
88
|
dependencies: {
|
|
89
|
-
"@actiondock/sdk": "^2.9.
|
|
89
|
+
"@actiondock/sdk": "^2.9.1",
|
|
90
90
|
},
|
|
91
91
|
devDependencies: {
|
|
92
|
-
"@actiondock/testing": "^2.9.
|
|
92
|
+
"@actiondock/testing": "^2.9.1",
|
|
93
93
|
"@types/node": "^22.13.0",
|
|
94
94
|
"tsx": "^4.19.0",
|
|
95
95
|
"typescript": "^5.7.0",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
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";
|
|
2
2
|
import { isTerminalRunStatus } from "../../storage/types.js";
|
|
3
|
+
import { parseDuration } from "../../utils/index.js";
|
|
3
4
|
import { readJsonBody } from "../body.js";
|
|
4
5
|
import { getSubPath, isActionAllowedByPolicy, isPackageAllowedByPolicy, jsonResponse } from "./common.js";
|
|
5
6
|
/**
|
|
@@ -69,6 +70,31 @@ export async function handleRunsRoutes(ctx) {
|
|
|
69
70
|
const packageId = url.searchParams.get("packageId") || body.packageId || undefined;
|
|
70
71
|
const actionId = url.searchParams.get("actionId") || body.actionId || undefined;
|
|
71
72
|
const status = url.searchParams.get("status") || body.status || undefined;
|
|
73
|
+
const rawOlderThan = url.searchParams.get("olderThan") ||
|
|
74
|
+
url.searchParams.get("olderThanMs") ||
|
|
75
|
+
body.olderThan ||
|
|
76
|
+
body.olderThanMs;
|
|
77
|
+
let olderThanMs;
|
|
78
|
+
if (typeof rawOlderThan === "number" && rawOlderThan > 0) {
|
|
79
|
+
olderThanMs = rawOlderThan;
|
|
80
|
+
}
|
|
81
|
+
else if (typeof rawOlderThan === "string" && rawOlderThan.trim()) {
|
|
82
|
+
try {
|
|
83
|
+
olderThanMs = parseDuration(rawOlderThan);
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
}
|
|
87
|
+
const rawKeep = url.searchParams.get("keep") || body.keep;
|
|
88
|
+
let keep;
|
|
89
|
+
if (typeof rawKeep === "number" && rawKeep >= 0) {
|
|
90
|
+
keep = rawKeep;
|
|
91
|
+
}
|
|
92
|
+
else if (typeof rawKeep === "string" && rawKeep.trim()) {
|
|
93
|
+
const parsed = parseInt(rawKeep.trim(), 10);
|
|
94
|
+
if (!isNaN(parsed) && parsed >= 0) {
|
|
95
|
+
keep = parsed;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
72
98
|
if (packageId && !isPackageAllowedByPolicy(packageId, policy)) {
|
|
73
99
|
return jsonResponse({
|
|
74
100
|
ok: false,
|
|
@@ -80,7 +106,13 @@ export async function handleRunsRoutes(ctx) {
|
|
|
80
106
|
}
|
|
81
107
|
let clearedCount = 0;
|
|
82
108
|
if (service.runs.clear) {
|
|
83
|
-
clearedCount = await service.runs.clear({
|
|
109
|
+
clearedCount = await service.runs.clear({
|
|
110
|
+
packageId,
|
|
111
|
+
actionId,
|
|
112
|
+
status,
|
|
113
|
+
olderThanMs,
|
|
114
|
+
keep,
|
|
115
|
+
});
|
|
84
116
|
}
|
|
85
117
|
return jsonResponse({ ok: true, clearedCount }, 200, corsHeaders);
|
|
86
118
|
}
|
|
@@ -88,6 +120,23 @@ export async function handleRunsRoutes(ctx) {
|
|
|
88
120
|
return jsonResponse({ ok: false, error: { code: RUNS_CLEAR_ERROR, message: err.message } }, 500, corsHeaders);
|
|
89
121
|
}
|
|
90
122
|
}
|
|
123
|
+
// 2.1 Runs Clean Expired: POST /api/v2/runs/clean-expired, POST /runs/clean-expired
|
|
124
|
+
if (subpath === "/runs/clean-expired" && req.method === "POST") {
|
|
125
|
+
try {
|
|
126
|
+
let body = {};
|
|
127
|
+
if (req.headers.get("content-type")?.includes("json")) {
|
|
128
|
+
body = await readJsonBody(req, { maxBytes: options.maxBodyBytes }).catch(() => ({}));
|
|
129
|
+
}
|
|
130
|
+
let cleanedCount = 0;
|
|
131
|
+
if (service.runs.cleanExpired) {
|
|
132
|
+
cleanedCount = await service.runs.cleanExpired(body);
|
|
133
|
+
}
|
|
134
|
+
return jsonResponse({ ok: true, cleanedCount }, 200, corsHeaders);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
return jsonResponse({ ok: false, error: { code: RUNS_CLEAR_ERROR, message: err.message } }, 500, corsHeaders);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
91
140
|
// 3. Run Events SSE: GET /api/v2/runs/:runId/events (and stream alias)
|
|
92
141
|
const runEventsMatch = subpath.match(/^\/runs\/([^/]+)\/(events|stream)$/);
|
|
93
142
|
if (runEventsMatch && req.method === "GET") {
|
package/dist/server/server.js
CHANGED
|
@@ -340,6 +340,17 @@ export async function startActionDockServer(options = {}) {
|
|
|
340
340
|
}
|
|
341
341
|
const actualHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
342
342
|
const protocol = options.tls ? "https" : "http";
|
|
343
|
+
// 启动后台运行记录定期自动清理巡检定时器(默认 1 小时巡检一次)
|
|
344
|
+
const runsCleanupIntervalMs = 3600_000;
|
|
345
|
+
let runsCleanupTimer;
|
|
346
|
+
if (serviceInstance.runs?.cleanExpired) {
|
|
347
|
+
runsCleanupTimer = setInterval(() => {
|
|
348
|
+
serviceInstance?.runs?.cleanExpired?.().catch((err) => {
|
|
349
|
+
console.warn(`[ActionDockServer] Periodic runs cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
350
|
+
});
|
|
351
|
+
}, runsCleanupIntervalMs);
|
|
352
|
+
runsCleanupTimer.unref();
|
|
353
|
+
}
|
|
343
354
|
const instance = {
|
|
344
355
|
get port() {
|
|
345
356
|
return server.port ?? port;
|
|
@@ -353,6 +364,10 @@ export async function startActionDockServer(options = {}) {
|
|
|
353
364
|
},
|
|
354
365
|
ready: Promise.resolve(),
|
|
355
366
|
stop: async (stopOptions) => {
|
|
367
|
+
if (runsCleanupTimer) {
|
|
368
|
+
clearInterval(runsCleanupTimer);
|
|
369
|
+
runsCleanupTimer = undefined;
|
|
370
|
+
}
|
|
356
371
|
await server.stop(true);
|
|
357
372
|
if (serviceInstance) {
|
|
358
373
|
try {
|
package/dist/service/local.js
CHANGED
package/dist/service/remote.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseActionRef } from "../catalog/resolve-action.js";
|
|
2
2
|
import { ActionDockError, ACTION_CANCELLED, CAPABILITY_UNAVAILABLE, INVALID_ARGUMENT, TIMEOUT, } from "../errors.js";
|
|
3
|
-
import { cancelRemoteRun, clearRemoteRuns, clearRemoteState, deleteRemoteConfig, deleteRemoteStateKey, executeRemoteAction, fetchRemoteActionShow, fetchRemoteActions, fetchRemoteConfig, fetchRemoteInfo, fetchRemotePlaybookShow, fetchRemotePlaybooks, fetchRemoteRun, fetchRemoteRuns, fetchRemoteStateList, getRemoteStateKey, setRemoteConfig, setRemoteStateKey, } from "../profile/client.js";
|
|
3
|
+
import { cancelRemoteRun, clearRemoteRuns, cleanExpiredRemoteRuns, clearRemoteState, deleteRemoteConfig, deleteRemoteStateKey, executeRemoteAction, fetchRemoteActionShow, fetchRemoteActions, fetchRemoteConfig, fetchRemoteInfo, fetchRemotePlaybookShow, fetchRemotePlaybooks, fetchRemoteRun, fetchRemoteRuns, fetchRemoteStateList, getRemoteStateKey, setRemoteConfig, setRemoteStateKey, } from "../profile/client.js";
|
|
4
4
|
import { isRemoteStateKeyNotFound, wrapRemoteError } from "./remote-errors.js";
|
|
5
5
|
import { formatTerminalRunResult, pollRunCompletion } from "./remote-polling.js";
|
|
6
6
|
import { streamRemoteEvents } from "./sse-stream.js";
|
|
@@ -306,6 +306,21 @@ export class RemoteActionDockService {
|
|
|
306
306
|
wrapRemoteError(err);
|
|
307
307
|
}
|
|
308
308
|
},
|
|
309
|
+
async cleanExpired(policy) {
|
|
310
|
+
self.assertNotClosed();
|
|
311
|
+
try {
|
|
312
|
+
const res = await cleanExpiredRemoteRuns(self.serverUrl, self.token, {
|
|
313
|
+
...policy,
|
|
314
|
+
allowInsecureHttp: self.allowInsecureHttp,
|
|
315
|
+
insecure: self.insecure,
|
|
316
|
+
dispatcher: self.dispatcher,
|
|
317
|
+
});
|
|
318
|
+
return res.cleanedCount ?? 0;
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
wrapRemoteError(err);
|
|
322
|
+
}
|
|
323
|
+
},
|
|
309
324
|
};
|
|
310
325
|
this.events = {
|
|
311
326
|
events(runId, opts) {
|
package/dist/service/types.d.ts
CHANGED
|
@@ -170,7 +170,11 @@ export interface RunsPort {
|
|
|
170
170
|
packageId?: string;
|
|
171
171
|
actionId?: string;
|
|
172
172
|
status?: string;
|
|
173
|
+
olderThanMs?: number;
|
|
174
|
+
keep?: number;
|
|
173
175
|
}): Promise<number>;
|
|
176
|
+
/** 按保留策略清理过期及超额的终态运行记录(基于时间与数量策略) */
|
|
177
|
+
cleanExpired?(policy?: import("../storage/types.js").RunsRetentionPolicy): Promise<number>;
|
|
174
178
|
}
|
|
175
179
|
/**
|
|
176
180
|
* 配置项管理服务端口。
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export declare function createStorage(packageId: string, options?: {
|
|
|
42
42
|
inMemory?: boolean;
|
|
43
43
|
customHome?: string;
|
|
44
44
|
recoverOrphans?: boolean;
|
|
45
|
+
retentionPolicy?: import("./types.js").RunsRetentionPolicy;
|
|
45
46
|
}): RuntimeStorage;
|
|
46
47
|
/**
|
|
47
48
|
* 解析全局共享数据库(global.db)文件的绝对路径。
|
package/dist/storage/index.js
CHANGED
|
@@ -59,7 +59,12 @@ export function resolveDatabasePath(packageId, options = {}) {
|
|
|
59
59
|
*/
|
|
60
60
|
export function createStorage(packageId, options = {}) {
|
|
61
61
|
const dbPath = resolveDatabasePath(packageId, options);
|
|
62
|
-
return new SqliteRuntimeStorage({
|
|
62
|
+
return new SqliteRuntimeStorage({
|
|
63
|
+
dbPath,
|
|
64
|
+
packageId,
|
|
65
|
+
recoverOrphans: options.recoverOrphans,
|
|
66
|
+
retentionPolicy: options.retentionPolicy,
|
|
67
|
+
});
|
|
63
68
|
}
|
|
64
69
|
/**
|
|
65
70
|
* 解析全局共享数据库(global.db)文件的绝对路径。
|
package/dist/storage/sqlite.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { decodeStateKey, encodeStateKey, escapeStateSegment, unescapeStateSegment, type RuntimeError, type RunRecord } from "@actiondock/sdk";
|
|
2
|
-
import { type IdempotencyCheckResult, type IdempotencyRecord, type RuntimeStorage, type StateEntry, type StorageOptions, type TerminalRunStatus } from "./types.js";
|
|
2
|
+
import { type IdempotencyCheckResult, type IdempotencyRecord, type RunsRetentionPolicy, type RuntimeStorage, type StateEntry, type StorageOptions, type TerminalRunStatus } from "./types.js";
|
|
3
3
|
export { decodeStateKey, encodeStateKey, escapeStateSegment, unescapeStateSegment, };
|
|
4
4
|
/**
|
|
5
5
|
* 统一 SQLite 运行时存储实现。
|
|
@@ -25,6 +25,11 @@ export declare class SqliteRuntimeStorage implements RuntimeStorage {
|
|
|
25
25
|
private statementCache;
|
|
26
26
|
private dbPath;
|
|
27
27
|
private recoverOrphans;
|
|
28
|
+
private retentionPolicy?;
|
|
29
|
+
private lastRunsCleanupAt;
|
|
30
|
+
private runInsertCount;
|
|
31
|
+
private static readonly RUNS_CLEANUP_INTERVAL_MS;
|
|
32
|
+
private static readonly RUNS_CLEANUP_BATCH_THRESHOLD;
|
|
28
33
|
get isOpen(): boolean;
|
|
29
34
|
get closed(): boolean;
|
|
30
35
|
constructor(options: StorageOptions);
|
|
@@ -84,7 +89,19 @@ export declare class SqliteRuntimeStorage implements RuntimeStorage {
|
|
|
84
89
|
clearRuns(options?: {
|
|
85
90
|
actionId?: string;
|
|
86
91
|
status?: string;
|
|
92
|
+
olderThanMs?: number;
|
|
93
|
+
keep?: number;
|
|
87
94
|
}): number;
|
|
95
|
+
/**
|
|
96
|
+
* 按保留策略清理过期的终态运行记录与超出容量限制的最旧运行记录。
|
|
97
|
+
*
|
|
98
|
+
* 包含:
|
|
99
|
+
* - 基于时间:删除早于 maxAgeMs 的终态记录(保留 minRetainRuns 保底数量)。
|
|
100
|
+
* - 基于数量:若终态记录总数超过 maxRuns,按时间由旧至新淘汰超出部分。
|
|
101
|
+
* - 级联清理孤立的幂等去重记录。
|
|
102
|
+
*/
|
|
103
|
+
cleanExpiredRuns(policy?: RunsRetentionPolicy): number;
|
|
104
|
+
private resolveRetentionPolicy;
|
|
88
105
|
checkAndRecordIdempotency(record: IdempotencyRecord): IdempotencyCheckResult;
|
|
89
106
|
getIdempotencyRecord(ownerId: string, actionRef: string, requestId: string): IdempotencyRecord | undefined;
|
|
90
107
|
private mapRunRecord;
|
package/dist/storage/sqlite.js
CHANGED
|
@@ -5,7 +5,7 @@ import { SystemClock } from "./clock.js";
|
|
|
5
5
|
import { createDefaultSqliteDriver } from "./driver.js";
|
|
6
6
|
import { safeParseStoredJson } from "./utils.js";
|
|
7
7
|
import { ActionDockError, AMBIGUOUS_STATE_KEY, STORED_ERROR_DECODE_FAILED, UNSUPPORTED_STORAGE_SCHEMA } from "../errors.js";
|
|
8
|
-
import { IDEMPOTENCY_RETENTION_MS, STORAGE_SCHEMA_VERSION, isTerminalRunStatus, } from "./types.js";
|
|
8
|
+
import { DEFAULT_MAX_RUNS_PER_PACKAGE, DEFAULT_MIN_RETAIN_RUNS, DEFAULT_RUNS_RETENTION_MS, IDEMPOTENCY_RETENTION_MS, STORAGE_SCHEMA_VERSION, isTerminalRunStatus, } from "./types.js";
|
|
9
9
|
export { decodeStateKey, encodeStateKey, escapeStateSegment, unescapeStateSegment, };
|
|
10
10
|
/**
|
|
11
11
|
* 统一 SQLite 运行时存储实现。
|
|
@@ -31,6 +31,11 @@ export class SqliteRuntimeStorage {
|
|
|
31
31
|
statementCache = new Map();
|
|
32
32
|
dbPath;
|
|
33
33
|
recoverOrphans;
|
|
34
|
+
retentionPolicy;
|
|
35
|
+
lastRunsCleanupAt = 0;
|
|
36
|
+
runInsertCount = 0;
|
|
37
|
+
static RUNS_CLEANUP_INTERVAL_MS = 3600_000;
|
|
38
|
+
static RUNS_CLEANUP_BATCH_THRESHOLD = 50;
|
|
34
39
|
get isOpen() {
|
|
35
40
|
return !this.isClosed;
|
|
36
41
|
}
|
|
@@ -43,6 +48,7 @@ export class SqliteRuntimeStorage {
|
|
|
43
48
|
const dbPath = options.dbPath || ":memory:";
|
|
44
49
|
this.dbPath = dbPath;
|
|
45
50
|
this.recoverOrphans = options.recoverOrphans === true;
|
|
51
|
+
this.retentionPolicy = options.retentionPolicy;
|
|
46
52
|
if (dbPath !== ":memory:") {
|
|
47
53
|
const dir = dirname(dbPath);
|
|
48
54
|
if (!existsSync(dir)) {
|
|
@@ -162,10 +168,11 @@ export class SqliteRuntimeStorage {
|
|
|
162
168
|
// 忽略非结构化表或兼容驱动异常
|
|
163
169
|
}
|
|
164
170
|
}
|
|
165
|
-
//
|
|
171
|
+
// 重启恢复:仅持有者身份的打开方在构造阶段收割死亡会话遗留的非终态运行并执行历史记录清理;
|
|
166
172
|
// 旁观查询打开保持只读语义,绝不触碰其他进程的在途记录
|
|
167
173
|
if (this.recoverOrphans) {
|
|
168
174
|
this.recoverDeadSessionRuns();
|
|
175
|
+
this.cleanExpiredRuns();
|
|
169
176
|
}
|
|
170
177
|
}
|
|
171
178
|
/**
|
|
@@ -504,6 +511,19 @@ export class SqliteRuntimeStorage {
|
|
|
504
511
|
const finishedAt = record.finishedAt || null;
|
|
505
512
|
const durationMs = typeof record.durationMs === "number" ? record.durationMs : null;
|
|
506
513
|
stmt.run(record.id, rootRunId, parentRunId, record.packageId || this.packageId, packageInstanceId, record.actionId ?? "", generationId, ownerId, hostSessionId, record.status, inputJson, outputJson, errorJson, record.startedAt || new Date().toISOString(), finishedAt, durationMs);
|
|
514
|
+
this.runInsertCount++;
|
|
515
|
+
const nowMs = this.clock.now().getTime();
|
|
516
|
+
if (this.runInsertCount >= SqliteRuntimeStorage.RUNS_CLEANUP_BATCH_THRESHOLD &&
|
|
517
|
+
nowMs - this.lastRunsCleanupAt >= SqliteRuntimeStorage.RUNS_CLEANUP_INTERVAL_MS) {
|
|
518
|
+
this.lastRunsCleanupAt = nowMs;
|
|
519
|
+
this.runInsertCount = 0;
|
|
520
|
+
try {
|
|
521
|
+
this.cleanExpiredRuns();
|
|
522
|
+
}
|
|
523
|
+
catch (cleanupErr) {
|
|
524
|
+
console.warn(`[actiondock] opportunistic cleanExpiredRuns failed: ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
507
527
|
}
|
|
508
528
|
updateRun(id, status, output, error, finishedAt) {
|
|
509
529
|
if (this.isClosed)
|
|
@@ -569,6 +589,27 @@ export class SqliteRuntimeStorage {
|
|
|
569
589
|
sql += " AND status = ?";
|
|
570
590
|
params.push(options.status);
|
|
571
591
|
}
|
|
592
|
+
if (typeof options.olderThanMs === "number" && options.olderThanMs > 0) {
|
|
593
|
+
const cutoff = new Date(this.clock.now().getTime() - options.olderThanMs).toISOString();
|
|
594
|
+
sql += " AND started_at < ?";
|
|
595
|
+
params.push(cutoff);
|
|
596
|
+
}
|
|
597
|
+
if (typeof options.keep === "number" && options.keep > 0) {
|
|
598
|
+
let subQuery = "SELECT id FROM runs WHERE package_id = ?";
|
|
599
|
+
const subParams = [this.packageId];
|
|
600
|
+
if (options.actionId) {
|
|
601
|
+
subQuery += " AND action_id = ?";
|
|
602
|
+
subParams.push(options.actionId);
|
|
603
|
+
}
|
|
604
|
+
if (options.status) {
|
|
605
|
+
subQuery += " AND status = ?";
|
|
606
|
+
subParams.push(options.status);
|
|
607
|
+
}
|
|
608
|
+
subQuery += " ORDER BY started_at DESC LIMIT ?";
|
|
609
|
+
subParams.push(options.keep);
|
|
610
|
+
sql += ` AND id NOT IN (${subQuery})`;
|
|
611
|
+
params.push(...subParams);
|
|
612
|
+
}
|
|
572
613
|
const stmt = this.getStatement(sql);
|
|
573
614
|
const res = stmt.run(...params);
|
|
574
615
|
// 运行记录清理后,级联清理已无对应运行的孤立去重索引记录
|
|
@@ -581,6 +622,122 @@ export class SqliteRuntimeStorage {
|
|
|
581
622
|
}
|
|
582
623
|
return res.changes;
|
|
583
624
|
}
|
|
625
|
+
/**
|
|
626
|
+
* 按保留策略清理过期的终态运行记录与超出容量限制的最旧运行记录。
|
|
627
|
+
*
|
|
628
|
+
* 包含:
|
|
629
|
+
* - 基于时间:删除早于 maxAgeMs 的终态记录(保留 minRetainRuns 保底数量)。
|
|
630
|
+
* - 基于数量:若终态记录总数超过 maxRuns,按时间由旧至新淘汰超出部分。
|
|
631
|
+
* - 级联清理孤立的幂等去重记录。
|
|
632
|
+
*/
|
|
633
|
+
cleanExpiredRuns(policy) {
|
|
634
|
+
if (this.isClosed)
|
|
635
|
+
return 0;
|
|
636
|
+
try {
|
|
637
|
+
const resolved = this.resolveRetentionPolicy(policy);
|
|
638
|
+
const { maxAgeMs, maxRuns, minRetainRuns } = resolved;
|
|
639
|
+
let totalDeleted = 0;
|
|
640
|
+
// 1. 基于时间策略清理(仅针对终态记录)
|
|
641
|
+
if (maxAgeMs > 0) {
|
|
642
|
+
const now = this.clock.now().getTime();
|
|
643
|
+
const cutoff = new Date(now - maxAgeMs).toISOString();
|
|
644
|
+
if (minRetainRuns > 0) {
|
|
645
|
+
const stmt = this.getStatement(`
|
|
646
|
+
DELETE FROM runs
|
|
647
|
+
WHERE package_id = ?
|
|
648
|
+
AND status IN ('success', 'failed', 'cancelled', 'timed_out', 'interrupted')
|
|
649
|
+
AND started_at < ?
|
|
650
|
+
AND id NOT IN (
|
|
651
|
+
SELECT id FROM runs
|
|
652
|
+
WHERE package_id = ?
|
|
653
|
+
AND status IN ('success', 'failed', 'cancelled', 'timed_out', 'interrupted')
|
|
654
|
+
ORDER BY started_at DESC
|
|
655
|
+
LIMIT ?
|
|
656
|
+
)
|
|
657
|
+
`);
|
|
658
|
+
const res = stmt.run(this.packageId, cutoff, this.packageId, minRetainRuns);
|
|
659
|
+
totalDeleted += res.changes;
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
const stmt = this.getStatement(`
|
|
663
|
+
DELETE FROM runs
|
|
664
|
+
WHERE package_id = ?
|
|
665
|
+
AND status IN ('success', 'failed', 'cancelled', 'timed_out', 'interrupted')
|
|
666
|
+
AND started_at < ?
|
|
667
|
+
`);
|
|
668
|
+
const res = stmt.run(this.packageId, cutoff);
|
|
669
|
+
totalDeleted += res.changes;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
// 2. 基于数量策略清理(若当前终态记录数超过 maxRuns,淘汰最旧的记录)
|
|
673
|
+
if (maxRuns > 0) {
|
|
674
|
+
const stmt = this.getStatement(`
|
|
675
|
+
DELETE FROM runs
|
|
676
|
+
WHERE package_id = ?
|
|
677
|
+
AND status IN ('success', 'failed', 'cancelled', 'timed_out', 'interrupted')
|
|
678
|
+
AND id NOT IN (
|
|
679
|
+
SELECT id FROM runs
|
|
680
|
+
WHERE package_id = ?
|
|
681
|
+
AND status IN ('success', 'failed', 'cancelled', 'timed_out', 'interrupted')
|
|
682
|
+
ORDER BY started_at DESC
|
|
683
|
+
LIMIT ?
|
|
684
|
+
)
|
|
685
|
+
`);
|
|
686
|
+
const res = stmt.run(this.packageId, this.packageId, maxRuns);
|
|
687
|
+
totalDeleted += res.changes;
|
|
688
|
+
}
|
|
689
|
+
// 3. 级联清理无对应运行记录的孤立幂等去重索引
|
|
690
|
+
if (totalDeleted > 0) {
|
|
691
|
+
try {
|
|
692
|
+
this.getStatement("DELETE FROM idempotency_keys WHERE run_id NOT IN (SELECT id FROM runs)").run();
|
|
693
|
+
}
|
|
694
|
+
catch (err) {
|
|
695
|
+
console.warn(`[actiondock] orphaned idempotency keys cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return totalDeleted;
|
|
699
|
+
}
|
|
700
|
+
catch (err) {
|
|
701
|
+
console.warn(`[actiondock] cleanExpiredRuns failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
702
|
+
return 0;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
resolveRetentionPolicy(policy) {
|
|
706
|
+
let configDays;
|
|
707
|
+
let configMaxRuns;
|
|
708
|
+
let configMinRetain;
|
|
709
|
+
try {
|
|
710
|
+
configDays = this.getConfig("runs.retentionDays");
|
|
711
|
+
configMaxRuns = this.getConfig("runs.maxRuns");
|
|
712
|
+
configMinRetain = this.getConfig("runs.minRetainRuns");
|
|
713
|
+
}
|
|
714
|
+
catch { }
|
|
715
|
+
const envDays = process.env.ACTIONDOCK_RUNS_RETENTION_DAYS
|
|
716
|
+
? parseInt(process.env.ACTIONDOCK_RUNS_RETENTION_DAYS, 10)
|
|
717
|
+
: undefined;
|
|
718
|
+
const envMaxRuns = process.env.ACTIONDOCK_RUNS_MAX_COUNT
|
|
719
|
+
? parseInt(process.env.ACTIONDOCK_RUNS_MAX_COUNT, 10)
|
|
720
|
+
: undefined;
|
|
721
|
+
const envMinRetain = process.env.ACTIONDOCK_RUNS_MIN_RETAIN
|
|
722
|
+
? parseInt(process.env.ACTIONDOCK_RUNS_MIN_RETAIN, 10)
|
|
723
|
+
: undefined;
|
|
724
|
+
const maxAgeMs = policy?.maxAgeMs ??
|
|
725
|
+
this.retentionPolicy?.maxAgeMs ??
|
|
726
|
+
(typeof configDays === "number" && configDays > 0 ? configDays * 86_400_000 : undefined) ??
|
|
727
|
+
(typeof envDays === "number" && !isNaN(envDays) && envDays > 0 ? envDays * 86_400_000 : undefined) ??
|
|
728
|
+
DEFAULT_RUNS_RETENTION_MS;
|
|
729
|
+
const maxRuns = policy?.maxRuns ??
|
|
730
|
+
this.retentionPolicy?.maxRuns ??
|
|
731
|
+
(typeof configMaxRuns === "number" && configMaxRuns > 0 ? configMaxRuns : undefined) ??
|
|
732
|
+
(typeof envMaxRuns === "number" && !isNaN(envMaxRuns) && envMaxRuns > 0 ? envMaxRuns : undefined) ??
|
|
733
|
+
DEFAULT_MAX_RUNS_PER_PACKAGE;
|
|
734
|
+
const minRetainRuns = policy?.minRetainRuns ??
|
|
735
|
+
this.retentionPolicy?.minRetainRuns ??
|
|
736
|
+
(typeof configMinRetain === "number" && configMinRetain >= 0 ? configMinRetain : undefined) ??
|
|
737
|
+
(typeof envMinRetain === "number" && !isNaN(envMinRetain) && envMinRetain >= 0 ? envMinRetain : undefined) ??
|
|
738
|
+
DEFAULT_MIN_RETAIN_RUNS;
|
|
739
|
+
return { maxAgeMs, maxRuns, minRetainRuns };
|
|
740
|
+
}
|
|
584
741
|
// --- Idempotency 幂等去重管理 ---
|
|
585
742
|
checkAndRecordIdempotency(record) {
|
|
586
743
|
if (this.isClosed) {
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -9,6 +9,29 @@ export declare const STORAGE_SCHEMA_VERSION = 2;
|
|
|
9
9
|
* 中的 idempotencyPolicy.retentionMs 保持单一事实源。
|
|
10
10
|
*/
|
|
11
11
|
export declare const IDEMPOTENCY_RETENTION_MS = 86400000;
|
|
12
|
+
/**
|
|
13
|
+
* 运行记录默认保留时长常量(14 天)。
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_RUNS_RETENTION_MS: number;
|
|
16
|
+
/**
|
|
17
|
+
* 运行记录单个包默认最大终态记录保留条数(5000 条)。
|
|
18
|
+
*/
|
|
19
|
+
export declare const DEFAULT_MAX_RUNS_PER_PACKAGE = 5000;
|
|
20
|
+
/**
|
|
21
|
+
* 运行记录时间过期清理默认保底保留条数(50 条)。
|
|
22
|
+
*/
|
|
23
|
+
export declare const DEFAULT_MIN_RETAIN_RUNS = 50;
|
|
24
|
+
/**
|
|
25
|
+
* 运行记录双重保留策略契约(基于时间与基于数量)。
|
|
26
|
+
*/
|
|
27
|
+
export interface RunsRetentionPolicy {
|
|
28
|
+
/** 最大保留时长(毫秒,默认 14 天) */
|
|
29
|
+
maxAgeMs?: number;
|
|
30
|
+
/** 单个包最大保留终态记录数(默认 5000,超出按最旧先淘汰) */
|
|
31
|
+
maxRuns?: number;
|
|
32
|
+
/** 最小保底保留终态记录数(默认 50,防止全清空) */
|
|
33
|
+
minRetainRuns?: number;
|
|
34
|
+
}
|
|
12
35
|
/**
|
|
13
36
|
* SQLite 基础参数值类型。
|
|
14
37
|
*/
|
|
@@ -95,6 +118,8 @@ export interface StorageOptions {
|
|
|
95
118
|
* 避免跨进程互毁在途运行。
|
|
96
119
|
*/
|
|
97
120
|
recoverOrphans?: boolean;
|
|
121
|
+
/** 运行记录保留策略配置 */
|
|
122
|
+
retentionPolicy?: RunsRetentionPolicy;
|
|
98
123
|
}
|
|
99
124
|
/**
|
|
100
125
|
* 数据库中存储的幂等请求去重记录实体。
|
|
@@ -179,7 +204,11 @@ export interface RuntimeStorage {
|
|
|
179
204
|
clearRuns(options?: {
|
|
180
205
|
actionId?: string;
|
|
181
206
|
status?: string;
|
|
207
|
+
olderThanMs?: number;
|
|
208
|
+
keep?: number;
|
|
182
209
|
}): number;
|
|
210
|
+
/** 按保留策略清理过期及超额的终态运行记录(基于时间与数量策略) */
|
|
211
|
+
cleanExpiredRuns?(policy?: RunsRetentionPolicy): number;
|
|
183
212
|
/**
|
|
184
213
|
* 收敛死亡会话遗留的非终态运行任务(含无会话标识的遗留非终态记录),
|
|
185
214
|
* 统一收敛为 interrupted。历史别名 recoverRunningRuns 已合并至本方法。
|
package/dist/storage/types.js
CHANGED
|
@@ -8,6 +8,18 @@ export const STORAGE_SCHEMA_VERSION = 2;
|
|
|
8
8
|
* 中的 idempotencyPolicy.retentionMs 保持单一事实源。
|
|
9
9
|
*/
|
|
10
10
|
export const IDEMPOTENCY_RETENTION_MS = 86_400_000;
|
|
11
|
+
/**
|
|
12
|
+
* 运行记录默认保留时长常量(14 天)。
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_RUNS_RETENTION_MS = 14 * 86_400_000;
|
|
15
|
+
/**
|
|
16
|
+
* 运行记录单个包默认最大终态记录保留条数(5000 条)。
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_MAX_RUNS_PER_PACKAGE = 5000;
|
|
19
|
+
/**
|
|
20
|
+
* 运行记录时间过期清理默认保底保留条数(50 条)。
|
|
21
|
+
*/
|
|
22
|
+
export const DEFAULT_MIN_RETAIN_RUNS = 50;
|
|
11
23
|
/**
|
|
12
24
|
* 判断指定运行状态是否为终态(收敛 success, failed, cancelled, timed_out, interrupted)。
|
|
13
25
|
*/
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actiondock/core",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"description": "ActionDock Core Engine - Project loader, runtime execution, SQLite storage, standalone builder, and skill exporter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"test": "node ../../scripts/run-tests.ts"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@actiondock/sdk": "^2.9.
|
|
69
|
+
"@actiondock/sdk": "^2.9.1",
|
|
70
70
|
"ajv": "^8.17.1",
|
|
71
71
|
"ajv-formats": "^3.0.1",
|
|
72
72
|
"undici": "^7.29.1"
|