@lesliechan721/aw 0.1.0-beta.8 → 0.1.0-beta.9
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/dist/authoring/scripts/add-skill.mjs +4 -4
- package/dist/authoring/scripts/add-skill.mjs.map +1 -1
- package/dist/authoring/scripts/init-plugin.mjs +4 -4
- package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
- package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
- package/dist/authoring/scripts/lib/skill-scaffold.d.mts +77 -90
- package/dist/authoring/scripts/lib/skill-scaffold.mjs +99 -120
- package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
- package/dist/authoring/scripts/remove-plugin.mjs +1 -1
- package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
- package/dist/authoring/scripts/validate-plugin.mjs +34 -778
- package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
- package/dist/scripts/aw-skill-eval-materializer.d.ts +24 -0
- package/dist/scripts/{plugin-eval-materializer.js → aw-skill-eval-materializer.js} +34 -145
- package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
- package/dist/scripts/bundle-plugins.d.ts +1 -0
- package/dist/scripts/bundle-plugins.js +2 -1
- package/dist/scripts/bundle-plugins.js.map +1 -1
- package/dist/scripts/plugin-staging.d.ts +5 -6
- package/dist/scripts/plugin-staging.js +12 -13
- package/dist/scripts/plugin-staging.js.map +1 -1
- package/dist/src/authoring/workspace-commands.d.ts +2 -1
- package/dist/src/authoring/workspace-commands.js.map +1 -1
- package/dist/src/commands/aw-capabilities.d.ts +2 -2
- package/dist/src/commands/aw-capabilities.js +6 -11
- package/dist/src/commands/aw-capabilities.js.map +1 -1
- package/dist/src/commands/catalog-dev-source.d.ts +18 -12
- package/dist/src/commands/catalog-dev-source.js +241 -305
- package/dist/src/commands/catalog-dev-source.js.map +1 -1
- package/dist/src/commands/catalog-generation.d.ts +6 -19
- package/dist/src/commands/catalog-generation.js +10 -11
- package/dist/src/commands/catalog-generation.js.map +1 -1
- package/dist/src/commands/catalog-publication-inspection.d.ts +28 -27
- package/dist/src/commands/catalog-publication-inspection.js +139 -50
- package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
- package/dist/src/commands/catalog-publication-journal.d.ts +49 -0
- package/dist/src/commands/catalog-publication-journal.js +147 -0
- package/dist/src/commands/catalog-publication-journal.js.map +1 -0
- package/dist/src/commands/catalog-publication-target.d.ts +2 -1
- package/dist/src/commands/catalog-publication-target.js +37 -2
- package/dist/src/commands/catalog-publication-target.js.map +1 -1
- package/dist/src/commands/catalog-publish.d.ts +51 -15
- package/dist/src/commands/catalog-publish.js +576 -219
- package/dist/src/commands/catalog-publish.js.map +1 -1
- package/dist/src/commands/catalog-source.d.ts +3 -28
- package/dist/src/commands/catalog-source.js +29 -140
- package/dist/src/commands/catalog-source.js.map +1 -1
- package/dist/src/commands/catalog-status.d.ts +6 -26
- package/dist/src/commands/catalog-status.js +8 -146
- package/dist/src/commands/catalog-status.js.map +1 -1
- package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
- package/dist/src/commands/catalog-workspace-resolution.js +42 -0
- package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
- package/dist/src/commands/dependency-operation-plan.d.ts +88 -9
- package/dist/src/commands/dependency-operation-plan.js +331 -6
- package/dist/src/commands/dependency-operation-plan.js.map +1 -1
- package/dist/src/commands/init.d.ts +5 -11
- package/dist/src/commands/init.js +30 -51
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/offline-cleanup-evidence.d.ts +20 -0
- package/dist/src/commands/offline-cleanup-evidence.js +87 -9
- package/dist/src/commands/offline-cleanup-evidence.js.map +1 -1
- package/dist/src/commands/options.d.ts +6 -1
- package/dist/src/commands/options.js +64 -6
- package/dist/src/commands/options.js.map +1 -1
- package/dist/src/commands/plugin-capabilities.js +4 -0
- package/dist/src/commands/plugin-capabilities.js.map +1 -1
- package/dist/src/commands/rebuild-command.d.ts +10 -8
- package/dist/src/commands/rebuild-command.js +1217 -426
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +54 -6
- package/dist/src/commands/rebuild-integration.js +691 -364
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/rebuild-operations.d.ts +2 -0
- package/dist/src/commands/rebuild-operations.js +31 -17
- package/dist/src/commands/rebuild-operations.js.map +1 -1
- package/dist/src/commands/skill-eval.d.ts +33 -0
- package/dist/src/commands/skill-eval.js +363 -0
- package/dist/src/commands/skill-eval.js.map +1 -0
- package/dist/src/commands/{status-v5.d.ts → status-v6.d.ts} +59 -26
- package/dist/src/commands/{status-v5.js → status-v6.js} +882 -79
- package/dist/src/commands/status-v6.js.map +1 -0
- package/dist/src/commands/sync-command.d.ts +21 -0
- package/dist/src/commands/sync-command.js +206 -0
- package/dist/src/commands/sync-command.js.map +1 -0
- package/dist/src/commands/wizard.d.ts +2 -2
- package/dist/src/commands/write-commands.js +1 -43
- package/dist/src/commands/write-commands.js.map +1 -1
- package/dist/src/constants.d.ts +5 -4
- package/dist/src/constants.js +5 -4
- package/dist/src/constants.js.map +1 -1
- package/dist/src/eval/manifest-v1.d.ts +66 -0
- package/dist/src/eval/manifest-v1.js +326 -0
- package/dist/src/eval/manifest-v1.js.map +1 -0
- package/dist/src/generators/local-plugin.d.ts +3 -0
- package/dist/src/generators/local-plugin.js +123 -55
- package/dist/src/generators/local-plugin.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +412 -152
- package/dist/src/index.js.map +1 -1
- package/dist/src/output/command-receipt.d.ts +58 -8
- package/dist/src/output/command-receipt.js +141 -11
- package/dist/src/output/command-receipt.js.map +1 -1
- package/dist/src/output/model.d.ts +3 -11
- package/dist/src/portable/commands.d.ts +2 -2
- package/dist/src/portable/commands.js +2 -8
- package/dist/src/portable/commands.js.map +1 -1
- package/dist/src/portable/materializer.d.ts +1 -4
- package/dist/src/portable/materializer.js +0 -2
- package/dist/src/portable/materializer.js.map +1 -1
- package/dist/src/tui/catalog-generation.d.ts +20 -41
- package/dist/src/tui/catalog-generation.js +65 -224
- package/dist/src/tui/catalog-generation.js.map +1 -1
- package/dist/src/tui/components.js +1 -2
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/console-app-v4.js +461 -330
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.d.ts +3 -5
- package/dist/src/tui/controller-runtime.js +182 -187
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +42 -45
- package/dist/src/tui/controller.js +454 -407
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/diagnostics.d.ts +1 -2
- package/dist/src/tui/diagnostics.js.map +1 -1
- package/dist/src/tui/index.d.ts +1 -1
- package/dist/src/tui/index.js +1 -1
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/model.d.ts +81 -13
- package/dist/src/tui/model.js +378 -95
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/router.d.ts +9 -0
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/types.d.ts +54 -47
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/agent-version.d.ts +4 -1
- package/dist/src/utils/agent-version.js +28 -3
- package/dist/src/utils/agent-version.js.map +1 -1
- package/dist/src/utils/catalog-digests.d.ts +7 -0
- package/dist/src/utils/catalog-digests.js +27 -0
- package/dist/src/utils/catalog-digests.js.map +1 -1
- package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
- package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
- package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
- package/dist/src/utils/catalog-generation-error.d.ts +2 -1
- package/dist/src/utils/catalog-generation-error.js +12 -0
- package/dist/src/utils/catalog-generation-error.js.map +1 -1
- package/dist/src/utils/catalog-generation-resolver.d.ts +10 -3
- package/dist/src/utils/catalog-generation-resolver.js +90 -76
- package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
- package/dist/src/utils/catalog-generation-service.d.ts +24 -33
- package/dist/src/utils/catalog-generation-service.js +253 -567
- package/dist/src/utils/catalog-generation-service.js.map +1 -1
- package/dist/src/utils/catalog-generation-state.d.ts +4 -0
- package/dist/src/utils/catalog-generation-state.js +73 -106
- package/dist/src/utils/catalog-generation-state.js.map +1 -1
- package/dist/src/utils/catalog-generation-store.d.ts +16 -26
- package/dist/src/utils/catalog-generation-store.js +154 -290
- package/dist/src/utils/catalog-generation-store.js.map +1 -1
- package/dist/src/utils/catalog-manifest-v6.d.ts +5 -0
- package/dist/src/utils/{catalog-manifest-v5.js → catalog-manifest-v6.js} +23 -35
- package/dist/src/utils/catalog-manifest-v6.js.map +1 -0
- package/dist/src/utils/catalog-operation-context.d.ts +6 -14
- package/dist/src/utils/catalog-operation-context.js +68 -169
- package/dist/src/utils/catalog-operation-context.js.map +1 -1
- package/dist/src/utils/catalog-public-error.js +1 -4
- package/dist/src/utils/catalog-public-error.js.map +1 -1
- package/dist/src/utils/catalog-runtime-projection.d.ts +3 -0
- package/dist/src/utils/catalog-runtime-projection.js +5 -40
- package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
- package/dist/src/utils/catalog-snapshot.d.ts +2 -2
- package/dist/src/utils/catalog-source-references.d.ts +7 -0
- package/dist/src/utils/catalog-source-references.js +44 -55
- package/dist/src/utils/catalog-source-references.js.map +1 -1
- package/dist/src/utils/catalog-sources.js +25 -9
- package/dist/src/utils/catalog-sources.js.map +1 -1
- package/dist/src/utils/codex-config.d.ts +2 -0
- package/dist/src/utils/codex-config.js +22 -1
- package/dist/src/utils/codex-config.js.map +1 -1
- package/dist/src/utils/codex-manager.d.ts +50 -2
- package/dist/src/utils/codex-manager.js +160 -6
- package/dist/src/utils/codex-manager.js.map +1 -1
- package/dist/src/utils/codex-reconciler.d.ts +131 -0
- package/dist/src/utils/codex-reconciler.js +327 -0
- package/dist/src/utils/codex-reconciler.js.map +1 -0
- package/dist/src/utils/codex-shared-native.d.ts +56 -0
- package/dist/src/utils/codex-shared-native.js +87 -0
- package/dist/src/utils/codex-shared-native.js.map +1 -0
- package/dist/src/utils/codex-surface-analysis.d.ts +44 -0
- package/dist/src/utils/codex-surface-analysis.js +247 -0
- package/dist/src/utils/codex-surface-analysis.js.map +1 -0
- package/dist/src/utils/git-catalog-provider.js +56 -115
- package/dist/src/utils/git-catalog-provider.js.map +1 -1
- package/dist/src/utils/manifest.d.ts +1 -3
- package/dist/src/utils/manifest.js +1 -3
- package/dist/src/utils/manifest.js.map +1 -1
- package/dist/src/utils/multi-source-catalog.d.ts +1 -10
- package/dist/src/utils/multi-source-catalog.js +8 -175
- package/dist/src/utils/multi-source-catalog.js.map +1 -1
- package/dist/src/utils/native-cli.d.ts +34 -0
- package/dist/src/utils/native-cli.js +91 -0
- package/dist/src/utils/native-cli.js.map +1 -1
- package/dist/src/utils/orphan-marketplace-cleanup.js +1 -0
- package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
- package/dist/src/utils/ownership.d.ts +0 -10
- package/dist/src/utils/ownership.js +3 -88
- package/dist/src/utils/ownership.js.map +1 -1
- package/dist/src/utils/path-catalog-provider.d.ts +1 -1
- package/dist/src/utils/path-catalog-provider.js +45 -62
- package/dist/src/utils/path-catalog-provider.js.map +1 -1
- package/dist/src/utils/paths.d.ts +0 -1
- package/dist/src/utils/paths.js +0 -1
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/src/utils/plugin-config-write.js +0 -5
- package/dist/src/utils/plugin-config-write.js.map +1 -1
- package/dist/src/utils/plugin-intent-state.d.ts +39 -0
- package/dist/src/utils/plugin-intent-state.js +325 -0
- package/dist/src/utils/plugin-intent-state.js.map +1 -0
- package/dist/src/utils/target-eligibility.d.ts +3 -2
- package/dist/src/utils/target-eligibility.js +4 -3
- package/dist/src/utils/target-eligibility.js.map +1 -1
- package/package.json +5 -4
- package/dist/scripts/aw-skill-eval-engine.d.ts +0 -13
- package/dist/scripts/aw-skill-eval-engine.js +0 -31
- package/dist/scripts/aw-skill-eval-engine.js.map +0 -1
- package/dist/scripts/codex-eval-app-server.d.ts +0 -91
- package/dist/scripts/codex-eval-app-server.js +0 -1334
- package/dist/scripts/codex-eval-app-server.js.map +0 -1
- package/dist/scripts/codex-eval-isolation.d.ts +0 -61
- package/dist/scripts/codex-eval-isolation.js +0 -318
- package/dist/scripts/codex-eval-isolation.js.map +0 -1
- package/dist/scripts/codex-eval-process-management.d.ts +0 -8
- package/dist/scripts/codex-eval-process-management.js +0 -69
- package/dist/scripts/codex-eval-process-management.js.map +0 -1
- package/dist/scripts/codex-eval-process-tree.d.ts +0 -38
- package/dist/scripts/codex-eval-process-tree.js +0 -803
- package/dist/scripts/codex-eval-process-tree.js.map +0 -1
- package/dist/scripts/codex-eval-runtime-config.d.ts +0 -62
- package/dist/scripts/codex-eval-runtime-config.js +0 -843
- package/dist/scripts/codex-eval-runtime-config.js.map +0 -1
- package/dist/scripts/codex-trigger-eval.d.ts +0 -84
- package/dist/scripts/codex-trigger-eval.js +0 -968
- package/dist/scripts/codex-trigger-eval.js.map +0 -1
- package/dist/scripts/eval-plugin-skills-audited.d.ts +0 -6
- package/dist/scripts/eval-plugin-skills-audited.js +0 -261
- package/dist/scripts/eval-plugin-skills-audited.js.map +0 -1
- package/dist/scripts/eval-plugin-skills.d.ts +0 -45
- package/dist/scripts/eval-plugin-skills.js +0 -1589
- package/dist/scripts/eval-plugin-skills.js.map +0 -1
- package/dist/scripts/eval-run-layout.d.ts +0 -17
- package/dist/scripts/eval-run-layout.js +0 -26
- package/dist/scripts/eval-run-layout.js.map +0 -1
- package/dist/scripts/plugin-eval-contract.d.ts +0 -189
- package/dist/scripts/plugin-eval-contract.js +0 -745
- package/dist/scripts/plugin-eval-contract.js.map +0 -1
- package/dist/scripts/plugin-eval-materializer.d.ts +0 -13
- package/dist/scripts/plugin-eval-materializer.js.map +0 -1
- package/dist/scripts/skill-reporting.d.ts +0 -212
- package/dist/scripts/skill-reporting.js +0 -922
- package/dist/scripts/skill-reporting.js.map +0 -1
- package/dist/src/commands/catalog-context.d.ts +0 -52
- package/dist/src/commands/catalog-context.js +0 -156
- package/dist/src/commands/catalog-context.js.map +0 -1
- package/dist/src/commands/status-v5.js.map +0 -1
- package/dist/src/utils/catalog-manifest-v5.d.ts +0 -5
- package/dist/src/utils/catalog-manifest-v5.js.map +0 -1
- package/dist/src/utils/catalog-source-cache.d.ts +0 -62
- package/dist/src/utils/catalog-source-cache.js +0 -321
- package/dist/src/utils/catalog-source-cache.js.map +0 -1
- package/dist/src/utils/codex-project-surface.d.ts +0 -3
- package/dist/src/utils/codex-project-surface.js +0 -49
- package/dist/src/utils/codex-project-surface.js.map +0 -1
- package/dist/src/utils/plugin-release.d.ts +0 -2
- package/dist/src/utils/plugin-release.js +0 -2
- package/dist/src/utils/plugin-release.js.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import { CatalogGenerationError } from '../utils/catalog-generation-error.js';
|
|
3
|
-
import {
|
|
3
|
+
import { requestedIntentAction } from '../output/command-receipt.js';
|
|
4
|
+
import { buildTuiViewModel, dependencyPlacementTargets, hasInstalledPlacement, } from './model.js';
|
|
4
5
|
import { safeDiagnosticCode } from './diagnostics.js';
|
|
5
6
|
import { targetHasArtifactCollision } from '../utils/target-eligibility.js';
|
|
6
7
|
import { TuiRouter, } from './router.js';
|
|
7
|
-
import { CatalogRefreshCoordinator, } from './catalog-generation.js';
|
|
8
8
|
function reviewPreparationStageLabel(stage) {
|
|
9
9
|
return {
|
|
10
10
|
validate: '验证当前状态',
|
|
@@ -75,11 +75,19 @@ function operationLabels(intent) {
|
|
|
75
75
|
destructive: false,
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
|
-
if (intent.kind === '
|
|
78
|
+
if (intent.kind === 'pluginWrite' && intent.dependencyPlacementOnly === 'desired') {
|
|
79
79
|
return {
|
|
80
|
-
title: '
|
|
81
|
-
description: '
|
|
82
|
-
primaryLabel: '
|
|
80
|
+
title: '调整依赖位置',
|
|
81
|
+
description: '先更新当前项目的 desired placement;不会删除所有项目中的实际内容。',
|
|
82
|
+
primaryLabel: '更新 desired placement',
|
|
83
|
+
destructive: false,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (intent.kind === 'pluginWrite' && intent.dependencyPlacementAdjustment) {
|
|
87
|
+
return {
|
|
88
|
+
title: '调整依赖位置',
|
|
89
|
+
description: '按最终依赖位置更新 desired intent,并应用非破坏性变更。',
|
|
90
|
+
primaryLabel: '应用依赖位置',
|
|
83
91
|
destructive: false,
|
|
84
92
|
};
|
|
85
93
|
}
|
|
@@ -114,6 +122,7 @@ function operationLabels(intent) {
|
|
|
114
122
|
reinstall: ['重新安装插件', '清理 AW 管理的内容后完整重建。', true],
|
|
115
123
|
uninstall: ['移除插件', '移除 AW 管理的安装内容。配置保持不变。', true],
|
|
116
124
|
cleanup: ['清理插件', '只移除可证明属于 AW 的现场内容。配置保持不变。', true],
|
|
125
|
+
sync: ['同步插件', '按已保存的 desired intent 修复当前现场。', false],
|
|
117
126
|
};
|
|
118
127
|
const [title, description, destructive] = labels[intent.action];
|
|
119
128
|
return { title, description, primaryLabel: title, destructive };
|
|
@@ -136,8 +145,8 @@ function operationLabels(intent) {
|
|
|
136
145
|
}
|
|
137
146
|
if (intent.kind === 'sourceMutation') {
|
|
138
147
|
const labels = {
|
|
139
|
-
add: ['添加来源', '
|
|
140
|
-
update: ['修改来源', '
|
|
148
|
+
add: ['添加来源', '只保存来源配置。保存后请在终端运行 source sync。', '保存来源', false],
|
|
149
|
+
update: ['修改来源', '只保存来源配置。保存后请在终端运行 source sync。', '保存来源', false],
|
|
141
150
|
setPrimary: ['设为主来源', '只改变默认 marketplace,不迁移安装。', '设为主来源', false],
|
|
142
151
|
remove: ['移除来源', '移除来源配置;不会删除其它来源。', '移除来源', true],
|
|
143
152
|
};
|
|
@@ -177,27 +186,8 @@ function reviewChanges(intent, evidence, dependencyOverview) {
|
|
|
177
186
|
description: review.collisions.map((collision) => collision.name).join('、') }] : []),
|
|
178
187
|
];
|
|
179
188
|
}
|
|
180
|
-
if (intent.kind === 'catalogActivate') {
|
|
181
|
-
const candidate = evidence?.catalogCandidate;
|
|
182
|
-
if (!candidate) {
|
|
183
|
-
throw new TuiOperationBlockedError('catalogGenerationCandidateUnavailable', '缺少 Catalog Candidate Review 证据。');
|
|
184
|
-
}
|
|
185
|
-
return [
|
|
186
|
-
{
|
|
187
|
-
kind: 'change',
|
|
188
|
-
title: `Catalog ${candidate.activeVersion} → ${candidate.candidateVersion}`,
|
|
189
|
-
description: `${candidate.channel === 'beta' ? 'Beta' : 'Release'} · ${candidate.requestedRef}`,
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
kind: 'change',
|
|
193
|
-
title: `插件变化 +${candidate.pluginChanges.added} ~${candidate.pluginChanges.updated} −${candidate.pluginChanges.removed}`,
|
|
194
|
-
},
|
|
195
|
-
{ kind: 'keep', title: '已安装插件不会自动升级' },
|
|
196
|
-
{ kind: 'keep', title: '当前设置不会改变' },
|
|
197
|
-
{ kind: 'change', title: '激活后重新加载插件列表和依赖关系' },
|
|
198
|
-
];
|
|
199
|
-
}
|
|
200
189
|
if (intent.kind === 'pluginWrite') {
|
|
190
|
+
const desiredIntentKind = requestedIntentAction(intent.action);
|
|
201
191
|
const runtimeChanges = evidence?.codexManager
|
|
202
192
|
? [{
|
|
203
193
|
kind: evidence.codexManager.state === 'requires-install' ? 'add' : 'keep',
|
|
@@ -212,42 +202,123 @@ function reviewChanges(intent, evidence, dependencyOverview) {
|
|
|
212
202
|
: `使用兼容的 system Codex ${evidence.codexManager.version}`,
|
|
213
203
|
}]
|
|
214
204
|
: [];
|
|
205
|
+
const codexChanges = (evidence?.codexReconcilePlans ?? []).flatMap((codexPlan) => [
|
|
206
|
+
...codexPlan.sharedNativeActions.map((action) => ({
|
|
207
|
+
kind: action.action === 'reuse' || action.action === 'claim' || action.action === 'none' ? 'keep'
|
|
208
|
+
: action.action === 'install' ? 'add' : 'change',
|
|
209
|
+
group: 'sharedCodexNative',
|
|
210
|
+
pluginId: action.pluginId,
|
|
211
|
+
title: action.action === 'claim'
|
|
212
|
+
? `${action.codexPluginId}:复用精确现有 shared native(不建立 ledger)`
|
|
213
|
+
: action.codexPluginId,
|
|
214
|
+
description: action.action === 'claim'
|
|
215
|
+
? '复用已存在的精确 shared native;不建立 ownership ledger'
|
|
216
|
+
: `${action.action} · shared${action.affectsOtherScopes ? ' · 可能影响其他项目' : ''}`,
|
|
217
|
+
})),
|
|
218
|
+
...codexPlan.activationActions.user.map((action) => ({
|
|
219
|
+
kind: action.action === 'none' ? 'keep' : action.desired ? 'change' : 'remove',
|
|
220
|
+
group: 'userActivation',
|
|
221
|
+
pluginId: action.pluginId,
|
|
222
|
+
title: action.codexPluginId,
|
|
223
|
+
description: `desired=${String(action.desired)} · ${action.action}`,
|
|
224
|
+
})),
|
|
225
|
+
...codexPlan.activationActions.project.map((action) => ({
|
|
226
|
+
kind: action.action === 'none' ? 'keep' : action.desired ? 'change' : 'remove',
|
|
227
|
+
group: 'projectActivation',
|
|
228
|
+
pluginId: action.pluginId,
|
|
229
|
+
title: action.codexPluginId,
|
|
230
|
+
description: `desired=${String(action.desired)} · ${action.action}`,
|
|
231
|
+
})),
|
|
232
|
+
...[...codexPlan.extensionWrites, ...codexPlan.extensionRemovals]
|
|
233
|
+
.map((action) => ({
|
|
234
|
+
kind: action.action === 'write' ? 'change' : 'remove',
|
|
235
|
+
group: 'awExtensions',
|
|
236
|
+
pluginId: action.pluginId,
|
|
237
|
+
title: action.componentKey,
|
|
238
|
+
description: `${action.scope} · ${action.action}`,
|
|
239
|
+
})),
|
|
240
|
+
]);
|
|
215
241
|
const plan = evidence?.dependencyPlan;
|
|
216
|
-
if (plan && (intent.action === 'install' || intent.action === 'upgrade'
|
|
242
|
+
if (plan && (intent.action === 'install' || intent.action === 'upgrade'
|
|
243
|
+
|| intent.action === 'reinstall' || intent.action === 'sync')) {
|
|
217
244
|
const changes = plan.targets.map((target) => {
|
|
218
245
|
const relation = dependencyRelation(plan, target.pluginId);
|
|
219
|
-
const actionKind = target.action === 'keep'
|
|
220
|
-
? 'keep'
|
|
221
|
-
: target.action === 'install'
|
|
222
|
-
? 'add'
|
|
223
|
-
: 'change';
|
|
224
246
|
return {
|
|
225
|
-
kind:
|
|
226
|
-
group:
|
|
247
|
+
kind: desiredIntentKind,
|
|
248
|
+
group: target.resolvedScope === 'project' ? 'projectIntent' : 'userIntent',
|
|
227
249
|
pluginId: target.pluginId,
|
|
228
250
|
relation,
|
|
229
251
|
title: pluginNameFromId(target.pluginId),
|
|
230
|
-
description: `${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${target.resolvedScope === 'user' ? '所有项目' : '当前项目'}${target.action === 'keep' ? ' · 已可用' : ''}`,
|
|
252
|
+
description: `${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${target.resolvedScope === 'user' ? '所有项目' : '当前项目'}${target.action === 'keep' ? ' · 已可用' : ''}${target.assignmentSource === 'existingWiderTarget' ? ' · 复用健康的所有项目安装' : target.assignmentSource === 'consumerPromotion' ? ' · 随上游依赖一并调整' : ''}`,
|
|
231
253
|
};
|
|
232
254
|
});
|
|
233
255
|
changes.push({
|
|
234
256
|
kind: 'keep',
|
|
235
257
|
group: 'preserved',
|
|
236
|
-
title:
|
|
237
|
-
|
|
258
|
+
title: intent.dependencyPlacementAdjustment
|
|
259
|
+
? '根插件和插件设置保持不变'
|
|
260
|
+
: '插件设置和已有依赖保持不变',
|
|
261
|
+
description: intent.dependencyPlacementAdjustment
|
|
262
|
+
? '这里只调整所列 dependency placement。'
|
|
263
|
+
: '移除根插件时不会自动清理依赖。',
|
|
238
264
|
});
|
|
239
|
-
|
|
265
|
+
if (intent.dependencyPlacementOnly === 'desired') {
|
|
266
|
+
changes.push({
|
|
267
|
+
kind: desiredIntentKind,
|
|
268
|
+
group: 'projectIntent',
|
|
269
|
+
title: '先更新依赖 desired placement',
|
|
270
|
+
description: '这一步不删除所有项目中的实际内容。',
|
|
271
|
+
}, {
|
|
272
|
+
kind: 'warning',
|
|
273
|
+
group: 'preserved',
|
|
274
|
+
title: '后续第 1 步:显式 user cleanup;不自动扫描/清理',
|
|
275
|
+
description: '请在已知的所有项目位置确认 cleanup。',
|
|
276
|
+
}, {
|
|
277
|
+
kind: 'keep',
|
|
278
|
+
group: 'preserved',
|
|
279
|
+
title: '后续第 2 步:同步当前项目',
|
|
280
|
+
description: 'cleanup 完成后运行当前项目 sync。',
|
|
281
|
+
});
|
|
282
|
+
return changes;
|
|
283
|
+
}
|
|
284
|
+
return [...changes, ...codexChanges, ...runtimeChanges];
|
|
240
285
|
}
|
|
241
286
|
const platforms = intent.platform === 'both'
|
|
242
287
|
? ['codex', 'claude']
|
|
243
288
|
: [intent.platform];
|
|
244
289
|
const changes = platforms.map((platform) => ({
|
|
290
|
+
kind: desiredIntentKind,
|
|
291
|
+
group: intent.scope === 'project' ? 'projectIntent' : 'userIntent',
|
|
292
|
+
pluginId: intent.pluginId,
|
|
293
|
+
relation: 'requested',
|
|
294
|
+
title: pluginNameFromId(intent.pluginId),
|
|
295
|
+
description: `${platform === 'codex' ? 'Codex' : 'Claude Code'} · ${intent.scope === 'user' ? '所有项目' : '当前项目'}`,
|
|
296
|
+
}));
|
|
297
|
+
changes.push(...platforms.map((platform) => ({
|
|
245
298
|
kind: intent.action === 'uninstall' || intent.action === 'cleanup' ? 'remove' : intent.action === 'install' ? 'add' : 'change',
|
|
299
|
+
group: 'requested',
|
|
300
|
+
pluginId: intent.pluginId,
|
|
301
|
+
relation: 'requested',
|
|
246
302
|
title: `${platform === 'codex' ? 'Codex' : 'Claude Code'} · ${intent.scope === 'user' ? '所有项目' : '当前项目'}`,
|
|
247
303
|
description: intent.action === 'uninstall' || intent.action === 'cleanup'
|
|
248
304
|
? '插件设置不会删除。'
|
|
249
305
|
: '执行前会再次检查当前安装状态。',
|
|
250
|
-
}));
|
|
306
|
+
})));
|
|
307
|
+
if (intent.action === 'cleanup' && evidence?.offlineCleanup) {
|
|
308
|
+
const actionLabels = {
|
|
309
|
+
removeOwnedOutput: '移除 AW-owned 输出',
|
|
310
|
+
removeNativeRegistration: '移除原生插件注册',
|
|
311
|
+
removeLifecycleSnapshot: '移除 lifecycle snapshot',
|
|
312
|
+
};
|
|
313
|
+
changes.push(...evidence.offlineCleanup.plan.actions.map((action) => ({
|
|
314
|
+
kind: 'remove',
|
|
315
|
+
group: 'requested',
|
|
316
|
+
pluginId: evidence.offlineCleanup.pluginId,
|
|
317
|
+
relation: 'requested',
|
|
318
|
+
title: actionLabels[action.action],
|
|
319
|
+
description: action.target,
|
|
320
|
+
})));
|
|
321
|
+
}
|
|
251
322
|
if (intent.action === 'uninstall' || intent.action === 'cleanup') {
|
|
252
323
|
changes.push(...(dependencyOverview?.dependencies ?? []).map((dependency) => ({
|
|
253
324
|
kind: 'keep',
|
|
@@ -268,7 +339,15 @@ function reviewChanges(intent, evidence, dependencyOverview) {
|
|
|
268
339
|
if (intent.originIssue) {
|
|
269
340
|
changes.push({ kind: 'keep', title: '插件设置保持不变' }, { kind: 'warning', title: '执行前重新检查完整目标和来源证据' });
|
|
270
341
|
}
|
|
271
|
-
|
|
342
|
+
if (intent.recoveryDescription) {
|
|
343
|
+
changes.push({
|
|
344
|
+
kind: 'warning',
|
|
345
|
+
group: 'preserved',
|
|
346
|
+
title: `后续:${intent.scope === 'user' ? '当前项目' : '所有项目'} sync`,
|
|
347
|
+
description: intent.recoveryDescription,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return [...changes, ...codexChanges, ...runtimeChanges];
|
|
272
351
|
}
|
|
273
352
|
if (intent.kind === 'configReset') {
|
|
274
353
|
const layerLabel = intent.scope === 'user'
|
|
@@ -371,13 +450,19 @@ function technicalDetails(evidence) {
|
|
|
371
450
|
if (evidence.offlineCleanup) {
|
|
372
451
|
details.push(`离线清理原因:${evidence.offlineCleanup.reason}`);
|
|
373
452
|
details.push(`离线清理目标:${short(evidence.offlineCleanup.targetFingerprint)}`);
|
|
453
|
+
details.push(`离线清理计划:${evidence.offlineCleanup.plan.actions.length} 个动作 · ${short(evidence.offlineCleanup.planDigest)}`);
|
|
454
|
+
for (const action of evidence.offlineCleanup.plan.actions) {
|
|
455
|
+
details.push(`离线清理动作:${action.action} · ${action.target}`);
|
|
456
|
+
}
|
|
374
457
|
}
|
|
375
458
|
if (evidence.dependencyPlan)
|
|
376
459
|
details.push(`安装计划:${evidence.dependencyPlan.resolvedPluginIds.length} 个插件`);
|
|
460
|
+
if (evidence.projectIntentDigest)
|
|
461
|
+
details.push(`Project intent:${short(evidence.projectIntentDigest)}`);
|
|
377
462
|
if (evidence.config)
|
|
378
463
|
details.push(`设置层:${evidence.config.scope}/${evidence.config.projectLayer} · ${short(evidence.config.storeDigest)}`);
|
|
379
464
|
if (evidence.sourceWrite) {
|
|
380
|
-
details.push(
|
|
465
|
+
details.push(`新配置:${short(evidence.sourceWrite.proposedConfigDigest)}`);
|
|
381
466
|
details.push(`引用清单:${short(evidence.sourceWrite.inventoryDigest)} · ${evidence.sourceWrite.referenceCount} 项`);
|
|
382
467
|
}
|
|
383
468
|
if (evidence.portable) {
|
|
@@ -422,18 +507,15 @@ export class TuiController {
|
|
|
422
507
|
#router = new TuiRouter();
|
|
423
508
|
#listeners = new Set();
|
|
424
509
|
#health = new Map();
|
|
425
|
-
#sourceTokens = new Map();
|
|
426
|
-
#successfulSourceRefreshes = new Map();
|
|
427
|
-
#sourceAborts = new Map();
|
|
428
510
|
#tickets = new Map();
|
|
429
511
|
#inflightOperations = new Map();
|
|
430
512
|
#abort = new AbortController();
|
|
431
|
-
#catalogRefreshCoordinator;
|
|
432
513
|
#progressUnsubscribe;
|
|
433
514
|
#progressFeed;
|
|
434
515
|
#phase = 'idle';
|
|
435
516
|
#status;
|
|
436
517
|
#catalogGenerations = new Map();
|
|
518
|
+
#sessionGenerationKeys = new Map();
|
|
437
519
|
#viewModel;
|
|
438
520
|
#progress = { messages: [] };
|
|
439
521
|
#progressTargets = [];
|
|
@@ -447,32 +529,13 @@ export class TuiController {
|
|
|
447
529
|
#portableRequestToken = 0;
|
|
448
530
|
#portableRequestAbort;
|
|
449
531
|
#portablePending;
|
|
532
|
+
#installPlacementDraft;
|
|
533
|
+
#installPlacementRequestToken = 0;
|
|
534
|
+
#installPlacementRequestAbort;
|
|
450
535
|
#generation = 0;
|
|
451
|
-
#refreshSequence = 0;
|
|
452
|
-
#appliedRefreshSequence = 0;
|
|
453
536
|
#disposed = false;
|
|
454
537
|
constructor(dependencies, progressFeed) {
|
|
455
538
|
this.dependencies = dependencies;
|
|
456
|
-
if (dependencies.refreshCatalogSource) {
|
|
457
|
-
this.#catalogRefreshCoordinator = new CatalogRefreshCoordinator({
|
|
458
|
-
maxConcurrency: 2,
|
|
459
|
-
execute: dependencies.refreshCatalogSource,
|
|
460
|
-
onUpdate: (sourceId, state, errorCode) => {
|
|
461
|
-
const current = this.#catalogGenerations.get(sourceId);
|
|
462
|
-
if (current) {
|
|
463
|
-
this.#catalogGenerations.set(sourceId, {
|
|
464
|
-
...current,
|
|
465
|
-
refreshState: state,
|
|
466
|
-
...(errorCode ? { lastCheckCode: errorCode } : {}),
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
this.#publish();
|
|
470
|
-
},
|
|
471
|
-
onResult: (source) => {
|
|
472
|
-
this.#catalogGenerations.set(source.sourceId, structuredClone(source));
|
|
473
|
-
},
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
539
|
this.#progressFeed = progressFeed;
|
|
477
540
|
if (progressFeed) {
|
|
478
541
|
this.#progressUnsubscribe = progressFeed.subscribe((progress) => {
|
|
@@ -510,6 +573,7 @@ export class TuiController {
|
|
|
510
573
|
sourceHealth: [...this.#health.values()],
|
|
511
574
|
catalogGenerations: [...this.#catalogGenerations.values()]
|
|
512
575
|
.sort((left, right) => left.sourceId.localeCompare(right.sourceId, 'en')),
|
|
576
|
+
reloadRequiredSourceIds: [],
|
|
513
577
|
progress: this.#progress,
|
|
514
578
|
progressTargets: this.#progressTargets,
|
|
515
579
|
...(this.#activeReview ? { activeReview: this.#activeReview } : {}),
|
|
@@ -517,6 +581,20 @@ export class TuiController {
|
|
|
517
581
|
...(this.#result ? { result: this.#result } : {}),
|
|
518
582
|
...(this.#loadError ? { loadError: this.#loadError } : {}),
|
|
519
583
|
...(this.#portableDraft ? { portableDraft: this.#portableDraft } : {}),
|
|
584
|
+
...(this.#installPlacementDraft
|
|
585
|
+
? { installPlacementDraft: {
|
|
586
|
+
pluginId: this.#installPlacementDraft.pluginId,
|
|
587
|
+
rootScope: this.#installPlacementDraft.rootScope,
|
|
588
|
+
platforms: this.#installPlacementDraft.platforms,
|
|
589
|
+
dependencyScopeOverrides: this.#installPlacementDraft.dependencyScopeOverrides,
|
|
590
|
+
state: this.#installPlacementDraft.state,
|
|
591
|
+
targets: this.#installPlacementDraft.targets,
|
|
592
|
+
canContinue: this.#installPlacementDraft.canContinue,
|
|
593
|
+
...(this.#installPlacementDraft.error
|
|
594
|
+
? { error: this.#installPlacementDraft.error }
|
|
595
|
+
: {}),
|
|
596
|
+
} }
|
|
597
|
+
: {}),
|
|
520
598
|
});
|
|
521
599
|
}
|
|
522
600
|
subscribe(listener) {
|
|
@@ -567,6 +645,8 @@ export class TuiController {
|
|
|
567
645
|
|| route.name === 'pluginDependencies'
|
|
568
646
|
|| route.name === 'installPlacement'
|
|
569
647
|
|| route.name === 'installTargets'
|
|
648
|
+
|| route.name === 'dependencyPlacement'
|
|
649
|
+
|| route.name === 'dependencyPlacementEditor'
|
|
570
650
|
|| route.name === 'pluginDetail'
|
|
571
651
|
|| route.name === 'platform'
|
|
572
652
|
|| route.name === 'pluginAbout'
|
|
@@ -580,6 +660,7 @@ export class TuiController {
|
|
|
580
660
|
name: 'pluginList',
|
|
581
661
|
mode: route.name === 'pluginPreview' || route.name === 'pluginDependencies'
|
|
582
662
|
|| route.name === 'installPlacement' || route.name === 'installTargets'
|
|
663
|
+
|| route.name === 'dependencyPlacement' || route.name === 'dependencyPlacementEditor'
|
|
583
664
|
? 'browse'
|
|
584
665
|
: 'installed',
|
|
585
666
|
};
|
|
@@ -595,14 +676,6 @@ export class TuiController {
|
|
|
595
676
|
&& !this.#viewModel?.sourceTabs.some((source) => source.id === route.sourceId)) {
|
|
596
677
|
return { name: 'sourceList' };
|
|
597
678
|
}
|
|
598
|
-
if ((route.name === 'catalogCandidate'
|
|
599
|
-
|| route.name === 'catalogRefreshPolicy'
|
|
600
|
-
|| route.name === 'catalogRefresh'
|
|
601
|
-
|| route.name === 'catalogImmutableViolation'
|
|
602
|
-
|| route.name === 'catalogActivationBlocked')
|
|
603
|
-
&& !this.#catalogGenerations.has(route.sourceId)) {
|
|
604
|
-
return { name: 'sourceList' };
|
|
605
|
-
}
|
|
606
679
|
return route;
|
|
607
680
|
}
|
|
608
681
|
#normalizeCurrentRoute() {
|
|
@@ -619,78 +692,25 @@ export class TuiController {
|
|
|
619
692
|
this.#catalogGenerations.set(source.sourceId, structuredClone(source));
|
|
620
693
|
}
|
|
621
694
|
}
|
|
695
|
+
this.#sessionGenerationKeys.clear();
|
|
696
|
+
if (status.sessionGenerationKeys) {
|
|
697
|
+
for (const [sourceId, generationKey] of Object.entries(status.sessionGenerationKeys)) {
|
|
698
|
+
this.#sessionGenerationKeys.set(sourceId, generationKey);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
622
701
|
this.#health.clear();
|
|
623
702
|
for (const source of status.payload.catalog.sources) {
|
|
624
703
|
this.#health.set(source.id, {
|
|
625
704
|
sourceId: source.id,
|
|
626
705
|
state: source.error ? 'unavailable'
|
|
627
|
-
:
|
|
628
|
-
: source.freshness === 'cache' ? 'cache' : 'ready',
|
|
706
|
+
: 'ready',
|
|
629
707
|
...(source.error ? { message: '来源当前没有可用数据。' } : {}),
|
|
630
708
|
});
|
|
631
|
-
const successful = this.#successfulSourceRefreshes.get(source.id);
|
|
632
|
-
if (successful?.generation === this.#generation
|
|
633
|
-
&& successful.sourceIdentity === source.identity
|
|
634
|
-
&& successful.requestedRef === source.requestedRef
|
|
635
|
-
&& !source.error) {
|
|
636
|
-
this.#health.set(source.id, {
|
|
637
|
-
sourceId: source.id,
|
|
638
|
-
state: source.type === 'path' || source.freshness === 'path' ? 'path' : 'ready',
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
const sourceIds = new Set(status.payload.catalog.sources.map((source) => source.id));
|
|
643
|
-
for (const sourceId of this.#successfulSourceRefreshes.keys()) {
|
|
644
|
-
if (!sourceIds.has(sourceId))
|
|
645
|
-
this.#successfulSourceRefreshes.delete(sourceId);
|
|
646
709
|
}
|
|
647
710
|
this.#project();
|
|
648
711
|
this.#normalizeCurrentRoute();
|
|
649
712
|
}
|
|
650
|
-
#applyRefreshStatus(status, sourceId, health, refreshSequence) {
|
|
651
|
-
if (refreshSequence < this.#appliedRefreshSequence)
|
|
652
|
-
return false;
|
|
653
|
-
this.#appliedRefreshSequence = refreshSequence;
|
|
654
|
-
this.#applyStatus(status);
|
|
655
|
-
if (!status.payload.catalog.sources.some((source) => source.id === sourceId))
|
|
656
|
-
return true;
|
|
657
|
-
this.#health.set(sourceId, health);
|
|
658
|
-
this.#project();
|
|
659
|
-
return true;
|
|
660
|
-
}
|
|
661
|
-
#recordSuccessfulSourceRefresh(sourceId, checked, generation) {
|
|
662
|
-
const authoritative = this.#status?.payload.catalog.sources.find((source) => source.id === sourceId);
|
|
663
|
-
if (!authoritative || authoritative.type !== 'git' || authoritative.error
|
|
664
|
-
|| authoritative.identity !== checked.sourceIdentity
|
|
665
|
-
|| authoritative.requestedRef !== checked.requestedRef)
|
|
666
|
-
return;
|
|
667
|
-
this.#successfulSourceRefreshes.set(sourceId, {
|
|
668
|
-
generation,
|
|
669
|
-
sourceIdentity: checked.sourceIdentity,
|
|
670
|
-
requestedRef: checked.requestedRef,
|
|
671
|
-
});
|
|
672
|
-
this.#health.set(sourceId, {
|
|
673
|
-
sourceId,
|
|
674
|
-
state: 'ready',
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
#cancelSourceRefreshes() {
|
|
678
|
-
for (const abort of this.#sourceAborts.values())
|
|
679
|
-
abort.abort();
|
|
680
|
-
this.#sourceAborts.clear();
|
|
681
|
-
}
|
|
682
|
-
#startGitSourceRefreshes() {
|
|
683
|
-
if (this.#catalogRefreshCoordinator) {
|
|
684
|
-
this.#catalogRefreshCoordinator.start([...this.#catalogGenerations.values()]);
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
for (const source of this.#status?.payload.catalog.sources ?? []) {
|
|
688
|
-
if (source.type === 'git')
|
|
689
|
-
void this.refreshSource(source.id).catch(() => undefined);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
713
|
async load() {
|
|
693
|
-
this.#cancelSourceRefreshes();
|
|
694
714
|
this.dependencies.beginCatalogSession?.();
|
|
695
715
|
const generation = ++this.#generation;
|
|
696
716
|
this.#phase = 'loading';
|
|
@@ -715,125 +735,11 @@ export class TuiController {
|
|
|
715
735
|
this.#phase = 'ready';
|
|
716
736
|
this.#loadError = undefined;
|
|
717
737
|
this.#publish();
|
|
718
|
-
this.#startGitSourceRefreshes();
|
|
719
|
-
}
|
|
720
|
-
async refreshSource(sourceId) {
|
|
721
|
-
if (this.#disposed || !this.#status)
|
|
722
|
-
return;
|
|
723
|
-
if (this.#catalogRefreshCoordinator) {
|
|
724
|
-
await this.#catalogRefreshCoordinator.refresh(sourceId, this.#catalogGenerations.get(sourceId)?.refreshState ?? 'idle');
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
this.#sourceAborts.get(sourceId)?.abort();
|
|
728
|
-
const sourceAbort = new AbortController();
|
|
729
|
-
this.#sourceAborts.set(sourceId, sourceAbort);
|
|
730
|
-
const finishRefresh = () => {
|
|
731
|
-
if (this.#sourceAborts.get(sourceId) === sourceAbort)
|
|
732
|
-
this.#sourceAborts.delete(sourceId);
|
|
733
|
-
};
|
|
734
|
-
const signal = AbortSignal.any([this.#abort.signal, sourceAbort.signal]);
|
|
735
|
-
const token = (this.#sourceTokens.get(sourceId) ?? 0) + 1;
|
|
736
|
-
this.#sourceTokens.set(sourceId, token);
|
|
737
|
-
const generation = this.#generation;
|
|
738
|
-
const current = this.#status.payload.catalog.sources.find((source) => source.id === sourceId);
|
|
739
|
-
if (!current) {
|
|
740
|
-
finishRefresh();
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
const hadCache = current.type === 'git' && current.error === undefined
|
|
744
|
-
&& (current.freshness === 'cache' || current.freshness === 'remote');
|
|
745
|
-
this.#health.set(sourceId, { sourceId, state: 'checking' });
|
|
746
|
-
this.#project();
|
|
747
|
-
this.#publish();
|
|
748
|
-
let checkError;
|
|
749
|
-
let checked;
|
|
750
|
-
try {
|
|
751
|
-
checked = await this.dependencies.checkSource(sourceId, signal);
|
|
752
|
-
}
|
|
753
|
-
catch (error) {
|
|
754
|
-
checkError = error;
|
|
755
|
-
}
|
|
756
|
-
if (this.#disposed || signal.aborted || generation !== this.#generation
|
|
757
|
-
|| token !== this.#sourceTokens.get(sourceId)) {
|
|
758
|
-
finishRefresh();
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
if (checkError)
|
|
762
|
-
this.#successfulSourceRefreshes.delete(sourceId);
|
|
763
|
-
const refreshSequence = ++this.#refreshSequence;
|
|
764
|
-
let status;
|
|
765
|
-
let authoritativeReloadSucceeded = true;
|
|
766
|
-
try {
|
|
767
|
-
status = await this.dependencies.loadStatus(signal);
|
|
768
|
-
}
|
|
769
|
-
catch (error) {
|
|
770
|
-
authoritativeReloadSucceeded = false;
|
|
771
|
-
if (!checkError)
|
|
772
|
-
checkError = error;
|
|
773
|
-
this.#successfulSourceRefreshes.delete(sourceId);
|
|
774
|
-
status = structuredClone(this.#status);
|
|
775
|
-
}
|
|
776
|
-
if (this.#disposed || signal.aborted || generation !== this.#generation
|
|
777
|
-
|| token !== this.#sourceTokens.get(sourceId)) {
|
|
778
|
-
finishRefresh();
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
if (checkError) {
|
|
782
|
-
await this.#logErrorSafe(checkError, 'catalogSourceRefreshFailed', {
|
|
783
|
-
privateConfigOperation: true,
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
const refreshed = status.payload.catalog.sources.find((source) => source.id === sourceId);
|
|
787
|
-
const refreshedMatchesCheck = checked !== undefined && refreshed !== undefined
|
|
788
|
-
&& refreshed.type === 'git'
|
|
789
|
-
&& !refreshed.error
|
|
790
|
-
&& refreshed.identity === checked.sourceIdentity
|
|
791
|
-
&& refreshed.requestedRef === checked.requestedRef;
|
|
792
|
-
const state = checkError
|
|
793
|
-
? authoritativeReloadSucceeded
|
|
794
|
-
? !refreshed || refreshed.error
|
|
795
|
-
? 'unavailable'
|
|
796
|
-
: refreshed.type === 'path' || refreshed.freshness === 'path'
|
|
797
|
-
? 'path'
|
|
798
|
-
: refreshed.freshness === 'cache' ? 'degraded' : 'unavailable'
|
|
799
|
-
: hadCache ? 'degraded' : 'unavailable'
|
|
800
|
-
: !refreshed
|
|
801
|
-
? 'unavailable'
|
|
802
|
-
: refreshed.error
|
|
803
|
-
? 'unavailable'
|
|
804
|
-
: !refreshedMatchesCheck
|
|
805
|
-
? refreshed.type === 'path' || refreshed.freshness === 'path'
|
|
806
|
-
? 'path'
|
|
807
|
-
: refreshed.freshness === 'cache' ? 'cache' : 'unavailable'
|
|
808
|
-
: refreshed.type === 'path' || refreshed.freshness === 'path'
|
|
809
|
-
? 'path'
|
|
810
|
-
: 'ready';
|
|
811
|
-
this.#applyRefreshStatus(status, sourceId, {
|
|
812
|
-
sourceId,
|
|
813
|
-
state,
|
|
814
|
-
...(state === 'degraded' || state === 'unavailable' ? {
|
|
815
|
-
issueId: `source:${sourceId}:${state === 'degraded' ? 'cache' : 'unavailable'}`,
|
|
816
|
-
message: checkError
|
|
817
|
-
? state === 'degraded'
|
|
818
|
-
? '远端检查失败。当前仍使用已验证的缓存数据。'
|
|
819
|
-
: '远端检查失败,并且没有可用缓存数据。'
|
|
820
|
-
: state === 'degraded'
|
|
821
|
-
? '远端检查后仍只能使用缓存数据。'
|
|
822
|
-
: '来源检查后没有可用数据。',
|
|
823
|
-
} : {}),
|
|
824
|
-
}, refreshSequence);
|
|
825
|
-
if (!checkError && refreshedMatchesCheck && checked) {
|
|
826
|
-
this.#recordSuccessfulSourceRefresh(sourceId, checked, generation);
|
|
827
|
-
}
|
|
828
|
-
finishRefresh();
|
|
829
|
-
if (generation !== this.#generation)
|
|
830
|
-
return;
|
|
831
|
-
this.#project();
|
|
832
|
-
this.#publish();
|
|
833
738
|
}
|
|
834
739
|
push(route, context) {
|
|
835
740
|
this.#cancelReviewPreparation();
|
|
836
741
|
this.#cancelPortableRequest();
|
|
742
|
+
this.#cancelInstallPlacementRequest();
|
|
837
743
|
this.#discardPortableDraftForRoute(route);
|
|
838
744
|
this.#router.push(route, context);
|
|
839
745
|
this.#publish();
|
|
@@ -1004,6 +910,7 @@ export class TuiController {
|
|
|
1004
910
|
replace(route, context) {
|
|
1005
911
|
this.#cancelReviewPreparation();
|
|
1006
912
|
this.#cancelPortableRequest();
|
|
913
|
+
this.#cancelInstallPlacementRequest();
|
|
1007
914
|
const current = this.#router.snapshot.current.route;
|
|
1008
915
|
if (current.name === 'portableBlocker' && current.code === 'requiredConfigMissing'
|
|
1009
916
|
&& route.name === 'configLayer' && route.pluginId === current.pluginId) {
|
|
@@ -1031,10 +938,9 @@ export class TuiController {
|
|
|
1031
938
|
}
|
|
1032
939
|
back() {
|
|
1033
940
|
const route = this.#router.snapshot.current.route;
|
|
1034
|
-
|
|
1035
|
-
this.#cancelReviewPreparation();
|
|
1036
|
-
}
|
|
941
|
+
this.#cancelReviewPreparation();
|
|
1037
942
|
this.#cancelPortableRequest();
|
|
943
|
+
this.#cancelInstallPlacementRequest();
|
|
1038
944
|
if (route.name === 'review' && this.#activeReview?.operationId === route.operationId) {
|
|
1039
945
|
this.#tickets.delete(this.#activeReview.reviewToken);
|
|
1040
946
|
this.#activeReview = undefined;
|
|
@@ -1148,6 +1054,213 @@ export class TuiController {
|
|
|
1148
1054
|
throw new DOMException('Portable request was cancelled.', 'AbortError');
|
|
1149
1055
|
}
|
|
1150
1056
|
}
|
|
1057
|
+
beginInstallFlow(pluginId, rootScope, options = {}) {
|
|
1058
|
+
this.#cancelInstallPlacementRequest();
|
|
1059
|
+
this.#installPlacementDraft = {
|
|
1060
|
+
pluginId,
|
|
1061
|
+
rootScope,
|
|
1062
|
+
platforms: [],
|
|
1063
|
+
dependencyScopeOverrides: [],
|
|
1064
|
+
state: 'selecting',
|
|
1065
|
+
targets: [],
|
|
1066
|
+
canContinue: false,
|
|
1067
|
+
adjustingExisting: options.adjustingExisting === true,
|
|
1068
|
+
revalidateOverrides: false,
|
|
1069
|
+
};
|
|
1070
|
+
this.#publish();
|
|
1071
|
+
}
|
|
1072
|
+
setInstallRootScope(rootScope) {
|
|
1073
|
+
const draft = this.#installPlacementDraft;
|
|
1074
|
+
if (!draft)
|
|
1075
|
+
return;
|
|
1076
|
+
this.#cancelInstallPlacementRequest();
|
|
1077
|
+
draft.rootScope = rootScope;
|
|
1078
|
+
draft.platforms = [];
|
|
1079
|
+
draft.revalidateOverrides = true;
|
|
1080
|
+
draft.plan = undefined;
|
|
1081
|
+
draft.targets = [];
|
|
1082
|
+
draft.state = 'selecting';
|
|
1083
|
+
draft.canContinue = false;
|
|
1084
|
+
draft.error = undefined;
|
|
1085
|
+
this.#publish();
|
|
1086
|
+
}
|
|
1087
|
+
setInstallPlatforms(platforms) {
|
|
1088
|
+
const draft = this.#installPlacementDraft;
|
|
1089
|
+
if (!draft)
|
|
1090
|
+
return;
|
|
1091
|
+
this.#cancelInstallPlacementRequest();
|
|
1092
|
+
const selected = [...new Set(platforms)].sort();
|
|
1093
|
+
draft.platforms = selected;
|
|
1094
|
+
draft.dependencyScopeOverrides = draft.dependencyScopeOverrides.filter((override) => selected.includes(override.platform));
|
|
1095
|
+
draft.revalidateOverrides = true;
|
|
1096
|
+
draft.plan = undefined;
|
|
1097
|
+
draft.targets = [];
|
|
1098
|
+
draft.state = 'selecting';
|
|
1099
|
+
draft.canContinue = selected.length > 0;
|
|
1100
|
+
draft.error = undefined;
|
|
1101
|
+
this.#publish();
|
|
1102
|
+
}
|
|
1103
|
+
#cancelInstallPlacementRequest() {
|
|
1104
|
+
const cancelled = this.#installPlacementRequestAbort !== undefined;
|
|
1105
|
+
this.#installPlacementRequestToken += 1;
|
|
1106
|
+
this.#installPlacementRequestAbort?.abort();
|
|
1107
|
+
this.#installPlacementRequestAbort = undefined;
|
|
1108
|
+
if (cancelled && this.#installPlacementDraft?.state === 'loading') {
|
|
1109
|
+
this.#installPlacementDraft.plan = undefined;
|
|
1110
|
+
this.#installPlacementDraft.targets = [];
|
|
1111
|
+
this.#installPlacementDraft.state = 'selecting';
|
|
1112
|
+
this.#installPlacementDraft.canContinue = false;
|
|
1113
|
+
this.#installPlacementDraft.error = undefined;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
#installPlacementIntent() {
|
|
1117
|
+
const draft = this.#installPlacementDraft;
|
|
1118
|
+
if (!draft || draft.platforms.length === 0) {
|
|
1119
|
+
throw new TuiOperationBlockedError('installPlacementIncomplete', '请先明确选择目标平台。');
|
|
1120
|
+
}
|
|
1121
|
+
return {
|
|
1122
|
+
kind: 'pluginWrite',
|
|
1123
|
+
action: 'install',
|
|
1124
|
+
pluginId: draft.pluginId,
|
|
1125
|
+
platform: draft.platforms.length === 2 ? 'both' : draft.platforms[0],
|
|
1126
|
+
scope: draft.rootScope,
|
|
1127
|
+
...(draft.dependencyScopeOverrides.length > 0
|
|
1128
|
+
? { dependencyScopeOverrides: draft.dependencyScopeOverrides }
|
|
1129
|
+
: {}),
|
|
1130
|
+
...(draft.adjustingExisting ? { dependencyPlacementAdjustment: true } : {}),
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
async prepareInstallDependencyPlacement() {
|
|
1134
|
+
const draft = this.#installPlacementDraft;
|
|
1135
|
+
if (!draft)
|
|
1136
|
+
throw new TuiOperationBlockedError('installPlacementMissing', '安装位置草稿不存在。');
|
|
1137
|
+
this.#cancelInstallPlacementRequest();
|
|
1138
|
+
const abort = new AbortController();
|
|
1139
|
+
this.#installPlacementRequestAbort = abort;
|
|
1140
|
+
const token = this.#installPlacementRequestToken;
|
|
1141
|
+
draft.state = 'loading';
|
|
1142
|
+
draft.canContinue = false;
|
|
1143
|
+
draft.error = undefined;
|
|
1144
|
+
this.#publish();
|
|
1145
|
+
try {
|
|
1146
|
+
const signal = AbortSignal.any([this.#abort.signal, abort.signal]);
|
|
1147
|
+
const preview = async (intent) => this.dependencies.previewDependencyPlacement
|
|
1148
|
+
? this.dependencies.previewDependencyPlacement(structuredClone(intent), signal)
|
|
1149
|
+
: (await this.dependencies.captureEvidence(structuredClone(intent), signal)).dependencyPlan;
|
|
1150
|
+
const throwIfCancelled = () => {
|
|
1151
|
+
if (this.#disposed || token !== this.#installPlacementRequestToken || abort.signal.aborted) {
|
|
1152
|
+
throw new DOMException('Dependency placement preview was cancelled.', 'AbortError');
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
const filterOverrides = (plan) => {
|
|
1156
|
+
const before = draft.dependencyScopeOverrides;
|
|
1157
|
+
const filtered = before.filter((override) => {
|
|
1158
|
+
const target = plan?.targets.find((candidate) => candidate.relation === 'dependency'
|
|
1159
|
+
&& candidate.pluginId === override.pluginId
|
|
1160
|
+
&& candidate.platform === override.platform);
|
|
1161
|
+
return Boolean(target
|
|
1162
|
+
&& target.allowedScopes.includes(override.scope)
|
|
1163
|
+
&& !(target.minimumScope === 'user' && override.scope === 'project'));
|
|
1164
|
+
});
|
|
1165
|
+
if (filtered.length === before.length)
|
|
1166
|
+
return false;
|
|
1167
|
+
draft.dependencyScopeOverrides = filtered;
|
|
1168
|
+
return true;
|
|
1169
|
+
};
|
|
1170
|
+
let plan;
|
|
1171
|
+
if (draft.revalidateOverrides && draft.dependencyScopeOverrides.length > 0) {
|
|
1172
|
+
const baselineIntent = this.#installPlacementIntent();
|
|
1173
|
+
delete baselineIntent.dependencyScopeOverrides;
|
|
1174
|
+
const baselinePlan = await preview(baselineIntent);
|
|
1175
|
+
throwIfCancelled();
|
|
1176
|
+
filterOverrides(baselinePlan);
|
|
1177
|
+
plan = draft.dependencyScopeOverrides.length > 0
|
|
1178
|
+
? await preview(this.#installPlacementIntent())
|
|
1179
|
+
: baselinePlan;
|
|
1180
|
+
throwIfCancelled();
|
|
1181
|
+
if (filterOverrides(plan)) {
|
|
1182
|
+
plan = await preview(this.#installPlacementIntent());
|
|
1183
|
+
throwIfCancelled();
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
else {
|
|
1187
|
+
plan = await preview(this.#installPlacementIntent());
|
|
1188
|
+
}
|
|
1189
|
+
throwIfCancelled();
|
|
1190
|
+
draft.revalidateOverrides = false;
|
|
1191
|
+
if (!plan) {
|
|
1192
|
+
draft.state = 'ready';
|
|
1193
|
+
draft.plan = undefined;
|
|
1194
|
+
draft.targets = [];
|
|
1195
|
+
draft.canContinue = true;
|
|
1196
|
+
this.#publish();
|
|
1197
|
+
return false;
|
|
1198
|
+
}
|
|
1199
|
+
draft.plan = structuredClone(plan);
|
|
1200
|
+
draft.targets = dependencyPlacementTargets(plan);
|
|
1201
|
+
const blocker = plan.targets.find((target) => target.action === 'block');
|
|
1202
|
+
draft.state = blocker ? 'blocked' : 'ready';
|
|
1203
|
+
draft.canContinue = !blocker;
|
|
1204
|
+
draft.error = blocker
|
|
1205
|
+
? {
|
|
1206
|
+
code: blocker.errorCode ?? 'dependencyPlacementBlocked',
|
|
1207
|
+
message: '依赖位置不能满足所选根插件和目标平台。',
|
|
1208
|
+
}
|
|
1209
|
+
: undefined;
|
|
1210
|
+
this.#publish();
|
|
1211
|
+
return draft.targets.some((target) => !target.locked);
|
|
1212
|
+
}
|
|
1213
|
+
catch (error) {
|
|
1214
|
+
if (error instanceof Error && error.name === 'AbortError')
|
|
1215
|
+
throw error;
|
|
1216
|
+
const code = issueCode(error);
|
|
1217
|
+
draft.state = 'blocked';
|
|
1218
|
+
draft.canContinue = false;
|
|
1219
|
+
draft.error = { code, message: '无法计算可信的依赖位置。请返回检查选择。' };
|
|
1220
|
+
this.#publish();
|
|
1221
|
+
throw error;
|
|
1222
|
+
}
|
|
1223
|
+
finally {
|
|
1224
|
+
if (token === this.#installPlacementRequestToken) {
|
|
1225
|
+
this.#installPlacementRequestAbort = undefined;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
async setDependencyScope(pluginId, platform, scope) {
|
|
1230
|
+
const draft = this.#installPlacementDraft;
|
|
1231
|
+
const target = draft?.targets.find((entry) => entry.pluginId === pluginId && entry.platform === platform);
|
|
1232
|
+
if (!draft || !target || target.locked || !target.allowedScopes.includes(scope)
|
|
1233
|
+
|| target.minimumScope === 'user' && scope === 'project') {
|
|
1234
|
+
throw new TuiOperationBlockedError('dependencyScopeOverrideInvalid', '这个依赖不支持所选位置。');
|
|
1235
|
+
}
|
|
1236
|
+
draft.dependencyScopeOverrides = [
|
|
1237
|
+
...draft.dependencyScopeOverrides.filter((override) => override.pluginId !== pluginId || override.platform !== platform),
|
|
1238
|
+
{ pluginId, platform, scope },
|
|
1239
|
+
].sort((left, right) => `${left.pluginId}\0${left.platform}`
|
|
1240
|
+
.localeCompare(`${right.pluginId}\0${right.platform}`, 'en'));
|
|
1241
|
+
draft.revalidateOverrides = false;
|
|
1242
|
+
await this.prepareInstallDependencyPlacement();
|
|
1243
|
+
}
|
|
1244
|
+
async resetDependencyScopes() {
|
|
1245
|
+
const draft = this.#installPlacementDraft;
|
|
1246
|
+
if (!draft)
|
|
1247
|
+
return;
|
|
1248
|
+
draft.dependencyScopeOverrides = [];
|
|
1249
|
+
draft.revalidateOverrides = false;
|
|
1250
|
+
await this.prepareInstallDependencyPlacement();
|
|
1251
|
+
}
|
|
1252
|
+
installPlacementIntent() {
|
|
1253
|
+
const intent = this.#installPlacementIntent();
|
|
1254
|
+
const narrowsUserTarget = this.#installPlacementDraft?.plan?.targets.some((target) => {
|
|
1255
|
+
if (target.relation !== 'dependency' || target.resolvedScope !== 'project')
|
|
1256
|
+
return false;
|
|
1257
|
+
const persistedScopes = new Set(this.#status?.payload.targets.flatMap((candidate) => candidate.pluginId === target.pluginId && candidate.platform === target.platform
|
|
1258
|
+
? candidate.desired?.scopes ?? []
|
|
1259
|
+
: []) ?? []);
|
|
1260
|
+
return persistedScopes.has('user') && !persistedScopes.has('project');
|
|
1261
|
+
});
|
|
1262
|
+
return narrowsUserTarget ? { ...intent, dependencyPlacementOnly: 'desired' } : intent;
|
|
1263
|
+
}
|
|
1151
1264
|
listPlugins(options) {
|
|
1152
1265
|
const query = options.query?.trim().toLocaleLowerCase('en');
|
|
1153
1266
|
return cloneFrozen((this.#viewModel?.plugins ?? []).filter((plugin) => (!options.sourceId || plugin.sourceId === options.sourceId)
|
|
@@ -1192,62 +1305,47 @@ export class TuiController {
|
|
|
1192
1305
|
}));
|
|
1193
1306
|
}
|
|
1194
1307
|
return plugin.supportedPlatforms.map((platform) => {
|
|
1195
|
-
const
|
|
1308
|
+
const platformTargets = this.#status?.payload.targets.filter((target) => target.pluginId === pluginId && target.platform === platform) ?? [];
|
|
1309
|
+
const rootSummary = this.#status?.pluginSummaries.find((summary) => summary.pluginId === pluginId);
|
|
1310
|
+
const rootSupportedScopes = rootSummary?.supportedTargets?.[platform]
|
|
1311
|
+
?? platformTargets.filter((target) => !target.errors.some((error) => error.code === 'unsupportedTarget')).map((target) => target.scope);
|
|
1312
|
+
if (!rootSupportedScopes.includes(scope)) {
|
|
1313
|
+
return { platform, compatible: false, disabled: '不支持所选位置' };
|
|
1314
|
+
}
|
|
1315
|
+
const statusTarget = platformTargets.find((target) => target.scope === scope)
|
|
1316
|
+
?? (platform === 'codex' ? platformTargets[0] : undefined);
|
|
1196
1317
|
if (statusTarget && targetHasArtifactCollision(statusTarget)) {
|
|
1197
1318
|
return { platform, compatible: false, disabled: '存在输出冲突;请先移除或迁移占用内容' };
|
|
1198
1319
|
}
|
|
1199
|
-
const placement = plugin.installedPlacements.find((entry) => entry.platform === platform);
|
|
1200
|
-
if (!placement)
|
|
1201
|
-
return { platform, compatible: false, disabled: '当前没有可信的平台能力信息' };
|
|
1202
|
-
if (placement.state === 'scopeConflict') {
|
|
1203
|
-
return { platform, compatible: false, disabled: '安装位置冲突;请先处理问题' };
|
|
1204
|
-
}
|
|
1205
|
-
if (placement.state === 'installed') {
|
|
1206
|
-
return {
|
|
1207
|
-
platform,
|
|
1208
|
-
compatible: false,
|
|
1209
|
-
disabled: `已安装到${placement.scope === 'user' ? '所有项目' : '当前项目'};请先移除现有安装`,
|
|
1210
|
-
};
|
|
1211
|
-
}
|
|
1212
|
-
if (!placement.supportedScopes.includes(scope)) {
|
|
1213
|
-
return { platform, compatible: false, disabled: '不支持所选位置' };
|
|
1214
|
-
}
|
|
1215
1320
|
const overview = this.#viewModel?.dependencyOverviews.find((entry) => entry.pluginId === pluginId);
|
|
1216
1321
|
for (const dependency of overview?.dependencies ?? []) {
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1322
|
+
const dependencySummary = this.#status?.pluginSummaries.find((summary) => summary.pluginId === dependency.pluginId);
|
|
1323
|
+
const allowedScopes = dependencySummary?.supportedTargets?.[platform]
|
|
1324
|
+
?? (this.#status?.payload.targets.filter((target) => target.pluginId === dependency.pluginId
|
|
1325
|
+
&& target.platform === platform
|
|
1326
|
+
&& !target.errors.some((error) => error.code === 'unsupportedTarget'))
|
|
1327
|
+
.map((target) => target.scope) ?? []);
|
|
1328
|
+
const viableScopes = allowedScopes.filter((candidateScope) => scope === 'project' || candidateScope === 'user');
|
|
1329
|
+
if (viableScopes.length === 0) {
|
|
1221
1330
|
return {
|
|
1222
1331
|
platform,
|
|
1223
1332
|
compatible: false,
|
|
1224
|
-
disabled: `${dependency.displayName}
|
|
1333
|
+
disabled: `${dependency.displayName} 不支持${platform === 'codex' ? 'Codex' : 'Claude Code'}或所选位置`,
|
|
1225
1334
|
};
|
|
1226
1335
|
}
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1336
|
+
const collisionAtEveryViableScope = viableScopes.every((candidateScope) => {
|
|
1337
|
+
const target = this.#status?.payload.targets.find((entry) => entry.pluginId === dependency.pluginId
|
|
1338
|
+
&& entry.platform === platform
|
|
1339
|
+
&& entry.scope === candidateScope);
|
|
1340
|
+
return Boolean(target && targetHasArtifactCollision(target));
|
|
1341
|
+
});
|
|
1342
|
+
if (collisionAtEveryViableScope) {
|
|
1229
1343
|
return {
|
|
1230
1344
|
platform,
|
|
1231
1345
|
compatible: false,
|
|
1232
|
-
disabled: `${dependency.displayName}
|
|
1346
|
+
disabled: `${dependency.displayName} 存在输出冲突`,
|
|
1233
1347
|
};
|
|
1234
1348
|
}
|
|
1235
|
-
const dependencyPlacement = dependentPlugin.installedPlacements.find((entry) => entry.platform === platform);
|
|
1236
|
-
if (dependencyPlacement?.state === 'scopeConflict') {
|
|
1237
|
-
return { platform, compatible: false, disabled: `${dependency.displayName} 的安装位置冲突` };
|
|
1238
|
-
}
|
|
1239
|
-
if (dependencyPlacement?.state === 'installed') {
|
|
1240
|
-
if (dependencyPlacement.health !== 'ready') {
|
|
1241
|
-
return { platform, compatible: false, disabled: `${dependency.displayName} 需要先处理` };
|
|
1242
|
-
}
|
|
1243
|
-
if (scope === 'user' && dependencyPlacement.scope === 'project') {
|
|
1244
|
-
return { platform, compatible: false, disabled: `${dependency.displayName} 只在当前项目可用` };
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
if (dependencyPlacement?.state === 'notInstalled'
|
|
1248
|
-
&& !dependencyPlacement.supportedScopes.includes(scope)) {
|
|
1249
|
-
return { platform, compatible: false, disabled: `${dependency.displayName} 不支持所选位置` };
|
|
1250
|
-
}
|
|
1251
1349
|
}
|
|
1252
1350
|
return { platform, compatible: true };
|
|
1253
1351
|
});
|
|
@@ -1267,11 +1365,6 @@ export class TuiController {
|
|
|
1267
1365
|
health: this.#health.get(sourceId),
|
|
1268
1366
|
});
|
|
1269
1367
|
}
|
|
1270
|
-
listCatalogCandidates() {
|
|
1271
|
-
return cloneFrozen([...this.#catalogGenerations.values()]
|
|
1272
|
-
.filter((source) => source.candidate)
|
|
1273
|
-
.sort((left, right) => left.sourceId.localeCompare(right.sourceId, 'en')));
|
|
1274
|
-
}
|
|
1275
1368
|
getCatalogGeneration(sourceId) {
|
|
1276
1369
|
const source = this.#catalogGenerations.get(sourceId);
|
|
1277
1370
|
return source ? cloneFrozen(source) : undefined;
|
|
@@ -1279,76 +1372,32 @@ export class TuiController {
|
|
|
1279
1372
|
getSessionCatalogVersion(sourceId) {
|
|
1280
1373
|
return this.#status?.payload.catalog.sources.find((source) => source.id === sourceId)?.catalogVersion;
|
|
1281
1374
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
}
|
|
1296
|
-
else if (options.dirtyDraft) {
|
|
1297
|
-
reason = options.dirtyDraft.kind === 'config' ? 'configDraft' : 'sourceDraft';
|
|
1298
|
-
preservedLabel = options.dirtyDraft.label;
|
|
1299
|
-
}
|
|
1300
|
-
else if (this.#portableDraft) {
|
|
1301
|
-
reason = 'portableDraft';
|
|
1302
|
-
preservedLabel = 'Portable Skills 当前选择';
|
|
1303
|
-
}
|
|
1304
|
-
else if (this.#reviewPreparation)
|
|
1305
|
-
reason = 'reviewPreparation';
|
|
1306
|
-
else if (this.#router.snapshot.current.route.name === 'progress') {
|
|
1307
|
-
reason = this.#activeReview?.operationKind === 'sourceMutation'
|
|
1308
|
-
? 'sourceMutationProgress' : 'writeProgress';
|
|
1309
|
-
}
|
|
1310
|
-
else if (this.#activeReview)
|
|
1311
|
-
reason = 'activeReview';
|
|
1312
|
-
else {
|
|
1313
|
-
const refreshing = [...this.#catalogGenerations.values()].find((candidate) => candidate.refreshState === 'checking'
|
|
1314
|
-
|| candidate.refreshState === 'downloading'
|
|
1315
|
-
|| candidate.refreshState === 'validating');
|
|
1316
|
-
if (refreshing) {
|
|
1317
|
-
reason = 'refreshProgress';
|
|
1318
|
-
preservedLabel = `${refreshing.sourceId} Catalog 来源刷新`;
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
if (reason) {
|
|
1322
|
-
this.#router.push({
|
|
1323
|
-
name: 'catalogActivationBlocked',
|
|
1324
|
-
sourceId: options.sourceId,
|
|
1325
|
-
reason,
|
|
1326
|
-
...(preservedLabel ? { preservedLabel } : {}),
|
|
1327
|
-
});
|
|
1375
|
+
async reloadCatalog(options) {
|
|
1376
|
+
this.dependencies.beginCatalogSession?.();
|
|
1377
|
+
const generation = ++this.#generation;
|
|
1378
|
+
this.#phase = 'loading';
|
|
1379
|
+
this.#publish();
|
|
1380
|
+
try {
|
|
1381
|
+
const status = await this.dependencies.loadStatus(this.#abort.signal);
|
|
1382
|
+
if (this.#disposed || generation !== this.#generation)
|
|
1383
|
+
return false;
|
|
1384
|
+
this.#applyStatus(status);
|
|
1385
|
+
this.#phase = 'ready';
|
|
1386
|
+
this.#loadError = undefined;
|
|
1387
|
+
this.#router.replace({ name: 'sourceDetail', sourceId: options.sourceId });
|
|
1328
1388
|
this.#publish();
|
|
1329
|
-
return
|
|
1389
|
+
return true;
|
|
1330
1390
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
async setCatalogRefreshPolicy(sourceId, refreshPolicy) {
|
|
1341
|
-
if (!this.dependencies.setCatalogRefreshPolicy) {
|
|
1342
|
-
throw new TuiOperationBlockedError('catalogGenerationRefreshPolicyUnavailable', 'Catalog 自动刷新设置不可用。');
|
|
1391
|
+
catch (error) {
|
|
1392
|
+
if (this.#disposed || generation !== this.#generation)
|
|
1393
|
+
return false;
|
|
1394
|
+
const code = issueCode(error);
|
|
1395
|
+
await this.#logErrorSafe(error, code);
|
|
1396
|
+
this.#phase = 'error';
|
|
1397
|
+
this.#loadError = { code, message: '无法安全重新加载 Catalog。请检查诊断后重试。' };
|
|
1398
|
+
this.#publish();
|
|
1399
|
+
return false;
|
|
1343
1400
|
}
|
|
1344
|
-
const source = await this.dependencies.setCatalogRefreshPolicy(sourceId, refreshPolicy);
|
|
1345
|
-
if (this.#disposed)
|
|
1346
|
-
return;
|
|
1347
|
-
const current = this.#catalogGenerations.get(sourceId);
|
|
1348
|
-
this.#catalogGenerations.set(sourceId, current
|
|
1349
|
-
? { ...current, refreshPolicy: source.refreshPolicy }
|
|
1350
|
-
: structuredClone(source));
|
|
1351
|
-
this.#publish();
|
|
1352
1401
|
}
|
|
1353
1402
|
async inspectSourceRemoval(sourceId) {
|
|
1354
1403
|
if (!this.dependencies.inspectSourceRemoval) {
|
|
@@ -1467,8 +1516,7 @@ export class TuiController {
|
|
|
1467
1516
|
return Boolean(preparation && preparation.token === token
|
|
1468
1517
|
&& !preparation.abort.signal.aborted
|
|
1469
1518
|
&& !this.#disposed
|
|
1470
|
-
&&
|
|
1471
|
-
|| route.name === 'catalogActivationBlocked' && route.reason === 'reviewPreparation'));
|
|
1519
|
+
&& route.name === 'reviewPreparing' && route.preparationId === preparation.snapshot.id);
|
|
1472
1520
|
}
|
|
1473
1521
|
async #runReviewPreparation(token) {
|
|
1474
1522
|
const preparation = this.#reviewPreparation;
|
|
@@ -1563,6 +1611,7 @@ export class TuiController {
|
|
|
1563
1611
|
this.#pruneReviewTickets(now);
|
|
1564
1612
|
const expiresAt = now
|
|
1565
1613
|
+ (this.dependencies.reviewTtlMs ?? 2 * 60_000);
|
|
1614
|
+
const requiresConfirmation = (evidence.codexReconcilePlans ?? []).some((plan) => plan.review.requiresConfirmation);
|
|
1566
1615
|
const pluginId = 'pluginId' in intent ? intent.pluginId : intent.sourceId;
|
|
1567
1616
|
if ('pluginId' in intent)
|
|
1568
1617
|
sourceIdFromPluginId(intent.pluginId);
|
|
@@ -1600,13 +1649,20 @@ export class TuiController {
|
|
|
1600
1649
|
: {}),
|
|
1601
1650
|
changes: reviewChanges(intent, evidence, 'pluginId' in intent ? this.getPluginDependencyOverview(intent.pluginId) : undefined),
|
|
1602
1651
|
technicalDetails: technicalDetails(evidence),
|
|
1603
|
-
...(intent.kind === 'pluginWrite'
|
|
1604
|
-
? { equivalentCommand: `aw ${intent.action} ${intent.pluginId} --platform ${intent.platform} --scope ${intent.scope}${intent.action === 'cleanup' && intent.force === true ? ' --force' : ''}${evidence.codexManager?.state === 'requires-install' ? ' --allow-managed-codex-install' : ''}` }
|
|
1652
|
+
...(intent.kind === 'pluginWrite' && intent.dependencyPlacementOnly !== 'desired'
|
|
1653
|
+
? { equivalentCommand: `aw ${intent.action} ${intent.pluginId} --platform ${intent.platform} --scope ${intent.scope}${(intent.dependencyScopeOverrides ?? []).map((override) => ` --dependency-scope ${override.pluginId}#${override.platform}=${override.scope}`).join('')}${intent.action === 'cleanup' && intent.force === true ? ' --force' : ''}${evidence.codexManager?.state === 'requires-install' ? ' --allow-managed-codex-install' : ''}${requiresConfirmation ? ' --yes' : ''}` }
|
|
1605
1654
|
: {}),
|
|
1606
1655
|
primaryLabel: intent.kind === 'pluginWrite' && evidence.dependencyPlan
|
|
1607
1656
|
? `${labels.primaryLabel} ${evidence.dependencyPlan.resolvedPluginIds.length} 个插件`
|
|
1608
1657
|
: labels.primaryLabel,
|
|
1609
1658
|
...(labels.destructive ? { destructive: true } : {}),
|
|
1659
|
+
requiresConfirmation,
|
|
1660
|
+
sharedImpact: {
|
|
1661
|
+
pluginIds: [...new Set((evidence.codexReconcilePlans ?? []).flatMap((plan) => plan.review.sharedImpact.pluginIds))],
|
|
1662
|
+
...((evidence.codexReconcilePlans ?? []).find((plan) => plan.review.sharedImpact.warning)?.review.sharedImpact.warning
|
|
1663
|
+
? { warning: (evidence.codexReconcilePlans ?? []).find((plan) => plan.review.sharedImpact.warning).review.sharedImpact.warning }
|
|
1664
|
+
: {}),
|
|
1665
|
+
},
|
|
1610
1666
|
};
|
|
1611
1667
|
this.#tickets.set(reviewToken, {
|
|
1612
1668
|
intent: structuredClone(intent),
|
|
@@ -1683,10 +1739,6 @@ export class TuiController {
|
|
|
1683
1739
|
await this.#logOperationError(error, code, ticket.intent);
|
|
1684
1740
|
if (!this.#operationIsCurrent(generation))
|
|
1685
1741
|
return this.#detachedBlocked(operationId, ticket.intent);
|
|
1686
|
-
if (code === 'operationReviewStale' && ticket.intent.kind === 'catalogActivate') {
|
|
1687
|
-
return this.#rebuildStaleReview(ticket, operationId, undefined, generation);
|
|
1688
|
-
}
|
|
1689
|
-
this.#cancelSourceRefreshes();
|
|
1690
1742
|
generation = ++this.#generation;
|
|
1691
1743
|
try {
|
|
1692
1744
|
const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
|
|
@@ -1719,9 +1771,6 @@ export class TuiController {
|
|
|
1719
1771
|
await this.#logOperationError(error, code, ticket.intent);
|
|
1720
1772
|
if (!this.#operationIsCurrent(generation))
|
|
1721
1773
|
return this.#detachedBlocked(operationId, ticket.intent);
|
|
1722
|
-
if (code === 'operationReviewStale' && ticket.intent.kind === 'catalogActivate') {
|
|
1723
|
-
return this.#rebuildStaleReview(ticket, operationId, undefined, generation);
|
|
1724
|
-
}
|
|
1725
1774
|
return this.#setBlocked(operationId, code, { intent: ticket.intent, returnRoute: ticket.returnRoute });
|
|
1726
1775
|
}
|
|
1727
1776
|
if (!this.#operationIsCurrent(generation))
|
|
@@ -1731,7 +1780,6 @@ export class TuiController {
|
|
|
1731
1780
|
}
|
|
1732
1781
|
}
|
|
1733
1782
|
if (ticket.intent.kind === 'sourceMutation') {
|
|
1734
|
-
this.#cancelSourceRefreshes();
|
|
1735
1783
|
generation = ++this.#generation;
|
|
1736
1784
|
}
|
|
1737
1785
|
try {
|
|
@@ -1744,16 +1792,13 @@ export class TuiController {
|
|
|
1744
1792
|
});
|
|
1745
1793
|
if (!this.#operationIsCurrent(generation))
|
|
1746
1794
|
return this.#detachedBlocked(operationId, ticket.intent);
|
|
1747
|
-
this.#cancelSourceRefreshes();
|
|
1748
1795
|
generation = ++this.#generation;
|
|
1749
|
-
if (ticket.intent.kind === 'sourceMutation'
|
|
1750
|
-
this.dependencies.beginCatalogSession?.(
|
|
1751
|
-
|
|
1752
|
-
|
|
1796
|
+
if (ticket.intent.kind === 'sourceMutation') {
|
|
1797
|
+
this.dependencies.beginCatalogSession?.({
|
|
1798
|
+
reuseValidatedGenerations: true,
|
|
1799
|
+
});
|
|
1753
1800
|
try {
|
|
1754
|
-
this.#progressFeed?.setCurrent({ target:
|
|
1755
|
-
? 'Catalog 激活:重新加载当前会话'
|
|
1756
|
-
: '来源操作:读取最终状态');
|
|
1801
|
+
this.#progressFeed?.setCurrent({ target: 'Catalog 来源配置', phase: '读取最终状态' }, '来源操作:读取最终状态');
|
|
1757
1802
|
}
|
|
1758
1803
|
catch {
|
|
1759
1804
|
// Progress diagnostics must not replace the completed source write.
|
|
@@ -1900,8 +1945,6 @@ export class TuiController {
|
|
|
1900
1945
|
this.#activeReview = undefined;
|
|
1901
1946
|
this.#router.replace({ name: 'result', operationId });
|
|
1902
1947
|
this.#publish();
|
|
1903
|
-
if (ticket.intent.kind !== 'catalogActivate')
|
|
1904
|
-
this.#startGitSourceRefreshes();
|
|
1905
1948
|
return cloneFrozen(result);
|
|
1906
1949
|
}
|
|
1907
1950
|
catch (error) {
|
|
@@ -1914,7 +1957,6 @@ export class TuiController {
|
|
|
1914
1957
|
if (code === 'operationReviewStale') {
|
|
1915
1958
|
return this.#rebuildStaleReview(ticket, operationId, undefined, generation);
|
|
1916
1959
|
}
|
|
1917
|
-
this.#cancelSourceRefreshes();
|
|
1918
1960
|
generation = ++this.#generation;
|
|
1919
1961
|
try {
|
|
1920
1962
|
const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
|
|
@@ -1952,8 +1994,7 @@ export class TuiController {
|
|
|
1952
1994
|
});
|
|
1953
1995
|
}
|
|
1954
1996
|
async #rebuildStaleReview(ticket, operationId, currentEvidence, generation = this.#generation) {
|
|
1955
|
-
this
|
|
1956
|
-
this.dependencies.beginCatalogSession?.({ bootstrapMissingActive: false });
|
|
1997
|
+
this.dependencies.beginCatalogSession?.();
|
|
1957
1998
|
generation = ++this.#generation;
|
|
1958
1999
|
try {
|
|
1959
2000
|
const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
|
|
@@ -1971,14 +2012,7 @@ export class TuiController {
|
|
|
1971
2012
|
returnRoute: ticket.returnRoute,
|
|
1972
2013
|
});
|
|
1973
2014
|
}
|
|
1974
|
-
const intent = ticket.intent
|
|
1975
|
-
? (() => {
|
|
1976
|
-
const candidate = this.#catalogGenerations.get(ticket.intent.sourceId)?.candidate;
|
|
1977
|
-
return candidate
|
|
1978
|
-
? { ...ticket.intent, candidateGenerationKey: candidate.generationKey }
|
|
1979
|
-
: ticket.intent;
|
|
1980
|
-
})()
|
|
1981
|
-
: ticket.intent;
|
|
2015
|
+
const intent = ticket.intent;
|
|
1982
2016
|
try {
|
|
1983
2017
|
this.#assertIssueOrigin(intent);
|
|
1984
2018
|
await this.dependencies.validateIntent?.(structuredClone(intent), this.#abort.signal);
|
|
@@ -2153,9 +2187,8 @@ export class TuiController {
|
|
|
2153
2187
|
this.#disposed = true;
|
|
2154
2188
|
this.#cancelReviewPreparation();
|
|
2155
2189
|
this.#cancelPortableRequest();
|
|
2190
|
+
this.#cancelInstallPlacementRequest();
|
|
2156
2191
|
this.#abort.abort();
|
|
2157
|
-
this.#catalogRefreshCoordinator?.dispose();
|
|
2158
|
-
this.#cancelSourceRefreshes();
|
|
2159
2192
|
this.#progressUnsubscribe?.();
|
|
2160
2193
|
this.#tickets.clear();
|
|
2161
2194
|
this.#phase = 'disposed';
|
|
@@ -2163,11 +2196,16 @@ export class TuiController {
|
|
|
2163
2196
|
}
|
|
2164
2197
|
}
|
|
2165
2198
|
function operationResultSummary(intent, execution) {
|
|
2166
|
-
if (intent.kind === '
|
|
2199
|
+
if (intent.kind === 'pluginWrite' && intent.dependencyPlacementOnly === 'desired') {
|
|
2167
2200
|
return {
|
|
2168
2201
|
code: 'operationSucceeded',
|
|
2169
|
-
message: '
|
|
2170
|
-
|
|
2202
|
+
message: '依赖 desired placement 已更新。',
|
|
2203
|
+
pluginId: intent.pluginId,
|
|
2204
|
+
action: intent.action,
|
|
2205
|
+
messages: [
|
|
2206
|
+
'第 1 步:AW 不会扫描未知项目或自动 cleanup;请在已知的所有项目位置显式确认 cleanup。',
|
|
2207
|
+
'第 2 步:cleanup 完成后同步当前项目。',
|
|
2208
|
+
],
|
|
2171
2209
|
};
|
|
2172
2210
|
}
|
|
2173
2211
|
if (intent.kind === 'portableInstall') {
|
|
@@ -2273,7 +2311,16 @@ function operationResultSummary(intent, execution) {
|
|
|
2273
2311
|
action: intent.action,
|
|
2274
2312
|
} : {}),
|
|
2275
2313
|
...(inferredTargets.length > 0 ? { targets: inferredTargets } : {}),
|
|
2276
|
-
...(sourceWarnings.length > 0
|
|
2314
|
+
...(sourceWarnings.length > 0 || intent.kind === 'pluginWrite' && intent.recoveryDescription
|
|
2315
|
+
? { messages: [
|
|
2316
|
+
...sourceWarnings,
|
|
2317
|
+
...(intent.kind === 'pluginWrite' && intent.recoveryDescription
|
|
2318
|
+
? [
|
|
2319
|
+
intent.recoveryDescription,
|
|
2320
|
+
`后续:${intent.scope === 'user' ? '当前项目' : '所有项目'} sync`,
|
|
2321
|
+
] : []),
|
|
2322
|
+
] }
|
|
2323
|
+
: {}),
|
|
2277
2324
|
};
|
|
2278
2325
|
}
|
|
2279
2326
|
//# sourceMappingURL=controller.js.map
|