@openforge-app/plugin-sdk 0.2.8 → 0.2.10

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/backend.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import type { BackendFileSystemAPI, BackendMethodRegistration, BackendMethodRegistry, BackendOpenForgeAPI, BackendPlugin, BackendPluginContext, BackgroundServiceRegistration, BackgroundServiceRegistry, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, Disposable, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, ExternalReadFileSystemAPI, UserDataDirectoryRequest, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot } from './types';
1
+ import type { BackendFileSystemAPI, BackendMethodRegistration, BackendMethodRegistry, BackendOpenForgeAPI, BackendPlugin, BackendPluginContext, BackgroundServiceRegistration, BackgroundServiceRegistry, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, Disposable, ExternalFileMetadata, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, ExternalReadFileSystemAPI, UserDataDirectoryRequest, UserDataFileAppendResult, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot } from './types';
2
2
  export declare function defineBackendPlugin<const TPlugin extends BackendPlugin>(plugin: TPlugin): TPlugin;
3
- export type { BackendFileSystemAPI, BackendMethodRegistration, BackendMethodRegistry, BackendOpenForgeAPI, BackendPlugin, BackendPluginContext, BackgroundServiceRegistration, BackgroundServiceRegistry, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, Disposable, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, ExternalReadFileSystemAPI, UserDataDirectoryRequest, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, };
3
+ export type { BackendFileSystemAPI, BackendMethodRegistration, BackendMethodRegistry, BackendOpenForgeAPI, BackendPlugin, BackendPluginContext, BackgroundServiceRegistration, BackgroundServiceRegistry, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, Disposable, ExternalFileMetadata, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, ExternalReadFileSystemAPI, UserDataDirectoryRequest, UserDataFileAppendResult, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, };
@@ -2,6 +2,7 @@ import { writable } from 'svelte/store';
2
2
  // The store started life in the host app as src/lib/infoPanelSectionState.ts. The key is
3
3
  // deliberately unchanged: renaming it would reset every user's collapsed sections.
4
4
  export const COLLAPSED_SECTIONS_STORAGE_KEY = 'openforge.infoPanelSectionCollapse.v1';
5
+ const SHARED_STATE_SYMBOL = Symbol.for('com.openforge.plugin-sdk.collapsible-section-state.v1');
5
6
  export function pluginSectionKey(pluginId, sectionKey) {
6
7
  return `plugin:${pluginId}:${sectionKey}`;
7
8
  }
@@ -13,10 +14,10 @@ function getLocalStorage() {
13
14
  return null;
14
15
  }
15
16
  }
16
- function readPersisted() {
17
+ function readPersisted(fallback = {}) {
17
18
  const storage = getLocalStorage();
18
19
  if (storage === null)
19
- return {};
20
+ return fallback;
20
21
  try {
21
22
  const rawValue = storage.getItem(COLLAPSED_SECTIONS_STORAGE_KEY);
22
23
  if (rawValue === null)
@@ -32,7 +33,7 @@ function readPersisted() {
32
33
  return state;
33
34
  }
34
35
  catch {
35
- return {};
36
+ return fallback;
36
37
  }
37
38
  }
38
39
  function writePersisted(state) {
@@ -56,23 +57,40 @@ function writePersisted(state) {
56
57
  // Persistence is best effort and must not block the section from rendering.
57
58
  }
58
59
  }
59
- const { subscribe, set, update } = writable(readPersisted());
60
+ function updatePersistedSection(current, key, collapsed) {
61
+ // localStorage is the cross-realm source of truth. Re-read it for every mutation so
62
+ // a bundle that cannot share this page's singleton still cannot overwrite newer keys.
63
+ const next = { ...readPersisted(current) };
64
+ if (collapsed)
65
+ next[key] = true;
66
+ else
67
+ delete next[key];
68
+ writePersisted(next);
69
+ return next;
70
+ }
71
+ function getSharedState() {
72
+ const registry = globalThis;
73
+ const existing = registry[SHARED_STATE_SYMBOL];
74
+ if (existing !== undefined)
75
+ return existing;
76
+ // Trusted Plugin bundles contain their own copy of this module. Symbol.for gives those
77
+ // copies and the host one live access to the same Svelte store in this renderer realm.
78
+ const shared = { store: writable(readPersisted()) };
79
+ registry[SHARED_STATE_SYMBOL] = shared;
80
+ return shared;
81
+ }
82
+ const { subscribe, set, update } = getSharedState().store;
60
83
  export const collapsedSections = { subscribe };
61
84
  export function isSectionCollapsed(state, key) {
62
85
  return state[key] === true;
63
86
  }
64
87
  export function setSectionCollapsed(key, collapsed) {
65
- update((current) => {
66
- const next = { ...current, [key]: collapsed };
67
- writePersisted(next);
68
- return next;
69
- });
88
+ update((current) => updatePersistedSection(current, key, collapsed));
70
89
  }
71
90
  export function toggleSection(key) {
72
91
  update((current) => {
73
- const next = { ...current, [key]: !isSectionCollapsed(current, key) };
74
- writePersisted(next);
75
- return next;
92
+ const latest = readPersisted(current);
93
+ return updatePersistedSection(latest, key, !isSectionCollapsed(latest, key));
76
94
  });
77
95
  }
78
96
  export function clearCollapsedSections() {
package/dist/domain.d.ts CHANGED
@@ -179,6 +179,7 @@ export interface MergeReadinessInfo extends MergeStatusInfo {
179
179
  review_status?: string | null;
180
180
  draft?: boolean;
181
181
  is_queued?: boolean;
182
+ merge_queue_required?: boolean | null;
182
183
  unaddressed_comment_count?: number;
183
184
  head_sha?: string | null;
184
185
  updated_at?: number | null;
package/dist/domain.js CHANGED
@@ -199,7 +199,8 @@ export function getMergeReadiness(pr, options = {}) {
199
199
  warnings.push(mergeReadinessDetail('unprotected_fallback', 'Using simple mergeability because no protected-branch checks or review state are available.'));
200
200
  }
201
201
  if (hasDirectMergeability || isUnprotectedFallback) {
202
- return mergeReadinessResult(pr, options.requireMergeQueue === true ? 'ready_to_enqueue' : 'ready_to_merge', options.requireMergeQueue === true ? 'enqueue' : 'merge', blockers, warnings);
202
+ const mergeQueueRequired = options.requireMergeQueue === true || pr.merge_queue_required === true;
203
+ return mergeReadinessResult(pr, mergeQueueRequired ? 'ready_to_enqueue' : 'ready_to_merge', mergeQueueRequired ? 'enqueue' : 'merge', blockers, warnings);
203
204
  }
204
205
  if (mergeableState === 'unknown' || pr.mergeable === null || (mergeableState === null && pr.mergeable !== false)) {
205
206
  warnings.push(mergeReadinessDetail('mergeability_unknown', 'GitHub has not reported definitive mergeability yet.'));
@@ -1,5 +1,5 @@
1
1
  import type { BrowserSurfaceCapture, BrowserDevToolsPanel, BrowserSurfaceFeedbackSelection, BrowserSurfaceVisualFeedback, BrowserSurfaceRegion, BrowserSurfaceErrorCode, BrowserSurfaceNavigationError, BrowserSurfacesAPI, GetOrCreateBrowserSurfaceRequest, TaskBrowserSurfaceController, TaskBrowserSurfaceState } from './browserSurfaces';
2
- import type { CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, Disposable, FrontendBackendBridge, FrontendOpenForgeAPI, FrontendPlugin, FrontendPluginContext, FrontendSettingsRegistry, FrontendTaskPaneRegistry, FrontendInjectionPointRegistry, FrontendTaskStartRegistry, FrontendTaskUIRegistry, FrontendViewRegistry, InjectionPointLocation, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, TaskLinkHandler, TaskLinkHandlerResult, TaskLinkOpenRequest, TaskLinksAPI, PluginIcon, PluginSidebarNavigationProps, PluginSidebarViewIdentity, PluginInjectionPointProps, PluginInjectionPointRegistration, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsSectionScope, PluginStorageScope, PluginSvgIcon, PluginTaskPaneProps, PluginTaskPaneTabRegistration, PluginTaskUISectionProps, PluginTaskUISectionRegistration, PluginViewProps, PluginViewRegistration, TaskStartPrefixContext, TaskStartPrefixProviderRegistration, PtyBufferState, TerminalImageProtocol, TerminalViewSnapshot } from './types';
2
+ import type { CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, Disposable, FrontendBackendBridge, FrontendOpenForgeAPI, FrontendPlugin, FrontendPluginContext, FrontendSettingsRegistry, FrontendTaskPaneRegistry, FrontendInjectionPointRegistry, FrontendTaskStartRegistry, FrontendReviewUIRegistry, FrontendTaskUIRegistry, FrontendViewRegistry, InjectionPointLocation, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, PluginIcon, PluginSidebarNavigationProps, PluginSidebarViewIdentity, PluginInjectionPointProps, PluginInjectionPointRegistration, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsSectionScope, PluginStorageScope, PluginSvgIcon, PluginTaskPaneProps, PluginTaskPaneTabRegistration, PluginReviewRowActionProps, PluginReviewRowActionRegistration, PluginTaskUISectionProps, PluginTaskUISectionRegistration, PluginViewProps, PluginViewRegistration, TaskStartPrefixContext, TaskStartPrefixProviderRegistration, PtyBufferState, TerminalImageProtocol, TerminalViewSnapshot } from './types';
3
3
  export declare const OPENFORGE_FRONTEND_PLUGIN_MARKER = "__openforgeFrontendPlugin";
4
4
  export type MarkedFrontendPlugin<TPlugin extends FrontendPlugin = FrontendPlugin> = TPlugin & {
5
5
  readonly [OPENFORGE_FRONTEND_PLUGIN_MARKER]: true;
@@ -7,4 +7,4 @@ export type MarkedFrontendPlugin<TPlugin extends FrontendPlugin = FrontendPlugin
7
7
  export declare function defineFrontendPlugin<const TPlugin extends FrontendPlugin>(plugin: TPlugin): MarkedFrontendPlugin<TPlugin>;
8
8
  export { BrowserSurfaceError, isAllowedBrowserSurfaceUrl } from './browserSurfaces';
9
9
  export type { BrowserSurfaceCapture, BrowserDevToolsPanel, BrowserSurfaceFeedbackSelection, BrowserSurfaceVisualFeedback, BrowserSurfaceRegion, BrowserSurfaceErrorCode, BrowserSurfaceNavigationError, BrowserSurfacesAPI, GetOrCreateBrowserSurfaceRequest, TaskBrowserSurfaceController, TaskBrowserSurfaceState, };
10
- export type { CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, Disposable, FrontendBackendBridge, FrontendOpenForgeAPI, FrontendPlugin, FrontendPluginContext, FrontendSettingsRegistry, FrontendTaskPaneRegistry, FrontendInjectionPointRegistry, FrontendTaskStartRegistry, FrontendTaskUIRegistry, FrontendViewRegistry, InjectionPointLocation, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, TaskLinkHandler, TaskLinkHandlerResult, TaskLinkOpenRequest, TaskLinksAPI, PluginIcon, PluginSidebarNavigationProps, PluginSidebarViewIdentity, PluginInjectionPointProps, PluginInjectionPointRegistration, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsSectionScope, PluginStorageScope, PluginSvgIcon, PluginTaskPaneProps, PluginTaskPaneTabRegistration, PluginTaskUISectionProps, PluginTaskUISectionRegistration, PluginViewProps, PluginViewRegistration, TaskStartPrefixContext, TaskStartPrefixProviderRegistration, PtyBufferState, TerminalImageProtocol, TerminalViewSnapshot, };
10
+ export type { CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, Disposable, FrontendBackendBridge, FrontendOpenForgeAPI, FrontendPlugin, FrontendPluginContext, FrontendSettingsRegistry, FrontendTaskPaneRegistry, FrontendInjectionPointRegistry, FrontendTaskStartRegistry, FrontendReviewUIRegistry, FrontendTaskUIRegistry, FrontendViewRegistry, InjectionPointLocation, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, PluginIcon, PluginSidebarNavigationProps, PluginSidebarViewIdentity, PluginInjectionPointProps, PluginInjectionPointRegistration, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsSectionScope, PluginStorageScope, PluginSvgIcon, PluginTaskPaneProps, PluginTaskPaneTabRegistration, PluginReviewRowActionProps, PluginReviewRowActionRegistration, PluginTaskUISectionProps, PluginTaskUISectionRegistration, PluginViewProps, PluginViewRegistration, TaskStartPrefixContext, TaskStartPrefixProviderRegistration, PtyBufferState, TerminalImageProtocol, TerminalViewSnapshot, };
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ export { BrowserSurfaceError, isAllowedBrowserSurfaceUrl } from './browserSurfac
2
2
  export type { BrowserSurfaceCapture, BrowserSurfaceFeedbackSelection, BrowserSurfaceVisualFeedback, BrowserSurfaceRegion, BrowserSurfaceErrorCode, BrowserSurfaceNavigationError, BrowserSurfacesAPI, GetOrCreateBrowserSurfaceRequest, TaskBrowserSurfaceController, TaskBrowserSurfaceState, } from './browserSurfaces';
3
3
  export { OPENFORGE_PACKAGE_METADATA_SCHEMA, OPENFORGE_PLUGIN_CAPABILITIES, isOpenForgePackageMetadata, isPluginPackageMetadata, isSupportedOpenForgeApiVersion, validateOpenForgePackageMetadata, validatePluginPackageMetadata, } from './manifest';
4
4
  export { createMemoryPluginStorage, createMockBackendOpenForgeApi, createMockFrontendOpenForgeApi, createMockOpenForgeApi, createMockPluginContext, createOpenForgeRegistryFake, createTestingCalls, TestingOpenForgeRegistryFake, TestingSubscriptionSink, } from './testing';
5
- export { DEFAULT_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, TaskFollowUpError, MAX_SUPPORTED_API_VERSION, MIN_SUPPORTED_API_VERSION, OPENFORGE_PLUGIN_API_VERSION, SUPPORTED_OPENFORGE_API_VERSIONS, isPluginViewKey, resolveExternalTextFileChunkSize, makePluginViewKey, parsePluginViewKey, } from './types';
6
- export type { AgentCommandDescriptor, AgentCommandMetadata, AgentCommandRuntime, AttentionAPI, BackendReadyState, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, ConfigureStartPromptContributionRequest, CreateTaskRequest, Disposable, BackendFileSystemAPI, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, ExternalReadFileSystemAPI, FileSystemAPI, UserDataDirectoryRequest, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, ImplementationRun, InjectionPointLocation, JsonObject, JsonPrimitive, JsonSchema, JsonValue, MaybePromise, KeyValueConfigAPI, NotificationRequest, NavigationAPI, NotificationsAPI, OpenForgeCommonAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, OpenForgePackageMetadata, OpenForgePluginCapability, OpenForgePluginContext, OpenForgePluginPackageJson, PluginCommandInvocationContext, PluginCommandInvocationSource, PluginComponentLoader, PluginComponentModule, PluginEntry, PluginIcon, PluginSidebarNavigationProps, PluginSidebarViewIdentity, PluginInjectionPointProps, PluginSettingsSectionProps, PluginState, PluginSvgIcon, PluginTaskPaneProps, PluginTaskUISectionProps, PluginViewProps, PluginStorage, PluginStorageScope, PluginViewKey, ProjectsAPI, PtyBufferState, ShellAPI, ShellResizeRequest, TaskStartPrefixContext, TaskStartPrefixProviderRegistration, TerminalImageProtocol, TerminalViewSnapshot, ShellSessionRequest, ShellSpawnRequest, ShellTerminalQueryResponseRequest, ShellWriteRequest, StartPromptContribution, SendTaskFollowUpRequest, StartTaskImplementationRequest, TaskFollowUpDisposition, TaskFollowUpErrorCode, TaskFollowUpReceipt, SubscriptionSink, SupportedOpenForgeApiVersion, SystemAPI, TaskLinkHandler, TaskLinkHandlerResult, TaskLinkOpenRequest, TaskLinksAPI, TasksAPI, ValidationError, } from './types';
5
+ export { MAX_AGENT_SESSION_PAGE_SIZE, DEFAULT_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, TaskFollowUpError, MAX_SUPPORTED_API_VERSION, MIN_SUPPORTED_API_VERSION, OPENFORGE_PLUGIN_API_VERSION, SUPPORTED_OPENFORGE_API_VERSIONS, isPluginViewKey, resolveExternalTextFileChunkSize, makePluginViewKey, parsePluginViewKey, } from './types';
6
+ export type { AgentSessionCursor, AgentSessionOverlap, AgentSessionsAPI, AgentSessionSummary, AgentSessionSummaryPage, AgentSessionTaskSummary, AgentSessionWorkspace, AgentCommandDescriptor, AgentCommandMetadata, AgentCommandRuntime, AttentionAPI, BackendReadyState, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, ConfigureStartPromptContributionRequest, CreateTaskRequest, Disposable, BackendFileSystemAPI, ExternalFileMetadata, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, ExternalReadFileSystemAPI, FileSystemAPI, UserDataDirectoryRequest, UserDataFileAppendResult, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, ImplementationRun, ListAgentSessionsRequest, ListTaskSessionsRequest, InjectionPointLocation, JsonObject, JsonPrimitive, JsonSchema, JsonValue, MaybePromise, KeyValueConfigAPI, NotificationRequest, NavigationAPI, NotificationsAPI, OpenForgeCommonAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, OpenForgePackageMetadata, OpenForgePluginCapability, OpenForgePluginContext, OpenForgePluginPackageJson, PluginCommandInvocationContext, PluginCommandInvocationSource, PluginComponentLoader, PluginComponentModule, PluginEntry, PluginIcon, PluginSidebarNavigationProps, PluginSidebarViewIdentity, PluginInjectionPointProps, PluginSettingsSectionProps, PluginState, PluginSvgIcon, PluginTaskPaneProps, PluginTaskUISectionProps, PluginViewProps, PluginStorage, PluginStorageScope, PluginViewKey, ProjectsAPI, PtyBufferState, ShellAPI, ShellResizeRequest, TaskStartPrefixContext, TaskStartPrefixProviderRegistration, TerminalImageProtocol, TerminalViewSnapshot, ShellSessionRequest, ShellSpawnRequest, ShellWriteRequest, StartPromptContribution, SendTaskFollowUpRequest, StartTaskImplementationRequest, TaskFollowUpDisposition, TaskFollowUpErrorCode, TaskFollowUpReceipt, SubscriptionSink, SupportedOpenForgeApiVersion, SystemAPI, TasksAPI, ValidationError, } from './types';
7
7
  export type { MockBackendOpenForgeAPI, MockFrontendOpenForgeAPI, TestingBackgroundServiceContribution, TestingBackendMethodContribution, TestingCommandContribution, TestingContributionBase, TestingExternalTextFile, TestingExternalTextFileChunksCall, TestingEventListenerContribution, TestingOpenForgeApiCalls, TestingOpenForgeApiOptions, TestingOpenForgeRegistrySnapshot, TestingRuntimeKind, TestingRuntimeScope, TestingSettingsSectionContribution, TestingTaskPaneTabContribution, TestingTaskUISectionContribution, TestingViewContribution, } from './testing';
8
8
  export { parseStrictFiniteNumber } from './numberParsing';
9
9
  export { buildProjectFileTree, flattenVisibleProjectFileTree, formatProjectFileTreeSize, getProjectFileTreeDepth, getProjectFileTreeItemAccessibility, getProjectFileTreeKeyboardAction, getProjectFileTreeParentPath, hasProjectFileTreeShortcutModifier, projectFileTreePathToId, } from './projectFileTree';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { BrowserSurfaceError, isAllowedBrowserSurfaceUrl } from './browserSurfaces';
2
2
  export { OPENFORGE_PACKAGE_METADATA_SCHEMA, OPENFORGE_PLUGIN_CAPABILITIES, isOpenForgePackageMetadata, isPluginPackageMetadata, isSupportedOpenForgeApiVersion, validateOpenForgePackageMetadata, validatePluginPackageMetadata, } from './manifest';
3
3
  export { createMemoryPluginStorage, createMockBackendOpenForgeApi, createMockFrontendOpenForgeApi, createMockOpenForgeApi, createMockPluginContext, createOpenForgeRegistryFake, createTestingCalls, TestingOpenForgeRegistryFake, TestingSubscriptionSink, } from './testing';
4
- export { DEFAULT_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, TaskFollowUpError, MAX_SUPPORTED_API_VERSION, MIN_SUPPORTED_API_VERSION, OPENFORGE_PLUGIN_API_VERSION, SUPPORTED_OPENFORGE_API_VERSIONS, isPluginViewKey, resolveExternalTextFileChunkSize, makePluginViewKey, parsePluginViewKey, } from './types';
4
+ export { MAX_AGENT_SESSION_PAGE_SIZE, DEFAULT_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, TaskFollowUpError, MAX_SUPPORTED_API_VERSION, MIN_SUPPORTED_API_VERSION, OPENFORGE_PLUGIN_API_VERSION, SUPPORTED_OPENFORGE_API_VERSIONS, isPluginViewKey, resolveExternalTextFileChunkSize, makePluginViewKey, parsePluginViewKey, } from './types';
5
5
  export { parseStrictFiniteNumber } from './numberParsing';
6
6
  export { buildProjectFileTree, flattenVisibleProjectFileTree, formatProjectFileTreeSize, getProjectFileTreeDepth, getProjectFileTreeItemAccessibility, getProjectFileTreeKeyboardAction, getProjectFileTreeParentPath, hasProjectFileTreeShortcutModifier, projectFileTreePathToId, } from './projectFileTree';
7
7
  export { canMergePullRequest, getMergeReadiness, hasMergeConflicts, isClosedUnmergedPullRequest, isMergedPullRequest, isQueuedForMerge, isReadyToMerge, parseCheckRuns, preservePullRequestState, splitCheckRuns, } from './domain';
@@ -117,6 +117,7 @@ export declare const OPENFORGE_PACKAGE_METADATA_SCHEMA: {
117
117
  backend: {
118
118
  type: string;
119
119
  minLength: number;
120
+ pattern: string;
120
121
  description: string;
121
122
  };
122
123
  requires: {
package/dist/manifest.js CHANGED
@@ -28,6 +28,15 @@ function validateOptionalString(value, path) {
28
28
  }
29
29
  return [];
30
30
  }
31
+ function validateBackendEntry(value) {
32
+ const errors = validateOptionalString(value, 'backend');
33
+ if (!isNonEmptyString(value))
34
+ return errors;
35
+ if (!['.mjs', '.js', '.cjs'].some(extension => value.endsWith(extension))) {
36
+ errors.push({ path: 'backend', message: 'Must point to a built .mjs, .js, or .cjs artifact' });
37
+ }
38
+ return errors;
39
+ }
31
40
  function validateEnablement(value) {
32
41
  if (value === undefined || value === 'app' || value === 'project') {
33
42
  return [];
@@ -117,7 +126,7 @@ export function validateOpenForgePackageMetadata(data) {
117
126
  if (data.frontendStyles !== undefined && !isNonEmptyString(data.frontend)) {
118
127
  errors.push({ path: 'frontendStyles', message: 'Requires a frontend entry' });
119
128
  }
120
- errors.push(...validateOptionalString(data.backend, 'backend'));
129
+ errors.push(...validateBackendEntry(data.backend));
121
130
  errors.push(...validateRequires(data.requires));
122
131
  if (data.enablement === 'app' && (!Array.isArray(data.requires) || !data.requires.includes('appEnablement'))) {
123
132
  errors.push({ path: 'requires', message: 'App enablement requires the appEnablement capability' });
@@ -125,9 +134,6 @@ export function validateOpenForgePackageMetadata(data) {
125
134
  if (Array.isArray(data.requires) && data.requires.includes('browserSurfaces') && !isNonEmptyString(data.frontend)) {
126
135
  errors.push({ path: 'requires', message: 'browserSurfaces capability requires a frontend entry' });
127
136
  }
128
- if (Array.isArray(data.requires) && data.requires.includes('taskLinks') && !isNonEmptyString(data.frontend)) {
129
- errors.push({ path: 'requires', message: 'taskLinks capability requires a frontend entry' });
130
- }
131
137
  if (data.contributes !== undefined) {
132
138
  errors.push({ path: 'contributes', message: 'Manifest contribution arrays are not supported; register contributions at runtime' });
133
139
  }
@@ -11,6 +11,15 @@ export interface ResolvedMarkdownMedia {
11
11
  }
12
12
  /** Attribute holding the original URL of a `<img>`/`<a>` awaiting resolution. */
13
13
  export declare const MARKDOWN_REMOTE_MEDIA_ATTRIBUTE = "data-markdown-remote-src";
14
+ export interface RenderedMarkdownCacheStats {
15
+ capacity: number;
16
+ size: number;
17
+ hits: number;
18
+ misses: number;
19
+ evictions: number;
20
+ }
21
+ export declare function getRenderedMarkdownCacheStats(): Readonly<RenderedMarkdownCacheStats>;
22
+ export declare function clearRenderedMarkdownCache(): void;
14
23
  export declare function resolveMarkdownRepositoryPath(value: string | null, markdownFilePath: string): string | null;
15
24
  export declare function getMarkdownRepositoryLinkSuffix(value: string): string;
16
25
  export declare function resolveMarkdownImageSrc(src: string | null, imageBaseUrl: string | null | undefined, markdownFilePath?: string): string | null;
package/dist/markdown.js CHANGED
@@ -6,6 +6,56 @@ const markedOptions = {
6
6
  gfm: true,
7
7
  breaks: true,
8
8
  };
9
+ const RENDERED_MARKDOWN_CACHE_CAPACITY = 100;
10
+ const renderedMarkdownCache = new Map();
11
+ let renderedMarkdownCacheHits = 0;
12
+ let renderedMarkdownCacheMisses = 0;
13
+ let renderedMarkdownCacheEvictions = 0;
14
+ function renderedMarkdownCacheKey(content, options) {
15
+ return JSON.stringify([
16
+ content,
17
+ options.imageBaseUrl ?? null,
18
+ options.markdownFilePath ?? null,
19
+ Boolean(options.deferRepositoryImages),
20
+ Boolean(options.deferRemoteMedia),
21
+ ]);
22
+ }
23
+ function readRenderedMarkdownCache(key) {
24
+ const cached = renderedMarkdownCache.get(key);
25
+ if (cached === undefined) {
26
+ renderedMarkdownCacheMisses++;
27
+ return undefined;
28
+ }
29
+ renderedMarkdownCacheHits++;
30
+ renderedMarkdownCache.delete(key);
31
+ renderedMarkdownCache.set(key, cached);
32
+ return cached;
33
+ }
34
+ function writeRenderedMarkdownCache(key, html) {
35
+ if (renderedMarkdownCache.size >= RENDERED_MARKDOWN_CACHE_CAPACITY) {
36
+ const oldestKey = renderedMarkdownCache.keys().next().value;
37
+ if (oldestKey !== undefined) {
38
+ renderedMarkdownCache.delete(oldestKey);
39
+ renderedMarkdownCacheEvictions++;
40
+ }
41
+ }
42
+ renderedMarkdownCache.set(key, html);
43
+ }
44
+ export function getRenderedMarkdownCacheStats() {
45
+ return {
46
+ capacity: RENDERED_MARKDOWN_CACHE_CAPACITY,
47
+ size: renderedMarkdownCache.size,
48
+ hits: renderedMarkdownCacheHits,
49
+ misses: renderedMarkdownCacheMisses,
50
+ evictions: renderedMarkdownCacheEvictions,
51
+ };
52
+ }
53
+ export function clearRenderedMarkdownCache() {
54
+ renderedMarkdownCache.clear();
55
+ renderedMarkdownCacheHits = 0;
56
+ renderedMarkdownCacheMisses = 0;
57
+ renderedMarkdownCacheEvictions = 0;
58
+ }
9
59
  function hasAbsoluteOrSpecialUrl(value) {
10
60
  return /^[a-z][a-z\d+.-]*:/i.test(value) || value.startsWith('//') || value.startsWith('#');
11
61
  }
@@ -144,6 +194,12 @@ function prepareMarkdownMediaSources(html, options) {
144
194
  return template.innerHTML;
145
195
  }
146
196
  export function renderMarkdownHtml(content, options = {}) {
197
+ const cacheKey = renderedMarkdownCacheKey(content, options);
198
+ const cached = readRenderedMarkdownCache(cacheKey);
199
+ if (cached !== undefined)
200
+ return cached;
147
201
  const rawHtml = marked.parse(content, markedOptions);
148
- return sanitizeHtml(prepareMarkdownMediaSources(rawHtml, options));
202
+ const html = sanitizeHtml(prepareMarkdownMediaSources(rawHtml, options));
203
+ writeRenderedMarkdownCache(cacheKey, html);
204
+ return html;
149
205
  }
@@ -19,15 +19,6 @@
19
19
  },
20
20
  "then": { "required": ["frontend"] }
21
21
  },
22
- {
23
- "if": {
24
- "properties": {
25
- "requires": { "contains": { "const": "taskLinks" } }
26
- },
27
- "required": ["requires"]
28
- },
29
- "then": { "required": ["frontend"] }
30
- },
31
22
  {
32
23
  "if": {
33
24
  "properties": { "enablement": { "const": "app" } },
@@ -106,7 +97,8 @@
106
97
  "backend": {
107
98
  "type": "string",
108
99
  "minLength": 1,
109
- "description": "Path to the built backend JavaScript entry artifact."
100
+ "pattern": "\\.(?:mjs|js|cjs)$",
101
+ "description": "Path to the built backend JavaScript artifact. Use .mjs for new plugins; .js and .cjs remain supported for compatibility. The host runs each backend in a replaceable worker."
110
102
  },
111
103
  "requires": {
112
104
  "type": "array",
@@ -136,9 +128,9 @@
136
128
  "config",
137
129
  "projectConfig",
138
130
  "browserSurfaces",
139
- "taskLinks",
140
131
  "appEnablement",
141
- "customSidebarNavigation"
132
+ "customSidebarNavigation",
133
+ "reviewUI"
142
134
  ]
143
135
  }
144
136
  }
@@ -0,0 +1,26 @@
1
+ export type OpenForgePluginSdkConditionalExport = Readonly<{
2
+ types: `./dist/${string}.d.ts`
3
+ default: `./dist/${string}.js`
4
+ }>
5
+
6
+ export type OpenForgePluginSdkPublicEntrypoint = Readonly<{
7
+ packageSubpath: '.' | `./${string}`
8
+ importSpecifier: '@openforge-app/plugin-sdk' | `@openforge-app/plugin-sdk/${string}`
9
+ sourcePath: `src/${string}`
10
+ workspaceSourcePath: `packages/plugin-sdk/src/${string}`
11
+ packageExport: string | OpenForgePluginSdkConditionalExport
12
+ }>
13
+
14
+ export const OPENFORGE_PLUGIN_SDK_PUBLIC_ENTRYPOINTS: readonly OpenForgePluginSdkPublicEntrypoint[]
15
+
16
+ export function createOpenForgePluginSdkPackageExports(): Record<
17
+ string,
18
+ string | { types: string; default: string }
19
+ >
20
+
21
+ export function createOpenForgePluginSdkTypeScriptPaths(): Record<string, [string]>
22
+
23
+ export function assertOpenForgePluginSdkEntrypointRegistries(registries: {
24
+ packageExports: unknown
25
+ typeScriptPaths: unknown
26
+ }): void
@@ -0,0 +1,103 @@
1
+ import { isDeepStrictEqual } from 'node:util'
2
+ import { OPENFORGE_PLUGIN_SDK_PUBLIC_UI_EXPORTS } from './publicUiExports.mjs'
3
+
4
+ const PLUGIN_SDK_PACKAGE_NAME = '@openforge-app/plugin-sdk'
5
+
6
+ function moduleEntrypoint(packageSubpath, sourceName) {
7
+ const importSuffix = packageSubpath === '.' ? '' : packageSubpath.slice(1)
8
+ return Object.freeze({
9
+ packageSubpath,
10
+ importSpecifier: `${PLUGIN_SDK_PACKAGE_NAME}${importSuffix}`,
11
+ sourcePath: `src/${sourceName}.ts`,
12
+ workspaceSourcePath: `packages/plugin-sdk/src/${sourceName}.ts`,
13
+ packageExport: Object.freeze({
14
+ types: `./dist/${sourceName}.d.ts`,
15
+ default: `./dist/${sourceName}.js`,
16
+ }),
17
+ })
18
+ }
19
+
20
+ const PUBLIC_MODULE_ENTRYPOINTS = [
21
+ moduleEntrypoint('.', 'index'),
22
+ moduleEntrypoint('./frontend', 'frontend'),
23
+ moduleEntrypoint('./backend', 'backend'),
24
+ moduleEntrypoint('./testing', 'testing'),
25
+ moduleEntrypoint('./vite', 'vite'),
26
+ moduleEntrypoint('./domain', 'domain'),
27
+ moduleEntrypoint('./prStatusPresentation', 'prStatusPresentation'),
28
+ moduleEntrypoint('./markdown', 'markdown'),
29
+ moduleEntrypoint('./numberParsing', 'numberParsing'),
30
+ moduleEntrypoint('./projectFileTree', 'projectFileTree'),
31
+ moduleEntrypoint('./sanitize', 'sanitize'),
32
+ moduleEntrypoint('./pluginIcons', 'pluginIcons'),
33
+ moduleEntrypoint('./fileIcons', 'fileIcons'),
34
+ moduleEntrypoint('./collapsibleSectionState', 'collapsibleSectionState'),
35
+ moduleEntrypoint('./taskBrowserDevToolsShortcuts', 'taskBrowserDevToolsShortcuts'),
36
+ ]
37
+
38
+ const PACKAGE_METADATA_SCHEMA_ENTRYPOINT = Object.freeze({
39
+ packageSubpath: './package-metadata-schema.json',
40
+ importSpecifier: `${PLUGIN_SDK_PACKAGE_NAME}/package-metadata-schema.json`,
41
+ sourcePath: 'src/openforgePackageMetadataSchema.json',
42
+ workspaceSourcePath: 'packages/plugin-sdk/src/openforgePackageMetadataSchema.json',
43
+ packageExport: './dist/openforgePackageMetadataSchema.json',
44
+ })
45
+
46
+ const PUBLIC_UI_ENTRYPOINTS = OPENFORGE_PLUGIN_SDK_PUBLIC_UI_EXPORTS.map((entrypoint) => Object.freeze({
47
+ packageSubpath: entrypoint.packageSubpath,
48
+ importSpecifier: entrypoint.importSpecifier,
49
+ sourcePath: entrypoint.sourcePath,
50
+ workspaceSourcePath: entrypoint.workspaceSourcePath,
51
+ packageExport: entrypoint.distPath,
52
+ }))
53
+
54
+ /** Canonical registrations for every public Plugin SDK entrypoint. */
55
+ export const OPENFORGE_PLUGIN_SDK_PUBLIC_ENTRYPOINTS = Object.freeze([
56
+ ...PUBLIC_MODULE_ENTRYPOINTS.slice(0, 5),
57
+ PACKAGE_METADATA_SCHEMA_ENTRYPOINT,
58
+ ...PUBLIC_MODULE_ENTRYPOINTS.slice(5),
59
+ ...PUBLIC_UI_ENTRYPOINTS,
60
+ ])
61
+
62
+ export function createOpenForgePluginSdkPackageExports() {
63
+ return Object.fromEntries(
64
+ OPENFORGE_PLUGIN_SDK_PUBLIC_ENTRYPOINTS.map(({ packageSubpath, packageExport }) => [
65
+ packageSubpath,
66
+ typeof packageExport === 'string' ? packageExport : { ...packageExport },
67
+ ]),
68
+ )
69
+ }
70
+
71
+ export function createOpenForgePluginSdkTypeScriptPaths() {
72
+ return Object.fromEntries(
73
+ OPENFORGE_PLUGIN_SDK_PUBLIC_ENTRYPOINTS.map(({ importSpecifier, workspaceSourcePath }) => [
74
+ importSpecifier,
75
+ [`./${workspaceSourcePath}`],
76
+ ]),
77
+ )
78
+ }
79
+
80
+ export function assertOpenForgePluginSdkEntrypointRegistries({ packageExports, typeScriptPaths }) {
81
+ assertRegistryMatches('package exports', packageExports, createOpenForgePluginSdkPackageExports())
82
+ assertRegistryMatches('root TypeScript paths', typeScriptPaths, createOpenForgePluginSdkTypeScriptPaths())
83
+ }
84
+
85
+ function assertRegistryMatches(registryName, actual, expected) {
86
+ if (!actual || typeof actual !== 'object' || Array.isArray(actual)) {
87
+ throw new Error(`Plugin SDK ${registryName} must be an object`)
88
+ }
89
+
90
+ const missingOrMismatched = Object.entries(expected)
91
+ .filter(([key, value]) => !isDeepStrictEqual(actual[key], value))
92
+ .map(([key]) => key)
93
+ const unexpected = Object.keys(actual).filter((key) => !(key in expected))
94
+
95
+ if (missingOrMismatched.length === 0 && unexpected.length === 0) return
96
+
97
+ const details = [
98
+ missingOrMismatched.length > 0 ? `missing or mismatched: ${missingOrMismatched.join(', ')}` : null,
99
+ unexpected.length > 0 ? `not in the canonical manifest: ${unexpected.join(', ')}` : null,
100
+ ].filter(Boolean)
101
+
102
+ throw new Error(`Plugin SDK ${registryName} drifted from the canonical manifest (${details.join('; ')})`)
103
+ }
@@ -7,9 +7,11 @@ const PUBLIC_UI_COMPONENT_NAMES = Object.freeze([
7
7
  'ResizablePanel',
8
8
  'Modal',
9
9
  'PluginPageHeader',
10
+ 'PluginPageShell',
10
11
  'PluginViewState',
11
12
  'PluginSidebarLink',
12
13
  'FileTypeIcon',
14
+ 'ProjectFileTree',
13
15
  'CollapsibleSection',
14
16
  ])
15
17
 
@@ -0,0 +1,12 @@
1
+ export type TaskBrowserDevToolsShortcutPlatform = 'macos' | 'other';
2
+ export type TaskBrowserDevToolsShortcut = 'toggle' | 'elements' | 'console';
3
+ export interface TaskBrowserDevToolsShortcutInput {
4
+ key: string;
5
+ keyDown: boolean;
6
+ repeat: boolean;
7
+ control: boolean;
8
+ shift: boolean;
9
+ alt: boolean;
10
+ meta: boolean;
11
+ }
12
+ export declare function classifyTaskBrowserDevToolsShortcut(platform: TaskBrowserDevToolsShortcutPlatform, input: TaskBrowserDevToolsShortcutInput): TaskBrowserDevToolsShortcut | null;
@@ -0,0 +1,20 @@
1
+ export function classifyTaskBrowserDevToolsShortcut(platform, input) {
2
+ if (!input.keyDown || input.repeat)
3
+ return null;
4
+ if (input.key === 'f12')
5
+ return 'toggle';
6
+ if (input.key === 'c') {
7
+ const elementsModified = platform === 'macos'
8
+ ? input.meta && input.shift && !input.control && !input.alt
9
+ : input.control && input.shift && !input.meta && !input.alt;
10
+ return elementsModified ? 'elements' : null;
11
+ }
12
+ const modified = platform === 'macos'
13
+ ? input.meta && input.alt && !input.control && !input.shift
14
+ : input.control && input.shift && !input.meta && !input.alt;
15
+ if (!modified)
16
+ return null;
17
+ if (input.key === 'i')
18
+ return 'toggle';
19
+ return input.key === 'j' ? 'console' : null;
20
+ }