@rackbops/ac-plugin-contract 2.0.0-alpha.33
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/AUTHORING.md +934 -0
- package/LICENSE +201 -0
- package/README.md +29 -0
- package/dist/action-options.d.ts +49 -0
- package/dist/action-options.js +9 -0
- package/dist/attention.d.ts +43 -0
- package/dist/attention.js +12 -0
- package/dist/host-api.d.ts +365 -0
- package/dist/host-api.js +10 -0
- package/dist/index-envelope.d.ts +36 -0
- package/dist/index-envelope.js +2 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +9 -0
- package/dist/issue-filter.d.ts +122 -0
- package/dist/issue-filter.js +152 -0
- package/dist/manifest.d.ts +217 -0
- package/dist/manifest.js +9 -0
- package/dist/ov-widgets.d.ts +21 -0
- package/dist/ov-widgets.js +33 -0
- package/dist/slots.d.ts +139 -0
- package/dist/slots.js +17 -0
- package/dist/version.d.ts +31 -0
- package/dist/version.js +19 -0
- package/dist/views.d.ts +262 -0
- package/dist/views.js +2 -0
- package/host.d.ts +60 -0
- package/package.json +47 -0
- package/schema.json +495 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `HostApi` is the service surface the host passes to `createPlugin(host)` on the server, and the
|
|
3
|
+
* `@ac/host` module the shell exposes to frontend bundles in the browser (design.md §3). This is
|
|
4
|
+
* the v1 shape; the server implementation landed in #28, with `sidecar` real as of #29's proxy,
|
|
5
|
+
* `jobs` as of #31's scheduler, `agent` as of E4, and `github` real as of #127's Octokit client; the
|
|
6
|
+
* browser side lands in #32. Methods are added additively — a breaking change bumps
|
|
7
|
+
* `HOST_API_VERSION`.
|
|
8
|
+
*/
|
|
9
|
+
import type { Octokit } from "@octokit/rest";
|
|
10
|
+
import type { HttpMethod } from "./manifest.js";
|
|
11
|
+
import type { AgentCallOutcome, JobMachineRun, MachineView } from "./views.js";
|
|
12
|
+
/** A typed, per-plugin settings accessor (backed by the host's settings overlay). */
|
|
13
|
+
export interface SettingsService {
|
|
14
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
15
|
+
set(key: string, value: unknown): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/** Per-plugin durable storage: a SQLite table scoped to the plugin, with atomic JSON helpers. */
|
|
18
|
+
export interface StorageService {
|
|
19
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
20
|
+
set(key: string, value: unknown): Promise<void>;
|
|
21
|
+
delete(key: string): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/** Register/trigger the plugin's contributed jobs and read their last result. */
|
|
24
|
+
export interface JobsService {
|
|
25
|
+
/** Run a contributed job now (the manual-trigger endpoint). For a `runOn: "agent"` job this resolves
|
|
26
|
+
* once every currently paired machine has either run or been queued. */
|
|
27
|
+
trigger(jobId: string): Promise<void>;
|
|
28
|
+
/** The last run of a job, if it has run — for an agent job, the newest recorded run across every
|
|
29
|
+
* machine. */
|
|
30
|
+
lastRun(jobId: string): Promise<{
|
|
31
|
+
startedAt: string;
|
|
32
|
+
ok: boolean;
|
|
33
|
+
} | undefined>;
|
|
34
|
+
/** One row per relevant machine (#51): a single null-id row for a host job, one row per currently
|
|
35
|
+
* paired machine for an agent job. */
|
|
36
|
+
machineRuns(jobId: string): Promise<JobMachineRun[]>;
|
|
37
|
+
/** #78: the SAME shape as `machineRuns`, for an OTHER plugin's declared job -- `machineRuns` stays
|
|
38
|
+
* scoped to the calling plugin's own jobs; this is the needs-attention rollup's in-process read of
|
|
39
|
+
* every OTHER loaded plugin's job health, so `overview`'s `/attention` route never has to make an
|
|
40
|
+
* HTTP call back into its own host. Discloses nothing `GET /api/jobs/:pluginId/:jobId/runs`
|
|
41
|
+
* doesn't already serve publicly -- just a non-HTTP path to the same data. Optional: only
|
|
42
|
+
* `overview` calls it today. */
|
|
43
|
+
machineRunsOf?(pluginId: string, jobId: string): Promise<JobMachineRun[]>;
|
|
44
|
+
}
|
|
45
|
+
/** #78: every loaded in-process plugin's own declared jobs -- lets the needs-attention rollup
|
|
46
|
+
* discover what to poll via `jobs.machineRunsOf` without a second manifest fetch or an HTTP
|
|
47
|
+
* self-loop. Host-internal discovery only (unlike `PluginView`, never served over HTTP as its own
|
|
48
|
+
* route); optional on `HostApi` since only `overview` uses it. */
|
|
49
|
+
export interface PluginJobsService {
|
|
50
|
+
list(): {
|
|
51
|
+
pluginId: string;
|
|
52
|
+
jobs: JobDeclaration[];
|
|
53
|
+
}[];
|
|
54
|
+
}
|
|
55
|
+
/** The subset of `JobContribution` (`manifest.ts`) the rollup needs -- id, schedule metadata are
|
|
56
|
+
* irrelevant to it, so this stays a narrow projection rather than the full manifest shape. */
|
|
57
|
+
export interface JobDeclaration {
|
|
58
|
+
id: string;
|
|
59
|
+
runOn: "host" | "agent";
|
|
60
|
+
}
|
|
61
|
+
/** The typed event bus (e.g. `issue.changed`, `job.finished`, `artifact.rendered`). */
|
|
62
|
+
export interface EventsService {
|
|
63
|
+
emit(type: string, payload: unknown): void;
|
|
64
|
+
/** Subscribe; returns an unsubscribe function. */
|
|
65
|
+
on(type: string, handler: (payload: unknown) => void): () => void;
|
|
66
|
+
}
|
|
67
|
+
/** One shared, rate-limit-aware GitHub client (Octokit; PAT from env, #127). Consumers make every
|
|
68
|
+
* GitHub call through `octokit()` rather than constructing a second client. */
|
|
69
|
+
export interface GithubService {
|
|
70
|
+
/** The authenticated login, cached. */
|
|
71
|
+
viewer(): Promise<string>;
|
|
72
|
+
/** The one shared Octokit REST client — consumers make all GitHub calls through this. */
|
|
73
|
+
octokit(): Octokit;
|
|
74
|
+
/** Remaining primary-rate-limit budget from the last response's headers, or undefined before any
|
|
75
|
+
* call. */
|
|
76
|
+
rateLimit(): {
|
|
77
|
+
remaining: number;
|
|
78
|
+
limit: number;
|
|
79
|
+
resetAt: string;
|
|
80
|
+
} | undefined;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Call a paired host-agent verb, or list the paired machines (#50). `HostApi.agent` is scoped per
|
|
84
|
+
* plugin the same way `storage`/`settings` are (`host-factory.ts`) -- the plugin id a call carries
|
|
85
|
+
* is the host's own, never a value the plugin supplies, so a plugin can only ever act as itself and
|
|
86
|
+
* cannot reach pairing or grants through this surface at all. Streaming (`GET /v1/jobs/:id/events`)
|
|
87
|
+
* and the `runOn: "agent"` job fan-out are #51/#54.
|
|
88
|
+
*/
|
|
89
|
+
export interface AgentService {
|
|
90
|
+
/** The ids of the machines currently paired with the console. */
|
|
91
|
+
machines(): Promise<string[]>;
|
|
92
|
+
/** The paired machines, with their current grants. */
|
|
93
|
+
list(): Promise<MachineView[]>;
|
|
94
|
+
/** Call one of this plugin's declared capabilities on a paired machine's agent. */
|
|
95
|
+
call<T = unknown>(machineId: string, method: "GET" | "POST", path: string, body?: unknown, opts?: {
|
|
96
|
+
/** How long to wait for the agent's response headers (undici's headersTimeout; its default is
|
|
97
|
+
* 300 s). A long-running verb -- a scan -- passes the verb's own limit plus a margin, so the
|
|
98
|
+
* agent's own timeout answer arrives before the console gives up. */
|
|
99
|
+
timeoutMs?: number;
|
|
100
|
+
}): Promise<AgentCallOutcome<T>>;
|
|
101
|
+
/**
|
|
102
|
+
* Open a paired machine's job event stream (`GET /v1/jobs/:jobId/events`, #54) and relay it into a
|
|
103
|
+
* new host task: every agent `line` frame becomes a `task.log`, the `done` frame finishes it --
|
|
104
|
+
* the tasks drawer streams the machine's job with no new UI (decision 12/20). Resolves with the new
|
|
105
|
+
* task's id once the stream is confirmed open (the agent answered `200`); the relay itself then runs
|
|
106
|
+
* detached and can never surface here. Rejects (starting no task) on an invalid `jobId`, an unknown
|
|
107
|
+
* machine, or any non-`200` response from the agent (a `404` unknown job, a `403` refusal, ...) --
|
|
108
|
+
* the same failure shapes `call` throws for a bad path, surfaced the same way.
|
|
109
|
+
*/
|
|
110
|
+
streamJob(machineId: string, jobId: string, opts: {
|
|
111
|
+
label: string;
|
|
112
|
+
}): Promise<{
|
|
113
|
+
taskId: string;
|
|
114
|
+
}>;
|
|
115
|
+
}
|
|
116
|
+
/** Proxy to a sidecar plugin's HTTP surface (the host handles health + base URL). */
|
|
117
|
+
export interface SidecarService {
|
|
118
|
+
/** The base URL the host proxies to for a sidecar plugin. */
|
|
119
|
+
baseUrl(pluginId: string): string;
|
|
120
|
+
}
|
|
121
|
+
/** Register a confirm-gated action; the shell builds its button + modal from the manifest data. */
|
|
122
|
+
export interface ActionsService {
|
|
123
|
+
register(action: {
|
|
124
|
+
id: string;
|
|
125
|
+
run: (input: unknown) => Promise<unknown>;
|
|
126
|
+
}): void;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The `name` of the `Error` an action's `run` throws to say the CALLER's input was malformed (a
|
|
130
|
+
* missing or wrong-typed field, a bad `owner/name`, ...) as opposed to something going wrong in the
|
|
131
|
+
* world (a GitHub failure, a store that isn't cloned): the host's action route
|
|
132
|
+
* (`POST /api/actions/:pluginId/:actionId`) answers such an error `400` with its message, and any
|
|
133
|
+
* other thrown error `500`.
|
|
134
|
+
*
|
|
135
|
+
* A NAME, deliberately not a class the host exports for plugins to import: a plugin server imports
|
|
136
|
+
* `@ac/contract` as `import type` only (`docs/plugins/authoring.md` §2 -- its compiled `dist/server.js`
|
|
137
|
+
* carries no runtime imports), and a class here would give every plugin a runtime dependency on it,
|
|
138
|
+
* while `instanceof` cannot see across a rollup-bundled server that inlines its own copy. Each plugin
|
|
139
|
+
* that validates input carries a small local class instead (the `AbortError` idiom), typed against
|
|
140
|
+
* this name so renaming it here fails the plugin's build rather than silently turning its 400s into
|
|
141
|
+
* 500s:
|
|
142
|
+
*
|
|
143
|
+
* ```ts
|
|
144
|
+
* import type { ActionInputErrorName } from "@rackbops/ac-plugin-contract"
|
|
145
|
+
*
|
|
146
|
+
* export class ActionInputError extends Error {
|
|
147
|
+
* override name: ActionInputErrorName = "ActionInputError"
|
|
148
|
+
* }
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* Types only: no runtime export, no `HOST_API_VERSION` bump.
|
|
152
|
+
*/
|
|
153
|
+
export type ActionInputErrorName = "ActionInputError";
|
|
154
|
+
/**
|
|
155
|
+
* The in-process composition-slot binding (#84): a plugin registers its provider instance for a slot
|
|
156
|
+
* it `contributes.provides` in `activate()`; the host holds the one process-level binding and resolves
|
|
157
|
+
* it for the pipeline (`auth@1`) or another plugin (`notify@1`, #87) to call. Deliberately untyped by
|
|
158
|
+
* slot here (`impl: unknown`) -- the host casts to the slot's real interface (`AuthProvider`,
|
|
159
|
+
* `NotifyProvider`, `slots.ts`) when it resolves, the same way `RoutesService`/`ActionsService` don't
|
|
160
|
+
* know their callers' payload shapes either. Two providers for the same `slot@version` is a boot
|
|
161
|
+
* error (auth/notify are singletons) -- surfaced by the host-side registry `provide` throws from, not
|
|
162
|
+
* this interface, since this is the plugin's OWN scoped view (pre-bound to its id, so the error can
|
|
163
|
+
* name the real offending plugin without a pluginId parameter here). */
|
|
164
|
+
export interface SlotRegistry {
|
|
165
|
+
/** Register this plugin's provider instance for `slot@version`. Throws if that slot already has a
|
|
166
|
+
* provider (from this plugin or another) -- auth/notify are singletons, never a last-write-wins. */
|
|
167
|
+
provide(slot: string, version: number, impl: unknown): void;
|
|
168
|
+
}
|
|
169
|
+
/** A logger prefixed with the plugin id. */
|
|
170
|
+
export interface LogService {
|
|
171
|
+
info(message: string, ...args: unknown[]): void;
|
|
172
|
+
warn(message: string, ...args: unknown[]): void;
|
|
173
|
+
error(message: string, ...args: unknown[]): void;
|
|
174
|
+
}
|
|
175
|
+
/** Read declared env keys only (the manifest's `env[]`). */
|
|
176
|
+
export interface EnvService {
|
|
177
|
+
get(key: string): string | undefined;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Register HTTP handlers for the plugin's declared routes. The host mounts each under the plugin's
|
|
181
|
+
* own namespace `/api/x/<id>/…` (never bare); `handler` is a web-standard `Request` -> `Response`, so
|
|
182
|
+
* a plugin does not depend on the host's server. A plugin may only register a `(method, path)` it
|
|
183
|
+
* declared in its manifest `contributes.routes[]`. (Same-origin/CSRF protection of mutating routes is
|
|
184
|
+
* added across every mutating route by #85.)
|
|
185
|
+
*/
|
|
186
|
+
export interface RoutesService {
|
|
187
|
+
register(method: HttpMethod, path: string, handler: (request: Request) => Response | Promise<Response>): void;
|
|
188
|
+
}
|
|
189
|
+
/** The server-side services passed to `createPlugin(host)`. */
|
|
190
|
+
export interface HostApi {
|
|
191
|
+
settings: SettingsService;
|
|
192
|
+
storage: StorageService;
|
|
193
|
+
jobs: JobsService;
|
|
194
|
+
events: EventsService;
|
|
195
|
+
github: GithubService;
|
|
196
|
+
agent: AgentService;
|
|
197
|
+
sidecar: SidecarService;
|
|
198
|
+
routes: RoutesService;
|
|
199
|
+
actions: ActionsService;
|
|
200
|
+
log: LogService;
|
|
201
|
+
env: EnvService;
|
|
202
|
+
/** #78: see `PluginJobsService`. Optional -- only `overview`'s needs-attention rollup uses it. */
|
|
203
|
+
plugins?: PluginJobsService;
|
|
204
|
+
/** #84: see `SlotRegistry`. */
|
|
205
|
+
slots: SlotRegistry;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The context the host passes to `Plugin.runJob` for one job run (#31). `jobId` selects which
|
|
209
|
+
* contributed job to run (one `runJob` dispatches all of the plugin's `contributes.jobs[]`); `signal`
|
|
210
|
+
* aborts when the run exceeds its `timeoutSec`, and a cooperative job should stop when it fires; `log`
|
|
211
|
+
* appends a line to THIS run's recorded log tail. Only lines written to `log` appear in the run
|
|
212
|
+
* record — `host.log` is operational logging and is not captured in the tail. `machine` is present
|
|
213
|
+
* for every run of a `runOn: "agent"` job (one run per paired machine, #51) and absent for a
|
|
214
|
+
* `runOn: "host"` job, which runs once with no machine.
|
|
215
|
+
*/
|
|
216
|
+
export interface JobRunContext {
|
|
217
|
+
jobId: string;
|
|
218
|
+
signal: AbortSignal;
|
|
219
|
+
log(line: string): void;
|
|
220
|
+
machine?: {
|
|
221
|
+
id: string;
|
|
222
|
+
name: string;
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/** A plugin instance. `createPlugin(host)` is pure; I/O only in `activate()` (design §4 lifecycle). */
|
|
226
|
+
export interface Plugin {
|
|
227
|
+
activate(): void | Promise<void>;
|
|
228
|
+
/**
|
|
229
|
+
* Run one of the plugin's contributed jobs, dispatched by `ctx.jobId` (#31's scheduler). Optional —
|
|
230
|
+
* a plugin that contributes no jobs omits it; the host records a job as failed if it is declared but
|
|
231
|
+
* no `runJob` is exported. A `runOn: "host"` job runs in-process once; a `runOn: "agent"` job runs
|
|
232
|
+
* in-process too, once per currently paired machine, with `ctx.machine` set (#51) — the agent itself
|
|
233
|
+
* runs no plugin code, so machine-side work goes through `host.agent.call(ctx.machine.id, …)` (#50).
|
|
234
|
+
*/
|
|
235
|
+
runJob?(ctx: JobRunContext): void | Promise<void>;
|
|
236
|
+
}
|
|
237
|
+
/** A plugin's server entry: `dist/server.js` exports this as `createPlugin`. */
|
|
238
|
+
export type CreatePlugin = (host: HostApi) => Plugin;
|
|
239
|
+
/** Toast severity, matching notify. */
|
|
240
|
+
export type ToastSeverity = "info" | "warning" | "error";
|
|
241
|
+
/**
|
|
242
|
+
* The browser-side `@ac/host` a frontend bundle imports. v1 (implemented in #32) is `theme` tokens +
|
|
243
|
+
* `toast`; a `fetch` wrapper and `slots` grow additively when a consumer needs them (the issues panel,
|
|
244
|
+
* E5). The E3 chrome-integrated upgrade landed across #38 (toast portal), #41 (modal), and #142
|
|
245
|
+
* (quickcast). Methods are added additively — a breaking change bumps `HOST_API_VERSION`.
|
|
246
|
+
*/
|
|
247
|
+
export interface BrowserHostApi {
|
|
248
|
+
/** Show a toast. */
|
|
249
|
+
toast(message: string, opts?: {
|
|
250
|
+
severity?: ToastSeverity;
|
|
251
|
+
}): void;
|
|
252
|
+
/** Resolve a theme token (a `--rb-*` custom property) to its current value. */
|
|
253
|
+
theme: {
|
|
254
|
+
token(name: string): string;
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* The shell's header button slot (#38): put a button in the header; returns the unregister. Safe to
|
|
258
|
+
* call from a mounted panel/card (an effect) or at bundle import time — a registration made before the
|
|
259
|
+
* shell mounts is held and delivered when it does, and its unregister works in either state.
|
|
260
|
+
*/
|
|
261
|
+
header: {
|
|
262
|
+
registerButton(button: HeaderButtonSpec): () => void;
|
|
263
|
+
};
|
|
264
|
+
/**
|
|
265
|
+
* Open the universal confirm modal for a registered action (#41). Fire-and-forget (returns nothing);
|
|
266
|
+
* safe to call before the shell mounts — held and opened once it does, or dropped if it never mounts,
|
|
267
|
+
* the same pending-buffer semantics as `header.registerButton`.
|
|
268
|
+
*
|
|
269
|
+
* `subscribe` (#460) is the completion signal `open` lacks: the listener runs once for every action
|
|
270
|
+
* the modal actually dispatched and got a result for -- success or failure -- whoever opened it (a
|
|
271
|
+
* plugin's `open`, the Ctrl+K palette), so a panel can refresh what an action just changed. A Cancel
|
|
272
|
+
* or Escape never fires it. Returns the unsubscribe. A listener that throws is logged and skipped;
|
|
273
|
+
* it never reaches the modal or another listener.
|
|
274
|
+
*/
|
|
275
|
+
actions: {
|
|
276
|
+
open(ref: ActionOpenRef): void;
|
|
277
|
+
subscribe(listener: (event: ActionSettledEvent) => void): () => void;
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* Register a source of quickcast items for the shell's Ctrl+K palette (#40): the palette calls every
|
|
281
|
+
* registered source on open (and again on each filter pass) and lists the collected items after its
|
|
282
|
+
* own built-in panel/action items, in registration order. The plugin's `run` decides what happens
|
|
283
|
+
* (navigate, open its own UI, call `actions.open`); the palette closes and then calls `run`. Returns
|
|
284
|
+
* the unregister. Safe to call before the shell mounts — held and delivered when it does, the same
|
|
285
|
+
* pending-buffer semantics as `header.registerButton`.
|
|
286
|
+
*/
|
|
287
|
+
quickcast: {
|
|
288
|
+
register(source: () => QuickcastItem[]): () => void;
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* Same-origin fetch to a host/plugin route (E5 #56, 56c). A relative `path` resolves against the
|
|
292
|
+
* console origin; an absolute `path` whose origin differs from the console's own is REJECTED (throws)
|
|
293
|
+
* rather than followed -- `credentials: "same-origin"` alone would not stop the request itself, or a
|
|
294
|
+
* write's `X-AC-CSRF` token, from reaching a third-party origin. Credentials are included
|
|
295
|
+
* (`same-origin`); a non-GET/HEAD/OPTIONS request carries the `X-AC-CSRF` token fetched once from
|
|
296
|
+
* `GET /api/config` (#85) and cached -- defense-in-depth alongside the Origin check #85 already
|
|
297
|
+
* enforces, not itself required yet. Panels read a provider's routes (e.g.
|
|
298
|
+
* `/api/x/provider-github/…`) through this rather than a bare `fetch`. Returns the raw `Response`;
|
|
299
|
+
* the caller parses it.
|
|
300
|
+
*/
|
|
301
|
+
fetch(path: string, init?: RequestInit): Promise<Response>;
|
|
302
|
+
/**
|
|
303
|
+
* `fetch`, wrapped in a per-path TTL cache (E7 #77): only `GET` may be cached (throws on any
|
|
304
|
+
* other method); a call within `ttlMs` (default 45 s) of the path's last fetch reuses that SAME
|
|
305
|
+
* underlying request rather than issuing a new one, and each caller gets its own `Response` via
|
|
306
|
+
* `.clone()` -- two independent reads of a cache hit never collide on an already-consumed body.
|
|
307
|
+
* A rejected fetch is dropped immediately, not cached for the rest of the window. A card reads
|
|
308
|
+
* through `cached`, never bare `fetch`, so Refresh (`invalidate`) and the heartbeat reach it.
|
|
309
|
+
*/
|
|
310
|
+
cached(path: string, init?: RequestInit, opts?: {
|
|
311
|
+
ttlMs?: number;
|
|
312
|
+
}): Promise<Response>;
|
|
313
|
+
/** Bypass the cache: drop one path's entry, or every entry when `path` is omitted (Refresh). */
|
|
314
|
+
invalidate(path?: string): void;
|
|
315
|
+
}
|
|
316
|
+
/** One entry a plugin contributes to the quickcast palette (#40, #142). */
|
|
317
|
+
export interface QuickcastItem {
|
|
318
|
+
label: string;
|
|
319
|
+
hint?: string;
|
|
320
|
+
run: () => void;
|
|
321
|
+
}
|
|
322
|
+
/** A button a plugin contributes to the shell header (#38). Re-registering an `id` replaces it. */
|
|
323
|
+
export interface HeaderButtonSpec {
|
|
324
|
+
id: string;
|
|
325
|
+
label: string;
|
|
326
|
+
onClick: () => void;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Which registered action to open the universal modal for (#41), and what it operates on. `target`
|
|
330
|
+
* scopes the draft key (e.g. an issue number) when the action is opened against a specific object;
|
|
331
|
+
* omitted for a `scope: "global"` action, which falls back to the action's own `scope` as the key.
|
|
332
|
+
*/
|
|
333
|
+
export interface ActionOpenRef {
|
|
334
|
+
pluginId: string;
|
|
335
|
+
actionId: string;
|
|
336
|
+
target?: string;
|
|
337
|
+
/** Initial field values for this open (#71a). An explicit seed WINS over a stored draft for the same
|
|
338
|
+
* scope key -- for the fields it NAMES (#478): every other field keeps the draft's value, else its
|
|
339
|
+
* default, so text typed into the form survives a cancel and a seeded reopen. The merged values are
|
|
340
|
+
* written as the draft at once, so a reload keeps exactly what the form shows. A field the seed names as
|
|
341
|
+
* empty (`""`, `[]`, `0`, `false`) still wins -- so seed only what you know, and omit a key rather than
|
|
342
|
+
* seed `""` for "unknown" -- and only an absent key (or `undefined`) leaves it to the draft. Absent
|
|
343
|
+
* `input` -> the draft's values for the action's own fields, then the field defaults. A key not among the action's
|
|
344
|
+
* declared `fields` is dropped, from the seed and from the draft, before it ever reaches the form or
|
|
345
|
+
* the submitted input, so a stale/mistargeted seed can never post a stray field. A seed must name every
|
|
346
|
+
* field whose meaning depends on another field it names (`originalLabels` with `labels`): the one it
|
|
347
|
+
* leaves out keeps a possibly stale drafted value. */
|
|
348
|
+
input?: Record<string, unknown>;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* What `actions.subscribe` delivers (#460): one action the universal modal dispatched and got a
|
|
352
|
+
* result for. `target` is the opened ref's own `target` (absent when the opener passed none -- the
|
|
353
|
+
* Ctrl+K palette carries none, and a `scope: "global"` action is usually opened without one) -- what it
|
|
354
|
+
* was opened FOR, not what the operator finally typed into the form. `error` is the failure message
|
|
355
|
+
* (the server's `error` text, or a client-side one such as `http 500`, a network error or a timeout).
|
|
356
|
+
* The event carries no input -- a listener can refetch what changed but never read what was sent.
|
|
357
|
+
*/
|
|
358
|
+
export interface ActionSettledEvent {
|
|
359
|
+
pluginId: string;
|
|
360
|
+
actionId: string;
|
|
361
|
+
target?: string;
|
|
362
|
+
ok: boolean;
|
|
363
|
+
error?: string;
|
|
364
|
+
}
|
|
365
|
+
//# sourceMappingURL=host-api.d.ts.map
|
package/dist/host-api.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `HostApi` is the service surface the host passes to `createPlugin(host)` on the server, and the
|
|
3
|
+
* `@ac/host` module the shell exposes to frontend bundles in the browser (design.md §3). This is
|
|
4
|
+
* the v1 shape; the server implementation landed in #28, with `sidecar` real as of #29's proxy,
|
|
5
|
+
* `jobs` as of #31's scheduler, `agent` as of E4, and `github` real as of #127's Octokit client; the
|
|
6
|
+
* browser side lands in #32. Methods are added additively — a breaking change bumps
|
|
7
|
+
* `HOST_API_VERSION`.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=host-api.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { EnvVar } from "./manifest.js";
|
|
2
|
+
/**
|
|
3
|
+
* The plugin-index envelope (#35) — the `plugins.json` shape a host publishes so others can discover its
|
|
4
|
+
* plugins, shared with the discord-bot index (design §4): the same `schemaVersion`/`generatedAt` header
|
|
5
|
+
* and the per-plugin fields `name`, `package`, `version`, `hostApiVersion`, `env[]`, `releases[]`, plus a
|
|
6
|
+
* `host` discriminator so one repo could serve both hosts. `contributes` is deliberately NOT here — it is
|
|
7
|
+
* private to each host. These are pure types; they are not part of the manifest JSON Schema (`gen-schema`
|
|
8
|
+
* generates only from `AcPluginManifest`), so adding this file does not change `schema.json`.
|
|
9
|
+
*/
|
|
10
|
+
/** One published release of a plugin, derived from its CHANGELOG. */
|
|
11
|
+
export interface Release {
|
|
12
|
+
version: string;
|
|
13
|
+
/** ISO date of the release, from the CHANGELOG section. */
|
|
14
|
+
date: string;
|
|
15
|
+
/** The CHANGELOG section body, if any. */
|
|
16
|
+
notes?: string;
|
|
17
|
+
}
|
|
18
|
+
/** One plugin's entry in an index. `name` is the shared identifier (AC2 sources it from the manifest `id`). */
|
|
19
|
+
export interface PluginIndexEntry {
|
|
20
|
+
/** Which host this plugin targets, e.g. "artifact-console" — the discriminator on a shared index. */
|
|
21
|
+
host: string;
|
|
22
|
+
name: string;
|
|
23
|
+
/** The npm/package name (package.json `name`). */
|
|
24
|
+
package: string;
|
|
25
|
+
version: string;
|
|
26
|
+
hostApiVersion: number;
|
|
27
|
+
env: EnvVar[];
|
|
28
|
+
releases: Release[];
|
|
29
|
+
}
|
|
30
|
+
/** A `plugins.json` index. `generatedAt` is stamped by a publishing registry; a workspace drift-copy omits it. */
|
|
31
|
+
export interface PluginIndex {
|
|
32
|
+
schemaVersion: number;
|
|
33
|
+
generatedAt?: string;
|
|
34
|
+
plugins: PluginIndexEntry[];
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=index-envelope.d.ts.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ac/contract` — the plugin ABI: the `acPlugin` manifest + contribution points, the `HostApi`
|
|
3
|
+
* services, the four composition slots, and the version constants. The manifest's JSON Schema is
|
|
4
|
+
* generated from these types by `gen-schema.mjs` into `schema.json`.
|
|
5
|
+
*/
|
|
6
|
+
export type * from "./action-options.js";
|
|
7
|
+
export type * from "./attention.js";
|
|
8
|
+
export type * from "./host-api.js";
|
|
9
|
+
export type * from "./index-envelope.js";
|
|
10
|
+
export * from "./issue-filter.js";
|
|
11
|
+
export type * from "./manifest.js";
|
|
12
|
+
export * from "./ov-widgets.js";
|
|
13
|
+
export type * from "./slots.js";
|
|
14
|
+
export { HOST_API_VERSION, SLOTS } from "./version.js";
|
|
15
|
+
export type * from "./views.js";
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ac/contract` — the plugin ABI: the `acPlugin` manifest + contribution points, the `HostApi`
|
|
3
|
+
* services, the four composition slots, and the version constants. The manifest's JSON Schema is
|
|
4
|
+
* generated from these types by `gen-schema.mjs` into `schema.json`.
|
|
5
|
+
*/
|
|
6
|
+
export * from "./issue-filter.js";
|
|
7
|
+
export * from "./ov-widgets.js";
|
|
8
|
+
export { HOST_API_VERSION, SLOTS } from "./version.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { IssueRecord } from "./slots.js";
|
|
2
|
+
/**
|
|
3
|
+
* The shared filter model (E5 #56, 56c, plan of record decision 4): the eight-axis filter types + a
|
|
4
|
+
* pure `matchesIssue` predicate over the `IssueRecord` shape 56a shipped. Both issue panels — Board
|
|
5
|
+
* (#63) and Classic (#59) — apply this SAME predicate, so their counts agree by construction; neither
|
|
6
|
+
* panel depends on the other, and the predicate lives here (the shared/provider layer) rather than in
|
|
7
|
+
* either. `now` is always injected, never read from `Date.now()` internally, so a caller's tests (and
|
|
8
|
+
* this module's own) are deterministic.
|
|
9
|
+
*/
|
|
10
|
+
export type FilterOp = "is" | "is-not";
|
|
11
|
+
/**
|
|
12
|
+
* A set-membership test: `is` matches when the record's value is in `values`; `is-not` when it is not.
|
|
13
|
+
* A value is a plain exact match (a label name, a login) UNLESS it is one of the tokens below (#464a):
|
|
14
|
+
* `@me`, `@unassigned`, `@bots`, `@any`, `@upto:<label>` -- which the predicate resolves against the record
|
|
15
|
+
* and a `MatchContext`. Any OTHER value, including one that merely starts with `@` (a label may be named
|
|
16
|
+
* `@needs-review`), is still the plain exact match it always was.
|
|
17
|
+
*/
|
|
18
|
+
export interface SetFilter {
|
|
19
|
+
op: FilterOp;
|
|
20
|
+
values: string[];
|
|
21
|
+
}
|
|
22
|
+
/** The viewer: an assignee/author login equal to `MatchContext.me`, compared ignoring case. */
|
|
23
|
+
export declare const FILTER_ME = "@me";
|
|
24
|
+
/** Nobody: on `assignee`, a record with no assignee; on `author`, a record with no author. */
|
|
25
|
+
export declare const FILTER_UNASSIGNED = "@unassigned";
|
|
26
|
+
/** A bot: an assignee/author whose `Person.isBot` is true -- GitHub's own flag, never a guess from the login. */
|
|
27
|
+
export declare const FILTER_BOTS = "@bots";
|
|
28
|
+
/** Any label of the axis's ladder (`priority`/`effort`/`type`). `is-not @any` is 1.x's "none". */
|
|
29
|
+
export declare const FILTER_ANY = "@any";
|
|
30
|
+
/**
|
|
31
|
+
* "At or before this rung of the ladder" (`priority`/`effort`): the record carries the label named
|
|
32
|
+
* `name` or any label the axis's ladder lists BEFORE it -- "High and above" on the priority ladder
|
|
33
|
+
* (critical first), "S and below" on the effort ladder (XS first): one rule, two wordings. `name` is the
|
|
34
|
+
* full label name, as the ladder carries it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function filterUpTo(name: string): string;
|
|
37
|
+
/** A filter value read as a token; `unknown` is an `@...` that is none of the tokens this version defines --
|
|
38
|
+
* the predicate compares it as a plain value (so it matches nothing unless a label/login is spelled so). */
|
|
39
|
+
export type FilterToken = {
|
|
40
|
+
kind: "me" | "unassigned" | "bots" | "any";
|
|
41
|
+
} | {
|
|
42
|
+
kind: "upto";
|
|
43
|
+
name: string;
|
|
44
|
+
} | {
|
|
45
|
+
kind: "unknown";
|
|
46
|
+
value: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* A value as a token, or `undefined` for a plain value (one not starting with `@`). For the UIs' chip
|
|
50
|
+
* labels; the predicate reads values through it too, so what a chip says and what a filter does cannot
|
|
51
|
+
* disagree about what is a token. An `@...` that is not one of the five tokens comes back as `unknown`,
|
|
52
|
+
* which the predicate treats as a plain value.
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseFilterToken(value: string): FilterToken | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* What a token needs from outside the record (#464a): the viewer's login for `@me`, and each ladder axis's
|
|
57
|
+
* label names in the standard's own order (critical first, XS first) for `@any` and `@upto:`. Every field
|
|
58
|
+
* is optional: a token that cannot be resolved without one it was not given matches nothing. Passed as
|
|
59
|
+
* `matchesIssue`'s optional fourth argument, so every three-argument caller behaves exactly as before.
|
|
60
|
+
*/
|
|
61
|
+
export interface MatchContext {
|
|
62
|
+
me?: string;
|
|
63
|
+
ladders?: {
|
|
64
|
+
priority?: string[];
|
|
65
|
+
effort?: string[];
|
|
66
|
+
type?: string[];
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** An age/activity bucket, measured from `now` against a record's `createdAt`/`updatedAt`. */
|
|
70
|
+
export type AgeBucket = "1d" | "1w" | "1m" | "3m" | "older";
|
|
71
|
+
export interface AgeFilter {
|
|
72
|
+
op: FilterOp;
|
|
73
|
+
buckets: AgeBucket[];
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Every axis is optional; an absent axis imposes no constraint. `priority`/`effort`/`type`/`tags` are
|
|
77
|
+
* all "the record has a label whose name is in the set" -- the UI groups labels into these ladders
|
|
78
|
+
* (from `labels.json`, #58), but for a plain value the predicate treats all four identically: a plain
|
|
79
|
+
* label-name test. A token (#464a) adds `@any` and `@upto:<rung>` on the ladder axes, resolved against
|
|
80
|
+
* `MatchContext.ladders`; `tags` has no ladder, so a token there matches nothing.
|
|
81
|
+
*/
|
|
82
|
+
export interface IssueFilter {
|
|
83
|
+
priority?: SetFilter;
|
|
84
|
+
effort?: SetFilter;
|
|
85
|
+
type?: SetFilter;
|
|
86
|
+
tags?: SetFilter;
|
|
87
|
+
/** Over `assignees[].login`; a record with no assignees matches `is-not` of any set of plain logins, never
|
|
88
|
+
* `is` -- `@unassigned` is the token that matches exactly that record, `@me`/`@bots` the viewer/bots. */
|
|
89
|
+
assignee?: SetFilter;
|
|
90
|
+
/** Over the single `author?.login`; `undefined` never matches `is` of a plain login, always matches
|
|
91
|
+
* `is-not` (`@unassigned` matches a record with no author; `@me`/`@bots` as for `assignee`). */
|
|
92
|
+
author?: SetFilter;
|
|
93
|
+
/** Bucket of `now - createdAt`. */
|
|
94
|
+
age?: AgeFilter;
|
|
95
|
+
/** Bucket of `now - updatedAt`. */
|
|
96
|
+
activity?: AgeFilter;
|
|
97
|
+
/** Title substring (case-insensitive) OR an exact `#<number>` match (a leading `#` is stripped). */
|
|
98
|
+
search?: string;
|
|
99
|
+
}
|
|
100
|
+
/** Classic's list sort order (#59b). Lives here (not in the Classic plugin) so a `SavedView`'s
|
|
101
|
+
* `sort` field and the panel's own sort state share ONE type instead of two independently-typed
|
|
102
|
+
* string unions that could drift. */
|
|
103
|
+
export type SortMode = "opened" | "priority" | "updated";
|
|
104
|
+
/**
|
|
105
|
+
* A saved view (#60): a named filter+sort bundle a picker applies in one click, replacing the whole
|
|
106
|
+
* filter state (never merging into it, per 1.x's own documented decision -- a leftover axis would
|
|
107
|
+
* make a view mean something different depending on what preceded it). Config, not localStorage:
|
|
108
|
+
* `views.yaml` (the issues-classic plugin's own loader) ships the 1.x five as defaults and is the
|
|
109
|
+
* source of truth once an operator edits the file.
|
|
110
|
+
*/
|
|
111
|
+
export interface SavedView {
|
|
112
|
+
name: string;
|
|
113
|
+
filter: IssueFilter;
|
|
114
|
+
sort: SortMode;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Pure AND across every present axis. An empty `filter` (`{}`) matches every record. `ctx` (#464a) is what
|
|
118
|
+
* the filter's tokens resolve against (the viewer, the ladders); omit it and every plain value behaves
|
|
119
|
+
* exactly as it always did, while a token that needs `ctx` matches nothing.
|
|
120
|
+
*/
|
|
121
|
+
export declare function matchesIssue(record: IssueRecord, filter: IssueFilter, now: number, ctx?: MatchContext): boolean;
|
|
122
|
+
//# sourceMappingURL=issue-filter.d.ts.map
|