@flowdot.ai/daemon 1.0.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.
Files changed (85) hide show
  1. package/LICENSE +45 -0
  2. package/README.md +51 -0
  3. package/dist/goals/DependencyResolver.d.ts +54 -0
  4. package/dist/goals/DependencyResolver.js +329 -0
  5. package/dist/goals/ErrorRecovery.d.ts +133 -0
  6. package/dist/goals/ErrorRecovery.js +489 -0
  7. package/dist/goals/GoalApiClient.d.ts +81 -0
  8. package/dist/goals/GoalApiClient.js +743 -0
  9. package/dist/goals/GoalCache.d.ts +65 -0
  10. package/dist/goals/GoalCache.js +243 -0
  11. package/dist/goals/GoalCommsHandler.d.ts +150 -0
  12. package/dist/goals/GoalCommsHandler.js +378 -0
  13. package/dist/goals/GoalExporter.d.ts +164 -0
  14. package/dist/goals/GoalExporter.js +318 -0
  15. package/dist/goals/GoalImporter.d.ts +107 -0
  16. package/dist/goals/GoalImporter.js +345 -0
  17. package/dist/goals/GoalManager.d.ts +110 -0
  18. package/dist/goals/GoalManager.js +535 -0
  19. package/dist/goals/GoalReporter.d.ts +105 -0
  20. package/dist/goals/GoalReporter.js +534 -0
  21. package/dist/goals/GoalScheduler.d.ts +102 -0
  22. package/dist/goals/GoalScheduler.js +209 -0
  23. package/dist/goals/GoalValidator.d.ts +72 -0
  24. package/dist/goals/GoalValidator.js +657 -0
  25. package/dist/goals/MetaGoalEnforcer.d.ts +111 -0
  26. package/dist/goals/MetaGoalEnforcer.js +536 -0
  27. package/dist/goals/MilestoneBreaker.d.ts +74 -0
  28. package/dist/goals/MilestoneBreaker.js +348 -0
  29. package/dist/goals/PermissionBridge.d.ts +109 -0
  30. package/dist/goals/PermissionBridge.js +326 -0
  31. package/dist/goals/ProgressTracker.d.ts +113 -0
  32. package/dist/goals/ProgressTracker.js +324 -0
  33. package/dist/goals/ReviewScheduler.d.ts +106 -0
  34. package/dist/goals/ReviewScheduler.js +360 -0
  35. package/dist/goals/TaskExecutor.d.ts +116 -0
  36. package/dist/goals/TaskExecutor.js +370 -0
  37. package/dist/goals/TaskFeedback.d.ts +126 -0
  38. package/dist/goals/TaskFeedback.js +402 -0
  39. package/dist/goals/TaskGenerator.d.ts +75 -0
  40. package/dist/goals/TaskGenerator.js +329 -0
  41. package/dist/goals/TaskQueue.d.ts +84 -0
  42. package/dist/goals/TaskQueue.js +331 -0
  43. package/dist/goals/TaskSanitizer.d.ts +61 -0
  44. package/dist/goals/TaskSanitizer.js +464 -0
  45. package/dist/goals/errors.d.ts +116 -0
  46. package/dist/goals/errors.js +299 -0
  47. package/dist/goals/index.d.ts +24 -0
  48. package/dist/goals/index.js +23 -0
  49. package/dist/goals/types.d.ts +395 -0
  50. package/dist/goals/types.js +230 -0
  51. package/dist/index.d.ts +4 -0
  52. package/dist/index.js +3 -0
  53. package/dist/loop/DaemonIPC.d.ts +67 -0
  54. package/dist/loop/DaemonIPC.js +358 -0
  55. package/dist/loop/IntervalParser.d.ts +39 -0
  56. package/dist/loop/IntervalParser.js +217 -0
  57. package/dist/loop/LoopDaemon.d.ts +123 -0
  58. package/dist/loop/LoopDaemon.js +1821 -0
  59. package/dist/loop/LoopExecutor.d.ts +93 -0
  60. package/dist/loop/LoopExecutor.js +326 -0
  61. package/dist/loop/LoopManager.d.ts +79 -0
  62. package/dist/loop/LoopManager.js +476 -0
  63. package/dist/loop/LoopScheduler.d.ts +69 -0
  64. package/dist/loop/LoopScheduler.js +329 -0
  65. package/dist/loop/LoopStore.d.ts +57 -0
  66. package/dist/loop/LoopStore.js +406 -0
  67. package/dist/loop/LoopValidator.d.ts +55 -0
  68. package/dist/loop/LoopValidator.js +603 -0
  69. package/dist/loop/errors.d.ts +115 -0
  70. package/dist/loop/errors.js +312 -0
  71. package/dist/loop/index.d.ts +11 -0
  72. package/dist/loop/index.js +10 -0
  73. package/dist/loop/notifications/Notifier.d.ts +28 -0
  74. package/dist/loop/notifications/Notifier.js +78 -0
  75. package/dist/loop/notifications/SlackNotifier.d.ts +28 -0
  76. package/dist/loop/notifications/SlackNotifier.js +203 -0
  77. package/dist/loop/notifications/TerminalNotifier.d.ts +18 -0
  78. package/dist/loop/notifications/TerminalNotifier.js +72 -0
  79. package/dist/loop/notifications/WebhookNotifier.d.ts +24 -0
  80. package/dist/loop/notifications/WebhookNotifier.js +123 -0
  81. package/dist/loop/notifications/index.d.ts +24 -0
  82. package/dist/loop/notifications/index.js +109 -0
  83. package/dist/loop/types.d.ts +280 -0
  84. package/dist/loop/types.js +222 -0
  85. package/package.json +92 -0
@@ -0,0 +1,743 @@
1
+ import { ApiRequestFailedError, ApiAuthenticationFailedError, ApiRateLimitedError, ApiTimeoutError, GoalNotFoundError, TaskNotFoundError, MilestoneNotFoundError, ValidationError, } from './errors.js';
2
+ import { deserializeGoal, deserializeMilestone, deserializeTask, deserializeGoalMemory, deserializeMetaGoal, deserializeGoalActionLog, } from './types.js';
3
+ const DEFAULT_TIMEOUT_MS = 30000;
4
+ const DEFAULT_RETRIES = 2;
5
+ const RETRY_DELAY_MS = 1000;
6
+ const TRANSIENT_STATUS_CODES = [502, 503, 504];
7
+ const noopLogger = {
8
+ debug: () => { },
9
+ info: () => { },
10
+ warn: () => { },
11
+ error: () => { },
12
+ };
13
+ export class GoalApiClient {
14
+ baseUrl;
15
+ tokenProvider;
16
+ timeout;
17
+ retries;
18
+ logger;
19
+ constructor(options) {
20
+ this.baseUrl = options.baseUrl.replace(/\/$/, '');
21
+ this.tokenProvider = options.tokenProvider;
22
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
23
+ this.retries = options.retries ?? DEFAULT_RETRIES;
24
+ this.logger = options.logger ?? noopLogger;
25
+ }
26
+ async listGoals(options) {
27
+ const response = await this.request({
28
+ method: 'GET',
29
+ path: '/api/hub/goals',
30
+ query: {
31
+ status: options?.status,
32
+ page: options?.page,
33
+ per_page: options?.perPage,
34
+ },
35
+ });
36
+ if (!response.success || !response.data) {
37
+ throw new ApiRequestFailedError('GET', '/api/hub/goals', 0, response.message || 'Failed to list goals');
38
+ }
39
+ return response.data.map(deserializeGoal);
40
+ }
41
+ async getGoal(hash) {
42
+ const response = await this.request({
43
+ method: 'GET',
44
+ path: `/api/hub/goals/${hash}`,
45
+ });
46
+ if (!response.success) {
47
+ if (response.message?.includes('not found')) {
48
+ throw new GoalNotFoundError(hash);
49
+ }
50
+ throw new ApiRequestFailedError('GET', `/api/hub/goals/${hash}`, 0, response.message || 'Failed to get goal');
51
+ }
52
+ if (!response.data) {
53
+ throw new GoalNotFoundError(hash);
54
+ }
55
+ return deserializeGoal(response.data);
56
+ }
57
+ async createGoal(input) {
58
+ const response = await this.request({
59
+ method: 'POST',
60
+ path: '/api/hub/goals',
61
+ body: this.transformGoalInput(input),
62
+ });
63
+ if (!response.success) {
64
+ if (response.errors) {
65
+ throw new ValidationError('Goal validation failed', response.errors);
66
+ }
67
+ throw new ApiRequestFailedError('POST', '/api/hub/goals', 0, response.message || 'Failed to create goal');
68
+ }
69
+ if (!response.data) {
70
+ throw new ApiRequestFailedError('POST', '/api/hub/goals', 0, 'No data returned');
71
+ }
72
+ return deserializeGoal(response.data);
73
+ }
74
+ async updateGoal(hash, input) {
75
+ const response = await this.request({
76
+ method: 'PUT',
77
+ path: `/api/hub/goals/${hash}`,
78
+ body: this.transformGoalInput(input),
79
+ });
80
+ if (!response.success) {
81
+ if (response.message?.includes('not found')) {
82
+ throw new GoalNotFoundError(hash);
83
+ }
84
+ if (response.errors) {
85
+ throw new ValidationError('Goal validation failed', response.errors);
86
+ }
87
+ throw new ApiRequestFailedError('PUT', `/api/hub/goals/${hash}`, 0, response.message || 'Failed to update goal');
88
+ }
89
+ if (!response.data) {
90
+ throw new GoalNotFoundError(hash);
91
+ }
92
+ return deserializeGoal(response.data);
93
+ }
94
+ async deleteGoal(hash) {
95
+ const response = await this.request({
96
+ method: 'DELETE',
97
+ path: `/api/hub/goals/${hash}`,
98
+ });
99
+ if (!response.success) {
100
+ if (response.message?.includes('not found')) {
101
+ throw new GoalNotFoundError(hash);
102
+ }
103
+ throw new ApiRequestFailedError('DELETE', `/api/hub/goals/${hash}`, 0, response.message || 'Failed to delete goal');
104
+ }
105
+ }
106
+ async pauseGoal(hash) {
107
+ const response = await this.request({
108
+ method: 'POST',
109
+ path: `/api/hub/goals/${hash}/pause`,
110
+ });
111
+ if (!response.success || !response.data) {
112
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${hash}/pause`, 0, response.message || 'Failed to pause goal');
113
+ }
114
+ return deserializeGoal(response.data);
115
+ }
116
+ async resumeGoal(hash) {
117
+ const response = await this.request({
118
+ method: 'POST',
119
+ path: `/api/hub/goals/${hash}/resume`,
120
+ });
121
+ if (!response.success || !response.data) {
122
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${hash}/resume`, 0, response.message || 'Failed to resume goal');
123
+ }
124
+ return deserializeGoal(response.data);
125
+ }
126
+ async completeGoal(hash) {
127
+ const response = await this.request({
128
+ method: 'POST',
129
+ path: `/api/hub/goals/${hash}/complete`,
130
+ });
131
+ if (!response.success || !response.data) {
132
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${hash}/complete`, 0, response.message || 'Failed to complete goal');
133
+ }
134
+ return deserializeGoal(response.data);
135
+ }
136
+ async abandonGoal(hash) {
137
+ const response = await this.request({
138
+ method: 'POST',
139
+ path: `/api/hub/goals/${hash}/abandon`,
140
+ });
141
+ if (!response.success || !response.data) {
142
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${hash}/abandon`, 0, response.message || 'Failed to abandon goal');
143
+ }
144
+ return deserializeGoal(response.data);
145
+ }
146
+ async getGoalProgress(hash) {
147
+ const response = await this.request({
148
+ method: 'GET',
149
+ path: `/api/hub/goals/${hash}/progress`,
150
+ });
151
+ if (!response.success || !response.data) {
152
+ throw new ApiRequestFailedError('GET', `/api/hub/goals/${hash}/progress`, 0, response.message || 'Failed to get goal progress');
153
+ }
154
+ return response.data;
155
+ }
156
+ async listMilestones(goalHash) {
157
+ const response = await this.request({
158
+ method: 'GET',
159
+ path: `/api/hub/goals/${goalHash}/milestones`,
160
+ });
161
+ if (!response.success || !response.data) {
162
+ throw new ApiRequestFailedError('GET', `/api/hub/goals/${goalHash}/milestones`, 0, response.message || 'Failed to list milestones');
163
+ }
164
+ return response.data.map(deserializeMilestone);
165
+ }
166
+ async createMilestone(goalHash, input) {
167
+ const response = await this.request({
168
+ method: 'POST',
169
+ path: `/api/hub/goals/${goalHash}/milestones`,
170
+ body: {
171
+ title: input.title,
172
+ description: input.description,
173
+ target_date: input.targetDate,
174
+ is_manual: input.isManual ?? true,
175
+ },
176
+ });
177
+ if (!response.success) {
178
+ if (response.errors) {
179
+ throw new ValidationError('Milestone validation failed', response.errors);
180
+ }
181
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/milestones`, 0, response.message || 'Failed to create milestone');
182
+ }
183
+ if (!response.data) {
184
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/milestones`, 0, 'No data returned');
185
+ }
186
+ return deserializeMilestone(response.data);
187
+ }
188
+ async getMilestone(goalHash, milestoneId) {
189
+ const response = await this.request({
190
+ method: 'GET',
191
+ path: `/api/hub/goals/${goalHash}/milestones/${milestoneId}`,
192
+ });
193
+ if (!response.success) {
194
+ if (response.message?.includes('not found')) {
195
+ throw new MilestoneNotFoundError(goalHash, milestoneId);
196
+ }
197
+ throw new ApiRequestFailedError('GET', `/api/hub/goals/${goalHash}/milestones/${milestoneId}`, 0, response.message || 'Failed to get milestone');
198
+ }
199
+ if (!response.data) {
200
+ throw new MilestoneNotFoundError(goalHash, milestoneId);
201
+ }
202
+ return deserializeMilestone(response.data);
203
+ }
204
+ async updateMilestone(goalHash, milestoneId, input) {
205
+ const response = await this.request({
206
+ method: 'PUT',
207
+ path: `/api/hub/goals/${goalHash}/milestones/${milestoneId}`,
208
+ body: {
209
+ title: input.title,
210
+ description: input.description,
211
+ target_date: input.targetDate,
212
+ },
213
+ });
214
+ if (!response.success || !response.data) {
215
+ throw new ApiRequestFailedError('PUT', `/api/hub/goals/${goalHash}/milestones/${milestoneId}`, 0, response.message || 'Failed to update milestone');
216
+ }
217
+ return deserializeMilestone(response.data);
218
+ }
219
+ async deleteMilestone(goalHash, milestoneId) {
220
+ const response = await this.request({
221
+ method: 'DELETE',
222
+ path: `/api/hub/goals/${goalHash}/milestones/${milestoneId}`,
223
+ });
224
+ if (!response.success) {
225
+ throw new ApiRequestFailedError('DELETE', `/api/hub/goals/${goalHash}/milestones/${milestoneId}`, 0, response.message || 'Failed to delete milestone');
226
+ }
227
+ }
228
+ async completeMilestone(goalHash, milestoneId) {
229
+ const response = await this.request({
230
+ method: 'POST',
231
+ path: `/api/hub/goals/${goalHash}/milestones/${milestoneId}/complete`,
232
+ });
233
+ if (!response.success || !response.data) {
234
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/milestones/${milestoneId}/complete`, 0, response.message || 'Failed to complete milestone');
235
+ }
236
+ return deserializeMilestone(response.data);
237
+ }
238
+ async listTasks(goalHash, options) {
239
+ const response = await this.request({
240
+ method: 'GET',
241
+ path: `/api/hub/goals/${goalHash}/tasks`,
242
+ query: {
243
+ status: options?.status,
244
+ page: options?.page,
245
+ per_page: options?.perPage,
246
+ },
247
+ });
248
+ if (!response.success || !response.data) {
249
+ throw new ApiRequestFailedError('GET', `/api/hub/goals/${goalHash}/tasks`, 0, response.message || 'Failed to list tasks');
250
+ }
251
+ return response.data.map(deserializeTask);
252
+ }
253
+ async getPendingTasks() {
254
+ const response = await this.request({
255
+ method: 'GET',
256
+ path: '/api/hub/tasks/pending',
257
+ });
258
+ if (!response.success || !response.data) {
259
+ throw new ApiRequestFailedError('GET', '/api/hub/tasks/pending', 0, response.message || 'Failed to get pending tasks');
260
+ }
261
+ return response.data.map(deserializeTask);
262
+ }
263
+ async getTasksAwaitingApproval() {
264
+ const response = await this.request({
265
+ method: 'GET',
266
+ path: '/api/hub/tasks/awaiting-approval',
267
+ });
268
+ if (!response.success || !response.data) {
269
+ throw new ApiRequestFailedError('GET', '/api/hub/tasks/awaiting-approval', 0, response.message || 'Failed to get tasks awaiting approval');
270
+ }
271
+ return response.data.map(deserializeTask);
272
+ }
273
+ async createTask(goalHash, input) {
274
+ const response = await this.request({
275
+ method: 'POST',
276
+ path: `/api/hub/goals/${goalHash}/tasks`,
277
+ body: {
278
+ title: input.title,
279
+ description: input.description,
280
+ task_type: input.taskType ?? 'research',
281
+ permission_category: input.permissionCategory,
282
+ scheduled_for: input.scheduledFor,
283
+ milestone_id: input.milestoneId,
284
+ },
285
+ });
286
+ if (!response.success) {
287
+ if (response.errors) {
288
+ throw new ValidationError('Task validation failed', response.errors);
289
+ }
290
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/tasks`, 0, response.message || 'Failed to create task');
291
+ }
292
+ if (!response.data) {
293
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/tasks`, 0, 'No data returned');
294
+ }
295
+ return deserializeTask(response.data);
296
+ }
297
+ async getTask(goalHash, taskId) {
298
+ const response = await this.request({
299
+ method: 'GET',
300
+ path: `/api/hub/goals/${goalHash}/tasks/${taskId}`,
301
+ });
302
+ if (!response.success) {
303
+ if (response.message?.includes('not found')) {
304
+ throw new TaskNotFoundError(goalHash, taskId);
305
+ }
306
+ throw new ApiRequestFailedError('GET', `/api/hub/goals/${goalHash}/tasks/${taskId}`, 0, response.message || 'Failed to get task');
307
+ }
308
+ if (!response.data) {
309
+ throw new TaskNotFoundError(goalHash, taskId);
310
+ }
311
+ return deserializeTask(response.data);
312
+ }
313
+ async updateTask(goalHash, taskId, input) {
314
+ const response = await this.request({
315
+ method: 'PUT',
316
+ path: `/api/hub/goals/${goalHash}/tasks/${taskId}`,
317
+ body: {
318
+ title: input.title,
319
+ description: input.description,
320
+ task_type: input.taskType,
321
+ permission_category: input.permissionCategory,
322
+ scheduled_for: input.scheduledFor,
323
+ milestone_id: input.milestoneId,
324
+ },
325
+ });
326
+ if (!response.success || !response.data) {
327
+ throw new ApiRequestFailedError('PUT', `/api/hub/goals/${goalHash}/tasks/${taskId}`, 0, response.message || 'Failed to update task');
328
+ }
329
+ return deserializeTask(response.data);
330
+ }
331
+ async deleteTask(goalHash, taskId) {
332
+ const response = await this.request({
333
+ method: 'DELETE',
334
+ path: `/api/hub/goals/${goalHash}/tasks/${taskId}`,
335
+ });
336
+ if (!response.success) {
337
+ throw new ApiRequestFailedError('DELETE', `/api/hub/goals/${goalHash}/tasks/${taskId}`, 0, response.message || 'Failed to delete task');
338
+ }
339
+ }
340
+ async approveTask(goalHash, taskId) {
341
+ const response = await this.request({
342
+ method: 'POST',
343
+ path: `/api/hub/goals/${goalHash}/tasks/${taskId}/approve`,
344
+ });
345
+ if (!response.success || !response.data) {
346
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/tasks/${taskId}/approve`, 0, response.message || 'Failed to approve task');
347
+ }
348
+ return deserializeTask(response.data);
349
+ }
350
+ async denyTask(goalHash, taskId) {
351
+ const response = await this.request({
352
+ method: 'POST',
353
+ path: `/api/hub/goals/${goalHash}/tasks/${taskId}/deny`,
354
+ });
355
+ if (!response.success || !response.data) {
356
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/tasks/${taskId}/deny`, 0, response.message || 'Failed to deny task');
357
+ }
358
+ return deserializeTask(response.data);
359
+ }
360
+ async startTask(goalHash, taskId) {
361
+ const response = await this.request({
362
+ method: 'POST',
363
+ path: `/api/hub/goals/${goalHash}/tasks/${taskId}/start`,
364
+ });
365
+ if (!response.success || !response.data) {
366
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/tasks/${taskId}/start`, 0, response.message || 'Failed to start task');
367
+ }
368
+ return deserializeTask(response.data);
369
+ }
370
+ async completeTask(goalHash, taskId, result) {
371
+ const response = await this.request({
372
+ method: 'POST',
373
+ path: `/api/hub/goals/${goalHash}/tasks/${taskId}/complete`,
374
+ body: result ? { result } : undefined,
375
+ });
376
+ if (!response.success || !response.data) {
377
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/tasks/${taskId}/complete`, 0, response.message || 'Failed to complete task');
378
+ }
379
+ return deserializeTask(response.data);
380
+ }
381
+ async failTask(goalHash, taskId, input) {
382
+ const response = await this.request({
383
+ method: 'POST',
384
+ path: `/api/hub/goals/${goalHash}/tasks/${taskId}/fail`,
385
+ body: {
386
+ error: input.error,
387
+ result: input.result,
388
+ },
389
+ });
390
+ if (!response.success || !response.data) {
391
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/tasks/${taskId}/fail`, 0, response.message || 'Failed to fail task');
392
+ }
393
+ return deserializeTask(response.data);
394
+ }
395
+ async skipTask(goalHash, taskId) {
396
+ const response = await this.request({
397
+ method: 'POST',
398
+ path: `/api/hub/goals/${goalHash}/tasks/${taskId}/skip`,
399
+ });
400
+ if (!response.success || !response.data) {
401
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/tasks/${taskId}/skip`, 0, response.message || 'Failed to skip task');
402
+ }
403
+ return deserializeTask(response.data);
404
+ }
405
+ async addTaskFeedback(goalHash, taskId, input) {
406
+ const response = await this.request({
407
+ method: 'POST',
408
+ path: `/api/hub/goals/${goalHash}/tasks/${taskId}/feedback`,
409
+ body: {
410
+ quality: input.quality,
411
+ comment: input.comment,
412
+ },
413
+ });
414
+ if (!response.success || !response.data) {
415
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/tasks/${taskId}/feedback`, 0, response.message || 'Failed to add task feedback');
416
+ }
417
+ return deserializeTask(response.data);
418
+ }
419
+ async listMemories(goalHash) {
420
+ const response = await this.request({
421
+ method: 'GET',
422
+ path: `/api/hub/goals/${goalHash}/memories`,
423
+ });
424
+ if (!response.success || !response.data) {
425
+ throw new ApiRequestFailedError('GET', `/api/hub/goals/${goalHash}/memories`, 0, response.message || 'Failed to list memories');
426
+ }
427
+ return response.data.map(deserializeGoalMemory);
428
+ }
429
+ async getEnabledMemories(goalHash) {
430
+ const response = await this.request({
431
+ method: 'GET',
432
+ path: `/api/hub/goals/${goalHash}/memories/enabled`,
433
+ });
434
+ if (!response.success || !response.data) {
435
+ throw new ApiRequestFailedError('GET', `/api/hub/goals/${goalHash}/memories/enabled`, 0, response.message || 'Failed to get enabled memories');
436
+ }
437
+ return response.data.map(deserializeGoalMemory);
438
+ }
439
+ async createMemory(goalHash, input) {
440
+ const response = await this.request({
441
+ method: 'POST',
442
+ path: `/api/hub/goals/${goalHash}/memories`,
443
+ body: {
444
+ content: input.content,
445
+ memory_type: input.memoryType ?? 'learning',
446
+ source: input.source ?? 'auto',
447
+ },
448
+ });
449
+ if (!response.success) {
450
+ if (response.errors) {
451
+ throw new ValidationError('Memory validation failed', response.errors);
452
+ }
453
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/memories`, 0, response.message || 'Failed to create memory');
454
+ }
455
+ if (!response.data) {
456
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/memories`, 0, 'No data returned');
457
+ }
458
+ return deserializeGoalMemory(response.data);
459
+ }
460
+ async updateMemory(goalHash, memoryId, input) {
461
+ const response = await this.request({
462
+ method: 'PUT',
463
+ path: `/api/hub/goals/${goalHash}/memories/${memoryId}`,
464
+ body: {
465
+ content: input.content,
466
+ memory_type: input.memoryType,
467
+ is_enabled: input.isEnabled,
468
+ },
469
+ });
470
+ if (!response.success || !response.data) {
471
+ throw new ApiRequestFailedError('PUT', `/api/hub/goals/${goalHash}/memories/${memoryId}`, 0, response.message || 'Failed to update memory');
472
+ }
473
+ return deserializeGoalMemory(response.data);
474
+ }
475
+ async toggleMemory(goalHash, memoryId) {
476
+ const response = await this.request({
477
+ method: 'POST',
478
+ path: `/api/hub/goals/${goalHash}/memories/${memoryId}/toggle`,
479
+ });
480
+ if (!response.success || !response.data) {
481
+ throw new ApiRequestFailedError('POST', `/api/hub/goals/${goalHash}/memories/${memoryId}/toggle`, 0, response.message || 'Failed to toggle memory');
482
+ }
483
+ return deserializeGoalMemory(response.data);
484
+ }
485
+ async deleteMemory(goalHash, memoryId) {
486
+ const response = await this.request({
487
+ method: 'DELETE',
488
+ path: `/api/hub/goals/${goalHash}/memories/${memoryId}`,
489
+ });
490
+ if (!response.success) {
491
+ throw new ApiRequestFailedError('DELETE', `/api/hub/goals/${goalHash}/memories/${memoryId}`, 0, response.message || 'Failed to delete memory');
492
+ }
493
+ }
494
+ async listMetaGoals(active) {
495
+ const response = await this.request({
496
+ method: 'GET',
497
+ path: '/api/hub/meta-goals',
498
+ query: active !== undefined ? { active } : undefined,
499
+ });
500
+ if (!response.success || !response.data) {
501
+ throw new ApiRequestFailedError('GET', '/api/hub/meta-goals', 0, response.message || 'Failed to list meta-goals');
502
+ }
503
+ return response.data.map(deserializeMetaGoal);
504
+ }
505
+ async getMetaGoalsForGoal(goalHash) {
506
+ const response = await this.request({
507
+ method: 'GET',
508
+ path: `/api/hub/meta-goals/for-goal/${goalHash}`,
509
+ });
510
+ if (!response.success || !response.data) {
511
+ throw new ApiRequestFailedError('GET', `/api/hub/meta-goals/for-goal/${goalHash}`, 0, response.message || 'Failed to get meta-goals for goal');
512
+ }
513
+ return response.data.map(deserializeMetaGoal);
514
+ }
515
+ async createMetaGoal(input) {
516
+ const response = await this.request({
517
+ method: 'POST',
518
+ path: '/api/hub/meta-goals',
519
+ body: {
520
+ description: input.description,
521
+ priority: input.priority ?? 0,
522
+ scope: input.scope ?? 'all',
523
+ },
524
+ });
525
+ if (!response.success) {
526
+ if (response.errors) {
527
+ throw new ValidationError('Meta-goal validation failed', response.errors);
528
+ }
529
+ throw new ApiRequestFailedError('POST', '/api/hub/meta-goals', 0, response.message || 'Failed to create meta-goal');
530
+ }
531
+ if (!response.data) {
532
+ throw new ApiRequestFailedError('POST', '/api/hub/meta-goals', 0, 'No data returned');
533
+ }
534
+ return deserializeMetaGoal(response.data);
535
+ }
536
+ async getMetaGoal(id) {
537
+ const response = await this.request({
538
+ method: 'GET',
539
+ path: `/api/hub/meta-goals/${id}`,
540
+ });
541
+ if (!response.success || !response.data) {
542
+ throw new ApiRequestFailedError('GET', `/api/hub/meta-goals/${id}`, 0, response.message || 'Failed to get meta-goal');
543
+ }
544
+ return deserializeMetaGoal(response.data);
545
+ }
546
+ async updateMetaGoal(id, input) {
547
+ const response = await this.request({
548
+ method: 'PUT',
549
+ path: `/api/hub/meta-goals/${id}`,
550
+ body: {
551
+ description: input.description,
552
+ priority: input.priority,
553
+ scope: input.scope,
554
+ is_active: input.isActive,
555
+ },
556
+ });
557
+ if (!response.success || !response.data) {
558
+ throw new ApiRequestFailedError('PUT', `/api/hub/meta-goals/${id}`, 0, response.message || 'Failed to update meta-goal');
559
+ }
560
+ return deserializeMetaGoal(response.data);
561
+ }
562
+ async toggleMetaGoal(id) {
563
+ const response = await this.request({
564
+ method: 'POST',
565
+ path: `/api/hub/meta-goals/${id}/toggle`,
566
+ });
567
+ if (!response.success || !response.data) {
568
+ throw new ApiRequestFailedError('POST', `/api/hub/meta-goals/${id}/toggle`, 0, response.message || 'Failed to toggle meta-goal');
569
+ }
570
+ return deserializeMetaGoal(response.data);
571
+ }
572
+ async deleteMetaGoal(id) {
573
+ const response = await this.request({
574
+ method: 'DELETE',
575
+ path: `/api/hub/meta-goals/${id}`,
576
+ });
577
+ if (!response.success) {
578
+ throw new ApiRequestFailedError('DELETE', `/api/hub/meta-goals/${id}`, 0, response.message || 'Failed to delete meta-goal');
579
+ }
580
+ }
581
+ async getActionLogs(goalHash, limit) {
582
+ const response = await this.request({
583
+ method: 'GET',
584
+ path: `/api/hub/goals/${goalHash}/logs`,
585
+ query: limit ? { limit } : undefined,
586
+ });
587
+ if (!response.success || !response.data) {
588
+ throw new ApiRequestFailedError('GET', `/api/hub/goals/${goalHash}/logs`, 0, response.message || 'Failed to get action logs');
589
+ }
590
+ return response.data.map(deserializeGoalActionLog);
591
+ }
592
+ transformGoalInput(input) {
593
+ const result = {};
594
+ if ('name' in input && input.name !== undefined)
595
+ result.name = input.name;
596
+ if ('description' in input && input.description !== undefined)
597
+ result.description = input.description;
598
+ if ('priority' in input && input.priority !== undefined)
599
+ result.priority = input.priority;
600
+ if ('approvalMode' in input && input.approvalMode !== undefined)
601
+ result.approval_mode = input.approvalMode;
602
+ if ('deadline' in input && input.deadline !== undefined)
603
+ result.deadline = input.deadline;
604
+ if ('restrictions' in input && input.restrictions !== undefined)
605
+ result.restrictions = input.restrictions;
606
+ if ('schedule' in input && input.schedule !== undefined)
607
+ result.schedule = input.schedule;
608
+ if ('notificationPreferences' in input && input.notificationPreferences !== undefined) {
609
+ result.notification_preferences = input.notificationPreferences;
610
+ }
611
+ if ('reviewSchedule' in input && input.reviewSchedule !== undefined)
612
+ result.review_schedule = input.reviewSchedule;
613
+ if ('parentHash' in input && input.parentHash !== undefined)
614
+ result.parent_hash = input.parentHash;
615
+ if ('dependsOn' in input && input.dependsOn !== undefined)
616
+ result.dependsOn = input.dependsOn;
617
+ return result;
618
+ }
619
+ async request(options) {
620
+ const { method, path, body, query } = options;
621
+ let url = `${this.baseUrl}${path}`;
622
+ if (query) {
623
+ const params = new URLSearchParams();
624
+ for (const [key, value] of Object.entries(query)) {
625
+ if (value !== undefined) {
626
+ params.append(key, String(value));
627
+ }
628
+ }
629
+ const queryString = params.toString();
630
+ if (queryString) {
631
+ url += `?${queryString}`;
632
+ }
633
+ }
634
+ const token = await this.tokenProvider();
635
+ let lastError = null;
636
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
637
+ try {
638
+ const result = await this.executeRequest(method, url, token, body);
639
+ return result;
640
+ }
641
+ catch (error) {
642
+ lastError = error instanceof Error ? error : new Error(String(error));
643
+ if (error instanceof ApiAuthenticationFailedError ||
644
+ error instanceof ApiRateLimitedError ||
645
+ error instanceof ValidationError) {
646
+ throw error;
647
+ }
648
+ const statusCode = error instanceof ApiRequestFailedError
649
+ ? (typeof error.context?.statusCode === 'number' ? error.context.statusCode : 0)
650
+ : 0;
651
+ const isTransient = error instanceof ApiTimeoutError ||
652
+ (error instanceof ApiRequestFailedError && TRANSIENT_STATUS_CODES.includes(statusCode));
653
+ if (!isTransient || attempt >= this.retries) {
654
+ throw error;
655
+ }
656
+ this.logger.debug('GOAL_API', `Request failed, retrying (${attempt + 1}/${this.retries})`, {
657
+ method,
658
+ path,
659
+ error: lastError.message,
660
+ });
661
+ await this.delay(RETRY_DELAY_MS * (attempt + 1));
662
+ }
663
+ }
664
+ throw lastError ?? new ApiRequestFailedError(method, path, 0, 'Unknown error');
665
+ }
666
+ async executeRequest(method, url, token, body) {
667
+ const controller = new AbortController();
668
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
669
+ try {
670
+ const headers = {
671
+ 'Content-Type': 'application/json',
672
+ 'Accept': 'application/json',
673
+ };
674
+ if (token) {
675
+ headers['Authorization'] = `Bearer ${token}`;
676
+ }
677
+ const response = await fetch(url, {
678
+ method,
679
+ headers,
680
+ body: body ? JSON.stringify(body) : undefined,
681
+ signal: controller.signal,
682
+ });
683
+ if (!response.ok) {
684
+ await this.handleHttpError(response, method, url);
685
+ }
686
+ const contentType = response.headers.get('content-type');
687
+ if (contentType?.includes('application/json')) {
688
+ return (await response.json());
689
+ }
690
+ return { success: true };
691
+ }
692
+ catch (error) {
693
+ if (error instanceof ApiRequestFailedError ||
694
+ error instanceof ApiAuthenticationFailedError ||
695
+ error instanceof ApiRateLimitedError) {
696
+ throw error;
697
+ }
698
+ if (error instanceof Error) {
699
+ if (error.name === 'AbortError') {
700
+ throw new ApiTimeoutError(url, this.timeout);
701
+ }
702
+ throw new ApiRequestFailedError(method, url, 0, error.message);
703
+ }
704
+ throw new ApiRequestFailedError(method, url, 0, String(error));
705
+ }
706
+ finally {
707
+ clearTimeout(timeoutId);
708
+ }
709
+ }
710
+ async handleHttpError(response, method, url) {
711
+ const status = response.status;
712
+ let errorBody = null;
713
+ try {
714
+ const contentType = response.headers.get('content-type');
715
+ if (contentType?.includes('application/json')) {
716
+ errorBody = await response.json();
717
+ }
718
+ }
719
+ catch {
720
+ }
721
+ const message = errorBody?.message ||
722
+ errorBody?.error ||
723
+ response.statusText;
724
+ if (status === 401) {
725
+ throw new ApiAuthenticationFailedError(message);
726
+ }
727
+ if (status === 429) {
728
+ const retryAfterHeader = response.headers.get('retry-after');
729
+ const retryAfter = retryAfterHeader ? parseInt(retryAfterHeader, 10) : null;
730
+ throw new ApiRateLimitedError(Number.isNaN(retryAfter) ? null : retryAfter);
731
+ }
732
+ if (status === 422 && errorBody?.errors) {
733
+ throw new ValidationError('Validation failed', errorBody.errors);
734
+ }
735
+ throw new ApiRequestFailedError(method, url, status, message);
736
+ }
737
+ delay(ms) {
738
+ return new Promise((resolve) => setTimeout(resolve, ms));
739
+ }
740
+ }
741
+ export function createGoalApiClient(options) {
742
+ return new GoalApiClient(options);
743
+ }