@flowriki/agent 0.1.0-beta.5 → 0.1.0-beta.6
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.
|
@@ -4994,12 +4994,19 @@ function redactPanel(state) {
|
|
|
4994
4994
|
if (!Array.isArray(panel.steps)) return state;
|
|
4995
4995
|
return { ...panel, steps: panel.steps.map((s) => redact(s)) };
|
|
4996
4996
|
}
|
|
4997
|
+
var START_GRACE_MS = 3e3;
|
|
4997
4998
|
var RecordingCoordinator = class {
|
|
4998
4999
|
constructor(deps) {
|
|
4999
5000
|
this.deps = deps;
|
|
5000
5001
|
}
|
|
5001
5002
|
deps;
|
|
5002
5003
|
session = null;
|
|
5004
|
+
/** The browser opening, while `start` has already answered.
|
|
5005
|
+
*
|
|
5006
|
+
* Held so that stop and status can wait for it rather than racing it: a
|
|
5007
|
+
* recording asked to stop a second after it was asked to start must not find
|
|
5008
|
+
* a half-built session. */
|
|
5009
|
+
starting = null;
|
|
5003
5010
|
draft = null;
|
|
5004
5011
|
stopInFlight = null;
|
|
5005
5012
|
active() {
|
|
@@ -5008,7 +5015,10 @@ var RecordingCoordinator = class {
|
|
|
5008
5015
|
}
|
|
5009
5016
|
status() {
|
|
5010
5017
|
return {
|
|
5011
|
-
active:
|
|
5018
|
+
// A browser still opening counts as active: `start` has answered, the
|
|
5019
|
+
// draft exists, and telling the dashboard "nothing is recording" in that
|
|
5020
|
+
// window is what makes a Stop pressed early do nothing at all.
|
|
5021
|
+
active: Boolean(this.session) || Boolean(this.draft && this.starting),
|
|
5012
5022
|
featureId: this.draft?.featureId ?? null,
|
|
5013
5023
|
name: this.draft?.name ?? null,
|
|
5014
5024
|
steps: (this.draft?.steps ?? []).map(redact),
|
|
@@ -5047,6 +5057,30 @@ var RecordingCoordinator = class {
|
|
|
5047
5057
|
startPath: "/",
|
|
5048
5058
|
recordedBy: recordedBy?.trim() || ""
|
|
5049
5059
|
};
|
|
5060
|
+
let failure = null;
|
|
5061
|
+
this.starting = this.open(target, opts).catch((error) => {
|
|
5062
|
+
failure = error;
|
|
5063
|
+
this.draft = null;
|
|
5064
|
+
this.session = null;
|
|
5065
|
+
this.deps.lease?.release("recording");
|
|
5066
|
+
this.deps.emit({ type: "recording.interrupted", reason: String(error.message).split("\n")[0] });
|
|
5067
|
+
return null;
|
|
5068
|
+
});
|
|
5069
|
+
await Promise.race([this.starting, new Promise((r) => setTimeout(r, START_GRACE_MS).unref?.())]);
|
|
5070
|
+
if (failure) throw failure;
|
|
5071
|
+
return { url: target };
|
|
5072
|
+
}
|
|
5073
|
+
/** Resolves once the browser is open, or the attempt has failed.
|
|
5074
|
+
*
|
|
5075
|
+
* For callers that need the session itself rather than the answer — the
|
|
5076
|
+
* local server driving a panel, and every test that types into the page. */
|
|
5077
|
+
async ready() {
|
|
5078
|
+
await this.starting?.catch(() => null);
|
|
5079
|
+
}
|
|
5080
|
+
/** Everything that used to happen before `start` answered. */
|
|
5081
|
+
async open(target, opts) {
|
|
5082
|
+
const config = this.deps.store.readConfig();
|
|
5083
|
+
const configuredBase = config.baseUrl;
|
|
5050
5084
|
try {
|
|
5051
5085
|
this.session = await startRecordingSession({
|
|
5052
5086
|
url: target,
|
|
@@ -5077,12 +5111,13 @@ var RecordingCoordinator = class {
|
|
|
5077
5111
|
throw err;
|
|
5078
5112
|
}
|
|
5079
5113
|
this.deps.lease?.activate("recording");
|
|
5114
|
+
if (!this.session || !this.draft) return null;
|
|
5080
5115
|
const opened = new URL(this.session.startUrl);
|
|
5081
5116
|
const projectOrigin = configuredBase ? new URL(normalizeStartUrl(configuredBase)).origin : "";
|
|
5082
5117
|
this.draft.baseUrl = opened.origin === projectOrigin ? "" : opened.origin;
|
|
5083
5118
|
this.draft.startPath = opened.pathname + opened.search;
|
|
5084
5119
|
this.deps.emit({ type: "recording.started", name: this.draft.name, url: this.session.startUrl });
|
|
5085
|
-
return
|
|
5120
|
+
return null;
|
|
5086
5121
|
}
|
|
5087
5122
|
/** onStep fires more than once per step — again when the outcome window
|
|
5088
5123
|
* resolves and attaches assertions — so this is an upsert, not an append. */
|
|
@@ -5114,6 +5149,8 @@ var RecordingCoordinator = class {
|
|
|
5114
5149
|
return this.stopInFlight;
|
|
5115
5150
|
}
|
|
5116
5151
|
async doStop() {
|
|
5152
|
+
await this.starting?.catch(() => null);
|
|
5153
|
+
this.starting = null;
|
|
5117
5154
|
const draft = this.draft;
|
|
5118
5155
|
if (!draft) return { ok: false, error: "no recording in progress" };
|
|
5119
5156
|
const environment = this.session?.environment;
|
|
@@ -5536,8 +5573,26 @@ var CommandSchema = z4.object({
|
|
|
5536
5573
|
/** Wall-clock expiry. A command that surfaced from a queue an hour late must
|
|
5537
5574
|
* not start a browser on somebody's machine. */
|
|
5538
5575
|
expiresAt: z4.number().int().positive(),
|
|
5576
|
+
/** When the service stamped this, by the service's clock.
|
|
5577
|
+
*
|
|
5578
|
+
* Present so the agent never has to compare a timestamp minted on another
|
|
5579
|
+
* machine against its own clock. `expiresAt` alone made the deadline
|
|
5580
|
+
* meaningless the moment two clocks disagreed: a machine running five
|
|
5581
|
+
* minutes fast rejected every command the instant it arrived, saying it had
|
|
5582
|
+
* expired before it got there — which is true only if you believe both
|
|
5583
|
+
* clocks. Optional, so an older service that does not send it still works;
|
|
5584
|
+
* the agent falls back to the absolute deadline it has always used. */
|
|
5585
|
+
issuedAt: z4.number().int().positive().optional(),
|
|
5539
5586
|
args: z4.record(z4.unknown()).default({})
|
|
5540
|
-
|
|
5587
|
+
// UNKNOWN FIELDS ARE IGNORED, not refused.
|
|
5588
|
+
//
|
|
5589
|
+
// Strictness here looked like caution and was a compatibility trap: the day
|
|
5590
|
+
// the service added `issuedAt`, every agent older than that release began
|
|
5591
|
+
// refusing every command as unrecognised — over a field it had no need to
|
|
5592
|
+
// read. The allowlist on command NAMES is the security boundary and is
|
|
5593
|
+
// unchanged; which extra keys ride along is not, because nothing acts on a
|
|
5594
|
+
// field it does not know about.
|
|
5595
|
+
});
|
|
5541
5596
|
var AckSchema = z4.object({
|
|
5542
5597
|
type: z4.literal("ack"),
|
|
5543
5598
|
commandId: z4.string().min(1),
|
|
@@ -5718,7 +5773,7 @@ var AgentState = class {
|
|
|
5718
5773
|
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, v]) => [key, canonical(v)]));
|
|
5719
5774
|
return value;
|
|
5720
5775
|
};
|
|
5721
|
-
const { expiresAt: _expiry, ...work } = command;
|
|
5776
|
+
const { expiresAt: _expiry, issuedAt: _issued, ...work } = command;
|
|
5722
5777
|
const text = JSON.stringify(canonical(work));
|
|
5723
5778
|
if (Buffer.byteLength(text) > 256 * 1024) throw new Error("command exceeds local size limit");
|
|
5724
5779
|
return crypto22.createHash("sha256").update(text).digest("hex");
|
|
@@ -5882,7 +5937,9 @@ var Agent = class {
|
|
|
5882
5937
|
}
|
|
5883
5938
|
const previous = this.previous(command.id, fingerprint2);
|
|
5884
5939
|
if (previous) return previous;
|
|
5885
|
-
|
|
5940
|
+
const ttl = command.issuedAt === void 0 ? null : command.expiresAt - command.issuedAt;
|
|
5941
|
+
const expired = ttl === null ? command.expiresAt <= this.now() : ttl <= 0;
|
|
5942
|
+
if (expired) {
|
|
5886
5943
|
return this.refuse(command.id, "expired", "the command expired before it reached this machine");
|
|
5887
5944
|
}
|
|
5888
5945
|
const handler = this.opts.handlers[command.name];
|
|
@@ -6981,7 +7038,7 @@ function openBrowser(url, env = process.env) {
|
|
|
6981
7038
|
}
|
|
6982
7039
|
|
|
6983
7040
|
// src/engine.ts
|
|
6984
|
-
var AGENT_VERSION = "0.1.0-beta.
|
|
7041
|
+
var AGENT_VERSION = "0.1.0-beta.6";
|
|
6985
7042
|
async function connect(options = {}) {
|
|
6986
7043
|
let service;
|
|
6987
7044
|
try {
|
package/dist/cli.js
CHANGED
package/dist/engine.js
CHANGED
package/package.json
CHANGED