@lesliechan721/aw 0.1.0-beta.16 → 0.1.0-beta.18
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/src/commands/offline-cleanup-evidence.d.ts +8 -0
- package/dist/src/commands/offline-cleanup-evidence.js +18 -0
- package/dist/src/commands/offline-cleanup-evidence.js.map +1 -1
- package/dist/src/commands/rebuild-command.d.ts +4 -1
- package/dist/src/commands/rebuild-command.js +662 -177
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/status-v6.d.ts +2 -2
- package/dist/src/commands/status-v6.js +40 -11
- package/dist/src/commands/status-v6.js.map +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/output/command-receipt.js +11 -6
- package/dist/src/output/command-receipt.js.map +1 -1
- package/dist/src/tui/console-app-v4.js +9 -6
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.js +191 -151
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +1 -0
- package/dist/src/tui/controller.js +10 -7
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/model.d.ts +1 -1
- package/dist/src/tui/model.js +42 -8
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-review-planner.js +103 -26
- package/dist/src/tui/operation-review-planner.js.map +1 -1
- package/dist/src/utils/codex-manager.d.ts +5 -7
- package/dist/src/utils/codex-manager.js +24 -97
- package/dist/src/utils/codex-manager.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import fs from 'fs-extra';
|
|
3
4
|
import { getCodexOperationRequirement, probeAgentVersion } from '../utils/agent-version.js';
|
|
@@ -38,7 +39,7 @@ import { AwCapabilityUnsupportedError, IMPLEMENTED_AW_CAPABILITIES, } from './aw
|
|
|
38
39
|
import { checkAwCapabilitySupport } from '../utils/manifest.js';
|
|
39
40
|
import { createDependencyPlacementPlan, } from './dependency-operation-plan.js';
|
|
40
41
|
import { createReconcileOperationPlan, } from './reconcile-operation-plan.js';
|
|
41
|
-
import { forceOfflineCleanupEvidence, offlineCleanupEvidenceFromInventory, offlineCleanupEvidenceMatches, } from './offline-cleanup-evidence.js';
|
|
42
|
+
import { forceOfflineCleanupEvidence, intentOnlyOfflineCleanupEvidence, offlineCleanupEvidenceFromInventory, offlineCleanupEvidenceMatches, } from './offline-cleanup-evidence.js';
|
|
42
43
|
async function measureCommandPerformanceStage(runtime, name, operation) {
|
|
43
44
|
const startedAt = Date.now();
|
|
44
45
|
let phase = 'success';
|
|
@@ -108,6 +109,37 @@ function omitSelectedPluginConfigErrors(inventory, targets) {
|
|
|
108
109
|
function selectedPlatforms(selection) {
|
|
109
110
|
return selection === 'both' ? ['codex', 'claude'] : [selection];
|
|
110
111
|
}
|
|
112
|
+
async function selectedProjectCodexSkillReferences(options) {
|
|
113
|
+
const skillsRoot = path.join(resolveRepoRootSync(options.cwd), '.agents', 'skills');
|
|
114
|
+
const entries = await fs.readdir(skillsRoot, { withFileTypes: true }).catch((error) => {
|
|
115
|
+
const code = error.code;
|
|
116
|
+
if (code === 'ENOENT' || code === 'ENOTDIR')
|
|
117
|
+
return [];
|
|
118
|
+
throw error;
|
|
119
|
+
});
|
|
120
|
+
const references = [];
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
123
|
+
continue;
|
|
124
|
+
const skillRoot = path.join(skillsRoot, entry.name);
|
|
125
|
+
const marker = await readSkillOwnerMarker({ skillRoot, allowedRoot: skillsRoot }).catch(() => null);
|
|
126
|
+
if (!marker?.sourceId
|
|
127
|
+
|| marker.pluginId !== `${marker.sourceId}/${marker.plugin}`
|
|
128
|
+
|| marker.platform !== 'codex'
|
|
129
|
+
|| marker.scope !== 'project')
|
|
130
|
+
continue;
|
|
131
|
+
references.push({
|
|
132
|
+
kind: 'namespacedOwner',
|
|
133
|
+
location: path.join(skillRoot, SKILL_OWNER_MARKER),
|
|
134
|
+
sourceId: marker.sourceId,
|
|
135
|
+
pluginName: marker.plugin,
|
|
136
|
+
platform: marker.platform,
|
|
137
|
+
scope: marker.scope,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return references;
|
|
141
|
+
}
|
|
142
|
+
const cleanupProjectSkillsByInventory = new WeakMap();
|
|
111
143
|
function selectCodexActivationPlans(plans, targetScopes, preservedActivations) {
|
|
112
144
|
return plans.flatMap((plan) => {
|
|
113
145
|
const includesScope = (pluginId, scope) => targetScopes.has(`${pluginId}\0${scope}`);
|
|
@@ -116,10 +148,7 @@ function selectCodexActivationPlans(plans, targetScopes, preservedActivations) {
|
|
|
116
148
|
: []));
|
|
117
149
|
if (pluginIds.size === 0)
|
|
118
150
|
return [];
|
|
119
|
-
const includesActivationAction = (action) => includesScope(action.pluginId, action.scope)
|
|
120
|
-
|| !(action.scope === plan.requirement.scope
|
|
121
|
-
? plan.managedInventory.desired
|
|
122
|
-
: plan.managedInventory.related.desired).includes(action.codexPluginId);
|
|
151
|
+
const includesActivationAction = (action) => includesScope(action.pluginId, action.scope) || !action.desired;
|
|
123
152
|
const sharedNativeActions = plan.sharedNativeActions.filter((action) => pluginIds.has(action.pluginId));
|
|
124
153
|
const userActions = plan.activationActions.user.filter((action) => pluginIds.has(action.pluginId) && includesActivationAction(action));
|
|
125
154
|
const projectActions = plan.activationActions.project.filter((action) => pluginIds.has(action.pluginId) && includesActivationAction(action));
|
|
@@ -251,12 +280,16 @@ function deriveDesiredIntentState(options) {
|
|
|
251
280
|
if (options.command === 'install') {
|
|
252
281
|
for (const root of options.selectedRoots)
|
|
253
282
|
next = mergePluginIntentRootPlatforms(next, root);
|
|
254
|
-
|
|
283
|
+
if (options.selectedRoots.length > 0) {
|
|
284
|
+
next = updateIntentOverrides({ state: next, plan: options.plan });
|
|
285
|
+
}
|
|
255
286
|
}
|
|
256
|
-
else if (options.command === 'uninstall') {
|
|
287
|
+
else if (options.command === 'uninstall' || options.command === 'cleanup') {
|
|
257
288
|
for (const root of options.selectedRoots)
|
|
258
289
|
next = removePluginIntentRootPlatforms(next, root);
|
|
259
|
-
|
|
290
|
+
if (options.command === 'uninstall') {
|
|
291
|
+
next = pruneUnreachableDependencyScopeOverrides(next, options.catalogEntries);
|
|
292
|
+
}
|
|
260
293
|
}
|
|
261
294
|
return next;
|
|
262
295
|
}
|
|
@@ -767,6 +800,199 @@ function projectLegacyTargets(references) {
|
|
|
767
800
|
return [`${target.pluginName}\0${target.platform}\0${target.scope}`, target];
|
|
768
801
|
})).values()];
|
|
769
802
|
}
|
|
803
|
+
function cleanupIntentTargets(options) {
|
|
804
|
+
const selectedPlatforms = new Set(options.platforms);
|
|
805
|
+
const liveByKey = new Map(options.liveTargets.map((target) => [
|
|
806
|
+
`${target.pluginId}\0${target.platform}`,
|
|
807
|
+
target,
|
|
808
|
+
]));
|
|
809
|
+
const selectedIds = options.all
|
|
810
|
+
? new Set(options.state.roots.map((root) => root.pluginId))
|
|
811
|
+
: new Set(options.selectors.flatMap((selector) => {
|
|
812
|
+
const parsed = parsePluginId(selector);
|
|
813
|
+
if (parsed.sourceId)
|
|
814
|
+
return [formatPluginId(parsed)];
|
|
815
|
+
const matches = options.state.roots
|
|
816
|
+
.filter((root) => parsePluginId(root.pluginId).pluginName === parsed.pluginName)
|
|
817
|
+
.map((root) => root.pluginId);
|
|
818
|
+
const liveMatches = options.liveTargets
|
|
819
|
+
.filter((target) => target.pluginName === parsed.pluginName)
|
|
820
|
+
.map((target) => target.pluginId);
|
|
821
|
+
const identities = [...new Set([...matches, ...liveMatches])];
|
|
822
|
+
if (identities.length > 1) {
|
|
823
|
+
throw new Error(`pluginSelectorAmbiguous: Plugin "${parsed.pluginName}" has multiple Intent or live matches: ${identities.sort().join(', ')}.`);
|
|
824
|
+
}
|
|
825
|
+
return identities.length === 1
|
|
826
|
+
? identities
|
|
827
|
+
: options.primarySourceId
|
|
828
|
+
? [formatPluginId({ sourceId: options.primarySourceId, pluginName: parsed.pluginName })]
|
|
829
|
+
: [];
|
|
830
|
+
}));
|
|
831
|
+
return options.state.roots.flatMap((root) => root.platforms.flatMap((platform) => {
|
|
832
|
+
if (!selectedIds.has(root.pluginId) || !selectedPlatforms.has(platform))
|
|
833
|
+
return [];
|
|
834
|
+
const live = liveByKey.get(`${root.pluginId}\0${platform}`);
|
|
835
|
+
if (live)
|
|
836
|
+
return [live];
|
|
837
|
+
const parsed = parsePluginId(root.pluginId);
|
|
838
|
+
return [{
|
|
839
|
+
pluginId: root.pluginId,
|
|
840
|
+
pluginName: parsed.pluginName,
|
|
841
|
+
sourceId: parsed.sourceId,
|
|
842
|
+
sourceIdentity: 'unavailable',
|
|
843
|
+
platform,
|
|
844
|
+
scope: options.scope,
|
|
845
|
+
}];
|
|
846
|
+
}));
|
|
847
|
+
}
|
|
848
|
+
function cleanupSelectedRoots(targets) {
|
|
849
|
+
const platformsByPluginId = new Map();
|
|
850
|
+
for (const target of targets) {
|
|
851
|
+
const platforms = platformsByPluginId.get(target.pluginId) ?? new Set();
|
|
852
|
+
platforms.add(target.platform);
|
|
853
|
+
platformsByPluginId.set(target.pluginId, platforms);
|
|
854
|
+
}
|
|
855
|
+
return [...platformsByPluginId].map(([pluginId, platforms]) => ({
|
|
856
|
+
pluginId,
|
|
857
|
+
platforms: [...platforms],
|
|
858
|
+
}));
|
|
859
|
+
}
|
|
860
|
+
function cleanupIntentResult(target) {
|
|
861
|
+
return {
|
|
862
|
+
status: 'succeeded',
|
|
863
|
+
command: 'cleanup',
|
|
864
|
+
target: {
|
|
865
|
+
pluginId: target.pluginId,
|
|
866
|
+
pluginName: target.pluginName,
|
|
867
|
+
sourceId: target.sourceId,
|
|
868
|
+
sourceIdentity: target.sourceIdentity,
|
|
869
|
+
pluginVersion: '0.0.0',
|
|
870
|
+
platform: target.platform,
|
|
871
|
+
scope: target.scope,
|
|
872
|
+
cwd: '.',
|
|
873
|
+
},
|
|
874
|
+
actions: [],
|
|
875
|
+
cleanupAttempted: false,
|
|
876
|
+
removed: [],
|
|
877
|
+
absent: [],
|
|
878
|
+
residual: [],
|
|
879
|
+
quarantined: [],
|
|
880
|
+
failures: [],
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
function cleanupInventoryWarnings(inventory, targets) {
|
|
884
|
+
return inventory.errors.flatMap((error) => {
|
|
885
|
+
const relevance = error.relevance;
|
|
886
|
+
const relevant = relevance.global || targets.some((target) => (relevance.sourceId === undefined || relevance.sourceId === target.sourceId)
|
|
887
|
+
&& (relevance.pluginName === undefined || relevance.pluginName === target.pluginName)
|
|
888
|
+
&& (relevance.platform === undefined || relevance.platform === target.platform)
|
|
889
|
+
&& (relevance.scope === undefined || relevance.scope === target.scope));
|
|
890
|
+
if (!relevant)
|
|
891
|
+
return [];
|
|
892
|
+
const digest = crypto.createHash('sha256').update(JSON.stringify(error)).digest('hex');
|
|
893
|
+
return [`stateInventoryResidual: ${error.kind} (sha256:${digest})`];
|
|
894
|
+
}).sort();
|
|
895
|
+
}
|
|
896
|
+
function cleanupInventoryErrorsForTarget(inventory, target) {
|
|
897
|
+
return inventory.errors.filter((error) => {
|
|
898
|
+
const relevance = error.relevance;
|
|
899
|
+
return relevance.global || ((relevance.sourceId === undefined || relevance.sourceId === target.sourceId)
|
|
900
|
+
&& (relevance.pluginName === undefined || relevance.pluginName === target.pluginName)
|
|
901
|
+
&& (relevance.platform === undefined || relevance.platform === target.platform)
|
|
902
|
+
&& (relevance.scope === undefined || relevance.scope === target.scope));
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
function cleanupInventoryErrorsForSelection(options) {
|
|
906
|
+
const selectors = options.selectors.map(parsePluginId);
|
|
907
|
+
return options.inventory.errors.filter((error) => {
|
|
908
|
+
const relevance = error.relevance;
|
|
909
|
+
if (relevance.global)
|
|
910
|
+
return true;
|
|
911
|
+
if (relevance.scope !== undefined && relevance.scope !== options.scope)
|
|
912
|
+
return false;
|
|
913
|
+
if (relevance.platform !== undefined && !options.platforms.includes(relevance.platform))
|
|
914
|
+
return false;
|
|
915
|
+
if (options.all)
|
|
916
|
+
return true;
|
|
917
|
+
return selectors.some((selector) => (relevance.pluginName === undefined || relevance.pluginName === selector.pluginName)
|
|
918
|
+
&& (selector.sourceId === undefined
|
|
919
|
+
|| relevance.sourceId === undefined
|
|
920
|
+
|| relevance.sourceId === selector.sourceId));
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
function cleanupActionCoversLocation(evidence, location, env, cwd) {
|
|
924
|
+
const rawRepoRoot = resolveRepoRootSync(cwd);
|
|
925
|
+
const sensitiveRoots = [
|
|
926
|
+
canonicalizePathThroughExistingAncestorSync(getCodexHome(env)),
|
|
927
|
+
canonicalizePathThroughExistingAncestorSync(getClaudeHome(env)),
|
|
928
|
+
canonicalizePathThroughExistingAncestorSync(rawRepoRoot),
|
|
929
|
+
getCodexHome(env),
|
|
930
|
+
getClaudeHome(env),
|
|
931
|
+
rawRepoRoot,
|
|
932
|
+
];
|
|
933
|
+
const normalizedLocation = location.replaceAll('\\', '/');
|
|
934
|
+
return evidence?.plan.actions.some((action) => {
|
|
935
|
+
if (action.action !== 'removeOwnedOutput')
|
|
936
|
+
return false;
|
|
937
|
+
const normalizedTarget = sanitizeCatalogSourceErrorMessage(action.target, env, sensitiveRoots).replaceAll('\\', '/');
|
|
938
|
+
const relative = path.posix.relative(normalizedTarget, normalizedLocation);
|
|
939
|
+
return relative === ''
|
|
940
|
+
|| (relative !== '..' && !relative.startsWith('../') && !path.posix.isAbsolute(relative));
|
|
941
|
+
}) ?? false;
|
|
942
|
+
}
|
|
943
|
+
function cleanupLocatorPaths(component) {
|
|
944
|
+
const locator = component.locator;
|
|
945
|
+
switch (locator.kind) {
|
|
946
|
+
case 'claude-permissions': return [locator.settingsPath];
|
|
947
|
+
case 'codex-instruction': return [locator.configPath];
|
|
948
|
+
case 'codex-activation': return [locator.configPath];
|
|
949
|
+
case 'legacy-codex-mcp': return [locator.configPath];
|
|
950
|
+
case 'legacy-codex-hook': return [locator.hooksPath];
|
|
951
|
+
case 'codex-role-registration': return [locator.configPath, locator.rolePath];
|
|
952
|
+
case 'codex-marketplace':
|
|
953
|
+
case 'claude-marketplace': return [locator.marketplaceRoot];
|
|
954
|
+
case 'corrupted': return [];
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function cleanupPathAuthorized(pathValue, evidence) {
|
|
958
|
+
const normalizedPath = canonicalizePathThroughExistingAncestorSync(pathValue);
|
|
959
|
+
return evidence.plan.actions.some((action) => {
|
|
960
|
+
if (action.action !== 'removeOwnedOutput')
|
|
961
|
+
return false;
|
|
962
|
+
const actionPath = action.target.split('#', 1)[0];
|
|
963
|
+
const normalizedActionPath = canonicalizePathThroughExistingAncestorSync(actionPath);
|
|
964
|
+
const relative = path.relative(normalizedPath, normalizedActionPath);
|
|
965
|
+
return relative === ''
|
|
966
|
+
|| (relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
function cleanupComponentAuthorized(component, evidence) {
|
|
970
|
+
if ('path' in component)
|
|
971
|
+
return cleanupPathAuthorized(component.path, evidence);
|
|
972
|
+
if (component.kind === 'native-plugin') {
|
|
973
|
+
return evidence.plan.actions.some((action) => action.action === 'removeNativeRegistration' && action.target === component.pluginId);
|
|
974
|
+
}
|
|
975
|
+
const locator = component.locator;
|
|
976
|
+
if (locator.kind === 'codex-activation') {
|
|
977
|
+
return evidence.plan.actions.some((action) => action.action === 'removeNativeRegistration'
|
|
978
|
+
&& action.target === locator.codexPluginId);
|
|
979
|
+
}
|
|
980
|
+
if (locator.kind === 'codex-marketplace'
|
|
981
|
+
|| locator.kind === 'claude-marketplace') {
|
|
982
|
+
return evidence.plan.actions.some((action) => action.action === 'removeNativeRegistration'
|
|
983
|
+
&& action.marketplaceName === locator.marketplaceName);
|
|
984
|
+
}
|
|
985
|
+
if (evidence.plan.actions.some((action) => action.action === 'removeOwnedOutput' && action.target === component.label))
|
|
986
|
+
return true;
|
|
987
|
+
return cleanupLocatorPaths(component).some((candidate) => cleanupPathAuthorized(candidate, evidence));
|
|
988
|
+
}
|
|
989
|
+
function cleanupComponentResidual(component) {
|
|
990
|
+
if ('path' in component)
|
|
991
|
+
return component.path;
|
|
992
|
+
if (component.kind === 'native-plugin')
|
|
993
|
+
return component.pluginId;
|
|
994
|
+
return component.label;
|
|
995
|
+
}
|
|
770
996
|
function resolveLiveCleanupTargets(options) {
|
|
771
997
|
const visible = options.references.filter((reference) => (reference.kind === 'namespacedOwner'
|
|
772
998
|
|| reference.kind === 'namespacedMarketplace')
|
|
@@ -857,84 +1083,145 @@ async function cleanupOfflineOwnedComponents(options) {
|
|
|
857
1083
|
};
|
|
858
1084
|
const integration = createRebuildIntegration({ context });
|
|
859
1085
|
for (const target of options.targets) {
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1086
|
+
try {
|
|
1087
|
+
const inventoryErrors = (options.inventoryErrors ?? []).filter((error) => error.relevance.global || ((error.relevance.sourceId === undefined || error.relevance.sourceId === target.sourceId)
|
|
1088
|
+
&& (error.relevance.pluginName === undefined || error.relevance.pluginName === target.pluginName)
|
|
1089
|
+
&& (error.relevance.platform === undefined || error.relevance.platform === target.platform)
|
|
1090
|
+
&& (error.relevance.scope === undefined || error.relevance.scope === target.scope)));
|
|
1091
|
+
const evidence = options.evidence?.find((candidate) => candidate.pluginId === target.pluginId
|
|
1092
|
+
&& candidate.sourceIdentity === target.sourceIdentity
|
|
1093
|
+
&& candidate.platform === target.platform
|
|
1094
|
+
&& candidate.scope === target.scope);
|
|
1095
|
+
const activationAction = target.platform === 'codex'
|
|
1096
|
+
? evidence?.plan.actions.find((action) => action.action === 'removeNativeRegistration'
|
|
1097
|
+
&& action.activationScope === target.scope)
|
|
1098
|
+
: undefined;
|
|
1099
|
+
let activationDisposition = 'remove';
|
|
1100
|
+
if (activationAction && target.scope === 'user') {
|
|
1101
|
+
activationDisposition = 'disable';
|
|
1102
|
+
}
|
|
1103
|
+
const plugin = createSyntheticPlugin(target.pluginName, options.env);
|
|
1104
|
+
plugin.sourceId = target.sourceId;
|
|
1105
|
+
plugin.sourceIdentity = target.sourceIdentity;
|
|
1106
|
+
plugin.pluginId = target.pluginId;
|
|
1107
|
+
const marketplaceName = target.marketplaceName ?? 'aw';
|
|
1108
|
+
plugin.marketplaceName = marketplaceName;
|
|
1109
|
+
const desired = await integration.buildTarget({
|
|
1110
|
+
sourceId: target.sourceId,
|
|
1111
|
+
sourceIdentity: target.sourceIdentity,
|
|
1112
|
+
pluginId: target.pluginId,
|
|
1113
|
+
marketplaceName,
|
|
1114
|
+
marketplaceRoot: path.join(resolveAwHome(options.env), 'marketplaces', target.sourceId),
|
|
1115
|
+
plugin,
|
|
1116
|
+
platform: target.platform,
|
|
1117
|
+
scope: target.scope,
|
|
1118
|
+
cwd: options.cwd,
|
|
1119
|
+
command: 'cleanup',
|
|
1120
|
+
catalog: {
|
|
1121
|
+
rootDir: resolveAwHome(options.env),
|
|
1122
|
+
source: 'local',
|
|
1123
|
+
catalogVersion: '0.0.0',
|
|
1124
|
+
channel: 'beta',
|
|
1125
|
+
},
|
|
1126
|
+
includeNativeCleanupCandidate: target.nativeRegistration === true,
|
|
1127
|
+
preserveCodexSharedNativePayload: target.platform === 'codex',
|
|
1128
|
+
...(activationAction
|
|
1129
|
+
? {
|
|
1130
|
+
codexActivationCleanup: [{
|
|
1131
|
+
codexPluginId: activationAction.target,
|
|
1132
|
+
scope: activationAction.activationScope,
|
|
1133
|
+
disposition: activationDisposition,
|
|
1134
|
+
}],
|
|
1135
|
+
}
|
|
1136
|
+
: {}),
|
|
1137
|
+
});
|
|
1138
|
+
const unplannedCandidates = evidence
|
|
1139
|
+
? desired.ownedComponents.filter((component) => !cleanupComponentAuthorized(component, evidence))
|
|
1140
|
+
: [];
|
|
1141
|
+
const unplannedComponents = unplannedCandidates.length > 0
|
|
1142
|
+
? (await inspectOwnedComponents({
|
|
1143
|
+
components: unplannedCandidates,
|
|
1144
|
+
executor: integration.driver.ownership,
|
|
1145
|
+
})).components.flatMap((entry) => entry.state === 'present' ? [entry.component] : [])
|
|
1146
|
+
: [];
|
|
1147
|
+
if (evidence) {
|
|
1148
|
+
const authorized = (component) => cleanupComponentAuthorized(component, evidence);
|
|
1149
|
+
desired.ownedComponents = desired.ownedComponents.filter(authorized);
|
|
1150
|
+
desired.desiredOwnedComponents = desired.desiredOwnedComponents?.filter(authorized);
|
|
1151
|
+
desired.optionalOwnedComponents = desired.optionalOwnedComponents?.filter(authorized);
|
|
1152
|
+
desired.artifacts = desired.artifacts.filter((artifact) => cleanupPathAuthorized(artifact.path, evidence));
|
|
1153
|
+
const nativeAuthorized = evidence.plan.actions.some((action) => action.action === 'removeNativeRegistration');
|
|
1154
|
+
desired.requiresNative &&= nativeAuthorized;
|
|
1155
|
+
desired.writeNativeRequired &&= nativeAuthorized;
|
|
1156
|
+
desired.operationActions = desired.operationActions?.filter((action) => {
|
|
1157
|
+
if (action.kind === 'removeOwnedArtifact') {
|
|
1158
|
+
return cleanupPathAuthorized(action.path, evidence);
|
|
1159
|
+
}
|
|
1160
|
+
if (action.kind === 'removeNativeRegistration') {
|
|
1161
|
+
return evidence.plan.actions.some((planned) => planned.action === 'removeNativeRegistration'
|
|
1162
|
+
&& planned.target === action.registration);
|
|
1163
|
+
}
|
|
1164
|
+
if (action.kind === 'setActivation')
|
|
1165
|
+
return nativeAuthorized;
|
|
1166
|
+
return false;
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
const result = await (options.executeCleanupOperation ?? executeRebuildOperation)({
|
|
1170
|
+
command: 'cleanup',
|
|
1171
|
+
target: desired,
|
|
1172
|
+
driver: integration.driver,
|
|
1173
|
+
});
|
|
1174
|
+
if (target.marketplaceIdentityUnresolved) {
|
|
1175
|
+
result.residual.push('nativeMarketplaceIdentityUnresolved');
|
|
1176
|
+
warnings.push(`nativeMarketplaceIdentityUnresolved: Preserved ambiguous native registration for ${target.pluginId} on ${target.platform}/${target.scope}.`);
|
|
1177
|
+
}
|
|
1178
|
+
let finalResult = result;
|
|
1179
|
+
if (unplannedComponents.length > 0) {
|
|
1180
|
+
const message = 'operationPlanResidual: Newly discovered owned output was not included in the cleanup plan and was preserved.';
|
|
1181
|
+
result.residual = [...new Set([
|
|
1182
|
+
...result.residual,
|
|
1183
|
+
...unplannedComponents.map(cleanupComponentResidual),
|
|
1184
|
+
])];
|
|
1185
|
+
warnings.push(message);
|
|
1186
|
+
if (result.status === 'succeeded') {
|
|
1187
|
+
finalResult = {
|
|
1188
|
+
...result,
|
|
1189
|
+
status: 'failed',
|
|
1190
|
+
failures: [...result.failures, { phase: 'cleanup.owned', message }],
|
|
1191
|
+
error: { phase: 'cleanup.owned', message },
|
|
1192
|
+
};
|
|
908
1193
|
}
|
|
909
|
-
: {}),
|
|
910
|
-
});
|
|
911
|
-
const result = await executeRebuildOperation({
|
|
912
|
-
command: 'cleanup',
|
|
913
|
-
target: desired,
|
|
914
|
-
driver: integration.driver,
|
|
915
|
-
});
|
|
916
|
-
if (target.marketplaceIdentityUnresolved) {
|
|
917
|
-
result.residual.push('nativeMarketplaceIdentityUnresolved');
|
|
918
|
-
warnings.push(`nativeMarketplaceIdentityUnresolved: Preserved ambiguous native registration for ${target.pluginId} on ${target.platform}/${target.scope}.`);
|
|
919
|
-
}
|
|
920
|
-
let finalResult = result;
|
|
921
|
-
if (inventoryErrors.length > 0) {
|
|
922
|
-
const message = 'stateInventoryResidual: Some weak state could not be verified. Retry cleanup, use cleanup --force only for exclusively owned damaged output, or run reinstall/sync to restore the target.';
|
|
923
|
-
result.residual = [...new Set([
|
|
924
|
-
...result.residual,
|
|
925
|
-
...inventoryErrors.map((error) => error.location),
|
|
926
|
-
])];
|
|
927
|
-
warnings.push(...inventoryErrors.map((error) => `stateInventoryResidual: ${error.kind}: ${error.message}`));
|
|
928
|
-
if (result.status === 'succeeded') {
|
|
929
|
-
finalResult = {
|
|
930
|
-
...result,
|
|
931
|
-
status: 'failed',
|
|
932
|
-
failures: [...result.failures, { phase: 'cleanup.owned', message }],
|
|
933
|
-
error: { phase: 'cleanup.owned', message },
|
|
934
|
-
};
|
|
935
1194
|
}
|
|
1195
|
+
if (inventoryErrors.length > 0) {
|
|
1196
|
+
const message = 'stateInventoryResidual: Some weak state could not be verified. Retry cleanup, use cleanup --force only for exclusively owned damaged output, or run reinstall/sync to restore the target.';
|
|
1197
|
+
result.residual = [...new Set([
|
|
1198
|
+
...result.residual,
|
|
1199
|
+
...inventoryErrors.map((error) => error.location),
|
|
1200
|
+
])];
|
|
1201
|
+
warnings.push(...inventoryErrors.map((error) => `stateInventoryResidual: ${error.kind}: ${error.message}`));
|
|
1202
|
+
if (finalResult.status === 'succeeded') {
|
|
1203
|
+
finalResult = {
|
|
1204
|
+
...finalResult,
|
|
1205
|
+
status: 'failed',
|
|
1206
|
+
failures: [...finalResult.failures, { phase: 'cleanup.owned', message }],
|
|
1207
|
+
error: { phase: 'cleanup.owned', message },
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
results.push(finalResult);
|
|
1212
|
+
}
|
|
1213
|
+
catch (error) {
|
|
1214
|
+
results.push(failedResult('cleanup', {
|
|
1215
|
+
pluginId: target.pluginId,
|
|
1216
|
+
pluginName: target.pluginName,
|
|
1217
|
+
sourceId: target.sourceId,
|
|
1218
|
+
sourceIdentity: target.sourceIdentity,
|
|
1219
|
+
pluginVersion: '0.0.0',
|
|
1220
|
+
platform: target.platform,
|
|
1221
|
+
scope: target.scope,
|
|
1222
|
+
cwd: options.cwd,
|
|
1223
|
+
}, error instanceof Error ? error.message : String(error), 'cleanup.owned'));
|
|
936
1224
|
}
|
|
937
|
-
results.push(finalResult);
|
|
938
1225
|
}
|
|
939
1226
|
const obsoleteContracts = path.join(resolveAwHome(options.env), 'installation-contracts');
|
|
940
1227
|
try {
|
|
@@ -973,9 +1260,16 @@ async function previewOfflineOwnedComponents(options) {
|
|
|
973
1260
|
await options.onPreviewStage?.('dependencyPlan');
|
|
974
1261
|
return preview;
|
|
975
1262
|
}
|
|
976
|
-
function createOfflineCleanupOperationPlan(evidence) {
|
|
1263
|
+
function createOfflineCleanupOperationPlan(evidence, intent, warnings = []) {
|
|
1264
|
+
const beforeDigest = intent ? calculatePluginIntentDigest(intent.before) : undefined;
|
|
1265
|
+
const afterDigest = intent ? calculatePluginIntentDigest(intent.after) : undefined;
|
|
1266
|
+
const removesIntent = (target) => intent !== undefined
|
|
1267
|
+
&& intent.before.roots.some((root) => root.pluginId === target.pluginId && root.platforms.includes(target.platform))
|
|
1268
|
+
&& !intent.after.roots.some((root) => root.pluginId === target.pluginId && root.platforms.includes(target.platform));
|
|
977
1269
|
return createOperationPlan({
|
|
978
|
-
intentChanges:
|
|
1270
|
+
intentChanges: intent && beforeDigest !== afterDigest
|
|
1271
|
+
? [{ scope: intent.scope, beforeDigest: beforeDigest, afterDigest: afterDigest }]
|
|
1272
|
+
: [],
|
|
979
1273
|
actions: evidence.flatMap((target) => target.plan.actions.map((action) => action.action === 'removeNativeRegistration'
|
|
980
1274
|
? {
|
|
981
1275
|
kind: 'removeNativeRegistration',
|
|
@@ -992,14 +1286,19 @@ function createOfflineCleanupOperationPlan(evidence) {
|
|
|
992
1286
|
path: action.target,
|
|
993
1287
|
ownerFingerprint: target.targetFingerprint,
|
|
994
1288
|
})),
|
|
995
|
-
changes:
|
|
996
|
-
|
|
1289
|
+
changes: [
|
|
1290
|
+
...evidence.flatMap((target) => removesIntent(target)
|
|
1291
|
+
? [`${target.pluginId}:${target.platform}:${target.scope}:removeIntent`]
|
|
1292
|
+
: []),
|
|
1293
|
+
...evidence.flatMap((target) => target.plan.actions.map((action) => `${target.pluginId}:${target.platform}:${target.scope}:${action.action}:${action.target}`)),
|
|
1294
|
+
],
|
|
1295
|
+
warnings: [...warnings],
|
|
997
1296
|
evidence: {
|
|
998
1297
|
catalogDigests: evidence.map((target) => ({
|
|
999
1298
|
sourceId: target.sourceId,
|
|
1000
1299
|
digest: target.sourceIdentity,
|
|
1001
1300
|
})),
|
|
1002
|
-
intentDigests: [],
|
|
1301
|
+
intentDigests: intent ? [{ scope: intent.scope, digest: beforeDigest }] : [],
|
|
1003
1302
|
configDigests: [],
|
|
1004
1303
|
targetOwnerFingerprints: evidence.map((target) => ({
|
|
1005
1304
|
pluginId: target.pluginId,
|
|
@@ -1244,15 +1543,51 @@ async function runRebuildCommandInSession(options) {
|
|
|
1244
1543
|
?? inventoryCatalogSourceReferences;
|
|
1245
1544
|
let ordinaryInventory;
|
|
1246
1545
|
const readOrdinaryInventory = async () => {
|
|
1247
|
-
ordinaryInventory ??= await measureCommandPerformanceStage(options.runtime, 'inventory', () =>
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1546
|
+
ordinaryInventory ??= await measureCommandPerformanceStage(options.runtime, 'inventory', async () => {
|
|
1547
|
+
const inventory = await readInventory({
|
|
1548
|
+
cwd: options.commandOptions.cwd,
|
|
1549
|
+
env: runtimeEnv,
|
|
1550
|
+
});
|
|
1551
|
+
if (options.command !== 'cleanup'
|
|
1552
|
+
|| options.commandOptions.scope !== 'project'
|
|
1553
|
+
|| !selectedPlatforms(options.commandOptions.platform).includes('codex'))
|
|
1554
|
+
return inventory;
|
|
1555
|
+
let projectSkills = cleanupProjectSkillsByInventory.get(inventory);
|
|
1556
|
+
if (!projectSkills) {
|
|
1557
|
+
projectSkills = await selectedProjectCodexSkillReferences({
|
|
1558
|
+
cwd: options.commandOptions.cwd,
|
|
1559
|
+
});
|
|
1560
|
+
cleanupProjectSkillsByInventory.set(inventory, projectSkills);
|
|
1561
|
+
}
|
|
1562
|
+
const selectors = options.pluginNames.map(parsePluginId);
|
|
1563
|
+
const selectedProjectSkills = options.all === true
|
|
1564
|
+
? projectSkills
|
|
1565
|
+
: projectSkills.filter((reference) => selectors.some((selector) => selector.pluginName === reference.pluginName
|
|
1566
|
+
&& (selector.sourceId === undefined || selector.sourceId === reference.sourceId)));
|
|
1567
|
+
return {
|
|
1568
|
+
...inventory,
|
|
1569
|
+
references: [...new Map([...inventory.references, ...selectedProjectSkills].map((reference) => [[
|
|
1570
|
+
reference.kind,
|
|
1571
|
+
reference.location,
|
|
1572
|
+
reference.sourceId ?? '',
|
|
1573
|
+
reference.pluginName ?? '',
|
|
1574
|
+
reference.platform ?? '',
|
|
1575
|
+
reference.scope ?? '',
|
|
1576
|
+
].join('\0'), reference])).values()],
|
|
1577
|
+
};
|
|
1578
|
+
});
|
|
1251
1579
|
return ordinaryInventory;
|
|
1252
1580
|
};
|
|
1253
1581
|
let requestedPluginNames = options.pluginNames;
|
|
1254
1582
|
const cleanupForce = options.command === 'cleanup'
|
|
1255
1583
|
&& options.commandOptions.force === true;
|
|
1584
|
+
const cleanupIntentBefore = options.command === 'cleanup'
|
|
1585
|
+
? await readPluginIntentState({
|
|
1586
|
+
scope: options.commandOptions.scope,
|
|
1587
|
+
cwd: options.commandOptions.cwd,
|
|
1588
|
+
env: runtimeEnv,
|
|
1589
|
+
})
|
|
1590
|
+
: undefined;
|
|
1256
1591
|
const expectedOfflineCleanup = options.runtime?.expectedOfflineCleanupEvidence;
|
|
1257
1592
|
let cleanupInventory;
|
|
1258
1593
|
let cleanupLiveTargets = [];
|
|
@@ -1269,7 +1604,9 @@ async function runRebuildCommandInSession(options) {
|
|
|
1269
1604
|
});
|
|
1270
1605
|
requestedPluginNames = live.selectors;
|
|
1271
1606
|
cleanupLiveTargets = live.targets;
|
|
1272
|
-
if (expectedOfflineCleanup
|
|
1607
|
+
if (expectedOfflineCleanup
|
|
1608
|
+
&& expectedOfflineCleanup.forceExclusive !== true
|
|
1609
|
+
&& expectedOfflineCleanup.plan.actions.length > 0) {
|
|
1273
1610
|
const liveTarget = cleanupLiveTargets.find((target) => target.pluginId === expectedOfflineCleanup.pluginId
|
|
1274
1611
|
&& target.pluginName === expectedOfflineCleanup.pluginName
|
|
1275
1612
|
&& target.sourceId === expectedOfflineCleanup.sourceId
|
|
@@ -1338,17 +1675,96 @@ async function runRebuildCommandInSession(options) {
|
|
|
1338
1675
|
const pluginId = formatPluginId({ sourceId: reference.sourceId, pluginName: reference.pluginName });
|
|
1339
1676
|
identities.add(pluginId);
|
|
1340
1677
|
}
|
|
1678
|
+
if (options.command === 'cleanup') {
|
|
1679
|
+
for (const root of cleanupIntentBefore.roots)
|
|
1680
|
+
identities.add(root.pluginId);
|
|
1681
|
+
}
|
|
1341
1682
|
requestedPluginNames = [...identities];
|
|
1342
1683
|
}
|
|
1684
|
+
let cleanupTargets = [];
|
|
1685
|
+
let cleanupIntentAfter;
|
|
1686
|
+
let cleanupSelectionErrors = [];
|
|
1687
|
+
if (options.command === 'cleanup') {
|
|
1688
|
+
const primarySourceId = cleanupForce
|
|
1689
|
+
? await readCatalogSourcesConfigSnapshot(runtimeEnv)
|
|
1690
|
+
.then((snapshot) => snapshot.config.sources.find((source) => source.primary)?.id)
|
|
1691
|
+
.catch(() => undefined)
|
|
1692
|
+
: undefined;
|
|
1693
|
+
const intentTargets = cleanupIntentTargets({
|
|
1694
|
+
state: cleanupIntentBefore,
|
|
1695
|
+
selectors: options.pluginNames,
|
|
1696
|
+
platforms: selectedPlatforms(options.commandOptions.platform),
|
|
1697
|
+
scope: options.commandOptions.scope,
|
|
1698
|
+
all: options.all === true,
|
|
1699
|
+
liveTargets: cleanupLiveTargets,
|
|
1700
|
+
primarySourceId,
|
|
1701
|
+
});
|
|
1702
|
+
const damagedTargets = options.pluginNames.flatMap((selector) => {
|
|
1703
|
+
const parsed = parsePluginId(selector);
|
|
1704
|
+
if (!parsed.sourceId)
|
|
1705
|
+
return [];
|
|
1706
|
+
return selectedPlatforms(options.commandOptions.platform).flatMap((platform) => cleanupInventory.errors.some((error) => error.relevance.global || ((error.relevance.sourceId === undefined || error.relevance.sourceId === parsed.sourceId)
|
|
1707
|
+
&& (error.relevance.pluginName === undefined || error.relevance.pluginName === parsed.pluginName)
|
|
1708
|
+
&& (error.relevance.platform === undefined || error.relevance.platform === platform)
|
|
1709
|
+
&& (error.relevance.scope === undefined || error.relevance.scope === options.commandOptions.scope))) ? [{
|
|
1710
|
+
pluginId: formatPluginId(parsed),
|
|
1711
|
+
pluginName: parsed.pluginName,
|
|
1712
|
+
sourceId: parsed.sourceId,
|
|
1713
|
+
sourceIdentity: 'unavailable',
|
|
1714
|
+
platform,
|
|
1715
|
+
scope: options.commandOptions.scope,
|
|
1716
|
+
}] : []);
|
|
1717
|
+
});
|
|
1718
|
+
cleanupTargets = [...new Map([...damagedTargets, ...intentTargets, ...cleanupLiveTargets].map((target) => [
|
|
1719
|
+
`${target.pluginId}\0${target.platform}\0${target.scope}`,
|
|
1720
|
+
target,
|
|
1721
|
+
])).values()];
|
|
1722
|
+
requestedPluginNames = [...new Set([
|
|
1723
|
+
...requestedPluginNames,
|
|
1724
|
+
...cleanupTargets.map((target) => target.pluginId),
|
|
1725
|
+
])];
|
|
1726
|
+
cleanupIntentAfter = deriveDesiredIntentState({
|
|
1727
|
+
command: 'cleanup',
|
|
1728
|
+
state: cleanupIntentBefore,
|
|
1729
|
+
selectedRoots: cleanupSelectedRoots(cleanupTargets),
|
|
1730
|
+
plan: { requestedPluginIds: [], resolvedPluginIds: [], dependencyEdges: [], targets: [], scopeOverrides: [] },
|
|
1731
|
+
});
|
|
1732
|
+
const filtered = omitSelectedPluginConfigErrors(cleanupInventory, cleanupTargets);
|
|
1733
|
+
cleanupInventory = filtered.inventory;
|
|
1734
|
+
malformedSelectedConfigPreserved ||= filtered.omitted;
|
|
1735
|
+
cleanupSelectionErrors = cleanupInventoryErrorsForSelection({
|
|
1736
|
+
inventory: cleanupInventory,
|
|
1737
|
+
selectors: options.pluginNames,
|
|
1738
|
+
platforms: selectedPlatforms(options.commandOptions.platform),
|
|
1739
|
+
scope: options.commandOptions.scope,
|
|
1740
|
+
all: options.all === true,
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1343
1743
|
const selectors = requestedPluginNames.map(parsePluginId);
|
|
1344
1744
|
const sourceIds = planPluginSelectorSourceIds(selectors);
|
|
1345
|
-
if (options.all && selectors.length === 0) {
|
|
1745
|
+
if (options.all && selectors.length === 0 && options.command !== 'cleanup') {
|
|
1346
1746
|
if (legacyLiveTargets.length > 0) {
|
|
1347
1747
|
throw namespacedPreflightError(`legacyStateUnsupported: legacy live runtime state exists for ${legacyLiveTargets.map((target) => `${target.pluginName}:${target.platform}/${target.scope}`).join(', ')}.`, [], legacyLiveTargets);
|
|
1348
1748
|
}
|
|
1349
1749
|
return mergeRebuildCommandResults([]);
|
|
1350
1750
|
}
|
|
1351
|
-
if (options.command === 'cleanup'
|
|
1751
|
+
if (options.command === 'cleanup') {
|
|
1752
|
+
if (!cleanupForce
|
|
1753
|
+
&& cleanupTargets.length === 0
|
|
1754
|
+
&& cleanupSelectionErrors.length === 0
|
|
1755
|
+
&& legacyLiveTargets.length === 0) {
|
|
1756
|
+
const result = mergeRebuildCommandResults([]);
|
|
1757
|
+
const operationPlan = createOfflineCleanupOperationPlan([], {
|
|
1758
|
+
scope: options.commandOptions.scope,
|
|
1759
|
+
before: cleanupIntentBefore,
|
|
1760
|
+
after: cleanupIntentAfter,
|
|
1761
|
+
}, cleanupInventoryWarnings(cleanupInventory, []));
|
|
1762
|
+
if (options.runtime?.expectedPlanFingerprint) {
|
|
1763
|
+
assertOperationPlanFingerprint(options.runtime.expectedPlanFingerprint, operationPlan);
|
|
1764
|
+
}
|
|
1765
|
+
result.operationPlan = operationPlan;
|
|
1766
|
+
return result;
|
|
1767
|
+
}
|
|
1352
1768
|
if (cleanupForce) {
|
|
1353
1769
|
const requestedPlatforms = selectedPlatforms(options.commandOptions.platform);
|
|
1354
1770
|
const cleanupSourceSnapshot = await readCatalogSourcesConfigSnapshot(runtimeEnv).catch(() => undefined);
|
|
@@ -1379,18 +1795,27 @@ async function runRebuildCommandInSession(options) {
|
|
|
1379
1795
|
&& (error.relevance.scope === undefined || error.relevance.scope === options.commandOptions.scope));
|
|
1380
1796
|
return missingLiveTarget || damagedExclusiveEvidence;
|
|
1381
1797
|
});
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1798
|
+
const supplementAttempts = await Promise.allSettled(selectorsNeedingSupplement.map((selector) => buildForceExclusiveCleanupPlan({
|
|
1799
|
+
selectors: [selector],
|
|
1800
|
+
platforms: requestedPlatforms,
|
|
1801
|
+
scope: options.commandOptions.scope,
|
|
1802
|
+
cwd: options.commandOptions.cwd,
|
|
1803
|
+
env: runtimeEnv,
|
|
1804
|
+
primarySourceId: primarySource,
|
|
1805
|
+
damagedOnly: cleanupLiveTargets.length > 0,
|
|
1806
|
+
})));
|
|
1807
|
+
const forcePlan = await mergeForceCleanupPlans(livePlan, ...supplementAttempts.flatMap((attempt) => attempt.status === 'fulfilled' ? [attempt.value] : []));
|
|
1808
|
+
const forceCleanupTargets = [...new Map([
|
|
1809
|
+
...cleanupTargets,
|
|
1810
|
+
...forcePlan.targets.map((target) => ({
|
|
1811
|
+
pluginId: target.identity.pluginId,
|
|
1812
|
+
pluginName: target.identity.pluginName,
|
|
1813
|
+
sourceId: target.identity.sourceId,
|
|
1814
|
+
sourceIdentity: target.identity.sourceIdentity,
|
|
1815
|
+
platform: target.identity.platform,
|
|
1816
|
+
scope: target.identity.scope,
|
|
1817
|
+
})),
|
|
1818
|
+
].map((target) => [`${target.pluginId}\0${target.platform}\0${target.scope}`, target])).values()];
|
|
1394
1819
|
const requestedTargets = requestedPluginNames.flatMap((selector) => {
|
|
1395
1820
|
const parsed = parsePluginId(selector);
|
|
1396
1821
|
const sourceId = parsed.sourceId ?? primarySource;
|
|
@@ -1405,7 +1830,11 @@ async function runRebuildCommandInSession(options) {
|
|
|
1405
1830
|
});
|
|
1406
1831
|
const unresolvedTargets = requestedTargets.filter((requested) => !forcePlan.targets.some((target) => target.identity.pluginId === requested.pluginId
|
|
1407
1832
|
&& target.identity.platform === requested.platform
|
|
1408
|
-
&& target.identity.scope === requested.scope)
|
|
1833
|
+
&& target.identity.scope === requested.scope)
|
|
1834
|
+
&& cleanupInventory.errors.some((error) => error.relevance.global || ((error.relevance.sourceId === undefined || error.relevance.sourceId === requested.sourceId)
|
|
1835
|
+
&& (error.relevance.pluginName === undefined || error.relevance.pluginName === requested.pluginName)
|
|
1836
|
+
&& (error.relevance.platform === undefined || error.relevance.platform === requested.platform)
|
|
1837
|
+
&& (error.relevance.scope === undefined || error.relevance.scope === requested.scope))));
|
|
1409
1838
|
if (expectedOfflineCleanup?.forceExclusive === true) {
|
|
1410
1839
|
const actual = forcePlan.evidence.find((candidate) => candidate.pluginId === expectedOfflineCleanup.pluginId
|
|
1411
1840
|
&& candidate.platform === expectedOfflineCleanup.platform
|
|
@@ -1414,21 +1843,23 @@ async function runRebuildCommandInSession(options) {
|
|
|
1414
1843
|
throw namespacedPreflightError('operationReviewStale: force cleanup path or ownership evidence changed before mutation.', cleanupLiveTargets);
|
|
1415
1844
|
}
|
|
1416
1845
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1846
|
+
const forceEvidence = forceCleanupTargets.map((target) => forcePlan.evidence.find((candidate) => candidate.pluginId === target.pluginId
|
|
1847
|
+
&& candidate.platform === target.platform
|
|
1848
|
+
&& candidate.scope === target.scope)
|
|
1849
|
+
?? intentOnlyOfflineCleanupEvidence(target));
|
|
1850
|
+
const operationPlan = createOfflineCleanupOperationPlan(forceEvidence, {
|
|
1851
|
+
scope: options.commandOptions.scope,
|
|
1852
|
+
before: cleanupIntentBefore,
|
|
1853
|
+
after: cleanupIntentAfter,
|
|
1854
|
+
}, cleanupInventoryWarnings(cleanupInventory, forceCleanupTargets));
|
|
1424
1855
|
if (options.runtime?.expectedPlanFingerprint) {
|
|
1425
1856
|
assertOperationPlanFingerprint(options.runtime.expectedPlanFingerprint, operationPlan);
|
|
1426
1857
|
}
|
|
1427
1858
|
if (options.runtime?.previewOperationPlan) {
|
|
1428
1859
|
return {
|
|
1429
1860
|
...await previewOfflineOwnedComponents({
|
|
1430
|
-
targets:
|
|
1431
|
-
evidence:
|
|
1861
|
+
targets: forceCleanupTargets,
|
|
1862
|
+
evidence: forceEvidence,
|
|
1432
1863
|
cwd: options.commandOptions.cwd,
|
|
1433
1864
|
env: runtimeEnv,
|
|
1434
1865
|
onPreviewStage: options.runtime.onPreviewStage,
|
|
@@ -1436,6 +1867,14 @@ async function runRebuildCommandInSession(options) {
|
|
|
1436
1867
|
operationPlan,
|
|
1437
1868
|
};
|
|
1438
1869
|
}
|
|
1870
|
+
if (calculatePluginIntentDigest(cleanupIntentBefore)
|
|
1871
|
+
!== calculatePluginIntentDigest(cleanupIntentAfter)) {
|
|
1872
|
+
await (options.runtime?.writePluginIntentState ?? writePluginIntentState)({
|
|
1873
|
+
scope: options.commandOptions.scope,
|
|
1874
|
+
cwd: options.commandOptions.cwd,
|
|
1875
|
+
env: runtimeEnv,
|
|
1876
|
+
}, cleanupIntentAfter);
|
|
1877
|
+
}
|
|
1439
1878
|
const forceResults = await Promise.all(forcePlan.targets.map(async (target) => {
|
|
1440
1879
|
const integration = createRebuildIntegration({
|
|
1441
1880
|
context: {
|
|
@@ -1446,86 +1885,89 @@ async function runRebuildCommandInSession(options) {
|
|
|
1446
1885
|
},
|
|
1447
1886
|
});
|
|
1448
1887
|
const { finalizeCleanup: _finalizeCleanup, ...pathOnlyDriver } = integration.driver;
|
|
1449
|
-
const result = await
|
|
1888
|
+
const result = await (options.runtime?.executeCleanupOperation ?? executeRebuildOperation)({
|
|
1889
|
+
command: 'cleanup', target, driver: pathOnlyDriver,
|
|
1890
|
+
});
|
|
1450
1891
|
if (cleanupLiveTargets.some((liveTarget) => liveTarget.marketplaceIdentityUnresolved
|
|
1451
1892
|
&& liveTarget.pluginId === result.target.pluginId
|
|
1452
1893
|
&& liveTarget.platform === result.target.platform
|
|
1453
1894
|
&& liveTarget.scope === result.target.scope)) {
|
|
1454
1895
|
result.residual.push('nativeMarketplaceIdentityUnresolved');
|
|
1455
1896
|
}
|
|
1897
|
+
const targetEvidence = forceEvidence.find((candidate) => candidate.pluginId === target.identity.pluginId
|
|
1898
|
+
&& candidate.platform === target.identity.platform
|
|
1899
|
+
&& candidate.scope === target.identity.scope);
|
|
1900
|
+
const targetDescriptor = forceCleanupTargets.find((candidate) => candidate.pluginId === target.identity.pluginId
|
|
1901
|
+
&& candidate.platform === target.identity.platform
|
|
1902
|
+
&& candidate.scope === target.identity.scope);
|
|
1903
|
+
const unresolvedErrors = cleanupInventoryErrorsForTarget(cleanupInventory, targetDescriptor).filter((error) => !cleanupActionCoversLocation(targetEvidence, error.location, runtimeEnv, options.commandOptions.cwd));
|
|
1904
|
+
if (unresolvedErrors.length > 0) {
|
|
1905
|
+
const message = 'stateInventoryResidual: Some weak state could not be verified after force cleanup.';
|
|
1906
|
+
result.residual = [...new Set([
|
|
1907
|
+
...result.residual,
|
|
1908
|
+
...unresolvedErrors.map((error) => error.location),
|
|
1909
|
+
])];
|
|
1910
|
+
if (result.status === 'succeeded') {
|
|
1911
|
+
return {
|
|
1912
|
+
...result,
|
|
1913
|
+
status: 'failed',
|
|
1914
|
+
failures: [...result.failures, { phase: 'cleanup.owned', message }],
|
|
1915
|
+
error: { phase: 'cleanup.owned', message },
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1456
1919
|
return result;
|
|
1457
1920
|
}));
|
|
1921
|
+
const unresolvedResults = unresolvedTargets.map((target) => failedResult('cleanup', { ...target, pluginVersion: '0.0.0', cwd: options.commandOptions.cwd }, 'forceCleanupPathUnavailable: No exact exclusive output is available from live evidence or the local Active shallow plan. Restore the local Active bundle, then retry cleanup --force, or inspect and remove the exact output manually.'));
|
|
1922
|
+
const intentOnlyResults = forceCleanupTargets
|
|
1923
|
+
.filter((target) => !forcePlan.targets.some((forceTarget) => forceTarget.identity.pluginId === target.pluginId
|
|
1924
|
+
&& forceTarget.identity.platform === target.platform
|
|
1925
|
+
&& forceTarget.identity.scope === target.scope)
|
|
1926
|
+
&& !unresolvedTargets.some((unresolved) => unresolved.pluginId === target.pluginId
|
|
1927
|
+
&& unresolved.platform === target.platform
|
|
1928
|
+
&& unresolved.scope === target.scope))
|
|
1929
|
+
.map(cleanupIntentResult);
|
|
1930
|
+
const allResults = [...forceResults, ...unresolvedResults, ...intentOnlyResults];
|
|
1458
1931
|
const warnings = cleanupLiveTargets.flatMap((target) => target.marketplaceIdentityUnresolved
|
|
1459
1932
|
? [`nativeMarketplaceIdentityUnresolved: Preserved ambiguous native registration for ${target.pluginId} on ${target.platform}/${target.scope}.`]
|
|
1460
1933
|
: []);
|
|
1461
1934
|
return leaseSafeRebuildResult({
|
|
1462
|
-
platforms: [...new Set(
|
|
1463
|
-
plugins:
|
|
1935
|
+
platforms: [...new Set(allResults.map((entry) => entry.target.platform))],
|
|
1936
|
+
plugins: allResults.filter((entry) => entry.status === 'succeeded')
|
|
1464
1937
|
.map((entry) => entry.target.pluginName),
|
|
1465
1938
|
warnings,
|
|
1466
|
-
rebuildResults:
|
|
1467
|
-
targetResults:
|
|
1468
|
-
overallState:
|
|
1939
|
+
rebuildResults: allResults,
|
|
1940
|
+
targetResults: allResults.map(toTargetResult),
|
|
1941
|
+
overallState: allResults.every((entry) => entry.status === 'succeeded')
|
|
1469
1942
|
? 'succeeded'
|
|
1470
|
-
:
|
|
1471
|
-
|
|
1472
|
-
|
|
1943
|
+
: operationPlan.intentChanges.length > 0
|
|
1944
|
+
|| allResults.some((entry) => entry.status === 'succeeded'
|
|
1945
|
+
|| entry.actions.some((action) => action.status === 'succeeded'))
|
|
1946
|
+
? 'partial' : 'failed',
|
|
1947
|
+
receiptTargets: allResults.map((entry) => entry.target),
|
|
1948
|
+
offlineCleanupPlans: forceEvidence,
|
|
1473
1949
|
operationPlan,
|
|
1474
1950
|
});
|
|
1475
1951
|
}
|
|
1476
|
-
if (
|
|
1477
|
-
const requestedPlatforms = selectedPlatforms(options.commandOptions.platform);
|
|
1478
|
-
const primarySource = await readCatalogSourcesConfigSnapshot(runtimeEnv)
|
|
1479
|
-
.then((snapshot) => snapshot.config.sources.find((source) => source.primary)?.id)
|
|
1480
|
-
.catch(() => undefined);
|
|
1481
|
-
const unresolvedTargets = requestedPluginNames.flatMap((selector) => {
|
|
1482
|
-
const parsed = parsePluginId(selector);
|
|
1483
|
-
const sourceId = parsed.sourceId ?? primarySource;
|
|
1484
|
-
return sourceId ? requestedPlatforms.map((platform) => ({
|
|
1485
|
-
pluginId: formatPluginId({ sourceId, pluginName: parsed.pluginName }),
|
|
1486
|
-
pluginName: parsed.pluginName,
|
|
1487
|
-
sourceId,
|
|
1488
|
-
sourceIdentity: 'unavailable',
|
|
1489
|
-
platform,
|
|
1490
|
-
scope: options.commandOptions.scope,
|
|
1491
|
-
})) : [];
|
|
1492
|
-
});
|
|
1493
|
-
const qualifiedTargets = requestedPluginNames.flatMap((selector) => {
|
|
1494
|
-
const parsed = parsePluginId(selector);
|
|
1495
|
-
return parsed.sourceId ? [{ sourceId: parsed.sourceId, pluginName: parsed.pluginName }] : [];
|
|
1496
|
-
});
|
|
1497
|
-
const filtered = cleanupInventory && qualifiedTargets.length > 0
|
|
1498
|
-
? omitSelectedPluginConfigErrors(cleanupInventory, qualifiedTargets)
|
|
1499
|
-
: { inventory: cleanupInventory, omitted: false };
|
|
1500
|
-
malformedSelectedConfigPreserved ||= filtered.omitted;
|
|
1501
|
-
const inventoryError = filtered.inventory?.errors.find((error) => {
|
|
1502
|
-
if (!cleanupForce
|
|
1503
|
-
|| (error.kind !== 'ownerMarkerRead' && error.kind !== 'ownerMarkerValidation')
|
|
1504
|
-
|| error.relevance.global)
|
|
1505
|
-
return true;
|
|
1506
|
-
const relevant = unresolvedTargets.some((target) => !error.relevance.global
|
|
1507
|
-
&& (error.relevance.sourceId === undefined || error.relevance.sourceId === target.sourceId)
|
|
1508
|
-
&& (error.relevance.pluginName === undefined || error.relevance.pluginName === target.pluginName)
|
|
1509
|
-
&& (error.relevance.platform === undefined || error.relevance.platform === target.platform)
|
|
1510
|
-
&& (error.relevance.scope === undefined || error.relevance.scope === target.scope));
|
|
1511
|
-
return relevant
|
|
1512
|
-
&& !(error.relevance.platform === 'codex' && error.relevance.scope === 'project');
|
|
1513
|
-
});
|
|
1514
|
-
if (inventoryError) {
|
|
1515
|
-
throw namespacedPreflightError(`stateInventoryFailed: ${inventoryError.message}`, unresolvedTargets);
|
|
1516
|
-
}
|
|
1952
|
+
if (cleanupTargets.length === 0) {
|
|
1517
1953
|
const result = mergeRebuildCommandResults([]);
|
|
1954
|
+
const operationPlan = createOfflineCleanupOperationPlan([], {
|
|
1955
|
+
scope: options.commandOptions.scope,
|
|
1956
|
+
before: cleanupIntentBefore,
|
|
1957
|
+
after: cleanupIntentAfter,
|
|
1958
|
+
}, cleanupInventoryWarnings(cleanupInventory, []));
|
|
1959
|
+
if (options.runtime?.expectedPlanFingerprint) {
|
|
1960
|
+
assertOperationPlanFingerprint(options.runtime.expectedPlanFingerprint, operationPlan);
|
|
1961
|
+
}
|
|
1962
|
+
result.operationPlan = operationPlan;
|
|
1963
|
+
result.overallState = cleanupSelectionErrors.length > 0 || legacyLiveTargets.length > 0
|
|
1964
|
+
? 'failed'
|
|
1965
|
+
: 'succeeded';
|
|
1966
|
+
result.warnings.push(...cleanupSelectionErrors.map((error) => `stateInventoryResidual: ${error.kind}: ${error.message}`));
|
|
1967
|
+
result.warnings.push(...legacyLiveTargets.map((target) => `legacyStateUnsupported: legacy live runtime state exists for ${target.pluginName}:${target.platform}/${target.scope}.`));
|
|
1518
1968
|
if (malformedSelectedConfigPreserved) {
|
|
1519
1969
|
result.warnings.push(MALFORMED_CONFIG_PRESERVED_WARNING);
|
|
1520
1970
|
}
|
|
1521
|
-
if (cleanupForce && !options.runtime?.previewOperationPlan) {
|
|
1522
|
-
try {
|
|
1523
|
-
await (options.runtime?.removeObsoleteInstallationContracts ?? fs.remove)(path.join(resolveAwHome(runtimeEnv), 'installation-contracts'));
|
|
1524
|
-
}
|
|
1525
|
-
catch {
|
|
1526
|
-
result.warnings.push(OBSOLETE_CONTRACTS_CLEANUP_WARNING);
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
1971
|
return result;
|
|
1530
1972
|
}
|
|
1531
1973
|
const cleanupSourceSnapshot = await readCatalogSourcesConfigSnapshot(runtimeEnv).catch(() => undefined);
|
|
@@ -1545,19 +1987,37 @@ async function runRebuildCommandInSession(options) {
|
|
|
1545
1987
|
});
|
|
1546
1988
|
return value ? [value] : [];
|
|
1547
1989
|
});
|
|
1990
|
+
evidence = cleanupTargets.map((target) => evidence.find((candidate) => candidate.pluginId === target.pluginId
|
|
1991
|
+
&& candidate.platform === target.platform
|
|
1992
|
+
&& candidate.scope === target.scope)
|
|
1993
|
+
?? intentOnlyOfflineCleanupEvidence(target));
|
|
1994
|
+
const operationPlan = createOfflineCleanupOperationPlan(evidence, {
|
|
1995
|
+
scope: options.commandOptions.scope,
|
|
1996
|
+
before: cleanupIntentBefore,
|
|
1997
|
+
after: cleanupIntentAfter,
|
|
1998
|
+
}, cleanupInventoryWarnings(cleanupInventory, cleanupTargets));
|
|
1548
1999
|
if (options.runtime?.expectedPlanFingerprint) {
|
|
1549
|
-
assertOperationPlanFingerprint(options.runtime.expectedPlanFingerprint,
|
|
2000
|
+
assertOperationPlanFingerprint(options.runtime.expectedPlanFingerprint, operationPlan);
|
|
1550
2001
|
}
|
|
1551
2002
|
if (options.runtime?.previewOperationPlan) {
|
|
1552
2003
|
const previewResult = await previewOfflineOwnedComponents({
|
|
1553
|
-
targets:
|
|
2004
|
+
targets: cleanupTargets,
|
|
1554
2005
|
evidence,
|
|
1555
2006
|
cwd: options.commandOptions.cwd,
|
|
1556
2007
|
env: runtimeEnv,
|
|
1557
2008
|
onPreviewStage: options.runtime.onPreviewStage,
|
|
1558
2009
|
});
|
|
2010
|
+
previewResult.operationPlan = operationPlan;
|
|
1559
2011
|
return previewResult;
|
|
1560
2012
|
}
|
|
2013
|
+
if (calculatePluginIntentDigest(cleanupIntentBefore)
|
|
2014
|
+
!== calculatePluginIntentDigest(cleanupIntentAfter)) {
|
|
2015
|
+
await (options.runtime?.writePluginIntentState ?? writePluginIntentState)({
|
|
2016
|
+
scope: options.commandOptions.scope,
|
|
2017
|
+
cwd: options.commandOptions.cwd,
|
|
2018
|
+
env: runtimeEnv,
|
|
2019
|
+
}, cleanupIntentAfter);
|
|
2020
|
+
}
|
|
1561
2021
|
const cleanupResult = await cleanupOfflineOwnedComponents({
|
|
1562
2022
|
targets: cleanupLiveTargets,
|
|
1563
2023
|
evidence,
|
|
@@ -1567,7 +2027,32 @@ async function runRebuildCommandInSession(options) {
|
|
|
1567
2027
|
removeObsoleteInstallationContracts: options.runtime?.removeObsoleteInstallationContracts,
|
|
1568
2028
|
nativeCliRunner: options.runtime?.nativeCliRunner,
|
|
1569
2029
|
codexManager: options.runtime?.codexManager,
|
|
2030
|
+
executeCleanupOperation: options.runtime?.executeCleanupOperation,
|
|
2031
|
+
});
|
|
2032
|
+
const liveKeys = new Set(cleanupLiveTargets.map((target) => `${target.pluginId}\0${target.platform}\0${target.scope}`));
|
|
2033
|
+
const intentOnlyResults = cleanupTargets
|
|
2034
|
+
.filter((target) => !liveKeys.has(`${target.pluginId}\0${target.platform}\0${target.scope}`))
|
|
2035
|
+
.map((target) => {
|
|
2036
|
+
const inventoryError = cleanupInventory.errors.find((error) => error.relevance.global || ((error.relevance.sourceId === undefined || error.relevance.sourceId === target.sourceId)
|
|
2037
|
+
&& (error.relevance.pluginName === undefined || error.relevance.pluginName === target.pluginName)
|
|
2038
|
+
&& (error.relevance.platform === undefined || error.relevance.platform === target.platform)
|
|
2039
|
+
&& (error.relevance.scope === undefined || error.relevance.scope === target.scope)));
|
|
2040
|
+
return inventoryError
|
|
2041
|
+
? failedResult('cleanup', { ...cleanupIntentResult(target).target, cwd: options.commandOptions.cwd }, `stateInventoryResidual: ${inventoryError.message}`)
|
|
2042
|
+
: cleanupIntentResult(target);
|
|
1570
2043
|
});
|
|
2044
|
+
cleanupResult.rebuildResults.push(...intentOnlyResults);
|
|
2045
|
+
cleanupResult.targetResults.push(...intentOnlyResults.map(toTargetResult));
|
|
2046
|
+
cleanupResult.receiptTargets.push(...intentOnlyResults.map((result) => result.target));
|
|
2047
|
+
cleanupResult.platforms = [...new Set([...cleanupResult.platforms, ...intentOnlyResults.map((result) => result.target.platform)])];
|
|
2048
|
+
cleanupResult.plugins = [...new Set([...cleanupResult.plugins, ...intentOnlyResults.map((result) => result.target.pluginName)])];
|
|
2049
|
+
cleanupResult.operationPlan = operationPlan;
|
|
2050
|
+
if (cleanupResult.overallState === 'failed'
|
|
2051
|
+
&& operationPlan.intentChanges.length > 0)
|
|
2052
|
+
cleanupResult.overallState = 'partial';
|
|
2053
|
+
else if (intentOnlyResults.some((result) => result.status === 'failed')) {
|
|
2054
|
+
cleanupResult.overallState = operationPlan.intentChanges.length > 0 ? 'partial' : 'failed';
|
|
2055
|
+
}
|
|
1571
2056
|
if (malformedSelectedConfigPreserved) {
|
|
1572
2057
|
cleanupResult.warnings.push(MALFORMED_CONFIG_PRESERVED_WARNING);
|
|
1573
2058
|
}
|