@openforge-app/plugin-sdk 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend.d.ts +2 -2
- package/dist/collapsibleSectionState.js +30 -12
- package/dist/domain.d.ts +1 -0
- package/dist/domain.js +2 -1
- package/dist/frontend.d.ts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/manifest.d.ts +1 -0
- package/dist/manifest.js +10 -4
- package/dist/markdown.d.ts +9 -0
- package/dist/markdown.js +57 -1
- package/dist/openforgePackageMetadataSchema.json +2 -11
- package/dist/publicEntrypoints.d.mts +26 -0
- package/dist/publicEntrypoints.mjs +103 -0
- package/dist/publicUiExports.mjs +2 -0
- package/dist/taskBrowserDevToolsShortcuts.d.ts +12 -0
- package/dist/taskBrowserDevToolsShortcuts.js +20 -0
- package/dist/testing/commonApiFake.js +206 -6
- package/dist/testing/contracts.d.ts +15 -3
- package/dist/testing/frontendContributionFake.d.ts +2 -3
- package/dist/testing/frontendContributionFake.js +0 -32
- package/dist/testing/support.d.ts +5 -2
- package/dist/testing/support.js +12 -1
- package/dist/types.d.ts +80 -19
- package/dist/types.js +1 -1
- package/dist/ui/Modal.svelte +33 -3
- package/dist/ui/PluginPageHeader.svelte +11 -4
- package/dist/ui/PluginPageShell.svelte +20 -0
- package/dist/ui/ProjectFileTree.svelte +192 -0
- package/dist/vite.js +7 -17
- package/package.json +9 -2
|
@@ -1,6 +1,71 @@
|
|
|
1
|
-
import { resolveExternalTextFileChunkSize } from '../types.js';
|
|
1
|
+
import { MAX_AGENT_SESSION_PAGE_SIZE, resolveExternalTextFileChunkSize } from '../types.js';
|
|
2
2
|
import { assertFunction, assertTitle, commandDescriptor, createDisposable, isJsonValue, normalizeAgentCommandMetadata, } from './support.js';
|
|
3
3
|
const UTF8_ENCODER = new TextEncoder();
|
|
4
|
+
const UTF8_DECODER = new TextDecoder('utf-8', { fatal: true });
|
|
5
|
+
const TERMINAL_AGENT_SESSION_STATUSES = new Set(['completed', 'failed', 'interrupted']);
|
|
6
|
+
function encodeAgentSessionCursor(payload) {
|
|
7
|
+
const bytes = UTF8_ENCODER.encode(JSON.stringify(payload));
|
|
8
|
+
let binary = '';
|
|
9
|
+
for (const byte of bytes)
|
|
10
|
+
binary += String.fromCharCode(byte);
|
|
11
|
+
return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
|
|
12
|
+
}
|
|
13
|
+
function parseAgentSessionCursor(cursor) {
|
|
14
|
+
try {
|
|
15
|
+
const base64 = cursor.replaceAll('-', '+').replaceAll('_', '/');
|
|
16
|
+
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
|
|
17
|
+
const binary = atob(padded);
|
|
18
|
+
const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
19
|
+
const payload = JSON.parse(UTF8_DECODER.decode(bytes));
|
|
20
|
+
const filters = payload.filters;
|
|
21
|
+
if (payload.version !== 1
|
|
22
|
+
|| !Number.isSafeInteger(payload.createdAt)
|
|
23
|
+
|| typeof payload.id !== 'string'
|
|
24
|
+
|| payload.id.length === 0
|
|
25
|
+
|| !filters
|
|
26
|
+
|| typeof filters.provider !== 'string'
|
|
27
|
+
|| !Number.isSafeInteger(filters.startInclusive)
|
|
28
|
+
|| !Number.isSafeInteger(filters.endExclusive)
|
|
29
|
+
|| (filters.taskId !== null && typeof filters.taskId !== 'string')) {
|
|
30
|
+
throw new Error('invalid payload');
|
|
31
|
+
}
|
|
32
|
+
return payload;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new TypeError('cursor is malformed');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function providerSessionId(session) {
|
|
39
|
+
switch (session.provider) {
|
|
40
|
+
case 'opencode': return session.opencode_session_id;
|
|
41
|
+
case 'claude-code': return session.claude_session_id;
|
|
42
|
+
case 'pi': return session.pi_session_id;
|
|
43
|
+
case 'grok': return session.grok_session_id;
|
|
44
|
+
default: return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function assertNonNegativeSafeInteger(value, name) {
|
|
48
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
49
|
+
throw new RangeError(`${name} must be a non-negative safe integer`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function testingExternalFileIdentity(file) {
|
|
53
|
+
return file.identity ?? `${file.root}:${file.path}`;
|
|
54
|
+
}
|
|
55
|
+
function readTestingExternalTextRange(file, startOffsetBytes, maxBytes, expectedIdentity) {
|
|
56
|
+
assertNonNegativeSafeInteger(startOffsetBytes, 'startOffsetBytes');
|
|
57
|
+
if (maxBytes !== undefined)
|
|
58
|
+
assertNonNegativeSafeInteger(maxBytes, 'maxBytes');
|
|
59
|
+
const identity = testingExternalFileIdentity(file);
|
|
60
|
+
if (expectedIdentity !== undefined && expectedIdentity !== identity) {
|
|
61
|
+
throw new Error(`External file identity changed: expected ${expectedIdentity}, received ${identity}`);
|
|
62
|
+
}
|
|
63
|
+
const bytes = UTF8_ENCODER.encode(file.content);
|
|
64
|
+
const endOffsetBytes = maxBytes === undefined
|
|
65
|
+
? bytes.byteLength
|
|
66
|
+
: Math.min(bytes.byteLength, startOffsetBytes + maxBytes);
|
|
67
|
+
return UTF8_DECODER.decode(bytes.slice(startOffsetBytes, endOffsetBytes));
|
|
68
|
+
}
|
|
4
69
|
function* splitExternalTextFile(content, maxBytes) {
|
|
5
70
|
let chunk = '';
|
|
6
71
|
let chunkBytes = 0;
|
|
@@ -45,6 +110,100 @@ export class TestingCommonApiFake {
|
|
|
45
110
|
context: {
|
|
46
111
|
getSnapshot: () => this.services.getContextSnapshot(),
|
|
47
112
|
},
|
|
113
|
+
agentSessions: {
|
|
114
|
+
list: async (request) => {
|
|
115
|
+
if (typeof request.provider !== 'string' || request.provider.trim().length === 0) {
|
|
116
|
+
throw new TypeError('provider must be a non-empty string');
|
|
117
|
+
}
|
|
118
|
+
if (request.taskId !== undefined && (typeof request.taskId !== 'string' || request.taskId.trim().length === 0)) {
|
|
119
|
+
throw new TypeError('taskId must be a non-empty string');
|
|
120
|
+
}
|
|
121
|
+
if (request.cursor !== undefined && (typeof request.cursor !== 'string' || request.cursor.length === 0)) {
|
|
122
|
+
throw new TypeError('cursor must be a non-empty string');
|
|
123
|
+
}
|
|
124
|
+
if (!request.overlaps || typeof request.overlaps !== 'object') {
|
|
125
|
+
throw new TypeError('overlaps must contain startInclusive and endExclusive');
|
|
126
|
+
}
|
|
127
|
+
assertNonNegativeSafeInteger(request.overlaps.startInclusive, 'overlaps.startInclusive');
|
|
128
|
+
assertNonNegativeSafeInteger(request.overlaps.endExclusive, 'overlaps.endExclusive');
|
|
129
|
+
if (request.overlaps.startInclusive >= request.overlaps.endExclusive) {
|
|
130
|
+
throw new RangeError('overlaps must satisfy startInclusive < endExclusive');
|
|
131
|
+
}
|
|
132
|
+
if (!Number.isSafeInteger(request.pageSize)
|
|
133
|
+
|| request.pageSize < 1
|
|
134
|
+
|| request.pageSize > MAX_AGENT_SESSION_PAGE_SIZE) {
|
|
135
|
+
throw new RangeError(`pageSize must be between 1 and ${MAX_AGENT_SESSION_PAGE_SIZE}`);
|
|
136
|
+
}
|
|
137
|
+
const filters = {
|
|
138
|
+
provider: request.provider,
|
|
139
|
+
startInclusive: request.overlaps.startInclusive,
|
|
140
|
+
endExclusive: request.overlaps.endExclusive,
|
|
141
|
+
taskId: request.taskId ?? null,
|
|
142
|
+
};
|
|
143
|
+
const cursor = request.cursor === undefined ? null : parseAgentSessionCursor(request.cursor);
|
|
144
|
+
if (cursor !== null
|
|
145
|
+
&& (cursor.filters.provider !== filters.provider
|
|
146
|
+
|| cursor.filters.startInclusive !== filters.startInclusive
|
|
147
|
+
|| cursor.filters.endExclusive !== filters.endExclusive
|
|
148
|
+
|| cursor.filters.taskId !== filters.taskId)) {
|
|
149
|
+
throw new TypeError('cursor does not match request filters');
|
|
150
|
+
}
|
|
151
|
+
this.services.calls.agentSessionListRequests.push({
|
|
152
|
+
...request,
|
|
153
|
+
overlaps: { ...request.overlaps },
|
|
154
|
+
});
|
|
155
|
+
const taskById = new Map(this.services.seededTasks.map((task) => [task.id, task]));
|
|
156
|
+
const rows = this.services.seededAgentSessions
|
|
157
|
+
.filter((session) => taskById.has(session.ticket_id))
|
|
158
|
+
.filter((session) => session.provider === request.provider)
|
|
159
|
+
.filter((session) => request.taskId === undefined || session.ticket_id === request.taskId)
|
|
160
|
+
.filter((session) => session.created_at < request.overlaps.endExclusive
|
|
161
|
+
&& (!TERMINAL_AGENT_SESSION_STATUSES.has(session.status)
|
|
162
|
+
|| session.updated_at > request.overlaps.startInclusive))
|
|
163
|
+
.filter((session) => cursor === null
|
|
164
|
+
|| session.created_at > cursor.createdAt
|
|
165
|
+
|| (session.created_at === cursor.createdAt && session.id > cursor.id))
|
|
166
|
+
.slice()
|
|
167
|
+
.sort((left, right) => left.created_at - right.created_at
|
|
168
|
+
|| (left.id < right.id ? -1 : left.id > right.id ? 1 : 0));
|
|
169
|
+
const pageRows = rows.slice(0, request.pageSize);
|
|
170
|
+
const items = pageRows.map((session) => {
|
|
171
|
+
const task = taskById.get(session.ticket_id);
|
|
172
|
+
if (!task)
|
|
173
|
+
throw new Error(`Missing seeded Task for Agent Session ${session.id}`);
|
|
174
|
+
const workspace = this.services.agentSessionWorkspaces[task.id];
|
|
175
|
+
return {
|
|
176
|
+
id: session.id,
|
|
177
|
+
provider: session.provider,
|
|
178
|
+
providerSessionId: providerSessionId(session),
|
|
179
|
+
createdAt: session.created_at,
|
|
180
|
+
updatedAt: session.updated_at,
|
|
181
|
+
task: {
|
|
182
|
+
id: task.id,
|
|
183
|
+
title: task.title?.trim() || task.id,
|
|
184
|
+
status: task.status,
|
|
185
|
+
createdAt: task.created_at,
|
|
186
|
+
updatedAt: task.updated_at,
|
|
187
|
+
},
|
|
188
|
+
workspace: workspace
|
|
189
|
+
? { rootPath: workspace.rootPath, kind: workspace.kind }
|
|
190
|
+
: null,
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
const last = pageRows.at(-1);
|
|
194
|
+
return {
|
|
195
|
+
items,
|
|
196
|
+
nextCursor: rows.length > request.pageSize && last
|
|
197
|
+
? encodeAgentSessionCursor({
|
|
198
|
+
version: 1,
|
|
199
|
+
createdAt: last.created_at,
|
|
200
|
+
id: last.id,
|
|
201
|
+
filters,
|
|
202
|
+
})
|
|
203
|
+
: null,
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
},
|
|
48
207
|
tasks: {
|
|
49
208
|
list: async (request) => {
|
|
50
209
|
const projectId = request?.projectId ?? null;
|
|
@@ -125,6 +284,16 @@ export class TestingCommonApiFake {
|
|
|
125
284
|
},
|
|
126
285
|
getWorkspace: async () => null,
|
|
127
286
|
getLatestSession: async () => null,
|
|
287
|
+
listSessions: async (request) => {
|
|
288
|
+
this.services.calls.taskSessionListRequests.push({ ...request });
|
|
289
|
+
return this.services.seededAgentSessions
|
|
290
|
+
.map((session, index) => ({ session, index }))
|
|
291
|
+
.filter(({ session }) => session.ticket_id === request.taskId)
|
|
292
|
+
.filter(({ session }) => request.provider === undefined || session.provider === request.provider)
|
|
293
|
+
.filter(({ session }) => request.createdAtOrAfter === undefined || session.created_at >= request.createdAtOrAfter)
|
|
294
|
+
.sort((left, right) => right.session.created_at - left.session.created_at || right.index - left.index)
|
|
295
|
+
.map(({ session }) => session);
|
|
296
|
+
},
|
|
128
297
|
},
|
|
129
298
|
projects: {
|
|
130
299
|
list: async () => [],
|
|
@@ -146,7 +315,6 @@ export class TestingCommonApiFake {
|
|
|
146
315
|
write: async (request) => {
|
|
147
316
|
this.services.calls.shellWrites.push(request);
|
|
148
317
|
},
|
|
149
|
-
writeTerminalQueryResponse: async () => { },
|
|
150
318
|
resize: async (request) => {
|
|
151
319
|
this.services.calls.shellResizes.push(request);
|
|
152
320
|
},
|
|
@@ -215,10 +383,17 @@ export class TestingCommonApiFake {
|
|
|
215
383
|
},
|
|
216
384
|
readTextFile: async (request) => {
|
|
217
385
|
this.services.calls.fsUserDataReads.push(request);
|
|
218
|
-
return '';
|
|
386
|
+
return this.services.userDataTextFiles.get(request.path) ?? '';
|
|
219
387
|
},
|
|
220
388
|
writeTextFile: async (request) => {
|
|
221
389
|
this.services.calls.fsUserDataWrites.push(request);
|
|
390
|
+
this.services.userDataTextFiles.set(request.path, request.content);
|
|
391
|
+
},
|
|
392
|
+
appendTextFile: async (request) => {
|
|
393
|
+
this.services.calls.fsUserDataAppends.push(request);
|
|
394
|
+
const content = `${this.services.userDataTextFiles.get(request.path) ?? ''}${request.content}`;
|
|
395
|
+
this.services.userDataTextFiles.set(request.path, content);
|
|
396
|
+
return { sizeBytes: UTF8_ENCODER.encode(content).byteLength };
|
|
222
397
|
},
|
|
223
398
|
},
|
|
224
399
|
external: {
|
|
@@ -230,14 +405,39 @@ export class TestingCommonApiFake {
|
|
|
230
405
|
this.services.calls.fsExternalReads.push(request);
|
|
231
406
|
return '';
|
|
232
407
|
},
|
|
408
|
+
stat: async (request) => {
|
|
409
|
+
this.services.calls.fsExternalStats.push(request);
|
|
410
|
+
const file = this.services.externalTextFiles.find(candidate => candidate.root === request.root && candidate.path === request.path);
|
|
411
|
+
if (!file)
|
|
412
|
+
throw new Error(`External file not found: ${request.root}/${request.path}`);
|
|
413
|
+
return {
|
|
414
|
+
identity: testingExternalFileIdentity(file),
|
|
415
|
+
sizeBytes: UTF8_ENCODER.encode(file.content).byteLength,
|
|
416
|
+
modifiedAtMs: file.modifiedAtMs ?? null,
|
|
417
|
+
};
|
|
418
|
+
},
|
|
233
419
|
readTextFileChunks: (request) => {
|
|
234
420
|
const chunkSizeBytes = resolveExternalTextFileChunkSize(request.chunkSizeBytes);
|
|
235
|
-
const { root, path, signal } = request;
|
|
236
|
-
this.services.calls.fsExternalReadTextFileChunks.push({
|
|
237
|
-
|
|
421
|
+
const { root, path, signal, expectedIdentity, startOffsetBytes = 0, maxBytes, } = request;
|
|
422
|
+
this.services.calls.fsExternalReadTextFileChunks.push({
|
|
423
|
+
root,
|
|
424
|
+
path,
|
|
425
|
+
chunkSizeBytes,
|
|
426
|
+
...(expectedIdentity === undefined ? {} : { expectedIdentity }),
|
|
427
|
+
...(request.startOffsetBytes === undefined ? {} : { startOffsetBytes }),
|
|
428
|
+
...(maxBytes === undefined ? {} : { maxBytes }),
|
|
429
|
+
});
|
|
430
|
+
const file = this.services.externalTextFiles.find(candidate => candidate.root === root && candidate.path === path);
|
|
238
431
|
return (async function* () {
|
|
432
|
+
signal?.throwIfAborted();
|
|
433
|
+
if (!file)
|
|
434
|
+
throw new Error(`External file not found: ${root}/${path}`);
|
|
435
|
+
const content = readTestingExternalTextRange(file, startOffsetBytes, maxBytes, expectedIdentity);
|
|
239
436
|
for (const chunk of splitExternalTextFile(content, chunkSizeBytes)) {
|
|
240
437
|
signal?.throwIfAborted();
|
|
438
|
+
if (expectedIdentity !== undefined && testingExternalFileIdentity(file) !== expectedIdentity) {
|
|
439
|
+
throw new Error(`External file identity changed: expected ${expectedIdentity}, received ${testingExternalFileIdentity(file)}`);
|
|
440
|
+
}
|
|
241
441
|
yield chunk;
|
|
242
442
|
}
|
|
243
443
|
signal?.throwIfAborted();
|
|
@@ -1,7 +1,7 @@
|
|
|
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, PluginReviewRowActionRegistration, PluginTaskUISectionRegistration, PluginViewRegistration, ShellSpawnRequest, SendTaskFollowUpRequest, StartTaskImplementationRequest,
|
|
4
|
-
import type { Task } from '../domain';
|
|
3
|
+
import type { BackendMethodRegistration, BackendOpenForgeAPI, BackgroundServiceRegistration, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ConfigureStartPromptContributionRequest, CreateTaskRequest, FrontendOpenForgeAPI, InjectionPointLocation, AgentSessionWorkspace, ListAgentSessionsRequest, ListTaskSessionsRequest, JsonValue, NotificationRequest, OpenForgeNavigationRequest, OpenForgePackageMetadata, PluginSettingsSectionRegistration, PluginStorage, PluginTaskPaneTabRegistration, PluginReviewRowActionRegistration, PluginTaskUISectionRegistration, PluginViewRegistration, ShellSpawnRequest, SendTaskFollowUpRequest, StartTaskImplementationRequest, TaskStartPrefixContext, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, UserDataDirectoryRequest, UserDataFileRequest, UserDataFileWriteRequest } from '../types';
|
|
4
|
+
import type { AgentSession, Task } from '../domain';
|
|
5
5
|
export type TestingRuntimeScope = 'global' | 'project' | 'task';
|
|
6
6
|
export type TestingRuntimeKind = 'commands' | 'events' | 'views' | 'taskPane' | 'taskUI' | 'reviewUI' | 'settings' | 'backend' | 'background';
|
|
7
7
|
export type TestingMaybePromise<T> = T | Promise<T>;
|
|
@@ -9,6 +9,9 @@ export type TestingCommandHandler = (payload?: unknown) => TestingMaybePromise<u
|
|
|
9
9
|
export type TestingEventHandler = (payload: unknown) => void;
|
|
10
10
|
export interface TestingExternalTextFile extends ExternalReadFileRequest {
|
|
11
11
|
content: string;
|
|
12
|
+
/** Defaults to a deterministic identity derived from root and path. */
|
|
13
|
+
identity?: string;
|
|
14
|
+
modifiedAtMs?: number | null;
|
|
12
15
|
}
|
|
13
16
|
export type TestingExternalTextFileChunksCall = Omit<ExternalReadTextFileChunksRequest, 'signal' | 'chunkSizeBytes'> & {
|
|
14
17
|
chunkSizeBytes: number;
|
|
@@ -21,12 +24,18 @@ export interface TestingOpenForgeApiOptions {
|
|
|
21
24
|
viewId?: string;
|
|
22
25
|
packageMetadata?: OpenForgePackageMetadata;
|
|
23
26
|
storage?: PluginStorage;
|
|
27
|
+
/** Initial files exposed through `fs.userData`. Defaults to none. */
|
|
28
|
+
userDataTextFiles?: UserDataFileWriteRequest[];
|
|
24
29
|
/**
|
|
25
30
|
* Tasks returned by `tasks.list`. The mock filters them by the requested
|
|
26
31
|
* `projectId` (when given) and drops `done` tasks unless `includeDone: true`,
|
|
27
32
|
* mirroring the host capability. Defaults to an empty list.
|
|
28
33
|
*/
|
|
29
34
|
tasks?: Task[];
|
|
35
|
+
/** Agent Sessions returned by `tasks.listSessions`. Defaults to an empty list. */
|
|
36
|
+
agentSessions?: AgentSession[];
|
|
37
|
+
/** Compact workspace context keyed by Task ID for `agentSessions.list`. Defaults to none. */
|
|
38
|
+
agentSessionWorkspaces?: Readonly<Record<string, AgentSessionWorkspace>>;
|
|
30
39
|
/** UTF-8 files returned by `fs.external.readTextFileChunks`. Defaults to none. */
|
|
31
40
|
externalTextFiles?: TestingExternalTextFile[];
|
|
32
41
|
}
|
|
@@ -56,7 +65,6 @@ export interface TestingOpenForgeApiCalls {
|
|
|
56
65
|
}>;
|
|
57
66
|
openUrl: string[];
|
|
58
67
|
clipboardWrites: string[];
|
|
59
|
-
taskLinkOpenRequests: TaskLinkOpenRequest[];
|
|
60
68
|
navigationRequests: OpenForgeNavigationRequest[];
|
|
61
69
|
notify: NotificationRequest[];
|
|
62
70
|
taskCreations: CreateTaskRequest[];
|
|
@@ -68,6 +76,8 @@ export interface TestingOpenForgeApiCalls {
|
|
|
68
76
|
projectId: string | null;
|
|
69
77
|
includeDone: boolean;
|
|
70
78
|
}>;
|
|
79
|
+
agentSessionListRequests: ListAgentSessionsRequest[];
|
|
80
|
+
taskSessionListRequests: ListTaskSessionsRequest[];
|
|
71
81
|
taskStatusUpdates: Array<{
|
|
72
82
|
taskId: string;
|
|
73
83
|
status: string;
|
|
@@ -85,8 +95,10 @@ export interface TestingOpenForgeApiCalls {
|
|
|
85
95
|
fsUserDataReadDirs: UserDataDirectoryRequest[];
|
|
86
96
|
fsUserDataReads: UserDataFileRequest[];
|
|
87
97
|
fsUserDataWrites: UserDataFileWriteRequest[];
|
|
98
|
+
fsUserDataAppends: UserDataFileWriteRequest[];
|
|
88
99
|
fsExternalReadDirs: ExternalReadDirectoryRequest[];
|
|
89
100
|
fsExternalReads: ExternalReadFileRequest[];
|
|
101
|
+
fsExternalStats: ExternalReadFileRequest[];
|
|
90
102
|
fsExternalReadTextFileChunks: TestingExternalTextFileChunksCall[];
|
|
91
103
|
shellSpawns: ShellSpawnRequest[];
|
|
92
104
|
shellWrites: Array<{
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { TaskBrowserSurfaceState } from '../browserSurfaces';
|
|
2
2
|
import type { FrontendOpenForgeAPI } from '../types';
|
|
3
3
|
import { type TestingRegistryServices } from './support.js';
|
|
4
4
|
import type { TestingInjectionPointContribution, TestingTaskStartPrefixProviderContribution, TestingSettingsSectionContribution, TestingTaskPaneTabContribution, TestingReviewRowActionContribution, TestingTaskUISectionContribution, TestingViewContribution } from './contracts';
|
|
5
|
-
type TestingFrontendContributionApi = Pick<FrontendOpenForgeAPI, 'browserSurfaces' | '
|
|
5
|
+
type TestingFrontendContributionApi = Pick<FrontendOpenForgeAPI, 'browserSurfaces' | 'views' | 'taskUI' | 'reviewUI' | 'taskPane' | 'settings' | 'backend' | 'injectionPoints' | 'taskStart'>;
|
|
6
6
|
export declare class TestingFrontendContributionFake {
|
|
7
7
|
private readonly services;
|
|
8
8
|
private readonly invokeBackendMethod;
|
|
@@ -14,7 +14,6 @@ export declare class TestingFrontendContributionFake {
|
|
|
14
14
|
private readonly injectionPoints;
|
|
15
15
|
private readonly taskStartPrefixProviders;
|
|
16
16
|
private readonly browserSurfaces;
|
|
17
|
-
private taskLinkHandler;
|
|
18
17
|
private api;
|
|
19
18
|
constructor(services: TestingRegistryServices, invokeBackendMethod: (method: string, payload?: unknown) => Promise<unknown>);
|
|
20
19
|
createApi(): TestingFrontendContributionApi;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createTestingBrowserSurfaces } from '../browserSurfacesTesting.js';
|
|
2
|
-
import { isAllowedBrowserSurfaceUrl } from '../browserSurfaces.js';
|
|
3
2
|
import { assertFunction, assertTitle, createDisposable, } from './support.js';
|
|
4
3
|
export class TestingFrontendContributionFake {
|
|
5
4
|
services;
|
|
@@ -12,7 +11,6 @@ export class TestingFrontendContributionFake {
|
|
|
12
11
|
injectionPoints = new Map();
|
|
13
12
|
taskStartPrefixProviders = new Map();
|
|
14
13
|
browserSurfaces;
|
|
15
|
-
taskLinkHandler = null;
|
|
16
14
|
api = null;
|
|
17
15
|
constructor(services, invokeBackendMethod) {
|
|
18
16
|
this.services = services;
|
|
@@ -24,36 +22,6 @@ export class TestingFrontendContributionFake {
|
|
|
24
22
|
return this.api;
|
|
25
23
|
const api = {
|
|
26
24
|
browserSurfaces: this.browserSurfaces.api,
|
|
27
|
-
taskLinks: {
|
|
28
|
-
open: async (request) => {
|
|
29
|
-
this.services.calls.taskLinkOpenRequests.push(request);
|
|
30
|
-
if (!isAllowedBrowserSurfaceUrl(request.url)) {
|
|
31
|
-
throw new Error('Task links must use a valid HTTP(S) URL');
|
|
32
|
-
}
|
|
33
|
-
if (this.taskLinkHandler === null) {
|
|
34
|
-
this.services.calls.openUrl.push(request.url);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const result = await this.taskLinkHandler(request);
|
|
38
|
-
if (result === 'declined') {
|
|
39
|
-
this.services.calls.openUrl.push(request.url);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
if (result !== 'handled') {
|
|
43
|
-
throw new Error(`Task link handler returned an invalid result: ${String(result)}`);
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
registerHandler: (handler) => {
|
|
47
|
-
if (this.taskLinkHandler !== null) {
|
|
48
|
-
throw new Error('A Task link handler is already registered');
|
|
49
|
-
}
|
|
50
|
-
this.taskLinkHandler = handler;
|
|
51
|
-
return createDisposable(() => {
|
|
52
|
-
if (this.taskLinkHandler === handler)
|
|
53
|
-
this.taskLinkHandler = null;
|
|
54
|
-
});
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
25
|
views: {
|
|
58
26
|
register: (registration) => this.registerView(registration),
|
|
59
27
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AgentCommandMetadata, CommandDescriptor, Disposable, JsonValue, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, OpenForgePackageMetadata, PluginStorage, StartPromptContribution, SubscriptionSink } from '../types';
|
|
2
|
-
import type { Task } from '../domain';
|
|
1
|
+
import type { AgentSessionWorkspace, AgentCommandMetadata, CommandDescriptor, Disposable, JsonValue, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, OpenForgePackageMetadata, PluginStorage, StartPromptContribution, SubscriptionSink } from '../types';
|
|
2
|
+
import type { AgentSession, Task } from '../domain';
|
|
3
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 {
|
|
@@ -31,7 +31,10 @@ export declare class TestingRegistryServices {
|
|
|
31
31
|
readonly storage: PluginStorage;
|
|
32
32
|
readonly config: Map<string, JsonValue>;
|
|
33
33
|
readonly seededTasks: Task[];
|
|
34
|
+
readonly seededAgentSessions: AgentSession[];
|
|
35
|
+
readonly agentSessionWorkspaces: Readonly<Record<string, AgentSessionWorkspace>>;
|
|
34
36
|
readonly externalTextFiles: TestingExternalTextFile[];
|
|
37
|
+
readonly userDataTextFiles: Map<string, string>;
|
|
35
38
|
readonly claims: TestingContributionClaims;
|
|
36
39
|
constructor(options?: TestingOpenForgeApiOptions);
|
|
37
40
|
localQualifiedId(kind: TestingRuntimeKind, id: string): string;
|
package/dist/testing/support.js
CHANGED
|
@@ -36,7 +36,6 @@ export function createTestingCalls() {
|
|
|
36
36
|
emittedGlobalEvents: [],
|
|
37
37
|
openUrl: [],
|
|
38
38
|
clipboardWrites: [],
|
|
39
|
-
taskLinkOpenRequests: [],
|
|
40
39
|
navigationRequests: [],
|
|
41
40
|
notify: [],
|
|
42
41
|
taskCreations: [],
|
|
@@ -45,14 +44,18 @@ export function createTestingCalls() {
|
|
|
45
44
|
taskImplementationStarts: [],
|
|
46
45
|
taskFollowUps: [],
|
|
47
46
|
taskListRequests: [],
|
|
47
|
+
agentSessionListRequests: [],
|
|
48
|
+
taskSessionListRequests: [],
|
|
48
49
|
taskStatusUpdates: [],
|
|
49
50
|
configWrites: [],
|
|
50
51
|
fsWrites: [],
|
|
51
52
|
fsUserDataReadDirs: [],
|
|
52
53
|
fsUserDataReads: [],
|
|
53
54
|
fsUserDataWrites: [],
|
|
55
|
+
fsUserDataAppends: [],
|
|
54
56
|
fsExternalReadDirs: [],
|
|
55
57
|
fsExternalReads: [],
|
|
58
|
+
fsExternalStats: [],
|
|
56
59
|
fsExternalReadTextFileChunks: [],
|
|
57
60
|
shellSpawns: [],
|
|
58
61
|
shellWrites: [],
|
|
@@ -202,7 +205,10 @@ export class TestingRegistryServices {
|
|
|
202
205
|
storage;
|
|
203
206
|
config = new Map();
|
|
204
207
|
seededTasks;
|
|
208
|
+
seededAgentSessions;
|
|
209
|
+
agentSessionWorkspaces;
|
|
205
210
|
externalTextFiles;
|
|
211
|
+
userDataTextFiles = new Map();
|
|
206
212
|
claims = new TestingContributionClaims();
|
|
207
213
|
constructor(options = {}) {
|
|
208
214
|
this.pluginId = options.pluginId ?? 'test-plugin';
|
|
@@ -218,7 +224,12 @@ export class TestingRegistryServices {
|
|
|
218
224
|
this.calls = createTestingCalls();
|
|
219
225
|
this.storage = options.storage ?? createMemoryPluginStorage(this.calls);
|
|
220
226
|
this.seededTasks = options.tasks ?? [];
|
|
227
|
+
this.seededAgentSessions = options.agentSessions ?? [];
|
|
228
|
+
this.agentSessionWorkspaces = options.agentSessionWorkspaces ?? {};
|
|
221
229
|
this.externalTextFiles = options.externalTextFiles ?? [];
|
|
230
|
+
for (const file of options.userDataTextFiles ?? []) {
|
|
231
|
+
this.userDataTextFiles.set(file.path, file.content);
|
|
232
|
+
}
|
|
222
233
|
}
|
|
223
234
|
localQualifiedId(kind, id) {
|
|
224
235
|
assertLocalId(kind, id);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Component } from 'svelte';
|
|
2
2
|
import type { BrowserSurfacesAPI } from './browserSurfaces';
|
|
3
|
-
import type { AgentSession, CommandInfo, FileContent, FileEntry, Project, ProjectAttention, ReviewPullRequest, Task, TaskWorkspaceInfo, WritableBoardStatus } from './domain';
|
|
3
|
+
import type { BoardStatus, AgentSession, CommandInfo, FileContent, FileEntry, Project, ProjectAttention, ReviewPullRequest, Task, TaskWorkspaceInfo, WritableBoardStatus } from './domain';
|
|
4
4
|
export type SupportedOpenForgeApiVersion = 1;
|
|
5
5
|
export declare const SUPPORTED_OPENFORGE_API_VERSIONS: readonly [1, ...1[]];
|
|
6
6
|
export declare const OPENFORGE_PLUGIN_API_VERSION: SupportedOpenForgeApiVersion;
|
|
@@ -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', '
|
|
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', 'appEnablement', 'customSidebarNavigation', 'reviewUI'];
|
|
21
21
|
export type OpenForgePluginCapability = (typeof OPENFORGE_PLUGIN_CAPABILITY_TYPE_MEMBERS)[number];
|
|
22
22
|
export interface OpenForgePackageMetadata {
|
|
23
23
|
id: string;
|
|
@@ -74,16 +74,6 @@ export interface NavigationAPI {
|
|
|
74
74
|
get(): OpenForgeNavigationSnapshot;
|
|
75
75
|
navigate(request: OpenForgeNavigationRequest): Promise<OpenForgeNavigationSnapshot>;
|
|
76
76
|
}
|
|
77
|
-
export interface TaskLinkOpenRequest {
|
|
78
|
-
taskId: string;
|
|
79
|
-
url: string;
|
|
80
|
-
}
|
|
81
|
-
export type TaskLinkHandlerResult = 'handled' | 'declined';
|
|
82
|
-
export type TaskLinkHandler = (request: TaskLinkOpenRequest) => Promise<TaskLinkHandlerResult>;
|
|
83
|
-
export interface TaskLinksAPI {
|
|
84
|
-
open(request: TaskLinkOpenRequest): Promise<void>;
|
|
85
|
-
registerHandler(handler: TaskLinkHandler): Disposable;
|
|
86
|
-
}
|
|
87
77
|
export interface OpenForgePluginContext {
|
|
88
78
|
pluginId: string;
|
|
89
79
|
apiVersion: SupportedOpenForgeApiVersion;
|
|
@@ -409,6 +399,9 @@ export interface UserDataFileRequest {
|
|
|
409
399
|
export interface UserDataFileWriteRequest extends UserDataFileRequest {
|
|
410
400
|
content: string;
|
|
411
401
|
}
|
|
402
|
+
export interface UserDataFileAppendResult {
|
|
403
|
+
sizeBytes: number;
|
|
404
|
+
}
|
|
412
405
|
export interface ExternalReadDirectoryRequest {
|
|
413
406
|
root: string;
|
|
414
407
|
path?: string | null;
|
|
@@ -417,6 +410,12 @@ export interface ExternalReadFileRequest {
|
|
|
417
410
|
root: string;
|
|
418
411
|
path: string;
|
|
419
412
|
}
|
|
413
|
+
export interface ExternalFileMetadata {
|
|
414
|
+
/** Stable while the same filesystem object is appended in place. */
|
|
415
|
+
identity: string;
|
|
416
|
+
sizeBytes: number;
|
|
417
|
+
modifiedAtMs: number | null;
|
|
418
|
+
}
|
|
420
419
|
export declare const DEFAULT_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES: number;
|
|
421
420
|
export declare const MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES = 4;
|
|
422
421
|
export declare const MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES: number;
|
|
@@ -425,17 +424,27 @@ export declare function resolveExternalTextFileChunkSize(chunkSizeBytes?: number
|
|
|
425
424
|
export interface ExternalReadTextFileChunksRequest extends ExternalReadFileRequest {
|
|
426
425
|
/** UTF-8 chunks contain at most this many bytes. Defaults to 64 KiB. */
|
|
427
426
|
chunkSizeBytes?: number;
|
|
427
|
+
/** First byte to read. Must be a UTF-8 code point boundary. Defaults to zero. */
|
|
428
|
+
startOffsetBytes?: number;
|
|
429
|
+
/** Maximum total bytes to read. The range end must be a UTF-8 code point boundary. */
|
|
430
|
+
maxBytes?: number;
|
|
431
|
+
/** Fails the read if the file no longer has this identity. */
|
|
432
|
+
expectedIdentity?: string;
|
|
428
433
|
/** Stops future reads. An in-flight host read may finish, but its result is discarded. */
|
|
429
434
|
signal?: AbortSignal;
|
|
430
435
|
}
|
|
431
436
|
export interface UserDataFileSystemAPI {
|
|
432
437
|
readDir(request?: UserDataDirectoryRequest): Promise<FileEntry[]>;
|
|
433
438
|
readTextFile(request: UserDataFileRequest): Promise<string>;
|
|
439
|
+
/** Atomically replaces the file and syncs its contents before resolving. */
|
|
434
440
|
writeTextFile(request: UserDataFileWriteRequest): Promise<void>;
|
|
441
|
+
/** Appends and syncs content before returning the resulting UTF-8 byte size. */
|
|
442
|
+
appendTextFile(request: UserDataFileWriteRequest): Promise<UserDataFileAppendResult>;
|
|
435
443
|
}
|
|
436
444
|
export interface ExternalReadFileSystemAPI {
|
|
437
445
|
readDir(request: ExternalReadDirectoryRequest): Promise<FileEntry[]>;
|
|
438
446
|
readTextFile(request: ExternalReadFileRequest): Promise<string>;
|
|
447
|
+
stat(request: ExternalReadFileRequest): Promise<ExternalFileMetadata>;
|
|
439
448
|
/** Lazily reads a UTF-8 file without retaining a host file handle between chunks. */
|
|
440
449
|
readTextFileChunks(request: ExternalReadTextFileChunksRequest): AsyncIterable<string>;
|
|
441
450
|
}
|
|
@@ -457,10 +466,6 @@ export interface ShellSpawnRequest extends ShellSessionRequest {
|
|
|
457
466
|
export interface ShellWriteRequest extends ShellSessionRequest {
|
|
458
467
|
data: string;
|
|
459
468
|
}
|
|
460
|
-
export interface ShellTerminalQueryResponseRequest extends ShellSessionRequest {
|
|
461
|
-
ptyInstanceId: number;
|
|
462
|
-
data: string;
|
|
463
|
-
}
|
|
464
469
|
export interface ShellResizeRequest extends ShellSessionRequest {
|
|
465
470
|
cols: number;
|
|
466
471
|
rows: number;
|
|
@@ -469,9 +474,9 @@ export interface TerminalViewSnapshot {
|
|
|
469
474
|
instanceId: number;
|
|
470
475
|
watermark: number;
|
|
471
476
|
data: string;
|
|
477
|
+
compatibilityData?: string;
|
|
472
478
|
}
|
|
473
479
|
export interface PtyBufferState {
|
|
474
|
-
authority?: 'xterm-authoritative' | 'ghostty-authoritative';
|
|
475
480
|
buffer: string | null;
|
|
476
481
|
snapshot?: TerminalViewSnapshot | null;
|
|
477
482
|
isLive: boolean;
|
|
@@ -480,7 +485,6 @@ export interface PtyBufferState {
|
|
|
480
485
|
export interface ShellAPI {
|
|
481
486
|
spawn(request: ShellSpawnRequest): Promise<number>;
|
|
482
487
|
write(request: ShellWriteRequest): Promise<void>;
|
|
483
|
-
writeTerminalQueryResponse(request: ShellTerminalQueryResponseRequest): Promise<void>;
|
|
484
488
|
resize(request: ShellResizeRequest): Promise<void>;
|
|
485
489
|
kill(request: ShellSessionRequest): Promise<void>;
|
|
486
490
|
getBuffer(request: ShellSessionRequest): Promise<PtyBufferState>;
|
|
@@ -542,6 +546,61 @@ export declare class TaskFollowUpError extends Error {
|
|
|
542
546
|
readonly code: TaskFollowUpErrorCode;
|
|
543
547
|
constructor(code: TaskFollowUpErrorCode, message: string);
|
|
544
548
|
}
|
|
549
|
+
export interface ListTaskSessionsRequest {
|
|
550
|
+
taskId: string;
|
|
551
|
+
/** Inclusive Unix timestamp in seconds. Omit to return the Task's full Agent Session history. */
|
|
552
|
+
createdAtOrAfter?: number;
|
|
553
|
+
/** Open-ended provider identifier such as `pi`. Omit to include every provider. */
|
|
554
|
+
provider?: string;
|
|
555
|
+
}
|
|
556
|
+
export declare const MAX_AGENT_SESSION_PAGE_SIZE = 250;
|
|
557
|
+
export type AgentSessionCursor = string;
|
|
558
|
+
export interface AgentSessionOverlap {
|
|
559
|
+
/** Inclusive lower bound as a Unix timestamp in seconds. */
|
|
560
|
+
startInclusive: number;
|
|
561
|
+
/** Exclusive upper bound as a Unix timestamp in seconds. */
|
|
562
|
+
endExclusive: number;
|
|
563
|
+
}
|
|
564
|
+
export interface ListAgentSessionsRequest {
|
|
565
|
+
/** Open-ended provider identifier such as `pi`. */
|
|
566
|
+
provider: string;
|
|
567
|
+
overlaps: AgentSessionOverlap;
|
|
568
|
+
/** Restrict the query to one Task without enumerating unrelated Tasks. */
|
|
569
|
+
taskId?: string;
|
|
570
|
+
/** Opaque cursor returned by the preceding page. */
|
|
571
|
+
cursor?: AgentSessionCursor;
|
|
572
|
+
/** Number of Agent Sessions to return, from 1 through 250. */
|
|
573
|
+
pageSize: number;
|
|
574
|
+
}
|
|
575
|
+
export interface AgentSessionTaskSummary {
|
|
576
|
+
id: string;
|
|
577
|
+
title: string;
|
|
578
|
+
status: BoardStatus;
|
|
579
|
+
createdAt: number;
|
|
580
|
+
updatedAt: number;
|
|
581
|
+
}
|
|
582
|
+
export interface AgentSessionWorkspace {
|
|
583
|
+
rootPath: string;
|
|
584
|
+
kind: 'project' | 'worktree';
|
|
585
|
+
}
|
|
586
|
+
export interface AgentSessionSummary {
|
|
587
|
+
/** OpenForge Agent Session ID. */
|
|
588
|
+
id: string;
|
|
589
|
+
provider: string;
|
|
590
|
+
/** Provider-owned Agent Session ID, or null when the host has none. */
|
|
591
|
+
providerSessionId: string | null;
|
|
592
|
+
createdAt: number;
|
|
593
|
+
updatedAt: number;
|
|
594
|
+
task: AgentSessionTaskSummary;
|
|
595
|
+
workspace: AgentSessionWorkspace | null;
|
|
596
|
+
}
|
|
597
|
+
export interface AgentSessionSummaryPage {
|
|
598
|
+
items: AgentSessionSummary[];
|
|
599
|
+
nextCursor: AgentSessionCursor | null;
|
|
600
|
+
}
|
|
601
|
+
export interface AgentSessionsAPI {
|
|
602
|
+
list(request: ListAgentSessionsRequest): Promise<AgentSessionSummaryPage>;
|
|
603
|
+
}
|
|
545
604
|
export interface TasksAPI {
|
|
546
605
|
/**
|
|
547
606
|
* Lists tasks, optionally scoped to a project. By default done tasks are
|
|
@@ -570,6 +629,8 @@ export interface TasksAPI {
|
|
|
570
629
|
sendFollowUp(request: SendTaskFollowUpRequest): Promise<TaskFollowUpReceipt>;
|
|
571
630
|
getWorkspace(taskId: string): Promise<TaskWorkspaceInfo | null>;
|
|
572
631
|
getLatestSession(taskId: string): Promise<AgentSession | null>;
|
|
632
|
+
/** Returns matching Agent Sessions newest first. */
|
|
633
|
+
listSessions(request: ListTaskSessionsRequest): Promise<AgentSession[]>;
|
|
573
634
|
}
|
|
574
635
|
export interface ProjectsAPI {
|
|
575
636
|
list(): Promise<Project[]>;
|
|
@@ -601,6 +662,7 @@ export interface OpenForgeCommonAPI {
|
|
|
601
662
|
context: {
|
|
602
663
|
getSnapshot(): OpenForgeContextSnapshot;
|
|
603
664
|
};
|
|
665
|
+
agentSessions: AgentSessionsAPI;
|
|
604
666
|
tasks: TasksAPI;
|
|
605
667
|
projects: ProjectsAPI;
|
|
606
668
|
fs: FileSystemAPI;
|
|
@@ -613,7 +675,6 @@ export interface OpenForgeCommonAPI {
|
|
|
613
675
|
}
|
|
614
676
|
export interface FrontendOpenForgeAPI extends OpenForgeCommonAPI {
|
|
615
677
|
browserSurfaces: BrowserSurfacesAPI;
|
|
616
|
-
taskLinks: TaskLinksAPI;
|
|
617
678
|
navigation: NavigationAPI;
|
|
618
679
|
views: FrontendViewRegistry;
|
|
619
680
|
taskUI: FrontendTaskUIRegistry;
|