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/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
- excludeInWorktree(wt, ['.flowviant/']);
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
- system: plainTab ? SYSTEM_WORK_PLAIN : captureTab ? SYSTEM_CAPTURE : SYSTEM_WORK,
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
- system: SYSTEM_AGENT,
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.92.1",
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": {