osuite 2.9.0 → 2.9.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 +122 -62
- package/cli.js +367 -79
- package/index.cjs +29 -139
- package/legacy/index-v1.cjs +2 -90
- package/legacy/osuite-v1-runtime.js +7 -0
- package/legacy/osuite-v1.js +1 -1
- package/osuite.js +505 -10
- package/package.json +3 -9
- package/reviewSurface.js +131 -0
- package/LICENSE +0 -21
- package/dashclaw.js +0 -988
- package/legacy/dashclaw-v1.js +0 -2888
package/dashclaw.js
DELETED
|
@@ -1,988 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DashClaw SDK v2 (Stable Runtime API)
|
|
3
|
-
* Focused governance runtime client for AI agents.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
class ApprovalDeniedError extends Error {
|
|
7
|
-
constructor(message, decision) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.name = 'ApprovalDeniedError';
|
|
10
|
-
this.decision = decision;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
class GuardBlockedError extends Error {
|
|
15
|
-
constructor(decision) {
|
|
16
|
-
super(decision.reason || 'Action blocked by policy');
|
|
17
|
-
this.name = 'GuardBlockedError';
|
|
18
|
-
this.decision = decision;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const PCAA_CHECKPOINTS = [
|
|
23
|
-
{
|
|
24
|
-
id: 'pre_action_admissibility',
|
|
25
|
-
title: 'Pre-action admissibility',
|
|
26
|
-
summary: 'Evaluate whether the proposed action is allowed, simulated first, blocked, or approval-gated before side effects happen.',
|
|
27
|
-
sdkMethod: 'guard',
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: 'action_open',
|
|
31
|
-
title: 'Action open',
|
|
32
|
-
summary: 'Create the portable action record that becomes the trust object for replay, scoring, and proof.',
|
|
33
|
-
sdkMethod: 'createAction',
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: 'assumption_capture',
|
|
37
|
-
title: 'Assumption capture',
|
|
38
|
-
summary: 'Record what the runtime believed or depended on so operators can replay the reasoning boundary later.',
|
|
39
|
-
sdkMethod: 'recordAssumption',
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
id: 'approval_checkpoint',
|
|
43
|
-
title: 'Approval checkpoint',
|
|
44
|
-
summary: 'Pause, wait, or externally hold execution when policy requires a human checkpoint.',
|
|
45
|
-
sdkMethod: 'waitForApproval',
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
id: 'outcome_closure',
|
|
49
|
-
title: 'Outcome closure',
|
|
50
|
-
summary: 'Write the final result, evidence, and status so the action certificate closes cleanly.',
|
|
51
|
-
sdkMethod: 'updateOutcome',
|
|
52
|
-
},
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
function toPercent(numerator, denominator) {
|
|
56
|
-
if (!denominator || denominator <= 0) return 0;
|
|
57
|
-
return Math.max(0, Math.min(100, Math.round((numerator / denominator) * 100)));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function resolveRouteDecision({ action = null, proofBundle = null } = {}) {
|
|
61
|
-
return (
|
|
62
|
-
proofBundle?.route_decision
|
|
63
|
-
|| proofBundle?.action_certificate?.route_decision
|
|
64
|
-
|| action?.policy_snapshot?.effective_decision
|
|
65
|
-
|| null
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function resolveOutcomeStatus(status) {
|
|
70
|
-
const normalized = String(status || '').toLowerCase();
|
|
71
|
-
if (['completed', 'failed', 'blocked', 'cancelled', 'denied'].includes(normalized)) return 'complete';
|
|
72
|
-
if (['running', 'pending', 'pending_approval'].includes(normalized)) return 'pending';
|
|
73
|
-
return 'inactive';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function buildPcaaCheckpointStates({ action = null, trace = null, proofBundle = null } = {}) {
|
|
77
|
-
const routeDecision = resolveRouteDecision({ action, proofBundle });
|
|
78
|
-
const assumptionCount = Number(
|
|
79
|
-
trace?.trace?.assumptions?.total
|
|
80
|
-
?? trace?.assumptions?.total
|
|
81
|
-
?? (Array.isArray(trace?.assumptions) ? trace.assumptions.length : 0),
|
|
82
|
-
);
|
|
83
|
-
const approvalTriggered = ['require_approval', 'require_dual_approval', 'simulate_first', 'warn', 'block']
|
|
84
|
-
.includes(String(routeDecision || '').toLowerCase())
|
|
85
|
-
|| String(action?.status || '').toLowerCase() === 'pending_approval';
|
|
86
|
-
const approvalComplete = Boolean(
|
|
87
|
-
action?.approved_by
|
|
88
|
-
|| action?.approved_at
|
|
89
|
-
|| (Array.isArray(proofBundle?.approvals) && proofBundle.approvals.length > 0),
|
|
90
|
-
);
|
|
91
|
-
const outcomeStatus = resolveOutcomeStatus(action?.status);
|
|
92
|
-
|
|
93
|
-
return PCAA_CHECKPOINTS.map((checkpoint) => {
|
|
94
|
-
if (checkpoint.id === 'pre_action_admissibility') {
|
|
95
|
-
return {
|
|
96
|
-
...checkpoint,
|
|
97
|
-
status: routeDecision ? 'complete' : 'inactive',
|
|
98
|
-
value: routeDecision || 'not evaluated',
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (checkpoint.id === 'action_open') {
|
|
103
|
-
return {
|
|
104
|
-
...checkpoint,
|
|
105
|
-
status: action?.action_id ? 'complete' : 'inactive',
|
|
106
|
-
value: action?.action_id || '--',
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (checkpoint.id === 'assumption_capture') {
|
|
111
|
-
return {
|
|
112
|
-
...checkpoint,
|
|
113
|
-
status: assumptionCount > 0 ? 'complete' : 'inactive',
|
|
114
|
-
value: assumptionCount > 0 ? `${assumptionCount} recorded` : 'none recorded',
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (checkpoint.id === 'approval_checkpoint') {
|
|
119
|
-
return {
|
|
120
|
-
...checkpoint,
|
|
121
|
-
status: approvalComplete ? 'complete' : approvalTriggered ? 'active' : 'inactive',
|
|
122
|
-
value: approvalComplete ? 'approved' : approvalTriggered ? (routeDecision || 'active') : 'not required',
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
...checkpoint,
|
|
128
|
-
status: outcomeStatus,
|
|
129
|
-
value: action?.status || '--',
|
|
130
|
-
};
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
class DashClaw {
|
|
135
|
-
/**
|
|
136
|
-
* @param {Object} options
|
|
137
|
-
* @param {string} options.baseUrl - DashClaw base URL
|
|
138
|
-
* @param {string} options.apiKey - API key for authentication
|
|
139
|
-
* @param {string} options.agentId - Unique identifier for this agent
|
|
140
|
-
*/
|
|
141
|
-
constructor({ baseUrl, apiKey, agentId, agentKeyId = null, provenanceVersion = 'dc-prov-v1' }) {
|
|
142
|
-
if (!baseUrl) throw new Error('baseUrl is required');
|
|
143
|
-
if (!apiKey) throw new Error('apiKey is required');
|
|
144
|
-
if (!agentId) throw new Error('agentId is required');
|
|
145
|
-
|
|
146
|
-
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
147
|
-
this.apiKey = apiKey;
|
|
148
|
-
this.agentId = agentId;
|
|
149
|
-
this.agentKeyId = agentKeyId;
|
|
150
|
-
this.provenanceVersion = provenanceVersion;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
_withProvenance(payload = {}) {
|
|
154
|
-
const body = { ...payload };
|
|
155
|
-
const signature = body.agentSignature || body.agent_signature || body._signature || null;
|
|
156
|
-
|
|
157
|
-
delete body.agentSignature;
|
|
158
|
-
delete body.agent_signature;
|
|
159
|
-
|
|
160
|
-
if (signature) {
|
|
161
|
-
body._signature = signature;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (body.agent_key_id === undefined && this.agentKeyId) {
|
|
165
|
-
body.agent_key_id = this.agentKeyId;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (body.provenance_version === undefined && this.provenanceVersion) {
|
|
169
|
-
body.provenance_version = this.provenanceVersion;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return body;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async _request(path, method = 'GET', body = null, params = null, options = {}) {
|
|
176
|
-
let url = `${this.baseUrl}${path}`;
|
|
177
|
-
if (params) {
|
|
178
|
-
const qs = new URLSearchParams(params).toString();
|
|
179
|
-
if (qs) url += `?${qs}`;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const headers = {
|
|
183
|
-
'Content-Type': 'application/json',
|
|
184
|
-
'x-api-key': this.apiKey
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const res = await fetch(url, {
|
|
188
|
-
method,
|
|
189
|
-
headers,
|
|
190
|
-
body: body ? JSON.stringify(body) : undefined
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
let data = {};
|
|
194
|
-
if (typeof res.text === 'function') {
|
|
195
|
-
const text = await res.text();
|
|
196
|
-
data = text ? JSON.parse(text) : {};
|
|
197
|
-
} else if (typeof res.json === 'function') {
|
|
198
|
-
data = await res.json();
|
|
199
|
-
}
|
|
200
|
-
const allowedStatuses = new Set(options.allowStatuses || []);
|
|
201
|
-
|
|
202
|
-
if (!res.ok && !allowedStatuses.has(res.status)) {
|
|
203
|
-
if (res.status === 403 && data.decision && data.decision.decision === 'block') {
|
|
204
|
-
throw new GuardBlockedError(data.decision);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Prioritize reason (from governance blocks) over generic error field
|
|
208
|
-
const errorMessage = data.reason || data.error || `Request failed with status ${res.status}`;
|
|
209
|
-
const err = new Error(errorMessage);
|
|
210
|
-
err.status = res.status;
|
|
211
|
-
err.details = data.details;
|
|
212
|
-
err.decision = data;
|
|
213
|
-
throw err;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return data;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* POST /api/guard — "Can I do X?"
|
|
221
|
-
* @param {Object} context
|
|
222
|
-
* @returns {Promise<{decision: 'allow'|'block'|'require_approval', action_id: string, reason: string, signals: string[]}>}
|
|
223
|
-
*/
|
|
224
|
-
async guard(context) {
|
|
225
|
-
return this._request('/api/guard', 'POST', this._withProvenance({
|
|
226
|
-
...context,
|
|
227
|
-
agent_id: context.agent_id || this.agentId,
|
|
228
|
-
}));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* POST /api/actions — "I am attempting X."
|
|
233
|
-
*/
|
|
234
|
-
async createAction(action) {
|
|
235
|
-
return this._request('/api/actions', 'POST', this._withProvenance({
|
|
236
|
-
...action,
|
|
237
|
-
agent_id: this.agentId,
|
|
238
|
-
}));
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* PATCH /api/actions/:id — "X finished with result Y."
|
|
243
|
-
*/
|
|
244
|
-
async updateOutcome(actionId, outcome) {
|
|
245
|
-
return this._request(`/api/actions/${actionId}`, 'PATCH', {
|
|
246
|
-
...outcome,
|
|
247
|
-
timestamp_end: outcome.timestamp_end || new Date().toISOString()
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* GET /api/actions/:id — Fetch a single action by ID.
|
|
253
|
-
*/
|
|
254
|
-
async getAction(actionId) {
|
|
255
|
-
return this._request(`/api/actions/${actionId}`, 'GET');
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* GET /api/actions/:id/trace — Fetch replay trace, assumptions, loops, and root-cause indicators.
|
|
260
|
-
*/
|
|
261
|
-
async getActionTrace(actionId) {
|
|
262
|
-
return this._request(`/api/actions/${actionId}/trace`, 'GET');
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* GET /api/actions?status=pending_approval — List actions awaiting approval.
|
|
267
|
-
*/
|
|
268
|
-
async getPendingApprovals(limit = 20, offset = 0) {
|
|
269
|
-
return this._request('/api/actions', 'GET', null, {
|
|
270
|
-
status: 'pending_approval',
|
|
271
|
-
limit,
|
|
272
|
-
offset,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* POST /api/actions/:id/approve — Approve or deny an action.
|
|
278
|
-
* @param {string} actionId
|
|
279
|
-
* @param {'allow'|'deny'} decision
|
|
280
|
-
* @param {string} [reasoning]
|
|
281
|
-
*/
|
|
282
|
-
async approveAction(actionId, decision, reasoning) {
|
|
283
|
-
const body = { decision };
|
|
284
|
-
if (reasoning) body.reasoning = reasoning;
|
|
285
|
-
return this._request(`/api/approvals/${actionId}`, 'POST', body);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* POST /api/assumptions — "I believe Z is true while doing X."
|
|
290
|
-
*/
|
|
291
|
-
async recordAssumption(assumption) {
|
|
292
|
-
return this._request('/api/assumptions', 'POST', assumption);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* GET /api/actions/:id — Polling helper for human approval.
|
|
297
|
-
*/
|
|
298
|
-
async waitForApproval(actionId, { timeout = 300000, interval = 5000 } = {}) {
|
|
299
|
-
const startTime = Date.now();
|
|
300
|
-
let wasPending = false;
|
|
301
|
-
let printedBlock = false;
|
|
302
|
-
|
|
303
|
-
while (Date.now() - startTime < timeout) {
|
|
304
|
-
const { action } = await this._request(`/api/actions/${actionId}`, 'GET');
|
|
305
|
-
|
|
306
|
-
// Print structured approval block on first fetch
|
|
307
|
-
if (!printedBlock) {
|
|
308
|
-
printedBlock = true;
|
|
309
|
-
try {
|
|
310
|
-
const actionType = action.action_type || 'unknown';
|
|
311
|
-
const riskScore = action.risk_score != null ? String(action.risk_score) : '-';
|
|
312
|
-
const goal = action.declared_goal || '-';
|
|
313
|
-
const agent = action.agent_id || this.agentId;
|
|
314
|
-
const replayUrl = `${this.baseUrl}/replay/${actionId}`;
|
|
315
|
-
|
|
316
|
-
const lines = [
|
|
317
|
-
'\u2554\u2550\u2550 DashClaw Approval Required \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557',
|
|
318
|
-
` Action ID: ${actionId}`,
|
|
319
|
-
` Agent: ${agent}`,
|
|
320
|
-
` Action: ${actionType}`,
|
|
321
|
-
' Policy: require_approval',
|
|
322
|
-
` Risk Score: ${riskScore}`,
|
|
323
|
-
` Goal: ${goal}`,
|
|
324
|
-
'',
|
|
325
|
-
` Replay: ${replayUrl}`,
|
|
326
|
-
'',
|
|
327
|
-
' Waiting for approval... (Ctrl+C to abort)',
|
|
328
|
-
'\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d',
|
|
329
|
-
];
|
|
330
|
-
process.stdout.write('\n' + lines.join('\n') + '\n\n');
|
|
331
|
-
} catch (_) {
|
|
332
|
-
// Rendering failure must not prevent the wait from proceeding
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (action.status === 'pending_approval') {
|
|
337
|
-
wasPending = true;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Explicitly unblocked by approval metadata.
|
|
341
|
-
// API key / CLI approvals may not have a human user id, so approved_at is
|
|
342
|
-
// also treated as authoritative approval evidence.
|
|
343
|
-
if (action.approved_by || action.approved_at) return action;
|
|
344
|
-
|
|
345
|
-
// Denial cases
|
|
346
|
-
if (action.status === 'failed' || action.status === 'cancelled') {
|
|
347
|
-
throw new ApprovalDeniedError(action.error_message || 'Operator denied the action.', action.status);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Requirement 4: If an action leaves pending_approval without approval metadata, throw an error.
|
|
351
|
-
// This prevents "auto-approval" bugs where status is changed by non-approval paths.
|
|
352
|
-
if (wasPending && action.status !== 'pending_approval') {
|
|
353
|
-
throw new Error(`Action ${actionId} left pending_approval state without explicit approval metadata (Status: ${action.status})`);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// If allowed directly (never intercepted), return immediately
|
|
357
|
-
if (!wasPending && action.status === 'running') {
|
|
358
|
-
return { action };
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
await new Promise(r => setTimeout(r, interval));
|
|
362
|
-
}
|
|
363
|
-
throw new Error(`Timed out waiting for approval of action ${actionId}`);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Subscribe to real-time SSE events from the OSuite server.
|
|
368
|
-
* Uses fetch-based SSE parsing for Node 18+ compatibility.
|
|
369
|
-
*/
|
|
370
|
-
events({ reconnect = true, maxRetries = Infinity, retryInterval = 3000 } = {}) {
|
|
371
|
-
const url = `${this.baseUrl}/api/stream`;
|
|
372
|
-
const handlers = new Map();
|
|
373
|
-
let closed = false;
|
|
374
|
-
let controller = null;
|
|
375
|
-
let lastEventId = null;
|
|
376
|
-
let retryCount = 0;
|
|
377
|
-
|
|
378
|
-
const emit = (eventType, data) => {
|
|
379
|
-
const callbacks = handlers.get(eventType) || [];
|
|
380
|
-
for (const callback of callbacks) {
|
|
381
|
-
try {
|
|
382
|
-
callback(data);
|
|
383
|
-
} catch {
|
|
384
|
-
// Event handlers are intentionally isolated from stream health
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
|
|
389
|
-
const connect = async () => {
|
|
390
|
-
controller = new AbortController();
|
|
391
|
-
const headers = { 'x-api-key': this.apiKey };
|
|
392
|
-
if (lastEventId) headers['last-event-id'] = lastEventId;
|
|
393
|
-
|
|
394
|
-
const res = await fetch(url, {
|
|
395
|
-
headers,
|
|
396
|
-
signal: controller.signal,
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
if (!res.ok) {
|
|
400
|
-
throw new Error(`SSE connection failed: ${res.status} ${res.statusText}`);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
retryCount = 0;
|
|
404
|
-
|
|
405
|
-
const reader = res.body.getReader();
|
|
406
|
-
const decoder = new TextDecoder();
|
|
407
|
-
let buffer = '';
|
|
408
|
-
let currentEvent = null;
|
|
409
|
-
let currentData = '';
|
|
410
|
-
|
|
411
|
-
while (!closed) {
|
|
412
|
-
const { done, value } = await reader.read();
|
|
413
|
-
if (done) break;
|
|
414
|
-
buffer += decoder.decode(value, { stream: true });
|
|
415
|
-
|
|
416
|
-
const lines = buffer.split('\n');
|
|
417
|
-
buffer = lines.pop() || '';
|
|
418
|
-
|
|
419
|
-
for (const line of lines) {
|
|
420
|
-
if (line.startsWith('id: ')) {
|
|
421
|
-
lastEventId = line.slice(4).trim();
|
|
422
|
-
} else if (line.startsWith('event: ')) {
|
|
423
|
-
currentEvent = line.slice(7).trim();
|
|
424
|
-
} else if (line.startsWith('data: ')) {
|
|
425
|
-
currentData += line.slice(6);
|
|
426
|
-
} else if (line.startsWith(':')) {
|
|
427
|
-
continue;
|
|
428
|
-
} else if (line === '' && currentEvent) {
|
|
429
|
-
if (currentData) {
|
|
430
|
-
try {
|
|
431
|
-
emit(currentEvent, JSON.parse(currentData));
|
|
432
|
-
} catch {
|
|
433
|
-
// Ignore malformed frames to keep the stream alive
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
currentEvent = null;
|
|
437
|
-
currentData = '';
|
|
438
|
-
} else if (line === '') {
|
|
439
|
-
currentEvent = null;
|
|
440
|
-
currentData = '';
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
const connectLoop = async () => {
|
|
447
|
-
while (!closed) {
|
|
448
|
-
try {
|
|
449
|
-
await connect();
|
|
450
|
-
} catch (error) {
|
|
451
|
-
if (closed) return;
|
|
452
|
-
emit('error', error);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (closed) return;
|
|
456
|
-
if (!reconnect || retryCount >= maxRetries) {
|
|
457
|
-
emit('error', new Error('SSE stream ended'));
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
retryCount += 1;
|
|
462
|
-
emit('reconnecting', { attempt: retryCount, maxRetries });
|
|
463
|
-
await new Promise((resolve) => setTimeout(resolve, retryInterval));
|
|
464
|
-
}
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
const connectionPromise = connectLoop();
|
|
468
|
-
|
|
469
|
-
const handle = {
|
|
470
|
-
on(eventType, callback) {
|
|
471
|
-
if (!handlers.has(eventType)) handlers.set(eventType, []);
|
|
472
|
-
handlers.get(eventType).push(callback);
|
|
473
|
-
return handle;
|
|
474
|
-
},
|
|
475
|
-
close() {
|
|
476
|
-
closed = true;
|
|
477
|
-
if (controller) controller.abort();
|
|
478
|
-
},
|
|
479
|
-
_promise: connectionPromise,
|
|
480
|
-
};
|
|
481
|
-
|
|
482
|
-
return handle;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* POST /api/agents/heartbeat
|
|
487
|
-
*/
|
|
488
|
-
async heartbeat(status = 'online', metadata = null) {
|
|
489
|
-
return this._request('/api/agents/heartbeat', 'POST', {
|
|
490
|
-
agent_id: this.agentId,
|
|
491
|
-
status,
|
|
492
|
-
metadata
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* POST /api/agents/connections
|
|
498
|
-
*/
|
|
499
|
-
async reportConnections(connections) {
|
|
500
|
-
return this._request('/api/agents/connections', 'POST', {
|
|
501
|
-
agent_id: this.agentId,
|
|
502
|
-
connections
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* POST /api/actions/loops
|
|
508
|
-
*/
|
|
509
|
-
async registerOpenLoop(actionId, loopType, description, metadata = null) {
|
|
510
|
-
return this._request('/api/actions/loops', 'POST', {
|
|
511
|
-
action_id: actionId,
|
|
512
|
-
loop_type: loopType,
|
|
513
|
-
description,
|
|
514
|
-
metadata
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* PATCH /api/actions/loops/:id
|
|
520
|
-
*/
|
|
521
|
-
async resolveOpenLoop(loopId, status, resolution = null) {
|
|
522
|
-
return this._request(`/api/actions/loops/${loopId}`, 'PATCH', {
|
|
523
|
-
status,
|
|
524
|
-
resolution
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* GET /api/actions/signals
|
|
530
|
-
*/
|
|
531
|
-
async getSignals() {
|
|
532
|
-
return this._request('/api/actions/signals');
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* GET /api/governance/proof — Fetch operator-facing or bundle-form governance proof.
|
|
537
|
-
*/
|
|
538
|
-
async getGovernanceProof({ actionId = null, format = null } = {}) {
|
|
539
|
-
return this._request('/api/governance/proof', 'GET', null, {
|
|
540
|
-
...(actionId ? { action_id: actionId } : {}),
|
|
541
|
-
...(format ? { format } : {}),
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/**
|
|
546
|
-
* GET /api/governance/proof?format=bundle — Fetch machine-readable PCAA / proof bundle output.
|
|
547
|
-
*/
|
|
548
|
-
async getGovernanceProofBundle({ actionId = null } = {}) {
|
|
549
|
-
return this.getGovernanceProof({ actionId, format: 'bundle' });
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* POST /api/governance/proof/verify — Verify one action attestation and proof bundle.
|
|
554
|
-
*/
|
|
555
|
-
async verifyGovernanceProof(actionId, { throwOnInvalid = false } = {}) {
|
|
556
|
-
return this._request(
|
|
557
|
-
'/api/governance/proof/verify',
|
|
558
|
-
'POST',
|
|
559
|
-
{ action_id: actionId },
|
|
560
|
-
null,
|
|
561
|
-
throwOnInvalid ? {} : { allowStatuses: [422] },
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* GET /api/compliance/evidence — Fetch live evidence aggregates for PCAA / compliance.
|
|
567
|
-
*/
|
|
568
|
-
async getComplianceEvidence({ window = '30d' } = {}) {
|
|
569
|
-
return this._request('/api/compliance/evidence', 'GET', null, { window });
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/**
|
|
573
|
-
* GET /api/proof/export — Fetch a portable proof export by action or bundle id.
|
|
574
|
-
*/
|
|
575
|
-
async exportProof({ actionId = null, bundleId = null } = {}) {
|
|
576
|
-
if (!actionId && !bundleId) {
|
|
577
|
-
throw new Error('actionId or bundleId is required');
|
|
578
|
-
}
|
|
579
|
-
return this._request('/api/proof/export', 'GET', null, {
|
|
580
|
-
...(actionId ? { action_id: actionId } : {}),
|
|
581
|
-
...(bundleId ? { bundle_id: bundleId } : {}),
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* Build the same PCAA health summary used by the workspace banner.
|
|
587
|
-
*/
|
|
588
|
-
async getPcaaHealth({ window = '30d', agentId = null } = {}) {
|
|
589
|
-
const [actionsStats, complianceEvidence] = await Promise.all([
|
|
590
|
-
this._request('/api/actions/stats', 'GET', null, {
|
|
591
|
-
...(agentId ? { agent_id: agentId } : {}),
|
|
592
|
-
}),
|
|
593
|
-
this.getComplianceEvidence({ window }),
|
|
594
|
-
]);
|
|
595
|
-
|
|
596
|
-
const evidence = complianceEvidence?.evidence || {};
|
|
597
|
-
const totalActions = Number(actionsStats?.total || 0);
|
|
598
|
-
const recordedActions = Number(evidence.action_records_total || 0);
|
|
599
|
-
const approvals = Number(evidence.approval_requests || actionsStats?.approval || 0);
|
|
600
|
-
const proofBundles = Number(evidence.proof_bundles_total || 0);
|
|
601
|
-
const completeBundles = Number(evidence.complete_proof_bundles || 0);
|
|
602
|
-
|
|
603
|
-
return {
|
|
604
|
-
totalActions,
|
|
605
|
-
recordedActions,
|
|
606
|
-
approvals,
|
|
607
|
-
proofBundles,
|
|
608
|
-
completeBundles,
|
|
609
|
-
checkpointCoverage: toPercent(recordedActions, totalActions || recordedActions || 1),
|
|
610
|
-
approvalRate: toPercent(approvals, totalActions || approvals || 1),
|
|
611
|
-
evidenceCompletion: toPercent(completeBundles, proofBundles || completeBundles || 1),
|
|
612
|
-
window,
|
|
613
|
-
generatedAt: complianceEvidence?.generated_at || new Date().toISOString(),
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
* Fetch one action's PCAA view: action, trace, proof bundle, export bundle, and derived checkpoints.
|
|
619
|
-
*/
|
|
620
|
-
async getPcaaAction(actionId) {
|
|
621
|
-
const [actionPayload, trace, proofBundle, proofExport] = await Promise.all([
|
|
622
|
-
this.getAction(actionId),
|
|
623
|
-
this.getActionTrace(actionId),
|
|
624
|
-
this.getGovernanceProofBundle({ actionId }),
|
|
625
|
-
this.exportProof({ actionId }).catch((error) => {
|
|
626
|
-
if (error?.status === 404) return null;
|
|
627
|
-
throw error;
|
|
628
|
-
}),
|
|
629
|
-
]);
|
|
630
|
-
|
|
631
|
-
const action = actionPayload?.action || actionPayload || null;
|
|
632
|
-
return {
|
|
633
|
-
action,
|
|
634
|
-
trace,
|
|
635
|
-
proofBundle,
|
|
636
|
-
proofExport,
|
|
637
|
-
checkpoints: buildPcaaCheckpointStates({ action, trace, proofBundle }),
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* GET /api/learning/analytics/velocity
|
|
643
|
-
*/
|
|
644
|
-
async getLearningVelocity(lookbackDays = 30) {
|
|
645
|
-
return this._request('/api/learning/analytics/velocity', 'GET', null, {
|
|
646
|
-
agent_id: this.agentId,
|
|
647
|
-
lookback_days: lookbackDays
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
/**
|
|
652
|
-
* GET /api/learning/analytics/curves
|
|
653
|
-
*/
|
|
654
|
-
async getLearningCurves(lookbackDays = 60) {
|
|
655
|
-
return this._request('/api/learning/analytics/curves', 'GET', null, {
|
|
656
|
-
agent_id: this.agentId,
|
|
657
|
-
lookback_days: lookbackDays
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
/**
|
|
662
|
-
* GET /api/learning/lessons — Fetch consolidated lessons from scored outcomes.
|
|
663
|
-
*/
|
|
664
|
-
async getLessons({ actionType, limit } = {}) {
|
|
665
|
-
return this._request('/api/learning/lessons', 'GET', null, {
|
|
666
|
-
agent_id: this.agentId,
|
|
667
|
-
...(actionType && { action_type: actionType }),
|
|
668
|
-
...(limit && { limit }),
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
/**
|
|
673
|
-
* POST /api/prompts/render
|
|
674
|
-
*/
|
|
675
|
-
async renderPrompt({ template_id, version_id, variables, record = false }) {
|
|
676
|
-
return this._request('/api/prompts/render', 'POST', {
|
|
677
|
-
template_id,
|
|
678
|
-
version_id,
|
|
679
|
-
variables,
|
|
680
|
-
agent_id: this.agentId,
|
|
681
|
-
record
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* POST /api/evaluations/scorers
|
|
687
|
-
*/
|
|
688
|
-
async createScorer(name, scorer_type, config = null, description = null) {
|
|
689
|
-
return this._request('/api/evaluations/scorers', 'POST', {
|
|
690
|
-
name,
|
|
691
|
-
scorer_type,
|
|
692
|
-
config,
|
|
693
|
-
description
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
/**
|
|
698
|
-
* POST /api/scoring/profiles
|
|
699
|
-
*/
|
|
700
|
-
async createScoringProfile(profile) {
|
|
701
|
-
return this._request('/api/scoring/profiles', 'POST', profile);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* GET /api/scoring/profiles
|
|
706
|
-
*/
|
|
707
|
-
async listScoringProfiles(filters = {}) {
|
|
708
|
-
return this._request('/api/scoring/profiles', 'GET', null, filters);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/**
|
|
712
|
-
* GET /api/scoring/profiles/:id
|
|
713
|
-
*/
|
|
714
|
-
async getScoringProfile(profileId) {
|
|
715
|
-
return this._request(`/api/scoring/profiles/${profileId}`, 'GET');
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
/**
|
|
719
|
-
* PATCH /api/scoring/profiles/:id
|
|
720
|
-
*/
|
|
721
|
-
async updateScoringProfile(profileId, updates) {
|
|
722
|
-
return this._request(`/api/scoring/profiles/${profileId}`, 'PATCH', updates);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* DELETE /api/scoring/profiles/:id
|
|
727
|
-
*/
|
|
728
|
-
async deleteScoringProfile(profileId) {
|
|
729
|
-
return this._request(`/api/scoring/profiles/${profileId}`, 'DELETE');
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/**
|
|
733
|
-
* POST /api/scoring/profiles/:id/dimensions
|
|
734
|
-
*/
|
|
735
|
-
async addScoringDimension(profileId, dimension) {
|
|
736
|
-
return this._request(`/api/scoring/profiles/${profileId}/dimensions`, 'POST', dimension);
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
* PATCH /api/scoring/profiles/:id/dimensions/:dimId
|
|
741
|
-
*/
|
|
742
|
-
async updateScoringDimension(profileId, dimensionId, updates) {
|
|
743
|
-
return this._request(`/api/scoring/profiles/${profileId}/dimensions/${dimensionId}`, 'PATCH', updates);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* DELETE /api/scoring/profiles/:id/dimensions/:dimId
|
|
748
|
-
*/
|
|
749
|
-
async deleteScoringDimension(profileId, dimensionId) {
|
|
750
|
-
return this._request(`/api/scoring/profiles/${profileId}/dimensions/${dimensionId}`, 'DELETE');
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
/**
|
|
754
|
-
* POST /api/scoring/score — score a single action against a profile
|
|
755
|
-
*/
|
|
756
|
-
async scoreWithProfile(profileId, action) {
|
|
757
|
-
return this._request('/api/scoring/score', 'POST', { profile_id: profileId, action });
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
/**
|
|
761
|
-
* POST /api/scoring/score — batch score multiple actions against a profile
|
|
762
|
-
*/
|
|
763
|
-
async batchScoreWithProfile(profileId, actions) {
|
|
764
|
-
return this._request('/api/scoring/score', 'POST', { profile_id: profileId, actions });
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
/**
|
|
768
|
-
* GET /api/scoring/score — list stored profile scores
|
|
769
|
-
*/
|
|
770
|
-
async getProfileScores(filters = {}) {
|
|
771
|
-
return this._request('/api/scoring/score', 'GET', null, filters);
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
/**
|
|
775
|
-
* GET /api/scoring/score?view=stats — aggregate stats for a profile
|
|
776
|
-
*/
|
|
777
|
-
async getProfileScoreStats(profileId) {
|
|
778
|
-
return this._request('/api/scoring/score', 'GET', null, { profile_id: profileId, view: 'stats' });
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
/**
|
|
782
|
-
* POST /api/scoring/risk-templates
|
|
783
|
-
*/
|
|
784
|
-
async createRiskTemplate(template) {
|
|
785
|
-
return this._request('/api/scoring/risk-templates', 'POST', template);
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
/**
|
|
789
|
-
* GET /api/scoring/risk-templates
|
|
790
|
-
*/
|
|
791
|
-
async listRiskTemplates(filters = {}) {
|
|
792
|
-
return this._request('/api/scoring/risk-templates', 'GET', null, filters);
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
/**
|
|
796
|
-
* PATCH /api/scoring/risk-templates/:id
|
|
797
|
-
*/
|
|
798
|
-
async updateRiskTemplate(templateId, updates) {
|
|
799
|
-
return this._request(`/api/scoring/risk-templates/${templateId}`, 'PATCH', updates);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
/**
|
|
803
|
-
* DELETE /api/scoring/risk-templates/:id
|
|
804
|
-
*/
|
|
805
|
-
async deleteRiskTemplate(templateId) {
|
|
806
|
-
return this._request(`/api/scoring/risk-templates/${templateId}`, 'DELETE');
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
/**
|
|
810
|
-
* POST /api/scoring/calibrate — analyze historical data and suggest dimension thresholds
|
|
811
|
-
*/
|
|
812
|
-
async autoCalibrate(options = {}) {
|
|
813
|
-
return this._request('/api/scoring/calibrate', 'POST', options);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// ---------------------------------------------------------------------------
|
|
817
|
-
// Agent Messaging
|
|
818
|
-
// ---------------------------------------------------------------------------
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
* POST /api/messages — Send a message to another agent or the dashboard.
|
|
822
|
-
*/
|
|
823
|
-
async sendMessage({ to, type, subject, body, threadId, urgent, actionId }) {
|
|
824
|
-
return this._request('/api/messages', 'POST', {
|
|
825
|
-
from_agent_id: this.agentId,
|
|
826
|
-
to_agent_id: to,
|
|
827
|
-
message_type: type,
|
|
828
|
-
subject,
|
|
829
|
-
body,
|
|
830
|
-
thread_id: threadId,
|
|
831
|
-
urgent,
|
|
832
|
-
action_id: actionId,
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
/**
|
|
837
|
-
* Create a scoped action context that auto-tags messages and assumptions
|
|
838
|
-
* with the given action_id.
|
|
839
|
-
* @param {string} actionId - The action_id to attach to all operations
|
|
840
|
-
* @returns {{ sendMessage, recordAssumption, updateOutcome }}
|
|
841
|
-
*/
|
|
842
|
-
actionContext(actionId) {
|
|
843
|
-
return {
|
|
844
|
-
sendMessage: ({ to, type, subject, body, threadId, urgent }) => {
|
|
845
|
-
return this.sendMessage({ to, type, subject, body, threadId, urgent, actionId });
|
|
846
|
-
},
|
|
847
|
-
recordAssumption: (assumption) => {
|
|
848
|
-
return this.recordAssumption({ ...assumption, action_id: actionId });
|
|
849
|
-
},
|
|
850
|
-
updateOutcome: (outcome) => {
|
|
851
|
-
return this.updateOutcome(actionId, outcome);
|
|
852
|
-
},
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* GET /api/messages — Fetch this agent's inbox.
|
|
858
|
-
*/
|
|
859
|
-
async getInbox({ type, unread, limit } = {}) {
|
|
860
|
-
return this._request('/api/messages', 'GET', null, {
|
|
861
|
-
agent_id: this.agentId,
|
|
862
|
-
direction: 'inbox',
|
|
863
|
-
...(type && { type }),
|
|
864
|
-
...(unread != null && { unread }),
|
|
865
|
-
...(limit && { limit }),
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// ---------------------------------------------------------------------------
|
|
870
|
-
// Session Handoffs
|
|
871
|
-
// ---------------------------------------------------------------------------
|
|
872
|
-
|
|
873
|
-
/**
|
|
874
|
-
* POST /api/handoffs — Create a session handoff record.
|
|
875
|
-
*/
|
|
876
|
-
async createHandoff(handoff) {
|
|
877
|
-
return this._request('/api/handoffs', 'POST', {
|
|
878
|
-
agent_id: this.agentId,
|
|
879
|
-
...handoff,
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
/**
|
|
884
|
-
* GET /api/handoffs — Fetch the most recent handoff for this agent.
|
|
885
|
-
*/
|
|
886
|
-
async getLatestHandoff() {
|
|
887
|
-
return this._request('/api/handoffs', 'GET', null, {
|
|
888
|
-
agent_id: this.agentId,
|
|
889
|
-
latest: 'true',
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
// ---------------------------------------------------------------------------
|
|
894
|
-
// Security Scanning
|
|
895
|
-
// ---------------------------------------------------------------------------
|
|
896
|
-
|
|
897
|
-
/**
|
|
898
|
-
* POST /api/security/prompt-injection — Scan text for prompt injection attacks.
|
|
899
|
-
*/
|
|
900
|
-
async scanPromptInjection(text, { source } = {}) {
|
|
901
|
-
return this._request('/api/security/prompt-injection', 'POST', {
|
|
902
|
-
text,
|
|
903
|
-
source,
|
|
904
|
-
agent_id: this.agentId,
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// ---------------------------------------------------------------------------
|
|
909
|
-
// User Feedback
|
|
910
|
-
// ---------------------------------------------------------------------------
|
|
911
|
-
|
|
912
|
-
/**
|
|
913
|
-
* POST /api/feedback — Submit user feedback linked to an action.
|
|
914
|
-
*/
|
|
915
|
-
async submitFeedback({ action_id, rating, comment, category, tags, metadata }) {
|
|
916
|
-
return this._request('/api/feedback', 'POST', {
|
|
917
|
-
action_id,
|
|
918
|
-
agent_id: this.agentId,
|
|
919
|
-
rating,
|
|
920
|
-
comment,
|
|
921
|
-
category,
|
|
922
|
-
tags,
|
|
923
|
-
metadata,
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
// ---------------------------------------------------------------------------
|
|
928
|
-
// Context Threads
|
|
929
|
-
// ---------------------------------------------------------------------------
|
|
930
|
-
|
|
931
|
-
/**
|
|
932
|
-
* POST /api/context/threads — Create a reasoning context thread.
|
|
933
|
-
*/
|
|
934
|
-
async createThread(thread) {
|
|
935
|
-
return this._request('/api/context/threads', 'POST', {
|
|
936
|
-
agent_id: this.agentId,
|
|
937
|
-
...thread,
|
|
938
|
-
});
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
/**
|
|
942
|
-
* POST /api/context/threads/:id/entries — Append a reasoning step.
|
|
943
|
-
*/
|
|
944
|
-
async addThreadEntry(threadId, content, entryType) {
|
|
945
|
-
return this._request(`/api/context/threads/${threadId}/entries`, 'POST', {
|
|
946
|
-
content,
|
|
947
|
-
entry_type: entryType,
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
/**
|
|
952
|
-
* PATCH /api/context/threads/:id — Close a reasoning thread.
|
|
953
|
-
*/
|
|
954
|
-
async closeThread(threadId, summary) {
|
|
955
|
-
return this._request(`/api/context/threads/${threadId}`, 'PATCH', {
|
|
956
|
-
status: 'closed',
|
|
957
|
-
...(summary ? { summary } : {}),
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// ---------------------------------------------------------------------------
|
|
962
|
-
// Bulk Sync
|
|
963
|
-
// ---------------------------------------------------------------------------
|
|
964
|
-
|
|
965
|
-
/**
|
|
966
|
-
* POST /api/sync — Bulk state sync for periodic updates or bootstrap.
|
|
967
|
-
*/
|
|
968
|
-
async syncState(state) {
|
|
969
|
-
return this._request('/api/sync', 'POST', {
|
|
970
|
-
agent_id: this.agentId,
|
|
971
|
-
...state,
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
const OSuite = DashClaw;
|
|
977
|
-
const Osuite = DashClaw;
|
|
978
|
-
|
|
979
|
-
export default OSuite;
|
|
980
|
-
export {
|
|
981
|
-
DashClaw,
|
|
982
|
-
OSuite,
|
|
983
|
-
Osuite,
|
|
984
|
-
ApprovalDeniedError,
|
|
985
|
-
GuardBlockedError,
|
|
986
|
-
PCAA_CHECKPOINTS,
|
|
987
|
-
buildPcaaCheckpointStates,
|
|
988
|
-
};
|