@flowriki/agent 0.1.0-beta.1 → 0.1.0-beta.3
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/bin.js +9 -0
- package/dist/{chunk-ATOHKUGQ.js → chunk-XT52RW5S.js} +81 -26
- package/dist/cli.js +1 -5
- package/dist/engine.js +1 -1
- package/package.json +2 -2
package/dist/bin.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/bin.ts
|
|
4
|
+
process.removeAllListeners("warning");
|
|
5
|
+
process.on("warning", (warning) => {
|
|
6
|
+
if (warning.name === "ExperimentalWarning" && /SQLite/i.test(warning.message)) return;
|
|
7
|
+
console.warn(`${warning.name}: ${warning.message}`);
|
|
8
|
+
});
|
|
9
|
+
await import("./cli.js");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/engine.ts
|
|
4
|
-
import
|
|
4
|
+
import os5 from "os";
|
|
5
5
|
|
|
6
6
|
// ../agent-core/dist/index.js
|
|
7
7
|
import { z as z4 } from "zod";
|
|
@@ -11,13 +11,14 @@ import crypto22 from "crypto";
|
|
|
11
11
|
import { createRequire as createRequire3 } from "module";
|
|
12
12
|
import { WebSocket, WebSocketServer } from "ws";
|
|
13
13
|
import fs23 from "fs";
|
|
14
|
+
import os2 from "os";
|
|
14
15
|
import path23 from "path";
|
|
15
16
|
|
|
16
17
|
// ../orchestrator/dist/index.js
|
|
17
18
|
import fs4 from "fs";
|
|
18
19
|
import path3 from "path";
|
|
19
20
|
|
|
20
|
-
// ../protocol/dist/chunk-
|
|
21
|
+
// ../protocol/dist/chunk-ROEX7TWK.js
|
|
21
22
|
import { z } from "zod";
|
|
22
23
|
var LocatorOverrideSchema = z.object({
|
|
23
24
|
strategy: z.enum(["css", "xpath", "id", "testId", "text", "label", "placeholder", "altText", "title"]),
|
|
@@ -249,6 +250,9 @@ var RecordedStepSchema = RecordedTargetSchema.extend({
|
|
|
249
250
|
* stays unresolved and is reported, never invented. */
|
|
250
251
|
secretRef: z.string().regex(SECRET_REF_PATTERN).optional()
|
|
251
252
|
}).strict();
|
|
253
|
+
function slugify(name) {
|
|
254
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
255
|
+
}
|
|
252
256
|
var CURRENT_SCHEMA_VERSION = 5;
|
|
253
257
|
var FlowDocumentSchema = z.object({
|
|
254
258
|
schemaVersion: z.literal(CURRENT_SCHEMA_VERSION),
|
|
@@ -572,6 +576,7 @@ var ProjectMarkerSchema = z3.object({
|
|
|
572
576
|
/** `v1:ed25519:<base64url>` */
|
|
573
577
|
signature: z3.string()
|
|
574
578
|
});
|
|
579
|
+
var PRODUCTION_SERVICE = "https://flowriki-cloud.onrender.com";
|
|
575
580
|
|
|
576
581
|
// ../core/dist/index.js
|
|
577
582
|
function proposeAssertions(before, after) {
|
|
@@ -4200,9 +4205,6 @@ function clampRepeat(value) {
|
|
|
4200
4205
|
if (!Number.isFinite(n) || n < 2) return DEFAULT_FLAKINESS_REPEAT;
|
|
4201
4206
|
return Math.min(n, MAX_FLAKINESS_REPEAT);
|
|
4202
4207
|
}
|
|
4203
|
-
function slugify(name) {
|
|
4204
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4205
|
-
}
|
|
4206
4208
|
function uniqueRef(flowSlug, name, taken) {
|
|
4207
4209
|
const base = `${flowSlug}_${slugify(name) || "secret"}`;
|
|
4208
4210
|
if (!taken.has(base)) {
|
|
@@ -5427,6 +5429,20 @@ function ensureRootCommit(root) {
|
|
|
5427
5429
|
git2(root, ["commit", "--allow-empty", "-m", "Initialise repository"]);
|
|
5428
5430
|
return git2(root, ["rev-list", "--max-parents=0", "HEAD"]).split("\n").pop();
|
|
5429
5431
|
}
|
|
5432
|
+
function deliveredRun(delivered) {
|
|
5433
|
+
const { flow, secrets, config } = delivered;
|
|
5434
|
+
return {
|
|
5435
|
+
readConfig: () => config,
|
|
5436
|
+
// By id, and only the one that was sent. A machine serving no checkout has
|
|
5437
|
+
// no other flow to confuse it with.
|
|
5438
|
+
getFlow: (id) => id === flow.id ? flow : null,
|
|
5439
|
+
readSecrets: () => secrets,
|
|
5440
|
+
baseUrlFor: (f) => f.baseUrl ?? config.baseUrl ?? "",
|
|
5441
|
+
environmentFor: (f) => ({ ...config.environment, ...f.environment ?? {} }),
|
|
5442
|
+
// See RunSource.resolveFile: there is no project directory here.
|
|
5443
|
+
resolveFile: () => void 0
|
|
5444
|
+
};
|
|
5445
|
+
}
|
|
5430
5446
|
|
|
5431
5447
|
// ../agent-core/dist/index.js
|
|
5432
5448
|
var PROTOCOL_VERSION = 1;
|
|
@@ -5833,7 +5849,9 @@ var Agent = class {
|
|
|
5833
5849
|
`this agent speaks protocol ${PROTOCOL_VERSION}; the command is version ${command.v}`
|
|
5834
5850
|
);
|
|
5835
5851
|
}
|
|
5836
|
-
const
|
|
5852
|
+
const delivered = command.args["flow"];
|
|
5853
|
+
const produces = command.name.startsWith("record.") || command.name === "run.cancel";
|
|
5854
|
+
const project = this.opts.projects.find((p) => p.id === command.projectId) ?? (delivered || produces ? { id: command.projectId, name: String(delivered?.name ?? command.projectId), root: "" } : void 0);
|
|
5837
5855
|
if (!project) {
|
|
5838
5856
|
return this.refuse(
|
|
5839
5857
|
command.id,
|
|
@@ -6104,6 +6122,15 @@ function dialWebSocket(opts) {
|
|
|
6104
6122
|
socket.once("error", failed);
|
|
6105
6123
|
});
|
|
6106
6124
|
}
|
|
6125
|
+
var scratchRoots = /* @__PURE__ */ new Map();
|
|
6126
|
+
function scratchRoot(projectId) {
|
|
6127
|
+
let root = scratchRoots.get(projectId);
|
|
6128
|
+
if (!root) {
|
|
6129
|
+
root = fs23.mkdtempSync(path23.join(os2.tmpdir(), `flowriki-record-${projectId.replace(/[^\w-]/g, "")}-`));
|
|
6130
|
+
scratchRoots.set(projectId, root);
|
|
6131
|
+
}
|
|
6132
|
+
return root;
|
|
6133
|
+
}
|
|
6107
6134
|
function buildHandlers(deps) {
|
|
6108
6135
|
const { storeFor, storage = null, onActivity, lease, recorder } = deps;
|
|
6109
6136
|
const debugging = deps.debugger;
|
|
@@ -6389,14 +6416,22 @@ function buildHandlers(deps) {
|
|
|
6389
6416
|
if (command.flowRevision === void 0) {
|
|
6390
6417
|
return { ok: false, reason: "stale_revision", detail: "reload the flow and supply its revision before running" };
|
|
6391
6418
|
}
|
|
6392
|
-
const store = storeFor(project.root);
|
|
6393
6419
|
const flowId = String(command.args["flowId"] ?? "");
|
|
6394
|
-
const
|
|
6420
|
+
const carried = command.args["flow"];
|
|
6421
|
+
const source = carried ? deliveredRun({
|
|
6422
|
+
flow: carried,
|
|
6423
|
+
secrets: command.args["secrets"] ?? {},
|
|
6424
|
+
config: command.args["config"] ?? {}
|
|
6425
|
+
}) : storeFor(project.root);
|
|
6426
|
+
if (!carried && !project.root) {
|
|
6427
|
+
return { ok: false, reason: "failed", detail: "this machine has no checkout of that project" };
|
|
6428
|
+
}
|
|
6429
|
+
const flow = source.getFlow(flowId);
|
|
6395
6430
|
if (!flow) return { ok: false, reason: "failed", detail: `no flow ${flowId} in this project` };
|
|
6396
6431
|
if (flow.revision !== command.flowRevision) {
|
|
6397
6432
|
return { ok: false, reason: "stale_revision", detail: "the flow changed \u2014 reload it before running" };
|
|
6398
6433
|
}
|
|
6399
|
-
const missing =
|
|
6434
|
+
const missing = carried ? [] : storeFor(project.root).unresolvedSecrets(flow);
|
|
6400
6435
|
if (missing.length) {
|
|
6401
6436
|
return {
|
|
6402
6437
|
ok: false,
|
|
@@ -6418,7 +6453,7 @@ function buildHandlers(deps) {
|
|
|
6418
6453
|
signal: aborter.signal,
|
|
6419
6454
|
...typeof command.args["headless"] === "boolean" ? { headless: command.args["headless"] } : {},
|
|
6420
6455
|
...command.args["repeat"] ? { repeat: Number(command.args["repeat"]) } : {},
|
|
6421
|
-
store,
|
|
6456
|
+
store: source,
|
|
6422
6457
|
storage,
|
|
6423
6458
|
flowId,
|
|
6424
6459
|
expectedRevision: command.flowRevision,
|
|
@@ -6473,14 +6508,28 @@ function buildHandlers(deps) {
|
|
|
6473
6508
|
const url = optional("url");
|
|
6474
6509
|
const route = optional("path");
|
|
6475
6510
|
const recordedBy = optional("recordedBy");
|
|
6511
|
+
const headless = typeof command.args["headless"] === "boolean" ? command.args["headless"] : void 0;
|
|
6512
|
+
const root = project.root || scratchRoot(project.id);
|
|
6513
|
+
if (!project.root) {
|
|
6514
|
+
if (!/^f_[a-z0-9]+(?:-[a-z0-9]+)*$/.test(featureId)) throw new Error("Invalid feature ID");
|
|
6515
|
+
const workspace = new Store(root);
|
|
6516
|
+
if (!workspace.listFeatures().some((feature) => feature.id === featureId)) workspace.createFeature(featureId.slice(2));
|
|
6517
|
+
}
|
|
6476
6518
|
const started = await recorder.start({
|
|
6477
|
-
|
|
6519
|
+
// A SCRATCH DIRECTORY when this machine has no checkout of the
|
|
6520
|
+
// project. The recorder writes as it goes — that is what makes a
|
|
6521
|
+
// half-finished recording survivable — and the flow it produces is
|
|
6522
|
+
// returned on stop for the service to commit to the repository. A
|
|
6523
|
+
// machine that has the project checked out still records into it.
|
|
6524
|
+
root,
|
|
6525
|
+
projectId: project.id,
|
|
6478
6526
|
featureId,
|
|
6479
6527
|
name,
|
|
6480
6528
|
...url ? { url } : {},
|
|
6481
6529
|
...route ? { path: route } : {},
|
|
6482
6530
|
...recordedBy ? { recordedBy } : {},
|
|
6483
|
-
...command.args["environment"] ? { environment: command.args["environment"] } : {}
|
|
6531
|
+
...command.args["environment"] ? { environment: command.args["environment"] } : {},
|
|
6532
|
+
...headless === void 0 ? {} : { headless }
|
|
6484
6533
|
});
|
|
6485
6534
|
lease?.activate("run");
|
|
6486
6535
|
onActivity?.({ kind: "recording", projectId: project.id, detail: name });
|
|
@@ -6495,7 +6544,7 @@ function buildHandlers(deps) {
|
|
|
6495
6544
|
"record.stop": async (_command, project) => {
|
|
6496
6545
|
if (!recorder) return { ok: false, reason: "failed", detail: "this agent cannot record \u2014 no browser is configured" };
|
|
6497
6546
|
try {
|
|
6498
|
-
const saved = await recorder.stop();
|
|
6547
|
+
const saved = await recorder.stop(project.id);
|
|
6499
6548
|
if (!saved.ok) return { ok: false, reason: "failed", detail: saved.error };
|
|
6500
6549
|
return { ok: true, result: { flow: saved.flow, projectId: project.id } };
|
|
6501
6550
|
} finally {
|
|
@@ -6572,12 +6621,15 @@ function createAgentRuntime(options) {
|
|
|
6572
6621
|
let journal = null;
|
|
6573
6622
|
const coordinators = /* @__PURE__ */ new Map();
|
|
6574
6623
|
let recordingIn = null;
|
|
6624
|
+
let recordingProject = null;
|
|
6625
|
+
let lastRecording = null;
|
|
6575
6626
|
const coordinatorFor = (projectRoot) => {
|
|
6576
6627
|
let existing = coordinators.get(projectRoot);
|
|
6577
6628
|
if (!existing) {
|
|
6578
6629
|
existing = new RecordingCoordinator({
|
|
6579
6630
|
store: new Store(projectRoot),
|
|
6580
6631
|
emit: (event) => {
|
|
6632
|
+
if (event.type === "recording.stopped") lastRecording = { ok: true, flow: event.flow };
|
|
6581
6633
|
journal?.append({ name: `recording.${event.type.replace(/^recording\./, "")}`, data: event });
|
|
6582
6634
|
}
|
|
6583
6635
|
});
|
|
@@ -6636,13 +6688,17 @@ function createAgentRuntime(options) {
|
|
|
6636
6688
|
const coordinator = coordinatorFor(opts.root);
|
|
6637
6689
|
const { root: _root, ...rest } = opts;
|
|
6638
6690
|
const started = await coordinator.start(rest);
|
|
6691
|
+
recordingProject = opts["projectId"];
|
|
6692
|
+
lastRecording = null;
|
|
6639
6693
|
recordingIn = coordinator;
|
|
6640
6694
|
return started;
|
|
6641
6695
|
},
|
|
6642
|
-
stop: async () => {
|
|
6643
|
-
if (
|
|
6696
|
+
stop: async (projectId) => {
|
|
6697
|
+
if (projectId && recordingProject && projectId !== recordingProject) return { ok: false, error: "This recording belongs to another project" };
|
|
6698
|
+
if (lastRecording?.ok) return lastRecording;
|
|
6699
|
+
if (!recordingIn) return lastRecording ?? { ok: false, error: "no recording is in progress on this machine" };
|
|
6644
6700
|
const result = await recordingIn.stop();
|
|
6645
|
-
|
|
6701
|
+
lastRecording = result;
|
|
6646
6702
|
return result;
|
|
6647
6703
|
},
|
|
6648
6704
|
discard: async () => {
|
|
@@ -6708,20 +6764,20 @@ import fs7 from "fs";
|
|
|
6708
6764
|
|
|
6709
6765
|
// src/paths.ts
|
|
6710
6766
|
import fs6 from "fs";
|
|
6711
|
-
import
|
|
6767
|
+
import os3 from "os";
|
|
6712
6768
|
import path5 from "path";
|
|
6713
6769
|
function dataDir(env = process.env) {
|
|
6714
6770
|
const override = env["FLOWRIKI_HOME"]?.trim();
|
|
6715
6771
|
if (override) return path5.resolve(override);
|
|
6716
6772
|
if (process.platform === "win32") {
|
|
6717
6773
|
const appData = env["APPDATA"]?.trim();
|
|
6718
|
-
return appData ? path5.join(appData, "Flowriki") : path5.join(
|
|
6774
|
+
return appData ? path5.join(appData, "Flowriki") : path5.join(os3.homedir(), "AppData", "Roaming", "Flowriki");
|
|
6719
6775
|
}
|
|
6720
6776
|
if (process.platform === "darwin") {
|
|
6721
|
-
return path5.join(
|
|
6777
|
+
return path5.join(os3.homedir(), "Library", "Application Support", "Flowriki");
|
|
6722
6778
|
}
|
|
6723
6779
|
const state = env["XDG_STATE_HOME"]?.trim();
|
|
6724
|
-
return state ? path5.join(state, "flowriki") : path5.join(
|
|
6780
|
+
return state ? path5.join(state, "flowriki") : path5.join(os3.homedir(), ".local", "state", "flowriki");
|
|
6725
6781
|
}
|
|
6726
6782
|
function ensureDataDir(env = process.env) {
|
|
6727
6783
|
const dir = dataDir(env);
|
|
@@ -6788,7 +6844,6 @@ function forgetConnection(service, env = process.env) {
|
|
|
6788
6844
|
var fingerprint = (secret) => crypto3.createHash("sha256").update(secret).digest("hex").slice(0, 12);
|
|
6789
6845
|
|
|
6790
6846
|
// src/service.ts
|
|
6791
|
-
var PRODUCTION_SERVICE = "https://flowriki-cloud.onrender.com";
|
|
6792
6847
|
var BadServiceUrl = class extends Error {
|
|
6793
6848
|
};
|
|
6794
6849
|
function resolveService(explicit, env = process.env) {
|
|
@@ -6811,11 +6866,11 @@ function resolveService(explicit, env = process.env) {
|
|
|
6811
6866
|
|
|
6812
6867
|
// src/lock.ts
|
|
6813
6868
|
import fs8 from "fs";
|
|
6814
|
-
import
|
|
6869
|
+
import os4 from "os";
|
|
6815
6870
|
function takeLock(env = process.env) {
|
|
6816
6871
|
ensureDataDir(env);
|
|
6817
6872
|
const file = lockPath(env);
|
|
6818
|
-
const mine = { pid: process.pid, since: (/* @__PURE__ */ new Date()).toISOString(), host:
|
|
6873
|
+
const mine = { pid: process.pid, since: (/* @__PURE__ */ new Date()).toISOString(), host: os4.hostname() };
|
|
6819
6874
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
6820
6875
|
try {
|
|
6821
6876
|
fs8.writeFileSync(file, JSON.stringify(mine), { flag: "wx", mode: 384 });
|
|
@@ -6891,7 +6946,7 @@ function openBrowser(url, env = process.env) {
|
|
|
6891
6946
|
}
|
|
6892
6947
|
|
|
6893
6948
|
// src/engine.ts
|
|
6894
|
-
var AGENT_VERSION = "0.1.0-beta.
|
|
6949
|
+
var AGENT_VERSION = "0.1.0-beta.3";
|
|
6895
6950
|
async function connect(options = {}) {
|
|
6896
6951
|
let service;
|
|
6897
6952
|
try {
|
|
@@ -6908,7 +6963,7 @@ async function connect(options = {}) {
|
|
|
6908
6963
|
message: "the stored connection is no longer accepted \u2014 asking for a new one"
|
|
6909
6964
|
});
|
|
6910
6965
|
}
|
|
6911
|
-
const machineName = options.machineName?.trim() || `${
|
|
6966
|
+
const machineName = options.machineName?.trim() || `${os5.userInfo().username}@${os5.hostname()}`;
|
|
6912
6967
|
let asked;
|
|
6913
6968
|
try {
|
|
6914
6969
|
const res = await fetch(`${service}/api/pairing-requests`, {
|
|
@@ -7122,7 +7177,7 @@ async function doctor(serviceArg) {
|
|
|
7122
7177
|
}
|
|
7123
7178
|
function inspectPlaywright() {
|
|
7124
7179
|
try {
|
|
7125
|
-
const browsersPath = process.env["PLAYWRIGHT_BROWSERS_PATH"] ?? (process.platform === "win32" ? path6.join(
|
|
7180
|
+
const browsersPath = process.env["PLAYWRIGHT_BROWSERS_PATH"] ?? (process.platform === "win32" ? path6.join(os5.homedir(), "AppData", "Local", "ms-playwright") : process.platform === "darwin" ? path6.join(os5.homedir(), "Library", "Caches", "ms-playwright") : path6.join(os5.homedir(), ".cache", "ms-playwright"));
|
|
7126
7181
|
const chromiumInstalled = fs9.existsSync(browsersPath) ? fs9.readdirSync(browsersPath).some((d) => d.startsWith("chromium")) : false;
|
|
7127
7182
|
return { version: null, browsersPath, chromiumInstalled };
|
|
7128
7183
|
} catch {
|
package/dist/cli.js
CHANGED
|
@@ -7,14 +7,10 @@ import {
|
|
|
7
7
|
resolveCheckouts,
|
|
8
8
|
start,
|
|
9
9
|
status
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-XT52RW5S.js";
|
|
11
11
|
|
|
12
12
|
// src/cli.ts
|
|
13
13
|
import { parseArgs } from "util";
|
|
14
|
-
process.on("warning", (warning) => {
|
|
15
|
-
if (warning.name === "ExperimentalWarning" && /SQLite/i.test(warning.message)) return;
|
|
16
|
-
console.warn(`${warning.name}: ${warning.message}`);
|
|
17
|
-
});
|
|
18
14
|
var paint = (code) => (s) => `\x1B[${code}m${s}\x1B[0m`;
|
|
19
15
|
var bold = paint("1");
|
|
20
16
|
var dim = paint("2");
|
package/dist/engine.js
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowriki/agent",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.3",
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
|
-
"flowriki-agent": "dist/
|
|
8
|
+
"flowriki-agent": "dist/bin.js"
|
|
9
9
|
},
|
|
10
10
|
"main": "./dist/engine.js",
|
|
11
11
|
"exports": {
|