@ai-setting/roy-plugin-task-show 0.4.0 → 0.5.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/README.md +266 -218
- package/dist/collector.d.ts +11 -0
- package/dist/collector.d.ts.map +1 -1
- package/dist/collector.js +15 -0
- package/dist/collector.js.map +1 -1
- package/dist/event-bus.d.ts +98 -0
- package/dist/event-bus.d.ts.map +1 -0
- package/dist/event-bus.js +210 -0
- package/dist/event-bus.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +67 -64
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +234 -148
- package/dist/plugin.js.map +1 -1
- package/dist/server.d.ts +16 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +104 -53
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +78 -60
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +14 -21
- package/dist/types.js.map +1 -1
- package/dist/url-injector.d.ts +13 -48
- package/dist/url-injector.d.ts.map +1 -1
- package/dist/url-injector.js +13 -58
- package/dist/url-injector.js.map +1 -1
- package/package.json +1 -1
- package/plugin.json +33 -11
- package/public/app.js +409 -57
- package/public/index.html +28 -10
- package/public/style.css +49 -1
package/README.md
CHANGED
|
@@ -1,35 +1,41 @@
|
|
|
1
1
|
# roy-plugin-task-show
|
|
2
2
|
|
|
3
3
|
> A [roy-agent](https://example/roy-agent) plugin that visualizes the tool-call
|
|
4
|
-
> chain of every task on a local web service
|
|
5
|
-
>
|
|
4
|
+
> chain of every task on a local web service with **real-time Server-Sent
|
|
5
|
+
> Events** for live page updates.
|
|
6
6
|
|
|
7
7
|
## What it does
|
|
8
8
|
|
|
9
9
|
When loaded into a `roy-agent` host, this plugin:
|
|
10
10
|
|
|
11
11
|
1. Listens to the `tool:before.execute`, `tool:after.execute`,
|
|
12
|
-
`task:after.
|
|
13
|
-
hook points.
|
|
12
|
+
`task:before.create`, `task:after.create`, `task:after.complete`
|
|
13
|
+
(preferred) and `task:after.update` (legacy fallback) hook points.
|
|
14
14
|
2. Records every tool invocation (tool name, args, result preview, duration,
|
|
15
15
|
success flag, timestamp) into an in-memory collector keyed by task id.
|
|
16
|
-
3.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
`
|
|
16
|
+
3. **Pushes every state change over a Server-Sent Events stream** served by
|
|
17
|
+
the plugin's local HTTP service. The frontend subscribes to
|
|
18
|
+
`GET /api/events` and re-renders the page within milliseconds of:
|
|
19
|
+
- a new task being created (`task.created`)
|
|
20
|
+
- a tool call being recorded (`tool.recorded`)
|
|
21
|
+
- a task status changing (`task.updated`)
|
|
22
|
+
- a task transitioning to a terminal status (`task.completed`)
|
|
22
23
|
4. Serves the visualization on a local HTTP server (default
|
|
23
|
-
`http://127.0.0.1:7788/`)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
>
|
|
24
|
+
`http://127.0.0.1:7788/`) — index page, per-task detail page, and the
|
|
25
|
+
`/api/events` SSE stream.
|
|
26
|
+
|
|
27
|
+
The result: every task gets a clickable link the user can open in a
|
|
28
|
+
browser. Once open, the page **stays in sync** with the running agent
|
|
29
|
+
loop — tool calls appear as they happen, status badges update in
|
|
30
|
+
real-time, and the progress bar grows with each call. No manual refresh
|
|
31
|
+
needed.
|
|
32
|
+
|
|
33
|
+
> **v0.5.0+** — the plugin no longer uses `env.notify`. The previous
|
|
34
|
+
> `env.notify({type:"visualization_ready"})` mechanism has been replaced
|
|
35
|
+
> with an in-process event bus + SSE stream. This makes the plugin work
|
|
36
|
+
> with **any** roy-agent host (no NotificationChannel required) and gives
|
|
37
|
+
> the frontend proper incremental updates instead of just a one-shot URL
|
|
38
|
+
> at the end. See [SSE Migration (v0.5.0+)](#sse-migration-v050) for the
|
|
33
39
|
> migration guide.
|
|
34
40
|
|
|
35
41
|
## Repository layout
|
|
@@ -46,17 +52,20 @@ roy-plugin-task-show/
|
|
|
46
52
|
│ └── app.js
|
|
47
53
|
├── src/
|
|
48
54
|
│ ├── index.ts ← public API (re-exports)
|
|
49
|
-
│ ├── types.ts ← shared types & default config
|
|
55
|
+
│ ├── types.ts ← shared types & default config + TaskEvent
|
|
50
56
|
│ ├── collector.ts ← in-memory tool-call collector
|
|
51
|
-
│ ├── server.ts ← tiny standalone Node http server
|
|
52
|
-
│ ├──
|
|
57
|
+
│ ├── server.ts ← tiny standalone Node http server + SSE endpoint
|
|
58
|
+
│ ├── event-bus.ts ← SSE event bus (broadcast / subscribe / keep-alive)
|
|
59
|
+
│ ├── url-injector.ts ← URL builders (no more mutation helpers)
|
|
53
60
|
│ ├── plugin.ts ← the TaskShowPlugin class (BasePlugin-compatible)
|
|
54
61
|
│ └── core-stub.d.ts ← type stub for the optional core peer dep
|
|
55
62
|
├── test/
|
|
56
63
|
│ ├── collector.test.ts
|
|
57
64
|
│ ├── url-injector.test.ts
|
|
58
|
-
│ ├── server.test.ts
|
|
59
|
-
│
|
|
65
|
+
│ ├── server.test.ts ← includes SSE endpoint test
|
|
66
|
+
│ ├── plugin.test.ts ← covers all six hooks + broadcast assertions
|
|
67
|
+
│ ├── plugin-sse.test.ts ← dedicated SSE event-bus consumer tests
|
|
68
|
+
│ └── integration.test.ts ← full lifecycle through HTTP + SSE
|
|
60
69
|
└── scripts/
|
|
61
70
|
├── verify-service.ts ← end-to-end check (port 7788 + curl pages)
|
|
62
71
|
└── run-demo.ts ← demo that simulates a real task lifecycle
|
|
@@ -134,85 +143,187 @@ await taskShow.dispose();
|
|
|
134
143
|
|
|
135
144
|
The plugin registers hooks for:
|
|
136
145
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
| Hook point | Priority | Purpose |
|
|
147
|
+
| ---------------------- | -------- | ------------------------------------------------------------------------ |
|
|
148
|
+
| `tool:before.execute` | 40 | Records per-call start timestamp for accurate `durationMs` |
|
|
149
|
+
| `tool:after.execute` | 50 | Records the call into the collector and broadcasts `tool.recorded` |
|
|
150
|
+
| `task:before.create` | 55 | Mints a fresh `TaskSession` and broadcasts `task.created` |
|
|
151
|
+
| `task:after.create` | 55 | Same as above (fallback for hosts that only emit `task:after.create`) |
|
|
152
|
+
| `task:after.complete` | 60 | **PREFERRED** — broadcasts `task.completed` on terminal transition |
|
|
153
|
+
| `task:after.update` | 60 | Legacy fallback; broadcasts `task.updated` or `task.completed` |
|
|
154
|
+
|
|
155
|
+
When both `task:after.complete` AND `task:after.update` fire (newer
|
|
156
|
+
hosts do this on completion), the handler dedupes — at most one
|
|
157
|
+
`task.completed` event per task.
|
|
145
158
|
|
|
146
159
|
## Configuration
|
|
147
160
|
|
|
148
161
|
All config lives in `plugin.json` (and is overridable at construction time):
|
|
149
162
|
|
|
150
|
-
| key
|
|
151
|
-
|
|
|
152
|
-
| `port`
|
|
153
|
-
| `host`
|
|
154
|
-
| `autoStart`
|
|
155
|
-
| `maxStoredTasks`
|
|
156
|
-
| `
|
|
157
|
-
|
|
163
|
+
| key | type | default | description |
|
|
164
|
+
| ---------------- | -------- | ----------- | -------------------------------------------------------- |
|
|
165
|
+
| `port` | number | `7788` | HTTP port for the visualization service. |
|
|
166
|
+
| `host` | string | `127.0.0.1` | HTTP host. Use `0.0.0.0` to allow LAN access. |
|
|
167
|
+
| `autoStart` | boolean | `true` | Start the HTTP server when `init()` is called. |
|
|
168
|
+
| `maxStoredTasks` | number | `50` | Max sessions kept in memory. Older ones are evicted. |
|
|
169
|
+
| `publicDir` | string | `public` | Directory holding the static frontend. |
|
|
170
|
+
|
|
171
|
+
> **v0.5.0**: the previous `urlInjectEnabled` config was removed. The
|
|
172
|
+
> plugin no longer mutates tool result output.
|
|
158
173
|
|
|
159
174
|
Override at construction time:
|
|
160
175
|
|
|
161
176
|
```ts
|
|
162
177
|
createTaskShowPlugin({
|
|
163
178
|
port: 9000,
|
|
164
|
-
|
|
179
|
+
maxStoredTasks: 100,
|
|
165
180
|
});
|
|
166
181
|
```
|
|
167
182
|
|
|
168
183
|
## HTTP API
|
|
169
184
|
|
|
170
|
-
| route | description
|
|
171
|
-
| --------------------------- |
|
|
172
|
-
| `GET /` | Index of recent task sessions.
|
|
173
|
-
| `GET /task/:taskId` | Per-task page with mermaid flow + tool-call table.
|
|
174
|
-
| `GET /api/sessions` | JSON list of all sessions (newest first).
|
|
175
|
-
| `GET /api/sessions/:taskId` | JSON detail of one session (full payload).
|
|
176
|
-
|
|
|
185
|
+
| route | description |
|
|
186
|
+
| --------------------------- | ------------------------------------------------------------ |
|
|
187
|
+
| `GET /` | Index of recent task sessions. |
|
|
188
|
+
| `GET /task/:taskId` | Per-task page with mermaid flow + tool-call table. |
|
|
189
|
+
| `GET /api/sessions` | JSON list of all sessions (newest first). |
|
|
190
|
+
| `GET /api/sessions/:taskId` | JSON detail of one session (full payload). |
|
|
191
|
+
| **`GET /api/events`** | **SSE stream** — see [Server-Sent Events](#server-sent-events). |
|
|
192
|
+
| `GET /static/*` | Static frontend assets (`style.css`, `app.js`). |
|
|
177
193
|
|
|
178
194
|
If the configured `port` is already in use, the service falls back to an OS
|
|
179
195
|
assigned port and logs the actual URL to stderr.
|
|
180
196
|
|
|
197
|
+
## Server-Sent Events
|
|
198
|
+
|
|
199
|
+
The local HTTP service exposes `GET /api/events` as a standard Server-Sent
|
|
200
|
+
Events stream. Every connected client receives:
|
|
201
|
+
|
|
202
|
+
1. An initial `snapshot` frame carrying the full list of currently-known
|
|
203
|
+
sessions — useful for instant render on connect / reconnect.
|
|
204
|
+
2. A `task.created` frame whenever a new task is opened
|
|
205
|
+
(`task:before.create` or `task:after.create`).
|
|
206
|
+
3. A `tool.recorded` frame after each tool call
|
|
207
|
+
(`tool:after.execute`).
|
|
208
|
+
4. A `task.updated` frame on any non-terminal status transition
|
|
209
|
+
(`task:after.update` with `running` / `paused` / etc.).
|
|
210
|
+
5. A `task.completed` frame on terminal transition
|
|
211
|
+
(`task:after.complete` or `task:after.update` with terminal status).
|
|
212
|
+
|
|
213
|
+
A 15-second keep-alive comment (`:keep-alive`) prevents reverse proxies /
|
|
214
|
+
load balancers from dropping the connection.
|
|
215
|
+
|
|
216
|
+
### Event shape
|
|
217
|
+
|
|
218
|
+
Every event has the same envelope:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
interface TaskEvent<T> {
|
|
222
|
+
type: "task.created" | "task.updated" | "task.completed" | "tool.recorded";
|
|
223
|
+
taskId: number;
|
|
224
|
+
timestamp: number; // Unix ms
|
|
225
|
+
pluginVersion: string; // "0.5.0"
|
|
226
|
+
data: T;
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The `data` payload differs per `type`:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
// task.created
|
|
234
|
+
data: { session: TaskSession }
|
|
235
|
+
|
|
236
|
+
// task.updated
|
|
237
|
+
data: { session: TaskSession, newStatus: TaskStatus, previousStatus?: TaskStatus }
|
|
238
|
+
|
|
239
|
+
// task.completed
|
|
240
|
+
data: { session: TaskSession, terminalStatus: TaskStatus }
|
|
241
|
+
|
|
242
|
+
// tool.recorded
|
|
243
|
+
data: { session: TaskSession, toolCall: ToolCallRecord }
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Subscribing from the browser
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
const source = new EventSource("/api/events");
|
|
250
|
+
source.addEventListener("snapshot", (ev) => {
|
|
251
|
+
const data = JSON.parse(ev.data);
|
|
252
|
+
console.log("initial sessions:", data.data.sessions);
|
|
253
|
+
});
|
|
254
|
+
source.addEventListener("task.created", (ev) => {
|
|
255
|
+
const data = JSON.parse(ev.data);
|
|
256
|
+
console.log("new task:", data.data.session.taskId);
|
|
257
|
+
});
|
|
258
|
+
source.addEventListener("tool.recorded", (ev) => {
|
|
259
|
+
const data = JSON.parse(ev.data);
|
|
260
|
+
console.log("tool call:", data.data.toolCall.toolName);
|
|
261
|
+
});
|
|
262
|
+
source.addEventListener("task.completed", (ev) => {
|
|
263
|
+
const data = JSON.parse(ev.data);
|
|
264
|
+
console.log("task done:", data.data.terminalStatus);
|
|
265
|
+
});
|
|
266
|
+
// EventSource auto-reconnects on connection drop. If SSE is unavailable
|
|
267
|
+
// (e.g. ancient browser), the bundled frontend falls back to a 3-second
|
|
268
|
+
// poll of /api/sessions.
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Subscribing from Node / custom adapter
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import http from "node:http";
|
|
275
|
+
|
|
276
|
+
http.get("http://127.0.0.1:7788/api/events", (res) => {
|
|
277
|
+
let buf = "";
|
|
278
|
+
res.on("data", (chunk) => {
|
|
279
|
+
buf += chunk.toString("utf-8");
|
|
280
|
+
let idx;
|
|
281
|
+
while ((idx = buf.indexOf("\n\n")) !== -1) {
|
|
282
|
+
const frame = buf.slice(0, idx);
|
|
283
|
+
buf = buf.slice(idx + 2);
|
|
284
|
+
// frame is `event: <type>\ndata: <json>`
|
|
285
|
+
// ...
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
181
291
|
## Architecture
|
|
182
292
|
|
|
183
|
-
The plugin runs
|
|
184
|
-
|
|
293
|
+
The plugin runs entirely inside its local HTTP server. The hook handlers
|
|
294
|
+
emit events into an in-process `EventBus` (`src/event-bus.ts`), and every
|
|
295
|
+
connected SSE client receives a frame. No host-side NotificationChannel
|
|
296
|
+
plumbing is required.
|
|
185
297
|
|
|
186
298
|
```
|
|
187
|
-
TaskShowPlugin (task
|
|
299
|
+
TaskShowPlugin (tool/task hooks)
|
|
188
300
|
│
|
|
189
|
-
│
|
|
301
|
+
│ eventBus.broadcast({type, taskId, timestamp, data})
|
|
190
302
|
▼
|
|
191
|
-
|
|
303
|
+
EventBus (in-process subscriber Set + keep-alive loop)
|
|
192
304
|
│
|
|
305
|
+
│ write frame to every connected http.ServerResponse
|
|
193
306
|
▼
|
|
194
|
-
|
|
307
|
+
TaskShowServer.handleSSE
|
|
195
308
|
│
|
|
196
|
-
│
|
|
197
|
-
│ type: "plugin.notification.visualization_ready",
|
|
198
|
-
│ metadata: { source: "plugin:roy-plugin-task-show", ...pluginMetadata },
|
|
199
|
-
│ payload: { title, content },
|
|
200
|
-
│ })
|
|
309
|
+
│ Content-Type: text/event-stream
|
|
201
310
|
▼
|
|
202
|
-
|
|
311
|
+
Browser EventSource ◄──── 3-second polling fallback if SSE unavailable
|
|
203
312
|
│
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
└──► custom sinks → log, metrics, etc.
|
|
313
|
+
▼
|
|
314
|
+
DOM updates (badge / progress / timeline / mermaid re-render)
|
|
207
315
|
```
|
|
208
316
|
|
|
209
|
-
The
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
317
|
+
The in-process bus also exposes `addListener(fn)` so custom callers
|
|
318
|
+
(CLI tools, IM adapters, log shippers) can subscribe to the same event
|
|
319
|
+
stream without going through HTTP:
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
const plugin = createTaskShowPlugin();
|
|
323
|
+
plugin.getEventBus().addListener((event) => {
|
|
324
|
+
console.log("event:", event.type, event.taskId);
|
|
325
|
+
});
|
|
326
|
+
```
|
|
216
327
|
|
|
217
328
|
### Plugin ↔ host version correspondence
|
|
218
329
|
|
|
@@ -221,143 +332,66 @@ that match its audience.
|
|
|
221
332
|
| `0.1.0` | pre-2026-07-10 (`task:after.update`) | Mutates tool result. **Removed** in v0.4.0 plugin. |
|
|
222
333
|
| `0.2.0` | pre-2026-07-10 (`task:after.update`) | Same as 0.1.0; tiny refactors. |
|
|
223
334
|
| `0.3.0` | `>= 2026-07-10` | Adds `tool:before.execute` for self-managed timing. |
|
|
224
|
-
| `0.4.0`
|
|
225
|
-
|
|
226
|
-
The host's `PluginEnv.notify` method is **optional** for the plugin — it
|
|
227
|
-
degrades gracefully on older hosts (logs a one-shot warn, continues
|
|
228
|
-
serving the visualization page). The plugin therefore remains
|
|
229
|
-
installable on any host version, but newer features only fire on
|
|
230
|
-
hosts implementing the v0.4.0+ contract.
|
|
335
|
+
| `0.4.0` | `>= 2026-07-10` (NotificationChannel) | Switches to `env.notify` consumer; no longer mutates output. |
|
|
336
|
+
| **`0.5.0` (this)** | **`>= 2026-07-10`** | **Adds `task:before.create` / `task:after.create`; switches from `env.notify` to in-process EventBus + SSE.** |
|
|
231
337
|
|
|
232
338
|
### Host design reference
|
|
233
339
|
|
|
234
340
|
The full host-side design — including the `NotificationChannel`
|
|
235
|
-
interface
|
|
236
|
-
`LarkCliNotificationChannel` (legacy, `@deprecated`) — is documented
|
|
341
|
+
interface and `EventBusNotificationChannel` (default) — is documented
|
|
237
342
|
in the host repository:
|
|
238
343
|
|
|
239
|
-
>
|
|
240
|
-
> version: feature/plugin-emit-notification-v2
|
|
241
|
-
> (commits `f96508f4` → `9ec78817`)
|
|
242
|
-
|
|
243
|
-
If you're integrating the plugin with a custom host, that document is
|
|
244
|
-
the authoritative reference for the `NotificationOptions` contract
|
|
245
|
-
and the metadata merge semantics.
|
|
246
|
-
|
|
247
|
-
## Notification payload (v0.4.0+)
|
|
248
|
-
|
|
249
|
-
When a task reaches a terminal status, the plugin calls
|
|
250
|
-
`env.notify(...)` with a `visualization_ready` payload. The host's
|
|
251
|
-
`NotificationChannel` (default: `EventBusNotificationChannel`) re-emits
|
|
252
|
-
this on the env event bus as `plugin.notification.visualization_ready`.
|
|
253
|
-
|
|
254
|
-
The payload looks like:
|
|
255
|
-
|
|
256
|
-
```ts
|
|
257
|
-
{
|
|
258
|
-
type: "visualization_ready", // == NOTIFICATION_TYPE_VISUALIZATION_READY
|
|
259
|
-
title: "Task #1234 completed",
|
|
260
|
-
content: [
|
|
261
|
-
"Task: solve the bug",
|
|
262
|
-
"Status: completed",
|
|
263
|
-
"Visualization: http://127.0.0.1:7788/task/1234",
|
|
264
|
-
].join("\n"),
|
|
265
|
-
metadata: {
|
|
266
|
-
taskId: 1234,
|
|
267
|
-
terminalStatus: "completed",
|
|
268
|
-
url: "http://127.0.0.1:7788/task/1234",
|
|
269
|
-
taskTitle: "solve the bug", // when present in the hook payload
|
|
270
|
-
sessionId: "sess-xyz", // when present in the hook payload
|
|
271
|
-
pluginName: "roy-plugin-task-show",
|
|
272
|
-
pluginVersion: "0.4.0", // sync with host v2 (Task #N/A)
|
|
273
|
-
notificationTimestamp: 1718000000000, // captured at emit time (Date.now())
|
|
274
|
-
},
|
|
275
|
-
}
|
|
276
|
-
```
|
|
344
|
+
>
|
|
277
345
|
|
|
278
|
-
|
|
279
|
-
event with the plugin that emitted it (useful when multiple versions
|
|
280
|
-
of the same plugin are installed in different workspaces). The
|
|
281
|
-
`notificationTimestamp` is captured at emit time and is distinct from
|
|
282
|
-
the host's `event.timestamp` (which the host stamps at
|
|
283
|
-
`pushEnvEvent` call time) — useful for measuring queue / delivery
|
|
284
|
-
latency downstream.
|
|
346
|
+
## SSE Migration (v0.5.0+)
|
|
285
347
|
|
|
286
|
-
|
|
348
|
+
> **TL;DR**: stop depending on `env.notify`. The plugin now pushes events
|
|
349
|
+
> over SSE. Connect with `new EventSource('/api/events')` (browser) or
|
|
350
|
+
> `http.get(...)` (Node).
|
|
287
351
|
|
|
288
|
-
|
|
289
|
-
env event bus for `plugin.notification.visualization_ready`. Example
|
|
290
|
-
(in the host, e.g. `packages/cli`):
|
|
352
|
+
### Before (v0.4.0)
|
|
291
353
|
|
|
292
354
|
```ts
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
env.
|
|
296
|
-
|
|
297
|
-
(event) => {
|
|
298
|
-
const { taskId, url, terminalStatus, taskTitle } = event.metadata;
|
|
299
|
-
// Render the URL in the chat / send a message / open the browser, etc.
|
|
300
|
-
console.log(`📊 Task #${taskId} ${terminalStatus}: ${url}`);
|
|
301
|
-
},
|
|
302
|
-
);
|
|
355
|
+
// Plugin called env.notify({type:"visualization_ready", ...}).
|
|
356
|
+
// Host's EventBusNotificationChannel re-emitted on env event bus:
|
|
357
|
+
// env.pushEnvEvent({type:"plugin.notification.visualization_ready", ...})
|
|
358
|
+
// Subscribers (CLI / IM) listened on the env event bus.
|
|
303
359
|
```
|
|
304
360
|
|
|
305
|
-
|
|
306
|
-
`TaskShowPlugin.getVisualizationUrl(taskId)` for callers that need it
|
|
307
|
-
synchronously.
|
|
308
|
-
|
|
309
|
-
## env.notify Migration (v0.4.0+)
|
|
310
|
-
|
|
311
|
-
> **TL;DR**: stop mutating tool result output. Call `env.notify` instead.
|
|
312
|
-
|
|
313
|
-
### Before (v0.3.0 and earlier)
|
|
361
|
+
### After (v0.5.0+)
|
|
314
362
|
|
|
315
363
|
```ts
|
|
316
|
-
// Plugin
|
|
317
|
-
|
|
318
|
-
|
|
364
|
+
// Plugin emits via in-process EventBus; HTTP server streams frames
|
|
365
|
+
// over /api/events. Frontend connects with EventSource — no host
|
|
366
|
+
// NotificationChannel required.
|
|
367
|
+
const source = new EventSource("/api/events");
|
|
368
|
+
source.addEventListener("task.completed", (ev) => {
|
|
369
|
+
const { taskId, data } = JSON.parse(ev.data);
|
|
370
|
+
console.log(`Task ${taskId} → ${data.terminalStatus}`);
|
|
319
371
|
});
|
|
320
|
-
// ctx.result.output now contains:
|
|
321
|
-
// "...original...\n---\n📊 可视化工具调用链路: <http://...>"
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
### After (v0.4.0+)
|
|
325
|
-
|
|
326
|
-
```ts
|
|
327
|
-
// Plugin calls env.notify; the host's NotificationChannel pipeline
|
|
328
|
-
// re-emits it on the env event bus. ctx.result.output is preserved.
|
|
329
|
-
await plugin.onTaskAfterComplete({
|
|
330
|
-
data: {
|
|
331
|
-
task: { id: 1234, status: "completed", title: "demo" },
|
|
332
|
-
terminalStatus: "completed",
|
|
333
|
-
},
|
|
334
|
-
});
|
|
335
|
-
// env.notify was called with { type: "visualization_ready", ... }
|
|
336
|
-
// ctx.result.output is unchanged.
|
|
337
372
|
```
|
|
338
373
|
|
|
339
374
|
### Why we changed
|
|
340
375
|
|
|
341
|
-
1. **
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
3. **
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
376
|
+
1. **No host dependency**: the SSE stream is served by the plugin's own
|
|
377
|
+
HTTP server. Works with any roy-agent host — no NotificationChannel
|
|
378
|
+
required.
|
|
379
|
+
2. **Incremental updates**: instead of one-shot "URL injection" on
|
|
380
|
+
terminal status, the frontend receives a frame for *every*
|
|
381
|
+
meaningful change (created / recorded / updated / completed). The
|
|
382
|
+
page updates as the agent works, not after.
|
|
383
|
+
3. **Progress bar + timeline**: with continuous updates, the frontend
|
|
384
|
+
can show a live progress bar (tool call count) and a real-time
|
|
385
|
+
timeline without manual refresh.
|
|
386
|
+
4. **Simpler host contract**: removed `env.notify` plumbing from the
|
|
387
|
+
plugin surface. The plugin's `PluginEnvLike` type no longer mentions
|
|
388
|
+
`notify`.
|
|
353
389
|
|
|
354
390
|
### Backward compatibility
|
|
355
391
|
|
|
356
|
-
The
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
mutation behavior — useful for test harnesses and very old hosts that
|
|
360
|
-
don't have the `NotificationChannel` abstraction.
|
|
392
|
+
The plugin no longer mutates tool result output (that mechanism was
|
|
393
|
+
removed in v0.4.0). Pin to `roy-plugin-task-show@0.4.x` if you depend
|
|
394
|
+
on the legacy `env.notify` consumer path.
|
|
361
395
|
|
|
362
396
|
## Development
|
|
363
397
|
|
|
@@ -368,7 +402,7 @@ bun run typecheck
|
|
|
368
402
|
# Build (emit dist/)
|
|
369
403
|
bun run build
|
|
370
404
|
|
|
371
|
-
# Run unit tests (
|
|
405
|
+
# Run unit tests (52 tests across 6 files)
|
|
372
406
|
bun test
|
|
373
407
|
|
|
374
408
|
# End-to-end check (boots the service, hits it, asserts response)
|
|
@@ -410,54 +444,68 @@ It also recognizes (in priority order):
|
|
|
410
444
|
If no task id can be recovered, the call lands in a synthetic session so
|
|
411
445
|
the visualization still works.
|
|
412
446
|
|
|
413
|
-
### `task:
|
|
414
|
-
|
|
415
|
-
The plugin accepts the standard `TaskCompleteResultContext`:
|
|
447
|
+
### `task:before.create` (v0.5.0+)
|
|
416
448
|
|
|
417
449
|
```ts
|
|
418
450
|
{
|
|
419
|
-
task: { id: number,
|
|
420
|
-
|
|
421
|
-
changes: Partial<UpdateTaskOptions>,
|
|
422
|
-
sessionId: string,
|
|
423
|
-
tagService: TagService,
|
|
451
|
+
data: { task: { id: number, title?: string } }
|
|
452
|
+
// OR flat: { taskId: number, title?: string }
|
|
424
453
|
}
|
|
425
454
|
```
|
|
426
455
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
below.
|
|
456
|
+
If a session already exists for the task id (re-create / retry), its
|
|
457
|
+
`toolCalls` are preserved.
|
|
430
458
|
|
|
431
|
-
### `task:after.
|
|
459
|
+
### `task:after.create` (v0.5.0+)
|
|
432
460
|
|
|
433
|
-
|
|
434
|
-
|
|
461
|
+
Same payload as `task:before.create`. Useful for hosts that don't emit
|
|
462
|
+
`task:before.create`; the plugin opens the session here and broadcasts
|
|
463
|
+
`task.created`.
|
|
464
|
+
|
|
465
|
+
### `task:after.complete` (preferred, consumed)
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
{
|
|
469
|
+
data: {
|
|
470
|
+
task: { id: number, status: 'completed' | 'failed' | 'cancelled', title?: string },
|
|
471
|
+
terminalStatus: 'completed' | 'failed' | 'cancelled',
|
|
472
|
+
changes: Partial<UpdateTaskOptions>,
|
|
473
|
+
sessionId: string,
|
|
474
|
+
tagService: TagService,
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### `task:after.update` (legacy fallback, consumed)
|
|
435
480
|
|
|
436
481
|
```ts
|
|
437
482
|
{ data: { task: { id: number, status: string, title?: string }, changes, tagService } }
|
|
438
483
|
```
|
|
439
484
|
|
|
440
|
-
The handler filters for terminal status (`completed` / `failed` /
|
|
441
|
-
`
|
|
442
|
-
that supports both hooks),
|
|
485
|
+
The handler filters for terminal status (`completed` / `failed` /
|
|
486
|
+
`cancelled`) and dedupes — if `AFTER_COMPLETE` also fires (it can, on a
|
|
487
|
+
host that supports both hooks), only one `task.completed` event is
|
|
488
|
+
broadcast.
|
|
443
489
|
|
|
444
490
|
## Limitations & follow-ups
|
|
445
491
|
|
|
446
|
-
- **Memory-only**: the collector keeps data in memory only. Restarting
|
|
447
|
-
host process drops everything. Persisting to a file would be a
|
|
448
|
-
follow-up.
|
|
449
|
-
- **Single host**: if you launch multiple `roy-agent` instances on the
|
|
450
|
-
machine, change the `port` to avoid clashes (the service falls
|
|
451
|
-
random port automatically, but the URL won't be
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
- **
|
|
459
|
-
|
|
492
|
+
- **Memory-only**: the collector keeps data in memory only. Restarting
|
|
493
|
+
the host process drops everything. Persisting to a file would be a
|
|
494
|
+
small follow-up.
|
|
495
|
+
- **Single host**: if you launch multiple `roy-agent` instances on the
|
|
496
|
+
same machine, change the `port` to avoid clashes (the service falls
|
|
497
|
+
back to a random port automatically, but the URL won't be
|
|
498
|
+
predictable).
|
|
499
|
+
- **Mermaid CDN**: the frontend loads `mermaid@10` from jsDelivr.
|
|
500
|
+
Air-gapped deployments should vendor a copy locally.
|
|
501
|
+
- **No file persistence**: data lives only in memory. A future
|
|
502
|
+
improvement would be a tiny SQLite sink so a restart still shows
|
|
503
|
+
recent tasks.
|
|
504
|
+
- **SSE-only push**: the bundled frontend uses EventSource as the
|
|
505
|
+
primary update channel. A 3-second polling fallback covers older
|
|
506
|
+
browsers and proxies that strip SSE, but it's a degraded experience.
|
|
507
|
+
WebSocket support is a possible future addition.
|
|
460
508
|
|
|
461
509
|
## License
|
|
462
510
|
|
|
463
|
-
MIT
|
|
511
|
+
MIT
|
package/dist/collector.d.ts
CHANGED
|
@@ -86,6 +86,17 @@ export declare class ToolCallCollector {
|
|
|
86
86
|
* context that happens to know the title, e.g. task:before.create).
|
|
87
87
|
*/
|
|
88
88
|
setTaskTitle(taskId: number, title: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* Insert or replace a session. Used by `task:before.create` /
|
|
91
|
+
* `task:after.create` to mint a fresh TaskSession before any tool call
|
|
92
|
+
* is recorded — gives the frontend a stable taskId/title even for tasks
|
|
93
|
+
* that complete without firing any tool.
|
|
94
|
+
*
|
|
95
|
+
* If a session already exists for the taskId, it is overwritten with the
|
|
96
|
+
* new value (callers should pass a snapshot built from the existing
|
|
97
|
+
* session if they want to preserve toolCalls).
|
|
98
|
+
*/
|
|
99
|
+
upsertSession(session: TaskSession): void;
|
|
89
100
|
/**
|
|
90
101
|
* Clear all data. Useful in tests and when the plugin is disposed.
|
|
91
102
|
*/
|
package/dist/collector.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../src/collector.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,WAAW,EAEX,cAAc,EACf,MAAM,YAAY,CAAC;AA6DpB;;;;;;;;;;;;GAYG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAiB;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAa;gBAGrC,GAAG,EAAE,cAAc,EACnB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAOzD;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAMnD;;OAEG;IACH,YAAY,IAAI,WAAW,EAAE;IAM7B;;OAEG;IACH,IAAI,IAAI,MAAM;IAId;;;;;;;OAOG;IACH,cAAc,CAAC,IAAI,EAAE;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC9B,OAAO,EAAE,OAAO,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACnC,GAAG,CAAC,EAAE,GAAG,CAAC;QACV,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,MAAM;IA6CV;;;;;;;;;;;;OAYG;IACH,oBAAoB,CAAC,IAAI,EAAE;QACzB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;QACjC,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,WAAW,GAAG,SAAS;IAiC3B;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAOjD;;OAEG;IACH,KAAK,IAAI,IAAI;IASb;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAoBrB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;CAGrB"}
|
|
1
|
+
{"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../src/collector.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,WAAW,EAEX,cAAc,EACf,MAAM,YAAY,CAAC;AA6DpB;;;;;;;;;;;;GAYG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAiB;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAa;gBAGrC,GAAG,EAAE,cAAc,EACnB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAOzD;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAMnD;;OAEG;IACH,YAAY,IAAI,WAAW,EAAE;IAM7B;;OAEG;IACH,IAAI,IAAI,MAAM;IAId;;;;;;;OAOG;IACH,cAAc,CAAC,IAAI,EAAE;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC9B,OAAO,EAAE,OAAO,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACnC,GAAG,CAAC,EAAE,GAAG,CAAC;QACV,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,MAAM;IA6CV;;;;;;;;;;;;OAYG;IACH,oBAAoB,CAAC,IAAI,EAAE;QACzB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;QACjC,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,WAAW,GAAG,SAAS;IAiC3B;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAOjD;;;;;;;;;OASG;IACH,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAMzC;;OAEG;IACH,KAAK,IAAI,IAAI;IASb;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAoBrB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;CAGrB"}
|