@thinkwell/conductor 0.2.0

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.
Files changed (54) hide show
  1. package/README.md +140 -0
  2. package/dist/conductor.d.ts +219 -0
  3. package/dist/conductor.d.ts.map +1 -0
  4. package/dist/conductor.js +960 -0
  5. package/dist/conductor.js.map +1 -0
  6. package/dist/connectors/channel.d.ts +60 -0
  7. package/dist/connectors/channel.d.ts.map +1 -0
  8. package/dist/connectors/channel.js +155 -0
  9. package/dist/connectors/channel.js.map +1 -0
  10. package/dist/connectors/index.d.ts +6 -0
  11. package/dist/connectors/index.d.ts.map +1 -0
  12. package/dist/connectors/index.js +6 -0
  13. package/dist/connectors/index.js.map +1 -0
  14. package/dist/connectors/stdio.d.ts +36 -0
  15. package/dist/connectors/stdio.d.ts.map +1 -0
  16. package/dist/connectors/stdio.js +198 -0
  17. package/dist/connectors/stdio.js.map +1 -0
  18. package/dist/index.d.ts +72 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +76 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/instantiators.d.ts +129 -0
  23. package/dist/instantiators.d.ts.map +1 -0
  24. package/dist/instantiators.js +183 -0
  25. package/dist/instantiators.js.map +1 -0
  26. package/dist/logger.d.ts +121 -0
  27. package/dist/logger.d.ts.map +1 -0
  28. package/dist/logger.js +162 -0
  29. package/dist/logger.js.map +1 -0
  30. package/dist/mcp-bridge/http-listener.d.ts +35 -0
  31. package/dist/mcp-bridge/http-listener.d.ts.map +1 -0
  32. package/dist/mcp-bridge/http-listener.js +204 -0
  33. package/dist/mcp-bridge/http-listener.js.map +1 -0
  34. package/dist/mcp-bridge/index.d.ts +9 -0
  35. package/dist/mcp-bridge/index.d.ts.map +1 -0
  36. package/dist/mcp-bridge/index.js +8 -0
  37. package/dist/mcp-bridge/index.js.map +1 -0
  38. package/dist/mcp-bridge/mcp-bridge.d.ts +80 -0
  39. package/dist/mcp-bridge/mcp-bridge.d.ts.map +1 -0
  40. package/dist/mcp-bridge/mcp-bridge.js +170 -0
  41. package/dist/mcp-bridge/mcp-bridge.js.map +1 -0
  42. package/dist/mcp-bridge/types.d.ts +69 -0
  43. package/dist/mcp-bridge/types.d.ts.map +1 -0
  44. package/dist/mcp-bridge/types.js +8 -0
  45. package/dist/mcp-bridge/types.js.map +1 -0
  46. package/dist/message-queue.d.ts +46 -0
  47. package/dist/message-queue.d.ts.map +1 -0
  48. package/dist/message-queue.js +90 -0
  49. package/dist/message-queue.js.map +1 -0
  50. package/dist/types.d.ts +129 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +13 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +40 -0
package/dist/index.js ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @thinkwell/conductor - TypeScript conductor for ACP proxy chains
3
+ *
4
+ * The conductor orchestrates message routing between clients, proxies, and agents.
5
+ * It sits between every component, managing process lifecycle and message flow.
6
+ *
7
+ * ## Quick Start
8
+ *
9
+ * ```typescript
10
+ * import { Conductor, fromCommands, createChannelPair } from '@thinkwell/conductor';
11
+ *
12
+ * // Create a conductor that spawns an agent subprocess
13
+ * const conductor = new Conductor({
14
+ * instantiator: fromCommands(['my-agent']),
15
+ * });
16
+ *
17
+ * // Connect via a channel (for testing) or stdio (for production)
18
+ * const [clientEnd, conductorEnd] = createChannelPair();
19
+ * await conductor.connect(conductorEnd);
20
+ * ```
21
+ *
22
+ * ## Logging
23
+ *
24
+ * Enable logging to see what the conductor is doing:
25
+ *
26
+ * ```typescript
27
+ * const conductor = new Conductor({
28
+ * instantiator: fromCommands(['my-agent']),
29
+ * logging: {
30
+ * level: 'debug', // 'error' | 'warn' | 'info' | 'debug' | 'trace'
31
+ * name: 'my-app',
32
+ * },
33
+ * });
34
+ * ```
35
+ *
36
+ * ## JSONL Tracing
37
+ *
38
+ * Write all messages to a JSONL file for debugging:
39
+ *
40
+ * ```typescript
41
+ * const conductor = new Conductor({
42
+ * instantiator: fromCommands(['my-agent']),
43
+ * trace: {
44
+ * path: '/tmp/conductor-trace.jsonl',
45
+ * },
46
+ * });
47
+ * ```
48
+ *
49
+ * ## Architecture
50
+ *
51
+ * The conductor uses a central message queue to preserve ordering:
52
+ *
53
+ * ```
54
+ * Client ←→ Conductor ←→ [Proxy 0] ←→ [Proxy 1] ←→ ... ←→ Agent
55
+ * ```
56
+ *
57
+ * All messages flow through the conductor's event loop, ensuring that
58
+ * responses never overtake notifications.
59
+ *
60
+ * @module
61
+ */
62
+ // Conductor
63
+ export { Conductor } from "./conductor.js";
64
+ // Logging
65
+ export { createLogger, createNoopLogger, getLogger, setLogger, } from "./logger.js";
66
+ // Instantiators
67
+ export { fromCommands, fromConnectors, dynamic, staticInstantiator, } from "./instantiators.js";
68
+ export { ROLE_COUNTERPART } from "./types.js";
69
+ // Message queue
70
+ export { MessageQueue } from "./message-queue.js";
71
+ // Connectors
72
+ export { StdioConnector, stdio, ChannelConnector, createChannelPair, inProcess, echoComponent, } from "./connectors/index.js";
73
+ // MCP Bridge
74
+ export { McpBridge, createHttpListener, } from "./mcp-bridge/index.js";
75
+ export { isJsonRpcRequest, isJsonRpcNotification, isJsonRpcResponse, createRequest, createNotification, createSuccessResponse, createErrorResponse, createResponder, } from "@thinkwell/protocol";
76
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAEH,YAAY;AACZ,OAAO,EAAE,SAAS,EAAwB,MAAM,gBAAgB,CAAC;AAEjE,UAAU;AACV,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,SAAS,EACT,SAAS,GAOV,MAAM,aAAa,CAAC;AAErB,gBAAgB;AAChB,OAAO,EACL,YAAY,EACZ,cAAc,EACd,OAAO,EACP,kBAAkB,GAKnB,MAAM,oBAAoB,CAAC;AAc5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,gBAAgB;AAChB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,aAAa;AACb,OAAO,EACL,cAAc,EACd,KAAK,EACL,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,aAAa,GAId,MAAM,uBAAuB,CAAC;AAE/B,aAAa;AACb,OAAO,EACL,SAAS,EACT,kBAAkB,GASnB,MAAM,uBAAuB,CAAC;AAiB/B,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,GAChB,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Component instantiator helpers
3
+ *
4
+ * These functions create ComponentInstantiator objects that determine how
5
+ * components are created when the conductor receives an initialize request.
6
+ *
7
+ * ## Instantiation Modes
8
+ *
9
+ * - **Static**: Components are determined at construction time (e.g., from a list of commands)
10
+ * - **Dynamic**: Components are determined at runtime based on the initialize request
11
+ *
12
+ * ## Lazy Instantiation
13
+ *
14
+ * All instantiators are lazy - components are only spawned when the first
15
+ * `initialize` request arrives. This allows the conductor to be constructed
16
+ * before the component processes need to exist.
17
+ */
18
+ import type { ComponentConnector, ComponentInstantiator, InitializeRequest, InstantiatedComponents } from "./types.js";
19
+ import type { StdioConnectorOptions } from "./connectors/stdio.js";
20
+ /**
21
+ * Options for a component command
22
+ */
23
+ export interface CommandOptions extends StdioConnectorOptions {
24
+ }
25
+ /**
26
+ * A command specification - either a string or full options
27
+ */
28
+ export type CommandSpec = string | CommandOptions;
29
+ /**
30
+ * Static configuration for component instantiation
31
+ */
32
+ export interface StaticInstantiatorConfig {
33
+ /** Proxy commands to spawn (in order) */
34
+ proxies?: CommandSpec[];
35
+ /** Agent command to spawn (required) */
36
+ agent: CommandSpec;
37
+ }
38
+ /**
39
+ * Create a component instantiator from static command specifications.
40
+ *
41
+ * This is the most common way to create an instantiator - provide the
42
+ * commands for each component and they will be spawned when needed.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * // Simple string commands
47
+ * const instantiator = staticInstantiator({
48
+ * proxies: ['sparkle-acp'],
49
+ * agent: 'claude-agent',
50
+ * });
51
+ *
52
+ * // With options
53
+ * const instantiator = staticInstantiator({
54
+ * agent: {
55
+ * command: 'my-agent',
56
+ * args: ['--mode', 'production'],
57
+ * env: { DEBUG: 'true' },
58
+ * },
59
+ * });
60
+ * ```
61
+ */
62
+ export declare function staticInstantiator(config: StaticInstantiatorConfig): ComponentInstantiator;
63
+ /**
64
+ * Create a component instantiator from a simple list of commands.
65
+ *
66
+ * The last command is treated as the agent, all others as proxies.
67
+ * This is a convenience wrapper around `staticInstantiator`.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * // Single agent (no proxies)
72
+ * const instantiator = fromCommands(['claude-agent']);
73
+ *
74
+ * // Agent with proxies
75
+ * const instantiator = fromCommands(['sparkle-acp', 'claude-agent']);
76
+ * ```
77
+ */
78
+ export declare function fromCommands(commands: CommandSpec[]): ComponentInstantiator;
79
+ /**
80
+ * Create a component instantiator from explicit connectors.
81
+ *
82
+ * This is useful when you have pre-configured connectors or want to use
83
+ * in-memory connections for testing.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * const instantiator = fromConnectors(agentConnector, [proxyConnector]);
88
+ * ```
89
+ */
90
+ export declare function fromConnectors(agent: ComponentConnector, proxies?: ComponentConnector[]): ComponentInstantiator;
91
+ /**
92
+ * Factory function type for dynamic instantiation.
93
+ *
94
+ * The factory receives the initialize request and returns the components
95
+ * to instantiate. This allows for runtime decisions about what to spawn.
96
+ */
97
+ export type DynamicInstantiatorFactory = (initRequest: InitializeRequest) => Promise<InstantiatedComponents>;
98
+ /**
99
+ * Create a component instantiator with a dynamic factory function.
100
+ *
101
+ * The factory function is called when the first `initialize` request arrives,
102
+ * allowing you to make decisions about what components to spawn based on
103
+ * the request content.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * const instantiator = dynamic(async (initRequest) => {
108
+ * // Choose agent based on client capabilities
109
+ * const capabilities = initRequest.params?.capabilities ?? {};
110
+ * const agentCommand = capabilities.advanced ? 'advanced-agent' : 'basic-agent';
111
+ *
112
+ * const { StdioConnector } = await import('./connectors/stdio.js');
113
+ * return {
114
+ * proxies: [],
115
+ * agent: new StdioConnector(agentCommand),
116
+ * };
117
+ * });
118
+ *
119
+ * // Or use it to inspect MCP servers
120
+ * const instantiator = dynamic(async (initRequest) => {
121
+ * const mcpServers = initRequest.params?.mcpServers ?? [];
122
+ * console.log('Client provided MCP servers:', mcpServers);
123
+ *
124
+ * // ... create appropriate components
125
+ * });
126
+ * ```
127
+ */
128
+ export declare function dynamic(factory: DynamicInstantiatorFactory): ComponentInstantiator;
129
+ //# sourceMappingURL=instantiators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instantiators.d.ts","sourceRoot":"","sources":["../src/instantiators.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACvH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,cAAe,SAAQ,qBAAqB;CAE5D;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,cAAc,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,yCAAyC;IACzC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,wCAAwC;IACxC,KAAK,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,wBAAwB,GAAG,qBAAqB,CAkB1F;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,qBAAqB,CAS3E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,kBAAkB,EACzB,OAAO,GAAE,kBAAkB,EAAO,GACjC,qBAAqB,CAMvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,0BAA0B,GAAG,CACvC,WAAW,EAAE,iBAAiB,KAC3B,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,0BAA0B,GAAG,qBAAqB,CAIlF"}
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Component instantiator helpers
3
+ *
4
+ * These functions create ComponentInstantiator objects that determine how
5
+ * components are created when the conductor receives an initialize request.
6
+ *
7
+ * ## Instantiation Modes
8
+ *
9
+ * - **Static**: Components are determined at construction time (e.g., from a list of commands)
10
+ * - **Dynamic**: Components are determined at runtime based on the initialize request
11
+ *
12
+ * ## Lazy Instantiation
13
+ *
14
+ * All instantiators are lazy - components are only spawned when the first
15
+ * `initialize` request arrives. This allows the conductor to be constructed
16
+ * before the component processes need to exist.
17
+ */
18
+ /**
19
+ * Create a component instantiator from static command specifications.
20
+ *
21
+ * This is the most common way to create an instantiator - provide the
22
+ * commands for each component and they will be spawned when needed.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * // Simple string commands
27
+ * const instantiator = staticInstantiator({
28
+ * proxies: ['sparkle-acp'],
29
+ * agent: 'claude-agent',
30
+ * });
31
+ *
32
+ * // With options
33
+ * const instantiator = staticInstantiator({
34
+ * agent: {
35
+ * command: 'my-agent',
36
+ * args: ['--mode', 'production'],
37
+ * env: { DEBUG: 'true' },
38
+ * },
39
+ * });
40
+ * ```
41
+ */
42
+ export function staticInstantiator(config) {
43
+ return {
44
+ async instantiate() {
45
+ // Dynamic import to avoid circular dependency
46
+ const { StdioConnector } = await import("./connectors/stdio.js");
47
+ const proxyConnectors = (config.proxies ?? []).map((spec) => new StdioConnector(normalizeCommandSpec(spec)));
48
+ const agentConnector = new StdioConnector(normalizeCommandSpec(config.agent));
49
+ return {
50
+ proxies: proxyConnectors,
51
+ agent: agentConnector,
52
+ };
53
+ },
54
+ };
55
+ }
56
+ /**
57
+ * Create a component instantiator from a simple list of commands.
58
+ *
59
+ * The last command is treated as the agent, all others as proxies.
60
+ * This is a convenience wrapper around `staticInstantiator`.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * // Single agent (no proxies)
65
+ * const instantiator = fromCommands(['claude-agent']);
66
+ *
67
+ * // Agent with proxies
68
+ * const instantiator = fromCommands(['sparkle-acp', 'claude-agent']);
69
+ * ```
70
+ */
71
+ export function fromCommands(commands) {
72
+ if (commands.length === 0) {
73
+ throw new Error("At least one command (the agent) is required");
74
+ }
75
+ return staticInstantiator({
76
+ proxies: commands.slice(0, -1),
77
+ agent: commands[commands.length - 1],
78
+ });
79
+ }
80
+ /**
81
+ * Create a component instantiator from explicit connectors.
82
+ *
83
+ * This is useful when you have pre-configured connectors or want to use
84
+ * in-memory connections for testing.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * const instantiator = fromConnectors(agentConnector, [proxyConnector]);
89
+ * ```
90
+ */
91
+ export function fromConnectors(agent, proxies = []) {
92
+ return {
93
+ async instantiate() {
94
+ return { proxies, agent };
95
+ },
96
+ };
97
+ }
98
+ /**
99
+ * Create a component instantiator with a dynamic factory function.
100
+ *
101
+ * The factory function is called when the first `initialize` request arrives,
102
+ * allowing you to make decisions about what components to spawn based on
103
+ * the request content.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * const instantiator = dynamic(async (initRequest) => {
108
+ * // Choose agent based on client capabilities
109
+ * const capabilities = initRequest.params?.capabilities ?? {};
110
+ * const agentCommand = capabilities.advanced ? 'advanced-agent' : 'basic-agent';
111
+ *
112
+ * const { StdioConnector } = await import('./connectors/stdio.js');
113
+ * return {
114
+ * proxies: [],
115
+ * agent: new StdioConnector(agentCommand),
116
+ * };
117
+ * });
118
+ *
119
+ * // Or use it to inspect MCP servers
120
+ * const instantiator = dynamic(async (initRequest) => {
121
+ * const mcpServers = initRequest.params?.mcpServers ?? [];
122
+ * console.log('Client provided MCP servers:', mcpServers);
123
+ *
124
+ * // ... create appropriate components
125
+ * });
126
+ * ```
127
+ */
128
+ export function dynamic(factory) {
129
+ return {
130
+ instantiate: factory,
131
+ };
132
+ }
133
+ /**
134
+ * Normalize a command specification to full options
135
+ */
136
+ function normalizeCommandSpec(spec) {
137
+ if (typeof spec === "string") {
138
+ // Parse command string into command and args
139
+ const parts = parseCommand(spec);
140
+ return {
141
+ command: parts[0],
142
+ args: parts.slice(1),
143
+ };
144
+ }
145
+ return spec;
146
+ }
147
+ /**
148
+ * Parse a command string into command and arguments.
149
+ * Handles quoted strings.
150
+ */
151
+ function parseCommand(command) {
152
+ const parts = [];
153
+ let current = "";
154
+ let inQuote = null;
155
+ for (let i = 0; i < command.length; i++) {
156
+ const char = command[i];
157
+ if (inQuote) {
158
+ if (char === inQuote) {
159
+ inQuote = null;
160
+ }
161
+ else {
162
+ current += char;
163
+ }
164
+ }
165
+ else if (char === '"' || char === "'") {
166
+ inQuote = char;
167
+ }
168
+ else if (char === " " || char === "\t") {
169
+ if (current) {
170
+ parts.push(current);
171
+ current = "";
172
+ }
173
+ }
174
+ else {
175
+ current += char;
176
+ }
177
+ }
178
+ if (current) {
179
+ parts.push(current);
180
+ }
181
+ return parts;
182
+ }
183
+ //# sourceMappingURL=instantiators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instantiators.js","sourceRoot":"","sources":["../src/instantiators.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Logging module for the conductor
3
+ *
4
+ * Provides structured logging with configurable levels and optional JSONL tracing.
5
+ * All conductor logging goes through this module to enable consistent output
6
+ * formatting and filtering.
7
+ *
8
+ * ## Log Levels
9
+ *
10
+ * - `error`: Errors that affect operation (component crashes, protocol violations)
11
+ * - `warn`: Warnings about potential issues (missing responses, malformed messages)
12
+ * - `info`: Key operational events (component start/stop, connections)
13
+ * - `debug`: Detailed debugging information (message routing, state changes)
14
+ * - `trace`: Very detailed tracing (every message, internal state)
15
+ *
16
+ * ## Usage
17
+ *
18
+ * ```typescript
19
+ * const logger = createLogger({ level: 'debug', name: 'my-conductor' });
20
+ * logger.info('Component started', { component: 'agent', pid: 1234 });
21
+ * logger.error('Failed to connect', { error: err.message });
22
+ * ```
23
+ *
24
+ * ## JSONL Tracing
25
+ *
26
+ * When enabled, all messages routed through the conductor are written to a
27
+ * JSONL file for debugging and analysis:
28
+ *
29
+ * ```typescript
30
+ * const logger = createLogger({
31
+ * level: 'info',
32
+ * trace: { path: '/tmp/conductor.jsonl' }
33
+ * });
34
+ * ```
35
+ */
36
+ import type { ConductorMessage } from "./types.js";
37
+ import type { JsonRpcMessage } from "@thinkwell/protocol";
38
+ /**
39
+ * Log level enumeration (lower = more severe)
40
+ */
41
+ export type LogLevel = "error" | "warn" | "info" | "debug" | "trace";
42
+ /**
43
+ * Log entry structure for structured logging
44
+ */
45
+ export interface LogEntry {
46
+ timestamp: string;
47
+ level: LogLevel;
48
+ name?: string;
49
+ message: string;
50
+ data?: Record<string, unknown>;
51
+ }
52
+ /**
53
+ * Trace entry for JSONL message tracing
54
+ */
55
+ export interface TraceEntry {
56
+ timestamp: string;
57
+ direction: "left-to-right" | "right-to-left" | "internal";
58
+ source?: string;
59
+ target?: string;
60
+ message: ConductorMessage | JsonRpcMessage;
61
+ }
62
+ /**
63
+ * Options for JSONL tracing output
64
+ */
65
+ export interface TraceOptions {
66
+ /** Path to the JSONL trace file */
67
+ path: string;
68
+ }
69
+ /**
70
+ * Logger configuration
71
+ */
72
+ export interface LoggerOptions {
73
+ /** Minimum log level to output (default: 'info') */
74
+ level?: LogLevel;
75
+ /** Optional name prefix for log messages */
76
+ name?: string;
77
+ /** Optional JSONL trace output */
78
+ trace?: TraceOptions;
79
+ /** Use JSON output format instead of human-readable (default: false) */
80
+ json?: boolean;
81
+ }
82
+ /**
83
+ * Logger interface
84
+ */
85
+ export interface Logger {
86
+ /** Log an error message */
87
+ error(message: string, data?: Record<string, unknown>): void;
88
+ /** Log a warning message */
89
+ warn(message: string, data?: Record<string, unknown>): void;
90
+ /** Log an info message */
91
+ info(message: string, data?: Record<string, unknown>): void;
92
+ /** Log a debug message */
93
+ debug(message: string, data?: Record<string, unknown>): void;
94
+ /** Log a trace message */
95
+ trace(message: string, data?: Record<string, unknown>): void;
96
+ /** Write a trace entry for message inspection */
97
+ traceMessage(entry: Omit<TraceEntry, "timestamp">): void;
98
+ /** Check if a log level is enabled */
99
+ isEnabled(level: LogLevel): boolean;
100
+ /** Create a child logger with additional context */
101
+ child(name: string): Logger;
102
+ /** Close any open resources (trace file, etc.) */
103
+ close(): Promise<void>;
104
+ }
105
+ /**
106
+ * Create a no-op logger that discards all output
107
+ */
108
+ export declare function createNoopLogger(): Logger;
109
+ /**
110
+ * Create a logger with the specified options
111
+ */
112
+ export declare function createLogger(options?: LoggerOptions): Logger;
113
+ /**
114
+ * Get the default logger
115
+ */
116
+ export declare function getLogger(): Logger;
117
+ /**
118
+ * Set the default logger
119
+ */
120
+ export declare function setLogger(logger: Logger): void;
121
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAUrE;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,eAAe,GAAG,eAAe,GAAG,UAAU,CAAC;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,gBAAgB,GAAG,cAAc,CAAC;CAC5C;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,wEAAwE;IACxE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,2BAA2B;IAC3B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7D,4BAA4B;IAC5B,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,0BAA0B;IAC1B,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,0BAA0B;IAC1B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7D,0BAA0B;IAC1B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAE7D,iDAAiD;IACjD,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,GAAG,IAAI,CAAC;IAEzD,sCAAsC;IACtC,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC;IAEpC,oDAAoD;IACpD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAE5B,kDAAkD;IAClD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAazC;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,aAAkB,GAAG,MAAM,CAyFhE;AAOD;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE9C"}
package/dist/logger.js ADDED
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Logging module for the conductor
3
+ *
4
+ * Provides structured logging with configurable levels and optional JSONL tracing.
5
+ * All conductor logging goes through this module to enable consistent output
6
+ * formatting and filtering.
7
+ *
8
+ * ## Log Levels
9
+ *
10
+ * - `error`: Errors that affect operation (component crashes, protocol violations)
11
+ * - `warn`: Warnings about potential issues (missing responses, malformed messages)
12
+ * - `info`: Key operational events (component start/stop, connections)
13
+ * - `debug`: Detailed debugging information (message routing, state changes)
14
+ * - `trace`: Very detailed tracing (every message, internal state)
15
+ *
16
+ * ## Usage
17
+ *
18
+ * ```typescript
19
+ * const logger = createLogger({ level: 'debug', name: 'my-conductor' });
20
+ * logger.info('Component started', { component: 'agent', pid: 1234 });
21
+ * logger.error('Failed to connect', { error: err.message });
22
+ * ```
23
+ *
24
+ * ## JSONL Tracing
25
+ *
26
+ * When enabled, all messages routed through the conductor are written to a
27
+ * JSONL file for debugging and analysis:
28
+ *
29
+ * ```typescript
30
+ * const logger = createLogger({
31
+ * level: 'info',
32
+ * trace: { path: '/tmp/conductor.jsonl' }
33
+ * });
34
+ * ```
35
+ */
36
+ import { createWriteStream } from "node:fs";
37
+ const LOG_LEVELS = {
38
+ error: 0,
39
+ warn: 1,
40
+ info: 2,
41
+ debug: 3,
42
+ trace: 4,
43
+ };
44
+ /**
45
+ * Create a no-op logger that discards all output
46
+ */
47
+ export function createNoopLogger() {
48
+ const noop = () => { };
49
+ return {
50
+ error: noop,
51
+ warn: noop,
52
+ info: noop,
53
+ debug: noop,
54
+ trace: noop,
55
+ traceMessage: noop,
56
+ isEnabled: () => false,
57
+ child: () => createNoopLogger(),
58
+ close: async () => { },
59
+ };
60
+ }
61
+ /**
62
+ * Create a logger with the specified options
63
+ */
64
+ export function createLogger(options = {}) {
65
+ const level = options.level ?? "info";
66
+ const levelNum = LOG_LEVELS[level];
67
+ const name = options.name;
68
+ const useJson = options.json ?? false;
69
+ // Set up trace file if configured
70
+ let traceStream = null;
71
+ if (options.trace) {
72
+ traceStream = createWriteStream(options.trace.path, { flags: "a" });
73
+ }
74
+ function isEnabled(checkLevel) {
75
+ return LOG_LEVELS[checkLevel] <= levelNum;
76
+ }
77
+ function log(logLevel, message, data) {
78
+ if (!isEnabled(logLevel)) {
79
+ return;
80
+ }
81
+ const entry = {
82
+ timestamp: new Date().toISOString(),
83
+ level: logLevel,
84
+ name,
85
+ message,
86
+ data,
87
+ };
88
+ if (useJson) {
89
+ // Output as JSON for machine parsing
90
+ const output = logLevel === "error" || logLevel === "warn" ? console.error : console.log;
91
+ output(JSON.stringify(entry));
92
+ }
93
+ else {
94
+ // Human-readable format
95
+ const prefix = name ? `[${name}]` : "";
96
+ const levelStr = logLevel.toUpperCase().padEnd(5);
97
+ const dataStr = data ? ` ${JSON.stringify(data)}` : "";
98
+ const output = logLevel === "error" || logLevel === "warn" ? console.error : console.log;
99
+ output(`${entry.timestamp} ${levelStr} ${prefix}${prefix ? " " : ""}${message}${dataStr}`);
100
+ }
101
+ }
102
+ function traceMessage(entry) {
103
+ if (!traceStream) {
104
+ return;
105
+ }
106
+ const fullEntry = {
107
+ timestamp: new Date().toISOString(),
108
+ ...entry,
109
+ };
110
+ traceStream.write(JSON.stringify(fullEntry) + "\n");
111
+ }
112
+ function child(childName) {
113
+ const fullName = name ? `${name}:${childName}` : childName;
114
+ return createLogger({
115
+ ...options,
116
+ name: fullName,
117
+ // Share trace stream with parent
118
+ trace: undefined, // Don't create new stream
119
+ });
120
+ }
121
+ async function close() {
122
+ if (traceStream) {
123
+ await new Promise((resolve, reject) => {
124
+ traceStream.end((err) => {
125
+ if (err)
126
+ reject(err);
127
+ else
128
+ resolve();
129
+ });
130
+ });
131
+ traceStream = null;
132
+ }
133
+ }
134
+ return {
135
+ error: (message, data) => log("error", message, data),
136
+ warn: (message, data) => log("warn", message, data),
137
+ info: (message, data) => log("info", message, data),
138
+ debug: (message, data) => log("debug", message, data),
139
+ trace: (message, data) => log("trace", message, data),
140
+ traceMessage,
141
+ isEnabled,
142
+ child,
143
+ close,
144
+ };
145
+ }
146
+ /**
147
+ * Default logger instance (silent by default, can be replaced)
148
+ */
149
+ let defaultLogger = createNoopLogger();
150
+ /**
151
+ * Get the default logger
152
+ */
153
+ export function getLogger() {
154
+ return defaultLogger;
155
+ }
156
+ /**
157
+ * Set the default logger
158
+ */
159
+ export function setLogger(logger) {
160
+ defaultLogger = logger;
161
+ }
162
+ //# sourceMappingURL=logger.js.map