@flowriki/agent 0.1.0-beta.46 → 0.1.0-beta.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,21 +1,35 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ AGENT_LATEST_URL,
3
4
  AGENT_VERSION,
4
- CLOUD_PROVIDERS,
5
- CLOUD_PROVIDER_NAME,
6
- MobileLane,
7
- RecordedStepSchema,
5
+ agentUpdateFor,
8
6
  authenticationResolver,
9
7
  checkAssertion,
10
- checkMobileAssertion,
11
8
  closeRunSession,
12
9
  connect,
13
10
  connectionFor,
11
+ dataDir,
14
12
  disconnect,
15
13
  doctor,
16
14
  executeStep,
17
15
  fieldNames,
18
16
  helperRunner,
17
+ openRunSession,
18
+ prepareBrowsers,
19
+ replayCase,
20
+ replayFlow,
21
+ resolveCheckouts,
22
+ resolveService,
23
+ runtimeValue,
24
+ start,
25
+ status
26
+ } from "./chunk-JUAJABZC.js";
27
+ import {
28
+ CLOUD_PROVIDERS,
29
+ CLOUD_PROVIDER_NAME,
30
+ MobileLane,
31
+ RecordedStepSchema,
32
+ checkMobileAssertion,
19
33
  isMobile,
20
34
  listDevices,
21
35
  locate,
@@ -24,24 +38,15 @@ import {
24
38
  mobileOf,
25
39
  mobileSetup,
26
40
  openMobileSession,
27
- openRunSession,
28
41
  parseGherkin,
29
42
  performMobileStep,
30
43
  platformsOf,
31
- prepareBrowsers,
32
- replayCase,
33
- replayFlow,
34
44
  replayMobileCase,
35
- resolveCheckouts,
36
- resolveService,
37
45
  runMobileSteps,
38
- runtimeValue,
39
46
  screenLines,
40
- start,
41
- status,
42
47
  stepRefusedOn,
43
48
  targetFor
44
- } from "./chunk-LIURHWRP.js";
49
+ } from "./chunk-7X54JYUL.js";
45
50
 
46
51
  // src/cli.ts
47
52
  import { parseArgs } from "util";
@@ -137,8 +142,8 @@ var ServiceClient = class {
137
142
  get origin() {
138
143
  return this.connection.service;
139
144
  }
140
- async call(path, init = {}) {
141
- const res = await this.fetcher(`${this.connection.service}${path}`, {
145
+ async call(path2, init = {}) {
146
+ const res = await this.fetcher(`${this.connection.service}${path2}`, {
142
147
  ...init,
143
148
  headers: {
144
149
  "x-flowriki-device": this.connection.deviceId,
@@ -280,9 +285,9 @@ var ServiceClient = class {
280
285
  }
281
286
  // ── Manual cycles and bugs: the dashboard's own routes, behind this
282
287
  // machine's credential, so the assistant can do what a person can. ──────
283
- scoped(path, projectId, extra = {}) {
288
+ scoped(path2, projectId, extra = {}) {
284
289
  const query = new URLSearchParams({ projectId, ...extra });
285
- return `/api/agent${path}?${query}`;
290
+ return `/api/agent${path2}?${query}`;
286
291
  }
287
292
  cycles(projectId) {
288
293
  return this.call(this.scoped("/cycles", projectId));
@@ -321,8 +326,8 @@ var ServiceClient = class {
321
326
  knowledgePages(projectId) {
322
327
  return this.call(this.scoped("/knowledge", projectId));
323
328
  }
324
- knowledgePage(projectId, path) {
325
- return this.call(this.scoped("/knowledge/page", projectId, { path }));
329
+ knowledgePage(projectId, path2) {
330
+ return this.call(this.scoped("/knowledge/page", projectId, { path: path2 }));
326
331
  }
327
332
  writeKnowledge(projectId, body) {
328
333
  return this.call(`/api/agent/knowledge?projectId=${encodeURIComponent(projectId)}`, { method: "POST", body: JSON.stringify(body) });
@@ -2003,7 +2008,7 @@ Use it with start_flow signIn {"profileId": "\u2026", "accountId": "${made.accou
2003
2008
  run: async (args) => {
2004
2009
  const projectId = await projectFor(args["projectId"]);
2005
2010
  const { pages } = await service.knowledgePages(projectId);
2006
- const read = async (path) => pages.some((p) => p.path === path) ? (await service.knowledgePage(projectId, path)).content : null;
2011
+ const read = async (path2) => pages.some((p) => p.path === path2) ? (await service.knowledgePage(projectId, path2)).content : null;
2007
2012
  const [product, glossary, decisions] = await Promise.all(["product.md", "glossary.md", "memory/decisions.md"].map(read));
2008
2013
  const codebases = pages.filter((p) => p.path.startsWith("codebase/"));
2009
2014
  const headers = await Promise.all(codebases.map(async (p) => `- ${p.path}: ${(await read(p.path))?.split("\n").filter((l) => l.trim()).slice(0, 3).join(" \xB7 ") ?? ""}`));
@@ -2097,6 +2102,212 @@ ${pages.filter((p) => !shown.has(p.path)).map((p) => `- ${p.path}`).join("\n") |
2097
2102
  }));
2098
2103
  }
2099
2104
 
2105
+ // src/mcp/upgrade.ts
2106
+ import { spawn } from "child_process";
2107
+ import readline2 from "readline";
2108
+ var PRIVATE = "flowriki-upgrade";
2109
+ var startWithNpx = (version) => spawn(process.platform === "win32" ? "npx.cmd" : "npx", ["-y", `@flowriki/agent@${version}`, "mcp"], {
2110
+ env: { ...process.env, FLOWRIKI_MCP_CHILD: "1" },
2111
+ stdio: ["pipe", "pipe", "inherit"]
2112
+ });
2113
+ var toolShape = (tools) => JSON.stringify(tools.map((t) => [t.name, t.description ?? "", t.inputSchema ?? null]).sort());
2114
+ function serveWithUpgrades(tools, info, opts) {
2115
+ const input = opts.input ?? process.stdin;
2116
+ const output = opts.output ?? process.stdout;
2117
+ const log = opts.log ?? ((line) => process.stderr.write(`flowriki: ${line}
2118
+ `));
2119
+ const send = (m) => output.write(`${JSON.stringify(m)}
2120
+ `);
2121
+ if (!opts.output) {
2122
+ console.log = console.info = console.warn = console.debug = (...args) => {
2123
+ process.stderr.write(`${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
2124
+ `);
2125
+ };
2126
+ }
2127
+ let initialize = null;
2128
+ let localCalls = 0;
2129
+ let ready = null;
2130
+ let active = null;
2131
+ let preparing = false;
2132
+ let notice = null;
2133
+ const forwarded = /* @__PURE__ */ new Set();
2134
+ let timer = null;
2135
+ const withNotice = (answer) => {
2136
+ if (!notice || !answer.result || typeof answer.result !== "object") return answer;
2137
+ const result = answer.result;
2138
+ if (!Array.isArray(result.content)) return answer;
2139
+ const text2 = notice;
2140
+ notice = null;
2141
+ return { ...answer, result: { ...result, content: [{ type: "text", text: text2 }, ...result.content] } };
2142
+ };
2143
+ const prepare = async (version) => {
2144
+ if (preparing || ready || active || !initialize) return;
2145
+ preparing = true;
2146
+ let child = null;
2147
+ try {
2148
+ child = (opts.start ?? startWithNpx)(version);
2149
+ const c = child;
2150
+ const lines2 = readline2.createInterface({ input: c.stdout, crlfDelay: Infinity });
2151
+ const waiting = /* @__PURE__ */ new Map();
2152
+ const exited = new Promise((_, reject) => c.once("exit", (code) => reject(new Error(`it stopped while starting (exit ${code})`))));
2153
+ exited.catch(() => void 0);
2154
+ c.once("error", (e) => waiting.forEach((resolve) => resolve({ jsonrpc: "2.0", error: { message: e.message } })));
2155
+ lines2.on("line", (line) => {
2156
+ let m;
2157
+ try {
2158
+ m = JSON.parse(line);
2159
+ } catch {
2160
+ return;
2161
+ }
2162
+ if (typeof m.id === "string" && m.id.startsWith(PRIVATE)) {
2163
+ waiting.get(m.id)?.(m);
2164
+ waiting.delete(m.id);
2165
+ return;
2166
+ }
2167
+ if (active?.child !== c) return;
2168
+ if (m.id !== void 0 && m.id !== null) forwarded.delete(m.id);
2169
+ send(m.id !== void 0 && m.id !== null && "result" in m ? withNotice(m) : m);
2170
+ });
2171
+ const ask = (method, params, id) => new Promise((resolve, reject) => {
2172
+ waiting.set(id, resolve);
2173
+ c.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, ...params ? { params } : {} })}
2174
+ `);
2175
+ setTimeout(() => reject(new Error("it did not answer in time")), opts.readyWithinMs ?? 18e4).unref();
2176
+ });
2177
+ const hello = await Promise.race([ask("initialize", initialize.params, `${PRIVATE}-init`), exited]);
2178
+ if (hello.error) throw new Error(JSON.stringify(hello.error));
2179
+ c.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })}
2180
+ `);
2181
+ const listed = await Promise.race([ask("tools/list", void 0, `${PRIVATE}-tools`), exited]);
2182
+ const theirs = listed.result?.tools ?? [];
2183
+ ready = { child: c, version, lines: lines2, toolsChanged: toolShape(theirs) !== toolShape(tools) };
2184
+ c.once("exit", () => {
2185
+ if (ready?.child === c) ready = null;
2186
+ if (active?.child === c) {
2187
+ active = null;
2188
+ for (const id of forwarded) send({ jsonrpc: "2.0", id, error: { code: -32603, message: "the updated Flowriki stopped \u2014 ask again" } });
2189
+ forwarded.clear();
2190
+ notice = `The updated Flowriki (${version}) stopped, so this connection carries on with ${opts.current}.`;
2191
+ log(`updated server ${version} exited; back on ${opts.current}`);
2192
+ }
2193
+ });
2194
+ log(`${version} is ready to take over from ${opts.current}`);
2195
+ } catch (error) {
2196
+ child?.kill();
2197
+ log(`could not start ${version}: ${error.message}`);
2198
+ notice ??= `Flowriki ${version} is out (this connection runs ${opts.current}) and could not switch over by itself: ${error.message}. A new Claude session starts the new version.`;
2199
+ } finally {
2200
+ preparing = false;
2201
+ }
2202
+ };
2203
+ const maybeSwitch = async (incomingTool) => {
2204
+ if (!ready || active || localCalls > 0) return false;
2205
+ if (!opts.idle() && incomingTool !== "start_flow") return false;
2206
+ const next = ready;
2207
+ ready = null;
2208
+ await opts.handOff().catch(() => void 0);
2209
+ active = next;
2210
+ if (timer) clearInterval(timer);
2211
+ notice = `Flowriki updated itself from ${opts.current} to ${next.version} \u2014 the new version is answering now.` + (next.toolsChanged ? " It brings new or changed tools; if Claude does not offer them yet, start a new Claude session (in the terminal version of Claude Code, /mcp then Reconnect also works)." : "");
2212
+ send({ jsonrpc: "2.0", method: "notifications/tools/list_changed" });
2213
+ log(`switched to ${next.version}`);
2214
+ return true;
2215
+ };
2216
+ const look = async () => {
2217
+ if (active || ready || preparing) return;
2218
+ const version = await opts.check().catch(() => null);
2219
+ if (version) {
2220
+ await prepare(version);
2221
+ await maybeSwitch();
2222
+ }
2223
+ };
2224
+ setTimeout(() => void look(), opts.firstCheckMs ?? 15e3).unref();
2225
+ timer = setInterval(() => void look(), opts.everyMs ?? 30 * 6e4);
2226
+ timer.unref();
2227
+ const lines = readline2.createInterface({ input, crlfDelay: Infinity });
2228
+ const pending = /* @__PURE__ */ new Set();
2229
+ lines.on("line", (line) => {
2230
+ if (!line.trim()) return;
2231
+ let message;
2232
+ try {
2233
+ message = JSON.parse(line);
2234
+ } catch {
2235
+ send({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } });
2236
+ return;
2237
+ }
2238
+ if (message.method === "initialize") initialize = message;
2239
+ const task = (async () => {
2240
+ const tool = message.method === "tools/call" ? String(message.params?.["name"] ?? "") : void 0;
2241
+ if (!active && tool) await maybeSwitch(tool);
2242
+ if (active) {
2243
+ if (message.id !== void 0 && message.id !== null) forwarded.add(message.id);
2244
+ active.child.stdin.write(`${line}
2245
+ `);
2246
+ return;
2247
+ }
2248
+ if (tool) localCalls++;
2249
+ try {
2250
+ const answer = await handle(message, tools, info);
2251
+ if (answer) send(tool ? withNotice(answer) : answer);
2252
+ if (message.method === "notifications/initialized") send({ jsonrpc: "2.0", method: "notifications/tools/list_changed" });
2253
+ } finally {
2254
+ if (tool) localCalls--;
2255
+ }
2256
+ if (tool && ready) await maybeSwitch();
2257
+ })();
2258
+ pending.add(task);
2259
+ void task.finally(() => pending.delete(task));
2260
+ });
2261
+ return new Promise((resolve) => lines.on("close", () => {
2262
+ void Promise.allSettled([...pending]).then(() => {
2263
+ if (timer) clearInterval(timer);
2264
+ const child = active?.child ?? ready?.child;
2265
+ if (!child) return resolve();
2266
+ child.stdin?.end();
2267
+ const done = () => resolve();
2268
+ child.once("exit", done);
2269
+ setTimeout(() => {
2270
+ child.kill();
2271
+ done();
2272
+ }, 5e3).unref();
2273
+ });
2274
+ }));
2275
+ }
2276
+
2277
+ // src/updates.ts
2278
+ import fs from "fs";
2279
+ import path from "path";
2280
+ var CACHE_MS = 30 * 60 * 1e3;
2281
+ async function latestPublished(opts = {}) {
2282
+ const file = path.join(dataDir(), "latest-agent.json");
2283
+ if (!opts.fresh) {
2284
+ try {
2285
+ const kept = JSON.parse(fs.readFileSync(file, "utf8"));
2286
+ if (kept.version && kept.at && Date.now() - kept.at < CACHE_MS) return kept.version;
2287
+ } catch {
2288
+ }
2289
+ }
2290
+ try {
2291
+ const res = await fetch(AGENT_LATEST_URL, { signal: AbortSignal.timeout(5e3), headers: { accept: "application/json" } });
2292
+ if (!res.ok) return null;
2293
+ const version = (await res.json()).version ?? null;
2294
+ if (version) {
2295
+ try {
2296
+ fs.mkdirSync(path.dirname(file), { recursive: true });
2297
+ fs.writeFileSync(file, JSON.stringify({ version, at: Date.now() }));
2298
+ } catch {
2299
+ }
2300
+ }
2301
+ return version;
2302
+ } catch {
2303
+ return null;
2304
+ }
2305
+ }
2306
+ async function updateFor(running) {
2307
+ const latest = await latestPublished();
2308
+ return agentUpdateFor(running, latest) ? latest : null;
2309
+ }
2310
+
2100
2311
  // src/mcp/index.ts
2101
2312
  async function runMcp(opts) {
2102
2313
  let service;
@@ -2120,7 +2331,20 @@ async function runMcp(opts) {
2120
2331
  };
2121
2332
  process.on("SIGINT", () => void closing());
2122
2333
  process.on("SIGTERM", () => void closing());
2123
- await serve(tools, { name: "flowriki", version: opts.version, instructions: INSTRUCTIONS });
2334
+ const info = { name: "flowriki", version: opts.version, instructions: INSTRUCTIONS };
2335
+ if (process.env["FLOWRIKI_MCP_CHILD"] === "1" || process.env["FLOWRIKI_NO_UPDATE"] === "1" || !/^\d+\.\d+\.\d+/.test(opts.version)) {
2336
+ await serve(tools, info);
2337
+ } else {
2338
+ await serveWithUpgrades(tools, info, {
2339
+ current: opts.version,
2340
+ check: () => updateFor(opts.version),
2341
+ idle: () => !session?.open,
2342
+ handOff: async () => {
2343
+ await session?.close();
2344
+ if (session) session.draft = null;
2345
+ }
2346
+ });
2347
+ }
2124
2348
  await session?.close();
2125
2349
  return 0;
2126
2350
  }
@@ -2270,6 +2494,17 @@ Run \`flowriki-agent --help\`.`);
2270
2494
  console.log(dim(` project ${project.name} (${project.root})`));
2271
2495
  }
2272
2496
  console.log(dim("\n Running in the foreground. Ctrl-C to stop.\n"));
2497
+ let told = null;
2498
+ const tellIfBehind = async () => {
2499
+ const newer = await updateFor(AGENT_VERSION).catch(() => null);
2500
+ if (!newer || newer === told) return;
2501
+ told = newer;
2502
+ console.log(yellow(` ! Flowriki agent ${newer} is out \u2014 this is ${AGENT_VERSION}.`));
2503
+ console.log(dim(` Update: npm install -g @flowriki/agent@latest then stop (Ctrl-C) and start again
2504
+ `));
2505
+ };
2506
+ void tellIfBehind();
2507
+ setInterval(() => void tellIfBehind(), 6 * 60 * 6e4).unref();
2273
2508
  let stopping = false;
2274
2509
  const shutdown = async (signal) => {
2275
2510
  if (stopping) return;
@@ -2349,7 +2584,7 @@ Run \`flowriki-agent --help\`.`);
2349
2584
  for (const [title, lines] of [["Appium", found.appium], ["Android", found.android], ["iOS", found.ios]]) {
2350
2585
  console.log(bold(title));
2351
2586
  for (const line of lines) {
2352
- console.log(` ${line.ok ? green("\u2713") : red("\u2717")} ${line.item.padEnd(15)} ${line.ok ? dim(line.detail) : line.detail}`);
2587
+ console.log(` ${line.ok ? green("\u2713") : line.optional ? yellow("\u2013") : red("\u2717")} ${line.item.padEnd(15)} ${line.ok ? dim(line.detail) : line.detail}`);
2353
2588
  if (!line.ok && line.fix) console.log(` ${yellow("\u2192")} ${line.fix}`);
2354
2589
  }
2355
2590
  }
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ AGENT_HOME,
4
+ APPIUM_DIR,
5
+ AppiumMissing,
6
+ CLOUD_HUBS,
7
+ CheckError,
8
+ LocateError,
9
+ MOBILE_TIMEOUTS,
10
+ MobileLane,
11
+ MobileSession,
12
+ WebDriverError,
13
+ WebDriverSession,
14
+ adbPath,
15
+ androidHome,
16
+ appOf,
17
+ appiumPath,
18
+ appiumServer,
19
+ bootDevice,
20
+ centre,
21
+ checkMobileAssertion,
22
+ chooseDevice,
23
+ cloudDevices,
24
+ describeTarget,
25
+ filmsThis,
26
+ finishVideo,
27
+ javaHome,
28
+ listDevices,
29
+ locate,
30
+ mobileDoctor,
31
+ mobileSetup,
32
+ nodeAt,
33
+ openMobileSession,
34
+ performMobileStep,
35
+ readScreen,
36
+ replayMobileCase,
37
+ replayMobileFlow,
38
+ resolveOn,
39
+ run,
40
+ runMobileSteps,
41
+ screenLines,
42
+ stopAppium,
43
+ targetFor,
44
+ textOf,
45
+ toolEnv,
46
+ typesSecrets
47
+ } from "./chunk-7X54JYUL.js";
48
+ export {
49
+ AGENT_HOME,
50
+ APPIUM_DIR,
51
+ AppiumMissing,
52
+ CLOUD_HUBS,
53
+ CheckError,
54
+ LocateError,
55
+ MOBILE_TIMEOUTS,
56
+ MobileLane,
57
+ MobileSession,
58
+ WebDriverError,
59
+ WebDriverSession,
60
+ adbPath,
61
+ androidHome,
62
+ appOf,
63
+ appiumPath,
64
+ appiumServer,
65
+ bootDevice,
66
+ centre,
67
+ checkMobileAssertion,
68
+ chooseDevice,
69
+ cloudDevices,
70
+ describeTarget,
71
+ filmsThis,
72
+ finishVideo,
73
+ javaHome,
74
+ listDevices,
75
+ locate,
76
+ mobileDoctor,
77
+ mobileSetup,
78
+ nodeAt,
79
+ openMobileSession,
80
+ performMobileStep,
81
+ readScreen,
82
+ replayMobileCase,
83
+ replayMobileFlow,
84
+ resolveOn,
85
+ runMobileSteps,
86
+ run as runTool,
87
+ screenLines,
88
+ stopAppium,
89
+ targetFor,
90
+ textOf,
91
+ toolEnv,
92
+ typesSecrets
93
+ };
package/dist/engine.js CHANGED
@@ -9,7 +9,8 @@ import {
9
9
  resolveCheckouts,
10
10
  start,
11
11
  status
12
- } from "./chunk-LIURHWRP.js";
12
+ } from "./chunk-JUAJABZC.js";
13
+ import "./chunk-7X54JYUL.js";
13
14
  export {
14
15
  AGENT_VERSION,
15
16
  browsersReady,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowriki/agent",
3
- "version": "0.1.0-beta.46",
3
+ "version": "0.1.0-beta.48",
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",