@actiondock/core 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +50 -0
  2. package/package.json +51 -0
  3. package/src/build/builder.ts +205 -0
  4. package/src/build/index.ts +2 -0
  5. package/src/build/templates.ts +59 -0
  6. package/src/doctor/doctor.ts +332 -0
  7. package/src/doctor/index.ts +2 -0
  8. package/src/doctor/types.ts +25 -0
  9. package/src/export/index.ts +2 -0
  10. package/src/export/skill.ts +349 -0
  11. package/src/export/templates.ts +258 -0
  12. package/src/filter/index.ts +1 -0
  13. package/src/filter/intent.ts +154 -0
  14. package/src/index.ts +13 -0
  15. package/src/profile/client.ts +302 -0
  16. package/src/profile/index.ts +3 -0
  17. package/src/profile/manager.ts +341 -0
  18. package/src/profile/types.ts +71 -0
  19. package/src/project/index.ts +3 -0
  20. package/src/project/init.ts +194 -0
  21. package/src/project/loader.ts +382 -0
  22. package/src/project/types.ts +62 -0
  23. package/src/registry/index.ts +2 -0
  24. package/src/registry/registry.ts +703 -0
  25. package/src/registry/types.ts +127 -0
  26. package/src/runtime/context.ts +232 -0
  27. package/src/runtime/env.ts +172 -0
  28. package/src/runtime/execution-manager.ts +74 -0
  29. package/src/runtime/index.ts +5 -0
  30. package/src/runtime/runner.ts +368 -0
  31. package/src/runtime/standalone.ts +429 -0
  32. package/src/schema/validator.ts +61 -0
  33. package/src/server/body.ts +112 -0
  34. package/src/server/index.ts +6 -0
  35. package/src/server/runtime-registry.ts +80 -0
  36. package/src/server/security.ts +115 -0
  37. package/src/server/server.ts +572 -0
  38. package/src/server/types.ts +42 -0
  39. package/src/storage/index.ts +64 -0
  40. package/src/storage/mask.ts +34 -0
  41. package/src/storage/sqlite.ts +578 -0
  42. package/src/storage/types.ts +111 -0
  43. package/src/utils/index.ts +60 -0
@@ -0,0 +1,368 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type {
3
+ ActionContext,
4
+ ActionDefinition,
5
+ ExecutionResult,
6
+ RuntimeError,
7
+ RunRecord,
8
+ } from "@actiondock/sdk";
9
+ import type { ProjectConfig } from "../project/types";
10
+ import { validateSchema } from "../schema/validator";
11
+ import type { RuntimeStorage, TerminalRunStatus } from "../storage/types";
12
+ import { RuntimeConfig, RuntimeStateStore, StderrLogger } from "./context";
13
+
14
+ /**
15
+ * ActionRunner 初始化配置选项。
16
+ */
17
+ export interface RunnerOptions {
18
+ /** 运行所属的 Package ID */
19
+ packageId: string;
20
+ /** 持久化运行时存储实例(SQLite) */
21
+ storage: RuntimeStorage;
22
+ /** 项目元数据配置 */
23
+ projectConfig?: ProjectConfig;
24
+ /** CLI 或上层注入的临时配置覆盖项 */
25
+ configOverrides?: Record<string, unknown>;
26
+ /** 预加载的 Action 映射表 */
27
+ actions?: Map<string, ActionDefinition>;
28
+ }
29
+
30
+ /**
31
+ * 启动 Action 执行时的可选控制参数。
32
+ */
33
+ export interface ExecutionStartOptions {
34
+ /** 父级运行 ID(嵌套调用场景下建立调用链树) */
35
+ parentRunId?: string;
36
+ /** 调用栈数组(用于检测 A -> B -> A 环路死锁) */
37
+ callStack?: string[];
38
+ /** 外部传入的 AbortSignal 取消信号 */
39
+ signal?: AbortSignal;
40
+ /** 最大超时时间(毫秒),超时将自动中止执行并标记为 ACTION_TIMEOUT */
41
+ timeoutMs?: number;
42
+ }
43
+
44
+ /**
45
+ * 异步执行句柄,支持获取执行结果 Promise 与主动取消操作。
46
+ */
47
+ export interface ExecutionHandle {
48
+ /** 本次执行生成的全局唯一运行 ID */
49
+ runId: string;
50
+ /** 最终执行结果信封 Promise */
51
+ result: Promise<ExecutionResult>;
52
+ /**
53
+ * 取消当前正在执行的任务
54
+ * @param reason 取消原因
55
+ * @returns 是否成功触发取消
56
+ */
57
+ cancel(reason?: string): boolean;
58
+ }
59
+
60
+ /**
61
+ * ActionDock 核心执行引擎(ActionRunner)。
62
+ *
63
+ * 职责:
64
+ * 1. 负责 Action 执行的全生命周期管理(校验、隔离、跟踪、落库)。
65
+ * 2. 入参 (inputSchema) 与出参 (outputSchema) 的 JSON Schema 严格校验。
66
+ * 3. 嵌套 Action 相互调用的环路检测(Cycle Detection)。
67
+ * 4. 超时 (Timeout) 与中断信号 (AbortSignal) 竞态控制。
68
+ * 5. 自动记录并持久化 RunRecord 运行记录至 SQLite 存储。
69
+ */
70
+ export class ActionRunner {
71
+ private packageId: string;
72
+ private storage: RuntimeStorage;
73
+ private projectConfig?: ProjectConfig;
74
+ private configOverrides: Record<string, unknown>;
75
+ private actions: Map<string, ActionDefinition>;
76
+
77
+ constructor(options: RunnerOptions) {
78
+ this.packageId = options.packageId;
79
+ this.storage = options.storage;
80
+ this.projectConfig = options.projectConfig;
81
+ this.configOverrides = options.configOverrides || {};
82
+ this.actions = options.actions || new Map();
83
+ }
84
+
85
+ /**
86
+ * 注册单个 Action 到当前 Runner。
87
+ */
88
+ public registerAction(action: ActionDefinition): void {
89
+ this.actions.set(action.id, action);
90
+ }
91
+
92
+ /**
93
+ * 根据 ID 检索注册的 Action。
94
+ */
95
+ public getAction(id: string): ActionDefinition | undefined {
96
+ return this.actions.get(id);
97
+ }
98
+
99
+ /**
100
+ * 获取当前 Runner 已注册的所有 Action 列表。
101
+ */
102
+ public listActions(): ActionDefinition[] {
103
+ return Array.from(this.actions.values());
104
+ }
105
+
106
+ /**
107
+ * 异步启动 Action 的执行并立即返回 ExecutionHandle 句柄。
108
+ *
109
+ * @param actionOrId Action 定义对象或已注册的 Action ID
110
+ * @param input 传递给 Action 的输入数据
111
+ * @param options 执行控制选项(超时、取消信号、父运行 ID 等)
112
+ * @returns 包含 runId、result Promise 和 cancel 方法的执行句柄
113
+ */
114
+ start(
115
+ actionOrId: ActionDefinition | string,
116
+ input: unknown = {},
117
+ options: ExecutionStartOptions = {}
118
+ ): ExecutionHandle {
119
+ const runId = randomUUID();
120
+ const startedAt = new Date().toISOString();
121
+ const callStack = [...(options.callStack || [])];
122
+
123
+ let action: ActionDefinition;
124
+ if (typeof actionOrId === "string") {
125
+ const found = this.actions.get(actionOrId);
126
+ if (!found) {
127
+ const error: RuntimeError = {
128
+ code: "ACTION_NOT_FOUND",
129
+ message: `Action '${actionOrId}' not found in registry`,
130
+ };
131
+ return {
132
+ runId,
133
+ result: Promise.resolve({ ok: false, runId, error }),
134
+ cancel: () => false,
135
+ };
136
+ }
137
+ action = found;
138
+ } else {
139
+ action = actionOrId;
140
+ }
141
+
142
+ // 1. 环路死锁检测 (Cycle Detection)
143
+ if (callStack.includes(action.id)) {
144
+ const error: RuntimeError = {
145
+ code: "ACTION_CYCLE_DETECTED",
146
+ message: `Cycle detected in action invocation: ${callStack.join(" -> ")} -> ${action.id}`,
147
+ };
148
+ return {
149
+ runId,
150
+ result: Promise.resolve({ ok: false, runId, error }),
151
+ cancel: () => false,
152
+ };
153
+ }
154
+ callStack.push(action.id);
155
+
156
+ // 2. 输入参数 JSON Schema 校验
157
+ if (action.inputSchema) {
158
+ const val = validateSchema(action.inputSchema, input);
159
+ if (!val.valid) {
160
+ const error: RuntimeError = {
161
+ code: "INPUT_VALIDATION_FAILED",
162
+ message: `Input schema validation failed for action '${action.id}'`,
163
+ details: val.errors,
164
+ };
165
+ return {
166
+ runId,
167
+ result: Promise.resolve({ ok: false, runId, error }),
168
+ cancel: () => false,
169
+ };
170
+ }
171
+ }
172
+
173
+ // 3. 插入初始运行记录 (状态: running)
174
+ const initialRun: RunRecord = {
175
+ id: runId,
176
+ packageId: this.packageId,
177
+ actionId: action.id,
178
+ parentRunId: options.parentRunId,
179
+ status: "running",
180
+ input,
181
+ startedAt,
182
+ };
183
+ this.storage.createRun(initialRun);
184
+
185
+ // 4. 初始化 AbortController 与超时定时器
186
+ const controller = new AbortController();
187
+ if (options.signal) {
188
+ if (options.signal.aborted) {
189
+ controller.abort(options.signal.reason);
190
+ } else {
191
+ options.signal.addEventListener(
192
+ "abort",
193
+ () => controller.abort(options.signal?.reason),
194
+ { once: true }
195
+ );
196
+ }
197
+ }
198
+
199
+ let isTimeout = false;
200
+ let timeoutTimer: ReturnType<typeof setTimeout> | undefined;
201
+ if (typeof options.timeoutMs === "number" && options.timeoutMs > 0) {
202
+ timeoutTimer = setTimeout(() => {
203
+ isTimeout = true;
204
+ controller.abort(new Error(`Action exceeded timeout of ${options.timeoutMs}ms`));
205
+ }, options.timeoutMs);
206
+ }
207
+
208
+ let finalized = false;
209
+ const finalizeRun = (
210
+ status: TerminalRunStatus,
211
+ output?: unknown,
212
+ error?: RuntimeError
213
+ ) => {
214
+ if (timeoutTimer) {
215
+ clearTimeout(timeoutTimer);
216
+ timeoutTimer = undefined;
217
+ }
218
+ if (finalized) return;
219
+ finalized = true;
220
+ this.storage.updateRun(runId, status, output, error);
221
+ };
222
+
223
+ // 5. 构建 ActionContext 运行时上下文
224
+ const config = new RuntimeConfig(
225
+ this.storage,
226
+ this.configOverrides,
227
+ this.projectConfig
228
+ );
229
+ const state = new RuntimeStateStore(this.storage);
230
+ const log = new StderrLogger(action.id);
231
+
232
+ const invoker = {
233
+ invoke: async <I, O>(
234
+ childAction: ActionDefinition<I, O>,
235
+ childInput: I
236
+ ): Promise<O> => {
237
+ const childResult = await this.execute(childAction, childInput, {
238
+ parentRunId: runId,
239
+ callStack,
240
+ signal: controller.signal,
241
+ });
242
+ if (!childResult.ok) {
243
+ const err = new Error(childResult.error.message);
244
+ (err as any).code = childResult.error.code;
245
+ (err as any).details = childResult.error.details;
246
+ throw err;
247
+ }
248
+ return childResult.data as O;
249
+ },
250
+ };
251
+
252
+ const ctx: ActionContext = {
253
+ config,
254
+ state,
255
+ actions: invoker,
256
+ log,
257
+ signal: controller.signal,
258
+ };
259
+
260
+ // 6. 执行 Action 业务逻辑并与取消/超时信号进行竞态
261
+ const abortPromise = new Promise<never>((_, reject) => {
262
+ if (controller.signal.aborted) {
263
+ reject(controller.signal.reason || new Error("Action execution was cancelled"));
264
+ } else {
265
+ controller.signal.addEventListener(
266
+ "abort",
267
+ () => reject(controller.signal.reason || new Error("Action execution was cancelled")),
268
+ { once: true }
269
+ );
270
+ }
271
+ });
272
+
273
+ const executionPromise = (async (): Promise<ExecutionResult> => {
274
+ try {
275
+ const rawOutput = await Promise.race([
276
+ Promise.resolve().then(() => action.run(input, ctx)),
277
+ abortPromise,
278
+ ]);
279
+
280
+ // 输出结果 Schema 校验
281
+ if (action.outputSchema) {
282
+ const outVal = validateSchema(action.outputSchema, rawOutput);
283
+ if (!outVal.valid) {
284
+ const error: RuntimeError = {
285
+ code: "OUTPUT_VALIDATION_FAILED",
286
+ message: `Output schema validation failed for action '${action.id}'`,
287
+ details: outVal.errors,
288
+ };
289
+ finalizeRun("failed", undefined, error);
290
+ return { ok: false, runId, error };
291
+ }
292
+ }
293
+
294
+ finalizeRun("success", rawOutput);
295
+ return {
296
+ ok: true,
297
+ runId,
298
+ data: rawOutput,
299
+ };
300
+ } catch (err: any) {
301
+ if (isTimeout) {
302
+ const error: RuntimeError = {
303
+ code: "ACTION_TIMEOUT",
304
+ message: `Action exceeded timeout of ${options.timeoutMs}ms`,
305
+ };
306
+ finalizeRun("failed", undefined, error);
307
+ return { ok: false, runId, error };
308
+ }
309
+
310
+ if (controller.signal.aborted) {
311
+ const reason = controller.signal.reason;
312
+ const reasonMsg =
313
+ reason instanceof Error
314
+ ? reason.message
315
+ : typeof reason === "string"
316
+ ? reason
317
+ : undefined;
318
+ const error: RuntimeError = {
319
+ code: "ACTION_CANCELLED",
320
+ message: "Action execution was cancelled",
321
+ details: reasonMsg ? { reason: reasonMsg } : undefined,
322
+ };
323
+ finalizeRun("cancelled", undefined, error);
324
+ return { ok: false, runId, error };
325
+ }
326
+
327
+ const error: RuntimeError = {
328
+ code: err?.code || "ACTION_FAILED",
329
+ message: err?.message || String(err),
330
+ details: err?.details,
331
+ };
332
+ finalizeRun("failed", undefined, error);
333
+ return {
334
+ ok: false,
335
+ runId,
336
+ error,
337
+ };
338
+ }
339
+ })();
340
+
341
+ return {
342
+ runId,
343
+ result: executionPromise,
344
+ cancel: (reason?: string): boolean => {
345
+ if (finalized || controller.signal.aborted) {
346
+ return false;
347
+ }
348
+ controller.abort(new Error(reason || "Action execution was cancelled"));
349
+ return true;
350
+ },
351
+ };
352
+ }
353
+
354
+ /**
355
+ * 同步等待方式执行指定 Action,直接返回 ExecutionResult 信封结果。
356
+ *
357
+ * @param actionOrId Action 定义对象或 ID
358
+ * @param input 输入参数
359
+ * @param options 执行控制选项
360
+ */
361
+ async execute(
362
+ actionOrId: ActionDefinition | string,
363
+ input: unknown = {},
364
+ options: ExecutionStartOptions = {}
365
+ ): Promise<ExecutionResult> {
366
+ return this.start(actionOrId, input, options).result;
367
+ }
368
+ }