@love-moon/conductor-sdk 0.2.11 → 0.2.13
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/client.d.ts +14 -0
- package/dist/client.js +109 -20
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/limits/index.d.ts +1 -0
- package/dist/limits/index.js +1 -0
- package/dist/limits/plan_limits.d.ts +3 -0
- package/dist/limits/plan_limits.js +80 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface ConductorClientConnectOptions {
|
|
|
21
21
|
isReconnect: boolean;
|
|
22
22
|
}) => void;
|
|
23
23
|
onDisconnected?: () => void;
|
|
24
|
+
onStopTask?: (event: StopTaskEvent) => Promise<void> | void;
|
|
24
25
|
}
|
|
25
26
|
interface ConductorClientInit {
|
|
26
27
|
config: ConductorConfig;
|
|
@@ -32,6 +33,12 @@ interface ConductorClientInit {
|
|
|
32
33
|
sessionStore: SessionDiskStore;
|
|
33
34
|
messageRouter: MessageRouter;
|
|
34
35
|
agentHost: string;
|
|
36
|
+
onStopTask?: (event: StopTaskEvent) => Promise<void> | void;
|
|
37
|
+
}
|
|
38
|
+
export interface StopTaskEvent {
|
|
39
|
+
taskId: string;
|
|
40
|
+
requestId?: string;
|
|
41
|
+
reason?: string;
|
|
35
42
|
}
|
|
36
43
|
export declare class ConductorClient {
|
|
37
44
|
private readonly config;
|
|
@@ -43,6 +50,7 @@ export declare class ConductorClient {
|
|
|
43
50
|
private readonly sessionStore;
|
|
44
51
|
private readonly messageRouter;
|
|
45
52
|
private readonly agentHost;
|
|
53
|
+
private readonly onStopTask?;
|
|
46
54
|
private closed;
|
|
47
55
|
constructor(init: ConductorClientInit);
|
|
48
56
|
static connect(options?: ConductorClientConnectOptions): Promise<ConductorClient>;
|
|
@@ -62,6 +70,11 @@ export declare class ConductorClient {
|
|
|
62
70
|
event_type?: string;
|
|
63
71
|
accepted?: boolean;
|
|
64
72
|
}): Promise<Record<string, any>>;
|
|
73
|
+
sendTaskStopAck(payload: {
|
|
74
|
+
task_id: string;
|
|
75
|
+
request_id: string;
|
|
76
|
+
accepted?: boolean;
|
|
77
|
+
}): Promise<Record<string, any>>;
|
|
65
78
|
receiveMessages(taskId: string, limit?: number): Promise<Record<string, any>>;
|
|
66
79
|
ackMessages(taskId: string, ackToken?: string | null): Promise<Record<string, any> | undefined>;
|
|
67
80
|
listProjects(): Promise<Record<string, any>>;
|
|
@@ -73,6 +86,7 @@ export declare class ConductorClient {
|
|
|
73
86
|
private readonly handleBackendEvent;
|
|
74
87
|
private sendEnvelope;
|
|
75
88
|
private maybeAckInboundCommand;
|
|
89
|
+
private handleStopTaskEvent;
|
|
76
90
|
private resolveHostname;
|
|
77
91
|
private waitForTaskCreation;
|
|
78
92
|
private readIntEnv;
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import { BackendApiClient } from './backend/index.js';
|
|
3
3
|
import { loadConfig } from './config/index.js';
|
|
4
|
+
import { getPlanLimitMessageFromError } from './limits/index.js';
|
|
4
5
|
import { MessageRouter } from './message/index.js';
|
|
5
6
|
import { SessionDiskStore, SessionManager, currentHostname, currentSessionId } from './session/index.js';
|
|
6
7
|
import { ConductorWebSocketClient } from './ws/index.js';
|
|
@@ -15,6 +16,7 @@ export class ConductorClient {
|
|
|
15
16
|
sessionStore;
|
|
16
17
|
messageRouter;
|
|
17
18
|
agentHost;
|
|
19
|
+
onStopTask;
|
|
18
20
|
closed = false;
|
|
19
21
|
constructor(init) {
|
|
20
22
|
this.config = init.config;
|
|
@@ -26,6 +28,7 @@ export class ConductorClient {
|
|
|
26
28
|
this.sessionStore = init.sessionStore;
|
|
27
29
|
this.messageRouter = init.messageRouter;
|
|
28
30
|
this.agentHost = init.agentHost;
|
|
31
|
+
this.onStopTask = init.onStopTask;
|
|
29
32
|
this.wsClient.registerHandler(this.handleBackendEvent);
|
|
30
33
|
}
|
|
31
34
|
static async connect(options = {}) {
|
|
@@ -53,6 +56,7 @@ export class ConductorClient {
|
|
|
53
56
|
sessionStore,
|
|
54
57
|
messageRouter,
|
|
55
58
|
agentHost,
|
|
59
|
+
onStopTask: options.onStopTask,
|
|
56
60
|
});
|
|
57
61
|
await client.wsClient.connect();
|
|
58
62
|
return client;
|
|
@@ -73,22 +77,31 @@ export class ConductorClient {
|
|
|
73
77
|
const taskId = String(payload.task_id || safeRandomUuid());
|
|
74
78
|
const sessionId = String(payload.session_id || taskId);
|
|
75
79
|
await this.sessions.addSession(taskId, sessionId, projectId);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
try {
|
|
81
|
+
await this.backendApi.createTask({
|
|
82
|
+
id: taskId,
|
|
83
|
+
projectId,
|
|
84
|
+
title,
|
|
85
|
+
backendType: typeof payload.backend_type === 'string'
|
|
86
|
+
? payload.backend_type
|
|
87
|
+
: typeof payload.backendType === 'string'
|
|
88
|
+
? payload.backendType
|
|
89
|
+
: undefined,
|
|
90
|
+
initialContent: typeof payload.prefill === 'string' ? payload.prefill : undefined,
|
|
91
|
+
agentHost: typeof payload.agent_host === 'string'
|
|
92
|
+
? payload.agent_host
|
|
93
|
+
: typeof payload.agentHost === 'string'
|
|
94
|
+
? payload.agentHost
|
|
95
|
+
: this.agentHost,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
const limitMessage = getPlanLimitMessageFromError(error);
|
|
100
|
+
if (limitMessage) {
|
|
101
|
+
throw new Error(limitMessage, error instanceof Error ? { cause: error } : undefined);
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
92
105
|
await this.waitForTaskCreation(projectId, taskId);
|
|
93
106
|
const projectPath = typeof payload.project_path === 'string' && payload.project_path
|
|
94
107
|
? payload.project_path
|
|
@@ -143,6 +156,12 @@ export class ConductorClient {
|
|
|
143
156
|
reply_to: payload?.reply_to,
|
|
144
157
|
backend: payload?.backend,
|
|
145
158
|
thread_id: payload?.thread_id,
|
|
159
|
+
daemon: payload?.daemon,
|
|
160
|
+
pid: payload?.pid,
|
|
161
|
+
session_id: payload?.session_id,
|
|
162
|
+
session_file_path: payload?.session_file_path,
|
|
163
|
+
token_usage_percent: payload?.token_usage_percent,
|
|
164
|
+
context_usage_percent: payload?.context_usage_percent,
|
|
146
165
|
created_at: payload?.created_at,
|
|
147
166
|
},
|
|
148
167
|
});
|
|
@@ -177,6 +196,25 @@ export class ConductorClient {
|
|
|
177
196
|
});
|
|
178
197
|
return { delivered: true };
|
|
179
198
|
}
|
|
199
|
+
async sendTaskStopAck(payload) {
|
|
200
|
+
const taskId = String(payload.task_id || '').trim();
|
|
201
|
+
const requestId = String(payload.request_id || '').trim();
|
|
202
|
+
if (!taskId) {
|
|
203
|
+
throw new Error('task_id is required');
|
|
204
|
+
}
|
|
205
|
+
if (!requestId) {
|
|
206
|
+
throw new Error('request_id is required');
|
|
207
|
+
}
|
|
208
|
+
await this.sendEnvelope({
|
|
209
|
+
type: 'task_stop_ack',
|
|
210
|
+
payload: {
|
|
211
|
+
task_id: taskId,
|
|
212
|
+
request_id: requestId,
|
|
213
|
+
accepted: payload.accepted !== false,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
return { delivered: true };
|
|
217
|
+
}
|
|
180
218
|
async receiveMessages(taskId, limit = 20) {
|
|
181
219
|
const messages = await this.sessions.popMessages(taskId, limit);
|
|
182
220
|
return formatMessagesResponse(messages);
|
|
@@ -278,15 +316,20 @@ export class ConductorClient {
|
|
|
278
316
|
};
|
|
279
317
|
}
|
|
280
318
|
handleBackendEvent = async (payload) => {
|
|
319
|
+
const stopCommandAccepted = await this.handleStopTaskEvent(payload);
|
|
281
320
|
await this.messageRouter.handleBackendEvent(payload);
|
|
282
|
-
await this.maybeAckInboundCommand(payload
|
|
321
|
+
await this.maybeAckInboundCommand(payload, {
|
|
322
|
+
accepted: typeof stopCommandAccepted === 'boolean'
|
|
323
|
+
? stopCommandAccepted
|
|
324
|
+
: undefined,
|
|
325
|
+
});
|
|
283
326
|
};
|
|
284
327
|
async sendEnvelope(envelope) {
|
|
285
328
|
await this.wsClient.sendJson(envelope);
|
|
286
329
|
}
|
|
287
|
-
async maybeAckInboundCommand(payload) {
|
|
330
|
+
async maybeAckInboundCommand(payload, options = {}) {
|
|
288
331
|
const eventType = typeof payload?.type === 'string' ? payload.type : '';
|
|
289
|
-
if (eventType !== 'task_user_message' && eventType !== 'task_action') {
|
|
332
|
+
if (eventType !== 'task_user_message' && eventType !== 'task_action' && eventType !== 'stop_task') {
|
|
290
333
|
return;
|
|
291
334
|
}
|
|
292
335
|
const data = payload?.payload && typeof payload.payload === 'object'
|
|
@@ -304,7 +347,7 @@ export class ConductorClient {
|
|
|
304
347
|
request_id: requestId,
|
|
305
348
|
task_id: typeof data.task_id === 'string' ? data.task_id : undefined,
|
|
306
349
|
event_type: eventType,
|
|
307
|
-
accepted: true,
|
|
350
|
+
accepted: typeof options.accepted === 'boolean' ? options.accepted : true,
|
|
308
351
|
});
|
|
309
352
|
}
|
|
310
353
|
catch (error) {
|
|
@@ -312,6 +355,52 @@ export class ConductorClient {
|
|
|
312
355
|
console.warn(`[sdk] failed to ack inbound command ${requestId}: ${message}`);
|
|
313
356
|
}
|
|
314
357
|
}
|
|
358
|
+
async handleStopTaskEvent(payload) {
|
|
359
|
+
if (typeof payload?.type !== 'string' || payload.type !== 'stop_task') {
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
const data = payload?.payload && typeof payload.payload === 'object'
|
|
363
|
+
? payload.payload
|
|
364
|
+
: null;
|
|
365
|
+
if (!data) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
const taskId = typeof data.task_id === 'string' ? data.task_id.trim() : '';
|
|
369
|
+
const requestId = typeof data.request_id === 'string' ? data.request_id.trim() : '';
|
|
370
|
+
const reason = typeof data.reason === 'string' ? data.reason.trim() : '';
|
|
371
|
+
if (!taskId) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
let accepted = false;
|
|
375
|
+
if (this.onStopTask) {
|
|
376
|
+
try {
|
|
377
|
+
await this.onStopTask({
|
|
378
|
+
taskId,
|
|
379
|
+
requestId: requestId || undefined,
|
|
380
|
+
reason: reason || undefined,
|
|
381
|
+
});
|
|
382
|
+
accepted = true;
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
386
|
+
console.warn(`[sdk] stop_task callback failed for task ${taskId}: ${message}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (requestId) {
|
|
390
|
+
try {
|
|
391
|
+
await this.sendTaskStopAck({
|
|
392
|
+
task_id: taskId,
|
|
393
|
+
request_id: requestId,
|
|
394
|
+
accepted,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
399
|
+
console.warn(`[sdk] failed to send task_stop_ack for task ${taskId}: ${message}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return accepted;
|
|
403
|
+
}
|
|
315
404
|
resolveHostname() {
|
|
316
405
|
const records = this.sessionStore.load();
|
|
317
406
|
for (const record of records) {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './plan_limits.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './plan_limits.js';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const PLAN_LIMIT_MESSAGES = {
|
|
2
|
+
manual_fire_active_task: 'Free plan limit reached: only 1 active fire task is allowed.',
|
|
3
|
+
app_active_task: 'Free plan limit reached: only 1 active app task is allowed.',
|
|
4
|
+
daemon_active_connection: 'Free plan limit reached: only 1 active daemon connection is allowed.',
|
|
5
|
+
};
|
|
6
|
+
function normalizeText(value) {
|
|
7
|
+
if (typeof value !== 'string') {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const text = value.trim();
|
|
11
|
+
return text ? text : null;
|
|
12
|
+
}
|
|
13
|
+
function getMessageByLimitType(limitType) {
|
|
14
|
+
const normalized = limitType.trim().toLowerCase();
|
|
15
|
+
if (normalized in PLAN_LIMIT_MESSAGES) {
|
|
16
|
+
return PLAN_LIMIT_MESSAGES[normalized];
|
|
17
|
+
}
|
|
18
|
+
if (normalized === 'free_daemon_connection' || normalized === 'free-daemon-limit') {
|
|
19
|
+
return PLAN_LIMIT_MESSAGES.daemon_active_connection;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function inferFromText(raw) {
|
|
24
|
+
const text = normalizeText(raw);
|
|
25
|
+
if (!text) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const lower = text.toLowerCase();
|
|
29
|
+
if (lower.includes('one active manual fire task')) {
|
|
30
|
+
return PLAN_LIMIT_MESSAGES.manual_fire_active_task;
|
|
31
|
+
}
|
|
32
|
+
if (lower.includes('one active app task')) {
|
|
33
|
+
return PLAN_LIMIT_MESSAGES.app_active_task;
|
|
34
|
+
}
|
|
35
|
+
if (lower.includes('one active daemon connection')) {
|
|
36
|
+
return PLAN_LIMIT_MESSAGES.daemon_active_connection;
|
|
37
|
+
}
|
|
38
|
+
if (lower.includes('free plan task limit reached')) {
|
|
39
|
+
return 'Free plan task limit reached: only 1 active fire task and 1 active app task are allowed.';
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
export function getPlanLimitMessageFromDetails(details) {
|
|
44
|
+
if (!details) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (typeof details === 'string') {
|
|
48
|
+
return inferFromText(details);
|
|
49
|
+
}
|
|
50
|
+
if (typeof details !== 'object') {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const payload = details;
|
|
54
|
+
const byType = normalizeText(payload.limit_type);
|
|
55
|
+
if (byType) {
|
|
56
|
+
const mapped = getMessageByLimitType(byType);
|
|
57
|
+
if (mapped) {
|
|
58
|
+
return mapped;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return inferFromText(payload.message) ?? inferFromText(payload.error);
|
|
62
|
+
}
|
|
63
|
+
export function getPlanLimitMessageFromError(error) {
|
|
64
|
+
if (!error || typeof error !== 'object') {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const payload = error;
|
|
68
|
+
return (getPlanLimitMessageFromDetails(payload.details) ??
|
|
69
|
+
inferFromText(payload.message));
|
|
70
|
+
}
|
|
71
|
+
export function getPlanLimitMessageFromRealtimeEvent(event) {
|
|
72
|
+
if (!event || typeof event !== 'object') {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const payload = event;
|
|
76
|
+
if (typeof payload.type === 'string' && payload.type !== 'error') {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return getPlanLimitMessageFromDetails(payload.payload);
|
|
80
|
+
}
|