flowviant 0.125.5 → 0.125.7
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 +4 -5
- package/bin/lib/agentInternalWait.mjs +1 -5
- package/bin/lib/agentPlan.mjs +13 -163
- package/bin/lib/agentRepositories.mjs +3 -1
- package/bin/lib/claudeInput.mjs +89 -10
- package/bin/lib/claudeMachineMcp.mjs +85 -0
- package/bin/lib/claudePersonal.mjs +6 -57
- package/bin/lib/claudePosture.mjs +6 -15
- package/bin/lib/claudeSettingsArg.mjs +67 -0
- package/bin/lib/claudeStream.mjs +30 -1
- package/bin/lib/claudeSubagentProgress.mjs +12 -28
- package/bin/lib/claudeSubagents.mjs +4 -2
- package/bin/lib/codexContract.mjs +52 -0
- package/bin/lib/codexFencedMount.mjs +275 -0
- package/bin/lib/codexPersonal.mjs +1 -265
- package/bin/lib/codexProjectRead.mjs +5 -4
- package/bin/lib/codexResearch.mjs +9 -3
- package/bin/lib/codexSubagentEvents.mjs +11 -7
- package/bin/lib/codexSubagents.mjs +55 -52
- package/bin/lib/codexToml.mjs +328 -0
- package/bin/lib/config.mjs +22 -1
- package/bin/lib/conversationIds.mjs +10 -0
- package/bin/lib/fleet.mjs +2 -2
- package/bin/lib/git.mjs +32 -0
- package/bin/lib/heldTool.mjs +49 -0
- package/bin/lib/hooks/mcpAllow.mjs +11 -5
- package/bin/lib/hooks/orchestrator.mjs +20 -10
- package/bin/lib/intakeDraft.mjs +1 -1
- package/bin/lib/jsonlTail.mjs +52 -0
- package/bin/lib/mcpInventory.mjs +11 -6
- package/bin/lib/mcpReasons.mjs +26 -0
- package/bin/lib/mcpReportBounds.mjs +10 -0
- package/bin/lib/mcpStdio.mjs +35 -0
- package/bin/lib/mergeBatch.mjs +6 -24
- package/bin/lib/mergeOrder.mjs +18 -0
- package/bin/lib/modelJson.mjs +173 -0
- package/bin/lib/nativeAgentTools.mjs +72 -0
- package/bin/lib/orchestrator.mjs +221 -133
- package/bin/lib/orchestratorClient.mjs +17 -21
- package/bin/lib/orchestratorHooks.mjs +21 -4
- package/bin/lib/orchestratorNative.mjs +58 -0
- package/bin/lib/orchestratorPrompt.mjs +7 -4
- package/bin/lib/orchestratorProtocol.mjs +25 -3
- package/bin/lib/postureTools.mjs +58 -13
- package/bin/lib/prWorkflow.mjs +13 -2
- package/bin/lib/processGroup.mjs +41 -0
- package/bin/lib/projectRepositories.mjs +2 -2
- package/bin/lib/projectToolRuntimes.mjs +92 -245
- package/bin/lib/projectTools.mjs +60 -19
- package/bin/lib/prompts.mjs +47 -18
- package/bin/lib/reportJournal.mjs +61 -0
- package/bin/lib/repositoryDelivery.mjs +6 -4
- package/bin/lib/repositoryMerge.mjs +8 -12
- package/bin/lib/repositoryPlaces.mjs +2 -2
- package/bin/lib/repositoryPrompts.mjs +42 -0
- package/bin/lib/repositoryTool.mjs +9 -32
- package/bin/lib/researchUse.mjs +39 -0
- package/bin/lib/resumeLost.mjs +21 -1
- package/bin/lib/resumeMarker.mjs +43 -0
- package/bin/lib/runTurn.mjs +55 -52
- package/bin/lib/runtimeAntigravity.mjs +1 -1
- package/bin/lib/runtimeCapabilities.mjs +13 -18
- package/bin/lib/runtimeClaude.mjs +68 -24
- package/bin/lib/runtimeCodex.mjs +12 -6
- package/bin/lib/runtimeEvents.mjs +13 -2
- package/bin/lib/runtimeOfferings.mjs +12 -5
- package/bin/lib/runtimes.mjs +3 -1
- package/bin/lib/startProject.mjs +10 -0
- package/bin/lib/subagentBounds.mjs +20 -0
- package/bin/lib/trace.mjs +30 -5
- package/bin/lib/turnDeclaration.mjs +1 -1
- package/bin/lib/turnDiagnostics.mjs +9 -12
- package/bin/lib/turnReceipts.mjs +64 -0
- package/bin/lib/turnSubagents.mjs +91 -0
- package/bin/lib/turnUsage.mjs +9 -6
- package/bin/lib/update.mjs +18 -4
- package/bin/lib/versionOrder.mjs +11 -1
- package/bin/lib/work.mjs +9 -9
- package/bin/lib/workAgentMerges.mjs +5 -7
- package/bin/lib/workAgentPrecheck.mjs +5 -5
- package/bin/lib/workAgentRecovery.mjs +2 -1
- package/bin/lib/workAgentResume.mjs +121 -0
- package/bin/lib/workAgentTools.mjs +111 -0
- package/bin/lib/workAgentTurnExecution.mjs +82 -246
- package/bin/lib/workAgentTurnOutcome.mjs +30 -3
- package/bin/lib/workAgentTurnReports.mjs +16 -0
- package/bin/lib/workAgentTurnShape.mjs +90 -0
- package/bin/lib/workAgentTurns.mjs +28 -12
- package/bin/lib/workArtifactReconcile.mjs +3 -2
- package/bin/lib/workAttachments.mjs +27 -5
- package/bin/lib/workMergeBatch.mjs +7 -5
- package/bin/lib/workNarration.mjs +32 -0
- package/bin/lib/workPlaceReports.mjs +3 -3
- package/bin/lib/workPlaces.mjs +14 -0
- package/bin/lib/workReportQueue.mjs +18 -1
- package/bin/lib/workSessionRuntime.mjs +2 -5
- package/bin/lib/workSessionTurns.mjs +68 -80
- package/bin/lib/workShip.mjs +8 -18
- package/bin/lib/workTurnShape.mjs +6 -4
- package/bin/lib/worktreeDiff.mjs +29 -5
- package/package.json +1 -1
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
* what the turn IS, plain, plan, capture or build, with its prompt pair
|
|
17
17
|
* (workTurnShape.mjs, audit 2026-10-10 F159).
|
|
18
18
|
*/
|
|
19
|
-
import { existsSync,
|
|
19
|
+
import { existsSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
20
20
|
import { git } from './git.mjs';
|
|
21
21
|
import { measureRepositoryBaseline } from './repositoryBaseline.mjs';
|
|
22
|
-
import { REPOSITORY_BASELINE_PARAGRAPH } from './prompts.mjs';
|
|
22
|
+
import { REPOSITORY_BASELINE_PARAGRAPH, tabFilesNote } from './prompts.mjs';
|
|
23
23
|
import { processStartTime } from './procRegistry.mjs';
|
|
24
24
|
import { c, note, ok, warn } from './ui.mjs';
|
|
25
25
|
import { mcpFor } from './claude.mjs';
|
|
@@ -31,13 +31,15 @@ import { isTerminalSessionLive } from './claudeSessions.mjs';
|
|
|
31
31
|
import { isAgyConversationLive } from './agySessions.mjs';
|
|
32
32
|
import { adoptFence } from './localSessions.mjs';
|
|
33
33
|
import { resumeRetriesFresh } from './resumeLost.mjs';
|
|
34
|
+
import { CODEX_CONTRACT_MARKER, codexContractKnown, recordCodexContract } from './codexContract.mjs';
|
|
34
35
|
import { turnLockedByLivePid } from './turnLock.mjs';
|
|
35
36
|
import { createDeliveryReporter } from './workDelivery.mjs';
|
|
36
37
|
import { createLiveMessages } from './workLiveMessages.mjs';
|
|
37
|
-
import { killGroup } from './processGroup.mjs';
|
|
38
|
+
import { interruptThenKill, killGroup } from './processGroup.mjs';
|
|
38
39
|
import { printable } from './printable.mjs';
|
|
39
40
|
import { brainFor } from './workBrain.mjs';
|
|
40
41
|
import { AGY_CONV_RE, CLAUDE_SESSION_RE, CODEX_THREAD_RE, RESUME_MARKERS } from './conversationIds.mjs';
|
|
42
|
+
import { pinId, readPinnedId } from './resumeMarker.mjs';
|
|
41
43
|
import { sessionTurnKickoff, sessionTurnShape, sessionTurnSystem } from './workTurnShape.mjs';
|
|
42
44
|
import { createToolLog } from './workToolLog.mjs';
|
|
43
45
|
import { createCommandAudit } from './workCommandAudit.mjs';
|
|
@@ -75,6 +77,9 @@ export function createWorkSessionTurns({
|
|
|
75
77
|
agyRegistryLookup,
|
|
76
78
|
carryDirtyState,
|
|
77
79
|
fetchAttachments,
|
|
80
|
+
// Each file landed or named as missed (workAttachments.mjs); a caller that
|
|
81
|
+
// hands only `fetchAttachments` (an older test) has its paths as landed.
|
|
82
|
+
fetchTabFiles = async (wt, attachments) => (await fetchAttachments(wt, attachments)).map((path) => ({ path })),
|
|
78
83
|
makeNarrator,
|
|
79
84
|
sayTurnDeferred,
|
|
80
85
|
lastDeferSaid,
|
|
@@ -584,48 +589,16 @@ export function createWorkSessionTurns({
|
|
|
584
589
|
* directory belongs to this tab alone, which is the one case it was
|
|
585
590
|
* ever right for.
|
|
586
591
|
*/
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
if (convMarker && existsSync(convMarker)) {
|
|
591
|
-
try {
|
|
592
|
-
const v = readFileSync(convMarker, 'utf8').trim();
|
|
593
|
-
if (CLAUDE_SESSION_RE.test(v)) claudeResumeId = v;
|
|
594
|
-
} catch {
|
|
595
|
-
/* unreadable marker — run fresh */
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
let codexResumeId = null;
|
|
600
|
-
if (rt.id === 'codex') {
|
|
601
|
-
const threadMarker = sessionMetaPath(dir.wt, RESUME_MARKERS.codex, job.sessionId);
|
|
602
|
-
if (threadMarker && existsSync(threadMarker)) {
|
|
603
|
-
try {
|
|
604
|
-
const v = readFileSync(threadMarker, 'utf8').trim();
|
|
605
|
-
if (CODEX_THREAD_RE.test(v)) codexResumeId = v;
|
|
606
|
-
} catch {
|
|
607
|
-
/* unreadable marker — run fresh */
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
592
|
+
// An unreadable or misshapen marker is unknown: run fresh (resumeMarker.mjs).
|
|
593
|
+
const claudeResumeId = rt.id === 'claude' ? readPinnedId(sessionMetaPath(dir.wt, RESUME_MARKERS.claude, job.sessionId), CLAUDE_SESSION_RE) : null;
|
|
594
|
+
const codexResumeId = rt.id === 'codex' ? readPinnedId(sessionMetaPath(dir.wt, RESUME_MARKERS.codex, job.sessionId), CODEX_THREAD_RE) : null;
|
|
611
595
|
// AGY RESUMES BY CONVERSATION ID, learned once and pinned beside the
|
|
612
596
|
// runtime marker: an adopted tab knows it from the adopt hint; a new
|
|
613
597
|
// tab learns it from agy's own cwd registry after its first turn.
|
|
614
598
|
// The marker beats `--continue` because it is the tab's OWN identity
|
|
615
599
|
// — the registry maps a cwd to whatever ran there LAST, and a
|
|
616
600
|
// dispatch sharing the machine could overwrite that between turns.
|
|
617
|
-
|
|
618
|
-
if (rt.id === 'antigravity' && !adopting) {
|
|
619
|
-
const convMarker = sessionMetaPath(dir.wt, RESUME_MARKERS.antigravity, job.sessionId);
|
|
620
|
-
if (convMarker && existsSync(convMarker)) {
|
|
621
|
-
try {
|
|
622
|
-
const v = readFileSync(convMarker, 'utf8').trim();
|
|
623
|
-
if (AGY_CONV_RE.test(v)) agyConvId = v;
|
|
624
|
-
} catch {
|
|
625
|
-
/* unreadable marker — run fresh */
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
601
|
+
const agyConvId = rt.id === 'antigravity' && !adopting ? readPinnedId(sessionMetaPath(dir.wt, RESUME_MARKERS.antigravity, job.sessionId), AGY_CONV_RE) : null;
|
|
629
602
|
// Resume iff a conversation is known to live in THIS directory. For
|
|
630
603
|
// Claude that proof is the server's sessionRef — only ever a path
|
|
631
604
|
// some turn actually SPOKE from (see the settle below), and it must
|
|
@@ -685,6 +658,17 @@ export function createWorkSessionTurns({
|
|
|
685
658
|
// refused for want of an approval, by `server.tool`.
|
|
686
659
|
let codexSpoke = false;
|
|
687
660
|
const approvalRefused = [];
|
|
661
|
+
// WHAT THE CLI SAID OF ITS OWN RUN (2026-10-19): a failed result or
|
|
662
|
+
// error event, or an exit that was not a clean one. A usage limit, a
|
|
663
|
+
// signed-out CLI or a crash printed its words and settled as the
|
|
664
|
+
// tab's REPLY; they settle as the failure they are now, in the same
|
|
665
|
+
// words. Reset for the fresh retry, which is its own run.
|
|
666
|
+
let cliFailed = false;
|
|
667
|
+
let badExit = null;
|
|
668
|
+
// A Codex tab's contract and whether this turn sent it (codexContract.mjs).
|
|
669
|
+
const contractPath = rt.id === 'codex' ? sessionMetaPath(dir.wt, CODEX_CONTRACT_MARKER, job.sessionId) : null;
|
|
670
|
+
let turnSystem = '';
|
|
671
|
+
let contractSent = true;
|
|
688
672
|
const spawned = []; // this turn's children, for the teardown registry
|
|
689
673
|
// THE TURN'S TOOL LOG — see workToolLog.mjs for its shape rules.
|
|
690
674
|
const turnLog = createToolLog(dir.wt);
|
|
@@ -706,22 +690,16 @@ export function createWorkSessionTurns({
|
|
|
706
690
|
// Files first, then the message that references them: the agent
|
|
707
691
|
// must be able to open what it is being told about. Only the ones
|
|
708
692
|
// that actually landed are named.
|
|
709
|
-
const
|
|
710
|
-
const filesNote = files.length
|
|
711
|
-
? `[FILES THE HUMAN ATTACHED TO THIS MESSAGE — already on disk in this worktree]\n${files
|
|
712
|
-
.map((f) => `- ${f}`)
|
|
713
|
-
.join('\n')}`
|
|
714
|
-
: '';
|
|
693
|
+
const filesNote = tabFilesNote(await fetchTabFiles(dir.wt, job.attachments));
|
|
715
694
|
const message = [job.body, filesNote, adoptNote, carryNote]
|
|
716
695
|
.filter(Boolean)
|
|
717
696
|
.join('\n\n');
|
|
718
697
|
const turnArgs = {
|
|
719
|
-
...(
|
|
698
|
+
...(shape.liveInput ? { liveMessages: (run.live = makeLiveMessages({
|
|
720
699
|
scope: 'session', turnId: job.id, attempt: job.attempt, cwd: dir.wt, sessionMetaPath,
|
|
721
700
|
interrupted: () => run.interrupted,
|
|
722
701
|
format: async (followup) => {
|
|
723
|
-
const
|
|
724
|
-
const note = paths.length ? `[FILES THE HUMAN ATTACHED TO THIS MESSAGE — already on disk in this worktree]\n${paths.map((f) => `- ${f}`).join('\n')}` : '';
|
|
702
|
+
const note = tabFilesNote(await fetchTabFiles(dir.wt, followup.attachments));
|
|
725
703
|
const words = shape.capture ? (followup.libraryPrompt ?? '') + followup.body : followup.body + (followup.libraryPrompt ?? '');
|
|
726
704
|
return sessionTurnKickoff(shape, { sessionId: job.sessionId, sessionName: job.sessionName,
|
|
727
705
|
askedByName: followup.askedByName, message: [words, note].filter(Boolean).join('\n\n') });
|
|
@@ -759,11 +737,18 @@ export function createWorkSessionTurns({
|
|
|
759
737
|
// artifacts paragraph, the planning sentence appended). The
|
|
760
738
|
// artifacts paragraph also waits on the server — ARTIFACTS
|
|
761
739
|
// (0.94.0): only while it can show one, read here at spawn.
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
740
|
+
// …and a Codex thread that already holds it is sent none
|
|
741
|
+
// (codexContract.mjs); `turnSystem` keeps the text for a
|
|
742
|
+
// fresh retry and the marker.
|
|
743
|
+
system: (() => {
|
|
744
|
+
turnSystem = sessionTurnSystem(shape, {
|
|
745
|
+
knowledgeDir,
|
|
746
|
+
artifactsAccepted: getArtifactsAccepted(),
|
|
747
|
+
runtime: rt.id,
|
|
748
|
+
}) + (toolLog.baseline ? '\n\n' + REPOSITORY_BASELINE_PARAGRAPH(toolLog.baseline) : '') + (artifactScan?.instructions ? `\n\n${artifactScan.instructions}` : '');
|
|
749
|
+
contractSent = !codexContractKnown(contractPath, codexResumeId, turnSystem);
|
|
750
|
+
return contractSent ? turnSystem : '';
|
|
751
|
+
})(),
|
|
767
752
|
// The directory is OUTSIDE a worktree's cwd; Claude Code is
|
|
768
753
|
// told it may read there (`--add-dir`) rather than left to
|
|
769
754
|
// refuse a read-only capture turn a path it was just handed.
|
|
@@ -805,6 +790,15 @@ export function createWorkSessionTurns({
|
|
|
805
790
|
onModel: (m) => {
|
|
806
791
|
model = m;
|
|
807
792
|
},
|
|
793
|
+
// The CLI's own context measure, live while the turn runs
|
|
794
|
+
// (claudeInput.mjs); the server clears it at settle.
|
|
795
|
+
onContext: (ctx) => narrator.context(ctx),
|
|
796
|
+
onCliFailure: () => {
|
|
797
|
+
cliFailed = true;
|
|
798
|
+
},
|
|
799
|
+
onExit: (code, signal) => {
|
|
800
|
+
badExit = signal || (code ? `exit ${code}` : null);
|
|
801
|
+
},
|
|
808
802
|
// What this CLI says it can be asked for by name, and which of
|
|
809
803
|
// its connectors need a sign-in, are harvested off this init by
|
|
810
804
|
// runTurn itself (`initHarvestFor`, runtimeCapabilities.mjs) —
|
|
@@ -815,10 +809,7 @@ export function createWorkSessionTurns({
|
|
|
815
809
|
// same conversation and recover its native message UUIDs.
|
|
816
810
|
if (typeof i.sessionId === 'string' && i.sessionId.trim()) {
|
|
817
811
|
seenClaudeSession = i.sessionId.trim();
|
|
818
|
-
if (CLAUDE_SESSION_RE.test(seenClaudeSession))
|
|
819
|
-
const marker = sessionMetaPath(dir.wt, RESUME_MARKERS.claude, job.sessionId);
|
|
820
|
-
if (marker) try { writeFileSync(marker, seenClaudeSession); } catch { /* best effort */ }
|
|
821
|
-
}
|
|
812
|
+
if (CLAUDE_SESSION_RE.test(seenClaudeSession)) pinId(sessionMetaPath(dir.wt, RESUME_MARKERS.claude, job.sessionId), CLAUDE_SESSION_RE, seenClaudeSession);
|
|
822
813
|
}
|
|
823
814
|
},
|
|
824
815
|
cwd: dir.wt,
|
|
@@ -838,7 +829,7 @@ export function createWorkSessionTurns({
|
|
|
838
829
|
},
|
|
839
830
|
onSpawn: (ch) => {
|
|
840
831
|
if (!ch) return;
|
|
841
|
-
if (
|
|
832
|
+
if (!shape.liveInput) reportDelivery('session', job, 'taken');
|
|
842
833
|
spawned.push(ch);
|
|
843
834
|
run.spawned.push(ch);
|
|
844
835
|
// Asked to stop between the check and the spawn.
|
|
@@ -905,9 +896,19 @@ export function createWorkSessionTurns({
|
|
|
905
896
|
*/
|
|
906
897
|
if (
|
|
907
898
|
!adopting &&
|
|
908
|
-
resumeRetriesFresh({
|
|
899
|
+
resumeRetriesFresh({
|
|
900
|
+
resume,
|
|
901
|
+
out,
|
|
902
|
+
runtime: rt.id,
|
|
903
|
+
// The CLI's own word that it found the conversation: Claude's
|
|
904
|
+
// init, Codex's `thread.started` naming the pinned thread.
|
|
905
|
+
sawInit: rt.id === 'codex' ? Boolean(codexResumeId && seenThreadId === codexResumeId) : Boolean(seenClaudeSession),
|
|
906
|
+
})
|
|
909
907
|
) {
|
|
910
|
-
|
|
908
|
+
cliFailed = false;
|
|
909
|
+
badExit = null;
|
|
910
|
+
contractSent = true;
|
|
911
|
+
out = await runCli({ ...turnArgs, resume: false, system: turnSystem });
|
|
911
912
|
}
|
|
912
913
|
} finally {
|
|
913
914
|
// The CLI has stopped printing, so stop relaying. The LINE itself
|
|
@@ -931,14 +932,8 @@ export function createWorkSessionTurns({
|
|
|
931
932
|
// `resume <id>` — and best-effort, like the runtime pin: an
|
|
932
933
|
// unwritable marker just means the tab runs fresh next turn.
|
|
933
934
|
if (rt.id === 'codex' && seenThreadId && CODEX_THREAD_RE.test(seenThreadId)) {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
try {
|
|
937
|
-
writeFileSync(threadMarker, seenThreadId);
|
|
938
|
-
} catch {
|
|
939
|
-
/* best-effort */
|
|
940
|
-
}
|
|
941
|
-
}
|
|
935
|
+
pinId(sessionMetaPath(dir.wt, RESUME_MARKERS.codex, job.sessionId), CODEX_THREAD_RE, seenThreadId);
|
|
936
|
+
recordCodexContract(contractPath, seenThreadId, turnSystem, contractSent);
|
|
942
937
|
}
|
|
943
938
|
/**
|
|
944
939
|
* A PERSON INTERRUPTED IT. Whatever the CLI printed was cut off, so
|
|
@@ -984,16 +979,8 @@ export function createWorkSessionTurns({
|
|
|
984
979
|
// the registry is never trusted again.
|
|
985
980
|
if (rt.id === 'antigravity' && answer.length > 0) {
|
|
986
981
|
const convMarker = sessionMetaPath(dir.wt, RESUME_MARKERS.antigravity, job.sessionId);
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
if (learned && AGY_CONV_RE.test(learned)) {
|
|
990
|
-
try {
|
|
991
|
-
writeFileSync(convMarker, learned);
|
|
992
|
-
} catch {
|
|
993
|
-
/* best-effort — an unpinned tab resumes via --continue's cwd key */
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
}
|
|
982
|
+
// Best-effort — an unpinned tab resumes via --continue's cwd key.
|
|
983
|
+
if (convMarker && !existsSync(convMarker)) pinId(convMarker, AGY_CONV_RE, adopting ? job.adopt.id : agyRegistryLookup(dir.wt));
|
|
997
984
|
}
|
|
998
985
|
/**
|
|
999
986
|
* NO WORDS, SAID AS MEASURED (2026-09-29). Claude's init event means
|
|
@@ -1025,7 +1012,7 @@ export function createWorkSessionTurns({
|
|
|
1025
1012
|
artifactFailure = `Artifact delivery is incomplete: ${error.message}`;
|
|
1026
1013
|
}
|
|
1027
1014
|
}
|
|
1028
|
-
const spoke = answer.length > 0 && !loginFailure;
|
|
1015
|
+
const spoke = answer.length > 0 && !loginFailure && !cliFailed && !badExit;
|
|
1029
1016
|
await settleTurn(job.id, {
|
|
1030
1017
|
...attempt,
|
|
1031
1018
|
ok: spoke && !artifactFailure,
|
|
@@ -1048,6 +1035,7 @@ export function createWorkSessionTurns({
|
|
|
1048
1035
|
});
|
|
1049
1036
|
if (spoke) ok(`${c.cyan('tab')} ${c.dim('— replied in the session')}`);
|
|
1050
1037
|
else if (loginFailure) warn('codex answered 401 for this session turn — settled as failed, in words');
|
|
1038
|
+
else if (answer && (cliFailed || badExit)) warn(`the CLI reported its turn failed${badExit ? ` (${badExit})` : ''} — settled as failed, in its words`);
|
|
1051
1039
|
else warn('session turn produced no output — settled as failed');
|
|
1052
1040
|
} catch (e) {
|
|
1053
1041
|
if (toolLog) toolLog.state = 'partial';
|
|
@@ -1123,7 +1111,7 @@ export function createWorkSessionTurns({
|
|
|
1123
1111
|
if (run) {
|
|
1124
1112
|
if (!run.interrupted) note(`${c.cyan('tab')} ${c.dim('— interrupting the running turn')}`);
|
|
1125
1113
|
run.interrupted = true;
|
|
1126
|
-
for (const ch of run.spawned)
|
|
1114
|
+
for (const ch of run.spawned) interruptThenKill(ch);
|
|
1127
1115
|
continue;
|
|
1128
1116
|
}
|
|
1129
1117
|
if (pendingWorkReports.has(id) || settledRecently(id) || notRunningSaid.has(id)) continue;
|
package/bin/lib/workShip.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/** Session branch shipping and settlement. */
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { git, baseBranchName, isSafePathSegment, sessionBranch
|
|
4
|
+
import { git, baseBranchName, isSafePathSegment, sessionBranch } from './git.mjs';
|
|
5
5
|
import { mergeOutward as shipMergeOutward } from './shipMerge.mjs';
|
|
6
6
|
import { gitMergeIn } from './gitMergeIdentity.mjs';
|
|
7
7
|
import { c, note, ok, warn } from './ui.mjs';
|
|
8
8
|
import { scrub as envScrub } from './uplinkScrub.mjs';
|
|
9
|
+
import { relayedText } from './prWorkflow.mjs';
|
|
9
10
|
import { REPO_PLACE } from './workPlaces.mjs';
|
|
10
11
|
import { placeTurnLockHolder } from './turnLock.mjs';
|
|
11
12
|
import { printable } from './printable.mjs';
|
|
@@ -272,15 +273,10 @@ export function createWorkShipper({
|
|
|
272
273
|
'conflicts with main — the tab is closed, so open a new session from this branch to resolve them, then ship again',
|
|
273
274
|
});
|
|
274
275
|
} else {
|
|
275
|
-
// Userinfo stripped as well as scrubbed (2026-10-10, F153)
|
|
276
|
-
// outward merge pushes to `origin`, and a push
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
String(detail)
|
|
280
|
-
.split('\n')
|
|
281
|
-
.find((l) => l.trim()) ?? 'git merge failed'
|
|
282
|
-
)
|
|
283
|
-
);
|
|
276
|
+
// Userinfo stripped as well as scrubbed (2026-10-10, F153), by
|
|
277
|
+
// relayedText: the outward merge pushes to `origin`, and a push
|
|
278
|
+
// names the remote.
|
|
279
|
+
const line = relayedText(String(detail).split('\n').find((l) => l.trim()) ?? 'git merge failed');
|
|
284
280
|
await done({ ok: false, error: `the merge failed: ${line.slice(0, 300)}` });
|
|
285
281
|
}
|
|
286
282
|
return;
|
|
@@ -331,13 +327,7 @@ export function createWorkShipper({
|
|
|
331
327
|
} else {
|
|
332
328
|
// An honest error beats a fabricated conflict — the human can
|
|
333
329
|
// only fix what they are told about.
|
|
334
|
-
const line =
|
|
335
|
-
stripUrlUserinfo(
|
|
336
|
-
String(detail)
|
|
337
|
-
.split('\n')
|
|
338
|
-
.find((l) => l.trim()) ?? 'git merge failed'
|
|
339
|
-
)
|
|
340
|
-
);
|
|
330
|
+
const line = relayedText(String(detail).split('\n').find((l) => l.trim()) ?? 'git merge failed');
|
|
341
331
|
await done({ ok: false, error: `the merge failed: ${line.slice(0, 300)}` });
|
|
342
332
|
}
|
|
343
333
|
return;
|
|
@@ -368,7 +358,7 @@ export function createWorkShipper({
|
|
|
368
358
|
warn(`ship failed: ${e?.message ?? e}`);
|
|
369
359
|
await done({
|
|
370
360
|
ok: false,
|
|
371
|
-
error:
|
|
361
|
+
error: relayedText(e?.message ?? 'the merge failed').slice(0, 500),
|
|
372
362
|
});
|
|
373
363
|
} finally {
|
|
374
364
|
if (!settled && !deferred) {
|
|
@@ -15,8 +15,8 @@ import { discoveryGuide } from './discoveryContract.mjs';
|
|
|
15
15
|
* other answer off it; the prompt pair is composed here too.
|
|
16
16
|
*
|
|
17
17
|
* The runtime facts come off the row (runtime{Claude,Codex,Antigravity}.mjs):
|
|
18
|
-
* `tab` ('plain' runs no MCP), `planTurns` (the CLI can spell plan mode)
|
|
19
|
-
* `adopt` ('fork' | 'move' | null). The LIFECYCLE stays the lane's — its
|
|
18
|
+
* `tab` ('plain' runs no MCP), `planTurns` (the CLI can spell plan mode),
|
|
19
|
+
* `adopt` ('fork' | 'move' | null) and `liveInput` (follow-ups on stdin). The LIFECYCLE stays the lane's — its
|
|
20
20
|
* refusal order is the contract — and only this composition moved.
|
|
21
21
|
*/
|
|
22
22
|
import { runtimeById } from './runtimes.mjs';
|
|
@@ -55,7 +55,7 @@ export const PLAN_TURN_SENTENCE =
|
|
|
55
55
|
* verdict that this first turn adopts a terminal session.
|
|
56
56
|
*
|
|
57
57
|
* Returns `{ refusal?, plain, plan, capture, kickoff, systemBase,
|
|
58
|
-
* planSentence, artifacts, profile, mountMcp, display }`:
|
|
58
|
+
* planSentence, artifacts, profile, mountMcp, display, liveInput }`:
|
|
59
59
|
* refusal — the sentence the turn settles in, unrun, when it has one;
|
|
60
60
|
* kickoff /
|
|
61
61
|
* systemBase — which contract the prompt pair runs: 'plain' | 'capture' | 'work';
|
|
@@ -65,7 +65,8 @@ export const PLAN_TURN_SENTENCE =
|
|
|
65
65
|
* one, as the lane reads it at spawn);
|
|
66
66
|
* profile — the permission profile: 'build' | 'plan' | 'plan-mode';
|
|
67
67
|
* mountMcp — the session's MCP config is mounted;
|
|
68
|
-
* display — the CLI keeps its environment's display
|
|
68
|
+
* display — the CLI keeps its environment's display;
|
|
69
|
+
* liveInput — follow-ups reach the running CLI on stdin.
|
|
69
70
|
*/
|
|
70
71
|
export function sessionTurnShape({ runtime, adopting = false, job = null }) {
|
|
71
72
|
const row = runtimeById(runtime);
|
|
@@ -137,6 +138,7 @@ export function sessionTurnShape({ runtime, adopting = false, job = null }) {
|
|
|
137
138
|
// only reads and stages cards, and the person talking to it may be on a
|
|
138
139
|
// phone (noWindowEnv.mjs).
|
|
139
140
|
display: !isCapture,
|
|
141
|
+
liveInput: row?.liveInput === true,
|
|
140
142
|
};
|
|
141
143
|
}
|
|
142
144
|
|
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.7",
|
|
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": {
|