@ziggs-ai/api-client 0.1.3 → 0.1.4

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 (49) hide show
  1. package/README.md +13 -7
  2. package/dist/ConnectionManager.d.ts +45 -0
  3. package/dist/ConnectionManager.js +118 -0
  4. package/dist/http/AgentSearchClient.d.ts +36 -0
  5. package/dist/http/AgentSearchClient.js +72 -0
  6. package/dist/http/AgreementClient.d.ts +153 -0
  7. package/dist/http/AgreementClient.js +457 -0
  8. package/dist/http/ArtifactsClient.d.ts +48 -0
  9. package/dist/http/ArtifactsClient.js +90 -0
  10. package/dist/http/ChatClient.d.ts +14 -0
  11. package/dist/http/ChatClient.js +69 -0
  12. package/dist/http/MarketplaceClient.d.ts +19 -0
  13. package/dist/http/MarketplaceClient.js +72 -0
  14. package/dist/http/MessagesClient.d.ts +22 -0
  15. package/dist/http/MessagesClient.js +41 -0
  16. package/dist/http/ScopeClient.d.ts +33 -0
  17. package/dist/http/ScopeClient.js +39 -0
  18. package/dist/http/TaskClient.d.ts +75 -0
  19. package/dist/http/TaskClient.js +343 -0
  20. package/dist/http/TelemetryClient.d.ts +11 -0
  21. package/dist/http/TelemetryClient.js +53 -0
  22. package/dist/http/index.d.ts +12 -0
  23. package/dist/http/index.js +9 -0
  24. package/dist/index.d.ts +9 -0
  25. package/{src → dist}/index.js +2 -12
  26. package/dist/shared/runtimeLog.d.ts +14 -0
  27. package/dist/shared/runtimeLog.js +64 -0
  28. package/dist/types.d.ts +120 -0
  29. package/dist/types.js +50 -0
  30. package/dist/utils/urlUtils.d.ts +2 -0
  31. package/dist/utils/urlUtils.js +8 -0
  32. package/dist/websocket/ControlSocket.d.ts +13 -0
  33. package/dist/websocket/ControlSocket.js +36 -0
  34. package/dist/websocket/WebSocketClient.d.ts +71 -0
  35. package/dist/websocket/WebSocketClient.js +217 -0
  36. package/dist/websocket/index.js +1 -0
  37. package/package.json +13 -6
  38. package/src/ConnectionManager.js +0 -179
  39. package/src/http/AgentSearchClient.js +0 -113
  40. package/src/http/ContextReader.js +0 -99
  41. package/src/http/ContextWriter.js +0 -98
  42. package/src/http/TaskClient.js +0 -612
  43. package/src/http/TelemetryClient.js +0 -43
  44. package/src/http/index.js +0 -6
  45. package/src/types.js +0 -28
  46. package/src/utils/urlUtils.js +0 -17
  47. package/src/websocket/ControlSocket.js +0 -55
  48. package/src/websocket/WebSocketClient.js +0 -318
  49. /package/{src/websocket/index.js → dist/websocket/index.d.ts} +0 -0
@@ -1,98 +0,0 @@
1
- import 'dotenv/config';
2
- import { EntryTypes, ContentTypes } from '../types.js';
3
- import { getBackendUrl } from '../utils/urlUtils.js';
4
-
5
- export class ContextWriter {
6
- constructor(operatorKey, agentId) {
7
- if (!operatorKey) {
8
- throw new Error('ContextWriter: operatorKey is required');
9
- }
10
- if (!agentId) {
11
- throw new Error('ContextWriter: agentId is required (operator-token impersonation)');
12
- }
13
- this.operatorKey = operatorKey;
14
- this.agentId = agentId;
15
- }
16
-
17
- _getBaseUrl() {
18
- const route = process.env.HISTORY_SERVICE_ROUTE || 'context';
19
- return `${getBackendUrl()}/${route}`;
20
- }
21
-
22
- async recordOperation(chatId, operation) {
23
- if (!chatId) return;
24
-
25
- const prefix = operation.state === 'completed'
26
- ? 'operation_completed:'
27
- : operation.state === 'failed'
28
- ? 'operation_error:'
29
- : 'operation_started:';
30
-
31
- const text = prefix + JSON.stringify(operation);
32
-
33
- try {
34
- await fetch(`${this._getBaseUrl()}/ingest`, {
35
- method: 'POST',
36
- headers: this._buildHeaders(),
37
- body: JSON.stringify({
38
- chatId,
39
- text,
40
- entryType: EntryTypes.ARTIFACT,
41
- content_type: ContentTypes.OPERATION
42
- })
43
- });
44
- } catch (error) {
45
- console.warn(`⚠️ Failed to record operation: ${error.message}`);
46
- }
47
- }
48
-
49
- async recordMessage(chatId, message) {
50
- if (!chatId) return;
51
-
52
- try {
53
- await fetch(`${this._getBaseUrl()}/ingest`, {
54
- method: 'POST',
55
- headers: this._buildHeaders(),
56
- body: JSON.stringify({
57
- chatId,
58
- text: message.text,
59
- entryType: EntryTypes.MESSAGE,
60
- content_type: ContentTypes.TEXT
61
- })
62
- });
63
- } catch (error) {
64
- console.warn(`⚠️ Failed to record message: ${error.message}`);
65
- }
66
- }
67
-
68
- /**
69
- * Send agent thought (reasoning) as artifact so it appears in timeline and frontend.
70
- * Backend stores with entryType=artifact, content_type=thought. Sender is taken from JWT.
71
- */
72
- async recordThought(chatId, thoughtText) {
73
- if (!chatId || thoughtText == null || String(thoughtText).trim() === '') return;
74
-
75
- try {
76
- await fetch(`${this._getBaseUrl()}/ingest`, {
77
- method: 'POST',
78
- headers: this._buildHeaders(),
79
- body: JSON.stringify({
80
- chatId,
81
- text: String(thoughtText).trim(),
82
- entryType: EntryTypes.ARTIFACT,
83
- content_type: ContentTypes.THOUGHT
84
- })
85
- });
86
- } catch (error) {
87
- console.warn(`⚠️ Failed to record thought artifact: ${error.message}`);
88
- }
89
- }
90
-
91
- _buildHeaders() {
92
- return {
93
- 'content-type': 'application/json',
94
- 'Authorization': `Bearer ${this.operatorKey}`,
95
- 'X-Agent-Id': this.agentId,
96
- };
97
- }
98
- }
@@ -1,612 +0,0 @@
1
- /**
2
- * HTTP client for the Tasks API.
3
- *
4
- * Every function returns a Task object matching the backend ITask shape:
5
- * { taskId, description, chatId, agentId, executorId, payerId, contract,
6
- * perspective, state, proposal, processing, deleted, result, errorMessage,
7
- * createdAt, updatedAt, parentTaskId, rootTaskId, plan, history }
8
- *
9
- * ⚠️ SYNC: Field names must match backend/src/tasks/task-core.ts ITask interface.
10
- */
11
-
12
- import 'dotenv/config';
13
- import { getBackendUrl } from '../utils/urlUtils.js';
14
-
15
- const baseUrl = getBackendUrl();
16
- const TASK_BASE_URL = `${baseUrl}/tasks`;
17
-
18
- /**
19
- * @typedef {{ operatorKey: string, agentId: string }} Creds
20
- */
21
-
22
- function buildHeaders(creds) {
23
- const { operatorKey, agentId } = creds || {};
24
- if (!operatorKey) {
25
- throw new Error('operatorKey is required for task service requests');
26
- }
27
- if (!agentId) {
28
- throw new Error('agentId is required for task service requests (operator-token impersonation)');
29
- }
30
- return {
31
- 'content-type': 'application/json',
32
- Authorization: `Bearer ${operatorKey}`,
33
- 'X-Agent-Id': agentId,
34
- };
35
- }
36
-
37
- function assertCreds(creds, op) {
38
- if (!creds?.operatorKey) throw new Error(`operatorKey is required for ${op}`);
39
- if (!creds?.agentId) throw new Error(`agentId is required for ${op}`);
40
- }
41
-
42
- /**
43
- * Parse error message from API response.
44
- * Checks for details, error, or message fields in JSON response.
45
- * Falls back to raw body or default message.
46
- */
47
- function parseErrorMessage(responseBody, defaultMessage) {
48
- if (!responseBody) return defaultMessage;
49
- try {
50
- const errorData = JSON.parse(responseBody);
51
- return errorData.details || errorData.error || errorData.message || defaultMessage;
52
- } catch {
53
- return responseBody || defaultMessage;
54
- }
55
- }
56
-
57
- function throwApiError(response, responseBody, defaultMessage) {
58
- const errorMessage = parseErrorMessage(responseBody, defaultMessage);
59
- const error = new Error(errorMessage);
60
- error.status = response.status;
61
- error.body = responseBody;
62
- throw error;
63
- }
64
-
65
- /**
66
- * Extract the Task object from an API response.
67
- * Handles both shapes: `{ task: {...} }` (POST /tasks, PATCH) and direct task (GET, proposal endpoints).
68
- * @returns {Object|null} The task object, or null if not found.
69
- */
70
- function extractTask(data) {
71
- if (!data) return null;
72
- // Wrapped: { status: '...', task: { taskId, ... } }
73
- if (data.task && data.task.taskId) return data.task;
74
- // Direct: { taskId, description, ... }
75
- if (data.taskId) return data;
76
- return null;
77
- }
78
-
79
- // ---------------------------------------------------------------------------
80
- // CRUD
81
- // ---------------------------------------------------------------------------
82
-
83
- /**
84
- * Create a standalone task (state='active').
85
- * POST /tasks
86
- *
87
- * @param {Object} taskData
88
- * @param {string} taskData.description
89
- * @param {string} [taskData.chatId]
90
- * @param {string} [taskData.executorId]
91
- * @param {string} [taskData.payerId]
92
- * @param {string} [taskData.parentTaskId]
93
- * @param {Object} [taskData.contract]
94
- * @param {Object} [taskData.perspective]
95
- * @param {Object} [taskData.plan]
96
- * @param {string} operatorKey
97
- * @returns {Promise<Object>} Task object
98
- */
99
- export async function createTask(taskData, creds) {
100
- if (!taskData) throw new Error('Task data is required for task creation');
101
- assertCreds(creds, 'task creation');
102
-
103
- const res = await fetch(`${TASK_BASE_URL}`, {
104
- method: 'POST',
105
- headers: buildHeaders(creds),
106
- body: JSON.stringify(taskData),
107
- });
108
-
109
- if (!res.ok) {
110
- const body = await res.text().catch(() => '');
111
- throwApiError(res, body, `Task creation failed: ${res.status} ${res.statusText}`);
112
- }
113
-
114
- const data = await res.json().catch(() => null);
115
- const task = extractTask(data);
116
- if (!task) throw new Error('Invalid response: task data not found');
117
- return task;
118
- }
119
-
120
- /**
121
- * Get a single task by ID.
122
- * GET /tasks/:taskId
123
- *
124
- * @param {string} taskId
125
- * @param {string} operatorKey
126
- * @returns {Promise<Object|null>} Task object or null
127
- */
128
- export async function getTask(taskId, creds) {
129
- if (!taskId) return null;
130
- assertCreds(creds, 'task retrieval');
131
-
132
- try {
133
- const res = await fetch(`${TASK_BASE_URL}/${taskId}`, {
134
- method: 'GET',
135
- headers: buildHeaders(creds),
136
- });
137
-
138
- if (!res.ok) {
139
- const body = await res.text().catch(() => '');
140
- console.warn(`⚠️ Task get failed: ${res.status} ${res.statusText} ${body}`);
141
- return null;
142
- }
143
-
144
- const data = await res.json().catch(() => null);
145
- return extractTask(data);
146
- } catch (e) {
147
- console.warn(`⚠️ Task get failed: ${e.message}`);
148
- return null;
149
- }
150
- }
151
-
152
- /**
153
- * Update a task's state.
154
- * PATCH /tasks/:taskId/state
155
- *
156
- * Valid transitions (enforced by backend):
157
- * proposal → active | cancelled
158
- * active → completed | failed | cancelled
159
- *
160
- * @param {string} taskId
161
- * @param {'proposal'|'active'|'completed'|'failed'|'cancelled'} state
162
- * @param {Object} [data]
163
- * @param {*} [data.result]
164
- * @param {string} [data.errorMessage]
165
- * @param {string} operatorKey
166
- * @returns {Promise<Object|null>} Updated task or null
167
- */
168
- export async function updateTaskState(taskId, state, data = {}, creds) {
169
- if (!taskId || !state) return null;
170
- assertCreds(creds, 'task state update');
171
-
172
- try {
173
- const res = await fetch(`${TASK_BASE_URL}/${taskId}/state`, {
174
- method: 'PATCH',
175
- headers: buildHeaders(creds),
176
- body: JSON.stringify({ state, ...data }),
177
- });
178
-
179
- if (!res.ok) {
180
- const body = await res.text().catch(() => '');
181
- console.warn(`⚠️ Task state update failed: ${res.status} ${res.statusText} ${body}`);
182
- return null;
183
- }
184
-
185
- const result = await res.json().catch(() => null);
186
- return extractTask(result);
187
- } catch (e) {
188
- console.warn(`⚠️ Task state update failed: ${e.message}`);
189
- return null;
190
- }
191
- }
192
-
193
- /**
194
- * Get active tasks, optionally filtered by chatId and/or agentId.
195
- * GET /tasks?state=active[&chatId=...][&agentId=...]
196
- *
197
- * @param {string|null} chatId
198
- * @param {string} operatorKey
199
- * @param {string|null} [agentId]
200
- * @returns {Promise<Object[]>} Array of Task objects
201
- */
202
- export async function getActiveTasks(chatId = null, creds, filterAgentId = null) {
203
- assertCreds(creds, 'getting active tasks');
204
-
205
- try {
206
- const url = new URL(`${TASK_BASE_URL}`);
207
- url.searchParams.set('state', 'active');
208
- if (chatId) url.searchParams.set('chatId', chatId);
209
- if (filterAgentId) url.searchParams.set('agentId', filterAgentId);
210
-
211
- const res = await fetch(url.toString(), {
212
- method: 'GET',
213
- headers: buildHeaders(creds),
214
- });
215
-
216
- if (!res.ok) {
217
- const body = await res.text().catch(() => '');
218
- console.warn(`⚠️ Get active tasks failed: ${res.status} ${res.statusText} ${body}`);
219
- return [];
220
- }
221
-
222
- const data = await res.json().catch(() => null);
223
- return Array.isArray(data?.tasks) ? data.tasks : [];
224
- } catch (e) {
225
- console.warn(`⚠️ Get active tasks failed: ${e.message}`);
226
- return [];
227
- }
228
- }
229
-
230
- /**
231
- * Acquire or release the atomic processing lock on a task.
232
- * PATCH /tasks/:taskId/processing
233
- *
234
- * @param {string} taskId
235
- * @param {boolean} processing - true to acquire, false to release
236
- * @param {string} operatorKey
237
- * @returns {Promise<{acquired: boolean, task: Object|null}>}
238
- */
239
- export async function markTaskProcessing(taskId, processing, creds) {
240
- if (!taskId) return { acquired: false, task: null };
241
- assertCreds(creds, 'task processing lock');
242
-
243
- try {
244
- const res = await fetch(`${TASK_BASE_URL}/${taskId}/processing`, {
245
- method: 'PATCH',
246
- headers: buildHeaders(creds),
247
- body: JSON.stringify({ processing }),
248
- });
249
-
250
- if (!res.ok) {
251
- const body = await res.text().catch(() => '');
252
- console.warn(`⚠️ Task processing lock failed: ${res.status} ${res.statusText} ${body}`);
253
- return { acquired: false, task: null };
254
- }
255
-
256
- const data = await res.json().catch(() => null);
257
- return {
258
- acquired: data?.acquired ?? false,
259
- task: data?.task || null,
260
- };
261
- } catch (e) {
262
- console.warn(`⚠️ Task processing lock failed: ${e.message}`);
263
- return { acquired: false, task: null };
264
- }
265
- }
266
-
267
- /**
268
- * Cancel a task (transitions to state='cancelled').
269
- * PATCH /tasks/:taskId/cancel
270
- *
271
- * @param {string} taskId
272
- * @param {string} operatorKey
273
- * @returns {Promise<Object|null>} Cancelled task or null
274
- */
275
- export async function cancelTask(taskId, creds) {
276
- if (!taskId) return null;
277
- assertCreds(creds, 'task cancellation');
278
-
279
- try {
280
- const res = await fetch(`${TASK_BASE_URL}/${taskId}/cancel`, {
281
- method: 'PATCH',
282
- headers: buildHeaders(creds),
283
- });
284
-
285
- if (!res.ok) {
286
- const body = await res.text().catch(() => '');
287
- console.warn(`⚠️ Task cancel failed: ${res.status} ${res.statusText} ${body}`);
288
- return null;
289
- }
290
-
291
- const result = await res.json().catch(() => null);
292
- return extractTask(result);
293
- } catch (e) {
294
- console.warn(`⚠️ Task cancel failed: ${e.message}`);
295
- return null;
296
- }
297
- }
298
-
299
- /**
300
- * Get subtasks for a parent task.
301
- * GET /tasks?parentTaskId=...
302
- *
303
- * @param {string} parentTaskId
304
- * @param {string} operatorKey
305
- * @returns {Promise<Object[]>} Array of Task objects
306
- */
307
- export async function getSubtasks(parentTaskId, creds) {
308
- if (!parentTaskId) return [];
309
- assertCreds(creds, 'getting subtasks');
310
-
311
- try {
312
- const url = new URL(`${TASK_BASE_URL}`);
313
- url.searchParams.set('parentTaskId', parentTaskId);
314
-
315
- const res = await fetch(url.toString(), {
316
- method: 'GET',
317
- headers: buildHeaders(creds),
318
- });
319
-
320
- if (!res.ok) {
321
- const body = await res.text().catch(() => '');
322
- console.warn(`⚠️ Get subtasks failed: ${res.status} ${res.statusText} ${body}`);
323
- return [];
324
- }
325
-
326
- const data = await res.json().catch(() => null);
327
- return Array.isArray(data?.tasks) ? data.tasks : [];
328
- } catch (e) {
329
- console.warn(`⚠️ Get subtasks failed: ${e.message}`);
330
- return [];
331
- }
332
- }
333
-
334
- // ---------------------------------------------------------------------------
335
- // Proposal workflow
336
- // ---------------------------------------------------------------------------
337
-
338
- /**
339
- * Propose work to a user or agent for approval.
340
- * POST /tasks/proposeToDoWork
341
- *
342
- * Creates a task with state='proposal', proposal.status='pending'.
343
- *
344
- * @param {Object} proposalData
345
- * @param {string} proposalData.description
346
- * @param {string} proposalData.proposedTo - userId or agentId to approve
347
- * @param {string} proposalData.chatId
348
- * @param {Object} [proposalData.contract]
349
- * @param {Object} [proposalData.perspective]
350
- * @param {string} [proposalData.parentTaskId]
351
- * @param {string} [proposalData.payerId]
352
- * @param {string} operatorKey
353
- * @returns {Promise<Object>} TaskWithFlags object
354
- */
355
- export async function proposeToDoWork(proposalData, creds) {
356
- if (!proposalData) throw new Error('Proposal data is required for proposal creation');
357
- assertCreds(creds, 'proposal creation');
358
-
359
- const { description, proposedTo, chatId, contract, perspective, parentTaskId, payerId } = proposalData;
360
- const res = await fetch(`${baseUrl}/tasks/proposeToDoWork`, {
361
- method: 'POST',
362
- headers: buildHeaders(creds),
363
- body: JSON.stringify({ description, proposedTo, chatId, contract, perspective, parentTaskId, payerId }),
364
- });
365
-
366
- if (!res.ok) {
367
- const body = await res.text().catch(() => '');
368
- throwApiError(res, body, `Proposal creation failed: ${res.status} ${res.statusText}`);
369
- }
370
-
371
- const data = await res.json().catch(() => null);
372
- const task = extractTask(data);
373
- if (!task) throw new Error('Invalid response: proposal data not found');
374
- return task;
375
- }
376
-
377
- /**
378
- * Assign task directly (no proposal step). Requires parentTaskId.
379
- * POST /tasks/assignTask
380
- *
381
- * Creates a task with state='active' immediately.
382
- *
383
- * @param {Object} assignData
384
- * @param {string} assignData.description
385
- * @param {string} assignData.executorId
386
- * @param {string} assignData.chatId
387
- * @param {string} assignData.parentTaskId - required (proves user approval)
388
- * @param {Object} [assignData.contract]
389
- * @param {Object} [assignData.perspective]
390
- * @param {string} [assignData.payerId]
391
- * @param {string} operatorKey
392
- * @returns {Promise<Object>} TaskWithFlags object
393
- */
394
- export async function assignTask(assignData, creds) {
395
- if (!assignData) throw new Error('Assign data is required');
396
- assertCreds(creds, 'task assignment');
397
-
398
- const { description, executorId, chatId, contract, perspective, parentTaskId, payerId } = assignData;
399
- const res = await fetch(`${baseUrl}/tasks/assignTask`, {
400
- method: 'POST',
401
- headers: buildHeaders(creds),
402
- body: JSON.stringify({ description, executorId, chatId, contract, perspective, parentTaskId, payerId }),
403
- });
404
-
405
- if (!res.ok) {
406
- const body = await res.text().catch(() => '');
407
- throwApiError(res, body, `Task assignment failed: ${res.status} ${res.statusText}`);
408
- }
409
-
410
- const data = await res.json().catch(() => null);
411
- const task = extractTask(data);
412
- if (!task) throw new Error('Invalid response: assignment data not found');
413
- return task;
414
- }
415
-
416
- /**
417
- * Delegate task to another agent (creates proposal to executor). Requires parentTaskId.
418
- * POST /tasks/delegateToAgent
419
- *
420
- * Creates a task with state='proposal', proposedTo=executorId.
421
- *
422
- * @param {Object} proposalData
423
- * @param {string} proposalData.description
424
- * @param {string} proposalData.executorId
425
- * @param {string} proposalData.chatId
426
- * @param {string} proposalData.parentTaskId - required (proves user approval)
427
- * @param {Object} [proposalData.contract]
428
- * @param {Object} [proposalData.perspective]
429
- * @param {string} [proposalData.payerId]
430
- * @param {string} operatorKey
431
- * @returns {Promise<Object>} TaskWithFlags object
432
- */
433
- export async function delegateToAgent(proposalData, creds) {
434
- if (!proposalData) throw new Error('Proposal data is required for delegation');
435
- assertCreds(creds, 'proposal creation');
436
-
437
- const { description, executorId, chatId, contract, perspective, parentTaskId, payerId } = proposalData;
438
- const res = await fetch(`${baseUrl}/tasks/delegateToAgent`, {
439
- method: 'POST',
440
- headers: buildHeaders(creds),
441
- body: JSON.stringify({ description, executorId, chatId, contract, perspective, parentTaskId, payerId }),
442
- });
443
-
444
- if (!res.ok) {
445
- const body = await res.text().catch(() => '');
446
- throwApiError(res, body, `Delegation failed: ${res.status} ${res.statusText}`);
447
- }
448
-
449
- const data = await res.json().catch(() => null);
450
- const task = extractTask(data);
451
- if (!task) throw new Error('Invalid response: delegation data not found');
452
- return task;
453
- }
454
-
455
- /**
456
- * Update a plan step within a task.
457
- * PATCH /tasks/:taskId/plan
458
- *
459
- * @param {string} taskId
460
- * @param {string} stepId
461
- * @param {'pending'|'in_progress'|'completed'|'skipped'} status
462
- * @param {*} [result]
463
- * @param {string} operatorKey
464
- * @returns {Promise<Object|null>} Updated task or null
465
- */
466
- export async function updatePlanStep(taskId, stepId, status, result, creds) {
467
- if (!taskId || !stepId || !status) return null;
468
- assertCreds(creds, 'plan step update');
469
-
470
- try {
471
- const body = { stepId, status };
472
- if (result !== undefined) body.result = result;
473
-
474
- const res = await fetch(`${TASK_BASE_URL}/${taskId}/plan`, {
475
- method: 'PATCH',
476
- headers: buildHeaders(creds),
477
- body: JSON.stringify(body),
478
- });
479
-
480
- if (!res.ok) {
481
- const responseBody = await res.text().catch(() => '');
482
- console.warn(`⚠️ Plan step update failed: ${res.status} ${res.statusText} ${responseBody}`);
483
- return null;
484
- }
485
-
486
- const data = await res.json().catch(() => null);
487
- return extractTask(data);
488
- } catch (e) {
489
- console.warn(`⚠️ Plan step update failed: ${e.message}`);
490
- return null;
491
- }
492
- }
493
-
494
- /**
495
- * Respond to a proposal — approve or reject.
496
- * POST /tasks/respondToProposal
497
- *
498
- * approve: state proposal→active, proposal.status='approved'
499
- * reject: state proposal→cancelled, proposal.status='rejected'
500
- *
501
- * @param {string} taskId
502
- * @param {'approve'|'reject'} action
503
- * @param {string} operatorKey
504
- * @returns {Promise<Object>} TaskWithFlags object
505
- */
506
- export async function respondToProposal(taskId, action, creds) {
507
- if (!taskId || !action) throw new Error('Task ID and action are required for proposal response');
508
- assertCreds(creds, 'proposal response');
509
-
510
- const res = await fetch(`${baseUrl}/tasks/respondToProposal`, {
511
- method: 'POST',
512
- headers: buildHeaders(creds),
513
- body: JSON.stringify({ taskId, action }),
514
- });
515
-
516
- if (!res.ok) {
517
- const body = await res.text().catch(() => '');
518
- throwApiError(res, body, `Proposal response failed: ${res.status} ${res.statusText}`);
519
- }
520
-
521
- const data = await res.json().catch(() => null);
522
- const task = extractTask(data);
523
- if (!task) throw new Error('Invalid response: proposal response data not found');
524
- return task;
525
- }
526
-
527
- // ---------------------------------------------------------------------------
528
- // Shared Task Ledger (Pub/Sub)
529
- // ---------------------------------------------------------------------------
530
-
531
- /**
532
- * Publish a task to the shared ledger (open for any agent to pull/claim).
533
- * POST /tasks/ledger/publish
534
- *
535
- * Creates a task with state='proposal', proposal.proposedTo='everyone'.
536
- *
537
- * @param {Object} payload
538
- * @param {string} payload.description
539
- * @param {string} [payload.chatId]
540
- * @param {string} [payload.payerId]
541
- * @param {string} [payload.parentTaskId]
542
- * @param {Object} [payload.contract]
543
- * @param {Object} [payload.perspective]
544
- * @param {string} operatorKey
545
- * @returns {Promise<Object>} Task object
546
- */
547
- export async function publishToLedger(payload, creds) {
548
- assertCreds(creds, 'ledger publish');
549
- const res = await fetch(`${TASK_BASE_URL}/ledger/publish`, {
550
- method: 'POST',
551
- headers: buildHeaders(creds),
552
- body: JSON.stringify(payload || {}),
553
- });
554
- if (!res.ok) {
555
- const body = await res.text().catch(() => '');
556
- throwApiError(res, body, `Ledger publish failed: ${res.status}`);
557
- }
558
- const data = await res.json().catch(() => null);
559
- return extractTask(data);
560
- }
561
-
562
- /**
563
- * Pull open tasks from the ledger (for agents to accept/ignore or claim).
564
- * POST /tasks/ledger/pull
565
- *
566
- * @param {Object} [options]
567
- * @param {number} [options.limit] - max tasks to return (default 50, max 100)
568
- * @param {string} [options.since] - ISO date string, only return tasks created after this
569
- * @param {string} operatorKey
570
- * @returns {Promise<Object[]>} Array of Task objects
571
- */
572
- export async function pullFromLedger(options, creds) {
573
- const headers = buildHeaders(creds);
574
- const res = await fetch(`${TASK_BASE_URL}/ledger/pull`, {
575
- method: 'POST',
576
- headers,
577
- body: JSON.stringify(options || {}),
578
- });
579
- if (!res.ok) {
580
- const body = await res.text().catch(() => '');
581
- throwApiError(res, body, `Ledger pull failed: ${res.status}`);
582
- }
583
- const data = await res.json().catch(() => null);
584
- return data?.tasks ?? [];
585
- }
586
-
587
- /**
588
- * Claim a ledger task so only this agent works on it.
589
- * POST /tasks/ledger/claim
590
- *
591
- * Atomically sets executorId and transitions state proposal→active.
592
- *
593
- * @param {string} taskId
594
- * @param {string} operatorKey
595
- * @returns {Promise<Object>} TaskWithFlags object
596
- */
597
- export async function claimLedgerTask(taskId, creds) {
598
- if (!taskId) throw new Error('taskId is required');
599
- assertCreds(creds, 'ledger claim');
600
- const res = await fetch(`${TASK_BASE_URL}/ledger/claim`, {
601
- method: 'POST',
602
- headers: buildHeaders(creds),
603
- body: JSON.stringify({ taskId }),
604
- });
605
- if (!res.ok) {
606
- const body = await res.text().catch(() => '');
607
- throwApiError(res, body, `Ledger claim failed: ${res.status}`);
608
- }
609
- const data = await res.json().catch(() => null);
610
- if (!data?.ok) throw new Error(data?.error || 'Claim failed');
611
- return data.task;
612
- }