@love-moon/conductor-sdk 0.2.6 → 0.2.8
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/backend/client.d.ts +4 -0
- package/dist/backend/client.js +40 -14
- package/dist/client.d.ts +80 -0
- package/dist/client.js +391 -0
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -3
- package/dist/message/router.d.ts +4 -2
- package/package.json +1 -2
- package/dist/bin/mcp-server.d.ts +0 -2
- package/dist/bin/mcp-server.js +0 -222
- package/dist/mcp/index.d.ts +0 -2
- package/dist/mcp/index.js +0 -2
- package/dist/mcp/notifications.d.ts +0 -20
- package/dist/mcp/notifications.js +0 -44
- package/dist/mcp/server.d.ts +0 -42
- package/dist/mcp/server.js +0 -313
- package/dist/orchestrator.d.ts +0 -21
- package/dist/orchestrator.js +0 -20
- package/dist/reporter/event_stream.d.ts +0 -7
- package/dist/reporter/event_stream.js +0 -20
- package/dist/reporter/index.d.ts +0 -1
- package/dist/reporter/index.js +0 -1
package/dist/backend/client.d.ts
CHANGED
|
@@ -75,6 +75,10 @@ export declare class BackendApiClient {
|
|
|
75
75
|
metadata?: Record<string, unknown>;
|
|
76
76
|
}): Promise<ProjectSummary>;
|
|
77
77
|
private request;
|
|
78
|
+
private buildUrl;
|
|
79
|
+
private sendRequest;
|
|
80
|
+
private shouldRetryWithApiPrefix;
|
|
81
|
+
private withApiPrefix;
|
|
78
82
|
private parseJson;
|
|
79
83
|
private safeJson;
|
|
80
84
|
}
|
package/dist/backend/client.js
CHANGED
|
@@ -167,23 +167,16 @@ export class BackendApiClient {
|
|
|
167
167
|
return ProjectSummary.fromJSON(payload);
|
|
168
168
|
}
|
|
169
169
|
async request(method, pathname, opts = {}) {
|
|
170
|
-
const url =
|
|
171
|
-
if (opts.query) {
|
|
172
|
-
opts.query.forEach((value, key) => url.searchParams.set(key, value));
|
|
173
|
-
}
|
|
170
|
+
const url = this.buildUrl(pathname, opts.query);
|
|
174
171
|
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
175
172
|
const timeout = controller ? setTimeout(() => controller.abort(), this.timeoutMs) : null;
|
|
176
173
|
try {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
},
|
|
184
|
-
body: opts.body,
|
|
185
|
-
signal: controller?.signal,
|
|
186
|
-
});
|
|
174
|
+
let response = await this.sendRequest(url, method, opts.body, controller);
|
|
175
|
+
if (response.status === 404 &&
|
|
176
|
+
this.shouldRetryWithApiPrefix(pathname, url)) {
|
|
177
|
+
const retryUrl = this.buildUrl(this.withApiPrefix(pathname), opts.query);
|
|
178
|
+
response = await this.sendRequest(retryUrl, method, opts.body, controller);
|
|
179
|
+
}
|
|
187
180
|
if (!response.ok) {
|
|
188
181
|
const details = await this.safeJson(response);
|
|
189
182
|
throw new BackendApiError(`Backend responded with ${response.status}`, response.status, details);
|
|
@@ -202,6 +195,39 @@ export class BackendApiClient {
|
|
|
202
195
|
}
|
|
203
196
|
}
|
|
204
197
|
}
|
|
198
|
+
buildUrl(pathname, query) {
|
|
199
|
+
const url = new URL(`${this.baseUrl}${pathname}`);
|
|
200
|
+
if (query) {
|
|
201
|
+
query.forEach((value, key) => url.searchParams.set(key, value));
|
|
202
|
+
}
|
|
203
|
+
return url;
|
|
204
|
+
}
|
|
205
|
+
async sendRequest(url, method, body, controller) {
|
|
206
|
+
return this.fetchImpl(url.toString(), {
|
|
207
|
+
method,
|
|
208
|
+
headers: {
|
|
209
|
+
Authorization: `Bearer ${this.config.agentToken}`,
|
|
210
|
+
Accept: 'application/json',
|
|
211
|
+
...(body ? { 'Content-Type': 'application/json' } : {}),
|
|
212
|
+
},
|
|
213
|
+
body,
|
|
214
|
+
signal: controller?.signal,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
shouldRetryWithApiPrefix(pathname, url) {
|
|
218
|
+
if (!pathname.startsWith('/'))
|
|
219
|
+
return false;
|
|
220
|
+
if (pathname === '/api' || pathname.startsWith('/api/'))
|
|
221
|
+
return false;
|
|
222
|
+
// If backendUrl already includes /api in the base path, don't retry.
|
|
223
|
+
return !url.pathname.startsWith('/api/');
|
|
224
|
+
}
|
|
225
|
+
withApiPrefix(pathname) {
|
|
226
|
+
if (pathname === '/api' || pathname.startsWith('/api/')) {
|
|
227
|
+
return pathname;
|
|
228
|
+
}
|
|
229
|
+
return `/api${pathname}`;
|
|
230
|
+
}
|
|
205
231
|
async parseJson(response) {
|
|
206
232
|
try {
|
|
207
233
|
return await response.json();
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { BackendApiClient } from './backend/index.js';
|
|
2
|
+
import { ConductorConfig } from './config/index.js';
|
|
3
|
+
import { MessageRouter } from './message/index.js';
|
|
4
|
+
import { SessionDiskStore, SessionManager } from './session/index.js';
|
|
5
|
+
import { ConductorWebSocketClient } from './ws/index.js';
|
|
6
|
+
type BackendApiLike = Pick<BackendApiClient, 'listProjects' | 'createProject' | 'listTasks' | 'createTask' | 'matchProjectByPath' | 'getProject' | 'updateProject'>;
|
|
7
|
+
type RealtimeClientLike = Pick<ConductorWebSocketClient, 'registerHandler' | 'connect' | 'disconnect' | 'sendJson'>;
|
|
8
|
+
export interface ConductorClientConnectOptions {
|
|
9
|
+
config?: ConductorConfig;
|
|
10
|
+
configFile?: string;
|
|
11
|
+
env?: Record<string, string | undefined>;
|
|
12
|
+
extraEnv?: Record<string, string | undefined>;
|
|
13
|
+
projectPath?: string;
|
|
14
|
+
backendApi?: BackendApiLike;
|
|
15
|
+
wsClient?: RealtimeClientLike;
|
|
16
|
+
sessionManager?: SessionManager;
|
|
17
|
+
sessionStore?: SessionDiskStore;
|
|
18
|
+
messageRouter?: MessageRouter;
|
|
19
|
+
agentHost?: string;
|
|
20
|
+
onConnected?: (event: {
|
|
21
|
+
isReconnect: boolean;
|
|
22
|
+
}) => void;
|
|
23
|
+
onDisconnected?: () => void;
|
|
24
|
+
}
|
|
25
|
+
interface ConductorClientInit {
|
|
26
|
+
config: ConductorConfig;
|
|
27
|
+
env: Record<string, string | undefined>;
|
|
28
|
+
projectPath: string;
|
|
29
|
+
backendApi: BackendApiLike;
|
|
30
|
+
wsClient: RealtimeClientLike;
|
|
31
|
+
sessionManager: SessionManager;
|
|
32
|
+
sessionStore: SessionDiskStore;
|
|
33
|
+
messageRouter: MessageRouter;
|
|
34
|
+
agentHost: string;
|
|
35
|
+
}
|
|
36
|
+
export declare class ConductorClient {
|
|
37
|
+
private readonly config;
|
|
38
|
+
private readonly env;
|
|
39
|
+
private readonly projectPath;
|
|
40
|
+
private readonly backendApi;
|
|
41
|
+
private readonly wsClient;
|
|
42
|
+
private readonly sessions;
|
|
43
|
+
private readonly sessionStore;
|
|
44
|
+
private readonly messageRouter;
|
|
45
|
+
private readonly agentHost;
|
|
46
|
+
private closed;
|
|
47
|
+
constructor(init: ConductorClientInit);
|
|
48
|
+
static connect(options?: ConductorClientConnectOptions): Promise<ConductorClient>;
|
|
49
|
+
close(): Promise<void>;
|
|
50
|
+
createTaskSession(payload: Record<string, any>): Promise<Record<string, any>>;
|
|
51
|
+
sendMessage(taskId: string, content: string, metadata?: Record<string, any>): Promise<Record<string, any>>;
|
|
52
|
+
sendTaskStatus(taskId: string, payload: Record<string, any>): Promise<Record<string, any>>;
|
|
53
|
+
sendRuntimeStatus(taskId: string, payload: Record<string, any>): Promise<Record<string, any>>;
|
|
54
|
+
sendAgentResume(payload?: {
|
|
55
|
+
active_tasks?: string[];
|
|
56
|
+
source?: string;
|
|
57
|
+
metadata?: Record<string, any>;
|
|
58
|
+
}): Promise<Record<string, any>>;
|
|
59
|
+
sendAgentCommandAck(payload: {
|
|
60
|
+
request_id: string;
|
|
61
|
+
task_id?: string;
|
|
62
|
+
event_type?: string;
|
|
63
|
+
accepted?: boolean;
|
|
64
|
+
}): Promise<Record<string, any>>;
|
|
65
|
+
receiveMessages(taskId: string, limit?: number): Promise<Record<string, any>>;
|
|
66
|
+
ackMessages(taskId: string, ackToken?: string | null): Promise<Record<string, any> | undefined>;
|
|
67
|
+
listProjects(): Promise<Record<string, any>>;
|
|
68
|
+
createProject(name: string, description?: string, metadata?: Record<string, unknown>): Promise<Record<string, any>>;
|
|
69
|
+
listTasks(payload?: Record<string, any>): Promise<Record<string, any>>;
|
|
70
|
+
getLocalProjectRecord(payload?: Record<string, any>): Promise<Record<string, any>>;
|
|
71
|
+
matchProjectByPath(payload?: Record<string, any>): Promise<Record<string, any>>;
|
|
72
|
+
bindProjectPath(projectId: string, payload?: Record<string, any>): Promise<Record<string, any>>;
|
|
73
|
+
private readonly handleBackendEvent;
|
|
74
|
+
private sendEnvelope;
|
|
75
|
+
private maybeAckInboundCommand;
|
|
76
|
+
private resolveHostname;
|
|
77
|
+
private waitForTaskCreation;
|
|
78
|
+
private readIntEnv;
|
|
79
|
+
}
|
|
80
|
+
export {};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { BackendApiClient } from './backend/index.js';
|
|
3
|
+
import { loadConfig } from './config/index.js';
|
|
4
|
+
import { MessageRouter } from './message/index.js';
|
|
5
|
+
import { SessionDiskStore, SessionManager, currentHostname, currentSessionId } from './session/index.js';
|
|
6
|
+
import { ConductorWebSocketClient } from './ws/index.js';
|
|
7
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
|
+
export class ConductorClient {
|
|
9
|
+
config;
|
|
10
|
+
env;
|
|
11
|
+
projectPath;
|
|
12
|
+
backendApi;
|
|
13
|
+
wsClient;
|
|
14
|
+
sessions;
|
|
15
|
+
sessionStore;
|
|
16
|
+
messageRouter;
|
|
17
|
+
agentHost;
|
|
18
|
+
closed = false;
|
|
19
|
+
constructor(init) {
|
|
20
|
+
this.config = init.config;
|
|
21
|
+
this.env = init.env;
|
|
22
|
+
this.projectPath = init.projectPath;
|
|
23
|
+
this.backendApi = init.backendApi;
|
|
24
|
+
this.wsClient = init.wsClient;
|
|
25
|
+
this.sessions = init.sessionManager;
|
|
26
|
+
this.sessionStore = init.sessionStore;
|
|
27
|
+
this.messageRouter = init.messageRouter;
|
|
28
|
+
this.agentHost = init.agentHost;
|
|
29
|
+
this.wsClient.registerHandler(this.handleBackendEvent);
|
|
30
|
+
}
|
|
31
|
+
static async connect(options = {}) {
|
|
32
|
+
const env = options.extraEnv ?? options.env ?? process.env;
|
|
33
|
+
const config = options.config ?? loadConfig(options.configFile, { env });
|
|
34
|
+
const projectPath = options.projectPath ?? process.cwd();
|
|
35
|
+
const backendApi = options.backendApi ?? new BackendApiClient(config);
|
|
36
|
+
const sessions = options.sessionManager ?? new SessionManager();
|
|
37
|
+
const sessionStore = options.sessionStore ?? SessionDiskStore.forBackendUrl(config.backendUrl);
|
|
38
|
+
const messageRouter = options.messageRouter ?? new MessageRouter(sessions);
|
|
39
|
+
const agentHost = resolveAgentHost(env, options.agentHost);
|
|
40
|
+
const wsClient = options.wsClient ??
|
|
41
|
+
new ConductorWebSocketClient(config, {
|
|
42
|
+
hostName: agentHost,
|
|
43
|
+
onConnected: options.onConnected,
|
|
44
|
+
onDisconnected: options.onDisconnected,
|
|
45
|
+
});
|
|
46
|
+
const client = new ConductorClient({
|
|
47
|
+
config,
|
|
48
|
+
env,
|
|
49
|
+
projectPath,
|
|
50
|
+
backendApi,
|
|
51
|
+
wsClient,
|
|
52
|
+
sessionManager: sessions,
|
|
53
|
+
sessionStore,
|
|
54
|
+
messageRouter,
|
|
55
|
+
agentHost,
|
|
56
|
+
});
|
|
57
|
+
await client.wsClient.connect();
|
|
58
|
+
return client;
|
|
59
|
+
}
|
|
60
|
+
async close() {
|
|
61
|
+
if (this.closed) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.closed = true;
|
|
65
|
+
await this.wsClient.disconnect();
|
|
66
|
+
}
|
|
67
|
+
async createTaskSession(payload) {
|
|
68
|
+
const projectId = String(payload.project_id || '').trim();
|
|
69
|
+
if (!projectId) {
|
|
70
|
+
throw new Error('project_id is required');
|
|
71
|
+
}
|
|
72
|
+
const title = String(payload.task_title || 'Untitled');
|
|
73
|
+
const taskId = String(payload.task_id || safeRandomUuid());
|
|
74
|
+
const sessionId = String(payload.session_id || taskId);
|
|
75
|
+
await this.sessions.addSession(taskId, sessionId, projectId);
|
|
76
|
+
await this.backendApi.createTask({
|
|
77
|
+
id: taskId,
|
|
78
|
+
projectId,
|
|
79
|
+
title,
|
|
80
|
+
backendType: typeof payload.backend_type === 'string'
|
|
81
|
+
? payload.backend_type
|
|
82
|
+
: typeof payload.backendType === 'string'
|
|
83
|
+
? payload.backendType
|
|
84
|
+
: undefined,
|
|
85
|
+
initialContent: typeof payload.prefill === 'string' ? payload.prefill : undefined,
|
|
86
|
+
agentHost: typeof payload.agent_host === 'string'
|
|
87
|
+
? payload.agent_host
|
|
88
|
+
: typeof payload.agentHost === 'string'
|
|
89
|
+
? payload.agentHost
|
|
90
|
+
: this.agentHost,
|
|
91
|
+
});
|
|
92
|
+
await this.waitForTaskCreation(projectId, taskId);
|
|
93
|
+
const projectPath = typeof payload.project_path === 'string' && payload.project_path
|
|
94
|
+
? payload.project_path
|
|
95
|
+
: this.projectPath;
|
|
96
|
+
this.sessionStore.upsert({
|
|
97
|
+
projectId,
|
|
98
|
+
taskId,
|
|
99
|
+
projectPath,
|
|
100
|
+
sessionId: currentSessionId(this.env),
|
|
101
|
+
hostname: this.resolveHostname(),
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
task_id: taskId,
|
|
105
|
+
session_id: sessionId,
|
|
106
|
+
app_url: payload.app_url,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async sendMessage(taskId, content, metadata) {
|
|
110
|
+
await this.sendEnvelope({
|
|
111
|
+
type: 'sdk_message',
|
|
112
|
+
payload: {
|
|
113
|
+
task_id: taskId,
|
|
114
|
+
content,
|
|
115
|
+
metadata,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
return { delivered: true };
|
|
119
|
+
}
|
|
120
|
+
async sendTaskStatus(taskId, payload) {
|
|
121
|
+
await this.sendEnvelope({
|
|
122
|
+
type: 'task_status_update',
|
|
123
|
+
payload: {
|
|
124
|
+
task_id: taskId,
|
|
125
|
+
status: payload?.status,
|
|
126
|
+
summary: payload?.summary,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
return { delivered: true };
|
|
130
|
+
}
|
|
131
|
+
async sendRuntimeStatus(taskId, payload) {
|
|
132
|
+
await this.sendEnvelope({
|
|
133
|
+
type: 'task_runtime_status',
|
|
134
|
+
payload: {
|
|
135
|
+
task_id: taskId,
|
|
136
|
+
state: payload?.state,
|
|
137
|
+
phase: payload?.phase,
|
|
138
|
+
source: payload?.source,
|
|
139
|
+
reply_in_progress: payload?.reply_in_progress,
|
|
140
|
+
status_line: payload?.status_line,
|
|
141
|
+
status_done_line: payload?.status_done_line,
|
|
142
|
+
reply_preview: payload?.reply_preview,
|
|
143
|
+
reply_to: payload?.reply_to,
|
|
144
|
+
backend: payload?.backend,
|
|
145
|
+
thread_id: payload?.thread_id,
|
|
146
|
+
created_at: payload?.created_at,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
return { delivered: true };
|
|
150
|
+
}
|
|
151
|
+
async sendAgentResume(payload = {}) {
|
|
152
|
+
await this.sendEnvelope({
|
|
153
|
+
type: 'agent_resume',
|
|
154
|
+
payload: {
|
|
155
|
+
active_tasks: Array.isArray(payload.active_tasks)
|
|
156
|
+
? payload.active_tasks.map((taskId) => String(taskId)).filter(Boolean)
|
|
157
|
+
: [],
|
|
158
|
+
source: payload.source,
|
|
159
|
+
metadata: payload.metadata,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
return { delivered: true };
|
|
163
|
+
}
|
|
164
|
+
async sendAgentCommandAck(payload) {
|
|
165
|
+
const requestId = String(payload.request_id || '').trim();
|
|
166
|
+
if (!requestId) {
|
|
167
|
+
throw new Error('request_id is required');
|
|
168
|
+
}
|
|
169
|
+
await this.sendEnvelope({
|
|
170
|
+
type: 'agent_command_ack',
|
|
171
|
+
payload: {
|
|
172
|
+
request_id: requestId,
|
|
173
|
+
task_id: payload.task_id,
|
|
174
|
+
event_type: payload.event_type,
|
|
175
|
+
accepted: payload.accepted !== false,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
return { delivered: true };
|
|
179
|
+
}
|
|
180
|
+
async receiveMessages(taskId, limit = 20) {
|
|
181
|
+
const messages = await this.sessions.popMessages(taskId, limit);
|
|
182
|
+
return formatMessagesResponse(messages);
|
|
183
|
+
}
|
|
184
|
+
async ackMessages(taskId, ackToken) {
|
|
185
|
+
if (!ackToken) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
const success = await this.sessions.ack(taskId, ackToken);
|
|
189
|
+
return { status: success ? 'ok' : 'ignored' };
|
|
190
|
+
}
|
|
191
|
+
async listProjects() {
|
|
192
|
+
const projects = await this.backendApi.listProjects();
|
|
193
|
+
return {
|
|
194
|
+
projects: projects.map((project) => typeof project.asObject === 'function'
|
|
195
|
+
? project.asObject()
|
|
196
|
+
: {
|
|
197
|
+
id: project.id,
|
|
198
|
+
name: project.name ?? null,
|
|
199
|
+
description: project.description ?? null,
|
|
200
|
+
}),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
async createProject(name, description, metadata) {
|
|
204
|
+
const project = await this.backendApi.createProject({ name, description, metadata });
|
|
205
|
+
return typeof project.asObject === 'function' ? project.asObject() : project;
|
|
206
|
+
}
|
|
207
|
+
async listTasks(payload = {}) {
|
|
208
|
+
const tasks = await this.backendApi.listTasks({
|
|
209
|
+
projectId: payload.project_id ? String(payload.project_id) : undefined,
|
|
210
|
+
status: payload.status ? String(payload.status) : undefined,
|
|
211
|
+
});
|
|
212
|
+
return {
|
|
213
|
+
tasks: tasks.map((task) => ({
|
|
214
|
+
id: task.id,
|
|
215
|
+
project_id: task.project_id ?? task.projectId ?? null,
|
|
216
|
+
title: task.title,
|
|
217
|
+
status: task.status,
|
|
218
|
+
created_at: task.created_at ?? task.createdAt ?? null,
|
|
219
|
+
updated_at: task.updated_at ?? task.updatedAt ?? null,
|
|
220
|
+
})),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
async getLocalProjectRecord(payload = {}) {
|
|
224
|
+
const projectPath = typeof payload.project_path === 'string' && payload.project_path
|
|
225
|
+
? payload.project_path
|
|
226
|
+
: this.projectPath;
|
|
227
|
+
const record = this.sessionStore.findByPath(projectPath);
|
|
228
|
+
if (!record) {
|
|
229
|
+
throw new Error(`No session record found for project path ${projectPath}`);
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
project_id: record.projectId,
|
|
233
|
+
task_id: Array.from(record.taskIds),
|
|
234
|
+
session_id: record.sessionId,
|
|
235
|
+
hostname: record.hostname,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async matchProjectByPath(payload = {}) {
|
|
239
|
+
const hostname = typeof payload.hostname === 'string' ? payload.hostname : currentHostname();
|
|
240
|
+
const projectPath = typeof payload.project_path === 'string' && payload.project_path
|
|
241
|
+
? payload.project_path
|
|
242
|
+
: this.projectPath;
|
|
243
|
+
const result = await this.backendApi.matchProjectByPath({
|
|
244
|
+
hostname,
|
|
245
|
+
path: projectPath,
|
|
246
|
+
});
|
|
247
|
+
if (result.project) {
|
|
248
|
+
return {
|
|
249
|
+
project_id: result.project.id,
|
|
250
|
+
project_name: result.project.name,
|
|
251
|
+
matched_path: result.matchedPath,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
project_id: null,
|
|
256
|
+
project_name: null,
|
|
257
|
+
matched_path: null,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
async bindProjectPath(projectId, payload = {}) {
|
|
261
|
+
if (!projectId) {
|
|
262
|
+
throw new Error('project_id is required');
|
|
263
|
+
}
|
|
264
|
+
const hostname = typeof payload.hostname === 'string' ? payload.hostname : currentHostname();
|
|
265
|
+
const projectPath = typeof payload.project_path === 'string' && payload.project_path
|
|
266
|
+
? payload.project_path
|
|
267
|
+
: this.projectPath;
|
|
268
|
+
const project = await this.backendApi.getProject(projectId);
|
|
269
|
+
const metadata = (project.metadata || {});
|
|
270
|
+
const localPaths = (metadata.localPaths || {});
|
|
271
|
+
localPaths[hostname] = projectPath;
|
|
272
|
+
metadata.localPaths = localPaths;
|
|
273
|
+
await this.backendApi.updateProject(projectId, { metadata });
|
|
274
|
+
return {
|
|
275
|
+
success: true,
|
|
276
|
+
hostname,
|
|
277
|
+
path: projectPath,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
handleBackendEvent = async (payload) => {
|
|
281
|
+
await this.messageRouter.handleBackendEvent(payload);
|
|
282
|
+
await this.maybeAckInboundCommand(payload);
|
|
283
|
+
};
|
|
284
|
+
async sendEnvelope(envelope) {
|
|
285
|
+
await this.wsClient.sendJson(envelope);
|
|
286
|
+
}
|
|
287
|
+
async maybeAckInboundCommand(payload) {
|
|
288
|
+
const eventType = typeof payload?.type === 'string' ? payload.type : '';
|
|
289
|
+
if (eventType !== 'task_user_message' && eventType !== 'task_action') {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const data = payload?.payload && typeof payload.payload === 'object'
|
|
293
|
+
? payload.payload
|
|
294
|
+
: null;
|
|
295
|
+
if (!data) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const requestId = typeof data.request_id === 'string' ? data.request_id.trim() : '';
|
|
299
|
+
if (!requestId) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
await this.sendAgentCommandAck({
|
|
304
|
+
request_id: requestId,
|
|
305
|
+
task_id: typeof data.task_id === 'string' ? data.task_id : undefined,
|
|
306
|
+
event_type: eventType,
|
|
307
|
+
accepted: true,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
312
|
+
console.warn(`[sdk] failed to ack inbound command ${requestId}: ${message}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
resolveHostname() {
|
|
316
|
+
const records = this.sessionStore.load();
|
|
317
|
+
for (const record of records) {
|
|
318
|
+
if (record.hostname) {
|
|
319
|
+
return record.hostname;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return currentHostname();
|
|
323
|
+
}
|
|
324
|
+
async waitForTaskCreation(projectId, taskId) {
|
|
325
|
+
const retries = this.readIntEnv('CONDUCTOR_TASK_CREATE_RETRIES', 10);
|
|
326
|
+
if (retries <= 0) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const delayMs = this.readIntEnv('CONDUCTOR_TASK_CREATE_DELAY_MS', 250);
|
|
330
|
+
for (let attempt = 0; attempt < retries; attempt += 1) {
|
|
331
|
+
try {
|
|
332
|
+
const tasks = await this.backendApi.listTasks({ projectId });
|
|
333
|
+
if (tasks.some((task) => String(task?.id || '') === taskId)) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
339
|
+
console.warn(`[sdk] createTaskSession unable to confirm task ${taskId}: ${message}`);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (attempt < retries - 1) {
|
|
343
|
+
await sleep(delayMs);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
console.warn(`[sdk] createTaskSession timed out waiting for task ${taskId}`);
|
|
347
|
+
}
|
|
348
|
+
readIntEnv(key, fallback) {
|
|
349
|
+
const raw = this.env[key];
|
|
350
|
+
if (!raw) {
|
|
351
|
+
return fallback;
|
|
352
|
+
}
|
|
353
|
+
const value = parseInt(raw, 10);
|
|
354
|
+
return Number.isFinite(value) ? value : fallback;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function formatMessagesResponse(messages) {
|
|
358
|
+
return {
|
|
359
|
+
messages: messages.map((msg) => ({
|
|
360
|
+
message_id: msg.messageId,
|
|
361
|
+
role: msg.role,
|
|
362
|
+
content: msg.content,
|
|
363
|
+
ack_token: msg.ackToken,
|
|
364
|
+
created_at: msg.createdAt.toISOString(),
|
|
365
|
+
})),
|
|
366
|
+
next_ack_token: messages.length ? messages[messages.length - 1].ackToken ?? null : null,
|
|
367
|
+
has_more: false,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function safeRandomUuid() {
|
|
371
|
+
if (typeof crypto.randomUUID === 'function') {
|
|
372
|
+
return crypto.randomUUID();
|
|
373
|
+
}
|
|
374
|
+
return crypto.randomBytes(16).toString('hex');
|
|
375
|
+
}
|
|
376
|
+
function resolveAgentHost(env, explicit) {
|
|
377
|
+
if (explicit && explicit.trim()) {
|
|
378
|
+
return explicit.trim();
|
|
379
|
+
}
|
|
380
|
+
const fromAgent = env.CONDUCTOR_AGENT_NAME;
|
|
381
|
+
if (typeof fromAgent === 'string' && fromAgent.trim()) {
|
|
382
|
+
return fromAgent.trim();
|
|
383
|
+
}
|
|
384
|
+
const fromDaemon = env.CONDUCTOR_DAEMON_NAME;
|
|
385
|
+
if (typeof fromDaemon === 'string' && fromDaemon.trim()) {
|
|
386
|
+
return fromDaemon.trim();
|
|
387
|
+
}
|
|
388
|
+
const pid = process.pid;
|
|
389
|
+
const host = env.HOSTNAME || env.COMPUTERNAME || 'unknown-host';
|
|
390
|
+
return `conductor-fire-${host}-${pid}`;
|
|
391
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,5 @@ export * from './backend/index.js';
|
|
|
3
3
|
export * from './ws/index.js';
|
|
4
4
|
export * from './session/index.js';
|
|
5
5
|
export * from './message/index.js';
|
|
6
|
+
export * from './client.js';
|
|
6
7
|
export * from './context/index.js';
|
|
7
|
-
export * from './mcp/index.js';
|
|
8
|
-
export * from './reporter/index.js';
|
|
9
|
-
export * from './orchestrator.js';
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,5 @@ export * from './backend/index.js';
|
|
|
3
3
|
export * from './ws/index.js';
|
|
4
4
|
export * from './session/index.js';
|
|
5
5
|
export * from './message/index.js';
|
|
6
|
+
export * from './client.js';
|
|
6
7
|
export * from './context/index.js';
|
|
7
|
-
export * from './mcp/index.js';
|
|
8
|
-
export * from './reporter/index.js';
|
|
9
|
-
export * from './orchestrator.js';
|
package/dist/message/router.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { SessionManager } from '../session/manager.js';
|
|
2
|
-
import { MCPNotifier } from '../mcp/notifications.js';
|
|
3
2
|
export type BackendPayload = Record<string, any>;
|
|
4
3
|
export type OutboundHandler = (payload: BackendPayload) => Promise<void> | void;
|
|
4
|
+
export interface MessageNotifier {
|
|
5
|
+
notifyNewMessage(taskId: string): Promise<void>;
|
|
6
|
+
}
|
|
5
7
|
export declare class MessageRouter {
|
|
6
8
|
private readonly sessions;
|
|
7
9
|
private readonly notifier?;
|
|
8
10
|
private readonly outboundHandlers;
|
|
9
|
-
constructor(sessions: SessionManager, notifier?:
|
|
11
|
+
constructor(sessions: SessionManager, notifier?: MessageNotifier | undefined);
|
|
10
12
|
registerOutboundHandler(handler: OutboundHandler): void;
|
|
11
13
|
handleBackendEvent(payload: BackendPayload): Promise<void>;
|
|
12
14
|
sendToBackend(payload: BackendPayload): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/conductor-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
"prepublishOnly": "npm run build"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
21
20
|
"ws": "^8.18.0",
|
|
22
21
|
"yaml": "^2.6.0",
|
|
23
22
|
"zod": "^3.24.1"
|
package/dist/bin/mcp-server.d.ts
DELETED
package/dist/bin/mcp-server.js
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
-
import { loadConfig, SessionManager, MessageRouter, BackendApiClient, ConductorWebSocketClient, MCPServer, EventReporter, SDKOrchestrator, MCPNotifier, } from '../index.js';
|
|
6
|
-
async function main() {
|
|
7
|
-
const config = loadConfig();
|
|
8
|
-
const sessions = new SessionManager();
|
|
9
|
-
const notifier = new MCPNotifier();
|
|
10
|
-
const router = new MessageRouter(sessions, notifier);
|
|
11
|
-
const backendApi = new BackendApiClient(config);
|
|
12
|
-
const configuredAgentHost = typeof process.env.CONDUCTOR_AGENT_NAME === 'string' && process.env.CONDUCTOR_AGENT_NAME.trim()
|
|
13
|
-
? process.env.CONDUCTOR_AGENT_NAME.trim()
|
|
14
|
-
: typeof process.env.CONDUCTOR_DAEMON_NAME === 'string' && process.env.CONDUCTOR_DAEMON_NAME.trim()
|
|
15
|
-
? process.env.CONDUCTOR_DAEMON_NAME.trim()
|
|
16
|
-
: defaultConductorFireHostName();
|
|
17
|
-
const wsClient = new ConductorWebSocketClient(config, { hostName: configuredAgentHost });
|
|
18
|
-
const backendSender = async (envelope) => {
|
|
19
|
-
await wsClient.sendJson(envelope);
|
|
20
|
-
};
|
|
21
|
-
const reporter = new EventReporter(backendSender);
|
|
22
|
-
const mcpServerLogic = new MCPServer(config, {
|
|
23
|
-
sessionManager: sessions,
|
|
24
|
-
messageRouter: router,
|
|
25
|
-
backendSender,
|
|
26
|
-
backendApi,
|
|
27
|
-
agentHost: configuredAgentHost,
|
|
28
|
-
});
|
|
29
|
-
const orchestrator = new SDKOrchestrator({
|
|
30
|
-
wsClient,
|
|
31
|
-
messageRouter: router,
|
|
32
|
-
sessionManager: sessions,
|
|
33
|
-
mcpServer: mcpServerLogic,
|
|
34
|
-
reporter,
|
|
35
|
-
});
|
|
36
|
-
const server = new Server({
|
|
37
|
-
name: 'conductor-ts',
|
|
38
|
-
version: '0.1.0',
|
|
39
|
-
}, {
|
|
40
|
-
capabilities: {
|
|
41
|
-
tools: {},
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
45
|
-
return {
|
|
46
|
-
tools: [
|
|
47
|
-
{
|
|
48
|
-
name: 'create_task_session',
|
|
49
|
-
description: 'Create a new task session',
|
|
50
|
-
inputSchema: {
|
|
51
|
-
type: 'object',
|
|
52
|
-
properties: {
|
|
53
|
-
project_id: { type: 'string' },
|
|
54
|
-
task_title: { type: 'string' },
|
|
55
|
-
agent_host: { type: 'string' },
|
|
56
|
-
prefill: { type: 'string' },
|
|
57
|
-
project_path: { type: 'string' },
|
|
58
|
-
},
|
|
59
|
-
required: ['project_id'],
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: 'list_projects',
|
|
64
|
-
description: 'List available projects',
|
|
65
|
-
inputSchema: {
|
|
66
|
-
type: 'object',
|
|
67
|
-
properties: {},
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
name: 'create_project',
|
|
72
|
-
description: 'Create a project',
|
|
73
|
-
inputSchema: {
|
|
74
|
-
type: 'object',
|
|
75
|
-
properties: {
|
|
76
|
-
name: { type: 'string' },
|
|
77
|
-
description: { type: 'string' },
|
|
78
|
-
metadata: { type: 'object' },
|
|
79
|
-
},
|
|
80
|
-
required: ['name'],
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
name: 'list_tasks',
|
|
85
|
-
description: 'List tasks',
|
|
86
|
-
inputSchema: {
|
|
87
|
-
type: 'object',
|
|
88
|
-
properties: {
|
|
89
|
-
project_id: { type: 'string' },
|
|
90
|
-
status: { type: 'string' },
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
name: 'send_message',
|
|
96
|
-
description: 'Send a message to a task',
|
|
97
|
-
inputSchema: {
|
|
98
|
-
type: 'object',
|
|
99
|
-
properties: {
|
|
100
|
-
task_id: { type: 'string' },
|
|
101
|
-
content: { type: 'string' },
|
|
102
|
-
metadata: { type: 'object' },
|
|
103
|
-
},
|
|
104
|
-
required: ['task_id', 'content'],
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
name: 'send_task_status',
|
|
109
|
-
description: 'Send a terminal or lifecycle task status update',
|
|
110
|
-
inputSchema: {
|
|
111
|
-
type: 'object',
|
|
112
|
-
properties: {
|
|
113
|
-
task_id: { type: 'string' },
|
|
114
|
-
status: { type: 'string' },
|
|
115
|
-
summary: { type: 'string' },
|
|
116
|
-
},
|
|
117
|
-
required: ['task_id', 'status'],
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
name: 'send_runtime_status',
|
|
122
|
-
description: 'Send a runtime status update to a task',
|
|
123
|
-
inputSchema: {
|
|
124
|
-
type: 'object',
|
|
125
|
-
properties: {
|
|
126
|
-
task_id: { type: 'string' },
|
|
127
|
-
state: { type: 'string' },
|
|
128
|
-
phase: { type: 'string' },
|
|
129
|
-
source: { type: 'string' },
|
|
130
|
-
reply_in_progress: { type: 'boolean' },
|
|
131
|
-
status_line: { type: 'string' },
|
|
132
|
-
status_done_line: { type: 'string' },
|
|
133
|
-
reply_preview: { type: 'string' },
|
|
134
|
-
reply_to: { type: 'string' },
|
|
135
|
-
backend: { type: 'string' },
|
|
136
|
-
thread_id: { type: 'string' },
|
|
137
|
-
created_at: { type: 'string' },
|
|
138
|
-
},
|
|
139
|
-
required: ['task_id'],
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: 'receive_messages',
|
|
144
|
-
description: 'Receive messages from a task',
|
|
145
|
-
inputSchema: {
|
|
146
|
-
type: 'object',
|
|
147
|
-
properties: {
|
|
148
|
-
task_id: { type: 'string' },
|
|
149
|
-
limit: { type: 'number' },
|
|
150
|
-
},
|
|
151
|
-
required: ['task_id'],
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
name: 'ack_messages',
|
|
156
|
-
description: 'Acknowledge messages',
|
|
157
|
-
inputSchema: {
|
|
158
|
-
type: 'object',
|
|
159
|
-
properties: {
|
|
160
|
-
task_id: { type: 'string' },
|
|
161
|
-
ack_token: { type: 'string' },
|
|
162
|
-
},
|
|
163
|
-
required: ['task_id', 'ack_token'],
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
name: 'get_local_project_id',
|
|
168
|
-
description: 'Get project ID for local path',
|
|
169
|
-
inputSchema: {
|
|
170
|
-
type: 'object',
|
|
171
|
-
properties: {
|
|
172
|
-
project_path: { type: 'string' },
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
],
|
|
177
|
-
};
|
|
178
|
-
});
|
|
179
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
180
|
-
const { name, arguments: args } = request.params;
|
|
181
|
-
try {
|
|
182
|
-
const result = await mcpServerLogic.handleRequest(name, args || {});
|
|
183
|
-
return {
|
|
184
|
-
content: [
|
|
185
|
-
{
|
|
186
|
-
type: 'text',
|
|
187
|
-
text: JSON.stringify(result),
|
|
188
|
-
},
|
|
189
|
-
],
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
return {
|
|
194
|
-
content: [
|
|
195
|
-
{
|
|
196
|
-
type: 'text',
|
|
197
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
198
|
-
},
|
|
199
|
-
],
|
|
200
|
-
isError: true,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
await orchestrator.start();
|
|
205
|
-
const transport = new StdioServerTransport();
|
|
206
|
-
await server.connect(transport);
|
|
207
|
-
// Keep alive
|
|
208
|
-
process.on('SIGINT', async () => {
|
|
209
|
-
await orchestrator.stop();
|
|
210
|
-
await server.close();
|
|
211
|
-
process.exit(0);
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
function defaultConductorFireHostName() {
|
|
215
|
-
const pid = process.pid;
|
|
216
|
-
const host = process.env.HOSTNAME || process.env.COMPUTERNAME || 'unknown-host';
|
|
217
|
-
return `conductor-fire-${host}-${pid}`;
|
|
218
|
-
}
|
|
219
|
-
main().catch((err) => {
|
|
220
|
-
console.error(err);
|
|
221
|
-
process.exit(1);
|
|
222
|
-
});
|
package/dist/mcp/index.d.ts
DELETED
package/dist/mcp/index.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface LogSession {
|
|
2
|
-
sendLogMessage(args: {
|
|
3
|
-
level: string;
|
|
4
|
-
data: string;
|
|
5
|
-
logger?: string;
|
|
6
|
-
}): Promise<void>;
|
|
7
|
-
}
|
|
8
|
-
export interface MCPContext {
|
|
9
|
-
requestContext?: {
|
|
10
|
-
session?: LogSession;
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
export declare class MCPNotifier {
|
|
14
|
-
private session?;
|
|
15
|
-
private readonly lock;
|
|
16
|
-
bindContext(ctx?: MCPContext | null): Promise<void>;
|
|
17
|
-
setSession(session: LogSession): Promise<void>;
|
|
18
|
-
notifyNewMessage(taskId: string): Promise<void>;
|
|
19
|
-
private getSession;
|
|
20
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
class AsyncLock {
|
|
2
|
-
tail = Promise.resolve();
|
|
3
|
-
async runExclusive(fn) {
|
|
4
|
-
const run = this.tail.then(fn, fn);
|
|
5
|
-
this.tail = run
|
|
6
|
-
.then(() => undefined)
|
|
7
|
-
.catch(() => undefined);
|
|
8
|
-
return run;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
export class MCPNotifier {
|
|
12
|
-
session;
|
|
13
|
-
lock = new AsyncLock();
|
|
14
|
-
async bindContext(ctx) {
|
|
15
|
-
if (!ctx?.requestContext?.session) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
await this.setSession(ctx.requestContext.session);
|
|
19
|
-
}
|
|
20
|
-
async setSession(session) {
|
|
21
|
-
await this.lock.runExclusive(async () => {
|
|
22
|
-
this.session = session;
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
async notifyNewMessage(taskId) {
|
|
26
|
-
const session = await this.getSession();
|
|
27
|
-
if (!session) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
try {
|
|
31
|
-
await session.sendLogMessage({
|
|
32
|
-
level: 'info',
|
|
33
|
-
data: `任务 ${taskId} 收到了新的消息,请调用 receive_messages 工具查看。`,
|
|
34
|
-
logger: 'conductor.notifications',
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
// Best-effort notification; ignore errors.
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
async getSession() {
|
|
42
|
-
return this.lock.runExclusive(async () => this.session);
|
|
43
|
-
}
|
|
44
|
-
}
|
package/dist/mcp/server.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { BackendPayload, MessageRouter } from '../message/router.js';
|
|
2
|
-
import { SessionManager } from '../session/manager.js';
|
|
3
|
-
import { SessionDiskStore } from '../session/store.js';
|
|
4
|
-
import { ConductorConfig } from '../config/index.js';
|
|
5
|
-
import { BackendApiClient } from '../backend/index.js';
|
|
6
|
-
type BackendSender = (payload: BackendPayload) => Promise<void>;
|
|
7
|
-
export interface MCPServerOptions {
|
|
8
|
-
sessionManager: SessionManager;
|
|
9
|
-
messageRouter: MessageRouter;
|
|
10
|
-
backendSender: BackendSender;
|
|
11
|
-
backendApi: Pick<BackendApiClient, 'listProjects' | 'listTasks' | 'createProject' | 'createTask' | 'matchProjectByPath' | 'getProject' | 'updateProject'>;
|
|
12
|
-
sessionStore?: SessionDiskStore;
|
|
13
|
-
env?: Record<string, string | undefined>;
|
|
14
|
-
agentHost?: string;
|
|
15
|
-
}
|
|
16
|
-
type ToolRequest = Record<string, any>;
|
|
17
|
-
type ToolResponse = Record<string, any>;
|
|
18
|
-
export declare class MCPServer {
|
|
19
|
-
private readonly config;
|
|
20
|
-
private readonly options;
|
|
21
|
-
private readonly tools;
|
|
22
|
-
private readonly sessionStore;
|
|
23
|
-
private readonly env;
|
|
24
|
-
constructor(config: ConductorConfig, options: MCPServerOptions);
|
|
25
|
-
handleRequest(toolName: string, payload: ToolRequest): Promise<ToolResponse>;
|
|
26
|
-
private toolCreateTaskSession;
|
|
27
|
-
private toolSendMessage;
|
|
28
|
-
private toolSendTaskStatus;
|
|
29
|
-
private toolSendRuntimeStatus;
|
|
30
|
-
private toolReceiveMessages;
|
|
31
|
-
private toolAckMessages;
|
|
32
|
-
private toolListProjects;
|
|
33
|
-
private toolCreateProject;
|
|
34
|
-
private toolListTasks;
|
|
35
|
-
private toolGetLocalProjectId;
|
|
36
|
-
private toolMatchProjectByPath;
|
|
37
|
-
private toolBindProjectPath;
|
|
38
|
-
private resolveHostname;
|
|
39
|
-
private waitForTaskCreation;
|
|
40
|
-
private readIntEnv;
|
|
41
|
-
}
|
|
42
|
-
export {};
|
package/dist/mcp/server.js
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import { SessionDiskStore, currentHostname, currentSessionId } from '../session/store.js';
|
|
3
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4
|
-
export class MCPServer {
|
|
5
|
-
config;
|
|
6
|
-
options;
|
|
7
|
-
tools;
|
|
8
|
-
sessionStore;
|
|
9
|
-
env;
|
|
10
|
-
constructor(config, options) {
|
|
11
|
-
this.config = config;
|
|
12
|
-
this.options = options;
|
|
13
|
-
// Use backend URL to determine session file path (isolates different environments)
|
|
14
|
-
this.sessionStore = options.sessionStore ?? SessionDiskStore.forBackendUrl(config.backendUrl);
|
|
15
|
-
this.env = options.env ?? process.env;
|
|
16
|
-
this.tools = {
|
|
17
|
-
create_task_session: this.toolCreateTaskSession,
|
|
18
|
-
send_message: this.toolSendMessage,
|
|
19
|
-
send_task_status: this.toolSendTaskStatus,
|
|
20
|
-
send_runtime_status: this.toolSendRuntimeStatus,
|
|
21
|
-
receive_messages: this.toolReceiveMessages,
|
|
22
|
-
ack_messages: this.toolAckMessages,
|
|
23
|
-
list_projects: this.toolListProjects,
|
|
24
|
-
create_project: this.toolCreateProject,
|
|
25
|
-
list_tasks: this.toolListTasks,
|
|
26
|
-
get_local_project_id: this.toolGetLocalProjectId,
|
|
27
|
-
match_project_by_path: this.toolMatchProjectByPath,
|
|
28
|
-
bind_project_path: this.toolBindProjectPath,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
async handleRequest(toolName, payload) {
|
|
32
|
-
const handler = this.tools[toolName];
|
|
33
|
-
if (!handler) {
|
|
34
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
35
|
-
}
|
|
36
|
-
return handler.call(this, payload);
|
|
37
|
-
}
|
|
38
|
-
async toolCreateTaskSession(payload) {
|
|
39
|
-
const projectId = String(payload.project_id || '');
|
|
40
|
-
if (!projectId) {
|
|
41
|
-
throw new Error('project_id is required');
|
|
42
|
-
}
|
|
43
|
-
const title = String(payload.task_title || 'Untitled');
|
|
44
|
-
const taskId = String(payload.task_id || crypto.randomUUID());
|
|
45
|
-
const sessionId = String(payload.session_id || taskId);
|
|
46
|
-
console.error(`[mcp] create_task_session task=${taskId} project=${projectId} title=${title} session=${sessionId}`);
|
|
47
|
-
await this.options.sessionManager.addSession(taskId, sessionId, projectId);
|
|
48
|
-
// Create task in database via HTTP API
|
|
49
|
-
await this.options.backendApi.createTask({
|
|
50
|
-
id: taskId,
|
|
51
|
-
projectId,
|
|
52
|
-
title,
|
|
53
|
-
backendType: typeof payload.backend_type === 'string'
|
|
54
|
-
? payload.backend_type
|
|
55
|
-
: typeof payload.backendType === 'string'
|
|
56
|
-
? payload.backendType
|
|
57
|
-
: undefined,
|
|
58
|
-
initialContent: payload.prefill,
|
|
59
|
-
agentHost: typeof payload.agent_host === 'string'
|
|
60
|
-
? payload.agent_host
|
|
61
|
-
: typeof payload.agentHost === 'string'
|
|
62
|
-
? payload.agentHost
|
|
63
|
-
: this.options.agentHost,
|
|
64
|
-
});
|
|
65
|
-
await this.waitForTaskCreation(projectId, taskId);
|
|
66
|
-
const projectPath = typeof payload.project_path === 'string' && payload.project_path
|
|
67
|
-
? payload.project_path
|
|
68
|
-
: process.cwd();
|
|
69
|
-
this.sessionStore.upsert({
|
|
70
|
-
projectId,
|
|
71
|
-
taskId,
|
|
72
|
-
projectPath,
|
|
73
|
-
sessionId: currentSessionId(this.env),
|
|
74
|
-
hostname: this.resolveHostname(),
|
|
75
|
-
});
|
|
76
|
-
return {
|
|
77
|
-
task_id: taskId,
|
|
78
|
-
session_id: sessionId,
|
|
79
|
-
app_url: payload.app_url,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
async toolSendMessage(payload) {
|
|
83
|
-
const taskId = String(payload.task_id || '');
|
|
84
|
-
if (!taskId) {
|
|
85
|
-
throw new Error('task_id required');
|
|
86
|
-
}
|
|
87
|
-
await this.options.backendSender({
|
|
88
|
-
type: 'sdk_message',
|
|
89
|
-
payload: {
|
|
90
|
-
task_id: taskId,
|
|
91
|
-
content: payload.content,
|
|
92
|
-
metadata: payload.metadata,
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
return { delivered: true };
|
|
96
|
-
}
|
|
97
|
-
async toolSendTaskStatus(payload) {
|
|
98
|
-
const taskId = String(payload.task_id || '');
|
|
99
|
-
if (!taskId) {
|
|
100
|
-
throw new Error('task_id required');
|
|
101
|
-
}
|
|
102
|
-
await this.options.backendSender({
|
|
103
|
-
type: 'task_status_update',
|
|
104
|
-
payload: {
|
|
105
|
-
task_id: taskId,
|
|
106
|
-
status: payload.status,
|
|
107
|
-
summary: payload.summary,
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
return { delivered: true };
|
|
111
|
-
}
|
|
112
|
-
async toolSendRuntimeStatus(payload) {
|
|
113
|
-
const taskId = String(payload.task_id || '');
|
|
114
|
-
if (!taskId) {
|
|
115
|
-
throw new Error('task_id required');
|
|
116
|
-
}
|
|
117
|
-
await this.options.backendSender({
|
|
118
|
-
type: 'task_runtime_status',
|
|
119
|
-
payload: {
|
|
120
|
-
task_id: taskId,
|
|
121
|
-
state: payload.state,
|
|
122
|
-
phase: payload.phase,
|
|
123
|
-
source: payload.source,
|
|
124
|
-
reply_in_progress: payload.reply_in_progress,
|
|
125
|
-
status_line: payload.status_line,
|
|
126
|
-
status_done_line: payload.status_done_line,
|
|
127
|
-
reply_preview: payload.reply_preview,
|
|
128
|
-
reply_to: payload.reply_to,
|
|
129
|
-
backend: payload.backend,
|
|
130
|
-
thread_id: payload.thread_id,
|
|
131
|
-
created_at: payload.created_at,
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
return { delivered: true };
|
|
135
|
-
}
|
|
136
|
-
async toolReceiveMessages(payload) {
|
|
137
|
-
const taskId = String(payload.task_id || '');
|
|
138
|
-
if (!taskId) {
|
|
139
|
-
throw new Error('task_id required');
|
|
140
|
-
}
|
|
141
|
-
const limit = typeof payload.limit === 'number' ? payload.limit : 20;
|
|
142
|
-
const messages = await this.options.sessionManager.popMessages(taskId, limit);
|
|
143
|
-
return formatMessagesResponse(messages);
|
|
144
|
-
}
|
|
145
|
-
async toolAckMessages(payload) {
|
|
146
|
-
const taskId = String(payload.task_id || '');
|
|
147
|
-
const ackToken = String(payload.ack_token || '');
|
|
148
|
-
if (!taskId || !ackToken) {
|
|
149
|
-
throw new Error('task_id and ack_token required');
|
|
150
|
-
}
|
|
151
|
-
const success = await this.options.sessionManager.ack(taskId, ackToken);
|
|
152
|
-
return { status: success ? 'ok' : 'ignored' };
|
|
153
|
-
}
|
|
154
|
-
async toolListProjects(_payload) {
|
|
155
|
-
const projects = await this.options.backendApi.listProjects();
|
|
156
|
-
return {
|
|
157
|
-
projects: projects.map((project) => typeof project.asObject === 'function'
|
|
158
|
-
? project.asObject()
|
|
159
|
-
: {
|
|
160
|
-
id: project.id,
|
|
161
|
-
name: project.name ?? null,
|
|
162
|
-
description: project.description ?? null,
|
|
163
|
-
}),
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
async toolCreateProject(payload) {
|
|
167
|
-
const name = String(payload.name || '').trim();
|
|
168
|
-
if (!name) {
|
|
169
|
-
throw new Error('name is required');
|
|
170
|
-
}
|
|
171
|
-
const description = payload.description ? String(payload.description) : undefined;
|
|
172
|
-
const metadata = payload.metadata && typeof payload.metadata === 'object' ? payload.metadata : undefined;
|
|
173
|
-
const project = await this.options.backendApi.createProject({
|
|
174
|
-
name,
|
|
175
|
-
description,
|
|
176
|
-
metadata,
|
|
177
|
-
});
|
|
178
|
-
return typeof project.asObject === 'function' ? project.asObject() : project;
|
|
179
|
-
}
|
|
180
|
-
async toolListTasks(payload) {
|
|
181
|
-
const tasks = await this.options.backendApi.listTasks({
|
|
182
|
-
projectId: payload.project_id ? String(payload.project_id) : undefined,
|
|
183
|
-
status: payload.status ? String(payload.status) : undefined,
|
|
184
|
-
});
|
|
185
|
-
return {
|
|
186
|
-
tasks: tasks.map((task) => ({
|
|
187
|
-
id: task.id,
|
|
188
|
-
project_id: task.project_id ?? task.projectId ?? null,
|
|
189
|
-
title: task.title,
|
|
190
|
-
status: task.status,
|
|
191
|
-
created_at: task.created_at ?? task.createdAt ?? null,
|
|
192
|
-
updated_at: task.updated_at ?? task.updatedAt ?? null,
|
|
193
|
-
})),
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
async toolGetLocalProjectId(payload) {
|
|
197
|
-
const projectPath = typeof payload.project_path === 'string' && payload.project_path
|
|
198
|
-
? payload.project_path
|
|
199
|
-
: process.cwd();
|
|
200
|
-
const record = this.sessionStore.findByPath(projectPath);
|
|
201
|
-
if (!record) {
|
|
202
|
-
throw new Error(`No session record found for project path ${projectPath}`);
|
|
203
|
-
}
|
|
204
|
-
return {
|
|
205
|
-
project_id: record.projectId,
|
|
206
|
-
task_id: Array.from(record.taskIds),
|
|
207
|
-
session_id: record.sessionId,
|
|
208
|
-
hostname: record.hostname,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
async toolMatchProjectByPath(payload) {
|
|
212
|
-
const hostname = typeof payload.hostname === 'string' ? payload.hostname : currentHostname();
|
|
213
|
-
const projectPath = typeof payload.project_path === 'string' && payload.project_path
|
|
214
|
-
? payload.project_path
|
|
215
|
-
: process.cwd();
|
|
216
|
-
console.error(`[mcp] match_project_by_path hostname=${hostname} path=${projectPath}`);
|
|
217
|
-
const result = await this.options.backendApi.matchProjectByPath({
|
|
218
|
-
hostname,
|
|
219
|
-
path: projectPath,
|
|
220
|
-
});
|
|
221
|
-
if (result.project) {
|
|
222
|
-
return {
|
|
223
|
-
project_id: result.project.id,
|
|
224
|
-
project_name: result.project.name,
|
|
225
|
-
matched_path: result.matchedPath,
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
return {
|
|
229
|
-
project_id: null,
|
|
230
|
-
project_name: null,
|
|
231
|
-
matched_path: null,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
async toolBindProjectPath(payload) {
|
|
235
|
-
const projectId = String(payload.project_id || '');
|
|
236
|
-
if (!projectId) {
|
|
237
|
-
throw new Error('project_id is required');
|
|
238
|
-
}
|
|
239
|
-
const hostname = typeof payload.hostname === 'string' ? payload.hostname : currentHostname();
|
|
240
|
-
const projectPath = typeof payload.project_path === 'string' && payload.project_path
|
|
241
|
-
? payload.project_path
|
|
242
|
-
: process.cwd();
|
|
243
|
-
console.error(`[mcp] bind_project_path project=${projectId} hostname=${hostname} path=${projectPath}`);
|
|
244
|
-
// Get current project metadata
|
|
245
|
-
const project = await this.options.backendApi.getProject(projectId);
|
|
246
|
-
const metadata = (project.metadata || {});
|
|
247
|
-
const localPaths = (metadata.localPaths || {});
|
|
248
|
-
// Update localPaths with new binding
|
|
249
|
-
localPaths[hostname] = projectPath;
|
|
250
|
-
metadata.localPaths = localPaths;
|
|
251
|
-
// Update project
|
|
252
|
-
await this.options.backendApi.updateProject(projectId, { metadata });
|
|
253
|
-
return {
|
|
254
|
-
success: true,
|
|
255
|
-
hostname,
|
|
256
|
-
path: projectPath,
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
resolveHostname() {
|
|
260
|
-
const records = this.sessionStore.load();
|
|
261
|
-
for (const record of records) {
|
|
262
|
-
if (record.hostname) {
|
|
263
|
-
return record.hostname;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return currentHostname();
|
|
267
|
-
}
|
|
268
|
-
async waitForTaskCreation(projectId, taskId) {
|
|
269
|
-
const retries = this.readIntEnv('CONDUCTOR_TASK_CREATE_RETRIES', 10);
|
|
270
|
-
if (retries <= 0) {
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
const delayMs = this.readIntEnv('CONDUCTOR_TASK_CREATE_DELAY_MS', 250);
|
|
274
|
-
for (let attempt = 0; attempt < retries; attempt += 1) {
|
|
275
|
-
try {
|
|
276
|
-
const tasks = await this.options.backendApi.listTasks({ projectId });
|
|
277
|
-
if (tasks.some((task) => String(task?.id || '') === taskId)) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
catch (error) {
|
|
282
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
283
|
-
console.warn(`[mcp] create_task_session unable to confirm task ${taskId}: ${message}`);
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
if (attempt < retries - 1) {
|
|
287
|
-
await sleep(delayMs);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
console.warn(`[mcp] create_task_session timed out waiting for task ${taskId}`);
|
|
291
|
-
}
|
|
292
|
-
readIntEnv(key, fallback) {
|
|
293
|
-
const raw = this.env[key];
|
|
294
|
-
if (!raw) {
|
|
295
|
-
return fallback;
|
|
296
|
-
}
|
|
297
|
-
const value = parseInt(raw, 10);
|
|
298
|
-
return Number.isFinite(value) ? value : fallback;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
function formatMessagesResponse(messages) {
|
|
302
|
-
return {
|
|
303
|
-
messages: messages.map((msg) => ({
|
|
304
|
-
message_id: msg.messageId,
|
|
305
|
-
role: msg.role,
|
|
306
|
-
content: msg.content,
|
|
307
|
-
ack_token: msg.ackToken,
|
|
308
|
-
created_at: msg.createdAt.toISOString(),
|
|
309
|
-
})),
|
|
310
|
-
next_ack_token: messages.length ? messages[messages.length - 1].ackToken ?? null : null,
|
|
311
|
-
has_more: false,
|
|
312
|
-
};
|
|
313
|
-
}
|
package/dist/orchestrator.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { MessageRouter } from './message/index.js';
|
|
2
|
-
import { EventReporter } from './reporter/index.js';
|
|
3
|
-
import { SessionManager } from './session/index.js';
|
|
4
|
-
import { ConductorWebSocketClient } from './ws/client.js';
|
|
5
|
-
import { MCPServer } from './mcp/server.js';
|
|
6
|
-
export interface OrchestratorDeps {
|
|
7
|
-
wsClient: ConductorWebSocketClient;
|
|
8
|
-
messageRouter: MessageRouter;
|
|
9
|
-
sessionManager: SessionManager;
|
|
10
|
-
mcpServer: MCPServer;
|
|
11
|
-
reporter: EventReporter;
|
|
12
|
-
}
|
|
13
|
-
export declare class SDKOrchestrator {
|
|
14
|
-
private readonly deps;
|
|
15
|
-
private readonly wsClient;
|
|
16
|
-
private readonly router;
|
|
17
|
-
constructor(deps: OrchestratorDeps);
|
|
18
|
-
start(): Promise<void>;
|
|
19
|
-
stop(): Promise<void>;
|
|
20
|
-
private handleBackendEvent;
|
|
21
|
-
}
|
package/dist/orchestrator.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export class SDKOrchestrator {
|
|
2
|
-
deps;
|
|
3
|
-
wsClient;
|
|
4
|
-
router;
|
|
5
|
-
constructor(deps) {
|
|
6
|
-
this.deps = deps;
|
|
7
|
-
this.wsClient = deps.wsClient;
|
|
8
|
-
this.router = deps.messageRouter;
|
|
9
|
-
this.wsClient.registerHandler((payload) => this.handleBackendEvent(payload));
|
|
10
|
-
}
|
|
11
|
-
async start() {
|
|
12
|
-
await this.wsClient.connect();
|
|
13
|
-
}
|
|
14
|
-
async stop() {
|
|
15
|
-
await this.wsClient.disconnect();
|
|
16
|
-
}
|
|
17
|
-
async handleBackendEvent(payload) {
|
|
18
|
-
await this.router.handleBackendEvent(payload);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export type BackendSender = (payload: Record<string, any>) => Promise<void>;
|
|
2
|
-
export declare class EventReporter {
|
|
3
|
-
private readonly backendSender;
|
|
4
|
-
constructor(backendSender: BackendSender);
|
|
5
|
-
emit(eventType: string, payload: Record<string, any>): Promise<void>;
|
|
6
|
-
taskStatus(taskId: string, status: string, summary?: string | null): Promise<void>;
|
|
7
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export class EventReporter {
|
|
2
|
-
backendSender;
|
|
3
|
-
constructor(backendSender) {
|
|
4
|
-
this.backendSender = backendSender;
|
|
5
|
-
}
|
|
6
|
-
async emit(eventType, payload) {
|
|
7
|
-
await this.backendSender({
|
|
8
|
-
type: eventType,
|
|
9
|
-
timestamp: new Date().toISOString(),
|
|
10
|
-
payload,
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
async taskStatus(taskId, status, summary) {
|
|
14
|
-
await this.emit('task_status_update', {
|
|
15
|
-
task_id: taskId,
|
|
16
|
-
status,
|
|
17
|
-
summary: summary ?? undefined,
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
}
|
package/dist/reporter/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './event_stream.js';
|
package/dist/reporter/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './event_stream.js';
|