flowviant 0.62.0 → 0.63.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.
@@ -115,7 +115,7 @@ export function pickCommand(text) {
115
115
  * the caller's only job with a failure is to relay it, and an exception at this
116
116
  * boundary would strand the row.
117
117
  */
118
- export async function resolveDevCommandOnMachine({ cwd, model, log }) {
118
+ export async function resolveDevCommandOnMachine({ cwd, model, log, onActivity }) {
119
119
  let sessionId = null;
120
120
  let timer;
121
121
  try {
@@ -127,6 +127,18 @@ export async function resolveDevCommandOnMachine({ cwd, model, log }) {
127
127
  answerFromResult: true,
128
128
  model,
129
129
  label: 'dev',
130
+ /**
131
+ * THE TURN'S OWN HUMANIZED TAIL, forwarded so a browser can watch it.
132
+ *
133
+ * This is the only feature in the product where a Claude does work
134
+ * nobody can see — no transcript, by design — and the driver's answer
135
+ * to that is the right one: "we could have it stream the output for
136
+ * transparency on the menu." So the same `read …` / `+ npm install …`
137
+ * lines a tab relays are forwarded here. It is the CLI's own stdout
138
+ * humanized, never an inference about it, which is the standing rule
139
+ * for every activity readout in this product.
140
+ */
141
+ onActivity,
130
142
  onInit: (i) => {
131
143
  if (i?.sessionId) sessionId = i.sessionId;
132
144
  },
package/bin/lib/work.mjs CHANGED
@@ -112,6 +112,7 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
112
112
  const DEV_RUN_CLAIM_URL = FLEET_URL.replace(/\/agents\/?$/, '/dev-run-claim');
113
113
  const DEV_RUN_DONE_URL = FLEET_URL.replace(/\/agents\/?$/, '/dev-run-done');
114
114
  const DEV_RUN_RESOLVED_URL = FLEET_URL.replace(/\/agents\/?$/, '/dev-run-resolved');
115
+ const DEV_RUN_PROGRESS_URL = FLEET_URL.replace(/\/agents\/?$/, '/dev-run-progress');
115
116
  const SESSION_COMMANDS_URL = FLEET_URL.replace(/\/agents\/?$/, '/session-commands');
116
117
  const ATTACHMENT_URL = FLEET_URL.replace(/\/agents\/?$/, '/attachment');
117
118
  const workAnswering = new Set(); // turn ids currently queued/running here
@@ -784,6 +785,65 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
784
785
  * a second implementation of that policy inside the one component a deploy
785
786
  * cannot upgrade is exactly the drift this product keeps closing.
786
787
  */
788
+ /**
789
+ * THE RESOLVE TURN'S OUTPUT, streamed while it happens.
790
+ *
791
+ * A headless turn is the one place in this product where a Claude works and
792
+ * nobody can see it — deliberately, since it must not reach the transcript —
793
+ * and the answer to that is transparency rather than a promise: "we could
794
+ * have it stream the output for transparency on the menu."
795
+ *
796
+ * THROTTLED, and the tail is BOUNDED at the machine. This is a per-line hook
797
+ * on a turn that may run for minutes; posting each line would be hundreds of
798
+ * requests, and sending the whole transcript would grow without limit. Same
799
+ * shape as the session activity relay, for the same reasons.
800
+ */
801
+ const devProgress = new Map(); // sessionId → { lines, at, timer }
802
+ const DEV_PROGRESS_MS = 2_000;
803
+ const DEV_PROGRESS_LINES = 40;
804
+
805
+ const flushDevProgress = async (sessionId) => {
806
+ const st = devProgress.get(sessionId);
807
+ if (!st || !st.lines.length) return;
808
+ st.at = Date.now();
809
+ const logTail = st.lines.join('\n').slice(-4000);
810
+ try {
811
+ await fetch(DEV_RUN_PROGRESS_URL, {
812
+ method: 'POST',
813
+ headers: {
814
+ Authorization: `Bearer ${FLEET_TOKEN}`,
815
+ 'User-Agent': USER_AGENT,
816
+ 'Content-Type': 'application/json',
817
+ },
818
+ signal: AbortSignal.timeout(15_000),
819
+ body: JSON.stringify({ sessionId, instance: DAEMON_INSTANCE, logTail }),
820
+ });
821
+ } catch {
822
+ /* progress is best-effort — the row's own state is the truth */
823
+ }
824
+ };
825
+
826
+ const noteDevProgress = (sessionId, label) => {
827
+ if (!label) return;
828
+ const st = devProgress.get(sessionId) ?? { lines: [], at: 0, timer: null };
829
+ st.lines.push(String(label).slice(0, 300));
830
+ if (st.lines.length > DEV_PROGRESS_LINES) st.lines.splice(0, st.lines.length - DEV_PROGRESS_LINES);
831
+ devProgress.set(sessionId, st);
832
+ // Leading-edge post, then a trailing one — so the FIRST line appears at
833
+ // once (an empty panel for two seconds reads as nothing happening) and the
834
+ // last line is never left unsent.
835
+ if (Date.now() - st.at > DEV_PROGRESS_MS) {
836
+ void flushDevProgress(sessionId);
837
+ return;
838
+ }
839
+ if (st.timer) return;
840
+ st.timer = setTimeout(() => {
841
+ st.timer = null;
842
+ void flushDevProgress(sessionId);
843
+ }, DEV_PROGRESS_MS);
844
+ st.timer.unref?.();
845
+ };
846
+
787
847
  const postDevResolved = async (body) => {
788
848
  try {
789
849
  await fetch(DEV_RUN_RESOLVED_URL, {
@@ -865,6 +925,8 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
865
925
  log: (m) => note(`${sessionId.slice(0, 8)}: ${m}`),
866
926
  })
867
927
  );
928
+ // The last lines, before the row leaves the resolving state.
929
+ await flushDevProgress(sessionId);
868
930
  await postDevResolved({
869
931
  sessionId,
870
932
  command: out?.command ?? null,
@@ -877,6 +939,9 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
877
939
  error: `the machine could not run that turn: ${e?.message ?? 'unknown error'}`,
878
940
  });
879
941
  } finally {
942
+ const st = devProgress.get(sessionId);
943
+ if (st?.timer) clearTimeout(st.timer);
944
+ devProgress.delete(sessionId);
880
945
  devRunClaiming.delete(sessionId);
881
946
  }
882
947
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowviant",
3
- "version": "0.62.0",
3
+ "version": "0.63.0",
4
4
  "description": "Run your own coding CLIs as build agents for Flowviant — 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": {