@defrex/autobuild 0.8.0
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/LICENSE +201 -0
- package/README.md +192 -0
- package/SPEC.md +2269 -0
- package/bin/ab-build-runner.ts +47 -0
- package/bin/ab-dev.ts +27 -0
- package/bin/ab-dispatch-kernel.ts +92 -0
- package/bin/ab-harvest-runner.ts +29 -0
- package/bin/ab-init-probe.ts +19 -0
- package/bin/ab-process-group-reaper.ts +32 -0
- package/bin/ab.ts +6 -0
- package/bin/agent/ab +3 -0
- package/docs/answering-blocked-builds.md +89 -0
- package/docs/architecture.md +726 -0
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +1623 -0
- package/docs/hosted-dispatcher-cutover-validation.md +178 -0
- package/docs/hosted-dispatcher.md +204 -0
- package/docs/mcp.md +119 -0
- package/docs/operator-api.md +186 -0
- package/docs/remote-store-protocol.md +972 -0
- package/docs/remote-ticket-protocol.md +79 -0
- package/docs/setup.md +289 -0
- package/docs/spec-standard.md +63 -0
- package/docs/ticket-dependencies.md +59 -0
- package/docs/vercel-sandbox-rollout-probe.md +7 -0
- package/package.json +135 -0
- package/packages/core/src/cli/args.ts +74 -0
- package/packages/core/src/cli/artifact.ts +182 -0
- package/packages/core/src/cli/binary.ts +198 -0
- package/packages/core/src/cli/build-control.ts +789 -0
- package/packages/core/src/cli/build-progress.ts +55 -0
- package/packages/core/src/cli/bulk-control.ts +385 -0
- package/packages/core/src/cli/context.ts +470 -0
- package/packages/core/src/cli/dashboard/actions.ts +59 -0
- package/packages/core/src/cli/dashboard/cells.ts +92 -0
- package/packages/core/src/cli/dashboard/composer.ts +230 -0
- package/packages/core/src/cli/dashboard/detail.ts +100 -0
- package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
- package/packages/core/src/cli/dashboard/live.ts +152 -0
- package/packages/core/src/cli/dashboard/model.ts +1173 -0
- package/packages/core/src/cli/dashboard/poll.ts +196 -0
- package/packages/core/src/cli/dashboard/render.ts +1568 -0
- package/packages/core/src/cli/dashboard/selection.ts +66 -0
- package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
- package/packages/core/src/cli/dashboard/session-view.ts +506 -0
- package/packages/core/src/cli/dashboard/transcript.ts +108 -0
- package/packages/core/src/cli/dev.ts +46 -0
- package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
- package/packages/core/src/cli/dispatch-process.ts +259 -0
- package/packages/core/src/cli/dispatch.ts +3692 -0
- package/packages/core/src/cli/dotenv.ts +48 -0
- package/packages/core/src/cli/env.ts +206 -0
- package/packages/core/src/cli/harvest.ts +698 -0
- package/packages/core/src/cli/help.ts +713 -0
- package/packages/core/src/cli/init-agent.ts +106 -0
- package/packages/core/src/cli/init-validation.ts +583 -0
- package/packages/core/src/cli/init.ts +756 -0
- package/packages/core/src/cli/installation.ts +333 -0
- package/packages/core/src/cli/keyboard.ts +261 -0
- package/packages/core/src/cli/main.ts +1399 -0
- package/packages/core/src/cli/mcp.ts +279 -0
- package/packages/core/src/cli/models.ts +65 -0
- package/packages/core/src/cli/observe.ts +55 -0
- package/packages/core/src/cli/plugin.ts +227 -0
- package/packages/core/src/cli/pr-attachments.ts +138 -0
- package/packages/core/src/cli/pr-summary.ts +124 -0
- package/packages/core/src/cli/publication-settlement.ts +188 -0
- package/packages/core/src/cli/repo-state.ts +213 -0
- package/packages/core/src/cli/repository-status.ts +80 -0
- package/packages/core/src/cli/self-update.ts +443 -0
- package/packages/core/src/cli/status.ts +895 -0
- package/packages/core/src/cli/store-opening.contract.ts +289 -0
- package/packages/core/src/cli/store-opening.ts +167 -0
- package/packages/core/src/cli/store-ref.ts +33 -0
- package/packages/core/src/cli/terminal-restore.ts +174 -0
- package/packages/core/src/cli/terminal.ts +361 -0
- package/packages/core/src/cli/terminals.ts +995 -0
- package/packages/core/src/cli/testkit.ts +194 -0
- package/packages/core/src/cli/ticket.ts +484 -0
- package/packages/core/src/cli/upgrade-agent.ts +180 -0
- package/packages/core/src/cli/upgrade-commit.ts +468 -0
- package/packages/core/src/cli/upgrade-notice.ts +91 -0
- package/packages/core/src/cli/upgrade-progress.ts +110 -0
- package/packages/core/src/cli/upgrade.ts +888 -0
- package/packages/core/src/cli/wait.ts +587 -0
- package/packages/core/src/cli/watch.ts +932 -0
- package/packages/core/src/config/live.ts +255 -0
- package/packages/core/src/config/load.ts +98 -0
- package/packages/core/src/config/roles.ts +438 -0
- package/packages/core/src/config/schema.ts +774 -0
- package/packages/core/src/distribution.ts +24 -0
- package/packages/core/src/events/catalog.ts +201 -0
- package/packages/core/src/events/envelope.ts +59 -0
- package/packages/core/src/events/payloads.ts +618 -0
- package/packages/core/src/events/repository.ts +559 -0
- package/packages/core/src/events/sessions.ts +180 -0
- package/packages/core/src/harvest/schema.ts +147 -0
- package/packages/core/src/ids.ts +45 -0
- package/packages/core/src/integration/harness.ts +681 -0
- package/packages/core/src/kernel/auto-merge.ts +259 -0
- package/packages/core/src/kernel/converge.ts +117 -0
- package/packages/core/src/kernel/dispatch-settings.ts +60 -0
- package/packages/core/src/kernel/dispatch-status.ts +141 -0
- package/packages/core/src/kernel/engine.ts +1047 -0
- package/packages/core/src/kernel/harvest.ts +823 -0
- package/packages/core/src/kernel/origin.ts +69 -0
- package/packages/core/src/kernel/phases.ts +196 -0
- package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
- package/packages/core/src/kernel/pr-attachments.ts +82 -0
- package/packages/core/src/kernel/reducer.ts +752 -0
- package/packages/core/src/kernel/stall.ts +155 -0
- package/packages/core/src/kernel/verify-applicability.ts +52 -0
- package/packages/core/src/markdown.ts +67 -0
- package/packages/core/src/ontology.ts +288 -0
- package/packages/core/src/open-map.ts +203 -0
- package/packages/core/src/operator/annotations.ts +187 -0
- package/packages/core/src/operator/client.ts +311 -0
- package/packages/core/src/operator/control.ts +117 -0
- package/packages/core/src/operator/index.ts +62 -0
- package/packages/core/src/operator/presentation.ts +22 -0
- package/packages/core/src/operator/protocol.ts +98 -0
- package/packages/core/src/operator/query.ts +201 -0
- package/packages/core/src/operator/registry.ts +1043 -0
- package/packages/core/src/operator/requests.ts +138 -0
- package/packages/core/src/operator/sandbox.ts +458 -0
- package/packages/core/src/operator/server.ts +616 -0
- package/packages/core/src/operator/sessions.ts +223 -0
- package/packages/core/src/operator/tickets.ts +177 -0
- package/packages/core/src/plugin-sdk/index.ts +174 -0
- package/packages/core/src/plugins/contract-entry.ts +74 -0
- package/packages/core/src/plugins/contract-env.ts +3 -0
- package/packages/core/src/plugins/load.ts +259 -0
- package/packages/core/src/plugins/manifest.ts +236 -0
- package/packages/core/src/plugins/registry.ts +274 -0
- package/packages/core/src/plugins/runtimes.ts +75 -0
- package/packages/core/src/ports/forge/contract.ts +258 -0
- package/packages/core/src/ports/forge/create.ts +74 -0
- package/packages/core/src/ports/forge/fake.ts +488 -0
- package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
- package/packages/core/src/ports/forge/github-transport.ts +352 -0
- package/packages/core/src/ports/forge/github.ts +1162 -0
- package/packages/core/src/ports/forge/local-git.ts +568 -0
- package/packages/core/src/ports/runner/claude.ts +873 -0
- package/packages/core/src/ports/runner/cli-stream.ts +90 -0
- package/packages/core/src/ports/runner/codex.ts +873 -0
- package/packages/core/src/ports/runner/contract.ts +492 -0
- package/packages/core/src/ports/runner/fake.ts +213 -0
- package/packages/core/src/ports/runner/json-record.ts +2 -0
- package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
- package/packages/core/src/ports/runner/one-shot.ts +29 -0
- package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
- package/packages/core/src/ports/runner/pi-bridge.js +6 -0
- package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
- package/packages/core/src/ports/runner/pi.ts +617 -0
- package/packages/core/src/ports/runner/production.ts +74 -0
- package/packages/core/src/ports/runner/provider-error.ts +121 -0
- package/packages/core/src/ports/runner/routing.ts +234 -0
- package/packages/core/src/ports/runner/runtime.ts +214 -0
- package/packages/core/src/ports/runner/session-env.ts +34 -0
- package/packages/core/src/ports/runner/stream-parts.ts +103 -0
- package/packages/core/src/ports/tickets/contract.ts +570 -0
- package/packages/core/src/ports/tickets/create.ts +135 -0
- package/packages/core/src/ports/tickets/fake.ts +215 -0
- package/packages/core/src/ports/tickets/file.ts +559 -0
- package/packages/core/src/ports/tickets/linear.ts +811 -0
- package/packages/core/src/ports/tickets/operations.ts +161 -0
- package/packages/core/src/ports/tickets/remote-index.ts +13 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
- package/packages/core/src/ports/tickets/remote-server.ts +185 -0
- package/packages/core/src/ports/tickets/remote.ts +130 -0
- package/packages/core/src/ports/tickets/update.ts +51 -0
- package/packages/core/src/ports/types.ts +528 -0
- package/packages/core/src/ports/workspace/build-execution.ts +123 -0
- package/packages/core/src/ports/workspace/contract.ts +136 -0
- package/packages/core/src/ports/workspace/create.ts +132 -0
- package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
- package/packages/core/src/ports/workspace/fake.ts +432 -0
- package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
- package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
- package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
- package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
- package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
- package/packages/core/src/processes/build-child-exit.ts +78 -0
- package/packages/core/src/processes/build-child.ts +119 -0
- package/packages/core/src/processes/build-execution-state.ts +95 -0
- package/packages/core/src/processes/build-parent-watch.ts +35 -0
- package/packages/core/src/processes/build-runner.ts +2614 -0
- package/packages/core/src/processes/dispatcher.ts +2825 -0
- package/packages/core/src/processes/execution-settlement.ts +171 -0
- package/packages/core/src/processes/harvest-child.ts +115 -0
- package/packages/core/src/processes/harvest-execution-state.ts +121 -0
- package/packages/core/src/processes/harvest-runner.ts +1656 -0
- package/packages/core/src/processes/harvest.ts +595 -0
- package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
- package/packages/core/src/processes/phase-failure-budget.ts +52 -0
- package/packages/core/src/processes/process-group-reaper.ts +35 -0
- package/packages/core/src/processes/process-group.ts +65 -0
- package/packages/core/src/processes/publication-loss.ts +62 -0
- package/packages/core/src/processes/publication-state.ts +93 -0
- package/packages/core/src/processes/sandbox-state.ts +101 -0
- package/packages/core/src/registry.ts +24 -0
- package/packages/core/src/sessions/reducer.ts +185 -0
- package/packages/core/src/skills.ts +7 -0
- package/packages/core/src/spec-standard.ts +47 -0
- package/packages/core/src/store/adapter.ts +80 -0
- package/packages/core/src/store/build-scope.ts +272 -0
- package/packages/core/src/store/contract.ts +2581 -0
- package/packages/core/src/store/local/blobs.ts +38 -0
- package/packages/core/src/store/local/schema.ts +180 -0
- package/packages/core/src/store/local/store.ts +1713 -0
- package/packages/core/src/store/memory.ts +1138 -0
- package/packages/core/src/store/phase-session.ts +425 -0
- package/packages/core/src/store/remote/client.ts +780 -0
- package/packages/core/src/store/remote/index.ts +12 -0
- package/packages/core/src/store/remote/protocol.ts +348 -0
- package/packages/core/src/store/remote/server.ts +923 -0
- package/packages/core/src/store/remote/token.ts +137 -0
- package/packages/core/src/store/remote/version.ts +10 -0
- package/packages/core/src/store/retention.ts +91 -0
- package/packages/core/src/store/session-handle.ts +265 -0
- package/packages/core/src/store/streams/assemble.ts +79 -0
- package/packages/core/src/store/streams/lock.ts +42 -0
- package/packages/core/src/store/streams/session-writer.ts +306 -0
- package/packages/core/src/store/streams/types.ts +171 -0
- package/packages/core/src/store/streams/wait.ts +127 -0
- package/packages/core/src/store/subscribe.ts +105 -0
- package/packages/core/src/store/types.ts +466 -0
- package/packages/core/src/testing/fixed.ts +44 -0
- package/packages/core/src/testing/store-failures.ts +32 -0
- package/packages/core/src/zod-issues.ts +91 -0
- package/skills/code-review/SKILL.md +100 -0
- package/skills/finalize/SKILL.md +44 -0
- package/skills/guide/SKILL.md +1781 -0
- package/skills/guide/references/operator-api.md +164 -0
- package/skills/guide/references/plugin-authoring.md +354 -0
- package/skills/guide/references/remote-store-protocol.md +972 -0
- package/skills/guide/references/remote-ticket-protocol.md +79 -0
- package/skills/guide/references/setup.md +289 -0
- package/skills/guide/references/spec-standard.md +63 -0
- package/skills/guide/references/ticket-dependencies.md +59 -0
- package/skills/harvest/SKILL.md +66 -0
- package/skills/harvest-review/SKILL.md +63 -0
- package/skills/implement/SKILL.md +82 -0
- package/skills/plan/SKILL.md +123 -0
- package/skills/plan-review/SKILL.md +121 -0
- package/skills/reconcile/SKILL.md +65 -0
- package/skills/spec/SKILL.md +132 -0
- package/skills/tickets/SKILL.md +139 -0
- package/templates/autobuild.toml +36 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The open-map preservation contract: author-chosen keys → strictly validated
|
|
3
|
+
* values, with EVERY declared key surviving validation verbatim.
|
|
4
|
+
*
|
|
5
|
+
* `z.record` and `z.looseObject` build their result by assignment, so a
|
|
6
|
+
* perfectly valid entry named `__proto__` invokes the legacy
|
|
7
|
+
* `Object.prototype.__proto__` setter instead of creating an own key. Parsing
|
|
8
|
+
* SUCCEEDS and the entry vanishes — never resolved, never selectable, and
|
|
9
|
+
* invisible to whatever diagnostic exists to catch dead configuration. A silent
|
|
10
|
+
* drop is the one outcome an otherwise strict validator must never produce.
|
|
11
|
+
*
|
|
12
|
+
* So these keep the key intact end to end: own descriptors in, `defineProperty`
|
|
13
|
+
* out, null prototype on the result. Consumers can then read an author-chosen
|
|
14
|
+
* key off these maps without `Object.hasOwn` ceremony, and a name nobody
|
|
15
|
+
* declared answers `undefined` rather than an inherited object member.
|
|
16
|
+
*
|
|
17
|
+
* The entry boundary keeps the other half of the same promise: it must not
|
|
18
|
+
* narrow what the value schema said. A value that validates *to* nothing is
|
|
19
|
+
* kept — success is carried in the result's `ok`, never inferred from the value,
|
|
20
|
+
* or a legitimately absent value would be read as a rejection and produce the
|
|
21
|
+
* silent drop above from the other direction. And a rejection reaches the map's
|
|
22
|
+
* caller as validation reported it — every issue forwarded whole, with only its
|
|
23
|
+
* path prefixed — because re-encoding an issue as prose discards the structure a
|
|
24
|
+
* diagnostic needs to say what was actually wrong (see `../zod-issues`).
|
|
25
|
+
*
|
|
26
|
+
* Two surfaces consume this. `autobuild.toml`'s open maps ([commands],
|
|
27
|
+
* [roles], [workspace.config], and the named [verify.<step>] / [finalize.<step>]
|
|
28
|
+
* table sets) are one; the plugin manifest's adapter maps (`ticketSources`,
|
|
29
|
+
* `agentRuntimes`, `workspaceProviders`, `forges`) are the other, whose keys are
|
|
30
|
+
* likewise author-chosen — there, by the plugin author rather than the operator.
|
|
31
|
+
*/
|
|
32
|
+
import { z } from 'zod'
|
|
33
|
+
import { forwardIssues, type IssueSink } from './zod-issues'
|
|
34
|
+
|
|
35
|
+
/** How an entry name is validated. Autobuild-owned maps must be able to address
|
|
36
|
+
* every entry, so a nameless one is dead configuration; a map whose names belong
|
|
37
|
+
* to a plugin is not Autobuild's to narrow. */
|
|
38
|
+
export type OpenMapKeyPolicy = 'nonempty' | 'nonblank' | 'any'
|
|
39
|
+
|
|
40
|
+
export interface OpenMapOptions {
|
|
41
|
+
keys?: OpenMapKeyPolicy
|
|
42
|
+
/** What the container is called when the input is not one. */
|
|
43
|
+
shape?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const DEFAULT_SHAPE = 'a table of named entries'
|
|
47
|
+
|
|
48
|
+
function keyIsValid(key: string, policy: OpenMapKeyPolicy): boolean {
|
|
49
|
+
switch (policy) {
|
|
50
|
+
case 'nonempty':
|
|
51
|
+
return key.length > 0
|
|
52
|
+
case 'nonblank':
|
|
53
|
+
return key.trim().length > 0
|
|
54
|
+
case 'any':
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Whether a container is a plain record of named entries — a parsed TOML table
|
|
61
|
+
* (null prototype), an object literal, or any object built on top of those.
|
|
62
|
+
*
|
|
63
|
+
* The exclusions are load-bearing rather than pedantic. A `Map`, a `Set`, a
|
|
64
|
+
* `Date`, an array, or a class instance has no own string-keyed entries to
|
|
65
|
+
* collect, so accepting one would produce an EMPTY map from an input that
|
|
66
|
+
* plainly meant to declare something — the same silent drop this module exists
|
|
67
|
+
* to prevent, just arrived at from the other direction. `null` is rejected for
|
|
68
|
+
* the same reason: absence is spelled by omission, which `openMap`'s
|
|
69
|
+
* `.prefault({})` turns into `{}` before this ever runs.
|
|
70
|
+
*
|
|
71
|
+
* The decision itself is `z.record`'s, deliberately: this module replaced that
|
|
72
|
+
* schema on the manifest surface, and a container it accepted must keep
|
|
73
|
+
* parsing. Hence "was it built by something other than a plain-object
|
|
74
|
+
* constructor" rather than an identity test against the local `Object` — which
|
|
75
|
+
* would turn away a record whose prototype declares a non-function
|
|
76
|
+
* `constructor`, one whose constructor prototype owns `isPrototypeOf`, and
|
|
77
|
+
* every plain object arriving from another realm.
|
|
78
|
+
*
|
|
79
|
+
* The one departure: `constructor` is read off the PROTOTYPE, never off
|
|
80
|
+
* `input`. Reading it off the input is what `z.record` does, and it is the same
|
|
81
|
+
* prototype hazard this module exists to close — a map declaring an adapter
|
|
82
|
+
* named `constructor` shadows the property the check depends on, and the whole
|
|
83
|
+
* container is rejected over an entry that is merely unusually named. Starting
|
|
84
|
+
* one link up cannot be shadowed by any declared entry, and answers identically
|
|
85
|
+
* for every container that does not declare that name.
|
|
86
|
+
*/
|
|
87
|
+
function isRecord(input: unknown): input is object {
|
|
88
|
+
if (typeof input !== 'object' || input === null) return false
|
|
89
|
+
const prototype = Object.getPrototypeOf(input)
|
|
90
|
+
if (prototype === null) return true
|
|
91
|
+
const builder = (prototype as { constructor?: unknown }).constructor
|
|
92
|
+
if (typeof builder !== 'function') return true
|
|
93
|
+
const builderPrototype: unknown = (builder as { prototype?: unknown }).prototype
|
|
94
|
+
if (typeof builderPrototype !== 'object' || builderPrototype === null) return false
|
|
95
|
+
return Object.hasOwn(builderPrototype, 'isPrototypeOf')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The own ENUMERABLE STRING entries of a parsed container, read BY DESCRIPTOR.
|
|
100
|
+
* `input[key]` is wrong here: on any object that has a prototype, reading
|
|
101
|
+
* `"__proto__"` answers that prototype rather than the declared entry.
|
|
102
|
+
*
|
|
103
|
+
* The key set is `z.record`'s — own and enumerable — because a container that
|
|
104
|
+
* schema collected must still be collected the same way. Non-enumerable
|
|
105
|
+
* properties are internal bookkeeping rather than declarations, and are skipped
|
|
106
|
+
* in both.
|
|
107
|
+
*
|
|
108
|
+
* An own enumerable SYMBOL is an error rather than a skip. `z.record` ran every
|
|
109
|
+
* own key through its string key schema, so a symbol failed validation and the
|
|
110
|
+
* whole container was invalid; passing over it silently instead would be the
|
|
111
|
+
* exact drop this module exists to prevent, applied to the one key shape that
|
|
112
|
+
* can never name an entry in the map it produces.
|
|
113
|
+
*
|
|
114
|
+
* `label` is rendered verbatim into the diagnostic — `'[roles]'` for a TOML
|
|
115
|
+
* table, `'forges'` for a manifest map.
|
|
116
|
+
*/
|
|
117
|
+
export function ownEntries(
|
|
118
|
+
input: unknown,
|
|
119
|
+
label: string,
|
|
120
|
+
ctx: IssueSink,
|
|
121
|
+
shape?: string,
|
|
122
|
+
): [string, unknown][] {
|
|
123
|
+
if (!isRecord(input)) {
|
|
124
|
+
ctx.addIssue({ code: 'custom', message: `${label} must be ${shape ?? DEFAULT_SHAPE}` })
|
|
125
|
+
return []
|
|
126
|
+
}
|
|
127
|
+
for (const symbol of Object.getOwnPropertySymbols(input)) {
|
|
128
|
+
if (Object.getOwnPropertyDescriptor(input, symbol)?.enumerable !== true) continue
|
|
129
|
+
ctx.addIssue({
|
|
130
|
+
code: 'custom',
|
|
131
|
+
message: `${label} entry names must be strings; ${String(symbol)} cannot name an entry`,
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
const entries: [string, unknown][] = []
|
|
135
|
+
for (const key of Object.getOwnPropertyNames(input)) {
|
|
136
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key)
|
|
137
|
+
if (descriptor === undefined || !descriptor.enumerable) continue
|
|
138
|
+
// TOML only ever produces data properties, but a manifest is real
|
|
139
|
+
// JavaScript: `forges: { get acme() { … } }` is a valid declaration, and
|
|
140
|
+
// `descriptor.value` is `undefined` for it. Invoking the getter still never
|
|
141
|
+
// consults the prototype chain.
|
|
142
|
+
entries.push([key, 'value' in descriptor ? descriptor.value : descriptor.get?.call(input)])
|
|
143
|
+
}
|
|
144
|
+
return entries
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Record a named entry so the key survives verbatim. Plain assignment is what
|
|
148
|
+
* loses `__proto__`; `defineProperty` on a null-prototype target cannot. */
|
|
149
|
+
export function defineEntry<T>(entries: Record<string, T>, key: string, value: T): void {
|
|
150
|
+
Object.defineProperty(entries, key, {
|
|
151
|
+
value,
|
|
152
|
+
enumerable: true,
|
|
153
|
+
writable: true,
|
|
154
|
+
configurable: true,
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Whether one entry's value validated, and what it validated to. Success is
|
|
159
|
+
* explicit rather than read off the value: a schema may legitimately validate to
|
|
160
|
+
* `undefined`, and that entry is kept like any other. */
|
|
161
|
+
export type ParsedEntry<T> = { ok: true; value: T } | { ok: false }
|
|
162
|
+
|
|
163
|
+
/** Validate one open-map value, forwarding every issue under its own key. */
|
|
164
|
+
export function parseEntry<T>(
|
|
165
|
+
schema: z.ZodType<T>,
|
|
166
|
+
raw: unknown,
|
|
167
|
+
key: string,
|
|
168
|
+
ctx: IssueSink,
|
|
169
|
+
): ParsedEntry<T> {
|
|
170
|
+
const parsed = schema.safeParse(raw)
|
|
171
|
+
if (parsed.success) return { ok: true, value: parsed.data }
|
|
172
|
+
forwardIssues(parsed.error.issues, ctx, [key])
|
|
173
|
+
return { ok: false }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* An open map: author-chosen keys → strictly validated values, keys preserved.
|
|
178
|
+
*
|
|
179
|
+
* Preservation is the shared contract; the key vocabulary is not. An
|
|
180
|
+
* Autobuild-owned map names entries Autobuild itself must be able to address —
|
|
181
|
+
* a role to dispatch, a command to run — so a nameless entry there is dead
|
|
182
|
+
* configuration and an error. A plugin-owned pass-through map is not
|
|
183
|
+
* Autobuild's to narrow, and a map of adapter names is narrower still: a name
|
|
184
|
+
* made only of whitespace is unaddressable from configuration.
|
|
185
|
+
*/
|
|
186
|
+
export function openMap<T>(label: string, valueSchema: z.ZodType<T>, opts: OpenMapOptions = {}) {
|
|
187
|
+
const keys = opts.keys ?? 'nonempty'
|
|
188
|
+
return z
|
|
189
|
+
.unknown()
|
|
190
|
+
.transform((input, ctx): Record<string, T> => {
|
|
191
|
+
const entries: Record<string, T> = Object.create(null)
|
|
192
|
+
for (const [key, raw] of ownEntries(input, label, ctx, opts.shape)) {
|
|
193
|
+
if (!keyIsValid(key, keys)) {
|
|
194
|
+
ctx.addIssue({ code: 'custom', message: `${label} entry names must be ${keys}` })
|
|
195
|
+
continue
|
|
196
|
+
}
|
|
197
|
+
const parsed = parseEntry(valueSchema, raw, key, ctx)
|
|
198
|
+
if (parsed.ok) defineEntry(entries, key, parsed.value)
|
|
199
|
+
}
|
|
200
|
+
return entries
|
|
201
|
+
})
|
|
202
|
+
.prefault({})
|
|
203
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The checked-in risk table for the agent tool registry (operator/registry.ts).
|
|
3
|
+
*
|
|
4
|
+
* MCP annotations are per-tool hints, never per-argument, so a tool whose
|
|
5
|
+
* action set *may* destroy work carries `destructiveHint: true` for the whole
|
|
6
|
+
* tool, and each destructive action value is additionally named in the input
|
|
7
|
+
* field's description so a model still sees the per-value risk. Today exactly
|
|
8
|
+
* three tools may destroy work: `builds.control` (abort, discard),
|
|
9
|
+
* `builds.answer` (revise-spec), and `tickets.move` (a move into the
|
|
10
|
+
* configured ready state).
|
|
11
|
+
*
|
|
12
|
+
* The registry entries and the contract suite both read this table, so a
|
|
13
|
+
* change to any tool's risk class is a one-line visible diff this suite
|
|
14
|
+
* enforces. `approval` is the registry's own vocabulary (`'never'` for reads,
|
|
15
|
+
* `'default'` for mutators): the registry carries the class, and each binding
|
|
16
|
+
* decides what to do with it — the registry itself enforces only that a
|
|
17
|
+
* mutator refuses to run without an attributed identity.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export interface ToolAnnotationsTableEntry {
|
|
21
|
+
readOnlyHint: boolean
|
|
22
|
+
destructiveHint: boolean
|
|
23
|
+
idempotentHint: boolean
|
|
24
|
+
approval: 'never' | 'default'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The exact version-one tool names; the registry's closed table must match. */
|
|
28
|
+
export const OPERATOR_TOOL_ANNOTATIONS = {
|
|
29
|
+
'builds.list': {
|
|
30
|
+
readOnlyHint: true,
|
|
31
|
+
destructiveHint: false,
|
|
32
|
+
idempotentHint: true,
|
|
33
|
+
approval: 'never',
|
|
34
|
+
},
|
|
35
|
+
'builds.get': {
|
|
36
|
+
readOnlyHint: true,
|
|
37
|
+
destructiveHint: false,
|
|
38
|
+
idempotentHint: true,
|
|
39
|
+
approval: 'never',
|
|
40
|
+
},
|
|
41
|
+
'builds.events': {
|
|
42
|
+
readOnlyHint: true,
|
|
43
|
+
destructiveHint: false,
|
|
44
|
+
idempotentHint: true,
|
|
45
|
+
approval: 'never',
|
|
46
|
+
},
|
|
47
|
+
'builds.artifact': {
|
|
48
|
+
readOnlyHint: true,
|
|
49
|
+
destructiveHint: false,
|
|
50
|
+
idempotentHint: true,
|
|
51
|
+
approval: 'never',
|
|
52
|
+
},
|
|
53
|
+
'builds.control': {
|
|
54
|
+
readOnlyHint: false,
|
|
55
|
+
destructiveHint: true,
|
|
56
|
+
idempotentHint: false,
|
|
57
|
+
approval: 'default',
|
|
58
|
+
},
|
|
59
|
+
'builds.answer': {
|
|
60
|
+
readOnlyHint: false,
|
|
61
|
+
destructiveHint: true,
|
|
62
|
+
idempotentHint: false,
|
|
63
|
+
approval: 'default',
|
|
64
|
+
},
|
|
65
|
+
'repository.status': {
|
|
66
|
+
readOnlyHint: true,
|
|
67
|
+
destructiveHint: false,
|
|
68
|
+
idempotentHint: true,
|
|
69
|
+
approval: 'never',
|
|
70
|
+
},
|
|
71
|
+
'repository.settings': {
|
|
72
|
+
readOnlyHint: false,
|
|
73
|
+
destructiveHint: false,
|
|
74
|
+
idempotentHint: false,
|
|
75
|
+
approval: 'default',
|
|
76
|
+
},
|
|
77
|
+
'repository.bulk_control': {
|
|
78
|
+
readOnlyHint: false,
|
|
79
|
+
destructiveHint: false,
|
|
80
|
+
idempotentHint: false,
|
|
81
|
+
approval: 'default',
|
|
82
|
+
},
|
|
83
|
+
'harvest.status': {
|
|
84
|
+
readOnlyHint: true,
|
|
85
|
+
destructiveHint: false,
|
|
86
|
+
idempotentHint: true,
|
|
87
|
+
approval: 'never',
|
|
88
|
+
},
|
|
89
|
+
'harvest.control': {
|
|
90
|
+
readOnlyHint: false,
|
|
91
|
+
destructiveHint: false,
|
|
92
|
+
idempotentHint: false,
|
|
93
|
+
approval: 'default',
|
|
94
|
+
},
|
|
95
|
+
'tickets.list': {
|
|
96
|
+
readOnlyHint: true,
|
|
97
|
+
destructiveHint: false,
|
|
98
|
+
idempotentHint: true,
|
|
99
|
+
approval: 'never',
|
|
100
|
+
},
|
|
101
|
+
'tickets.get': {
|
|
102
|
+
readOnlyHint: true,
|
|
103
|
+
destructiveHint: false,
|
|
104
|
+
idempotentHint: true,
|
|
105
|
+
approval: 'never',
|
|
106
|
+
},
|
|
107
|
+
'tickets.create': {
|
|
108
|
+
readOnlyHint: false,
|
|
109
|
+
destructiveHint: false,
|
|
110
|
+
idempotentHint: false,
|
|
111
|
+
approval: 'default',
|
|
112
|
+
},
|
|
113
|
+
'tickets.update': {
|
|
114
|
+
readOnlyHint: false,
|
|
115
|
+
destructiveHint: false,
|
|
116
|
+
idempotentHint: false,
|
|
117
|
+
approval: 'default',
|
|
118
|
+
},
|
|
119
|
+
'tickets.block': {
|
|
120
|
+
readOnlyHint: false,
|
|
121
|
+
destructiveHint: false,
|
|
122
|
+
idempotentHint: false,
|
|
123
|
+
approval: 'default',
|
|
124
|
+
},
|
|
125
|
+
'tickets.unblock': {
|
|
126
|
+
readOnlyHint: false,
|
|
127
|
+
destructiveHint: false,
|
|
128
|
+
idempotentHint: false,
|
|
129
|
+
approval: 'default',
|
|
130
|
+
},
|
|
131
|
+
'tickets.move': {
|
|
132
|
+
readOnlyHint: false,
|
|
133
|
+
destructiveHint: true,
|
|
134
|
+
idempotentHint: false,
|
|
135
|
+
approval: 'default',
|
|
136
|
+
},
|
|
137
|
+
'notes.read': {
|
|
138
|
+
readOnlyHint: true,
|
|
139
|
+
destructiveHint: false,
|
|
140
|
+
idempotentHint: true,
|
|
141
|
+
approval: 'never',
|
|
142
|
+
},
|
|
143
|
+
'notes.write': {
|
|
144
|
+
readOnlyHint: false,
|
|
145
|
+
destructiveHint: false,
|
|
146
|
+
idempotentHint: false,
|
|
147
|
+
approval: 'default',
|
|
148
|
+
},
|
|
149
|
+
'sandbox.exec': {
|
|
150
|
+
readOnlyHint: false,
|
|
151
|
+
destructiveHint: false,
|
|
152
|
+
idempotentHint: false,
|
|
153
|
+
approval: 'default',
|
|
154
|
+
},
|
|
155
|
+
'sandbox.start': {
|
|
156
|
+
readOnlyHint: false,
|
|
157
|
+
destructiveHint: false,
|
|
158
|
+
idempotentHint: false,
|
|
159
|
+
approval: 'default',
|
|
160
|
+
},
|
|
161
|
+
'sandbox.wait': {
|
|
162
|
+
readOnlyHint: false,
|
|
163
|
+
destructiveHint: false,
|
|
164
|
+
idempotentHint: false,
|
|
165
|
+
approval: 'default',
|
|
166
|
+
},
|
|
167
|
+
'sandbox.read_file': {
|
|
168
|
+
readOnlyHint: false,
|
|
169
|
+
destructiveHint: false,
|
|
170
|
+
idempotentHint: false,
|
|
171
|
+
approval: 'default',
|
|
172
|
+
},
|
|
173
|
+
'sandbox.write_file': {
|
|
174
|
+
readOnlyHint: false,
|
|
175
|
+
destructiveHint: false,
|
|
176
|
+
idempotentHint: false,
|
|
177
|
+
approval: 'default',
|
|
178
|
+
},
|
|
179
|
+
'sandbox.reset': {
|
|
180
|
+
readOnlyHint: false,
|
|
181
|
+
destructiveHint: true,
|
|
182
|
+
idempotentHint: false,
|
|
183
|
+
approval: 'default',
|
|
184
|
+
},
|
|
185
|
+
} as const satisfies Record<string, ToolAnnotationsTableEntry>
|
|
186
|
+
|
|
187
|
+
export type OperatorToolName = keyof typeof OPERATOR_TOOL_ANNOTATIONS
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import type { BuildControlResult } from '../cli/build-control'
|
|
2
|
+
import type { BulkControlSummary } from '../cli/bulk-control'
|
|
3
|
+
import type { HarvestStatusView } from '../cli/harvest'
|
|
4
|
+
import type { RepositoryStatus } from '../cli/repository-status'
|
|
5
|
+
import type { BuildSummary } from '../cli/status'
|
|
6
|
+
import {
|
|
7
|
+
AUTOBUILD_VERSION,
|
|
8
|
+
AUTOBUILD_VERSION_HEADER,
|
|
9
|
+
REMOTE_STORE_PROTOCOL_VERSION,
|
|
10
|
+
REMOTE_STORE_PROTOCOL_VERSION_HEADER,
|
|
11
|
+
} from '../store/remote/version'
|
|
12
|
+
import {
|
|
13
|
+
operatorErrorSchema,
|
|
14
|
+
type OperatorAnswerRequest,
|
|
15
|
+
type OperatorBuildControlRequest,
|
|
16
|
+
type OperatorTicketBlockerRequest,
|
|
17
|
+
type OperatorTicketCreateRequest,
|
|
18
|
+
type OperatorTicketMoveRequest,
|
|
19
|
+
type OperatorTicketUpdateRequest,
|
|
20
|
+
} from './protocol'
|
|
21
|
+
import type { OperatorBuildView, OperatorDashboardSnapshot } from './query'
|
|
22
|
+
import type { OperatorTicketDetail, OperatorTicketQueue } from './tickets'
|
|
23
|
+
import type { OperatorSessionView } from './sessions'
|
|
24
|
+
import type { SessionRecord } from '../store/types'
|
|
25
|
+
import type { StreamRead } from '../store/streams/types'
|
|
26
|
+
import type {
|
|
27
|
+
OperatorSessionApprovalRequest,
|
|
28
|
+
OperatorSessionCreateRequest,
|
|
29
|
+
OperatorSessionMessageRequest,
|
|
30
|
+
OperatorSessionWakeRequest,
|
|
31
|
+
} from './protocol'
|
|
32
|
+
|
|
33
|
+
export class OperatorApiError extends Error {
|
|
34
|
+
constructor(
|
|
35
|
+
readonly status: number,
|
|
36
|
+
readonly kind: string,
|
|
37
|
+
readonly code?: string,
|
|
38
|
+
readonly progress?: unknown,
|
|
39
|
+
) {
|
|
40
|
+
super(kind)
|
|
41
|
+
this.name = 'OperatorApiError'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface OperatorApiClientOptions {
|
|
46
|
+
url: string
|
|
47
|
+
token: string
|
|
48
|
+
fetchFn?: typeof fetch
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface DownloadedArtifact {
|
|
52
|
+
content: Uint8Array
|
|
53
|
+
kind: string
|
|
54
|
+
revision: number
|
|
55
|
+
blobRef: string
|
|
56
|
+
disposition?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class OperatorApiClient {
|
|
60
|
+
private readonly base: string
|
|
61
|
+
private readonly fetchFn: typeof fetch
|
|
62
|
+
constructor(private readonly options: OperatorApiClientOptions) {
|
|
63
|
+
this.base = options.url.replace(/\/+$/, '')
|
|
64
|
+
this.fetchFn = options.fetchFn ?? fetch
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private repoPath(repo: string, suffix: string): string {
|
|
68
|
+
return `/operator/v1/repos/${encodeURIComponent(repo)}/${suffix}`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private async request<T>(repo: string, suffix: string, init: RequestInit = {}): Promise<T> {
|
|
72
|
+
const headers = new Headers(init.headers)
|
|
73
|
+
headers.set(AUTOBUILD_VERSION_HEADER, AUTOBUILD_VERSION)
|
|
74
|
+
headers.set(REMOTE_STORE_PROTOCOL_VERSION_HEADER, REMOTE_STORE_PROTOCOL_VERSION)
|
|
75
|
+
headers.set('authorization', `Bearer ${this.options.token}`)
|
|
76
|
+
if (init.body !== undefined) headers.set('content-type', 'application/json')
|
|
77
|
+
const response = await this.fetchFn(`${this.base}${this.repoPath(repo, suffix)}`, {
|
|
78
|
+
...init,
|
|
79
|
+
headers,
|
|
80
|
+
})
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
const parsed = operatorErrorSchema.safeParse(await response.json().catch(() => null))
|
|
83
|
+
if (!parsed.success) throw new Error(`operator API returned HTTP ${response.status}`)
|
|
84
|
+
const error = new OperatorApiError(
|
|
85
|
+
response.status,
|
|
86
|
+
parsed.data.kind,
|
|
87
|
+
parsed.data.code,
|
|
88
|
+
parsed.data.progress,
|
|
89
|
+
)
|
|
90
|
+
error.message = parsed.data.error
|
|
91
|
+
throw error
|
|
92
|
+
}
|
|
93
|
+
return (await response.json()) as T
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
listBuilds(repo: string, scope: 'active' | 'queued' | 'all' = 'active'): Promise<BuildSummary[]> {
|
|
97
|
+
return this.request(repo, `builds?scope=${scope}`)
|
|
98
|
+
}
|
|
99
|
+
getBuild(repo: string, slug: string): Promise<OperatorBuildView> {
|
|
100
|
+
return this.request(repo, `builds/${encodeURIComponent(slug)}`)
|
|
101
|
+
}
|
|
102
|
+
dashboard(repo: string): Promise<OperatorDashboardSnapshot> {
|
|
103
|
+
return this.request(repo, 'dashboard')
|
|
104
|
+
}
|
|
105
|
+
repositoryStatus(repo: string): Promise<RepositoryStatus> {
|
|
106
|
+
return this.request(repo, 'status')
|
|
107
|
+
}
|
|
108
|
+
harvestStatus(repo: string): Promise<HarvestStatusView> {
|
|
109
|
+
return this.request(repo, 'harvest/status')
|
|
110
|
+
}
|
|
111
|
+
listTickets(
|
|
112
|
+
repo: string,
|
|
113
|
+
filters: { state?: string; labels?: string[] } = {},
|
|
114
|
+
): Promise<OperatorTicketQueue> {
|
|
115
|
+
const query = new URLSearchParams()
|
|
116
|
+
if (filters.state !== undefined) query.set('state', filters.state)
|
|
117
|
+
for (const label of filters.labels ?? []) query.append('label', label)
|
|
118
|
+
const suffix = query.size > 0 ? `tickets?${query}` : 'tickets'
|
|
119
|
+
return this.request(repo, suffix)
|
|
120
|
+
}
|
|
121
|
+
getTicket(repo: string, id: string): Promise<OperatorTicketDetail> {
|
|
122
|
+
return this.request(repo, `tickets/${encodeURIComponent(id)}`)
|
|
123
|
+
}
|
|
124
|
+
createTicket(repo: string, request: OperatorTicketCreateRequest): Promise<OperatorTicketDetail> {
|
|
125
|
+
return this.request(repo, 'tickets', { method: 'POST', body: JSON.stringify(request) })
|
|
126
|
+
}
|
|
127
|
+
updateTicket(
|
|
128
|
+
repo: string,
|
|
129
|
+
id: string,
|
|
130
|
+
request: OperatorTicketUpdateRequest,
|
|
131
|
+
): Promise<OperatorTicketDetail> {
|
|
132
|
+
return this.request(repo, `tickets/${encodeURIComponent(id)}`, {
|
|
133
|
+
method: 'PATCH',
|
|
134
|
+
body: JSON.stringify(request),
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
moveTicket(
|
|
138
|
+
repo: string,
|
|
139
|
+
id: string,
|
|
140
|
+
request: OperatorTicketMoveRequest,
|
|
141
|
+
): Promise<OperatorTicketDetail> {
|
|
142
|
+
return this.request(repo, `tickets/${encodeURIComponent(id)}/move`, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
body: JSON.stringify(request),
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
blockTicket(
|
|
148
|
+
repo: string,
|
|
149
|
+
id: string,
|
|
150
|
+
request: OperatorTicketBlockerRequest,
|
|
151
|
+
): Promise<OperatorTicketDetail> {
|
|
152
|
+
return this.request(repo, `tickets/${encodeURIComponent(id)}/block`, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
body: JSON.stringify(request),
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
unblockTicket(
|
|
158
|
+
repo: string,
|
|
159
|
+
id: string,
|
|
160
|
+
request: OperatorTicketBlockerRequest,
|
|
161
|
+
): Promise<OperatorTicketDetail> {
|
|
162
|
+
return this.request(repo, `tickets/${encodeURIComponent(id)}/unblock`, {
|
|
163
|
+
method: 'POST',
|
|
164
|
+
body: JSON.stringify(request),
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
listSessions(repo: string): Promise<SessionRecord[]> {
|
|
168
|
+
return this.request(repo, 'sessions')
|
|
169
|
+
}
|
|
170
|
+
createSession(repo: string, request: OperatorSessionCreateRequest): Promise<SessionRecord> {
|
|
171
|
+
return this.request(repo, 'sessions', { method: 'POST', body: JSON.stringify(request) })
|
|
172
|
+
}
|
|
173
|
+
getSession(repo: string, sid: string): Promise<OperatorSessionView> {
|
|
174
|
+
return this.request(repo, `sessions/${encodeURIComponent(sid)}`)
|
|
175
|
+
}
|
|
176
|
+
postSessionMessage(
|
|
177
|
+
repo: string,
|
|
178
|
+
sid: string,
|
|
179
|
+
request: OperatorSessionMessageRequest,
|
|
180
|
+
): Promise<{ ok: boolean }> {
|
|
181
|
+
return this.request(repo, `sessions/${encodeURIComponent(sid)}/messages`, {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
body: JSON.stringify(request),
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
setSessionWake(
|
|
187
|
+
repo: string,
|
|
188
|
+
sid: string,
|
|
189
|
+
request: OperatorSessionWakeRequest,
|
|
190
|
+
): Promise<{ ok: boolean }> {
|
|
191
|
+
return this.request(repo, `sessions/${encodeURIComponent(sid)}/wake`, {
|
|
192
|
+
method: 'PUT',
|
|
193
|
+
body: JSON.stringify(request),
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
answerSessionApproval(
|
|
197
|
+
repo: string,
|
|
198
|
+
sid: string,
|
|
199
|
+
request: OperatorSessionApprovalRequest,
|
|
200
|
+
): Promise<{ ok: boolean }> {
|
|
201
|
+
return this.request(repo, `sessions/${encodeURIComponent(sid)}/approvals`, {
|
|
202
|
+
method: 'POST',
|
|
203
|
+
body: JSON.stringify(request),
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
archiveSession(repo: string, sid: string): Promise<{ ok: boolean }> {
|
|
207
|
+
return this.request(repo, `sessions/${encodeURIComponent(sid)}/archive`, {
|
|
208
|
+
method: 'POST',
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
readSessionTurnStream(
|
|
212
|
+
repo: string,
|
|
213
|
+
sid: string,
|
|
214
|
+
turn: string,
|
|
215
|
+
opts: { since?: number; waitSeconds?: number } = {},
|
|
216
|
+
): Promise<StreamRead> {
|
|
217
|
+
const query = new URLSearchParams({ since: String(opts?.since ?? 0) })
|
|
218
|
+
if (opts?.waitSeconds !== undefined) query.set('wait', String(opts.waitSeconds))
|
|
219
|
+
return this.request(
|
|
220
|
+
repo,
|
|
221
|
+
`sessions/${encodeURIComponent(sid)}/turns/${encodeURIComponent(turn)}/stream?${query}`,
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
controlBuild(
|
|
225
|
+
repo: string,
|
|
226
|
+
slug: string,
|
|
227
|
+
request: OperatorBuildControlRequest,
|
|
228
|
+
): Promise<BuildControlResult> {
|
|
229
|
+
return this.request(repo, `builds/${encodeURIComponent(slug)}/control`, {
|
|
230
|
+
method: 'POST',
|
|
231
|
+
body: JSON.stringify(request),
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
/** The registry's generic protocol face: execute any tool from the closed
|
|
235
|
+
* table by name. The body is the tool's input (repo included); failures
|
|
236
|
+
* parse into OperatorApiError exactly like the typed routes. */
|
|
237
|
+
callTool(repo: string, tool: string, input: unknown): Promise<unknown> {
|
|
238
|
+
return this.request(repo, `tools/${encodeURIComponent(tool)}`, {
|
|
239
|
+
method: 'POST',
|
|
240
|
+
body: JSON.stringify(input),
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
answer(repo: string, slug: string, request: OperatorAnswerRequest): Promise<BuildControlResult> {
|
|
244
|
+
return this.request(repo, `builds/${encodeURIComponent(slug)}/answer`, {
|
|
245
|
+
method: 'POST',
|
|
246
|
+
body: JSON.stringify(request),
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
setIntake(repo: string, enabled: boolean): Promise<unknown> {
|
|
250
|
+
return this.request(repo, 'settings/intake', {
|
|
251
|
+
method: 'PUT',
|
|
252
|
+
body: JSON.stringify({ enabled }),
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
toggleIntake(repo: string): Promise<unknown> {
|
|
256
|
+
return this.request(repo, 'settings/intake/toggle', { method: 'POST' })
|
|
257
|
+
}
|
|
258
|
+
setAutoMergeDefault(repo: string, enabled: boolean): Promise<unknown> {
|
|
259
|
+
return this.request(repo, 'settings/auto-merge-default', {
|
|
260
|
+
method: 'PUT',
|
|
261
|
+
body: JSON.stringify({ enabled }),
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
toggleAutoMergeDefault(repo: string): Promise<unknown> {
|
|
265
|
+
return this.request(repo, 'settings/auto-merge-default/toggle', { method: 'POST' })
|
|
266
|
+
}
|
|
267
|
+
bulkControl(repo: string, action: 'pause' | 'resume'): Promise<BulkControlSummary> {
|
|
268
|
+
return this.request(repo, 'bulk-control', { method: 'POST', body: JSON.stringify({ action }) })
|
|
269
|
+
}
|
|
270
|
+
toggleHarvest(repo: string): Promise<unknown> {
|
|
271
|
+
return this.request(repo, 'harvest/control', {
|
|
272
|
+
method: 'POST',
|
|
273
|
+
body: JSON.stringify({ action: 'toggle-gate' }),
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
controlHarvestRun(repo: string, run: string): Promise<unknown> {
|
|
277
|
+
return this.request(repo, 'harvest/control', {
|
|
278
|
+
method: 'POST',
|
|
279
|
+
body: JSON.stringify({ action: 'run', run }),
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
async downloadArtifact(
|
|
283
|
+
repo: string,
|
|
284
|
+
slug: string,
|
|
285
|
+
kind: string,
|
|
286
|
+
rev?: number,
|
|
287
|
+
): Promise<DownloadedArtifact> {
|
|
288
|
+
const suffix = `builds/${encodeURIComponent(slug)}/artifacts/${encodeURIComponent(kind)}${rev === undefined ? '' : `?rev=${rev}`}`
|
|
289
|
+
const headers = new Headers({
|
|
290
|
+
[AUTOBUILD_VERSION_HEADER]: AUTOBUILD_VERSION,
|
|
291
|
+
[REMOTE_STORE_PROTOCOL_VERSION_HEADER]: REMOTE_STORE_PROTOCOL_VERSION,
|
|
292
|
+
authorization: `Bearer ${this.options.token}`,
|
|
293
|
+
})
|
|
294
|
+
const response = await this.fetchFn(`${this.base}${this.repoPath(repo, suffix)}`, { headers })
|
|
295
|
+
if (!response.ok) {
|
|
296
|
+
const parsed = operatorErrorSchema.parse(await response.json())
|
|
297
|
+
const error = new OperatorApiError(response.status, parsed.kind, parsed.code, parsed.progress)
|
|
298
|
+
error.message = parsed.error
|
|
299
|
+
throw error
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
content: new Uint8Array(await response.arrayBuffer()),
|
|
303
|
+
kind: response.headers.get('x-autobuild-artifact-kind') ?? kind,
|
|
304
|
+
revision: Number(response.headers.get('x-autobuild-artifact-revision')),
|
|
305
|
+
blobRef: response.headers.get('x-autobuild-artifact-blob-ref') ?? '',
|
|
306
|
+
...(response.headers.get('content-disposition') !== null
|
|
307
|
+
? { disposition: response.headers.get('content-disposition')! }
|
|
308
|
+
: {}),
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|