@gaia-ai/ui 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/bun-runtime.d.ts +84 -0
- package/dist/src/bun-runtime.js +149 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/ui-home.d.ts +24 -18
- package/dist/src/ui-home.js +16 -35
- package/dist/src/ui.d.ts +12 -4
- package/dist/src/ui.js +75 -17
- package/package.json +3 -3
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GAIA-326 — `@opentui/core` reaches its Zig core through FFI, which Node does
|
|
3
|
+
* not expose before 26.1 (and then only behind `--experimental-ffi`). So the
|
|
4
|
+
* cockpit runs under the `bun` that `@gaia-ai/addon-gaia-ui` installs.
|
|
5
|
+
*
|
|
6
|
+
* `process.versions.bun` is the loop guard: it is defined under Bun and nowhere
|
|
7
|
+
* else, so a re-exec can never recurse and no sentinel variable is introduced.
|
|
8
|
+
* `--print-config` deliberately stays on Node — it is what a human runs when the
|
|
9
|
+
* cockpit will not start, and a diagnostic that needs the thing it diagnoses is
|
|
10
|
+
* worth nothing.
|
|
11
|
+
*
|
|
12
|
+
* `injectedRenderer` is the same argument one step further: the runtime is
|
|
13
|
+
* needed by the DYNAMICALLY-resolved `@gaia-ai/addon-gaia-ui`, so a caller that
|
|
14
|
+
* supplies its own `runUi` never reaches OpenTUI and must not be re-execed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function shouldReExecUnderBun(input: {
|
|
17
|
+
bunVersion?: string | undefined;
|
|
18
|
+
noBun?: string | undefined;
|
|
19
|
+
printConfig: boolean;
|
|
20
|
+
injectedRenderer?: boolean | undefined;
|
|
21
|
+
}): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the `bun` executable from the renderer addon's own base — the same
|
|
24
|
+
* dynamic seam `loadGaiaUiModule` resolves the renderer through, so the one
|
|
25
|
+
* dynamic edge in this package stays one edge.
|
|
26
|
+
*
|
|
27
|
+
* The manifest is read with a plain `readFileSync` rather than through the
|
|
28
|
+
* resolver: `createRequire(...).resolve()` gives an absolute path, and reading a
|
|
29
|
+
* known path needs no module system. `bun`'s manifest is resolved by name
|
|
30
|
+
* (`bun/package.json`) because the launcher's location is the thing we are
|
|
31
|
+
* looking up; its `bin` entry is a relative path from the manifest's directory.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveBunBinary(bases: string[]): string | undefined;
|
|
34
|
+
export type LibcFamily = 'glibc' | 'musl' | 'unknown';
|
|
35
|
+
/**
|
|
36
|
+
* GAIA-326 (A1) — which C library is this host running?
|
|
37
|
+
*
|
|
38
|
+
* Two evidence sources, cheapest first, both injected so the answer never
|
|
39
|
+
* depends on the machine the test runs on:
|
|
40
|
+
*
|
|
41
|
+
* - `/proc/self/maps` (~0.1 ms) names the interpreter the kernel ACTUALLY
|
|
42
|
+
* mapped into this process. That is the strongest signal available: a glibc
|
|
43
|
+
* box with a musl-compat package installed has `/lib/ld-musl-x86_64.so.1` on
|
|
44
|
+
* disk but never maps it, so the `existsSync` shortcut would misread it.
|
|
45
|
+
* - the node report's `header.glibcVersionRuntime` (~4.6 ms) — present on
|
|
46
|
+
* glibc, absent on musl. It needs no filesystem, which is why it is the
|
|
47
|
+
* fallback for a container that hides `/proc`. Verified on all four
|
|
48
|
+
* combinations: node+glibc `"2.43"`, bun+glibc `"2.43"`, node+musl absent,
|
|
49
|
+
* bun+musl absent (node:22-alpine).
|
|
50
|
+
*
|
|
51
|
+
* Non-Linux returns `unknown` and NOT `musl`: the report header omits
|
|
52
|
+
* `glibcVersionRuntime` on macOS and Windows too, so reading its absence as
|
|
53
|
+
* musl would mislabel every mac. musl is a Linux question.
|
|
54
|
+
*
|
|
55
|
+
* Inconclusive is `unknown`, and `unknown` changes nothing — glibc is upstream's
|
|
56
|
+
* default and the overwhelmingly common case, so a failed probe must degrade to
|
|
57
|
+
* today's behaviour rather than to a guess.
|
|
58
|
+
*/
|
|
59
|
+
export declare function detectLibcFamily(probe: {
|
|
60
|
+
platform: string;
|
|
61
|
+
readProcSelfMaps: () => string | undefined;
|
|
62
|
+
readReportLibc: () => LibcFamily;
|
|
63
|
+
}): LibcFamily;
|
|
64
|
+
/** The real probe. Every leg is wrapped: an inconclusive read must not throw. */
|
|
65
|
+
export declare function nodeLibcFamily(): LibcFamily;
|
|
66
|
+
/**
|
|
67
|
+
* GAIA-326 (A1) — make the cockpit start on musl with no manual step.
|
|
68
|
+
*
|
|
69
|
+
* `@opentui/core@0.5.1` selects its native platform package from
|
|
70
|
+
* `process.env.OPENTUI_LIBC` and DEFAULTS TO GLIBC — it runs no probe of its
|
|
71
|
+
* own. Both prebuilts are always on disk (npm and pnpm filter optional deps by
|
|
72
|
+
* `os`/`cpu`, never by libc), so on Alpine the wrong one loads and the first
|
|
73
|
+
* frame dies with `Error loading shared library ld-linux-x86-64.so.2 (needed by
|
|
74
|
+
* libopentui.so)`. Requiring the user to export the variable IS a manual step,
|
|
75
|
+
* so A1 is only met once we set it.
|
|
76
|
+
*
|
|
77
|
+
* An explicit value always wins: an operator who exports `OPENTUI_LIBC=glibc`
|
|
78
|
+
* on a musl box is doing something deliberate, and a probe must not overrule
|
|
79
|
+
* them. Because a set value short-circuits BEFORE `detect` is called, the Bun
|
|
80
|
+
* child — which inherits this env — never pays for the probe a second time.
|
|
81
|
+
*/
|
|
82
|
+
export declare function applyOpentuiLibc(env: NodeJS.ProcessEnv, detect?: () => LibcFamily): void;
|
|
83
|
+
/** Replace this process with the same command under Bun. Never returns. */
|
|
84
|
+
export declare function reExecUnderBun(bin: string, argv: string[]): never;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* GAIA-326 — `@opentui/core` reaches its Zig core through FFI, which Node does
|
|
7
|
+
* not expose before 26.1 (and then only behind `--experimental-ffi`). So the
|
|
8
|
+
* cockpit runs under the `bun` that `@gaia-ai/addon-gaia-ui` installs.
|
|
9
|
+
*
|
|
10
|
+
* `process.versions.bun` is the loop guard: it is defined under Bun and nowhere
|
|
11
|
+
* else, so a re-exec can never recurse and no sentinel variable is introduced.
|
|
12
|
+
* `--print-config` deliberately stays on Node — it is what a human runs when the
|
|
13
|
+
* cockpit will not start, and a diagnostic that needs the thing it diagnoses is
|
|
14
|
+
* worth nothing.
|
|
15
|
+
*
|
|
16
|
+
* `injectedRenderer` is the same argument one step further: the runtime is
|
|
17
|
+
* needed by the DYNAMICALLY-resolved `@gaia-ai/addon-gaia-ui`, so a caller that
|
|
18
|
+
* supplies its own `runUi` never reaches OpenTUI and must not be re-execed.
|
|
19
|
+
*/
|
|
20
|
+
export function shouldReExecUnderBun(input) {
|
|
21
|
+
if (input.printConfig)
|
|
22
|
+
return false;
|
|
23
|
+
if (input.injectedRenderer)
|
|
24
|
+
return false;
|
|
25
|
+
if (input.bunVersion !== undefined)
|
|
26
|
+
return false;
|
|
27
|
+
if (input.noBun !== undefined && input.noBun !== '')
|
|
28
|
+
return false;
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the `bun` executable from the renderer addon's own base — the same
|
|
33
|
+
* dynamic seam `loadGaiaUiModule` resolves the renderer through, so the one
|
|
34
|
+
* dynamic edge in this package stays one edge.
|
|
35
|
+
*
|
|
36
|
+
* The manifest is read with a plain `readFileSync` rather than through the
|
|
37
|
+
* resolver: `createRequire(...).resolve()` gives an absolute path, and reading a
|
|
38
|
+
* known path needs no module system. `bun`'s manifest is resolved by name
|
|
39
|
+
* (`bun/package.json`) because the launcher's location is the thing we are
|
|
40
|
+
* looking up; its `bin` entry is a relative path from the manifest's directory.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveBunBinary(bases) {
|
|
43
|
+
for (const base of bases) {
|
|
44
|
+
try {
|
|
45
|
+
const addon = createRequire(base).resolve('@gaia-ai/addon-gaia-ui');
|
|
46
|
+
const manifest = createRequire(addon).resolve('bun/package.json');
|
|
47
|
+
const { bin } = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
48
|
+
const relative = typeof bin === 'string' ? bin : bin?.bun;
|
|
49
|
+
if (relative)
|
|
50
|
+
return join(dirname(manifest), relative);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// try the next base
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The dynamic linker musl installs. Matched as a path segment ending in `.so`
|
|
60
|
+
* rather than a bare `includes('ld-musl')`, so an unrelated mapped file whose
|
|
61
|
+
* NAME happens to carry the string cannot be mistaken for the interpreter.
|
|
62
|
+
*/
|
|
63
|
+
const MUSL_INTERPRETER = /\/ld-musl-[^/\s]*\.so/;
|
|
64
|
+
/**
|
|
65
|
+
* GAIA-326 (A1) — which C library is this host running?
|
|
66
|
+
*
|
|
67
|
+
* Two evidence sources, cheapest first, both injected so the answer never
|
|
68
|
+
* depends on the machine the test runs on:
|
|
69
|
+
*
|
|
70
|
+
* - `/proc/self/maps` (~0.1 ms) names the interpreter the kernel ACTUALLY
|
|
71
|
+
* mapped into this process. That is the strongest signal available: a glibc
|
|
72
|
+
* box with a musl-compat package installed has `/lib/ld-musl-x86_64.so.1` on
|
|
73
|
+
* disk but never maps it, so the `existsSync` shortcut would misread it.
|
|
74
|
+
* - the node report's `header.glibcVersionRuntime` (~4.6 ms) — present on
|
|
75
|
+
* glibc, absent on musl. It needs no filesystem, which is why it is the
|
|
76
|
+
* fallback for a container that hides `/proc`. Verified on all four
|
|
77
|
+
* combinations: node+glibc `"2.43"`, bun+glibc `"2.43"`, node+musl absent,
|
|
78
|
+
* bun+musl absent (node:22-alpine).
|
|
79
|
+
*
|
|
80
|
+
* Non-Linux returns `unknown` and NOT `musl`: the report header omits
|
|
81
|
+
* `glibcVersionRuntime` on macOS and Windows too, so reading its absence as
|
|
82
|
+
* musl would mislabel every mac. musl is a Linux question.
|
|
83
|
+
*
|
|
84
|
+
* Inconclusive is `unknown`, and `unknown` changes nothing — glibc is upstream's
|
|
85
|
+
* default and the overwhelmingly common case, so a failed probe must degrade to
|
|
86
|
+
* today's behaviour rather than to a guess.
|
|
87
|
+
*/
|
|
88
|
+
export function detectLibcFamily(probe) {
|
|
89
|
+
if (probe.platform !== 'linux')
|
|
90
|
+
return 'unknown';
|
|
91
|
+
const maps = probe.readProcSelfMaps();
|
|
92
|
+
if (maps !== undefined)
|
|
93
|
+
return MUSL_INTERPRETER.test(maps) ? 'musl' : 'glibc';
|
|
94
|
+
return probe.readReportLibc();
|
|
95
|
+
}
|
|
96
|
+
/** The real probe. Every leg is wrapped: an inconclusive read must not throw. */
|
|
97
|
+
export function nodeLibcFamily() {
|
|
98
|
+
return detectLibcFamily({
|
|
99
|
+
platform: process.platform,
|
|
100
|
+
readProcSelfMaps: () => {
|
|
101
|
+
try {
|
|
102
|
+
return readFileSync('/proc/self/maps', 'utf8');
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
readReportLibc: () => {
|
|
109
|
+
try {
|
|
110
|
+
const report = process.report.getReport();
|
|
111
|
+
return report.header?.glibcVersionRuntime ? 'glibc' : 'musl';
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return 'unknown';
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* GAIA-326 (A1) — make the cockpit start on musl with no manual step.
|
|
121
|
+
*
|
|
122
|
+
* `@opentui/core@0.5.1` selects its native platform package from
|
|
123
|
+
* `process.env.OPENTUI_LIBC` and DEFAULTS TO GLIBC — it runs no probe of its
|
|
124
|
+
* own. Both prebuilts are always on disk (npm and pnpm filter optional deps by
|
|
125
|
+
* `os`/`cpu`, never by libc), so on Alpine the wrong one loads and the first
|
|
126
|
+
* frame dies with `Error loading shared library ld-linux-x86-64.so.2 (needed by
|
|
127
|
+
* libopentui.so)`. Requiring the user to export the variable IS a manual step,
|
|
128
|
+
* so A1 is only met once we set it.
|
|
129
|
+
*
|
|
130
|
+
* An explicit value always wins: an operator who exports `OPENTUI_LIBC=glibc`
|
|
131
|
+
* on a musl box is doing something deliberate, and a probe must not overrule
|
|
132
|
+
* them. Because a set value short-circuits BEFORE `detect` is called, the Bun
|
|
133
|
+
* child — which inherits this env — never pays for the probe a second time.
|
|
134
|
+
*/
|
|
135
|
+
export function applyOpentuiLibc(env, detect = nodeLibcFamily) {
|
|
136
|
+
const current = env.OPENTUI_LIBC;
|
|
137
|
+
if (current !== undefined && current !== '')
|
|
138
|
+
return;
|
|
139
|
+
if (detect() === 'musl')
|
|
140
|
+
env.OPENTUI_LIBC = 'musl';
|
|
141
|
+
}
|
|
142
|
+
/** Replace this process with the same command under Bun. Never returns. */
|
|
143
|
+
export function reExecUnderBun(bin, argv) {
|
|
144
|
+
const result = spawnSync(bin, argv.slice(1), {
|
|
145
|
+
stdio: 'inherit',
|
|
146
|
+
env: process.env,
|
|
147
|
+
});
|
|
148
|
+
process.exit(result.status ?? 1);
|
|
149
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { type HereContext, isTicketIdentifier, isUuid, normalizeRepoUrl, type ParsedTarget, parseTarget, parseTicketIdentifierFromBranch, resolveHere, type TargetInputs, UI_VIEW_MODES, type UiInitialRoute, type UiViewMode, } from './route.js';
|
|
2
2
|
export { cmdUi, default, type RunGaiaUiOptions, type UiDeps } from './ui.js';
|
|
3
|
-
export { type HomeConnection, type ProjectRooting, resolveAgentCommand,
|
|
3
|
+
export { type HomeConnection, type ProjectRooting, resolveAgentCommand, resolveProjectRooting, } from './ui-home.js';
|
package/dist/src/index.js
CHANGED
|
@@ -7,4 +7,4 @@
|
|
|
7
7
|
// GAIA-223: the deep-link grammar (the pure CLI seam).
|
|
8
8
|
export { isTicketIdentifier, isUuid, normalizeRepoUrl, parseTarget, parseTicketIdentifierFromBranch, resolveHere, UI_VIEW_MODES, } from './route.js';
|
|
9
9
|
export { cmdUi, default } from './ui.js';
|
|
10
|
-
export { resolveAgentCommand,
|
|
10
|
+
export { resolveAgentCommand, resolveProjectRooting, } from './ui-home.js';
|
package/dist/src/ui-home.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type MachineContext } from '@gaia-ai/core';
|
|
2
2
|
/**
|
|
3
3
|
* The resolved control-plane connection passed to the renderer launcher.
|
|
4
4
|
* A structural mirror of `@gaia-ai/addon-gaia-ui`'s `HomeConnection` — kept
|
|
@@ -20,12 +20,6 @@ export interface HomeConnection {
|
|
|
20
20
|
*/
|
|
21
21
|
writeProfile?: string | undefined;
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Resolve the default project (opaque group) from the conductor registry: the
|
|
25
|
-
* `preferred` project when it is registered, else the first entry's project.
|
|
26
|
-
* `undefined` when the registry is empty — a valid empty cockpit.
|
|
27
|
-
*/
|
|
28
|
-
export declare function resolveDefaultProject(entries: ConductorRegistryEntry[], preferred?: string): string | undefined;
|
|
29
23
|
/**
|
|
30
24
|
* Resolve the agent-launch command builder from the home config. Turns a TUI
|
|
31
25
|
* prompt into the shell command herdr runs. Defaults to `claude` (an empty
|
|
@@ -38,11 +32,18 @@ export declare function resolveAgentCommand(machine: Partial<MachineContext> & {
|
|
|
38
32
|
/** Where `gaia ui` roots the agents it launches, and which project they belong to. */
|
|
39
33
|
export interface ProjectRooting {
|
|
40
34
|
cwd: string;
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
/**
|
|
36
|
+
* The `.gaia` dir of the project we are standing in, when there is one.
|
|
37
|
+
*
|
|
38
|
+
* The ui hands this over rather than a resolved project name: matching it
|
|
39
|
+
* against a conductor's `workspace_root` needs the control plane, and the
|
|
40
|
+
* client that reaches it first exists in the renderer (GAIA-232). Undefined
|
|
41
|
+
* outside any project.
|
|
42
|
+
*/
|
|
43
|
+
gaiaDir?: string | undefined;
|
|
43
44
|
}
|
|
44
45
|
/**
|
|
45
|
-
* Decide the agents' working directory
|
|
46
|
+
* Decide the agents' working directory (GAIA-219 AC-9).
|
|
46
47
|
*
|
|
47
48
|
* The trigger is the **cwd**, deliberately not the winning connection config:
|
|
48
49
|
* "which connection did we resolve" and "which project am I standing in" are
|
|
@@ -50,17 +51,22 @@ export interface ProjectRooting {
|
|
|
50
51
|
* silently relocate the agents' working directory.
|
|
51
52
|
*
|
|
52
53
|
* Standing **in** a project (core's walk-up finds a `.gaia/` project dir that
|
|
53
|
-
* is not the user's own `~/.gaia`), the cwd is that repo's **root
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
54
|
+
* is not the user's own `~/.gaia`), the cwd is that repo's **root**. Otherwise
|
|
55
|
+
* the agents root at the home dir and the cockpit opens with no default group —
|
|
56
|
+
* an honest empty cockpit rather than a guess.
|
|
57
|
+
*
|
|
58
|
+
* The project name and this machine's conductor id used to come from a local
|
|
59
|
+
* registry file, which GAIA-232 deleted. Both now resolve in the renderer's
|
|
60
|
+
* `run()` — `Entity/conductor/Data/own-conductor.ts`, reached through the
|
|
61
|
+
* launcher's `gaiaDir` — from the conductor record whose `workspace_root`
|
|
62
|
+
* equals this dir: the same data, from the store that also knows whether the
|
|
63
|
+
* conductor is alive, and matched by the same exact normalised path comparison
|
|
64
|
+
* the registry lookup used.
|
|
57
65
|
*
|
|
58
66
|
* The marker is core's `findProjectGaiaDir` — "is there a `.gaia/` here" —
|
|
59
67
|
* NOT the connection walk-up `findGaiaConfig`. Since GAIA-230 the latter skips
|
|
60
68
|
* an engine-only `conductor.config.js`, which GAIA-218 makes the canonical repo
|
|
61
|
-
* shape, so keying on it would leave the most common project rooted
|
|
62
|
-
* registry default. Which connection won and which project we stand in stay two
|
|
63
|
-
* separate questions, as this function's whole point requires.
|
|
69
|
+
* shape, so keying on it would leave the most common project rooted at home.
|
|
64
70
|
*
|
|
65
71
|
* `~/.gaia` is excluded for the same reason core labels it `home`: the walk-up
|
|
66
72
|
* reaches it from every directory under `$HOME`, so counting it would make
|
|
@@ -69,4 +75,4 @@ export interface ProjectRooting {
|
|
|
69
75
|
* Pure with respect to precedence — the walk-up is core's; this helper owns no
|
|
70
76
|
* filesystem probe of its own.
|
|
71
77
|
*/
|
|
72
|
-
export declare function resolveProjectRooting(cwd: string,
|
|
78
|
+
export declare function resolveProjectRooting(cwd: string, homeDir: string): ProjectRooting;
|
package/dist/src/ui-home.js
CHANGED
|
@@ -5,17 +5,6 @@
|
|
|
5
5
|
// holds no connection-precedence rule of its own (AC-6).
|
|
6
6
|
import { dirname, resolve } from 'node:path';
|
|
7
7
|
import { findProjectGaiaDir, homeGaiaDir, shellQuote, } from '@gaia-ai/core';
|
|
8
|
-
/**
|
|
9
|
-
* Resolve the default project (opaque group) from the conductor registry: the
|
|
10
|
-
* `preferred` project when it is registered, else the first entry's project.
|
|
11
|
-
* `undefined` when the registry is empty — a valid empty cockpit.
|
|
12
|
-
*/
|
|
13
|
-
export function resolveDefaultProject(entries, preferred) {
|
|
14
|
-
if (preferred && entries.some((e) => e.project === preferred)) {
|
|
15
|
-
return preferred;
|
|
16
|
-
}
|
|
17
|
-
return entries[0]?.project;
|
|
18
|
-
}
|
|
19
8
|
/**
|
|
20
9
|
* Resolve the agent-launch command builder from the home config. Turns a TUI
|
|
21
10
|
* prompt into the shell command herdr runs. Defaults to `claude` (an empty
|
|
@@ -27,7 +16,7 @@ export function resolveAgentCommand(machine) {
|
|
|
27
16
|
return (prompt) => prompt.trim() === '' ? bin : `${bin} ${shellQuote(prompt)}`;
|
|
28
17
|
}
|
|
29
18
|
/**
|
|
30
|
-
* Decide the agents' working directory
|
|
19
|
+
* Decide the agents' working directory (GAIA-219 AC-9).
|
|
31
20
|
*
|
|
32
21
|
* The trigger is the **cwd**, deliberately not the winning connection config:
|
|
33
22
|
* "which connection did we resolve" and "which project am I standing in" are
|
|
@@ -35,17 +24,22 @@ export function resolveAgentCommand(machine) {
|
|
|
35
24
|
* silently relocate the agents' working directory.
|
|
36
25
|
*
|
|
37
26
|
* Standing **in** a project (core's walk-up finds a `.gaia/` project dir that
|
|
38
|
-
* is not the user's own `~/.gaia`), the cwd is that repo's **root
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
27
|
+
* is not the user's own `~/.gaia`), the cwd is that repo's **root**. Otherwise
|
|
28
|
+
* the agents root at the home dir and the cockpit opens with no default group —
|
|
29
|
+
* an honest empty cockpit rather than a guess.
|
|
30
|
+
*
|
|
31
|
+
* The project name and this machine's conductor id used to come from a local
|
|
32
|
+
* registry file, which GAIA-232 deleted. Both now resolve in the renderer's
|
|
33
|
+
* `run()` — `Entity/conductor/Data/own-conductor.ts`, reached through the
|
|
34
|
+
* launcher's `gaiaDir` — from the conductor record whose `workspace_root`
|
|
35
|
+
* equals this dir: the same data, from the store that also knows whether the
|
|
36
|
+
* conductor is alive, and matched by the same exact normalised path comparison
|
|
37
|
+
* the registry lookup used.
|
|
42
38
|
*
|
|
43
39
|
* The marker is core's `findProjectGaiaDir` — "is there a `.gaia/` here" —
|
|
44
40
|
* NOT the connection walk-up `findGaiaConfig`. Since GAIA-230 the latter skips
|
|
45
41
|
* an engine-only `conductor.config.js`, which GAIA-218 makes the canonical repo
|
|
46
|
-
* shape, so keying on it would leave the most common project rooted
|
|
47
|
-
* registry default. Which connection won and which project we stand in stay two
|
|
48
|
-
* separate questions, as this function's whole point requires.
|
|
42
|
+
* shape, so keying on it would leave the most common project rooted at home.
|
|
49
43
|
*
|
|
50
44
|
* `~/.gaia` is excluded for the same reason core labels it `home`: the walk-up
|
|
51
45
|
* reaches it from every directory under `$HOME`, so counting it would make
|
|
@@ -54,25 +48,12 @@ export function resolveAgentCommand(machine) {
|
|
|
54
48
|
* Pure with respect to precedence — the walk-up is core's; this helper owns no
|
|
55
49
|
* filesystem probe of its own.
|
|
56
50
|
*/
|
|
57
|
-
export function resolveProjectRooting(cwd,
|
|
51
|
+
export function resolveProjectRooting(cwd, homeDir) {
|
|
58
52
|
const gaiaDir = findProjectGaiaDir(cwd);
|
|
59
53
|
// Core owns where `.gaia` lives; the ui never spells the path itself.
|
|
60
54
|
const homeDotGaia = resolve(homeGaiaDir(homeDir));
|
|
61
55
|
if (gaiaDir !== undefined && resolve(gaiaDir) !== homeDotGaia) {
|
|
62
|
-
|
|
63
|
-
// so that — not the repo root — is what an entry is matched on.
|
|
64
|
-
const entry = entries.find((e) => resolve(e.path) === resolve(gaiaDir));
|
|
65
|
-
return {
|
|
66
|
-
cwd: dirname(gaiaDir),
|
|
67
|
-
project: entry?.project,
|
|
68
|
-
ownConductorMachineId: entry?.id,
|
|
69
|
-
};
|
|
56
|
+
return { cwd: dirname(gaiaDir), gaiaDir };
|
|
70
57
|
}
|
|
71
|
-
|
|
72
|
-
const projectEntry = entries.find((e) => e.project === project);
|
|
73
|
-
return {
|
|
74
|
-
cwd: projectEntry?.path ?? homeDir,
|
|
75
|
-
project,
|
|
76
|
-
ownConductorMachineId: projectEntry?.id,
|
|
77
|
-
};
|
|
58
|
+
return { cwd: homeDir };
|
|
78
59
|
}
|
package/dist/src/ui.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AgentLaunchHost } from '@gaia-ai/addon-herdr';
|
|
2
|
-
import { type
|
|
2
|
+
import { type GaiaCommandHost, type GaiaCommandPlugin, type GaiaConnectionConfig, type MachineContext } from '@gaia-ai/core';
|
|
3
3
|
import { type UiInitialRoute } from './route.js';
|
|
4
4
|
import { type HomeConnection } from './ui-home.js';
|
|
5
5
|
/** Prompt-level agent service the renderer consumes (mirror of gaia-ui's `TuiAgentService`). */
|
|
@@ -38,7 +38,15 @@ export interface RunGaiaUiOptions {
|
|
|
38
38
|
project?: string | undefined;
|
|
39
39
|
agents?: TuiAgentService | undefined;
|
|
40
40
|
currentUser?: string | undefined;
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The `.gaia` dir of the project the ui was started in, when there is one.
|
|
43
|
+
*
|
|
44
|
+
* Replaces the `ownConductorMachineId` the local registry used to supply
|
|
45
|
+
* (GAIA-232): the renderer matches this against each conductor's
|
|
46
|
+
* `workspace_root` to find which record is this machine's own — it holds the
|
|
47
|
+
* JSON:API client, and this entry does not.
|
|
48
|
+
*/
|
|
49
|
+
gaiaDir?: string | undefined;
|
|
42
50
|
createPrompt?: string | undefined;
|
|
43
51
|
buildProgram: (opts: {
|
|
44
52
|
plugins: unknown[];
|
|
@@ -62,8 +70,6 @@ export interface BuildWriteClientOptions {
|
|
|
62
70
|
export interface UiDeps {
|
|
63
71
|
/** Machine context (identity + connection); default: read from disk. */
|
|
64
72
|
machine?: Partial<MachineContext>;
|
|
65
|
-
/** Conductor registry entries; default: `listRegisteredConductors()`. */
|
|
66
|
-
registryEntries?: ConductorRegistryEntry[];
|
|
67
73
|
/** herdr-backed agent host; default: `herdrAgentHost(exec herdr)`. */
|
|
68
74
|
agentHost?: AgentLaunchHost;
|
|
69
75
|
/** The renderer launcher; default: the dynamically-resolved `runGaiaUi`. */
|
|
@@ -83,6 +89,8 @@ export interface UiDeps {
|
|
|
83
89
|
printConfig?: boolean;
|
|
84
90
|
/** Sink for `--print-config`; default: `process.stdout.write`. */
|
|
85
91
|
stdout?: (s: string) => void;
|
|
92
|
+
/** Daily update lookup; injectable for startup ordering tests. */
|
|
93
|
+
updateCheck?: () => Promise<string | null>;
|
|
86
94
|
/** Positional `[target]` (`GAIA-221`, `ticket:…`, `project:…`, `run:…`). */
|
|
87
95
|
target?: string | undefined;
|
|
88
96
|
/** `--ticket <id>`. */
|
package/dist/src/ui.js
CHANGED
|
@@ -2,9 +2,10 @@ import { createRequire } from 'node:module';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { herdrAgentHost } from '@gaia-ai/addon-herdr';
|
|
5
|
-
import { CommandRunner, createLogger, exec,
|
|
5
|
+
import { CommandRunner, createLogger, exec, fetchUpdateNotice, loadGaiaConfig, machineContextPath, readMachineContext, resolveCliVersion, setDefaultCommandRunner, } from '@gaia-ai/core';
|
|
6
6
|
import { buildProgram as buildDropshProgram, resolveAuth } from 'dropsh';
|
|
7
7
|
import { createHttpClient, createJsonApiClient } from 'dropsh/plugin';
|
|
8
|
+
import { applyOpentuiLibc, reExecUnderBun, resolveBunBinary, shouldReExecUnderBun, } from './bun-runtime.js';
|
|
8
9
|
import { parseTarget, resolveHere, UI_VIEW_MODES, } from './route.js';
|
|
9
10
|
import { resolveAgentCommand, resolveProjectRooting, } from './ui-home.js';
|
|
10
11
|
/** The auth profile `gaia ui` writes as. Reads stay on `session`. */
|
|
@@ -57,11 +58,13 @@ async function readCurrentBranch(cwd) {
|
|
|
57
58
|
* with no `origin`, it yields '' and the ladder produces its named error.
|
|
58
59
|
*
|
|
59
60
|
* Revision 4 replaced the previous rung (an `import()` of the repo's engine
|
|
60
|
-
* `conductor.config.js` plus a
|
|
61
|
-
* this one read:
|
|
62
|
-
*
|
|
63
|
-
* needed a dynamic import of a possibly secret-bearing module, and the
|
|
64
|
-
* works in a fresh clone that has no `.gaia/` at all.
|
|
61
|
+
* `conductor.config.js` plus a longest-prefix fallback over the then-still-alive
|
|
62
|
+
* local conductor registry) with this one read: neither could answer inside a
|
|
63
|
+
* worktree (both keyed on `.gaia` DIRS, and a worktree has its own), the config
|
|
64
|
+
* read needed a dynamic import of a possibly secret-bearing module, and the
|
|
65
|
+
* remote works in a fresh clone that has no `.gaia/` at all. Revision 5 then
|
|
66
|
+
* deleted that registry outright, which only removes an alternative this rung
|
|
67
|
+
* had already rejected.
|
|
65
68
|
*/
|
|
66
69
|
async function readOriginRemoteUrl(cwd) {
|
|
67
70
|
try {
|
|
@@ -90,8 +93,19 @@ function resolveExplicitConfig(flag) {
|
|
|
90
93
|
}
|
|
91
94
|
return undefined;
|
|
92
95
|
}
|
|
93
|
-
/**
|
|
94
|
-
|
|
96
|
+
/**
|
|
97
|
+
* The `--print-config` block (AC-7): which file won, and what it points at.
|
|
98
|
+
*
|
|
99
|
+
* `project` reports the `--project` flag, and `gaia_dir` the project dir the
|
|
100
|
+
* rooting resolved. GAIA-232 split the two on purpose: the DEFAULT project is no
|
|
101
|
+
* longer knowable here. It used to be read out of `~/.gaia/conductors.json`,
|
|
102
|
+
* which this ticket deleted, and its replacement is the conductor record whose
|
|
103
|
+
* `workspace_root` equals `gaia_dir` — a control-plane read, and this command
|
|
104
|
+
* holds no JSON:API client (GAIA-223 spec Finding 1). Printing `gaia_dir` is
|
|
105
|
+
* therefore the honest form: it names the exact input the renderer resolves the
|
|
106
|
+
* project from, instead of a `project:` line that could only ever say `(none)`.
|
|
107
|
+
*/
|
|
108
|
+
function formatConnectionReport(connection, rooting, project) {
|
|
95
109
|
const yesNo = (b) => (b ? 'yes' : 'no');
|
|
96
110
|
const lines = [
|
|
97
111
|
['connection', connection.config_path],
|
|
@@ -100,7 +114,11 @@ function formatConnectionReport(connection, rooting) {
|
|
|
100
114
|
['legacy', yesNo(connection.legacy)],
|
|
101
115
|
['base_url', connection.site.base_url],
|
|
102
116
|
['cwd', rooting.cwd],
|
|
103
|
-
['
|
|
117
|
+
['gaia_dir', rooting.gaiaDir ?? '(none)'],
|
|
118
|
+
[
|
|
119
|
+
'project',
|
|
120
|
+
project ?? (rooting.gaiaDir ? '(resolved from gaia_dir)' : '(none)'),
|
|
121
|
+
],
|
|
104
122
|
];
|
|
105
123
|
return `${lines.map(([k, v]) => `${`${k}:`.padEnd(12)}${v}`).join('\n')}\n`;
|
|
106
124
|
}
|
|
@@ -156,6 +174,38 @@ export async function cmdUi(deps = {}) {
|
|
|
156
174
|
(deps.stderr ?? ((line) => process.stderr.write(line)))(`${message}\n`);
|
|
157
175
|
process.exitCode = 1;
|
|
158
176
|
};
|
|
177
|
+
// GAIA-326 (A1) — teach OpenTUI which libc this host runs BEFORE anything
|
|
178
|
+
// else. It is set on THIS process rather than only on the re-exec's `env`
|
|
179
|
+
// argument, and that placement is the whole point: `reExecUnderBun` passes
|
|
180
|
+
// `env: process.env`, so the Bun child inherits it for free, while the three
|
|
181
|
+
// paths that never re-exec (already under Bun, `$GAIA_UI_NO_BUN` on Node 26 +
|
|
182
|
+
// --experimental-ffi, an injected `runUi`) load `@opentui/core` in THIS
|
|
183
|
+
// process and would otherwise get the glibc default on Alpine. `@opentui/core`
|
|
184
|
+
// reads the variable when it resolves its native asset — strictly later than
|
|
185
|
+
// here, since the renderer is a dynamic import — so one assignment covers
|
|
186
|
+
// every path. On glibc it is a no-op.
|
|
187
|
+
applyOpentuiLibc(process.env);
|
|
188
|
+
// GAIA-326 — the renderer needs Bun's FFI. Re-exec here rather than in a bin
|
|
189
|
+
// shim: there are three shims, the host already owns the value-aware argv
|
|
190
|
+
// pre-scan that says `ui` was invoked, and `--print-config` must keep working
|
|
191
|
+
// without Bun. This sits after `fail` is declared (the only ordering the
|
|
192
|
+
// named-error path allows) and still before every config load and the whole
|
|
193
|
+
// deep-link ladder, so nothing is resolved twice across the two processes.
|
|
194
|
+
if (shouldReExecUnderBun({
|
|
195
|
+
bunVersion: process.versions.bun,
|
|
196
|
+
noBun: process.env.GAIA_UI_NO_BUN,
|
|
197
|
+
printConfig: Boolean(deps.printConfig),
|
|
198
|
+
injectedRenderer: deps.runUi !== undefined,
|
|
199
|
+
})) {
|
|
200
|
+
const bin = resolveBunBinary(host.resolveBases);
|
|
201
|
+
if (bin === undefined) {
|
|
202
|
+
fail('gaia ui: could not resolve the bun runtime. `@gaia-ai/addon-gaia-ui` ' +
|
|
203
|
+
'depends on it; reinstall, or set $GAIA_UI_NO_BUN=1 to run on Node ' +
|
|
204
|
+
'26+ with --experimental-ffi.');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
reExecUnderBun(bin, process.argv);
|
|
208
|
+
}
|
|
159
209
|
// GAIA-223: build the deep-link route FIRST — argv parsing, the conflict rule
|
|
160
210
|
// and the whole `--here` ladder are offline, so every one of their failures
|
|
161
211
|
// costs no config load and no network round-trip, and never reaches the
|
|
@@ -186,9 +236,6 @@ export async function cmdUi(deps = {}) {
|
|
|
186
236
|
fail(err instanceof Error ? err.message : String(err));
|
|
187
237
|
return;
|
|
188
238
|
}
|
|
189
|
-
// Read only AFTER the route: the whole grammar + ladder is offline, so a
|
|
190
|
-
// parse/conflict/no-remote failure must not even touch the registry file.
|
|
191
|
-
const entries = deps.registryEntries ?? (await listRegisteredConductors());
|
|
192
239
|
const mcPath = machineContextPath();
|
|
193
240
|
// The machine context still supplies identity (`user_id`) + the agent binary;
|
|
194
241
|
// it is NOT the connection any more — core resolves that.
|
|
@@ -209,6 +256,14 @@ export async function cmdUi(deps = {}) {
|
|
|
209
256
|
process.exitCode = 1;
|
|
210
257
|
return;
|
|
211
258
|
}
|
|
259
|
+
const notice = deps.updateCheck
|
|
260
|
+
? await deps.updateCheck()
|
|
261
|
+
: deps.runUi
|
|
262
|
+
? null
|
|
263
|
+
: await fetchUpdateNotice(resolveCliVersion());
|
|
264
|
+
if (notice !== null) {
|
|
265
|
+
(deps.stdout ?? ((line) => process.stdout.write(line)))(`${notice}\n`);
|
|
266
|
+
}
|
|
212
267
|
const connection = {
|
|
213
268
|
baseUrl: resolved.site.base_url,
|
|
214
269
|
jsonapiPrefix: resolved.site.jsonapi_prefix,
|
|
@@ -242,9 +297,8 @@ export async function cmdUi(deps = {}) {
|
|
|
242
297
|
`(${err instanceof Error ? err.message : String(err)}); ` +
|
|
243
298
|
`edits will be attempted as '${connection.authProfile}'.`);
|
|
244
299
|
}
|
|
245
|
-
const rooting = resolveProjectRooting(cwd,
|
|
246
|
-
const
|
|
247
|
-
const project = rooting.project;
|
|
300
|
+
const rooting = resolveProjectRooting(cwd, homedir());
|
|
301
|
+
const project = deps.project;
|
|
248
302
|
// AC-7, always on: the resolution lands in the log even when the TUI takes
|
|
249
303
|
// over the screen a moment later.
|
|
250
304
|
logger.info({
|
|
@@ -254,13 +308,17 @@ export async function cmdUi(deps = {}) {
|
|
|
254
308
|
legacy: resolved.legacy,
|
|
255
309
|
base_url: resolved.site.base_url,
|
|
256
310
|
cwd: rooting.cwd,
|
|
311
|
+
// The `.gaia` dir, not a project name: since GAIA-232 the default project
|
|
312
|
+
// is resolved in the renderer from the conductor record whose
|
|
313
|
+
// `workspace_root` is this dir. `project` here is the flag alone.
|
|
314
|
+
gaia_dir: rooting.gaiaDir,
|
|
257
315
|
project,
|
|
258
316
|
}, 'gaia ui: resolved connection');
|
|
259
317
|
// AC-7, the human surface: print and exit 0 WITHOUT booting the TUI, so a
|
|
260
318
|
// wrong-control-plane situation is diagnosable at a prompt.
|
|
261
319
|
if (deps.printConfig) {
|
|
262
320
|
const write = deps.stdout ?? ((s) => process.stdout.write(s));
|
|
263
|
-
write(formatConnectionReport(resolved, rooting));
|
|
321
|
+
write(formatConnectionReport(resolved, rooting, project));
|
|
264
322
|
return;
|
|
265
323
|
}
|
|
266
324
|
const agentCommand = resolveAgentCommand(machine);
|
|
@@ -315,7 +373,7 @@ export async function cmdUi(deps = {}) {
|
|
|
315
373
|
...(project !== undefined ? { project } : {}),
|
|
316
374
|
agents,
|
|
317
375
|
...(machine.user_id ? { currentUser: machine.user_id } : {}),
|
|
318
|
-
...(
|
|
376
|
+
...(rooting.gaiaDir ? { gaiaDir: rooting.gaiaDir } : {}),
|
|
319
377
|
// The plugins are real constructed `DropSHPlugin[]` now (core built them),
|
|
320
378
|
// matching dropsh's declared contract — F3's substance, a descriptor handed
|
|
321
379
|
// to an API expecting instances, is gone. The two casts that remain are
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaia-ai/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "GAIA project-first cockpit: the `gaia ui` command plugin (renderer resolved dynamically).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"directory": "gaia-cli/ui"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@gaia-ai/core": "^0.
|
|
30
|
-
"@gaia-ai/addon-herdr": "^0.
|
|
29
|
+
"@gaia-ai/core": "^0.9.0",
|
|
30
|
+
"@gaia-ai/addon-herdr": "^0.9.0",
|
|
31
31
|
"commander": "^12.1.0",
|
|
32
32
|
"dropsh": "^0.5.8"
|
|
33
33
|
}
|