@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.
Files changed (112) hide show
  1. package/dist/adapter/client.d.ts +24 -4
  2. package/dist/adapter/client.d.ts.map +1 -1
  3. package/dist/adapter/client.js +368 -78
  4. package/dist/adapter/client.js.map +1 -1
  5. package/dist/adapter/types.d.ts +38 -0
  6. package/dist/adapter/types.d.ts.map +1 -1
  7. package/dist/agents/index.d.ts +6 -3
  8. package/dist/agents/index.d.ts.map +1 -1
  9. package/dist/agents/index.js +132 -23
  10. package/dist/agents/index.js.map +1 -1
  11. package/dist/agents/lifecycle.d.ts.map +1 -1
  12. package/dist/agents/lifecycle.js +28 -2
  13. package/dist/agents/lifecycle.js.map +1 -1
  14. package/dist/agents/model-selector.d.ts.map +1 -1
  15. package/dist/agents/model-selector.js +10 -37
  16. package/dist/agents/model-selector.js.map +1 -1
  17. package/dist/casting/index.d.ts.map +1 -1
  18. package/dist/casting/index.js +10 -2
  19. package/dist/casting/index.js.map +1 -1
  20. package/dist/client/event-bus.d.ts.map +1 -1
  21. package/dist/client/event-bus.js +0 -1
  22. package/dist/client/event-bus.js.map +1 -1
  23. package/dist/client/index.d.ts +7 -4
  24. package/dist/client/index.d.ts.map +1 -1
  25. package/dist/client/index.js +22 -10
  26. package/dist/client/index.js.map +1 -1
  27. package/dist/config/init.d.ts.map +1 -1
  28. package/dist/config/init.js +19 -12
  29. package/dist/config/init.js.map +1 -1
  30. package/dist/coordinator/coordinator.d.ts.map +1 -1
  31. package/dist/coordinator/coordinator.js +73 -55
  32. package/dist/coordinator/coordinator.js.map +1 -1
  33. package/dist/coordinator/index.d.ts +19 -4
  34. package/dist/coordinator/index.d.ts.map +1 -1
  35. package/dist/coordinator/index.js +138 -21
  36. package/dist/coordinator/index.js.map +1 -1
  37. package/dist/index.d.ts +12 -2
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +12 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/ralph/index.d.ts +3 -1
  42. package/dist/ralph/index.d.ts.map +1 -1
  43. package/dist/ralph/index.js +83 -16
  44. package/dist/ralph/index.js.map +1 -1
  45. package/dist/resolution.d.ts +59 -0
  46. package/dist/resolution.d.ts.map +1 -1
  47. package/dist/resolution.js +145 -0
  48. package/dist/resolution.js.map +1 -1
  49. package/dist/runtime/config.d.ts +3 -2
  50. package/dist/runtime/config.d.ts.map +1 -1
  51. package/dist/runtime/config.js +7 -6
  52. package/dist/runtime/config.js.map +1 -1
  53. package/dist/runtime/constants.d.ts +35 -0
  54. package/dist/runtime/constants.d.ts.map +1 -0
  55. package/dist/runtime/constants.js +58 -0
  56. package/dist/runtime/constants.js.map +1 -0
  57. package/dist/runtime/event-bus-otel-bridge.d.ts +19 -0
  58. package/dist/runtime/event-bus-otel-bridge.d.ts.map +1 -0
  59. package/dist/runtime/event-bus-otel-bridge.js +61 -0
  60. package/dist/runtime/event-bus-otel-bridge.js.map +1 -0
  61. package/dist/runtime/event-bus-ws-bridge.d.ts +35 -0
  62. package/dist/runtime/event-bus-ws-bridge.d.ts.map +1 -0
  63. package/dist/runtime/event-bus-ws-bridge.js +55 -0
  64. package/dist/runtime/event-bus-ws-bridge.js.map +1 -0
  65. package/dist/runtime/event-payloads.d.ts +108 -0
  66. package/dist/runtime/event-payloads.d.ts.map +1 -0
  67. package/dist/runtime/event-payloads.js +28 -0
  68. package/dist/runtime/event-payloads.js.map +1 -0
  69. package/dist/runtime/health.d.ts.map +1 -1
  70. package/dist/runtime/health.js +2 -1
  71. package/dist/runtime/health.js.map +1 -1
  72. package/dist/runtime/otel-bridge.d.ts +52 -0
  73. package/dist/runtime/otel-bridge.d.ts.map +1 -0
  74. package/dist/runtime/otel-bridge.js +132 -0
  75. package/dist/runtime/otel-bridge.js.map +1 -0
  76. package/dist/runtime/otel-init.d.ts +72 -0
  77. package/dist/runtime/otel-init.d.ts.map +1 -0
  78. package/dist/runtime/otel-init.js +68 -0
  79. package/dist/runtime/otel-init.js.map +1 -0
  80. package/dist/runtime/otel-metrics.d.ts +42 -0
  81. package/dist/runtime/otel-metrics.d.ts.map +1 -0
  82. package/dist/runtime/otel-metrics.js +196 -0
  83. package/dist/runtime/otel-metrics.js.map +1 -0
  84. package/dist/runtime/otel.d.ts +53 -0
  85. package/dist/runtime/otel.d.ts.map +1 -0
  86. package/dist/runtime/otel.js +127 -0
  87. package/dist/runtime/otel.js.map +1 -0
  88. package/dist/runtime/squad-observer.d.ts +75 -0
  89. package/dist/runtime/squad-observer.d.ts.map +1 -0
  90. package/dist/runtime/squad-observer.js +190 -0
  91. package/dist/runtime/squad-observer.js.map +1 -0
  92. package/dist/runtime/streaming.d.ts +9 -0
  93. package/dist/runtime/streaming.d.ts.map +1 -1
  94. package/dist/runtime/streaming.js +37 -1
  95. package/dist/runtime/streaming.js.map +1 -1
  96. package/dist/tools/index.d.ts +8 -0
  97. package/dist/tools/index.d.ts.map +1 -1
  98. package/dist/tools/index.js +57 -1
  99. package/dist/tools/index.js.map +1 -1
  100. package/dist/upstream/index.d.ts +8 -0
  101. package/dist/upstream/index.d.ts.map +1 -0
  102. package/dist/upstream/index.js +7 -0
  103. package/dist/upstream/index.js.map +1 -0
  104. package/dist/upstream/resolver.d.ts +37 -0
  105. package/dist/upstream/resolver.d.ts.map +1 -0
  106. package/dist/upstream/resolver.js +234 -0
  107. package/dist/upstream/resolver.js.map +1 -0
  108. package/dist/upstream/types.d.ts +55 -0
  109. package/dist/upstream/types.d.ts.map +1 -0
  110. package/dist/upstream/types.js +11 -0
  111. package/dist/upstream/types.js.map +1 -0
  112. 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