@gaia-ai/conductor 0.6.0 → 0.6.2
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/README.md +1 -1
- package/dist/src/cli/config-schema.d.ts +42 -7
- package/dist/src/cli/config-schema.js +64 -9
- package/dist/src/cli/init.js +16 -10
- package/dist/src/cli/migrate-addon-names.d.ts +82 -0
- package/dist/src/cli/migrate-addon-names.js +351 -0
- package/dist/src/cli/upgrade.d.ts +15 -0
- package/dist/src/cli/upgrade.js +87 -8
- package/dist/src/commands/conductor.d.ts +27 -3
- package/dist/src/commands/conductor.js +39 -52
- package/dist/src/config.d.ts +15 -1
- package/dist/src/config.js +119 -4
- package/dist/src/contract.d.ts +8 -0
- package/dist/src/contract.js +16 -0
- package/dist/src/core/conductor.d.ts +16 -1
- package/dist/src/core/conductor.js +23 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.js +20 -3
- package/dist/src/plugins/agent.d.ts +61 -0
- package/dist/src/plugins/agent.js +11 -0
- package/dist/src/plugins/executor.d.ts +104 -0
- package/dist/src/plugins/executor.js +1 -0
- package/dist/src/plugins/plugins.d.ts +60 -0
- package/dist/src/plugins/plugins.js +42 -0
- package/dist/src/plugins/preset.d.ts +48 -0
- package/dist/src/plugins/preset.js +23 -0
- package/dist/src/plugins/remote.d.ts +203 -0
- package/dist/src/plugins/remote.js +1 -0
- package/dist/src/plugins/workspace.d.ts +35 -0
- package/dist/src/plugins/workspace.js +1 -0
- package/dist/src/preset.d.ts +2 -2
- package/dist/src/types.d.ts +65 -0
- package/dist/src/types.js +1 -0
- package/package.json +4 -3
- package/dist/src/cli/conductor-registry.d.ts +0 -7
- package/dist/src/cli/conductor-registry.js +0 -6
- package/dist/src/cli/deployment.d.ts +0 -34
- package/dist/src/cli/deployment.js +0 -63
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export interface SpawnedSession {
|
|
2
|
+
sessionRef: string;
|
|
3
|
+
}
|
|
4
|
+
export interface ExecutorCapabilities {
|
|
5
|
+
persistent: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface SpawnRunInput {
|
|
8
|
+
ticket: {
|
|
9
|
+
uuid: string;
|
|
10
|
+
identifier: string;
|
|
11
|
+
title: string;
|
|
12
|
+
branchName: string;
|
|
13
|
+
state: string;
|
|
14
|
+
url?: string;
|
|
15
|
+
};
|
|
16
|
+
run: {
|
|
17
|
+
uuid: string;
|
|
18
|
+
id: number;
|
|
19
|
+
handler: string;
|
|
20
|
+
};
|
|
21
|
+
workspacePath: string;
|
|
22
|
+
instructions: {
|
|
23
|
+
path: string;
|
|
24
|
+
sha256: string;
|
|
25
|
+
text: string;
|
|
26
|
+
} | null;
|
|
27
|
+
command?: string;
|
|
28
|
+
env?: Record<string, string>;
|
|
29
|
+
}
|
|
30
|
+
/** The four lifecycle-hook slots, keyed exactly like `config.hooks`. */
|
|
31
|
+
export type HookName = 'after_create' | 'before_run' | 'after_run' | 'after_done';
|
|
32
|
+
/** Context for a hook invocation — carries the ticket for observable logging. */
|
|
33
|
+
export interface HookContext {
|
|
34
|
+
/** The ticket identifier/uuid the hook is running for (log context). */
|
|
35
|
+
ticket: string;
|
|
36
|
+
}
|
|
37
|
+
export interface GaiaExecutor {
|
|
38
|
+
id: string;
|
|
39
|
+
capabilities(): ExecutorCapabilities;
|
|
40
|
+
/**
|
|
41
|
+
* Run the lifecycle hook `name` (command from `config.hooks[name]`) in `cwd`,
|
|
42
|
+
* best-effort. This is the SINGLE catch point for all lifecycle hooks:
|
|
43
|
+
*
|
|
44
|
+
* - MUST NEVER throw. No configured command → silent no-op. A failing command
|
|
45
|
+
* → `logger.error({hook,worktree,ticket,err}, 'lifecycle hook failed')` then
|
|
46
|
+
* return. So a hook failure never aborts dispatch or wedges a run in
|
|
47
|
+
* `claimed`.
|
|
48
|
+
* - The underlying shell command still fails honestly (a non-zero exit
|
|
49
|
+
* rejects); only the executor catches + logs + continues.
|
|
50
|
+
*
|
|
51
|
+
* `env`, when given, is the run's resolved environment (per-ticket env_vars
|
|
52
|
+
* merged with the core GAIA_* vars, GAIA-99), merged over the hook process's
|
|
53
|
+
* inherited env. Values may be sensitive — implementations must never log
|
|
54
|
+
* them (log key names only).
|
|
55
|
+
*/
|
|
56
|
+
runHook(name: HookName, cwd: string, ctx: HookContext, env?: Record<string, string>): Promise<void>;
|
|
57
|
+
startRun(input: SpawnRunInput): Promise<SpawnedSession>;
|
|
58
|
+
/**
|
|
59
|
+
* Signal the run's agent to stop. Called by the conductor's run-finalise pass
|
|
60
|
+
* BEFORE it captures the agent transcript (GAIA-132), so a stop must not
|
|
61
|
+
* destroy the log. For herdr this is a NO-OP: at finalise time the agent is
|
|
62
|
+
* idle and its jsonl transcript is already fully written to disk, and the
|
|
63
|
+
* subsequent {@link cleanupRun} tab-close kills the PTY (the agent dies with
|
|
64
|
+
* it). The seam exists for executors whose agent outlives its UI surface.
|
|
65
|
+
* Best-effort; gated by the conductor on `capabilities().persistent`.
|
|
66
|
+
*/
|
|
67
|
+
stopRun(branch: string): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Tear down ONE run's hosted UI surface — for herdr, close only the tab whose
|
|
70
|
+
* label carries the `#<runId>` token (`startRun` labels every tab
|
|
71
|
+
* `<identifier> · <state> #<run.id>`), via `herdr tab close <tab_id>` (killing
|
|
72
|
+
* that PTY). Run-scoped by design (GAIA-183): a sibling run's tab in the same
|
|
73
|
+
* branch workspace — a still-open prior-state run or a concurrent re-claim —
|
|
74
|
+
* and any non-run tab are left untouched. Called as the LAST finalisation step,
|
|
75
|
+
* strictly after the transcript is captured, and again at dispatch to clear a
|
|
76
|
+
* reused workspace's leftover tab for the run being (re-)started. No matching
|
|
77
|
+
* tab → a quiet no-op. Does NOT touch the branch worktree (that is the
|
|
78
|
+
* reap/{@link removeWorktree} lifecycle). Best-effort: a failing tab-close is
|
|
79
|
+
* swallowed and never aborts finalisation or dispatch. A no-op for
|
|
80
|
+
* non-persistent executors.
|
|
81
|
+
*/
|
|
82
|
+
cleanupRun(branch: string, runId: number): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Tear down the branch's entire worktree (git worktree + hosted workspace),
|
|
85
|
+
* reclaiming its disk. Called by the conductor's ticket-cleanup pass once a
|
|
86
|
+
* ticket is done. A no-op for non-persistent executors (no hosted workspace);
|
|
87
|
+
* the conductor gates the call on `capabilities().persistent`.
|
|
88
|
+
*
|
|
89
|
+
* `worktreePath` is the stable, identifier-derived worktree path (from the
|
|
90
|
+
* ticket's latest run). Prefer it over `branch` to resolve the worktree: the
|
|
91
|
+
* checked-out branch is mutable (the coding agent may rename/switch it), the
|
|
92
|
+
* path is not.
|
|
93
|
+
*
|
|
94
|
+
* Returns whether the worktree was actually present on THIS host and torn
|
|
95
|
+
* down (or reclaimed on disk) — i.e. whether the teardown happened locally.
|
|
96
|
+
* `false` means nothing matched here: the worktree is either already gone or
|
|
97
|
+
* lives on another conductor's host. The reaper uses this to avoid marking a
|
|
98
|
+
* ticket `cleaned_up` for a worktree it did not actually tear down (a
|
|
99
|
+
* cross-host false-teardown), leaving it for the host that physically holds
|
|
100
|
+
* it. A genuine failure still throws (surfaced as a teardown miss); a `false`
|
|
101
|
+
* return is a clean "not here", not an error.
|
|
102
|
+
*/
|
|
103
|
+
removeWorktree(branch: string, worktreePath?: string): Promise<boolean>;
|
|
104
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ConductorLogger } from '@gaia-ai/core';
|
|
2
|
+
import type { DropSHPlugin } from 'dropsh/plugin';
|
|
3
|
+
import type { ConductorFileConfig } from '../types.js';
|
|
4
|
+
import type { GaiaAgent } from './agent.js';
|
|
5
|
+
import type { GaiaExecutor } from './executor.js';
|
|
6
|
+
import type { GaiaRemote, Ticket } from './remote.js';
|
|
7
|
+
import type { GaiaWorkspace } from './workspace.js';
|
|
8
|
+
/** Runtime dependencies handed to a plugin factory at selection time. */
|
|
9
|
+
export interface ExecutorDeps {
|
|
10
|
+
/** The in-scope conductor logger — the executor logs hook failures through it. */
|
|
11
|
+
logger: ConductorLogger;
|
|
12
|
+
}
|
|
13
|
+
export interface RemotePlugin extends DropSHPlugin {
|
|
14
|
+
readonly kind: 'remote';
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly requiredModules: string[];
|
|
17
|
+
createRemote(config: ConductorFileConfig): Promise<GaiaRemote>;
|
|
18
|
+
}
|
|
19
|
+
export interface ExecutorPlugin extends DropSHPlugin {
|
|
20
|
+
readonly kind: 'executor';
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly requiredModules: string[];
|
|
23
|
+
createExecutor(config: ConductorFileConfig, deps: ExecutorDeps): Promise<GaiaExecutor>;
|
|
24
|
+
}
|
|
25
|
+
export interface WorkspacePlugin extends DropSHPlugin {
|
|
26
|
+
readonly kind: 'workspace';
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly requiredModules: string[];
|
|
29
|
+
createWorkspace(config: ConductorFileConfig): Promise<GaiaWorkspace>;
|
|
30
|
+
}
|
|
31
|
+
export interface AgentPlugin extends DropSHPlugin {
|
|
32
|
+
readonly kind: 'agent';
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly requiredModules: string[];
|
|
35
|
+
createAgent(config: ConductorFileConfig): Promise<GaiaAgent>;
|
|
36
|
+
}
|
|
37
|
+
/** Resolves the remote from its named config slot. */
|
|
38
|
+
export declare function selectRemote(config: ConductorFileConfig): Promise<GaiaRemote>;
|
|
39
|
+
/** Resolves the executor from its named config slot, injecting the logger. */
|
|
40
|
+
export declare function selectExecutor(config: ConductorFileConfig, logger: ConductorLogger): Promise<GaiaExecutor>;
|
|
41
|
+
/** Resolves the workspace from its named config slot. */
|
|
42
|
+
export declare function selectWorkspace(config: ConductorFileConfig): Promise<GaiaWorkspace>;
|
|
43
|
+
/** A config-side agent choice: an agent plugin + an optional static priority over the ticket. */
|
|
44
|
+
export interface AgentCandidate {
|
|
45
|
+
agent: AgentPlugin;
|
|
46
|
+
priority?: (ticket: Ticket) => number;
|
|
47
|
+
}
|
|
48
|
+
/** A constructed candidate: the agent id, its GaiaAgent, and the config-side priority. */
|
|
49
|
+
export interface ResolvedAgent {
|
|
50
|
+
id: string;
|
|
51
|
+
agent: GaiaAgent;
|
|
52
|
+
priority?: (ticket: Ticket) => number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Pick the agent for a ticket: highest `priority(ticket)` wins; a candidate with
|
|
56
|
+
* no `priority` scores lowest; ties resolve by config array order (stable sort).
|
|
57
|
+
*/
|
|
58
|
+
export declare function selectAgent(candidates: ResolvedAgent[], ticket: Ticket): ResolvedAgent;
|
|
59
|
+
/** Constructs every agent candidate from the resolved config slot (single or array). */
|
|
60
|
+
export declare function selectAgents(config: ConductorFileConfig): Promise<ResolvedAgent[]>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Resolves the remote from its named config slot. */
|
|
2
|
+
export async function selectRemote(config) {
|
|
3
|
+
return config.remote.createRemote(config);
|
|
4
|
+
}
|
|
5
|
+
/** Resolves the executor from its named config slot, injecting the logger. */
|
|
6
|
+
export async function selectExecutor(config, logger) {
|
|
7
|
+
return config.executor.createExecutor(config, { logger });
|
|
8
|
+
}
|
|
9
|
+
/** Resolves the workspace from its named config slot. */
|
|
10
|
+
export async function selectWorkspace(config) {
|
|
11
|
+
return config.workspace.createWorkspace(config);
|
|
12
|
+
}
|
|
13
|
+
/** A missing `priority` scores strictly below any real number (AC-4). */
|
|
14
|
+
const DEFAULT_PRIORITY = Number.NEGATIVE_INFINITY;
|
|
15
|
+
/**
|
|
16
|
+
* Pick the agent for a ticket: highest `priority(ticket)` wins; a candidate with
|
|
17
|
+
* no `priority` scores lowest; ties resolve by config array order (stable sort).
|
|
18
|
+
*/
|
|
19
|
+
export function selectAgent(candidates, ticket) {
|
|
20
|
+
const best = candidates
|
|
21
|
+
.map((c, index) => ({
|
|
22
|
+
c,
|
|
23
|
+
index,
|
|
24
|
+
score: c.priority ? c.priority(ticket) : DEFAULT_PRIORITY,
|
|
25
|
+
}))
|
|
26
|
+
.sort((a, b) => b.score - a.score || a.index - b.index)[0];
|
|
27
|
+
if (!best) {
|
|
28
|
+
throw new Error('selectAgent: no agent candidates configured');
|
|
29
|
+
}
|
|
30
|
+
return best.c;
|
|
31
|
+
}
|
|
32
|
+
/** Constructs every agent candidate from the resolved config slot (single or array). */
|
|
33
|
+
export async function selectAgents(config) {
|
|
34
|
+
const candidates = Array.isArray(config.agent)
|
|
35
|
+
? config.agent
|
|
36
|
+
: [config.agent];
|
|
37
|
+
return Promise.all(candidates.map(async (c) => ({
|
|
38
|
+
id: c.agent.id,
|
|
39
|
+
agent: await c.agent.createAgent(config),
|
|
40
|
+
...(c.priority ? { priority: c.priority } : {}),
|
|
41
|
+
})));
|
|
42
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AddonEntry, DiscoveredContributions, GaiaPreset } from '@gaia-ai/core';
|
|
2
|
+
import type { AgentCandidate, ExecutorPlugin, RemotePlugin, WorkspacePlugin } from './plugins.js';
|
|
3
|
+
import type { Ticket } from './remote.js';
|
|
4
|
+
/**
|
|
5
|
+
* The conductor surface's concrete contributions — the strongly-typed view of
|
|
6
|
+
* the four conductor accumulators `discoverAddons(entries, 'conductor', …)`
|
|
7
|
+
* fills. Core returns them as opaque `unknown[]`; the engine narrows once, at
|
|
8
|
+
* the discovery call site (see {@link narrowConductorContributions}).
|
|
9
|
+
*/
|
|
10
|
+
export interface ConductorContributions {
|
|
11
|
+
remotes: RemotePlugin[];
|
|
12
|
+
executors: ExecutorPlugin[];
|
|
13
|
+
workspaces: WorkspacePlugin[];
|
|
14
|
+
agents: AgentCandidate[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* One `addons[]` entry on the conductor surface: core's surface-agnostic
|
|
18
|
+
* `AddonEntry` with the agent `priority` narrowed to the real `Ticket`.
|
|
19
|
+
*/
|
|
20
|
+
export type ConductorAddonEntry = string | (Exclude<AddonEntry, string> & {
|
|
21
|
+
priority?: (ticket: Ticket) => number;
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* A preset as a CONDUCTOR-surface addon writes it: core's `GaiaPreset` with the
|
|
25
|
+
* four conductor accumulators concretely typed.
|
|
26
|
+
*
|
|
27
|
+
* It literally `extends GaiaPreset`, so the compiler — not a comment — enforces
|
|
28
|
+
* that this view stays a faithful narrowing of the kernel contract. That only
|
|
29
|
+
* type-checks because core's `OpaqueAccumulator` takes `never[]` on the input
|
|
30
|
+
* side: `never[]` is assignable to `RemotePlugin[]`, so the concrete member below
|
|
31
|
+
* satisfies the opaque one contravariantly. If core ever widened that parameter
|
|
32
|
+
* to `unknown[]`, this `extends` would fail loudly here instead of the two views
|
|
33
|
+
* silently drifting apart.
|
|
34
|
+
*/
|
|
35
|
+
export interface Preset<O = unknown> extends GaiaPreset<O> {
|
|
36
|
+
remotes?: (acc: RemotePlugin[], opts: O) => RemotePlugin[] | Promise<RemotePlugin[]>;
|
|
37
|
+
executors?: (acc: ExecutorPlugin[], opts: O) => ExecutorPlugin[] | Promise<ExecutorPlugin[]>;
|
|
38
|
+
workspaces?: (acc: WorkspacePlugin[], opts: O) => WorkspacePlugin[] | Promise<WorkspacePlugin[]>;
|
|
39
|
+
agents?: (acc: AgentCandidate[], opts: O) => AgentCandidate[] | Promise<AgentCandidate[]>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The single, reviewed narrowing at the engine's discovery seam: reinterpret
|
|
43
|
+
* core's opaque conductor accumulators as this surface's concrete contribution
|
|
44
|
+
* lists. Safe because every conductor-surface preset is typed against
|
|
45
|
+
* {@link Preset}, and `resolveConductorSlots` still runs the runtime `.kind`
|
|
46
|
+
* guard on whatever actually landed in a singleton slot.
|
|
47
|
+
*/
|
|
48
|
+
export declare function narrowConductorContributions(discovered: Pick<DiscoveredContributions, 'remotes' | 'executors' | 'workspaces' | 'agents'>): ConductorContributions;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// GAIA-224 (Finding 6, decision 7): the CONDUCTOR-SURFACE view of the preset
|
|
2
|
+
// contract. `@gaia-ai/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 `@gaia-ai/conductor` — breaking the
|
|
9
|
+
// "core has zero @gaia-ai/* 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 `GaiaPreset`, 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,203 @@
|
|
|
1
|
+
export interface ConductorRegistration {
|
|
2
|
+
id: string;
|
|
3
|
+
project: string;
|
|
4
|
+
/** Empty = serve all claimable states in the project (GAIA-207). */
|
|
5
|
+
states: string[];
|
|
6
|
+
workspace: string;
|
|
7
|
+
label: string;
|
|
8
|
+
max_parallel: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ClaimOptions {
|
|
11
|
+
leaseSeconds: number;
|
|
12
|
+
conductorId: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ClaimedRun {
|
|
15
|
+
runUuid: string;
|
|
16
|
+
runId: number;
|
|
17
|
+
ticketUuid: string;
|
|
18
|
+
stateAtStart: string;
|
|
19
|
+
handler: string;
|
|
20
|
+
}
|
|
21
|
+
export interface Ticket {
|
|
22
|
+
uuid: string;
|
|
23
|
+
identifier: string;
|
|
24
|
+
title: string;
|
|
25
|
+
state: string;
|
|
26
|
+
branchName: string;
|
|
27
|
+
/** Branch the ticket's work is based on (computed server-side): parent branch, project default, or 'main'. */
|
|
28
|
+
baseBranch?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Effective environment variables (GAIA-99), resolved server-side along the
|
|
31
|
+
* parent_id chain (child wins) as canonical `.env`-style `KEY=value` lines.
|
|
32
|
+
* The conductor parses this, strips reserved keys, overlays the core GAIA_*
|
|
33
|
+
* vars, and injects the result into the agent run env AND the workspace hooks.
|
|
34
|
+
* Omitted when the ticket (and its ancestors) set none. Values may be secret.
|
|
35
|
+
*/
|
|
36
|
+
effectiveEnvVars?: string;
|
|
37
|
+
issueUrl?: string;
|
|
38
|
+
/** Ticket label term names (gaia_labels vocab), sideloaded for agent selection. Empty when none. */
|
|
39
|
+
labels: string[];
|
|
40
|
+
/** Ticket environments (gaia_environment refs), each as name + tier, sideloaded for agent selection. Empty when none. */
|
|
41
|
+
environments: {
|
|
42
|
+
name: string;
|
|
43
|
+
tier: string;
|
|
44
|
+
}[];
|
|
45
|
+
}
|
|
46
|
+
export interface ActiveRun {
|
|
47
|
+
runUuid: string;
|
|
48
|
+
ticketUuid: string;
|
|
49
|
+
ticketIdentifier: string;
|
|
50
|
+
branchName: string;
|
|
51
|
+
state: string;
|
|
52
|
+
stateAtStart: string;
|
|
53
|
+
/** Absolute path of the per-run git worktree, or '' when unset. */
|
|
54
|
+
worktreePath: string;
|
|
55
|
+
}
|
|
56
|
+
export interface ConductorStatus {
|
|
57
|
+
id: string;
|
|
58
|
+
project: string;
|
|
59
|
+
label: string;
|
|
60
|
+
status: string;
|
|
61
|
+
lastSeen: number;
|
|
62
|
+
load: number;
|
|
63
|
+
}
|
|
64
|
+
export interface FinalizableRun {
|
|
65
|
+
runUuid: string;
|
|
66
|
+
/** Numeric run id (the `#<runId>` tab-label token); scopes the run's tab-close at finalise (GAIA-183). */
|
|
67
|
+
runId: number;
|
|
68
|
+
/** Absolute path of the per-run git worktree, or '' when unset. */
|
|
69
|
+
worktreePath: string;
|
|
70
|
+
/** Id of the agent that ran (GAIA-144); routes footprint parsing at finalize. '' / undefined when unset. */
|
|
71
|
+
agent?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Per-run effort footprint the conductor writes onto `gaia_run` at close
|
|
75
|
+
* (GAIA-132). Six integer metrics, all parsed from the agent transcript —
|
|
76
|
+
* including `duration_s`, the run's wall-clock length derived from the
|
|
77
|
+
* transcript's first→last timestamps (GAIA-151), not a re-read `started_at`.
|
|
78
|
+
*/
|
|
79
|
+
export interface RunMetrics {
|
|
80
|
+
tokens: number;
|
|
81
|
+
duration_s: number;
|
|
82
|
+
agent_turns: number;
|
|
83
|
+
tool_calls: number;
|
|
84
|
+
user_prompts: number;
|
|
85
|
+
user_prompt_words: number;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A finished ticket whose worktree has not yet been torn down — the
|
|
89
|
+
* conductor's teardown work list, driven by the durable `cleaned_up` flag
|
|
90
|
+
* (GAIA-89). "Finished" = reached `done` OR already `closed`; `cleaned_up=0`
|
|
91
|
+
* means the herdr worktree still needs reclaiming. The reconciliation is NOT
|
|
92
|
+
* scoped to a conductor's machine_id, so it also surfaces tickets whose
|
|
93
|
+
* conductor was down at `done` or whose `conductor_id` was reassigned.
|
|
94
|
+
*/
|
|
95
|
+
export interface UncleanTicket {
|
|
96
|
+
ticketUuid: string;
|
|
97
|
+
/** The ticket's git branch — the key herdr resolves the worktree by. */
|
|
98
|
+
branchName: string;
|
|
99
|
+
/**
|
|
100
|
+
* Absolute path of the ticket's worktree (from its latest run's
|
|
101
|
+
* `worktree_path`), or '' when unresolved. The cwd the teardown runs in.
|
|
102
|
+
*/
|
|
103
|
+
worktreePath: string;
|
|
104
|
+
/** Workflow state (e.g. `coding`, `done`). */
|
|
105
|
+
state: string;
|
|
106
|
+
/** Whether the ticket's lifecycle has already been closed out. */
|
|
107
|
+
closed: boolean;
|
|
108
|
+
}
|
|
109
|
+
/** Extra conductor-writable gaia_run attributes (besides state/lease). */
|
|
110
|
+
export interface RunWriteAttributes {
|
|
111
|
+
/** Absolute path of the per-run git worktree (set by the conductor). */
|
|
112
|
+
worktree_path?: string;
|
|
113
|
+
/** Id of the agent the conductor chose for this run (GAIA-144), for footprint routing. */
|
|
114
|
+
agent?: string;
|
|
115
|
+
}
|
|
116
|
+
export interface GaiaRemote {
|
|
117
|
+
/** Upserts by machine_id; sets status=online. Returns the Drupal entity uuid. */
|
|
118
|
+
registerConductor(reg: ConductorRegistration): Promise<string>;
|
|
119
|
+
/**
|
|
120
|
+
* Records a heartbeat server-side: upserts by machine_id (recreating a
|
|
121
|
+
* vanished registration), refreshes last_seen/lease/load, and brings the
|
|
122
|
+
* conductor online. Keyed on machine_id, so it never 404s — the self-heal
|
|
123
|
+
* lives on the server, not the client. Returns the resulting status
|
|
124
|
+
* (`online` | `offline`).
|
|
125
|
+
*/
|
|
126
|
+
heartbeat(reg: ConductorRegistration, currentLoad: number, leaseSeconds?: number): Promise<string>;
|
|
127
|
+
getConductorStatus(conductorId: string): Promise<string | null>;
|
|
128
|
+
setConductorStatus(conductorId: string, status: 'offline' | 'online'): Promise<void>;
|
|
129
|
+
listConductors(owner?: 'me'): Promise<ConductorStatus[]>;
|
|
130
|
+
activeRunCount(conductorId: string): Promise<number>;
|
|
131
|
+
fetchActiveRuns(conductorId: string): Promise<ActiveRun[]>;
|
|
132
|
+
claimNext(claim: ClaimOptions): Promise<ClaimedRun | null>;
|
|
133
|
+
getTicket(ticketUuid: string): Promise<Ticket>;
|
|
134
|
+
/** Returns the run's worktree_path, or '' when unset. */
|
|
135
|
+
getRunWorktree(runUuid: string): Promise<string>;
|
|
136
|
+
/**
|
|
137
|
+
* Returns the identifier of the run's ticket, or '' when unresolved. Lets the
|
|
138
|
+
* release path derive the tab matching ref (the ticket identifier) from a run
|
|
139
|
+
* uuid alone.
|
|
140
|
+
*/
|
|
141
|
+
getRunTicketIdentifier(runUuid: string): Promise<string>;
|
|
142
|
+
/**
|
|
143
|
+
* Returns the branch_name of the run's ticket, or '' when unresolved. Used
|
|
144
|
+
* by dispatch for the per-(branch,state) herdr tab model.
|
|
145
|
+
*/
|
|
146
|
+
getRunTicketBranchName(runUuid: string): Promise<string>;
|
|
147
|
+
markRunning(runUuid: string, attrs?: RunWriteAttributes): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Terminalise a run to the `failed` state on a dispatch/setup error (GAIA-149).
|
|
150
|
+
* PATCHes state=failed, records the failure detail in `error_log`, and closes
|
|
151
|
+
* the run (closed + closed_date). `failed` is a terminal sibling of `expired`
|
|
152
|
+
* (dispatch error vs lease lapse), so the run stops counting toward capacity
|
|
153
|
+
* and its ticket is no longer blocked by the single-active-run gate — instead
|
|
154
|
+
* of the old "leave it `claimed` to expire after ~300s" silent stall. The
|
|
155
|
+
* failure is visible immediately and its cause is recorded.
|
|
156
|
+
*/
|
|
157
|
+
markFailed(runUuid: string, errorLog: string): Promise<void>;
|
|
158
|
+
/**
|
|
159
|
+
* Runs this conductor owns that are state=done but not yet closed — the
|
|
160
|
+
* conductor's one-shot finalisation work list.
|
|
161
|
+
*/
|
|
162
|
+
fetchFinalizableRuns(conductorId: string): Promise<FinalizableRun[]>;
|
|
163
|
+
/**
|
|
164
|
+
* Finalise a run: set closed + closed_date, (when non-empty) log, and (when
|
|
165
|
+
* given) the per-run footprint metrics (GAIA-132). No state change — the run
|
|
166
|
+
* is already done.
|
|
167
|
+
*/
|
|
168
|
+
finalizeRun(uuid: string, log: string, metrics?: RunMetrics): Promise<void>;
|
|
169
|
+
/**
|
|
170
|
+
* Finished tickets (state=done OR closed) whose worktree is not yet torn
|
|
171
|
+
* down (cleaned_up=0) — the conductor's teardown work list (GAIA-89). Driven
|
|
172
|
+
* by the durable `cleaned_up` flag AND scoped to the reaping conductor
|
|
173
|
+
* (GAIA-121): only tickets assigned to `conductorId` are loaded, so every
|
|
174
|
+
* ticket on the list is unambiguously this conductor's — a missing worktree
|
|
175
|
+
* means "already torn down on this host", not "belongs to another host". The
|
|
176
|
+
* same query backs the live tick and the standalone `gaia conductor reap`.
|
|
177
|
+
* Empty once every finished ticket this conductor owns is cleaned.
|
|
178
|
+
*/
|
|
179
|
+
fetchUncleanedTickets(conductorId: string): Promise<UncleanTicket[]>;
|
|
180
|
+
/**
|
|
181
|
+
* Close a ticket: set closed=true + closed_date. Ticket-lifecycle only, and
|
|
182
|
+
* decoupled from teardown — a done ticket is closed even if its worktree
|
|
183
|
+
* teardown later fails. No state change (the ticket is already done).
|
|
184
|
+
*/
|
|
185
|
+
closeTicket(uuid: string): Promise<void>;
|
|
186
|
+
/**
|
|
187
|
+
* Mark a ticket's worktree torn down: set cleaned_up=true so it drops off the
|
|
188
|
+
* {@link fetchUncleanedTickets} work list. Written ONLY after a verified
|
|
189
|
+
* teardown, so a teardown miss leaves cleaned_up=0 and the next reconciliation
|
|
190
|
+
* retries — one miss never orphans the workspace, and a re-run on an
|
|
191
|
+
* already-cleaned ticket is a no-op (it is no longer on the list).
|
|
192
|
+
*/
|
|
193
|
+
markTicketCleanedUp(uuid: string): Promise<void>;
|
|
194
|
+
/**
|
|
195
|
+
* Resolve a ticket identifier (e.g. "GAIA-134") to its uuid+title within a
|
|
196
|
+
* project, or null when no such ticket exists. Used by the
|
|
197
|
+
* `gaia deployment tickets` helper to turn commit-message identifiers into tickets.
|
|
198
|
+
*/
|
|
199
|
+
resolveTicketByIdentifier(project: string, identifier: string): Promise<{
|
|
200
|
+
uuid: string;
|
|
201
|
+
title: string;
|
|
202
|
+
} | null>;
|
|
203
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
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 only on a fresh
|
|
11
|
+
* worktree, so it needs to know which happened. Lifecycle hooks themselves
|
|
12
|
+
* are no longer a workspace concern (GAIA-84): the executor owns and runs
|
|
13
|
+
* them best-effort.
|
|
14
|
+
*/
|
|
15
|
+
created: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface GaiaWorkspace {
|
|
18
|
+
/**
|
|
19
|
+
* Ensure a per-ticket worktree exists. `branch`, when given, is the branch
|
|
20
|
+
* name the dispatcher computed for the ticket. The git plugin treats it as a
|
|
21
|
+
* readable title to slug into its branch template; the herdr plugin uses it
|
|
22
|
+
* verbatim as the worktree branch. The worktree directory key/path stays
|
|
23
|
+
* identifier- or branch-derived so reuse is stable across runs.
|
|
24
|
+
*
|
|
25
|
+
* `baseRef`, when given, overrides the base the new branch is created from
|
|
26
|
+
* (e.g. `origin/<base_branch>` so a sub-ticket stacks on its parent); if it
|
|
27
|
+
* does not resolve on the remote the implementation falls back to its default
|
|
28
|
+
* base — never a hard error.
|
|
29
|
+
*
|
|
30
|
+
* Reports `created` so the caller can run the `after_create` hook only on a
|
|
31
|
+
* fresh worktree. Lifecycle-hook invocation is NOT a workspace responsibility
|
|
32
|
+
* anymore — the executor owns all hooks (GAIA-84).
|
|
33
|
+
*/
|
|
34
|
+
ensure(identifier: string, branch?: string, baseRef?: string): Promise<EnsuredWorkspace>;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/src/preset.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const commands:
|
|
1
|
+
import type { Preset } from './plugins/preset.js';
|
|
2
|
+
export declare const commands: Preset['commands'];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { DropSHPlugin } from 'dropsh/plugin';
|
|
2
|
+
import type { AgentCandidate, ExecutorPlugin, RemotePlugin, WorkspacePlugin } from './plugins/plugins.js';
|
|
3
|
+
/** Normalized single conductor. */
|
|
4
|
+
export interface ConductorSettings {
|
|
5
|
+
/** Default: `${project} @ ${checkoutRoot}`. */
|
|
6
|
+
label: string;
|
|
7
|
+
/**
|
|
8
|
+
* Stable node identity (gaia_conductor.machine_id) this process registers as.
|
|
9
|
+
* Defaults to a hash of hostname + checkout path. Set it to pin a conductor to
|
|
10
|
+
* a known identity — e.g. so a ticket can be pre-assigned to it (conductor
|
|
11
|
+
* assignment is the run-start trigger), which the e2e fixtures rely on.
|
|
12
|
+
*/
|
|
13
|
+
machine_id?: string;
|
|
14
|
+
/** Project name (gaia_project.name); resolved at registration. */
|
|
15
|
+
project: string;
|
|
16
|
+
/**
|
|
17
|
+
* Workflow states this conductor serves. An empty list means it serves every
|
|
18
|
+
* claimable state in its project; a non-empty list narrows it to those
|
|
19
|
+
* states (GAIA-207).
|
|
20
|
+
*/
|
|
21
|
+
states: string[];
|
|
22
|
+
/**
|
|
23
|
+
* Agent prompt template - the GAIA run contract, NOT project workflow. Bounds
|
|
24
|
+
* the agent to exactly one state and tells it to release + stop, keeping run
|
|
25
|
+
* mechanics out of the repo's WORKFLOW.md. Placeholders: `{identifier}`,
|
|
26
|
+
* `{state}`, `{runUuid}`. Defaults to `DEFAULT_AGENT_PROMPT`.
|
|
27
|
+
*/
|
|
28
|
+
prompt: string;
|
|
29
|
+
/** Default: 1. */
|
|
30
|
+
max_parallel: number;
|
|
31
|
+
poll_interval_ms: number;
|
|
32
|
+
lease_seconds: number;
|
|
33
|
+
hooks?: {
|
|
34
|
+
after_create?: string;
|
|
35
|
+
before_run?: string;
|
|
36
|
+
after_run?: string;
|
|
37
|
+
after_done?: string;
|
|
38
|
+
};
|
|
39
|
+
/** Control-plane site settings used for dropsh and agent env. */
|
|
40
|
+
site: {
|
|
41
|
+
base_url: string;
|
|
42
|
+
jsonapi_prefix: string;
|
|
43
|
+
};
|
|
44
|
+
/** Absolute path of the loaded config. */
|
|
45
|
+
config_path: string;
|
|
46
|
+
}
|
|
47
|
+
/** Whole conductor.config.js (dropsh-superset): site + named plugin slots. */
|
|
48
|
+
export interface ConductorFileConfig extends ConductorSettings {
|
|
49
|
+
remote: RemotePlugin;
|
|
50
|
+
executor: ExecutorPlugin;
|
|
51
|
+
/** The agent slot: a single candidate or an array of candidates (shape preserved as authored). Selection normalizes + picks one per dispatch. */
|
|
52
|
+
agent: AgentCandidate | AgentCandidate[];
|
|
53
|
+
workspace: WorkspacePlugin;
|
|
54
|
+
/** dropsh-layer plugins (auth etc.); separate from the GAIA slots. */
|
|
55
|
+
plugins?: DropSHPlugin[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* GAIA-201 split config model: the ENGINE half of a conductor config —
|
|
59
|
+
* everything a `conductor.config.js` carries EXCEPT the connection (`site`) and
|
|
60
|
+
* the auth `plugins`, which now live in a separate `gaia.config.js`
|
|
61
|
+
* (`GaiaConnectionConfig`). `loadConductorConfig` returns this; the conductor
|
|
62
|
+
* command composes it with the connection (`composeConductorConfig`) into the
|
|
63
|
+
* full `ConductorFileConfig` the engine consumes.
|
|
64
|
+
*/
|
|
65
|
+
export type ConductorEngineConfig = Omit<ConductorFileConfig, 'site' | 'plugins'>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaia-ai/conductor",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "GAIA conductor engine + CLI: registers, claims tickets via JSON:API, dispatches agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
11
|
".": "./dist/src/index.js",
|
|
12
|
+
"./contract": "./dist/src/contract.js",
|
|
12
13
|
"./preset": "./dist/src/preset.js",
|
|
13
14
|
"./commands/conductor": "./dist/src/commands/conductor.js",
|
|
14
15
|
"./package.json": "./package.json"
|
|
@@ -24,10 +25,10 @@
|
|
|
24
25
|
"repository": {
|
|
25
26
|
"type": "git",
|
|
26
27
|
"url": "git+https://git.key-tec.de/keytec/gaia.git",
|
|
27
|
-
"directory": "conductor
|
|
28
|
+
"directory": "gaia-cli/conductor"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"@gaia-ai/core": "^0.6.
|
|
31
|
+
"@gaia-ai/core": "^0.6.2",
|
|
31
32
|
"@dropsh/plugin-oauth2": "^0.5.7",
|
|
32
33
|
"@dropsh/plugin-jsonapi-schema": "^0.5.8",
|
|
33
34
|
"commander": "^12.1.0",
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export type { ConductorRegistryEntry } from '@gaia-ai/core';
|
|
2
|
-
import { conductorRegistryPath, getRegisteredConductor, listRegisteredConductors, registerConductor, removeConductor } from '@gaia-ai/core';
|
|
3
|
-
export { conductorRegistryPath };
|
|
4
|
-
export declare const list: typeof listRegisteredConductors;
|
|
5
|
-
export declare const get: typeof getRegisteredConductor;
|
|
6
|
-
export declare const register: typeof registerConductor;
|
|
7
|
-
export declare const remove: typeof removeConductor;
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { conductorRegistryPath, getRegisteredConductor, listRegisteredConductors, registerConductor, removeConductor, } from '@gaia-ai/core';
|
|
2
|
-
export { conductorRegistryPath };
|
|
3
|
-
export const list = listRegisteredConductors;
|
|
4
|
-
export const get = getRegisteredConductor;
|
|
5
|
-
export const register = registerConductor;
|
|
6
|
-
export const remove = removeConductor;
|