@gaia-ai/conductor 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.
@@ -1,4 +1,5 @@
1
1
  import { type ConductorFileConfig, type ConductorLogger, type GaiaAgent, type GaiaExecutor, type GaiaRemote, type GaiaWorkspace } from '@gaia-ai/core';
2
+ import { Command } from 'commander';
2
3
  /** Test seam: inject any subset of dependencies. */
3
4
  export interface GaiaCliDeps {
4
5
  remote?: GaiaRemote;
@@ -15,5 +16,6 @@ export declare function parseHerdrJson(output: string, command: string): unknown
15
16
  * when unauthenticated.
16
17
  */
17
18
  export declare function ensureAuthenticated(config: ConductorFileConfig, logger: ConductorLogger): Promise<boolean>;
19
+ export declare function buildProgram(deps: GaiaCliDeps): Command;
18
20
  export declare function runGaiaCli(argv: string[], deps?: GaiaCliDeps): Promise<void>;
19
21
  export declare function main(argv: string[]): Promise<void>;
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { createInterface } from 'node:readline';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { CommandRunner, conductorId, createLogger, exec, setDefaultCommandRunner, } from '@gaia-ai/core';
5
+ import { CommandRunner, createLogger, exec, setDefaultCommandRunner, } from '@gaia-ai/core';
6
6
  import { selectAgent, selectExecutor, selectRemote, selectWorkspace, } from '@gaia-ai/core/plugins';
7
7
  import { Command } from 'commander';
8
8
  import { authStatus, buildProgram as buildDropshProgram } from 'dropsh';
@@ -32,13 +32,17 @@ function checkoutRootOf(config) {
32
32
  return dirname(config.config_path);
33
33
  }
34
34
  /**
35
- * The conductor's stable identity (gaia_conductor.machine_id). The loader
36
- * always resolves this (config override or hostname+path hash), so lifecycle
37
- * commands read it here instead of re-deriving the hash otherwise a pinned
38
- * machine_id and the CLI's id would diverge.
35
+ * The conductor's stable identity (gaia_conductor.machine_id). machine_id is
36
+ * required the loader (loadConductorConfig) throws without it so lifecycle
37
+ * commands read it here, never re-derive. A config not built by the loader that
38
+ * somehow lacks it fails loudly rather than silently minting a divergent id.
39
39
  */
40
40
  function conductorIdOf(config) {
41
- return config.machine_id ?? conductorId(checkoutRootOf(config));
41
+ const id = config.machine_id;
42
+ if (id === undefined || id.trim() === '') {
43
+ throw new Error('conductor config has no machine_id');
44
+ }
45
+ return id;
42
46
  }
43
47
  /** Read the `conductor`-level --log-level / --log-sink flags from a subcommand. */
44
48
  function logOptsOf(cmd) {
@@ -388,9 +392,23 @@ function resolveCliVersion() {
388
392
  }
389
393
  return '0.0.0';
390
394
  }
391
- function buildProgram(deps) {
395
+ export function buildProgram(deps) {
392
396
  const program = new Command();
393
- program.name('gaia').description('GAIA conductor + client CLI');
397
+ program
398
+ .name('gaia')
399
+ .description('GAIA conductor + client CLI')
400
+ .option('--conductor <name>', 'select a named .gaia/<name>.config.js (default: the sole config; env $GAIA_CONDUCTOR)');
401
+ // GAIA-126: a repo may hold several named .gaia/<stem>.config.js conductors.
402
+ // resolveConfigPath already honours $GAIA_CONDUCTOR; thread the global
403
+ // --conductor flag into it (flag wins over env) so every command selects the
404
+ // named config without per-command wiring. A per-invocation flag beats any
405
+ // ambient env for this run.
406
+ program.hook('preAction', () => {
407
+ const name = program.opts().conductor;
408
+ if (typeof name === 'string' && name !== '') {
409
+ process.env.GAIA_CONDUCTOR = name;
410
+ }
411
+ });
394
412
  const cliVersion = resolveCliVersion();
395
413
  program.version(cliVersion); // -V, --version
396
414
  program
@@ -1,4 +1,4 @@
1
- import { type ConductorFileConfig } from '@gaia-ai/core';
1
+ import type { ConductorFileConfig } from '@gaia-ai/core';
2
2
  /**
3
3
  * Default agent prompt - the GAIA run contract. One run works EXACTLY one state;
4
4
  * the agent must stop instead of running the whole flow in one session. The run
@@ -28,13 +28,17 @@ import { type ConductorFileConfig } from '@gaia-ai/core';
28
28
  */
29
29
  export declare const DEFAULT_AGENT_PROMPT: string;
30
30
  /**
31
- * Resolve the conductor config path from `cwd`. An explicit `--config`
32
- * override or `$GAIA_CONDUCTOR_CONFIG` wins verbatim (short-circuit, no
33
- * filesystem walk). Otherwise walk from `cwd` root-ward (git/eslint style) to
34
- * the nearest ancestor holding `.gaia/conductor.config.js` and return that
35
- * absolute path — so any subdirectory of a project/worktree resolves the same
36
- * config. If none is found up to the filesystem root, throw a clear, actionable
37
- * error instead of leaking a raw "Cannot find module" from a later import().
31
+ * Resolve the conductor config path from `cwd`.
32
+ *
33
+ * 1. An explicit `--config` / `$GAIA_CONDUCTOR_CONFIG` wins verbatim (no walk).
34
+ * 2. Otherwise walk root-ward to the nearest `.gaia/` dir holding `*.config.js`
35
+ * — so any subdirectory of a project/worktree resolves the same dir.
36
+ * - A `--conductor <name>` / `$GAIA_CONDUCTOR` selector resolves
37
+ * `<gaiaDir>/<name>.config.js` (error listing the stems if absent).
38
+ * - No selector: exactly one config → use it (back-compat: the lone
39
+ * `conductor.config.js`); many → error naming the stems + the selector.
40
+ * 3. No `.gaia/` config anywhere up the tree → an actionable `gaia init` error
41
+ * (never leak a raw "Cannot find module" from a later import()).
38
42
  */
39
- export declare function resolveConfigPath(override?: string, cwd?: string): string;
43
+ export declare function resolveConfigPath(override?: string, cwd?: string, conductorName?: string): string;
40
44
  export declare function loadConductorConfig(configFile: string): Promise<ConductorFileConfig>;
@@ -1,8 +1,7 @@
1
- import { existsSync } from 'node:fs';
1
+ import { existsSync, readdirSync } from 'node:fs';
2
2
  import { createRequire } from 'node:module';
3
3
  import { dirname, join, resolve } from 'node:path';
4
4
  import { pathToFileURL } from 'node:url';
5
- import { conductorId, } from '@gaia-ai/core';
6
5
  /**
7
6
  * Default agent prompt - the GAIA run contract. One run works EXACTLY one state;
8
7
  * the agent must stop instead of running the whole flow in one session. The run
@@ -123,13 +122,19 @@ async function resolveSlot(value, kind, configPath) {
123
122
  /**
124
123
  * Resolve the `plugins[]` array: descriptor entries are constructed, already-
125
124
  * constructed entries pass through. No kind-guard (plugins are not slotted).
125
+ *
126
+ * A factory may return one plugin OR an array of plugins — an aggregator built
127
+ * with dropsh's `composePlugins(a(), b())` returns the flat child list. dropsh's
128
+ * own `loadConfig` flattens such a nested entry one level; we mirror that here so
129
+ * `buildProgram({ plugins })` — which reads each entry's hooks/renderers per
130
+ * top-level element and does NOT recurse — sees every child.
126
131
  */
127
132
  async function resolvePlugins(raw, configPath) {
128
133
  if (!Array.isArray(raw)) {
129
134
  return [];
130
135
  }
131
136
  const resolved = await Promise.all(raw.map((entry) => isPluginDescriptor(entry) ? loadNamedPlugin(entry, configPath) : entry));
132
- return resolved;
137
+ return resolved.flat();
133
138
  }
134
139
  function isRecord(value) {
135
140
  return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -149,35 +154,70 @@ function optionalPositiveInteger(value, fallback, key) {
149
154
  }
150
155
  return value;
151
156
  }
152
- /** The conductor config lives at .gaia/conductor.config.js inside a project root. */
153
- const CONFIG_RELATIVE_PATH = join('.gaia', 'conductor.config.js');
157
+ /** A repo's config files live in this dir, one `<stem>.config.js` per conductor. */
158
+ const GAIA_DIR = '.gaia';
159
+ /** List the `*.config.js` stems in a `.gaia/` dir (e.g. `shop.config.js` → `shop`). */
160
+ function configStems(gaiaDir) {
161
+ return readdirSync(gaiaDir)
162
+ .filter((f) => f.endsWith('.config.js'))
163
+ .map((f) => f.slice(0, -'.config.js'.length))
164
+ .sort();
165
+ }
154
166
  /**
155
- * Resolve the conductor config path from `cwd`. An explicit `--config`
156
- * override or `$GAIA_CONDUCTOR_CONFIG` wins verbatim (short-circuit, no
157
- * filesystem walk). Otherwise walk from `cwd` root-ward (git/eslint style) to
158
- * the nearest ancestor holding `.gaia/conductor.config.js` and return that
159
- * absolute path — so any subdirectory of a project/worktree resolves the same
160
- * config. If none is found up to the filesystem root, throw a clear, actionable
161
- * error instead of leaking a raw "Cannot find module" from a later import().
167
+ * Walk from `cwd` root-ward (git/eslint style) to the nearest ancestor whose
168
+ * `.gaia/` dir holds at least one `*.config.js`; return that `.gaia/` dir, or
169
+ * `undefined` if none is found up to the filesystem root.
162
170
  */
163
- export function resolveConfigPath(override, cwd = process.cwd()) {
164
- const explicit = override ?? process.env.GAIA_CONDUCTOR_CONFIG;
165
- if (explicit) {
166
- return explicit;
167
- }
171
+ function findGaiaDir(cwd) {
168
172
  let dir = resolve(cwd);
169
173
  for (;;) {
170
- const candidate = join(dir, CONFIG_RELATIVE_PATH);
171
- if (existsSync(candidate)) {
172
- return candidate;
174
+ const gaiaDir = join(dir, GAIA_DIR);
175
+ if (existsSync(gaiaDir) && configStems(gaiaDir).length > 0) {
176
+ return gaiaDir;
173
177
  }
174
178
  const parent = dirname(dir);
175
179
  if (parent === dir) {
176
- break;
180
+ return undefined;
177
181
  }
178
182
  dir = parent;
179
183
  }
180
- throw new Error(`no .gaia/conductor.config.js found from ${cwd} upward; run \`gaia init\``);
184
+ }
185
+ /**
186
+ * Resolve the conductor config path from `cwd`.
187
+ *
188
+ * 1. An explicit `--config` / `$GAIA_CONDUCTOR_CONFIG` wins verbatim (no walk).
189
+ * 2. Otherwise walk root-ward to the nearest `.gaia/` dir holding `*.config.js`
190
+ * — so any subdirectory of a project/worktree resolves the same dir.
191
+ * - A `--conductor <name>` / `$GAIA_CONDUCTOR` selector resolves
192
+ * `<gaiaDir>/<name>.config.js` (error listing the stems if absent).
193
+ * - No selector: exactly one config → use it (back-compat: the lone
194
+ * `conductor.config.js`); many → error naming the stems + the selector.
195
+ * 3. No `.gaia/` config anywhere up the tree → an actionable `gaia init` error
196
+ * (never leak a raw "Cannot find module" from a later import()).
197
+ */
198
+ export function resolveConfigPath(override, cwd = process.cwd(), conductorName) {
199
+ const explicit = override ?? process.env.GAIA_CONDUCTOR_CONFIG;
200
+ if (explicit) {
201
+ return explicit;
202
+ }
203
+ const gaiaDir = findGaiaDir(cwd);
204
+ if (gaiaDir === undefined) {
205
+ throw new Error(`no .gaia/conductor.config.js found from ${cwd} upward; run \`gaia init\``);
206
+ }
207
+ const stems = configStems(gaiaDir);
208
+ const name = conductorName ?? process.env.GAIA_CONDUCTOR;
209
+ if (name !== undefined && name !== '') {
210
+ const candidate = join(gaiaDir, `${name}.config.js`);
211
+ if (!existsSync(candidate)) {
212
+ throw new Error(`no conductor '${name}' in ${gaiaDir}; available: ${stems.join(', ')}`);
213
+ }
214
+ return candidate;
215
+ }
216
+ if (stems.length === 1) {
217
+ return join(gaiaDir, `${stems[0]}.config.js`);
218
+ }
219
+ throw new Error(`${stems.length} conductors in ${gaiaDir} (${stems.join(', ')}); ` +
220
+ 'select one with --conductor <name> or $GAIA_CONDUCTOR');
181
221
  }
182
222
  export async function loadConductorConfig(configFile) {
183
223
  const configPath = resolve(configFile);
@@ -194,14 +234,15 @@ export async function loadConductorConfig(configFile) {
194
234
  throw new Error('conductor config requires non-empty states');
195
235
  }
196
236
  const states = config.states.map((state) => requireNonEmptyString(state, 'states'));
197
- const checkoutRoot = dirname(configPath);
198
- // Resolve the effective machine_id once (config override or hostname+path
199
- // hash) so the id is defined in a single place the CLI lifecycle commands
200
- // and registration all read config.machine_id, never re-derive it. The
201
- // conductor label defaults to this id (a conductor is identified by it).
202
- const machineId = typeof config.machine_id === 'string' && config.machine_id.trim() !== ''
203
- ? config.machine_id
204
- : conductorId(checkoutRoot);
237
+ // machine_id is required — the config MUST set it; there is no derived
238
+ // fallback. The id is defined in a single place (config.machine_id), which
239
+ // the CLI lifecycle commands and registration all read, never re-derive. The
240
+ // conductor label defaults to it (a conductor is identified by it). A repo's
241
+ // committed config composes it from the machine context
242
+ // (`${user_id}-${machine_id}-${project}`); with several named configs in one
243
+ // .gaia/ (GAIA-126) each composes its own from its own project, so co-located
244
+ // conductors get distinct identities with no filename-based fallback.
245
+ const machineId = requireNonEmptyString(config.machine_id, 'machine_id');
205
246
  const label = typeof config.label === 'string' && config.label.trim() !== ''
206
247
  ? config.label
207
248
  : machineId;
@@ -38,27 +38,25 @@ export declare class Conductor {
38
38
  * flag via {@link GaiaRemote.fetchUncleanedTickets}: every ticket that is
39
39
  * finished (state=done OR closed) but not yet cleaned. The SAME reconciliation
40
40
  * runs on every tick AND standalone as `gaia conductor reap`, and the query is
41
- * NOT machine-scoped — so it catches the orphans a live-tick-only path
42
- * structurally cannot (RC2/RC3): the conductor was down when the ticket hit
43
- * `done`, crashed mid-cleanup, or the ticket's `conductor_id` was reassigned.
44
- * A foreign (non-gaia) worktree has no gaia ticket, so it is never on the work
45
- * list and never touched foreign-safety falls out of the DB-driven model
46
- * with no host enumeration.
41
+ * scoped to THIS conductor (GAIA-121) — so it catches the orphans a
42
+ * live-tick-only path structurally cannot (the conductor was down when the
43
+ * ticket hit `done`, or crashed mid-cleanup) for its OWN tickets. A foreign
44
+ * (non-gaia) worktree has no gaia ticket, so it is never on the work list; a
45
+ * ticket assigned to another conductor is filtered out at the query — both are
46
+ * left untouched with no host enumeration.
47
47
  *
48
48
  * Per ticket, close is DECOUPLED from teardown (no withholding): a done ticket
49
49
  * is closed at once as a lifecycle step, independent of whether its worktree
50
- * teardown then succeeds. Teardown is best-effort + verified — `cleaned_up` is
51
- * set only when the worktree was actually torn down ON THIS HOST. Since the
52
- * query is not machine-scoped, a conductor may see a finished ticket whose
53
- * worktree lives on ANOTHER host; `removeWorktree` returns `false` there (a
54
- * clean "not here") and the reaper DEFERS it does not flag the ticket
55
- * cleaned, leaving the teardown to the host that physically holds the
56
- * worktree. That gate is what keeps the not-machine-scoped query from
57
- * cross-host-false-teardowning a still-live workspace. A teardown that throws
58
- * logs loudly and leaves `cleaned_up=0`, so the next reconciliation retries;
59
- * one miss never orphans the workspace (RC1). Re-running on an already-cleaned
60
- * ticket is a no-op — it is off the list (idempotent). Each ticket is isolated
61
- * in a try/catch so one failure never aborts the rest.
50
+ * teardown then succeeds. Because the list is conductor-scoped, every ticket's
51
+ * worktree belongs on THIS host: `removeWorktree` returning `false` means the
52
+ * directory was already deleted out of band (nothing left to remove), which is
53
+ * itself a completed teardown flag `cleaned_up`. There is no cross-host defer
54
+ * (GAIA-121) the manufactured ambiguity that required it is gone once the
55
+ * query no longer loads foreign tickets. A teardown that THROWS logs loudly and
56
+ * leaves `cleaned_up=0`, so the next reconciliation retries; one miss never
57
+ * orphans the workspace (RC1). Re-running on an already-cleaned ticket is a
58
+ * no-op it is off the list (idempotent). Each ticket is isolated in a
59
+ * try/catch so one failure never aborts the rest.
62
60
  *
63
61
  * The `after_done` hook runs through the executor (GAIA-84), which owns the
64
62
  * best-effort policy — it logs+swallows a hook failure and never throws, so
@@ -194,8 +194,22 @@ export class Conductor {
194
194
  const log = r.worktreePath
195
195
  ? await this.agent.getRunLog(r.worktreePath)
196
196
  : '';
197
- await this.remote.finalizeRun(r.runUuid, log);
198
- this.logger.info({ run: r.runUuid }, 'run finalised');
197
+ // Footprint (GAIA-132): the agent parses its own transcript for the
198
+ // effort metrics (transcript format is agent-specific); the conductor
199
+ // adds duration_s from started_at → now. The conductor owns the run, so
200
+ // this write always lands (the agent session could not).
201
+ const parsed = this.agent.parseFootprint(log);
202
+ const now = Math.floor(Date.now() / 1000);
203
+ const metrics = {
204
+ tokens: parsed.tokens,
205
+ duration_s: r.startedAt !== undefined ? Math.max(0, now - r.startedAt) : 0,
206
+ agent_turns: parsed.agent_turns,
207
+ tool_calls: parsed.tool_calls,
208
+ user_prompts: parsed.user_prompts,
209
+ user_prompt_words: parsed.user_prompt_words,
210
+ };
211
+ await this.remote.finalizeRun(r.runUuid, log, metrics);
212
+ this.logger.info({ run: r.runUuid, footprint: metrics }, 'run finalised');
199
213
  }
200
214
  catch (err) {
201
215
  this.logger.warn({ run: r.runUuid, err: String(err) }, 'run finalise failed');
@@ -208,34 +222,32 @@ export class Conductor {
208
222
  * flag via {@link GaiaRemote.fetchUncleanedTickets}: every ticket that is
209
223
  * finished (state=done OR closed) but not yet cleaned. The SAME reconciliation
210
224
  * runs on every tick AND standalone as `gaia conductor reap`, and the query is
211
- * NOT machine-scoped — so it catches the orphans a live-tick-only path
212
- * structurally cannot (RC2/RC3): the conductor was down when the ticket hit
213
- * `done`, crashed mid-cleanup, or the ticket's `conductor_id` was reassigned.
214
- * A foreign (non-gaia) worktree has no gaia ticket, so it is never on the work
215
- * list and never touched foreign-safety falls out of the DB-driven model
216
- * with no host enumeration.
225
+ * scoped to THIS conductor (GAIA-121) — so it catches the orphans a
226
+ * live-tick-only path structurally cannot (the conductor was down when the
227
+ * ticket hit `done`, or crashed mid-cleanup) for its OWN tickets. A foreign
228
+ * (non-gaia) worktree has no gaia ticket, so it is never on the work list; a
229
+ * ticket assigned to another conductor is filtered out at the query — both are
230
+ * left untouched with no host enumeration.
217
231
  *
218
232
  * Per ticket, close is DECOUPLED from teardown (no withholding): a done ticket
219
233
  * is closed at once as a lifecycle step, independent of whether its worktree
220
- * teardown then succeeds. Teardown is best-effort + verified — `cleaned_up` is
221
- * set only when the worktree was actually torn down ON THIS HOST. Since the
222
- * query is not machine-scoped, a conductor may see a finished ticket whose
223
- * worktree lives on ANOTHER host; `removeWorktree` returns `false` there (a
224
- * clean "not here") and the reaper DEFERS it does not flag the ticket
225
- * cleaned, leaving the teardown to the host that physically holds the
226
- * worktree. That gate is what keeps the not-machine-scoped query from
227
- * cross-host-false-teardowning a still-live workspace. A teardown that throws
228
- * logs loudly and leaves `cleaned_up=0`, so the next reconciliation retries;
229
- * one miss never orphans the workspace (RC1). Re-running on an already-cleaned
230
- * ticket is a no-op — it is off the list (idempotent). Each ticket is isolated
231
- * in a try/catch so one failure never aborts the rest.
234
+ * teardown then succeeds. Because the list is conductor-scoped, every ticket's
235
+ * worktree belongs on THIS host: `removeWorktree` returning `false` means the
236
+ * directory was already deleted out of band (nothing left to remove), which is
237
+ * itself a completed teardown flag `cleaned_up`. There is no cross-host defer
238
+ * (GAIA-121) the manufactured ambiguity that required it is gone once the
239
+ * query no longer loads foreign tickets. A teardown that THROWS logs loudly and
240
+ * leaves `cleaned_up=0`, so the next reconciliation retries; one miss never
241
+ * orphans the workspace (RC1). Re-running on an already-cleaned ticket is a
242
+ * no-op it is off the list (idempotent). Each ticket is isolated in a
243
+ * try/catch so one failure never aborts the rest.
232
244
  *
233
245
  * The `after_done` hook runs through the executor (GAIA-84), which owns the
234
246
  * best-effort policy — it logs+swallows a hook failure and never throws, so
235
247
  * the core needs no per-call-site try/catch around it.
236
248
  */
237
249
  async reap() {
238
- const tickets = await this.remote.fetchUncleanedTickets();
250
+ const tickets = await this.remote.fetchUncleanedTickets(this.id);
239
251
  for (const t of tickets) {
240
252
  try {
241
253
  // Lifecycle: close a done+unclosed ticket at once, decoupled from the
@@ -259,9 +271,12 @@ export class Conductor {
259
271
  });
260
272
  }
261
273
  if (this.executor.capabilities().persistent) {
262
- let removed;
263
274
  try {
264
- removed = await this.executor.removeWorktree(t.branchName, t.worktreePath || undefined);
275
+ // The work list is scoped to this conductor (GAIA-121), so its
276
+ // worktree belongs on THIS host. Whether removeWorktree tears one
277
+ // down or finds it already gone (`false`), teardown is complete —
278
+ // the boolean is irrelevant, only a THROW (a real failure) matters.
279
+ await this.executor.removeWorktree(t.branchName, t.worktreePath || undefined);
265
280
  }
266
281
  catch (err) {
267
282
  this.logger.warn({
@@ -272,20 +287,6 @@ export class Conductor {
272
287
  }, 'worktree teardown failed');
273
288
  continue; // leave cleaned_up=0 → the next reconciliation retries.
274
289
  }
275
- // A recorded worktree path that was NOT present on this host belongs
276
- // to another conductor's host (the query is not machine-scoped, so we
277
- // see every finished ticket). Do NOT flag it cleaned — that would be a
278
- // cross-host false-teardown, orphaning a still-live workspace. Defer:
279
- // the host that physically holds it reaps + flags it. A ticket with no
280
- // recorded path (nothing hosted to locate) falls through to cleaned.
281
- if (t.worktreePath && !removed) {
282
- this.logger.info({
283
- ticket: t.ticketUuid,
284
- branch: t.branchName,
285
- worktreePath: t.worktreePath,
286
- }, 'worktree not on this host — deferring teardown to its host');
287
- continue;
288
- }
289
290
  }
290
291
  // Teardown verified locally (or nothing hosted to tear down): flag it
291
292
  // cleaned so it drops off the work list.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaia-ai/conductor",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "GAIA conductor engine + CLI: registers, claims tickets via JSON:API, dispatches agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,10 +25,10 @@
25
25
  "directory": "conductor/packages/conductor"
26
26
  },
27
27
  "dependencies": {
28
- "@gaia-ai/core": "^0.4.3",
28
+ "@gaia-ai/core": "^0.4.5",
29
29
  "@dropsh/plugin-oauth2": "^0.4.1",
30
- "@dropsh/plugin-jsonapi-schema": "^0.5.1",
30
+ "@dropsh/plugin-jsonapi-schema": "^0.5.6",
31
31
  "commander": "^12.1.0",
32
- "dropsh": "^0.5.3"
32
+ "dropsh": "^0.5.6"
33
33
  }
34
34
  }