@gaia-ai/core 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 +2 -2
- package/dist/src/cli/config-source.d.ts +47 -0
- package/dist/src/cli/config-source.js +197 -0
- package/dist/src/cli/gaia-dir.d.ts +48 -3
- package/dist/src/cli/gaia-dir.js +100 -10
- package/dist/src/cli/load-gaia-config.d.ts +5 -0
- package/dist/src/cli/load-gaia-config.js +10 -1
- package/dist/src/core/exec.d.ts +1 -1
- package/dist/src/core/exec.js +1 -1
- package/dist/src/index.d.ts +4 -11
- package/dist/src/index.js +18 -7
- package/dist/src/plugins/discover-addons.d.ts +19 -8
- package/dist/src/plugins/discover-addons.js +13 -3
- package/dist/src/plugins/preset.d.ts +30 -12
- package/dist/src/plugins/preset.js +10 -0
- package/package.json +4 -5
- 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 -12
- package/dist/src/plugins/auth/basic.js +0 -37
- package/dist/src/plugins/builtins-preset.d.ts +0 -5
- package/dist/src/plugins/builtins-preset.js +0 -32
- package/dist/src/plugins/executor/executor.d.ts +0 -104
- package/dist/src/plugins/executor/executor.js +0 -1
- 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 -40
- package/dist/src/plugins/remote/drupal.js +0 -393
- package/dist/src/plugins/remote/fake.d.ts +0 -113
- package/dist/src/plugins/remote/fake.js +0 -247
- package/dist/src/plugins/remote/remote.d.ts +0 -203
- 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 -65
- package/dist/src/types.js +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# @gaia-ai/core
|
|
2
2
|
|
|
3
|
-
GAIA
|
|
3
|
+
GAIA surface-agnostic kernel: host contract, split-config helpers, addon preset/discovery machinery, shared primitives.
|
|
4
4
|
|
|
5
|
-
Part of the GAIA
|
|
5
|
+
Part of the GAIA CLI. Install the meta package `@gaia-ai/gaia` to get the `gaia` CLI with all addons. Source: https://git.key-tec.de/keytec/gaia (gaia-cli/).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Skip a `//`-line comment; return the index of the terminating newline (or EOF). */
|
|
2
|
+
export declare function skipLineComment(src: string, i: number): number;
|
|
3
|
+
/** Skip a block comment; return the index just past it. */
|
|
4
|
+
export declare function skipBlockComment(src: string, i: number): number;
|
|
5
|
+
/** Skip a string/template literal (honouring `\` escapes and `${ … }`
|
|
6
|
+
* interpolations in backtick strings); return the index just past the close. */
|
|
7
|
+
export declare function skipString(src: string, i: number): number;
|
|
8
|
+
/** Given the index of an opening `{`/`[`/`(`, return the index of its matching
|
|
9
|
+
* close, skipping nested delimiters, strings and comments. -1 if unbalanced. */
|
|
10
|
+
export declare function matchDelimiter(src: string, openIdx: number): number;
|
|
11
|
+
/** Skip whitespace + comments starting at `i` (bounded by `limit`). */
|
|
12
|
+
export declare function skipTrivia(src: string, i: number, limit: number): number;
|
|
13
|
+
/** Read a property key (bare identifier or quoted string) at `i`. */
|
|
14
|
+
export declare function readKey(src: string, i: number): {
|
|
15
|
+
name?: string;
|
|
16
|
+
end: number;
|
|
17
|
+
};
|
|
18
|
+
/** From `from`, scan to just past the next top-level `,` (or to `limit` when the
|
|
19
|
+
* value runs to the object close), skipping nested delimiters/strings/comments. */
|
|
20
|
+
export declare function scanToTopLevelComma(src: string, from: number, limit: number): number;
|
|
21
|
+
/** One top-level property of a config's default-export object literal. */
|
|
22
|
+
export interface PropEntry {
|
|
23
|
+
key: string | undefined;
|
|
24
|
+
/** Start of the entry INCLUDING its leading trivia (comments/whitespace). */
|
|
25
|
+
start: number;
|
|
26
|
+
/** End of the entry (just past its trailing comma, or the object close). */
|
|
27
|
+
end: number;
|
|
28
|
+
}
|
|
29
|
+
/** Locate the `export default { … }` object; return the body span (exclusive of
|
|
30
|
+
* the braces) or undefined when the default export is not an object literal. */
|
|
31
|
+
export declare function findDefaultExportObject(src: string): {
|
|
32
|
+
bodyStart: number;
|
|
33
|
+
bodyEnd: number;
|
|
34
|
+
} | undefined;
|
|
35
|
+
/** Enumerate the top-level properties of the object body [bodyStart, bodyEnd). */
|
|
36
|
+
export declare function scanTopLevelProperties(src: string, bodyStart: number, bodyEnd: number): PropEntry[];
|
|
37
|
+
/**
|
|
38
|
+
* Does this config SOURCE declare `key` as a real top-level property of its
|
|
39
|
+
* `export default { … }` object?
|
|
40
|
+
*
|
|
41
|
+
* Deliberately stricter than a `/site\s*:/` regex, which would also fire on the
|
|
42
|
+
* word in a comment, in a string, or on a NESTED `site:` inside some slot's
|
|
43
|
+
* `with: { … }`. `false` when the default export is not an object literal at all
|
|
44
|
+
* (a `defineConfig(…)` call, a re-export shim) — such a file cannot be read as a
|
|
45
|
+
* connection source by inspection, and guessing is worse than falling through.
|
|
46
|
+
*/
|
|
47
|
+
export declare function declaresTopLevelKey(src: string, key: string): boolean;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// GAIA-230: the balanced-delimiter, comment-aware SOURCE scanner for a config
|
|
2
|
+
// module's `export default { … }` object — hoisted here from the conductor's
|
|
3
|
+
// `config.ts` (where GAIA-218 AC-7's `stripLegacyConnectionFromConfigSource`
|
|
4
|
+
// introduced it) so both users share one source of truth for "which top-level
|
|
5
|
+
// properties does this config source declare?":
|
|
6
|
+
//
|
|
7
|
+
// - `@gaia-ai/core` — `findGaiaConfig` gates the LEGACY `conductor.config.js`
|
|
8
|
+
// connection read on CONTENT: a config declaring no `site` is not a
|
|
9
|
+
// connection source (root cause 2). Core is layer 0 with zero `@gaia-ai/*`
|
|
10
|
+
// edges, so it cannot import the conductor's copy — hence the hoist.
|
|
11
|
+
// - `@gaia-ai/conductor` — the strip pass re-uses these primitives.
|
|
12
|
+
//
|
|
13
|
+
// Everything here is a PURE TEXT read. Nothing executes the config module: a
|
|
14
|
+
// config may import a secret-bearing machine context and have side effects, and
|
|
15
|
+
// both callers (a sync path resolver, an idempotent `--dry-run` migration) must
|
|
16
|
+
// stay safe to run on an untrusted tree.
|
|
17
|
+
const CLOSER = { '{': '}', '[': ']', '(': ')' };
|
|
18
|
+
/** Skip a `//`-line comment; return the index of the terminating newline (or EOF). */
|
|
19
|
+
export function skipLineComment(src, i) {
|
|
20
|
+
const nl = src.indexOf('\n', i);
|
|
21
|
+
return nl === -1 ? src.length : nl;
|
|
22
|
+
}
|
|
23
|
+
/** Skip a block comment; return the index just past it. */
|
|
24
|
+
export function skipBlockComment(src, i) {
|
|
25
|
+
const end = src.indexOf('*/', i + 2);
|
|
26
|
+
return end === -1 ? src.length : end + 2;
|
|
27
|
+
}
|
|
28
|
+
/** Skip a string/template literal (honouring `\` escapes and `${ … }`
|
|
29
|
+
* interpolations in backtick strings); return the index just past the close. */
|
|
30
|
+
export function skipString(src, i) {
|
|
31
|
+
const quote = src[i];
|
|
32
|
+
i++;
|
|
33
|
+
while (i < src.length) {
|
|
34
|
+
const c = src[i];
|
|
35
|
+
if (c === '\\') {
|
|
36
|
+
i += 2;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (quote === '`' && c === '$' && src[i + 1] === '{') {
|
|
40
|
+
i = matchDelimiter(src, i + 1) + 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (c === quote)
|
|
44
|
+
return i + 1;
|
|
45
|
+
i++;
|
|
46
|
+
}
|
|
47
|
+
return i;
|
|
48
|
+
}
|
|
49
|
+
/** Given the index of an opening `{`/`[`/`(`, return the index of its matching
|
|
50
|
+
* close, skipping nested delimiters, strings and comments. -1 if unbalanced. */
|
|
51
|
+
export function matchDelimiter(src, openIdx) {
|
|
52
|
+
const stack = [CLOSER[src[openIdx]]];
|
|
53
|
+
let i = openIdx + 1;
|
|
54
|
+
while (i < src.length && stack.length > 0) {
|
|
55
|
+
const c = src[i];
|
|
56
|
+
if (c === '/' && src[i + 1] === '/') {
|
|
57
|
+
i = skipLineComment(src, i);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (c === '/' && src[i + 1] === '*') {
|
|
61
|
+
i = skipBlockComment(src, i);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (c === "'" || c === '"' || c === '`') {
|
|
65
|
+
i = skipString(src, i);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (c === '{' || c === '[' || c === '(') {
|
|
69
|
+
stack.push(CLOSER[c]);
|
|
70
|
+
i++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (c === '}' || c === ']' || c === ')') {
|
|
74
|
+
if (c === stack[stack.length - 1])
|
|
75
|
+
stack.pop();
|
|
76
|
+
i++;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
i++;
|
|
80
|
+
}
|
|
81
|
+
return stack.length === 0 ? i - 1 : -1;
|
|
82
|
+
}
|
|
83
|
+
/** Skip whitespace + comments starting at `i` (bounded by `limit`). */
|
|
84
|
+
export function skipTrivia(src, i, limit) {
|
|
85
|
+
while (i < limit) {
|
|
86
|
+
const c = src[i];
|
|
87
|
+
if (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
|
|
88
|
+
i++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (c === '/' && src[i + 1] === '/') {
|
|
92
|
+
i = skipLineComment(src, i);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (c === '/' && src[i + 1] === '*') {
|
|
96
|
+
i = skipBlockComment(src, i);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
return i;
|
|
102
|
+
}
|
|
103
|
+
/** Read a property key (bare identifier or quoted string) at `i`. */
|
|
104
|
+
export function readKey(src, i) {
|
|
105
|
+
const c = src[i];
|
|
106
|
+
if (c === "'" || c === '"') {
|
|
107
|
+
const end = skipString(src, i);
|
|
108
|
+
return { name: src.slice(i + 1, end - 1), end };
|
|
109
|
+
}
|
|
110
|
+
const m = /^[A-Za-z0-9_$]+/.exec(src.slice(i));
|
|
111
|
+
if (m)
|
|
112
|
+
return { name: m[0], end: i + m[0].length };
|
|
113
|
+
return { end: i };
|
|
114
|
+
}
|
|
115
|
+
/** From `from`, scan to just past the next top-level `,` (or to `limit` when the
|
|
116
|
+
* value runs to the object close), skipping nested delimiters/strings/comments. */
|
|
117
|
+
export function scanToTopLevelComma(src, from, limit) {
|
|
118
|
+
let i = from;
|
|
119
|
+
while (i < limit) {
|
|
120
|
+
const c = src[i];
|
|
121
|
+
if (c === '/' && src[i + 1] === '/') {
|
|
122
|
+
i = skipLineComment(src, i);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (c === '/' && src[i + 1] === '*') {
|
|
126
|
+
i = skipBlockComment(src, i);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (c === "'" || c === '"' || c === '`') {
|
|
130
|
+
i = skipString(src, i);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (c === '{' || c === '[' || c === '(') {
|
|
134
|
+
i = matchDelimiter(src, i) + 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (c === ',')
|
|
138
|
+
return i + 1;
|
|
139
|
+
i++;
|
|
140
|
+
}
|
|
141
|
+
return limit;
|
|
142
|
+
}
|
|
143
|
+
/** Locate the `export default { … }` object; return the body span (exclusive of
|
|
144
|
+
* the braces) or undefined when the default export is not an object literal. */
|
|
145
|
+
export function findDefaultExportObject(src) {
|
|
146
|
+
const m = /export\s+default\s*/.exec(src);
|
|
147
|
+
if (!m)
|
|
148
|
+
return undefined;
|
|
149
|
+
const braceIdx = m.index + m[0].length;
|
|
150
|
+
if (src[braceIdx] !== '{')
|
|
151
|
+
return undefined;
|
|
152
|
+
const end = matchDelimiter(src, braceIdx);
|
|
153
|
+
if (end === -1)
|
|
154
|
+
return undefined;
|
|
155
|
+
return { bodyStart: braceIdx + 1, bodyEnd: end };
|
|
156
|
+
}
|
|
157
|
+
/** Enumerate the top-level properties of the object body [bodyStart, bodyEnd). */
|
|
158
|
+
export function scanTopLevelProperties(src, bodyStart, bodyEnd) {
|
|
159
|
+
const entries = [];
|
|
160
|
+
let i = bodyStart;
|
|
161
|
+
while (i < bodyEnd) {
|
|
162
|
+
const entryStart = i;
|
|
163
|
+
const keyPos = skipTrivia(src, i, bodyEnd);
|
|
164
|
+
if (keyPos >= bodyEnd)
|
|
165
|
+
break;
|
|
166
|
+
const key = readKey(src, keyPos);
|
|
167
|
+
let j = skipTrivia(src, key.end, bodyEnd);
|
|
168
|
+
if (src[j] !== ':') {
|
|
169
|
+
// Not a `key: value` property (spread/computed/shorthand) — keep it.
|
|
170
|
+
const term = scanToTopLevelComma(src, keyPos, bodyEnd);
|
|
171
|
+
entries.push({ key: undefined, start: entryStart, end: term });
|
|
172
|
+
i = term;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
j++;
|
|
176
|
+
const term = scanToTopLevelComma(src, j, bodyEnd);
|
|
177
|
+
entries.push({ key: key.name, start: entryStart, end: term });
|
|
178
|
+
i = term;
|
|
179
|
+
}
|
|
180
|
+
return entries;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Does this config SOURCE declare `key` as a real top-level property of its
|
|
184
|
+
* `export default { … }` object?
|
|
185
|
+
*
|
|
186
|
+
* Deliberately stricter than a `/site\s*:/` regex, which would also fire on the
|
|
187
|
+
* word in a comment, in a string, or on a NESTED `site:` inside some slot's
|
|
188
|
+
* `with: { … }`. `false` when the default export is not an object literal at all
|
|
189
|
+
* (a `defineConfig(…)` call, a re-export shim) — such a file cannot be read as a
|
|
190
|
+
* connection source by inspection, and guessing is worse than falling through.
|
|
191
|
+
*/
|
|
192
|
+
export function declaresTopLevelKey(src, key) {
|
|
193
|
+
const obj = findDefaultExportObject(src);
|
|
194
|
+
if (!obj)
|
|
195
|
+
return false;
|
|
196
|
+
return scanTopLevelProperties(src, obj.bodyStart, obj.bodyEnd).some((e) => e.key === key);
|
|
197
|
+
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The user-global `~/.gaia` dir. Core owns where the split-config files live,
|
|
3
|
+
* so a consumer that must recognise the home dir (e.g. to exclude it from a
|
|
4
|
+
* project walk-up, GAIA-219 AC-9) asks for it rather than joining it itself.
|
|
5
|
+
*/
|
|
6
|
+
export declare function homeGaiaDir(home?: string): string;
|
|
1
7
|
/**
|
|
2
8
|
* Walk from `cwd` root-ward (git/eslint style) to the nearest ancestor whose
|
|
3
9
|
* `.gaia/` dir holds at least one conductor config; return that `.gaia/` dir, or
|
|
@@ -17,6 +23,12 @@ export declare function findGaiaDir(cwd: string): string | undefined;
|
|
|
17
23
|
* 3. No `.gaia/` config anywhere up the tree → an actionable `gaia init` error.
|
|
18
24
|
*/
|
|
19
25
|
export declare function resolveConfigPath(override?: string, cwd?: string, conductorName?: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Which leg of the connection precedence produced the resolved path. Purely
|
|
28
|
+
* diagnostic (GAIA-219 D4): it lets a consumer report project-vs-home without
|
|
29
|
+
* re-deriving the rule, which would be a second precedence implementation.
|
|
30
|
+
*/
|
|
31
|
+
export type GaiaConfigSource = 'explicit' | 'project' | 'home' | 'fallback';
|
|
20
32
|
/** A resolved connection-config file: its path, whether it is the shipped
|
|
21
33
|
* machine-context fallback, and whether it is a legacy `conductor.config.js`. */
|
|
22
34
|
export interface GaiaConfigResolution {
|
|
@@ -25,23 +37,56 @@ export interface GaiaConfigResolution {
|
|
|
25
37
|
fallback: boolean;
|
|
26
38
|
/** true when the resolved file is a legacy `conductor.config.js` connection. */
|
|
27
39
|
legacy: boolean;
|
|
40
|
+
/** Which leg produced `path` (GAIA-219 D4). */
|
|
41
|
+
source: GaiaConfigSource;
|
|
28
42
|
}
|
|
29
43
|
/**
|
|
30
44
|
* Walk root-ward for the nearest `.gaia/` connection config: prefer
|
|
31
45
|
* `gaia.config.js`; fall back to a legacy `conductor.config.js` in the same dir
|
|
32
|
-
*
|
|
46
|
+
* **only when it really declares a connection** (a top-level `site` — GAIA-230).
|
|
47
|
+
* An engine-only `conductor.config.js` is skipped, and the walk continues, so the
|
|
48
|
+
* home `~/.gaia/gaia.config.js` every repo inherits is genuinely reachable.
|
|
49
|
+
* Returns `undefined` when no connection source is found up the tree.
|
|
33
50
|
*/
|
|
34
51
|
export declare function findGaiaConfig(cwd: string): {
|
|
35
52
|
path: string;
|
|
36
53
|
legacy: boolean;
|
|
37
54
|
} | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Walk root-ward for the nearest `.gaia/` dir that marks a **project** — one
|
|
57
|
+
* holding any gaia config at all, a `gaia.config.js` or at least one conductor
|
|
58
|
+
* config. Returns that `.gaia/` dir, or `undefined` up to the filesystem root.
|
|
59
|
+
*
|
|
60
|
+
* Deliberately a different question from `findGaiaConfig` (GAIA-219 AC-9):
|
|
61
|
+
* "which project am I standing in" is not "where does my connection come from".
|
|
62
|
+
* Since GAIA-230 the connection walk-up skips an engine-only
|
|
63
|
+
* `conductor.config.js`, and GAIA-218 makes exactly that the canonical repo
|
|
64
|
+
* shape — so keying project detection on the connection would stop recognising
|
|
65
|
+
* the most common project as a project. It is content-agnostic for the same
|
|
66
|
+
* reason `findGaiaDir` is: a `.gaia/` dir is the marker.
|
|
67
|
+
*
|
|
68
|
+
* It is a superset of `findGaiaDir`, which requires a *conductor* config
|
|
69
|
+
* because it resolves the engine config; a repo carrying only a project
|
|
70
|
+
* connection override is a project here and not there.
|
|
71
|
+
*/
|
|
72
|
+
export declare function findProjectGaiaDir(cwd: string): string | undefined;
|
|
38
73
|
/**
|
|
39
74
|
* Resolve the CONNECTION (`gaia.config.js`) config path. Precedence (AC-4):
|
|
40
75
|
* 1. `explicit` (`--config` / `$GAIA_CONFIG` / `$DROPSH_CONFIG`) wins verbatim.
|
|
41
|
-
* 2. Project `./.gaia/gaia.config.js` found by walk-up (legacy
|
|
42
|
-
* `conductor.config.js` accepted as a connection source
|
|
76
|
+
* 2. Project `./.gaia/gaia.config.js` found by walk-up (a legacy
|
|
77
|
+
* `conductor.config.js` accepted as a connection source only when it declares
|
|
78
|
+
* a top-level `site` — GAIA-230).
|
|
43
79
|
* 3. Home `~/.gaia/gaia.config.js` when present.
|
|
44
80
|
* 4. The shipped fallback (`homeFallbackGaiaConfigPath`, reads the machine context).
|
|
81
|
+
*
|
|
82
|
+
* `source` (GAIA-219 D4) names the leg that won. One correction applies **to
|
|
83
|
+
* the project leg only**: the walk-up in step 2 climbs to the filesystem root,
|
|
84
|
+
* so from any cwd under `$HOME` it reaches `~/.gaia/gaia.config.js` and would
|
|
85
|
+
* return it as `project`. That is the same file step 3 would return, so
|
|
86
|
+
* resolution is unaffected — but the label would be a lie, hence step 2
|
|
87
|
+
* re-labels the home candidate `home`. An **explicit** path that happens to
|
|
88
|
+
* name the home config still reports `explicit`: there the user did choose it
|
|
89
|
+
* by name, and which leg won is exactly what the report is for.
|
|
45
90
|
*/
|
|
46
91
|
export declare function resolveGaiaConfigPath(opts?: {
|
|
47
92
|
cwd?: string;
|
package/dist/src/cli/gaia-dir.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { declaresTopLevelKey } from './config-source.js';
|
|
4
5
|
import { homeFallbackGaiaConfigPath } from './paths.js';
|
|
5
6
|
// GAIA-201: the pure `.gaia/` walk-up, hoisted from the conductor's `config.ts`.
|
|
6
7
|
// Two config kinds now live under `.gaia/`:
|
|
@@ -11,6 +12,16 @@ import { homeFallbackGaiaConfigPath } from './paths.js';
|
|
|
11
12
|
// `conductor.config.js` still carrying `site`/`plugins` is accepted
|
|
12
13
|
// as a connection source (back-compat) when no `gaia.config.js` is
|
|
13
14
|
// found, so an un-migrated repo keeps working.
|
|
15
|
+
//
|
|
16
|
+
// GAIA-230 (root cause 2): that back-compat read is gated on CONTENT, not on file
|
|
17
|
+
// existence. `gaia upgrade` step 0 (GAIA-218 AC-7) deliberately STRIPS
|
|
18
|
+
// `site`/`plugins` from every engine config, and step 1 defaults the project
|
|
19
|
+
// connection override to skip — because the global `~/.gaia/gaia.config.js` is the
|
|
20
|
+
// default connection every repo inherits. An existence-only read let the
|
|
21
|
+
// engine-only file it leaves behind keep precedence 2 and shadow that inherited
|
|
22
|
+
// default, so `loadGaiaConfig` died on `requires site.base_url`. A
|
|
23
|
+
// `conductor.config.js` declaring no top-level `site` is therefore NOT a
|
|
24
|
+
// connection source and falls through to the home config.
|
|
14
25
|
/** A repo's config files live in this dir, one conductor config per conductor. */
|
|
15
26
|
const GAIA_DIR = '.gaia';
|
|
16
27
|
/** The default conductor's file name; its stem is `conductor`. */
|
|
@@ -40,6 +51,14 @@ function configStems(gaiaDir) {
|
|
|
40
51
|
function fileForStem(stem) {
|
|
41
52
|
return stem === 'conductor' ? DEFAULT_CONFIG : `${stem}${VARIANT_SUFFIX}`;
|
|
42
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The user-global `~/.gaia` dir. Core owns where the split-config files live,
|
|
56
|
+
* so a consumer that must recognise the home dir (e.g. to exclude it from a
|
|
57
|
+
* project walk-up, GAIA-219 AC-9) asks for it rather than joining it itself.
|
|
58
|
+
*/
|
|
59
|
+
export function homeGaiaDir(home = homedir()) {
|
|
60
|
+
return join(home, GAIA_DIR);
|
|
61
|
+
}
|
|
43
62
|
/**
|
|
44
63
|
* Walk from `cwd` root-ward (git/eslint style) to the nearest ancestor whose
|
|
45
64
|
* `.gaia/` dir holds at least one conductor config; return that `.gaia/` dir, or
|
|
@@ -98,10 +117,28 @@ export function resolveConfigPath(override, cwd = process.cwd(), conductorName)
|
|
|
98
117
|
throw new Error(`${stems.length} conductors in ${gaiaDir} (${stems.join(', ')}); ` +
|
|
99
118
|
'select one with --conductor <name> or $GAIA_CONDUCTOR');
|
|
100
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* GAIA-230: is this `conductor.config.js` a legacy CONNECTION source, i.e. does
|
|
122
|
+
* its source still declare a top-level `site`? A pure source read — a config may
|
|
123
|
+
* import a secret-bearing machine context, and this resolver is sync and must
|
|
124
|
+
* never execute the module. An unreadable file, or one whose default export is
|
|
125
|
+
* not an object literal, is not a connection source.
|
|
126
|
+
*/
|
|
127
|
+
function declaresConnection(path) {
|
|
128
|
+
try {
|
|
129
|
+
return declaresTopLevelKey(readFileSync(path, 'utf8'), 'site');
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
101
135
|
/**
|
|
102
136
|
* Walk root-ward for the nearest `.gaia/` connection config: prefer
|
|
103
137
|
* `gaia.config.js`; fall back to a legacy `conductor.config.js` in the same dir
|
|
104
|
-
*
|
|
138
|
+
* **only when it really declares a connection** (a top-level `site` — GAIA-230).
|
|
139
|
+
* An engine-only `conductor.config.js` is skipped, and the walk continues, so the
|
|
140
|
+
* home `~/.gaia/gaia.config.js` every repo inherits is genuinely reachable.
|
|
141
|
+
* Returns `undefined` when no connection source is found up the tree.
|
|
105
142
|
*/
|
|
106
143
|
export function findGaiaConfig(cwd) {
|
|
107
144
|
let dir = resolve(cwd);
|
|
@@ -112,8 +149,41 @@ export function findGaiaConfig(cwd) {
|
|
|
112
149
|
if (existsSync(gaiaCfg))
|
|
113
150
|
return { path: gaiaCfg, legacy: false };
|
|
114
151
|
const legacy = join(gaiaDir, DEFAULT_CONFIG);
|
|
115
|
-
if (existsSync(legacy))
|
|
152
|
+
if (existsSync(legacy) && declaresConnection(legacy)) {
|
|
116
153
|
return { path: legacy, legacy: true };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const parent = dirname(dir);
|
|
157
|
+
if (parent === dir)
|
|
158
|
+
return undefined;
|
|
159
|
+
dir = parent;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Walk root-ward for the nearest `.gaia/` dir that marks a **project** — one
|
|
164
|
+
* holding any gaia config at all, a `gaia.config.js` or at least one conductor
|
|
165
|
+
* config. Returns that `.gaia/` dir, or `undefined` up to the filesystem root.
|
|
166
|
+
*
|
|
167
|
+
* Deliberately a different question from `findGaiaConfig` (GAIA-219 AC-9):
|
|
168
|
+
* "which project am I standing in" is not "where does my connection come from".
|
|
169
|
+
* Since GAIA-230 the connection walk-up skips an engine-only
|
|
170
|
+
* `conductor.config.js`, and GAIA-218 makes exactly that the canonical repo
|
|
171
|
+
* shape — so keying project detection on the connection would stop recognising
|
|
172
|
+
* the most common project as a project. It is content-agnostic for the same
|
|
173
|
+
* reason `findGaiaDir` is: a `.gaia/` dir is the marker.
|
|
174
|
+
*
|
|
175
|
+
* It is a superset of `findGaiaDir`, which requires a *conductor* config
|
|
176
|
+
* because it resolves the engine config; a repo carrying only a project
|
|
177
|
+
* connection override is a project here and not there.
|
|
178
|
+
*/
|
|
179
|
+
export function findProjectGaiaDir(cwd) {
|
|
180
|
+
let dir = resolve(cwd);
|
|
181
|
+
for (;;) {
|
|
182
|
+
const gaiaDir = join(dir, GAIA_DIR);
|
|
183
|
+
if (existsSync(gaiaDir) &&
|
|
184
|
+
(existsSync(join(gaiaDir, GAIA_CONFIG)) ||
|
|
185
|
+
configStems(gaiaDir).length > 0)) {
|
|
186
|
+
return gaiaDir;
|
|
117
187
|
}
|
|
118
188
|
const parent = dirname(dir);
|
|
119
189
|
if (parent === dir)
|
|
@@ -124,29 +194,49 @@ export function findGaiaConfig(cwd) {
|
|
|
124
194
|
/**
|
|
125
195
|
* Resolve the CONNECTION (`gaia.config.js`) config path. Precedence (AC-4):
|
|
126
196
|
* 1. `explicit` (`--config` / `$GAIA_CONFIG` / `$DROPSH_CONFIG`) wins verbatim.
|
|
127
|
-
* 2. Project `./.gaia/gaia.config.js` found by walk-up (legacy
|
|
128
|
-
* `conductor.config.js` accepted as a connection source
|
|
197
|
+
* 2. Project `./.gaia/gaia.config.js` found by walk-up (a legacy
|
|
198
|
+
* `conductor.config.js` accepted as a connection source only when it declares
|
|
199
|
+
* a top-level `site` — GAIA-230).
|
|
129
200
|
* 3. Home `~/.gaia/gaia.config.js` when present.
|
|
130
201
|
* 4. The shipped fallback (`homeFallbackGaiaConfigPath`, reads the machine context).
|
|
202
|
+
*
|
|
203
|
+
* `source` (GAIA-219 D4) names the leg that won. One correction applies **to
|
|
204
|
+
* the project leg only**: the walk-up in step 2 climbs to the filesystem root,
|
|
205
|
+
* so from any cwd under `$HOME` it reaches `~/.gaia/gaia.config.js` and would
|
|
206
|
+
* return it as `project`. That is the same file step 3 would return, so
|
|
207
|
+
* resolution is unaffected — but the label would be a lie, hence step 2
|
|
208
|
+
* re-labels the home candidate `home`. An **explicit** path that happens to
|
|
209
|
+
* name the home config still reports `explicit`: there the user did choose it
|
|
210
|
+
* by name, and which leg won is exactly what the report is for.
|
|
131
211
|
*/
|
|
132
212
|
export function resolveGaiaConfigPath(opts = {}) {
|
|
213
|
+
const homeCfg = join(homeGaiaDir(opts.home), GAIA_CONFIG);
|
|
133
214
|
const explicit = opts.explicit;
|
|
134
215
|
if (explicit !== undefined && explicit.trim() !== '') {
|
|
135
|
-
return {
|
|
216
|
+
return {
|
|
217
|
+
path: explicit,
|
|
218
|
+
fallback: false,
|
|
219
|
+
legacy: false,
|
|
220
|
+
source: 'explicit',
|
|
221
|
+
};
|
|
136
222
|
}
|
|
137
223
|
const cwd = opts.cwd ?? process.cwd();
|
|
138
224
|
const project = findGaiaConfig(cwd);
|
|
139
225
|
if (project !== undefined) {
|
|
140
|
-
return {
|
|
226
|
+
return {
|
|
227
|
+
path: project.path,
|
|
228
|
+
fallback: false,
|
|
229
|
+
legacy: project.legacy,
|
|
230
|
+
source: project.path === homeCfg ? 'home' : 'project',
|
|
231
|
+
};
|
|
141
232
|
}
|
|
142
|
-
const home = opts.home ?? homedir();
|
|
143
|
-
const homeCfg = join(home, GAIA_DIR, GAIA_CONFIG);
|
|
144
233
|
if (existsSync(homeCfg)) {
|
|
145
|
-
return { path: homeCfg, fallback: false, legacy: false };
|
|
234
|
+
return { path: homeCfg, fallback: false, legacy: false, source: 'home' };
|
|
146
235
|
}
|
|
147
236
|
return {
|
|
148
237
|
path: opts.shippedFallback ?? homeFallbackGaiaConfigPath(),
|
|
149
238
|
fallback: true,
|
|
150
239
|
legacy: false,
|
|
240
|
+
source: 'fallback',
|
|
151
241
|
};
|
|
152
242
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DropSHPlugin } from 'dropsh/plugin';
|
|
2
2
|
import type { GaiaCommandHost } from './commands.js';
|
|
3
|
+
import { type GaiaConfigSource } from './gaia-dir.js';
|
|
3
4
|
/** The resolved control-plane connection: site + constructed auth plugins. */
|
|
4
5
|
export interface GaiaConnectionConfig {
|
|
5
6
|
site: {
|
|
@@ -11,6 +12,10 @@ export interface GaiaConnectionConfig {
|
|
|
11
12
|
config_path: string;
|
|
12
13
|
/** true when this is the shipped machine-context fallback. */
|
|
13
14
|
fallback: boolean;
|
|
15
|
+
/** Which leg of the precedence produced `config_path` (GAIA-219 D4). */
|
|
16
|
+
source: GaiaConfigSource;
|
|
17
|
+
/** true when the loaded file is a legacy `conductor.config.js` connection. */
|
|
18
|
+
legacy: boolean;
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
21
|
* Load the connection config for `gaia ui` / `gaia dropsh` / conductor-auth.
|
|
@@ -80,7 +80,14 @@ export async function loadGaiaConfig(host, opts = {}) {
|
|
|
80
80
|
const site = isRecord(raw.site) ? raw.site : {};
|
|
81
81
|
const baseUrl = typeof site.base_url === 'string' ? site.base_url : '';
|
|
82
82
|
if (baseUrl.trim() === '') {
|
|
83
|
-
|
|
83
|
+
// GAIA-218 (AC-9): a legible failure that names the resolved connection path
|
|
84
|
+
// and the file that was expected to supply `site.base_url`, so a mis-resolved
|
|
85
|
+
// or empty connection is diagnosable — never a downstream plugin error (auth
|
|
86
|
+
// no longer lives in the engine config, so no plugin resolution runs here).
|
|
87
|
+
const expected = resolution.fallback
|
|
88
|
+
? 'the shipped machine-context fallback resolved no base_url — run `gaia conductor init` or create ~/.gaia/gaia.config.js'
|
|
89
|
+
: 'expected a gaia.config.js (project ./.gaia/gaia.config.js or home ~/.gaia/gaia.config.js) declaring site.base_url';
|
|
90
|
+
throw new Error(`gaia connection config requires site.base_url — resolved ${configPath}; ${expected}`);
|
|
84
91
|
}
|
|
85
92
|
const jsonapiPrefix = typeof site.jsonapi_prefix === 'string' && site.jsonapi_prefix.trim() !== ''
|
|
86
93
|
? site.jsonapi_prefix
|
|
@@ -106,5 +113,7 @@ export async function loadGaiaConfig(host, opts = {}) {
|
|
|
106
113
|
plugins,
|
|
107
114
|
config_path: configPath,
|
|
108
115
|
fallback: resolution.fallback,
|
|
116
|
+
source: resolution.source,
|
|
117
|
+
legacy: resolution.legacy,
|
|
109
118
|
};
|
|
110
119
|
}
|
package/dist/src/core/exec.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export declare class CommandRunner {
|
|
|
28
28
|
/**
|
|
29
29
|
* Replace the module-level singleton runner used by {@link exec}.
|
|
30
30
|
*
|
|
31
|
-
* Note: out-of-package plugins (e.g. `@gaia-ai/
|
|
31
|
+
* Note: out-of-package plugins (e.g. `@gaia-ai/addon-herdr`) import `exec` via
|
|
32
32
|
* the `@gaia-ai/gaia/plugin` barrel, which resolves to the built `dist/`
|
|
33
33
|
* copy of this module. This means `setDefaultCommandRunner` only shares the
|
|
34
34
|
* runner with those plugins when the conductor itself runs from `dist`
|
package/dist/src/core/exec.js
CHANGED
|
@@ -64,7 +64,7 @@ let defaultRunner = new CommandRunner(noopLogger);
|
|
|
64
64
|
/**
|
|
65
65
|
* Replace the module-level singleton runner used by {@link exec}.
|
|
66
66
|
*
|
|
67
|
-
* Note: out-of-package plugins (e.g. `@gaia-ai/
|
|
67
|
+
* Note: out-of-package plugins (e.g. `@gaia-ai/addon-herdr`) import `exec` via
|
|
68
68
|
* the `@gaia-ai/gaia/plugin` barrel, which resolves to the built `dist/`
|
|
69
69
|
* copy of this module. This means `setDefaultCommandRunner` only shares the
|
|
70
70
|
* runner with those plugins when the conductor itself runs from `dist`
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type { GaiaCommandHost, GaiaCommandPlugin } from './cli/commands.js';
|
|
2
|
-
export type
|
|
3
|
-
export {
|
|
2
|
+
export { declaresTopLevelKey, findDefaultExportObject, matchDelimiter, type PropEntry, readKey, scanTopLevelProperties, scanToTopLevelComma, skipBlockComment, skipLineComment, skipString, skipTrivia, } from './cli/config-source.js';
|
|
3
|
+
export type { GaiaConfigResolution, GaiaConfigSource, } from './cli/gaia-dir.js';
|
|
4
|
+
export { findGaiaConfig, findGaiaDir, findProjectGaiaDir, homeGaiaDir, resolveConfigPath, resolveGaiaConfigPath, } from './cli/gaia-dir.js';
|
|
4
5
|
export { type GaiaConnectionConfig, loadGaiaConfig, } from './cli/load-gaia-config.js';
|
|
5
6
|
export { legacyMachineContextPath, type MachineContext, machineContextPath, readMachineContext, resolveMachineContextPath, } from './cli/machine-context.js';
|
|
6
7
|
export { corePackageRoot, homeFallbackGaiaConfigPath, } from './cli/paths.js';
|
|
@@ -11,14 +12,6 @@ export { CommandRunner, ExecError, exec, setDefaultCommandRunner, } from './core
|
|
|
11
12
|
export { type ConductorLogger, createLogger } from './core/logger.js';
|
|
12
13
|
export { shellQuote } from './core/shell.js';
|
|
13
14
|
export { DEFAULT_SLUG_MAX_LENGTH, slugify } from './core/slug.js';
|
|
14
|
-
export { type AgentFootprint, emptyAgentFootprint, type GaiaAgent, } from './plugins/agent/agent.js';
|
|
15
15
|
export { discoverAddons, type ResolvedConductorSlots, resolveConductorSlots, } from './plugins/discover-addons.js';
|
|
16
|
-
export type
|
|
17
|
-
export type { AgentCandidate, AgentPlugin, ExecutorDeps, ExecutorPlugin, RemotePlugin, ResolvedAgent, WorkspacePlugin, } from './plugins/plugins.js';
|
|
18
|
-
export { selectAgent, selectAgents } from './plugins/plugins.js';
|
|
19
|
-
export { type AddonEntry, type CommandDescriptor, type DiscoveredContributions, emptyContributions, type GaiaPreset, type GaiaSurface, PRESET_FUNCTION_KEYS, SURFACE_KEYS, } from './plugins/preset.js';
|
|
20
|
-
export type * from './plugins/remote/remote.js';
|
|
21
|
-
export { loadInstructions } from './plugins/workspace/instructions.js';
|
|
22
|
-
export type { EnsuredWorkspace, GaiaWorkspace, } from './plugins/workspace/workspace.js';
|
|
23
|
-
export type * from './types.js';
|
|
16
|
+
export { type AddonEntry, type CommandDescriptor, type DiscoveredContributions, emptyContributions, type GaiaPreset, type GaiaSurface, type OpaqueAccumulator, PRESET_FUNCTION_KEYS, SURFACE_KEYS, } from './plugins/preset.js';
|
|
24
17
|
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,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
// GAIA-230: the comment-aware config-SOURCE scanner. Owned by the kernel so the
|
|
2
|
+
// content-gated legacy connection read (`findGaiaConfig`) and the conductor's
|
|
3
|
+
// GAIA-218 strip pass share one answer to "which top-level properties does this
|
|
4
|
+
// config source declare?" — core is layer 0 and cannot import the conductor's copy.
|
|
5
|
+
export { declaresTopLevelKey, findDefaultExportObject, matchDelimiter, readKey, scanTopLevelProperties, scanToTopLevelComma, skipBlockComment, skipLineComment, skipString, skipTrivia, } from './cli/config-source.js';
|
|
6
|
+
export { findGaiaConfig, findGaiaDir, findProjectGaiaDir, homeGaiaDir, resolveConfigPath, resolveGaiaConfigPath, } from './cli/gaia-dir.js';
|
|
2
7
|
export { loadGaiaConfig, } from './cli/load-gaia-config.js';
|
|
3
8
|
export { legacyMachineContextPath, machineContextPath, readMachineContext, resolveMachineContextPath, } from './cli/machine-context.js';
|
|
4
9
|
export { corePackageRoot, homeFallbackGaiaConfigPath, } from './cli/paths.js';
|
|
@@ -9,14 +14,20 @@ export { CommandRunner, ExecError, exec, setDefaultCommandRunner, } from './core
|
|
|
9
14
|
export { createLogger } from './core/logger.js';
|
|
10
15
|
export { shellQuote } from './core/shell.js';
|
|
11
16
|
export { DEFAULT_SLUG_MAX_LENGTH, slugify } from './core/slug.js';
|
|
12
|
-
export { emptyAgentFootprint, } from './plugins/agent/agent.js';
|
|
13
17
|
// GAIA-215: the Storybook-style preset contract + the shared addon discovery.
|
|
18
|
+
// GAIA-224 (Finding 6): SURFACE-AGNOSTIC only. The conductor-surface interfaces
|
|
19
|
+
// (`GaiaRemote`/`GaiaExecutor`/`GaiaWorkspace`/`GaiaAgent`, the four `*Plugin`
|
|
20
|
+
// interfaces, `ExecutorDeps`, `AgentCandidate`/`ResolvedAgent`, the `select*`
|
|
21
|
+
// selectors), the config contract (`ConductorSettings`/`ConductorFileConfig`/
|
|
22
|
+
// `ConductorEngineConfig`) and every built-in IMPLEMENTATION have moved out of
|
|
23
|
+
// this kernel — the contract to `@gaia-ai/conductor` (subpath
|
|
24
|
+
// `@gaia-ai/conductor/contract`), the impls to their own addons
|
|
25
|
+
// (`@gaia-ai/addon-remote-drupal`, `@gaia-ai/addon-workspace-git`,
|
|
26
|
+
// `@gaia-ai/addon-auth-basic`, `@gaia-ai/addon-fake`). `loadInstructions` moved to
|
|
27
|
+
// `@gaia-ai/addon-workspace-git`. Core ships NO plugin implementations.
|
|
14
28
|
export { discoverAddons, resolveConductorSlots, } from './plugins/discover-addons.js';
|
|
15
|
-
export { selectAgent, selectAgents } from './plugins/plugins.js';
|
|
16
29
|
export { emptyContributions, PRESET_FUNCTION_KEYS, SURFACE_KEYS, } from './plugins/preset.js';
|
|
17
30
|
// GAIA-194 AC-2: the agent-host contract (`AgentLaunchHost` / `HostedAgent` /
|
|
18
|
-
// `supportsAgentHost`)
|
|
19
|
-
// seam (`@gaia-ai/
|
|
20
|
-
// the conductor engine contract only.
|
|
21
|
-
export { loadInstructions } from './plugins/workspace/instructions.js';
|
|
31
|
+
// `supportsAgentHost`) is likewise NOT here — it is the TUI renderer's own plugin
|
|
32
|
+
// seam (`@gaia-ai/addon-gaia-ui`), and herdr types its impl locally.
|
|
22
33
|
export { expandLoad, parseStepValues, readSkillWhen, StepContractError, validateLoad, WORKFLOW_STEPS, } from './workflow/step-contract.js';
|