@actiondock/core 2.15.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.
Files changed (52) hide show
  1. package/dist/catalog/discovery.d.ts +3 -3
  2. package/dist/catalog/graph.d.ts +8 -6
  3. package/dist/catalog/graph.js +62 -197
  4. package/dist/catalog/resolve-playbook.d.ts +2 -2
  5. package/dist/catalog/resolve-playbook.js +53 -12
  6. package/dist/execution/service.js +6 -0
  7. package/dist/execution/types.d.ts +2 -2
  8. package/dist/host/host.js +16 -5
  9. package/dist/host/routing.d.ts +1 -1
  10. package/dist/host/routing.js +1 -1
  11. package/dist/index.d.ts +2 -4
  12. package/dist/index.js +0 -1
  13. package/dist/invocation/types.d.ts +5 -1
  14. package/dist/invocation/types.js +5 -0
  15. package/dist/package/runtime.d.ts +2 -2
  16. package/dist/package/runtime.js +9 -5
  17. package/dist/package/static-index.d.ts +6 -4
  18. package/dist/package/static-index.js +60 -12
  19. package/dist/package/types.d.ts +5 -5
  20. package/dist/project/index.d.ts +0 -1
  21. package/dist/project/index.js +0 -1
  22. package/dist/project/init.js +2 -2
  23. package/dist/project/loader.d.ts +2 -4
  24. package/dist/project/loader.js +34 -38
  25. package/dist/project/manifest.d.ts +0 -2
  26. package/dist/project/manifest.js +1 -17
  27. package/dist/project/types.d.ts +0 -31
  28. package/dist/registry/registry.js +15 -27
  29. package/dist/runtime/context.d.ts +3 -3
  30. package/dist/runtime/index.d.ts +1 -0
  31. package/dist/runtime/index.js +1 -0
  32. package/dist/runtime/run-origin.d.ts +9 -0
  33. package/dist/runtime/run-origin.js +33 -0
  34. package/dist/runtime/run-persistence.d.ts +3 -1
  35. package/dist/runtime/run-persistence.js +3 -0
  36. package/dist/runtime/standalone-commands.js +2 -8
  37. package/dist/runtime/standalone.d.ts +0 -2
  38. package/dist/server/policy.d.ts +8 -0
  39. package/dist/server/policy.js +14 -0
  40. package/dist/server/routes/actions.js +5 -0
  41. package/dist/service/types.d.ts +3 -1
  42. package/dist/storage/sqlite-runs.js +9 -3
  43. package/dist/storage/sqlite-schema.js +5 -1
  44. package/dist/storage/utils.d.ts +5 -2
  45. package/dist/storage/utils.js +7 -2
  46. package/dist/utils/semver.d.ts +1 -2
  47. package/dist/utils/semver.js +5 -11
  48. package/dist/version.d.ts +1 -1
  49. package/dist/version.js +1 -1
  50. package/package.json +2 -2
  51. package/dist/project/skill-dir.d.ts +0 -72
  52. package/dist/project/skill-dir.js +0 -244
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync, readFileSync, renameSync, statSync } from "node:
2
2
  import { readFile, rename, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, join, resolve, sep } from "node:path";
4
4
  import { findProjectRoot, loadProjectConfig } from "../project/loader.js";
5
- import { inspectLocalPackageDirectory, isExplicitPath } from "../project/skill-dir.js";
6
5
  import { getActionDockHome, getPackageSlug } from "../utils/index.js";
7
6
  import { ActionDockError, MANIFEST_NOT_FOUND, REGISTRY_CORRUPTED } from "../errors.js";
8
7
  import { withRegistryLock } from "./lock.js";
@@ -400,42 +399,31 @@ export function resolvePackageRoot(packageIdOrPath, cwd, customHome) {
400
399
  const resolvedPath = resolve(baseDir, packageIdOrPath);
401
400
  // 检查 packageIdOrPath 是否为磁盘上现存的目录或文件路径
402
401
  if (existsSync(resolvedPath)) {
403
- let info;
404
402
  try {
405
- info = statSync(resolvedPath);
406
- }
407
- catch (err) {
408
- if (err?.code !== "ENOENT" && err?.code !== "ENOTDIR") {
409
- throw err;
410
- }
411
- }
412
- if (info) {
403
+ const info = statSync(resolvedPath);
413
404
  const targetDir = info.isDirectory() ? resolvedPath : dirname(resolvedPath);
414
- // 普通开发包根目录:直接包含 actiondock.json 清单。
415
- // 普通清单根目录定位与封闭 Skill 执行校验分离,不能被当成只含一个包的 Skill 闭包拒绝。
416
405
  if (existsSync(join(targetDir, "actiondock.json"))) {
417
- loadProjectConfig(targetDir);
418
406
  return targetDir;
419
407
  }
420
- // 复合技能布局:根目录下存在 packages/ 目录且包含有效子包。
421
- const localSkill = inspectLocalPackageDirectory(targetDir);
422
- if (localSkill) {
423
- return localSkill.root;
424
- }
425
- // 具体文件查找所属工程根目录:仅当 packageIdOrPath 指向具体文件(非目录)时,
426
- // 才尝试向上查找所属工程根目录;目录形态绝不向上回退,防止显式目录越界命中父级工程。
427
- if (!info.isDirectory()) {
428
- const parentRoot = findProjectRoot(targetDir);
429
- if (parentRoot) {
430
- loadProjectConfig(parentRoot);
431
- return parentRoot;
432
- }
408
+ const parentRoot = findProjectRoot(targetDir);
409
+ if (parentRoot) {
410
+ return parentRoot;
433
411
  }
434
412
  }
413
+ catch {
414
+ // stat 失败(如并发删除):忽略并继续走标识符解析路径
415
+ }
435
416
  }
436
417
  // 显式路径形态但未解析成功的场景:
437
418
  // scoped package 标识符(如 @team/tools)虽含 '/' 但属于包 ID 而非文件路径
438
- if (isExplicitPath(packageIdOrPath)) {
419
+ const isScopedPackage = packageIdOrPath.startsWith("@");
420
+ const isExplicitPath = !isScopedPackage &&
421
+ (packageIdOrPath.startsWith(".") ||
422
+ packageIdOrPath.startsWith("/") ||
423
+ packageIdOrPath.startsWith("~") ||
424
+ packageIdOrPath.includes("/") ||
425
+ packageIdOrPath.includes("\\"));
426
+ if (isExplicitPath) {
439
427
  // 显式路径不存在或不是 ActionDock 项目时必须失败,禁止静默回退
440
428
  return null;
441
429
  }
@@ -1,5 +1,5 @@
1
1
  import type { ActionContext, ActionRef, Config, Logger, ProcessAPI, ProgressReporter, StateStore } from "@actiondock/sdk";
2
- import type { ProjectConfig, RuntimeProjectConfig } from "../project/types.js";
2
+ import type { ProjectConfig } from "../project/types.js";
3
3
  import type { RuntimeStorage } from "../storage/types.js";
4
4
  import { ProcessManager, type ProcessOwner } from "../process/index.js";
5
5
  /**
@@ -17,7 +17,7 @@ export declare class RuntimeConfig implements Config {
17
17
  private storage;
18
18
  private projectConfig?;
19
19
  private globalStorage?;
20
- constructor(storage: RuntimeStorage, overrides?: Record<string, unknown>, projectConfig?: ProjectConfig | RuntimeProjectConfig, globalStorage?: RuntimeStorage);
20
+ constructor(storage: RuntimeStorage, overrides?: Record<string, unknown>, projectConfig?: ProjectConfig, globalStorage?: RuntimeStorage);
21
21
  get<T = unknown>(key: string): T | undefined;
22
22
  get<T = unknown>(key: string, defaultValue: T): T;
23
23
  has(key: string): boolean;
@@ -70,7 +70,7 @@ export interface ContextOptions {
70
70
  storage: RuntimeStorage;
71
71
  globalStorage?: RuntimeStorage;
72
72
  overrides?: Record<string, unknown>;
73
- projectConfig?: ProjectConfig | RuntimeProjectConfig;
73
+ projectConfig?: ProjectConfig;
74
74
  parentRunId?: string;
75
75
  runId?: string;
76
76
  rootRunId?: string;
@@ -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,
@@ -29,7 +29,7 @@ export async function handleList(ctx, subArgs) {
29
29
  const effectiveIntent = intent || (positionalPatterns.length > 0 ? positionalPatterns.join("|") : undefined);
30
30
  const actions = await ctx.service.discovery.listActions();
31
31
  const list = actions.map((a) => ({
32
- id: ctx.options.multiPackage && a.packageId && !a.id.includes("/") ? `${a.packageId}/${a.id}` : a.id,
32
+ id: a.id,
33
33
  description: a.description || "",
34
34
  }));
35
35
  const filterRes = filterWithFallbackInfo(list, effectiveIntent, [(a) => a.id, (a) => a.description], !noFallback);
@@ -66,7 +66,7 @@ export async function handleDescribe(ctx, subArgs) {
66
66
  });
67
67
  }
68
68
  const payload = buildActionDescribePayload(action, {
69
- packageId: action.packageId || ctx.options.packageId,
69
+ packageId: ctx.options.packageId,
70
70
  });
71
71
  if (isJson) {
72
72
  ctx.writeOut(JSON.stringify(payload, null, 2));
@@ -274,9 +274,6 @@ function isServiceCapabilityError(err) {
274
274
  code === "TARGET_CAPABILITY_UNAVAILABLE");
275
275
  }
276
276
  export async function handleConfig(ctx, subArgs) {
277
- if (ctx.options.multiPackage) {
278
- return ctx.emitError(subArgs.includes("--json"), "CAPABILITY_UNAVAILABLE", "Multi-package entries do not support config/state management; configure actions using --config and --data-dir.", ExitCode.INVALID_ARGUMENT);
279
- }
280
277
  const sub = subArgs[0] || "list";
281
278
  if (!ctx.service.management) {
282
279
  ctx.writeErr("Error: management port is not enabled on this service");
@@ -353,9 +350,6 @@ function decodeStateKeyArg(key) {
353
350
  }
354
351
  }
355
352
  export async function handleState(ctx, subArgs) {
356
- if (ctx.options.multiPackage) {
357
- return ctx.emitError(subArgs.includes("--json"), "CAPABILITY_UNAVAILABLE", "Multi-package entries do not support config/state management; configure actions using --config and --data-dir.", ExitCode.INVALID_ARGUMENT);
358
- }
359
353
  const sub = subArgs[0] || "list";
360
354
  let namespace;
361
355
  let isAll = false;
@@ -24,8 +24,6 @@ export interface InvocationControl {
24
24
  export interface StandaloneDispatcherOptions {
25
25
  /** 所属 Package ID */
26
26
  packageId: string;
27
- /** 多包入口使用完全限定发现标识,不以展示名操作配置和状态。 */
28
- multiPackage?: boolean;
29
27
  /** 版本号 */
30
28
  version: string;
31
29
  /** 描述信息 */
@@ -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;
@@ -6,11 +6,10 @@ export interface SemVer {
6
6
  minor: number;
7
7
  patch: number;
8
8
  prerelease?: string;
9
- build?: string;
10
9
  }
11
10
  /**
12
11
  * 解析基础 SemVer 版本字符串(单一事实源)。
13
- * 仅接受可选 v/= 前缀的严格三段式语义化版本,可携带预发布后缀与构建元数据;
12
+ * 仅接受可选 v/= 前缀的严格三段式语义化版本,可携带预发布后缀;
14
13
  * 其余形态(两段式、非数值等)返回 null,由调用方自行兜底。
15
14
  *
16
15
  * 本模块不依赖任何 Node 内建与 core 领域模块,可在无 loader 的原生
@@ -1,25 +1,19 @@
1
1
  /**
2
2
  * 解析基础 SemVer 版本字符串(单一事实源)。
3
- * 仅接受可选 v/= 前缀的严格三段式语义化版本,可携带预发布后缀与构建元数据;
3
+ * 仅接受可选 v/= 前缀的严格三段式语义化版本,可携带预发布后缀;
4
4
  * 其余形态(两段式、非数值等)返回 null,由调用方自行兜底。
5
5
  *
6
6
  * 本模块不依赖任何 Node 内建与 core 领域模块,可在无 loader 的原生
7
7
  * Node 直接以 .ts 形态导入(工程脚本域复用此处单一事实源)。
8
8
  */
9
9
  export function parseSemVer(v) {
10
- const match = v.trim().replace(/^[v=]/, "").match(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/);
10
+ const match = v.trim().replace(/^[v=]/, "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
11
11
  if (!match)
12
12
  return null;
13
- const [major, minor, patch] = match.slice(1, 4).map(Number);
14
- if (![major, minor, patch].every(Number.isSafeInteger))
15
- return null;
16
- if (match[4]?.split(".").some((id) => /^\d+$/.test(id) && id.length > 1 && id.startsWith("0")))
17
- return null;
18
13
  return {
19
- major,
20
- minor,
21
- patch,
14
+ major: parseInt(match[1], 10),
15
+ minor: parseInt(match[2], 10),
16
+ patch: parseInt(match[3], 10),
22
17
  prerelease: match[4],
23
- ...(match[5] === undefined ? {} : { build: match[5] }),
24
18
  };
25
19
  }
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * ActionDock 核心版本号单一事实源。
3
3
  */
4
- export declare const ACTIONDOCK_VERSION = "2.15.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.15.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.15.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.15.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"
@@ -1,72 +0,0 @@
1
- import type { ProjectConfig } from "./types.js";
2
- /**
3
- * 本地包条目;是否构成封闭集合由校验入口保证。
4
- */
5
- export interface LocalPackageEntry {
6
- packageId: string;
7
- packageRoot: string;
8
- projectConfig: ProjectConfig;
9
- }
10
- /**
11
- * 本地技能 / 包目录发现视图,不代表已经通过封闭集合校验。
12
- */
13
- export interface LocalSkillDirectory {
14
- type: "single" | "composite";
15
- root: string;
16
- packages: LocalPackageEntry[];
17
- }
18
- /**
19
- * 判定路径字符串是否为显式文件/目录路径形态(而非包标识符)。
20
- *
21
- * 规则:
22
- * - 以 .、/、~ 开头;
23
- * - 包含 / 或 \\,且不是 scoped 包(@scope/name 仅含一个 /);
24
- * - scoped 包若包含反斜杠或多于一个 /,亦视作路径。
25
- */
26
- export declare function isExplicitPath(p: string): boolean;
27
- /**
28
- * 对明确的本地封闭包集合实施一致性校验。
29
- *
30
- * 校验规则:
31
- * - 重复包标识防护:集合内不得存在相同 packageId;
32
- * - 依赖存在性与版本兼容性:声明的直接依赖必须在集合内存在且满足语义版本约束(复用 PackageGraphBuilder);
33
- * - 跨包 uses 目标完整性:uses 声明引用的目标包与目标 Action 必须在集合内存在。
34
- *
35
- * @param packages 封闭包条目集合
36
- */
37
- export declare function validateLocalSkillPackages(packages: Array<{
38
- packageId: string;
39
- packageRoot: string;
40
- projectConfig: ProjectConfig;
41
- }>): void;
42
- /**
43
- * 发现本地 Skill / 包目录结构(支持单包根目录与 packages/* 复合套件布局)。
44
- * 仅执行物理布局发现与清单加载,不执行跨包封闭依赖集合校验。
45
- *
46
- * @param directoryPath 待检查的目录路径(相对路径将基于 cwd 解析)
47
- * @param cwd 解析基准工作目录(默认为 process.cwd())
48
- */
49
- export declare function discoverLocalSkillDirectory(directoryPath: string, cwd?: string): LocalSkillDirectory | null;
50
- /**
51
- * 查询指定路径的本地包或技能目录(供 info / 查询命令使用)。
52
- * 单包不要求 node_modules 依赖闭包;复合目录仍要求封闭集合完整。
53
- *
54
- * @param directoryPath 待检查的目录路径(相对路径将基于 cwd 解析)
55
- * @param cwd 解析基准工作目录(默认为 process.cwd())
56
- */
57
- export declare function queryLocalPackageDirectory(directoryPath: string, cwd?: string): LocalSkillDirectory | null;
58
- /**
59
- * 探测指定路径是否为本地 Skill / 包目录(支持单包根目录与 packages/* 复合套件布局)。
60
- *
61
- * 封闭包装配与校验规则:
62
- * - 符号链接按目录布局正常解析;Action 与规程入口的文件边界由相应加载器校验;
63
- * - 单包布局:目录根部存在 actiondock.json,解析为包含单一包的 LocalSkillDirectory;
64
- * - 复合布局:目录根部存在 packages/ 目录,遍历其直接子目录,收集包含 actiondock.json 的子包;
65
- * - 集合一致性校验:通过 validateLocalSkillPackages 严格核查依赖闭包、版本兼容与 uses 目标;
66
- * - 异常透传:坏清单或权限异常原样抛出,严禁静默回退 Registry;
67
- * - 若非候选技能目录,返回 null。
68
- *
69
- * @param directoryPath 待检查的目录路径(相对路径将基于 cwd 解析)
70
- * @param cwd 解析基准工作目录(默认为 process.cwd())
71
- */
72
- export declare function inspectLocalPackageDirectory(directoryPath: string, cwd?: string): LocalSkillDirectory | null;