@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
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { createInterface } from 'node:readline';
|
|
4
|
-
import { CommandRunner, createLogger, exec, loadGaiaConfig, machineContextPath, readMachineContext, resolveConfigPath, setDefaultCommandRunner, } from '@gaia-ai/core';
|
|
5
|
-
import { selectAgents, selectExecutor, selectRemote, selectWorkspace, } from '@gaia-ai/core/plugins';
|
|
4
|
+
import { CommandRunner, createLogger, exec, getRegisteredConductor, listRegisteredConductors, loadGaiaConfig, machineContextPath, readMachineContext, registerConductor, removeConductor, resolveConfigPath, setDefaultCommandRunner, } from '@gaia-ai/core';
|
|
6
5
|
import { Command } from 'commander';
|
|
7
6
|
import { authStatus } from 'dropsh';
|
|
8
|
-
import
|
|
9
|
-
import { registerDeployment } from '../cli/deployment.js';
|
|
10
|
-
import { scaffold, scaffoldGaiaConfig } from '../cli/init.js';
|
|
7
|
+
import { scaffold } from '../cli/init.js';
|
|
11
8
|
import { runUpgrade } from '../cli/upgrade.js';
|
|
12
9
|
import { fetchUpdateNotice, printVersionLine } from '../cli/version-check.js';
|
|
13
10
|
import { composeConductorConfig, loadConductorConfig } from '../config.js';
|
|
14
11
|
import { Conductor } from '../core/conductor.js';
|
|
12
|
+
import { selectAgents, selectExecutor, selectRemote, selectWorkspace, } from '../plugins/plugins.js';
|
|
15
13
|
/** Default host bases for connection/plugin resolution when none is injected. */
|
|
16
14
|
function defaultHost() {
|
|
17
15
|
return { resolveBases: [import.meta.url, `${process.cwd()}/`] };
|
|
@@ -64,7 +62,7 @@ function loggerFor(checkoutRoot, log = {}) {
|
|
|
64
62
|
setDefaultCommandRunner(new CommandRunner(logger));
|
|
65
63
|
return logger;
|
|
66
64
|
}
|
|
67
|
-
// --- herdr host (
|
|
65
|
+
// --- herdr host (shells out to herdr; injectable via deps.herdr) -----------
|
|
68
66
|
export function parseHerdrJson(output, command) {
|
|
69
67
|
try {
|
|
70
68
|
return JSON.parse(output);
|
|
@@ -100,6 +98,14 @@ async function killViaHerdr(label) {
|
|
|
100
98
|
await exec('herdr', ['workspace', 'close', ws.workspace_id]);
|
|
101
99
|
}
|
|
102
100
|
}
|
|
101
|
+
/** The production host: the real `herdr` binary. */
|
|
102
|
+
export const realHerdrHost = {
|
|
103
|
+
spawn: spawnViaHerdr,
|
|
104
|
+
kill: killViaHerdr,
|
|
105
|
+
};
|
|
106
|
+
function herdrHostOf(deps) {
|
|
107
|
+
return deps.herdr ?? realHerdrHost;
|
|
108
|
+
}
|
|
103
109
|
// --- ls/status freshness ----------------------------------------------------
|
|
104
110
|
const DEFAULT_FRESH_S = 120;
|
|
105
111
|
function freshnessThresholdS(config) {
|
|
@@ -119,7 +125,7 @@ function classify(hub, freshS) {
|
|
|
119
125
|
return fresh ? 'running' : 'wedged';
|
|
120
126
|
}
|
|
121
127
|
async function buildLsRows(remote, config, onlyId) {
|
|
122
|
-
const entries = await
|
|
128
|
+
const entries = await listRegisteredConductors();
|
|
123
129
|
let hub = [];
|
|
124
130
|
try {
|
|
125
131
|
hub = await remote.listConductors('me');
|
|
@@ -249,7 +255,7 @@ async function cmdStartBody(deps, log) {
|
|
|
249
255
|
return;
|
|
250
256
|
const remote = await resolveRemote(deps, config);
|
|
251
257
|
const id = conductorIdOf(config);
|
|
252
|
-
const existing = await
|
|
258
|
+
const existing = await getRegisteredConductor(id);
|
|
253
259
|
if (existing) {
|
|
254
260
|
const hubStatus = await remote.getConductorStatus(id);
|
|
255
261
|
if (hubStatus !== null && hubStatus !== 'offline') {
|
|
@@ -258,7 +264,7 @@ async function cmdStartBody(deps, log) {
|
|
|
258
264
|
}
|
|
259
265
|
}
|
|
260
266
|
const handle = `gaia-conductor:${id}`;
|
|
261
|
-
await
|
|
267
|
+
await registerConductor({
|
|
262
268
|
id,
|
|
263
269
|
path: checkoutRoot,
|
|
264
270
|
project: config.project,
|
|
@@ -274,7 +280,7 @@ async function cmdStartBody(deps, log) {
|
|
|
274
280
|
.join(' ');
|
|
275
281
|
const fgCmd = `GAIA_CONDUCTOR_LOG=file gaia conductor ${fgFlags} start --foreground`.replace(/\s+/g, ' ');
|
|
276
282
|
try {
|
|
277
|
-
await
|
|
283
|
+
await herdrHostOf(deps).spawn(handle, checkoutRoot, fgCmd);
|
|
278
284
|
logger.info({ id, handle }, 'started conductor via herdr');
|
|
279
285
|
}
|
|
280
286
|
catch (err) {
|
|
@@ -287,9 +293,9 @@ async function cmdStop(deps, now) {
|
|
|
287
293
|
const remote = await resolveRemote(deps, config);
|
|
288
294
|
const id = conductorIdOf(config);
|
|
289
295
|
if (now) {
|
|
290
|
-
const entry = await
|
|
296
|
+
const entry = await getRegisteredConductor(id);
|
|
291
297
|
if (entry && entry.host === 'herdr') {
|
|
292
|
-
await
|
|
298
|
+
await herdrHostOf(deps).kill(entry.handle);
|
|
293
299
|
console.log(`hard-stopped conductor ${id} (${entry.handle})`);
|
|
294
300
|
}
|
|
295
301
|
else {
|
|
@@ -297,9 +303,9 @@ async function cmdStop(deps, now) {
|
|
|
297
303
|
}
|
|
298
304
|
return;
|
|
299
305
|
}
|
|
300
|
-
const entry = await
|
|
306
|
+
const entry = await getRegisteredConductor(id);
|
|
301
307
|
if (entry && entry.host === 'herdr') {
|
|
302
|
-
await
|
|
308
|
+
await herdrHostOf(deps).kill(entry.handle);
|
|
303
309
|
}
|
|
304
310
|
await remote.setConductorStatus(id, 'offline');
|
|
305
311
|
console.log(`stopped conductor ${id} (offline)`);
|
|
@@ -320,7 +326,7 @@ async function cmdStatus(deps) {
|
|
|
320
326
|
async function cmdRm(deps) {
|
|
321
327
|
const config = await resolveConfig(deps);
|
|
322
328
|
const id = conductorIdOf(config);
|
|
323
|
-
await
|
|
329
|
+
await removeConductor(id);
|
|
324
330
|
console.log(`removed conductor ${id} from registry`);
|
|
325
331
|
}
|
|
326
332
|
/** ls may run without a config file; best-effort load. */
|
|
@@ -516,15 +522,11 @@ function registerInit(conductor) {
|
|
|
516
522
|
skipMachine: !onboarding,
|
|
517
523
|
skipCommitted: !hasProject,
|
|
518
524
|
});
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const conn = scaffoldGaiaConfig(opts.config, opts.force);
|
|
525
|
-
wroteConnection = conn.wrote;
|
|
526
|
-
connectionPath = conn.path;
|
|
527
|
-
}
|
|
525
|
+
// GAIA-218: `conductor init` writes the ENGINE config only. It no longer
|
|
526
|
+
// scaffolds a project-local `gaia.config.js` — the global
|
|
527
|
+
// `~/.gaia/gaia.config.js` (seeded by the reachability step below) is the
|
|
528
|
+
// default connection every repo inherits; a project override is opt-in
|
|
529
|
+
// via `gaia upgrade`.
|
|
528
530
|
if (!onboarding) {
|
|
529
531
|
console.log(`machine context found (${machinePath}) → setting up project only`);
|
|
530
532
|
if (typeof existing.base_url !== 'string' ||
|
|
@@ -545,20 +547,19 @@ function registerInit(conductor) {
|
|
|
545
547
|
console.log(res.wroteCommitted
|
|
546
548
|
? `wrote ${res.committedPath} (engine)`
|
|
547
549
|
: `kept ${res.committedPath} (exists — pass --force to replace)`);
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
: `kept ${connectionPath} (exists — pass --force to replace)`);
|
|
551
|
-
// GAIA-216: make the split-config seed reachable from `init` itself —
|
|
552
|
-
// migrate a legacy machine context and seed the home connection config
|
|
550
|
+
// GAIA-216/218: make the split-config seed reachable from `init` itself
|
|
551
|
+
// — migrate a legacy machine context and seed the HOME connection config
|
|
553
552
|
// so an install can never be left with an engine config and no
|
|
554
|
-
// connection config.
|
|
555
|
-
//
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
//
|
|
553
|
+
// connection config. `projectConnection: 'skip'` keeps `init` from
|
|
554
|
+
// writing a project override (the global connection is the default;
|
|
555
|
+
// overrides are opt-in via `gaia upgrade`). Idempotent: a fresh init
|
|
556
|
+
// reports these as kept. `home` is derived from the machine-context path
|
|
557
|
+
// (canonical `<home>/.gaia/machine.config.js`) so the home-side steps
|
|
558
|
+
// target the same home the context lives in.
|
|
559
559
|
const reach = runUpgrade({
|
|
560
560
|
cwd: dirname(dirname(opts.config)),
|
|
561
561
|
home: dirname(dirname(machinePath)),
|
|
562
|
+
projectConnection: 'skip',
|
|
562
563
|
});
|
|
563
564
|
for (const line of reach.actions)
|
|
564
565
|
console.log(line);
|
|
@@ -577,22 +578,15 @@ function registerInit(conductor) {
|
|
|
577
578
|
' gaia dropsh auth status # both profiles present');
|
|
578
579
|
});
|
|
579
580
|
}
|
|
580
|
-
/** The `conductor` command plugin the host mounts (GAIA-201).
|
|
581
|
-
* the `gaia deployment` batch helper
|
|
581
|
+
/** The `conductor` command plugin the host mounts (GAIA-201). GAIA-224
|
|
582
|
+
* (Finding 7): the `gaia deployment` batch helper is no longer registered here —
|
|
583
|
+
* it is its own command addon, `@gaia-ai/addon-deployment`. */
|
|
582
584
|
const conductorCommandPlugin = {
|
|
583
585
|
kind: 'command',
|
|
584
586
|
name: 'conductor',
|
|
585
587
|
describe: 'node-agent lifecycle + local registry',
|
|
586
588
|
register(program, host) {
|
|
587
|
-
|
|
588
|
-
program.addCommand(createConductorCommand(deps));
|
|
589
|
-
registerDeployment(program, async () => {
|
|
590
|
-
const config = await resolveConfig(deps);
|
|
591
|
-
return resolveRemote(deps, config);
|
|
592
|
-
}, async () => {
|
|
593
|
-
const config = await resolveConfig(deps);
|
|
594
|
-
return config.project;
|
|
595
|
-
}, () => process.cwd());
|
|
589
|
+
program.addCommand(createConductorCommand({ host }));
|
|
596
590
|
},
|
|
597
591
|
};
|
|
598
592
|
export default conductorCommandPlugin;
|
|
@@ -604,13 +598,6 @@ export function buildConductorProgram(deps = {}) {
|
|
|
604
598
|
const program = new Command();
|
|
605
599
|
program.name('gaia').description('GAIA conductor + client CLI');
|
|
606
600
|
program.addCommand(createConductorCommand(deps));
|
|
607
|
-
registerDeployment(program, async () => {
|
|
608
|
-
const config = await resolveConfig(deps);
|
|
609
|
-
return resolveRemote(deps, config);
|
|
610
|
-
}, async () => {
|
|
611
|
-
const config = await resolveConfig(deps);
|
|
612
|
-
return config.project;
|
|
613
|
-
}, () => process.cwd());
|
|
614
601
|
return program;
|
|
615
602
|
}
|
|
616
603
|
/** Parse argv against the conductor program (test entry). */
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type GaiaConnectionConfig } from '@gaia-ai/core';
|
|
2
|
+
import type { AgentCandidate } from './plugins/plugins.js';
|
|
3
|
+
import type { ConductorEngineConfig, ConductorFileConfig } from './types.js';
|
|
2
4
|
export { findGaiaDir, resolveConfigPath, } from '@gaia-ai/core';
|
|
3
5
|
/**
|
|
4
6
|
* Default agent prompt - the project instruction contract. One run works EXACTLY one state;
|
|
@@ -87,3 +89,15 @@ export declare function listConductorConfigFiles(gaiaDir: string): string[];
|
|
|
87
89
|
* `substates:`/other key containing "states" is not matched.
|
|
88
90
|
*/
|
|
89
91
|
export declare function stripStatesFromConfigSource(source: string): string;
|
|
92
|
+
/**
|
|
93
|
+
* Strip the `site:` and `plugins:` properties from an engine config's
|
|
94
|
+
* `export default { … }` (with their leading comment/whitespace), plus the
|
|
95
|
+
* now-orphaned connection preamble consts. Returns the rewritten source and the
|
|
96
|
+
* list of object keys removed (`[]` — source returned unchanged — when there is
|
|
97
|
+
* nothing connection-related to remove). remote/executor/agent/workspace/hooks/
|
|
98
|
+
* project/machine_id/loadLocal are left intact.
|
|
99
|
+
*/
|
|
100
|
+
export declare function stripLegacyConnectionFromConfigSource(source: string): {
|
|
101
|
+
text: string;
|
|
102
|
+
removed: string[];
|
|
103
|
+
};
|
package/dist/src/config.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readdirSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
-
import { discoverAddons, emptyContributions, resolveConductorSlots, resolveModuleEslintStyle, } from '@gaia-ai/core';
|
|
4
|
+
import { discoverAddons, emptyContributions, findDefaultExportObject, matchDelimiter, resolveConductorSlots, resolveModuleEslintStyle, scanTopLevelProperties, } from '@gaia-ai/core';
|
|
5
|
+
import { narrowConductorContributions } from './plugins/preset.js';
|
|
5
6
|
// GAIA-201: the `.gaia/` walk-up + connection resolution moved to `@gaia-ai/core`
|
|
6
7
|
// (`resolveConfigPath` / `resolveGaiaConfigPath` / `findGaiaDir`). Re-export the
|
|
7
8
|
// engine resolver here so existing conductor callers/tests keep importing it
|
|
@@ -40,7 +41,7 @@ export { findGaiaDir, resolveConfigPath, } from '@gaia-ai/core';
|
|
|
40
41
|
* `qualification` for unclassified tickets).
|
|
41
42
|
*/
|
|
42
43
|
export const DEFAULT_AGENT_PROMPT = `You are working ticket {identifier}, current state: {state}. Read the ticket and all comments ` +
|
|
43
|
-
`(gaia dropsh read gaia_ticket/gaia_ticket/$GAIA_ID --include comments), ` +
|
|
44
|
+
`(gaia dropsh --format md read gaia_ticket/gaia_ticket/$GAIA_ID --include comments), ` +
|
|
44
45
|
`then read ./WORKFLOW.md and execute only its ordered "State: {state}" section. ` +
|
|
45
46
|
`Do not start or prepare a later state.`;
|
|
46
47
|
function requirePlugin(value, kind) {
|
|
@@ -169,6 +170,15 @@ export async function loadConductorConfig(configFile) {
|
|
|
169
170
|
if (!isRecord(raw)) {
|
|
170
171
|
throw new Error('conductor config default export must be an object');
|
|
171
172
|
}
|
|
173
|
+
// GAIA-218 (AC-6): the engine config must not carry connection/auth material.
|
|
174
|
+
// A legacy config still declaring `site`/`plugins` gets exactly ONE actionable
|
|
175
|
+
// warning naming the file + the offending key(s); the values are never merged
|
|
176
|
+
// (`ConductorEngineConfig` omits both structurally).
|
|
177
|
+
const leftover = ['site', 'plugins'].filter((k) => k in raw);
|
|
178
|
+
if (leftover.length > 0) {
|
|
179
|
+
console.warn(`warning: ${configPath} declares ${leftover.join('/')} — ignored; ` +
|
|
180
|
+
'auth/connection belong in the sibling gaia.config.js (GAIA-218)');
|
|
181
|
+
}
|
|
172
182
|
const config = raw;
|
|
173
183
|
const project = requireNonEmptyString(config.project, 'project');
|
|
174
184
|
// `states` is optional. An empty (or absent) list means the conductor serves
|
|
@@ -192,13 +202,18 @@ export async function loadConductorConfig(configFile) {
|
|
|
192
202
|
// GAIA-215: discover the conductor `addons: []` surface (last-wins singletons,
|
|
193
203
|
// agent candidate list). A pre-existing per-slot descriptor still loads and
|
|
194
204
|
// WINS over a discovered contributor of the same kind (back-compat additive).
|
|
195
|
-
|
|
205
|
+
// GAIA-224: `discoverAddons` is surface-AGNOSTIC — it returns opaque
|
|
206
|
+
// accumulators. This is the single, reviewed narrowing seam where the engine
|
|
207
|
+
// reinterprets them as its own concrete conductor contributions; the runtime
|
|
208
|
+
// `.kind` guard inside `resolveConductorSlots` still fails loudly on a
|
|
209
|
+
// mis-declared contribution.
|
|
210
|
+
const discovered = narrowConductorContributions(Array.isArray(config.addons)
|
|
196
211
|
? await discoverAddons(config.addons, 'conductor', [
|
|
197
212
|
pathToFileURL(configPath).href,
|
|
198
213
|
pathToFileURL(`${process.cwd()}/`).href,
|
|
199
214
|
import.meta.url,
|
|
200
215
|
])
|
|
201
|
-
: emptyContributions();
|
|
216
|
+
: emptyContributions());
|
|
202
217
|
const wired = resolveConductorSlots(discovered);
|
|
203
218
|
const remote = config.remote !== undefined
|
|
204
219
|
? await resolveSlot(config.remote, 'remote', configPath)
|
|
@@ -301,3 +316,103 @@ export function listConductorConfigFiles(gaiaDir) {
|
|
|
301
316
|
export function stripStatesFromConfigSource(source) {
|
|
302
317
|
return source.replace(/\n?[ \t]*(?<![A-Za-z0-9_$])states[ \t]*:[ \t]*\[[^\]]*\][ \t]*,?/g, '');
|
|
303
318
|
}
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
// GAIA-218 (AC-7): strip legacy connection/auth from an ENGINE config source.
|
|
321
|
+
//
|
|
322
|
+
// Unlike `stripStatesFromConfigSource`'s flat `\[[^\]]*\]` regex (nested-unsafe),
|
|
323
|
+
// this is a balanced-delimiter, comment-aware source transform. The engine
|
|
324
|
+
// config's `plugins: [ … ]` array holds nested `{}` (oauth2 profiles AND
|
|
325
|
+
// `@gaia-ai/addon-essentials`), a sibling `agent: [ … ]` array, and the word
|
|
326
|
+
// "plugins" also appears in comments — so the removal keys on the REAL `site:` /
|
|
327
|
+
// `plugins:` *properties* of the default-export object, scanning each value from
|
|
328
|
+
// its opening delimiter to the matching close while skipping string/comment
|
|
329
|
+
// bytes. It also removes the now-orphaned connection preamble consts
|
|
330
|
+
// (`baseUrl`/`clientId`/`clientSecret`) and a connection-only `loadMachine`
|
|
331
|
+
// (with its `const machine = await loadMachine()`) when they become unreferenced.
|
|
332
|
+
// ---------------------------------------------------------------------------
|
|
333
|
+
// GAIA-230: the scanner primitives themselves now live in `@gaia-ai/core`
|
|
334
|
+
// (`core/src/cli/config-source.ts`) — the kernel needs them for the content-gated
|
|
335
|
+
// legacy connection read, and layer 0 cannot import this package. Imported above.
|
|
336
|
+
/** Count word-boundary occurrences of an identifier in the source. */
|
|
337
|
+
function countIdent(src, name) {
|
|
338
|
+
const re = new RegExp(`(?<![A-Za-z0-9_$])${name}(?![A-Za-z0-9_$])`, 'g');
|
|
339
|
+
return (src.match(re) ?? []).length;
|
|
340
|
+
}
|
|
341
|
+
/** Remove a single-line `const <name> = …;` declaration (no-op if absent). */
|
|
342
|
+
function removeConstDecl(src, name) {
|
|
343
|
+
const re = new RegExp(`^[ \\t]*const ${name}\\b[^\\n]*\\n`, 'm');
|
|
344
|
+
return src.replace(re, '');
|
|
345
|
+
}
|
|
346
|
+
/** Remove an `[async ]function <name>(…) { … }` declaration (balanced body). */
|
|
347
|
+
function removeFunctionDecl(src, name) {
|
|
348
|
+
const re = new RegExp(`(?:async[ \\t]+)?function[ \\t]+${name}[ \\t]*\\(`);
|
|
349
|
+
const m = re.exec(src);
|
|
350
|
+
if (!m)
|
|
351
|
+
return src;
|
|
352
|
+
const braceIdx = src.indexOf('{', m.index);
|
|
353
|
+
if (braceIdx === -1)
|
|
354
|
+
return src;
|
|
355
|
+
const end = matchDelimiter(src, braceIdx);
|
|
356
|
+
if (end === -1)
|
|
357
|
+
return src;
|
|
358
|
+
let e = end + 1;
|
|
359
|
+
if (src[e] === '\n')
|
|
360
|
+
e++;
|
|
361
|
+
return src.slice(0, m.index) + src.slice(e);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Is `machine` referenced anywhere OTHER than its own `const machine = …;` decl
|
|
365
|
+
* and inside the `loadMachine` function body? (The body's `…/machine.config.js`
|
|
366
|
+
* path string must not count as a live reference.) Used to decide whether a
|
|
367
|
+
* connection-only `loadMachine` has become orphaned.
|
|
368
|
+
*/
|
|
369
|
+
function machineReferencedElsewhere(src) {
|
|
370
|
+
let probe = removeFunctionDecl(src, 'loadMachine');
|
|
371
|
+
probe = removeConstDecl(probe, 'machine');
|
|
372
|
+
return countIdent(probe, 'machine') > 0;
|
|
373
|
+
}
|
|
374
|
+
/** Drop connection preamble that is orphaned once `site`/`plugins` are gone. */
|
|
375
|
+
function removeOrphanedPreamble(src) {
|
|
376
|
+
let text = src;
|
|
377
|
+
for (const name of ['baseUrl', 'clientId', 'clientSecret']) {
|
|
378
|
+
if (countIdent(text, name) <= 1)
|
|
379
|
+
text = removeConstDecl(text, name);
|
|
380
|
+
}
|
|
381
|
+
// A connection-only `loadMachine`: remove `const machine = await loadMachine()`
|
|
382
|
+
// + the function itself only when `machine` is otherwise unreferenced (so an
|
|
383
|
+
// engine config that still composes `machine_id` from `machine.user_id` keeps
|
|
384
|
+
// its loadMachine — the "connection-only" qualifier).
|
|
385
|
+
if (/\bconst machine\b/.test(text) &&
|
|
386
|
+
/\bfunction loadMachine\b/.test(text) &&
|
|
387
|
+
!machineReferencedElsewhere(text)) {
|
|
388
|
+
text = removeConstDecl(text, 'machine');
|
|
389
|
+
text = removeFunctionDecl(text, 'loadMachine');
|
|
390
|
+
}
|
|
391
|
+
return text;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Strip the `site:` and `plugins:` properties from an engine config's
|
|
395
|
+
* `export default { … }` (with their leading comment/whitespace), plus the
|
|
396
|
+
* now-orphaned connection preamble consts. Returns the rewritten source and the
|
|
397
|
+
* list of object keys removed (`[]` — source returned unchanged — when there is
|
|
398
|
+
* nothing connection-related to remove). remote/executor/agent/workspace/hooks/
|
|
399
|
+
* project/machine_id/loadLocal are left intact.
|
|
400
|
+
*/
|
|
401
|
+
export function stripLegacyConnectionFromConfigSource(source) {
|
|
402
|
+
const obj = findDefaultExportObject(source);
|
|
403
|
+
if (!obj)
|
|
404
|
+
return { text: source, removed: [] };
|
|
405
|
+
const entries = scanTopLevelProperties(source, obj.bodyStart, obj.bodyEnd);
|
|
406
|
+
const targets = new Set(['site', 'plugins']);
|
|
407
|
+
const toRemove = entries.filter((e) => e.key !== undefined && targets.has(e.key));
|
|
408
|
+
if (toRemove.length === 0)
|
|
409
|
+
return { text: source, removed: [] };
|
|
410
|
+
let text = source;
|
|
411
|
+
for (const e of [...toRemove].sort((a, b) => b.start - a.start)) {
|
|
412
|
+
text = text.slice(0, e.start) + text.slice(e.end);
|
|
413
|
+
}
|
|
414
|
+
text = removeOrphanedPreamble(text);
|
|
415
|
+
// Report in source order for a stable `site/plugins` message.
|
|
416
|
+
const removed = ['site', 'plugins'].filter((k) => toRemove.some((e) => e.key === k));
|
|
417
|
+
return { text, removed };
|
|
418
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { type AgentFootprint, emptyAgentFootprint, type GaiaAgent, } from './plugins/agent.js';
|
|
2
|
+
export type { ExecutorCapabilities, GaiaExecutor, HookContext, HookName, SpawnedSession, SpawnRunInput, } from './plugins/executor.js';
|
|
3
|
+
export type { AgentCandidate, AgentPlugin, ExecutorDeps, ExecutorPlugin, RemotePlugin, ResolvedAgent, WorkspacePlugin, } from './plugins/plugins.js';
|
|
4
|
+
export { selectAgent, selectAgents, selectExecutor, selectRemote, selectWorkspace, } from './plugins/plugins.js';
|
|
5
|
+
export { type ConductorAddonEntry, type ConductorContributions, narrowConductorContributions, type Preset, } from './plugins/preset.js';
|
|
6
|
+
export type { ActiveRun, ClaimedRun, ClaimOptions, ConductorRegistration, ConductorStatus, FinalizableRun, GaiaRemote, RunMetrics, RunWriteAttributes, Ticket, UncleanTicket, } from './plugins/remote.js';
|
|
7
|
+
export type { EnsuredWorkspace, GaiaWorkspace, } from './plugins/workspace.js';
|
|
8
|
+
export type { ConductorEngineConfig, ConductorFileConfig, ConductorSettings, } from './types.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// GAIA-224 (Finding 6): the CONDUCTOR-SURFACE CONTRACT, exposed as the
|
|
2
|
+
// runtime-light subpath `@gaia-ai/conductor/contract`.
|
|
3
|
+
//
|
|
4
|
+
// This is the module a conductor-surface addon (`addons/remote-drupal`,
|
|
5
|
+
// `addons/workspace-git`, `addons/herdr`, `addons/claude`, …) imports. It carries
|
|
6
|
+
// ONLY the surface interfaces, the config contract, the preset view, and the two
|
|
7
|
+
// tiny runtime helpers (`emptyAgentFootprint`, the `select*` slot selectors) — it
|
|
8
|
+
// imports NO engine, NO commander, NO dropsh runtime. Importing it therefore
|
|
9
|
+
// costs an addon nothing at boot, unlike the package main (`@gaia-ai/conductor`),
|
|
10
|
+
// which pulls the whole run engine + the `conductor` command plugin.
|
|
11
|
+
//
|
|
12
|
+
// The package main re-exports everything here too, so `import type { GaiaRemote }
|
|
13
|
+
// from '@gaia-ai/conductor'` keeps working for type-only consumers.
|
|
14
|
+
export { emptyAgentFootprint, } from './plugins/agent.js';
|
|
15
|
+
export { selectAgent, selectAgents, selectExecutor, selectRemote, selectWorkspace, } from './plugins/plugins.js';
|
|
16
|
+
export { narrowConductorContributions, } from './plugins/preset.js';
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ConductorLogger } from '@gaia-ai/core';
|
|
2
|
+
import type { GaiaExecutor } from '../plugins/executor.js';
|
|
3
|
+
import { type ResolvedAgent } from '../plugins/plugins.js';
|
|
4
|
+
import type { GaiaRemote } from '../plugins/remote.js';
|
|
5
|
+
import type { GaiaWorkspace } from '../plugins/workspace.js';
|
|
6
|
+
import type { ConductorFileConfig } from '../types.js';
|
|
2
7
|
/**
|
|
3
8
|
* Resolve the environment a run executes in (GAIA-99): parse the ticket's
|
|
4
9
|
* effective env_vars, drop any reserved key (loud warn — key NAME only, never
|
|
@@ -66,5 +71,15 @@ export declare class Conductor {
|
|
|
66
71
|
reap(): Promise<void>;
|
|
67
72
|
private dispatch;
|
|
68
73
|
serve(signal?: AbortSignal): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* True once this conductor's own checkout has been removed from disk (its
|
|
76
|
+
* worktree was reaped while the process kept running). Such a conductor is a
|
|
77
|
+
* pure liability: it still heartbeats and still claims runs, but every
|
|
78
|
+
* dispatch fails — no git command and no relative path can resolve from a
|
|
79
|
+
* deleted directory — so each claim burns one attempt and three of them park
|
|
80
|
+
* the ticket behind the circuit breaker. Observed as 30 orphans claiming and
|
|
81
|
+
* failing tickets they could never dispatch.
|
|
82
|
+
*/
|
|
83
|
+
private checkoutGone;
|
|
69
84
|
private pollLoop;
|
|
70
85
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { conductorId } from '@gaia-ai/core';
|
|
3
|
+
import { selectAgent } from '../plugins/plugins.js';
|
|
2
4
|
function sleep(ms, signal) {
|
|
3
5
|
return new Promise((resolve) => {
|
|
4
6
|
if (signal?.aborted) {
|
|
@@ -433,8 +435,28 @@ export class Conductor {
|
|
|
433
435
|
async serve(signal) {
|
|
434
436
|
await this.pollLoop(signal);
|
|
435
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* True once this conductor's own checkout has been removed from disk (its
|
|
440
|
+
* worktree was reaped while the process kept running). Such a conductor is a
|
|
441
|
+
* pure liability: it still heartbeats and still claims runs, but every
|
|
442
|
+
* dispatch fails — no git command and no relative path can resolve from a
|
|
443
|
+
* deleted directory — so each claim burns one attempt and three of them park
|
|
444
|
+
* the ticket behind the circuit breaker. Observed as 30 orphans claiming and
|
|
445
|
+
* failing tickets they could never dispatch.
|
|
446
|
+
*/
|
|
447
|
+
checkoutGone() {
|
|
448
|
+
return !existsSync(this.checkoutRoot);
|
|
449
|
+
}
|
|
436
450
|
async pollLoop(signal) {
|
|
437
451
|
while (!signal?.aborted) {
|
|
452
|
+
if (this.checkoutGone()) {
|
|
453
|
+
// Stop claiming and let the loop end — the process exits and the cron
|
|
454
|
+
// reaper flips status to offline on lease expiry, same as any crash.
|
|
455
|
+
// Deliberately NOT an offline write from here: the loop never owns that
|
|
456
|
+
// transition (see the note at the end of this method).
|
|
457
|
+
this.logger.error({ conductorId: this.id, workspace: this.checkoutRoot }, 'checkout is gone — stopping conductor instead of claiming runs it cannot dispatch');
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
438
460
|
try {
|
|
439
461
|
await this.tick();
|
|
440
462
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export type * from '@gaia-ai/core';
|
|
2
|
+
export type { ConductorRegistryEntry } from '@gaia-ai/core';
|
|
2
3
|
export { conductorId } from '@gaia-ai/core';
|
|
3
|
-
export {
|
|
4
|
-
export type { ConductorRegistryEntry } from './cli/conductor-registry.js';
|
|
4
|
+
export { GAIA_CONFIG_SCHEMA_VERSION, readConfigSchemaVersion, } from './cli/config-schema.js';
|
|
5
5
|
export { renderGaiaConfig } from './cli/init.js';
|
|
6
|
-
export {
|
|
6
|
+
export { type AddonRenameResult, type ConfigSurface, HOST_BARREL_PATHS, migrateAddonNames, migrateAddonNamesInFile, RENAMED_ADDONS, runAddonRenameMigration, } from './cli/migrate-addon-names.js';
|
|
7
|
+
export { hasProjectConnection, type ProjectConnectionChoice, runConnectionUpgrade, runUpgrade, type UpgradeReport, } from './cli/upgrade.js';
|
|
7
8
|
export { default as conductorCommandPlugin, type GaiaCliDeps, runConductorCli, } from './commands/conductor.js';
|
|
8
|
-
export { composeConductorConfig, DEFAULT_AGENT_PROMPT, loadConductorConfig, } from './config.js';
|
|
9
|
+
export { composeConductorConfig, DEFAULT_AGENT_PROMPT, loadConductorConfig, stripLegacyConnectionFromConfigSource, } from './config.js';
|
|
10
|
+
export * from './contract.js';
|
|
9
11
|
export { Conductor } from './core/conductor.js';
|
package/dist/src/index.js
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
export { conductorId } from '@gaia-ai/core';
|
|
2
|
-
|
|
2
|
+
// GAIA-216: the connection-config schema version + its regex reader. GAIA-230:
|
|
3
|
+
// the host's `gaia upgrade` intro prints the `from → to` pair from these.
|
|
4
|
+
export { GAIA_CONFIG_SCHEMA_VERSION, readConfigSchemaVersion, } from './cli/config-schema.js';
|
|
3
5
|
// GAIA-201: the connection-config template, reused by `gaia upgrade` to seed a
|
|
4
6
|
// `gaia.config.js`.
|
|
5
7
|
export { renderGaiaConfig } from './cli/init.js';
|
|
8
|
+
// GAIA-224 (Finding 8b): the addon package-name rename pass `gaia upgrade` runs
|
|
9
|
+
// as its last step (`@gaia-ai/plugin-*` + the deleted `@gaia-ai/core/builtins`
|
|
10
|
+
// and `@gaia-ai/core/plugins` → `@gaia-ai/addon-*`).
|
|
11
|
+
export { HOST_BARREL_PATHS, migrateAddonNames, migrateAddonNamesInFile, RENAMED_ADDONS, runAddonRenameMigration, } from './cli/migrate-addon-names.js';
|
|
6
12
|
// GAIA-216: the seed/migration routine, hoisted from the host so `conductor init`
|
|
7
13
|
// (intra-package) and `gaia upgrade` (host→engine) share one implementation.
|
|
8
|
-
export { runConnectionUpgrade, runUpgrade, } from './cli/upgrade.js';
|
|
14
|
+
export { hasProjectConnection, runConnectionUpgrade, runUpgrade, } from './cli/upgrade.js';
|
|
9
15
|
// GAIA-201: the conductor is now a COMMAND PLUGIN mounted by the `@gaia-ai/gaia`
|
|
10
16
|
// host, not the CLI entrypoint. `main`/`runGaiaCli` are gone; the default export
|
|
11
17
|
// is the `GaiaCommandPlugin`, exposed here as `./commands/conductor` too.
|
|
12
18
|
export { default as conductorCommandPlugin, runConductorCli, } from './commands/conductor.js';
|
|
13
|
-
|
|
19
|
+
// GAIA-218: the balanced strip is exported for the host/tests + reuse.
|
|
20
|
+
export { composeConductorConfig, DEFAULT_AGENT_PROMPT, loadConductorConfig, stripLegacyConnectionFromConfigSource, } from './config.js';
|
|
21
|
+
// GAIA-224 (Finding 6): this package now OWNS the conductor-surface contract —
|
|
22
|
+
// the interfaces, the config types, the preset view and the slot selectors — and
|
|
23
|
+
// re-exports the whole of it here. The runtime-light subpath
|
|
24
|
+
// `@gaia-ai/conductor/contract` is the one an ADDON should import (this main
|
|
25
|
+
// entry pulls the engine + the command plugin). The built-in IMPLEMENTATIONS it
|
|
26
|
+
// used to re-export from the deleted `@gaia-ai/core/plugins` barrel now live in
|
|
27
|
+
// their own addons (`@gaia-ai/addon-remote-drupal`,
|
|
28
|
+
// `@gaia-ai/addon-workspace-git`, `@gaia-ai/addon-fake`) and are deliberately NOT
|
|
29
|
+
// re-exported: the engine must not edge an addon (acyclic layer rule).
|
|
30
|
+
export * from './contract.js';
|
|
14
31
|
export { Conductor } from './core/conductor.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-agent effort footprint parsed from that agent's run transcript
|
|
3
|
+
* (GAIA-132). The metrics are transcript-derived and therefore
|
|
4
|
+
* **agent-specific** (each agent's transcript has its own format), so parsing
|
|
5
|
+
* lives behind the agent abstraction (see {@link GaiaAgent.parseFootprint}) —
|
|
6
|
+
* the conductor stays agent-agnostic and writes the footprint verbatim.
|
|
7
|
+
* `duration_s` is likewise derived from the transcript (its first→last entry
|
|
8
|
+
* timestamps), NOT recomputed by the conductor from a re-read `started_at`
|
|
9
|
+
* (GAIA-151): the log is the single source of the run's wall-clock length, so
|
|
10
|
+
* there is no timestamp round-trip through JSON:API to mis-parse.
|
|
11
|
+
*/
|
|
12
|
+
export interface AgentFootprint {
|
|
13
|
+
/** Total tokens across every usage bucket of every assistant turn. */
|
|
14
|
+
tokens: number;
|
|
15
|
+
/**
|
|
16
|
+
* Wall-clock run length in seconds, derived from the transcript's first→last
|
|
17
|
+
* entry timestamps (GAIA-151). 0 when the transcript has fewer than two
|
|
18
|
+
* timestamped entries (empty/absent log).
|
|
19
|
+
*/
|
|
20
|
+
duration_s: number;
|
|
21
|
+
/** Number of assistant turns in the transcript. */
|
|
22
|
+
agent_turns: number;
|
|
23
|
+
/** Number of tool-use calls across all assistant turns. */
|
|
24
|
+
tool_calls: number;
|
|
25
|
+
/** Number of user-submitted prompts. */
|
|
26
|
+
user_prompts: number;
|
|
27
|
+
/** Total words across those user prompts. */
|
|
28
|
+
user_prompt_words: number;
|
|
29
|
+
/** Model of the last assistant turn, when present (informational). */
|
|
30
|
+
model?: string;
|
|
31
|
+
}
|
|
32
|
+
/** An all-zero footprint — the honest result for an empty/absent transcript. */
|
|
33
|
+
export declare function emptyAgentFootprint(): AgentFootprint;
|
|
34
|
+
/**
|
|
35
|
+
* A GAIA agent: the program the conductor runs to work a ticket (e.g. claude).
|
|
36
|
+
* It knows how it is launched (CLI + model + flags) and where its per-run
|
|
37
|
+
* transcript/log lives — so the conductor stays agent-agnostic and the CLI can
|
|
38
|
+
* attach the run log on release without agent-specific knowledge.
|
|
39
|
+
*/
|
|
40
|
+
export interface GaiaAgent {
|
|
41
|
+
/** Stable id, e.g. 'claude'. */
|
|
42
|
+
readonly id: string;
|
|
43
|
+
/**
|
|
44
|
+
* Build the full agent CLI invocation for a GAIA prompt. With an empty
|
|
45
|
+
* prompt, returns the bare launch command (no prompt argument).
|
|
46
|
+
*/
|
|
47
|
+
launchCommand(prompt: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Locate and read this agent's run transcript for a run that executed in
|
|
50
|
+
* `worktreePath`. MUST return '' (never throw) when nothing is found.
|
|
51
|
+
*/
|
|
52
|
+
getRunLog(worktreePath: string): Promise<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Parse this agent's run transcript (as returned by {@link getRunLog}) into
|
|
55
|
+
* a footprint of effort metrics (GAIA-132). The transcript format is
|
|
56
|
+
* agent-specific, so each agent owns its own parser. MUST be tolerant of
|
|
57
|
+
* blank/partial/absent input (never throw) — an empty log yields
|
|
58
|
+
* {@link emptyAgentFootprint}.
|
|
59
|
+
*/
|
|
60
|
+
parseFootprint(log: string): AgentFootprint;
|
|
61
|
+
}
|