@ai-setting/roy-plugin-task-show 0.3.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.
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @fileoverview Server-Sent Events (SSE) event bus.
3
+ *
4
+ * The bus maintains a Set of connected SSE clients (each is an
5
+ * `http.ServerResponse` already configured for SSE). When the plugin
6
+ * records a tool call or a task status change, it calls
7
+ * `eventBus.broadcast(event)` and every connected client receives a
8
+ * standard SSE `data:` frame.
9
+ *
10
+ * The bus also handles client disconnect cleanup and periodic keep-alive
11
+ * pings (so reverse proxies / load balancers don't drop the connection).
12
+ *
13
+ * The implementation is intentionally tiny — no external dependencies, no
14
+ * abstractions beyond what the plugin needs. Each subscriber is identified
15
+ * only by a numeric id (assigned on `subscribe`) so `unsubscribe` is
16
+ * idempotent and safe to call multiple times.
17
+ */
18
+ import type * as http from "node:http";
19
+ import type { TaskEvent } from "./types.js";
20
+ import { SSE_HEARTBEAT_INTERVAL_MS as HEARTBEAT_MS } from "./types.js";
21
+ /**
22
+ * Callback invoked by the bus on each broadcast — useful for tests that
23
+ * don't want to spin up a real HTTP server + EventSource.
24
+ */
25
+ export type EventListener = (event: TaskEvent) => void;
26
+ /**
27
+ * Connection handle returned by `subscribe()`. Pass to `unsubscribe()`
28
+ * when the client disconnects.
29
+ */
30
+ export interface SubscriberHandle {
31
+ /** Numeric id assigned at subscribe time. */
32
+ readonly id: number;
33
+ }
34
+ export declare class EventBus {
35
+ private clients;
36
+ private listeners;
37
+ private nextId;
38
+ private heartbeatTimer;
39
+ /**
40
+ * Register an HTTP response as a new SSE subscriber.
41
+ *
42
+ * Caller is responsible for setting up SSE headers on `res` BEFORE
43
+ * calling this method (use `SSEClient.writeHeaders`). The bus will:
44
+ * - register a `close` listener that auto-removes the subscriber
45
+ * - start a periodic keep-alive ping (if not already running)
46
+ *
47
+ * Returns a handle that can be passed to `unsubscribe()` for explicit
48
+ * removal (the close listener handles cleanup automatically on socket
49
+ * close).
50
+ */
51
+ subscribe(res: http.ServerResponse, label?: string): SubscriberHandle;
52
+ /**
53
+ * Remove a subscriber (idempotent — safe to call multiple times).
54
+ */
55
+ unsubscribe(handle: SubscriberHandle): void;
56
+ /**
57
+ * Broadcast an event to every connected client and registered listener.
58
+ *
59
+ * `event` is serialized with `JSON.stringify` and sent as a single SSE
60
+ * `data:` frame. Clients with closed sockets are silently dropped from
61
+ * the subscriber set on the next write attempt.
62
+ *
63
+ * Exceptions from individual client writes are caught and logged — one
64
+ * broken subscriber must not break the rest.
65
+ */
66
+ broadcast(event: TaskEvent): void;
67
+ /**
68
+ * Register an in-process event listener (test-only path).
69
+ * Returns a function that unregisters the listener.
70
+ */
71
+ addListener(fn: EventListener): () => void;
72
+ /** Number of currently-connected SSE clients (for tests / health checks). */
73
+ size(): number;
74
+ /** True if there are no subscribers. */
75
+ isEmpty(): boolean;
76
+ /** Force-close every subscriber (called on dispose). */
77
+ closeAll(): void;
78
+ private maybeStartHeartbeat;
79
+ private maybeStopHeartbeat;
80
+ }
81
+ /**
82
+ * Format a TaskEvent as an SSE frame. Uses `data: <json>\n\n` per the SSE
83
+ * spec (https://html.spec.whatwg.org/multipage/server-sent-events.html).
84
+ *
85
+ * Optionally a caller can supply an `id:` line so clients can resume from
86
+ * the last event after a reconnect — currently we omit it (not needed
87
+ * for the plugin's use case where the frontend re-fetches the full state
88
+ * snapshot on reconnect).
89
+ */
90
+ export declare function formatSSEFrame(event: TaskEvent): string;
91
+ /**
92
+ * Write the standard SSE response headers and the initial comment line.
93
+ * Helper for `server.ts` to keep the handler readable.
94
+ */
95
+ export declare function writeSSEHeaders(res: http.ServerResponse): void;
96
+ /** Re-export heartbeat constant for convenience. */
97
+ export { HEARTBEAT_MS };
98
+ //# sourceMappingURL=event-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../src/event-bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,yBAAyB,IAAI,YAAY,EAAE,MAAM,YAAY,CAAC;AAMvE;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;AAEvD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAyBD,qBAAa,QAAQ;IACnB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,cAAc,CAA+C;IAErE;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,gBAAgB;IAoBrE;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAe3C;;;;;;;;;OASG;IACH,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IA8BjC;;;OAGG;IACH,WAAW,CAAC,EAAE,EAAE,aAAa,GAAG,MAAM,IAAI;IAK1C,6EAA6E;IAC7E,IAAI,IAAI,MAAM;IAId,wCAAwC;IACxC,OAAO,IAAI,OAAO;IAIlB,wDAAwD;IACxD,QAAQ,IAAI,IAAI;IAiBhB,OAAO,CAAC,mBAAmB;IAoB3B,OAAO,CAAC,kBAAkB;CAM3B;AAMD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAGvD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAW9D;AAED,oDAAoD;AACpD,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * @fileoverview Server-Sent Events (SSE) event bus.
3
+ *
4
+ * The bus maintains a Set of connected SSE clients (each is an
5
+ * `http.ServerResponse` already configured for SSE). When the plugin
6
+ * records a tool call or a task status change, it calls
7
+ * `eventBus.broadcast(event)` and every connected client receives a
8
+ * standard SSE `data:` frame.
9
+ *
10
+ * The bus also handles client disconnect cleanup and periodic keep-alive
11
+ * pings (so reverse proxies / load balancers don't drop the connection).
12
+ *
13
+ * The implementation is intentionally tiny — no external dependencies, no
14
+ * abstractions beyond what the plugin needs. Each subscriber is identified
15
+ * only by a numeric id (assigned on `subscribe`) so `unsubscribe` is
16
+ * idempotent and safe to call multiple times.
17
+ */
18
+ import { SSE_HEARTBEAT_INTERVAL_MS as HEARTBEAT_MS } from "./types.js";
19
+ // ============================================================================
20
+ // EventBus
21
+ // ============================================================================
22
+ export class EventBus {
23
+ clients = new Set();
24
+ listeners = new Set();
25
+ nextId = 1;
26
+ heartbeatTimer = null;
27
+ /**
28
+ * Register an HTTP response as a new SSE subscriber.
29
+ *
30
+ * Caller is responsible for setting up SSE headers on `res` BEFORE
31
+ * calling this method (use `SSEClient.writeHeaders`). The bus will:
32
+ * - register a `close` listener that auto-removes the subscriber
33
+ * - start a periodic keep-alive ping (if not already running)
34
+ *
35
+ * Returns a handle that can be passed to `unsubscribe()` for explicit
36
+ * removal (the close listener handles cleanup automatically on socket
37
+ * close).
38
+ */
39
+ subscribe(res, label) {
40
+ const id = this.nextId++;
41
+ const client = {
42
+ id,
43
+ res,
44
+ connectedAt: Date.now(),
45
+ ...(label !== undefined ? { label } : {}),
46
+ };
47
+ this.clients.add(client);
48
+ // Auto-cleanup on socket close.
49
+ res.on("close", () => {
50
+ this.clients.delete(client);
51
+ this.maybeStopHeartbeat();
52
+ });
53
+ this.maybeStartHeartbeat();
54
+ return { id };
55
+ }
56
+ /**
57
+ * Remove a subscriber (idempotent — safe to call multiple times).
58
+ */
59
+ unsubscribe(handle) {
60
+ for (const c of this.clients) {
61
+ if (c.id === handle.id) {
62
+ this.clients.delete(c);
63
+ try {
64
+ c.res.end();
65
+ }
66
+ catch {
67
+ // socket may already be closed
68
+ }
69
+ break;
70
+ }
71
+ }
72
+ this.maybeStopHeartbeat();
73
+ }
74
+ /**
75
+ * Broadcast an event to every connected client and registered listener.
76
+ *
77
+ * `event` is serialized with `JSON.stringify` and sent as a single SSE
78
+ * `data:` frame. Clients with closed sockets are silently dropped from
79
+ * the subscriber set on the next write attempt.
80
+ *
81
+ * Exceptions from individual client writes are caught and logged — one
82
+ * broken subscriber must not break the rest.
83
+ */
84
+ broadcast(event) {
85
+ const frame = formatSSEFrame(event);
86
+ // Drop dead clients up front.
87
+ const dead = [];
88
+ for (const c of this.clients) {
89
+ if (c.res.writableEnded || c.res.destroyed) {
90
+ dead.push(c);
91
+ continue;
92
+ }
93
+ try {
94
+ c.res.write(frame);
95
+ }
96
+ catch {
97
+ dead.push(c);
98
+ }
99
+ }
100
+ for (const d of dead)
101
+ this.clients.delete(d);
102
+ // Listeners (for tests / in-process subscribers).
103
+ for (const l of this.listeners) {
104
+ try {
105
+ l(event);
106
+ }
107
+ catch {
108
+ // Listener errors must not break the broadcast.
109
+ }
110
+ }
111
+ this.maybeStopHeartbeat();
112
+ }
113
+ /**
114
+ * Register an in-process event listener (test-only path).
115
+ * Returns a function that unregisters the listener.
116
+ */
117
+ addListener(fn) {
118
+ this.listeners.add(fn);
119
+ return () => this.listeners.delete(fn);
120
+ }
121
+ /** Number of currently-connected SSE clients (for tests / health checks). */
122
+ size() {
123
+ return this.clients.size;
124
+ }
125
+ /** True if there are no subscribers. */
126
+ isEmpty() {
127
+ return this.clients.size === 0;
128
+ }
129
+ /** Force-close every subscriber (called on dispose). */
130
+ closeAll() {
131
+ for (const c of this.clients) {
132
+ try {
133
+ c.res.end();
134
+ }
135
+ catch {
136
+ // ignore
137
+ }
138
+ }
139
+ this.clients.clear();
140
+ this.listeners.clear();
141
+ this.maybeStopHeartbeat();
142
+ }
143
+ // ---------------------------------------------------------------------
144
+ // Internals
145
+ // ---------------------------------------------------------------------
146
+ maybeStartHeartbeat() {
147
+ if (this.heartbeatTimer)
148
+ return;
149
+ if (this.clients.size === 0)
150
+ return;
151
+ this.heartbeatTimer = setInterval(() => {
152
+ for (const c of this.clients) {
153
+ if (c.res.writableEnded || c.res.destroyed)
154
+ continue;
155
+ try {
156
+ // SSE comment lines start with `:` and are ignored by EventSource.
157
+ c.res.write(":keep-alive\n\n");
158
+ }
159
+ catch {
160
+ // ignore — next broadcast will reap
161
+ }
162
+ }
163
+ }, HEARTBEAT_MS);
164
+ // Don't keep the event loop alive just for heartbeats.
165
+ if (typeof this.heartbeatTimer.unref === "function") {
166
+ this.heartbeatTimer.unref();
167
+ }
168
+ }
169
+ maybeStopHeartbeat() {
170
+ if (this.heartbeatTimer && this.clients.size === 0) {
171
+ clearInterval(this.heartbeatTimer);
172
+ this.heartbeatTimer = null;
173
+ }
174
+ }
175
+ }
176
+ // ============================================================================
177
+ // Helpers
178
+ // ============================================================================
179
+ /**
180
+ * Format a TaskEvent as an SSE frame. Uses `data: <json>\n\n` per the SSE
181
+ * spec (https://html.spec.whatwg.org/multipage/server-sent-events.html).
182
+ *
183
+ * Optionally a caller can supply an `id:` line so clients can resume from
184
+ * the last event after a reconnect — currently we omit it (not needed
185
+ * for the plugin's use case where the frontend re-fetches the full state
186
+ * snapshot on reconnect).
187
+ */
188
+ export function formatSSEFrame(event) {
189
+ const json = JSON.stringify(event);
190
+ return `event: ${event.type}\ndata: ${json}\n\n`;
191
+ }
192
+ /**
193
+ * Write the standard SSE response headers and the initial comment line.
194
+ * Helper for `server.ts` to keep the handler readable.
195
+ */
196
+ export function writeSSEHeaders(res) {
197
+ res.statusCode = 200;
198
+ res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
199
+ res.setHeader("Cache-Control", "no-cache, no-transform");
200
+ res.setHeader("Connection", "keep-alive");
201
+ res.setHeader("X-Accel-Buffering", "no"); // disable nginx buffering
202
+ // Some browsers / proxies require a 2KB initial response to flush headers.
203
+ res.write(":ok\n\n");
204
+ if (typeof res.flushHeaders === "function") {
205
+ res.flushHeaders();
206
+ }
207
+ }
208
+ /** Re-export heartbeat constant for convenience. */
209
+ export { HEARTBEAT_MS };
210
+ //# sourceMappingURL=event-bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../src/event-bus.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -9,12 +9,13 @@
9
9
  export { TaskShowPlugin, createTaskShowPlugin, } from "./plugin.js";
10
10
  export type { TaskShowPluginInterface } from "./plugin.js";
11
11
  export { DEFAULT_CONFIG, } from "./types.js";
12
- export type { TaskShowConfig, PluginEnvLike, TaskSession, ToolCallRecord, } from "./types.js";
12
+ export type { TaskShowConfig, PluginEnvLike, TaskSession, ToolCallRecord, TaskEvent, TaskEventType, TaskCreatedEventData, TaskUpdatedEventData, TaskCompletedEventData, ToolRecordedEventData, TypedTaskEvent, } from "./types.js";
13
13
  export { ToolCallCollector, } from "./collector.js";
14
14
  export { TaskShowServer, } from "./server.js";
15
15
  export type { ServerInfo } from "./server.js";
16
- export { buildVisualizationUrl, buildIndexUrl, injectVisualizationUrl, appendVisualizationBanner, } from "./url-injector.js";
17
- export type { InjectionResult } from "./url-injector.js";
16
+ export { EventBus, formatSSEFrame, writeSSEHeaders, } from "./event-bus.js";
17
+ export type { SubscriberHandle, EventListener } from "./event-bus.js";
18
+ export { buildVisualizationUrl, buildIndexUrl, } from "./url-injector.js";
18
19
  import TaskShowPlugin from "./plugin.js";
19
20
  export default TaskShowPlugin;
20
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,cAAc,EACd,oBAAoB,GACrB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,OAAO,EACL,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,cAAc,EACd,aAAa,EACb,WAAW,EACX,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,cAAc,MAAM,aAAa,CAAC;AACzC,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,cAAc,EACd,oBAAoB,GACrB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,OAAO,EACL,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,cAAc,EACd,aAAa,EACb,WAAW,EACX,cAAc,EACd,SAAS,EACT,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EACL,QAAQ,EACR,cAAc,EACd,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEtE,OAAO,EACL,qBAAqB,EACrB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,cAAc,MAAM,aAAa,CAAC;AACzC,eAAe,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -10,7 +10,8 @@ export { TaskShowPlugin, createTaskShowPlugin, } from "./plugin.js";
10
10
  export { DEFAULT_CONFIG, } from "./types.js";
11
11
  export { ToolCallCollector, } from "./collector.js";
12
12
  export { TaskShowServer, } from "./server.js";
13
- export { buildVisualizationUrl, buildIndexUrl, injectVisualizationUrl, appendVisualizationBanner, } from "./url-injector.js";
13
+ export { EventBus, formatSSEFrame, writeSSEHeaders, } from "./event-bus.js";
14
+ export { buildVisualizationUrl, buildIndexUrl, } from "./url-injector.js";
14
15
  import TaskShowPlugin from "./plugin.js";
15
16
  export default TaskShowPlugin;
16
17
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,cAAc,EACd,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,cAAc,GACf,MAAM,YAAY,CAAC;AAQpB,OAAO,EACL,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,cAAc,GACf,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAG3B,OAAO,cAAc,MAAM,aAAa,CAAC;AACzC,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,cAAc,EACd,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,cAAc,GACf,MAAM,YAAY,CAAC;AAepB,OAAO,EACL,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,cAAc,GACf,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,QAAQ,EACR,cAAc,EACd,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,qBAAqB,EACrB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,cAAc,MAAM,aAAa,CAAC;AACzC,eAAe,cAAc,CAAC"}
package/dist/plugin.d.ts CHANGED
@@ -4,16 +4,37 @@
4
4
  * Lifecycle (mirrors `ReminderPlugin` / `TaskTagPlugin`):
5
5
  * 1. Constructor accepts a config (defaults merged in).
6
6
  * 2. `init(env)` registers hooks on the global hook manager:
7
- * - `tool:after.execute` → record each tool call
8
- * - `task:after.update` → freeze session + emit URL
7
+ * - `tool:before.execute` → record per-call start timestamp
8
+ * - `tool:after.execute` → record each tool call
9
+ * - `task:before.create` → open a new TaskSession
10
+ * - `task:after.create` → mark session ready
11
+ * - `task:after.update` → freeze + broadcast status update
12
+ * - `task:after.complete` → freeze + broadcast terminal status
9
13
  * 3. `dispose()` stops the HTTP service & releases listeners.
10
14
  *
11
15
  * The plugin is intentionally lean: all the heavy lifting lives in
12
- * `collector.ts`, `server.ts`, and `url-injector.ts`. That keeps this file
16
+ * `collector.ts`, `server.ts`, and `event-bus.ts`. That keeps this file
13
17
  * readable and makes the surface area easy to test.
18
+ *
19
+ * ## Visualization reach-out (v0.5.0+)
20
+ *
21
+ * v0.5.0 replaces the previous `env.notify({type:"visualization_ready"})`
22
+ * mechanism with a Server-Sent Events stream served by the local HTTP
23
+ * service. The frontend subscribes to `GET /api/events` and receives a
24
+ * real-time push every time:
25
+ *
26
+ * - a new task is created (`task.created`)
27
+ * - a tool call is recorded (`tool.recorded`)
28
+ * - a task status changes (`task.updated`)
29
+ * - a task transitions to terminal status (`task.completed`)
30
+ *
31
+ * This eliminates the host-side NotificationChannel plumbing and works
32
+ * with any roy-agent host — including ones that pre-date the
33
+ * NotificationChannel abstraction.
14
34
  */
15
35
  import type { PluginEnvLike, TaskShowConfig } from "./types.js";
16
36
  import { ToolCallCollector } from "./collector.js";
37
+ import { EventBus } from "./event-bus.js";
17
38
  /**
18
39
  * Public, friendly type used in README + tests.
19
40
  *
@@ -31,8 +52,15 @@ export interface TaskShowPluginInterface {
31
52
  getServerPort(): number | null;
32
53
  /** accessor for tests / health checks */
33
54
  getCollector(): ToolCallCollector;
55
+ /** accessor for tests / health checks */
56
+ getEventBus(): EventBus;
34
57
  /** accessor for tests that want to bypass the hook manager entirely */
35
58
  getConfig(): TaskShowConfig;
59
+ /**
60
+ * Compute the URL the page would be served at. Helpful in tests where
61
+ * `init()` was skipped.
62
+ */
63
+ getVisualizationUrl(taskId: number): string;
36
64
  }
37
65
  /**
38
66
  * Main plugin class. Decorated with the static `roy-agent` metadata block
@@ -40,15 +68,14 @@ export interface TaskShowPluginInterface {
40
68
  */
41
69
  export declare class TaskShowPlugin implements TaskShowPluginInterface {
42
70
  readonly name = "roy-plugin-task-show";
43
- readonly version = "0.1.0";
44
- readonly description = "Visually trace a task's tool-call chain on a local HTTP service and inject the URL into the last tool result.";
71
+ readonly version = "0.5.0";
72
+ readonly description = "Visually trace a task's tool-call chain on a local HTTP service. v0.5.0+ uses Server-Sent Events for real-time page updates (no host NotificationChannel required).";
45
73
  private readonly cfg;
46
74
  private readonly collector;
47
75
  private readonly server;
76
+ private readonly eventBus;
48
77
  private env;
49
78
  private disposed;
50
- /** Last successful `tool:after.execute` result, used to inject the URL on task completion. */
51
- private lastResultByTask;
52
79
  /**
53
80
  * `tool:before.execute` → `tool:after.execute` start-time buffer.
54
81
  * Keyed by a fingerprint derived from (taskId, toolName, argsHash) so
@@ -66,8 +93,8 @@ export declare class TaskShowPlugin implements TaskShowPluginInterface {
66
93
  * (which can happen on newer hosts where both hook points fire on
67
94
  * completion). Cleared on dispose().
68
95
  */
69
- private injectedTaskIds;
70
- /** cached list of hook points we registered, so dispose() can be defensive even if the env lacks unregister. */
96
+ private finalizedTaskIds;
97
+ /** Cached list of hook points we registered, so dispose() can be defensive even if the env lacks unregister. */
71
98
  private registered;
72
99
  constructor(config?: Partial<TaskShowConfig>);
73
100
  /**
@@ -82,7 +109,9 @@ export declare class TaskShowPlugin implements TaskShowPluginInterface {
82
109
  dispose(): Promise<void>;
83
110
  getServerPort(): number | null;
84
111
  getCollector(): ToolCallCollector;
112
+ getEventBus(): EventBus;
85
113
  getConfig(): TaskShowConfig;
114
+ getVisualizationUrl(taskId: number): string;
86
115
  /**
87
116
  * Public function exposed so unit tests can simulate the
88
117
  * `tool:before.execute` payload (without going through the global hook
@@ -100,21 +129,35 @@ export declare class TaskShowPlugin implements TaskShowPluginInterface {
100
129
  */
101
130
  onToolAfterExecute(ctx: any, metadata?: Record<string, unknown>): Promise<void>;
102
131
  /**
103
- * Public function exposed for tests; mirrors the `task:after.update`
104
- * payload.
132
+ * Public function exposed for tests; mirrors the `task:before.create`
133
+ * payload. We use it to mint a fresh TaskSession before any tool call
134
+ * is recorded (gives us a stable taskId/title even if no tool fires).
105
135
  */
136
+ onTaskBeforeCreate(ctx: any): Promise<void>;
137
+ /**
138
+ * Public function exposed for tests; mirrors the `task:after.create`
139
+ * payload. Right now this is mostly a no-op (session was already opened
140
+ * in `onTaskBeforeCreate`); the hook is wired so the frontend can
141
+ * distinguish "task opened" from "task started executing".
142
+ *
143
+ * Hosts that only emit `task:after.create` (no before) are also
144
+ * supported — in that case the session is opened here.
145
+ */
146
+ onTaskAfterCreate(ctx: any): Promise<void>;
106
147
  /**
107
148
  * Public function exposed for tests; mirrors BOTH the preferred
108
- * `task:after.complete` payload (added in roy-agent 2026-07-10) AND
109
- * the legacy `task:after.update` payload. Both hook points reach this
110
- * handler; an internal dedup set guarantees at most one URL injection
111
- * per task.
149
+ * `task:after.complete` payload AND the legacy `task:after.update`
150
+ * payload.
151
+ *
152
+ * - `task:after.complete` (preferred, terminal):
153
+ * - `data.terminalStatus` is explicit; no filtering needed.
154
+ * - `task:after.update` (legacy):
155
+ * - `data.task.status` carries the new status. We treat it as a
156
+ * generic status update — terminal vs non-terminal decides which
157
+ * event type we broadcast.
112
158
  *
113
- * - Preferred (roy-agent 2026-07-10+): `task:after.complete`
114
- * - `data.terminalStatus` is explicit; no status filtering needed.
115
- * - Legacy fallback (pre-2026-07-10 roy-agent): `task:after.update`
116
- * - only `data.task` is provided; we filter for terminal statuses
117
- * the same way the previous implementation did.
159
+ * An internal dedup set guarantees that when both hook points fire
160
+ * (newer hosts), we broadcast the terminal event exactly once.
118
161
  */
119
162
  onTaskAfterComplete(ctx: any): Promise<void>;
120
163
  /**
@@ -124,15 +167,13 @@ export declare class TaskShowPlugin implements TaskShowPluginInterface {
124
167
  */
125
168
  onTaskAfterUpdate(ctx: any): Promise<void>;
126
169
  /**
127
- * Manual injection hook useful when a custom caller has a known task
128
- * id + ToolResult at hand (e.g. tests).
129
- */
130
- injectFor(taskId: number, result: any): boolean;
131
- /**
132
- * Compute the URL the page would be served at. Helpful in tests where
133
- * `init()` was skipped.
170
+ * Construct a TaskEvent and push it to every connected SSE client.
171
+ *
172
+ * The broadcast is fire-and-forget; SSE write failures are absorbed by
173
+ * the bus itself, so this method never throws. Centralizing the event
174
+ * construction here keeps every hook handler symmetric.
134
175
  */
135
- getVisualizationUrl(taskId: number): string;
176
+ private broadcast;
136
177
  /**
137
178
  * Subscribe to the hook points we care about. We try to use the env's
138
179
  * `registerHook` first (the modern, type-safe plugin API), then fall back
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}