@nanobpm/nano-workforce 0.120.1 → 0.121.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/openapi.yaml CHANGED
@@ -1616,6 +1616,20 @@ components:
1616
1616
  diagram:
1617
1617
  type: string
1618
1618
  description: The mermaid flowchart of the compiled graph (preview).
1619
+ humanNodes:
1620
+ type: array
1621
+ items:
1622
+ $ref: "#/components/schemas/DeliveryHumanStop"
1623
+ description: >-
1624
+ The human stop-points the compiled graph parks on (preview) — where it waits for a person
1625
+ (or an agent answering on their behalf), rendered by the Delivery Graphs page (#441).
1626
+ sideEffects:
1627
+ type: array
1628
+ items:
1629
+ $ref: "#/components/schemas/DeliverySideEffect"
1630
+ description: >-
1631
+ The side-effecting (`agent`/`connector`) actions the compiled graph WILL perform (preview)
1632
+ — what an approval authorises (Decision 7), rendered by the Delivery Graphs page (#441).
1619
1633
  ResolvedDeliveryNode:
1620
1634
  description: >-
1621
1635
  A normalised node in the compiled graph (ADR 0005 slice S1) — its `id`, `kind`, the
@@ -29,6 +29,9 @@ test("compile-delivery-graph: a well-formed graph → 200 with the pure preview"
29
29
  assertEquals(res.status, 200);
30
30
  assertEquals(res.body.ok, true);
31
31
  assert(typeof res.body.bpmn === "string" && res.body.bpmn.length > 0);
32
+ // The compile preview must show what actually deploys — including the auto-laid-out diagram
33
+ // interchange (#440), so the process explorer can render the previewed graph.
34
+ assert(res.body.bpmn.includes("<bpmndi:BPMNDiagram"), "the previewed bpmn carries diagram interchange");
32
35
  assert(typeof res.body.diagram === "string" && res.body.diagram.length > 0);
33
36
  assertEquals(res.body.resolved.nodes.length, 2);
34
37
  assertEquals(res.body.humanNodes.length, 1);
@@ -20,7 +20,7 @@ export default defineOperation("compileDeliveryGraph", async ({ body }, app) =>
20
20
  // the SEMANTIC checks (acyclicity, edge integrity, fact resolution) the schema cannot express. A
21
21
  // directly-invoked delegate could still pass `undefined` — the compiler reads its input as
22
22
  // `unknown` and maps that to a clean `ok:false`, never a 500.
23
- const result = compileDeliveryGraph(body);
23
+ const result = await compileDeliveryGraph(body);
24
24
  if (!result.ok) {
25
25
  app.log.warn("compile-delivery-graph rejected", { errors: result.errors.length });
26
26
  return { status: 400, body: result };
@@ -37,7 +37,7 @@ export default defineOperation("dispatchDeliveryGraph", async (input, app) => {
37
37
  // dispatches. A compile failure here surfaces as a clean 400 rather than reaching the start door.
38
38
  let approvalToken: string | undefined;
39
39
  if (approve) {
40
- const compiled = compileDeliveryGraph(parsed.graph);
40
+ const compiled = await compileDeliveryGraph(parsed.graph);
41
41
  if (!compiled.ok) {
42
42
  app.log.warn("dispatch-delivery-graph rejected: compile", { errors: compiled.errors.length });
43
43
  return {
@@ -35,6 +35,15 @@ test("preview-delivery-graph: a pasted well-formed graph → 200 summary with di
35
35
  assertEquals(res.body.sideEffecting, true);
36
36
  assert(typeof res.body.diagram === "string" && res.body.diagram.length > 0);
37
37
  assertEquals(res.body.title, "runbook");
38
+ // The FULL preview detail (#441) — the human stop-points and side-effecting actions the page
39
+ // renders, not just the counts. `a` is the side-effecting agent node; `b` is the human stop.
40
+ assert(Array.isArray(res.body.humanNodes) && res.body.humanNodes.length === 1);
41
+ assertEquals(res.body.humanNodes[0].nodeId, "b");
42
+ assertEquals(res.body.humanNodes[0].prompt, "do X");
43
+ assert(Array.isArray(res.body.sideEffects) && res.body.sideEffects.length === 1);
44
+ assertEquals(res.body.sideEffects[0].nodeId, "a");
45
+ assertEquals(res.body.sideEffects[0].kind, "agent");
46
+ assert(typeof res.body.sideEffects[0].description === "string" && res.body.sideEffects[0].description.length > 0);
38
47
  });
39
48
 
40
49
  test("preview-delivery-graph: is PURE — repeated previews return the identical digest", async () => {
@@ -22,7 +22,7 @@ export default defineOperation("previewDeliveryGraph", async ({ body }, app) =>
22
22
  app.log.warn("preview-delivery-graph rejected: parse", { message: parsed.error });
23
23
  return { status: 400, body: { ok: false, error: parsed.error } };
24
24
  }
25
- const compiled = compileDeliveryGraph(parsed.graph);
25
+ const compiled = await compileDeliveryGraph(parsed.graph);
26
26
  if (!compiled.ok) {
27
27
  app.log.warn("preview-delivery-graph rejected: compile", { errors: compiled.errors.length });
28
28
  return {
@@ -54,6 +54,12 @@ export default defineOperation("previewDeliveryGraph", async ({ body }, app) =>
54
54
  humanNodeCount: compiled.humanNodes.length,
55
55
  sideEffectCount: compiled.sideEffects.length,
56
56
  diagram: compiled.diagram,
57
+ // The FULL extracted preview detail (not just the counts): the human stop-points and the
58
+ // side-effecting actions the operator is being asked to approve (Decision 7). The Delivery
59
+ // Graphs page renders these lists so the operator sees WHERE it parks on a person and WHAT it
60
+ // will do before dispatching — the "preview before dispatch" principle made visible (#441).
61
+ humanNodes: compiled.humanNodes,
62
+ sideEffects: compiled.sideEffects,
57
63
  },
58
64
  };
59
65
  });
@@ -56,7 +56,7 @@ export default defineOperation("startDeliveryGraph", async ({ body }, app) => {
56
56
 
57
57
  // 2) Compile via S1. This yields the deterministic BPMN (→ the content digest / approval token) plus
58
58
  // the graph's shape: its side effects (whether approval is required), human stops, and node count.
59
- const compiled = compileDeliveryGraph(graph);
59
+ const compiled = await compileDeliveryGraph(graph);
60
60
  if (!compiled.ok) {
61
61
  app.log.warn("start-delivery-graph rejected: compile", { count: compiled.errors.length });
62
62
  return { status: 400, body: { ok: false, errors: compiled.errors } };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-workforce",
3
- "version": "0.120.1",
3
+ "version": "0.121.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",
@@ -59,7 +59,8 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@nanobpm/agentic": "^0.1.0",
62
- "@nanobpm/urban": "^0.75.0"
62
+ "@nanobpm/urban": "^0.75.0",
63
+ "bpmn-auto-layout": "^2.0.0-alpha.2"
63
64
  },
64
65
  "devDependencies": {
65
66
  "@biomejs/biome": "^2.4.11",
@@ -0,0 +1,273 @@
1
+ :root {
2
+ color-scheme: dark;
3
+ }
4
+
5
+ .dg {
6
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
7
+ color: #d7e0ea;
8
+ background: #0b0f14;
9
+ min-height: 100%;
10
+ box-sizing: border-box;
11
+ padding: 16px;
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: 16px;
15
+ }
16
+
17
+ .dg .card {
18
+ background: #121821;
19
+ border: 1px solid #223042;
20
+ border-radius: 10px;
21
+ padding: 14px 16px;
22
+ }
23
+
24
+ .dg .card-warn {
25
+ border-color: rgba(210, 153, 34, 0.5);
26
+ }
27
+
28
+ .dg .card-err {
29
+ border-color: rgba(248, 81, 73, 0.5);
30
+ }
31
+
32
+ .dg .card-ok {
33
+ border-color: rgba(63, 185, 80, 0.5);
34
+ }
35
+
36
+ .dg h2 {
37
+ margin: 0 0 8px;
38
+ font-size: 15px;
39
+ font-weight: 600;
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 8px;
43
+ }
44
+
45
+ .dg p {
46
+ margin: 6px 0;
47
+ font-size: 13px;
48
+ }
49
+
50
+ .dg .json {
51
+ width: 100%;
52
+ box-sizing: border-box;
53
+ min-height: 200px;
54
+ resize: vertical;
55
+ background: #0d141d;
56
+ color: #d7e0ea;
57
+ border: 1px solid #223042;
58
+ border-radius: 8px;
59
+ padding: 10px 12px;
60
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
61
+ font-size: 12.5px;
62
+ line-height: 1.5;
63
+ }
64
+
65
+ .dg .idem {
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: 4px;
69
+ margin-top: 10px;
70
+ font-size: 12px;
71
+ }
72
+
73
+ .dg .input {
74
+ background: #0d141d;
75
+ color: #d7e0ea;
76
+ border: 1px solid #223042;
77
+ border-radius: 6px;
78
+ padding: 6px 10px;
79
+ font-size: 13px;
80
+ }
81
+
82
+ .dg .actions {
83
+ display: flex;
84
+ align-items: center;
85
+ gap: 10px;
86
+ margin-top: 12px;
87
+ flex-wrap: wrap;
88
+ }
89
+
90
+ .dg .btn {
91
+ background: #1c2735;
92
+ color: #d7e0ea;
93
+ border: 1px solid #2b3a4e;
94
+ border-radius: 7px;
95
+ padding: 7px 16px;
96
+ font-size: 13px;
97
+ font-weight: 500;
98
+ cursor: pointer;
99
+ }
100
+
101
+ .dg .btn:hover {
102
+ background: #223042;
103
+ }
104
+
105
+ .dg .btn:disabled {
106
+ opacity: 0.5;
107
+ cursor: default;
108
+ }
109
+
110
+ .dg .btn-primary {
111
+ background: #1f6feb;
112
+ border-color: #388bfd;
113
+ color: #fff;
114
+ }
115
+
116
+ .dg .btn-primary:hover {
117
+ background: #388bfd;
118
+ }
119
+
120
+ .dg .btn-ghost {
121
+ background: transparent;
122
+ border-color: #2b3a4e;
123
+ color: #8aa0b8;
124
+ }
125
+
126
+ .dg .status {
127
+ font-size: 12.5px;
128
+ color: #8aa0b8;
129
+ }
130
+
131
+ .dg .status-ok {
132
+ color: #56d364;
133
+ }
134
+
135
+ .dg .status-err {
136
+ color: #ff7b72;
137
+ }
138
+
139
+ .dg .status-warn {
140
+ color: #e3b341;
141
+ }
142
+
143
+ .dg .chips {
144
+ display: flex;
145
+ flex-wrap: wrap;
146
+ gap: 8px;
147
+ margin-top: 8px;
148
+ }
149
+
150
+ .dg .chip {
151
+ background: #0d141d;
152
+ border: 1px solid #223042;
153
+ border-radius: 999px;
154
+ padding: 3px 12px;
155
+ font-size: 12px;
156
+ }
157
+
158
+ .dg .count {
159
+ background: #0d141d;
160
+ border: 1px solid #223042;
161
+ border-radius: 999px;
162
+ padding: 0 9px;
163
+ font-size: 12px;
164
+ font-weight: 500;
165
+ color: #8aa0b8;
166
+ }
167
+
168
+ .dg table.grid {
169
+ width: 100%;
170
+ border-collapse: collapse;
171
+ font-size: 13px;
172
+ margin-top: 4px;
173
+ }
174
+
175
+ .dg table.grid th,
176
+ .dg table.grid td {
177
+ text-align: left;
178
+ padding: 6px 8px;
179
+ border-bottom: 1px solid #1c2735;
180
+ vertical-align: top;
181
+ }
182
+
183
+ .dg table.grid th {
184
+ color: #8aa0b8;
185
+ font-weight: 500;
186
+ }
187
+
188
+ .dg .diagram {
189
+ background: #0d141d;
190
+ border: 1px solid #223042;
191
+ border-radius: 8px;
192
+ padding: 12px;
193
+ overflow-x: auto;
194
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
195
+ font-size: 12.5px;
196
+ line-height: 1.5;
197
+ color: #b9c6d6;
198
+ margin: 0;
199
+ }
200
+
201
+ .dg .errors {
202
+ margin: 8px 0 0;
203
+ padding-left: 18px;
204
+ font-size: 13px;
205
+ }
206
+
207
+ .dg .errors li {
208
+ margin: 3px 0;
209
+ }
210
+
211
+ .dg code {
212
+ background: #0d141d;
213
+ border: 1px solid #223042;
214
+ border-radius: 4px;
215
+ padding: 1px 5px;
216
+ font-size: 12px;
217
+ }
218
+
219
+ .dg .muted {
220
+ color: #6b7f95;
221
+ }
222
+
223
+ .dg .ok {
224
+ color: #56d364;
225
+ }
226
+
227
+ .dg .warn {
228
+ color: #e3b341;
229
+ }
230
+
231
+ .dg .red {
232
+ color: #ff7b72;
233
+ }
234
+
235
+ .dg .pill {
236
+ display: inline-block;
237
+ border-radius: 999px;
238
+ padding: 1px 9px;
239
+ font-size: 11px;
240
+ font-weight: 600;
241
+ text-transform: uppercase;
242
+ letter-spacing: 0.03em;
243
+ }
244
+
245
+ .dg .pill-agent {
246
+ background: rgba(31, 111, 235, 0.18);
247
+ color: #58a6ff;
248
+ border: 1px solid rgba(31, 111, 235, 0.4);
249
+ }
250
+
251
+ .dg .pill-connector {
252
+ background: rgba(210, 153, 34, 0.18);
253
+ color: #e3b341;
254
+ border: 1px solid rgba(210, 153, 34, 0.4);
255
+ }
256
+
257
+ .dg .pill-wait {
258
+ background: rgba(63, 185, 80, 0.18);
259
+ color: #56d364;
260
+ border: 1px solid rgba(63, 185, 80, 0.4);
261
+ }
262
+
263
+ .dg .pill-human {
264
+ background: rgba(163, 113, 247, 0.18);
265
+ color: #bc8cff;
266
+ border: 1px solid rgba(163, 113, 247, 0.4);
267
+ }
268
+
269
+ .dg .pill-unknown {
270
+ background: rgba(139, 148, 158, 0.18);
271
+ color: #8b949e;
272
+ border: 1px solid rgba(139, 148, 158, 0.4);
273
+ }
@@ -0,0 +1,32 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Delivery graphs — compose · preview · dispatch (App View embed)</title>
7
+ <link rel="stylesheet" href="./delivery-graphs.css" />
8
+ <style>
9
+ html, body { margin: 0; height: 100%; background: #0b0f14; }
10
+ </style>
11
+ </head>
12
+ <body>
13
+ <!--
14
+ Console App-View embed (ADR 0057). The console loads this document into its App-View surface and
15
+ hands it a host element; we mount the SAME compose → preview → dispatch view via the SAME
16
+ ./mount.js as the standalone shell — only the host and the injected endpoint config differ, so the
17
+ view renders identically. When the console injects endpoint config via `window.__NANO_APP_VIEW__`,
18
+ it wins.
19
+ -->
20
+ <main id="delivery-graphs-root"></main>
21
+ <script type="module">
22
+ import { mountDeliveryGraphs } from "./mount.js";
23
+
24
+ const cfg = window.__NANO_APP_VIEW__ ?? {};
25
+ mountDeliveryGraphs(cfg.host ?? document.getElementById("delivery-graphs-root"), {
26
+ previewUrl: cfg.previewUrl,
27
+ dispatchUrl: cfg.dispatchUrl,
28
+ hookSecret: cfg.hookSecret,
29
+ });
30
+ </script>
31
+ </body>
32
+ </html>