@gaia-ai/gaia 0.2.0 → 0.4.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/bin/gaia +1 -1
- package/dist/src/plugins-barrel.d.ts +1 -0
- package/dist/src/plugins-barrel.js +1 -0
- package/package.json +10 -16
- package/dist/src/cli/gaia.d.ts +0 -24
- package/dist/src/cli/gaia.js +0 -572
- package/dist/src/cli/init.d.ts +0 -74
- package/dist/src/cli/init.js +0 -220
- package/dist/src/cli/local-registry.d.ts +0 -14
- package/dist/src/cli/local-registry.js +0 -56
- package/dist/src/config.d.ts +0 -13
- package/dist/src/config.js +0 -186
- package/dist/src/core/conductor-id.d.ts +0 -1
- package/dist/src/core/conductor-id.js +0 -8
- package/dist/src/core/conductor.d.ts +0 -47
- package/dist/src/core/conductor.js +0 -287
- package/dist/src/core/exec.d.ts +0 -33
- package/dist/src/core/exec.js +0 -65
- package/dist/src/core/logger.d.ts +0 -31
- package/dist/src/core/logger.js +0 -36
- package/dist/src/core/slug.d.ts +0 -9
- package/dist/src/core/slug.js +0 -18
- package/dist/src/index.d.ts +0 -16
- package/dist/src/index.js +0 -9
- package/dist/src/plugin-api.d.ts +0 -7
- package/dist/src/plugin-api.js +0 -3
- package/dist/src/plugins/agent/agent.d.ts +0 -20
- package/dist/src/plugins/agent/agent.js +0 -1
- package/dist/src/plugins/auth/basic.d.ts +0 -11
- package/dist/src/plugins/auth/basic.js +0 -35
- package/dist/src/plugins/executor/executor.d.ts +0 -61
- package/dist/src/plugins/executor/executor.js +0 -1
- package/dist/src/plugins/plugins.d.ts +0 -38
- package/dist/src/plugins/plugins.js +0 -16
- 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 -47
- package/dist/src/plugins/remote/drupal.js +0 -337
- package/dist/src/plugins/remote/fake.d.ts +0 -81
- package/dist/src/plugins/remote/fake.js +0 -203
- package/dist/src/plugins/remote/remote.d.ts +0 -143
- package/dist/src/plugins/remote/remote.js +0 -1
- package/dist/src/plugins/workspace/fake.d.ts +0 -9
- package/dist/src/plugins/workspace/fake.js +0 -19
- package/dist/src/plugins/workspace/git.d.ts +0 -53
- package/dist/src/plugins/workspace/git.js +0 -113
- 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 -33
- package/dist/src/plugins/workspace/workspace.js +0 -1
- package/dist/src/types.d.ts +0 -51
- package/dist/src/types.js +0 -1
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
import { conductorId } from './conductor-id.js';
|
|
2
|
-
function sleep(ms, signal) {
|
|
3
|
-
return new Promise((resolve) => {
|
|
4
|
-
if (signal?.aborted) {
|
|
5
|
-
resolve();
|
|
6
|
-
return;
|
|
7
|
-
}
|
|
8
|
-
const timer = setTimeout(() => {
|
|
9
|
-
signal?.removeEventListener('abort', onAbort);
|
|
10
|
-
resolve();
|
|
11
|
-
}, ms);
|
|
12
|
-
const onAbort = () => {
|
|
13
|
-
clearTimeout(timer);
|
|
14
|
-
resolve();
|
|
15
|
-
};
|
|
16
|
-
signal?.addEventListener('abort', onAbort, { once: true });
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Formats a ticket's comments into the `{comments}` prompt block, oldest first.
|
|
21
|
-
* The conductor injects this so a review→coding bounce reaches the coder with the
|
|
22
|
-
* review summary in hand, independent of whether the agent remembers to fetch it.
|
|
23
|
-
*
|
|
24
|
-
* The body is the raw gaia_rich markup — the agent reads HTML fine, so we don't
|
|
25
|
-
* strip it (a tag/entity stripper is a maintenance footgun that buys nothing for
|
|
26
|
-
* an LLM reader).
|
|
27
|
-
*/
|
|
28
|
-
function renderComments(comments) {
|
|
29
|
-
if (comments.length === 0) {
|
|
30
|
-
return '(no comments on this ticket yet)';
|
|
31
|
-
}
|
|
32
|
-
return comments
|
|
33
|
-
.map((c, i) => {
|
|
34
|
-
const when = c.created ? ` (${c.created})` : '';
|
|
35
|
-
return `[#${i + 1}] type=${c.type}${when}\n${c.body}`;
|
|
36
|
-
})
|
|
37
|
-
.join('\n\n---\n\n');
|
|
38
|
-
}
|
|
39
|
-
function renderPrompt(template, values) {
|
|
40
|
-
return template
|
|
41
|
-
.replaceAll('{identifier}', values.identifier)
|
|
42
|
-
.replaceAll('{state}', values.state)
|
|
43
|
-
.replaceAll('{runUuid}', values.runUuid)
|
|
44
|
-
.replaceAll('{comments}', renderComments(values.comments));
|
|
45
|
-
}
|
|
46
|
-
export class Conductor {
|
|
47
|
-
config;
|
|
48
|
-
remote;
|
|
49
|
-
executor;
|
|
50
|
-
workspace;
|
|
51
|
-
agent;
|
|
52
|
-
logger;
|
|
53
|
-
checkoutRoot;
|
|
54
|
-
uuid = null;
|
|
55
|
-
constructor(config, remote, executor, workspace, agent, logger, checkoutRoot = process.cwd()) {
|
|
56
|
-
this.config = config;
|
|
57
|
-
this.remote = remote;
|
|
58
|
-
this.executor = executor;
|
|
59
|
-
this.workspace = workspace;
|
|
60
|
-
this.agent = agent;
|
|
61
|
-
this.logger = logger;
|
|
62
|
-
this.checkoutRoot = checkoutRoot;
|
|
63
|
-
}
|
|
64
|
-
get id() {
|
|
65
|
-
return this.config.machine_id ?? conductorId(this.checkoutRoot);
|
|
66
|
-
}
|
|
67
|
-
/** This conductor's registration payload, built from config. */
|
|
68
|
-
registration() {
|
|
69
|
-
return {
|
|
70
|
-
id: this.id,
|
|
71
|
-
project: this.config.project,
|
|
72
|
-
states: this.config.states,
|
|
73
|
-
workspace: this.checkoutRoot,
|
|
74
|
-
label: this.config.label,
|
|
75
|
-
max_parallel: this.config.max_parallel,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
async start() {
|
|
79
|
-
this.uuid = await this.remote.registerConductor(this.registration());
|
|
80
|
-
this.logger.info({ conductorId: this.id, uuid: this.uuid, project: this.config.project }, 'conductor started');
|
|
81
|
-
}
|
|
82
|
-
async tick() {
|
|
83
|
-
if (!this.uuid)
|
|
84
|
-
throw new Error('not started');
|
|
85
|
-
let count = await this.remote.activeRunCount(this.id);
|
|
86
|
-
// A heartbeat is the server-side self-heal: it upserts by machine_id (so it
|
|
87
|
-
// never 404s and recreates a vanished registration) and refreshes the lease
|
|
88
|
-
// — no client-side re-register, no separate status read.
|
|
89
|
-
await this.remote.heartbeat(this.registration(), count, this.config.lease_seconds);
|
|
90
|
-
this.logger.info({ conductorId: this.id, active: count }, 'conductor tick');
|
|
91
|
-
await this.finalizeDoneRuns();
|
|
92
|
-
await this.cleanupDoneTickets();
|
|
93
|
-
let claimed = 0;
|
|
94
|
-
while (count < this.config.max_parallel) {
|
|
95
|
-
const run = await this.remote.claimNext({
|
|
96
|
-
leaseSeconds: this.config.lease_seconds,
|
|
97
|
-
conductorId: this.id,
|
|
98
|
-
});
|
|
99
|
-
if (!run)
|
|
100
|
-
break;
|
|
101
|
-
this.logger.info({ conductorId: this.id, run: run.runUuid, ticket: run.ticketUuid }, 'claimed run');
|
|
102
|
-
try {
|
|
103
|
-
await this.dispatch(run);
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
// A failed dispatch must NOT terminalise the run. The run lifecycle is
|
|
107
|
-
// server-owned; a client-side releaseRun('done') here fabricated a
|
|
108
|
-
// terminal state on failure, which reopened the ticket for claiming and
|
|
109
|
-
// re-dispatched it every tick — the GAIA-41 runaway. Leaving the run
|
|
110
|
-
// `claimed` lets the server's same-state claim conflict
|
|
111
|
-
// (TicketClaimService::retireSupersededRuns) block a re-claim of the
|
|
112
|
-
// same ticket, so we just log and move on. A run stuck `claimed` on a
|
|
113
|
-
// persistently-failing dispatch is freed by the lease-expiry reaper
|
|
114
|
-
// (separate follow-up), not here.
|
|
115
|
-
this.logger.error({ run: run.runUuid, err: String(err) }, 'dispatch failed');
|
|
116
|
-
}
|
|
117
|
-
count += 1;
|
|
118
|
-
claimed += 1;
|
|
119
|
-
}
|
|
120
|
-
if (claimed === 0) {
|
|
121
|
-
this.logger.info({
|
|
122
|
-
conductorId: this.id,
|
|
123
|
-
active: count,
|
|
124
|
-
capacity: this.config.max_parallel,
|
|
125
|
-
}, count >= this.config.max_parallel
|
|
126
|
-
? 'at capacity, nothing claimed'
|
|
127
|
-
: 'idle, nothing to claim');
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* One-shot finalisation: for each of this conductor's runs that is
|
|
132
|
-
* state=done but not yet closed, read the agent transcript from its worktree
|
|
133
|
-
* and write it as the final log, then mark the run closed. Keyed off the
|
|
134
|
-
* durable `state=done AND closed=false` query — no in-memory state, so a
|
|
135
|
-
* restarted conductor still finalises any unclosed done run on its next tick.
|
|
136
|
-
*/
|
|
137
|
-
async finalizeDoneRuns() {
|
|
138
|
-
const runs = await this.remote.fetchFinalizableRuns(this.id);
|
|
139
|
-
for (const r of runs) {
|
|
140
|
-
try {
|
|
141
|
-
// Ask the agent to exit gracefully (/exit) before capturing its log.
|
|
142
|
-
// Best-effort and gated on the persistent capability (only hosted
|
|
143
|
-
// executors have a live agent pane) — mirrors removeWorktree's gate.
|
|
144
|
-
// A stop failure must never block finalisation.
|
|
145
|
-
if (this.executor.capabilities().persistent) {
|
|
146
|
-
const branch = await this.remote.getRunTicketBranchName(r.runUuid);
|
|
147
|
-
if (branch) {
|
|
148
|
-
try {
|
|
149
|
-
await this.executor.stopAgent(branch);
|
|
150
|
-
}
|
|
151
|
-
catch (err) {
|
|
152
|
-
this.logger.warn({ run: r.runUuid, err: String(err) }, 'agent stop failed');
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
const log = r.worktreePath
|
|
157
|
-
? await this.agent.getRunLog(r.worktreePath)
|
|
158
|
-
: '';
|
|
159
|
-
await this.remote.finalizeRun(r.runUuid, log);
|
|
160
|
-
this.logger.info({ run: r.runUuid }, 'run finalised');
|
|
161
|
-
}
|
|
162
|
-
catch (err) {
|
|
163
|
-
this.logger.warn({ run: r.runUuid, err: String(err) }, 'run finalise failed');
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* One-shot ticket cleanup, symmetric to {@link finalizeDoneRuns} on the
|
|
169
|
-
* ticket axis: for each of this conductor's tickets that is state=done but not
|
|
170
|
-
* yet closed, run the workspace's `after_done` hook in the ticket's worktree,
|
|
171
|
-
* tear the worktree down (herdr), then mark the ticket closed. Keyed off the
|
|
172
|
-
* durable `state=done AND closed=false` query — no in-memory state, so a
|
|
173
|
-
* restarted conductor still cleans up.
|
|
174
|
-
*
|
|
175
|
-
* Error handling mirrors finalise: each ticket is isolated in a try/catch so
|
|
176
|
-
* one failure never wedges the tick. The `after_done` hook itself is
|
|
177
|
-
* best-effort — its failure is logged but does not block the teardown/close
|
|
178
|
-
* (reclaiming the worktree matters more than a clean environment teardown).
|
|
179
|
-
*/
|
|
180
|
-
async cleanupDoneTickets() {
|
|
181
|
-
const tickets = await this.remote.fetchFinalizableTickets(this.id);
|
|
182
|
-
for (const t of tickets) {
|
|
183
|
-
try {
|
|
184
|
-
if (t.worktreePath) {
|
|
185
|
-
try {
|
|
186
|
-
await this.workspace.afterDone(t.worktreePath);
|
|
187
|
-
}
|
|
188
|
-
catch (err) {
|
|
189
|
-
this.logger.warn({ ticket: t.ticketUuid, err: String(err) }, 'ticket after_done hook failed');
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
// Only persistent executors host a workspace to tear down; the abstract
|
|
193
|
-
// capability keeps this executor-agnostic (mirrors the release path).
|
|
194
|
-
// Pass the stable worktreePath so the executor resolves the worktree by
|
|
195
|
-
// path, not by the mutable checked-out branch (which the coding agent
|
|
196
|
-
// may have renamed). Teardown is idempotent: a worktree herdr no longer
|
|
197
|
-
// tracks is a no-op (already gone) or a direct on-disk reclaim, not a
|
|
198
|
-
// failure — foreign worktrees in herdr's machine-global list are never
|
|
199
|
-
// surfaced. A genuine teardown failure is best-effort like after_done:
|
|
200
|
-
// logged but must not block the close (a still-closed ticket beats a
|
|
201
|
-
// wedged cleanup loop).
|
|
202
|
-
if (this.executor.capabilities().persistent) {
|
|
203
|
-
try {
|
|
204
|
-
await this.executor.removeWorktree(t.branchName, t.worktreePath || undefined);
|
|
205
|
-
}
|
|
206
|
-
catch (err) {
|
|
207
|
-
this.logger.warn({
|
|
208
|
-
ticket: t.ticketUuid,
|
|
209
|
-
branch: t.branchName,
|
|
210
|
-
worktreePath: t.worktreePath,
|
|
211
|
-
err: String(err),
|
|
212
|
-
}, 'worktree teardown failed');
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
await this.remote.closeTicket(t.ticketUuid);
|
|
216
|
-
this.logger.info({ ticket: t.ticketUuid }, 'ticket cleaned up');
|
|
217
|
-
}
|
|
218
|
-
catch (err) {
|
|
219
|
-
this.logger.warn({ ticket: t.ticketUuid, err: String(err) }, 'ticket cleanup failed');
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
async dispatch(run) {
|
|
224
|
-
const t = await this.remote.getTicket(run.ticketUuid);
|
|
225
|
-
const baseRef = t.baseBranch ? `origin/${t.baseBranch}` : undefined;
|
|
226
|
-
const ws = await this.workspace.ensure(t.identifier, t.branchName, baseRef);
|
|
227
|
-
await this.workspace.beforeRun(ws.path);
|
|
228
|
-
await this.executor.retire(t.branchName);
|
|
229
|
-
const prompt = ws.instructions
|
|
230
|
-
? renderPrompt(this.config.prompt, {
|
|
231
|
-
identifier: t.identifier,
|
|
232
|
-
state: t.state || 'triage',
|
|
233
|
-
runUuid: run.runUuid,
|
|
234
|
-
comments: t.comments,
|
|
235
|
-
})
|
|
236
|
-
: '';
|
|
237
|
-
await this.executor.startRun({
|
|
238
|
-
ticket: {
|
|
239
|
-
uuid: t.uuid,
|
|
240
|
-
identifier: t.identifier,
|
|
241
|
-
title: t.title,
|
|
242
|
-
branchName: t.branchName,
|
|
243
|
-
state: t.state,
|
|
244
|
-
...(t.issueUrl ? { url: t.issueUrl } : {}),
|
|
245
|
-
},
|
|
246
|
-
run: { uuid: run.runUuid, id: run.runId, handler: run.handler },
|
|
247
|
-
workspacePath: ws.path,
|
|
248
|
-
instructions: ws.instructions,
|
|
249
|
-
command: this.agent.launchCommand(prompt),
|
|
250
|
-
env: {
|
|
251
|
-
GAIA_URL: this.config.site.base_url,
|
|
252
|
-
GAIA_ID: run.ticketUuid,
|
|
253
|
-
GAIA_RUN_UUID: run.runUuid,
|
|
254
|
-
WORKSPACE_ROOT: this.checkoutRoot,
|
|
255
|
-
...(t.issueUrl ? { TICKET_URL: t.issueUrl } : {}),
|
|
256
|
-
},
|
|
257
|
-
});
|
|
258
|
-
this.logger.info({
|
|
259
|
-
ticket: t.identifier,
|
|
260
|
-
run: run.runUuid,
|
|
261
|
-
state: t.state,
|
|
262
|
-
workspace: ws.path,
|
|
263
|
-
}, 'dispatched run');
|
|
264
|
-
// The conductor — not the agent — knows the per-run git worktree path, so
|
|
265
|
-
// it persists `worktree_path` to gaia_run here alongside the running state.
|
|
266
|
-
await this.remote.markRunning(run.runUuid, { worktree_path: ws.path });
|
|
267
|
-
}
|
|
268
|
-
async serve(signal) {
|
|
269
|
-
await this.pollLoop(signal);
|
|
270
|
-
}
|
|
271
|
-
async pollLoop(signal) {
|
|
272
|
-
while (!signal?.aborted) {
|
|
273
|
-
try {
|
|
274
|
-
await this.tick();
|
|
275
|
-
}
|
|
276
|
-
catch (err) {
|
|
277
|
-
this.logger.error({ conductorId: this.id, err: String(err) }, 'tick failed');
|
|
278
|
-
}
|
|
279
|
-
await sleep(this.config.poll_interval_ms, signal);
|
|
280
|
-
}
|
|
281
|
-
// NB: the poll loop never writes status=offline itself. A graceful stop
|
|
282
|
-
// (`gaia conductor stop`) writes offline directly server-side; a crash or
|
|
283
|
-
// hard-kill leaves a stale lease that the Drupal cron reaper flips to
|
|
284
|
-
// offline (gaia_core cron → ConductorReaper). Liveness is thus always
|
|
285
|
-
// derived from status + last_seen/lease_expires_at, never from the loop.
|
|
286
|
-
}
|
|
287
|
-
}
|
package/dist/src/core/exec.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
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 declare class CommandRunner {
|
|
12
|
-
private readonly logger;
|
|
13
|
-
constructor(logger: ConductorLogger);
|
|
14
|
-
run(file: string, args: string[], opts?: {
|
|
15
|
-
cwd?: string;
|
|
16
|
-
}): Promise<string>;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Replace the module-level singleton runner used by {@link exec}.
|
|
20
|
-
*
|
|
21
|
-
* Note: out-of-package plugins (e.g. `@gaia-ai/plugin-herdr`) import `exec` via
|
|
22
|
-
* the `@gaia-ai/gaia/plugin` barrel, which resolves to the built `dist/`
|
|
23
|
-
* copy of this module. This means `setDefaultCommandRunner` only shares the
|
|
24
|
-
* runner with those plugins when the conductor itself runs from `dist`
|
|
25
|
-
* (production / after `pnpm build`). Under `tsx`/dev, the entry point loads
|
|
26
|
-
* this module from source while the plugin loads it from dist — two separate
|
|
27
|
-
* module instances — so plugin exec calls fall back to the noop logger.
|
|
28
|
-
* This is functionally harmless; logging only.
|
|
29
|
-
*/
|
|
30
|
-
export declare function setDefaultCommandRunner(r: CommandRunner): void;
|
|
31
|
-
export declare function exec(file: string, args: string[], opts?: {
|
|
32
|
-
cwd?: string;
|
|
33
|
-
}): Promise<string>;
|
package/dist/src/core/exec.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
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
|
-
try {
|
|
25
|
-
const { stdout } = await execFileAsync(file, args, {
|
|
26
|
-
encoding: 'utf8',
|
|
27
|
-
...opts,
|
|
28
|
-
});
|
|
29
|
-
this.logger.debug({ file, args, cwd: opts.cwd }, 'exec ok');
|
|
30
|
-
return stdout;
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
const e = err;
|
|
34
|
-
const exitCode = typeof e.code === 'number' ? e.code : null;
|
|
35
|
-
const stderr = e.stderr ?? '';
|
|
36
|
-
this.logger.error({ file, args, cwd: opts.cwd, exitCode, stderr }, 'exec failed');
|
|
37
|
-
throw new ExecError(file, args, exitCode, stderr, { cause: err });
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const noopLogger = {
|
|
42
|
-
debug() { },
|
|
43
|
-
info() { },
|
|
44
|
-
warn() { },
|
|
45
|
-
error() { },
|
|
46
|
-
};
|
|
47
|
-
let defaultRunner = new CommandRunner(noopLogger);
|
|
48
|
-
/**
|
|
49
|
-
* Replace the module-level singleton runner used by {@link exec}.
|
|
50
|
-
*
|
|
51
|
-
* Note: out-of-package plugins (e.g. `@gaia-ai/plugin-herdr`) import `exec` via
|
|
52
|
-
* the `@gaia-ai/gaia/plugin` barrel, which resolves to the built `dist/`
|
|
53
|
-
* copy of this module. This means `setDefaultCommandRunner` only shares the
|
|
54
|
-
* runner with those plugins when the conductor itself runs from `dist`
|
|
55
|
-
* (production / after `pnpm build`). Under `tsx`/dev, the entry point loads
|
|
56
|
-
* this module from source while the plugin loads it from dist — two separate
|
|
57
|
-
* module instances — so plugin exec calls fall back to the noop logger.
|
|
58
|
-
* This is functionally harmless; logging only.
|
|
59
|
-
*/
|
|
60
|
-
export function setDefaultCommandRunner(r) {
|
|
61
|
-
defaultRunner = r;
|
|
62
|
-
}
|
|
63
|
-
export function exec(file, args, opts = {}) {
|
|
64
|
-
return defaultRunner.run(file, args, opts);
|
|
65
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
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;
|
package/dist/src/core/logger.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
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
|
-
}
|
package/dist/src/core/slug.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
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;
|
package/dist/src/core/slug.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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
|
-
}
|
package/dist/src/index.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type { GaiaCliDeps } from './cli/gaia.js';
|
|
2
|
-
export { main, runGaiaCli } from './cli/gaia.js';
|
|
3
|
-
export type { ConductorRegistryEntry } from './cli/local-registry.js';
|
|
4
|
-
export { DEFAULT_AGENT_PROMPT, loadConductorConfig } from './config.js';
|
|
5
|
-
export { Conductor } from './core/conductor.js';
|
|
6
|
-
export { conductorId } from './core/conductor-id.js';
|
|
7
|
-
export type { GaiaExecutor } from './plugins/executor/executor.js';
|
|
8
|
-
export type { ExecutorPlugin, RemotePlugin, WorkspacePlugin, } from './plugins/plugins.js';
|
|
9
|
-
export { selectExecutor, selectRemote, selectWorkspace, } from './plugins/plugins.js';
|
|
10
|
-
export { DrupalGaiaRemote, drupalRemote } from './plugins/remote/drupal.js';
|
|
11
|
-
export { FakeGaiaRemote, fakeRemote } from './plugins/remote/fake.js';
|
|
12
|
-
export type { GaiaRemote } from './plugins/remote/remote.js';
|
|
13
|
-
export { FakeWorkspace, fakeWorkspace } from './plugins/workspace/fake.js';
|
|
14
|
-
export { GitWorkspace, gitWorkspace } from './plugins/workspace/git.js';
|
|
15
|
-
export type { GaiaWorkspace } from './plugins/workspace/workspace.js';
|
|
16
|
-
export type * from './types.js';
|
package/dist/src/index.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { main, runGaiaCli } from './cli/gaia.js';
|
|
2
|
-
export { DEFAULT_AGENT_PROMPT, loadConductorConfig } from './config.js';
|
|
3
|
-
export { Conductor } from './core/conductor.js';
|
|
4
|
-
export { conductorId } from './core/conductor-id.js';
|
|
5
|
-
export { selectExecutor, selectRemote, selectWorkspace, } from './plugins/plugins.js';
|
|
6
|
-
export { DrupalGaiaRemote, drupalRemote } from './plugins/remote/drupal.js';
|
|
7
|
-
export { FakeGaiaRemote, fakeRemote } from './plugins/remote/fake.js';
|
|
8
|
-
export { FakeWorkspace, fakeWorkspace } from './plugins/workspace/fake.js';
|
|
9
|
-
export { GitWorkspace, gitWorkspace } from './plugins/workspace/git.js';
|
package/dist/src/plugin-api.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { CommandRunner, ExecError, exec, setDefaultCommandRunner, } from './core/exec.js';
|
|
2
|
-
export { DEFAULT_SLUG_MAX_LENGTH, slugify } from './core/slug.js';
|
|
3
|
-
export type { GaiaAgent } from './plugins/agent/agent.js';
|
|
4
|
-
export type * from './plugins/executor/executor.js';
|
|
5
|
-
export type { AgentPlugin, ExecutorPlugin, RemotePlugin, WorkspacePlugin, } from './plugins/plugins.js';
|
|
6
|
-
export { loadInstructions } from './plugins/workspace/instructions.js';
|
|
7
|
-
export type { EnsuredWorkspace, GaiaWorkspace, } from './plugins/workspace/workspace.js';
|
package/dist/src/plugin-api.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A GAIA agent: the program the conductor runs to work a ticket (e.g. claude).
|
|
3
|
-
* It knows how it is launched (CLI + model + flags) and where its per-run
|
|
4
|
-
* transcript/log lives — so the conductor stays agent-agnostic and the CLI can
|
|
5
|
-
* attach the run log on release without agent-specific knowledge.
|
|
6
|
-
*/
|
|
7
|
-
export interface GaiaAgent {
|
|
8
|
-
/** Stable id, e.g. 'claude'. */
|
|
9
|
-
readonly id: string;
|
|
10
|
-
/**
|
|
11
|
-
* Build the full agent CLI invocation for a GAIA prompt. With an empty
|
|
12
|
-
* prompt, returns the bare launch command (no prompt argument).
|
|
13
|
-
*/
|
|
14
|
-
launchCommand(prompt: string): string;
|
|
15
|
-
/**
|
|
16
|
-
* Locate and read this agent's run transcript for a run that executed in
|
|
17
|
-
* `worktreePath`. MUST return '' (never throw) when nothing is found.
|
|
18
|
-
*/
|
|
19
|
-
getRunLog(worktreePath: string): Promise<string>;
|
|
20
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,11 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
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
|
-
export interface GaiaExecutor {
|
|
31
|
-
id: string;
|
|
32
|
-
capabilities(): ExecutorCapabilities;
|
|
33
|
-
startRun(input: SpawnRunInput): Promise<SpawnedSession>;
|
|
34
|
-
/** Send C-c to every open (non-`(done)`) tab in the branch workspace and
|
|
35
|
-
* append ` (done)` to its label. Best-effort, non-destructive: tabs stay open
|
|
36
|
-
* for the human to close. */
|
|
37
|
-
retire(branch: string): Promise<void>;
|
|
38
|
-
/**
|
|
39
|
-
* Ask the agent in each open (non-`(done)`) tab of the branch workspace to
|
|
40
|
-
* exit gracefully by typing `/exit` + Enter into its pane — a clean shutdown
|
|
41
|
-
* vs {@link retire}'s C-c SIGINT. Called by the conductor's run-finalise pass
|
|
42
|
-
* once a run is done. Best-effort and non-destructive (tabs stay open); a
|
|
43
|
-
* no-op for non-persistent executors (no hosted agent pane), so the conductor
|
|
44
|
-
* gates the call on `capabilities().persistent`.
|
|
45
|
-
*/
|
|
46
|
-
stopAgent(branch: string): Promise<void>;
|
|
47
|
-
/**
|
|
48
|
-
* Tear down the branch's entire worktree (git worktree + hosted workspace),
|
|
49
|
-
* reclaiming its disk. Called by the conductor's ticket-cleanup pass once a
|
|
50
|
-
* ticket is done. A no-op for non-persistent executors (no hosted workspace);
|
|
51
|
-
* the conductor gates the call on `capabilities().persistent`.
|
|
52
|
-
*
|
|
53
|
-
* `worktreePath` is the stable, identifier-derived worktree path (from the
|
|
54
|
-
* ticket's latest run). Prefer it over `branch` to resolve the worktree: the
|
|
55
|
-
* checked-out branch is mutable (the coding agent may rename/switch it), the
|
|
56
|
-
* path is not. Implementations MUST NOT silently succeed when no worktree
|
|
57
|
-
* matches — surface the miss (throw with the wanted branch/path and what the
|
|
58
|
-
* executor actually lists) so it is visible rather than a silent skip.
|
|
59
|
-
*/
|
|
60
|
-
removeWorktree(branch: string, worktreePath?: string): Promise<void>;
|
|
61
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|