@warlock.js/devtools 5.31.0 → 5.33.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/cjs/ai-source-lia4YS30.cjs +212 -0
  3. package/cjs/ai-source-lia4YS30.cjs.map +1 -0
  4. package/cjs/index.cjs +2 -727
  5. package/cjs/{server-CQ0qhO8c.cjs → server-BNlyhwiR.cjs} +62 -3
  6. package/cjs/server-BNlyhwiR.cjs.map +1 -0
  7. package/cjs/src-CiNStw0i.cjs +699 -0
  8. package/cjs/src-CiNStw0i.cjs.map +1 -0
  9. package/esm/caller-location.mjs.map +1 -1
  10. package/esm/devtools-connector.d.mts +2 -4
  11. package/esm/devtools-connector.d.mts.map +1 -1
  12. package/esm/devtools-connector.mjs +6 -0
  13. package/esm/devtools-connector.mjs.map +1 -1
  14. package/esm/index.d.mts +2 -1
  15. package/esm/process-panel.mjs +16 -0
  16. package/esm/process-panel.mjs.map +1 -0
  17. package/esm/server/ai-routes.mjs +32 -0
  18. package/esm/server/ai-routes.mjs.map +1 -0
  19. package/esm/server/guard.mjs +1 -1
  20. package/esm/server/guard.mjs.map +1 -1
  21. package/esm/server/index.mjs +4 -0
  22. package/esm/server/index.mjs.map +1 -1
  23. package/esm/server/process-routes.mjs +11 -0
  24. package/esm/server/process-routes.mjs.map +1 -0
  25. package/esm/server/requests-routes.mjs +1 -0
  26. package/esm/server/requests-routes.mjs.map +1 -1
  27. package/esm/server/stream-route.mjs +4 -0
  28. package/esm/server/stream-route.mjs.map +1 -1
  29. package/esm/sources/ai-source.d.mts +29 -0
  30. package/esm/sources/ai-source.d.mts.map +1 -0
  31. package/esm/sources/ai-source.mjs +136 -0
  32. package/esm/sources/ai-source.mjs.map +1 -0
  33. package/esm/sources/log-source.mjs +1 -1
  34. package/esm/sources/query-source.mjs +1 -1
  35. package/esm/types.d.mts +45 -30
  36. package/esm/types.d.mts.map +1 -1
  37. package/esm/ui-root.mjs.map +1 -1
  38. package/llms-full.txt +85 -1
  39. package/llms.txt +1 -1
  40. package/package.json +9 -5
  41. package/skills/devtools-overview/SKILL.md +85 -1
  42. package/ui/app.css +13 -0
  43. package/ui/app.js +213 -16
  44. package/ui/index.html +2 -0
  45. package/cjs/index.cjs.map +0 -1
  46. package/cjs/server-CQ0qhO8c.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-source.mjs","names":[],"sources":["../../../../../../../../packages/devtools/src/sources/ai-source.ts"],"sourcesContent":["import type { Trace, TraceSpan, TraceStoreContract } from \"@warlock.js/ai-panoptic\";\nimport { toSafeValue } from \"../safe-value\";\nimport type { DevtoolsDisposer } from \"../types\";\n\nexport const AI_INSTALL_GUIDANCE =\n \"Install @warlock.js/ai and @warlock.js/ai-panoptic, then restart warlock dev. Import @warlock.js/ai-panoptic and enable ai.config({ panoptic: { observeAll: true } }). If already installed, check that both packages are compatible and can be loaded.\";\n\nexport type DevtoolsAiEvent = { traceId: string; requestId?: string };\n\n/** Only the connector's development branch constructs this source. */\nexport type DevtoolsAiSource =\n | { available: false; message: string; dispose: DevtoolsDisposer }\n | {\n available: true;\n store: TraceStoreContract;\n summarize: (trace: Trace) => DevtoolsAiRun;\n subscribe: (listener: (event: DevtoolsAiEvent) => void) => DevtoolsDisposer;\n dispose: DevtoolsDisposer;\n };\n\nexport type DevtoolsAiRun = {\n traceId: string;\n requestId?: string;\n sessionId?: string;\n name: string;\n status: TraceSpan[\"status\"];\n startedAt: string;\n duration: number;\n usage: Trace[\"usage\"];\n cost?: number;\n models: string[];\n toolCalls: number;\n retries: number;\n cacheHits?: number;\n incomplete: boolean;\n truncatedTrips: number;\n compactions: number;\n skippedCompactions: number;\n providerCompactions: number;\n};\n\n/** Summaries use root usage once: nested spans already roll up into it. */\nexport function summarizeAiTrace(trace: Trace, cost?: number): DevtoolsAiRun {\n const models = new Set<string>();\n let toolCalls = 0;\n let retries = 0;\n let cacheHits: number | undefined;\n let incomplete = false;\n let truncatedTrips = 0;\n let compactions = 0;\n let skippedCompactions = 0;\n let providerCompactions = 0;\n const visit = (span: TraceSpan): void => {\n const attributes = span.attributes ?? {};\n const model = attributes[\"agent.model.name\"];\n const provider = attributes[\"agent.model.provider\"];\n if (typeof model === \"string\")\n models.add(typeof provider === \"string\" ? `${provider}/${model}` : model);\n if (span.type === \"tool\") toolCalls += 1;\n if (typeof attributes.retries === \"number\") retries += attributes.retries;\n if (typeof attributes[\"agent.cacheHits\"] === \"number\")\n cacheHits = (cacheHits ?? 0) + attributes[\"agent.cacheHits\"];\n if (typeof attributes[\"agent.truncatedTrips\"] === \"number\")\n truncatedTrips += attributes[\"agent.truncatedTrips\"];\n if (typeof attributes[\"agent.providerCompactions\"] === \"number\")\n providerCompactions += attributes[\"agent.providerCompactions\"];\n if (Array.isArray(attributes[\"agent.compactions\"])) {\n const records: unknown[] = attributes[\"agent.compactions\"];\n for (const record of records) {\n if (typeof record !== \"object\" || record === null || !(\"type\" in record)) continue;\n if (record.type === \"applied\") compactions += 1;\n if (record.type === \"skipped\") skippedCompactions += 1;\n }\n }\n incomplete ||=\n span.status === \"incomplete\" ||\n span.error?.type === \"AgentIncompleteError\" ||\n span.error?.type === \"AGENT_INCOMPLETE\";\n span.children.forEach(visit);\n };\n visit(trace.root);\n return {\n traceId: trace.traceId,\n requestId: trace.requestId,\n sessionId: trace.sessionId,\n name: trace.root.name,\n status: trace.root.status,\n startedAt: trace.startedAt,\n duration: trace.duration,\n usage: trace.usage,\n cost,\n models: [...models],\n toolCalls,\n retries,\n cacheHits,\n incomplete,\n truncatedTrips,\n compactions,\n skippedCompactions,\n providerCompactions,\n };\n}\n\n/** Content-free snapshot; never retain prompts, completions or tool arguments/results. */\nfunction snapshotSpan(span: TraceSpan): TraceSpan {\n return {\n spanId: span.spanId,\n parentSpanId: span.parentSpanId,\n traceId: span.traceId,\n sessionId: span.sessionId,\n name: span.name,\n version: span.version,\n type: span.type,\n status: span.status,\n startedAt: span.startedAt,\n endedAt: span.endedAt,\n duration: span.duration,\n usage: structuredClone(span.usage),\n error: span.error ? { type: span.error.type, message: span.error.message } : undefined,\n attributes: toSafeValue(span.attributes) as Record<string, unknown> | undefined,\n children: span.children.map(snapshotSpan),\n };\n}\n\n/**\n * Optional, lazy integration. Retains only traces collected after devtools boots,\n * using Panoptic's bounded query store; existing exporter/cache stores stay independent.\n */\nexport async function attachAiSource(capacity: number): Promise<DevtoolsAiSource> {\n let panoptic: typeof import(\"@warlock.js/ai-panoptic\");\n try {\n panoptic = await import(\"@warlock.js/ai-panoptic\");\n if (typeof panoptic.subscribeTraces !== \"function\")\n throw new Error(\"Trace subscription unavailable\");\n } catch {\n return { available: false, message: AI_INSTALL_GUIDANCE, dispose() {} };\n }\n\n const { currentRequestId } = await import(\"../current-request-id\");\n const store = panoptic.createInMemoryTraceStore({ capacity });\n const listeners = new Set<(event: DevtoolsAiEvent) => void>();\n const detach = panoptic.subscribeTraces((trace) => {\n // Read ALS before any asynchronous exporter work, including for runs that\n // finish after the HTTP response. Never use sessionId as a request id.\n const requestId = currentRequestId() ?? trace.requestId;\n store.add({\n ...trace,\n requestId,\n usage: structuredClone(trace.usage),\n root: snapshotSpan(trace.root),\n });\n for (const listener of listeners) {\n try {\n listener({ traceId: trace.traceId, requestId });\n } catch {\n /* isolate clients */\n }\n }\n });\n\n return {\n available: true,\n store,\n summarize: (trace) => summarizeAiTrace(trace, panoptic.totalCostUsd(trace.usage)),\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n dispose() {\n detach();\n listeners.clear();\n store.clear();\n },\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { currentRequestId } from "../current-request-id.mjs";
2
1
  import { toSafeValue } from "../safe-value.mjs";
2
+ import { currentRequestId } from "../current-request-id.mjs";
3
3
  import { LogChannel, log } from "@warlock.js/logger";
4
4
 
5
5
  //#region ../../packages/devtools/src/sources/log-source.ts
@@ -1,5 +1,5 @@
1
- import { currentRequestId } from "../current-request-id.mjs";
2
1
  import { toSafeValue } from "../safe-value.mjs";
2
+ import { currentRequestId } from "../current-request-id.mjs";
3
3
  import { captureCallerLocation } from "../caller-location.mjs";
4
4
  import { applyRedact, log } from "@warlock.js/logger";
5
5
 
package/esm/types.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { DevtoolsAiEvent } from "./sources/ai-source.mjs";
1
2
  //#region ../../packages/devtools/src/types.d.ts
2
3
  /**
3
4
  * The records the devtools collector keeps and the dashboard API returns.
@@ -7,7 +8,7 @@
7
8
  * ring buffer that is lost on restart.
8
9
  */
9
10
  /** Options accepted by `devtoolsConnector()`. */
10
- type DevtoolsOptions = {
11
+ export type DevtoolsOptions = {
11
12
  /**
12
13
  * URL prefix the dashboard and its API are served under.
13
14
  *
@@ -29,44 +30,49 @@ type DevtoolsOptions = {
29
30
  nPlusOneThreshold?: number;
30
31
  };
31
32
  /** `DevtoolsOptions` with every default applied. */
32
- type ResolvedDevtoolsOptions = Required<DevtoolsOptions>;
33
+ export type ResolvedDevtoolsOptions = Required<DevtoolsOptions>;
33
34
  /** A source file position, as shown next to an N+1 warning. */
34
- type DevtoolsCallerLocation = {
35
- /** Absolute path, forward slashes. */file: string;
35
+ export type DevtoolsCallerLocation = {
36
+ /** Absolute path, forward slashes. */
37
+ file: string;
36
38
  line: number;
37
39
  column: number;
38
40
  };
39
41
  /** One timed phase of a request (router middleware, loader, render, …). */
40
- type DevtoolsPhase = {
42
+ export type DevtoolsPhase = {
41
43
  name: string;
42
44
  startedAt: number;
43
45
  durationMs: number;
44
46
  attrs?: Record<string, unknown>;
45
47
  };
46
48
  /** One database query, Postgres or Mongo. */
47
- type DevtoolsQuery = {
48
- /** Unique within the collector. */id: string;
49
+ export type DevtoolsQuery = {
50
+ /** Unique within the collector. */
51
+ id: string;
49
52
  connection: string;
50
53
  driver: "postgres" | "mongodb";
51
- sql?: string; /** Redacted through the logger's redaction rules before storage. */
54
+ sql?: string;
55
+ /** Redacted through the logger's redaction rules before storage. */
52
56
  bindings?: unknown[];
53
57
  collection?: string;
54
58
  command?: string;
55
59
  pipeline?: unknown;
56
60
  startedAt: number;
57
61
  durationMs: number;
58
- rowCount?: number; /** The error message when the query failed. */
62
+ rowCount?: number;
63
+ /** The error message when the query failed. */
59
64
  error?: string;
60
65
  /**
61
66
  * The query with its values stripped — SQL text without bindings, or the
62
67
  * Mongo command with every leaf value replaced by `?`. Two queries with the
63
68
  * same shape are "the same query" for N+1 detection.
64
69
  */
65
- shape: string; /** The first application frame that issued the query, when resolvable. */
70
+ shape: string;
71
+ /** The first application frame that issued the query, when resolvable. */
66
72
  caller?: DevtoolsCallerLocation;
67
73
  };
68
74
  /** One cache operation. */
69
- type DevtoolsCacheOperation = {
75
+ export type DevtoolsCacheOperation = {
70
76
  type: "hit" | "miss" | "set" | "removed" | "invalidated" | "flushed";
71
77
  driver: string;
72
78
  key?: string;
@@ -76,16 +82,17 @@ type DevtoolsCacheOperation = {
76
82
  durationMs?: number;
77
83
  };
78
84
  /** One log entry. */
79
- type DevtoolsLogEntry = {
85
+ export type DevtoolsLogEntry = {
80
86
  level: "debug" | "info" | "warn" | "error" | "success";
81
87
  module: string;
82
88
  action: string;
83
89
  message: string;
84
- at: number; /** Redacted through the logger's redaction rules before storage. */
90
+ at: number;
91
+ /** Redacted through the logger's redaction rules before storage. */
85
92
  context?: unknown;
86
93
  };
87
94
  /** A mail captured in development (see core's `mailEvents.onCaptured`). */
88
- type DevtoolsMail = {
95
+ export type DevtoolsMail = {
89
96
  id: string;
90
97
  requestId?: string;
91
98
  capturedAt: number;
@@ -100,13 +107,13 @@ type DevtoolsMail = {
100
107
  attachments: DevtoolsMailAttachment[];
101
108
  };
102
109
  /** Attachment metadata only — the bytes are never stored. */
103
- type DevtoolsMailAttachment = {
110
+ export type DevtoolsMailAttachment = {
104
111
  filename: string;
105
112
  contentType?: string;
106
113
  size?: number;
107
114
  };
108
115
  /** A problem devtools noticed about a request. */
109
- type DevtoolsWarning = {
116
+ export type DevtoolsWarning = {
110
117
  type: "n+1";
111
118
  message: string;
112
119
  shape: string;
@@ -114,24 +121,28 @@ type DevtoolsWarning = {
114
121
  caller?: DevtoolsCallerLocation;
115
122
  };
116
123
  /** Everything recorded for one HTTP request. */
117
- type DevtoolsRequest = {
118
- /** The framework's `request.id`. */id: string;
124
+ export type DevtoolsRequest = {
125
+ /** The framework's `request.id`. */
126
+ id: string;
119
127
  method: string;
120
- path: string; /** Matched route pattern, e.g. `/posts/:slug`. */
128
+ path: string;
129
+ /** Matched route pattern, e.g. `/posts/:slug`. */
121
130
  route?: string;
122
131
  status?: number;
123
- startedAt: number; /** Undefined while the request is still in flight. */
132
+ startedAt: number;
133
+ /** Undefined while the request is still in flight. */
124
134
  durationMs?: number;
125
135
  error?: string;
126
136
  phases: DevtoolsPhase[];
127
137
  queries: DevtoolsQuery[];
128
138
  cache: DevtoolsCacheOperation[];
129
- logs: DevtoolsLogEntry[]; /** Ids of mails captured during this request (see `DevtoolsMail`). */
139
+ logs: DevtoolsLogEntry[];
140
+ /** Ids of mails captured during this request (see `DevtoolsMail`). */
130
141
  mails: string[];
131
142
  warnings: DevtoolsWarning[];
132
143
  };
133
144
  /** The row the request list shows; `GET <path>/api/requests` returns these. */
134
- type DevtoolsRequestSummary = {
145
+ export type DevtoolsRequestSummary = {
135
146
  id: string;
136
147
  method: string;
137
148
  path: string;
@@ -143,7 +154,7 @@ type DevtoolsRequestSummary = {
143
154
  warningCount: number;
144
155
  };
145
156
  /** The row the mailbox list shows; `GET <path>/api/mails` returns these. */
146
- type DevtoolsMailSummary = {
157
+ export type DevtoolsMailSummary = {
147
158
  id: string;
148
159
  requestId?: string;
149
160
  capturedAt: number;
@@ -154,13 +165,13 @@ type DevtoolsMailSummary = {
154
165
  * Work that happened outside any request (boot, a scheduler tick, a queue
155
166
  * job): kept separately so it never pollutes a request's timeline.
156
167
  */
157
- type DevtoolsBackground = {
168
+ export type DevtoolsBackground = {
158
169
  queries: DevtoolsQuery[];
159
170
  cache: DevtoolsCacheOperation[];
160
171
  logs: DevtoolsLogEntry[];
161
172
  };
162
173
  /** Server-sent events on `GET <path>/api/stream`. */
163
- type DevtoolsStreamEvent = {
174
+ export type DevtoolsStreamEvent = {
164
175
  type: "request";
165
176
  data: DevtoolsRequestSummary;
166
177
  } | {
@@ -169,29 +180,34 @@ type DevtoolsStreamEvent = {
169
180
  } | {
170
181
  type: "log";
171
182
  data: DevtoolsLogEntry;
183
+ } | {
184
+ type: "ai";
185
+ data: DevtoolsAiEvent;
172
186
  };
173
187
  /** Unsubscribes a listener or detaches a source. */
174
- type DevtoolsDisposer = () => void;
188
+ export type DevtoolsDisposer = () => void;
175
189
  /**
176
190
  * The in-memory store every source writes to and the dashboard API reads
177
191
  * from. `requestId` is the framework's `request.id`; `undefined` means the
178
192
  * work happened outside a request and goes to the background bucket.
179
193
  */
180
- type DevtoolsCollector = {
194
+ export type DevtoolsCollector = {
181
195
  startRequest(request: {
182
196
  id: string;
183
197
  method: string;
184
198
  path: string;
185
199
  route?: string;
186
200
  startedAt: number;
187
- }): void; /** Settles the request, runs N+1 detection and emits a `request` stream event. */
201
+ }): void;
202
+ /** Settles the request, runs N+1 detection and emits a `request` stream event. */
188
203
  endRequest(id: string, result: {
189
204
  status?: number;
190
205
  durationMs: number;
191
206
  error?: string;
192
207
  route?: string;
193
208
  }): void;
194
- addPhase(requestId: string, phase: DevtoolsPhase): void; /** Computes `id` and `shape` itself. */
209
+ addPhase(requestId: string, phase: DevtoolsPhase): void;
210
+ /** Computes `id` and `shape` itself. */
195
211
  addQuery(requestId: string | undefined, query: Omit<DevtoolsQuery, "id" | "shape">): DevtoolsQuery;
196
212
  addCacheOperation(requestId: string | undefined, operation: DevtoolsCacheOperation): void;
197
213
  addLog(requestId: string | undefined, entry: DevtoolsLogEntry): void;
@@ -209,5 +225,4 @@ type DevtoolsCollector = {
209
225
  clear(): void;
210
226
  };
211
227
  //#endregion
212
- export { DevtoolsBackground, DevtoolsCacheOperation, DevtoolsCallerLocation, DevtoolsCollector, DevtoolsDisposer, DevtoolsLogEntry, DevtoolsMail, DevtoolsMailAttachment, DevtoolsMailSummary, DevtoolsOptions, DevtoolsPhase, DevtoolsQuery, DevtoolsRequest, DevtoolsRequestSummary, DevtoolsStreamEvent, DevtoolsWarning, ResolvedDevtoolsOptions };
213
228
  //# sourceMappingURL=types.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/devtools/src/types.ts"],"mappings":"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"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/devtools/src/types.ts"],"mappings":";;;;;;;;;;YAWY;;;;;;EAMV;;;;;;EAMA;;;;;;;EAOA;;;YAIU,0BAA0B,SAAS;;YAGnC;;EAEV;EACA;EACA;;;YAIU;EACV;EACA;EACA;EACA,QAAQ;;;YAIE;;EAEV;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;;;;;;EAMA;;EAEA,SAAS;;;YAIC;EACV;EACA;EACA;EACA;EACA;EACA;EACA;;;YAIU;EACV;EACA;EACA;EACA;EACA;;EAEA;;;YAIU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,SAAS;EACT,aAAa;;;YAIH;EACV;EACA;EACA;;;YAIU;EACV;EACA;EACA;EACA;EACA,SAAS;;;YAIC;;EAEV;EACA;EACA;;EAEA;EACA;EACA;;EAEA;EACA;EACA,QAAQ;EACR,SAAS;EACT,OAAO;EACP,MAAM;;EAEN;EACA,UAAU;;;YAIA;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;YAIU;EACV;EACA;EACA;EACA;EACA;;;;;;YAOU;EACV,SAAS;EACT,OAAO;EACP,MAAM;;;YAII;EACN;EAAiB,MAAM;;EACvB;EAAc,MAAM;;EACpB;EAAa,MAAM;;EACnB;EAAY,MAAM;;;YAGZ;;;;;;YAOA;EACV,aAAa;IACX;IACA;IACA;IACA;IACA;;;EAGF,WACE,YACA;IAAU;IAAiB;IAAoB;IAAgB;;EAEjE,SAAS,mBAAmB,OAAO;;EAEnC,SACE,+BACA,OAAO,KAAK,iCACX;EACH,kBAAkB,+BAA+B,WAAW;EAC5D,OAAO,+BAA+B,OAAO;EAC7C,QAAQ,MAAM;EACd,gBAAgB;EAChB,WAAW,aAAa;EACxB,aAAa;EACb,QAAQ,aAAa;EACrB,SAAS;IAAW,QAAQ;IAA2B;MAAoB;EAC3E,iBAAiB;EACjB,UAAU,WAAW,OAAO,+BAA+B;EAC3D"}
@@ -1 +1 @@
1
- {"version":3,"file":"ui-root.mjs","names":[],"sources":["../../../../../../../packages/devtools/src/ui-root.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nlet cachedUiRoot: string | undefined;\n\n/** Whether `dir` is this package's root: it has `ui/index.html` beside a `package.json` named `@warlock.js/devtools`. */\nfunction isPackageRoot(dir: string): boolean {\n const indexHtml = path.join(dir, \"ui\", \"index.html\");\n const packageJson = path.join(dir, \"package.json\");\n\n if (!existsSync(indexHtml) || !existsSync(packageJson)) {\n return false;\n }\n\n try {\n const parsed = JSON.parse(readFileSync(packageJson, \"utf8\")) as { name?: string };\n return parsed.name === \"@warlock.js/devtools\";\n } catch {\n return false;\n }\n}\n\n/**\n * Locate this package's `ui/` directory by walking up from the current\n * module's directory. Works from `src/` in development and from the built\n * `esm/`/`cjs/` output, since both sit beside `ui/` and `package.json` at\n * the package root.\n */\nexport function resolveUiRoot(): string {\n if (cachedUiRoot) {\n return cachedUiRoot;\n }\n\n let dir = path.dirname(fileURLToPath(import.meta.url));\n\n while (true) {\n if (isPackageRoot(dir)) {\n cachedUiRoot = path.join(dir, \"ui\");\n return cachedUiRoot;\n }\n\n const parent = path.dirname(dir);\n if (parent === dir) {\n throw new Error(\n \"Could not locate the devtools UI directory: no ancestor of \" +\n \"this module has a ui/index.html beside a package.json named \" +\n \"@warlock.js/devtools.\",\n );\n }\n\n dir = parent;\n }\n}\n"],"mappings":";;;;;AAIA,IAAI;;AAGJ,SAAS,cAAc,KAAsB;CAC3C,MAAM,YAAY,KAAK,KAAK,KAAK,MAAM,YAAY;CACnD,MAAM,cAAc,KAAK,KAAK,KAAK,cAAc;CAEjD,IAAI,CAAC,WAAW,SAAS,KAAK,CAAC,WAAW,WAAW,GACnD,OAAO;CAGT,IAAI;EAEF,OADe,KAAK,MAAM,aAAa,aAAa,MAAM,CAC9C,CAAC,CAAC,SAAS;CACzB,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;AAQA,SAAgB,gBAAwB;CACtC,IAAI,cACF,OAAO;CAGT,IAAI,MAAM,KAAK,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC;CAErD,OAAO,MAAM;EACX,IAAI,cAAc,GAAG,GAAG;GACtB,eAAe,KAAK,KAAK,KAAK,IAAI;GAClC,OAAO;EACT;EAEA,MAAM,SAAS,KAAK,QAAQ,GAAG;EAC/B,IAAI,WAAW,KACb,MAAM,IAAI,MACR,8IAGF;EAGF,MAAM;CACR;AACF"}
1
+ {"version":3,"file":"ui-root.mjs","names":[],"sources":["../../../../../../../packages/devtools/src/ui-root.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nlet cachedUiRoot: string | undefined;\n\n/** Whether `dir` is this package's root: it has `ui/index.html` beside a `package.json` named `@warlock.js/devtools`. */\nfunction isPackageRoot(dir: string): boolean {\n const indexHtml = path.join(dir, \"ui\", \"index.html\");\n const packageJson = path.join(dir, \"package.json\");\n\n if (!existsSync(indexHtml) || !existsSync(packageJson)) {\n return false;\n }\n\n try {\n const parsed = JSON.parse(readFileSync(packageJson, \"utf8\")) as { name?: string };\n return parsed.name === \"@warlock.js/devtools\";\n } catch {\n return false;\n }\n}\n\n/**\n * Locate this package's `ui/` directory by walking up from the current\n * module's directory. Works from `src/` in development and from the built\n * `esm/`/`cjs/` output, since both sit beside `ui/` and `package.json` at\n * the package root.\n */\nexport function resolveUiRoot(): string {\n if (cachedUiRoot) {\n return cachedUiRoot;\n }\n\n let dir = path.dirname(fileURLToPath(import.meta.url));\n\n while (true) {\n if (isPackageRoot(dir)) {\n cachedUiRoot = path.join(dir, \"ui\");\n return cachedUiRoot;\n }\n\n const parent = path.dirname(dir);\n if (parent === dir) {\n throw new Error(\n \"Could not locate the devtools UI directory: no ancestor of \" +\n \"this module has a ui/index.html beside a package.json named \" +\n \"@warlock.js/devtools.\",\n );\n }\n\n dir = parent;\n }\n}\n"],"mappings":";;;;;AAIA,IAAI;;AAGJ,SAAS,cAAc,KAAsB;CAC3C,MAAM,YAAY,KAAK,KAAK,KAAK,MAAM,YAAY;CACnD,MAAM,cAAc,KAAK,KAAK,KAAK,cAAc;CAEjD,IAAI,CAAC,WAAW,SAAS,KAAK,CAAC,WAAW,WAAW,GACnD,OAAO;CAGT,IAAI;EAEF,OADe,KAAK,MAAM,aAAa,aAAa,MAAM,CAC9C,CAAC,CAAC,SAAS;CACzB,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;AAQA,SAAgB,gBAAwB;CACtC,IAAI,cACF,OAAO;CAGT,IAAI,MAAM,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;CAErD,OAAO,MAAM;EACX,IAAI,cAAc,GAAG,GAAG;GACtB,eAAe,KAAK,KAAK,KAAK,IAAI;GAClC,OAAO;EACT;EAEA,MAAM,SAAS,KAAK,QAAQ,GAAG;EAC/B,IAAI,WAAW,KACb,MAAM,IAAI,MACR,8IAGF;EAGF,MAAM;CACR;AACF"}
package/llms-full.txt CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ---
10
10
  name: devtools-overview
11
- description: 'Development-only dashboard for Warlock.js apps at `/__warlock`: request timeline (router + page middleware, loaders, render, deferred values), every SQL/Mongo query with EXPLAIN, N+1 warnings naming the file and line, a dev mailbox, logs, cache operations, the route map and an API docs page (`/__warlock/docs`, the OpenAPI 3.1 document of the app rendered with Scalar or Swagger UI at `/__warlock/docs/swagger`, with OpenAPI and Postman downloads). Use for: "why is this page slow", "which queries does this request run", "find N+1 queries", "see the email my app just sent in dev", "open the dashboard", "browse my API docs in dev", "see the OpenAPI for my routes", "open Swagger UI for my API", "export my routes to Postman". Install with `warlock add devtools`; never registered in warlock.config.ts. Not this package: production monitoring/APM or OpenTelemetry export — use `http.tracing` hooks in @warlock.js/core; slow-query logging in production — subscribe to cascade `dataSourceRegistry.on("query")` yourself.'
11
+ description: 'Development-only dashboard for Warlock.js apps at `/__warlock`: request timeline (router + page middleware, loaders, render, deferred values), every SQL/Mongo query with EXPLAIN, N+1 warnings naming the file and line, a dev mailbox, logs, cache operations, AI traces per HTTP request (model, tokens, cost, cache hits, tools, retries, truncation, compaction), the route map and an API docs page (`/__warlock/docs`, the OpenAPI 3.1 document of the app rendered with Scalar or Swagger UI at `/__warlock/docs/swagger`, with OpenAPI and Postman downloads). Use for: "why is this page slow", "which queries does this request run", "find N+1 queries", "see the email my app just sent in dev", "open the dashboard", "browse my API docs in dev", "see the OpenAPI for my routes", "open Swagger UI for my API", "export my routes to Postman". Install with `warlock add devtools`; never registered in warlock.config.ts. Not this package: production monitoring/APM or OpenTelemetry export — use `http.tracing` hooks in @warlock.js/core; slow-query logging in production — subscribe to cascade `dataSourceRegistry.on("query")` yourself.'
12
12
  ---
13
13
 
14
14
  # `@warlock.js/devtools` — overview
@@ -58,6 +58,78 @@ of it would fail to resolve there.
58
58
  Work outside a request (boot, scheduler ticks, queue jobs) goes to a separate
59
59
  background list, so it never clutters a request's timeline.
60
60
 
61
+ ## AI runs alongside HTTP requests
62
+
63
+ The **AI** tab and each request's **AI runs** section display completed traces
64
+ from optional `@warlock.js/ai-panoptic`. If it cannot be imported, the tab shows
65
+ install/restart guidance and the other panels keep working.
66
+
67
+ ```bash
68
+ warlock add devtools
69
+ npm install @warlock.js/ai @warlock.js/ai-panoptic
70
+ ```
71
+
72
+ At app boot, enable observation, then restart `warlock dev`:
73
+
74
+ ```ts
75
+ import { ai } from "@warlock.js/ai";
76
+ import "@warlock.js/ai-panoptic";
77
+ ai.config({ panoptic: { observeAll: true } });
78
+ ```
79
+
80
+ No `dashboard: true` or extra listener port is needed. Existing `attach`,
81
+ middleware and explicit collection through Panoptic's built-in collector also
82
+ feed the panel. An installed but unobserved flow produces no trace.
83
+
84
+ - `DevtoolsAiRun` and `DevtoolsAiEvent` are public types from
85
+ `@warlock.js/devtools`; `DevtoolsStreamEvent` includes the `ai` event.
86
+ - Read all runs with `GET /__warlock/api/ai`, or filter by an exact HTTP id with
87
+ `GET /__warlock/api/ai?requestId=<encoded-id>`. Responses include `available`,
88
+ `runs` and a root-usage `aggregate`; unavailable responses contain `message`.
89
+ - Inspect tools/timings, retry attempts and compaction records with
90
+ `GET /__warlock/api/ai/runs/<encoded-trace-id>` (404 when evicted). The existing
91
+ stream emits `ai` events with `{ traceId, requestId? }`.
92
+ - Model, input/output/cache-read/cache-write tokens and historical USD cost come
93
+ from the trace, never a guessed price table. Unknown cost is unpriced. Root
94
+ usage includes children already; do not sum all spans for a run total.
95
+ - Cache hits count trips reporting positive provider `cachedTokens`.
96
+ Semantic-cache hits have no explicit report flag and remain unreported; zero
97
+ tokens alone do not prove a hit. `—` means unreported rather than zero.
98
+ - `incomplete` status or normalized `AGENT_INCOMPLETE` / `AgentIncompleteError`
99
+ marks truncation. Recovered truncated trips still contribute to their count.
100
+ - Compaction metadata includes applied/skipped agent decisions and provider
101
+ compaction-block counts; summary content is omitted. Orchestrator result-only
102
+ compaction cannot be reconstructed from a report-only trace.
103
+
104
+ A same-origin browser tool can consume the public types directly:
105
+
106
+ ```ts
107
+ import type { DevtoolsAiEvent, DevtoolsAiRun, DevtoolsStreamEvent } from "@warlock.js/devtools";
108
+
109
+ const response = await fetch("/__warlock/api/ai?requestId=request-42");
110
+ const { runs }: { runs: DevtoolsAiRun[] } = await response.json();
111
+ console.log(runs.map((run) => [run.traceId, run.usage, run.cost]));
112
+
113
+ function handleStream(event: DevtoolsStreamEvent): void {
114
+ if (event.type === "ai") {
115
+ const update: DevtoolsAiEvent = event.data;
116
+ console.log(update.traceId, update.requestId);
117
+ }
118
+ }
119
+ handleStream({ type: "ai", data: { traceId: "run-42", requestId: "request-42" } });
120
+ ```
121
+
122
+ Request correlation reads core's current request id at collection time, before
123
+ exporters. Concurrent/late runs can retain their async context. No id means
124
+ background; `sessionId` is never substituted. Replayed reports outside the
125
+ request cannot recover its context automatically.
126
+
127
+ Devtools retains its own content-free in-memory store, at most `maxRequests`
128
+ traces (200 by default), independently of HTTP request retention. Clear/shutdown
129
+ empties this view, leaving application exporter/cache stores alone. Boot-time
130
+ and persisted traces are not replayed. The source only attaches in development;
131
+ AI routes keep the same socket-loopback guard as every other panel.
132
+
61
133
  ## API docs
62
134
 
63
135
  `/__warlock/docs` renders the app's OpenAPI 3.1 document with [Scalar](https://github.com/scalar/scalar); the dashboard's **API** tab shows the same page and has a **Scalar | Swagger UI** switch (remembered in the browser). `/__warlock/docs/swagger` renders the same document with [Swagger UI](https://github.com/swagger-api/swagger-ui). The document comes from `GET /__warlock/api/openapi.json`, built in-process by core's `getDevelopmentOpenApiDocument()` from the routes already registered, so it is the same generator as `warlock generate.openapi` and follows your edits with no CLI run. It covers API routes (not pages): paths, parameters, request bodies from Seal validation, responses from each handler's `responseSchema`, `401`/`422` and bearer/cookie security from `authMiddleware()`. See core's `generate-openapi` topic for what is documented and how to declare it.
@@ -99,4 +171,16 @@ why the dashboard is development-only and loopback-only.
99
171
  cascade `dataSourceRegistry.on("query")`, core `mailEvents.onCaptured()` and
100
172
  the cache `invalidated` event are public APIs that also work without devtools.
101
173
 
174
+ ## Process supervision panel
175
+
176
+ Run `npx warlock add process` to create an app-owned supervisor and
177
+ `src/app/processes/devtools.ts`, exporting `processPanel = { list: () =>
178
+ processes.list() }`. Devtools discovers this registration only in development.
179
+ The **Processes** tab refreshes `GET /__warlock/api/processes`: PIDs, workload
180
+ categories, labels, display commands, limits, enforcement, and peak memory.
181
+ It excludes ownership tokens. Without registration the list is empty. The
182
+ endpoint retains development/loopback guards and provides no stop action.
183
+ Use the `supervise-processes` skill of `@warlock.js/process` for launch,
184
+ recovery and stop APIs; configure one owner/registry per application instance.
185
+
102
186
 
package/llms.txt CHANGED
@@ -6,4 +6,4 @@
6
6
 
7
7
  ## Skills
8
8
 
9
- - [devtools-overview](@warlock.js/devtools/devtools-overview/SKILL.md): Development-only dashboard for Warlock.js apps at `/__warlock`: request timeline (router + page middleware, loaders, render, deferred values), every SQL/Mongo query with EXPLAIN, N+1 warnings naming the file and line, a dev mailbox, logs, cache operations, the route map and an API docs page (`/__warlock/docs`, the OpenAPI 3.1 document of the app rendered with Scalar or Swagger UI at `/__warlock/docs/swagger`, with OpenAPI and Postman downloads). Use for: "why is this page slow", "which queries does this request run", "find N+1 queries", "see the email my app just sent in dev", "open the dashboard", "browse my API docs in dev", "see the OpenAPI for my routes", "open Swagger UI for my API", "export my routes to Postman". Install with `warlock add devtools`; never registered in warlock.config.ts. Not this package: production monitoring/APM or OpenTelemetry export — use `http.tracing` hooks in @warlock.js/core; slow-query logging in production — subscribe to cascade `dataSourceRegistry.on("query")` yourself.
9
+ - [devtools-overview](@warlock.js/devtools/devtools-overview/SKILL.md): Development-only dashboard for Warlock.js apps at `/__warlock`: request timeline (router + page middleware, loaders, render, deferred values), every SQL/Mongo query with EXPLAIN, N+1 warnings naming the file and line, a dev mailbox, logs, cache operations, AI traces per HTTP request (model, tokens, cost, cache hits, tools, retries, truncation, compaction), the route map and an API docs page (`/__warlock/docs`, the OpenAPI 3.1 document of the app rendered with Scalar or Swagger UI at `/__warlock/docs/swagger`, with OpenAPI and Postman downloads). Use for: "why is this page slow", "which queries does this request run", "find N+1 queries", "see the email my app just sent in dev", "open the dashboard", "browse my API docs in dev", "see the OpenAPI for my routes", "open Swagger UI for my API", "export my routes to Postman". Install with `warlock add devtools`; never registered in warlock.config.ts. Not this package: production monitoring/APM or OpenTelemetry export — use `http.tracing` hooks in @warlock.js/core; slow-query logging in production — subscribe to cascade `dataSourceRegistry.on("query")` yourself.
package/package.json CHANGED
@@ -5,12 +5,16 @@
5
5
  "environment": "server"
6
6
  },
7
7
  "peerDependencies": {
8
- "@warlock.js/cache": "5.31.0",
9
- "@warlock.js/cascade": "5.31.0",
10
- "@warlock.js/core": "5.31.0",
11
- "@warlock.js/logger": "5.31.0"
8
+ "@warlock.js/ai-panoptic": "5.33.0",
9
+ "@warlock.js/cache": "5.33.0",
10
+ "@warlock.js/cascade": "5.33.0",
11
+ "@warlock.js/core": "5.33.0",
12
+ "@warlock.js/logger": "5.33.0"
12
13
  },
13
14
  "peerDependenciesMeta": {
15
+ "@warlock.js/ai-panoptic": {
16
+ "optional": true
17
+ },
14
18
  "@warlock.js/cache": {
15
19
  "optional": true
16
20
  },
@@ -34,7 +38,7 @@
34
38
  ],
35
39
  "author": "hassanzohdy",
36
40
  "license": "MIT",
37
- "version": "5.31.0",
41
+ "version": "5.33.0",
38
42
  "main": "./cjs/index.cjs",
39
43
  "module": "./esm/index.mjs",
40
44
  "types": "./esm/index.d.mts",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: devtools-overview
3
- description: 'Development-only dashboard for Warlock.js apps at `/__warlock`: request timeline (router + page middleware, loaders, render, deferred values), every SQL/Mongo query with EXPLAIN, N+1 warnings naming the file and line, a dev mailbox, logs, cache operations, the route map and an API docs page (`/__warlock/docs`, the OpenAPI 3.1 document of the app rendered with Scalar or Swagger UI at `/__warlock/docs/swagger`, with OpenAPI and Postman downloads). Use for: "why is this page slow", "which queries does this request run", "find N+1 queries", "see the email my app just sent in dev", "open the dashboard", "browse my API docs in dev", "see the OpenAPI for my routes", "open Swagger UI for my API", "export my routes to Postman". Install with `warlock add devtools`; never registered in warlock.config.ts. Not this package: production monitoring/APM or OpenTelemetry export — use `http.tracing` hooks in @warlock.js/core; slow-query logging in production — subscribe to cascade `dataSourceRegistry.on("query")` yourself.'
3
+ description: 'Development-only dashboard for Warlock.js apps at `/__warlock`: request timeline (router + page middleware, loaders, render, deferred values), every SQL/Mongo query with EXPLAIN, N+1 warnings naming the file and line, a dev mailbox, logs, cache operations, AI traces per HTTP request (model, tokens, cost, cache hits, tools, retries, truncation, compaction), the route map and an API docs page (`/__warlock/docs`, the OpenAPI 3.1 document of the app rendered with Scalar or Swagger UI at `/__warlock/docs/swagger`, with OpenAPI and Postman downloads). Use for: "why is this page slow", "which queries does this request run", "find N+1 queries", "see the email my app just sent in dev", "open the dashboard", "browse my API docs in dev", "see the OpenAPI for my routes", "open Swagger UI for my API", "export my routes to Postman". Install with `warlock add devtools`; never registered in warlock.config.ts. Not this package: production monitoring/APM or OpenTelemetry export — use `http.tracing` hooks in @warlock.js/core; slow-query logging in production — subscribe to cascade `dataSourceRegistry.on("query")` yourself.'
4
4
  ---
5
5
 
6
6
  # `@warlock.js/devtools` — overview
@@ -50,6 +50,78 @@ of it would fail to resolve there.
50
50
  Work outside a request (boot, scheduler ticks, queue jobs) goes to a separate
51
51
  background list, so it never clutters a request's timeline.
52
52
 
53
+ ## AI runs alongside HTTP requests
54
+
55
+ The **AI** tab and each request's **AI runs** section display completed traces
56
+ from optional `@warlock.js/ai-panoptic`. If it cannot be imported, the tab shows
57
+ install/restart guidance and the other panels keep working.
58
+
59
+ ```bash
60
+ warlock add devtools
61
+ npm install @warlock.js/ai @warlock.js/ai-panoptic
62
+ ```
63
+
64
+ At app boot, enable observation, then restart `warlock dev`:
65
+
66
+ ```ts
67
+ import { ai } from "@warlock.js/ai";
68
+ import "@warlock.js/ai-panoptic";
69
+ ai.config({ panoptic: { observeAll: true } });
70
+ ```
71
+
72
+ No `dashboard: true` or extra listener port is needed. Existing `attach`,
73
+ middleware and explicit collection through Panoptic's built-in collector also
74
+ feed the panel. An installed but unobserved flow produces no trace.
75
+
76
+ - `DevtoolsAiRun` and `DevtoolsAiEvent` are public types from
77
+ `@warlock.js/devtools`; `DevtoolsStreamEvent` includes the `ai` event.
78
+ - Read all runs with `GET /__warlock/api/ai`, or filter by an exact HTTP id with
79
+ `GET /__warlock/api/ai?requestId=<encoded-id>`. Responses include `available`,
80
+ `runs` and a root-usage `aggregate`; unavailable responses contain `message`.
81
+ - Inspect tools/timings, retry attempts and compaction records with
82
+ `GET /__warlock/api/ai/runs/<encoded-trace-id>` (404 when evicted). The existing
83
+ stream emits `ai` events with `{ traceId, requestId? }`.
84
+ - Model, input/output/cache-read/cache-write tokens and historical USD cost come
85
+ from the trace, never a guessed price table. Unknown cost is unpriced. Root
86
+ usage includes children already; do not sum all spans for a run total.
87
+ - Cache hits count trips reporting positive provider `cachedTokens`.
88
+ Semantic-cache hits have no explicit report flag and remain unreported; zero
89
+ tokens alone do not prove a hit. `—` means unreported rather than zero.
90
+ - `incomplete` status or normalized `AGENT_INCOMPLETE` / `AgentIncompleteError`
91
+ marks truncation. Recovered truncated trips still contribute to their count.
92
+ - Compaction metadata includes applied/skipped agent decisions and provider
93
+ compaction-block counts; summary content is omitted. Orchestrator result-only
94
+ compaction cannot be reconstructed from a report-only trace.
95
+
96
+ A same-origin browser tool can consume the public types directly:
97
+
98
+ ```ts
99
+ import type { DevtoolsAiEvent, DevtoolsAiRun, DevtoolsStreamEvent } from "@warlock.js/devtools";
100
+
101
+ const response = await fetch("/__warlock/api/ai?requestId=request-42");
102
+ const { runs }: { runs: DevtoolsAiRun[] } = await response.json();
103
+ console.log(runs.map((run) => [run.traceId, run.usage, run.cost]));
104
+
105
+ function handleStream(event: DevtoolsStreamEvent): void {
106
+ if (event.type === "ai") {
107
+ const update: DevtoolsAiEvent = event.data;
108
+ console.log(update.traceId, update.requestId);
109
+ }
110
+ }
111
+ handleStream({ type: "ai", data: { traceId: "run-42", requestId: "request-42" } });
112
+ ```
113
+
114
+ Request correlation reads core's current request id at collection time, before
115
+ exporters. Concurrent/late runs can retain their async context. No id means
116
+ background; `sessionId` is never substituted. Replayed reports outside the
117
+ request cannot recover its context automatically.
118
+
119
+ Devtools retains its own content-free in-memory store, at most `maxRequests`
120
+ traces (200 by default), independently of HTTP request retention. Clear/shutdown
121
+ empties this view, leaving application exporter/cache stores alone. Boot-time
122
+ and persisted traces are not replayed. The source only attaches in development;
123
+ AI routes keep the same socket-loopback guard as every other panel.
124
+
53
125
  ## API docs
54
126
 
55
127
  `/__warlock/docs` renders the app's OpenAPI 3.1 document with [Scalar](https://github.com/scalar/scalar); the dashboard's **API** tab shows the same page and has a **Scalar | Swagger UI** switch (remembered in the browser). `/__warlock/docs/swagger` renders the same document with [Swagger UI](https://github.com/swagger-api/swagger-ui). The document comes from `GET /__warlock/api/openapi.json`, built in-process by core's `getDevelopmentOpenApiDocument()` from the routes already registered, so it is the same generator as `warlock generate.openapi` and follows your edits with no CLI run. It covers API routes (not pages): paths, parameters, request bodies from Seal validation, responses from each handler's `responseSchema`, `401`/`422` and bearer/cookie security from `authMiddleware()`. See core's `generate-openapi` topic for what is documented and how to declare it.
@@ -90,3 +162,15 @@ why the dashboard is development-only and loopback-only.
90
162
  - Building your own tooling on the same hooks: core `registerTracingHooks()`,
91
163
  cascade `dataSourceRegistry.on("query")`, core `mailEvents.onCaptured()` and
92
164
  the cache `invalidated` event are public APIs that also work without devtools.
165
+
166
+ ## Process supervision panel
167
+
168
+ Run `npx warlock add process` to create an app-owned supervisor and
169
+ `src/app/processes/devtools.ts`, exporting `processPanel = { list: () =>
170
+ processes.list() }`. Devtools discovers this registration only in development.
171
+ The **Processes** tab refreshes `GET /__warlock/api/processes`: PIDs, workload
172
+ categories, labels, display commands, limits, enforcement, and peak memory.
173
+ It excludes ownership tokens. Without registration the list is empty. The
174
+ endpoint retains development/loopback guards and provides no stop action.
175
+ Use the `supervise-processes` skill of `@warlock.js/process` for launch,
176
+ recovery and stop APIs; configure one owner/registry per application instance.
package/ui/app.css CHANGED
@@ -361,3 +361,16 @@ details summary {
361
361
  grid-template-columns: 120px 1fr;
362
362
  }
363
363
  }
364
+
365
+ .ai-span {
366
+ margin: 0.75rem 0 0.75rem 0.75rem;
367
+ padding-left: 0.75rem;
368
+ border-left: 2px solid currentColor;
369
+ }
370
+ .ai-span summary {
371
+ cursor: pointer;
372
+ }
373
+ .toolbar input {
374
+ flex: 1;
375
+ min-width: 12rem;
376
+ }