@vornrun/connector-sdk 0.7.0-beta.7 → 0.7.0
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/README.md +285 -2
- package/dist/check-62s2GvcO.d.ts +1137 -0
- package/dist/chunk-ZKHXHE3O.js +3511 -0
- package/dist/cli.d.ts +14 -1
- package/dist/cli.js +105 -12
- package/dist/index.d.ts +176 -412
- package/dist/index.js +98 -34
- package/package.json +4 -2
- package/dist/chunk-457KOZUU.js +0 -773
|
@@ -0,0 +1,1137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author-facing types for Vorn connectors.
|
|
3
|
+
*
|
|
4
|
+
* A connector written with this SDK runs as an ordinary MCP stdio server, so
|
|
5
|
+
* it is shared as a normal npm package and installed by pointing a Vorn
|
|
6
|
+
* connection at `npx -y <package>`. Nothing about the host app has to change
|
|
7
|
+
* to accept a new connector.
|
|
8
|
+
*/
|
|
9
|
+
/** A raw item as the author's code returns it. Only id and title are required. */
|
|
10
|
+
interface ConnectorItem {
|
|
11
|
+
/** Stable upstream identity. Vorn dedupes on this, so it must not change. */
|
|
12
|
+
externalId: string | number;
|
|
13
|
+
title: string;
|
|
14
|
+
url?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
/** Raw upstream status (`open`, `Active`, `In Progress`, …). */
|
|
17
|
+
status?: string;
|
|
18
|
+
labels?: string[];
|
|
19
|
+
assignee?: string;
|
|
20
|
+
/**
|
|
21
|
+
* When the item last changed. Vorn advances its poll cursor from this field,
|
|
22
|
+
* so it must be monotonic per item and comparable as an ISO 8601 string.
|
|
23
|
+
* Defaults to poll time when omitted.
|
|
24
|
+
*/
|
|
25
|
+
updatedAt?: string | Date;
|
|
26
|
+
/** Extra fields to expose to workflow templates as `{{trigger.item.<key>}}`. */
|
|
27
|
+
data?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
/** A connector item after normalization. This is the exact JSON Vorn sees. */
|
|
30
|
+
interface NormalizedItem extends Record<string, unknown> {
|
|
31
|
+
externalId: string;
|
|
32
|
+
title: string;
|
|
33
|
+
url: string;
|
|
34
|
+
description: string;
|
|
35
|
+
status: string;
|
|
36
|
+
labels: string[];
|
|
37
|
+
updatedAt: string;
|
|
38
|
+
assignee?: string;
|
|
39
|
+
}
|
|
40
|
+
/** Declares a value the connector needs at runtime, read from the environment. */
|
|
41
|
+
interface ConnectorConfigField {
|
|
42
|
+
key: string;
|
|
43
|
+
label: string;
|
|
44
|
+
/** Environment variable the value is read from. Defaults to CONSTANT_CASE(key). */
|
|
45
|
+
env?: string;
|
|
46
|
+
required?: boolean;
|
|
47
|
+
/** Secrets are stored encrypted by Vorn and never printed by the CLI. */
|
|
48
|
+
secret?: boolean;
|
|
49
|
+
description?: string;
|
|
50
|
+
default?: string;
|
|
51
|
+
/**
|
|
52
|
+
* A note for whoever is building this connector rather than using it: where
|
|
53
|
+
* the value is found, what a good one looks like. The factory's agent reads
|
|
54
|
+
* these, so a field that is easy to get wrong can say so once here instead of
|
|
55
|
+
* being got wrong in every connector that copies it.
|
|
56
|
+
*/
|
|
57
|
+
builderHint?: string;
|
|
58
|
+
}
|
|
59
|
+
type ConnectorConfig = Record<string, string | undefined>;
|
|
60
|
+
interface PollContext {
|
|
61
|
+
config: ConnectorConfig;
|
|
62
|
+
/**
|
|
63
|
+
* Lower bound the host asked for, when it was able to supply one. Treat it
|
|
64
|
+
* as a hint: returning older items is safe because Vorn dedupes, but
|
|
65
|
+
* returning fewer than everything after `since` loses events.
|
|
66
|
+
*/
|
|
67
|
+
since?: string;
|
|
68
|
+
/** Opaque cursor previously returned by this trigger, when supplied. */
|
|
69
|
+
cursor?: string;
|
|
70
|
+
/** Upper bound on items to return in one page. */
|
|
71
|
+
limit?: number;
|
|
72
|
+
/** Injectable clock so tests are deterministic. */
|
|
73
|
+
now(): string;
|
|
74
|
+
/** Fetch with the SDK's retry and backoff applied. A poll is always a read. */
|
|
75
|
+
fetch: typeof fetch;
|
|
76
|
+
/** Present only when the connector signs in through a Vorn window. */
|
|
77
|
+
session?: SessionContext;
|
|
78
|
+
}
|
|
79
|
+
interface PollOutcome {
|
|
80
|
+
items: ConnectorItem[];
|
|
81
|
+
nextCursor?: string;
|
|
82
|
+
hasMore?: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* How the SDK decides which fetched items are new.
|
|
86
|
+
*
|
|
87
|
+
* - `timestamp` — for sources that expose a reliable "last changed" field and
|
|
88
|
+
* can filter on it. Handles the boundary case where several items share the
|
|
89
|
+
* newest timestamp, which is the classic source of both duplicates and
|
|
90
|
+
* silently dropped items.
|
|
91
|
+
* - `lastItem` — for feeds that return newest-first with no dependable
|
|
92
|
+
* timestamp. The cursor is the newest id already delivered.
|
|
93
|
+
*/
|
|
94
|
+
type DedupeStrategy = 'timestamp' | 'lastItem';
|
|
95
|
+
/**
|
|
96
|
+
* What a declarative trigger's `fetch` receives. Deliberately smaller than
|
|
97
|
+
* {@link PollContext}: cursor encoding, ordering, windowing and de-duplication
|
|
98
|
+
* are the SDK's job, so the author only has to answer "what is there now?".
|
|
99
|
+
*/
|
|
100
|
+
interface FetchContext {
|
|
101
|
+
config: ConnectorConfig;
|
|
102
|
+
/**
|
|
103
|
+
* With `dedupe: 'timestamp'`, everything changed at or after this instant is
|
|
104
|
+
* worth returning. Absent on the very first poll. Returning a little too
|
|
105
|
+
* much is safe — the SDK drops what was already delivered.
|
|
106
|
+
*/
|
|
107
|
+
since?: string;
|
|
108
|
+
/**
|
|
109
|
+
* With `dedupe: 'lastItem'`, the newest id already delivered. Absent on the
|
|
110
|
+
* very first poll. Return the feed newest-first and the SDK will stop there.
|
|
111
|
+
*/
|
|
112
|
+
lastItemId?: string;
|
|
113
|
+
/** Upper bound on items worth returning in one call. */
|
|
114
|
+
limit?: number;
|
|
115
|
+
/** Injectable clock so tests are deterministic. */
|
|
116
|
+
now(): string;
|
|
117
|
+
/** Fetch with the SDK's retry and backoff applied. A fetch is always a read. */
|
|
118
|
+
fetch: typeof fetch;
|
|
119
|
+
/** Present only when the connector signs in through a Vorn window. */
|
|
120
|
+
session?: SessionContext;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* What an upstream state should become when an item is imported as a task.
|
|
124
|
+
*
|
|
125
|
+
* A suggestion, not a rule: it seeds the connection form, and the person
|
|
126
|
+
* setting it up can change it. Without any, everything a connector imports
|
|
127
|
+
* lands as `todo` regardless of whether it was closed a year ago.
|
|
128
|
+
*/
|
|
129
|
+
interface StatusSuggestion {
|
|
130
|
+
/** The value the connector reports in `ConnectorItem.status`. */
|
|
131
|
+
upstream: string;
|
|
132
|
+
suggestedLocal: 'todo' | 'in_progress' | 'in_review' | 'done' | 'cancelled';
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The workflow to create when a connection is made.
|
|
136
|
+
*
|
|
137
|
+
* A connector that fires on a schedule is useless until something polls it, and
|
|
138
|
+
* expecting every person to build that workflow by hand is how a connection
|
|
139
|
+
* ends up configured and silent. Seeded workflows are ordinary, visible and
|
|
140
|
+
* editable — the schedule is a starting point, not a fixed rule.
|
|
141
|
+
*/
|
|
142
|
+
interface DefaultWorkflow {
|
|
143
|
+
name: string;
|
|
144
|
+
defaultCronFromMinutes: number;
|
|
145
|
+
}
|
|
146
|
+
interface TriggerBase {
|
|
147
|
+
/** Event key, e.g. `workItemCreated`. Becomes the `poll_<type>` MCP tool. */
|
|
148
|
+
type: string;
|
|
149
|
+
label: string;
|
|
150
|
+
description?: string;
|
|
151
|
+
/** Seeds the connection's status mapping; the person setting it up owns it. */
|
|
152
|
+
statusMapping?: StatusSuggestion[];
|
|
153
|
+
/** Seeds a polling workflow when a connection is created. */
|
|
154
|
+
defaultWorkflow?: DefaultWorkflow;
|
|
155
|
+
/**
|
|
156
|
+
* Representative items. `vorn-connector check` replays these through the
|
|
157
|
+
* real dedupe pipeline, so a connector can be verified before anyone has
|
|
158
|
+
* credentials for it.
|
|
159
|
+
*/
|
|
160
|
+
sample?: ConnectorItem[];
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* A trigger is either declarative or hand-written, never both — expressed as a
|
|
164
|
+
* union so the invalid combinations are a type error at authoring time rather
|
|
165
|
+
* than a throw when the connector is first loaded.
|
|
166
|
+
*/
|
|
167
|
+
type TriggerDefinition = TriggerBase & ({
|
|
168
|
+
/**
|
|
169
|
+
* Declarative polling: return what the source has and let the SDK
|
|
170
|
+
* handle cursors and de-duplication.
|
|
171
|
+
*/
|
|
172
|
+
dedupe: DedupeStrategy;
|
|
173
|
+
fetch(context: FetchContext): Promise<ConnectorItem[]> | ConnectorItem[];
|
|
174
|
+
poll?: never;
|
|
175
|
+
} | {
|
|
176
|
+
/**
|
|
177
|
+
* Full control over cursors and paging. Use only when the source's
|
|
178
|
+
* paging cannot be expressed as "give me everything since X".
|
|
179
|
+
*/
|
|
180
|
+
poll(context: PollContext): Promise<PollOutcome> | PollOutcome;
|
|
181
|
+
dedupe?: never;
|
|
182
|
+
fetch?: never;
|
|
183
|
+
});
|
|
184
|
+
/**
|
|
185
|
+
* What kind of value an action argument takes.
|
|
186
|
+
*
|
|
187
|
+
* Every argument still arrives as a string — Vorn renders them from templates —
|
|
188
|
+
* so this says how to read one, and how to draw its field. `select` is a
|
|
189
|
+
* string with known choices; `json` is a string holding a structured value.
|
|
190
|
+
*/
|
|
191
|
+
type ActionInputType = 'string' | 'number' | 'boolean' | 'select' | 'json';
|
|
192
|
+
/** One choice a `select` argument offers. */
|
|
193
|
+
interface ActionInputOption {
|
|
194
|
+
value: string;
|
|
195
|
+
/** Shown instead of the value where the raw value would not read well. */
|
|
196
|
+
label?: string;
|
|
197
|
+
}
|
|
198
|
+
interface ActionInputField {
|
|
199
|
+
key: string;
|
|
200
|
+
label: string;
|
|
201
|
+
type?: ActionInputType;
|
|
202
|
+
required?: boolean;
|
|
203
|
+
description?: string;
|
|
204
|
+
/** Fixed choices, for a `select` whose options are known when it is written. */
|
|
205
|
+
options?: ActionInputOption[];
|
|
206
|
+
/**
|
|
207
|
+
* Names an options set the connector serves, for a `select` whose choices
|
|
208
|
+
* are only knowable against a live connection — the channels in a workspace,
|
|
209
|
+
* the projects in an account.
|
|
210
|
+
*/
|
|
211
|
+
loadOptions?: string;
|
|
212
|
+
/** A note for whoever is building the connector, not for whoever runs it. */
|
|
213
|
+
builderHint?: string;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* A field the action is known to return. Declaring these is optional — extra
|
|
217
|
+
* keys always pass through — but declared fields show up in Vorn's variable
|
|
218
|
+
* autocomplete as `{{steps.<action>.<key>}}`.
|
|
219
|
+
*/
|
|
220
|
+
interface ActionOutputField {
|
|
221
|
+
key: string;
|
|
222
|
+
type?: 'string' | 'number' | 'boolean';
|
|
223
|
+
description?: string;
|
|
224
|
+
}
|
|
225
|
+
interface ActionContext {
|
|
226
|
+
config: ConnectorConfig;
|
|
227
|
+
now(): string;
|
|
228
|
+
/**
|
|
229
|
+
* Fetch, with the SDK's retry, backoff and rate-limit handling already
|
|
230
|
+
* applied. Prefer it over the global one: a hand-written action gets the
|
|
231
|
+
* same resilience a declared request does, and tests can replace it.
|
|
232
|
+
*/
|
|
233
|
+
fetch: typeof fetch;
|
|
234
|
+
/** Present only when the connector signs in through a Vorn window. */
|
|
235
|
+
session?: SessionContext;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* One step of reshaping a response.
|
|
239
|
+
*
|
|
240
|
+
* Each op reads the whole value, or just what lives at its dotted `path`, and
|
|
241
|
+
* leaves the rest alone. They compose left to right, which is enough to turn
|
|
242
|
+
* most envelopes into the record a workflow step wants.
|
|
243
|
+
*/
|
|
244
|
+
type PostReceiveOp =
|
|
245
|
+
/** Keep only these keys, of the object or of every object in the list. */
|
|
246
|
+
{
|
|
247
|
+
op: 'pick';
|
|
248
|
+
keys: string[];
|
|
249
|
+
path?: string;
|
|
250
|
+
}
|
|
251
|
+
/** Give a key a better name, of the object or of every object in the list. */
|
|
252
|
+
| {
|
|
253
|
+
op: 'rename';
|
|
254
|
+
from: string;
|
|
255
|
+
to: string;
|
|
256
|
+
path?: string;
|
|
257
|
+
}
|
|
258
|
+
/** Replace the whole value with what is at this path — unwrap the envelope. */
|
|
259
|
+
| {
|
|
260
|
+
op: 'flatten';
|
|
261
|
+
path: string;
|
|
262
|
+
}
|
|
263
|
+
/** Keep the list entries whose `key` equals this value. */
|
|
264
|
+
| {
|
|
265
|
+
op: 'filter';
|
|
266
|
+
key: string;
|
|
267
|
+
equals: unknown;
|
|
268
|
+
path?: string;
|
|
269
|
+
}
|
|
270
|
+
/** Run these ops against every entry of the list. */
|
|
271
|
+
| {
|
|
272
|
+
op: 'map';
|
|
273
|
+
ops: PostReceiveOp[];
|
|
274
|
+
path?: string;
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* How to ask for the page after this one.
|
|
278
|
+
*
|
|
279
|
+
* Declared rather than written because every source does the same three things
|
|
280
|
+
* — hand back a cursor, count pages, or put a link in a header — and following
|
|
281
|
+
* them by hand is where "only the first 100 items ever arrive" comes from.
|
|
282
|
+
*/
|
|
283
|
+
type PaginationStrategy =
|
|
284
|
+
/** The response carries a cursor at `cursorPath`; send it back as `param`. */
|
|
285
|
+
{
|
|
286
|
+
kind: 'cursor';
|
|
287
|
+
cursorPath: string;
|
|
288
|
+
param: string;
|
|
289
|
+
itemsPath?: string;
|
|
290
|
+
}
|
|
291
|
+
/** Ask for page 1, 2, 3 … under `param`, until a page comes back short. */
|
|
292
|
+
| {
|
|
293
|
+
kind: 'page';
|
|
294
|
+
param: string;
|
|
295
|
+
startPage?: number;
|
|
296
|
+
itemsPath?: string;
|
|
297
|
+
}
|
|
298
|
+
/** Follow the `Link` header's `rel="next"`, as paged HTTP APIs do. */
|
|
299
|
+
| {
|
|
300
|
+
kind: 'link';
|
|
301
|
+
itemsPath?: string;
|
|
302
|
+
};
|
|
303
|
+
/** An HTTP call an action makes, with `{{args.x}}` and `{{config.y}}` filled in. */
|
|
304
|
+
interface ActionRequest {
|
|
305
|
+
/** Defaults to GET. */
|
|
306
|
+
method?: 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
307
|
+
url: string;
|
|
308
|
+
headers?: Record<string, string>;
|
|
309
|
+
/** Query parameters. An argument that resolves to nothing is left out. */
|
|
310
|
+
query?: Record<string, string>;
|
|
311
|
+
/** Sent as JSON unless it is already a string, or a content type says otherwise. */
|
|
312
|
+
body?: unknown;
|
|
313
|
+
/** Follow every page rather than returning only the first. */
|
|
314
|
+
paginate?: PaginationStrategy;
|
|
315
|
+
}
|
|
316
|
+
interface ActionBase {
|
|
317
|
+
/** Action key, e.g. `closeWorkItem`. Becomes an MCP tool of the same name. */
|
|
318
|
+
type: string;
|
|
319
|
+
label: string;
|
|
320
|
+
description?: string;
|
|
321
|
+
/**
|
|
322
|
+
* Whether repeating the call with the same arguments is safe. Surfaced in
|
|
323
|
+
* the MCP tool description, because an agent retrying a failed step has no
|
|
324
|
+
* other way to know whether it is about to create a second issue — and it is
|
|
325
|
+
* what decides whether the SDK may retry the call itself.
|
|
326
|
+
*/
|
|
327
|
+
idempotent?: boolean;
|
|
328
|
+
inputs?: ActionInputField[];
|
|
329
|
+
outputs?: ActionOutputField[];
|
|
330
|
+
sample?: Record<string, string>;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* An action is either declared or hand-written, never both — the same union
|
|
334
|
+
* shape triggers use, so the invalid combination is a type error while the
|
|
335
|
+
* connector is being written rather than a throw once it is installed.
|
|
336
|
+
*/
|
|
337
|
+
type ActionDefinition = ActionBase & ({
|
|
338
|
+
run(args: Record<string, unknown>, context: ActionContext): Promise<Record<string, unknown> | void> | Record<string, unknown> | void;
|
|
339
|
+
request?: never;
|
|
340
|
+
postReceive?: never;
|
|
341
|
+
} | {
|
|
342
|
+
/** The call to make. The SDK sends it and keeps the response. */
|
|
343
|
+
request: ActionRequest;
|
|
344
|
+
/** How to reshape what came back, before the step sees it. */
|
|
345
|
+
postReceive?: PostReceiveOp[];
|
|
346
|
+
run?: never;
|
|
347
|
+
});
|
|
348
|
+
/**
|
|
349
|
+
* A connector's own glyph, so an installed connector is recognizable in a list
|
|
350
|
+
* rather than sharing one generic icon with every other one.
|
|
351
|
+
*
|
|
352
|
+
* Path data only — deliberately not markup. Vorn draws these itself as
|
|
353
|
+
* `<path d="...">` inside an `<svg>` it owns, so a connector cannot inject
|
|
354
|
+
* elements, scripts or external references into the app rendering it.
|
|
355
|
+
*/
|
|
356
|
+
interface ConnectorIcon {
|
|
357
|
+
/** Defaults to `0 0 24 24`. */
|
|
358
|
+
viewBox?: string;
|
|
359
|
+
/** SVG path `d` data, drawn with `fill="currentColor"` so it inherits color. */
|
|
360
|
+
paths: string[];
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* What a connector reports about its own readiness.
|
|
364
|
+
*
|
|
365
|
+
* `message` is shown to the user verbatim, so it should say what to do rather
|
|
366
|
+
* than what went wrong — "run `gh auth login`" beats "not authenticated".
|
|
367
|
+
*/
|
|
368
|
+
interface PreflightResult {
|
|
369
|
+
ok: boolean;
|
|
370
|
+
message?: string;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* How a connector signs in, lowest rung first.
|
|
374
|
+
*
|
|
375
|
+
* `none` needs nothing — installing it is the whole setup. `cli` borrows a
|
|
376
|
+
* login that already works on the machine, which is the rung to prefer
|
|
377
|
+
* whenever a mature tool is signed in anyway. `key` asks for a credential.
|
|
378
|
+
* `browser` signs in through a Vorn window for a service with no API to key.
|
|
379
|
+
* `oauth` is declared but not yet carried by the host.
|
|
380
|
+
*/
|
|
381
|
+
type AuthRung = 'none' | 'cli' | 'key' | 'browser' | 'oauth';
|
|
382
|
+
/** How a `browser` connector signs in, and the only origins its calls may reach. */
|
|
383
|
+
interface BrowserSignIn {
|
|
384
|
+
/** The page the Vorn window opens for signing in. */
|
|
385
|
+
signInUrl: string;
|
|
386
|
+
/** `https://host` or `https://*.host`; the sign-in page and the check must sit inside them. */
|
|
387
|
+
origins: string[];
|
|
388
|
+
/** Answers 2xx only when signed in; `identity` names the fields of its JSON that say who. */
|
|
389
|
+
check: {
|
|
390
|
+
url: string;
|
|
391
|
+
identity: string[];
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/** The signed-in window, offered to a `browser` connector's code. */
|
|
395
|
+
interface SessionContext {
|
|
396
|
+
/** Runs the request inside the connection's signed-in window; cookies never reach the connector. */
|
|
397
|
+
fetch: typeof fetch;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* What a connector needs before it can talk to anything.
|
|
401
|
+
*
|
|
402
|
+
* Declaring this is what lets the app say how a connector signs in *before*
|
|
403
|
+
* anyone installs it, and lets a `cli` connector show who you already are
|
|
404
|
+
* instead of a token field. The credential itself is never described here —
|
|
405
|
+
* only where it comes from.
|
|
406
|
+
*/
|
|
407
|
+
interface ConnectorAuth {
|
|
408
|
+
rung: AuthRung;
|
|
409
|
+
/**
|
|
410
|
+
* Asks the borrowed tool whether it is signed in, e.g. `glab auth status`.
|
|
411
|
+
* Required for `cli`: without it the app has nothing to ask.
|
|
412
|
+
*/
|
|
413
|
+
probe?: {
|
|
414
|
+
command: string;
|
|
415
|
+
args?: string[];
|
|
416
|
+
};
|
|
417
|
+
/**
|
|
418
|
+
* What to take from the signed-in tool.
|
|
419
|
+
*
|
|
420
|
+
* `env` names variables to pass through, and every one of them must also
|
|
421
|
+
* appear in this connector's own `config` — the host refuses to borrow a
|
|
422
|
+
* name the connector does not openly read. `tokenArgs` is a command that
|
|
423
|
+
* prints a token, run fresh at spawn so nothing is ever stored, and
|
|
424
|
+
* `tokenEnv` names the one variable that receives it (the first of `env` by
|
|
425
|
+
* default). The rest are pass-throughs: a host or an account id filled with
|
|
426
|
+
* a token would authenticate against nothing.
|
|
427
|
+
*/
|
|
428
|
+
borrow?: {
|
|
429
|
+
env?: string[];
|
|
430
|
+
tokenArgs?: string[];
|
|
431
|
+
tokenEnv?: string;
|
|
432
|
+
};
|
|
433
|
+
/** Config field keys holding the credential. Required for `key`. */
|
|
434
|
+
keys?: string[];
|
|
435
|
+
/** Required for `browser`. */
|
|
436
|
+
browser?: BrowserSignIn;
|
|
437
|
+
}
|
|
438
|
+
/** What an options set is given to work out its choices. */
|
|
439
|
+
interface OptionsContext {
|
|
440
|
+
config: ConnectorConfig;
|
|
441
|
+
now(): string;
|
|
442
|
+
/** Fetch with the SDK's retry and backoff applied. Listing choices is a read. */
|
|
443
|
+
fetch: typeof fetch;
|
|
444
|
+
/** Present only when the connector signs in through a Vorn window. */
|
|
445
|
+
session?: SessionContext;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Answers the question "what can this field be?" against a live connection.
|
|
449
|
+
*
|
|
450
|
+
* A bare string is taken as a choice that shows itself; return the object form
|
|
451
|
+
* when the value a step should send and the words a person should read differ.
|
|
452
|
+
*/
|
|
453
|
+
type OptionsLoader = (context: OptionsContext) => Promise<Array<ActionInputOption | string>> | Array<ActionInputOption | string>;
|
|
454
|
+
interface ConnectorDefinition {
|
|
455
|
+
/** Stable connector id, e.g. `azure-devops`. */
|
|
456
|
+
id: string;
|
|
457
|
+
name: string;
|
|
458
|
+
version?: string;
|
|
459
|
+
description?: string;
|
|
460
|
+
icon?: ConnectorIcon;
|
|
461
|
+
config?: ConnectorConfigField[];
|
|
462
|
+
/**
|
|
463
|
+
* Named sets of choices an input can point at with `loadOptions`, for fields
|
|
464
|
+
* whose values only exist against a live connection — the channels in a
|
|
465
|
+
* workspace, the projects in an account.
|
|
466
|
+
*/
|
|
467
|
+
options?: Record<string, OptionsLoader>;
|
|
468
|
+
/**
|
|
469
|
+
* How this connector signs in. Absent means the app cannot say, which reads
|
|
470
|
+
* as "a key, probably" — declare it rather than leave that to be guessed.
|
|
471
|
+
*/
|
|
472
|
+
auth?: ConnectorAuth;
|
|
473
|
+
triggers?: TriggerDefinition[];
|
|
474
|
+
actions?: ActionDefinition[];
|
|
475
|
+
/**
|
|
476
|
+
* Whether this connector could work right now, asked before anyone waits on
|
|
477
|
+
* a poll.
|
|
478
|
+
*
|
|
479
|
+
* A connector whose credentials come from config fields does not need this:
|
|
480
|
+
* a missing field is already a visible, nameable error. One that borrows an
|
|
481
|
+
* external tool's login — `gh auth login`, `az login` — has no field to be
|
|
482
|
+
* missing, so without this the first sign that the tool is absent or signed
|
|
483
|
+
* out is a poll failing some minutes after the connection was saved.
|
|
484
|
+
*
|
|
485
|
+
* Answer `ok: false` with a message saying what to do about it. Throwing is
|
|
486
|
+
* equivalent — the server catches it and reports the same shape with the
|
|
487
|
+
* error's message — so there is one result for a caller to read and no
|
|
488
|
+
* behaviour riding on which you choose. Prefer returning when the state is
|
|
489
|
+
* one you recognise, because then you get to write the sentence.
|
|
490
|
+
*
|
|
491
|
+
* Absent means there is nothing to check, which is not the same answer as a
|
|
492
|
+
* check that passed.
|
|
493
|
+
*/
|
|
494
|
+
preflight?(): Promise<PreflightResult> | PreflightResult;
|
|
495
|
+
}
|
|
496
|
+
/** What a pack is: a connector polls a service, an extension contributes to a session card. */
|
|
497
|
+
type ConnectorKind = 'connector' | 'extension';
|
|
498
|
+
/** A validated definition. Every accessor below is guaranteed non-null. */
|
|
499
|
+
interface Connector extends ConnectorDefinition {
|
|
500
|
+
readonly version: string;
|
|
501
|
+
readonly config: ConnectorConfigField[];
|
|
502
|
+
readonly triggers: TriggerDefinition[];
|
|
503
|
+
readonly actions: ActionDefinition[];
|
|
504
|
+
readonly kind: ConnectorKind;
|
|
505
|
+
/** What an extension adds to a card. Absent on a connector. */
|
|
506
|
+
readonly contributes?: ExtensionContributions;
|
|
507
|
+
/** What an extension may ask the host for. Absent on a connector. */
|
|
508
|
+
readonly permissions?: ExtensionPermission[];
|
|
509
|
+
/** Where an extension shows at all. Absent on a connector. */
|
|
510
|
+
readonly activates?: ActivationPredicate;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* What an extension may ask the host for, named by what it grants rather than
|
|
514
|
+
* by the method that spends it.
|
|
515
|
+
*
|
|
516
|
+
* A closed set on purpose: a permission is shown to a person before they
|
|
517
|
+
* install, so every one of them has to be a sentence someone can weigh.
|
|
518
|
+
*/
|
|
519
|
+
type ExtensionPermission = 'git.read' | 'terminal.read' | 'terminal.selection' | 'terminal.send' | 'card.rename' | 'agent.usage';
|
|
520
|
+
/** Session types an extension can name; `shell` is a plain terminal. */
|
|
521
|
+
type ExtensionAgent = 'claude' | 'copilot' | 'codex' | 'opencode' | 'gemini' | 'shell';
|
|
522
|
+
type ExtensionPlatform = 'darwin' | 'linux' | 'win32';
|
|
523
|
+
/**
|
|
524
|
+
* Where a contribution shows.
|
|
525
|
+
*
|
|
526
|
+
* Every declared field must hold for it to show, and each is satisfied by any
|
|
527
|
+
* one of its values: a Rust footer says `workspaceContains: ['Cargo.toml']`
|
|
528
|
+
* and is simply absent everywhere else, rather than reporting nothing.
|
|
529
|
+
*/
|
|
530
|
+
interface ActivationPredicate {
|
|
531
|
+
/** Paths relative to the session's worktree; any one of them existing is enough. */
|
|
532
|
+
workspaceContains?: string[];
|
|
533
|
+
/** Host of the worktree's git remote, e.g. `github.com`. */
|
|
534
|
+
remoteHost?: string[];
|
|
535
|
+
agent?: ExtensionAgent[];
|
|
536
|
+
platform?: ExtensionPlatform[];
|
|
537
|
+
}
|
|
538
|
+
interface ContributionBase {
|
|
539
|
+
/** Stable within the extension; the host addresses the contribution by it. */
|
|
540
|
+
id: string;
|
|
541
|
+
title: string;
|
|
542
|
+
description?: string;
|
|
543
|
+
/** Narrows where this one shows, inside where the extension is active at all. */
|
|
544
|
+
when?: ActivationPredicate;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* A pane the extension adds beside the terminal: either a page it ships or a
|
|
548
|
+
* program it runs, never both — a union, so the invalid pair is a type error
|
|
549
|
+
* while the extension is being written.
|
|
550
|
+
*/
|
|
551
|
+
type PaneContribution = ContributionBase & {
|
|
552
|
+
/** Glyph for the menu row and the pane's own bar; the extension's is used when absent. */
|
|
553
|
+
icon?: ConnectorIcon;
|
|
554
|
+
} & ({
|
|
555
|
+
/** Page inside the pack, under `web/`, rendered in a pane. */
|
|
556
|
+
web: string;
|
|
557
|
+
command?: never;
|
|
558
|
+
} | {
|
|
559
|
+
/** Argv run in the session's worktree, drawn as a terminal. */
|
|
560
|
+
command: string[];
|
|
561
|
+
web?: never;
|
|
562
|
+
});
|
|
563
|
+
/** One reading in a footer band: a label, its value, and how the value reads. */
|
|
564
|
+
interface FooterItem {
|
|
565
|
+
label: string;
|
|
566
|
+
value: string;
|
|
567
|
+
/** `ok` and `danger` colour the value; anything else is ordinary text. */
|
|
568
|
+
tone?: 'default' | 'ok' | 'danger';
|
|
569
|
+
/** Opened when the item is clicked, for a reading that points somewhere. */
|
|
570
|
+
href?: string;
|
|
571
|
+
}
|
|
572
|
+
/** What the agent's provider says is left, for the windows it publishes. */
|
|
573
|
+
interface ExtensionUsageWindow {
|
|
574
|
+
/** The window's own name, e.g. `5h`. */
|
|
575
|
+
window: string;
|
|
576
|
+
/** How much of the allowance is left, 0 to 1. */
|
|
577
|
+
remaining: number;
|
|
578
|
+
resetsAt?: string;
|
|
579
|
+
}
|
|
580
|
+
interface ExtensionUsage {
|
|
581
|
+
contextTokens?: number;
|
|
582
|
+
contextWindow?: number;
|
|
583
|
+
/** Session-cumulative prompt-cache hit rate, 0 to 1. */
|
|
584
|
+
cacheHitRate?: number;
|
|
585
|
+
limits?: ExtensionUsageWindow[];
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* The host, as an extension sees it.
|
|
589
|
+
*
|
|
590
|
+
* Every method costs exactly one permission — `HOST_PERMISSIONS` says which —
|
|
591
|
+
* and calling one the manifest did not declare is refused rather than ignored,
|
|
592
|
+
* so an extension cannot quietly reach past what a person agreed to.
|
|
593
|
+
*/
|
|
594
|
+
interface ExtensionHost {
|
|
595
|
+
/** The worktree's diff against its base. */
|
|
596
|
+
diff(): Promise<string>;
|
|
597
|
+
/** Porcelain status of the worktree. */
|
|
598
|
+
status(): Promise<string>;
|
|
599
|
+
/** The session's recent terminal output, newest last. */
|
|
600
|
+
output(options?: {
|
|
601
|
+
lines?: number;
|
|
602
|
+
}): Promise<string>;
|
|
603
|
+
/** The text selected in the terminal, empty when nothing is selected. */
|
|
604
|
+
selection(): Promise<string>;
|
|
605
|
+
/** Type text into the session's terminal, as a person would. */
|
|
606
|
+
send(text: string): Promise<void>;
|
|
607
|
+
/** Name the session card, until a person names it themselves. */
|
|
608
|
+
rename(name: string): Promise<void>;
|
|
609
|
+
/** Context and provider allowance for the session's agent. */
|
|
610
|
+
usage(): Promise<ExtensionUsage>;
|
|
611
|
+
}
|
|
612
|
+
/** Every method of the host, so the table naming what each one costs stays complete. */
|
|
613
|
+
type ExtensionHostMethod = keyof ExtensionHost;
|
|
614
|
+
/** What a contribution is told about the session it is running for. */
|
|
615
|
+
interface ExtensionContext {
|
|
616
|
+
sessionId: string;
|
|
617
|
+
/** Where the session's work is, so a contribution reads the tree it is about. */
|
|
618
|
+
worktreePath: string;
|
|
619
|
+
agent: ExtensionAgent;
|
|
620
|
+
host: ExtensionHost;
|
|
621
|
+
/** Injectable clock so tests are deterministic. */
|
|
622
|
+
now(): string;
|
|
623
|
+
}
|
|
624
|
+
/** What a link handler is told, on top of the session it was clicked in. */
|
|
625
|
+
interface LinkContext extends ExtensionContext {
|
|
626
|
+
/** The clicked text, which matched this handler's pattern. */
|
|
627
|
+
url: string;
|
|
628
|
+
}
|
|
629
|
+
/** What a link handler asks the app to do once it has run. */
|
|
630
|
+
interface LinkHandled {
|
|
631
|
+
/** Id of one of this extension's panes, opened for the session. */
|
|
632
|
+
openPane?: string;
|
|
633
|
+
}
|
|
634
|
+
/** A band under the card's status bar, recomputed on its own interval. */
|
|
635
|
+
interface FooterContribution extends ContributionBase {
|
|
636
|
+
/** Seconds between calls; the host polls no faster than this. */
|
|
637
|
+
every: number;
|
|
638
|
+
run(context: ExtensionContext): Promise<FooterItem[]> | FooterItem[];
|
|
639
|
+
}
|
|
640
|
+
/** Offers this extension when the clicked text in a terminal matches. */
|
|
641
|
+
interface LinkHandlerContribution extends ContributionBase {
|
|
642
|
+
/** Matched as a regular expression against clicked text, under a bound the app sets. */
|
|
643
|
+
pattern: string;
|
|
644
|
+
/** A link this handler is for, which its pattern must match; `check` runs the handler on it. */
|
|
645
|
+
example: string;
|
|
646
|
+
run(context: LinkContext): Promise<LinkHandled | void> | LinkHandled | void;
|
|
647
|
+
}
|
|
648
|
+
interface ExtensionContributions {
|
|
649
|
+
panes?: PaneContribution[];
|
|
650
|
+
footers?: FooterContribution[];
|
|
651
|
+
linkHandlers?: LinkHandlerContribution[];
|
|
652
|
+
}
|
|
653
|
+
interface ExtensionDefinition {
|
|
654
|
+
/** Stable extension id, e.g. `review`. */
|
|
655
|
+
id: string;
|
|
656
|
+
name: string;
|
|
657
|
+
version?: string;
|
|
658
|
+
description?: string;
|
|
659
|
+
icon?: ConnectorIcon;
|
|
660
|
+
/** Everything this extension may ask the host for, declared rather than inferred. */
|
|
661
|
+
permissions: ExtensionPermission[];
|
|
662
|
+
/** Where the extension shows at all; absent means every session. */
|
|
663
|
+
activates?: ActivationPredicate;
|
|
664
|
+
panes?: PaneContribution[];
|
|
665
|
+
footers?: FooterContribution[];
|
|
666
|
+
linkHandlers?: LinkHandlerContribution[];
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* What a connector must be true of as a *package*, rather than as a definition.
|
|
671
|
+
*
|
|
672
|
+
* Both `check` and `pack` ask these questions — check to fail a pull request
|
|
673
|
+
* early, pack to refuse an artifact — so they live here rather than in either,
|
|
674
|
+
* and neither imports the other.
|
|
675
|
+
*/
|
|
676
|
+
/** Largest pack Vorn will install, matched by the server's own verification. */
|
|
677
|
+
declare const MAX_PACK_BYTES: number;
|
|
678
|
+
interface BundleRequest {
|
|
679
|
+
contents: string;
|
|
680
|
+
resolveDir: string;
|
|
681
|
+
}
|
|
682
|
+
interface BundleOutput {
|
|
683
|
+
code: string;
|
|
684
|
+
/** Specifiers the bundler left for the runtime to resolve. */
|
|
685
|
+
external: string[];
|
|
686
|
+
}
|
|
687
|
+
/** Reject a source package whose install would run code on the user's machine. */
|
|
688
|
+
declare function lifecycleScriptFindings(pkg: unknown): CheckFinding[];
|
|
689
|
+
/** Specifiers left outside a bundle, which would need a registry at launch. */
|
|
690
|
+
declare function bundleDependencyFindings(external: string[]): CheckFinding[];
|
|
691
|
+
declare function bundledRequireFindings(code: string): CheckFinding[];
|
|
692
|
+
/** Nearest package.json at or above a directory, or undefined when there is none. */
|
|
693
|
+
declare function readNearestPackageJson(fromDir: string): Record<string, unknown> | undefined;
|
|
694
|
+
/** The bundler `pack` uses, shared so `check` gates on the same answer. */
|
|
695
|
+
declare function esbuildBundle(request: BundleRequest): Promise<BundleOutput>;
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Surviving an upstream's bad minute.
|
|
699
|
+
*
|
|
700
|
+
* Every connector eventually meets the same three answers — a rate limit, a
|
|
701
|
+
* gateway that briefly forgot how to work, a socket that died mid-call — and
|
|
702
|
+
* every author writes the same retry loop for them, usually without the one
|
|
703
|
+
* part that matters: only repeating calls that are safe to repeat. Doing it
|
|
704
|
+
* here means a connector gets it by saying nothing at all.
|
|
705
|
+
*/
|
|
706
|
+
interface RetryPolicy {
|
|
707
|
+
/** Total tries, including the first. Defaults to 3. */
|
|
708
|
+
attempts?: number;
|
|
709
|
+
/** First backoff step; each retry doubles it. Defaults to 250ms. */
|
|
710
|
+
baseDelayMs?: number;
|
|
711
|
+
/** Ceiling for any single wait, including one the server asked for. */
|
|
712
|
+
maxDelayMs?: number;
|
|
713
|
+
}
|
|
714
|
+
interface ResilientFetchOptions {
|
|
715
|
+
fetchImpl: typeof fetch;
|
|
716
|
+
/**
|
|
717
|
+
* Whether repeating the call is safe. A read always is; a write is only when
|
|
718
|
+
* the action said so, because retrying a `create` invents a second one.
|
|
719
|
+
*/
|
|
720
|
+
retryable: boolean;
|
|
721
|
+
retry?: RetryPolicy;
|
|
722
|
+
/** Replaced in tests so backoff costs no real time. */
|
|
723
|
+
sleep?: (ms: number) => Promise<void>;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* How long the server asked us to wait, in milliseconds.
|
|
727
|
+
*
|
|
728
|
+
* `Retry-After` is either a count of seconds or an HTTP date; both are common
|
|
729
|
+
* enough that reading only one of them is how a connector ends up hammering a
|
|
730
|
+
* rate limiter it was politely asked to back off from.
|
|
731
|
+
*/
|
|
732
|
+
declare function retryAfterMs(header: string | null, now: number): number | undefined;
|
|
733
|
+
/** The wait before try number `attempt`, counting the first try as zero. */
|
|
734
|
+
declare function backoffMs(attempt: number, policy?: RetryPolicy): number;
|
|
735
|
+
/**
|
|
736
|
+
* Wrap a fetch so it retries what is worth retrying.
|
|
737
|
+
*
|
|
738
|
+
* The wrapper is the value handed to actions as `context.fetch`, so a
|
|
739
|
+
* hand-written action and a declared request are equally protected.
|
|
740
|
+
*/
|
|
741
|
+
declare function resilientFetch(options: ResilientFetchOptions): typeof fetch;
|
|
742
|
+
|
|
743
|
+
interface PollPage {
|
|
744
|
+
items: NormalizedItem[];
|
|
745
|
+
nextCursor?: string;
|
|
746
|
+
hasMore: boolean;
|
|
747
|
+
}
|
|
748
|
+
interface RunPollOptions {
|
|
749
|
+
config?: ConnectorConfig;
|
|
750
|
+
since?: string;
|
|
751
|
+
cursor?: string;
|
|
752
|
+
limit?: number;
|
|
753
|
+
now?: () => string;
|
|
754
|
+
/** Replaced by the harness and by tests; defaults to the global fetch. */
|
|
755
|
+
fetchImpl?: typeof fetch;
|
|
756
|
+
/** Replaced by the harness and by tests; defaults to the signed-in window Vorn serves. */
|
|
757
|
+
sessionFetchImpl?: typeof fetch;
|
|
758
|
+
/** The key Vorn gave this tool call, carried on each request through the window. */
|
|
759
|
+
sessionCall?: string;
|
|
760
|
+
retry?: RetryPolicy;
|
|
761
|
+
/** Replaced in tests so backoff costs no real time. */
|
|
762
|
+
sleep?: (ms: number) => Promise<void>;
|
|
763
|
+
}
|
|
764
|
+
/** Longest chain of pages `drainPoll` will follow before calling it a bug. */
|
|
765
|
+
declare const MAX_POLL_PAGES = 1000;
|
|
766
|
+
/**
|
|
767
|
+
* Run one poll page and normalize it. Shared by the MCP server, the CLI and
|
|
768
|
+
* the test harness so all three observe exactly what Vorn will observe.
|
|
769
|
+
*/
|
|
770
|
+
declare function runPoll(connector: Connector, triggerType: string, options?: RunPollOptions): Promise<PollPage>;
|
|
771
|
+
/**
|
|
772
|
+
* Follow `hasMore` to the end of a trigger's backlog. Mirrors how Vorn drains
|
|
773
|
+
* a connector, including its refusal to follow a cursor that does not move —
|
|
774
|
+
* so an author sees the infinite loop in a unit test instead of in the app.
|
|
775
|
+
*/
|
|
776
|
+
declare function drainPoll(connector: Connector, triggerType: string, options?: RunPollOptions): Promise<NormalizedItem[]>;
|
|
777
|
+
interface RunActionOptions {
|
|
778
|
+
config?: ConnectorConfig;
|
|
779
|
+
now?: () => string;
|
|
780
|
+
/** Replaced by the harness and by tests; defaults to the global fetch. */
|
|
781
|
+
fetchImpl?: typeof fetch;
|
|
782
|
+
/** Replaced by the harness and by tests; defaults to the signed-in window Vorn serves. */
|
|
783
|
+
sessionFetchImpl?: typeof fetch;
|
|
784
|
+
/** The key Vorn gave this tool call, carried on each request through the window. */
|
|
785
|
+
sessionCall?: string;
|
|
786
|
+
retry?: RetryPolicy;
|
|
787
|
+
/** Replaced in tests so backoff costs no real time. */
|
|
788
|
+
sleep?: (ms: number) => Promise<void>;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Ask a connector what one of its dynamic fields can be.
|
|
792
|
+
*
|
|
793
|
+
* Listing choices only reads, so it retries like a poll does. A bare string is
|
|
794
|
+
* taken as a choice that shows itself, which is the common case.
|
|
795
|
+
*/
|
|
796
|
+
declare function runOptions(connector: Connector, name: string, options?: RunActionOptions): Promise<ActionInputOption[]>;
|
|
797
|
+
/**
|
|
798
|
+
* Run an action with its declared inputs validated and coerced. Vorn renders
|
|
799
|
+
* every action argument as a template string, so numbers and booleans arrive
|
|
800
|
+
* as text and have to be converted back here.
|
|
801
|
+
*/
|
|
802
|
+
declare function runAction(connector: Connector, actionType: string, args: Record<string, unknown>, options?: RunActionOptions): Promise<Record<string, unknown>>;
|
|
803
|
+
|
|
804
|
+
/** MCP tool name a trigger is served under. */
|
|
805
|
+
declare function pollToolName(triggerType: string): string;
|
|
806
|
+
/** MCP tool name a footer is recomputed under. */
|
|
807
|
+
declare function footerToolName(footerId: string): string;
|
|
808
|
+
/** MCP tool name a link handler is run under. */
|
|
809
|
+
declare function handlerToolName(handlerId: string): string;
|
|
810
|
+
/** Tool that reports the connector's manifest and setup hints. */
|
|
811
|
+
declare const MANIFEST_TOOL = "vorn_connector_manifest";
|
|
812
|
+
/**
|
|
813
|
+
* Tool that reports whether the connector can run right now. Present only when
|
|
814
|
+
* the connector declares a `preflight`, so its absence means "nothing to
|
|
815
|
+
* check" rather than "check passed".
|
|
816
|
+
*/
|
|
817
|
+
declare const PREFLIGHT_TOOL = "vorn_connector_preflight";
|
|
818
|
+
/**
|
|
819
|
+
* Tool that lists the choices for one dynamic field. Present only when the
|
|
820
|
+
* connector serves an options set, for the same reason preflight is.
|
|
821
|
+
*/
|
|
822
|
+
declare const OPTIONS_TOOL = "vorn_connector_options";
|
|
823
|
+
interface ConnectionSetup {
|
|
824
|
+
connectorId: string;
|
|
825
|
+
triggerType: string;
|
|
826
|
+
/** Values to paste into Vorn's MCP connection form. */
|
|
827
|
+
filters: {
|
|
828
|
+
pollTool: string;
|
|
829
|
+
itemsPath: 'items';
|
|
830
|
+
idField: 'externalId';
|
|
831
|
+
timestampField: 'updatedAt';
|
|
832
|
+
titleField: 'title';
|
|
833
|
+
urlField: 'url';
|
|
834
|
+
cursorArg: 'cursor';
|
|
835
|
+
cursorPath: 'nextCursor';
|
|
836
|
+
};
|
|
837
|
+
/** Environment variable names the connector reads. */
|
|
838
|
+
env: Array<{
|
|
839
|
+
name: string;
|
|
840
|
+
required: boolean;
|
|
841
|
+
secret: boolean;
|
|
842
|
+
description?: string;
|
|
843
|
+
/** For whoever is building a connector like this one, not for whoever runs it. */
|
|
844
|
+
builderHint?: string;
|
|
845
|
+
}>;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Describe how to wire one trigger into a Vorn MCP connection.
|
|
849
|
+
*
|
|
850
|
+
* Every SDK connector normalizes to the same field names, so this mapping is
|
|
851
|
+
* fixed; it is generated rather than documented so a rename in the SDK cannot
|
|
852
|
+
* drift away from the setup instructions users copy. `cursorArg` hands the
|
|
853
|
+
* connector back its own cursor each poll, which is what lets its dedupe
|
|
854
|
+
* strategy — rather than Vorn's timestamp comparison — decide what is new.
|
|
855
|
+
*/
|
|
856
|
+
declare function connectionSetup(connector: Connector, triggerType: string): ConnectionSetup;
|
|
857
|
+
/** A contribution as the manifest carries it: everything but the code that runs it. */
|
|
858
|
+
interface ManifestContribution {
|
|
859
|
+
id: string;
|
|
860
|
+
title: string;
|
|
861
|
+
description?: string;
|
|
862
|
+
when?: ActivationPredicate;
|
|
863
|
+
}
|
|
864
|
+
interface ManifestContributions {
|
|
865
|
+
panes?: Array<ManifestContribution & {
|
|
866
|
+
icon?: ConnectorIcon;
|
|
867
|
+
web?: string;
|
|
868
|
+
command?: string[];
|
|
869
|
+
}>;
|
|
870
|
+
footers?: Array<ManifestContribution & {
|
|
871
|
+
every: number;
|
|
872
|
+
}>;
|
|
873
|
+
linkHandlers?: Array<ManifestContribution & {
|
|
874
|
+
pattern: string;
|
|
875
|
+
example: string;
|
|
876
|
+
}>;
|
|
877
|
+
}
|
|
878
|
+
interface ConnectorManifest {
|
|
879
|
+
id: string;
|
|
880
|
+
name: string;
|
|
881
|
+
version: string;
|
|
882
|
+
/** Absent on a manifest written before extensions, which reads as a connector. */
|
|
883
|
+
kind?: ConnectorKind;
|
|
884
|
+
description?: string;
|
|
885
|
+
icon?: ConnectorIcon;
|
|
886
|
+
/** How the connector signs in, so the app can say so before installing it. */
|
|
887
|
+
auth?: ConnectorAuth;
|
|
888
|
+
/** What an extension adds to a card. Present only on an extension. */
|
|
889
|
+
contributes?: ManifestContributions;
|
|
890
|
+
/** What an extension may ask the host for. Present only on an extension. */
|
|
891
|
+
permissions?: ExtensionPermission[];
|
|
892
|
+
/** Where an extension shows at all. Present only on an extension. */
|
|
893
|
+
activates?: ActivationPredicate;
|
|
894
|
+
triggers: Array<{
|
|
895
|
+
type: string;
|
|
896
|
+
label: string;
|
|
897
|
+
description?: string;
|
|
898
|
+
/** Seeds a connection's status mapping; absent when the connector was silent. */
|
|
899
|
+
statusMapping?: StatusSuggestion[];
|
|
900
|
+
/** Seeds the polling workflow created with the connection. */
|
|
901
|
+
defaultWorkflow?: DefaultWorkflow;
|
|
902
|
+
setup: ConnectionSetup;
|
|
903
|
+
}>;
|
|
904
|
+
actions: Array<{
|
|
905
|
+
type: string;
|
|
906
|
+
label: string;
|
|
907
|
+
description?: string;
|
|
908
|
+
inputs: Array<{
|
|
909
|
+
key: string;
|
|
910
|
+
label: string;
|
|
911
|
+
type: string;
|
|
912
|
+
required: boolean;
|
|
913
|
+
options?: ActionInputOption[];
|
|
914
|
+
/** An options set the connector serves, resolved against a live connection. */
|
|
915
|
+
loadOptions?: string;
|
|
916
|
+
/** For whoever is building a connector like this one, not for whoever runs it. */
|
|
917
|
+
builderHint?: string;
|
|
918
|
+
}>;
|
|
919
|
+
/**
|
|
920
|
+
* Fields the action is known to return. Absent when the connector declared
|
|
921
|
+
* none, which is not the same as saying it returns nothing.
|
|
922
|
+
*/
|
|
923
|
+
outputs?: Array<{
|
|
924
|
+
key: string;
|
|
925
|
+
type?: string;
|
|
926
|
+
description?: string;
|
|
927
|
+
}>;
|
|
928
|
+
/** Arguments a live check may call it with, when the author named some. */
|
|
929
|
+
sample?: Record<string, string>;
|
|
930
|
+
}>;
|
|
931
|
+
}
|
|
932
|
+
/** Full machine-readable description of a connector, served over MCP and printed by the CLI. */
|
|
933
|
+
declare function connectorManifest(connector: Connector): ConnectorManifest;
|
|
934
|
+
|
|
935
|
+
interface HarnessOptions {
|
|
936
|
+
config?: ConnectorConfig;
|
|
937
|
+
/** Fixed clock, so `updatedAt` defaults and cursors are deterministic. */
|
|
938
|
+
now?: () => string;
|
|
939
|
+
/** Answer the connector's calls from the test rather than the network. */
|
|
940
|
+
fetchImpl?: typeof fetch;
|
|
941
|
+
/** Answer its signed-in calls; defaults to `fetchImpl`, so one stub serves both. */
|
|
942
|
+
sessionFetchImpl?: typeof fetch;
|
|
943
|
+
/** Fake clock for backoff, so a retry test costs no real time. */
|
|
944
|
+
sleep?: (ms: number) => Promise<void>;
|
|
945
|
+
}
|
|
946
|
+
/** One reply the stub will serve, matched in the order the routes were given. */
|
|
947
|
+
interface MockRoute {
|
|
948
|
+
/**
|
|
949
|
+
* A string names a path: `/api/messages` matches that path and nothing else,
|
|
950
|
+
* and a trailing slash makes it a prefix — `/api/` matches everything under
|
|
951
|
+
* it. A pattern is tested against the whole URL, for the times host or query
|
|
952
|
+
* is what tells two calls apart.
|
|
953
|
+
*/
|
|
954
|
+
url: string | RegExp;
|
|
955
|
+
/** Matched case-insensitively; absent matches any method. */
|
|
956
|
+
method?: string;
|
|
957
|
+
/** Defaults to 200. */
|
|
958
|
+
status?: number;
|
|
959
|
+
/** Serialized as JSON unless it is already a string. */
|
|
960
|
+
body?: unknown;
|
|
961
|
+
headers?: Record<string, string>;
|
|
962
|
+
}
|
|
963
|
+
/** What the connector asked for, in the order it asked. */
|
|
964
|
+
interface MockCall {
|
|
965
|
+
method: string;
|
|
966
|
+
url: string;
|
|
967
|
+
body?: string;
|
|
968
|
+
}
|
|
969
|
+
interface MockRun<T> {
|
|
970
|
+
result: T;
|
|
971
|
+
calls: MockCall[];
|
|
972
|
+
}
|
|
973
|
+
interface ConnectorHarness {
|
|
974
|
+
poll(triggerType: string, options?: RunPollOptions): Promise<PollPage>;
|
|
975
|
+
drain(triggerType: string, options?: RunPollOptions): Promise<NormalizedItem[]>;
|
|
976
|
+
execute(actionType: string, args?: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
977
|
+
manifest(): ConnectorManifest;
|
|
978
|
+
/**
|
|
979
|
+
* Poll repeatedly the way Vorn does — carrying the newest `updatedAt`
|
|
980
|
+
* forward as the watermark — and return only items a real installation
|
|
981
|
+
* would treat as new. Catches the classic connector bug where a poll
|
|
982
|
+
* ignores its lower bound and re-delivers the same backlog forever.
|
|
983
|
+
*/
|
|
984
|
+
pollTwice(triggerType: string, options?: RunPollOptions): Promise<NormalizedItem[]>;
|
|
985
|
+
/**
|
|
986
|
+
* Run something with every HTTP request answered from `routes` instead of
|
|
987
|
+
* the network. A request no route matches is refused rather than served, so
|
|
988
|
+
* a test says which call escaped rather than reaching a real service.
|
|
989
|
+
*/
|
|
990
|
+
withMockHttp<T>(routes: MockRoute[], body: () => Promise<T> | T): Promise<MockRun<T>>;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* A call the routes did not offer.
|
|
994
|
+
*
|
|
995
|
+
* Its own class because callers wrap it: a declarative action rethrows with
|
|
996
|
+
* the action's name in front, so recognising an escape by reading the message
|
|
997
|
+
* would stop working the moment anything added a prefix.
|
|
998
|
+
*/
|
|
999
|
+
declare class MockRouteMissError extends Error {
|
|
1000
|
+
constructor(method: string, url: string);
|
|
1001
|
+
}
|
|
1002
|
+
/** Whether a call escaped the stub, however many times it was rethrown. */
|
|
1003
|
+
declare function escapedMockHttp(error: unknown): boolean;
|
|
1004
|
+
/**
|
|
1005
|
+
* Serve a connector's HTTP from a list of replies, in-process.
|
|
1006
|
+
*
|
|
1007
|
+
* Swapping `fetch` rather than opening a socket keeps a conformance run
|
|
1008
|
+
* hermetic: no port, no ordering between tests, and the same code path the
|
|
1009
|
+
* connector uses against the real service.
|
|
1010
|
+
*
|
|
1011
|
+
* One at a time, deliberately. Two overlapping installs share one global: the
|
|
1012
|
+
* first to finish restores the real `fetch` under the second — whose calls
|
|
1013
|
+
* then reach the network — and the second restores the first's dead stub
|
|
1014
|
+
* permanently. Refusing is the only outcome that cannot corrupt the process.
|
|
1015
|
+
*/
|
|
1016
|
+
declare function withMockHttp<T>(routes: MockRoute[], body: () => Promise<T> | T): Promise<MockRun<T>>;
|
|
1017
|
+
/**
|
|
1018
|
+
* Run a connector in-process, exactly as the MCP server would, without
|
|
1019
|
+
* spawning anything. Authors get real assertions in a plain unit test.
|
|
1020
|
+
*/
|
|
1021
|
+
declare function createConnectorHarness(connector: Connector, harnessOptions?: HarnessOptions): ConnectorHarness;
|
|
1022
|
+
/** Answers a footer or handler from fixtures, and refuses what the manifest never asked for. */
|
|
1023
|
+
interface MockHostRun {
|
|
1024
|
+
host: ExtensionHost;
|
|
1025
|
+
/** Permissions the run actually spent, so a declared-but-unused one can be named. */
|
|
1026
|
+
used: Set<ExtensionPermission>;
|
|
1027
|
+
}
|
|
1028
|
+
/** Replaces what the stub answers, for a test whose subject is the reading rather than the plumbing. */
|
|
1029
|
+
type MockHostAnswers = Partial<{
|
|
1030
|
+
[K in ExtensionHostMethod]: ExtensionHost[K];
|
|
1031
|
+
}>;
|
|
1032
|
+
/**
|
|
1033
|
+
* A host that answers from fixtures and enforces the manifest.
|
|
1034
|
+
*
|
|
1035
|
+
* The check runs every footer and handler against this rather than a real
|
|
1036
|
+
* session, which is what lets a conformance run catch an extension reaching
|
|
1037
|
+
* for something it never declared — before a person is asked to grant it.
|
|
1038
|
+
*/
|
|
1039
|
+
declare function mockExtensionHost(granted: readonly ExtensionPermission[], answers?: MockHostAnswers): MockHostRun;
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Every finding this SDK can report.
|
|
1043
|
+
*
|
|
1044
|
+
* A closed set, so `CHECK_OWNERS` cannot fall behind it: a new code that no
|
|
1045
|
+
* named check owns is a compile error rather than a receipt quietly vouching
|
|
1046
|
+
* for a check whose failure nothing was watching.
|
|
1047
|
+
*/
|
|
1048
|
+
type CheckCode = 'missing-description' | 'auth-undeclared' | 'auth-probe-missing' | 'secret-not-marked' | 'action-no-outputs' | 'input-type-unsupported' | 'missing-idempotent' | 'unverifiable' | 'sample-unusable' | 'poll-failed' | 'no-items' | 'no-cursor' | 'cursor-rejected' | 'redelivers-items' | 'stuck-cursor' | 'lifecycle-scripts' | 'keywords-missing' | 'runtime-dependencies' | 'mock-action-failed' | 'mock-network-escape' | 'mock-not-observed' | 'preflight-failed' | 'live-action-failed' | 'pack-launch' | 'pack-too-large' | 'web-entry-missing' | 'web-entry-outside-package' | 'footer-failed' | 'footer-items-invalid' | 'handler-failed' | 'permission-undeclared' | 'permission-unused';
|
|
1049
|
+
interface CheckFinding {
|
|
1050
|
+
/** `error` means the connector will misbehave in Vorn; `warn` is advisory. */
|
|
1051
|
+
level: 'error' | 'warn';
|
|
1052
|
+
code: CheckCode;
|
|
1053
|
+
/** Which part of the connector the finding is about. */
|
|
1054
|
+
target: string;
|
|
1055
|
+
message: string;
|
|
1056
|
+
}
|
|
1057
|
+
interface CheckOptions {
|
|
1058
|
+
/**
|
|
1059
|
+
* Poll every trigger against the real source. Off by default, so a check
|
|
1060
|
+
* runs on declared `sample` items and the definition alone.
|
|
1061
|
+
*/
|
|
1062
|
+
live?: boolean;
|
|
1063
|
+
/** Credentials, required by `live`. */
|
|
1064
|
+
config?: ConnectorConfig;
|
|
1065
|
+
now?: () => string;
|
|
1066
|
+
/**
|
|
1067
|
+
* Directory whose nearest package.json says how the connector ships. Given,
|
|
1068
|
+
* the checks that are about the package rather than the definition run too.
|
|
1069
|
+
*/
|
|
1070
|
+
packageDir?: string;
|
|
1071
|
+
/**
|
|
1072
|
+
* Bundles the connector so the check can see what would stay outside it.
|
|
1073
|
+
* Given, a pack's no-install-step promise is verified before packing.
|
|
1074
|
+
*/
|
|
1075
|
+
bundle?(request: BundleRequest): Promise<BundleOutput>;
|
|
1076
|
+
/** Module specifier the bundle starts from; required by `bundle`. */
|
|
1077
|
+
entry?: string;
|
|
1078
|
+
/**
|
|
1079
|
+
* Run every action against served HTTP rather than the network. Without
|
|
1080
|
+
* routes each request is answered `{}`, which proves an action runs and
|
|
1081
|
+
* escapes nowhere; with them, that it does the right thing.
|
|
1082
|
+
*/
|
|
1083
|
+
mock?: boolean;
|
|
1084
|
+
mockRoutes?: MockRoute[];
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Check a connector against the contract Vorn relies on.
|
|
1088
|
+
*
|
|
1089
|
+
* The point is a feedback loop: a connector — hand-written or generated — can
|
|
1090
|
+
* be verified before it is ever installed, catching the failures that are
|
|
1091
|
+
* otherwise invisible until duplicate tasks show up in someone's inbox days
|
|
1092
|
+
* later.
|
|
1093
|
+
*/
|
|
1094
|
+
declare function checkConnector(connector: Connector, options?: CheckOptions): Promise<CheckFinding[]>;
|
|
1095
|
+
/**
|
|
1096
|
+
* What the factory checked, and when.
|
|
1097
|
+
*
|
|
1098
|
+
* Mirrors the receipt the catalog carries. "Verified" is not a word here but a
|
|
1099
|
+
* list: the checks that ran and came back with nothing to say. A check that
|
|
1100
|
+
* could not run — no sample to replay, no credentials to go live with — is
|
|
1101
|
+
* absent rather than passed, because absent is the true answer.
|
|
1102
|
+
*/
|
|
1103
|
+
interface ConnectorVerification {
|
|
1104
|
+
/** Which receipt format this is, so a later one is not read as this one. */
|
|
1105
|
+
schema: 1;
|
|
1106
|
+
version: string;
|
|
1107
|
+
checkedAt: string;
|
|
1108
|
+
checks: string[];
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Which named check each finding belongs to, so one failure clears one name.
|
|
1112
|
+
*
|
|
1113
|
+
* `null` means the code belongs to no check a receipt can carry — `pack` has
|
|
1114
|
+
* its own gates, and a receipt speaks only for the conformance run.
|
|
1115
|
+
*/
|
|
1116
|
+
declare const CHECK_OWNERS: Record<CheckCode, string | null>;
|
|
1117
|
+
interface ConformanceRun {
|
|
1118
|
+
findings: CheckFinding[];
|
|
1119
|
+
/** Named checks that ran and had nothing to say. */
|
|
1120
|
+
passed: string[];
|
|
1121
|
+
/**
|
|
1122
|
+
* The receipt to publish, or nothing when an error means there is no claim
|
|
1123
|
+
* to make. Warnings do not void it — they are advice, not a failure.
|
|
1124
|
+
*/
|
|
1125
|
+
receipt?: ConnectorVerification;
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Check a connector and say what can be vouched for.
|
|
1129
|
+
*
|
|
1130
|
+
* `checkConnector` answers "what is wrong"; this answers the catalog's
|
|
1131
|
+
* question, "what did you check", which is what a verified badge shows.
|
|
1132
|
+
*/
|
|
1133
|
+
declare function runConformance(connector: Connector, options?: CheckOptions): Promise<ConformanceRun>;
|
|
1134
|
+
/** Render findings for a terminal. Returns an empty string when all clear. */
|
|
1135
|
+
declare function formatFindings(findings: CheckFinding[]): string;
|
|
1136
|
+
|
|
1137
|
+
export { MANIFEST_TOOL as $, type ActionRequest as A, type BundleRequest as B, type CheckFinding as C, type ConnectorHarness as D, type ExtensionPermission as E, type ConnectorIcon as F, type ConnectorKind as G, type ConnectorManifest as H, type ConnectorVerification as I, type DedupeStrategy as J, type DefaultWorkflow as K, type ExtensionAgent as L, type ExtensionContext as M, type NormalizedItem as N, type ExtensionContributions as O, type PollContext as P, type ExtensionPlatform as Q, type ExtensionUsage as R, type ExtensionUsageWindow as S, type TriggerDefinition as T, type FetchContext as U, type FooterContribution as V, type FooterItem as W, type HarnessOptions as X, type LinkContext as Y, type LinkHandled as Z, type LinkHandlerContribution as _, type BundleOutput as a, MAX_PACK_BYTES as a0, MAX_POLL_PAGES as a1, type ManifestContributions as a2, type MockCall as a3, type MockHostAnswers as a4, type MockHostRun as a5, type MockRoute as a6, MockRouteMissError as a7, type MockRun as a8, OPTIONS_TOOL as a9, lifecycleScriptFindings as aA, mockExtensionHost as aB, pollToolName as aC, readNearestPackageJson as aD, resilientFetch as aE, retryAfterMs as aF, runAction as aG, runConformance as aH, runOptions as aI, runPoll as aJ, withMockHttp as aK, type OptionsContext as aa, type OptionsLoader as ab, PREFLIGHT_TOOL as ac, type PaginationStrategy as ad, type PaneContribution as ae, type PollPage as af, type PreflightResult as ag, type ResilientFetchOptions as ah, type RetryPolicy as ai, type RunActionOptions as aj, type RunPollOptions as ak, type SessionContext as al, type StatusSuggestion as am, backoffMs as an, bundleDependencyFindings as ao, bundledRequireFindings as ap, checkConnector as aq, connectionSetup as ar, connectorManifest as as, createConnectorHarness as at, drainPoll as au, esbuildBundle as av, escapedMockHttp as aw, footerToolName as ax, formatFindings as ay, handlerToolName as az, type ExtensionHostMethod as b, type ConnectorDefinition as c, type Connector as d, type ExtensionDefinition as e, type ConnectorConfig as f, type ExtensionHost as g, type PollOutcome as h, type ConnectorItem as i, type PostReceiveOp as j, type ActionContext as k, type ActionDefinition as l, type ActionInputField as m, type ActionInputOption as n, type ActionInputType as o, type ActionOutputField as p, type ActivationPredicate as q, type AuthRung as r, type BrowserSignIn as s, CHECK_OWNERS as t, type CheckCode as u, type CheckOptions as v, type ConformanceRun as w, type ConnectionSetup as x, type ConnectorAuth as y, type ConnectorConfigField as z };
|