@flowriki/agent 0.1.0-beta.59 → 0.1.0-beta.60
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-WQ22OUIQ.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.60";
|
|
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-IBNVN2BD.js");
|
|
140
140
|
return {
|
|
141
141
|
agentVersion: AGENT_VERSION,
|
|
142
142
|
node: process.versions.node,
|
|
@@ -1757,6 +1757,7 @@ var ImportDraftSchema = z9.object({
|
|
|
1757
1757
|
createdAt: z9.string(),
|
|
1758
1758
|
updatedAt: z9.string()
|
|
1759
1759
|
}).strict();
|
|
1760
|
+
var UPDATE_DOCUMENT_MAX = 3e5;
|
|
1760
1761
|
var CaseUpdateRequestSchema = z9.object({
|
|
1761
1762
|
id: z9.string().regex(/^upd_[A-Za-z0-9]+$/),
|
|
1762
1763
|
projectId: z9.string().min(1),
|
|
@@ -1775,6 +1776,9 @@ var CaseUpdateRequestSchema = z9.object({
|
|
|
1775
1776
|
note: z9.string().max(2e3).default("")
|
|
1776
1777
|
}).strict()).default([]),
|
|
1777
1778
|
note: z9.string().max(4e3).default(""),
|
|
1779
|
+
/** A CHANGE DOCUMENT the person gave with the request — a spec, release
|
|
1780
|
+
* notes, a ticket — as text, for the assistant to read what changed. */
|
|
1781
|
+
document: z9.object({ name: z9.string().max(300), text: z9.string().max(UPDATE_DOCUMENT_MAX) }).strict().optional(),
|
|
1778
1782
|
createdAt: z9.string(),
|
|
1779
1783
|
updatedAt: z9.string()
|
|
1780
1784
|
}).strict();
|
|
@@ -1801,6 +1805,21 @@ var webCloudOf = (config) => {
|
|
|
1801
1805
|
const parsed = WebCloudSchema.safeParse(config?.webCloud);
|
|
1802
1806
|
return parsed.success ? parsed.data : null;
|
|
1803
1807
|
};
|
|
1808
|
+
var ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " " };
|
|
1809
|
+
function decodeEntities(text) {
|
|
1810
|
+
let out = text;
|
|
1811
|
+
for (let i = 0; i < 3 && /&(#\d+|#x[0-9a-f]+|[a-z]+);/i.test(out); i++) {
|
|
1812
|
+
out = out.replace(/&(#\d+|#x[0-9a-f]+|[a-z]+);/gi, (whole, code) => {
|
|
1813
|
+
if (code[0] === "#") {
|
|
1814
|
+
const n = code[1]?.toLowerCase() === "x" ? parseInt(code.slice(2), 16) : parseInt(code.slice(1), 10);
|
|
1815
|
+
return Number.isFinite(n) && n > 0 && n < 1114112 ? String.fromCodePoint(n) : whole;
|
|
1816
|
+
}
|
|
1817
|
+
return ENTITIES[code.toLowerCase()] ?? whole;
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
return out;
|
|
1821
|
+
}
|
|
1822
|
+
var nameKey = (name) => decodeEntities(name).toLowerCase().replace(/&/g, " and ").replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
1804
1823
|
|
|
1805
1824
|
// ../engine-appium/dist/index.js
|
|
1806
1825
|
import path5 from "path";
|
|
@@ -4174,6 +4193,7 @@ export {
|
|
|
4174
4193
|
runWhereOf,
|
|
4175
4194
|
WEB_CLOUD_BROWSER_NAME,
|
|
4176
4195
|
webCloudOf,
|
|
4196
|
+
nameKey,
|
|
4177
4197
|
proposeAssertions,
|
|
4178
4198
|
hasObservableChange,
|
|
4179
4199
|
normalizeStartUrl,
|
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-BYFYSV5H.js";
|
|
28
28
|
import {
|
|
29
29
|
CLOUD_PROVIDERS,
|
|
30
30
|
CLOUD_PROVIDER_NAME,
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
mobileDoctor,
|
|
42
42
|
mobileOf,
|
|
43
43
|
mobileSetup,
|
|
44
|
+
nameKey,
|
|
44
45
|
openMobileSession,
|
|
45
46
|
parseGherkin,
|
|
46
47
|
performMobileStep,
|
|
@@ -51,7 +52,7 @@ import {
|
|
|
51
52
|
slugify,
|
|
52
53
|
stepRefusedOn,
|
|
53
54
|
targetFor
|
|
54
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-WQ22OUIQ.js";
|
|
55
56
|
|
|
56
57
|
// src/cli.ts
|
|
57
58
|
import { parseArgs } from "util";
|
|
@@ -265,6 +266,11 @@ var ServiceClient = class {
|
|
|
265
266
|
if (!ASK_ID) return;
|
|
266
267
|
await this.call(`/api/agent/jobs/draft?projectId=${encodeURIComponent(projectId)}`, { method: "POST", body: JSON.stringify({ draft }) });
|
|
267
268
|
}
|
|
269
|
+
/** The test case being built, as it stands, for the dashboard's job. */
|
|
270
|
+
async reportCase(projectId, live) {
|
|
271
|
+
if (!ASK_ID) return;
|
|
272
|
+
await this.call(`/api/agent/jobs/case?projectId=${encodeURIComponent(projectId)}`, { method: "POST", body: JSON.stringify({ case: live }) });
|
|
273
|
+
}
|
|
268
274
|
buildCase(projectId, spec, dryRun) {
|
|
269
275
|
return this.call(`/api/agent/cases?projectId=${encodeURIComponent(projectId)}`, { method: "POST", body: JSON.stringify({ ...spec, dryRun }) });
|
|
270
276
|
}
|
|
@@ -731,12 +737,10 @@ ${view}`;
|
|
|
731
737
|
clearTimeout(this.reportTimer);
|
|
732
738
|
this.reportTimer = null;
|
|
733
739
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
if (!draft) return;
|
|
740
|
+
const draft = this.draft;
|
|
741
|
+
if (draft) {
|
|
737
742
|
const featureName = draft.featureName ?? this.catalog?.features?.find((f) => f.id === draft.featureId)?.name ?? "";
|
|
738
|
-
if (typeof this.service.reportDraft
|
|
739
|
-
await this.service.reportDraft(draft.projectId, {
|
|
743
|
+
if (typeof this.service.reportDraft === "function") await this.service.reportDraft(draft.projectId, {
|
|
740
744
|
name: draft.name,
|
|
741
745
|
feature: featureName,
|
|
742
746
|
platform: draft.platform ?? "web",
|
|
@@ -745,6 +749,82 @@ ${view}`;
|
|
|
745
749
|
steps: withoutTyped(draft.steps).map((st) => st.secret ? { ...st, value: "" } : st)
|
|
746
750
|
}).catch(() => void 0);
|
|
747
751
|
}
|
|
752
|
+
if (this.liveCase) await this.sendCaseReport();
|
|
753
|
+
}
|
|
754
|
+
// ── The test case a recording is for ────────────────────────────────
|
|
755
|
+
//
|
|
756
|
+
// A PERSON THINKS IN TEST CASES. The flows are how the assistant makes one
|
|
757
|
+
// runnable; what the person watches is the case filling in — every Given /
|
|
758
|
+
// When / Then with the steps that carry it out, as each step is kept, not
|
|
759
|
+
// after the flows are saved and the case is built. start_case names the
|
|
760
|
+
// case and its lines; each step recorded after it says which line it is
|
|
761
|
+
// for; save_case ties them on the saved case without being told again.
|
|
762
|
+
liveCase = null;
|
|
763
|
+
startCase(projectId, name, folder, lines) {
|
|
764
|
+
const same = this.liveCase && this.liveCase.projectId === projectId && nameKey(this.liveCase.name) === nameKey(name);
|
|
765
|
+
this.liveCase = same ? { ...this.liveCase, folder, lines } : { projectId, name, folder, lines, rows: [], lineOf: /* @__PURE__ */ new Map() };
|
|
766
|
+
void this.sendCaseReport();
|
|
767
|
+
return `Building the test case "${name}" in ${folder}:
|
|
768
|
+
${lines.map((l, i) => ` ${i + 1}. ${l.keyword} ${l.text}`).join("\n")}
|
|
769
|
+
Record the flows it needs; on every act, add_check and use_helper pass line: the number of the line that step carries out. save_case ties them on the case.`;
|
|
770
|
+
}
|
|
771
|
+
/** How many steps the flow being written has kept — before a tool runs, so
|
|
772
|
+
* the steps it keeps can be told apart. */
|
|
773
|
+
keptCount() {
|
|
774
|
+
return this.draft?.steps.length ?? 0;
|
|
775
|
+
}
|
|
776
|
+
/** The steps kept since `before` carry out line `line` of the case. */
|
|
777
|
+
tagSince(before, line) {
|
|
778
|
+
const live = this.liveCase;
|
|
779
|
+
const draft = this.draft;
|
|
780
|
+
if (!live || !draft) return "";
|
|
781
|
+
if (!Number.isInteger(line) || line < 1 || line > live.lines.length) return ` (There is no line ${line} in "${live.name}" \u2014 it has ${live.lines.length}.)`;
|
|
782
|
+
for (const step of draft.steps.slice(before)) live.lineOf.set(step.id, line);
|
|
783
|
+
void this.sendCaseReport();
|
|
784
|
+
return "";
|
|
785
|
+
}
|
|
786
|
+
/** The case's lines, each with the flow steps that carry it out — what
|
|
787
|
+
* save_case sends when it is not told. Null for another case. */
|
|
788
|
+
caseLinesFor(name) {
|
|
789
|
+
const live = this.liveCase;
|
|
790
|
+
if (!live || nameKey(live.name) !== nameKey(name)) return null;
|
|
791
|
+
const steps = this.caseRows().flatMap((r) => r.steps);
|
|
792
|
+
return live.lines.map((l, i) => ({
|
|
793
|
+
step: `${l.keyword} ${l.text}`,
|
|
794
|
+
flowSteps: steps.filter((st) => live.lineOf.get(st.id) === i + 1).map((st) => st.id)
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
caseRows() {
|
|
798
|
+
const live = this.liveCase;
|
|
799
|
+
const draft = this.draft && this.draft.projectId === live.projectId ? this.draft : null;
|
|
800
|
+
const rows = live.rows.filter((r) => !draft || nameKey(r.name) !== nameKey(draft.name));
|
|
801
|
+
const current = draft ? { name: draft.name, platform: draft.platform ?? "web", steps: draft.steps } : null;
|
|
802
|
+
const at = draft ? live.rows.findIndex((r) => nameKey(r.name) === nameKey(draft.name)) : -1;
|
|
803
|
+
if (current && at >= 0) rows.splice(at, 0, current);
|
|
804
|
+
else if (current) rows.push(current);
|
|
805
|
+
return rows;
|
|
806
|
+
}
|
|
807
|
+
/** The case as it stands, to the dashboard question this answers. */
|
|
808
|
+
async sendCaseReport() {
|
|
809
|
+
const live = this.liveCase;
|
|
810
|
+
if (!live || typeof this.service.reportCase !== "function") return;
|
|
811
|
+
const rows = this.caseRows();
|
|
812
|
+
const refOf = /* @__PURE__ */ new Map();
|
|
813
|
+
const occurrences = rows.map((r) => {
|
|
814
|
+
const occurrenceId = `live_${slugify(r.name) || "flow"}`;
|
|
815
|
+
for (const st of r.steps) refOf.set(st.id, `${occurrenceId}/${st.id}`);
|
|
816
|
+
return {
|
|
817
|
+
occurrenceId,
|
|
818
|
+
name: r.name,
|
|
819
|
+
...r.platform !== "web" ? { platform: r.platform } : {},
|
|
820
|
+
steps: withoutTyped(r.steps).map((st) => st.secret ? { ...st, value: "" } : st)
|
|
821
|
+
};
|
|
822
|
+
});
|
|
823
|
+
const businessSteps = live.lines.map((l, i) => {
|
|
824
|
+
const steps = rows.flatMap((r) => r.steps).filter((st) => live.lineOf.get(st.id) === i + 1).map((st) => refOf.get(st.id));
|
|
825
|
+
return { keyword: l.keyword, text: l.text, ...steps.length ? { steps } : {} };
|
|
826
|
+
});
|
|
827
|
+
await this.service.reportCase(live.projectId, { name: live.name, folder: live.folder, testCase: { name: live.name, occurrences, businessSteps } }).catch(() => void 0);
|
|
748
828
|
}
|
|
749
829
|
readDraft(projectId, name) {
|
|
750
830
|
try {
|
|
@@ -1097,6 +1177,13 @@ Fix the step that failed (remove_step, then act again) and save again \u2014 or
|
|
|
1097
1177
|
Claude has ${others.length} other open branch${others.length === 1 ? "" : "es"} on this project \u2014 gather_branches puts them into this one, for one review.` : "";
|
|
1098
1178
|
const after = draft.after?.length ? ` It starts after ${draft.after.map((a) => `"${a.name}"`).join(", ")}.` : "";
|
|
1099
1179
|
this.dropDraft(draft.projectId, draft.name);
|
|
1180
|
+
const live = this.liveCase;
|
|
1181
|
+
if (live && live.projectId === draft.projectId) {
|
|
1182
|
+
const row = { name: draft.name, platform: draft.platform ?? "web", steps: [...draft.steps] };
|
|
1183
|
+
const at = live.rows.findIndex((r) => nameKey(r.name) === nameKey(draft.name));
|
|
1184
|
+
if (at >= 0) live.rows[at] = row;
|
|
1185
|
+
else live.rows.push(row);
|
|
1186
|
+
}
|
|
1100
1187
|
return `${what} ${where_}.${replayed}${after}${vault} Its ${checkCount} check${checkCount === 1 ? " is" : "s are"} saved switched off \u2014 a test case switches on the ones it is about. Nothing is published yet.
|
|
1101
1188
|
Review and publish it here: ${review}${note}`;
|
|
1102
1189
|
}
|
|
@@ -1499,18 +1586,21 @@ HOW TO WORK:
|
|
|
1499
1586
|
catalog, then type {{its saveAs name}} into the field that needs it.
|
|
1500
1587
|
5. Add checks for what each step proves (a heading, a message, a value, the
|
|
1501
1588
|
URL). replay_flow, then save_flow. A flow must record at least one check.
|
|
1502
|
-
6.
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1589
|
+
6. THE PERSON THINKS IN TEST CASES; flows are how you make one runnable.
|
|
1590
|
+
Before recording, start_case with the case's name, folder and Given /
|
|
1591
|
+
When / Then lines (its own, from read_test_cases, when it has them). Then
|
|
1592
|
+
on EVERY act, add_check and use_helper pass line: the number of the line
|
|
1593
|
+
that step carries out \u2014 the person watches each line fill with its steps.
|
|
1594
|
+
preview_case lists the case's rows and every step, numbered; save_case
|
|
1595
|
+
with the checks to switch on runs the whole case, saves it only if it
|
|
1596
|
+
passed, and ties each line to its steps. Give the person the review link.
|
|
1509
1597
|
7. Saving the same name again replaces it on the working branch. ONE VERSION
|
|
1510
1598
|
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.
|
|
1512
|
-
|
|
1513
|
-
"
|
|
1599
|
+
(project_catalog lists them) rather than saving another beside it. A test's
|
|
1600
|
+
PLATFORM IS ITS LABEL, not words in its name: name flows and cases for what
|
|
1601
|
+
they do ("Sign up with valid credentials"), give start_flow its platform
|
|
1602
|
+
and preview_case / save_case theirs \u2014 the same name on two platforms is
|
|
1603
|
+
two tests, told apart by the label. Old branches
|
|
1514
1604
|
of yours? gather_branches puts them into one; discard_branch deletes one \u2014
|
|
1515
1605
|
only after the person asks or agrees.
|
|
1516
1606
|
Test data you invent (a new user's email or password for a sign-up) is fine to
|
|
@@ -1555,6 +1645,7 @@ var CASE_INPUT = {
|
|
|
1555
1645
|
additionalProperties: false
|
|
1556
1646
|
}
|
|
1557
1647
|
},
|
|
1648
|
+
platform: { type: "string", enum: ["web", "android", "ios"], description: "The platform this case is for, when the project tests several: its rows are that platform's flows. Its label \u2014 not words in its name." },
|
|
1558
1649
|
branch: { type: "string", description: "Optional: save onto this branch (chg_\u2026) instead of the working one." },
|
|
1559
1650
|
projectId: { type: "string" }
|
|
1560
1651
|
},
|
|
@@ -1567,7 +1658,8 @@ var caseSpec = (args) => ({
|
|
|
1567
1658
|
...args["description"] ? { description: String(args["description"]) } : {},
|
|
1568
1659
|
rows: Array.isArray(args["rows"]) ? args["rows"] : [],
|
|
1569
1660
|
...Array.isArray(args["businessSteps"]) ? { businessSteps: args["businessSteps"] } : {},
|
|
1570
|
-
...args["branch"] ? { changeId: String(args["branch"]) } : {}
|
|
1661
|
+
...args["branch"] ? { changeId: String(args["branch"]) } : {},
|
|
1662
|
+
...args["platform"] === "web" || args["platform"] === "android" || args["platform"] === "ios" ? { platform: args["platform"] } : {}
|
|
1571
1663
|
});
|
|
1572
1664
|
var text = (value, isError = false) => ({ content: [{ type: "text", text: value }], ...isError ? { isError } : {} });
|
|
1573
1665
|
var str = (value) => typeof value === "string" ? value : value === void 0 || value === null ? void 0 : String(value);
|
|
@@ -1590,6 +1682,17 @@ function authoringTools(service, session) {
|
|
|
1590
1682
|
catalogs.set(projectId, catalog);
|
|
1591
1683
|
return catalog;
|
|
1592
1684
|
};
|
|
1685
|
+
const tagged = async (args, run) => {
|
|
1686
|
+
const before = session.keptCount();
|
|
1687
|
+
const out = await run();
|
|
1688
|
+
const line = Number(args["line"]);
|
|
1689
|
+
return text(Number.isInteger(line) && line > 0 ? out + session.tagSince(before, line) : out);
|
|
1690
|
+
};
|
|
1691
|
+
const withLines = (spec) => {
|
|
1692
|
+
if (spec.businessSteps?.length) return spec;
|
|
1693
|
+
const lines = session.caseLinesFor(spec.name);
|
|
1694
|
+
return lines ? { ...spec, businessSteps: lines } : spec;
|
|
1695
|
+
};
|
|
1593
1696
|
const tools = [
|
|
1594
1697
|
{
|
|
1595
1698
|
name: "list_projects",
|
|
@@ -1677,6 +1780,28 @@ Helpers:${c.helpers.length ? "" : " none \u2014 create_helper makes one"}`,
|
|
|
1677
1780
|
].filter(Boolean).join("\n"));
|
|
1678
1781
|
}
|
|
1679
1782
|
},
|
|
1783
|
+
{
|
|
1784
|
+
name: "start_case",
|
|
1785
|
+
title: "Start building a test case",
|
|
1786
|
+
description: "Say which test case you are building, before recording what it needs: its name, its folder and its Given / When / Then lines (the case's own, from read_test_cases, when it already has them). The person watches it fill in: every step you then keep with act, add_check or use_helper \u2014 passing line, the number of the line it carries out \u2014 shows under that line at once. save_case then ties them on the saved case.",
|
|
1787
|
+
inputSchema: {
|
|
1788
|
+
type: "object",
|
|
1789
|
+
properties: {
|
|
1790
|
+
name: { type: "string", description: "The test case: the scenario it proves." },
|
|
1791
|
+
folder: { type: "string", description: 'Its folder \u2014 area and topic, e.g. "Account \u2013 Sign up".' },
|
|
1792
|
+
steps: { type: "array", items: { type: "string" }, description: "Its Given / When / Then lines, in order; And / But continue the line above." },
|
|
1793
|
+
projectId: { type: "string" }
|
|
1794
|
+
},
|
|
1795
|
+
required: ["name", "folder", "steps"],
|
|
1796
|
+
additionalProperties: false
|
|
1797
|
+
},
|
|
1798
|
+
run: async (args) => {
|
|
1799
|
+
const projectId = await projectFor(args["projectId"]);
|
|
1800
|
+
const lines = parseGherkin((Array.isArray(args["steps"]) ? args["steps"].map(String) : []).join("\n"));
|
|
1801
|
+
if (!lines?.length) throw new Error("the steps must each start with Given, When, Then, And or But");
|
|
1802
|
+
return text(session.startCase(projectId, str(args["name"]) ?? "", str(args["folder"]) ?? "", lines));
|
|
1803
|
+
}
|
|
1804
|
+
},
|
|
1680
1805
|
{
|
|
1681
1806
|
name: "start_flow",
|
|
1682
1807
|
title: "Start writing a flow",
|
|
@@ -1761,15 +1886,17 @@ ${page}`);
|
|
|
1761
1886
|
within: { type: "string", description: "When the name is not unique: the name of the region, dialog, form or navigation that holds it." },
|
|
1762
1887
|
nth: { type: "integer", minimum: 0, description: "Which one, when several elements share this role and name: 0 is the first in the order read_page lists them (each shared name is marked [nth N of M]). Leave it out when the name is unique." },
|
|
1763
1888
|
value: { type: "string" },
|
|
1764
|
-
secret: { type: "boolean", description: "fill only: the value is a secret (a password, PIN, key) \u2014 kept out of the flow and saved to the vault. Fields named like a password are secret without this." }
|
|
1889
|
+
secret: { type: "boolean", description: "fill only: the value is a secret (a password, PIN, key) \u2014 kept out of the flow and saved to the vault. Fields named like a password are secret without this." },
|
|
1890
|
+
line: { type: "integer", minimum: 1, description: "The number of the test case's line (from start_case) this step carries out." }
|
|
1765
1891
|
},
|
|
1766
1892
|
required: ["action"],
|
|
1767
1893
|
additionalProperties: false
|
|
1768
1894
|
},
|
|
1769
1895
|
run: async (args) => {
|
|
1770
1896
|
if (!isAction(args["action"])) throw new Error("action must be one of click, dblclick, rightclick, hover, fill, select, check, press, navigate, back, forward \u2014 or on a phone tap, longPress, swipe, scroll, hideKeyboard, permission, launchApp, terminateApp, resetApp, background, rotate");
|
|
1771
|
-
|
|
1772
|
-
|
|
1897
|
+
const action = args["action"];
|
|
1898
|
+
return tagged(args, async () => session.act({
|
|
1899
|
+
action,
|
|
1773
1900
|
...str(args["role"]) ? { role: str(args["role"]) } : {},
|
|
1774
1901
|
...str(args["name"]) !== void 0 ? { name: str(args["name"]) } : {},
|
|
1775
1902
|
...str(args["within"]) ? { within: str(args["within"]) } : {},
|
|
@@ -1792,12 +1919,13 @@ ${page}`);
|
|
|
1792
1919
|
role: { type: "string" },
|
|
1793
1920
|
name: { type: "string" },
|
|
1794
1921
|
within: { type: "string" },
|
|
1795
|
-
nth: { type: "integer", minimum: 0, description: "Which one, when several elements share this role and name: 0 is the first in the order read_page lists them (each shared name is marked [nth N of M])." }
|
|
1922
|
+
nth: { type: "integer", minimum: 0, description: "Which one, when several elements share this role and name: 0 is the first in the order read_page lists them (each shared name is marked [nth N of M])." },
|
|
1923
|
+
line: { type: "integer", minimum: 1, description: "The number of the test case's line (from start_case) this step carries out." }
|
|
1796
1924
|
},
|
|
1797
1925
|
required: ["kind", "value"],
|
|
1798
1926
|
additionalProperties: false
|
|
1799
1927
|
},
|
|
1800
|
-
run: async (args) =>
|
|
1928
|
+
run: async (args) => tagged(args, async () => session.check({
|
|
1801
1929
|
kind: str(args["kind"]),
|
|
1802
1930
|
value: str(args["value"]) ?? "",
|
|
1803
1931
|
...str(args["op"]) ? { op: str(args["op"]) } : {},
|
|
@@ -1816,12 +1944,13 @@ ${page}`);
|
|
|
1816
1944
|
helperId: { type: "string", description: "The helper's id or name from project_catalog." },
|
|
1817
1945
|
inputs: { type: "object", additionalProperties: { type: "string" }, description: `Values for the helper's {{names}}, e.g. {"email": "qa+signup1@example.com"}.` },
|
|
1818
1946
|
saveAs: { type: "string", description: "What to call the value, e.g. code. Default: code for a mailbox, value otherwise." },
|
|
1819
|
-
then: { type: "string", enum: ["remember", "open"] }
|
|
1947
|
+
then: { type: "string", enum: ["remember", "open"] },
|
|
1948
|
+
line: { type: "integer", minimum: 1, description: "The number of the test case's line (from start_case) this step carries out." }
|
|
1820
1949
|
},
|
|
1821
1950
|
required: ["helperId"],
|
|
1822
1951
|
additionalProperties: false
|
|
1823
1952
|
},
|
|
1824
|
-
run: async (args) =>
|
|
1953
|
+
run: async (args) => tagged(args, async () => session.useHelper({
|
|
1825
1954
|
helperId: str(args["helperId"]),
|
|
1826
1955
|
...args["inputs"] && typeof args["inputs"] === "object" ? { inputs: Object.fromEntries(Object.entries(args["inputs"]).map(([k, v]) => [k, String(v)])) } : {},
|
|
1827
1956
|
...str(args["saveAs"]) ? { saveAs: str(args["saveAs"]) } : {},
|
|
@@ -1934,7 +2063,7 @@ ${page}`);
|
|
|
1934
2063
|
annotations: { readOnlyHint: true },
|
|
1935
2064
|
run: async (args) => {
|
|
1936
2065
|
const projectId = await projectFor(args["projectId"]);
|
|
1937
|
-
return text(await session.previewCase(projectId, caseSpec(args)));
|
|
2066
|
+
return text(await session.previewCase(projectId, withLines(caseSpec(args))));
|
|
1938
2067
|
}
|
|
1939
2068
|
},
|
|
1940
2069
|
{
|
|
@@ -1945,7 +2074,7 @@ ${page}`);
|
|
|
1945
2074
|
annotations: { openWorldHint: true },
|
|
1946
2075
|
run: async (args) => {
|
|
1947
2076
|
const projectId = await projectFor(args["projectId"]);
|
|
1948
|
-
const out = await session.saveCase(projectId, caseSpec(args), await catalogOf(projectId, true));
|
|
2077
|
+
const out = await session.saveCase(projectId, withLines(caseSpec(args)), await catalogOf(projectId, true));
|
|
1949
2078
|
catalogs.delete(projectId);
|
|
1950
2079
|
return text(out);
|
|
1951
2080
|
}
|
|
@@ -2020,14 +2149,22 @@ ${got.text}`);
|
|
|
2020
2149
|
{
|
|
2021
2150
|
name: "read_update_request",
|
|
2022
2151
|
title: "Read what should change in some test cases",
|
|
2023
|
-
description: "Read a request to update test cases (its id is upd_\u2026): what the person wants changed, and each test case as it is now \u2014 its id, feature, name and business steps.",
|
|
2024
|
-
inputSchema: { type: "object", properties: { projectId: { type: "string" }, request: { type: "string", description: "upd_\u2026" } }, required: ["request"], additionalProperties: false },
|
|
2152
|
+
description: "Read a request to update test cases (its id is upd_\u2026): what the person wants changed, the change document they gave with it (a spec, release notes \u2014 read every part), and each test case as it is now \u2014 its id, feature, name and business steps.",
|
|
2153
|
+
inputSchema: { type: "object", properties: { projectId: { type: "string" }, request: { type: "string", description: "upd_\u2026" }, part: { type: "number", description: "Which part of a long change document, from 1." } }, required: ["request"], additionalProperties: false },
|
|
2025
2154
|
annotations: { readOnlyHint: true },
|
|
2026
2155
|
run: async (args) => {
|
|
2027
2156
|
const projectId = await projectFor(args["projectId"]);
|
|
2028
2157
|
const r = await service.updateRequest(projectId, str(args["request"]) ?? "");
|
|
2158
|
+
const PART = 4e4;
|
|
2159
|
+
const doc = r.document;
|
|
2160
|
+
const parts = doc ? Math.max(1, Math.ceil(doc.text.length / PART)) : 0;
|
|
2161
|
+
const part = Math.min(Math.max(1, Math.floor(Number(args["part"] ?? 1)) || 1), Math.max(parts, 1));
|
|
2029
2162
|
return text([
|
|
2030
2163
|
`What should change: ${r.instruction}`,
|
|
2164
|
+
...doc ? [`
|
|
2165
|
+
The change document "${doc.name}" \u2014 part ${part} of ${parts}${part < parts ? " (read the rest with part)" : ""}:
|
|
2166
|
+
${doc.text.slice((part - 1) * PART, part * PART)}
|
|
2167
|
+
\u2014 end of part ${part} \u2014`] : [],
|
|
2031
2168
|
...r.cases.map((c) => [`
|
|
2032
2169
|
${c.feature} \u203A ${c.name} (${c.id})`, ...c.businessSteps.length ? c.businessSteps.map((s) => ` ${s.keyword} ${s.text}`) : [" (no steps written)"]].join("\n"))
|
|
2033
2170
|
].join("\n"));
|
|
@@ -2256,9 +2393,9 @@ Use it with start_flow signIn {"profileId": "\u2026", "accountId": "${made.accou
|
|
|
2256
2393
|
}
|
|
2257
2394
|
const { pages } = await service.knowledgePages(projectId);
|
|
2258
2395
|
const read = async (path3) => pages.some((p) => p.path === path3) ? (await service.knowledgePage(projectId, path3)).content : null;
|
|
2259
|
-
const [product, glossary, decisions, web, android, ios,
|
|
2396
|
+
const [product, glossary, decisions, web, android, ios, tagged2] = await Promise.all(["product.md", "glossary.md", "memory/decisions.md", "product/web.md", "product/android.md", "product/ios.md", "codebase.json"].map(read));
|
|
2260
2397
|
const codebases = pages.filter((p) => p.path.startsWith("codebase/"));
|
|
2261
|
-
const tags = codebaseIndexOf(
|
|
2398
|
+
const tags = codebaseIndexOf(tagged2).folders;
|
|
2262
2399
|
const headers = await Promise.all(codebases.map(async (p) => {
|
|
2263
2400
|
const holds = tags[p.path.slice("codebase/".length, -".md".length)]?.tags ?? [];
|
|
2264
2401
|
return `- ${p.path}${holds.length ? ` (${holds.map((t) => CODEBASE_TAG_NAME[t]).join(", ")} code)` : ""}: ${(await read(p.path))?.split("\n").filter((l) => l.trim()).slice(0, 3).join(" \xB7 ") ?? ""}`;
|
package/dist/engine.js
CHANGED
package/package.json
CHANGED