@openforge-app/plugin-sdk 0.1.0 → 0.2.3
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/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/browserSurfaces.d.ts +78 -0
- package/dist/browserSurfaces.js +17 -0
- package/dist/browserSurfacesTesting.d.ts +60 -0
- package/dist/browserSurfacesTesting.js +240 -0
- package/dist/domain.d.ts +74 -15
- package/dist/domain.js +5 -1
- package/dist/fileIcons.d.ts +6 -0
- package/dist/fileIcons.js +84 -0
- package/dist/frontend.d.ts +5 -2
- package/dist/frontend.js +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +2 -1
- package/dist/manifest.d.ts +29 -0
- package/dist/manifest.js +37 -0
- package/dist/markdown.d.ts +5 -1
- package/dist/markdown.js +67 -18
- package/dist/openforgePackageMetadataSchema.json +40 -1
- package/dist/pluginIcons.d.ts +7 -0
- package/dist/pluginIcons.js +104 -0
- package/dist/publicUiExports.d.mts +16 -0
- package/dist/publicUiExports.mjs +59 -0
- package/dist/svelteHostRuntimeContract.d.mts +1 -0
- package/dist/svelteHostRuntimeContract.mjs +16 -0
- package/dist/testing/backendServicesFake.d.ts +19 -0
- package/dist/testing/backendServicesFake.js +87 -0
- package/dist/testing/commonApiFake.d.ts +22 -0
- package/dist/testing/commonApiFake.js +274 -0
- package/dist/testing/contracts.d.ts +231 -0
- package/dist/testing/contracts.js +1 -0
- package/dist/testing/frontendContributionFake.d.ts +36 -0
- package/dist/testing/frontendContributionFake.js +195 -0
- package/dist/testing/registryFake.d.ts +40 -0
- package/dist/testing/registryFake.js +124 -0
- package/dist/testing/support.d.ts +40 -0
- package/dist/testing/support.js +242 -0
- package/dist/testing.d.ts +3 -223
- package/dist/testing.js +2 -625
- package/dist/types.d.ts +193 -7
- package/dist/types.js +8 -0
- package/dist/ui/Button.svelte +49 -0
- package/dist/ui/Checkbox.svelte +137 -0
- package/dist/ui/FileTypeIcon.svelte +37 -0
- package/dist/ui/MarkdownContent.svelte +71 -11
- package/dist/ui/Modal.svelte +157 -0
- package/dist/ui/PluginPageHeader.svelte +26 -0
- package/dist/ui/PluginViewState.svelte +76 -0
- package/dist/ui/ResizablePanel.svelte +10 -0
- package/dist/ui/icons/README.md +9 -0
- package/dist/ui/icons/c.svg +1 -0
- package/dist/ui/icons/console.svg +1 -0
- package/dist/ui/icons/cpp.svg +1 -0
- package/dist/ui/icons/csharp.svg +1 -0
- package/dist/ui/icons/css.svg +3 -0
- package/dist/ui/icons/database.svg +1 -0
- package/dist/ui/icons/docker.svg +1 -0
- package/dist/ui/icons/document.svg +4 -0
- package/dist/ui/icons/file.svg +1 -0
- package/dist/ui/icons/folder-open.svg +1 -0
- package/dist/ui/icons/folder.svg +1 -0
- package/dist/ui/icons/git.svg +3 -0
- package/dist/ui/icons/go.svg +1 -0
- package/dist/ui/icons/graphql.svg +20 -0
- package/dist/ui/icons/h.svg +3 -0
- package/dist/ui/icons/html.svg +3 -0
- package/dist/ui/icons/image.svg +3 -0
- package/dist/ui/icons/java.svg +6 -0
- package/dist/ui/icons/javascript.svg +3 -0
- package/dist/ui/icons/json.svg +3 -0
- package/dist/ui/icons/kotlin.svg +1 -0
- package/dist/ui/icons/lock.svg +3 -0
- package/dist/ui/icons/markdown.svg +6 -0
- package/dist/ui/icons/nodejs.svg +6 -0
- package/dist/ui/icons/npm.svg +3 -0
- package/dist/ui/icons/pdf.svg +1 -0
- package/dist/ui/icons/php.svg +1 -0
- package/dist/ui/icons/python.svg +1 -0
- package/dist/ui/icons/react.svg +8 -0
- package/dist/ui/icons/react_ts.svg +8 -0
- package/dist/ui/icons/readme.svg +4 -0
- package/dist/ui/icons/ruby.svg +1 -0
- package/dist/ui/icons/rust.svg +1 -0
- package/dist/ui/icons/sass.svg +3 -0
- package/dist/ui/icons/settings.svg +4 -0
- package/dist/ui/icons/svelte.svg +1 -0
- package/dist/ui/icons/svg.svg +3 -0
- package/dist/ui/icons/swift.svg +1 -0
- package/dist/ui/icons/tune.svg +11 -0
- package/dist/ui/icons/typescript-def.svg +6 -0
- package/dist/ui/icons/typescript.svg +3 -0
- package/dist/ui/icons/vue.svg +1 -0
- package/dist/ui/icons/xml.svg +1 -0
- package/dist/ui/icons/yaml.svg +3 -0
- package/dist/ui/icons/zip.svg +3 -0
- package/dist/vite.js +5 -2
- package/package.json +34 -7
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Component } from 'svelte';
|
|
2
|
-
import type {
|
|
2
|
+
import type { BrowserSurfacesAPI } from './browserSurfaces';
|
|
3
|
+
import type { AgentSession, CommandInfo, FileContent, FileEntry, Project, ProjectAttention, Task, TaskWorkspaceInfo, WritableBoardStatus } from './domain';
|
|
3
4
|
export type SupportedOpenForgeApiVersion = 1;
|
|
4
5
|
export declare const SUPPORTED_OPENFORGE_API_VERSIONS: readonly [1, ...1[]];
|
|
5
6
|
export declare const OPENFORGE_PLUGIN_API_VERSION: SupportedOpenForgeApiVersion;
|
|
@@ -16,7 +17,7 @@ export interface ValidationError {
|
|
|
16
17
|
path: string;
|
|
17
18
|
message: string;
|
|
18
19
|
}
|
|
19
|
-
export type OpenForgePluginCapability = 'commands' | 'events' | 'views' | 'taskPane' | 'settings' | 'background' | 'backend' | 'storage' | 'context' | 'navigation' | 'tasks' | 'projects' | 'fs' | 'shell' | 'notifications' | 'attention' | 'system.openUrl' | 'config' | 'projectConfig';
|
|
20
|
+
export type OpenForgePluginCapability = 'commands' | 'events' | 'views' | 'taskPane' | 'taskStart' | 'settings' | 'background' | 'backend' | 'storage' | 'context' | 'navigation' | 'tasks' | 'projects' | 'fs' | 'shell' | 'notifications' | 'attention' | 'system.openUrl' | 'system.writeClipboardText' | 'config' | 'projectConfig' | 'browserSurfaces' | 'taskLinks';
|
|
20
21
|
export interface OpenForgePackageMetadata {
|
|
21
22
|
id: string;
|
|
22
23
|
apiVersion: SupportedOpenForgeApiVersion;
|
|
@@ -24,6 +25,7 @@ export interface OpenForgePackageMetadata {
|
|
|
24
25
|
description: string;
|
|
25
26
|
icon?: string;
|
|
26
27
|
frontend?: string;
|
|
28
|
+
frontendStyles?: string[];
|
|
27
29
|
backend?: string;
|
|
28
30
|
requires?: OpenForgePluginCapability[];
|
|
29
31
|
}
|
|
@@ -61,11 +63,23 @@ export interface OpenForgeNavigationRequest {
|
|
|
61
63
|
viewId?: string;
|
|
62
64
|
projectId?: string | null;
|
|
63
65
|
taskId?: string | null;
|
|
66
|
+
/** Plugin-local Task UI tab id. Requires a non-null taskId. */
|
|
67
|
+
taskViewId?: string;
|
|
64
68
|
}
|
|
65
69
|
export interface NavigationAPI {
|
|
66
70
|
get(): OpenForgeNavigationSnapshot;
|
|
67
71
|
navigate(request: OpenForgeNavigationRequest): Promise<OpenForgeNavigationSnapshot>;
|
|
68
72
|
}
|
|
73
|
+
export interface TaskLinkOpenRequest {
|
|
74
|
+
taskId: string;
|
|
75
|
+
url: string;
|
|
76
|
+
}
|
|
77
|
+
export type TaskLinkHandlerResult = 'handled' | 'declined';
|
|
78
|
+
export type TaskLinkHandler = (request: TaskLinkOpenRequest) => Promise<TaskLinkHandlerResult>;
|
|
79
|
+
export interface TaskLinksAPI {
|
|
80
|
+
open(request: TaskLinkOpenRequest): Promise<void>;
|
|
81
|
+
registerHandler(handler: TaskLinkHandler): Disposable;
|
|
82
|
+
}
|
|
69
83
|
export interface OpenForgePluginContext {
|
|
70
84
|
pluginId: string;
|
|
71
85
|
apiVersion: SupportedOpenForgeApiVersion;
|
|
@@ -89,6 +103,33 @@ export type CommandShortcutMetadata = string | {
|
|
|
89
103
|
scope?: 'global' | 'project' | 'task';
|
|
90
104
|
when?: string;
|
|
91
105
|
};
|
|
106
|
+
export interface AgentCommandMetadata {
|
|
107
|
+
/** Concise guidance explaining when and why an agent should use this command. */
|
|
108
|
+
description: string;
|
|
109
|
+
/** Example plugin-owned JSON inputs. Task and Project context are supplied separately. */
|
|
110
|
+
examples?: JsonValue[];
|
|
111
|
+
/** Whether the command appears in the routine agent catalog. Defaults to true. */
|
|
112
|
+
discoverable?: boolean;
|
|
113
|
+
}
|
|
114
|
+
export type AgentCommandRuntime = 'backend' | 'frontend';
|
|
115
|
+
/** Serializable agent-facing projection of a Plugin Command. Never contains its handler. */
|
|
116
|
+
export interface AgentCommandDescriptor {
|
|
117
|
+
qualifiedId: string;
|
|
118
|
+
pluginId: string;
|
|
119
|
+
runtime: AgentCommandRuntime;
|
|
120
|
+
description: string;
|
|
121
|
+
examples: JsonValue[];
|
|
122
|
+
discoverable: boolean;
|
|
123
|
+
input?: JsonSchema;
|
|
124
|
+
output?: JsonSchema;
|
|
125
|
+
}
|
|
126
|
+
export type PluginCommandInvocationSource = 'agent-cli' | 'plugin';
|
|
127
|
+
/** Host-resolved targeting information supplied separately from plugin-owned command input. */
|
|
128
|
+
export interface PluginCommandInvocationContext {
|
|
129
|
+
taskId: string | null;
|
|
130
|
+
projectId: string | null;
|
|
131
|
+
source: PluginCommandInvocationSource;
|
|
132
|
+
}
|
|
92
133
|
export interface CommandRegistration<TInput = unknown, TOutput = unknown> {
|
|
93
134
|
id: string;
|
|
94
135
|
title: string;
|
|
@@ -96,9 +137,11 @@ export interface CommandRegistration<TInput = unknown, TOutput = unknown> {
|
|
|
96
137
|
shortcut?: CommandShortcutMetadata;
|
|
97
138
|
/** Whether this command should appear in user-facing discovery surfaces such as the Command Palette. Defaults to true. */
|
|
98
139
|
discoverable?: boolean;
|
|
140
|
+
/** Explicitly opts this command into agent access. Omitted commands are unavailable to agents. */
|
|
141
|
+
agent?: AgentCommandMetadata;
|
|
99
142
|
input?: JsonSchema;
|
|
100
143
|
output?: JsonSchema;
|
|
101
|
-
handler(input: TInput): MaybePromise<TOutput>;
|
|
144
|
+
handler(input: TInput, context: PluginCommandInvocationContext): MaybePromise<TOutput>;
|
|
102
145
|
}
|
|
103
146
|
export interface CommandDescriptor {
|
|
104
147
|
id: string;
|
|
@@ -116,7 +159,18 @@ export interface CommandRegistry {
|
|
|
116
159
|
register<TInput = unknown, TOutput = unknown>(registration: CommandRegistration<TInput, TOutput>): Disposable;
|
|
117
160
|
invoke<TOutput = unknown>(id: string, payload?: unknown): Promise<TOutput>;
|
|
118
161
|
invokeGlobal<TOutput = unknown>(qualifiedId: string, payload?: unknown): Promise<TOutput>;
|
|
162
|
+
/** Plugin-registered commands (this and other enabled plugins). */
|
|
119
163
|
list(): Promise<CommandDescriptor[]>;
|
|
164
|
+
/**
|
|
165
|
+
* The host's Claude skills/commands catalog for the given project (skills from
|
|
166
|
+
* `~/.claude`/`.agents`, builtin commands, and plugin-provided commands), as the
|
|
167
|
+
* app's own injectable/autocomplete surfaces see it. Omitting `projectId` (or
|
|
168
|
+
* passing null) yields only project-independent entries. Distinct from `list()`,
|
|
169
|
+
* which returns plugin-registered commands.
|
|
170
|
+
*/
|
|
171
|
+
listCatalog(request?: {
|
|
172
|
+
projectId?: string | null;
|
|
173
|
+
}): Promise<CommandInfo[]>;
|
|
120
174
|
}
|
|
121
175
|
export type EventHandler<TPayload = unknown> = (payload: TPayload) => void;
|
|
122
176
|
export interface EventRegistry {
|
|
@@ -140,14 +194,33 @@ export interface PluginTaskPaneProps extends Record<string, unknown> {
|
|
|
140
194
|
taskId: string;
|
|
141
195
|
projectId: string | null;
|
|
142
196
|
}
|
|
197
|
+
export interface PluginTaskUISectionProps extends PluginTaskPaneProps {
|
|
198
|
+
/** True while the host is running a task-scoped action represented by the section. */
|
|
199
|
+
taskActionPending: boolean;
|
|
200
|
+
}
|
|
143
201
|
export interface PluginSettingsSectionProps extends Record<string, unknown> {
|
|
144
202
|
api: FrontendOpenForgeAPI;
|
|
145
203
|
context: OpenForgeContextSnapshot;
|
|
146
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Inline SVG geometry for a plugin view icon.
|
|
207
|
+
*
|
|
208
|
+
* The host sanitizes the markup, strips root sizing and accessibility metadata,
|
|
209
|
+
* renders it decoratively at the navigation surface's size, and uses the view
|
|
210
|
+
* title as the accessible navigation label. Plugins own the `viewBox`, paths,
|
|
211
|
+
* and paint. Use `currentColor` when the icon should follow host navigation
|
|
212
|
+
* states; literal safe colors remain plugin-owned and do not change with state.
|
|
213
|
+
*/
|
|
214
|
+
export interface PluginSvgIcon {
|
|
215
|
+
type: 'svg';
|
|
216
|
+
svg: string;
|
|
217
|
+
}
|
|
218
|
+
/** A host-registered icon name or inline SVG geometry. */
|
|
219
|
+
export type PluginIcon = string | PluginSvgIcon;
|
|
147
220
|
export interface PluginViewRegistration {
|
|
148
221
|
id: string;
|
|
149
222
|
title: string;
|
|
150
|
-
icon:
|
|
223
|
+
icon: PluginIcon;
|
|
151
224
|
/**
|
|
152
225
|
* Where the host surfaces the view's nav entry. `'rail'` (default) places it on
|
|
153
226
|
* the icon rail; `'sidebar'` places it in the left projects sidebar. Either way
|
|
@@ -165,15 +238,72 @@ export interface PluginTaskPaneTabRegistration {
|
|
|
165
238
|
order?: number;
|
|
166
239
|
component: PluginComponentLoader<PluginTaskPaneProps> | PluginComponent<PluginTaskPaneProps>;
|
|
167
240
|
}
|
|
241
|
+
export interface PluginTaskUISectionRegistration {
|
|
242
|
+
id: string;
|
|
243
|
+
order?: number;
|
|
244
|
+
component: PluginComponentLoader<PluginTaskUISectionProps> | PluginComponent<PluginTaskUISectionProps>;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Where a settings section is surfaced. `'project'` (the default) renders it on the
|
|
248
|
+
* per-project settings page, scoped to the active project. `'global'` renders it
|
|
249
|
+
* inside the plugin's own card on the global settings page — for configuration that
|
|
250
|
+
* is one value for the whole app (an API key, a credential) rather than per-project.
|
|
251
|
+
*/
|
|
252
|
+
export type PluginSettingsSectionScope = 'project' | 'global';
|
|
168
253
|
export interface PluginSettingsSectionRegistration {
|
|
169
254
|
id: string;
|
|
170
255
|
title: string;
|
|
171
256
|
order?: number;
|
|
257
|
+
/** Defaults to `'project'` when omitted. */
|
|
258
|
+
scope?: PluginSettingsSectionScope;
|
|
172
259
|
component: PluginComponentLoader<PluginSettingsSectionProps> | PluginComponent<PluginSettingsSectionProps>;
|
|
173
260
|
}
|
|
174
261
|
export interface FrontendViewRegistry {
|
|
175
262
|
register(registration: PluginViewRegistration): Disposable;
|
|
176
263
|
}
|
|
264
|
+
export type InjectionPointLocation = 'createTaskPrompt' | 'agentSession' | 'backlogPrompt';
|
|
265
|
+
export interface PluginInjectionPointProps extends Record<string, unknown> {
|
|
266
|
+
api: FrontendOpenForgeAPI;
|
|
267
|
+
context: OpenForgeContextSnapshot;
|
|
268
|
+
location: InjectionPointLocation;
|
|
269
|
+
projectId: string | null;
|
|
270
|
+
taskId: string | null;
|
|
271
|
+
onInsert: (text: string) => void;
|
|
272
|
+
}
|
|
273
|
+
export interface PluginInjectionPointRegistration {
|
|
274
|
+
id: string;
|
|
275
|
+
location: InjectionPointLocation;
|
|
276
|
+
component: PluginComponentLoader<PluginInjectionPointProps> | PluginComponent<PluginInjectionPointProps>;
|
|
277
|
+
}
|
|
278
|
+
export interface TaskStartPrefixContext {
|
|
279
|
+
/** The task being started, or null when the caller is authoring a new one. */
|
|
280
|
+
taskId: string | null;
|
|
281
|
+
projectId: string | null;
|
|
282
|
+
}
|
|
283
|
+
export interface TaskStartPrefixProviderRegistration {
|
|
284
|
+
id: string;
|
|
285
|
+
/** Menu label shown on the surface offering this provider. */
|
|
286
|
+
title: string;
|
|
287
|
+
/** Lower sorts first. Defaults to 0. */
|
|
288
|
+
order?: number;
|
|
289
|
+
/**
|
|
290
|
+
* Asks the user for a prefix. Returning null means they cancelled, and the
|
|
291
|
+
* task is not started. The host — not the provider — starts the task, so the
|
|
292
|
+
* diverged-branch gate, starting spinner and terminal handling all still run.
|
|
293
|
+
*/
|
|
294
|
+
provide(context: TaskStartPrefixContext): MaybePromise<string | null>;
|
|
295
|
+
}
|
|
296
|
+
export interface FrontendTaskStartRegistry {
|
|
297
|
+
registerPrefixProvider(registration: TaskStartPrefixProviderRegistration): Disposable;
|
|
298
|
+
}
|
|
299
|
+
export interface FrontendInjectionPointRegistry {
|
|
300
|
+
register(registration: PluginInjectionPointRegistration): Disposable;
|
|
301
|
+
}
|
|
302
|
+
export interface FrontendTaskUIRegistry {
|
|
303
|
+
registerTab(registration: PluginTaskPaneTabRegistration): Disposable;
|
|
304
|
+
registerSection(registration: PluginTaskUISectionRegistration): Disposable;
|
|
305
|
+
}
|
|
306
|
+
/** @deprecated Use `FrontendTaskUIRegistry.registerTab` through `openforge.taskUI`. */
|
|
177
307
|
export interface FrontendTaskPaneRegistry {
|
|
178
308
|
registerTab(registration: PluginTaskPaneTabRegistration): Disposable;
|
|
179
309
|
}
|
|
@@ -223,6 +353,7 @@ export interface FileSystemAPI {
|
|
|
223
353
|
limit?: number;
|
|
224
354
|
}): Promise<string[]>;
|
|
225
355
|
}
|
|
356
|
+
export type TerminalImageProtocol = 'iterm2';
|
|
226
357
|
export interface ShellSessionRequest {
|
|
227
358
|
taskId: string;
|
|
228
359
|
terminalIndex: number;
|
|
@@ -231,6 +362,7 @@ export interface ShellSpawnRequest extends ShellSessionRequest {
|
|
|
231
362
|
cwd: string;
|
|
232
363
|
cols: number;
|
|
233
364
|
rows: number;
|
|
365
|
+
terminalImageProtocol?: TerminalImageProtocol | null;
|
|
234
366
|
}
|
|
235
367
|
export interface ShellWriteRequest extends ShellSessionRequest {
|
|
236
368
|
data: string;
|
|
@@ -252,10 +384,27 @@ export interface CreateTaskRequest {
|
|
|
252
384
|
dependsOn?: string[];
|
|
253
385
|
labelNames?: string[];
|
|
254
386
|
}
|
|
387
|
+
export interface ComposeTaskRequest {
|
|
388
|
+
projectId: string;
|
|
389
|
+
/** Seeds the dialog's prompt field; the user edits it before saving. */
|
|
390
|
+
initialPrompt: string;
|
|
391
|
+
sourceTicketUrl?: string | null;
|
|
392
|
+
title?: string | null;
|
|
393
|
+
}
|
|
394
|
+
export interface ComposeTaskResult {
|
|
395
|
+
task: Task;
|
|
396
|
+
/** True when the user chose Start Task rather than plain Create. */
|
|
397
|
+
started: boolean;
|
|
398
|
+
}
|
|
255
399
|
export interface StartPromptContribution {
|
|
400
|
+
/** Host-assigned identity of the plugin that owns this persisted contribution. */
|
|
401
|
+
readonly ownerPluginId?: string;
|
|
256
402
|
id: string;
|
|
257
403
|
enabled: boolean;
|
|
258
|
-
/**
|
|
404
|
+
/**
|
|
405
|
+
* Prompt text injected before OpenForge's task prompt.
|
|
406
|
+
* The host substitutes {{taskId}} and {{task_id}}.
|
|
407
|
+
*/
|
|
259
408
|
content: string;
|
|
260
409
|
/** Lower values are injected first. Defaults to 0. */
|
|
261
410
|
order?: number;
|
|
@@ -271,17 +420,47 @@ export interface ImplementationRun {
|
|
|
271
420
|
sessionId: string;
|
|
272
421
|
workspacePath: string;
|
|
273
422
|
}
|
|
423
|
+
export type TaskFollowUpDisposition = 'delivered' | 'queued';
|
|
424
|
+
export interface SendTaskFollowUpRequest {
|
|
425
|
+
taskId: string;
|
|
426
|
+
message: string;
|
|
427
|
+
}
|
|
428
|
+
export interface TaskFollowUpReceipt {
|
|
429
|
+
taskId: string;
|
|
430
|
+
sessionId: string;
|
|
431
|
+
disposition: TaskFollowUpDisposition;
|
|
432
|
+
}
|
|
433
|
+
export type TaskFollowUpErrorCode = 'NO_SESSION' | 'DELIVERY_FAILED';
|
|
434
|
+
export declare class TaskFollowUpError extends Error {
|
|
435
|
+
readonly code: TaskFollowUpErrorCode;
|
|
436
|
+
constructor(code: TaskFollowUpErrorCode, message: string);
|
|
437
|
+
}
|
|
274
438
|
export interface TasksAPI {
|
|
439
|
+
/**
|
|
440
|
+
* Lists tasks, optionally scoped to a project. By default done tasks are
|
|
441
|
+
* excluded (matching the app board's active-only view); pass
|
|
442
|
+
* `includeDone: true` to include tasks in the terminal `done` state. The
|
|
443
|
+
* unscoped listing (no `projectId`) always returns all states, so
|
|
444
|
+
* `includeDone` only affects the project-scoped path.
|
|
445
|
+
*/
|
|
275
446
|
list(request?: {
|
|
276
447
|
projectId?: string | null;
|
|
448
|
+
includeDone?: boolean;
|
|
277
449
|
}): Promise<Task[]>;
|
|
278
|
-
get(taskId: string): Promise<Task>;
|
|
450
|
+
get(taskId: string): Promise<Task | null>;
|
|
279
451
|
create(request: CreateTaskRequest): Promise<Task>;
|
|
280
|
-
|
|
452
|
+
/**
|
|
453
|
+
* Opens the host's create-task dialog pre-filled, letting the user edit the
|
|
454
|
+
* prompt — including anything contributed at that injection point —
|
|
455
|
+
* before the task exists.
|
|
456
|
+
* Resolves null if they dismiss it.
|
|
457
|
+
*/
|
|
458
|
+
compose(request: ComposeTaskRequest): Promise<ComposeTaskResult | null>;
|
|
281
459
|
updateStatus(taskId: string, status: WritableBoardStatus): Promise<void>;
|
|
282
460
|
listStartPromptContributions(projectId: string): Promise<StartPromptContribution[]>;
|
|
283
461
|
configureStartPromptContribution(request: ConfigureStartPromptContributionRequest): Promise<StartPromptContribution[]>;
|
|
284
462
|
startImplementation(request: StartTaskImplementationRequest): Promise<ImplementationRun>;
|
|
463
|
+
sendFollowUp(request: SendTaskFollowUpRequest): Promise<TaskFollowUpReceipt>;
|
|
285
464
|
getWorkspace(taskId: string): Promise<TaskWorkspaceInfo | null>;
|
|
286
465
|
getLatestSession(taskId: string): Promise<AgentSession | null>;
|
|
287
466
|
}
|
|
@@ -302,6 +481,7 @@ export interface AttentionAPI {
|
|
|
302
481
|
}
|
|
303
482
|
export interface SystemAPI {
|
|
304
483
|
openUrl(url: string): Promise<void>;
|
|
484
|
+
writeClipboardText(text: string): Promise<void>;
|
|
305
485
|
}
|
|
306
486
|
export interface KeyValueConfigAPI {
|
|
307
487
|
get<T extends JsonValue = JsonValue>(key: string, projectId?: string): Promise<T | null>;
|
|
@@ -325,11 +505,17 @@ export interface OpenForgeCommonAPI {
|
|
|
325
505
|
projectConfig: KeyValueConfigAPI;
|
|
326
506
|
}
|
|
327
507
|
export interface FrontendOpenForgeAPI extends OpenForgeCommonAPI {
|
|
508
|
+
browserSurfaces: BrowserSurfacesAPI;
|
|
509
|
+
taskLinks: TaskLinksAPI;
|
|
328
510
|
navigation: NavigationAPI;
|
|
329
511
|
views: FrontendViewRegistry;
|
|
512
|
+
taskUI: FrontendTaskUIRegistry;
|
|
513
|
+
/** @deprecated Use `taskUI.registerTab(...)`. */
|
|
330
514
|
taskPane: FrontendTaskPaneRegistry;
|
|
331
515
|
settings: FrontendSettingsRegistry;
|
|
332
516
|
backend: FrontendBackendBridge;
|
|
517
|
+
injectionPoints: FrontendInjectionPointRegistry;
|
|
518
|
+
taskStart: FrontendTaskStartRegistry;
|
|
333
519
|
}
|
|
334
520
|
export interface BackendOpenForgeAPI extends OpenForgeCommonAPI {
|
|
335
521
|
backend: BackendMethodRegistry;
|
package/dist/types.js
CHANGED
|
@@ -10,6 +10,14 @@ export const SUPPORTED_OPENFORGE_API_VERSIONS = Object.freeze(readSupportedOpenF
|
|
|
10
10
|
export const OPENFORGE_PLUGIN_API_VERSION = SUPPORTED_OPENFORGE_API_VERSIONS[0];
|
|
11
11
|
export const MIN_SUPPORTED_API_VERSION = Math.min(...SUPPORTED_OPENFORGE_API_VERSIONS);
|
|
12
12
|
export const MAX_SUPPORTED_API_VERSION = Math.max(...SUPPORTED_OPENFORGE_API_VERSIONS);
|
|
13
|
+
export class TaskFollowUpError extends Error {
|
|
14
|
+
code;
|
|
15
|
+
constructor(code, message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'TaskFollowUpError';
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
13
21
|
export function makePluginViewKey(pluginId, viewId) {
|
|
14
22
|
return `plugin:${pluginId}:${viewId}`;
|
|
15
23
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import type { HTMLButtonAttributes } from 'svelte/elements'
|
|
4
|
+
|
|
5
|
+
type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'error'
|
|
6
|
+
type ButtonSize = 'xs' | 'sm' | 'md' | 'lg'
|
|
7
|
+
|
|
8
|
+
interface Props extends HTMLButtonAttributes {
|
|
9
|
+
children: Snippet
|
|
10
|
+
variant?: ButtonVariant
|
|
11
|
+
size?: ButtonSize
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const variantClasses: Record<ButtonVariant, string> = {
|
|
15
|
+
primary: 'btn-primary',
|
|
16
|
+
secondary: 'btn-secondary',
|
|
17
|
+
outline: 'btn-outline',
|
|
18
|
+
ghost: 'btn-ghost',
|
|
19
|
+
error: 'btn-error',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const sizeClasses: Record<ButtonSize, string> = {
|
|
23
|
+
xs: 'btn-xs',
|
|
24
|
+
sm: 'btn-sm',
|
|
25
|
+
md: 'btn-md',
|
|
26
|
+
lg: 'btn-lg',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let {
|
|
30
|
+
children,
|
|
31
|
+
variant = 'primary',
|
|
32
|
+
size = 'md',
|
|
33
|
+
class: className,
|
|
34
|
+
disabled = false,
|
|
35
|
+
onclick,
|
|
36
|
+
...attributes
|
|
37
|
+
}: Props = $props()
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<button
|
|
41
|
+
{...attributes}
|
|
42
|
+
class={['btn', variantClasses[variant], sizeClasses[size], className]}
|
|
43
|
+
{disabled}
|
|
44
|
+
onclick={(event) => {
|
|
45
|
+
if (!disabled) onclick?.(event)
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
{@render children()}
|
|
49
|
+
</button>
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements'
|
|
3
|
+
|
|
4
|
+
type CheckboxSize = 'xs' | 'sm' | 'md'
|
|
5
|
+
|
|
6
|
+
interface Props extends Omit<HTMLInputAttributes, 'checked' | 'size' | 'type'> {
|
|
7
|
+
checked?: boolean
|
|
8
|
+
indeterminate?: boolean
|
|
9
|
+
size?: CheckboxSize
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
checked = $bindable(false),
|
|
14
|
+
indeterminate = false,
|
|
15
|
+
size = 'sm',
|
|
16
|
+
class: className,
|
|
17
|
+
onchange,
|
|
18
|
+
...attributes
|
|
19
|
+
}: Props = $props()
|
|
20
|
+
|
|
21
|
+
let input = $state<HTMLInputElement | null>(null)
|
|
22
|
+
|
|
23
|
+
$effect(() => {
|
|
24
|
+
if (input) input.indeterminate = indeterminate
|
|
25
|
+
})
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<input
|
|
29
|
+
{...attributes}
|
|
30
|
+
bind:this={input}
|
|
31
|
+
bind:checked
|
|
32
|
+
type="checkbox"
|
|
33
|
+
class={["of-checkbox", className]}
|
|
34
|
+
data-size={size}
|
|
35
|
+
aria-checked={indeterminate ? 'mixed' : undefined}
|
|
36
|
+
{onchange}
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<style>
|
|
40
|
+
.of-checkbox {
|
|
41
|
+
width: 1.25rem;
|
|
42
|
+
height: 1.25rem;
|
|
43
|
+
flex: none;
|
|
44
|
+
appearance: none;
|
|
45
|
+
display: inline-grid;
|
|
46
|
+
place-content: center;
|
|
47
|
+
margin: 0;
|
|
48
|
+
border: 2px solid color-mix(in oklab, var(--color-base-content) 55%, transparent);
|
|
49
|
+
border-radius: 0.25rem;
|
|
50
|
+
background: var(--color-base-100);
|
|
51
|
+
color: var(--color-primary-content);
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
transition:
|
|
54
|
+
background-color 150ms ease,
|
|
55
|
+
border-color 150ms ease,
|
|
56
|
+
box-shadow 150ms ease;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.of-checkbox::before {
|
|
60
|
+
width: 0.75rem;
|
|
61
|
+
height: 0.75rem;
|
|
62
|
+
background: currentColor;
|
|
63
|
+
clip-path: polygon(14% 44%, 0 59%, 40% 100%, 100% 19%, 84% 4%, 39% 73%);
|
|
64
|
+
content: '';
|
|
65
|
+
transform: scale(0);
|
|
66
|
+
transform-origin: center;
|
|
67
|
+
transition: transform 120ms ease-out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.of-checkbox:hover:not(:disabled) {
|
|
71
|
+
border-color: var(--color-primary);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.of-checkbox:checked,
|
|
75
|
+
.of-checkbox:indeterminate {
|
|
76
|
+
border-color: var(--color-primary);
|
|
77
|
+
background: var(--color-primary);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.of-checkbox:checked::before {
|
|
81
|
+
transform: scale(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.of-checkbox:indeterminate::before {
|
|
85
|
+
width: 0.625rem;
|
|
86
|
+
height: 0.125rem;
|
|
87
|
+
clip-path: none;
|
|
88
|
+
transform: scale(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.of-checkbox:focus-visible {
|
|
92
|
+
outline: 2px solid var(--color-primary);
|
|
93
|
+
outline-offset: 2px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.of-checkbox:disabled {
|
|
97
|
+
cursor: not-allowed;
|
|
98
|
+
opacity: 0.45;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.of-checkbox[data-size='xs'] {
|
|
102
|
+
width: 1rem;
|
|
103
|
+
height: 1rem;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.of-checkbox[data-size='xs']::before {
|
|
107
|
+
width: 0.625rem;
|
|
108
|
+
height: 0.625rem;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.of-checkbox[data-size='md'] {
|
|
112
|
+
width: 1.5rem;
|
|
113
|
+
height: 1.5rem;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.of-checkbox[data-size='md']::before {
|
|
117
|
+
width: 0.875rem;
|
|
118
|
+
height: 0.875rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@media (prefers-reduced-motion: reduce) {
|
|
122
|
+
.of-checkbox,
|
|
123
|
+
.of-checkbox::before {
|
|
124
|
+
transition: none;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@media (forced-colors: active) {
|
|
129
|
+
.of-checkbox {
|
|
130
|
+
appearance: auto;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.of-checkbox::before {
|
|
134
|
+
display: none;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getFileIconName, getFolderIconName } from '../fileIcons'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
filename?: string
|
|
6
|
+
folder?: boolean
|
|
7
|
+
open?: boolean
|
|
8
|
+
class?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { filename, folder = false, open = false, class: className = '' }: Props = $props()
|
|
12
|
+
|
|
13
|
+
// Curated, vendored set only (~45 files) — eager raw is fine and avoids runtime asset URLs.
|
|
14
|
+
const icons = import.meta.glob('./icons/*.svg', {
|
|
15
|
+
query: '?raw',
|
|
16
|
+
import: 'default',
|
|
17
|
+
eager: true,
|
|
18
|
+
}) as Record<string, string>
|
|
19
|
+
|
|
20
|
+
let iconName = $derived(folder ? getFolderIconName(open) : getFileIconName(filename ?? ''))
|
|
21
|
+
let svg = $derived(icons[`./icons/${iconName}.svg`] ?? icons['./icons/file.svg'] ?? '')
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<span
|
|
25
|
+
class="file-type-icon inline-flex items-center justify-center shrink-0 {className}"
|
|
26
|
+
data-icon={iconName}
|
|
27
|
+
aria-hidden="true"
|
|
28
|
+
>
|
|
29
|
+
{@html svg}
|
|
30
|
+
</span>
|
|
31
|
+
|
|
32
|
+
<style>
|
|
33
|
+
.file-type-icon :global(svg) {
|
|
34
|
+
width: 100%;
|
|
35
|
+
height: 100%;
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
@@ -1,30 +1,90 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { onDestroy } from 'svelte'
|
|
3
|
+
import { getMarkdownRepositoryLinkSuffix, renderMarkdownHtml, resolveMarkdownRepositoryPath } from '../markdown'
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
content: string
|
|
6
7
|
imageBaseUrl?: string | null
|
|
8
|
+
markdownFilePath?: string | null
|
|
9
|
+
resolveRepositoryImage?: (repositoryPath: string) => Promise<string | null>
|
|
10
|
+
onOpenRepositoryPath?: (repositoryPath: string, suffix: string) => void | Promise<void>
|
|
7
11
|
onOpenUrl?: (url: string) => void | Promise<void>
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
let {
|
|
14
|
+
let {
|
|
15
|
+
content,
|
|
16
|
+
imageBaseUrl = null,
|
|
17
|
+
markdownFilePath = null,
|
|
18
|
+
resolveRepositoryImage,
|
|
19
|
+
onOpenRepositoryPath,
|
|
20
|
+
onOpenUrl,
|
|
21
|
+
}: Props = $props()
|
|
11
22
|
|
|
12
|
-
let
|
|
23
|
+
let root = $state<HTMLDivElement | null>(null)
|
|
24
|
+
let imageResolutionId = 0
|
|
25
|
+
let html = $derived(renderMarkdownHtml(content, {
|
|
26
|
+
imageBaseUrl: resolveRepositoryImage ? null : imageBaseUrl,
|
|
27
|
+
markdownFilePath,
|
|
28
|
+
deferRepositoryImages: Boolean(resolveRepositoryImage),
|
|
29
|
+
}))
|
|
13
30
|
|
|
14
|
-
function
|
|
15
|
-
if (!
|
|
31
|
+
async function resolveRepositoryImages(runId: number) {
|
|
32
|
+
if (!root || !markdownFilePath || !resolveRepositoryImage) return
|
|
16
33
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
|
|
34
|
+
const images = Array.from(root.querySelectorAll('img[data-markdown-repository-path]'))
|
|
35
|
+
await Promise.all(images.map(async (image) => {
|
|
36
|
+
const repositoryPath = image.getAttribute('data-markdown-repository-path')
|
|
37
|
+
if (!repositoryPath) return
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const resolvedSrc = await resolveRepositoryImage(repositoryPath)
|
|
41
|
+
if (runId !== imageResolutionId || !resolvedSrc) return
|
|
42
|
+
image.setAttribute('src', resolvedSrc)
|
|
43
|
+
image.removeAttribute('data-markdown-repository-path')
|
|
44
|
+
} catch {
|
|
45
|
+
// Leave the image inert when an asset is missing or cannot be previewed.
|
|
46
|
+
}
|
|
47
|
+
}))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
$effect(() => {
|
|
51
|
+
const runId = ++imageResolutionId
|
|
52
|
+
void html
|
|
53
|
+
if (!root || !markdownFilePath || !resolveRepositoryImage) return
|
|
54
|
+
|
|
55
|
+
void resolveRepositoryImages(runId)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
onDestroy(() => {
|
|
59
|
+
imageResolutionId++
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
function handleClick(event: MouseEvent) {
|
|
63
|
+
if (!(event.target instanceof Element)) return
|
|
64
|
+
|
|
65
|
+
const anchor = event.target.closest('a')
|
|
66
|
+
const href = anchor?.getAttribute('href')
|
|
67
|
+
if (!anchor || !href) return
|
|
68
|
+
|
|
69
|
+
if (markdownFilePath) {
|
|
70
|
+
const repositoryPath = resolveMarkdownRepositoryPath(href, markdownFilePath)
|
|
71
|
+
if (repositoryPath) {
|
|
72
|
+
event.preventDefault()
|
|
73
|
+
void onOpenRepositoryPath?.(repositoryPath, getMarkdownRepositoryLinkSuffix(href))
|
|
74
|
+
return
|
|
22
75
|
}
|
|
23
76
|
}
|
|
77
|
+
|
|
78
|
+
if (href.startsWith('#')) return
|
|
79
|
+
|
|
80
|
+
event.preventDefault()
|
|
81
|
+
if (onOpenUrl && anchor.href) {
|
|
82
|
+
void onOpenUrl(href.startsWith('//') ? `https:${href}` : anchor.href)
|
|
83
|
+
}
|
|
24
84
|
}
|
|
25
85
|
</script>
|
|
26
86
|
|
|
27
87
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
28
|
-
<div role="presentation" class="markdown-body" onclick={handleClick}>
|
|
88
|
+
<div bind:this={root} role="presentation" class="markdown-body" onclick={handleClick}>
|
|
29
89
|
{@html html}
|
|
30
90
|
</div>
|