@gaia-ai/conductor 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli/conductor-registry.d.ts +7 -0
- package/dist/src/cli/conductor-registry.js +6 -0
- package/dist/src/cli/config-schema.d.ts +54 -0
- package/dist/src/cli/config-schema.js +91 -0
- package/dist/src/cli/deployment.d.ts +34 -0
- package/dist/src/cli/deployment.js +63 -0
- package/dist/src/cli/init.d.ts +31 -33
- package/dist/src/cli/init.js +96 -80
- package/dist/src/cli/upgrade.d.ts +38 -0
- package/dist/src/cli/upgrade.js +143 -0
- package/dist/src/cli/version-check.js +5 -1
- package/dist/src/commands/conductor.d.ts +33 -0
- package/dist/src/{cli/gaia.js → commands/conductor.js} +115 -210
- package/dist/src/config.d.ts +50 -23
- package/dist/src/config.js +127 -153
- package/dist/src/core/conductor.js +14 -11
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.js +11 -2
- package/dist/src/preset.d.ts +2 -0
- package/dist/src/preset.js +8 -0
- package/package.json +7 -5
- package/dist/src/cli/gaia.d.ts +0 -23
- package/dist/src/cli/local-registry.d.ts +0 -14
- package/dist/src/cli/local-registry.js +0 -56
package/dist/src/config.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { dirname, join, resolve } from 'node:path';
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
4
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { discoverAddons, emptyContributions, resolveConductorSlots, resolveModuleEslintStyle, } from '@gaia-ai/core';
|
|
5
|
+
// GAIA-201: the `.gaia/` walk-up + connection resolution moved to `@gaia-ai/core`
|
|
6
|
+
// (`resolveConfigPath` / `resolveGaiaConfigPath` / `findGaiaDir`). Re-export the
|
|
7
|
+
// engine resolver here so existing conductor callers/tests keep importing it
|
|
8
|
+
// from `../config.js`.
|
|
9
|
+
export { findGaiaDir, resolveConfigPath, } from '@gaia-ai/core';
|
|
5
10
|
/**
|
|
6
|
-
* Default agent prompt - the
|
|
11
|
+
* Default agent prompt - the project instruction contract. One run works EXACTLY one state;
|
|
7
12
|
* the agent must stop instead of running the whole flow in one session. The run
|
|
8
13
|
* is closed automatically when the ticket state changes on the next claim - the
|
|
9
14
|
* agent does not release it. Run mechanics live here (not in the repo's
|
|
10
|
-
* WORKFLOW.md). The prompt
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* intake line unprinted unless an external skill-forcing hook happened to fire. A
|
|
14
|
-
* conductor config may override via the `prompt` field.
|
|
15
|
+
* WORKFLOW.md). The prompt points directly at the matching project-owned state
|
|
16
|
+
* section and does not prescribe an agent skill. A conductor config may override
|
|
17
|
+
* it via the `prompt` field.
|
|
15
18
|
*
|
|
16
19
|
* The ticket + its comments are NOT embedded (GAIA-112): embedding unbounded
|
|
17
20
|
* ticket content into a single typed pane line overran the PTY canonical line
|
|
@@ -34,15 +37,12 @@ import { pathToFileURL } from 'node:url';
|
|
|
34
37
|
* wrapper, GAIA-118, was the thing that re-inflated the line past the cap and
|
|
35
38
|
* truncated it mid-quote).
|
|
36
39
|
* Placeholders: `{identifier}`, `{state}`, `{runUuid}` ({state} falls back to
|
|
37
|
-
* `
|
|
40
|
+
* `qualification` for unclassified tickets).
|
|
38
41
|
*/
|
|
39
|
-
export const DEFAULT_AGENT_PROMPT = `You are
|
|
40
|
-
`Invoke the gaia skill and run \`ticket:run {identifier} {state}\` — it first ` +
|
|
41
|
-
`reads the ticket + all comments ` +
|
|
42
|
+
export const DEFAULT_AGENT_PROMPT = `You are working ticket {identifier}, current state: {state}. Read the ticket and all comments ` +
|
|
42
43
|
`(gaia dropsh read gaia_ticket/gaia_ticket/$GAIA_ID --include comments), ` +
|
|
43
|
-
`
|
|
44
|
-
`
|
|
45
|
-
`prepare a later state.`;
|
|
44
|
+
`then read ./WORKFLOW.md and execute only its ordered "State: {state}" section. ` +
|
|
45
|
+
`Do not start or prepare a later state.`;
|
|
46
46
|
function requirePlugin(value, kind) {
|
|
47
47
|
if (!isRecord(value) || value.kind !== kind) {
|
|
48
48
|
throw new Error(`conductor config requires a ${kind} plugin in the "${kind}" slot`);
|
|
@@ -73,16 +73,7 @@ async function loadNamedPlugin(entry, configPath) {
|
|
|
73
73
|
pathToFileURL(`${process.cwd()}/`).href,
|
|
74
74
|
import.meta.url,
|
|
75
75
|
];
|
|
76
|
-
|
|
77
|
-
for (const base of bases) {
|
|
78
|
-
try {
|
|
79
|
-
resolved = createRequire(base).resolve(entry.plugin);
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
// try the next base
|
|
84
|
-
}
|
|
85
|
-
}
|
|
76
|
+
const resolved = resolveModuleEslintStyle(entry.plugin, bases);
|
|
86
77
|
if (resolved === undefined) {
|
|
87
78
|
throw new Error(`conductor config cannot resolve plugin '${entry.plugin}'`);
|
|
88
79
|
}
|
|
@@ -144,23 +135,6 @@ export async function resolveAgents(raw, configPath) {
|
|
|
144
135
|
? Promise.all(raw.map(resolveOne))
|
|
145
136
|
: resolveOne(raw);
|
|
146
137
|
}
|
|
147
|
-
/**
|
|
148
|
-
* Resolve the `plugins[]` array: descriptor entries are constructed, already-
|
|
149
|
-
* constructed entries pass through. No kind-guard (plugins are not slotted).
|
|
150
|
-
*
|
|
151
|
-
* A factory may return one plugin OR an array of plugins — an aggregator built
|
|
152
|
-
* with dropsh's `composePlugins(a(), b())` returns the flat child list. dropsh's
|
|
153
|
-
* own `loadConfig` flattens such a nested entry one level; we mirror that here so
|
|
154
|
-
* `buildProgram({ plugins })` — which reads each entry's hooks/renderers per
|
|
155
|
-
* top-level element and does NOT recurse — sees every child.
|
|
156
|
-
*/
|
|
157
|
-
async function resolvePlugins(raw, configPath) {
|
|
158
|
-
if (!Array.isArray(raw)) {
|
|
159
|
-
return [];
|
|
160
|
-
}
|
|
161
|
-
const resolved = await Promise.all(raw.map((entry) => isPluginDescriptor(entry) ? loadNamedPlugin(entry, configPath) : entry));
|
|
162
|
-
return resolved.flat();
|
|
163
|
-
}
|
|
164
138
|
function isRecord(value) {
|
|
165
139
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
166
140
|
}
|
|
@@ -179,98 +153,15 @@ function optionalPositiveInteger(value, fallback, key) {
|
|
|
179
153
|
}
|
|
180
154
|
return value;
|
|
181
155
|
}
|
|
182
|
-
/** A repo's config files live in this dir, one conductor config per conductor. */
|
|
183
|
-
const GAIA_DIR = '.gaia';
|
|
184
|
-
/** The default conductor's file name; its stem is `conductor`. */
|
|
185
|
-
const DEFAULT_CONFIG = 'conductor.config.js';
|
|
186
|
-
/** Variant files are `<stem>.conductor.config.js`. */
|
|
187
|
-
const VARIANT_SUFFIX = '.conductor.config.js';
|
|
188
156
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
157
|
+
* Load the ENGINE half of a conductor config (GAIA-201 split model). Reads
|
|
158
|
+
* remote/executor/agent/workspace + project/states/machine_id/label/prompt/
|
|
159
|
+
* scheduler/hooks — and deliberately NOT `site` or the auth `plugins`, which now
|
|
160
|
+
* live in a `gaia.config.js` connection config. The conductor command composes
|
|
161
|
+
* the two via `composeConductorConfig`. A legacy `conductor.config.js` still
|
|
162
|
+
* carrying `site`/`plugins` loads fine — those two keys are simply ignored here
|
|
163
|
+
* (the connection loader reads them, back-compat).
|
|
194
164
|
*/
|
|
195
|
-
function configStems(gaiaDir) {
|
|
196
|
-
return readdirSync(gaiaDir)
|
|
197
|
-
.map((f) => f === DEFAULT_CONFIG
|
|
198
|
-
? 'conductor'
|
|
199
|
-
: f.endsWith(VARIANT_SUFFIX)
|
|
200
|
-
? f.slice(0, -VARIANT_SUFFIX.length)
|
|
201
|
-
: undefined)
|
|
202
|
-
.filter((s) => s !== undefined)
|
|
203
|
-
.sort();
|
|
204
|
-
}
|
|
205
|
-
/** Map a conductor stem back to its file name. */
|
|
206
|
-
function fileForStem(stem) {
|
|
207
|
-
return stem === 'conductor' ? DEFAULT_CONFIG : `${stem}${VARIANT_SUFFIX}`;
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Walk from `cwd` root-ward (git/eslint style) to the nearest ancestor whose
|
|
211
|
-
* `.gaia/` dir holds at least one conductor config; return that `.gaia/` dir, or
|
|
212
|
-
* `undefined` if none is found up to the filesystem root.
|
|
213
|
-
*/
|
|
214
|
-
function findGaiaDir(cwd) {
|
|
215
|
-
let dir = resolve(cwd);
|
|
216
|
-
for (;;) {
|
|
217
|
-
const gaiaDir = join(dir, GAIA_DIR);
|
|
218
|
-
if (existsSync(gaiaDir) && configStems(gaiaDir).length > 0) {
|
|
219
|
-
return gaiaDir;
|
|
220
|
-
}
|
|
221
|
-
const parent = dirname(dir);
|
|
222
|
-
if (parent === dir) {
|
|
223
|
-
return undefined;
|
|
224
|
-
}
|
|
225
|
-
dir = parent;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Resolve the conductor config path from `cwd`.
|
|
230
|
-
*
|
|
231
|
-
* 1. An explicit `--config` / `$GAIA_CONDUCTOR_CONFIG` wins verbatim (no walk).
|
|
232
|
-
* 2. Otherwise walk root-ward to the nearest `.gaia/` dir holding a conductor config
|
|
233
|
-
* — so any subdirectory of a project/worktree resolves the same dir.
|
|
234
|
-
* - A `--conductor <name>` / `$GAIA_CONDUCTOR` selector resolves the stem's
|
|
235
|
-
* file (`conductor` → `conductor.config.js`, else
|
|
236
|
-
* `<name>.conductor.config.js`; error listing the stems if absent).
|
|
237
|
-
* - No selector: `conductor.config.js` present → it is the default; else
|
|
238
|
-
* exactly one config → use it (back-compat); else → error naming the
|
|
239
|
-
* stems + the selector.
|
|
240
|
-
* 3. No `.gaia/` config anywhere up the tree → an actionable `gaia init` error
|
|
241
|
-
* (never leak a raw "Cannot find module" from a later import()).
|
|
242
|
-
*/
|
|
243
|
-
export function resolveConfigPath(override, cwd = process.cwd(), conductorName) {
|
|
244
|
-
const explicit = override ?? process.env.GAIA_CONDUCTOR_CONFIG;
|
|
245
|
-
if (explicit) {
|
|
246
|
-
return explicit;
|
|
247
|
-
}
|
|
248
|
-
const gaiaDir = findGaiaDir(cwd);
|
|
249
|
-
if (gaiaDir === undefined) {
|
|
250
|
-
throw new Error(`no .gaia/conductor.config.js found from ${cwd} upward; run \`gaia init\``);
|
|
251
|
-
}
|
|
252
|
-
const stems = configStems(gaiaDir);
|
|
253
|
-
const name = conductorName ?? process.env.GAIA_CONDUCTOR;
|
|
254
|
-
if (name !== undefined && name !== '') {
|
|
255
|
-
const candidate = join(gaiaDir, fileForStem(name));
|
|
256
|
-
if (!existsSync(candidate)) {
|
|
257
|
-
throw new Error(`no conductor '${name}' in ${gaiaDir}; available: ${stems.join(', ')}`);
|
|
258
|
-
}
|
|
259
|
-
return candidate;
|
|
260
|
-
}
|
|
261
|
-
// No selector: the file named conductor.config.js is the default (AC-1, AC-2).
|
|
262
|
-
// Two conductor.config.js files cannot coexist in one dir, so ">1 default"
|
|
263
|
-
// (AC-5) is structurally impossible — no ambiguity branch is needed.
|
|
264
|
-
if (stems.includes('conductor')) {
|
|
265
|
-
return join(gaiaDir, DEFAULT_CONFIG);
|
|
266
|
-
}
|
|
267
|
-
// Back-compat: a lone config resolves even without the default name.
|
|
268
|
-
if (stems.length === 1) {
|
|
269
|
-
return join(gaiaDir, fileForStem(stems[0]));
|
|
270
|
-
}
|
|
271
|
-
throw new Error(`${stems.length} conductors in ${gaiaDir} (${stems.join(', ')}); ` +
|
|
272
|
-
'select one with --conductor <name> or $GAIA_CONDUCTOR');
|
|
273
|
-
}
|
|
274
165
|
export async function loadConductorConfig(configFile) {
|
|
275
166
|
const configPath = resolve(configFile);
|
|
276
167
|
const module = (await import(pathToFileURL(configPath).href));
|
|
@@ -279,13 +170,13 @@ export async function loadConductorConfig(configFile) {
|
|
|
279
170
|
throw new Error('conductor config default export must be an object');
|
|
280
171
|
}
|
|
281
172
|
const config = raw;
|
|
282
|
-
const site = isRecord(config.site) ? config.site : {};
|
|
283
|
-
const baseUrl = requireNonEmptyString(site.base_url, 'site.base_url');
|
|
284
173
|
const project = requireNonEmptyString(config.project, 'project');
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const states = config.states
|
|
174
|
+
// `states` is optional. An empty (or absent) list means the conductor serves
|
|
175
|
+
// every claimable state in its project; a non-empty list narrows it to those
|
|
176
|
+
// states (GAIA-207). When present it must be an array of non-empty strings.
|
|
177
|
+
const states = Array.isArray(config.states)
|
|
178
|
+
? config.states.map((state) => requireNonEmptyString(state, 'states'))
|
|
179
|
+
: [];
|
|
289
180
|
// machine_id is required — the config MUST set it; there is no derived
|
|
290
181
|
// fallback. The id is defined in a single place (config.machine_id), which
|
|
291
182
|
// the CLI lifecycle commands and registration all read, never re-derive. The
|
|
@@ -298,21 +189,46 @@ export async function loadConductorConfig(configFile) {
|
|
|
298
189
|
const label = typeof config.label === 'string' && config.label.trim() !== ''
|
|
299
190
|
? config.label
|
|
300
191
|
: machineId;
|
|
192
|
+
// GAIA-215: discover the conductor `addons: []` surface (last-wins singletons,
|
|
193
|
+
// agent candidate list). A pre-existing per-slot descriptor still loads and
|
|
194
|
+
// WINS over a discovered contributor of the same kind (back-compat additive).
|
|
195
|
+
const discovered = Array.isArray(config.addons)
|
|
196
|
+
? await discoverAddons(config.addons, 'conductor', [
|
|
197
|
+
pathToFileURL(configPath).href,
|
|
198
|
+
pathToFileURL(`${process.cwd()}/`).href,
|
|
199
|
+
import.meta.url,
|
|
200
|
+
])
|
|
201
|
+
: emptyContributions();
|
|
202
|
+
const wired = resolveConductorSlots(discovered);
|
|
203
|
+
const remote = config.remote !== undefined
|
|
204
|
+
? await resolveSlot(config.remote, 'remote', configPath)
|
|
205
|
+
: wired.remote;
|
|
206
|
+
if (!remote) {
|
|
207
|
+
throw new Error('conductor config resolves no remote — name a remote addon in addons[] or set the remote slot');
|
|
208
|
+
}
|
|
209
|
+
const executor = config.executor !== undefined
|
|
210
|
+
? await resolveSlot(config.executor, 'executor', configPath)
|
|
211
|
+
: wired.executor;
|
|
212
|
+
if (!executor) {
|
|
213
|
+
throw new Error('conductor config resolves no executor — name an executor addon in addons[] or set the executor slot');
|
|
214
|
+
}
|
|
215
|
+
const workspace = config.workspace !== undefined
|
|
216
|
+
? await resolveSlot(config.workspace, 'workspace', configPath)
|
|
217
|
+
: wired.workspace;
|
|
218
|
+
if (!workspace) {
|
|
219
|
+
throw new Error('conductor config resolves no workspace — name a workspace addon in addons[] or set the workspace slot');
|
|
220
|
+
}
|
|
221
|
+
const agent = config.agent !== undefined
|
|
222
|
+
? await resolveAgents(config.agent, configPath)
|
|
223
|
+
: wired.agents;
|
|
224
|
+
if (Array.isArray(agent) && agent.length === 0) {
|
|
225
|
+
throw new Error('conductor config resolves no agent — name an agent addon in addons[] or set the agent slot');
|
|
226
|
+
}
|
|
301
227
|
return {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
? site.jsonapi_prefix
|
|
307
|
-
: '/jsonapi',
|
|
308
|
-
},
|
|
309
|
-
...(Array.isArray(config.plugins)
|
|
310
|
-
? { plugins: await resolvePlugins(config.plugins, configPath) }
|
|
311
|
-
: {}),
|
|
312
|
-
remote: await resolveSlot(config.remote, 'remote', configPath),
|
|
313
|
-
executor: await resolveSlot(config.executor, 'executor', configPath),
|
|
314
|
-
agent: await resolveAgents(config.agent, configPath),
|
|
315
|
-
workspace: await resolveSlot(config.workspace, 'workspace', configPath),
|
|
228
|
+
remote,
|
|
229
|
+
executor,
|
|
230
|
+
agent,
|
|
231
|
+
workspace,
|
|
316
232
|
label,
|
|
317
233
|
machine_id: machineId,
|
|
318
234
|
project,
|
|
@@ -327,3 +243,61 @@ export async function loadConductorConfig(configFile) {
|
|
|
327
243
|
config_path: configPath,
|
|
328
244
|
};
|
|
329
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Compose the full `ConductorFileConfig` the engine consumes from the engine
|
|
248
|
+
* half (`loadConductorConfig`) and the connection half (`loadGaiaConfig`). The
|
|
249
|
+
* connection supplies `site` + the auth `plugins`; `ensureAuthenticated` /
|
|
250
|
+
* `selectRemote` read them exactly as before — the fields just arrive from
|
|
251
|
+
* `gaia.config.js` now.
|
|
252
|
+
*/
|
|
253
|
+
export function composeConductorConfig(engine, connection) {
|
|
254
|
+
return {
|
|
255
|
+
...engine,
|
|
256
|
+
site: connection.site,
|
|
257
|
+
...(connection.plugins.length > 0 ? { plugins: connection.plugins } : {}),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const DEFAULT_CONFIG = 'conductor.config.js';
|
|
261
|
+
const VARIANT_SUFFIX = '.conductor.config.js';
|
|
262
|
+
/**
|
|
263
|
+
* The conductor **stems** in a `.gaia/` dir — the file naming predicate of
|
|
264
|
+
* GAIA-137: exactly `conductor.config.js` (stem `conductor`) or
|
|
265
|
+
* `<variant>.conductor.config.js` (stem `<variant>`). The resolution itself
|
|
266
|
+
* lives in `@gaia-ai/core` (re-exported above); this is the enumeration.
|
|
267
|
+
*/
|
|
268
|
+
export function configStems(gaiaDir) {
|
|
269
|
+
return readdirSync(gaiaDir)
|
|
270
|
+
.map((f) => f === DEFAULT_CONFIG
|
|
271
|
+
? 'conductor'
|
|
272
|
+
: f.endsWith(VARIANT_SUFFIX)
|
|
273
|
+
? f.slice(0, -VARIANT_SUFFIX.length)
|
|
274
|
+
: undefined)
|
|
275
|
+
.filter((s) => s !== undefined)
|
|
276
|
+
.sort();
|
|
277
|
+
}
|
|
278
|
+
/** Map a conductor stem back to its file name. */
|
|
279
|
+
export function fileForStem(stem) {
|
|
280
|
+
return stem === 'conductor' ? DEFAULT_CONFIG : `${stem}${VARIANT_SUFFIX}`;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* List the conductor-config **file names** in a `.gaia/` dir, using the same
|
|
284
|
+
* naming predicate the loader uses (`configStems`). This is the only supported
|
|
285
|
+
* enumeration for the `@gaia/upgrade-project` states-strip: it visits the
|
|
286
|
+
* default `conductor.config.js` + every `<variant>.conductor.config.js`, and
|
|
287
|
+
* never an unrelated JS config (`vite.config.js`, the near-miss
|
|
288
|
+
* `myconductor.config.js`).
|
|
289
|
+
*/
|
|
290
|
+
export function listConductorConfigFiles(gaiaDir) {
|
|
291
|
+
return configStems(gaiaDir).map(fileForStem);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Remove the obsolete `states:` array property from a conductor-config JS
|
|
295
|
+
* source (GAIA-207 made `states` optional — empty ⇒ serve all claimable
|
|
296
|
+
* states — so `@gaia/upgrade-project` drops the now-dead key). Pure string
|
|
297
|
+
* transform: it strips the `states: [ ... ]` property (line form or inline in
|
|
298
|
+
* an object literal) with its trailing comma, and rewrites nothing else. A
|
|
299
|
+
* `substates:`/other key containing "states" is not matched.
|
|
300
|
+
*/
|
|
301
|
+
export function stripStatesFromConfigSource(source) {
|
|
302
|
+
return source.replace(/\n?[ \t]*(?<![A-Za-z0-9_$])states[ \t]*:[ \t]*\[[^\]]*\][ \t]*,?/g, '');
|
|
303
|
+
}
|
|
@@ -236,7 +236,9 @@ export class Conductor {
|
|
|
236
236
|
const branch = await this.remote.getRunTicketBranchName(r.runUuid);
|
|
237
237
|
if (branch) {
|
|
238
238
|
try {
|
|
239
|
-
|
|
239
|
+
// Run-scoped (GAIA-183): close only this done run's own tab
|
|
240
|
+
// (label `#<runId>`), never a sibling/concurrent run's tab.
|
|
241
|
+
await this.executor.cleanupRun(branch, r.runId);
|
|
240
242
|
}
|
|
241
243
|
catch (err) {
|
|
242
244
|
this.logger.warn({ run: r.runUuid, err: String(err) }, 'run tab cleanup failed');
|
|
@@ -368,14 +370,11 @@ export class Conductor {
|
|
|
368
370
|
};
|
|
369
371
|
const env = resolveRunEnv(t.effectiveEnvVars, core, this.logger);
|
|
370
372
|
const ws = await this.workspace.ensure(t.identifier, t.branchName, baseRef);
|
|
371
|
-
// A claimable state has no
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
// engine defaults, and the gap is observable in the conductor log rather
|
|
375
|
-
// than wedging the run. `ws.instructions` IS the loaded WORKFLOW.md (null
|
|
376
|
-
// when absent), so no extra stat.
|
|
373
|
+
// A claimable state has no project policy without a WORKFLOW.md. Warn and
|
|
374
|
+
// proceed so the missing project instructions are observable without
|
|
375
|
+
// wedging the run. `ws.instructions` is the loaded file (null when absent).
|
|
377
376
|
if (!ws.instructions) {
|
|
378
|
-
this.logger.warn({ ticket: t.identifier, workspace: ws.path }, 'WORKFLOW.md missing —
|
|
377
|
+
this.logger.warn({ ticket: t.identifier, workspace: ws.path }, 'WORKFLOW.md missing — no project instructions dispatched');
|
|
379
378
|
}
|
|
380
379
|
// Lifecycle hooks are executor-owned + best-effort (GAIA-84): runHook never
|
|
381
380
|
// throws, so neither call can abort dispatch or wedge a run in `claimed`.
|
|
@@ -386,12 +385,16 @@ export class Conductor {
|
|
|
386
385
|
await this.executor.runHook('after_create', ws.path, { ticket: t.identifier }, env);
|
|
387
386
|
}
|
|
388
387
|
await this.executor.runHook('before_run', ws.path, { ticket: t.identifier }, env);
|
|
389
|
-
// Clear
|
|
390
|
-
|
|
388
|
+
// Clear a leftover tab for THIS run id in a reused workspace before
|
|
389
|
+
// starting it (GAIA-183): run-scoped so a still-open sibling run's tab is
|
|
390
|
+
// never killed. A fresh dispatch has no matching tab yet → a quiet no-op;
|
|
391
|
+
// a re-dispatch of the same run drops its stale duplicate before startRun
|
|
392
|
+
// re-creates it. Sibling prior-state run tabs are left to the worktree reap.
|
|
393
|
+
await this.executor.cleanupRun(t.branchName, run.runId);
|
|
391
394
|
const prompt = ws.instructions
|
|
392
395
|
? renderPrompt(this.config.prompt, {
|
|
393
396
|
identifier: t.identifier,
|
|
394
|
-
state: t.state || '
|
|
397
|
+
state: t.state || 'qualification',
|
|
395
398
|
runUuid: run.runUuid,
|
|
396
399
|
})
|
|
397
400
|
: '';
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export type * from '@gaia-ai/core';
|
|
2
2
|
export { conductorId } from '@gaia-ai/core';
|
|
3
3
|
export { DrupalGaiaRemote, drupalRemote, FakeGaiaRemote, FakeWorkspace, fakeRemote, fakeWorkspace, GitWorkspace, gitWorkspace, selectExecutor, selectRemote, selectWorkspace, } from '@gaia-ai/core/plugins';
|
|
4
|
-
export type {
|
|
5
|
-
export {
|
|
6
|
-
export type
|
|
7
|
-
export {
|
|
4
|
+
export type { ConductorRegistryEntry } from './cli/conductor-registry.js';
|
|
5
|
+
export { renderGaiaConfig } from './cli/init.js';
|
|
6
|
+
export { runConnectionUpgrade, runUpgrade, type UpgradeReport, } from './cli/upgrade.js';
|
|
7
|
+
export { default as conductorCommandPlugin, type GaiaCliDeps, runConductorCli, } from './commands/conductor.js';
|
|
8
|
+
export { composeConductorConfig, DEFAULT_AGENT_PROMPT, loadConductorConfig, } from './config.js';
|
|
8
9
|
export { Conductor } from './core/conductor.js';
|
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
export { conductorId } from '@gaia-ai/core';
|
|
2
2
|
export { DrupalGaiaRemote, drupalRemote, FakeGaiaRemote, FakeWorkspace, fakeRemote, fakeWorkspace, GitWorkspace, gitWorkspace, selectExecutor, selectRemote, selectWorkspace, } from '@gaia-ai/core/plugins';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
// GAIA-201: the connection-config template, reused by `gaia upgrade` to seed a
|
|
4
|
+
// `gaia.config.js`.
|
|
5
|
+
export { renderGaiaConfig } from './cli/init.js';
|
|
6
|
+
// GAIA-216: the seed/migration routine, hoisted from the host so `conductor init`
|
|
7
|
+
// (intra-package) and `gaia upgrade` (host→engine) share one implementation.
|
|
8
|
+
export { runConnectionUpgrade, runUpgrade, } from './cli/upgrade.js';
|
|
9
|
+
// GAIA-201: the conductor is now a COMMAND PLUGIN mounted by the `@gaia-ai/gaia`
|
|
10
|
+
// host, not the CLI entrypoint. `main`/`runGaiaCli` are gone; the default export
|
|
11
|
+
// is the `GaiaCommandPlugin`, exposed here as `./commands/conductor` too.
|
|
12
|
+
export { default as conductorCommandPlugin, runConductorCli, } from './commands/conductor.js';
|
|
13
|
+
export { composeConductorConfig, DEFAULT_AGENT_PROMPT, loadConductorConfig, } from './config.js';
|
|
5
14
|
export { Conductor } from './core/conductor.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaia-ai/conductor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
11
|
".": "./dist/src/index.js",
|
|
12
|
+
"./preset": "./dist/src/preset.js",
|
|
13
|
+
"./commands/conductor": "./dist/src/commands/conductor.js",
|
|
12
14
|
"./package.json": "./package.json"
|
|
13
15
|
},
|
|
14
16
|
"files": [
|
|
@@ -22,14 +24,14 @@
|
|
|
22
24
|
"repository": {
|
|
23
25
|
"type": "git",
|
|
24
26
|
"url": "git+https://git.key-tec.de/keytec/gaia.git",
|
|
25
|
-
"directory": "conductor/
|
|
27
|
+
"directory": "conductor/engine"
|
|
26
28
|
},
|
|
27
29
|
"dependencies": {
|
|
28
|
-
"@gaia-ai/core": "^0.
|
|
30
|
+
"@gaia-ai/core": "^0.6.0",
|
|
29
31
|
"@dropsh/plugin-oauth2": "^0.5.7",
|
|
30
|
-
"@dropsh/plugin-jsonapi-schema": "^0.5.
|
|
32
|
+
"@dropsh/plugin-jsonapi-schema": "^0.5.8",
|
|
31
33
|
"commander": "^12.1.0",
|
|
32
|
-
"dropsh": "^0.5.
|
|
34
|
+
"dropsh": "^0.5.8",
|
|
33
35
|
"semver": "^7.6.0"
|
|
34
36
|
}
|
|
35
37
|
}
|
package/dist/src/cli/gaia.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { type ConductorFileConfig, type ConductorLogger, type GaiaExecutor, type GaiaRemote, type GaiaWorkspace, type ResolvedAgent } from '@gaia-ai/core';
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
/** Test seam: inject any subset of dependencies. */
|
|
4
|
-
export interface GaiaCliDeps {
|
|
5
|
-
remote?: GaiaRemote;
|
|
6
|
-
executor?: GaiaExecutor;
|
|
7
|
-
workspace?: GaiaWorkspace;
|
|
8
|
-
agents?: ResolvedAgent[];
|
|
9
|
-
config?: ConductorFileConfig;
|
|
10
|
-
/** Injectable registry fetch for the start-time version check (tests). */
|
|
11
|
-
fetch?: typeof fetch;
|
|
12
|
-
}
|
|
13
|
-
export declare function parseHerdrJson(output: string, command: string): unknown;
|
|
14
|
-
/**
|
|
15
|
-
* Start-time auth gate. Returns true if authenticated (session or session-less
|
|
16
|
-
* provider); otherwise logs a single clear line and returns false. Must run
|
|
17
|
-
* before remote resolution (resolveRemote), which calls resolveAuth and throws
|
|
18
|
-
* when unauthenticated.
|
|
19
|
-
*/
|
|
20
|
-
export declare function ensureAuthenticated(config: ConductorFileConfig, logger: ConductorLogger): Promise<boolean>;
|
|
21
|
-
export declare function buildProgram(deps: GaiaCliDeps): Command;
|
|
22
|
-
export declare function runGaiaCli(argv: string[], deps?: GaiaCliDeps): Promise<void>;
|
|
23
|
-
export declare function main(argv: string[]): Promise<void>;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export interface ConductorRegistryEntry {
|
|
2
|
-
id: string;
|
|
3
|
-
path: string;
|
|
4
|
-
project: string;
|
|
5
|
-
label: string;
|
|
6
|
-
host: 'herdr' | 'process' | 'systemd';
|
|
7
|
-
handle: string;
|
|
8
|
-
}
|
|
9
|
-
/** `${GAIA_HOME ?? ~}/.gaia/conductors.json`. */
|
|
10
|
-
export declare function registryPath(): string;
|
|
11
|
-
export declare function register(entry: ConductorRegistryEntry): Promise<void>;
|
|
12
|
-
export declare function remove(id: string): Promise<void>;
|
|
13
|
-
export declare function list(): Promise<ConductorRegistryEntry[]>;
|
|
14
|
-
export declare function get(id: string): Promise<ConductorRegistryEntry | null>;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { dirname, join } from 'node:path';
|
|
4
|
-
/** `${GAIA_HOME ?? ~}/.gaia/conductors.json`. */
|
|
5
|
-
export function registryPath() {
|
|
6
|
-
const home = process.env.GAIA_HOME ?? homedir();
|
|
7
|
-
return join(home, '.gaia', 'conductors.json');
|
|
8
|
-
}
|
|
9
|
-
async function read() {
|
|
10
|
-
let raw;
|
|
11
|
-
try {
|
|
12
|
-
raw = await readFile(registryPath(), 'utf8');
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return {};
|
|
16
|
-
}
|
|
17
|
-
if (raw.trim() === '') {
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
try {
|
|
21
|
-
const parsed = JSON.parse(raw);
|
|
22
|
-
if (typeof parsed === 'object' &&
|
|
23
|
-
parsed !== null &&
|
|
24
|
-
!Array.isArray(parsed)) {
|
|
25
|
-
return parsed;
|
|
26
|
-
}
|
|
27
|
-
return {};
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return {};
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
async function write(data) {
|
|
34
|
-
const path = registryPath();
|
|
35
|
-
await mkdir(dirname(path), { recursive: true });
|
|
36
|
-
await writeFile(path, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
37
|
-
}
|
|
38
|
-
export async function register(entry) {
|
|
39
|
-
const data = await read();
|
|
40
|
-
data[entry.id] = entry;
|
|
41
|
-
await write(data);
|
|
42
|
-
}
|
|
43
|
-
export async function remove(id) {
|
|
44
|
-
const data = await read();
|
|
45
|
-
if (data[id]) {
|
|
46
|
-
delete data[id];
|
|
47
|
-
await write(data);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
export async function list() {
|
|
51
|
-
return Object.values(await read());
|
|
52
|
-
}
|
|
53
|
-
export async function get(id) {
|
|
54
|
-
const data = await read();
|
|
55
|
-
return data[id] ?? null;
|
|
56
|
-
}
|