@actiondock/core 2.16.0 → 2.17.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.
@@ -7,6 +7,7 @@ import { InMemoryEventSink } from "../runtime/events.js";
7
7
  import { ActionDockError, ACTION_CANCELLED, ACTION_FAILED, ACTION_NOT_FOUND, ACTION_TIMEOUT, EXECUTION_CONCURRENCY_LIMIT, EXECUTION_FAILED, IDEMPOTENCY_CONFLICT, INPUT_NOT_JSON, INPUT_VALIDATION_FAILED, INVALID_ARGUMENT, INVOCATION_UNSUPPORTED, OUTPUT_NOT_JSON, OUTPUT_VALIDATION_FAILED, RUN_REPOSITORY_UNAVAILABLE, SERVICE_CLOSED, UNHANDLED_EXECUTION_ERROR, describeActionLoadFailure, } from "../errors.js";
8
8
  import { resultStatusToRunStatus } from "../storage/types.js";
9
9
  import { createDefaultProcessOwner, } from "../invocation/types.js";
10
+ import { snapshotRunOrigin } from "../runtime/run-origin.js";
10
11
  import { ActionRegistry, isActionDefinitionObject, resolveAnonymousActionId, } from "../runtime/action-registry.js";
11
12
  import { buildInitialRunRecord, createRunFinalizer, createRunOrThrow, tryCreateRun, } from "../runtime/run-persistence.js";
12
13
  import { createActionContext } from "../runtime/context.js";
@@ -432,6 +433,7 @@ export class DefaultExecutionService {
432
433
  process: opts.process || this.process,
433
434
  owner: opts.owner,
434
435
  requestId: opts.requestId,
436
+ origin: snapshotRunOrigin(opts.origin),
435
437
  };
436
438
  }
437
439
  async raceExecution(args) {
@@ -548,6 +550,7 @@ export class DefaultExecutionService {
548
550
  progress: context.progress,
549
551
  process: effectiveProcess,
550
552
  owner: effectiveOwner,
553
+ origin: context.origin,
551
554
  };
552
555
  return await invoker(childAction, childInput, invocationContext);
553
556
  }
@@ -712,8 +715,10 @@ export class DefaultExecutionService {
712
715
  message: `Action '${targetActionId}' not found in package '${targetPackageId}'`,
713
716
  };
714
717
  const rootRunId = context.rootRunId || context.parentRunId || runId;
718
+ const origin = snapshotRunOrigin(context.origin);
715
719
  const initialRun = {
716
720
  id: runId,
721
+ ...(origin !== undefined ? { origin } : {}),
717
722
  rootRunId,
718
723
  parentRunId: context.parentRunId,
719
724
  packageId: targetPackageId,
@@ -777,6 +782,7 @@ export class DefaultExecutionService {
777
782
  runId,
778
783
  rootRunId,
779
784
  parentRunId: context.parentRunId,
785
+ origin: context.origin,
780
786
  ownerId: context.principalId || context.tenantId || this.ownerId,
781
787
  hostSessionId: context.hostSessionId || this.hostSessionId,
782
788
  hostPid: this.hostPid,
package/dist/host/host.js CHANGED
@@ -7,6 +7,7 @@ import { findProjectRoot, loadProjectConfig } from "../project/loader.js";
7
7
  import { hasPendingTransactions, isProjectLockHeld, recoverPendingTransactions } from "../project/transactions.js";
8
8
  import { listLinkedPackages } from "../registry/registry.js";
9
9
  import { InMemoryEventSink } from "../runtime/events.js";
10
+ import { snapshotRunOrigin } from "../runtime/run-origin.js";
10
11
  import { ActionDockError, ACTION_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_SUBRUN_LIMIT, EXECUTION_FAILED, INVALID_ACTION_REF, PACKAGE_ID_CONFLICT, PACKAGE_NOT_FOUND, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, SERVICE_CLOSED, UNDECLARED_ACTION_DEPENDENCY, } from "../errors.js";
11
12
  import { InvocationPolicy } from "../invocation/policy.js";
12
13
  import { createAbortError } from "../ipc/service.js";
@@ -135,6 +136,7 @@ export class DefaultActionDockHost {
135
136
  };
136
137
  this.execution = {
137
138
  async run(ref, input, opts) {
139
+ const origin = snapshotRunOrigin(opts?.origin);
138
140
  await self.ensureInitialized();
139
141
  const actionRef = typeof ref === "string" ? parseActionRef(ref) : ref;
140
142
  const cleanOpts = {
@@ -142,10 +144,12 @@ export class DefaultActionDockHost {
142
144
  timeoutMs: opts?.timeoutMs,
143
145
  config: opts?.config,
144
146
  requestId: opts?.requestId,
147
+ origin,
145
148
  };
146
149
  return self.runAction(actionRef, (input ?? {}), cleanOpts);
147
150
  },
148
151
  async start(ref, input, opts) {
152
+ const origin = snapshotRunOrigin(opts?.origin);
149
153
  await self.ensureInitialized();
150
154
  const actionRef = typeof ref === "string" ? parseActionRef(ref) : ref;
151
155
  const cleanOpts = {
@@ -153,6 +157,7 @@ export class DefaultActionDockHost {
153
157
  timeoutMs: opts?.timeoutMs,
154
158
  config: opts?.config,
155
159
  requestId: opts?.requestId,
160
+ origin,
156
161
  };
157
162
  return self.startAction(actionRef, (input ?? {}), cleanOpts);
158
163
  },
@@ -634,6 +639,7 @@ export class DefaultActionDockHost {
634
639
  progress: context.progress,
635
640
  process: context.process,
636
641
  owner: targetOwner,
642
+ origin: context.origin,
637
643
  };
638
644
  const ticket = await targetRuntime.startInvocation(targetActionId, childInput, subInvocationContext);
639
645
  if (!ticket.result) {
@@ -752,14 +758,18 @@ export class DefaultActionDockHost {
752
758
  return describeVisiblePlaybook(this.listRuntimes(), id, this.visibility());
753
759
  }
754
760
  async runAction(ref, input, options = {}) {
761
+ const origin = snapshotRunOrigin(options.origin);
762
+ const effectiveOptions = { ...options, origin };
755
763
  await this.ensureInitialized();
756
- const ticket = await this.startAction(ref, input, options);
764
+ const ticket = await this.startAction(ref, input, effectiveOptions);
757
765
  if (!ticket.result) {
758
766
  throw new ActionDockError(EXECUTION_FAILED, `Execution ticket for run '${ticket.runId}' has no result Promise`);
759
767
  }
760
768
  return ticket.result;
761
769
  }
762
770
  async startAction(ref, input, options = {}) {
771
+ const origin = snapshotRunOrigin(options.origin);
772
+ const effectiveOptions = { ...options, origin };
763
773
  await this.ensureInitialized();
764
774
  if (this.isClosed) {
765
775
  throw new ActionDockError(SERVICE_CLOSED, "ActionDockHost is closed: new tasks rejected");
@@ -837,13 +847,14 @@ export class DefaultActionDockHost {
837
847
  const rootContext = createRootInvocationContext({
838
848
  targetPackage: targetRuntime.identity,
839
849
  callStack: [rootKey],
840
- signal: options.signal,
841
- timeoutMs: options.timeoutMs,
842
- config: options.config,
843
- requestId: options.requestId,
850
+ signal: effectiveOptions.signal,
851
+ timeoutMs: effectiveOptions.timeoutMs,
852
+ config: effectiveOptions.config,
853
+ requestId: effectiveOptions.requestId,
844
854
  hostSessionId: this.hostSessionId,
845
855
  maxCallDepth: this.maxCallDepth,
846
856
  process: this.options.process,
857
+ origin: effectiveOptions.origin,
847
858
  });
848
859
  // 调度目标包执行服务
849
860
  return targetRuntime.startInvocation(targetActionId, input, rootContext);
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export type { TerminalRunStatus } from "./storage/types.js";
14
14
  export { parseDuration } from "./utils/index.js";
15
15
  export { toSnakeUpperCase, resolveEnvValue } from "./runtime/env.js";
16
16
  export type { ResolvedEnv } from "./runtime/env.js";
17
- export type { ActionRef, ResolvedActionRef, ExecutionResult, RunRecord, ExecutionEvent, } from "@actiondock/sdk";
17
+ export type { ActionRef, ResolvedActionRef, ExecutionResult, RunRecord, RunOrigin, ExecutionEvent, } from "@actiondock/sdk";
18
18
  export type { RunOptions, ExecutionTicket, CancelResult, } from "./execution/types.js";
19
19
  export type { ActionSpec, ActionSummary, PlaybookSpec, PlaybookSummary, PackageInfo, ListActionsOptions, } from "./package/types.js";
20
20
  export { createNodePlatform, } from "./platform/node.js";
@@ -1,4 +1,4 @@
1
- import type { ActionDefinition, ActionRef, Logger, ProcessAPI, ProgressReporter } from "@actiondock/sdk";
1
+ import type { ActionDefinition, ActionRef, Logger, ProcessAPI, ProgressReporter, RunOrigin } from "@actiondock/sdk";
2
2
  import type { ProcessOwner } from "../process/index.js";
3
3
  import { createPackageIdentity, type PackageIdentity } from "../runtime/identity.js";
4
4
  export { createPackageIdentity };
@@ -75,6 +75,8 @@ export interface InvocationContext {
75
75
  readonly process?: ProcessAPI;
76
76
  /** 进程属主身份 */
77
77
  readonly owner?: ProcessOwner;
78
+ /** 运行来源追踪快照 */
79
+ readonly origin?: RunOrigin;
78
80
  }
79
81
  /**
80
82
  * 内部调用上下文选项契约。
@@ -121,6 +123,8 @@ export interface CreateRootInvocationContextOptions {
121
123
  progress?: ProgressReporter;
122
124
  /** 显式执行归属所有者契约 */
123
125
  owner?: ProcessOwner;
126
+ /** 运行来源追踪快照 */
127
+ origin?: RunOrigin;
124
128
  }
125
129
  /**
126
130
  * 构造合法的受信任根调用上下文(Root InvocationContext)。
@@ -1,4 +1,5 @@
1
1
  import { createPackageIdentity } from "../runtime/identity.js";
2
+ import { snapshotRunOrigin } from "../runtime/run-origin.js";
2
3
  export { createPackageIdentity };
3
4
  /**
4
5
  * 构造默认进程归属所有者(ProcessOwner)的单一事实源工厂。
@@ -23,6 +24,7 @@ export function createInvocationContext(options) {
23
24
  const runId = options.runId ?? crypto.randomUUID();
24
25
  const rootRunId = options.rootRunId ?? runId;
25
26
  const pkg = options.package;
27
+ const origin = snapshotRunOrigin(options.origin);
26
28
  return {
27
29
  runId,
28
30
  rootRunId,
@@ -41,6 +43,7 @@ export function createInvocationContext(options) {
41
43
  logger: options.logger,
42
44
  progress: options.progress,
43
45
  process: options.process,
46
+ origin,
44
47
  owner: options.owner ??
45
48
  createDefaultProcessOwner({
46
49
  tenantId: options.tenantId,
@@ -75,6 +78,7 @@ export function createRootInvocationContext(options) {
75
78
  owner: options.owner,
76
79
  tenantId: options.tenantId,
77
80
  principalId: options.principalId,
81
+ origin: options.origin,
78
82
  });
79
83
  return {
80
84
  runId: base.runId,
@@ -93,5 +97,6 @@ export function createRootInvocationContext(options) {
93
97
  progress: base.progress,
94
98
  process: base.process,
95
99
  owner: base.owner,
100
+ origin: base.origin,
96
101
  };
97
102
  }
@@ -11,6 +11,7 @@ import { createPackageIdentity } from "../runtime/identity.js";
11
11
  import { ACTION_NOT_FOUND, ActionDockError, CAPABILITY_UNAVAILABLE, INVALID_ARGUMENT, INVOCATION_UNSUPPORTED, NOT_FOUND, SERVICE_CLOSED, } from "../errors.js";
12
12
  import { createAbortError } from "../ipc/service.js";
13
13
  import { createRootInvocationContext } from "../invocation/types.js";
14
+ import { snapshotRunOrigin } from "../runtime/run-origin.js";
14
15
  import { buildStaticActionMap, buildStaticPlaybookMap } from "./static-index.js";
15
16
  import { applyActionSummaryFilters } from "./types.js";
16
17
  function normalizeStateReadArgs(arg1, arg2, arg3) {
@@ -550,9 +551,12 @@ export class DefaultPackageRuntime {
550
551
  maxCallDepth: this.options.maxCallDepth,
551
552
  logger: this.options.logger,
552
553
  process: this.options.process ?? this.platform.process,
554
+ origin: options?.origin,
553
555
  });
554
556
  }
555
557
  async runAction(id, input, options) {
558
+ const origin = snapshotRunOrigin(options?.origin);
559
+ const effectiveOptions = { ...options, origin };
556
560
  const service = await this.prepareExecution();
557
561
  let actionId = id;
558
562
  if (actionId.includes("/")) {
@@ -563,10 +567,12 @@ export class DefaultPackageRuntime {
563
567
  throw new ActionDockError(INVOCATION_UNSUPPORTED, `PackageRuntime only accepts local action short ID '${id}'. Cross-package invocations must be dispatched via Host invoker.`);
564
568
  }
565
569
  }
566
- const context = this.buildRootInvocationContext(actionId, options);
570
+ const context = this.buildRootInvocationContext(actionId, effectiveOptions);
567
571
  return service.execute(actionId, input, context);
568
572
  }
569
573
  async startAction(id, input, options) {
574
+ const origin = snapshotRunOrigin(options?.origin);
575
+ const effectiveOptions = { ...options, origin };
570
576
  const service = await this.prepareExecution();
571
577
  let actionId = id;
572
578
  if (actionId.includes("/")) {
@@ -577,7 +583,7 @@ export class DefaultPackageRuntime {
577
583
  throw new ActionDockError(INVOCATION_UNSUPPORTED, `PackageRuntime only accepts local action short ID '${id}'. Cross-package invocations must be dispatched via Host invoker.`);
578
584
  }
579
585
  }
580
- const context = this.buildRootInvocationContext(actionId, options);
586
+ const context = this.buildRootInvocationContext(actionId, effectiveOptions);
581
587
  return service.start(actionId, input, context);
582
588
  }
583
589
  async startInvocation(actionId, input, context) {
@@ -86,10 +86,10 @@ export function initProject(targetDir, options = {}) {
86
86
  node: ">=24.12.0",
87
87
  },
88
88
  dependencies: {
89
- "@actiondock/sdk": "^2.16.0",
89
+ "@actiondock/sdk": "^2.17.0",
90
90
  },
91
91
  devDependencies: {
92
- "@actiondock/testing": "^2.16.0",
92
+ "@actiondock/testing": "^2.17.0",
93
93
  "@types/node": "^22.13.0",
94
94
  "tsx": "^4.19.0",
95
95
  "typescript": "^5.7.0",
@@ -6,3 +6,4 @@ export * from "./run-persistence.js";
6
6
  export * from "./standalone.js";
7
7
  export * from "./env.js";
8
8
  export * from "./events.js";
9
+ export * from "./run-origin.js";
@@ -6,3 +6,4 @@ export * from "./run-persistence.js";
6
6
  export * from "./standalone.js";
7
7
  export * from "./env.js";
8
8
  export * from "./events.js";
9
+ export * from "./run-origin.js";
@@ -0,0 +1,9 @@
1
+ import type { RunOrigin } from "@actiondock/sdk";
2
+ /**
3
+ * 运行来源快照辅助函数(纯领域无副作用,单一事实源)。
4
+ *
5
+ * 仅安全提取 channel、viewName 与 authenticated 3个非秘密字段并冻结对象,
6
+ * 严禁携带额外凭证或扩展字段,防范对象后续被篡改。
7
+ * 若入参缺省则返回 undefined;非法来源严格校验报错,杜绝静默吞没。
8
+ */
9
+ export declare function snapshotRunOrigin(origin?: unknown): RunOrigin | undefined;
@@ -0,0 +1,33 @@
1
+ import { ActionDockError, INVALID_ARGUMENT } from "../errors.js";
2
+ /**
3
+ * 运行来源快照辅助函数(纯领域无副作用,单一事实源)。
4
+ *
5
+ * 仅安全提取 channel、viewName 与 authenticated 3个非秘密字段并冻结对象,
6
+ * 严禁携带额外凭证或扩展字段,防范对象后续被篡改。
7
+ * 若入参缺省则返回 undefined;非法来源严格校验报错,杜绝静默吞没。
8
+ */
9
+ export function snapshotRunOrigin(origin) {
10
+ if (origin === undefined || origin === null) {
11
+ return undefined;
12
+ }
13
+ if (typeof origin !== "object" || Array.isArray(origin)) {
14
+ throw new ActionDockError(INVALID_ARGUMENT, "Invalid run origin: expected an object");
15
+ }
16
+ const raw = origin;
17
+ const channel = raw.channel;
18
+ if (channel !== "http" && channel !== "mcp") {
19
+ throw new ActionDockError(INVALID_ARGUMENT, "Invalid run origin channel: expected 'http' or 'mcp'");
20
+ }
21
+ const viewName = raw.viewName;
22
+ if (typeof viewName !== "string" || viewName.length === 0) {
23
+ throw new ActionDockError(INVALID_ARGUMENT, "Invalid run origin viewName: expected a non-empty string");
24
+ }
25
+ if (typeof raw.authenticated !== "boolean") {
26
+ throw new ActionDockError(INVALID_ARGUMENT, `Invalid run origin authenticated: expected boolean, got '${typeof raw.authenticated}'`);
27
+ }
28
+ return Object.freeze({
29
+ channel,
30
+ viewName,
31
+ authenticated: raw.authenticated,
32
+ });
33
+ }
@@ -1,4 +1,4 @@
1
- import type { RuntimeError, RunRecord } from "@actiondock/sdk";
1
+ import type { RuntimeError, RunOrigin, RunRecord } from "@actiondock/sdk";
2
2
  import type { RuntimeStorage, TerminalRunStatus } from "../storage/types.js";
3
3
  /**
4
4
  * 运行记录落库参数(RunRecord 构造与写入的领域入参)。
@@ -13,6 +13,8 @@ export interface InitialRunRecordInput {
13
13
  rootRunId: string;
14
14
  /** 父级运行 ID(嵌套调用场景下建立调用链树) */
15
15
  parentRunId?: string;
16
+ /** 运行来源追踪快照 */
17
+ origin?: RunOrigin;
16
18
  /** 执行归属所有者标识 */
17
19
  ownerId?: string;
18
20
  /** 执行宿主会话标识 */
@@ -1,11 +1,14 @@
1
1
  import { ActionDockError, RUN_PERSISTENCE_FAILED, RUN_REPOSITORY_UNAVAILABLE } from "../errors.js";
2
+ import { snapshotRunOrigin } from "./run-origin.js";
2
3
  /**
3
4
  * 构建运行初始记录(RunRecord 组装单一事实源)。
4
5
  */
5
6
  export function buildInitialRunRecord(params, status, error) {
6
7
  const { runId, rootRunId, parentRunId, ownerId, hostSessionId, hostPid, packageInstanceId, generationId, targetPackageId, targetActionId, startedAt, input, runnerPackageId, runnerPackageInstanceId, runnerGenerationId, runnerHostSessionId, } = params;
8
+ const origin = snapshotRunOrigin(params.origin);
7
9
  const record = {
8
10
  id: runId,
11
+ ...(origin !== undefined ? { origin } : {}),
9
12
  rootRunId,
10
13
  parentRunId,
11
14
  packageId: targetPackageId,
@@ -1,3 +1,4 @@
1
+ import type { RunOrigin } from "@actiondock/sdk";
1
2
  import { ActionDockError } from "../errors.js";
2
3
  import type { McpEndpointHandler } from "./mcp-endpoint.js";
3
4
  import type { EffectiveServerPolicy, ServerOptions, ServerViewOptions } from "./types.js";
@@ -136,3 +137,10 @@ export declare function assertActionAllowedByPolicy(action: {
136
137
  * @param policy 当前生效的服务端策略
137
138
  */
138
139
  export declare function assertPackageAllowedByPolicy(packageId: string, policy?: EffectiveServerPolicy): void;
140
+ /**
141
+ * 构造受信任的服务端执行来源快照。
142
+ *
143
+ * 仅挑选 channel、viewName(默认 "default")与 authenticated(视图配置非空有效令牌)。
144
+ * 严禁暴露令牌原文、片段或摘要。
145
+ */
146
+ export declare function createServerRunOrigin(policy: Pick<EffectiveServerPolicy, "viewName" | "token">, channel: RunOrigin["channel"]): RunOrigin;
@@ -1,3 +1,4 @@
1
+ import { snapshotRunOrigin } from "../runtime/run-origin.js";
1
2
  import { parseActionRef } from "../catalog/resolve-action.js";
2
3
  import { ActionDockError, ACTION_FORBIDDEN, PACKAGE_NOT_ALLOWED } from "../errors.js";
3
4
  import { safeEqual } from "./security.js";
@@ -366,3 +367,16 @@ export function assertPackageAllowedByPolicy(packageId, policy) {
366
367
  throw new PackageNotAllowedError(packageId);
367
368
  }
368
369
  }
370
+ /**
371
+ * 构造受信任的服务端执行来源快照。
372
+ *
373
+ * 仅挑选 channel、viewName(默认 "default")与 authenticated(视图配置非空有效令牌)。
374
+ * 严禁暴露令牌原文、片段或摘要。
375
+ */
376
+ export function createServerRunOrigin(policy, channel) {
377
+ return snapshotRunOrigin({
378
+ channel,
379
+ viewName: policy.viewName || "default",
380
+ authenticated: Boolean(policy.token?.trim()),
381
+ });
382
+ }
@@ -2,6 +2,7 @@ import { parseActionRef } from "../../catalog/resolve-action.js";
2
2
  import { filterByIntent } from "../../utils/intent.js";
3
3
  import { ACTION_EXECUTION_ERROR, ACTION_FORBIDDEN, ACTION_NOT_FOUND, ACTION_START_FAILED, ACTION_TIMEOUT, ACTIONS_LIST_ERROR, IDEMPOTENCY_CONFLICT, INPUT_VALIDATION_FAILED, INVALID_JSON, PACKAGE_FORBIDDEN, PACKAGE_NOT_FOUND, REQUEST_TOO_LARGE, } from "../../errors.js";
4
4
  import { InvalidJsonError, readJsonBody, RequestTooLargeError } from "../body.js";
5
+ import { createServerRunOrigin } from "../policy.js";
5
6
  import { filterActionsByPolicy, getSubPath, isActionAllowedByPolicy, isPackageAllowedByPolicy, jsonResponse, } from "./common.js";
6
7
  /**
7
8
  * 处理 Action 相关的 HTTP 路由(列表、规范查询、同步执行与异步启动)。
@@ -257,10 +258,12 @@ export async function handleActionsRoutes(ctx) {
257
258
  }
258
259
  if (isAsync) {
259
260
  try {
261
+ const origin = createServerRunOrigin(policy, "http");
260
262
  const ticket = await service.execution.start(actionRefObj, input, {
261
263
  timeoutMs,
262
264
  config: configOverrides,
263
265
  requestId,
266
+ origin,
264
267
  });
265
268
  if (ticket.status === "failed") {
266
269
  const res = await ticket.result;
@@ -296,11 +299,13 @@ export async function handleActionsRoutes(ctx) {
296
299
  }
297
300
  // 同步执行模式
298
301
  try {
302
+ const origin = createServerRunOrigin(policy, "http");
299
303
  const result = await service.execution.run(actionRefObj, input, {
300
304
  signal: req.signal,
301
305
  timeoutMs,
302
306
  config: configOverrides,
303
307
  requestId,
308
+ origin,
304
309
  });
305
310
  let status = 200;
306
311
  if (!result.ok) {
@@ -1,4 +1,4 @@
1
- import type { ActionRef, ExecutionEvent, ExecutionResult, JsonValue, RunRecord } from "@actiondock/sdk";
1
+ import type { ActionRef, ExecutionEvent, ExecutionResult, JsonValue, RunOrigin, RunRecord } from "@actiondock/sdk";
2
2
  import type { ActionSpec, ActionSummary, ListActionsOptions, PackageInfo, PackageRuntime, PackageRuntimeOptions, PlaybookSpec, PlaybookSummary } from "../package/types.js";
3
3
  import type { CancelResult, ExecutionTicket } from "../execution/types.js";
4
4
  import type { ActionDockHostOptions } from "../host/types.js";
@@ -18,6 +18,8 @@ export interface RunOptions {
18
18
  config?: Record<string, JsonValue>;
19
19
  /** 幂等请求标识 */
20
20
  requestId?: string;
21
+ /** 运行来源追踪快照 */
22
+ origin?: RunOrigin;
21
23
  }
22
24
  /**
23
25
  * 统一协议版本常量。
@@ -1,5 +1,6 @@
1
1
  import { isTerminalRunStatus, DEFAULT_MAX_RUNS_PER_PACKAGE, DEFAULT_MIN_RETAIN_RUNS, DEFAULT_RUNS_RETENTION_MS } from "./types.js";
2
2
  import { safeParseStoredJson } from "./utils.js";
3
+ import { snapshotRunOrigin } from "../runtime/run-origin.js";
3
4
  import { isRunHostDead } from "./run-liveness.js";
4
5
  import { ActionDockError, STORAGE_CLOSED, STORED_ERROR_DECODE_FAILED } from "../errors.js";
5
6
  export class SqliteRunsStore {
@@ -146,15 +147,17 @@ export class SqliteRunsStore {
146
147
  createRun(record) {
147
148
  const stmt = this.getStatement(`
148
149
  INSERT INTO runs (
149
- id, root_run_id, parent_run_id, package_id, package_instance_id,
150
+ id, root_run_id, parent_run_id, origin_json, package_id, package_instance_id,
150
151
  action_id, generation_id, owner_id, host_session_id, host_pid, heartbeat_at,
151
152
  status, input_json, output_json,
152
153
  error_json, started_at, finished_at, duration_ms
153
154
  )
154
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
155
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
155
156
  `);
156
157
  const rootRunId = record.rootRunId || record.id;
157
158
  const parentRunId = record.parentRunId || null;
159
+ const origin = snapshotRunOrigin(record.origin);
160
+ const originJson = origin ? JSON.stringify(origin) : null;
158
161
  const packageInstanceId = record.packageInstanceId || record.packageId || this.packageId;
159
162
  const generationId = record.generationId || "1";
160
163
  const ownerId = record.ownerId || "local";
@@ -168,7 +171,7 @@ export class SqliteRunsStore {
168
171
  const errorJson = record.error ? JSON.stringify(record.error) : null;
169
172
  const finishedAt = record.finishedAt || null;
170
173
  const durationMs = typeof record.durationMs === "number" ? record.durationMs : null;
171
- stmt.run(record.id, rootRunId, parentRunId, record.packageId || this.packageId, packageInstanceId, record.actionId ?? "", generationId, ownerId, hostSessionId, hostPid, heartbeatAt, record.status, inputJson, outputJson, errorJson, startedIso, finishedAt, durationMs);
174
+ stmt.run(record.id, rootRunId, parentRunId, originJson, record.packageId || this.packageId, packageInstanceId, record.actionId ?? "", generationId, ownerId, hostSessionId, hostPid, heartbeatAt, record.status, inputJson, outputJson, errorJson, startedIso, finishedAt, durationMs);
172
175
  this.runInsertCount++;
173
176
  const nowMs = this.clock.now().getTime();
174
177
  if (this.runInsertCount >= SqliteRunsStore.RUNS_CLEANUP_BATCH_THRESHOLD &&
@@ -595,8 +598,11 @@ export class SqliteRunsStore {
595
598
  ? error
596
599
  : { code: STORED_ERROR_DECODE_FAILED, message: String(error) }
597
600
  : undefined;
601
+ const rawOrigin = safeParseStoredJson(row.origin_json, `runs.origin db=${this.dbPath} package=${this.packageId} run=${row.id}`, { strict: true });
602
+ const origin = snapshotRunOrigin(rawOrigin);
598
603
  return {
599
604
  id: row.id,
605
+ ...(origin !== undefined ? { origin } : {}),
600
606
  rootRunId: row.root_run_id || row.id,
601
607
  parentRunId: row.parent_run_id || undefined,
602
608
  packageId: row.package_id,
@@ -49,6 +49,7 @@ export function initSchema(driver, dbPath, recoverOrphans, recoverDeadSessionFn,
49
49
  id TEXT PRIMARY KEY,
50
50
  root_run_id TEXT NOT NULL,
51
51
  parent_run_id TEXT,
52
+ origin_json TEXT,
52
53
  package_id TEXT NOT NULL,
53
54
  package_instance_id TEXT NOT NULL,
54
55
  action_id TEXT NOT NULL,
@@ -103,8 +104,11 @@ export function initSchema(driver, dbPath, recoverOrphans, recoverDeadSessionFn,
103
104
  }
104
105
  // 确保存量旧版本数据库补充新增的列或表
105
106
  if (version !== 0) {
107
+ const columns = driver.prepare("PRAGMA table_info(runs);").all();
108
+ if (Array.isArray(columns) && !columns.some((c) => c.name === "origin_json")) {
109
+ driver.exec("ALTER TABLE runs ADD COLUMN origin_json TEXT;");
110
+ }
106
111
  try {
107
- const columns = driver.prepare("PRAGMA table_info(runs);").all();
108
112
  if (Array.isArray(columns) && !columns.some((c) => c.name === "host_session_id")) {
109
113
  driver.exec("ALTER TABLE runs ADD COLUMN host_session_id TEXT;");
110
114
  }
@@ -17,6 +17,9 @@
17
17
  *
18
18
  * @param raw 存储层原始 JSON 字符串
19
19
  * @param context 存储路径上下文描述(包含数据库位置、包标识与键名)
20
- * @returns 解析成功返回解析后的值;入参为空返回 undefined;解析失败返回原始字符串
20
+ * @param options 严格模式下解析失败直接报错,且不输出原始载荷片段
21
+ * @returns 解析成功返回解析后的值;入参为空返回 undefined;默认模式解析失败返回原始字符串
21
22
  */
22
- export declare function safeParseStoredJson<T = unknown>(raw: string | null | undefined, context: string): T | undefined;
23
+ export declare function safeParseStoredJson<T = unknown>(raw: string | null | undefined, context: string, options?: {
24
+ strict?: boolean;
25
+ }): T | undefined;
@@ -1,3 +1,4 @@
1
+ import { ActionDockError, RUN_REPOSITORY_UNAVAILABLE } from "../errors.js";
1
2
  /**
2
3
  * 存储层 JSON 载荷统一安全解析工具。
3
4
  *
@@ -17,9 +18,10 @@
17
18
  *
18
19
  * @param raw 存储层原始 JSON 字符串
19
20
  * @param context 存储路径上下文描述(包含数据库位置、包标识与键名)
20
- * @returns 解析成功返回解析后的值;入参为空返回 undefined;解析失败返回原始字符串
21
+ * @param options 严格模式下解析失败直接报错,且不输出原始载荷片段
22
+ * @returns 解析成功返回解析后的值;入参为空返回 undefined;默认模式解析失败返回原始字符串
21
23
  */
22
- export function safeParseStoredJson(raw, context) {
24
+ export function safeParseStoredJson(raw, context, options) {
23
25
  if (raw === null || raw === undefined) {
24
26
  return undefined;
25
27
  }
@@ -27,6 +29,9 @@ export function safeParseStoredJson(raw, context) {
27
29
  return JSON.parse(raw);
28
30
  }
29
31
  catch (err) {
32
+ if (options?.strict) {
33
+ throw new ActionDockError(RUN_REPOSITORY_UNAVAILABLE, `Corrupted stored JSON at ${context}`);
34
+ }
30
35
  const reason = err instanceof Error ? err.message : String(err);
31
36
  console.warn(`[actiondock] stored JSON corrupted at ${context}: ${reason}`);
32
37
  return raw;
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * ActionDock 核心版本号单一事实源。
3
3
  */
4
- export declare const ACTIONDOCK_VERSION = "2.16.0";
4
+ export declare const ACTIONDOCK_VERSION = "2.17.0";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * ActionDock 核心版本号单一事实源。
3
3
  */
4
- export const ACTIONDOCK_VERSION = "2.16.0";
4
+ export const ACTIONDOCK_VERSION = "2.17.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actiondock/core",
3
- "version": "2.16.0",
3
+ "version": "2.17.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",
@@ -59,7 +59,7 @@
59
59
  "test": "node ../../scripts/run-tests.ts"
60
60
  },
61
61
  "dependencies": {
62
- "@actiondock/sdk": "^2.16.0",
62
+ "@actiondock/sdk": "^2.17.0",
63
63
  "ajv": "^8.17.1",
64
64
  "ajv-formats": "^3.0.1",
65
65
  "undici": "^7.29.1"