@gaia-ai/core 0.4.7 → 0.5.3

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.
@@ -5,7 +5,8 @@ export { shellQuote } from './core/shell.js';
5
5
  export { DEFAULT_SLUG_MAX_LENGTH, slugify } from './core/slug.js';
6
6
  export { type AgentFootprint, emptyAgentFootprint, type GaiaAgent, } from './plugins/agent/agent.js';
7
7
  export type * from './plugins/executor/executor.js';
8
- export type { AgentPlugin, ExecutorDeps, ExecutorPlugin, RemotePlugin, WorkspacePlugin, } from './plugins/plugins.js';
8
+ export type { AgentCandidate, AgentPlugin, ExecutorDeps, ExecutorPlugin, RemotePlugin, ResolvedAgent, WorkspacePlugin, } from './plugins/plugins.js';
9
+ export { selectAgent, selectAgents } from './plugins/plugins.js';
9
10
  export type * from './plugins/remote/remote.js';
10
11
  export { loadInstructions } from './plugins/workspace/instructions.js';
11
12
  export type { EnsuredWorkspace, GaiaWorkspace, } from './plugins/workspace/workspace.js';
package/dist/src/index.js CHANGED
@@ -4,4 +4,5 @@ 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
6
  export { emptyAgentFootprint, } from './plugins/agent/agent.js';
7
+ export { selectAgent, selectAgents } from './plugins/plugins.js';
7
8
  export { loadInstructions } from './plugins/workspace/instructions.js';
@@ -3,7 +3,7 @@ import type { ConductorLogger } from '../core/logger.js';
3
3
  import type { ConductorFileConfig } from '../types.js';
4
4
  import type { GaiaAgent } from './agent/agent.js';
5
5
  import type { GaiaExecutor } from './executor/executor.js';
6
- import type { GaiaRemote } from './remote/remote.js';
6
+ import type { GaiaRemote, Ticket } from './remote/remote.js';
7
7
  import type { GaiaWorkspace } from './workspace/workspace.js';
8
8
  /** Runtime dependencies handed to a plugin factory at selection time. */
9
9
  export interface ExecutorDeps {
@@ -40,5 +40,21 @@ export declare function selectRemote(config: ConductorFileConfig): Promise<GaiaR
40
40
  export declare function selectExecutor(config: ConductorFileConfig, logger: ConductorLogger): Promise<GaiaExecutor>;
41
41
  /** Resolves the workspace from its named config slot. */
42
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>;
43
+ /** A config-side agent choice: an agent plugin + an optional static priority over the ticket. */
44
+ export interface AgentCandidate {
45
+ agent: AgentPlugin;
46
+ priority?: (ticket: Ticket) => number;
47
+ }
48
+ /** A constructed candidate: the agent id, its GaiaAgent, and the config-side priority. */
49
+ export interface ResolvedAgent {
50
+ id: string;
51
+ agent: GaiaAgent;
52
+ priority?: (ticket: Ticket) => number;
53
+ }
54
+ /**
55
+ * Pick the agent for a ticket: highest `priority(ticket)` wins; a candidate with
56
+ * no `priority` scores lowest; ties resolve by config array order (stable sort).
57
+ */
58
+ export declare function selectAgent(candidates: ResolvedAgent[], ticket: Ticket): ResolvedAgent;
59
+ /** Constructs every agent candidate from the resolved config slot (single or array). */
60
+ export declare function selectAgents(config: ConductorFileConfig): Promise<ResolvedAgent[]>;
@@ -10,7 +10,33 @@ export async function selectExecutor(config, logger) {
10
10
  export async function selectWorkspace(config) {
11
11
  return config.workspace.createWorkspace(config);
12
12
  }
13
- /** Resolves the agent from its named config slot. */
14
- export async function selectAgent(config) {
15
- return config.agent.createAgent(config);
13
+ /** A missing `priority` scores strictly below any real number (AC-4). */
14
+ const DEFAULT_PRIORITY = Number.NEGATIVE_INFINITY;
15
+ /**
16
+ * Pick the agent for a ticket: highest `priority(ticket)` wins; a candidate with
17
+ * no `priority` scores lowest; ties resolve by config array order (stable sort).
18
+ */
19
+ export function selectAgent(candidates, ticket) {
20
+ const best = candidates
21
+ .map((c, index) => ({
22
+ c,
23
+ index,
24
+ score: c.priority ? c.priority(ticket) : DEFAULT_PRIORITY,
25
+ }))
26
+ .sort((a, b) => b.score - a.score || a.index - b.index)[0];
27
+ if (!best) {
28
+ throw new Error('selectAgent: no agent candidates configured');
29
+ }
30
+ return best.c;
31
+ }
32
+ /** Constructs every agent candidate from the resolved config slot (single or array). */
33
+ export async function selectAgents(config) {
34
+ const candidates = Array.isArray(config.agent)
35
+ ? config.agent
36
+ : [config.agent];
37
+ return Promise.all(candidates.map(async (c) => ({
38
+ id: c.agent.id,
39
+ agent: await c.agent.createAgent(config),
40
+ ...(c.priority ? { priority: c.priority } : {}),
41
+ })));
16
42
  }
@@ -1,5 +1,5 @@
1
1
  export { basicAuthProvider } from './auth/basic.js';
2
- export { selectAgent, selectExecutor, selectRemote, selectWorkspace, } from './plugins.js';
2
+ export { type AgentCandidate, type ResolvedAgent, selectAgent, selectAgents, selectExecutor, selectRemote, selectWorkspace, } from './plugins.js';
3
3
  export { DrupalGaiaRemote, drupalRemote } from './remote/drupal.js';
4
4
  export { FakeGaiaRemote, type FakeRemoteSeed, fakeRemote, } from './remote/fake.js';
5
5
  export { FakeWorkspace, fakeWorkspace } from './workspace/fake.js';
@@ -1,5 +1,5 @@
1
1
  export { basicAuthProvider } from './auth/basic.js';
2
- export { selectAgent, selectExecutor, selectRemote, selectWorkspace, } from './plugins.js';
2
+ export { selectAgent, selectAgents, selectExecutor, selectRemote, selectWorkspace, } from './plugins.js';
3
3
  export { DrupalGaiaRemote, drupalRemote } from './remote/drupal.js';
4
4
  export { FakeGaiaRemote, fakeRemote, } from './remote/fake.js';
5
5
  export { FakeWorkspace, fakeWorkspace } from './workspace/fake.js';
@@ -68,11 +68,30 @@ export class DrupalGaiaRemote {
68
68
  }
69
69
  async getTicket(uuid) {
70
70
  // The agent self-reads the ticket + its comments at run start (GAIA-112),
71
- // so getTicket only needs the ticket's own attributes for dispatch no
72
- // ?include=comments sideload.
73
- const doc = (await this.api.get(`gaia_ticket/gaia_ticket/${uuid}`));
71
+ // so getTicket needs no ?include=comments but it DOES sideload labels +
72
+ // environments (GAIA-144) so a config-side priority(ticket) can read them
73
+ // without fetching (AC-5).
74
+ const doc = (await this.api.get(`gaia_ticket/gaia_ticket/${uuid}?include=labels,environments`));
74
75
  const attrs = doc.data?.attributes ?? {};
75
76
  const issueUrl = typeof attrs.origin === 'string' ? attrs.origin : undefined;
77
+ const included = Array.isArray(doc.included) ? doc.included : [];
78
+ const byId = new Map(included.map((r) => [`${r.type}:${r.id}`, r]));
79
+ const refs = (name) => {
80
+ const data = doc.data?.relationships?.[name]?.data;
81
+ return Array.isArray(data) ? data : [];
82
+ };
83
+ const labels = refs('labels')
84
+ .map((ref) => byId.get(`${ref.type}:${ref.id}`)?.attributes?.name)
85
+ .filter((n) => typeof n === 'string');
86
+ const environments = refs('environments')
87
+ .map((ref) => {
88
+ const a = byId.get(`${ref.type}:${ref.id}`)?.attributes ?? {};
89
+ return {
90
+ name: typeof a.name === 'string' ? a.name : '',
91
+ tier: typeof a.tier === 'string' ? a.tier : '',
92
+ };
93
+ })
94
+ .filter((e) => e.name !== '');
76
95
  return {
77
96
  uuid: doc.data?.id ?? uuid,
78
97
  identifier: typeof attrs.identifier === 'string' ? attrs.identifier : uuid,
@@ -87,6 +106,8 @@ export class DrupalGaiaRemote {
87
106
  ? { effectiveEnvVars: attrs.effective_env_vars }
88
107
  : {}),
89
108
  ...(issueUrl ? { issueUrl } : {}),
109
+ labels,
110
+ environments,
90
111
  };
91
112
  }
92
113
  async getRunWorktree(uuid) {
@@ -195,6 +216,7 @@ export class DrupalGaiaRemote {
195
216
  state: 'running',
196
217
  heartbeat: t,
197
218
  ...(attrs?.worktree_path ? { worktree_path: attrs.worktree_path } : {}),
219
+ ...(attrs?.agent ? { agent: attrs.agent } : {}),
198
220
  },
199
221
  });
200
222
  }
@@ -204,7 +226,7 @@ export class DrupalGaiaRemote {
204
226
  .where('conductor_id.machine_id', '=', id)
205
227
  .where('state', '=', 'done')
206
228
  .where('closed', '=', '0')
207
- .fields(['worktree_path', 'started_at'])
229
+ .fields(['worktree_path', 'started_at', 'agent'])
208
230
  .page(100)
209
231
  .list();
210
232
  return rows.map((r) => {
@@ -213,6 +235,7 @@ export class DrupalGaiaRemote {
213
235
  runUuid: r.id,
214
236
  worktreePath: r.attr('worktree_path') ?? '',
215
237
  ...(typeof startedAt === 'number' ? { startedAt } : {}),
238
+ agent: r.attr('agent') ?? '',
216
239
  };
217
240
  });
218
241
  }
@@ -13,6 +13,8 @@ export interface FakeSeedRun {
13
13
  closed?: boolean;
14
14
  /** Unix timestamp (seconds) the run started — drives footprint duration_s. */
15
15
  startedAt?: number;
16
+ /** Id of the agent that ran (GAIA-144) — routes footprint parsing at finalize. */
17
+ agent?: string;
16
18
  }
17
19
  export interface FakeSeedTicket {
18
20
  identifier: string;
@@ -25,6 +27,13 @@ export interface FakeSeedTicket {
25
27
  effectiveEnvVars?: string;
26
28
  url?: string;
27
29
  workflow?: string;
30
+ /** Label term names sideloaded for agent selection (GAIA-144). */
31
+ labels?: string[];
32
+ /** Environments sideloaded for agent selection (GAIA-144). */
33
+ environments?: {
34
+ name: string;
35
+ tier: string;
36
+ }[];
28
37
  /** Whether the ticket is already closed (its lifecycle wound up). */
29
38
  closed?: boolean;
30
39
  /** Whether the ticket's worktree is already torn down (cleaned_up flag). */
@@ -44,6 +53,7 @@ export interface MarkRunningCall {
44
53
  runUuid: string;
45
54
  attrs?: {
46
55
  worktree_path?: string;
56
+ agent?: string;
47
57
  };
48
58
  }
49
59
  export interface FinalizeRunCall {
@@ -34,6 +34,7 @@ export class FakeGaiaRemote {
34
34
  ? { worktreePath: r.worktreePath }
35
35
  : {}),
36
36
  ...(r.startedAt !== undefined ? { startedAt: r.startedAt } : {}),
37
+ ...(r.agent !== undefined ? { agent: r.agent } : {}),
37
38
  ...(r.closed !== undefined ? { closed: r.closed } : {}),
38
39
  });
39
40
  this.queue.push({
@@ -104,6 +105,8 @@ export class FakeGaiaRemote {
104
105
  ...(t.baseBranch ? { baseBranch: t.baseBranch } : {}),
105
106
  ...(t.effectiveEnvVars ? { effectiveEnvVars: t.effectiveEnvVars } : {}),
106
107
  ...(t.url ? { issueUrl: t.url } : {}),
108
+ labels: t.labels ?? [],
109
+ environments: t.environments ?? [],
107
110
  };
108
111
  }
109
112
  async getRunWorktree(runUuid) {
@@ -136,6 +139,7 @@ export class FakeGaiaRemote {
136
139
  ...(attrs.worktree_path
137
140
  ? { worktree_path: attrs.worktree_path }
138
141
  : {}),
142
+ ...(attrs.agent ? { agent: attrs.agent } : {}),
139
143
  },
140
144
  }
141
145
  : {}),
@@ -146,6 +150,9 @@ export class FakeGaiaRemote {
146
150
  if (attrs?.worktree_path) {
147
151
  run.worktreePath = attrs.worktree_path;
148
152
  }
153
+ if (attrs?.agent) {
154
+ run.agent = attrs.agent;
155
+ }
149
156
  }
150
157
  }
151
158
  async fetchFinalizableRuns(_conductorId) {
@@ -155,6 +162,7 @@ export class FakeGaiaRemote {
155
162
  runUuid: r.runUuid,
156
163
  worktreePath: r.worktreePath ?? '',
157
164
  ...(r.startedAt !== undefined ? { startedAt: r.startedAt } : {}),
165
+ agent: r.agent ?? '',
158
166
  }));
159
167
  }
160
168
  async finalizeRun(runUuid, log, metrics) {
@@ -34,6 +34,13 @@ export interface Ticket {
34
34
  */
35
35
  effectiveEnvVars?: string;
36
36
  issueUrl?: string;
37
+ /** Ticket label term names (gaia_labels vocab), sideloaded for agent selection. Empty when none. */
38
+ labels: string[];
39
+ /** Ticket environments (gaia_environment refs), each as name + tier, sideloaded for agent selection. Empty when none. */
40
+ environments: {
41
+ name: string;
42
+ tier: string;
43
+ }[];
37
44
  }
38
45
  export interface ActiveRun {
39
46
  runUuid: string;
@@ -63,6 +70,8 @@ export interface FinalizableRun {
63
70
  * (GAIA-132).
64
71
  */
65
72
  startedAt?: number;
73
+ /** Id of the agent that ran (GAIA-144); routes footprint parsing at finalize. '' / undefined when unset. */
74
+ agent?: string;
66
75
  }
67
76
  /**
68
77
  * Per-run effort footprint the conductor writes onto `gaia_run` at close
@@ -103,6 +112,8 @@ export interface UncleanTicket {
103
112
  export interface RunWriteAttributes {
104
113
  /** Absolute path of the per-run git worktree (set by the conductor). */
105
114
  worktree_path?: string;
115
+ /** Id of the agent the conductor chose for this run (GAIA-144), for footprint routing. */
116
+ agent?: string;
106
117
  }
107
118
  export interface GaiaRemote {
108
119
  /** Upserts by machine_id; sets status=online. Returns the Drupal entity uuid. */
@@ -1,5 +1,5 @@
1
1
  import type { DropSHPlugin } from 'dropsh/plugin';
2
- import type { AgentPlugin, ExecutorPlugin, RemotePlugin, WorkspacePlugin } from './plugins/plugins.js';
2
+ import type { AgentCandidate, ExecutorPlugin, RemotePlugin, WorkspacePlugin } from './plugins/plugins.js';
3
3
  /** Normalized single conductor. */
4
4
  export interface ConductorSettings {
5
5
  /** Default: `${project} @ ${checkoutRoot}`. */
@@ -44,7 +44,8 @@ export interface ConductorSettings {
44
44
  export interface ConductorFileConfig extends ConductorSettings {
45
45
  remote: RemotePlugin;
46
46
  executor: ExecutorPlugin;
47
- agent: AgentPlugin;
47
+ /** The agent slot: a single candidate or an array of candidates (shape preserved as authored). Selection normalizes + picks one per dispatch. */
48
+ agent: AgentCandidate | AgentCandidate[];
48
49
  workspace: WorkspacePlugin;
49
50
  /** dropsh-layer plugins (auth etc.); separate from the GAIA slots. */
50
51
  plugins?: DropSHPlugin[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaia-ai/core",
3
- "version": "0.4.7",
3
+ "version": "0.5.3",
4
4
  "description": "GAIA global contract: plugin API, built-in remotes/workspaces/auth, shared primitives.",
5
5
  "type": "module",
6
6
  "license": "MIT",