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 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 > 32 * 1024) return null;
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 (!Array.isArray(remove) || remove.length > 40 || !remove.every(safeName) ||
54
- !replace || typeof replace !== 'object' || Array.isArray(replace) || Object.keys(replace).length > 40 ||
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.25",
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",