@actiondock/core 2.0.10 → 2.0.11-beta.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actiondock/core",
3
- "version": "2.0.10",
3
+ "version": "2.0.11-beta.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": "./src/index.ts",
@@ -27,7 +27,7 @@
27
27
  "test": "bun test"
28
28
  },
29
29
  "dependencies": {
30
- "@actiondock/sdk": "^2.0.10",
30
+ "@actiondock/sdk": "2.0.11-beta.0",
31
31
  "ajv": "^8.17.1",
32
32
  "ajv-formats": "^3.0.1",
33
33
  "yaml": "^2.7.0"
@@ -144,6 +144,14 @@ ad link "<skill_root>"
144
144
 
145
145
  > \`ad link\` 天然具备幂等性,同一 Package 多次执行会直接更新路径,可安全重复调用。
146
146
 
147
+ ### 动作参数契约按需调阅
148
+
149
+ 在调用未知参数的 Action 前,可在终端执行命令按需查阅该 Action 的输入输出模式与详细说明:
150
+
151
+ \`\`\`bash
152
+ ad action show ${pkgId}/${firstAction}
153
+ \`\`\`
154
+
147
155
  ### 执行 Action
148
156
 
149
157
  为避免多技能之间的 Action ID 命名冲突,建议统一使用带有 Package 前缀的完全限定 ID。
@@ -376,6 +384,125 @@ export function generateSkillMd(
376
384
  return generateStandaloneSkillMd(config, actions, playbooks, optionsOrBinaryPath.binaryRelPath || "./bin/action-bin");
377
385
  }
378
386
 
387
+ export interface CompositeSkillPackageInfo {
388
+ config: ProjectConfig;
389
+ actions: Array<{ id: string; description?: string }>;
390
+ playbooks: Array<{ id: string; name?: string; description?: string; filePath: string }>;
391
+ packageDir: string;
392
+ }
393
+
394
+ /**
395
+ * 生成多包聚合的复合模式 SKILL.md 文档。
396
+ */
397
+ export function generateCompositeSkillMd(
398
+ bundleName: string,
399
+ description: string,
400
+ packages: CompositeSkillPackageInfo[]
401
+ ): string {
402
+ const cleanName = bundleName.replace(/[^a-zA-Z0-9-_]/g, "-").toLowerCase();
403
+ const samplePkg = packages.find((p) => p.actions && p.actions.length > 0);
404
+ const sampleActionId = samplePkg
405
+ ? `${samplePkg.config.id}/${samplePkg.actions[0].id}`
406
+ : "sample.greet";
407
+
408
+ const actionSections = packages
409
+ .map((pkg) => {
410
+ const actList = pkg.actions
411
+ .map((a) => {
412
+ const desc = a.description ? `: ${a.description}` : "";
413
+ return `- \`${pkg.config.id}/${a.id}\`${desc}`;
414
+ })
415
+ .join("\n");
416
+ return `### ${pkg.config.name} (${pkg.config.id})\n${actList || "- 无可用 Action"}`;
417
+ })
418
+ .join("\n\n");
419
+
420
+ const playbookEntries: string[] = [];
421
+ for (const pkg of packages) {
422
+ for (const pb of pkg.playbooks) {
423
+ const relPath = `packages/${pkg.packageDir}/playbooks/${basename(pb.filePath)}`;
424
+ playbookEntries.push(`- [${pb.name || pb.id}](${relPath}): ${pb.description || "标准操作规程"}`);
425
+ }
426
+ }
427
+
428
+ const playbookSection =
429
+ playbookEntries.length > 0
430
+ ? `## 推荐操作规程\n\n涉及多步骤或业务流程时,优先遵循以下原位规程:\n\n${playbookEntries.join("\n")}\n\n---\n`
431
+ : "";
432
+
433
+ return `---
434
+ name: ${cleanName}
435
+ description: ${description}
436
+ ---
437
+
438
+ # ${bundleName} 复合技能套件
439
+
440
+ ${description}
441
+
442
+ ## ActionDock 运行时初始化
443
+
444
+ 本技能为 **ActionDock 复合工作区技能包**,聚合了多个功能包。智能体在初次调用或初始化时,在当前技能根目录执行注册命令:
445
+
446
+ \`\`\`bash
447
+ ad link "<skill_root>"
448
+ \`\`\`
449
+
450
+ > \`ad link\` 会自动识别并注册工作区下的所有子包,使其中的 Action 随时可以通过完全限定标识调用。
451
+
452
+ ## 动作参数契约按需调阅
453
+
454
+ 为节省上下文开销,各 Action 的详细参数结构不静态内嵌在说明书中。在调用未知参数的 Action 前,可在终端执行命令查阅输入输出约束:
455
+
456
+ \`\`\`bash
457
+ ad action show ${sampleActionId}
458
+ \`\`\`
459
+
460
+ ## 可用 Action 工具清单
461
+
462
+ ${actionSections}
463
+
464
+ ---
465
+
466
+ ${playbookSection}
467
+ ## 标准调用命令
468
+
469
+ 推荐使用参数文件传递内容,杜绝终端引号转义问题:
470
+
471
+ \`\`\`bash
472
+ cat << 'EOF' > /tmp/input.json
473
+ {
474
+ "param": "value"
475
+ }
476
+ EOF
477
+ ad run ${sampleActionId} --input-file /tmp/input.json
478
+ \`\`\`
479
+
480
+ ### 结构化响应解析
481
+
482
+ 所有 Action 执行结果均在 \`stdout\` 输出标准格式的 JSON 信封:
483
+
484
+ \`\`\`json
485
+ // 执行成功响应 (ok 为 true)
486
+ {
487
+ "ok": true,
488
+ "runId": "01J...",
489
+ "data": { ... }
490
+ }
491
+
492
+ // 执行失败响应 (ok 为 false)
493
+ {
494
+ "ok": false,
495
+ "runId": "01J...",
496
+ "error": {
497
+ "code": "ACTION_EXECUTION_FAILED",
498
+ "message": "错误详细描述信息"
499
+ }
500
+ }
501
+ \`\`\`
502
+ `;
503
+ }
504
+
505
+
379
506
  export interface GenerateSkillJsonOptions {
380
507
  mode?: "source" | "standalone";
381
508
  executable?: string;
@@ -108,10 +108,10 @@ export function initProject(targetDir: string, options: InitOptions = {}): void
108
108
  node: ">=22.12.0",
109
109
  },
110
110
  dependencies: {
111
- "@actiondock/sdk": "^2.0.10",
111
+ "@actiondock/sdk": "2.0.11-beta.0",
112
112
  },
113
113
  devDependencies: {
114
- "@actiondock/testing": "^2.0.10",
114
+ "@actiondock/testing": "2.0.11-beta.0",
115
115
  "@types/node": "^22.12.0",
116
116
  "tsx": "^4.19.0",
117
117
  "typescript": "^5.7.0",
@@ -348,6 +348,149 @@ async function projectHasAction(
348
348
  }
349
349
  }
350
350
 
351
+ function projectHasActionSync(
352
+ projectRoot: string,
353
+ actionsDir: string | undefined,
354
+ actionId: string
355
+ ): boolean {
356
+ const manifest = loadManifest(projectRoot);
357
+ if (manifest?.actions && actionId in manifest.actions) {
358
+ return true;
359
+ }
360
+ const dir = join(projectRoot, actionsDir || "actions");
361
+ if (!existsSync(dir)) {
362
+ return false;
363
+ }
364
+ if (actionId.includes("..") || actionId.startsWith("/") || actionId.startsWith("\\")) {
365
+ return false;
366
+ }
367
+ if (existsSync(join(dir, `${actionId}.ts`)) || existsSync(join(dir, `${actionId}.js`))) {
368
+ return true;
369
+ }
370
+ const relFile = actionId.replace(/\./g, "/");
371
+ if (existsSync(join(dir, `${relFile}.ts`)) || existsSync(join(dir, `${relFile}.js`))) {
372
+ return true;
373
+ }
374
+ return false;
375
+ }
376
+
377
+ export function resolveActionProjectSync(
378
+ actionIdentifier: string,
379
+ cwd: string = process.cwd(),
380
+ customHome?: string
381
+ ): ResolvedActionProject {
382
+ // 1. Check current directory / parent project
383
+ const currentRoot = findProjectRoot(cwd);
384
+ if (currentRoot) {
385
+ try {
386
+ const config = loadProjectConfig(currentRoot);
387
+ if (projectHasActionSync(currentRoot, config.actionsDir, actionIdentifier)) {
388
+ return {
389
+ projectRoot: currentRoot,
390
+ packageId: config.id,
391
+ actionId: actionIdentifier,
392
+ };
393
+ }
394
+ } catch {
395
+ // Ignore and proceed to registry lookup
396
+ }
397
+ }
398
+
399
+ // 2. Check if scoped format: <package-id>/<action-id> or <package-id>:<action-id>
400
+ let targetPackage: string | undefined;
401
+ let pureActionId = actionIdentifier;
402
+
403
+ if (actionIdentifier.includes("/")) {
404
+ const slashIdx = actionIdentifier.lastIndexOf("/");
405
+ targetPackage = actionIdentifier.slice(0, slashIdx);
406
+ pureActionId = actionIdentifier.slice(slashIdx + 1);
407
+ } else if (actionIdentifier.includes(":")) {
408
+ const colonIdx = actionIdentifier.lastIndexOf(":");
409
+ targetPackage = actionIdentifier.slice(0, colonIdx);
410
+ pureActionId = actionIdentifier.slice(colonIdx + 1);
411
+ }
412
+
413
+ const linkedList = listLinkedPackages(customHome);
414
+
415
+ if (targetPackage) {
416
+ let targetRoot: string | undefined;
417
+ let targetPkgId = targetPackage;
418
+
419
+ const pkg = linkedList.find(
420
+ (p) => p.id === targetPackage || getPackageSlug(p.id) === targetPackage
421
+ );
422
+ if (pkg && existsSync(pkg.path)) {
423
+ targetRoot = pkg.path;
424
+ targetPkgId = pkg.id;
425
+ } else if (currentRoot) {
426
+ try {
427
+ const config = loadProjectConfig(currentRoot);
428
+ if (config.id === targetPackage || getPackageSlug(config.id) === targetPackage) {
429
+ targetRoot = currentRoot;
430
+ targetPkgId = config.id;
431
+ }
432
+ } catch {
433
+ // Ignore
434
+ }
435
+ }
436
+
437
+ if (!targetRoot || !existsSync(targetRoot)) {
438
+ throw new Error(
439
+ `Linked package '${targetPackage}' not found or path no longer exists (${pkg?.path || "unregistered"}). Run 'ad link' in the package directory.`
440
+ );
441
+ }
442
+
443
+ const config = loadProjectConfig(targetRoot);
444
+ if (!projectHasActionSync(targetRoot, config.actionsDir, pureActionId)) {
445
+ throw new Error(`Action '${pureActionId}' not found in package '${targetPkgId}' (${targetRoot})`);
446
+ }
447
+
448
+ return {
449
+ projectRoot: targetRoot,
450
+ packageId: targetPkgId,
451
+ actionId: pureActionId,
452
+ };
453
+ }
454
+
455
+ // 3. Search across all linked packages
456
+ const matches: Array<{ entry: LinkedPackageEntry; actionId: string }> = [];
457
+
458
+ for (const pkg of linkedList) {
459
+ if (!existsSync(pkg.path)) continue;
460
+ try {
461
+ const config = loadProjectConfig(pkg.path);
462
+ if (projectHasActionSync(pkg.path, config.actionsDir, actionIdentifier)) {
463
+ matches.push({ entry: pkg, actionId: actionIdentifier });
464
+ }
465
+ } catch {
466
+ // Ignore invalid linked package
467
+ }
468
+ }
469
+
470
+ if (matches.length === 1) {
471
+ return {
472
+ projectRoot: matches[0].entry.path,
473
+ packageId: matches[0].entry.id,
474
+ actionId: matches[0].actionId,
475
+ };
476
+ }
477
+
478
+ if (matches.length > 1) {
479
+ const pkgList = matches.map((m) => `'${m.entry.id}'`).join(", ");
480
+ throw new Error(
481
+ `Action '${actionIdentifier}' is provided by multiple linked packages: ${pkgList}. Please specify using '<package-id>/${actionIdentifier}'.`
482
+ );
483
+ }
484
+
485
+ if (currentRoot) {
486
+ throw new Error(`Action '${actionIdentifier}' not found in current project or any linked packages`);
487
+ } else {
488
+ throw new Error(
489
+ `Action '${actionIdentifier}' not found. You are not in an ActionDock project, and no linked package provides '${actionIdentifier}'. Use 'ad link' to register your package.`
490
+ );
491
+ }
492
+ }
493
+
351
494
  export async function resolveActionProject(
352
495
  actionIdentifier: string,
353
496
  cwd: string = process.cwd(),
@@ -375,11 +518,11 @@ export async function resolveActionProject(
375
518
  let pureActionId = actionIdentifier;
376
519
 
377
520
  if (actionIdentifier.includes("/")) {
378
- const slashIdx = actionIdentifier.indexOf("/");
521
+ const slashIdx = actionIdentifier.lastIndexOf("/");
379
522
  targetPackage = actionIdentifier.slice(0, slashIdx);
380
523
  pureActionId = actionIdentifier.slice(slashIdx + 1);
381
524
  } else if (actionIdentifier.includes(":")) {
382
- const colonIdx = actionIdentifier.indexOf(":");
525
+ const colonIdx = actionIdentifier.lastIndexOf(":");
383
526
  targetPackage = actionIdentifier.slice(0, colonIdx);
384
527
  pureActionId = actionIdentifier.slice(colonIdx + 1);
385
528
  }
@@ -3,6 +3,7 @@ import type {
3
3
  ActionContext,
4
4
  ActionDefinition,
5
5
  ActionInvoker,
6
+ ActionRef,
6
7
  Config,
7
8
  Logger,
8
9
  ProcessAPI,
@@ -196,7 +197,7 @@ export interface ContextOptions {
196
197
  progress?: ProgressReporter;
197
198
  logger?: Logger;
198
199
  onActionInvoke?: (
199
- action: ActionDefinition,
200
+ action: ActionDefinition | ActionRef | string,
200
201
  input: unknown,
201
202
  parentRunId?: string
202
203
  ) => Promise<unknown>;
@@ -221,7 +222,10 @@ export function createActionContext(options: ContextOptions): ActionContext {
221
222
  const currentRootRunId = options.rootRunId || options.parentRunId || currentRunId;
222
223
 
223
224
  const invoker: ActionInvoker = {
224
- async invoke<I, O>(action: ActionDefinition<I, O>, input: I): Promise<O> {
225
+ async invoke<I, O>(
226
+ action: ActionDefinition<I, O> | ActionRef | string,
227
+ input?: I
228
+ ): Promise<O> {
225
229
  if (options.onActionInvoke) {
226
230
  return (await options.onActionInvoke(
227
231
  action as any,
@@ -1,7 +1,9 @@
1
+ import { existsSync } from "node:fs";
1
2
  import { randomUUID } from "node:crypto";
2
3
  import type {
3
4
  ActionContext,
4
5
  ActionDefinition,
6
+ ActionRef,
5
7
  ExecutionResult,
6
8
  JsonValue,
7
9
  Logger,
@@ -10,7 +12,10 @@ import type {
10
12
  RuntimeError,
11
13
  RunRecord,
12
14
  } from "@actiondock/sdk";
15
+ import { ActionResolver } from "../catalog/action-resolver";
16
+ import { loadActions, loadProjectConfig } from "../project/loader";
13
17
  import type { ProjectConfig } from "../project/types";
18
+ import { resolveActionProject } from "../registry/registry";
14
19
  import { validateSchema } from "../schema/validator";
15
20
  import type { RuntimeStorage, TerminalRunStatus } from "../storage/types";
16
21
  import { createActionContext, StderrLogger } from "./context";
@@ -31,6 +36,11 @@ export interface RunnerOptions {
31
36
  actions?: Map<string, ActionDefinition>;
32
37
  /** 外部注入的进程执行器 */
33
38
  process?: ProcessAPI;
39
+ /** 动态解析跨包或未注册 Action 的委托函数 */
40
+ actionResolver?: (
41
+ ref: ActionRef | string,
42
+ currentPackageId?: string
43
+ ) => ActionDefinition | undefined | Promise<ActionDefinition | undefined>;
34
44
  }
35
45
 
36
46
  /**
@@ -95,6 +105,10 @@ export class ActionRunner {
95
105
  private projectConfig?: ProjectConfig;
96
106
  private configOverrides: Record<string, unknown>;
97
107
  private actions: Map<string, ActionDefinition>;
108
+ private actionResolver?: (
109
+ ref: ActionRef | string,
110
+ currentPackageId?: string
111
+ ) => ActionDefinition | undefined | Promise<ActionDefinition | undefined>;
98
112
 
99
113
  constructor(options: RunnerOptions) {
100
114
  this.packageId = options.packageId;
@@ -102,6 +116,7 @@ export class ActionRunner {
102
116
  this.projectConfig = options.projectConfig;
103
117
  this.configOverrides = options.configOverrides || {};
104
118
  this.actions = options.actions || new Map();
119
+ this.actionResolver = options.actionResolver;
105
120
  }
106
121
 
107
122
  /**
@@ -118,6 +133,86 @@ export class ActionRunner {
118
133
  return this.actions.get(id);
119
134
  }
120
135
 
136
+ /**
137
+ * 动态解析 Action(支持本地注册表、自定义解析器委托与已链接包目录索引检索)。
138
+ *
139
+ * @param actionOrRef Action 定义对象、引用或标识符
140
+ * @returns 解析出的 ActionDefinition,若未找到则返回 undefined
141
+ */
142
+ public async resolveAction(
143
+ actionOrRef: ActionDefinition | ActionRef | string
144
+ ): Promise<ActionDefinition | undefined> {
145
+ if (
146
+ typeof actionOrRef === "object" &&
147
+ "run" in actionOrRef &&
148
+ typeof (actionOrRef as any).run === "function"
149
+ ) {
150
+ return actionOrRef as ActionDefinition;
151
+ }
152
+
153
+ const ref = actionOrRef as ActionRef | string;
154
+ const parsed = ActionResolver.parseRef(ref);
155
+ const targetActionId = parsed.actionId;
156
+ const targetPackageId = parsed.packageId;
157
+
158
+ // 1. 本地 actions 映射表优先检索
159
+ if (targetPackageId && targetPackageId !== this.packageId) {
160
+ if (this.actions.has(`${targetPackageId}/${targetActionId}`)) {
161
+ return this.actions.get(`${targetPackageId}/${targetActionId}`);
162
+ }
163
+ } else {
164
+ if (this.actions.has(targetActionId)) {
165
+ return this.actions.get(targetActionId);
166
+ }
167
+ if (this.packageId && this.actions.has(`${this.packageId}/${targetActionId}`)) {
168
+ return this.actions.get(`${this.packageId}/${targetActionId}`);
169
+ }
170
+ }
171
+
172
+ // 2. 外部注入的自定义 actionResolver 调度
173
+ if (this.actionResolver) {
174
+ const customResolved = await this.actionResolver(ref, this.packageId);
175
+ if (customResolved) {
176
+ if (targetPackageId && targetPackageId !== this.packageId) {
177
+ this.actions.set(`${targetPackageId}/${targetActionId}`, customResolved);
178
+ } else {
179
+ this.actions.set(targetActionId, customResolved);
180
+ if (this.packageId) {
181
+ this.actions.set(`${this.packageId}/${targetActionId}`, customResolved);
182
+ }
183
+ }
184
+ return customResolved;
185
+ }
186
+ }
187
+
188
+ // 3. 基于全局链接注册表与目录索引的动态寻址与按需加载
189
+ try {
190
+ const identifier = targetPackageId
191
+ ? `${targetPackageId}/${targetActionId}`
192
+ : targetActionId;
193
+ const resolved = await resolveActionProject(identifier);
194
+ if (resolved && existsSync(resolved.projectRoot)) {
195
+ const config = loadProjectConfig(resolved.projectRoot);
196
+ const actionsMap = await loadActions(resolved.projectRoot, config.actionsDir, {
197
+ autoInstall: false,
198
+ });
199
+ const matched = actionsMap.get(resolved.actionId);
200
+ if (matched) {
201
+ this.actions.set(`${resolved.packageId}/${resolved.actionId}`, matched);
202
+ // 仅当目标包就是当前项目时才注册短标识符,避免跨包动态载入污染全局短标识符
203
+ if (!targetPackageId || resolved.packageId === this.packageId) {
204
+ this.actions.set(resolved.actionId, matched);
205
+ }
206
+ return matched;
207
+ }
208
+ }
209
+ } catch {
210
+ // 忽略寻址异常并返回 undefined
211
+ }
212
+
213
+ return undefined;
214
+ }
215
+
121
216
  /**
122
217
  * 获取当前 Runner 已注册的所有 Action 列表。
123
218
  */
@@ -128,13 +223,13 @@ export class ActionRunner {
128
223
  /**
129
224
  * 异步启动 Action 的执行并立即返回 ExecutionHandle 句柄。
130
225
  *
131
- * @param actionOrId Action 定义对象或已注册的 Action ID
226
+ * @param actionOrId Action 定义对象、引用或标识符
132
227
  * @param input 传递给 Action 的输入数据
133
228
  * @param options 执行控制选项(超时、取消信号、父运行 ID 等)
134
229
  * @returns 包含 runId、result Promise 和 cancel 方法的执行句柄
135
230
  */
136
231
  start(
137
- actionOrId: ActionDefinition | string,
232
+ actionOrId: ActionDefinition | ActionRef | string,
138
233
  input: unknown = {},
139
234
  options: ExecutionStartOptions = {}
140
235
  ): ExecutionHandle {
@@ -142,30 +237,47 @@ export class ActionRunner {
142
237
  const startedAt = new Date().toISOString();
143
238
  const callStack = [...(options.callStack || [])];
144
239
 
145
- let action: ActionDefinition;
146
- if (typeof actionOrId === "string") {
147
- const found = this.actions.get(actionOrId);
148
- if (!found) {
149
- const error: RuntimeError = {
150
- code: "ACTION_NOT_FOUND",
151
- message: `Action '${actionOrId}' not found in registry`,
152
- };
153
- return {
154
- runId,
155
- result: Promise.resolve({ ok: false, runId, error }),
156
- cancel: () => false,
157
- };
158
- }
159
- action = found;
240
+ let action: ActionDefinition | undefined;
241
+ let targetActionId: string;
242
+ let targetPackageId: string = this.packageId;
243
+
244
+ if (
245
+ typeof actionOrId === "object" &&
246
+ "run" in actionOrId &&
247
+ typeof (actionOrId as any).run === "function"
248
+ ) {
249
+ action = actionOrId as ActionDefinition;
250
+ targetActionId = action.id;
160
251
  } else {
161
- action = actionOrId;
252
+ const parsed = ActionResolver.parseRef(actionOrId as ActionRef | string);
253
+ targetActionId = parsed.actionId;
254
+ if (parsed.packageId) {
255
+ targetPackageId = parsed.packageId;
256
+ }
257
+
258
+ if (parsed.packageId && parsed.packageId !== this.packageId) {
259
+ action = this.actions.get(`${parsed.packageId}/${targetActionId}`);
260
+ } else {
261
+ action =
262
+ this.actions.get(targetActionId) ||
263
+ (this.packageId ? this.actions.get(`${this.packageId}/${targetActionId}`) : undefined);
264
+ }
162
265
  }
163
266
 
164
267
  // 1. 环路死锁检测 (Cycle Detection)
165
- if (callStack.includes(action.id)) {
268
+ const isExternal = Boolean(targetPackageId && targetPackageId !== this.packageId);
269
+ const callKey = isExternal
270
+ ? `${targetPackageId}/${targetActionId}`
271
+ : targetActionId;
272
+
273
+ const hasCycle = isExternal
274
+ ? callStack.includes(callKey)
275
+ : (callStack.includes(callKey) || (this.packageId ? callStack.includes(`${this.packageId}/${targetActionId}`) : false));
276
+
277
+ if (hasCycle) {
166
278
  const error: RuntimeError = {
167
279
  code: "ACTION_CYCLE_DETECTED",
168
- message: `Cycle detected in action invocation: ${callStack.join(" -> ")} -> ${action.id}`,
280
+ message: `Cycle detected in action invocation: ${callStack.join(" -> ")} -> ${callKey}`,
169
281
  };
170
282
  return {
171
283
  runId,
@@ -173,10 +285,10 @@ export class ActionRunner {
173
285
  cancel: () => false,
174
286
  };
175
287
  }
176
- callStack.push(action.id);
288
+ callStack.push(callKey);
177
289
 
178
- // 2. 输入参数 JSON Schema 校验
179
- if (action.inputSchema) {
290
+ // 2. 输入参数 JSON Schema 校验(若 action 已就绪)
291
+ if (action?.inputSchema) {
180
292
  const val = validateSchema(action.inputSchema, input);
181
293
  if (!val.valid) {
182
294
  const error: RuntimeError = {
@@ -197,9 +309,9 @@ export class ActionRunner {
197
309
  id: runId,
198
310
  rootRunId: options.rootRunId || options.parentRunId || runId,
199
311
  parentRunId: options.parentRunId,
200
- packageId: this.packageId,
201
- packageInstanceId: options.packageInstanceId || this.packageId,
202
- actionId: action.id,
312
+ packageId: targetPackageId,
313
+ packageInstanceId: options.packageInstanceId || targetPackageId,
314
+ actionId: targetActionId,
203
315
  generationId: options.generationId || "1",
204
316
  ownerId: options.ownerId || "local",
205
317
  status: "running",
@@ -257,7 +369,7 @@ export class ActionRunner {
257
369
  signal: controller.signal,
258
370
  process: options.process,
259
371
  progress: options.progress,
260
- logger: options.logger || new StderrLogger(action.id),
372
+ logger: options.logger || new StderrLogger(action?.id || targetActionId),
261
373
  onActionInvoke: async (childAction, childInput, parentRunId) => {
262
374
  const childResult = await this.execute(childAction, childInput, {
263
375
  rootRunId: initialRun.rootRunId,
@@ -293,18 +405,44 @@ export class ActionRunner {
293
405
 
294
406
  const executionPromise = (async (): Promise<ExecutionResult> => {
295
407
  try {
408
+ let currentAction = action;
409
+ if (!currentAction) {
410
+ currentAction = await this.resolveAction(actionOrId);
411
+ if (!currentAction) {
412
+ const error: RuntimeError = {
413
+ code: "ACTION_NOT_FOUND",
414
+ message: `Action '${targetActionId}' not found in registry or linked packages`,
415
+ };
416
+ finalizeRun("failed", undefined, error);
417
+ return { ok: false, runId, error };
418
+ }
419
+
420
+ if (currentAction.inputSchema) {
421
+ const val = validateSchema(currentAction.inputSchema, input);
422
+ if (!val.valid) {
423
+ const error: RuntimeError = {
424
+ code: "INPUT_VALIDATION_FAILED",
425
+ message: `Input schema validation failed for action '${currentAction.id}'`,
426
+ details: val.errors,
427
+ };
428
+ finalizeRun("failed", undefined, error);
429
+ return { ok: false, runId, error };
430
+ }
431
+ }
432
+ }
433
+
296
434
  const rawOutput = await Promise.race([
297
- Promise.resolve().then(() => action.run(input, ctx)),
435
+ Promise.resolve().then(() => currentAction!.run(input, ctx)),
298
436
  abortPromise,
299
437
  ]);
300
438
 
301
439
  // 输出结果 Schema 校验
302
- if (action.outputSchema) {
303
- const outVal = validateSchema(action.outputSchema, rawOutput);
440
+ if (currentAction.outputSchema) {
441
+ const outVal = validateSchema(currentAction.outputSchema, rawOutput);
304
442
  if (!outVal.valid) {
305
443
  const error: RuntimeError = {
306
444
  code: "OUTPUT_VALIDATION_FAILED",
307
- message: `Output schema validation failed for action '${action.id}'`,
445
+ message: `Output schema validation failed for action '${currentAction.id}'`,
308
446
  details: outVal.errors,
309
447
  };
310
448
  finalizeRun("failed", undefined, error);
@@ -375,12 +513,12 @@ export class ActionRunner {
375
513
  /**
376
514
  * 同步等待方式执行指定 Action,直接返回 ExecutionResult 信封结果。
377
515
  *
378
- * @param actionOrId Action 定义对象或 ID
516
+ * @param actionOrId Action 定义对象、引用或标识符
379
517
  * @param input 输入参数
380
518
  * @param options 执行控制选项
381
519
  */
382
520
  async execute(
383
- actionOrId: ActionDefinition | string,
521
+ actionOrId: ActionDefinition | ActionRef | string,
384
522
  input: unknown = {},
385
523
  options: ExecutionStartOptions = {}
386
524
  ): Promise<ExecutionResult> {