dashclaw 2.9.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +323 -6
- package/dashclaw.js +376 -22
- package/legacy/dashclaw-v1.js +40 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# DashClaw SDK (v2.
|
|
1
|
+
# DashClaw SDK (v2.10.0)
|
|
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.10.0)
|
|
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 **45 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,273 @@ 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
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Execution Studio
|
|
454
|
+
|
|
455
|
+
Governance packaging and discovery — workflow templates, model strategies, knowledge collections, a capability registry, and a read-only execution graph. Added in v2.10.0.
|
|
456
|
+
|
|
457
|
+
### Execution Graph
|
|
458
|
+
|
|
459
|
+
```javascript
|
|
460
|
+
// Fetch the execution graph for any action (reuses existing trace data)
|
|
461
|
+
const { rootActionId, nodes, edges } = await claw.getActionGraph(actionId);
|
|
462
|
+
// nodes: action:<id>, assumption:<id>, loop:<id>
|
|
463
|
+
// edges: parent_child | related | assumption_of | loop_from
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Workflow Templates
|
|
467
|
+
|
|
468
|
+
```javascript
|
|
469
|
+
// List templates
|
|
470
|
+
const { templates } = await claw.listWorkflowTemplates({ status: 'active' });
|
|
471
|
+
|
|
472
|
+
// Create a template
|
|
473
|
+
const { template } = await claw.createWorkflowTemplate({
|
|
474
|
+
name: 'Release Hotfix',
|
|
475
|
+
description: 'Ship urgent production patches safely',
|
|
476
|
+
objective: 'Deploy with full policy + approval coverage',
|
|
477
|
+
linked_policy_ids: ['pol_prod_deploy'],
|
|
478
|
+
linked_capability_tags: ['deploy'],
|
|
479
|
+
model_strategy_id: 'mst_balanced_default'
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// Get / update / duplicate
|
|
483
|
+
const detail = await claw.getWorkflowTemplate(templateId);
|
|
484
|
+
await claw.updateWorkflowTemplate(templateId, {
|
|
485
|
+
steps: [{ id: 'plan' }, { id: 'test' }, { id: 'deploy' }]
|
|
486
|
+
}); // bumps version when steps change
|
|
487
|
+
await claw.duplicateWorkflowTemplate(templateId);
|
|
488
|
+
|
|
489
|
+
// Launch — creates a traceable action_records row with workflow metadata.
|
|
490
|
+
// If the template links a model_strategy_id, the resolved config is snapshotted.
|
|
491
|
+
const { launch } = await claw.launchWorkflowTemplate(templateId, { agent_id: 'deploy-bot' });
|
|
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
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Model Strategies
|
|
550
|
+
|
|
551
|
+
```javascript
|
|
552
|
+
// List and create
|
|
553
|
+
const { strategies } = await claw.listModelStrategies();
|
|
554
|
+
const { strategy } = await claw.createModelStrategy({
|
|
555
|
+
name: 'Balanced Default',
|
|
556
|
+
description: 'GPT-4.1 primary, Claude Sonnet 4 fallback',
|
|
557
|
+
config: {
|
|
558
|
+
primary: { provider: 'openai', model: 'gpt-4.1' },
|
|
559
|
+
fallback: [{ provider: 'anthropic', model: 'claude-sonnet-4' }],
|
|
560
|
+
costSensitivity: 'balanced', // 'low' | 'balanced' | 'high-quality'
|
|
561
|
+
latencySensitivity: 'medium', // 'low' | 'medium' | 'high'
|
|
562
|
+
maxBudgetUsd: 0.5,
|
|
563
|
+
maxRetries: 2,
|
|
564
|
+
allowedProviders: ['openai', 'anthropic']
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// Update (config patches merge over existing config)
|
|
569
|
+
await claw.updateModelStrategy(strategyId, { config: { maxBudgetUsd: 1.0 } });
|
|
570
|
+
|
|
571
|
+
// Delete (soft references on linked workflow_templates are nulled out)
|
|
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
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Knowledge Collections
|
|
585
|
+
|
|
586
|
+
Metadata-only layer — no embedding or retrieval yet. Ingestion execution is planned for Phase 2b.
|
|
587
|
+
|
|
588
|
+
```javascript
|
|
589
|
+
// Create a collection
|
|
590
|
+
const { collection } = await claw.createKnowledgeCollection({
|
|
591
|
+
name: 'Runbook Library',
|
|
592
|
+
description: 'Incident response runbooks',
|
|
593
|
+
source_type: 'files', // 'files' | 'urls' | 'external' | 'notes'
|
|
594
|
+
tags: ['ops', 'oncall']
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
// Add items (bumps parent doc_count; transitions ingestion_status from empty → pending)
|
|
598
|
+
await claw.addKnowledgeCollectionItem(collection.collection_id, {
|
|
599
|
+
source_uri: 'https://docs.example.com/runbook.md',
|
|
600
|
+
title: 'Deploy runbook',
|
|
601
|
+
mime_type: 'text/markdown'
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// List items
|
|
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)}...`));
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Capability Runtime
|
|
621
|
+
|
|
622
|
+
```javascript
|
|
623
|
+
// Canonical namespace for capability work
|
|
624
|
+
const caps = claw.execution.capabilities;
|
|
625
|
+
|
|
626
|
+
// Search the registry (category, risk_level, and search are combinable)
|
|
627
|
+
const { capabilities } = await caps.list({ risk_level: 'medium', search: 'slack' });
|
|
628
|
+
|
|
629
|
+
// Register a capability
|
|
630
|
+
await caps.create({
|
|
631
|
+
name: 'Send Slack Message',
|
|
632
|
+
description: 'Posts to a configured Slack channel',
|
|
633
|
+
category: 'messaging',
|
|
634
|
+
source_type: 'http_api', // 'internal_sdk' | 'http_api' | 'webhook' | 'human_approval' | 'external_marketplace'
|
|
635
|
+
auth_type: 'oauth',
|
|
636
|
+
risk_level: 'medium', // 'low' | 'medium' | 'high' | 'critical'
|
|
637
|
+
requires_approval: false,
|
|
638
|
+
tags: ['notify', 'slack'],
|
|
639
|
+
health_status: 'healthy',
|
|
640
|
+
docs_url: 'https://docs.example.com/slack'
|
|
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}`));
|
|
678
|
+
```
|
|
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
|
+
|
|
388
705
|
---
|
|
389
706
|
|
|
390
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
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (action.status === 'pending_approval') {
|
|
187
|
-
wasPending = true;
|
|
290
|
+
} catch (_) { /* rendering failure must not prevent wait */ }
|
|
188
291
|
}
|
|
189
292
|
|
|
190
|
-
|
|
191
|
-
if (action.approved_by) return action;
|
|
192
|
-
|
|
193
|
-
// Denial cases
|
|
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
|
}
|
|
@@ -643,6 +737,266 @@ class DashClaw {
|
|
|
643
737
|
async getSessionEvents(sessionId) {
|
|
644
738
|
return this._request(`/api/sessions/${sessionId}/events`, 'GET');
|
|
645
739
|
}
|
|
740
|
+
|
|
741
|
+
// ---------------------------------------------------------------------------
|
|
742
|
+
// Execution Studio — Execution Graph
|
|
743
|
+
// ---------------------------------------------------------------------------
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* GET /api/actions/:id/graph — Read-only execution graph (nodes + edges).
|
|
747
|
+
*/
|
|
748
|
+
async getActionGraph(actionId) {
|
|
749
|
+
return this._request(`/api/actions/${actionId}/graph`, 'GET');
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// ---------------------------------------------------------------------------
|
|
753
|
+
// Execution Studio — Workflow Templates
|
|
754
|
+
// ---------------------------------------------------------------------------
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* GET /api/workflows/templates — List workflow templates.
|
|
758
|
+
* @param {Object} [filters={}] - { status, limit, offset }
|
|
759
|
+
*/
|
|
760
|
+
async listWorkflowTemplates(filters = {}) {
|
|
761
|
+
return this._request('/api/workflows/templates', 'GET', null, filters);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* POST /api/workflows/templates — Create a workflow template.
|
|
766
|
+
*/
|
|
767
|
+
async createWorkflowTemplate(data) {
|
|
768
|
+
return this._request('/api/workflows/templates', 'POST', data);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* GET /api/workflows/templates/:id — Fetch a single template.
|
|
773
|
+
*/
|
|
774
|
+
async getWorkflowTemplate(templateId) {
|
|
775
|
+
return this._request(`/api/workflows/templates/${templateId}`, 'GET');
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* PATCH /api/workflows/templates/:id — Partial update. Bumps version when steps change.
|
|
780
|
+
*/
|
|
781
|
+
async updateWorkflowTemplate(templateId, patch) {
|
|
782
|
+
return this._request(`/api/workflows/templates/${templateId}`, 'PATCH', patch);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* POST /api/workflows/templates/:id/duplicate — Clone as a new draft.
|
|
787
|
+
*/
|
|
788
|
+
async duplicateWorkflowTemplate(templateId, overrides = {}) {
|
|
789
|
+
return this._request(`/api/workflows/templates/${templateId}/duplicate`, 'POST', overrides);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* POST /api/workflows/templates/:id/launch — Create a traceable action record.
|
|
794
|
+
* Resolves any linked model strategy into a snapshot at launch time.
|
|
795
|
+
*/
|
|
796
|
+
async launchWorkflowTemplate(templateId, options = {}) {
|
|
797
|
+
return this._request(`/api/workflows/templates/${templateId}/launch`, 'POST', options);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// ---------------------------------------------------------------------------
|
|
801
|
+
// Execution Studio — Model Strategies
|
|
802
|
+
// ---------------------------------------------------------------------------
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* GET /api/model-strategies — List model strategies.
|
|
806
|
+
*/
|
|
807
|
+
async listModelStrategies() {
|
|
808
|
+
return this._request('/api/model-strategies', 'GET');
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* POST /api/model-strategies — Create a model strategy.
|
|
813
|
+
* @param {Object} data - { name, description, config: { primary, fallback, costSensitivity, ... } }
|
|
814
|
+
*/
|
|
815
|
+
async createModelStrategy(data) {
|
|
816
|
+
return this._request('/api/model-strategies', 'POST', data);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* GET /api/model-strategies/:id — Fetch a single strategy.
|
|
821
|
+
*/
|
|
822
|
+
async getModelStrategy(strategyId) {
|
|
823
|
+
return this._request(`/api/model-strategies/${strategyId}`, 'GET');
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* PATCH /api/model-strategies/:id — Partial update. Config patches merge over existing.
|
|
828
|
+
*/
|
|
829
|
+
async updateModelStrategy(strategyId, patch) {
|
|
830
|
+
return this._request(`/api/model-strategies/${strategyId}`, 'PATCH', patch);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* DELETE /api/model-strategies/:id — Delete. Nulls soft refs on linked templates.
|
|
835
|
+
*/
|
|
836
|
+
async deleteModelStrategy(strategyId) {
|
|
837
|
+
return this._request(`/api/model-strategies/${strategyId}`, 'DELETE');
|
|
838
|
+
}
|
|
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
|
+
|
|
855
|
+
// ---------------------------------------------------------------------------
|
|
856
|
+
// Execution Studio — Knowledge Collections
|
|
857
|
+
// ---------------------------------------------------------------------------
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* GET /api/knowledge/collections — List knowledge collections.
|
|
861
|
+
* @param {Object} [filters={}] - { sourceType, limit, offset }
|
|
862
|
+
*/
|
|
863
|
+
async listKnowledgeCollections(filters = {}) {
|
|
864
|
+
const params = {};
|
|
865
|
+
if (filters.sourceType) params.source_type = filters.sourceType;
|
|
866
|
+
if (filters.limit) params.limit = filters.limit;
|
|
867
|
+
if (filters.offset) params.offset = filters.offset;
|
|
868
|
+
return this._request('/api/knowledge/collections', 'GET', null, params);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* POST /api/knowledge/collections — Create a knowledge collection.
|
|
873
|
+
*/
|
|
874
|
+
async createKnowledgeCollection(data) {
|
|
875
|
+
return this._request('/api/knowledge/collections', 'POST', data);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* GET /api/knowledge/collections/:id — Fetch a single collection.
|
|
880
|
+
*/
|
|
881
|
+
async getKnowledgeCollection(collectionId) {
|
|
882
|
+
return this._request(`/api/knowledge/collections/${collectionId}`, 'GET');
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* PATCH /api/knowledge/collections/:id — Update collection metadata.
|
|
887
|
+
*/
|
|
888
|
+
async updateKnowledgeCollection(collectionId, patch) {
|
|
889
|
+
return this._request(`/api/knowledge/collections/${collectionId}`, 'PATCH', patch);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* GET /api/knowledge/collections/:id/items — List items in a collection.
|
|
894
|
+
* @param {Object} [filters={}] - { limit, offset }
|
|
895
|
+
*/
|
|
896
|
+
async listKnowledgeCollectionItems(collectionId, filters = {}) {
|
|
897
|
+
return this._request(`/api/knowledge/collections/${collectionId}/items`, 'GET', null, filters);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* POST /api/knowledge/collections/:id/items — Add an item. Bumps parent doc_count.
|
|
902
|
+
*/
|
|
903
|
+
async addKnowledgeCollectionItem(collectionId, data) {
|
|
904
|
+
return this._request(`/api/knowledge/collections/${collectionId}/items`, 'POST', data);
|
|
905
|
+
}
|
|
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
|
+
|
|
927
|
+
// ---------------------------------------------------------------------------
|
|
928
|
+
// Execution Studio — Capability Registry
|
|
929
|
+
// ---------------------------------------------------------------------------
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* GET /api/capabilities — Search the capability registry.
|
|
933
|
+
* @param {Object} [filters={}] - { category, risk_level, search, limit, offset }
|
|
934
|
+
*/
|
|
935
|
+
async listCapabilities(filters = {}) {
|
|
936
|
+
return this._request('/api/capabilities', 'GET', null, filters);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* POST /api/capabilities — Register a capability.
|
|
941
|
+
*/
|
|
942
|
+
async createCapability(data) {
|
|
943
|
+
return this._request('/api/capabilities', 'POST', data);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* GET /api/capabilities/:id — Fetch a single capability.
|
|
948
|
+
*/
|
|
949
|
+
async getCapability(capabilityId) {
|
|
950
|
+
return this._request(`/api/capabilities/${capabilityId}`, 'GET');
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* PATCH /api/capabilities/:id — Update a capability.
|
|
955
|
+
*/
|
|
956
|
+
async updateCapability(capabilityId, patch) {
|
|
957
|
+
return this._request(`/api/capabilities/${capabilityId}`, 'PATCH', patch);
|
|
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
|
+
}
|
|
646
1000
|
}
|
|
647
1001
|
|
|
648
1002
|
export { DashClaw, ApprovalDeniedError, GuardBlockedError };
|
package/legacy/dashclaw-v1.js
CHANGED
|
@@ -2826,6 +2826,46 @@ class DashClaw {
|
|
|
2826
2826
|
return this._request('GET', '/api/scoring/score', null, { profile_id: profileId, view: 'stats' });
|
|
2827
2827
|
}
|
|
2828
2828
|
|
|
2829
|
+
// --- Capability Runtime Compatibility ------------------
|
|
2830
|
+
|
|
2831
|
+
async listCapabilities(params = {}) {
|
|
2832
|
+
return this._request('GET', '/api/capabilities', null, params);
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
async createCapability(data) {
|
|
2836
|
+
return this._request('POST', '/api/capabilities', data);
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
async getCapability(capabilityId) {
|
|
2840
|
+
return this._request('GET', `/api/capabilities/${capabilityId}`);
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
async updateCapability(capabilityId, data) {
|
|
2844
|
+
return this._request('PATCH', `/api/capabilities/${capabilityId}`, data);
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
async invokeCapability(capabilityId, payload = {}) {
|
|
2848
|
+
return this._request('POST', `/api/capabilities/${capabilityId}/invoke`, {
|
|
2849
|
+
...payload,
|
|
2850
|
+
agent_id: payload.agent_id || this.agentId,
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
async testCapability(capabilityId, payload = {}) {
|
|
2855
|
+
return this._request('POST', `/api/capabilities/${capabilityId}/test`, {
|
|
2856
|
+
...payload,
|
|
2857
|
+
agent_id: payload.agent_id || this.agentId,
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
async getCapabilityHealth(capabilityId) {
|
|
2862
|
+
return this._request('GET', `/api/capabilities/${capabilityId}/health`);
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
async listCapabilityHealth(params = {}) {
|
|
2866
|
+
return this._request('GET', '/api/capabilities/health', null, params);
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2829
2869
|
// --- Risk Templates ------------------------------------
|
|
2830
2870
|
|
|
2831
2871
|
async createRiskTemplate(data) {
|