flowviant 0.125.25 → 0.125.27
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 +22 -0
- package/bin/lib/artifactIteration.mjs +11 -3
- package/bin/lib/workAgentTurnReports.mjs +11 -1
- package/bin/lib/workAgentTurns.mjs +2 -0
- package/npm-shrinkwrap.json +69 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -328,6 +328,28 @@ pinned because [npm 12 removed shrinkwrap support](https://github.com/npm/cli/bl
|
|
|
328
328
|
and omits it from tarballs; npm 12 consumers also ignore that lockfile.
|
|
329
329
|
No sibling repository or separate app deploy key is required.
|
|
330
330
|
|
|
331
|
+
### Artifact cleanup and retry recovery (0.125.27)
|
|
332
|
+
|
|
333
|
+
Iteration declarations accept the shared agent retention bound for removals
|
|
334
|
+
and replacements and a full inventory of nested file names. Oversized counts
|
|
335
|
+
report their count separately from malformed paths. An acknowledged turn id
|
|
336
|
+
cannot be started again by a stale roster response, leaving the actual retry
|
|
337
|
+
free to run and stream under its own id. Publication checks the actual pinned
|
|
338
|
+
npm 11 publisher before uploading so npm 12 cannot omit the shrinkwrap again.
|
|
339
|
+
|
|
340
|
+
### UI review evidence retention (0.125.26)
|
|
341
|
+
|
|
342
|
+
Review reports and screenshots stay attached to their run without filling the
|
|
343
|
+
same-agent producer receipt. Legacy receipts migrate on retry without deleting
|
|
344
|
+
evidence. Identical captures within one review share a file and retain their
|
|
345
|
+
scenario, viewport, phase and hash references. Producer receipts use the shared
|
|
346
|
+
agent storage limit and report cumulative overflow before upload.
|
|
347
|
+
|
|
348
|
+
The 0.125.25 and 0.125.26 npm packages contain the runtime fix but omit the
|
|
349
|
+
dependency shrinkwrap. The pinned npm 11 dry-run includes that lockfile;
|
|
350
|
+
use the pinned publisher command above for future releases. All four
|
|
351
|
+
standalone 0.125.26 downloads were verified against their SHA-256 manifest.
|
|
352
|
+
|
|
331
353
|
### Agent worktree startup and restart protection (0.125.24)
|
|
332
354
|
|
|
333
355
|
Card launches verify their exact Git worktree and repository before starting,
|
|
@@ -5,8 +5,12 @@ import { join, dirname, resolve, sep } from 'node:path';
|
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { flowviantParentState, realDirState } from './ownedDirs.mjs';
|
|
7
7
|
import { ARTIFACT_DIR, artifactStoredName, walkArtifactDir } from './artifacts.mjs';
|
|
8
|
+
import { ARTIFACT_MAX_PER_AGENT } from './artifactPolicy.mjs';
|
|
8
9
|
|
|
9
10
|
const MANIFEST = '.iteration.json';
|
|
11
|
+
// An agent can retain 400 files. The former 32 KiB read bound silently
|
|
12
|
+
// ignored valid declarations for a full inventory of nested file names.
|
|
13
|
+
const MANIFEST_MAX_BYTES = 1024 * 1024;
|
|
10
14
|
const safeName = (name) => typeof name === 'string' && name.length <= 500 &&
|
|
11
15
|
name.split('/').every((p) => p && p !== '.' && p !== '..' && !p.startsWith('.') && !p.includes('\\'));
|
|
12
16
|
|
|
@@ -16,7 +20,7 @@ const manifestText = (placeDir) => {
|
|
|
16
20
|
try {
|
|
17
21
|
fd = openSync(join(placeDir, ARTIFACT_DIR, MANIFEST), constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
18
22
|
const st = fstatSync(fd);
|
|
19
|
-
if (!st.isFile() || st.size >
|
|
23
|
+
if (!st.isFile() || st.size > MANIFEST_MAX_BYTES) return null;
|
|
20
24
|
return { text: readFileSync(fd, 'utf8'), stamp: `${st.size}:${st.mtimeMs}` };
|
|
21
25
|
} catch { return null; }
|
|
22
26
|
finally { if (fd !== undefined) closeSync(fd); }
|
|
@@ -50,8 +54,12 @@ export function iterationChanges(placeDir, before) {
|
|
|
50
54
|
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Artifact iteration declaration must be an object.');
|
|
51
55
|
const remove = value.remove ?? [];
|
|
52
56
|
const replace = value.replace ?? {};
|
|
53
|
-
if (
|
|
54
|
-
|
|
57
|
+
if (Array.isArray(remove) && remove.length > ARTIFACT_MAX_PER_AGENT)
|
|
58
|
+
throw new Error(`Artifact iteration has ${remove.length} removal names; the declaration supports at most ${ARTIFACT_MAX_PER_AGENT}. Narrow the removal set before retrying.`);
|
|
59
|
+
if (replace && typeof replace === 'object' && !Array.isArray(replace) && Object.keys(replace).length > ARTIFACT_MAX_PER_AGENT)
|
|
60
|
+
throw new Error(`Artifact iteration has ${Object.keys(replace).length} replacement names; the declaration supports at most ${ARTIFACT_MAX_PER_AGENT}. Narrow the replacement set before retrying.`);
|
|
61
|
+
if (!Array.isArray(remove) || !remove.every(safeName) ||
|
|
62
|
+
!replace || typeof replace !== 'object' || Array.isArray(replace) ||
|
|
55
63
|
!Object.entries(replace).every(([from, to]) => safeName(from) && safeName(to) && from !== to)) {
|
|
56
64
|
throw new Error('Artifact iteration needs remove names and explicit old-to-new replacement names.');
|
|
57
65
|
}
|
|
@@ -55,6 +55,11 @@ export function createAgentTurnReports({ REJECT_RETRY_MS, baseDir = null }) {
|
|
|
55
55
|
const persistRecovery = () => journal?.save(new Map(agentReported), new Map()); // turnId -> { body, at }
|
|
56
56
|
const agentRejectedUntil = new Map(); // turnId -> earliest re-POST of a refused body
|
|
57
57
|
const AGENT_REPORT_GRACE_MS = 30 * 60_000;
|
|
58
|
+
// A roster read can race the settle's acknowledgement. Forgetting the
|
|
59
|
+
// completed id with its outbox body let that stale offer run a second CLI
|
|
60
|
+
// under an already answered turn, hiding its output from the real retry.
|
|
61
|
+
// These ids are not pending reports and must not keep the machine busy.
|
|
62
|
+
const settledAt = new Map();
|
|
58
63
|
|
|
59
64
|
/** Returns the server's reply, because it carries ONE instruction the machine
|
|
60
65
|
* can act on immediately: `review: true` means the agent's queue just
|
|
@@ -81,6 +86,7 @@ export function createAgentTurnReports({ REJECT_RETRY_MS, baseDir = null }) {
|
|
|
81
86
|
* re-POST backs off instead of hammering a body the server just refused.
|
|
82
87
|
*/
|
|
83
88
|
if (res.ok && (body.mergeResolved === undefined || typeof j?.data?.settled === 'boolean')) {
|
|
89
|
+
settledAt.set(turnId, Date.now());
|
|
84
90
|
agentReported.delete(turnId);
|
|
85
91
|
agentRejectedUntil.delete(turnId);
|
|
86
92
|
persistRecovery();
|
|
@@ -165,9 +171,12 @@ export function createAgentTurnReports({ REJECT_RETRY_MS, baseDir = null }) {
|
|
|
165
171
|
* the grace only covers its POST racing a final offer.
|
|
166
172
|
*/
|
|
167
173
|
const pruneHeldReports = (list) => {
|
|
168
|
-
if (!agentReported.size) return;
|
|
169
174
|
const offered = new Set(list.map((j) => String(j?.id || '')));
|
|
170
175
|
const now = Date.now();
|
|
176
|
+
for (const [id, at] of settledAt) {
|
|
177
|
+
if (offered.has(id)) settledAt.set(id, now);
|
|
178
|
+
else if (now - at > AGENT_REPORT_GRACE_MS) settledAt.delete(id);
|
|
179
|
+
}
|
|
171
180
|
for (const [id, held] of agentReported) {
|
|
172
181
|
if (offered.has(id)) held.at = now;
|
|
173
182
|
else if (now - held.at > AGENT_REPORT_GRACE_MS) {
|
|
@@ -187,5 +196,6 @@ export function createAgentTurnReports({ REJECT_RETRY_MS, baseDir = null }) {
|
|
|
187
196
|
pruneHeldReports,
|
|
188
197
|
agentReported,
|
|
189
198
|
agentRejectedUntil,
|
|
199
|
+
hasSettledTurn: id => settledAt.has(id),
|
|
190
200
|
};
|
|
191
201
|
}
|
|
@@ -73,6 +73,7 @@ export function createWorkAgentTurns({
|
|
|
73
73
|
pruneHeldReports,
|
|
74
74
|
agentReported,
|
|
75
75
|
agentRejectedUntil,
|
|
76
|
+
hasSettledTurn,
|
|
76
77
|
} = createAgentTurnReports({ REJECT_RETRY_MS, baseDir });
|
|
77
78
|
|
|
78
79
|
// ── AGENT TURNS: one task per prompt ───────────────────────────────────────
|
|
@@ -300,6 +301,7 @@ export function createWorkAgentTurns({
|
|
|
300
301
|
for (const job of list.slice(0, 4)) {
|
|
301
302
|
const id = String(job?.id || '');
|
|
302
303
|
if (!id) continue;
|
|
304
|
+
if (hasSettledTurn(id)) continue;
|
|
303
305
|
if (agentTurns.has(id)) {
|
|
304
306
|
const reason = agentRuns.get(id)?.waitingReason;
|
|
305
307
|
if (reason) reportDelivery('agent', job, 'queued', reason);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flowviant",
|
|
3
|
+
"version": "0.125.27",
|
|
4
|
+
"lockfileVersion": 3,
|
|
5
|
+
"requires": true,
|
|
6
|
+
"packages": {
|
|
7
|
+
"": {
|
|
8
|
+
"name": "flowviant",
|
|
9
|
+
"version": "0.125.27",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"libsodium-wrappers-sumo": "^0.8.4",
|
|
13
|
+
"ws": "^8.18.0"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"flowviant": "bin/cli.mjs"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"three": "0.180.0"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"node_modules/libsodium-sumo": {
|
|
26
|
+
"version": "0.8.4",
|
|
27
|
+
"resolved": "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.8.4.tgz",
|
|
28
|
+
"integrity": "sha512-TMtHShQfVVsaxDygyapvUC3o7YsPgXa/hRWeIgzyFz6w5k/1hirGptCxp1U7XwW3rCskaTTYKgV10v86UiGgNw==",
|
|
29
|
+
"license": "ISC"
|
|
30
|
+
},
|
|
31
|
+
"node_modules/libsodium-wrappers-sumo": {
|
|
32
|
+
"version": "0.8.4",
|
|
33
|
+
"resolved": "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.8.4.tgz",
|
|
34
|
+
"integrity": "sha512-ql7hcgulKZ3ekfa2DGAogcCKsWU0diA/0nArz1CFzh93WQdb46/Kj18ka/Hifq6uA3Ush34Pc6vU/6HXeRwUkg==",
|
|
35
|
+
"license": "ISC",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"libsodium-sumo": "^0.8.0"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"node_modules/three": {
|
|
41
|
+
"version": "0.180.0",
|
|
42
|
+
"resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz",
|
|
43
|
+
"integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==",
|
|
44
|
+
"dev": true,
|
|
45
|
+
"license": "MIT"
|
|
46
|
+
},
|
|
47
|
+
"node_modules/ws": {
|
|
48
|
+
"version": "8.21.1",
|
|
49
|
+
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
|
|
50
|
+
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=10.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"bufferutil": "^4.0.1",
|
|
57
|
+
"utf-8-validate": ">=5.0.2"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"bufferutil": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"utf-8-validate": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.125.
|
|
3
|
+
"version": "0.125.27",
|
|
4
4
|
"description": "Run your own coding CLIs as build agents for Flowviant \u2014 Claude Code, Codex or Antigravity, on your own credentials. Holds your sessions, keeps a worktree per tab, and ships branches on your word.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"test": "node --test bin/lib/*.test.mjs",
|
|
21
|
-
"prepublishOnly": "bun scripts/check-app-parity.mjs"
|
|
21
|
+
"prepublishOnly": "node scripts/check-publisher.mjs && bun scripts/check-app-parity.mjs"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"libsodium-wrappers-sumo": "^0.8.4",
|