dashclaw 3.0.0 → 4.0.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 CHANGED
@@ -447,21 +447,6 @@ if (result.recommendation === 'block') {
447
447
  }
448
448
  ```
449
449
 
450
- ### Context Threads
451
- - `createThread(thread)` -- Create a context thread for tracking multi-step work.
452
- - `addThreadEntry(threadId, content, entryType)` -- Add an entry to a context thread.
453
- - `closeThread(threadId, summary)` -- Close a context thread with an optional summary.
454
-
455
- ```javascript
456
- // Create a thread, add entries, and close it
457
- const thread = await claw.createThread({ name: 'Release Planning' });
458
-
459
- await claw.addThreadEntry(thread.thread_id, 'Kickoff complete', 'note');
460
- await claw.addThreadEntry(thread.thread_id, 'Tests green on staging', 'milestone');
461
-
462
- await claw.closeThread(thread.thread_id, 'Release shipped successfully');
463
- ```
464
-
465
450
  ### Bulk Sync
466
451
  - `syncState(state)` -- Push a full agent state snapshot in a single call.
467
452
 
@@ -644,7 +629,7 @@ If your agent supports Model Context Protocol (Claude Code, Claude Desktop, Mana
644
629
 
645
630
  **Streamable HTTP transport** (same surface, served by your DashClaw instance at `POST /api/mcp`).
646
631
 
647
- **23 tools** in 7 groups:
632
+ **26 tools** in 9 groups:
648
633
 
649
634
  - **Core governance (8):** `dashclaw_guard`, `dashclaw_record`, `dashclaw_invoke`, `dashclaw_capabilities_list`, `dashclaw_policies_list`, `dashclaw_wait_for_approval`, `dashclaw_session_start`, `dashclaw_session_end`.
650
635
  - **Optimal files (2):** `dashclaw_optimal_files_preview`, `dashclaw_optimal_files_manifest` — Code Sessions optimizer output (root CLAUDE.md, path-scoped rules, hooks, skill packs).
@@ -653,6 +638,8 @@ If your agent supports Model Context Protocol (Claude Code, Claude Desktop, Mana
653
638
  - **Skill safety (1):** `dashclaw_skill_scan` — static safety scan of untrusted skill files; results cached by content hash.
654
639
  - **Open loops (3):** `dashclaw_loop_add`, `dashclaw_loop_list`, `dashclaw_loop_close` — action-scoped commitments (the "I will X later" tracker).
655
640
  - **Learning + retrospection (3):** `dashclaw_learning_log`, `dashclaw_learning_query`, `dashclaw_decisions_recent` — log + query non-obvious decisions; recent governed-action ledger.
641
+ - **Agent inbox (2):** `dashclaw_inbox_list`, `dashclaw_messages_mark_read`
642
+ - **Behavior learning (1):** `dashclaw_behavior_suggestions`
656
643
 
657
644
  **6 resources:** `dashclaw://policies`, `dashclaw://capabilities`, `dashclaw://agent/{agent_id}/history`, `dashclaw://status`, `dashclaw://code-sessions/projects`, `dashclaw://code-sessions/sessions/{session_id}`.
658
645
 
package/dashclaw.js CHANGED
@@ -225,10 +225,18 @@ class DashClaw {
225
225
  */
226
226
  async *_connectSSE(controller) {
227
227
  const res = await fetch(`${this.baseUrl}/api/stream`, {
228
- headers: { 'x-api-key': this.apiKey },
228
+ headers: {
229
+ 'x-api-key': this.apiKey,
230
+ ...(this.authToken ? { 'Authorization': `Bearer ${this.authToken}` } : {}),
231
+ },
229
232
  signal: controller.signal,
230
233
  });
231
234
 
235
+ if (res.status === 403) {
236
+ let body = {};
237
+ try { body = await res.json(); } catch { /* ignore */ }
238
+ throw new GuardBlockedError(body.decision || { reason: 'SSE stream blocked by policy' });
239
+ }
232
240
  if (!res.ok || !res.body) return;
233
241
 
234
242
  const reader = res.body.getReader();
@@ -316,7 +324,7 @@ class DashClaw {
316
324
  if (!controller.signal.aborted) controller.abort();
317
325
  }
318
326
  } catch (err) {
319
- if (err instanceof ApprovalDeniedError || err.message?.includes('Timed out')) throw err;
327
+ if (err instanceof ApprovalDeniedError || err instanceof GuardBlockedError || err.message?.includes('Timed out')) throw err;
320
328
  // SSE failed — fall through to polling
321
329
  }
322
330
 
@@ -451,7 +459,7 @@ class DashClaw {
451
459
  return this._request('/api/learning/lessons', 'GET', null, {
452
460
  agent_id: this.agentId,
453
461
  ...(actionType && { action_type: actionType }),
454
- ...(limit && { limit }),
462
+ ...(limit != null && { limit }),
455
463
  });
456
464
  }
457
465
 
@@ -540,6 +548,7 @@ class DashClaw {
540
548
  * POST /api/scoring/score — score a single action against a profile
541
549
  */
542
550
  async scoreWithProfile(profileId, action) {
551
+ if (Array.isArray(action)) throw new TypeError('scoreWithProfile expects a single action object; use batchScoreWithProfile for arrays');
543
552
  return this._request('/api/scoring/score', 'POST', { profile_id: profileId, action });
544
553
  }
545
554
 
@@ -547,6 +556,7 @@ class DashClaw {
547
556
  * POST /api/scoring/score — batch score multiple actions against a profile
548
557
  */
549
558
  async batchScoreWithProfile(profileId, actions) {
559
+ if (!Array.isArray(actions)) throw new TypeError('batchScoreWithProfile expects an array of actions');
550
560
  return this._request('/api/scoring/score', 'POST', { profile_id: profileId, actions });
551
561
  }
552
562
 
@@ -648,7 +658,7 @@ class DashClaw {
648
658
  direction: 'inbox',
649
659
  ...(type && { type }),
650
660
  ...(unread != null && { unread }),
651
- ...(limit && { limit }),
661
+ ...(limit != null && { limit }),
652
662
  });
653
663
  }
654
664
 
@@ -661,7 +671,7 @@ class DashClaw {
661
671
  direction: 'sent',
662
672
  ...(type && { type }),
663
673
  ...(threadId && { thread_id: threadId }),
664
- ...(limit && { limit }),
674
+ ...(limit != null && { limit }),
665
675
  });
666
676
  }
667
677
 
@@ -675,7 +685,7 @@ class DashClaw {
675
685
  ...(type && { type }),
676
686
  ...(unread != null && { unread }),
677
687
  ...(threadId && { thread_id: threadId }),
678
- ...(limit && { limit }),
688
+ ...(limit != null && { limit }),
679
689
  });
680
690
  }
681
691
 
@@ -753,54 +763,6 @@ class DashClaw {
753
763
  });
754
764
  }
755
765
 
756
- // ---------------------------------------------------------------------------
757
- // Context Threads
758
- // ---------------------------------------------------------------------------
759
-
760
- /**
761
- * POST /api/context/threads — Create a reasoning context thread.
762
- */
763
- async createThread(thread) {
764
- return this._request('/api/context/threads', 'POST', {
765
- agent_id: this.agentId,
766
- ...thread,
767
- });
768
- }
769
-
770
- /**
771
- * POST /api/context/threads/:id/entries — Append a reasoning step.
772
- */
773
- async addThreadEntry(threadId, content, entryType) {
774
- return this._request(`/api/context/threads/${threadId}/entries`, 'POST', {
775
- content,
776
- entry_type: entryType,
777
- });
778
- }
779
-
780
- /**
781
- * PATCH /api/context/threads/:id — Close a reasoning thread.
782
- */
783
- async closeThread(threadId, summary) {
784
- return this._request(`/api/context/threads/${threadId}`, 'PATCH', {
785
- status: 'closed',
786
- ...(summary ? { summary } : {}),
787
- });
788
- }
789
-
790
- // ---------------------------------------------------------------------------
791
- // Bulk Sync
792
- // ---------------------------------------------------------------------------
793
-
794
- /**
795
- * POST /api/sync — Bulk state sync for periodic updates or bootstrap.
796
- */
797
- async syncState(state) {
798
- return this._request('/api/sync', 'POST', {
799
- agent_id: this.agentId,
800
- ...state,
801
- });
802
- }
803
-
804
766
  // ---------------------------------------------------------------------------
805
767
  // Session Lifecycle
806
768
  // ---------------------------------------------------------------------------
@@ -1165,6 +1127,13 @@ class DashClaw {
1165
1127
  return this._request(`/api/capabilities/${capabilityId}`, 'PATCH', patch);
1166
1128
  }
1167
1129
 
1130
+ /**
1131
+ * DELETE /api/capabilities/:id — Delete a capability.
1132
+ */
1133
+ async deleteCapability(capabilityId) {
1134
+ return this._request(`/api/capabilities/${capabilityId}`, 'DELETE');
1135
+ }
1136
+
1168
1137
  /**
1169
1138
  * POST /api/capabilities/:id/invoke — Invoke a governed capability.
1170
1139
  */
package/index.cjs CHANGED
@@ -1,14 +1,11 @@
1
1
  /**
2
2
  * DashClaw SDK v2 (Stable Runtime API)
3
3
  * CommonJS compatibility bridge.
4
- *
4
+ *
5
5
  * ESM: import { DashClaw } from 'dashclaw'
6
6
  * CJS: const { DashClaw } = require('dashclaw')
7
7
  */
8
8
 
9
- const fs = require('fs');
10
- const path = require('path');
11
-
12
9
  // Minimal CommonJS shim for the v2 SDK
13
10
  // We use a simplified bridge that forwards calls to the async ESM import
14
11
  let _module;
@@ -20,6 +17,55 @@ async function loadModule() {
20
17
  return _module;
21
18
  }
22
19
 
20
+ // Lazy error class factory: constructs a placeholder class that delegates
21
+ // instanceof checks to the real ESM class once the module loads, matching
22
+ // the Symbol.hasInstance pattern from legacy/index-v1.cjs so that
23
+ // catch(e) { if (e instanceof ApprovalDeniedError) } works across the
24
+ // ESM/CJS boundary.
25
+ function makeLazyErrorClass(name) {
26
+ const Placeholder = class extends Error {
27
+ constructor(...args) {
28
+ super(...args);
29
+ this.name = name;
30
+ }
31
+ };
32
+ Object.defineProperty(Placeholder, 'name', { value: name });
33
+ loadModule().then(m => {
34
+ if (m[name]) {
35
+ Object.defineProperty(Placeholder, Symbol.hasInstance, {
36
+ value: (instance) => instance && (instance.name === name || instance instanceof m[name])
37
+ });
38
+ }
39
+ });
40
+ return Placeholder;
41
+ }
42
+
43
+ // Recursive deferred proxy. Each property access records the access path and
44
+ // returns another callable proxy; invoking the leaf awaits the async ESM import,
45
+ // walks the path on the resolved instance, and calls the real method. This makes
46
+ // both flat methods (client.guard(...)) and nested namespaces
47
+ // (client.execution.capabilities.list(...)) work across the CJS bridge, where the
48
+ // ESM instance only exists after an async import.
49
+ function makeDeferred(target, path) {
50
+ return new Proxy(function () {}, {
51
+ get(_t, prop) {
52
+ if (prop === 'then' || typeof prop === 'symbol') return undefined;
53
+ return makeDeferred(target, path.concat(String(prop)));
54
+ },
55
+ apply(_t, _thisArg, args) {
56
+ return target._ready.then(() => {
57
+ let parent = target._instance;
58
+ for (let i = 0; i < path.length - 1; i++) parent = parent[path[i]];
59
+ const leaf = parent && parent[path[path.length - 1]];
60
+ if (typeof leaf !== 'function') {
61
+ throw new Error(`Method ${path.join('.')} does not exist on DashClaw v2`);
62
+ }
63
+ return leaf.apply(parent, args);
64
+ });
65
+ },
66
+ });
67
+ }
68
+
23
69
  module.exports = {
24
70
  // Sync wrapper that returns a proxy for the DashClaw class
25
71
  DashClaw: class DashClawProxy {
@@ -32,15 +78,10 @@ module.exports = {
32
78
  return new Proxy(this, {
33
79
  get(target, prop) {
34
80
  if (prop in target) return target[prop];
35
- if (prop === 'then') return undefined;
36
-
37
- return async (...args) => {
38
- await target._ready;
39
- if (!target._instance[prop]) {
40
- throw new Error(`Method ${String(prop)} does not exist on DashClaw v2`);
41
- }
42
- return target._instance[prop](...args);
43
- };
81
+ if (prop === 'then' || typeof prop === 'symbol') return undefined;
82
+ // Defer to the async ESM instance; supports flat methods and
83
+ // nested namespaces (e.g. client.execution.capabilities.list(...)).
84
+ return makeDeferred(target, [String(prop)]);
44
85
  }
45
86
  });
46
87
  }
@@ -51,20 +92,8 @@ module.exports = {
51
92
  }
52
93
  },
53
94
 
54
- // Errors from v2
55
- ApprovalDeniedError: class ApprovalDeniedError extends Error {
56
- constructor(message, decision) {
57
- super(message);
58
- this.name = 'ApprovalDeniedError';
59
- this.decision = decision;
60
- }
61
- },
62
-
63
- GuardBlockedError: class GuardBlockedError extends Error {
64
- constructor(decision) {
65
- super(decision.reason || 'Action blocked by policy');
66
- this.name = 'GuardBlockedError';
67
- this.decision = decision;
68
- }
69
- }
95
+ // Errors from v2 — lazy re-exports that resolve instanceof across the
96
+ // ESM/CJS boundary once the module has loaded.
97
+ ApprovalDeniedError: makeLazyErrorClass('ApprovalDeniedError'),
98
+ GuardBlockedError: makeLazyErrorClass('GuardBlockedError'),
70
99
  };
@@ -1419,6 +1419,7 @@ class DashClaw {
1419
1419
 
1420
1420
  /**
1421
1421
  * Capture a key point from the current session.
1422
+ * @deprecated The /api/context/points endpoint was retired in platform v4. This method always 404s.
1422
1423
  * @param {Object} point
1423
1424
  * @param {string} point.content - The key point content
1424
1425
  * @param {string} [point.category] - One of: decision, task, insight, question, general
@@ -1427,6 +1428,7 @@ class DashClaw {
1427
1428
  * @returns {Promise<{point: Object, point_id: string}>}
1428
1429
  */
1429
1430
  async captureKeyPoint(point) {
1431
+ console.warn('[DashClaw] captureKeyPoint targets a retired endpoint (/api/context/points) and will 404. Upgrade to platform v4+.');
1430
1432
  return this._request('/api/context/points', 'POST', {
1431
1433
  agent_id: this.agentId,
1432
1434
  ...point
@@ -1435,6 +1437,7 @@ class DashClaw {
1435
1437
 
1436
1438
  /**
1437
1439
  * Get key points with optional filters.
1440
+ * @deprecated The /api/context/points endpoint was retired in platform v4. This method always 404s.
1438
1441
  * @param {Object} [filters]
1439
1442
  * @param {string} [filters.category] - Filter by category
1440
1443
  * @param {string} [filters.session_date] - Filter by date
@@ -1442,6 +1445,7 @@ class DashClaw {
1442
1445
  * @returns {Promise<{points: Object[], total: number}>}
1443
1446
  */
1444
1447
  async getKeyPoints(filters = {}) {
1448
+ console.warn('[DashClaw] getKeyPoints targets a retired endpoint (/api/context/points) and will 404. Upgrade to platform v4+.');
1445
1449
  const params = new URLSearchParams({ agent_id: this.agentId });
1446
1450
  if (filters.category) params.set('category', filters.category);
1447
1451
  if (filters.session_date) params.set('session_date', filters.session_date);
@@ -1451,12 +1455,14 @@ class DashClaw {
1451
1455
 
1452
1456
  /**
1453
1457
  * Create a context thread for tracking a topic across entries.
1458
+ * @deprecated The /api/context/threads endpoint was retired in platform v4. This method always 404s.
1454
1459
  * @param {Object} thread
1455
1460
  * @param {string} thread.name - Thread name (unique per agent per org)
1456
1461
  * @param {string} [thread.summary] - Initial summary
1457
1462
  * @returns {Promise<{thread: Object, thread_id: string}>}
1458
1463
  */
1459
1464
  async createThread(thread) {
1465
+ console.warn('[DashClaw] createThread targets a retired endpoint (/api/context/threads) and will 404. Upgrade to platform v4+.');
1460
1466
  return this._request('/api/context/threads', 'POST', {
1461
1467
  agent_id: this.agentId,
1462
1468
  ...thread
@@ -1465,12 +1471,14 @@ class DashClaw {
1465
1471
 
1466
1472
  /**
1467
1473
  * Add an entry to an existing thread.
1474
+ * @deprecated The /api/context/threads endpoint was retired in platform v4. This method always 404s.
1468
1475
  * @param {string} threadId - The thread ID
1469
1476
  * @param {string} content - Entry content
1470
1477
  * @param {string} [entryType] - Entry type (default: 'note')
1471
1478
  * @returns {Promise<{entry: Object, entry_id: string}>}
1472
1479
  */
1473
1480
  async addThreadEntry(threadId, content, entryType) {
1481
+ console.warn('[DashClaw] addThreadEntry targets a retired endpoint (/api/context/threads) and will 404. Upgrade to platform v4+.');
1474
1482
  return this._request(`/api/context/threads/${threadId}/entries`, 'POST', {
1475
1483
  content,
1476
1484
  entry_type: entryType || 'note'
@@ -1479,11 +1487,13 @@ class DashClaw {
1479
1487
 
1480
1488
  /**
1481
1489
  * Close a thread with an optional summary.
1490
+ * @deprecated The /api/context/threads endpoint was retired in platform v4. This method always 404s.
1482
1491
  * @param {string} threadId - The thread ID
1483
1492
  * @param {string} [summary] - Final summary
1484
1493
  * @returns {Promise<{thread: Object}>}
1485
1494
  */
1486
1495
  async closeThread(threadId, summary) {
1496
+ console.warn('[DashClaw] closeThread targets a retired endpoint (/api/context/threads) and will 404. Upgrade to platform v4+.');
1487
1497
  const body = { status: 'closed' };
1488
1498
  if (summary) body.summary = summary;
1489
1499
  return this._request(`/api/context/threads/${threadId}`, 'PATCH', body);
@@ -1491,12 +1501,14 @@ class DashClaw {
1491
1501
 
1492
1502
  /**
1493
1503
  * Get threads with optional filters.
1504
+ * @deprecated The /api/context/threads endpoint was retired in platform v4. This method always 404s.
1494
1505
  * @param {Object} [filters]
1495
1506
  * @param {string} [filters.status] - Filter by status (active, closed)
1496
1507
  * @param {number} [filters.limit] - Max results
1497
1508
  * @returns {Promise<{threads: Object[], total: number}>}
1498
1509
  */
1499
1510
  async getThreads(filters = {}) {
1511
+ console.warn('[DashClaw] getThreads targets a retired endpoint (/api/context/threads) and will 404. Upgrade to platform v4+.');
1500
1512
  const params = new URLSearchParams({ agent_id: this.agentId });
1501
1513
  if (filters.status) params.set('status', filters.status);
1502
1514
  if (filters.limit) params.set('limit', String(filters.limit));
@@ -1505,9 +1517,11 @@ class DashClaw {
1505
1517
 
1506
1518
  /**
1507
1519
  * Get a combined context summary: today's key points + active threads.
1520
+ * @deprecated The /api/context endpoints were retired in platform v4. This method always 404s.
1508
1521
  * @returns {Promise<{points: Object[], threads: Object[]}>}
1509
1522
  */
1510
1523
  async getContextSummary() {
1524
+ console.warn('[DashClaw] getContextSummary targets retired endpoints (/api/context/*) and will 404. Upgrade to platform v4+.');
1511
1525
  const today = new Date().toISOString().split('T')[0];
1512
1526
  const [pointsResult, threadsResult] = await Promise.all([
1513
1527
  this.getKeyPoints({ session_date: today }),
@@ -1793,11 +1807,6 @@ class DashClaw {
1793
1807
  return this._request(`/api/messages?${params}`, 'GET');
1794
1808
  }
1795
1809
 
1796
- /**
1797
- * Mark messages as read.
1798
- * @param {string[]} messageIds - Array of message IDs to mark read
1799
- * @returns {Promise<{updated: number}>}
1800
- */
1801
1810
  /**
1802
1811
  * Get sent messages from this agent.
1803
1812
  * @param {Object} [params]
@@ -2818,10 +2827,12 @@ class DashClaw {
2818
2827
  }
2819
2828
 
2820
2829
  async scoreWithProfile(profileId, action) {
2830
+ if (Array.isArray(action)) throw new TypeError('use batchScoreWithProfile for arrays');
2821
2831
  return this._request('POST', '/api/scoring/score', { profile_id: profileId, action });
2822
2832
  }
2823
2833
 
2824
2834
  async batchScoreWithProfile(profileId, actions) {
2835
+ if (!Array.isArray(actions)) throw new TypeError('batchScoreWithProfile expects an array');
2825
2836
  return this._request('POST', '/api/scoring/score', { profile_id: profileId, actions });
2826
2837
  }
2827
2838
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashclaw",
3
- "version": "3.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "Minimal governance runtime for AI agents. Intercept, govern, and verify agent actions.",
5
5
  "type": "module",
6
6
  "publishConfig": {