@thinkwell/conductor 0.5.4 → 0.5.6
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.
- package/dist/conductor.js +627 -935
- package/dist/connectors/channel.js +75 -142
- package/dist/connectors/index.js +1 -5
- package/dist/connectors/stdio.js +100 -202
- package/dist/generated/features.d.ts +5 -0
- package/dist/generated/features.d.ts.map +1 -0
- package/dist/generated/features.js +4 -0
- package/dist/generated/features.js.map +1 -0
- package/dist/index.js +5 -73
- package/dist/instantiators.js +38 -171
- package/dist/logger.js +80 -148
- package/dist/mcp-bridge/http-listener.js +116 -212
- package/dist/mcp-bridge/index.js +0 -6
- package/dist/mcp-bridge/mcp-bridge.js +117 -164
- package/dist/mcp-bridge/types.js +0 -7
- package/dist/message-queue.js +49 -86
- package/dist/types.js +4 -11
- package/package.json +4 -3
package/dist/instantiators.js
CHANGED
|
@@ -1,183 +1,50 @@
|
|
|
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
1
|
export function staticInstantiator(config) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
};
|
|
53
|
-
},
|
|
54
|
-
};
|
|
2
|
+
return {
|
|
3
|
+
async instantiate() {
|
|
4
|
+
const { StdioConnector } = await import("./connectors/stdio.js"), proxyConnectors = (config.proxies ?? []).map((spec) => new StdioConnector(normalizeCommandSpec(spec))), agentConnector = new StdioConnector(normalizeCommandSpec(config.agent));
|
|
5
|
+
return {
|
|
6
|
+
proxies: proxyConnectors,
|
|
7
|
+
agent: agentConnector
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
};
|
|
55
11
|
}
|
|
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
12
|
export function fromCommands(commands) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
});
|
|
13
|
+
if (commands.length === 0)
|
|
14
|
+
throw new Error("At least one command (the agent) is required");
|
|
15
|
+
return staticInstantiator({
|
|
16
|
+
proxies: commands.slice(0, -1),
|
|
17
|
+
agent: commands[commands.length - 1]
|
|
18
|
+
});
|
|
79
19
|
}
|
|
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
20
|
export function fromConnectors(agent, proxies = []) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
21
|
+
return {
|
|
22
|
+
async instantiate() {
|
|
23
|
+
return { proxies, agent };
|
|
24
|
+
}
|
|
25
|
+
};
|
|
97
26
|
}
|
|
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
27
|
export function dynamic(factory) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
28
|
+
return {
|
|
29
|
+
instantiate: factory
|
|
30
|
+
};
|
|
132
31
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Normalize a command specification to full options
|
|
135
|
-
*/
|
|
136
32
|
function normalizeCommandSpec(spec) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return spec;
|
|
33
|
+
if (typeof spec == "string") {
|
|
34
|
+
const parts = parseCommand(spec);
|
|
35
|
+
return {
|
|
36
|
+
command: parts[0],
|
|
37
|
+
args: parts.slice(1)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return spec;
|
|
146
41
|
}
|
|
147
|
-
/**
|
|
148
|
-
* Parse a command string into command and arguments.
|
|
149
|
-
* Handles quoted strings.
|
|
150
|
-
*/
|
|
151
42
|
function parseCommand(command) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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;
|
|
43
|
+
const parts = [];
|
|
44
|
+
let current = "", inQuote = null;
|
|
45
|
+
for (let i = 0; i < command.length; i++) {
|
|
46
|
+
const char = command[i];
|
|
47
|
+
inQuote ? char === inQuote ? inQuote = null : current += char : char === '"' || char === "'" ? inQuote = char : char === " " || char === " " ? current && (parts.push(current), current = "") : current += char;
|
|
48
|
+
}
|
|
49
|
+
return current && parts.push(current), parts;
|
|
182
50
|
}
|
|
183
|
-
//# sourceMappingURL=instantiators.js.map
|
package/dist/logger.js
CHANGED
|
@@ -1,162 +1,94 @@
|
|
|
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
1
|
import { createWriteStream } from "node:fs";
|
|
37
2
|
const LOG_LEVELS = {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
3
|
+
error: 0,
|
|
4
|
+
warn: 1,
|
|
5
|
+
info: 2,
|
|
6
|
+
debug: 3,
|
|
7
|
+
trace: 4
|
|
43
8
|
};
|
|
44
|
-
/**
|
|
45
|
-
* Create a no-op logger that discards all output
|
|
46
|
-
*/
|
|
47
9
|
export function createNoopLogger() {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
10
|
+
const noop = () => {
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
error: noop,
|
|
14
|
+
warn: noop,
|
|
15
|
+
info: noop,
|
|
16
|
+
debug: noop,
|
|
17
|
+
trace: noop,
|
|
18
|
+
traceMessage: noop,
|
|
19
|
+
isEnabled: () => !1,
|
|
20
|
+
child: () => createNoopLogger(),
|
|
21
|
+
close: async () => {
|
|
22
|
+
}
|
|
23
|
+
};
|
|
60
24
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Create a logger with the specified options
|
|
63
|
-
*/
|
|
64
25
|
export function createLogger(options = {}) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
}
|
|
26
|
+
const level = options.level ?? "info", levelNum = LOG_LEVELS[level], name = options.name, useJson = options.json ?? !1;
|
|
27
|
+
let traceStream = null;
|
|
28
|
+
options.trace && (traceStream = createWriteStream(options.trace.path, { flags: "a" }));
|
|
29
|
+
function isEnabled(checkLevel) {
|
|
30
|
+
return LOG_LEVELS[checkLevel] <= levelNum;
|
|
31
|
+
}
|
|
32
|
+
function log(logLevel, message, data) {
|
|
33
|
+
if (!isEnabled(logLevel))
|
|
34
|
+
return;
|
|
35
|
+
const entry = {
|
|
36
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37
|
+
level: logLevel,
|
|
38
|
+
name,
|
|
39
|
+
message,
|
|
40
|
+
data
|
|
41
|
+
};
|
|
42
|
+
if (useJson)
|
|
43
|
+
(logLevel === "error" || logLevel === "warn" ? console.error : console.log)(JSON.stringify(entry));
|
|
44
|
+
else {
|
|
45
|
+
const prefix = name ? `[${name}]` : "", levelStr = logLevel.toUpperCase().padEnd(5), dataStr = data ? ` ${JSON.stringify(data)}` : "";
|
|
46
|
+
(logLevel === "error" || logLevel === "warn" ? console.error : console.log)(`${entry.timestamp} ${levelStr} ${prefix}${prefix ? " " : ""}${message}${dataStr}`);
|
|
133
47
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
isEnabled,
|
|
142
|
-
child,
|
|
143
|
-
close,
|
|
48
|
+
}
|
|
49
|
+
function traceMessage(entry) {
|
|
50
|
+
if (!traceStream)
|
|
51
|
+
return;
|
|
52
|
+
const fullEntry = {
|
|
53
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
54
|
+
...entry
|
|
144
55
|
};
|
|
56
|
+
traceStream.write(JSON.stringify(fullEntry) + `
|
|
57
|
+
`);
|
|
58
|
+
}
|
|
59
|
+
function child(childName) {
|
|
60
|
+
const fullName = name ? `${name}:${childName}` : childName;
|
|
61
|
+
return createLogger({
|
|
62
|
+
...options,
|
|
63
|
+
name: fullName,
|
|
64
|
+
// Share trace stream with parent
|
|
65
|
+
trace: void 0
|
|
66
|
+
// Don't create new stream
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async function close() {
|
|
70
|
+
traceStream && (await new Promise((resolve, reject) => {
|
|
71
|
+
traceStream.end((err) => {
|
|
72
|
+
err ? reject(err) : resolve();
|
|
73
|
+
});
|
|
74
|
+
}), traceStream = null);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
error: (message, data) => log("error", message, data),
|
|
78
|
+
warn: (message, data) => log("warn", message, data),
|
|
79
|
+
info: (message, data) => log("info", message, data),
|
|
80
|
+
debug: (message, data) => log("debug", message, data),
|
|
81
|
+
trace: (message, data) => log("trace", message, data),
|
|
82
|
+
traceMessage,
|
|
83
|
+
isEnabled,
|
|
84
|
+
child,
|
|
85
|
+
close
|
|
86
|
+
};
|
|
145
87
|
}
|
|
146
|
-
/**
|
|
147
|
-
* Default logger instance (silent by default, can be replaced)
|
|
148
|
-
*/
|
|
149
88
|
let defaultLogger = createNoopLogger();
|
|
150
|
-
/**
|
|
151
|
-
* Get the default logger
|
|
152
|
-
*/
|
|
153
89
|
export function getLogger() {
|
|
154
|
-
|
|
90
|
+
return defaultLogger;
|
|
155
91
|
}
|
|
156
|
-
/**
|
|
157
|
-
* Set the default logger
|
|
158
|
-
*/
|
|
159
92
|
export function setLogger(logger) {
|
|
160
|
-
|
|
93
|
+
defaultLogger = logger;
|
|
161
94
|
}
|
|
162
|
-
//# sourceMappingURL=logger.js.map
|