flowviant 0.125.17 → 0.125.18
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
|
@@ -318,6 +318,15 @@ pinned because [npm 12 removed shrinkwrap support](https://github.com/npm/cli/bl
|
|
|
318
318
|
and omits it from tarballs; npm 12 consumers also ignore that lockfile.
|
|
319
319
|
No sibling repository or separate app deploy key is required.
|
|
320
320
|
|
|
321
|
+
### Staged artifact handoff revisions (0.125.18)
|
|
322
|
+
|
|
323
|
+
A failed handoff's retry drops every pending source the agent's
|
|
324
|
+
`.flowviant/artifacts/.iteration.json` discards or replaces, instead of
|
|
325
|
+
demanding the discarded file at its old bytes on every resume. A producer's
|
|
326
|
+
required set is checked against the server's per-agent storage of 400 files
|
|
327
|
+
(`ARTIFACT_MAX_PER_AGENT`) rather than a Terminal tab's 40. Both are daemon-side
|
|
328
|
+
only; the API needs no floor. Published-latest remains unchanged until release.
|
|
329
|
+
|
|
321
330
|
### Staged agent queue context (0.125.17)
|
|
322
331
|
|
|
323
332
|
The daemon now includes the API's versioned `queueContext` in task, human and
|
|
@@ -23,6 +23,24 @@ const manifestText = (placeDir) => {
|
|
|
23
23
|
const signature = (value) => value === null ? null : `${value.stamp}:${createHash('sha256').update(value.text).digest('hex')}`;
|
|
24
24
|
export const iterationSignature = (placeDir) => signature(manifestText(placeDir));
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Every name the standing declaration discards or replaces, whenever it was
|
|
28
|
+
* written. A failed handoff's retry reads this (2026-10-23): the agent revised
|
|
29
|
+
* its outputs in a turn that did not deliver, and the retry kept demanding the
|
|
30
|
+
* discarded files at their old bytes on every resume. Unreadable or malformed
|
|
31
|
+
* is no discard; the reporter refuses a malformed declaration on its own.
|
|
32
|
+
*/
|
|
33
|
+
export function declaredDiscards(placeDir) {
|
|
34
|
+
const text = manifestText(placeDir);
|
|
35
|
+
if (text === null) return new Set();
|
|
36
|
+
try {
|
|
37
|
+
const value = JSON.parse(text.text);
|
|
38
|
+
const remove = Array.isArray(value?.remove) ? value.remove : [];
|
|
39
|
+
const replace = value?.replace && typeof value.replace === 'object' && !Array.isArray(value.replace) ? Object.keys(value.replace) : [];
|
|
40
|
+
return new Set([...remove, ...replace].filter(safeName));
|
|
41
|
+
} catch { return new Set(); }
|
|
42
|
+
}
|
|
43
|
+
|
|
26
44
|
export function iterationChanges(placeDir, before) {
|
|
27
45
|
const text = manifestText(placeDir);
|
|
28
46
|
if (text === null || signature(text) === before?.iterationSignature) return { remove: [], replace: {} };
|
|
@@ -16,8 +16,10 @@ export const ARTIFACT_MAX_BYTES = 2097152;
|
|
|
16
16
|
/** Binary model buffers (the extensions below). */
|
|
17
17
|
export const ARTIFACT_BINARY_MODEL_MAX_BYTES = 20971520;
|
|
18
18
|
export const ARTIFACT_BINARY_MODEL_EXTS = ['glb', 'bin', 'png', 'webp'];
|
|
19
|
-
/** Per session and
|
|
19
|
+
/** Per Terminal session, and the per-turn discovery window. */
|
|
20
20
|
export const ARTIFACT_MAX_PER_OWNER = 40;
|
|
21
|
+
/** Per agent — the server keeps as many for one agent. */
|
|
22
|
+
export const ARTIFACT_MAX_PER_AGENT = 400;
|
|
21
23
|
|
|
22
24
|
/** Extension → the one mime the server serves it under; `text` is scrubbed. */
|
|
23
25
|
export const ARTIFACT_TYPES = {
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
import { createHash, randomUUID } from 'node:crypto';
|
|
5
5
|
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, readSync, renameSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import { basename, dirname, join } from 'node:path';
|
|
7
|
-
import { ARTIFACT_MAX_DEPTH, artifactTypeFor, artifactMaxBytesFor, changedArtifacts, scanArtifacts } from './artifacts.mjs';
|
|
8
|
-
import { ARTIFACT_BINARY_MODEL_MAX_BYTES,
|
|
7
|
+
import { ARTIFACT_MAX_DEPTH, artifactStoredName, artifactTypeFor, artifactMaxBytesFor, changedArtifacts, scanArtifacts } from './artifacts.mjs';
|
|
8
|
+
import { ARTIFACT_BINARY_MODEL_MAX_BYTES, ARTIFACT_MAX_PER_AGENT } from './artifactPolicy.mjs';
|
|
9
|
+
import { declaredDiscards } from './artifactIteration.mjs';
|
|
9
10
|
import { ensureRealSubdir, realDirState } from './ownedDirs.mjs';
|
|
10
11
|
import { scrub, secretIn } from './uplinkScrub.mjs';
|
|
11
12
|
import { exportModelPage } from './modelExport.mjs';
|
|
@@ -147,9 +148,12 @@ export function createIntermediateArtifacts({ wt, agentId, path, exportPage = ex
|
|
|
147
148
|
const changed = changedArtifacts(before, standing);
|
|
148
149
|
// Failed export/storage retries are tied to a card and exact source hash.
|
|
149
150
|
// A changed page is a new version; it never reuses a previous GLB receipt.
|
|
151
|
+
// A source the agent's iteration declaration discards or replaces is the
|
|
152
|
+
// explicit revision and leaves the retry set.
|
|
150
153
|
const pending = state.pending?.taskId === taskId && taskId ? state.pending : null;
|
|
154
|
+
const discarded = pending ? declaredDiscards(wt) : new Set();
|
|
151
155
|
if (pending) for (const ref of pending.sources) {
|
|
152
|
-
if (changed.some(e => e.name === ref.name)) continue;
|
|
156
|
+
if (changed.some(e => e.name === ref.name) || discarded.has(artifactStoredName(ref.name))) continue;
|
|
153
157
|
const entry = standing.find(e => e.name === ref.name);
|
|
154
158
|
let bytes;
|
|
155
159
|
try { bytes = readIntermediateFile(wt, `.flowviant/artifacts/${ref.name}`, artifactMaxBytesFor(ref.name)); }
|
|
@@ -192,11 +196,12 @@ export function createIntermediateArtifacts({ wt, agentId, path, exportPage = ex
|
|
|
192
196
|
state.pending.sources = outputs.map(e => ({ name: e.name,
|
|
193
197
|
sha256: hash(readIntermediateFile(wt, `.flowviant/artifacts/${e.name}`, artifactMaxBytesFor(e.name))) }));
|
|
194
198
|
save(state);
|
|
195
|
-
// The server retains
|
|
196
|
-
// storage limit, but uploading a larger required set
|
|
197
|
-
// this very handoff's files and then falsely claim
|
|
198
|
-
|
|
199
|
-
|
|
199
|
+
// The server retains ARTIFACT_MAX_PER_AGENT artifacts per agent. Discovery
|
|
200
|
+
// crowding is not a storage limit, but uploading a larger required set
|
|
201
|
+
// would prune one of this very handoff's files and then falsely claim
|
|
202
|
+
// complete delivery.
|
|
203
|
+
if (outputs.length > ARTIFACT_MAX_PER_AGENT)
|
|
204
|
+
throw new Error(`This producer requires ${outputs.length} output files including model exports; artifact storage retains at most ${ARTIFACT_MAX_PER_AGENT} files per agent. Narrow this producer's output set before retrying; its handoff is unfinished.`);
|
|
200
205
|
const textures = [...state.files.flatMap(f => [...(/\.png$/i.test(f.name) ? [f] : []), ...(f.model?.textureInputs ?? [])]),
|
|
201
206
|
...outputs.filter(e => /\.png$/i.test(e.name)).map(e => snapshot(wt, e, identity, retain))]
|
|
202
207
|
.map(({ name, path, sha256, taskId, turnId, agentId, taskKind }) => ({ name, path, sha256, taskId, turnId, agentId, taskKind }));
|
|
@@ -22,6 +22,19 @@ export const orchestratesPosture = (posture) => Object.hasOwn(ORCHESTRATED_POSTU
|
|
|
22
22
|
*/
|
|
23
23
|
const SPLIT = `SPLIT INDEPENDENT WORK INTO NATIVE SUBAGENTS WITHOUT BEING ASKED. Before your first edit, list the card's parts in a short update. When two or more parts can proceed without waiting on each other, spawn one native in-process subagent for each independent part but one, and do that one yourself meanwhile. This holds however small the parts are; the person does not have to request it, and this contract is the authorization. Independent parts look like: several selected findings, separate files or areas (server and daemon, API and UI), tests written beside the code they cover, independent questions to investigate. A part that needs another part's result runs after that prerequisite, by you or by a child you start then. Stay solo only when the card has no independent parts, and if you keep an independent part yourself, say why in your update. Give each child a concrete title, prompt and kind (Backend/UI/Tests/Research…). Do not create extra branches or Flowviant agents. Use a native agent type that can call card_orchestrator MCP (Claude general-purpose); keep the work yourself if none can. Never give a child isolation or a different cwd: children share this card's worktree on this machine. After spawning, use describe_agent to report the kind and title you chose when its native role is generic.`;
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* WRITE THE SHARED CODE FIRST (2026-10-22). Parts that depend on each other
|
|
27
|
+
* only through code they all use — a schema, a wire field, a signature — fell
|
|
28
|
+
* under "runs after that prerequisite" and ran in sequence, or a child started
|
|
29
|
+
* from a prose description of code a sibling had not written yet. Once the
|
|
30
|
+
* orchestrator commits that shared code, the parts no longer wait on each
|
|
31
|
+
* other: Next-Concept Modeling's coarse-to-fine move, where each finer level is
|
|
32
|
+
* filled in parallel from the coarser levels already decoded, never from a
|
|
33
|
+
* plan of them. A child asks rather than edits it, because its siblings build
|
|
34
|
+
* on it. Code cards only: a research child writes nothing.
|
|
35
|
+
*/
|
|
36
|
+
const SHARED_FIRST = `WRITE THE CODE THE PARTS SHARE BEFORE YOU SPLIT THEM. Parts often depend on each other only through code they all use: a type or schema, a migration, a wire field, a function or route signature, a component's props. When you list the card's parts, name that shared code too. Write it yourself first — the declarations, with stub bodies where an implementation belongs to a part — and commit it as its own commit before you spawn any child. The parts that waited only on it are now independent: split them as above. Name the shared files in each child's prompt so it builds on the committed code, not on a description of it, and tell it not to change them: a child that finds the shared code wrong or missing asks you with agent_question, and you change it yourself and tell every child building on it. Keep this step to what two or more parts actually use; a part's own internals stay with that part. When the parts share no code, skip it.`;
|
|
37
|
+
|
|
25
38
|
const RESEARCH = `THIS IS A RESEARCH CARD, SO EVERY CHILD ONLY READS. A child works inside this card's research fence: the same read fence, web policy and working directory as you. It writes no file, edits nothing and commits nothing, and the gate refuses a child's write. Give each child one question or one set of sources, and ask it to return its findings with their sources (links or repository paths) and what it could not verify. You alone deliver the card: merge the children's findings, resolve or state their conflicts, write any requested document yourself, and keep the research contract's own stops, including an investigation's numbered-findings question and its wait for the person's selection.`;
|
|
26
39
|
|
|
27
40
|
/** Each CLI's own spelling of a child's model and effort, as measured
|
|
@@ -118,7 +131,7 @@ export function orchestratorContract({ posture = 'build', taskType = null, runti
|
|
|
118
131
|
return `CARD ORCHESTRATOR — this applies to this card's one CLI and worktree.
|
|
119
132
|
You are the model the person chose and remain responsible for the card and its final JSON.
|
|
120
133
|
When an agent queue snapshot is supplied, use it across resumed and fresh turns. Execute only the active card. A person's instruction keeps its stated scope across later cards; its recorded card is provenance, not an implicit target. Give each child a focused assignment with relevant prior findings, decisions and exact prerequisite references. Upcoming work never expands a child's assignment or the active card's product/write contract.
|
|
121
|
-
${SPLIT}${taskType === 'tests' ? `\n${TESTS_SCOPE} Give every child this Tests scope explicitly.` : ''}${reads ? `\n${RESEARCH}` : ''}
|
|
134
|
+
${SPLIT}${reads ? '' : `\n${SHARED_FIRST}`}${taskType === 'tests' ? `\n${TESTS_SCOPE} Give every child this Tests scope explicitly.` : ''}${reads ? `\n${RESEARCH}` : ''}
|
|
122
135
|
${childBrain({ runtime, model, effort })}
|
|
123
136
|
|
|
124
137
|
The daemon gates every native spawn against this project's Turns at once pool, shared with its other turns. Claude children run in the background so you remain available for messages and their questions; Codex spawns are already asynchronous. A refused spawn with a waiting id has not started. Keep working yourself; do not wait for it. When notified of a free slot, retry its native spawn (the same or a reworded prompt) or its native follow-up; the slot is held for about a minute, then offered again later. Never bypass this gate with another CLI process. The orchestrator itself also holds a slot. If no slot becomes available, finish the work yourself.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.125.
|
|
3
|
+
"version": "0.125.18",
|
|
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": {
|