@warlock.js/devtools 5.32.0 → 5.34.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 +9 -12
- 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-CUdjfOkO.cjs → server-BNlyhwiR.cjs} +60 -1
- 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/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/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 +4 -0
- package/esm/types.d.mts.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-CUdjfOkO.cjs.map +0 -1
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.34.0",
|
|
9
|
+
"@warlock.js/cache": "5.34.0",
|
|
10
|
+
"@warlock.js/cascade": "5.34.0",
|
|
11
|
+
"@warlock.js/core": "5.34.0",
|
|
12
|
+
"@warlock.js/logger": "5.34.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.34.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
|
+
}
|
package/ui/app.js
CHANGED
|
@@ -174,7 +174,7 @@ function queryRow(query, requestId) {
|
|
|
174
174
|
return details;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
function requestDetail(request) {
|
|
177
|
+
async function requestDetail(request) {
|
|
178
178
|
const section = el("section", { class: "detail" }, [
|
|
179
179
|
el("h2", {
|
|
180
180
|
text: `${request.method} ${request.path} · ${value(request.status)} · ${value(request.durationMs)} ms`,
|
|
@@ -225,6 +225,7 @@ function requestDetail(request) {
|
|
|
225
225
|
),
|
|
226
226
|
);
|
|
227
227
|
else section.append(empty("No mail captured."));
|
|
228
|
+
section.append(el("h3", { text: "AI runs" }), await requestAiView(request.id));
|
|
228
229
|
return section;
|
|
229
230
|
}
|
|
230
231
|
|
|
@@ -242,11 +243,186 @@ async function requestsView() {
|
|
|
242
243
|
);
|
|
243
244
|
const detail =
|
|
244
245
|
state.selectedRequest && state.details.get(state.selectedRequest)
|
|
245
|
-
? requestDetail(state.details.get(state.selectedRequest))
|
|
246
|
+
? await requestDetail(state.details.get(state.selectedRequest))
|
|
246
247
|
: el("div", { class: "panel" }, empty("Select a request to inspect it."));
|
|
247
248
|
return el("div", { class: "layout" }, [list, detail]);
|
|
248
249
|
}
|
|
249
250
|
|
|
251
|
+
function aiUsage(usage, cost) {
|
|
252
|
+
const cacheSemantics =
|
|
253
|
+
usage.inputIncludesCache === false
|
|
254
|
+
? "input excludes cache tokens"
|
|
255
|
+
: "input includes cache tokens";
|
|
256
|
+
return `${value(usage.input)} input · ${value(usage.output)} output · ${value(usage.cachedTokens)} cache read · ${value(usage.cacheWriteTokens)} cache write · ${cost == null ? "unpriced" : `$${cost.toFixed(6)} USD`} (${cacheSemantics})`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function aiRunSummary(run) {
|
|
260
|
+
return el("div", {}, [
|
|
261
|
+
el("div", {
|
|
262
|
+
class: "row-top",
|
|
263
|
+
text: `${run.name} · ${run.status}${run.incomplete ? " · Incomplete / truncated" : ""} · ${run.duration} ms`,
|
|
264
|
+
}),
|
|
265
|
+
el("div", { class: "meta", text: run.models.join(", ") || "Model unreported" }),
|
|
266
|
+
el("p", { text: aiUsage(run.usage, run.cost) }),
|
|
267
|
+
el("div", {
|
|
268
|
+
class: "meta",
|
|
269
|
+
text: `${run.toolCalls} tool calls · ${run.retries} retries · ${value(run.cacheHits)} provider cache-hit trips · ${run.truncatedTrips} truncated trips · ${run.compactions} applied / ${run.skippedCompactions} skipped compactions · ${run.providerCompactions} provider compactions`,
|
|
270
|
+
}),
|
|
271
|
+
el("div", {
|
|
272
|
+
class: "meta",
|
|
273
|
+
text: `${run.traceId} · ${run.requestId ? `Request ${run.requestId}` : "Background / no request id"}`,
|
|
274
|
+
}),
|
|
275
|
+
]);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function aiGuidance(data) {
|
|
279
|
+
return empty(
|
|
280
|
+
data.message ||
|
|
281
|
+
"Install @warlock.js/ai and @warlock.js/ai-panoptic, restart warlock dev, and enable Panoptic observation.",
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function requestAiView(requestId) {
|
|
286
|
+
try {
|
|
287
|
+
const data = await api(`api/ai?requestId=${encodeURIComponent(requestId)}`);
|
|
288
|
+
if (!data.available) return aiGuidance(data);
|
|
289
|
+
const section = el("div");
|
|
290
|
+
if (!data.runs.length) section.append(empty("No AI runs collected for this request yet."));
|
|
291
|
+
for (const run of data.runs) {
|
|
292
|
+
section.append(
|
|
293
|
+
el(
|
|
294
|
+
"button",
|
|
295
|
+
{
|
|
296
|
+
class: "row",
|
|
297
|
+
type: "button",
|
|
298
|
+
onclick: () => {
|
|
299
|
+
state.selectedAiRun = run.traceId;
|
|
300
|
+
state.aiRequestId = requestId;
|
|
301
|
+
setTab("ai");
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
aiRunSummary(run),
|
|
305
|
+
),
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
return section;
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return errorView(error);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function aiSpan(span, rootStartedAt) {
|
|
315
|
+
const incomplete =
|
|
316
|
+
span.status === "incomplete" ||
|
|
317
|
+
span.error?.type === "AgentIncompleteError" ||
|
|
318
|
+
span.error?.type === "AGENT_INCOMPLETE";
|
|
319
|
+
const start = Date.parse(span.startedAt) - Date.parse(rootStartedAt);
|
|
320
|
+
const section = el("details", { class: "ai-span", open: "" }, [
|
|
321
|
+
el("summary", {
|
|
322
|
+
text: `${span.type} ${span.name} · ${span.status}${incomplete ? " · Incomplete / truncated" : ""} · +${start} ms · ${span.duration} ms`,
|
|
323
|
+
}),
|
|
324
|
+
el("p", { class: "meta", text: `${span.startedAt} → ${span.endedAt}` }),
|
|
325
|
+
el("pre", {
|
|
326
|
+
text: JSON.stringify(
|
|
327
|
+
{ usage: span.usage, attributes: span.attributes, error: span.error },
|
|
328
|
+
null,
|
|
329
|
+
2,
|
|
330
|
+
),
|
|
331
|
+
}),
|
|
332
|
+
]);
|
|
333
|
+
span.children.forEach((child) => section.append(aiSpan(child, rootStartedAt)));
|
|
334
|
+
return section;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function aiView() {
|
|
338
|
+
const section = el("section", { class: "detail" }, el("h2", { text: "AI runs" }));
|
|
339
|
+
const input = el("input", {
|
|
340
|
+
type: "text",
|
|
341
|
+
placeholder: "HTTP request id (empty for all runs)",
|
|
342
|
+
"aria-label": "Filter AI runs by HTTP request id",
|
|
343
|
+
value: state.aiRequestId || "",
|
|
344
|
+
});
|
|
345
|
+
section.append(
|
|
346
|
+
el(
|
|
347
|
+
"form",
|
|
348
|
+
{
|
|
349
|
+
class: "toolbar",
|
|
350
|
+
onsubmit: (event) => {
|
|
351
|
+
event.preventDefault();
|
|
352
|
+
state.aiRequestId = input.value;
|
|
353
|
+
state.selectedAiRun = undefined;
|
|
354
|
+
render();
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
[input, el("button", { type: "submit", text: "Filter / refresh" })],
|
|
358
|
+
),
|
|
359
|
+
);
|
|
360
|
+
const data = await api(
|
|
361
|
+
`api/ai${state.aiRequestId ? `?requestId=${encodeURIComponent(state.aiRequestId)}` : ""}`,
|
|
362
|
+
);
|
|
363
|
+
if (!data.available) {
|
|
364
|
+
section.append(aiGuidance(data));
|
|
365
|
+
return section;
|
|
366
|
+
}
|
|
367
|
+
if (!data.runs.length) {
|
|
368
|
+
section.append(
|
|
369
|
+
empty(
|
|
370
|
+
"No AI runs yet. Enable Panoptic observation and execute a flow; runs appear when collection completes.",
|
|
371
|
+
),
|
|
372
|
+
);
|
|
373
|
+
return section;
|
|
374
|
+
}
|
|
375
|
+
const costs = data.runs.filter((run) => run.cost != null);
|
|
376
|
+
section.append(
|
|
377
|
+
el("p", {
|
|
378
|
+
text: `${data.aggregate.traces} runs · ${data.aggregate.usage.input} input tokens · ${data.aggregate.usage.output} output tokens · ${costs.length ? `$${costs.reduce((sum, run) => sum + run.cost, 0).toFixed(6)} USD (reported cost)` : "unpriced"}`,
|
|
379
|
+
}),
|
|
380
|
+
);
|
|
381
|
+
const list = el("div", { class: "panel list" });
|
|
382
|
+
for (const run of data.runs)
|
|
383
|
+
list.append(
|
|
384
|
+
el(
|
|
385
|
+
"button",
|
|
386
|
+
{
|
|
387
|
+
class: `row${run.traceId === state.selectedAiRun ? " is-selected" : ""}`,
|
|
388
|
+
type: "button",
|
|
389
|
+
onclick: () => {
|
|
390
|
+
state.selectedAiRun = run.traceId;
|
|
391
|
+
render();
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
aiRunSummary(run),
|
|
395
|
+
),
|
|
396
|
+
);
|
|
397
|
+
const detail = el("div", { class: "panel detail" });
|
|
398
|
+
const selected = data.runs.find((run) => run.traceId === state.selectedAiRun);
|
|
399
|
+
if (selected) {
|
|
400
|
+
detail.append(aiRunSummary(selected));
|
|
401
|
+
if (selected.requestId)
|
|
402
|
+
detail.append(
|
|
403
|
+
el("button", {
|
|
404
|
+
type: "button",
|
|
405
|
+
text: "Open HTTP request",
|
|
406
|
+
onclick: async () => {
|
|
407
|
+
state.tab = "requests";
|
|
408
|
+
tabs.forEach((button) =>
|
|
409
|
+
button.classList.toggle("is-active", button.dataset.tab === "requests"),
|
|
410
|
+
);
|
|
411
|
+
await showRequest(selected.requestId);
|
|
412
|
+
},
|
|
413
|
+
}),
|
|
414
|
+
);
|
|
415
|
+
try {
|
|
416
|
+
const trace = await api(`api/ai/runs/${encodeURIComponent(selected.traceId)}`);
|
|
417
|
+
detail.append(aiSpan(trace.root, trace.startedAt));
|
|
418
|
+
} catch (error) {
|
|
419
|
+
detail.append(errorView(error));
|
|
420
|
+
}
|
|
421
|
+
} else detail.append(empty("Select a run to inspect tools, retries and compaction metadata."));
|
|
422
|
+
section.append(el("div", { class: "layout" }, [list, detail]));
|
|
423
|
+
return section;
|
|
424
|
+
}
|
|
425
|
+
|
|
250
426
|
function queriesView() {
|
|
251
427
|
const requests = [...state.details.values()].sort((a, b) => b.startedAt - a.startedAt);
|
|
252
428
|
// This session-only tab intentionally lists queries from request details fetched so far.
|
|
@@ -535,23 +711,42 @@ function apiView() {
|
|
|
535
711
|
return section;
|
|
536
712
|
}
|
|
537
713
|
|
|
714
|
+
let renderVersion = 0;
|
|
715
|
+
|
|
716
|
+
async function processesView() {
|
|
717
|
+
const rows = await api("api/processes");
|
|
718
|
+
const section = el("section", { class: "card detail" }, [el("h2", { text: "Processes" })]);
|
|
719
|
+
section.append(el("button", { text: "Refresh", onclick: () => render() }));
|
|
720
|
+
if (!rows.length)
|
|
721
|
+
section.append(empty("No supervised processes. Configure with warlock add process."));
|
|
722
|
+
for (const row of rows) {
|
|
723
|
+
section.append(el("pre", { text: JSON.stringify(row, null, 2) }));
|
|
724
|
+
}
|
|
725
|
+
return section;
|
|
726
|
+
}
|
|
727
|
+
|
|
538
728
|
async function render() {
|
|
729
|
+
const version = ++renderVersion;
|
|
539
730
|
try {
|
|
540
731
|
const view =
|
|
541
|
-
state.tab === "
|
|
542
|
-
? await
|
|
543
|
-
: state.tab === "
|
|
544
|
-
?
|
|
545
|
-
: state.tab === "
|
|
546
|
-
?
|
|
547
|
-
: state.tab === "
|
|
548
|
-
? await
|
|
549
|
-
: state.tab === "
|
|
550
|
-
? await
|
|
551
|
-
:
|
|
552
|
-
|
|
732
|
+
state.tab === "ai"
|
|
733
|
+
? await aiView()
|
|
734
|
+
: state.tab === "requests"
|
|
735
|
+
? await requestsView()
|
|
736
|
+
: state.tab === "queries"
|
|
737
|
+
? queriesView()
|
|
738
|
+
: state.tab === "mail"
|
|
739
|
+
? await mailView()
|
|
740
|
+
: state.tab === "logs"
|
|
741
|
+
? await logsView()
|
|
742
|
+
: state.tab === "routes"
|
|
743
|
+
? await routesView()
|
|
744
|
+
: state.tab === "processes"
|
|
745
|
+
? await processesView()
|
|
746
|
+
: apiView();
|
|
747
|
+
if (version === renderVersion) app.replaceChildren(view);
|
|
553
748
|
} catch (error) {
|
|
554
|
-
app.replaceChildren(errorView(error));
|
|
749
|
+
if (version === renderVersion) app.replaceChildren(errorView(error));
|
|
555
750
|
}
|
|
556
751
|
}
|
|
557
752
|
|
|
@@ -562,16 +757,18 @@ document.querySelector("#clear").addEventListener("click", async () => {
|
|
|
562
757
|
state.requests = [];
|
|
563
758
|
state.details.clear();
|
|
564
759
|
state.selectedRequest = undefined;
|
|
760
|
+
state.selectedAiRun = undefined;
|
|
565
761
|
render();
|
|
566
762
|
} catch (error) {
|
|
567
763
|
app.prepend(errorView(error));
|
|
568
764
|
}
|
|
569
765
|
});
|
|
570
766
|
const events = new EventSource("api/stream");
|
|
571
|
-
for (const type of ["request", "mail", "log"])
|
|
767
|
+
for (const type of ["request", "mail", "log", "ai"])
|
|
572
768
|
events.addEventListener(type, (event) => {
|
|
573
769
|
try {
|
|
574
770
|
const data = JSON.parse(event.data);
|
|
771
|
+
if (type === "ai" && (state.tab === "ai" || state.tab === "requests")) render();
|
|
575
772
|
if (type === "request") {
|
|
576
773
|
state.requests = [data, ...state.requests.filter((item) => item.id !== data.id)].slice(
|
|
577
774
|
0,
|
package/ui/index.html
CHANGED
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
<h1>Warlock Devtools</h1>
|
|
12
12
|
<nav aria-label="Dashboard sections">
|
|
13
13
|
<button class="tab is-active" data-tab="requests">Requests</button>
|
|
14
|
+
<button class="tab" data-tab="ai">AI</button>
|
|
14
15
|
<button class="tab" data-tab="queries">Queries</button>
|
|
15
16
|
<button class="tab" data-tab="mail">Mail</button>
|
|
16
17
|
<button class="tab" data-tab="logs">Logs</button>
|
|
17
18
|
<button class="tab" data-tab="routes">Routes</button>
|
|
19
|
+
<button class="tab" data-tab="processes">Processes</button>
|
|
18
20
|
<button class="tab" data-tab="api">API</button>
|
|
19
21
|
</nav>
|
|
20
22
|
<button id="clear" class="clear" type="button">Clear</button>
|