@bakers-build/conductor 0.0.0-stage → 0.1.0-next.42

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 (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4 -2
  3. package/dist/src/bakers-project-context.d.ts +12 -0
  4. package/dist/src/bakers-project-context.js +59 -0
  5. package/dist/src/cli/config-plan.d.ts +35 -0
  6. package/dist/src/cli/config-plan.js +1264 -0
  7. package/dist/src/cli/config-schema.d.ts +137 -0
  8. package/dist/src/cli/config-schema.js +239 -0
  9. package/dist/src/cli/config-source-edit.d.ts +17 -0
  10. package/dist/src/cli/config-source-edit.js +95 -0
  11. package/dist/src/cli/config-templates.d.ts +43 -0
  12. package/dist/src/cli/config-templates.js +130 -0
  13. package/dist/src/cli/config-validation.d.ts +12 -0
  14. package/dist/src/cli/config-validation.js +353 -0
  15. package/dist/src/cli/migrate-addon-names.d.ts +82 -0
  16. package/dist/src/cli/migrate-addon-names.js +334 -0
  17. package/dist/src/cli/project-context.d.ts +21 -0
  18. package/dist/src/cli/project-context.js +105 -0
  19. package/dist/src/commands/conductor.d.ts +91 -0
  20. package/dist/src/commands/conductor.js +843 -0
  21. package/dist/src/config.d.ts +109 -0
  22. package/dist/src/config.js +712 -0
  23. package/dist/src/contract.d.ts +13 -0
  24. package/dist/src/contract.js +19 -0
  25. package/dist/src/core/action-queue.d.ts +31 -0
  26. package/dist/src/core/action-queue.js +69 -0
  27. package/dist/src/core/attempt.d.ts +25 -0
  28. package/dist/src/core/attempt.js +41 -0
  29. package/dist/src/core/conductor.d.ts +236 -0
  30. package/dist/src/core/conductor.js +1293 -0
  31. package/dist/src/core/host-key.d.ts +19 -0
  32. package/dist/src/core/host-key.js +27 -0
  33. package/dist/src/core/inbox-runtime.d.ts +48 -0
  34. package/dist/src/core/inbox-runtime.js +180 -0
  35. package/dist/src/core/mr-changes.d.ts +18 -0
  36. package/dist/src/core/mr-changes.js +47 -0
  37. package/dist/src/index.d.ts +8 -0
  38. package/dist/src/index.js +27 -0
  39. package/dist/src/plugins/agent-args.d.ts +43 -0
  40. package/dist/src/plugins/agent-args.js +221 -0
  41. package/dist/src/plugins/agent.d.ts +121 -0
  42. package/dist/src/plugins/agent.js +12 -0
  43. package/dist/src/plugins/executor.d.ts +182 -0
  44. package/dist/src/plugins/executor.js +1 -0
  45. package/dist/src/plugins/message.d.ts +61 -0
  46. package/dist/src/plugins/message.js +47 -0
  47. package/dist/src/plugins/plugins.d.ts +96 -0
  48. package/dist/src/plugins/plugins.js +120 -0
  49. package/dist/src/plugins/preset.d.ts +61 -0
  50. package/dist/src/plugins/preset.js +23 -0
  51. package/dist/src/plugins/process-host.d.ts +7 -0
  52. package/dist/src/plugins/process-host.js +1 -0
  53. package/dist/src/plugins/remote.d.ts +420 -0
  54. package/dist/src/plugins/remote.js +1 -0
  55. package/dist/src/plugins/workspace.d.ts +69 -0
  56. package/dist/src/plugins/workspace.js +1 -0
  57. package/dist/src/preset.d.ts +2 -0
  58. package/dist/src/preset.js +8 -0
  59. package/dist/src/types.d.ts +132 -0
  60. package/dist/src/types.js +6 -0
  61. package/package.json +38 -4
@@ -0,0 +1,61 @@
1
+ import type { AddonEntry, BbPreset, DiscoveredContributions } from '@bakers-build/core';
2
+ import type { HookListener } from './executor.js';
3
+ import type { DiscoveredInbox, InboxMessages } from './message.js';
4
+ import type { AgentCandidate, ExecutorPlugin, RemotePlugin, WorkspacePlugin } from './plugins.js';
5
+ import type { HerdrHost } from './process-host.js';
6
+ import type { Ticket } from './remote.js';
7
+ /**
8
+ * The conductor surface's concrete contributions — the strongly-typed view of
9
+ * the conductor accumulators `discoverAddons(entries, 'conductor', …)`
10
+ * fills. Core returns them as opaque `unknown[]`; the engine narrows once, at
11
+ * the discovery call site (see {@link narrowConductorContributions}).
12
+ */
13
+ export interface ConductorContributions {
14
+ remotes: RemotePlugin[];
15
+ executors: ExecutorPlugin[];
16
+ workspaces: WorkspacePlugin[];
17
+ agents: AgentCandidate[];
18
+ /** Lifecycle hook listeners, in accumulator order (BB-599). */
19
+ hooks: HookListener[];
20
+ /** Conductor process hosts (last wins). */
21
+ processHosts: HerdrHost[];
22
+ /** Addon `inbox` exports with their package name and version (contract 2, BB-661). */
23
+ addonInbox: DiscoveredInbox[];
24
+ }
25
+ /**
26
+ * One `addons[]` entry on the conductor surface: core's surface-agnostic
27
+ * `AddonEntry` with the agent `priority` narrowed to the real `Ticket`.
28
+ */
29
+ export type ConductorAddonEntry = string | (Exclude<AddonEntry, string> & {
30
+ priority?: (ticket: Ticket) => number;
31
+ });
32
+ /**
33
+ * A preset as a CONDUCTOR-surface addon writes it: core's `BbPreset` with the
34
+ * conductor accumulators concretely typed.
35
+ *
36
+ * It literally `extends BbPreset`, so the compiler — not a comment — enforces
37
+ * that this view stays a faithful narrowing of the kernel contract. That only
38
+ * type-checks because core's `OpaqueAccumulator` takes `never[]` on the input
39
+ * side: `never[]` is assignable to `RemotePlugin[]`, so the concrete member below
40
+ * satisfies the opaque one contravariantly. If core ever widened that parameter
41
+ * to `unknown[]`, this `extends` would fail loudly here instead of the two views
42
+ * silently drifting apart.
43
+ */
44
+ export interface Preset<O = unknown> extends BbPreset<O> {
45
+ remotes?: (acc: RemotePlugin[], opts: O) => RemotePlugin[] | Promise<RemotePlugin[]>;
46
+ executors?: (acc: ExecutorPlugin[], opts: O) => ExecutorPlugin[] | Promise<ExecutorPlugin[]>;
47
+ workspaces?: (acc: WorkspacePlugin[], opts: O) => WorkspacePlugin[] | Promise<WorkspacePlugin[]>;
48
+ agents?: (acc: AgentCandidate[], opts: O) => AgentCandidate[] | Promise<AgentCandidate[]>;
49
+ hooks?: (acc: HookListener[], opts: O) => HookListener[] | Promise<HookListener[]>;
50
+ processHosts?: (acc: HerdrHost[], opts: O) => HerdrHost[] | Promise<HerdrHost[]>;
51
+ /** Addon inbox messages: contract 2, BB-661. */
52
+ inbox?: InboxMessages;
53
+ }
54
+ /**
55
+ * The single, reviewed narrowing at the engine's discovery seam: reinterpret
56
+ * core's opaque conductor accumulators as this surface's concrete contribution
57
+ * lists. Safe because every conductor-surface preset is typed against
58
+ * {@link Preset}, and `resolveConductorSlots` still runs the runtime `.kind`
59
+ * guard on whatever actually landed in a singleton slot.
60
+ */
61
+ export declare function narrowConductorContributions(discovered: Pick<DiscoveredContributions, 'remotes' | 'executors' | 'workspaces' | 'agents' | 'hooks' | 'processHosts' | 'addonInbox'>): ConductorContributions;
@@ -0,0 +1,23 @@
1
+ // BB-224 (Finding 6, decision 7): the CONDUCTOR-SURFACE view of the preset
2
+ // contract. `@bakers-build/core` owns the surface-AGNOSTIC machinery — it knows the
3
+ // accumulator NAMES (`SURFACE_KEYS`) and threads them (`discoverAddons`), but its
4
+ // per-surface accumulators are OPAQUE (`OpaqueAccumulator`), because the element
5
+ // types of the conductor surface (`RemotePlugin`, `ExecutorPlugin`,
6
+ // `WorkspacePlugin`, `AgentCandidate`) live HERE, in the package that owns the
7
+ // surface. Without that split, `core/src/plugins/preset.ts` would have to import
8
+ // the conductor contract and core would edge `@bakers-build/conductor` — breaking the
9
+ // "core has zero @bakers-build/* edges" rule.
10
+ //
11
+ // A conductor-surface addon therefore types its `./preset` accumulators against
12
+ // THIS `Preset` (e.g. `export const agents: Preset['agents'] = …`) instead of
13
+ // core's `BbPreset`, and stays structurally assignable to it.
14
+ /**
15
+ * The single, reviewed narrowing at the engine's discovery seam: reinterpret
16
+ * core's opaque conductor accumulators as this surface's concrete contribution
17
+ * lists. Safe because every conductor-surface preset is typed against
18
+ * {@link Preset}, and `resolveConductorSlots` still runs the runtime `.kind`
19
+ * guard on whatever actually landed in a singleton slot.
20
+ */
21
+ export function narrowConductorContributions(discovered) {
22
+ return discovered;
23
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Process host that starts a detached conductor loop. The conductor package
3
+ * owns only this contract; `@bakers-build/addon-herdr` implements it.
4
+ */
5
+ export interface HerdrHost {
6
+ spawn(label: string, cwd: string, cmd: string): Promise<void>;
7
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,420 @@
1
+ import type { MessageDefinition, MessageResult } from '@bakers-build/message';
2
+ /** A contract-2 addon's wire registration shape (BB-661). */
3
+ export interface AddonDefinitionV2 {
4
+ name: string;
5
+ version: string;
6
+ contract: 2;
7
+ label?: string;
8
+ icon?: string;
9
+ messages: Record<string, MessageDefinition>;
10
+ }
11
+ export interface ConductorRegistration {
12
+ /** Conductor process registration key (bb_conductor.conductor_id). */
13
+ id: string;
14
+ /** Host identity stem (bb_machine.host_key / MachineContext.machine_id). */
15
+ host_key?: string;
16
+ project: string;
17
+ /** Empty = serve all claimable states in the project (BB-207). */
18
+ states: string[];
19
+ workspace: string;
20
+ label: string;
21
+ max_parallel: number;
22
+ /**
23
+ * The pid of the serve-loop process, when this conductor *is* that process.
24
+ *
25
+ * Absent from a registration sent by `poll` or `reap`, which build a
26
+ * Conductor and exit within seconds — reporting their pid would overwrite a
27
+ * running conductor's with a short-lived one. Absent likewise means "leave
28
+ * whatever is stored alone", never "clear it".
29
+ */
30
+ last_pid?: number;
31
+ /**
32
+ * The stem of the config file this conductor was started from (`conductor`
33
+ * for the default, `shop` for `shop.conductor.config.js`) — what
34
+ * `--conductor <name>` selects. Together with `workspace` it identifies one
35
+ * conductor in a repo that holds several.
36
+ */
37
+ conductor_name?: string;
38
+ }
39
+ export interface ClaimOptions {
40
+ leaseSeconds: number;
41
+ conductorId: string;
42
+ }
43
+ export interface ClaimedRun {
44
+ runUuid: string;
45
+ runId: number;
46
+ ticketUuid: string;
47
+ stateAtStart: string;
48
+ handler: string;
49
+ }
50
+ export interface Ticket {
51
+ uuid: string;
52
+ identifier: string;
53
+ title: string;
54
+ state: string;
55
+ branchName: string;
56
+ /** Branch the ticket's work is based on (computed server-side): parent branch, project default, or 'main'. */
57
+ baseBranch?: string;
58
+ /**
59
+ * Effective environment variables (BB-99), resolved server-side along the
60
+ * parent_id chain (child wins) as canonical `.env`-style `KEY=value` lines.
61
+ * The conductor parses this, strips reserved keys, overlays the core BB_*
62
+ * vars, and injects the result into the agent run env AND the workspace hooks.
63
+ * Omitted when the ticket (and its ancestors) set none. Values may be secret.
64
+ */
65
+ effectiveEnvVars?: string;
66
+ issueUrl?: string;
67
+ /** BB-495: the ticket's project, so a dispatched agent can name it on its own calls. */
68
+ projectId?: string;
69
+ projectName?: string;
70
+ /**
71
+ * BB-495: the project's workspace, resolved through `bb_project.group_id.workspace_id`.
72
+ * Omitted when the project or its group carries no resolvable workspace.
73
+ */
74
+ workspaceId?: string;
75
+ workspaceName?: string;
76
+ /** Ticket label term names (bb_labels vocab), sideloaded for agent selection. Empty when none. */
77
+ labels: string[];
78
+ /** Ticket environments (bb_environment refs), each as name + tier, sideloaded for agent selection. Empty when none. */
79
+ environments: {
80
+ name: string;
81
+ tier: string;
82
+ }[];
83
+ }
84
+ export interface ActiveRun {
85
+ runUuid: string;
86
+ ticketUuid: string;
87
+ ticketIdentifier: string;
88
+ branchName: string;
89
+ state: string;
90
+ stateAtStart: string;
91
+ /** Absolute path of the per-run git worktree, or '' when unset. */
92
+ worktreePath: string;
93
+ }
94
+ export interface ConductorStatus {
95
+ id: string;
96
+ project: string;
97
+ label: string;
98
+ status: string;
99
+ lastSeen: number;
100
+ load: number;
101
+ /**
102
+ * The checkout root the conductor last registered from (BB-450 D3) — the
103
+ * top of the working tree, not its `.bakers` config directory. Empty when
104
+ * never reported. A record written before BB-450 still carries the
105
+ * legacy `.bakers` value until its next heartbeat rewrites it.
106
+ */
107
+ workspaceRoot: string;
108
+ /** Lease expiry (unix seconds); with lastSeen it derives liveness. 0 = unset. */
109
+ leaseExpiresAt: number;
110
+ maxParallel: number;
111
+ /** The last pid the conductor's serve loop reported, if any (BB-232). */
112
+ lastPid?: number;
113
+ /** The config stem it was started from, as `--conductor <name>` takes it. */
114
+ conductorName?: string;
115
+ /**
116
+ * The MachineContext host stem (bb_machine.host_key) this record last
117
+ * heartbeat with (BB-450 AC-4) — `signalVerifiedProcess()` compares it
118
+ * against this machine's own, so lifecycle commands can never signal a
119
+ * conductor on another host. Empty when never reported.
120
+ */
121
+ hostKey?: string;
122
+ }
123
+ export interface FinalizableRun {
124
+ runUuid: string;
125
+ /** Numeric run id (the `#<runId>` tab-label token); scopes the run's tab-close at finalise (BB-183). */
126
+ runId: number;
127
+ /**
128
+ * The run's ticket and the state it started in, read from the run record so
129
+ * a restarted conductor can rebuild the `after_run` context (BB-599).
130
+ * '' / undefined when unset.
131
+ */
132
+ ticketUuid?: string;
133
+ stateAtStart?: string;
134
+ /** Absolute path of the per-run git worktree, or '' when unset. */
135
+ worktreePath: string;
136
+ /** Id of the agent that ran (BB-144); routes footprint parsing at finalize. '' / undefined when unset. */
137
+ agent?: string;
138
+ /**
139
+ * When the run started (bb_run.started_at), read back purely as a lower
140
+ * bound for transcript selection (BB-473) — an agent with no session-id
141
+ * mechanism (codex, opencode) discards any transcript older than this so a
142
+ * prior run's leftover transcript in the same worktree can never be
143
+ * finalised onto this run. NOT used to (re)compute `duration_s`, which
144
+ * stays transcript-derived (BB-151). '' / undefined when unset.
145
+ */
146
+ startedAt?: string;
147
+ }
148
+ /**
149
+ * The attributes the conductor writes in the single PATCH that closes a run:
150
+ * the effort footprint parsed from the transcript, and the runtime that
151
+ * produced it. The runtime members are optional because a value that cannot be
152
+ * resolved is omitted from the payload entirely rather than sent as a
153
+ * placeholder — an absent key leaves the field NULL on the server, which is a
154
+ * readable "not resolved", where an empty string would read as an answer.
155
+ */
156
+ export interface RunMetrics {
157
+ tokens: number;
158
+ duration_s: number;
159
+ agent_turns: number;
160
+ tool_calls: number;
161
+ user_prompts: number;
162
+ user_prompt_words: number;
163
+ /** Active agent time in seconds; the human wait is `duration_s - active_s`. */
164
+ active_s: number;
165
+ /** The harness package id that drove the session, e.g. `claude`. */
166
+ harness?: string;
167
+ /** The model the chosen candidate pins, when configured; otherwise the raw
168
+ * model id the transcript records, e.g. `claude-opus-5`. */
169
+ model?: string;
170
+ /** The candidate's configured exact model version, when configured;
171
+ * otherwise the raw model id the transcript records for the turn that
172
+ * ran. */
173
+ model_version?: string;
174
+ }
175
+ /** One `bb_metric` row of a ticket's `metrics` field, in the fixed output format. */
176
+ export interface TicketMetric {
177
+ name: string;
178
+ /** The number as a string with exactly 4 decimals, e.g. `"71.0000"`. */
179
+ value: string;
180
+ unit: string | null;
181
+ /**
182
+ * Unix seconds, shared by every row of one measurement; rows read back from
183
+ * the server may carry it as an RFC 3339 string.
184
+ */
185
+ recorded: number | string;
186
+ }
187
+ /**
188
+ * A closed ticket whose worktree has not yet been torn down — the
189
+ * conductor's teardown work list, driven by the durable `cleaned_up` flag
190
+ * (BB-89). Since BB-451 the server closes every terminal ticket on its own
191
+ * save, so "finished" collapses to `closed = 1`; `cleaned_up=0` (or never
192
+ * written at all) means the herdr worktree still needs reclaiming. The
193
+ * reconciliation is NOT scoped to a conductor's machine_id, so it also
194
+ * surfaces tickets whose conductor was down at close or whose `conductor_id`
195
+ * was reassigned.
196
+ */
197
+ export interface UncleanTicket {
198
+ ticketUuid: string;
199
+ /** The ticket's git branch — the key herdr resolves the worktree by. */
200
+ branchName: string;
201
+ /** The ticket's workflow state (`done`, `cancelled`, …), '' when unset. */
202
+ state: string;
203
+ /** The branch the ticket's merge request targets, '' when unset. */
204
+ baseBranch: string;
205
+ /** Whether the ticket's `links` hold a merge request (title `MR`). */
206
+ hasMr: boolean;
207
+ /**
208
+ * Absolute path of the ticket's worktree (from its latest run's
209
+ * `worktree_path`), or '' when unresolved. The cwd the teardown runs in.
210
+ */
211
+ worktreePath: string;
212
+ /**
213
+ * UUID of the ticket's latest run — the one {@link worktreePath} came from —
214
+ * or '' when the ticket has no run with a path. The record teardown notes are
215
+ * written to (BB-293). Resolved by the same query that resolves the path,
216
+ * so it costs no extra round-trip.
217
+ */
218
+ runUuid: string;
219
+ }
220
+ /** An open, frozen ticket of this conductor and where its worktree is. */
221
+ export interface FrozenTicket {
222
+ ticketUuid: string;
223
+ identifier: string;
224
+ /** Latest run's worktree path, or '' when unresolved. */
225
+ worktreePath: string;
226
+ }
227
+ /** Extra conductor-writable bb_run attributes (besides state/lease). */
228
+ export interface RunWriteAttributes {
229
+ /** Absolute path of the per-run git worktree (set by the conductor). */
230
+ worktree_path?: string;
231
+ /** Id of the agent the conductor chose for this run (BB-144), for footprint routing. */
232
+ agent?: string;
233
+ /**
234
+ * Timestamp of THIS dispatch attempt (BB-473 round 4), re-stamped on
235
+ * every `markRunning` call — including a re-dispatch of an already-run
236
+ * ticket. Read back as `FinalizableRun.startedAt` and used as a lower
237
+ * bound at finalise for agents with no session mechanism at all.
238
+ *
239
+ * Whole seconds since the epoch (`Math.floor(Date.now() / 1000)`),
240
+ * matching every other timestamp this method's caller writes (`heartbeat`,
241
+ * `closed_date`) — NOT a fractional-second ISO string. `bb_run.started_at`
242
+ * is a Drupal `timestamp` field, whose normalizer accepts `U` (epoch
243
+ * seconds), ISO 8601 or RFC 3339, none of which carries a fractional
244
+ * component; a fractional string is silently rejected by the JSON:API
245
+ * write (review round 4, Critical 1) and takes `worktree_path`/`agent` down
246
+ * with it, since all three are on the same PATCH.
247
+ */
248
+ startedAt?: string | number;
249
+ }
250
+ /** Eligible work withheld by the authoritative machine admission budget. */
251
+ export interface ClaimWithheld {
252
+ reason: 'machine_capacity';
253
+ occupancy: number;
254
+ capacity: number;
255
+ /** Running runs have no fixed release deadline. */
256
+ retryAt: null;
257
+ }
258
+ /** One pending inbox message for this conductor. */
259
+ export interface InboxMessage {
260
+ /** The message entity's UUID. */
261
+ id: string;
262
+ /** `<addon>.<message key>`, e.g. `ports.tunnel`. */
263
+ messageId: string;
264
+ /** The responsible handler's version at send time (recorded only). */
265
+ version: string;
266
+ ticketUuid: string;
267
+ payload: Record<string, unknown>;
268
+ /** The ticket's newest worktree on this conductor, null when none. */
269
+ worktreePath: string | null;
270
+ /** The sender's user UUID; absent when the server sends none. */
271
+ requestedBy?: string;
272
+ }
273
+ /** The inbox poll: this conductor's pending messages. */
274
+ export interface InboxSnapshot {
275
+ messages: InboxMessage[];
276
+ }
277
+ /** The server's answer to an addon registration. */
278
+ export interface AddonRegistration {
279
+ accepted: Array<{
280
+ name: string;
281
+ version: string;
282
+ id: string;
283
+ }>;
284
+ rejected: Array<{
285
+ name: string;
286
+ version: string;
287
+ error: string;
288
+ }>;
289
+ }
290
+ export interface BbRemote {
291
+ /** POSTs heartbeat registration; returns the Drupal entity uuid. */
292
+ registerConductor(reg: ConductorRegistration): Promise<string>;
293
+ /**
294
+ * Records a heartbeat server-side: POSTs the heartbeat resource
295
+ * (recreating a vanished registration), refreshes last_seen/lease/load,
296
+ * and brings the conductor online. Keyed on conductor_id, so it never
297
+ * 404s — the self-heal lives on the server, not the client. Returns the
298
+ * resulting status (`online` | `offline`).
299
+ */
300
+ heartbeat(reg: ConductorRegistration, currentLoad: number, leaseSeconds?: number): Promise<string>;
301
+ getConductorStatus(conductorId: string): Promise<string | null>;
302
+ setConductorStatus(conductorId: string, status: 'offline'): Promise<void>;
303
+ listConductors(owner?: 'me'): Promise<ConductorStatus[]>;
304
+ activeRunCount(conductorId: string): Promise<number>;
305
+ /**
306
+ * Shared host capacity for this `host_key` (bb_machine, BB-353 D5).
307
+ * After a heartbeat that reported this process's load, `currentLoad` is the
308
+ * sum of online conductors on the machine (peers + self). Null when the
309
+ * control plane has no machine row yet — callers fall back to local count.
310
+ */
311
+ machineCapacity(hostKey: string): Promise<{
312
+ maxParallel: number;
313
+ currentLoad: number;
314
+ } | null>;
315
+ fetchActiveRuns(conductorId: string): Promise<ActiveRun[]>;
316
+ claimNext(claim: ClaimOptions): Promise<ClaimedRun | ClaimWithheld | null>;
317
+ getTicket(ticketUuid: string): Promise<Ticket>;
318
+ /**
319
+ * BB-495: every `bb_workspace` uuid the dispatching identity can reach —
320
+ * dispatch exports this as `BB_WORKSPACE_IDS` so the agent knows the full
321
+ * radius it may name, not only the point it starts standing on.
322
+ */
323
+ getReachableWorkspaces(): Promise<string[]>;
324
+ /** Returns the run's worktree_path, or '' when unset. */
325
+ getRunWorktree(runUuid: string): Promise<string>;
326
+ /**
327
+ * Returns the identifier of the run's ticket, or '' when unresolved. Lets the
328
+ * release path derive the tab matching ref (the ticket identifier) from a run
329
+ * uuid alone.
330
+ */
331
+ getRunTicketIdentifier(runUuid: string): Promise<string>;
332
+ /**
333
+ * Returns the branch_name of the run's ticket, or '' when unresolved. Used
334
+ * by dispatch for the per-(branch,state) herdr tab model.
335
+ */
336
+ getRunTicketBranchName(runUuid: string): Promise<string>;
337
+ markRunning(runUuid: string, attrs?: RunWriteAttributes): Promise<void>;
338
+ /**
339
+ * Terminalise a run to the `failed` state on a dispatch/setup error (BB-149).
340
+ * PATCHes state=failed, records the failure detail in `error_log`, and closes
341
+ * the run (closed + closed_date). `failed` is a terminal sibling of `expired`
342
+ * (dispatch error vs lease lapse), so the run stops counting toward capacity
343
+ * and its ticket is no longer blocked by the single-active-run gate — instead
344
+ * of the old "leave it `claimed` to expire after ~300s" silent stall. The
345
+ * failure is visible immediately and its cause is recorded.
346
+ */
347
+ markFailed(runUuid: string, errorLog: string): Promise<void>;
348
+ /**
349
+ * Runs this conductor owns that are state in (done, cancelled) but not yet
350
+ * closed — the conductor's one-shot finalisation work list. Cancelled runs
351
+ * retain their transcript while their agent tab is stopped and closed.
352
+ */
353
+ fetchFinalizableRuns(conductorId: string): Promise<FinalizableRun[]>;
354
+ /**
355
+ * Finalise a run: set closed + closed_date, (when non-empty) log, and (when
356
+ * given) the per-run footprint metrics (BB-132). No state change — the run
357
+ * is already terminal.
358
+ */
359
+ finalizeRun(uuid: string, log: string, metrics?: RunMetrics): Promise<void>;
360
+ /**
361
+ * Closed tickets (closed=1) whose worktree is not yet torn down
362
+ * (cleaned_up=0, or never written at all) — the conductor's teardown work
363
+ * list (BB-89). Since BB-451 the server closes every terminal ticket on
364
+ * its own save, so this asks one question — closed but not cleaned up —
365
+ * with no workflow-state reasoning left in it. Driven by the durable
366
+ * `cleaned_up` flag AND scoped to the reaping conductor (BB-121): only
367
+ * tickets assigned to `conductorId` are loaded, so every ticket on the list
368
+ * is unambiguously this conductor's — a missing worktree means "already torn
369
+ * down on this host", not "belongs to another host". The same query backs
370
+ * the live tick and the standalone `bakers oven clean`. Empty once every
371
+ * closed ticket this conductor owns is cleaned.
372
+ */
373
+ fetchUncleanedTickets(conductorId: string): Promise<UncleanTicket[]>;
374
+ /**
375
+ * Mark a ticket's worktree torn down: set cleaned_up=true so it drops off the
376
+ * {@link fetchUncleanedTickets} work list. Written ONLY after a verified
377
+ * teardown, so a teardown miss leaves cleaned_up=0 and the next reconciliation
378
+ * retries — one miss never orphans the workspace, and a re-run on an
379
+ * already-cleaned ticket is a no-op (it is no longer on the list).
380
+ */
381
+ markTicketCleanedUp(uuid: string): Promise<void>;
382
+ /**
383
+ * Replace the ticket's `metrics` rows whose name starts with `namespace`
384
+ * by `rows`, keeping every other row: one read and one PATCH of `metrics`
385
+ * alone. The server keeps `metrics` writable on a closed ticket until it is
386
+ * cleaned up, so the reaper calls this before teardown.
387
+ */
388
+ replaceTicketMetrics(uuid: string, namespace: string, rows: TicketMetric[]): Promise<void>;
389
+ /**
390
+ * Open tickets assigned to this conductor that are frozen, each with its
391
+ * latest run's worktree path ('' when none). Drives `on_freeze`/`on_resume`.
392
+ */
393
+ fetchFrozenTickets(conductorId: string): Promise<FrozenTicket[]>;
394
+ /**
395
+ * Append one diagnostic line to a run's log (BB-293) — how the reaper
396
+ * records what it found on the run record itself, not only in this machine's
397
+ * process log. Touches `log` alone: the run is already closed, and its
398
+ * lifecycle fields are not this call's business. An empty `runUuid` writes
399
+ * nothing.
400
+ */
401
+ appendRunNote(runUuid: string, note: string): Promise<void>;
402
+ /**
403
+ * Resolve a ticket identifier (e.g. "BB-134") to its uuid+title within a
404
+ * project, or null when no such ticket exists. Used by the
405
+ * `bakers deployment tickets` helper to turn commit-message identifiers into tickets.
406
+ */
407
+ resolveTicketByIdentifier(project: string, identifier: string): Promise<{
408
+ uuid: string;
409
+ title: string;
410
+ } | null>;
411
+ /**
412
+ * Registers this conductor's addon definitions: each is upserted by name and
413
+ * version and lists this conductor; rejected ones carry the server's reason.
414
+ */
415
+ registerAddons(conductorId: string, definitions: Array<AddonDefinitionV2>): Promise<AddonRegistration>;
416
+ /** The conductor's pending inbox messages. */
417
+ fetchInbox(conductorId: string): Promise<InboxSnapshot>;
418
+ /** Answers an inbox message (by message UUID). */
419
+ writeInboxResult(messageUuid: string, status: 'running' | 'done' | 'failed', result?: MessageResult): Promise<void>;
420
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,69 @@
1
+ export interface EnsuredWorkspace {
2
+ path: string;
3
+ instructions: {
4
+ path: string;
5
+ sha256: string;
6
+ text: string;
7
+ } | null;
8
+ /**
9
+ * Whether this call CREATED the worktree (true) or reused an existing one
10
+ * (false). The core runs the `after_create` lifecycle hook on a fresh
11
+ * worktree, or on a reused one with no recorded successful `after_create`
12
+ * (BB-663), so it needs to know which happened. Lifecycle hooks themselves
13
+ * are no longer a workspace concern (BB-84): the executor owns and runs
14
+ * them best-effort.
15
+ */
16
+ created: boolean;
17
+ }
18
+ export interface BbWorkspace {
19
+ /**
20
+ * Ensure a per-ticket worktree exists. `branch`, when given, is the branch
21
+ * name the dispatcher computed for the ticket. The git plugin treats it as a
22
+ * readable title to slug into its branch template; the herdr plugin uses it
23
+ * verbatim as the worktree branch. The worktree directory key/path stays
24
+ * identifier- or branch-derived so reuse is stable across runs.
25
+ *
26
+ * `baseRef`, when given, overrides the base the new branch is created from
27
+ * (e.g. `origin/<base_branch>` so a sub-ticket stacks on its parent); if it
28
+ * does not resolve on the remote the implementation falls back to its default
29
+ * base — never a hard error.
30
+ *
31
+ * Reports `created` so the caller can decide whether `after_create` needs to
32
+ * run (fresh worktree, or no recorded success for this one — BB-663).
33
+ * Lifecycle-hook invocation is NOT a workspace responsibility
34
+ * anymore — the executor owns all hooks (BB-84).
35
+ */
36
+ ensure(identifier: string, branch?: string, baseRef?: string): Promise<EnsuredWorkspace>;
37
+ /**
38
+ * Apply the workspace's "open" layout — for a hosted workspace, the startup
39
+ * command(s) of the freshly created workspace's initial pane.
40
+ *
41
+ * OPTIONAL: a workspace with no layout concept (git, fake) simply omits it and
42
+ * the conductor calls it with `?.`.
43
+ *
44
+ * BB-237 — why this is a SEPARATE call and not part of `ensure()`. The
45
+ * layout's startup command typically names the bakers CLI, which the
46
+ * `after_create` lifecycle hook builds. Applied inside `ensure()` it
47
+ * necessarily ran BEFORE that hook, so in a self-hosting checkout the command
48
+ * fired minutes before the binary it names existed. The only remedy available
49
+ * to a config author was a cross-process handshake — a sentinel wait loop
50
+ * inside the pane command itself. Splitting the call lets the conductor order
51
+ * the two correctly (`after_create` → `applyOpenLayout`), which removes the
52
+ * need for any handshake.
53
+ *
54
+ * The conductor calls this ONLY on a freshly created workspace
55
+ * ({@link EnsuredWorkspace.created}), because only a fresh create has an
56
+ * initial pane to lay out; re-applying on reuse would start a duplicate editor
57
+ * process on every reattach. Deliberately a NARROWER gate than the one on
58
+ * `after_create`, which runs again on a reuse with no recorded success
59
+ * (BB-663), not on every dispatch.
60
+ *
61
+ * Best-effort by contract: implementations log and swallow a failing layout so
62
+ * it can never wedge dispatch.
63
+ */
64
+ applyOpenLayout?(path: string, ctx: {
65
+ identifier: string;
66
+ branch: string;
67
+ title: string;
68
+ }): Promise<void>;
69
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { Preset } from './plugins/preset.js';
2
+ export declare const commands: Preset['commands'];
@@ -0,0 +1,8 @@
1
+ export const commands = (acc) => [
2
+ ...acc,
3
+ {
4
+ name: 'oven',
5
+ plugin: '@bakers-build/conductor/commands/conductor',
6
+ describe: 'node-agent lifecycle + local registry',
7
+ },
8
+ ];