watchmyagents 1.2.1 → 1.3.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/src/logger.js CHANGED
@@ -33,6 +33,10 @@ const EXPORT_FIELDS = [
33
33
  'cost_usd', 'model',
34
34
  'session_tokens', 'session_cost_usd',
35
35
  'status', 'error', 'sequence_number', 'session_id',
36
+ // v1.3.0 — team correlation across cooperating agents. Populated by
37
+ // adapters that observe handoffs (OpenAI Agents SDK, future Claude
38
+ // Code workflow runId). Null when unknown / no team scope.
39
+ 'team_id',
36
40
  ];
37
41
 
38
42
  export class Logger {
@@ -78,7 +82,15 @@ export class Logger {
78
82
  const path = this._pathForToday();
79
83
  const full = {
80
84
  id: e.id || randomUUID(),
81
- agent_id: this.agentId,
85
+ // v1.3.0 — prefer the event's agent_id when present so multi-agent
86
+ // adapters (OpenAI Agents SDK handoffs, future LangGraph subgraphs)
87
+ // can attribute each event to its actual emitter. Falls back to
88
+ // the Logger's constructor agentId for single-agent adapters
89
+ // (Anthropic Managed) where every event from this logger belongs
90
+ // to the same agent. Backwards compatible: existing Anthropic
91
+ // adapter doesn't populate e.agent_id on every yield, so the
92
+ // fallback path keeps its behavior intact.
93
+ agent_id: e.agent_id || this.agentId,
82
94
  // PR-C: sub-agent fields. Defaults are honest for solo / root agents.
83
95
  // An adapter that detects hierarchy (e.g. OpenAI Agents handoffs)
84
96
  // populates these on the event, and the Logger threads them through.
@@ -107,6 +119,10 @@ export class Logger {
107
119
  session_id: this.sessionId,
108
120
  session_tokens: e.session_tokens ?? null,
109
121
  session_cost_usd: e.session_cost_usd ?? null,
122
+ // v1.3.0 — team correlation. Adapters that observe multi-agent
123
+ // groupings (OpenAI Agents SDK handoffs, future Claude Code
124
+ // workflow runId) populate this. Null when unknown.
125
+ team_id: e.team_id ?? null,
110
126
  input: e.input ?? null,
111
127
  output: e.output ?? null,
112
128
  };
@@ -140,9 +140,15 @@ export const COMPOSITION_PATTERNS = Object.freeze({
140
140
  // name here as they land so consumers can build provider-specific UI.
141
141
  export const PROVIDERS = Object.freeze({
142
142
  ANTHROPIC_MANAGED: 'anthropic-managed',
143
+ // v1.3.0 (Phase 2.A) — OpenAI Agents SDK (TypeScript/JS).
144
+ // Customer-instrumented (not auto-discovery). Push-model via:
145
+ // - Tool Input Guardrails (Shield enforcement: allow/deny/interrupt)
146
+ // - RunHooks EventEmitter (Watch observability)
147
+ // See `./openai-agents-js.js` and `docs/adapters/openai-agents-js.md`.
148
+ OPENAI_AGENTS: 'openai-agents',
143
149
  // Coming next:
144
- // OPENAI_AGENTS: 'openai-agents',
145
150
  // AWS_BEDROCK_AGENTCORE: 'aws-bedrock-agentcore',
151
+ // CLAUDE_CODE: 'claude-code',
146
152
  // LANGGRAPH: 'langgraph',
147
153
  // CREWAI: 'crewai',
148
154
  });
@@ -190,6 +196,30 @@ export const PROVIDERS = Object.freeze({
190
196
  // * @property {string|null} agent_name The human-named emitter of this event
191
197
  // * (the parent agent OR a sub-agent
192
198
  // * running inside the parent's session).
199
+ // *
200
+ // * TEAM CORRELATION (v1.3.0 — Phase 2.A OpenAI Agents SDK + future
201
+ // * Claude Code dynamic workflows):
202
+ // * @property {string|null} team_id Stable identifier shared by every
203
+ // * event in a logical group of
204
+ // * cooperating agents. Sources:
205
+ // * (a) customer override via
206
+ // * WMA_TEAM_ID env var or
207
+ // * programmatic setter,
208
+ // * (b) auto-detected from the
209
+ // * OpenAI Agents SDK
210
+ // * `agent_handoff` event —
211
+ // * all agents in the handoff
212
+ // * chain share the same
213
+ // * team_id,
214
+ // * (c) future: Claude Code dynamic
215
+ // * workflow `runId`.
216
+ // * Drives the Legions UI "Team view"
217
+ // * drill-down. Null when unknown.
218
+ // *
219
+ // * RUNTIME-COMPUTED CONTEXT (v1.2.0 — see src/shield/context.js):
220
+ // * ctx attributes (hour_of_day_utc, recent_error_rate, etc.) are NOT stored
221
+ // * on WMAAction — they're computed at policy eval time and live in the
222
+ // * ContextTracker only.
193
223
  // */
194
224
 
195
225
  const REQUIRED_FIELDS = ['id', 'provider', 'agent_id', 'session_id', 'action_type', 'timestamp', 'status'];
@@ -0,0 +1,278 @@
1
+ // TypeScript types for the OpenAI Agents SDK adapter — v1.3.0.
2
+ //
3
+ // These types pin the PUBLIC surface customers consume. The runtime
4
+ // (openai-agents-js.js) is plain JS to preserve the zero-runtime-deps
5
+ // invariant; this .d.ts is consumed by tsc only and never executed.
6
+ //
7
+ // The guardrail return shape and the EventEmitter event signatures
8
+ // mirror @openai/agents v0.x — we don't import from it (also to avoid
9
+ // dragging it as a runtime dep here), we re-declare the minimal subset
10
+ // we need.
11
+
12
+ // ── @openai/agents shapes we reference (minimal re-declaration) ────────
13
+
14
+ /** Mirrors @openai/agents `ToolGuardrailBehavior`. */
15
+ export type WMAToolGuardrailBehavior =
16
+ | { type: 'allow' }
17
+ | { type: 'rejectContent'; message: string }
18
+ | { type: 'throwException' };
19
+
20
+ /** Mirrors @openai/agents `ToolGuardrailFunctionOutput`. */
21
+ export interface WMAToolGuardrailFunctionOutput {
22
+ behavior: WMAToolGuardrailBehavior;
23
+ outputInfo?: unknown;
24
+ }
25
+
26
+ /** Mirrors @openai/agents `ToolInputGuardrailData`. */
27
+ export interface WMAToolInputGuardrailData {
28
+ context?: unknown;
29
+ agent?: { name?: string; model?: string; instructions?: string };
30
+ toolCall?: {
31
+ id?: string;
32
+ callId?: string;
33
+ name?: string;
34
+ arguments?: string | object;
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Return value of {@link wmaToolInputGuardrail}. Shape-compatible with
40
+ * @openai/agents `ToolInputGuardrailDefinition`. Drop into an Agent's
41
+ * `toolInputGuardrails: [...]` array.
42
+ */
43
+ export interface WMAToolInputGuardrail {
44
+ readonly type: 'tool_input';
45
+ readonly name: string;
46
+ run: (data: WMAToolInputGuardrailData) => Promise<WMAToolGuardrailFunctionOutput>;
47
+ }
48
+
49
+ // ── Adapter-specific types ─────────────────────────────────────────────
50
+
51
+ /** Minimal Logger surface — for advanced injection in tests. */
52
+ export interface WMALogger {
53
+ write(event: unknown): Promise<unknown>;
54
+ }
55
+
56
+ /** Minimal DecisionLogger surface. */
57
+ export interface WMADecisionLogger {
58
+ record(args: {
59
+ sourceEvent: unknown;
60
+ decision: string;
61
+ ruleId: string | null;
62
+ ruleName: string | null;
63
+ message: string | null;
64
+ decidedInMs: number | null;
65
+ mode: 'enforce' | 'shadow';
66
+ }): Promise<unknown>;
67
+ }
68
+
69
+ /** Minimal ContextTracker surface. */
70
+ export interface WMAContextTracker {
71
+ compute(event: unknown, at?: number): {
72
+ hour_of_day_utc: number;
73
+ day_of_week_utc: number;
74
+ agent_age_minutes: number;
75
+ session_duration_ms: number;
76
+ recent_error_rate: number;
77
+ event_count_recent: number;
78
+ event_count_total: number;
79
+ };
80
+ record(event: unknown, opts?: { isError?: boolean }): void;
81
+ reset(): void;
82
+ }
83
+
84
+ /** Minimal team tracker for handoff propagation. */
85
+ export interface WMATeamTracker {
86
+ resolve(sessionId: string): string | null;
87
+ bootstrap(sessionId: string): string;
88
+ recordHandoff(fromAgent: unknown, toAgent: unknown, sessionId: string): string | null;
89
+ }
90
+
91
+ /** Options for {@link wmaToolInputGuardrail}. */
92
+ export interface WMAGuardrailOptions {
93
+ /** Path to a local JSON policy file. Loaded lazily on first invocation. */
94
+ policiesPath?: string;
95
+ /** In-memory ruleset (overrides policiesPath). Useful for tests. */
96
+ ruleset?: { policies: unknown[]; default?: { action: string } };
97
+ /** Directory for NDJSON logs. Defaults to env WMA_LOG_DIR or ./watchmyagents-logs. */
98
+ logDir?: string;
99
+ /** Override the session id. Defaults to an auto-minted UUID. */
100
+ sessionId?: string;
101
+ /** Trailing window for `ctx.recent_error_rate` etc. Default: 20. */
102
+ recentWindowSize?: number;
103
+ /** On Shield internal error, allow vs deny. Default: false (fail-CLOSED). */
104
+ failOpen?: boolean;
105
+ /** Custom team id resolver — overrides env + handoff propagation. */
106
+ getTeamId?: () => string | null;
107
+ /** Inject an existing Logger (testing). */
108
+ logger?: WMALogger;
109
+ /** Inject an existing DecisionLogger (testing). */
110
+ decisionLogger?: WMADecisionLogger;
111
+ /** Inject an existing ContextTracker (testing). */
112
+ tracker?: WMAContextTracker;
113
+ }
114
+
115
+ /**
116
+ * Build the WMA Shield as an @openai/agents Tool Input Guardrail.
117
+ *
118
+ * Usage:
119
+ * ```typescript
120
+ * import { Agent } from '@openai/agents';
121
+ * import { wmaToolInputGuardrail } from 'watchmyagents';
122
+ *
123
+ * const wmaShield = wmaToolInputGuardrail({
124
+ * policiesPath: './policies/mitre-starter.json',
125
+ * });
126
+ *
127
+ * const agent = new Agent({
128
+ * name: 'Support bot',
129
+ * instructions: '...',
130
+ * tools: [...],
131
+ * toolInputGuardrails: [wmaShield],
132
+ * });
133
+ * ```
134
+ */
135
+ export function wmaToolInputGuardrail(options?: WMAGuardrailOptions): WMAToolInputGuardrail;
136
+
137
+ // ── Watch ──────────────────────────────────────────────────────────────
138
+
139
+ /** Minimal Runner surface (the part of @openai/agents we touch). */
140
+ export interface WMARunnerLike {
141
+ on(event: string, listener: (...args: any[]) => void): unknown;
142
+ off?(event: string, listener: (...args: any[]) => void): unknown;
143
+ }
144
+
145
+ /** Minimal Agent surface — same shape as Runner from an EventEmitter
146
+ * perspective. The methods are identical; the difference is the listener
147
+ * signatures (see attachWmaWatchToAgent). */
148
+ export interface WMAAgentLike {
149
+ on(event: string, listener: (...args: any[]) => void): unknown;
150
+ off?(event: string, listener: (...args: any[]) => void): unknown;
151
+ }
152
+
153
+ /** Options for {@link attachWmaWatch}. */
154
+ export interface WMAWatchOptions {
155
+ logDir?: string;
156
+ sessionId?: string;
157
+ logger?: WMALogger;
158
+ teamTracker?: WMATeamTracker;
159
+ }
160
+
161
+ /**
162
+ * Attach WMA Watch listeners to an @openai/agents Runner. Returns a
163
+ * detach function the customer can call on shutdown.
164
+ *
165
+ * USE THIS when the customer creates a Runner explicitly:
166
+ * ```typescript
167
+ * import { Runner } from '@openai/agents';
168
+ * import { attachWmaWatch } from 'watchmyagents';
169
+ *
170
+ * const runner = new Runner();
171
+ * const detach = attachWmaWatch(runner);
172
+ *
173
+ * await runner.run(agent, input);
174
+ *
175
+ * detach(); // optional, on process shutdown
176
+ * ```
177
+ *
178
+ * If the customer uses the convenience `run(agent, ...)` function
179
+ * (no explicit Runner), use {@link attachWmaWatchToAgent} instead.
180
+ */
181
+ export function attachWmaWatch(
182
+ runner: WMARunnerLike,
183
+ options?: WMAWatchOptions,
184
+ ): () => void;
185
+
186
+ /**
187
+ * Attach WMA Watch listeners to an @openai/agents Agent (AgentHooks
188
+ * pattern). Use this when the customer uses the convenience
189
+ * `run(agent, ...)` function rather than creating an explicit Runner.
190
+ *
191
+ * The AgentHooks event signatures differ slightly from RunHooks:
192
+ * agent_end, agent_handoff, agent_tool_start, agent_tool_end do NOT
193
+ * pass the agent in their args — it's captured via closure here.
194
+ *
195
+ * Usage:
196
+ * ```typescript
197
+ * import { Agent, run } from '@openai/agents';
198
+ * import { attachWmaWatchToAgent } from 'watchmyagents';
199
+ *
200
+ * const myAgent = new Agent({ name: 'support_bot', tools: [...] });
201
+ * const detach = attachWmaWatchToAgent(myAgent);
202
+ *
203
+ * await run(myAgent, 'help me reset my password');
204
+ *
205
+ * detach(); // optional, on process shutdown
206
+ * ```
207
+ */
208
+ export function attachWmaWatchToAgent(
209
+ agent: WMAAgentLike,
210
+ options?: WMAWatchOptions,
211
+ ): () => void;
212
+
213
+ // ── Normalizers (exported for advanced users / tests) ──────────────────
214
+
215
+ /** Mirrors the WMA contract `WMAAction` shape. */
216
+ export interface WMAAction {
217
+ id: string;
218
+ provider: string;
219
+ agent_id: string;
220
+ agent_name: string | null;
221
+ session_id: string;
222
+ session_thread_id: string | null;
223
+ action_type: string;
224
+ timestamp: string;
225
+ status: 'ok' | 'error' | 'blocked';
226
+ tool_name: string | null;
227
+ model: string | null;
228
+ duration_ms: number | null;
229
+ parent_agent_id: string | null;
230
+ composition_pattern: 'solo' | 'hierarchy' | 'graph' | 'peer';
231
+ team_id: string | null;
232
+ input: object | null;
233
+ output: object | null;
234
+ }
235
+
236
+ export interface NormalizeArgs<T extends string> {
237
+ sessionId: string;
238
+ teamId: string | null;
239
+ agent?: { name?: string; model?: string };
240
+ }
241
+
242
+ export function normalizeAgentStart(args: NormalizeArgs<'agent_start'> & { turnInput?: unknown[] }): Readonly<WMAAction>;
243
+ export function normalizeAgentEnd(args: NormalizeArgs<'agent_end'> & { output?: string }): Readonly<WMAAction>;
244
+ export function normalizeAgentHandoff(args: NormalizeArgs<'agent_handoff'> & {
245
+ fromAgent?: { name?: string; model?: string };
246
+ toAgent?: { name?: string; model?: string };
247
+ }): Readonly<WMAAction>;
248
+ export function normalizeToolStart(args: NormalizeArgs<'tool_start'> & {
249
+ tool?: { name?: string };
250
+ toolCall?: { id?: string; callId?: string; name?: string; arguments?: string | object };
251
+ }): Readonly<WMAAction>;
252
+ export function normalizeToolEnd(args: NormalizeArgs<'tool_end'> & {
253
+ tool?: { name?: string };
254
+ toolCall?: { id?: string; callId?: string; name?: string; arguments?: string | object };
255
+ result?: unknown;
256
+ }): Readonly<WMAAction>;
257
+
258
+ // ── Adapter metadata ───────────────────────────────────────────────────
259
+
260
+ export interface WMAAdapterMeta {
261
+ readonly provider: 'openai-agents';
262
+ readonly displayName: string;
263
+ readonly enforcement: 'sync_confirm' | 'sync_interrupt' | 'detect_only';
264
+ readonly compositionDefault: 'solo' | 'hierarchy' | 'graph' | 'peer';
265
+ readonly customerInstrumented: true;
266
+ readonly peerPackage: '@openai/agents';
267
+ readonly minPeerVersion: string;
268
+ readonly capabilities: Readonly<{
269
+ watch: boolean;
270
+ shield: boolean;
271
+ preToolDeny: boolean;
272
+ postToolFilter: boolean;
273
+ teamIdAutoDetect: boolean;
274
+ streamingSupported: boolean | string;
275
+ }>;
276
+ }
277
+
278
+ export const adapterMeta: WMAAdapterMeta;