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
|
@@ -6,8 +6,9 @@ import { finishRepositoryDelivery, allResolutionsCommitted } from './repositoryD
|
|
|
6
6
|
* decides WHICH turns run and makes sure each one is settled exactly once; this
|
|
7
7
|
* runs one, inside the place lock the lane already holds — the begun-guard
|
|
8
8
|
* (workAgentTurnBegun.mjs), the worktree and its branch facts, the kind's
|
|
9
|
-
* refusals, the prompt, the CLI and its resume
|
|
10
|
-
*
|
|
9
|
+
* refusals, the prompt, the CLI and its resume (whose markers are
|
|
10
|
+
* workAgentResume.mjs's since 2026-10-18), the trace and pulse relays, and the
|
|
11
|
+
* re-measurement after it exits — then hands the
|
|
11
12
|
* measurements to the one settlement decision (workAgentTurnOutcome.mjs) and
|
|
12
13
|
* posts the body that decision chose.
|
|
13
14
|
*
|
|
@@ -17,38 +18,39 @@ import { finishRepositoryDelivery, allResolutionsCommitted } from './repositoryD
|
|
|
17
18
|
* answered. `measured` is the same bargain the other way: the branch facts and
|
|
18
19
|
* the spend, written here as they are learned, read by that catch.
|
|
19
20
|
*/
|
|
20
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
21
21
|
import { PROJECT_ID } from './config.mjs';
|
|
22
22
|
import { setRuntimeLimit } from './daemonState.mjs';
|
|
23
23
|
import { gitOrNull } from './git.mjs';
|
|
24
|
+
import { turnCommits } from './turnReceipts.mjs';
|
|
25
|
+
import { addUsage } from './turnUsage.mjs';
|
|
24
26
|
import { stashCard } from './agentCards.mjs';
|
|
25
27
|
import { c } from './ui.mjs';
|
|
26
28
|
import { runTurn } from './runTurn.mjs';
|
|
27
29
|
import { killGroup } from './processGroup.mjs';
|
|
28
|
-
import {
|
|
30
|
+
import { AGENT_TASK_SPEC } from './prompts.mjs';
|
|
31
|
+
import { agentKickoff, agentFollowupKickoff, agentTurnSystem } from './workAgentTurnShape.mjs';
|
|
29
32
|
import { AGENT_TASK_KINDS, agentTaskKindOf, unknownAgentTaskKind } from './agentTaskKinds.mjs';
|
|
30
33
|
import { knowledgeDirFor } from './knowledgeFiles.mjs';
|
|
31
34
|
import { scrub as envScrub } from './uplinkScrub.mjs';
|
|
32
35
|
import { canRun, runtimeById } from './runtimes.mjs';
|
|
33
36
|
import { toolEventOf, CLAUDE_TOOL_PROSE_KINDS, scrubNativeText } from './runtimeEvents.mjs';
|
|
34
37
|
import { makeTraceRelay } from './trace.mjs';
|
|
35
|
-
import {
|
|
36
|
-
import { TURN_PROFILES } from './turnProfile.mjs';
|
|
37
|
-
import { postureToolsNote } from './postureTools.mjs';
|
|
38
|
+
import { prepareCardTools } from './workAgentTools.mjs';
|
|
38
39
|
import { runTurnResumingOnce } from './resumeLost.mjs';
|
|
39
|
-
import {
|
|
40
|
-
import { CODEX_THREAD_RE } from './workSessionRuntime.mjs';
|
|
40
|
+
import { runAgentWithInternalWaits } from './agentInternalWait.mjs';
|
|
41
41
|
import { brainFor } from './workBrain.mjs';
|
|
42
42
|
import { refreshRestartBase } from './workAgentRecovery.mjs';
|
|
43
43
|
import { begunTurnRefusal } from './workAgentTurnBegun.mjs';
|
|
44
|
-
import { agentTurnSettlement } from './workAgentTurnOutcome.mjs';
|
|
44
|
+
import { agentTurnSettlement, interruptedTurnSettlement } from './workAgentTurnOutcome.mjs';
|
|
45
45
|
import { ensureArtifactDir, ensureFenceScratch } from './artifacts.mjs';
|
|
46
46
|
import { agentBrowserHome } from './noWindowEnv.mjs';
|
|
47
|
-
import {
|
|
47
|
+
import { agentTurnResume } from './workAgentResume.mjs';
|
|
48
48
|
import { createDeliveryReporter } from './workDelivery.mjs';
|
|
49
49
|
import { createLiveMessages } from './workLiveMessages.mjs';
|
|
50
50
|
import { createOrchestrator } from './orchestrator.mjs';
|
|
51
|
-
import {
|
|
51
|
+
import { claudeOrchestrationSafe } from './orchestratorHooks.mjs';
|
|
52
|
+
import { codexSubagentsSafe } from './runtimeCodex.mjs';
|
|
53
|
+
import { measuredRuntimeVersion } from './runtimeDetection.mjs';
|
|
52
54
|
|
|
53
55
|
export function createAgentTurnExecution({
|
|
54
56
|
admit,
|
|
@@ -70,6 +72,7 @@ export function createAgentTurnExecution({
|
|
|
70
72
|
agentChildren,
|
|
71
73
|
postAgentTrace,
|
|
72
74
|
postAgentActivity,
|
|
75
|
+
postAgentContext = () => {},
|
|
73
76
|
postAgentParked,
|
|
74
77
|
fetchAgentFiles,
|
|
75
78
|
makeLiveMessages = createLiveMessages,
|
|
@@ -82,38 +85,6 @@ export function createAgentTurnExecution({
|
|
|
82
85
|
|
|
83
86
|
const lastAgentBeat = new Map(); // agentId -> last activity POST, ms
|
|
84
87
|
|
|
85
|
-
/** Every sha that landed on this branch between two points. Measured, never
|
|
86
|
-
* asserted — this is the whole reason an agent is not asked for its own. */
|
|
87
|
-
const commitsBetween = (wt, from) => {
|
|
88
|
-
if (!from) return [];
|
|
89
|
-
/**
|
|
90
|
-
* `--not <base>` and `--no-merges`, because `from..HEAD` alone reports
|
|
91
|
-
* BASE'S commits as this turn's receipts the moment anything folds base in
|
|
92
|
-
* — which the stale path does on purpose before a merge. A receipt naming
|
|
93
|
-
* somebody else's commit is worse than a missing one.
|
|
94
|
-
*/
|
|
95
|
-
/**
|
|
96
|
-
* IT CANNOT THROW, and that guard is the whole point of it being here.
|
|
97
|
-
*
|
|
98
|
-
* `git()` throws on a non-zero exit, and `baseRef()` is built from the
|
|
99
|
-
* project's Base branch setting — free text an owner types, never verified
|
|
100
|
-
* against the remote. Point it at a branch with no tracking ref (`develop`
|
|
101
|
-
* on a repo whose remote branch is `dev`) and this exits "fatal: ambiguous
|
|
102
|
-
* argument". The throw escaped `runAgentTurn` AFTER the CLI had already run
|
|
103
|
-
* the card, so `postAgentTurn` was never reached, the server never settled
|
|
104
|
-
* the turn, and the next poll handed back the identical turn — the same
|
|
105
|
-
* card re-run every poll for six hours, on the operator's shared account,
|
|
106
|
-
* piling commits onto the review branch, silently.
|
|
107
|
-
*
|
|
108
|
-
* An unreadable range means we cannot MEASURE the receipts, which is a
|
|
109
|
-
* smaller failure than not settling: the turn still reports, and ship-time
|
|
110
|
-
* reconciliation books whatever no card claimed. Missing beats fabricated
|
|
111
|
-
* and both beat a stall.
|
|
112
|
-
*/
|
|
113
|
-
const out = gitOrNull(['log', '--format=%H', '--no-merges', `${from}..HEAD`, '--not', baseRef()], wt);
|
|
114
|
-
return out?.split('\n').map((x) => x.trim()).filter(Boolean).slice(0, 50) ?? [];
|
|
115
|
-
};
|
|
116
|
-
|
|
117
88
|
/**
|
|
118
89
|
* The turn, run inside its place's writer lock (the lane takes it). `turnId`,
|
|
119
90
|
* `agentId` and `place` are the lane's own readings of the job, handed down
|
|
@@ -179,6 +150,7 @@ export function createAgentTurnExecution({
|
|
|
179
150
|
measured.branch = branch;
|
|
180
151
|
|
|
181
152
|
const rt = job.runtime || 'claude';
|
|
153
|
+
const liveInput = runtimeById(rt)?.liveInput === true;
|
|
182
154
|
/**
|
|
183
155
|
* WHAT THIS CARD HANDS BACK, AND THE POSTURE THAT FOLLOWS (0.97.0).
|
|
184
156
|
*
|
|
@@ -279,64 +251,9 @@ export function createAgentTurnExecution({
|
|
|
279
251
|
return;
|
|
280
252
|
}
|
|
281
253
|
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
// distinguishes the first turn from every later one across restarts.
|
|
286
|
-
//
|
|
287
|
-
// IT MEANS "CLAUDE HAS SPOKEN HERE", and since 2026-09-24 only a Claude
|
|
288
|
-
// turn writes it: an agent can be switched between CLIs, and a marker a
|
|
289
|
-
// codex turn left behind would send the first Claude turn to
|
|
290
|
-
// `--continue` a directory holding no Claude conversation at all.
|
|
291
|
-
const ranMarker = sessionMetaPath(wt, 'flowviant-agent-ran');
|
|
292
|
-
/**
|
|
293
|
-
* CODEX RESUMES BY ITS OWN THREAD ID, PINNED PER AGENT (2026-09-24).
|
|
294
|
-
*
|
|
295
|
-
* Resume was Claude-only, because codex's `resume --last` is
|
|
296
|
-
* MACHINE-GLOBAL and would cross-resume whichever conversation spoke
|
|
297
|
-
* last anywhere on the box. The cost was that every codex card started
|
|
298
|
-
* a stranger: no memory of the card before it, and — worse — an answer
|
|
299
|
-
* to the agent's own question arrived at a codex that never asked it.
|
|
300
|
-
* The Workbench solved the same problem in 0.69.0 by pinning the id
|
|
301
|
-
* `thread.started` announces; this is that, keyed by the AGENT rather
|
|
302
|
-
* than a tab. The id is shape-checked before it rides argv.
|
|
303
|
-
*/
|
|
304
|
-
const codexThreadMarker =
|
|
305
|
-
rt === 'codex' ? sessionMetaPath(wt, 'flowviant-agent-codex-thread', agentId) : null;
|
|
306
|
-
let codexResumeId = null;
|
|
307
|
-
if (codexThreadMarker && existsSync(codexThreadMarker)) {
|
|
308
|
-
try {
|
|
309
|
-
const v = readFileSync(codexThreadMarker, 'utf8').trim();
|
|
310
|
-
if (CODEX_THREAD_RE.test(v)) codexResumeId = v;
|
|
311
|
-
} catch {
|
|
312
|
-
/* unreadable marker — run fresh */
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
let seenThreadId = null;
|
|
316
|
-
const claudeMarker = rt === 'claude' ? sessionMetaPath(wt, RESUME_MARKERS.claude, agentId) : null;
|
|
317
|
-
let claudeResumeId = null;
|
|
318
|
-
try { const id = readFileSync(claudeMarker, 'utf8').trim(); if (CLAUDE_SESSION_RE.test(id)) claudeResumeId = id; } catch { /* unknown */ }
|
|
319
|
-
const resume =
|
|
320
|
-
rt === 'codex'
|
|
321
|
-
? Boolean(codexResumeId)
|
|
322
|
-
: rt === 'claude' && (Boolean(claudeResumeId) || Boolean(ranMarker && existsSync(ranMarker)));
|
|
323
|
-
/**
|
|
324
|
-
* …AND THE HOME THAT THREAD IS FILED UNDER, KEPT PER AGENT (2026-09-29).
|
|
325
|
-
*
|
|
326
|
-
* Codex's thread index records each rollout under the CODEX_HOME the turn
|
|
327
|
-
* ran with, as that path — not the personal store the link leads to — so
|
|
328
|
-
* the per-turn home in /tmp every turn used to get died with its turn and
|
|
329
|
-
* took the pinned thread with it: the next card's `resume` failed "no
|
|
330
|
-
* rollout found" and the agent went to Stuck. Beside the thread marker,
|
|
331
|
-
* in the worktree's private git dir: outside the working tree (never
|
|
332
|
-
* committed, never seen by `git add -A`), refreshed by every turn and
|
|
333
|
-
* removed only with the worktree, when the agent retires and its threads
|
|
334
|
-
* stop mattering. One agent runs one turn at a time (its place's writer
|
|
335
|
-
* lock), so no two turns refresh it at once. Null (no git dir to hold it)
|
|
336
|
-
* is the per-turn home again, and a lost resume then runs once fresh.
|
|
337
|
-
*/
|
|
338
|
-
const codexAgentHome =
|
|
339
|
-
rt === 'codex' ? sessionMetaPath(wt, 'flowviant-agent-codex-home', agentId) : null;
|
|
254
|
+
// Which conversation this turn resumes, and the kept Codex home it is
|
|
255
|
+
// filed under: the agent's resume across turns (workAgentResume.mjs).
|
|
256
|
+
const agentResume = agentTurnResume({ rt, wt, agentId, sessionMetaPath });
|
|
340
257
|
|
|
341
258
|
/**
|
|
342
259
|
* THE TURN'S FILES, ON DISK BEFORE THE PROMPT NAMES THEM (0.112.0) — the
|
|
@@ -370,11 +287,11 @@ export function createAgentTurnExecution({
|
|
|
370
287
|
* commits it made are on the branch, and a reviewer is entitled to read
|
|
371
288
|
* what was asked for either way.
|
|
372
289
|
*
|
|
373
|
-
* ONE SPEC BUILDER (`AGENT_TASK_SPEC`) shared with the kickoff
|
|
374
|
-
* what the reviewer reads is byte-identical
|
|
375
|
-
* card's discussion included (0.106.0): both
|
|
376
|
-
* `job.cardThread`, and the builder prints it inside
|
|
377
|
-
* card's files after it (0.112.0), fetched just above.
|
|
290
|
+
* ONE SPEC BUILDER (`AGENT_TASK_SPEC`) shared with the kickoff
|
|
291
|
+
* (workAgentTurnShape.mjs), so what the reviewer reads is byte-identical
|
|
292
|
+
* to what the agent read — the card's discussion included (0.106.0): both
|
|
293
|
+
* are handed the same `job.cardThread`, and the builder prints it inside
|
|
294
|
+
* the spec — and the card's files after it (0.112.0), fetched just above.
|
|
378
295
|
* Failure is swallowed inside `stashCard`: a note about a turn may never
|
|
379
296
|
* cost the turn.
|
|
380
297
|
*/
|
|
@@ -386,6 +303,12 @@ export function createAgentTurnExecution({
|
|
|
386
303
|
);
|
|
387
304
|
}
|
|
388
305
|
|
|
306
|
+
// ORCHESTRATED ONLY WHERE THE CLI CAN BE GATED (2026-10-17). v2 reaches every
|
|
307
|
+
// job at the floor; a fenced card or a CLI below its measured floor (unknown
|
|
308
|
+
// included) has no controller, so no marker: its notes fold on receipt.
|
|
309
|
+
const codexVersion = rt === 'codex' ? measuredRuntimeVersion('codex')?.version : undefined;
|
|
310
|
+
const orchestrating = job.orchestrationVersion === 2 && posture === 'build' &&
|
|
311
|
+
(rt === 'claude' ? claudeOrchestrationSafe(measuredRuntimeVersion('claude')?.version) : rt === 'codex' && codexSubagentsSafe(codexVersion));
|
|
389
312
|
/**
|
|
390
313
|
* THE WHOLE STREAM, not just its latest line — see trace.mjs.
|
|
391
314
|
*
|
|
@@ -398,12 +321,12 @@ export function createAgentTurnExecution({
|
|
|
398
321
|
agentId,
|
|
399
322
|
turnId,
|
|
400
323
|
post: postAgentTrace,
|
|
401
|
-
orchestrationVersion:
|
|
324
|
+
orchestrationVersion: orchestrating ? 2 : undefined,
|
|
402
325
|
scrub: envScrub,
|
|
403
326
|
});
|
|
404
327
|
// A live Send can choose its path as soon as the CLI starts, including a
|
|
405
328
|
// silent solo turn. Do not leave a two-second first-flush race at spawn.
|
|
406
|
-
if (
|
|
329
|
+
if (orchestrating) await trace.flush(1_000);
|
|
407
330
|
/**
|
|
408
331
|
* Prose the structured event will carry anyway, dropped so a read does
|
|
409
332
|
* not render twice — but ONLY on a runtime whose stream reaches
|
|
@@ -479,122 +402,42 @@ export function createAgentTurnExecution({
|
|
|
479
402
|
let projectTools = null;
|
|
480
403
|
let orchestrator = null;
|
|
481
404
|
try {
|
|
482
|
-
|
|
483
|
-
if (orchestrated && posture === 'build' && ['claude', 'codex'].includes(rt)) {
|
|
405
|
+
if (orchestrating) {
|
|
484
406
|
orchestrator = await createOrchestrator({ turnId, runtime: rt, trace, admit,
|
|
485
|
-
|
|
407
|
+
// Whether the words reached the CLI; Codex reads its inbox from hooks.
|
|
408
|
+
notify: message => liveInput ? run.live?.inject(message) === true : false,
|
|
486
409
|
taken: id => run.live?.onTaken(id) });
|
|
487
410
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const
|
|
491
|
-
const source = sources.find((r) => r.wt === wt);
|
|
492
|
-
const snapshot = readBaseTools(source?.repoRoot ?? repoRoot, source?.baseRef ?? baseRef());
|
|
493
|
-
for (const other of sources.filter((r) => r !== source)) {
|
|
494
|
-
const base = readBaseTools(other.repoRoot, other.baseRef);
|
|
495
|
-
snapshot.instructions.push(...[...base.instructions, ...base.skillFiles].map((file) => ({ ...file, path: `repositories/${other.folder}/${file.path}` })));
|
|
496
|
-
}
|
|
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
|
-
} });
|
|
501
|
-
const mountsProjectMcp = TURN_PROFILES[posture]?.projectMcp === true && (rt === 'claude' || rt === 'codex');
|
|
502
|
-
// A fenced posture on a runner that ignores its own file there (Codex's
|
|
503
|
-
// `--ignore-user-config`) takes the servers as `fencedMount` instead.
|
|
504
|
-
const fencedMount = mountsProjectMcp && posture !== 'build' ? projectTools.fencedMount : null;
|
|
505
|
-
// THE MACHINE'S OWN SERVERS ON CLAUDE (2026-10-11, the owner): the turn
|
|
506
|
-
// is not strict, and every `.mcp.json` name from the worktree up is
|
|
507
|
-
// disabled so only the base copy speaks for the project. Null (an
|
|
508
|
-
// unboundable walk) mounts strictly: the base copy alone.
|
|
509
|
-
const claudeDisabled = mountsProjectMcp && rt === 'claude' ? mcpJsonServerNamesAbove(wt) : null;
|
|
510
|
-
// What this card's posture withholds, said to the agent — with the
|
|
511
|
-
// servers it did and did not mount, and why — so it relays the fence
|
|
512
|
-
// instead of inventing a setting (postureTools.mjs). Empty for a code card.
|
|
513
|
-
const toolsNote = postureToolsNote({
|
|
514
|
-
posture,
|
|
515
|
-
runtime: rt,
|
|
516
|
-
mounted: !mountsProjectMcp ? [] : fencedMount ? fencedMount.mounted : projectTools.mcpServers,
|
|
517
|
-
machine: mountsProjectMcp && (rt === 'codex' || claudeDisabled != null),
|
|
518
|
-
unmounted: [...projectTools.mcpUnmounted.map((u) => ({ ...u, source: 'project' })), ...(fencedMount ? fencedMount.unmounted : [])],
|
|
519
|
-
});
|
|
411
|
+
const tools = prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources: repositoryRun?.sources ?? [], repositoryTool: repositoryRun?.tool ?? null, orchestratorConfig: orchestrator?.config ?? null, orchestrated: Boolean(orchestrator), codexHome: agentResume.codexHome });
|
|
412
|
+
projectTools = tools.projectTools;
|
|
413
|
+
const { toolsNote } = tools;
|
|
520
414
|
const agentTurnArgs = {
|
|
521
|
-
...((
|
|
415
|
+
...((liveInput || orchestrator) ? { liveMessages: (run.live = makeLiveMessages({
|
|
522
416
|
scope: 'agent', turnId, cwd: wt, sessionMetaPath, interrupted: () => run.interrupted,
|
|
523
417
|
observe: message => orchestrator?.note(message),
|
|
524
418
|
...(rt === 'codex' ? { nativeIds: () => null } : {}),
|
|
525
419
|
format: async (followup) => {
|
|
526
420
|
orchestrator?.note(followup);
|
|
527
421
|
const attached = followup.attachments?.length ? await fetchAgentFiles(wt, followup.attachments.map((file) => ({ ...file, from: 'message' }))) : { message: [], card: [] };
|
|
528
|
-
return
|
|
529
|
-
askedByName: followup.askedByName, task: job.task, cardThread: job.cardThread,
|
|
530
|
-
investigationContext: job.investigationContext, researchContext: job.researchContext,
|
|
531
|
-
messageFiles: attached.message, cardFiles: files.card, position: job.position ?? 1, total: job.total ?? 1 });
|
|
422
|
+
return agentFollowupKickoff(job, followup, { messageFiles: attached.message, cardFiles: files.card, orchestrated: Boolean(orchestrator) });
|
|
532
423
|
},
|
|
533
424
|
})) } : {}),
|
|
534
|
-
prompt
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
task: job.task,
|
|
539
|
-
// The card's discussion (0.106.0), printed inside THE CARD by
|
|
540
|
-
// the same builder the stash above wrote down — and the files
|
|
541
|
-
// on it (0.112.0), after it, the same way.
|
|
542
|
-
resumeBody: job.body,
|
|
543
|
-
investigationContext: job.investigationContext,
|
|
544
|
-
researchContext: job.researchContext,
|
|
545
|
-
cardThread: job.cardThread,
|
|
546
|
-
cardFiles: files.card,
|
|
547
|
-
position: job.position ?? 1,
|
|
548
|
-
total: job.total ?? 1,
|
|
549
|
-
})
|
|
550
|
-
: AGENT_HUMAN_KICKOFF({
|
|
551
|
-
agentName: job.agentName,
|
|
552
|
-
// INTERRUPT NOW: the previous run was stopped to send this.
|
|
553
|
-
interrupted: job.interrupted === true,
|
|
554
|
-
message: job.body ?? '',
|
|
555
|
-
investigationContext: job.investigationContext,
|
|
556
|
-
researchContext: job.researchContext,
|
|
557
|
-
askedByName: job.askedByName,
|
|
558
|
-
task: job.task,
|
|
559
|
-
// The discussion on the one card this message is about, in a
|
|
560
|
-
// fence of its own after the person's words (0.106.0).
|
|
561
|
-
cardThread: job.cardThread,
|
|
562
|
-
// The files (0.112.0): the person's inside their words, the
|
|
563
|
-
// card's at the foot of its discussion.
|
|
564
|
-
messageFiles: files.message,
|
|
565
|
-
cardFiles: files.card,
|
|
566
|
-
position: job.position ?? 1,
|
|
567
|
-
total: job.total ?? 1,
|
|
568
|
-
}),
|
|
569
|
-
// The knowledge paragraph, when this box holds a library — the same
|
|
570
|
-
// composer the tabs use, so an agent and a tab can never be told two
|
|
571
|
-
// different things about the same directory.
|
|
572
|
-
// Spoken to the CLI that runs it (0.115.0): a mockup on Codex is
|
|
573
|
-
// told it is Codex, never Claude.
|
|
574
|
-
system: withProjectContext(SYSTEM_AGENT_FOR(taskKind, runtimeById(rt)?.label, { prerequisitesAccepted: job.prerequisitesAccepted === true }), {
|
|
575
|
-
knowledgeDir: knowledgeDirFor(repoRoot),
|
|
576
|
-
// ARTIFACTS (0.94.0), while the server can show one — an agent's
|
|
577
|
-
// land on its page, under the facts row.
|
|
578
|
-
artifacts: getArtifactsAccepted(),
|
|
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}` : ''),
|
|
425
|
+
// The kickoff and the system prompt, composed by the agent lane's
|
|
426
|
+
// shape (workAgentTurnShape.mjs) from what this run measured.
|
|
427
|
+
prompt: agentKickoff(job, { cardFiles: files.card, messageFiles: files.message }),
|
|
428
|
+
system: agentTurnSystem({ taskKind, runtimeLabel: runtimeById(rt)?.label, prerequisitesAccepted: job.prerequisitesAccepted === true, knowledgeDir: knowledgeDirFor(repoRoot), artifactsAccepted: getArtifactsAccepted(), toolsNote, artifactInstructions, toolInstructions: projectTools.instructions, repositoryRun, orchestrated: Boolean(orchestrator) }),
|
|
580
429
|
agentTools: projectTools,
|
|
581
430
|
writableDirs: repositoryRun?.writableDirs,
|
|
582
|
-
...((
|
|
431
|
+
...((tools.envNeeded || repositoryRun?.tool || orchestrator) ? { mcpEnv: { ...tools.env, ...(repositoryRun?.tool?.env ?? {}), ...(orchestrator?.env ?? {}) } } : {}),
|
|
583
432
|
orchestration: Boolean(orchestrator),
|
|
433
|
+
...(rt === 'codex' ? { codexVersion } : {}),
|
|
584
434
|
// NOBODY IS AT THIS SCREEN (2026-09-29): no display, BROWSER=none, and
|
|
585
435
|
// a browser profile of the agent's own, kept beside its Codex home —
|
|
586
436
|
// so a Chrome it starts to look at its page never reaches the
|
|
587
437
|
// person's (noWindowEnv.mjs).
|
|
588
438
|
browserHome: agentBrowserHome(sessionMetaPath, wt, agentId),
|
|
589
|
-
//
|
|
590
|
-
|
|
591
|
-
...(mountsProjectMcp && rt === 'claude'
|
|
592
|
-
? {
|
|
593
|
-
mcpArgs: [...(claudeDisabled ? [] : ['--strict-mcp-config']), '--mcp-config', projectTools.mcpPath],
|
|
594
|
-
projectMcpServers: projectTools.mcpServers,
|
|
595
|
-
...(claudeDisabled ? { machineMcp: claudeDisabled, mcpCheckout: source?.repoRoot ?? repoRoot } : {}),
|
|
596
|
-
}
|
|
597
|
-
: fencedMount ? { mcpArgs: fencedMount.args } : {}),
|
|
439
|
+
// What the card mounts, as its runner takes it (workAgentTools.mjs).
|
|
440
|
+
...tools.mountArgs,
|
|
598
441
|
knowledgeDir: knowledgeDirFor(repoRoot),
|
|
599
442
|
// The kind's posture, as the one turn profile (turnProfile.mjs):
|
|
600
443
|
// build for code, the fenced design/research postures otherwise.
|
|
@@ -604,16 +447,17 @@ export function createAgentTurnExecution({
|
|
|
604
447
|
onSpawnResult: result => orchestrator?.spawnResult(result),
|
|
605
448
|
cwd: wt,
|
|
606
449
|
runtime: rt,
|
|
607
|
-
resume,
|
|
608
|
-
resumeThreadId:
|
|
450
|
+
resume: agentResume.resume,
|
|
451
|
+
resumeThreadId: agentResume.resumeThreadId,
|
|
609
452
|
onInit: (init) => {
|
|
610
453
|
orchestrator?.root(init.sessionId);
|
|
611
|
-
|
|
612
|
-
|
|
454
|
+
// A server that did not connect, as the CLI said (unknown: nothing).
|
|
455
|
+
trace.mcpStatus(init.mcpServers);
|
|
456
|
+
agentResume.sawSession(init.sessionId);
|
|
613
457
|
},
|
|
614
458
|
onThreadId: (id) => {
|
|
615
459
|
orchestrator?.root(id);
|
|
616
|
-
|
|
460
|
+
agentResume.sawThread(id);
|
|
617
461
|
},
|
|
618
462
|
onAnswer: (t) => {
|
|
619
463
|
lastAnswer = t;
|
|
@@ -622,6 +466,25 @@ export function createAgentTurnExecution({
|
|
|
622
466
|
...brain,
|
|
623
467
|
answerFromResult: true,
|
|
624
468
|
label: c.cyan('[agent]'),
|
|
469
|
+
// The CLI's own context measure while the turn runs (claudeInput.mjs),
|
|
470
|
+
// the latest one at a time; the server clears it at turn end.
|
|
471
|
+
onContext: (() => {
|
|
472
|
+
let next = null;
|
|
473
|
+
let busy = false;
|
|
474
|
+
return (ctx) => {
|
|
475
|
+
next = ctx;
|
|
476
|
+
if (busy) return;
|
|
477
|
+
busy = true;
|
|
478
|
+
void (async () => {
|
|
479
|
+
while (next) {
|
|
480
|
+
const measured = next;
|
|
481
|
+
next = null;
|
|
482
|
+
await postAgentContext(agentId, turnId, measured);
|
|
483
|
+
}
|
|
484
|
+
busy = false;
|
|
485
|
+
})();
|
|
486
|
+
};
|
|
487
|
+
})(),
|
|
625
488
|
// The CLI's own tail, relayed. Throttled by the same rule the tab's
|
|
626
489
|
// narrator keeps: overwritten, never appended, and ignored by the
|
|
627
490
|
// board past ~90 seconds.
|
|
@@ -757,7 +620,7 @@ export function createAgentTurnExecution({
|
|
|
757
620
|
resume: rt === 'codex' && posture === 'research' ? false : args.resume,
|
|
758
621
|
runtime: rt,
|
|
759
622
|
beforeFresh: () => {
|
|
760
|
-
|
|
623
|
+
agentResume.forgetThread();
|
|
761
624
|
lastAnswer = null;
|
|
762
625
|
model = null;
|
|
763
626
|
},
|
|
@@ -775,13 +638,10 @@ export function createAgentTurnExecution({
|
|
|
775
638
|
const counted = usage;
|
|
776
639
|
return runCli({
|
|
777
640
|
...agentTurnArgs, prompt, liveContinuation: true,
|
|
778
|
-
onUsage: (next) => agentTurnArgs.onUsage(
|
|
779
|
-
['input', 'output', 'cacheCreate', 'cacheRead'].map((k) =>
|
|
780
|
-
[k, (counted?.[k] ?? 0) + (next?.[k] ?? 0)])
|
|
781
|
-
)),
|
|
641
|
+
onUsage: (next) => agentTurnArgs.onUsage(addUsage(counted, next)),
|
|
782
642
|
writableDirs: repositoryRun.sourcesNow().filter((r) => r.wt !== wt).map((r) => r.wt),
|
|
783
643
|
resume: true,
|
|
784
|
-
resumeThreadId:
|
|
644
|
+
resumeThreadId: agentResume.threadNow() ?? agentTurnArgs.resumeThreadId,
|
|
785
645
|
});
|
|
786
646
|
},
|
|
787
647
|
});
|
|
@@ -829,23 +689,9 @@ export function createAgentTurnExecution({
|
|
|
829
689
|
}
|
|
830
690
|
for (const ch of spawnedChildren) workChildren.delete(ch);
|
|
831
691
|
if (agentChildren.get(place) === child) agentChildren.delete(place);
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
} catch {
|
|
836
|
-
/* a missing marker only costs one un-resumed turn */
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
// The thread THIS turn spoke under, pinned so the next card, answer
|
|
840
|
-
// or merge-resolve resumes it. After the turn, like the tab lane: an
|
|
841
|
-
// id learned mid-turn is only true once the turn that learned it ends.
|
|
842
|
-
if (codexThreadMarker && seenThreadId && CODEX_THREAD_RE.test(seenThreadId)) {
|
|
843
|
-
try {
|
|
844
|
-
writeFileSync(codexThreadMarker, seenThreadId);
|
|
845
|
-
} catch {
|
|
846
|
-
/* best-effort — the next turn runs fresh */
|
|
847
|
-
}
|
|
848
|
-
}
|
|
692
|
+
// The ran marker and the thread this turn spoke under, written now
|
|
693
|
+
// that the turn is over (workAgentResume.mjs).
|
|
694
|
+
agentResume.persist();
|
|
849
695
|
/**
|
|
850
696
|
* AN ACTION THAT CHANGES WHAT THE MACHINE WOULD MEASURE MUST CAUSE A
|
|
851
697
|
* NEW MEASUREMENT — the rule every session settle keeps, and the one
|
|
@@ -866,7 +712,7 @@ export function createAgentTurnExecution({
|
|
|
866
712
|
}
|
|
867
713
|
|
|
868
714
|
|
|
869
|
-
const commits =
|
|
715
|
+
const commits = turnCommits({ repositoryReceipts: repositoryRun?.receipts, wt, before, baseRef });
|
|
870
716
|
/**
|
|
871
717
|
* A PERSON INTERRUPTED IT (Interrupt now). Whatever the CLI printed was
|
|
872
718
|
* cut off, so there is no outcome to read: the turn reports `interrupted`
|
|
@@ -877,17 +723,7 @@ export function createAgentTurnExecution({
|
|
|
877
723
|
*/
|
|
878
724
|
if (run.interrupted) {
|
|
879
725
|
measured.ranCli = spawnedChildren.size > 0;
|
|
880
|
-
await postAgentTurn({
|
|
881
|
-
turnId,
|
|
882
|
-
outcome: 'nothing',
|
|
883
|
-
interrupted: true,
|
|
884
|
-
ran: spawnedChildren.size > 0,
|
|
885
|
-
commits,
|
|
886
|
-
...(usage ? { usage } : {}),
|
|
887
|
-
...(model ? { model } : {}),
|
|
888
|
-
...(branch ? { branch } : {}),
|
|
889
|
-
worktree: wt,
|
|
890
|
-
});
|
|
726
|
+
await postAgentTurn(interruptedTurnSettlement({ turnId, ran: spawnedChildren.size > 0, commits, usage, model, branch, wt }));
|
|
891
727
|
return;
|
|
892
728
|
}
|
|
893
729
|
// WHAT THE TURN REPORTS is one decision, off what was measured here
|
|
@@ -926,5 +762,5 @@ export function createAgentTurnExecution({
|
|
|
926
762
|
} finally { await repositoryRun?.tool?.close(); }
|
|
927
763
|
};
|
|
928
764
|
|
|
929
|
-
return { runAgentTurnInPlace
|
|
765
|
+
return { runAgentTurnInPlace };
|
|
930
766
|
}
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
* and settles once, so a crash in the run can never leave a turn unsettled to
|
|
17
17
|
* be re-offered and re-run on the operator's account.
|
|
18
18
|
*
|
|
19
|
+
* `interruptedTurnSettlement` is the sixth (2026-10-18, the SOLID pass): a turn
|
|
20
|
+
* a person stopped, where no outcome is read. The run and the lane's catch
|
|
21
|
+
* both send it — the catch when the stopped run then threw — and they had
|
|
22
|
+
* already drifted apart, so its body is built here once.
|
|
23
|
+
*
|
|
19
24
|
* `agentTurnSettlement` returns `{ res, limit, body, final }`; the caller posts
|
|
20
25
|
* `body` through the one turn-done wire, clears or sets the machine's limit off
|
|
21
26
|
* `res`/`limit`, parks on `limit`, and runs the review-entry beat only after a
|
|
@@ -27,6 +32,7 @@ import { changedArtifacts, scanArtifacts } from './artifacts.mjs';
|
|
|
27
32
|
import { scrub as envScrub } from './uplinkScrub.mjs';
|
|
28
33
|
import { usableImageArtifact } from './imageArtifact.mjs';
|
|
29
34
|
import { withoutTurnDeclaration } from './turnDeclaration.mjs';
|
|
35
|
+
import { ANSWER_DELIVERED_USES, researchUseOf, withResearchHeader } from './researchUse.mjs';
|
|
30
36
|
|
|
31
37
|
export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage, model = null, commits, artifactsBefore, branch, wt }) {
|
|
32
38
|
const res = (lastAnswer && parseTurnResult(lastAnswer)) || parseTurnResult(out);
|
|
@@ -117,9 +123,9 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
|
|
|
117
123
|
*/
|
|
118
124
|
// Existing document deliveries default to the Markdown proof. Only an
|
|
119
125
|
// explicit temporary-use declaration can deliver through the stored answer.
|
|
120
|
-
const briefUse =
|
|
126
|
+
const briefUse = researchUseOf(job.task?.brief);
|
|
121
127
|
const researchUse = kind.posture === 'research' ? briefUse ?? res.researchUse : null;
|
|
122
|
-
const temporaryResearch =
|
|
128
|
+
const temporaryResearch = ANSWER_DELIVERED_USES.includes(researchUse);
|
|
123
129
|
if (res.outcome === 'delivered' && temporaryResearch && !res.answer?.trim()) {
|
|
124
130
|
return report({ turnId, outcome: 'nothing', answer: 'The research turn returned no findings.', branch, worktree: wt });
|
|
125
131
|
}
|
|
@@ -154,7 +160,7 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
|
|
|
154
160
|
...(prerequisite && !cannotRequest ? { prerequisite } : {}),
|
|
155
161
|
outcome: res.outcome,
|
|
156
162
|
answer: cannotRequest ?? envScrub(kind.posture === 'research' && researchUse
|
|
157
|
-
?
|
|
163
|
+
? withResearchHeader(researchUse, res.answer ?? '') : res.answer ?? ''),
|
|
158
164
|
...(commits.length ? { commits } : {}),
|
|
159
165
|
// SCRUBBED like the answer beside it: a raised card lands in the
|
|
160
166
|
// project doc every member reads, and a brief quoting the `.env` the
|
|
@@ -235,3 +241,24 @@ export function failedTurnSettlement({ turnId, error, commits = [], usage = null
|
|
|
235
241
|
...(wt ? { branch, worktree: wt } : {}),
|
|
236
242
|
};
|
|
237
243
|
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* A PERSON INTERRUPTED IT (Interrupt now), so there is no outcome to read:
|
|
247
|
+
* whatever the CLI printed was cut off. What was measured rides along — the
|
|
248
|
+
* commits it made, its spend, the model it ran on, the branch and the
|
|
249
|
+
* worktree — and nothing measured is nothing claimed. `ran` is whether its
|
|
250
|
+
* CLI started at all.
|
|
251
|
+
*/
|
|
252
|
+
export function interruptedTurnSettlement({ turnId, ran, commits, usage = null, model = null, branch = null, wt = null }) {
|
|
253
|
+
return {
|
|
254
|
+
turnId,
|
|
255
|
+
outcome: 'nothing',
|
|
256
|
+
interrupted: true,
|
|
257
|
+
ran,
|
|
258
|
+
commits,
|
|
259
|
+
...(usage ? { usage } : {}),
|
|
260
|
+
...(model ? { model } : {}),
|
|
261
|
+
...(branch ? { branch } : {}),
|
|
262
|
+
...(wt ? { worktree: wt } : {}),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
@@ -114,6 +114,21 @@ export function createAgentTurnReports({ REJECT_RETRY_MS }) {
|
|
|
114
114
|
}
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
+
/** The CLI's context measure for a running turn (2026-10-19); the server
|
|
118
|
+
* drops one whose turn already settled. Best effort, like the line. */
|
|
119
|
+
const postAgentContext = async (agentId, turnId, context) => {
|
|
120
|
+
try {
|
|
121
|
+
await fetch(AGENT_ACTIVITY_URL, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: fleetHeaders(),
|
|
124
|
+
signal: AbortSignal.timeout(15_000),
|
|
125
|
+
body: JSON.stringify({ agentId, turnId, context }),
|
|
126
|
+
});
|
|
127
|
+
} catch {
|
|
128
|
+
/* a readout; the next measurement replaces it */
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
117
132
|
const postAgentParked = async (reason, runtime) => {
|
|
118
133
|
try {
|
|
119
134
|
await fetch(AGENT_PARKED_URL, {
|
|
@@ -153,6 +168,7 @@ export function createAgentTurnReports({ REJECT_RETRY_MS }) {
|
|
|
153
168
|
postAgentTurn,
|
|
154
169
|
postAgentTrace,
|
|
155
170
|
postAgentActivity,
|
|
171
|
+
postAgentContext,
|
|
156
172
|
postAgentParked,
|
|
157
173
|
pruneHeldReports,
|
|
158
174
|
agentReported,
|