@actiondock/cli 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.
@@ -1,6 +1,7 @@
1
1
  import { filterWithFallbackInfo, } from "@actiondock/core/project";
2
+ import { packageNotFoundError } from "../errors.js";
2
3
  import { renderActionList, renderResult } from "../renderer.js";
3
- import { applyTargetOptions, getEffectiveOptions, remoteTargetSuffix, resolveCommandTarget, resolveFallbackStrategy, resolveIntent, withService, } from "../utils/index.js";
4
+ import { applyTargetOptions, getEffectiveOptions, remoteTargetSuffix, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, withService, } from "../utils/index.js";
4
5
  /**
5
6
  * 挂载 list 子命令至指定 Commander 节点。
6
7
  *
@@ -22,20 +23,22 @@ export function attachListCommand(parent, context) {
22
23
  const options = getEffectiveOptions(rawOptions, cmd);
23
24
  const effectiveIntent = resolveIntent(options.intent, patterns);
24
25
  const { isMachine, shouldFallback } = resolveFallbackStrategy(options);
25
- // 目标拓扑与动作引用解析(远端目标完全隔离本地文件系统)
26
- const target = resolveCommandTarget(options, context);
26
+ // 目标拓扑解析(仅 local 分支需要包寻址)
27
+ const targetPackageRoot = resolveLocalPackageRoot(options.package);
28
+ if (options.package && !targetPackageRoot) {
29
+ throw packageNotFoundError(options.package);
30
+ }
27
31
  // 通过 Service 门面统一获取 Action 列表
28
32
  await withService(options, context, async (service, resolved) => {
29
33
  let rawSummaries = await service.discovery.listActions();
30
- // 若指定了包 ID(非本地技能目录),则按包归属精确过滤(本地与远端统一生效):
34
+ // 若指定了目标包但本地未过滤,则按包归属精确过滤:
31
35
  // 仅保留全限定名命中目标包前缀、或 packageId 归属目标包的条目;
32
36
  // 无法归属到目标包的无前缀短 id 条目不放行,避免他包短 id 混入
33
- if (options.package && !target.localSkill) {
37
+ if (options.package && !options.profile && !options.server) {
34
38
  const targetPkg = options.package;
35
39
  rawSummaries = rawSummaries.filter((s) => {
36
- const pkgId = s.packageId;
37
- if (pkgId) {
38
- return (pkgId === targetPkg ||
40
+ if (s.packageId) {
41
+ return (s.packageId === targetPkg ||
39
42
  s.id === targetPkg ||
40
43
  s.id.startsWith(`${targetPkg}/`));
41
44
  }
@@ -43,17 +46,14 @@ export function attachListCommand(parent, context) {
43
46
  return s.id === targetPkg || s.id.startsWith(`${targetPkg}/`);
44
47
  });
45
48
  }
46
- const isCompositeSkill = target.localSkill?.type === "composite";
47
49
  const rawList = rawSummaries.map((s) => {
48
50
  let id = s.id;
49
- // 复合技能保持完全限定 ID,不剥离包名前缀
50
- if (!isCompositeSkill && (target.targetPackageRoot || options.package)) {
51
- const targetPkg = target.localSkill ? target.localSkill.packages[0].packageId : options.package;
52
- const pkgId = targetPkg || s.packageId;
51
+ if (targetPackageRoot || options.package) {
52
+ const pkgId = s.packageId || options.package;
53
53
  if (pkgId && id.startsWith(`${pkgId}/`)) {
54
54
  id = id.slice(pkgId.length + 1);
55
55
  }
56
- else if (target.targetPackageRoot && id.includes("/")) {
56
+ else if (targetPackageRoot && id.includes("/")) {
57
57
  id = id.slice(id.indexOf("/") + 1);
58
58
  }
59
59
  }
@@ -80,7 +80,7 @@ export function attachListCommand(parent, context) {
80
80
  }
81
81
  const title = resolved.type === "remote"
82
82
  ? `Actions ${remoteTargetSuffix(resolved)}`
83
- : target.targetPackageRoot
83
+ : targetPackageRoot
84
84
  ? `Actions in ${options.package || "current project"}`
85
85
  : "Available Actions";
86
86
  renderResult(filterRes.items, {
@@ -88,7 +88,7 @@ export function attachListCommand(parent, context) {
88
88
  humanFormatter: () => renderActionList(filterRes.items, title, filterRes.isFallback, effectiveIntent),
89
89
  context,
90
90
  });
91
- }, { localRoot: target.targetPackageRoot, scanLinkedPackages: !target.localSkill });
91
+ }, { localRoot: targetPackageRoot || undefined, scanLinkedPackages: true });
92
92
  });
93
93
  }
94
94
  /**
@@ -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, loadManifest, loadPlaybooks, saveManifest, filterWithFallbackInfo, } from "@actiondock/core/project";
4
- import { DefaultActionCatalog, resolveAction, resolvePlaybook, } from "@actiondock/core/graph";
3
+ import { assertPathWithinRoot, loadActions, loadManifest, loadPlaybooks, saveManifest, filterWithFallbackInfo, } from "@actiondock/core/project";
4
+ import { DefaultActionCatalog, PackageGraphBuilder, 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, writeStderr, writeStdout, } from "../renderer.js";
10
- import { applyTargetOptions, buildScopedPackageGraph, getEffectiveOptions, remoteTargetSuffix, requirePackageRoot, resolveCommandTarget, resolveFallbackStrategy, resolveIntent, } from "../utils/index.js";
9
+ import { renderPlaybookDetail, renderPlaybookList, renderResult, writeStdout, } from "../renderer.js";
10
+ import { applyTargetOptions, getEffectiveOptions, remoteTargetSuffix, requirePackageRoot, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, resolveTargetFromOptions, } from "../utils/index.js";
11
11
  /**
12
12
  * 注册 Playbook 命令集合(list, show, validate, create)。
13
13
  *
@@ -34,15 +34,14 @@ 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 = resolveCommandTarget(options, context);
39
- // 2. 远端服务分支
40
- if (target.isRemote) {
41
- const remotePbs = await fetchRemotePlaybooks(target.resolved.serverUrl, target.resolved.token, {
37
+ // 1. 远端服务分支
38
+ const target = resolveTargetFromOptions(options, context);
39
+ if (target.type === "remote") {
40
+ const remotePbs = await fetchRemotePlaybooks(target.serverUrl, target.token, {
42
41
  intent: effectiveIntent,
43
42
  package: options.package,
44
43
  allowInsecureHttp: Boolean(options.allowInsecureHttp),
45
- insecure: target.resolved.insecure,
44
+ insecure: target.insecure,
46
45
  });
47
46
  if (isMachine) {
48
47
  renderResult({ items: remotePbs, hints }, { json: options.json, context });
@@ -50,44 +49,24 @@ export function registerPlaybookCommands(program, context) {
50
49
  }
51
50
  renderResult(remotePbs, {
52
51
  json: options.json,
53
- humanFormatter: () => renderPlaybookList(remotePbs, `Playbooks ${remoteTargetSuffix(target.resolved)}`, false, effectiveIntent),
52
+ humanFormatter: () => renderPlaybookList(remotePbs, `Playbooks ${remoteTargetSuffix(target)}`, false, effectiveIntent),
54
53
  context,
55
54
  });
56
55
  return;
57
56
  }
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);
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);
91
70
  if (isMachine) {
92
71
  if (filterRes.isFallback) {
93
72
  renderResult({ items: filterRes.items, isFallback: true, matchedCount: 0, hints }, { json: options.json, context });
@@ -102,7 +81,7 @@ export function registerPlaybookCommands(program, context) {
102
81
  }
103
82
  renderResult(filterRes.items, {
104
83
  json: options.json,
105
- humanFormatter: () => renderPlaybookList(filterRes.items, title, filterRes.isFallback, effectiveIntent),
84
+ humanFormatter: () => renderPlaybookList(filterRes.items, `Playbooks in ${config.id} (${targetRoot})`, filterRes.isFallback, effectiveIntent),
106
85
  context,
107
86
  });
108
87
  return;
@@ -123,22 +102,25 @@ export function registerPlaybookCommands(program, context) {
123
102
  }
124
103
  const aggregated = [];
125
104
  for (const pkg of linkedList) {
126
- if (!existsSync(pkg.path)) {
127
- writeStderr(`Skipping inaccessible linked package '${pkg.id}' at '${pkg.path}'`, context);
105
+ if (!existsSync(pkg.path))
128
106
  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 加载失败时跳过,继续聚合其余包
129
123
  }
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
- });
142
124
  }
143
125
  let filteredPackages = [];
144
126
  let isFallback = false;
@@ -201,14 +183,13 @@ export function registerPlaybookCommands(program, context) {
201
183
  if (!id) {
202
184
  throw new ArgumentError("Playbook ID is required for show");
203
185
  }
204
- // 1. 目标拓扑统一解析
205
- const target = resolveCommandTarget(options, context);
206
- // 2. 远端服务分支
207
- if (target.isRemote) {
186
+ // 1. 远端服务分支
187
+ const target = resolveTargetFromOptions(options, context);
188
+ if (target.type === "remote") {
208
189
  try {
209
- const detail = await fetchRemotePlaybookShow(target.resolved.serverUrl, target.resolvePlaybookRef(id), target.resolved.token, {
190
+ const detail = await fetchRemotePlaybookShow(target.serverUrl, id, target.token, {
210
191
  allowInsecureHttp: Boolean(options.allowInsecureHttp),
211
- insecure: target.resolved.insecure,
192
+ insecure: target.insecure,
212
193
  });
213
194
  renderResult(detail, {
214
195
  json: options.json,
@@ -222,15 +203,20 @@ export function registerPlaybookCommands(program, context) {
222
203
  throw new ArgumentError(`${err.message}\n${hint}`, err?.details, err?.code, hint);
223
204
  }
224
205
  }
225
- // 3. 本地分支(单包技能、复合技能、本地工程或已链接包)
226
- const showTarget = target.resolvePlaybookRef(id);
206
+ // 2. 本地项目模式
207
+ let showTarget = id;
208
+ if (options.package && !id.includes("/")) {
209
+ requirePackageRoot(options.package);
210
+ showTarget = `${options.package}/${id}`;
211
+ }
227
212
  let resolved;
228
213
  try {
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 });
214
+ const graph = new PackageGraphBuilder({
215
+ root: resolveLocalPackageRoot() || undefined,
216
+ customHome: context?.customHome,
217
+ allowDevLinks: true,
218
+ }).buildSync();
219
+ resolved = resolvePlaybook(showTarget, { graph });
234
220
  }
235
221
  catch (err) {
236
222
  const hint = "Tip: Run 'ad playbook list' to discover available playbooks.";
@@ -263,73 +249,36 @@ export function registerPlaybookCommands(program, context) {
263
249
  .option("--json", "Output as JSON")
264
250
  .action(async (id, rawOptions, cmd) => {
265
251
  const options = getEffectiveOptions(rawOptions, cmd);
266
- // 保持本地校验定位,profile 始终为 local
267
- const target = resolveCommandTarget({ ...options, profile: "local" }, context);
268
- const graph = buildScopedPackageGraph(target, context);
269
- const catalog = new DefaultActionCatalog(graph);
252
+ const root = options.package
253
+ ? requirePackageRoot(options.package).root
254
+ : resolveLocalPackageRoot();
270
255
  const targets = [];
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;
256
+ if (root) {
301
257
  const config = loadProjectConfig(root);
258
+ const playbooks = loadPlaybooks(root, config.playbooksDir);
302
259
  if (id) {
303
- const showTarget = target.resolvePlaybookRef(id);
304
- let resolved;
305
- try {
306
- resolved = resolvePlaybook(showTarget, { graph, caller: config.id });
260
+ const pb = playbooks.get(id);
261
+ if (!pb) {
262
+ throw new ArgumentError(`Playbook '${id}' not found in package '${config.id}'`);
307
263
  }
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
- });
264
+ targets.push({ root, packageId: config.id, playbooks: [pb] });
316
265
  }
317
266
  else {
318
- const playbooks = loadPlaybooks(root, config.playbooksDir);
319
- targets.push({
320
- root,
321
- packageId: config.id,
322
- playbooks: Array.from(playbooks.values()),
323
- });
267
+ targets.push({ root, packageId: config.id, playbooks: Array.from(playbooks.values()) });
324
268
  }
325
269
  }
326
270
  else if (id) {
327
271
  let resolved;
328
272
  try {
273
+ const graph = new PackageGraphBuilder({
274
+ root: resolveLocalPackageRoot() || undefined,
275
+ customHome: context?.customHome,
276
+ allowDevLinks: true,
277
+ }).buildSync();
329
278
  resolved = resolvePlaybook(id, { graph });
330
279
  }
331
280
  catch (err) {
332
- throw new ArgumentError(err.message, err?.details, err?.code);
281
+ throw new ArgumentError(err.message);
333
282
  }
334
283
  targets.push({
335
284
  root: resolved.projectRoot,
@@ -343,22 +292,34 @@ export function registerPlaybookCommands(program, context) {
343
292
  throw new ArgumentError(`${NO_PROJECT_NO_LINKED_MESSAGE} Please run 'ad link' inside a package first.`);
344
293
  }
345
294
  for (const pkg of linkedList) {
346
- if (!existsSync(pkg.path)) {
347
- writeStderr(`Skipping inaccessible linked package '${pkg.id}' at '${pkg.path}'`, context);
295
+ if (!existsSync(pkg.path))
348
296
  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 加载失败时跳过,不阻断整体校验
349
308
  }
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
- });
357
309
  }
358
310
  }
359
311
  const results = [];
360
- for (const targetItem of targets) {
361
- for (const pb of targetItem.playbooks) {
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) {
362
323
  const warnings = [];
363
324
  const errors = [];
364
325
  if (!pb.id) {
@@ -369,30 +330,26 @@ export function registerPlaybookCommands(program, context) {
369
330
  }
370
331
  if (pb.actions && Array.isArray(pb.actions)) {
371
332
  for (const actRef of pb.actions) {
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("/")) {
333
+ if (actRef.includes("/")) {
378
334
  try {
379
- resolveAction(trimmed, { graph, catalog, caller: targetItem.packageId });
335
+ resolveAction(actRef, { graph, catalog, caller: target.packageId });
380
336
  }
381
337
  catch (e) {
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.`);
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.`);
383
339
  }
384
340
  }
385
341
  else {
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.`);
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.`);
389
346
  }
390
347
  }
391
348
  }
392
349
  }
393
350
  results.push({
394
351
  id: pb.id,
395
- packageId: targetItem.packageId,
352
+ packageId: target.packageId,
396
353
  valid: errors.length === 0,
397
354
  warnings,
398
355
  errors,
@@ -1,8 +1,9 @@
1
1
  import { validateActionInputValue, mapInputValidationFailure, } from "@actiondock/core/project";
2
+ import { resolveTarget, } from "@actiondock/core/profile";
2
3
  import { ACTION_NOT_FOUND, NOT_FOUND, resolveExecutionHint, } from "@actiondock/core";
3
- import { ArgumentError, ExecutionError, SigintError } from "../errors.js";
4
+ import { ArgumentError, ExecutionError, SigintError, packageNotFoundError } from "../errors.js";
4
5
  import { extractTextFieldPayload, resolveActionDefaultTextField, writeStderr, writeStdout, } from "../renderer.js";
5
- import { applyTargetOptions, getEffectiveOptions, resolveActionInput, resolveCommandTarget, resolveTimeoutMs, withService, } from "../utils/index.js";
6
+ import { applyTargetOptions, getEffectiveOptions, resolveActionInput, resolveLocalPackageRoot, resolveTimeoutMs, withService, } from "../utils/index.js";
6
7
  /**
7
8
  * 以原始纯文本形式渲染 Action 执行终态结果(默认纯文本模式)。
8
9
  *
@@ -110,13 +111,25 @@ export async function executeAction(id, options, context, flatArgs, control) {
110
111
  }
111
112
  }
112
113
  try {
113
- // 目标拓扑与动作引用解析(远端目标完全隔离本地文件系统)
114
- const target = resolveCommandTarget(options, context);
115
114
  // 异步执行仅支持远端目标,本地模式直接拒绝
116
- if (!target.isRemote && options.async) {
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) {
117
122
  throw new ExecutionError("Async execution requires a long-running ActionDock server.\nUse --profile, --server, or start 'ad serve'.");
118
123
  }
119
- const targetRef = target.resolveActionRef(id);
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
+ }
120
133
  // 通过 Service 统一执行
121
134
  await withService(options, context, async (service) => {
122
135
  const isMachine = Boolean(options.json);
@@ -192,7 +205,7 @@ export async function executeAction(id, options, context, flatArgs, control) {
192
205
  return;
193
206
  }
194
207
  }
195
- }, { localRoot: target.targetPackageRoot || undefined, scanLinkedPackages: !target.localSkill, ownDataDir: true });
208
+ }, { localRoot: targetPackageRoot || undefined, scanLinkedPackages: true, ownDataDir: true });
196
209
  }
197
210
  catch (err) {
198
211
  // sigint 取消源、中断链路特征错误码或 SigintError 本身,均统一归并为 SigintError
@@ -3,7 +3,7 @@ import { resolve } from "node:path";
3
3
  import { createActionDock, createNodePlatform, findProjectRoot, loadProjectConfig, startActionDockServer, } from "@actiondock/core";
4
4
  import { resolvePackageRoot } from "@actiondock/core/registry";
5
5
  import { parseActionRef } from "@actiondock/core/graph";
6
- import { formatHostForUrl, } from "@actiondock/core/server";
6
+ import { formatHostForUrl, createServerRunOrigin, } from "@actiondock/core/server";
7
7
  import { ArgumentError, ExecutionError, packageNotFoundError } from "../errors.js";
8
8
  import { writeStderr, writeStdout } from "../renderer.js";
9
9
  import { ensureSelfSignedCertificate, getEffectiveOptions, loadViewsFromFile, mergeServerViews, normalizeCorsOrigins, registerStopSignalHandler, printServerBanner, resolveMaxBodyBytes, resolveServerHost, resolveServerPort, resolveServerToken, parseListOption, } from "../utils/index.js";
@@ -252,6 +252,7 @@ export function registerServeCommand(program, context) {
252
252
  packageAllowlist: view.packageAllowlist,
253
253
  packageIds: view.packageAllowlist,
254
254
  actionAllowlist: view.actionAllowlist,
255
+ origin: createServerRunOrigin(view, "mcp"),
255
256
  });
256
257
  }, {
257
258
  onerror: (err) => {
@@ -1,11 +1,11 @@
1
1
  import { decodeStateKey, } from "@actiondock/sdk";
2
- import { loadProjectConfig, inspectLocalPackageDirectory, } from "@actiondock/core";
2
+ import { loadProjectConfig, } 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, assertNotCompositeSkill, getEffectiveOptions, getTargetRoot, remoteTargetLabel, requirePackageRoot, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, resolveTargetFromOptions, withService, } from "../utils/index.js";
8
+ import { applyTargetOptions, getEffectiveOptions, getTargetRoot, remoteTargetLabel, requirePackageRoot, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, resolveTargetFromOptions, withService, } from "../utils/index.js";
9
9
  /**
10
10
  * 统一预解码状态键寻址(get、set、delete 共享的单一事实源)。
11
11
  *
@@ -57,22 +57,6 @@ 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
- }
76
60
  // 本地项目模式:getTargetRoot 会从键中剥离 pkg/ 前缀,寻址基于解耦后的裸键
77
61
  const { root, key: effectiveKey } = getTargetRoot(options.package, rawKey);
78
62
  const projConfig = loadProjectConfig(root);
@@ -191,7 +175,6 @@ export function registerStateCommands(program, context) {
191
175
  return;
192
176
  }
193
177
  // 本地工程与链接包模式:按目标根目录决策输出范围
194
- assertNotCompositeSkill(options.package);
195
178
  const targetRoot = options.package
196
179
  ? requirePackageRoot(options.package).root
197
180
  : resolveLocalPackageRoot();
@@ -43,7 +43,3 @@ 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,19 +194,3 @@ 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
- }
@@ -27,6 +27,9 @@ export function renderRunDetail(run) {
27
27
  lines.push(`Package: ${run.packageId}`);
28
28
  if (run.requestId)
29
29
  lines.push(`Request ID: ${run.requestId}`);
30
+ if (run.origin) {
31
+ lines.push(`Origin: channel=${run.origin.channel} view=${run.origin.viewName} authenticated=${run.origin.authenticated}`);
32
+ }
30
33
  lines.push(`Status: ${run.status}`);
31
34
  if (run.parentRunId)
32
35
  lines.push(`Parent Run: ${run.parentRunId}`);
@@ -1,4 +1,3 @@
1
- import { type LocalSkillDirectory } from "@actiondock/core/project";
2
1
  import type { AggregatedPackage, CliContext, ProjectDetailInfo } from "../types.js";
3
2
  /**
4
3
  * 装配单个工程的详情自省信息(清单、动作映射与 Playbook 映射)。
@@ -6,18 +5,6 @@ import type { AggregatedPackage, CliContext, ProjectDetailInfo } from "../types.
6
5
  * @param root 工程根目录
7
6
  */
8
7
  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
- };
21
8
  /**
22
9
  * 装配单个包的摘要聚合信息(不含动作与 Playbook 明细映射)。
23
10
  *
@@ -26,7 +13,7 @@ export declare function filterProjectDetails(details: ProjectDetailInfo[], effec
26
13
  export declare function getPackageSummary(root: string): AggregatedPackage;
27
14
  /**
28
15
  * 扫描本地候选包集合(当前工程根目录优先,叠加全局已链接包,按物理路径去重)。
29
- * 失效或损坏链接给出明确诊断记录,不静默吞没也不中断其余条目聚合。
16
+ * 清单损坏或磁盘失效的候选项跳过,不影响其余条目聚合。
30
17
  *
31
18
  * @param context CLI 上下文(提供 customHome 定位全局注册表)
32
19
  */