@gaia-ai/core 0.0.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/dist/src/core/conductor-id.d.ts +1 -0
  4. package/dist/src/core/conductor-id.js +8 -0
  5. package/dist/src/core/exec.d.ts +41 -0
  6. package/dist/src/core/exec.js +75 -0
  7. package/dist/src/core/logger.d.ts +31 -0
  8. package/dist/src/core/logger.js +36 -0
  9. package/dist/src/core/shell.d.ts +21 -0
  10. package/dist/src/core/shell.js +46 -0
  11. package/dist/src/core/slug.d.ts +9 -0
  12. package/dist/src/core/slug.js +18 -0
  13. package/dist/src/index.d.ts +12 -0
  14. package/dist/src/index.js +7 -0
  15. package/dist/src/plugins/agent/agent.d.ts +52 -0
  16. package/dist/src/plugins/agent/agent.js +10 -0
  17. package/dist/src/plugins/auth/basic.d.ts +11 -0
  18. package/dist/src/plugins/auth/basic.js +35 -0
  19. package/dist/src/plugins/executor/executor.d.ts +92 -0
  20. package/dist/src/plugins/executor/executor.js +1 -0
  21. package/dist/src/plugins/plugins.d.ts +44 -0
  22. package/dist/src/plugins/plugins.js +16 -0
  23. package/dist/src/plugins/registry-exports.d.ts +6 -0
  24. package/dist/src/plugins/registry-exports.js +6 -0
  25. package/dist/src/plugins/remote/drupal.d.ts +34 -0
  26. package/dist/src/plugins/remote/drupal.js +334 -0
  27. package/dist/src/plugins/remote/fake.d.ts +95 -0
  28. package/dist/src/plugins/remote/fake.js +227 -0
  29. package/dist/src/plugins/remote/remote.d.ts +175 -0
  30. package/dist/src/plugins/remote/remote.js +1 -0
  31. package/dist/src/plugins/workspace/fake.d.ts +6 -0
  32. package/dist/src/plugins/workspace/fake.js +16 -0
  33. package/dist/src/plugins/workspace/git.d.ts +37 -0
  34. package/dist/src/plugins/workspace/git.js +89 -0
  35. package/dist/src/plugins/workspace/instructions.d.ts +6 -0
  36. package/dist/src/plugins/workspace/instructions.js +16 -0
  37. package/dist/src/plugins/workspace/workspace.d.ts +35 -0
  38. package/dist/src/plugins/workspace/workspace.js +1 -0
  39. package/dist/src/plugins-index.d.ts +1 -0
  40. package/dist/src/plugins-index.js +1 -0
  41. package/dist/src/types.d.ts +51 -0
  42. package/dist/src/types.js +1 -0
  43. package/package.json +33 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 keytec GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @gaia-ai/core
2
+
3
+ GAIA global contract: plugin API, built-in remotes/workspaces/auth, shared primitives.
4
+
5
+ Part of the GAIA conductor. Install the meta package `@gaia-ai/gaia` to get the `gaia` CLI with all plugins. Source: https://git.key-tec.de/keytec/gaia (conductor/).
@@ -0,0 +1 @@
1
+ export declare function conductorId(absPath: string): string;
@@ -0,0 +1,8 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { hostname } from 'node:os';
3
+ export function conductorId(absPath) {
4
+ return createHash('sha256')
5
+ .update(`${hostname()}:${absPath}`)
6
+ .digest('hex')
7
+ .slice(0, 16);
8
+ }
@@ -0,0 +1,41 @@
1
+ import type { ConductorLogger } from './logger.js';
2
+ export declare class ExecError extends Error {
3
+ readonly file: string;
4
+ readonly args: string[];
5
+ readonly exitCode: number | null;
6
+ readonly stderr: string;
7
+ constructor(file: string, args: string[], exitCode: number | null, stderr: string, options?: {
8
+ cause?: unknown;
9
+ });
10
+ }
11
+ export interface ExecOpts {
12
+ cwd?: string;
13
+ env?: Record<string, string>;
14
+ /**
15
+ * Rewrite the args used for *logging* (both the `exec ok` debug line and the
16
+ * `exec failed` error line). The real args are always executed unchanged.
17
+ * Use this when an arg embeds a secret — e.g. herdr's `pane run` command
18
+ * string `KEY='value' claude` (GAIA-99) — so a failed exec never writes the
19
+ * secret to the log.
20
+ */
21
+ redactArgs?: (args: string[]) => string[];
22
+ }
23
+ export declare class CommandRunner {
24
+ private readonly logger;
25
+ constructor(logger: ConductorLogger);
26
+ run(file: string, args: string[], opts?: ExecOpts): Promise<string>;
27
+ }
28
+ /**
29
+ * Replace the module-level singleton runner used by {@link exec}.
30
+ *
31
+ * Note: out-of-package plugins (e.g. `@gaia-ai/plugin-herdr`) import `exec` via
32
+ * the `@gaia-ai/gaia/plugin` barrel, which resolves to the built `dist/`
33
+ * copy of this module. This means `setDefaultCommandRunner` only shares the
34
+ * runner with those plugins when the conductor itself runs from `dist`
35
+ * (production / after `pnpm build`). Under `tsx`/dev, the entry point loads
36
+ * this module from source while the plugin loads it from dist — two separate
37
+ * module instances — so plugin exec calls fall back to the noop logger.
38
+ * This is functionally harmless; logging only.
39
+ */
40
+ export declare function setDefaultCommandRunner(r: CommandRunner): void;
41
+ export declare function exec(file: string, args: string[], opts?: ExecOpts): Promise<string>;
@@ -0,0 +1,75 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ export class ExecError extends Error {
5
+ file;
6
+ args;
7
+ exitCode;
8
+ stderr;
9
+ constructor(file, args, exitCode, stderr, options) {
10
+ super(`exec failed: ${file}`, options);
11
+ this.file = file;
12
+ this.args = args;
13
+ this.exitCode = exitCode;
14
+ this.stderr = stderr;
15
+ this.name = 'ExecError';
16
+ }
17
+ }
18
+ export class CommandRunner {
19
+ logger;
20
+ constructor(logger) {
21
+ this.logger = logger;
22
+ }
23
+ async run(file, args, opts = {}) {
24
+ // Args can themselves carry secrets (herdr embeds `KEY='value'` in the
25
+ // pane-run command string — GAIA-99). `redactArgs` rewrites the args used
26
+ // for logging only; the real args are still executed verbatim.
27
+ const logArgs = opts.redactArgs ? opts.redactArgs(args) : args;
28
+ try {
29
+ const { env, redactArgs: _redact, ...rest } = opts;
30
+ // Merge over process.env, never replace it: a caller-supplied env adds/
31
+ // overrides keys but the child still inherits PATH etc. Values may be
32
+ // sensitive (GAIA-99) — never log them; only cwd is logged below.
33
+ const childEnv = env ? { ...process.env, ...env } : undefined;
34
+ const { stdout } = await execFileAsync(file, args, {
35
+ encoding: 'utf8',
36
+ ...rest,
37
+ ...(childEnv ? { env: childEnv } : {}),
38
+ });
39
+ this.logger.debug({ file, args: logArgs, cwd: opts.cwd }, 'exec ok');
40
+ return stdout;
41
+ }
42
+ catch (err) {
43
+ const e = err;
44
+ const exitCode = typeof e.code === 'number' ? e.code : null;
45
+ const stderr = e.stderr ?? '';
46
+ this.logger.error({ file, args: logArgs, cwd: opts.cwd, exitCode, stderr }, 'exec failed');
47
+ throw new ExecError(file, args, exitCode, stderr, { cause: err });
48
+ }
49
+ }
50
+ }
51
+ const noopLogger = {
52
+ debug() { },
53
+ info() { },
54
+ warn() { },
55
+ error() { },
56
+ };
57
+ let defaultRunner = new CommandRunner(noopLogger);
58
+ /**
59
+ * Replace the module-level singleton runner used by {@link exec}.
60
+ *
61
+ * Note: out-of-package plugins (e.g. `@gaia-ai/plugin-herdr`) import `exec` via
62
+ * the `@gaia-ai/gaia/plugin` barrel, which resolves to the built `dist/`
63
+ * copy of this module. This means `setDefaultCommandRunner` only shares the
64
+ * runner with those plugins when the conductor itself runs from `dist`
65
+ * (production / after `pnpm build`). Under `tsx`/dev, the entry point loads
66
+ * this module from source while the plugin loads it from dist — two separate
67
+ * module instances — so plugin exec calls fall back to the noop logger.
68
+ * This is functionally harmless; logging only.
69
+ */
70
+ export function setDefaultCommandRunner(r) {
71
+ defaultRunner = r;
72
+ }
73
+ export function exec(file, args, opts = {}) {
74
+ return defaultRunner.run(file, args, opts);
75
+ }
@@ -0,0 +1,31 @@
1
+ export type LogSink = {
2
+ kind: 'stdout';
3
+ } | {
4
+ kind: 'file';
5
+ path: string;
6
+ };
7
+ export interface ConductorLogger {
8
+ debug(obj: object, msg?: string): void;
9
+ info(obj: object, msg?: string): void;
10
+ warn(obj: object, msg?: string): void;
11
+ error(obj: object, msg?: string): void;
12
+ }
13
+ /**
14
+ * Pick the log sink. Precedence: explicit CLI override (`sink`) > the
15
+ * GAIA_CONDUCTOR_LOG env var > TTY default (stdout on a TTY, file otherwise).
16
+ */
17
+ export declare function resolveSink(env: NodeJS.ProcessEnv, isTTY: boolean, checkoutRoot: string, override?: string): LogSink;
18
+ /**
19
+ * Pick the log level. Precedence: explicit CLI override (`level`) >
20
+ * the GAIA_LOG_LEVEL env var > info.
21
+ */
22
+ export declare function resolveLevel(env: NodeJS.ProcessEnv, override?: string): string;
23
+ export declare function createLogger(opts: {
24
+ checkoutRoot: string;
25
+ isTTY?: boolean;
26
+ env?: NodeJS.ProcessEnv;
27
+ /** CLI override for the level — beats env + default. */
28
+ level?: string;
29
+ /** CLI override for the sink — beats env + default. */
30
+ sink?: string;
31
+ }): ConductorLogger;
@@ -0,0 +1,36 @@
1
+ import { join } from 'node:path';
2
+ import pino from 'pino';
3
+ import pretty from 'pino-pretty';
4
+ /**
5
+ * Pick the log sink. Precedence: explicit CLI override (`sink`) > the
6
+ * GAIA_CONDUCTOR_LOG env var > TTY default (stdout on a TTY, file otherwise).
7
+ */
8
+ export function resolveSink(env, isTTY, checkoutRoot, override) {
9
+ const fileSink = {
10
+ kind: 'file',
11
+ path: join(checkoutRoot, 'log.txt'),
12
+ };
13
+ const choice = override ?? env.GAIA_CONDUCTOR_LOG;
14
+ if (choice === 'stdout')
15
+ return { kind: 'stdout' };
16
+ if (choice === 'file')
17
+ return fileSink;
18
+ return isTTY ? { kind: 'stdout' } : fileSink;
19
+ }
20
+ /**
21
+ * Pick the log level. Precedence: explicit CLI override (`level`) >
22
+ * the GAIA_LOG_LEVEL env var > info.
23
+ */
24
+ export function resolveLevel(env, override) {
25
+ return override ?? env.GAIA_LOG_LEVEL ?? 'info';
26
+ }
27
+ export function createLogger(opts) {
28
+ const env = opts.env ?? process.env;
29
+ const isTTY = opts.isTTY ?? Boolean(process.stdout.isTTY);
30
+ const level = resolveLevel(env, opts.level);
31
+ const sink = resolveSink(env, isTTY, opts.checkoutRoot, opts.sink);
32
+ if (sink.kind === 'stdout') {
33
+ return pino({ level }, pretty({ colorize: true, sync: true }));
34
+ }
35
+ return pino({ level }, pino.destination({ dest: sink.path, append: true, mkdir: true }));
36
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Quote a string for safe embedding as a single shell argument, guaranteeing
3
+ * the result contains NO literal control bytes — critical when the command is
4
+ * *typed* into an interactive pty (herdr `pane run`), where a literal newline
5
+ * (`0x0A`) acts as Enter and submits the line early, breaking the command into
6
+ * still-quoted fragments so the agent invocation is never run (GAIA-101).
7
+ *
8
+ * Newline-free values use plain POSIX single-quoting (`'...'`) — byte-identical
9
+ * to the old quoter, so simple commands are unchanged. Values with a newline or
10
+ * any other control char use ANSI-C quoting (`$'...'`), which encodes those
11
+ * bytes as escape sequences (`\n`, `\t`, `\xHH`, …) the shell decodes back — so
12
+ * the argument is one logical line yet the agent still receives the newlines.
13
+ *
14
+ * Caveat: a NUL byte (`0x00`) is the one control char the shell cannot
15
+ * reconstruct — `$'\x00'` decodes to the empty string because a C-string argv
16
+ * entry is NUL-terminated. We still emit `\x00` (so no literal NUL reaches the
17
+ * typed pty line), but the value silently truncates there. This is an inherent
18
+ * POSIX-shell limit, not a bug; it never affects real LLM prompt text, which
19
+ * contains no NUL bytes.
20
+ */
21
+ export declare function shellQuote(value: string): string;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Quote a string for safe embedding as a single shell argument, guaranteeing
3
+ * the result contains NO literal control bytes — critical when the command is
4
+ * *typed* into an interactive pty (herdr `pane run`), where a literal newline
5
+ * (`0x0A`) acts as Enter and submits the line early, breaking the command into
6
+ * still-quoted fragments so the agent invocation is never run (GAIA-101).
7
+ *
8
+ * Newline-free values use plain POSIX single-quoting (`'...'`) — byte-identical
9
+ * to the old quoter, so simple commands are unchanged. Values with a newline or
10
+ * any other control char use ANSI-C quoting (`$'...'`), which encodes those
11
+ * bytes as escape sequences (`\n`, `\t`, `\xHH`, …) the shell decodes back — so
12
+ * the argument is one logical line yet the agent still receives the newlines.
13
+ *
14
+ * Caveat: a NUL byte (`0x00`) is the one control char the shell cannot
15
+ * reconstruct — `$'\x00'` decodes to the empty string because a C-string argv
16
+ * entry is NUL-terminated. We still emit `\x00` (so no literal NUL reaches the
17
+ * typed pty line), but the value silently truncates there. This is an inherent
18
+ * POSIX-shell limit, not a bug; it never affects real LLM prompt text, which
19
+ * contains no NUL bytes.
20
+ */
21
+ export function shellQuote(value) {
22
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: detecting control bytes is the point
23
+ if (!/[\x00-\x1f\x7f]/.test(value)) {
24
+ return `'${value.replaceAll("'", "'\\''")}'`;
25
+ }
26
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: escaping control bytes is the point
27
+ const escaped = value.replace(/[\\'\x00-\x1f\x7f]/g, (ch) => {
28
+ switch (ch) {
29
+ case '\\':
30
+ return '\\\\';
31
+ case "'":
32
+ return "\\'";
33
+ case '\n':
34
+ return '\\n';
35
+ case '\t':
36
+ return '\\t';
37
+ case '\r':
38
+ return '\\r';
39
+ default: {
40
+ const hex = ch.charCodeAt(0).toString(16).padStart(2, '0');
41
+ return `\\x${hex}`;
42
+ }
43
+ }
44
+ });
45
+ return `$'${escaped}'`;
46
+ }
@@ -0,0 +1,9 @@
1
+ /** Default cap for a title slug — keeps tab labels and branch names short. */
2
+ export declare const DEFAULT_SLUG_MAX_LENGTH = 40;
3
+ /**
4
+ * Turn free text (a ticket title) into a short, ref-safe slug:
5
+ * lowercase, non-alphanumeric runs collapsed to `-`, trimmed, and capped to
6
+ * `maxLength` characters without leaving a trailing separator. Returns `''`
7
+ * for input that has no usable characters.
8
+ */
9
+ export declare function slugify(text: string, maxLength?: number): string;
@@ -0,0 +1,18 @@
1
+ /** Default cap for a title slug — keeps tab labels and branch names short. */
2
+ export const DEFAULT_SLUG_MAX_LENGTH = 40;
3
+ /**
4
+ * Turn free text (a ticket title) into a short, ref-safe slug:
5
+ * lowercase, non-alphanumeric runs collapsed to `-`, trimmed, and capped to
6
+ * `maxLength` characters without leaving a trailing separator. Returns `''`
7
+ * for input that has no usable characters.
8
+ */
9
+ export function slugify(text, maxLength = DEFAULT_SLUG_MAX_LENGTH) {
10
+ const slug = text
11
+ .toLowerCase()
12
+ .replace(/[^a-z0-9]+/g, '-')
13
+ .replace(/^-+|-+$/g, '');
14
+ if (slug.length <= maxLength) {
15
+ return slug;
16
+ }
17
+ return slug.slice(0, maxLength).replace(/-+$/g, '');
18
+ }
@@ -0,0 +1,12 @@
1
+ export { conductorId } from './core/conductor-id.js';
2
+ export { CommandRunner, ExecError, exec, setDefaultCommandRunner, } from './core/exec.js';
3
+ export { type ConductorLogger, createLogger } from './core/logger.js';
4
+ export { shellQuote } from './core/shell.js';
5
+ export { DEFAULT_SLUG_MAX_LENGTH, slugify } from './core/slug.js';
6
+ export { type AgentFootprint, emptyAgentFootprint, type GaiaAgent, } from './plugins/agent/agent.js';
7
+ export type * from './plugins/executor/executor.js';
8
+ export type { AgentPlugin, ExecutorDeps, ExecutorPlugin, RemotePlugin, WorkspacePlugin, } from './plugins/plugins.js';
9
+ export type * from './plugins/remote/remote.js';
10
+ export { loadInstructions } from './plugins/workspace/instructions.js';
11
+ export type { EnsuredWorkspace, GaiaWorkspace, } from './plugins/workspace/workspace.js';
12
+ export type * from './types.js';
@@ -0,0 +1,7 @@
1
+ export { conductorId } from './core/conductor-id.js';
2
+ export { CommandRunner, ExecError, exec, setDefaultCommandRunner, } from './core/exec.js';
3
+ export { createLogger } from './core/logger.js';
4
+ export { shellQuote } from './core/shell.js';
5
+ export { DEFAULT_SLUG_MAX_LENGTH, slugify } from './core/slug.js';
6
+ export { emptyAgentFootprint, } from './plugins/agent/agent.js';
7
+ export { loadInstructions } from './plugins/workspace/instructions.js';
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Per-agent effort footprint parsed from that agent's run transcript
3
+ * (GAIA-132). The metrics are transcript-derived and therefore
4
+ * **agent-specific** (each agent's transcript has its own format), so parsing
5
+ * lives behind the agent abstraction (see {@link GaiaAgent.parseFootprint}) —
6
+ * the conductor stays agent-agnostic and only adds `duration_s` (wall-clock,
7
+ * agent-independent) before writing the run's footprint.
8
+ */
9
+ export interface AgentFootprint {
10
+ /** Total tokens across every usage bucket of every assistant turn. */
11
+ tokens: number;
12
+ /** Number of assistant turns in the transcript. */
13
+ agent_turns: number;
14
+ /** Number of tool-use calls across all assistant turns. */
15
+ tool_calls: number;
16
+ /** Number of user-submitted prompts. */
17
+ user_prompts: number;
18
+ /** Total words across those user prompts. */
19
+ user_prompt_words: number;
20
+ /** Model of the last assistant turn, when present (informational). */
21
+ model?: string;
22
+ }
23
+ /** An all-zero footprint — the honest result for an empty/absent transcript. */
24
+ export declare function emptyAgentFootprint(): AgentFootprint;
25
+ /**
26
+ * A GAIA agent: the program the conductor runs to work a ticket (e.g. claude).
27
+ * It knows how it is launched (CLI + model + flags) and where its per-run
28
+ * transcript/log lives — so the conductor stays agent-agnostic and the CLI can
29
+ * attach the run log on release without agent-specific knowledge.
30
+ */
31
+ export interface GaiaAgent {
32
+ /** Stable id, e.g. 'claude'. */
33
+ readonly id: string;
34
+ /**
35
+ * Build the full agent CLI invocation for a GAIA prompt. With an empty
36
+ * prompt, returns the bare launch command (no prompt argument).
37
+ */
38
+ launchCommand(prompt: string): string;
39
+ /**
40
+ * Locate and read this agent's run transcript for a run that executed in
41
+ * `worktreePath`. MUST return '' (never throw) when nothing is found.
42
+ */
43
+ getRunLog(worktreePath: string): Promise<string>;
44
+ /**
45
+ * Parse this agent's run transcript (as returned by {@link getRunLog}) into
46
+ * a footprint of effort metrics (GAIA-132). The transcript format is
47
+ * agent-specific, so each agent owns its own parser. MUST be tolerant of
48
+ * blank/partial/absent input (never throw) — an empty log yields
49
+ * {@link emptyAgentFootprint}.
50
+ */
51
+ parseFootprint(log: string): AgentFootprint;
52
+ }
@@ -0,0 +1,10 @@
1
+ /** An all-zero footprint — the honest result for an empty/absent transcript. */
2
+ export function emptyAgentFootprint() {
3
+ return {
4
+ tokens: 0,
5
+ agent_turns: 0,
6
+ tool_calls: 0,
7
+ user_prompts: 0,
8
+ user_prompt_words: 0,
9
+ };
10
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Static HTTP Basic auth provider built from an inline base64 `user:pass` token
3
+ * (conductor.config.local.js `auth.basic`, or the GAIA_E2E_BASIC env for tests).
4
+ * Returns the `{ id, authProvider }` plugin shape that dropsh's resolveAuth reads
5
+ * from `config.plugins`. Replaces the per-file basicAuthProvider that used to be
6
+ * hand-written into each dropsh.config.js.
7
+ */
8
+ export declare function basicAuthProvider(tokenBase64: string): {
9
+ id: string;
10
+ authProvider: unknown;
11
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Static HTTP Basic auth provider built from an inline base64 `user:pass` token
3
+ * (conductor.config.local.js `auth.basic`, or the GAIA_E2E_BASIC env for tests).
4
+ * Returns the `{ id, authProvider }` plugin shape that dropsh's resolveAuth reads
5
+ * from `config.plugins`. Replaces the per-file basicAuthProvider that used to be
6
+ * hand-written into each dropsh.config.js.
7
+ */
8
+ export function basicAuthProvider(tokenBase64) {
9
+ const authProvider = {
10
+ id: 'basic_auth',
11
+ displayName: 'HTTP Basic (inline)',
12
+ capabilities: { login: false, logout: false, status: true },
13
+ async login() {
14
+ throw new Error('GAIA uses static inline HTTP Basic auth.');
15
+ },
16
+ async logout() { },
17
+ async status() {
18
+ return { loggedIn: true, provider: 'basic_auth' };
19
+ },
20
+ createAdapter() {
21
+ return {
22
+ async apply(req) {
23
+ return {
24
+ ...req,
25
+ headers: {
26
+ ...(req.headers ?? {}),
27
+ Authorization: `Basic ${tokenBase64}`,
28
+ },
29
+ };
30
+ },
31
+ };
32
+ },
33
+ };
34
+ return { id: 'gaia-basic-auth', authProvider };
35
+ }
@@ -0,0 +1,92 @@
1
+ export interface SpawnedSession {
2
+ sessionRef: string;
3
+ }
4
+ export interface ExecutorCapabilities {
5
+ persistent: boolean;
6
+ }
7
+ export interface SpawnRunInput {
8
+ ticket: {
9
+ uuid: string;
10
+ identifier: string;
11
+ title: string;
12
+ branchName: string;
13
+ state: string;
14
+ url?: string;
15
+ };
16
+ run: {
17
+ uuid: string;
18
+ id: number;
19
+ handler: string;
20
+ };
21
+ workspacePath: string;
22
+ instructions: {
23
+ path: string;
24
+ sha256: string;
25
+ text: string;
26
+ } | null;
27
+ command?: string;
28
+ env?: Record<string, string>;
29
+ }
30
+ /** The four lifecycle-hook slots, keyed exactly like `config.hooks`. */
31
+ export type HookName = 'after_create' | 'before_run' | 'after_run' | 'after_done';
32
+ /** Context for a hook invocation — carries the ticket for observable logging. */
33
+ export interface HookContext {
34
+ /** The ticket identifier/uuid the hook is running for (log context). */
35
+ ticket: string;
36
+ }
37
+ export interface GaiaExecutor {
38
+ id: string;
39
+ capabilities(): ExecutorCapabilities;
40
+ /**
41
+ * Run the lifecycle hook `name` (command from `config.hooks[name]`) in `cwd`,
42
+ * best-effort. This is the SINGLE catch point for all lifecycle hooks:
43
+ *
44
+ * - MUST NEVER throw. No configured command → silent no-op. A failing command
45
+ * → `logger.error({hook,worktree,ticket,err}, 'lifecycle hook failed')` then
46
+ * return. So a hook failure never aborts dispatch or wedges a run in
47
+ * `claimed`.
48
+ * - The underlying shell command still fails honestly (a non-zero exit
49
+ * rejects); only the executor catches + logs + continues.
50
+ *
51
+ * `env`, when given, is the run's resolved environment (per-ticket env_vars
52
+ * merged with the core GAIA_* vars, GAIA-99), merged over the hook process's
53
+ * inherited env. Values may be sensitive — implementations must never log
54
+ * them (log key names only).
55
+ */
56
+ runHook(name: HookName, cwd: string, ctx: HookContext, env?: Record<string, string>): Promise<void>;
57
+ startRun(input: SpawnRunInput): Promise<SpawnedSession>;
58
+ /** Send C-c to every open (non-`(done)`) tab in the branch workspace and
59
+ * append ` (done)` to its label. Best-effort, non-destructive: tabs stay open
60
+ * for the human to close. */
61
+ retire(branch: string): Promise<void>;
62
+ /**
63
+ * Ask the agent in each open (non-`(done)`) tab of the branch workspace to
64
+ * exit gracefully by typing `/exit` + Enter into its pane — a clean shutdown
65
+ * vs {@link retire}'s C-c SIGINT. Called by the conductor's run-finalise pass
66
+ * once a run is done. Best-effort and non-destructive (tabs stay open); a
67
+ * no-op for non-persistent executors (no hosted agent pane), so the conductor
68
+ * gates the call on `capabilities().persistent`.
69
+ */
70
+ stopAgent(branch: string): Promise<void>;
71
+ /**
72
+ * Tear down the branch's entire worktree (git worktree + hosted workspace),
73
+ * reclaiming its disk. Called by the conductor's ticket-cleanup pass once a
74
+ * ticket is done. A no-op for non-persistent executors (no hosted workspace);
75
+ * the conductor gates the call on `capabilities().persistent`.
76
+ *
77
+ * `worktreePath` is the stable, identifier-derived worktree path (from the
78
+ * ticket's latest run). Prefer it over `branch` to resolve the worktree: the
79
+ * checked-out branch is mutable (the coding agent may rename/switch it), the
80
+ * path is not.
81
+ *
82
+ * Returns whether the worktree was actually present on THIS host and torn
83
+ * down (or reclaimed on disk) — i.e. whether the teardown happened locally.
84
+ * `false` means nothing matched here: the worktree is either already gone or
85
+ * lives on another conductor's host. The reaper uses this to avoid marking a
86
+ * ticket `cleaned_up` for a worktree it did not actually tear down (a
87
+ * cross-host false-teardown), leaving it for the host that physically holds
88
+ * it. A genuine failure still throws (surfaced as a teardown miss); a `false`
89
+ * return is a clean "not here", not an error.
90
+ */
91
+ removeWorktree(branch: string, worktreePath?: string): Promise<boolean>;
92
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ import type { DropSHPlugin } from 'dropsh/plugin';
2
+ import type { ConductorLogger } from '../core/logger.js';
3
+ import type { ConductorFileConfig } from '../types.js';
4
+ import type { GaiaAgent } from './agent/agent.js';
5
+ import type { GaiaExecutor } from './executor/executor.js';
6
+ import type { GaiaRemote } from './remote/remote.js';
7
+ import type { GaiaWorkspace } from './workspace/workspace.js';
8
+ /** Runtime dependencies handed to a plugin factory at selection time. */
9
+ export interface ExecutorDeps {
10
+ /** The in-scope conductor logger — the executor logs hook failures through it. */
11
+ logger: ConductorLogger;
12
+ }
13
+ export interface RemotePlugin extends DropSHPlugin {
14
+ readonly kind: 'remote';
15
+ readonly id: string;
16
+ readonly requiredModules: string[];
17
+ createRemote(config: ConductorFileConfig): Promise<GaiaRemote>;
18
+ }
19
+ export interface ExecutorPlugin extends DropSHPlugin {
20
+ readonly kind: 'executor';
21
+ readonly id: string;
22
+ readonly requiredModules: string[];
23
+ createExecutor(config: ConductorFileConfig, deps: ExecutorDeps): Promise<GaiaExecutor>;
24
+ }
25
+ export interface WorkspacePlugin extends DropSHPlugin {
26
+ readonly kind: 'workspace';
27
+ readonly id: string;
28
+ readonly requiredModules: string[];
29
+ createWorkspace(config: ConductorFileConfig): Promise<GaiaWorkspace>;
30
+ }
31
+ export interface AgentPlugin extends DropSHPlugin {
32
+ readonly kind: 'agent';
33
+ readonly id: string;
34
+ readonly requiredModules: string[];
35
+ createAgent(config: ConductorFileConfig): Promise<GaiaAgent>;
36
+ }
37
+ /** Resolves the remote from its named config slot. */
38
+ export declare function selectRemote(config: ConductorFileConfig): Promise<GaiaRemote>;
39
+ /** Resolves the executor from its named config slot, injecting the logger. */
40
+ export declare function selectExecutor(config: ConductorFileConfig, logger: ConductorLogger): Promise<GaiaExecutor>;
41
+ /** Resolves the workspace from its named config slot. */
42
+ export declare function selectWorkspace(config: ConductorFileConfig): Promise<GaiaWorkspace>;
43
+ /** Resolves the agent from its named config slot. */
44
+ export declare function selectAgent(config: ConductorFileConfig): Promise<GaiaAgent>;
@@ -0,0 +1,16 @@
1
+ /** Resolves the remote from its named config slot. */
2
+ export async function selectRemote(config) {
3
+ return config.remote.createRemote(config);
4
+ }
5
+ /** Resolves the executor from its named config slot, injecting the logger. */
6
+ export async function selectExecutor(config, logger) {
7
+ return config.executor.createExecutor(config, { logger });
8
+ }
9
+ /** Resolves the workspace from its named config slot. */
10
+ export async function selectWorkspace(config) {
11
+ return config.workspace.createWorkspace(config);
12
+ }
13
+ /** Resolves the agent from its named config slot. */
14
+ export async function selectAgent(config) {
15
+ return config.agent.createAgent(config);
16
+ }
@@ -0,0 +1,6 @@
1
+ export { basicAuthProvider } from './auth/basic.js';
2
+ export { selectAgent, selectExecutor, selectRemote, selectWorkspace, } from './plugins.js';
3
+ export { DrupalGaiaRemote, drupalRemote } from './remote/drupal.js';
4
+ export { FakeGaiaRemote, type FakeRemoteSeed, fakeRemote, } from './remote/fake.js';
5
+ export { FakeWorkspace, fakeWorkspace } from './workspace/fake.js';
6
+ export { GitWorkspace, gitWorkspace } from './workspace/git.js';
@@ -0,0 +1,6 @@
1
+ export { basicAuthProvider } from './auth/basic.js';
2
+ export { selectAgent, selectExecutor, selectRemote, selectWorkspace, } from './plugins.js';
3
+ export { DrupalGaiaRemote, drupalRemote } from './remote/drupal.js';
4
+ export { FakeGaiaRemote, fakeRemote, } from './remote/fake.js';
5
+ export { FakeWorkspace, fakeWorkspace } from './workspace/fake.js';
6
+ export { GitWorkspace, gitWorkspace } from './workspace/git.js';