flowviant 0.125.26 → 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,15 @@ 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
+
331
340
  ### UI review evidence retention (0.125.26)
332
341
 
333
342
  Review reports and screenshots stay attached to their run without filling the
@@ -336,9 +345,10 @@ evidence. Identical captures within one review share a file and retain their
336
345
  scenario, viewport, phase and hash references. Producer receipts use the shared
337
346
  agent storage limit and report cumulative overflow before upload.
338
347
 
339
- 0.125.26 includes the npm dependency shrinkwrap with the pinned npm 11
340
- publisher. The earlier 0.125.25 npm package contains the same runtime fix but
341
- was published by npm 12 without that lockfile.
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.
342
352
 
343
353
  ### Agent worktree startup and restart protection (0.125.24)
344
354
 
@@ -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.26",
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",