flowviant 0.39.0 → 0.40.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 +33 -2
- package/bin/lib/fleet.mjs +36 -2
- package/bin/lib/live.mjs +592 -35
- package/bin/lib/preflight.mjs +11 -1
- package/bin/lib/runtimes.mjs +464 -49
- package/package.json +1 -1
package/bin/lib/claude.mjs
CHANGED
|
@@ -594,7 +594,7 @@ function handleStreamLine(line, { cwd, emit, onActivity, appendText }) {
|
|
|
594
594
|
// returned string for sentinel detection, and each activity is handed to
|
|
595
595
|
// `onActivity` so the caller can forward progress. Build-agent turns leave it
|
|
596
596
|
// off and keep the raw text passthrough + line sentinels.
|
|
597
|
-
export function runTurn({ prompt, resume, system, cwd, mcpConfig, mcpArgs, mcpEnv, runtime = 'claude', label, onSpawn, streamJson, onActivity, wikiPerm, readOnly, model, effort }) {
|
|
597
|
+
export function runTurn({ prompt, resume, system, cwd, mcpConfig, mcpArgs, mcpEnv, runtime = 'claude', label, onSpawn, streamJson, onActivity, wikiPerm, readOnly, vaultDir, resultSchemaArgs, model, effort }) {
|
|
598
598
|
return new Promise((resolve) => {
|
|
599
599
|
const rt = runtimeById(runtime);
|
|
600
600
|
if (!rt.args) {
|
|
@@ -614,6 +614,17 @@ export function runTurn({ prompt, resume, system, cwd, mcpConfig, mcpArgs, mcpEn
|
|
|
614
614
|
// all: unset means the CLI's own default, the honest resting state.
|
|
615
615
|
//
|
|
616
616
|
// readOnly wins over wikiPerm: a consult must never inherit write tools.
|
|
617
|
+
//
|
|
618
|
+
// TWO FORMS OF THE SAME DECISION, and the redundancy is deliberate rather
|
|
619
|
+
// than sloppy. `profile` is the NAME of the posture — a promise about what
|
|
620
|
+
// must be impossible during the turn — and every runtime expresses it in its
|
|
621
|
+
// own vocabulary: Claude as an `--allowedTools` verb list, Codex as a kernel
|
|
622
|
+
// sandbox mode plus feature toggles. `perm` is Claude's expression, still
|
|
623
|
+
// computed here only because those three arrays live in this file; it
|
|
624
|
+
// collapses into the registry the day every runtime expresses every profile.
|
|
625
|
+
// Both derive from the same branch, so they cannot disagree about which
|
|
626
|
+
// posture a turn is running under.
|
|
627
|
+
const profile = readOnly ? 'consult' : wikiPerm ? 'wiki' : 'build';
|
|
617
628
|
const args = rt.args({
|
|
618
629
|
prompt,
|
|
619
630
|
system,
|
|
@@ -621,6 +632,16 @@ export function runTurn({ prompt, resume, system, cwd, mcpConfig, mcpArgs, mcpEn
|
|
|
621
632
|
effort,
|
|
622
633
|
resume,
|
|
623
634
|
streamJson,
|
|
635
|
+
profile,
|
|
636
|
+
// Only the wiki profile uses it, but it is passed unconditionally: a
|
|
637
|
+
// runtime that can path-scope its writes needs to know WHERE the vault is,
|
|
638
|
+
// and Claude — which cannot — simply ignores it.
|
|
639
|
+
vaultDir,
|
|
640
|
+
// Structured-output flags for the MEDIATED path. Handed to the adapter
|
|
641
|
+
// rather than appended here for the same reason `mcp` is: Codex takes its
|
|
642
|
+
// prompt as a trailing positional, so a flag after it is in the wrong
|
|
643
|
+
// place.
|
|
644
|
+
resultSchemaArgs,
|
|
624
645
|
perm: readOnly ? CONSULT_PERM : wikiPerm ? WIKI_PERM : PERM,
|
|
625
646
|
// Handed to the adapter rather than appended here, because WHERE these go
|
|
626
647
|
// is a property of the CLI: Codex reads its prompt as a trailing
|
|
@@ -692,8 +713,18 @@ export function runTurn({ prompt, resume, system, cwd, mcpConfig, mcpArgs, mcpEn
|
|
|
692
713
|
});
|
|
693
714
|
child.on('error', (e) => {
|
|
694
715
|
if (e.code === 'ENOENT') {
|
|
716
|
+
// A MISSING CLI FAILS THE TURN, NOT THE DAEMON. This called
|
|
717
|
+
// process.exit(1), which was defensible while `claude` was the only
|
|
718
|
+
// runtime and preflight refused to start without it — the process
|
|
719
|
+
// could not reach here. Both halves of that are gone: preflight is now
|
|
720
|
+
// fatal only when NOTHING is drivable, so a Codex-only machine starts
|
|
721
|
+
// legitimately, and the wiki/plan-check/consult turns still ask for
|
|
722
|
+
// Claude by default. On such a machine the first wiki sweep would have
|
|
723
|
+
// killed the whole daemon, taking every in-flight build with it,
|
|
724
|
+
// because one background job could not find one binary.
|
|
695
725
|
console.error(`\nerror: '${rt.bin}' CLI not found on PATH. Install ${rt.label} first: ${rt.install}`);
|
|
696
|
-
|
|
726
|
+
resolve('');
|
|
727
|
+
return;
|
|
697
728
|
}
|
|
698
729
|
console.error(e);
|
|
699
730
|
resolve(out);
|
package/bin/lib/fleet.mjs
CHANGED
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
} from './env.mjs';
|
|
77
77
|
import { processDeployJobs, reportDeployConfig } from './deploy.mjs';
|
|
78
78
|
import { machineSnapshot } from './resources.mjs';
|
|
79
|
-
import { detectRuntimes } from './runtimes.mjs';
|
|
79
|
+
import { detectRuntimes, pickRuntimeFor, RUNTIMES } from './runtimes.mjs';
|
|
80
80
|
|
|
81
81
|
async function fetchRoster(haveIds) {
|
|
82
82
|
const url = new URL(FLEET_URL);
|
|
@@ -699,6 +699,14 @@ export async function runFleetDaemon() {
|
|
|
699
699
|
checkingPlans.add(job.id);
|
|
700
700
|
(async () => {
|
|
701
701
|
try {
|
|
702
|
+
// WHICH CLI answers a turn nobody @mentioned. Resolved per job rather
|
|
703
|
+
// than once at startup: a CLI can be installed while the daemon runs.
|
|
704
|
+
const planRt = pickRuntimeFor('consult');
|
|
705
|
+
if (!planRt) {
|
|
706
|
+
warn(`plan check for "${job.title}" skipped — no installed CLI can run a read-only turn`);
|
|
707
|
+
checkingPlans.delete(job.id);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
702
710
|
note(`${c.cyan('plan')} ${c.dim(`— checking "${job.title}" against your code…`)}`);
|
|
703
711
|
const out = await withWikiLock(async () => {
|
|
704
712
|
ensureWikiWorktree();
|
|
@@ -709,6 +717,7 @@ export async function runFleetDaemon() {
|
|
|
709
717
|
cwd: wikiWt,
|
|
710
718
|
// Reads the repo and reports JSON — it authors nothing either.
|
|
711
719
|
readOnly: true,
|
|
720
|
+
runtime: planRt,
|
|
712
721
|
label: c.cyan('[plan]'),
|
|
713
722
|
});
|
|
714
723
|
});
|
|
@@ -804,6 +813,8 @@ export async function runFleetDaemon() {
|
|
|
804
813
|
// Compare-and-set BEFORE spending a Claude turn: two lanes can wake on
|
|
805
814
|
// the same push, and running one instruction twice into one worktree
|
|
806
815
|
// is exactly the double-edit this is meant to avoid.
|
|
816
|
+
const quickRt = pickRuntimeFor('build');
|
|
817
|
+
if (!quickRt) return; // nothing here can edit code; leave the join unclaimed
|
|
807
818
|
const claim = await postForData(JOIN_TAKE_URL, { joinId: job.id });
|
|
808
819
|
if (!claim?.taken) return;
|
|
809
820
|
note(
|
|
@@ -821,6 +832,7 @@ export async function runFleetDaemon() {
|
|
|
821
832
|
resume: false,
|
|
822
833
|
system: SYSTEM_QUICK_EDIT,
|
|
823
834
|
cwd: target.wt,
|
|
835
|
+
runtime: quickRt,
|
|
824
836
|
// No MCP: a join records no run, claims nothing, completes nothing.
|
|
825
837
|
// Its only report is the one this daemon posts below.
|
|
826
838
|
label: c.cyan('[quick]'),
|
|
@@ -864,6 +876,15 @@ export async function runFleetDaemon() {
|
|
|
864
876
|
// Serialised against the wiki queue as well: that queue hard-resets
|
|
865
877
|
// this worktree mid-turn, which would pull the files out from under a
|
|
866
878
|
// consult that is reading them.
|
|
879
|
+
// A consult's prompt is steered by a question ANY project editor can
|
|
880
|
+
// type, so the profile is the enforcement and a runtime that cannot
|
|
881
|
+
// express it does not get the job. Today that means Claude; see the
|
|
882
|
+
// `profiles` notes in runtimes.mjs for exactly what Codex is missing.
|
|
883
|
+
const consultRt = pickRuntimeFor('consult');
|
|
884
|
+
if (!consultRt) {
|
|
885
|
+
warn('a consult is waiting, but no installed CLI can run a read-only turn');
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
867
888
|
await withWikiLock(async () => {
|
|
868
889
|
ensureWikiWorktree();
|
|
869
890
|
const out = await runTurn({
|
|
@@ -879,6 +900,7 @@ export async function runFleetDaemon() {
|
|
|
879
900
|
// not to change anything, but the prompt is what an injected
|
|
880
901
|
// question competes with; the toolset is what it cannot.
|
|
881
902
|
readOnly: true,
|
|
903
|
+
runtime: consultRt,
|
|
882
904
|
label: c.cyan('[ask]'),
|
|
883
905
|
});
|
|
884
906
|
const answer = (out || '').trim();
|
|
@@ -1296,14 +1318,24 @@ export async function runFleetDaemon() {
|
|
|
1296
1318
|
warn(`wiki vault sync failed: ${e.message} — pages stay local; next turn retries`);
|
|
1297
1319
|
}
|
|
1298
1320
|
};
|
|
1321
|
+
// The cartographer needs to read the repo and write ONLY the vault —
|
|
1322
|
+
// a narrower promise than "build", so it is its own profile.
|
|
1323
|
+
const wikiRt = pickRuntimeFor('wiki');
|
|
1324
|
+
if (!wikiRt) {
|
|
1325
|
+
warn('wiki generation skipped — no installed CLI can run a vault-scoped turn');
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
const wikiLabel = RUNTIMES[wikiRt].label;
|
|
1299
1329
|
if (task.type === 'sweep') {
|
|
1300
|
-
note(`${c.cyan('wiki')} ${c.dim(
|
|
1330
|
+
note(`${c.cyan('wiki')} ${c.dim(`— regenerating: your ${wikiLabel} is reading the repo…`)}`);
|
|
1301
1331
|
const out = await runTurn({
|
|
1302
1332
|
prompt: WIKI_KICKOFF(sha, vaultDir),
|
|
1303
1333
|
resume: false,
|
|
1304
1334
|
system: SYSTEM_WIKI(vaultDir),
|
|
1305
1335
|
cwd: wikiWt,
|
|
1306
1336
|
wikiPerm: true,
|
|
1337
|
+
vaultDir,
|
|
1338
|
+
runtime: wikiRt,
|
|
1307
1339
|
label: c.cyan('[wiki]'),
|
|
1308
1340
|
streamJson: true,
|
|
1309
1341
|
onActivity,
|
|
@@ -1346,6 +1378,8 @@ export async function runFleetDaemon() {
|
|
|
1346
1378
|
system: SYSTEM_REGROUND(vaultDir),
|
|
1347
1379
|
cwd: wikiWt,
|
|
1348
1380
|
wikiPerm: true,
|
|
1381
|
+
vaultDir,
|
|
1382
|
+
runtime: wikiRt,
|
|
1349
1383
|
label: c.cyan('[wiki]'),
|
|
1350
1384
|
streamJson: true,
|
|
1351
1385
|
onActivity,
|