@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.
- package/CHANGELOG.md +15 -0
- package/cjs/ai-source-lia4YS30.cjs +212 -0
- package/cjs/ai-source-lia4YS30.cjs.map +1 -0
- package/cjs/index.cjs +2 -727
- package/cjs/{server-CQ0qhO8c.cjs → server-BNlyhwiR.cjs} +62 -3
- package/cjs/server-BNlyhwiR.cjs.map +1 -0
- package/cjs/src-CiNStw0i.cjs +699 -0
- package/cjs/src-CiNStw0i.cjs.map +1 -0
- package/esm/caller-location.mjs.map +1 -1
- package/esm/devtools-connector.d.mts +2 -4
- package/esm/devtools-connector.d.mts.map +1 -1
- package/esm/devtools-connector.mjs +6 -0
- package/esm/devtools-connector.mjs.map +1 -1
- package/esm/index.d.mts +2 -1
- package/esm/process-panel.mjs +16 -0
- package/esm/process-panel.mjs.map +1 -0
- package/esm/server/ai-routes.mjs +32 -0
- package/esm/server/ai-routes.mjs.map +1 -0
- package/esm/server/guard.mjs +1 -1
- package/esm/server/guard.mjs.map +1 -1
- package/esm/server/index.mjs +4 -0
- package/esm/server/index.mjs.map +1 -1
- package/esm/server/process-routes.mjs +11 -0
- package/esm/server/process-routes.mjs.map +1 -0
- package/esm/server/requests-routes.mjs +1 -0
- package/esm/server/requests-routes.mjs.map +1 -1
- package/esm/server/stream-route.mjs +4 -0
- package/esm/server/stream-route.mjs.map +1 -1
- package/esm/sources/ai-source.d.mts +29 -0
- package/esm/sources/ai-source.d.mts.map +1 -0
- package/esm/sources/ai-source.mjs +136 -0
- package/esm/sources/ai-source.mjs.map +1 -0
- package/esm/sources/log-source.mjs +1 -1
- package/esm/sources/query-source.mjs +1 -1
- package/esm/types.d.mts +45 -30
- package/esm/types.d.mts.map +1 -1
- package/esm/ui-root.mjs.map +1 -1
- package/llms-full.txt +85 -1
- package/llms.txt +1 -1
- package/package.json +9 -5
- package/skills/devtools-overview/SKILL.md +85 -1
- package/ui/app.css +13 -0
- package/ui/app.js +213 -16
- package/ui/index.html +2 -0
- package/cjs/index.cjs.map +0 -1
- 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. */
|
|
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. */
|
|
49
|
+
export type DevtoolsQuery = {
|
|
50
|
+
/** Unique within the collector. */
|
|
51
|
+
id: string;
|
|
49
52
|
connection: string;
|
|
50
53
|
driver: "postgres" | "mongodb";
|
|
51
|
-
sql?: string;
|
|
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;
|
|
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;
|
|
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;
|
|
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`. */
|
|
124
|
+
export type DevtoolsRequest = {
|
|
125
|
+
/** The framework's `request.id`. */
|
|
126
|
+
id: string;
|
|
119
127
|
method: string;
|
|
120
|
-
path: string;
|
|
128
|
+
path: string;
|
|
129
|
+
/** Matched route pattern, e.g. `/posts/:slug`. */
|
|
121
130
|
route?: string;
|
|
122
131
|
status?: number;
|
|
123
|
-
startedAt: number;
|
|
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[];
|
|
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;
|
|
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;
|
|
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
|
package/esm/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/devtools/src/types.ts"],"mappings":"
|
|
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"}
|
package/esm/ui-root.mjs.map
CHANGED
|
@@ -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,
|
|
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/
|
|
9
|
-
"@warlock.js/
|
|
10
|
-
"@warlock.js/
|
|
11
|
-
"@warlock.js/
|
|
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.
|
|
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
|
+
}
|