flowviant 0.21.0 → 0.23.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/claude.mjs +23 -11
- package/bin/lib/config.mjs +1 -1
- package/bin/lib/fleet.mjs +42 -22
- package/package.json +1 -1
package/bin/lib/claude.mjs
CHANGED
|
@@ -235,10 +235,15 @@ export function humanizeToolUse(name, input = {}, cwd = '') {
|
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
//
|
|
238
|
+
// Collapse whitespace + clip so a narration/thinking snippet is one tidy feed line.
|
|
239
|
+
const oneLine = (s, n = 160) => String(s).replace(/\s+/g, ' ').trim().slice(0, n);
|
|
240
|
+
|
|
241
|
+
// Parse ONE line of `--output-format stream-json` NDJSON into feed activities.
|
|
242
|
+
// Surfaces the WHOLE turn — thinking, narration, AND every tool — so neither the
|
|
243
|
+
// daemon console nor the app cover goes dark while Claude reasons (Opus thinks in
|
|
244
|
+
// bursts before/between tools; emitting only tools left long silent gaps).
|
|
245
|
+
// Assistant text is also folded into `out` so the WIKI_DONE/REGROUND_DONE
|
|
246
|
+
// sentinels still match. A non-JSON line (a stray warning) is kept as raw text.
|
|
242
247
|
function handleStreamLine(line, { cwd, emit, onActivity, appendText }) {
|
|
243
248
|
let ev;
|
|
244
249
|
try {
|
|
@@ -248,15 +253,22 @@ function handleStreamLine(line, { cwd, emit, onActivity, appendText }) {
|
|
|
248
253
|
emit(line + '\n');
|
|
249
254
|
return;
|
|
250
255
|
}
|
|
256
|
+
const push = (a) => {
|
|
257
|
+
if (!a || !a.label) return;
|
|
258
|
+
emit(a.label + '\n');
|
|
259
|
+
onActivity?.(a);
|
|
260
|
+
};
|
|
251
261
|
if (ev.type === 'assistant' && Array.isArray(ev.message?.content)) {
|
|
252
262
|
for (const b of ev.message.content) {
|
|
253
|
-
if (b.type === '
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
263
|
+
if (b.type === 'thinking' || b.type === 'redacted_thinking') {
|
|
264
|
+
// The `thinking` text is usually redacted (signature only), so emit a
|
|
265
|
+
// marker — enough to show Claude is actively reasoning, not hung.
|
|
266
|
+
push({ kind: 'think', label: b.thinking ? `thinking: ${oneLine(b.thinking)}` : 'thinking…' });
|
|
267
|
+
} else if (b.type === 'text' && b.text?.trim()) {
|
|
268
|
+
appendText(b.text + '\n');
|
|
269
|
+
push({ kind: 'say', label: oneLine(b.text) });
|
|
270
|
+
} else if (b.type === 'tool_use') {
|
|
271
|
+
push(humanizeToolUse(b.name, b.input || {}, cwd));
|
|
260
272
|
}
|
|
261
273
|
}
|
|
262
274
|
} else if (ev.type === 'result' && typeof ev.result === 'string') {
|
package/bin/lib/config.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { readFileSync } from 'node:fs';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
|
|
7
|
-
export const VERSION = '0.
|
|
7
|
+
export const VERSION = '0.23.0';
|
|
8
8
|
|
|
9
9
|
// Credential stored by `flowviant login` (device auth) — the no-token,
|
|
10
10
|
// no-env-var path. An explicit --fleet flag or FLOWVIANT_FLEET env still wins.
|
package/bin/lib/fleet.mjs
CHANGED
|
@@ -426,7 +426,7 @@ export async function runFleetDaemon() {
|
|
|
426
426
|
let lastProgressAt = 0;
|
|
427
427
|
const postWikiProgress = async (body, force = false) => {
|
|
428
428
|
const now = Date.now();
|
|
429
|
-
if (!force && now - lastProgressAt <
|
|
429
|
+
if (!force && now - lastProgressAt < 600) return;
|
|
430
430
|
lastProgressAt = now;
|
|
431
431
|
try {
|
|
432
432
|
await fetch(WIKI_PROGRESS_URL, {
|
|
@@ -486,25 +486,46 @@ export async function runFleetDaemon() {
|
|
|
486
486
|
}
|
|
487
487
|
const task = wikiQueue.shift();
|
|
488
488
|
const { dir, path: mcpConfig } = mcpConfigFor(token, mcpUrl);
|
|
489
|
-
// Live progress for this turn:
|
|
490
|
-
//
|
|
491
|
-
// to the app (throttled
|
|
489
|
+
// Live progress for this turn: a rolling FEED of everything Claude does
|
|
490
|
+
// (thinking, narration, reads, node writes), the file count, and the
|
|
491
|
+
// phase — streamed to the app (throttled; each frame carries the whole
|
|
492
|
+
// recent tail so a dropped POST loses nothing). elapsedSec is the
|
|
493
|
+
// daemon's own clock.
|
|
492
494
|
const mode = task.type === 'sweep' ? 'sweep' : 'reground';
|
|
493
495
|
const startedAt = Date.now();
|
|
494
496
|
let filesRead = 0;
|
|
495
497
|
let phase = 'reading';
|
|
498
|
+
const feed = [];
|
|
499
|
+
const frame = (extra) => ({
|
|
500
|
+
mode,
|
|
501
|
+
phase,
|
|
502
|
+
activity: feed[feed.length - 1] ?? '',
|
|
503
|
+
recent: feed.slice(-24),
|
|
504
|
+
filesRead,
|
|
505
|
+
elapsedSec: Math.round((Date.now() - startedAt) / 1000),
|
|
506
|
+
...extra,
|
|
507
|
+
});
|
|
496
508
|
const onActivity = (a) => {
|
|
497
509
|
if (a.kind === 'read') filesRead++;
|
|
498
510
|
if (a.kind === 'write') phase = 'writing';
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
});
|
|
511
|
+
// Collapse runs of bare "thinking…" so the feed doesn't fill with it.
|
|
512
|
+
if (!(a.label === 'thinking…' && feed[feed.length - 1] === 'thinking…')) {
|
|
513
|
+
feed.push(a.label);
|
|
514
|
+
if (feed.length > 48) feed.shift();
|
|
515
|
+
}
|
|
516
|
+
void postWikiProgress(frame());
|
|
506
517
|
};
|
|
518
|
+
// Heartbeat: re-send the current frame every 5s even with no new stream
|
|
519
|
+
// event, so the app's freshness window never lapses during a long
|
|
520
|
+
// thinking block or slow tool (which emit nothing until they finish) —
|
|
521
|
+
// otherwise the cover would flap back to the empty state mid-sweep.
|
|
522
|
+
let heartbeat = null;
|
|
507
523
|
try {
|
|
524
|
+
// Immediate frame so the cover shows the daemon feed right away (the
|
|
525
|
+
// "reading your code" phase), not a static message, while Claude warms up.
|
|
526
|
+
feed.push('starting…');
|
|
527
|
+
await postWikiProgress(frame(), true);
|
|
528
|
+
heartbeat = setInterval(() => void postWikiProgress(frame(), true), 5000);
|
|
508
529
|
if (!existsSync(wikiWt)) {
|
|
509
530
|
try {
|
|
510
531
|
git(['worktree', 'add', '--detach', wikiWt, baseRef], repoRoot);
|
|
@@ -565,19 +586,10 @@ export async function runFleetDaemon() {
|
|
|
565
586
|
} catch (e) {
|
|
566
587
|
warn(`wiki ${task.type} failed: ${e.message}`);
|
|
567
588
|
} finally {
|
|
589
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
568
590
|
// Terminal frame so the app cover clears promptly (don't wait for the
|
|
569
591
|
// freshness window to lapse). force-sent past the throttle.
|
|
570
|
-
await postWikiProgress(
|
|
571
|
-
{
|
|
572
|
-
mode,
|
|
573
|
-
phase,
|
|
574
|
-
activity: '',
|
|
575
|
-
filesRead,
|
|
576
|
-
elapsedSec: Math.round((Date.now() - startedAt) / 1000),
|
|
577
|
-
done: true,
|
|
578
|
-
},
|
|
579
|
-
true
|
|
580
|
-
);
|
|
592
|
+
await postWikiProgress(frame({ done: true }), true);
|
|
581
593
|
rmSync(dir, { recursive: true, force: true });
|
|
582
594
|
}
|
|
583
595
|
}
|
|
@@ -621,6 +633,14 @@ export async function runFleetDaemon() {
|
|
|
621
633
|
if (!connected) {
|
|
622
634
|
connected = true;
|
|
623
635
|
ok('Connected to Flowviant — watching your roster.');
|
|
636
|
+
// Name the scoped project so a mismatch (this daemon serves project A, but
|
|
637
|
+
// you're viewing project B's wiki) is obvious instead of a silent no-op.
|
|
638
|
+
if (roster.project) {
|
|
639
|
+
note(
|
|
640
|
+
`${c.cyan('project')} · ${c.bold(roster.project.name)} ${c.dim(`(${roster.project.id})`)}`
|
|
641
|
+
);
|
|
642
|
+
note(c.dim(' wiki + agents stream to THIS project — view its Code canvas in Flowviant.'));
|
|
643
|
+
}
|
|
624
644
|
}
|
|
625
645
|
if (roster.mcpUrl) mcpUrl = roster.mcpUrl;
|
|
626
646
|
if (roster.leaseTtlSeconds) leaseTtlSeconds = roster.leaseTtlSeconds;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Run your own Claude Code as headless build agents for Flowviant — on your own credentials. Claims dispatched work, opens PRs, captures review evidence, and routes questions back to you.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|