@openforge-app/plugin-sdk 0.2.4 → 0.2.6

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, ExternalReadFileSystemAPI, UserDataDirectoryRequest, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, NavigationAPI, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot } from './types';
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';
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, ExternalReadFileSystemAPI, UserDataDirectoryRequest, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, NavigationAPI, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, };
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, };
package/dist/domain.d.ts CHANGED
@@ -117,6 +117,7 @@ export interface PrComment {
117
117
  outdated: number;
118
118
  created_at: number;
119
119
  }
120
+ export type PullRequestMergeMethod = 'merge' | 'squash' | 'rebase';
120
121
  export interface PullRequestInfo {
121
122
  id: number;
122
123
  pr_number: number;
@@ -149,7 +150,11 @@ export interface PullRequestInfo {
149
150
  merge_queue_required: boolean | null;
150
151
  merge_queue_state: string | null;
151
152
  readiness_updated_at: number | null;
153
+ merge_methods_policy_known?: boolean | null;
154
+ allowed_merge_methods?: string | PullRequestMergeMethod[] | null;
155
+ default_merge_method?: PullRequestMergeMethod | null;
152
156
  }
157
+ export type PollOutcome = 'completed' | 'missing_github_token' | 'github_token_unavailable' | 'failed' | 'rate_limited';
153
158
  export interface PollResult {
154
159
  new_comments: number;
155
160
  ci_changes: number;
@@ -158,6 +163,7 @@ export interface PollResult {
158
163
  errors: number;
159
164
  rate_limited: boolean;
160
165
  rate_limit_reset_at: number | null;
166
+ outcome: PollOutcome;
161
167
  }
162
168
  export interface MergeStatusInfo {
163
169
  state: string;
@@ -1,5 +1,5 @@
1
1
  import type { BrowserSurfaceCapture, 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, 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 } from './types';
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 } 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, 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, 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, };
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, };
package/dist/index.d.ts CHANGED
@@ -2,9 +2,9 @@ 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 { TaskFollowUpError, MAX_SUPPORTED_API_VERSION, MIN_SUPPORTED_API_VERSION, OPENFORGE_PLUGIN_API_VERSION, SUPPORTED_OPENFORGE_API_VERSIONS, isPluginViewKey, makePluginViewKey, parsePluginViewKey, } from './types';
6
- export type { AgentCommandDescriptor, AgentCommandMetadata, AgentCommandRuntime, AttentionAPI, BackendReadyState, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, ConfigureStartPromptContributionRequest, CreateTaskRequest, Disposable, BackendFileSystemAPI, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadFileSystemAPI, FileSystemAPI, UserDataDirectoryRequest, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, ImplementationRun, InjectionPointLocation, JsonObject, JsonPrimitive, JsonSchema, JsonValue, MaybePromise, KeyValueConfigAPI, NotificationRequest, NavigationAPI, NotificationsAPI, OpenForgeCommonAPI, 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, ShellSessionRequest, ShellSpawnRequest, ShellWriteRequest, StartPromptContribution, SendTaskFollowUpRequest, StartTaskImplementationRequest, TaskFollowUpDisposition, TaskFollowUpErrorCode, TaskFollowUpReceipt, SubscriptionSink, SupportedOpenForgeApiVersion, SystemAPI, TaskLinkHandler, TaskLinkHandlerResult, TaskLinkOpenRequest, TaskLinksAPI, TasksAPI, ValidationError, } from './types';
7
- export type { MockBackendOpenForgeAPI, MockFrontendOpenForgeAPI, TestingBackgroundServiceContribution, TestingBackendMethodContribution, TestingCommandContribution, TestingContributionBase, TestingEventListenerContribution, TestingOpenForgeApiCalls, TestingOpenForgeApiOptions, TestingOpenForgeRegistrySnapshot, TestingRuntimeKind, TestingRuntimeScope, TestingSettingsSectionContribution, TestingTaskPaneTabContribution, TestingTaskUISectionContribution, TestingViewContribution, } 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, ShellSessionRequest, ShellSpawnRequest, ShellTerminalQueryResponseRequest, ShellWriteRequest, StartPromptContribution, SendTaskFollowUpRequest, StartTaskImplementationRequest, TaskFollowUpDisposition, TaskFollowUpErrorCode, TaskFollowUpReceipt, SubscriptionSink, SupportedOpenForgeApiVersion, SystemAPI, TaskLinkHandler, TaskLinkHandlerResult, TaskLinkOpenRequest, TaskLinksAPI, TasksAPI, ValidationError, } from './types';
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';
10
10
  export type { ProjectFileTreeEntry, ProjectFileTreeItemAccessibility, ProjectFileTreeKeyboardAction, ProjectFileTreeNode, } 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 { TaskFollowUpError, MAX_SUPPORTED_API_VERSION, MIN_SUPPORTED_API_VERSION, OPENFORGE_PLUGIN_API_VERSION, SUPPORTED_OPENFORGE_API_VERSIONS, isPluginViewKey, makePluginViewKey, parsePluginViewKey, } from './types';
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';
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';
@@ -30,10 +30,10 @@ export declare const OPENFORGE_PACKAGE_METADATA_SCHEMA: {
30
30
  } | {
31
31
  if: {
32
32
  properties: {
33
+ requires?: undefined;
33
34
  enablement: {
34
35
  const: string;
35
36
  };
36
- requires?: undefined;
37
37
  };
38
38
  required: string[];
39
39
  };
@@ -73,13 +73,15 @@ export declare const OPENFORGE_PACKAGE_METADATA_SCHEMA: {
73
73
  };
74
74
  icon: {
75
75
  oneOf: ({
76
+ properties?: undefined;
76
77
  type: string;
77
78
  minLength: number;
78
79
  pattern: string;
79
80
  additionalProperties?: undefined;
80
81
  required?: undefined;
81
- properties?: undefined;
82
82
  } | {
83
+ minLength?: undefined;
84
+ pattern?: undefined;
83
85
  type: string;
84
86
  additionalProperties: boolean;
85
87
  required: string[];
@@ -93,8 +95,6 @@ export declare const OPENFORGE_PACKAGE_METADATA_SCHEMA: {
93
95
  pattern: string;
94
96
  };
95
97
  };
96
- minLength?: undefined;
97
- pattern?: undefined;
98
98
  })[];
99
99
  description: string;
100
100
  };
@@ -2,7 +2,15 @@ export interface RenderMarkdownOptions {
2
2
  imageBaseUrl?: string | null;
3
3
  markdownFilePath?: string | null;
4
4
  deferRepositoryImages?: boolean;
5
+ deferRemoteMedia?: boolean;
5
6
  }
7
+ /** How a deferred remote URL should be presented once it has been resolved. */
8
+ export interface ResolvedMarkdownMedia {
9
+ url: string;
10
+ kind: 'image' | 'video';
11
+ }
12
+ /** Attribute holding the original URL of a `<img>`/`<a>` awaiting resolution. */
13
+ export declare const MARKDOWN_REMOTE_MEDIA_ATTRIBUTE = "data-markdown-remote-src";
6
14
  export declare function resolveMarkdownRepositoryPath(value: string | null, markdownFilePath: string): string | null;
7
15
  export declare function getMarkdownRepositoryLinkSuffix(value: string): string;
8
16
  export declare function resolveMarkdownImageSrc(src: string | null, imageBaseUrl: string | null | undefined, markdownFilePath?: string): string | null;
package/dist/markdown.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { marked } from 'marked';
2
2
  import { sanitizeHtml } from './sanitize';
3
+ /** Attribute holding the original URL of a `<img>`/`<a>` awaiting resolution. */
4
+ export const MARKDOWN_REMOTE_MEDIA_ATTRIBUTE = 'data-markdown-remote-src';
3
5
  const markedOptions = {
4
6
  gfm: true,
5
7
  breaks: true,
@@ -71,13 +73,57 @@ export function resolveMarkdownImageSrc(src, imageBaseUrl, markdownFilePath = ''
71
73
  return null;
72
74
  }
73
75
  }
74
- function prepareMarkdownImageSources(html, options) {
75
- if (typeof document === 'undefined' || (!options.imageBaseUrl && !options.deferRepositoryImages))
76
+ function isRemoteUrl(value) {
77
+ return /^https?:\/\//i.test(value);
78
+ }
79
+ const TEXT_NODE = 3;
80
+ /**
81
+ * Whether nothing but a line break separates `anchor` from the end of its line.
82
+ * GitHub only turns a bare URL into an embedded player when it stands on its own
83
+ * line, so link markup mid-sentence stays a link.
84
+ */
85
+ function isAtLineBoundary(from, step) {
86
+ let node = from;
87
+ while (node) {
88
+ if (node.nodeType !== TEXT_NODE)
89
+ return node.nodeName === 'BR';
90
+ if ((node.textContent ?? '').trim() !== '')
91
+ return false;
92
+ node = node[step];
93
+ }
94
+ return true;
95
+ }
96
+ function isBareLinkOnItsOwnLine(anchor, href) {
97
+ return (anchor.textContent ?? '').trim() === href
98
+ && isAtLineBoundary(anchor.previousSibling, 'previousSibling')
99
+ && isAtLineBoundary(anchor.nextSibling, 'nextSibling');
100
+ }
101
+ function deferRemoteMediaLinks(root) {
102
+ for (const anchor of root.querySelectorAll('a[href]')) {
103
+ const href = anchor.getAttribute('href')?.trim() ?? '';
104
+ if (!isRemoteUrl(href) || !isBareLinkOnItsOwnLine(anchor, href))
105
+ continue;
106
+ anchor.setAttribute(MARKDOWN_REMOTE_MEDIA_ATTRIBUTE, href);
107
+ }
108
+ }
109
+ function prepareMarkdownMediaSources(html, options) {
110
+ if (typeof document === 'undefined' ||
111
+ (!options.imageBaseUrl && !options.deferRepositoryImages && !options.deferRemoteMedia))
76
112
  return html;
77
113
  const template = document.createElement('template');
78
114
  template.innerHTML = html;
115
+ if (options.deferRemoteMedia) {
116
+ deferRemoteMediaLinks(template.content);
117
+ }
79
118
  for (const image of template.content.querySelectorAll('img[src]')) {
80
119
  const source = image.getAttribute('src')?.trim() ?? '';
120
+ // Hold the remote URL back so the caller can exchange it for one the
121
+ // renderer can actually load, instead of flashing a broken image first.
122
+ if (options.deferRemoteMedia && isRemoteUrl(source)) {
123
+ image.removeAttribute('src');
124
+ image.setAttribute(MARKDOWN_REMOTE_MEDIA_ATTRIBUTE, source);
125
+ continue;
126
+ }
81
127
  const shouldDefer = options.deferRepositoryImages && source.length > 0 && !hasAbsoluteOrSpecialUrl(source);
82
128
  if (shouldDefer) {
83
129
  image.removeAttribute('src');
@@ -99,5 +145,5 @@ function prepareMarkdownImageSources(html, options) {
99
145
  }
100
146
  export function renderMarkdownHtml(content, options = {}) {
101
147
  const rawHtml = marked.parse(content, markedOptions);
102
- return sanitizeHtml(prepareMarkdownImageSources(rawHtml, options));
148
+ return sanitizeHtml(prepareMarkdownMediaSources(rawHtml, options));
103
149
  }
@@ -1,8 +1,9 @@
1
- import { type MergeReadinessAction, type MergeReadinessDetail, type MergeReadinessStatus, type MergeStatusInfo } from './domain';
1
+ import { type MergeReadinessAction, type MergeReadinessDetail, type MergeReadinessStatus, type MergeStatusInfo, type PullRequestMergeMethod } from './domain';
2
2
  export type PrChipSurface = 'compact' | 'detail';
3
3
  export type PrChipVariant = 'success' | 'error' | 'pending' | 'muted' | 'neutral' | 'done' | 'merged' | 'closed';
4
4
  export type PrChipType = 'draft' | 'ci' | 'review' | 'merge';
5
5
  export type PrChipIcon = 'check' | 'cross' | 'clock' | null;
6
+ export declare function getPullRequestMergeActionLabel(method: PullRequestMergeMethod, prNumber?: number): string;
6
7
  export interface PrStatusChipSpec {
7
8
  type: PrChipType;
8
9
  label: string;
@@ -1,4 +1,15 @@
1
1
  import { getMergeReadiness, isClosedUnmergedPullRequest, isMergedPullRequest } from './domain';
2
+ const PULL_REQUEST_MERGE_ACTION_LABELS = {
3
+ merge: 'Create a merge commit',
4
+ squash: 'Squash and merge',
5
+ rebase: 'Rebase and merge',
6
+ };
7
+ export function getPullRequestMergeActionLabel(method, prNumber) {
8
+ const label = PULL_REQUEST_MERGE_ACTION_LABELS[method];
9
+ if (prNumber === undefined)
10
+ return label;
11
+ return method === 'merge' ? `${label} for PR #${prNumber}` : `${label} PR #${prNumber}`;
12
+ }
2
13
  export function getPrStatusChips(pr, surface) {
3
14
  const chips = [];
4
15
  if (pr.draft && pr.state === 'open') {
@@ -1,4 +1,22 @@
1
+ import { resolveExternalTextFileChunkSize } from '../types';
1
2
  import { assertFunction, assertTitle, commandDescriptor, createDisposable, isJsonValue, normalizeAgentCommandMetadata, } from './support';
3
+ const UTF8_ENCODER = new TextEncoder();
4
+ function* splitExternalTextFile(content, maxBytes) {
5
+ let chunk = '';
6
+ let chunkBytes = 0;
7
+ for (const character of content) {
8
+ const characterBytes = UTF8_ENCODER.encode(character).byteLength;
9
+ if (chunkBytes + characterBytes > maxBytes && chunk.length > 0) {
10
+ yield chunk;
11
+ chunk = '';
12
+ chunkBytes = 0;
13
+ }
14
+ chunk += character;
15
+ chunkBytes += characterBytes;
16
+ }
17
+ if (chunk.length > 0)
18
+ yield chunk;
19
+ }
2
20
  export class TestingCommonApiFake {
3
21
  services;
4
22
  commands = new Map();
@@ -128,6 +146,7 @@ export class TestingCommonApiFake {
128
146
  write: async (request) => {
129
147
  this.services.calls.shellWrites.push(request);
130
148
  },
149
+ writeTerminalQueryResponse: async () => { },
131
150
  resize: async (request) => {
132
151
  this.services.calls.shellResizes.push(request);
133
152
  },
@@ -136,7 +155,7 @@ export class TestingCommonApiFake {
136
155
  },
137
156
  getBuffer: async (request) => {
138
157
  this.services.calls.shellBuffers.push(request);
139
- return { buffer: null, isLive: false };
158
+ return { buffer: null, isLive: false, instanceId: null };
140
159
  },
141
160
  },
142
161
  notifications: {
@@ -211,6 +230,19 @@ export class TestingCommonApiFake {
211
230
  this.services.calls.fsExternalReads.push(request);
212
231
  return '';
213
232
  },
233
+ readTextFileChunks: (request) => {
234
+ const chunkSizeBytes = resolveExternalTextFileChunkSize(request.chunkSizeBytes);
235
+ const { root, path, signal } = request;
236
+ this.services.calls.fsExternalReadTextFileChunks.push({ root, path, chunkSizeBytes });
237
+ const content = this.services.externalTextFiles.find(file => file.root === root && file.path === path)?.content ?? '';
238
+ return (async function* () {
239
+ for (const chunk of splitExternalTextFile(content, chunkSizeBytes)) {
240
+ signal?.throwIfAborted();
241
+ yield chunk;
242
+ }
243
+ signal?.throwIfAborted();
244
+ })();
245
+ },
214
246
  },
215
247
  },
216
248
  };
@@ -1,12 +1,18 @@
1
1
  import type { BrowserSurfaceVisualFeedback } from '../browserSurfaces';
2
2
  import type { TestingOpenForgeRegistryFake } from './registryFake';
3
- import type { BackendMethodRegistration, BackendOpenForgeAPI, BackgroundServiceRegistration, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ConfigureStartPromptContributionRequest, CreateTaskRequest, FrontendOpenForgeAPI, InjectionPointLocation, JsonValue, NotificationRequest, OpenForgeNavigationRequest, OpenForgePackageMetadata, PluginSettingsSectionRegistration, PluginStorage, PluginTaskPaneTabRegistration, PluginTaskUISectionRegistration, PluginViewRegistration, ShellSpawnRequest, SendTaskFollowUpRequest, StartTaskImplementationRequest, TaskLinkOpenRequest, TaskStartPrefixContext, ExternalReadDirectoryRequest, ExternalReadFileRequest, UserDataDirectoryRequest, UserDataFileRequest, UserDataFileWriteRequest } from '../types';
3
+ import type { BackendMethodRegistration, BackendOpenForgeAPI, BackgroundServiceRegistration, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ConfigureStartPromptContributionRequest, CreateTaskRequest, FrontendOpenForgeAPI, InjectionPointLocation, JsonValue, NotificationRequest, OpenForgeNavigationRequest, OpenForgePackageMetadata, PluginSettingsSectionRegistration, PluginStorage, PluginTaskPaneTabRegistration, PluginTaskUISectionRegistration, PluginViewRegistration, ShellSpawnRequest, SendTaskFollowUpRequest, StartTaskImplementationRequest, TaskLinkOpenRequest, TaskStartPrefixContext, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, UserDataDirectoryRequest, UserDataFileRequest, UserDataFileWriteRequest } from '../types';
4
4
  import type { Task } from '../domain';
5
5
  export type TestingRuntimeScope = 'global' | 'project' | 'task';
6
6
  export type TestingRuntimeKind = 'commands' | 'events' | 'views' | 'taskPane' | 'taskUI' | 'settings' | 'backend' | 'background';
7
7
  export type TestingMaybePromise<T> = T | Promise<T>;
8
8
  export type TestingCommandHandler = (payload?: unknown) => TestingMaybePromise<unknown>;
9
9
  export type TestingEventHandler = (payload: unknown) => void;
10
+ export interface TestingExternalTextFile extends ExternalReadFileRequest {
11
+ content: string;
12
+ }
13
+ export type TestingExternalTextFileChunksCall = Omit<ExternalReadTextFileChunksRequest, 'signal' | 'chunkSizeBytes'> & {
14
+ chunkSizeBytes: number;
15
+ };
10
16
  export interface TestingOpenForgeApiOptions {
11
17
  pluginId?: string;
12
18
  projectId?: string | null;
@@ -21,6 +27,8 @@ export interface TestingOpenForgeApiOptions {
21
27
  * mirroring the host capability. Defaults to an empty list.
22
28
  */
23
29
  tasks?: Task[];
30
+ /** UTF-8 files returned by `fs.external.readTextFileChunks`. Defaults to none. */
31
+ externalTextFiles?: TestingExternalTextFile[];
24
32
  }
25
33
  export interface TestingOpenForgeApiCalls {
26
34
  commandInvocations: Array<{
@@ -79,6 +87,7 @@ export interface TestingOpenForgeApiCalls {
79
87
  fsUserDataWrites: UserDataFileWriteRequest[];
80
88
  fsExternalReadDirs: ExternalReadDirectoryRequest[];
81
89
  fsExternalReads: ExternalReadFileRequest[];
90
+ fsExternalReadTextFileChunks: TestingExternalTextFileChunksCall[];
82
91
  shellSpawns: ShellSpawnRequest[];
83
92
  shellWrites: Array<{
84
93
  taskId: string;
@@ -104,6 +104,7 @@ export class TestingOpenForgeRegistryFake {
104
104
  apiVersion: 1,
105
105
  packageMetadata: this.packageMetadata,
106
106
  subscriptions,
107
+ onDidChange: () => ({ dispose: () => undefined }),
107
108
  };
108
109
  }
109
110
  }
@@ -1,6 +1,6 @@
1
1
  import type { AgentCommandMetadata, CommandDescriptor, Disposable, JsonValue, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, OpenForgePackageMetadata, PluginStorage, StartPromptContribution, SubscriptionSink } from '../types';
2
2
  import type { Task } from '../domain';
3
- import type { TestingCommandContribution, TestingMaybePromise, TestingOpenForgeApiCalls, TestingOpenForgeApiOptions, TestingRuntimeKind } from './contracts';
3
+ import type { TestingCommandContribution, TestingMaybePromise, TestingExternalTextFile, TestingOpenForgeApiCalls, TestingOpenForgeApiOptions, TestingRuntimeKind } from './contracts';
4
4
  export declare function createDisposable(dispose: () => TestingMaybePromise<void>): Disposable;
5
5
  export declare class TestingSubscriptionSink implements SubscriptionSink {
6
6
  readonly subscriptions: Disposable[];
@@ -31,6 +31,7 @@ export declare class TestingRegistryServices {
31
31
  readonly storage: PluginStorage;
32
32
  readonly config: Map<string, JsonValue>;
33
33
  readonly seededTasks: Task[];
34
+ readonly externalTextFiles: TestingExternalTextFile[];
34
35
  readonly claims: TestingContributionClaims;
35
36
  constructor(options?: TestingOpenForgeApiOptions);
36
37
  localQualifiedId(kind: TestingRuntimeKind, id: string): string;
@@ -53,6 +53,7 @@ export function createTestingCalls() {
53
53
  fsUserDataWrites: [],
54
54
  fsExternalReadDirs: [],
55
55
  fsExternalReads: [],
56
+ fsExternalReadTextFileChunks: [],
56
57
  shellSpawns: [],
57
58
  shellWrites: [],
58
59
  shellResizes: [],
@@ -201,6 +202,7 @@ export class TestingRegistryServices {
201
202
  storage;
202
203
  config = new Map();
203
204
  seededTasks;
205
+ externalTextFiles;
204
206
  claims = new TestingContributionClaims();
205
207
  constructor(options = {}) {
206
208
  this.pluginId = options.pluginId ?? 'test-plugin';
@@ -216,6 +218,7 @@ export class TestingRegistryServices {
216
218
  this.calls = createTestingCalls();
217
219
  this.storage = options.storage ?? createMemoryPluginStorage(this.calls);
218
220
  this.seededTasks = options.tasks ?? [];
221
+ this.externalTextFiles = options.externalTextFiles ?? [];
219
222
  }
220
223
  localQualifiedId(kind, id) {
221
224
  assertLocalId(kind, id);
package/dist/testing.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { createMockBackendOpenForgeApi, createMockFrontendOpenForgeApi, createMockOpenForgeApi, createMockPluginContext, createOpenForgeRegistryFake, TestingOpenForgeRegistryFake, } from './testing/registryFake';
2
2
  export { createMemoryPluginStorage, createTestingCalls, TestingSubscriptionSink, } from './testing/support';
3
- export type { MockBackendOpenForgeAPI, MockFrontendOpenForgeAPI, TestingBackgroundServiceContribution, TestingBackendMethodContribution, TestingCommandContribution, TestingContributionBase, TestingEventListenerContribution, TestingInjectionPointContribution, TestingOpenForgeApiCalls, TestingOpenForgeApiOptions, TestingOpenForgeRegistrySnapshot, TestingRuntimeKind, TestingRuntimeScope, TestingSettingsSectionContribution, TestingTaskPaneTabContribution, TestingTaskUISectionContribution, TestingViewContribution, } from './testing/contracts';
3
+ export type { MockBackendOpenForgeAPI, MockFrontendOpenForgeAPI, TestingBackgroundServiceContribution, TestingBackendMethodContribution, TestingCommandContribution, TestingContributionBase, TestingExternalTextFile, TestingExternalTextFileChunksCall, TestingEventListenerContribution, TestingInjectionPointContribution, TestingOpenForgeApiCalls, TestingOpenForgeApiOptions, TestingOpenForgeRegistrySnapshot, TestingRuntimeKind, TestingRuntimeScope, TestingSettingsSectionContribution, TestingTaskPaneTabContribution, TestingTaskUISectionContribution, TestingViewContribution, } from './testing/contracts';
package/dist/types.d.ts CHANGED
@@ -17,7 +17,7 @@ export interface ValidationError {
17
17
  path: string;
18
18
  message: string;
19
19
  }
20
- declare const OPENFORGE_PLUGIN_CAPABILITY_TYPE_MEMBERS: readonly ["commands", "events", "views", "injectionPoints", "taskPane", "taskStart", "settings", "background", "backend", "storage", "context", "navigation", "tasks", "projects", "fs", "shell", "notifications", "attention", "system.openUrl", "system.writeClipboardText", "config", "projectConfig", "browserSurfaces", "taskLinks", "appEnablement", "customSidebarNavigation"];
20
+ declare const OPENFORGE_PLUGIN_CAPABILITY_TYPE_MEMBERS: readonly ['commands', 'events', 'views', 'injectionPoints', 'taskPane', 'taskStart', 'settings', 'background', 'backend', 'storage', 'context', 'navigation', 'tasks', 'projects', 'fs', 'shell', 'notifications', 'attention', 'system.openUrl', 'system.writeClipboardText', 'config', 'projectConfig', 'browserSurfaces', 'taskLinks', 'appEnablement', 'customSidebarNavigation'];
21
21
  export type OpenForgePluginCapability = (typeof OPENFORGE_PLUGIN_CAPABILITY_TYPE_MEMBERS)[number];
22
22
  export interface OpenForgePackageMetadata {
23
23
  id: string;
@@ -57,6 +57,7 @@ export interface OpenForgeContextSnapshot {
57
57
  projectId: string | null;
58
58
  taskId?: string | null;
59
59
  }
60
+ export type OpenForgeContextChangeHandler = (snapshot: OpenForgeContextSnapshot) => MaybePromise<void>;
60
61
  export interface OpenForgeNavigationSnapshot {
61
62
  activeProjectId: string | null;
62
63
  currentView: string;
@@ -88,6 +89,7 @@ export interface OpenForgePluginContext {
88
89
  apiVersion: SupportedOpenForgeApiVersion;
89
90
  packageMetadata: OpenForgePackageMetadata;
90
91
  subscriptions: SubscriptionSink;
92
+ onDidChange(handler: OpenForgeContextChangeHandler): Disposable;
91
93
  }
92
94
  export type FrontendPluginContext = OpenForgePluginContext;
93
95
  export type BackendPluginContext = OpenForgePluginContext;
@@ -390,6 +392,17 @@ export interface ExternalReadFileRequest {
390
392
  root: string;
391
393
  path: string;
392
394
  }
395
+ export declare const DEFAULT_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES: number;
396
+ export declare const MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES = 4;
397
+ export declare const MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES: number;
398
+ /** Applies the default external text chunk size and rejects values outside host limits. */
399
+ export declare function resolveExternalTextFileChunkSize(chunkSizeBytes?: number): number;
400
+ export interface ExternalReadTextFileChunksRequest extends ExternalReadFileRequest {
401
+ /** UTF-8 chunks contain at most this many bytes. Defaults to 64 KiB. */
402
+ chunkSizeBytes?: number;
403
+ /** Stops future reads. An in-flight host read may finish, but its result is discarded. */
404
+ signal?: AbortSignal;
405
+ }
393
406
  export interface UserDataFileSystemAPI {
394
407
  readDir(request?: UserDataDirectoryRequest): Promise<FileEntry[]>;
395
408
  readTextFile(request: UserDataFileRequest): Promise<string>;
@@ -398,6 +411,8 @@ export interface UserDataFileSystemAPI {
398
411
  export interface ExternalReadFileSystemAPI {
399
412
  readDir(request: ExternalReadDirectoryRequest): Promise<FileEntry[]>;
400
413
  readTextFile(request: ExternalReadFileRequest): Promise<string>;
414
+ /** Lazily reads a UTF-8 file without retaining a host file handle between chunks. */
415
+ readTextFileChunks(request: ExternalReadTextFileChunksRequest): AsyncIterable<string>;
401
416
  }
402
417
  export interface BackendFileSystemAPI extends FileSystemAPI {
403
418
  userData: UserDataFileSystemAPI;
@@ -417,6 +432,10 @@ export interface ShellSpawnRequest extends ShellSessionRequest {
417
432
  export interface ShellWriteRequest extends ShellSessionRequest {
418
433
  data: string;
419
434
  }
435
+ export interface ShellTerminalQueryResponseRequest extends ShellSessionRequest {
436
+ ptyInstanceId: number;
437
+ data: string;
438
+ }
420
439
  export interface ShellResizeRequest extends ShellSessionRequest {
421
440
  cols: number;
422
441
  rows: number;
@@ -424,10 +443,12 @@ export interface ShellResizeRequest extends ShellSessionRequest {
424
443
  export interface PtyBufferState {
425
444
  buffer: string | null;
426
445
  isLive: boolean;
446
+ instanceId: number | null;
427
447
  }
428
448
  export interface ShellAPI {
429
449
  spawn(request: ShellSpawnRequest): Promise<number>;
430
450
  write(request: ShellWriteRequest): Promise<void>;
451
+ writeTerminalQueryResponse(request: ShellTerminalQueryResponseRequest): Promise<void>;
431
452
  resize(request: ShellResizeRequest): Promise<void>;
432
453
  kill(request: ShellSessionRequest): Promise<void>;
433
454
  getBuffer(request: ShellSessionRequest): Promise<PtyBufferState>;
package/dist/types.js CHANGED
@@ -49,6 +49,19 @@ function assertOpenForgePluginCapabilitiesMatchSchema() {
49
49
  }
50
50
  }
51
51
  assertOpenForgePluginCapabilitiesMatchSchema();
52
+ export const DEFAULT_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES = 64 * 1024;
53
+ export const MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES = 4;
54
+ export const MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES = 1024 * 1024;
55
+ /** Applies the default external text chunk size and rejects values outside host limits. */
56
+ export function resolveExternalTextFileChunkSize(chunkSizeBytes) {
57
+ const size = chunkSizeBytes ?? DEFAULT_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES;
58
+ if (!Number.isInteger(size)
59
+ || size < MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES
60
+ || size > MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES) {
61
+ throw new RangeError(`chunkSizeBytes must be an integer between ${MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES} and ${MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES}`);
62
+ }
63
+ return size;
64
+ }
52
65
  export class TaskFollowUpError extends Error {
53
66
  code;
54
67
  constructor(code, message) {
@@ -1,12 +1,19 @@
1
1
  <script lang="ts">
2
2
  import { onDestroy } from 'svelte'
3
- import { getMarkdownRepositoryLinkSuffix, renderMarkdownHtml, resolveMarkdownRepositoryPath } from '../markdown'
3
+ import {
4
+ getMarkdownRepositoryLinkSuffix,
5
+ MARKDOWN_REMOTE_MEDIA_ATTRIBUTE,
6
+ renderMarkdownHtml,
7
+ resolveMarkdownRepositoryPath,
8
+ type ResolvedMarkdownMedia,
9
+ } from '../markdown'
4
10
 
5
11
  interface Props {
6
12
  content: string
7
13
  imageBaseUrl?: string | null
8
14
  markdownFilePath?: string | null
9
15
  resolveRepositoryImage?: (repositoryPath: string) => Promise<string | null>
16
+ resolveRemoteMedia?: (url: string) => Promise<ResolvedMarkdownMedia | null>
10
17
  onOpenRepositoryPath?: (repositoryPath: string, suffix: string) => void | Promise<void>
11
18
  onOpenUrl?: (url: string) => void | Promise<void>
12
19
  }
@@ -16,6 +23,7 @@
16
23
  imageBaseUrl = null,
17
24
  markdownFilePath = null,
18
25
  resolveRepositoryImage,
26
+ resolveRemoteMedia,
19
27
  onOpenRepositoryPath,
20
28
  onOpenUrl,
21
29
  }: Props = $props()
@@ -26,6 +34,7 @@
26
34
  imageBaseUrl: resolveRepositoryImage ? null : imageBaseUrl,
27
35
  markdownFilePath,
28
36
  deferRepositoryImages: Boolean(resolveRepositoryImage),
37
+ deferRemoteMedia: Boolean(resolveRemoteMedia),
29
38
  }))
30
39
 
31
40
  async function resolveRepositoryImages(runId: number) {
@@ -47,12 +56,55 @@
47
56
  }))
48
57
  }
49
58
 
59
+ function applyResolvedMedia(element: Element, url: string, resolved: ResolvedMarkdownMedia | null) {
60
+ element.removeAttribute(MARKDOWN_REMOTE_MEDIA_ATTRIBUTE)
61
+
62
+ if (element instanceof HTMLImageElement) {
63
+ element.setAttribute('src', resolved?.url ?? url)
64
+ return
65
+ }
66
+
67
+ // A bare link only becomes a player when the caller says the upload is one;
68
+ // everything else stays the link the Markdown asked for.
69
+ if (resolved?.kind !== 'video') return
70
+
71
+ const video = document.createElement('video')
72
+ video.src = resolved.url
73
+ video.controls = true
74
+ video.preload = 'metadata'
75
+ element.replaceWith(video)
76
+ }
77
+
78
+ // Remote media is held back by the renderer so the caller can swap in a URL
79
+ // this app can load (GitHub only serves upload URLs to a browser session).
80
+ // Anything the caller does not handle falls back to what the Markdown said.
81
+ async function resolveRemoteMediaElements(runId: number) {
82
+ if (!root || !resolveRemoteMedia) return
83
+
84
+ const elements = Array.from(root.querySelectorAll(`[${MARKDOWN_REMOTE_MEDIA_ATTRIBUTE}]`))
85
+ await Promise.all(elements.map(async (element) => {
86
+ const url = element.getAttribute(MARKDOWN_REMOTE_MEDIA_ATTRIBUTE)
87
+ if (!url) return
88
+
89
+ let resolved: ResolvedMarkdownMedia | null = null
90
+ try {
91
+ resolved = await resolveRemoteMedia(url)
92
+ } catch {
93
+ // Fall back to the original URL when resolution is unavailable.
94
+ }
95
+
96
+ if (runId !== imageResolutionId) return
97
+ applyResolvedMedia(element, url, resolved)
98
+ }))
99
+ }
100
+
50
101
  $effect(() => {
51
102
  const runId = ++imageResolutionId
52
103
  void html
53
- if (!root || !markdownFilePath || !resolveRepositoryImage) return
104
+ if (!root) return
54
105
 
55
106
  void resolveRepositoryImages(runId)
107
+ void resolveRemoteMediaElements(runId)
56
108
  })
57
109
 
58
110
  onDestroy(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openforge-app/plugin-sdk",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -93,17 +93,17 @@
93
93
  "test": "vitest run"
94
94
  },
95
95
  "dependencies": {
96
- "dompurify": "^3.4.13",
97
- "marked": "^18.0.9"
96
+ "dompurify": "catalog:",
97
+ "marked": "catalog:"
98
98
  },
99
99
  "peerDependencies": {
100
100
  "svelte": "^5.0.0"
101
101
  },
102
102
  "devDependencies": {
103
- "@types/node": "26.2.0",
104
- "svelte": "5.56.8",
105
- "typescript": "^6.0.3",
106
- "vitest": "^4.1.10",
103
+ "@types/node": "catalog:pinned",
104
+ "svelte": "catalog:pinned",
105
+ "typescript": "catalog:",
106
+ "vitest": "catalog:",
107
107
  "vscode-material-icons": "^0.1.1"
108
108
  }
109
109
  }
package/dist/context.d.ts DELETED
@@ -1,14 +0,0 @@
1
- import type { Disposable, OpenForgePackageMetadata, OpenForgePluginContext, SubscriptionSink, SupportedOpenForgeApiVersion } from './types';
2
- declare class SubscriptionSet implements SubscriptionSink {
3
- private readonly subscriptions;
4
- add(subscription: Disposable | (() => void)): void;
5
- disposeAll(): Promise<void>;
6
- }
7
- export declare class PluginContextImpl implements OpenForgePluginContext {
8
- readonly pluginId: string;
9
- readonly apiVersion: SupportedOpenForgeApiVersion;
10
- readonly packageMetadata: OpenForgePackageMetadata;
11
- readonly subscriptions: SubscriptionSet;
12
- constructor(pluginId: string, apiVersion: SupportedOpenForgeApiVersion, packageMetadata: OpenForgePackageMetadata);
13
- }
14
- export {};
package/dist/context.js DELETED
@@ -1,30 +0,0 @@
1
- class SubscriptionSet {
2
- subscriptions = new Set();
3
- add(subscription) {
4
- this.subscriptions.add(subscription);
5
- }
6
- async disposeAll() {
7
- const subscriptions = [...this.subscriptions];
8
- this.subscriptions.clear();
9
- await Promise.all(subscriptions.map(async (subscription) => {
10
- if (typeof subscription === 'function') {
11
- subscription();
12
- }
13
- else {
14
- await subscription.dispose();
15
- }
16
- }));
17
- }
18
- }
19
- export class PluginContextImpl {
20
- pluginId;
21
- apiVersion;
22
- packageMetadata;
23
- subscriptions;
24
- constructor(pluginId, apiVersion, packageMetadata) {
25
- this.pluginId = pluginId;
26
- this.apiVersion = apiVersion;
27
- this.packageMetadata = packageMetadata;
28
- this.subscriptions = new SubscriptionSet();
29
- }
30
- }