@gaia-ai/conductor 0.6.3 → 0.6.4
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.
|
@@ -383,13 +383,14 @@ export function createConductorCommand(deps) {
|
|
|
383
383
|
const conductor = new Command('conductor')
|
|
384
384
|
.description('node-agent lifecycle + local registry')
|
|
385
385
|
.option('--log-level <level>', 'log verbosity: debug | info | warn | error (overrides GAIA_LOG_LEVEL)')
|
|
386
|
-
.option('--log-sink <sink>', 'log sink: stdout | file (overrides GAIA_CONDUCTOR_LOG)')
|
|
386
|
+
.option('--log-sink <sink>', 'log sink: stdout | file | both (overrides GAIA_CONDUCTOR_LOG)')
|
|
387
387
|
.addHelpText('after', `
|
|
388
388
|
Logging (precedence: CLI flag > env var > default):
|
|
389
389
|
--log-level <level> debug | info (default) | warn | error.
|
|
390
390
|
Set debug to see per-poll ticks, claims, and idle cycles.
|
|
391
|
-
--log-sink <sink> stdout | file (
|
|
392
|
-
Default:
|
|
391
|
+
--log-sink <sink> stdout | file | both (file/both write <checkoutRoot>/log.txt).
|
|
392
|
+
Default: both on a TTY, file otherwise (GAIA-237 — a run in a
|
|
393
|
+
herdr pane is on a TTY, and its diagnostics must outlive the pane).
|
|
393
394
|
GAIA_LOG_LEVEL env fallback for --log-level.
|
|
394
395
|
GAIA_CONDUCTOR_LOG env fallback for --log-sink.
|
|
395
396
|
|
package/dist/src/contract.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { type AgentFootprint, emptyAgentFootprint, type GaiaAgent, } from './plugins/agent.js';
|
|
2
|
-
export type { ExecutorCapabilities, GaiaExecutor, HookContext, HookName, SpawnedSession, SpawnRunInput, } from './plugins/executor.js';
|
|
2
|
+
export type { ExecutorCapabilities, GaiaExecutor, HookContext, HookName, HookResult, SpawnedSession, SpawnRunInput, } from './plugins/executor.js';
|
|
3
3
|
export type { AgentCandidate, AgentPlugin, ExecutorDeps, ExecutorPlugin, RemotePlugin, ResolvedAgent, WorkspacePlugin, } from './plugins/plugins.js';
|
|
4
4
|
export { selectAgent, selectAgents, selectExecutor, selectRemote, selectWorkspace, } from './plugins/plugins.js';
|
|
5
5
|
export { type ConductorAddonEntry, type ConductorContributions, narrowConductorContributions, type Preset, } from './plugins/preset.js';
|
|
@@ -380,11 +380,53 @@ export class Conductor {
|
|
|
380
380
|
}
|
|
381
381
|
// Lifecycle hooks are executor-owned + best-effort (GAIA-84): runHook never
|
|
382
382
|
// throws, so neither call can abort dispatch or wedge a run in `claimed`.
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
383
|
+
// Both get the resolved run env so a setup/pre hook sees the same vars (e.g.
|
|
384
|
+
// a DB DSN) the agent does (GAIA-99).
|
|
385
|
+
//
|
|
386
|
+
// GAIA-237: `after_create` runs on EVERY dispatch, not only when
|
|
387
|
+
// `ws.created`. `created` answers "was this worktree born just now?", which
|
|
388
|
+
// is not the question dispatch needs answered — a worktree whose bootstrap
|
|
389
|
+
// aborted, an interrupted earlier run, or a hand-made `git worktree add` all
|
|
390
|
+
// exist WITHOUT being provisioned, and under the creation gate they never got
|
|
391
|
+
// another chance. Provisioning is therefore convergent: run the hook and let
|
|
392
|
+
// the hook decide. Keeping the repeat cheap is the HOOK's job (gaia's
|
|
393
|
+
// `.ddev/worktree-post.sh` short-circuits on its own sentinel) — the
|
|
394
|
+
// conductor deliberately learns no project-specific "is it provisioned?"
|
|
395
|
+
// marker, which is exactly why the gate cannot live here.
|
|
396
|
+
const provisioning = await this.executor.runHook('after_create', ws.path, { ticket: t.identifier }, env);
|
|
397
|
+
// AC-6: an agent is never dispatched into a worktree whose bootstrap failed
|
|
398
|
+
// without that being visible. Dispatch is NOT withheld — runHook's
|
|
399
|
+
// never-throw contract exists so a hook cannot wedge a run, and withholding
|
|
400
|
+
// would re-introduce that wedge — so the failure is recorded against the run
|
|
401
|
+
// instead (ticket + run + hook + workspace + err) and the run proceeds.
|
|
402
|
+
// Boundedness stays with the existing circuit breaker.
|
|
403
|
+
if (!provisioning.ok) {
|
|
404
|
+
this.logger.error({
|
|
405
|
+
hook: provisioning.hook,
|
|
406
|
+
ticket: t.identifier,
|
|
407
|
+
run: run.runUuid,
|
|
408
|
+
workspace: ws.path,
|
|
409
|
+
err: provisioning.error,
|
|
410
|
+
}, 'lifecycle hook failed');
|
|
411
|
+
}
|
|
412
|
+
// GAIA-237: apply the workspace's open layout HERE — after `after_create`,
|
|
413
|
+
// before the run starts. The layout's startup command names the gaia CLI
|
|
414
|
+
// that `after_create` builds; applied inside `workspace.ensure()` (where it
|
|
415
|
+
// used to live) it necessarily fired first, so a self-hosting checkout had
|
|
416
|
+
// to paper over the ordering with a sentinel wait loop inside the pane
|
|
417
|
+
// command. Ordering the two correctly removes the need for any handshake.
|
|
418
|
+
//
|
|
419
|
+
// Gated on `ws.created` — DELIBERATELY narrower than the `after_create` gate
|
|
420
|
+
// right above it, which is convergent (a worktree can exist without ever
|
|
421
|
+
// having been provisioned). Only a fresh create has an initial pane to lay
|
|
422
|
+
// out; re-applying on reuse would start a duplicate editor on reattach.
|
|
423
|
+
// Optional on the contract, so a workspace with no layout concept is a
|
|
424
|
+
// no-op. Best-effort by contract: implementations swallow their own errors.
|
|
386
425
|
if (ws.created) {
|
|
387
|
-
await this.
|
|
426
|
+
await this.workspace.applyOpenLayout?.(ws.path, {
|
|
427
|
+
identifier: t.identifier,
|
|
428
|
+
branch: t.branchName,
|
|
429
|
+
});
|
|
388
430
|
}
|
|
389
431
|
await this.executor.runHook('before_run', ws.path, { ticket: t.identifier }, env);
|
|
390
432
|
// Clear a leftover tab for THIS run id in a reused workspace before
|
|
@@ -34,6 +34,24 @@ export interface HookContext {
|
|
|
34
34
|
/** The ticket identifier/uuid the hook is running for (log context). */
|
|
35
35
|
ticket: string;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* What a lifecycle hook invocation DID (GAIA-237). `runHook` still never throws,
|
|
39
|
+
* but it no longer swallows the outcome silently: it reports it, so a caller can
|
|
40
|
+
* record a failed provisioning hook against the run it is about to dispatch
|
|
41
|
+
* instead of relying on a log line to survive (AC-6). Reporting — rather than
|
|
42
|
+
* throwing — keeps the never-wedge property that the never-throw contract exists
|
|
43
|
+
* for; boundedness stays with the conductor's circuit breaker.
|
|
44
|
+
*/
|
|
45
|
+
export interface HookResult {
|
|
46
|
+
/** The hook slot that was invoked. */
|
|
47
|
+
hook: HookName;
|
|
48
|
+
/** false when no command is configured for the slot — nothing ran. */
|
|
49
|
+
ran: boolean;
|
|
50
|
+
/** false ONLY when a configured command actually failed. */
|
|
51
|
+
ok: boolean;
|
|
52
|
+
/** The stringified failure; present iff `ok` is false. */
|
|
53
|
+
error?: string;
|
|
54
|
+
}
|
|
37
55
|
export interface GaiaExecutor {
|
|
38
56
|
id: string;
|
|
39
57
|
capabilities(): ExecutorCapabilities;
|
|
@@ -47,13 +65,16 @@ export interface GaiaExecutor {
|
|
|
47
65
|
* `claimed`.
|
|
48
66
|
* - The underlying shell command still fails honestly (a non-zero exit
|
|
49
67
|
* rejects); only the executor catches + logs + continues.
|
|
68
|
+
* - MUST report the outcome as a {@link HookResult} (GAIA-237): never throwing
|
|
69
|
+
* is not the same as never telling. The caller decides what a failure means
|
|
70
|
+
* for the run it is dispatching; the executor only refuses to abort.
|
|
50
71
|
*
|
|
51
72
|
* `env`, when given, is the run's resolved environment (per-ticket env_vars
|
|
52
73
|
* merged with the core GAIA_* vars, GAIA-99), merged over the hook process's
|
|
53
74
|
* inherited env. Values may be sensitive — implementations must never log
|
|
54
75
|
* them (log key names only).
|
|
55
76
|
*/
|
|
56
|
-
runHook(name: HookName, cwd: string, ctx: HookContext, env?: Record<string, string>): Promise<
|
|
77
|
+
runHook(name: HookName, cwd: string, ctx: HookContext, env?: Record<string, string>): Promise<HookResult>;
|
|
57
78
|
startRun(input: SpawnRunInput): Promise<SpawnedSession>;
|
|
58
79
|
/**
|
|
59
80
|
* Signal the run's agent to stop. Called by the conductor's run-finalise pass
|
|
@@ -32,4 +32,34 @@ export interface GaiaWorkspace {
|
|
|
32
32
|
* anymore — the executor owns all hooks (GAIA-84).
|
|
33
33
|
*/
|
|
34
34
|
ensure(identifier: string, branch?: string, baseRef?: string): Promise<EnsuredWorkspace>;
|
|
35
|
+
/**
|
|
36
|
+
* Apply the workspace's "open" layout — for a hosted workspace, the startup
|
|
37
|
+
* command(s) of the freshly created workspace's initial pane.
|
|
38
|
+
*
|
|
39
|
+
* OPTIONAL: a workspace with no layout concept (git, fake) simply omits it and
|
|
40
|
+
* the conductor calls it with `?.`.
|
|
41
|
+
*
|
|
42
|
+
* GAIA-237 — why this is a SEPARATE call and not part of `ensure()`. The
|
|
43
|
+
* layout's startup command typically names the gaia CLI, which the
|
|
44
|
+
* `after_create` lifecycle hook builds. Applied inside `ensure()` it
|
|
45
|
+
* necessarily ran BEFORE that hook, so in a self-hosting checkout the command
|
|
46
|
+
* fired minutes before the binary it names existed. The only remedy available
|
|
47
|
+
* to a config author was a cross-process handshake — a sentinel wait loop
|
|
48
|
+
* inside the pane command itself. Splitting the call lets the conductor order
|
|
49
|
+
* the two correctly (`after_create` → `applyOpenLayout`), which removes the
|
|
50
|
+
* need for any handshake.
|
|
51
|
+
*
|
|
52
|
+
* The conductor calls this ONLY on a freshly created workspace
|
|
53
|
+
* ({@link EnsuredWorkspace.created}), because only a fresh create has an
|
|
54
|
+
* initial pane to lay out; re-applying on reuse would start a duplicate editor
|
|
55
|
+
* process on every reattach. Deliberately a NARROWER gate than the one on
|
|
56
|
+
* `after_create`, which is convergent and runs on every dispatch.
|
|
57
|
+
*
|
|
58
|
+
* Best-effort by contract: implementations log and swallow a failing layout so
|
|
59
|
+
* it can never wedge dispatch.
|
|
60
|
+
*/
|
|
61
|
+
applyOpenLayout?(path: string, ctx: {
|
|
62
|
+
identifier: string;
|
|
63
|
+
branch: string;
|
|
64
|
+
}): Promise<void>;
|
|
35
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaia-ai/conductor",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "GAIA conductor engine + CLI: registers, claims tickets via JSON:API, dispatches agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"directory": "gaia-cli/conductor"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@gaia-ai/core": "^0.6.
|
|
31
|
+
"@gaia-ai/core": "^0.6.4",
|
|
32
32
|
"@dropsh/plugin-oauth2": "^0.5.7",
|
|
33
33
|
"@dropsh/plugin-jsonapi-schema": "^0.5.8",
|
|
34
34
|
"commander": "^12.1.0",
|