@actiondock/core 2.2.2 → 2.3.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 (46) hide show
  1. package/README.md +8 -3
  2. package/dist/app/app.d.ts +2 -0
  3. package/dist/app/app.js +6 -0
  4. package/dist/app/types.d.ts +12 -0
  5. package/dist/errors.d.ts +38 -0
  6. package/dist/errors.js +44 -0
  7. package/dist/execution/service.d.ts +2 -0
  8. package/dist/execution/service.js +13 -2
  9. package/dist/execution/types.d.ts +17 -0
  10. package/dist/host/host.js +46 -5
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.js +1 -0
  13. package/dist/ipc/host.d.ts +5 -0
  14. package/dist/ipc/host.js +43 -1
  15. package/dist/ipc/target.d.ts +18 -0
  16. package/dist/ipc/target.js +74 -6
  17. package/dist/ipc/types.d.ts +10 -1
  18. package/dist/platform/default.d.ts +7 -1
  19. package/dist/platform/default.js +40 -2
  20. package/dist/process/context-process.d.ts +83 -0
  21. package/dist/process/context-process.js +168 -0
  22. package/dist/process/cursor.d.ts +52 -0
  23. package/dist/process/cursor.js +144 -0
  24. package/dist/process/driver.d.ts +214 -0
  25. package/dist/process/driver.js +197 -0
  26. package/dist/process/index.d.ts +6 -0
  27. package/dist/process/index.js +6 -0
  28. package/dist/process/metadata-store.d.ts +205 -0
  29. package/dist/process/metadata-store.js +462 -0
  30. package/dist/process/output-log.d.ts +136 -0
  31. package/dist/process/output-log.js +331 -0
  32. package/dist/process/process-manager.d.ts +279 -0
  33. package/dist/process/process-manager.js +1879 -0
  34. package/dist/profile/client.js +63 -21
  35. package/dist/project/init.js +2 -2
  36. package/dist/registry/registry.js +12 -3
  37. package/dist/runtime/context.d.ts +9 -6
  38. package/dist/runtime/context.js +31 -8
  39. package/dist/runtime/process.d.ts +19 -2
  40. package/dist/runtime/process.js +103 -131
  41. package/dist/runtime/runner.d.ts +45 -24
  42. package/dist/runtime/runner.js +106 -25
  43. package/dist/target/remote.js +7 -0
  44. package/dist/version.d.ts +1 -1
  45. package/dist/version.js +1 -1
  46. package/package.json +2 -2
@@ -3,22 +3,45 @@ import type { ProjectConfig } from "../project/types.js";
3
3
  import type { RuntimeStorage } from "../storage/types.js";
4
4
  import type { Clock } from "./clock.js";
5
5
  import type { RuntimePlatform } from "../platform/types.js";
6
+ import type { ProcessOwner } from "../process/index.js";
6
7
  export { RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, MAX_SUBRUNS_REACHED, ACTION_CALL_CYCLE, ACTION_CYCLE_DETECTED, ACTION_MAX_DEPTH_EXCEEDED, } from "../errors.js";
7
8
  /**
8
9
  * 校验值是否为合法的 JSON 兼容结构,严禁 NaN、Infinity、循环引用及不可序列化类型。
10
+ *
11
+ * 环路检测采用「当前递归路径栈」语义:对象仅在自身子树遍历期间保持标记,
12
+ * 子树遍历完成后立即移除,从而正确放行共享子对象的有向无环结构(如 {a: shared, b: shared})。
9
13
  */
10
- export declare function validateJsonValue(val: unknown, seen?: WeakSet<object>): {
14
+ export declare function validateJsonValue(val: unknown, stack?: WeakSet<object>): {
11
15
  valid: true;
12
16
  } | {
13
17
  valid: false;
14
18
  reason: string;
15
19
  };
20
+ /**
21
+ * 跨包运行上下文解析结果契约。
22
+ */
23
+ export interface PackageContextResolution {
24
+ projectRoot?: string;
25
+ projectConfig?: ProjectConfig;
26
+ storage: RuntimeStorage;
27
+ actions?: Map<string, ActionDefinition>;
28
+ packageInstanceId?: string;
29
+ generationId?: string;
30
+ }
31
+ /**
32
+ * 跨包运行上下文解析委托函数契约。
33
+ */
34
+ export type PackageContextResolver = (packageId: string) => Promise<PackageContextResolution | undefined> | PackageContextResolution | undefined;
16
35
  /**
17
36
  * ActionRunner 初始化配置选项。
18
37
  */
19
38
  export interface RunnerOptions {
20
39
  /** 运行所属的 Package ID */
21
40
  packageId: string;
41
+ /** 包物理实例标识 */
42
+ packageInstanceId?: string;
43
+ /** 快照代次标识 */
44
+ generationId?: string;
22
45
  /** 持久化运行时存储实例(SQLite) */
23
46
  storage?: RuntimeStorage;
24
47
  /** 全局共享持久化存储实例(SQLite,用于单例池化避免泄漏) */
@@ -46,17 +69,7 @@ export interface RunnerOptions {
46
69
  /** 跨包存储工厂 */
47
70
  getStorageForPackage?: (packageId: string, projectRoot?: string) => RuntimeStorage;
48
71
  /** 跨包运行上下文解析委托函数 */
49
- packageContextResolver?: (packageId: string) => Promise<{
50
- projectRoot?: string;
51
- projectConfig?: ProjectConfig;
52
- storage: RuntimeStorage;
53
- actions?: Map<string, ActionDefinition>;
54
- } | undefined> | {
55
- projectRoot?: string;
56
- projectConfig?: ProjectConfig;
57
- storage: RuntimeStorage;
58
- actions?: Map<string, ActionDefinition>;
59
- } | undefined;
72
+ packageContextResolver?: PackageContextResolver;
60
73
  /** 自定义 ActionDock 用户家目录(用于测试隔离与多租户环境) */
61
74
  customHome?: string;
62
75
  /** 执行宿主会话标识 */
@@ -80,6 +93,12 @@ export interface ExecutionStartOptions {
80
93
  generationId?: string;
81
94
  /** 执行所有者标识 */
82
95
  ownerId?: string;
96
+ /** 租户标识 */
97
+ tenantId?: string;
98
+ /** 主体标识 */
99
+ principalId?: string;
100
+ /** 显式执行归属所有者契约 */
101
+ owner?: ProcessOwner;
83
102
  /** 执行宿主会话标识 */
84
103
  hostSessionId?: string;
85
104
  /** 调用栈数组(用于检测 A -> B -> A 环路死锁) */
@@ -142,7 +161,9 @@ export type ActionResolution = {
142
161
  * - 自动记录并持久化 RunRecord 运行记录至 SQLite 存储。
143
162
  */
144
163
  export declare class ActionRunner {
145
- private packageId;
164
+ readonly packageId: string;
165
+ readonly packageInstanceId: string;
166
+ readonly generationId: string;
146
167
  private storage;
147
168
  private globalStorage?;
148
169
  private projectRoot?;
@@ -156,6 +177,8 @@ export declare class ActionRunner {
156
177
  private maxSubRuns;
157
178
  private activeSubRuns;
158
179
  private packageRunners;
180
+ /** 跨包 Runner 构建中的 in-flight Promise:并发调用 await 同一构建任务,消除 check-then-act 竞态 */
181
+ private pendingPackageRunners;
159
182
  private actionResolver?;
160
183
  private getStorageForPackage?;
161
184
  private packageContextResolver?;
@@ -190,21 +213,19 @@ export declare class ActionRunner {
190
213
  /**
191
214
  * 注入或更新跨包运行上下文解析委托。
192
215
  */
193
- setPackageContextResolver(resolver: (packageId: string) => Promise<{
194
- projectRoot?: string;
195
- projectConfig?: ProjectConfig;
196
- storage: RuntimeStorage;
197
- actions?: Map<string, ActionDefinition>;
198
- } | undefined> | {
199
- projectRoot?: string;
200
- projectConfig?: ProjectConfig;
201
- storage: RuntimeStorage;
202
- actions?: Map<string, ActionDefinition>;
203
- } | undefined): void;
216
+ setPackageContextResolver(resolver: PackageContextResolver): void;
204
217
  /**
205
218
  * 跨包运行时解析与获取(确保跨包执行具备独立的配置、存储、状态与 Action 注册表)。
219
+ *
220
+ * 缓存 miss 后存在长异步窗口(加载配置、扫描 Action、打开存储),并发调用会重复构建
221
+ * Runner、重复打开 SQLite 连接且子任务限流计数分裂;故以 in-flight Promise 去重,
222
+ * 并发方 await 同一构建任务,构建失败时移除该 Promise 以便后续重试。
206
223
  */
207
224
  resolveTargetPackageRunner(targetPackageId: string): Promise<ActionRunner | undefined>;
225
+ /**
226
+ * 实际构建跨包 Runner(仅由 resolveTargetPackageRunner 串行调度)。
227
+ */
228
+ private buildTargetPackageRunner;
208
229
  /**
209
230
  * 异步启动 Action 的执行并立即返回 ExecutionHandle 句柄。
210
231
  *
@@ -1,5 +1,4 @@
1
1
  import { existsSync } from "node:fs";
2
- import { join } from "node:path";
3
2
  import { randomUUID } from "node:crypto";
4
3
  import { ActionResolver } from "../catalog/action-resolver.js";
5
4
  import { loadActions, loadProjectConfig } from "../project/loader.js";
@@ -11,8 +10,11 @@ import { createActionContext, StderrLogger } from "./context.js";
11
10
  export { RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, MAX_SUBRUNS_REACHED, ACTION_CALL_CYCLE, ACTION_CYCLE_DETECTED, ACTION_MAX_DEPTH_EXCEEDED, } from "../errors.js";
12
11
  /**
13
12
  * 校验值是否为合法的 JSON 兼容结构,严禁 NaN、Infinity、循环引用及不可序列化类型。
13
+ *
14
+ * 环路检测采用「当前递归路径栈」语义:对象仅在自身子树遍历期间保持标记,
15
+ * 子树遍历完成后立即移除,从而正确放行共享子对象的有向无环结构(如 {a: shared, b: shared})。
14
16
  */
15
- export function validateJsonValue(val, seen = new WeakSet()) {
17
+ export function validateJsonValue(val, stack = new WeakSet()) {
16
18
  if (val === null || typeof val === "boolean" || typeof val === "string") {
17
19
  return { valid: true };
18
20
  }
@@ -26,26 +28,32 @@ export function validateJsonValue(val, seen = new WeakSet()) {
26
28
  return { valid: false, reason: `Unsupported JSON type '${typeof val}'` };
27
29
  }
28
30
  if (typeof val === "object") {
29
- if (seen.has(val)) {
31
+ if (stack.has(val)) {
30
32
  return { valid: false, reason: "Circular reference detected in object structure" };
31
33
  }
32
- seen.add(val);
33
- if (Array.isArray(val)) {
34
- for (const item of val) {
35
- const res = validateJsonValue(item, seen);
36
- if (!res.valid)
37
- return res;
34
+ stack.add(val);
35
+ try {
36
+ if (Array.isArray(val)) {
37
+ for (const item of val) {
38
+ const res = validateJsonValue(item, stack);
39
+ if (!res.valid)
40
+ return res;
41
+ }
42
+ return { valid: true };
43
+ }
44
+ for (const v of Object.values(val)) {
45
+ if (v !== undefined) {
46
+ const res = validateJsonValue(v, stack);
47
+ if (!res.valid)
48
+ return res;
49
+ }
38
50
  }
39
51
  return { valid: true };
40
52
  }
41
- for (const v of Object.values(val)) {
42
- if (v !== undefined) {
43
- const res = validateJsonValue(v, seen);
44
- if (!res.valid)
45
- return res;
46
- }
53
+ finally {
54
+ // 无论正常返回还是提前返回,均须将当前对象移出路径栈,避免祖先对象被误判为环路
55
+ stack.delete(val);
47
56
  }
48
- return { valid: true };
49
57
  }
50
58
  return { valid: true };
51
59
  }
@@ -63,6 +71,8 @@ let anonymousRunnerActionCounter = 0;
63
71
  */
64
72
  export class ActionRunner {
65
73
  packageId;
74
+ packageInstanceId;
75
+ generationId;
66
76
  storage;
67
77
  globalStorage;
68
78
  projectRoot;
@@ -76,6 +86,8 @@ export class ActionRunner {
76
86
  maxSubRuns;
77
87
  activeSubRuns = 0;
78
88
  packageRunners = new Map();
89
+ /** 跨包 Runner 构建中的 in-flight Promise:并发调用 await 同一构建任务,消除 check-then-act 竞态 */
90
+ pendingPackageRunners = new Map();
79
91
  actionResolver;
80
92
  getStorageForPackage;
81
93
  packageContextResolver;
@@ -83,6 +95,8 @@ export class ActionRunner {
83
95
  hostSessionId;
84
96
  constructor(options) {
85
97
  this.packageId = options.packageId;
98
+ this.packageInstanceId = options.packageInstanceId || options.packageId;
99
+ this.generationId = options.generationId || "1";
86
100
  this.hostSessionId = options.hostSessionId;
87
101
  this.projectRoot = options.projectRoot;
88
102
  this.projectConfig = options.projectConfig;
@@ -272,16 +286,38 @@ export class ActionRunner {
272
286
  }
273
287
  /**
274
288
  * 跨包运行时解析与获取(确保跨包执行具备独立的配置、存储、状态与 Action 注册表)。
289
+ *
290
+ * 缓存 miss 后存在长异步窗口(加载配置、扫描 Action、打开存储),并发调用会重复构建
291
+ * Runner、重复打开 SQLite 连接且子任务限流计数分裂;故以 in-flight Promise 去重,
292
+ * 并发方 await 同一构建任务,构建失败时移除该 Promise 以便后续重试。
275
293
  */
276
294
  async resolveTargetPackageRunner(targetPackageId) {
277
- if (this.packageRunners.has(targetPackageId)) {
278
- return this.packageRunners.get(targetPackageId);
295
+ const cached = this.packageRunners.get(targetPackageId);
296
+ if (cached) {
297
+ return cached;
298
+ }
299
+ const inFlight = this.pendingPackageRunners.get(targetPackageId);
300
+ if (inFlight) {
301
+ return inFlight;
279
302
  }
303
+ const building = this.buildTargetPackageRunner(targetPackageId).finally(() => {
304
+ // 无论成败均移除 in-flight 记录:成功者已写入正式缓存,失败者允许重试
305
+ this.pendingPackageRunners.delete(targetPackageId);
306
+ });
307
+ this.pendingPackageRunners.set(targetPackageId, building);
308
+ return building;
309
+ }
310
+ /**
311
+ * 实际构建跨包 Runner(仅由 resolveTargetPackageRunner 串行调度)。
312
+ */
313
+ async buildTargetPackageRunner(targetPackageId) {
280
314
  if (this.packageContextResolver) {
281
315
  const resolved = await this.packageContextResolver(targetPackageId);
282
316
  if (resolved) {
283
317
  const runner = new ActionRunner({
284
318
  packageId: targetPackageId,
319
+ packageInstanceId: resolved.packageInstanceId || resolved.projectConfig?.packageInstanceId || targetPackageId,
320
+ generationId: resolved.generationId || resolved.projectConfig?.generationId || "1",
285
321
  storage: resolved.storage,
286
322
  globalStorage: this.globalStorage,
287
323
  projectRoot: resolved.projectRoot,
@@ -314,10 +350,9 @@ export class ActionRunner {
314
350
  });
315
351
  }
316
352
  else {
317
- const sqlitePath = config.storage?.sqlitePath || ".actiondock/storage.db";
318
- const dbPath = join(root, sqlitePath);
319
- const { SqliteRuntimeStorage } = await import("../storage/sqlite.js");
320
- storage = new SqliteRuntimeStorage({ dbPath, packageId: targetPackageId, clock: this.clock });
353
+ // 回退分支与主路径共用 createStorage 单一事实源,确保 run 记录落在统一解析的库文件
354
+ const { createStorage } = await import("../storage/index.js");
355
+ storage = createStorage(targetPackageId, { projectRoot: root, customHome: this.customHome });
321
356
  }
322
357
  const actionsMap = await loadActions(root, config.actionsDir, {
323
358
  autoInstall: false,
@@ -325,6 +360,8 @@ export class ActionRunner {
325
360
  });
326
361
  const runner = new ActionRunner({
327
362
  packageId: targetPackageId,
363
+ packageInstanceId: config.packageInstanceId || targetPackageId,
364
+ generationId: config.generationId || "1",
328
365
  storage,
329
366
  globalStorage: this.globalStorage,
330
367
  projectRoot: root,
@@ -578,9 +615,9 @@ export class ActionRunner {
578
615
  rootRunId: this.computeRootRunId(runCtx),
579
616
  parentRunId: options.parentRunId,
580
617
  packageId: targetPackageId,
581
- packageInstanceId: options.packageInstanceId || targetPackageId,
618
+ packageInstanceId: options.packageInstanceId || (this.packageId === targetPackageId ? this.packageInstanceId : targetPackageId),
582
619
  actionId: targetActionId,
583
- generationId: options.generationId || "1",
620
+ generationId: options.generationId || (this.packageId === targetPackageId ? this.generationId : "1"),
584
621
  ownerId: options.ownerId || "local",
585
622
  hostSessionId: options.hostSessionId || this.hostSessionId,
586
623
  status,
@@ -700,7 +737,13 @@ export class ActionRunner {
700
737
  */
701
738
  buildActionContext(args) {
702
739
  const { runCtx, controller, rootRunId } = args;
703
- const { options, targetActionId, effectiveProcess } = runCtx;
740
+ const { options, targetPackageId, targetActionId, effectiveProcess } = runCtx;
741
+ const effectiveOwner = options.owner || {
742
+ tenantId: options.tenantId || "default",
743
+ principalId: options.principalId || options.ownerId || "default",
744
+ packageInstanceId: options.packageInstanceId || (this.packageId === targetPackageId ? this.packageInstanceId : targetPackageId),
745
+ generationId: options.generationId || (this.packageId === targetPackageId ? this.generationId : "1"),
746
+ };
704
747
  return createActionContext({
705
748
  actionId: targetActionId,
706
749
  storage: this.storage,
@@ -712,6 +755,7 @@ export class ActionRunner {
712
755
  parentRunId: options.parentRunId,
713
756
  signal: controller.signal,
714
757
  process: effectiveProcess,
758
+ owner: effectiveOwner,
715
759
  progress: options.progress,
716
760
  logger: options.logger || new StderrLogger(targetActionId),
717
761
  onActionInvoke: (childAction, childInput, parentRunId) => this.invokeChildAction({
@@ -742,12 +786,31 @@ export class ActionRunner {
742
786
  childPackageId = parsed.packageId;
743
787
  }
744
788
  const childActionId = parsed.actionId;
789
+ const effectiveParentOwner = options.owner || {
790
+ tenantId: options.tenantId || "default",
791
+ principalId: options.principalId || options.ownerId || "default",
792
+ packageInstanceId: options.packageInstanceId || this.packageInstanceId || this.packageId,
793
+ generationId: options.generationId || this.generationId || "1",
794
+ };
795
+ const isSamePackage = !childPackageId || childPackageId === this.packageId;
745
796
  this.activeSubRuns++;
746
797
  try {
747
798
  const runnerToUse = await this.resolveChildRunner(childPackageId, childActionId);
748
799
  if (runnerToUse !== this) {
749
800
  this.assertDeclaredUses(runCtx.action, targetActionId, childPackageId, childActionId);
750
801
  }
802
+ const childPackageInstanceId = isSamePackage
803
+ ? effectiveParentOwner.packageInstanceId
804
+ : (runnerToUse !== this ? runnerToUse.packageInstanceId : childPackageId);
805
+ const childGenerationId = isSamePackage
806
+ ? effectiveParentOwner.generationId
807
+ : (runnerToUse !== this ? runnerToUse.generationId : "1");
808
+ const childOwner = {
809
+ tenantId: effectiveParentOwner.tenantId,
810
+ principalId: effectiveParentOwner.principalId,
811
+ packageInstanceId: childPackageInstanceId,
812
+ generationId: childGenerationId,
813
+ };
751
814
  const childResult = await runnerToUse.execute(childAction, childInput, {
752
815
  rootRunId,
753
816
  parentRunId,
@@ -759,6 +822,13 @@ export class ActionRunner {
759
822
  logger: options.logger,
760
823
  configOverrides: options.configOverrides,
761
824
  maxCallDepth: options.maxCallDepth ?? this.maxCallDepth,
825
+ tenantId: childOwner.tenantId,
826
+ principalId: childOwner.principalId,
827
+ ownerId: childOwner.principalId,
828
+ packageInstanceId: childOwner.packageInstanceId,
829
+ generationId: childOwner.generationId,
830
+ owner: childOwner,
831
+ hostSessionId: options.hostSessionId ?? this.hostSessionId,
762
832
  });
763
833
  if (!childResult.ok) {
764
834
  const err = new Error(childResult.error.message);
@@ -912,6 +982,17 @@ export class ActionRunner {
912
982
  catch (err) {
913
983
  return this.classifyExecutionError(err, runCtx, controller, finalizer);
914
984
  }
985
+ finally {
986
+ // 仅释放 run 级隔离实例;平台级共享 ContextProcessAPI 生命周期归平台所有,严禁在此误 dispose
987
+ if (ctx &&
988
+ ctx.process?.runScoped === true &&
989
+ typeof ctx.process.dispose === "function") {
990
+ try {
991
+ await ctx.process.dispose();
992
+ }
993
+ catch { }
994
+ }
995
+ }
915
996
  })();
916
997
  }
917
998
  /**
@@ -215,6 +215,13 @@ export async function* streamRemoteEvents(serverUrl, runId, token, options) {
215
215
  throw err;
216
216
  }
217
217
  finally {
218
+ // 防御性取消底层流:仅 releaseLock 会让连接保持挂起,造成连接泄漏
219
+ try {
220
+ await reader.cancel();
221
+ }
222
+ catch {
223
+ // 流已自然结束或已被取消时忽略次级异常
224
+ }
218
225
  try {
219
226
  reader.releaseLock();
220
227
  }
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * ActionDock 核心版本号单一事实源。
3
3
  */
4
- export declare const ACTIONDOCK_VERSION = "2.2.2";
4
+ export declare const ACTIONDOCK_VERSION = "2.3.0";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * ActionDock 核心版本号单一事实源。
3
3
  */
4
- export const ACTIONDOCK_VERSION = "2.2.2";
4
+ export const ACTIONDOCK_VERSION = "2.3.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actiondock/core",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
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",
@@ -29,7 +29,7 @@
29
29
  "test": "node ../../scripts/run-tests.ts"
30
30
  },
31
31
  "dependencies": {
32
- "@actiondock/sdk": "^2.2.2",
32
+ "@actiondock/sdk": "^2.3.0",
33
33
  "ajv": "^8.17.1",
34
34
  "ajv-formats": "^3.0.1"
35
35
  },