@ai-setting/roy-plugin-task-show 1.0.0 → 1.2.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/public/style.css CHANGED
@@ -598,6 +598,47 @@ details summary {
598
598
  margin-left: 8px;
599
599
  }
600
600
 
601
+ /* ---------- v1.1.0+ SSE state badge (5 states) ---------- */
602
+ /* All `.pipeline-badge` variants share the same shape so the header
603
+ * stays balanced; only the colour + animation differ. */
604
+ .pipeline-badge {
605
+ display: inline-block;
606
+ padding: 1px 8px;
607
+ border-radius: 8px;
608
+ font-size: 11px;
609
+ margin-left: 8px;
610
+ font-weight: 600;
611
+ letter-spacing: 0.02em;
612
+ }
613
+ .pipeline-stale {
614
+ /* legacy alias — same orange as the v0.9.x pill */
615
+ background: var(--warn);
616
+ color: #1f2937;
617
+ }
618
+ .pipeline-connecting {
619
+ background: #fde68a;
620
+ color: #78350f;
621
+ animation: badge-pulse 1.4s ease-in-out infinite;
622
+ }
623
+ .pipeline-live {
624
+ background: #22c55e;
625
+ color: #052e16;
626
+ animation: badge-pulse 1.8s ease-in-out infinite;
627
+ }
628
+ .pipeline-reconnecting {
629
+ background: #fde68a;
630
+ color: #78350f;
631
+ animation: badge-pulse 0.9s ease-in-out infinite;
632
+ }
633
+ .pipeline-error {
634
+ background: var(--bad);
635
+ color: #fff5f5;
636
+ }
637
+ @keyframes badge-pulse {
638
+ 0%, 100% { opacity: 1; }
639
+ 50% { opacity: 0.55; }
640
+ }
641
+
601
642
  .pipeline-error {
602
643
  background: rgba(239, 68, 68, 0.08);
603
644
  border: 1px solid var(--bad);
@@ -1049,3 +1090,67 @@ details summary {
1049
1090
  border: 1px solid rgba(148, 163, 184, 0.4);
1050
1091
  }
1051
1092
 
1093
+ /* ---------- Session forest metadata ---------- */
1094
+ .session-forest > ul {
1095
+ list-style: none;
1096
+ margin: 0;
1097
+ padding: 0;
1098
+ }
1099
+ .session-row {
1100
+ min-width: 0;
1101
+ margin: 8px 0;
1102
+ padding: 10px 12px;
1103
+ border: 1px solid var(--border);
1104
+ border-radius: 8px;
1105
+ background: rgba(24, 36, 66, 0.45);
1106
+ }
1107
+ .session-row .session-title {
1108
+ display: inline-block;
1109
+ font-weight: 600;
1110
+ overflow-wrap: anywhere;
1111
+ }
1112
+ .session-toggle {
1113
+ margin-left: 10px;
1114
+ }
1115
+ .session-details {
1116
+ margin-top: 8px;
1117
+ }
1118
+ .session-details dl {
1119
+ display: grid;
1120
+ grid-template-columns: minmax(10rem, 15rem) minmax(0, 1fr);
1121
+ gap: 6px 12px;
1122
+ margin: 10px 0 0;
1123
+ }
1124
+ .session-details dt {
1125
+ color: var(--muted);
1126
+ font-weight: 600;
1127
+ overflow-wrap: anywhere;
1128
+ }
1129
+ .session-details dd {
1130
+ min-width: 0;
1131
+ margin: 0;
1132
+ overflow-wrap: anywhere;
1133
+ }
1134
+ .session-details pre.metadata-json,
1135
+ .session-details pre.metadata-text {
1136
+ max-height: 24rem;
1137
+ margin: 0;
1138
+ white-space: pre-wrap;
1139
+ overflow: auto;
1140
+ overflow-wrap: anywhere;
1141
+ word-break: break-word;
1142
+ }
1143
+ .session-operations {
1144
+ list-style: none;
1145
+ margin: 12px 0 0;
1146
+ padding: 0;
1147
+ }
1148
+ @media (max-width: 700px) {
1149
+ .session-details dl {
1150
+ grid-template-columns: 1fr;
1151
+ gap: 2px;
1152
+ }
1153
+ .session-details dd {
1154
+ margin-bottom: 8px;
1155
+ }
1156
+ }
@@ -22,6 +22,12 @@
22
22
  var TERMINAL_STATUSES = { completed: 1, failed: 1, cancelled: 1 };
23
23
  var POLL_MS = 5000;
24
24
  var MAX_BACKOFF_MS = 30000;
25
+ /** v1.1.0+ SSE state — drives the pipeline header badge. */
26
+ var currentSseState = "stale";
27
+ /** v1.1.0+ SSE instance, owned by the controller. */
28
+ var pipelineSse = null;
29
+ /** v1.1.0+ consecutive SSE error count (3+ → "error" state). */
30
+ var sseErrorCount = 0;
25
31
 
26
32
  /** HTML escape — keep in sync with server-side `htmlEscape`. */
27
33
  function esc(s) {
@@ -116,24 +122,50 @@
116
122
  );
117
123
  }).join("");
118
124
 
119
- var staleBadge = stale ? ' <span class="pipeline-stale" data-pipeline-stale>stale</span>' : "";
125
+ var badge = renderSseBadge(stale, currentSseState);
120
126
  var idAttr = taskId ? ' data-task-id="' + esc(taskId) + '"' : "";
121
127
  if (!ops || ops.length === 0) {
122
128
  return (
123
129
  '<section class="panel panel-pipeline"' + idAttr + ' data-pipeline-root>' +
124
- '<h2>Task lifecycle pipeline' + staleBadge + '</h2>' +
130
+ '<h2>Task lifecycle pipeline' + badge + '</h2>' +
125
131
  '<p class="empty">No operations recorded yet.</p>' +
126
132
  '</section>'
127
133
  );
128
134
  }
129
135
  return (
130
136
  '<section class="panel panel-pipeline"' + idAttr + ' data-pipeline-root>' +
131
- '<h2>Task lifecycle pipeline' + staleBadge + '</h2>' +
137
+ '<h2>Task lifecycle pipeline' + badge + '</h2>' +
132
138
  '<ol class="op-timeline" role="list">' + nodes + '</ol>' +
133
139
  '</section>'
134
140
  );
135
141
  }
136
142
 
143
+ /**
144
+ * v1.1.0+: render the SSE state badge (5-state) when SSE is wired,
145
+ * otherwise fall back to the legacy orange `pipeline-stale` pill for
146
+ * cache-TTL staleness.
147
+ */
148
+ function renderSseBadge(stale, sseState) {
149
+ if (sseState) {
150
+ var labels = {
151
+ stale: "stale",
152
+ connecting: "connecting…",
153
+ live: "live",
154
+ reconnecting: "reconnecting…",
155
+ error: "error",
156
+ };
157
+ return (
158
+ ' <span class="pipeline-badge pipeline-' + sseState + '" data-sse-state="' + sseState + '">' +
159
+ esc(labels[sseState] || sseState) +
160
+ '</span>'
161
+ );
162
+ }
163
+ if (stale) {
164
+ return ' <span class="pipeline-stale" data-pipeline-stale>stale</span>';
165
+ }
166
+ return "";
167
+ }
168
+
137
169
  function renderErrorHtml(code, taskId) {
138
170
  var idAttr = taskId ? ' data-task-id="' + esc(taskId) + '"' : "";
139
171
  return (
@@ -250,6 +282,7 @@
250
282
  notifyMermaidRebuild();
251
283
  if (r.terminal) {
252
284
  self.stopped = true;
285
+ self.teardownSse();
253
286
  return;
254
287
  }
255
288
  self.scheduleNext();
@@ -257,6 +290,7 @@
257
290
  swapIn(renderErrorHtml(r.code || "error", self.taskId));
258
291
  if (r.terminal) {
259
292
  self.stopped = true;
293
+ self.teardownSse();
260
294
  return;
261
295
  }
262
296
  self.scheduleNext(true);
@@ -268,6 +302,103 @@
268
302
  });
269
303
  };
270
304
 
305
+ /**
306
+ * v1.1.0+: subscribe to /api/events and react to operation.updated /
307
+ * tool.called / task.* events for THIS task id. The handler updates
308
+ * `currentSseState` (drives the header badge) and patches the
309
+ * pipeline DOM in place — no full re-fetch needed.
310
+ *
311
+ * EventSource auto-reconnects; we just track the error count so
312
+ * 3+ consecutive failures flip the badge to `error`.
313
+ */
314
+ PipelineController.prototype.attachSse = function () {
315
+ var self = this;
316
+ if (typeof EventSource === "undefined") return;
317
+ if (pipelineSse) return; // idempotent
318
+ setSseState("connecting");
319
+ try {
320
+ pipelineSse = new EventSource("/api/events");
321
+ } catch (_) {
322
+ setSseState("error");
323
+ return;
324
+ }
325
+ pipelineSse.addEventListener("open", function () {
326
+ setSseState("live");
327
+ sseErrorCount = 0;
328
+ });
329
+
330
+ pipelineSse.addEventListener("operation.updated", function (ev) {
331
+ var payload;
332
+ try { payload = JSON.parse(ev.data); } catch (_) { return; }
333
+ if (!payload || payload.taskId !== self.taskId) return;
334
+ var ops = (payload.data && payload.data.operations) || [];
335
+ var merged = mergeNodes(self.lastOps, ops);
336
+ self.lastOps = merged;
337
+ swapIn(renderPipelineHtml(merged, !!payload.data.stale, self.taskId));
338
+ window.__lifecycleOps = {
339
+ taskId: self.taskId,
340
+ operations: merged,
341
+ fetchedAt: Date.now(),
342
+ };
343
+ notifyMermaidRebuild();
344
+ });
345
+
346
+ ["task.created", "task.updated", "task.completed"].forEach(function (type) {
347
+ pipelineSse.addEventListener(type, function (ev) {
348
+ // For task.completed, stop the controller — no more polling needed.
349
+ var payload;
350
+ try { payload = JSON.parse(ev.data); } catch (_) { return; }
351
+ if (!payload) return;
352
+ var sid = payload.data && payload.data.session && payload.data.session.taskId;
353
+ if (sid !== self.taskId) return;
354
+ if (type === "task.completed") {
355
+ self.stopped = true;
356
+ if (self.timer) { clearTimeout(self.timer); self.timer = null; }
357
+ }
358
+ });
359
+ });
360
+
361
+ pipelineSse.addEventListener("tool.called", function (ev) {
362
+ // tool.called doesn't change the pipeline timeline, but the
363
+ // Mermaid re-render hook listens for any data refresh — dispatch
364
+ // a synthetic ops-loaded event so the diagram reflects the new
365
+ // tool call. The timeline itself is patched via operation.updated.
366
+ notifyMermaidRebuild();
367
+ });
368
+
369
+ pipelineSse.addEventListener("error", function () {
370
+ sseErrorCount += 1;
371
+ if (sseErrorCount >= 3) {
372
+ setSseState("error");
373
+ } else {
374
+ setSseState("reconnecting");
375
+ }
376
+ });
377
+ };
378
+
379
+ PipelineController.prototype.teardownSse = function () {
380
+ if (pipelineSse) {
381
+ try { pipelineSse.close(); } catch (_) {}
382
+ pipelineSse = null;
383
+ }
384
+ setSseState("stale");
385
+ };
386
+
387
+ /**
388
+ * v1.1.0+ Update the SSE state and re-render the badge in place.
389
+ * Idempotent — safe to call multiple times for the same state.
390
+ */
391
+ function setSseState(state) {
392
+ if (currentSseState === state) return;
393
+ currentSseState = state;
394
+ // Re-render the pipeline header with the new state.
395
+ var root = findRoot();
396
+ if (!root) return;
397
+ var pipeline = window.__pipelineController;
398
+ if (!pipeline) return;
399
+ swapIn(renderPipelineHtml(pipeline.lastOps, false, pipeline.taskId));
400
+ }
401
+
271
402
  /**
272
403
  * Fire a CustomEvent the app.js Mermaid re-renderer listens to.
273
404
  * We don't import a global helper to keep this file standalone.
@@ -313,6 +444,10 @@
313
444
  }
314
445
  // Expose for tests
315
446
  window.__pipelineController = new PipelineController(root);
447
+ // v1.1.0+: wire the SSE subscriber so operation.updated /
448
+ // tool.called events patch the pipeline DOM in place without
449
+ // waiting for the next polling tick.
450
+ window.__pipelineController.attachSse();
316
451
  }
317
452
 
318
453
  // v0.9.0: expose renderPipelineHtml to the global scope so other