flowviant 0.71.0 → 0.71.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/work.mjs +64 -3
- package/package.json +1 -1
package/bin/lib/work.mjs
CHANGED
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
import { git, gitRaw, splitNul, baseBranchName, isSafePathSegment } from './git.mjs';
|
|
42
42
|
import { listenersIn, measureListeners, listenersSupported } from './listeners.mjs';
|
|
43
43
|
import { measureProcesses, liveGroups, processesSupported } from './processes.mjs';
|
|
44
|
-
import { mutateRegistry, readRegistry } from './procRegistry.mjs';
|
|
44
|
+
import { mutateRegistry, processAlive, readRegistry } from './procRegistry.mjs';
|
|
45
45
|
import { createPlaceLock } from './placeLock.mjs';
|
|
46
46
|
import { sweepMergedBranch } from './shipSweep.mjs';
|
|
47
47
|
import { mergeOutward as shipMergeOutward } from './shipMerge.mjs';
|
|
@@ -1070,6 +1070,30 @@ export function createWorkManager({ repoRoot, baseDir, getBaseRef, getMcpUrl, ge
|
|
|
1070
1070
|
return false;
|
|
1071
1071
|
};
|
|
1072
1072
|
|
|
1073
|
+
/**
|
|
1074
|
+
* How long to watch for the process to actually go before answering.
|
|
1075
|
+
*
|
|
1076
|
+
* SIGTERM is a REQUEST, not an event: a dev server traps it and tears down
|
|
1077
|
+
* its children, which takes a beat. Answering the instant the signal returns
|
|
1078
|
+
* would report "signalled" over a process that is about to die, and the
|
|
1079
|
+
* surface would then offer Force stop on something already on its way out.
|
|
1080
|
+
*
|
|
1081
|
+
* Four seconds is long enough for the ordinary teardown and short enough that
|
|
1082
|
+
* a person is still looking at the row. Past it the honest answer is that the
|
|
1083
|
+
* signal landed and the thing is still there — which is a real state, and the
|
|
1084
|
+
* one where escalating actually means something.
|
|
1085
|
+
*/
|
|
1086
|
+
const KILL_GRACE_MS = 4000;
|
|
1087
|
+
|
|
1088
|
+
const waitForExit = async (pid) => {
|
|
1089
|
+
const until = Date.now() + KILL_GRACE_MS;
|
|
1090
|
+
while (Date.now() < until) {
|
|
1091
|
+
if (!processAlive(pid)) return true;
|
|
1092
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
1093
|
+
}
|
|
1094
|
+
return !processAlive(pid);
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1073
1097
|
const runKill = async (job) => {
|
|
1074
1098
|
const id = String(job.id);
|
|
1075
1099
|
const sessionId = String(job.sessionId || '');
|
|
@@ -1083,20 +1107,57 @@ export function createWorkManager({ repoRoot, baseDir, getBaseRef, getMcpUrl, ge
|
|
|
1083
1107
|
if (!killTargetOk(sessionId, pid)) {
|
|
1084
1108
|
// Not a lie and not a failure: the process is genuinely no longer one of
|
|
1085
1109
|
// this tab's, which is the common case when somebody clicks a row that
|
|
1086
|
-
// has since exited. The asker gets that sentence rather than a spinner
|
|
1110
|
+
// has since exited. The asker gets that sentence rather than a spinner —
|
|
1111
|
+
// and the RE-MEASURE below is what takes the stale row off their screen,
|
|
1112
|
+
// since a row you can click for something already gone is the readout
|
|
1113
|
+
// being behind, not the person being wrong.
|
|
1087
1114
|
await postKill({ id, outcome: 'not_found' });
|
|
1115
|
+
await remeasureAfterKill(sessionId);
|
|
1088
1116
|
return;
|
|
1089
1117
|
}
|
|
1090
1118
|
if (!(await claimKill(id))) return;
|
|
1091
1119
|
try {
|
|
1092
1120
|
process.kill(pid, signal);
|
|
1093
|
-
|
|
1121
|
+
// WHAT HAPPENED, not what we did. "We sent a signal" is a fact about us;
|
|
1122
|
+
// "it stopped" is a fact about the machine, and the machine is standing
|
|
1123
|
+
// right here able to check. Reporting the weaker word would also make the
|
|
1124
|
+
// Force stop offer wrong for the whole window, since escalating only
|
|
1125
|
+
// means something while the process is genuinely still there.
|
|
1126
|
+
const gone = await waitForExit(pid);
|
|
1127
|
+
await postKill({ id, outcome: gone ? 'stopped' : 'signalled', signal });
|
|
1094
1128
|
} catch (e) {
|
|
1095
1129
|
// EPERM is the daemon-on-its-own-user posture doing exactly what it is
|
|
1096
1130
|
// for. Report it as its own word: "we may not" and "it was gone" are
|
|
1097
1131
|
// different sentences and the surface says which.
|
|
1098
1132
|
await postKill({ id, outcome: e?.code === 'ESRCH' ? 'not_found' : 'error', detail: String(e?.code || e) });
|
|
1099
1133
|
}
|
|
1134
|
+
await remeasureAfterKill(sessionId);
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* THE LIST THE PERSON IS LOOKING AT WAS MEASURED BEFORE ANY OF THIS.
|
|
1139
|
+
*
|
|
1140
|
+
* Without this the row survives the thing it describes: the panel renders the
|
|
1141
|
+
* last sweep's `listening`, the sweep is on a SIXTY-SECOND beat, and the
|
|
1142
|
+
* reported outcome sits next to a port row still claiming to be live. The
|
|
1143
|
+
* first person to use it said exactly that — "i clicked stop on the listening
|
|
1144
|
+
* but its still running… then it finally disappears".
|
|
1145
|
+
*
|
|
1146
|
+
* The rule it was missing is one this file already keeps everywhere else: an
|
|
1147
|
+
* action that changes what the machine would measure must cause a new
|
|
1148
|
+
* measurement. A turn settling does it; a kill did not. `reportSessionWorktree`
|
|
1149
|
+
* is the un-throttled per-session path built for precisely this and it was
|
|
1150
|
+
* being called from exactly one place.
|
|
1151
|
+
*
|
|
1152
|
+
* Never awaited by the caller's answer path: the outcome is posted first, so
|
|
1153
|
+
* a slow re-measure can delay the list but never the sentence.
|
|
1154
|
+
*/
|
|
1155
|
+
const remeasureAfterKill = async (sessionId) => {
|
|
1156
|
+
try {
|
|
1157
|
+
await reportSessionWorktree(sessionId);
|
|
1158
|
+
} catch {
|
|
1159
|
+
/* the 60s sweep still carries it — this only makes it prompt */
|
|
1160
|
+
}
|
|
1100
1161
|
};
|
|
1101
1162
|
|
|
1102
1163
|
const processKillJobs = (jobs) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.71.
|
|
3
|
+
"version": "0.71.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": {
|