watchmyagents 1.0.1 → 1.0.2
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/package.json +1 -1
- package/src/anonymizer.js +19 -0
- package/src/logger.js +7 -0
- package/src/sources/anthropic-managed.js +22 -0
- package/src/sources/contract.js +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "watchmyagents",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Security observability + real-time policy enforcement for AI agents. Local-first NDJSON capture with a continuous Watch daemon that auto-uploads anonymized signals, Shield CLI that blocks policy violations live (with policies pulled from Fortress cloud), anonymizer producing signals-only payloads, bidirectional sync with WatchMyAgents Fortress, and one-command install as an always-on launchd/systemd service — closing the recursive Watch→Guardian→Shield security loop.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
package/src/anonymizer.js
CHANGED
|
@@ -134,6 +134,13 @@ export class SignalsAggregator {
|
|
|
134
134
|
this.entryCount = 0;
|
|
135
135
|
this._prevActionType = null;
|
|
136
136
|
this._prevSessionId = null;
|
|
137
|
+
// v1.0.2 F-6b — opaque session ids active in this window. Shipped to
|
|
138
|
+
// Fortress in the payload as `session_ids[]` so an operator looking at
|
|
139
|
+
// a Shield decision in the dashboard can grep their LOCAL NDJSON by
|
|
140
|
+
// session_id immediately (forensics short-circuit). The Anthropic
|
|
141
|
+
// session_id is a non-semantic token like `sess_01XaNB…` — same
|
|
142
|
+
// sensitivity class as `agent_id`, which we already transmit.
|
|
143
|
+
this.seenSessions = new Set(); // unique session_ids
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
add(entry) {
|
|
@@ -147,6 +154,13 @@ export class SignalsAggregator {
|
|
|
147
154
|
if (!this.windowEnd || ts > this.windowEnd) this.windowEnd = ts;
|
|
148
155
|
}
|
|
149
156
|
|
|
157
|
+
// F-6b — collect every distinct session_id encountered in the window.
|
|
158
|
+
// Stays opaque (no string transformation), bounded by the natural
|
|
159
|
+
// number of sessions in the window.
|
|
160
|
+
if (typeof entry.session_id === 'string' && entry.session_id.length > 0) {
|
|
161
|
+
this.seenSessions.add(entry.session_id);
|
|
162
|
+
}
|
|
163
|
+
|
|
150
164
|
// Counts
|
|
151
165
|
const at = entry.action_type || 'unknown';
|
|
152
166
|
this.counts[at] = (this.counts[at] || 0) + 1;
|
|
@@ -233,6 +247,11 @@ export class SignalsAggregator {
|
|
|
233
247
|
sequences_top10: sequencesTop,
|
|
234
248
|
stop_reasons: this.stopReasons,
|
|
235
249
|
tokens_total: this.tokensTotal,
|
|
250
|
+
// F-6c — opaque session ids active in this window, sorted for
|
|
251
|
+
// determinism. Operator forensic chain:
|
|
252
|
+
// Fortress decision → window_start/end + session_ids → grep
|
|
253
|
+
// the local NDJSON of the affected agent → full raw context.
|
|
254
|
+
session_ids: [...this.seenSessions].sort(),
|
|
236
255
|
},
|
|
237
256
|
_meta: {
|
|
238
257
|
entries_processed: this.entryCount,
|
package/src/logger.js
CHANGED
|
@@ -13,6 +13,8 @@ import { assertSafePathSegment } from './validate.js';
|
|
|
13
13
|
const EXPORT_FIELDS = [
|
|
14
14
|
'id', 'agent_id', 'parent_agent_id', 'composition_pattern',
|
|
15
15
|
'provider', 'timestamp', 'action_type',
|
|
16
|
+
// v1.0.2 F-6a — Anthropic-style sub-agent discriminators preserved locally
|
|
17
|
+
'session_thread_id', 'agent_name',
|
|
16
18
|
'tool_name', 'duration_ms', 'tokens_used',
|
|
17
19
|
'input_tokens', 'output_tokens', 'cache_read_tokens', 'cache_creation_tokens',
|
|
18
20
|
'cost_usd', 'model',
|
|
@@ -60,6 +62,11 @@ export class Logger {
|
|
|
60
62
|
// populates these on the event, and the Logger threads them through.
|
|
61
63
|
parent_agent_id: e.parent_agent_id ?? null,
|
|
62
64
|
composition_pattern: e.composition_pattern || 'solo',
|
|
65
|
+
// v1.0.2 F-6a: Anthropic-style discriminators preserved LOCAL ONLY
|
|
66
|
+
// (never sent raw to Fortress — SignalsAggregator derives the
|
|
67
|
+
// aggregated session_ids list from these at finalize time).
|
|
68
|
+
session_thread_id: e.session_thread_id ?? null,
|
|
69
|
+
agent_name: e.agent_name ?? null,
|
|
63
70
|
provider: e.provider || e.framework || 'generic',
|
|
64
71
|
timestamp: e.timestamp || new Date().toISOString(),
|
|
65
72
|
action_type: e.action_type || 'tool_call',
|
|
@@ -185,6 +185,13 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
185
185
|
if (!RELEVANT.has(ev.type)) continue;
|
|
186
186
|
const type = ev.type;
|
|
187
187
|
const ts = ev.processed_at || ev.created_at || new Date().toISOString();
|
|
188
|
+
// v1.0.2 F-6a: capture Anthropic's own discriminators on EVERY event,
|
|
189
|
+
// not just thread_message_*. session_thread_id + agent_name are how
|
|
190
|
+
// the vendor itself tells parent activity from sub-agent activity.
|
|
191
|
+
// Preserved LOCALLY (NDJSON) only — never sent raw to Fortress.
|
|
192
|
+
const session_thread_id = ev.session_thread_id ?? null;
|
|
193
|
+
const agent_name = ev.agent_name ?? null;
|
|
194
|
+
const subAgentMeta = { session_thread_id, agent_name };
|
|
188
195
|
const tsMillis = tsMs(ev);
|
|
189
196
|
|
|
190
197
|
if (type === 'span.model_request_start') {
|
|
@@ -201,6 +208,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
201
208
|
const cw = u.cache_creation_input_tokens || 0;
|
|
202
209
|
yield {
|
|
203
210
|
...base,
|
|
211
|
+
...subAgentMeta,
|
|
204
212
|
id: ev.id,
|
|
205
213
|
action_type: 'llm_call',
|
|
206
214
|
tool_name: null,
|
|
@@ -220,6 +228,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
220
228
|
if (type === 'user.message') {
|
|
221
229
|
yield {
|
|
222
230
|
...base,
|
|
231
|
+
...subAgentMeta,
|
|
223
232
|
id: ev.id,
|
|
224
233
|
action_type: 'user_message',
|
|
225
234
|
tool_name: null,
|
|
@@ -234,6 +243,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
234
243
|
if (type === 'user.interrupt') {
|
|
235
244
|
yield {
|
|
236
245
|
...base,
|
|
246
|
+
...subAgentMeta,
|
|
237
247
|
id: ev.id,
|
|
238
248
|
action_type: 'user_interrupt',
|
|
239
249
|
tool_name: null,
|
|
@@ -249,6 +259,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
249
259
|
const denied = ev.result === 'deny';
|
|
250
260
|
yield {
|
|
251
261
|
...base,
|
|
262
|
+
...subAgentMeta,
|
|
252
263
|
id: ev.id,
|
|
253
264
|
action_type: 'tool_confirmation',
|
|
254
265
|
tool_name: null,
|
|
@@ -265,6 +276,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
265
276
|
if (type === 'user.custom_tool_result') {
|
|
266
277
|
yield {
|
|
267
278
|
...base,
|
|
279
|
+
...subAgentMeta,
|
|
268
280
|
id: ev.id,
|
|
269
281
|
action_type: 'custom_tool_result',
|
|
270
282
|
tool_name: null,
|
|
@@ -280,6 +292,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
280
292
|
if (type === 'agent.message') {
|
|
281
293
|
yield {
|
|
282
294
|
...base,
|
|
295
|
+
...subAgentMeta,
|
|
283
296
|
id: ev.id,
|
|
284
297
|
action_type: 'message',
|
|
285
298
|
tool_name: null,
|
|
@@ -294,6 +307,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
294
307
|
if (type === 'agent.thinking') {
|
|
295
308
|
yield {
|
|
296
309
|
...base,
|
|
310
|
+
...subAgentMeta,
|
|
297
311
|
id: ev.id,
|
|
298
312
|
action_type: 'thinking',
|
|
299
313
|
tool_name: null,
|
|
@@ -321,6 +335,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
321
335
|
const isError = ev.is_error === true;
|
|
322
336
|
yield {
|
|
323
337
|
...base,
|
|
338
|
+
...subAgentMeta,
|
|
324
339
|
id: ev.id,
|
|
325
340
|
action_type: start?.isMcp ? 'mcp_tool_use' : 'tool_use',
|
|
326
341
|
tool_name: start?.name || 'unknown',
|
|
@@ -337,6 +352,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
337
352
|
if (type === 'agent.custom_tool_use') {
|
|
338
353
|
yield {
|
|
339
354
|
...base,
|
|
355
|
+
...subAgentMeta,
|
|
340
356
|
id: ev.id,
|
|
341
357
|
action_type: 'custom_tool_use',
|
|
342
358
|
tool_name: ev.name || 'unknown',
|
|
@@ -351,6 +367,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
351
367
|
if (type === 'agent.thread_context_compacted') {
|
|
352
368
|
yield {
|
|
353
369
|
...base,
|
|
370
|
+
...subAgentMeta,
|
|
354
371
|
id: ev.id,
|
|
355
372
|
action_type: 'context_compacted',
|
|
356
373
|
tool_name: null,
|
|
@@ -370,6 +387,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
370
387
|
const direction = type.endsWith('_sent') ? 'sent' : 'received';
|
|
371
388
|
yield {
|
|
372
389
|
...base,
|
|
390
|
+
...subAgentMeta,
|
|
373
391
|
id: ev.id,
|
|
374
392
|
action_type: `thread_message_${direction}`,
|
|
375
393
|
tool_name: null,
|
|
@@ -391,6 +409,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
391
409
|
const { id: _id, type: _type, processed_at: _pa, created_at: _ca, ...changes } = ev;
|
|
392
410
|
yield {
|
|
393
411
|
...base,
|
|
412
|
+
...subAgentMeta,
|
|
394
413
|
id: ev.id,
|
|
395
414
|
action_type: 'config_change',
|
|
396
415
|
tool_name: null,
|
|
@@ -405,6 +424,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
405
424
|
if (type === 'session.thread_created') {
|
|
406
425
|
yield {
|
|
407
426
|
...base,
|
|
427
|
+
...subAgentMeta,
|
|
408
428
|
id: ev.id,
|
|
409
429
|
action_type: 'thread_created',
|
|
410
430
|
tool_name: null,
|
|
@@ -422,6 +442,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
422
442
|
if (type === 'session.error') {
|
|
423
443
|
yield {
|
|
424
444
|
...base,
|
|
445
|
+
...subAgentMeta,
|
|
425
446
|
id: ev.id,
|
|
426
447
|
action_type: 'session_error',
|
|
427
448
|
tool_name: null,
|
|
@@ -443,6 +464,7 @@ export async function* fetchSessionEntries({ apiKey, agentId, sessionId, model }
|
|
|
443
464
|
const fatal = state === 'terminated';
|
|
444
465
|
yield {
|
|
445
466
|
...base,
|
|
467
|
+
...subAgentMeta,
|
|
446
468
|
id: ev.id,
|
|
447
469
|
action_type: 'state_transition',
|
|
448
470
|
tool_name: null,
|
package/src/sources/contract.js
CHANGED
|
@@ -127,6 +127,19 @@ export const PROVIDERS = Object.freeze({
|
|
|
127
127
|
// * SUB-AGENT FIELDS (PR-C — see WMAAction.parent_agent_id):
|
|
128
128
|
// * @property {string|null} parent_agent_id Null for root agents
|
|
129
129
|
// * @property {string|null} composition_pattern From COMPOSITION_PATTERNS
|
|
130
|
+
// *
|
|
131
|
+
// * MULTI-AGENT DISCRIMINATORS (v1.0.2 F-6a — preserved LOCALLY only,
|
|
132
|
+
// * never sent raw to Fortress; the SignalsAggregator derives the
|
|
133
|
+
// * aggregated session_ids list from them at finalize time):
|
|
134
|
+
// * @property {string|null} session_thread_id The thread the event happened in.
|
|
135
|
+
// * For frameworks where one session can
|
|
136
|
+
// * host multiple threads/sub-agents
|
|
137
|
+
// * (Anthropic Task tool, future similar
|
|
138
|
+
// * designs), this is how the vendor
|
|
139
|
+
// * itself discriminates "parent vs sub".
|
|
140
|
+
// * @property {string|null} agent_name The human-named emitter of this event
|
|
141
|
+
// * (the parent agent OR a sub-agent
|
|
142
|
+
// * running inside the parent's session).
|
|
130
143
|
// */
|
|
131
144
|
|
|
132
145
|
const REQUIRED_FIELDS = ['id', 'provider', 'agent_id', 'session_id', 'action_type', 'timestamp', 'status'];
|