@lesliechan721/aw 0.1.0-beta.11 → 0.1.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/authoring/scripts/validate-plugin.mjs +33 -42
  2. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  3. package/dist/src/commands/operation-planner.js +11 -1
  4. package/dist/src/commands/operation-planner.js.map +1 -1
  5. package/dist/src/commands/rebuild-command.d.ts +12 -0
  6. package/dist/src/commands/rebuild-command.js +72 -17
  7. package/dist/src/commands/rebuild-command.js.map +1 -1
  8. package/dist/src/commands/rebuild-integration.d.ts +1 -0
  9. package/dist/src/commands/rebuild-integration.js +138 -41
  10. package/dist/src/commands/rebuild-integration.js.map +1 -1
  11. package/dist/src/commands/status-v6.js +6 -6
  12. package/dist/src/commands/status-v6.js.map +1 -1
  13. package/dist/src/index.js +52 -14
  14. package/dist/src/index.js.map +1 -1
  15. package/dist/src/portable/materializer.js +5 -3
  16. package/dist/src/portable/materializer.js.map +1 -1
  17. package/dist/src/tui/console-app-v4.js +1 -21
  18. package/dist/src/tui/console-app-v4.js.map +1 -1
  19. package/dist/src/tui/controller-runtime.js +165 -48
  20. package/dist/src/tui/controller-runtime.js.map +1 -1
  21. package/dist/src/tui/controller.d.ts +4 -4
  22. package/dist/src/tui/controller.js +19 -7
  23. package/dist/src/tui/controller.js.map +1 -1
  24. package/dist/src/tui/diagnostics.js +6 -6
  25. package/dist/src/tui/diagnostics.js.map +1 -1
  26. package/dist/src/tui/operation-review-planner.js +66 -19
  27. package/dist/src/tui/operation-review-planner.js.map +1 -1
  28. package/dist/src/utils/artifact-plan.d.ts +2 -2
  29. package/dist/src/utils/artifact-plan.js +8 -6
  30. package/dist/src/utils/artifact-plan.js.map +1 -1
  31. package/dist/src/utils/artifact-renderer.js +96 -77
  32. package/dist/src/utils/artifact-renderer.js.map +1 -1
  33. package/dist/src/utils/artifact-request.d.ts +5 -0
  34. package/dist/src/utils/artifact-request.js +40 -0
  35. package/dist/src/utils/artifact-request.js.map +1 -1
  36. package/dist/src/utils/plugin-config-impact.d.ts +33 -0
  37. package/dist/src/utils/plugin-config-impact.js +298 -0
  38. package/dist/src/utils/plugin-config-impact.js.map +1 -0
  39. package/dist/src/utils/plugin-config-write.d.ts +22 -2
  40. package/dist/src/utils/plugin-config-write.js +368 -104
  41. package/dist/src/utils/plugin-config-write.js.map +1 -1
  42. package/dist/src/utils/plugin-config.d.ts +10 -0
  43. package/dist/src/utils/plugin-config.js +39 -22
  44. package/dist/src/utils/plugin-config.js.map +1 -1
  45. package/dist/src/utils/plugin-scripts.js +47 -39
  46. package/dist/src/utils/plugin-scripts.js.map +1 -1
  47. package/dist/src/utils/project-boundary.js +22 -9
  48. package/dist/src/utils/project-boundary.js.map +1 -1
  49. package/dist/src/utils/sensitive-content.d.ts +3 -0
  50. package/dist/src/utils/sensitive-content.js +28 -7
  51. package/dist/src/utils/sensitive-content.js.map +1 -1
  52. package/package.json +3 -3
@@ -52,6 +52,7 @@ export interface RebuildExecutionContext {
52
52
  codexUserActivationDesired?: ReadonlySet<string>;
53
53
  codexObservationSession?: CodexNativeObservationSession;
54
54
  codexObservationScopeByMarketplace?: ReadonlyMap<string, 'user' | 'both'>;
55
+ preApplyExecutionCache?: Map<string, Promise<void>>;
55
56
  }
56
57
  export declare function executeCodexReconcileActivations(options: {
57
58
  plans: readonly CodexReconcilePlan[];
@@ -6,16 +6,16 @@ import { generateInstructionArtifacts, renderCodexManagedInstructionBlock, } fro
6
6
  import { generateCodexPermissionArtifacts, mergeClaudePermissions, } from '../generators/permissions.js';
7
7
  import { buildGeneratedName } from '../generators/shared.js';
8
8
  import { applyConditionalCodexConfigTransactionFile, applyCodexConfigTransactionFile, composeManagedDeveloperInstructionsBlock, readCodexConfigFile, } from '../utils/codex-config.js';
9
- import { canonicalizePathThroughExistingAncestor } from '../utils/state-paths.js';
9
+ import { canonicalizePathThroughExistingAncestor, resolveClaudeProjectKey, resolveCodexProjectKey, } from '../utils/state-paths.js';
10
10
  import { createCodexSkillExpectedTree, createCodexSkillStagingTreeContentDigest, } from '../utils/codex-skill-staging.js';
11
- import { createDynamicSkillExpectedTree, createDynamicSkillTreeContentDigest, readDynamicSkillTree, validateSkillDirectory, writeDynamicSkillTree, } from '../utils/dynamic-skill-tree.js';
11
+ import { AW_PLUGIN_REQUIREMENTS_RECEIPT_PATH, createDynamicSkillExpectedTree, createDynamicSkillTreeContentDigest, readDynamicSkillTree, validateSkillDirectory, writeDynamicSkillTree, } from '../utils/dynamic-skill-tree.js';
12
12
  import { parseCodexSharedHooksFile, serializeTrackedHookGroupIdentity, } from '../utils/hooks-contract.js';
13
13
  import { buildCodexMarketplaceAddInvocation, buildCodexMarketplaceRemoveInvocation, buildCodexPluginAddInvocation, buildCodexPluginRemoveInvocation, CodexProjectConfigNotEffectiveError, runNativeCliChecked, SYSTEM_CODEX_MANAGER, } from '../utils/native-cli.js';
14
14
  import { OWNED_TEXT_HEADER_PREFIX, createSharedOwnerToken, createSkillOwnerMarkerContent, parseSharedOwnerToken, parseOwnedTextHeader, readSkillOwnerMarker, renderOwnedTextFile, inspectOwnedComponents, writeOwnedTextFile, } from '../utils/ownership.js';
15
15
  import { resolveArtifactRenderConfig, resolveArtifactRendererTarget, runArtifactRenderer, } from '../utils/artifact-renderer.js';
16
16
  import { createArtifactConfigDigest, validateArtifactPlan, } from '../utils/artifact-plan.js';
17
- import { resolveAwDeliveredArtifactIds } from '../utils/artifact-request.js';
18
- import { createAwPluginRequirementsReceipt, } from '../utils/manifest.js';
17
+ import { resolveAwDeliveredArtifactRequestBatches } from '../utils/artifact-request.js';
18
+ import { createAwPluginRequirementsReceipt, parseAwPluginRequirementsReceipt, } from '../utils/manifest.js';
19
19
  import { resolvePlatformEntries } from '../utils/platform-entries.js';
20
20
  import { resolveAwHome, resolveClaudePaths, resolveCodexPaths, resolveRepoRoot, getCodexHome, } from '../utils/paths.js';
21
21
  import { assertNoSensitiveConfigInFiles, collectSensitivePluginConfigValues, runPreApplyScript, } from '../utils/plugin-scripts.js';
@@ -39,6 +39,26 @@ function requirementsReceipt(plugin) {
39
39
  const receipt = createAwPluginRequirementsReceipt({ name: plugin.manifest.name, version: plugin.manifest.version }, plugin.manifest.requires?.aw.capabilities ?? []);
40
40
  return Buffer.from(`${JSON.stringify(receipt, null, 2)}\n`, 'utf8');
41
41
  }
42
+ function withoutVerifiedRequirementsReceipt(plugin, files) {
43
+ const receipt = files[AW_PLUGIN_REQUIREMENTS_RECEIPT_PATH];
44
+ if (!receipt)
45
+ return { ...files };
46
+ let actual;
47
+ let expected;
48
+ try {
49
+ actual = parseAwPluginRequirementsReceipt(JSON.parse(receipt.toString('utf8')));
50
+ expected = parseAwPluginRequirementsReceipt(JSON.parse(requirementsReceipt(plugin).toString('utf8')));
51
+ }
52
+ catch {
53
+ return { ...files };
54
+ }
55
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
56
+ return { ...files };
57
+ }
58
+ const output = { ...files };
59
+ delete output[AW_PLUGIN_REQUIREMENTS_RECEIPT_PATH];
60
+ return output;
61
+ }
42
62
  export class CodexActivationPostcheckError extends Error {
43
63
  code = 'codexActivationPostcheckFailed';
44
64
  codexPluginIds;
@@ -924,25 +944,29 @@ function renderCodexRole(options) {
924
944
  ].join('\n');
925
945
  }
926
946
  async function resolveDynamicArtifacts(options) {
927
- const requestedArtifactIds = resolveAwDeliveredArtifactIds({
947
+ const requestBatches = resolveAwDeliveredArtifactRequestBatches({
928
948
  manifest: options.plugin.manifest,
929
949
  platform: options.platform,
930
950
  scope: options.scope,
931
951
  });
932
- if (requestedArtifactIds.length === 0) {
952
+ if (requestBatches.length === 0) {
933
953
  return { artifacts: [], sourceDirs: () => [], sensitiveValues: [] };
934
954
  }
935
- const render = await runArtifactRenderer({
936
- event: options.event,
937
- plugin: options.plugin,
938
- platform: options.platform,
939
- scope: options.scope,
940
- cwd: options.cwd,
941
- commandEnv: options.env,
942
- previousArtifacts: [...options.previousArtifacts],
943
- requestedArtifactIds,
944
- scriptTimeoutMs: options.scriptTimeoutMs,
945
- });
955
+ const previousArtifacts = [...options.previousArtifacts];
956
+ const renders = [];
957
+ for (const requestedArtifactIds of requestBatches) {
958
+ renders.push(await runArtifactRenderer({
959
+ event: options.event,
960
+ plugin: options.plugin,
961
+ platform: options.platform,
962
+ scope: options.scope,
963
+ cwd: options.cwd,
964
+ commandEnv: options.env,
965
+ previousArtifacts,
966
+ requestedArtifactIds,
967
+ scriptTimeoutMs: options.scriptTimeoutMs,
968
+ }));
969
+ }
946
970
  const config = await resolveArtifactRenderConfig({
947
971
  sourceId: options.plugin.sourceId,
948
972
  sourceIdentity: options.plugin.sourceIdentity,
@@ -953,19 +977,26 @@ async function resolveDynamicArtifacts(options) {
953
977
  cwd: options.cwd,
954
978
  env: options.env,
955
979
  });
956
- const artifacts = validateArtifactPlan({
957
- manifest: options.plugin.manifest,
958
- plan: render.plan,
959
- platform: options.platform,
960
- scope: options.scope,
961
- requestedArtifactIds: render.context.requestedArtifactIds,
962
- renderConfig: config,
963
- rendererDigest: render.target.rendererDigest,
964
- manifestDigest: render.target.manifestDigest,
965
- }).artifacts;
980
+ const artifacts = [];
981
+ const sourceDirsByArtifact = new Map();
982
+ for (const render of renders) {
983
+ artifacts.push(...validateArtifactPlan({
984
+ manifest: options.plugin.manifest,
985
+ plan: render.plan,
986
+ platform: options.platform,
987
+ scope: options.scope,
988
+ requestedArtifactIds: render.context.requestedArtifactIds,
989
+ renderConfig: config,
990
+ rendererDigest: render.target.rendererDigest,
991
+ manifestDigest: render.target.manifestDigest,
992
+ }).artifacts);
993
+ for (const source of render.context.artifactSources) {
994
+ sourceDirsByArtifact.set(`${source.id}\0${source.target}`, source.sourceDirs);
995
+ }
996
+ }
966
997
  return {
967
998
  artifacts,
968
- sourceDirs: (artifact) => render.context.artifactSources.find((entry) => entry.id === artifact.id && entry.target === artifact.target)?.sourceDirs ?? [],
999
+ sourceDirs: (artifact) => sourceDirsByArtifact.get(`${artifact.id}\0${artifact.target}`) ?? [],
969
1000
  sensitiveValues: collectSensitivePluginConfigValues(options.plugin.manifest, config.values),
970
1001
  };
971
1002
  }
@@ -1140,7 +1171,7 @@ async function buildClaudeWrites(options) {
1140
1171
  requirementsReceipt: requirementsReceipt(options.plugin),
1141
1172
  sourceRequirementsReceiptPolicy: 'allow',
1142
1173
  });
1143
- assertNoSensitiveConfigInFiles(tree.files, dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
1174
+ assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
1144
1175
  writes.push(dynamicSkillWrite({
1145
1176
  ...options,
1146
1177
  platform: 'claude',
@@ -1217,7 +1248,7 @@ async function buildCodexWrites(options) {
1217
1248
  requirementsReceipt: requirementsReceipt(options.plugin),
1218
1249
  sourceRequirementsReceiptPolicy: 'allow',
1219
1250
  });
1220
- assertNoSensitiveConfigInFiles(tree.files, dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
1251
+ assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
1221
1252
  writes.push(dynamicSkillWrite({
1222
1253
  ...options,
1223
1254
  platform: 'codex',
@@ -1810,7 +1841,7 @@ async function readCodexNativeRenderReceiptEvidence(payloadRoot) {
1810
1841
  if (!stats)
1811
1842
  return { state: 'absent' };
1812
1843
  if (!stats.isFile() || stats.isSymbolicLink())
1813
- return { state: 'unreadable' };
1844
+ return { state: 'unsafe' };
1814
1845
  try {
1815
1846
  return {
1816
1847
  state: 'present',
@@ -1818,7 +1849,7 @@ async function readCodexNativeRenderReceiptEvidence(payloadRoot) {
1818
1849
  };
1819
1850
  }
1820
1851
  catch {
1821
- return { state: 'unreadable' };
1852
+ return { state: 'invalid' };
1822
1853
  }
1823
1854
  }
1824
1855
  async function resolveCodexNativeRenderExpectedEvidence(options) {
@@ -1900,7 +1931,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1900
1931
  if (targets.length === 0
1901
1932
  || targets.length !== receipt.targets.length
1902
1933
  || targets.some((target) => receiptTargets.get(target.id)?.skillKey !== target.skillKey)) {
1903
- return { state: 'present', payloadDigest, sourceProjection: 'drifted' };
1934
+ return { state: 'present', payloadDigest, receipt, sourceProjection: 'drifted' };
1904
1935
  }
1905
1936
  const renderer = await resolveArtifactRendererTarget(options.plugin);
1906
1937
  if (renderer.noOp || !renderer.rendererDigest) {
@@ -1934,7 +1965,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1934
1965
  target: 'codex.skill',
1935
1966
  refreshKeys: target.refreshKeys,
1936
1967
  },
1937
- configValues: config.values,
1968
+ renderConfig: config,
1938
1969
  digestSalt: renderer.rendererDigest,
1939
1970
  }),
1940
1971
  artifactDigest: receiptTargets.get(target.id).artifactDigest,
@@ -1942,6 +1973,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1942
1973
  });
1943
1974
  return {
1944
1975
  state: 'present',
1976
+ receipt,
1945
1977
  projection,
1946
1978
  payloadDigest,
1947
1979
  sourceProjection: codexNativeRenderProjectionsEqual(receipt, projection)
@@ -1966,11 +1998,31 @@ async function assertNoSensitiveValuesInCanonicalCodexNativePayload(options) {
1966
1998
  throw new Error('Codex native sensitive scan canonical marketplace path is unsafe.');
1967
1999
  }
1968
2000
  const payloadRoot = path.join(options.marketplaceRoot, 'plugins', options.plugin.manifest.name);
2001
+ if (!await codexPayloadPathSafe(payloadRoot, options.marketplaceRoot)) {
2002
+ throw new Error('Codex native sensitive scan payload path is unsafe.');
2003
+ }
2004
+ const receiptEvidence = await readCodexNativeRenderReceiptEvidence(payloadRoot);
2005
+ if (receiptEvidence.state === 'unsafe') {
2006
+ throw new Error('codexNativeRenderReceiptInvalid: Codex native render receipt path is unsafe.');
2007
+ }
2008
+ let verifiedRenderReceipt;
2009
+ if (receiptEvidence.state === 'present') {
2010
+ const expected = await resolveCodexNativeRenderExpectedEvidence({
2011
+ plugin: options.plugin,
2012
+ marketplaceRoot: options.marketplaceRoot,
2013
+ marketplaceName: options.marketplaceName,
2014
+ env: options.env,
2015
+ }).catch(() => undefined);
2016
+ if (expected?.state === 'present' && expected.sourceProjection === 'current') {
2017
+ verifiedRenderReceipt = expected.receipt;
2018
+ }
2019
+ }
1969
2020
  await assertNoSensitiveValuesInCodexNativePayload({
1970
2021
  plugin: options.plugin,
1971
2022
  payloadRoot,
1972
2023
  allowedRoot: options.marketplaceRoot,
1973
2024
  sensitiveValues: options.sensitiveValues,
2025
+ verifiedRenderReceipt,
1974
2026
  });
1975
2027
  }
1976
2028
  function observedCodexNativeState(observation, operation) {
@@ -2522,7 +2574,7 @@ async function assertNoSensitiveValuesInCodexNativePayload(options) {
2522
2574
  const tree = await readDynamicSkillTree(skillRoot, 'codex.skill');
2523
2575
  if (!tree)
2524
2576
  continue;
2525
- const leakedPath = findSensitiveTokenPath(tree.files, options.sensitiveValues);
2577
+ const leakedPath = findSensitiveTokenPath(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), options.sensitiveValues);
2526
2578
  if (leakedPath) {
2527
2579
  throw new Error(`codexNativeRenderSensitiveValue: Codex native Skill "${entry.name}" exposes ` +
2528
2580
  `sensitive config in "${leakedPath}".`);
@@ -2535,10 +2587,22 @@ async function assertNoSensitiveValuesInCodexNativePayload(options) {
2535
2587
  if (!receiptStats.isFile()) {
2536
2588
  throw new Error('Codex native sensitive scan render receipt path is unsafe.');
2537
2589
  }
2538
- const receiptLeak = findSensitiveTokenPath({ [CODEX_NATIVE_RENDER_RECEIPT_PATH]: await fs.readFile(receiptPath) }, options.sensitiveValues);
2539
- if (receiptLeak) {
2540
- throw new Error(`codexNativeRenderSensitiveValue: Codex native render receipt exposes sensitive config in ` +
2541
- `"${receiptLeak}".`);
2590
+ const receiptContent = await fs.readFile(receiptPath);
2591
+ let actualReceipt;
2592
+ try {
2593
+ actualReceipt = parseCodexNativeRenderReceipt(receiptContent.toString('utf8'));
2594
+ }
2595
+ catch {
2596
+ actualReceipt = undefined;
2597
+ }
2598
+ const verified = options.verifiedRenderReceipt
2599
+ && JSON.stringify(actualReceipt) === JSON.stringify(options.verifiedRenderReceipt);
2600
+ if (!verified) {
2601
+ const receiptLeak = findSensitiveTokenPath({ [CODEX_NATIVE_RENDER_RECEIPT_PATH]: receiptContent }, options.sensitiveValues);
2602
+ if (receiptLeak) {
2603
+ throw new Error(`codexNativeRenderSensitiveValue: Codex native render receipt exposes sensitive config in ` +
2604
+ `"${receiptLeak}".`);
2605
+ }
2542
2606
  }
2543
2607
  }
2544
2608
  }
@@ -2596,7 +2660,7 @@ async function prepareCodexNativeRender(options) {
2596
2660
  requirementsReceipt: requirementsReceipt(options.plugin),
2597
2661
  sourceRequirementsReceiptPolicy: 'allow',
2598
2662
  });
2599
- assertNoSensitiveConfigInFiles(tree.files, collectSensitivePluginConfigValues(options.plugin.manifest, rendered.context.config.values), 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
2663
+ assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), collectSensitivePluginConfigValues(options.plugin.manifest, rendered.context.config.values), 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
2600
2664
  const skillKey = artifact.skillKey;
2601
2665
  renderedArtifacts.push({
2602
2666
  artifact,
@@ -2693,6 +2757,7 @@ async function materializeCodexNativeRender(options) {
2693
2757
  return {
2694
2758
  payloadDigest,
2695
2759
  projection: options.prepared.projection,
2760
+ receipt,
2696
2761
  presentSkillKeys: options.prepared.presentSkillKeys,
2697
2762
  targetSkillKeys: options.prepared.targetSkillKeys,
2698
2763
  };
@@ -3085,6 +3150,7 @@ function createNativeDriver(options) {
3085
3150
  payloadRoot: path.join(stagingRoot, 'plugins', candidate.identity.pluginName),
3086
3151
  allowedRoot: stagingRoot,
3087
3152
  sensitiveValues: await resolveSensitiveUserConfigValues(candidate.plugin, options.context.env),
3153
+ ...(nativeRender ? { verifiedRenderReceipt: nativeRender.receipt } : {}),
3088
3154
  });
3089
3155
  }
3090
3156
  }
@@ -3478,6 +3544,7 @@ function createNativeDriver(options) {
3478
3544
  await assertNoSensitiveValuesInCanonicalCodexNativePayload({
3479
3545
  plugin: target.plugin,
3480
3546
  marketplaceRoot: target.identity.marketplaceRoot ?? resolveAwHome(options.context.env),
3547
+ marketplaceName: target.identity.marketplaceName ?? MARKETPLACE_NAME,
3481
3548
  env: options.context.env,
3482
3549
  sensitiveValues,
3483
3550
  });
@@ -3803,7 +3870,7 @@ function createNativeDriver(options) {
3803
3870
  return target.artifacts;
3804
3871
  },
3805
3872
  runPreApply: async (invocation) => {
3806
- await runPreApplyScript({
3873
+ const run = () => runPreApplyScript({
3807
3874
  plugin: invocation.plugin,
3808
3875
  platform: invocation.target.identity.platform,
3809
3876
  scope: invocation.target.identity.scope,
@@ -3811,6 +3878,36 @@ function createNativeDriver(options) {
3811
3878
  context: options.context,
3812
3879
  scriptTimeoutMs: options.scriptTimeoutMs,
3813
3880
  });
3881
+ const cache = options.context.preApplyExecutionCache;
3882
+ if (!cache) {
3883
+ await run();
3884
+ return;
3885
+ }
3886
+ const { identity } = invocation.target;
3887
+ const sourceIdentity = invocation.plugin.sourceIdentity ?? identity.sourceIdentity;
3888
+ const pluginId = invocation.plugin.pluginId ?? identity.pluginId;
3889
+ if (!sourceIdentity || !pluginId) {
3890
+ await run();
3891
+ return;
3892
+ }
3893
+ const projectKey = identity.scope === 'project'
3894
+ ? identity.platform === 'codex'
3895
+ ? await resolveCodexProjectKey(identity.cwd)
3896
+ : await resolveClaudeProjectKey(identity.cwd)
3897
+ : '';
3898
+ const key = [
3899
+ sourceIdentity,
3900
+ pluginId,
3901
+ identity.platform,
3902
+ identity.scope,
3903
+ projectKey,
3904
+ ].join('\0');
3905
+ let execution = cache.get(key);
3906
+ if (!execution) {
3907
+ execution = run();
3908
+ cache.set(key, execution);
3909
+ }
3910
+ await execution;
3814
3911
  },
3815
3912
  };
3816
3913
  }