flowviant 0.125.0 → 0.125.2
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/cli.mjs +10 -0
- package/bin/lib/agentAsk.mjs +6 -12
- package/bin/lib/agentCards.mjs +5 -30
- package/bin/lib/agentFiles.mjs +1 -1
- package/bin/lib/agentFootprint.mjs +5 -5
- package/bin/lib/agentPlan.mjs +16 -27
- package/bin/lib/cardThread.mjs +1 -1
- package/bin/lib/claudeStream.mjs +18 -7
- package/bin/lib/claudeSubagentProgress.mjs +62 -0
- package/bin/lib/claudeSubagents.mjs +77 -0
- package/bin/lib/codexRelay.mjs +6 -22
- package/bin/lib/codexSubagentEvents.mjs +34 -0
- package/bin/lib/codexSubagents.mjs +232 -0
- package/bin/lib/hooks/orchestrator.mjs +22 -0
- package/bin/lib/orchestrator.mjs +295 -0
- package/bin/lib/orchestratorClient.mjs +30 -0
- package/bin/lib/orchestratorHooks.mjs +15 -0
- package/bin/lib/orchestratorPrompt.mjs +19 -0
- package/bin/lib/orchestratorProtocol.mjs +15 -0
- package/bin/lib/promptTransport.mjs +34 -0
- package/bin/lib/runTurn.mjs +87 -14
- package/bin/lib/runtimeClaude.mjs +3 -1
- package/bin/lib/runtimeCodex.mjs +32 -11
- package/bin/lib/runtimeEvents.mjs +12 -1
- package/bin/lib/trace.mjs +48 -10
- package/bin/lib/turnDiagnostics.mjs +13 -5
- package/bin/lib/work.mjs +2 -1
- package/bin/lib/workAgentTurnExecution.mjs +84 -7
- package/bin/lib/workAgentTurnOutcome.mjs +8 -8
- package/bin/lib/workAgentTurns.mjs +2 -0
- package/bin/lib/workChildren.mjs +4 -0
- package/bin/lib/workLiveMessages.mjs +7 -1
- package/bin/lib/workSessionTurns.mjs +3 -3
- package/package.json +2 -2
|
@@ -30,7 +30,7 @@ import { AGENT_TASK_KINDS, agentTaskKindOf, unknownAgentTaskKind } from './agent
|
|
|
30
30
|
import { knowledgeDirFor } from './knowledgeFiles.mjs';
|
|
31
31
|
import { scrub as envScrub } from './uplinkScrub.mjs';
|
|
32
32
|
import { canRun, runtimeById } from './runtimes.mjs';
|
|
33
|
-
import { toolEventOf, CLAUDE_TOOL_PROSE_KINDS } from './runtimeEvents.mjs';
|
|
33
|
+
import { toolEventOf, CLAUDE_TOOL_PROSE_KINDS, scrubNativeText } from './runtimeEvents.mjs';
|
|
34
34
|
import { makeTraceRelay } from './trace.mjs';
|
|
35
35
|
import { mcpJsonServerNamesAbove, readBaseTools, prepareAgentTools } from './projectTools.mjs';
|
|
36
36
|
import { TURN_PROFILES } from './turnProfile.mjs';
|
|
@@ -47,8 +47,11 @@ import { agentBrowserHome } from './noWindowEnv.mjs';
|
|
|
47
47
|
import { CLAUDE_SESSION_RE, RESUME_MARKERS } from './conversationIds.mjs';
|
|
48
48
|
import { createDeliveryReporter } from './workDelivery.mjs';
|
|
49
49
|
import { createLiveMessages } from './workLiveMessages.mjs';
|
|
50
|
+
import { createOrchestrator } from './orchestrator.mjs';
|
|
51
|
+
import { ORCHESTRATOR_CONTRACT } from './orchestratorPrompt.mjs';
|
|
50
52
|
|
|
51
53
|
export function createAgentTurnExecution({
|
|
54
|
+
admit,
|
|
52
55
|
agentRepositories,
|
|
53
56
|
makeBrief,
|
|
54
57
|
baseRef,
|
|
@@ -395,8 +398,12 @@ export function createAgentTurnExecution({
|
|
|
395
398
|
agentId,
|
|
396
399
|
turnId,
|
|
397
400
|
post: postAgentTrace,
|
|
401
|
+
orchestrationVersion: job.orchestrationVersion,
|
|
398
402
|
scrub: envScrub,
|
|
399
403
|
});
|
|
404
|
+
// A live Send can choose its path as soon as the CLI starts, including a
|
|
405
|
+
// silent solo turn. Do not leave a two-second first-flush race at spawn.
|
|
406
|
+
if (job.orchestrationVersion === 2) await trace.flush(1_000);
|
|
400
407
|
/**
|
|
401
408
|
* Prose the structured event will carry anyway, dropped so a read does
|
|
402
409
|
* not render twice — but ONLY on a runtime whose stream reaches
|
|
@@ -470,7 +477,14 @@ export function createAgentTurnExecution({
|
|
|
470
477
|
const artifactInstructions = getArtifactsAccepted()
|
|
471
478
|
? await artifacts.prepare?.({ placeDir: wt, before: artifactsBefore, agentId }) ?? '' : '';
|
|
472
479
|
let projectTools = null;
|
|
480
|
+
let orchestrator = null;
|
|
473
481
|
try {
|
|
482
|
+
const orchestrated = job.orchestrationVersion === 2;
|
|
483
|
+
if (orchestrated && posture === 'build' && ['claude', 'codex'].includes(rt)) {
|
|
484
|
+
orchestrator = await createOrchestrator({ turnId, runtime: rt, trace, admit,
|
|
485
|
+
notify: message => { if (rt === 'claude') run.live?.inject(message); },
|
|
486
|
+
taken: id => run.live?.onTaken(id) });
|
|
487
|
+
}
|
|
474
488
|
// A turn reads the base ref afresh. Changes the agent has made to its
|
|
475
489
|
// own worktree cannot silently change the next turn's tools.
|
|
476
490
|
const sources = repositoryRun?.sources ?? [];
|
|
@@ -480,7 +494,10 @@ export function createAgentTurnExecution({
|
|
|
480
494
|
const base = readBaseTools(other.repoRoot, other.baseRef);
|
|
481
495
|
snapshot.instructions.push(...[...base.instructions, ...base.skillFiles].map((file) => ({ ...file, path: `repositories/${other.folder}/${file.path}` })));
|
|
482
496
|
}
|
|
483
|
-
projectTools = prepareAgentTools(snapshot, rt, process.env, wt, { codexHome: codexAgentHome, turnServers:
|
|
497
|
+
projectTools = prepareAgentTools(snapshot, rt, process.env, wt, { codexHome: codexAgentHome, turnServers: {
|
|
498
|
+
...(repositoryRun?.tool ? { card_repositories: repositoryRun.tool.config } : {}),
|
|
499
|
+
...(orchestrator ? { card_orchestrator: orchestrator.config } : {}),
|
|
500
|
+
} });
|
|
484
501
|
const mountsProjectMcp = TURN_PROFILES[posture]?.projectMcp === true && (rt === 'claude' || rt === 'codex');
|
|
485
502
|
// A fenced posture on a runner that ignores its own file there (Codex's
|
|
486
503
|
// `--ignore-user-config`) takes the servers as `fencedMount` instead.
|
|
@@ -501,11 +518,14 @@ export function createAgentTurnExecution({
|
|
|
501
518
|
unmounted: [...projectTools.mcpUnmounted.map((u) => ({ ...u, source: 'project' })), ...(fencedMount ? fencedMount.unmounted : [])],
|
|
502
519
|
});
|
|
503
520
|
const agentTurnArgs = {
|
|
504
|
-
...(rt === 'claude' ? { liveMessages: (run.live = makeLiveMessages({
|
|
521
|
+
...((rt === 'claude' || orchestrator) ? { liveMessages: (run.live = makeLiveMessages({
|
|
505
522
|
scope: 'agent', turnId, cwd: wt, sessionMetaPath, interrupted: () => run.interrupted,
|
|
523
|
+
observe: message => orchestrator?.note(message),
|
|
524
|
+
...(rt === 'codex' ? { nativeIds: () => null } : {}),
|
|
506
525
|
format: async (followup) => {
|
|
526
|
+
orchestrator?.note(followup);
|
|
507
527
|
const attached = followup.attachments?.length ? await fetchAgentFiles(wt, followup.attachments.map((file) => ({ ...file, from: 'message' }))) : { message: [], card: [] };
|
|
508
|
-
return AGENT_HUMAN_KICKOFF({ agentName: job.agentName, message: followup.body + (followup.libraryPrompt ?? ''),
|
|
528
|
+
return AGENT_HUMAN_KICKOFF({ agentName: job.agentName, message: followup.body + (followup.libraryPrompt ?? '') + (orchestrator ? `\nMessage id: ${followup.id}. Choose and report its route with card_orchestrator.route_message.` : ''),
|
|
509
529
|
askedByName: followup.askedByName, task: job.task, cardThread: job.cardThread,
|
|
510
530
|
investigationContext: job.investigationContext, researchContext: job.researchContext,
|
|
511
531
|
messageFiles: attached.message, cardFiles: files.card, position: job.position ?? 1, total: job.total ?? 1 });
|
|
@@ -556,10 +576,11 @@ export function createAgentTurnExecution({
|
|
|
556
576
|
// ARTIFACTS (0.94.0), while the server can show one — an agent's
|
|
557
577
|
// land on its page, under the facts row.
|
|
558
578
|
artifacts: getArtifactsAccepted(),
|
|
559
|
-
}) + (toolsNote ? `\n\n${toolsNote}` : '') + (artifactInstructions ? `\n\n${artifactInstructions}` : '') + `\n\n${INTERNAL_WAIT_CONTRACT}` + (projectTools.instructions ? `\n\n${projectTools.instructions}` : '') + (repositoryRun ? `\n\n${repositoryRun.note}\nRepository command argv: ${JSON.stringify([repositoryRun.tool?.config.command, ...(repositoryRun.tool?.config.args.slice(0, -1) ?? []), "__repository", "<name>"])}` : ''),
|
|
579
|
+
}) + (toolsNote ? `\n\n${toolsNote}` : '') + (artifactInstructions ? `\n\n${artifactInstructions}` : '') + `\n\n${INTERNAL_WAIT_CONTRACT}` + (projectTools.instructions ? `\n\n${projectTools.instructions}` : '') + (repositoryRun ? `\n\n${repositoryRun.note}\nRepository command argv: ${JSON.stringify([repositoryRun.tool?.config.command, ...(repositoryRun.tool?.config.args.slice(0, -1) ?? []), "__repository", "<name>"])}` : '') + (orchestrator ? `\n\n${ORCHESTRATOR_CONTRACT}` : ''),
|
|
560
580
|
agentTools: projectTools,
|
|
561
581
|
writableDirs: repositoryRun?.writableDirs,
|
|
562
|
-
...((projectTools.codexHome || fencedMount || repositoryRun?.tool) ? { mcpEnv: { ...(projectTools.codexHome ? { CODEX_HOME: projectTools.codexHome } : {}), ...(fencedMount?.env ?? {}), ...(repositoryRun?.tool?.env ?? {}) } } : {}),
|
|
582
|
+
...((projectTools.codexHome || fencedMount || repositoryRun?.tool || orchestrator) ? { mcpEnv: { ...(projectTools.codexHome ? { CODEX_HOME: projectTools.codexHome } : {}), ...(fencedMount?.env ?? {}), ...(repositoryRun?.tool?.env ?? {}), ...(orchestrator?.env ?? {}) } } : {}),
|
|
583
|
+
orchestration: Boolean(orchestrator),
|
|
563
584
|
// NOBODY IS AT THIS SCREEN (2026-09-29): no display, BROWSER=none, and
|
|
564
585
|
// a browser profile of the agent's own, kept beside its Codex home —
|
|
565
586
|
// so a Chrome it starts to look at its page never reaches the
|
|
@@ -578,15 +599,20 @@ export function createAgentTurnExecution({
|
|
|
578
599
|
// The kind's posture, as the one turn profile (turnProfile.mjs):
|
|
579
600
|
// build for code, the fenced design/research postures otherwise.
|
|
580
601
|
profile: posture,
|
|
602
|
+
cardTurn: Boolean(orchestrator),
|
|
603
|
+
onSubagent: (update) => orchestrator ? orchestrator.observe(update) : trace.agent(update),
|
|
604
|
+
onSpawnResult: result => orchestrator?.spawnResult(result),
|
|
581
605
|
cwd: wt,
|
|
582
606
|
runtime: rt,
|
|
583
607
|
resume,
|
|
584
608
|
resumeThreadId: codexResumeId || claudeResumeId || undefined,
|
|
585
609
|
onInit: (init) => {
|
|
610
|
+
orchestrator?.root(init.sessionId);
|
|
586
611
|
if (claudeMarker && CLAUDE_SESSION_RE.test(init.sessionId ?? ''))
|
|
587
612
|
try { writeFileSync(claudeMarker, init.sessionId); } catch { /* best effort */ }
|
|
588
613
|
},
|
|
589
614
|
onThreadId: (id) => {
|
|
615
|
+
orchestrator?.root(id);
|
|
590
616
|
seenThreadId = String(id ?? '').trim() || seenThreadId;
|
|
591
617
|
},
|
|
592
618
|
onAnswer: (t) => {
|
|
@@ -600,6 +626,20 @@ export function createAgentTurnExecution({
|
|
|
600
626
|
// narrator keeps: overwritten, never appended, and ignored by the
|
|
601
627
|
// board past ~90 seconds.
|
|
602
628
|
onActivity: (a) => {
|
|
629
|
+
if (a?.agentId) {
|
|
630
|
+
// Tool labels are already clipped. Their structured, scrubbed
|
|
631
|
+
// twin below supplies Now without leaking a credential prefix.
|
|
632
|
+
if (doubledKinds?.has(a.kind)) return;
|
|
633
|
+
const body = scrubNativeText(a.full ?? a.label, 4_000, envScrub);
|
|
634
|
+
const kind = a.kind;
|
|
635
|
+
const step = id => {
|
|
636
|
+
trace.agent({ id, now: body });
|
|
637
|
+
if (kind === 'doing') trace.doing(id, body);
|
|
638
|
+
else trace.prose(kind, body, id);
|
|
639
|
+
};
|
|
640
|
+
if (orchestrator) orchestrator.attributed(a.agentId, step); else step(a.agentId);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
603
643
|
brief?.append(a?.kind ?? 'activity', a?.full ?? a?.label);
|
|
604
644
|
let line = a?.label;
|
|
605
645
|
if (!line) return;
|
|
@@ -632,6 +672,19 @@ export function createAgentTurnExecution({
|
|
|
632
672
|
// the builder itself (bounded window BEFORE its caps; see
|
|
633
673
|
// toolEventOf). A tool it does not know pushes nothing.
|
|
634
674
|
onToolEvent: (name, input, meta) => {
|
|
675
|
+
if (meta?.agentId) {
|
|
676
|
+
if (!meta.result) {
|
|
677
|
+
const event = toolEventOf(name, input, wt, envScrub);
|
|
678
|
+
const now = event?.c ? `$ ${event.c}` : event?.p ?? event?.q;
|
|
679
|
+
const step = id => {
|
|
680
|
+
if (now) trace.agent({ id, now });
|
|
681
|
+
trace.tool(event, id);
|
|
682
|
+
if (name === 'Bash' && now) trace.doing(id, now);
|
|
683
|
+
};
|
|
684
|
+
if (orchestrator) orchestrator.attributed(meta.agentId, step); else step(meta.agentId);
|
|
685
|
+
}
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
635
688
|
brief?.append(meta?.result ? 'tool result' : 'tool call', meta?.result
|
|
636
689
|
? { id: meta.id, exitCode: meta.exitCode, isError: meta.isError, output: meta.output }
|
|
637
690
|
: { name, input });
|
|
@@ -660,6 +713,7 @@ export function createAgentTurnExecution({
|
|
|
660
713
|
// (runTurn.mjs, `initHarvestFor`). An agent-only box learns from the
|
|
661
714
|
// startup probe and its Terminal tabs.
|
|
662
715
|
onSpawn: (ch) => {
|
|
716
|
+
if (orchestrator && rt === 'codex') run.live?.connect((id, body) => orchestrator.input(id, body));
|
|
663
717
|
if (rt !== 'claude') reportDelivery('agent', job, 'taken');
|
|
664
718
|
brief?.running(true);
|
|
665
719
|
ch.once('close', () => { if (child === ch) brief?.running(false); });
|
|
@@ -732,6 +786,12 @@ export function createAgentTurnExecution({
|
|
|
732
786
|
},
|
|
733
787
|
});
|
|
734
788
|
} finally {
|
|
789
|
+
// The loopback capability stays registered until every retained CLI
|
|
790
|
+
// answer is scrubbed; then it can leave the machine's secret set.
|
|
791
|
+
out = envScrub(out);
|
|
792
|
+
if (lastAnswer) lastAnswer = envScrub(lastAnswer);
|
|
793
|
+
await run.live?.stop();
|
|
794
|
+
await orchestrator?.close(run.interrupted);
|
|
735
795
|
await brief?.stop();
|
|
736
796
|
// What the CLI counted, for the lane's catch if anything below throws.
|
|
737
797
|
measured.usage = usage;
|
|
@@ -749,7 +809,24 @@ export function createAgentTurnExecution({
|
|
|
749
809
|
* uplink that will not answer costs the tail and nothing else.
|
|
750
810
|
*/
|
|
751
811
|
trace.stop();
|
|
752
|
-
|
|
812
|
+
// Trace reports remain turn-scoped and the API accepts their late tail.
|
|
813
|
+
// An interrupted process has already exited: a slow trace uplink must
|
|
814
|
+
// not delay its settlement or the queued prompt's continuation.
|
|
815
|
+
const tail = trace.flush(TRACE_FINAL_FLUSH_MS);
|
|
816
|
+
if (!setupController.signal.aborted) {
|
|
817
|
+
let releaseWait;
|
|
818
|
+
try {
|
|
819
|
+
// An interrupt may arrive after the CLI exited, while an ordinary
|
|
820
|
+
// final flush is already waiting. The same run cancellation signal
|
|
821
|
+
// releases that wait without cancelling the turn-scoped upload.
|
|
822
|
+
await Promise.race([tail, new Promise(resolve => {
|
|
823
|
+
releaseWait = () => resolve();
|
|
824
|
+
setupController.signal.addEventListener('abort', releaseWait, { once: true });
|
|
825
|
+
})]);
|
|
826
|
+
} finally {
|
|
827
|
+
setupController.signal.removeEventListener('abort', releaseWait);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
753
830
|
for (const ch of spawnedChildren) workChildren.delete(ch);
|
|
754
831
|
if (agentChildren.get(place) === child) agentChildren.delete(place);
|
|
755
832
|
if (ranMarker && rt === 'claude') {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* that follow a CLI (a limit, no declared outcome, a missing artifact, the
|
|
7
7
|
* parsed result) are one decision with its own rules — a limit only when the
|
|
8
8
|
* turn declared nothing, an artifact kind delivered only once its file is
|
|
9
|
-
* measured, every relayed string scrubbed
|
|
9
|
+
* measured, and every relayed string scrubbed without prose truncation — and the run that
|
|
10
10
|
* feeds it (workAgentTurnExecution.mjs) changes for reasons none of those
|
|
11
11
|
* rules share. Pure but for one read-only artifact scan, so each rule is a
|
|
12
12
|
* unit test here instead of a source pin behind a CLI nobody spawns in tests.
|
|
@@ -55,7 +55,7 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
|
|
|
55
55
|
return report({
|
|
56
56
|
turnId,
|
|
57
57
|
outcome: 'nothing',
|
|
58
|
-
answer: limit
|
|
58
|
+
answer: readableOut.trim() === limit ? limit : `${limit}\n\n${envScrub(readableOut)}`,
|
|
59
59
|
...(usage ? { usage } : {}),
|
|
60
60
|
branch,
|
|
61
61
|
worktree: wt,
|
|
@@ -71,7 +71,7 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
|
|
|
71
71
|
// The CLI's own words when it produced any. A turn that explained why
|
|
72
72
|
// it stopped is far more use than "the agent stopped".
|
|
73
73
|
answer: raw.trim()
|
|
74
|
-
? readable.trim() ? envScrub(readable)
|
|
74
|
+
? readable.trim() ? envScrub(readable)
|
|
75
75
|
: 'The turn returned an unreadable outcome declaration. This work has not been delivered.'
|
|
76
76
|
: 'the turn produced no output on the machine — its CLI may be signed out',
|
|
77
77
|
...(commits.length ? { commits } : {}),
|
|
@@ -120,8 +120,8 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
|
|
|
120
120
|
const briefUse = /^Research intended use: (tasks|context|document)(?:\r?\n|$)/.exec(job.task?.brief ?? '')?.[1];
|
|
121
121
|
const researchUse = kind.posture === 'research' ? briefUse ?? res.researchUse : null;
|
|
122
122
|
const temporaryResearch = ['tasks', 'context'].includes(researchUse);
|
|
123
|
-
if (res.outcome === 'delivered' && temporaryResearch &&
|
|
124
|
-
return report({ turnId, outcome: 'nothing', answer:
|
|
123
|
+
if (res.outcome === 'delivered' && temporaryResearch && !res.answer?.trim()) {
|
|
124
|
+
return report({ turnId, outcome: 'nothing', answer: 'The research turn returned no findings.', branch, worktree: wt });
|
|
125
125
|
}
|
|
126
126
|
if (res.outcome === 'delivered' && kind.artifact && !temporaryResearch) {
|
|
127
127
|
const want = kind.artifact.match;
|
|
@@ -154,7 +154,7 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
|
|
|
154
154
|
...(prerequisite && !cannotRequest ? { prerequisite } : {}),
|
|
155
155
|
outcome: res.outcome,
|
|
156
156
|
answer: cannotRequest ?? envScrub(kind.posture === 'research' && researchUse
|
|
157
|
-
? `Research intended use: ${researchUse}\n\n${res.answer ?? ''}` : res.answer ?? '')
|
|
157
|
+
? `Research intended use: ${researchUse}\n\n${res.answer ?? ''}` : res.answer ?? ''),
|
|
158
158
|
...(commits.length ? { commits } : {}),
|
|
159
159
|
// SCRUBBED like the answer beside it: a raised card lands in the
|
|
160
160
|
// project doc every member reads, and a brief quoting the `.env` the
|
|
@@ -164,7 +164,7 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
|
|
|
164
164
|
? {
|
|
165
165
|
raised: res.raised.map((r) => ({
|
|
166
166
|
title: envScrub(r.title).slice(0, 300),
|
|
167
|
-
...(r.brief ? { brief: envScrub(r.brief)
|
|
167
|
+
...(r.brief ? { brief: envScrub(r.brief) } : {}),
|
|
168
168
|
})),
|
|
169
169
|
}
|
|
170
170
|
: {}),
|
|
@@ -228,7 +228,7 @@ export function failedTurnSettlement({ turnId, error, commits = [], usage = null
|
|
|
228
228
|
outcome: 'nothing',
|
|
229
229
|
answer: envScrub(
|
|
230
230
|
why ? `This machine could not finish the turn: ${why}` : 'This machine could not finish the turn.'
|
|
231
|
-
)
|
|
231
|
+
),
|
|
232
232
|
...(commits.length ? { commits } : {}),
|
|
233
233
|
...(usage ? { usage } : {}),
|
|
234
234
|
...(model ? { model } : {}),
|
|
@@ -112,6 +112,7 @@ export function createWorkAgentTurns({
|
|
|
112
112
|
const stoppedFor = new Map(); // agentId -> when
|
|
113
113
|
|
|
114
114
|
const { runAgentTurnInPlace, commitsBetween } = createAgentTurnExecution({
|
|
115
|
+
admit,
|
|
115
116
|
agentRepositories,
|
|
116
117
|
makeBrief,
|
|
117
118
|
baseRef,
|
|
@@ -415,6 +416,7 @@ export function createWorkAgentTurns({
|
|
|
415
416
|
const run = agentRuns.get(id);
|
|
416
417
|
if (run) {
|
|
417
418
|
if (!run.interrupted) note(`${c.cyan('agent')} ${c.dim('— interrupting the running turn')}`);
|
|
419
|
+
if (!run.interrupted) reportDelivery('agent', { id }, 'interrupting');
|
|
418
420
|
run.interrupted = true;
|
|
419
421
|
run.abortSetup?.();
|
|
420
422
|
const child = agentChildren.get(run.place);
|
package/bin/lib/workChildren.mjs
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
* to, the teardown that SIGTERMs them, the count the concurrency ceiling is
|
|
4
4
|
* on, and the admission built over that count.
|
|
5
5
|
*
|
|
6
|
+
* This manager is project-scoped. Native card children hold reservations in
|
|
7
|
+
* the same admission while live (orchestrator.mjs); other projects have their
|
|
8
|
+
* own pool, per the owner's 2026-10-15 ruling.
|
|
9
|
+
*
|
|
6
10
|
* Split out of work.mjs (2026-09-26, SOLID F037). Six lanes write
|
|
7
11
|
* `workChildren` (tab turns, agent turns, plans, the check, the pre-review)
|
|
8
12
|
* and three things read it (teardown, `liveTurnCount`, the machine snapshot's
|
|
@@ -9,7 +9,7 @@ import { fleetEndpoint, isConsideredRefusal } from './fleetWire.mjs';
|
|
|
9
9
|
import { DAEMON_INSTANCE, FLEET_URL } from './config.mjs';
|
|
10
10
|
import { messageIdsForSession } from './claudeSessions.mjs';
|
|
11
11
|
|
|
12
|
-
export function createLiveMessages({ scope, turnId, attempt, cwd, sessionMetaPath, format, interrupted = () => false,
|
|
12
|
+
export function createLiveMessages({ scope, turnId, attempt, cwd, sessionMetaPath, format, observe = () => {}, interrupted = () => false,
|
|
13
13
|
request = fleetRequest, nativeIds = messageIdsForSession, setTimer = setInterval, clearTimer = clearInterval, finalFlushMs = 1_000 }) {
|
|
14
14
|
const identity = { scope, turnId, instance: DAEMON_INSTANCE, ...(attempt !== undefined ? { attempt } : {}) };
|
|
15
15
|
const file = sessionMetaPath(cwd, 'flowviant-live-messages', turnId);
|
|
@@ -73,6 +73,7 @@ export function createLiveMessages({ scope, turnId, attempt, cwd, sessionMetaPat
|
|
|
73
73
|
if (!result.json?.data?.active) return;
|
|
74
74
|
const seen = sessionId ? nativeIds(cwd, sessionId) : null;
|
|
75
75
|
for (const message of result.json.data.messages ?? []) {
|
|
76
|
+
observe(message);
|
|
76
77
|
if (stopped || interrupted()) break;
|
|
77
78
|
if (taken.has(message.id) || seen?.has(message.id)) { onTaken(message.id); continue; }
|
|
78
79
|
if (sent.has(message.id)) continue;
|
|
@@ -83,6 +84,11 @@ export function createLiveMessages({ scope, turnId, attempt, cwd, sessionMetaPat
|
|
|
83
84
|
return flight;
|
|
84
85
|
};
|
|
85
86
|
return {
|
|
87
|
+
inject(prompt) {
|
|
88
|
+
if (stopped || interrupted() || !send) return false;
|
|
89
|
+
const id = randomUUID(); initialIds.add(id);
|
|
90
|
+
return send(id, prompt) === true;
|
|
91
|
+
},
|
|
86
92
|
turnId, onTaken, tick,
|
|
87
93
|
connected: () => Boolean(send) && !stopped,
|
|
88
94
|
get initialId() { return initialId; },
|
|
@@ -41,7 +41,7 @@ import { AGY_CONV_RE, CLAUDE_SESSION_RE, CODEX_THREAD_RE, RESUME_MARKERS } from
|
|
|
41
41
|
import { sessionTurnKickoff, sessionTurnShape, sessionTurnSystem } from './workTurnShape.mjs';
|
|
42
42
|
import { createToolLog } from './workToolLog.mjs';
|
|
43
43
|
import { createCommandAudit } from './workCommandAudit.mjs';
|
|
44
|
-
import { APPROVAL_REFUSED, codexApprovalSentence, codexLoginFailure, codexLoginSentence,
|
|
44
|
+
import { APPROVAL_REFUSED, codexApprovalSentence, codexLoginFailure, codexLoginSentence, withRelay } from './codexRelay.mjs';
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* HOW LONG, AND HOW MANY, SETTLED TURNS THE LANE REMEMBERS (2026-09-29) — see
|
|
@@ -1035,7 +1035,7 @@ export function createWorkSessionTurns({
|
|
|
1035
1035
|
? refusedLine
|
|
1036
1036
|
? withRelay(refusedLine, answer)
|
|
1037
1037
|
: // Scrub: a reply can quote config or env-adjacent code.
|
|
1038
|
-
envScrub(answer)
|
|
1038
|
+
envScrub(answer)
|
|
1039
1039
|
: (refusedLine ?? noWords),
|
|
1040
1040
|
// Only a turn that actually SPOKE proves a conversation lives
|
|
1041
1041
|
// here. Recording the path unconditionally is how a crashed first
|
|
@@ -1062,7 +1062,7 @@ export function createWorkSessionTurns({
|
|
|
1062
1062
|
// Scrub, like every string that leaves this machine: an exception
|
|
1063
1063
|
// routinely quotes command output, and command output can quote a
|
|
1064
1064
|
// synced secret.
|
|
1065
|
-
answer: envScrub(String(e?.message ?? 'the session turn failed'))
|
|
1065
|
+
answer: envScrub(String(e?.message ?? 'the session turn failed')),
|
|
1066
1066
|
// "What did it do before it failed" is exactly the question a
|
|
1067
1067
|
// crashed turn's log answers, and a turn that threw after its CLI
|
|
1068
1068
|
// ran still spent what it spent — the main settle's own spread.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.125.
|
|
4
|
-
"description": "Run your own coding CLIs as build agents for Flowviant
|
|
3
|
+
"version": "0.125.2",
|
|
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": {
|
|
7
7
|
"flowviant": "bin/cli.mjs"
|