@allurereport/plugin-agent 3.11.0 → 3.12.0
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/README.md +36 -1
- package/dist/capabilities.d.ts +31 -3
- package/dist/capabilities.js +97 -4
- package/dist/guidance.d.ts +3 -3
- package/dist/guidance.js +34 -8
- package/dist/harness.d.ts +4 -0
- package/dist/harness.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/invalid-output.js +1 -1
- package/dist/model.d.ts +25 -0
- package/dist/plugin.js +70 -2
- package/dist/query.d.ts +2 -0
- package/dist/query.js +2 -0
- package/dist/state.d.ts +48 -7
- package/dist/state.js +252 -58
- package/dist/utils.d.ts +17 -0
- package/dist/utils.js +171 -0
- package/package.json +4 -4
package/dist/utils.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, open, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
6
|
+
export const ALLURE_AGENT_STATE_DIR_ENV = "ALLURE_AGENT_STATE_DIR";
|
|
7
|
+
export const AGENT_MANAGED_OUTPUT_DIR_PREFIX = "allure-agent-";
|
|
8
|
+
const AGENT_STATE_LOCK_STALE_MS = 10 * 60 * 1000;
|
|
9
|
+
const AGENT_STATE_LOCK_RETRIES = 100;
|
|
10
|
+
const AGENT_STATE_LOCK_RETRY_MS = 20;
|
|
11
|
+
export const isFileNotFoundError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
12
|
+
const isFileExistsError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
13
|
+
const projectHash = (cwd) => createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
14
|
+
export const resolveAgentStateDir = (_cwd) => {
|
|
15
|
+
const configuredDir = process.env[ALLURE_AGENT_STATE_DIR_ENV]?.trim();
|
|
16
|
+
if (configuredDir) {
|
|
17
|
+
return resolve(configuredDir);
|
|
18
|
+
}
|
|
19
|
+
return join(tmpdir(), "allure-agent-state");
|
|
20
|
+
};
|
|
21
|
+
export const projectStatePath = (cwd) => join(resolveAgentStateDir(cwd), `${projectHash(resolve(cwd))}.jsonl`);
|
|
22
|
+
const projectLockPath = (cwd) => join(resolveAgentStateDir(cwd), `${projectHash(resolve(cwd))}.lock`);
|
|
23
|
+
export const listAgentStatePaths = async (cwd) => {
|
|
24
|
+
const stateDir = resolveAgentStateDir(cwd);
|
|
25
|
+
try {
|
|
26
|
+
const entries = await readdir(stateDir, { withFileTypes: true });
|
|
27
|
+
return entries
|
|
28
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl"))
|
|
29
|
+
.map((entry) => join(stateDir, entry.name));
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (isFileNotFoundError(error)) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
export const listAgentManagedTempOutputDirs = async () => {
|
|
39
|
+
const tempRoot = tmpdir();
|
|
40
|
+
try {
|
|
41
|
+
const entries = await readdir(tempRoot, { withFileTypes: true });
|
|
42
|
+
return entries
|
|
43
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith(AGENT_MANAGED_OUTPUT_DIR_PREFIX))
|
|
44
|
+
.map((entry) => join(tempRoot, entry.name));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (isFileNotFoundError(error)) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
export const writeJsonlAtomic = async (filePath, values) => {
|
|
54
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
55
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
56
|
+
const content = values.map((value) => JSON.stringify(value)).join("\n") + (values.length ? "\n" : "");
|
|
57
|
+
await writeFile(tempPath, content, "utf-8");
|
|
58
|
+
await rename(tempPath, filePath);
|
|
59
|
+
};
|
|
60
|
+
const removeLockIfStale = async (lockPath) => {
|
|
61
|
+
try {
|
|
62
|
+
const lockStat = await stat(lockPath);
|
|
63
|
+
if (Date.now() - lockStat.mtimeMs > AGENT_STATE_LOCK_STALE_MS) {
|
|
64
|
+
await rm(lockPath, { force: true });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (isFileNotFoundError(error)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const openAgentStateLock = async (lockPath) => {
|
|
77
|
+
let lockHandle;
|
|
78
|
+
try {
|
|
79
|
+
lockHandle = await open(lockPath, "wx");
|
|
80
|
+
await lockHandle.writeFile(`${process.pid}\n${Date.now()}\n`, "utf-8");
|
|
81
|
+
return lockHandle;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (lockHandle) {
|
|
85
|
+
await lockHandle.close().catch(() => undefined);
|
|
86
|
+
await rm(lockPath, { force: true }).catch(() => undefined);
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const runWithAgentStateLock = async (lockPath, lockHandle, operation) => {
|
|
92
|
+
try {
|
|
93
|
+
return await operation();
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await lockHandle.close().catch(() => undefined);
|
|
97
|
+
await rm(lockPath, { force: true }).catch(() => undefined);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
export const withAgentStateLock = async (cwd, operation) => {
|
|
101
|
+
const normalizedCwd = resolve(cwd);
|
|
102
|
+
const stateDir = resolveAgentStateDir(normalizedCwd);
|
|
103
|
+
const lockPath = projectLockPath(normalizedCwd);
|
|
104
|
+
let lastError;
|
|
105
|
+
await mkdir(stateDir, { recursive: true });
|
|
106
|
+
for (let attempt = 0; attempt < AGENT_STATE_LOCK_RETRIES; attempt += 1) {
|
|
107
|
+
try {
|
|
108
|
+
const lockHandle = await openAgentStateLock(lockPath);
|
|
109
|
+
return await runWithAgentStateLock(lockPath, lockHandle, operation);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (!isFileExistsError(error)) {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
lastError = error;
|
|
116
|
+
await removeLockIfStale(lockPath);
|
|
117
|
+
await sleep(AGENT_STATE_LOCK_RETRY_MS);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
throw lastError instanceof Error ? lastError : new Error(`Could not acquire agent state lock: ${lockPath}`);
|
|
121
|
+
};
|
|
122
|
+
export const tryWithAgentStateLock = async (cwd, operation) => {
|
|
123
|
+
const normalizedCwd = resolve(cwd);
|
|
124
|
+
const stateDir = resolveAgentStateDir(normalizedCwd);
|
|
125
|
+
const lockPath = projectLockPath(normalizedCwd);
|
|
126
|
+
await mkdir(stateDir, { recursive: true });
|
|
127
|
+
try {
|
|
128
|
+
const lockHandle = await openAgentStateLock(lockPath);
|
|
129
|
+
return {
|
|
130
|
+
acquired: true,
|
|
131
|
+
result: await runWithAgentStateLock(lockPath, lockHandle, operation),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (!isFileExistsError(error)) {
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
if (!(await removeLockIfStale(lockPath))) {
|
|
139
|
+
return { acquired: false };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const lockHandle = await openAgentStateLock(lockPath);
|
|
144
|
+
return {
|
|
145
|
+
acquired: true,
|
|
146
|
+
result: await runWithAgentStateLock(lockPath, lockHandle, operation),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (isFileExistsError(error)) {
|
|
151
|
+
return { acquired: false };
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
export const pathExists = async (path) => {
|
|
157
|
+
try {
|
|
158
|
+
await stat(path);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (isFileNotFoundError(error)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
export const readPathMtimeMs = async (path) => {
|
|
169
|
+
const pathStat = await stat(path);
|
|
170
|
+
return pathStat.mtimeMs;
|
|
171
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allurereport/plugin-agent",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
4
4
|
"description": "Allure Agent Plugin – AI-friendly markdown report generator",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"lint:fix": "yarn run -T oxlint --import-plugin --fix src test features stories"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@allurereport/core-api": "3.
|
|
35
|
-
"@allurereport/plugin-api": "3.
|
|
34
|
+
"@allurereport/core-api": "3.12.0",
|
|
35
|
+
"@allurereport/plugin-api": "3.12.0",
|
|
36
36
|
"yaml": "^2.8.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@allurereport/reader-api": "3.
|
|
39
|
+
"@allurereport/reader-api": "3.12.0",
|
|
40
40
|
"@types/node": "^20",
|
|
41
41
|
"@vitest/runner": "^2",
|
|
42
42
|
"allure-js-commons": "^3",
|