@flowriki/agent 0.1.0-beta.58 → 0.1.0-beta.59
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.
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
textIndependent,
|
|
69
69
|
validateActors,
|
|
70
70
|
webCloudOf
|
|
71
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-MJXAVVGP.js";
|
|
72
72
|
|
|
73
73
|
// src/engine/machine.ts
|
|
74
74
|
import fs from "fs";
|
|
@@ -76,7 +76,7 @@ import os from "os";
|
|
|
76
76
|
import path from "path";
|
|
77
77
|
import { createRequire } from "module";
|
|
78
78
|
import { spawnSync } from "child_process";
|
|
79
|
-
var AGENT_VERSION = "0.1.0-beta.
|
|
79
|
+
var AGENT_VERSION = "0.1.0-beta.59";
|
|
80
80
|
async function prepareBrowsers() {
|
|
81
81
|
if (browsersReady()) {
|
|
82
82
|
return { ok: true, already: true, message: "chromium is already installed" };
|
|
@@ -136,7 +136,7 @@ async function checkMachine() {
|
|
|
136
136
|
}
|
|
137
137
|
} catch {
|
|
138
138
|
}
|
|
139
|
-
const { mobileDoctor: mobileDoctor2 } = await import("./dist-
|
|
139
|
+
const { mobileDoctor: mobileDoctor2 } = await import("./dist-BB5LTG3H.js");
|
|
140
140
|
return {
|
|
141
141
|
agentVersion: AGENT_VERSION,
|
|
142
142
|
node: process.versions.node,
|
|
@@ -10210,6 +10210,16 @@ ${brief}` : ""
|
|
|
10210
10210
|
...opts.sessionId ? ["--resume", opts.sessionId] : []
|
|
10211
10211
|
];
|
|
10212
10212
|
}
|
|
10213
|
+
function clearOld(dir) {
|
|
10214
|
+
try {
|
|
10215
|
+
for (const name of fs43.readdirSync(dir)) {
|
|
10216
|
+
if (!/^mcp-.+\.json$/.test(name)) continue;
|
|
10217
|
+
const file2 = path42.join(dir, name);
|
|
10218
|
+
if (Date.now() - fs43.statSync(file2).mtimeMs > 24 * 60 * 6e4) fs43.rmSync(file2, { force: true });
|
|
10219
|
+
}
|
|
10220
|
+
} catch {
|
|
10221
|
+
}
|
|
10222
|
+
}
|
|
10213
10223
|
function eventsFromLine(askId, line) {
|
|
10214
10224
|
let parsed;
|
|
10215
10225
|
try {
|
|
@@ -10378,10 +10388,11 @@ function createClaudeCli(options) {
|
|
|
10378
10388
|
fs43.mkdirSync(scratchRoot2, { recursive: true, mode: 448 });
|
|
10379
10389
|
const dir = path42.join(scratchRoot2, `project-${ask.projectId.replace(/[^A-Za-z0-9_-]/g, "_")}`);
|
|
10380
10390
|
fs43.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
10381
|
-
|
|
10391
|
+
clearOld(dir);
|
|
10392
|
+
const mcpConfigPath = path42.join(dir, `mcp-${ask.askId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 80)}.json`);
|
|
10382
10393
|
fs43.writeFileSync(
|
|
10383
10394
|
mcpConfigPath,
|
|
10384
|
-
JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: { command: options.mcp.command, args: options.mcp.args } } }),
|
|
10395
|
+
JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: { command: options.mcp.command, args: options.mcp.args, env: { FLOWRIKI_ASK_ID: ask.askId } } } }),
|
|
10385
10396
|
{ mode: 384 }
|
|
10386
10397
|
);
|
|
10387
10398
|
return launch(ask, dir, mcpConfigPath, laneSession(ask, dir));
|
|
@@ -687,7 +687,13 @@ var ExecutionConfigSchema = z3.object({
|
|
|
687
687
|
var BUSINESS_KEYWORDS = ["Given", "When", "Then", "And", "But"];
|
|
688
688
|
var BusinessStepSchema = z22.object({
|
|
689
689
|
keyword: z22.enum(BUSINESS_KEYWORDS),
|
|
690
|
-
text: z22.string().min(1).max(2e3)
|
|
690
|
+
text: z22.string().min(1).max(2e3),
|
|
691
|
+
/** THE CASE STEPS THAT CARRY IT OUT, once it is automated — each as
|
|
692
|
+
* `<occurrenceId>/<stepId>`, so a person reads the case line by line with
|
|
693
|
+
* the steps under the words they prove. Said by whoever automated it (the
|
|
694
|
+
* assistant, when it builds the case). A step whose row is gone is simply
|
|
695
|
+
* not found, and shows with the steps no line claims. */
|
|
696
|
+
steps: z22.array(z22.string().min(1).max(200)).max(500).optional()
|
|
691
697
|
}).strict();
|
|
692
698
|
var TestCaseOriginSchema = z22.object({
|
|
693
699
|
kind: z22.enum(["import", "assistant"]),
|
package/dist/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
runtimeValue,
|
|
25
25
|
start,
|
|
26
26
|
status
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-HXUFFE2E.js";
|
|
28
28
|
import {
|
|
29
29
|
CLOUD_PROVIDERS,
|
|
30
30
|
CLOUD_PROVIDER_NAME,
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
slugify,
|
|
52
52
|
stepRefusedOn,
|
|
53
53
|
targetFor
|
|
54
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-MJXAVVGP.js";
|
|
55
55
|
|
|
56
56
|
// src/cli.ts
|
|
57
57
|
import { parseArgs } from "util";
|
|
@@ -137,6 +137,7 @@ function serve(tools, info, io = {}) {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
// src/mcp/service-client.ts
|
|
140
|
+
var ASK_ID = (process.env["FLOWRIKI_ASK_ID"] ?? "").trim().slice(0, 200);
|
|
140
141
|
var ServiceClient = class {
|
|
141
142
|
constructor(connection, fetcher = fetch) {
|
|
142
143
|
this.connection = connection;
|
|
@@ -159,7 +160,10 @@ var ServiceClient = class {
|
|
|
159
160
|
headers: {
|
|
160
161
|
"x-flowriki-device": this.connection.deviceId,
|
|
161
162
|
"x-flowriki-device-secret": this.connection.secret,
|
|
162
|
-
"content-type": "application/json"
|
|
163
|
+
"content-type": "application/json",
|
|
164
|
+
// The dashboard's question these tools are answering, when they are:
|
|
165
|
+
// what they build is filed on it, for the person to watch.
|
|
166
|
+
...ASK_ID ? { "x-flowriki-ask": ASK_ID } : {}
|
|
163
167
|
}
|
|
164
168
|
});
|
|
165
169
|
const text2 = await res.text();
|
|
@@ -255,6 +259,12 @@ var ServiceClient = class {
|
|
|
255
259
|
);
|
|
256
260
|
}
|
|
257
261
|
/** Build a test case from flows — as a dry run, or saved. */
|
|
262
|
+
/** The flow being recorded, as it stands, for the dashboard's job. Only
|
|
263
|
+
* when these tools answer a question asked in the dashboard. */
|
|
264
|
+
async reportDraft(projectId, draft) {
|
|
265
|
+
if (!ASK_ID) return;
|
|
266
|
+
await this.call(`/api/agent/jobs/draft?projectId=${encodeURIComponent(projectId)}`, { method: "POST", body: JSON.stringify({ draft }) });
|
|
267
|
+
}
|
|
258
268
|
buildCase(projectId, spec, dryRun) {
|
|
259
269
|
return this.call(`/api/agent/cases?projectId=${encodeURIComponent(projectId)}`, { method: "POST", body: JSON.stringify({ ...spec, dryRun }) });
|
|
260
270
|
}
|
|
@@ -497,11 +507,21 @@ function deviceFrom(said, platform, devices, mobile) {
|
|
|
497
507
|
async function devicesSummary() {
|
|
498
508
|
return listDevices().catch(() => []);
|
|
499
509
|
}
|
|
500
|
-
function describeRows(rows) {
|
|
501
|
-
return rows.map((r) =>
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
510
|
+
function describeRows(rows, testCase) {
|
|
511
|
+
return rows.map((r, i) => {
|
|
512
|
+
const steps = testCase?.occurrences[i]?.steps;
|
|
513
|
+
if (r.steps?.length && steps?.length === r.steps.length) {
|
|
514
|
+
const on = new Map(r.checks.map((c) => [c.n, c.on]));
|
|
515
|
+
return [
|
|
516
|
+
`Row ${r.row}: "${r.flow}" (${r.browser})`,
|
|
517
|
+
...r.steps.map((st, j) => ` step ${st.n}. ${describe(steps[j])}${st.check !== void 0 ? ` \u2014 check ${st.check} ${on.get(st.check) ? "[on]" : "[off]"}` : ""}`)
|
|
518
|
+
].join("\n");
|
|
519
|
+
}
|
|
520
|
+
return [
|
|
521
|
+
`Row ${r.row}: "${r.flow}" (${r.browser})`,
|
|
522
|
+
...r.checks.length ? r.checks.map((c) => ` ${c.on ? "[on] " : "[off]"} ${c.n}. ${c.check}`) : [" (no checks in this flow)"]
|
|
523
|
+
].join("\n");
|
|
524
|
+
}).join("\n");
|
|
505
525
|
}
|
|
506
526
|
function describe(step) {
|
|
507
527
|
const target = (step.name ? `${step.role || "element"} "${step.name}"` : step.role || "") + (step.nth !== void 0 ? ` (nth ${step.nth})` : "");
|
|
@@ -634,6 +654,7 @@ var AuthoringSession = class _AuthoringSession {
|
|
|
634
654
|
const left = opts.fresh ? null : this.readDraft(input.projectId, input.name);
|
|
635
655
|
if (opts.fresh) this.dropDraft(input.projectId, input.name);
|
|
636
656
|
const opened = isMobile(input.platform) ? await this.startMobile(input.platform, catalog) : await this.startWeb(input, catalog);
|
|
657
|
+
this.report();
|
|
637
658
|
if (!left?.steps.length) return opened;
|
|
638
659
|
return this.resume(left.steps, opened);
|
|
639
660
|
}
|
|
@@ -686,6 +707,7 @@ ${view}`;
|
|
|
686
707
|
const draft = this.draft;
|
|
687
708
|
if (!draft) return;
|
|
688
709
|
this.verified = null;
|
|
710
|
+
this.report();
|
|
689
711
|
try {
|
|
690
712
|
const file = _AuthoringSession.draftFile(draft.projectId, draft.name);
|
|
691
713
|
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 448 });
|
|
@@ -694,6 +716,36 @@ ${view}`;
|
|
|
694
716
|
} catch {
|
|
695
717
|
}
|
|
696
718
|
}
|
|
719
|
+
/** The draft as it stands, to the dashboard question this answers — at
|
|
720
|
+
* most every half second, a recording keeps a step a second. */
|
|
721
|
+
reportTimer = null;
|
|
722
|
+
report() {
|
|
723
|
+
if (this.reportTimer) return;
|
|
724
|
+
this.reportTimer = setTimeout(() => void this.sendReport(), 500);
|
|
725
|
+
this.reportTimer.unref?.();
|
|
726
|
+
}
|
|
727
|
+
/** Whatever is waiting to be reported, now — before a save or a close, so
|
|
728
|
+
* the dashboard ends on the flow as it was saved. */
|
|
729
|
+
async sendReport() {
|
|
730
|
+
if (this.reportTimer) {
|
|
731
|
+
clearTimeout(this.reportTimer);
|
|
732
|
+
this.reportTimer = null;
|
|
733
|
+
}
|
|
734
|
+
{
|
|
735
|
+
const draft = this.draft;
|
|
736
|
+
if (!draft) return;
|
|
737
|
+
const featureName = draft.featureName ?? this.catalog?.features?.find((f) => f.id === draft.featureId)?.name ?? "";
|
|
738
|
+
if (typeof this.service.reportDraft !== "function") return;
|
|
739
|
+
await this.service.reportDraft(draft.projectId, {
|
|
740
|
+
name: draft.name,
|
|
741
|
+
feature: featureName,
|
|
742
|
+
platform: draft.platform ?? "web",
|
|
743
|
+
after: (draft.after ?? []).map((a) => a.name),
|
|
744
|
+
signsIn: Boolean(draft.signIn),
|
|
745
|
+
steps: withoutTyped(draft.steps).map((st) => st.secret ? { ...st, value: "" } : st)
|
|
746
|
+
}).catch(() => void 0);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
697
749
|
readDraft(projectId, name) {
|
|
698
750
|
try {
|
|
699
751
|
const raw = JSON.parse(fs.readFileSync(_AuthoringSession.draftFile(projectId, name), "utf8"));
|
|
@@ -1019,6 +1071,7 @@ Fix the step that failed (remove_step, then act again) and save again \u2014 or
|
|
|
1019
1071
|
replayed = " It was replayed from the start first and passed.";
|
|
1020
1072
|
}
|
|
1021
1073
|
const checksOff = draft.steps.map((step) => step.type === "assert" ? { ...step, proposals: step.proposals.map((p) => ({ ...p, enabled: false })) } : step);
|
|
1074
|
+
if (this.reportTimer) await this.sendReport();
|
|
1022
1075
|
const checkCount = checksOff.filter((step) => step.type === "assert").length;
|
|
1023
1076
|
const secretValues = {};
|
|
1024
1077
|
for (const step of checksOff) {
|
|
@@ -1052,7 +1105,7 @@ Review and publish it here: ${review}${note}`;
|
|
|
1052
1105
|
async previewCase(projectId, spec) {
|
|
1053
1106
|
const built = await this.service.buildCase(projectId, spec, true);
|
|
1054
1107
|
return `"${spec.name}" in ${spec.folder} would be:
|
|
1055
|
-
${describeRows(built.rows)}`;
|
|
1108
|
+
${describeRows(built.rows, built.testCase)}`;
|
|
1056
1109
|
}
|
|
1057
1110
|
/** RUN THE CASE, THEN SAVE IT. Built as a dry run, replayed in a fresh
|
|
1058
1111
|
* browser exactly as a test run does it, and saved onto Claude's working
|
|
@@ -1102,7 +1155,7 @@ ${describeRows(built.rows)}`;
|
|
|
1102
1155
|
return `The case did not pass, so it was NOT saved.
|
|
1103
1156
|
${lines.join("\n")}
|
|
1104
1157
|
|
|
1105
|
-
${describeRows(built.rows)}`;
|
|
1158
|
+
${describeRows(built.rows, testCase)}`;
|
|
1106
1159
|
}
|
|
1107
1160
|
const saved = await this.service.buildCase(projectId, spec, false);
|
|
1108
1161
|
const review = `${this.service.origin}${saved.path}`;
|
|
@@ -1111,6 +1164,7 @@ ${lines.join("\n")}
|
|
|
1111
1164
|
Review and publish it here: ${review}`;
|
|
1112
1165
|
}
|
|
1113
1166
|
async close() {
|
|
1167
|
+
if (this.reportTimer) await this.sendReport();
|
|
1114
1168
|
const run = this.run;
|
|
1115
1169
|
const mobile = this.mobile;
|
|
1116
1170
|
this.run = null;
|
|
@@ -1445,10 +1499,18 @@ HOW TO WORK:
|
|
|
1445
1499
|
catalog, then type {{its saveAs name}} into the field that needs it.
|
|
1446
1500
|
5. Add checks for what each step proves (a heading, a message, a value, the
|
|
1447
1501
|
URL). replay_flow, then save_flow. A flow must record at least one check.
|
|
1448
|
-
6. Build each test case: preview_case lists its rows and every
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1502
|
+
6. Build each test case: preview_case lists its rows and every step, numbered
|
|
1503
|
+
across the case, with each check's number in its row; save_case with the
|
|
1504
|
+
checks to switch on runs the whole case and saves it only if it passed.
|
|
1505
|
+
Pass businessSteps to both: the case's Given / When / Then lines (its own,
|
|
1506
|
+
from read_test_cases, when it has them) each with the step numbers that
|
|
1507
|
+
carry it out, so the person reads every line with its steps under it.
|
|
1508
|
+
Give the person the review link.
|
|
1509
|
+
7. Saving the same name again replaces it on the working branch. ONE VERSION
|
|
1510
|
+
of each scenario per platform: redo a flow or case under its existing name
|
|
1511
|
+
(project_catalog lists them) rather than saving another beside it. When the
|
|
1512
|
+
project tests several platforms, name flows "<scenario> \u2014 Web", "\u2014 Android",
|
|
1513
|
+
"\u2014 iOS". Old branches
|
|
1452
1514
|
of yours? gather_branches puts them into one; discard_branch deletes one \u2014
|
|
1453
1515
|
only after the person asks or agrees.
|
|
1454
1516
|
Test data you invent (a new user's email or password for a sign-up) is fine to
|
|
@@ -1480,6 +1542,19 @@ var CASE_INPUT = {
|
|
|
1480
1542
|
additionalProperties: false
|
|
1481
1543
|
}
|
|
1482
1544
|
},
|
|
1545
|
+
businessSteps: {
|
|
1546
|
+
type: "array",
|
|
1547
|
+
description: 'The case in Given / When / Then lines, in order, each with the step numbers from preview_case that carry it out \u2014 e.g. {"step": "Given the user is on the sign-in page", "steps": [1, 2]}. A case that already has business steps keeps its own words: give the same lines.',
|
|
1548
|
+
items: {
|
|
1549
|
+
type: "object",
|
|
1550
|
+
properties: {
|
|
1551
|
+
step: { type: "string", description: "One line, starting Given, When, Then, And or But." },
|
|
1552
|
+
steps: { type: "array", items: { type: "number" }, description: "The step numbers (across the case) that carry it out." }
|
|
1553
|
+
},
|
|
1554
|
+
required: ["step"],
|
|
1555
|
+
additionalProperties: false
|
|
1556
|
+
}
|
|
1557
|
+
},
|
|
1483
1558
|
branch: { type: "string", description: "Optional: save onto this branch (chg_\u2026) instead of the working one." },
|
|
1484
1559
|
projectId: { type: "string" }
|
|
1485
1560
|
},
|
|
@@ -1491,6 +1566,7 @@ var caseSpec = (args) => ({
|
|
|
1491
1566
|
folder: String(args["folder"] ?? ""),
|
|
1492
1567
|
...args["description"] ? { description: String(args["description"]) } : {},
|
|
1493
1568
|
rows: Array.isArray(args["rows"]) ? args["rows"] : [],
|
|
1569
|
+
...Array.isArray(args["businessSteps"]) ? { businessSteps: args["businessSteps"] } : {},
|
|
1494
1570
|
...args["branch"] ? { changeId: String(args["branch"]) } : {}
|
|
1495
1571
|
});
|
|
1496
1572
|
var text = (value, isError = false) => ({ content: [{ type: "text", text: value }], ...isError ? { isError } : {} });
|
package/dist/engine.js
CHANGED
package/package.json
CHANGED