dashclaw 1.6.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -8
- package/dashclaw.js +395 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Full reference for the DashClaw SDK (Node.js). For Python, see the [Python SDK docs](../sdk-python/README.md).
|
|
4
4
|
|
|
5
|
-
Install, configure, and instrument your AI agents with
|
|
5
|
+
Install, configure, and instrument your AI agents with 60+ methods across action recording, behavior guard, context management, session handoffs, security scanning, and more.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -19,7 +19,8 @@ npm install dashclaw
|
|
|
19
19
|
import { DashClaw } from 'dashclaw';
|
|
20
20
|
|
|
21
21
|
const claw = new DashClaw({
|
|
22
|
-
baseUrl:
|
|
22
|
+
baseUrl: process.env.DASHCLAW_BASE_URL || 'http://localhost:3000',
|
|
23
|
+
// Use http://localhost:3000 for local, or https://your-app.vercel.app for cloud
|
|
23
24
|
apiKey: process.env.DASHCLAW_API_KEY,
|
|
24
25
|
agentId: 'my-agent',
|
|
25
26
|
agentName: 'My Agent',
|
|
@@ -52,31 +53,48 @@ await claw.updateOutcome(action_id, {
|
|
|
52
53
|
Create a DashClaw instance. Requires Node 18+ (native fetch).
|
|
53
54
|
|
|
54
55
|
```javascript
|
|
55
|
-
const claw = new DashClaw({
|
|
56
|
+
const claw = new DashClaw({
|
|
57
|
+
baseUrl,
|
|
58
|
+
apiKey,
|
|
59
|
+
agentId,
|
|
60
|
+
agentName,
|
|
61
|
+
swarmId,
|
|
62
|
+
guardMode,
|
|
63
|
+
guardCallback,
|
|
64
|
+
autoRecommend,
|
|
65
|
+
recommendationConfidenceMin,
|
|
66
|
+
recommendationCallback,
|
|
67
|
+
hitlMode,
|
|
68
|
+
});
|
|
56
69
|
```
|
|
57
70
|
|
|
58
71
|
### Parameters
|
|
59
72
|
| Parameter | Type | Required | Description |
|
|
60
73
|
|-----------|------|----------|-------------|
|
|
61
|
-
| baseUrl | string | Yes | DashClaw dashboard URL (e.g. "https://your-app.vercel.app") |
|
|
74
|
+
| baseUrl | string | Yes | DashClaw dashboard URL (e.g. "http://localhost:3000" or "https://your-app.vercel.app") |
|
|
62
75
|
| apiKey | string | Yes | API key for authentication (determines which org\'s data you access) |
|
|
63
76
|
| agentId | string | Yes | Unique identifier for this agent |
|
|
64
77
|
| agentName | string | No | Human-readable agent name |
|
|
65
78
|
| swarmId | string | No | Swarm/group identifier if part of a multi-agent system |
|
|
66
79
|
| guardMode | string | No | Auto guard check before createAction/track: "off" (default), "warn" (log + proceed), "enforce" (throw on block) |
|
|
67
80
|
| guardCallback | Function | No | Called with guard decision object when guardMode is active |
|
|
81
|
+
| autoRecommend | string | No | Recommendation auto-adapt mode: "off" (default), "warn" (record override), "enforce" (apply safe hints) |
|
|
82
|
+
| recommendationConfidenceMin | number | No | Min recommendation confidence required for auto-adapt in enforce mode (default 70) |
|
|
83
|
+
| recommendationCallback | Function | No | Called with recommendation adaptation details when autoRecommend is active |
|
|
68
84
|
| hitlMode | string | No | HITL behavior: "off" (default - return 202 immediately), "wait" (automatically block and poll until approved/denied) |
|
|
69
85
|
|
|
70
|
-
### Guard Mode
|
|
71
|
-
When
|
|
86
|
+
### Guard Mode, Auto-Recommend, and HITL
|
|
87
|
+
When enabled, every call to `createAction()` can run recommendation adaptation and guard checks before submission.
|
|
72
88
|
|
|
73
89
|
```javascript
|
|
74
90
|
import { DashClaw, GuardBlockedError, ApprovalDeniedError } from 'dashclaw';
|
|
75
91
|
|
|
76
92
|
const claw = new DashClaw({
|
|
77
|
-
baseUrl: '
|
|
93
|
+
baseUrl: 'http://localhost:3000',
|
|
78
94
|
apiKey: process.env.DASHCLAW_API_KEY,
|
|
79
95
|
agentId: 'my-agent',
|
|
96
|
+
autoRecommend: 'enforce', // apply safe recommendation hints
|
|
97
|
+
recommendationConfidenceMin: 80,
|
|
80
98
|
guardMode: 'enforce', // throws GuardBlockedError on block
|
|
81
99
|
hitlMode: 'wait', // poll until approved or throw ApprovalDeniedError
|
|
82
100
|
});
|
|
@@ -339,7 +357,7 @@ Get drift report for assumptions with risk scoring. Shows which assumptions are
|
|
|
339
357
|
|
|
340
358
|
## Signals
|
|
341
359
|
|
|
342
|
-
Automatic detection of problematic agent behavior. Seven signal types fire based on action patterns
|
|
360
|
+
Automatic detection of problematic agent behavior. Seven signal types fire based on action patterns - no configuration required.
|
|
343
361
|
|
|
344
362
|
### claw.getSignals()
|
|
345
363
|
Get current risk signals across all agents. Returns 7 signal types: autonomy_spike, high_impact_low_oversight, repeated_failures, stale_loop, assumption_drift, stale_assumption, and stale_running_action.
|
|
@@ -408,6 +426,65 @@ Record a decision for the learning database. Track what your agent decides and w
|
|
|
408
426
|
|
|
409
427
|
**Returns:** `Promise<{ decision: Object }>`
|
|
410
428
|
|
|
429
|
+
### claw.getRecommendations(filters?)
|
|
430
|
+
Get adaptive recommendations synthesized from scored historical episodes.
|
|
431
|
+
|
|
432
|
+
**Parameters:**
|
|
433
|
+
| Parameter | Type | Required | Description |
|
|
434
|
+
|-----------|------|----------|-------------|
|
|
435
|
+
| filters.action_type | string | No | Filter by action type |
|
|
436
|
+
| filters.agent_id | string | No | Override agent scope (defaults to SDK agent) |
|
|
437
|
+
| filters.include_inactive | boolean | No | Include disabled recommendations (admin/service only) |
|
|
438
|
+
| filters.track_events | boolean | No | Record fetched telemetry (default true) |
|
|
439
|
+
| filters.include_metrics | boolean | No | Include computed metrics in response |
|
|
440
|
+
| filters.lookback_days | number | No | Lookback window for include_metrics |
|
|
441
|
+
| filters.limit | number | No | Max results (default 50) |
|
|
442
|
+
|
|
443
|
+
**Returns:** `Promise<{ recommendations: Object[], metrics?: Object, total: number }>`
|
|
444
|
+
|
|
445
|
+
### claw.getRecommendationMetrics(filters?)
|
|
446
|
+
Get recommendation telemetry and effectiveness deltas.
|
|
447
|
+
|
|
448
|
+
**Parameters:**
|
|
449
|
+
| Parameter | Type | Required | Description |
|
|
450
|
+
|-----------|------|----------|-------------|
|
|
451
|
+
| filters.action_type | string | No | Filter by action type |
|
|
452
|
+
| filters.agent_id | string | No | Override agent scope (defaults to SDK agent) |
|
|
453
|
+
| filters.lookback_days | number | No | Lookback window (default 30) |
|
|
454
|
+
| filters.limit | number | No | Max recommendations to evaluate (default 100) |
|
|
455
|
+
| filters.include_inactive | boolean | No | Include disabled recommendations (admin/service only) |
|
|
456
|
+
|
|
457
|
+
**Returns:** `Promise<{ metrics: Object[], summary: Object, lookback_days: number }>`
|
|
458
|
+
|
|
459
|
+
### claw.recordRecommendationEvents(events)
|
|
460
|
+
Write recommendation telemetry events (single event or batch).
|
|
461
|
+
|
|
462
|
+
**Returns:** `Promise<{ created: Object[], created_count: number }>`
|
|
463
|
+
|
|
464
|
+
### claw.setRecommendationActive(recommendationId, active)
|
|
465
|
+
Enable or disable one recommendation.
|
|
466
|
+
|
|
467
|
+
**Returns:** `Promise<{ recommendation: Object }>`
|
|
468
|
+
|
|
469
|
+
### claw.rebuildRecommendations(options?)
|
|
470
|
+
Recompute recommendations from recent learning episodes.
|
|
471
|
+
|
|
472
|
+
**Parameters:**
|
|
473
|
+
| Parameter | Type | Required | Description |
|
|
474
|
+
|-----------|------|----------|-------------|
|
|
475
|
+
| options.action_type | string | No | Restrict rebuild to one action type |
|
|
476
|
+
| options.lookback_days | number | No | Episode history window (default 30) |
|
|
477
|
+
| options.min_samples | number | No | Minimum samples per recommendation (default 5) |
|
|
478
|
+
| options.episode_limit | number | No | Episode scan cap (default 5000) |
|
|
479
|
+
| options.action_id | string | No | Score this action before rebuilding |
|
|
480
|
+
|
|
481
|
+
**Returns:** `Promise<{ recommendations: Object[], total: number, episodes_scanned: number }>`
|
|
482
|
+
|
|
483
|
+
### claw.recommendAction(action)
|
|
484
|
+
Apply top recommendation hints to an action payload without mutating the original object.
|
|
485
|
+
|
|
486
|
+
**Returns:** `Promise<{ action: Object, recommendation: Object|null, adapted_fields: string[] }>`
|
|
487
|
+
|
|
411
488
|
### claw.createGoal(goal)
|
|
412
489
|
Create a goal in the goals tracker.
|
|
413
490
|
|
package/dashclaw.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Full-featured agent toolkit for the DashClaw platform.
|
|
4
4
|
* Zero-dependency ESM SDK — requires Node 18+ (native fetch).
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 60+ methods across 13+ categories:
|
|
7
7
|
* - Action Recording (7)
|
|
8
8
|
* - Loops & Assumptions (7)
|
|
9
9
|
* - Signals (1)
|
|
@@ -22,17 +22,33 @@
|
|
|
22
22
|
class DashClaw {
|
|
23
23
|
/**
|
|
24
24
|
* @param {Object} options
|
|
25
|
-
* @param {string} options.baseUrl - DashClaw base URL (e.g. "https://your-app.vercel.app")
|
|
25
|
+
* @param {string} options.baseUrl - DashClaw base URL (e.g. "http://localhost:3000" or "https://your-app.vercel.app")
|
|
26
26
|
* @param {string} options.apiKey - API key for authentication (determines which org's data you access)
|
|
27
27
|
* @param {string} options.agentId - Unique identifier for this agent
|
|
28
28
|
* @param {string} [options.agentName] - Human-readable agent name
|
|
29
29
|
* @param {string} [options.swarmId] - Swarm/group identifier if part of a multi-agent system
|
|
30
30
|
* @param {string} [options.guardMode='off'] - Auto guard check before createAction: 'off' | 'warn' | 'enforce'
|
|
31
31
|
* @param {Function} [options.guardCallback] - Called with guard decision object when guardMode is active
|
|
32
|
+
* @param {string} [options.autoRecommend='off'] - Recommendation mode: 'off' | 'warn' | 'enforce'
|
|
33
|
+
* @param {number} [options.recommendationConfidenceMin=70] - Minimum recommendation confidence to auto-apply in enforce mode
|
|
34
|
+
* @param {Function} [options.recommendationCallback] - Called with recommendation adaptation details when autoRecommend is active
|
|
32
35
|
* @param {string} [options.hitlMode='off'] - How to handle pending approvals: 'off' (return immediately) | 'wait' (block and poll)
|
|
33
36
|
* @param {CryptoKey} [options.privateKey] - Web Crypto API Private Key for signing actions
|
|
34
37
|
*/
|
|
35
|
-
constructor({
|
|
38
|
+
constructor({
|
|
39
|
+
baseUrl,
|
|
40
|
+
apiKey,
|
|
41
|
+
agentId,
|
|
42
|
+
agentName,
|
|
43
|
+
swarmId,
|
|
44
|
+
guardMode,
|
|
45
|
+
guardCallback,
|
|
46
|
+
autoRecommend,
|
|
47
|
+
recommendationConfidenceMin,
|
|
48
|
+
recommendationCallback,
|
|
49
|
+
hitlMode,
|
|
50
|
+
privateKey
|
|
51
|
+
}) {
|
|
36
52
|
if (!baseUrl) throw new Error('baseUrl is required');
|
|
37
53
|
if (!apiKey) throw new Error('apiKey is required');
|
|
38
54
|
if (!agentId) throw new Error('agentId is required');
|
|
@@ -41,6 +57,9 @@ class DashClaw {
|
|
|
41
57
|
if (guardMode && !validModes.includes(guardMode)) {
|
|
42
58
|
throw new Error(`guardMode must be one of: ${validModes.join(', ')}`);
|
|
43
59
|
}
|
|
60
|
+
if (autoRecommend && !validModes.includes(autoRecommend)) {
|
|
61
|
+
throw new Error(`autoRecommend must be one of: ${validModes.join(', ')}`);
|
|
62
|
+
}
|
|
44
63
|
|
|
45
64
|
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
46
65
|
this.apiKey = apiKey;
|
|
@@ -49,6 +68,12 @@ class DashClaw {
|
|
|
49
68
|
this.swarmId = swarmId || null;
|
|
50
69
|
this.guardMode = guardMode || 'off';
|
|
51
70
|
this.guardCallback = guardCallback || null;
|
|
71
|
+
this.autoRecommend = autoRecommend || 'off';
|
|
72
|
+
const parsedConfidenceMin = Number(recommendationConfidenceMin);
|
|
73
|
+
this.recommendationConfidenceMin = Number.isFinite(parsedConfidenceMin)
|
|
74
|
+
? Math.max(0, Math.min(parsedConfidenceMin, 100))
|
|
75
|
+
: 70;
|
|
76
|
+
this.recommendationCallback = recommendationCallback || null;
|
|
52
77
|
this.hitlMode = hitlMode || 'off';
|
|
53
78
|
this.privateKey = privateKey || null;
|
|
54
79
|
|
|
@@ -100,6 +125,66 @@ class DashClaw {
|
|
|
100
125
|
return data;
|
|
101
126
|
}
|
|
102
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Create an agent pairing request (returns a link the user can click to approve).
|
|
130
|
+
*
|
|
131
|
+
* @param {Object} options
|
|
132
|
+
* @param {string} options.publicKeyPem - PEM public key (SPKI) to register for this agent.
|
|
133
|
+
* @param {string} [options.algorithm='RSASSA-PKCS1-v1_5']
|
|
134
|
+
* @param {string} [options.agentName]
|
|
135
|
+
* @returns {Promise<{pairing: Object, pairing_url: string}>}
|
|
136
|
+
*/
|
|
137
|
+
async createPairing({ publicKeyPem, algorithm = 'RSASSA-PKCS1-v1_5', agentName } = {}) {
|
|
138
|
+
if (!publicKeyPem) throw new Error('publicKeyPem is required');
|
|
139
|
+
return this._request('/api/pairings', 'POST', {
|
|
140
|
+
agent_id: this.agentId,
|
|
141
|
+
agent_name: agentName || this.agentName,
|
|
142
|
+
public_key: publicKeyPem,
|
|
143
|
+
algorithm,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async _derivePublicKeyPemFromPrivateJwk(privateJwk) {
|
|
148
|
+
// Node-only helper (works in the typical agent runtime).
|
|
149
|
+
const { createPrivateKey, createPublicKey } = await import('node:crypto');
|
|
150
|
+
const priv = createPrivateKey({ key: privateJwk, format: 'jwk' });
|
|
151
|
+
const pub = createPublicKey(priv);
|
|
152
|
+
return pub.export({ type: 'spki', format: 'pem' });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Convenience: derive public PEM from a private JWK and create a pairing request.
|
|
157
|
+
* @param {Object} privateJwk
|
|
158
|
+
* @param {Object} [options]
|
|
159
|
+
* @param {string} [options.agentName]
|
|
160
|
+
*/
|
|
161
|
+
async createPairingFromPrivateJwk(privateJwk, { agentName } = {}) {
|
|
162
|
+
if (!privateJwk) throw new Error('privateJwk is required');
|
|
163
|
+
const publicKeyPem = await this._derivePublicKeyPemFromPrivateJwk(privateJwk);
|
|
164
|
+
return this.createPairing({ publicKeyPem, agentName });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Poll a pairing until it is approved/expired.
|
|
169
|
+
* @param {string} pairingId
|
|
170
|
+
* @param {Object} [options]
|
|
171
|
+
* @param {number} [options.timeout=300000] - Max wait time (5 min)
|
|
172
|
+
* @param {number} [options.interval=2000] - Poll interval
|
|
173
|
+
* @returns {Promise<Object>} pairing object
|
|
174
|
+
*/
|
|
175
|
+
async waitForPairing(pairingId, { timeout = 300000, interval = 2000 } = {}) {
|
|
176
|
+
const start = Date.now();
|
|
177
|
+
while (Date.now() - start < timeout) {
|
|
178
|
+
const res = await this._request(`/api/pairings/${encodeURIComponent(pairingId)}`, 'GET');
|
|
179
|
+
const pairing = res.pairing;
|
|
180
|
+
if (!pairing) throw new Error('Pairing response missing pairing');
|
|
181
|
+
if (pairing.status === 'approved') return pairing;
|
|
182
|
+
if (pairing.status === 'expired') throw new Error('Pairing expired');
|
|
183
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
184
|
+
}
|
|
185
|
+
throw new Error('Timed out waiting for pairing approval');
|
|
186
|
+
}
|
|
187
|
+
|
|
103
188
|
/**
|
|
104
189
|
* Internal: check guard policies before action creation.
|
|
105
190
|
* Only active when guardMode is 'warn' or 'enforce'.
|
|
@@ -143,6 +228,168 @@ class DashClaw {
|
|
|
143
228
|
}
|
|
144
229
|
}
|
|
145
230
|
|
|
231
|
+
_canonicalJsonStringify(value) {
|
|
232
|
+
const canonicalize = (v) => {
|
|
233
|
+
if (v === null) return 'null';
|
|
234
|
+
|
|
235
|
+
const t = typeof v;
|
|
236
|
+
if (t === 'string' || t === 'number' || t === 'boolean') return JSON.stringify(v);
|
|
237
|
+
|
|
238
|
+
if (t === 'undefined') return 'null';
|
|
239
|
+
|
|
240
|
+
if (Array.isArray(v)) {
|
|
241
|
+
return `[${v.map((x) => (typeof x === 'undefined' ? 'null' : canonicalize(x))).join(',')}]`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (t === 'object') {
|
|
245
|
+
const keys = Object.keys(v)
|
|
246
|
+
.filter((k) => typeof v[k] !== 'undefined')
|
|
247
|
+
.sort();
|
|
248
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalize(v[k])}`).join(',')}}`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return 'null';
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
return canonicalize(value);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
_toBase64(bytes) {
|
|
258
|
+
if (typeof btoa === 'function') {
|
|
259
|
+
return btoa(String.fromCharCode(...bytes));
|
|
260
|
+
}
|
|
261
|
+
return Buffer.from(bytes).toString('base64');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
_isRestrictiveDecision(decision) {
|
|
265
|
+
return decision?.decision === 'block' || decision?.decision === 'require_approval';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
_buildGuardContext(actionDef) {
|
|
269
|
+
return {
|
|
270
|
+
action_type: actionDef.action_type,
|
|
271
|
+
risk_score: actionDef.risk_score,
|
|
272
|
+
systems_touched: actionDef.systems_touched,
|
|
273
|
+
reversible: actionDef.reversible,
|
|
274
|
+
declared_goal: actionDef.declared_goal,
|
|
275
|
+
agent_id: this.agentId,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async _reportRecommendationEvent(event) {
|
|
280
|
+
try {
|
|
281
|
+
await this._request('/api/learning/recommendations/events', 'POST', {
|
|
282
|
+
...event,
|
|
283
|
+
agent_id: event.agent_id || this.agentId,
|
|
284
|
+
});
|
|
285
|
+
} catch {
|
|
286
|
+
// Telemetry should never break action execution
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async _autoRecommend(actionDef) {
|
|
291
|
+
if (this.autoRecommend === 'off' || !actionDef?.action_type) {
|
|
292
|
+
return { action: actionDef, recommendation: null, adapted_fields: [] };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
let result;
|
|
296
|
+
try {
|
|
297
|
+
result = await this.recommendAction(actionDef);
|
|
298
|
+
} catch (err) {
|
|
299
|
+
console.warn(`[DashClaw] Recommendation fetch failed (proceeding): ${err.message}`);
|
|
300
|
+
return { action: actionDef, recommendation: null, adapted_fields: [] };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (this.recommendationCallback) {
|
|
304
|
+
try { this.recommendationCallback(result); } catch { /* ignore callback errors */ }
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const recommendation = result.recommendation || null;
|
|
308
|
+
if (!recommendation) return result;
|
|
309
|
+
|
|
310
|
+
const confidence = Number(recommendation.confidence || 0);
|
|
311
|
+
if (confidence < this.recommendationConfidenceMin) {
|
|
312
|
+
const override_reason = `confidence_below_threshold:${confidence}<${this.recommendationConfidenceMin}`;
|
|
313
|
+
await this._reportRecommendationEvent({
|
|
314
|
+
recommendation_id: recommendation.id,
|
|
315
|
+
event_type: 'overridden',
|
|
316
|
+
details: { action_type: actionDef.action_type, reason: override_reason },
|
|
317
|
+
});
|
|
318
|
+
return {
|
|
319
|
+
...result,
|
|
320
|
+
action: {
|
|
321
|
+
...actionDef,
|
|
322
|
+
recommendation_id: recommendation.id,
|
|
323
|
+
recommendation_applied: false,
|
|
324
|
+
recommendation_override_reason: override_reason,
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let guardDecision = null;
|
|
330
|
+
try {
|
|
331
|
+
guardDecision = await this.guard(this._buildGuardContext(result.action || actionDef));
|
|
332
|
+
} catch (err) {
|
|
333
|
+
console.warn(`[DashClaw] Recommendation guard probe failed: ${err.message}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (this._isRestrictiveDecision(guardDecision)) {
|
|
337
|
+
const override_reason = `guard_restrictive:${guardDecision.decision}`;
|
|
338
|
+
await this._reportRecommendationEvent({
|
|
339
|
+
recommendation_id: recommendation.id,
|
|
340
|
+
event_type: 'overridden',
|
|
341
|
+
details: { action_type: actionDef.action_type, reason: override_reason },
|
|
342
|
+
});
|
|
343
|
+
return {
|
|
344
|
+
...result,
|
|
345
|
+
action: {
|
|
346
|
+
...actionDef,
|
|
347
|
+
recommendation_id: recommendation.id,
|
|
348
|
+
recommendation_applied: false,
|
|
349
|
+
recommendation_override_reason: override_reason,
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (this.autoRecommend === 'warn') {
|
|
355
|
+
const override_reason = 'warn_mode_no_autoadapt';
|
|
356
|
+
await this._reportRecommendationEvent({
|
|
357
|
+
recommendation_id: recommendation.id,
|
|
358
|
+
event_type: 'overridden',
|
|
359
|
+
details: { action_type: actionDef.action_type, reason: override_reason },
|
|
360
|
+
});
|
|
361
|
+
return {
|
|
362
|
+
...result,
|
|
363
|
+
action: {
|
|
364
|
+
...actionDef,
|
|
365
|
+
recommendation_id: recommendation.id,
|
|
366
|
+
recommendation_applied: false,
|
|
367
|
+
recommendation_override_reason: override_reason,
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
await this._reportRecommendationEvent({
|
|
373
|
+
recommendation_id: recommendation.id,
|
|
374
|
+
event_type: 'applied',
|
|
375
|
+
details: {
|
|
376
|
+
action_type: actionDef.action_type,
|
|
377
|
+
adapted_fields: result.adapted_fields || [],
|
|
378
|
+
confidence,
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
...result,
|
|
384
|
+
action: {
|
|
385
|
+
...(result.action || actionDef),
|
|
386
|
+
recommendation_id: recommendation.id,
|
|
387
|
+
recommendation_applied: true,
|
|
388
|
+
recommendation_override_reason: null,
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
146
393
|
// ══════════════════════════════════════════════
|
|
147
394
|
// Category 1: Action Recording (6 methods)
|
|
148
395
|
// ══════════════════════════════════════════════
|
|
@@ -165,21 +412,24 @@ class DashClaw {
|
|
|
165
412
|
* @returns {Promise<{action: Object, action_id: string}>}
|
|
166
413
|
*/
|
|
167
414
|
async createAction(action) {
|
|
168
|
-
await this.
|
|
415
|
+
const recommendationResult = await this._autoRecommend(action);
|
|
416
|
+
const finalAction = recommendationResult.action || action;
|
|
417
|
+
|
|
418
|
+
await this._guardCheck(finalAction);
|
|
169
419
|
if (this._pendingKeyImport) await this._pendingKeyImport;
|
|
170
420
|
|
|
171
421
|
const payload = {
|
|
172
422
|
agent_id: this.agentId,
|
|
173
423
|
agent_name: this.agentName,
|
|
174
424
|
swarm_id: this.swarmId,
|
|
175
|
-
...
|
|
425
|
+
...finalAction
|
|
176
426
|
};
|
|
177
427
|
|
|
178
428
|
let signature = null;
|
|
179
429
|
if (this.privateKey) {
|
|
180
430
|
try {
|
|
181
431
|
const encoder = new TextEncoder();
|
|
182
|
-
const data = encoder.encode(
|
|
432
|
+
const data = encoder.encode(this._canonicalJsonStringify(payload));
|
|
183
433
|
// Use global crypto or fallback to node:crypto
|
|
184
434
|
const cryptoSubtle = globalThis.crypto?.subtle || (await import('node:crypto')).webcrypto.subtle;
|
|
185
435
|
|
|
@@ -189,7 +439,7 @@ class DashClaw {
|
|
|
189
439
|
data
|
|
190
440
|
);
|
|
191
441
|
// Base64 encode signature
|
|
192
|
-
signature =
|
|
442
|
+
signature = this._toBase64(new Uint8Array(sigBuffer));
|
|
193
443
|
} catch (err) {
|
|
194
444
|
throw new Error(`Failed to sign action: ${err.message}`);
|
|
195
445
|
}
|
|
@@ -482,6 +732,144 @@ class DashClaw {
|
|
|
482
732
|
});
|
|
483
733
|
}
|
|
484
734
|
|
|
735
|
+
/**
|
|
736
|
+
* Get adaptive learning recommendations derived from prior episodes.
|
|
737
|
+
* @param {Object} [filters]
|
|
738
|
+
* @param {string} [filters.action_type] - Filter by action type
|
|
739
|
+
* @param {string} [filters.agent_id] - Override agent_id (defaults to SDK agent)
|
|
740
|
+
* @param {boolean} [filters.include_inactive] - Include disabled recommendations (admin/service only)
|
|
741
|
+
* @param {boolean} [filters.track_events=true] - Record recommendation fetched telemetry
|
|
742
|
+
* @param {boolean} [filters.include_metrics] - Include computed metrics in the response payload
|
|
743
|
+
* @param {number} [filters.limit=50] - Max recommendations to return
|
|
744
|
+
* @param {number} [filters.lookback_days=30] - Lookback days used when include_metrics=true
|
|
745
|
+
* @returns {Promise<{recommendations: Object[], metrics?: Object, total: number, lastUpdated: string}>}
|
|
746
|
+
*/
|
|
747
|
+
async getRecommendations(filters = {}) {
|
|
748
|
+
const params = new URLSearchParams({
|
|
749
|
+
agent_id: filters.agent_id || this.agentId,
|
|
750
|
+
});
|
|
751
|
+
if (filters.action_type) params.set('action_type', filters.action_type);
|
|
752
|
+
if (filters.limit) params.set('limit', String(filters.limit));
|
|
753
|
+
if (filters.include_inactive) params.set('include_inactive', 'true');
|
|
754
|
+
if (filters.track_events !== false) params.set('track_events', 'true');
|
|
755
|
+
if (filters.include_metrics) params.set('include_metrics', 'true');
|
|
756
|
+
if (filters.lookback_days) params.set('lookback_days', String(filters.lookback_days));
|
|
757
|
+
return this._request(`/api/learning/recommendations?${params}`, 'GET');
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Get recommendation effectiveness metrics and telemetry aggregates.
|
|
762
|
+
* @param {Object} [filters]
|
|
763
|
+
* @param {string} [filters.action_type] - Filter by action type
|
|
764
|
+
* @param {string} [filters.agent_id] - Override agent_id (defaults to SDK agent)
|
|
765
|
+
* @param {number} [filters.lookback_days=30] - Lookback window for episodes/events
|
|
766
|
+
* @param {number} [filters.limit=100] - Max recommendations considered
|
|
767
|
+
* @param {boolean} [filters.include_inactive] - Include inactive recommendations (admin/service only)
|
|
768
|
+
* @returns {Promise<{metrics: Object[], summary: Object, lookback_days: number, lastUpdated: string}>}
|
|
769
|
+
*/
|
|
770
|
+
async getRecommendationMetrics(filters = {}) {
|
|
771
|
+
const params = new URLSearchParams({
|
|
772
|
+
agent_id: filters.agent_id || this.agentId,
|
|
773
|
+
});
|
|
774
|
+
if (filters.action_type) params.set('action_type', filters.action_type);
|
|
775
|
+
if (filters.lookback_days) params.set('lookback_days', String(filters.lookback_days));
|
|
776
|
+
if (filters.limit) params.set('limit', String(filters.limit));
|
|
777
|
+
if (filters.include_inactive) params.set('include_inactive', 'true');
|
|
778
|
+
return this._request(`/api/learning/recommendations/metrics?${params}`, 'GET');
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Record recommendation telemetry events (single event or batch).
|
|
783
|
+
* @param {Object|Object[]} events
|
|
784
|
+
* @returns {Promise<{created: Object[], created_count: number}>}
|
|
785
|
+
*/
|
|
786
|
+
async recordRecommendationEvents(events) {
|
|
787
|
+
if (Array.isArray(events)) {
|
|
788
|
+
return this._request('/api/learning/recommendations/events', 'POST', { events });
|
|
789
|
+
}
|
|
790
|
+
return this._request('/api/learning/recommendations/events', 'POST', events || {});
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Enable or disable a recommendation.
|
|
795
|
+
* @param {string} recommendationId - Recommendation ID
|
|
796
|
+
* @param {boolean} active - Desired active state
|
|
797
|
+
* @returns {Promise<{recommendation: Object}>}
|
|
798
|
+
*/
|
|
799
|
+
async setRecommendationActive(recommendationId, active) {
|
|
800
|
+
return this._request(`/api/learning/recommendations/${recommendationId}`, 'PATCH', { active: !!active });
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Rebuild recommendations from scored learning episodes.
|
|
805
|
+
* @param {Object} [options]
|
|
806
|
+
* @param {string} [options.action_type] - Scope rebuild to one action type
|
|
807
|
+
* @param {string} [options.agent_id] - Override agent_id (defaults to SDK agent)
|
|
808
|
+
* @param {number} [options.lookback_days=30] - Days of episode history to analyze
|
|
809
|
+
* @param {number} [options.min_samples=5] - Minimum episodes required per recommendation
|
|
810
|
+
* @param {number} [options.episode_limit=5000] - Episode scan cap
|
|
811
|
+
* @param {string} [options.action_id] - Optionally score this action before rebuild
|
|
812
|
+
* @returns {Promise<{recommendations: Object[], total: number, episodes_scanned: number}>}
|
|
813
|
+
*/
|
|
814
|
+
async rebuildRecommendations(options = {}) {
|
|
815
|
+
return this._request('/api/learning/recommendations', 'POST', {
|
|
816
|
+
agent_id: options.agent_id || this.agentId,
|
|
817
|
+
action_type: options.action_type,
|
|
818
|
+
lookback_days: options.lookback_days,
|
|
819
|
+
min_samples: options.min_samples,
|
|
820
|
+
episode_limit: options.episode_limit,
|
|
821
|
+
action_id: options.action_id,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Apply top recommendation hints to an action definition (non-destructive).
|
|
827
|
+
* @param {Object} action - Action payload compatible with createAction()
|
|
828
|
+
* @returns {Promise<{action: Object, recommendation: Object|null, adapted_fields: string[]}>}
|
|
829
|
+
*/
|
|
830
|
+
async recommendAction(action) {
|
|
831
|
+
if (!action?.action_type) {
|
|
832
|
+
return { action, recommendation: null, adapted_fields: [] };
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const response = await this.getRecommendations({ action_type: action.action_type, limit: 1 });
|
|
836
|
+
const recommendation = response.recommendations?.[0] || null;
|
|
837
|
+
if (!recommendation) {
|
|
838
|
+
return { action, recommendation: null, adapted_fields: [] };
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const adapted = { ...action };
|
|
842
|
+
const adaptedFields = [];
|
|
843
|
+
const hints = recommendation.hints || {};
|
|
844
|
+
|
|
845
|
+
if (
|
|
846
|
+
typeof hints.preferred_risk_cap === 'number' &&
|
|
847
|
+
(adapted.risk_score === undefined || adapted.risk_score > hints.preferred_risk_cap)
|
|
848
|
+
) {
|
|
849
|
+
adapted.risk_score = hints.preferred_risk_cap;
|
|
850
|
+
adaptedFields.push('risk_score');
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
if (hints.prefer_reversible === true && adapted.reversible === undefined) {
|
|
854
|
+
adapted.reversible = true;
|
|
855
|
+
adaptedFields.push('reversible');
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (
|
|
859
|
+
typeof hints.confidence_floor === 'number' &&
|
|
860
|
+
(adapted.confidence === undefined || adapted.confidence < hints.confidence_floor)
|
|
861
|
+
) {
|
|
862
|
+
adapted.confidence = hints.confidence_floor;
|
|
863
|
+
adaptedFields.push('confidence');
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return {
|
|
867
|
+
action: adapted,
|
|
868
|
+
recommendation,
|
|
869
|
+
adapted_fields: adaptedFields,
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
485
873
|
/**
|
|
486
874
|
* Create a goal.
|
|
487
875
|
* @param {Object} goal
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dashclaw",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Full-featured agent toolkit for the DashClaw platform.
|
|
3
|
+
"version": "1.7.1",
|
|
4
|
+
"description": "Full-featured agent toolkit for the DashClaw platform. 60+ methods for action recording, context management, session handoffs, security scanning, behavior guard, bulk sync, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|