@minicor/mcp-server 3.3.4 → 3.4.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/README.md +148 -25
- package/dist/__tests__/middleware-service-client.test.d.ts +2 -0
- package/dist/__tests__/middleware-service-client.test.d.ts.map +1 -0
- package/dist/__tests__/middleware-service-client.test.js +293 -0
- package/dist/__tests__/middleware-service-client.test.js.map +1 -0
- package/dist/codemode.js +10 -9
- package/dist/codemode.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +1 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/middleware-service-client.d.ts +258 -0
- package/dist/middleware-service-client.d.ts.map +1 -0
- package/dist/middleware-service-client.js +264 -0
- package/dist/middleware-service-client.js.map +1 -0
- package/dist/prompts/build-rpa.d.ts.map +1 -1
- package/dist/prompts/build-rpa.js +17 -0
- package/dist/prompts/build-rpa.js.map +1 -1
- package/dist/prompts/nestjs-to-job.d.ts +11 -0
- package/dist/prompts/nestjs-to-job.d.ts.map +1 -0
- package/dist/prompts/nestjs-to-job.js +110 -0
- package/dist/prompts/nestjs-to-job.js.map +1 -0
- package/dist/tools/jobs.d.ts +21 -0
- package/dist/tools/jobs.d.ts.map +1 -0
- package/dist/tools/jobs.js +470 -0
- package/dist/tools/jobs.js.map +1 -0
- package/dist/tools/vm-rpa.d.ts.map +1 -1
- package/dist/tools/vm-rpa.js +109 -25
- package/dist/tools/vm-rpa.js.map +1 -1
- package/package.json +1 -1
- package/skills/general/nestjs-middleware-to-job.md +82 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the middleware-service management API (Jobs + Test Cases).
|
|
3
|
+
*
|
|
4
|
+
* AUTH / TRANSPORT
|
|
5
|
+
* ----------------
|
|
6
|
+
* The middleware-service management API authenticates a *user* Bearer token
|
|
7
|
+
* (validated via /auth/me) plus a workspace-access check — it does NOT accept
|
|
8
|
+
* the MCP's raw API key. The MCP, however, runs with a user bearer token
|
|
9
|
+
* (from ~/.laminar/tokens.json / LAMINAR_ACCESS_TOKEN, refreshed in index.ts).
|
|
10
|
+
*
|
|
11
|
+
* BASE-URL SELECTION (mirrors how the frontend's server/jobs.ts +
|
|
12
|
+
* lib/jobs/types.ts resolve the middleware-service host — region-aware):
|
|
13
|
+
*
|
|
14
|
+
* 1. PROXY mode — set `MIDDLEWARE_SERVICE_PROXY_BASE` (the deployed frontend
|
|
15
|
+
* origin, e.g. https://app.laminar.run; region-aware via
|
|
16
|
+
* `MIDDLEWARE_SERVICE_PROXY_BASE_US` / `_CA`). Requests go to
|
|
17
|
+
* `${proxyBase}${proxyPrefix}/<path>` where `proxyPrefix` defaults to
|
|
18
|
+
* `/api/middleware` (override with `MIDDLEWARE_SERVICE_PROXY_PREFIX`), and
|
|
19
|
+
* the leading `/api/` is stripped from the management path (so
|
|
20
|
+
* `/api/workspaces/...` becomes `/api/middleware/workspaces/...`). The
|
|
21
|
+
* frontend validates the user bearer and forwards to the (IAM-private)
|
|
22
|
+
* middleware-service. NOTE: the frontend currently reaches middleware-service
|
|
23
|
+
* through Next.js *server actions* (server/jobs.ts), not an HTTP proxy
|
|
24
|
+
* route — there is no `/api/middleware/[...path]` handler yet. Once one is
|
|
25
|
+
* deployed, setting MIDDLEWARE_SERVICE_PROXY_BASE flips the MCP onto it with
|
|
26
|
+
* no code change.
|
|
27
|
+
*
|
|
28
|
+
* 2. DIRECT override — set `MIDDLEWARE_SERVICE_URL` (all regions) or
|
|
29
|
+
* `MIDDLEWARE_SERVICE_URL_US` / `_CA` (per region) to hit a specific
|
|
30
|
+
* reachable instance (dev/local). Requests go to `${base}/api/workspaces/...`.
|
|
31
|
+
*
|
|
32
|
+
* 3. DEFAULT (deployed) — the public middleware-service Cloud Run URL for the
|
|
33
|
+
* region (us / ca), hit directly at `${base}/api/workspaces/...`. These are
|
|
34
|
+
* the same hosts the frontend's lib/jobs/types.ts targets.
|
|
35
|
+
*
|
|
36
|
+
* Precedence: PROXY (1) > DIRECT override (2) > DEFAULT Cloud Run (3).
|
|
37
|
+
*
|
|
38
|
+
* A "Job" is a middleware *route* whose behavior is a structured `definition`
|
|
39
|
+
* (step graph) rather than freeform `handlerCode` (see middlewares/JOBS-DESIGN.md).
|
|
40
|
+
*/
|
|
41
|
+
// ─── Client ───────────────────────────────────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* Public middleware-service Cloud Run URLs (the deployed default), per region.
|
|
44
|
+
* These mirror the frontend's lib/jobs/types.ts `middlewareRuntimeHost` and the
|
|
45
|
+
* earlier MCP `SERVICE_BASE` map. Used when neither the proxy nor a direct
|
|
46
|
+
* override is configured.
|
|
47
|
+
*/
|
|
48
|
+
const DEFAULT_DIRECT_BASE = {
|
|
49
|
+
us: "https://middleware-service-489511347341.northamerica-northeast1.run.app",
|
|
50
|
+
ca: "https://middleware-service-ca-ernfuwetfa-nn.a.run.app",
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The frontend origin that fronts a middleware proxy, if one is deployed.
|
|
54
|
+
* Region-aware to match the platform's split frontends (e.g. app.laminar.run vs
|
|
55
|
+
* ca.app.laminar.run): `MIDDLEWARE_SERVICE_PROXY_BASE` applies to all regions,
|
|
56
|
+
* `MIDDLEWARE_SERVICE_PROXY_BASE_US` / `_CA` override it per region.
|
|
57
|
+
*/
|
|
58
|
+
function proxyBaseOverride(isCa) {
|
|
59
|
+
const generic = process.env.MIDDLEWARE_SERVICE_PROXY_BASE;
|
|
60
|
+
const regional = isCa
|
|
61
|
+
? process.env.MIDDLEWARE_SERVICE_PROXY_BASE_CA
|
|
62
|
+
: process.env.MIDDLEWARE_SERVICE_PROXY_BASE_US;
|
|
63
|
+
const base = regional ?? generic;
|
|
64
|
+
return base && base.trim() !== "" ? base : undefined;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* DIRECT base override (dev/local, or a specific reachable instance).
|
|
68
|
+
* `MIDDLEWARE_SERVICE_URL` applies to all regions; `MIDDLEWARE_SERVICE_URL_US` /
|
|
69
|
+
* `_CA` override it per region.
|
|
70
|
+
*/
|
|
71
|
+
function directBaseOverride(isCa) {
|
|
72
|
+
const generic = process.env.MIDDLEWARE_SERVICE_URL;
|
|
73
|
+
return isCa
|
|
74
|
+
? process.env.MIDDLEWARE_SERVICE_URL_CA ?? generic
|
|
75
|
+
: process.env.MIDDLEWARE_SERVICE_URL_US ?? generic;
|
|
76
|
+
}
|
|
77
|
+
export class MiddlewareServiceClient {
|
|
78
|
+
token;
|
|
79
|
+
base;
|
|
80
|
+
viaProxy;
|
|
81
|
+
/** Proxy path prefix (proxy mode only), e.g. `/api/middleware`. */
|
|
82
|
+
proxyPrefix;
|
|
83
|
+
constructor(apiBase, token) {
|
|
84
|
+
this.token = token;
|
|
85
|
+
const isCa = /\bca\./.test(apiBase);
|
|
86
|
+
const proxyBase = proxyBaseOverride(isCa);
|
|
87
|
+
const directOverride = directBaseOverride(isCa);
|
|
88
|
+
if (proxyBase) {
|
|
89
|
+
// (1) PROXY mode — route through the deployed frontend proxy.
|
|
90
|
+
this.base = proxyBase.replace(/\/+$/, "");
|
|
91
|
+
this.viaProxy = true;
|
|
92
|
+
this.proxyPrefix =
|
|
93
|
+
"/" +
|
|
94
|
+
(process.env.MIDDLEWARE_SERVICE_PROXY_PREFIX ?? "/api/middleware")
|
|
95
|
+
.replace(/^\/+/, "")
|
|
96
|
+
.replace(/\/+$/, "");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// (2) DIRECT override, else (3) the region's public Cloud Run default —
|
|
100
|
+
// both call `${base}/api/workspaces/...` directly with the user bearer.
|
|
101
|
+
this.base = (directOverride ?? (isCa ? DEFAULT_DIRECT_BASE.ca : DEFAULT_DIRECT_BASE.us)).replace(/\/+$/, "");
|
|
102
|
+
this.viaProxy = false;
|
|
103
|
+
this.proxyPrefix = "";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Proxy mode: `${proxyBase}${proxyPrefix}/<path>` with the leading `/api/`
|
|
108
|
+
* stripped (so `/api/workspaces/...` -> `/api/middleware/workspaces/...`),
|
|
109
|
+
* matching the agent/skills proxy convention.
|
|
110
|
+
* Direct mode: `${base}/api/workspaces/...`.
|
|
111
|
+
*/
|
|
112
|
+
async request(method, apiPath, body) {
|
|
113
|
+
const path = apiPath.startsWith("/") ? apiPath : `/${apiPath}`;
|
|
114
|
+
const url = this.viaProxy
|
|
115
|
+
? `${this.base}${this.proxyPrefix}/${path.replace(/^\/api\//, "").replace(/^\/+/, "")}`
|
|
116
|
+
: `${this.base}${path}`;
|
|
117
|
+
const res = await fetch(url, {
|
|
118
|
+
method,
|
|
119
|
+
headers: {
|
|
120
|
+
"Content-Type": "application/json",
|
|
121
|
+
Authorization: `Bearer ${this.token}`,
|
|
122
|
+
},
|
|
123
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
124
|
+
});
|
|
125
|
+
if (!res.ok) {
|
|
126
|
+
const errText = await res.text().catch(() => "");
|
|
127
|
+
throw new Error(`Middleware service ${method} ${apiPath} failed (${res.status}): ${errText}`);
|
|
128
|
+
}
|
|
129
|
+
if (res.status === 204)
|
|
130
|
+
return undefined;
|
|
131
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
132
|
+
if (contentType.includes("application/json"))
|
|
133
|
+
return res.json();
|
|
134
|
+
return res.text();
|
|
135
|
+
}
|
|
136
|
+
routesBase(workspaceId, middlewareId) {
|
|
137
|
+
return `/api/workspaces/${workspaceId}/middlewares/${middlewareId}/routes`;
|
|
138
|
+
}
|
|
139
|
+
middlewaresBase(workspaceId) {
|
|
140
|
+
return `/api/workspaces/${workspaceId}/middlewares`;
|
|
141
|
+
}
|
|
142
|
+
// ── Middlewares (routers) ──────────────────────────────────
|
|
143
|
+
/** List the routers (middlewares) in a workspace. */
|
|
144
|
+
async listMiddlewares(workspaceId) {
|
|
145
|
+
return this.request("GET", this.middlewaresBase(workspaceId));
|
|
146
|
+
}
|
|
147
|
+
/** Create a router (middleware) that jobs/routes attach to. */
|
|
148
|
+
async createMiddleware(workspaceId, body) {
|
|
149
|
+
return this.request("POST", this.middlewaresBase(workspaceId), body);
|
|
150
|
+
}
|
|
151
|
+
/** Auto-configure the router's workspace API key from the workspace's keys. */
|
|
152
|
+
async autoConfigureMiddleware(workspaceId, middlewareId) {
|
|
153
|
+
return this.request("POST", `${this.middlewaresBase(workspaceId)}/${middlewareId}/auto-configure`);
|
|
154
|
+
}
|
|
155
|
+
/** Fetch a single router (middleware) by id (list + filter; no single GET). */
|
|
156
|
+
async getMiddleware(workspaceId, middlewareId) {
|
|
157
|
+
const middlewares = await this.listMiddlewares(workspaceId);
|
|
158
|
+
return middlewares.find((m) => m.id === middlewareId);
|
|
159
|
+
}
|
|
160
|
+
/** Update a router (middleware) — e.g. to set its `workspaceApiKey`. */
|
|
161
|
+
async updateMiddleware(workspaceId, middlewareId, body) {
|
|
162
|
+
return this.request("PUT", `${this.middlewaresBase(workspaceId)}/${middlewareId}`, body);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Set the router's `workspaceApiKey`, then RE-READ the middleware and return
|
|
166
|
+
* it so callers can confirm the key actually stuck (non-null/non-empty).
|
|
167
|
+
*/
|
|
168
|
+
async setWorkspaceApiKey(workspaceId, middlewareId, workspaceApiKey) {
|
|
169
|
+
await this.updateMiddleware(workspaceId, middlewareId, { workspaceApiKey });
|
|
170
|
+
return this.getMiddleware(workspaceId, middlewareId);
|
|
171
|
+
}
|
|
172
|
+
// ── Jobs (routes) ──────────────────────────────────────────
|
|
173
|
+
/**
|
|
174
|
+
* Create a job-backed route. The management API exposes route creation under
|
|
175
|
+
* the middleware; passing a `definition` makes the route job-backed.
|
|
176
|
+
*/
|
|
177
|
+
async createJob(workspaceId, middlewareId, body) {
|
|
178
|
+
return this.request("POST", this.routesBase(workspaceId, middlewareId),
|
|
179
|
+
// Spread first, then apply the default so `method` is `body.method ?? "POST"`
|
|
180
|
+
// even when the caller passes `method: undefined`.
|
|
181
|
+
{ ...body, method: body.method ?? "POST" });
|
|
182
|
+
}
|
|
183
|
+
async updateJob(workspaceId, middlewareId, routeId, body) {
|
|
184
|
+
return this.request("PUT", `${this.routesBase(workspaceId, middlewareId)}/${routeId}`, body);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The contract notes there is currently NO `GET /routes/:routeId` (single).
|
|
188
|
+
* List all routes and filter client-side.
|
|
189
|
+
*/
|
|
190
|
+
async listRoutes(workspaceId, middlewareId) {
|
|
191
|
+
const data = await this.request("GET", this.routesBase(workspaceId, middlewareId));
|
|
192
|
+
if (Array.isArray(data))
|
|
193
|
+
return data;
|
|
194
|
+
return data?.routes ?? [];
|
|
195
|
+
}
|
|
196
|
+
async getJob(workspaceId, middlewareId, routeId) {
|
|
197
|
+
const routes = await this.listRoutes(workspaceId, middlewareId);
|
|
198
|
+
return routes.find((r) => r.id === routeId);
|
|
199
|
+
}
|
|
200
|
+
async runJob(workspaceId, middlewareId, routeId, input, trigger = "mcp") {
|
|
201
|
+
return this.request("POST", `${this.routesBase(workspaceId, middlewareId)}/${routeId}/run`, { input, trigger });
|
|
202
|
+
}
|
|
203
|
+
async getExecution(workspaceId, middlewareId, routeId, executionId) {
|
|
204
|
+
return this.request("GET", `${this.routesBase(workspaceId, middlewareId)}/${routeId}/executions/${executionId}`);
|
|
205
|
+
}
|
|
206
|
+
async listExecutions(workspaceId, middlewareId, routeId, opts) {
|
|
207
|
+
const params = new URLSearchParams();
|
|
208
|
+
if (opts?.page != null)
|
|
209
|
+
params.set("page", String(opts.page));
|
|
210
|
+
if (opts?.size != null)
|
|
211
|
+
params.set("size", String(opts.size));
|
|
212
|
+
if (opts?.status)
|
|
213
|
+
params.set("status", opts.status);
|
|
214
|
+
if (opts?.search)
|
|
215
|
+
params.set("search", opts.search);
|
|
216
|
+
const qs = params.toString();
|
|
217
|
+
return this.request("GET", `${this.routesBase(workspaceId, middlewareId)}/${routeId}/executions${qs ? `?${qs}` : ""}`);
|
|
218
|
+
}
|
|
219
|
+
// ── Test cases ─────────────────────────────────────────────
|
|
220
|
+
async listTestCases(workspaceId, middlewareId, routeId) {
|
|
221
|
+
return this.request("GET", `${this.routesBase(workspaceId, middlewareId)}/${routeId}/test-cases`);
|
|
222
|
+
}
|
|
223
|
+
async addTestCase(workspaceId, middlewareId, routeId, body) {
|
|
224
|
+
return this.request("POST", `${this.routesBase(workspaceId, middlewareId)}/${routeId}/test-cases`, body);
|
|
225
|
+
}
|
|
226
|
+
async updateTestCase(workspaceId, middlewareId, routeId, testCaseId, body) {
|
|
227
|
+
return this.request("PUT", `${this.routesBase(workspaceId, middlewareId)}/${routeId}/test-cases/${testCaseId}`, body);
|
|
228
|
+
}
|
|
229
|
+
// ── Test runs ──────────────────────────────────────────────
|
|
230
|
+
async startTestRun(workspaceId, middlewareId, routeId, testCaseIds) {
|
|
231
|
+
return this.request("POST", `${this.routesBase(workspaceId, middlewareId)}/${routeId}/test-runs`, testCaseIds ? { testCaseIds } : {});
|
|
232
|
+
}
|
|
233
|
+
async getTestRun(workspaceId, middlewareId, routeId, testRunId) {
|
|
234
|
+
return this.request("GET", `${this.routesBase(workspaceId, middlewareId)}/${routeId}/test-runs/${testRunId}`);
|
|
235
|
+
}
|
|
236
|
+
// ── Polling helpers ────────────────────────────────────────
|
|
237
|
+
async pollExecution(workspaceId, middlewareId, routeId, executionId, opts) {
|
|
238
|
+
const interval = opts?.intervalMs ?? 3000;
|
|
239
|
+
const timeout = opts?.timeoutMs ?? 5 * 60 * 1000;
|
|
240
|
+
const deadline = Date.now() + timeout;
|
|
241
|
+
let last;
|
|
242
|
+
while (Date.now() < deadline) {
|
|
243
|
+
last = await this.getExecution(workspaceId, middlewareId, routeId, executionId);
|
|
244
|
+
if (last.status !== "running" && last.status !== "queued")
|
|
245
|
+
return last;
|
|
246
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
247
|
+
}
|
|
248
|
+
throw new Error(`Timed out polling job execution ${executionId} after ${timeout}ms (last status: ${last?.status ?? "unknown"})`);
|
|
249
|
+
}
|
|
250
|
+
async pollTestRun(workspaceId, middlewareId, routeId, testRunId, opts) {
|
|
251
|
+
const interval = opts?.intervalMs ?? 3000;
|
|
252
|
+
const timeout = opts?.timeoutMs ?? 10 * 60 * 1000;
|
|
253
|
+
const deadline = Date.now() + timeout;
|
|
254
|
+
let last;
|
|
255
|
+
while (Date.now() < deadline) {
|
|
256
|
+
last = await this.getTestRun(workspaceId, middlewareId, routeId, testRunId);
|
|
257
|
+
if (last.testRun.status !== "running")
|
|
258
|
+
return last;
|
|
259
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
260
|
+
}
|
|
261
|
+
throw new Error(`Timed out polling test run ${testRunId} after ${timeout}ms (last status: ${last?.testRun.status ?? "unknown"})`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=middleware-service-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-service-client.js","sourceRoot":"","sources":["../src/middleware-service-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-rpa.d.ts","sourceRoot":"","sources":["../../src/prompts/build-rpa.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,wBAAgB,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"build-rpa.d.ts","sourceRoot":"","sources":["../../src/prompts/build-rpa.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,wBAAgB,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,QAAQ,QA+K5C"}
|
|
@@ -40,6 +40,20 @@ ${appName}
|
|
|
40
40
|
ID: ${workspaceId}${workflowId ? `\nExisting workflow ID: ${workflowId} (add steps to this workflow)` : "\nCreate a new workflow for this automation."}
|
|
41
41
|
${modeSection}
|
|
42
42
|
|
|
43
|
+
## WORK BACKWARDS — Define the Test Case FIRST
|
|
44
|
+
|
|
45
|
+
Before building anything, write down what "done" means as a concrete, runnable test:
|
|
46
|
+
|
|
47
|
+
1. **State the contract.** What input does this automation take, and what output proves it worked? Write the input JSON and the expected output shape.
|
|
48
|
+
2. **Author the green-gate.** A workflow's behavior is locked in by a **Job** with **test cases**. As the very first build artifact:
|
|
49
|
+
- Register a thin Job for this automation: \`register_job\` with a \`definition\` whose single \`workflow\` step calls this workflow (use a placeholder \`workflowId\` you'll fill in, or the existing one). The job is the testable wrapper above the workflow.
|
|
50
|
+
- Add the test case NOW: \`add_test_case\` with the input from step 1 and assertions \`[{ name, expr }]\` describing the expected output (e.g. \`output.patientId != null\`, \`output.status === "ok"\`). \`expr\` is a TS boolean over \`{ input, output, ctx, steps }\`.
|
|
51
|
+
3. **The test is RED.** It will fail until the workflow exists and is correct. That red test is your target.
|
|
52
|
+
4. **Build the workflow** (the loop below) to turn the test GREEN.
|
|
53
|
+
5. **Green-gate:** the build is NOT done until \`run_tests\` reports \`green: true\`. Re-run \`run_tests\` after every meaningful change.
|
|
54
|
+
|
|
55
|
+
Use test patients only — never real patient data. Pick inputs/slots far in the future so tests don't collide with real schedules.
|
|
56
|
+
|
|
43
57
|
## MANDATORY FIRST STEP — Load Testing Skill
|
|
44
58
|
|
|
45
59
|
Before doing ANYTHING else, run: \`get_skill("rpa-testing-workflow")\`
|
|
@@ -121,6 +135,9 @@ b. Poll \`get_execution_status\` every 5-10s, \`vm_screenshot\` at intervals to
|
|
|
121
135
|
c. On failure → \`diagnose_execution\` + \`vm_screenshot\`, fix with \`update_flow\`, re-run FULL workflow
|
|
122
136
|
d. Iterate until full workflow passes. **Do NOT declare done until it passes.**${isProduction ? "\ne. Run the workflow a SECOND time to verify idempotency." : ""}
|
|
123
137
|
|
|
138
|
+
### 6b. GREEN-GATE — run_tests
|
|
139
|
+
The workflow passing once is not enough. Point the Job's workflow step at the finished \`workflowId\` (\`update_job\` if needed), then run \`run_tests\` for the job. **The build is done ONLY when \`run_tests\` returns \`green: true\`** and every test case passes. On a red result, drill into the failing case's job execution → step → Minicor run, fix, and re-run \`run_tests\`. Add more \`add_test_case\` entries for edge cases (empty results, not-found, second-run idempotency) and keep them green.
|
|
140
|
+
|
|
124
141
|
### 7. Iterate with the User
|
|
125
142
|
${isProduction ? `
|
|
126
143
|
### 8. Production Hardening
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-rpa.js","sourceRoot":"","sources":["../../src/prompts/build-rpa.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,UAAU,QAAQ,CAAC,EAAE,MAAM,EAAY;IAC3C,MAAM,CAAC,MAAM,CACX,oBAAoB,EACpB,wSAAwS,EACxS;QACE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACxD,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,QAAQ,CACP,iHAAiH,CAClH;QACH,OAAO,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,CACP,uFAAuF,CACxF;QACH,UAAU,EAAE,CAAC;aACV,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,sEAAsE,CACvE;QACH,IAAI,EAAE,CAAC;aACJ,IAAI,CAAC,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;aAC3B,QAAQ,EAAE;aACV,QAAQ,CACP,4HAA4H,CAC7H;KACJ,EACD,KAAK,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE;QACzD,MAAM,YAAY,GAAG,IAAI,KAAK,YAAY,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,KAAK,KAAK,CAAC;QAC7B,MAAM,WAAW,GAAG,IAAI;YACtB,CAAC,CAAC,aAAa,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,0CAA0C,CAAC,CAAC,CAAC,iFAAiF,EAAE;YAC9K,CAAC,CAAC,yIAAyI,CAAC;QAE9I,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAe;oBACrB,OAAO,EAAE;wBACP,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE;;;EAGlB,IAAI;;;EAGJ,OAAO;;;MAGH,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,2BAA2B,UAAU,+BAA+B,CAAC,CAAC,CAAC,8CAA8C;EACpJ,WAAW
|
|
1
|
+
{"version":3,"file":"build-rpa.js","sourceRoot":"","sources":["../../src/prompts/build-rpa.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,UAAU,QAAQ,CAAC,EAAE,MAAM,EAAY;IAC3C,MAAM,CAAC,MAAM,CACX,oBAAoB,EACpB,wSAAwS,EACxS;QACE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACxD,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,QAAQ,CACP,iHAAiH,CAClH;QACH,OAAO,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,CACP,uFAAuF,CACxF;QACH,UAAU,EAAE,CAAC;aACV,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,sEAAsE,CACvE;QACH,IAAI,EAAE,CAAC;aACJ,IAAI,CAAC,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;aAC3B,QAAQ,EAAE;aACV,QAAQ,CACP,4HAA4H,CAC7H;KACJ,EACD,KAAK,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE;QACzD,MAAM,YAAY,GAAG,IAAI,KAAK,YAAY,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,KAAK,KAAK,CAAC;QAC7B,MAAM,WAAW,GAAG,IAAI;YACtB,CAAC,CAAC,aAAa,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,0CAA0C,CAAC,CAAC,CAAC,iFAAiF,EAAE;YAC9K,CAAC,CAAC,yIAAyI,CAAC;QAE9I,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAe;oBACrB,OAAO,EAAE;wBACP,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE;;;EAGlB,IAAI;;;EAGJ,OAAO;;;MAGH,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,2BAA2B,UAAU,+BAA+B,CAAC,CAAC,CAAC,8CAA8C;EACpJ,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0EAqC6D,YAAY,CAAC,CAAC,CAAC,sGAAsG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,oGAAoG;;;;;;;;;;;;;;;;;;;;;;;;;YAyBvS,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iFAiC8D,YAAY,CAAC,CAAC,CAAC,4DAA4D,CAAC,CAAC,CAAC,EAAE;;;;;;EAM/J,YAAY,CAAC,CAAC,CAAC;;;;;;CAMhB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;;;;CAIhB;;;qFAGoF,YAAY,CAAC,CAAC,CAAC,8EAA8E,CAAC,CAAC,CAAC,EAAE;;sIAEjD;qBACzH;iBACF;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ToolDeps } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Prompt: turn an existing NestJS customer middleware into a Job.
|
|
4
|
+
*
|
|
5
|
+
* Given a Nest.js controller that orchestrates Laminar workflows (login →
|
|
6
|
+
* lookup → create, with header munging and error mapping), this prompt walks
|
|
7
|
+
* the agent through mapping that endpoint into a structured JobDefinition step
|
|
8
|
+
* graph and registering it with register_job.
|
|
9
|
+
*/
|
|
10
|
+
export declare function register({ server }: ToolDeps): void;
|
|
11
|
+
//# sourceMappingURL=nestjs-to-job.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nestjs-to-job.d.ts","sourceRoot":"","sources":["../../src/prompts/nestjs-to-job.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,QAAQ,QAwG5C"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Prompt: turn an existing NestJS customer middleware into a Job.
|
|
4
|
+
*
|
|
5
|
+
* Given a Nest.js controller that orchestrates Laminar workflows (login →
|
|
6
|
+
* lookup → create, with header munging and error mapping), this prompt walks
|
|
7
|
+
* the agent through mapping that endpoint into a structured JobDefinition step
|
|
8
|
+
* graph and registering it with register_job.
|
|
9
|
+
*/
|
|
10
|
+
export function register({ server }) {
|
|
11
|
+
server.prompt("nestjs-middleware-to-job", "Convert an existing NestJS customer middleware endpoint (a controller method that orchestrates Minicor workflows) into a Job — a structured JobDefinition step graph (workflow steps + code blocks + when/forEach) registered via register_job, with test cases as the green-gate.", {
|
|
12
|
+
controllerPath: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe("Path to the NestJS controller file (e.g. src/controllers/ams.controller.ts)"),
|
|
15
|
+
endpoint: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Which controller method/route to convert (e.g. POST /ams/account-lookup). Omit to convert all."),
|
|
19
|
+
workspaceId: z.string().describe("Minicor workspace ID"),
|
|
20
|
+
middlewareId: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Existing router (middleware) ID to attach the job route to. Omit to create one with create_router first."),
|
|
24
|
+
}, async ({ controllerPath, endpoint, workspaceId, middlewareId }) => {
|
|
25
|
+
return {
|
|
26
|
+
messages: [
|
|
27
|
+
{
|
|
28
|
+
role: "user",
|
|
29
|
+
content: {
|
|
30
|
+
type: "text",
|
|
31
|
+
text: `You are converting a NestJS customer middleware endpoint into a **Job**.
|
|
32
|
+
|
|
33
|
+
## What you're converting
|
|
34
|
+
- Controller: \`${controllerPath}\`
|
|
35
|
+
- Endpoint: ${endpoint ?? "ALL methods in the controller"}
|
|
36
|
+
- Workspace: ${workspaceId}
|
|
37
|
+
- Router (middleware): ${middlewareId ?? "none yet — create one with create_router first, then use its id"}
|
|
38
|
+
|
|
39
|
+
## Mental model (read this first)
|
|
40
|
+
- **A Job ≡ a middleware route** whose behavior is a structured \`definition\` (a step graph) instead of freeform handler code.
|
|
41
|
+
- A Job sits **above** workflows. A job step either **calls an entire Minicor workflow** (\`kind: "workflow"\`, by \`workflowId\`) or **runs a code block** (\`kind: "code"\`, a \`(ctx) => ...\` string). The job never touches a workflow's internal steps — a workflow is an opaque callable with an input and an output.
|
|
42
|
+
- Keep job graphs small: typically \`login → lookup/create → shape output\`.
|
|
43
|
+
|
|
44
|
+
## How a NestJS controller maps to a JobDefinition
|
|
45
|
+
A typical controller method does: validate the body (DTO) → run a login workflow → run the target workflow → munge the result (headers, status) → return / throw. Map each piece:
|
|
46
|
+
|
|
47
|
+
| NestJS | JobDefinition |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| \`@Body() dto\` + DTO class | \`inputSchema\` (JSON Schema) |
|
|
50
|
+
| return type / DTO | \`outputSchema\` (JSON Schema) |
|
|
51
|
+
| \`await laminar.executeWorkflow({ workflowId: LOGIN, data:{} })\` | a \`kind:"workflow"\` step with \`cache\` (login gate) |
|
|
52
|
+
| \`await laminar.executeWorkflow({ workflowId: TARGET, data })\` | a \`kind:"workflow"\` step; \`input\` is \`{{ctx.input.*}}\` templating or a CodeBlock |
|
|
53
|
+
| header munging (e.g. \`x-recording-url\` → \`web_agent_video_url\`) | a \`kind:"code"\` step, or the \`output\` CodeBlock |
|
|
54
|
+
| \`if (cond) ...\` branching | step \`when\` (skip) / \`onError\` / \`{ goto }\` |
|
|
55
|
+
| looping over a collection | step \`forEach\` |
|
|
56
|
+
| \`throw new HttpException(...)\` mapping | engine sets failed on \`onError:"abort"\`; \`output\` block decides \`partial\`/\`review\` |
|
|
57
|
+
| serialize per desktop (login lands before run) | \`resource.lockKey\` |
|
|
58
|
+
|
|
59
|
+
## Worked example — FurtherAI AMS360 \`runWithLogin\` → Job
|
|
60
|
+
A controller that does "ensure AMS360 login, then run the target workflow, then merge the recording URL header" becomes:
|
|
61
|
+
|
|
62
|
+
\`\`\`json
|
|
63
|
+
{
|
|
64
|
+
"region": "us",
|
|
65
|
+
"resource": { "lockKey": "ams360-desktop" },
|
|
66
|
+
"inputSchema": { "type": "object", "properties": { "insured_name": { "type": "string" } }, "required": ["insured_name"] },
|
|
67
|
+
"outputSchema": { "type": "object" },
|
|
68
|
+
"steps": [
|
|
69
|
+
{
|
|
70
|
+
"id": "login", "name": "AMS360 Login", "kind": "workflow",
|
|
71
|
+
"workflowId": 0,
|
|
72
|
+
"input": {},
|
|
73
|
+
"cache": { "key": "ams360-login", "ttlMs": 1800000 },
|
|
74
|
+
"onError": "abort"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "lookup", "name": "Account Lookup", "kind": "workflow",
|
|
78
|
+
"workflowId": 2078,
|
|
79
|
+
"saveAs": "lookup",
|
|
80
|
+
"input": { "task_type": "ams_account_lookup", "insured_name": "{{ctx.input.insured_name}}" },
|
|
81
|
+
"onError": "abort"
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"output": "(ctx) => { const r = ctx.lookup?.data ?? ctx.lookup; const rec = ctx.lookup?.headers?.['x-recording-url']; return rec ? { ...r, web_agent_video_url: String(rec) } : r; }"
|
|
85
|
+
}
|
|
86
|
+
\`\`\`
|
|
87
|
+
(Replace the placeholder \`workflowId: 0\` login id and \`2078\` with the real ids from \`WORKFLOW_IDS\` / \`list_workflows\`. Map every constant in the controller's config to a real workflow id.)
|
|
88
|
+
|
|
89
|
+
## Procedure
|
|
90
|
+
1. **Read the controller** at \`${controllerPath}\` and its DTOs/constants. List each endpoint, the workflow ids it calls, the call order, and the result-shaping logic.
|
|
91
|
+
2. **Resolve workflow ids** with \`list_workflows(workspaceId: ${workspaceId})\` — match the controller's \`WORKFLOW_IDS\`/constants to live ids.
|
|
92
|
+
3. **Author inputSchema/outputSchema** from the DTOs.
|
|
93
|
+
4. **Build the step graph** per the mapping table. Login/smart-launch → a \`cache\`d workflow step. Each downstream workflow → a workflow step with templated \`input\`. Header/result munging → the \`output\` CodeBlock (or a \`code\` step).
|
|
94
|
+
5. **Define the green-gate FIRST**: \`add_test_case\` with a representative input (use test data only) and assertions over the expected output (e.g. \`output.web_agent_video_url != null\`, \`output.account != null\`).
|
|
95
|
+
6. **Register** with \`register_job(workspaceId, middlewareId, path, definition)\`. Use ${middlewareId ? `middlewareId ${middlewareId}` : "the middlewareId of the router you created"}.
|
|
96
|
+
7. **Run it**: \`run_job\` with the test input; inspect the returned JobExecution (per-step \`workflowExecutionId\`/\`region\` deep-link to the real Minicor runs).
|
|
97
|
+
8. **Green-gate**: \`run_tests\` until \`green: true\`. Drill into any failing case → its job execution → step → Minicor run; fix \`update_job\` and re-run.
|
|
98
|
+
|
|
99
|
+
## Rules
|
|
100
|
+
- One job per controller endpoint. Keep the graph shallow.
|
|
101
|
+
- Never hardcode credentials — keep secrets in the workflow's config store (\`resource.configStoreId\` / step \`configStoreId\`).
|
|
102
|
+
- Preserve the controller's ordering guarantees with \`resource.lockKey\` (serialize per desktop) so login always lands before the work.
|
|
103
|
+
- Test data only — never real patient/customer data.`,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=nestjs-to-job.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nestjs-to-job.js","sourceRoot":"","sources":["../../src/prompts/nestjs-to-job.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAE,MAAM,EAAY;IAC3C,MAAM,CAAC,MAAM,CACX,0BAA0B,EAC1B,oRAAoR,EACpR;QACE,cAAc,EAAE,CAAC;aACd,MAAM,EAAE;aACR,QAAQ,CAAC,6EAA6E,CAAC;QAC1F,QAAQ,EAAE,CAAC;aACR,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,gGAAgG,CAAC;QAC7G,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACxD,YAAY,EAAE,CAAC;aACZ,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,0GAA0G,CAAC;KACxH,EACD,KAAK,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,EAAE,EAAE;QAChE,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAe;oBACrB,OAAO,EAAE;wBACP,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE;;;kBAGF,cAAc;cAClB,QAAQ,IAAI,+BAA+B;eAC1C,WAAW;yBACD,YAAY,IAAI,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAqDxE,cAAc;iEACiB,WAAW;;;;0FAIc,YAAY,CAAC,CAAC,CAAC,gBAAgB,YAAY,EAAE,CAAC,CAAC,CAAC,4CAA4C;;;;;;;;qDAQjI;qBACxC;iBACF;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tools for Jobs + Test Cases.
|
|
3
|
+
*
|
|
4
|
+
* A Job is a middleware route whose behavior is a structured `definition`
|
|
5
|
+
* (step graph) instead of freeform handler code. Jobs sit *above* workflows: a
|
|
6
|
+
* job step calls an entire Minicor workflow (by workflowId) or runs a code
|
|
7
|
+
* block. Test cases attach to a job (route): a named input + code-block
|
|
8
|
+
* assertions `[{ name, expr }]` evaluated against the execution.
|
|
9
|
+
*
|
|
10
|
+
* See middlewares/JOBS-DESIGN.md for the full model + API contract (v1).
|
|
11
|
+
*
|
|
12
|
+
* Auth/transport is handled by MiddlewareServiceClient, which by default calls
|
|
13
|
+
* the region's public middleware-service Cloud Run directly with the MCP's user
|
|
14
|
+
* bearer token. Set MIDDLEWARE_SERVICE_PROXY_BASE to route through a deployed
|
|
15
|
+
* frontend proxy instead, or MIDDLEWARE_SERVICE_URL[_US|_CA] to hit a specific
|
|
16
|
+
* reachable instance (dev/local). See middleware-service-client.ts for the full
|
|
17
|
+
* precedence.
|
|
18
|
+
*/
|
|
19
|
+
import type { ToolDeps } from "../types.js";
|
|
20
|
+
export declare function register(deps: ToolDeps): void;
|
|
21
|
+
//# sourceMappingURL=jobs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../src/tools/jobs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAOH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAmG5C,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,QAsdtC"}
|