@gaia-ai/core 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.
@@ -3,6 +3,14 @@ export type LogSink = {
3
3
  } | {
4
4
  kind: 'file';
5
5
  path: string;
6
+ }
7
+ /**
8
+ * Tee: pretty stdout AND the durable `log.txt`. The DEFAULT on a TTY since
9
+ * GAIA-237 — see {@link resolveSink}.
10
+ */
11
+ | {
12
+ kind: 'both';
13
+ path: string;
6
14
  };
7
15
  export interface ConductorLogger {
8
16
  debug(obj: object, msg?: string): void;
@@ -12,7 +20,16 @@ export interface ConductorLogger {
12
20
  }
13
21
  /**
14
22
  * Pick the log sink. Precedence: explicit CLI override (`sink`) > the
15
- * GAIA_CONDUCTOR_LOG env var > TTY default (stdout on a TTY, file otherwise).
23
+ * GAIA_CONDUCTOR_LOG env var > default (`both` on a TTY, `file` otherwise).
24
+ *
25
+ * GAIA-237: the TTY default used to be stdout-ONLY, which made the conductor's
26
+ * own diagnostics non-durable exactly where it normally runs — inside a herdr
27
+ * pane, which IS a TTY. A well-formed `lifecycle hook failed` record (hook +
28
+ * worktree + ticket + err) was therefore written nowhere and died with the pane,
29
+ * so an aborting worktree bootstrap left no trace to find. The default now tees:
30
+ * the operator keeps pretty stdout AND the record lands in `log.txt`. An
31
+ * EXPLICIT `stdout` / `file` (CLI flag or GAIA_CONDUCTOR_LOG) keeps its exact
32
+ * previous meaning — one stream, chosen deliberately.
16
33
  */
17
34
  export declare function resolveSink(env: NodeJS.ProcessEnv, isTTY: boolean, checkoutRoot: string, override?: string): LogSink;
18
35
  /**
@@ -3,19 +3,27 @@ import pino from 'pino';
3
3
  import pretty from 'pino-pretty';
4
4
  /**
5
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).
6
+ * GAIA_CONDUCTOR_LOG env var > default (`both` on a TTY, `file` otherwise).
7
+ *
8
+ * GAIA-237: the TTY default used to be stdout-ONLY, which made the conductor's
9
+ * own diagnostics non-durable exactly where it normally runs — inside a herdr
10
+ * pane, which IS a TTY. A well-formed `lifecycle hook failed` record (hook +
11
+ * worktree + ticket + err) was therefore written nowhere and died with the pane,
12
+ * so an aborting worktree bootstrap left no trace to find. The default now tees:
13
+ * the operator keeps pretty stdout AND the record lands in `log.txt`. An
14
+ * EXPLICIT `stdout` / `file` (CLI flag or GAIA_CONDUCTOR_LOG) keeps its exact
15
+ * previous meaning — one stream, chosen deliberately.
7
16
  */
8
17
  export function resolveSink(env, isTTY, checkoutRoot, override) {
9
- const fileSink = {
10
- kind: 'file',
11
- path: join(checkoutRoot, 'log.txt'),
12
- };
18
+ const path = join(checkoutRoot, 'log.txt');
13
19
  const choice = override ?? env.GAIA_CONDUCTOR_LOG;
14
20
  if (choice === 'stdout')
15
21
  return { kind: 'stdout' };
16
22
  if (choice === 'file')
17
- return fileSink;
18
- return isTTY ? { kind: 'stdout' } : fileSink;
23
+ return { kind: 'file', path };
24
+ if (choice === 'both')
25
+ return { kind: 'both', path };
26
+ return isTTY ? { kind: 'both', path } : { kind: 'file', path };
19
27
  }
20
28
  /**
21
29
  * Pick the log level. Precedence: explicit CLI override (`level`) >
@@ -32,5 +40,24 @@ export function createLogger(opts) {
32
40
  if (sink.kind === 'stdout') {
33
41
  return pino({ level }, pretty({ colorize: true, sync: true }));
34
42
  }
35
- return pino({ level }, pino.destination({ dest: sink.path, append: true, mkdir: true }));
43
+ const file = () => pino.destination({
44
+ dest: sink.path,
45
+ append: true,
46
+ mkdir: true,
47
+ sync: true,
48
+ });
49
+ if (sink.kind === 'file') {
50
+ return pino({ level }, file());
51
+ }
52
+ // 'both' — tee via multistream so the pane stays readable AND the record is
53
+ // durable (GAIA-237). `level` is enforced by the logger, so each stream takes
54
+ // everything that got through.
55
+ // multistream filters per stream and defaults each entry to `info`, which
56
+ // would silently drop `--log-level debug` from BOTH streams — so each entry
57
+ // carries the resolved level and the logger stays the single gate.
58
+ const at = level;
59
+ return pino({ level }, pino.multistream([
60
+ { level: at, stream: pretty({ colorize: true, sync: true }) },
61
+ { level: at, stream: file() },
62
+ ]));
36
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaia-ai/core",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "GAIA surface-agnostic kernel: host contract, split-config helpers, addon preset/discovery machinery, shared primitives.",
5
5
  "type": "module",
6
6
  "license": "MIT",