@gaia-ai/core 0.5.5 → 0.6.1
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 +2 -2
- package/dist/src/cli/commands.d.ts +27 -0
- package/dist/src/cli/gaia-dir.d.ts +51 -0
- package/dist/src/cli/gaia-dir.js +152 -0
- package/dist/src/cli/load-gaia-config.d.ts +25 -0
- package/dist/src/cli/load-gaia-config.js +117 -0
- package/dist/src/cli/machine-context.d.ts +24 -0
- package/dist/src/cli/machine-context.js +45 -0
- package/dist/src/cli/paths.d.ts +11 -0
- package/dist/src/cli/paths.js +31 -0
- package/dist/src/cli/resolve-module.d.ts +6 -0
- package/dist/src/cli/resolve-module.js +24 -0
- package/dist/src/conductor-registry/index.d.ts +23 -0
- package/dist/src/conductor-registry/index.js +59 -0
- package/dist/src/core/exec.d.ts +1 -1
- package/dist/src/core/exec.js +1 -1
- package/dist/src/index.d.ts +11 -8
- package/dist/src/index.js +23 -3
- package/dist/src/plugins/discover-addons.d.ts +33 -0
- package/dist/src/plugins/discover-addons.js +238 -0
- package/dist/src/plugins/preset.d.ts +94 -0
- package/dist/src/plugins/preset.js +52 -0
- package/dist/src/workflow/step-contract.d.ts +119 -0
- package/dist/src/workflow/step-contract.js +430 -0
- package/package.json +11 -6
- package/dist/src/plugins/agent/agent.d.ts +0 -61
- package/dist/src/plugins/agent/agent.js +0 -11
- package/dist/src/plugins/auth/basic.d.ts +0 -11
- package/dist/src/plugins/auth/basic.js +0 -35
- package/dist/src/plugins/executor/executor.d.ts +0 -104
- package/dist/src/plugins/plugins.d.ts +0 -60
- package/dist/src/plugins/plugins.js +0 -42
- package/dist/src/plugins/registry-exports.d.ts +0 -6
- package/dist/src/plugins/registry-exports.js +0 -6
- package/dist/src/plugins/remote/drupal.d.ts +0 -35
- package/dist/src/plugins/remote/drupal.js +0 -369
- package/dist/src/plugins/remote/fake.d.ts +0 -109
- package/dist/src/plugins/remote/fake.js +0 -243
- package/dist/src/plugins/remote/remote.d.ts +0 -193
- package/dist/src/plugins/remote/remote.js +0 -1
- package/dist/src/plugins/workspace/fake.d.ts +0 -6
- package/dist/src/plugins/workspace/fake.js +0 -16
- package/dist/src/plugins/workspace/git.d.ts +0 -37
- package/dist/src/plugins/workspace/git.js +0 -89
- package/dist/src/plugins/workspace/instructions.d.ts +0 -6
- package/dist/src/plugins/workspace/instructions.js +0 -16
- package/dist/src/plugins/workspace/workspace.d.ts +0 -35
- package/dist/src/plugins/workspace/workspace.js +0 -1
- package/dist/src/plugins-index.d.ts +0 -1
- package/dist/src/plugins-index.js +0 -1
- package/dist/src/types.d.ts +0 -52
- package/dist/src/types.js +0 -1
- /package/dist/src/{plugins/executor/executor.js → cli/commands.js} +0 -0
package/dist/src/index.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
export type { GaiaCommandHost, GaiaCommandPlugin } from './cli/commands.js';
|
|
2
|
+
export type { GaiaConfigResolution } from './cli/gaia-dir.js';
|
|
3
|
+
export { findGaiaConfig, findGaiaDir, resolveConfigPath, resolveGaiaConfigPath, } from './cli/gaia-dir.js';
|
|
4
|
+
export { type GaiaConnectionConfig, loadGaiaConfig, } from './cli/load-gaia-config.js';
|
|
5
|
+
export { legacyMachineContextPath, type MachineContext, machineContextPath, readMachineContext, resolveMachineContextPath, } from './cli/machine-context.js';
|
|
6
|
+
export { corePackageRoot, homeFallbackGaiaConfigPath, } from './cli/paths.js';
|
|
7
|
+
export { resolveModuleEslintStyle } from './cli/resolve-module.js';
|
|
8
|
+
export { type ConductorRegistryEntry, conductorRegistryPath, getRegisteredConductor, listRegisteredConductors, registerConductor, removeConductor, } from './conductor-registry/index.js';
|
|
1
9
|
export { conductorId } from './core/conductor-id.js';
|
|
2
10
|
export { CommandRunner, ExecError, exec, setDefaultCommandRunner, } from './core/exec.js';
|
|
3
11
|
export { type ConductorLogger, createLogger } from './core/logger.js';
|
|
4
12
|
export { shellQuote } from './core/shell.js';
|
|
5
13
|
export { DEFAULT_SLUG_MAX_LENGTH, slugify } from './core/slug.js';
|
|
6
|
-
export { type
|
|
7
|
-
export type
|
|
8
|
-
export type
|
|
9
|
-
export { selectAgent, selectAgents } from './plugins/plugins.js';
|
|
10
|
-
export type * from './plugins/remote/remote.js';
|
|
11
|
-
export { loadInstructions } from './plugins/workspace/instructions.js';
|
|
12
|
-
export type { EnsuredWorkspace, GaiaWorkspace, } from './plugins/workspace/workspace.js';
|
|
13
|
-
export type * from './types.js';
|
|
14
|
+
export { discoverAddons, type ResolvedConductorSlots, resolveConductorSlots, } from './plugins/discover-addons.js';
|
|
15
|
+
export { type AddonEntry, type CommandDescriptor, type DiscoveredContributions, emptyContributions, type GaiaPreset, type GaiaSurface, type OpaqueAccumulator, PRESET_FUNCTION_KEYS, SURFACE_KEYS, } from './plugins/preset.js';
|
|
16
|
+
export { type ExpandedLoad, expandLoad, type InputDecl, parseStepValues, readSkillWhen, type SkillContract, type SkillWhen, StepContractError, type StepValues, type Triple, validateLoad, type WhenValue, WORKFLOW_STEPS, type WorkflowStep, } from './workflow/step-contract.js';
|
package/dist/src/index.js
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
|
+
export { findGaiaConfig, findGaiaDir, resolveConfigPath, resolveGaiaConfigPath, } from './cli/gaia-dir.js';
|
|
2
|
+
export { loadGaiaConfig, } from './cli/load-gaia-config.js';
|
|
3
|
+
export { legacyMachineContextPath, machineContextPath, readMachineContext, resolveMachineContextPath, } from './cli/machine-context.js';
|
|
4
|
+
export { corePackageRoot, homeFallbackGaiaConfigPath, } from './cli/paths.js';
|
|
5
|
+
export { resolveModuleEslintStyle } from './cli/resolve-module.js';
|
|
6
|
+
export { conductorRegistryPath, getRegisteredConductor, listRegisteredConductors, registerConductor, removeConductor, } from './conductor-registry/index.js';
|
|
1
7
|
export { conductorId } from './core/conductor-id.js';
|
|
2
8
|
export { CommandRunner, ExecError, exec, setDefaultCommandRunner, } from './core/exec.js';
|
|
3
9
|
export { createLogger } from './core/logger.js';
|
|
4
10
|
export { shellQuote } from './core/shell.js';
|
|
5
11
|
export { DEFAULT_SLUG_MAX_LENGTH, slugify } from './core/slug.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
// GAIA-215: the Storybook-style preset contract + the shared addon discovery.
|
|
13
|
+
// GAIA-224 (Finding 6): SURFACE-AGNOSTIC only. The conductor-surface interfaces
|
|
14
|
+
// (`GaiaRemote`/`GaiaExecutor`/`GaiaWorkspace`/`GaiaAgent`, the four `*Plugin`
|
|
15
|
+
// interfaces, `ExecutorDeps`, `AgentCandidate`/`ResolvedAgent`, the `select*`
|
|
16
|
+
// selectors), the config contract (`ConductorSettings`/`ConductorFileConfig`/
|
|
17
|
+
// `ConductorEngineConfig`) and every built-in IMPLEMENTATION have moved out of
|
|
18
|
+
// this kernel — the contract to `@gaia-ai/conductor` (subpath
|
|
19
|
+
// `@gaia-ai/conductor/contract`), the impls to their own addons
|
|
20
|
+
// (`@gaia-ai/addon-remote-drupal`, `@gaia-ai/addon-workspace-git`,
|
|
21
|
+
// `@gaia-ai/addon-auth-basic`, `@gaia-ai/addon-fake`). `loadInstructions` moved to
|
|
22
|
+
// `@gaia-ai/addon-workspace-git`. Core ships NO plugin implementations.
|
|
23
|
+
export { discoverAddons, resolveConductorSlots, } from './plugins/discover-addons.js';
|
|
24
|
+
export { emptyContributions, PRESET_FUNCTION_KEYS, SURFACE_KEYS, } from './plugins/preset.js';
|
|
25
|
+
// GAIA-194 AC-2: the agent-host contract (`AgentLaunchHost` / `HostedAgent` /
|
|
26
|
+
// `supportsAgentHost`) is likewise NOT here — it is the TUI renderer's own plugin
|
|
27
|
+
// seam (`@gaia-ai/addon-gaia-ui`), and herdr types its impl locally.
|
|
28
|
+
export { expandLoad, parseStepValues, readSkillWhen, StepContractError, validateLoad, WORKFLOW_STEPS, } from './workflow/step-contract.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ConductorLogger } from '../core/logger.js';
|
|
2
|
+
import { type AddonEntry, type DiscoveredContributions, type GaiaSurface } from './preset.js';
|
|
3
|
+
/**
|
|
4
|
+
* Discover + accumulate the `entries` for one `surface`. `bases` is the
|
|
5
|
+
* ESLint-style resolution order (config dir → cwd → install). Depth-first over
|
|
6
|
+
* meta-addon `addons` (children before parent), visited-set keyed by resolved
|
|
7
|
+
* path + serialized options (dedup + cycle-safe). Returns the accumulated
|
|
8
|
+
* per-extension-point arrays; singleton resolution is `resolveConductorSlots`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function discoverAddons(entries: AddonEntry[] | undefined, surface: GaiaSurface, bases: string[], opts?: {
|
|
11
|
+
logger?: ConductorLogger;
|
|
12
|
+
}): Promise<DiscoveredContributions>;
|
|
13
|
+
/**
|
|
14
|
+
* The resolved conductor slots (last-wins singletons + agent candidate list).
|
|
15
|
+
* Generic over the four element types so the kernel stays surface-agnostic while
|
|
16
|
+
* the caller keeps full type-safety: the engine passes its narrowed
|
|
17
|
+
* `ConductorContributions` (see `@gaia-ai/conductor`'s
|
|
18
|
+
* `narrowConductorContributions`) and gets `RemotePlugin`/`ExecutorPlugin`/
|
|
19
|
+
* `WorkspacePlugin`/`AgentCandidate` back.
|
|
20
|
+
*/
|
|
21
|
+
export interface ResolvedConductorSlots<R = unknown, E = unknown, W = unknown, A = unknown> {
|
|
22
|
+
remote?: R;
|
|
23
|
+
executor?: E;
|
|
24
|
+
workspace?: W;
|
|
25
|
+
agents: A[];
|
|
26
|
+
}
|
|
27
|
+
/** Resolve discovered conductor contributions into the four engine slots. */
|
|
28
|
+
export declare function resolveConductorSlots<R, E, W, A>(contributions: {
|
|
29
|
+
remotes: R[];
|
|
30
|
+
executors: E[];
|
|
31
|
+
workspaces: W[];
|
|
32
|
+
agents: A[];
|
|
33
|
+
}, logger?: ConductorLogger): ResolvedConductorSlots<R, E, W, A>;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// GAIA-215: addon discovery. One shared `discoverAddons(entries, surface, bases)`
|
|
2
|
+
// called by the conductor loader, the connection loader, and the host — each
|
|
3
|
+
// runs ONLY its surface's accumulators. Resolution reuses the ESLint-style
|
|
4
|
+
// resolver (`resolveModuleEslintStyle`) so an addon named in a config resolves
|
|
5
|
+
// the same way a slot plugin did before (config dir → cwd → install).
|
|
6
|
+
//
|
|
7
|
+
// Ordering / override semantics (Storybook never wrote these down; spec'd here):
|
|
8
|
+
// 1. addons apply in `addons[]` ARRAY ORDER;
|
|
9
|
+
// 2. a preset's `addons` children apply DEPTH-FIRST BEFORE the declaring preset
|
|
10
|
+
// (a parent overrides its children — essentials-style composition);
|
|
11
|
+
// 3. the user config file is the final preset (its own `addons[]` are the
|
|
12
|
+
// top-level entries, processed last → it always wins);
|
|
13
|
+
// 4. an EXACT duplicate addon (same resolved path AND same options) is
|
|
14
|
+
// processed once (visited-set keyed by path + serialized options) and
|
|
15
|
+
// warned — this also prevents meta-addon cycles and fixes the old one-level
|
|
16
|
+
// `composePlugins` flatten. The same package with DIFFERENT options is a
|
|
17
|
+
// legitimate multi-instance (e.g. two oauth2 profiles) and both load.
|
|
18
|
+
// Per entry: resolve → import `<pkg>/preset` (fallback: the package's default
|
|
19
|
+
// export adapted as a single contribution, logged) → run the surface's
|
|
20
|
+
// accumulators with the entry's `with` → for `agents`, attach the entry's
|
|
21
|
+
// `priority`. The runtime `.kind` guard is retained at singleton resolution.
|
|
22
|
+
//
|
|
23
|
+
// GAIA-224 (Finding 6, decision 7): surface-AGNOSTIC. The per-surface
|
|
24
|
+
// accumulators are opaque `unknown[]` here (their element types belong to the
|
|
25
|
+
// surface owner — `@gaia-ai/conductor` for the conductor surface), so this module
|
|
26
|
+
// keeps zero `@gaia-ai/*` edges. Everything it needs from a contribution is
|
|
27
|
+
// structural: the `.kind` tag for the singleton guard, `.id` for the warn line,
|
|
28
|
+
// and `.priority` for the agent-candidate attach.
|
|
29
|
+
import { pathToFileURL } from 'node:url';
|
|
30
|
+
import { resolveModuleEslintStyle } from '../cli/resolve-module.js';
|
|
31
|
+
import { emptyContributions, PRESET_FUNCTION_KEYS, SURFACE_KEYS, } from './preset.js';
|
|
32
|
+
function warnFrom(logger) {
|
|
33
|
+
if (logger)
|
|
34
|
+
return (msg) => logger.warn({}, msg);
|
|
35
|
+
return (msg) => {
|
|
36
|
+
console.warn(msg);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Normalize an `AddonEntry` to `{ spec, opts, priority }`. */
|
|
40
|
+
function normalizeEntry(entry) {
|
|
41
|
+
if (typeof entry === 'string')
|
|
42
|
+
return { spec: entry, opts: undefined };
|
|
43
|
+
const spec = entry.use ?? entry.name;
|
|
44
|
+
if (typeof spec !== 'string' || spec.trim() === '') {
|
|
45
|
+
throw new Error(`addons[] entry must name a package via a string or { use } / { name }; got ${JSON.stringify(entry)}`);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
spec,
|
|
49
|
+
opts: entry.with ?? entry.options,
|
|
50
|
+
...(entry.priority ? { priority: entry.priority } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Does an imported module look like a preset (any accumulator fn or `addons`)? */
|
|
54
|
+
function isPresetShaped(mod) {
|
|
55
|
+
if (Array.isArray(mod.addons))
|
|
56
|
+
return true;
|
|
57
|
+
return PRESET_FUNCTION_KEYS.some((k) => typeof mod[k] === 'function');
|
|
58
|
+
}
|
|
59
|
+
/** Pick a module's single factory: the default export, else a sole function. */
|
|
60
|
+
function pickFactory(mod) {
|
|
61
|
+
if (typeof mod.default === 'function') {
|
|
62
|
+
return mod.default;
|
|
63
|
+
}
|
|
64
|
+
const fns = Object.keys(mod).filter((k) => typeof mod[k] === 'function');
|
|
65
|
+
if (fns.length === 1)
|
|
66
|
+
return mod[fns[0]];
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Adapt a not-yet-migrated package (no `./preset`, module not preset-shaped) by
|
|
71
|
+
* constructing its default/sole factory and routing the built plugin into the
|
|
72
|
+
* surface accumulator matching its `.kind`. Incremental-migration path; logged.
|
|
73
|
+
* A factory returning an array (dropsh `composePlugins` aggregator) is flattened.
|
|
74
|
+
*/
|
|
75
|
+
function adaptDefaultExport(mod, spec, warn) {
|
|
76
|
+
const factory = pickFactory(mod);
|
|
77
|
+
if (!factory) {
|
|
78
|
+
throw new Error(`addon '${spec}' has no ./preset, is not preset-shaped, and has no default/sole factory to adapt`);
|
|
79
|
+
}
|
|
80
|
+
warn(`addon '${spec}' has no ./preset export — adapting its default export (legacy; migrate it to a ./preset, GAIA-215 AC-3)`);
|
|
81
|
+
return {
|
|
82
|
+
connectionPlugins: (acc, opts) => {
|
|
83
|
+
const built = factory(opts);
|
|
84
|
+
const list = Array.isArray(built) ? built : [built];
|
|
85
|
+
// Route by kind: only plugins WITHOUT a GAIA engine kind are dropsh
|
|
86
|
+
// connection plugins; engine kinds are routed by the branches below.
|
|
87
|
+
const conn = list.filter((p) => !isEngineKind(p.kind));
|
|
88
|
+
return [
|
|
89
|
+
...acc,
|
|
90
|
+
...conn,
|
|
91
|
+
];
|
|
92
|
+
},
|
|
93
|
+
remotes: (acc, opts) => routeByKind(factory, opts, 'remote', acc),
|
|
94
|
+
executors: (acc, opts) => routeByKind(factory, opts, 'executor', acc),
|
|
95
|
+
workspaces: (acc, opts) => routeByKind(factory, opts, 'workspace', acc),
|
|
96
|
+
agents: (acc, opts) => {
|
|
97
|
+
const built = factory(opts);
|
|
98
|
+
const list = Array.isArray(built) ? built : [built];
|
|
99
|
+
const agents = list.filter((p) => p.kind === 'agent');
|
|
100
|
+
return [...acc, ...agents.map((agent) => ({ agent }))];
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function isEngineKind(kind) {
|
|
105
|
+
return (kind === 'remote' ||
|
|
106
|
+
kind === 'executor' ||
|
|
107
|
+
kind === 'workspace' ||
|
|
108
|
+
kind === 'agent');
|
|
109
|
+
}
|
|
110
|
+
function routeByKind(factory, opts, kind, acc) {
|
|
111
|
+
const built = factory(opts);
|
|
112
|
+
const list = Array.isArray(built) ? built : [built];
|
|
113
|
+
const matching = list.filter((p) => p.kind === kind);
|
|
114
|
+
return [...acc, ...matching];
|
|
115
|
+
}
|
|
116
|
+
/** Resolve + import the preset for one addon spec. Returns the preset + a stable
|
|
117
|
+
* visited key (the resolved module path). */
|
|
118
|
+
async function loadPreset(spec, bases, warn) {
|
|
119
|
+
// 1. the conventional `<pkg>/preset` subpath.
|
|
120
|
+
const presetPath = resolveModuleEslintStyle(`${spec}/preset`, bases);
|
|
121
|
+
if (presetPath !== undefined) {
|
|
122
|
+
const mod = (await import(pathToFileURL(presetPath).href));
|
|
123
|
+
if (!isPresetShaped(mod)) {
|
|
124
|
+
throw new Error(`addon '${spec}' exposes a ./preset that declares no contributions`);
|
|
125
|
+
}
|
|
126
|
+
return { preset: mod, key: presetPath };
|
|
127
|
+
}
|
|
128
|
+
// 2. the package's main export — either preset-shaped, or a legacy default.
|
|
129
|
+
const mainPath = resolveModuleEslintStyle(spec, bases);
|
|
130
|
+
if (mainPath === undefined) {
|
|
131
|
+
throw new Error(`addons[] cannot resolve addon '${spec}'`);
|
|
132
|
+
}
|
|
133
|
+
const mod = (await import(pathToFileURL(mainPath).href));
|
|
134
|
+
if (isPresetShaped(mod))
|
|
135
|
+
return { preset: mod, key: mainPath };
|
|
136
|
+
return { preset: adaptDefaultExport(mod, spec, warn), key: mainPath };
|
|
137
|
+
}
|
|
138
|
+
/** Run one preset's surface accumulators into `acc`, attaching `priority` to any
|
|
139
|
+
* newly appended agent candidates. */
|
|
140
|
+
async function applyPreset(preset, opts, priority, surface, acc) {
|
|
141
|
+
for (const key of SURFACE_KEYS[surface]) {
|
|
142
|
+
const fn = preset[key];
|
|
143
|
+
if (typeof fn !== 'function')
|
|
144
|
+
continue;
|
|
145
|
+
const before = key === 'agents' ? acc.agents.length : 0;
|
|
146
|
+
// biome-ignore lint/suspicious/noExplicitAny: heterogeneous per-key arrays
|
|
147
|
+
acc[key] = await fn(acc[key], opts);
|
|
148
|
+
if (key === 'agents' && priority) {
|
|
149
|
+
for (let i = before; i < acc.agents.length; i++) {
|
|
150
|
+
const c = acc.agents[i];
|
|
151
|
+
if (!c.priority)
|
|
152
|
+
acc.agents[i] = { ...c, priority };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Discover + accumulate the `entries` for one `surface`. `bases` is the
|
|
159
|
+
* ESLint-style resolution order (config dir → cwd → install). Depth-first over
|
|
160
|
+
* meta-addon `addons` (children before parent), visited-set keyed by resolved
|
|
161
|
+
* path + serialized options (dedup + cycle-safe). Returns the accumulated
|
|
162
|
+
* per-extension-point arrays; singleton resolution is `resolveConductorSlots`.
|
|
163
|
+
*/
|
|
164
|
+
export async function discoverAddons(entries, surface, bases, opts = {}) {
|
|
165
|
+
const acc = emptyContributions();
|
|
166
|
+
if (!Array.isArray(entries) || entries.length === 0)
|
|
167
|
+
return acc;
|
|
168
|
+
const warn = warnFrom(opts.logger);
|
|
169
|
+
const visited = new Set();
|
|
170
|
+
const walk = async (list) => {
|
|
171
|
+
for (const entry of list) {
|
|
172
|
+
const { spec, opts: withOpts, priority } = normalizeEntry(entry);
|
|
173
|
+
const { preset, key } = await loadPreset(spec, bases, warn);
|
|
174
|
+
// Dedup on (resolved path + serialized options): an EXACT repeat is a
|
|
175
|
+
// mistake (skip + warn), but the SAME package with DIFFERENT `with` is a
|
|
176
|
+
// legitimate multi-instance (e.g. two @dropsh/plugin-oauth2 profiles —
|
|
177
|
+
// session + pm), so both must load. Cycle-safe: a meta-addon that re-lists
|
|
178
|
+
// itself with the same options hits the visited key and stops.
|
|
179
|
+
const dedupKey = `${key}${optionsKey(withOpts)}`;
|
|
180
|
+
if (visited.has(dedupKey)) {
|
|
181
|
+
warn(`addon '${spec}' already registered — skipping the exact duplicate`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
visited.add(dedupKey);
|
|
185
|
+
// Depth-first: meta-addon children apply BEFORE the declaring preset.
|
|
186
|
+
if (Array.isArray(preset.addons) && preset.addons.length > 0) {
|
|
187
|
+
await walk(preset.addons);
|
|
188
|
+
}
|
|
189
|
+
await applyPreset(preset, withOpts, priority, surface, acc);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
await walk(entries);
|
|
193
|
+
return acc;
|
|
194
|
+
}
|
|
195
|
+
/** Stable-ish serialization of an addon entry's `with` options for dedup keying.
|
|
196
|
+
* Functions collapse to a marker; unserializable values fall back to String(). */
|
|
197
|
+
function optionsKey(opts) {
|
|
198
|
+
if (opts === undefined)
|
|
199
|
+
return '';
|
|
200
|
+
try {
|
|
201
|
+
return (JSON.stringify(opts, (_k, v) => (typeof v === 'function' ? '[fn]' : v)) ??
|
|
202
|
+
'');
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return String(opts);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** Take the LAST contributor for a singleton slot; warn when >1 competed; run
|
|
209
|
+
* the runtime `.kind` guard so a mis-declared contribution still fails loudly. */
|
|
210
|
+
function resolveSingleton(list, kind, logger) {
|
|
211
|
+
if (list.length === 0)
|
|
212
|
+
return undefined;
|
|
213
|
+
const warn = warnFrom(logger);
|
|
214
|
+
if (list.length > 1) {
|
|
215
|
+
const ids = list
|
|
216
|
+
.map((p) => p.id ?? '<?>')
|
|
217
|
+
.join(', ');
|
|
218
|
+
warn(`${kind}: ${list.length} addons contributed a ${kind} ([${ids}]); using the last (override wins)`);
|
|
219
|
+
}
|
|
220
|
+
const picked = list[list.length - 1];
|
|
221
|
+
const pickedKind = picked.kind;
|
|
222
|
+
if (pickedKind !== kind) {
|
|
223
|
+
throw new Error(`addon contributed a '${String(pickedKind)}' into the ${kind} slot`);
|
|
224
|
+
}
|
|
225
|
+
return picked;
|
|
226
|
+
}
|
|
227
|
+
/** Resolve discovered conductor contributions into the four engine slots. */
|
|
228
|
+
export function resolveConductorSlots(contributions, logger) {
|
|
229
|
+
const remote = resolveSingleton(contributions.remotes, 'remote', logger);
|
|
230
|
+
const executor = resolveSingleton(contributions.executors, 'executor', logger);
|
|
231
|
+
const workspace = resolveSingleton(contributions.workspaces, 'workspace', logger);
|
|
232
|
+
return {
|
|
233
|
+
...(remote ? { remote } : {}),
|
|
234
|
+
...(executor ? { executor } : {}),
|
|
235
|
+
...(workspace ? { workspace } : {}),
|
|
236
|
+
agents: contributions.agents,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { DropSHPlugin } from 'dropsh/plugin';
|
|
2
|
+
/**
|
|
3
|
+
* A lazy command pointer (moved here from the host, GAIA-201, + `describe`). The
|
|
4
|
+
* command surface accumulates DESCRIPTORS — never constructed command plugins —
|
|
5
|
+
* so the host runs every addon's `commands` at boot without importing their
|
|
6
|
+
* runtime (preserves the GAIA-201 lazy-mount + "gaia --help imports none").
|
|
7
|
+
*/
|
|
8
|
+
export interface CommandDescriptor {
|
|
9
|
+
/** The subcommand name mounted as `gaia <name> …`. */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Module specifier that default- (or `export`-) exports the `GaiaCommandPlugin`. */
|
|
12
|
+
plugin: string;
|
|
13
|
+
/** Shown in `gaia --help` even when the plugin is NOT loaded (describe-only stub). */
|
|
14
|
+
describe: string;
|
|
15
|
+
/** Named export to pick instead of the module default. */
|
|
16
|
+
export?: string;
|
|
17
|
+
}
|
|
18
|
+
/** The three surfaces, each named for its config home (surface = file = plugins). */
|
|
19
|
+
export type GaiaSurface = 'command' | 'conductor' | 'connection';
|
|
20
|
+
/**
|
|
21
|
+
* One entry in an `addons[]` array — a bare package name, or a descriptor naming
|
|
22
|
+
* the package (`use`, or SB's `name`) plus its `with` options and, for agents, a
|
|
23
|
+
* static `priority` over the ticket. Never carries a `kind`/`export`: the surface
|
|
24
|
+
* is decided by which preset function the addon exports; the kind by the
|
|
25
|
+
* contribution's own `.kind`.
|
|
26
|
+
*/
|
|
27
|
+
export type AddonEntry = string | {
|
|
28
|
+
/** The addon package (GAIA term). */
|
|
29
|
+
use?: string;
|
|
30
|
+
/** Storybook's term for the same field; accepted as an alias. */
|
|
31
|
+
name?: string;
|
|
32
|
+
/** Options handed to the addon's accumulators as `opts`. */
|
|
33
|
+
with?: unknown;
|
|
34
|
+
/** Alias for `with`. */
|
|
35
|
+
options?: unknown;
|
|
36
|
+
/**
|
|
37
|
+
* Agent-surface only: the static per-ticket priority (see the conductor
|
|
38
|
+
* surface's `selectAgent`). The ticket type belongs to the conductor
|
|
39
|
+
* surface, so the kernel types the parameter as `never` — that makes ANY
|
|
40
|
+
* concretely-typed `(ticket: Ticket) => number` assignable here (a `never`
|
|
41
|
+
* parameter is contravariantly assignable to every parameter type) while
|
|
42
|
+
* keeping this module free of a conductor import.
|
|
43
|
+
*/
|
|
44
|
+
priority?: (ticket: never) => number;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The accumulated contributions across an `addons[]` list, one array per
|
|
48
|
+
* extension point. `discoverAddons` populates only the arrays belonging to the
|
|
49
|
+
* requested surface (the others stay empty) — so a connection load never runs
|
|
50
|
+
* the conductor accumulators.
|
|
51
|
+
*/
|
|
52
|
+
export interface DiscoveredContributions {
|
|
53
|
+
commands: CommandDescriptor[];
|
|
54
|
+
/** Opaque: `RemotePlugin[]` on the conductor surface. */
|
|
55
|
+
remotes: unknown[];
|
|
56
|
+
/** Opaque: `ExecutorPlugin[]` on the conductor surface. */
|
|
57
|
+
executors: unknown[];
|
|
58
|
+
/** Opaque: `WorkspacePlugin[]` on the conductor surface. */
|
|
59
|
+
workspaces: unknown[];
|
|
60
|
+
/** Opaque: `AgentCandidate[]` on the conductor surface. */
|
|
61
|
+
agents: unknown[];
|
|
62
|
+
connectionPlugins: DropSHPlugin[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* An OPAQUE per-surface accumulator. The kernel threads it without knowing its
|
|
66
|
+
* element type; the surface owner declares the concrete signature (see
|
|
67
|
+
* `@gaia-ai/conductor`'s `Preset`). `never[]` on the input side is deliberate:
|
|
68
|
+
* it keeps a concretely-typed accumulator such as
|
|
69
|
+
* `(acc: RemotePlugin[], opts) => RemotePlugin[]` structurally assignable to
|
|
70
|
+
* this opaque form, so a conductor-surface `Preset` still satisfies `GaiaPreset`.
|
|
71
|
+
*/
|
|
72
|
+
export type OpaqueAccumulator<O> = (acc: never[], opts: O) => unknown[] | Promise<unknown[]>;
|
|
73
|
+
/** The extension points each surface runs (the preset function names to call). */
|
|
74
|
+
export declare const SURFACE_KEYS: Record<GaiaSurface, Array<keyof DiscoveredContributions>>;
|
|
75
|
+
/**
|
|
76
|
+
* A preset: named per-surface accumulator functions + a meta-addon `addons`
|
|
77
|
+
* array. Every accumulator is `(acc, opts) => acc` (sync or async); returning a
|
|
78
|
+
* new array lets an addon append, replace, or even FILTER a prior contribution
|
|
79
|
+
* (override semantics — the user config participates as the final preset).
|
|
80
|
+
*/
|
|
81
|
+
export interface GaiaPreset<O = unknown> {
|
|
82
|
+
/** Meta-addon composition: children apply depth-first BEFORE this preset. */
|
|
83
|
+
addons?: AddonEntry[];
|
|
84
|
+
commands?: (acc: CommandDescriptor[], opts: O) => CommandDescriptor[] | Promise<CommandDescriptor[]>;
|
|
85
|
+
remotes?: OpaqueAccumulator<O>;
|
|
86
|
+
executors?: OpaqueAccumulator<O>;
|
|
87
|
+
workspaces?: OpaqueAccumulator<O>;
|
|
88
|
+
agents?: OpaqueAccumulator<O>;
|
|
89
|
+
connectionPlugins?: (acc: DropSHPlugin[], opts: O) => DropSHPlugin[] | Promise<DropSHPlugin[]>;
|
|
90
|
+
}
|
|
91
|
+
/** The preset function keys, for shape-detection during discovery. */
|
|
92
|
+
export declare const PRESET_FUNCTION_KEYS: Array<keyof DiscoveredContributions>;
|
|
93
|
+
/** An empty contributions accumulator. */
|
|
94
|
+
export declare function emptyContributions(): DiscoveredContributions;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// GAIA-215: the preset contract — the Storybook-style self-declaration every
|
|
2
|
+
// GAIA addon exposes through its `./preset` export subpath. A preset declares an
|
|
3
|
+
// addon's contributions through named, per-surface ACCUMULATOR functions
|
|
4
|
+
// `(acc, opts) => acc` (Storybook's `managerEntries`/`previewAnnotations` model),
|
|
5
|
+
// plus an `addons` array for meta-addon composition. Each extension point is
|
|
6
|
+
// list-valued; the core discovers presets and threads these accumulators, so a
|
|
7
|
+
// conforming addon needs no per-slot `export:`/kind wiring in config.
|
|
8
|
+
//
|
|
9
|
+
// The three GAIA surfaces (each named for its config home):
|
|
10
|
+
// - command → the `@gaia-ai/gaia` host registry (CommandDescriptor[]).
|
|
11
|
+
// - conductor → the run engine (remote / executor / workspace / agent).
|
|
12
|
+
// - connection → dropsh (auth + renderer plugins).
|
|
13
|
+
//
|
|
14
|
+
// Settings (machine_id / project / states / hooks / scheduler) are NEVER
|
|
15
|
+
// preset-contributed — identity must not be composable — so this interface has
|
|
16
|
+
// no such members and an unknown preset export is rejected loudly at discovery.
|
|
17
|
+
//
|
|
18
|
+
// GAIA-224 (Finding 6, decision 7): this module is surface-AGNOSTIC. It knows
|
|
19
|
+
// the accumulator NAMES of every surface (`SURFACE_KEYS`) and the shape of the
|
|
20
|
+
// pure-data command surface (`CommandDescriptor`), but the element types of the
|
|
21
|
+
// CONDUCTOR surface (`RemotePlugin` / `ExecutorPlugin` / `WorkspacePlugin` /
|
|
22
|
+
// `AgentCandidate`, and the `Ticket` an agent priority scores) live in
|
|
23
|
+
// `@gaia-ai/conductor`. Importing them here would give the kernel an
|
|
24
|
+
// `@gaia-ai/*` edge, which the acyclic guard forbids — so those four
|
|
25
|
+
// accumulators are OPAQUE (see `OpaqueAccumulator`) and the concrete view is
|
|
26
|
+
// declared by the surface owner (`@gaia-ai/conductor`'s `Preset`).
|
|
27
|
+
/** The extension points each surface runs (the preset function names to call). */
|
|
28
|
+
export const SURFACE_KEYS = {
|
|
29
|
+
command: ['commands'],
|
|
30
|
+
conductor: ['remotes', 'executors', 'workspaces', 'agents'],
|
|
31
|
+
connection: ['connectionPlugins'],
|
|
32
|
+
};
|
|
33
|
+
/** The preset function keys, for shape-detection during discovery. */
|
|
34
|
+
export const PRESET_FUNCTION_KEYS = [
|
|
35
|
+
'commands',
|
|
36
|
+
'remotes',
|
|
37
|
+
'executors',
|
|
38
|
+
'workspaces',
|
|
39
|
+
'agents',
|
|
40
|
+
'connectionPlugins',
|
|
41
|
+
];
|
|
42
|
+
/** An empty contributions accumulator. */
|
|
43
|
+
export function emptyContributions() {
|
|
44
|
+
return {
|
|
45
|
+
commands: [],
|
|
46
|
+
remotes: [],
|
|
47
|
+
executors: [],
|
|
48
|
+
workspaces: [],
|
|
49
|
+
agents: [],
|
|
50
|
+
connectionPlugins: [],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** The workflow states an agent works — the legal `step` values. */
|
|
2
|
+
export declare const WORKFLOW_STEPS: readonly ["qualification", "spec", "diagnose", "coding", "review", "pre_deployment", "post_deployment", "verifying", "summary"];
|
|
3
|
+
export type WorkflowStep = (typeof WORKFLOW_STEPS)[number];
|
|
4
|
+
/** Typed defect raised on the first contract violation. */
|
|
5
|
+
export declare class StepContractError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A normalized `when` clause value: a concrete list of allowed values, the
|
|
10
|
+
* wildcard `'*'` (matches anything), or `undefined` (key omitted — also any).
|
|
11
|
+
*/
|
|
12
|
+
export type WhenValue = string[] | '*' | undefined;
|
|
13
|
+
/** A skill's `when` triple. Any of the three keys may be omitted (= any). */
|
|
14
|
+
export interface SkillWhen {
|
|
15
|
+
work_type: WhenValue;
|
|
16
|
+
workflow: WhenValue;
|
|
17
|
+
step: WhenValue;
|
|
18
|
+
}
|
|
19
|
+
/** One declared input: a value key with a short description and an optional default. */
|
|
20
|
+
export interface InputDecl {
|
|
21
|
+
/** The value key the skill reads (e.g. `test.command`). */
|
|
22
|
+
key: string;
|
|
23
|
+
/** A short human description of what the value is. */
|
|
24
|
+
description?: string;
|
|
25
|
+
/** The default used when `WORKFLOW.md` supplies no override for this key. */
|
|
26
|
+
default?: string;
|
|
27
|
+
}
|
|
28
|
+
/** The parsed contract of a single skill's frontmatter. */
|
|
29
|
+
export interface SkillContract {
|
|
30
|
+
/** Skill name from frontmatter. */
|
|
31
|
+
name: string;
|
|
32
|
+
/**
|
|
33
|
+
* The `when` triple, or `undefined` when the skill declares no `when` — a
|
|
34
|
+
* helper/capability skill that owns no step and routes nothing.
|
|
35
|
+
*/
|
|
36
|
+
when?: SkillWhen;
|
|
37
|
+
/** The inputs the skill declares (each: key + description + optional default). */
|
|
38
|
+
inputs: InputDecl[];
|
|
39
|
+
/**
|
|
40
|
+
* A **bundle** skill carries its own `## Loaded skills` section in its markdown
|
|
41
|
+
* **body** — the same list a `WORKFLOW.md` carries. `loads` holds those member
|
|
42
|
+
* names in declared order. Nothing in the frontmatter marks a bundle; the
|
|
43
|
+
* section is the marker. Present only on bundles.
|
|
44
|
+
*/
|
|
45
|
+
loads?: string[];
|
|
46
|
+
/**
|
|
47
|
+
* A bundle's member values: the inline `key: value` overrides written under a
|
|
48
|
+
* member's bullet in the bundle body, keyed by member skill name. Present only
|
|
49
|
+
* on bundles.
|
|
50
|
+
*/
|
|
51
|
+
values?: StepValues;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A concrete `(work_type, workflow, step)` the project can produce. `work_type`
|
|
55
|
+
* is `null` for a non-work-typed step (qualification, spec, deployment,
|
|
56
|
+
* verification), a `work:*` label (without the prefix) otherwise.
|
|
57
|
+
*/
|
|
58
|
+
export interface Triple {
|
|
59
|
+
work_type: string | null;
|
|
60
|
+
workflow: string;
|
|
61
|
+
step: WorkflowStep;
|
|
62
|
+
}
|
|
63
|
+
/** Per-skill values, keyed by skill `name` (the `@gaia/` prefix stripped). */
|
|
64
|
+
export type StepValues = Record<string, Record<string, string>>;
|
|
65
|
+
/**
|
|
66
|
+
* Read the per-skill value **overrides** from a `WORKFLOW.md`'s `## Loaded skills`
|
|
67
|
+
* section. A project overrides a skill default **inline under that skill's bullet**
|
|
68
|
+
* (indented `key: value` YAML, block scalars allowed), not in one global block.
|
|
69
|
+
* Returns a map keyed by skill `name` (the `@gaia/` prefix stripped); a skill with no
|
|
70
|
+
* overrides maps to `{}` (it runs on its declared defaults).
|
|
71
|
+
*/
|
|
72
|
+
export declare function parseStepValues(workflowMd: string): StepValues;
|
|
73
|
+
/**
|
|
74
|
+
* Parse a skill's frontmatter into its `SkillContract` — the `when` triple (when
|
|
75
|
+
* present) and the declared `inputs`. Malformed frontmatter raises `StepContractError`.
|
|
76
|
+
*/
|
|
77
|
+
export declare function readSkillWhen(skillMd: string): SkillContract;
|
|
78
|
+
/** What a load expands to: the flat leaf skills plus the values that reached them. */
|
|
79
|
+
export interface ExpandedLoad {
|
|
80
|
+
/** The leaf skills (step-owners + helpers), deduplicated, in first-seen order. */
|
|
81
|
+
skills: SkillContract[];
|
|
82
|
+
/** The effective per-skill values, keyed by skill name. */
|
|
83
|
+
values: StepValues;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Read a `WORKFLOW.md` and expand what it loads into the flat, deduplicated list of
|
|
87
|
+
* **leaf** skills (step-owners + helpers) the project effectively runs, together with
|
|
88
|
+
* the values that reach each leaf. One call is the whole load step: parse, expand,
|
|
89
|
+
* resolve — hand the result straight to `validateLoad`.
|
|
90
|
+
*
|
|
91
|
+
* A **bundle** — a skill whose markdown body carries its own `## Loaded skills`
|
|
92
|
+
* section, so its contract carries `loads` — is recursed into, in its declared
|
|
93
|
+
* member order; a step-owner or helper is emitted as itself. Leaves are
|
|
94
|
+
* deduplicated **by name, first occurrence wins**, so the result is deterministic
|
|
95
|
+
* and idempotent — listing the same bundle twice, or a bundle plus one of its
|
|
96
|
+
* members as an explicit bullet, yields each leaf exactly once in first-seen order.
|
|
97
|
+
*
|
|
98
|
+
* **Value precedence**, narrowest wins: the project's own inline override in
|
|
99
|
+
* `WORKFLOW.md` beats a bundle's inline value for that member, which beats the
|
|
100
|
+
* skill's declared `default` (resolved later, in `validateLoad`). Between nested
|
|
101
|
+
* bundles the outermost wins, matching first-occurrence dedup.
|
|
102
|
+
*
|
|
103
|
+
* A root or member name absent from `byName`, or a cycle in the bundle graph,
|
|
104
|
+
* raises a typed `StepContractError`. Bundles may nest (cycle-safe); gaia ships one
|
|
105
|
+
* flat level. This never dispatches or routes.
|
|
106
|
+
*/
|
|
107
|
+
export declare function expandLoad(workflowMd: string, byName: Map<string, SkillContract>): ExpandedLoad;
|
|
108
|
+
/**
|
|
109
|
+
* Validate a loaded set of skills against the project's triple set and per-skill values.
|
|
110
|
+
*
|
|
111
|
+
* For every `(work_type, workflow, step)` the project can produce, **exactly one**
|
|
112
|
+
* loaded skill's `when` must match — a triple with no match (gap) or two matches
|
|
113
|
+
* (collision) is a configuration error. Every `input` a matched skill declares must
|
|
114
|
+
* resolve to a value: the `values` map (as resolved by `expandLoad`) or the input's
|
|
115
|
+
* own declared `default`.
|
|
116
|
+
*
|
|
117
|
+
* Reports the first defect as a typed `StepContractError`. Never dispatches or routes.
|
|
118
|
+
*/
|
|
119
|
+
export declare function validateLoad(skills: SkillContract[], values: StepValues, projectTriples: Triple[]): void;
|