flowviant 0.117.0 → 0.124.1
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 +41 -0
- package/bin/cli.mjs +44 -0
- package/bin/lib/agentAsk.mjs +123 -0
- package/bin/lib/agentBrief.mjs +36 -0
- package/bin/lib/agentFootprint.mjs +76 -0
- package/bin/lib/agentInternalWait.mjs +4 -3
- package/bin/lib/agentPlan.mjs +86 -7
- package/bin/lib/agentPrerequisiteContract.mjs +43 -0
- package/bin/lib/agentRepositories.mjs +48 -0
- package/bin/lib/agentTaskKinds.mjs +1 -1
- package/bin/lib/artifactContracts.mjs +91 -20
- package/bin/lib/artifactIteration.mjs +182 -0
- package/bin/lib/artifactRecovery.mjs +36 -0
- package/bin/lib/artifactReporter.mjs +183 -19
- package/bin/lib/artifacts.mjs +6 -3
- package/bin/lib/checkoutIdentity.mjs +25 -0
- package/bin/lib/claudeAuth.mjs +15 -5
- package/bin/lib/claudeInput.mjs +32 -0
- package/bin/lib/claudePosture.mjs +110 -14
- package/bin/lib/claudeSessions.mjs +20 -0
- package/bin/lib/claudeStream.mjs +20 -2
- package/bin/lib/codexProjectRead.mjs +63 -0
- package/bin/lib/codexResearch.mjs +16 -3
- package/bin/lib/commandSpecs.mjs +6 -0
- package/bin/lib/credentials.mjs +52 -17
- package/bin/lib/daemonLocks.mjs +26 -6
- package/bin/lib/desktopContract.mjs +3 -0
- package/bin/lib/discoveryContract.mjs +19 -0
- package/bin/lib/fleet.mjs +65 -10
- package/bin/lib/fleetReports.mjs +44 -3
- package/bin/lib/fleetRoster.mjs +1 -89
- package/bin/lib/fleetStartup.mjs +11 -14
- package/bin/lib/holder.mjs +4 -37
- package/bin/lib/hooks/mcpAllow.mjs +60 -0
- package/bin/lib/hooks/readGuard.mjs +13 -4
- package/bin/lib/imageArtifact.mjs +74 -0
- package/bin/lib/instance.mjs +20 -7
- package/bin/lib/landed.mjs +2 -2
- package/bin/lib/login.mjs +39 -4
- package/bin/lib/maintenance.mjs +47 -0
- package/bin/lib/maintenanceInventory.mjs +39 -0
- package/bin/lib/mcpInstall.mjs +163 -0
- package/bin/lib/mcpInventory.mjs +119 -0
- package/bin/lib/placeLock.mjs +9 -7
- package/bin/lib/planDependencies.mjs +70 -0
- package/bin/lib/postureTools.mjs +85 -0
- package/bin/lib/prWorkflow.mjs +15 -0
- package/bin/lib/projectLanded.mjs +16 -0
- package/bin/lib/projectRepositories.mjs +60 -0
- package/bin/lib/projectToolRuntimes.mjs +227 -3
- package/bin/lib/projectTools.mjs +63 -5
- package/bin/lib/projectWorktreeHome.mjs +25 -0
- package/bin/lib/prompts.mjs +170 -42
- package/bin/lib/repositoryBaseline.mjs +68 -0
- package/bin/lib/repositoryDelivery.mjs +64 -0
- package/bin/lib/repositoryMerge.mjs +116 -0
- package/bin/lib/repositoryPlaces.mjs +179 -0
- package/bin/lib/repositorySetup.mjs +58 -0
- package/bin/lib/repositoryTool.mjs +71 -0
- package/bin/lib/runTurn.mjs +58 -18
- package/bin/lib/runtimeAntigravity.mjs +2 -1
- package/bin/lib/runtimeCapabilities.mjs +77 -3
- package/bin/lib/runtimeClaude.mjs +31 -10
- package/bin/lib/runtimeCodex.mjs +28 -59
- package/bin/lib/runtimeDetection.mjs +6 -0
- package/bin/lib/runtimeEvents.mjs +12 -1
- package/bin/lib/runtimeOfferings.mjs +75 -3
- package/bin/lib/shipMerge.mjs +5 -2
- package/bin/lib/startProject.mjs +4 -4
- package/bin/lib/trace.mjs +8 -3
- package/bin/lib/turnDeclaration.mjs +88 -0
- package/bin/lib/turnDiagnostics.mjs +93 -0
- package/bin/lib/turnLock.mjs +48 -8
- package/bin/lib/turnProfile.mjs +15 -3
- package/bin/lib/work.mjs +48 -29
- package/bin/lib/workAgentBrief.mjs +92 -0
- package/bin/lib/workAgentBriefRunner.mjs +59 -0
- package/bin/lib/workAgentCheck.mjs +6 -5
- package/bin/lib/workAgentMerges.mjs +19 -2
- package/bin/lib/workAgentPlans.mjs +21 -70
- package/bin/lib/workAgentPrecheck.mjs +10 -5
- package/bin/lib/workAgentPublish.mjs +18 -5
- package/bin/lib/workAgentRecovery.mjs +85 -0
- package/bin/lib/workAgentReview.mjs +5 -3
- package/bin/lib/workAgentTurnBegun.mjs +6 -4
- package/bin/lib/workAgentTurnExecution.mjs +167 -29
- package/bin/lib/workAgentTurnOutcome.mjs +20 -5
- package/bin/lib/workAgentTurns.mjs +92 -12
- package/bin/lib/workArtifactReconcile.mjs +79 -0
- package/bin/lib/workCappedTurn.mjs +10 -0
- package/bin/lib/workCommandAudit.mjs +8 -1
- package/bin/lib/workDelivery.mjs +21 -0
- package/bin/lib/workDiffs.mjs +18 -11
- package/bin/lib/workLiveMessages.mjs +129 -0
- package/bin/lib/workPlaceReports.mjs +5 -0
- package/bin/lib/workPlaces.mjs +24 -3
- package/bin/lib/workRetire.mjs +5 -0
- package/bin/lib/workSessionTurns.mjs +219 -52
- package/bin/lib/workShip.mjs +17 -16
- package/bin/lib/workToolLog.mjs +70 -3
- package/bin/lib/workTurnShape.mjs +6 -4
- package/package.json +7 -3
- package/npm-shrinkwrap.json +0 -59
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { finishRepositoryDelivery, allResolutionsCommitted } from './repositoryDelivery.mjs';
|
|
1
2
|
/**
|
|
2
3
|
* ONE AGENT TURN, RUN: the worktree, the CLI, and what it left behind.
|
|
3
4
|
*
|
|
@@ -23,6 +24,7 @@ import { gitOrNull } from './git.mjs';
|
|
|
23
24
|
import { stashCard } from './agentCards.mjs';
|
|
24
25
|
import { c } from './ui.mjs';
|
|
25
26
|
import { runTurn } from './runTurn.mjs';
|
|
27
|
+
import { killGroup } from './processGroup.mjs';
|
|
26
28
|
import { SYSTEM_AGENT_FOR, AGENT_TASK_KICKOFF, AGENT_TASK_SPEC, AGENT_HUMAN_KICKOFF, withProjectContext } from './prompts.mjs';
|
|
27
29
|
import { AGENT_TASK_KINDS, agentTaskKindOf, unknownAgentTaskKind } from './agentTaskKinds.mjs';
|
|
28
30
|
import { knowledgeDirFor } from './knowledgeFiles.mjs';
|
|
@@ -30,17 +32,25 @@ import { scrub as envScrub } from './uplinkScrub.mjs';
|
|
|
30
32
|
import { canRun, runtimeById } from './runtimes.mjs';
|
|
31
33
|
import { toolEventOf, CLAUDE_TOOL_PROSE_KINDS } from './runtimeEvents.mjs';
|
|
32
34
|
import { makeTraceRelay } from './trace.mjs';
|
|
33
|
-
import { readBaseTools, prepareAgentTools } from './projectTools.mjs';
|
|
35
|
+
import { mcpJsonServerNamesAbove, readBaseTools, prepareAgentTools } from './projectTools.mjs';
|
|
36
|
+
import { TURN_PROFILES } from './turnProfile.mjs';
|
|
37
|
+
import { postureToolsNote } from './postureTools.mjs';
|
|
34
38
|
import { runTurnResumingOnce } from './resumeLost.mjs';
|
|
35
39
|
import { INTERNAL_WAIT_CONTRACT, runAgentWithInternalWaits } from './agentInternalWait.mjs';
|
|
36
40
|
import { CODEX_THREAD_RE } from './workSessionRuntime.mjs';
|
|
37
41
|
import { brainFor } from './workBrain.mjs';
|
|
42
|
+
import { refreshRestartBase } from './workAgentRecovery.mjs';
|
|
38
43
|
import { begunTurnRefusal } from './workAgentTurnBegun.mjs';
|
|
39
44
|
import { agentTurnSettlement } from './workAgentTurnOutcome.mjs';
|
|
40
45
|
import { ensureArtifactDir, ensureFenceScratch } from './artifacts.mjs';
|
|
41
46
|
import { agentBrowserHome } from './noWindowEnv.mjs';
|
|
47
|
+
import { CLAUDE_SESSION_RE, RESUME_MARKERS } from './conversationIds.mjs';
|
|
48
|
+
import { createDeliveryReporter } from './workDelivery.mjs';
|
|
49
|
+
import { createLiveMessages } from './workLiveMessages.mjs';
|
|
42
50
|
|
|
43
51
|
export function createAgentTurnExecution({
|
|
52
|
+
agentRepositories,
|
|
53
|
+
makeBrief,
|
|
44
54
|
baseRef,
|
|
45
55
|
baseDir,
|
|
46
56
|
repoRoot,
|
|
@@ -59,7 +69,9 @@ export function createAgentTurnExecution({
|
|
|
59
69
|
postAgentActivity,
|
|
60
70
|
postAgentParked,
|
|
61
71
|
fetchAgentFiles,
|
|
72
|
+
makeLiveMessages = createLiveMessages,
|
|
62
73
|
}) {
|
|
74
|
+
const reportDelivery = createDeliveryReporter();
|
|
63
75
|
/** How long the final flush may hold the settle. Bounded because the settle
|
|
64
76
|
* is the turn's contract and the trace is a readout: a wedged uplink costs
|
|
65
77
|
* the tail of a trace, never the answer behind it. */
|
|
@@ -107,7 +119,14 @@ export function createAgentTurnExecution({
|
|
|
107
119
|
* `releaseSlot` hands back the admission reservation the lane took on this
|
|
108
120
|
* turn's behalf, at the moment the CLI actually exists. Idempotent.
|
|
109
121
|
*/
|
|
110
|
-
|
|
122
|
+
/** `run` is the lane's record of this turn (`interrupted`, set by
|
|
123
|
+
* `interruptAgentTurns`); absent, nothing can interrupt it. */
|
|
124
|
+
const runAgentTurnInPlace = async (job, { turnId, agentId, place, releaseSlot, postAgentTurn, measured, run = { interrupted: false } }) => {
|
|
125
|
+
let repositoryRun = null;
|
|
126
|
+
const setupController = new AbortController();
|
|
127
|
+
run.abortSetup = () => setupController.abort();
|
|
128
|
+
if (run.interrupted) setupController.abort();
|
|
129
|
+
try {
|
|
111
130
|
// WORK THAT HAS BEGUN LIVES ON EXACTLY ONE BOX — read BEFORE anything is
|
|
112
131
|
// cut, because `placeWtFor` would cut a rival branch of the same name. The
|
|
113
132
|
// rule and its three-state branch measurement: workAgentTurnBegun.mjs.
|
|
@@ -118,7 +137,10 @@ export function createAgentTurnExecution({
|
|
|
118
137
|
return;
|
|
119
138
|
}
|
|
120
139
|
}
|
|
121
|
-
|
|
140
|
+
if (!(await refreshRestartBase(job, repoRoot))) return;
|
|
141
|
+
repositoryRun = await agentRepositories?.prepare(job, place, { signal: setupController.signal });
|
|
142
|
+
measured.repositoryReceipts = repositoryRun?.receipts;
|
|
143
|
+
const dir = repositoryRun ?? placeWtFor(place);
|
|
122
144
|
if (!dir) {
|
|
123
145
|
// No worktree and none could be cut. `nothing`, WITH git's own words:
|
|
124
146
|
// without them the agent sat in Stuck with no question and no reason
|
|
@@ -288,10 +310,13 @@ export function createAgentTurnExecution({
|
|
|
288
310
|
}
|
|
289
311
|
}
|
|
290
312
|
let seenThreadId = null;
|
|
313
|
+
const claudeMarker = rt === 'claude' ? sessionMetaPath(wt, RESUME_MARKERS.claude, agentId) : null;
|
|
314
|
+
let claudeResumeId = null;
|
|
315
|
+
try { const id = readFileSync(claudeMarker, 'utf8').trim(); if (CLAUDE_SESSION_RE.test(id)) claudeResumeId = id; } catch { /* unknown */ }
|
|
291
316
|
const resume =
|
|
292
317
|
rt === 'codex'
|
|
293
318
|
? Boolean(codexResumeId)
|
|
294
|
-
: rt === 'claude' && Boolean(ranMarker && existsSync(ranMarker));
|
|
319
|
+
: rt === 'claude' && (Boolean(claudeResumeId) || Boolean(ranMarker && existsSync(ranMarker)));
|
|
295
320
|
/**
|
|
296
321
|
* …AND THE HOME THAT THREAD IS FILED UNDER, KEPT PER AGENT (2026-09-29).
|
|
297
322
|
*
|
|
@@ -375,8 +400,8 @@ export function createAgentTurnExecution({
|
|
|
375
400
|
/**
|
|
376
401
|
* Prose the structured event will carry anyway, dropped so a read does
|
|
377
402
|
* not render twice — but ONLY on a runtime whose stream reaches
|
|
378
|
-
* `onToolEvent`
|
|
379
|
-
*
|
|
403
|
+
* `onToolEvent` here. Codex uses completed activity; agy has its own
|
|
404
|
+
* parser. Dropping their tool prose would blank their agents' traces.
|
|
380
405
|
* `parse: null` is exactly the claudeStream.mjs stream path. See
|
|
381
406
|
* CLAUDE_TOOL_PROSE_KINDS.
|
|
382
407
|
*/
|
|
@@ -408,6 +433,9 @@ export function createAgentTurnExecution({
|
|
|
408
433
|
* ever, which holds the self-update and the tray's install for ever.
|
|
409
434
|
*/
|
|
410
435
|
const spawnedChildren = new Set();
|
|
436
|
+
/** The CLI, unless a person already asked to stop this turn (Interrupt
|
|
437
|
+
* now): then nothing starts — no fresh retry, no continuation. */
|
|
438
|
+
const runCli = (args) => (run.interrupted ? Promise.resolve('') : runTurn(args));
|
|
411
439
|
/**
|
|
412
440
|
* WHAT THIS TURN SPENT, AS THE CLI COUNTED IT (2026-09-19).
|
|
413
441
|
*
|
|
@@ -426,6 +454,8 @@ export function createAgentTurnExecution({
|
|
|
426
454
|
/** THE MODEL THE TURN RAN ON, as its CLI named it (turnModel.mjs) — rides
|
|
427
455
|
* every settle `usage` rides, for the same reason; null sends no key. */
|
|
428
456
|
let model = null;
|
|
457
|
+
const brief = makeBrief?.({ agentId, turnId, runtime: rt, model: () => model ?? job.model });
|
|
458
|
+
brief?.append('prompt', job.task ? { title: job.task.title, message: job.body, discussion: job.cardThread, brief: job.task.brief, criteria: job.task.criteria } : job.body);
|
|
429
459
|
/**
|
|
430
460
|
* THE AGENT'S LAST MESSAGE, ALONE — where the final JSON object lives.
|
|
431
461
|
* Codex's `out` is every message plus stderr plus error text, and
|
|
@@ -437,12 +467,49 @@ export function createAgentTurnExecution({
|
|
|
437
467
|
/** The agent's artifact directory as it stood before this turn — the
|
|
438
468
|
* tab lane's rule, in the agent's own worktree (2026-09-22). */
|
|
439
469
|
const artifactsBefore = beforeArtifacts(wt);
|
|
470
|
+
const artifactInstructions = getArtifactsAccepted()
|
|
471
|
+
? await artifacts.prepare?.({ placeDir: wt, before: artifactsBefore, agentId }) ?? '' : '';
|
|
440
472
|
let projectTools = null;
|
|
441
473
|
try {
|
|
442
474
|
// A turn reads the base ref afresh. Changes the agent has made to its
|
|
443
475
|
// own worktree cannot silently change the next turn's tools.
|
|
444
|
-
|
|
476
|
+
const sources = repositoryRun?.sources ?? [];
|
|
477
|
+
const source = sources.find((r) => r.wt === wt);
|
|
478
|
+
const snapshot = readBaseTools(source?.repoRoot ?? repoRoot, source?.baseRef ?? baseRef());
|
|
479
|
+
for (const other of sources.filter((r) => r !== source)) {
|
|
480
|
+
const base = readBaseTools(other.repoRoot, other.baseRef);
|
|
481
|
+
snapshot.instructions.push(...[...base.instructions, ...base.skillFiles].map((file) => ({ ...file, path: `repositories/${other.folder}/${file.path}` })));
|
|
482
|
+
}
|
|
483
|
+
projectTools = prepareAgentTools(snapshot, rt, process.env, wt, { codexHome: codexAgentHome, turnServers: repositoryRun?.tool ? { card_repositories: repositoryRun.tool.config } : {} });
|
|
484
|
+
const mountsProjectMcp = TURN_PROFILES[posture]?.projectMcp === true && (rt === 'claude' || rt === 'codex');
|
|
485
|
+
// A fenced posture on a runner that ignores its own file there (Codex's
|
|
486
|
+
// `--ignore-user-config`) takes the servers as `fencedMount` instead.
|
|
487
|
+
const fencedMount = mountsProjectMcp && posture !== 'build' ? projectTools.fencedMount : null;
|
|
488
|
+
// THE MACHINE'S OWN SERVERS ON CLAUDE (2026-10-11, the owner): the turn
|
|
489
|
+
// is not strict, and every `.mcp.json` name from the worktree up is
|
|
490
|
+
// disabled so only the base copy speaks for the project. Null (an
|
|
491
|
+
// unboundable walk) mounts strictly: the base copy alone.
|
|
492
|
+
const claudeDisabled = mountsProjectMcp && rt === 'claude' ? mcpJsonServerNamesAbove(wt) : null;
|
|
493
|
+
// What this card's posture withholds, said to the agent — with the
|
|
494
|
+
// servers it did and did not mount, and why — so it relays the fence
|
|
495
|
+
// instead of inventing a setting (postureTools.mjs). Empty for a code card.
|
|
496
|
+
const toolsNote = postureToolsNote({
|
|
497
|
+
posture,
|
|
498
|
+
runtime: rt,
|
|
499
|
+
mounted: !mountsProjectMcp ? [] : fencedMount ? fencedMount.mounted : projectTools.mcpServers,
|
|
500
|
+
machine: mountsProjectMcp && (rt === 'codex' || claudeDisabled != null),
|
|
501
|
+
unmounted: [...projectTools.mcpUnmounted.map((u) => ({ ...u, source: 'project' })), ...(fencedMount ? fencedMount.unmounted : [])],
|
|
502
|
+
});
|
|
445
503
|
const agentTurnArgs = {
|
|
504
|
+
...(rt === 'claude' ? { liveMessages: (run.live = makeLiveMessages({
|
|
505
|
+
scope: 'agent', turnId, cwd: wt, sessionMetaPath, interrupted: () => run.interrupted,
|
|
506
|
+
format: async (followup) => {
|
|
507
|
+
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 ?? ''),
|
|
509
|
+
askedByName: followup.askedByName, task: job.task, cardThread: job.cardThread,
|
|
510
|
+
messageFiles: attached.message, cardFiles: files.card, position: job.position ?? 1, total: job.total ?? 1 });
|
|
511
|
+
},
|
|
512
|
+
})) } : {}),
|
|
446
513
|
prompt:
|
|
447
514
|
job.kind === 'task' && job.task
|
|
448
515
|
? AGENT_TASK_KICKOFF({
|
|
@@ -451,6 +518,8 @@ export function createAgentTurnExecution({
|
|
|
451
518
|
// The card's discussion (0.106.0), printed inside THE CARD by
|
|
452
519
|
// the same builder the stash above wrote down — and the files
|
|
453
520
|
// on it (0.112.0), after it, the same way.
|
|
521
|
+
resumeBody: job.body,
|
|
522
|
+
investigationContext: job.investigationContext,
|
|
454
523
|
cardThread: job.cardThread,
|
|
455
524
|
cardFiles: files.card,
|
|
456
525
|
position: job.position ?? 1,
|
|
@@ -458,7 +527,10 @@ export function createAgentTurnExecution({
|
|
|
458
527
|
})
|
|
459
528
|
: AGENT_HUMAN_KICKOFF({
|
|
460
529
|
agentName: job.agentName,
|
|
530
|
+
// INTERRUPT NOW: the previous run was stopped to send this.
|
|
531
|
+
interrupted: job.interrupted === true,
|
|
461
532
|
message: job.body ?? '',
|
|
533
|
+
investigationContext: job.investigationContext,
|
|
462
534
|
askedByName: job.askedByName,
|
|
463
535
|
task: job.task,
|
|
464
536
|
// The discussion on the one card this message is about, in a
|
|
@@ -476,26 +548,29 @@ export function createAgentTurnExecution({
|
|
|
476
548
|
// different things about the same directory.
|
|
477
549
|
// Spoken to the CLI that runs it (0.115.0): a mockup on Codex is
|
|
478
550
|
// told it is Codex, never Claude.
|
|
479
|
-
system: withProjectContext(SYSTEM_AGENT_FOR(taskKind, runtimeById(rt)?.label), {
|
|
551
|
+
system: withProjectContext(SYSTEM_AGENT_FOR(taskKind, runtimeById(rt)?.label, { prerequisitesAccepted: job.prerequisitesAccepted === true }), {
|
|
480
552
|
knowledgeDir: knowledgeDirFor(repoRoot),
|
|
481
553
|
// ARTIFACTS (0.94.0), while the server can show one — an agent's
|
|
482
554
|
// land on its page, under the facts row.
|
|
483
555
|
artifacts: getArtifactsAccepted(),
|
|
484
|
-
}) + `\n\n${INTERNAL_WAIT_CONTRACT}` + (projectTools.instructions ? `\n\n${projectTools.instructions}` : ''),
|
|
556
|
+
}) + (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>"])}` : ''),
|
|
485
557
|
agentTools: projectTools,
|
|
486
|
-
|
|
558
|
+
writableDirs: repositoryRun?.writableDirs,
|
|
559
|
+
...((projectTools.codexHome || fencedMount || repositoryRun?.tool) ? { mcpEnv: { ...(projectTools.codexHome ? { CODEX_HOME: projectTools.codexHome } : {}), ...(fencedMount?.env ?? {}), ...(repositoryRun?.tool?.env ?? {}) } } : {}),
|
|
487
560
|
// NOBODY IS AT THIS SCREEN (2026-09-29): no display, BROWSER=none, and
|
|
488
561
|
// a browser profile of the agent's own, kept beside its Codex home —
|
|
489
562
|
// so a Chrome it starts to look at its page never reaches the
|
|
490
563
|
// person's (noWindowEnv.mjs).
|
|
491
564
|
browserHome: agentBrowserHome(sessionMetaPath, wt, agentId),
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
565
|
+
// Reviewed project servers and the machine's configured servers, on
|
|
566
|
+
// initial cards, answers and review iterations alike.
|
|
567
|
+
...(mountsProjectMcp && rt === 'claude'
|
|
568
|
+
? {
|
|
569
|
+
mcpArgs: [...(claudeDisabled ? [] : ['--strict-mcp-config']), '--mcp-config', projectTools.mcpPath],
|
|
570
|
+
projectMcpServers: projectTools.mcpServers,
|
|
571
|
+
...(claudeDisabled ? { machineMcp: claudeDisabled } : {}),
|
|
572
|
+
}
|
|
573
|
+
: fencedMount ? { mcpArgs: fencedMount.args } : {}),
|
|
499
574
|
knowledgeDir: knowledgeDirFor(repoRoot),
|
|
500
575
|
// The kind's posture, as the one turn profile (turnProfile.mjs):
|
|
501
576
|
// build for code, the fenced design/research postures otherwise.
|
|
@@ -503,7 +578,11 @@ export function createAgentTurnExecution({
|
|
|
503
578
|
cwd: wt,
|
|
504
579
|
runtime: rt,
|
|
505
580
|
resume,
|
|
506
|
-
resumeThreadId: codexResumeId || undefined,
|
|
581
|
+
resumeThreadId: codexResumeId || claudeResumeId || undefined,
|
|
582
|
+
onInit: (init) => {
|
|
583
|
+
if (claudeMarker && CLAUDE_SESSION_RE.test(init.sessionId ?? ''))
|
|
584
|
+
try { writeFileSync(claudeMarker, init.sessionId); } catch { /* best effort */ }
|
|
585
|
+
},
|
|
507
586
|
onThreadId: (id) => {
|
|
508
587
|
seenThreadId = String(id ?? '').trim() || seenThreadId;
|
|
509
588
|
},
|
|
@@ -518,7 +597,8 @@ export function createAgentTurnExecution({
|
|
|
518
597
|
// narrator keeps: overwritten, never appended, and ignored by the
|
|
519
598
|
// board past ~90 seconds.
|
|
520
599
|
onActivity: (a) => {
|
|
521
|
-
|
|
600
|
+
brief?.append(a?.kind ?? 'activity', a?.full ?? a?.label);
|
|
601
|
+
let line = a?.label;
|
|
522
602
|
if (!line) return;
|
|
523
603
|
// THE TRACE TAKES EVERY LINE; the pulse takes one every two
|
|
524
604
|
// seconds. Two channels, one stream, and the drop-sampler stays a
|
|
@@ -533,7 +613,12 @@ export function createAgentTurnExecution({
|
|
|
533
613
|
// daemon carried for all of them. The PULSE below is deliberately
|
|
534
614
|
// still `line`: it is one overwritten line on a board, and a
|
|
535
615
|
// paragraph there would be a paragraph nobody can read.
|
|
536
|
-
if (!doubledKinds || !doubledKinds.has(a.kind))
|
|
616
|
+
if (!doubledKinds || !doubledKinds.has(a.kind)) {
|
|
617
|
+
const full = a.full ?? line;
|
|
618
|
+
const readable = trace.prose(a.kind, full);
|
|
619
|
+
if (readable === null) return;
|
|
620
|
+
if (readable !== full) line = envScrub(readable).replace(/\s+/g, ' ').trim().slice(0, 160);
|
|
621
|
+
}
|
|
537
622
|
const now = Date.now();
|
|
538
623
|
if (now - (lastAgentBeat.get(agentId) ?? 0) < 2_000) return;
|
|
539
624
|
lastAgentBeat.set(agentId, now);
|
|
@@ -543,7 +628,13 @@ export function createAgentTurnExecution({
|
|
|
543
628
|
// Workbench's tool cards are built from, scrubbed at collection by
|
|
544
629
|
// the builder itself (bounded window BEFORE its caps; see
|
|
545
630
|
// toolEventOf). A tool it does not know pushes nothing.
|
|
546
|
-
onToolEvent: (name, input) => {
|
|
631
|
+
onToolEvent: (name, input, meta) => {
|
|
632
|
+
brief?.append(meta?.result ? 'tool result' : 'tool call', meta?.result
|
|
633
|
+
? { id: meta.id, exitCode: meta.exitCode, isError: meta.isError, output: meta.output }
|
|
634
|
+
: { name, input });
|
|
635
|
+
// Codex request/result pairs feed capture/planner evidence. This
|
|
636
|
+
// append-only agent trace already relays its completed activity.
|
|
637
|
+
if (meta?.runtime === 'codex') return;
|
|
547
638
|
trace.tool(toolEventOf(name, input, wt, envScrub));
|
|
548
639
|
},
|
|
549
640
|
// The CLI's own per-turn token counts, off the `result` event. One
|
|
@@ -566,8 +657,12 @@ export function createAgentTurnExecution({
|
|
|
566
657
|
// (runTurn.mjs, `initHarvestFor`). An agent-only box learns from the
|
|
567
658
|
// startup probe and its Terminal tabs.
|
|
568
659
|
onSpawn: (ch) => {
|
|
660
|
+
if (rt !== 'claude') reportDelivery('agent', job, 'taken');
|
|
661
|
+
brief?.running(true);
|
|
662
|
+
ch.once('close', () => { if (child === ch) brief?.running(false); });
|
|
569
663
|
child = ch;
|
|
570
664
|
spawnedChildren.add(ch);
|
|
665
|
+
measured.ranCli = true;
|
|
571
666
|
// The AGENT it serves — what the machine snapshot charges this
|
|
572
667
|
// child's memory to.
|
|
573
668
|
workChildren.set(ch, agentId);
|
|
@@ -583,6 +678,8 @@ export function createAgentTurnExecution({
|
|
|
583
678
|
// and a place id IS one. It is what lets a hard stop actually reach
|
|
584
679
|
// the CLI — see `retireWorkSessions`.
|
|
585
680
|
agentChildren.set(place, ch);
|
|
681
|
+
// Asked to stop between the check and the spawn.
|
|
682
|
+
if (run.interrupted) killGroup(ch);
|
|
586
683
|
},
|
|
587
684
|
};
|
|
588
685
|
/**
|
|
@@ -597,7 +694,7 @@ export function createAgentTurnExecution({
|
|
|
597
694
|
* MENTIONS a missing session is never run twice.
|
|
598
695
|
*/
|
|
599
696
|
out = await runAgentWithInternalWaits(
|
|
600
|
-
(args, continuing) => continuing ?
|
|
697
|
+
(args, continuing) => continuing ? runCli(args) : runTurnResumingOnce(runCli, args, {
|
|
601
698
|
// A Codex write-up always runs fresh over its copy (codexResearch
|
|
602
699
|
// .mjs), so there is no lost conversation to retry it for.
|
|
603
700
|
resume: rt === 'codex' && posture === 'research' ? false : args.resume,
|
|
@@ -614,11 +711,30 @@ export function createAgentTurnExecution({
|
|
|
614
711
|
void postAgentActivity(agentId, 'resuming after an internal wait');
|
|
615
712
|
} }
|
|
616
713
|
);
|
|
714
|
+
out = await finishRepositoryDelivery({
|
|
715
|
+
job, run: repositoryRun, out, answer: () => lastAnswer,
|
|
716
|
+
resume: async (prompt) => {
|
|
717
|
+
lastAnswer = null;
|
|
718
|
+
const counted = usage;
|
|
719
|
+
return runCli({
|
|
720
|
+
...agentTurnArgs, prompt, liveContinuation: true,
|
|
721
|
+
onUsage: (next) => agentTurnArgs.onUsage(Object.fromEntries(
|
|
722
|
+
['input', 'output', 'cacheCreate', 'cacheRead'].map((k) =>
|
|
723
|
+
[k, (counted?.[k] ?? 0) + (next?.[k] ?? 0)])
|
|
724
|
+
)),
|
|
725
|
+
writableDirs: repositoryRun.sourcesNow().filter((r) => r.wt !== wt).map((r) => r.wt),
|
|
726
|
+
resume: true,
|
|
727
|
+
resumeThreadId: seenThreadId ?? agentTurnArgs.resumeThreadId,
|
|
728
|
+
});
|
|
729
|
+
},
|
|
730
|
+
});
|
|
617
731
|
} finally {
|
|
732
|
+
await brief?.stop();
|
|
618
733
|
// What the CLI counted, for the lane's catch if anything below throws.
|
|
619
734
|
measured.usage = usage;
|
|
620
735
|
measured.model = model;
|
|
621
736
|
projectTools?.cleanup();
|
|
737
|
+
|
|
622
738
|
/**
|
|
623
739
|
* THE TAIL, BEFORE THE SETTLE — so the last thing the agent did is on
|
|
624
740
|
* the record by the time the board is told the turn is over.
|
|
@@ -664,16 +780,36 @@ export function createAgentTurnExecution({
|
|
|
664
780
|
* floor: it must never delay the settle behind it.
|
|
665
781
|
*/
|
|
666
782
|
void reportSessionWorktree(place).catch(() => {});
|
|
667
|
-
//
|
|
668
|
-
//
|
|
669
|
-
|
|
670
|
-
void artifacts
|
|
671
|
-
.report({ placeDir: wt, before: artifactsBefore, agentId, turnId })
|
|
672
|
-
.catch(() => {});
|
|
783
|
+
// A file card delivers uploaded bytes, not an in-memory retry. Otherwise
|
|
784
|
+
// its dependent card can start while Review has nothing to show.
|
|
785
|
+
await artifacts.report({ placeDir: wt, before: artifactsBefore, agentId, turnId, requireStored: kind.artifact != null });
|
|
673
786
|
}
|
|
674
787
|
|
|
675
788
|
|
|
676
|
-
const commits = commitsBetween(wt, before);
|
|
789
|
+
const commits = repositoryRun ? [...new Set(repositoryRun.receipts().map((r) => r.sha))].slice(0, 50) : commitsBetween(wt, before);
|
|
790
|
+
/**
|
|
791
|
+
* A PERSON INTERRUPTED IT (Interrupt now). Whatever the CLI printed was
|
|
792
|
+
* cut off, so there is no outcome to read: the turn reports `interrupted`
|
|
793
|
+
* with what it measured — the commits it made, its spend, the branch —
|
|
794
|
+
* and leaves the worktree exactly as the stopped run left it. The message
|
|
795
|
+
* sent to interrupt it runs next, here, resuming this conversation. No
|
|
796
|
+
* limit, no park and no review entry: none of that happened.
|
|
797
|
+
*/
|
|
798
|
+
if (run.interrupted) {
|
|
799
|
+
measured.ranCli = spawnedChildren.size > 0;
|
|
800
|
+
await postAgentTurn({
|
|
801
|
+
turnId,
|
|
802
|
+
outcome: 'nothing',
|
|
803
|
+
interrupted: true,
|
|
804
|
+
ran: spawnedChildren.size > 0,
|
|
805
|
+
commits,
|
|
806
|
+
...(usage ? { usage } : {}),
|
|
807
|
+
...(model ? { model } : {}),
|
|
808
|
+
...(branch ? { branch } : {}),
|
|
809
|
+
worktree: wt,
|
|
810
|
+
});
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
677
813
|
// WHAT THE TURN REPORTS is one decision, off what was measured here
|
|
678
814
|
// (workAgentTurnOutcome.mjs). The side effects it implies stay on this
|
|
679
815
|
// side: the machine's limit state, the park, and the review-entry beat.
|
|
@@ -700,12 +836,14 @@ export function createAgentTurnExecution({
|
|
|
700
836
|
// reported as `nothing` — it did not deliver and it did not ask.
|
|
701
837
|
await postAgentParked(limit, rt);
|
|
702
838
|
}
|
|
839
|
+
if (job.kind === 'merge_resolve' && repositoryRun) body.mergeResolved = allResolutionsCommitted(repositoryRun.sourcesNow());
|
|
703
840
|
const reply = await postAgentTurn(body);
|
|
704
841
|
// The queue just emptied. Run the project's own check and the AI
|
|
705
842
|
// pre-review HERE, in the worktree we are already standing in and still
|
|
706
843
|
// hold the lock on — see `runReviewEntry`. Only after the settle that may
|
|
707
844
|
// say delivered: the three `nothing` settles never opened it.
|
|
708
845
|
if (final && reply?.review === true) await runReviewEntry(agentId, wt, job.agentName);
|
|
846
|
+
} finally { await repositoryRun?.tool?.close(); }
|
|
709
847
|
};
|
|
710
848
|
|
|
711
849
|
return { runAgentTurnInPlace, commitsBetween };
|
|
@@ -25,6 +25,8 @@ import { limitLine } from './cliLimit.mjs';
|
|
|
25
25
|
import { parseTurnResult } from './agentPlan.mjs';
|
|
26
26
|
import { changedArtifacts, scanArtifacts } from './artifacts.mjs';
|
|
27
27
|
import { scrub as envScrub } from './uplinkScrub.mjs';
|
|
28
|
+
import { usableImageArtifact } from './imageArtifact.mjs';
|
|
29
|
+
import { withoutTurnDeclaration } from './turnDeclaration.mjs';
|
|
28
30
|
|
|
29
31
|
export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage, model = null, commits, artifactsBefore, branch, wt }) {
|
|
30
32
|
const res = (lastAnswer && parseTurnResult(lastAnswer)) || parseTurnResult(out);
|
|
@@ -38,7 +40,9 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
|
|
|
38
40
|
* makes the match mean what it says: the CLI failed and this is the
|
|
39
41
|
* sentence it failed with.
|
|
40
42
|
*/
|
|
41
|
-
|
|
43
|
+
// An unreadable protocol object is not a native CLI diagnostic either.
|
|
44
|
+
const readableOut = res ? '' : withoutTurnDeclaration(out);
|
|
45
|
+
const limit = res ? null : limitLine(readableOut);
|
|
42
46
|
/** What the caller posts, and whether it is the one settle that may say
|
|
43
47
|
* delivered (the only one whose reply can open the review-entry beat).
|
|
44
48
|
* Every one carries the model the CLI said it ran on, when it said
|
|
@@ -59,13 +63,16 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
if (!res) {
|
|
66
|
+
const raw = lastAnswer || out;
|
|
67
|
+
const readable = lastAnswer ? withoutTurnDeclaration(raw) : readableOut;
|
|
62
68
|
return report({
|
|
63
69
|
turnId,
|
|
64
70
|
outcome: 'nothing',
|
|
65
71
|
// The CLI's own words when it produced any. A turn that explained why
|
|
66
72
|
// it stopped is far more use than "the agent stopped".
|
|
67
|
-
answer:
|
|
68
|
-
? envScrub(
|
|
73
|
+
answer: raw.trim()
|
|
74
|
+
? readable.trim() ? envScrub(readable).slice(-1500)
|
|
75
|
+
: 'The turn returned an unreadable outcome declaration. This work has not been delivered.'
|
|
69
76
|
: 'the turn produced no output on the machine — its CLI may be signed out',
|
|
70
77
|
...(commits.length ? { commits } : {}),
|
|
71
78
|
...(usage ? { usage } : {}),
|
|
@@ -110,7 +117,8 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
|
|
|
110
117
|
*/
|
|
111
118
|
if (res.outcome === 'delivered' && kind.artifact) {
|
|
112
119
|
const want = kind.artifact.match;
|
|
113
|
-
const has = (l) => l.some((e) => want.test(e.name)
|
|
120
|
+
const has = (l) => l.some((e) => want.test(e.name)
|
|
121
|
+
&& (kind.posture !== 'image' || usableImageArtifact(e)));
|
|
114
122
|
const standing = scanArtifacts(wt);
|
|
115
123
|
const wrote = has(changedArtifacts(artifactsBefore, standing));
|
|
116
124
|
const present = wrote || ((job.kind !== 'task' || job.redo === true) && has(standing));
|
|
@@ -126,11 +134,18 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
|
|
|
126
134
|
});
|
|
127
135
|
}
|
|
128
136
|
}
|
|
137
|
+
const prerequisite = res.prerequisite;
|
|
138
|
+
const cannotRequest = prerequisite && job.prerequisitesAccepted !== true
|
|
139
|
+
? 'This server does not support prerequisite-card requests yet. This work has not been delivered.'
|
|
140
|
+
: prerequisite?.taskKind === 'image' && (job.runtime ?? 'claude') !== 'codex'
|
|
141
|
+
? 'This agent runs on a CLI that cannot generate images. Image cards require Codex’s own image generation; this agent cannot run the requested texture work.'
|
|
142
|
+
: null;
|
|
129
143
|
return report(
|
|
130
144
|
{
|
|
131
145
|
turnId,
|
|
146
|
+
...(prerequisite && !cannotRequest ? { prerequisite } : {}),
|
|
132
147
|
outcome: res.outcome,
|
|
133
|
-
answer: envScrub(res.answer ?? '').slice(0, 8000),
|
|
148
|
+
answer: cannotRequest ?? envScrub(res.answer ?? '').slice(0, 8000),
|
|
134
149
|
...(commits.length ? { commits } : {}),
|
|
135
150
|
// SCRUBBED like the answer beside it: a raised card lands in the
|
|
136
151
|
// project doc every member reads, and a brief quoting the `.env` the
|