@love-moon/conductor-sdk 0.6.1 → 0.7.1
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/CHANGELOG.md +12 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/shared.d.ts +1 -1
- package/dist/api/tasks.d.ts +68 -0
- package/dist/api/tasks.js +113 -0
- package/dist/backend/client.d.ts +9 -0
- package/dist/backend/client.js +31 -0
- package/dist/context/project_context.js +10 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @love-moon/conductor-sdk
|
|
2
2
|
|
|
3
|
+
## 0.7.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3f35925: Canonicalize the `github-duinodu` SSH host alias when normalizing Git remotes.
|
|
8
|
+
|
|
9
|
+
## 0.7.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 689fc50: Add scheduled message management APIs and conductor task schedule list/create/delete commands.
|
|
14
|
+
|
|
3
15
|
## 0.6.1
|
|
4
16
|
|
|
5
17
|
## 0.6.0
|
package/dist/api/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { ProjectsApi, type Project, type CreateProjectInput, type ListProjectsOptions, type ResolveProjectInput, } from './projects.js';
|
|
2
2
|
export { IssuesApi, type Issue, type ListIssuesInput, type CreateIssueInput, type UpdateIssueInput, type UpdateIssueStatusOptions, } from './issues.js';
|
|
3
|
-
export { TasksApi, type Task, type Message, type ListTasksInput, type SendTaskMessageOptions, type ListTaskMessagesOptions, } from './tasks.js';
|
|
3
|
+
export { TasksApi, type Task, type Message, type ListTasksInput, type SendTaskMessageOptions, type ListTaskMessagesOptions, type ScheduledMessage, type ScheduledMessageSchedule, type CreateScheduledMessageInput, } from './tasks.js';
|
|
4
4
|
export { ProjectNotResolvedError, buildAuditMetadata, isBackendApiError, type ApiClient, type SdkClientOptions, } from './shared.js';
|
package/dist/api/shared.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BackendApiClient, BackendApiError } from '../backend/index.js';
|
|
2
|
-
export type ApiClient = Pick<BackendApiClient, 'listProjects' | 'createProject' | 'getProject' | 'updateProject' | 'patchProjectByQuery' | 'setDefaultProject' | 'matchProjectByPath' | 'listIssues' | 'getIssue' | 'createIssue' | 'patchIssue' | 'deleteIssue' | 'listTasks' | 'getTask' | 'listTaskMessages' | 'postTaskMessage'>;
|
|
2
|
+
export type ApiClient = Pick<BackendApiClient, 'listProjects' | 'createProject' | 'getProject' | 'updateProject' | 'patchProjectByQuery' | 'setDefaultProject' | 'matchProjectByPath' | 'listIssues' | 'getIssue' | 'createIssue' | 'patchIssue' | 'deleteIssue' | 'listTasks' | 'getTask' | 'listTaskMessages' | 'postTaskMessage' | 'postTaskInsert' | 'listScheduledMessages' | 'createScheduledMessage' | 'deleteScheduledMessage'>;
|
|
3
3
|
export interface SdkClientOptions {
|
|
4
4
|
/**
|
|
5
5
|
* Optional override for the SDK version stamped onto audit metadata. Falls
|
package/dist/api/tasks.d.ts
CHANGED
|
@@ -21,6 +21,51 @@ export interface Message {
|
|
|
21
21
|
createdAt?: string | null;
|
|
22
22
|
raw: Record<string, unknown>;
|
|
23
23
|
}
|
|
24
|
+
export type ScheduledMessageSchedule = {
|
|
25
|
+
mode: 'delay';
|
|
26
|
+
amount: number;
|
|
27
|
+
unit: 'minute' | 'hour';
|
|
28
|
+
} | {
|
|
29
|
+
mode: 'at';
|
|
30
|
+
sendAt: string;
|
|
31
|
+
timezone?: string | null;
|
|
32
|
+
} | {
|
|
33
|
+
mode: 'interval';
|
|
34
|
+
every: number;
|
|
35
|
+
unit: 'minute' | 'hour';
|
|
36
|
+
condition?: 'none' | 'ai_idle';
|
|
37
|
+
stop?: {
|
|
38
|
+
maxRuns?: number | null;
|
|
39
|
+
maxSkips?: number | null;
|
|
40
|
+
stopAt?: string | null;
|
|
41
|
+
stopWhenTaskNotRunning?: boolean | null;
|
|
42
|
+
} | null;
|
|
43
|
+
};
|
|
44
|
+
export interface ScheduledMessage {
|
|
45
|
+
id: string;
|
|
46
|
+
userId?: string | null;
|
|
47
|
+
taskId: string;
|
|
48
|
+
sourceMessageId?: string | null;
|
|
49
|
+
content: string;
|
|
50
|
+
kind: string;
|
|
51
|
+
condition: string;
|
|
52
|
+
intervalMs?: number | null;
|
|
53
|
+
timezone?: string | null;
|
|
54
|
+
status: string;
|
|
55
|
+
nextRunAt: string;
|
|
56
|
+
runCount: number;
|
|
57
|
+
skipCount: number;
|
|
58
|
+
failureCount: number;
|
|
59
|
+
maxRuns?: number | null;
|
|
60
|
+
maxSkips?: number | null;
|
|
61
|
+
stopAt?: string | null;
|
|
62
|
+
stopWhenTaskNotRunning: boolean;
|
|
63
|
+
lastRunAt?: string | null;
|
|
64
|
+
lastError?: string | null;
|
|
65
|
+
createdAt?: string | null;
|
|
66
|
+
updatedAt?: string | null;
|
|
67
|
+
raw: Record<string, unknown>;
|
|
68
|
+
}
|
|
24
69
|
export interface ListTasksInput {
|
|
25
70
|
projectId?: string;
|
|
26
71
|
issueId?: string;
|
|
@@ -31,10 +76,23 @@ export interface SendTaskMessageOptions {
|
|
|
31
76
|
metadata?: Record<string, unknown>;
|
|
32
77
|
clientRequestId?: string;
|
|
33
78
|
}
|
|
79
|
+
export interface InsertTaskMessageOptions {
|
|
80
|
+
metadata?: Record<string, unknown>;
|
|
81
|
+
/**
|
|
82
|
+
* The reply target of the in-flight turn to interrupt. When omitted, the
|
|
83
|
+
* server resolves the most recent user message as the target.
|
|
84
|
+
*/
|
|
85
|
+
targetReplyTo?: string;
|
|
86
|
+
}
|
|
34
87
|
export interface ListTaskMessagesOptions {
|
|
35
88
|
limit?: number;
|
|
36
89
|
before?: string;
|
|
37
90
|
}
|
|
91
|
+
export interface CreateScheduledMessageInput {
|
|
92
|
+
content: string;
|
|
93
|
+
schedule: ScheduledMessageSchedule;
|
|
94
|
+
sourceMessageId?: string | null;
|
|
95
|
+
}
|
|
38
96
|
export declare class TasksApi {
|
|
39
97
|
private readonly client;
|
|
40
98
|
private readonly options;
|
|
@@ -42,5 +100,15 @@ export declare class TasksApi {
|
|
|
42
100
|
listTasks(input?: ListTasksInput): Promise<Task[]>;
|
|
43
101
|
getTask(taskId: string): Promise<Task>;
|
|
44
102
|
sendTaskMessage(taskId: string, content: string, options?: SendTaskMessageOptions): Promise<Message>;
|
|
103
|
+
/**
|
|
104
|
+
* Insert a mid-turn message into a running task. The message is delivered and
|
|
105
|
+
* the in-flight turn is interrupted so the inserted message runs next, instead
|
|
106
|
+
* of being queued until the current turn finishes (which is what
|
|
107
|
+
* {@link sendTaskMessage} does).
|
|
108
|
+
*/
|
|
109
|
+
insertTaskMessage(taskId: string, content: string, options?: InsertTaskMessageOptions): Promise<Message>;
|
|
45
110
|
listTaskMessages(taskId: string, options?: ListTaskMessagesOptions): Promise<Message[]>;
|
|
111
|
+
listScheduledMessages(taskId: string): Promise<ScheduledMessage[]>;
|
|
112
|
+
createScheduledMessage(taskId: string, input: CreateScheduledMessageInput): Promise<ScheduledMessage>;
|
|
113
|
+
deleteScheduledMessage(taskId: string, scheduleId: string): Promise<void>;
|
|
46
114
|
}
|
package/dist/api/tasks.js
CHANGED
|
@@ -43,6 +43,53 @@ const normalizeMessage = (payload) => {
|
|
|
43
43
|
raw: payload,
|
|
44
44
|
};
|
|
45
45
|
};
|
|
46
|
+
const normalizeNumber = (value, fallback = 0) => {
|
|
47
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === 'string' && value.trim()) {
|
|
51
|
+
const parsed = Number(value);
|
|
52
|
+
if (Number.isFinite(parsed)) {
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return fallback;
|
|
57
|
+
};
|
|
58
|
+
const normalizeScheduledMessage = (payload) => {
|
|
59
|
+
const id = payload.id ? String(payload.id) : '';
|
|
60
|
+
if (!id) {
|
|
61
|
+
throw new Error('Scheduled message payload missing id');
|
|
62
|
+
}
|
|
63
|
+
const taskId = payload.taskId ?? payload.task_id;
|
|
64
|
+
if (!taskId) {
|
|
65
|
+
throw new Error('Scheduled message payload missing taskId');
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
id,
|
|
69
|
+
userId: payload.userId ?? payload.user_id ?? null,
|
|
70
|
+
taskId: String(taskId),
|
|
71
|
+
sourceMessageId: payload.sourceMessageId ?? payload.source_message_id ?? null,
|
|
72
|
+
content: typeof payload.content === 'string' ? payload.content : '',
|
|
73
|
+
kind: String(payload.kind ?? ''),
|
|
74
|
+
condition: String(payload.condition ?? 'none'),
|
|
75
|
+
intervalMs: payload.intervalMs ?? payload.interval_ms ?? null,
|
|
76
|
+
timezone: payload.timezone ?? null,
|
|
77
|
+
status: String(payload.status ?? ''),
|
|
78
|
+
nextRunAt: String(payload.nextRunAt ?? payload.next_run_at ?? ''),
|
|
79
|
+
runCount: normalizeNumber(payload.runCount ?? payload.run_count),
|
|
80
|
+
skipCount: normalizeNumber(payload.skipCount ?? payload.skip_count),
|
|
81
|
+
failureCount: normalizeNumber(payload.failureCount ?? payload.failure_count),
|
|
82
|
+
maxRuns: payload.maxRuns ?? payload.max_runs ?? null,
|
|
83
|
+
maxSkips: payload.maxSkips ?? payload.max_skips ?? null,
|
|
84
|
+
stopAt: payload.stopAt ?? payload.stop_at ?? null,
|
|
85
|
+
stopWhenTaskNotRunning: Boolean(payload.stopWhenTaskNotRunning ?? payload.stop_when_task_not_running),
|
|
86
|
+
lastRunAt: payload.lastRunAt ?? payload.last_run_at ?? null,
|
|
87
|
+
lastError: payload.lastError ?? payload.last_error ?? null,
|
|
88
|
+
createdAt: payload.createdAt ?? payload.created_at ?? null,
|
|
89
|
+
updatedAt: payload.updatedAt ?? payload.updated_at ?? null,
|
|
90
|
+
raw: payload,
|
|
91
|
+
};
|
|
92
|
+
};
|
|
46
93
|
export class TasksApi {
|
|
47
94
|
client;
|
|
48
95
|
options;
|
|
@@ -104,6 +151,31 @@ export class TasksApi {
|
|
|
104
151
|
const payload = await this.client.postTaskMessage(trimmed, body);
|
|
105
152
|
return normalizeMessage(payload);
|
|
106
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Insert a mid-turn message into a running task. The message is delivered and
|
|
156
|
+
* the in-flight turn is interrupted so the inserted message runs next, instead
|
|
157
|
+
* of being queued until the current turn finishes (which is what
|
|
158
|
+
* {@link sendTaskMessage} does).
|
|
159
|
+
*/
|
|
160
|
+
async insertTaskMessage(taskId, content, options = {}) {
|
|
161
|
+
const trimmed = String(taskId ?? '').trim();
|
|
162
|
+
if (!trimmed) {
|
|
163
|
+
throw new Error('taskId is required');
|
|
164
|
+
}
|
|
165
|
+
if (typeof content !== 'string' || content.length === 0) {
|
|
166
|
+
throw new Error('content is required');
|
|
167
|
+
}
|
|
168
|
+
const metadata = buildAuditMetadata(options.metadata, this.options);
|
|
169
|
+
const body = {
|
|
170
|
+
content,
|
|
171
|
+
metadata,
|
|
172
|
+
};
|
|
173
|
+
if (options.targetReplyTo) {
|
|
174
|
+
body.target_reply_to = options.targetReplyTo;
|
|
175
|
+
}
|
|
176
|
+
const payload = await this.client.postTaskInsert(trimmed, body);
|
|
177
|
+
return normalizeMessage(payload.message ?? payload);
|
|
178
|
+
}
|
|
107
179
|
async listTaskMessages(taskId, options = {}) {
|
|
108
180
|
const trimmed = String(taskId ?? '').trim();
|
|
109
181
|
if (!trimmed) {
|
|
@@ -115,4 +187,45 @@ export class TasksApi {
|
|
|
115
187
|
});
|
|
116
188
|
return messages.map((entry) => normalizeMessage(entry));
|
|
117
189
|
}
|
|
190
|
+
async listScheduledMessages(taskId) {
|
|
191
|
+
const trimmed = String(taskId ?? '').trim();
|
|
192
|
+
if (!trimmed) {
|
|
193
|
+
throw new Error('taskId is required');
|
|
194
|
+
}
|
|
195
|
+
const schedules = await this.client.listScheduledMessages(trimmed);
|
|
196
|
+
return schedules.map((entry) => normalizeScheduledMessage(entry));
|
|
197
|
+
}
|
|
198
|
+
async createScheduledMessage(taskId, input) {
|
|
199
|
+
const trimmed = String(taskId ?? '').trim();
|
|
200
|
+
if (!trimmed) {
|
|
201
|
+
throw new Error('taskId is required');
|
|
202
|
+
}
|
|
203
|
+
const content = typeof input?.content === 'string' ? input.content.trim() : '';
|
|
204
|
+
if (!content) {
|
|
205
|
+
throw new Error('content is required');
|
|
206
|
+
}
|
|
207
|
+
if (!input?.schedule || typeof input.schedule !== 'object') {
|
|
208
|
+
throw new Error('schedule is required');
|
|
209
|
+
}
|
|
210
|
+
const body = {
|
|
211
|
+
content,
|
|
212
|
+
schedule: input.schedule,
|
|
213
|
+
};
|
|
214
|
+
if (Object.prototype.hasOwnProperty.call(input, 'sourceMessageId')) {
|
|
215
|
+
body.sourceMessageId = input.sourceMessageId ?? null;
|
|
216
|
+
}
|
|
217
|
+
const schedule = await this.client.createScheduledMessage(trimmed, body);
|
|
218
|
+
return normalizeScheduledMessage(schedule);
|
|
219
|
+
}
|
|
220
|
+
async deleteScheduledMessage(taskId, scheduleId) {
|
|
221
|
+
const trimmedTaskId = String(taskId ?? '').trim();
|
|
222
|
+
const trimmedScheduleId = String(scheduleId ?? '').trim();
|
|
223
|
+
if (!trimmedTaskId) {
|
|
224
|
+
throw new Error('taskId is required');
|
|
225
|
+
}
|
|
226
|
+
if (!trimmedScheduleId) {
|
|
227
|
+
throw new Error('scheduleId is required');
|
|
228
|
+
}
|
|
229
|
+
await this.client.deleteScheduledMessage(trimmedTaskId, trimmedScheduleId);
|
|
230
|
+
}
|
|
118
231
|
}
|
package/dist/backend/client.d.ts
CHANGED
|
@@ -216,6 +216,15 @@ export declare class BackendApiClient {
|
|
|
216
216
|
* endpoint directly here. (RFC 0025 §2 picks the simpler path.)
|
|
217
217
|
*/
|
|
218
218
|
postTaskMessage(taskId: string, params: Record<string, unknown>): Promise<Record<string, any>>;
|
|
219
|
+
/**
|
|
220
|
+
* POST /tasks/[taskId]/insert — insert a mid-turn message. Unlike
|
|
221
|
+
* {@link postTaskMessage}, which queues a message for after the current turn,
|
|
222
|
+
* this interrupts the running turn so the inserted message is processed next.
|
|
223
|
+
*/
|
|
224
|
+
postTaskInsert(taskId: string, params: Record<string, unknown>): Promise<Record<string, any>>;
|
|
225
|
+
listScheduledMessages(taskId: string): Promise<Record<string, any>[]>;
|
|
226
|
+
createScheduledMessage(taskId: string, params: Record<string, unknown>): Promise<Record<string, any>>;
|
|
227
|
+
deleteScheduledMessage(taskId: string, scheduleId: string): Promise<void>;
|
|
219
228
|
private request;
|
|
220
229
|
private buildUrl;
|
|
221
230
|
private sendRequest;
|
package/dist/backend/client.js
CHANGED
|
@@ -410,6 +410,37 @@ export class BackendApiClient {
|
|
|
410
410
|
});
|
|
411
411
|
return this.parseJson(response);
|
|
412
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* POST /tasks/[taskId]/insert — insert a mid-turn message. Unlike
|
|
415
|
+
* {@link postTaskMessage}, which queues a message for after the current turn,
|
|
416
|
+
* this interrupts the running turn so the inserted message is processed next.
|
|
417
|
+
*/
|
|
418
|
+
async postTaskInsert(taskId, params) {
|
|
419
|
+
const response = await this.request('POST', `/tasks/${taskId}/insert`, {
|
|
420
|
+
body: JSON.stringify(params),
|
|
421
|
+
});
|
|
422
|
+
return this.parseJson(response);
|
|
423
|
+
}
|
|
424
|
+
async listScheduledMessages(taskId) {
|
|
425
|
+
const response = await this.request('GET', `/tasks/${taskId}/scheduled-messages`);
|
|
426
|
+
const payload = await this.parseJson(response);
|
|
427
|
+
if (payload && typeof payload === 'object' && Array.isArray(payload.schedules)) {
|
|
428
|
+
return payload.schedules;
|
|
429
|
+
}
|
|
430
|
+
if (Array.isArray(payload)) {
|
|
431
|
+
return payload;
|
|
432
|
+
}
|
|
433
|
+
throw new BackendApiError('Invalid scheduled messages response', response.status, payload);
|
|
434
|
+
}
|
|
435
|
+
async createScheduledMessage(taskId, params) {
|
|
436
|
+
const response = await this.request('POST', `/tasks/${taskId}/scheduled-messages`, {
|
|
437
|
+
body: JSON.stringify(params),
|
|
438
|
+
});
|
|
439
|
+
return this.parseJson(response);
|
|
440
|
+
}
|
|
441
|
+
async deleteScheduledMessage(taskId, scheduleId) {
|
|
442
|
+
await this.request('DELETE', `/tasks/${taskId}/scheduled-messages/${scheduleId}`);
|
|
443
|
+
}
|
|
413
444
|
async request(method, pathname, opts = {}) {
|
|
414
445
|
const url = this.buildUrl(pathname, opts.query);
|
|
415
446
|
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
@@ -178,7 +178,7 @@ export function normalizeGitRemoteUrl(raw) {
|
|
|
178
178
|
// `pathname` preserves the leading slash so `${host}${pathname}` reads
|
|
179
179
|
// naturally; we drop trailing `/` and trailing `.git` afterwards.
|
|
180
180
|
const path = parsed.pathname.replace(/\/+$/, '');
|
|
181
|
-
const combined = `${parsed.host}${path}`.replace(/\.git$/i, '');
|
|
181
|
+
const combined = `${canonicalGitRemoteHost(parsed.host)}${path}`.replace(/\.git$/i, '');
|
|
182
182
|
return combined.toLowerCase() || null;
|
|
183
183
|
}
|
|
184
184
|
// scp form: `[user@]host:path`. The host segment must not contain `/`
|
|
@@ -187,7 +187,7 @@ export function normalizeGitRemoteUrl(raw) {
|
|
|
187
187
|
// remote URL anyway).
|
|
188
188
|
const scpMatch = trimmed.match(/^(?:[^@/:]+@)?([^/:]+):(?!\/)(.*)$/);
|
|
189
189
|
if (scpMatch) {
|
|
190
|
-
const host = scpMatch[1];
|
|
190
|
+
const host = canonicalGitRemoteHost(scpMatch[1]);
|
|
191
191
|
const path = scpMatch[2].replace(/\/+$/, '').replace(/\.git$/i, '');
|
|
192
192
|
return `${host}/${path}`.toLowerCase() || null;
|
|
193
193
|
}
|
|
@@ -196,6 +196,14 @@ export function normalizeGitRemoteUrl(raw) {
|
|
|
196
196
|
// real upstream URL.
|
|
197
197
|
return null;
|
|
198
198
|
}
|
|
199
|
+
function canonicalGitRemoteHost(host) {
|
|
200
|
+
const normalized = host.trim().toLowerCase();
|
|
201
|
+
// SSH config alias used for GitHub remotes in our deployments.
|
|
202
|
+
if (normalized === 'github-duinodu') {
|
|
203
|
+
return 'github.com';
|
|
204
|
+
}
|
|
205
|
+
return normalized;
|
|
206
|
+
}
|
|
199
207
|
function* walkFiles(root) {
|
|
200
208
|
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
201
209
|
for (const entry of entries) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/conductor-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"typescript": "^5.6.3",
|
|
34
34
|
"vitest": "^2.1.4"
|
|
35
35
|
},
|
|
36
|
-
"gitCommitId": "
|
|
36
|
+
"gitCommitId": "7e2be9c"
|
|
37
37
|
}
|