@flowriki/agent 0.1.0-beta.56 → 0.1.0-beta.58

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.
@@ -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.56";
79
+ var AGENT_VERSION = "0.1.0-beta.58";
80
80
  async function prepareBrowsers() {
81
81
  if (browsersReady()) {
82
82
  return { ok: true, already: true, message: "chromium is already installed" };
@@ -9793,7 +9793,8 @@ function assistantHandlers({ deps }) {
9793
9793
  const status2 = await assistant.status();
9794
9794
  if (!status2.installed) return { ok: false, reason: "failed", detail: status2.detail ?? "the assistant is not installed on this machine" };
9795
9795
  if (!status2.signedIn) return { ok: false, reason: "failed", detail: "the assistant is not signed in on this machine" };
9796
- const askId = randomUUID();
9796
+ const given = command.args["askId"];
9797
+ const askId = typeof given === "string" && /^[\w-]{8,100}$/.test(given) ? given : randomUUID();
9797
9798
  const linked = foldersFor(project.id).filter((f) => f.exists).map((f) => f.path);
9798
9799
  const mode = command.args["mode"] === "ask" ? "ask" : command.args["mode"] === "do" ? "do" : void 0;
9799
9800
  const brief = typeof command.args["brief"] === "string" && command.args["brief"].trim() ? command.args["brief"].slice(0, 4e4) : void 0;
@@ -9805,6 +9806,8 @@ function assistantHandlers({ deps }) {
9805
9806
  ...linked.length ? { folders: linked } : {},
9806
9807
  ...mode ? { mode } : {},
9807
9808
  ...brief ? { brief } : {},
9809
+ ...command.args["platform"] === "web" || command.args["platform"] === "android" || command.args["platform"] === "ios" ? { platform: command.args["platform"] } : {},
9810
+ ...command.args["reuse"] === false ? { reuse: false } : {},
9808
9811
  ...typeof sessionId === "string" ? { sessionId } : {},
9809
9812
  emit
9810
9813
  });
@@ -10174,7 +10177,8 @@ function buildAskArgs(opts) {
10174
10177
  const brief = opts.brief?.trim();
10175
10178
  const guidance = [
10176
10179
  `You are the Flowriki assistant for the project "${opts.projectName}" (id ${opts.projectId}). Use the Flowriki tools, and pass projectId "${opts.projectId}" wherever a tool takes one. ` + (brief ? "The project brief below is this project's memory for this job: read it first." : "Start with project_brief: it is this project's memory."),
10177
- ask ? "ASK MODE: answer the question; change nothing. Cite what you rely on as [[testCase:<its id, tc_\u2026>]], [[knowledge:features/checkout.md]] or [[file:path/in/repo.ts]]." : "DO MODE: do what is asked, on Claude's working branch, for a person to review. Ask in the chat and wait before publishing, deleting, or sending anything to another system. End by writing a short summary of what you did with write_knowledge (memory/sessions/<date>-<topic>.md, append) so the next session knows.",
10180
+ ask ? "ASK MODE: answer the question; change nothing. Cite what you rely on as [[testCase:<its id, tc_\u2026>]], [[knowledge:features/checkout.md]] or [[file:path/in/repo.ts]]." : "DO MODE: do what is asked, on Claude's working branch, for a person to review. Ask in the chat and wait before publishing, deleting, or sending anything to another system. Work quietly: the person sees what you build and your final answer, so keep words between tool calls to a minimum and end with a short result \u2014 what was saved, and anything that needs them. When you saved something, write a short summary with write_knowledge (memory/sessions/<date>-<topic>.md, append) so the next session knows.",
10181
+ "Use only the tools you have: a tool is used by calling it, not by writing the call out as text. If Flowriki's tools are missing, say so in one sentence and stop.",
10178
10182
  folders.length ? `The project's code is in ${folders.join(" and ")}. Read it with Read, Grep and Glob when it helps; you cannot change it.` : "",
10179
10183
  brief ? `
10180
10184
  ${brief}` : ""
@@ -10214,8 +10218,10 @@ function eventsFromLine(askId, line) {
10214
10218
  return [];
10215
10219
  }
10216
10220
  const out = [];
10217
- if (parsed.type === "system" && parsed.subtype === "init" && parsed.session_id) {
10218
- out.push({ name: "assistant.session", data: { askId, sessionId: parsed.session_id } });
10221
+ if (parsed.type === "system" && parsed.subtype === "init") {
10222
+ const ours = (parsed.mcp_servers ?? []).find((s2) => s2.name === MCP_SERVER_NAME);
10223
+ if (ours) out.push({ name: "assistant.tools", data: { askId, status: ours.status ?? "unknown" } });
10224
+ if (parsed.session_id) out.push({ name: "assistant.session", data: { askId, sessionId: parsed.session_id } });
10219
10225
  }
10220
10226
  if (parsed.type === "assistant") {
10221
10227
  for (const block of parsed.message?.content ?? []) {
@@ -10298,6 +10304,35 @@ function createClaudeCli(options) {
10298
10304
  child.on("close", (code) => finish(code, false));
10299
10305
  });
10300
10306
  }
10307
+ const LANE_FRESH_MS = 60 * 6e4;
10308
+ const LANE_MAX_TOKENS = 15e4;
10309
+ const laneFile = (dir) => path42.join(dir, "lane-sessions.json");
10310
+ const readLanes = (dir) => {
10311
+ try {
10312
+ return JSON.parse(fs43.readFileSync(laneFile(dir), "utf8"));
10313
+ } catch {
10314
+ return {};
10315
+ }
10316
+ };
10317
+ const writeLane = (dir, platform, record) => {
10318
+ const all = readLanes(dir);
10319
+ if (record) all[platform] = record;
10320
+ else delete all[platform];
10321
+ try {
10322
+ fs43.writeFileSync(laneFile(dir), JSON.stringify(all), { mode: 384 });
10323
+ } catch {
10324
+ }
10325
+ };
10326
+ function laneSession(ask, dir) {
10327
+ if (ask.sessionId || !ask.platform) return ask.sessionId;
10328
+ if (ask.reuse === false) {
10329
+ writeLane(dir, ask.platform, null);
10330
+ return void 0;
10331
+ }
10332
+ const kept = readLanes(dir)[ask.platform];
10333
+ if (!kept || Date.now() - kept.updatedAt > LANE_FRESH_MS || kept.tokens > LANE_MAX_TOKENS) return void 0;
10334
+ return kept.sessionId;
10335
+ }
10301
10336
  return {
10302
10337
  async status() {
10303
10338
  const version = await capture(["--version"]);
@@ -10349,7 +10384,7 @@ function createClaudeCli(options) {
10349
10384
  JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: { command: options.mcp.command, args: options.mcp.args } } }),
10350
10385
  { mode: 384 }
10351
10386
  );
10352
- return launch(ask, dir, mcpConfigPath, ask.sessionId);
10387
+ return launch(ask, dir, mcpConfigPath, laneSession(ask, dir));
10353
10388
  },
10354
10389
  cancel(askId) {
10355
10390
  const child = running.get(askId);
@@ -10365,7 +10400,7 @@ function createClaudeCli(options) {
10365
10400
  return running.size >= MAX_AT_ONCE;
10366
10401
  }
10367
10402
  };
10368
- function launch(ask, dir, mcpConfigPath, sessionId) {
10403
+ function launch(ask, dir, mcpConfigPath, sessionId, attempt = 1) {
10369
10404
  let child;
10370
10405
  try {
10371
10406
  child = spawn2(
@@ -10389,8 +10424,21 @@ function createClaudeCli(options) {
10389
10424
  running.set(ask.askId, child);
10390
10425
  let buffer = "", stderr = "", sawDone = false, ended = false;
10391
10426
  let silentFailure = false;
10427
+ let toolsMissing = false;
10428
+ let session;
10429
+ let spent = 0;
10392
10430
  const handleLine = (line) => {
10431
+ if (toolsMissing) return;
10393
10432
  for (const event of eventsFromLine(ask.askId, line)) {
10433
+ if (event.name === "assistant.tools") {
10434
+ if (event.data["status"] !== "connected") {
10435
+ toolsMissing = true;
10436
+ child.kill("SIGTERM");
10437
+ return;
10438
+ }
10439
+ continue;
10440
+ }
10441
+ if (event.name === "assistant.session") session = String(event.data["sessionId"] ?? "") || session;
10394
10442
  if (event.name === "assistant.done" && event.data["ok"] === false && !String(event.data["result"] ?? "").trim()) {
10395
10443
  silentFailure = true;
10396
10444
  continue;
@@ -10398,6 +10446,14 @@ function createClaudeCli(options) {
10398
10446
  if (event.name === "assistant.done") sawDone = true;
10399
10447
  ask.emit(event.name, event.data);
10400
10448
  }
10449
+ try {
10450
+ const parsed = JSON.parse(line);
10451
+ if (parsed.type === "result" && parsed.usage) {
10452
+ const u = parsed.usage;
10453
+ spent += (u.input_tokens ?? 0) + (u.output_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
10454
+ }
10455
+ } catch {
10456
+ }
10401
10457
  };
10402
10458
  const end = (ok, detail) => {
10403
10459
  if (ended) return;
@@ -10405,10 +10461,26 @@ function createClaudeCli(options) {
10405
10461
  clearTimeout(timer);
10406
10462
  if (buffer.trim()) handleLine(buffer);
10407
10463
  running.delete(ask.askId);
10464
+ if (toolsMissing && cancelled !== ask.askId) {
10465
+ if (attempt < 3) {
10466
+ setTimeout(() => {
10467
+ const again = launch(ask, dir, mcpConfigPath, sessionId, attempt + 1);
10468
+ if (!again.started) ask.emit("assistant.done", { askId: ask.askId, ok: false, result: again.detail ?? "The assistant could not be started." });
10469
+ }, 1500 * attempt + Math.floor(Math.random() * 1e3)).unref?.();
10470
+ return;
10471
+ }
10472
+ ask.emit("assistant.done", { askId: ask.askId, ok: false, result: "Flowriki's tools did not connect to the assistant on this computer after three tries, so nothing was done. Send it again in a moment; if it keeps happening, restart the agent." });
10473
+ return;
10474
+ }
10408
10475
  if (silentFailure && sessionId && /no conversation found/i.test(stderr) && cancelled !== ask.askId) {
10476
+ if (ask.platform) writeLane(dir, ask.platform, null);
10409
10477
  const again = launch(ask, dir, mcpConfigPath, void 0);
10410
10478
  if (again.started) return;
10411
10479
  }
10480
+ if (ask.platform && session && ok && !silentFailure) {
10481
+ const before = sessionId && readLanes(dir)[ask.platform]?.sessionId === sessionId ? readLanes(dir)[ask.platform].tokens : 0;
10482
+ writeLane(dir, ask.platform, { sessionId: session, updatedAt: Date.now(), tokens: Math.max(before, spent) });
10483
+ }
10412
10484
  if (!sawDone) {
10413
10485
  const why = detail || stderr.trim().split("\n").filter((l) => l && !/^Warning:/.test(l)).slice(-3).join(" ") || "The assistant stopped without saying why.";
10414
10486
  ask.emit("assistant.done", { askId: ask.askId, ok: ok && !silentFailure, result: ok && !silentFailure ? "" : why });
package/dist/cli.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  runtimeValue,
25
25
  start,
26
26
  status
27
- } from "./chunk-H3YHYPVE.js";
27
+ } from "./chunk-RLRHWYSJ.js";
28
28
  import {
29
29
  CLOUD_PROVIDERS,
30
30
  CLOUD_PROVIDER_NAME,
package/dist/engine.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveCheckouts,
10
10
  start,
11
11
  status
12
- } from "./chunk-H3YHYPVE.js";
12
+ } from "./chunk-RLRHWYSJ.js";
13
13
  import "./chunk-O34WXNH6.js";
14
14
  export {
15
15
  AGENT_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowriki/agent",
3
- "version": "0.1.0-beta.56",
3
+ "version": "0.1.0-beta.58",
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",