@bpmnkit/proxy 0.0.16 → 0.0.17
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/dist/index.js +186 -3
- package/dist/prompt.js +57 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -4,13 +4,13 @@ import http from "node:http";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { Bpmn, expand, optimize } from "@bpmnkit/core";
|
|
7
|
+
import { Bpmn, applyOperations, compactify, expand, optimize } from "@bpmnkit/core";
|
|
8
8
|
import { createClientFromProfile } from "@bpmnkit/profiles";
|
|
9
9
|
import { getActiveName, getActiveProfile, getAuthHeader, getProfile, listProfiles, } from "@bpmnkit/profiles";
|
|
10
10
|
import * as claude from "./adapters/claude.js";
|
|
11
11
|
import * as copilot from "./adapters/copilot.js";
|
|
12
12
|
import * as gemini from "./adapters/gemini.js";
|
|
13
|
-
import { buildIncidentSystemPrompt, buildIncidentUserMessage, buildMcpExplainPrompt, buildMcpImprovePrompt, buildMcpSystemPrompt, buildOperateChatSystemPrompt, buildSearchSystemPrompt, buildSystemPrompt, } from "./prompt.js";
|
|
13
|
+
import { buildImproveSystemPrompt, buildImproveUserMessage, buildIncidentSystemPrompt, buildIncidentUserMessage, buildMcpExplainPrompt, buildMcpImprovePrompt, buildMcpSystemPrompt, buildOperateChatSystemPrompt, buildSearchSystemPrompt, buildSystemPrompt, } from "./prompt.js";
|
|
14
14
|
const PORT = process.env.AI_SERVER_PORT ? Number(process.env.AI_SERVER_PORT) : 3033;
|
|
15
15
|
// Resolve the compiled mcp-server entry point relative to this file.
|
|
16
16
|
// When bundled as bundle.cjs, import.meta.url ends with .cjs → use mcp-server.cjs.
|
|
@@ -62,6 +62,24 @@ function extractCompactDiagram(text) {
|
|
|
62
62
|
}
|
|
63
63
|
return null;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Extract a BpmnOperation[] from LLM text output.
|
|
67
|
+
* Looks for the first ```json block containing a JSON array.
|
|
68
|
+
*/
|
|
69
|
+
function extractOperations(text) {
|
|
70
|
+
const match = /```json\s*\n([\s\S]*?)\n```/.exec(text);
|
|
71
|
+
if (!match?.[1])
|
|
72
|
+
return null;
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(match[1]);
|
|
75
|
+
if (Array.isArray(parsed))
|
|
76
|
+
return parsed;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
/* invalid JSON */
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
65
83
|
const server = http.createServer(async (req, res) => {
|
|
66
84
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
67
85
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
@@ -260,6 +278,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
260
278
|
apiType: p.apiType,
|
|
261
279
|
baseUrl: p.config.baseUrl ?? null,
|
|
262
280
|
authType: p.config.auth?.type ?? "none",
|
|
281
|
+
description: p.description,
|
|
282
|
+
tags: p.tags,
|
|
263
283
|
}));
|
|
264
284
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
265
285
|
res.end(JSON.stringify(payload));
|
|
@@ -731,15 +751,136 @@ const server = http.createServer(async (req, res) => {
|
|
|
731
751
|
res.end(JSON.stringify({ endpoint: finalSpec.endpoint, filter: finalSpec.filter, items, total }));
|
|
732
752
|
return;
|
|
733
753
|
}
|
|
754
|
+
// ── POST /improve — structured AI-assisted BPMN improvement ─────────────────
|
|
755
|
+
// Token-efficient alternative to /chat?action=improve.
|
|
756
|
+
// Phase 1: optimize() auto-fix (no AI). Phase 2: AI outputs BpmnOperation[].
|
|
757
|
+
// Emits SSE: tokens (explanation) + ops event + xml event + done.
|
|
758
|
+
if (url.pathname === "/improve" && req.method === "POST") {
|
|
759
|
+
const body = await readBody(req);
|
|
760
|
+
let context;
|
|
761
|
+
let instruction;
|
|
762
|
+
let backend;
|
|
763
|
+
try {
|
|
764
|
+
const parsed = JSON.parse(body);
|
|
765
|
+
context = parsed.context;
|
|
766
|
+
instruction = parsed.instruction ?? null;
|
|
767
|
+
backend = parsed.backend ?? null;
|
|
768
|
+
}
|
|
769
|
+
catch {
|
|
770
|
+
res.writeHead(400);
|
|
771
|
+
res.end("Bad Request");
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const available = await detectAll();
|
|
775
|
+
const detected = backend
|
|
776
|
+
? (available.find((a) => a.name === backend) ?? available[0])
|
|
777
|
+
: available[0];
|
|
778
|
+
if (!detected) {
|
|
779
|
+
res.writeHead(503);
|
|
780
|
+
res.end("No AI CLI available. Install claude, copilot, or gemini.");
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
// ── Phase 1: auto-fix ─────────────────────────────────────────────────
|
|
784
|
+
let fixedCompact = context;
|
|
785
|
+
let autoFixCount = 0;
|
|
786
|
+
try {
|
|
787
|
+
const defs = expand(context);
|
|
788
|
+
const report = optimize(defs);
|
|
789
|
+
const fixable = report.findings
|
|
790
|
+
.filter((f) => f.applyFix)
|
|
791
|
+
.sort((a, b) => {
|
|
792
|
+
const ord = { error: 0, warning: 1, info: 2 };
|
|
793
|
+
return (ord[a.severity] ?? 2) - (ord[b.severity] ?? 2);
|
|
794
|
+
});
|
|
795
|
+
for (const f of fixable)
|
|
796
|
+
f.applyFix?.(defs);
|
|
797
|
+
autoFixCount = fixable.length;
|
|
798
|
+
if (autoFixCount > 0) {
|
|
799
|
+
fixedCompact = compactify(defs);
|
|
800
|
+
console.log(`[server] /improve → auto-fixed ${autoFixCount} issue(s)`);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
catch (err) {
|
|
804
|
+
console.error("[server] /improve auto-fix failed:", String(err));
|
|
805
|
+
}
|
|
806
|
+
// ── Phase 2: collect remaining findings ───────────────────────────────
|
|
807
|
+
const findings = [];
|
|
808
|
+
try {
|
|
809
|
+
const remaining = optimize(expand(fixedCompact));
|
|
810
|
+
for (const f of remaining.findings) {
|
|
811
|
+
findings.push({
|
|
812
|
+
category: f.category,
|
|
813
|
+
severity: f.severity,
|
|
814
|
+
message: f.message,
|
|
815
|
+
suggestion: f.suggestion,
|
|
816
|
+
elementIds: f.elementIds,
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
/* non-fatal */
|
|
822
|
+
}
|
|
823
|
+
console.log(`[server] /improve → adapter: ${detected.name}, findings: ${findings.length}, autoFix: ${autoFixCount}`);
|
|
824
|
+
res.writeHead(200, {
|
|
825
|
+
"Content-Type": "text/event-stream",
|
|
826
|
+
"Cache-Control": "no-cache",
|
|
827
|
+
Connection: "keep-alive",
|
|
828
|
+
});
|
|
829
|
+
// ── Phase 3: AI call — outputs explanation + ```json operations block ─
|
|
830
|
+
const systemPrompt = buildImproveSystemPrompt();
|
|
831
|
+
const improveCtx = {
|
|
832
|
+
compact: fixedCompact,
|
|
833
|
+
findings,
|
|
834
|
+
autoFixCount,
|
|
835
|
+
instruction,
|
|
836
|
+
};
|
|
837
|
+
const userMessage = buildImproveUserMessage(improveCtx);
|
|
838
|
+
const accumulated = [];
|
|
839
|
+
try {
|
|
840
|
+
await detected.adapter.stream([{ role: "user", content: userMessage }], systemPrompt, null, (token) => {
|
|
841
|
+
accumulated.push(token);
|
|
842
|
+
res.write(`data: ${JSON.stringify({ type: "token", text: token })}\n\n`);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
catch (err) {
|
|
846
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
847
|
+
console.error(`[server] /improve adapter error: ${msg}`);
|
|
848
|
+
res.write(`data: ${JSON.stringify({ type: "error", message: msg })}\n\n`);
|
|
849
|
+
res.write(`data: ${JSON.stringify({ type: "done" })}\n\n`);
|
|
850
|
+
res.end();
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
// ── Phase 4: parse ops, apply, expand, emit ───────────────────────────
|
|
854
|
+
const fullText = accumulated.join("");
|
|
855
|
+
const ops = extractOperations(fullText) ?? [];
|
|
856
|
+
res.write(`data: ${JSON.stringify({ type: "ops", ops, autoFixCount })}\n\n`);
|
|
857
|
+
if (ops.length > 0 || autoFixCount > 0) {
|
|
858
|
+
try {
|
|
859
|
+
const finalCompact = ops.length > 0 ? applyOperations(fixedCompact, ops) : fixedCompact;
|
|
860
|
+
const xml = Bpmn.export(expand(finalCompact));
|
|
861
|
+
res.write(`data: ${JSON.stringify({ type: "xml", xml })}\n\n`);
|
|
862
|
+
console.log(`[server] /improve → ${ops.length} ops applied, XML emitted`);
|
|
863
|
+
}
|
|
864
|
+
catch (err) {
|
|
865
|
+
console.error("[server] /improve expand failed:", String(err));
|
|
866
|
+
res.write(`data: ${JSON.stringify({ type: "error", message: `Failed to apply operations: ${String(err)}` })}\n\n`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
res.write(`data: ${JSON.stringify({ type: "done" })}\n\n`);
|
|
870
|
+
res.end();
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
734
873
|
// ── POST /operate/chat — operations-context AI chat ──────────────────────────
|
|
735
874
|
if (url.pathname === "/operate/chat" && req.method === "POST") {
|
|
736
875
|
const body = await readBody(req);
|
|
737
876
|
let messages;
|
|
738
877
|
let stats;
|
|
878
|
+
let backend;
|
|
739
879
|
try {
|
|
740
880
|
const parsed = JSON.parse(body);
|
|
741
881
|
messages = parsed.messages;
|
|
742
882
|
stats = parsed.stats ?? null;
|
|
883
|
+
backend = parsed.backend ?? null;
|
|
743
884
|
}
|
|
744
885
|
catch {
|
|
745
886
|
res.writeHead(400);
|
|
@@ -747,7 +888,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
747
888
|
return;
|
|
748
889
|
}
|
|
749
890
|
const available = await detectAll();
|
|
750
|
-
const detected =
|
|
891
|
+
const detected = backend
|
|
892
|
+
? (available.find((a) => a.name === backend) ?? available[0])
|
|
893
|
+
: available[0];
|
|
751
894
|
if (!detected) {
|
|
752
895
|
res.writeHead(503);
|
|
753
896
|
res.end("No AI adapter available. Install claude, copilot, or gemini.");
|
|
@@ -823,6 +966,46 @@ const server = http.createServer(async (req, res) => {
|
|
|
823
966
|
res.end(await upstream.text());
|
|
824
967
|
return;
|
|
825
968
|
}
|
|
969
|
+
// ── POST /http-request — CORS bypass for wasm worker REST connectors ─────
|
|
970
|
+
if (url.pathname === "/http-request" && req.method === "POST") {
|
|
971
|
+
const body = await readBody(req);
|
|
972
|
+
let targetUrl;
|
|
973
|
+
let method;
|
|
974
|
+
let headers;
|
|
975
|
+
let reqBody;
|
|
976
|
+
try {
|
|
977
|
+
const parsed = JSON.parse(body);
|
|
978
|
+
targetUrl = parsed.url;
|
|
979
|
+
method = (parsed.method ?? "GET").toUpperCase();
|
|
980
|
+
headers = parsed.headers ?? {};
|
|
981
|
+
reqBody = parsed.body;
|
|
982
|
+
}
|
|
983
|
+
catch {
|
|
984
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
985
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
console.log(`[http-request] ${method} ${targetUrl}`);
|
|
989
|
+
try {
|
|
990
|
+
const upstream = await fetch(targetUrl, {
|
|
991
|
+
method,
|
|
992
|
+
headers,
|
|
993
|
+
body: method !== "GET" && method !== "HEAD" ? reqBody : undefined,
|
|
994
|
+
});
|
|
995
|
+
const responseText = await upstream.text();
|
|
996
|
+
const contentType = upstream.headers.get("content-type") ?? "application/json";
|
|
997
|
+
res.writeHead(upstream.status, {
|
|
998
|
+
"Content-Type": contentType,
|
|
999
|
+
"Access-Control-Allow-Origin": "*",
|
|
1000
|
+
});
|
|
1001
|
+
res.end(responseText);
|
|
1002
|
+
}
|
|
1003
|
+
catch (err) {
|
|
1004
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
1005
|
+
res.end(JSON.stringify({ error: `Upstream unreachable: ${String(err)}` }));
|
|
1006
|
+
}
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
826
1009
|
res.writeHead(404);
|
|
827
1010
|
res.end("Not Found");
|
|
828
1011
|
});
|
package/dist/prompt.js
CHANGED
|
@@ -222,6 +222,63 @@ export function buildOperateChatSystemPrompt(stats) {
|
|
|
222
222
|
lines.push("## Available actions (user performs these in the UI)", "- View and cancel running instances → Instances page", "- View and retry failed incidents → Incidents page", "- Claim and complete user tasks → Tasks page", "- Start new process instances → Definitions page → Start Instance button", "- Deploy new processes → Models page → Deploy button", "", "When asked to do something, explain which UI page to visit and what to click.", "If asked about a specific instance/incident/task, say you can only see aggregate counts unless you query for details.");
|
|
223
223
|
return lines.join("\n");
|
|
224
224
|
}
|
|
225
|
+
// ── Improve prompt builders ───────────────────────────────────────────────────
|
|
226
|
+
const OPERATIONS_FORMAT = `
|
|
227
|
+
BpmnOperation types (use stable element IDs, never array positions):
|
|
228
|
+
{ "op": "rename", "id": "...", "name": "new name" }
|
|
229
|
+
{ "op": "update", "id": "...", "patch": { /* partial CompactElement fields */ } }
|
|
230
|
+
{ "op": "delete", "id": "..." }
|
|
231
|
+
{ "op": "insert", "element": { /* full CompactElement with new unique id */ }, "after"?: "id", "before"?: "id", "parent"?: "sub-process-id" }
|
|
232
|
+
{ "op": "add_flow", "from": "id", "to": "id", "condition"?: "FEEL expr", "name"?: "...", "parent"?: "sub-process-id" }
|
|
233
|
+
{ "op": "delete_flow", "id": "..." }
|
|
234
|
+
{ "op": "redirect_flow", "id": "...", "from"?: "new-source-id", "to"?: "new-target-id" }`.trim();
|
|
235
|
+
export function buildImproveSystemPrompt() {
|
|
236
|
+
return [
|
|
237
|
+
"You are a BPMN 2.0 process improvement expert.",
|
|
238
|
+
"",
|
|
239
|
+
"Output format — follow this EXACTLY:",
|
|
240
|
+
"1. Write 2–4 sentences explaining what you will change and why.",
|
|
241
|
+
"2. Then output a single ```json block containing ONLY a JSON array of BpmnOperation objects.",
|
|
242
|
+
"",
|
|
243
|
+
OPERATIONS_FORMAT,
|
|
244
|
+
"",
|
|
245
|
+
"Rules:",
|
|
246
|
+
"- Reference only IDs that exist in the provided model (except 'insert' adds new IDs).",
|
|
247
|
+
"- For 'insert': generate a short, unique camelCase ID (e.g. 'task_notify', 'gw_valid').",
|
|
248
|
+
"- Output ONLY the operations array in the ```json block — no prose inside it.",
|
|
249
|
+
"- If no changes are needed, output [].",
|
|
250
|
+
"",
|
|
251
|
+
"Apply Camunda BPMN best practices:",
|
|
252
|
+
' • Tasks: "Verb Object" — "Verify Invoice", "Send Notification"',
|
|
253
|
+
' • Start events: past participle — "Order Received", "Payment Initiated"',
|
|
254
|
+
' • End events: object + state — "Order Fulfilled", "Payment Failed"',
|
|
255
|
+
' • XOR split gateways: yes/no question ending in "?" — "Invoice valid?"',
|
|
256
|
+
' • XOR split outgoing flows: label with condition — "Yes"/"No", "Approved"/"Rejected"',
|
|
257
|
+
" • Join gateways: no label",
|
|
258
|
+
" • Never send >1 incoming flow to a task without a join gateway first",
|
|
259
|
+
].join("\n");
|
|
260
|
+
}
|
|
261
|
+
export function buildImproveUserMessage(ctx) {
|
|
262
|
+
const lines = [];
|
|
263
|
+
if (ctx.autoFixCount > 0) {
|
|
264
|
+
lines.push(`Note: ${ctx.autoFixCount} structural issue(s) were already auto-fixed before this analysis.`, "");
|
|
265
|
+
}
|
|
266
|
+
lines.push("Current process model:", "```json", JSON.stringify(ctx.compact, null, 2), "```", "");
|
|
267
|
+
if (ctx.findings.length > 0) {
|
|
268
|
+
lines.push("Detected issues to fix:");
|
|
269
|
+
for (const f of ctx.findings) {
|
|
270
|
+
const els = f.elementIds.length > 0 ? ` [elements: ${f.elementIds.join(", ")}]` : "";
|
|
271
|
+
lines.push(`- [${f.severity}/${f.category}] ${f.message}${els}`);
|
|
272
|
+
lines.push(` → ${f.suggestion}`);
|
|
273
|
+
}
|
|
274
|
+
lines.push("");
|
|
275
|
+
}
|
|
276
|
+
if (ctx.instruction) {
|
|
277
|
+
lines.push(`Additional instructions: ${ctx.instruction}`, "");
|
|
278
|
+
}
|
|
279
|
+
lines.push("Explain your changes, then output the BpmnOperation array in a ```json block.");
|
|
280
|
+
return lines.join("\n");
|
|
281
|
+
}
|
|
225
282
|
// ── Fallback prompt builders (for non-MCP adapters like Gemini) ───────────────
|
|
226
283
|
/** Full system prompt for non-MCP adapters that must return a CompactDiagram JSON block. */
|
|
227
284
|
export function buildSystemPrompt(context) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bpmnkit/proxy",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "Local proxy server for BPMN Kit — AI bridge (SSE/MCP) and Camunda API proxy using stored CLI profiles",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@bpmnkit/api": "0.0.13",
|
|
20
|
-
"@bpmnkit/core": "0.0.
|
|
21
|
-
"@bpmnkit/profiles": "0.0.
|
|
20
|
+
"@bpmnkit/core": "0.0.16",
|
|
21
|
+
"@bpmnkit/profiles": "0.0.11"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public"
|