@nanobpm/nano-workforce 0.85.2 → 0.85.3

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 CHANGED
@@ -1,3 +1,10 @@
1
+ ## [0.85.3](https://github.com/nanobpm/nano-workforce/compare/v0.85.2...v0.85.3) (2026-08-18)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * resolve cockpit endpoints base-relative so Studio App-View populates ([#280](https://github.com/nanobpm/nano-workforce/issues/280)) ([eb7b69b](https://github.com/nanobpm/nano-workforce/commit/eb7b69bf5e3df192205e5ac227e36a01ceb71a2b)), closes [#279](https://github.com/nanobpm/nano-workforce/issues/279)
7
+
1
8
  ## [0.85.2](https://github.com/nanobpm/nano-workforce/compare/v0.85.1...v0.85.2) (2026-08-18)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-workforce",
3
- "version": "0.85.2",
3
+ "version": "0.85.3",
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",
@@ -32,6 +32,7 @@
32
32
  const cfg = window.__NANO_APP_VIEW__ ?? {};
33
33
  mountCockpit(cfg.host ?? document.getElementById("cockpit-root"), {
34
34
  reportUrl: cfg.reportUrl,
35
+ transcriptsUrl: cfg.transcriptsUrl,
35
36
  relayUrl: cfg.relayUrl,
36
37
  hookSecret: cfg.hookSecret,
37
38
  relayToken: cfg.relayToken,
@@ -318,7 +318,8 @@ function relaySocketFactory(url) {
318
318
  *
319
319
  * @param {Element} host — where the cockpit renders (standalone: document.body; embedded: the App-View host).
320
320
  * @param {object} [opts]
321
- * @param {string} [opts.reportUrl] — the supply JSON endpoint the app serves.
321
+ * @param {string} [opts.reportUrl] — the supply JSON endpoint the app serves (default
322
+ * `"app/api/agentic/supply"`, base-relative).
322
323
  * @param {string} [opts.relayUrl] — the agentic channel WebSocket URL (with auth token + capability query).
323
324
  * @param {string} [opts.hookSecret] — shared secret sent as `x-hook-secret` on the report fetch when the
324
325
  * app's supply endpoint is guarded by NANO_PR_WEBHOOK_SECRET (omit for open deployments).
@@ -329,8 +330,8 @@ function relaySocketFactory(url) {
329
330
  * least this many ms old (default 15000).
330
331
  * @param {number} [opts.pastFetchTimeoutMs] — upper bound (ms) on a single past-sessions transcripts
331
332
  * fetch; the fetch is aborted past this so a hung endpoint can't wedge the past panel (default 15000).
332
- * @param {string} [opts.transcriptsUrl] — the captured-session list endpoint (default
333
- * /app/api/agentic/transcripts) backing the always-on "past sessions" history + replay.
333
+ * @param {string} [opts.transcriptsUrl] — the captured-session list endpoint backing the always-on
334
+ * "past sessions" history + replay (default `"app/api/agentic/transcripts"`, base-relative).
334
335
  * @returns a handle with `.dispose()`.
335
336
  */
336
337
  export function mountCockpit(host, opts = {}) {
@@ -340,8 +341,16 @@ export function mountCockpit(host, opts = {}) {
340
341
  );
341
342
  }
342
343
  const doc = document;
343
- const reportUrl = opts.reportUrl ?? "/app/api/agentic/supply";
344
- const transcriptsUrl = opts.transcriptsUrl ?? "/app/api/agentic/transcripts";
344
+ // Base-relative defaults (no leading slash) so the browser resolves them against the document's
345
+ // baseURI. Standalone (served at the app root) this is the origin root; through the Studio console
346
+ // App-View the document base is the app-view path (`/console/app-view/<AppName>/`) while the iframe
347
+ // ORIGIN is the console (:8080) — an absolute (leading-slash) path would resolve against the console
348
+ // origin root (`:8080/app/api/agentic/supply` → 404) instead of the app-view base that proxies the
349
+ // API, leaving the cockpit empty (#279). A base-relative default lands on the right endpoint both
350
+ // ways, so the cockpit populates identically standalone and embedded even though the console never
351
+ // injects window.__NANO_APP_VIEW__.
352
+ const reportUrl = opts.reportUrl ?? "app/api/agentic/supply";
353
+ const transcriptsUrl = opts.transcriptsUrl ?? "app/api/agentic/transcripts";
345
354
  const hookSecret = opts.hookSecret;
346
355
  const relayUrl = opts.relayUrl ?? defaultRelayUrl(opts.relayToken, opts.relayCapability);
347
356
  const refreshMs = opts.refreshMs ?? DEFAULT_REFRESH_MS;
@@ -0,0 +1,73 @@
1
+ // Regression guard for issue #279: the cockpit renders empty when viewed through the Studio console
2
+ // App-View because its default supply/transcript endpoints were ABSOLUTE (leading-slash) paths.
3
+ //
4
+ // Studio frames the app in an iframe whose document base is the app-view path
5
+ // (`/console/app-view/<AppName>/`) but whose ORIGIN is the console (`:8080`). A leading-slash fetch
6
+ // therefore resolves against the console origin root — `:8080/app/api/agentic/supply` → 404 — instead
7
+ // of the app-view base that actually serves the API. The console does NOT inject
8
+ // `window.__NANO_APP_VIEW__`, so `mount.js` falls back to its default; the default must be
9
+ // BASE-RELATIVE (no leading slash) so the browser resolves it against `document.baseURI` and it lands
10
+ // on the app-view base standalone AND embedded alike. This test pins that resolution behaviour so the
11
+ // absolute-path regression can't return.
12
+ import { test } from "node:test";
13
+ import { assert, assertEquals } from "#test-assert";
14
+ import { readFileSync } from "node:fs";
15
+
16
+ const ROOT = decodeURIComponent(new URL("../", import.meta.url).pathname);
17
+ const MOUNT_JS = readFileSync(`${ROOT}pages/cockpit/mount.js`, "utf8");
18
+ const EMBED_HTML = readFileSync(`${ROOT}pages/cockpit/embed.html`, "utf8");
19
+
20
+ // Pull the string default out of `const <name> = opts.<name> ?? "<default>";` in mount.js.
21
+ function defaultEndpoint(name: string): string {
22
+ const m = MOUNT_JS.match(new RegExp(`opts\\.${name}\\s*\\?\\?\\s*"([^"]*)"`));
23
+ assert(m, `mount.js must default opts.${name} to a string literal`);
24
+ return m![1];
25
+ }
26
+
27
+ // The two surfaces mount.js serves, per the issue's URL table:
28
+ const APP_VIEW_BASE = "http://studio-host:8080/console/app-view/Workforce/"; // Studio iframe base
29
+ const STANDALONE_BASE = "http://127.0.0.1:3000/"; // standalone shell served at the app root
30
+
31
+ for (const name of ["reportUrl", "transcriptsUrl"] as const) {
32
+ test(`#279: mount.js default ${name} is base-relative (no leading slash)`, () => {
33
+ const def = defaultEndpoint(name);
34
+ assert(
35
+ !def.startsWith("/"),
36
+ `default ${name} "${def}" must not start with "/": a leading-slash path resolves against the ` +
37
+ `iframe ORIGIN (console :8080), not the app-view base, so every fetch 404s through Studio (#279)`,
38
+ );
39
+ });
40
+
41
+ test(`#279: default ${name} resolves onto the app-view base inside the Studio iframe`, () => {
42
+ const def = defaultEndpoint(name);
43
+ // The bug: the default resolved to the console origin root (dropping /console/app-view/Workforce/).
44
+ // The fix: it must resolve UNDER the app-view base so it hits the endpoint the console proxies.
45
+ assertEquals(
46
+ new URL(def, APP_VIEW_BASE).href,
47
+ `http://studio-host:8080/console/app-view/Workforce/${def}`,
48
+ `default ${name} must resolve under the app-view base, not the console origin root (#279)`,
49
+ );
50
+ });
51
+
52
+ test(`#279: default ${name} still resolves to the origin root standalone`, () => {
53
+ const def = defaultEndpoint(name);
54
+ assertEquals(
55
+ new URL(def, STANDALONE_BASE).href,
56
+ `http://127.0.0.1:3000/${def}`,
57
+ `default ${name} must keep working for the standalone shell at the app root`,
58
+ );
59
+ });
60
+ }
61
+
62
+ test("#279: embed.html forwards BOTH reportUrl and transcriptsUrl from the injected config", () => {
63
+ // embed.html previously forwarded only reportUrl, so even if the console injected endpoint config
64
+ // the transcripts panel kept its (formerly absolute) default. Both must be forwarded.
65
+ assert(
66
+ /transcriptsUrl:\s*cfg\.transcriptsUrl/.test(EMBED_HTML),
67
+ "embed.html must forward transcriptsUrl: cfg.transcriptsUrl so the injected config reaches the past-sessions panel (#279)",
68
+ );
69
+ assert(
70
+ /reportUrl:\s*cfg\.reportUrl/.test(EMBED_HTML),
71
+ "embed.html must forward reportUrl: cfg.reportUrl",
72
+ );
73
+ });