@kitlangton/motel 0.2.5 → 0.2.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.
- package/AGENTS.md +11 -8
- package/README.md +13 -2
- package/package.json +31 -19
- package/skills/motel-debug/SKILL.md +203 -0
- package/skills/motel-debug/references/effect.md +38 -0
- package/src/App.tsx +3 -5
- package/src/StartupGate.tsx +8 -10
- package/src/cli.ts +15 -16
- package/src/config.ts +7 -1
- package/src/daemon.test.ts +332 -51
- package/src/daemon.ts +103 -152
- package/src/httpApi.ts +1 -0
- package/src/httpListPolicy.test.ts +76 -0
- package/src/httpListPolicy.ts +129 -0
- package/src/localServer.ts +194 -323
- package/src/mcp.ts +2 -1
- package/src/opentui-jsx.d.ts +11 -0
- package/src/otlp.test.ts +65 -0
- package/src/otlp.ts +20 -0
- package/src/otlpProtobuf.ts +35 -0
- package/src/registry.ts +37 -11
- package/src/runtime.ts +2 -6
- package/src/services/AsyncIngest.ts +20 -8
- package/src/services/LogQueryService.ts +11 -25
- package/src/services/TelemetryQuery.ts +62 -0
- package/src/services/TelemetryStore.ts +433 -249
- package/src/services/TraceQueryService.ts +18 -52
- package/src/services/ingestRpc.ts +2 -4
- package/src/services/queryRpc.ts +15 -0
- package/src/services/telemetryQueryWorker.ts +32 -0
- package/src/services/telemetryWorker.ts +5 -8
- package/src/storybook/aiChatStory.tsx +1 -1
- package/src/telemetry.test.ts +307 -41
- package/src/ui/AiChatView.tsx +1 -1
- package/src/ui/AttrFilterModal.tsx +1 -1
- package/src/ui/ServiceLogs.tsx +10 -7
- package/src/ui/SpanContentView.tsx +24 -21
- package/src/ui/TraceDetailsPane.tsx +1 -1
- package/src/ui/TraceList.tsx +1 -1
- package/src/ui/aiState.ts +10 -22
- package/src/ui/app/TraceWorkspace.tsx +2 -1
- package/src/ui/app/useAppLayout.ts +1 -1
- package/src/ui/app/useTraceScreenData.ts +22 -18
- package/src/ui/cachedLoader.test.ts +23 -0
- package/src/ui/cachedLoader.ts +60 -0
- package/src/ui/loaders.ts +34 -53
- package/src/ui/primitives.tsx +1 -1
- package/src/ui/state.ts +2 -0
- package/src/ui/traceDetailsWidth.repro.test.ts +12 -1
- package/src/ui/traceSortNav.repro.seed.ts +1 -1
- package/src/ui/traceSortNav.repro.test.ts +12 -2
- package/src/ui/useAttrFilterPicker.ts +10 -8
- package/src/ui/useKeyboardNav.ts +3 -6
- package/src/ui/waterfallNav.repro.seed.ts +1 -1
- package/src/ui/waterfallNav.repro.test.ts +16 -8
- package/web/dist/assets/index-B01z9BaO.css +2 -0
- package/web/dist/assets/index-M86tcih5.js +22 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-DnyVo03x.js +0 -27
- package/web/dist/assets/index-DzuHNBGV.css +0 -2
|
@@ -2,6 +2,10 @@ import { Effect, Layer, Context } from "effect"
|
|
|
2
2
|
import type { AiCallDetail, SpanItem, TraceItem, TraceSummaryItem } from "../domain.js"
|
|
3
3
|
import { TelemetryStore } from "./TelemetryStore.js"
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Compatibility adapter for consumers importing the historical trace query module.
|
|
7
|
+
* New internal callers should use TelemetryStoreReadonly directly.
|
|
8
|
+
*/
|
|
5
9
|
export class TraceQueryService extends Context.Service<
|
|
6
10
|
TraceQueryService,
|
|
7
11
|
{
|
|
@@ -22,56 +26,18 @@ export class TraceQueryService extends Context.Service<
|
|
|
22
26
|
|
|
23
27
|
export const TraceQueryServiceLive = Layer.effect(
|
|
24
28
|
TraceQueryService,
|
|
25
|
-
Effect.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
yield* Effect.annotateCurrentSpan("trace.result_count", traces.length)
|
|
40
|
-
return traces
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
const listTraceSummaries = Effect.fn("motel/TraceQueryService.listTraceSummaries")(function* (serviceName: string | null, options?: { readonly lookbackMinutes?: number; readonly limit?: number; readonly cursorStartedAtMs?: number; readonly cursorTraceId?: string }) {
|
|
44
|
-
yield* Effect.annotateCurrentSpan({
|
|
45
|
-
"trace.service_name": serviceName,
|
|
46
|
-
})
|
|
47
|
-
const traces = yield* store.listTraceSummaries(serviceName, options)
|
|
48
|
-
yield* Effect.annotateCurrentSpan("trace.result_count", traces.length)
|
|
49
|
-
return traces
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
const getTrace = Effect.fn("motel/TraceQueryService.getTrace")(function* (traceId: string) {
|
|
53
|
-
yield* Effect.annotateCurrentSpan("trace.trace_id", traceId)
|
|
54
|
-
return yield* store.getTrace(traceId)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const getSpan = Effect.fn("motel/TraceQueryService.getSpan")(function* (spanId: string) {
|
|
58
|
-
yield* Effect.annotateCurrentSpan("trace.span_id", spanId)
|
|
59
|
-
return yield* store.getSpan(spanId)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
return TraceQueryService.of({
|
|
63
|
-
listServices,
|
|
64
|
-
listRecentTraces,
|
|
65
|
-
listTraceSummaries,
|
|
66
|
-
searchTraceSummaries: store.searchTraceSummaries,
|
|
67
|
-
listFacets: store.listFacets,
|
|
68
|
-
searchTraces: store.searchTraces,
|
|
69
|
-
traceStats: store.traceStats,
|
|
70
|
-
getTrace,
|
|
71
|
-
getSpan,
|
|
72
|
-
getAiCall: store.getAiCall,
|
|
73
|
-
listTraceSpans: store.listTraceSpans,
|
|
74
|
-
searchSpans: store.searchSpans,
|
|
75
|
-
})
|
|
76
|
-
}),
|
|
29
|
+
Effect.map(TelemetryStore, (store) => TraceQueryService.of({
|
|
30
|
+
listServices: store.listServices,
|
|
31
|
+
listRecentTraces: store.listRecentTraces,
|
|
32
|
+
listTraceSummaries: store.listTraceSummaries,
|
|
33
|
+
searchTraceSummaries: store.searchTraceSummaries,
|
|
34
|
+
listFacets: store.listFacets,
|
|
35
|
+
searchTraces: store.searchTraces,
|
|
36
|
+
traceStats: store.traceStats,
|
|
37
|
+
getTrace: store.getTrace,
|
|
38
|
+
getSpan: store.getSpan,
|
|
39
|
+
getAiCall: store.getAiCall,
|
|
40
|
+
listTraceSpans: store.listTraceSpans,
|
|
41
|
+
searchSpans: store.searchSpans,
|
|
42
|
+
})),
|
|
77
43
|
)
|
|
@@ -5,10 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Only ingestTraces and ingestLogs run through RPC — those are the
|
|
7
7
|
* methods whose SQLite writes used to block the main event loop for
|
|
8
|
-
* seconds at a time.
|
|
9
|
-
*
|
|
10
|
-
* lets the reader (main) and writer (worker) hold independent
|
|
11
|
-
* connections to the same file concurrently without contention.
|
|
8
|
+
* seconds at a time. Read-only TelemetryStore methods use the separate
|
|
9
|
+
* query RPC worker so no synchronous SQLite call runs on the HTTP thread.
|
|
12
10
|
*
|
|
13
11
|
* Payloads are typed as Schema.Unknown because OTLP's protobuf-JSON
|
|
14
12
|
* shape is enormous and nested — the store validates structurally
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Schema } from "effect"
|
|
2
|
+
import * as Rpc from "effect/unstable/rpc/Rpc"
|
|
3
|
+
import * as RpcGroup from "effect/unstable/rpc/RpcGroup"
|
|
4
|
+
|
|
5
|
+
export class QueryError extends Schema.TaggedErrorClass<QueryError>()("QueryError", {
|
|
6
|
+
message: Schema.String,
|
|
7
|
+
}) {}
|
|
8
|
+
|
|
9
|
+
export const QueryRpcs = RpcGroup.make(
|
|
10
|
+
Rpc.make("query", {
|
|
11
|
+
payload: { method: Schema.String, args: Schema.Array(Schema.Unknown) },
|
|
12
|
+
success: Schema.Unknown,
|
|
13
|
+
error: QueryError,
|
|
14
|
+
}),
|
|
15
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BunRuntime } from "@effect/platform-bun"
|
|
2
|
+
import * as BunWorkerRunner from "@effect/platform-bun/BunWorkerRunner"
|
|
3
|
+
import { Effect, Layer } from "effect"
|
|
4
|
+
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization"
|
|
5
|
+
import * as RpcServer from "effect/unstable/rpc/RpcServer"
|
|
6
|
+
import { TelemetryStoreQueryWorkerLive, TelemetryStoreReadonly, type TelemetryStoreReader } from "./TelemetryStore.js"
|
|
7
|
+
import { QueryError, QueryRpcs } from "./queryRpc.js"
|
|
8
|
+
|
|
9
|
+
type QueryMethod = keyof TelemetryStoreReader
|
|
10
|
+
|
|
11
|
+
const QueryHandlers = QueryRpcs.toLayer(Effect.gen(function*() {
|
|
12
|
+
const store = yield* TelemetryStoreReadonly
|
|
13
|
+
return {
|
|
14
|
+
query: ({ method, args }) => {
|
|
15
|
+
const member = Reflect.get(store, method as QueryMethod) as unknown
|
|
16
|
+
const result = typeof member === "function" ? Reflect.apply(member, store, args) : member
|
|
17
|
+
return (result as Effect.Effect<unknown, Error>).pipe(
|
|
18
|
+
Effect.mapError((error) => new QueryError({ message: String(error) })),
|
|
19
|
+
)
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
const WorkerLive = RpcServer.layer(QueryRpcs).pipe(
|
|
25
|
+
Layer.provide(QueryHandlers),
|
|
26
|
+
Layer.provide(TelemetryStoreQueryWorkerLive),
|
|
27
|
+
Layer.provide(RpcServer.layerProtocolWorkerRunner),
|
|
28
|
+
Layer.provide(RpcSerialization.layerMsgPack),
|
|
29
|
+
Layer.provide(BunWorkerRunner.layer),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
Layer.launch(WorkerLive).pipe(BunRuntime.runMain)
|
|
@@ -6,15 +6,12 @@
|
|
|
6
6
|
* evaluated in a FRESH module graph on the worker side. In particular
|
|
7
7
|
* `TelemetryStoreWorkerLive` opens its own SQLite connection here — the main
|
|
8
8
|
* thread's store connection is unrelated. SQLite's WAL journal mode
|
|
9
|
-
* lets
|
|
10
|
-
*
|
|
9
|
+
* lets writer and query-worker connections coexist against the same
|
|
10
|
+
* `.sqlite` file without blocking the HTTP event loop.
|
|
11
11
|
*
|
|
12
|
-
* The worker only
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* clone would add more overhead than it saves. This is a deliberately
|
|
16
|
-
* narrow interface — the payoff is that main-thread HTTP queries
|
|
17
|
-
* never queue behind a heavy OTLP batch again.
|
|
12
|
+
* The worker exposes only `ingestTraces` / `ingestLogs` (see ingestRpc.ts)
|
|
13
|
+
* and owns writer maintenance. Read queries run in telemetryQueryWorker.ts;
|
|
14
|
+
* neither path can block the HTTP event loop.
|
|
18
15
|
*/
|
|
19
16
|
|
|
20
17
|
import { BunRuntime } from "@effect/platform-bun"
|
|
@@ -18,7 +18,7 @@ import { useEffect, useMemo, useRef, useState } from "react"
|
|
|
18
18
|
import { buildChunks, type Chunk } from "../ui/aiChatModel.ts"
|
|
19
19
|
import { AiChatView } from "../ui/AiChatView.tsx"
|
|
20
20
|
import { Divider, TextLine } from "../ui/primitives.tsx"
|
|
21
|
-
import type { AiCallDetailState } from "../ui/
|
|
21
|
+
import type { AiCallDetailState } from "../ui/aiState.ts"
|
|
22
22
|
import { applyTheme, colors, SEPARATOR } from "../ui/theme.ts"
|
|
23
23
|
import type { ChatFixture } from "./fixtures/index.ts"
|
|
24
24
|
import { errorFixture } from "./fixtures/errorState.ts"
|