flowviant 0.60.0 → 0.62.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 +11 -1
- package/bin/lib/devResolve.mjs +155 -0
- package/bin/lib/fleet.mjs +5 -0
- package/bin/lib/runtimes.mjs +12 -1
- package/bin/lib/work.mjs +105 -0
- package/package.json +1 -1
package/bin/lib/claude.mjs
CHANGED
|
@@ -236,7 +236,17 @@ function handleStreamLine(line, { cwd, emit, onActivity, appendText, answerFromR
|
|
|
236
236
|
//
|
|
237
237
|
// Only ever REPORTED, never enforced. Flowviant does not decide what your
|
|
238
238
|
// Claude can do; it relays what your Claude said it has.
|
|
239
|
-
|
|
239
|
+
// `sessionId` rides along for one reason: a `-p` turn WRITES a transcript,
|
|
240
|
+
// and `localSessions.mjs` offers the newest ended session per directory as
|
|
241
|
+
// ADOPTABLE — so any headless turn we run for our own purposes would leave
|
|
242
|
+
// a phantom untitled session in the `+` menu. The caller that needs to
|
|
243
|
+
// clean up after itself cannot do so without this id.
|
|
244
|
+
if (Array.isArray(ev.skills) || typeof ev.session_id === 'string') {
|
|
245
|
+
onInit?.({
|
|
246
|
+
skills: Array.isArray(ev.skills) ? ev.skills.map(String) : undefined,
|
|
247
|
+
sessionId: typeof ev.session_id === 'string' ? ev.session_id : undefined,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
240
250
|
} else if (ev.type === 'result') {
|
|
241
251
|
// The final assistant text (carries WIKI_DONE / REGROUND_DONE).
|
|
242
252
|
if (typeof ev.result === 'string') appendText(ev.result + '\n');
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHAT STARTS THIS PROJECT — asked of a Claude, on the machine, in the
|
|
3
|
+
* background.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. Starting a dev server used to require a command a human had
|
|
6
|
+
* typed into a sheet, prefilled `npm run dev`. That prefill is a guess about
|
|
7
|
+
* somebody else's stack, and it was called out as one: "the option to run or
|
|
8
|
+
* start the dev server shouldnt be npm run dev or show it as npm run dev
|
|
9
|
+
* because thats not agnostic to everyones set up. no need to show that."
|
|
10
|
+
*
|
|
11
|
+
* The mechanism is the driver's own: "we are literally asking a claude session
|
|
12
|
+
* to start it for us" — and, decisively, "i still want claude to start the
|
|
13
|
+
* server for me but i dont want it to literally open a chat. have it do it in
|
|
14
|
+
* the background." So this is a headless turn. It reaches no transcript, spends
|
|
15
|
+
* no tab, and leaves no message anybody has to read.
|
|
16
|
+
*
|
|
17
|
+
* IT ANSWERS WITH A STRING AND STARTS NOTHING. The server parses what comes
|
|
18
|
+
* back, through the same `parseDevCommand` a human's answer goes through, and
|
|
19
|
+
* hands the machine an ordinary start job on the next poll. That split is the
|
|
20
|
+
* whole safety story: the policy for what may execute has exactly one
|
|
21
|
+
* implementation, and it lives in the component this repo can actually upgrade.
|
|
22
|
+
* A daemon that decided for itself what counted as a legal command would be a
|
|
23
|
+
* second copy of that policy, free to drift, published, and unrecallable.
|
|
24
|
+
*
|
|
25
|
+
* IT MAY INSTALL. That is not a loophole in the install refusal — it is the
|
|
26
|
+
* refusal's own stated remedy. `parseDevCommand` refuses `npm install` because
|
|
27
|
+
* a SPAWNED command runs lifecycle scripts from the repo and every transitive
|
|
28
|
+
* dependency with no agent in the loop; the file says the remedy is "a turn in
|
|
29
|
+
* the tab: a human asking, an agent doing it". This is exactly that turn, with
|
|
30
|
+
* the human asking by pressing the button. And it is the case that matters: a
|
|
31
|
+
* fresh worktree has no `node_modules` (they are gitignored, so they never come
|
|
32
|
+
* across with the branch), which is precisely the dead end that produced "it
|
|
33
|
+
* was stuck on 'working', does it really take that long to run dev?"
|
|
34
|
+
*
|
|
35
|
+
* IT CLEANS UP AFTER ITSELF. A `-p` turn writes a transcript, and
|
|
36
|
+
* `localSessions.mjs` offers the newest ended session per directory as
|
|
37
|
+
* adoptable — left behind, every resolve would drop a phantom untitled session
|
|
38
|
+
* into the `+` menu.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { runTurn } from './claude.mjs';
|
|
42
|
+
import { removeProbeTranscript } from './runtimes.mjs';
|
|
43
|
+
|
|
44
|
+
/** Long, because an install can sit in front of the answer. The SERVER holds
|
|
45
|
+
* the real ceiling (`RESOLVE_TTL_MS`); this is the machine giving up first so
|
|
46
|
+
* a wedged child does not hold a slot until then. */
|
|
47
|
+
export const DEV_RESOLVE_TIMEOUT_MS = 10 * 60_000;
|
|
48
|
+
|
|
49
|
+
/** The sentinel for "I could not tell", so an honest failure is distinguishable
|
|
50
|
+
* from a model padding an answer it does not have. */
|
|
51
|
+
export const NO_COMMAND = 'NONE';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The argv0s the server will accept. Named in the prompt NOT as a security
|
|
55
|
+
* control — the server enforces it either way, and would refuse anything else
|
|
56
|
+
* with the parser's own words — but because a model that knows the shape of an
|
|
57
|
+
* acceptable answer gives one, and a refused proposal costs the asker a whole
|
|
58
|
+
* round trip to learn nothing.
|
|
59
|
+
*/
|
|
60
|
+
const ALLOWED = [
|
|
61
|
+
'npm', 'pnpm', 'yarn', 'bun', 'node', 'deno', 'go', 'python', 'python3',
|
|
62
|
+
'make', 'cargo', 'rails', 'php', 'dotnet',
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
export function resolvePrompt() {
|
|
66
|
+
return [
|
|
67
|
+
'Work out the ONE command that starts this project’s development server, and reply with only that command.',
|
|
68
|
+
'',
|
|
69
|
+
'How to work it out: read the repo. Check package.json scripts, Makefile, Procfile, docker-compose, pyproject.toml, Cargo.toml, README — whatever this project actually uses. Prefer the script the project itself documents for local development.',
|
|
70
|
+
'',
|
|
71
|
+
'You MAY install dependencies first if they are missing (for example a worktree with no node_modules). Do that before answering.',
|
|
72
|
+
'',
|
|
73
|
+
'Rules for the answer:',
|
|
74
|
+
`- It must begin with one of: ${ALLOWED.join(', ')} — or a ./path to a script in this repo.`,
|
|
75
|
+
'- One line. No shell operators (&&, |, ;, >, $, backticks). If the project needs several steps, name a script in the repo that does them.',
|
|
76
|
+
'- Not an install command. Install as part of your work above if needed; the answer is the command that RUNS the server.',
|
|
77
|
+
'- It must not daemonize or background itself. It should stay in the foreground; something else supervises it.',
|
|
78
|
+
`- If you genuinely cannot tell, reply exactly ${NO_COMMAND}.`,
|
|
79
|
+
'',
|
|
80
|
+
'Reply with the command alone — no explanation, no backticks, no prose.',
|
|
81
|
+
].join('\n');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Last plausible command line out of whatever the model said.
|
|
86
|
+
*
|
|
87
|
+
* DELIBERATELY FORGIVING, because the cost of being wrong is low and asymmetric:
|
|
88
|
+
* the server parses this and refuses anything outside the policy, naming what
|
|
89
|
+
* was proposed. Being strict here would turn a model that wrapped its answer in
|
|
90
|
+
* backticks into a failure the asker cannot act on, having already paid for the
|
|
91
|
+
* turn.
|
|
92
|
+
*/
|
|
93
|
+
export function pickCommand(text) {
|
|
94
|
+
const lines = String(text ?? '')
|
|
95
|
+
.split('\n')
|
|
96
|
+
.map((l) => l.trim())
|
|
97
|
+
// Fence markers and bullet/quote decoration, which are formatting rather
|
|
98
|
+
// than part of anybody's command.
|
|
99
|
+
.filter((l) => l && !/^```/.test(l))
|
|
100
|
+
.map((l) => l.replace(/^[-*>\s]+/, '').replace(/^`+|`+$/g, '').trim())
|
|
101
|
+
.filter(Boolean);
|
|
102
|
+
if (lines.length === 0) return null;
|
|
103
|
+
// The LAST such line: a model that explains before complying puts the answer
|
|
104
|
+
// at the end, and one that complies exactly has only one line anyway.
|
|
105
|
+
const last = lines[lines.length - 1];
|
|
106
|
+
if (!last || last === NO_COMMAND) return null;
|
|
107
|
+
// A sentence is not a command. Cheap shape check so obvious prose becomes
|
|
108
|
+
// "could not work it out" rather than a refusal quoting a paragraph back.
|
|
109
|
+
if (last.split(/\s+/).length > 8 || /[.!?]$/.test(last)) return null;
|
|
110
|
+
return last;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Run the turn. Resolves `{ command }` or `{ error }` — never throws, because
|
|
115
|
+
* the caller's only job with a failure is to relay it, and an exception at this
|
|
116
|
+
* boundary would strand the row.
|
|
117
|
+
*/
|
|
118
|
+
export async function resolveDevCommandOnMachine({ cwd, model, log }) {
|
|
119
|
+
let sessionId = null;
|
|
120
|
+
let timer;
|
|
121
|
+
try {
|
|
122
|
+
const out = await Promise.race([
|
|
123
|
+
runTurn({
|
|
124
|
+
prompt: resolvePrompt(),
|
|
125
|
+
cwd,
|
|
126
|
+
streamJson: true,
|
|
127
|
+
answerFromResult: true,
|
|
128
|
+
model,
|
|
129
|
+
label: 'dev',
|
|
130
|
+
onInit: (i) => {
|
|
131
|
+
if (i?.sessionId) sessionId = i.sessionId;
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
new Promise((r) => {
|
|
135
|
+
timer = setTimeout(() => r(null), DEV_RESOLVE_TIMEOUT_MS);
|
|
136
|
+
timer.unref?.();
|
|
137
|
+
}),
|
|
138
|
+
]);
|
|
139
|
+
if (out === null) {
|
|
140
|
+
return { error: 'your Claude did not finish working out how to start this project in time.' };
|
|
141
|
+
}
|
|
142
|
+
const command = pickCommand(out);
|
|
143
|
+
if (!command) {
|
|
144
|
+
return { error: 'your Claude could not work out how to start this project.' };
|
|
145
|
+
}
|
|
146
|
+
log?.(`dev: resolved start command — ${command}`);
|
|
147
|
+
return { command };
|
|
148
|
+
} catch (e) {
|
|
149
|
+
return { error: `your Claude could not be run here: ${e?.message ?? 'unknown error'}` };
|
|
150
|
+
} finally {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
// After the turn, so the delete does not race a child still writing.
|
|
153
|
+
if (sessionId) setTimeout(() => removeProbeTranscript(cwd, sessionId), 750).unref?.();
|
|
154
|
+
}
|
|
155
|
+
}
|
package/bin/lib/fleet.mjs
CHANGED
|
@@ -824,6 +824,7 @@ export async function runFleetDaemon() {
|
|
|
824
824
|
// retirement — lives in work.mjs; this hands it the loop's mutable state.
|
|
825
825
|
const {
|
|
826
826
|
flushWorkReports,
|
|
827
|
+
learnPlaces,
|
|
827
828
|
processWorkTurns,
|
|
828
829
|
processShipJobs,
|
|
829
830
|
processDiffJobs,
|
|
@@ -1549,6 +1550,10 @@ export async function runFleetDaemon() {
|
|
|
1549
1550
|
void flushWorkReports();
|
|
1550
1551
|
processMergeJobs(roster.mergeJobs);
|
|
1551
1552
|
processPatchRevertJobs(roster.patchRevertJobs);
|
|
1553
|
+
// WHERE each live tab works, BEFORE anything measures one: the sweep below
|
|
1554
|
+
// and every preview check ask `placeOf`, and a tab nobody has typed into
|
|
1555
|
+
// yet has taught it nothing.
|
|
1556
|
+
learnPlaces(roster.sessionPlaces);
|
|
1552
1557
|
processWorkTurns(roster.workTurnJobs);
|
|
1553
1558
|
// The roster's live-session list rides along: an ENDED session's ship
|
|
1554
1559
|
// must not be refused by checks whose remedies need a live tab.
|
package/bin/lib/runtimes.mjs
CHANGED
|
@@ -1054,7 +1054,18 @@ function transcriptCandidates(cwd, sessionId) {
|
|
|
1054
1054
|
return out;
|
|
1055
1055
|
}
|
|
1056
1056
|
|
|
1057
|
-
|
|
1057
|
+
/**
|
|
1058
|
+
* Delete the transcript a headless `-p` turn left behind.
|
|
1059
|
+
*
|
|
1060
|
+
* EXPORTED because the skills probe is no longer the only thing that runs one:
|
|
1061
|
+
* resolving a project's dev command is a background Claude turn too, and every
|
|
1062
|
+
* such turn has the same footprint. `claude -p` writes
|
|
1063
|
+
* `~/.claude/projects/<munged-cwd>/<id>.jsonl` at startup, and
|
|
1064
|
+
* `localSessions.mjs` offers the newest ENDED session per directory as
|
|
1065
|
+
* ADOPTABLE — so anything we run for our own purposes would put a phantom
|
|
1066
|
+
* untitled session in somebody's `+` menu.
|
|
1067
|
+
*/
|
|
1068
|
+
export function removeProbeTranscript(cwd, sessionId) {
|
|
1058
1069
|
if (!sessionId || !/^[A-Za-z0-9_-]{8,64}$/.test(sessionId)) return;
|
|
1059
1070
|
for (const f of transcriptCandidates(cwd, sessionId)) {
|
|
1060
1071
|
try {
|
package/bin/lib/work.mjs
CHANGED
|
@@ -36,11 +36,13 @@ import {
|
|
|
36
36
|
USER_AGENT,
|
|
37
37
|
REFRESH_BEFORE_SECONDS,
|
|
38
38
|
DAEMON_INSTANCE,
|
|
39
|
+
MODEL,
|
|
39
40
|
} from './config.mjs';
|
|
40
41
|
import { git, gitRaw, splitNul, baseBranchName, isSafePathSegment } from './git.mjs';
|
|
41
42
|
import { listenersIn, listenersSupported } from './listeners.mjs';
|
|
42
43
|
import { openTunnel } from './preview.mjs';
|
|
43
44
|
import { startDevServer, reapOrphanDevRuns, killDevRunEntry } from './devServer.mjs';
|
|
45
|
+
import { resolveDevCommandOnMachine } from './devResolve.mjs';
|
|
44
46
|
import { c, note, ok, warn } from './ui.mjs';
|
|
45
47
|
import { mcpFor, runTurn } from './claude.mjs';
|
|
46
48
|
import {
|
|
@@ -109,6 +111,7 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
109
111
|
const PREVIEW_DONE_URL = FLEET_URL.replace(/\/agents\/?$/, '/preview-done');
|
|
110
112
|
const DEV_RUN_CLAIM_URL = FLEET_URL.replace(/\/agents\/?$/, '/dev-run-claim');
|
|
111
113
|
const DEV_RUN_DONE_URL = FLEET_URL.replace(/\/agents\/?$/, '/dev-run-done');
|
|
114
|
+
const DEV_RUN_RESOLVED_URL = FLEET_URL.replace(/\/agents\/?$/, '/dev-run-resolved');
|
|
112
115
|
const SESSION_COMMANDS_URL = FLEET_URL.replace(/\/agents\/?$/, '/session-commands');
|
|
113
116
|
const ATTACHMENT_URL = FLEET_URL.replace(/\/agents\/?$/, '/attachment');
|
|
114
117
|
const workAnswering = new Set(); // turn ids currently queued/running here
|
|
@@ -355,6 +358,25 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
355
358
|
*/
|
|
356
359
|
const sessionPlaces = new Map();
|
|
357
360
|
const placeOf = (sessionId) => sessionPlaces.get(sessionId) ?? sessionId;
|
|
361
|
+
/**
|
|
362
|
+
* Places straight off the roster, so a tab is measured the moment it EXISTS
|
|
363
|
+
* rather than after somebody types into it.
|
|
364
|
+
*
|
|
365
|
+
* Learning only from turn jobs meant a tab nobody had spoken to yet was
|
|
366
|
+
* measured at `sessions/<id>` — a directory that does not exist for a tab
|
|
367
|
+
* working in the checkout — so it reported no branch, no diffstat and no
|
|
368
|
+
* ports, and every control reading those had nothing to render.
|
|
369
|
+
*
|
|
370
|
+
* The roster is authoritative and turn jobs still agree with it; a session
|
|
371
|
+
* the server does not name keeps whatever a turn taught, and failing that its
|
|
372
|
+
* own id, which is the pre-places default.
|
|
373
|
+
*/
|
|
374
|
+
const learnPlaces = (map) => {
|
|
375
|
+
if (!map || typeof map !== 'object') return;
|
|
376
|
+
for (const [sid, place] of Object.entries(map)) {
|
|
377
|
+
if (typeof place === 'string' && place) sessionPlaces.set(sid, place);
|
|
378
|
+
}
|
|
379
|
+
};
|
|
358
380
|
/** The DIRECTORY a session works in. Every path that used to build
|
|
359
381
|
* `sessions/<id>` by hand goes through here, or a tab in the checkout gets
|
|
360
382
|
* measured against a directory that does not exist. */
|
|
@@ -755,6 +777,30 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
755
777
|
}
|
|
756
778
|
};
|
|
757
779
|
|
|
780
|
+
/**
|
|
781
|
+
* The answer to a `resolve`, handed up as a STRING for the server to parse.
|
|
782
|
+
*
|
|
783
|
+
* Never argv: `parseDevCommand` is the single owner of what may execute, and
|
|
784
|
+
* a second implementation of that policy inside the one component a deploy
|
|
785
|
+
* cannot upgrade is exactly the drift this product keeps closing.
|
|
786
|
+
*/
|
|
787
|
+
const postDevResolved = async (body) => {
|
|
788
|
+
try {
|
|
789
|
+
await fetch(DEV_RUN_RESOLVED_URL, {
|
|
790
|
+
method: 'POST',
|
|
791
|
+
headers: {
|
|
792
|
+
Authorization: `Bearer ${FLEET_TOKEN}`,
|
|
793
|
+
'User-Agent': USER_AGENT,
|
|
794
|
+
'Content-Type': 'application/json',
|
|
795
|
+
},
|
|
796
|
+
signal: AbortSignal.timeout(30_000),
|
|
797
|
+
body: JSON.stringify({ ...body, instance: DAEMON_INSTANCE }),
|
|
798
|
+
});
|
|
799
|
+
} catch {
|
|
800
|
+
/* the row's own TTL is the backstop */
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
|
|
758
804
|
const stopDevRun = async (sessionId, reason) => {
|
|
759
805
|
const live = liveDevRuns.get(sessionId);
|
|
760
806
|
liveDevRuns.delete(sessionId);
|
|
@@ -779,6 +825,64 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
779
825
|
continue;
|
|
780
826
|
}
|
|
781
827
|
|
|
828
|
+
/**
|
|
829
|
+
* RESOLVE — ask a Claude what starts this project, and say so. Starts
|
|
830
|
+
* NOTHING.
|
|
831
|
+
*
|
|
832
|
+
* It exists because the sheet used to open with a text field prefilled
|
|
833
|
+
* `npm run dev`, which presumes a stack. The turn is headless and reaches
|
|
834
|
+
* no transcript: "i still want claude to start the server for me but i
|
|
835
|
+
* dont want it to literally open a chat. have it do it in the
|
|
836
|
+
* background."
|
|
837
|
+
*
|
|
838
|
+
* IT TAKES THE PLACE LOCK, through the same `chainFor` every turn goes
|
|
839
|
+
* through, and that is deliberate rather than incidental. The turn reads
|
|
840
|
+
* the repo and may `npm install` into this worktree; running it beside a
|
|
841
|
+
* tab turn editing the same directory is the exact collision the chain
|
|
842
|
+
* exists to prevent. The cost is that a resolve makes the tab wait, which
|
|
843
|
+
* is honest — you cannot usefully build while an install is running
|
|
844
|
+
* anyway — and it is bounded, because this turn ENDS. That is the whole
|
|
845
|
+
* reason it answers with a command instead of running one: a foreground
|
|
846
|
+
* `npm run dev` would never return, and the lock would be held for as
|
|
847
|
+
* long as the server lived.
|
|
848
|
+
*/
|
|
849
|
+
if (job?.action === 'resolve') {
|
|
850
|
+
if (liveDevRuns.has(sessionId)) continue;
|
|
851
|
+
if (devRunClaiming.has(sessionId)) continue;
|
|
852
|
+
devRunClaiming.add(sessionId);
|
|
853
|
+
void (async () => {
|
|
854
|
+
try {
|
|
855
|
+
if (!(await claimDevRun(sessionId))) return; // somebody else has it
|
|
856
|
+
const wt = placeDir(sessionId);
|
|
857
|
+
const out = await chainFor(placeOf(sessionId), () =>
|
|
858
|
+
resolveDevCommandOnMachine({
|
|
859
|
+
cwd: wt,
|
|
860
|
+
// The machine's own pin, exactly as a tab turn gets — never
|
|
861
|
+
// the user's global default, which for Claude may be a
|
|
862
|
+
// long-context tier their subscription cannot bill autonomous
|
|
863
|
+
// work on. This turn is autonomous by definition.
|
|
864
|
+
model: MODEL,
|
|
865
|
+
log: (m) => note(`${sessionId.slice(0, 8)}: ${m}`),
|
|
866
|
+
})
|
|
867
|
+
);
|
|
868
|
+
await postDevResolved({
|
|
869
|
+
sessionId,
|
|
870
|
+
command: out?.command ?? null,
|
|
871
|
+
error: out?.error ?? null,
|
|
872
|
+
});
|
|
873
|
+
} catch (e) {
|
|
874
|
+
await postDevResolved({
|
|
875
|
+
sessionId,
|
|
876
|
+
command: null,
|
|
877
|
+
error: `the machine could not run that turn: ${e?.message ?? 'unknown error'}`,
|
|
878
|
+
});
|
|
879
|
+
} finally {
|
|
880
|
+
devRunClaiming.delete(sessionId);
|
|
881
|
+
}
|
|
882
|
+
})();
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
|
|
782
886
|
// RE-VALIDATE THE SHAPE at this boundary. The server parsed the string
|
|
783
887
|
// and owns the policy; the machine owns the refusal to execute something
|
|
784
888
|
// malformed, because one place doing a check is one deploy away from
|
|
@@ -2617,6 +2721,7 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
2617
2721
|
|
|
2618
2722
|
return {
|
|
2619
2723
|
flushWorkReports,
|
|
2724
|
+
learnPlaces,
|
|
2620
2725
|
processWorkTurns,
|
|
2621
2726
|
processShipJobs,
|
|
2622
2727
|
processDiffJobs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.62.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": {
|