flowviant 0.92.1 → 0.94.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/bin/lib/agentPlan.mjs +53 -1
- package/bin/lib/artifacts.mjs +298 -0
- package/bin/lib/claude.mjs +9 -1
- package/bin/lib/fleet.mjs +34 -0
- package/bin/lib/knowledge.mjs +531 -0
- package/bin/lib/prompts.mjs +98 -1
- package/bin/lib/runtimes.mjs +6 -1
- package/bin/lib/work.mjs +136 -4
- package/package.json +1 -1
package/bin/lib/work.mjs
CHANGED
|
@@ -81,7 +81,10 @@ import {
|
|
|
81
81
|
AGENT_TASK_SPEC,
|
|
82
82
|
AGENT_HUMAN_KICKOFF,
|
|
83
83
|
AGENT_PRECHECK_KICKOFF,
|
|
84
|
+
withProjectContext,
|
|
84
85
|
} from './prompts.mjs';
|
|
86
|
+
import { knowledgeDirFor, FLOWVIANT_OWN_PATHS } from './knowledge.mjs';
|
|
87
|
+
import { createArtifactReporter, snapshotArtifacts } from './artifacts.mjs';
|
|
85
88
|
import { myPubB64, scrub as envScrub } from './env.mjs';
|
|
86
89
|
import {
|
|
87
90
|
detectRuntimes,
|
|
@@ -194,6 +197,15 @@ export function createWorkManager({
|
|
|
194
197
|
* a hole in it.
|
|
195
198
|
*/
|
|
196
199
|
extraLiveTurns = () => 0,
|
|
200
|
+
/**
|
|
201
|
+
* DOES THE SERVER TAKE ARTIFACTS (2026-09-22)? The roster's
|
|
202
|
+
* `artifactsAccepted`, read at SPAWN like the knowledge directory is, so the
|
|
203
|
+
* first turn after a server deploy already carries the paragraph. A getter
|
|
204
|
+
* for `getBaseRef`'s reason: the answer is the latest poll's, not startup's.
|
|
205
|
+
* Absent (false) is what an older server says, and then no turn is told
|
|
206
|
+
* about a panel that server cannot draw.
|
|
207
|
+
*/
|
|
208
|
+
getArtifactsAccepted = () => false,
|
|
197
209
|
}) {
|
|
198
210
|
/**
|
|
199
211
|
* WHERE SHIP LANDS, read fresh every time rather than captured at startup.
|
|
@@ -235,6 +247,37 @@ export function createWorkManager({
|
|
|
235
247
|
// that can move origin/<base>: the sweep's fetch, a ship's push, a PR merge
|
|
236
248
|
// this daemon performed. See landed.mjs for the seeding and delivery rules.
|
|
237
249
|
const landed = createLandedObserver({ repoRoot, baseRef });
|
|
250
|
+
/**
|
|
251
|
+
* THE ARTIFACT RELAY (2026-09-22, 0.94.0) — what a turn wrote under
|
|
252
|
+
* `.flowviant/artifacts/`, uploaded after it. One reporter for the life of
|
|
253
|
+
* the manager so its held bodies survive from one beat to the next; see
|
|
254
|
+
* artifacts.mjs for the snapshot rule, the bounds and the delivery shape.
|
|
255
|
+
*/
|
|
256
|
+
const artifacts = createArtifactReporter({
|
|
257
|
+
fleetUrl: FLEET_URL,
|
|
258
|
+
token: FLEET_TOKEN,
|
|
259
|
+
userAgent: USER_AGENT,
|
|
260
|
+
scrub: envScrub,
|
|
261
|
+
log: (line) => warn(line),
|
|
262
|
+
});
|
|
263
|
+
/**
|
|
264
|
+
* Snapshot a place's artifact directory before a CLI spawns in it — and make
|
|
265
|
+
* sure git cannot see what this daemon writes under `.flowviant/` there
|
|
266
|
+
* first (`FLOWVIANT_OWN_PATHS` — never the whole directory, whose
|
|
267
|
+
* `check.json` and `deploy.json` are the repo's own). The exclude was only ever
|
|
268
|
+
* written by an attachment fetch or a knowledge sync, so a turn that wrote
|
|
269
|
+
* an artifact in a worktree neither had touched left an untracked
|
|
270
|
+
* `.flowviant/` for the agent's own `git add -A` to commit. Idempotent, and
|
|
271
|
+
* one call covers every worktree (it resolves `--git-common-dir`).
|
|
272
|
+
*/
|
|
273
|
+
const beforeArtifacts = (placeDir) => {
|
|
274
|
+
try {
|
|
275
|
+
excludeInWorktree(placeDir, FLOWVIANT_OWN_PATHS);
|
|
276
|
+
} catch {
|
|
277
|
+
/* a convenience, never a guarantee — the prompt also says never commit */
|
|
278
|
+
}
|
|
279
|
+
return snapshotArtifacts(placeDir);
|
|
280
|
+
};
|
|
238
281
|
const workAnswering = new Set(); // turn ids currently queued/running here
|
|
239
282
|
const workAttempts = new Map(); // turn id -> completed runTurn attempts
|
|
240
283
|
const MAX_WORK_TRIES = 3;
|
|
@@ -2074,8 +2117,11 @@ export function createWorkManager({
|
|
|
2074
2117
|
// mechanism the materialized env files used until the vault was deleted —
|
|
2075
2118
|
// the exclude file git actually reads (git.mjs, where the helper moved when
|
|
2076
2119
|
// env.mjs shrank), which already skips lines it has written before, so
|
|
2077
|
-
// calling it per fetch is idempotent.
|
|
2078
|
-
|
|
2120
|
+
// calling it per fetch is idempotent. NARROWED 2026-09-23 to the paths
|
|
2121
|
+
// this daemon writes (`FLOWVIANT_OWN_PATHS`, knowledge.mjs says why): the
|
|
2122
|
+
// whole-directory line also hid a repo's own new `.flowviant/check.json`
|
|
2123
|
+
// from its agent's `git add -A`.
|
|
2124
|
+
excludeInWorktree(wt, FLOWVIANT_OWN_PATHS);
|
|
2079
2125
|
const written = [];
|
|
2080
2126
|
for (const a of attachments.slice(0, 8)) {
|
|
2081
2127
|
if (!a?.id || typeof a.id !== 'string' || !/^[0-9a-f-]{8,64}$/i.test(a.id)) continue;
|
|
@@ -2109,6 +2155,9 @@ export function createWorkManager({
|
|
|
2109
2155
|
|
|
2110
2156
|
let flushingReports = false;
|
|
2111
2157
|
const flushWorkReports = async () => {
|
|
2158
|
+
// Held artifact uploads ride this beat too — the settle retry's own shape —
|
|
2159
|
+
// and are never awaited: a readout's retry must not hold a settle's.
|
|
2160
|
+
void artifacts.retryPending().catch(() => {});
|
|
2112
2161
|
if (flushingReports) return;
|
|
2113
2162
|
if (pendingWorkReports.size === 0 && pendingShipReports.size === 0) return;
|
|
2114
2163
|
flushingReports = true;
|
|
@@ -2887,6 +2936,10 @@ export function createWorkManager({
|
|
|
2887
2936
|
sessionPlaces.set(job.sessionId, place);
|
|
2888
2937
|
// A READER: other turns in this place run alongside it. See `inPlace`.
|
|
2889
2938
|
inPlace(place, false, async () => {
|
|
2939
|
+
/** This turn's artifact snapshot — set once the place is resolved and
|
|
2940
|
+
* a CLI is about to spawn there; read in the `finally`. Null on every
|
|
2941
|
+
* path that never ran a CLI, which is exactly when nothing is new. */
|
|
2942
|
+
let artifactScan = null;
|
|
2890
2943
|
try {
|
|
2891
2944
|
const tries = workAttempts.get(job.id) ?? 0;
|
|
2892
2945
|
if (tries >= MAX_WORK_TRIES) {
|
|
@@ -3028,6 +3081,10 @@ export function createWorkManager({
|
|
|
3028
3081
|
// which is what every tab ran on until now) — honored by
|
|
3029
3082
|
// sessionRuntime: on a first turn a named runtime IS the pick, and a
|
|
3030
3083
|
// named runtime that disagrees with the pin settles below.
|
|
3084
|
+
// What the artifact directory held BEFORE this turn — the capture chat
|
|
3085
|
+
// excepted: it runs read-only, is never told about artifacts, and
|
|
3086
|
+
// whatever sibling tabs wrote in this shared place is theirs.
|
|
3087
|
+
if (job.capture !== true) artifactScan = { dir: dir.wt, before: beforeArtifacts(dir.wt) };
|
|
3031
3088
|
const rt = sessionRuntime(dir.wt, job.runtime || null, job.sessionId);
|
|
3032
3089
|
if (rt.mismatch) {
|
|
3033
3090
|
// Something upstream changed this tab's identity mid-life. A held
|
|
@@ -3396,7 +3453,25 @@ export function createWorkManager({
|
|
|
3396
3453
|
// speaks once, the fork lives natively here and turn 2+ is the
|
|
3397
3454
|
// ordinary --continue resume path, unchanged.
|
|
3398
3455
|
...(adopting ? { adoptResumeId: job.adopt.id } : {}),
|
|
3399
|
-
|
|
3456
|
+
// THE PROJECT'S KNOWLEDGE LIBRARY (0.94.0) rides as one appended
|
|
3457
|
+
// paragraph, and only while this box holds one — see
|
|
3458
|
+
// `withProjectContext`. Resolved at spawn, not at startup, so a
|
|
3459
|
+
// library that synced a second ago is in THIS turn.
|
|
3460
|
+
...(() => {
|
|
3461
|
+
const knowledgeDir = knowledgeDirFor(repoRoot);
|
|
3462
|
+
return {
|
|
3463
|
+
system: withProjectContext(
|
|
3464
|
+
plainTab ? SYSTEM_WORK_PLAIN : captureTab ? SYSTEM_CAPTURE : SYSTEM_WORK,
|
|
3465
|
+
// ARTIFACTS (0.94.0): every tab but the read-only capture
|
|
3466
|
+
// chat, and only while the server can show one.
|
|
3467
|
+
{ knowledgeDir, artifacts: !captureTab && getArtifactsAccepted() }
|
|
3468
|
+
),
|
|
3469
|
+
// The directory is OUTSIDE a worktree's cwd; Claude Code is
|
|
3470
|
+
// told it may read there (`--add-dir`) rather than left to
|
|
3471
|
+
// refuse a read-only capture turn a path it was just handed.
|
|
3472
|
+
knowledgeDir,
|
|
3473
|
+
};
|
|
3474
|
+
})(),
|
|
3400
3475
|
// Present only when the tab named one — see brainFor.
|
|
3401
3476
|
...brain,
|
|
3402
3477
|
// The tab watches the CLI work. Claude needs the flag to speak
|
|
@@ -3633,6 +3708,19 @@ export function createWorkManager({
|
|
|
3633
3708
|
// would delay the next turn of that tab behind a readout.
|
|
3634
3709
|
void reportPlaceWorktrees(job.sessionId).catch(() => {});
|
|
3635
3710
|
burstListeners(job.sessionId);
|
|
3711
|
+
// …and relay what it wrote to SHOW its owner (2026-09-22) — the same
|
|
3712
|
+
// beat, the same reason it is not awaited. A failed turn's half-drawn
|
|
3713
|
+
// page is still what the turn left, and the tab should see it.
|
|
3714
|
+
if (artifactScan) {
|
|
3715
|
+
void artifacts
|
|
3716
|
+
.report({
|
|
3717
|
+
placeDir: artifactScan.dir,
|
|
3718
|
+
before: artifactScan.before,
|
|
3719
|
+
sessionId: job.sessionId,
|
|
3720
|
+
turnId: job.id,
|
|
3721
|
+
})
|
|
3722
|
+
.catch(() => {});
|
|
3723
|
+
}
|
|
3636
3724
|
}
|
|
3637
3725
|
});
|
|
3638
3726
|
}
|
|
@@ -4891,6 +4979,9 @@ export function createWorkManager({
|
|
|
4891
4979
|
* what makes `...(usage ? … : {})` the whole guard.
|
|
4892
4980
|
*/
|
|
4893
4981
|
let usage = null;
|
|
4982
|
+
/** The agent's artifact directory as it stood before this turn — the
|
|
4983
|
+
* tab lane's rule, in the agent's own worktree (2026-09-22). */
|
|
4984
|
+
const artifactsBefore = beforeArtifacts(wt);
|
|
4894
4985
|
try {
|
|
4895
4986
|
out = await runTurn({
|
|
4896
4987
|
prompt:
|
|
@@ -4909,7 +5000,16 @@ export function createWorkManager({
|
|
|
4909
5000
|
position: job.position ?? 1,
|
|
4910
5001
|
total: job.total ?? 1,
|
|
4911
5002
|
}),
|
|
4912
|
-
|
|
5003
|
+
// The knowledge paragraph, when this box holds a library — the same
|
|
5004
|
+
// composer the tabs use, so an agent and a tab can never be told two
|
|
5005
|
+
// different things about the same directory.
|
|
5006
|
+
system: withProjectContext(SYSTEM_AGENT, {
|
|
5007
|
+
knowledgeDir: knowledgeDirFor(repoRoot),
|
|
5008
|
+
// ARTIFACTS (0.94.0), while the server can show one — an agent's
|
|
5009
|
+
// land on its page, under the facts row.
|
|
5010
|
+
artifacts: getArtifactsAccepted(),
|
|
5011
|
+
}),
|
|
5012
|
+
knowledgeDir: knowledgeDirFor(repoRoot),
|
|
4913
5013
|
cwd: wt,
|
|
4914
5014
|
runtime: rt,
|
|
4915
5015
|
resume,
|
|
@@ -5008,6 +5108,12 @@ export function createWorkManager({
|
|
|
5008
5108
|
* floor: it must never delay the settle behind it.
|
|
5009
5109
|
*/
|
|
5010
5110
|
void reportSessionWorktree(place).catch(() => {});
|
|
5111
|
+
// …and what it drew to SHOW the person (2026-09-22): uploaded against
|
|
5112
|
+
// the AGENT, so it lands on the agent's page whoever is looking.
|
|
5113
|
+
// Fire-and-forget for the same reason — never delay the settle.
|
|
5114
|
+
void artifacts
|
|
5115
|
+
.report({ placeDir: wt, before: artifactsBefore, agentId, turnId })
|
|
5116
|
+
.catch(() => {});
|
|
5011
5117
|
}
|
|
5012
5118
|
|
|
5013
5119
|
const commits = commitsBetween(wt, before);
|
|
@@ -5062,6 +5168,32 @@ export function createWorkManager({
|
|
|
5062
5168
|
...(commits.length ? { commits } : {}),
|
|
5063
5169
|
...(res.raised?.length ? { raised: res.raised } : {}),
|
|
5064
5170
|
...(usage ? { usage } : {}),
|
|
5171
|
+
/**
|
|
5172
|
+
* THE AGENT'S RUNNING ACCOUNT OF THIS BRANCH (2026-09-22).
|
|
5173
|
+
*
|
|
5174
|
+
* The owner: "we should add a brief summary of what the agent has done
|
|
5175
|
+
* overall at the top that updates." A RELAY — the agent wrote it in its
|
|
5176
|
+
* own final JSON object and nothing here composes, narrows or infers
|
|
5177
|
+
* one. It rides only this settle, which is the one that follows a
|
|
5178
|
+
* PARSED result: the two `nothing` settles above it come from a turn
|
|
5179
|
+
* that declared no outcome at all, so there is no account to carry and
|
|
5180
|
+
* inventing one would be the machine speaking for the agent.
|
|
5181
|
+
*
|
|
5182
|
+
* SCRUBBED BEFORE IT IS CUT, the order that matters and the one the
|
|
5183
|
+
* check's output lane learned the expensive way: `envScrub` replaces
|
|
5184
|
+
* EXACT values, so a paragraph capped first hands the scrub a
|
|
5185
|
+
* credential already cut in half — it matches nothing and the surviving
|
|
5186
|
+
* prefix ships. `parseTurnResult` trims it and bounds it for absurdity
|
|
5187
|
+
* at 8000, DELIBERATELY above the 1000 below, so this slice is the
|
|
5188
|
+
* first one a value of any interest meets and the scrub has already
|
|
5189
|
+
* run when it does. The first cut of this feature bounded the parser at
|
|
5190
|
+
* 1000 as well, which read identically and quietly put the cap first.
|
|
5191
|
+
*
|
|
5192
|
+
* OMITTED WHEN THE TURN DID NOT WRITE ONE, never sent empty. Absence is
|
|
5193
|
+
* the server's signal to KEEP the last account that was true; an empty
|
|
5194
|
+
* string would blank the head because a model dropped a key.
|
|
5195
|
+
*/
|
|
5196
|
+
...(res.progress ? { progress: envScrub(res.progress).slice(0, 1000) } : {}),
|
|
5065
5197
|
branch,
|
|
5066
5198
|
worktree: wt,
|
|
5067
5199
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.94.0",
|
|
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": {
|