@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.
- package/LICENSE +21 -0
- package/README.md +4 -2
- package/dist/src/bakers-project-context.d.ts +12 -0
- package/dist/src/bakers-project-context.js +59 -0
- package/dist/src/cli/config-plan.d.ts +35 -0
- package/dist/src/cli/config-plan.js +1264 -0
- package/dist/src/cli/config-schema.d.ts +137 -0
- package/dist/src/cli/config-schema.js +239 -0
- package/dist/src/cli/config-source-edit.d.ts +17 -0
- package/dist/src/cli/config-source-edit.js +95 -0
- package/dist/src/cli/config-templates.d.ts +43 -0
- package/dist/src/cli/config-templates.js +130 -0
- package/dist/src/cli/config-validation.d.ts +12 -0
- package/dist/src/cli/config-validation.js +353 -0
- package/dist/src/cli/migrate-addon-names.d.ts +82 -0
- package/dist/src/cli/migrate-addon-names.js +334 -0
- package/dist/src/cli/project-context.d.ts +21 -0
- package/dist/src/cli/project-context.js +105 -0
- package/dist/src/commands/conductor.d.ts +91 -0
- package/dist/src/commands/conductor.js +843 -0
- package/dist/src/config.d.ts +109 -0
- package/dist/src/config.js +712 -0
- package/dist/src/contract.d.ts +13 -0
- package/dist/src/contract.js +19 -0
- package/dist/src/core/action-queue.d.ts +31 -0
- package/dist/src/core/action-queue.js +69 -0
- package/dist/src/core/attempt.d.ts +25 -0
- package/dist/src/core/attempt.js +41 -0
- package/dist/src/core/conductor.d.ts +236 -0
- package/dist/src/core/conductor.js +1293 -0
- package/dist/src/core/host-key.d.ts +19 -0
- package/dist/src/core/host-key.js +27 -0
- package/dist/src/core/inbox-runtime.d.ts +48 -0
- package/dist/src/core/inbox-runtime.js +180 -0
- package/dist/src/core/mr-changes.d.ts +18 -0
- package/dist/src/core/mr-changes.js +47 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +27 -0
- package/dist/src/plugins/agent-args.d.ts +43 -0
- package/dist/src/plugins/agent-args.js +221 -0
- package/dist/src/plugins/agent.d.ts +121 -0
- package/dist/src/plugins/agent.js +12 -0
- package/dist/src/plugins/executor.d.ts +182 -0
- package/dist/src/plugins/executor.js +1 -0
- package/dist/src/plugins/message.d.ts +61 -0
- package/dist/src/plugins/message.js +47 -0
- package/dist/src/plugins/plugins.d.ts +96 -0
- package/dist/src/plugins/plugins.js +120 -0
- package/dist/src/plugins/preset.d.ts +61 -0
- package/dist/src/plugins/preset.js +23 -0
- package/dist/src/plugins/process-host.d.ts +7 -0
- package/dist/src/plugins/process-host.js +1 -0
- package/dist/src/plugins/remote.d.ts +420 -0
- package/dist/src/plugins/remote.js +1 -0
- package/dist/src/plugins/workspace.d.ts +69 -0
- package/dist/src/plugins/workspace.js +1 -0
- package/dist/src/preset.d.ts +2 -0
- package/dist/src/preset.js +8 -0
- package/dist/src/types.d.ts +132 -0
- package/dist/src/types.js +6 -0
- 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 @@
|
|
|
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 {};
|