kernl 0.12.3 → 0.12.6

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 (100) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/README.md +29 -0
  4. package/dist/agent.d.ts.map +1 -1
  5. package/dist/agent.js +2 -0
  6. package/dist/kernl/kernl.d.ts +6 -0
  7. package/dist/kernl/kernl.d.ts.map +1 -1
  8. package/dist/kernl/kernl.js +19 -0
  9. package/dist/kernl/types.d.ts +6 -0
  10. package/dist/kernl/types.d.ts.map +1 -1
  11. package/dist/lib/env.d.ts +2 -2
  12. package/dist/mcp/http.d.ts.map +1 -1
  13. package/dist/mcp/http.js +1 -5
  14. package/dist/mcp/sse.d.ts.map +1 -1
  15. package/dist/mcp/sse.js +1 -5
  16. package/dist/mcp/stdio.d.ts.map +1 -1
  17. package/dist/mcp/stdio.js +1 -5
  18. package/dist/task.d.ts.map +1 -1
  19. package/dist/task.js +0 -1
  20. package/dist/thread/__tests__/thread.test.js +241 -0
  21. package/dist/thread/thread.d.ts +5 -4
  22. package/dist/thread/thread.d.ts.map +1 -1
  23. package/dist/thread/thread.js +91 -22
  24. package/dist/thread/types.d.ts +5 -0
  25. package/dist/thread/types.d.ts.map +1 -1
  26. package/dist/tool/tool.d.ts +2 -2
  27. package/dist/tool/tool.d.ts.map +1 -1
  28. package/dist/tracing/__tests__/composite.test.d.ts +2 -0
  29. package/dist/tracing/__tests__/composite.test.d.ts.map +1 -0
  30. package/dist/tracing/__tests__/composite.test.js +146 -0
  31. package/dist/tracing/__tests__/dispatch.test.d.ts +2 -0
  32. package/dist/tracing/__tests__/dispatch.test.d.ts.map +1 -0
  33. package/dist/tracing/__tests__/dispatch.test.js +160 -0
  34. package/dist/tracing/__tests__/helpers.d.ts +69 -0
  35. package/dist/tracing/__tests__/helpers.d.ts.map +1 -0
  36. package/dist/tracing/__tests__/helpers.js +109 -0
  37. package/dist/tracing/__tests__/integration.test.d.ts +2 -0
  38. package/dist/tracing/__tests__/integration.test.d.ts.map +1 -0
  39. package/dist/tracing/__tests__/integration.test.js +675 -0
  40. package/dist/tracing/__tests__/span.test.d.ts +2 -0
  41. package/dist/tracing/__tests__/span.test.d.ts.map +1 -0
  42. package/dist/tracing/__tests__/span.test.js +188 -0
  43. package/dist/tracing/dispatch.d.ts +43 -0
  44. package/dist/tracing/dispatch.d.ts.map +1 -0
  45. package/dist/tracing/dispatch.js +70 -0
  46. package/dist/tracing/index.d.ts +8 -0
  47. package/dist/tracing/index.d.ts.map +1 -0
  48. package/dist/tracing/index.js +6 -0
  49. package/dist/tracing/span.d.ts +69 -0
  50. package/dist/tracing/span.d.ts.map +1 -0
  51. package/dist/tracing/span.js +64 -0
  52. package/dist/tracing/subscriber.d.ts +53 -0
  53. package/dist/tracing/subscriber.d.ts.map +1 -0
  54. package/dist/tracing/subscriber.js +1 -0
  55. package/dist/tracing/subscribers/composite.d.ts +26 -0
  56. package/dist/tracing/subscribers/composite.d.ts.map +1 -0
  57. package/dist/tracing/subscribers/composite.js +96 -0
  58. package/dist/tracing/subscribers/console.d.ts +22 -0
  59. package/dist/tracing/subscribers/console.d.ts.map +1 -0
  60. package/dist/tracing/subscribers/console.js +82 -0
  61. package/dist/tracing/types.d.ts +77 -0
  62. package/dist/tracing/types.d.ts.map +1 -0
  63. package/dist/tracing/types.js +1 -0
  64. package/package.json +6 -2
  65. package/src/agent.ts +2 -0
  66. package/src/index.ts +1 -0
  67. package/src/kernl/kernl.ts +21 -0
  68. package/src/kernl/types.ts +7 -0
  69. package/src/mcp/http.ts +1 -9
  70. package/src/mcp/sse.ts +1 -10
  71. package/src/mcp/stdio.ts +1 -10
  72. package/src/task.ts +0 -1
  73. package/src/thread/__tests__/thread.test.ts +280 -0
  74. package/src/thread/thread.ts +111 -24
  75. package/src/thread/types.ts +5 -0
  76. package/src/tool/tool.ts +1 -1
  77. package/src/tracing/__tests__/composite.test.ts +218 -0
  78. package/src/tracing/__tests__/dispatch.test.ts +222 -0
  79. package/src/tracing/__tests__/helpers.ts +138 -0
  80. package/src/tracing/__tests__/integration.test.ts +808 -0
  81. package/src/tracing/__tests__/span.test.ts +250 -0
  82. package/src/tracing/dispatch.ts +114 -0
  83. package/src/tracing/index.ts +39 -0
  84. package/src/tracing/span.ts +115 -0
  85. package/src/tracing/subscriber.ts +62 -0
  86. package/src/tracing/subscribers/composite.ts +102 -0
  87. package/src/tracing/subscribers/console.ts +101 -0
  88. package/src/tracing/types.ts +115 -0
  89. package/dist/trace/processor.d.ts +0 -1
  90. package/dist/trace/processor.d.ts.map +0 -1
  91. package/dist/trace/processor.js +0 -1
  92. package/dist/trace/traces.d.ts +0 -1
  93. package/dist/trace/traces.d.ts.map +0 -1
  94. package/dist/trace/traces.js +0 -73
  95. package/dist/trace/utils.d.ts +0 -22
  96. package/dist/trace/utils.d.ts.map +0 -1
  97. package/dist/trace/utils.js +0 -30
  98. package/src/trace/processor.ts +0 -0
  99. package/src/trace/traces.ts +0 -86
  100. package/src/trace/utils.ts +0 -38
@@ -0,0 +1,222 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+
3
+ import {
4
+ span,
5
+ event,
6
+ run,
7
+ current,
8
+ setSubscriber,
9
+ clearSubscriber,
10
+ getSubscriber,
11
+ } from "../dispatch";
12
+ import { NoopSpan, SpanImpl } from "../span";
13
+ import { TestSubscriber } from "./helpers";
14
+
15
+ describe("dispatch", () => {
16
+ let subscriber: TestSubscriber;
17
+
18
+ beforeEach(() => {
19
+ subscriber = new TestSubscriber();
20
+ });
21
+
22
+ afterEach(() => {
23
+ clearSubscriber();
24
+ });
25
+
26
+ describe("setSubscriber / clearSubscriber / getSubscriber", () => {
27
+ it("should set and get subscriber", () => {
28
+ expect(getSubscriber()).toBeNull();
29
+ setSubscriber(subscriber);
30
+ expect(getSubscriber()).toBe(subscriber);
31
+ });
32
+
33
+ it("should throw if subscriber already set", () => {
34
+ setSubscriber(subscriber);
35
+ expect(() => setSubscriber(new TestSubscriber())).toThrow(
36
+ "Global subscriber already set",
37
+ );
38
+ });
39
+
40
+ it("should allow re-setting after clear", () => {
41
+ setSubscriber(subscriber);
42
+ clearSubscriber();
43
+ expect(getSubscriber()).toBeNull();
44
+
45
+ const newSubscriber = new TestSubscriber();
46
+ setSubscriber(newSubscriber);
47
+ expect(getSubscriber()).toBe(newSubscriber);
48
+ });
49
+ });
50
+
51
+ describe("span", () => {
52
+ it("should return NoopSpan when no subscriber is set", () => {
53
+ const s = span({ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" });
54
+ expect(s).toBeInstanceOf(NoopSpan);
55
+ expect(s.noop()).toBe(true);
56
+ });
57
+
58
+ it("should return NoopSpan when subscriber.enabled returns false", () => {
59
+ subscriber.enabledKinds = new Set(["model.call"]); // only model.call enabled
60
+ setSubscriber(subscriber);
61
+
62
+ const s = span({ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" });
63
+ expect(s).toBeInstanceOf(NoopSpan);
64
+ expect(subscriber.spans.size).toBe(0);
65
+ });
66
+
67
+ it("should return SpanImpl when subscriber is set and enabled", () => {
68
+ setSubscriber(subscriber);
69
+
70
+ const s = span({ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" });
71
+ expect(s).toBeInstanceOf(SpanImpl);
72
+ expect(s.noop()).toBe(false);
73
+ expect(s.id).toBeDefined();
74
+ });
75
+
76
+ it("should pass span data to subscriber", () => {
77
+ setSubscriber(subscriber);
78
+
79
+ const data = { kind: "thread" as const, threadId: "t1", agentId: "a1", namespace: "ns" };
80
+ span(data);
81
+
82
+ expect(subscriber.spans.size).toBe(1);
83
+ const [, captured] = [...subscriber.spans.entries()][0];
84
+ expect(captured.data).toEqual(data);
85
+ });
86
+
87
+ it("should use null parent when parent=null", () => {
88
+ setSubscriber(subscriber);
89
+
90
+ span({ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" }, null);
91
+
92
+ const [, captured] = [...subscriber.spans.entries()][0];
93
+ expect(captured.parent).toBeNull();
94
+ });
95
+
96
+ it("should use explicit parent when provided", () => {
97
+ setSubscriber(subscriber);
98
+
99
+ span({ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" }, "parent_123");
100
+
101
+ const [, captured] = [...subscriber.spans.entries()][0];
102
+ expect(captured.parent).toBe("parent_123");
103
+ });
104
+
105
+ it("should resolve parent from context when parent='current' (default)", () => {
106
+ setSubscriber(subscriber);
107
+
108
+ // Create parent span
109
+ const parentSpan = span(
110
+ { kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" },
111
+ null,
112
+ );
113
+
114
+ // Create child span within run() context
115
+ run(parentSpan.id, () => {
116
+ span({ kind: "model.call", provider: "test", modelId: "m1" });
117
+ });
118
+
119
+ const modelSpans = subscriber.spansOfKind("model.call");
120
+ expect(modelSpans).toHaveLength(1);
121
+ expect(modelSpans[0].parent).toBe(parentSpan.id);
122
+ });
123
+
124
+ it("should use null parent when parent='current' but no context", () => {
125
+ setSubscriber(subscriber);
126
+
127
+ // No run() context
128
+ span({ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" });
129
+
130
+ const [, captured] = [...subscriber.spans.entries()][0];
131
+ expect(captured.parent).toBeNull();
132
+ });
133
+ });
134
+
135
+ describe("run / current", () => {
136
+ it("should return null when no context", () => {
137
+ expect(current()).toBeNull();
138
+ });
139
+
140
+ it("should return spanId within run context", () => {
141
+ const result = run("span_123", () => {
142
+ return current();
143
+ });
144
+ expect(result).toBe("span_123");
145
+ });
146
+
147
+ it("should return to null after run completes", () => {
148
+ run("span_123", () => {
149
+ expect(current()).toBe("span_123");
150
+ });
151
+ expect(current()).toBeNull();
152
+ });
153
+
154
+ it("should support nested run contexts", () => {
155
+ const results: (string | null)[] = [];
156
+
157
+ run("outer", () => {
158
+ results.push(current());
159
+ run("inner", () => {
160
+ results.push(current());
161
+ });
162
+ results.push(current());
163
+ });
164
+
165
+ expect(results).toEqual(["outer", "inner", "outer"]);
166
+ });
167
+
168
+ it("should handle null spanId in run", () => {
169
+ run("span_123", () => {
170
+ run(null, () => {
171
+ expect(current()).toBeNull();
172
+ });
173
+ });
174
+ });
175
+ });
176
+
177
+ describe("event", () => {
178
+ it("should do nothing when no subscriber is set", () => {
179
+ // Should not throw
180
+ event({ kind: "thread.error", message: "test" });
181
+ });
182
+
183
+ it("should emit event to subscriber", () => {
184
+ setSubscriber(subscriber);
185
+
186
+ event({ kind: "thread.error", message: "test error", stack: "stack" });
187
+
188
+ expect(subscriber.events).toHaveLength(1);
189
+ expect(subscriber.events[0].data).toEqual({
190
+ kind: "thread.error",
191
+ message: "test error",
192
+ stack: "stack",
193
+ });
194
+ });
195
+
196
+ it("should use null parent when parent=null", () => {
197
+ setSubscriber(subscriber);
198
+
199
+ event({ kind: "thread.error", message: "test" }, null);
200
+
201
+ expect(subscriber.events[0].parent).toBeNull();
202
+ });
203
+
204
+ it("should use explicit parent when provided", () => {
205
+ setSubscriber(subscriber);
206
+
207
+ event({ kind: "thread.error", message: "test" }, "parent_123");
208
+
209
+ expect(subscriber.events[0].parent).toBe("parent_123");
210
+ });
211
+
212
+ it("should resolve parent from context when parent='current' (default)", () => {
213
+ setSubscriber(subscriber);
214
+
215
+ run("span_123", () => {
216
+ event({ kind: "thread.error", message: "test" });
217
+ });
218
+
219
+ expect(subscriber.events[0].parent).toBe("span_123");
220
+ });
221
+ });
222
+ });
@@ -0,0 +1,138 @@
1
+ import type { SpanId, SpanData, EventData } from "../types";
2
+ import type { Subscriber } from "../subscriber";
3
+
4
+ /**
5
+ * A test subscriber that captures all tracing calls for assertions.
6
+ */
7
+ export class TestSubscriber implements Subscriber {
8
+ private nextId = 0;
9
+
10
+ // Captured data
11
+ spans = new Map<SpanId, { data: SpanData; parent: SpanId | null }>();
12
+ recorded = new Map<SpanId, Partial<SpanData>[]>();
13
+ errors = new Map<SpanId, Error[]>();
14
+ events: { data: EventData; parent: SpanId | null }[] = [];
15
+
16
+ // Call log for verifying order
17
+ calls: { method: string; spanId?: SpanId; args?: unknown }[] = [];
18
+
19
+ // State tracking
20
+ entered = new Set<SpanId>();
21
+ exited = new Set<SpanId>();
22
+ closed = new Set<SpanId>();
23
+
24
+ // Configuration
25
+ enabledKinds: Set<SpanData["kind"]> | null = null; // null = all enabled
26
+
27
+ enabled(data: SpanData): boolean {
28
+ if (this.enabledKinds === null) return true;
29
+ return this.enabledKinds.has(data.kind);
30
+ }
31
+
32
+ span(data: SpanData, parent: SpanId | null): SpanId {
33
+ const id = `test_span_${this.nextId++}`;
34
+ this.spans.set(id, { data, parent });
35
+ this.calls.push({ method: "span", spanId: id, args: { data, parent } });
36
+ return id;
37
+ }
38
+
39
+ enter(spanId: SpanId): void {
40
+ this.entered.add(spanId);
41
+ this.calls.push({ method: "enter", spanId });
42
+ }
43
+
44
+ exit(spanId: SpanId): void {
45
+ this.exited.add(spanId);
46
+ this.calls.push({ method: "exit", spanId });
47
+ }
48
+
49
+ record(spanId: SpanId, delta: Partial<SpanData>): void {
50
+ const existing = this.recorded.get(spanId) ?? [];
51
+ existing.push(delta);
52
+ this.recorded.set(spanId, existing);
53
+ this.calls.push({ method: "record", spanId, args: delta });
54
+ }
55
+
56
+ error(spanId: SpanId, err: Error): void {
57
+ const existing = this.errors.get(spanId) ?? [];
58
+ existing.push(err);
59
+ this.errors.set(spanId, existing);
60
+ this.calls.push({ method: "error", spanId, args: err });
61
+ }
62
+
63
+ close(spanId: SpanId): void {
64
+ this.closed.add(spanId);
65
+ this.calls.push({ method: "close", spanId });
66
+ }
67
+
68
+ event(data: EventData, parent: SpanId | null): void {
69
+ this.events.push({ data, parent });
70
+ this.calls.push({ method: "event", args: { data, parent } });
71
+ }
72
+
73
+ async flush(): Promise<void> {
74
+ this.calls.push({ method: "flush" });
75
+ }
76
+
77
+ async shutdown(_timeout?: number): Promise<void> {
78
+ this.calls.push({ method: "shutdown" });
79
+ }
80
+
81
+ // --- Test helpers ---
82
+
83
+ /**
84
+ * Get all spans of a specific kind.
85
+ */
86
+ spansOfKind<K extends SpanData["kind"]>(
87
+ kind: K,
88
+ ): Array<{ id: SpanId; data: Extract<SpanData, { kind: K }>; parent: SpanId | null }> {
89
+ const result: Array<{ id: SpanId; data: Extract<SpanData, { kind: K }>; parent: SpanId | null }> = [];
90
+ for (const [id, { data, parent }] of this.spans) {
91
+ if (data.kind === kind) {
92
+ result.push({ id, data: data as Extract<SpanData, { kind: K }>, parent });
93
+ }
94
+ }
95
+ return result;
96
+ }
97
+
98
+ /**
99
+ * Get events of a specific kind.
100
+ */
101
+ eventsOfKind<K extends EventData["kind"]>(
102
+ kind: K,
103
+ ): Array<{ data: Extract<EventData, { kind: K }>; parent: SpanId | null }> {
104
+ return this.events.filter((e) => e.data.kind === kind) as Array<{
105
+ data: Extract<EventData, { kind: K }>;
106
+ parent: SpanId | null;
107
+ }>;
108
+ }
109
+
110
+ /**
111
+ * Get recorded data for a span.
112
+ */
113
+ getRecorded(spanId: SpanId): Partial<SpanData>[] {
114
+ return this.recorded.get(spanId) ?? [];
115
+ }
116
+
117
+ /**
118
+ * Check if a span was fully completed (entered, exited, closed).
119
+ */
120
+ isComplete(spanId: SpanId): boolean {
121
+ return this.entered.has(spanId) && this.exited.has(spanId) && this.closed.has(spanId);
122
+ }
123
+
124
+ /**
125
+ * Reset all captured data.
126
+ */
127
+ reset(): void {
128
+ this.nextId = 0;
129
+ this.spans.clear();
130
+ this.recorded.clear();
131
+ this.errors.clear();
132
+ this.events = [];
133
+ this.calls = [];
134
+ this.entered.clear();
135
+ this.exited.clear();
136
+ this.closed.clear();
137
+ }
138
+ }