@rackbops/ac-plugin-contract 2.0.0-alpha.59 → 2.0.0-alpha.62

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 CHANGED
@@ -198,7 +198,12 @@ is never left open). Register last whatever must not exist when a later step fai
198
198
 
199
199
  - **Route** — `host.routes.register(method, path, handler)`. The handler is a web-standard
200
200
  `(request: Request) => Response | Promise<Response>`, mounted at `/api/x/<id><path>`. You may only register a
201
- `(method, path)` you declared in `contributes.routes` — an undeclared route throws. **Never answer `502` or
201
+ `(method, path)` you declared in `contributes.routes` — an undeclared route throws.
202
+ `operator: true` on the route (#867): With the admin kill switch off (`admin_enabled = false`), the host
203
+ refuses this route for its declared method with `403 admin disabled`, as it refuses the admin plugin's own
204
+ surface. Mark an action or route an operator would expect the switch to shut: one that runs a job the job-run
205
+ route refuses, writes to GitHub or the store on the operator's behalf, or changes the console's configuration.
206
+ Additive; a host released before this field refuses a manifest that declares it. **Never answer `502` or
202
207
  `504` yourself** (#474): the production console is reached through Cloudflare, and Cloudflare's own
203
208
  documentation says it "returns a Cloudflare-branded HTTP 502 or 504 error when your origin web server responds
204
209
  with a standard HTTP 502 bad gateway or 504 gateway timeout error" — so on the production console (default
@@ -237,6 +242,11 @@ is never left open). Register last whatever must not exist when a later step fai
237
242
  same action again while that locked modal is up reuses it and does not re-run it. After a failed result the
238
243
  primary is enabled again and a retry runs, so a double-click whose first run fails fast can run the action
239
244
  twice (the second a retry): an action whose failure may have partly applied should tolerate that.
245
+ `operator: true` (#867): With the admin kill switch off (`admin_enabled = false`), the host refuses this
246
+ action (`POST /api/actions/<plugin>/<id>`) with `403 admin disabled`, as it refuses the admin plugin's own
247
+ surface. Mark an action or route an operator would expect the switch to shut: one that runs a job the job-run
248
+ route refuses, writes to GitHub or the store on the operator's behalf, or changes the console's configuration.
249
+ Additive; a host released before this field refuses a manifest that declares it.
240
250
  `confirm` stays a valid manifest flag, so an existing manifest keeps loading, but neither the modal nor the
241
251
  palette acts on it.
242
252
  - **Job** — implement `runJob(ctx)` and dispatch on `ctx.jobId`. The host runs only jobs you declared in
@@ -1053,6 +1063,14 @@ the machine asleep or off the network, a reboot, a lost NAT mapping) settles `fa
1053
1063
  after the last byte on the console's Linux host; a machine asleep for longer than that mid-job reads
1054
1064
  `failed` even if the job finishes after it wakes.
1055
1065
 
1066
+ A host restart mid-job is the one end the relay cannot report from inside the new process (#911): the
1067
+ host records each relay it opens and forgets it when the relay sees its end, and
1068
+ `host.agent.unfinishedStreams?()` (optional, like `finished`) returns, once, the calling plugin's relays
1069
+ that an earlier host process opened and never saw end, each `{ taskId, jobId, machineId, machineName,
1070
+ label, openedAt }` (the `taskId` is the earlier process's and no longer exists). Call it once in
1071
+ `activate()` and report each run as ended with an unknown outcome; `plugins/claude-runner` emits
1072
+ `claude.run` `status: "unknown"` for each. `ac jobs run` keeps no record and returns none.
1073
+
1056
1074
  ```ts
1057
1075
  const { jobId } = await execResult // however the plugin's own verb call started the job
1058
1076
  const { taskId, finished } = await host.agent.streamJob(machineId, jobId, { label: `code: ${prompt}` })
@@ -1089,7 +1107,7 @@ interface AttentionItem {
1089
1107
  href?: string // an external link; wins over `target` when both are set. Linked only
1090
1108
  // when its scheme is http:, https:, mailto: or tel: (#722); any other
1091
1109
  // href is not linked and `target` is used instead
1092
- target?: string // an in-app destination: "widget:<cardId>" or "panel:<pluginId>"
1110
+ target?: string // an in-app destination: "widget:<cardId>" (a home card: scrolls to and flashes it; text when the card is not on the page) or "panel:<pluginId>"
1093
1111
  hint?: string // the exact command to run, or the reason -- shown as a tooltip/expander
1094
1112
  action?: { kind: "issue"; repo?: string; title: string; body: string } // opens provider-github's issue.create, seeded
1095
1113
  }
@@ -1225,9 +1243,9 @@ what it's for.
1225
1243
  |---|---|---|
1226
1244
  | Panel | `panels[]` (`id`, `title`, `route`, `export`, `nav?`, `order?`, `isolation?`) | a component export in `ui.js` |
1227
1245
  | Card | `cards[]` (`id`, `title`, `sizes`, `export`) | a component export in `ui.js` |
1228
- | Action | `actions[]` (`id`, `scope`, `title?`, `confirm?`, `danger?`, `fields?`) | `host.actions.register({ id, run })` |
1246
+ | Action | `actions[]` (`id`, `scope`, `title?`, `confirm?`, `danger?`, `operator?`, `fields?`) | `host.actions.register({ id, run })` |
1229
1247
  | Job | `jobs[]` (`id`, `schedule`, `timeoutSec`, `runOn`, `minAgentVersion?`, `capabilities?`) | `runJob(ctx)` dispatching on `ctx.jobId` |
1230
- | Route | `routes[]` (`method`, `path`) | `host.routes.register(method, path, handler)` |
1248
+ | Route | `routes[]` (`method`, `path`, `operator?`) | `host.routes.register(method, path, handler)` |
1231
1249
  | Setting | `settings[]` (`key`, `type`, `default?`) | read via `host.settings.get/set` |
1232
1250
  | Attention | `attention: true` | `host.routes.register("GET", "/attention", handler)` -> `{ ok: true, items }` |
1233
1251
 
@@ -1293,6 +1311,9 @@ bound for a slot, or `null` when none is. It throws unless the plugin's own mani
1293
1311
  `{slot, version}` in `contributes.consumes`, and an installed plugin may not resolve `auth` at all. Call
1294
1312
  it at request time (inside a route handler or job), never in `activate()`: providers bind during
1295
1313
  activation in load order, so a consumer that activates first would read `null` there for good.
1314
+ `host.slots.providerId?.(slot, version)` (#866, optional: a host older than #866 has none) answers the id of the
1315
+ plugin bound to a slot, or `null`, without the provider itself and without a `consumes` entry; an installed
1316
+ plugin may not read `auth`'s.
1296
1317
  `ac-artifacts-weft` (`artifacts@1`) is the reference consumer: the second plugin to declare `consumes`
1297
1318
  for it, after `artifacts`, which reads `provider-artifacts`' HTTP routes from the browser, and the first to
1298
1319
  call `resolve`. `hello` contributes none of the six points above via a slot (nor `attention` -- its own manifest
@@ -39,8 +39,9 @@ export interface AttentionItem {
39
39
  * custom scheme, one that does not parse) renders no link, and the row uses `target` instead, as
40
40
  * if `href` were absent. */
41
41
  href?: string;
42
- /** An in-app destination: `widget:<cardId>` flashes/scrolls to a sibling card on Overview,
43
- * `panel:<pluginId>` navigates to that plugin's panel (its first panel route, or `/<pluginId>`
42
+ /** An in-app destination: `widget:<cardId>` scrolls to the home card whose `data-card-id` is
43
+ * `<cardId>` or ends `:<cardId>`, and flashes it (when no such card is on the page the row is
44
+ * plain text), `panel:<pluginId>` navigates to that plugin's panel (its first panel route, or `/<pluginId>`
44
45
  * when the console knows none). A value that is not a string is ignored. */
45
46
  target?: string;
46
47
  /** The exact command to run, or the reason, shown as a tooltip/expander (1.x's `hint`). */
@@ -81,7 +81,9 @@ export interface JobDeclaration {
81
81
  export interface EventsService {
82
82
  /** Calls every subscriber of `type` synchronously, in subscription order, and never awaits one.
83
83
  * A subscriber that throws, or returns a promise that rejects, is logged by the host and stops
84
- * neither the others nor the emitter (#728). */
84
+ * neither the others nor the emitter (#728). An event a plugin emits while plugins are still
85
+ * activating also reaches, once every plugin has activated, a subscriber that registered after
86
+ * it, so a handler registered in `activate()` does not depend on load order (#1061). */
85
87
  emit(type: string, payload: unknown): void;
86
88
  /** Subscribe; returns an unsubscribe function. The handler may be `async` (#728): when it
87
89
  * returns a promise (or any thenable) the host attaches a rejection handler, so a rejection is
@@ -98,7 +100,10 @@ export interface GithubService {
98
100
  /** The one shared Octokit REST client — consumers make all GitHub calls through this. It
99
101
  * authenticates with the console's PAT (`AC_GITHUB_TOKEN`), and `octokit().auth()` returns that
100
102
  * token to whichever plugin asks: no `env[]` declaration is needed, and nothing hides it (#784;
101
- * in-process plugins are trusted code, not sandboxed). */
103
+ * in-process plugins are trusted code, not sandboxed). Since #753 the client caps requests at 10 in
104
+ * flight, retries a GET's 5xx (GraphQL and writes are POSTs and are not) and a short secondary limit once, gives each attempt 20 s, and rejects a
105
+ * request at once with `GitHub rate limit exhausted until <ISO>` while a 403/429 has reported that
106
+ * request's budget spent. */
102
107
  octokit(): Octokit;
103
108
  /** Remaining primary-rate-limit budget from the last response whose three `x-ratelimit-*` headers
104
109
  * are all finite numbers (and the reset a valid time), a failed response's included (#784: a 403 or
@@ -180,6 +185,22 @@ export interface AgentService {
180
185
  reason: string;
181
186
  }>;
182
187
  }>;
188
+ /**
189
+ * The relays this plugin opened with `streamJob` in an earlier host process whose end that process
190
+ * never saw (#911): a host restart mid-job. Each is returned once, then forgotten. A process that
191
+ * keeps no relay record (`ac jobs run`) and a host older than #911 return none or lack the method.
192
+ * `taskId` is the earlier process's task id, which no longer exists.
193
+ */
194
+ unfinishedStreams?(): Promise<UnfinishedStream[]>;
195
+ }
196
+ /** A `streamJob` relay whose end an earlier host process never saw (#911). */
197
+ export interface UnfinishedStream {
198
+ taskId: string;
199
+ jobId: string;
200
+ machineId: string;
201
+ machineName: string;
202
+ label: string;
203
+ openedAt: string;
183
204
  }
184
205
  /** Proxy to a sidecar plugin's HTTP surface (the host handles health + base URL). */
185
206
  export interface SidecarService {
@@ -229,7 +250,8 @@ export type ActionInputErrorName = "ActionInputError";
229
250
  * know their callers' payload shapes either. Two providers for the same `slot@version` is a boot
230
251
  * error (auth/notify are singletons) -- surfaced by the host-side registry `provide` throws from, not
231
252
  * this interface, since this is the plugin's OWN scoped view (pre-bound to its id, so the error can
232
- * name the real offending plugin without a pluginId parameter here). */
253
+ * name the real offending plugin without a pluginId parameter here). `providerId` (#866) reads which
254
+ * plugin holds a binding without exposing it (the admin Security card reads `auth@1`'s). */
233
255
  export interface SlotRegistry {
234
256
  /** Register this plugin's provider instance for `slot@version`. Throws if that slot already has a
235
257
  * provider (from this plugin or another) -- auth/notify are singletons, never a last-write-wins. */
@@ -244,6 +266,13 @@ export interface SlotRegistry {
244
266
  * during activation in load order, so a consumer that activates before its provider would read `null`
245
267
  * there for good (#97). */
246
268
  resolve<T = unknown>(slot: string, version: number): T | null;
269
+ /** The id of the plugin bound to `slot@version`, or `null` when nothing is bound (#866). Never the
270
+ * provider itself, so it opens no way to the impl. Unlike `resolve` it needs no `contributes.consumes`
271
+ * entry. An installed plugin may not read the `auth` slot's (it throws, naming the plugin), as it may
272
+ * not resolve it. Optional: a host older than #866 has no such method, so check for it before calling
273
+ * (`host.slots.providerId?.(slot, version)`). Call it at request time, never in `activate()`, for the
274
+ * same load-order reason as `resolve`. */
275
+ providerId?(slot: string, version: number): string | null;
247
276
  }
248
277
  /** A logger prefixed with the plugin id. A plain-object first arg is the line's structured fields; an
249
278
  * `Error` first arg is logged as its own enumerable properties (`code`, `status`, ...) plus
@@ -294,6 +323,11 @@ export interface HostApi {
294
323
  /** #506: see `ConsoleSettingsService`. Optional so an out-of-repo plugin's own test fake still
295
324
  * compiles. */
296
325
  consoleSettings?: ConsoleSettingsService;
326
+ /** #875: `true` only in the process that serves HTTP and owns the plugins' on-disk state (`main.ts`);
327
+ * absent in a one-shot CLI process (`ac jobs run`), which loads every plugin too. Work that must run
328
+ * once per server boot, and must never run beside a live server (clearing a stale lock file under a
329
+ * git clone, say), is gated on it. Optional so an out-of-repo plugin's own test fake still compiles. */
330
+ serving?: boolean;
297
331
  }
298
332
  /**
299
333
  * The context the host passes to `Plugin.runJob` for one job run (#31). `jobId` selects which
@@ -103,6 +103,14 @@ export interface ActionContribution {
103
103
  * asks for a second click. */
104
104
  confirm?: boolean;
105
105
  danger?: boolean;
106
+ /**
107
+ * With the admin kill switch off (`admin_enabled = false`), the host refuses this action (`POST /api/actions/<plugin>/<id>`) with
108
+ * `403 admin disabled`, as it refuses the admin plugin's own surface. Mark an action or route an operator
109
+ * would expect the switch to shut: one that runs a job the job-run route refuses, writes to GitHub or the
110
+ * store on the operator's behalf, or changes the console's configuration. Additive; a host released before
111
+ * this field refuses a manifest that declares it (#867).
112
+ */
113
+ operator?: true;
106
114
  /** The form the shell builds the action's modal from; omit for a no-input action. */
107
115
  fields?: ActionField[];
108
116
  }
@@ -149,6 +157,14 @@ export interface RouteContribution {
149
157
  method: HttpMethod;
150
158
  /** Path under the plugin's namespace, e.g. "/feed". */
151
159
  path: string;
160
+ /**
161
+ * With the admin kill switch off (`admin_enabled = false`), the host refuses this route for its declared method with
162
+ * `403 admin disabled`, as it refuses the admin plugin's own surface. Mark an action or route an operator
163
+ * would expect the switch to shut: one that runs a job the job-run route refuses, writes to GitHub or the
164
+ * store on the operator's behalf, or changes the console's configuration. Additive; a host released before
165
+ * this field refuses a manifest that declares it (#867).
166
+ */
167
+ operator?: true;
152
168
  }
153
169
  /** JSON scalar type of a plugin setting (rendered by Admin). */
154
170
  export type SettingType = "string" | "integer" | "number" | "boolean";
package/dist/views.d.ts CHANGED
@@ -82,6 +82,10 @@ export type PluginOrigin = "bundled" | "installed";
82
82
  export interface PluginView {
83
83
  id: string;
84
84
  enabled: boolean;
85
+ /** The enable state the next restart applies, when it differs from `enabled` (#767): an operator's persisted
86
+ * choice (`POST /api/plugins/:id/enabled`) not yet applied, with `AC_PLUGINS_FORCE_ENABLED` winning as it does
87
+ * at boot. Absent when the two agree. Additive. */
88
+ pendingEnabled?: boolean;
85
89
  /** Required even on an errored entry — it is a property of where the plugin came from, not of loading. */
86
90
  origin: PluginOrigin;
87
91
  error?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rackbops/ac-plugin-contract",
3
- "version": "2.0.0-alpha.59",
3
+ "version": "2.0.0-alpha.62",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/schema.json CHANGED
@@ -235,6 +235,11 @@
235
235
  "danger": {
236
236
  "type": "boolean"
237
237
  },
238
+ "operator": {
239
+ "type": "boolean",
240
+ "const": true,
241
+ "description": "With the admin kill switch off (`admin_enabled = false`), the host refuses this action (`POST /api/actions/<plugin>/<id>`) with `403 admin disabled`, as it refuses the admin plugin's own surface. Mark an action or route an operator would expect the switch to shut: one that runs a job the job-run route refuses, writes to GitHub or the store on the operator's behalf, or changes the console's configuration. Additive; a host released before this field refuses a manifest that declares it (#867)."
242
+ },
238
243
  "fields": {
239
244
  "type": "array",
240
245
  "items": {
@@ -394,6 +399,11 @@
394
399
  "path": {
395
400
  "type": "string",
396
401
  "description": "Path under the plugin's namespace, e.g. \"/feed\"."
402
+ },
403
+ "operator": {
404
+ "type": "boolean",
405
+ "const": true,
406
+ "description": "With the admin kill switch off (`admin_enabled = false`), the host refuses this route for its declared method with `403 admin disabled`, as it refuses the admin plugin's own surface. Mark an action or route an operator would expect the switch to shut: one that runs a job the job-run route refuses, writes to GitHub or the store on the operator's behalf, or changes the console's configuration. Additive; a host released before this field refuses a manifest that declares it (#867)."
397
407
  }
398
408
  },
399
409
  "required": [