@bradygaster/squad-sdk 0.8.0 → 0.8.3
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/dist/adapter/client.d.ts +24 -4
- package/dist/adapter/client.d.ts.map +1 -1
- package/dist/adapter/client.js +368 -78
- package/dist/adapter/client.js.map +1 -1
- package/dist/adapter/types.d.ts +38 -0
- package/dist/adapter/types.d.ts.map +1 -1
- package/dist/agents/index.d.ts +6 -3
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +132 -23
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/lifecycle.d.ts.map +1 -1
- package/dist/agents/lifecycle.js +28 -2
- package/dist/agents/lifecycle.js.map +1 -1
- package/dist/agents/model-selector.d.ts.map +1 -1
- package/dist/agents/model-selector.js +10 -37
- package/dist/agents/model-selector.js.map +1 -1
- package/dist/casting/index.d.ts.map +1 -1
- package/dist/casting/index.js +10 -2
- package/dist/casting/index.js.map +1 -1
- package/dist/client/event-bus.d.ts.map +1 -1
- package/dist/client/event-bus.js +0 -1
- package/dist/client/event-bus.js.map +1 -1
- package/dist/client/index.d.ts +7 -4
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +22 -10
- package/dist/client/index.js.map +1 -1
- package/dist/config/init.d.ts.map +1 -1
- package/dist/config/init.js +19 -12
- package/dist/config/init.js.map +1 -1
- package/dist/coordinator/coordinator.d.ts.map +1 -1
- package/dist/coordinator/coordinator.js +73 -55
- package/dist/coordinator/coordinator.js.map +1 -1
- package/dist/coordinator/index.d.ts +19 -4
- package/dist/coordinator/index.d.ts.map +1 -1
- package/dist/coordinator/index.js +138 -21
- package/dist/coordinator/index.js.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/ralph/index.d.ts +3 -1
- package/dist/ralph/index.d.ts.map +1 -1
- package/dist/ralph/index.js +83 -16
- package/dist/ralph/index.js.map +1 -1
- package/dist/resolution.d.ts +59 -0
- package/dist/resolution.d.ts.map +1 -1
- package/dist/resolution.js +145 -0
- package/dist/resolution.js.map +1 -1
- package/dist/runtime/config.d.ts +3 -2
- package/dist/runtime/config.d.ts.map +1 -1
- package/dist/runtime/config.js +7 -6
- package/dist/runtime/config.js.map +1 -1
- package/dist/runtime/constants.d.ts +35 -0
- package/dist/runtime/constants.d.ts.map +1 -0
- package/dist/runtime/constants.js +58 -0
- package/dist/runtime/constants.js.map +1 -0
- package/dist/runtime/event-bus-otel-bridge.d.ts +19 -0
- package/dist/runtime/event-bus-otel-bridge.d.ts.map +1 -0
- package/dist/runtime/event-bus-otel-bridge.js +61 -0
- package/dist/runtime/event-bus-otel-bridge.js.map +1 -0
- package/dist/runtime/event-bus-ws-bridge.d.ts +35 -0
- package/dist/runtime/event-bus-ws-bridge.d.ts.map +1 -0
- package/dist/runtime/event-bus-ws-bridge.js +55 -0
- package/dist/runtime/event-bus-ws-bridge.js.map +1 -0
- package/dist/runtime/event-payloads.d.ts +108 -0
- package/dist/runtime/event-payloads.d.ts.map +1 -0
- package/dist/runtime/event-payloads.js +28 -0
- package/dist/runtime/event-payloads.js.map +1 -0
- package/dist/runtime/health.d.ts.map +1 -1
- package/dist/runtime/health.js +2 -1
- package/dist/runtime/health.js.map +1 -1
- package/dist/runtime/otel-bridge.d.ts +52 -0
- package/dist/runtime/otel-bridge.d.ts.map +1 -0
- package/dist/runtime/otel-bridge.js +132 -0
- package/dist/runtime/otel-bridge.js.map +1 -0
- package/dist/runtime/otel-init.d.ts +72 -0
- package/dist/runtime/otel-init.d.ts.map +1 -0
- package/dist/runtime/otel-init.js +68 -0
- package/dist/runtime/otel-init.js.map +1 -0
- package/dist/runtime/otel-metrics.d.ts +42 -0
- package/dist/runtime/otel-metrics.d.ts.map +1 -0
- package/dist/runtime/otel-metrics.js +196 -0
- package/dist/runtime/otel-metrics.js.map +1 -0
- package/dist/runtime/otel.d.ts +53 -0
- package/dist/runtime/otel.d.ts.map +1 -0
- package/dist/runtime/otel.js +127 -0
- package/dist/runtime/otel.js.map +1 -0
- package/dist/runtime/squad-observer.d.ts +75 -0
- package/dist/runtime/squad-observer.d.ts.map +1 -0
- package/dist/runtime/squad-observer.js +190 -0
- package/dist/runtime/squad-observer.js.map +1 -0
- package/dist/runtime/streaming.d.ts +9 -0
- package/dist/runtime/streaming.d.ts.map +1 -1
- package/dist/runtime/streaming.js +37 -1
- package/dist/runtime/streaming.js.map +1 -1
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +57 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/upstream/index.d.ts +8 -0
- package/dist/upstream/index.d.ts.map +1 -0
- package/dist/upstream/index.js +7 -0
- package/dist/upstream/index.js.map +1 -0
- package/dist/upstream/resolver.d.ts +37 -0
- package/dist/upstream/resolver.d.ts.map +1 -0
- package/dist/upstream/resolver.js +234 -0
- package/dist/upstream/resolver.js.map +1 -0
- package/dist/upstream/types.d.ts +55 -0
- package/dist/upstream/types.d.ts.map +1 -0
- package/dist/upstream/types.js +11 -0
- package/dist/upstream/types.js.map +1 -0
- package/package.json +84 -2
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTel Metrics (Issues #261, #262, #263, #264)
|
|
3
|
+
*
|
|
4
|
+
* Provides counters, histograms, and gauges for token usage,
|
|
5
|
+
* agent performance, session pool, and response latency.
|
|
6
|
+
* No-op when OTel is not configured (getMeter returns a no-op meter).
|
|
7
|
+
*
|
|
8
|
+
* @module runtime/otel-metrics
|
|
9
|
+
*/
|
|
10
|
+
import { getMeter } from './otel.js';
|
|
11
|
+
let _tokenMetrics;
|
|
12
|
+
function ensureTokenMetrics() {
|
|
13
|
+
if (!_tokenMetrics) {
|
|
14
|
+
const meter = getMeter('squad-sdk');
|
|
15
|
+
_tokenMetrics = {
|
|
16
|
+
inputCounter: meter.createCounter('squad.tokens.input', {
|
|
17
|
+
description: 'Total input tokens consumed',
|
|
18
|
+
unit: 'tokens',
|
|
19
|
+
}),
|
|
20
|
+
outputCounter: meter.createCounter('squad.tokens.output', {
|
|
21
|
+
description: 'Total output tokens produced',
|
|
22
|
+
unit: 'tokens',
|
|
23
|
+
}),
|
|
24
|
+
costCounter: meter.createCounter('squad.tokens.cost', {
|
|
25
|
+
description: 'Estimated cost in USD',
|
|
26
|
+
unit: 'USD',
|
|
27
|
+
}),
|
|
28
|
+
totalCounter: meter.createUpDownCounter('squad.tokens.total', {
|
|
29
|
+
description: 'Running total of all tokens',
|
|
30
|
+
unit: 'tokens',
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return _tokenMetrics;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Record token usage from a UsageEvent.
|
|
38
|
+
* Safe to call when OTel is not configured — metrics are no-ops.
|
|
39
|
+
*/
|
|
40
|
+
export function recordTokenUsage(event) {
|
|
41
|
+
const m = ensureTokenMetrics();
|
|
42
|
+
const attrs = {
|
|
43
|
+
'agent.name': event.agentName ?? 'unknown',
|
|
44
|
+
'model': event.model,
|
|
45
|
+
};
|
|
46
|
+
m.inputCounter.add(event.inputTokens, attrs);
|
|
47
|
+
m.outputCounter.add(event.outputTokens, attrs);
|
|
48
|
+
m.costCounter.add(event.estimatedCost, attrs);
|
|
49
|
+
m.totalCounter.add(event.inputTokens + event.outputTokens, attrs);
|
|
50
|
+
}
|
|
51
|
+
let _agentMetrics;
|
|
52
|
+
function ensureAgentMetrics() {
|
|
53
|
+
if (!_agentMetrics) {
|
|
54
|
+
const meter = getMeter('squad-sdk');
|
|
55
|
+
_agentMetrics = {
|
|
56
|
+
spawnsCounter: meter.createCounter('squad.agent.spawns', {
|
|
57
|
+
description: 'Total agent spawns',
|
|
58
|
+
}),
|
|
59
|
+
durationHistogram: meter.createHistogram('squad.agent.duration', {
|
|
60
|
+
description: 'Agent task duration in milliseconds',
|
|
61
|
+
unit: 'ms',
|
|
62
|
+
}),
|
|
63
|
+
errorsCounter: meter.createCounter('squad.agent.errors', {
|
|
64
|
+
description: 'Total agent errors',
|
|
65
|
+
}),
|
|
66
|
+
activeGauge: meter.createUpDownCounter('squad.agent.active', {
|
|
67
|
+
description: 'Currently active agent sessions',
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return _agentMetrics;
|
|
72
|
+
}
|
|
73
|
+
/** Record an agent spawn event. */
|
|
74
|
+
export function recordAgentSpawn(agentName, mode = 'sync') {
|
|
75
|
+
const m = ensureAgentMetrics();
|
|
76
|
+
m.spawnsCounter.add(1, { 'agent.name': agentName, mode });
|
|
77
|
+
m.activeGauge.add(1, { 'agent.name': agentName });
|
|
78
|
+
}
|
|
79
|
+
/** Record agent task completion with duration. */
|
|
80
|
+
export function recordAgentDuration(agentName, durationMs, status = 'success') {
|
|
81
|
+
const m = ensureAgentMetrics();
|
|
82
|
+
m.durationHistogram.record(durationMs, { 'agent.name': agentName, status });
|
|
83
|
+
if (status === 'error') {
|
|
84
|
+
m.errorsCounter.add(1, { 'agent.name': agentName, 'error.type': 'task_failure' });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Record an agent error. */
|
|
88
|
+
export function recordAgentError(agentName, errorType) {
|
|
89
|
+
const m = ensureAgentMetrics();
|
|
90
|
+
m.errorsCounter.add(1, { 'agent.name': agentName, 'error.type': errorType });
|
|
91
|
+
}
|
|
92
|
+
/** Record agent session destruction (decrements active count). */
|
|
93
|
+
export function recordAgentDestroy(agentName) {
|
|
94
|
+
const m = ensureAgentMetrics();
|
|
95
|
+
m.activeGauge.add(-1, { 'agent.name': agentName });
|
|
96
|
+
}
|
|
97
|
+
let _sessionPoolMetrics;
|
|
98
|
+
function ensureSessionPoolMetrics() {
|
|
99
|
+
if (!_sessionPoolMetrics) {
|
|
100
|
+
const meter = getMeter('squad-sdk');
|
|
101
|
+
_sessionPoolMetrics = {
|
|
102
|
+
activeCounter: meter.createUpDownCounter('squad.sessions.active', {
|
|
103
|
+
description: 'Currently active sessions',
|
|
104
|
+
}),
|
|
105
|
+
idleCounter: meter.createUpDownCounter('squad.sessions.idle', {
|
|
106
|
+
description: 'Currently idle pooled sessions',
|
|
107
|
+
}),
|
|
108
|
+
createdCounter: meter.createCounter('squad.sessions.created', {
|
|
109
|
+
description: 'Total sessions created',
|
|
110
|
+
}),
|
|
111
|
+
closedCounter: meter.createCounter('squad.sessions.closed', {
|
|
112
|
+
description: 'Total sessions closed',
|
|
113
|
+
}),
|
|
114
|
+
errorsCounter: meter.createCounter('squad.sessions.errors', {
|
|
115
|
+
description: 'Session creation/streaming failures',
|
|
116
|
+
}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return _sessionPoolMetrics;
|
|
120
|
+
}
|
|
121
|
+
/** Record a new session being created. */
|
|
122
|
+
export function recordSessionCreated() {
|
|
123
|
+
const m = ensureSessionPoolMetrics();
|
|
124
|
+
m.createdCounter.add(1);
|
|
125
|
+
m.activeCounter.add(1);
|
|
126
|
+
}
|
|
127
|
+
/** Record a session becoming idle. */
|
|
128
|
+
export function recordSessionIdle() {
|
|
129
|
+
const m = ensureSessionPoolMetrics();
|
|
130
|
+
m.activeCounter.add(-1);
|
|
131
|
+
m.idleCounter.add(1);
|
|
132
|
+
}
|
|
133
|
+
/** Record an idle session becoming active again. */
|
|
134
|
+
export function recordSessionReactivated() {
|
|
135
|
+
const m = ensureSessionPoolMetrics();
|
|
136
|
+
m.idleCounter.add(-1);
|
|
137
|
+
m.activeCounter.add(1);
|
|
138
|
+
}
|
|
139
|
+
/** Record a session being closed. */
|
|
140
|
+
export function recordSessionClosed() {
|
|
141
|
+
const m = ensureSessionPoolMetrics();
|
|
142
|
+
m.activeCounter.add(-1);
|
|
143
|
+
m.closedCounter.add(1);
|
|
144
|
+
}
|
|
145
|
+
/** Record a session error. */
|
|
146
|
+
export function recordSessionError() {
|
|
147
|
+
const m = ensureSessionPoolMetrics();
|
|
148
|
+
m.errorsCounter.add(1);
|
|
149
|
+
}
|
|
150
|
+
let _latencyMetrics;
|
|
151
|
+
function ensureLatencyMetrics() {
|
|
152
|
+
if (!_latencyMetrics) {
|
|
153
|
+
const meter = getMeter('squad-sdk');
|
|
154
|
+
_latencyMetrics = {
|
|
155
|
+
ttftHistogram: meter.createHistogram('squad.response.ttft', {
|
|
156
|
+
description: 'Time to first token in milliseconds',
|
|
157
|
+
unit: 'ms',
|
|
158
|
+
}),
|
|
159
|
+
durationHistogram: meter.createHistogram('squad.response.duration', {
|
|
160
|
+
description: 'Total response duration in milliseconds',
|
|
161
|
+
unit: 'ms',
|
|
162
|
+
}),
|
|
163
|
+
tokensPerSecGauge: meter.createGauge('squad.response.tokens_per_second', {
|
|
164
|
+
description: 'Streaming throughput (tokens/second)',
|
|
165
|
+
unit: 'tokens/s',
|
|
166
|
+
}),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return _latencyMetrics;
|
|
170
|
+
}
|
|
171
|
+
/** Record time to first token. */
|
|
172
|
+
export function recordTimeToFirstToken(ttftMs) {
|
|
173
|
+
const m = ensureLatencyMetrics();
|
|
174
|
+
m.ttftHistogram.record(ttftMs);
|
|
175
|
+
}
|
|
176
|
+
/** Record total response duration. */
|
|
177
|
+
export function recordResponseDuration(durationMs) {
|
|
178
|
+
const m = ensureLatencyMetrics();
|
|
179
|
+
m.durationHistogram.record(durationMs);
|
|
180
|
+
}
|
|
181
|
+
/** Record streaming throughput. */
|
|
182
|
+
export function recordTokensPerSecond(tokensPerSec) {
|
|
183
|
+
const m = ensureLatencyMetrics();
|
|
184
|
+
m.tokensPerSecGauge.record(tokensPerSec);
|
|
185
|
+
}
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// Reset (for testing)
|
|
188
|
+
// ============================================================================
|
|
189
|
+
/** Reset all cached metric instances. Used in tests only. */
|
|
190
|
+
export function _resetMetrics() {
|
|
191
|
+
_tokenMetrics = undefined;
|
|
192
|
+
_agentMetrics = undefined;
|
|
193
|
+
_sessionPoolMetrics = undefined;
|
|
194
|
+
_latencyMetrics = undefined;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=otel-metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel-metrics.js","sourceRoot":"","sources":["../../src/runtime/otel-metrics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry Provider Initialization (Issue #255)
|
|
3
|
+
*
|
|
4
|
+
* Configures TracerProvider and MeterProvider with OTLP HTTP exporters.
|
|
5
|
+
* Disabled by default — activates only when explicit config or
|
|
6
|
+
* OTEL_EXPORTER_OTLP_ENDPOINT env var is present.
|
|
7
|
+
*
|
|
8
|
+
* @module runtime/otel
|
|
9
|
+
*/
|
|
10
|
+
import { type Tracer, type Meter } from '@opentelemetry/api';
|
|
11
|
+
/** Configuration for OTel initialization. */
|
|
12
|
+
export interface OTelConfig {
|
|
13
|
+
/** OTLP endpoint URL (e.g. http://localhost:4318) */
|
|
14
|
+
endpoint?: string;
|
|
15
|
+
/** Service name override (default: 'squad-sdk') */
|
|
16
|
+
serviceName?: string;
|
|
17
|
+
/** Enable debug diagnostics */
|
|
18
|
+
debug?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Initialize the TracerProvider with an OTLP HTTP exporter.
|
|
22
|
+
* Returns `true` if a provider was registered, `false` if disabled.
|
|
23
|
+
*/
|
|
24
|
+
export declare function initializeTracing(config?: OTelConfig): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the MeterProvider with an OTLP HTTP exporter.
|
|
27
|
+
* Returns `true` if a provider was registered, `false` if disabled.
|
|
28
|
+
*/
|
|
29
|
+
export declare function initializeMetrics(config?: OTelConfig): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Convenience wrapper — initializes both tracing and metrics.
|
|
32
|
+
* Returns an object indicating which subsystems were activated.
|
|
33
|
+
*/
|
|
34
|
+
export declare function initializeOTel(config?: OTelConfig): {
|
|
35
|
+
tracing: boolean;
|
|
36
|
+
metrics: boolean;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Flush pending telemetry and shut down providers.
|
|
40
|
+
* Safe to call even if OTel was never initialized.
|
|
41
|
+
*/
|
|
42
|
+
export declare function shutdownOTel(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Return a Tracer instance. Falls back to the no-op tracer when
|
|
45
|
+
* tracing has not been initialized.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getTracer(name?: string): Tracer;
|
|
48
|
+
/**
|
|
49
|
+
* Return a Meter instance. Falls back to the no-op meter when
|
|
50
|
+
* metrics have not been initialized.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getMeter(name?: string): Meter;
|
|
53
|
+
//# sourceMappingURL=otel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel.d.ts","sourceRoot":"","sources":["../../src/runtime/otel.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAkB,KAAK,MAAM,EAAE,KAAK,KAAK,EAAyC,MAAM,oBAAoB,CAAC;AAapH,6CAA6C;AAC7C,MAAM,WAAW,UAAU;IACzB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA8DD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAO9D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAO9D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAK1F;AAED;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAOlD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,SAAc,GAAG,MAAM,CAEpD;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,SAAc,GAAG,KAAK,CAElD"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry Provider Initialization (Issue #255)
|
|
3
|
+
*
|
|
4
|
+
* Configures TracerProvider and MeterProvider with OTLP HTTP exporters.
|
|
5
|
+
* Disabled by default — activates only when explicit config or
|
|
6
|
+
* OTEL_EXPORTER_OTLP_ENDPOINT env var is present.
|
|
7
|
+
*
|
|
8
|
+
* @module runtime/otel
|
|
9
|
+
*/
|
|
10
|
+
import { trace, metrics, DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';
|
|
11
|
+
import { NodeSDK, resources, metrics as sdkMetrics } from '@opentelemetry/sdk-node';
|
|
12
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
13
|
+
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
|
|
14
|
+
import { createRequire } from 'module';
|
|
15
|
+
const { Resource } = resources;
|
|
16
|
+
const { PeriodicExportingMetricReader } = sdkMetrics;
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Internal state
|
|
19
|
+
// ============================================================================
|
|
20
|
+
let _sdk;
|
|
21
|
+
let _tracingActive = false;
|
|
22
|
+
let _metricsActive = false;
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Helpers
|
|
25
|
+
// ============================================================================
|
|
26
|
+
function resolveEndpoint(config) {
|
|
27
|
+
return config?.endpoint ?? process.env['OTEL_EXPORTER_OTLP_ENDPOINT'] ?? undefined;
|
|
28
|
+
}
|
|
29
|
+
function buildResource(config) {
|
|
30
|
+
const req = createRequire(import.meta.url);
|
|
31
|
+
let version = 'unknown';
|
|
32
|
+
try {
|
|
33
|
+
const pkg = req('../../package.json');
|
|
34
|
+
version = pkg.version;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// package.json not resolvable — use fallback
|
|
38
|
+
}
|
|
39
|
+
return new Resource({
|
|
40
|
+
'service.name': config?.serviceName ?? 'squad-sdk',
|
|
41
|
+
'squad.version': version,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function ensureSDK(config) {
|
|
45
|
+
if (_sdk)
|
|
46
|
+
return;
|
|
47
|
+
const endpoint = resolveEndpoint(config);
|
|
48
|
+
if (!endpoint)
|
|
49
|
+
return;
|
|
50
|
+
if (config?.debug) {
|
|
51
|
+
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
|
|
52
|
+
}
|
|
53
|
+
const resource = buildResource(config);
|
|
54
|
+
_sdk = new NodeSDK({
|
|
55
|
+
resource,
|
|
56
|
+
traceExporter: new OTLPTraceExporter({ url: `${endpoint}/v1/traces` }),
|
|
57
|
+
metricReader: new PeriodicExportingMetricReader({
|
|
58
|
+
exporter: new OTLPMetricExporter({ url: `${endpoint}/v1/metrics` }),
|
|
59
|
+
exportIntervalMillis: 30_000,
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
_sdk.start();
|
|
63
|
+
}
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Public API
|
|
66
|
+
// ============================================================================
|
|
67
|
+
/**
|
|
68
|
+
* Initialize the TracerProvider with an OTLP HTTP exporter.
|
|
69
|
+
* Returns `true` if a provider was registered, `false` if disabled.
|
|
70
|
+
*/
|
|
71
|
+
export function initializeTracing(config) {
|
|
72
|
+
const endpoint = resolveEndpoint(config);
|
|
73
|
+
if (!endpoint)
|
|
74
|
+
return false;
|
|
75
|
+
ensureSDK(config);
|
|
76
|
+
_tracingActive = true;
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Initialize the MeterProvider with an OTLP HTTP exporter.
|
|
81
|
+
* Returns `true` if a provider was registered, `false` if disabled.
|
|
82
|
+
*/
|
|
83
|
+
export function initializeMetrics(config) {
|
|
84
|
+
const endpoint = resolveEndpoint(config);
|
|
85
|
+
if (!endpoint)
|
|
86
|
+
return false;
|
|
87
|
+
ensureSDK(config);
|
|
88
|
+
_metricsActive = true;
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Convenience wrapper — initializes both tracing and metrics.
|
|
93
|
+
* Returns an object indicating which subsystems were activated.
|
|
94
|
+
*/
|
|
95
|
+
export function initializeOTel(config) {
|
|
96
|
+
return {
|
|
97
|
+
tracing: initializeTracing(config),
|
|
98
|
+
metrics: initializeMetrics(config),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Flush pending telemetry and shut down providers.
|
|
103
|
+
* Safe to call even if OTel was never initialized.
|
|
104
|
+
*/
|
|
105
|
+
export async function shutdownOTel() {
|
|
106
|
+
if (_sdk) {
|
|
107
|
+
await _sdk.shutdown();
|
|
108
|
+
_sdk = undefined;
|
|
109
|
+
}
|
|
110
|
+
_tracingActive = false;
|
|
111
|
+
_metricsActive = false;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Return a Tracer instance. Falls back to the no-op tracer when
|
|
115
|
+
* tracing has not been initialized.
|
|
116
|
+
*/
|
|
117
|
+
export function getTracer(name = 'squad-sdk') {
|
|
118
|
+
return trace.getTracer(name);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Return a Meter instance. Falls back to the no-op meter when
|
|
122
|
+
* metrics have not been initialized.
|
|
123
|
+
*/
|
|
124
|
+
export function getMeter(name = 'squad-sdk') {
|
|
125
|
+
return metrics.getMeter(name);
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=otel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel.js","sourceRoot":"","sources":["../../src/runtime/otel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Squad Observer — File watcher for .squad/ directory changes (Issue #268)
|
|
3
|
+
*
|
|
4
|
+
* Monitors the .squad/ directory for file changes and emits OTel spans
|
|
5
|
+
* and EventBus events for real-time observability of squad state changes.
|
|
6
|
+
* Ported from paulyuk/squad#1.
|
|
7
|
+
*
|
|
8
|
+
* @module runtime/squad-observer
|
|
9
|
+
*/
|
|
10
|
+
import { EventBus } from './event-bus.js';
|
|
11
|
+
/** Categories of .squad/ file changes. */
|
|
12
|
+
export type SquadFileCategory = 'agent' | 'casting' | 'config' | 'decision' | 'skill' | 'unknown';
|
|
13
|
+
/** A detected file change in the .squad/ directory. */
|
|
14
|
+
export interface SquadFileChange {
|
|
15
|
+
/** Relative path from .squad/ root */
|
|
16
|
+
relativePath: string;
|
|
17
|
+
/** Absolute path on disk */
|
|
18
|
+
absolutePath: string;
|
|
19
|
+
/** Type of change */
|
|
20
|
+
changeType: 'created' | 'modified' | 'deleted';
|
|
21
|
+
/** Category of the changed file */
|
|
22
|
+
category: SquadFileCategory;
|
|
23
|
+
/** Timestamp of detection */
|
|
24
|
+
timestamp: Date;
|
|
25
|
+
}
|
|
26
|
+
/** Configuration for the observer. */
|
|
27
|
+
export interface SquadObserverConfig {
|
|
28
|
+
/** Path to the .squad/ directory to watch */
|
|
29
|
+
squadDir: string;
|
|
30
|
+
/** Optional EventBus to emit events to */
|
|
31
|
+
eventBus?: EventBus;
|
|
32
|
+
/** Debounce interval in ms (default: 200) */
|
|
33
|
+
debounceMs?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Classify a file path within .squad/ into a category.
|
|
37
|
+
*/
|
|
38
|
+
export declare function classifyFile(relativePath: string): SquadFileCategory;
|
|
39
|
+
/**
|
|
40
|
+
* File watcher that monitors .squad/ directory and emits OTel events.
|
|
41
|
+
*
|
|
42
|
+
* Usage:
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const observer = new SquadObserver({
|
|
45
|
+
* squadDir: '/path/to/.squad',
|
|
46
|
+
* eventBus: myEventBus,
|
|
47
|
+
* });
|
|
48
|
+
* observer.start();
|
|
49
|
+
* // ... later
|
|
50
|
+
* observer.stop();
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare class SquadObserver {
|
|
54
|
+
private config;
|
|
55
|
+
private watcher;
|
|
56
|
+
private debounceTimers;
|
|
57
|
+
private running;
|
|
58
|
+
constructor(config: SquadObserverConfig);
|
|
59
|
+
/**
|
|
60
|
+
* Start watching the .squad/ directory for changes.
|
|
61
|
+
* Emits OTel spans and EventBus events for each detected change.
|
|
62
|
+
*/
|
|
63
|
+
start(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Stop watching and clean up.
|
|
66
|
+
*/
|
|
67
|
+
stop(): void;
|
|
68
|
+
/** Whether the observer is currently running. */
|
|
69
|
+
get isRunning(): boolean;
|
|
70
|
+
private handleChange;
|
|
71
|
+
private processChange;
|
|
72
|
+
private emitSpan;
|
|
73
|
+
private emitEvent;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=squad-observer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"squad-observer.d.ts","sourceRoot":"","sources":["../../src/runtime/squad-observer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EAAE,QAAQ,EAAmB,MAAM,gBAAgB,CAAC;AAM3D,0CAA0C;AAC1C,MAAM,MAAM,iBAAiB,GACzB,OAAO,GACP,SAAS,GACT,QAAQ,GACR,UAAU,GACV,OAAO,GACP,SAAS,CAAC;AAEd,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,4BAA4B;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,qBAAqB;IACrB,UAAU,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;IAC/C,mCAAmC;IACnC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,6BAA6B;IAC7B,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,sCAAsC;AACtC,MAAM,WAAW,mBAAmB;IAClC,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAMD;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,iBAAiB,CAQpE;AAMD;;;;;;;;;;;;;GAaG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAyG;IACvH,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,cAAc,CAAyD;IAC/E,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,EAAE,mBAAmB;IAQvC;;;OAGG;IACH,KAAK,IAAI,IAAI;IAwCb;;OAEG;IACH,IAAI,IAAI,IAAI;IAkBZ,iDAAiD;IACjD,IAAI,SAAS,IAAI,OAAO,CAEvB;IAMD,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,SAAS;CAmBlB"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Squad Observer — File watcher for .squad/ directory changes (Issue #268)
|
|
3
|
+
*
|
|
4
|
+
* Monitors the .squad/ directory for file changes and emits OTel spans
|
|
5
|
+
* and EventBus events for real-time observability of squad state changes.
|
|
6
|
+
* Ported from paulyuk/squad#1.
|
|
7
|
+
*
|
|
8
|
+
* @module runtime/squad-observer
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
13
|
+
import { getTracer } from './otel.js';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Category classification
|
|
16
|
+
// ============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Classify a file path within .squad/ into a category.
|
|
19
|
+
*/
|
|
20
|
+
export function classifyFile(relativePath) {
|
|
21
|
+
const normalized = relativePath.replace(/\\/g, '/');
|
|
22
|
+
if (normalized.startsWith('agents/'))
|
|
23
|
+
return 'agent';
|
|
24
|
+
if (normalized.startsWith('casting/'))
|
|
25
|
+
return 'casting';
|
|
26
|
+
if (normalized.startsWith('skills/'))
|
|
27
|
+
return 'skill';
|
|
28
|
+
if (normalized.startsWith('decisions/') || normalized === 'decisions.md')
|
|
29
|
+
return 'decision';
|
|
30
|
+
if (normalized === 'config.json' || normalized === 'team.md' || normalized.startsWith('config/'))
|
|
31
|
+
return 'config';
|
|
32
|
+
return 'unknown';
|
|
33
|
+
}
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Observer implementation
|
|
36
|
+
// ============================================================================
|
|
37
|
+
/**
|
|
38
|
+
* File watcher that monitors .squad/ directory and emits OTel events.
|
|
39
|
+
*
|
|
40
|
+
* Usage:
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const observer = new SquadObserver({
|
|
43
|
+
* squadDir: '/path/to/.squad',
|
|
44
|
+
* eventBus: myEventBus,
|
|
45
|
+
* });
|
|
46
|
+
* observer.start();
|
|
47
|
+
* // ... later
|
|
48
|
+
* observer.stop();
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export class SquadObserver {
|
|
52
|
+
config;
|
|
53
|
+
watcher;
|
|
54
|
+
debounceTimers = new Map();
|
|
55
|
+
running = false;
|
|
56
|
+
constructor(config) {
|
|
57
|
+
this.config = {
|
|
58
|
+
squadDir: config.squadDir,
|
|
59
|
+
eventBus: config.eventBus,
|
|
60
|
+
debounceMs: config.debounceMs ?? 200,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Start watching the .squad/ directory for changes.
|
|
65
|
+
* Emits OTel spans and EventBus events for each detected change.
|
|
66
|
+
*/
|
|
67
|
+
start() {
|
|
68
|
+
if (this.running)
|
|
69
|
+
return;
|
|
70
|
+
if (!fs.existsSync(this.config.squadDir)) {
|
|
71
|
+
throw new Error(`Squad directory not found: ${this.config.squadDir}`);
|
|
72
|
+
}
|
|
73
|
+
const tracer = getTracer('squad-observer');
|
|
74
|
+
const span = tracer.startSpan('squad.observer.start', {
|
|
75
|
+
attributes: {
|
|
76
|
+
'squad.dir': this.config.squadDir,
|
|
77
|
+
'debounce_ms': this.config.debounceMs,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
try {
|
|
81
|
+
this.watcher = fs.watch(this.config.squadDir, { recursive: true }, (eventType, filename) => {
|
|
82
|
+
if (!filename)
|
|
83
|
+
return;
|
|
84
|
+
this.handleChange(eventType, filename);
|
|
85
|
+
});
|
|
86
|
+
this.watcher.on('error', (err) => {
|
|
87
|
+
const errSpan = tracer.startSpan('squad.observer.error', {
|
|
88
|
+
attributes: { 'error.message': err.message },
|
|
89
|
+
});
|
|
90
|
+
errSpan.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
|
|
91
|
+
errSpan.recordException(err);
|
|
92
|
+
errSpan.end();
|
|
93
|
+
});
|
|
94
|
+
this.running = true;
|
|
95
|
+
span.end();
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
99
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
100
|
+
span.recordException(error);
|
|
101
|
+
span.end();
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Stop watching and clean up.
|
|
107
|
+
*/
|
|
108
|
+
stop() {
|
|
109
|
+
if (!this.running)
|
|
110
|
+
return;
|
|
111
|
+
const tracer = getTracer('squad-observer');
|
|
112
|
+
const span = tracer.startSpan('squad.observer.stop');
|
|
113
|
+
// Clear all pending debounce timers
|
|
114
|
+
for (const timer of this.debounceTimers.values()) {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
}
|
|
117
|
+
this.debounceTimers.clear();
|
|
118
|
+
this.watcher?.close();
|
|
119
|
+
this.watcher = undefined;
|
|
120
|
+
this.running = false;
|
|
121
|
+
span.end();
|
|
122
|
+
}
|
|
123
|
+
/** Whether the observer is currently running. */
|
|
124
|
+
get isRunning() {
|
|
125
|
+
return this.running;
|
|
126
|
+
}
|
|
127
|
+
// --------------------------------------------------------------------------
|
|
128
|
+
// Internal
|
|
129
|
+
// --------------------------------------------------------------------------
|
|
130
|
+
handleChange(eventType, filename) {
|
|
131
|
+
// Debounce rapid changes to the same file
|
|
132
|
+
const existing = this.debounceTimers.get(filename);
|
|
133
|
+
if (existing)
|
|
134
|
+
clearTimeout(existing);
|
|
135
|
+
this.debounceTimers.set(filename, setTimeout(() => {
|
|
136
|
+
this.debounceTimers.delete(filename);
|
|
137
|
+
this.processChange(filename);
|
|
138
|
+
}, this.config.debounceMs));
|
|
139
|
+
}
|
|
140
|
+
processChange(filename) {
|
|
141
|
+
const absolutePath = path.join(this.config.squadDir, filename);
|
|
142
|
+
const category = classifyFile(filename);
|
|
143
|
+
const exists = fs.existsSync(absolutePath);
|
|
144
|
+
// Determine change type — basic heuristic since fs.watch doesn't tell us
|
|
145
|
+
const changeType = exists ? 'modified' : 'deleted';
|
|
146
|
+
const change = {
|
|
147
|
+
relativePath: filename,
|
|
148
|
+
absolutePath,
|
|
149
|
+
changeType,
|
|
150
|
+
category,
|
|
151
|
+
timestamp: new Date(),
|
|
152
|
+
};
|
|
153
|
+
// Emit OTel span
|
|
154
|
+
this.emitSpan(change);
|
|
155
|
+
// Emit EventBus event
|
|
156
|
+
if (this.config.eventBus) {
|
|
157
|
+
this.emitEvent(change);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
emitSpan(change) {
|
|
161
|
+
const tracer = getTracer('squad-observer');
|
|
162
|
+
const span = tracer.startSpan('squad.observer.file_change', {
|
|
163
|
+
attributes: {
|
|
164
|
+
'file.path': change.relativePath,
|
|
165
|
+
'file.category': change.category,
|
|
166
|
+
'change.type': change.changeType,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
span.end();
|
|
170
|
+
}
|
|
171
|
+
emitEvent(change) {
|
|
172
|
+
if (!this.config.eventBus)
|
|
173
|
+
return;
|
|
174
|
+
// Map file categories to appropriate event types
|
|
175
|
+
const event = {
|
|
176
|
+
type: 'agent:milestone',
|
|
177
|
+
agentName: change.category === 'agent' ? path.basename(path.dirname(change.relativePath)) : undefined,
|
|
178
|
+
payload: {
|
|
179
|
+
action: 'file_change',
|
|
180
|
+
file: change.relativePath,
|
|
181
|
+
category: change.category,
|
|
182
|
+
changeType: change.changeType,
|
|
183
|
+
},
|
|
184
|
+
timestamp: change.timestamp,
|
|
185
|
+
};
|
|
186
|
+
// Fire and forget — errors handled by EventBus
|
|
187
|
+
void this.config.eventBus.emit(event);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=squad-observer.js.map
|