@actiondock/cli 2.13.0 → 2.15.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.
@@ -1,13 +1,13 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
2
  import { dirname, isAbsolute, relative, resolve } from "node:path";
3
- import { assertPathWithinRoot, loadActions, loadManifest, loadPlaybooks, saveManifest, filterWithFallbackInfo, } from "@actiondock/core/project";
4
- import { DefaultActionCatalog, PackageGraphBuilder, resolveAction, resolvePlaybook, } from "@actiondock/core/graph";
3
+ import { assertPathWithinRoot, loadManifest, loadPlaybooks, saveManifest, filterWithFallbackInfo, } from "@actiondock/core/project";
4
+ import { DefaultActionCatalog, resolveAction, resolvePlaybook, } from "@actiondock/core/graph";
5
5
  import { fetchRemotePlaybooks, fetchRemotePlaybookShow, } from "@actiondock/core/profile";
6
6
  import { loadProjectConfig, } from "@actiondock/core";
7
7
  import { listLinkedPackages, } from "@actiondock/core/registry";
8
8
  import { ArgumentError, CliError, ExecutionError, NO_PROJECT_NO_LINKED_MESSAGE, wrapAsExecutionError, } from "../errors.js";
9
- import { renderPlaybookDetail, renderPlaybookList, renderResult, writeStdout, } from "../renderer.js";
10
- import { applyTargetOptions, getEffectiveOptions, remoteTargetSuffix, requirePackageRoot, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, resolveTargetFromOptions, } from "../utils/index.js";
9
+ import { renderPlaybookDetail, renderPlaybookList, renderResult, writeStderr, writeStdout, } from "../renderer.js";
10
+ import { applyTargetOptions, buildScopedPackageGraph, getEffectiveOptions, remoteTargetSuffix, requirePackageRoot, resolveCommandTarget, resolveFallbackStrategy, resolveIntent, } from "../utils/index.js";
11
11
  /**
12
12
  * 注册 Playbook 命令集合(list, show, validate, create)。
13
13
  *
@@ -34,14 +34,15 @@ export function registerPlaybookCommands(program, context) {
34
34
  const hints = [
35
35
  "Tip: Run 'ad playbook show <id>' to inspect procedure steps before execution.",
36
36
  ];
37
- // 1. 远端服务分支
38
- const target = resolveTargetFromOptions(options, context);
39
- if (target.type === "remote") {
40
- const remotePbs = await fetchRemotePlaybooks(target.serverUrl, target.token, {
37
+ // 1. 目标拓扑统一解析
38
+ const target = resolveCommandTarget(options, context);
39
+ // 2. 远端服务分支
40
+ if (target.isRemote) {
41
+ const remotePbs = await fetchRemotePlaybooks(target.resolved.serverUrl, target.resolved.token, {
41
42
  intent: effectiveIntent,
42
43
  package: options.package,
43
44
  allowInsecureHttp: Boolean(options.allowInsecureHttp),
44
- insecure: target.insecure,
45
+ insecure: target.resolved.insecure,
45
46
  });
46
47
  if (isMachine) {
47
48
  renderResult({ items: remotePbs, hints }, { json: options.json, context });
@@ -49,24 +50,44 @@ export function registerPlaybookCommands(program, context) {
49
50
  }
50
51
  renderResult(remotePbs, {
51
52
  json: options.json,
52
- humanFormatter: () => renderPlaybookList(remotePbs, `Playbooks ${remoteTargetSuffix(target)}`, false, effectiveIntent),
53
+ humanFormatter: () => renderPlaybookList(remotePbs, `Playbooks ${remoteTargetSuffix(target.resolved)}`, false, effectiveIntent),
53
54
  context,
54
55
  });
55
56
  return;
56
57
  }
57
- // 2. 本地工程模式
58
- const targetRoot = options.package
59
- ? requirePackageRoot(options.package).root
60
- : resolveLocalPackageRoot();
61
- if (targetRoot) {
62
- const config = loadProjectConfig(targetRoot);
63
- const playbooks = loadPlaybooks(targetRoot, config.playbooksDir);
64
- const rawList = Array.from(playbooks.values()).map((pb) => ({
65
- id: pb.id,
66
- description: pb.description,
67
- packageId: config.id,
68
- }));
69
- const filterRes = filterWithFallbackInfo(rawList, effectiveIntent, [(p) => p.id, (p) => p.description], shouldFallback);
58
+ // 3. 本地模式(单包技能、复合技能或本地工程)
59
+ if (target.localSkill || target.targetPackageRoot) {
60
+ const rawList = [];
61
+ let title;
62
+ if (target.localSkill) {
63
+ const isComposite = target.localSkill.type === "composite";
64
+ for (const pkg of target.localSkill.packages) {
65
+ const playbooks = loadPlaybooks(pkg.packageRoot, pkg.projectConfig.playbooksDir, pkg.projectConfig);
66
+ for (const pb of playbooks.values()) {
67
+ rawList.push({
68
+ id: isComposite ? `${pkg.packageId}/${pb.id}` : pb.id,
69
+ description: pb.description,
70
+ packageId: pkg.packageId,
71
+ });
72
+ }
73
+ }
74
+ title = isComposite
75
+ ? `Playbooks in ${options.package || target.localSkill.root}`
76
+ : `Playbooks in ${target.localSkill.packages[0].packageId} (${target.localSkill.packages[0].packageRoot})`;
77
+ }
78
+ else {
79
+ const config = loadProjectConfig(target.targetPackageRoot);
80
+ const playbooks = loadPlaybooks(target.targetPackageRoot, config.playbooksDir, config);
81
+ for (const pb of playbooks.values()) {
82
+ rawList.push({
83
+ id: pb.id,
84
+ description: pb.description,
85
+ packageId: config.id,
86
+ });
87
+ }
88
+ title = `Playbooks in ${config.id} (${target.targetPackageRoot})`;
89
+ }
90
+ const filterRes = filterWithFallbackInfo(rawList, effectiveIntent, [(p) => p.id, (p) => p.description || ""], shouldFallback);
70
91
  if (isMachine) {
71
92
  if (filterRes.isFallback) {
72
93
  renderResult({ items: filterRes.items, isFallback: true, matchedCount: 0, hints }, { json: options.json, context });
@@ -81,7 +102,7 @@ export function registerPlaybookCommands(program, context) {
81
102
  }
82
103
  renderResult(filterRes.items, {
83
104
  json: options.json,
84
- humanFormatter: () => renderPlaybookList(filterRes.items, `Playbooks in ${config.id} (${targetRoot})`, filterRes.isFallback, effectiveIntent),
105
+ humanFormatter: () => renderPlaybookList(filterRes.items, title, filterRes.isFallback, effectiveIntent),
85
106
  context,
86
107
  });
87
108
  return;
@@ -102,25 +123,22 @@ export function registerPlaybookCommands(program, context) {
102
123
  }
103
124
  const aggregated = [];
104
125
  for (const pkg of linkedList) {
105
- if (!existsSync(pkg.path))
126
+ if (!existsSync(pkg.path)) {
127
+ writeStderr(`Skipping inaccessible linked package '${pkg.id}' at '${pkg.path}'`, context);
106
128
  continue;
107
- try {
108
- const config = loadProjectConfig(pkg.path);
109
- const pbs = loadPlaybooks(pkg.path, config.playbooksDir);
110
- const list = Array.from(pbs.values()).map((p) => ({
111
- id: p.id,
112
- description: p.description,
113
- }));
114
- aggregated.push({
115
- packageId: pkg.id,
116
- packageName: pkg.name,
117
- path: pkg.path,
118
- playbooks: list,
119
- });
120
- }
121
- catch {
122
- // 单个链接包清单损坏或 Playbook 加载失败时跳过,继续聚合其余包
123
129
  }
130
+ const config = loadProjectConfig(pkg.path);
131
+ const pbs = loadPlaybooks(pkg.path, config.playbooksDir, config);
132
+ const list = Array.from(pbs.values()).map((p) => ({
133
+ id: p.id,
134
+ description: p.description,
135
+ }));
136
+ aggregated.push({
137
+ packageId: pkg.id,
138
+ packageName: pkg.name,
139
+ path: pkg.path,
140
+ playbooks: list,
141
+ });
124
142
  }
125
143
  let filteredPackages = [];
126
144
  let isFallback = false;
@@ -183,13 +201,14 @@ export function registerPlaybookCommands(program, context) {
183
201
  if (!id) {
184
202
  throw new ArgumentError("Playbook ID is required for show");
185
203
  }
186
- // 1. 远端服务分支
187
- const target = resolveTargetFromOptions(options, context);
188
- if (target.type === "remote") {
204
+ // 1. 目标拓扑统一解析
205
+ const target = resolveCommandTarget(options, context);
206
+ // 2. 远端服务分支
207
+ if (target.isRemote) {
189
208
  try {
190
- const detail = await fetchRemotePlaybookShow(target.serverUrl, id, target.token, {
209
+ const detail = await fetchRemotePlaybookShow(target.resolved.serverUrl, target.resolvePlaybookRef(id), target.resolved.token, {
191
210
  allowInsecureHttp: Boolean(options.allowInsecureHttp),
192
- insecure: target.insecure,
211
+ insecure: target.resolved.insecure,
193
212
  });
194
213
  renderResult(detail, {
195
214
  json: options.json,
@@ -203,20 +222,15 @@ export function registerPlaybookCommands(program, context) {
203
222
  throw new ArgumentError(`${err.message}\n${hint}`, err?.details, err?.code, hint);
204
223
  }
205
224
  }
206
- // 2. 本地项目模式
207
- let showTarget = id;
208
- if (options.package && !id.includes("/")) {
209
- requirePackageRoot(options.package);
210
- showTarget = `${options.package}/${id}`;
211
- }
225
+ // 3. 本地分支(单包技能、复合技能、本地工程或已链接包)
226
+ const showTarget = target.resolvePlaybookRef(id);
212
227
  let resolved;
213
228
  try {
214
- const graph = new PackageGraphBuilder({
215
- root: resolveLocalPackageRoot() || undefined,
216
- customHome: context?.customHome,
217
- allowDevLinks: true,
218
- }).buildSync();
219
- resolved = resolvePlaybook(showTarget, { graph });
229
+ const graph = buildScopedPackageGraph(target, context);
230
+ const caller = target.localSkill
231
+ ? (target.localSkill.type === "single" ? target.localSkill.packages[0].packageId : undefined)
232
+ : (target.targetPackageRoot ? loadProjectConfig(target.targetPackageRoot).id : undefined);
233
+ resolved = resolvePlaybook(showTarget, { graph, caller });
220
234
  }
221
235
  catch (err) {
222
236
  const hint = "Tip: Run 'ad playbook list' to discover available playbooks.";
@@ -249,36 +263,73 @@ export function registerPlaybookCommands(program, context) {
249
263
  .option("--json", "Output as JSON")
250
264
  .action(async (id, rawOptions, cmd) => {
251
265
  const options = getEffectiveOptions(rawOptions, cmd);
252
- const root = options.package
253
- ? requirePackageRoot(options.package).root
254
- : resolveLocalPackageRoot();
266
+ // 保持本地校验定位,profile 始终为 local
267
+ const target = resolveCommandTarget({ ...options, profile: "local" }, context);
268
+ const graph = buildScopedPackageGraph(target, context);
269
+ const catalog = new DefaultActionCatalog(graph);
255
270
  const targets = [];
256
- if (root) {
271
+ if (target.localSkill) {
272
+ if (id) {
273
+ const showTarget = target.resolvePlaybookRef(id);
274
+ const caller = target.localSkill.type === "single" ? target.localSkill.packages[0].packageId : undefined;
275
+ let resolved;
276
+ try {
277
+ resolved = resolvePlaybook(showTarget, { graph, caller });
278
+ }
279
+ catch (err) {
280
+ throw new ArgumentError(err.message, err?.details, err?.code);
281
+ }
282
+ targets.push({
283
+ root: resolved.projectRoot,
284
+ packageId: resolved.packageId,
285
+ playbooks: [resolved.playbook],
286
+ });
287
+ }
288
+ else {
289
+ for (const pkg of target.localSkill.packages) {
290
+ const playbooks = loadPlaybooks(pkg.packageRoot, pkg.projectConfig.playbooksDir);
291
+ targets.push({
292
+ root: pkg.packageRoot,
293
+ packageId: pkg.packageId,
294
+ playbooks: Array.from(playbooks.values()),
295
+ });
296
+ }
297
+ }
298
+ }
299
+ else if (target.targetPackageRoot) {
300
+ const root = target.targetPackageRoot;
257
301
  const config = loadProjectConfig(root);
258
- const playbooks = loadPlaybooks(root, config.playbooksDir);
259
302
  if (id) {
260
- const pb = playbooks.get(id);
261
- if (!pb) {
262
- throw new ArgumentError(`Playbook '${id}' not found in package '${config.id}'`);
303
+ const showTarget = target.resolvePlaybookRef(id);
304
+ let resolved;
305
+ try {
306
+ resolved = resolvePlaybook(showTarget, { graph, caller: config.id });
263
307
  }
264
- targets.push({ root, packageId: config.id, playbooks: [pb] });
308
+ catch (err) {
309
+ throw new ArgumentError(err.message, err?.details, err?.code);
310
+ }
311
+ targets.push({
312
+ root: resolved.projectRoot,
313
+ packageId: resolved.packageId,
314
+ playbooks: [resolved.playbook],
315
+ });
265
316
  }
266
317
  else {
267
- targets.push({ root, packageId: config.id, playbooks: Array.from(playbooks.values()) });
318
+ const playbooks = loadPlaybooks(root, config.playbooksDir);
319
+ targets.push({
320
+ root,
321
+ packageId: config.id,
322
+ playbooks: Array.from(playbooks.values()),
323
+ });
268
324
  }
269
325
  }
270
326
  else if (id) {
271
327
  let resolved;
272
328
  try {
273
- const graph = new PackageGraphBuilder({
274
- root: resolveLocalPackageRoot() || undefined,
275
- customHome: context?.customHome,
276
- allowDevLinks: true,
277
- }).buildSync();
278
329
  resolved = resolvePlaybook(id, { graph });
279
330
  }
280
331
  catch (err) {
281
- throw new ArgumentError(err.message);
332
+ throw new ArgumentError(err.message, err?.details, err?.code);
282
333
  }
283
334
  targets.push({
284
335
  root: resolved.projectRoot,
@@ -292,34 +343,22 @@ export function registerPlaybookCommands(program, context) {
292
343
  throw new ArgumentError(`${NO_PROJECT_NO_LINKED_MESSAGE} Please run 'ad link' inside a package first.`);
293
344
  }
294
345
  for (const pkg of linkedList) {
295
- if (!existsSync(pkg.path))
346
+ if (!existsSync(pkg.path)) {
347
+ writeStderr(`Skipping inaccessible linked package '${pkg.id}' at '${pkg.path}'`, context);
296
348
  continue;
297
- try {
298
- const config = loadProjectConfig(pkg.path);
299
- const playbooks = loadPlaybooks(pkg.path, config.playbooksDir);
300
- targets.push({
301
- root: pkg.path,
302
- packageId: pkg.id,
303
- playbooks: Array.from(playbooks.values()),
304
- });
305
- }
306
- catch {
307
- // 单个链接包清单损坏或 Playbook 加载失败时跳过,不阻断整体校验
308
349
  }
350
+ const config = loadProjectConfig(pkg.path);
351
+ const playbooks = loadPlaybooks(pkg.path, config.playbooksDir, config);
352
+ targets.push({
353
+ root: pkg.path,
354
+ packageId: pkg.id,
355
+ playbooks: Array.from(playbooks.values()),
356
+ });
309
357
  }
310
358
  }
311
359
  const results = [];
312
- const graph = new PackageGraphBuilder({
313
- root: resolveLocalPackageRoot() || undefined,
314
- customHome: context?.customHome,
315
- allowDevLinks: true,
316
- }).buildSync();
317
- const catalog = new DefaultActionCatalog(graph);
318
- for (const target of targets) {
319
- const config = loadProjectConfig(target.root);
320
- const manifest = loadManifest(target.root);
321
- const actions = await loadActions(target.root, config.actionsDir);
322
- for (const pb of target.playbooks) {
360
+ for (const targetItem of targets) {
361
+ for (const pb of targetItem.playbooks) {
323
362
  const warnings = [];
324
363
  const errors = [];
325
364
  if (!pb.id) {
@@ -330,26 +369,30 @@ export function registerPlaybookCommands(program, context) {
330
369
  }
331
370
  if (pb.actions && Array.isArray(pb.actions)) {
332
371
  for (const actRef of pb.actions) {
333
- if (actRef.includes("/")) {
372
+ if (typeof actRef !== "string" || !actRef.trim()) {
373
+ errors.push(`Referenced action must be a non-empty string, received ${JSON.stringify(actRef)}`);
374
+ continue;
375
+ }
376
+ const trimmed = actRef.trim();
377
+ if (trimmed.includes("/")) {
334
378
  try {
335
- resolveAction(actRef, { graph, catalog, caller: target.packageId });
379
+ resolveAction(trimmed, { graph, catalog, caller: targetItem.packageId });
336
380
  }
337
381
  catch (e) {
338
- errors.push(`Referenced cross-package action '${actRef}' not resolvable: ${e.message}. Hint: Action '${actRef}' not found. Run 'ad action create ${actRef}' to create local action, or 'ad add <pkg>' to install external dependency.`);
382
+ errors.push(`Referenced cross-package action '${trimmed}' not resolvable: ${e.message}. Hint: Action '${trimmed}' not found. Run 'ad action create ${trimmed}' to create local action, or 'ad add <pkg>' to install external dependency.`);
339
383
  }
340
384
  }
341
385
  else {
342
- const foundInActions = actions.has(actRef);
343
- const foundInManifest = Boolean(manifest?.actions?.[actRef]);
344
- if (!foundInActions && !foundInManifest) {
345
- errors.push(`Referenced action '${actRef}' not found in project actions. Hint: Action '${actRef}' not found. Run 'ad action create ${actRef}' to create local action, or 'ad add <pkg>' to install external dependency.`);
386
+ // 短引用只接受所属包清单登记的 Action,不能由源码文件或其他包补位。
387
+ if (!catalog.get(targetItem.packageId, trimmed)) {
388
+ errors.push(`Referenced action '${trimmed}' not found in project actions. Hint: Action '${trimmed}' not found. Run 'ad action create ${trimmed}' to create local action, or 'ad add <pkg>' to install external dependency.`);
346
389
  }
347
390
  }
348
391
  }
349
392
  }
350
393
  results.push({
351
394
  id: pb.id,
352
- packageId: target.packageId,
395
+ packageId: targetItem.packageId,
353
396
  valid: errors.length === 0,
354
397
  warnings,
355
398
  errors,
@@ -1,9 +1,8 @@
1
1
  import { validateActionInputValue, mapInputValidationFailure, } from "@actiondock/core/project";
2
- import { resolveTarget, } from "@actiondock/core/profile";
3
2
  import { ACTION_NOT_FOUND, NOT_FOUND, resolveExecutionHint, } from "@actiondock/core";
4
- import { ArgumentError, ExecutionError, SigintError, packageNotFoundError } from "../errors.js";
3
+ import { ArgumentError, ExecutionError, SigintError } from "../errors.js";
5
4
  import { extractTextFieldPayload, resolveActionDefaultTextField, writeStderr, writeStdout, } from "../renderer.js";
6
- import { applyTargetOptions, getEffectiveOptions, resolveActionInput, resolveLocalPackageRoot, resolveTimeoutMs, withService, } from "../utils/index.js";
5
+ import { applyTargetOptions, getEffectiveOptions, resolveActionInput, resolveCommandTarget, resolveTimeoutMs, withService, } from "../utils/index.js";
7
6
  /**
8
7
  * 以原始纯文本形式渲染 Action 执行终态结果(默认纯文本模式)。
9
8
  *
@@ -111,25 +110,13 @@ export async function executeAction(id, options, context, flatArgs, control) {
111
110
  }
112
111
  }
113
112
  try {
113
+ // 目标拓扑与动作引用解析(远端目标完全隔离本地文件系统)
114
+ const target = resolveCommandTarget(options, context);
114
115
  // 异步执行仅支持远端目标,本地模式直接拒绝
115
- const resolvedTopology = resolveTarget({
116
- profile: options.profile,
117
- server: options.server,
118
- token: options.token,
119
- insecure: options.insecure,
120
- }, context?.customHome);
121
- if (resolvedTopology.type === "local" && options.async) {
116
+ if (!target.isRemote && options.async) {
122
117
  throw new ExecutionError("Async execution requires a long-running ActionDock server.\nUse --profile, --server, or start 'ad serve'.");
123
118
  }
124
- // 目标拓扑解析(仅 local 分支需要包寻址)
125
- const targetPackageRoot = resolveLocalPackageRoot(options.package);
126
- if (options.package && !targetPackageRoot) {
127
- throw packageNotFoundError(options.package);
128
- }
129
- let targetRef = id;
130
- if (options.package && !id.includes("/")) {
131
- targetRef = `${options.package}/${id}`;
132
- }
119
+ const targetRef = target.resolveActionRef(id);
133
120
  // 通过 Service 统一执行
134
121
  await withService(options, context, async (service) => {
135
122
  const isMachine = Boolean(options.json);
@@ -205,7 +192,7 @@ export async function executeAction(id, options, context, flatArgs, control) {
205
192
  return;
206
193
  }
207
194
  }
208
- }, { localRoot: targetPackageRoot || undefined, scanLinkedPackages: true, ownDataDir: true });
195
+ }, { localRoot: target.targetPackageRoot || undefined, scanLinkedPackages: !target.localSkill, ownDataDir: true });
209
196
  }
210
197
  catch (err) {
211
198
  // sigint 取消源、中断链路特征错误码或 SigintError 本身,均统一归并为 SigintError
@@ -1,11 +1,11 @@
1
1
  import { decodeStateKey, } from "@actiondock/sdk";
2
- import { loadProjectConfig, } from "@actiondock/core";
2
+ import { loadProjectConfig, inspectLocalPackageDirectory, } from "@actiondock/core";
3
3
  import { filterWithFallbackInfo, } from "@actiondock/core/project";
4
4
  import { listLinkedPackages, } from "@actiondock/core/registry";
5
5
  import { existsSync } from "node:fs";
6
6
  import { ArgumentError, ExecutionError, NO_PROJECT_NO_LINKED_MESSAGE } from "../errors.js";
7
7
  import { renderResult, renderStateList, writeStderr, writeStdout } from "../renderer.js";
8
- import { applyTargetOptions, getEffectiveOptions, getTargetRoot, remoteTargetLabel, requirePackageRoot, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, resolveTargetFromOptions, withService, } from "../utils/index.js";
8
+ import { applyTargetOptions, assertNotCompositeSkill, getEffectiveOptions, getTargetRoot, remoteTargetLabel, requirePackageRoot, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, resolveTargetFromOptions, withService, } from "../utils/index.js";
9
9
  /**
10
10
  * 统一预解码状态键寻址(get、set、delete 共享的单一事实源)。
11
11
  *
@@ -57,6 +57,22 @@ function resolveStateScope(resolved, options, rawKey) {
57
57
  localRoot: undefined,
58
58
  };
59
59
  }
60
+ assertNotCompositeSkill(options.package);
61
+ // 本地单包技能目录优先处理
62
+ if (options.package) {
63
+ const localSkill = inspectLocalPackageDirectory(options.package);
64
+ if (localSkill?.type === "single") {
65
+ const pkg = localSkill.packages[0];
66
+ return {
67
+ packageId: pkg.packageId,
68
+ scopeSuffix: `in package '${pkg.packageId}'`,
69
+ deleteSuffix: `from package '${pkg.packageId}'`,
70
+ effectiveKey: rawKey || "",
71
+ projConfig: pkg.projectConfig,
72
+ localRoot: pkg.packageRoot,
73
+ };
74
+ }
75
+ }
60
76
  // 本地项目模式:getTargetRoot 会从键中剥离 pkg/ 前缀,寻址基于解耦后的裸键
61
77
  const { root, key: effectiveKey } = getTargetRoot(options.package, rawKey);
62
78
  const projConfig = loadProjectConfig(root);
@@ -175,6 +191,7 @@ export function registerStateCommands(program, context) {
175
191
  return;
176
192
  }
177
193
  // 本地工程与链接包模式:按目标根目录决策输出范围
194
+ assertNotCompositeSkill(options.package);
178
195
  const targetRoot = options.package
179
196
  ? requirePackageRoot(options.package).root
180
197
  : resolveLocalPackageRoot();
@@ -43,3 +43,7 @@ export type RegistryTreeView = RegistryStatusReport | (Record<string, unknown> &
43
43
  * 格式化渲染注册表层级树结构。
44
44
  */
45
45
  export declare function renderRegistryTree(status: RegistryTreeView): string;
46
+ /**
47
+ * 格式化渲染复合技能各子包的自省卡片列表。
48
+ */
49
+ export declare function renderCompositeSkillDetail(root: string, filteredDetails: ProjectDetailInfo[], isFallback?: boolean, effectiveIntent?: string): string;
@@ -194,3 +194,19 @@ export function renderRegistryTree(status) {
194
194
  }
195
195
  return lines.join("\n");
196
196
  }
197
+ /**
198
+ * 格式化渲染复合技能各子包的自省卡片列表。
199
+ */
200
+ export function renderCompositeSkillDetail(root, filteredDetails, isFallback, effectiveIntent) {
201
+ const lines = [];
202
+ if (isFallback) {
203
+ lines.push(`(No packages in skill matched intent '${effectiveIntent}', showing all packages)\n`);
204
+ }
205
+ lines.push(`ActionDock Composite Skill: ${root}`);
206
+ lines.push(`Packages (${filteredDetails.length}):\n`);
207
+ for (const d of filteredDetails) {
208
+ lines.push(renderProjectDetail(d));
209
+ lines.push("");
210
+ }
211
+ return lines.join("\n").trimEnd();
212
+ }
@@ -1,3 +1,4 @@
1
+ import { type LocalSkillDirectory } from "@actiondock/core/project";
1
2
  import type { AggregatedPackage, CliContext, ProjectDetailInfo } from "../types.js";
2
3
  /**
3
4
  * 装配单个工程的详情自省信息(清单、动作映射与 Playbook 映射)。
@@ -5,6 +6,18 @@ import type { AggregatedPackage, CliContext, ProjectDetailInfo } from "../types.
5
6
  * @param root 工程根目录
6
7
  */
7
8
  export declare function getProjectDetailInfo(root: string): Promise<ProjectDetailInfo>;
9
+ /**
10
+ * 装配复合技能各子包的自省详情,将 Action 与 Playbook 的调用索引提升为完全限定 ID。
11
+ */
12
+ export declare function buildCompositeDetails(localSkill: LocalSkillDirectory): Promise<ProjectDetailInfo[]>;
13
+ /**
14
+ * 单包与复合技能共享相同的详情过滤规则(单一事实源)。
15
+ */
16
+ export declare function filterProjectDetails(details: ProjectDetailInfo[], effectiveIntent?: string, shouldFallback?: boolean): {
17
+ items: ProjectDetailInfo[];
18
+ matchedCount: number;
19
+ isFallback: boolean;
20
+ };
8
21
  /**
9
22
  * 装配单个包的摘要聚合信息(不含动作与 Playbook 明细映射)。
10
23
  *
@@ -13,7 +26,7 @@ export declare function getProjectDetailInfo(root: string): Promise<ProjectDetai
13
26
  export declare function getPackageSummary(root: string): AggregatedPackage;
14
27
  /**
15
28
  * 扫描本地候选包集合(当前工程根目录优先,叠加全局已链接包,按物理路径去重)。
16
- * 清单损坏或磁盘失效的候选项跳过,不影响其余条目聚合。
29
+ * 失效或损坏链接给出明确诊断记录,不静默吞没也不中断其余条目聚合。
17
30
  *
18
31
  * @param context CLI 上下文(提供 customHome 定位全局注册表)
19
32
  */
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { findProjectRoot, loadProjectConfig, } from "@actiondock/core";
3
3
  import { listLinkedPackages, } from "@actiondock/core/registry";
4
- import { loadManifest, loadPlaybooks, } from "@actiondock/core/project";
4
+ import { filterWithFallbackInfo, loadManifest, loadPlaybooks, } from "@actiondock/core/project";
5
5
  /**
6
6
  * 装配单个工程的详情自省信息(清单、动作映射与 Playbook 映射)。
7
7
  *
@@ -38,6 +38,51 @@ export async function getProjectDetailInfo(root) {
38
38
  playbooksMap: playbooks,
39
39
  };
40
40
  }
41
+ /**
42
+ * 装配复合技能各子包的自省详情,将 Action 与 Playbook 的调用索引提升为完全限定 ID。
43
+ */
44
+ export async function buildCompositeDetails(localSkill) {
45
+ const details = [];
46
+ for (const pkg of localSkill.packages) {
47
+ const detail = await getProjectDetailInfo(pkg.packageRoot);
48
+ const qualifiedActionsMap = new Map();
49
+ if (detail.actionsMap) {
50
+ for (const [actId, act] of detail.actionsMap.entries()) {
51
+ const fullId = `${pkg.packageId}/${actId}`;
52
+ qualifiedActionsMap.set(fullId, { id: fullId, description: act.description });
53
+ }
54
+ }
55
+ const qualifiedPlaybooksMap = new Map();
56
+ if (detail.playbooksMap) {
57
+ for (const [pbId, pb] of detail.playbooksMap.entries()) {
58
+ const fullId = `${pkg.packageId}/${pbId}`;
59
+ qualifiedPlaybooksMap.set(fullId, { ...pb, id: fullId });
60
+ }
61
+ }
62
+ detail.actionsMap = qualifiedActionsMap;
63
+ detail.actions = Array.from(qualifiedActionsMap.keys());
64
+ detail.playbooksMap = qualifiedPlaybooksMap;
65
+ detail.playbooks = Array.from(qualifiedPlaybooksMap.keys());
66
+ details.push(detail);
67
+ }
68
+ return details;
69
+ }
70
+ /**
71
+ * 单包与复合技能共享相同的详情过滤规则(单一事实源)。
72
+ */
73
+ export function filterProjectDetails(details, effectiveIntent, shouldFallback = false) {
74
+ if (!effectiveIntent) {
75
+ return { items: details, matchedCount: details.length, isFallback: false };
76
+ }
77
+ return filterWithFallbackInfo(details, effectiveIntent, [
78
+ (d) => d.id,
79
+ (d) => d.name,
80
+ (d) => d.description || "",
81
+ (d) => d.projectRoot,
82
+ (d) => d.actions.join(" "),
83
+ (d) => d.playbooks.join(" "),
84
+ ], Boolean(shouldFallback));
85
+ }
41
86
  /**
42
87
  * 装配单个包的摘要聚合信息(不含动作与 Playbook 明细映射)。
43
88
  *
@@ -63,7 +108,7 @@ export function getPackageSummary(root) {
63
108
  }
64
109
  /**
65
110
  * 扫描本地候选包集合(当前工程根目录优先,叠加全局已链接包,按物理路径去重)。
66
- * 清单损坏或磁盘失效的候选项跳过,不影响其余条目聚合。
111
+ * 失效或损坏链接给出明确诊断记录,不静默吞没也不中断其余条目聚合。
67
112
  *
68
113
  * @param context CLI 上下文(提供 customHome 定位全局注册表)
69
114
  */
@@ -73,25 +118,22 @@ export function scanLocalAggregatedPackages(context) {
73
118
  const aggregated = [];
74
119
  const seenPaths = new Set();
75
120
  if (currentRoot) {
76
- try {
77
- aggregated.push(getPackageSummary(currentRoot));
78
- seenPaths.add(currentRoot);
79
- }
80
- catch {
81
- // 忽略异常工程根目录(清单损坏)
82
- }
121
+ aggregated.push(getPackageSummary(currentRoot));
122
+ seenPaths.add(currentRoot);
83
123
  }
84
124
  for (const pkg of linkedList) {
85
- if (!existsSync(pkg.path))
125
+ if (!existsSync(pkg.path)) {
126
+ process.stderr.write(`[actiondock] Diagnostic: Skipping inaccessible linked package '${pkg.id}' at '${pkg.path}'\n`);
86
127
  continue;
128
+ }
87
129
  if (seenPaths.has(pkg.path))
88
130
  continue;
89
131
  try {
90
132
  aggregated.push(getPackageSummary(pkg.path));
91
133
  seenPaths.add(pkg.path);
92
134
  }
93
- catch {
94
- // 忽略失效链接(清单损坏或磁盘异常)
135
+ catch (err) {
136
+ process.stderr.write(`[actiondock] Diagnostic: Skipping invalid linked package '${pkg.id}' at '${pkg.path}': ${err?.message || String(err)}\n`);
95
137
  }
96
138
  }
97
139
  return { currentRoot, aggregated };
@@ -4,4 +4,3 @@ export * from "./spawn.js";
4
4
  export * from "./cert.js";
5
5
  export * from "./input.js";
6
6
  export * from "./server-launch.js";
7
- export * from "./warning.js";
@@ -4,4 +4,3 @@ export * from "./spawn.js";
4
4
  export * from "./cert.js";
5
5
  export * from "./input.js";
6
6
  export * from "./server-launch.js";
7
- export * from "./warning.js";