@openforge-app/plugin-sdk 0.1.0 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/dist/backend.d.ts +2 -2
  4. package/dist/browserSurfaces.d.ts +78 -0
  5. package/dist/browserSurfaces.js +17 -0
  6. package/dist/browserSurfacesTesting.d.ts +60 -0
  7. package/dist/browserSurfacesTesting.js +240 -0
  8. package/dist/domain.d.ts +74 -15
  9. package/dist/domain.js +5 -1
  10. package/dist/fileIcons.d.ts +6 -0
  11. package/dist/fileIcons.js +84 -0
  12. package/dist/frontend.d.ts +5 -2
  13. package/dist/frontend.js +1 -0
  14. package/dist/index.d.ts +5 -3
  15. package/dist/index.js +2 -1
  16. package/dist/manifest.d.ts +80 -2
  17. package/dist/manifest.js +55 -1
  18. package/dist/markdown.d.ts +5 -1
  19. package/dist/markdown.js +67 -18
  20. package/dist/openforgePackageMetadataSchema.json +80 -4
  21. package/dist/pluginIconContract.d.ts +3 -0
  22. package/dist/pluginIconContract.js +12 -0
  23. package/dist/pluginIcons.d.ts +7 -0
  24. package/dist/pluginIcons.js +99 -0
  25. package/dist/publicUiExports.d.mts +16 -0
  26. package/dist/publicUiExports.mjs +60 -0
  27. package/dist/svelteHostRuntimeContract.d.mts +1 -0
  28. package/dist/svelteHostRuntimeContract.mjs +16 -0
  29. package/dist/testing/backendServicesFake.d.ts +19 -0
  30. package/dist/testing/backendServicesFake.js +87 -0
  31. package/dist/testing/commonApiFake.d.ts +23 -0
  32. package/dist/testing/commonApiFake.js +306 -0
  33. package/dist/testing/contracts.d.ts +236 -0
  34. package/dist/testing/contracts.js +1 -0
  35. package/dist/testing/frontendContributionFake.d.ts +36 -0
  36. package/dist/testing/frontendContributionFake.js +204 -0
  37. package/dist/testing/registryFake.d.ts +40 -0
  38. package/dist/testing/registryFake.js +124 -0
  39. package/dist/testing/support.d.ts +40 -0
  40. package/dist/testing/support.js +247 -0
  41. package/dist/testing.d.ts +3 -223
  42. package/dist/testing.js +2 -625
  43. package/dist/types.d.ts +251 -9
  44. package/dist/types.js +47 -0
  45. package/dist/ui/Button.svelte +49 -0
  46. package/dist/ui/Checkbox.svelte +137 -0
  47. package/dist/ui/FileTypeIcon.svelte +37 -0
  48. package/dist/ui/MarkdownContent.svelte +71 -11
  49. package/dist/ui/Modal.svelte +157 -0
  50. package/dist/ui/PluginPageHeader.svelte +26 -0
  51. package/dist/ui/PluginSidebarLink.svelte +54 -0
  52. package/dist/ui/PluginViewState.svelte +76 -0
  53. package/dist/ui/ResizablePanel.svelte +10 -0
  54. package/dist/ui/icons/README.md +9 -0
  55. package/dist/ui/icons/c.svg +1 -0
  56. package/dist/ui/icons/console.svg +1 -0
  57. package/dist/ui/icons/cpp.svg +1 -0
  58. package/dist/ui/icons/csharp.svg +1 -0
  59. package/dist/ui/icons/css.svg +3 -0
  60. package/dist/ui/icons/database.svg +1 -0
  61. package/dist/ui/icons/docker.svg +1 -0
  62. package/dist/ui/icons/document.svg +4 -0
  63. package/dist/ui/icons/file.svg +1 -0
  64. package/dist/ui/icons/folder-open.svg +1 -0
  65. package/dist/ui/icons/folder.svg +1 -0
  66. package/dist/ui/icons/git.svg +3 -0
  67. package/dist/ui/icons/go.svg +1 -0
  68. package/dist/ui/icons/graphql.svg +20 -0
  69. package/dist/ui/icons/h.svg +3 -0
  70. package/dist/ui/icons/html.svg +3 -0
  71. package/dist/ui/icons/image.svg +3 -0
  72. package/dist/ui/icons/java.svg +6 -0
  73. package/dist/ui/icons/javascript.svg +3 -0
  74. package/dist/ui/icons/json.svg +3 -0
  75. package/dist/ui/icons/kotlin.svg +1 -0
  76. package/dist/ui/icons/lock.svg +3 -0
  77. package/dist/ui/icons/markdown.svg +6 -0
  78. package/dist/ui/icons/nodejs.svg +6 -0
  79. package/dist/ui/icons/npm.svg +3 -0
  80. package/dist/ui/icons/pdf.svg +1 -0
  81. package/dist/ui/icons/php.svg +1 -0
  82. package/dist/ui/icons/python.svg +1 -0
  83. package/dist/ui/icons/react.svg +8 -0
  84. package/dist/ui/icons/react_ts.svg +8 -0
  85. package/dist/ui/icons/readme.svg +4 -0
  86. package/dist/ui/icons/ruby.svg +1 -0
  87. package/dist/ui/icons/rust.svg +1 -0
  88. package/dist/ui/icons/sass.svg +3 -0
  89. package/dist/ui/icons/settings.svg +4 -0
  90. package/dist/ui/icons/svelte.svg +1 -0
  91. package/dist/ui/icons/svg.svg +3 -0
  92. package/dist/ui/icons/swift.svg +1 -0
  93. package/dist/ui/icons/tune.svg +11 -0
  94. package/dist/ui/icons/typescript-def.svg +6 -0
  95. package/dist/ui/icons/typescript.svg +3 -0
  96. package/dist/ui/icons/vue.svg +1 -0
  97. package/dist/ui/icons/xml.svg +1 -0
  98. package/dist/ui/icons/yaml.svg +3 -0
  99. package/dist/ui/icons/zip.svg +3 -0
  100. package/dist/vite.js +5 -2
  101. package/package.json +35 -7
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Component } from 'svelte';
2
- import type { AgentSession, FileContent, FileEntry, Project, ProjectAttention, Task, TaskWorkspaceInfo, WritableBoardStatus } from './domain';
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,14 +17,18 @@ 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
+ 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
+ export type OpenForgePluginCapability = (typeof OPENFORGE_PLUGIN_CAPABILITY_TYPE_MEMBERS)[number];
20
22
  export interface OpenForgePackageMetadata {
21
23
  id: string;
22
24
  apiVersion: SupportedOpenForgeApiVersion;
23
25
  displayName: string;
24
26
  description: string;
25
- icon?: string;
27
+ /** Defaults to project-owned lifecycle when omitted. */
28
+ enablement?: 'app' | 'project';
29
+ icon?: PluginIcon;
26
30
  frontend?: string;
31
+ frontendStyles?: string[];
27
32
  backend?: string;
28
33
  requires?: OpenForgePluginCapability[];
29
34
  }
@@ -61,11 +66,23 @@ export interface OpenForgeNavigationRequest {
61
66
  viewId?: string;
62
67
  projectId?: string | null;
63
68
  taskId?: string | null;
69
+ /** Plugin-local Task UI tab id. Requires a non-null taskId. */
70
+ taskViewId?: string;
64
71
  }
65
72
  export interface NavigationAPI {
66
73
  get(): OpenForgeNavigationSnapshot;
67
74
  navigate(request: OpenForgeNavigationRequest): Promise<OpenForgeNavigationSnapshot>;
68
75
  }
76
+ export interface TaskLinkOpenRequest {
77
+ taskId: string;
78
+ url: string;
79
+ }
80
+ export type TaskLinkHandlerResult = 'handled' | 'declined';
81
+ export type TaskLinkHandler = (request: TaskLinkOpenRequest) => Promise<TaskLinkHandlerResult>;
82
+ export interface TaskLinksAPI {
83
+ open(request: TaskLinkOpenRequest): Promise<void>;
84
+ registerHandler(handler: TaskLinkHandler): Disposable;
85
+ }
69
86
  export interface OpenForgePluginContext {
70
87
  pluginId: string;
71
88
  apiVersion: SupportedOpenForgeApiVersion;
@@ -89,6 +106,33 @@ export type CommandShortcutMetadata = string | {
89
106
  scope?: 'global' | 'project' | 'task';
90
107
  when?: string;
91
108
  };
109
+ export interface AgentCommandMetadata {
110
+ /** Concise guidance explaining when and why an agent should use this command. */
111
+ description: string;
112
+ /** Example plugin-owned JSON inputs. Task and Project context are supplied separately. */
113
+ examples?: JsonValue[];
114
+ /** Whether the command appears in the routine agent catalog. Defaults to true. */
115
+ discoverable?: boolean;
116
+ }
117
+ export type AgentCommandRuntime = 'backend' | 'frontend';
118
+ /** Serializable agent-facing projection of a Plugin Command. Never contains its handler. */
119
+ export interface AgentCommandDescriptor {
120
+ qualifiedId: string;
121
+ pluginId: string;
122
+ runtime: AgentCommandRuntime;
123
+ description: string;
124
+ examples: JsonValue[];
125
+ discoverable: boolean;
126
+ input?: JsonSchema;
127
+ output?: JsonSchema;
128
+ }
129
+ export type PluginCommandInvocationSource = 'agent-cli' | 'plugin';
130
+ /** Host-resolved targeting information supplied separately from plugin-owned command input. */
131
+ export interface PluginCommandInvocationContext {
132
+ taskId: string | null;
133
+ projectId: string | null;
134
+ source: PluginCommandInvocationSource;
135
+ }
92
136
  export interface CommandRegistration<TInput = unknown, TOutput = unknown> {
93
137
  id: string;
94
138
  title: string;
@@ -96,9 +140,11 @@ export interface CommandRegistration<TInput = unknown, TOutput = unknown> {
96
140
  shortcut?: CommandShortcutMetadata;
97
141
  /** Whether this command should appear in user-facing discovery surfaces such as the Command Palette. Defaults to true. */
98
142
  discoverable?: boolean;
143
+ /** Explicitly opts this command into agent access. Omitted commands are unavailable to agents. */
144
+ agent?: AgentCommandMetadata;
99
145
  input?: JsonSchema;
100
146
  output?: JsonSchema;
101
- handler(input: TInput): MaybePromise<TOutput>;
147
+ handler(input: TInput, context: PluginCommandInvocationContext): MaybePromise<TOutput>;
102
148
  }
103
149
  export interface CommandDescriptor {
104
150
  id: string;
@@ -116,7 +162,18 @@ export interface CommandRegistry {
116
162
  register<TInput = unknown, TOutput = unknown>(registration: CommandRegistration<TInput, TOutput>): Disposable;
117
163
  invoke<TOutput = unknown>(id: string, payload?: unknown): Promise<TOutput>;
118
164
  invokeGlobal<TOutput = unknown>(qualifiedId: string, payload?: unknown): Promise<TOutput>;
165
+ /** Plugin-registered commands (this and other enabled plugins). */
119
166
  list(): Promise<CommandDescriptor[]>;
167
+ /**
168
+ * The host's Claude skills/commands catalog for the given project (skills from
169
+ * `~/.claude`/`.agents`, builtin commands, and plugin-provided commands), as the
170
+ * app's own injectable/autocomplete surfaces see it. Omitting `projectId` (or
171
+ * passing null) yields only project-independent entries. Distinct from `list()`,
172
+ * which returns plugin-registered commands.
173
+ */
174
+ listCatalog(request?: {
175
+ projectId?: string | null;
176
+ }): Promise<CommandInfo[]>;
120
177
  }
121
178
  export type EventHandler<TPayload = unknown> = (payload: TPayload) => void;
122
179
  export interface EventRegistry {
@@ -134,20 +191,54 @@ export interface PluginViewProps extends Record<string, unknown> {
134
191
  api: FrontendOpenForgeAPI;
135
192
  context: OpenForgeContextSnapshot;
136
193
  }
194
+ export interface PluginSidebarViewIdentity {
195
+ pluginId: string;
196
+ id: string;
197
+ qualifiedId: string;
198
+ title: string;
199
+ icon: PluginIcon;
200
+ }
201
+ export interface PluginSidebarNavigationProps extends Record<string, unknown> {
202
+ api: FrontendOpenForgeAPI;
203
+ context: OpenForgeContextSnapshot;
204
+ active: boolean;
205
+ collapsed: boolean;
206
+ view: PluginSidebarViewIdentity;
207
+ onActivate: () => void;
208
+ }
137
209
  export interface PluginTaskPaneProps extends Record<string, unknown> {
138
210
  api: FrontendOpenForgeAPI;
139
211
  context: OpenForgeContextSnapshot;
140
212
  taskId: string;
141
213
  projectId: string | null;
142
214
  }
215
+ export interface PluginTaskUISectionProps extends PluginTaskPaneProps {
216
+ /** True while the host is running a task-scoped action represented by the section. */
217
+ taskActionPending: boolean;
218
+ }
143
219
  export interface PluginSettingsSectionProps extends Record<string, unknown> {
144
220
  api: FrontendOpenForgeAPI;
145
221
  context: OpenForgeContextSnapshot;
146
222
  }
223
+ /**
224
+ * Inline SVG geometry for a plugin view icon.
225
+ *
226
+ * The host sanitizes the markup, strips root sizing and accessibility metadata,
227
+ * renders it decoratively at the navigation surface's size, and uses the view
228
+ * title as the accessible navigation label. Plugins own the `viewBox`, paths,
229
+ * and paint. Use `currentColor` when the icon should follow host navigation
230
+ * states; literal safe colors remain plugin-owned and do not change with state.
231
+ */
232
+ export interface PluginSvgIcon {
233
+ type: 'svg';
234
+ svg: string;
235
+ }
236
+ /** A host-registered icon name or inline SVG geometry. */
237
+ export type PluginIcon = string | PluginSvgIcon;
147
238
  export interface PluginViewRegistration {
148
239
  id: string;
149
240
  title: string;
150
- icon: string;
241
+ icon: PluginIcon;
151
242
  /**
152
243
  * Where the host surfaces the view's nav entry. `'rail'` (default) places it on
153
244
  * the icon rail; `'sidebar'` places it in the left projects sidebar. Either way
@@ -157,6 +248,8 @@ export interface PluginViewRegistration {
157
248
  order?: number;
158
249
  shortcut?: string;
159
250
  component: PluginComponentLoader<PluginViewProps> | PluginComponent<PluginViewProps>;
251
+ /** Custom content for a sidebar-placed View's host-owned navigation slot. */
252
+ navigationComponent?: PluginComponentLoader<PluginSidebarNavigationProps> | PluginComponent<PluginSidebarNavigationProps>;
160
253
  }
161
254
  export interface PluginTaskPaneTabRegistration {
162
255
  id: string;
@@ -165,15 +258,72 @@ export interface PluginTaskPaneTabRegistration {
165
258
  order?: number;
166
259
  component: PluginComponentLoader<PluginTaskPaneProps> | PluginComponent<PluginTaskPaneProps>;
167
260
  }
261
+ export interface PluginTaskUISectionRegistration {
262
+ id: string;
263
+ order?: number;
264
+ component: PluginComponentLoader<PluginTaskUISectionProps> | PluginComponent<PluginTaskUISectionProps>;
265
+ }
266
+ /**
267
+ * Where a settings section is surfaced. `'project'` (the default) renders it on the
268
+ * per-project settings page, scoped to the active project. `'global'` renders it
269
+ * inside the plugin's own card on the global settings page — for configuration that
270
+ * is one value for the whole app (an API key, a credential) rather than per-project.
271
+ */
272
+ export type PluginSettingsSectionScope = 'project' | 'global';
168
273
  export interface PluginSettingsSectionRegistration {
169
274
  id: string;
170
275
  title: string;
171
276
  order?: number;
277
+ /** Defaults to `'project'` when omitted. */
278
+ scope?: PluginSettingsSectionScope;
172
279
  component: PluginComponentLoader<PluginSettingsSectionProps> | PluginComponent<PluginSettingsSectionProps>;
173
280
  }
174
281
  export interface FrontendViewRegistry {
175
282
  register(registration: PluginViewRegistration): Disposable;
176
283
  }
284
+ export type InjectionPointLocation = 'createTaskPrompt' | 'agentSession' | 'backlogPrompt';
285
+ export interface PluginInjectionPointProps extends Record<string, unknown> {
286
+ api: FrontendOpenForgeAPI;
287
+ context: OpenForgeContextSnapshot;
288
+ location: InjectionPointLocation;
289
+ projectId: string | null;
290
+ taskId: string | null;
291
+ onInsert: (text: string) => void;
292
+ }
293
+ export interface PluginInjectionPointRegistration {
294
+ id: string;
295
+ location: InjectionPointLocation;
296
+ component: PluginComponentLoader<PluginInjectionPointProps> | PluginComponent<PluginInjectionPointProps>;
297
+ }
298
+ export interface TaskStartPrefixContext {
299
+ /** The task being started, or null when the caller is authoring a new one. */
300
+ taskId: string | null;
301
+ projectId: string | null;
302
+ }
303
+ export interface TaskStartPrefixProviderRegistration {
304
+ id: string;
305
+ /** Menu label shown on the surface offering this provider. */
306
+ title: string;
307
+ /** Lower sorts first. Defaults to 0. */
308
+ order?: number;
309
+ /**
310
+ * Asks the user for a prefix. Returning null means they cancelled, and the
311
+ * task is not started. The host — not the provider — starts the task, so the
312
+ * diverged-branch gate, starting spinner and terminal handling all still run.
313
+ */
314
+ provide(context: TaskStartPrefixContext): MaybePromise<string | null>;
315
+ }
316
+ export interface FrontendTaskStartRegistry {
317
+ registerPrefixProvider(registration: TaskStartPrefixProviderRegistration): Disposable;
318
+ }
319
+ export interface FrontendInjectionPointRegistry {
320
+ register(registration: PluginInjectionPointRegistration): Disposable;
321
+ }
322
+ export interface FrontendTaskUIRegistry {
323
+ registerTab(registration: PluginTaskPaneTabRegistration): Disposable;
324
+ registerSection(registration: PluginTaskUISectionRegistration): Disposable;
325
+ }
326
+ /** @deprecated Use `FrontendTaskUIRegistry.registerTab` through `openforge.taskUI`. */
177
327
  export interface FrontendTaskPaneRegistry {
178
328
  registerTab(registration: PluginTaskPaneTabRegistration): Disposable;
179
329
  }
@@ -223,6 +373,37 @@ export interface FileSystemAPI {
223
373
  limit?: number;
224
374
  }): Promise<string[]>;
225
375
  }
376
+ export interface UserDataDirectoryRequest {
377
+ path?: string | null;
378
+ }
379
+ export interface UserDataFileRequest {
380
+ path: string;
381
+ }
382
+ export interface UserDataFileWriteRequest extends UserDataFileRequest {
383
+ content: string;
384
+ }
385
+ export interface ExternalReadDirectoryRequest {
386
+ root: string;
387
+ path?: string | null;
388
+ }
389
+ export interface ExternalReadFileRequest {
390
+ root: string;
391
+ path: string;
392
+ }
393
+ export interface UserDataFileSystemAPI {
394
+ readDir(request?: UserDataDirectoryRequest): Promise<FileEntry[]>;
395
+ readTextFile(request: UserDataFileRequest): Promise<string>;
396
+ writeTextFile(request: UserDataFileWriteRequest): Promise<void>;
397
+ }
398
+ export interface ExternalReadFileSystemAPI {
399
+ readDir(request: ExternalReadDirectoryRequest): Promise<FileEntry[]>;
400
+ readTextFile(request: ExternalReadFileRequest): Promise<string>;
401
+ }
402
+ export interface BackendFileSystemAPI extends FileSystemAPI {
403
+ userData: UserDataFileSystemAPI;
404
+ external: ExternalReadFileSystemAPI;
405
+ }
406
+ export type TerminalImageProtocol = 'iterm2';
226
407
  export interface ShellSessionRequest {
227
408
  taskId: string;
228
409
  terminalIndex: number;
@@ -231,6 +412,7 @@ export interface ShellSpawnRequest extends ShellSessionRequest {
231
412
  cwd: string;
232
413
  cols: number;
233
414
  rows: number;
415
+ terminalImageProtocol?: TerminalImageProtocol | null;
234
416
  }
235
417
  export interface ShellWriteRequest extends ShellSessionRequest {
236
418
  data: string;
@@ -239,12 +421,16 @@ export interface ShellResizeRequest extends ShellSessionRequest {
239
421
  cols: number;
240
422
  rows: number;
241
423
  }
424
+ export interface PtyBufferState {
425
+ buffer: string | null;
426
+ isLive: boolean;
427
+ }
242
428
  export interface ShellAPI {
243
429
  spawn(request: ShellSpawnRequest): Promise<number>;
244
430
  write(request: ShellWriteRequest): Promise<void>;
245
431
  resize(request: ShellResizeRequest): Promise<void>;
246
432
  kill(request: ShellSessionRequest): Promise<void>;
247
- getBuffer(request: ShellSessionRequest): Promise<string | null>;
433
+ getBuffer(request: ShellSessionRequest): Promise<PtyBufferState>;
248
434
  }
249
435
  export interface CreateTaskRequest {
250
436
  initialPrompt: string;
@@ -252,10 +438,27 @@ export interface CreateTaskRequest {
252
438
  dependsOn?: string[];
253
439
  labelNames?: string[];
254
440
  }
441
+ export interface ComposeTaskRequest {
442
+ projectId: string;
443
+ /** Seeds the dialog's prompt field; the user edits it before saving. */
444
+ initialPrompt: string;
445
+ sourceTicketUrl?: string | null;
446
+ title?: string | null;
447
+ }
448
+ export interface ComposeTaskResult {
449
+ task: Task;
450
+ /** True when the user chose Start Task rather than plain Create. */
451
+ started: boolean;
452
+ }
255
453
  export interface StartPromptContribution {
454
+ /** Host-assigned identity of the plugin that owns this persisted contribution. */
455
+ readonly ownerPluginId?: string;
256
456
  id: string;
257
457
  enabled: boolean;
258
- /** Prompt text injected before OpenForge's task prompt. The host substitutes {{taskId}} and {{task_id}}. */
458
+ /**
459
+ * Prompt text injected before OpenForge's task prompt.
460
+ * The host substitutes {{taskId}} and {{task_id}}.
461
+ */
259
462
  content: string;
260
463
  /** Lower values are injected first. Defaults to 0. */
261
464
  order?: number;
@@ -271,17 +474,47 @@ export interface ImplementationRun {
271
474
  sessionId: string;
272
475
  workspacePath: string;
273
476
  }
477
+ export type TaskFollowUpDisposition = 'delivered' | 'queued';
478
+ export interface SendTaskFollowUpRequest {
479
+ taskId: string;
480
+ message: string;
481
+ }
482
+ export interface TaskFollowUpReceipt {
483
+ taskId: string;
484
+ sessionId: string;
485
+ disposition: TaskFollowUpDisposition;
486
+ }
487
+ export type TaskFollowUpErrorCode = 'NO_SESSION' | 'DELIVERY_FAILED';
488
+ export declare class TaskFollowUpError extends Error {
489
+ readonly code: TaskFollowUpErrorCode;
490
+ constructor(code: TaskFollowUpErrorCode, message: string);
491
+ }
274
492
  export interface TasksAPI {
493
+ /**
494
+ * Lists tasks, optionally scoped to a project. By default done tasks are
495
+ * excluded (matching the app board's active-only view); pass
496
+ * `includeDone: true` to include tasks in the terminal `done` state. The
497
+ * unscoped listing (no `projectId`) always returns all states, so
498
+ * `includeDone` only affects the project-scoped path.
499
+ */
275
500
  list(request?: {
276
501
  projectId?: string | null;
502
+ includeDone?: boolean;
277
503
  }): Promise<Task[]>;
278
- get(taskId: string): Promise<Task>;
504
+ get(taskId: string): Promise<Task | null>;
279
505
  create(request: CreateTaskRequest): Promise<Task>;
280
- updateSummary(taskId: string, summary: string): Promise<void>;
506
+ /**
507
+ * Opens the host's create-task dialog pre-filled, letting the user edit the
508
+ * prompt — including anything contributed at that injection point —
509
+ * before the task exists.
510
+ * Resolves null if they dismiss it.
511
+ */
512
+ compose(request: ComposeTaskRequest): Promise<ComposeTaskResult | null>;
281
513
  updateStatus(taskId: string, status: WritableBoardStatus): Promise<void>;
282
514
  listStartPromptContributions(projectId: string): Promise<StartPromptContribution[]>;
283
515
  configureStartPromptContribution(request: ConfigureStartPromptContributionRequest): Promise<StartPromptContribution[]>;
284
516
  startImplementation(request: StartTaskImplementationRequest): Promise<ImplementationRun>;
517
+ sendFollowUp(request: SendTaskFollowUpRequest): Promise<TaskFollowUpReceipt>;
285
518
  getWorkspace(taskId: string): Promise<TaskWorkspaceInfo | null>;
286
519
  getLatestSession(taskId: string): Promise<AgentSession | null>;
287
520
  }
@@ -302,6 +535,7 @@ export interface AttentionAPI {
302
535
  }
303
536
  export interface SystemAPI {
304
537
  openUrl(url: string): Promise<void>;
538
+ writeClipboardText(text: string): Promise<void>;
305
539
  }
306
540
  export interface KeyValueConfigAPI {
307
541
  get<T extends JsonValue = JsonValue>(key: string, projectId?: string): Promise<T | null>;
@@ -325,13 +559,20 @@ export interface OpenForgeCommonAPI {
325
559
  projectConfig: KeyValueConfigAPI;
326
560
  }
327
561
  export interface FrontendOpenForgeAPI extends OpenForgeCommonAPI {
562
+ browserSurfaces: BrowserSurfacesAPI;
563
+ taskLinks: TaskLinksAPI;
328
564
  navigation: NavigationAPI;
329
565
  views: FrontendViewRegistry;
566
+ taskUI: FrontendTaskUIRegistry;
567
+ /** @deprecated Use `taskUI.registerTab(...)`. */
330
568
  taskPane: FrontendTaskPaneRegistry;
331
569
  settings: FrontendSettingsRegistry;
332
570
  backend: FrontendBackendBridge;
571
+ injectionPoints: FrontendInjectionPointRegistry;
572
+ taskStart: FrontendTaskStartRegistry;
333
573
  }
334
574
  export interface BackendOpenForgeAPI extends OpenForgeCommonAPI {
575
+ fs: BackendFileSystemAPI;
335
576
  backend: BackendMethodRegistry;
336
577
  background: BackgroundServiceRegistry;
337
578
  }
@@ -348,3 +589,4 @@ export declare function parsePluginViewKey(key: PluginViewKey): {
348
589
  pluginId: string;
349
590
  viewId: string;
350
591
  };
592
+ export {};
package/dist/types.js CHANGED
@@ -10,6 +10,53 @@ 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
+ const OPENFORGE_PLUGIN_CAPABILITY_TYPE_MEMBERS = [
14
+ 'commands',
15
+ 'events',
16
+ 'views',
17
+ 'injectionPoints',
18
+ 'taskPane',
19
+ 'taskStart',
20
+ 'settings',
21
+ 'background',
22
+ 'backend',
23
+ 'storage',
24
+ 'context',
25
+ 'navigation',
26
+ 'tasks',
27
+ 'projects',
28
+ 'fs',
29
+ 'shell',
30
+ 'notifications',
31
+ 'attention',
32
+ 'system.openUrl',
33
+ 'system.writeClipboardText',
34
+ 'config',
35
+ 'projectConfig',
36
+ 'browserSurfaces',
37
+ 'taskLinks',
38
+ 'appEnablement',
39
+ 'customSidebarNavigation',
40
+ ];
41
+ function assertOpenForgePluginCapabilitiesMatchSchema() {
42
+ const schemaCapabilities = packageMetadataSchemaData.properties.requires.items.enum;
43
+ if (!Array.isArray(schemaCapabilities) || !schemaCapabilities.every((capability) => typeof capability === 'string')) {
44
+ throw new Error('openforgePackageMetadataSchema.json properties.requires.items.enum must contain only strings');
45
+ }
46
+ if (schemaCapabilities.length !== OPENFORGE_PLUGIN_CAPABILITY_TYPE_MEMBERS.length
47
+ || schemaCapabilities.some((capability, index) => capability !== OPENFORGE_PLUGIN_CAPABILITY_TYPE_MEMBERS[index])) {
48
+ throw new Error('OpenForgePluginCapability must match openforgePackageMetadataSchema.json properties.requires.items.enum');
49
+ }
50
+ }
51
+ assertOpenForgePluginCapabilitiesMatchSchema();
52
+ export class TaskFollowUpError extends Error {
53
+ code;
54
+ constructor(code, message) {
55
+ super(message);
56
+ this.name = 'TaskFollowUpError';
57
+ this.code = code;
58
+ }
59
+ }
13
60
  export function makePluginViewKey(pluginId, viewId) {
14
61
  return `plugin:${pluginId}:${viewId}`;
15
62
  }
@@ -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>