flowviant 0.125.6 → 0.125.9
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/README.md +84 -6
- package/bin/cli.mjs +7 -1
- package/bin/lib/activityFeed.mjs +69 -0
- package/bin/lib/agentMergeReports.mjs +46 -0
- package/bin/lib/agentMergeResolution.mjs +44 -0
- package/bin/lib/agentPlan.mjs +6 -2
- package/bin/lib/claudeInput.mjs +61 -1
- package/bin/lib/claudePosture.mjs +21 -0
- package/bin/lib/claudeStream.mjs +54 -4
- package/bin/lib/codexContract.mjs +52 -0
- package/bin/lib/codexResearch.mjs +33 -5
- package/bin/lib/commandSpecs.mjs +8 -5
- package/bin/lib/config.mjs +22 -1
- package/bin/lib/daemonJournal.mjs +102 -0
- package/bin/lib/daemonLogging.mjs +22 -18
- package/bin/lib/fleet.mjs +16 -6
- package/bin/lib/fleetReports.mjs +12 -5
- package/bin/lib/hooks/orchestrator.mjs +3 -1
- package/bin/lib/logReplay.mjs +133 -0
- package/bin/lib/mcpInventory.mjs +2 -1
- package/bin/lib/mcpReasons.mjs +6 -0
- package/bin/lib/nativeAgentTools.mjs +20 -3
- package/bin/lib/observedLines.mjs +63 -0
- package/bin/lib/orchestrator.mjs +49 -7
- package/bin/lib/orchestratorNative.mjs +133 -3
- package/bin/lib/orchestratorPrompt.mjs +50 -4
- package/bin/lib/postureTools.mjs +14 -6
- package/bin/lib/printable.mjs +1 -1
- package/bin/lib/processGroup.mjs +41 -0
- package/bin/lib/projectTools.mjs +3 -2
- package/bin/lib/prompts.mjs +47 -18
- package/bin/lib/reportJournal.mjs +61 -0
- package/bin/lib/repositoryMerge.mjs +1 -1
- package/bin/lib/researchHandoff.mjs +144 -0
- package/bin/lib/researchHandoffText.mjs +3 -0
- package/bin/lib/researchKnowledge.mjs +60 -0
- package/bin/lib/researchRestoration.mjs +38 -0
- package/bin/lib/resumeLost.mjs +21 -1
- package/bin/lib/runTurn.mjs +98 -44
- package/bin/lib/runtimeCapabilities.mjs +7 -2
- package/bin/lib/runtimeClaude.mjs +30 -8
- package/bin/lib/runtimeCodex.mjs +54 -22
- package/bin/lib/runtimeEvents.mjs +17 -7
- package/bin/lib/runtimeOfferings.mjs +3 -1
- package/bin/lib/startProject.mjs +10 -0
- package/bin/lib/turnDiagnostics.mjs +2 -1
- package/bin/lib/turnOutput.mjs +63 -0
- package/bin/lib/turnRecord.mjs +11 -0
- package/bin/lib/turnSubagents.mjs +13 -3
- package/bin/lib/ui.mjs +2 -2
- package/bin/lib/update.mjs +18 -4
- package/bin/lib/uplinkScrub.mjs +49 -0
- package/bin/lib/views.mjs +49 -58
- package/bin/lib/wikiRunner.mjs +2 -2
- package/bin/lib/work.mjs +5 -5
- package/bin/lib/workActivity.mjs +65 -0
- package/bin/lib/workAgentBriefRunner.mjs +2 -2
- package/bin/lib/workAgentMerges.mjs +57 -52
- package/bin/lib/workAgentPlans.mjs +76 -27
- package/bin/lib/workAgentPrecheck.mjs +1 -1
- package/bin/lib/workAgentTurnExecution.mjs +62 -8
- package/bin/lib/workAgentTurnReports.mjs +33 -3
- package/bin/lib/workAgentTurnShape.mjs +6 -4
- package/bin/lib/workAgentTurns.mjs +12 -8
- package/bin/lib/workAttachments.mjs +27 -5
- package/bin/lib/workIntake.mjs +8 -2
- package/bin/lib/workMergeBatch.mjs +7 -1
- package/bin/lib/workNarration.mjs +32 -0
- package/bin/lib/workPlanRecovery.mjs +50 -0
- package/bin/lib/workReportQueue.mjs +18 -1
- package/bin/lib/workSessionTurns.mjs +64 -22
- package/bin/lib/worktreeDiff.mjs +29 -5
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sessionWork, reportWorkOutcome } from './workActivity.mjs';
|
|
1
2
|
/**
|
|
2
3
|
* A WORKBENCH TAB'S TURN — offered, admitted, placed, adopted, pinned to its
|
|
3
4
|
* CLI, credentialed, resumed, run, persisted and settled.
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
import { existsSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
20
21
|
import { git } from './git.mjs';
|
|
21
22
|
import { measureRepositoryBaseline } from './repositoryBaseline.mjs';
|
|
22
|
-
import { REPOSITORY_BASELINE_PARAGRAPH } from './prompts.mjs';
|
|
23
|
+
import { REPOSITORY_BASELINE_PARAGRAPH, tabFilesNote } from './prompts.mjs';
|
|
23
24
|
import { processStartTime } from './procRegistry.mjs';
|
|
24
25
|
import { c, note, ok, warn } from './ui.mjs';
|
|
25
26
|
import { mcpFor } from './claude.mjs';
|
|
@@ -31,10 +32,11 @@ import { isTerminalSessionLive } from './claudeSessions.mjs';
|
|
|
31
32
|
import { isAgyConversationLive } from './agySessions.mjs';
|
|
32
33
|
import { adoptFence } from './localSessions.mjs';
|
|
33
34
|
import { resumeRetriesFresh } from './resumeLost.mjs';
|
|
35
|
+
import { CODEX_CONTRACT_MARKER, codexContractKnown, recordCodexContract } from './codexContract.mjs';
|
|
34
36
|
import { turnLockedByLivePid } from './turnLock.mjs';
|
|
35
37
|
import { createDeliveryReporter } from './workDelivery.mjs';
|
|
36
38
|
import { createLiveMessages } from './workLiveMessages.mjs';
|
|
37
|
-
import { killGroup } from './processGroup.mjs';
|
|
39
|
+
import { interruptThenKill, killGroup } from './processGroup.mjs';
|
|
38
40
|
import { printable } from './printable.mjs';
|
|
39
41
|
import { brainFor } from './workBrain.mjs';
|
|
40
42
|
import { AGY_CONV_RE, CLAUDE_SESSION_RE, CODEX_THREAD_RE, RESUME_MARKERS } from './conversationIds.mjs';
|
|
@@ -76,6 +78,9 @@ export function createWorkSessionTurns({
|
|
|
76
78
|
agyRegistryLookup,
|
|
77
79
|
carryDirtyState,
|
|
78
80
|
fetchAttachments,
|
|
81
|
+
// Each file landed or named as missed (workAttachments.mjs); a caller that
|
|
82
|
+
// hands only `fetchAttachments` (an older test) has its paths as landed.
|
|
83
|
+
fetchTabFiles = async (wt, attachments) => (await fetchAttachments(wt, attachments)).map((path) => ({ path })),
|
|
79
84
|
makeNarrator,
|
|
80
85
|
sayTurnDeferred,
|
|
81
86
|
lastDeferSaid,
|
|
@@ -145,6 +150,8 @@ export function createWorkSessionTurns({
|
|
|
145
150
|
return settledHere.has(turnId);
|
|
146
151
|
};
|
|
147
152
|
const settleTurn = (turnId, payload) => {
|
|
153
|
+
const run = runs.get(turnId);
|
|
154
|
+
if (run?.job) reportWorkOutcome(sessionWork(run.job), { ...payload, outcome: payload.ok ? 'replied' : 'nothing' });
|
|
148
155
|
const at = now();
|
|
149
156
|
settledHere.delete(turnId); // re-inserted at the end: the map stays in time order
|
|
150
157
|
settledHere.set(turnId, at);
|
|
@@ -219,7 +226,7 @@ export function createWorkSessionTurns({
|
|
|
219
226
|
const releaseSlot = admit.reserve();
|
|
220
227
|
lastDeferSaid.delete(job.sessionId);
|
|
221
228
|
workAnswering.add(job.id);
|
|
222
|
-
const run = { interrupted: false, spawned: [], sessionId: job.sessionId, live: null };
|
|
229
|
+
const run = { interrupted: false, spawned: [], sessionId: job.sessionId, live: null, job };
|
|
223
230
|
runs.set(job.id, run);
|
|
224
231
|
/** The execution generation the server handed, echoed on every settle
|
|
225
232
|
* so the server can tell this execution's answer from an older one's. */
|
|
@@ -654,6 +661,17 @@ export function createWorkSessionTurns({
|
|
|
654
661
|
// refused for want of an approval, by `server.tool`.
|
|
655
662
|
let codexSpoke = false;
|
|
656
663
|
const approvalRefused = [];
|
|
664
|
+
// WHAT THE CLI SAID OF ITS OWN RUN (2026-10-19): a failed result or
|
|
665
|
+
// error event, or an exit that was not a clean one. A usage limit, a
|
|
666
|
+
// signed-out CLI or a crash printed its words and settled as the
|
|
667
|
+
// tab's REPLY; they settle as the failure they are now, in the same
|
|
668
|
+
// words. Reset for the fresh retry, which is its own run.
|
|
669
|
+
let cliFailed = false;
|
|
670
|
+
let badExit = null;
|
|
671
|
+
// A Codex tab's contract and whether this turn sent it (codexContract.mjs).
|
|
672
|
+
const contractPath = rt.id === 'codex' ? sessionMetaPath(dir.wt, CODEX_CONTRACT_MARKER, job.sessionId) : null;
|
|
673
|
+
let turnSystem = '';
|
|
674
|
+
let contractSent = true;
|
|
657
675
|
const spawned = []; // this turn's children, for the teardown registry
|
|
658
676
|
// THE TURN'S TOOL LOG — see workToolLog.mjs for its shape rules.
|
|
659
677
|
const turnLog = createToolLog(dir.wt);
|
|
@@ -675,12 +693,7 @@ export function createWorkSessionTurns({
|
|
|
675
693
|
// Files first, then the message that references them: the agent
|
|
676
694
|
// must be able to open what it is being told about. Only the ones
|
|
677
695
|
// that actually landed are named.
|
|
678
|
-
const
|
|
679
|
-
const filesNote = files.length
|
|
680
|
-
? `[FILES THE HUMAN ATTACHED TO THIS MESSAGE — already on disk in this worktree]\n${files
|
|
681
|
-
.map((f) => `- ${f}`)
|
|
682
|
-
.join('\n')}`
|
|
683
|
-
: '';
|
|
696
|
+
const filesNote = tabFilesNote(await fetchTabFiles(dir.wt, job.attachments));
|
|
684
697
|
const message = [job.body, filesNote, adoptNote, carryNote]
|
|
685
698
|
.filter(Boolean)
|
|
686
699
|
.join('\n\n');
|
|
@@ -689,8 +702,7 @@ export function createWorkSessionTurns({
|
|
|
689
702
|
scope: 'session', turnId: job.id, attempt: job.attempt, cwd: dir.wt, sessionMetaPath,
|
|
690
703
|
interrupted: () => run.interrupted,
|
|
691
704
|
format: async (followup) => {
|
|
692
|
-
const
|
|
693
|
-
const note = paths.length ? `[FILES THE HUMAN ATTACHED TO THIS MESSAGE — already on disk in this worktree]\n${paths.map((f) => `- ${f}`).join('\n')}` : '';
|
|
705
|
+
const note = tabFilesNote(await fetchTabFiles(dir.wt, followup.attachments));
|
|
694
706
|
const words = shape.capture ? (followup.libraryPrompt ?? '') + followup.body : followup.body + (followup.libraryPrompt ?? '');
|
|
695
707
|
return sessionTurnKickoff(shape, { sessionId: job.sessionId, sessionName: job.sessionName,
|
|
696
708
|
askedByName: followup.askedByName, message: [words, note].filter(Boolean).join('\n\n') });
|
|
@@ -728,11 +740,18 @@ export function createWorkSessionTurns({
|
|
|
728
740
|
// artifacts paragraph, the planning sentence appended). The
|
|
729
741
|
// artifacts paragraph also waits on the server — ARTIFACTS
|
|
730
742
|
// (0.94.0): only while it can show one, read here at spawn.
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
743
|
+
// …and a Codex thread that already holds it is sent none
|
|
744
|
+
// (codexContract.mjs); `turnSystem` keeps the text for a
|
|
745
|
+
// fresh retry and the marker.
|
|
746
|
+
system: (() => {
|
|
747
|
+
turnSystem = sessionTurnSystem(shape, {
|
|
748
|
+
knowledgeDir,
|
|
749
|
+
artifactsAccepted: getArtifactsAccepted(),
|
|
750
|
+
runtime: rt.id,
|
|
751
|
+
}) + (toolLog.baseline ? '\n\n' + REPOSITORY_BASELINE_PARAGRAPH(toolLog.baseline) : '') + (artifactScan?.instructions ? `\n\n${artifactScan.instructions}` : '');
|
|
752
|
+
contractSent = !codexContractKnown(contractPath, codexResumeId, turnSystem);
|
|
753
|
+
return contractSent ? turnSystem : '';
|
|
754
|
+
})(),
|
|
736
755
|
// The directory is OUTSIDE a worktree's cwd; Claude Code is
|
|
737
756
|
// told it may read there (`--add-dir`) rather than left to
|
|
738
757
|
// refuse a read-only capture turn a path it was just handed.
|
|
@@ -774,6 +793,15 @@ export function createWorkSessionTurns({
|
|
|
774
793
|
onModel: (m) => {
|
|
775
794
|
model = m;
|
|
776
795
|
},
|
|
796
|
+
// The CLI's own context measure, live while the turn runs
|
|
797
|
+
// (claudeInput.mjs); the server clears it at settle.
|
|
798
|
+
onContext: (ctx) => narrator.context(ctx),
|
|
799
|
+
onCliFailure: () => {
|
|
800
|
+
cliFailed = true;
|
|
801
|
+
},
|
|
802
|
+
onExit: (code, signal) => {
|
|
803
|
+
badExit = signal || (code ? `exit ${code}` : null);
|
|
804
|
+
},
|
|
777
805
|
// What this CLI says it can be asked for by name, and which of
|
|
778
806
|
// its connectors need a sign-in, are harvested off this init by
|
|
779
807
|
// runTurn itself (`initHarvestFor`, runtimeCapabilities.mjs) —
|
|
@@ -794,7 +822,7 @@ export function createWorkSessionTurns({
|
|
|
794
822
|
// chat does not (workTurnShape.mjs).
|
|
795
823
|
display: shape.display,
|
|
796
824
|
runtime: rt.id,
|
|
797
|
-
|
|
825
|
+
work: sessionWork(job),
|
|
798
826
|
// Only codex announces one (thread.started); held here so the id
|
|
799
827
|
// this turn actually SPOKE under is what gets persisted after it
|
|
800
828
|
// ends. Last write wins on purpose: a failed resume that fell
|
|
@@ -871,9 +899,19 @@ export function createWorkSessionTurns({
|
|
|
871
899
|
*/
|
|
872
900
|
if (
|
|
873
901
|
!adopting &&
|
|
874
|
-
resumeRetriesFresh({
|
|
902
|
+
resumeRetriesFresh({
|
|
903
|
+
resume,
|
|
904
|
+
out,
|
|
905
|
+
runtime: rt.id,
|
|
906
|
+
// The CLI's own word that it found the conversation: Claude's
|
|
907
|
+
// init, Codex's `thread.started` naming the pinned thread.
|
|
908
|
+
sawInit: rt.id === 'codex' ? Boolean(codexResumeId && seenThreadId === codexResumeId) : Boolean(seenClaudeSession),
|
|
909
|
+
})
|
|
875
910
|
) {
|
|
876
|
-
|
|
911
|
+
cliFailed = false;
|
|
912
|
+
badExit = null;
|
|
913
|
+
contractSent = true;
|
|
914
|
+
out = await runCli({ ...turnArgs, resume: false, system: turnSystem });
|
|
877
915
|
}
|
|
878
916
|
} finally {
|
|
879
917
|
// The CLI has stopped printing, so stop relaying. The LINE itself
|
|
@@ -896,7 +934,10 @@ export function createWorkSessionTurns({
|
|
|
896
934
|
// before it ever touches disk — it later rides in argv as
|
|
897
935
|
// `resume <id>` — and best-effort, like the runtime pin: an
|
|
898
936
|
// unwritable marker just means the tab runs fresh next turn.
|
|
899
|
-
if (rt.id === 'codex' && seenThreadId && CODEX_THREAD_RE.test(seenThreadId))
|
|
937
|
+
if (rt.id === 'codex' && seenThreadId && CODEX_THREAD_RE.test(seenThreadId)) {
|
|
938
|
+
pinId(sessionMetaPath(dir.wt, RESUME_MARKERS.codex, job.sessionId), CODEX_THREAD_RE, seenThreadId);
|
|
939
|
+
recordCodexContract(contractPath, seenThreadId, turnSystem, contractSent);
|
|
940
|
+
}
|
|
900
941
|
/**
|
|
901
942
|
* A PERSON INTERRUPTED IT. Whatever the CLI printed was cut off, so
|
|
902
943
|
* it is not an answer: the turn settles `interrupted`, with what it
|
|
@@ -974,7 +1015,7 @@ export function createWorkSessionTurns({
|
|
|
974
1015
|
artifactFailure = `Artifact delivery is incomplete: ${error.message}`;
|
|
975
1016
|
}
|
|
976
1017
|
}
|
|
977
|
-
const spoke = answer.length > 0 && !loginFailure;
|
|
1018
|
+
const spoke = answer.length > 0 && !loginFailure && !cliFailed && !badExit;
|
|
978
1019
|
await settleTurn(job.id, {
|
|
979
1020
|
...attempt,
|
|
980
1021
|
ok: spoke && !artifactFailure,
|
|
@@ -997,6 +1038,7 @@ export function createWorkSessionTurns({
|
|
|
997
1038
|
});
|
|
998
1039
|
if (spoke) ok(`${c.cyan('tab')} ${c.dim('— replied in the session')}`);
|
|
999
1040
|
else if (loginFailure) warn('codex answered 401 for this session turn — settled as failed, in words');
|
|
1041
|
+
else if (answer && (cliFailed || badExit)) warn(`the CLI reported its turn failed${badExit ? ` (${badExit})` : ''} — settled as failed, in its words`);
|
|
1000
1042
|
else warn('session turn produced no output — settled as failed');
|
|
1001
1043
|
} catch (e) {
|
|
1002
1044
|
if (toolLog) toolLog.state = 'partial';
|
|
@@ -1072,7 +1114,7 @@ export function createWorkSessionTurns({
|
|
|
1072
1114
|
if (run) {
|
|
1073
1115
|
if (!run.interrupted) note(`${c.cyan('tab')} ${c.dim('— interrupting the running turn')}`);
|
|
1074
1116
|
run.interrupted = true;
|
|
1075
|
-
for (const ch of run.spawned)
|
|
1117
|
+
for (const ch of run.spawned) interruptThenKill(ch);
|
|
1076
1118
|
continue;
|
|
1077
1119
|
}
|
|
1078
1120
|
if (pendingWorkReports.has(id) || settledRecently(id) || notRunningSaid.has(id)) continue;
|
package/bin/lib/worktreeDiff.mjs
CHANGED
|
@@ -191,14 +191,38 @@ function branchCommits(wt, base) {
|
|
|
191
191
|
.split('\n')
|
|
192
192
|
.filter(Boolean)
|
|
193
193
|
);
|
|
194
|
+
// EVERY COMMIT ON THE BRANCH, NOT ONLY THE CARDED ONES (2026-10-19). A
|
|
195
|
+
// Terminal tab's prompt says a commit for no named card needs no trailer,
|
|
196
|
+
// so its own commits were never reported and the rail said "N commits
|
|
197
|
+
// ahead" with none of them listed or openable. One more exec names the
|
|
198
|
+
// rest — sha, author, date and the folded subject, NUL-separated (git
|
|
199
|
+
// allows a NUL in none of them) and one per line (`%s` folds its
|
|
200
|
+
// newlines), keyed by sha from the rev-list set with the first line
|
|
201
|
+
// winning, the same membership rule the trailered read keeps. No body and
|
|
202
|
+
// no numstat: the rail lists a commit by its subject, and its patch is the
|
|
203
|
+
// commit-diff job's.
|
|
204
|
+
const listed = new Map();
|
|
205
|
+
for (const line of git(['log', '--no-merges', '-n', String(MAX_COMMITS), '--format=%H%x00%an%x00%aI%x00%s', `${base}..HEAD`], wt).split('\n')) {
|
|
206
|
+
const [sha, author, at, subject] = line.split('\0');
|
|
207
|
+
if (real.includes(sha) && !listed.has(sha)) listed.set(sha, { author, at, subject });
|
|
208
|
+
}
|
|
194
209
|
for (const sha of real) {
|
|
195
|
-
if (!named.has(sha)) continue;
|
|
196
210
|
// A commit we cannot stat (a numstat past the pipe's buffer) still names
|
|
197
211
|
// its cards — send it with zero counts rather than drop it, as before.
|
|
198
|
-
const rec = commitRecord(sha, wt, { numstat: true }) ?? commitRecord(sha, wt);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
212
|
+
const rec = named.has(sha) ? commitRecord(sha, wt, { numstat: true }) ?? commitRecord(sha, wt) : null;
|
|
213
|
+
const taskIds = rec ? taskIdsFromMessage(stripDelims(rec.body)) : [];
|
|
214
|
+
if (taskIds.length === 0) {
|
|
215
|
+
const row = listed.get(sha);
|
|
216
|
+
if (!row) continue;
|
|
217
|
+
out.push({
|
|
218
|
+
sha: sha.slice(0, 64),
|
|
219
|
+
subject: stripDelims(row.subject).slice(0, 200),
|
|
220
|
+
author: stripDelims(row.author).slice(0, 80),
|
|
221
|
+
at: stripDelims(row.at).slice(0, 40),
|
|
222
|
+
taskIds: [],
|
|
223
|
+
});
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
202
226
|
out.push({
|
|
203
227
|
// Clamped to the server's zod caps, same rule as everything else in
|
|
204
228
|
// this file: one over-cap string 400s the whole batch.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.125.
|
|
3
|
+
"version": "0.125.9",
|
|
4
4
|
"description": "Run your own coding CLIs as build agents for Flowviant \u2014 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": {
|