@love-moon/conductor-sdk 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @love-moon/conductor-sdk
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 689fc50: Add scheduled message management APIs and conductor task schedule list/create/delete commands.
8
+
9
+ ## 0.6.1
10
+
3
11
  ## 0.6.0
4
12
 
5
13
  ## 0.5.1
@@ -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';
@@ -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
@@ -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
  }
@@ -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;
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@love-moon/conductor-sdk",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
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": "2f914a7"
36
+ "gitCommitId": "ed4e3ab"
37
37
  }