@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-MJXAVVGP.js";
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.59";
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-BB5LTG3H.js");
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-HXUFFE2E.js";
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-MJXAVVGP.js";
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
- const draft = this.draft;
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 !== "function") return;
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. 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.
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. When the
1512
- project tests several platforms, name flows "<scenario> \u2014 Web", "\u2014 Android",
1513
- "\u2014 iOS". Old branches
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
- return text(await session.act({
1772
- action: args["action"],
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) => text(await session.check({
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) => text(await session.useHelper({
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, tagged] = await Promise.all(["product.md", "glossary.md", "memory/decisions.md", "product/web.md", "product/android.md", "product/ios.md", "codebase.json"].map(read));
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(tagged).folders;
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 ") ?? ""}`;
@@ -46,7 +46,7 @@ import {
46
46
  textOf,
47
47
  toolEnv,
48
48
  typesSecrets
49
- } from "./chunk-MJXAVVGP.js";
49
+ } from "./chunk-WQ22OUIQ.js";
50
50
  export {
51
51
  AGENT_HOME,
52
52
  APPIUM_DIR,
package/dist/engine.js CHANGED
@@ -9,8 +9,8 @@ import {
9
9
  resolveCheckouts,
10
10
  start,
11
11
  status
12
- } from "./chunk-HXUFFE2E.js";
13
- import "./chunk-MJXAVVGP.js";
12
+ } from "./chunk-BYFYSV5H.js";
13
+ import "./chunk-WQ22OUIQ.js";
14
14
  export {
15
15
  AGENT_VERSION,
16
16
  browsersReady,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowriki/agent",
3
- "version": "0.1.0-beta.59",
3
+ "version": "0.1.0-beta.60",
4
4
  "description": "Run Flowriki UI tests on your own computer. Connects this machine to your Flowriki account.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",