dashclaw 2.10.0 → 2.11.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 +211 -9
- package/dashclaw.js +192 -22
- package/legacy/dashclaw-v1.js +49 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# DashClaw SDK (v2.
|
|
1
|
+
# DashClaw SDK (v2.11.1)
|
|
2
2
|
|
|
3
3
|
**Minimal governance runtime for AI agents.**
|
|
4
4
|
|
|
@@ -70,16 +70,44 @@ claw.update_outcome(action_id, status="completed")
|
|
|
70
70
|
|
|
71
71
|
---
|
|
72
72
|
|
|
73
|
-
## SDK
|
|
73
|
+
## SDK Tiers
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
DashClaw currently exposes a canonical Node SDK surface plus a legacy compatibility layer:
|
|
76
|
+
|
|
77
|
+
| | Node SDK | Python SDK |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| **Focus** | Canonical product surface for new work | Broader current surface |
|
|
80
|
+
| **Methods** | Core runtime + execution surfaces | Broad platform surface |
|
|
81
|
+
| **Core governance** | ✅ | ✅ |
|
|
82
|
+
| **Scoring profiles** | ✅ | ✅ |
|
|
83
|
+
| **Learning loop** | ✅ | ✅ |
|
|
84
|
+
| **Framework integrations** | — | LangChain, CrewAI, AutoGen, Claude Managed Agents |
|
|
85
|
+
| **Compliance engine** | — | ✅ |
|
|
86
|
+
| **Execution graphs** | — | ✅ |
|
|
87
|
+
| **Webhooks management** | — | ✅ |
|
|
88
|
+
|
|
89
|
+
**Node** is designed for most agents — fast, minimal, covers the governance loop and common workflows. **Python** is the enterprise/power-user surface with compliance reporting, execution graph traversal, and framework-native integrations.
|
|
90
|
+
|
|
91
|
+
**Policy:** new product work should target the main `dashclaw` client first. `dashclaw/legacy` exists for compatibility with older integrations and older method shapes.
|
|
92
|
+
|
|
93
|
+
See:
|
|
94
|
+
|
|
95
|
+
- [SDK Consolidation RFC](../docs/rfcs/2026-04-07-sdk-consolidation.md)
|
|
96
|
+
- [SDK Migration Matrix](../docs/planning/2026-04-07-sdk-migration-matrix.md)
|
|
97
|
+
- [SDK Parity Matrix](../docs/sdk-parity.md)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## SDK Surface Area (v2.11.1)
|
|
102
|
+
|
|
103
|
+
The v2 SDK exposes the stable governance runtime plus promoted execution domains in the canonical Node client:
|
|
76
104
|
|
|
77
105
|
### Core Runtime
|
|
78
106
|
- `guard(context)` -- Policy evaluation ("Can I do X?"). Returns `risk_score` (server-computed) and `agent_risk_score` (raw agent value)
|
|
79
107
|
- `createAction(action)` -- Lifecycle tracking ("I am doing X")
|
|
80
108
|
- `updateOutcome(id, outcome)` -- Result recording ("X finished with Y")
|
|
81
109
|
- `recordAssumption(assumption)` -- Integrity tracking ("I believe Z while doing X")
|
|
82
|
-
- `waitForApproval(id)` --
|
|
110
|
+
- `waitForApproval(id)` -- Real-time SSE listener for human-in-the-loop approvals (automatic polling fallback)
|
|
83
111
|
- `approveAction(id, decision, reasoning?)` -- Submit approval decisions from code
|
|
84
112
|
- `getPendingApprovals()` -- List actions awaiting human review
|
|
85
113
|
|
|
@@ -89,7 +117,7 @@ The v2 SDK exposes **67 methods** optimized for stability and zero-overhead gove
|
|
|
89
117
|
- `getSignals()` -- Get current risk signals across all agents.
|
|
90
118
|
|
|
91
119
|
### Swarm & Connectivity
|
|
92
|
-
- `heartbeat(status, metadata)` -- Report agent presence and health.
|
|
120
|
+
- `heartbeat(status, metadata)` -- Report agent presence and health. **As of DashClaw 2.13.0, heartbeats are implicit on `createAction()` — you only need this if you want to report presence without recording an action.**
|
|
93
121
|
- `reportConnections(connections)` -- Report active provider connections.
|
|
94
122
|
|
|
95
123
|
### Learning & Optimization
|
|
@@ -360,6 +388,26 @@ dashclaw deny <actionId> # deny a specific action
|
|
|
360
388
|
|
|
361
389
|
When an agent calls `waitForApproval()`, it prints the action ID and replay link to stdout. Approve from any terminal or the dashboard, and the agent unblocks instantly.
|
|
362
390
|
|
|
391
|
+
## MCP Server (Zero-Code Integration)
|
|
392
|
+
|
|
393
|
+
If your agent supports MCP (Claude Code, Claude Desktop, Managed Agents), you can skip the SDK entirely:
|
|
394
|
+
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"mcpServers": {
|
|
398
|
+
"dashclaw": {
|
|
399
|
+
"command": "npx",
|
|
400
|
+
"args": ["@dashclaw/mcp-server"],
|
|
401
|
+
"env": { "DASHCLAW_URL": "...", "DASHCLAW_API_KEY": "oc_live_..." }
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
The MCP server exposes the same governance surface as the SDK (guard, record, invoke, wait for approval) plus discovery (capabilities, policies) and session lifecycle.
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
363
411
|
## Claude Code Hooks
|
|
364
412
|
|
|
365
413
|
Govern Claude Code tool calls without any SDK instrumentation. Copy two files from the `hooks/` directory in the repo into your `.claude/hooks/` folder:
|
|
@@ -376,7 +424,9 @@ Then merge the hooks block from `hooks/settings.json` into your `.claude/setting
|
|
|
376
424
|
|
|
377
425
|
## Legacy SDK (v1)
|
|
378
426
|
|
|
379
|
-
|
|
427
|
+
`dashclaw/legacy` is a compatibility layer for older integrations. It is not the preferred target for new feature design.
|
|
428
|
+
|
|
429
|
+
Use it only when you need methods that have not yet been promoted into the canonical SDK surface.
|
|
380
430
|
|
|
381
431
|
```javascript
|
|
382
432
|
// v1 legacy import
|
|
@@ -385,6 +435,19 @@ import { DashClaw } from 'dashclaw/legacy';
|
|
|
385
435
|
|
|
386
436
|
Methods moved to v1 only: `createWebhook`, `getActivityLogs`, `mapCompliance`, `getProofReport`.
|
|
387
437
|
|
|
438
|
+
Legacy also exposes flat compatibility wrappers for the capability runtime routes:
|
|
439
|
+
|
|
440
|
+
- `claw.listCapabilities(...)`
|
|
441
|
+
- `claw.createCapability(...)`
|
|
442
|
+
- `claw.getCapability(...)`
|
|
443
|
+
- `claw.updateCapability(...)`
|
|
444
|
+
- `claw.invokeCapability(...)`
|
|
445
|
+
- `claw.testCapability(...)`
|
|
446
|
+
- `claw.getCapabilityHealth(...)`
|
|
447
|
+
- `claw.listCapabilityHealth(...)`
|
|
448
|
+
|
|
449
|
+
Those wrappers exist to keep older integrations working. New product work should still target `claw.execution.capabilities.*` on the main SDK first.
|
|
450
|
+
|
|
388
451
|
---
|
|
389
452
|
|
|
390
453
|
## Execution Studio
|
|
@@ -427,6 +490,60 @@ await claw.duplicateWorkflowTemplate(templateId);
|
|
|
427
490
|
// If the template links a model_strategy_id, the resolved config is snapshotted.
|
|
428
491
|
const { launch } = await claw.launchWorkflowTemplate(templateId, { agent_id: 'deploy-bot' });
|
|
429
492
|
console.log(launch.action_id); // act_... — view it in /decisions/<action_id>
|
|
493
|
+
|
|
494
|
+
// List past runs for a template (HTTP only — no SDK wrapper yet)
|
|
495
|
+
const runs = await fetch(`${baseUrl}/api/workflows/templates/${templateId}/runs?limit=10`, {
|
|
496
|
+
headers: { 'x-api-key': apiKey },
|
|
497
|
+
}).then(r => r.json());
|
|
498
|
+
|
|
499
|
+
// Get full run detail with step inputs/outputs
|
|
500
|
+
const run = await fetch(`${baseUrl}/api/workflows/templates/${templateId}/runs/${runActionId}`, {
|
|
501
|
+
headers: { 'x-api-key': apiKey },
|
|
502
|
+
}).then(r => r.json());
|
|
503
|
+
// run.steps[].input / run.steps[].output contain full JSON (no truncation)
|
|
504
|
+
|
|
505
|
+
// Resume a failed run from the last completed checkpoint
|
|
506
|
+
const resumed = await fetch(`${baseUrl}/api/workflows/templates/${templateId}/runs/${runActionId}/resume`, {
|
|
507
|
+
method: 'POST',
|
|
508
|
+
headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
|
|
509
|
+
body: JSON.stringify({}),
|
|
510
|
+
}).then(r => r.json());
|
|
511
|
+
// resumed.action_id is the new run; reused steps have status='reused'
|
|
512
|
+
|
|
513
|
+
// Cancel a running workflow
|
|
514
|
+
await fetch(`${baseUrl}/api/workflows/templates/${templateId}/runs/${runActionId}/cancel`, {
|
|
515
|
+
method: 'POST',
|
|
516
|
+
headers: { 'x-api-key': apiKey },
|
|
517
|
+
});
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### Artifacts
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
// List artifacts (optionally filter by action, step, agent, type)
|
|
524
|
+
const { artifacts } = await fetch(`${baseUrl}/api/artifacts?action_id=${actionId}`, {
|
|
525
|
+
headers: { 'x-api-key': apiKey },
|
|
526
|
+
}).then(r => r.json());
|
|
527
|
+
|
|
528
|
+
// Create an artifact
|
|
529
|
+
const { artifact } = await fetch(`${baseUrl}/api/artifacts`, {
|
|
530
|
+
method: 'POST',
|
|
531
|
+
headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
|
|
532
|
+
body: JSON.stringify({
|
|
533
|
+
artifact_type: 'json',
|
|
534
|
+
name: 'Analysis results',
|
|
535
|
+
content_json: { findings: ['...'] },
|
|
536
|
+
source_action_id: actionId,
|
|
537
|
+
}),
|
|
538
|
+
}).then(r => r.json());
|
|
539
|
+
|
|
540
|
+
// Generate an evidence bundle for a governed action
|
|
541
|
+
const bundle = await fetch(`${baseUrl}/api/artifacts/evidence-bundle`, {
|
|
542
|
+
method: 'POST',
|
|
543
|
+
headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
|
|
544
|
+
body: JSON.stringify({ action_id: actionId }),
|
|
545
|
+
}).then(r => r.json());
|
|
546
|
+
// bundle.action + bundle.steps + bundle.artifacts
|
|
430
547
|
```
|
|
431
548
|
|
|
432
549
|
### Model Strategies
|
|
@@ -453,6 +570,15 @@ await claw.updateModelStrategy(strategyId, { config: { maxBudgetUsd: 1.0 } });
|
|
|
453
570
|
|
|
454
571
|
// Delete (soft references on linked workflow_templates are nulled out)
|
|
455
572
|
await claw.deleteModelStrategy(strategyId);
|
|
573
|
+
|
|
574
|
+
// Execute a chat completion using the strategy (BYOK, fallback, budget enforcement)
|
|
575
|
+
const result = await claw.completeWithStrategy(strategyId, [
|
|
576
|
+
{ role: 'user', content: 'Summarize the deploy plan' }
|
|
577
|
+
], { max_tokens: 512, temperature: 0.7, task_mode: 'reasoning' });
|
|
578
|
+
console.log(result.content); // LLM response text
|
|
579
|
+
console.log(result.provider); // e.g. 'openai'
|
|
580
|
+
console.log(result.cost_usd); // estimated cost
|
|
581
|
+
console.log(result.fallback_used); // true if primary failed
|
|
456
582
|
```
|
|
457
583
|
|
|
458
584
|
### Knowledge Collections
|
|
@@ -477,16 +603,31 @@ await claw.addKnowledgeCollectionItem(collection.collection_id, {
|
|
|
477
603
|
|
|
478
604
|
// List items
|
|
479
605
|
const { items } = await claw.listKnowledgeCollectionItems(collection.collection_id);
|
|
606
|
+
|
|
607
|
+
// Sync — ingest pending items (fetch, chunk, embed via BYOK OpenAI key)
|
|
608
|
+
const { sync } = await claw.syncKnowledgeCollection(collection.collection_id);
|
|
609
|
+
console.log(sync.ingested, sync.chunks_created); // e.g. 3 ingested, 42 chunks
|
|
610
|
+
|
|
611
|
+
// Search — semantic similarity over embedded chunks
|
|
612
|
+
const { results } = await claw.searchKnowledgeCollection(
|
|
613
|
+
collection.collection_id,
|
|
614
|
+
'How do I roll back a deploy?',
|
|
615
|
+
{ limit: 5 }
|
|
616
|
+
);
|
|
617
|
+
results.forEach(r => console.log(`${(r.score * 100).toFixed(1)}%: ${r.content.slice(0, 80)}...`));
|
|
480
618
|
```
|
|
481
619
|
|
|
482
|
-
### Capability
|
|
620
|
+
### Capability Runtime
|
|
483
621
|
|
|
484
622
|
```javascript
|
|
623
|
+
// Canonical namespace for capability work
|
|
624
|
+
const caps = claw.execution.capabilities;
|
|
625
|
+
|
|
485
626
|
// Search the registry (category, risk_level, and search are combinable)
|
|
486
|
-
const { capabilities } = await
|
|
627
|
+
const { capabilities } = await caps.list({ risk_level: 'medium', search: 'slack' });
|
|
487
628
|
|
|
488
629
|
// Register a capability
|
|
489
|
-
await
|
|
630
|
+
await caps.create({
|
|
490
631
|
name: 'Send Slack Message',
|
|
491
632
|
description: 'Posts to a configured Slack channel',
|
|
492
633
|
category: 'messaging',
|
|
@@ -498,8 +639,69 @@ await claw.createCapability({
|
|
|
498
639
|
health_status: 'healthy',
|
|
499
640
|
docs_url: 'https://docs.example.com/slack'
|
|
500
641
|
});
|
|
642
|
+
|
|
643
|
+
// Invoke a governed capability
|
|
644
|
+
const result = await caps.invoke('cap_123', {
|
|
645
|
+
query: 'What is x402?'
|
|
646
|
+
});
|
|
647
|
+
console.log(result.governed, result.action_id);
|
|
648
|
+
// When retry_policy is configured on the capability, the response includes retry_metadata:
|
|
649
|
+
// result.retry_metadata → { total_attempts, retried, attempts: [...] }
|
|
650
|
+
|
|
651
|
+
// Run a non-production validation call (bypasses circuit breaker)
|
|
652
|
+
const testRun = await caps.test('cap_123', {
|
|
653
|
+
query: 'What is x402?'
|
|
654
|
+
});
|
|
655
|
+
console.log(testRun.tested, testRun.health_status, testRun.certification_status);
|
|
656
|
+
// testRun.retry_metadata is also present when the capability has retry_policy configured
|
|
657
|
+
|
|
658
|
+
// Fetch derived capability health
|
|
659
|
+
const health = await caps.getHealth('cap_123');
|
|
660
|
+
console.log(health.status, health.certification_status, health.last_test_status);
|
|
661
|
+
|
|
662
|
+
// List derived health for matching capabilities
|
|
663
|
+
const { capabilities: healthRows } = await caps.listHealth({
|
|
664
|
+
risk_level: 'medium',
|
|
665
|
+
certification_status: 'certified',
|
|
666
|
+
stale_only: false,
|
|
667
|
+
limit: 10,
|
|
668
|
+
});
|
|
669
|
+
console.log(healthRows.map((cap) => `${cap.slug}:${cap.status}:${cap.certification_status}`));
|
|
670
|
+
|
|
671
|
+
// Fetch recent invoke/test events for one capability
|
|
672
|
+
const history = await caps.getHistory('cap_123', {
|
|
673
|
+
action_type: 'capability_test',
|
|
674
|
+
status: 'failed',
|
|
675
|
+
limit: 5,
|
|
676
|
+
});
|
|
677
|
+
console.log(history.events.map((event) => `${event.action_type}:${event.status}`));
|
|
501
678
|
```
|
|
502
679
|
|
|
680
|
+
The existing flat registry methods remain available for compatibility:
|
|
681
|
+
|
|
682
|
+
- `claw.listCapabilities(...)`
|
|
683
|
+
- `claw.createCapability(...)`
|
|
684
|
+
- `claw.getCapability(...)`
|
|
685
|
+
- `claw.updateCapability(...)`
|
|
686
|
+
|
|
687
|
+
Use the canonical capability runtime paths:
|
|
688
|
+
|
|
689
|
+
- `claw.execution.capabilities.invoke(...)`
|
|
690
|
+
- `claw.execution.capabilities.test(...)`
|
|
691
|
+
- `claw.execution.capabilities.getHealth(...)`
|
|
692
|
+
- `claw.execution.capabilities.listHealth(...)`
|
|
693
|
+
- `claw.execution.capabilities.getHistory(...)`
|
|
694
|
+
|
|
695
|
+
Health responses now include certification and recency fields such as:
|
|
696
|
+
|
|
697
|
+
- `certification_status`
|
|
698
|
+
- `last_tested_at`
|
|
699
|
+
- `last_test_status`
|
|
700
|
+
- `stale_check`
|
|
701
|
+
- `success_rate_1d`
|
|
702
|
+
- `success_rate_7d`
|
|
703
|
+
- `p95_latency_ms`
|
|
704
|
+
|
|
503
705
|
---
|
|
504
706
|
|
|
505
707
|
## License
|
package/dashclaw.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DashClaw SDK v2.
|
|
2
|
+
* DashClaw SDK v2.11.0 (Stable Runtime API)
|
|
3
3
|
* Focused governance runtime client for AI agents.
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -34,6 +34,20 @@ class DashClaw {
|
|
|
34
34
|
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
35
35
|
this.apiKey = apiKey;
|
|
36
36
|
this.agentId = agentId;
|
|
37
|
+
|
|
38
|
+
this.execution = {
|
|
39
|
+
capabilities: {
|
|
40
|
+
list: (filters = {}) => this.listCapabilities(filters),
|
|
41
|
+
create: (data) => this.createCapability(data),
|
|
42
|
+
get: (capabilityId) => this.getCapability(capabilityId),
|
|
43
|
+
update: (capabilityId, patch) => this.updateCapability(capabilityId, patch),
|
|
44
|
+
invoke: (capabilityId, payload = {}) => this.invokeCapability(capabilityId, payload),
|
|
45
|
+
test: (capabilityId, payload = {}) => this.testCapability(capabilityId, payload),
|
|
46
|
+
getHealth: (capabilityId) => this.getCapabilityHealth(capabilityId),
|
|
47
|
+
listHealth: (filters = {}) => this.listCapabilityHealth(filters),
|
|
48
|
+
getHistory: (capabilityId, filters = {}) => this.getCapabilityHistory(capabilityId, filters),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
async _request(path, method = 'GET', body = null, params = null) {
|
|
@@ -143,17 +157,112 @@ class DashClaw {
|
|
|
143
157
|
}
|
|
144
158
|
|
|
145
159
|
/**
|
|
146
|
-
*
|
|
160
|
+
* @private Connect to SSE stream and yield parsed events.
|
|
161
|
+
*/
|
|
162
|
+
async *_connectSSE(controller) {
|
|
163
|
+
const res = await fetch(`${this.baseUrl}/api/stream`, {
|
|
164
|
+
headers: { 'x-api-key': this.apiKey },
|
|
165
|
+
signal: controller.signal,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (!res.ok || !res.body) return;
|
|
169
|
+
|
|
170
|
+
const reader = res.body.getReader();
|
|
171
|
+
const decoder = new TextDecoder();
|
|
172
|
+
let buffer = '';
|
|
173
|
+
let currentEvent = null;
|
|
174
|
+
let currentData = '';
|
|
175
|
+
let currentId = null;
|
|
176
|
+
|
|
177
|
+
while (true) {
|
|
178
|
+
const { done, value } = await reader.read();
|
|
179
|
+
if (done) break;
|
|
180
|
+
buffer += decoder.decode(value, { stream: true });
|
|
181
|
+
|
|
182
|
+
const lines = buffer.split('\n');
|
|
183
|
+
buffer = lines.pop();
|
|
184
|
+
|
|
185
|
+
for (const line of lines) {
|
|
186
|
+
if (line.startsWith('id: ')) {
|
|
187
|
+
currentId = line.slice(4).trim();
|
|
188
|
+
} else if (line.startsWith('event: ')) {
|
|
189
|
+
currentEvent = line.slice(7).trim();
|
|
190
|
+
} else if (line.startsWith('data: ')) {
|
|
191
|
+
currentData += line.slice(6);
|
|
192
|
+
} else if (line.startsWith(':')) {
|
|
193
|
+
// SSE comment (heartbeat)
|
|
194
|
+
} else if (line === '' && currentEvent) {
|
|
195
|
+
if (currentData) {
|
|
196
|
+
try {
|
|
197
|
+
yield { event: currentEvent, data: JSON.parse(currentData), id: currentId };
|
|
198
|
+
} catch { /* ignore parse errors */ }
|
|
199
|
+
}
|
|
200
|
+
currentEvent = null;
|
|
201
|
+
currentData = '';
|
|
202
|
+
currentId = null;
|
|
203
|
+
} else if (line === '') {
|
|
204
|
+
currentEvent = null;
|
|
205
|
+
currentData = '';
|
|
206
|
+
currentId = null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Wait for human approval. SSE-first with polling fallback.
|
|
147
214
|
*/
|
|
148
215
|
async waitForApproval(actionId, { timeout = 300000, interval = 5000 } = {}) {
|
|
149
216
|
const startTime = Date.now();
|
|
217
|
+
|
|
218
|
+
const checkAction = (action) => {
|
|
219
|
+
if (action.approved_by) return { resolved: true, result: { action } };
|
|
220
|
+
if (action.status === 'failed' || action.status === 'cancelled') {
|
|
221
|
+
return { resolved: true, error: new ApprovalDeniedError(action.error_message || 'Operator denied the action.', action.status) };
|
|
222
|
+
}
|
|
223
|
+
return { resolved: false };
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// Try SSE first
|
|
227
|
+
try {
|
|
228
|
+
const controller = new AbortController();
|
|
229
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
for await (const frame of this._connectSSE(controller)) {
|
|
233
|
+
if (frame.event === 'action.updated' && frame.data?.action_id === actionId) {
|
|
234
|
+
const check = checkAction(frame.data);
|
|
235
|
+
if (check.resolved) {
|
|
236
|
+
clearTimeout(timeoutId);
|
|
237
|
+
controller.abort();
|
|
238
|
+
if (check.error) throw check.error;
|
|
239
|
+
const confirmed = await this._request(`/api/actions/${actionId}`, 'GET');
|
|
240
|
+
return confirmed;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (Date.now() - startTime >= timeout) {
|
|
245
|
+
clearTimeout(timeoutId);
|
|
246
|
+
controller.abort();
|
|
247
|
+
throw new Error(`Timed out waiting for approval of action ${actionId}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} finally {
|
|
251
|
+
clearTimeout(timeoutId);
|
|
252
|
+
if (!controller.signal.aborted) controller.abort();
|
|
253
|
+
}
|
|
254
|
+
} catch (err) {
|
|
255
|
+
if (err instanceof ApprovalDeniedError || err.message?.includes('Timed out')) throw err;
|
|
256
|
+
// SSE failed — fall through to polling
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Polling fallback
|
|
150
260
|
let wasPending = false;
|
|
151
261
|
let printedBlock = false;
|
|
152
262
|
|
|
153
263
|
while (Date.now() - startTime < timeout) {
|
|
154
264
|
const { action } = await this._request(`/api/actions/${actionId}`, 'GET');
|
|
155
265
|
|
|
156
|
-
// Print structured approval block on first fetch
|
|
157
266
|
if (!printedBlock) {
|
|
158
267
|
printedBlock = true;
|
|
159
268
|
try {
|
|
@@ -178,33 +287,18 @@ class DashClaw {
|
|
|
178
287
|
'\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d',
|
|
179
288
|
];
|
|
180
289
|
process.stdout.write('\n' + lines.join('\n') + '\n\n');
|
|
181
|
-
} catch (_) {
|
|
182
|
-
// Rendering failure must not prevent the wait from proceeding
|
|
183
|
-
}
|
|
290
|
+
} catch (_) { /* rendering failure must not prevent wait */ }
|
|
184
291
|
}
|
|
185
|
-
|
|
186
|
-
if (action.status === 'pending_approval') {
|
|
187
|
-
wasPending = true;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Explicitly unblocked by approval metadata
|
|
191
|
-
if (action.approved_by) return action;
|
|
192
292
|
|
|
193
|
-
|
|
293
|
+
if (action.status === 'pending_approval') wasPending = true;
|
|
294
|
+
if (action.approved_by) return { action };
|
|
194
295
|
if (action.status === 'failed' || action.status === 'cancelled') {
|
|
195
296
|
throw new ApprovalDeniedError(action.error_message || 'Operator denied the action.', action.status);
|
|
196
297
|
}
|
|
197
|
-
|
|
198
|
-
// Requirement 4: If an action leaves pending_approval without approval metadata, throw an error.
|
|
199
|
-
// This prevents "auto-approval" bugs where status is changed by non-approval paths.
|
|
200
298
|
if (wasPending && action.status !== 'pending_approval') {
|
|
201
299
|
throw new Error(`Action ${actionId} left pending_approval state without explicit approval metadata (Status: ${action.status})`);
|
|
202
300
|
}
|
|
203
|
-
|
|
204
|
-
// If allowed directly (never intercepted), return immediately
|
|
205
|
-
if (!wasPending && action.status === 'running') {
|
|
206
|
-
return { action };
|
|
207
|
-
}
|
|
301
|
+
if (!wasPending && action.status === 'running') return { action };
|
|
208
302
|
|
|
209
303
|
await new Promise(r => setTimeout(r, interval));
|
|
210
304
|
}
|
|
@@ -743,6 +837,21 @@ class DashClaw {
|
|
|
743
837
|
return this._request(`/api/model-strategies/${strategyId}`, 'DELETE');
|
|
744
838
|
}
|
|
745
839
|
|
|
840
|
+
/**
|
|
841
|
+
* POST /api/model-strategies/:id/complete — Execute a chat completion using
|
|
842
|
+
* this strategy. Resolves BYOK provider credentials, handles fallback chain,
|
|
843
|
+
* enforces budget caps.
|
|
844
|
+
* @param {string} strategyId
|
|
845
|
+
* @param {Array<{role: string, content: string}>} messages
|
|
846
|
+
* @param {Object} [options={}] - { max_tokens, temperature, task_mode }
|
|
847
|
+
*/
|
|
848
|
+
async completeWithStrategy(strategyId, messages, options = {}) {
|
|
849
|
+
return this._request(`/api/model-strategies/${strategyId}/complete`, 'POST', {
|
|
850
|
+
messages,
|
|
851
|
+
...options,
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
|
|
746
855
|
// ---------------------------------------------------------------------------
|
|
747
856
|
// Execution Studio — Knowledge Collections
|
|
748
857
|
// ---------------------------------------------------------------------------
|
|
@@ -795,6 +904,26 @@ class DashClaw {
|
|
|
795
904
|
return this._request(`/api/knowledge/collections/${collectionId}/items`, 'POST', data);
|
|
796
905
|
}
|
|
797
906
|
|
|
907
|
+
/**
|
|
908
|
+
* POST /api/knowledge/collections/:id/sync — Ingest pending items (chunk + embed).
|
|
909
|
+
*/
|
|
910
|
+
async syncKnowledgeCollection(collectionId) {
|
|
911
|
+
return this._request(`/api/knowledge/collections/${collectionId}/sync`, 'POST', {});
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* POST /api/knowledge/collections/:id/search — Semantic search over chunks.
|
|
916
|
+
* @param {string} collectionId
|
|
917
|
+
* @param {string} query
|
|
918
|
+
* @param {Object} [options={}] - { limit }
|
|
919
|
+
*/
|
|
920
|
+
async searchKnowledgeCollection(collectionId, query, options = {}) {
|
|
921
|
+
return this._request(`/api/knowledge/collections/${collectionId}/search`, 'POST', {
|
|
922
|
+
query,
|
|
923
|
+
...options,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
|
|
798
927
|
// ---------------------------------------------------------------------------
|
|
799
928
|
// Execution Studio — Capability Registry
|
|
800
929
|
// ---------------------------------------------------------------------------
|
|
@@ -827,6 +956,47 @@ class DashClaw {
|
|
|
827
956
|
async updateCapability(capabilityId, patch) {
|
|
828
957
|
return this._request(`/api/capabilities/${capabilityId}`, 'PATCH', patch);
|
|
829
958
|
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* POST /api/capabilities/:id/invoke — Invoke a governed capability.
|
|
962
|
+
*/
|
|
963
|
+
async invokeCapability(capabilityId, payload = {}) {
|
|
964
|
+
return this._request(`/api/capabilities/${capabilityId}/invoke`, 'POST', {
|
|
965
|
+
...payload,
|
|
966
|
+
agent_id: payload.agent_id || this.agentId,
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* POST /api/capabilities/:id/test — Run a non-production capability validation call.
|
|
972
|
+
*/
|
|
973
|
+
async testCapability(capabilityId, payload = {}) {
|
|
974
|
+
return this._request(`/api/capabilities/${capabilityId}/test`, 'POST', {
|
|
975
|
+
...payload,
|
|
976
|
+
agent_id: payload.agent_id || this.agentId,
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* GET /api/capabilities/:id/health — Fetch derived capability health.
|
|
982
|
+
*/
|
|
983
|
+
async getCapabilityHealth(capabilityId) {
|
|
984
|
+
return this._request(`/api/capabilities/${capabilityId}/health`, 'GET');
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* GET /api/capabilities/health — List derived health summaries for matching capabilities.
|
|
989
|
+
*/
|
|
990
|
+
async listCapabilityHealth(filters = {}) {
|
|
991
|
+
return this._request('/api/capabilities/health', 'GET', null, filters);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* GET /api/capabilities/:id/history — Fetch recent test and invoke events for a capability.
|
|
996
|
+
*/
|
|
997
|
+
async getCapabilityHistory(capabilityId, filters = {}) {
|
|
998
|
+
return this._request(`/api/capabilities/${capabilityId}/history`, 'GET', null, filters);
|
|
999
|
+
}
|
|
830
1000
|
}
|
|
831
1001
|
|
|
832
1002
|
export { DashClaw, ApprovalDeniedError, GuardBlockedError };
|
package/legacy/dashclaw-v1.js
CHANGED
|
@@ -263,11 +263,18 @@ class DashClaw {
|
|
|
263
263
|
try { this.guardCallback(decision); } catch { /* ignore callback errors */ }
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
|
|
266
|
+
// Only `block` is a hard stop. `require_approval` is the normal HITL path:
|
|
267
|
+
// the server will create the action with status='pending_approval' and the
|
|
268
|
+
// approval queue / waitForApproval handles the rest. Throwing here would
|
|
269
|
+
// prevent the POST to /api/actions and break the PWA approval surface.
|
|
270
|
+
const isBlocked = decision.decision === 'block';
|
|
267
271
|
|
|
268
272
|
if (this.guardMode === 'warn' && isBlocked) {
|
|
273
|
+
const reasons = Array.isArray(decision.reasons)
|
|
274
|
+
? decision.reasons.join('; ')
|
|
275
|
+
: (decision.reason || 'no reason');
|
|
269
276
|
console.warn(
|
|
270
|
-
`[DashClaw] Guard ${decision.decision}: ${
|
|
277
|
+
`[DashClaw] Guard ${decision.decision}: ${reasons}. Proceeding in warn mode.`
|
|
271
278
|
);
|
|
272
279
|
return;
|
|
273
280
|
}
|
|
@@ -2826,6 +2833,46 @@ class DashClaw {
|
|
|
2826
2833
|
return this._request('GET', '/api/scoring/score', null, { profile_id: profileId, view: 'stats' });
|
|
2827
2834
|
}
|
|
2828
2835
|
|
|
2836
|
+
// --- Capability Runtime Compatibility ------------------
|
|
2837
|
+
|
|
2838
|
+
async listCapabilities(params = {}) {
|
|
2839
|
+
return this._request('GET', '/api/capabilities', null, params);
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
async createCapability(data) {
|
|
2843
|
+
return this._request('POST', '/api/capabilities', data);
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
async getCapability(capabilityId) {
|
|
2847
|
+
return this._request('GET', `/api/capabilities/${capabilityId}`);
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
async updateCapability(capabilityId, data) {
|
|
2851
|
+
return this._request('PATCH', `/api/capabilities/${capabilityId}`, data);
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
async invokeCapability(capabilityId, payload = {}) {
|
|
2855
|
+
return this._request('POST', `/api/capabilities/${capabilityId}/invoke`, {
|
|
2856
|
+
...payload,
|
|
2857
|
+
agent_id: payload.agent_id || this.agentId,
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
async testCapability(capabilityId, payload = {}) {
|
|
2862
|
+
return this._request('POST', `/api/capabilities/${capabilityId}/test`, {
|
|
2863
|
+
...payload,
|
|
2864
|
+
agent_id: payload.agent_id || this.agentId,
|
|
2865
|
+
});
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
async getCapabilityHealth(capabilityId) {
|
|
2869
|
+
return this._request('GET', `/api/capabilities/${capabilityId}/health`);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
async listCapabilityHealth(params = {}) {
|
|
2873
|
+
return this._request('GET', '/api/capabilities/health', null, params);
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2829
2876
|
// --- Risk Templates ------------------------------------
|
|
2830
2877
|
|
|
2831
2878
|
async createRiskTemplate(data) {
|