@flowriki/agent 0.1.0-beta.36 → 0.1.0-beta.39
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/{chunk-N3P4O3OH.js → chunk-7CH5XDEE.js} +915 -167
- package/dist/cli.js +777 -19
- package/dist/engine.js +1 -1
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import os from "os";
|
|
|
6
6
|
import path from "path";
|
|
7
7
|
import { createRequire } from "module";
|
|
8
8
|
import { spawnSync } from "child_process";
|
|
9
|
-
var AGENT_VERSION = "0.1.0-beta.
|
|
9
|
+
var AGENT_VERSION = "0.1.0-beta.39";
|
|
10
10
|
async function prepareBrowsers() {
|
|
11
11
|
if (browsersReady()) {
|
|
12
12
|
return { ok: true, already: true, message: "chromium is already installed" };
|
|
@@ -113,29 +113,29 @@ function allConnections(env = process.env) {
|
|
|
113
113
|
}
|
|
114
114
|
function saveConnection(connection, env = process.env) {
|
|
115
115
|
ensureDataDir(env);
|
|
116
|
-
const
|
|
116
|
+
const file2 = credentialsPath(env);
|
|
117
117
|
const next = {
|
|
118
118
|
version: 3,
|
|
119
119
|
connections: [...readFile(env).connections.filter((c) => c.service !== connection.service), connection]
|
|
120
120
|
};
|
|
121
|
-
const temporary = `${
|
|
121
|
+
const temporary = `${file2}.${process.pid}.tmp`;
|
|
122
122
|
fs3.writeFileSync(temporary, JSON.stringify(next, null, 2) + "\n", { mode: 384 });
|
|
123
|
-
fs3.renameSync(temporary,
|
|
123
|
+
fs3.renameSync(temporary, file2);
|
|
124
124
|
}
|
|
125
125
|
function forgetConnection(service, env = process.env) {
|
|
126
126
|
const before = readFile(env).connections;
|
|
127
127
|
const after = before.filter((c) => c.service !== service);
|
|
128
128
|
if (after.length === before.length) return false;
|
|
129
129
|
ensureDataDir(env);
|
|
130
|
-
const
|
|
131
|
-
const temporary = `${
|
|
130
|
+
const file2 = credentialsPath(env);
|
|
131
|
+
const temporary = `${file2}.${process.pid}.tmp`;
|
|
132
132
|
fs3.writeFileSync(temporary, JSON.stringify({ version: 3, connections: after }, null, 2) + "\n", { mode: 384 });
|
|
133
|
-
fs3.renameSync(temporary,
|
|
133
|
+
fs3.renameSync(temporary, file2);
|
|
134
134
|
return true;
|
|
135
135
|
}
|
|
136
136
|
var fingerprint = (secret2) => crypto2.createHash("sha256").update(secret2).digest("hex").slice(0, 12);
|
|
137
137
|
|
|
138
|
-
// ../contracts/dist/chunk-
|
|
138
|
+
// ../contracts/dist/chunk-4VVBTBPF.js
|
|
139
139
|
import { z } from "zod";
|
|
140
140
|
var PROTOCOL_VERSION = 1;
|
|
141
141
|
var PAIRING_CODE_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -168,7 +168,15 @@ var CommandNameSchema = z.enum([
|
|
|
168
168
|
"runs.list",
|
|
169
169
|
"runs.stats",
|
|
170
170
|
"run.get",
|
|
171
|
-
"artifact.fetch"
|
|
171
|
+
"artifact.fetch",
|
|
172
|
+
// The AI assistant on this machine, driven from the dashboard. NOT baseline: an
|
|
173
|
+
// agent from before these existed must be told so up front rather than be
|
|
174
|
+
// sent a command it has no handler for.
|
|
175
|
+
"assistant.status",
|
|
176
|
+
"assistant.connect",
|
|
177
|
+
"assistant.ask",
|
|
178
|
+
"assistant.cancel",
|
|
179
|
+
"assistant.folder"
|
|
172
180
|
]);
|
|
173
181
|
var EFFECTFUL = /* @__PURE__ */ new Set([
|
|
174
182
|
"record.start",
|
|
@@ -200,6 +208,13 @@ var EFFECTFUL = /* @__PURE__ */ new Set([
|
|
|
200
208
|
// dropped socket would find it already there and report a failure for
|
|
201
209
|
// something that had in fact succeeded.
|
|
202
210
|
"feature.create",
|
|
211
|
+
// Signing in opens a browser on somebody's machine; asking starts a program
|
|
212
|
+
// that spends their assistant usage and can record tests. Neither is a read,
|
|
213
|
+
// and a blind retry after a dropped socket would do either twice.
|
|
214
|
+
// (`assistant.status` stays out: reading is safe to repeat.)
|
|
215
|
+
"assistant.connect",
|
|
216
|
+
"assistant.ask",
|
|
217
|
+
"assistant.cancel",
|
|
203
218
|
// Claiming writes a file, makes a commit and pushes. Blind retry after a
|
|
204
219
|
// dropped socket would commit twice.
|
|
205
220
|
"project.claim",
|
|
@@ -990,6 +1005,9 @@ import { z as z4 } from "zod";
|
|
|
990
1005
|
import { z as z22 } from "zod";
|
|
991
1006
|
import { z as z32 } from "zod";
|
|
992
1007
|
import { z as z42 } from "zod";
|
|
1008
|
+
import { z as z5 } from "zod";
|
|
1009
|
+
import { z as z6 } from "zod";
|
|
1010
|
+
import { z as z7 } from "zod";
|
|
993
1011
|
var EngineSchema = z4.enum(["chromium", "firefox", "webkit"]);
|
|
994
1012
|
var CapabilitySchema = z4.object({
|
|
995
1013
|
id: z4.string(),
|
|
@@ -1756,6 +1774,285 @@ var DeliveredHelperSchema = z42.object({
|
|
|
1756
1774
|
name: z42.string(),
|
|
1757
1775
|
config: HelperConfigSchema
|
|
1758
1776
|
}).strict();
|
|
1777
|
+
var LIBRARY_CASE_SCHEMA_VERSION = 1;
|
|
1778
|
+
var TEST_TYPES = ["functional", "negative", "edge", "regression", "smoke", "accessibility", "other"];
|
|
1779
|
+
var PRIORITIES = ["P0", "P1", "P2", "P3"];
|
|
1780
|
+
var CASE_STATUSES = ["draft", "ready", "deprecated"];
|
|
1781
|
+
var AUTOMATION_STATES = ["none", "planned", "automated", "not-automatable"];
|
|
1782
|
+
var LibraryStepSchema = z5.object({
|
|
1783
|
+
id: z5.string().min(1),
|
|
1784
|
+
action: z5.string().min(1),
|
|
1785
|
+
/** What to type or choose, when the action needs it. */
|
|
1786
|
+
data: z5.string().optional(),
|
|
1787
|
+
/** What should happen. Absent for a step that only gets somewhere. */
|
|
1788
|
+
expected: z5.string().optional()
|
|
1789
|
+
}).strict();
|
|
1790
|
+
var AutomationLinkSchema = z5.object({
|
|
1791
|
+
automationId: z5.string().min(1),
|
|
1792
|
+
caseRevision: z5.number().int().nonnegative(),
|
|
1793
|
+
/** The automation's revision when the link was made or last confirmed —
|
|
1794
|
+
* an automation edited since may no longer check what the case says. */
|
|
1795
|
+
automationRevision: z5.number().int().nonnegative().optional(),
|
|
1796
|
+
at: z5.string(),
|
|
1797
|
+
by: z5.string().default("")
|
|
1798
|
+
}).strict();
|
|
1799
|
+
var CaseOriginSchema = z5.discriminatedUnion("kind", [
|
|
1800
|
+
z5.object({ kind: z5.literal("manual") }).strict(),
|
|
1801
|
+
z5.object({
|
|
1802
|
+
kind: z5.literal("import"),
|
|
1803
|
+
/** "csv", "xlsx", "testrail", "zephyr", "xray", "text"… */
|
|
1804
|
+
format: z5.string().min(1),
|
|
1805
|
+
file: z5.string().min(1),
|
|
1806
|
+
/** Row or item in that file, to find it again. */
|
|
1807
|
+
row: z5.string().optional()
|
|
1808
|
+
}).strict(),
|
|
1809
|
+
z5.object({
|
|
1810
|
+
kind: z5.literal("generated"),
|
|
1811
|
+
sourceId: z5.string().min(1),
|
|
1812
|
+
sourceVersion: z5.number().int().positive(),
|
|
1813
|
+
requirementIds: z5.array(z5.string().min(1)).default([])
|
|
1814
|
+
}).strict()
|
|
1815
|
+
]);
|
|
1816
|
+
var TestDataSchema = z5.object({
|
|
1817
|
+
name: z5.string().min(1),
|
|
1818
|
+
value: z5.string().optional(),
|
|
1819
|
+
secretRef: z5.string().regex(/^[a-z][a-z0-9_]*$/i).optional()
|
|
1820
|
+
}).strict();
|
|
1821
|
+
var LibraryCaseSchema = z5.object({
|
|
1822
|
+
schemaVersion: z5.literal(LIBRARY_CASE_SCHEMA_VERSION),
|
|
1823
|
+
id: z5.string().regex(/^tcs_[A-Za-z0-9]+$/),
|
|
1824
|
+
/** "TC-142": what people say out loud. The id is what links point at. */
|
|
1825
|
+
key: z5.string().regex(/^TC-[1-9][0-9]*$/),
|
|
1826
|
+
/** The same features flows are filed under (`f_<slug>`), so one feature
|
|
1827
|
+
* holds both what to test and what tests it. */
|
|
1828
|
+
featureId: z5.string().min(1),
|
|
1829
|
+
slug: z5.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
1830
|
+
title: z5.string().min(1).max(300),
|
|
1831
|
+
description: z5.string().default(""),
|
|
1832
|
+
type: z5.enum(TEST_TYPES).default("functional"),
|
|
1833
|
+
priority: z5.enum(PRIORITIES).default("P2"),
|
|
1834
|
+
status: z5.enum(CASE_STATUSES).default("draft"),
|
|
1835
|
+
preconditions: z5.array(z5.string().min(1)).default([]),
|
|
1836
|
+
steps: z5.array(LibraryStepSchema).default([]),
|
|
1837
|
+
testData: z5.array(TestDataSchema).default([]),
|
|
1838
|
+
tags: z5.array(z5.string().min(1)).default([]),
|
|
1839
|
+
/** Requirement ids (Flowriki storage). The text is not copied here. */
|
|
1840
|
+
covers: z5.array(z5.string().min(1)).default([]),
|
|
1841
|
+
automation: z5.object({
|
|
1842
|
+
state: z5.enum(AUTOMATION_STATES).default("none"),
|
|
1843
|
+
/** Why it cannot be automated, when it cannot. */
|
|
1844
|
+
reason: z5.string().optional(),
|
|
1845
|
+
/** The plan for automating it: flows to reuse, flows to record, checks to
|
|
1846
|
+
* switch on, accounts and helpers it needs, and what cannot be automated.
|
|
1847
|
+
* Written (usually by the assistant) and approved before anything is
|
|
1848
|
+
* recorded. */
|
|
1849
|
+
plan: z5.string().max(2e4).optional(),
|
|
1850
|
+
planApproved: z5.boolean().optional(),
|
|
1851
|
+
links: z5.array(AutomationLinkSchema).default([])
|
|
1852
|
+
}).strict().default({}),
|
|
1853
|
+
origin: CaseOriginSchema.default({ kind: "manual" }),
|
|
1854
|
+
createdAt: z5.string(),
|
|
1855
|
+
createdBy: z5.string().default(""),
|
|
1856
|
+
updatedAt: z5.string(),
|
|
1857
|
+
updatedBy: z5.string().default(""),
|
|
1858
|
+
revision: z5.number().int().nonnegative().default(0)
|
|
1859
|
+
}).strict().superRefine((doc, ctx) => {
|
|
1860
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1861
|
+
doc.steps.forEach((step, i) => {
|
|
1862
|
+
if (seen.has(step.id)) ctx.addIssue({ code: "custom", path: ["steps", i, "id"], message: "Step IDs must be unique" });
|
|
1863
|
+
seen.add(step.id);
|
|
1864
|
+
});
|
|
1865
|
+
const linked = /* @__PURE__ */ new Set();
|
|
1866
|
+
doc.automation.links.forEach((link, i) => {
|
|
1867
|
+
if (linked.has(link.automationId)) {
|
|
1868
|
+
ctx.addIssue({ code: "custom", path: ["automation", "links", i], message: "An automation is linked once" });
|
|
1869
|
+
}
|
|
1870
|
+
linked.add(link.automationId);
|
|
1871
|
+
});
|
|
1872
|
+
if (doc.automation.state === "not-automatable" && !doc.automation.reason?.trim()) {
|
|
1873
|
+
ctx.addIssue({ code: "custom", path: ["automation", "reason"], message: "Say why it cannot be automated" });
|
|
1874
|
+
}
|
|
1875
|
+
});
|
|
1876
|
+
var normaliseText = (text) => text.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
1877
|
+
var SOURCE_KINDS = ["upload", "jira", "figma", "confluence", "linear", "github-issue", "notion", "url"];
|
|
1878
|
+
var SourceVersionSchema = z5.object({
|
|
1879
|
+
v: z5.number().int().positive(),
|
|
1880
|
+
hash: z5.string().min(1),
|
|
1881
|
+
extractedText: z5.string(),
|
|
1882
|
+
/** Where the original file is kept, for an upload. */
|
|
1883
|
+
originalKey: z5.string().optional(),
|
|
1884
|
+
mediaType: z5.string().optional(),
|
|
1885
|
+
at: z5.string(),
|
|
1886
|
+
by: z5.string().default("")
|
|
1887
|
+
}).strict();
|
|
1888
|
+
var SourceSchema = z5.object({
|
|
1889
|
+
id: z5.string().regex(/^src_[A-Za-z0-9]+$/),
|
|
1890
|
+
projectId: z5.string().min(1),
|
|
1891
|
+
title: z5.string().min(1).max(300),
|
|
1892
|
+
kind: z5.enum(SOURCE_KINDS),
|
|
1893
|
+
versions: z5.array(SourceVersionSchema).min(1),
|
|
1894
|
+
integrationId: z5.string().optional(),
|
|
1895
|
+
externalId: z5.string().optional(),
|
|
1896
|
+
externalUrl: z5.string().url().optional(),
|
|
1897
|
+
createdAt: z5.string(),
|
|
1898
|
+
createdBy: z5.string().default("")
|
|
1899
|
+
}).strict();
|
|
1900
|
+
var RequirementSchema = z5.object({
|
|
1901
|
+
id: z5.string().regex(/^req_[A-Za-z0-9]+$/),
|
|
1902
|
+
projectId: z5.string().min(1),
|
|
1903
|
+
sourceId: z5.string().min(1),
|
|
1904
|
+
sourceVersion: z5.number().int().positive(),
|
|
1905
|
+
/** Where in the source: a heading path, a Jira key, a Figma node. */
|
|
1906
|
+
anchor: z5.string().min(1),
|
|
1907
|
+
text: z5.string().min(1),
|
|
1908
|
+
featureId: z5.string().optional(),
|
|
1909
|
+
/** As of the source's latest version: new in it, changed by it, unchanged
|
|
1910
|
+
* ("active"), or gone from it. */
|
|
1911
|
+
state: z5.enum(["new", "active", "changed", "removed"]).default("new"),
|
|
1912
|
+
history: z5.array(z5.object({
|
|
1913
|
+
sourceVersion: z5.number().int().positive(),
|
|
1914
|
+
text: z5.string(),
|
|
1915
|
+
state: z5.enum(["new", "active", "changed", "removed"])
|
|
1916
|
+
}).strict()).default([])
|
|
1917
|
+
}).strict();
|
|
1918
|
+
var MANUAL_OUTCOMES = ["untested", "passed", "failed", "blocked", "skipped"];
|
|
1919
|
+
var EvidenceSchema = z6.object({
|
|
1920
|
+
id: z6.string().regex(/^ev_[A-Za-z0-9]+$/),
|
|
1921
|
+
/** A file kept in Flowriki's evidence storage, or a link to one elsewhere. */
|
|
1922
|
+
kind: z6.enum(["file", "link"]),
|
|
1923
|
+
name: z6.string().min(1).max(200),
|
|
1924
|
+
mediaType: z6.string().optional(),
|
|
1925
|
+
size: z6.number().int().nonnegative().optional(),
|
|
1926
|
+
url: z6.string().url().optional(),
|
|
1927
|
+
/** The step it shows, when it belongs to one. */
|
|
1928
|
+
stepId: z6.string().optional(),
|
|
1929
|
+
at: z6.string(),
|
|
1930
|
+
by: z6.string().default("")
|
|
1931
|
+
}).strict();
|
|
1932
|
+
var StepOutcomeSchema = z6.object({
|
|
1933
|
+
stepId: z6.string().min(1),
|
|
1934
|
+
outcome: z6.enum(MANUAL_OUTCOMES),
|
|
1935
|
+
/** What actually happened, when it was not what was expected. */
|
|
1936
|
+
actual: z6.string().max(4e3).optional()
|
|
1937
|
+
}).strict();
|
|
1938
|
+
var ManualResultSchema = z6.object({
|
|
1939
|
+
outcome: z6.enum(MANUAL_OUTCOMES),
|
|
1940
|
+
steps: z6.array(StepOutcomeSchema).default([]),
|
|
1941
|
+
note: z6.string().max(8e3).default(""),
|
|
1942
|
+
evidence: z6.array(EvidenceSchema).default([]),
|
|
1943
|
+
bugIds: z6.array(z6.string().min(1)).default([]),
|
|
1944
|
+
tester: z6.string().default(""),
|
|
1945
|
+
at: z6.string()
|
|
1946
|
+
}).strict();
|
|
1947
|
+
var CycleEntrySchema = z6.object({
|
|
1948
|
+
testCaseId: z6.string().min(1),
|
|
1949
|
+
key: z6.string().min(1),
|
|
1950
|
+
title: z6.string().min(1),
|
|
1951
|
+
featureId: z6.string().min(1),
|
|
1952
|
+
priority: z6.enum(PRIORITIES),
|
|
1953
|
+
/** The case revision snapshot, so "the case changed since" is answerable. */
|
|
1954
|
+
caseRevision: z6.number().int().nonnegative(),
|
|
1955
|
+
preconditions: z6.array(z6.string()).default([]),
|
|
1956
|
+
steps: z6.array(z6.object({ id: z6.string(), action: z6.string(), data: z6.string().optional(), expected: z6.string().optional() }).strict()).default([]),
|
|
1957
|
+
assignee: z6.string().optional(),
|
|
1958
|
+
result: ManualResultSchema.nullable().default(null),
|
|
1959
|
+
/** Earlier results for this case in this cycle, newest last — a retest
|
|
1960
|
+
* keeps what the first attempt found. */
|
|
1961
|
+
history: z6.array(ManualResultSchema).default([])
|
|
1962
|
+
}).strict();
|
|
1963
|
+
var TestCycleSchema = z6.object({
|
|
1964
|
+
id: z6.string().regex(/^cyc_[A-Za-z0-9]+$/),
|
|
1965
|
+
projectId: z6.string().min(1),
|
|
1966
|
+
name: z6.string().min(1).max(200),
|
|
1967
|
+
description: z6.string().max(4e3).default(""),
|
|
1968
|
+
status: z6.enum(["open", "closed"]).default("open"),
|
|
1969
|
+
/** Which branch the cases were read from; null for the published tests. */
|
|
1970
|
+
changeId: z6.string().nullable().default(null),
|
|
1971
|
+
entries: z6.array(CycleEntrySchema).default([]),
|
|
1972
|
+
createdAt: z6.string(),
|
|
1973
|
+
createdBy: z6.string().default(""),
|
|
1974
|
+
updatedAt: z6.string(),
|
|
1975
|
+
closedAt: z6.string().nullable().default(null)
|
|
1976
|
+
}).strict();
|
|
1977
|
+
var BUG_STATUSES = ["open", "in-progress", "fixed", "verified", "closed", "wont-fix"];
|
|
1978
|
+
var SEVERITIES = ["critical", "major", "minor", "trivial"];
|
|
1979
|
+
var BugSchema = z6.object({
|
|
1980
|
+
id: z6.string().regex(/^bug_[A-Za-z0-9]+$/),
|
|
1981
|
+
key: z6.string().regex(/^BUG-[1-9][0-9]*$/),
|
|
1982
|
+
projectId: z6.string().min(1),
|
|
1983
|
+
title: z6.string().min(1).max(300),
|
|
1984
|
+
description: z6.string().max(2e4).default(""),
|
|
1985
|
+
severity: z6.enum(SEVERITIES).default("major"),
|
|
1986
|
+
status: z6.enum(BUG_STATUSES).default("open"),
|
|
1987
|
+
tracker: z6.enum(["flowriki", "jira", "linear", "github"]).default("flowriki"),
|
|
1988
|
+
externalKey: z6.string().optional(),
|
|
1989
|
+
url: z6.string().url().optional(),
|
|
1990
|
+
/** Where it was found: a case, the step in it, the cycle that ran it. */
|
|
1991
|
+
testCaseIds: z6.array(z6.string()).default([]),
|
|
1992
|
+
cycleIds: z6.array(z6.string()).default([]),
|
|
1993
|
+
runIds: z6.array(z6.string()).default([]),
|
|
1994
|
+
automationIds: z6.array(z6.string()).default([]),
|
|
1995
|
+
foundAt: z6.object({ cycleId: z6.string(), testCaseId: z6.string(), stepId: z6.string().optional() }).strict().optional(),
|
|
1996
|
+
createdAt: z6.string(),
|
|
1997
|
+
createdBy: z6.string().default(""),
|
|
1998
|
+
updatedAt: z6.string()
|
|
1999
|
+
}).strict();
|
|
2000
|
+
var STOP = new Set("a an the and or of to in on for with by is are be as at it this that from user users can should must shall will when then given".split(" "));
|
|
2001
|
+
function similarity(a, b2) {
|
|
2002
|
+
const words = (s2) => new Set(normaliseText(s2).split(" ").filter((w) => w.length > 1 && !STOP.has(w)));
|
|
2003
|
+
const x = words(a);
|
|
2004
|
+
const y = words(b2);
|
|
2005
|
+
if (x.size === 0 || y.size === 0) return 0;
|
|
2006
|
+
let shared = 0;
|
|
2007
|
+
for (const w of x) if (y.has(w)) shared += 1;
|
|
2008
|
+
return shared / (x.size + y.size - shared);
|
|
2009
|
+
}
|
|
2010
|
+
var ConversationSchema = z7.object({
|
|
2011
|
+
id: z7.string().regex(/^conv_[A-Za-z0-9]+$/),
|
|
2012
|
+
projectId: z7.string().min(1),
|
|
2013
|
+
/** Whose: conversations are personal. */
|
|
2014
|
+
userId: z7.string().min(1),
|
|
2015
|
+
title: z7.string().min(1).max(200),
|
|
2016
|
+
mode: z7.enum(["ask", "do"]).default("ask"),
|
|
2017
|
+
/** The assistant's own session, to continue where it left off. */
|
|
2018
|
+
sessionId: z7.string().optional(),
|
|
2019
|
+
/** The conversation as the dashboard draws it. Kept as given: the shape
|
|
2020
|
+
* belongs to the screen, and the service only stores and returns it. */
|
|
2021
|
+
lines: z7.array(z7.unknown()).max(2e3).default([]),
|
|
2022
|
+
createdAt: z7.string(),
|
|
2023
|
+
updatedAt: z7.string()
|
|
2024
|
+
}).strict();
|
|
2025
|
+
var INTEGRATION_PLATFORMS = ["jira", "linear", "github", "figma", "confluence"];
|
|
2026
|
+
var IntegrationSchema = z7.object({
|
|
2027
|
+
id: z7.string().regex(/^int_[A-Za-z0-9]+$/),
|
|
2028
|
+
projectId: z7.string().min(1),
|
|
2029
|
+
platform: z7.enum(INTEGRATION_PLATFORMS),
|
|
2030
|
+
name: z7.string().min(1).max(120),
|
|
2031
|
+
/** Where it is: a Jira or Confluence site, a GitHub repository, a Figma
|
|
2032
|
+
* file, a Linear team. Never a credential. */
|
|
2033
|
+
config: z7.object({
|
|
2034
|
+
baseUrl: z7.string().url().optional(),
|
|
2035
|
+
email: z7.string().optional(),
|
|
2036
|
+
jql: z7.string().max(2e3).optional(),
|
|
2037
|
+
projectKey: z7.string().max(40).optional(),
|
|
2038
|
+
repository: z7.string().regex(/^[\w.-]+\/[\w.-]+$/).optional(),
|
|
2039
|
+
fileKey: z7.string().max(120).optional(),
|
|
2040
|
+
teamKey: z7.string().max(40).optional(),
|
|
2041
|
+
spaceKey: z7.string().max(80).optional(),
|
|
2042
|
+
pageIds: z7.array(z7.string().max(40)).max(200).optional(),
|
|
2043
|
+
/** Raise bugs here when asked. */
|
|
2044
|
+
bugs: z7.boolean().optional(),
|
|
2045
|
+
/** Read items here as sources. */
|
|
2046
|
+
sources: z7.boolean().optional()
|
|
2047
|
+
}).strict(),
|
|
2048
|
+
/** The vault reference for its token. */
|
|
2049
|
+
tokenRef: z7.string().regex(/^[a-z][a-z0-9_]*$/),
|
|
2050
|
+
status: z7.enum(["connected", "failing"]).default("connected"),
|
|
2051
|
+
lastSyncAt: z7.string().nullable().default(null),
|
|
2052
|
+
lastError: z7.string().nullable().default(null),
|
|
2053
|
+
createdAt: z7.string(),
|
|
2054
|
+
createdBy: z7.string().default("")
|
|
2055
|
+
}).strict();
|
|
1759
2056
|
|
|
1760
2057
|
// ../engine-playwright/dist/index.js
|
|
1761
2058
|
import { createHmac, randomBytes as randomBytes2 } from "crypto";
|
|
@@ -3990,10 +4287,10 @@ async function captureFailure(opts) {
|
|
|
3990
4287
|
}
|
|
3991
4288
|
let screenshot = null;
|
|
3992
4289
|
try {
|
|
3993
|
-
const
|
|
4290
|
+
const file2 = `step-${String(ordinal).padStart(2, "0")}.png`;
|
|
3994
4291
|
fs4.mkdirSync(dir, { recursive: true });
|
|
3995
4292
|
await page.screenshot({
|
|
3996
|
-
path: path22.join(dir,
|
|
4293
|
+
path: path22.join(dir, file2),
|
|
3997
4294
|
// The viewport, not the full page: a failure is about what was on screen,
|
|
3998
4295
|
// and a full-page capture of an infinite-scroll app is a 20MB image.
|
|
3999
4296
|
fullPage: false,
|
|
@@ -4004,7 +4301,7 @@ async function captureFailure(opts) {
|
|
|
4004
4301
|
maskColor: "#475569",
|
|
4005
4302
|
timeout: 5e3
|
|
4006
4303
|
});
|
|
4007
|
-
screenshot =
|
|
4304
|
+
screenshot = file2;
|
|
4008
4305
|
} catch {
|
|
4009
4306
|
screenshot = null;
|
|
4010
4307
|
}
|
|
@@ -4042,20 +4339,20 @@ async function finaliseArtifacts(opts) {
|
|
|
4042
4339
|
const { context, dir, passed, artifacts, consoleLines } = opts;
|
|
4043
4340
|
const keep = (mode) => mode === "always" || mode === "on-failure" && !passed;
|
|
4044
4341
|
const out = [];
|
|
4045
|
-
const size = (
|
|
4342
|
+
const size = (file2) => {
|
|
4046
4343
|
try {
|
|
4047
|
-
return fs4.statSync(path22.join(dir,
|
|
4344
|
+
return fs4.statSync(path22.join(dir, file2)).size;
|
|
4048
4345
|
} catch {
|
|
4049
4346
|
return null;
|
|
4050
4347
|
}
|
|
4051
4348
|
};
|
|
4052
4349
|
if (artifacts.trace !== "off") {
|
|
4053
|
-
const
|
|
4350
|
+
const file2 = "trace.zip";
|
|
4054
4351
|
try {
|
|
4055
4352
|
if (keep(artifacts.trace)) {
|
|
4056
4353
|
fs4.mkdirSync(dir, { recursive: true });
|
|
4057
|
-
await context.tracing.stop({ path: path22.join(dir,
|
|
4058
|
-
out.push({ kind: "trace", relPath:
|
|
4354
|
+
await context.tracing.stop({ path: path22.join(dir, file2) });
|
|
4355
|
+
out.push({ kind: "trace", relPath: file2, bytes: size(file2) });
|
|
4059
4356
|
} else {
|
|
4060
4357
|
await context.tracing.stop();
|
|
4061
4358
|
}
|
|
@@ -4063,11 +4360,11 @@ async function finaliseArtifacts(opts) {
|
|
|
4063
4360
|
}
|
|
4064
4361
|
}
|
|
4065
4362
|
if (artifacts.console && consoleLines.length) {
|
|
4066
|
-
const
|
|
4363
|
+
const file2 = "console.log";
|
|
4067
4364
|
try {
|
|
4068
4365
|
fs4.mkdirSync(dir, { recursive: true });
|
|
4069
|
-
fs4.writeFileSync(path22.join(dir,
|
|
4070
|
-
out.push({ kind: "console", relPath:
|
|
4366
|
+
fs4.writeFileSync(path22.join(dir, file2), consoleLines.join("\n") + "\n");
|
|
4367
|
+
out.push({ kind: "console", relPath: file2, bytes: size(file2) });
|
|
4071
4368
|
} catch {
|
|
4072
4369
|
}
|
|
4073
4370
|
}
|
|
@@ -4638,8 +4935,8 @@ async function applyAction(loc, step, value, ctx = {}) {
|
|
|
4638
4935
|
const name = String(value ?? "");
|
|
4639
4936
|
const names = step.values ?? (name ? [name] : []);
|
|
4640
4937
|
const files = names.map((n2) => ctx.resolveFile?.(n2));
|
|
4641
|
-
const
|
|
4642
|
-
if (!
|
|
4938
|
+
const file2 = files.every(Boolean);
|
|
4939
|
+
if (!file2) {
|
|
4643
4940
|
throw Object.assign(
|
|
4644
4941
|
new Error(
|
|
4645
4942
|
`this step uploads "${name || "(unnamed)"}", which is not on this machine \u2014 put a copy in .flowriki/files/`
|
|
@@ -6185,8 +6482,8 @@ async function verifySteps(opts) {
|
|
|
6185
6482
|
var cachedAccName = null;
|
|
6186
6483
|
function accNameScript2() {
|
|
6187
6484
|
if (cachedAccName === null) {
|
|
6188
|
-
const
|
|
6189
|
-
cachedAccName = fs32.readFileSync(
|
|
6485
|
+
const path43 = accNameScriptPath();
|
|
6486
|
+
cachedAccName = fs32.readFileSync(path43, "utf8");
|
|
6190
6487
|
}
|
|
6191
6488
|
return cachedAccName;
|
|
6192
6489
|
}
|
|
@@ -6680,8 +6977,8 @@ async function fieldNames(page) {
|
|
|
6680
6977
|
}
|
|
6681
6978
|
|
|
6682
6979
|
// ../results/dist/chunk-LYCTVHPR.js
|
|
6683
|
-
import { z as
|
|
6684
|
-
var RunStatusSchema =
|
|
6980
|
+
import { z as z8 } from "zod";
|
|
6981
|
+
var RunStatusSchema = z8.enum([
|
|
6685
6982
|
"queued",
|
|
6686
6983
|
"running",
|
|
6687
6984
|
"passed",
|
|
@@ -6689,7 +6986,7 @@ var RunStatusSchema = z5.enum([
|
|
|
6689
6986
|
"cancelled",
|
|
6690
6987
|
"errored"
|
|
6691
6988
|
]);
|
|
6692
|
-
var TaskStatusSchema =
|
|
6989
|
+
var TaskStatusSchema = z8.enum([
|
|
6693
6990
|
"queued",
|
|
6694
6991
|
"running",
|
|
6695
6992
|
"passed",
|
|
@@ -6698,7 +6995,7 @@ var TaskStatusSchema = z5.enum([
|
|
|
6698
6995
|
"cancelled",
|
|
6699
6996
|
"errored"
|
|
6700
6997
|
]);
|
|
6701
|
-
var FailureKindSchema =
|
|
6998
|
+
var FailureKindSchema = z8.enum([
|
|
6702
6999
|
"not_found",
|
|
6703
7000
|
"ambiguous",
|
|
6704
7001
|
"timeout",
|
|
@@ -6707,30 +7004,30 @@ var FailureKindSchema = z5.enum([
|
|
|
6707
7004
|
"crash",
|
|
6708
7005
|
"unknown"
|
|
6709
7006
|
]);
|
|
6710
|
-
var RunCheckSchema =
|
|
6711
|
-
id:
|
|
6712
|
-
assertionId:
|
|
6713
|
-
kind:
|
|
6714
|
-
expected:
|
|
6715
|
-
actual:
|
|
6716
|
-
ok:
|
|
7007
|
+
var RunCheckSchema = z8.object({
|
|
7008
|
+
id: z8.string(),
|
|
7009
|
+
assertionId: z8.string().nullable(),
|
|
7010
|
+
kind: z8.string(),
|
|
7011
|
+
expected: z8.string().nullable(),
|
|
7012
|
+
actual: z8.string().nullable(),
|
|
7013
|
+
ok: z8.boolean(),
|
|
6717
7014
|
/** A failed soft check is reported but does not fail its step, so a run can
|
|
6718
7015
|
* be green and still carry findings. Stored per check rather than derived,
|
|
6719
7016
|
* because the flow it came from can be edited afterwards and history has to
|
|
6720
7017
|
* keep saying what actually happened. */
|
|
6721
|
-
soft:
|
|
6722
|
-
detail:
|
|
7018
|
+
soft: z8.boolean().default(false),
|
|
7019
|
+
detail: z8.string().nullable()
|
|
6723
7020
|
});
|
|
6724
|
-
var RunStepSchema =
|
|
6725
|
-
id:
|
|
6726
|
-
ordinal:
|
|
7021
|
+
var RunStepSchema = z8.object({
|
|
7022
|
+
id: z8.string(),
|
|
7023
|
+
ordinal: z8.number().int().nonnegative(),
|
|
6727
7024
|
/** The STABLE document step id — this is what joins runs across time and
|
|
6728
7025
|
* makes flake-rate a view over existing columns rather than a migration. */
|
|
6729
|
-
stepId:
|
|
6730
|
-
action:
|
|
6731
|
-
targetRole:
|
|
6732
|
-
targetName:
|
|
6733
|
-
targetScope:
|
|
7026
|
+
stepId: z8.string(),
|
|
7027
|
+
action: z8.string(),
|
|
7028
|
+
targetRole: z8.string().nullable(),
|
|
7029
|
+
targetName: z8.string().nullable(),
|
|
7030
|
+
targetScope: z8.string().nullable(),
|
|
6734
7031
|
/** What the step typed, chose or pressed.
|
|
6735
7032
|
*
|
|
6736
7033
|
* A report that says `fill "Password"` and nothing else cannot tell you
|
|
@@ -6739,100 +7036,100 @@ var RunStepSchema = z5.object({
|
|
|
6739
7036
|
*
|
|
6740
7037
|
* NEVER set for a secret — see `secret` below. A run report is the thing
|
|
6741
7038
|
* people paste into a ticket. */
|
|
6742
|
-
value:
|
|
7039
|
+
value: z8.string().nullable().default(null),
|
|
6743
7040
|
/** The step typed a secret, so `value` is deliberately absent rather than
|
|
6744
7041
|
* merely unknown. Stored, because "no value" and "a value withheld" render
|
|
6745
7042
|
* differently and history has to keep saying which it was. */
|
|
6746
|
-
secret:
|
|
6747
|
-
status:
|
|
7043
|
+
secret: z8.boolean().default(false),
|
|
7044
|
+
status: z8.enum(["passed", "failed", "skipped"]),
|
|
6748
7045
|
failureKind: FailureKindSchema.nullable(),
|
|
6749
|
-
detail:
|
|
7046
|
+
detail: z8.string().nullable(),
|
|
6750
7047
|
/** WHICH WAY FOUND IT, when the first way did not.
|
|
6751
7048
|
*
|
|
6752
7049
|
* A step carries several routes to its element and takes the first that
|
|
6753
7050
|
* matches. Passing on a later one is not a failure, but it does mean the
|
|
6754
7051
|
* page has moved under the step — and a suite that repairs itself quietly
|
|
6755
7052
|
* drifts away from the application it tests. Null is the ordinary case. */
|
|
6756
|
-
foundBy:
|
|
6757
|
-
durationMs:
|
|
7053
|
+
foundBy: z8.string().nullable().default(null),
|
|
7054
|
+
durationMs: z8.number().int().nonnegative().nullable(),
|
|
6758
7055
|
/** Where the browser was when this step ran. Recorded on failure, and
|
|
6759
7056
|
* frequently the whole answer — the element is missing because a step
|
|
6760
7057
|
* before it navigated somewhere unexpected, not because it was renamed. */
|
|
6761
|
-
pageUrl:
|
|
6762
|
-
pageTitle:
|
|
6763
|
-
checks:
|
|
7058
|
+
pageUrl: z8.string().nullable().default(null),
|
|
7059
|
+
pageTitle: z8.string().nullable().default(null),
|
|
7060
|
+
checks: z8.array(RunCheckSchema).default([])
|
|
6764
7061
|
});
|
|
6765
|
-
var RunTaskSchema =
|
|
6766
|
-
id:
|
|
6767
|
-
ordinal:
|
|
6768
|
-
caseName:
|
|
6769
|
-
rowId:
|
|
7062
|
+
var RunTaskSchema = z8.object({
|
|
7063
|
+
id: z8.string(),
|
|
7064
|
+
ordinal: z8.number().int().nonnegative(),
|
|
7065
|
+
caseName: z8.string(),
|
|
7066
|
+
rowId: z8.string().nullable(),
|
|
6770
7067
|
capability: CapabilitySchema,
|
|
6771
|
-
provider:
|
|
6772
|
-
attempt:
|
|
6773
|
-
maxAttempts:
|
|
7068
|
+
provider: z8.string(),
|
|
7069
|
+
attempt: z8.number().int().positive().default(1),
|
|
7070
|
+
maxAttempts: z8.number().int().positive().default(1),
|
|
6774
7071
|
status: TaskStatusSchema,
|
|
6775
|
-
workerId:
|
|
7072
|
+
workerId: z8.string().nullable(),
|
|
6776
7073
|
/** Cloud provider's own session, so a failure is one click from their video. */
|
|
6777
|
-
sessionId:
|
|
6778
|
-
sessionUrl:
|
|
6779
|
-
detail:
|
|
6780
|
-
stepsRun:
|
|
6781
|
-
stepsTotal:
|
|
6782
|
-
durationMs:
|
|
6783
|
-
steps:
|
|
7074
|
+
sessionId: z8.string().nullable(),
|
|
7075
|
+
sessionUrl: z8.string().nullable(),
|
|
7076
|
+
detail: z8.string().nullable(),
|
|
7077
|
+
stepsRun: z8.number().int().nonnegative().default(0),
|
|
7078
|
+
stepsTotal: z8.number().int().nonnegative().default(0),
|
|
7079
|
+
durationMs: z8.number().int().nonnegative().nullable(),
|
|
7080
|
+
steps: z8.array(RunStepSchema).default([])
|
|
6784
7081
|
});
|
|
6785
|
-
var ArtifactKindSchema =
|
|
6786
|
-
var ArtifactSchema =
|
|
6787
|
-
id:
|
|
6788
|
-
runId:
|
|
6789
|
-
runStepId:
|
|
7082
|
+
var ArtifactKindSchema = z8.enum(["screenshot", "video", "trace", "console", "har"]);
|
|
7083
|
+
var ArtifactSchema = z8.object({
|
|
7084
|
+
id: z8.string(),
|
|
7085
|
+
runId: z8.string(),
|
|
7086
|
+
runStepId: z8.string().nullable(),
|
|
6790
7087
|
kind: ArtifactKindSchema,
|
|
6791
|
-
relPath:
|
|
6792
|
-
bytes:
|
|
6793
|
-
masked:
|
|
6794
|
-
createdAt:
|
|
7088
|
+
relPath: z8.string(),
|
|
7089
|
+
bytes: z8.number().int().nonnegative().nullable(),
|
|
7090
|
+
masked: z8.boolean().default(false),
|
|
7091
|
+
createdAt: z8.string()
|
|
6795
7092
|
});
|
|
6796
|
-
var RunSummarySchema =
|
|
6797
|
-
id:
|
|
6798
|
-
projectId:
|
|
7093
|
+
var RunSummarySchema = z8.object({
|
|
7094
|
+
id: z8.string(),
|
|
7095
|
+
projectId: z8.string(),
|
|
6799
7096
|
/** What the run was FOR.
|
|
6800
7097
|
*
|
|
6801
7098
|
* `flake` is a deliberate repetition of one flow to measure how often it
|
|
6802
7099
|
* passes. It is kept out of pass-rate statistics: a 98/100 flakiness check is
|
|
6803
7100
|
* a useful finding, not a failing suite, and counting it as one would make
|
|
6804
7101
|
* measuring reliability look like losing it. */
|
|
6805
|
-
kind:
|
|
6806
|
-
trigger:
|
|
7102
|
+
kind: z8.enum(["smoke", "test", "adhoc", "flake"]),
|
|
7103
|
+
trigger: z8.enum(["ui", "cli", "ci"]),
|
|
6807
7104
|
status: RunStatusSchema,
|
|
6808
|
-
flowId:
|
|
6809
|
-
flowName:
|
|
6810
|
-
gitSha:
|
|
6811
|
-
gitBranch:
|
|
6812
|
-
gitDirty:
|
|
6813
|
-
env:
|
|
6814
|
-
concurrency:
|
|
6815
|
-
tasksTotal:
|
|
6816
|
-
tasksPassed:
|
|
6817
|
-
tasksFailed:
|
|
7105
|
+
flowId: z8.string().nullable(),
|
|
7106
|
+
flowName: z8.string().nullable(),
|
|
7107
|
+
gitSha: z8.string().nullable(),
|
|
7108
|
+
gitBranch: z8.string().nullable(),
|
|
7109
|
+
gitDirty: z8.boolean().default(false),
|
|
7110
|
+
env: z8.string().nullable(),
|
|
7111
|
+
concurrency: z8.number().int().positive().nullable(),
|
|
7112
|
+
tasksTotal: z8.number().int().nonnegative().default(0),
|
|
7113
|
+
tasksPassed: z8.number().int().nonnegative().default(0),
|
|
7114
|
+
tasksFailed: z8.number().int().nonnegative().default(0),
|
|
6818
7115
|
/** Rolled up from the run's tasks rather than stored on the run, so they
|
|
6819
7116
|
* cannot drift from the step rows they describe. "9 of 12" in a list is
|
|
6820
7117
|
* what tells a reader where a run died; "1 of 1 tasks" tells them nothing
|
|
6821
7118
|
* until there is more than one browser in play. */
|
|
6822
|
-
stepsRun:
|
|
6823
|
-
stepsTotal:
|
|
6824
|
-
queuedAt:
|
|
6825
|
-
startedAt:
|
|
6826
|
-
finishedAt:
|
|
6827
|
-
durationMs:
|
|
7119
|
+
stepsRun: z8.number().int().nonnegative().default(0),
|
|
7120
|
+
stepsTotal: z8.number().int().nonnegative().default(0),
|
|
7121
|
+
queuedAt: z8.string(),
|
|
7122
|
+
startedAt: z8.string().nullable(),
|
|
7123
|
+
finishedAt: z8.string().nullable(),
|
|
7124
|
+
durationMs: z8.number().int().nonnegative().nullable()
|
|
6828
7125
|
});
|
|
6829
7126
|
var RunDetailSchema = RunSummarySchema.extend({
|
|
6830
|
-
errorMessage:
|
|
6831
|
-
artifactsDir:
|
|
6832
|
-
tasks:
|
|
7127
|
+
errorMessage: z8.string().nullable(),
|
|
7128
|
+
artifactsDir: z8.string().nullable(),
|
|
7129
|
+
tasks: z8.array(RunTaskSchema).default([]),
|
|
6833
7130
|
/** Screenshots and the like, addressed by `runStepId`. Kept flat rather than
|
|
6834
7131
|
* nested inside each step: an artifact can belong to the run as a whole. */
|
|
6835
|
-
artifacts:
|
|
7132
|
+
artifacts: z8.array(ArtifactSchema).default([]),
|
|
6836
7133
|
/** The document as it was executed — a flow, or a test case with the copies
|
|
6837
7134
|
* it held that day.
|
|
6838
7135
|
*
|
|
@@ -6841,7 +7138,7 @@ var RunDetailSchema = RunSummarySchema.extend({
|
|
|
6841
7138
|
* edited, and without surfacing it the promise was only half kept: the bytes
|
|
6842
7139
|
* were there and nothing could reach them. Null for runs recorded before a
|
|
6843
7140
|
* snapshot was taken. */
|
|
6844
|
-
flowSnapshot:
|
|
7141
|
+
flowSnapshot: z8.unknown().nullable().default(null)
|
|
6845
7142
|
});
|
|
6846
7143
|
|
|
6847
7144
|
// ../results/dist/chunk-IW3PJWP6.js
|
|
@@ -7068,8 +7365,8 @@ var RUN_COLUMNS = `r.*,
|
|
|
7068
7365
|
(SELECT COALESCE(SUM(steps_run), 0) FROM run_task t WHERE t.run_id = r.id) AS steps_run,
|
|
7069
7366
|
(SELECT COALESCE(SUM(steps_total), 0) FROM run_task t WHERE t.run_id = r.id) AS steps_total`;
|
|
7070
7367
|
var SqliteStorage = class {
|
|
7071
|
-
constructor(
|
|
7072
|
-
this.file =
|
|
7368
|
+
constructor(file2 = defaultDbPath()) {
|
|
7369
|
+
this.file = file2;
|
|
7073
7370
|
}
|
|
7074
7371
|
file;
|
|
7075
7372
|
db = null;
|
|
@@ -7540,7 +7837,7 @@ function toSummary(row) {
|
|
|
7540
7837
|
};
|
|
7541
7838
|
}
|
|
7542
7839
|
|
|
7543
|
-
// ../repository-store/dist/chunk-
|
|
7840
|
+
// ../repository-store/dist/chunk-NTWCKFD3.js
|
|
7544
7841
|
var FLOWRIKI_DIR = ".flowriki";
|
|
7545
7842
|
var FEATURES_DIR = `${FLOWRIKI_DIR}/features`;
|
|
7546
7843
|
var CONFIG_PATH = `${FLOWRIKI_DIR}/config.json`;
|
|
@@ -7552,6 +7849,7 @@ var SUITES_DIR = `${FLOWRIKI_DIR}/suites`;
|
|
|
7552
7849
|
var AUTH_DIR = `${FLOWRIKI_DIR}/auth`;
|
|
7553
7850
|
var ACCOUNTS_DIR = `${AUTH_DIR}/accounts`;
|
|
7554
7851
|
var HELPERS_DIR = `${FLOWRIKI_DIR}/helpers`;
|
|
7852
|
+
var LIBRARY_DIR = `${FLOWRIKI_DIR}/library`;
|
|
7555
7853
|
function assertSlug(slug) {
|
|
7556
7854
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) throw new Error(`invalid slug: ${slug}`);
|
|
7557
7855
|
return slug;
|
|
@@ -7807,8 +8105,8 @@ ${line}
|
|
|
7807
8105
|
);
|
|
7808
8106
|
}
|
|
7809
8107
|
};
|
|
7810
|
-
function writeFileAtomic(
|
|
7811
|
-
const tmp = `${
|
|
8108
|
+
function writeFileAtomic(file2, contents) {
|
|
8109
|
+
const tmp = `${file2}.tmp-${process.pid}-${Date.now()}`;
|
|
7812
8110
|
const handle = fs23.openSync(tmp, "w");
|
|
7813
8111
|
try {
|
|
7814
8112
|
fs23.writeFileSync(handle, contents);
|
|
@@ -7817,7 +8115,7 @@ function writeFileAtomic(file, contents) {
|
|
|
7817
8115
|
fs23.closeSync(handle);
|
|
7818
8116
|
}
|
|
7819
8117
|
try {
|
|
7820
|
-
fs23.renameSync(tmp,
|
|
8118
|
+
fs23.renameSync(tmp, file2);
|
|
7821
8119
|
} catch (err) {
|
|
7822
8120
|
fs23.rmSync(tmp, { force: true });
|
|
7823
8121
|
throw err;
|
|
@@ -7844,13 +8142,13 @@ var Store = class {
|
|
|
7844
8142
|
const out = [];
|
|
7845
8143
|
for (const entry of fs23.readdirSync(this.featuresDir, { withFileTypes: true })) {
|
|
7846
8144
|
if (!entry.isDirectory()) continue;
|
|
7847
|
-
const
|
|
7848
|
-
if (!fs23.existsSync(
|
|
8145
|
+
const file2 = path23.join(this.featuresDir, entry.name, FEATURE_FILE);
|
|
8146
|
+
if (!fs23.existsSync(file2)) continue;
|
|
7849
8147
|
try {
|
|
7850
|
-
out.push(FeatureSchema.parse(JSON.parse(fs23.readFileSync(
|
|
8148
|
+
out.push(FeatureSchema.parse(JSON.parse(fs23.readFileSync(file2, "utf8"))));
|
|
7851
8149
|
} catch (err) {
|
|
7852
8150
|
this.featureProblems.push({
|
|
7853
|
-
path: path23.relative(this.root,
|
|
8151
|
+
path: path23.relative(this.root, file2),
|
|
7854
8152
|
error: String(err instanceof Error ? err.message : err).split("\n")[0].trim(),
|
|
7855
8153
|
recoverable: false
|
|
7856
8154
|
});
|
|
@@ -7892,9 +8190,9 @@ var Store = class {
|
|
|
7892
8190
|
for (const slug of features) {
|
|
7893
8191
|
const dir = path23.join(this.featuresDir, slug);
|
|
7894
8192
|
if (!fs23.existsSync(dir)) continue;
|
|
7895
|
-
for (const
|
|
7896
|
-
if (!isFlowFile(
|
|
7897
|
-
const full = path23.join(dir,
|
|
8193
|
+
for (const file2 of fs23.readdirSync(dir)) {
|
|
8194
|
+
if (!isFlowFile(file2)) continue;
|
|
8195
|
+
const full = path23.join(dir, file2);
|
|
7898
8196
|
try {
|
|
7899
8197
|
out.push(parseFlowDocument(JSON.parse(fs23.readFileSync(full, "utf8"))));
|
|
7900
8198
|
} catch (err) {
|
|
@@ -7923,16 +8221,16 @@ var Store = class {
|
|
|
7923
8221
|
*
|
|
7924
8222
|
* Outside `features/`, because that directory is the product and is
|
|
7925
8223
|
* committed; a recovery copy is machine-local noise. */
|
|
7926
|
-
recoveryPathFor(
|
|
7927
|
-
return path23.join(this.root, ".flowriki", ".recovery", path23.basename(
|
|
8224
|
+
recoveryPathFor(file2) {
|
|
8225
|
+
return path23.join(this.root, ".flowriki", ".recovery", path23.basename(file2) + ".bak");
|
|
7928
8226
|
}
|
|
7929
8227
|
/** Restore a flow file from its recovery copy. Returns false when there is
|
|
7930
8228
|
* none, which is the honest answer rather than an invented document. */
|
|
7931
8229
|
recoverFlowFile(relativePath) {
|
|
7932
|
-
const
|
|
7933
|
-
const backup = this.recoveryPathFor(
|
|
8230
|
+
const file2 = path23.join(this.root, relativePath);
|
|
8231
|
+
const backup = this.recoveryPathFor(file2);
|
|
7934
8232
|
if (!fs23.existsSync(backup)) return false;
|
|
7935
|
-
fs23.copyFileSync(backup,
|
|
8233
|
+
fs23.copyFileSync(backup, file2);
|
|
7936
8234
|
return true;
|
|
7937
8235
|
}
|
|
7938
8236
|
/** Write a flow, atomically, keeping the previous contents recoverable.
|
|
@@ -7951,9 +8249,9 @@ var Store = class {
|
|
|
7951
8249
|
const featureSlug = this.assertSlug(doc.featureId.replace(/^f_/, ""));
|
|
7952
8250
|
const dir = path23.join(this.featuresDir, featureSlug);
|
|
7953
8251
|
fs23.mkdirSync(dir, { recursive: true });
|
|
7954
|
-
const
|
|
7955
|
-
const destination = this.readFlowFile(
|
|
7956
|
-
if (fs23.existsSync(
|
|
8252
|
+
const file2 = path23.join(dir, `${this.assertSlug(doc.slug)}${FLOW_SUFFIX}`);
|
|
8253
|
+
const destination = this.readFlowFile(file2);
|
|
8254
|
+
if (fs23.existsSync(file2) && (!destination || destination.id !== doc.id)) {
|
|
7957
8255
|
throw new FlowConflict("the destination contains another flow or an unreadable document \u2014 choose another name or recover it first", destination?.revision ?? 0);
|
|
7958
8256
|
}
|
|
7959
8257
|
const current = this.getFlow(doc.id) ?? destination;
|
|
@@ -7970,21 +8268,21 @@ var Store = class {
|
|
|
7970
8268
|
}
|
|
7971
8269
|
}
|
|
7972
8270
|
doc.revision = (current?.revision ?? doc.revision ?? 0) + 1;
|
|
7973
|
-
const previousFile = current ? path23.join(this.root, this.relPath(current)) :
|
|
8271
|
+
const previousFile = current ? path23.join(this.root, this.relPath(current)) : file2;
|
|
7974
8272
|
if (fs23.existsSync(previousFile)) {
|
|
7975
8273
|
const backup = this.recoveryPathFor(previousFile);
|
|
7976
8274
|
fs23.mkdirSync(path23.dirname(backup), { recursive: true });
|
|
7977
8275
|
fs23.copyFileSync(previousFile, backup);
|
|
7978
8276
|
this.ensureRecoveryIgnored();
|
|
7979
8277
|
}
|
|
7980
|
-
writeFileAtomic(
|
|
7981
|
-
if (previousFile !==
|
|
7982
|
-
return path23.relative(this.root,
|
|
8278
|
+
writeFileAtomic(file2, JSON.stringify(doc, null, 2) + "\n");
|
|
8279
|
+
if (previousFile !== file2) fs23.rmSync(previousFile);
|
|
8280
|
+
return path23.relative(this.root, file2);
|
|
7983
8281
|
}
|
|
7984
8282
|
/** Read one flow file, or null when it is missing or unreadable. */
|
|
7985
|
-
readFlowFile(
|
|
8283
|
+
readFlowFile(file2) {
|
|
7986
8284
|
try {
|
|
7987
|
-
return parseFlowDocument(JSON.parse(fs23.readFileSync(
|
|
8285
|
+
return parseFlowDocument(JSON.parse(fs23.readFileSync(file2, "utf8")));
|
|
7988
8286
|
} catch {
|
|
7989
8287
|
return null;
|
|
7990
8288
|
}
|
|
@@ -8111,8 +8409,8 @@ var Store = class {
|
|
|
8111
8409
|
resolveFile(name) {
|
|
8112
8410
|
const first = name;
|
|
8113
8411
|
if (!first || first.includes("/") || first.includes("\\") || first.includes("..")) return void 0;
|
|
8114
|
-
const
|
|
8115
|
-
return fs23.existsSync(
|
|
8412
|
+
const file2 = path23.join(this.filesDir, first);
|
|
8413
|
+
return fs23.existsSync(file2) ? file2 : void 0;
|
|
8116
8414
|
}
|
|
8117
8415
|
// ── secret values, held by `LocalSecrets` ───────────────────────────────
|
|
8118
8416
|
//
|
|
@@ -8239,9 +8537,9 @@ function writeMarker(root, marker, opts = {}) {
|
|
|
8239
8537
|
if (!facts.clean) {
|
|
8240
8538
|
throw new ClaimRefused("this repository has uncommitted changes \u2014 commit or stash them first");
|
|
8241
8539
|
}
|
|
8242
|
-
const
|
|
8243
|
-
fs33.mkdirSync(path33.dirname(
|
|
8244
|
-
fs33.writeFileSync(
|
|
8540
|
+
const file2 = path33.join(root, MARKER_PATH);
|
|
8541
|
+
fs33.mkdirSync(path33.dirname(file2), { recursive: true });
|
|
8542
|
+
fs33.writeFileSync(file2, JSON.stringify(marker, null, 2) + "\n");
|
|
8245
8543
|
git2(root, ["add", "--", MARKER_PATH]);
|
|
8246
8544
|
git2(root, ["commit", "-m", `Bind this repository to Flowriki project ${marker.projectId}`]);
|
|
8247
8545
|
const commit = git2(root, ["rev-parse", "HEAD"]);
|
|
@@ -8588,7 +8886,7 @@ async function executeFlow(opts) {
|
|
|
8588
8886
|
for (let attempt = 1; attempt <= repeat; attempt++) {
|
|
8589
8887
|
if (opts.signal?.aborted) break;
|
|
8590
8888
|
const evidenceDir = runDir && repeat > 1 ? path7.join(runDir, `attempt-${String(attempt).padStart(3, "0")}`) : runDir;
|
|
8591
|
-
const relativeTo = (
|
|
8889
|
+
const relativeTo = (file2) => repeat > 1 ? `attempt-${String(attempt).padStart(3, "0")}/${file2}` : file2;
|
|
8592
8890
|
const taskId = storage && run ? await storage.createTask(run.id, {
|
|
8593
8891
|
caseName: repeat > 1 ? `${flow.name} \xB7 attempt ${attempt}` : flow.name,
|
|
8594
8892
|
capability: LOCAL_CHROMIUM,
|
|
@@ -8771,10 +9069,10 @@ function report(attempts, perStep) {
|
|
|
8771
9069
|
steps
|
|
8772
9070
|
};
|
|
8773
9071
|
}
|
|
8774
|
-
function sizeOf(dir,
|
|
9072
|
+
function sizeOf(dir, file2) {
|
|
8775
9073
|
if (!dir) return null;
|
|
8776
9074
|
try {
|
|
8777
|
-
return fs7.statSync(path7.join(dir,
|
|
9075
|
+
return fs7.statSync(path7.join(dir, file2)).size;
|
|
8778
9076
|
} catch {
|
|
8779
9077
|
return null;
|
|
8780
9078
|
}
|
|
@@ -9227,10 +9525,10 @@ var RecordingCoordinator = class {
|
|
|
9227
9525
|
// saveFlow bumps this to 1; a fresh document starts unrevised.
|
|
9228
9526
|
revision: 0
|
|
9229
9527
|
};
|
|
9230
|
-
const
|
|
9528
|
+
const file2 = this.deps.store.saveFlow(doc);
|
|
9231
9529
|
this.draft = null;
|
|
9232
|
-
const saved = { ...this.deps.store.getFlow(doc.id) ?? doc, path:
|
|
9233
|
-
this.deps.emit({ type: "recording.stopped", flow: saved, file });
|
|
9530
|
+
const saved = { ...this.deps.store.getFlow(doc.id) ?? doc, path: file2 };
|
|
9531
|
+
this.deps.emit({ type: "recording.stopped", flow: saved, file: file2 });
|
|
9234
9532
|
return this.lastResult = { ok: true, flow: saved, secrets: captured };
|
|
9235
9533
|
}
|
|
9236
9534
|
/** Throw the recording away: close the browser, drop the draft, write
|
|
@@ -9575,6 +9873,9 @@ function resolveCheckouts(dirs) {
|
|
|
9575
9873
|
return { projects, skipped };
|
|
9576
9874
|
}
|
|
9577
9875
|
|
|
9876
|
+
// src/engine/session.ts
|
|
9877
|
+
import fs12 from "fs";
|
|
9878
|
+
|
|
9578
9879
|
// ../agent-core/dist/chunk-LR7LBGCT.js
|
|
9579
9880
|
import { WebSocket, WebSocketServer } from "ws";
|
|
9580
9881
|
var HEARTBEAT_MS = 3e4;
|
|
@@ -9753,15 +10054,15 @@ var EventJournal = class {
|
|
|
9753
10054
|
};
|
|
9754
10055
|
var { DatabaseSync } = createRequire4(import.meta.url)("node:sqlite");
|
|
9755
10056
|
var AgentState = class {
|
|
9756
|
-
constructor(
|
|
10057
|
+
constructor(file2, deviceId, commandLimit = 1e5) {
|
|
9757
10058
|
this.commandLimit = commandLimit;
|
|
9758
|
-
if (!
|
|
10059
|
+
if (!file2 || file2 === ":memory:") throw new Error("a persistent agent state file is required");
|
|
9759
10060
|
if (!Number.isInteger(commandLimit) || commandLimit < 1) throw new Error("invalid command capacity");
|
|
9760
|
-
fs9.mkdirSync(path9.dirname(
|
|
9761
|
-
const fd = fs9.openSync(
|
|
10061
|
+
fs9.mkdirSync(path9.dirname(file2), { recursive: true, mode: 448 });
|
|
10062
|
+
const fd = fs9.openSync(file2, "a", 384);
|
|
9762
10063
|
fs9.closeSync(fd);
|
|
9763
|
-
fs9.chmodSync(
|
|
9764
|
-
this.db = new DatabaseSync(
|
|
10064
|
+
fs9.chmodSync(file2, 384);
|
|
10065
|
+
this.db = new DatabaseSync(file2);
|
|
9765
10066
|
try {
|
|
9766
10067
|
this.db.exec("PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL;");
|
|
9767
10068
|
const version = Number(this.db.prepare("PRAGMA user_version").get()?.user_version);
|
|
@@ -10179,6 +10480,13 @@ import os5 from "os";
|
|
|
10179
10480
|
import path10 from "path";
|
|
10180
10481
|
import fs25 from "fs";
|
|
10181
10482
|
import path24 from "path";
|
|
10483
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
10484
|
+
import fs34 from "fs";
|
|
10485
|
+
import path34 from "path";
|
|
10486
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
10487
|
+
import fs43 from "fs";
|
|
10488
|
+
import os22 from "os";
|
|
10489
|
+
import path42 from "path";
|
|
10182
10490
|
var NO_CHECKOUT = {
|
|
10183
10491
|
ok: false,
|
|
10184
10492
|
reason: "failed",
|
|
@@ -10248,12 +10556,12 @@ function repositoryHandlers({ storeFor }) {
|
|
|
10248
10556
|
return { ok: false, reason: "failed", detail: "no flow document was sent" };
|
|
10249
10557
|
}
|
|
10250
10558
|
try {
|
|
10251
|
-
const
|
|
10559
|
+
const path52 = store.saveFlow(flow, {
|
|
10252
10560
|
create: command.args["create"] === true,
|
|
10253
10561
|
...command.flowRevision !== void 0 ? { expectedRevision: command.flowRevision } : {}
|
|
10254
10562
|
});
|
|
10255
10563
|
const saved = store.getFlow(flow.id);
|
|
10256
|
-
return { ok: true, result: { path:
|
|
10564
|
+
return { ok: true, result: { path: path52, revision: saved?.revision ?? null } };
|
|
10257
10565
|
} catch (err) {
|
|
10258
10566
|
if (err instanceof FlowConflict) {
|
|
10259
10567
|
return { ok: false, reason: "stale_revision", detail: err.message };
|
|
@@ -10422,11 +10730,11 @@ function historyHandlers({ storage, artifactLimit }) {
|
|
|
10422
10730
|
const artifact = run.artifacts.find((a) => a.id === artifactId);
|
|
10423
10731
|
if (!artifact) return { ok: false, reason: "failed", detail: "no such artifact on that run" };
|
|
10424
10732
|
const dir = run.artifactsDir ?? runArtifactsDir(runId, run.projectId);
|
|
10425
|
-
const
|
|
10426
|
-
if (!isInside(dir,
|
|
10733
|
+
const file2 = path24.join(dir, artifact.relPath);
|
|
10734
|
+
if (!isInside(dir, file2) || !fs25.existsSync(file2) || !isInside(fs25.realpathSync(dir), fs25.realpathSync(file2))) {
|
|
10427
10735
|
return { ok: false, reason: "failed", detail: "the artifact file is not on this machine" };
|
|
10428
10736
|
}
|
|
10429
|
-
const { size } = fs25.statSync(
|
|
10737
|
+
const { size } = fs25.statSync(file2);
|
|
10430
10738
|
if (size > artifactLimit) {
|
|
10431
10739
|
return { ok: false, reason: "failed", detail: `this artifact is ${size} bytes, over the ${artifactLimit}-byte transfer limit` };
|
|
10432
10740
|
}
|
|
@@ -10437,7 +10745,7 @@ function historyHandlers({ storage, artifactLimit }) {
|
|
|
10437
10745
|
kind: artifact.kind,
|
|
10438
10746
|
masked: artifact.masked,
|
|
10439
10747
|
bytes: size,
|
|
10440
|
-
base64: fs25.readFileSync(
|
|
10748
|
+
base64: fs25.readFileSync(file2).toString("base64")
|
|
10441
10749
|
}
|
|
10442
10750
|
};
|
|
10443
10751
|
}
|
|
@@ -10838,6 +11146,118 @@ function debuggingHandlers(ctx) {
|
|
|
10838
11146
|
}
|
|
10839
11147
|
};
|
|
10840
11148
|
}
|
|
11149
|
+
var file = () => path34.join(flowrikiHome(), "assistant-folders.json");
|
|
11150
|
+
function readAll() {
|
|
11151
|
+
try {
|
|
11152
|
+
const parsed = JSON.parse(fs34.readFileSync(file(), "utf8"));
|
|
11153
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
11154
|
+
} catch {
|
|
11155
|
+
return {};
|
|
11156
|
+
}
|
|
11157
|
+
}
|
|
11158
|
+
function gitHead(folder) {
|
|
11159
|
+
try {
|
|
11160
|
+
const head = fs34.readFileSync(path34.join(folder, ".git", "HEAD"), "utf8").trim();
|
|
11161
|
+
const ref = /^ref: (.+)$/.exec(head)?.[1];
|
|
11162
|
+
if (!ref) return { commit: /^[0-9a-f]{40}$/.test(head) ? head : null, branch: null };
|
|
11163
|
+
const branch = ref.replace(/^refs\/heads\//, "");
|
|
11164
|
+
const loose = path34.join(folder, ".git", ref);
|
|
11165
|
+
if (fs34.existsSync(loose)) return { commit: fs34.readFileSync(loose, "utf8").trim(), branch };
|
|
11166
|
+
const packed = fs34.readFileSync(path34.join(folder, ".git", "packed-refs"), "utf8");
|
|
11167
|
+
const line = packed.split("\n").find((l) => l.endsWith(` ${ref}`));
|
|
11168
|
+
return { commit: line?.split(" ")[0] ?? null, branch };
|
|
11169
|
+
} catch {
|
|
11170
|
+
return { commit: null, branch: null };
|
|
11171
|
+
}
|
|
11172
|
+
}
|
|
11173
|
+
function describeFolder(folder) {
|
|
11174
|
+
const exists = (() => {
|
|
11175
|
+
try {
|
|
11176
|
+
return fs34.statSync(folder).isDirectory();
|
|
11177
|
+
} catch {
|
|
11178
|
+
return false;
|
|
11179
|
+
}
|
|
11180
|
+
})();
|
|
11181
|
+
return { path: folder, exists, ...exists ? gitHead(folder) : { commit: null, branch: null } };
|
|
11182
|
+
}
|
|
11183
|
+
function folderFor(projectId) {
|
|
11184
|
+
const folder = readAll()[projectId];
|
|
11185
|
+
return folder ? describeFolder(folder) : null;
|
|
11186
|
+
}
|
|
11187
|
+
function setFolder(projectId, folder) {
|
|
11188
|
+
const all = readAll();
|
|
11189
|
+
if (folder === null) {
|
|
11190
|
+
delete all[projectId];
|
|
11191
|
+
} else {
|
|
11192
|
+
const resolved = path34.resolve(folder.replace(/^~(?=$|\/)/, process.env["HOME"] ?? "~"));
|
|
11193
|
+
if (!path34.isAbsolute(resolved)) throw new Error("give the folder's full path");
|
|
11194
|
+
if (!describeFolder(resolved).exists) throw new Error(`there is no folder at ${resolved} on this computer`);
|
|
11195
|
+
if (resolved === path34.parse(resolved).root || resolved === process.env["HOME"]) throw new Error("link the project's own folder, not the whole disk or home folder");
|
|
11196
|
+
all[projectId] = resolved;
|
|
11197
|
+
}
|
|
11198
|
+
fs34.mkdirSync(path34.dirname(file()), { recursive: true, mode: 448 });
|
|
11199
|
+
fs34.writeFileSync(file(), JSON.stringify(all, null, 2), { mode: 384 });
|
|
11200
|
+
return folder === null ? null : folderFor(projectId);
|
|
11201
|
+
}
|
|
11202
|
+
var MAX_PROMPT = 4e3;
|
|
11203
|
+
function assistantHandlers({ deps }) {
|
|
11204
|
+
const assistant = deps.assistant;
|
|
11205
|
+
if (!assistant) return {};
|
|
11206
|
+
return {
|
|
11207
|
+
"assistant.status": async () => ({
|
|
11208
|
+
ok: true,
|
|
11209
|
+
result: { ...await assistant.status(), busy: assistant.busy() }
|
|
11210
|
+
}),
|
|
11211
|
+
"assistant.connect": async (_command, _project, emit) => {
|
|
11212
|
+
const started = assistant.connect(emit);
|
|
11213
|
+
return started.started ? { ok: true, result: { started: true } } : { ok: false, reason: "busy", detail: started.detail ?? "could not start the sign-in" };
|
|
11214
|
+
},
|
|
11215
|
+
"assistant.ask": async (command, project, emit) => {
|
|
11216
|
+
const prompt = typeof command.args["prompt"] === "string" ? command.args["prompt"].trim() : "";
|
|
11217
|
+
if (!prompt) return { ok: false, reason: "failed", detail: "there is nothing to ask" };
|
|
11218
|
+
if (prompt.length > MAX_PROMPT) {
|
|
11219
|
+
return { ok: false, reason: "failed", detail: `keep it under ${MAX_PROMPT} characters` };
|
|
11220
|
+
}
|
|
11221
|
+
const sessionId = command.args["sessionId"];
|
|
11222
|
+
if (sessionId !== void 0 && !(typeof sessionId === "string" && /^[\w-]{8,100}$/.test(sessionId))) {
|
|
11223
|
+
return { ok: false, reason: "failed", detail: "that conversation id is not valid" };
|
|
11224
|
+
}
|
|
11225
|
+
const status2 = await assistant.status();
|
|
11226
|
+
if (!status2.installed) return { ok: false, reason: "failed", detail: status2.detail ?? "the assistant is not installed on this machine" };
|
|
11227
|
+
if (!status2.signedIn) return { ok: false, reason: "failed", detail: "the assistant is not signed in on this machine" };
|
|
11228
|
+
const askId = randomUUID2();
|
|
11229
|
+
const linked = folderFor(project.id);
|
|
11230
|
+
const mode = command.args["mode"] === "ask" ? "ask" : command.args["mode"] === "do" ? "do" : void 0;
|
|
11231
|
+
const started = assistant.ask({
|
|
11232
|
+
askId,
|
|
11233
|
+
prompt,
|
|
11234
|
+
projectId: project.id,
|
|
11235
|
+
projectName: project.name,
|
|
11236
|
+
...linked?.exists ? { folder: linked.path } : {},
|
|
11237
|
+
...mode ? { mode } : {},
|
|
11238
|
+
...typeof sessionId === "string" ? { sessionId } : {},
|
|
11239
|
+
emit
|
|
11240
|
+
});
|
|
11241
|
+
return started.started ? { ok: true, result: { askId } } : { ok: false, reason: "busy", detail: started.detail ?? "the assistant is busy" };
|
|
11242
|
+
},
|
|
11243
|
+
/** Which folder on this computer holds the project's code — read with no
|
|
11244
|
+
* `path`, linked with a path, unlinked with null. */
|
|
11245
|
+
"assistant.folder": async (command, project) => {
|
|
11246
|
+
try {
|
|
11247
|
+
if (command.args["path"] === void 0) return { ok: true, result: { folder: folderFor(project.id) } };
|
|
11248
|
+
const asked = command.args["path"];
|
|
11249
|
+
const folder = setFolder(project.id, asked === null ? null : String(asked));
|
|
11250
|
+
return { ok: true, result: { folder } };
|
|
11251
|
+
} catch (error) {
|
|
11252
|
+
return { ok: false, reason: "failed", detail: error.message };
|
|
11253
|
+
}
|
|
11254
|
+
},
|
|
11255
|
+
"assistant.cancel": async (command) => {
|
|
11256
|
+
const askId = String(command.args["askId"] ?? "");
|
|
11257
|
+
return assistant.cancel(askId) ? { ok: true, result: { cancelled: true } } : { ok: false, reason: "failed", detail: "that question is not running" };
|
|
11258
|
+
}
|
|
11259
|
+
};
|
|
11260
|
+
}
|
|
10841
11261
|
function buildHandlers(deps) {
|
|
10842
11262
|
const context = {
|
|
10843
11263
|
deps,
|
|
@@ -10864,9 +11284,322 @@ function buildHandlers(deps) {
|
|
|
10864
11284
|
...historyHandlers(context),
|
|
10865
11285
|
...executionHandlers(context),
|
|
10866
11286
|
...recordingHandlers(context),
|
|
11287
|
+
...assistantHandlers(context),
|
|
10867
11288
|
...debug
|
|
10868
11289
|
};
|
|
10869
11290
|
}
|
|
11291
|
+
var MCP_SERVER_NAME = "flowriki";
|
|
11292
|
+
var TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
|
|
11293
|
+
var SHOWN_KEYS = ["action", "role", "name", "kind", "title", "flow", "feature", "folder", "case", "op"];
|
|
11294
|
+
function describeToolInput(input) {
|
|
11295
|
+
if (!input || typeof input !== "object") return "";
|
|
11296
|
+
const record = input;
|
|
11297
|
+
const parts = [];
|
|
11298
|
+
for (const key of SHOWN_KEYS) {
|
|
11299
|
+
const value = record[key];
|
|
11300
|
+
if (typeof value === "string" && value) parts.push(value.length > 80 ? `${value.slice(0, 77)}\u2026` : value);
|
|
11301
|
+
}
|
|
11302
|
+
return parts.join(" \xB7 ");
|
|
11303
|
+
}
|
|
11304
|
+
var clip = (value, max = 100) => value.length > max ? `${value.slice(0, max - 1)}\u2026` : value;
|
|
11305
|
+
function toolArgs(tool, input) {
|
|
11306
|
+
if (!input || typeof input !== "object") return {};
|
|
11307
|
+
const record = input;
|
|
11308
|
+
const out = {};
|
|
11309
|
+
for (const key of ["action", "role", "name", "kind", "op", "within", "title", "flow", "feature", "folder", "startPath", "helperId", "saveAs", "signIn"]) {
|
|
11310
|
+
const value = record[key];
|
|
11311
|
+
if (typeof value === "string" && value) out[key] = clip(value);
|
|
11312
|
+
}
|
|
11313
|
+
for (const key of ["nth", "number"]) {
|
|
11314
|
+
const value = record[key];
|
|
11315
|
+
if (typeof value === "number" && Number.isFinite(value)) out[key] = value;
|
|
11316
|
+
}
|
|
11317
|
+
if (record["newBranch"] === true) out["newBranch"] = true;
|
|
11318
|
+
if (tool === "add_check" && typeof record["value"] === "string") out["value"] = clip(record["value"], 120);
|
|
11319
|
+
if (Array.isArray(record["after"])) {
|
|
11320
|
+
out["after"] = record["after"].filter((v) => typeof v === "string").slice(0, 5).map((v) => clip(v));
|
|
11321
|
+
}
|
|
11322
|
+
if (Array.isArray(record["rows"])) {
|
|
11323
|
+
out["flows"] = record["rows"].map((row) => row && typeof row === "object" ? row["flow"] : void 0).filter((v) => typeof v === "string").slice(0, 12).map((v) => clip(v));
|
|
11324
|
+
}
|
|
11325
|
+
return out;
|
|
11326
|
+
}
|
|
11327
|
+
function parseAuthStatus(raw) {
|
|
11328
|
+
try {
|
|
11329
|
+
const parsed = JSON.parse(raw);
|
|
11330
|
+
return {
|
|
11331
|
+
signedIn: parsed.loggedIn === true,
|
|
11332
|
+
method: typeof parsed.authMethod === "string" ? parsed.authMethod : null,
|
|
11333
|
+
plan: typeof parsed.subscriptionType === "string" ? parsed.subscriptionType : null
|
|
11334
|
+
};
|
|
11335
|
+
} catch {
|
|
11336
|
+
return { signedIn: false, method: null, plan: null };
|
|
11337
|
+
}
|
|
11338
|
+
}
|
|
11339
|
+
var ASK_MODE_TOOLS = [
|
|
11340
|
+
"list_projects",
|
|
11341
|
+
"project_catalog",
|
|
11342
|
+
"project_brief",
|
|
11343
|
+
"knowledge_search",
|
|
11344
|
+
"knowledge_read",
|
|
11345
|
+
"sources",
|
|
11346
|
+
"requirements",
|
|
11347
|
+
"find_similar_cases",
|
|
11348
|
+
"library_cases",
|
|
11349
|
+
"test_cycles",
|
|
11350
|
+
"bugs",
|
|
11351
|
+
"list_my_branches",
|
|
11352
|
+
"integrations"
|
|
11353
|
+
];
|
|
11354
|
+
function buildAskArgs(opts) {
|
|
11355
|
+
const ask = opts.mode === "ask";
|
|
11356
|
+
const guidance = [
|
|
11357
|
+
`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. Start with project_brief: it is this project's memory.`,
|
|
11358
|
+
ask ? "ASK MODE: answer the question; change nothing. Cite what you rely on as [[testCase:TC-12]], [[source:src_x@v2]], [[requirement:req_x]], [[knowledge:features/checkout.md]], [[automation:tc_x]], [[cycle:cyc_x]], [[bug:BUG-3]] 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.",
|
|
11359
|
+
opts.folder ? `The project's code is in ${opts.folder}. Read it with Read, Grep and Glob when it helps; you cannot change it.` : ""
|
|
11360
|
+
].filter(Boolean).join("\n");
|
|
11361
|
+
const flowriki = ask ? ASK_MODE_TOOLS.map((t) => `mcp__${MCP_SERVER_NAME}__${t}`) : [`mcp__${MCP_SERVER_NAME}`];
|
|
11362
|
+
const files = opts.folder ? ["Read", "Grep", "Glob"] : [];
|
|
11363
|
+
return [
|
|
11364
|
+
"-p",
|
|
11365
|
+
opts.prompt,
|
|
11366
|
+
"--output-format",
|
|
11367
|
+
"stream-json",
|
|
11368
|
+
"--verbose",
|
|
11369
|
+
"--mcp-config",
|
|
11370
|
+
opts.mcpConfigPath,
|
|
11371
|
+
"--strict-mcp-config",
|
|
11372
|
+
// No built-in tools but reading files in the linked folder — no shell, no
|
|
11373
|
+
// edits, no web.
|
|
11374
|
+
"--tools",
|
|
11375
|
+
files.join(","),
|
|
11376
|
+
...opts.folder ? ["--add-dir", opts.folder] : [],
|
|
11377
|
+
// Only these, and anything else is denied rather than asked about: there
|
|
11378
|
+
// is nobody at a terminal to answer.
|
|
11379
|
+
"--allowedTools",
|
|
11380
|
+
[...flowriki, ...files].join(","),
|
|
11381
|
+
"--permission-mode",
|
|
11382
|
+
"dontAsk",
|
|
11383
|
+
"--append-system-prompt",
|
|
11384
|
+
guidance,
|
|
11385
|
+
...opts.sessionId ? ["--resume", opts.sessionId] : []
|
|
11386
|
+
];
|
|
11387
|
+
}
|
|
11388
|
+
function eventsFromLine(askId, line) {
|
|
11389
|
+
let parsed;
|
|
11390
|
+
try {
|
|
11391
|
+
parsed = JSON.parse(line);
|
|
11392
|
+
} catch {
|
|
11393
|
+
return [];
|
|
11394
|
+
}
|
|
11395
|
+
const out = [];
|
|
11396
|
+
if (parsed.type === "system" && parsed.subtype === "init" && parsed.session_id) {
|
|
11397
|
+
out.push({ name: "assistant.session", data: { askId, sessionId: parsed.session_id } });
|
|
11398
|
+
}
|
|
11399
|
+
if (parsed.type === "assistant") {
|
|
11400
|
+
for (const block of parsed.message?.content ?? []) {
|
|
11401
|
+
if (block.type === "text" && block.text?.trim()) {
|
|
11402
|
+
out.push({ name: "assistant.text", data: { askId, text: block.text } });
|
|
11403
|
+
} else if (block.type === "tool_use" && block.name) {
|
|
11404
|
+
const tool = block.name.replace(TOOL_PREFIX, "");
|
|
11405
|
+
out.push({
|
|
11406
|
+
name: "assistant.tool",
|
|
11407
|
+
data: {
|
|
11408
|
+
askId,
|
|
11409
|
+
tool,
|
|
11410
|
+
target: describeToolInput(block.input),
|
|
11411
|
+
args: toolArgs(tool, block.input),
|
|
11412
|
+
...block.id ? { id: block.id } : {}
|
|
11413
|
+
}
|
|
11414
|
+
});
|
|
11415
|
+
}
|
|
11416
|
+
}
|
|
11417
|
+
}
|
|
11418
|
+
if (parsed.type === "user") {
|
|
11419
|
+
for (const block of parsed.message?.content ?? []) {
|
|
11420
|
+
if (block.type !== "tool_result" || !block.tool_use_id) continue;
|
|
11421
|
+
const raw = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((part) => part && typeof part === "object" ? String(part.text ?? "") : "").join("\n") : "";
|
|
11422
|
+
const first = raw.split("\n").map((l) => l.trim()).find(Boolean) ?? "";
|
|
11423
|
+
const changeId = /\/changes\/(chg_[A-Za-z0-9]+)/.exec(raw)?.[1];
|
|
11424
|
+
out.push({
|
|
11425
|
+
name: "assistant.toolResult",
|
|
11426
|
+
data: { askId, id: block.tool_use_id, ok: block.is_error !== true, summary: clip(first, 240), ...changeId ? { changeId } : {} }
|
|
11427
|
+
});
|
|
11428
|
+
}
|
|
11429
|
+
}
|
|
11430
|
+
if (parsed.type === "result") {
|
|
11431
|
+
out.push({
|
|
11432
|
+
name: "assistant.done",
|
|
11433
|
+
data: {
|
|
11434
|
+
askId,
|
|
11435
|
+
ok: parsed.is_error !== true,
|
|
11436
|
+
result: typeof parsed.result === "string" ? parsed.result : "",
|
|
11437
|
+
...parsed.session_id ? { sessionId: parsed.session_id } : {}
|
|
11438
|
+
}
|
|
11439
|
+
});
|
|
11440
|
+
}
|
|
11441
|
+
return out;
|
|
11442
|
+
}
|
|
11443
|
+
function createClaudeCli(options) {
|
|
11444
|
+
const spawn2 = options.spawn ?? nodeSpawn;
|
|
11445
|
+
const binary = options.binary ?? process.env["FLOWRIKI_CLAUDE_BIN"] ?? "claude";
|
|
11446
|
+
const maxAskMs = options.maxAskMs ?? 30 * 6e4;
|
|
11447
|
+
const scratchRoot2 = options.scratchRoot ?? path42.join(os22.tmpdir(), "flowriki-claude");
|
|
11448
|
+
let current = null;
|
|
11449
|
+
let cancelled = null;
|
|
11450
|
+
function capture(args, timeoutMs = 15e3) {
|
|
11451
|
+
return new Promise((resolve) => {
|
|
11452
|
+
let out = "", err = "", settled = false;
|
|
11453
|
+
const finish = (code, failed) => {
|
|
11454
|
+
if (settled) return;
|
|
11455
|
+
settled = true;
|
|
11456
|
+
clearTimeout(timer);
|
|
11457
|
+
resolve({ code, out, err, failed });
|
|
11458
|
+
};
|
|
11459
|
+
let child;
|
|
11460
|
+
try {
|
|
11461
|
+
child = spawn2(binary, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
11462
|
+
} catch {
|
|
11463
|
+
return resolve({ code: null, out: "", err: "", failed: true });
|
|
11464
|
+
}
|
|
11465
|
+
const timer = setTimeout(() => {
|
|
11466
|
+
child.kill("SIGKILL");
|
|
11467
|
+
finish(null, true);
|
|
11468
|
+
}, timeoutMs);
|
|
11469
|
+
child.stdout?.on("data", (chunk) => {
|
|
11470
|
+
out += chunk.toString();
|
|
11471
|
+
});
|
|
11472
|
+
child.stderr?.on("data", (chunk) => {
|
|
11473
|
+
err += chunk.toString();
|
|
11474
|
+
});
|
|
11475
|
+
child.on("error", () => finish(null, true));
|
|
11476
|
+
child.on("close", (code) => finish(code, false));
|
|
11477
|
+
});
|
|
11478
|
+
}
|
|
11479
|
+
return {
|
|
11480
|
+
async status() {
|
|
11481
|
+
const version = await capture(["--version"]);
|
|
11482
|
+
if (version.failed || version.code !== 0) {
|
|
11483
|
+
return { installed: false, version: null, signedIn: false, method: null, plan: null, detail: "The assistant\u2019s command-line tool was not found on this machine." };
|
|
11484
|
+
}
|
|
11485
|
+
const auth = await capture(["auth", "status", "--json"]);
|
|
11486
|
+
const parsed = parseAuthStatus(auth.out);
|
|
11487
|
+
return { installed: true, version: version.out.trim().split(/\s+/)[0] ?? null, ...parsed };
|
|
11488
|
+
},
|
|
11489
|
+
connect(emit) {
|
|
11490
|
+
if (current) return { started: false, detail: "The assistant is busy answering a question." };
|
|
11491
|
+
let child;
|
|
11492
|
+
try {
|
|
11493
|
+
child = spawn2(binary, ["auth", "login"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
11494
|
+
} catch {
|
|
11495
|
+
return { started: false, detail: "The assistant\u2019s command-line tool could not be started." };
|
|
11496
|
+
}
|
|
11497
|
+
let finished = false;
|
|
11498
|
+
const done = async (detail) => {
|
|
11499
|
+
if (finished) return;
|
|
11500
|
+
finished = true;
|
|
11501
|
+
const status2 = await this.status();
|
|
11502
|
+
emit("assistant.connect.finished", { signedIn: status2.signedIn, ...detail ? { detail } : {} });
|
|
11503
|
+
};
|
|
11504
|
+
const timer = setTimeout(() => {
|
|
11505
|
+
child.kill("SIGTERM");
|
|
11506
|
+
void done("The sign-in was not completed in time.");
|
|
11507
|
+
}, 5 * 6e4);
|
|
11508
|
+
timer.unref?.();
|
|
11509
|
+
child.on("error", () => {
|
|
11510
|
+
clearTimeout(timer);
|
|
11511
|
+
void done("The assistant\u2019s command-line tool could not be started.");
|
|
11512
|
+
});
|
|
11513
|
+
child.on("close", () => {
|
|
11514
|
+
clearTimeout(timer);
|
|
11515
|
+
void done();
|
|
11516
|
+
});
|
|
11517
|
+
return { started: true };
|
|
11518
|
+
},
|
|
11519
|
+
ask(ask) {
|
|
11520
|
+
if (current) return { started: false, detail: "The assistant is already answering a question on this machine." };
|
|
11521
|
+
fs43.mkdirSync(scratchRoot2, { recursive: true, mode: 448 });
|
|
11522
|
+
const dir = fs43.mkdtempSync(path42.join(scratchRoot2, "ask-"));
|
|
11523
|
+
const mcpConfigPath = path42.join(dir, "mcp.json");
|
|
11524
|
+
fs43.writeFileSync(
|
|
11525
|
+
mcpConfigPath,
|
|
11526
|
+
JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: { command: options.mcp.command, args: options.mcp.args } } }),
|
|
11527
|
+
{ mode: 384 }
|
|
11528
|
+
);
|
|
11529
|
+
let child;
|
|
11530
|
+
try {
|
|
11531
|
+
child = spawn2(
|
|
11532
|
+
binary,
|
|
11533
|
+
buildAskArgs({
|
|
11534
|
+
prompt: ask.prompt,
|
|
11535
|
+
mcpConfigPath,
|
|
11536
|
+
projectId: ask.projectId,
|
|
11537
|
+
projectName: ask.projectName,
|
|
11538
|
+
...ask.sessionId ? { sessionId: ask.sessionId } : {},
|
|
11539
|
+
...ask.folder ? { folder: ask.folder } : {},
|
|
11540
|
+
...ask.mode ? { mode: ask.mode } : {}
|
|
11541
|
+
}),
|
|
11542
|
+
// THE SCRATCH FOLDER, not the project: nothing here to read or write.
|
|
11543
|
+
{ cwd: dir, stdio: ["ignore", "pipe", "pipe"] }
|
|
11544
|
+
);
|
|
11545
|
+
} catch {
|
|
11546
|
+
fs43.rmSync(dir, { recursive: true, force: true });
|
|
11547
|
+
return { started: false, detail: "The assistant\u2019s command-line tool could not be started." };
|
|
11548
|
+
}
|
|
11549
|
+
current = { askId: ask.askId, child };
|
|
11550
|
+
let buffer = "", stderr = "", sawDone = false, ended = false;
|
|
11551
|
+
const handleLine = (line) => {
|
|
11552
|
+
for (const event of eventsFromLine(ask.askId, line)) {
|
|
11553
|
+
if (event.name === "assistant.done") sawDone = true;
|
|
11554
|
+
ask.emit(event.name, event.data);
|
|
11555
|
+
}
|
|
11556
|
+
};
|
|
11557
|
+
const end = (ok, detail) => {
|
|
11558
|
+
if (ended) return;
|
|
11559
|
+
ended = true;
|
|
11560
|
+
clearTimeout(timer);
|
|
11561
|
+
if (buffer.trim()) handleLine(buffer);
|
|
11562
|
+
if (!sawDone) ask.emit("assistant.done", { askId: ask.askId, ok, result: detail });
|
|
11563
|
+
current = null;
|
|
11564
|
+
fs43.rmSync(dir, { recursive: true, force: true });
|
|
11565
|
+
};
|
|
11566
|
+
const timer = setTimeout(() => {
|
|
11567
|
+
child.kill("SIGTERM");
|
|
11568
|
+
end(false, `Stopped after ${Math.max(1, Math.round(maxAskMs / 6e4))} minutes. Whatever it saved so far is on the Branches page \u2014 send \u201Ccontinue\u201D to carry on.`);
|
|
11569
|
+
}, maxAskMs);
|
|
11570
|
+
timer.unref?.();
|
|
11571
|
+
child.stdout?.on("data", (chunk) => {
|
|
11572
|
+
buffer += chunk.toString();
|
|
11573
|
+
let at;
|
|
11574
|
+
while ((at = buffer.indexOf("\n")) >= 0) {
|
|
11575
|
+
handleLine(buffer.slice(0, at));
|
|
11576
|
+
buffer = buffer.slice(at + 1);
|
|
11577
|
+
}
|
|
11578
|
+
});
|
|
11579
|
+
child.stderr?.on("data", (chunk) => {
|
|
11580
|
+
stderr = (stderr + chunk.toString()).slice(-2e3);
|
|
11581
|
+
});
|
|
11582
|
+
child.on("error", () => end(false, "The assistant\u2019s command-line tool could not be started."));
|
|
11583
|
+
child.on(
|
|
11584
|
+
"close",
|
|
11585
|
+
(code) => end(
|
|
11586
|
+
code === 0,
|
|
11587
|
+
cancelled === ask.askId ? "Stopped." : code === 0 ? "" : stderr.trim().split("\n").slice(-3).join(" ") || `The assistant stopped (exit ${code ?? "?"}).`
|
|
11588
|
+
)
|
|
11589
|
+
);
|
|
11590
|
+
return { started: true };
|
|
11591
|
+
},
|
|
11592
|
+
cancel(askId) {
|
|
11593
|
+
if (!current || current.askId !== askId) return false;
|
|
11594
|
+
cancelled = askId;
|
|
11595
|
+
current.child.kill("SIGTERM");
|
|
11596
|
+
const child = current.child;
|
|
11597
|
+
setTimeout(() => child.kill("SIGKILL"), 3e3).unref?.();
|
|
11598
|
+
return true;
|
|
11599
|
+
},
|
|
11600
|
+
busy: () => current !== null
|
|
11601
|
+
};
|
|
11602
|
+
}
|
|
10870
11603
|
function createAgentRuntime(options) {
|
|
10871
11604
|
const lease = new BrowserLease();
|
|
10872
11605
|
let journal = null;
|
|
@@ -10995,6 +11728,7 @@ function createAgentRuntime(options) {
|
|
|
10995
11728
|
const handlers = buildHandlers({
|
|
10996
11729
|
lease,
|
|
10997
11730
|
authenticationReuse: keptSignIns,
|
|
11731
|
+
...options.mcpServer ? { assistant: createClaudeCli({ mcp: options.mcpServer }) } : {},
|
|
10998
11732
|
storeFor: (r) => new Store(r),
|
|
10999
11733
|
...options.storage ? { storage: options.storage } : {},
|
|
11000
11734
|
recorder,
|
|
@@ -11053,24 +11787,24 @@ import fs11 from "fs";
|
|
|
11053
11787
|
import os6 from "os";
|
|
11054
11788
|
function takeLock(env = process.env) {
|
|
11055
11789
|
ensureDataDir(env);
|
|
11056
|
-
const
|
|
11790
|
+
const file2 = lockPath(env);
|
|
11057
11791
|
const mine = { pid: process.pid, since: (/* @__PURE__ */ new Date()).toISOString(), host: os6.hostname() };
|
|
11058
11792
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
11059
11793
|
try {
|
|
11060
|
-
fs11.writeFileSync(
|
|
11061
|
-
return { ok: true, lock: { release: () => releaseLock(
|
|
11794
|
+
fs11.writeFileSync(file2, JSON.stringify(mine), { flag: "wx", mode: 384 });
|
|
11795
|
+
return { ok: true, lock: { release: () => releaseLock(file2, mine.pid) } };
|
|
11062
11796
|
} catch {
|
|
11063
|
-
const held = readLock(
|
|
11797
|
+
const held = readLock(file2);
|
|
11064
11798
|
if (!held) {
|
|
11065
11799
|
try {
|
|
11066
|
-
fs11.rmSync(
|
|
11800
|
+
fs11.rmSync(file2, { force: true });
|
|
11067
11801
|
} catch {
|
|
11068
11802
|
}
|
|
11069
11803
|
continue;
|
|
11070
11804
|
}
|
|
11071
11805
|
if (held.host === mine.host && !alive(held.pid)) {
|
|
11072
11806
|
try {
|
|
11073
|
-
fs11.rmSync(
|
|
11807
|
+
fs11.rmSync(file2, { force: true });
|
|
11074
11808
|
} catch {
|
|
11075
11809
|
}
|
|
11076
11810
|
continue;
|
|
@@ -11078,11 +11812,11 @@ function takeLock(env = process.env) {
|
|
|
11078
11812
|
return { ok: false, held };
|
|
11079
11813
|
}
|
|
11080
11814
|
}
|
|
11081
|
-
return { ok: false, held: readLock(
|
|
11815
|
+
return { ok: false, held: readLock(file2) ?? { pid: -1, since: "", host: "unknown" } };
|
|
11082
11816
|
}
|
|
11083
|
-
function readLock(
|
|
11817
|
+
function readLock(file2) {
|
|
11084
11818
|
try {
|
|
11085
|
-
const held = JSON.parse(fs11.readFileSync(
|
|
11819
|
+
const held = JSON.parse(fs11.readFileSync(file2, "utf8"));
|
|
11086
11820
|
return typeof held.pid === "number" ? { pid: held.pid, since: held.since ?? "", host: held.host ?? "" } : null;
|
|
11087
11821
|
} catch {
|
|
11088
11822
|
return null;
|
|
@@ -11097,17 +11831,26 @@ function alive(pid) {
|
|
|
11097
11831
|
return error.code === "EPERM";
|
|
11098
11832
|
}
|
|
11099
11833
|
}
|
|
11100
|
-
function releaseLock(
|
|
11101
|
-
const held = readLock(
|
|
11834
|
+
function releaseLock(file2, pid) {
|
|
11835
|
+
const held = readLock(file2);
|
|
11102
11836
|
if (held?.pid === pid) {
|
|
11103
11837
|
try {
|
|
11104
|
-
fs11.rmSync(
|
|
11838
|
+
fs11.rmSync(file2, { force: true });
|
|
11105
11839
|
} catch {
|
|
11106
11840
|
}
|
|
11107
11841
|
}
|
|
11108
11842
|
}
|
|
11109
11843
|
|
|
11110
11844
|
// src/engine/session.ts
|
|
11845
|
+
function mcpServer() {
|
|
11846
|
+
const script2 = process.argv[1];
|
|
11847
|
+
if (!script2) return null;
|
|
11848
|
+
try {
|
|
11849
|
+
return { command: process.execPath, args: [fs12.realpathSync(script2), "mcp"] };
|
|
11850
|
+
} catch {
|
|
11851
|
+
return null;
|
|
11852
|
+
}
|
|
11853
|
+
}
|
|
11111
11854
|
async function start(options = {}) {
|
|
11112
11855
|
let service;
|
|
11113
11856
|
try {
|
|
@@ -11162,6 +11905,10 @@ async function start(options = {}) {
|
|
|
11162
11905
|
stateFile: ledgerPath(connection.deviceId),
|
|
11163
11906
|
projects,
|
|
11164
11907
|
storage,
|
|
11908
|
+
// THE SAME PROGRAM, started again as `mcp`, so Claude's tools are the ones
|
|
11909
|
+
// this very install provides — not whatever `flowriki-agent` happens to be
|
|
11910
|
+
// first on somebody's PATH.
|
|
11911
|
+
...mcpServer() ? { mcpServer: mcpServer() } : {},
|
|
11165
11912
|
onEvent: (event) => options.onProgress?.({ kind: event.name, message: "", detail: event.data })
|
|
11166
11913
|
});
|
|
11167
11914
|
const link = new AgentLink({
|
|
@@ -11271,6 +12018,7 @@ export {
|
|
|
11271
12018
|
connect,
|
|
11272
12019
|
RecordedStepSchema,
|
|
11273
12020
|
mirrorCheckTargets,
|
|
12021
|
+
similarity,
|
|
11274
12022
|
runtimeValue,
|
|
11275
12023
|
authenticationResolver,
|
|
11276
12024
|
checkAssertion,
|