@gaia-ai/core 0.4.3 → 0.4.5
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/index.d.ts +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/plugins/agent/agent.d.ts +32 -0
- package/dist/src/plugins/agent/agent.js +10 -1
- package/dist/src/plugins/remote/drupal.d.ts +3 -3
- package/dist/src/plugins/remote/drupal.js +21 -13
- package/dist/src/plugins/remote/fake.d.ts +12 -3
- package/dist/src/plugins/remote/fake.js +19 -5
- package/dist/src/plugins/remote/remote.d.ts +30 -8
- package/package.json +2 -2
package/dist/src/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { CommandRunner, ExecError, exec, setDefaultCommandRunner, } from './core
|
|
|
3
3
|
export { type ConductorLogger, createLogger } from './core/logger.js';
|
|
4
4
|
export { shellQuote } from './core/shell.js';
|
|
5
5
|
export { DEFAULT_SLUG_MAX_LENGTH, slugify } from './core/slug.js';
|
|
6
|
-
export type
|
|
6
|
+
export { type AgentFootprint, emptyAgentFootprint, type GaiaAgent, } from './plugins/agent/agent.js';
|
|
7
7
|
export type * from './plugins/executor/executor.js';
|
|
8
8
|
export type { AgentPlugin, ExecutorDeps, ExecutorPlugin, RemotePlugin, WorkspacePlugin, } from './plugins/plugins.js';
|
|
9
9
|
export type * from './plugins/remote/remote.js';
|
package/dist/src/index.js
CHANGED
|
@@ -3,4 +3,5 @@ export { CommandRunner, ExecError, exec, setDefaultCommandRunner, } from './core
|
|
|
3
3
|
export { createLogger } from './core/logger.js';
|
|
4
4
|
export { shellQuote } from './core/shell.js';
|
|
5
5
|
export { DEFAULT_SLUG_MAX_LENGTH, slugify } from './core/slug.js';
|
|
6
|
+
export { emptyAgentFootprint, } from './plugins/agent/agent.js';
|
|
6
7
|
export { loadInstructions } from './plugins/workspace/instructions.js';
|
|
@@ -1,3 +1,27 @@
|
|
|
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;
|
|
1
25
|
/**
|
|
2
26
|
* A GAIA agent: the program the conductor runs to work a ticket (e.g. claude).
|
|
3
27
|
* It knows how it is launched (CLI + model + flags) and where its per-run
|
|
@@ -17,4 +41,12 @@ export interface GaiaAgent {
|
|
|
17
41
|
* `worktreePath`. MUST return '' (never throw) when nothing is found.
|
|
18
42
|
*/
|
|
19
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;
|
|
20
52
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { JsonApiClient } from 'dropsh/plugin';
|
|
2
2
|
import type { RemotePlugin } from '../plugins.js';
|
|
3
|
-
import type { ActiveRun, ClaimedRun, ClaimOptions, ConductorRegistration, ConductorStatus, FinalizableRun, GaiaRemote, RunWriteAttributes, Ticket, UncleanTicket } from './remote.js';
|
|
3
|
+
import type { ActiveRun, ClaimedRun, ClaimOptions, ConductorRegistration, ConductorStatus, FinalizableRun, GaiaRemote, RunMetrics, RunWriteAttributes, Ticket, UncleanTicket } from './remote.js';
|
|
4
4
|
export declare class DrupalGaiaRemote implements GaiaRemote {
|
|
5
5
|
private readonly api;
|
|
6
6
|
constructor(api: JsonApiClient);
|
|
@@ -18,8 +18,8 @@ export declare class DrupalGaiaRemote implements GaiaRemote {
|
|
|
18
18
|
listConductors(owner?: 'me'): Promise<ConductorStatus[]>;
|
|
19
19
|
markRunning(uuid: string, attrs?: RunWriteAttributes): Promise<void>;
|
|
20
20
|
fetchFinalizableRuns(id: string): Promise<FinalizableRun[]>;
|
|
21
|
-
finalizeRun(uuid: string, log: string): Promise<void>;
|
|
22
|
-
fetchUncleanedTickets(): Promise<UncleanTicket[]>;
|
|
21
|
+
finalizeRun(uuid: string, log: string, metrics?: RunMetrics): Promise<void>;
|
|
22
|
+
fetchUncleanedTickets(id: string): Promise<UncleanTicket[]>;
|
|
23
23
|
closeTicket(uuid: string): Promise<void>;
|
|
24
24
|
markTicketCleanedUp(uuid: string): Promise<void>;
|
|
25
25
|
/**
|
|
@@ -204,35 +204,42 @@ export class DrupalGaiaRemote {
|
|
|
204
204
|
.where('conductor_id.machine_id', '=', id)
|
|
205
205
|
.where('state', '=', 'done')
|
|
206
206
|
.where('closed', '=', '0')
|
|
207
|
-
.fields(['worktree_path'])
|
|
207
|
+
.fields(['worktree_path', 'started_at'])
|
|
208
208
|
.page(100)
|
|
209
209
|
.list();
|
|
210
|
-
return rows.map((r) =>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
210
|
+
return rows.map((r) => {
|
|
211
|
+
const startedAt = r.attr('started_at');
|
|
212
|
+
return {
|
|
213
|
+
runUuid: r.id,
|
|
214
|
+
worktreePath: r.attr('worktree_path') ?? '',
|
|
215
|
+
...(typeof startedAt === 'number' ? { startedAt } : {}),
|
|
216
|
+
};
|
|
217
|
+
});
|
|
214
218
|
}
|
|
215
|
-
async finalizeRun(uuid, log) {
|
|
219
|
+
async finalizeRun(uuid, log, metrics) {
|
|
216
220
|
const t = Math.floor(Date.now() / 1000);
|
|
217
221
|
await this.api.update('gaia_run', uuid, {
|
|
218
222
|
attributes: {
|
|
219
223
|
closed: true,
|
|
220
224
|
closed_date: t,
|
|
221
225
|
...(log ? { log } : {}),
|
|
226
|
+
...(metrics ?? {}),
|
|
222
227
|
},
|
|
223
228
|
}); // NO state — the run is already done.
|
|
224
229
|
}
|
|
225
|
-
async fetchUncleanedTickets() {
|
|
230
|
+
async fetchUncleanedTickets(id) {
|
|
226
231
|
// Finished tickets whose worktree is not yet torn down (cleaned_up=0),
|
|
227
|
-
// driven by the durable `cleaned_up` flag (GAIA-89)
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
232
|
+
// driven by the durable `cleaned_up` flag (GAIA-89) AND scoped to this
|
|
233
|
+
// conductor (GAIA-121): only tickets assigned to `id` are loaded, so a
|
|
234
|
+
// ticket owned by another conductor never reaches the reaper loop. "Finished"
|
|
235
|
+
// = reached `done` OR already `closed`; the collection builder has no OR, so
|
|
236
|
+
// this is two AND-queries merged + de-duped by uuid. The `conductor_id`
|
|
237
|
+
// relationship carries the assigned conductor's `machine_id` — the same
|
|
238
|
+
// nested filter the sibling reaper queries (`fetchFinalizableRuns` etc.) use.
|
|
233
239
|
const fields = ['branch_name', 'state', 'closed'];
|
|
234
240
|
const done = await this.api
|
|
235
241
|
.collection('gaia_ticket')
|
|
242
|
+
.where('conductor_id.machine_id', '=', id)
|
|
236
243
|
.where('state', '=', 'done')
|
|
237
244
|
.where('cleaned_up', '=', '0')
|
|
238
245
|
.fields(fields)
|
|
@@ -240,6 +247,7 @@ export class DrupalGaiaRemote {
|
|
|
240
247
|
.list();
|
|
241
248
|
const closed = await this.api
|
|
242
249
|
.collection('gaia_ticket')
|
|
250
|
+
.where('conductor_id.machine_id', '=', id)
|
|
243
251
|
.where('closed', '=', '1')
|
|
244
252
|
.where('cleaned_up', '=', '0')
|
|
245
253
|
.fields(fields)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RemotePlugin } from '../plugins.js';
|
|
2
|
-
import type { ActiveRun, ClaimedRun, ClaimOptions, ConductorRegistration, ConductorStatus, FinalizableRun, GaiaRemote, RunWriteAttributes, Ticket, UncleanTicket } from './remote.js';
|
|
2
|
+
import type { ActiveRun, ClaimedRun, ClaimOptions, ConductorRegistration, ConductorStatus, FinalizableRun, GaiaRemote, RunMetrics, RunWriteAttributes, Ticket, UncleanTicket } from './remote.js';
|
|
3
3
|
export interface FakeSeedRun {
|
|
4
4
|
runUuid: string;
|
|
5
5
|
/** Numeric (drupal_internal__id) run id. Defaults to a stable 1-based counter when omitted. */
|
|
@@ -11,6 +11,8 @@ export interface FakeSeedRun {
|
|
|
11
11
|
handler?: string;
|
|
12
12
|
worktreePath?: string;
|
|
13
13
|
closed?: boolean;
|
|
14
|
+
/** Unix timestamp (seconds) the run started — drives footprint duration_s. */
|
|
15
|
+
startedAt?: number;
|
|
14
16
|
}
|
|
15
17
|
export interface FakeSeedTicket {
|
|
16
18
|
identifier: string;
|
|
@@ -27,6 +29,12 @@ export interface FakeSeedTicket {
|
|
|
27
29
|
closed?: boolean;
|
|
28
30
|
/** Whether the ticket's worktree is already torn down (cleaned_up flag). */
|
|
29
31
|
cleanedUp?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* machine_id of the conductor this ticket is assigned to (GAIA-121). Omit to
|
|
34
|
+
* mean "belongs to the querying conductor" (the common case for reaper tests);
|
|
35
|
+
* set an explicit value to test conductor-scoping, or `''` for unassigned.
|
|
36
|
+
*/
|
|
37
|
+
conductorId?: string;
|
|
30
38
|
}
|
|
31
39
|
export interface FakeRemoteSeed {
|
|
32
40
|
runs?: FakeSeedRun[];
|
|
@@ -41,6 +49,7 @@ export interface MarkRunningCall {
|
|
|
41
49
|
export interface FinalizeRunCall {
|
|
42
50
|
runUuid: string;
|
|
43
51
|
log: string;
|
|
52
|
+
metrics?: RunMetrics;
|
|
44
53
|
}
|
|
45
54
|
export declare class FakeGaiaRemote implements GaiaRemote {
|
|
46
55
|
readonly calls: {
|
|
@@ -75,8 +84,8 @@ export declare class FakeGaiaRemote implements GaiaRemote {
|
|
|
75
84
|
getRunTicketBranchName(runUuid: string): Promise<string>;
|
|
76
85
|
markRunning(runUuid: string, attrs?: RunWriteAttributes): Promise<void>;
|
|
77
86
|
fetchFinalizableRuns(_conductorId: string): Promise<FinalizableRun[]>;
|
|
78
|
-
finalizeRun(runUuid: string, log: string): Promise<void>;
|
|
79
|
-
fetchUncleanedTickets(): Promise<UncleanTicket[]>;
|
|
87
|
+
finalizeRun(runUuid: string, log: string, metrics?: RunMetrics): Promise<void>;
|
|
88
|
+
fetchUncleanedTickets(conductorId: string): Promise<UncleanTicket[]>;
|
|
80
89
|
closeTicket(uuid: string): Promise<void>;
|
|
81
90
|
markTicketCleanedUp(uuid: string): Promise<void>;
|
|
82
91
|
/** Worktree path of the ticket's most recently seeded run, or '' when none. */
|
|
@@ -33,6 +33,7 @@ export class FakeGaiaRemote {
|
|
|
33
33
|
...(r.worktreePath !== undefined
|
|
34
34
|
? { worktreePath: r.worktreePath }
|
|
35
35
|
: {}),
|
|
36
|
+
...(r.startedAt !== undefined ? { startedAt: r.startedAt } : {}),
|
|
36
37
|
...(r.closed !== undefined ? { closed: r.closed } : {}),
|
|
37
38
|
});
|
|
38
39
|
this.queue.push({
|
|
@@ -150,10 +151,18 @@ export class FakeGaiaRemote {
|
|
|
150
151
|
async fetchFinalizableRuns(_conductorId) {
|
|
151
152
|
return [...this.runs.values()]
|
|
152
153
|
.filter((r) => r.state === 'done' && !r.closed)
|
|
153
|
-
.map((r) => ({
|
|
154
|
+
.map((r) => ({
|
|
155
|
+
runUuid: r.runUuid,
|
|
156
|
+
worktreePath: r.worktreePath ?? '',
|
|
157
|
+
...(r.startedAt !== undefined ? { startedAt: r.startedAt } : {}),
|
|
158
|
+
}));
|
|
154
159
|
}
|
|
155
|
-
async finalizeRun(runUuid, log) {
|
|
156
|
-
this.calls.finalizeRun.push({
|
|
160
|
+
async finalizeRun(runUuid, log, metrics) {
|
|
161
|
+
this.calls.finalizeRun.push({
|
|
162
|
+
runUuid,
|
|
163
|
+
log,
|
|
164
|
+
...(metrics ? { metrics } : {}),
|
|
165
|
+
});
|
|
157
166
|
const run = this.runs.get(runUuid);
|
|
158
167
|
if (run) {
|
|
159
168
|
run.closed = true;
|
|
@@ -161,9 +170,14 @@ export class FakeGaiaRemote {
|
|
|
161
170
|
run.log = log;
|
|
162
171
|
}
|
|
163
172
|
}
|
|
164
|
-
async fetchUncleanedTickets() {
|
|
173
|
+
async fetchUncleanedTickets(conductorId) {
|
|
174
|
+
// Scoped to the querying conductor (GAIA-121). A seed without an explicit
|
|
175
|
+
// `conductorId` defaults to the querying conductor (the common reaper-test
|
|
176
|
+
// case); an explicit value scopes it, and `''` models an unassigned ticket.
|
|
165
177
|
return Object.entries(this.tickets)
|
|
166
|
-
.filter(([, t]) => (t.state === 'done' || t.closed) &&
|
|
178
|
+
.filter(([, t]) => (t.state === 'done' || t.closed) &&
|
|
179
|
+
!t.cleanedUp &&
|
|
180
|
+
(t.conductorId ?? conductorId) === conductorId)
|
|
167
181
|
.map(([ticketUuid, t]) => ({
|
|
168
182
|
ticketUuid,
|
|
169
183
|
branchName: t.branchName ?? `gaia/${t.identifier.toLowerCase()}`,
|
|
@@ -57,6 +57,25 @@ export interface FinalizableRun {
|
|
|
57
57
|
runUuid: string;
|
|
58
58
|
/** Absolute path of the per-run git worktree, or '' when unset. */
|
|
59
59
|
worktreePath: string;
|
|
60
|
+
/**
|
|
61
|
+
* Unix timestamp (seconds) the run started, or undefined when unset. The
|
|
62
|
+
* conductor derives the footprint `duration_s` as `now − startedAt` at close
|
|
63
|
+
* (GAIA-132).
|
|
64
|
+
*/
|
|
65
|
+
startedAt?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Per-run effort footprint the conductor writes onto `gaia_run` at close
|
|
69
|
+
* (GAIA-132). Six integer metrics: the five parsed from the agent transcript
|
|
70
|
+
* plus `duration_s` computed from `started_at → close`.
|
|
71
|
+
*/
|
|
72
|
+
export interface RunMetrics {
|
|
73
|
+
tokens: number;
|
|
74
|
+
duration_s: number;
|
|
75
|
+
agent_turns: number;
|
|
76
|
+
tool_calls: number;
|
|
77
|
+
user_prompts: number;
|
|
78
|
+
user_prompt_words: number;
|
|
60
79
|
}
|
|
61
80
|
/**
|
|
62
81
|
* A finished ticket whose worktree has not yet been torn down — the
|
|
@@ -123,19 +142,22 @@ export interface GaiaRemote {
|
|
|
123
142
|
*/
|
|
124
143
|
fetchFinalizableRuns(conductorId: string): Promise<FinalizableRun[]>;
|
|
125
144
|
/**
|
|
126
|
-
* Finalise a run: set closed + closed_date
|
|
127
|
-
*
|
|
145
|
+
* Finalise a run: set closed + closed_date, (when non-empty) log, and (when
|
|
146
|
+
* given) the per-run footprint metrics (GAIA-132). No state change — the run
|
|
147
|
+
* is already done.
|
|
128
148
|
*/
|
|
129
|
-
finalizeRun(uuid: string, log: string): Promise<void>;
|
|
149
|
+
finalizeRun(uuid: string, log: string, metrics?: RunMetrics): Promise<void>;
|
|
130
150
|
/**
|
|
131
151
|
* Finished tickets (state=done OR closed) whose worktree is not yet torn
|
|
132
152
|
* down (cleaned_up=0) — the conductor's teardown work list (GAIA-89). Driven
|
|
133
|
-
* by the durable `cleaned_up` flag
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
153
|
+
* by the durable `cleaned_up` flag AND scoped to the reaping conductor
|
|
154
|
+
* (GAIA-121): only tickets assigned to `conductorId` are loaded, so every
|
|
155
|
+
* ticket on the list is unambiguously this conductor's — a missing worktree
|
|
156
|
+
* means "already torn down on this host", not "belongs to another host". The
|
|
157
|
+
* same query backs the live tick and the standalone `gaia conductor reap`.
|
|
158
|
+
* Empty once every finished ticket this conductor owns is cleaned.
|
|
137
159
|
*/
|
|
138
|
-
fetchUncleanedTickets(): Promise<UncleanTicket[]>;
|
|
160
|
+
fetchUncleanedTickets(conductorId: string): Promise<UncleanTicket[]>;
|
|
139
161
|
/**
|
|
140
162
|
* Close a ticket: set closed=true + closed_date. Ticket-lifecycle only, and
|
|
141
163
|
* decoupled from teardown — a done ticket is closed even if its worktree
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaia-ai/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "GAIA global contract: plugin API, built-in remotes/workspaces/auth, shared primitives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"directory": "conductor/packages/core"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"dropsh": "^0.5.
|
|
29
|
+
"dropsh": "^0.5.6",
|
|
30
30
|
"pino": "^9.6.0",
|
|
31
31
|
"pino-pretty": "^13.0.0"
|
|
32
32
|
}
|