@lesliechan721/aw 0.1.0-beta.25 → 0.1.0-beta.27
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/portable/commands.d.ts +39 -5
- package/dist/src/portable/commands.js +249 -30
- package/dist/src/portable/commands.js.map +1 -1
- package/dist/src/portable/skills-cli.d.ts +15 -7
- package/dist/src/portable/skills-cli.js +35 -19
- package/dist/src/portable/skills-cli.js.map +1 -1
- package/dist/src/tui/catalog-sources/session.js +9 -0
- package/dist/src/tui/catalog-sources/session.js.map +1 -1
- package/dist/src/tui/catalog-store-task.d.ts +16 -0
- package/dist/src/tui/catalog-store-task.js +19 -0
- package/dist/src/tui/catalog-store-task.js.map +1 -0
- package/dist/src/tui/components.d.ts +15 -0
- package/dist/src/tui/components.js +24 -3
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/console-app-v4.d.ts +2 -1
- package/dist/src/tui/console-app-v4.js +2147 -1476
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.js +396 -16
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +131 -41
- package/dist/src/tui/controller.js +493 -33
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/dependency-graph.d.ts +11 -0
- package/dist/src/tui/dependency-graph.js +19 -0
- package/dist/src/tui/dependency-graph.js.map +1 -1
- package/dist/src/tui/diagnostic-projection.js +83 -0
- package/dist/src/tui/diagnostic-projection.js.map +1 -1
- package/dist/src/tui/model.d.ts +4 -1
- package/dist/src/tui/model.js +18 -13
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-diagnostic-screens.js +3 -2
- package/dist/src/tui/operation-diagnostic-screens.js.map +1 -1
- package/dist/src/tui/operation-review-planner.js +95 -8
- package/dist/src/tui/operation-review-planner.js.map +1 -1
- package/dist/src/tui/plugin-content.d.ts +23 -0
- package/dist/src/tui/plugin-content.js +336 -0
- package/dist/src/tui/plugin-content.js.map +1 -0
- package/dist/src/tui/router.d.ts +27 -9
- package/dist/src/tui/router.js +13 -9
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/screen-registry.d.ts +81 -14
- package/dist/src/tui/screen-registry.js +46 -17
- 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.d.ts +1 -0
- package/dist/src/tui/session-host.js +13 -1
- package/dist/src/tui/session-host.js.map +1 -1
- package/dist/src/utils/codex-reconciler.js +5 -1
- package/dist/src/utils/codex-reconciler.js.map +1 -1
- package/dist/src/utils/codex-surface-analysis.d.ts +1 -0
- package/dist/src/utils/codex-surface-analysis.js +2 -0
- package/dist/src/utils/codex-surface-analysis.js.map +1 -1
- package/package.json +3 -3
|
@@ -11,7 +11,7 @@ import { readCatalogSourcesConfigSnapshot, recoverInvalidCatalogSourcesConfig, }
|
|
|
11
11
|
import { withResolvedCatalogPlugin, withResolvedMultiSourceCatalog, formatPluginId, parsePluginId, } from '../utils/multi-source-catalog.js';
|
|
12
12
|
import { coercePluginConfigUpdates, inspectPluginConfigResetTarget, listNamespacedPluginConfigStoreIdentities, readPluginConfigStore, resolveConfigRead, resolveEffectivePluginConfig, resolvePluginConfigStorePath, } from '../utils/plugin-config.js';
|
|
13
13
|
import { assertProjectSharedConfigPathVisible } from '../utils/project-boundary.js';
|
|
14
|
-
import { readPluginIntentState } from '../utils/plugin-intent-state.js';
|
|
14
|
+
import { calculatePluginIntentDigest, readPluginIntentState, } from '../utils/plugin-intent-state.js';
|
|
15
15
|
import { applyPluginConfigWrite, replayConfigRuntimeWrites, resolveConfigPreApplyAuthorization, resetPluginConfigWithHelper, } from '../utils/plugin-config-write.js';
|
|
16
16
|
import { analyzePluginConfigMutationImpact } from '../utils/plugin-config-impact.js';
|
|
17
17
|
import { createBatchIntentCompatiblePlanFingerprint } from '../commands/operation-plan.js';
|
|
@@ -24,6 +24,7 @@ import { projectCatalogSourceLocation } from '../utils/catalog-source-presentati
|
|
|
24
24
|
import { calculateCatalogSourceConfigFingerprint, calculateCatalogSourceIdentity, } from '../utils/catalog-source-identity.js';
|
|
25
25
|
import { checkAwCapabilitySupport, readPluginManifest } from '../utils/manifest.js';
|
|
26
26
|
import { withAwHomeMutationLock } from '../utils/aw-home-mutation-lock.js';
|
|
27
|
+
import { resolveCanonicalRepoProjectKey } from '../utils/state-paths.js';
|
|
27
28
|
import { IMPLEMENTED_AW_CAPABILITIES } from '../commands/aw-capabilities.js';
|
|
28
29
|
import { calculateCatalogGenerationSetDigest, resolveActiveCatalogGeneration, } from '../utils/catalog-generation-resolver.js';
|
|
29
30
|
import { resolveCatalogOperationContext, resolvePartialCatalogOperationContext, selectCatalogOperationContext, } from '../utils/catalog-operation-context.js';
|
|
@@ -34,8 +35,10 @@ import { readCatalogGenerationState } from '../utils/catalog-generation-state.js
|
|
|
34
35
|
import { CatalogGenerationError } from '../utils/catalog-generation-error.js';
|
|
35
36
|
import { CatalogProviderCompatibilityError } from '../utils/catalog-public-error.js';
|
|
36
37
|
import { loadCatalogGenerationViewModels } from './catalog-generation.js';
|
|
37
|
-
import { installPortableSkills, preparePortableInstall, PortableCommandError, } from '../portable/commands.js';
|
|
38
|
+
import { executePortableRetry, installPortableSkills, preparePortableInstall, preparePortableRetry, PortableCommandError, recheckPortableInstall, } from '../portable/commands.js';
|
|
38
39
|
import { MANAGED_CODEX_PACKAGE_NAME, MANAGED_CODEX_VERSION, } from '../utils/codex-manager.js';
|
|
40
|
+
import { projectPluginContentOverview, } from './plugin-content.js';
|
|
41
|
+
import { executeCatalogStoreTask, inspectCatalogStoreTask, prepareCatalogStoreTask, } from './catalog-store-task.js';
|
|
39
42
|
function reusableCandidateEvidenceIsComplete(candidate) {
|
|
40
43
|
const evidence = candidate.probe.candidate;
|
|
41
44
|
return [
|
|
@@ -151,6 +154,31 @@ function configIntentRequestsApply(intent) {
|
|
|
151
154
|
return Object.keys(intent.updates ?? {}).length > 0
|
|
152
155
|
|| (intent.removeKeys?.length ?? 0) > 0;
|
|
153
156
|
}
|
|
157
|
+
function configSaveBatchGroupIntent(intent, group) {
|
|
158
|
+
return {
|
|
159
|
+
kind: 'configSave',
|
|
160
|
+
pluginId: intent.pluginId,
|
|
161
|
+
scope: group.scope,
|
|
162
|
+
apply: intent.apply,
|
|
163
|
+
...(intent.scriptTimeoutMs === undefined ? {} : { scriptTimeoutMs: intent.scriptTimeoutMs }),
|
|
164
|
+
...(group.updates ? { updates: group.updates } : {}),
|
|
165
|
+
...(group.removeKeys ? { removeKeys: group.removeKeys } : {}),
|
|
166
|
+
...(group.sensitiveKeys ? { sensitiveKeys: group.sensitiveKeys } : {}),
|
|
167
|
+
...(group.privateKeys ? { privateKeys: group.privateKeys } : {}),
|
|
168
|
+
...(group.changes ? { changes: group.changes } : {}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function assertConfigSaveBatchIntent(intent) {
|
|
172
|
+
const order = { project: 0, user: 1 };
|
|
173
|
+
if (intent.groups.length === 0 || intent.groups.length > 2
|
|
174
|
+
|| new Set(intent.groups.map((group) => group.scope)).size !== intent.groups.length
|
|
175
|
+
|| intent.groups.some((group, index) => index > 0
|
|
176
|
+
&& order[intent.groups[index - 1].scope] >= order[group.scope])
|
|
177
|
+
|| intent.groups.some((group) => Object.keys(group.updates ?? {}).length === 0
|
|
178
|
+
&& (group.removeKeys?.length ?? 0) === 0)) {
|
|
179
|
+
throw new TuiOperationBlockedError('configBatchInvalid', '设置批次必须包含按当前项目、个人默认排序的唯一非空目标。');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
154
182
|
function operationReviewStale(message, diagnostics) {
|
|
155
183
|
return new TuiOperationBlockedError('operationReviewStale', `operationReviewStale: ${message}`, diagnostics);
|
|
156
184
|
}
|
|
@@ -170,6 +198,20 @@ function sourceEvidenceForPlugin(source, pluginName) {
|
|
|
170
198
|
channel: source.manifest.channel,
|
|
171
199
|
};
|
|
172
200
|
}
|
|
201
|
+
async function configLayerStoreDigest(options) {
|
|
202
|
+
const store = await readPluginConfigStore({
|
|
203
|
+
sourceId: options.source.sourceId,
|
|
204
|
+
sourceIdentity: options.source.sourceIdentity,
|
|
205
|
+
...('logicalIdentity' in options.source
|
|
206
|
+
? { logicalIdentity: options.source.logicalIdentity }
|
|
207
|
+
: {}),
|
|
208
|
+
pluginName: options.pluginName,
|
|
209
|
+
scope: options.scope,
|
|
210
|
+
cwd: options.cwd,
|
|
211
|
+
env: options.env,
|
|
212
|
+
});
|
|
213
|
+
return digest({ exists: store.exists, rawDigest: store.rawDigest });
|
|
214
|
+
}
|
|
173
215
|
async function inspectConfigShareability(options) {
|
|
174
216
|
if (options.scope !== 'project')
|
|
175
217
|
return undefined;
|
|
@@ -222,7 +264,7 @@ function reviewedScriptTimeoutMs(intent) {
|
|
|
222
264
|
|| intent.action === 'reinstall' || intent.action === 'sync')) {
|
|
223
265
|
return parseScriptTimeoutMs(intent.scriptTimeoutMs ?? SCRIPT_TIMEOUT_DEFAULT_MS);
|
|
224
266
|
}
|
|
225
|
-
if (intent.kind === 'configSave' && intent.apply) {
|
|
267
|
+
if ((intent.kind === 'configSave' || intent.kind === 'configSaveBatch') && intent.apply) {
|
|
226
268
|
return parseScriptTimeoutMs(intent.scriptTimeoutMs ?? SCRIPT_TIMEOUT_DEFAULT_MS);
|
|
227
269
|
}
|
|
228
270
|
return undefined;
|
|
@@ -292,6 +334,25 @@ async function resolveBatchGroups(intent, env, cwd, frozenRequestedPluginIds) {
|
|
|
292
334
|
throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目全部操作只支持 project scope sync。');
|
|
293
335
|
}
|
|
294
336
|
const state = await readPluginIntentState({ scope: 'project', cwd, env });
|
|
337
|
+
const projectIntentDigest = calculatePluginIntentDigest(state);
|
|
338
|
+
if (intent.selection.kind === 'currentProjectIntentTargets') {
|
|
339
|
+
if (intent.selection.targets.length === 0
|
|
340
|
+
|| intent.selection.targets.some((target) => target.scope !== 'project')) {
|
|
341
|
+
throw new TuiOperationBlockedError('batchTargetInvalid', '当前项目精确同步目标必须是非空 project targets。');
|
|
342
|
+
}
|
|
343
|
+
const exact = selectedBatchGroups({
|
|
344
|
+
...intent,
|
|
345
|
+
selection: { kind: 'targets', targets: intent.selection.targets },
|
|
346
|
+
});
|
|
347
|
+
const declaredPlatforms = new Map(state.roots.map((root) => [
|
|
348
|
+
root.pluginId,
|
|
349
|
+
new Set(root.platforms),
|
|
350
|
+
]));
|
|
351
|
+
if (intent.selection.targets.some((target) => !declaredPlatforms.get(target.pluginId)?.has(target.platform))) {
|
|
352
|
+
throw new TuiOperationBlockedError('batchTargetInvalid', '当前项目精确同步目标必须属于完整 project intent roots 声明。');
|
|
353
|
+
}
|
|
354
|
+
return { ...exact, projectIntentDigest };
|
|
355
|
+
}
|
|
295
356
|
const selection = resolveSyncIntentSelection({
|
|
296
357
|
roots: state.roots,
|
|
297
358
|
selectors: frozenRequestedPluginIds ?? [],
|
|
@@ -303,6 +364,7 @@ async function resolveBatchGroups(intent, env, cwd, frozenRequestedPluginIds) {
|
|
|
303
364
|
}
|
|
304
365
|
return {
|
|
305
366
|
requestedPluginIds: selection.requestedPluginIds,
|
|
367
|
+
projectIntentDigest,
|
|
306
368
|
groups: selection.groups.map((group) => ({
|
|
307
369
|
groupId: `${group.sourceId}\0${group.platform}\0${intent.scope}`,
|
|
308
370
|
sourceId: group.sourceId,
|
|
@@ -757,8 +819,15 @@ export function createTuiControllerDependencies(context, progressFeed, options =
|
|
|
757
819
|
}))).flat();
|
|
758
820
|
signal.throwIfAborted();
|
|
759
821
|
const statusById = new Map(result.payload.catalog.sources.map((source) => [source.id, source]));
|
|
822
|
+
const [projectRoot, projectIntentState] = await Promise.all([
|
|
823
|
+
resolveCanonicalRepoProjectKey(context.cwd),
|
|
824
|
+
readPluginIntentState({ scope: 'project', cwd: context.cwd, env: context.env }),
|
|
825
|
+
]);
|
|
826
|
+
signal.throwIfAborted();
|
|
760
827
|
return {
|
|
761
828
|
cliChannel: packageInfo.version.includes('-') ? 'beta' : 'latest',
|
|
829
|
+
projectRoot,
|
|
830
|
+
projectIntentRoots: structuredClone(projectIntentState.roots),
|
|
762
831
|
configuredSources: structuredClone(configured.sources),
|
|
763
832
|
payload: result.payload,
|
|
764
833
|
pluginSummaries: result.pluginSummaries,
|
|
@@ -797,8 +866,98 @@ export function createTuiControllerDependencies(context, progressFeed, options =
|
|
|
797
866
|
}
|
|
798
867
|
throw new TuiOperationBlockedError('operationReviewStale', '状态读取期间来源配置持续变化。请重试。');
|
|
799
868
|
};
|
|
869
|
+
const loadPluginContent = async (pluginId, signal) => {
|
|
870
|
+
signal.throwIfAborted();
|
|
871
|
+
try {
|
|
872
|
+
const packageInfo = await readCliPackageInfo();
|
|
873
|
+
const catalogOperation = sessionContextForPlugin(pluginId);
|
|
874
|
+
return await withResolvedCatalogPlugin({
|
|
875
|
+
selector: pluginId,
|
|
876
|
+
cliVersion: packageInfo.version,
|
|
877
|
+
env: context.env,
|
|
878
|
+
signal,
|
|
879
|
+
...(catalogOperation ? { operationContext: catalogOperation } : {}),
|
|
880
|
+
consume: ({ plugin, source }) => projectPluginContentOverview({
|
|
881
|
+
pluginId,
|
|
882
|
+
catalogVersion: source.manifest.catalogVersion,
|
|
883
|
+
plugin,
|
|
884
|
+
}),
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
catch {
|
|
888
|
+
signal.throwIfAborted();
|
|
889
|
+
const summary = sessionStatusResult?.pluginSummaries.find((entry) => entry.pluginId === pluginId);
|
|
890
|
+
let sourceId;
|
|
891
|
+
try {
|
|
892
|
+
sourceId = parsePluginId(pluginId).sourceId;
|
|
893
|
+
}
|
|
894
|
+
catch {
|
|
895
|
+
// The fixed unavailable result below does not repeat an unsafe selector.
|
|
896
|
+
}
|
|
897
|
+
const catalogVersion = sessionStatusResult?.payload.catalog.sources
|
|
898
|
+
.find((source) => source.id === sourceId)?.catalogVersion;
|
|
899
|
+
return {
|
|
900
|
+
pluginId,
|
|
901
|
+
pluginVersion: summary?.version ?? '待确认',
|
|
902
|
+
catalogVersion: catalogVersion ?? '待确认',
|
|
903
|
+
evidence: 'unavailable',
|
|
904
|
+
items: [],
|
|
905
|
+
notices: ['当前无法安全读取这个插件的目录内容。请检查来源状态后重试。'],
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
};
|
|
800
909
|
const captureEvidence = async (intent, signal, preparation, operationInventory = createOperationInventoryReader(), freshCatalog = false, reusableCandidate, codexObservationSession, frozenBatchEvidence) => {
|
|
801
910
|
reviewedScriptTimeoutMs(intent);
|
|
911
|
+
if (intent.kind === 'catalogStorePrune') {
|
|
912
|
+
signal?.throwIfAborted();
|
|
913
|
+
const config = await readCatalogSourcesConfigSnapshot(context.env);
|
|
914
|
+
const catalogStore = await prepareCatalogStoreTask({ env: context.env });
|
|
915
|
+
signal?.throwIfAborted();
|
|
916
|
+
return {
|
|
917
|
+
catalog: { configDigest: config.digest, compositeDigest: config.digest },
|
|
918
|
+
catalogStore,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
if (intent.kind === 'configSaveBatch') {
|
|
922
|
+
assertConfigSaveBatchIntent(intent);
|
|
923
|
+
const groupEvidence = [];
|
|
924
|
+
let batchBase;
|
|
925
|
+
let batchCatalogOperation;
|
|
926
|
+
for (const group of intent.groups) {
|
|
927
|
+
const current = await captureEvidence(configSaveBatchGroupIntent(intent, group), signal, preparation, operationInventory, freshCatalog, reusableCandidate, codexObservationSession);
|
|
928
|
+
if (!current.config || !current.source || !current.targetFingerprint) {
|
|
929
|
+
throw operationReviewStale('设置批次缺少单层证据。');
|
|
930
|
+
}
|
|
931
|
+
if (batchBase && (JSON.stringify(batchBase.catalog) !== JSON.stringify(current.catalog)
|
|
932
|
+
|| JSON.stringify(batchBase.source) !== JSON.stringify(current.source))) {
|
|
933
|
+
throw operationReviewStale('准备设置批次期间 Catalog 或来源发生变化。');
|
|
934
|
+
}
|
|
935
|
+
batchBase ??= current;
|
|
936
|
+
batchCatalogOperation ??= catalogOperationByEvidence.get(current);
|
|
937
|
+
groupEvidence.push({
|
|
938
|
+
scope: group.scope,
|
|
939
|
+
config: {
|
|
940
|
+
...current.config,
|
|
941
|
+
storeDigest: await configLayerStoreDigest({
|
|
942
|
+
source: current.source,
|
|
943
|
+
pluginName: parsePluginId(intent.pluginId).pluginName,
|
|
944
|
+
scope: group.scope,
|
|
945
|
+
cwd: context.cwd,
|
|
946
|
+
env: context.env,
|
|
947
|
+
}),
|
|
948
|
+
},
|
|
949
|
+
targetFingerprint: current.targetFingerprint,
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
const evidence = {
|
|
953
|
+
catalog: batchBase.catalog,
|
|
954
|
+
source: batchBase.source,
|
|
955
|
+
configBatch: { groups: groupEvidence },
|
|
956
|
+
};
|
|
957
|
+
if (batchCatalogOperation)
|
|
958
|
+
catalogOperationByEvidence.set(evidence, batchCatalogOperation);
|
|
959
|
+
return evidence;
|
|
960
|
+
}
|
|
802
961
|
if ((intent.kind === 'pluginWrite'
|
|
803
962
|
&& (intent.platform === 'codex' || intent.platform === 'both')
|
|
804
963
|
|| intent.kind === 'pluginWriteTask'
|
|
@@ -1808,6 +1967,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
|
|
|
1808
1967
|
?? digest(groups.map((group) => [group.groupId, group.preview.operationPlan?.planFingerprint])),
|
|
1809
1968
|
},
|
|
1810
1969
|
requestedPluginIds: selection.requestedPluginIds,
|
|
1970
|
+
...(selection.projectIntentDigest
|
|
1971
|
+
? { projectIntentDigest: selection.projectIntentDigest }
|
|
1972
|
+
: {}),
|
|
1811
1973
|
groups,
|
|
1812
1974
|
targetVersions: [...new Map(targetVersions.map((target) => [[
|
|
1813
1975
|
target.pluginId, target.platform, target.scope,
|
|
@@ -1877,23 +2039,39 @@ export function createTuiControllerDependencies(context, progressFeed, options =
|
|
|
1877
2039
|
}
|
|
1878
2040
|
if (intent.kind === 'portableInstall') {
|
|
1879
2041
|
await preparation?.reporter.emit('portable');
|
|
2042
|
+
if (intent.retry
|
|
2043
|
+
&& (intent.retry.receipt.plugin.id !== intent.pluginId
|
|
2044
|
+
|| intent.retry.receipt.scope !== intent.scope)) {
|
|
2045
|
+
throw operationReviewStale('Portable 重试结果与当前插件或范围不匹配。');
|
|
2046
|
+
}
|
|
1880
2047
|
const portableSourceId = parsePluginId(intent.pluginId).sourceId;
|
|
1881
2048
|
const portableCatalogOperation = sessionCatalogOperationContext
|
|
1882
2049
|
? selectCatalogOperationContext(sessionCatalogOperationContext, [portableSourceId])
|
|
1883
2050
|
: undefined;
|
|
1884
|
-
const portable =
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
2051
|
+
const portable = intent.retry
|
|
2052
|
+
? await preparePortableRetry({
|
|
2053
|
+
receipt: intent.retry.receipt,
|
|
2054
|
+
cwd: context.cwd,
|
|
2055
|
+
env: context.env,
|
|
2056
|
+
cliVersion: packageInfo.version,
|
|
2057
|
+
signal,
|
|
2058
|
+
...(portableCatalogOperation
|
|
2059
|
+
? { catalogOperationContext: portableCatalogOperation }
|
|
2060
|
+
: {}),
|
|
2061
|
+
})
|
|
2062
|
+
: await preparePortableInstall({
|
|
2063
|
+
selector: intent.pluginId,
|
|
2064
|
+
scope: intent.scope,
|
|
2065
|
+
...(intent.selectedSkills.length > 0 ? { requestedSkills: intent.selectedSkills } : {}),
|
|
2066
|
+
...(intent.requestedAgents ? { requestedAgents: intent.requestedAgents } : {}),
|
|
2067
|
+
cwd: context.cwd,
|
|
2068
|
+
env: context.env,
|
|
2069
|
+
cliVersion: packageInfo.version,
|
|
2070
|
+
signal,
|
|
2071
|
+
...(portableCatalogOperation
|
|
2072
|
+
? { catalogOperationContext: portableCatalogOperation }
|
|
2073
|
+
: {}),
|
|
2074
|
+
});
|
|
1897
2075
|
signal?.throwIfAborted();
|
|
1898
2076
|
await preparation?.reporter.emit('evidence');
|
|
1899
2077
|
signal?.throwIfAborted();
|
|
@@ -2326,6 +2504,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
|
|
|
2326
2504
|
}
|
|
2327
2505
|
},
|
|
2328
2506
|
loadStatus,
|
|
2507
|
+
loadPluginContent,
|
|
2329
2508
|
async probeCatalogFreshness(sourceId, freshnessOptions, signal) {
|
|
2330
2509
|
signal.throwIfAborted();
|
|
2331
2510
|
const packageInfo = await readCliPackageInfo();
|
|
@@ -2439,6 +2618,13 @@ export function createTuiControllerDependencies(context, progressFeed, options =
|
|
|
2439
2618
|
}
|
|
2440
2619
|
return;
|
|
2441
2620
|
}
|
|
2621
|
+
if (intent.kind === 'configSaveBatch') {
|
|
2622
|
+
assertConfigSaveBatchIntent(intent);
|
|
2623
|
+
for (const group of intent.groups) {
|
|
2624
|
+
await dependencies.validateIntent(configSaveBatchGroupIntent(intent, group), signal);
|
|
2625
|
+
}
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2442
2628
|
if (intent.kind === 'configSave') {
|
|
2443
2629
|
const packageInfo = await readCliPackageInfo();
|
|
2444
2630
|
await withResolvedCatalogPlugin({
|
|
@@ -2476,6 +2662,18 @@ export function createTuiControllerDependencies(context, progressFeed, options =
|
|
|
2476
2662
|
}
|
|
2477
2663
|
},
|
|
2478
2664
|
captureEvidence,
|
|
2665
|
+
inspectCatalogStore(signal) {
|
|
2666
|
+
signal.throwIfAborted();
|
|
2667
|
+
return inspectCatalogStoreTask({ env: context.env });
|
|
2668
|
+
},
|
|
2669
|
+
recheckPortableInstall(receipt, signal) {
|
|
2670
|
+
return recheckPortableInstall({
|
|
2671
|
+
receipt,
|
|
2672
|
+
cwd: context.cwd,
|
|
2673
|
+
env: context.env,
|
|
2674
|
+
signal,
|
|
2675
|
+
});
|
|
2676
|
+
},
|
|
2479
2677
|
logError(_error, code, logContext) {
|
|
2480
2678
|
return context.debugLogger.log({
|
|
2481
2679
|
type: 'tui:controller-error',
|
|
@@ -2671,6 +2869,16 @@ export function createTuiControllerDependencies(context, progressFeed, options =
|
|
|
2671
2869
|
};
|
|
2672
2870
|
},
|
|
2673
2871
|
async executeIntent(intent, expectedEvidence, signal) {
|
|
2872
|
+
if (intent.kind === 'catalogStorePrune') {
|
|
2873
|
+
signal?.throwIfAborted();
|
|
2874
|
+
if (!expectedEvidence.catalogStore) {
|
|
2875
|
+
throw new TuiOperationBlockedError('catalogStoreReviewStale', '确认信息缺少精确清理目标。');
|
|
2876
|
+
}
|
|
2877
|
+
return executeCatalogStoreTask({
|
|
2878
|
+
env: context.env,
|
|
2879
|
+
authorizedItems: expectedEvidence.catalogStore.authorizedItems,
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2674
2882
|
if (intent.kind === 'catalogSourcesRecovery') {
|
|
2675
2883
|
signal?.throwIfAborted();
|
|
2676
2884
|
return recoverInvalidCatalogSourcesConfig({ env: context.env });
|
|
@@ -2844,6 +3052,25 @@ export function createTuiControllerDependencies(context, progressFeed, options =
|
|
|
2844
3052
|
}
|
|
2845
3053
|
const packageInfo = await readCliPackageInfo();
|
|
2846
3054
|
try {
|
|
3055
|
+
if (intent.retry) {
|
|
3056
|
+
if (!('retryPairs' in expectedEvidence.portable)) {
|
|
3057
|
+
throw operationReviewStale('确认信息缺少 Portable 重试目标。');
|
|
3058
|
+
}
|
|
3059
|
+
return await executePortableRetry({
|
|
3060
|
+
receipt: intent.retry.receipt,
|
|
3061
|
+
review: expectedEvidence.portable,
|
|
3062
|
+
expectedEvidenceDigest: expectedEvidence.portable.evidenceDigest,
|
|
3063
|
+
cwd: context.cwd,
|
|
3064
|
+
env: context.env,
|
|
3065
|
+
cliVersion: packageInfo.version,
|
|
3066
|
+
signal,
|
|
3067
|
+
onProgress(stage) {
|
|
3068
|
+
const phase = stage === 'prepare' ? '重新检查失败目标'
|
|
3069
|
+
: stage === 'add' ? '重试缺失的 Skills' : '验证重试结果';
|
|
3070
|
+
progressFeed?.setCurrent({ target: 'Skills CLI', phase }, `Portable Skills:${phase}`);
|
|
3071
|
+
},
|
|
3072
|
+
});
|
|
3073
|
+
}
|
|
2847
3074
|
return await installPortableSkills({
|
|
2848
3075
|
selector: intent.pluginId,
|
|
2849
3076
|
scope: intent.scope,
|
|
@@ -2951,6 +3178,12 @@ export function createTuiControllerDependencies(context, progressFeed, options =
|
|
|
2951
3178
|
}
|
|
2952
3179
|
catch (error) {
|
|
2953
3180
|
const code = error instanceof TuiOperationBlockedError ? error.code : undefined;
|
|
3181
|
+
if (intent.kind === 'pluginWriteBatch'
|
|
3182
|
+
&& (intent.selection.kind === 'currentProjectIntentRoots'
|
|
3183
|
+
|| intent.selection.kind === 'currentProjectIntentTargets')
|
|
3184
|
+
&& error instanceof TuiOperationBlockedError) {
|
|
3185
|
+
throw operationReviewStale('确认后完整项目 Intent 声明发生变化。');
|
|
3186
|
+
}
|
|
2954
3187
|
if (intent.kind === 'pluginWriteTask'
|
|
2955
3188
|
&& (code === 'taskHasBlockedTargets' || code === 'taskNoExecutableTargets')) {
|
|
2956
3189
|
throw operationReviewStale('确认后任务可执行目标发生变化。', error instanceof TuiOperationBlockedError ? error.diagnostics : undefined);
|
|
@@ -2960,6 +3193,12 @@ export function createTuiControllerDependencies(context, progressFeed, options =
|
|
|
2960
3193
|
if (intent.kind === 'pluginWriteBatch') {
|
|
2961
3194
|
const expectedBatch = expectedEvidence;
|
|
2962
3195
|
const currentBatch = currentEvidence;
|
|
3196
|
+
if ((intent.selection.kind === 'currentProjectIntentRoots'
|
|
3197
|
+
|| intent.selection.kind === 'currentProjectIntentTargets')
|
|
3198
|
+
&& (!expectedBatch.projectIntentDigest
|
|
3199
|
+
|| expectedBatch.projectIntentDigest !== currentBatch.projectIntentDigest)) {
|
|
3200
|
+
throw operationReviewStale('确认后完整项目 Intent 声明发生变化。');
|
|
3201
|
+
}
|
|
2963
3202
|
const reviewedGroups = expectedBatch.groups.map((group) => ({
|
|
2964
3203
|
groupId: group.groupId,
|
|
2965
3204
|
sourceId: group.sourceId,
|
|
@@ -3330,6 +3569,147 @@ export function createTuiControllerDependencies(context, progressFeed, options =
|
|
|
3330
3569
|
throwIfWriteReviewStale(result);
|
|
3331
3570
|
return result;
|
|
3332
3571
|
}
|
|
3572
|
+
if (intent.kind === 'configSaveBatch') {
|
|
3573
|
+
assertConfigSaveBatchIntent(intent);
|
|
3574
|
+
const reviewedBatch = expectedEvidence.configBatch;
|
|
3575
|
+
if (!reviewedBatch || !currentEvidence.configBatch || !currentEvidence.source) {
|
|
3576
|
+
throw operationReviewStale('确认信息缺少设置批次证据。');
|
|
3577
|
+
}
|
|
3578
|
+
const approvedApplyTargets = new Set(reviewedBatch.groups.flatMap((group) => group.config.affectedTargets.map((target) => `${target.platform}\0${target.scope}`)));
|
|
3579
|
+
const approvedPreApplyTargets = reviewedBatch.groups.flatMap((group) => group.config.preApplyAuthorization ?? []);
|
|
3580
|
+
const results = [];
|
|
3581
|
+
const packageInfo = await readCliPackageInfo();
|
|
3582
|
+
for (const group of intent.groups) {
|
|
3583
|
+
const groupIntent = configSaveBatchGroupIntent(intent, group);
|
|
3584
|
+
const reviewedGroup = reviewedBatch.groups.find((item) => item.scope === group.scope);
|
|
3585
|
+
if (!reviewedGroup) {
|
|
3586
|
+
throw operationReviewStale('确认信息缺少设置层基线。');
|
|
3587
|
+
}
|
|
3588
|
+
let configSaved = false;
|
|
3589
|
+
const operationRebuildResults = [];
|
|
3590
|
+
try {
|
|
3591
|
+
const freshEvidence = await captureEvidence(groupIntent, undefined, undefined, operationInventory, true);
|
|
3592
|
+
const freshSource = freshEvidence.source;
|
|
3593
|
+
const freshConfig = freshEvidence.config;
|
|
3594
|
+
if (!freshSource || !freshConfig) {
|
|
3595
|
+
throw operationReviewStale('设置目标缺少最新证据。');
|
|
3596
|
+
}
|
|
3597
|
+
assertSourceEvidenceMatches(currentEvidence.source, freshSource);
|
|
3598
|
+
const freshLayerStoreDigest = await configLayerStoreDigest({
|
|
3599
|
+
source: freshSource,
|
|
3600
|
+
pluginName: parsePluginId(intent.pluginId).pluginName,
|
|
3601
|
+
scope: group.scope,
|
|
3602
|
+
cwd: context.cwd,
|
|
3603
|
+
env: context.env,
|
|
3604
|
+
});
|
|
3605
|
+
if (freshLayerStoreDigest !== reviewedGroup.config.storeDigest) {
|
|
3606
|
+
throw operationReviewStale('保存设置期间此设置层的原始内容发生变化。');
|
|
3607
|
+
}
|
|
3608
|
+
if (freshConfig.affectedTargets.some((target) => !approvedApplyTargets.has(`${target.platform}\0${target.scope}`))) {
|
|
3609
|
+
throw operationReviewStale('同批前一层变更扩大了未经确认的应用目标。');
|
|
3610
|
+
}
|
|
3611
|
+
const groupCatalogOperation = catalogOperationByEvidence.get(freshEvidence);
|
|
3612
|
+
if (expectedEvidence.catalog.generationSetDigest
|
|
3613
|
+
&& groupCatalogOperation?.generationSetDigest
|
|
3614
|
+
!== expectedEvidence.catalog.generationSetDigest) {
|
|
3615
|
+
throw operationReviewStale('保存设置期间 Active Catalog generation 发生变化。');
|
|
3616
|
+
}
|
|
3617
|
+
const result = await withResolvedCatalogPlugin({
|
|
3618
|
+
selector: intent.pluginId,
|
|
3619
|
+
cliVersion: packageInfo.version,
|
|
3620
|
+
env: context.env,
|
|
3621
|
+
...(groupCatalogOperation ? { operationContext: groupCatalogOperation } : {}),
|
|
3622
|
+
consume: async ({ plugin, source }) => {
|
|
3623
|
+
assertSourceEvidenceMatches(freshSource, sourceEvidenceForPlugin(source, plugin.manifest.name));
|
|
3624
|
+
const applyTargets = intent.apply
|
|
3625
|
+
? freshConfig.affectedTargets.map((target) => ({
|
|
3626
|
+
pluginName: plugin.manifest.name,
|
|
3627
|
+
platform: target.platform,
|
|
3628
|
+
scope: target.scope,
|
|
3629
|
+
}))
|
|
3630
|
+
: [];
|
|
3631
|
+
return applyPluginConfigWrite({
|
|
3632
|
+
plugin,
|
|
3633
|
+
pluginName: plugin.manifest.name,
|
|
3634
|
+
scope: group.scope,
|
|
3635
|
+
cwd: context.cwd,
|
|
3636
|
+
env: context.env,
|
|
3637
|
+
nativeCliRunner: context.nativeCliRunner,
|
|
3638
|
+
rebuildRuntime: {
|
|
3639
|
+
...context,
|
|
3640
|
+
awHomeMutationToken,
|
|
3641
|
+
operationDiagnosticSink: (entries) => {
|
|
3642
|
+
operationRebuildResults.push(...structuredClone(entries));
|
|
3643
|
+
},
|
|
3644
|
+
...(reviewedScriptTimeoutMs(intent) === undefined ? {} : {
|
|
3645
|
+
scriptTimeoutMs: reviewedScriptTimeoutMs(intent),
|
|
3646
|
+
}),
|
|
3647
|
+
...(groupCatalogOperation ? {
|
|
3648
|
+
catalogOperationContext: groupCatalogOperation,
|
|
3649
|
+
expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
|
|
3650
|
+
} : {}),
|
|
3651
|
+
},
|
|
3652
|
+
awHomeMutationToken,
|
|
3653
|
+
applyTargets,
|
|
3654
|
+
request: { updates: group.updates, removeKeys: group.removeKeys },
|
|
3655
|
+
sharedNativeApplyConfirmed: true,
|
|
3656
|
+
preApplyConfirmed: true,
|
|
3657
|
+
preApplyAuthorizedTargets: approvedPreApplyTargets,
|
|
3658
|
+
});
|
|
3659
|
+
},
|
|
3660
|
+
});
|
|
3661
|
+
configSaved = result.configWrite === 'succeeded';
|
|
3662
|
+
const applyStatus = !intent.apply
|
|
3663
|
+
? 'notRequested'
|
|
3664
|
+
: result.apply.state === 'succeeded'
|
|
3665
|
+
? 'succeeded'
|
|
3666
|
+
: result.apply.state === 'skipped'
|
|
3667
|
+
? 'notNeeded'
|
|
3668
|
+
: 'failed';
|
|
3669
|
+
results.push({
|
|
3670
|
+
scope: group.scope,
|
|
3671
|
+
status: applyStatus === 'failed' ? 'failed' : 'succeeded',
|
|
3672
|
+
configSaved,
|
|
3673
|
+
applyRequested: intent.apply,
|
|
3674
|
+
applyStatus,
|
|
3675
|
+
execution: { ...result, operationRebuildResults },
|
|
3676
|
+
...(applyStatus === 'failed' ? {
|
|
3677
|
+
errorCode: result.apply.state === 'unavailable'
|
|
3678
|
+
? result.apply.error?.code ?? result.apply.reason
|
|
3679
|
+
: result.apply.targetResults.find((target) => target.errorCode)?.errorCode
|
|
3680
|
+
?? 'runtimeApplyFailed',
|
|
3681
|
+
} : {}),
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
catch (error) {
|
|
3685
|
+
configSaved = error?.configWrite === 'succeeded';
|
|
3686
|
+
const errorCode = typeof error === 'object' && error !== null
|
|
3687
|
+
&& 'code' in error && typeof error.code === 'string'
|
|
3688
|
+
? error.code
|
|
3689
|
+
: configSaved ? 'runtimeApplyFailed' : 'configWriteFailed';
|
|
3690
|
+
results.push({
|
|
3691
|
+
scope: group.scope,
|
|
3692
|
+
status: 'failed',
|
|
3693
|
+
configSaved,
|
|
3694
|
+
applyRequested: intent.apply,
|
|
3695
|
+
applyStatus: intent.apply ? 'failed' : 'notRequested',
|
|
3696
|
+
errorCode,
|
|
3697
|
+
...(operationRebuildResults.length > 0
|
|
3698
|
+
? { execution: { operationRebuildResults } }
|
|
3699
|
+
: {}),
|
|
3700
|
+
});
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
const succeeded = results.filter((result) => result.status === 'succeeded').length;
|
|
3704
|
+
return {
|
|
3705
|
+
status: succeeded === results.length
|
|
3706
|
+
? 'succeeded'
|
|
3707
|
+
: succeeded > 0 || results.some((result) => result.configSaved)
|
|
3708
|
+
? 'partial'
|
|
3709
|
+
: 'failed',
|
|
3710
|
+
groups: results,
|
|
3711
|
+
};
|
|
3712
|
+
}
|
|
3333
3713
|
if (intent.kind === 'configSave' || intent.kind === 'configReset') {
|
|
3334
3714
|
const operationRebuildResults = [];
|
|
3335
3715
|
const operationDiagnosticSink = (results) => {
|