@dashclaw/mcp-server 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.
package/lib/tools.js ADDED
@@ -0,0 +1,680 @@
1
+ // mcp-server/lib/tools.js
2
+
3
+ /**
4
+ * DashClaw MCP tool definitions and handlers.
5
+ * Tool definitions follow JSON Schema (for both MCP registerTool and JSON-RPC).
6
+ * Handlers are pure functions that call DashClawClient and return text content.
7
+ *
8
+ * This file is HAND-CURATED on purpose. Every MCP tool has a semantically
9
+ * precise description and custom handler logic (e.g., dashclaw_wait_for_approval
10
+ * polls until status changes) that can't be auto-generated from route metadata.
11
+ *
12
+ * For the live API surface, see `routes-inventory.generated.json` (regenerated
13
+ * by `npm run livingcode:refresh`). When adding a new route that agents should
14
+ * invoke, diff the inventory against TOOL_DEFINITIONS below to decide whether
15
+ * a new tool wrapper is warranted.
16
+ */
17
+
18
+ export const TOOL_DEFINITIONS = [
19
+ {
20
+ name: 'dashclaw_guard',
21
+ description:
22
+ 'Evaluate DashClaw governance policies before taking a risky action. Call this BEFORE ' +
23
+ 'any action that modifies external systems, deploys code, sends messages, or touches ' +
24
+ 'production data. Returns a decision: "allow" (proceed), "warn" (proceed with caution), ' +
25
+ '"block" (stop), or "require_approval" (wait for human in Mission Control). If the ' +
26
+ 'decision is "block", do NOT proceed with the action.',
27
+ inputSchema: {
28
+ type: 'object',
29
+ properties: {
30
+ action_type: { type: 'string', description: 'Category of action (e.g., deploy, send_email, database_write, api_call)' },
31
+ declared_goal: { type: 'string', description: 'What you intend to do, in plain language' },
32
+ risk_score: { type: 'integer', description: 'Estimated risk 0-100. Use 70+ for production systems.' },
33
+ agent_id: { type: 'string', description: 'Override default agent ID' },
34
+ systems_touched: { type: 'array', items: { type: 'string' }, description: 'Systems affected (e.g., production, database, email)' },
35
+ reversible: { type: 'boolean', description: 'Whether the action can be undone' },
36
+ },
37
+ required: ['action_type', 'declared_goal', 'risk_score'],
38
+ },
39
+ },
40
+ {
41
+ name: 'dashclaw_record',
42
+ description:
43
+ 'Record a governed action in DashClaw\'s audit trail. Use this to log significant ' +
44
+ 'decisions, completed tasks, or notable outcomes. Every important action the agent takes ' +
45
+ 'should be recorded for governance visibility in Mission Control and the Decisions ledger.',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ action_type: { type: 'string', description: 'Category (e.g., research, analysis, code_change, deploy)' },
50
+ declared_goal: { type: 'string', description: 'What was accomplished' },
51
+ status: { type: 'string', enum: ['running', 'completed', 'failed', 'pending_approval'], description: 'Outcome status' },
52
+ risk_score: { type: 'integer', description: 'Risk level 0-100 (default 30)' },
53
+ agent_id: { type: 'string', description: 'Override default agent ID' },
54
+ reasoning: { type: 'string', description: 'Why this action was chosen' },
55
+ confidence: { type: 'integer', description: 'Confidence 0-100' },
56
+ systems_touched: { type: 'array', items: { type: 'string' }, description: 'Systems affected' },
57
+ reversible: { type: 'boolean', description: 'Whether the action can be undone' },
58
+ output_summary: { type: 'string', description: 'Brief summary of what was produced' },
59
+ tokens_in: { type: 'integer', description: 'Input tokens consumed' },
60
+ tokens_out: { type: 'integer', description: 'Output tokens produced' },
61
+ model: { type: 'string', description: 'Model used' },
62
+ cost_estimate: { type: 'number', description: 'Estimated cost in USD' },
63
+ },
64
+ required: ['action_type', 'declared_goal', 'status'],
65
+ },
66
+ },
67
+ {
68
+ name: 'dashclaw_invoke',
69
+ description:
70
+ 'Invoke a DashClaw-governed capability (external API). The capability is guarded ' +
71
+ '(policy check), executed (HTTP call), and recorded (audit trail) automatically. Use ' +
72
+ 'this instead of making direct HTTP calls when the target API is registered as a DashClaw ' +
73
+ 'capability. Call dashclaw_capabilities_list first to discover available capability IDs.',
74
+ inputSchema: {
75
+ type: 'object',
76
+ properties: {
77
+ capability_id: { type: 'string', description: 'The capability ID (e.g., cap_abc123)' },
78
+ declared_goal: { type: 'string', description: 'What you\'re trying to accomplish' },
79
+ agent_id: { type: 'string', description: 'Override default agent ID' },
80
+ payload: { type: 'object', description: 'Request payload for the capability' },
81
+ },
82
+ required: ['capability_id', 'declared_goal'],
83
+ },
84
+ },
85
+ {
86
+ name: 'dashclaw_capabilities_list',
87
+ description:
88
+ 'List available capabilities registered in DashClaw. Use this to discover what external ' +
89
+ 'APIs and tools are available before invoking them. Returns capability IDs, names, health ' +
90
+ 'status, and risk levels. Filter by category, risk level, or search term.',
91
+ inputSchema: {
92
+ type: 'object',
93
+ properties: {
94
+ category: { type: 'string', description: 'Filter by category: external_api, webhook, function' },
95
+ risk_level: { type: 'string', description: 'Filter: low, medium, high, critical' },
96
+ search: { type: 'string', description: 'Search by name or description' },
97
+ },
98
+ },
99
+ },
100
+ {
101
+ name: 'dashclaw_policies_list',
102
+ description:
103
+ 'List active governance policies. Use this to understand what rules govern your actions ' +
104
+ 'before taking them. Helps calibrate risk scores and know which action types require ' +
105
+ 'approval. Optionally filter to policies applying to a specific agent.',
106
+ inputSchema: {
107
+ type: 'object',
108
+ properties: {
109
+ agent_id: { type: 'string', description: 'Filter to policies applying to a specific agent' },
110
+ },
111
+ },
112
+ },
113
+ {
114
+ name: 'dashclaw_wait_for_approval',
115
+ description:
116
+ 'Wait for a human to approve or deny a pending action in DashClaw Mission Control. ' +
117
+ 'Call this after a guard decision returns "require_approval" or after recording an ' +
118
+ 'action with status "pending_approval". Polls the action status until it changes. ' +
119
+ 'Default timeout is 300 seconds (5 minutes).',
120
+ inputSchema: {
121
+ type: 'object',
122
+ properties: {
123
+ action_id: { type: 'string', description: 'The action ID to wait on (e.g., act_abc123)' },
124
+ timeout_seconds: { type: 'number', description: 'Max wait time (default 300)' },
125
+ poll_interval_seconds: { type: 'number', description: 'Polling frequency (default 3)' },
126
+ },
127
+ required: ['action_id'],
128
+ },
129
+ },
130
+ {
131
+ name: 'dashclaw_session_start',
132
+ description:
133
+ 'Register this agent session with DashClaw. Creates a session record that groups all ' +
134
+ 'subsequent actions for tracking and observability. Call this at the beginning of a task ' +
135
+ 'to establish a governance boundary.',
136
+ inputSchema: {
137
+ type: 'object',
138
+ properties: {
139
+ agent_id: { type: 'string', description: 'Agent identifier (required)' },
140
+ workspace: { type: 'string', description: 'Workspace or project context' },
141
+ branch: { type: 'string', description: 'Git branch or task branch' },
142
+ },
143
+ required: ['agent_id'],
144
+ },
145
+ },
146
+ {
147
+ name: 'dashclaw_session_end',
148
+ description:
149
+ 'Close a DashClaw session and update its status. Call this when the task is complete ' +
150
+ 'or if the session needs to be marked as failed. Provides a clean lifecycle boundary ' +
151
+ 'for governance reporting in Mission Control.',
152
+ inputSchema: {
153
+ type: 'object',
154
+ properties: {
155
+ session_id: { type: 'string', description: 'Session ID from dashclaw_session_start' },
156
+ status: { type: 'string', enum: ['completed', 'failed', 'cancelled'], description: 'Final session status' },
157
+ summary: { type: 'string', description: 'Brief description of what was accomplished' },
158
+ },
159
+ required: ['session_id', 'status'],
160
+ },
161
+ },
162
+ // --- Code Sessions: Optimal Files (Phase 6) ------------------------------
163
+ {
164
+ name: 'dashclaw_optimal_files_preview',
165
+ description:
166
+ 'Preview the Optimal Files bundle DashClaw Code Sessions would generate for a given session. Returns the per-file plan with confidence, secret-scan, and overwrite-risk flags. Read-only — does NOT write to disk; pair with dashclaw_optimal_files_manifest to persist a chosen subset.',
167
+ inputSchema: {
168
+ type: 'object',
169
+ properties: {
170
+ session_id: { type: 'string', description: 'Code session id (cs_*) from /api/code-sessions/sessions/...' },
171
+ },
172
+ required: ['session_id'],
173
+ },
174
+ },
175
+ {
176
+ name: 'dashclaw_optimal_files_manifest',
177
+ description:
178
+ 'Persist a write plan for selected Optimal Files entries. Returns { manifest_id, expires_at, apply_command }. The local CLI invokes `dashclaw code apply <manifest_id>` to apply the plan to disk. Manifest expires after 24h.',
179
+ inputSchema: {
180
+ type: 'object',
181
+ properties: {
182
+ session_id: { type: 'string', description: 'Code session id (cs_*)' },
183
+ selections: {
184
+ type: 'array',
185
+ description: 'Subset of paths from the preview to write. Each item: { path, mode?: "skip"|"side_by_side"|"merge"|"overwrite", overwrite?, acceptedHeadings?, acceptedBullets? }',
186
+ items: { type: 'object' },
187
+ },
188
+ },
189
+ required: ['session_id', 'selections'],
190
+ },
191
+ },
192
+ {
193
+ name: 'dashclaw_handoff_create',
194
+ description:
195
+ 'Create a session handoff bundle for the next session of this agent to consume on start. ' +
196
+ 'Call this when wrapping up — include a 1-2 sentence summary, any open loops, decisions made, ' +
197
+ 'and freeform state you want the next session to see.',
198
+ inputSchema: {
199
+ type: 'object',
200
+ properties: {
201
+ agent_id: { type: 'string', description: 'Agent ID (override default)' },
202
+ project_id: { type: 'string', description: 'Optional project ID — handoff is project-scoped' },
203
+ bundle: {
204
+ type: 'object',
205
+ description: 'Handoff content: { summary, open_loops, decisions_made, state_snapshot, generated_at }',
206
+ },
207
+ },
208
+ required: ['bundle'],
209
+ },
210
+ },
211
+ {
212
+ name: 'dashclaw_handoff_latest',
213
+ description:
214
+ 'Fetch the latest unconsumed session handoff for this agent (+ project, optional). ' +
215
+ 'Call this on session start to pick up where the last session left off. Returns null if ' +
216
+ 'no handoff is waiting.',
217
+ inputSchema: {
218
+ type: 'object',
219
+ properties: {
220
+ agent_id: { type: 'string' },
221
+ project_id: { type: 'string' },
222
+ },
223
+ },
224
+ },
225
+ {
226
+ name: 'dashclaw_handoff_consume',
227
+ description:
228
+ 'Mark a handoff as consumed. Call after dashclaw_handoff_latest returns a bundle and you ' +
229
+ 'have processed it. Idempotent.',
230
+ inputSchema: {
231
+ type: 'object',
232
+ properties: {
233
+ id: { type: 'string', description: 'Handoff id (hf_*) from handoff_latest' },
234
+ session_id: { type: 'string', description: 'Optional current session id for provenance' },
235
+ },
236
+ required: ['id'],
237
+ },
238
+ },
239
+ {
240
+ name: 'dashclaw_secret_list',
241
+ description:
242
+ 'List tracked secrets (metadata only — no values). Returns each entry with name, rotation ' +
243
+ 'interval, last_rotated_at, and computed next_rotation_due.',
244
+ inputSchema: {
245
+ type: 'object',
246
+ properties: {
247
+ agent_id: { type: 'string', description: 'Optional — scope to this agent' },
248
+ },
249
+ },
250
+ },
251
+ {
252
+ name: 'dashclaw_secret_due',
253
+ description:
254
+ 'List secrets coming due for rotation. Call this BEFORE acting on credentials. If a ' +
255
+ 'credential you would use is in the result, flag the operator rather than proceeding.',
256
+ inputSchema: {
257
+ type: 'object',
258
+ properties: {
259
+ within_days: { type: 'integer', description: 'Lookahead window in days (default 14)' },
260
+ agent_id: { type: 'string' },
261
+ },
262
+ },
263
+ },
264
+ {
265
+ name: 'dashclaw_secret_mark_rotated',
266
+ description:
267
+ 'Mark a tracked secret as rotated (sets last_rotated_at = now). Agents only call this if ' +
268
+ 'the operator instructs; secret registration is an operator task.',
269
+ inputSchema: {
270
+ type: 'object',
271
+ properties: {
272
+ id: { type: 'string', description: 'Secret id (sec_*)' },
273
+ },
274
+ required: ['id'],
275
+ },
276
+ },
277
+ {
278
+ name: 'dashclaw_skill_scan',
279
+ description:
280
+ 'Run a static safety scan against the contents of an untrusted skill before loading it. ' +
281
+ 'Returns findings (severity, file, line) and a passed boolean. If passed=false, do NOT load ' +
282
+ 'the skill — show the findings to the operator.',
283
+ inputSchema: {
284
+ type: 'object',
285
+ properties: {
286
+ skill_name: { type: 'string' },
287
+ files: {
288
+ type: 'object',
289
+ description: 'Map of filename -> file content (string)',
290
+ },
291
+ },
292
+ required: ['skill_name', 'files'],
293
+ },
294
+ },
295
+ {
296
+ name: 'dashclaw_loop_add',
297
+ description:
298
+ 'Register an open loop on a parent action — a commitment made in conversation that needs ' +
299
+ 'follow-up. Use when you say "I will X later" so the loop is tracked outside of context. ' +
300
+ 'Loops are action-scoped; action_id is required.',
301
+ inputSchema: {
302
+ type: 'object',
303
+ properties: {
304
+ action_id: { type: 'string', description: 'Parent action id (act_*) the loop attaches to' },
305
+ loop_type: { type: 'string', description: 'Category (e.g., follow_up, blocker, decision_pending)' },
306
+ description: { type: 'string' },
307
+ priority: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], description: 'Priority (default medium)' },
308
+ owner: { type: 'string', description: 'Optional owner (agent or human handle)' },
309
+ },
310
+ required: ['action_id', 'loop_type', 'description'],
311
+ },
312
+ },
313
+ {
314
+ name: 'dashclaw_loop_list',
315
+ description:
316
+ 'List open (or resolved) loops with optional filters. Use on session start to remember ' +
317
+ 'what you promised to follow up on.',
318
+ inputSchema: {
319
+ type: 'object',
320
+ properties: {
321
+ action_id: { type: 'string', description: 'Filter by parent action' },
322
+ status: { type: 'string', enum: ['open', 'resolved', 'cancelled'] },
323
+ priority: { type: 'string', enum: ['low', 'medium', 'high', 'critical'] },
324
+ agent_id: { type: 'string', description: 'Filter by agent (joined via parent action)' },
325
+ from: { type: 'string', description: 'ISO timestamp lower bound (reserved)' },
326
+ to: { type: 'string', description: 'ISO timestamp upper bound (reserved)' },
327
+ },
328
+ },
329
+ },
330
+ {
331
+ name: 'dashclaw_loop_close',
332
+ description:
333
+ 'Resolve an open loop. Call when the followed-up-on item is complete. Requires the loop_id ' +
334
+ 'and a short resolution note.',
335
+ inputSchema: {
336
+ type: 'object',
337
+ properties: {
338
+ id: { type: 'string', description: 'Loop id (loop_*)' },
339
+ resolution: { type: 'string', description: 'Short note describing how the loop was closed' },
340
+ },
341
+ required: ['id'],
342
+ },
343
+ },
344
+ {
345
+ name: 'dashclaw_learning_log',
346
+ description:
347
+ 'Log a decision + outcome to the learning database. Use after making a non-obvious decision ' +
348
+ 'so future sessions can recall the reasoning and outcome.',
349
+ inputSchema: {
350
+ type: 'object',
351
+ properties: {
352
+ agent_id: { type: 'string' },
353
+ decision: { type: 'string', description: 'What was decided' },
354
+ context: { type: 'string', description: 'Why this decision was made' },
355
+ outcome: { type: 'string', description: 'What happened (optional, can be updated later)' },
356
+ },
357
+ required: ['decision'],
358
+ },
359
+ },
360
+ {
361
+ name: 'dashclaw_learning_query',
362
+ description:
363
+ 'Query the learning database for prior decisions and lessons. Use BEFORE making a decision ' +
364
+ 'similar to one you might have made before.',
365
+ inputSchema: {
366
+ type: 'object',
367
+ properties: {
368
+ agent_id: { type: 'string' },
369
+ query: { type: 'string', description: 'Search text (matches decision/context)' },
370
+ limit: { type: 'integer', description: 'Max results (default 10)' },
371
+ },
372
+ },
373
+ },
374
+ {
375
+ name: 'dashclaw_decisions_recent',
376
+ description:
377
+ 'Query the guardrail decisions ledger for recent governed actions. Filter by agent, action ' +
378
+ 'type, decision verdict, or time window. Use for in-session retrospection — "what have I done ' +
379
+ 'recently?"',
380
+ inputSchema: {
381
+ type: 'object',
382
+ properties: {
383
+ agent_id: { type: 'string' },
384
+ action_type: { type: 'string' },
385
+ decision: { type: 'string', enum: ['allow', 'warn', 'block', 'require_approval'] },
386
+ since: { type: 'string', description: 'ISO timestamp lower bound' },
387
+ limit: { type: 'integer', description: 'Max results (default 20)' },
388
+ },
389
+ },
390
+ },
391
+ ];
392
+
393
+ /**
394
+ * Create tool handler functions bound to a DashClawClient instance.
395
+ * Each handler accepts input args and returns a JSON string (MCP text content).
396
+ * @param {import('./client.js').DashClawClient} client
397
+ * @returns {Object<string, function>}
398
+ */
399
+ export function createToolHandlers(client) {
400
+ const agentId = (input) => input.agent_id || client.agentId;
401
+
402
+ return {
403
+ async dashclaw_optimal_files_preview(input) {
404
+ const result = await client.post(`/api/code-sessions/sessions/${encodeURIComponent(input.session_id)}/optimal-files/preview`, {}, { timeout: 20000 });
405
+ return JSON.stringify(result);
406
+ },
407
+
408
+ async dashclaw_optimal_files_manifest(input) {
409
+ const result = await client.post(`/api/code-sessions/sessions/${encodeURIComponent(input.session_id)}/optimal-files/manifest`,
410
+ { selections: input.selections || [] }, { timeout: 20000 });
411
+ return JSON.stringify(result);
412
+ },
413
+
414
+ async dashclaw_guard(input) {
415
+ const result = await client.post('/api/guard', {
416
+ action_type: input.action_type,
417
+ declared_goal: input.declared_goal,
418
+ risk_score: input.risk_score,
419
+ agent_id: agentId(input),
420
+ systems_touched: input.systems_touched,
421
+ reversible: input.reversible,
422
+ }, { timeout: 10000 });
423
+ return JSON.stringify(result);
424
+ },
425
+
426
+ async dashclaw_record(input) {
427
+ const body = {
428
+ action_type: input.action_type,
429
+ declared_goal: input.declared_goal,
430
+ status: input.status,
431
+ risk_score: input.risk_score ?? 30,
432
+ agent_id: agentId(input),
433
+ reasoning: input.reasoning,
434
+ confidence: input.confidence,
435
+ systems_touched: input.systems_touched,
436
+ reversible: input.reversible,
437
+ output_summary: input.output_summary,
438
+ tokens_in: input.tokens_in,
439
+ tokens_out: input.tokens_out,
440
+ model: input.model,
441
+ cost_estimate: input.cost_estimate,
442
+ };
443
+ const result = await client.post('/api/actions', body, { timeout: 10000 });
444
+ return JSON.stringify(result);
445
+ },
446
+
447
+ async dashclaw_invoke(input) {
448
+ const result = await client.post(`/api/capabilities/${input.capability_id}/invoke`, {
449
+ agent_id: agentId(input),
450
+ declared_goal: input.declared_goal,
451
+ payload: input.payload,
452
+ }, { timeout: 30000 });
453
+ return JSON.stringify(result);
454
+ },
455
+
456
+ async dashclaw_capabilities_list(input) {
457
+ const result = await client.get('/api/capabilities', {
458
+ category: input.category,
459
+ risk_level: input.risk_level,
460
+ search: input.search,
461
+ }, { timeout: 10000 });
462
+ return JSON.stringify(result);
463
+ },
464
+
465
+ async dashclaw_policies_list(input) {
466
+ const result = await client.get('/api/policies', {
467
+ agent_id: input.agent_id,
468
+ }, { timeout: 10000 });
469
+ return JSON.stringify(result);
470
+ },
471
+
472
+ async dashclaw_wait_for_approval(input) {
473
+ const timeout = (input.timeout_seconds ?? 300) * 1000;
474
+ const interval = (input.poll_interval_seconds ?? 3) * 1000;
475
+ const start = Date.now();
476
+
477
+ while (Date.now() - start < timeout) {
478
+ const result = await client.get(`/api/actions/${input.action_id}`, {}, { timeout: 10000 });
479
+ const status = result?.action?.status;
480
+
481
+ if (status && status !== 'pending_approval') {
482
+ const approved = status === 'completed';
483
+ // Distinguish explicit operator denial (failed/cancelled) from
484
+ // a genuine approval. The JS and Python SDKs throw on denial;
485
+ // MCP can't throw through the tool channel, so surface a
486
+ // clear `denied:true` + reason instead of returning
487
+ // approved:false with no further signal.
488
+ const denied = !approved && (status === 'failed' || status === 'cancelled');
489
+ return JSON.stringify({
490
+ approved,
491
+ denied,
492
+ denial_reason: denied
493
+ ? (result?.action?.error_message || `Operator marked action as ${status}`)
494
+ : null,
495
+ action: result.action,
496
+ waited_seconds: Math.round((Date.now() - start) / 1000),
497
+ });
498
+ }
499
+
500
+ await new Promise((r) => setTimeout(r, interval));
501
+ }
502
+
503
+ return JSON.stringify({
504
+ approved: false,
505
+ timed_out: true,
506
+ action: { status: 'pending_approval' },
507
+ waited_seconds: Math.round((Date.now() - start) / 1000),
508
+ });
509
+ },
510
+
511
+ async dashclaw_session_start(input) {
512
+ const result = await client.post('/api/sessions', {
513
+ agent_id: input.agent_id,
514
+ workspace: input.workspace,
515
+ branch: input.branch,
516
+ }, { timeout: 10000 });
517
+ return JSON.stringify(result);
518
+ },
519
+
520
+ async dashclaw_session_end(input) {
521
+ const result = await client.patch(`/api/sessions/${input.session_id}`, {
522
+ status: input.status,
523
+ summary: input.summary,
524
+ }, { timeout: 10000 });
525
+ return JSON.stringify(result);
526
+ },
527
+
528
+ async dashclaw_handoff_create(args) {
529
+ const res = await client.fetch('/api/handoffs', {
530
+ method: 'POST',
531
+ body: JSON.stringify({
532
+ agent_id: agentId(args),
533
+ project_id: args.project_id,
534
+ bundle: args.bundle,
535
+ }),
536
+ });
537
+ const data = await res.json();
538
+ return JSON.stringify(data);
539
+ },
540
+
541
+ async dashclaw_handoff_latest(args) {
542
+ const params = new URLSearchParams();
543
+ const aid = agentId(args);
544
+ if (aid) params.set('agent_id', aid);
545
+ if (args.project_id) params.set('project_id', args.project_id);
546
+ const res = await client.fetch(`/api/handoffs/latest?${params}`);
547
+ if (res.status === 404) return JSON.stringify(null);
548
+ const data = await res.json();
549
+ return JSON.stringify(data);
550
+ },
551
+
552
+ async dashclaw_handoff_consume(args) {
553
+ const res = await client.fetch(`/api/handoffs/${encodeURIComponent(args.id)}/consume`, {
554
+ method: 'POST',
555
+ body: JSON.stringify({ session_id: args.session_id }),
556
+ });
557
+ const data = await res.json();
558
+ return JSON.stringify(data);
559
+ },
560
+
561
+ async dashclaw_secret_list(args) {
562
+ const params = new URLSearchParams();
563
+ const aid = agentId(args);
564
+ if (aid) params.set('agent_id', aid);
565
+ const res = await client.fetch(`/api/secrets?${params}`);
566
+ const data = await res.json();
567
+ return JSON.stringify(data);
568
+ },
569
+
570
+ async dashclaw_secret_due(args) {
571
+ const params = new URLSearchParams();
572
+ if (args.within_days != null) params.set('within_days', String(args.within_days));
573
+ const aid = agentId(args);
574
+ if (aid) params.set('agent_id', aid);
575
+ const res = await client.fetch(`/api/secrets/rotation-due?${params}`);
576
+ const data = await res.json();
577
+ return JSON.stringify(data);
578
+ },
579
+
580
+ async dashclaw_secret_mark_rotated(args) {
581
+ const res = await client.fetch(`/api/secrets/${encodeURIComponent(args.id)}`, {
582
+ method: 'PATCH',
583
+ body: JSON.stringify({ last_rotated_at: new Date().toISOString() }),
584
+ });
585
+ const data = await res.json();
586
+ return JSON.stringify(data);
587
+ },
588
+
589
+ async dashclaw_skill_scan(args) {
590
+ const res = await client.fetch('/api/skills/scan', {
591
+ method: 'POST',
592
+ body: JSON.stringify({
593
+ skill_name: args.skill_name,
594
+ files: args.files,
595
+ }),
596
+ });
597
+ const data = await res.json();
598
+ return JSON.stringify(data);
599
+ },
600
+
601
+ async dashclaw_loop_add(args) {
602
+ const res = await client.fetch('/api/actions/loops', {
603
+ method: 'POST',
604
+ body: JSON.stringify({
605
+ action_id: args.action_id,
606
+ loop_type: args.loop_type,
607
+ description: args.description,
608
+ priority: args.priority,
609
+ owner: args.owner,
610
+ }),
611
+ });
612
+ const data = await res.json();
613
+ return JSON.stringify(data);
614
+ },
615
+
616
+ async dashclaw_loop_list(args) {
617
+ const params = new URLSearchParams();
618
+ if (args.action_id) params.set('action_id', args.action_id);
619
+ if (args.status) params.set('status', args.status);
620
+ if (args.priority) params.set('priority', args.priority);
621
+ const aid = agentId(args);
622
+ if (aid) params.set('agent_id', aid);
623
+ if (args.from) params.set('from', args.from);
624
+ if (args.to) params.set('to', args.to);
625
+ const res = await client.fetch(`/api/actions/loops?${params}`);
626
+ const data = await res.json();
627
+ return JSON.stringify(data);
628
+ },
629
+
630
+ async dashclaw_loop_close(args) {
631
+ const res = await client.fetch(`/api/actions/loops/${encodeURIComponent(args.id)}`, {
632
+ method: 'PATCH',
633
+ body: JSON.stringify({
634
+ status: 'resolved',
635
+ resolution: args.resolution || 'Closed by agent via dashclaw_loop_close',
636
+ }),
637
+ });
638
+ const data = await res.json();
639
+ return JSON.stringify(data);
640
+ },
641
+
642
+ async dashclaw_learning_log(args) {
643
+ const res = await client.fetch('/api/learning', {
644
+ method: 'POST',
645
+ body: JSON.stringify({
646
+ agent_id: agentId(args),
647
+ decision: args.decision,
648
+ context: args.context,
649
+ outcome: args.outcome,
650
+ }),
651
+ });
652
+ const data = await res.json();
653
+ return JSON.stringify(data);
654
+ },
655
+
656
+ async dashclaw_learning_query(args) {
657
+ const params = new URLSearchParams();
658
+ const aid = agentId(args);
659
+ if (aid) params.set('agent_id', aid);
660
+ if (args.query) params.set('q', args.query);
661
+ if (args.limit) params.set('limit', String(args.limit));
662
+ const res = await client.fetch(`/api/learning/lessons?${params}`);
663
+ const data = await res.json();
664
+ return JSON.stringify(data);
665
+ },
666
+
667
+ async dashclaw_decisions_recent(args) {
668
+ const params = new URLSearchParams();
669
+ const aid = agentId(args);
670
+ if (aid) params.set('agent_id', aid);
671
+ if (args.action_type) params.set('action_type', args.action_type);
672
+ if (args.decision) params.set('decision', args.decision);
673
+ if (args.since) params.set('since', args.since);
674
+ if (args.limit) params.set('limit', String(args.limit));
675
+ const res = await client.fetch(`/api/guard/decisions?${params}`);
676
+ const data = await res.json();
677
+ return JSON.stringify(data);
678
+ },
679
+ };
680
+ }