flowviant 0.62.0 → 0.63.1
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/devResolve.mjs +13 -1
- package/bin/lib/work.mjs +94 -7
- package/package.json +1 -1
package/bin/lib/devResolve.mjs
CHANGED
|
@@ -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, {
|
|
@@ -847,12 +907,27 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
847
907
|
* long as the server lived.
|
|
848
908
|
*/
|
|
849
909
|
if (job?.action === 'resolve') {
|
|
850
|
-
|
|
851
|
-
|
|
910
|
+
/**
|
|
911
|
+
* EVERY SKIP SAYS SO. These were silent `continue`s, and that cost real
|
|
912
|
+
* time: a request sat unclaimed and the only sentence anyone got was
|
|
913
|
+
* "the machine did not pick this up — it may be offline", which was
|
|
914
|
+
* false — the machine was polling throughout. A job dropped without a
|
|
915
|
+
* trace in the row OR the log is undiagnosable from either end, so the
|
|
916
|
+
* skips are narrated even though they are all legitimate states.
|
|
917
|
+
*/
|
|
918
|
+
if (liveDevRuns.has(sessionId)) {
|
|
919
|
+
note(`dev ${sessionId.slice(0, 8)}: already serving this tab — ignoring the request`);
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
if (devRunClaiming.has(sessionId)) continue; // in flight this tick; not news
|
|
852
923
|
devRunClaiming.add(sessionId);
|
|
853
924
|
void (async () => {
|
|
854
925
|
try {
|
|
855
|
-
if (!(await claimDevRun(sessionId)))
|
|
926
|
+
if (!(await claimDevRun(sessionId))) {
|
|
927
|
+
note(`dev ${sessionId.slice(0, 8)}: another process holds this request`);
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
note(`dev ${sessionId.slice(0, 8)}: working out how to start this project…`);
|
|
856
931
|
const wt = placeDir(sessionId);
|
|
857
932
|
const out = await chainFor(placeOf(sessionId), () =>
|
|
858
933
|
resolveDevCommandOnMachine({
|
|
@@ -865,6 +940,8 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
865
940
|
log: (m) => note(`${sessionId.slice(0, 8)}: ${m}`),
|
|
866
941
|
})
|
|
867
942
|
);
|
|
943
|
+
// The last lines, before the row leaves the resolving state.
|
|
944
|
+
await flushDevProgress(sessionId);
|
|
868
945
|
await postDevResolved({
|
|
869
946
|
sessionId,
|
|
870
947
|
command: out?.command ?? null,
|
|
@@ -877,6 +954,9 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
877
954
|
error: `the machine could not run that turn: ${e?.message ?? 'unknown error'}`,
|
|
878
955
|
});
|
|
879
956
|
} finally {
|
|
957
|
+
const st = devProgress.get(sessionId);
|
|
958
|
+
if (st?.timer) clearTimeout(st.timer);
|
|
959
|
+
devProgress.delete(sessionId);
|
|
880
960
|
devRunClaiming.delete(sessionId);
|
|
881
961
|
}
|
|
882
962
|
})();
|
|
@@ -898,14 +978,21 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
898
978
|
continue;
|
|
899
979
|
}
|
|
900
980
|
// Already serving this tab. Re-starting would kill a server somebody is
|
|
901
|
-
// looking at right now.
|
|
902
|
-
|
|
903
|
-
if (
|
|
981
|
+
// looking at right now. Narrated for the same reason the resolve branch
|
|
982
|
+
// is: a silent skip is invisible from the browser AND from the machine.
|
|
983
|
+
if (liveDevRuns.has(sessionId)) {
|
|
984
|
+
note(`dev ${sessionId.slice(0, 8)}: already serving this tab — ignoring the request`);
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
if (devRunClaiming.has(sessionId)) continue; // in flight this tick; not news
|
|
904
988
|
devRunClaiming.add(sessionId);
|
|
905
989
|
|
|
906
990
|
void (async () => {
|
|
907
991
|
try {
|
|
908
|
-
if (!(await claimDevRun(sessionId)))
|
|
992
|
+
if (!(await claimDevRun(sessionId))) {
|
|
993
|
+
note(`dev ${sessionId.slice(0, 8)}: another process holds this request`);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
909
996
|
const wt = placeDir(sessionId);
|
|
910
997
|
const r = await startDevServer({
|
|
911
998
|
sessionId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.63.1",
|
|
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": {
|