@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.
@@ -0,0 +1,262 @@
1
+ import type { ActionField, JobRunOn } from "./manifest.js";
2
+ /**
3
+ * The `GET /api/plugins` and `GET /api/cards` response item shapes (#32/#33) — additive-only wire
4
+ * contracts (design.md's additive-only rule) shared by the host (which builds them,
5
+ * `packages/host/src/plugins/view.ts` / `packages/host/src/cards/config-cards.ts`) and the shell
6
+ * (which renders them, `packages/ui-shell/src/plugins.ts` / `packages/ui-shell/src/cards/`), so the
7
+ * two sides cannot silently drift on a field (#148). Not part of the plugin manifest schema
8
+ * (`gen-schema.mjs` generates only from `AcPluginManifest` in `./manifest.js`).
9
+ */
10
+ export interface PluginPanelView {
11
+ id: string;
12
+ title: string;
13
+ route: string;
14
+ nav?: string;
15
+ export: string;
16
+ navOwner: boolean;
17
+ /**
18
+ * This panel's position among every panel in the response (#472): a non-negative integer, lower
19
+ * first, resolved by the host per request from the operator's `tab_order` setting, then each
20
+ * manifest's `order`, then the existing order (`packages/host/src/plugins/rank.ts`). The tab strip,
21
+ * the nav drawer and the quickcast panel list all sort by it, so the shell needs no second fetch and
22
+ * the three surfaces cannot disagree. A shell reading a response with no `rank` (an older host) keeps
23
+ * the order it arrived in.
24
+ */
25
+ rank: number;
26
+ }
27
+ export interface PluginCardView {
28
+ id: string;
29
+ title: string;
30
+ sizes: string[];
31
+ export: string;
32
+ }
33
+ /**
34
+ * A confirm-gated action the shell builds a button + form from (#34). Unlike panels/cards, an action is
35
+ * pure data (no `export`), so it is surfaced for any loadable plugin regardless of whether it ships a
36
+ * frontend bundle. `confirm`/`danger` are normalised to booleans and `fields` to an array for the shell.
37
+ */
38
+ export interface PluginActionView {
39
+ id: string;
40
+ title: string;
41
+ scope: string;
42
+ confirm: boolean;
43
+ danger: boolean;
44
+ fields: ActionField[];
45
+ }
46
+ /**
47
+ * A job this plugin declares (#83) — the sched mini card's own source, since no Admin jobs table
48
+ * exists yet (E8). Like actions, pure data (no `export`), so it is surfaced for any loadable plugin
49
+ * regardless of whether it ships a frontend bundle. `runOn` is carried because `GET /api/jobs/
50
+ * :pluginId/:jobId/runs`'s response already exposes it per call, and this card's row model
51
+ * (`SchedRow.runOn`) plumbs it through end to end even though its current render doesn't surface it
52
+ * (found in review, round 2 — a candidate for a future small UI addition, not a bug).
53
+ * `timeoutSec`/`minAgentVersion` are deliberately left off instead, independent of that: the runs
54
+ * route's `skipped` reason already reflects a `minAgentVersion` gate as a per-machine fact, so
55
+ * duplicating the threshold itself here would have no reader.
56
+ */
57
+ export interface PluginJobView {
58
+ id: string;
59
+ /** 5-field cron expression, exactly as declared — no parsing/formatting here. */
60
+ schedule: string;
61
+ runOn: JobRunOn;
62
+ }
63
+ /**
64
+ * Where a plugin was discovered from (#183): `bundled` under the image's own `plugins/` dir, `installed`
65
+ * under an operator-installed-plugins dir. Derived from the scan root a plugin was found under, never
66
+ * from the manifest — a plugin cannot declare itself first-party. Until the install path exists (E2's
67
+ * #35 follow-up) every plugin is `bundled`.
68
+ */
69
+ export type PluginOrigin = "bundled" | "installed";
70
+ export interface PluginView {
71
+ id: string;
72
+ enabled: boolean;
73
+ /** Required even on an errored entry — it is a property of where the plugin came from, not of loading. */
74
+ origin: PluginOrigin;
75
+ error?: string;
76
+ installedVersion?: string;
77
+ sidecar?: {
78
+ state: "healthy" | "degraded";
79
+ };
80
+ /** URL to `import()` the single frontend bundle; present only for a loadable in-process plugin. */
81
+ frontend?: string;
82
+ panels?: PluginPanelView[];
83
+ cards?: PluginCardView[];
84
+ actions?: PluginActionView[];
85
+ /** Jobs this plugin declares (#83), pure data like `actions` above; present only when non-empty --
86
+ * what the `overview` plugin's `sched` mini card lists one row per. */
87
+ jobs?: PluginJobView[];
88
+ /** Host-agent capabilities this plugin declares (#50), sorted; present only when non-empty --
89
+ * what the Agents panel's Grants grid lists one row per. */
90
+ capabilities?: string[];
91
+ /** #78: mirrors the manifest's `contributes.attention` -- present (`true`) only when this plugin
92
+ * serves `GET /api/x/<id>/attention`, so the needs-attention rollup discovers contributors from
93
+ * this ONE `/api/plugins` read rather than a second manifest fetch per plugin. */
94
+ attention?: true;
95
+ }
96
+ /**
97
+ * `GET /api/server` (#43): a cheap liveness/identity probe the shell's heartbeat polls to tell a
98
+ * restarted process (changed `pid`/`startedAt`) from a merely slow one, and to show uptime/version.
99
+ * Additive-only, like the other wire contracts in this file; `ok` is always `true` here (a fetch
100
+ * failure or non-200, not a `false` field, is how the shell learns the host is unreachable).
101
+ */
102
+ export interface ServerStatus {
103
+ ok: true;
104
+ pid: number;
105
+ startedAt: string;
106
+ uptimeSeconds: number;
107
+ version: string;
108
+ apiVersion: number;
109
+ }
110
+ /** One field of a declarative card's template — a dot-path into the validated response. */
111
+ export interface CardField {
112
+ label: string;
113
+ /** A dot-path into the validated response, e.g. "status" or "data.uptime". */
114
+ path: string;
115
+ }
116
+ /**
117
+ * A no-code declarative card (#33): a host-relative route URL, a JSON Schema for its response, and a
118
+ * field-list template. Authored in `config.toml`'s `[[card]]` array-of-tables, served at
119
+ * `GET /api/cards`, and rendered by the shell's `DeclarativeCard`.
120
+ */
121
+ export interface DeclarativeCardDef {
122
+ id: string;
123
+ title: string;
124
+ url: string;
125
+ /** A JSON Schema for the fetched response; a response that fails it renders the card's error state. */
126
+ schema: object;
127
+ template: {
128
+ fields: CardField[];
129
+ };
130
+ }
131
+ /** What started a task: a manually-triggered job (#31), an agent run (E4), or a sidecar run (E4/E6). */
132
+ export type TaskKind = "job" | "agent" | "sidecar";
133
+ /** `queued` (#51): an agent-job task waiting on an unreachable machine, badged red in the drawer. */
134
+ export type TaskStatus = "running" | "done" | "failed" | "queued";
135
+ /**
136
+ * The `GET /api/tasks` list item (#42) — the host's in-memory task registry's view of one run. Not a
137
+ * durable record: `id`s are per-process (`t<seq>`) and the list empties on restart; `job_runs` (#31)
138
+ * stays the durable history for jobs specifically.
139
+ */
140
+ export interface TaskView {
141
+ id: string;
142
+ kind: TaskKind;
143
+ label: string;
144
+ status: TaskStatus;
145
+ startedAt: string;
146
+ endedAt?: string;
147
+ }
148
+ /**
149
+ * One machine's row in `JobsService.machineRuns` / `GET /api/jobs/:pluginId/:jobId/runs` (#51). A host
150
+ * job (`runOn: "host"`) is always exactly one entry with both ids `null`. An agent job has one entry
151
+ * per currently paired machine: `lastRun` when that machine has a recorded run, `queued` when a job is
152
+ * currently waiting in the durable queue for it. The two are independent and CAN both be set -- a
153
+ * machine with an earlier successful run that has since gone unreachable shows both its last known
154
+ * run and the current wait, rather than losing the last-run fact while queued.
155
+ */
156
+ export interface JobMachineRun {
157
+ machineId: string | null;
158
+ machineName: string | null;
159
+ lastRun?: {
160
+ startedAt: string;
161
+ ok: boolean;
162
+ };
163
+ queued?: {
164
+ queuedAt: string;
165
+ reason: string;
166
+ };
167
+ /** This machine's paired agent version compares lower than the job's declared `minAgentVersion`
168
+ * (#372, #80) -- computed live from the machine's CURRENT version every call, never persisted, so
169
+ * it always reflects "would a dispatch right now skip this machine", not a stale historical fact.
170
+ * Independent of `lastRun`/`queued`, which can still be set from before the machine fell behind. */
171
+ skipped?: {
172
+ reason: string;
173
+ };
174
+ }
175
+ /**
176
+ * The Overview home-cards layout (#44), 1.x-compatible: `GET /api/layout` / `POST /api/layout` shape,
177
+ * and the localStorage `ac-layout` mirror. `order`/`hidden` are card ids (`config:<id>` for a
178
+ * declarative card, `plugin:<pluginId>:<cardId>` for a plugin's — the scheme E8's `import-v1` targets);
179
+ * `sizes` is a sparse per-card height-tier override. The store is id-dumb — it never validates an id
180
+ * against a real card, so a card added/removed/renamed in a later plugin build never needs a server
181
+ * change (`packages/host/src/layouts/store.ts`'s `cleanLayout`).
182
+ */
183
+ export interface DashLayout {
184
+ order: string[];
185
+ hidden: string[];
186
+ sizes: Record<string, {
187
+ h: 1 | 2 | 3;
188
+ }>;
189
+ }
190
+ /**
191
+ * `GET /api/agents` (#48): one paired machine, as the console's `machines` table sees it. Never
192
+ * carries the bearer token the console sends it (`packages/host/src/agents/store.ts`'s `list`/`get`
193
+ * don't select it) -- the row is safe to render straight into the Agents panel.
194
+ */
195
+ export interface MachineView {
196
+ id: string;
197
+ name: string;
198
+ url: string;
199
+ certFingerprint: string;
200
+ agentVersion: string;
201
+ apiVersion: number;
202
+ lastSeenAt?: string;
203
+ createdAt: string;
204
+ /** Whether this machine can open a `claude://` link, as its agent last reported on `GET
205
+ * /v1/health` (#457): `true` (a handler is registered), `false` (the agent looked and found no
206
+ * handler), or `null` -- the agent did not say: it predates the field, it holds no conclusive answer
207
+ * yet (it omits the field until it does), or the machine has not been probed since it was paired.
208
+ * Optional in the type so no pre-existing fixture has to name it; this host always sets it, and a
209
+ * consumer treats `undefined` exactly like `null`. `POST /deeplink` refuses only a known `false`. */
210
+ claudeDesktop?: boolean | null;
211
+ /** This machine's current grants, plugin id -> capabilities (#50). */
212
+ grants?: Record<string, string[]>;
213
+ /** `grantsRevision` of the row above -- what `HostApi.agent.call` sends so a stale agent can
214
+ * answer 409 and self-heal instead of enforcing against data the console has already changed. */
215
+ grantsRevision?: string;
216
+ }
217
+ /**
218
+ * The outcome of `HostApi.agent.call` (#50). `kind` tells a plugin which refusals are its own to
219
+ * show verbatim (`grant`: the agent's 403 text) from ones that are the console's problem to
220
+ * describe differently (`unknown-machine`; `refused`: not a verb the service will forward; `pin`:
221
+ * the agent's certificate no longer matches; `network`; `http`: an unexpected status).
222
+ */
223
+ export type AgentCallOutcome<T> = {
224
+ ok: true;
225
+ status: number;
226
+ body: T;
227
+ } | {
228
+ ok: false;
229
+ error: {
230
+ kind: "unknown-machine" | "refused" | "grant" | "pin" | "network" | "http";
231
+ status?: number;
232
+ reason: string;
233
+ };
234
+ };
235
+ /**
236
+ * `POST /api/agents/:id/preflight` (#55): the agent's own `GET /v1/preflight` report, relayed
237
+ * verbatim by the host. `missing`/`broken` on a `required: true` row means the install isn't
238
+ * finished; on an optional one (`chromium`) it just degrades one output. `off-path` is installed
239
+ * but invisible to a service-started agent (a version-manager node, say) -- a PATH fix, not a
240
+ * reinstall. `unauthenticated` is `gh`-only: present and running, but `gh auth status` failed.
241
+ * Independently defined here from the agent's own (identically-shaped) internal type -- the host
242
+ * never depends on `@rackbops/ac-agent` at runtime, only as a devDependency for its integration
243
+ * tests, matching `grants.ts`'s own id/capability pattern.
244
+ */
245
+ export type PreflightStatus = "ok" | "missing" | "broken" | "off-path" | "unauthenticated";
246
+ export interface PreflightRow {
247
+ cmd: string;
248
+ label: string;
249
+ status: PreflightStatus;
250
+ required: boolean;
251
+ path: string;
252
+ why: string;
253
+ version: string;
254
+ detail: string;
255
+ }
256
+ export interface PreflightReport {
257
+ machine: string;
258
+ checkedAt: string;
259
+ searchPath: "registry" | "process";
260
+ rows: PreflightRow[];
261
+ }
262
+ //# sourceMappingURL=views.d.ts.map
package/dist/views.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=views.js.map
package/host.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Types for the shell's own import of `@ac/host` (#38). At runtime the specifier resolves through the
3
+ * host page's import map to `/vendor/ac-host.js` — the SAME module instance every plugin imports — which
4
+ * is what lets the shell register its chrome (toast portal, confirm modal #41, quickcast palette #142)
5
+ * into the module plugins call. The implementation is `vendor/ac-host-entry.mjs` (plain JS); this declaration
6
+ * types it against `@ac/contract`'s `BrowserHostApi` for the shell's TypeScript. In Vitest the
7
+ * specifier is aliased to the entry file (see `vite.config.ts` `test.alias`); in the Vite build it
8
+ * stays external (`vendor.config.mjs` `EXTERNALS`).
9
+ *
10
+ * The marker pair below (search this file for "SHELL-INTERNAL", #450) wraps the chrome-registration
11
+ * declarations that are shell-internal, never plugin-facing — `packages/plugin-contract/build.mjs`
12
+ * strips everything between them when it generates the published `@rackbops/ac-plugin-contract/host`
13
+ * declaration for out-of-repo plugin authors, and fails loudly (never publishes a partial/guessed
14
+ * result) if a marker goes missing, or if either wrapped name survives the strip regardless. This
15
+ * comment deliberately does not spell either name out literally, so it can never itself be mistaken
16
+ * for a leftover.
17
+ */
18
+ declare module "@ac/host" {
19
+ import type {
20
+ ActionOpenRef,
21
+ ActionSettledEvent,
22
+ BrowserHostApi,
23
+ FilterToken,
24
+ HeaderButtonSpec,
25
+ IssueFilter,
26
+ IssueRecord,
27
+ MatchContext,
28
+ QuickcastItem,
29
+ ToastSeverity,
30
+ } from "@rackbops/ac-plugin-contract"
31
+
32
+ export const hostApiVersion: number
33
+ export const theme: BrowserHostApi["theme"]
34
+ export const toast: BrowserHostApi["toast"]
35
+ export const header: BrowserHostApi["header"]
36
+ export const actions: BrowserHostApi["actions"]
37
+ export const quickcast: BrowserHostApi["quickcast"]
38
+ export const fetch: BrowserHostApi["fetch"]
39
+ export const cached: BrowserHostApi["cached"]
40
+ export const invalidate: BrowserHostApi["invalidate"]
41
+ /** The shared filter predicate (E5 #56, decision 4/9/10): pure, `now`-injected, over `IssueRecord`.
42
+ * Both the Board (#63) and Classic (#59) panels apply this same function so their counts agree by
43
+ * construction. The filter *types* (`IssueFilter`, `SetFilter`, `AgeFilter`, `AgeBucket`, `FilterOp`)
44
+ * are compile-time only -- a plugin's own `.ts` imports those directly from `@ac/contract` with
45
+ * `import type` (erased at build time, so it is never a runtime import the import map would need an
46
+ * entry for); only this function and the filter tokens below need re-exporting through `@ac/host`. `ctx` (#464a) is what
47
+ * the filter tokens below resolve against -- the viewer's login and each ladder's label names. */
48
+ // biome-ignore format: ONE line, so each plugin's ac-host-drift.test.ts pins the whole signature and not only its first line
49
+ export function matchesIssue(record: IssueRecord, filter: IssueFilter, now: number, ctx?: MatchContext): boolean
50
+ /** The filter tokens (#464a, `@ac/contract`'s `issue-filter.ts`): the values `@me`, `@unassigned`, `@bots`,
51
+ * `@any` and `@upto:<label>` in a `SetFilter` -- the viewer, nobody, bots, any ladder label, and "this rung
52
+ * or before it". */
53
+ export const FILTER_ME: string
54
+ export const FILTER_UNASSIGNED: string
55
+ export const FILTER_BOTS: string
56
+ export const FILTER_ANY: string
57
+ export function filterUpTo(name: string): string
58
+ export function parseFilterToken(value: string): FilterToken | undefined
59
+
60
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@rackbops/ac-plugin-contract",
3
+ "version": "2.0.0-alpha.33",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "The artifact-console plugin ABI for out-of-repo plugin authors: HostApi, the acPlugin manifest contribution points, the generated manifest JSON Schema, and the @ac/host ambient browser declaration.",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/Rackbops/artifact-console",
10
+ "directory": "packages/plugin-contract"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./schema.json": "./schema.json",
18
+ "./host": {
19
+ "types": "./host.d.ts"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "schema.json",
25
+ "host.d.ts",
26
+ "AUTHORING.md",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "dependencies": {
34
+ "@octokit/rest": "22.0.1"
35
+ },
36
+ "devDependencies": {
37
+ "@ac/contract": "0.0.0",
38
+ "@types/node": "^26.0.0",
39
+ "typescript": "^7.0.0",
40
+ "vitest": "^5.0.0"
41
+ },
42
+ "scripts": {
43
+ "build": "node build.mjs",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "vitest run"
46
+ }
47
+ }