@rackbops/ac-plugin-contract 2.0.0-alpha.58 → 2.0.0-alpha.60
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 +143 -26
- package/README.md +10 -1
- package/dist/action-options.d.ts +0 -1
- package/dist/action-options.js +0 -1
- package/dist/attention.d.ts +6 -6
- package/dist/attention.js +2 -3
- package/dist/host-api.d.ts +46 -9
- package/dist/host-api.js +0 -1
- package/dist/index-envelope.d.ts +0 -1
- package/dist/index-envelope.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/issue-filter.d.ts +3 -3
- package/dist/issue-filter.js +0 -1
- package/dist/manifest.d.ts +16 -1
- package/dist/manifest.js +0 -1
- package/dist/ov-widgets.d.ts +1 -1
- package/dist/ov-widgets.js +0 -1
- package/dist/slots.d.ts +0 -1
- package/dist/slots.js +0 -1
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
- package/dist/views.d.ts +19 -8
- package/dist/views.js +0 -1
- package/package.json +2 -1
- package/schema.json +10 -0
package/AUTHORING.md
CHANGED
|
@@ -39,6 +39,7 @@ key is itself a hard validation error, not a silently-ignored field.
|
|
|
39
39
|
"kind": "in-process",
|
|
40
40
|
"server": "dist/server.js",
|
|
41
41
|
"frontend": "dist/ui.js",
|
|
42
|
+
"capabilities": ["agent.git.read"],
|
|
42
43
|
"contributes": {
|
|
43
44
|
"panels": [{ "id": "hello-panel", "title": "Hello", "route": "/hello", "export": "HelloPanel" }],
|
|
44
45
|
"cards": [{ "id": "hello-card", "title": "Hello", "sizes": ["2x1"], "export": "HelloCard" }],
|
|
@@ -56,8 +57,7 @@ key is itself a hard validation error, not a silently-ignored field.
|
|
|
56
57
|
{ "id": "hello.machines", "schedule": "17 3 * * *", "timeoutSec": 30, "runOn": "agent" }
|
|
57
58
|
],
|
|
58
59
|
"routes": [{ "method": "GET", "path": "/ping" }],
|
|
59
|
-
"settings": [{ "key": "greeting", "type": "string", "default": "hello" }]
|
|
60
|
-
"attention": true
|
|
60
|
+
"settings": [{ "key": "greeting", "type": "string", "default": "hello" }]
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
```
|
|
@@ -69,24 +69,28 @@ missing any of them fails `pnpm run check`. The complete shape (the `acPlugin` b
|
|
|
69
69
|
```jsonc
|
|
70
70
|
{
|
|
71
71
|
"name": "@ac/plugin-hello",
|
|
72
|
-
"version": "0.1.
|
|
72
|
+
"version": "0.1.1",
|
|
73
73
|
"private": true,
|
|
74
74
|
"type": "module",
|
|
75
75
|
"exports": { ".": { "default": "./dist/server.js" } },
|
|
76
76
|
"acPlugin": { /* the block shown above */ },
|
|
77
77
|
"scripts": {
|
|
78
78
|
"typecheck": "tsc --noEmit && tsc -p tsconfig.ui.json --noEmit",
|
|
79
|
-
"build": "tsc && tsc -p tsconfig.ui.json",
|
|
79
|
+
"build": "tsc -p tsconfig.build.json && tsc -p tsconfig.ui.build.json",
|
|
80
80
|
"test": "vitest run"
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"@ac/contract": "workspace:*",
|
|
84
|
-
"@rackbops/ui-react": "^0.2.
|
|
84
|
+
"@rackbops/ui-react": "^0.2.43",
|
|
85
85
|
"react": "^19.2.0"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
|
+
"@testing-library/dom": "^10.4.2",
|
|
89
|
+
"@testing-library/react": "^16.1.0",
|
|
88
90
|
"@types/node": "catalog:",
|
|
89
91
|
"@types/react": "catalog:",
|
|
92
|
+
"@types/react-dom": "catalog:",
|
|
93
|
+
"jsdom": "^30.1.1",
|
|
90
94
|
"typescript": "catalog:",
|
|
91
95
|
"vitest": "catalog:"
|
|
92
96
|
}
|
|
@@ -144,12 +148,26 @@ no host I/O, so the host can construct every plugin before any side effect. All
|
|
|
144
148
|
|
|
145
149
|
```ts
|
|
146
150
|
import type { CreatePlugin } from "@ac/contract"
|
|
151
|
+
import { checkRow } from "./stored-row.js"
|
|
152
|
+
|
|
153
|
+
export const DEFAULT_GREETING = "hello"
|
|
154
|
+
|
|
155
|
+
function isString(value: unknown): value is string {
|
|
156
|
+
return typeof value === "string"
|
|
157
|
+
}
|
|
147
158
|
|
|
148
159
|
export const createPlugin: CreatePlugin = (host) => ({
|
|
149
160
|
activate() {
|
|
150
161
|
// GET /api/x/hello/ping → the greeting setting (its default comes from the manifest `settings[]`).
|
|
162
|
+
// A stored greeting that is not a string reads as unset (#900; see "Setting" below).
|
|
151
163
|
host.routes.register("GET", "/ping", async () => {
|
|
152
|
-
const greeting =
|
|
164
|
+
const greeting =
|
|
165
|
+
checkRow(await host.settings.get("greeting"), isString, {
|
|
166
|
+
log: host.log,
|
|
167
|
+
store: "plugin-settings",
|
|
168
|
+
key: "greeting",
|
|
169
|
+
expected: "string",
|
|
170
|
+
}) ?? DEFAULT_GREETING
|
|
153
171
|
return Response.json({ greeting })
|
|
154
172
|
})
|
|
155
173
|
// An action: the button, its flags and the fields come from the manifest; the handler is here.
|
|
@@ -180,7 +198,12 @@ is never left open). Register last whatever must not exist when a later step fai
|
|
|
180
198
|
|
|
181
199
|
- **Route** — `host.routes.register(method, path, handler)`. The handler is a web-standard
|
|
182
200
|
`(request: Request) => Response | Promise<Response>`, mounted at `/api/x/<id><path>`. You may only register a
|
|
183
|
-
`(method, path)` you declared in `contributes.routes` — an undeclared route throws.
|
|
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
|
|
184
207
|
`504` yourself** (#474): the production console is reached through Cloudflare, and Cloudflare's own
|
|
185
208
|
documentation says it "returns a Cloudflare-branded HTTP 502 or 504 error when your origin web server responds
|
|
186
209
|
with a standard HTTP 502 bad gateway or 504 gateway timeout error" — so on the production console (default
|
|
@@ -219,6 +242,11 @@ is never left open). Register last whatever must not exist when a later step fai
|
|
|
219
242
|
same action again while that locked modal is up reuses it and does not re-run it. After a failed result the
|
|
220
243
|
primary is enabled again and a retry runs, so a double-click whose first run fails fast can run the action
|
|
221
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.
|
|
222
250
|
`confirm` stays a valid manifest flag, so an existing manifest keeps loading, but neither the modal nor the
|
|
223
251
|
palette acts on it.
|
|
224
252
|
- **Job** — implement `runJob(ctx)` and dispatch on `ctx.jobId`. The host runs only jobs you declared in
|
|
@@ -254,6 +282,27 @@ is never left open). Register last whatever must not exist when a later step fai
|
|
|
254
282
|
carries it. See §7.
|
|
255
283
|
- **Setting** — read/write with `host.settings.get(key)` / `host.settings.set(key, value)`. Both are async;
|
|
256
284
|
`get` returns the manifest `default` when unset.
|
|
285
|
+
- **Storage** — `host.storage.get(key)` / `host.storage.set(key, value)` / `host.storage.delete(key)`, async,
|
|
286
|
+
any JSON value, scoped to your plugin.
|
|
287
|
+
- **Check the shape of what you read back** (#900). `get<T>` on either store is only a cast: the host
|
|
288
|
+
guarantees a row is valid JSON (a row that is not reads as unset, #879), never that it is the shape your
|
|
289
|
+
code expects. A hand edit, a restore from a damaged copy or an older version of your plugin can leave a
|
|
290
|
+
number where you wrote an object. So read untyped (`get(key)` answers `unknown`), check the fields you
|
|
291
|
+
dereference at the read site, and treat a row of the wrong shape as unset: your default applies (for a
|
|
292
|
+
setting, the manifest default), and log a warn line naming the key, never the value. Two refinements:
|
|
293
|
+
- if unset would **widen** what your plugin acts on (a deny list, a guard, a limit), refuse the row
|
|
294
|
+
instead and fail closed: an unreadable exclusion list must never mean "exclude nothing";
|
|
295
|
+
- for a row that keeps a list (a queue, a history), keep the good entries and drop only the bad ones,
|
|
296
|
+
and before you write such a row back over a value you could not read whole, keep the raw value
|
|
297
|
+
somewhere (`<key>/unreadable`), or one malformed entry, or a history saved in another shape, is lost
|
|
298
|
+
for good. Never write over a kept copy: if it already holds a different value, refuse the write
|
|
299
|
+
(fail the job or action, naming both keys) until the operator repairs the row or deletes the copy.
|
|
300
|
+
|
|
301
|
+
`plugins/hello`'s `/ping` is the worked example, and its `src/stored-row.ts` (`checkRow`, `requireRow`,
|
|
302
|
+
`checkList`, `checkMap`, `preserveUnreadable`) is the helper every bundled plugin copies: copy it into
|
|
303
|
+
yours, since plugins share no runtime code. It writes each line at most once an hour per row, so a
|
|
304
|
+
polled route over a bad row does not flood the log. The bundled plugins' lines and fields are in
|
|
305
|
+
`docs/identifiers.md` (#900's entry).
|
|
257
306
|
- **Console settings** (#506) — `host.consoleSettings?.get(key)`, read-only, optional. Unlike `Setting`
|
|
258
307
|
above (per-plugin), this reads the console-WIDE settings an operator edits in Admin's Settings card
|
|
259
308
|
(`repo_owners`, `layout_file`, `tab_order`, `plan_default_machine`, `repo_pins`) -- but only a key on
|
|
@@ -261,7 +310,7 @@ is never left open). Register last whatever must not exist when a later step fai
|
|
|
261
310
|
is the reference consumer (`repo_owners`/`repo_pins`, its own repo-set resolution).
|
|
262
311
|
|
|
263
312
|
`createPlugin` must import from `@ac/contract` **type-only** (`import type`), so the compiled `dist/server.js`
|
|
264
|
-
has no runtime
|
|
313
|
+
has no runtime import of it — see §6 (a sibling module of your own, such as `./stored-row.js`, is fine).
|
|
265
314
|
|
|
266
315
|
### Rejecting a bad action input — `ActionInputError` (#440)
|
|
267
316
|
|
|
@@ -616,6 +665,16 @@ A `hint` (optional, e.g. a category or the plugin id) is searched the same as `l
|
|
|
616
665
|
`>hel` finds an item whose label doesn't contain "hel" but whose hint does — and renders as muted secondary
|
|
617
666
|
text in the row.
|
|
618
667
|
|
|
668
|
+
**What the chrome tolerates (#761).** A plugin bundle is built separately, so the shell does not trust the
|
|
669
|
+
shapes it hands to the chrome. A quickcast source that throws or returns a non-array is skipped, and an item
|
|
670
|
+
without a string `label` (or a `custom` item without a function `run`) is dropped, each logged with
|
|
671
|
+
`console.warn` at most once a minute per source. A toast message that is not a string is shown as its
|
|
672
|
+
`message` (an `Error`) or its `String()` form, and an unknown `severity` shows as `info`. A header button
|
|
673
|
+
label is shown as `String(label)`, and a button whose `onClick` is not a function is not added. If one of the
|
|
674
|
+
command palette, the toasts or the header buttons still fails to render, only that piece is replaced by a
|
|
675
|
+
one-line note and the rest of the console stays. The types in `@ac/contract` remain the contract; this is a
|
|
676
|
+
backstop, not a license.
|
|
677
|
+
|
|
619
678
|
**Ctrl/Cmd+K from inside a same-origin iframe (#369).** A key pressed while an iframe has focus lands in the
|
|
620
679
|
iframe's own document, never the shell's, so the palette does not open by itself. A panel that hosts a
|
|
621
680
|
same-origin iframe (the Artifacts report preview does) forwards the hotkey: bind a `keydown` listener on the
|
|
@@ -695,34 +754,70 @@ Build it with `tsc` and `"jsx": "react-jsx"` — `tsc` under `nodenext` preserve
|
|
|
695
754
|
import and injects `import { jsx } from "react/jsx-runtime"`, emitting exactly the servable ESM the import map
|
|
696
755
|
resolves. Do **not** bundle (no rollup/esbuild bundling of React) — that would ship a second React copy.
|
|
697
756
|
|
|
698
|
-
hello uses
|
|
757
|
+
hello uses four tsconfigs. The server (no JSX) and the frontend (JSX) build separately, and each has a typecheck config that covers its tests and a build-only config that does not, so `build` never emits a compiled test into `dist/` (#350; `pnpm run check`'s `check-no-test-emit.mjs` fails otherwise, and `check-typecheck-covers-tests.mjs` fails if a test file is in no typecheck config):
|
|
699
758
|
|
|
700
|
-
`tsconfig.json` (server):
|
|
759
|
+
`tsconfig.json` (typecheck, server):
|
|
701
760
|
|
|
702
761
|
```json
|
|
703
762
|
{
|
|
704
763
|
"extends": "../../tsconfig.base.json",
|
|
705
764
|
"compilerOptions": { "rootDir": "src", "outDir": "dist" },
|
|
706
|
-
"include": [
|
|
765
|
+
"include": [
|
|
766
|
+
"src/server.ts",
|
|
767
|
+
"src/server.test.ts",
|
|
768
|
+
"src/stored-row.ts",
|
|
769
|
+
"src/stored-row.test.ts",
|
|
770
|
+
"src/ac-host-drift.test.ts"
|
|
771
|
+
]
|
|
707
772
|
}
|
|
708
773
|
```
|
|
709
774
|
|
|
710
|
-
`tsconfig.ui.json` (frontend):
|
|
775
|
+
`tsconfig.ui.json` (typecheck, frontend):
|
|
711
776
|
|
|
712
777
|
```json
|
|
713
778
|
{
|
|
714
779
|
"extends": "../../tsconfig.base.json",
|
|
715
|
-
"compilerOptions": {
|
|
716
|
-
|
|
780
|
+
"compilerOptions": {
|
|
781
|
+
"rootDir": "src",
|
|
782
|
+
"outDir": "dist",
|
|
783
|
+
"jsx": "react-jsx",
|
|
784
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"]
|
|
785
|
+
},
|
|
786
|
+
"include": ["src/ui.tsx", "src/ui.test.tsx", "src/quickcast.test.tsx", "src/ac-host.d.ts"]
|
|
717
787
|
}
|
|
718
788
|
```
|
|
719
789
|
|
|
720
|
-
|
|
790
|
+
`tsconfig.build.json` (build, server):
|
|
791
|
+
|
|
792
|
+
```json
|
|
793
|
+
{
|
|
794
|
+
"extends": "../../tsconfig.base.json",
|
|
795
|
+
"compilerOptions": { "rootDir": "src", "outDir": "dist" },
|
|
796
|
+
"include": ["src/server.ts", "src/stored-row.ts"]
|
|
797
|
+
}
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
`tsconfig.ui.build.json` (build, frontend):
|
|
801
|
+
|
|
802
|
+
```json
|
|
803
|
+
{
|
|
804
|
+
"extends": "../../tsconfig.base.json",
|
|
805
|
+
"compilerOptions": {
|
|
806
|
+
"rootDir": "src",
|
|
807
|
+
"outDir": "dist",
|
|
808
|
+
"jsx": "react-jsx",
|
|
809
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"]
|
|
810
|
+
},
|
|
811
|
+
"include": ["src/ui.tsx", "src/ac-host.d.ts"]
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
and `package.json` builds with the build-only pair and typechecks with the other two:
|
|
721
816
|
|
|
722
817
|
```json
|
|
723
818
|
"scripts": {
|
|
724
819
|
"typecheck": "tsc --noEmit && tsc -p tsconfig.ui.json --noEmit",
|
|
725
|
-
"build": "tsc && tsc -p tsconfig.ui.json",
|
|
820
|
+
"build": "tsc -p tsconfig.build.json && tsc -p tsconfig.ui.build.json",
|
|
726
821
|
"test": "vitest run"
|
|
727
822
|
}
|
|
728
823
|
```
|
|
@@ -874,12 +969,21 @@ if (outcome.ok) {
|
|
|
874
969
|
}
|
|
875
970
|
```
|
|
876
971
|
|
|
972
|
+
**Treat every string union in the contract as open** (`error.kind` here, `ToastSeverity`, `TaskStatus`,
|
|
973
|
+
`PreflightStatus`, `OverviewOwner`, `ActionFieldType`, a filter token's `kind`): a later minor release may add a
|
|
974
|
+
member, which the contract calls additive. Keep a default branch (no `assertNever`) in any `switch` over one.
|
|
975
|
+
The unions most likely to grow carry a `MAY GROW` tag in their doc comment (`ActionFieldType` has grown before, #463, and may again; its doc comment is in the published schema, so it carries no tag).
|
|
976
|
+
|
|
877
977
|
`outcome.error.kind === "grant"` is the one worth showing to the person almost verbatim -- it
|
|
878
978
|
carries the agent's own refusal text (`"<capability> is not granted to <plugin> on <machine>"`). The
|
|
879
979
|
others are the console's problem to describe differently: `unknown-machine` (a stale machine id),
|
|
880
|
-
`refused` (the path isn't a `/v1/*` verb, or is `/v1/pair`/`/v1/grants` -- the control plane, never
|
|
980
|
+
`refused` (the path isn't a `/v1/*` verb, or is `/v1/pair`/`/v1/grants`/`/v1/preflight`/`/v1/update` -- the control plane, never
|
|
881
981
|
reachable this way, whatever the path spelling), `pin` (the agent's certificate changed since
|
|
882
|
-
pairing), `network`, `http` (an unexpected status the console doesn't special-case).
|
|
982
|
+
pairing), `network`, `http` (an unexpected status the console doesn't special-case). `ok: true` means the
|
|
983
|
+
agent answered a 2xx and its body was read (#778): a 2xx whose body could not be read (a timeout or a reset
|
|
984
|
+
mid-body, a body that is not JSON) is `ok: false`, `kind: "http"`, with that 2xx as `status` and the reason
|
|
985
|
+
`agent answered <status> but its body could not be read: <cause>`. The agent may have acted on such a call,
|
|
986
|
+
so a verb that starts something should not be retried blindly. A `204` is `ok: true` with no body.
|
|
883
987
|
|
|
884
988
|
An agent job declares the capabilities its own verbs need, in the job's `capabilities` (#843). Each one
|
|
885
989
|
must also be in the top-level list and match the capability grammar (`agent.` and lowercase segments).
|
|
@@ -952,10 +1056,20 @@ drawer shows: `{ status: "done" }` only for the agent's `done` frame with `statu
|
|
|
952
1056
|
`stream ended before done: <message>`, the drawer's last line without its brackets, when the stream
|
|
953
1057
|
closed or errored first or the `done` frame did not parse, so the job's own failure and a lost stream
|
|
954
1058
|
read differently. It never rejects. It never settles when the relay sees no end at all: the host
|
|
955
|
-
stopping while the job runs (the relay stops with the process), or a hung job
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1059
|
+
stopping while the job runs (the relay stops with the process), or a hung job (the stream has no body
|
|
1060
|
+
timeout and the agent sends no heartbeat). A connection silently gone (no FIN or RST reaches the host:
|
|
1061
|
+
the machine asleep or off the network, a reboot, a lost NAT mapping) settles `failed` with
|
|
1062
|
+
`stream ended before done: terminated` once TCP keep-alive's probes go unanswered (#777), about 70 s
|
|
1063
|
+
after the last byte on the console's Linux host; a machine asleep for longer than that mid-job reads
|
|
1064
|
+
`failed` even if the job finishes after it wakes.
|
|
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.
|
|
959
1073
|
|
|
960
1074
|
```ts
|
|
961
1075
|
const { jobId } = await execResult // however the plugin's own verb call started the job
|
|
@@ -987,13 +1101,13 @@ by `${source}:${id}`.
|
|
|
987
1101
|
interface AttentionItem {
|
|
988
1102
|
id: string // unique within YOUR plugin; the rollup's dedupe key is `${source}:${id}`
|
|
989
1103
|
severity: "danger" | "warn" | "info"
|
|
990
|
-
source: string // your own plugin id -- the
|
|
1104
|
+
source: string // your own plugin id -- the dedupe key and draft scope; the rollup does not display it
|
|
991
1105
|
text: string // always a counted sentence: "3 open actions in the ledger", never "some actions"
|
|
992
1106
|
badge?: string
|
|
993
1107
|
href?: string // an external link; wins over `target` when both are set. Linked only
|
|
994
1108
|
// when its scheme is http:, https:, mailto: or tel: (#722); any other
|
|
995
1109
|
// href is not linked and `target` is used instead
|
|
996
|
-
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>"
|
|
997
1111
|
hint?: string // the exact command to run, or the reason -- shown as a tooltip/expander
|
|
998
1112
|
action?: { kind: "issue"; repo?: string; title: string; body: string } // opens provider-github's issue.create, seeded
|
|
999
1113
|
}
|
|
@@ -1129,9 +1243,9 @@ what it's for.
|
|
|
1129
1243
|
|---|---|---|
|
|
1130
1244
|
| Panel | `panels[]` (`id`, `title`, `route`, `export`, `nav?`, `order?`, `isolation?`) | a component export in `ui.js` |
|
|
1131
1245
|
| Card | `cards[]` (`id`, `title`, `sizes`, `export`) | a component export in `ui.js` |
|
|
1132
|
-
| 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 })` |
|
|
1133
1247
|
| Job | `jobs[]` (`id`, `schedule`, `timeoutSec`, `runOn`, `minAgentVersion?`, `capabilities?`) | `runJob(ctx)` dispatching on `ctx.jobId` |
|
|
1134
|
-
| Route | `routes[]` (`method`, `path`) | `host.routes.register(method, path, handler)` |
|
|
1248
|
+
| Route | `routes[]` (`method`, `path`, `operator?`) | `host.routes.register(method, path, handler)` |
|
|
1135
1249
|
| Setting | `settings[]` (`key`, `type`, `default?`) | read via `host.settings.get/set` |
|
|
1136
1250
|
| Attention | `attention: true` | `host.routes.register("GET", "/attention", handler)` -> `{ ok: true, items }` |
|
|
1137
1251
|
|
|
@@ -1197,6 +1311,9 @@ bound for a slot, or `null` when none is. It throws unless the plugin's own mani
|
|
|
1197
1311
|
`{slot, version}` in `contributes.consumes`, and an installed plugin may not resolve `auth` at all. Call
|
|
1198
1312
|
it at request time (inside a route handler or job), never in `activate()`: providers bind during
|
|
1199
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.
|
|
1200
1317
|
`ac-artifacts-weft` (`artifacts@1`) is the reference consumer: the second plugin to declare `consumes`
|
|
1201
1318
|
for it, after `artifacts`, which reads `provider-artifacts`' HTTP routes from the browser, and the first to
|
|
1202
1319
|
call `resolve`. `hello` contributes none of the six points above via a slot (nor `attention` -- its own manifest
|
package/README.md
CHANGED
|
@@ -18,11 +18,20 @@ convention.
|
|
|
18
18
|
|
|
19
19
|
- `import type { HostApi, Plugin, ... } from "@rackbops/ac-plugin-contract"` — the server-side types.
|
|
20
20
|
- `@rackbops/ac-plugin-contract/schema.json` — the manifest JSON Schema, for a vendored drift check
|
|
21
|
-
|
|
21
|
+
(§9 of AUTHORING.md, "Vendoring `schema.json` with a drift check"). The check compares your
|
|
22
|
+
committed copy with the one in the installed package, so it detects a version bump that changed
|
|
23
|
+
the schema; it does not look at a running console.
|
|
22
24
|
- `@rackbops/ac-plugin-contract/host` — a types-only ambient declaration of `@ac/host` (the browser
|
|
23
25
|
API a plugin's frontend imports at runtime, resolved through the console's own import map). Add it
|
|
24
26
|
to your tsconfig's `"types"` array or a triple-slash reference — it carries no runtime export.
|
|
25
27
|
|
|
28
|
+
## Unions are open
|
|
29
|
+
|
|
30
|
+
Treat every string union this package exports (`AgentCallOutcome`'s `error.kind`, `ToastSeverity`,
|
|
31
|
+
`TaskStatus`, `PreflightStatus`, `OverviewOwner`, `ActionFieldType` and the like) as open: a later minor
|
|
32
|
+
release may add a member, so keep a default branch in any `switch` over one. The unions most likely to grow
|
|
33
|
+
carry a `MAY GROW` tag in their doc comment.
|
|
34
|
+
|
|
26
35
|
## Version
|
|
27
36
|
|
|
28
37
|
This package's version always equals the published `artifact-console` host's own version — pin your
|
package/dist/action-options.d.ts
CHANGED
package/dist/action-options.js
CHANGED
package/dist/attention.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* `contributes.attention: true` in its manifest and serves `GET /api/x/<id>/attention` ->
|
|
4
4
|
* `{ ok: true, items: AttentionItem[] }`. The `overview` plugin's rollup card polls every
|
|
5
5
|
* contributor (discovered via `GET /api/plugins`'s `PluginView.attention`), merges, sorts by
|
|
6
|
-
* severity, and dedupes by `${source}:${id}`. Ported from 1.x's `attentionRows` (`
|
|
7
|
-
*
|
|
6
|
+
* severity, and dedupes by `${source}:${id}`. Ported from 1.x's `attentionRows` (the `artifacts-console`
|
|
7
|
+
* repo's overview `attention.ts`) -- "own the fact, own
|
|
8
8
|
* the row": each plugin knows its own facts best, so the rollup itself derives nothing, it only
|
|
9
9
|
* merges what contributors already computed.
|
|
10
10
|
*/
|
|
@@ -28,7 +28,7 @@ export interface AttentionItem {
|
|
|
28
28
|
/** Unique within the contributing plugin; the rollup's dedupe key is `${source}:${id}`. */
|
|
29
29
|
id: string;
|
|
30
30
|
severity: AttentionSeverity;
|
|
31
|
-
/** The contributing plugin's id, e.g. "scans" -- the
|
|
31
|
+
/** The contributing plugin's id, e.g. "scans" -- identifies the contributing plugin (dedupe key, draft scope); the rollup does not display it. */
|
|
32
32
|
source: string;
|
|
33
33
|
/** Always a counted sentence (1.x's `attCount`, e.g. "1 scheduled job run failed"). */
|
|
34
34
|
text: string;
|
|
@@ -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>`
|
|
43
|
-
*
|
|
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`). */
|
|
@@ -53,4 +54,3 @@ export interface AttentionResponse {
|
|
|
53
54
|
ok: true;
|
|
54
55
|
items: AttentionItem[];
|
|
55
56
|
}
|
|
56
|
-
//# sourceMappingURL=attention.d.ts.map
|
package/dist/attention.js
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
* `contributes.attention: true` in its manifest and serves `GET /api/x/<id>/attention` ->
|
|
4
4
|
* `{ ok: true, items: AttentionItem[] }`. The `overview` plugin's rollup card polls every
|
|
5
5
|
* contributor (discovered via `GET /api/plugins`'s `PluginView.attention`), merges, sorts by
|
|
6
|
-
* severity, and dedupes by `${source}:${id}`. Ported from 1.x's `attentionRows` (`
|
|
7
|
-
*
|
|
6
|
+
* severity, and dedupes by `${source}:${id}`. Ported from 1.x's `attentionRows` (the `artifacts-console`
|
|
7
|
+
* repo's overview `attention.ts`) -- "own the fact, own
|
|
8
8
|
* the row": each plugin knows its own facts best, so the rollup itself derives nothing, it only
|
|
9
9
|
* merges what contributors already computed.
|
|
10
10
|
*/
|
|
11
11
|
export {};
|
|
12
|
-
//# sourceMappingURL=attention.js.map
|
package/dist/host-api.d.ts
CHANGED
|
@@ -28,7 +28,9 @@ export interface SettingsService {
|
|
|
28
28
|
export interface ConsoleSettingsService {
|
|
29
29
|
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
30
30
|
}
|
|
31
|
-
/** Per-plugin durable storage:
|
|
31
|
+
/** Per-plugin durable storage: the plugin's rows in the host's shared `plugin_storage` table, keyed by
|
|
32
|
+
* (plugin id, key), with plain `get`/`set`/`delete` over JSON values (an `undefined` value is stored as
|
|
33
|
+
* `null`). */
|
|
32
34
|
export interface StorageService {
|
|
33
35
|
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
34
36
|
set(key: string, value: unknown): Promise<void>;
|
|
@@ -73,7 +75,9 @@ export interface JobDeclaration {
|
|
|
73
75
|
id: string;
|
|
74
76
|
runOn: "host" | "agent";
|
|
75
77
|
}
|
|
76
|
-
/** The
|
|
78
|
+
/** The event bus: `emit(type: string, payload: unknown)` is untyped, so a type name is a convention between
|
|
79
|
+
* emitter and subscriber (the host emits `job.finished`; core plugins emit `artifacts.rendered` and
|
|
80
|
+
* `claude.run`; `issue.changed` is the documented example of a plugin-to-plugin event). */
|
|
77
81
|
export interface EventsService {
|
|
78
82
|
/** Calls every subscriber of `type` synchronously, in subscription order, and never awaits one.
|
|
79
83
|
* A subscriber that throws, or returns a promise that rejects, is logged by the host and stops
|
|
@@ -94,7 +98,10 @@ export interface GithubService {
|
|
|
94
98
|
/** The one shared Octokit REST client — consumers make all GitHub calls through this. It
|
|
95
99
|
* authenticates with the console's PAT (`AC_GITHUB_TOKEN`), and `octokit().auth()` returns that
|
|
96
100
|
* token to whichever plugin asks: no `env[]` declaration is needed, and nothing hides it (#784;
|
|
97
|
-
* in-process plugins are trusted code, not sandboxed).
|
|
101
|
+
* in-process plugins are trusted code, not sandboxed). Since #753 the client caps requests at 10 in
|
|
102
|
+
* 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
|
|
103
|
+
* request at once with `GitHub rate limit exhausted until <ISO>` while a 403/429 has reported that
|
|
104
|
+
* request's budget spent. */
|
|
98
105
|
octokit(): Octokit;
|
|
99
106
|
/** Remaining primary-rate-limit budget from the last response whose three `x-ratelimit-*` headers
|
|
100
107
|
* are all finite numbers (and the reset a valid time), a failed response's included (#784: a 403 or
|
|
@@ -158,9 +165,11 @@ export interface AgentService {
|
|
|
158
165
|
* tasks drawer's last line without its brackets, when the stream closed or errored before its
|
|
159
166
|
* `done` frame, or that frame did not parse. It never rejects. It never settles when the relay sees
|
|
160
167
|
* no end at all: when the host stops while the job runs (the relay stops with the process), or when
|
|
161
|
-
* the job hangs
|
|
162
|
-
*
|
|
163
|
-
*
|
|
168
|
+
* the job hangs (the stream has no body timeout and the agent sends no heartbeat). A connection
|
|
169
|
+
* silently gone (the machine asleep or off the network, so no FIN or RST reaches the host) settles
|
|
170
|
+
* `failed` with `stream ended before done: terminated` once TCP keep-alive's probes go unanswered
|
|
171
|
+
* (#777), about 70 s after the last byte on the console's Linux host; a machine asleep for longer than
|
|
172
|
+
* that mid-job reads `failed` even if the job finishes after it wakes.
|
|
164
173
|
*/
|
|
165
174
|
streamJob(machineId: string, jobId: string, opts: {
|
|
166
175
|
label: string;
|
|
@@ -174,6 +183,22 @@ export interface AgentService {
|
|
|
174
183
|
reason: string;
|
|
175
184
|
}>;
|
|
176
185
|
}>;
|
|
186
|
+
/**
|
|
187
|
+
* The relays this plugin opened with `streamJob` in an earlier host process whose end that process
|
|
188
|
+
* never saw (#911): a host restart mid-job. Each is returned once, then forgotten. A process that
|
|
189
|
+
* keeps no relay record (`ac jobs run`) and a host older than #911 return none or lack the method.
|
|
190
|
+
* `taskId` is the earlier process's task id, which no longer exists.
|
|
191
|
+
*/
|
|
192
|
+
unfinishedStreams?(): Promise<UnfinishedStream[]>;
|
|
193
|
+
}
|
|
194
|
+
/** A `streamJob` relay whose end an earlier host process never saw (#911). */
|
|
195
|
+
export interface UnfinishedStream {
|
|
196
|
+
taskId: string;
|
|
197
|
+
jobId: string;
|
|
198
|
+
machineId: string;
|
|
199
|
+
machineName: string;
|
|
200
|
+
label: string;
|
|
201
|
+
openedAt: string;
|
|
177
202
|
}
|
|
178
203
|
/** Proxy to a sidecar plugin's HTTP surface (the host handles health + base URL). */
|
|
179
204
|
export interface SidecarService {
|
|
@@ -223,7 +248,8 @@ export type ActionInputErrorName = "ActionInputError";
|
|
|
223
248
|
* know their callers' payload shapes either. Two providers for the same `slot@version` is a boot
|
|
224
249
|
* error (auth/notify are singletons) -- surfaced by the host-side registry `provide` throws from, not
|
|
225
250
|
* this interface, since this is the plugin's OWN scoped view (pre-bound to its id, so the error can
|
|
226
|
-
* name the real offending plugin without a pluginId parameter here).
|
|
251
|
+
* name the real offending plugin without a pluginId parameter here). `providerId` (#866) reads which
|
|
252
|
+
* plugin holds a binding without exposing it (the admin Security card reads `auth@1`'s). */
|
|
227
253
|
export interface SlotRegistry {
|
|
228
254
|
/** Register this plugin's provider instance for `slot@version`. Throws if that slot already has a
|
|
229
255
|
* provider (from this plugin or another) -- auth/notify are singletons, never a last-write-wins. */
|
|
@@ -238,6 +264,13 @@ export interface SlotRegistry {
|
|
|
238
264
|
* during activation in load order, so a consumer that activates before its provider would read `null`
|
|
239
265
|
* there for good (#97). */
|
|
240
266
|
resolve<T = unknown>(slot: string, version: number): T | null;
|
|
267
|
+
/** The id of the plugin bound to `slot@version`, or `null` when nothing is bound (#866). Never the
|
|
268
|
+
* provider itself, so it opens no way to the impl. Unlike `resolve` it needs no `contributes.consumes`
|
|
269
|
+
* entry. An installed plugin may not read the `auth` slot's (it throws, naming the plugin), as it may
|
|
270
|
+
* not resolve it. Optional: a host older than #866 has no such method, so check for it before calling
|
|
271
|
+
* (`host.slots.providerId?.(slot, version)`). Call it at request time, never in `activate()`, for the
|
|
272
|
+
* same load-order reason as `resolve`. */
|
|
273
|
+
providerId?(slot: string, version: number): string | null;
|
|
241
274
|
}
|
|
242
275
|
/** A logger prefixed with the plugin id. A plain-object first arg is the line's structured fields; an
|
|
243
276
|
* `Error` first arg is logged as its own enumerable properties (`code`, `status`, ...) plus
|
|
@@ -288,6 +321,11 @@ export interface HostApi {
|
|
|
288
321
|
/** #506: see `ConsoleSettingsService`. Optional so an out-of-repo plugin's own test fake still
|
|
289
322
|
* compiles. */
|
|
290
323
|
consoleSettings?: ConsoleSettingsService;
|
|
324
|
+
/** #875: `true` only in the process that serves HTTP and owns the plugins' on-disk state (`main.ts`);
|
|
325
|
+
* absent in a one-shot CLI process (`ac jobs run`), which loads every plugin too. Work that must run
|
|
326
|
+
* once per server boot, and must never run beside a live server (clearing a stale lock file under a
|
|
327
|
+
* git clone, say), is gated on it. Optional so an out-of-repo plugin's own test fake still compiles. */
|
|
328
|
+
serving?: boolean;
|
|
291
329
|
}
|
|
292
330
|
/**
|
|
293
331
|
* The context the host passes to `Plugin.runJob` for one job run (#31). `jobId` selects which
|
|
@@ -326,7 +364,7 @@ export interface Plugin {
|
|
|
326
364
|
}
|
|
327
365
|
/** A plugin's server entry: `dist/server.js` exports this as `createPlugin`. */
|
|
328
366
|
export type CreatePlugin = (host: HostApi) => Plugin;
|
|
329
|
-
/** Toast severity, matching notify. */
|
|
367
|
+
/** Toast severity, matching notify. MAY GROW: keep a default branch. */
|
|
330
368
|
export type ToastSeverity = "info" | "warning" | "error";
|
|
331
369
|
/**
|
|
332
370
|
* The browser-side `@ac/host` a frontend bundle imports. v1 (implemented in #32) is `theme` tokens +
|
|
@@ -454,4 +492,3 @@ export interface ActionSettledEvent {
|
|
|
454
492
|
ok: boolean;
|
|
455
493
|
error?: string;
|
|
456
494
|
}
|
|
457
|
-
//# sourceMappingURL=host-api.d.ts.map
|
package/dist/host-api.js
CHANGED
package/dist/index-envelope.d.ts
CHANGED
package/dist/index-envelope.js
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/issue-filter.d.ts
CHANGED
|
@@ -76,8 +76,9 @@ export interface AgeFilter {
|
|
|
76
76
|
* Every axis is optional; an absent axis imposes no constraint. `priority`/`effort`/`type`/`tags` are
|
|
77
77
|
* all "the record has a label whose name is in the set" -- the UI groups labels into these ladders
|
|
78
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>`
|
|
80
|
-
* `MatchContext.ladders`; `tags` has no ladder, so
|
|
79
|
+
* label-name test. A token (#464a) adds `@any` (any ladder axis) and `@upto:<rung>` (priority and effort
|
|
80
|
+
* only; it matches nothing on `type`), resolved against `MatchContext.ladders`; `tags` has no ladder, so
|
|
81
|
+
* a token there matches nothing.
|
|
81
82
|
*/
|
|
82
83
|
export interface IssueFilter {
|
|
83
84
|
priority?: SetFilter;
|
|
@@ -119,4 +120,3 @@ export interface SavedView {
|
|
|
119
120
|
* exactly as it always did, while a token that needs `ctx` matches nothing.
|
|
120
121
|
*/
|
|
121
122
|
export declare function matchesIssue(record: IssueRecord, filter: IssueFilter, now: number, ctx?: MatchContext): boolean;
|
|
122
|
-
//# sourceMappingURL=issue-filter.d.ts.map
|
package/dist/issue-filter.js
CHANGED
package/dist/manifest.d.ts
CHANGED
|
@@ -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";
|
|
@@ -253,4 +269,3 @@ export interface PluginStateEntry {
|
|
|
253
269
|
state: "healthy" | "degraded";
|
|
254
270
|
};
|
|
255
271
|
}
|
|
256
|
-
//# sourceMappingURL=manifest.d.ts.map
|
package/dist/manifest.js
CHANGED
package/dist/ov-widgets.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* to round-trip. RENAMING an `id` here orphans every stored layout that references it, the same as
|
|
9
9
|
* 1.x's own comment warns; adding/reordering rows is safe.
|
|
10
10
|
*/
|
|
11
|
+
/** MAY GROW: keep a default branch. */
|
|
11
12
|
export type OverviewOwner = "overview" | "metrics-claude" | "repos" | "artifacts" | "provider-github";
|
|
12
13
|
export interface OverviewWidget {
|
|
13
14
|
id: string;
|
|
@@ -18,4 +19,3 @@ export interface OverviewWidget {
|
|
|
18
19
|
export declare const OV_WIDGETS: readonly OverviewWidget[];
|
|
19
20
|
/** The registry id `HomeCards` actually renders for one `OV_WIDGETS` row: `plugin:<owner>:<id>`. */
|
|
20
21
|
export declare function ovRegistryId(widget: OverviewWidget): string;
|
|
21
|
-
//# sourceMappingURL=ov-widgets.d.ts.map
|
package/dist/ov-widgets.js
CHANGED
package/dist/slots.d.ts
CHANGED
package/dist/slots.js
CHANGED
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
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;
|
|
@@ -142,7 +146,8 @@ export interface DeclarativeCardDef {
|
|
|
142
146
|
}
|
|
143
147
|
/** What started a task: a manually-triggered job (#31), an agent run (E4), or a sidecar run (E4/E6). */
|
|
144
148
|
export type TaskKind = "job" | "agent" | "sidecar";
|
|
145
|
-
/** `queued` (#51): an agent-job task waiting on an unreachable machine, badged red in the drawer.
|
|
149
|
+
/** `queued` (#51): an agent-job task waiting on an unreachable machine, badged red in the drawer.
|
|
150
|
+
* MAY GROW: keep a default branch. */
|
|
146
151
|
export type TaskStatus = "running" | "done" | "failed" | "queued";
|
|
147
152
|
/**
|
|
148
153
|
* The `GET /api/tasks` list item (#42) — the host's in-memory task registry's view of one run. Not a
|
|
@@ -180,9 +185,11 @@ export interface JobMachineRun {
|
|
|
180
185
|
* plugin every capability the job declares (#843; reason `not granted: <capability>[, ...]`, the
|
|
181
186
|
* missing ones in declared order), or its paired agent version compares lower than the job's
|
|
182
187
|
* declared `minAgentVersion` (#372, #80; reason `agent <version> < <min>`). A missing grant wins
|
|
183
|
-
* when both hold. Computed
|
|
184
|
-
*
|
|
185
|
-
*
|
|
188
|
+
* when both hold. Computed on every read, never persisted: the grant half from the machine's
|
|
189
|
+
* CURRENT grants, the version half from the LAST RECORDED agent version (what the last probe, ping,
|
|
190
|
+
* preflight or update poll stored). A dispatch re-probes the agent and judges the floor on the version
|
|
191
|
+
* that probe just reported, so after an upgrade or downgrade this field can differ from what a dispatch
|
|
192
|
+
* right now would do until the stored version is refreshed. Independent of `lastRun`/`queued`. Either can be set from before the skip
|
|
186
193
|
* applied, and a `queued` row with a version cause can also be written WHILE the skip applies: an
|
|
187
194
|
* unreachable machine is queued before any floor is judged, since the floor is judged only on a
|
|
188
195
|
* version a probe just reported. A grant skip is judged before the unreachable branch, so it never
|
|
@@ -209,7 +216,8 @@ export interface DashLayout {
|
|
|
209
216
|
/**
|
|
210
217
|
* `GET /api/agents` (#48): one paired machine, as the console's `machines` table sees it. Never
|
|
211
218
|
* carries the bearer token the console sends it (`packages/host/src/agents/store.ts`'s `list`/`get`
|
|
212
|
-
*
|
|
219
|
+
* read the whole row, but `toView` is the allow-list that builds this shape and leaves the token out)
|
|
220
|
+
* -- the row is safe to render straight into the Agents panel.
|
|
213
221
|
*/
|
|
214
222
|
export interface MachineView {
|
|
215
223
|
id: string;
|
|
@@ -237,7 +245,10 @@ export interface MachineView {
|
|
|
237
245
|
* The outcome of `HostApi.agent.call` (#50). `kind` tells a plugin which refusals are its own to
|
|
238
246
|
* show verbatim (`grant`: the agent's 403 text) from ones that are the console's problem to
|
|
239
247
|
* describe differently (`unknown-machine`; `refused`: not a verb the service will forward; `pin`:
|
|
240
|
-
* the agent's certificate no longer matches; `network`; `http`: an unexpected status).
|
|
248
|
+
* the agent's certificate no longer matches; `network`; `http`: an unexpected status). `ok: true` means
|
|
249
|
+
* the agent answered a 2xx and its body was read (#778): a 2xx whose body could not be read (a timeout
|
|
250
|
+
* or reset mid-body, a body that is not JSON) is `ok: false` with `kind: "http"` and that 2xx `status`,
|
|
251
|
+
* since the agent may have acted on the call. A `204` is `ok: true` with `body` undefined.
|
|
241
252
|
*/
|
|
242
253
|
export type AgentCallOutcome<T> = {
|
|
243
254
|
ok: true;
|
|
@@ -246,6 +257,7 @@ export type AgentCallOutcome<T> = {
|
|
|
246
257
|
} | {
|
|
247
258
|
ok: false;
|
|
248
259
|
error: {
|
|
260
|
+
/** MAY GROW: a new kind is an additive change, so keep a default branch. */
|
|
249
261
|
kind: "unknown-machine" | "refused" | "grant" | "pin" | "network" | "http";
|
|
250
262
|
status?: number;
|
|
251
263
|
reason: string;
|
|
@@ -260,7 +272,7 @@ export type AgentCallOutcome<T> = {
|
|
|
260
272
|
* reinstall. `unauthenticated` is `gh`-only: present and running, but `gh auth status` failed.
|
|
261
273
|
* Independently defined here from the agent's own (identically-shaped) internal type -- the host
|
|
262
274
|
* never depends on `@rackbops/ac-agent` at runtime, only as a devDependency for its integration
|
|
263
|
-
* tests, matching `grants.ts`'s own id/capability pattern.
|
|
275
|
+
* tests, matching `grants.ts`'s own id/capability pattern. MAY GROW: keep a default branch.
|
|
264
276
|
*/
|
|
265
277
|
export type PreflightStatus = "ok" | "missing" | "broken" | "off-path" | "unauthenticated";
|
|
266
278
|
export interface PreflightRow {
|
|
@@ -279,4 +291,3 @@ export interface PreflightReport {
|
|
|
279
291
|
searchPath: "registry" | "process";
|
|
280
292
|
rows: PreflightRow[];
|
|
281
293
|
}
|
|
282
|
-
//# sourceMappingURL=views.d.ts.map
|
package/dist/views.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rackbops/ac-plugin-contract",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.60",
|
|
4
4
|
"private": false,
|
|
5
|
+
"license": "Apache-2.0",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"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
8
|
"repository": {
|
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": [
|