dashclaw 2.0.3 → 2.1.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/README.md +56 -2120
- package/dashclaw.js +139 -2885
- package/index.cjs +48 -69
- package/legacy/dashclaw-v1.js +2887 -0
- package/legacy/index-v1.cjs +91 -0
- package/package.json +49 -46
package/dashclaw.js
CHANGED
|
@@ -1,2885 +1,139 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DashClaw SDK
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
path += (path.includes('?') ? '&' : '?') + qsStr;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const url = `${this.baseUrl}${path}`;
|
|
145
|
-
const headers = {
|
|
146
|
-
'Content-Type': 'application/json',
|
|
147
|
-
'x-api-key': this.apiKey
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const res = await fetch(url, {
|
|
151
|
-
method,
|
|
152
|
-
headers,
|
|
153
|
-
body: body ? JSON.stringify(body) : undefined
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const data = await res.json();
|
|
157
|
-
|
|
158
|
-
if (!res.ok) {
|
|
159
|
-
const err = new Error(data.error || `Request failed with status ${res.status}`);
|
|
160
|
-
err.status = res.status;
|
|
161
|
-
err.details = data.details;
|
|
162
|
-
throw err;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return data;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Create an agent pairing request (returns a link the user can click to approve).
|
|
170
|
-
*
|
|
171
|
-
* @param {Object} options
|
|
172
|
-
* @param {string} options.publicKeyPem - PEM public key (SPKI) to register for this agent.
|
|
173
|
-
* @param {string} [options.algorithm='RSASSA-PKCS1-v1_5']
|
|
174
|
-
* @param {string} [options.agentName]
|
|
175
|
-
* @returns {Promise<{pairing: Object, pairing_url: string}>}
|
|
176
|
-
*/
|
|
177
|
-
async createPairing({ publicKeyPem, algorithm = 'RSASSA-PKCS1-v1_5', agentName } = {}) {
|
|
178
|
-
if (!publicKeyPem) throw new Error('publicKeyPem is required');
|
|
179
|
-
return this._request('/api/pairings', 'POST', {
|
|
180
|
-
agent_id: this.agentId,
|
|
181
|
-
agent_name: agentName || this.agentName,
|
|
182
|
-
public_key: publicKeyPem,
|
|
183
|
-
algorithm,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async _derivePublicKeyPemFromPrivateJwk(privateJwk) {
|
|
188
|
-
// Node-only helper (works in the typical agent runtime).
|
|
189
|
-
const { createPrivateKey, createPublicKey } = await import('node:crypto');
|
|
190
|
-
const priv = createPrivateKey({ key: privateJwk, format: 'jwk' });
|
|
191
|
-
const pub = createPublicKey(priv);
|
|
192
|
-
return pub.export({ type: 'spki', format: 'pem' });
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Convenience: derive public PEM from a private JWK and create a pairing request.
|
|
197
|
-
* @param {Object} privateJwk
|
|
198
|
-
* @param {Object} [options]
|
|
199
|
-
* @param {string} [options.agentName]
|
|
200
|
-
*/
|
|
201
|
-
async createPairingFromPrivateJwk(privateJwk, { agentName } = {}) {
|
|
202
|
-
if (!privateJwk) throw new Error('privateJwk is required');
|
|
203
|
-
const publicKeyPem = await this._derivePublicKeyPemFromPrivateJwk(privateJwk);
|
|
204
|
-
return this.createPairing({ publicKeyPem, agentName });
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Poll a pairing until it is approved/expired.
|
|
209
|
-
* @param {string} pairingId
|
|
210
|
-
* @param {Object} [options]
|
|
211
|
-
* @param {number} [options.timeout=300000] - Max wait time (5 min)
|
|
212
|
-
* @param {number} [options.interval=2000] - Poll interval
|
|
213
|
-
* @returns {Promise<Object>} pairing object
|
|
214
|
-
*/
|
|
215
|
-
async waitForPairing(pairingId, { timeout = 300000, interval = 2000 } = {}) {
|
|
216
|
-
const start = Date.now();
|
|
217
|
-
while (Date.now() - start < timeout) {
|
|
218
|
-
const res = await this._request(`/api/pairings/${encodeURIComponent(pairingId)}`, 'GET');
|
|
219
|
-
const pairing = res.pairing;
|
|
220
|
-
if (!pairing) throw new Error('Pairing response missing pairing');
|
|
221
|
-
if (pairing.status === 'approved') return pairing;
|
|
222
|
-
if (pairing.status === 'expired') throw new Error('Pairing expired');
|
|
223
|
-
await new Promise((r) => setTimeout(r, interval));
|
|
224
|
-
}
|
|
225
|
-
throw new Error('Timed out waiting for pairing approval');
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Get a pairing request by ID.
|
|
230
|
-
* @param {string} pairingId
|
|
231
|
-
* @returns {Promise<{pairing: Object}>}
|
|
232
|
-
*/
|
|
233
|
-
async getPairing(pairingId) {
|
|
234
|
-
return this._request(`/api/pairings/${encodeURIComponent(pairingId)}`, 'GET');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Internal: check guard policies before action creation.
|
|
239
|
-
* Only active when guardMode is 'warn' or 'enforce'.
|
|
240
|
-
* @param {Object} actionDef - Action definition from createAction()
|
|
241
|
-
*/
|
|
242
|
-
async _guardCheck(actionDef) {
|
|
243
|
-
if (this.guardMode === 'off') return;
|
|
244
|
-
|
|
245
|
-
const context = {
|
|
246
|
-
action_type: actionDef.action_type,
|
|
247
|
-
risk_score: actionDef.risk_score,
|
|
248
|
-
systems_touched: actionDef.systems_touched,
|
|
249
|
-
reversible: actionDef.reversible,
|
|
250
|
-
declared_goal: actionDef.declared_goal,
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
let decision;
|
|
254
|
-
try {
|
|
255
|
-
decision = await this.guard(context);
|
|
256
|
-
} catch (err) {
|
|
257
|
-
// Guard API failure is fail-open: log and proceed
|
|
258
|
-
console.warn(`[DashClaw] Guard check failed (proceeding): ${err.message}`);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (this.guardCallback) {
|
|
263
|
-
try { this.guardCallback(decision); } catch { /* ignore callback errors */ }
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const isBlocked = decision.decision === 'block' || decision.decision === 'require_approval';
|
|
267
|
-
|
|
268
|
-
if (this.guardMode === 'warn' && isBlocked) {
|
|
269
|
-
console.warn(
|
|
270
|
-
`[DashClaw] Guard ${decision.decision}: ${decision.reasons.join('; ') || 'no reason'}. Proceeding in warn mode.`
|
|
271
|
-
);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (this.guardMode === 'enforce' && isBlocked) {
|
|
276
|
-
throw new GuardBlockedError(decision);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
_canonicalJsonStringify(value) {
|
|
281
|
-
const canonicalize = (v) => {
|
|
282
|
-
if (v === null) return 'null';
|
|
283
|
-
|
|
284
|
-
const t = typeof v;
|
|
285
|
-
if (t === 'string' || t === 'number' || t === 'boolean') return JSON.stringify(v);
|
|
286
|
-
|
|
287
|
-
if (t === 'undefined') return 'null';
|
|
288
|
-
|
|
289
|
-
if (Array.isArray(v)) {
|
|
290
|
-
return `[${v.map((x) => (typeof x === 'undefined' ? 'null' : canonicalize(x))).join(',')}]`;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (t === 'object') {
|
|
294
|
-
const keys = Object.keys(v)
|
|
295
|
-
.filter((k) => typeof v[k] !== 'undefined')
|
|
296
|
-
.sort();
|
|
297
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalize(v[k])}`).join(',')}}`;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return 'null';
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
return canonicalize(value);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
_toBase64(bytes) {
|
|
307
|
-
if (typeof btoa === 'function') {
|
|
308
|
-
return btoa(String.fromCharCode(...bytes));
|
|
309
|
-
}
|
|
310
|
-
return Buffer.from(bytes).toString('base64');
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
_isRestrictiveDecision(decision) {
|
|
314
|
-
return decision?.decision === 'block' || decision?.decision === 'require_approval';
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
_buildGuardContext(actionDef) {
|
|
318
|
-
return {
|
|
319
|
-
action_type: actionDef.action_type,
|
|
320
|
-
risk_score: actionDef.risk_score,
|
|
321
|
-
systems_touched: actionDef.systems_touched,
|
|
322
|
-
reversible: actionDef.reversible,
|
|
323
|
-
declared_goal: actionDef.declared_goal,
|
|
324
|
-
agent_id: this.agentId,
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
async _reportRecommendationEvent(event) {
|
|
329
|
-
try {
|
|
330
|
-
await this._request('/api/learning/recommendations/events', 'POST', {
|
|
331
|
-
...event,
|
|
332
|
-
agent_id: event.agent_id || this.agentId,
|
|
333
|
-
});
|
|
334
|
-
} catch {
|
|
335
|
-
// Telemetry should never break action execution
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
async _autoRecommend(actionDef) {
|
|
340
|
-
if (this.autoRecommend === 'off' || !actionDef?.action_type) {
|
|
341
|
-
return { action: actionDef, recommendation: null, adapted_fields: [] };
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
let result;
|
|
345
|
-
try {
|
|
346
|
-
result = await this.recommendAction(actionDef);
|
|
347
|
-
} catch (err) {
|
|
348
|
-
console.warn(`[DashClaw] Recommendation fetch failed (proceeding): ${err.message}`);
|
|
349
|
-
return { action: actionDef, recommendation: null, adapted_fields: [] };
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (this.recommendationCallback) {
|
|
353
|
-
try { this.recommendationCallback(result); } catch { /* ignore callback errors */ }
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const recommendation = result.recommendation || null;
|
|
357
|
-
if (!recommendation) return result;
|
|
358
|
-
|
|
359
|
-
const confidence = Number(recommendation.confidence || 0);
|
|
360
|
-
if (confidence < this.recommendationConfidenceMin) {
|
|
361
|
-
const override_reason = `confidence_below_threshold:${confidence}<${this.recommendationConfidenceMin}`;
|
|
362
|
-
await this._reportRecommendationEvent({
|
|
363
|
-
recommendation_id: recommendation.id,
|
|
364
|
-
event_type: 'overridden',
|
|
365
|
-
details: { action_type: actionDef.action_type, reason: override_reason },
|
|
366
|
-
});
|
|
367
|
-
return {
|
|
368
|
-
...result,
|
|
369
|
-
action: {
|
|
370
|
-
...actionDef,
|
|
371
|
-
recommendation_id: recommendation.id,
|
|
372
|
-
recommendation_applied: false,
|
|
373
|
-
recommendation_override_reason: override_reason,
|
|
374
|
-
},
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
let guardDecision = null;
|
|
379
|
-
try {
|
|
380
|
-
guardDecision = await this.guard(this._buildGuardContext(result.action || actionDef));
|
|
381
|
-
} catch (err) {
|
|
382
|
-
console.warn(`[DashClaw] Recommendation guard probe failed: ${err.message}`);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (this._isRestrictiveDecision(guardDecision)) {
|
|
386
|
-
const override_reason = `guard_restrictive:${guardDecision.decision}`;
|
|
387
|
-
await this._reportRecommendationEvent({
|
|
388
|
-
recommendation_id: recommendation.id,
|
|
389
|
-
event_type: 'overridden',
|
|
390
|
-
details: { action_type: actionDef.action_type, reason: override_reason },
|
|
391
|
-
});
|
|
392
|
-
return {
|
|
393
|
-
...result,
|
|
394
|
-
action: {
|
|
395
|
-
...actionDef,
|
|
396
|
-
recommendation_id: recommendation.id,
|
|
397
|
-
recommendation_applied: false,
|
|
398
|
-
recommendation_override_reason: override_reason,
|
|
399
|
-
},
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (this.autoRecommend === 'warn') {
|
|
404
|
-
const override_reason = 'warn_mode_no_autoadapt';
|
|
405
|
-
await this._reportRecommendationEvent({
|
|
406
|
-
recommendation_id: recommendation.id,
|
|
407
|
-
event_type: 'overridden',
|
|
408
|
-
details: { action_type: actionDef.action_type, reason: override_reason },
|
|
409
|
-
});
|
|
410
|
-
return {
|
|
411
|
-
...result,
|
|
412
|
-
action: {
|
|
413
|
-
...actionDef,
|
|
414
|
-
recommendation_id: recommendation.id,
|
|
415
|
-
recommendation_applied: false,
|
|
416
|
-
recommendation_override_reason: override_reason,
|
|
417
|
-
},
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
await this._reportRecommendationEvent({
|
|
422
|
-
recommendation_id: recommendation.id,
|
|
423
|
-
event_type: 'applied',
|
|
424
|
-
details: {
|
|
425
|
-
action_type: actionDef.action_type,
|
|
426
|
-
adapted_fields: result.adapted_fields || [],
|
|
427
|
-
confidence,
|
|
428
|
-
},
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
return {
|
|
432
|
-
...result,
|
|
433
|
-
action: {
|
|
434
|
-
...(result.action || actionDef),
|
|
435
|
-
recommendation_id: recommendation.id,
|
|
436
|
-
recommendation_applied: true,
|
|
437
|
-
recommendation_override_reason: null,
|
|
438
|
-
},
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// ══════════════════════════════════════════════
|
|
443
|
-
// Category 1: Decision Recording (6 methods)
|
|
444
|
-
// ══════════════════════════════════════════════
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* Record a governed decision. Every action is a decision with a full audit trail: goal, reasoning, assumptions, and policy compliance.
|
|
448
|
-
* @param {Object} action
|
|
449
|
-
* @param {string} action.action_type - One of: build, deploy, post, apply, security, message, api, calendar, research, review, fix, refactor, test, config, monitor, alert, cleanup, sync, migrate, other
|
|
450
|
-
* @param {string} action.declared_goal - What this action aims to accomplish
|
|
451
|
-
* @param {string} [action.action_id] - Custom action ID (auto-generated if omitted)
|
|
452
|
-
* @param {string} [action.reasoning] - Why the agent decided to take this action
|
|
453
|
-
* @param {string} [action.authorization_scope] - What permissions were granted
|
|
454
|
-
* @param {string} [action.trigger] - What triggered this action
|
|
455
|
-
* @param {string[]} [action.systems_touched] - Systems this action interacts with
|
|
456
|
-
* @param {string} [action.input_summary] - Summary of input data
|
|
457
|
-
* @param {string} [action.parent_action_id] - Parent action if this is a sub-action
|
|
458
|
-
* @param {boolean} [action.reversible=true] - Whether this action can be undone
|
|
459
|
-
* @param {number} [action.risk_score=0] - Risk score 0-100
|
|
460
|
-
* @param {number} [action.confidence=50] - Confidence level 0-100
|
|
461
|
-
* @returns {Promise<{action: Object, action_id: string}>}
|
|
462
|
-
*/
|
|
463
|
-
async createAction(action) {
|
|
464
|
-
const recommendationResult = await this._autoRecommend(action);
|
|
465
|
-
const finalAction = recommendationResult.action || action;
|
|
466
|
-
|
|
467
|
-
await this._guardCheck(finalAction);
|
|
468
|
-
if (this._pendingKeyImport) await this._pendingKeyImport;
|
|
469
|
-
|
|
470
|
-
const payload = {
|
|
471
|
-
agent_id: this.agentId,
|
|
472
|
-
agent_name: this.agentName,
|
|
473
|
-
swarm_id: this.swarmId,
|
|
474
|
-
...finalAction
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
let signature = null;
|
|
478
|
-
if (this.privateKey) {
|
|
479
|
-
try {
|
|
480
|
-
const encoder = new TextEncoder();
|
|
481
|
-
const data = encoder.encode(this._canonicalJsonStringify(payload));
|
|
482
|
-
// Use global crypto or fallback to node:crypto
|
|
483
|
-
const cryptoSubtle = globalThis.crypto?.subtle || (await import('node:crypto')).webcrypto.subtle;
|
|
484
|
-
|
|
485
|
-
const sigBuffer = await cryptoSubtle.sign(
|
|
486
|
-
{ name: "RSASSA-PKCS1-v1_5" },
|
|
487
|
-
this.privateKey,
|
|
488
|
-
data
|
|
489
|
-
);
|
|
490
|
-
// Base64 encode signature
|
|
491
|
-
signature = this._toBase64(new Uint8Array(sigBuffer));
|
|
492
|
-
} catch (err) {
|
|
493
|
-
throw new Error(`Failed to sign action: ${err.message}`);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const res = await this._request('/api/actions', 'POST', {
|
|
498
|
-
...payload,
|
|
499
|
-
_signature: signature
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
// Handle HITL Approval
|
|
503
|
-
if (res.action?.status === 'pending_approval' && this.hitlMode === 'wait') {
|
|
504
|
-
console.log(`[DashClaw] Action ${res.action_id} requires human approval. Waiting...`);
|
|
505
|
-
return this.waitForApproval(res.action_id);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
return res;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Poll for human approval of a pending action.
|
|
513
|
-
* @param {string} actionId
|
|
514
|
-
* @param {Object} [options]
|
|
515
|
-
* @param {number} [options.timeout=300000] - Max wait time (5 min)
|
|
516
|
-
* @param {number} [options.interval=5000] - Poll interval
|
|
517
|
-
* @param {boolean} [options.useEvents=false] - Use SSE stream instead of polling
|
|
518
|
-
*/
|
|
519
|
-
async waitForApproval(actionId, { timeout = 300000, interval = 5000, useEvents = false } = {}) {
|
|
520
|
-
if (!useEvents) {
|
|
521
|
-
return this._waitForApprovalPolling(actionId, timeout, interval);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
return new Promise((resolve, reject) => {
|
|
525
|
-
const stream = this.events();
|
|
526
|
-
const timeoutId = setTimeout(() => {
|
|
527
|
-
stream.close();
|
|
528
|
-
reject(new Error(`Timed out waiting for approval of action ${actionId}`));
|
|
529
|
-
}, timeout);
|
|
530
|
-
|
|
531
|
-
stream.on('action.updated', (data) => {
|
|
532
|
-
if (data.action_id !== actionId) return;
|
|
533
|
-
if (data.status === 'running') {
|
|
534
|
-
clearTimeout(timeoutId);
|
|
535
|
-
stream.close();
|
|
536
|
-
resolve({ action: data, action_id: actionId });
|
|
537
|
-
} else if (data.status === 'failed' || data.status === 'cancelled') {
|
|
538
|
-
clearTimeout(timeoutId);
|
|
539
|
-
stream.close();
|
|
540
|
-
reject(new ApprovalDeniedError(data.error_message || 'Operator denied the action.'));
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
stream.on('error', (err) => {
|
|
545
|
-
clearTimeout(timeoutId);
|
|
546
|
-
stream.close();
|
|
547
|
-
reject(err);
|
|
548
|
-
});
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/** @private Polling-based waitForApproval implementation. */
|
|
553
|
-
async _waitForApprovalPolling(actionId, timeout, interval) {
|
|
554
|
-
const startTime = Date.now();
|
|
555
|
-
|
|
556
|
-
while (Date.now() - startTime < timeout) {
|
|
557
|
-
const { action } = await this.getAction(actionId);
|
|
558
|
-
|
|
559
|
-
if (action.status === 'running') {
|
|
560
|
-
console.log(`[DashClaw] Action ${actionId} approved by operator.`);
|
|
561
|
-
return { action, action_id: actionId };
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
if (action.status === 'failed' || action.status === 'cancelled') {
|
|
565
|
-
throw new ApprovalDeniedError(action.error_message || 'Operator denied the action.');
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
await new Promise(r => setTimeout(r, interval));
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
throw new Error(`[DashClaw] Timed out waiting for approval of action ${actionId}`);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* Approve or deny a pending action as a human operator.
|
|
576
|
-
* @param {string} actionId - The action ID to approve or deny
|
|
577
|
-
* @param {'allow'|'deny'} decision - The approval decision
|
|
578
|
-
* @param {string} [reasoning] - Optional reasoning for the decision
|
|
579
|
-
* @returns {Promise<{action: Object}>}
|
|
580
|
-
*/
|
|
581
|
-
async approveAction(actionId, decision, reasoning) {
|
|
582
|
-
if (!['allow', 'deny'].includes(decision)) {
|
|
583
|
-
throw new Error("decision must be either 'allow' or 'deny'");
|
|
584
|
-
}
|
|
585
|
-
const payload = { decision };
|
|
586
|
-
if (reasoning !== undefined) payload.reasoning = reasoning;
|
|
587
|
-
return this._request(`/api/actions/${encodeURIComponent(actionId)}/approve`, 'POST', payload);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
* Get all actions currently pending human approval.
|
|
592
|
-
* @param {Object} [params]
|
|
593
|
-
* @param {number} [params.limit=20]
|
|
594
|
-
* @param {number} [params.offset=0]
|
|
595
|
-
* @returns {Promise<{actions: Object[], total: number}>}
|
|
596
|
-
*/
|
|
597
|
-
async getPendingApprovals({ limit = 20, offset = 0 } = {}) {
|
|
598
|
-
return this.getActions({ status: 'pending_approval', limit, offset });
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// ══════════════════════════════════════════════
|
|
602
|
-
// Real-Time Events (1 method)
|
|
603
|
-
// ══════════════════════════════════════════════
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* Subscribe to real-time SSE events from the DashClaw server.
|
|
607
|
-
* Uses fetch-based SSE parsing for Node 18+ compatibility (no native EventSource required).
|
|
608
|
-
*
|
|
609
|
-
* @param {Object} [options]
|
|
610
|
-
* @param {boolean} [options.reconnect=true] - Auto-reconnect on disconnect (resumes from last event ID)
|
|
611
|
-
* @param {number} [options.maxRetries=Infinity] - Max reconnection attempts before giving up
|
|
612
|
-
* @param {number} [options.retryInterval=3000] - Milliseconds between reconnection attempts
|
|
613
|
-
* @returns {{ on(eventType: string, callback: Function): this, close(): void, _promise: Promise<void> }}
|
|
614
|
-
*
|
|
615
|
-
* @example
|
|
616
|
-
* const stream = client.events();
|
|
617
|
-
* stream
|
|
618
|
-
* .on('action.created', (data) => console.log('New action:', data))
|
|
619
|
-
* .on('action.updated', (data) => console.log('Action updated:', data))
|
|
620
|
-
* .on('loop.created', (data) => console.log('New loop:', data))
|
|
621
|
-
* .on('loop.updated', (data) => console.log('Loop updated:', data))
|
|
622
|
-
* .on('goal.created', (data) => console.log('New goal:', data))
|
|
623
|
-
* .on('goal.updated', (data) => console.log('Goal updated:', data))
|
|
624
|
-
* .on('policy.updated', (data) => console.log('Policy changed:', data))
|
|
625
|
-
* .on('task.assigned', (data) => console.log('Task assigned:', data))
|
|
626
|
-
* .on('task.completed', (data) => console.log('Task done:', data))
|
|
627
|
-
* .on('reconnecting', ({ attempt }) => console.log(`Reconnecting #${attempt}...`))
|
|
628
|
-
* .on('error', (err) => console.error('Stream error:', err));
|
|
629
|
-
*
|
|
630
|
-
* // Later:
|
|
631
|
-
* stream.close();
|
|
632
|
-
*/
|
|
633
|
-
events({ reconnect = true, maxRetries = Infinity, retryInterval = 3000 } = {}) {
|
|
634
|
-
const url = `${this.baseUrl}/api/stream`;
|
|
635
|
-
const apiKey = this.apiKey;
|
|
636
|
-
|
|
637
|
-
const handlers = new Map();
|
|
638
|
-
let closed = false;
|
|
639
|
-
let controller = null;
|
|
640
|
-
let lastEventId = null;
|
|
641
|
-
let retryCount = 0;
|
|
642
|
-
|
|
643
|
-
const emit = (eventType, data) => {
|
|
644
|
-
const cbs = handlers.get(eventType) || [];
|
|
645
|
-
for (const cb of cbs) {
|
|
646
|
-
try { cb(data); } catch { /* ignore handler errors */ }
|
|
647
|
-
}
|
|
648
|
-
};
|
|
649
|
-
|
|
650
|
-
const connect = async () => {
|
|
651
|
-
controller = new AbortController();
|
|
652
|
-
const headers = { 'x-api-key': apiKey };
|
|
653
|
-
if (lastEventId) headers['last-event-id'] = lastEventId;
|
|
654
|
-
|
|
655
|
-
const res = await fetch(url, {
|
|
656
|
-
headers,
|
|
657
|
-
signal: controller.signal,
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
if (!res.ok) {
|
|
661
|
-
throw new Error(`SSE connection failed: ${res.status} ${res.statusText}`);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
retryCount = 0; // Reset on successful connection
|
|
665
|
-
|
|
666
|
-
const reader = res.body.getReader();
|
|
667
|
-
const decoder = new TextDecoder();
|
|
668
|
-
let buffer = '';
|
|
669
|
-
// Persist across reads so frames split across chunks are handled correctly
|
|
670
|
-
let currentEvent = null;
|
|
671
|
-
let currentData = '';
|
|
672
|
-
|
|
673
|
-
while (!closed) {
|
|
674
|
-
const { done, value } = await reader.read();
|
|
675
|
-
if (done) break;
|
|
676
|
-
buffer += decoder.decode(value, { stream: true });
|
|
677
|
-
|
|
678
|
-
// Parse SSE frames from buffer
|
|
679
|
-
const lines = buffer.split('\n');
|
|
680
|
-
buffer = lines.pop(); // Keep incomplete line in buffer
|
|
681
|
-
|
|
682
|
-
for (const line of lines) {
|
|
683
|
-
if (line.startsWith('id: ')) {
|
|
684
|
-
lastEventId = line.slice(4).trim();
|
|
685
|
-
} else if (line.startsWith('event: ')) {
|
|
686
|
-
currentEvent = line.slice(7).trim();
|
|
687
|
-
} else if (line.startsWith('data: ')) {
|
|
688
|
-
currentData += line.slice(6);
|
|
689
|
-
} else if (line.startsWith(':')) {
|
|
690
|
-
// SSE comment (keepalive heartbeat). Ignore.
|
|
691
|
-
} else if (line === '' && currentEvent) {
|
|
692
|
-
// End of SSE frame. Dispatch.
|
|
693
|
-
if (currentData) {
|
|
694
|
-
try {
|
|
695
|
-
const parsed = JSON.parse(currentData);
|
|
696
|
-
emit(currentEvent, parsed);
|
|
697
|
-
} catch { /* ignore parse errors */ }
|
|
698
|
-
}
|
|
699
|
-
currentEvent = null;
|
|
700
|
-
currentData = '';
|
|
701
|
-
} else if (line === '') {
|
|
702
|
-
// Blank line without a pending event. Reset partial state.
|
|
703
|
-
currentEvent = null;
|
|
704
|
-
currentData = '';
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
const connectLoop = async () => {
|
|
711
|
-
while (!closed) {
|
|
712
|
-
try {
|
|
713
|
-
await connect();
|
|
714
|
-
} catch (err) {
|
|
715
|
-
if (closed) return;
|
|
716
|
-
emit('error', err);
|
|
717
|
-
}
|
|
718
|
-
// Stream ended (server closed, network drop, etc.)
|
|
719
|
-
if (closed) return;
|
|
720
|
-
if (!reconnect || retryCount >= maxRetries) {
|
|
721
|
-
emit('error', new Error('SSE stream ended'));
|
|
722
|
-
return;
|
|
723
|
-
}
|
|
724
|
-
retryCount++;
|
|
725
|
-
emit('reconnecting', { attempt: retryCount, maxRetries });
|
|
726
|
-
await new Promise((r) => setTimeout(r, retryInterval));
|
|
727
|
-
}
|
|
728
|
-
};
|
|
729
|
-
|
|
730
|
-
const connectionPromise = connectLoop();
|
|
731
|
-
|
|
732
|
-
const handle = {
|
|
733
|
-
on(eventType, callback) {
|
|
734
|
-
if (!handlers.has(eventType)) handlers.set(eventType, []);
|
|
735
|
-
handlers.get(eventType).push(callback);
|
|
736
|
-
return handle;
|
|
737
|
-
},
|
|
738
|
-
close() {
|
|
739
|
-
closed = true;
|
|
740
|
-
if (controller) controller.abort();
|
|
741
|
-
},
|
|
742
|
-
_promise: connectionPromise,
|
|
743
|
-
};
|
|
744
|
-
|
|
745
|
-
return handle;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
/**
|
|
749
|
-
* Report agent presence and health.
|
|
750
|
-
* @param {Object} [options]
|
|
751
|
-
* @param {'online'|'busy'|'error'} [options.status='online']
|
|
752
|
-
* @param {string} [options.currentTaskId]
|
|
753
|
-
* @param {Object} [options.metadata]
|
|
754
|
-
* @returns {Promise<{status: string, timestamp: string}>}
|
|
755
|
-
*/
|
|
756
|
-
async heartbeat({ status = 'online', currentTaskId, metadata } = {}) {
|
|
757
|
-
return this._request('/api/agents/heartbeat', 'POST', {
|
|
758
|
-
agent_id: this.agentId,
|
|
759
|
-
agent_name: this.agentName,
|
|
760
|
-
status,
|
|
761
|
-
current_task_id: currentTaskId,
|
|
762
|
-
metadata,
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
/**
|
|
767
|
-
* Start an automatic heartbeat timer.
|
|
768
|
-
* @param {Object} [options]
|
|
769
|
-
* @param {number} [options.interval=60000] - Interval in ms
|
|
770
|
-
*/
|
|
771
|
-
startHeartbeat(options = {}) {
|
|
772
|
-
if (this._heartbeatTimer) return;
|
|
773
|
-
const interval = options.interval || 60000;
|
|
774
|
-
this.heartbeat(options).catch(() => {}); // Initial heartbeat
|
|
775
|
-
this._heartbeatTimer = setInterval(() => {
|
|
776
|
-
this.heartbeat(options).catch(() => {});
|
|
777
|
-
}, interval);
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
/**
|
|
781
|
-
* Stop the automatic heartbeat timer.
|
|
782
|
-
*/
|
|
783
|
-
stopHeartbeat() {
|
|
784
|
-
if (this._heartbeatTimer) {
|
|
785
|
-
clearInterval(this._heartbeatTimer);
|
|
786
|
-
this._heartbeatTimer = null;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
/**
|
|
791
|
-
* Update the outcome of an existing action.
|
|
792
|
-
* @param {string} actionId - The action_id to update
|
|
793
|
-
* @param {Object} outcome
|
|
794
|
-
* @param {string} [outcome.status] - New status: completed, failed, cancelled
|
|
795
|
-
* @param {string} [outcome.output_summary] - What happened
|
|
796
|
-
* @param {string[]} [outcome.side_effects] - Unintended consequences
|
|
797
|
-
* @param {string[]} [outcome.artifacts_created] - Files, records, etc. created
|
|
798
|
-
* @param {string} [outcome.error_message] - Error details if failed
|
|
799
|
-
* @param {number} [outcome.duration_ms] - How long it took
|
|
800
|
-
* @param {number} [outcome.cost_estimate] - Estimated cost in USD
|
|
801
|
-
* @returns {Promise<{action: Object}>}
|
|
802
|
-
*/
|
|
803
|
-
async updateOutcome(actionId, outcome) {
|
|
804
|
-
return this._request(`/api/actions/${actionId}`, 'PATCH', {
|
|
805
|
-
...outcome,
|
|
806
|
-
timestamp_end: outcome.timestamp_end || new Date().toISOString()
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
/**
|
|
811
|
-
* Get a list of actions with optional filters.
|
|
812
|
-
* @param {Object} [filters]
|
|
813
|
-
* @param {string} [filters.agent_id] - Filter by agent
|
|
814
|
-
* @param {string} [filters.swarm_id] - Filter by swarm
|
|
815
|
-
* @param {string} [filters.status] - Filter by status
|
|
816
|
-
* @param {string} [filters.action_type] - Filter by type
|
|
817
|
-
* @param {number} [filters.risk_min] - Minimum risk score
|
|
818
|
-
* @param {number} [filters.limit=50] - Max results
|
|
819
|
-
* @param {number} [filters.offset=0] - Pagination offset
|
|
820
|
-
* @returns {Promise<{actions: Object[], total: number, stats: Object}>}
|
|
821
|
-
*/
|
|
822
|
-
async getActions(filters = {}) {
|
|
823
|
-
const params = new URLSearchParams();
|
|
824
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
825
|
-
if (value !== undefined && value !== null && value !== '') {
|
|
826
|
-
params.set(key, String(value));
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
return this._request(`/api/actions?${params}`, 'GET');
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
/**
|
|
833
|
-
* Get a single action with its open loops and assumptions.
|
|
834
|
-
* @param {string} actionId
|
|
835
|
-
* @returns {Promise<{action: Object, open_loops: Object[], assumptions: Object[]}>}
|
|
836
|
-
*/
|
|
837
|
-
async getAction(actionId) {
|
|
838
|
-
return this._request(`/api/actions/${actionId}`, 'GET');
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
/**
|
|
842
|
-
* Get root-cause trace for an action.
|
|
843
|
-
* @param {string} actionId
|
|
844
|
-
* @returns {Promise<{action: Object, trace: Object}>}
|
|
845
|
-
*/
|
|
846
|
-
async getActionTrace(actionId) {
|
|
847
|
-
return this._request(`/api/actions/${actionId}/trace`, 'GET');
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
/**
|
|
851
|
-
* Helper: Create an action, run a function, and auto-update the outcome.
|
|
852
|
-
* @param {Object} actionDef - Action definition (same as createAction)
|
|
853
|
-
* @param {Function} fn - Async function to execute. Receives { action_id } as argument.
|
|
854
|
-
* @returns {Promise<*>} - The return value of fn
|
|
855
|
-
*/
|
|
856
|
-
async track(actionDef, fn) {
|
|
857
|
-
const startTime = Date.now();
|
|
858
|
-
const { action_id } = await this.createAction(actionDef);
|
|
859
|
-
|
|
860
|
-
try {
|
|
861
|
-
const result = await fn({ action_id });
|
|
862
|
-
await this.updateOutcome(action_id, {
|
|
863
|
-
status: 'completed',
|
|
864
|
-
duration_ms: Date.now() - startTime,
|
|
865
|
-
output_summary: typeof result === 'string' ? result : JSON.stringify(result)
|
|
866
|
-
});
|
|
867
|
-
return result;
|
|
868
|
-
} catch (error) {
|
|
869
|
-
await this.updateOutcome(action_id, {
|
|
870
|
-
status: 'failed',
|
|
871
|
-
duration_ms: Date.now() - startTime,
|
|
872
|
-
error_message: error.message || String(error)
|
|
873
|
-
}).catch(() => {}); // Don't throw if outcome update fails
|
|
874
|
-
throw error;
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
// ══════════════════════════════════════════════
|
|
879
|
-
// Category 2: Decision Integrity (Loops & Assumptions) (7 methods)
|
|
880
|
-
// ══════════════════════════════════════════════
|
|
881
|
-
|
|
882
|
-
/**
|
|
883
|
-
* Register an unresolved dependency for a decision. Open loops track work that must be completed before the decision can be considered fully resolved.
|
|
884
|
-
* @param {Object} loop
|
|
885
|
-
* @param {string} loop.action_id - Parent action ID
|
|
886
|
-
* @param {string} loop.loop_type - One of: followup, question, dependency, approval, review, handoff, other
|
|
887
|
-
* @param {string} loop.description - What needs to be resolved
|
|
888
|
-
* @param {string} [loop.priority='medium'] - One of: low, medium, high, critical
|
|
889
|
-
* @param {string} [loop.owner] - Who is responsible for resolving this
|
|
890
|
-
* @returns {Promise<{loop: Object, loop_id: string}>}
|
|
891
|
-
*/
|
|
892
|
-
async registerOpenLoop(loop) {
|
|
893
|
-
return this._request('/api/actions/loops', 'POST', loop);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
/**
|
|
897
|
-
* Resolve or cancel an open loop.
|
|
898
|
-
* @param {string} loopId - The loop_id to resolve
|
|
899
|
-
* @param {string} status - 'resolved' or 'cancelled'
|
|
900
|
-
* @param {string} [resolution] - Resolution description (required when resolving)
|
|
901
|
-
* @returns {Promise<{loop: Object}>}
|
|
902
|
-
*/
|
|
903
|
-
async resolveOpenLoop(loopId, status, resolution) {
|
|
904
|
-
return this._request(`/api/actions/loops/${loopId}`, 'PATCH', {
|
|
905
|
-
status,
|
|
906
|
-
resolution
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
/**
|
|
911
|
-
* Get open loops with optional filters.
|
|
912
|
-
* @param {Object} [filters]
|
|
913
|
-
* @param {string} [filters.status] - Filter by status (open, resolved, cancelled)
|
|
914
|
-
* @param {string} [filters.loop_type] - Filter by loop type
|
|
915
|
-
* @param {string} [filters.priority] - Filter by priority
|
|
916
|
-
* @param {number} [filters.limit=50] - Max results
|
|
917
|
-
* @returns {Promise<{loops: Object[], total: number, stats: Object}>}
|
|
918
|
-
*/
|
|
919
|
-
async getOpenLoops(filters = {}) {
|
|
920
|
-
const params = new URLSearchParams();
|
|
921
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
922
|
-
if (value !== undefined && value !== null && value !== '') {
|
|
923
|
-
params.set(key, String(value));
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
return this._request(`/api/actions/loops?${params}`, 'GET');
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
/**
|
|
930
|
-
* Register assumptions underlying a decision. Assumptions are the decision basis. They must be validated or invalidated to maintain decision integrity.
|
|
931
|
-
* @param {Object} assumption
|
|
932
|
-
* @param {string} assumption.action_id - Parent action ID
|
|
933
|
-
* @param {string} assumption.assumption - The assumption being made
|
|
934
|
-
* @param {string} [assumption.basis] - Evidence or reasoning for the assumption
|
|
935
|
-
* @param {boolean} [assumption.validated=false] - Whether this has been validated
|
|
936
|
-
* @returns {Promise<{assumption: Object, assumption_id: string}>}
|
|
937
|
-
*/
|
|
938
|
-
async registerAssumption(assumption) {
|
|
939
|
-
return this._request('/api/actions/assumptions', 'POST', assumption);
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
/**
|
|
943
|
-
* Get a single assumption by ID.
|
|
944
|
-
* @param {string} assumptionId
|
|
945
|
-
* @returns {Promise<{assumption: Object}>}
|
|
946
|
-
*/
|
|
947
|
-
async getAssumption(assumptionId) {
|
|
948
|
-
return this._request(`/api/actions/assumptions/${assumptionId}`, 'GET');
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
/**
|
|
952
|
-
* Validate or invalidate an assumption.
|
|
953
|
-
* @param {string} assumptionId - The assumption_id to update
|
|
954
|
-
* @param {boolean} validated - true to validate, false to invalidate
|
|
955
|
-
* @param {string} [invalidated_reason] - Required when invalidating
|
|
956
|
-
* @returns {Promise<{assumption: Object}>}
|
|
957
|
-
*/
|
|
958
|
-
async validateAssumption(assumptionId, validated, invalidated_reason) {
|
|
959
|
-
if (typeof validated !== 'boolean') throw new Error('validated must be a boolean');
|
|
960
|
-
if (validated === false && !invalidated_reason) {
|
|
961
|
-
throw new Error('invalidated_reason is required when invalidating an assumption');
|
|
962
|
-
}
|
|
963
|
-
const body = { validated };
|
|
964
|
-
if (invalidated_reason !== undefined) body.invalidated_reason = invalidated_reason;
|
|
965
|
-
return this._request(`/api/actions/assumptions/${assumptionId}`, 'PATCH', body);
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
/**
|
|
969
|
-
* Get drift report for assumptions with risk scoring.
|
|
970
|
-
* @param {Object} [filters]
|
|
971
|
-
* @param {string} [filters.action_id] - Filter by action
|
|
972
|
-
* @param {number} [filters.limit=50] - Max results
|
|
973
|
-
* @returns {Promise<{assumptions: Object[], drift_summary: Object}>}
|
|
974
|
-
*/
|
|
975
|
-
async getDriftReport(filters = {}) {
|
|
976
|
-
const params = new URLSearchParams({ drift: 'true' });
|
|
977
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
978
|
-
if (value !== undefined && value !== null && value !== '') {
|
|
979
|
-
params.set(key, String(value));
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
return this._request(`/api/actions/assumptions?${params}`, 'GET');
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// ══════════════════════════════════════════════
|
|
986
|
-
// Category 3: Decision Integrity Signals (1 method)
|
|
987
|
-
// ══════════════════════════════════════════════
|
|
988
|
-
|
|
989
|
-
/**
|
|
990
|
-
* Get current decision integrity signals. Returns autonomy breaches, logic drift, and governance violations.
|
|
991
|
-
* @returns {Promise<{signals: Object[], counts: {red: number, amber: number, total: number}}>}
|
|
992
|
-
*/
|
|
993
|
-
async getSignals() {
|
|
994
|
-
return this._request('/api/actions/signals', 'GET');
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// ══════════════════════════════════════════════
|
|
998
|
-
// Category 4: Dashboard Data (9 methods)
|
|
999
|
-
// ══════════════════════════════════════════════
|
|
1000
|
-
|
|
1001
|
-
/**
|
|
1002
|
-
* Report token usage snapshot (disabled in dashboard, API still functional).
|
|
1003
|
-
* @param {Object} usage
|
|
1004
|
-
* @param {number} usage.tokens_in - Input tokens consumed
|
|
1005
|
-
* @param {number} usage.tokens_out - Output tokens generated
|
|
1006
|
-
* @param {number} [usage.context_used] - Context window tokens used
|
|
1007
|
-
* @param {number} [usage.context_max] - Context window max capacity
|
|
1008
|
-
* @param {string} [usage.model] - Model name
|
|
1009
|
-
* @returns {Promise<{snapshot: Object}>}
|
|
1010
|
-
*/
|
|
1011
|
-
async reportTokenUsage(usage) {
|
|
1012
|
-
return this._request('/api/tokens', 'POST', {
|
|
1013
|
-
...usage,
|
|
1014
|
-
agent_id: this.agentId
|
|
1015
|
-
});
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
/**
|
|
1019
|
-
* Internal: fire-and-forget token report extracted from an LLM response.
|
|
1020
|
-
* @private
|
|
1021
|
-
*/
|
|
1022
|
-
async _reportTokenUsageFromLLM({ tokens_in, tokens_out, model }) {
|
|
1023
|
-
if (tokens_in == null && tokens_out == null) return;
|
|
1024
|
-
try {
|
|
1025
|
-
await this._request('/api/tokens', 'POST', {
|
|
1026
|
-
tokens_in: tokens_in || 0,
|
|
1027
|
-
tokens_out: tokens_out || 0,
|
|
1028
|
-
model: model || undefined,
|
|
1029
|
-
agent_id: this.agentId,
|
|
1030
|
-
});
|
|
1031
|
-
} catch (_) {
|
|
1032
|
-
// fire-and-forget: never let telemetry break the caller
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
/**
|
|
1037
|
-
* Wrap an Anthropic or OpenAI client to auto-report token usage after each call.
|
|
1038
|
-
* Returns the same client instance (mutated) for fluent usage.
|
|
1039
|
-
*
|
|
1040
|
-
* @param {Object} llmClient - An Anthropic or OpenAI SDK client instance
|
|
1041
|
-
* @param {Object} [options]
|
|
1042
|
-
* @param {'anthropic'|'openai'} [options.provider] - Force provider detection
|
|
1043
|
-
* @returns {Object} The wrapped client
|
|
1044
|
-
*
|
|
1045
|
-
* @example
|
|
1046
|
-
* const anthropic = claw.wrapClient(new Anthropic());
|
|
1047
|
-
* const msg = await anthropic.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 1024, messages: [...] });
|
|
1048
|
-
* // Token usage is auto-reported to DashClaw
|
|
1049
|
-
*/
|
|
1050
|
-
wrapClient(llmClient, { provider } = {}) {
|
|
1051
|
-
if (llmClient._dashclawWrapped) return llmClient;
|
|
1052
|
-
|
|
1053
|
-
const detected = provider
|
|
1054
|
-
|| (llmClient.messages?.create ? 'anthropic' : null)
|
|
1055
|
-
|| (llmClient.chat?.completions?.create ? 'openai' : null);
|
|
1056
|
-
|
|
1057
|
-
if (!detected) {
|
|
1058
|
-
throw new Error(
|
|
1059
|
-
'DashClaw.wrapClient: unable to detect provider. Pass { provider: "anthropic" } or { provider: "openai" }.'
|
|
1060
|
-
);
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
if (detected === 'anthropic') {
|
|
1064
|
-
const original = llmClient.messages.create.bind(llmClient.messages);
|
|
1065
|
-
llmClient.messages.create = async (...args) => {
|
|
1066
|
-
const response = await original(...args);
|
|
1067
|
-
this._reportTokenUsageFromLLM({
|
|
1068
|
-
tokens_in: response?.usage?.input_tokens ?? null,
|
|
1069
|
-
tokens_out: response?.usage?.output_tokens ?? null,
|
|
1070
|
-
model: response?.model ?? null,
|
|
1071
|
-
});
|
|
1072
|
-
return response;
|
|
1073
|
-
};
|
|
1074
|
-
} else if (detected === 'openai') {
|
|
1075
|
-
const original = llmClient.chat.completions.create.bind(llmClient.chat.completions);
|
|
1076
|
-
llmClient.chat.completions.create = async (...args) => {
|
|
1077
|
-
const response = await original(...args);
|
|
1078
|
-
this._reportTokenUsageFromLLM({
|
|
1079
|
-
tokens_in: response?.usage?.prompt_tokens ?? null,
|
|
1080
|
-
tokens_out: response?.usage?.completion_tokens ?? null,
|
|
1081
|
-
model: response?.model ?? null,
|
|
1082
|
-
});
|
|
1083
|
-
return response;
|
|
1084
|
-
};
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
llmClient._dashclawWrapped = true;
|
|
1088
|
-
return llmClient;
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
/**
|
|
1092
|
-
* Record a decision for the learning database.
|
|
1093
|
-
* @param {Object} entry
|
|
1094
|
-
* @param {string} entry.decision - What was decided
|
|
1095
|
-
* @param {string} [entry.context] - Context around the decision
|
|
1096
|
-
* @param {string} [entry.reasoning] - Why this decision was made
|
|
1097
|
-
* @param {string} [entry.outcome] - 'success', 'failure', or 'pending'
|
|
1098
|
-
* @param {number} [entry.confidence] - Confidence level 0-100
|
|
1099
|
-
* @returns {Promise<{decision: Object}>}
|
|
1100
|
-
*/
|
|
1101
|
-
async recordDecision(entry) {
|
|
1102
|
-
return this._request('/api/learning', 'POST', {
|
|
1103
|
-
...entry,
|
|
1104
|
-
agent_id: this.agentId
|
|
1105
|
-
});
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
/**
|
|
1109
|
-
* Get adaptive learning recommendations derived from prior episodes.
|
|
1110
|
-
* @param {Object} [filters]
|
|
1111
|
-
* @param {string} [filters.action_type] - Filter by action type
|
|
1112
|
-
* @param {string} [filters.agent_id] - Override agent_id (defaults to SDK agent)
|
|
1113
|
-
* @param {boolean} [filters.include_inactive] - Include disabled recommendations (admin/service only)
|
|
1114
|
-
* @param {boolean} [filters.track_events=true] - Record recommendation fetched telemetry
|
|
1115
|
-
* @param {boolean} [filters.include_metrics] - Include computed metrics in the response payload
|
|
1116
|
-
* @param {number} [filters.limit=50] - Max recommendations to return
|
|
1117
|
-
* @param {number} [filters.lookback_days=30] - Lookback days used when include_metrics=true
|
|
1118
|
-
* @returns {Promise<{recommendations: Object[], metrics?: Object, total: number, lastUpdated: string}>}
|
|
1119
|
-
*/
|
|
1120
|
-
async getRecommendations(filters = {}) {
|
|
1121
|
-
const params = new URLSearchParams({
|
|
1122
|
-
agent_id: filters.agent_id || this.agentId,
|
|
1123
|
-
});
|
|
1124
|
-
if (filters.action_type) params.set('action_type', filters.action_type);
|
|
1125
|
-
if (filters.limit) params.set('limit', String(filters.limit));
|
|
1126
|
-
if (filters.include_inactive) params.set('include_inactive', 'true');
|
|
1127
|
-
if (filters.track_events !== false) params.set('track_events', 'true');
|
|
1128
|
-
if (filters.include_metrics) params.set('include_metrics', 'true');
|
|
1129
|
-
if (filters.lookback_days) params.set('lookback_days', String(filters.lookback_days));
|
|
1130
|
-
return this._request(`/api/learning/recommendations?${params}`, 'GET');
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
/**
|
|
1134
|
-
* Get recommendation effectiveness metrics and telemetry aggregates.
|
|
1135
|
-
* @param {Object} [filters]
|
|
1136
|
-
* @param {string} [filters.action_type] - Filter by action type
|
|
1137
|
-
* @param {string} [filters.agent_id] - Override agent_id (defaults to SDK agent)
|
|
1138
|
-
* @param {number} [filters.lookback_days=30] - Lookback window for episodes/events
|
|
1139
|
-
* @param {number} [filters.limit=100] - Max recommendations considered
|
|
1140
|
-
* @param {boolean} [filters.include_inactive] - Include inactive recommendations (admin/service only)
|
|
1141
|
-
* @returns {Promise<{metrics: Object[], summary: Object, lookback_days: number, lastUpdated: string}>}
|
|
1142
|
-
*/
|
|
1143
|
-
async getRecommendationMetrics(filters = {}) {
|
|
1144
|
-
const params = new URLSearchParams({
|
|
1145
|
-
agent_id: filters.agent_id || this.agentId,
|
|
1146
|
-
});
|
|
1147
|
-
if (filters.action_type) params.set('action_type', filters.action_type);
|
|
1148
|
-
if (filters.lookback_days) params.set('lookback_days', String(filters.lookback_days));
|
|
1149
|
-
if (filters.limit) params.set('limit', String(filters.limit));
|
|
1150
|
-
if (filters.include_inactive) params.set('include_inactive', 'true');
|
|
1151
|
-
return this._request(`/api/learning/recommendations/metrics?${params}`, 'GET');
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
/**
|
|
1155
|
-
* Record recommendation telemetry events (single event or batch).
|
|
1156
|
-
* @param {Object|Object[]} events
|
|
1157
|
-
* @returns {Promise<{created: Object[], created_count: number}>}
|
|
1158
|
-
*/
|
|
1159
|
-
async recordRecommendationEvents(events) {
|
|
1160
|
-
if (Array.isArray(events)) {
|
|
1161
|
-
return this._request('/api/learning/recommendations/events', 'POST', { events });
|
|
1162
|
-
}
|
|
1163
|
-
return this._request('/api/learning/recommendations/events', 'POST', events || {});
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
/**
|
|
1167
|
-
* Enable or disable a recommendation.
|
|
1168
|
-
* @param {string} recommendationId - Recommendation ID
|
|
1169
|
-
* @param {boolean} active - Desired active state
|
|
1170
|
-
* @returns {Promise<{recommendation: Object}>}
|
|
1171
|
-
*/
|
|
1172
|
-
async setRecommendationActive(recommendationId, active) {
|
|
1173
|
-
return this._request(`/api/learning/recommendations/${recommendationId}`, 'PATCH', { active: !!active });
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
/**
|
|
1177
|
-
* Rebuild recommendations from scored learning episodes.
|
|
1178
|
-
* @param {Object} [options]
|
|
1179
|
-
* @param {string} [options.action_type] - Scope rebuild to one action type
|
|
1180
|
-
* @param {string} [options.agent_id] - Override agent_id (defaults to SDK agent)
|
|
1181
|
-
* @param {number} [options.lookback_days=30] - Days of episode history to analyze
|
|
1182
|
-
* @param {number} [options.min_samples=5] - Minimum episodes required per recommendation
|
|
1183
|
-
* @param {number} [options.episode_limit=5000] - Episode scan cap
|
|
1184
|
-
* @param {string} [options.action_id] - Optionally score this action before rebuild
|
|
1185
|
-
* @returns {Promise<{recommendations: Object[], total: number, episodes_scanned: number}>}
|
|
1186
|
-
*/
|
|
1187
|
-
async rebuildRecommendations(options = {}) {
|
|
1188
|
-
return this._request('/api/learning/recommendations', 'POST', {
|
|
1189
|
-
agent_id: options.agent_id || this.agentId,
|
|
1190
|
-
action_type: options.action_type,
|
|
1191
|
-
lookback_days: options.lookback_days,
|
|
1192
|
-
min_samples: options.min_samples,
|
|
1193
|
-
episode_limit: options.episode_limit,
|
|
1194
|
-
action_id: options.action_id,
|
|
1195
|
-
});
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
/**
|
|
1199
|
-
* Apply top recommendation hints to an action definition (non-destructive).
|
|
1200
|
-
* @param {Object} action - Action payload compatible with createAction()
|
|
1201
|
-
* @returns {Promise<{action: Object, recommendation: Object|null, adapted_fields: string[]}>}
|
|
1202
|
-
*/
|
|
1203
|
-
async recommendAction(action) {
|
|
1204
|
-
if (!action?.action_type) {
|
|
1205
|
-
return { action, recommendation: null, adapted_fields: [] };
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
const response = await this.getRecommendations({ action_type: action.action_type, limit: 1 });
|
|
1209
|
-
const recommendation = response.recommendations?.[0] || null;
|
|
1210
|
-
if (!recommendation) {
|
|
1211
|
-
return { action, recommendation: null, adapted_fields: [] };
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
const adapted = { ...action };
|
|
1215
|
-
const adaptedFields = [];
|
|
1216
|
-
const hints = recommendation.hints || {};
|
|
1217
|
-
|
|
1218
|
-
if (
|
|
1219
|
-
typeof hints.preferred_risk_cap === 'number' &&
|
|
1220
|
-
(adapted.risk_score === undefined || adapted.risk_score > hints.preferred_risk_cap)
|
|
1221
|
-
) {
|
|
1222
|
-
adapted.risk_score = hints.preferred_risk_cap;
|
|
1223
|
-
adaptedFields.push('risk_score');
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
if (hints.prefer_reversible === true && adapted.reversible === undefined) {
|
|
1227
|
-
adapted.reversible = true;
|
|
1228
|
-
adaptedFields.push('reversible');
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
if (
|
|
1232
|
-
typeof hints.confidence_floor === 'number' &&
|
|
1233
|
-
(adapted.confidence === undefined || adapted.confidence < hints.confidence_floor)
|
|
1234
|
-
) {
|
|
1235
|
-
adapted.confidence = hints.confidence_floor;
|
|
1236
|
-
adaptedFields.push('confidence');
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
return {
|
|
1240
|
-
action: adapted,
|
|
1241
|
-
recommendation,
|
|
1242
|
-
adapted_fields: adaptedFields,
|
|
1243
|
-
};
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
/**
|
|
1247
|
-
* Create a goal.
|
|
1248
|
-
* @param {Object} goal
|
|
1249
|
-
* @param {string} goal.title - Goal title
|
|
1250
|
-
* @param {string} [goal.category] - Goal category
|
|
1251
|
-
* @param {string} [goal.description] - Detailed description
|
|
1252
|
-
* @param {string} [goal.target_date] - Target completion date (ISO string)
|
|
1253
|
-
* @param {number} [goal.progress] - Progress 0-100
|
|
1254
|
-
* @param {string} [goal.status] - 'active', 'completed', 'paused'
|
|
1255
|
-
* @returns {Promise<{goal: Object}>}
|
|
1256
|
-
*/
|
|
1257
|
-
async createGoal(goal) {
|
|
1258
|
-
return this._request('/api/goals', 'POST', {
|
|
1259
|
-
...goal,
|
|
1260
|
-
agent_id: this.agentId
|
|
1261
|
-
});
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
/**
|
|
1265
|
-
* Record content creation.
|
|
1266
|
-
* @param {Object} content
|
|
1267
|
-
* @param {string} content.title - Content title
|
|
1268
|
-
* @param {string} [content.platform] - Platform (e.g., 'linkedin', 'twitter')
|
|
1269
|
-
* @param {string} [content.status] - 'draft' or 'published'
|
|
1270
|
-
* @param {string} [content.url] - Published URL
|
|
1271
|
-
* @returns {Promise<{content: Object}>}
|
|
1272
|
-
*/
|
|
1273
|
-
async recordContent(content) {
|
|
1274
|
-
return this._request('/api/content', 'POST', {
|
|
1275
|
-
...content,
|
|
1276
|
-
agent_id: this.agentId
|
|
1277
|
-
});
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
/**
|
|
1281
|
-
* Record a relationship interaction.
|
|
1282
|
-
* @param {Object} interaction
|
|
1283
|
-
* @param {string} interaction.summary - What happened
|
|
1284
|
-
* @param {string} [interaction.contact_name] - Contact name (auto-resolves to contact_id)
|
|
1285
|
-
* @param {string} [interaction.contact_id] - Direct contact ID
|
|
1286
|
-
* @param {string} [interaction.direction] - 'inbound' or 'outbound'
|
|
1287
|
-
* @param {string} [interaction.type] - Interaction type
|
|
1288
|
-
* @param {string} [interaction.platform] - Platform used
|
|
1289
|
-
* @returns {Promise<{interaction: Object}>}
|
|
1290
|
-
*/
|
|
1291
|
-
async recordInteraction(interaction) {
|
|
1292
|
-
return this._request('/api/relationships', 'POST', {
|
|
1293
|
-
...interaction,
|
|
1294
|
-
agent_id: this.agentId
|
|
1295
|
-
});
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
/**
|
|
1299
|
-
* Create a calendar event.
|
|
1300
|
-
* @param {Object} event
|
|
1301
|
-
* @param {string} event.summary - Event title/summary
|
|
1302
|
-
* @param {string} event.start_time - Start time (ISO string)
|
|
1303
|
-
* @param {string} [event.end_time] - End time (ISO string)
|
|
1304
|
-
* @param {string} [event.location] - Event location
|
|
1305
|
-
* @param {string} [event.description] - Event description
|
|
1306
|
-
* @returns {Promise<{event: Object}>}
|
|
1307
|
-
*/
|
|
1308
|
-
async createCalendarEvent(event) {
|
|
1309
|
-
return this._request('/api/calendar', 'POST', event);
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
/**
|
|
1313
|
-
* Record an idea/inspiration.
|
|
1314
|
-
* @param {Object} idea
|
|
1315
|
-
* @param {string} idea.title - Idea title
|
|
1316
|
-
* @param {string} [idea.description] - Detailed description
|
|
1317
|
-
* @param {string} [idea.category] - Category
|
|
1318
|
-
* @param {number} [idea.score] - Priority/quality score 0-100
|
|
1319
|
-
* @param {string} [idea.status] - 'pending', 'in_progress', 'shipped', 'rejected'
|
|
1320
|
-
* @param {string} [idea.source] - Where this idea came from
|
|
1321
|
-
* @returns {Promise<{idea: Object}>}
|
|
1322
|
-
*/
|
|
1323
|
-
async recordIdea(idea) {
|
|
1324
|
-
return this._request('/api/inspiration', 'POST', idea);
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
/**
|
|
1328
|
-
* Report memory health snapshot with entities and topics.
|
|
1329
|
-
* @param {Object} report
|
|
1330
|
-
* @param {Object} report.health - Health metrics
|
|
1331
|
-
* @param {number} report.health.score - Health score 0-100
|
|
1332
|
-
* @param {Object[]} [report.entities] - Key entities found in memory
|
|
1333
|
-
* @param {Object[]} [report.topics] - Topics/themes found in memory
|
|
1334
|
-
* @returns {Promise<{snapshot: Object, entities_count: number, topics_count: number}>}
|
|
1335
|
-
*/
|
|
1336
|
-
async reportMemoryHealth(report) {
|
|
1337
|
-
return this._request('/api/memory', 'POST', report);
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
/**
|
|
1341
|
-
* Report active connections/integrations for this agent.
|
|
1342
|
-
* @param {Object[]} connections - Array of connection objects
|
|
1343
|
-
* @param {string} connections[].provider - Service name (e.g., 'anthropic', 'github')
|
|
1344
|
-
* @param {string} [connections[].authType] - Auth method
|
|
1345
|
-
* @param {string} [connections[].planName] - Plan name
|
|
1346
|
-
* @param {string} [connections[].status] - Connection status: active, inactive, error
|
|
1347
|
-
* @param {Object|string} [connections[].metadata] - Optional metadata
|
|
1348
|
-
* @returns {Promise<{connections: Object[], created: number}>}
|
|
1349
|
-
*/
|
|
1350
|
-
async reportConnections(connections) {
|
|
1351
|
-
return this._request('/api/agents/connections', 'POST', {
|
|
1352
|
-
agent_id: this.agentId,
|
|
1353
|
-
connections: connections.map(c => ({
|
|
1354
|
-
provider: c.provider,
|
|
1355
|
-
auth_type: c.authType || c.auth_type || 'api_key',
|
|
1356
|
-
plan_name: c.planName || c.plan_name || null,
|
|
1357
|
-
status: c.status || 'active',
|
|
1358
|
-
metadata: c.metadata || null
|
|
1359
|
-
}))
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
// ══════════════════════════════════════════════
|
|
1364
|
-
// Category 5: Session Handoffs (3 methods)
|
|
1365
|
-
// ══════════════════════════════════════════════
|
|
1366
|
-
|
|
1367
|
-
/**
|
|
1368
|
-
* Create a session handoff document.
|
|
1369
|
-
* @param {Object} handoff
|
|
1370
|
-
* @param {string} handoff.summary - Session summary
|
|
1371
|
-
* @param {string} [handoff.session_date] - Date string (defaults to today)
|
|
1372
|
-
* @param {string[]} [handoff.key_decisions] - Key decisions made
|
|
1373
|
-
* @param {string[]} [handoff.open_tasks] - Tasks still open
|
|
1374
|
-
* @param {string} [handoff.mood_notes] - Mood/energy observations
|
|
1375
|
-
* @param {string[]} [handoff.next_priorities] - What to focus on next
|
|
1376
|
-
* @returns {Promise<{handoff: Object, handoff_id: string}>}
|
|
1377
|
-
*/
|
|
1378
|
-
async createHandoff(handoff) {
|
|
1379
|
-
return this._request('/api/handoffs', 'POST', {
|
|
1380
|
-
agent_id: this.agentId,
|
|
1381
|
-
...handoff
|
|
1382
|
-
});
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
/**
|
|
1386
|
-
* Get handoffs with optional filters.
|
|
1387
|
-
* @param {Object} [filters]
|
|
1388
|
-
* @param {string} [filters.date] - Filter by session_date
|
|
1389
|
-
* @param {number} [filters.limit] - Max results
|
|
1390
|
-
* @returns {Promise<{handoffs: Object[], total: number}>}
|
|
1391
|
-
*/
|
|
1392
|
-
async getHandoffs(filters = {}) {
|
|
1393
|
-
const params = new URLSearchParams({ agent_id: this.agentId });
|
|
1394
|
-
if (filters.date) params.set('date', filters.date);
|
|
1395
|
-
if (filters.limit) params.set('limit', String(filters.limit));
|
|
1396
|
-
return this._request(`/api/handoffs?${params}`, 'GET');
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
/**
|
|
1400
|
-
* Get the most recent handoff for this agent.
|
|
1401
|
-
* @returns {Promise<{handoff: Object|null}>}
|
|
1402
|
-
*/
|
|
1403
|
-
async getLatestHandoff() {
|
|
1404
|
-
return this._request(`/api/handoffs?agent_id=${this.agentId}&latest=true`, 'GET');
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
// ══════════════════════════════════════════════
|
|
1408
|
-
// Category 6: Context Manager (7 methods)
|
|
1409
|
-
// ══════════════════════════════════════════════
|
|
1410
|
-
|
|
1411
|
-
/**
|
|
1412
|
-
* Capture a key point from the current session.
|
|
1413
|
-
* @param {Object} point
|
|
1414
|
-
* @param {string} point.content - The key point content
|
|
1415
|
-
* @param {string} [point.category] - One of: decision, task, insight, question, general
|
|
1416
|
-
* @param {number} [point.importance] - Importance 1-10 (default 5)
|
|
1417
|
-
* @param {string} [point.session_date] - Date string (defaults to today)
|
|
1418
|
-
* @returns {Promise<{point: Object, point_id: string}>}
|
|
1419
|
-
*/
|
|
1420
|
-
async captureKeyPoint(point) {
|
|
1421
|
-
return this._request('/api/context/points', 'POST', {
|
|
1422
|
-
agent_id: this.agentId,
|
|
1423
|
-
...point
|
|
1424
|
-
});
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
/**
|
|
1428
|
-
* Get key points with optional filters.
|
|
1429
|
-
* @param {Object} [filters]
|
|
1430
|
-
* @param {string} [filters.category] - Filter by category
|
|
1431
|
-
* @param {string} [filters.session_date] - Filter by date
|
|
1432
|
-
* @param {number} [filters.limit] - Max results
|
|
1433
|
-
* @returns {Promise<{points: Object[], total: number}>}
|
|
1434
|
-
*/
|
|
1435
|
-
async getKeyPoints(filters = {}) {
|
|
1436
|
-
const params = new URLSearchParams({ agent_id: this.agentId });
|
|
1437
|
-
if (filters.category) params.set('category', filters.category);
|
|
1438
|
-
if (filters.session_date) params.set('session_date', filters.session_date);
|
|
1439
|
-
if (filters.limit) params.set('limit', String(filters.limit));
|
|
1440
|
-
return this._request(`/api/context/points?${params}`, 'GET');
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
/**
|
|
1444
|
-
* Create a context thread for tracking a topic across entries.
|
|
1445
|
-
* @param {Object} thread
|
|
1446
|
-
* @param {string} thread.name - Thread name (unique per agent per org)
|
|
1447
|
-
* @param {string} [thread.summary] - Initial summary
|
|
1448
|
-
* @returns {Promise<{thread: Object, thread_id: string}>}
|
|
1449
|
-
*/
|
|
1450
|
-
async createThread(thread) {
|
|
1451
|
-
return this._request('/api/context/threads', 'POST', {
|
|
1452
|
-
agent_id: this.agentId,
|
|
1453
|
-
...thread
|
|
1454
|
-
});
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
/**
|
|
1458
|
-
* Add an entry to an existing thread.
|
|
1459
|
-
* @param {string} threadId - The thread ID
|
|
1460
|
-
* @param {string} content - Entry content
|
|
1461
|
-
* @param {string} [entryType] - Entry type (default: 'note')
|
|
1462
|
-
* @returns {Promise<{entry: Object, entry_id: string}>}
|
|
1463
|
-
*/
|
|
1464
|
-
async addThreadEntry(threadId, content, entryType) {
|
|
1465
|
-
return this._request(`/api/context/threads/${threadId}/entries`, 'POST', {
|
|
1466
|
-
content,
|
|
1467
|
-
entry_type: entryType || 'note'
|
|
1468
|
-
});
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
/**
|
|
1472
|
-
* Close a thread with an optional summary.
|
|
1473
|
-
* @param {string} threadId - The thread ID
|
|
1474
|
-
* @param {string} [summary] - Final summary
|
|
1475
|
-
* @returns {Promise<{thread: Object}>}
|
|
1476
|
-
*/
|
|
1477
|
-
async closeThread(threadId, summary) {
|
|
1478
|
-
const body = { status: 'closed' };
|
|
1479
|
-
if (summary) body.summary = summary;
|
|
1480
|
-
return this._request(`/api/context/threads/${threadId}`, 'PATCH', body);
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
/**
|
|
1484
|
-
* Get threads with optional filters.
|
|
1485
|
-
* @param {Object} [filters]
|
|
1486
|
-
* @param {string} [filters.status] - Filter by status (active, closed)
|
|
1487
|
-
* @param {number} [filters.limit] - Max results
|
|
1488
|
-
* @returns {Promise<{threads: Object[], total: number}>}
|
|
1489
|
-
*/
|
|
1490
|
-
async getThreads(filters = {}) {
|
|
1491
|
-
const params = new URLSearchParams({ agent_id: this.agentId });
|
|
1492
|
-
if (filters.status) params.set('status', filters.status);
|
|
1493
|
-
if (filters.limit) params.set('limit', String(filters.limit));
|
|
1494
|
-
return this._request(`/api/context/threads?${params}`, 'GET');
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
/**
|
|
1498
|
-
* Get a combined context summary: today's key points + active threads.
|
|
1499
|
-
* @returns {Promise<{points: Object[], threads: Object[]}>}
|
|
1500
|
-
*/
|
|
1501
|
-
async getContextSummary() {
|
|
1502
|
-
const today = new Date().toISOString().split('T')[0];
|
|
1503
|
-
const [pointsResult, threadsResult] = await Promise.all([
|
|
1504
|
-
this.getKeyPoints({ session_date: today }),
|
|
1505
|
-
this.getThreads({ status: 'active' }),
|
|
1506
|
-
]);
|
|
1507
|
-
return {
|
|
1508
|
-
points: pointsResult.points,
|
|
1509
|
-
threads: threadsResult.threads,
|
|
1510
|
-
};
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
// ══════════════════════════════════════════════
|
|
1514
|
-
// Category 7: Automation Snippets (5 methods)
|
|
1515
|
-
// ══════════════════════════════════════════════
|
|
1516
|
-
|
|
1517
|
-
/**
|
|
1518
|
-
* Save or update a reusable code snippet.
|
|
1519
|
-
* @param {Object} snippet
|
|
1520
|
-
* @param {string} snippet.name - Snippet name (unique per org, upserts on conflict)
|
|
1521
|
-
* @param {string} snippet.code - The snippet code
|
|
1522
|
-
* @param {string} [snippet.description] - What this snippet does
|
|
1523
|
-
* @param {string} [snippet.language] - Programming language
|
|
1524
|
-
* @param {string[]} [snippet.tags] - Tags for categorization
|
|
1525
|
-
* @returns {Promise<{snippet: Object, snippet_id: string}>}
|
|
1526
|
-
*/
|
|
1527
|
-
async saveSnippet(snippet) {
|
|
1528
|
-
return this._request('/api/snippets', 'POST', {
|
|
1529
|
-
agent_id: this.agentId,
|
|
1530
|
-
...snippet
|
|
1531
|
-
});
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
/**
|
|
1535
|
-
* Search and list snippets.
|
|
1536
|
-
* @param {Object} [filters]
|
|
1537
|
-
* @param {string} [filters.search] - Search name/description
|
|
1538
|
-
* @param {string} [filters.tag] - Filter by tag
|
|
1539
|
-
* @param {string} [filters.language] - Filter by language
|
|
1540
|
-
* @param {number} [filters.limit] - Max results
|
|
1541
|
-
* @returns {Promise<{snippets: Object[], total: number}>}
|
|
1542
|
-
*/
|
|
1543
|
-
async getSnippets(filters = {}) {
|
|
1544
|
-
const params = new URLSearchParams();
|
|
1545
|
-
if (filters.search) params.set('search', filters.search);
|
|
1546
|
-
if (filters.tag) params.set('tag', filters.tag);
|
|
1547
|
-
if (filters.language) params.set('language', filters.language);
|
|
1548
|
-
if (filters.limit) params.set('limit', String(filters.limit));
|
|
1549
|
-
return this._request(`/api/snippets?${params}`, 'GET');
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
/**
|
|
1553
|
-
* Fetch a single snippet by ID.
|
|
1554
|
-
* @param {string} snippetId - The snippet ID
|
|
1555
|
-
* @returns {Promise<{snippet: Object}>}
|
|
1556
|
-
*/
|
|
1557
|
-
async getSnippet(snippetId) {
|
|
1558
|
-
return this._request(`/api/snippets/${snippetId}`, 'GET');
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
/**
|
|
1562
|
-
* Mark a snippet as used (increments use_count).
|
|
1563
|
-
* @param {string} snippetId - The snippet ID
|
|
1564
|
-
* @returns {Promise<{snippet: Object}>}
|
|
1565
|
-
*/
|
|
1566
|
-
async useSnippet(snippetId) {
|
|
1567
|
-
return this._request(`/api/snippets/${snippetId}/use`, 'POST');
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
/**
|
|
1571
|
-
* Delete a snippet.
|
|
1572
|
-
* @param {string} snippetId - The snippet ID
|
|
1573
|
-
* @returns {Promise<{deleted: boolean, id: string}>}
|
|
1574
|
-
*/
|
|
1575
|
-
async deleteSnippet(snippetId) {
|
|
1576
|
-
return this._request(`/api/snippets?id=${snippetId}`, 'DELETE');
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
// ══════════════════════════════════════════════
|
|
1580
|
-
// Category 8: User Preferences (6 methods)
|
|
1581
|
-
// ══════════════════════════════════════════════
|
|
1582
|
-
|
|
1583
|
-
/**
|
|
1584
|
-
* Log a user observation (what you noticed about the user).
|
|
1585
|
-
* @param {Object} obs
|
|
1586
|
-
* @param {string} obs.observation - The observation text
|
|
1587
|
-
* @param {string} [obs.category] - Category tag
|
|
1588
|
-
* @param {number} [obs.importance] - Importance 1-10
|
|
1589
|
-
* @returns {Promise<{observation: Object, observation_id: string}>}
|
|
1590
|
-
*/
|
|
1591
|
-
async logObservation(obs) {
|
|
1592
|
-
return this._request('/api/preferences', 'POST', {
|
|
1593
|
-
type: 'observation',
|
|
1594
|
-
agent_id: this.agentId,
|
|
1595
|
-
...obs
|
|
1596
|
-
});
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
/**
|
|
1600
|
-
* Set a learned user preference.
|
|
1601
|
-
* @param {Object} pref
|
|
1602
|
-
* @param {string} pref.preference - The preference description
|
|
1603
|
-
* @param {string} [pref.category] - Category tag
|
|
1604
|
-
* @param {number} [pref.confidence] - Confidence 0-100
|
|
1605
|
-
* @returns {Promise<{preference: Object, preference_id: string}>}
|
|
1606
|
-
*/
|
|
1607
|
-
async setPreference(pref) {
|
|
1608
|
-
return this._request('/api/preferences', 'POST', {
|
|
1609
|
-
type: 'preference',
|
|
1610
|
-
agent_id: this.agentId,
|
|
1611
|
-
...pref
|
|
1612
|
-
});
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
/**
|
|
1616
|
-
* Log user mood/energy for a session.
|
|
1617
|
-
* @param {Object} entry
|
|
1618
|
-
* @param {string} entry.mood - Mood description (e.g., 'focused', 'frustrated')
|
|
1619
|
-
* @param {string} [entry.energy] - Energy level (e.g., 'high', 'low')
|
|
1620
|
-
* @param {string} [entry.notes] - Additional notes
|
|
1621
|
-
* @returns {Promise<{mood: Object, mood_id: string}>}
|
|
1622
|
-
*/
|
|
1623
|
-
async logMood(entry) {
|
|
1624
|
-
return this._request('/api/preferences', 'POST', {
|
|
1625
|
-
type: 'mood',
|
|
1626
|
-
agent_id: this.agentId,
|
|
1627
|
-
...entry
|
|
1628
|
-
});
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
/**
|
|
1632
|
-
* Track an approach and whether it succeeded or failed.
|
|
1633
|
-
* @param {Object} entry
|
|
1634
|
-
* @param {string} entry.approach - The approach description
|
|
1635
|
-
* @param {string} [entry.context] - Context for when to use this approach
|
|
1636
|
-
* @param {boolean} [entry.success] - true = worked, false = failed, undefined = just recording
|
|
1637
|
-
* @returns {Promise<{approach: Object, approach_id: string}>}
|
|
1638
|
-
*/
|
|
1639
|
-
async trackApproach(entry) {
|
|
1640
|
-
return this._request('/api/preferences', 'POST', {
|
|
1641
|
-
type: 'approach',
|
|
1642
|
-
agent_id: this.agentId,
|
|
1643
|
-
...entry
|
|
1644
|
-
});
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
/**
|
|
1648
|
-
* Get a summary of all user preference data.
|
|
1649
|
-
* @returns {Promise<{summary: Object}>}
|
|
1650
|
-
*/
|
|
1651
|
-
async getPreferenceSummary() {
|
|
1652
|
-
return this._request(`/api/preferences?type=summary&agent_id=${this.agentId}`, 'GET');
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
/**
|
|
1656
|
-
* Get tracked approaches with success/fail counts.
|
|
1657
|
-
* @param {Object} [filters]
|
|
1658
|
-
* @param {number} [filters.limit] - Max results
|
|
1659
|
-
* @returns {Promise<{approaches: Object[], total: number}>}
|
|
1660
|
-
*/
|
|
1661
|
-
async getApproaches(filters = {}) {
|
|
1662
|
-
const params = new URLSearchParams({ type: 'approaches', agent_id: this.agentId });
|
|
1663
|
-
if (filters.limit) params.set('limit', String(filters.limit));
|
|
1664
|
-
return this._request(`/api/preferences?${params}`, 'GET');
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
// ══════════════════════════════════════════════
|
|
1668
|
-
// Category 9: Daily Digest (1 method)
|
|
1669
|
-
// ══════════════════════════════════════════════
|
|
1670
|
-
|
|
1671
|
-
/**
|
|
1672
|
-
* Get a daily activity digest aggregated from all data sources.
|
|
1673
|
-
* @param {string} [date] - Date string YYYY-MM-DD (defaults to today)
|
|
1674
|
-
* @returns {Promise<{date: string, digest: Object, summary: Object}>}
|
|
1675
|
-
*/
|
|
1676
|
-
async getDailyDigest(date) {
|
|
1677
|
-
const params = new URLSearchParams({ agent_id: this.agentId });
|
|
1678
|
-
if (date) params.set('date', date);
|
|
1679
|
-
return this._request(`/api/digest?${params}`, 'GET');
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
// ══════════════════════════════════════════════
|
|
1683
|
-
// Category 10: Security Scanning (3 methods)
|
|
1684
|
-
// ══════════════════════════════════════════════
|
|
1685
|
-
|
|
1686
|
-
/**
|
|
1687
|
-
* Scan text for sensitive data (API keys, tokens, PII, etc.).
|
|
1688
|
-
* Returns findings and redacted text. Does NOT store the original content.
|
|
1689
|
-
* @param {string} text - Text to scan
|
|
1690
|
-
* @param {string} [destination] - Where this text is headed (for context)
|
|
1691
|
-
* @returns {Promise<{clean: boolean, findings_count: number, findings: Object[], redacted_text: string}>}
|
|
1692
|
-
*/
|
|
1693
|
-
async scanContent(text, destination) {
|
|
1694
|
-
return this._request('/api/security/scan', 'POST', {
|
|
1695
|
-
text,
|
|
1696
|
-
destination,
|
|
1697
|
-
agent_id: this.agentId,
|
|
1698
|
-
store: false,
|
|
1699
|
-
});
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
/**
|
|
1703
|
-
* Scan text and store finding metadata (never the content itself).
|
|
1704
|
-
* Use this for audit trails of security scans.
|
|
1705
|
-
* @param {string} text - Text to scan
|
|
1706
|
-
* @param {string} [destination] - Where this text is headed
|
|
1707
|
-
* @returns {Promise<{clean: boolean, findings_count: number, findings: Object[], redacted_text: string}>}
|
|
1708
|
-
*/
|
|
1709
|
-
async reportSecurityFinding(text, destination) {
|
|
1710
|
-
return this._request('/api/security/scan', 'POST', {
|
|
1711
|
-
text,
|
|
1712
|
-
destination,
|
|
1713
|
-
agent_id: this.agentId,
|
|
1714
|
-
store: true,
|
|
1715
|
-
});
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
/**
|
|
1719
|
-
* Scan text for prompt injection attacks (role overrides, delimiter injection,
|
|
1720
|
-
* instruction smuggling, data exfiltration attempts, etc.).
|
|
1721
|
-
* @param {string} text - Text to scan
|
|
1722
|
-
* @param {Object} [options]
|
|
1723
|
-
* @param {string} [options.source] - Where this text came from (for context)
|
|
1724
|
-
* @returns {Promise<{clean: boolean, risk_level: string, recommendation: string, findings_count: number, critical_count: number, categories: string[], findings: Object[]}>}
|
|
1725
|
-
*/
|
|
1726
|
-
async scanPromptInjection(text, options = {}) {
|
|
1727
|
-
return this._request('/api/security/prompt-injection', 'POST', {
|
|
1728
|
-
text,
|
|
1729
|
-
source: options.source,
|
|
1730
|
-
agent_id: this.agentId,
|
|
1731
|
-
});
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
// ══════════════════════════════════════════════
|
|
1735
|
-
// Category 11: Agent Messaging (11 methods)
|
|
1736
|
-
// ══════════════════════════════════════════════
|
|
1737
|
-
|
|
1738
|
-
/**
|
|
1739
|
-
* Send a message to another agent or broadcast to all.
|
|
1740
|
-
* @param {Object} params
|
|
1741
|
-
* @param {string} [params.to] - Target agent ID (omit for broadcast)
|
|
1742
|
-
* @param {string} [params.type='info'] - Message type: action|info|lesson|question|status
|
|
1743
|
-
* @param {string} [params.subject] - Subject line (max 200 chars)
|
|
1744
|
-
* @param {string} params.body - Message body (max 2000 chars)
|
|
1745
|
-
* @param {string} [params.threadId] - Thread ID to attach message to
|
|
1746
|
-
* @param {boolean} [params.urgent=false] - Mark as urgent
|
|
1747
|
-
* @param {string} [params.docRef] - Reference to a shared doc ID
|
|
1748
|
-
* @param {Array<{filename: string, mime_type: string, data: string}>} [params.attachments] - File attachments (base64 data, max 3, max 5MB each)
|
|
1749
|
-
* @returns {Promise<{message: Object, message_id: string}>}
|
|
1750
|
-
*/
|
|
1751
|
-
async sendMessage({ to, type, subject, body, threadId, urgent, docRef, attachments }) {
|
|
1752
|
-
const payload = {
|
|
1753
|
-
from_agent_id: this.agentId,
|
|
1754
|
-
to_agent_id: to || null,
|
|
1755
|
-
message_type: type || 'info',
|
|
1756
|
-
subject,
|
|
1757
|
-
body,
|
|
1758
|
-
thread_id: threadId,
|
|
1759
|
-
urgent,
|
|
1760
|
-
doc_ref: docRef,
|
|
1761
|
-
};
|
|
1762
|
-
if (attachments?.length) payload.attachments = attachments;
|
|
1763
|
-
return this._request('/api/messages', 'POST', payload);
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
/**
|
|
1767
|
-
* Get inbox messages for this agent.
|
|
1768
|
-
* @param {Object} [params]
|
|
1769
|
-
* @param {string} [params.type] - Filter by message type
|
|
1770
|
-
* @param {boolean} [params.unread] - Only unread messages
|
|
1771
|
-
* @param {string} [params.threadId] - Filter by thread
|
|
1772
|
-
* @param {number} [params.limit=50] - Max messages to return
|
|
1773
|
-
* @returns {Promise<{messages: Object[], total: number, unread_count: number}>}
|
|
1774
|
-
*/
|
|
1775
|
-
async getInbox({ type, unread, threadId, limit } = {}) {
|
|
1776
|
-
const params = new URLSearchParams({
|
|
1777
|
-
agent_id: this.agentId,
|
|
1778
|
-
direction: 'inbox',
|
|
1779
|
-
});
|
|
1780
|
-
if (type) params.set('type', type);
|
|
1781
|
-
if (unread) params.set('unread', 'true');
|
|
1782
|
-
if (threadId) params.set('thread_id', threadId);
|
|
1783
|
-
if (limit) params.set('limit', String(limit));
|
|
1784
|
-
return this._request(`/api/messages?${params}`, 'GET');
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
/**
|
|
1788
|
-
* Mark messages as read.
|
|
1789
|
-
* @param {string[]} messageIds - Array of message IDs to mark read
|
|
1790
|
-
* @returns {Promise<{updated: number}>}
|
|
1791
|
-
*/
|
|
1792
|
-
/**
|
|
1793
|
-
* Get sent messages from this agent.
|
|
1794
|
-
* @param {Object} [params]
|
|
1795
|
-
* @param {string} [params.type] - Filter by message type
|
|
1796
|
-
* @param {string} [params.threadId] - Filter by thread
|
|
1797
|
-
* @param {number} [params.limit=50] - Max messages to return
|
|
1798
|
-
* @returns {Promise<{messages: Object[], total: number}>}
|
|
1799
|
-
*/
|
|
1800
|
-
async getSentMessages({ type, threadId, limit } = {}) {
|
|
1801
|
-
const params = new URLSearchParams({
|
|
1802
|
-
agent_id: this.agentId,
|
|
1803
|
-
direction: 'sent',
|
|
1804
|
-
});
|
|
1805
|
-
if (type) params.set('type', type);
|
|
1806
|
-
if (threadId) params.set('thread_id', threadId);
|
|
1807
|
-
if (limit) params.set('limit', String(limit));
|
|
1808
|
-
return this._request(`/api/messages?${params}`, 'GET');
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
|
-
/**
|
|
1812
|
-
* Get all messages (inbox + sent) with full filter control.
|
|
1813
|
-
* @param {Object} [params]
|
|
1814
|
-
* @param {string} [params.direction='all'] - 'inbox' | 'sent' | 'all'
|
|
1815
|
-
* @param {string} [params.type] - Filter by message type
|
|
1816
|
-
* @param {boolean} [params.unread] - Only unread messages
|
|
1817
|
-
* @param {string} [params.threadId] - Filter by thread
|
|
1818
|
-
* @param {number} [params.limit=50] - Max messages to return
|
|
1819
|
-
* @returns {Promise<{messages: Object[], total: number, unread_count: number}>}
|
|
1820
|
-
*/
|
|
1821
|
-
async getMessages({ direction, type, unread, threadId, limit } = {}) {
|
|
1822
|
-
const params = new URLSearchParams({ agent_id: this.agentId });
|
|
1823
|
-
if (direction) params.set('direction', direction);
|
|
1824
|
-
if (type) params.set('type', type);
|
|
1825
|
-
if (unread) params.set('unread', 'true');
|
|
1826
|
-
if (threadId) params.set('thread_id', threadId);
|
|
1827
|
-
if (limit) params.set('limit', String(limit));
|
|
1828
|
-
return this._request(`/api/messages?${params}`, 'GET');
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
/**
|
|
1832
|
-
* Get a single message by ID.
|
|
1833
|
-
* @param {string} messageId - The message ID (msg_*)
|
|
1834
|
-
* @returns {Promise<{message: Object}>}
|
|
1835
|
-
*/
|
|
1836
|
-
async getMessage(messageId) {
|
|
1837
|
-
return this._request(`/api/messages/${encodeURIComponent(messageId)}`, 'GET');
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
/**
|
|
1841
|
-
* Mark messages as read.
|
|
1842
|
-
* @param {string[]} messageIds - Array of message IDs to mark read
|
|
1843
|
-
* @returns {Promise<{updated: number}>}
|
|
1844
|
-
*/
|
|
1845
|
-
async markRead(messageIds) {
|
|
1846
|
-
return this._request('/api/messages', 'PATCH', {
|
|
1847
|
-
message_ids: messageIds,
|
|
1848
|
-
action: 'read',
|
|
1849
|
-
agent_id: this.agentId,
|
|
1850
|
-
});
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
/**
|
|
1854
|
-
* Archive messages.
|
|
1855
|
-
* @param {string[]} messageIds - Array of message IDs to archive
|
|
1856
|
-
* @returns {Promise<{updated: number}>}
|
|
1857
|
-
*/
|
|
1858
|
-
async archiveMessages(messageIds) {
|
|
1859
|
-
return this._request('/api/messages', 'PATCH', {
|
|
1860
|
-
message_ids: messageIds,
|
|
1861
|
-
action: 'archive',
|
|
1862
|
-
agent_id: this.agentId,
|
|
1863
|
-
});
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
/**
|
|
1867
|
-
* Broadcast a message to all agents in the organization.
|
|
1868
|
-
* @param {Object} params
|
|
1869
|
-
* @param {string} [params.type='info'] - Message type
|
|
1870
|
-
* @param {string} [params.subject] - Subject line
|
|
1871
|
-
* @param {string} params.body - Message body
|
|
1872
|
-
* @param {string} [params.threadId] - Thread ID
|
|
1873
|
-
* @returns {Promise<{message: Object, message_id: string}>}
|
|
1874
|
-
*/
|
|
1875
|
-
async broadcast({ type, subject, body, threadId }) {
|
|
1876
|
-
return this.sendMessage({ to: null, type, subject, body, threadId });
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
/**
|
|
1880
|
-
* Create a new message thread for multi-turn conversations.
|
|
1881
|
-
* @param {Object} params
|
|
1882
|
-
* @param {string} params.name - Thread name
|
|
1883
|
-
* @param {string[]} [params.participants] - Agent IDs (null = open to all)
|
|
1884
|
-
* @returns {Promise<{thread: Object, thread_id: string}>}
|
|
1885
|
-
*/
|
|
1886
|
-
async createMessageThread({ name, participants }) {
|
|
1887
|
-
return this._request('/api/messages/threads', 'POST', {
|
|
1888
|
-
name,
|
|
1889
|
-
participants,
|
|
1890
|
-
created_by: this.agentId,
|
|
1891
|
-
});
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
/**
|
|
1895
|
-
* List message threads.
|
|
1896
|
-
* @param {Object} [params]
|
|
1897
|
-
* @param {string} [params.status] - Filter by status: open|resolved|archived
|
|
1898
|
-
* @param {number} [params.limit=20] - Max threads to return
|
|
1899
|
-
* @returns {Promise<{threads: Object[], total: number}>}
|
|
1900
|
-
*/
|
|
1901
|
-
async getMessageThreads({ status, limit } = {}) {
|
|
1902
|
-
const params = new URLSearchParams({ agent_id: this.agentId });
|
|
1903
|
-
if (status) params.set('status', status);
|
|
1904
|
-
if (limit) params.set('limit', String(limit));
|
|
1905
|
-
return this._request(`/api/messages/threads?${params}`, 'GET');
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
/**
|
|
1909
|
-
* Resolve (close) a message thread.
|
|
1910
|
-
* @param {string} threadId - Thread ID to resolve
|
|
1911
|
-
* @param {string} [summary] - Resolution summary
|
|
1912
|
-
* @returns {Promise<{thread: Object}>}
|
|
1913
|
-
*/
|
|
1914
|
-
async resolveMessageThread(threadId, summary) {
|
|
1915
|
-
return this._request('/api/messages/threads', 'PATCH', {
|
|
1916
|
-
thread_id: threadId,
|
|
1917
|
-
status: 'resolved',
|
|
1918
|
-
summary,
|
|
1919
|
-
});
|
|
1920
|
-
}
|
|
1921
|
-
|
|
1922
|
-
/**
|
|
1923
|
-
* Create or update a shared workspace document.
|
|
1924
|
-
* Upserts by (org_id, name). Updates increment the version.
|
|
1925
|
-
* @param {Object} params
|
|
1926
|
-
* @param {string} params.name - Document name (unique per org)
|
|
1927
|
-
* @param {string} params.content - Document content
|
|
1928
|
-
* @returns {Promise<{doc: Object, doc_id: string}>}
|
|
1929
|
-
*/
|
|
1930
|
-
async saveSharedDoc({ name, content }) {
|
|
1931
|
-
return this._request('/api/messages/docs', 'POST', {
|
|
1932
|
-
name,
|
|
1933
|
-
content,
|
|
1934
|
-
agent_id: this.agentId,
|
|
1935
|
-
});
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
/**
|
|
1939
|
-
* Get an attachment's download URL or fetch its binary data.
|
|
1940
|
-
* @param {string} attachmentId - Attachment ID (att_*)
|
|
1941
|
-
* @returns {string} URL to fetch the attachment
|
|
1942
|
-
*/
|
|
1943
|
-
getAttachmentUrl(attachmentId) {
|
|
1944
|
-
return `${this.baseUrl}/api/messages/attachments?id=${encodeURIComponent(attachmentId)}`;
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
/**
|
|
1948
|
-
* Download an attachment as a Buffer.
|
|
1949
|
-
* @param {string} attachmentId - Attachment ID (att_*)
|
|
1950
|
-
* @returns {Promise<{data: Buffer, filename: string, mimeType: string}>}
|
|
1951
|
-
*/
|
|
1952
|
-
async getAttachment(attachmentId) {
|
|
1953
|
-
const url = this.getAttachmentUrl(attachmentId);
|
|
1954
|
-
const res = await fetch(url, {
|
|
1955
|
-
headers: { 'x-api-key': this.apiKey },
|
|
1956
|
-
});
|
|
1957
|
-
if (!res.ok) {
|
|
1958
|
-
const err = await res.json().catch(() => ({}));
|
|
1959
|
-
throw new Error(err.error || `Attachment fetch failed: ${res.status}`);
|
|
1960
|
-
}
|
|
1961
|
-
const data = Buffer.from(await res.arrayBuffer());
|
|
1962
|
-
const cd = res.headers.get('content-disposition') || '';
|
|
1963
|
-
const match = cd.match(/filename="(.+?)"/);
|
|
1964
|
-
return {
|
|
1965
|
-
data,
|
|
1966
|
-
filename: match ? match[1] : attachmentId,
|
|
1967
|
-
mimeType: res.headers.get('content-type') || 'application/octet-stream',
|
|
1968
|
-
};
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
// ══════════════════════════════════════════════
|
|
1972
|
-
// Category 13: Policy Enforcement (Guard) (2 methods)
|
|
1973
|
-
// ══════════════════════════════════════════════
|
|
1974
|
-
|
|
1975
|
-
/**
|
|
1976
|
-
* Enforce policies before a decision executes. Guard is the heart of DashClaw. It intercepts intent and returns allow/warn/block/require_approval.
|
|
1977
|
-
* @param {Object} context
|
|
1978
|
-
* @param {string} context.action_type - Action type (required)
|
|
1979
|
-
* @param {number} [context.risk_score] - Risk score 0-100
|
|
1980
|
-
* @param {string[]} [context.systems_touched] - Systems involved
|
|
1981
|
-
* @param {boolean} [context.reversible] - Whether the action is reversible
|
|
1982
|
-
* @param {string} [context.declared_goal] - What the action aims to do
|
|
1983
|
-
* @param {Object} [options]
|
|
1984
|
-
* @param {boolean} [options.includeSignals=false] - Include live signal warnings
|
|
1985
|
-
* @returns {Promise<{decision: string, reasons: string[], warnings: string[], matched_policies: string[], evaluated_at: string}>}
|
|
1986
|
-
*/
|
|
1987
|
-
async guard(context, options = {}) {
|
|
1988
|
-
const params = new URLSearchParams();
|
|
1989
|
-
if (options.includeSignals) params.set('include_signals', 'true');
|
|
1990
|
-
const qs = params.toString();
|
|
1991
|
-
return this._request(`/api/guard${qs ? `?${qs}` : ''}`, 'POST', {
|
|
1992
|
-
...context,
|
|
1993
|
-
agent_id: context.agent_id || this.agentId,
|
|
1994
|
-
});
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
/**
|
|
1998
|
-
* Get recent guard decisions (audit log).
|
|
1999
|
-
* @param {Object} [filters]
|
|
2000
|
-
* @param {string} [filters.decision] - Filter by decision: allow|warn|block|require_approval
|
|
2001
|
-
* @param {number} [filters.limit=20] - Max results
|
|
2002
|
-
* @param {number} [filters.offset=0] - Pagination offset
|
|
2003
|
-
* @returns {Promise<{decisions: Object[], total: number, stats: Object}>}
|
|
2004
|
-
*/
|
|
2005
|
-
async getGuardDecisions(filters = {}) {
|
|
2006
|
-
const params = new URLSearchParams({ agent_id: this.agentId });
|
|
2007
|
-
if (filters.decision) params.set('decision', filters.decision);
|
|
2008
|
-
if (filters.limit) params.set('limit', String(filters.limit));
|
|
2009
|
-
if (filters.offset) params.set('offset', String(filters.offset));
|
|
2010
|
-
return this._request(`/api/guard?${params}`, 'GET');
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
// ══════════════════════════════════════════════════════════
|
|
2014
|
-
// Category 14: Policy Testing (3 methods)
|
|
2015
|
-
// ══════════════════════════════════════════════════════════
|
|
2016
|
-
|
|
2017
|
-
/**
|
|
2018
|
-
* Run guardrails tests against all active policies for this org.
|
|
2019
|
-
* @returns {Promise<{success: boolean, total_policies: number, total_tests: number, passed: number, failed: number, details: Object[]}>}
|
|
2020
|
-
*/
|
|
2021
|
-
async testPolicies() {
|
|
2022
|
-
return this._request('/api/policies/test', 'POST', {
|
|
2023
|
-
agent_id: this.agentId,
|
|
2024
|
-
});
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
|
-
/**
|
|
2028
|
-
* Generate a compliance proof report from active policies.
|
|
2029
|
-
* @param {Object} [options]
|
|
2030
|
-
* @param {string} [options.format='json'] - 'json' or 'md'
|
|
2031
|
-
* @returns {Promise<Object|string>}
|
|
2032
|
-
*/
|
|
2033
|
-
async getProofReport(options = {}) {
|
|
2034
|
-
const params = new URLSearchParams();
|
|
2035
|
-
if (options.format) params.set('format', options.format);
|
|
2036
|
-
return this._request(`/api/policies/proof?${params}`, 'GET');
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
/**
|
|
2040
|
-
* Import a policy pack or raw YAML into the org's guard policies.
|
|
2041
|
-
* Requires admin role.
|
|
2042
|
-
* @param {Object} options
|
|
2043
|
-
* @param {string} [options.pack] - Pack name: enterprise-strict, smb-safe, startup-growth, development
|
|
2044
|
-
* @param {string} [options.yaml] - Raw YAML string of policies to import
|
|
2045
|
-
* @returns {Promise<{imported: number, skipped: number, errors: string[], policies: Object[]}>}
|
|
2046
|
-
*/
|
|
2047
|
-
async importPolicies({ pack, yaml } = {}) {
|
|
2048
|
-
return this._request('/api/policies/import', 'POST', { pack, yaml });
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
// ══════════════════════════════════════════════════════════
|
|
2052
|
-
// Category 15: Compliance Engine (5 methods)
|
|
2053
|
-
// ══════════════════════════════════════════════════════════
|
|
2054
|
-
|
|
2055
|
-
/**
|
|
2056
|
-
* Map active policies to a compliance framework's controls.
|
|
2057
|
-
* @param {string} framework - Framework ID: soc2, iso27001, gdpr, nist-ai-rmf, imda-agentic
|
|
2058
|
-
* @returns {Promise<Object>} Compliance map with controls, coverage, and gaps
|
|
2059
|
-
*/
|
|
2060
|
-
async mapCompliance(framework) {
|
|
2061
|
-
return this._request(`/api/compliance/map?framework=${encodeURIComponent(framework)}`, 'GET');
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
/**
|
|
2065
|
-
* Run gap analysis on a compliance framework mapping.
|
|
2066
|
-
* @param {string} framework - Framework ID
|
|
2067
|
-
* @returns {Promise<Object>} Gap analysis with remediation plan and risk assessment
|
|
2068
|
-
*/
|
|
2069
|
-
async analyzeGaps(framework) {
|
|
2070
|
-
return this._request(`/api/compliance/gaps?framework=${encodeURIComponent(framework)}`, 'GET');
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
/**
|
|
2074
|
-
* Generate a full compliance report (markdown or JSON) and save a snapshot.
|
|
2075
|
-
* @param {string} framework - Framework ID
|
|
2076
|
-
* @param {Object} [options]
|
|
2077
|
-
* @param {string} [options.format='json'] - 'json' or 'md'
|
|
2078
|
-
* @returns {Promise<Object>}
|
|
2079
|
-
*/
|
|
2080
|
-
async getComplianceReport(framework, options = {}) {
|
|
2081
|
-
const params = new URLSearchParams({ framework });
|
|
2082
|
-
if (options.format) params.set('format', options.format);
|
|
2083
|
-
return this._request(`/api/compliance/report?${params}`, 'GET');
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
/**
|
|
2087
|
-
* List available compliance frameworks.
|
|
2088
|
-
* @returns {Promise<{frameworks: Object[]}>}
|
|
2089
|
-
*/
|
|
2090
|
-
async listFrameworks() {
|
|
2091
|
-
return this._request('/api/compliance/frameworks', 'GET');
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
/**
|
|
2095
|
-
* Get live compliance evidence from guard decisions and action records.
|
|
2096
|
-
* @param {Object} [options]
|
|
2097
|
-
* @param {string} [options.window='30d'] - Time window (e.g., '7d', '30d', '90d')
|
|
2098
|
-
* @returns {Promise<{evidence: Object}>}
|
|
2099
|
-
*/
|
|
2100
|
-
async getComplianceEvidence(options = {}) {
|
|
2101
|
-
const params = new URLSearchParams();
|
|
2102
|
-
if (options.window) params.set('window', options.window);
|
|
2103
|
-
return this._request(`/api/compliance/evidence?${params}`, 'GET');
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
// ══════════════════════════════════════════════════════════
|
|
2107
|
-
// Category 16: Task Routing (10 methods)
|
|
2108
|
-
// ══════════════════════════════════════════════════════════
|
|
2109
|
-
|
|
2110
|
-
/**
|
|
2111
|
-
* List routing agents registered in this org.
|
|
2112
|
-
* @param {Object} [filters]
|
|
2113
|
-
* @param {string} [filters.status] - Filter by status: available, busy, offline
|
|
2114
|
-
* @returns {Promise<{agents: Object[]}>}
|
|
2115
|
-
*/
|
|
2116
|
-
async listRoutingAgents(filters = {}) {
|
|
2117
|
-
const params = new URLSearchParams();
|
|
2118
|
-
if (filters.status) params.set('status', filters.status);
|
|
2119
|
-
return this._request(`/api/routing/agents?${params}`, 'GET');
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
/**
|
|
2123
|
-
* Register an agent for task routing.
|
|
2124
|
-
* @param {Object} agent
|
|
2125
|
-
* @param {string} agent.name - Agent name
|
|
2126
|
-
* @param {Array} [agent.capabilities] - Skills/capabilities (strings or {skill, priority} objects)
|
|
2127
|
-
* @param {number} [agent.maxConcurrent=3] - Max concurrent tasks
|
|
2128
|
-
* @param {string} [agent.endpoint] - Webhook endpoint for task dispatch
|
|
2129
|
-
* @returns {Promise<{agent: Object}>}
|
|
2130
|
-
*/
|
|
2131
|
-
async registerRoutingAgent(agent) {
|
|
2132
|
-
return this._request('/api/routing/agents', 'POST', agent);
|
|
2133
|
-
}
|
|
2134
|
-
|
|
2135
|
-
/**
|
|
2136
|
-
* Get a single routing agent by ID.
|
|
2137
|
-
* @param {string} agentId - Routing agent ID
|
|
2138
|
-
* @returns {Promise<{agent: Object, metrics: Object[]}>}
|
|
2139
|
-
*/
|
|
2140
|
-
async getRoutingAgent(agentId) {
|
|
2141
|
-
return this._request(`/api/routing/agents/${encodeURIComponent(agentId)}`, 'GET');
|
|
2142
|
-
}
|
|
2143
|
-
|
|
2144
|
-
/**
|
|
2145
|
-
* Update routing agent status.
|
|
2146
|
-
* @param {string} agentId - Routing agent ID
|
|
2147
|
-
* @param {string} status - New status: available, busy, offline
|
|
2148
|
-
* @returns {Promise<{agent: Object}>}
|
|
2149
|
-
*/
|
|
2150
|
-
async updateRoutingAgentStatus(agentId, status) {
|
|
2151
|
-
return this._request(`/api/routing/agents/${encodeURIComponent(agentId)}`, 'PATCH', { status });
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
/**
|
|
2155
|
-
* Unregister (delete) a routing agent.
|
|
2156
|
-
* @param {string} agentId - Routing agent ID
|
|
2157
|
-
* @returns {Promise<{deleted: Object}>}
|
|
2158
|
-
*/
|
|
2159
|
-
async deleteRoutingAgent(agentId) {
|
|
2160
|
-
return this._request(`/api/routing/agents/${encodeURIComponent(agentId)}`, 'DELETE');
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
/**
|
|
2164
|
-
* List routing tasks with optional filters.
|
|
2165
|
-
* @param {Object} [filters]
|
|
2166
|
-
* @param {string} [filters.status] - Filter by status
|
|
2167
|
-
* @param {string} [filters.assignedTo] - Filter by assigned agent
|
|
2168
|
-
* @param {number} [filters.limit=50] - Max results
|
|
2169
|
-
* @returns {Promise<{tasks: Object[]}>}
|
|
2170
|
-
*/
|
|
2171
|
-
async listRoutingTasks(filters = {}) {
|
|
2172
|
-
const params = new URLSearchParams();
|
|
2173
|
-
if (filters.status) params.set('status', filters.status);
|
|
2174
|
-
if (filters.assignedTo) params.set('assigned_to', filters.assignedTo);
|
|
2175
|
-
if (filters.limit) params.set('limit', String(filters.limit));
|
|
2176
|
-
return this._request(`/api/routing/tasks?${params}`, 'GET');
|
|
2177
|
-
}
|
|
2178
|
-
|
|
2179
|
-
/**
|
|
2180
|
-
* Submit a task for auto-routing to the best available agent.
|
|
2181
|
-
* @param {Object} task
|
|
2182
|
-
* @param {string} task.title - Task title
|
|
2183
|
-
* @param {string} [task.description] - Task description
|
|
2184
|
-
* @param {string[]} [task.requiredSkills] - Skills needed to complete this task
|
|
2185
|
-
* @param {string} [task.urgency='normal'] - Urgency: low, normal, high, critical
|
|
2186
|
-
* @param {number} [task.timeoutSeconds=3600] - Timeout in seconds
|
|
2187
|
-
* @param {number} [task.maxRetries=2] - Max retry attempts
|
|
2188
|
-
* @param {string} [task.callbackUrl] - Webhook URL for task completion callback
|
|
2189
|
-
* @returns {Promise<{task: Object, routing: Object}>}
|
|
2190
|
-
*/
|
|
2191
|
-
async submitRoutingTask(task) {
|
|
2192
|
-
return this._request('/api/routing/tasks', 'POST', task);
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
/**
|
|
2196
|
-
* Complete a routing task.
|
|
2197
|
-
* @param {string} taskId - Task ID
|
|
2198
|
-
* @param {Object} [result]
|
|
2199
|
-
* @param {boolean} [result.success=true] - Whether task succeeded
|
|
2200
|
-
* @param {Object} [result.result] - Task result data
|
|
2201
|
-
* @param {string} [result.error] - Error message if failed
|
|
2202
|
-
* @returns {Promise<{task: Object, routing: Object}>}
|
|
2203
|
-
*/
|
|
2204
|
-
async completeRoutingTask(taskId, result = {}) {
|
|
2205
|
-
return this._request(`/api/routing/tasks/${encodeURIComponent(taskId)}/complete`, 'POST', result);
|
|
2206
|
-
}
|
|
2207
|
-
|
|
2208
|
-
/**
|
|
2209
|
-
* Get routing statistics for the org.
|
|
2210
|
-
* @returns {Promise<{agents: Object, tasks: Object, routing: Object}>}
|
|
2211
|
-
*/
|
|
2212
|
-
async getRoutingStats() {
|
|
2213
|
-
return this._request('/api/routing/stats', 'GET');
|
|
2214
|
-
}
|
|
2215
|
-
|
|
2216
|
-
/**
|
|
2217
|
-
* Get routing system health status.
|
|
2218
|
-
* @returns {Promise<{status: string, agents: Object, tasks: Object}>}
|
|
2219
|
-
*/
|
|
2220
|
-
async getRoutingHealth() {
|
|
2221
|
-
return this._request('/api/routing/health', 'GET');
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
// ══════════════════════════════════════════════════════════
|
|
2225
|
-
// Agent Pairing (3 methods)
|
|
2226
|
-
// ══════════════════════════════════════════════════════════
|
|
2227
|
-
|
|
2228
|
-
// createPairing, createPairingFromPrivateJwk, waitForPairing
|
|
2229
|
-
// (defined near the top of the class)
|
|
2230
|
-
|
|
2231
|
-
// ══════════════════════════════════════════════════════════
|
|
2232
|
-
// Identity Binding (2 methods)
|
|
2233
|
-
// ══════════════════════════════════════════════════════════
|
|
2234
|
-
|
|
2235
|
-
/**
|
|
2236
|
-
* Register or update an agent's public key for identity verification.
|
|
2237
|
-
* Requires admin API key.
|
|
2238
|
-
* @param {Object} identity
|
|
2239
|
-
* @param {string} identity.agent_id - Agent ID to register
|
|
2240
|
-
* @param {string} identity.public_key - PEM public key (SPKI format)
|
|
2241
|
-
* @param {string} [identity.algorithm='RSASSA-PKCS1-v1_5'] - Signing algorithm
|
|
2242
|
-
* @returns {Promise<{identity: Object}>}
|
|
2243
|
-
*/
|
|
2244
|
-
async registerIdentity(identity) {
|
|
2245
|
-
return this._request('/api/identities', 'POST', identity);
|
|
2246
|
-
}
|
|
2247
|
-
|
|
2248
|
-
/**
|
|
2249
|
-
* List all registered agent identities for this org.
|
|
2250
|
-
* @returns {Promise<{identities: Object[]}>}
|
|
2251
|
-
*/
|
|
2252
|
-
async getIdentities() {
|
|
2253
|
-
return this._request('/api/identities', 'GET');
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
|
-
// ══════════════════════════════════════════════════════════
|
|
2257
|
-
// Organization Management (5 methods)
|
|
2258
|
-
// ══════════════════════════════════════════════════════════
|
|
2259
|
-
|
|
2260
|
-
/**
|
|
2261
|
-
* Get the current organization's details. Requires admin API key.
|
|
2262
|
-
* @returns {Promise<{organizations: Object[]}>}
|
|
2263
|
-
*/
|
|
2264
|
-
async getOrg() {
|
|
2265
|
-
return this._request('/api/orgs', 'GET');
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2268
|
-
/**
|
|
2269
|
-
* Create a new organization with an initial admin API key. Requires admin API key.
|
|
2270
|
-
* @param {Object} org
|
|
2271
|
-
* @param {string} org.name - Organization name
|
|
2272
|
-
* @param {string} org.slug - URL-safe slug (lowercase alphanumeric + hyphens)
|
|
2273
|
-
* @returns {Promise<{organization: Object, api_key: Object}>}
|
|
2274
|
-
*/
|
|
2275
|
-
async createOrg(org) {
|
|
2276
|
-
return this._request('/api/orgs', 'POST', org);
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
/**
|
|
2280
|
-
* Get organization details by ID. Requires admin API key.
|
|
2281
|
-
* @param {string} orgId - Organization ID
|
|
2282
|
-
* @returns {Promise<{organization: Object}>}
|
|
2283
|
-
*/
|
|
2284
|
-
async getOrgById(orgId) {
|
|
2285
|
-
return this._request(`/api/orgs/${encodeURIComponent(orgId)}`, 'GET');
|
|
2286
|
-
}
|
|
2287
|
-
|
|
2288
|
-
/**
|
|
2289
|
-
* Update organization details. Requires admin API key.
|
|
2290
|
-
* @param {string} orgId - Organization ID
|
|
2291
|
-
* @param {Object} updates - Fields to update (name, slug)
|
|
2292
|
-
* @returns {Promise<{organization: Object}>}
|
|
2293
|
-
*/
|
|
2294
|
-
async updateOrg(orgId, updates) {
|
|
2295
|
-
return this._request(`/api/orgs/${encodeURIComponent(orgId)}`, 'PATCH', updates);
|
|
2296
|
-
}
|
|
2297
|
-
|
|
2298
|
-
/**
|
|
2299
|
-
* List API keys for an organization. Requires admin API key.
|
|
2300
|
-
* @param {string} orgId - Organization ID
|
|
2301
|
-
* @returns {Promise<{keys: Object[]}>}
|
|
2302
|
-
*/
|
|
2303
|
-
async getOrgKeys(orgId) {
|
|
2304
|
-
return this._request(`/api/orgs/${encodeURIComponent(orgId)}/keys`, 'GET');
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
// ══════════════════════════════════════════════════════════
|
|
2308
|
-
// Activity Logs (1 method)
|
|
2309
|
-
// ══════════════════════════════════════════════════════════
|
|
2310
|
-
|
|
2311
|
-
/**
|
|
2312
|
-
* Get activity/audit logs for the organization.
|
|
2313
|
-
* @param {Object} [filters]
|
|
2314
|
-
* @param {string} [filters.action] - Filter by action type
|
|
2315
|
-
* @param {string} [filters.actor_id] - Filter by actor
|
|
2316
|
-
* @param {string} [filters.resource_type] - Filter by resource type
|
|
2317
|
-
* @param {string} [filters.before] - Before timestamp (ISO string)
|
|
2318
|
-
* @param {string} [filters.after] - After timestamp (ISO string)
|
|
2319
|
-
* @param {number} [filters.limit=50] - Max results (max 200)
|
|
2320
|
-
* @param {number} [filters.offset=0] - Pagination offset
|
|
2321
|
-
* @returns {Promise<{logs: Object[], stats: Object, pagination: Object}>}
|
|
2322
|
-
*/
|
|
2323
|
-
async getActivityLogs(filters = {}) {
|
|
2324
|
-
const params = new URLSearchParams();
|
|
2325
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
2326
|
-
if (value !== undefined && value !== null && value !== '') {
|
|
2327
|
-
params.set(key, String(value));
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
return this._request(`/api/activity?${params}`, 'GET');
|
|
2331
|
-
}
|
|
2332
|
-
|
|
2333
|
-
// ══════════════════════════════════════════════════════════
|
|
2334
|
-
// Webhooks (5 methods)
|
|
2335
|
-
// ══════════════════════════════════════════════════════════
|
|
2336
|
-
|
|
2337
|
-
/**
|
|
2338
|
-
* List all webhooks for this org.
|
|
2339
|
-
* @returns {Promise<{webhooks: Object[]}>}
|
|
2340
|
-
*/
|
|
2341
|
-
async getWebhooks() {
|
|
2342
|
-
return this._request('/api/webhooks', 'GET');
|
|
2343
|
-
}
|
|
2344
|
-
|
|
2345
|
-
/**
|
|
2346
|
-
* Create a new webhook subscription.
|
|
2347
|
-
* @param {Object} webhook
|
|
2348
|
-
* @param {string} webhook.url - Webhook endpoint URL
|
|
2349
|
-
* @param {string[]} [webhook.events] - Event types to subscribe to
|
|
2350
|
-
* @returns {Promise<{webhook: Object}>}
|
|
2351
|
-
*/
|
|
2352
|
-
async createWebhook(webhook) {
|
|
2353
|
-
return this._request('/api/webhooks', 'POST', webhook);
|
|
2354
|
-
}
|
|
2355
|
-
|
|
2356
|
-
/**
|
|
2357
|
-
* Delete a webhook.
|
|
2358
|
-
* @param {string} webhookId - Webhook ID
|
|
2359
|
-
* @returns {Promise<{deleted: boolean}>}
|
|
2360
|
-
*/
|
|
2361
|
-
async deleteWebhook(webhookId) {
|
|
2362
|
-
return this._request(`/api/webhooks?id=${encodeURIComponent(webhookId)}`, 'DELETE');
|
|
2363
|
-
}
|
|
2364
|
-
|
|
2365
|
-
/**
|
|
2366
|
-
* Send a test event to a webhook.
|
|
2367
|
-
* @param {string} webhookId - Webhook ID
|
|
2368
|
-
* @returns {Promise<{delivery: Object}>}
|
|
2369
|
-
*/
|
|
2370
|
-
async testWebhook(webhookId) {
|
|
2371
|
-
return this._request(`/api/webhooks/${encodeURIComponent(webhookId)}/test`, 'POST');
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2374
|
-
/**
|
|
2375
|
-
* Get delivery history for a webhook.
|
|
2376
|
-
* @param {string} webhookId - Webhook ID
|
|
2377
|
-
* @returns {Promise<{deliveries: Object[]}>}
|
|
2378
|
-
*/
|
|
2379
|
-
async getWebhookDeliveries(webhookId) {
|
|
2380
|
-
return this._request(`/api/webhooks/${encodeURIComponent(webhookId)}/deliveries`, 'GET');
|
|
2381
|
-
}
|
|
2382
|
-
|
|
2383
|
-
// ─── Bulk Sync ────────────────────────────────────────────
|
|
2384
|
-
|
|
2385
|
-
/**
|
|
2386
|
-
* Sync multiple data categories in a single request.
|
|
2387
|
-
* Every key is optional. Only provided categories are processed.
|
|
2388
|
-
* @param {Object} state - Data to sync (connections, memory, goals, learning, content, inspiration, context_points, context_threads, handoffs, preferences, snippets)
|
|
2389
|
-
* @returns {Promise<{results: Object, total_synced: number, total_errors: number, duration_ms: number}>}
|
|
2390
|
-
*/
|
|
2391
|
-
async syncState(state) {
|
|
2392
|
-
return this._request('/api/sync', 'POST', {
|
|
2393
|
-
agent_id: this.agentId,
|
|
2394
|
-
...state,
|
|
2395
|
-
});
|
|
2396
|
-
}
|
|
2397
|
-
|
|
2398
|
-
// ----------------------------------------------
|
|
2399
|
-
// Category: Evaluations
|
|
2400
|
-
// ----------------------------------------------
|
|
2401
|
-
|
|
2402
|
-
/**
|
|
2403
|
-
* Create an evaluation score for an action.
|
|
2404
|
-
* @param {Object} params
|
|
2405
|
-
* @param {string} params.actionId - Action record ID
|
|
2406
|
-
* @param {string} params.scorerName - Name of the scorer
|
|
2407
|
-
* @param {number} params.score - Score between 0.0 and 1.0
|
|
2408
|
-
* @param {string} [params.label] - Category label (e.g., 'correct', 'incorrect')
|
|
2409
|
-
* @param {string} [params.reasoning] - Explanation of the score
|
|
2410
|
-
* @param {string} [params.evaluatedBy] - 'auto', 'human', or 'llm_judge'
|
|
2411
|
-
* @param {Object} [params.metadata] - Additional metadata
|
|
2412
|
-
* @returns {Promise<Object>}
|
|
2413
|
-
*/
|
|
2414
|
-
async createScore({ actionId, scorerName, score, label, reasoning, evaluatedBy, metadata }) {
|
|
2415
|
-
return this._request('/api/evaluations', 'POST', {
|
|
2416
|
-
action_id: actionId,
|
|
2417
|
-
scorer_name: scorerName,
|
|
2418
|
-
score,
|
|
2419
|
-
label,
|
|
2420
|
-
reasoning,
|
|
2421
|
-
evaluated_by: evaluatedBy,
|
|
2422
|
-
metadata,
|
|
2423
|
-
});
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
/**
|
|
2427
|
-
* List evaluation scores with optional filters.
|
|
2428
|
-
* @param {Object} [filters] - { action_id, scorer_name, evaluated_by, min_score, max_score, limit, offset, agent_id }
|
|
2429
|
-
* @returns {Promise<{ scores: Object[], total: number }>}
|
|
2430
|
-
*/
|
|
2431
|
-
async getScores(filters = {}) {
|
|
2432
|
-
const params = new URLSearchParams();
|
|
2433
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
2434
|
-
if (value !== undefined && value !== null && value !== '') {
|
|
2435
|
-
params.set(key, String(value));
|
|
2436
|
-
}
|
|
2437
|
-
}
|
|
2438
|
-
return this._request(`/api/evaluations?${params}`, 'GET');
|
|
2439
|
-
}
|
|
2440
|
-
|
|
2441
|
-
/**
|
|
2442
|
-
* Create a reusable scorer definition.
|
|
2443
|
-
* @param {Object} params
|
|
2444
|
-
* @param {string} params.name - Scorer name (unique per org)
|
|
2445
|
-
* @param {string} params.scorerType - 'regex', 'contains', 'numeric_range', 'custom_function', or 'llm_judge'
|
|
2446
|
-
* @param {Object} params.config - Scorer configuration
|
|
2447
|
-
* @param {string} [params.description] - Description
|
|
2448
|
-
* @returns {Promise<Object>}
|
|
2449
|
-
*/
|
|
2450
|
-
async createScorer({ name, scorerType, config, description }) {
|
|
2451
|
-
return this._request('/api/evaluations/scorers', 'POST', {
|
|
2452
|
-
name,
|
|
2453
|
-
scorer_type: scorerType,
|
|
2454
|
-
config,
|
|
2455
|
-
description,
|
|
2456
|
-
});
|
|
2457
|
-
}
|
|
2458
|
-
|
|
2459
|
-
/**
|
|
2460
|
-
* List all scorers for this org.
|
|
2461
|
-
* @returns {Promise<{ scorers: Object[], llm_available: boolean }>}
|
|
2462
|
-
*/
|
|
2463
|
-
async getScorers() {
|
|
2464
|
-
return this._request('/api/evaluations/scorers', 'GET');
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
/**
|
|
2468
|
-
* Update a scorer.
|
|
2469
|
-
* @param {string} scorerId
|
|
2470
|
-
* @param {Object} updates - { name?, description?, config? }
|
|
2471
|
-
* @returns {Promise<Object>}
|
|
2472
|
-
*/
|
|
2473
|
-
async updateScorer(scorerId, updates) {
|
|
2474
|
-
return this._request(`/api/evaluations/scorers/${scorerId}`, 'PATCH', updates);
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
|
-
/**
|
|
2478
|
-
* Delete a scorer.
|
|
2479
|
-
* @param {string} scorerId
|
|
2480
|
-
* @returns {Promise<Object>}
|
|
2481
|
-
*/
|
|
2482
|
-
async deleteScorer(scorerId) {
|
|
2483
|
-
return this._request(`/api/evaluations/scorers/${scorerId}`, 'DELETE');
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2486
|
-
/**
|
|
2487
|
-
* Create and start an evaluation run.
|
|
2488
|
-
* @param {Object} params
|
|
2489
|
-
* @param {string} params.name - Run name
|
|
2490
|
-
* @param {string} params.scorerId - Scorer to use
|
|
2491
|
-
* @param {Object} [params.actionFilters] - Filters for which actions to evaluate
|
|
2492
|
-
* @returns {Promise<Object>}
|
|
2493
|
-
*/
|
|
2494
|
-
async createEvalRun({ name, scorerId, actionFilters }) {
|
|
2495
|
-
return this._request('/api/evaluations/runs', 'POST', {
|
|
2496
|
-
name,
|
|
2497
|
-
scorer_id: scorerId,
|
|
2498
|
-
action_filters: actionFilters,
|
|
2499
|
-
});
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
/**
|
|
2503
|
-
* List evaluation runs.
|
|
2504
|
-
* @param {Object} [filters] - { status, limit, offset }
|
|
2505
|
-
* @returns {Promise<{ runs: Object[] }>}
|
|
2506
|
-
*/
|
|
2507
|
-
async getEvalRuns(filters = {}) {
|
|
2508
|
-
const params = new URLSearchParams();
|
|
2509
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
2510
|
-
if (value !== undefined && value !== null && value !== '') {
|
|
2511
|
-
params.set(key, String(value));
|
|
2512
|
-
}
|
|
2513
|
-
}
|
|
2514
|
-
return this._request(`/api/evaluations/runs?${params}`, 'GET');
|
|
2515
|
-
}
|
|
2516
|
-
|
|
2517
|
-
/**
|
|
2518
|
-
* Get details of an evaluation run.
|
|
2519
|
-
* @param {string} runId
|
|
2520
|
-
* @returns {Promise<{ run: Object, distribution: Object[] }>}
|
|
2521
|
-
*/
|
|
2522
|
-
async getEvalRun(runId) {
|
|
2523
|
-
return this._request(`/api/evaluations/runs/${runId}`, 'GET');
|
|
2524
|
-
}
|
|
2525
|
-
|
|
2526
|
-
/**
|
|
2527
|
-
* Get aggregate evaluation statistics.
|
|
2528
|
-
* @param {Object} [filters] - { agent_id, scorer_name, days }
|
|
2529
|
-
* @returns {Promise<Object>}
|
|
2530
|
-
*/
|
|
2531
|
-
async getEvalStats(filters = {}) {
|
|
2532
|
-
const params = new URLSearchParams();
|
|
2533
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
2534
|
-
if (value !== undefined && value !== null && value !== '') {
|
|
2535
|
-
params.set(key, String(value));
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
return this._request(`/api/evaluations/stats?${params}`, 'GET');
|
|
2539
|
-
}
|
|
2540
|
-
|
|
2541
|
-
// -----------------------------------------------
|
|
2542
|
-
// Prompt Management
|
|
2543
|
-
// -----------------------------------------------
|
|
2544
|
-
|
|
2545
|
-
async listPromptTemplates({ category } = {}) {
|
|
2546
|
-
const params = category ? `?category=${encodeURIComponent(category)}` : '';
|
|
2547
|
-
return this._request(`/api/prompts/templates${params}`, 'GET');
|
|
2548
|
-
}
|
|
2549
|
-
|
|
2550
|
-
async createPromptTemplate({ name, description, category }) {
|
|
2551
|
-
return this._request('/api/prompts/templates', 'POST', { name, description, category });
|
|
2552
|
-
}
|
|
2553
|
-
|
|
2554
|
-
async getPromptTemplate(templateId) {
|
|
2555
|
-
return this._request(`/api/prompts/templates/${templateId}`, 'GET');
|
|
2556
|
-
}
|
|
2557
|
-
|
|
2558
|
-
async updatePromptTemplate(templateId, fields) {
|
|
2559
|
-
return this._request(`/api/prompts/templates/${templateId}`, 'PATCH', fields);
|
|
2560
|
-
}
|
|
2561
|
-
|
|
2562
|
-
async deletePromptTemplate(templateId) {
|
|
2563
|
-
return this._request(`/api/prompts/templates/${templateId}`, 'DELETE');
|
|
2564
|
-
}
|
|
2565
|
-
|
|
2566
|
-
async listPromptVersions(templateId) {
|
|
2567
|
-
return this._request(`/api/prompts/templates/${templateId}/versions`, 'GET');
|
|
2568
|
-
}
|
|
2569
|
-
|
|
2570
|
-
async createPromptVersion(templateId, { content, model_hint, parameters, changelog }) {
|
|
2571
|
-
return this._request(`/api/prompts/templates/${templateId}/versions`, 'POST', { content, model_hint, parameters, changelog });
|
|
2572
|
-
}
|
|
2573
|
-
|
|
2574
|
-
async getPromptVersion(templateId, versionId) {
|
|
2575
|
-
return this._request(`/api/prompts/templates/${templateId}/versions/${versionId}`, 'GET');
|
|
2576
|
-
}
|
|
2577
|
-
|
|
2578
|
-
async activatePromptVersion(templateId, versionId) {
|
|
2579
|
-
return this._request(`/api/prompts/templates/${templateId}/versions/${versionId}`, 'POST');
|
|
2580
|
-
}
|
|
2581
|
-
|
|
2582
|
-
async renderPrompt({ template_id, version_id, variables, action_id, agent_id, record }) {
|
|
2583
|
-
return this._request('/api/prompts/render', 'POST', { template_id, version_id, variables, action_id, agent_id, record });
|
|
2584
|
-
}
|
|
2585
|
-
|
|
2586
|
-
async listPromptRuns({ template_id, version_id, limit } = {}) {
|
|
2587
|
-
const params = new URLSearchParams();
|
|
2588
|
-
if (template_id) params.set('template_id', template_id);
|
|
2589
|
-
if (version_id) params.set('version_id', version_id);
|
|
2590
|
-
if (limit) params.set('limit', String(limit));
|
|
2591
|
-
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
2592
|
-
return this._request(`/api/prompts/runs${qs}`, 'GET');
|
|
2593
|
-
}
|
|
2594
|
-
|
|
2595
|
-
async getPromptStats({ template_id } = {}) {
|
|
2596
|
-
const params = template_id ? `?template_id=${encodeURIComponent(template_id)}` : '';
|
|
2597
|
-
return this._request(`/api/prompts/stats${params}`, 'GET');
|
|
2598
|
-
}
|
|
2599
|
-
|
|
2600
|
-
// -----------------------------------------------
|
|
2601
|
-
// User Feedback
|
|
2602
|
-
// -----------------------------------------------
|
|
2603
|
-
|
|
2604
|
-
async submitFeedback({ action_id, agent_id, rating, comment, category, tags, metadata }) {
|
|
2605
|
-
return this._request('/api/feedback', 'POST', { action_id, agent_id, rating, comment, category, tags, metadata, source: 'sdk' });
|
|
2606
|
-
}
|
|
2607
|
-
|
|
2608
|
-
async listFeedback({ action_id, agent_id, category, sentiment, resolved, limit, offset } = {}) {
|
|
2609
|
-
const params = new URLSearchParams();
|
|
2610
|
-
if (action_id) params.set('action_id', action_id);
|
|
2611
|
-
if (agent_id) params.set('agent_id', agent_id);
|
|
2612
|
-
if (category) params.set('category', category);
|
|
2613
|
-
if (sentiment) params.set('sentiment', sentiment);
|
|
2614
|
-
if (resolved !== undefined) params.set('resolved', String(resolved));
|
|
2615
|
-
if (limit) params.set('limit', String(limit));
|
|
2616
|
-
if (offset) params.set('offset', String(offset));
|
|
2617
|
-
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
2618
|
-
return this._request(`/api/feedback${qs}`, 'GET');
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2621
|
-
async getFeedback(feedbackId) {
|
|
2622
|
-
return this._request(`/api/feedback/${feedbackId}`, 'GET');
|
|
2623
|
-
}
|
|
2624
|
-
|
|
2625
|
-
async resolveFeedback(feedbackId) {
|
|
2626
|
-
return this._request(`/api/feedback/${feedbackId}`, 'PATCH', { resolved_by: 'sdk' });
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
|
-
async deleteFeedback(feedbackId) {
|
|
2630
|
-
return this._request(`/api/feedback/${feedbackId}`, 'DELETE');
|
|
2631
|
-
}
|
|
2632
|
-
|
|
2633
|
-
async getFeedbackStats({ agent_id } = {}) {
|
|
2634
|
-
const params = agent_id ? `?agent_id=${encodeURIComponent(agent_id)}` : '';
|
|
2635
|
-
return this._request(`/api/feedback/stats${params}`, 'GET');
|
|
2636
|
-
}
|
|
2637
|
-
|
|
2638
|
-
// -----------------------------------------------
|
|
2639
|
-
// Compliance Export
|
|
2640
|
-
// -----------------------------------------------
|
|
2641
|
-
|
|
2642
|
-
async createComplianceExport({ name, frameworks, format, window_days, include_evidence, include_remediation, include_trends }) {
|
|
2643
|
-
return this._request('/api/compliance/exports', 'POST', { name, frameworks, format, window_days, include_evidence, include_remediation, include_trends });
|
|
2644
|
-
}
|
|
2645
|
-
|
|
2646
|
-
async listComplianceExports({ limit } = {}) {
|
|
2647
|
-
const params = limit ? `?limit=${limit}` : '';
|
|
2648
|
-
return this._request(`/api/compliance/exports${params}`, 'GET');
|
|
2649
|
-
}
|
|
2650
|
-
|
|
2651
|
-
async getComplianceExport(exportId) {
|
|
2652
|
-
return this._request(`/api/compliance/exports/${exportId}`, 'GET');
|
|
2653
|
-
}
|
|
2654
|
-
|
|
2655
|
-
async downloadComplianceExport(exportId) {
|
|
2656
|
-
return this._request(`/api/compliance/exports/${exportId}/download`, 'GET');
|
|
2657
|
-
}
|
|
2658
|
-
|
|
2659
|
-
async deleteComplianceExport(exportId) {
|
|
2660
|
-
return this._request(`/api/compliance/exports/${exportId}`, 'DELETE');
|
|
2661
|
-
}
|
|
2662
|
-
|
|
2663
|
-
async createComplianceSchedule({ name, frameworks, format, window_days, cron_expression, include_evidence, include_remediation, include_trends }) {
|
|
2664
|
-
return this._request('/api/compliance/schedules', 'POST', { name, frameworks, format, window_days, cron_expression, include_evidence, include_remediation, include_trends });
|
|
2665
|
-
}
|
|
2666
|
-
|
|
2667
|
-
async listComplianceSchedules() {
|
|
2668
|
-
return this._request('/api/compliance/schedules', 'GET');
|
|
2669
|
-
}
|
|
2670
|
-
|
|
2671
|
-
async updateComplianceSchedule(scheduleId, fields) {
|
|
2672
|
-
return this._request(`/api/compliance/schedules/${scheduleId}`, 'PATCH', fields);
|
|
2673
|
-
}
|
|
2674
|
-
|
|
2675
|
-
async deleteComplianceSchedule(scheduleId) {
|
|
2676
|
-
return this._request(`/api/compliance/schedules/${scheduleId}`, 'DELETE');
|
|
2677
|
-
}
|
|
2678
|
-
|
|
2679
|
-
async getComplianceTrends({ framework, limit } = {}) {
|
|
2680
|
-
const params = new URLSearchParams();
|
|
2681
|
-
if (framework) params.set('framework', framework);
|
|
2682
|
-
if (limit) params.set('limit', String(limit));
|
|
2683
|
-
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
2684
|
-
return this._request(`/api/compliance/trends${qs}`, 'GET');
|
|
2685
|
-
}
|
|
2686
|
-
|
|
2687
|
-
// -----------------------------------------------
|
|
2688
|
-
// Drift Detection
|
|
2689
|
-
// -----------------------------------------------
|
|
2690
|
-
|
|
2691
|
-
async computeDriftBaselines({ agent_id, lookback_days } = {}) {
|
|
2692
|
-
return this._request('/api/drift/alerts', 'POST', { action: 'compute_baselines', agent_id, lookback_days });
|
|
2693
|
-
}
|
|
2694
|
-
|
|
2695
|
-
async detectDrift({ agent_id, window_days } = {}) {
|
|
2696
|
-
return this._request('/api/drift/alerts', 'POST', { action: 'detect', agent_id, window_days });
|
|
2697
|
-
}
|
|
2698
|
-
|
|
2699
|
-
async recordDriftSnapshots() {
|
|
2700
|
-
return this._request('/api/drift/alerts', 'POST', { action: 'record_snapshots' });
|
|
2701
|
-
}
|
|
2702
|
-
|
|
2703
|
-
async listDriftAlerts({ agent_id, severity, acknowledged, limit } = {}) {
|
|
2704
|
-
const params = new URLSearchParams();
|
|
2705
|
-
if (agent_id) params.set('agent_id', agent_id);
|
|
2706
|
-
if (severity) params.set('severity', severity);
|
|
2707
|
-
if (acknowledged !== undefined) params.set('acknowledged', String(acknowledged));
|
|
2708
|
-
if (limit) params.set('limit', String(limit));
|
|
2709
|
-
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
2710
|
-
return this._request(`/api/drift/alerts${qs}`, 'GET');
|
|
2711
|
-
}
|
|
2712
|
-
|
|
2713
|
-
async acknowledgeDriftAlert(alertId) {
|
|
2714
|
-
return this._request(`/api/drift/alerts/${alertId}`, 'PATCH');
|
|
2715
|
-
}
|
|
2716
|
-
|
|
2717
|
-
async deleteDriftAlert(alertId) {
|
|
2718
|
-
return this._request(`/api/drift/alerts/${alertId}`, 'DELETE');
|
|
2719
|
-
}
|
|
2720
|
-
|
|
2721
|
-
async getDriftStats({ agent_id } = {}) {
|
|
2722
|
-
const params = agent_id ? `?agent_id=${encodeURIComponent(agent_id)}` : '';
|
|
2723
|
-
return this._request(`/api/drift/stats${params}`, 'GET');
|
|
2724
|
-
}
|
|
2725
|
-
|
|
2726
|
-
async getDriftSnapshots({ agent_id, metric, limit } = {}) {
|
|
2727
|
-
const params = new URLSearchParams();
|
|
2728
|
-
if (agent_id) params.set('agent_id', agent_id);
|
|
2729
|
-
if (metric) params.set('metric', metric);
|
|
2730
|
-
if (limit) params.set('limit', String(limit));
|
|
2731
|
-
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
2732
|
-
return this._request(`/api/drift/snapshots${qs}`, 'GET');
|
|
2733
|
-
}
|
|
2734
|
-
|
|
2735
|
-
async getDriftMetrics() {
|
|
2736
|
-
return this._request('/api/drift/metrics', 'GET');
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
// -----------------------------------------------
|
|
2740
|
-
// Learning Analytics
|
|
2741
|
-
// -----------------------------------------------
|
|
2742
|
-
|
|
2743
|
-
async computeLearningVelocity({ agent_id, lookback_days, period } = {}) {
|
|
2744
|
-
return this._request('/api/learning/analytics/velocity', 'POST', { agent_id, lookback_days, period });
|
|
2745
|
-
}
|
|
2746
|
-
|
|
2747
|
-
async getLearningVelocity({ agent_id, limit } = {}) {
|
|
2748
|
-
const params = new URLSearchParams();
|
|
2749
|
-
if (agent_id) params.set('agent_id', agent_id);
|
|
2750
|
-
if (limit) params.set('limit', String(limit));
|
|
2751
|
-
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
2752
|
-
return this._request(`/api/learning/analytics/velocity${qs}`, 'GET');
|
|
2753
|
-
}
|
|
2754
|
-
|
|
2755
|
-
async computeLearningCurves({ agent_id, lookback_days } = {}) {
|
|
2756
|
-
return this._request('/api/learning/analytics/curves', 'POST', { agent_id, lookback_days });
|
|
2757
|
-
}
|
|
2758
|
-
|
|
2759
|
-
async getLearningCurves({ agent_id, action_type, limit } = {}) {
|
|
2760
|
-
const params = new URLSearchParams();
|
|
2761
|
-
if (agent_id) params.set('agent_id', agent_id);
|
|
2762
|
-
if (action_type) params.set('action_type', action_type);
|
|
2763
|
-
if (limit) params.set('limit', String(limit));
|
|
2764
|
-
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
2765
|
-
return this._request(`/api/learning/analytics/curves${qs}`, 'GET');
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
async getLearningAnalyticsSummary({ agent_id } = {}) {
|
|
2769
|
-
const params = agent_id ? `?agent_id=${encodeURIComponent(agent_id)}` : '';
|
|
2770
|
-
return this._request(`/api/learning/analytics/summary${params}`, 'GET');
|
|
2771
|
-
}
|
|
2772
|
-
|
|
2773
|
-
async getMaturityLevels() {
|
|
2774
|
-
return this._request('/api/learning/analytics/maturity', 'GET');
|
|
2775
|
-
}
|
|
2776
|
-
|
|
2777
|
-
// --- Scoring Profiles -----------------------------------
|
|
2778
|
-
|
|
2779
|
-
async createScoringProfile(data) {
|
|
2780
|
-
return this._request('POST', '/api/scoring/profiles', data);
|
|
2781
|
-
}
|
|
2782
|
-
|
|
2783
|
-
async listScoringProfiles(params = {}) {
|
|
2784
|
-
return this._request('GET', '/api/scoring/profiles', null, params);
|
|
2785
|
-
}
|
|
2786
|
-
|
|
2787
|
-
async getScoringProfile(profileId) {
|
|
2788
|
-
return this._request('GET', `/api/scoring/profiles/${profileId}`);
|
|
2789
|
-
}
|
|
2790
|
-
|
|
2791
|
-
async updateScoringProfile(profileId, data) {
|
|
2792
|
-
return this._request('PATCH', `/api/scoring/profiles/${profileId}`, data);
|
|
2793
|
-
}
|
|
2794
|
-
|
|
2795
|
-
async deleteScoringProfile(profileId) {
|
|
2796
|
-
return this._request('DELETE', `/api/scoring/profiles/${profileId}`);
|
|
2797
|
-
}
|
|
2798
|
-
|
|
2799
|
-
async addScoringDimension(profileId, data) {
|
|
2800
|
-
return this._request('POST', `/api/scoring/profiles/${profileId}/dimensions`, data);
|
|
2801
|
-
}
|
|
2802
|
-
|
|
2803
|
-
async updateScoringDimension(profileId, dimensionId, data) {
|
|
2804
|
-
return this._request('PATCH', `/api/scoring/profiles/${profileId}/dimensions/${dimensionId}`, data);
|
|
2805
|
-
}
|
|
2806
|
-
|
|
2807
|
-
async deleteScoringDimension(profileId, dimensionId) {
|
|
2808
|
-
return this._request('DELETE', `/api/scoring/profiles/${profileId}/dimensions/${dimensionId}`);
|
|
2809
|
-
}
|
|
2810
|
-
|
|
2811
|
-
async scoreWithProfile(profileId, action) {
|
|
2812
|
-
return this._request('POST', '/api/scoring/score', { profile_id: profileId, action });
|
|
2813
|
-
}
|
|
2814
|
-
|
|
2815
|
-
async batchScoreWithProfile(profileId, actions) {
|
|
2816
|
-
return this._request('POST', '/api/scoring/score', { profile_id: profileId, actions });
|
|
2817
|
-
}
|
|
2818
|
-
|
|
2819
|
-
async getProfileScores(params = {}) {
|
|
2820
|
-
return this._request('GET', '/api/scoring/score', null, params);
|
|
2821
|
-
}
|
|
2822
|
-
|
|
2823
|
-
async getProfileScoreStats(profileId) {
|
|
2824
|
-
return this._request('GET', '/api/scoring/score', null, { profile_id: profileId, view: 'stats' });
|
|
2825
|
-
}
|
|
2826
|
-
|
|
2827
|
-
// --- Risk Templates ------------------------------------
|
|
2828
|
-
|
|
2829
|
-
async createRiskTemplate(data) {
|
|
2830
|
-
return this._request('POST', '/api/scoring/risk-templates', data);
|
|
2831
|
-
}
|
|
2832
|
-
|
|
2833
|
-
async listRiskTemplates(params = {}) {
|
|
2834
|
-
return this._request('GET', '/api/scoring/risk-templates', null, params);
|
|
2835
|
-
}
|
|
2836
|
-
|
|
2837
|
-
async updateRiskTemplate(templateId, data) {
|
|
2838
|
-
return this._request('PATCH', `/api/scoring/risk-templates/${templateId}`, data);
|
|
2839
|
-
}
|
|
2840
|
-
|
|
2841
|
-
async deleteRiskTemplate(templateId) {
|
|
2842
|
-
return this._request('DELETE', `/api/scoring/risk-templates/${templateId}`);
|
|
2843
|
-
}
|
|
2844
|
-
|
|
2845
|
-
// --- Auto-Calibration ----------------------------------
|
|
2846
|
-
|
|
2847
|
-
async autoCalibrate(options = {}) {
|
|
2848
|
-
return this._request('POST', '/api/scoring/calibrate', options);
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
|
|
2852
|
-
/**
|
|
2853
|
-
* Error thrown when guardMode is 'enforce' and guard blocks an action.
|
|
2854
|
-
*/
|
|
2855
|
-
class GuardBlockedError extends Error {
|
|
2856
|
-
/**
|
|
2857
|
-
* @param {Object} decision - Guard decision object
|
|
2858
|
-
*/
|
|
2859
|
-
constructor(decision) {
|
|
2860
|
-
const reasons = (decision.reasons || []).join('; ') || 'no reason';
|
|
2861
|
-
super(`Guard blocked action: ${decision.decision}. Reasons: ${reasons}`);
|
|
2862
|
-
this.name = 'GuardBlockedError';
|
|
2863
|
-
this.decision = decision.decision;
|
|
2864
|
-
this.reasons = decision.reasons || [];
|
|
2865
|
-
this.warnings = decision.warnings || [];
|
|
2866
|
-
this.matchedPolicies = decision.matched_policies || [];
|
|
2867
|
-
this.riskScore = decision.risk_score ?? null;
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
|
|
2871
|
-
/**
|
|
2872
|
-
* Error thrown when a human operator denies an action.
|
|
2873
|
-
*/
|
|
2874
|
-
class ApprovalDeniedError extends Error {
|
|
2875
|
-
constructor(message) {
|
|
2876
|
-
super(message);
|
|
2877
|
-
this.name = 'ApprovalDeniedError';
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
|
|
2881
|
-
// Backward compatibility alias (Legacy)
|
|
2882
|
-
const OpenClawAgent = DashClaw;
|
|
2883
|
-
|
|
2884
|
-
export default DashClaw;
|
|
2885
|
-
export { DashClaw, OpenClawAgent, GuardBlockedError, ApprovalDeniedError };
|
|
1
|
+
/**
|
|
2
|
+
* DashClaw SDK v2 (Stable Runtime API)
|
|
3
|
+
* Focused governance runtime client for AI agents.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class ApprovalDeniedError extends Error {
|
|
7
|
+
constructor(message, decision) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'ApprovalDeniedError';
|
|
10
|
+
this.decision = decision;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class GuardBlockedError extends Error {
|
|
15
|
+
constructor(decision) {
|
|
16
|
+
super(decision.reason || 'Action blocked by policy');
|
|
17
|
+
this.name = 'GuardBlockedError';
|
|
18
|
+
this.decision = decision;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class DashClaw {
|
|
23
|
+
/**
|
|
24
|
+
* @param {Object} options
|
|
25
|
+
* @param {string} options.baseUrl - DashClaw base URL
|
|
26
|
+
* @param {string} options.apiKey - API key for authentication
|
|
27
|
+
* @param {string} options.agentId - Unique identifier for this agent
|
|
28
|
+
*/
|
|
29
|
+
constructor({ baseUrl, apiKey, agentId }) {
|
|
30
|
+
if (!baseUrl) throw new Error('baseUrl is required');
|
|
31
|
+
if (!apiKey) throw new Error('apiKey is required');
|
|
32
|
+
if (!agentId) throw new Error('agentId is required');
|
|
33
|
+
|
|
34
|
+
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
35
|
+
this.apiKey = apiKey;
|
|
36
|
+
this.agentId = agentId;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async _request(path, method = 'GET', body = null, params = null) {
|
|
40
|
+
let url = `${this.baseUrl}${path}`;
|
|
41
|
+
if (params) {
|
|
42
|
+
const qs = new URLSearchParams(params).toString();
|
|
43
|
+
if (qs) url += `?${qs}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const headers = {
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
'x-api-key': this.apiKey
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const res = await fetch(url, {
|
|
52
|
+
method,
|
|
53
|
+
headers,
|
|
54
|
+
body: body ? JSON.stringify(body) : undefined
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const data = await res.json();
|
|
58
|
+
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
// Prioritize reason (from governance blocks) over generic error field
|
|
61
|
+
const errorMessage = data.reason || data.error || `Request failed with status ${res.status}`;
|
|
62
|
+
const err = new Error(errorMessage);
|
|
63
|
+
err.status = res.status;
|
|
64
|
+
err.details = data.details;
|
|
65
|
+
err.decision = data;
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return data;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* POST /api/guard — "Can I do X?"
|
|
74
|
+
* @param {Object} context
|
|
75
|
+
* @param {string} context.action - Action type (e.g. "deploy")
|
|
76
|
+
* @param {string} [context.intent] - What the action aims to do
|
|
77
|
+
* @param {number} [context.risk_score] - Risk score 0-100
|
|
78
|
+
* @returns {Promise<{decision: 'allow'|'block'|'require_approval', action_id: string, reason: string, signals: string[]}>}
|
|
79
|
+
*/
|
|
80
|
+
async guard(context) {
|
|
81
|
+
return this._request('/api/guard', 'POST', {
|
|
82
|
+
...context,
|
|
83
|
+
agent_id: context.agent_id || this.agentId,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* POST /api/actions — "I am attempting X."
|
|
89
|
+
* @param {Object} action
|
|
90
|
+
* @param {string} action.action_type - e.g. "deploy"
|
|
91
|
+
* @param {string} action.declared_goal - e.g. "deploy to production"
|
|
92
|
+
* @returns {Promise<{action: Object, action_id: string}>}
|
|
93
|
+
*/
|
|
94
|
+
async createAction(action) {
|
|
95
|
+
const res = await this._request('/api/actions', 'POST', {
|
|
96
|
+
...action,
|
|
97
|
+
agent_id: this.agentId,
|
|
98
|
+
});
|
|
99
|
+
return res;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* PATCH /api/actions/:id — "X finished with result Y."
|
|
104
|
+
* @param {string} actionId
|
|
105
|
+
* @param {Object} outcome
|
|
106
|
+
*/
|
|
107
|
+
async updateOutcome(actionId, outcome) {
|
|
108
|
+
return this._request(`/api/actions/${actionId}`, 'PATCH', {
|
|
109
|
+
...outcome,
|
|
110
|
+
timestamp_end: outcome.timestamp_end || new Date().toISOString()
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* POST /api/assumptions — "I believe Z is true while doing X."
|
|
116
|
+
* @param {Object} assumption
|
|
117
|
+
*/
|
|
118
|
+
async recordAssumption(assumption) {
|
|
119
|
+
return this._request('/api/assumptions', 'POST', assumption);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* GET /api/actions/:id — Polling helper for human approval.
|
|
124
|
+
*/
|
|
125
|
+
async waitForApproval(actionId, { timeout = 300000, interval = 5000 } = {}) {
|
|
126
|
+
const startTime = Date.now();
|
|
127
|
+
while (Date.now() - startTime < timeout) {
|
|
128
|
+
const { action } = await this._request(`/api/actions/${actionId}`, 'GET');
|
|
129
|
+
if (action.status === 'running' || action.status === 'completed') return action;
|
|
130
|
+
if (action.status === 'failed' || action.status === 'cancelled') {
|
|
131
|
+
throw new ApprovalDeniedError(action.error_message || 'Operator denied the action.', action.status);
|
|
132
|
+
}
|
|
133
|
+
await new Promise(r => setTimeout(r, interval));
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`Timed out waiting for approval of action ${actionId}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { DashClaw, ApprovalDeniedError, GuardBlockedError };
|