@nanobpm/nano-workforce 0.130.0 → 0.131.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-workforce",
3
- "version": "0.130.0",
3
+ "version": "0.131.0",
4
4
  "description": "Nano Workforce — an Agent Graph Orchestration application for Agentic SDLC: durable BPMN processes that coordinate a graph of AI agents across the software delivery lifecycle.",
5
5
  "type": "module",
6
6
  "main": "main.ts",
@@ -173,6 +173,18 @@
173
173
  border-radius: 6px;
174
174
  }
175
175
 
176
+ /* The "connected — waiting for output" status note under the terminal (blank-terminal defect fix):
177
+ hidden until a live drill is connected-but-quiet, so a blank panel reads as "waiting" not "broken". */
178
+ .cockpit-terminal-note {
179
+ margin: 8px 0 0;
180
+ font-size: 0.85em;
181
+ color: #7c8794;
182
+ font-style: italic;
183
+ }
184
+ .cockpit-terminal-note[data-terminal-note="none"] {
185
+ display: none;
186
+ }
187
+
176
188
  /* ── Past sessions (H3 read path / #222): the captured-session history + replay. ──────────────── */
177
189
 
178
190
  .cockpit-past-header {
@@ -72,6 +72,7 @@ function workerView(worker, staleAfterMs, byJobKey) {
72
72
  host: worker.host ?? "\u2014",
73
73
  jobKeys,
74
74
  jobs: jobKeys.length,
75
+ drillable: jobKeys.length > 0,
75
76
  correlations,
76
77
  liveness: liveness(worker, staleAfterMs),
77
78
  staleMs: worker.staleMs,
@@ -141,12 +142,18 @@ function workerRow(doc, worker, onDrill, onOpenWorker) {
141
142
  button.setAttribute("data-stream", worker.stream);
142
143
  if (onOpenWorker) button.addEventListener("click", () => onOpenWorker(worker.instance));
143
144
  nameCell.appendChild(button);
144
- const drill = el(doc, "button", "cockpit-worker-drill", "terminal");
145
- drill.setAttribute("type", "button");
146
- drill.setAttribute("data-instance", worker.instance);
147
- drill.setAttribute("data-stream", worker.stream);
148
- if (onDrill) drill.addEventListener("click", () => onDrill(worker.stream));
149
- nameCell.appendChild(drill);
145
+ // The inline live-terminal drill ONLY for a worker that currently holds a job. An idle worker's
146
+ // `stream` is its bare instance id, which no producer writes to, so drilling it opens a permanently
147
+ // blank "live" terminal. Suppress the affordance when there is nothing live to stream (mirrors
148
+ // app/agentic/cockpit/supply-render.ts).
149
+ if (worker.drillable) {
150
+ const drill = el(doc, "button", "cockpit-worker-drill", "terminal");
151
+ drill.setAttribute("type", "button");
152
+ drill.setAttribute("data-instance", worker.instance);
153
+ drill.setAttribute("data-stream", worker.stream);
154
+ if (onDrill) drill.addEventListener("click", () => onDrill(worker.stream));
155
+ nameCell.appendChild(drill);
156
+ }
150
157
  row.appendChild(nameCell);
151
158
 
152
159
  row.appendChild(el(doc, "td", "cockpit-td cockpit-supply-family", worker.family));
@@ -494,6 +501,12 @@ export function mountCockpit(host, opts = {}) {
494
501
  const terminalHost = el(doc, "div", "cockpit-terminal-host");
495
502
  terminalHost.setAttribute("data-terminal", "host");
496
503
  terminalPanel.appendChild(terminalHost);
504
+ // Status note under the terminal, shown while a LIVE drill has connected but no output has arrived
505
+ // yet (a quiet job between frames), so a blank panel reads as "waiting" not "broken". Cleared on
506
+ // the first frame and on every mode change (mirrors app/agentic/cockpit/supply-boot.ts).
507
+ const terminalNote = el(doc, "p", "cockpit-terminal-note");
508
+ terminalNote.setAttribute("data-terminal-note", "none");
509
+ terminalPanel.appendChild(terminalNote);
497
510
  shell.appendChild(listRegion);
498
511
  shell.appendChild(pastRegion);
499
512
  shell.appendChild(terminalPanel);
@@ -524,6 +537,19 @@ export function mountCockpit(host, opts = {}) {
524
537
  if (next === "live") terminalTitle.textContent = "Worker terminal — live";
525
538
  else if (next === "replay") terminalTitle.textContent = "Worker terminal — replay (past session)";
526
539
  else terminalTitle.textContent = "Worker terminal";
540
+ // Any mode change replaces what's behind the panel, so the prior "waiting" note is stale — clear
541
+ // it. A live drill re-arms it once its fresh terminal is mounted.
542
+ setNote(undefined);
543
+ }
544
+
545
+ function setNote(text) {
546
+ if (text == null) {
547
+ terminalNote.textContent = "";
548
+ terminalNote.setAttribute("data-terminal-note", "none");
549
+ return;
550
+ }
551
+ terminalNote.textContent = text;
552
+ terminalNote.setAttribute("data-terminal-note", "waiting");
527
553
  }
528
554
 
529
555
  function teardownTerminal() {
@@ -586,8 +612,19 @@ export function mountCockpit(host, opts = {}) {
586
612
  teardownTerminal();
587
613
  try {
588
614
  terminalHost.replaceChildren();
589
- const sink = xtermSink(terminalHost);
590
- terminal = sink;
615
+ const rawSink = xtermSink(terminalHost);
616
+ terminal = rawSink;
617
+ // Wrap the sink so the first byte of live output clears the "waiting" note (mirrors supply-boot).
618
+ let cleared = false;
619
+ const sink = {
620
+ write: (chunk) => {
621
+ if (!cleared) {
622
+ cleared = true;
623
+ setNote(undefined);
624
+ }
625
+ rawSink.write(chunk);
626
+ },
627
+ };
591
628
  let session;
592
629
  const client = new RelayChannelClient({
593
630
  connect: connectRelay,
@@ -599,6 +636,8 @@ export function mountCockpit(host, opts = {}) {
599
636
  client.open();
600
637
  drill = { stream, client };
601
638
  setMode("live", stream);
639
+ // Arm the "waiting for output" note (after setMode, which clears it) until the first frame.
640
+ setNote("Connected — waiting for live output…");
602
641
  } catch (err) {
603
642
  // The new terminal failed to build after the prior one was torn down: reset the region to idle
604
643
  // (and drop any partially-built terminal) so the UI never shows a stale "live"/"replay"