dashclaw 1.0.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.
Files changed (5) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +177 -0
  3. package/dashclaw.js +1089 -0
  4. package/index.cjs +66 -0
  5. package/package.json +40 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenClaw
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # DashClaw
2
+
3
+ Full-featured agent toolkit for the [DashClaw](https://github.com/ucsandman/DashClaw) platform. Zero dependencies, requires Node 18+ (native fetch).
4
+
5
+ **57 methods** across 13 categories: action recording, context management, session handoffs, security scanning, agent messaging, behavior guard, user preferences, and more.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install dashclaw
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```js
16
+ import { DashClaw } from 'dashclaw';
17
+
18
+ const claw = new DashClaw({
19
+ baseUrl: 'https://your-app.vercel.app',
20
+ apiKey: process.env.DASHCLAW_API_KEY,
21
+ agentId: 'my-agent',
22
+ agentName: 'My Agent',
23
+ });
24
+
25
+ // Record an action
26
+ const { action_id } = await claw.createAction({
27
+ action_type: 'deploy',
28
+ declared_goal: 'Deploy auth service to production',
29
+ risk_score: 60,
30
+ });
31
+
32
+ // ... do the work ...
33
+
34
+ await claw.updateOutcome(action_id, {
35
+ status: 'completed',
36
+ output_summary: 'Auth service deployed successfully',
37
+ });
38
+ ```
39
+
40
+ ## Migration from OpenClawAgent
41
+
42
+ The backward-compatible alias `OpenClawAgent` is preserved:
43
+
44
+ ```js
45
+ // Old:
46
+ import { OpenClawAgent } from './openclaw-agent.js';
47
+ // New (both work):
48
+ import { DashClaw } from 'dashclaw';
49
+ import { OpenClawAgent } from 'dashclaw';
50
+ ```
51
+
52
+ ## API Reference (54 methods)
53
+
54
+ ### Action Recording (6)
55
+
56
+ | Method | Description |
57
+ |--------|-------------|
58
+ | `createAction(action)` | Record a new action |
59
+ | `updateOutcome(actionId, outcome)` | Update action result |
60
+ | `getActions(filters?)` | List actions with filters |
61
+ | `getAction(actionId)` | Get single action with loops + assumptions |
62
+ | `getActionTrace(actionId)` | Get root-cause trace |
63
+ | `track(actionDef, fn)` | Auto-tracked action wrapper |
64
+
65
+ ### Loops & Assumptions (7)
66
+
67
+ | Method | Description |
68
+ |--------|-------------|
69
+ | `registerOpenLoop(loop)` | Register an unresolved item |
70
+ | `resolveOpenLoop(loopId, status, resolution)` | Close an open loop |
71
+ | `getOpenLoops(filters?)` | List open loops |
72
+ | `registerAssumption(assumption)` | Record an assumption |
73
+ | `getAssumption(assumptionId)` | Get single assumption |
74
+ | `validateAssumption(id, validated, reason?)` | Validate or invalidate |
75
+ | `getDriftReport(filters?)` | Get assumption drift scores |
76
+
77
+ ### Signals (1)
78
+
79
+ | Method | Description |
80
+ |--------|-------------|
81
+ | `getSignals()` | Get computed risk signals |
82
+
83
+ ### Dashboard Data (8)
84
+
85
+ | Method | Description |
86
+ |--------|-------------|
87
+ | `recordDecision(entry)` | Record a decision |
88
+ | `createGoal(goal)` | Create a goal |
89
+ | `recordContent(content)` | Record content creation |
90
+ | `recordInteraction(interaction)` | Record a relationship interaction |
91
+ | `reportConnections(connections)` | Report agent integrations |
92
+ | `createCalendarEvent(event)` | Create a calendar event |
93
+ | `recordIdea(idea)` | Record an idea/inspiration |
94
+ | `reportMemoryHealth(report)` | Report memory health snapshot |
95
+
96
+ ### Session Handoffs (3)
97
+
98
+ | Method | Description |
99
+ |--------|-------------|
100
+ | `createHandoff(handoff)` | Create a session handoff document |
101
+ | `getHandoffs(filters?)` | Get handoffs for this agent |
102
+ | `getLatestHandoff()` | Get the most recent handoff |
103
+
104
+ ### Context Manager (7)
105
+
106
+ | Method | Description |
107
+ |--------|-------------|
108
+ | `captureKeyPoint(point)` | Capture a key point |
109
+ | `getKeyPoints(filters?)` | Get key points |
110
+ | `createThread(thread)` | Create a context thread |
111
+ | `addThreadEntry(threadId, content)` | Add entry to a thread |
112
+ | `closeThread(threadId, summary?)` | Close a thread |
113
+ | `getThreads(filters?)` | Get threads |
114
+ | `getContextSummary()` | Today's points + active threads |
115
+
116
+ ### Automation Snippets (4)
117
+
118
+ | Method | Description |
119
+ |--------|-------------|
120
+ | `saveSnippet(snippet)` | Save/update a code snippet |
121
+ | `getSnippets(filters?)` | Search and list snippets |
122
+ | `useSnippet(snippetId)` | Mark snippet as used |
123
+ | `deleteSnippet(snippetId)` | Delete a snippet |
124
+
125
+ ### User Preferences (6)
126
+
127
+ | Method | Description |
128
+ |--------|-------------|
129
+ | `logObservation(obs)` | Log a user observation |
130
+ | `setPreference(pref)` | Set a learned preference |
131
+ | `logMood(entry)` | Log user mood/energy |
132
+ | `trackApproach(entry)` | Track approach success/failure |
133
+ | `getPreferenceSummary()` | Get preference summary |
134
+ | `getApproaches(filters?)` | Get tracked approaches |
135
+
136
+ ### Daily Digest (1)
137
+
138
+ | Method | Description |
139
+ |--------|-------------|
140
+ | `getDailyDigest(date?)` | Aggregated daily summary |
141
+
142
+ ### Security Scanning (2)
143
+
144
+ | Method | Description |
145
+ |--------|-------------|
146
+ | `scanContent(text, destination?)` | Scan text for sensitive data |
147
+ | `reportSecurityFinding(text, dest?)` | Scan + store finding metadata |
148
+
149
+ ### Agent Messaging (9)
150
+
151
+ | Method | Description |
152
+ |--------|-------------|
153
+ | `sendMessage(params)` | Send message to agent or broadcast |
154
+ | `getInbox(filters?)` | Get inbox messages |
155
+ | `markRead(messageIds)` | Mark messages as read |
156
+ | `archiveMessages(messageIds)` | Archive messages |
157
+ | `broadcast(params)` | Send to all agents in org |
158
+ | `createMessageThread(params)` | Start a conversation thread |
159
+ | `getMessageThreads(filters?)` | List message threads |
160
+ | `resolveMessageThread(threadId, summary?)` | Close a thread |
161
+ | `saveSharedDoc(params)` | Create/update shared document |
162
+
163
+ ## Authentication
164
+
165
+ The SDK sends your API key via the `x-api-key` header. The key determines which organization's data you access.
166
+
167
+ ```js
168
+ const claw = new DashClaw({
169
+ baseUrl: 'https://your-deployment.vercel.app',
170
+ apiKey: process.env.DASHCLAW_API_KEY,
171
+ agentId: 'my-agent',
172
+ });
173
+ ```
174
+
175
+ ## License
176
+
177
+ MIT
package/dashclaw.js ADDED
@@ -0,0 +1,1089 @@
1
+ /**
2
+ * DashClaw SDK
3
+ * Full-featured agent toolkit for the DashClaw platform.
4
+ * Zero-dependency ESM SDK — requires Node 18+ (native fetch).
5
+ *
6
+ * 57 methods across 13 categories:
7
+ * - Action Recording (6)
8
+ * - Loops & Assumptions (7)
9
+ * - Signals (1)
10
+ * - Dashboard Data (8)
11
+ * - Session Handoffs (3)
12
+ * - Context Manager (7)
13
+ * - Automation Snippets (4)
14
+ * - User Preferences (6)
15
+ * - Daily Digest (1)
16
+ * - Security Scanning (2)
17
+ * - Agent Messaging (9)
18
+ * - Behavior Guard (2)
19
+ * - Bulk Sync (1)
20
+ */
21
+
22
+ class DashClaw {
23
+ /**
24
+ * @param {Object} options
25
+ * @param {string} options.baseUrl - DashClaw base URL (e.g. "https://your-app.vercel.app")
26
+ * @param {string} options.apiKey - API key for authentication (determines which org's data you access)
27
+ * @param {string} options.agentId - Unique identifier for this agent
28
+ * @param {string} [options.agentName] - Human-readable agent name
29
+ * @param {string} [options.swarmId] - Swarm/group identifier if part of a multi-agent system
30
+ * @param {string} [options.guardMode='off'] - Auto guard check before createAction: 'off' | 'warn' | 'enforce'
31
+ * @param {Function} [options.guardCallback] - Called with guard decision object when guardMode is active
32
+ */
33
+ constructor({ baseUrl, apiKey, agentId, agentName, swarmId, guardMode, guardCallback }) {
34
+ if (!baseUrl) throw new Error('baseUrl is required');
35
+ if (!apiKey) throw new Error('apiKey is required');
36
+ if (!agentId) throw new Error('agentId is required');
37
+
38
+ const validModes = ['off', 'warn', 'enforce'];
39
+ if (guardMode && !validModes.includes(guardMode)) {
40
+ throw new Error(`guardMode must be one of: ${validModes.join(', ')}`);
41
+ }
42
+
43
+ this.baseUrl = baseUrl.replace(/\/$/, '');
44
+ this.apiKey = apiKey;
45
+ this.agentId = agentId;
46
+ this.agentName = agentName || null;
47
+ this.swarmId = swarmId || null;
48
+ this.guardMode = guardMode || 'off';
49
+ this.guardCallback = guardCallback || null;
50
+ }
51
+
52
+ async _request(path, method, body) {
53
+ const url = `${this.baseUrl}${path}`;
54
+ const headers = {
55
+ 'Content-Type': 'application/json',
56
+ 'x-api-key': this.apiKey
57
+ };
58
+
59
+ const res = await fetch(url, {
60
+ method,
61
+ headers,
62
+ body: body ? JSON.stringify(body) : undefined
63
+ });
64
+
65
+ const data = await res.json();
66
+
67
+ if (!res.ok) {
68
+ const err = new Error(data.error || `Request failed with status ${res.status}`);
69
+ err.status = res.status;
70
+ err.details = data.details;
71
+ throw err;
72
+ }
73
+
74
+ return data;
75
+ }
76
+
77
+ /**
78
+ * Internal: check guard policies before action creation.
79
+ * Only active when guardMode is 'warn' or 'enforce'.
80
+ * @param {Object} actionDef - Action definition from createAction()
81
+ */
82
+ async _guardCheck(actionDef) {
83
+ if (this.guardMode === 'off') return;
84
+
85
+ const context = {
86
+ action_type: actionDef.action_type,
87
+ risk_score: actionDef.risk_score,
88
+ systems_touched: actionDef.systems_touched,
89
+ reversible: actionDef.reversible,
90
+ declared_goal: actionDef.declared_goal,
91
+ };
92
+
93
+ let decision;
94
+ try {
95
+ decision = await this.guard(context);
96
+ } catch (err) {
97
+ // Guard API failure is fail-open — log and proceed
98
+ console.warn(`[DashClaw] Guard check failed (proceeding): ${err.message}`);
99
+ return;
100
+ }
101
+
102
+ if (this.guardCallback) {
103
+ try { this.guardCallback(decision); } catch { /* ignore callback errors */ }
104
+ }
105
+
106
+ const isBlocked = decision.decision === 'block' || decision.decision === 'require_approval';
107
+
108
+ if (this.guardMode === 'warn' && isBlocked) {
109
+ console.warn(
110
+ `[DashClaw] Guard ${decision.decision}: ${decision.reasons.join('; ') || 'no reason'}. Proceeding in warn mode.`
111
+ );
112
+ return;
113
+ }
114
+
115
+ if (this.guardMode === 'enforce' && isBlocked) {
116
+ throw new GuardBlockedError(decision);
117
+ }
118
+ }
119
+
120
+ // ══════════════════════════════════════════════
121
+ // Category 1: Action Recording (6 methods)
122
+ // ══════════════════════════════════════════════
123
+
124
+ /**
125
+ * Create a new action record.
126
+ * @param {Object} action
127
+ * @param {string} action.action_type - One of: build, deploy, post, apply, security, message, api, calendar, research, review, fix, refactor, test, config, monitor, alert, cleanup, sync, migrate, other
128
+ * @param {string} action.declared_goal - What this action aims to accomplish
129
+ * @param {string} [action.action_id] - Custom action ID (auto-generated if omitted)
130
+ * @param {string} [action.reasoning] - Why the agent decided to take this action
131
+ * @param {string} [action.authorization_scope] - What permissions were granted
132
+ * @param {string} [action.trigger] - What triggered this action
133
+ * @param {string[]} [action.systems_touched] - Systems this action interacts with
134
+ * @param {string} [action.input_summary] - Summary of input data
135
+ * @param {string} [action.parent_action_id] - Parent action if this is a sub-action
136
+ * @param {boolean} [action.reversible=true] - Whether this action can be undone
137
+ * @param {number} [action.risk_score=0] - Risk score 0-100
138
+ * @param {number} [action.confidence=50] - Confidence level 0-100
139
+ * @returns {Promise<{action: Object, action_id: string}>}
140
+ */
141
+ async createAction(action) {
142
+ await this._guardCheck(action);
143
+ return this._request('/api/actions', 'POST', {
144
+ agent_id: this.agentId,
145
+ agent_name: this.agentName,
146
+ swarm_id: this.swarmId,
147
+ ...action
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Update the outcome of an existing action.
153
+ * @param {string} actionId - The action_id to update
154
+ * @param {Object} outcome
155
+ * @param {string} [outcome.status] - New status: completed, failed, cancelled
156
+ * @param {string} [outcome.output_summary] - What happened
157
+ * @param {string[]} [outcome.side_effects] - Unintended consequences
158
+ * @param {string[]} [outcome.artifacts_created] - Files, records, etc. created
159
+ * @param {string} [outcome.error_message] - Error details if failed
160
+ * @param {number} [outcome.duration_ms] - How long it took
161
+ * @param {number} [outcome.cost_estimate] - Estimated cost in USD
162
+ * @returns {Promise<{action: Object}>}
163
+ */
164
+ async updateOutcome(actionId, outcome) {
165
+ return this._request(`/api/actions/${actionId}`, 'PATCH', {
166
+ ...outcome,
167
+ timestamp_end: outcome.timestamp_end || new Date().toISOString()
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Get a list of actions with optional filters.
173
+ * @param {Object} [filters]
174
+ * @param {string} [filters.agent_id] - Filter by agent
175
+ * @param {string} [filters.swarm_id] - Filter by swarm
176
+ * @param {string} [filters.status] - Filter by status
177
+ * @param {string} [filters.action_type] - Filter by type
178
+ * @param {number} [filters.risk_min] - Minimum risk score
179
+ * @param {number} [filters.limit=50] - Max results
180
+ * @param {number} [filters.offset=0] - Pagination offset
181
+ * @returns {Promise<{actions: Object[], total: number, stats: Object}>}
182
+ */
183
+ async getActions(filters = {}) {
184
+ const params = new URLSearchParams();
185
+ for (const [key, value] of Object.entries(filters)) {
186
+ if (value !== undefined && value !== null && value !== '') {
187
+ params.set(key, String(value));
188
+ }
189
+ }
190
+ return this._request(`/api/actions?${params}`, 'GET');
191
+ }
192
+
193
+ /**
194
+ * Get a single action with its open loops and assumptions.
195
+ * @param {string} actionId
196
+ * @returns {Promise<{action: Object, open_loops: Object[], assumptions: Object[]}>}
197
+ */
198
+ async getAction(actionId) {
199
+ return this._request(`/api/actions/${actionId}`, 'GET');
200
+ }
201
+
202
+ /**
203
+ * Get root-cause trace for an action.
204
+ * @param {string} actionId
205
+ * @returns {Promise<{action: Object, trace: Object}>}
206
+ */
207
+ async getActionTrace(actionId) {
208
+ return this._request(`/api/actions/${actionId}/trace`, 'GET');
209
+ }
210
+
211
+ /**
212
+ * Helper: Create an action, run a function, and auto-update the outcome.
213
+ * @param {Object} actionDef - Action definition (same as createAction)
214
+ * @param {Function} fn - Async function to execute. Receives { action_id } as argument.
215
+ * @returns {Promise<*>} - The return value of fn
216
+ */
217
+ async track(actionDef, fn) {
218
+ const startTime = Date.now();
219
+ const { action_id } = await this.createAction(actionDef);
220
+
221
+ try {
222
+ const result = await fn({ action_id });
223
+ await this.updateOutcome(action_id, {
224
+ status: 'completed',
225
+ duration_ms: Date.now() - startTime,
226
+ output_summary: typeof result === 'string' ? result : JSON.stringify(result)
227
+ });
228
+ return result;
229
+ } catch (error) {
230
+ await this.updateOutcome(action_id, {
231
+ status: 'failed',
232
+ duration_ms: Date.now() - startTime,
233
+ error_message: error.message || String(error)
234
+ }).catch(() => {}); // Don't throw if outcome update fails
235
+ throw error;
236
+ }
237
+ }
238
+
239
+ // ══════════════════════════════════════════════
240
+ // Category 2: Loops & Assumptions (7 methods)
241
+ // ══════════════════════════════════════════════
242
+
243
+ /**
244
+ * Register an open loop for an action.
245
+ * @param {Object} loop
246
+ * @param {string} loop.action_id - Parent action ID
247
+ * @param {string} loop.loop_type - One of: followup, question, dependency, approval, review, handoff, other
248
+ * @param {string} loop.description - What needs to be resolved
249
+ * @param {string} [loop.priority='medium'] - One of: low, medium, high, critical
250
+ * @param {string} [loop.owner] - Who is responsible for resolving this
251
+ * @returns {Promise<{loop: Object, loop_id: string}>}
252
+ */
253
+ async registerOpenLoop(loop) {
254
+ return this._request('/api/actions/loops', 'POST', loop);
255
+ }
256
+
257
+ /**
258
+ * Resolve or cancel an open loop.
259
+ * @param {string} loopId - The loop_id to resolve
260
+ * @param {string} status - 'resolved' or 'cancelled'
261
+ * @param {string} [resolution] - Resolution description (required when resolving)
262
+ * @returns {Promise<{loop: Object}>}
263
+ */
264
+ async resolveOpenLoop(loopId, status, resolution) {
265
+ return this._request(`/api/actions/loops/${loopId}`, 'PATCH', {
266
+ status,
267
+ resolution
268
+ });
269
+ }
270
+
271
+ /**
272
+ * Get open loops with optional filters.
273
+ * @param {Object} [filters]
274
+ * @param {string} [filters.status] - Filter by status (open, resolved, cancelled)
275
+ * @param {string} [filters.loop_type] - Filter by loop type
276
+ * @param {string} [filters.priority] - Filter by priority
277
+ * @param {number} [filters.limit=50] - Max results
278
+ * @returns {Promise<{loops: Object[], total: number, stats: Object}>}
279
+ */
280
+ async getOpenLoops(filters = {}) {
281
+ const params = new URLSearchParams();
282
+ for (const [key, value] of Object.entries(filters)) {
283
+ if (value !== undefined && value !== null && value !== '') {
284
+ params.set(key, String(value));
285
+ }
286
+ }
287
+ return this._request(`/api/actions/loops?${params}`, 'GET');
288
+ }
289
+
290
+ /**
291
+ * Register assumptions made during an action.
292
+ * @param {Object} assumption
293
+ * @param {string} assumption.action_id - Parent action ID
294
+ * @param {string} assumption.assumption - The assumption being made
295
+ * @param {string} [assumption.basis] - Evidence or reasoning for the assumption
296
+ * @param {boolean} [assumption.validated=false] - Whether this has been validated
297
+ * @returns {Promise<{assumption: Object, assumption_id: string}>}
298
+ */
299
+ async registerAssumption(assumption) {
300
+ return this._request('/api/actions/assumptions', 'POST', assumption);
301
+ }
302
+
303
+ /**
304
+ * Get a single assumption by ID.
305
+ * @param {string} assumptionId
306
+ * @returns {Promise<{assumption: Object}>}
307
+ */
308
+ async getAssumption(assumptionId) {
309
+ return this._request(`/api/actions/assumptions/${assumptionId}`, 'GET');
310
+ }
311
+
312
+ /**
313
+ * Validate or invalidate an assumption.
314
+ * @param {string} assumptionId - The assumption_id to update
315
+ * @param {boolean} validated - true to validate, false to invalidate
316
+ * @param {string} [invalidated_reason] - Required when invalidating
317
+ * @returns {Promise<{assumption: Object}>}
318
+ */
319
+ async validateAssumption(assumptionId, validated, invalidated_reason) {
320
+ if (typeof validated !== 'boolean') throw new Error('validated must be a boolean');
321
+ if (validated === false && !invalidated_reason) {
322
+ throw new Error('invalidated_reason is required when invalidating an assumption');
323
+ }
324
+ const body = { validated };
325
+ if (invalidated_reason !== undefined) body.invalidated_reason = invalidated_reason;
326
+ return this._request(`/api/actions/assumptions/${assumptionId}`, 'PATCH', body);
327
+ }
328
+
329
+ /**
330
+ * Get drift report for assumptions with risk scoring.
331
+ * @param {Object} [filters]
332
+ * @param {string} [filters.action_id] - Filter by action
333
+ * @param {number} [filters.limit=50] - Max results
334
+ * @returns {Promise<{assumptions: Object[], drift_summary: Object}>}
335
+ */
336
+ async getDriftReport(filters = {}) {
337
+ const params = new URLSearchParams({ drift: 'true' });
338
+ for (const [key, value] of Object.entries(filters)) {
339
+ if (value !== undefined && value !== null && value !== '') {
340
+ params.set(key, String(value));
341
+ }
342
+ }
343
+ return this._request(`/api/actions/assumptions?${params}`, 'GET');
344
+ }
345
+
346
+ // ══════════════════════════════════════════════
347
+ // Category 3: Signals (1 method)
348
+ // ══════════════════════════════════════════════
349
+
350
+ /**
351
+ * Get current risk signals.
352
+ * @returns {Promise<{signals: Object[], counts: {red: number, amber: number, total: number}}>}
353
+ */
354
+ async getSignals() {
355
+ return this._request('/api/actions/signals', 'GET');
356
+ }
357
+
358
+ // ══════════════════════════════════════════════
359
+ // Category 4: Dashboard Data (8 methods)
360
+ // ══════════════════════════════════════════════
361
+
362
+ /**
363
+ * Report token usage snapshot (disabled in dashboard, API still functional).
364
+ * @param {Object} usage
365
+ * @param {number} usage.tokens_in - Input tokens consumed
366
+ * @param {number} usage.tokens_out - Output tokens generated
367
+ * @param {number} [usage.context_used] - Context window tokens used
368
+ * @param {number} [usage.context_max] - Context window max capacity
369
+ * @param {string} [usage.model] - Model name
370
+ * @returns {Promise<{snapshot: Object}>}
371
+ */
372
+ async reportTokenUsage(usage) {
373
+ return this._request('/api/tokens', 'POST', {
374
+ ...usage,
375
+ agent_id: this.agentId
376
+ });
377
+ }
378
+
379
+ /**
380
+ * Record a decision for the learning database.
381
+ * @param {Object} entry
382
+ * @param {string} entry.decision - What was decided
383
+ * @param {string} [entry.context] - Context around the decision
384
+ * @param {string} [entry.reasoning] - Why this decision was made
385
+ * @param {string} [entry.outcome] - 'success', 'failure', or 'pending'
386
+ * @param {number} [entry.confidence] - Confidence level 0-100
387
+ * @returns {Promise<{decision: Object}>}
388
+ */
389
+ async recordDecision(entry) {
390
+ return this._request('/api/learning', 'POST', {
391
+ ...entry,
392
+ agent_id: this.agentId
393
+ });
394
+ }
395
+
396
+ /**
397
+ * Create a goal.
398
+ * @param {Object} goal
399
+ * @param {string} goal.title - Goal title
400
+ * @param {string} [goal.category] - Goal category
401
+ * @param {string} [goal.description] - Detailed description
402
+ * @param {string} [goal.target_date] - Target completion date (ISO string)
403
+ * @param {number} [goal.progress] - Progress 0-100
404
+ * @param {string} [goal.status] - 'active', 'completed', 'paused'
405
+ * @returns {Promise<{goal: Object}>}
406
+ */
407
+ async createGoal(goal) {
408
+ return this._request('/api/goals', 'POST', {
409
+ ...goal,
410
+ agent_id: this.agentId
411
+ });
412
+ }
413
+
414
+ /**
415
+ * Record content creation.
416
+ * @param {Object} content
417
+ * @param {string} content.title - Content title
418
+ * @param {string} [content.platform] - Platform (e.g., 'linkedin', 'twitter')
419
+ * @param {string} [content.status] - 'draft' or 'published'
420
+ * @param {string} [content.url] - Published URL
421
+ * @returns {Promise<{content: Object}>}
422
+ */
423
+ async recordContent(content) {
424
+ return this._request('/api/content', 'POST', {
425
+ ...content,
426
+ agent_id: this.agentId
427
+ });
428
+ }
429
+
430
+ /**
431
+ * Record a relationship interaction.
432
+ * @param {Object} interaction
433
+ * @param {string} interaction.summary - What happened
434
+ * @param {string} [interaction.contact_name] - Contact name (auto-resolves to contact_id)
435
+ * @param {string} [interaction.contact_id] - Direct contact ID
436
+ * @param {string} [interaction.direction] - 'inbound' or 'outbound'
437
+ * @param {string} [interaction.type] - Interaction type
438
+ * @param {string} [interaction.platform] - Platform used
439
+ * @returns {Promise<{interaction: Object}>}
440
+ */
441
+ async recordInteraction(interaction) {
442
+ return this._request('/api/relationships', 'POST', {
443
+ ...interaction,
444
+ agent_id: this.agentId
445
+ });
446
+ }
447
+
448
+ /**
449
+ * Create a calendar event.
450
+ * @param {Object} event
451
+ * @param {string} event.summary - Event title/summary
452
+ * @param {string} event.start_time - Start time (ISO string)
453
+ * @param {string} [event.end_time] - End time (ISO string)
454
+ * @param {string} [event.location] - Event location
455
+ * @param {string} [event.description] - Event description
456
+ * @returns {Promise<{event: Object}>}
457
+ */
458
+ async createCalendarEvent(event) {
459
+ return this._request('/api/calendar', 'POST', event);
460
+ }
461
+
462
+ /**
463
+ * Record an idea/inspiration.
464
+ * @param {Object} idea
465
+ * @param {string} idea.title - Idea title
466
+ * @param {string} [idea.description] - Detailed description
467
+ * @param {string} [idea.category] - Category
468
+ * @param {number} [idea.score] - Priority/quality score 0-100
469
+ * @param {string} [idea.status] - 'pending', 'in_progress', 'shipped', 'rejected'
470
+ * @param {string} [idea.source] - Where this idea came from
471
+ * @returns {Promise<{idea: Object}>}
472
+ */
473
+ async recordIdea(idea) {
474
+ return this._request('/api/inspiration', 'POST', idea);
475
+ }
476
+
477
+ /**
478
+ * Report memory health snapshot with entities and topics.
479
+ * @param {Object} report
480
+ * @param {Object} report.health - Health metrics
481
+ * @param {number} report.health.score - Health score 0-100
482
+ * @param {Object[]} [report.entities] - Key entities found in memory
483
+ * @param {Object[]} [report.topics] - Topics/themes found in memory
484
+ * @returns {Promise<{snapshot: Object, entities_count: number, topics_count: number}>}
485
+ */
486
+ async reportMemoryHealth(report) {
487
+ return this._request('/api/memory', 'POST', report);
488
+ }
489
+
490
+ /**
491
+ * Report active connections/integrations for this agent.
492
+ * @param {Object[]} connections - Array of connection objects
493
+ * @param {string} connections[].provider - Service name (e.g., 'anthropic', 'github')
494
+ * @param {string} [connections[].authType] - Auth method
495
+ * @param {string} [connections[].planName] - Plan name
496
+ * @param {string} [connections[].status] - Connection status: active, inactive, error
497
+ * @param {Object|string} [connections[].metadata] - Optional metadata
498
+ * @returns {Promise<{connections: Object[], created: number}>}
499
+ */
500
+ async reportConnections(connections) {
501
+ return this._request('/api/agents/connections', 'POST', {
502
+ agent_id: this.agentId,
503
+ connections: connections.map(c => ({
504
+ provider: c.provider,
505
+ auth_type: c.authType || c.auth_type || 'api_key',
506
+ plan_name: c.planName || c.plan_name || null,
507
+ status: c.status || 'active',
508
+ metadata: c.metadata || null
509
+ }))
510
+ });
511
+ }
512
+
513
+ // ══════════════════════════════════════════════
514
+ // Category 5: Session Handoffs (3 methods)
515
+ // ══════════════════════════════════════════════
516
+
517
+ /**
518
+ * Create a session handoff document.
519
+ * @param {Object} handoff
520
+ * @param {string} handoff.summary - Session summary
521
+ * @param {string} [handoff.session_date] - Date string (defaults to today)
522
+ * @param {string[]} [handoff.key_decisions] - Key decisions made
523
+ * @param {string[]} [handoff.open_tasks] - Tasks still open
524
+ * @param {string} [handoff.mood_notes] - Mood/energy observations
525
+ * @param {string[]} [handoff.next_priorities] - What to focus on next
526
+ * @returns {Promise<{handoff: Object, handoff_id: string}>}
527
+ */
528
+ async createHandoff(handoff) {
529
+ return this._request('/api/handoffs', 'POST', {
530
+ agent_id: this.agentId,
531
+ ...handoff
532
+ });
533
+ }
534
+
535
+ /**
536
+ * Get handoffs with optional filters.
537
+ * @param {Object} [filters]
538
+ * @param {string} [filters.date] - Filter by session_date
539
+ * @param {number} [filters.limit] - Max results
540
+ * @returns {Promise<{handoffs: Object[], total: number}>}
541
+ */
542
+ async getHandoffs(filters = {}) {
543
+ const params = new URLSearchParams({ agent_id: this.agentId });
544
+ if (filters.date) params.set('date', filters.date);
545
+ if (filters.limit) params.set('limit', String(filters.limit));
546
+ return this._request(`/api/handoffs?${params}`, 'GET');
547
+ }
548
+
549
+ /**
550
+ * Get the most recent handoff for this agent.
551
+ * @returns {Promise<{handoff: Object|null}>}
552
+ */
553
+ async getLatestHandoff() {
554
+ return this._request(`/api/handoffs?agent_id=${this.agentId}&latest=true`, 'GET');
555
+ }
556
+
557
+ // ══════════════════════════════════════════════
558
+ // Category 6: Context Manager (7 methods)
559
+ // ══════════════════════════════════════════════
560
+
561
+ /**
562
+ * Capture a key point from the current session.
563
+ * @param {Object} point
564
+ * @param {string} point.content - The key point content
565
+ * @param {string} [point.category] - One of: decision, task, insight, question, general
566
+ * @param {number} [point.importance] - Importance 1-10 (default 5)
567
+ * @param {string} [point.session_date] - Date string (defaults to today)
568
+ * @returns {Promise<{point: Object, point_id: string}>}
569
+ */
570
+ async captureKeyPoint(point) {
571
+ return this._request('/api/context/points', 'POST', {
572
+ agent_id: this.agentId,
573
+ ...point
574
+ });
575
+ }
576
+
577
+ /**
578
+ * Get key points with optional filters.
579
+ * @param {Object} [filters]
580
+ * @param {string} [filters.category] - Filter by category
581
+ * @param {string} [filters.session_date] - Filter by date
582
+ * @param {number} [filters.limit] - Max results
583
+ * @returns {Promise<{points: Object[], total: number}>}
584
+ */
585
+ async getKeyPoints(filters = {}) {
586
+ const params = new URLSearchParams({ agent_id: this.agentId });
587
+ if (filters.category) params.set('category', filters.category);
588
+ if (filters.session_date) params.set('session_date', filters.session_date);
589
+ if (filters.limit) params.set('limit', String(filters.limit));
590
+ return this._request(`/api/context/points?${params}`, 'GET');
591
+ }
592
+
593
+ /**
594
+ * Create a context thread for tracking a topic across entries.
595
+ * @param {Object} thread
596
+ * @param {string} thread.name - Thread name (unique per agent per org)
597
+ * @param {string} [thread.summary] - Initial summary
598
+ * @returns {Promise<{thread: Object, thread_id: string}>}
599
+ */
600
+ async createThread(thread) {
601
+ return this._request('/api/context/threads', 'POST', {
602
+ agent_id: this.agentId,
603
+ ...thread
604
+ });
605
+ }
606
+
607
+ /**
608
+ * Add an entry to an existing thread.
609
+ * @param {string} threadId - The thread ID
610
+ * @param {string} content - Entry content
611
+ * @param {string} [entryType] - Entry type (default: 'note')
612
+ * @returns {Promise<{entry: Object, entry_id: string}>}
613
+ */
614
+ async addThreadEntry(threadId, content, entryType) {
615
+ return this._request(`/api/context/threads/${threadId}/entries`, 'POST', {
616
+ content,
617
+ entry_type: entryType || 'note'
618
+ });
619
+ }
620
+
621
+ /**
622
+ * Close a thread with an optional summary.
623
+ * @param {string} threadId - The thread ID
624
+ * @param {string} [summary] - Final summary
625
+ * @returns {Promise<{thread: Object}>}
626
+ */
627
+ async closeThread(threadId, summary) {
628
+ const body = { status: 'closed' };
629
+ if (summary) body.summary = summary;
630
+ return this._request(`/api/context/threads/${threadId}`, 'PATCH', body);
631
+ }
632
+
633
+ /**
634
+ * Get threads with optional filters.
635
+ * @param {Object} [filters]
636
+ * @param {string} [filters.status] - Filter by status (active, closed)
637
+ * @param {number} [filters.limit] - Max results
638
+ * @returns {Promise<{threads: Object[], total: number}>}
639
+ */
640
+ async getThreads(filters = {}) {
641
+ const params = new URLSearchParams({ agent_id: this.agentId });
642
+ if (filters.status) params.set('status', filters.status);
643
+ if (filters.limit) params.set('limit', String(filters.limit));
644
+ return this._request(`/api/context/threads?${params}`, 'GET');
645
+ }
646
+
647
+ /**
648
+ * Get a combined context summary: today's key points + active threads.
649
+ * @returns {Promise<{points: Object[], threads: Object[]}>}
650
+ */
651
+ async getContextSummary() {
652
+ const today = new Date().toISOString().split('T')[0];
653
+ const [pointsResult, threadsResult] = await Promise.all([
654
+ this.getKeyPoints({ session_date: today }),
655
+ this.getThreads({ status: 'active' }),
656
+ ]);
657
+ return {
658
+ points: pointsResult.points,
659
+ threads: threadsResult.threads,
660
+ };
661
+ }
662
+
663
+ // ══════════════════════════════════════════════
664
+ // Category 7: Automation Snippets (4 methods)
665
+ // ══════════════════════════════════════════════
666
+
667
+ /**
668
+ * Save or update a reusable code snippet.
669
+ * @param {Object} snippet
670
+ * @param {string} snippet.name - Snippet name (unique per org, upserts on conflict)
671
+ * @param {string} snippet.code - The snippet code
672
+ * @param {string} [snippet.description] - What this snippet does
673
+ * @param {string} [snippet.language] - Programming language
674
+ * @param {string[]} [snippet.tags] - Tags for categorization
675
+ * @returns {Promise<{snippet: Object, snippet_id: string}>}
676
+ */
677
+ async saveSnippet(snippet) {
678
+ return this._request('/api/snippets', 'POST', {
679
+ agent_id: this.agentId,
680
+ ...snippet
681
+ });
682
+ }
683
+
684
+ /**
685
+ * Search and list snippets.
686
+ * @param {Object} [filters]
687
+ * @param {string} [filters.search] - Search name/description
688
+ * @param {string} [filters.tag] - Filter by tag
689
+ * @param {string} [filters.language] - Filter by language
690
+ * @param {number} [filters.limit] - Max results
691
+ * @returns {Promise<{snippets: Object[], total: number}>}
692
+ */
693
+ async getSnippets(filters = {}) {
694
+ const params = new URLSearchParams();
695
+ if (filters.search) params.set('search', filters.search);
696
+ if (filters.tag) params.set('tag', filters.tag);
697
+ if (filters.language) params.set('language', filters.language);
698
+ if (filters.limit) params.set('limit', String(filters.limit));
699
+ return this._request(`/api/snippets?${params}`, 'GET');
700
+ }
701
+
702
+ /**
703
+ * Mark a snippet as used (increments use_count).
704
+ * @param {string} snippetId - The snippet ID
705
+ * @returns {Promise<{snippet: Object}>}
706
+ */
707
+ async useSnippet(snippetId) {
708
+ return this._request(`/api/snippets/${snippetId}/use`, 'POST');
709
+ }
710
+
711
+ /**
712
+ * Delete a snippet.
713
+ * @param {string} snippetId - The snippet ID
714
+ * @returns {Promise<{deleted: boolean, id: string}>}
715
+ */
716
+ async deleteSnippet(snippetId) {
717
+ return this._request(`/api/snippets?id=${snippetId}`, 'DELETE');
718
+ }
719
+
720
+ // ══════════════════════════════════════════════
721
+ // Category 8: User Preferences (6 methods)
722
+ // ══════════════════════════════════════════════
723
+
724
+ /**
725
+ * Log a user observation (what you noticed about the user).
726
+ * @param {Object} obs
727
+ * @param {string} obs.observation - The observation text
728
+ * @param {string} [obs.category] - Category tag
729
+ * @param {number} [obs.importance] - Importance 1-10
730
+ * @returns {Promise<{observation: Object, observation_id: string}>}
731
+ */
732
+ async logObservation(obs) {
733
+ return this._request('/api/preferences', 'POST', {
734
+ type: 'observation',
735
+ agent_id: this.agentId,
736
+ ...obs
737
+ });
738
+ }
739
+
740
+ /**
741
+ * Set a learned user preference.
742
+ * @param {Object} pref
743
+ * @param {string} pref.preference - The preference description
744
+ * @param {string} [pref.category] - Category tag
745
+ * @param {number} [pref.confidence] - Confidence 0-100
746
+ * @returns {Promise<{preference: Object, preference_id: string}>}
747
+ */
748
+ async setPreference(pref) {
749
+ return this._request('/api/preferences', 'POST', {
750
+ type: 'preference',
751
+ agent_id: this.agentId,
752
+ ...pref
753
+ });
754
+ }
755
+
756
+ /**
757
+ * Log user mood/energy for a session.
758
+ * @param {Object} entry
759
+ * @param {string} entry.mood - Mood description (e.g., 'focused', 'frustrated')
760
+ * @param {string} [entry.energy] - Energy level (e.g., 'high', 'low')
761
+ * @param {string} [entry.notes] - Additional notes
762
+ * @returns {Promise<{mood: Object, mood_id: string}>}
763
+ */
764
+ async logMood(entry) {
765
+ return this._request('/api/preferences', 'POST', {
766
+ type: 'mood',
767
+ agent_id: this.agentId,
768
+ ...entry
769
+ });
770
+ }
771
+
772
+ /**
773
+ * Track an approach and whether it succeeded or failed.
774
+ * @param {Object} entry
775
+ * @param {string} entry.approach - The approach description
776
+ * @param {string} [entry.context] - Context for when to use this approach
777
+ * @param {boolean} [entry.success] - true = worked, false = failed, undefined = just recording
778
+ * @returns {Promise<{approach: Object, approach_id: string}>}
779
+ */
780
+ async trackApproach(entry) {
781
+ return this._request('/api/preferences', 'POST', {
782
+ type: 'approach',
783
+ agent_id: this.agentId,
784
+ ...entry
785
+ });
786
+ }
787
+
788
+ /**
789
+ * Get a summary of all user preference data.
790
+ * @returns {Promise<{summary: Object}>}
791
+ */
792
+ async getPreferenceSummary() {
793
+ return this._request(`/api/preferences?type=summary&agent_id=${this.agentId}`, 'GET');
794
+ }
795
+
796
+ /**
797
+ * Get tracked approaches with success/fail counts.
798
+ * @param {Object} [filters]
799
+ * @param {number} [filters.limit] - Max results
800
+ * @returns {Promise<{approaches: Object[], total: number}>}
801
+ */
802
+ async getApproaches(filters = {}) {
803
+ const params = new URLSearchParams({ type: 'approaches', agent_id: this.agentId });
804
+ if (filters.limit) params.set('limit', String(filters.limit));
805
+ return this._request(`/api/preferences?${params}`, 'GET');
806
+ }
807
+
808
+ // ══════════════════════════════════════════════
809
+ // Category 9: Daily Digest (1 method)
810
+ // ══════════════════════════════════════════════
811
+
812
+ /**
813
+ * Get a daily activity digest aggregated from all data sources.
814
+ * @param {string} [date] - Date string YYYY-MM-DD (defaults to today)
815
+ * @returns {Promise<{date: string, digest: Object, summary: Object}>}
816
+ */
817
+ async getDailyDigest(date) {
818
+ const params = new URLSearchParams({ agent_id: this.agentId });
819
+ if (date) params.set('date', date);
820
+ return this._request(`/api/digest?${params}`, 'GET');
821
+ }
822
+
823
+ // ══════════════════════════════════════════════
824
+ // Category 10: Security Scanning (2 methods)
825
+ // ══════════════════════════════════════════════
826
+
827
+ /**
828
+ * Scan text for sensitive data (API keys, tokens, PII, etc.).
829
+ * Returns findings and redacted text. Does NOT store the original content.
830
+ * @param {string} text - Text to scan
831
+ * @param {string} [destination] - Where this text is headed (for context)
832
+ * @returns {Promise<{clean: boolean, findings_count: number, findings: Object[], redacted_text: string}>}
833
+ */
834
+ async scanContent(text, destination) {
835
+ return this._request('/api/security/scan', 'POST', {
836
+ text,
837
+ destination,
838
+ agent_id: this.agentId,
839
+ store: false,
840
+ });
841
+ }
842
+
843
+ /**
844
+ * Scan text and store finding metadata (never the content itself).
845
+ * Use this for audit trails of security scans.
846
+ * @param {string} text - Text to scan
847
+ * @param {string} [destination] - Where this text is headed
848
+ * @returns {Promise<{clean: boolean, findings_count: number, findings: Object[], redacted_text: string}>}
849
+ */
850
+ async reportSecurityFinding(text, destination) {
851
+ return this._request('/api/security/scan', 'POST', {
852
+ text,
853
+ destination,
854
+ agent_id: this.agentId,
855
+ store: true,
856
+ });
857
+ }
858
+
859
+ // ══════════════════════════════════════════════
860
+ // Category 11: Agent Messaging (9 methods)
861
+ // ══════════════════════════════════════════════
862
+
863
+ /**
864
+ * Send a message to another agent or broadcast to all.
865
+ * @param {Object} params
866
+ * @param {string} [params.to] - Target agent ID (omit for broadcast)
867
+ * @param {string} [params.type='info'] - Message type: action|info|lesson|question|status
868
+ * @param {string} [params.subject] - Subject line (max 200 chars)
869
+ * @param {string} params.body - Message body (max 2000 chars)
870
+ * @param {string} [params.threadId] - Thread ID to attach message to
871
+ * @param {boolean} [params.urgent=false] - Mark as urgent
872
+ * @param {string} [params.docRef] - Reference to a shared doc ID
873
+ * @returns {Promise<{message: Object, message_id: string}>}
874
+ */
875
+ async sendMessage({ to, type, subject, body, threadId, urgent, docRef }) {
876
+ return this._request('/api/messages', 'POST', {
877
+ from_agent_id: this.agentId,
878
+ to_agent_id: to || null,
879
+ message_type: type || 'info',
880
+ subject,
881
+ body,
882
+ thread_id: threadId,
883
+ urgent,
884
+ doc_ref: docRef,
885
+ });
886
+ }
887
+
888
+ /**
889
+ * Get inbox messages for this agent.
890
+ * @param {Object} [params]
891
+ * @param {string} [params.type] - Filter by message type
892
+ * @param {boolean} [params.unread] - Only unread messages
893
+ * @param {string} [params.threadId] - Filter by thread
894
+ * @param {number} [params.limit=50] - Max messages to return
895
+ * @returns {Promise<{messages: Object[], total: number, unread_count: number}>}
896
+ */
897
+ async getInbox({ type, unread, threadId, limit } = {}) {
898
+ const params = new URLSearchParams({
899
+ agent_id: this.agentId,
900
+ direction: 'inbox',
901
+ });
902
+ if (type) params.set('type', type);
903
+ if (unread) params.set('unread', 'true');
904
+ if (threadId) params.set('thread_id', threadId);
905
+ if (limit) params.set('limit', String(limit));
906
+ return this._request(`/api/messages?${params}`, 'GET');
907
+ }
908
+
909
+ /**
910
+ * Mark messages as read.
911
+ * @param {string[]} messageIds - Array of message IDs to mark read
912
+ * @returns {Promise<{updated: number}>}
913
+ */
914
+ async markRead(messageIds) {
915
+ return this._request('/api/messages', 'PATCH', {
916
+ message_ids: messageIds,
917
+ action: 'read',
918
+ agent_id: this.agentId,
919
+ });
920
+ }
921
+
922
+ /**
923
+ * Archive messages.
924
+ * @param {string[]} messageIds - Array of message IDs to archive
925
+ * @returns {Promise<{updated: number}>}
926
+ */
927
+ async archiveMessages(messageIds) {
928
+ return this._request('/api/messages', 'PATCH', {
929
+ message_ids: messageIds,
930
+ action: 'archive',
931
+ agent_id: this.agentId,
932
+ });
933
+ }
934
+
935
+ /**
936
+ * Broadcast a message to all agents in the organization.
937
+ * @param {Object} params
938
+ * @param {string} [params.type='info'] - Message type
939
+ * @param {string} [params.subject] - Subject line
940
+ * @param {string} params.body - Message body
941
+ * @param {string} [params.threadId] - Thread ID
942
+ * @returns {Promise<{message: Object, message_id: string}>}
943
+ */
944
+ async broadcast({ type, subject, body, threadId }) {
945
+ return this.sendMessage({ to: null, type, subject, body, threadId });
946
+ }
947
+
948
+ /**
949
+ * Create a new message thread for multi-turn conversations.
950
+ * @param {Object} params
951
+ * @param {string} params.name - Thread name
952
+ * @param {string[]} [params.participants] - Agent IDs (null = open to all)
953
+ * @returns {Promise<{thread: Object, thread_id: string}>}
954
+ */
955
+ async createMessageThread({ name, participants }) {
956
+ return this._request('/api/messages/threads', 'POST', {
957
+ name,
958
+ participants,
959
+ created_by: this.agentId,
960
+ });
961
+ }
962
+
963
+ /**
964
+ * List message threads.
965
+ * @param {Object} [params]
966
+ * @param {string} [params.status] - Filter by status: open|resolved|archived
967
+ * @param {number} [params.limit=20] - Max threads to return
968
+ * @returns {Promise<{threads: Object[], total: number}>}
969
+ */
970
+ async getMessageThreads({ status, limit } = {}) {
971
+ const params = new URLSearchParams({ agent_id: this.agentId });
972
+ if (status) params.set('status', status);
973
+ if (limit) params.set('limit', String(limit));
974
+ return this._request(`/api/messages/threads?${params}`, 'GET');
975
+ }
976
+
977
+ /**
978
+ * Resolve (close) a message thread.
979
+ * @param {string} threadId - Thread ID to resolve
980
+ * @param {string} [summary] - Resolution summary
981
+ * @returns {Promise<{thread: Object}>}
982
+ */
983
+ async resolveMessageThread(threadId, summary) {
984
+ return this._request('/api/messages/threads', 'PATCH', {
985
+ thread_id: threadId,
986
+ status: 'resolved',
987
+ summary,
988
+ });
989
+ }
990
+
991
+ /**
992
+ * Create or update a shared workspace document.
993
+ * Upserts by (org_id, name) — updates increment the version.
994
+ * @param {Object} params
995
+ * @param {string} params.name - Document name (unique per org)
996
+ * @param {string} params.content - Document content
997
+ * @returns {Promise<{doc: Object, doc_id: string}>}
998
+ */
999
+ async saveSharedDoc({ name, content }) {
1000
+ return this._request('/api/messages/docs', 'POST', {
1001
+ name,
1002
+ content,
1003
+ agent_id: this.agentId,
1004
+ });
1005
+ }
1006
+
1007
+ // ══════════════════════════════════════════════
1008
+ // Category 13: Behavior Guard (2 methods)
1009
+ // ══════════════════════════════════════════════
1010
+
1011
+ /**
1012
+ * Check guard policies before executing a risky action.
1013
+ * Returns allow/warn/block/require_approval.
1014
+ * @param {Object} context
1015
+ * @param {string} context.action_type - Action type (required)
1016
+ * @param {number} [context.risk_score] - Risk score 0-100
1017
+ * @param {string[]} [context.systems_touched] - Systems involved
1018
+ * @param {boolean} [context.reversible] - Whether the action is reversible
1019
+ * @param {string} [context.declared_goal] - What the action aims to do
1020
+ * @param {Object} [options]
1021
+ * @param {boolean} [options.includeSignals=false] - Include live signal warnings
1022
+ * @returns {Promise<{decision: string, reasons: string[], warnings: string[], matched_policies: string[], evaluated_at: string}>}
1023
+ */
1024
+ async guard(context, options = {}) {
1025
+ const params = new URLSearchParams();
1026
+ if (options.includeSignals) params.set('include_signals', 'true');
1027
+ const qs = params.toString();
1028
+ return this._request(`/api/guard${qs ? `?${qs}` : ''}`, 'POST', {
1029
+ ...context,
1030
+ agent_id: context.agent_id || this.agentId,
1031
+ });
1032
+ }
1033
+
1034
+ /**
1035
+ * Get recent guard decisions (audit log).
1036
+ * @param {Object} [filters]
1037
+ * @param {string} [filters.decision] - Filter by decision: allow|warn|block|require_approval
1038
+ * @param {number} [filters.limit=20] - Max results
1039
+ * @param {number} [filters.offset=0] - Pagination offset
1040
+ * @returns {Promise<{decisions: Object[], total: number, stats: Object}>}
1041
+ */
1042
+ async getGuardDecisions(filters = {}) {
1043
+ const params = new URLSearchParams({ agent_id: this.agentId });
1044
+ if (filters.decision) params.set('decision', filters.decision);
1045
+ if (filters.limit) params.set('limit', String(filters.limit));
1046
+ if (filters.offset) params.set('offset', String(filters.offset));
1047
+ return this._request(`/api/guard?${params}`, 'GET');
1048
+ }
1049
+
1050
+ // ─── Bulk Sync ────────────────────────────────────────────
1051
+
1052
+ /**
1053
+ * Sync multiple data categories in a single request.
1054
+ * Every key is optional — only provided categories are processed.
1055
+ * @param {Object} state - Data to sync (connections, memory, goals, learning, content, inspiration, context_points, context_threads, handoffs, preferences, snippets)
1056
+ * @returns {Promise<{results: Object, total_synced: number, total_errors: number, duration_ms: number}>}
1057
+ */
1058
+ async syncState(state) {
1059
+ return this._request('/api/sync', 'POST', {
1060
+ agent_id: this.agentId,
1061
+ ...state,
1062
+ });
1063
+ }
1064
+ }
1065
+
1066
+ /**
1067
+ * Error thrown when guardMode is 'enforce' and guard blocks an action.
1068
+ */
1069
+ class GuardBlockedError extends Error {
1070
+ /**
1071
+ * @param {Object} decision - Guard decision object
1072
+ */
1073
+ constructor(decision) {
1074
+ const reasons = (decision.reasons || []).join('; ') || 'no reason';
1075
+ super(`Guard blocked action: ${decision.decision}. Reasons: ${reasons}`);
1076
+ this.name = 'GuardBlockedError';
1077
+ this.decision = decision.decision;
1078
+ this.reasons = decision.reasons || [];
1079
+ this.warnings = decision.warnings || [];
1080
+ this.matchedPolicies = decision.matched_policies || [];
1081
+ this.riskScore = decision.risk_score ?? null;
1082
+ }
1083
+ }
1084
+
1085
+ // Backward compatibility alias
1086
+ const OpenClawAgent = DashClaw;
1087
+
1088
+ export default DashClaw;
1089
+ export { DashClaw, OpenClawAgent, GuardBlockedError };
package/index.cjs ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * DashClaw SDK — CommonJS compatibility wrapper.
3
+ * For ESM: import { DashClaw } from 'dashclaw'
4
+ * For CJS: const { DashClaw } = require('dashclaw')
5
+ */
6
+
7
+ let _module;
8
+
9
+ async function loadModule() {
10
+ if (!_module) {
11
+ _module = await import('./dashclaw.js');
12
+ }
13
+ return _module;
14
+ }
15
+
16
+ // Re-export via dynamic import (CJS → ESM bridge)
17
+ module.exports = new Proxy({}, {
18
+ get(target, prop) {
19
+ if (prop === '__esModule') return true;
20
+ if (prop === 'then') return undefined; // Prevent Promise-like behavior
21
+
22
+ // Return a lazy-loading constructor wrapper
23
+ if (prop === 'GuardBlockedError') {
24
+ // Return a placeholder that resolves to the real class
25
+ return class GuardBlockedErrorProxy extends Error {
26
+ constructor(decision) {
27
+ const reasons = (decision.reasons || []).join('; ') || 'no reason';
28
+ super(`Guard blocked action: ${decision.decision}. Reasons: ${reasons}`);
29
+ this.name = 'GuardBlockedError';
30
+ this.decision = decision.decision;
31
+ this.reasons = decision.reasons || [];
32
+ this.warnings = decision.warnings || [];
33
+ this.matchedPolicies = decision.matched_policies || [];
34
+ this.riskScore = decision.risk_score ?? null;
35
+ }
36
+ };
37
+ }
38
+ if (prop === 'DashClaw' || prop === 'OpenClawAgent' || prop === 'default') {
39
+ return class DashClawProxy {
40
+ constructor(opts) {
41
+ // Store options, actual instance created async
42
+ this._opts = opts;
43
+ this._ready = loadModule().then(m => {
44
+ const Cls = m.DashClaw || m.default;
45
+ this._instance = new Cls(opts);
46
+ });
47
+ }
48
+
49
+ // For synchronous construction, use DashClaw.create()
50
+ static async create(opts) {
51
+ const mod = await loadModule();
52
+ const Cls = mod.DashClaw || mod.default;
53
+ return new Cls(opts);
54
+ }
55
+ };
56
+ }
57
+ return undefined;
58
+ }
59
+ });
60
+
61
+ // Preferred: async factory
62
+ module.exports.create = async function create(opts) {
63
+ const mod = await loadModule();
64
+ const Cls = mod.DashClaw || mod.default;
65
+ return new Cls(opts);
66
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "dashclaw",
3
+ "version": "1.0.0",
4
+ "description": "Full-featured agent toolkit for the DashClaw platform. 57 methods for action recording, context management, session handoffs, security scanning, behavior guard, bulk sync, and more.",
5
+ "type": "module",
6
+ "main": "./index.cjs",
7
+ "module": "./dashclaw.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dashclaw.js",
11
+ "require": "./index.cjs"
12
+ }
13
+ },
14
+ "files": [
15
+ "dashclaw.js",
16
+ "index.cjs",
17
+ "LICENSE",
18
+ "README.md"
19
+ ],
20
+ "keywords": [
21
+ "ai-agent",
22
+ "observability",
23
+ "agent-toolkit",
24
+ "openclaw",
25
+ "dashclaw",
26
+ "action-recording",
27
+ "context-management",
28
+ "security-scanning"
29
+ ],
30
+ "author": "DashClaw",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/ucsandman/DashClaw.git",
35
+ "directory": "sdk"
36
+ },
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ }
40
+ }