@lesliechan721/aw 0.1.0-beta.64 → 0.1.0-beta.65
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/assets/aw-runtime-config.mjs +3 -3
- package/dist/authoring/assets/aw-runtime-config.mjs.map +1 -1
- package/dist/authoring/scripts/add-skill.mjs +3 -17
- package/dist/authoring/scripts/add-skill.mjs.map +1 -1
- package/dist/authoring/scripts/init-plugin.mjs +1 -0
- 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 +2 -0
- package/dist/authoring/scripts/lib/skill-scaffold.mjs +2 -0
- package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
- package/dist/authoring/scripts/validate-plugin.mjs +95 -698
- package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
- package/dist/scripts/aw-skill-eval-materializer.d.ts +2 -0
- package/dist/scripts/aw-skill-eval-materializer.js +74 -44
- package/dist/scripts/aw-skill-eval-materializer.js.map +1 -1
- package/dist/scripts/bundle-plugins.js +10 -2
- package/dist/scripts/bundle-plugins.js.map +1 -1
- package/dist/scripts/plugin-staging.d.ts +1 -1
- package/dist/scripts/plugin-staging.js +1 -1
- package/dist/scripts/plugin-staging.js.map +1 -1
- package/dist/scripts/test-plugin-scripts.js +31 -31
- package/dist/scripts/test-plugin-scripts.js.map +1 -1
- package/dist/src/commands/aw-capabilities.d.ts +1 -1
- package/dist/src/commands/aw-capabilities.js +2 -0
- package/dist/src/commands/aw-capabilities.js.map +1 -1
- package/dist/src/commands/console.js +4 -3
- package/dist/src/commands/console.js.map +1 -1
- package/dist/src/commands/intent-command.d.ts +30 -0
- package/dist/src/commands/intent-command.js +164 -0
- package/dist/src/commands/intent-command.js.map +1 -0
- package/dist/src/commands/operation-plan.d.ts +2 -0
- package/dist/src/commands/operation-plan.js +2 -1
- package/dist/src/commands/operation-plan.js.map +1 -1
- package/dist/src/commands/options.d.ts +3 -11
- package/dist/src/commands/options.js +11 -17
- package/dist/src/commands/options.js.map +1 -1
- package/dist/src/commands/plugin-capabilities.js +27 -33
- package/dist/src/commands/plugin-capabilities.js.map +1 -1
- package/dist/src/commands/rebuild-command.d.ts +108 -6
- package/dist/src/commands/rebuild-command.js +617 -1549
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +19 -3
- package/dist/src/commands/rebuild-integration.js +500 -459
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/rebuild-operations.js +1 -2
- package/dist/src/commands/rebuild-operations.js.map +1 -1
- package/dist/src/commands/review-inspection.d.ts +2 -2
- package/dist/src/commands/review-inspection.js +30 -86
- package/dist/src/commands/review-inspection.js.map +1 -1
- package/dist/src/commands/review-preparation.js +16 -7
- package/dist/src/commands/review-preparation.js.map +1 -1
- package/dist/src/commands/skill-eval.d.ts +2 -0
- package/dist/src/commands/skill-eval.js +15 -9
- package/dist/src/commands/skill-eval.js.map +1 -1
- package/dist/src/commands/status-v6.d.ts +38 -5
- package/dist/src/commands/status-v6.js +269 -102
- package/dist/src/commands/status-v6.js.map +1 -1
- package/dist/src/commands/targets-command.d.ts +27 -0
- package/dist/src/commands/targets-command.js +77 -0
- package/dist/src/commands/targets-command.js.map +1 -0
- package/dist/src/commands/write-commands.js +5 -5
- package/dist/src/commands/write-commands.js.map +1 -1
- package/dist/src/eval/manifest-v1.d.ts +2 -0
- package/dist/src/eval/manifest-v1.js +7 -2
- package/dist/src/eval/manifest-v1.js.map +1 -1
- package/dist/src/generators/local-plugin.d.ts +2 -1
- package/dist/src/generators/local-plugin.js +111 -108
- package/dist/src/generators/local-plugin.js.map +1 -1
- package/dist/src/index.d.ts +10 -17
- package/dist/src/index.js +585 -1349
- package/dist/src/index.js.map +1 -1
- package/dist/src/intent/deployment-preferences.d.ts +63 -0
- package/dist/src/intent/deployment-preferences.js +170 -0
- package/dist/src/intent/deployment-preferences.js.map +1 -0
- package/dist/src/intent/diagnostics.d.ts +11 -0
- package/dist/src/intent/diagnostics.js +14 -0
- package/dist/src/intent/diagnostics.js.map +1 -0
- package/dist/src/intent/intent-codec.d.ts +19 -0
- package/dist/src/intent/intent-codec.js +117 -0
- package/dist/src/intent/intent-codec.js.map +1 -0
- package/dist/src/intent/intent-store.d.ts +43 -0
- package/dist/src/intent/intent-store.js +118 -0
- package/dist/src/intent/intent-store.js.map +1 -0
- package/dist/src/intent/scope-context.d.ts +33 -0
- package/dist/src/intent/scope-context.js +145 -0
- package/dist/src/intent/scope-context.js.map +1 -0
- package/dist/src/intent/work-context.d.ts +13 -0
- package/dist/src/intent/work-context.js +22 -0
- package/dist/src/intent/work-context.js.map +1 -0
- package/dist/src/output/command-receipt.js +16 -9
- package/dist/src/output/command-receipt.js.map +1 -1
- package/dist/src/portable/commands.d.ts +18 -8
- package/dist/src/portable/commands.js +46 -562
- package/dist/src/portable/commands.js.map +1 -1
- package/dist/src/portable/historical-materialization.d.ts +22 -0
- package/dist/src/portable/historical-materialization.js +124 -0
- package/dist/src/portable/historical-materialization.js.map +1 -0
- package/dist/src/portable/materializer.d.ts +8 -0
- package/dist/src/portable/materializer.js +75 -44
- package/dist/src/portable/materializer.js.map +1 -1
- package/dist/src/portable/recovery-layout.d.ts +17 -0
- package/dist/src/portable/recovery-layout.js +55 -0
- package/dist/src/portable/recovery-layout.js.map +1 -0
- package/dist/src/portable/recovery.d.ts +143 -0
- package/dist/src/portable/recovery.js +339 -0
- package/dist/src/portable/recovery.js.map +1 -0
- package/dist/src/portable/writer.d.ts +64 -0
- package/dist/src/portable/writer.js +316 -0
- package/dist/src/portable/writer.js.map +1 -0
- package/dist/src/recovery/coordinator.d.ts +137 -0
- package/dist/src/recovery/coordinator.js +1198 -0
- package/dist/src/recovery/coordinator.js.map +1 -0
- package/dist/src/recovery/demand.d.ts +44 -0
- package/dist/src/recovery/demand.js +178 -0
- package/dist/src/recovery/demand.js.map +1 -0
- package/dist/src/recovery/joint-coordinator.d.ts +24 -0
- package/dist/src/recovery/joint-coordinator.js +770 -0
- package/dist/src/recovery/joint-coordinator.js.map +1 -0
- package/dist/src/recovery/joint-demand.d.ts +68 -0
- package/dist/src/recovery/joint-demand.js +166 -0
- package/dist/src/recovery/joint-demand.js.map +1 -0
- package/dist/src/recovery/receipt.d.ts +81 -0
- package/dist/src/recovery/receipt.js +2 -0
- package/dist/src/recovery/receipt.js.map +1 -0
- package/dist/src/recovery/retirement.d.ts +45 -0
- package/dist/src/recovery/retirement.js +366 -0
- package/dist/src/recovery/retirement.js.map +1 -0
- package/dist/src/recovery/status.d.ts +108 -0
- package/dist/src/recovery/status.js +417 -0
- package/dist/src/recovery/status.js.map +1 -0
- package/dist/src/recovery/types.d.ts +86 -0
- package/dist/src/recovery/types.js +2 -0
- package/dist/src/recovery/types.js.map +1 -0
- package/dist/src/review/review-profile.d.ts +2 -1
- package/dist/src/review/review-profile.js +15 -15
- package/dist/src/review/review-profile.js.map +1 -1
- package/dist/src/review/review-selection.d.ts +1 -2
- package/dist/src/review/review-selection.js +40 -114
- package/dist/src/review/review-selection.js.map +1 -1
- package/dist/src/review/review-types.d.ts +4 -4
- package/dist/src/review/review-types.js.map +1 -1
- package/dist/src/review/skill-materialization.js +15 -6
- package/dist/src/review/skill-materialization.js.map +1 -1
- package/dist/src/review/source-binding.d.ts +14 -4
- package/dist/src/review/source-binding.js +62 -117
- package/dist/src/review/source-binding.js.map +1 -1
- package/dist/src/tui/agent-management.d.ts +23 -0
- package/dist/src/tui/agent-management.js +217 -0
- package/dist/src/tui/agent-management.js.map +1 -0
- package/dist/src/tui/agent-settings.d.ts +44 -0
- package/dist/src/tui/agent-settings.js +98 -0
- package/dist/src/tui/agent-settings.js.map +1 -0
- package/dist/src/tui/components.d.ts +3 -0
- package/dist/src/tui/components.js +10 -1
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/config-references.d.ts +6 -3
- package/dist/src/tui/config-references.js +17 -12
- package/dist/src/tui/config-references.js.map +1 -1
- package/dist/src/tui/console-app-v4.d.ts +1 -2
- package/dist/src/tui/console-app-v4.js +1533 -1047
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.d.ts +2 -1
- package/dist/src/tui/controller-runtime.js +405 -1626
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +131 -81
- package/dist/src/tui/controller.js +307 -227
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/entity-cards.d.ts +2 -2
- package/dist/src/tui/entity-cards.js +98 -20
- package/dist/src/tui/entity-cards.js.map +1 -1
- package/dist/src/tui/glossary.d.ts +2 -0
- package/dist/src/tui/glossary.js +20 -0
- package/dist/src/tui/glossary.js.map +1 -1
- package/dist/src/tui/model.d.ts +28 -10
- package/dist/src/tui/model.js +41 -41
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-review-planner.d.ts +2 -0
- package/dist/src/tui/operation-review-planner.js +58 -122
- package/dist/src/tui/operation-review-planner.js.map +1 -1
- package/dist/src/tui/plugin-content.js +7 -4
- package/dist/src/tui/plugin-content.js.map +1 -1
- package/dist/src/tui/plugin-detail.d.ts +22 -3
- package/dist/src/tui/plugin-detail.js +113 -26
- package/dist/src/tui/plugin-detail.js.map +1 -1
- package/dist/src/tui/reader.d.ts +1 -0
- package/dist/src/tui/reader.js +3 -1
- package/dist/src/tui/reader.js.map +1 -1
- package/dist/src/tui/router.d.ts +23 -33
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/scoped-flow.d.ts +45 -0
- package/dist/src/tui/scoped-flow.js +96 -0
- package/dist/src/tui/scoped-flow.js.map +1 -0
- package/dist/src/tui/scoped-inventory.d.ts +122 -0
- package/dist/src/tui/scoped-inventory.js +225 -0
- package/dist/src/tui/scoped-inventory.js.map +1 -0
- package/dist/src/tui/screen-registry.d.ts +25 -25
- package/dist/src/tui/screen-registry.js +3 -7
- package/dist/src/tui/screen-registry.js.map +1 -1
- package/dist/src/tui/session-console-app.js +1 -1
- package/dist/src/tui/session-console-app.js.map +1 -1
- package/dist/src/tui/session-host.js +12 -2
- package/dist/src/tui/session-host.js.map +1 -1
- package/dist/src/types.d.ts +15 -12
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/agent-version.js +2 -0
- package/dist/src/utils/agent-version.js.map +1 -1
- package/dist/src/utils/artifact-plan.js +13 -6
- package/dist/src/utils/artifact-plan.js.map +1 -1
- package/dist/src/utils/artifact-renderer.d.ts +6 -3
- package/dist/src/utils/artifact-renderer.js +27 -32
- package/dist/src/utils/artifact-renderer.js.map +1 -1
- package/dist/src/utils/artifact-request.d.ts +1 -0
- package/dist/src/utils/artifact-request.js +22 -12
- package/dist/src/utils/artifact-request.js.map +1 -1
- package/dist/src/utils/artifact-sources.d.ts +2 -2
- package/dist/src/utils/bundle-layout.js +18 -16
- package/dist/src/utils/bundle-layout.js.map +1 -1
- package/dist/src/utils/catalog-digests.js +2 -0
- package/dist/src/utils/catalog-digests.js.map +1 -1
- package/dist/src/utils/catalog-runtime-projection.d.ts +4 -3
- package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
- package/dist/src/utils/catalog-source-references.d.ts +5 -0
- package/dist/src/utils/catalog-source-references.js +42 -57
- package/dist/src/utils/catalog-source-references.js.map +1 -1
- package/dist/src/utils/claude-native-render-receipt.d.ts +14 -0
- package/dist/src/utils/claude-native-render-receipt.js +26 -0
- package/dist/src/utils/claude-native-render-receipt.js.map +1 -0
- package/dist/src/utils/claude-skills-root.js +14 -3
- package/dist/src/utils/claude-skills-root.js.map +1 -1
- package/dist/src/utils/codex-manager.js +6 -12
- package/dist/src/utils/codex-manager.js.map +1 -1
- package/dist/src/utils/codex-native-observation.js +29 -13
- package/dist/src/utils/codex-native-observation.js.map +1 -1
- package/dist/src/utils/codex-reconciler.d.ts +2 -5
- package/dist/src/utils/codex-reconciler.js +25 -1
- package/dist/src/utils/codex-reconciler.js.map +1 -1
- package/dist/src/utils/codex-surface-analysis.js +5 -3
- package/dist/src/utils/codex-surface-analysis.js.map +1 -1
- package/dist/src/utils/local-marketplace.d.ts +14 -2
- package/dist/src/utils/local-marketplace.js +209 -42
- package/dist/src/utils/local-marketplace.js.map +1 -1
- package/dist/src/utils/managed-command-evidence.js +1 -1
- package/dist/src/utils/managed-command-evidence.js.map +1 -1
- package/dist/src/utils/managed-command-materialization.d.ts +4 -0
- package/dist/src/utils/managed-command-materialization.js +15 -6
- package/dist/src/utils/managed-command-materialization.js.map +1 -1
- package/dist/src/utils/managed-command-store.d.ts +15 -0
- package/dist/src/utils/managed-command-store.js +73 -8
- package/dist/src/utils/managed-command-store.js.map +1 -1
- package/dist/src/utils/native-cli.d.ts +2 -0
- package/dist/src/utils/native-cli.js +22 -0
- package/dist/src/utils/native-cli.js.map +1 -1
- package/dist/src/utils/native-demand.d.ts +10 -0
- package/dist/src/utils/native-demand.js +4 -0
- package/dist/src/utils/native-demand.js.map +1 -0
- package/dist/src/utils/ownership.js +2 -0
- package/dist/src/utils/ownership.js.map +1 -1
- package/dist/src/utils/paths.js +1 -1
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/src/utils/platform-support.d.ts +1 -1
- package/dist/src/utils/platform-support.js +1 -1
- package/dist/src/utils/platform-support.js.map +1 -1
- package/dist/src/utils/plugin-config-impact.d.ts +3 -0
- package/dist/src/utils/plugin-config-impact.js +115 -273
- package/dist/src/utils/plugin-config-impact.js.map +1 -1
- package/dist/src/utils/plugin-config-references.d.ts +9 -2
- package/dist/src/utils/plugin-config-references.js +75 -85
- package/dist/src/utils/plugin-config-references.js.map +1 -1
- package/dist/src/utils/plugin-config-write.d.ts +58 -1
- package/dist/src/utils/plugin-config-write.js +756 -809
- package/dist/src/utils/plugin-config-write.js.map +1 -1
- package/dist/src/utils/plugin-config.d.ts +47 -46
- package/dist/src/utils/plugin-config.js +292 -833
- package/dist/src/utils/plugin-config.js.map +1 -1
- package/dist/src/utils/plugin-scripts.d.ts +11 -6
- package/dist/src/utils/plugin-scripts.js +52 -35
- package/dist/src/utils/plugin-scripts.js.map +1 -1
- package/dist/src/utils/runtime-config.js +5 -4
- package/dist/src/utils/runtime-config.js.map +1 -1
- package/dist/src/utils/runtime-skill-closure.d.ts +2 -2
- package/dist/src/utils/runtime-skill-closure.js.map +1 -1
- package/dist/src/utils/state-paths.d.ts +8 -3
- package/dist/src/utils/state-paths.js +53 -16
- package/dist/src/utils/state-paths.js.map +1 -1
- package/dist/src/utils/windows-npm-shim.d.ts +2 -0
- package/dist/src/utils/windows-npm-shim.js +5 -7
- package/dist/src/utils/windows-npm-shim.js.map +1 -1
- package/dist/vitest.config.js +1 -0
- package/dist/vitest.config.js.map +1 -1
- package/package.json +3 -3
- package/dist/src/commands/sync-command.d.ts +0 -40
- package/dist/src/commands/sync-command.js +0 -254
- package/dist/src/commands/sync-command.js.map +0 -1
- package/dist/src/portable/skills-cli.d.ts +0 -51
- package/dist/src/portable/skills-cli.js +0 -202
- package/dist/src/portable/skills-cli.js.map +0 -1
- package/dist/src/tui/project-inventory.d.ts +0 -68
- package/dist/src/tui/project-inventory.js +0 -203
- package/dist/src/tui/project-inventory.js.map +0 -1
- package/dist/src/utils/plugin-intent-state.d.ts +0 -49
- package/dist/src/utils/plugin-intent-state.js +0 -320
- package/dist/src/utils/plugin-intent-state.js.map +0 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { isPlatformTarget } from '@lesliechan721/aw-plugin-core/manifest';
|
|
1
2
|
import crypto from 'node:crypto';
|
|
2
3
|
import { CatalogGenerationError } from '../utils/catalog-generation-error.js';
|
|
3
4
|
import { readSafeOperationError } from '../utils/safe-operation-diagnostic.js';
|
|
4
|
-
import {
|
|
5
|
+
import { NativeCliError } from '../utils/native-cli.js';
|
|
6
|
+
import { flattenStatusTargets, statusTargetActivation, statusTargetIntent, statusTargetOwnershipState, statusTargetSharedNative, } from '../commands/status-v6.js';
|
|
5
7
|
import { buildRebuildReceiptJson, requestedIntentAction } from '../output/command-receipt.js';
|
|
6
8
|
import { buildOperationResultModel } from '../commands/operation-planner.js';
|
|
7
9
|
import { buildOperationReviewProjection } from './operation-review-planner.js';
|
|
@@ -11,7 +13,8 @@ import { safeDiagnosticCode, sanitizeDiagnosticText } from './diagnostics.js';
|
|
|
11
13
|
import { projectExecutionDiagnostics, projectThrownOperationDiagnostic } from './diagnostic-projection.js';
|
|
12
14
|
import { OperationHistoryStore, } from './operation-history.js';
|
|
13
15
|
import { targetHasArtifactCollision } from '../utils/target-eligibility.js';
|
|
14
|
-
import {
|
|
16
|
+
import { hasStatusInstallationEvidence } from './scoped-inventory.js';
|
|
17
|
+
import { buildScopedInventory } from './scoped-inventory.js';
|
|
15
18
|
import { TuiRouter, } from './router.js';
|
|
16
19
|
import { normalizeTuiRoute } from './screen-registry.js';
|
|
17
20
|
import { CatalogSourcesSession } from './catalog-sources/session.js';
|
|
@@ -91,89 +94,6 @@ function reviewPreparationStageLabel(stage) {
|
|
|
91
94
|
evidence: '准备确认信息',
|
|
92
95
|
}[stage];
|
|
93
96
|
}
|
|
94
|
-
function projectReminderTargetKey(target) {
|
|
95
|
-
return [target.pluginId, target.platform, target.scope].join('\0');
|
|
96
|
-
}
|
|
97
|
-
export function projectCurrentIntentReminder(status) {
|
|
98
|
-
if (status.projectIntent && status.projectIntent.state !== 'ready')
|
|
99
|
-
return undefined;
|
|
100
|
-
const actionableTargets = [];
|
|
101
|
-
const blockedTargets = [];
|
|
102
|
-
const unconfirmedTargets = [];
|
|
103
|
-
const projectRoots = new Map((status.projectIntent?.state === 'ready'
|
|
104
|
-
? status.projectIntent.roots : status.projectIntentRoots ?? []).map((root) => [
|
|
105
|
-
root.pluginId,
|
|
106
|
-
new Set(root.platforms),
|
|
107
|
-
]));
|
|
108
|
-
const projectTargets = flattenStatusTargets(status.payload.targets).filter((target) => {
|
|
109
|
-
const intent = statusTargetIntent(target);
|
|
110
|
-
return target.scope === 'project'
|
|
111
|
-
&& projectRoots.get(target.pluginId)?.has(target.platform) === true
|
|
112
|
-
&& intent.scopes.includes('project');
|
|
113
|
-
});
|
|
114
|
-
for (const target of projectTargets) {
|
|
115
|
-
if (target.state === 'ready')
|
|
116
|
-
continue;
|
|
117
|
-
const reminderTarget = {
|
|
118
|
-
pluginId: target.pluginId,
|
|
119
|
-
platform: target.platform,
|
|
120
|
-
scope: 'project',
|
|
121
|
-
};
|
|
122
|
-
const statusReasons = [
|
|
123
|
-
...target.reasons,
|
|
124
|
-
...(target.sharedReasons ?? []),
|
|
125
|
-
];
|
|
126
|
-
const reasonCodes = [...new Set(statusReasons.flatMap((reason) => [
|
|
127
|
-
reason.code,
|
|
128
|
-
...(reason.rootIssueCode ? [reason.rootIssueCode] : []),
|
|
129
|
-
]))].sort((left, right) => left.localeCompare(right, 'en'));
|
|
130
|
-
const sharedNative = statusTargetSharedNative(target);
|
|
131
|
-
const activation = statusTargetActivation(target);
|
|
132
|
-
const extensions = statusTargetExtensions(target);
|
|
133
|
-
const ownership = statusTargetOwnershipState(target);
|
|
134
|
-
const blocking = target.state === 'blocked'
|
|
135
|
-
|| [...target.reasons, ...(target.sharedReasons ?? [])]
|
|
136
|
-
.some((reason) => reason.severity === 'blocking');
|
|
137
|
-
const unobserved = ownership === 'error'
|
|
138
|
-
|| (!blocking && (sharedNative?.state === 'unobserved'
|
|
139
|
-
|| activation?.evidence === 'unobserved'
|
|
140
|
-
|| reasonCodes.includes('codexSharedNativeUnobserved')
|
|
141
|
-
|| target.components.some((component) => component.metadata?.rootIssueCode === 'codexSharedNativeUnobserved')));
|
|
142
|
-
const confirmedDifference = ownership === 'absent' || ownership === 'partial'
|
|
143
|
-
|| sharedNative?.state === 'missing' || sharedNative?.state === 'replaceable'
|
|
144
|
-
|| extensions?.state === 'missing' || extensions?.state === 'drifted'
|
|
145
|
-
|| activation?.evidence === 'observed' && activation.project === false
|
|
146
|
-
|| target.components.some((component) => component.kind === 'dependency'
|
|
147
|
-
&& (component.state === 'absent' || component.state === 'incomplete'));
|
|
148
|
-
if (blocking && confirmedDifference) {
|
|
149
|
-
blockedTargets.push({ ...reminderTarget, reasonCodes });
|
|
150
|
-
}
|
|
151
|
-
else if (!unobserved && target.state === 'needsSync' && confirmedDifference) {
|
|
152
|
-
actionableTargets.push({ ...reminderTarget, reasonCodes });
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
unconfirmedTargets.push({ ...reminderTarget, reasonCodes });
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
if (projectTargets.length === 0)
|
|
159
|
-
return undefined;
|
|
160
|
-
const canonicalActionable = actionableTargets.sort((left, right) => projectReminderTargetKey(left).localeCompare(projectReminderTargetKey(right), 'en'));
|
|
161
|
-
const canonicalBlocked = blockedTargets.sort((left, right) => projectReminderTargetKey(left).localeCompare(projectReminderTargetKey(right), 'en'));
|
|
162
|
-
const canonicalUnconfirmed = unconfirmedTargets.sort((left, right) => projectReminderTargetKey(left).localeCompare(projectReminderTargetKey(right), 'en'));
|
|
163
|
-
const projectRoot = status.projectRoot ?? projectTargets[0].cwd;
|
|
164
|
-
const signature = crypto.createHash('sha256').update(JSON.stringify({
|
|
165
|
-
projectRoot,
|
|
166
|
-
actionableTargets: canonicalActionable.map(projectReminderTargetKey),
|
|
167
|
-
blockedTargets: canonicalBlocked.map(projectReminderTargetKey),
|
|
168
|
-
})).digest('hex');
|
|
169
|
-
return {
|
|
170
|
-
projectRoot,
|
|
171
|
-
signature: `sha256:${signature}`,
|
|
172
|
-
actionableTargets: canonicalActionable,
|
|
173
|
-
blockedTargets: canonicalBlocked,
|
|
174
|
-
unconfirmedTargets: canonicalUnconfirmed,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
97
|
export class TuiOperationBlockedError extends Error {
|
|
178
98
|
code;
|
|
179
99
|
diagnostics;
|
|
@@ -220,16 +140,17 @@ function canonicalPluginWriteTaskTargets(targets) {
|
|
|
220
140
|
.localeCompare(pluginWriteTaskTargetKey(right), 'en'));
|
|
221
141
|
}
|
|
222
142
|
function normalizeOperationIntent(intent) {
|
|
223
|
-
if (intent.kind === 'configSave'
|
|
224
|
-
return { ...intent,
|
|
143
|
+
if (intent.kind === 'configSave' || intent.kind === 'configReset' || intent.kind === 'configSaveBatch') {
|
|
144
|
+
return { ...intent, context: intent.context ?? 'user',
|
|
145
|
+
...(intent.kind === 'configSave' ? { scope: 'user',
|
|
146
|
+
apply: intent.retryApply ? true : intent.apply } : {}) };
|
|
225
147
|
}
|
|
226
148
|
if (intent.kind === 'pluginWriteTask') {
|
|
227
149
|
return { ...intent, targets: canonicalPluginWriteTaskTargets(intent.targets) };
|
|
228
150
|
}
|
|
229
151
|
if (intent.kind !== 'pluginWriteBatch')
|
|
230
152
|
return intent;
|
|
231
|
-
if (intent.selection.kind === 'targets'
|
|
232
|
-
|| intent.selection.kind === 'currentProjectIntentTargets') {
|
|
153
|
+
if (intent.selection.kind === 'targets') {
|
|
233
154
|
return {
|
|
234
155
|
...intent,
|
|
235
156
|
selection: {
|
|
@@ -713,6 +634,7 @@ export class TuiController {
|
|
|
713
634
|
#reviewPreparationIntents = new Map();
|
|
714
635
|
#inflightOperations = new Map();
|
|
715
636
|
#pendingReads = new Set();
|
|
637
|
+
#readCleanupError;
|
|
716
638
|
#pluginContent = new Map();
|
|
717
639
|
#pluginContentInflight = new Map();
|
|
718
640
|
#pluginContentEpoch = 0;
|
|
@@ -740,10 +662,14 @@ export class TuiController {
|
|
|
740
662
|
#portableResultLifecycle = 0;
|
|
741
663
|
#portableResultRecheck;
|
|
742
664
|
#completionNotice;
|
|
743
|
-
#
|
|
744
|
-
#
|
|
745
|
-
#
|
|
746
|
-
#
|
|
665
|
+
#scopedInventories = {};
|
|
666
|
+
#lastRefreshedScope;
|
|
667
|
+
#scopedInventoryRefresh = {};
|
|
668
|
+
#scopedRefreshToken = { user: 0, project: 0 };
|
|
669
|
+
#scopedPreparationAbort;
|
|
670
|
+
#scopedExecuted = new WeakSet();
|
|
671
|
+
#scopedTaskInflight;
|
|
672
|
+
#configSaveInflight;
|
|
747
673
|
#loadError;
|
|
748
674
|
#portableDraft;
|
|
749
675
|
#recoverablePortableDraft;
|
|
@@ -860,16 +786,9 @@ export class TuiController {
|
|
|
860
786
|
} }
|
|
861
787
|
: {}),
|
|
862
788
|
...(this.#completionNotice ? { completionNotice: this.#completionNotice } : {}),
|
|
863
|
-
...(this.#
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
...(this.#projectInventory ? { projectInventory: this.#projectInventory } : {}),
|
|
867
|
-
projectInventoryRefresh: this.#projectInventoryRefresh,
|
|
868
|
-
...(this.#status?.projectIntent?.state === 'absent' ? { projectDeclarationAbsent: true } : {}),
|
|
869
|
-
...(this.#status?.projectIntent?.state === 'invalid'
|
|
870
|
-
|| this.#status?.projectIntent?.state === 'unavailable'
|
|
871
|
-
? { projectIntentIssue: this.#status.projectIntent }
|
|
872
|
-
: {}),
|
|
789
|
+
...(this.#status?.projectRoot ? { projectRoot: this.#status.projectRoot } : {}),
|
|
790
|
+
scopedInventories: this.#scopedInventories,
|
|
791
|
+
scopedInventoryRefresh: this.#scopedInventoryRefresh,
|
|
873
792
|
});
|
|
874
793
|
}
|
|
875
794
|
subscribe(listener) {
|
|
@@ -878,7 +797,8 @@ export class TuiController {
|
|
|
878
797
|
return () => this.#listeners.delete(listener);
|
|
879
798
|
}
|
|
880
799
|
get hasActiveWrite() {
|
|
881
|
-
return this.#inflightOperations.size > 0
|
|
800
|
+
return this.#inflightOperations.size > 0 || this.#scopedTaskInflight !== undefined
|
|
801
|
+
|| this.#configSaveInflight !== undefined;
|
|
882
802
|
}
|
|
883
803
|
get retiring() {
|
|
884
804
|
return this.#retiring;
|
|
@@ -946,9 +866,16 @@ export class TuiController {
|
|
|
946
866
|
return recordId;
|
|
947
867
|
}
|
|
948
868
|
#trackRead(promise) {
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
869
|
+
const tracked = promise.catch(async (error) => {
|
|
870
|
+
if (error instanceof NativeCliError && error.cleanupError) {
|
|
871
|
+
this.#readCleanupError ??= error;
|
|
872
|
+
await this.#logErrorSafe(error, issueCode(error));
|
|
873
|
+
}
|
|
874
|
+
throw error;
|
|
875
|
+
});
|
|
876
|
+
this.#pendingReads.add(tracked);
|
|
877
|
+
void tracked.finally(() => this.#pendingReads.delete(tracked)).catch(() => undefined);
|
|
878
|
+
return tracked;
|
|
952
879
|
}
|
|
953
880
|
beginRetirement() {
|
|
954
881
|
this.#retiring = true;
|
|
@@ -956,8 +883,12 @@ export class TuiController {
|
|
|
956
883
|
}
|
|
957
884
|
async waitForWrites() {
|
|
958
885
|
this.beginRetirement();
|
|
959
|
-
while (this
|
|
960
|
-
await Promise.allSettled([
|
|
886
|
+
while (this.hasActiveWrite) {
|
|
887
|
+
await Promise.allSettled([
|
|
888
|
+
...[...this.#inflightOperations.values()].map((operation) => operation.promise),
|
|
889
|
+
...(this.#scopedTaskInflight ? [this.#scopedTaskInflight.promise] : []),
|
|
890
|
+
...(this.#configSaveInflight ? [this.#configSaveInflight] : []),
|
|
891
|
+
]);
|
|
961
892
|
}
|
|
962
893
|
}
|
|
963
894
|
async cancelAndWaitForReads() {
|
|
@@ -974,6 +905,8 @@ export class TuiController {
|
|
|
974
905
|
await Promise.allSettled([...this.#pendingReads]);
|
|
975
906
|
}
|
|
976
907
|
await catalogDrain;
|
|
908
|
+
if (this.#readCleanupError)
|
|
909
|
+
throw this.#readCleanupError;
|
|
977
910
|
return !this.hasActiveWrite;
|
|
978
911
|
}
|
|
979
912
|
#assertAcceptingActions() {
|
|
@@ -1090,7 +1023,9 @@ export class TuiController {
|
|
|
1090
1023
|
stage,
|
|
1091
1024
|
fallbackCode: code,
|
|
1092
1025
|
sensitiveValues,
|
|
1093
|
-
...(intent && 'pluginId' in intent && 'platform' in intent
|
|
1026
|
+
...(intent && 'pluginId' in intent && 'platform' in intent
|
|
1027
|
+
&& (intent.platform === 'codex' || intent.platform === 'claude')
|
|
1028
|
+
&& (intent.scope === 'user' || intent.scope === 'project')
|
|
1094
1029
|
? { target: { pluginId: intent.pluginId, platform: intent.platform, scope: intent.scope } }
|
|
1095
1030
|
: {}),
|
|
1096
1031
|
})],
|
|
@@ -1195,7 +1130,6 @@ export class TuiController {
|
|
|
1195
1130
|
#project() {
|
|
1196
1131
|
if (!this.#status)
|
|
1197
1132
|
return;
|
|
1198
|
-
this.#projectIntentReminder = projectCurrentIntentReminder(this.#status);
|
|
1199
1133
|
this.#viewModel = buildTuiViewModel({
|
|
1200
1134
|
status: this.#status.payload,
|
|
1201
1135
|
pluginSummaries: this.#status.pluginSummaries,
|
|
@@ -1212,15 +1146,26 @@ export class TuiController {
|
|
|
1212
1146
|
portablePluginIds: this.#status.portablePluginIds,
|
|
1213
1147
|
configStoreInventory: this.#status.configStoreInventory,
|
|
1214
1148
|
});
|
|
1215
|
-
this.#
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1149
|
+
for (const issue of this.#viewModel.issues) {
|
|
1150
|
+
// Cross-scope recovery needs separate Intent, cleanup, and sync reviews.
|
|
1151
|
+
issue.actions = issue.actions.filter((action) => action.id !== 'keepProjectScope' && action.id !== 'keepUserScope');
|
|
1152
|
+
}
|
|
1153
|
+
for (const scope of ['user', 'project']) {
|
|
1154
|
+
const recovery = this.#status.scopedRecovery?.[scope];
|
|
1155
|
+
if (!recovery) {
|
|
1156
|
+
delete this.#scopedInventories[scope];
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
this.#scopedInventories[scope] = buildScopedInventory({
|
|
1160
|
+
recovery,
|
|
1161
|
+
status: recovery.nativeSnapshot?.payload ?? this.#status.payload,
|
|
1162
|
+
pluginSummaries: recovery.nativeSnapshot?.pluginSummaries ?? this.#viewModel.plugins,
|
|
1163
|
+
targetRevisions: recovery.nativeSnapshot?.targetRevisions ?? this.#status.targetRevisions,
|
|
1164
|
+
dependencyOverviews: recovery.nativeSnapshot?.dependencyOverviews ?? this.#status.dependencyOverviews,
|
|
1165
|
+
...(scope === 'project' && this.#status.projectRoot
|
|
1166
|
+
? { projectRoot: this.#status.projectRoot } : {}),
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1224
1169
|
}
|
|
1225
1170
|
#normalizeRoute(route) {
|
|
1226
1171
|
const state = this.#routeEntityState();
|
|
@@ -1237,8 +1182,7 @@ export class TuiController {
|
|
|
1237
1182
|
#routeEntityState() {
|
|
1238
1183
|
return {
|
|
1239
1184
|
hasPlugin: (pluginId) => this.#viewModel?.plugins.some((plugin) => plugin.pluginId === pluginId)
|
|
1240
|
-
|| this.#
|
|
1241
|
-
|| this.#projectInventory?.extraInstalledRoots.some((root) => root.pluginId === pluginId)
|
|
1185
|
+
|| Object.values(this.#scopedInventories).some((inventory) => inventory.declaredRoots.some((root) => root.pluginId === pluginId) || inventory.extraInstalledRoots.some((root) => root.pluginId === pluginId))
|
|
1242
1186
|
|| false,
|
|
1243
1187
|
hasSource: (sourceId) => this.#viewModel?.sourceTabs.some((source) => source.id === sourceId) ?? false,
|
|
1244
1188
|
hasIssue: (issueId) => this.#viewModel?.issues.some((issue) => issue.issueId === issueId) ?? false,
|
|
@@ -1430,9 +1374,7 @@ export class TuiController {
|
|
|
1430
1374
|
? {
|
|
1431
1375
|
...structuredClone(intent),
|
|
1432
1376
|
selection: {
|
|
1433
|
-
kind:
|
|
1434
|
-
|| intent.selection.kind === 'currentProjectIntentTargets'
|
|
1435
|
-
? 'currentProjectIntentTargets' : 'targets',
|
|
1377
|
+
kind: 'targets',
|
|
1436
1378
|
targets: structuredClone(result.summary.batch.retryTargets),
|
|
1437
1379
|
},
|
|
1438
1380
|
}
|
|
@@ -1459,7 +1401,10 @@ export class TuiController {
|
|
|
1459
1401
|
? result.returnRoute.batchOrigin.flowId
|
|
1460
1402
|
: undefined;
|
|
1461
1403
|
const completedPluginId = completedSinglePluginId(intent, result);
|
|
1462
|
-
|
|
1404
|
+
const hasUnreviewedTaskTargets = intent.kind === 'pluginWriteTask'
|
|
1405
|
+
&& 'returnRoute' in result && result.returnRoute?.name === 'pluginWriteTask'
|
|
1406
|
+
&& result.returnRoute.targetKeys?.some((key) => !intent.targets.some((candidate) => pluginWriteTaskTargetKey(candidate) === key));
|
|
1407
|
+
if (completedPluginId && target.name === 'pluginDetail' && !hasUnreviewedTaskTargets) {
|
|
1463
1408
|
this.#recordTerminalResult();
|
|
1464
1409
|
const action = intent.kind === 'pluginWrite' || intent.kind === 'pluginWriteBatch'
|
|
1465
1410
|
? intent.action : 'sync';
|
|
@@ -1494,6 +1439,7 @@ export class TuiController {
|
|
|
1494
1439
|
this.#router.replace(terminal);
|
|
1495
1440
|
}
|
|
1496
1441
|
#applyStatus(status) {
|
|
1442
|
+
this.#lastRefreshedScope = undefined;
|
|
1497
1443
|
this.#pluginContentEpoch += 1;
|
|
1498
1444
|
this.#pluginContent.clear();
|
|
1499
1445
|
this.#pluginContentInflight.clear();
|
|
@@ -1563,8 +1509,10 @@ export class TuiController {
|
|
|
1563
1509
|
this.#assertAcceptingActions();
|
|
1564
1510
|
this.dependencies.beginCatalogSession?.();
|
|
1565
1511
|
const generation = ++this.#generation;
|
|
1566
|
-
this.#
|
|
1567
|
-
this.#
|
|
1512
|
+
this.#scopedRefreshToken.user += 1;
|
|
1513
|
+
this.#scopedRefreshToken.project += 1;
|
|
1514
|
+
this.#scopedInventoryRefresh = {};
|
|
1515
|
+
this.#scopedInventories = {};
|
|
1568
1516
|
this.#pluginContentEpoch += 1;
|
|
1569
1517
|
this.#pluginContent.clear();
|
|
1570
1518
|
this.#pluginContentInflight.clear();
|
|
@@ -1592,49 +1540,106 @@ export class TuiController {
|
|
|
1592
1540
|
this.#loadError = undefined;
|
|
1593
1541
|
this.#publish();
|
|
1594
1542
|
}
|
|
1595
|
-
|
|
1543
|
+
listScopedInventoryRoots(scope, section = 'declared') {
|
|
1544
|
+
const inventory = this.#scopedInventories[scope];
|
|
1596
1545
|
return cloneFrozen(section === 'declared'
|
|
1597
|
-
?
|
|
1598
|
-
: this.#projectInventory?.extraInstalledRoots ?? []);
|
|
1599
|
-
}
|
|
1600
|
-
getProjectInventoryRoot(pluginId) {
|
|
1601
|
-
const root = [...(this.#projectInventory?.declaredRoots ?? []),
|
|
1602
|
-
...(this.#projectInventory?.extraInstalledRoots ?? [])]
|
|
1603
|
-
.find((entry) => entry.pluginId === pluginId);
|
|
1604
|
-
return root ? cloneFrozen(root) : undefined;
|
|
1546
|
+
? inventory?.declaredRoots ?? [] : inventory?.extraInstalledRoots ?? []);
|
|
1605
1547
|
}
|
|
1606
|
-
async
|
|
1548
|
+
async refreshScopedInventory(scope) {
|
|
1607
1549
|
this.#assertAcceptingActions();
|
|
1608
|
-
if (!this.#status || this.#phase !== 'ready')
|
|
1550
|
+
if (!this.#status || this.#phase !== 'ready' || !this.dependencies.loadScopedStatus)
|
|
1609
1551
|
return false;
|
|
1610
|
-
const token = ++this.#
|
|
1552
|
+
const token = ++this.#scopedRefreshToken[scope];
|
|
1611
1553
|
const generation = this.#generation;
|
|
1612
|
-
this.#
|
|
1554
|
+
this.#scopedInventoryRefresh[scope] = { state: 'checking' };
|
|
1613
1555
|
this.#publish();
|
|
1614
1556
|
try {
|
|
1615
|
-
const
|
|
1616
|
-
if (this.#disposed || generation !== this.#generation
|
|
1617
|
-
|| token !== this.#projectInventoryRefreshToken)
|
|
1557
|
+
const recovery = await this.#trackRead(this.dependencies.loadScopedStatus(scope, this.#abort.signal));
|
|
1558
|
+
if (this.#disposed || generation !== this.#generation || token !== this.#scopedRefreshToken[scope])
|
|
1618
1559
|
return false;
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1560
|
+
const inventory = buildScopedInventory({
|
|
1561
|
+
recovery, status: recovery.nativeSnapshot.payload, pluginSummaries: recovery.nativeSnapshot.pluginSummaries,
|
|
1562
|
+
targetRevisions: recovery.nativeSnapshot.targetRevisions,
|
|
1563
|
+
dependencyOverviews: recovery.nativeSnapshot.dependencyOverviews,
|
|
1564
|
+
...(scope === 'project' && this.#status.projectRoot ? { projectRoot: this.#status.projectRoot } : {}),
|
|
1565
|
+
});
|
|
1566
|
+
this.#status.scopedRecovery = { ...this.#status.scopedRecovery, [scope]: structuredClone(recovery) };
|
|
1567
|
+
this.#scopedInventories[scope] = inventory;
|
|
1568
|
+
this.#lastRefreshedScope = scope;
|
|
1569
|
+
this.#scopedInventoryRefresh[scope] = { state: 'idle' };
|
|
1624
1570
|
this.#publish();
|
|
1625
1571
|
return true;
|
|
1626
1572
|
}
|
|
1627
1573
|
catch (error) {
|
|
1628
|
-
if (this.#disposed || generation !== this.#generation
|
|
1629
|
-
|| token !== this.#projectInventoryRefreshToken)
|
|
1574
|
+
if (this.#disposed || generation !== this.#generation || token !== this.#scopedRefreshToken[scope])
|
|
1630
1575
|
return false;
|
|
1631
1576
|
const code = issueCode(error);
|
|
1632
1577
|
await this.#logErrorSafe(error, code);
|
|
1633
|
-
this.#
|
|
1578
|
+
if (this.#disposed || generation !== this.#generation || token !== this.#scopedRefreshToken[scope])
|
|
1579
|
+
return false;
|
|
1580
|
+
this.#scopedInventoryRefresh[scope] = { state: 'error', errorCode: code };
|
|
1634
1581
|
this.#publish();
|
|
1635
1582
|
return false;
|
|
1636
1583
|
}
|
|
1637
1584
|
}
|
|
1585
|
+
async prepareScopedTask(request) {
|
|
1586
|
+
this.#assertAcceptingActions();
|
|
1587
|
+
if (!this.dependencies.prepareScopedTask)
|
|
1588
|
+
throw new Error('recoveryUnavailable');
|
|
1589
|
+
this.#scopedPreparationAbort?.abort();
|
|
1590
|
+
const abort = new AbortController();
|
|
1591
|
+
this.#scopedPreparationAbort = abort;
|
|
1592
|
+
try {
|
|
1593
|
+
return await this.#trackRead(this.dependencies.prepareScopedTask(request, AbortSignal.any([this.#abort.signal, abort.signal])));
|
|
1594
|
+
}
|
|
1595
|
+
finally {
|
|
1596
|
+
if (this.#scopedPreparationAbort === abort)
|
|
1597
|
+
this.#scopedPreparationAbort = undefined;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
async readAgentSettings() {
|
|
1601
|
+
if (!this.dependencies.readAgentSettings)
|
|
1602
|
+
throw new Error('agentSettingsUnavailable');
|
|
1603
|
+
return this.#trackRead(this.dependencies.readAgentSettings());
|
|
1604
|
+
}
|
|
1605
|
+
async previewAgentSettings(preferences, pluginId) {
|
|
1606
|
+
if (!this.dependencies.previewAgentSettings)
|
|
1607
|
+
throw new Error('agentSettingsUnavailable');
|
|
1608
|
+
return this.#trackRead(this.dependencies.previewAgentSettings(preferences, pluginId));
|
|
1609
|
+
}
|
|
1610
|
+
async saveAgentSettings(request) {
|
|
1611
|
+
this.#assertAcceptingActions();
|
|
1612
|
+
if (this.hasActiveWrite || !this.dependencies.saveAgentSettings)
|
|
1613
|
+
throw new Error('agentSettingsUnavailable');
|
|
1614
|
+
return this.dependencies.saveAgentSettings(request);
|
|
1615
|
+
}
|
|
1616
|
+
cancelScopedPreparation() { this.#scopedPreparationAbort?.abort(); }
|
|
1617
|
+
async discardScopedPlan(plan) {
|
|
1618
|
+
this.#scopedExecuted.add(plan);
|
|
1619
|
+
if (plan.kind === 'recovery' || plan.kind === 'retire')
|
|
1620
|
+
await plan.plan.context.dispose();
|
|
1621
|
+
}
|
|
1622
|
+
executeScopedTask(plan) {
|
|
1623
|
+
if (this.#retiring || this.#disposed)
|
|
1624
|
+
return Promise.reject(new Error('sessionRetiring'));
|
|
1625
|
+
if (!this.dependencies.executeScopedTask)
|
|
1626
|
+
return Promise.reject(new Error('recoveryUnavailable'));
|
|
1627
|
+
if (this.#scopedTaskInflight?.plan === plan)
|
|
1628
|
+
return this.#scopedTaskInflight.promise;
|
|
1629
|
+
if (this.hasActiveWrite || this.#scopedExecuted.has(plan)) {
|
|
1630
|
+
return Promise.reject(new Error('reviewTicketInvalid'));
|
|
1631
|
+
}
|
|
1632
|
+
this.#scopedExecuted.add(plan);
|
|
1633
|
+
this.#progressFeed?.clearCurrent();
|
|
1634
|
+
this.#progress = { messages: [], startedAtMs: this.dependencies.now?.() ?? Date.now() };
|
|
1635
|
+
this.#publish();
|
|
1636
|
+
const promise = this.dependencies.executeScopedTask(plan).finally(() => {
|
|
1637
|
+
if (this.#scopedTaskInflight?.promise === promise)
|
|
1638
|
+
this.#scopedTaskInflight = undefined;
|
|
1639
|
+
});
|
|
1640
|
+
this.#scopedTaskInflight = { plan, promise };
|
|
1641
|
+
return promise;
|
|
1642
|
+
}
|
|
1638
1643
|
push(route, context) {
|
|
1639
1644
|
if (this.#router.snapshot.current.route.name === 'result') {
|
|
1640
1645
|
this.#invalidatePortableResultRecheck();
|
|
@@ -1974,12 +1979,12 @@ export class TuiController {
|
|
|
1974
1979
|
this.#router.replace(nextRoute, context);
|
|
1975
1980
|
this.#publish();
|
|
1976
1981
|
}
|
|
1977
|
-
beginConfigFlow(pluginId) {
|
|
1982
|
+
beginConfigFlow(pluginId, options = {}) {
|
|
1978
1983
|
this.#router.beginConfigFlow(pluginId);
|
|
1979
1984
|
this.#router.push({
|
|
1980
1985
|
name: 'configFields',
|
|
1981
1986
|
pluginId,
|
|
1982
|
-
layer: {
|
|
1987
|
+
layer: { profile: options.platform ?? 'common', context: options.context ?? 'user' },
|
|
1983
1988
|
});
|
|
1984
1989
|
this.#publish();
|
|
1985
1990
|
}
|
|
@@ -1998,8 +2003,9 @@ export class TuiController {
|
|
|
1998
2003
|
this.#tickets.delete(this.#activeReview.reviewToken);
|
|
1999
2004
|
this.#activeReview = undefined;
|
|
2000
2005
|
this.#router.beginFlow('configEdit', route, this.#router.snapshot.currentFrameId, current.context);
|
|
2001
|
-
|
|
2002
|
-
this.#router.push({ name: '
|
|
2006
|
+
const layer = ('layer' in route ? route.layer : undefined) ?? { profile: 'common', context: 'user' };
|
|
2007
|
+
this.#router.push({ name: 'configFields', pluginId, layer });
|
|
2008
|
+
this.#router.push({ name: 'configFieldEditor', pluginId, layer, fieldKey, editor: 'array', mode: 'choices' });
|
|
2003
2009
|
this.#publish();
|
|
2004
2010
|
}
|
|
2005
2011
|
beginSavedConfigRecovery(pluginId) {
|
|
@@ -2013,9 +2019,7 @@ export class TuiController {
|
|
|
2013
2019
|
name: 'configUnavailable',
|
|
2014
2020
|
pluginId,
|
|
2015
2021
|
storedScopes: plugin.savedConfigScopes,
|
|
2016
|
-
|
|
2017
|
-
? { layer: { scope: plugin.savedConfigScopes[0] } }
|
|
2018
|
-
: {}),
|
|
2022
|
+
layer: { profile: 'common', context: 'user' },
|
|
2019
2023
|
diagnosticCode: 'catalogSourceUnavailable',
|
|
2020
2024
|
diagnosticStage: 'read-config',
|
|
2021
2025
|
});
|
|
@@ -2032,11 +2036,11 @@ export class TuiController {
|
|
|
2032
2036
|
|| !this.#viewModel?.sourceTabs.some((source) => source.id === sourceId)) {
|
|
2033
2037
|
throw new TuiOperationBlockedError('configSourceRecoveryUnavailable', '来源恢复只能从不可用的设置流程开始。');
|
|
2034
2038
|
}
|
|
2035
|
-
const layer = route.layer
|
|
2039
|
+
const layer = route.layer ?? route.resumeRoute?.layer ?? { profile: 'common', context: 'user' };
|
|
2036
2040
|
const returnTarget = route.resumeRoute ?? {
|
|
2037
2041
|
name: 'configFields',
|
|
2038
2042
|
pluginId: route.pluginId,
|
|
2039
|
-
layer
|
|
2043
|
+
layer,
|
|
2040
2044
|
};
|
|
2041
2045
|
this.#router.beginFlow('sourceRecovery', returnTarget, undefined, route.resumeContext);
|
|
2042
2046
|
this.#router.push({ name: 'sourceDetail', sourceId });
|
|
@@ -2048,21 +2052,22 @@ export class TuiController {
|
|
|
2048
2052
|
kind: 'pluginWrite', action: 'cleanup', pluginId, platform, scope, force: true,
|
|
2049
2053
|
};
|
|
2050
2054
|
try {
|
|
2051
|
-
const
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
&& entry.scope === scope);
|
|
2056
|
-
if (!evidence.operationPlan
|
|
2057
|
-
|| !candidate
|
|
2058
|
-
|| candidate.platform !== platform
|
|
2059
|
-
|| candidate.scope !== scope
|
|
2060
|
-
|| (candidate.forceExclusive !== true && candidate.plan.actions.length > 0)
|
|
2061
|
-
|| (candidate.plan.actions.length === 0
|
|
2062
|
-
&& !evidence.operationPlan.intentChanges.some((change) => change.scope === scope))) {
|
|
2055
|
+
const prepared = await this.prepareScopedTask({ kind: 'recovery', command: 'cleanup',
|
|
2056
|
+
scope, selectors: [pluginId], targets: [platform === 'claude' ? 'claude' : 'codex'],
|
|
2057
|
+
exactTargets: [{ pluginId, agent: platform === 'claude' ? 'claude' : 'codex' }], force: true });
|
|
2058
|
+
if (prepared.kind !== 'recovery')
|
|
2063
2059
|
return undefined;
|
|
2060
|
+
try {
|
|
2061
|
+
const candidate = prepared.plan.nativeGroups.flatMap((group) => group.preview.operationPlan ? group.preview.offlineCleanupPlans ?? [] : [])
|
|
2062
|
+
.find((entry) => entry.pluginId === pluginId && entry.platform === platform && entry.scope === scope);
|
|
2063
|
+
if (prepared.plan.blockExecution || !candidate || candidate.forceExclusive !== true
|
|
2064
|
+
|| candidate.plan.actions.length === 0)
|
|
2065
|
+
return undefined;
|
|
2066
|
+
return cloneFrozen({ pluginId, platform, scope, actionCount: candidate.plan.actions.length });
|
|
2067
|
+
}
|
|
2068
|
+
finally {
|
|
2069
|
+
await this.discardScopedPlan(prepared);
|
|
2064
2070
|
}
|
|
2065
|
-
return cloneFrozen({ pluginId, platform, scope, actionCount: candidate.plan.actions.length });
|
|
2066
2071
|
}
|
|
2067
2072
|
catch (error) {
|
|
2068
2073
|
await this.#logOperationError(error, issueCode(error), intent);
|
|
@@ -2070,14 +2075,10 @@ export class TuiController {
|
|
|
2070
2075
|
}
|
|
2071
2076
|
}
|
|
2072
2077
|
startForceCleanupReview(candidate) {
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
pluginId: candidate.pluginId,
|
|
2077
|
-
platform: candidate.platform,
|
|
2078
|
-
scope: candidate.scope,
|
|
2079
|
-
force: true,
|
|
2080
|
-
});
|
|
2078
|
+
const agent = candidate.platform === 'claude' ? 'claude' : 'codex';
|
|
2079
|
+
return this.prepareScopedTask({ kind: 'recovery', command: 'cleanup',
|
|
2080
|
+
scope: candidate.scope, selectors: [candidate.pluginId], targets: [agent],
|
|
2081
|
+
exactTargets: [{ pluginId: candidate.pluginId, agent }], force: true });
|
|
2081
2082
|
}
|
|
2082
2083
|
configOrigin(pluginId) {
|
|
2083
2084
|
const origin = this.#router.configOrigin(pluginId);
|
|
@@ -2426,7 +2427,7 @@ export class TuiController {
|
|
|
2426
2427
|
platform: draft.platforms.length === 2 ? 'both' : draft.platforms[0],
|
|
2427
2428
|
scope: draft.rootScope,
|
|
2428
2429
|
...(draft.dependencyScopeOverrides.length > 0
|
|
2429
|
-
? { dependencyScopeOverrides: structuredClone(draft.dependencyScopeOverrides) } : {}),
|
|
2430
|
+
? { dependencyScopeOverrides: structuredClone(draft.dependencyScopeOverrides), saveDependencyScopeOverrides: true } : {}),
|
|
2430
2431
|
};
|
|
2431
2432
|
return {
|
|
2432
2433
|
kind: 'pluginWrite',
|
|
@@ -2435,7 +2436,7 @@ export class TuiController {
|
|
|
2435
2436
|
platform: draft.platforms.length === 2 ? 'both' : draft.platforms[0],
|
|
2436
2437
|
scope: draft.rootScope,
|
|
2437
2438
|
...(draft.dependencyScopeOverrides.length > 0
|
|
2438
|
-
? { dependencyScopeOverrides: draft.dependencyScopeOverrides }
|
|
2439
|
+
? { dependencyScopeOverrides: draft.dependencyScopeOverrides, saveDependencyScopeOverrides: true }
|
|
2439
2440
|
: {}),
|
|
2440
2441
|
};
|
|
2441
2442
|
}
|
|
@@ -2534,6 +2535,8 @@ export class TuiController {
|
|
|
2534
2535
|
listPlugins(options) {
|
|
2535
2536
|
const query = options.query?.trim().toLocaleLowerCase('en');
|
|
2536
2537
|
const actualScopes = new Map();
|
|
2538
|
+
const portableObservedScopes = new Set();
|
|
2539
|
+
const uncertainPortablePlugins = new Set();
|
|
2537
2540
|
const statusTargets = flattenStatusTargets(this.#status?.payload.targets ?? []);
|
|
2538
2541
|
const targetsByPlugin = new Map();
|
|
2539
2542
|
for (const target of statusTargets) {
|
|
@@ -2554,13 +2557,39 @@ export class TuiController {
|
|
|
2554
2557
|
scopes.add(target.scope);
|
|
2555
2558
|
actualScopes.set(target.pluginId, scopes);
|
|
2556
2559
|
}
|
|
2560
|
+
for (const inventory of Object.values(this.#scopedInventories)) {
|
|
2561
|
+
if (inventory.coverage?.portable) {
|
|
2562
|
+
for (const scope of inventory.coverage.scopes ?? [inventory.scope])
|
|
2563
|
+
portableObservedScopes.add(scope);
|
|
2564
|
+
}
|
|
2565
|
+
for (const root of [...inventory.declaredRoots, ...inventory.extraInstalledRoots]) {
|
|
2566
|
+
for (const plugin of [root, ...root.dependencies]) {
|
|
2567
|
+
for (const target of plugin.targets) {
|
|
2568
|
+
if (target.delivery !== 'agent-skills')
|
|
2569
|
+
continue;
|
|
2570
|
+
if (target.actual !== true) {
|
|
2571
|
+
if (target.observation !== 'known')
|
|
2572
|
+
uncertainPortablePlugins.add(plugin.pluginId);
|
|
2573
|
+
continue;
|
|
2574
|
+
}
|
|
2575
|
+
const scopes = actualScopes.get(plugin.pluginId) ?? new Set();
|
|
2576
|
+
scopes.add(target.scope);
|
|
2577
|
+
actualScopes.set(plugin.pluginId, scopes);
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
const portableAbsenceObserved = portableObservedScopes.has('user')
|
|
2583
|
+
&& (!this.#status?.projectRoot || portableObservedScopes.has('project'));
|
|
2557
2584
|
return cloneFrozen((this.#viewModel?.plugins ?? []).filter((plugin) => (!options.sourceId || plugin.sourceId === options.sourceId)
|
|
2558
2585
|
&& (options.installFilter !== 'installed'
|
|
2559
2586
|
|| (actualScopes.get(plugin.pluginId)?.size ?? 0) > 0)
|
|
2560
2587
|
&& (options.installFilter !== 'uninstalled'
|
|
2561
2588
|
|| plugin.aggregateStatus === 'available'
|
|
2562
|
-
&& (targetsByPlugin.get(plugin.pluginId)?.length ?? 0) > 0
|
|
2563
|
-
|
|
2589
|
+
&& ((targetsByPlugin.get(plugin.pluginId)?.length ?? 0) > 0
|
|
2590
|
+
|| plugin.portableSkills && portableAbsenceObserved)
|
|
2591
|
+
&& (targetsByPlugin.get(plugin.pluginId) ?? []).every((target) => target.state === 'absent')
|
|
2592
|
+
&& !uncertainPortablePlugins.has(plugin.pluginId)
|
|
2564
2593
|
&& (actualScopes.get(plugin.pluginId)?.size ?? 0) === 0)
|
|
2565
2594
|
&& (options.installFilter !== 'project'
|
|
2566
2595
|
|| actualScopes.get(plugin.pluginId)?.has('project') === true)
|
|
@@ -2586,7 +2615,12 @@ export class TuiController {
|
|
|
2586
2615
|
pluginSummaries: this.#status.pluginSummaries,
|
|
2587
2616
|
targetRevisions: this.#status.targetRevisions,
|
|
2588
2617
|
plugins: this.#viewModel.plugins,
|
|
2589
|
-
|
|
2618
|
+
scopedInventories: this.#scopedInventories,
|
|
2619
|
+
scopedSnapshots: {
|
|
2620
|
+
user: this.#status.scopedRecovery?.user?.nativeSnapshot,
|
|
2621
|
+
project: this.#status.scopedRecovery?.project?.nativeSnapshot,
|
|
2622
|
+
},
|
|
2623
|
+
latestScope: this.#lastRefreshedScope,
|
|
2590
2624
|
dependencyOverviews: this.#status.dependencyOverviews,
|
|
2591
2625
|
});
|
|
2592
2626
|
return subject ? cloneFrozen(subject) : undefined;
|
|
@@ -3060,14 +3094,14 @@ export class TuiController {
|
|
|
3060
3094
|
}
|
|
3061
3095
|
return cloneFrozen(await this.#trackRead(this.dependencies.inspectCatalogStore(this.#abort.signal)));
|
|
3062
3096
|
}
|
|
3063
|
-
async getConfigGroups(pluginId,
|
|
3097
|
+
async getConfigGroups(pluginId, options) {
|
|
3064
3098
|
this.#assertAcceptingActions();
|
|
3065
3099
|
if (!this.dependencies.getConfigGroups) {
|
|
3066
3100
|
throw new TuiOperationBlockedError('configUnavailable', '设置读取入口不可用。');
|
|
3067
3101
|
}
|
|
3068
|
-
return cloneFrozen(await this.#trackRead(this.dependencies.getConfigGroups(pluginId, structuredClone(
|
|
3102
|
+
return cloneFrozen(await this.#trackRead(this.dependencies.getConfigGroups(pluginId, structuredClone(options), this.#abort.signal)));
|
|
3069
3103
|
}
|
|
3070
|
-
async getConfigPage(pluginId,
|
|
3104
|
+
async getConfigPage(pluginId, options, signal) {
|
|
3071
3105
|
this.#assertAcceptingActions();
|
|
3072
3106
|
if (!this.dependencies.getConfigPage) {
|
|
3073
3107
|
throw new TuiOperationBlockedError('configUnavailable', '设置读取入口不可用。');
|
|
@@ -3075,14 +3109,61 @@ export class TuiController {
|
|
|
3075
3109
|
const combined = signal
|
|
3076
3110
|
? AbortSignal.any([this.#abort.signal, signal])
|
|
3077
3111
|
: this.#abort.signal;
|
|
3078
|
-
return cloneFrozen(await this.#trackRead(this.dependencies.getConfigPage(pluginId, structuredClone(
|
|
3112
|
+
return cloneFrozen(await this.#trackRead(this.dependencies.getConfigPage(pluginId, structuredClone(options), combined)));
|
|
3079
3113
|
}
|
|
3080
|
-
async getConfigReferenceContext(pluginId,
|
|
3114
|
+
async getConfigReferenceContext(pluginId, options, draft, signal) {
|
|
3081
3115
|
this.#assertAcceptingActions();
|
|
3082
3116
|
if (!this.dependencies.getConfigReferenceContext)
|
|
3083
3117
|
return { selections: {} };
|
|
3084
3118
|
const combined = signal ? AbortSignal.any([this.#abort.signal, signal]) : this.#abort.signal;
|
|
3085
|
-
return cloneFrozen(await this.#trackRead(this.dependencies.getConfigReferenceContext(pluginId,
|
|
3119
|
+
return cloneFrozen(await this.#trackRead(this.dependencies.getConfigReferenceContext(pluginId, structuredClone(options), structuredClone(draft), combined)));
|
|
3120
|
+
}
|
|
3121
|
+
async saveConfigDraft(request, signal) {
|
|
3122
|
+
this.#assertAcceptingActions();
|
|
3123
|
+
if (this.hasActiveWrite)
|
|
3124
|
+
throw new TuiOperationBlockedError('writeInProgress', '另一个写操作仍在执行。');
|
|
3125
|
+
if (request.apply || request.kind === 'configSave' && request.retryApply) {
|
|
3126
|
+
throw new TuiOperationBlockedError('configSaveOnlyRequired', '此入口只保存配置。请单独准备插件更新。');
|
|
3127
|
+
}
|
|
3128
|
+
const intent = normalizeOperationIntent(structuredClone(request));
|
|
3129
|
+
const combined = signal ? AbortSignal.any([this.#abort.signal, signal]) : this.#abort.signal;
|
|
3130
|
+
const promise = (async () => {
|
|
3131
|
+
try {
|
|
3132
|
+
this.#validateReviewIntent(intent);
|
|
3133
|
+
const evidence = await this.#prepareReviewEvidence(intent, combined);
|
|
3134
|
+
combined.throwIfAborted();
|
|
3135
|
+
const execution = await this.dependencies.executeIntent(intent, evidence, combined);
|
|
3136
|
+
if (intent.kind === 'configSaveBatch') {
|
|
3137
|
+
const batch = execution;
|
|
3138
|
+
return {
|
|
3139
|
+
status: batch.status,
|
|
3140
|
+
savedProfiles: batch.groups.filter((group) => group.configSaved).map((group) => group.platform ?? 'common'),
|
|
3141
|
+
failedProfiles: batch.groups.filter((group) => !group.configSaved).map((group) => ({
|
|
3142
|
+
profile: group.platform ?? 'common',
|
|
3143
|
+
...(group.errorCode ? { errorCode: safeDiagnosticCode(group.errorCode, 'configWriteFailed') } : {}),
|
|
3144
|
+
})),
|
|
3145
|
+
};
|
|
3146
|
+
}
|
|
3147
|
+
const saved = typeof execution === 'object' && execution !== null
|
|
3148
|
+
&& execution.configWrite === 'succeeded';
|
|
3149
|
+
return { status: saved ? 'succeeded' : 'failed', savedProfiles: saved ? [intent.platform ?? 'common'] : [],
|
|
3150
|
+
failedProfiles: saved ? [] : [{ profile: intent.platform ?? 'common', errorCode: 'configWriteUnconfirmed' }] };
|
|
3151
|
+
}
|
|
3152
|
+
catch (error) {
|
|
3153
|
+
await this.#logOperationError(error, issueCode(error), intent);
|
|
3154
|
+
throw new TuiOperationBlockedError(issueCode(error), sanitizeDiagnosticText(error instanceof Error ? error.message : '设置未保存。', [], collectOperationSensitiveValues(intent)));
|
|
3155
|
+
}
|
|
3156
|
+
})();
|
|
3157
|
+
this.#configSaveInflight = promise;
|
|
3158
|
+
this.#publish();
|
|
3159
|
+
try {
|
|
3160
|
+
return cloneFrozen(await promise);
|
|
3161
|
+
}
|
|
3162
|
+
finally {
|
|
3163
|
+
if (this.#configSaveInflight === promise)
|
|
3164
|
+
this.#configSaveInflight = undefined;
|
|
3165
|
+
this.#publish();
|
|
3166
|
+
}
|
|
3086
3167
|
}
|
|
3087
3168
|
async previewConfigSaveImpact(intent, signal) {
|
|
3088
3169
|
this.#assertAcceptingActions();
|
|
@@ -3097,12 +3178,12 @@ export class TuiController {
|
|
|
3097
3178
|
}
|
|
3098
3179
|
return cloneFrozen(evidence.config);
|
|
3099
3180
|
}
|
|
3100
|
-
async getConfigResetAvailability(pluginId,
|
|
3181
|
+
async getConfigResetAvailability(pluginId, options) {
|
|
3101
3182
|
this.#assertAcceptingActions();
|
|
3102
3183
|
if (!this.dependencies.getConfigResetAvailability) {
|
|
3103
3184
|
throw new TuiOperationBlockedError('configResetVerificationUnavailable', '设置层可信读取入口不可用。');
|
|
3104
3185
|
}
|
|
3105
|
-
return cloneFrozen(await this.#trackRead(this.dependencies.getConfigResetAvailability(pluginId, structuredClone(
|
|
3186
|
+
return cloneFrozen(await this.#trackRead(this.dependencies.getConfigResetAvailability(pluginId, structuredClone(options), this.#abort.signal)));
|
|
3106
3187
|
}
|
|
3107
3188
|
listUserIssues() {
|
|
3108
3189
|
return cloneFrozen(this.#viewModel?.issues ?? []);
|
|
@@ -3215,25 +3296,6 @@ export class TuiController {
|
|
|
3215
3296
|
}
|
|
3216
3297
|
}
|
|
3217
3298
|
if (intent.kind === 'pluginWriteBatch') {
|
|
3218
|
-
if ((intent.selection.kind === 'currentProjectIntentRoots'
|
|
3219
|
-
|| intent.selection.kind === 'currentProjectIntentTargets')
|
|
3220
|
-
&& (intent.action !== 'sync' || intent.scope !== 'project')) {
|
|
3221
|
-
throw new TuiOperationBlockedError('batchSelectionInvalid', '当前项目全部同步只支持 project scope sync。');
|
|
3222
|
-
}
|
|
3223
|
-
if (intent.selection.kind === 'currentProjectIntentTargets') {
|
|
3224
|
-
const targets = intent.selection.targets;
|
|
3225
|
-
const canonical = [...new Map(targets.map((target) => [
|
|
3226
|
-
projectReminderTargetKey(target), target,
|
|
3227
|
-
])).values()].sort((left, right) => projectReminderTargetKey(left)
|
|
3228
|
-
.localeCompare(projectReminderTargetKey(right), 'en'));
|
|
3229
|
-
if (targets.length === 0
|
|
3230
|
-
|| targets.some((target) => target.scope !== 'project')
|
|
3231
|
-
|| JSON.stringify(targets) !== JSON.stringify(canonical)) {
|
|
3232
|
-
throw new TuiOperationBlockedError('batchSelectionInvalid', '当前项目同步目标必须是非空、规范且精确的 project 目标集合。');
|
|
3233
|
-
}
|
|
3234
|
-
for (const target of targets)
|
|
3235
|
-
sourceIdFromPluginId(target.pluginId);
|
|
3236
|
-
}
|
|
3237
3299
|
if (intent.selection.kind === 'selected') {
|
|
3238
3300
|
const canonical = canonicalPluginIds(intent.selection.pluginIds);
|
|
3239
3301
|
if (canonical.length === 0) {
|
|
@@ -3258,7 +3320,7 @@ export class TuiController {
|
|
|
3258
3320
|
if (intent.kind === 'pluginWriteBatch') {
|
|
3259
3321
|
return intent.selection.kind === 'selected'
|
|
3260
3322
|
? `${intent.selection.pluginIds.length} 个插件`
|
|
3261
|
-
:
|
|
3323
|
+
: `${intent.selection.targets.length} 个目标`;
|
|
3262
3324
|
}
|
|
3263
3325
|
if ('pluginId' in intent) {
|
|
3264
3326
|
return this.#viewModel?.plugins.find((plugin) => plugin.pluginId === intent.pluginId)?.displayName
|
|
@@ -3464,7 +3526,7 @@ export class TuiController {
|
|
|
3464
3526
|
const configImpact = configEvidence ? {
|
|
3465
3527
|
state: configEvidence.some((group) => group.impactState !== 'known') ? 'unknown' : 'known',
|
|
3466
3528
|
affectedTargetCount: new Set(configEvidence.flatMap((group) => group.affectedTargets
|
|
3467
|
-
.map((target) => `${target.platform}\0${target.scope}`))).size
|
|
3529
|
+
.map((target) => `${target.pluginName ?? ''}\0${target.platform}\0${target.scope}`))).size
|
|
3468
3530
|
+ new Set(configEvidence.flatMap((group) => (group.commandPackages ?? [])
|
|
3469
3531
|
.map((target) => target.packageId))).size,
|
|
3470
3532
|
} : undefined;
|
|
@@ -3887,6 +3949,19 @@ export class TuiController {
|
|
|
3887
3949
|
summary: pluginSummary,
|
|
3888
3950
|
});
|
|
3889
3951
|
}
|
|
3952
|
+
const concurrentScopeConflict = ticket.intent.kind === 'pluginWrite'
|
|
3953
|
+
&& ticket.intent.action === 'install'
|
|
3954
|
+
&& unsuccessfulTargets.length === pluginSummary.targets?.length
|
|
3955
|
+
&& unsuccessfulTargets.every((target) => target.errorCode === 'codexExtensionScopeConflict');
|
|
3956
|
+
if (concurrentScopeConflict) {
|
|
3957
|
+
return this.#setBlocked(operationId, 'codexExtensionScopeConflict', {
|
|
3958
|
+
intent: ticket.intent,
|
|
3959
|
+
navigation: 'result',
|
|
3960
|
+
returnRoute: ticket.returnRoute,
|
|
3961
|
+
summary: pluginSummary,
|
|
3962
|
+
writeMayHaveCompleted: false,
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3890
3965
|
const dependencyStillRequired = unsuccessfulTargets.length > 0
|
|
3891
3966
|
&& unsuccessfulTargets.every((target) => target.errorCode === 'dependencyStillRequired');
|
|
3892
3967
|
return this.#setBlocked(operationId, dependencyStillRequired ? 'dependencyStillRequired' : 'operationFailed', {
|
|
@@ -3902,7 +3977,11 @@ export class TuiController {
|
|
|
3902
3977
|
});
|
|
3903
3978
|
}
|
|
3904
3979
|
const originIssue = ticket.intent.kind === 'pluginWrite' ? ticket.intent.originIssue : undefined;
|
|
3980
|
+
const projectRemovalKeepsDemand = ticket.intent.kind === 'pluginWrite'
|
|
3981
|
+
&& ticket.intent.action === 'uninstall' && ticket.intent.scope === 'project'
|
|
3982
|
+
&& pluginSummary?.code === 'operationSucceeded';
|
|
3905
3983
|
if (originIssue && !dependencyPartial
|
|
3984
|
+
&& !projectRemovalKeepsDemand
|
|
3906
3985
|
&& this.#viewModel?.issues.some((issue) => originIssueRemains(issue, originIssue))) {
|
|
3907
3986
|
return this.#setBlocked(operationId, 'issueStillPresent', {
|
|
3908
3987
|
intent: ticket.intent,
|
|
@@ -3912,7 +3991,7 @@ export class TuiController {
|
|
|
3912
3991
|
verification: 'issueStillPresent',
|
|
3913
3992
|
});
|
|
3914
3993
|
}
|
|
3915
|
-
if (ticket.intent.kind === 'configSave' || ticket.intent.kind === 'configReset') {
|
|
3994
|
+
if ((ticket.intent.kind === 'configSave' && !ticket.intent.retryApply) || ticket.intent.kind === 'configReset') {
|
|
3916
3995
|
const configWrite = typeof execution === 'object' && execution !== null
|
|
3917
3996
|
? execution.configWrite
|
|
3918
3997
|
: undefined;
|
|
@@ -3986,7 +4065,7 @@ export class TuiController {
|
|
|
3986
4065
|
: ticket.returnRoute)),
|
|
3987
4066
|
};
|
|
3988
4067
|
const configResultNeedsAttention = result.state === 'succeeded' && (result.summary.messages?.some((message) => message.startsWith('警告:')
|
|
3989
|
-
|| message === '提示:其它项目没有被扫描,可能需要在对应项目目录中运行 aw sync。') ?? false);
|
|
4068
|
+
|| message === '提示:其它项目没有被扫描,可能需要在对应项目目录中运行 aw plugin sync --context project。') ?? false);
|
|
3990
4069
|
if ((ticket.intent.kind === 'configSave' || ticket.intent.kind === 'configSaveBatch'
|
|
3991
4070
|
|| ticket.intent.kind === 'configReset')
|
|
3992
4071
|
&& this.#portableRepreparePluginId === ticket.intent.pluginId
|
|
@@ -4085,7 +4164,7 @@ export class TuiController {
|
|
|
4085
4164
|
? '设置已保存,但后续应用失败。'
|
|
4086
4165
|
: '设置已保存,但后续步骤未完成。',
|
|
4087
4166
|
pluginId: ticket.intent.pluginId,
|
|
4088
|
-
configScope: ticket.intent.
|
|
4167
|
+
configScope: 'user', configPlatform: ticket.intent.platform, configContext: ticket.intent.context ?? 'user',
|
|
4089
4168
|
...(ticket.intent.kind === 'configSave' && ticket.intent.apply
|
|
4090
4169
|
? { configApplyRetryAvailable: true } : {}),
|
|
4091
4170
|
elapsedMs,
|
|
@@ -4279,6 +4358,7 @@ export class TuiController {
|
|
|
4279
4358
|
if (this.#disposed)
|
|
4280
4359
|
return;
|
|
4281
4360
|
this.#disposed = true;
|
|
4361
|
+
this.#scopedPreparationAbort?.abort();
|
|
4282
4362
|
this.#invalidatePortableResultRecheck();
|
|
4283
4363
|
this.#cancelReviewPreparation();
|
|
4284
4364
|
this.#cancelPortableRequest();
|
|
@@ -4687,6 +4767,7 @@ function operationResultSummaryUnsafe(intent, execution) {
|
|
|
4687
4767
|
: false,
|
|
4688
4768
|
configGroups: batch.groups.map((group) => ({
|
|
4689
4769
|
scope: group.scope,
|
|
4770
|
+
platform: group.platform,
|
|
4690
4771
|
configSaved: group.configSaved,
|
|
4691
4772
|
applyRequested: group.applyRequested,
|
|
4692
4773
|
applyStatus: group.applyStatus,
|
|
@@ -4698,7 +4779,7 @@ function operationResultSummaryUnsafe(intent, execution) {
|
|
|
4698
4779
|
: []),
|
|
4699
4780
|
messages: [
|
|
4700
4781
|
...batch.groups.map((group) => {
|
|
4701
|
-
const label = group.
|
|
4782
|
+
const label = group.platform ? `${group.platform} 平台配置` : '公共配置';
|
|
4702
4783
|
return group.configSaved
|
|
4703
4784
|
? `${label}已保存${group.errorCode === 'invalidConfig'
|
|
4704
4785
|
? ',但当前配置无效,未应用'
|
|
@@ -4707,7 +4788,7 @@ function operationResultSummaryUnsafe(intent, execution) {
|
|
|
4707
4788
|
}),
|
|
4708
4789
|
...publicWarnings.map((warning) => `警告:${warning}`),
|
|
4709
4790
|
...(otherProjectsMayRequireSync
|
|
4710
|
-
? ['提示:其它项目没有被扫描,可能需要在对应项目目录中运行 aw sync。']
|
|
4791
|
+
? ['提示:其它项目没有被扫描,可能需要在对应项目目录中运行 aw plugin sync --context project。']
|
|
4711
4792
|
: []),
|
|
4712
4793
|
],
|
|
4713
4794
|
};
|
|
@@ -4727,7 +4808,7 @@ function operationResultSummaryUnsafe(intent, execution) {
|
|
|
4727
4808
|
const target = typeof entry.target === 'object' && entry.target !== null
|
|
4728
4809
|
? entry.target
|
|
4729
4810
|
: entry;
|
|
4730
|
-
if ((target.platform
|
|
4811
|
+
if (!isPlatformTarget(target.platform)
|
|
4731
4812
|
|| (target.scope !== 'user' && target.scope !== 'project')
|
|
4732
4813
|
|| (entry.state !== 'completed' && entry.state !== 'failed' && entry.state !== 'skipped'))
|
|
4733
4814
|
return [];
|
|
@@ -4761,13 +4842,12 @@ function operationResultSummaryUnsafe(intent, execution) {
|
|
|
4761
4842
|
? execution.warnings.filter((warning) => typeof warning === 'string'
|
|
4762
4843
|
&& warning.startsWith('projectConfigShareabilityUnverified:'))
|
|
4763
4844
|
: [];
|
|
4764
|
-
const otherProjectsMayRequireSync =
|
|
4765
|
-
&& typeof execution === 'object' && execution !== null
|
|
4845
|
+
const otherProjectsMayRequireSync = typeof execution === 'object' && execution !== null
|
|
4766
4846
|
&& execution.otherProjectsMayRequireSync === true;
|
|
4767
4847
|
const publicMessages = [
|
|
4768
4848
|
...publicWarnings.map((warning) => `警告:${warning}`),
|
|
4769
4849
|
...(otherProjectsMayRequireSync
|
|
4770
|
-
? ['提示:其它项目没有被扫描,可能需要在对应项目目录中运行 aw sync。']
|
|
4850
|
+
? ['提示:其它项目没有被扫描,可能需要在对应项目目录中运行 aw plugin sync --context project。']
|
|
4771
4851
|
: []),
|
|
4772
4852
|
];
|
|
4773
4853
|
const partial = overallState === 'partial'
|
|
@@ -4784,7 +4864,7 @@ function operationResultSummaryUnsafe(intent, execution) {
|
|
|
4784
4864
|
message: invalidConfig ? '设置已保存,但没有应用。当前配置仍无效。'
|
|
4785
4865
|
: failed || partial ? '设置已保存,但插件内容重新应用失败或部分失败。' : '操作已完成。',
|
|
4786
4866
|
pluginId: intent.pluginId,
|
|
4787
|
-
configScope: intent.
|
|
4867
|
+
configScope: 'user', configPlatform: intent.platform, configContext: intent.context ?? 'user',
|
|
4788
4868
|
configApplyRetryAvailable: intent.kind === 'configSave'
|
|
4789
4869
|
&& intent.apply && !invalidConfig && (applyState === 'partial' || applyState === 'unavailable'),
|
|
4790
4870
|
...(intent.kind === 'configSave' && intent.retryApply ? { configRetryApply: true } : {}),
|
|
@@ -4804,7 +4884,7 @@ function operationResultSummaryUnsafe(intent, execution) {
|
|
|
4804
4884
|
? { quarantineBasename } : {}),
|
|
4805
4885
|
}
|
|
4806
4886
|
: {}),
|
|
4807
|
-
...(targets.length > 0 ? { targets } : {}),
|
|
4887
|
+
...(targets.length > 0 ? { configTargets: targets } : {}),
|
|
4808
4888
|
...(commandResults.length > 0 ? { commandResults,
|
|
4809
4889
|
recoveryActions: [...new Set(commandResults.flatMap((result) => result.recoveryActions))] } : {}),
|
|
4810
4890
|
};
|