@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,152 @@
|
|
|
1
|
+
// ---- tokens (#464a) ---------------------------------------------------------------------------------
|
|
2
|
+
//
|
|
3
|
+
// The person and ladder shortcuts 1.x's filter bar offers ("me", "unassigned", "bots", "High and above",
|
|
4
|
+
// "none"), carried inside the existing `SetFilter` so no new filter shape exists: a value is a token only
|
|
5
|
+
// when it is exactly one of the five spellings below, and everything else is the plain exact match it
|
|
6
|
+
// always was -- so every filter written before tokens existed means what it meant (a label named
|
|
7
|
+
// `@needs-review` still filters on `tags`), and an `@...` a LATER version defines matches nothing here
|
|
8
|
+
// unless a label or login is literally spelled that way. A label named exactly like a token (`@me`) cannot
|
|
9
|
+
// be selected as a plain value. A saved view (`views.yaml`) may use the tokens as-is.
|
|
10
|
+
/** The viewer: an assignee/author login equal to `MatchContext.me`, compared ignoring case. */
|
|
11
|
+
export const FILTER_ME = "@me";
|
|
12
|
+
/** Nobody: on `assignee`, a record with no assignee; on `author`, a record with no author. */
|
|
13
|
+
export const FILTER_UNASSIGNED = "@unassigned";
|
|
14
|
+
/** A bot: an assignee/author whose `Person.isBot` is true -- GitHub's own flag, never a guess from the login. */
|
|
15
|
+
export const FILTER_BOTS = "@bots";
|
|
16
|
+
/** Any label of the axis's ladder (`priority`/`effort`/`type`). `is-not @any` is 1.x's "none". */
|
|
17
|
+
export const FILTER_ANY = "@any";
|
|
18
|
+
const UPTO_PREFIX = "@upto:";
|
|
19
|
+
/**
|
|
20
|
+
* "At or before this rung of the ladder" (`priority`/`effort`): the record carries the label named
|
|
21
|
+
* `name` or any label the axis's ladder lists BEFORE it -- "High and above" on the priority ladder
|
|
22
|
+
* (critical first), "S and below" on the effort ladder (XS first): one rule, two wordings. `name` is the
|
|
23
|
+
* full label name, as the ladder carries it.
|
|
24
|
+
*/
|
|
25
|
+
export function filterUpTo(name) {
|
|
26
|
+
return `${UPTO_PREFIX}${name}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A value as a token, or `undefined` for a plain value (one not starting with `@`). For the UIs' chip
|
|
30
|
+
* labels; the predicate reads values through it too, so what a chip says and what a filter does cannot
|
|
31
|
+
* disagree about what is a token. An `@...` that is not one of the five tokens comes back as `unknown`,
|
|
32
|
+
* which the predicate treats as a plain value.
|
|
33
|
+
*/
|
|
34
|
+
export function parseFilterToken(value) {
|
|
35
|
+
if (!value.startsWith("@"))
|
|
36
|
+
return undefined;
|
|
37
|
+
if (value === FILTER_ME)
|
|
38
|
+
return { kind: "me" };
|
|
39
|
+
if (value === FILTER_UNASSIGNED)
|
|
40
|
+
return { kind: "unassigned" };
|
|
41
|
+
if (value === FILTER_BOTS)
|
|
42
|
+
return { kind: "bots" };
|
|
43
|
+
if (value === FILTER_ANY)
|
|
44
|
+
return { kind: "any" };
|
|
45
|
+
if (value.startsWith(UPTO_PREFIX) && value.length > UPTO_PREFIX.length) {
|
|
46
|
+
return { kind: "upto", name: value.slice(UPTO_PREFIX.length) };
|
|
47
|
+
}
|
|
48
|
+
return { kind: "unknown", value };
|
|
49
|
+
}
|
|
50
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
51
|
+
// Bucket boundaries are inclusive upper bounds ("1d" is "at most 24h old"): a record exactly 24h old
|
|
52
|
+
// is "1d", not "1w" -- ageBucket below uses `<=`, never `<`, at every boundary.
|
|
53
|
+
const BUCKET_BOUNDS_MS = [
|
|
54
|
+
["1d", 1 * DAY_MS],
|
|
55
|
+
["1w", 7 * DAY_MS],
|
|
56
|
+
["1m", 30 * DAY_MS],
|
|
57
|
+
["3m", 90 * DAY_MS],
|
|
58
|
+
];
|
|
59
|
+
/** The bucket of `now - isoTimestamp`, in ascending-boundary order; anything past the last bound is `older`. */
|
|
60
|
+
function ageBucket(isoTimestamp, now) {
|
|
61
|
+
const elapsed = now - Date.parse(isoTimestamp);
|
|
62
|
+
for (const [bucket, bound] of BUCKET_BOUNDS_MS) {
|
|
63
|
+
if (elapsed <= bound)
|
|
64
|
+
return bucket;
|
|
65
|
+
}
|
|
66
|
+
return "older";
|
|
67
|
+
}
|
|
68
|
+
/** `is` matches when ANY of the filter's values hits; `is-not` when none does. `hit` reads one value. */
|
|
69
|
+
function matchesSet(filter, hit) {
|
|
70
|
+
if (filter === undefined)
|
|
71
|
+
return true;
|
|
72
|
+
const present = filter.values.some(hit);
|
|
73
|
+
return filter.op === "is" ? present : !present;
|
|
74
|
+
}
|
|
75
|
+
/** Whether one value of a label axis hits a record's label names: a plain value (an `@...` that is none of
|
|
76
|
+
* the five tokens included) is exact membership; a token is resolved against the axis's ladder, and only
|
|
77
|
+
* `@any` (any ladder axis) and `@upto:` (priority and effort) mean anything here -- every other token, and
|
|
78
|
+
* any token at all on `tags`, hits nothing. */
|
|
79
|
+
function labelHit(axis, labelNames, ctx) {
|
|
80
|
+
const ladder = axis === "tags" ? undefined : ctx?.ladders?.[axis];
|
|
81
|
+
return (value) => {
|
|
82
|
+
const token = parseFilterToken(value);
|
|
83
|
+
if (token === undefined || token.kind === "unknown")
|
|
84
|
+
return labelNames.includes(value);
|
|
85
|
+
if (ladder === undefined)
|
|
86
|
+
return false;
|
|
87
|
+
if (token.kind === "any")
|
|
88
|
+
return labelNames.some((n) => ladder.includes(n));
|
|
89
|
+
if (token.kind === "upto" && axis !== "type") {
|
|
90
|
+
const rung = ladder.indexOf(token.name);
|
|
91
|
+
if (rung < 0)
|
|
92
|
+
return false;
|
|
93
|
+
return labelNames.some((n) => {
|
|
94
|
+
const at = ladder.indexOf(n);
|
|
95
|
+
return at >= 0 && at <= rung;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Whether one value of a person axis hits a record's people (its assignees, or its author alone): a plain
|
|
102
|
+
* value is an exact login; `@me`, `@unassigned` and `@bots` are resolved as `MatchContext`/`Person.isBot`
|
|
103
|
+
* say; the other two tokens (`@any`, `@upto:`) hit nothing here. An unknown `@...` is a plain login
|
|
104
|
+
* compare, which matches nothing (a GitHub login cannot start with `@`). */
|
|
105
|
+
function personHit(people, ctx) {
|
|
106
|
+
const me = ctx?.me === undefined || ctx.me === "" ? undefined : ctx.me.toLowerCase();
|
|
107
|
+
return (value) => {
|
|
108
|
+
const token = parseFilterToken(value);
|
|
109
|
+
if (token === undefined || token.kind === "unknown")
|
|
110
|
+
return people.some((p) => p.login === value);
|
|
111
|
+
if (token.kind === "me")
|
|
112
|
+
return me !== undefined && people.some((p) => p.login.toLowerCase() === me);
|
|
113
|
+
if (token.kind === "unassigned")
|
|
114
|
+
return people.length === 0;
|
|
115
|
+
if (token.kind === "bots")
|
|
116
|
+
return people.some((p) => p.isBot === true);
|
|
117
|
+
return false;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function matchesAge(bucket, filter) {
|
|
121
|
+
if (filter === undefined)
|
|
122
|
+
return true;
|
|
123
|
+
const present = filter.buckets.includes(bucket);
|
|
124
|
+
return filter.op === "is" ? present : !present;
|
|
125
|
+
}
|
|
126
|
+
function matchesSearch(record, query) {
|
|
127
|
+
if (query === undefined || query === "")
|
|
128
|
+
return true;
|
|
129
|
+
const trimmed = query.trim();
|
|
130
|
+
const asNumber = trimmed.startsWith("#") ? trimmed.slice(1) : trimmed;
|
|
131
|
+
if (asNumber !== "" && /^\d+$/.test(asNumber) && Number(asNumber) === record.number)
|
|
132
|
+
return true;
|
|
133
|
+
return record.title.toLowerCase().includes(trimmed.toLowerCase());
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Pure AND across every present axis. An empty `filter` (`{}`) matches every record. `ctx` (#464a) is what
|
|
137
|
+
* the filter's tokens resolve against (the viewer, the ladders); omit it and every plain value behaves
|
|
138
|
+
* exactly as it always did, while a token that needs `ctx` matches nothing.
|
|
139
|
+
*/
|
|
140
|
+
export function matchesIssue(record, filter, now, ctx) {
|
|
141
|
+
const labelNames = record.labels.map((l) => l.name);
|
|
142
|
+
return (matchesSet(filter.priority, labelHit("priority", labelNames, ctx)) &&
|
|
143
|
+
matchesSet(filter.effort, labelHit("effort", labelNames, ctx)) &&
|
|
144
|
+
matchesSet(filter.type, labelHit("type", labelNames, ctx)) &&
|
|
145
|
+
matchesSet(filter.tags, labelHit("tags", labelNames, ctx)) &&
|
|
146
|
+
matchesSet(filter.assignee, personHit(record.assignees, ctx)) &&
|
|
147
|
+
matchesSet(filter.author, personHit(record.author ? [record.author] : [], ctx)) &&
|
|
148
|
+
matchesAge(ageBucket(record.createdAt, now), filter.age) &&
|
|
149
|
+
matchesAge(ageBucket(record.updatedAt, now), filter.activity) &&
|
|
150
|
+
matchesSearch(record, filter.search));
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=issue-filter.js.map
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `acPlugin` manifest block and its contribution points — the data the host validates before
|
|
3
|
+
* loading any plugin code (design.md §4). This file is the JSON-Schema generation target
|
|
4
|
+
* (`gen-schema.mjs` -> `schema.json`), so keep it self-contained (no relative imports) and every
|
|
5
|
+
* shape a plain data object. Field names are shared verbatim with the discord-bot index
|
|
6
|
+
* (`hostApiVersion`, `env[]`).
|
|
7
|
+
*/
|
|
8
|
+
/** How the host runs a plugin. */
|
|
9
|
+
export type PluginKind = "in-process" | "sidecar";
|
|
10
|
+
/** A contributed nav tab (panel). */
|
|
11
|
+
export interface PanelContribution {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
/** Route the panel mounts at, e.g. "/board". */
|
|
15
|
+
route: string;
|
|
16
|
+
/** Named nav slot this panel wants to own (e.g. "issues"); optional. */
|
|
17
|
+
nav?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Where this panel wants to sit among the tabs (#472): a finite number, lower first, after the fixed
|
|
20
|
+
* `Home` tab. Unset keeps the panel after every ordered one, in the existing order (plugin directory
|
|
21
|
+
* name, then declaration order), so declaring `order` on one panel never moves a panel that did not.
|
|
22
|
+
* A hint, not a claim: the operator's `tab_order` setting outranks it, and among the ordered panels an
|
|
23
|
+
* `installed` plugin's never precedes a `bundled` plugin's, so a third-party package declaring a small
|
|
24
|
+
* number cannot jump ahead of the bundled ordered panels (it still precedes every panel that declares
|
|
25
|
+
* no `order`). Additive; a manifest without it validates as before.
|
|
26
|
+
*/
|
|
27
|
+
order?: number;
|
|
28
|
+
/** The export name in the plugin's frontend bundle. */
|
|
29
|
+
export: string;
|
|
30
|
+
}
|
|
31
|
+
/** A contributed Overview card. */
|
|
32
|
+
export interface CardContribution {
|
|
33
|
+
id: string;
|
|
34
|
+
title: string;
|
|
35
|
+
/** Allowed grid sizes, e.g. ["2x1", "3x1"]. */
|
|
36
|
+
sizes: string[];
|
|
37
|
+
/** The export name in the plugin's frontend bundle. */
|
|
38
|
+
export: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Input control kind for an action field; the shell renders each `fields` entry by its `type`.
|
|
42
|
+
* `multiselect` (#463) is a set of choices whose submitted value is a `string[]`; `hidden` (#463) is a
|
|
43
|
+
* value with no control at all -- a snapshot the operator must not type into (the seed, the stored draft
|
|
44
|
+
* or the default is submitted unchanged). Both are additive; every earlier type behaves as before.
|
|
45
|
+
*/
|
|
46
|
+
export type ActionFieldType = "string" | "text" | "number" | "boolean" | "select" | "multiselect" | "hidden";
|
|
47
|
+
/**
|
|
48
|
+
* Where a `select` / `multiselect` field's choices come from when they are not a fixed `options` list
|
|
49
|
+
* (#463): a `GET` route the DECLARING plugin itself registers, answering an `ActionOptionsResponse`
|
|
50
|
+
* (`action-options.ts`). The host validates it at manifest-load time (`plugins/validate.ts`).
|
|
51
|
+
*/
|
|
52
|
+
export interface ActionOptionsSource {
|
|
53
|
+
/**
|
|
54
|
+
* The route, relative to the declaring plugin's own namespace: the shell fetches
|
|
55
|
+
* `/api/x/<plugin-id><route>`. It must start with `/`, carry no query string, fragment, `..`, `//`, `%`
|
|
56
|
+
* or `\`, and be one of the plugin's own declared `GET` routes (`contributes.routes`).
|
|
57
|
+
*/
|
|
58
|
+
route: string;
|
|
59
|
+
/**
|
|
60
|
+
* Names of OTHER fields of the same action whose current values are sent as query parameters (so a
|
|
61
|
+
* route can answer for the repo the operator has typed, say). The route is not fetched while any of
|
|
62
|
+
* them is empty, and is fetched again when one changes.
|
|
63
|
+
*/
|
|
64
|
+
params?: string[];
|
|
65
|
+
}
|
|
66
|
+
/** One field of an action's form; the shell builds a labelled control from it. */
|
|
67
|
+
export interface ActionField {
|
|
68
|
+
/** Key the submitted value is collected under. */
|
|
69
|
+
name: string;
|
|
70
|
+
label: string;
|
|
71
|
+
type: ActionFieldType;
|
|
72
|
+
/**
|
|
73
|
+
* Choices for a `select`. A bare string is both value and label; the object form allows a distinct
|
|
74
|
+
* display label (so a later value≠label select is additive, not a breaking `string[]`→object change).
|
|
75
|
+
*/
|
|
76
|
+
options?: (string | {
|
|
77
|
+
value: string;
|
|
78
|
+
label: string;
|
|
79
|
+
})[];
|
|
80
|
+
/**
|
|
81
|
+
* Choices fetched from a plugin route instead (#463), for a `select` or `multiselect` only -- the
|
|
82
|
+
* validator rejects it on any other type, and beside a non-empty `options` (one source of choices,
|
|
83
|
+
* not two). When the route cannot be loaded the shell degrades the field
|
|
84
|
+
* to a text box (a `multiselect`'s text is a comma-separated list) and shows the reason, so an action
|
|
85
|
+
* is never blocked by its picker. Additive.
|
|
86
|
+
*/
|
|
87
|
+
optionsFrom?: ActionOptionsSource;
|
|
88
|
+
}
|
|
89
|
+
/** A confirm-gated action; the shell builds its button + modal from these fields (#34). */
|
|
90
|
+
export interface ActionContribution {
|
|
91
|
+
id: string;
|
|
92
|
+
/** Button/label text; the shell falls back to `id` when omitted. */
|
|
93
|
+
title?: string;
|
|
94
|
+
/** What the action operates on, e.g. "issue" | "report" | "global". */
|
|
95
|
+
scope: string;
|
|
96
|
+
confirm?: boolean;
|
|
97
|
+
danger?: boolean;
|
|
98
|
+
/** The form the shell builds the action's modal from; omit for a no-input action. */
|
|
99
|
+
fields?: ActionField[];
|
|
100
|
+
}
|
|
101
|
+
/** Where a contributed job runs: in the host, or fanned out to the paired agents (E4). */
|
|
102
|
+
export type JobRunOn = "host" | "agent";
|
|
103
|
+
/** A scheduled job. */
|
|
104
|
+
export interface JobContribution {
|
|
105
|
+
id: string;
|
|
106
|
+
/** 5-field cron expression. */
|
|
107
|
+
schedule: string;
|
|
108
|
+
/** Per-run timeout in seconds. */
|
|
109
|
+
timeoutSec: number;
|
|
110
|
+
runOn: JobRunOn;
|
|
111
|
+
/** A `runOn: "agent"` job is not dispatched to a machine whose paired agent version compares lower
|
|
112
|
+
* than this (#372, #80); the run row records the skip with its reason instead. Ignored for a
|
|
113
|
+
* `runOn: "host"` job. Additive; absent means every paired machine is always dispatched, as
|
|
114
|
+
* before this field existed. */
|
|
115
|
+
minAgentVersion?: string;
|
|
116
|
+
}
|
|
117
|
+
/** HTTP method for a contributed route. */
|
|
118
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
119
|
+
/** An HTTP route; the host mounts it under `/api/x/<plugin-id>/<path>`. */
|
|
120
|
+
export interface RouteContribution {
|
|
121
|
+
method: HttpMethod;
|
|
122
|
+
/** Path under the plugin's namespace, e.g. "/feed". */
|
|
123
|
+
path: string;
|
|
124
|
+
}
|
|
125
|
+
/** JSON scalar type of a plugin setting (rendered by Admin). */
|
|
126
|
+
export type SettingType = "string" | "integer" | "number" | "boolean";
|
|
127
|
+
/** A plugin setting. */
|
|
128
|
+
export interface SettingContribution {
|
|
129
|
+
key: string;
|
|
130
|
+
type: SettingType;
|
|
131
|
+
default?: string | number | boolean;
|
|
132
|
+
}
|
|
133
|
+
/** A theme contributed as token JSON only (no code). */
|
|
134
|
+
export interface ThemeContribution {
|
|
135
|
+
id: string;
|
|
136
|
+
title?: string;
|
|
137
|
+
}
|
|
138
|
+
/** A named, versioned slot this plugin provides or consumes. */
|
|
139
|
+
export interface SlotRef {
|
|
140
|
+
/** The slot name, one of the `SLOTS` ids, e.g. "issues". */
|
|
141
|
+
slot: string;
|
|
142
|
+
/** Integer slot version; the host resolves provider/consumer by exact equality. */
|
|
143
|
+
version: number;
|
|
144
|
+
}
|
|
145
|
+
/** The contribution points a plugin offers. Every field is optional; a plugin contributes a subset. */
|
|
146
|
+
export interface Contributes {
|
|
147
|
+
panels?: PanelContribution[];
|
|
148
|
+
cards?: CardContribution[];
|
|
149
|
+
actions?: ActionContribution[];
|
|
150
|
+
jobs?: JobContribution[];
|
|
151
|
+
routes?: RouteContribution[];
|
|
152
|
+
settings?: SettingContribution[];
|
|
153
|
+
themes?: ThemeContribution[];
|
|
154
|
+
provides?: SlotRef[];
|
|
155
|
+
consumes?: SlotRef[];
|
|
156
|
+
/** #78: the plugin serves `GET /api/x/<id>/attention` -> `{ ok: true, items: AttentionItem[] }`
|
|
157
|
+
* (`attention.ts`). The `overview` plugin's rollup card discovers contributors via
|
|
158
|
+
* `PluginView.attention` (`views.ts`) rather than a second manifest read. Additive; always `true`
|
|
159
|
+
* when present (a boolean, not a route list, since the path is fixed). */
|
|
160
|
+
attention?: true;
|
|
161
|
+
}
|
|
162
|
+
/** A declared environment variable (field names shared verbatim with the discord-bot index). */
|
|
163
|
+
export interface EnvVar {
|
|
164
|
+
key: string;
|
|
165
|
+
/** Anchored regex the value must match, e.g. "^[0-9]+$". */
|
|
166
|
+
format?: string;
|
|
167
|
+
required?: boolean;
|
|
168
|
+
secret?: boolean;
|
|
169
|
+
}
|
|
170
|
+
/** A sidecar plugin's image and health path. Present when `kind === "sidecar"`. */
|
|
171
|
+
export interface SidecarSpec {
|
|
172
|
+
image: string;
|
|
173
|
+
healthPath: string;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* The `acPlugin` block of a plugin's package.json — everything the host needs before it loads any
|
|
177
|
+
* code. The host validates this against the generated JSON Schema (issue #26). `hostApiVersion` is
|
|
178
|
+
* an integer that must equal the host's `HOST_API_VERSION`.
|
|
179
|
+
*/
|
|
180
|
+
export interface AcPluginManifest {
|
|
181
|
+
/** Permanent id: state key, route namespace (`/api/x/<id>/`), log prefix. Never rename. */
|
|
182
|
+
id: string;
|
|
183
|
+
/** Integer, equal to the host's HOST_API_VERSION. */
|
|
184
|
+
hostApiVersion: number;
|
|
185
|
+
kind: PluginKind;
|
|
186
|
+
/** Server entry exporting `createPlugin(host)`; required for `kind: "in-process"`. */
|
|
187
|
+
server?: string;
|
|
188
|
+
/** Frontend ESM entry; omitted by a plugin that contributes no UI. */
|
|
189
|
+
frontend?: string;
|
|
190
|
+
/** Image + health path; required when `kind: "sidecar"`. */
|
|
191
|
+
sidecar?: SidecarSpec;
|
|
192
|
+
/** Host-agent capabilities the plugin may use, each granted per machine in Admin (E4). */
|
|
193
|
+
capabilities?: string[];
|
|
194
|
+
contributes?: Contributes;
|
|
195
|
+
env?: EnvVar[];
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The host-side state the host keeps per plugin (design.md §3). Not part of the manifest; lives in
|
|
199
|
+
* SQLite and is surfaced by Admin. A plugin being `enabled` is distinct from being configured — an
|
|
200
|
+
* enabled-but-misconfigured plugin loads and reports its `error`, it is not dropped.
|
|
201
|
+
*/
|
|
202
|
+
export interface PluginStateEntry {
|
|
203
|
+
id: string;
|
|
204
|
+
enabled: boolean;
|
|
205
|
+
/** The version currently loaded, if the plugin came from an index. */
|
|
206
|
+
installedVersion?: string;
|
|
207
|
+
/** The last load/validation/activation error, if any (shown by Admin, never fatal to the host). */
|
|
208
|
+
error?: string;
|
|
209
|
+
/** Host-agent capabilities granted to this plugin, by machine (E4). Unused; grants live in
|
|
210
|
+
* `/api/agents`, #50. */
|
|
211
|
+
grantedCapabilities?: string[];
|
|
212
|
+
/** For a `kind: "sidecar"` plugin, its health as of the last poll (#29); absent for in-process. */
|
|
213
|
+
sidecar?: {
|
|
214
|
+
state: "healthy" | "degraded";
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=manifest.d.ts.map
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `acPlugin` manifest block and its contribution points — the data the host validates before
|
|
3
|
+
* loading any plugin code (design.md §4). This file is the JSON-Schema generation target
|
|
4
|
+
* (`gen-schema.mjs` -> `schema.json`), so keep it self-contained (no relative imports) and every
|
|
5
|
+
* shape a plain data object. Field names are shared verbatim with the discord-bot index
|
|
6
|
+
* (`hostApiVersion`, `env[]`).
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 16 1.x `OV_WIDGETS` ids (E7 #77), ported byte-identical from
|
|
3
|
+
* `frontend/src/layout.ts`'s own `id`/`label`/`h` (its `size` field is always `"third"` in 1.x and
|
|
4
|
+
* has no 2.0 equivalent -- height tiers are `h` alone here). Each row also names its 2.0 OWNER
|
|
5
|
+
* plugin (plan §5 decision 1): the registry id `HomeCards` actually renders is
|
|
6
|
+
* `plugin:<owner>:<id>` (`ovRegistryId` below), never the bare 1.x id -- a saved 1.x
|
|
7
|
+
* `dashboard-layout.json` needs the one-time upgrade in `shell/layout.ts`'s `upgradeLegacyLayout`
|
|
8
|
+
* to round-trip. RENAMING an `id` here orphans every stored layout that references it, the same as
|
|
9
|
+
* 1.x's own comment warns; adding/reordering rows is safe.
|
|
10
|
+
*/
|
|
11
|
+
export type OverviewOwner = "overview" | "metrics-claude" | "repos" | "artifacts" | "provider-github";
|
|
12
|
+
export interface OverviewWidget {
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
owner: OverviewOwner;
|
|
16
|
+
h: 1 | 2 | 3;
|
|
17
|
+
}
|
|
18
|
+
export declare const OV_WIDGETS: readonly OverviewWidget[];
|
|
19
|
+
/** The registry id `HomeCards` actually renders for one `OV_WIDGETS` row: `plugin:<owner>:<id>`. */
|
|
20
|
+
export declare function ovRegistryId(widget: OverviewWidget): string;
|
|
21
|
+
//# sourceMappingURL=ov-widgets.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 16 1.x `OV_WIDGETS` ids (E7 #77), ported byte-identical from
|
|
3
|
+
* `frontend/src/layout.ts`'s own `id`/`label`/`h` (its `size` field is always `"third"` in 1.x and
|
|
4
|
+
* has no 2.0 equivalent -- height tiers are `h` alone here). Each row also names its 2.0 OWNER
|
|
5
|
+
* plugin (plan §5 decision 1): the registry id `HomeCards` actually renders is
|
|
6
|
+
* `plugin:<owner>:<id>` (`ovRegistryId` below), never the bare 1.x id -- a saved 1.x
|
|
7
|
+
* `dashboard-layout.json` needs the one-time upgrade in `shell/layout.ts`'s `upgradeLegacyLayout`
|
|
8
|
+
* to round-trip. RENAMING an `id` here orphans every stored layout that references it, the same as
|
|
9
|
+
* 1.x's own comment warns; adding/reordering rows is safe.
|
|
10
|
+
*/
|
|
11
|
+
export const OV_WIDGETS = [
|
|
12
|
+
{ id: "attention", label: "Needs attention", owner: "overview", h: 2 },
|
|
13
|
+
{ id: "actions", label: "Action ledger", owner: "artifacts", h: 2 },
|
|
14
|
+
{ id: "sched", label: "Scheduled tasks", owner: "overview", h: 2 },
|
|
15
|
+
{ id: "recent", label: "Recent activity", owner: "artifacts", h: 2 },
|
|
16
|
+
{ id: "trend", label: "Store growth", owner: "artifacts", h: 2 },
|
|
17
|
+
{ id: "reviews", label: "Code review status", owner: "artifacts", h: 2 },
|
|
18
|
+
{ id: "prs", label: "PR review & auto-merge", owner: "provider-github", h: 3 },
|
|
19
|
+
{ id: "issues", label: "Open issues", owner: "provider-github", h: 3 },
|
|
20
|
+
{ id: "projects", label: "Build & release status", owner: "provider-github", h: 3 },
|
|
21
|
+
{ id: "actions-usage", label: "Actions usage", owner: "provider-github", h: 3 },
|
|
22
|
+
{ id: "context-pressure", label: "Context pressure", owner: "metrics-claude", h: 3 },
|
|
23
|
+
{ id: "memory-footprint", label: "Memory footprint", owner: "metrics-claude", h: 3 },
|
|
24
|
+
{ id: "subagent-cost", label: "Subagent cost", owner: "metrics-claude", h: 3 },
|
|
25
|
+
{ id: "latest", label: "Latest by category", owner: "artifacts", h: 3 },
|
|
26
|
+
{ id: "distribution", label: "Category distribution", owner: "artifacts", h: 3 },
|
|
27
|
+
{ id: "repo-currency", label: "Repo currency", owner: "repos", h: 3 },
|
|
28
|
+
];
|
|
29
|
+
/** The registry id `HomeCards` actually renders for one `OV_WIDGETS` row: `plugin:<owner>:<id>`. */
|
|
30
|
+
export function ovRegistryId(widget) {
|
|
31
|
+
return `plugin:${widget.owner}:${widget.id}`;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=ov-widgets.js.map
|
package/dist/slots.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four composition slots v2.0 defines (design.md §4). A slot is a named, versioned TypeScript
|
|
3
|
+
* interface one plugin `provides` and others `consume`; the host refuses to activate a consumer
|
|
4
|
+
* whose slot has no provider and says why (issue #30). The slot names/versions live in `version.ts`
|
|
5
|
+
* (`SLOTS`) and are irreversible.
|
|
6
|
+
*
|
|
7
|
+
* These are the v1 seams. The rich record shapes are enriched additively by the consuming epics —
|
|
8
|
+
* `issues@1` by E5 (`ac-provider-github`, #56), `artifacts@1` by E6 (`ac-artifacts`, #68) — so add
|
|
9
|
+
* fields backward-compatibly and bump the slot version for a breaking change.
|
|
10
|
+
*
|
|
11
|
+
* `issues@1` was enriched by 56a (E5 #56): `labels`/`assignees` grew from bare strings into
|
|
12
|
+
* `Label`/`Person` records (colour for the Board's hex chips, avatar for people), and `IssuesProvider`
|
|
13
|
+
* gained `listPullRequests`/`counts` -- still `issues@1`, no shipped consumer existed yet (panels are
|
|
14
|
+
* built later in E5), so completing the v1 shape here is not a breaking change.
|
|
15
|
+
*/
|
|
16
|
+
/** A forge-agnostic reference to a repository (design §4: forge-agnostic records). */
|
|
17
|
+
export interface RepoRef {
|
|
18
|
+
owner: string;
|
|
19
|
+
name: string;
|
|
20
|
+
}
|
|
21
|
+
/** A GitHub label, carrying its colour for the Board's hex chips. `color` is the 6-hex RGB with no '#'.
|
|
22
|
+
* `description` is additive (#86c-labels): populated for a label sourced from the org standard
|
|
23
|
+
* (labels.json), omitted for a label sourced from an issue/PR's own `labels[]` -- neither existing
|
|
24
|
+
* consumer (the Board's chips, the issue/PR label lists) needs it, so it stays optional rather than
|
|
25
|
+
* forcing every `mapLabels` call site to fetch it. */
|
|
26
|
+
export interface Label {
|
|
27
|
+
name: string;
|
|
28
|
+
color: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
}
|
|
31
|
+
/** A GitHub account (assignee/author). */
|
|
32
|
+
export interface Person {
|
|
33
|
+
login: string;
|
|
34
|
+
avatarUrl?: string;
|
|
35
|
+
/** GitHub's own flag for a bot account (`type === "Bot"`): true for one, OMITTED for everyone else (never
|
|
36
|
+
* `false`, so existing records and fixtures are unchanged). Never guessed from the login (#464a). */
|
|
37
|
+
isBot?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** A forge-agnostic issue record (v1 seam; E5 #56 enriches). */
|
|
40
|
+
export interface IssueRecord {
|
|
41
|
+
repo: RepoRef;
|
|
42
|
+
number: number;
|
|
43
|
+
title: string;
|
|
44
|
+
state: "open" | "closed";
|
|
45
|
+
labels: Label[];
|
|
46
|
+
assignees: Person[];
|
|
47
|
+
author?: Person;
|
|
48
|
+
commentsCount: number;
|
|
49
|
+
createdAt: string;
|
|
50
|
+
updatedAt: string;
|
|
51
|
+
url: string;
|
|
52
|
+
}
|
|
53
|
+
/** An open/closed pull request. `body` is kept for the closes-#N priority inference (pulse priority.ts). */
|
|
54
|
+
export interface PullRequestRecord {
|
|
55
|
+
repo: RepoRef;
|
|
56
|
+
number: number;
|
|
57
|
+
title: string;
|
|
58
|
+
state: "open" | "closed";
|
|
59
|
+
body: string;
|
|
60
|
+
labels: Label[];
|
|
61
|
+
author?: Person;
|
|
62
|
+
createdAt: string;
|
|
63
|
+
updatedAt: string;
|
|
64
|
+
url: string;
|
|
65
|
+
}
|
|
66
|
+
/** Open/closed issue counts for a repo. */
|
|
67
|
+
export interface IssueCounts {
|
|
68
|
+
open: number;
|
|
69
|
+
closed: number;
|
|
70
|
+
}
|
|
71
|
+
/** Latest repo-level CI/Pages signals for the Board (E5 #64). A null conclusion = not applicable
|
|
72
|
+
* (no workflows / no Pages) — the Board renders a transparent CI border and a neutral globe, never red. */
|
|
73
|
+
export interface RepoSignals {
|
|
74
|
+
/** Latest Actions workflow-run conclusion ("success"|"failure"|"cancelled"|…), or null if the repo has no runs. */
|
|
75
|
+
workflowConclusion: string | null;
|
|
76
|
+
/** Latest GitHub Pages build conclusion ("built"|"errored"|…), or null if Pages is not configured. */
|
|
77
|
+
pagesConclusion: string | null;
|
|
78
|
+
}
|
|
79
|
+
/** The org label standard, read live from Rackbops/Tooling's labels.json (E5 #58). Arrays preserve
|
|
80
|
+
* labels.json order (the filter pickers render in this order). `exclusive` names the single-select groups. */
|
|
81
|
+
export interface LabelStandard {
|
|
82
|
+
priority: Label[];
|
|
83
|
+
effort: Label[];
|
|
84
|
+
type: Label[];
|
|
85
|
+
exclusive: string[];
|
|
86
|
+
}
|
|
87
|
+
/** `issues@1`: list/search/labels/people/mutate over forge-agnostic records. */
|
|
88
|
+
export interface IssuesProvider {
|
|
89
|
+
/** List issues for a repo in a given state. */
|
|
90
|
+
listIssues(repo: RepoRef, state: "open" | "closed" | "all"): Promise<IssueRecord[]>;
|
|
91
|
+
/** List pull requests for a repo in a given state. */
|
|
92
|
+
listPullRequests(repo: RepoRef, state: "open" | "closed" | "all"): Promise<PullRequestRecord[]>;
|
|
93
|
+
/** Open/closed issue counts for a repo. */
|
|
94
|
+
counts(repo: RepoRef): Promise<IssueCounts>;
|
|
95
|
+
/** Latest workflow-run + Pages-run signals for a repo (E5 #64). */
|
|
96
|
+
signals(repo: RepoRef): Promise<RepoSignals>;
|
|
97
|
+
/** The org label standard, read live from Rackbops/Tooling's labels.json (E5 #58). */
|
|
98
|
+
standard(): Promise<LabelStandard>;
|
|
99
|
+
/** A repo's own labels, independent of the standard (E5 #58). */
|
|
100
|
+
repoLabels(repo: RepoRef): Promise<Label[]>;
|
|
101
|
+
}
|
|
102
|
+
/** A node in the artifacts report tree (v1 seam; E6 #68 enriches). */
|
|
103
|
+
export interface ArtifactNode {
|
|
104
|
+
id: string;
|
|
105
|
+
title: string;
|
|
106
|
+
kind: "category" | "run" | "report";
|
|
107
|
+
children?: ArtifactNode[];
|
|
108
|
+
}
|
|
109
|
+
/** `artifacts@1`: tree/read/search/render-status. */
|
|
110
|
+
export interface ArtifactsProvider {
|
|
111
|
+
/** The report tree. */
|
|
112
|
+
tree(): Promise<ArtifactNode[]>;
|
|
113
|
+
/** Raw source of one report by id. */
|
|
114
|
+
read(id: string): Promise<string>;
|
|
115
|
+
}
|
|
116
|
+
/** An authenticated identity. */
|
|
117
|
+
export interface Identity {
|
|
118
|
+
id: string;
|
|
119
|
+
email?: string;
|
|
120
|
+
name?: string;
|
|
121
|
+
}
|
|
122
|
+
/** `auth@1`: resolve the identity for a request, or null in open mode. */
|
|
123
|
+
export interface AuthProvider {
|
|
124
|
+
/** Resolve the identity from a request's headers, or null when unauthenticated. */
|
|
125
|
+
identify(headers: Record<string, string | undefined>): Promise<Identity | null>;
|
|
126
|
+
}
|
|
127
|
+
/** Severity of a notification, for styling/routing. */
|
|
128
|
+
export type NotifySeverity = "info" | "warning" | "error";
|
|
129
|
+
/** A message to fan out through a notify provider. */
|
|
130
|
+
export interface NotifyMessage {
|
|
131
|
+
title: string;
|
|
132
|
+
body: string;
|
|
133
|
+
severity?: NotifySeverity;
|
|
134
|
+
}
|
|
135
|
+
/** `notify@1`: fan out a message to this provider's channel. */
|
|
136
|
+
export interface NotifyProvider {
|
|
137
|
+
send(message: NotifyMessage): Promise<void>;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=slots.d.ts.map
|
package/dist/slots.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four composition slots v2.0 defines (design.md §4). A slot is a named, versioned TypeScript
|
|
3
|
+
* interface one plugin `provides` and others `consume`; the host refuses to activate a consumer
|
|
4
|
+
* whose slot has no provider and says why (issue #30). The slot names/versions live in `version.ts`
|
|
5
|
+
* (`SLOTS`) and are irreversible.
|
|
6
|
+
*
|
|
7
|
+
* These are the v1 seams. The rich record shapes are enriched additively by the consuming epics —
|
|
8
|
+
* `issues@1` by E5 (`ac-provider-github`, #56), `artifacts@1` by E6 (`ac-artifacts`, #68) — so add
|
|
9
|
+
* fields backward-compatibly and bump the slot version for a breaking change.
|
|
10
|
+
*
|
|
11
|
+
* `issues@1` was enriched by 56a (E5 #56): `labels`/`assignees` grew from bare strings into
|
|
12
|
+
* `Label`/`Person` records (colour for the Board's hex chips, avatar for people), and `IssuesProvider`
|
|
13
|
+
* gained `listPullRequests`/`counts` -- still `issues@1`, no shipped consumer existed yet (panels are
|
|
14
|
+
* built later in E5), so completing the v1 shape here is not a breaking change.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=slots.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version constants for the plugin contract. These are additive-only, irreversible identifiers:
|
|
3
|
+
* a plugin's manifest declares `hostApiVersion` and the four slot names, and a deployed plugin
|
|
4
|
+
* resolves against them, so a bump or rename must be deliberate (pinned by `version.test.ts`).
|
|
5
|
+
*/
|
|
6
|
+
/** The host API version. A plugin's manifest `hostApiVersion` must equal this (integer equality). */
|
|
7
|
+
export declare const HOST_API_VERSION = 1;
|
|
8
|
+
/**
|
|
9
|
+
* The four composition slots v2.0 ships, each a `name@version`. The `name` strings are the ids a
|
|
10
|
+
* plugin puts in `contributes.provides` / `contributes.consumes`; renaming one orphans every
|
|
11
|
+
* plugin that named it, so they are treated as irreversible (CLAUDE.md, "Irreversible identifiers").
|
|
12
|
+
*/
|
|
13
|
+
export declare const SLOTS: {
|
|
14
|
+
readonly issues: {
|
|
15
|
+
readonly name: "issues";
|
|
16
|
+
readonly version: 1;
|
|
17
|
+
};
|
|
18
|
+
readonly artifacts: {
|
|
19
|
+
readonly name: "artifacts";
|
|
20
|
+
readonly version: 1;
|
|
21
|
+
};
|
|
22
|
+
readonly auth: {
|
|
23
|
+
readonly name: "auth";
|
|
24
|
+
readonly version: 1;
|
|
25
|
+
};
|
|
26
|
+
readonly notify: {
|
|
27
|
+
readonly name: "notify";
|
|
28
|
+
readonly version: 1;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version constants for the plugin contract. These are additive-only, irreversible identifiers:
|
|
3
|
+
* a plugin's manifest declares `hostApiVersion` and the four slot names, and a deployed plugin
|
|
4
|
+
* resolves against them, so a bump or rename must be deliberate (pinned by `version.test.ts`).
|
|
5
|
+
*/
|
|
6
|
+
/** The host API version. A plugin's manifest `hostApiVersion` must equal this (integer equality). */
|
|
7
|
+
export const HOST_API_VERSION = 1;
|
|
8
|
+
/**
|
|
9
|
+
* The four composition slots v2.0 ships, each a `name@version`. The `name` strings are the ids a
|
|
10
|
+
* plugin puts in `contributes.provides` / `contributes.consumes`; renaming one orphans every
|
|
11
|
+
* plugin that named it, so they are treated as irreversible (CLAUDE.md, "Irreversible identifiers").
|
|
12
|
+
*/
|
|
13
|
+
export const SLOTS = {
|
|
14
|
+
issues: { name: "issues", version: 1 },
|
|
15
|
+
artifacts: { name: "artifacts", version: 1 },
|
|
16
|
+
auth: { name: "auth", version: 1 },
|
|
17
|
+
notify: { name: "notify", version: 1 },
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=version.js.map
|