@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/CHANGELOG.md +15 -0
- package/app/deliveryGraphCompiler.test.ts +70 -42
- package/app/deliveryGraphCompiler.ts +58 -2
- package/app/deliveryGraphRun.test.ts +2 -2
- package/app/deliveryRunner.test.ts +28 -17
- package/app/deliveryRunner.ts +15 -6
- package/app/pollUserTasks.test.ts +49 -1
- package/app/service.ts +16 -5
- package/openapi.yaml +14 -0
- package/operations/compileDeliveryGraph.test.ts +3 -0
- package/operations/compileDeliveryGraph.ts +1 -1
- package/operations/dispatchDeliveryGraph.ts +1 -1
- package/operations/previewDeliveryGraph.test.ts +9 -0
- package/operations/previewDeliveryGraph.ts +7 -1
- package/operations/startDeliveryGraph.ts +1 -1
- package/package.json +3 -2
- package/pages/delivery-graphs/delivery-graphs.css +273 -0
- package/pages/delivery-graphs/embed.html +32 -0
- package/pages/delivery-graphs/mount.js +365 -0
- package/pages/delivery-graphs/standalone.html +38 -0
- package/pages/delivery-graphs.page.json +7 -31
- package/scripts/pages-contract.test.ts +8 -16
- package/test/delivery-graphs-embed.test.ts +85 -0
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.
|
|
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>
|