@aexol/opencode-wizard 0.3.6 → 0.3.8

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.
@@ -21,9 +21,34 @@ export { buildSystemNote, resolvePluginStatusSnapshot, toPluginAuthStateSummary,
21
21
  const importOpencodePluginModule = new Function('specifier', 'return import(specifier)');
22
22
  export { resolvePluginStatusSnapshotWithAuthBootstrap } from './auth-bootstrap.js';
23
23
  export { setPublishedSkillIgnored, setPublishedSkillInstalled } from './preferences.js';
24
- const getDetailCacheKey = (catalogCacheKey, skillVersionId) => {
25
- return JSON.stringify([catalogCacheKey, skillVersionId]);
24
+ const getDetailCacheKey = (catalogCacheKey, skillVersionId, revision) => {
25
+ return JSON.stringify([catalogCacheKey, skillVersionId, revision]);
26
26
  };
27
+ const getPublishedSkillRevision = item => {
28
+ return item.publishedArtifact.revision ?? `${item.publishedArtifact.checksum}:${item.publishedArtifact.updatedAtCursor ?? item.publishedArtifact.publishedAt}`;
29
+ };
30
+ const getCatalogCursor = (items, catalogItems) => {
31
+ return [...items.map(getPublishedSkillRevision), ...catalogItems.map(item => getPublishedSkillRevision({
32
+ ...item,
33
+ assignmentSource: 'CATALOG',
34
+ assignmentType: 'PATH',
35
+ scopePath: '',
36
+ includeChildren: true
37
+ }))].sort().join('|');
38
+ };
39
+ const getWizardArtifactRevision = item => {
40
+ return item.artifactVersion.revision ?? `${item.artifactVersion.checksum}:${item.artifactVersion.updatedAtCursor ?? item.artifactVersion.publishedAt ?? 'unpublished'}`;
41
+ };
42
+ const getWizardArtifactCatalogCursor = (items, catalogItems) => {
43
+ return [...items.map(getWizardArtifactRevision), ...catalogItems.map(getWizardArtifactRevision)].sort().join('|');
44
+ };
45
+ const toWizardArtifactCatalogCursorItems = items => items.map(item => ({
46
+ ...item,
47
+ assignmentSource: 'CATALOG',
48
+ assignmentType: 'PATH',
49
+ scopePath: '',
50
+ includeChildren: true
51
+ }));
27
52
  const getDetailInflightKey = (catalogCacheKey, skillVersionId, purpose) => {
28
53
  return JSON.stringify([catalogCacheKey, skillVersionId, purpose]);
29
54
  };
@@ -102,7 +127,9 @@ export const OpencodeWizardSkillsPlugin = async input => {
102
127
  const cache = new Map();
103
128
  const catalogInflight = new Map();
104
129
  const detailCache = new Map();
130
+ const wizardArtifactDetailCache = new Map();
105
131
  const detailInflight = new Map();
132
+ const wizardArtifactDetailInflight = new Map();
106
133
  const initialAuthState = await resolveStoredAuthState(input.worktree, config);
107
134
  const loginBootstrap = {
108
135
  promise: null,
@@ -197,7 +224,9 @@ export const OpencodeWizardSkillsPlugin = async input => {
197
224
  cache.clear();
198
225
  catalogInflight.clear();
199
226
  detailCache.clear();
227
+ wizardArtifactDetailCache.clear();
200
228
  detailInflight.clear();
229
+ wizardArtifactDetailInflight.clear();
201
230
  };
202
231
  const persistAuthState = async session => {
203
232
  const authState = toAuthState(session);
@@ -337,7 +366,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
337
366
  });
338
367
  cache.set(cacheKey, {
339
368
  result: fetchResult,
340
- expiresAt: Date.now() + CACHE_TTL_MS
369
+ expiresAt: Date.now() + CACHE_TTL_MS,
370
+ cursor: fetchResult.ok ? getCatalogCursor(fetchResult.payload.skills, fetchResult.payload.catalogSkills) : fetchResult.fetchedAt
341
371
  });
342
372
  return {
343
373
  directoryPath,
@@ -362,7 +392,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
362
392
  const directoryPath = workspaceResolution.directoryPath;
363
393
  const preferenceContext = await resolvePublishedSkillPreferenceCacheContext(config);
364
394
  const catalogCacheKey = getCatalogCacheKey(workspaceResolution, preferenceContext);
365
- const cacheKey = getDetailCacheKey(catalogCacheKey, item.skillVersion.id);
395
+ const itemRevision = getPublishedSkillRevision(item);
396
+ const cacheKey = getDetailCacheKey(catalogCacheKey, item.skillVersion.id, itemRevision);
366
397
  const inflightKey = getDetailInflightKey(catalogCacheKey, item.skillVersion.id, purpose);
367
398
  const cached = detailCache.get(cacheKey);
368
399
  if (useCache && cached && cached.expiresAt > Date.now()) {
@@ -412,7 +443,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
412
443
  }
413
444
  detailCache.set(cacheKey, {
414
445
  artifact: detailResult.artifact,
415
- expiresAt: Date.now() + CACHE_TTL_MS
446
+ expiresAt: Date.now() + CACHE_TTL_MS,
447
+ revision: itemRevision
416
448
  });
417
449
  return {
418
450
  ok: true,
@@ -429,6 +461,62 @@ export const OpencodeWizardSkillsPlugin = async input => {
429
461
  detailInflight.delete(inflightKey);
430
462
  }
431
463
  };
464
+ const loadWizardArtifactDetail = async ({
465
+ workspaceResolution,
466
+ item,
467
+ signal,
468
+ useCache,
469
+ purpose,
470
+ artifactKind
471
+ }) => {
472
+ const preferenceContext = await resolvePublishedSkillPreferenceCacheContext(config);
473
+ const catalogCacheKey = getCatalogCacheKey(workspaceResolution, preferenceContext);
474
+ const itemRevision = getWizardArtifactRevision(item);
475
+ const cacheKey = getDetailCacheKey(catalogCacheKey, item.artifactVersion.id, itemRevision);
476
+ const inflightKey = JSON.stringify([catalogCacheKey, item.artifactVersion.id, itemRevision, purpose]);
477
+ const cached = wizardArtifactDetailCache.get(cacheKey);
478
+ if (useCache && cached && cached.expiresAt > Date.now()) {
479
+ return {
480
+ ok: true,
481
+ artifact: cached.artifact
482
+ };
483
+ }
484
+ const inflight = wizardArtifactDetailInflight.get(inflightKey);
485
+ if (useCache && inflight) {
486
+ const inflightResult = await inflight;
487
+ if (!inflightResult.ok) return inflightResult;
488
+ return {
489
+ ok: true,
490
+ artifact: inflightResult.artifact
491
+ };
492
+ }
493
+ const requestPromise = fetchWizardArtifactDetail({
494
+ worktree: input.worktree,
495
+ config,
496
+ resolution: workspaceResolution,
497
+ artifactKind,
498
+ artifactVersionId: item.artifactVersion.id,
499
+ signal,
500
+ onAuthStateChanged: clearPublishedSkillState,
501
+ purpose
502
+ });
503
+ wizardArtifactDetailInflight.set(inflightKey, requestPromise);
504
+ try {
505
+ const detailResult = await requestPromise;
506
+ if (!detailResult.ok) return detailResult;
507
+ wizardArtifactDetailCache.set(cacheKey, {
508
+ artifact: detailResult.artifact,
509
+ expiresAt: Date.now() + CACHE_TTL_MS,
510
+ revision: itemRevision
511
+ });
512
+ return {
513
+ ok: true,
514
+ artifact: detailResult.artifact
515
+ };
516
+ } finally {
517
+ wizardArtifactDetailInflight.delete(inflightKey);
518
+ }
519
+ };
432
520
  const executePublishedSkillsFetchTool = async ({
433
521
  args,
434
522
  context
@@ -520,13 +608,15 @@ export const OpencodeWizardSkillsPlugin = async input => {
520
608
  },
521
609
  fetchedAt: filteredPublishedSkillsResult.fetchResult.fetchedAt,
522
610
  source: filteredPublishedSkillsResult.fetchResult.source,
611
+ cacheCursor: getCatalogCursor(filteredPublishedSkillsResult.fetchResult.payload.skills, filteredPublishedSkillsResult.fetchResult.payload.catalogSkills),
523
612
  cacheTtlMs: CACHE_TTL_MS,
524
- message: args.refresh ? 'Catalog discovery refreshed from the backend. Provide `skill` for one identifier or prefer `skills` for comma/newline-separated multiple identifiers to fetch markdown bodies/details.' : 'Catalog discovery only. Cached results expire automatically after 30 seconds, or pass `refresh: true` to force a backend refresh immediately. Provide `skill` for one identifier or prefer `skills` for comma/newline-separated multiple identifiers to fetch markdown bodies/details.'
613
+ message: args.refresh ? 'Catalog discovery refreshed from the backend. Provide `skill` for one identifier or prefer `skills` for comma/newline-separated multiple identifiers to fetch markdown bodies/details.' : 'Catalog discovery only. Cached results include deterministic revision cursors; pass `refresh: true` to force a backend refresh immediately. Provide `skill` for one identifier or prefer `skills` for comma/newline-separated multiple identifiers to fetch markdown bodies/details.'
525
614
  }, null, 2),
526
615
  metadata: {
527
616
  status: 'ready',
528
617
  ...toWorkspaceResolutionMetadata(filteredPublishedSkillsResult.workspaceResolution),
529
618
  source: filteredPublishedSkillsResult.fetchResult.source,
619
+ cacheCursor: getCatalogCursor(filteredPublishedSkillsResult.fetchResult.payload.skills, filteredPublishedSkillsResult.fetchResult.payload.catalogSkills),
530
620
  publishedSkillCount: catalog.publishedSkillCount.toString(),
531
621
  globalAssignmentCount: catalog.assignmentCounts.global.toString(),
532
622
  projectAssignmentCount: catalog.assignmentCounts.project.toString(),
@@ -696,6 +786,7 @@ export const OpencodeWizardSkillsPlugin = async input => {
696
786
  pluginId: PLUGIN_ID,
697
787
  availableTools: resolveAvailableTools(authState?.role ?? null)
698
788
  });
789
+ const cacheCursor = getWizardArtifactCatalogCursor(fetchResult.payload.artifacts, toWizardArtifactCatalogCursorItems(fetchResult.payload.catalogArtifacts));
699
790
  return {
700
791
  output: JSON.stringify({
701
792
  ...catalog,
@@ -704,14 +795,18 @@ export const OpencodeWizardSkillsPlugin = async input => {
704
795
  workspaceResolution: toWorkspaceResolutionOutput(workspaceResolution),
705
796
  fetchedAt: fetchResult.fetchedAt,
706
797
  source: fetchResult.source,
707
- message: 'DESIGN_DOC catalog discovery only. Full DESIGN.md bodies/files require opencode_wizard_artifact_fetch with artifactKind DESIGN_DOC.'
798
+ cacheCursor,
799
+ cacheTtlMs: CACHE_TTL_MS,
800
+ message: 'Generic artifact catalog discovery only. Full bodies/files require opencode_wizard_artifact_fetch with artifactKind and artifact identifiers.'
708
801
  }, null, 2),
709
802
  metadata: {
710
803
  status: 'ready',
711
804
  artifactKind,
712
805
  artifactCount: catalog.artifactCount.toString(),
713
806
  ...toWorkspaceResolutionMetadata(workspaceResolution),
714
- source: fetchResult.source
807
+ source: fetchResult.source,
808
+ cacheCursor,
809
+ cacheTtlMs: CACHE_TTL_MS.toString()
715
810
  }
716
811
  };
717
812
  }
@@ -775,6 +870,7 @@ export const OpencodeWizardSkillsPlugin = async input => {
775
870
  pluginId: PLUGIN_ID,
776
871
  availableTools: resolveAvailableTools(authState?.role ?? null)
777
872
  });
873
+ const cacheCursor = getWizardArtifactCatalogCursor(fetchResult.payload.artifacts, toWizardArtifactCatalogCursorItems(fetchResult.payload.catalogArtifacts));
778
874
  return {
779
875
  output: JSON.stringify({
780
876
  ...catalog,
@@ -783,11 +879,15 @@ export const OpencodeWizardSkillsPlugin = async input => {
783
879
  workspaceResolution: toWorkspaceResolutionOutput(workspaceResolution),
784
880
  fetchedAt: fetchResult.fetchedAt,
785
881
  source: fetchResult.source,
786
- message: 'Provide artifact or artifacts to fetch DESIGN.md body/files.'
882
+ cacheCursor,
883
+ cacheTtlMs: CACHE_TTL_MS,
884
+ message: 'Provide artifact or artifacts to fetch artifact body/files.'
787
885
  }, null, 2),
788
886
  metadata: {
789
887
  status: 'ready',
790
888
  artifactKind,
889
+ cacheCursor,
890
+ cacheTtlMs: CACHE_TTL_MS.toString(),
791
891
  ...toWorkspaceResolutionMetadata(workspaceResolution)
792
892
  }
793
893
  };
@@ -811,15 +911,13 @@ export const OpencodeWizardSkillsPlugin = async input => {
811
911
  }
812
912
  };
813
913
  }
814
- const detailResults = await Promise.all(selection.selectedItems.map(item => fetchWizardArtifactDetail({
815
- worktree: input.worktree,
816
- config,
817
- resolution: workspaceResolution,
818
- artifactKind,
819
- artifactVersionId: item.artifactVersion.id,
914
+ const detailResults = await Promise.all(selection.selectedItems.map(item => loadWizardArtifactDetail({
915
+ workspaceResolution,
916
+ item,
820
917
  signal: context.abort,
821
- onAuthStateChanged: clearPublishedSkillState,
822
- purpose: 'TOOL_FETCH'
918
+ useCache: !args.refresh,
919
+ purpose: 'TOOL_FETCH',
920
+ artifactKind
823
921
  })));
824
922
  const failedDetail = detailResults.find(result => !result.ok);
825
923
  if (failedDetail && !failedDetail.ok) {
@@ -849,6 +947,7 @@ export const OpencodeWizardSkillsPlugin = async input => {
849
947
  artifactVersion: result.artifact
850
948
  });
851
949
  });
950
+ const cacheCursor = getWizardArtifactCatalogCursor(selection.selectedItems, toWizardArtifactCatalogCursorItems(fetchResult.payload.catalogArtifacts));
852
951
  return {
853
952
  output: JSON.stringify({
854
953
  pluginId: PLUGIN_ID,
@@ -859,6 +958,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
859
958
  workspace: fetchResult.payload.workspace,
860
959
  fetchedAt: fetchResult.fetchedAt,
861
960
  source: fetchResult.source,
961
+ cacheCursor,
962
+ cacheTtlMs: CACHE_TTL_MS,
862
963
  requestedArtifacts,
863
964
  missingArtifacts: selection.missingIdentifiers,
864
965
  artifacts: details
@@ -867,6 +968,7 @@ export const OpencodeWizardSkillsPlugin = async input => {
867
968
  status: selection.missingIdentifiers.length > 0 ? 'partial' : 'ready',
868
969
  artifactKind,
869
970
  matchedCount: details.length.toString(),
971
+ cacheTtlMs: CACHE_TTL_MS.toString(),
870
972
  ...toWorkspaceResolutionMetadata(workspaceResolution)
871
973
  }
872
974
  };