@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.
Files changed (251) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +192 -0
  3. package/SPEC.md +2269 -0
  4. package/bin/ab-build-runner.ts +47 -0
  5. package/bin/ab-dev.ts +27 -0
  6. package/bin/ab-dispatch-kernel.ts +92 -0
  7. package/bin/ab-harvest-runner.ts +29 -0
  8. package/bin/ab-init-probe.ts +19 -0
  9. package/bin/ab-process-group-reaper.ts +32 -0
  10. package/bin/ab.ts +6 -0
  11. package/bin/agent/ab +3 -0
  12. package/docs/answering-blocked-builds.md +89 -0
  13. package/docs/architecture.md +726 -0
  14. package/docs/assets/headline-wide.png +0 -0
  15. package/docs/configuration.md +1623 -0
  16. package/docs/hosted-dispatcher-cutover-validation.md +178 -0
  17. package/docs/hosted-dispatcher.md +204 -0
  18. package/docs/mcp.md +119 -0
  19. package/docs/operator-api.md +186 -0
  20. package/docs/remote-store-protocol.md +972 -0
  21. package/docs/remote-ticket-protocol.md +79 -0
  22. package/docs/setup.md +289 -0
  23. package/docs/spec-standard.md +63 -0
  24. package/docs/ticket-dependencies.md +59 -0
  25. package/docs/vercel-sandbox-rollout-probe.md +7 -0
  26. package/package.json +135 -0
  27. package/packages/core/src/cli/args.ts +74 -0
  28. package/packages/core/src/cli/artifact.ts +182 -0
  29. package/packages/core/src/cli/binary.ts +198 -0
  30. package/packages/core/src/cli/build-control.ts +789 -0
  31. package/packages/core/src/cli/build-progress.ts +55 -0
  32. package/packages/core/src/cli/bulk-control.ts +385 -0
  33. package/packages/core/src/cli/context.ts +470 -0
  34. package/packages/core/src/cli/dashboard/actions.ts +59 -0
  35. package/packages/core/src/cli/dashboard/cells.ts +92 -0
  36. package/packages/core/src/cli/dashboard/composer.ts +230 -0
  37. package/packages/core/src/cli/dashboard/detail.ts +100 -0
  38. package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
  39. package/packages/core/src/cli/dashboard/live.ts +152 -0
  40. package/packages/core/src/cli/dashboard/model.ts +1173 -0
  41. package/packages/core/src/cli/dashboard/poll.ts +196 -0
  42. package/packages/core/src/cli/dashboard/render.ts +1568 -0
  43. package/packages/core/src/cli/dashboard/selection.ts +66 -0
  44. package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
  45. package/packages/core/src/cli/dashboard/session-view.ts +506 -0
  46. package/packages/core/src/cli/dashboard/transcript.ts +108 -0
  47. package/packages/core/src/cli/dev.ts +46 -0
  48. package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
  49. package/packages/core/src/cli/dispatch-process.ts +259 -0
  50. package/packages/core/src/cli/dispatch.ts +3692 -0
  51. package/packages/core/src/cli/dotenv.ts +48 -0
  52. package/packages/core/src/cli/env.ts +206 -0
  53. package/packages/core/src/cli/harvest.ts +698 -0
  54. package/packages/core/src/cli/help.ts +713 -0
  55. package/packages/core/src/cli/init-agent.ts +106 -0
  56. package/packages/core/src/cli/init-validation.ts +583 -0
  57. package/packages/core/src/cli/init.ts +756 -0
  58. package/packages/core/src/cli/installation.ts +333 -0
  59. package/packages/core/src/cli/keyboard.ts +261 -0
  60. package/packages/core/src/cli/main.ts +1399 -0
  61. package/packages/core/src/cli/mcp.ts +279 -0
  62. package/packages/core/src/cli/models.ts +65 -0
  63. package/packages/core/src/cli/observe.ts +55 -0
  64. package/packages/core/src/cli/plugin.ts +227 -0
  65. package/packages/core/src/cli/pr-attachments.ts +138 -0
  66. package/packages/core/src/cli/pr-summary.ts +124 -0
  67. package/packages/core/src/cli/publication-settlement.ts +188 -0
  68. package/packages/core/src/cli/repo-state.ts +213 -0
  69. package/packages/core/src/cli/repository-status.ts +80 -0
  70. package/packages/core/src/cli/self-update.ts +443 -0
  71. package/packages/core/src/cli/status.ts +895 -0
  72. package/packages/core/src/cli/store-opening.contract.ts +289 -0
  73. package/packages/core/src/cli/store-opening.ts +167 -0
  74. package/packages/core/src/cli/store-ref.ts +33 -0
  75. package/packages/core/src/cli/terminal-restore.ts +174 -0
  76. package/packages/core/src/cli/terminal.ts +361 -0
  77. package/packages/core/src/cli/terminals.ts +995 -0
  78. package/packages/core/src/cli/testkit.ts +194 -0
  79. package/packages/core/src/cli/ticket.ts +484 -0
  80. package/packages/core/src/cli/upgrade-agent.ts +180 -0
  81. package/packages/core/src/cli/upgrade-commit.ts +468 -0
  82. package/packages/core/src/cli/upgrade-notice.ts +91 -0
  83. package/packages/core/src/cli/upgrade-progress.ts +110 -0
  84. package/packages/core/src/cli/upgrade.ts +888 -0
  85. package/packages/core/src/cli/wait.ts +587 -0
  86. package/packages/core/src/cli/watch.ts +932 -0
  87. package/packages/core/src/config/live.ts +255 -0
  88. package/packages/core/src/config/load.ts +98 -0
  89. package/packages/core/src/config/roles.ts +438 -0
  90. package/packages/core/src/config/schema.ts +774 -0
  91. package/packages/core/src/distribution.ts +24 -0
  92. package/packages/core/src/events/catalog.ts +201 -0
  93. package/packages/core/src/events/envelope.ts +59 -0
  94. package/packages/core/src/events/payloads.ts +618 -0
  95. package/packages/core/src/events/repository.ts +559 -0
  96. package/packages/core/src/events/sessions.ts +180 -0
  97. package/packages/core/src/harvest/schema.ts +147 -0
  98. package/packages/core/src/ids.ts +45 -0
  99. package/packages/core/src/integration/harness.ts +681 -0
  100. package/packages/core/src/kernel/auto-merge.ts +259 -0
  101. package/packages/core/src/kernel/converge.ts +117 -0
  102. package/packages/core/src/kernel/dispatch-settings.ts +60 -0
  103. package/packages/core/src/kernel/dispatch-status.ts +141 -0
  104. package/packages/core/src/kernel/engine.ts +1047 -0
  105. package/packages/core/src/kernel/harvest.ts +823 -0
  106. package/packages/core/src/kernel/origin.ts +69 -0
  107. package/packages/core/src/kernel/phases.ts +196 -0
  108. package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
  109. package/packages/core/src/kernel/pr-attachments.ts +82 -0
  110. package/packages/core/src/kernel/reducer.ts +752 -0
  111. package/packages/core/src/kernel/stall.ts +155 -0
  112. package/packages/core/src/kernel/verify-applicability.ts +52 -0
  113. package/packages/core/src/markdown.ts +67 -0
  114. package/packages/core/src/ontology.ts +288 -0
  115. package/packages/core/src/open-map.ts +203 -0
  116. package/packages/core/src/operator/annotations.ts +187 -0
  117. package/packages/core/src/operator/client.ts +311 -0
  118. package/packages/core/src/operator/control.ts +117 -0
  119. package/packages/core/src/operator/index.ts +62 -0
  120. package/packages/core/src/operator/presentation.ts +22 -0
  121. package/packages/core/src/operator/protocol.ts +98 -0
  122. package/packages/core/src/operator/query.ts +201 -0
  123. package/packages/core/src/operator/registry.ts +1043 -0
  124. package/packages/core/src/operator/requests.ts +138 -0
  125. package/packages/core/src/operator/sandbox.ts +458 -0
  126. package/packages/core/src/operator/server.ts +616 -0
  127. package/packages/core/src/operator/sessions.ts +223 -0
  128. package/packages/core/src/operator/tickets.ts +177 -0
  129. package/packages/core/src/plugin-sdk/index.ts +174 -0
  130. package/packages/core/src/plugins/contract-entry.ts +74 -0
  131. package/packages/core/src/plugins/contract-env.ts +3 -0
  132. package/packages/core/src/plugins/load.ts +259 -0
  133. package/packages/core/src/plugins/manifest.ts +236 -0
  134. package/packages/core/src/plugins/registry.ts +274 -0
  135. package/packages/core/src/plugins/runtimes.ts +75 -0
  136. package/packages/core/src/ports/forge/contract.ts +258 -0
  137. package/packages/core/src/ports/forge/create.ts +74 -0
  138. package/packages/core/src/ports/forge/fake.ts +488 -0
  139. package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
  140. package/packages/core/src/ports/forge/github-transport.ts +352 -0
  141. package/packages/core/src/ports/forge/github.ts +1162 -0
  142. package/packages/core/src/ports/forge/local-git.ts +568 -0
  143. package/packages/core/src/ports/runner/claude.ts +873 -0
  144. package/packages/core/src/ports/runner/cli-stream.ts +90 -0
  145. package/packages/core/src/ports/runner/codex.ts +873 -0
  146. package/packages/core/src/ports/runner/contract.ts +492 -0
  147. package/packages/core/src/ports/runner/fake.ts +213 -0
  148. package/packages/core/src/ports/runner/json-record.ts +2 -0
  149. package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
  150. package/packages/core/src/ports/runner/one-shot.ts +29 -0
  151. package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
  152. package/packages/core/src/ports/runner/pi-bridge.js +6 -0
  153. package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
  154. package/packages/core/src/ports/runner/pi.ts +617 -0
  155. package/packages/core/src/ports/runner/production.ts +74 -0
  156. package/packages/core/src/ports/runner/provider-error.ts +121 -0
  157. package/packages/core/src/ports/runner/routing.ts +234 -0
  158. package/packages/core/src/ports/runner/runtime.ts +214 -0
  159. package/packages/core/src/ports/runner/session-env.ts +34 -0
  160. package/packages/core/src/ports/runner/stream-parts.ts +103 -0
  161. package/packages/core/src/ports/tickets/contract.ts +570 -0
  162. package/packages/core/src/ports/tickets/create.ts +135 -0
  163. package/packages/core/src/ports/tickets/fake.ts +215 -0
  164. package/packages/core/src/ports/tickets/file.ts +559 -0
  165. package/packages/core/src/ports/tickets/linear.ts +811 -0
  166. package/packages/core/src/ports/tickets/operations.ts +161 -0
  167. package/packages/core/src/ports/tickets/remote-index.ts +13 -0
  168. package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
  169. package/packages/core/src/ports/tickets/remote-server.ts +185 -0
  170. package/packages/core/src/ports/tickets/remote.ts +130 -0
  171. package/packages/core/src/ports/tickets/update.ts +51 -0
  172. package/packages/core/src/ports/types.ts +528 -0
  173. package/packages/core/src/ports/workspace/build-execution.ts +123 -0
  174. package/packages/core/src/ports/workspace/contract.ts +136 -0
  175. package/packages/core/src/ports/workspace/create.ts +132 -0
  176. package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
  177. package/packages/core/src/ports/workspace/fake.ts +432 -0
  178. package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
  179. package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
  180. package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
  181. package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
  182. package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
  183. package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
  184. package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
  185. package/packages/core/src/processes/build-child-exit.ts +78 -0
  186. package/packages/core/src/processes/build-child.ts +119 -0
  187. package/packages/core/src/processes/build-execution-state.ts +95 -0
  188. package/packages/core/src/processes/build-parent-watch.ts +35 -0
  189. package/packages/core/src/processes/build-runner.ts +2614 -0
  190. package/packages/core/src/processes/dispatcher.ts +2825 -0
  191. package/packages/core/src/processes/execution-settlement.ts +171 -0
  192. package/packages/core/src/processes/harvest-child.ts +115 -0
  193. package/packages/core/src/processes/harvest-execution-state.ts +121 -0
  194. package/packages/core/src/processes/harvest-runner.ts +1656 -0
  195. package/packages/core/src/processes/harvest.ts +595 -0
  196. package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
  197. package/packages/core/src/processes/phase-failure-budget.ts +52 -0
  198. package/packages/core/src/processes/process-group-reaper.ts +35 -0
  199. package/packages/core/src/processes/process-group.ts +65 -0
  200. package/packages/core/src/processes/publication-loss.ts +62 -0
  201. package/packages/core/src/processes/publication-state.ts +93 -0
  202. package/packages/core/src/processes/sandbox-state.ts +101 -0
  203. package/packages/core/src/registry.ts +24 -0
  204. package/packages/core/src/sessions/reducer.ts +185 -0
  205. package/packages/core/src/skills.ts +7 -0
  206. package/packages/core/src/spec-standard.ts +47 -0
  207. package/packages/core/src/store/adapter.ts +80 -0
  208. package/packages/core/src/store/build-scope.ts +272 -0
  209. package/packages/core/src/store/contract.ts +2581 -0
  210. package/packages/core/src/store/local/blobs.ts +38 -0
  211. package/packages/core/src/store/local/schema.ts +180 -0
  212. package/packages/core/src/store/local/store.ts +1713 -0
  213. package/packages/core/src/store/memory.ts +1138 -0
  214. package/packages/core/src/store/phase-session.ts +425 -0
  215. package/packages/core/src/store/remote/client.ts +780 -0
  216. package/packages/core/src/store/remote/index.ts +12 -0
  217. package/packages/core/src/store/remote/protocol.ts +348 -0
  218. package/packages/core/src/store/remote/server.ts +923 -0
  219. package/packages/core/src/store/remote/token.ts +137 -0
  220. package/packages/core/src/store/remote/version.ts +10 -0
  221. package/packages/core/src/store/retention.ts +91 -0
  222. package/packages/core/src/store/session-handle.ts +265 -0
  223. package/packages/core/src/store/streams/assemble.ts +79 -0
  224. package/packages/core/src/store/streams/lock.ts +42 -0
  225. package/packages/core/src/store/streams/session-writer.ts +306 -0
  226. package/packages/core/src/store/streams/types.ts +171 -0
  227. package/packages/core/src/store/streams/wait.ts +127 -0
  228. package/packages/core/src/store/subscribe.ts +105 -0
  229. package/packages/core/src/store/types.ts +466 -0
  230. package/packages/core/src/testing/fixed.ts +44 -0
  231. package/packages/core/src/testing/store-failures.ts +32 -0
  232. package/packages/core/src/zod-issues.ts +91 -0
  233. package/skills/code-review/SKILL.md +100 -0
  234. package/skills/finalize/SKILL.md +44 -0
  235. package/skills/guide/SKILL.md +1781 -0
  236. package/skills/guide/references/operator-api.md +164 -0
  237. package/skills/guide/references/plugin-authoring.md +354 -0
  238. package/skills/guide/references/remote-store-protocol.md +972 -0
  239. package/skills/guide/references/remote-ticket-protocol.md +79 -0
  240. package/skills/guide/references/setup.md +289 -0
  241. package/skills/guide/references/spec-standard.md +63 -0
  242. package/skills/guide/references/ticket-dependencies.md +59 -0
  243. package/skills/harvest/SKILL.md +66 -0
  244. package/skills/harvest-review/SKILL.md +63 -0
  245. package/skills/implement/SKILL.md +82 -0
  246. package/skills/plan/SKILL.md +123 -0
  247. package/skills/plan-review/SKILL.md +121 -0
  248. package/skills/reconcile/SKILL.md +65 -0
  249. package/skills/spec/SKILL.md +132 -0
  250. package/skills/tickets/SKILL.md +139 -0
  251. package/templates/autobuild.toml +36 -0
@@ -0,0 +1,117 @@
1
+ import { humanActor, type Via } from '../events/envelope'
2
+ import type { RepositoryEventEnvelope } from '../events/repository'
3
+ import { reduceDispatchSettings } from '../kernel/dispatch-settings'
4
+ import { reduceHarvest } from '../kernel/harvest'
5
+ import type { BuildStore } from '../store/types'
6
+ import { projectHarvest, type HarvestRunAction } from '../cli/dashboard/model'
7
+
8
+ export class OperatorControlError extends Error {
9
+ constructor(
10
+ readonly code:
11
+ | 'stale-harvest-run'
12
+ | 'harvest-paused'
13
+ | 'harvest-pending'
14
+ | 'harvest-unavailable',
15
+ message: string,
16
+ ) {
17
+ super(message)
18
+ this.name = 'OperatorControlError'
19
+ }
20
+ }
21
+
22
+ export type RepositorySetting = 'intake' | 'auto-merge-default'
23
+
24
+ export async function setRepositorySetting(opts: {
25
+ store: BuildStore
26
+ repo: string
27
+ user: string
28
+ setting: RepositorySetting
29
+ enabled: boolean
30
+ /** Delegated-write attribution marker threaded onto the event's actor. */
31
+ via?: Via
32
+ }): Promise<{ enabled: boolean; event: RepositoryEventEnvelope }> {
33
+ await opts.store.ensureRepo(opts.repo)
34
+ const event =
35
+ opts.setting === 'intake'
36
+ ? await opts.store.appendRepo(opts.repo, {
37
+ actor: humanActor(opts.user, opts.via),
38
+ type: 'dispatcher.intake-set',
39
+ payload: { enabled: opts.enabled },
40
+ })
41
+ : await opts.store.appendRepo(opts.repo, {
42
+ actor: humanActor(opts.user, opts.via),
43
+ type: 'dispatcher.auto-merge-default-set',
44
+ payload: { enabled: opts.enabled },
45
+ })
46
+ return { enabled: opts.enabled, event }
47
+ }
48
+
49
+ export async function toggleRepositorySetting(opts: {
50
+ store: BuildStore
51
+ repo: string
52
+ user: string
53
+ setting: RepositorySetting
54
+ via?: Via
55
+ }): Promise<{ enabled: boolean; event: RepositoryEventEnvelope }> {
56
+ await opts.store.ensureRepo(opts.repo)
57
+ const settings = reduceDispatchSettings(await opts.store.getRepoEvents(opts.repo))
58
+ const enabled = opts.setting === 'intake' ? !settings.intake : !settings.defaultAutoMerge
59
+ return setRepositorySetting({ ...opts, enabled })
60
+ }
61
+
62
+ export async function toggleHarvestGate(opts: {
63
+ store: BuildStore
64
+ repo: string
65
+ user: string
66
+ via?: Via
67
+ }): Promise<{ command: 'pause' | 'resume'; event: RepositoryEventEnvelope }> {
68
+ await opts.store.ensureRepo(opts.repo)
69
+ const state = reduceHarvest(await opts.store.getRepoEvents(opts.repo))
70
+ const pending = state.pendingCommands.at(-1)
71
+ const requestedPaused = pending === undefined ? state.paused : pending.command === 'pause'
72
+ const command = requestedPaused ? 'resume' : 'pause'
73
+ const event = await opts.store.appendRepo(opts.repo, {
74
+ actor: humanActor(opts.user, opts.via),
75
+ type: command === 'resume' ? 'harvest.resume-requested' : 'harvest.pause-requested',
76
+ payload: {},
77
+ })
78
+ return { command, event }
79
+ }
80
+
81
+ /** Act only on the concrete dashboard run captured by the caller. */
82
+ export async function controlHarvestRun(opts: {
83
+ store: BuildStore
84
+ repo: string
85
+ user: string
86
+ run: string
87
+ via?: Via
88
+ }): Promise<{ action: HarvestRunAction; event: RepositoryEventEnvelope }> {
89
+ await opts.store.ensureRepo(opts.repo)
90
+ const events = await opts.store.getRepoEvents(opts.repo)
91
+ const state = reduceHarvest(events)
92
+ const projected = projectHarvest(events)
93
+ if (projected === undefined || projected.run !== opts.run) {
94
+ throw new OperatorControlError(
95
+ 'stale-harvest-run',
96
+ 'harvest run action ignored: selected run is no longer active',
97
+ )
98
+ }
99
+ if (state.paused) {
100
+ throw new OperatorControlError(
101
+ 'harvest-paused',
102
+ 'harvest run action unavailable while harvest is OFF; select Dispatcher and press h',
103
+ )
104
+ }
105
+ if (state.pendingCommands.some((command) => command.command === 'resume')) {
106
+ throw new OperatorControlError('harvest-pending', 'harvest run: resume acknowledgement pending')
107
+ }
108
+ if (projected.action === undefined) {
109
+ throw new OperatorControlError('harvest-unavailable', 'harvest run has no available action')
110
+ }
111
+ const event = await opts.store.appendRepo(opts.repo, {
112
+ actor: humanActor(opts.user, opts.via),
113
+ type: 'harvest.resume-requested',
114
+ payload: {},
115
+ })
116
+ return { action: projected.action, event }
117
+ }
@@ -0,0 +1,62 @@
1
+ export { OperatorApiClient, OperatorApiError } from './client'
2
+ export type { DownloadedArtifact, OperatorApiClientOptions } from './client'
3
+ export { createOperatorServer } from './server'
4
+ export type { OperatorServerOptions } from './server'
5
+ export {
6
+ answerRequestSchema,
7
+ buildControlRequestSchema,
8
+ buildListScopeSchema,
9
+ bulkControlRequestSchema,
10
+ harvestControlRequestSchema,
11
+ operatorErrorSchema,
12
+ settingRequestSchema,
13
+ ticketBlockerRequestSchema,
14
+ ticketCreateRequestSchema,
15
+ ticketMoveRequestSchema,
16
+ ticketUpdateRequestSchema,
17
+ } from './protocol'
18
+ export type {
19
+ OperatorAnswerRequest,
20
+ OperatorBuildControlRequest,
21
+ OperatorErrorBody,
22
+ OperatorTicketBlockerRequest,
23
+ OperatorTicketCreateRequest,
24
+ OperatorTicketMoveRequest,
25
+ OperatorTicketUpdateRequest,
26
+ } from './protocol'
27
+ export type {
28
+ BuildListScope,
29
+ OperatorBuildView,
30
+ OperatorDashboardSnapshot,
31
+ } from './query'
32
+ export { OperatorControlError } from './control'
33
+ export { OperatorQueryError } from './query'
34
+ export {
35
+ OPERATOR_NOTES_ARTIFACT,
36
+ TOOLS,
37
+ attributed,
38
+ buildRegistry,
39
+ RegistryError,
40
+ } from './registry'
41
+ export type {
42
+ OperatorToolRegistry,
43
+ RegistryErrorReason,
44
+ RegistryOptions,
45
+ ToolApproval,
46
+ ToolContext,
47
+ ToolEntry,
48
+ ToolFailureBody,
49
+ ToolHandler,
50
+ } from './registry'
51
+ export { OPERATOR_TOOL_ANNOTATIONS } from './annotations'
52
+ export type { OperatorToolName, ToolAnnotationsTableEntry } from './annotations'
53
+ export type { RouteRefusal } from './requests'
54
+ export { RouteRefusalError } from './requests'
55
+ export type {
56
+ OperatorTicketBackend,
57
+ OperatorTicketBuild,
58
+ OperatorTicketContext,
59
+ OperatorTicketDetail,
60
+ OperatorTicketQueue,
61
+ } from './tickets'
62
+ export * from './presentation'
@@ -0,0 +1,22 @@
1
+ export {
2
+ buildActionAvailability,
3
+ dashboardBuildControl,
4
+ harvestActionAvailability,
5
+ repositoryActionAvailability,
6
+ } from '../cli/dashboard/actions'
7
+ export type { DashboardActionAvailability, DashboardBuildControl } from '../cli/dashboard/actions'
8
+ export type {
9
+ DashboardBuild,
10
+ DashboardHarvest,
11
+ DashboardModel,
12
+ EffectiveStatus,
13
+ PipelineStep,
14
+ StepState,
15
+ StepTiming,
16
+ } from '../cli/dashboard/model'
17
+ export { parseTranscript } from '../cli/dashboard/transcript'
18
+ export type {
19
+ TranscriptPresentation,
20
+ TranscriptTurn,
21
+ TranscriptUsage,
22
+ } from '../cli/dashboard/transcript'
@@ -0,0 +1,98 @@
1
+ import { z } from 'zod'
2
+
3
+ export const buildListScopeSchema = z.enum(['active', 'queued', 'all'])
4
+
5
+ export const buildControlRequestSchema = z.discriminatedUnion('action', [
6
+ z.strictObject({ action: z.literal('pause') }),
7
+ z.strictObject({ action: z.literal('cancel-pause') }),
8
+ z.strictObject({ action: z.literal('resume') }),
9
+ z.strictObject({ action: z.literal('abort') }),
10
+ z.strictObject({ action: z.literal('discard') }),
11
+ z.strictObject({ action: z.literal('auto-merge-on') }),
12
+ z.strictObject({ action: z.literal('auto-merge-off') }),
13
+ ])
14
+ export type OperatorBuildControlRequest = z.infer<typeof buildControlRequestSchema>
15
+
16
+ const optionalText = z.string().optional()
17
+ export const answerRequestSchema = z.union([
18
+ z.strictObject({ resolution: z.literal('guidance'), text: z.string().trim().min(1) }),
19
+ z.strictObject({ resolution: z.literal('retry') }),
20
+ z.strictObject({ resolution: z.literal('dismiss'), text: optionalText }),
21
+ z.strictObject({
22
+ resolution: z.literal('review-round-ceiling'),
23
+ ceiling: z.number().int().positive(),
24
+ text: optionalText,
25
+ }),
26
+ z.strictObject({
27
+ resolution: z.literal('revise-spec'),
28
+ origin: z.literal('body'),
29
+ body: z.string(),
30
+ text: optionalText,
31
+ /** Accepted only so the shared core can return its exact incompatible-options refusal. */
32
+ ceiling: z.number().int().positive().optional(),
33
+ }),
34
+ z.strictObject({
35
+ resolution: z.literal('revise-spec'),
36
+ origin: z.literal('ticket'),
37
+ body: z.string(),
38
+ text: optionalText,
39
+ ceiling: z.number().int().positive().optional(),
40
+ }),
41
+ ])
42
+ export type OperatorAnswerRequest = z.infer<typeof answerRequestSchema>
43
+
44
+ export const settingRequestSchema = z.strictObject({ enabled: z.boolean() })
45
+ export const bulkControlRequestSchema = z.strictObject({ action: z.enum(['pause', 'resume']) })
46
+ export const harvestControlRequestSchema = z.discriminatedUnion('action', [
47
+ z.strictObject({ action: z.literal('toggle-gate') }),
48
+ z.strictObject({ action: z.literal('run'), run: z.string().min(1) }),
49
+ ])
50
+
51
+ export const ticketCreateRequestSchema = z.strictObject({
52
+ title: z.string(),
53
+ body: z.string(),
54
+ labels: z.array(z.string()).optional(),
55
+ state: z.string().min(1).optional(),
56
+ blockedBy: z.array(z.string().min(1)).optional(),
57
+ })
58
+ export const ticketUpdateRequestSchema = z
59
+ .strictObject({
60
+ title: z.string().optional(),
61
+ body: z.string().optional(),
62
+ labels: z.array(z.string()).optional(),
63
+ })
64
+ .refine((value) => Object.keys(value).length > 0, 'update must name at least one field')
65
+ export const ticketMoveRequestSchema = z.strictObject({ state: z.string().min(1) })
66
+ export const ticketBlockerRequestSchema = z.strictObject({
67
+ blockerIds: z.array(z.string().min(1)).min(1),
68
+ })
69
+ export type OperatorTicketCreateRequest = z.infer<typeof ticketCreateRequestSchema>
70
+ export type OperatorTicketUpdateRequest = z.infer<typeof ticketUpdateRequestSchema>
71
+ export type OperatorTicketMoveRequest = z.infer<typeof ticketMoveRequestSchema>
72
+ export type OperatorTicketBlockerRequest = z.infer<typeof ticketBlockerRequestSchema>
73
+
74
+ export const operatorErrorSchema = z.strictObject({
75
+ kind: z.enum(['validation', 'auth', 'not-found', 'conflict', 'refusal', 'internal']),
76
+ error: z.string(),
77
+ code: z.string().optional(),
78
+ progress: z.unknown().optional(),
79
+ })
80
+ export type OperatorErrorBody = z.infer<typeof operatorErrorSchema>
81
+
82
+ export const sessionCreateRequestSchema = z.strictObject({ title: z.string().min(1).optional() })
83
+ export type OperatorSessionCreateRequest = z.infer<typeof sessionCreateRequestSchema>
84
+
85
+ export const sessionMessageRequestSchema = z.strictObject({ text: z.string().trim().min(1) })
86
+ export type OperatorSessionMessageRequest = z.infer<typeof sessionMessageRequestSchema>
87
+
88
+ export const sessionWakeRequestSchema = z.strictObject({
89
+ globs: z.array(z.string().min(1)).max(100),
90
+ })
91
+ export type OperatorSessionWakeRequest = z.infer<typeof sessionWakeRequestSchema>
92
+
93
+ export const sessionApprovalRequestSchema = z.strictObject({
94
+ turn: z.string().min(1),
95
+ toolCallId: z.string().min(1),
96
+ decision: z.enum(['approve', 'deny']),
97
+ })
98
+ export type OperatorSessionApprovalRequest = z.infer<typeof sessionApprovalRequestSchema>
@@ -0,0 +1,201 @@
1
+ import { configSchema, type Config } from '../config/schema'
2
+ import { detail, statusFilter, summarize, type BuildDetail, type BuildSummary } from '../cli/status'
3
+ import { projectRepositoryStatus, type RepositoryStatus } from '../cli/repository-status'
4
+ import { projectHarvestStatus, type HarvestStatusView } from '../cli/harvest'
5
+ import {
6
+ buildDashboardFromProjected,
7
+ projectBuild,
8
+ type DashboardBuild,
9
+ type DashboardModel,
10
+ } from '../cli/dashboard/model'
11
+ import { reduceBuild } from '../kernel/reducer'
12
+ import { reduceDispatchStatus } from '../kernel/dispatch-status'
13
+ import { scanUnclaimedObservations } from '../processes/harvest'
14
+ import type { BuildStore, Clock } from '../store/types'
15
+
16
+ export type BuildListScope = 'active' | 'queued' | 'all'
17
+
18
+ export class OperatorQueryError extends Error {
19
+ constructor(
20
+ readonly code: 'not-found' | 'effective-config-unavailable',
21
+ message: string,
22
+ ) {
23
+ super(message)
24
+ this.name = 'OperatorQueryError'
25
+ }
26
+ }
27
+
28
+ async function repoEvents(store: BuildStore, repo: string) {
29
+ return (await store.getRepo(repo)) === null ? [] : store.getRepoEvents(repo)
30
+ }
31
+
32
+ export async function listOperatorBuilds(opts: {
33
+ store: BuildStore
34
+ repo: string
35
+ scope: BuildListScope
36
+ now: Date
37
+ }): Promise<BuildSummary[]> {
38
+ const statuses = new Set(statusFilter(opts.scope === 'all', opts.scope === 'queued'))
39
+ const output: BuildSummary[] = []
40
+ for (const record of await opts.store.listBuilds()) {
41
+ if (record.repo !== opts.repo) continue
42
+ const projected = summarize(record, await opts.store.getEvents(record.slug), opts.now)
43
+ if (statuses.has(projected.status)) output.push(projected)
44
+ }
45
+ return output.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
46
+ }
47
+
48
+ async function requireBuild(store: BuildStore, repo: string, slug: string) {
49
+ const record = await store.getBuild(slug)
50
+ if (record === null) throw new OperatorQueryError('not-found', `no build "${slug}" in this store`)
51
+ if (record.repo !== repo) {
52
+ throw new OperatorQueryError('not-found', `unknown build "${slug}"`)
53
+ }
54
+ return record
55
+ }
56
+
57
+ export async function effectiveConfig(
58
+ store: BuildStore,
59
+ repo: string,
60
+ ): Promise<{
61
+ config: Config
62
+ repositoryEvents: Awaited<ReturnType<typeof repoEvents>>
63
+ status: ReturnType<typeof reduceDispatchStatus>
64
+ }> {
65
+ const repositoryEvents = await repoEvents(store, repo)
66
+ let latestRun: string | undefined
67
+ for (const event of repositoryEvents) {
68
+ if (event.type === 'dispatcher.run-started') latestRun = event.payload.run
69
+ }
70
+ if (latestRun === undefined) {
71
+ throw new OperatorQueryError(
72
+ 'effective-config-unavailable',
73
+ `effective config unavailable for repository "${repo}": no dispatcher run is recorded`,
74
+ )
75
+ }
76
+ const status = reduceDispatchStatus(repositoryEvents, latestRun)
77
+ const ref = status.effectiveConfig
78
+ if (ref === undefined) {
79
+ throw new OperatorQueryError(
80
+ 'effective-config-unavailable',
81
+ `effective config unavailable for repository "${repo}"`,
82
+ )
83
+ }
84
+ const artifact = await store.getRepoArtifact(repo, ref.kind, ref.rev)
85
+ if (artifact === null) {
86
+ throw new OperatorQueryError(
87
+ 'effective-config-unavailable',
88
+ `effective config ${ref.kind}@${ref.rev} is not retrievable`,
89
+ )
90
+ }
91
+ let raw: unknown
92
+ try {
93
+ raw = JSON.parse(new TextDecoder().decode(artifact.content))
94
+ } catch {
95
+ throw new OperatorQueryError(
96
+ 'effective-config-unavailable',
97
+ `effective config ${ref.kind}@${ref.rev} is not valid JSON`,
98
+ )
99
+ }
100
+ const parsed = configSchema.safeParse(raw)
101
+ if (!parsed.success) {
102
+ throw new OperatorQueryError(
103
+ 'effective-config-unavailable',
104
+ `effective config ${ref.kind}@${ref.rev} is invalid: ${parsed.error.message}`,
105
+ )
106
+ }
107
+ return { config: parsed.data, repositoryEvents, status }
108
+ }
109
+
110
+ export interface OperatorBuildView {
111
+ detail: BuildDetail
112
+ dashboardRow: DashboardBuild | null
113
+ }
114
+
115
+ export async function getOperatorBuild(opts: {
116
+ store: BuildStore
117
+ repo: string
118
+ slug: string
119
+ now: Date
120
+ }): Promise<OperatorBuildView> {
121
+ const record = await requireBuild(opts.store, opts.repo, opts.slug)
122
+ const events = await opts.store.getEvents(opts.slug)
123
+ const state = reduceBuild(events)
124
+ const { config } = await effectiveConfig(opts.store, opts.repo)
125
+ const dashboardRow = projectBuild(record, state, config, events)
126
+ return { detail: detail(record, events, opts.now), dashboardRow }
127
+ }
128
+
129
+ export async function getRepositoryStatus(
130
+ store: BuildStore,
131
+ repo: string,
132
+ ): Promise<RepositoryStatus> {
133
+ return projectRepositoryStatus(repo, await repoEvents(store, repo))
134
+ }
135
+
136
+ export async function getHarvestStatus(
137
+ store: BuildStore,
138
+ repo: string,
139
+ ): Promise<HarvestStatusView> {
140
+ return projectHarvestStatus(repo, await repoEvents(store, repo))
141
+ }
142
+
143
+ export interface OperatorDashboardSnapshot {
144
+ generatedAt: string
145
+ model: DashboardModel
146
+ settingsHeader: {
147
+ intake: boolean
148
+ repositoryPaused: boolean
149
+ defaultAutoMerge: boolean
150
+ harvestPaused: boolean
151
+ }
152
+ }
153
+
154
+ export async function getOperatorDashboard(opts: {
155
+ store: BuildStore
156
+ repo: string
157
+ clock: Clock
158
+ }): Promise<OperatorDashboardSnapshot> {
159
+ const { config, repositoryEvents, status } = await effectiveConfig(opts.store, opts.repo)
160
+ const projected: DashboardBuild[] = []
161
+ let activeCount = 0
162
+ for (const record of await opts.store.listBuilds()) {
163
+ if (record.repo !== opts.repo) continue
164
+ const events = await opts.store.getEvents(record.slug)
165
+ const state = reduceBuild(events)
166
+ if (state.status !== 'done' && state.status !== 'aborted') activeCount += 1
167
+ const row = projectBuild(record, state, config, events)
168
+ if (row !== null) projected.push(row)
169
+ }
170
+ const scan = await scanUnclaimedObservations(opts.store, opts.repo)
171
+ const warningLines = [
172
+ ...status.roleWarnings,
173
+ ...(status.warningNotice !== undefined ? [status.warningNotice] : []),
174
+ ]
175
+ const model = buildDashboardFromProjected(
176
+ projected,
177
+ {
178
+ repo: opts.repo,
179
+ queued: status.queued ?? 0,
180
+ activeCount,
181
+ capacity: config.capacity,
182
+ observationCount: scan.observations.length,
183
+ observationLimit: config.policy.harvestThreshold,
184
+ ...(status.availableUpgrade !== undefined
185
+ ? { availableUpgrade: status.availableUpgrade }
186
+ : {}),
187
+ ...(warningLines.length > 0 ? { warningLines } : {}),
188
+ },
189
+ repositoryEvents,
190
+ )
191
+ return {
192
+ generatedAt: opts.clock().toISOString(),
193
+ model,
194
+ settingsHeader: {
195
+ intake: !model.drained,
196
+ repositoryPaused: model.repositoryPaused,
197
+ defaultAutoMerge: model.defaultAutoMerge,
198
+ harvestPaused: model.harvestPaused,
199
+ },
200
+ }
201
+ }