@lesliechan721/aw 0.1.0-beta.18 → 0.1.0-beta.20

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 (47) hide show
  1. package/dist/src/commands/operation-planner.d.ts +3 -3
  2. package/dist/src/commands/rebuild-command.js +0 -5
  3. package/dist/src/commands/rebuild-command.js.map +1 -1
  4. package/dist/src/commands/rebuild-integration.d.ts +0 -1
  5. package/dist/src/commands/rebuild-integration.js +3 -152
  6. package/dist/src/commands/rebuild-integration.js.map +1 -1
  7. package/dist/src/commands/status-v6.d.ts +81 -31
  8. package/dist/src/commands/status-v6.js +494 -183
  9. package/dist/src/commands/status-v6.js.map +1 -1
  10. package/dist/src/commands/wizard.d.ts +2 -2
  11. package/dist/src/index.js +2 -2
  12. package/dist/src/tui/catalog-sources/projection.d.ts +2 -2
  13. package/dist/src/tui/catalog-sources/screens.js +17 -10
  14. package/dist/src/tui/catalog-sources/screens.js.map +1 -1
  15. package/dist/src/tui/console-app-v4.js +1300 -437
  16. package/dist/src/tui/console-app-v4.js.map +1 -1
  17. package/dist/src/tui/controller-runtime.d.ts +3 -3
  18. package/dist/src/tui/controller-runtime.js +114 -36
  19. package/dist/src/tui/controller-runtime.js.map +1 -1
  20. package/dist/src/tui/controller.d.ts +17 -7
  21. package/dist/src/tui/controller.js +269 -24
  22. package/dist/src/tui/controller.js.map +1 -1
  23. package/dist/src/tui/dependency-graph.d.ts +3 -0
  24. package/dist/src/tui/dependency-graph.js +37 -1
  25. package/dist/src/tui/dependency-graph.js.map +1 -1
  26. package/dist/src/tui/diagnostic-projection.d.ts +1 -0
  27. package/dist/src/tui/diagnostic-projection.js +51 -4
  28. package/dist/src/tui/diagnostic-projection.js.map +1 -1
  29. package/dist/src/tui/model.d.ts +22 -12
  30. package/dist/src/tui/model.js +251 -86
  31. package/dist/src/tui/model.js.map +1 -1
  32. package/dist/src/tui/operation-history.d.ts +1 -0
  33. package/dist/src/tui/operation-history.js +5 -0
  34. package/dist/src/tui/operation-history.js.map +1 -1
  35. package/dist/src/tui/operation-review-planner.js +67 -2
  36. package/dist/src/tui/operation-review-planner.js.map +1 -1
  37. package/dist/src/tui/plugin-write-task.d.ts +2 -0
  38. package/dist/src/tui/plugin-write-task.js.map +1 -1
  39. package/dist/src/tui/router.d.ts +6 -0
  40. package/dist/src/tui/router.js +56 -12
  41. package/dist/src/tui/router.js.map +1 -1
  42. package/dist/src/tui/screen-registry.d.ts +7 -0
  43. package/dist/src/utils/plugin-config-write.js +3 -3
  44. package/dist/src/utils/plugin-config-write.js.map +1 -1
  45. package/dist/src/utils/target-eligibility.d.ts +2 -2
  46. package/dist/src/utils/target-eligibility.js.map +1 -1
  47. package/package.json +3 -3
@@ -1,5 +1,5 @@
1
1
  import type { CliExecutionContext } from '../commands/runtime.js';
2
- import { type StatusTargetRevision, type StatusV7Target } from '../commands/status-v6.js';
2
+ import { type StatusTargetRevision, type StatusScopedTarget, type StatusV8Target } from '../commands/status-v6.js';
3
3
  import { type CatalogSourceMutationProgress } from '../commands/catalog-source.js';
4
4
  import type { RebuildProgressFeed } from '../output/rebuild-progress.js';
5
5
  import { inventoryCatalogSourceReferences } from '../utils/catalog-source-references.js';
@@ -13,8 +13,8 @@ export interface TuiControllerRuntimeTestHooks {
13
13
  afterCatalogGenerationCandidateResolved?: () => Promise<void>;
14
14
  }
15
15
  export declare function setTuiControllerRuntimeTestHooksForTests(hooks: TuiControllerRuntimeTestHooks | undefined): void;
16
- export declare function statusTargetFingerprint(targets: StatusV7Target[], revisions?: StatusTargetRevision[]): string;
17
- export declare function configAffectedStatusTargets(targets: StatusV7Target[], pluginId: string, configScope: 'user' | 'project'): StatusV7Target[];
16
+ export declare function statusTargetFingerprint(targets: StatusScopedTarget[], revisions?: StatusTargetRevision[]): string;
17
+ export declare function configAffectedStatusTargets(targets: StatusV8Target[], pluginId: string, configScope: 'user' | 'project'): StatusScopedTarget[];
18
18
  export declare function createTuiControllerDependencies(context: CliExecutionContext, progressFeed?: RebuildProgressFeed, options?: {
19
19
  now?: () => number;
20
20
  inventoryCatalogSourceReferences?: typeof inventoryCatalogSourceReferences;
@@ -1,6 +1,6 @@
1
1
  import crypto from 'node:crypto';
2
2
  import path from 'node:path';
3
- import { statusCommandV7, statusTargetActivation, statusTargetIntent, statusTargetOwnershipState, } from '../commands/status-v6.js';
3
+ import { flattenStatusTargets, statusCommandV8, statusTargetActivation, statusTargetIntent, statusTargetOwnershipState, } from '../commands/status-v6.js';
4
4
  import { addCatalogSourceCommand, listCatalogSources, preflightCatalogSourceMutation, removeCatalogSourceCommand, setPrimaryCatalogSourceCommand, updateCatalogSourceCommand, } from '../commands/catalog-source.js';
5
5
  import { cleanupWriteCommand, failedWriteCommandResult, installWriteCommand, mergeWriteCommandResults, reinstallWriteCommand, syncWriteCommand, taskWriteCommand, uninstallWriteCommand, } from '../commands/write-commands.js';
6
6
  import { resolveSyncIntentSelection } from '../commands/sync-command.js';
@@ -19,6 +19,7 @@ import { inventoryCatalogSourceReferences } from '../utils/catalog-source-refere
19
19
  import { operationEvidenceMatches, operationEvidenceFingerprint, TuiConfigInvalidStoreError, TuiController, TuiOperationBlockedError, } from './controller.js';
20
20
  import { buildConfigGroups, buildConfigPage, buildTuiViewModel, projectPlatformPlacement, } from './model.js';
21
21
  import { buildPluginWriteTaskCandidates, buildPluginWriteTaskDraft, evaluatePluginWriteTaskRestoreVersion, pluginWriteTaskTargetKey, } from './plugin-write-task.js';
22
+ import { projectExecutionDiagnostics, projectThrownOperationDiagnostic } from './diagnostic-projection.js';
22
23
  import { createCatalogManagementService, } from './catalog-management.js';
23
24
  import { projectCatalogSourceLocation } from '../utils/catalog-source-presentation.js';
24
25
  import { calculateCatalogSourceConfigFingerprint, calculateCatalogSourceIdentity, } from '../utils/catalog-source-identity.js';
@@ -91,8 +92,13 @@ export function statusTargetFingerprint(targets, revisions = []) {
91
92
  scope: target.scope,
92
93
  state: target.state,
93
94
  errors: target.reasons.map((error) => error.code).sort(),
95
+ sharedReasons: [...(target.sharedReasons ?? [])]
96
+ .sort((left, right) => `${left.code}\0${left.message}`.localeCompare(`${right.code}\0${right.message}`, 'en')),
94
97
  components: target.components.map((component) => [component.key, component.state])
95
98
  .sort(([left], [right]) => left.localeCompare(right, 'en')),
99
+ sharedComponents: (target.sharedComponents ?? [])
100
+ .map((component) => [component.key, component.state, component.metadata])
101
+ .sort(([left], [right]) => left.localeCompare(right, 'en')),
96
102
  })).sort((left, right) => [left.pluginId, left.platform, left.scope].join('\0')
97
103
  .localeCompare([right.pluginId, right.platform, right.scope].join('\0'), 'en')),
98
104
  revisions: revisions.map((entry) => ({
@@ -106,7 +112,7 @@ export function statusTargetFingerprint(targets, revisions = []) {
106
112
  }
107
113
  export function configAffectedStatusTargets(targets, pluginId, configScope) {
108
114
  const affected = new Map();
109
- for (const target of targets) {
115
+ for (const target of flattenStatusTargets(targets)) {
110
116
  const intent = statusTargetIntent(target);
111
117
  if (target.pluginId !== pluginId || !intent.enabled)
112
118
  continue;
@@ -146,8 +152,8 @@ function configIntentRequestsApply(intent) {
146
152
  return Object.keys(intent.updates ?? {}).length > 0
147
153
  || (intent.removeKeys?.length ?? 0) > 0;
148
154
  }
149
- function operationReviewStale(message) {
150
- return new TuiOperationBlockedError('operationReviewStale', `operationReviewStale: ${message}`);
155
+ function operationReviewStale(message, diagnostics) {
156
+ return new TuiOperationBlockedError('operationReviewStale', `operationReviewStale: ${message}`, diagnostics);
151
157
  }
152
158
  function sourceEvidenceForPlugin(source, pluginName) {
153
159
  const entry = source.manifest.entries.find((candidate) => candidate.name === pluginName);
@@ -364,7 +370,7 @@ async function loadDependencyOverviews(options) {
364
370
  }
365
371
  }
366
372
  const targetsByPlugin = new Map();
367
- for (const target of options.status.payload.targets) {
373
+ for (const target of flattenStatusTargets(options.status.payload.targets)) {
368
374
  targetsByPlugin.set(target.pluginId, [...(targetsByPlugin.get(target.pluginId) ?? []), target]);
369
375
  }
370
376
  const unsupportedPluginIds = new Set(options.status.pluginSummaries
@@ -647,7 +653,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
647
653
  if (catalogOperationContext)
648
654
  sessionCatalogOperationContext = catalogOperationContext;
649
655
  for (let attempt = 0; attempt < 3; attempt += 1) {
650
- const result = await statusCommandV7({
656
+ const result = await statusCommandV8({
651
657
  cwd: context.cwd,
652
658
  env: context.env,
653
659
  nativeCliRunner: context.nativeCliRunner,
@@ -1208,16 +1214,39 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1208
1214
  ...(signal ? { signal } : {}),
1209
1215
  });
1210
1216
  }
1211
- catch {
1212
- blockedTargets.push(...requestedTargets.map((target) => ({
1213
- target,
1214
- reason: 'targetUnavailable',
1215
- issueIds: [],
1216
- })));
1217
+ catch (error) {
1218
+ if (error instanceof Error && error.name === 'AbortError')
1219
+ throw error;
1220
+ blockedTargets.push(...requestedTargets.map((target, index) => {
1221
+ const diagnostic = error instanceof CatalogGenerationError ? {
1222
+ id: `task-source:${sourceId}:${index}`,
1223
+ code: error.code,
1224
+ stage: 'source',
1225
+ sourceId,
1226
+ safeMessage: 'The Catalog source could not be prepared safely.',
1227
+ target: { pluginId: target.pluginId, platform: target.platform, scope: target.scope },
1228
+ causes: [],
1229
+ detailLines: [],
1230
+ } : projectThrownOperationDiagnostic({
1231
+ error,
1232
+ id: `task-source:${sourceId}:${index}`,
1233
+ stage: 'source',
1234
+ fallbackCode: 'catalogSourceResolutionFailed',
1235
+ sourceId,
1236
+ target,
1237
+ });
1238
+ return {
1239
+ target,
1240
+ reason: 'targetUnavailable',
1241
+ issueIds: [],
1242
+ errorCodes: [diagnostic.code],
1243
+ diagnostics: [diagnostic],
1244
+ };
1245
+ }));
1217
1246
  continue;
1218
1247
  }
1219
1248
  operationContexts.set(sourceId, catalogOperation);
1220
- const status = await statusCommandV7({
1249
+ const status = await statusCommandV8({
1221
1250
  plugins: [...new Set(requestedTargets.map((target) => target.pluginId))],
1222
1251
  cwd: context.cwd,
1223
1252
  env: context.env,
@@ -1227,7 +1256,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1227
1256
  catalogOperationContext: catalogOperation,
1228
1257
  inventoryCatalogSourceReferences: operationInventory,
1229
1258
  });
1230
- observedTargets.push(...status.payload.targets);
1259
+ observedTargets.push(...flattenStatusTargets(status.payload.targets));
1231
1260
  observedRevisions.push(...status.targetRevisions);
1232
1261
  const model = buildTuiViewModel({
1233
1262
  status: status.payload,
@@ -1240,7 +1269,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1240
1269
  const expected = target.expectedUpdate;
1241
1270
  const revision = status.targetRevisions.find((entry) => entry.pluginId === target.pluginId && entry.platform === target.platform
1242
1271
  && entry.scope === target.scope);
1243
- const live = status.payload.targets.find((entry) => {
1272
+ const live = flattenStatusTargets(status.payload.targets).find((entry) => {
1244
1273
  if (entry.pluginId !== target.pluginId || entry.platform !== target.platform)
1245
1274
  return false;
1246
1275
  const desired = statusTargetIntent(entry);
@@ -1325,12 +1354,41 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1325
1354
  } : {}),
1326
1355
  });
1327
1356
  if (!preview.operationPlan) {
1328
- blockedTargets.push(...draft.executableTargets.map((target) => ({
1329
- target,
1330
- reason: 'targetUnavailable',
1331
- issueIds: draft.causes.filter((cause) => cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === pluginWriteTaskTargetKey(target)))
1332
- .map((cause) => cause.issueId),
1333
- })));
1357
+ const projected = projectExecutionDiagnostics(preview, `task-preview:${sourceId}`, [], 'pluginWrite');
1358
+ const sourceDiagnostics = projected.diagnostics.filter((diagnostic) => !diagnostic.target)
1359
+ .map((diagnostic) => ({ ...diagnostic, sourceId, target: undefined }));
1360
+ blockedTargets.push(...draft.executableTargets.map((target, targetIndex) => {
1361
+ const matchingDiagnostics = projected.diagnostics.filter((diagnostic) => diagnostic.target?.pluginId === target.pluginId
1362
+ && diagnostic.target.platform === target.platform
1363
+ && diagnostic.target.scope === target.scope);
1364
+ const preparedDiagnostics = [
1365
+ ...matchingDiagnostics,
1366
+ ...(targetIndex === 0 ? sourceDiagnostics : []),
1367
+ ];
1368
+ const targetDiagnostics = preparedDiagnostics.length > 0
1369
+ ? preparedDiagnostics
1370
+ : [projectThrownOperationDiagnostic({
1371
+ error: undefined,
1372
+ id: `task-preview:${sourceId}:${targetIndex}`,
1373
+ stage: 'dependencyPlan',
1374
+ fallbackCode: 'taskOperationPlanMissing',
1375
+ sourceId,
1376
+ target,
1377
+ })];
1378
+ const diagnostics = targetDiagnostics.map((diagnostic, diagnosticIndex) => ({
1379
+ ...structuredClone(diagnostic),
1380
+ id: `task-preview:${sourceId}:${targetIndex}:${diagnosticIndex}`,
1381
+ sourceId,
1382
+ }));
1383
+ return {
1384
+ target,
1385
+ reason: 'targetUnavailable',
1386
+ issueIds: draft.causes.filter((cause) => cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === pluginWriteTaskTargetKey(target)))
1387
+ .map((cause) => cause.issueId),
1388
+ errorCodes: [...new Set(diagnostics.map((diagnostic) => diagnostic.code))],
1389
+ diagnostics,
1390
+ };
1391
+ }));
1334
1392
  draft.executableTargets = [];
1335
1393
  draft.causes = [];
1336
1394
  break;
@@ -1431,7 +1489,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1431
1489
  if (blockedPlanTargets.length === 0)
1432
1490
  break;
1433
1491
  const requestedKeys = new Set(draft.executableTargets.map(pluginWriteTaskTargetKey));
1434
- const errorCodesByRoot = new Map();
1492
+ const blockedPlansByRoot = new Map();
1435
1493
  for (const blockedPlanTarget of blockedPlanTargets) {
1436
1494
  const pending = [blockedPlanTarget];
1437
1495
  const visited = new Set();
@@ -1475,25 +1533,45 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1475
1533
  throw new TuiOperationBlockedError('taskPlanConsumerMissing', '任务计划中的阻止项无法映射到请求目标。');
1476
1534
  }
1477
1535
  for (const key of mappedRoots) {
1478
- const codes = errorCodesByRoot.get(key) ?? new Set();
1479
- codes.add(blockedPlanTarget.errorCode ?? 'writePlanBlocked');
1480
- errorCodesByRoot.set(key, codes);
1536
+ const plans = blockedPlansByRoot.get(key) ?? [];
1537
+ plans.push(blockedPlanTarget);
1538
+ blockedPlansByRoot.set(key, plans);
1481
1539
  }
1482
1540
  }
1483
1541
  blockedTargets.push(...draft.executableTargets.flatMap((target) => {
1484
- const codes = errorCodesByRoot.get(pluginWriteTaskTargetKey(target));
1485
- return codes ? [{
1542
+ const plans = blockedPlansByRoot.get(pluginWriteTaskTargetKey(target));
1543
+ return plans ? [{
1486
1544
  target,
1487
1545
  reason: 'targetUnavailable',
1488
1546
  issueIds: draft.causes.filter((cause) => cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === pluginWriteTaskTargetKey(target)))
1489
1547
  .map((cause) => cause.issueId),
1490
- errorCodes: [...codes].sort((left, right) => left.localeCompare(right, 'en')),
1548
+ errorCodes: [...new Set(plans.map((plan) => plan.errorCode ?? 'writePlanBlocked'))]
1549
+ .sort((left, right) => left.localeCompare(right, 'en')),
1550
+ diagnostics: plans.map((plan, index) => ({
1551
+ id: `task-plan:${sourceId}:${target.pluginId}:${target.platform}:${target.scope}:${index}`,
1552
+ code: plan.errorCode ?? 'writePlanBlocked',
1553
+ stage: 'dependencyPlan',
1554
+ sourceId,
1555
+ target: {
1556
+ pluginId: plan.pluginId, platform: plan.platform,
1557
+ scope: plan.requestedScope ?? plan.resolvedScope,
1558
+ },
1559
+ causes: [],
1560
+ detailLines: [],
1561
+ safeMessage: plan.errorCode === 'artifactCollision'
1562
+ ? '输出位置存在外来内容,无法覆盖。'
1563
+ : plan.errorCode === 'exclusiveOwnerEvidenceInvalid'
1564
+ ? '独立扩展的归属标记缺失或损坏。'
1565
+ : plan.errorCode === 'dependencyNotReady'
1566
+ ? '依赖安装状态无法读取。'
1567
+ : '依赖计划阻止了此目标。',
1568
+ })),
1491
1569
  }] : [];
1492
1570
  }));
1493
- draft.executableTargets = draft.executableTargets.filter((target) => !errorCodesByRoot.has(pluginWriteTaskTargetKey(target)));
1571
+ draft.executableTargets = draft.executableTargets.filter((target) => !blockedPlansByRoot.has(pluginWriteTaskTargetKey(target)));
1494
1572
  draft.causes = draft.causes.map((cause) => ({
1495
1573
  ...cause,
1496
- consumers: cause.consumers.filter((consumer) => !errorCodesByRoot.has(pluginWriteTaskTargetKey(consumer))),
1574
+ consumers: cause.consumers.filter((consumer) => !blockedPlansByRoot.has(pluginWriteTaskTargetKey(consumer))),
1497
1575
  })).filter((cause) => cause.consumers.length > 0);
1498
1576
  }
1499
1577
  if (!preview?.operationPlan || draft.executableTargets.length === 0)
@@ -1520,10 +1598,10 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1520
1598
  ])).values()].sort((left, right) => pluginWriteTaskTargetKey(left.target)
1521
1599
  .localeCompare(pluginWriteTaskTargetKey(right.target), 'en'));
1522
1600
  if (executableTargets.length === 0) {
1523
- throw new TuiOperationBlockedError('taskNoExecutableTargets', '任务没有可安全执行的目标。');
1601
+ throw new TuiOperationBlockedError('taskNoExecutableTargets', '任务没有可安全执行的目标。', canonicalBlocked.flatMap((target) => target.diagnostics ?? []));
1524
1602
  }
1525
1603
  if (intent.executionPolicy === 'all' && canonicalBlocked.length > 0) {
1526
- throw new TuiOperationBlockedError('taskHasBlockedTargets', '部分目标需要先处理。请调整选择。');
1604
+ throw new TuiOperationBlockedError('taskHasBlockedTargets', '部分目标需要先处理。请调整选择。', canonicalBlocked.flatMap((target) => target.diagnostics ?? []));
1527
1605
  }
1528
1606
  await preparation?.reporter.emit('evidence');
1529
1607
  const evidence = {
@@ -1662,7 +1740,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1662
1740
  return [];
1663
1741
  const platform = group.platform;
1664
1742
  return group.requestedPluginIds.map((pluginId) => {
1665
- const live = sessionStatusResult?.payload.targets.find((target) => target.pluginId === pluginId && target.platform === platform
1743
+ const live = flattenStatusTargets(sessionStatusResult?.payload.targets ?? []).find((target) => target.pluginId === pluginId && target.platform === platform
1666
1744
  && (target.scope === group.scope || statusTargetIntent(target).scopes.includes(group.scope)
1667
1745
  || platform === 'codex' && statusTargetActivation(target)?.[group.scope] === true));
1668
1746
  const revision = sessionStatusResult?.targetRevisions?.find((target) => target.pluginId === pluginId && target.platform === platform
@@ -1915,7 +1993,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1915
1993
  await preparation?.reporter.emit('status');
1916
1994
  const statusCatalogOperation = catalogOperation
1917
1995
  ?? (!freshCatalog ? sessionCatalogOperationContext : undefined);
1918
- const status = await statusCommandV7({
1996
+ const status = await statusCommandV8({
1919
1997
  plugins: [...new Set([intent.pluginId,
1920
1998
  ...(intent.kind === 'pluginWrite' && intent.originIssue?.consumerPluginId
1921
1999
  ? [intent.originIssue.consumerPluginId] : [])])],
@@ -2118,7 +2196,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2118
2196
  signal?.throwIfAborted();
2119
2197
  const evidenceTargets = intent.kind === 'configSave'
2120
2198
  ? configVisibleTargets
2121
- : status.payload.targets;
2199
+ : flattenStatusTargets(status.payload.targets);
2122
2200
  const evidenceRevisions = intent.kind === 'configSave'
2123
2201
  ? (status.targetRevisions ?? []).filter((revision) => evidenceTargets.some((target) => target.platform === revision.platform
2124
2202
  && target.scope === revision.scope
@@ -2849,7 +2927,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2849
2927
  const code = error instanceof TuiOperationBlockedError ? error.code : undefined;
2850
2928
  if (intent.kind === 'pluginWriteTask'
2851
2929
  && (code === 'taskHasBlockedTargets' || code === 'taskNoExecutableTargets')) {
2852
- throw operationReviewStale('确认后任务可执行目标发生变化。');
2930
+ throw operationReviewStale('确认后任务可执行目标发生变化。', error instanceof TuiOperationBlockedError ? error.diagnostics : undefined);
2853
2931
  }
2854
2932
  throw error;
2855
2933
  }