flowviant 0.125.30 → 0.125.31

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.
Files changed (43) hide show
  1. package/bin/lib/agentPlan.mjs +8 -0
  2. package/bin/lib/agentQueueContext.mjs +6 -0
  3. package/bin/lib/agentSplit.mjs +90 -0
  4. package/bin/lib/architectureContract.mjs +57 -0
  5. package/bin/lib/architectureReport.mjs +38 -0
  6. package/bin/lib/artifactContracts.mjs +21 -2
  7. package/bin/lib/claudeInput.mjs +1 -1
  8. package/bin/lib/claudeSubagents.mjs +46 -6
  9. package/bin/lib/claudeWorkflows.mjs +94 -0
  10. package/bin/lib/cliLimit.mjs +5 -0
  11. package/bin/lib/codexInput.mjs +191 -0
  12. package/bin/lib/codexSubagentEvents.mjs +3 -1
  13. package/bin/lib/codexSubagents.mjs +34 -9
  14. package/bin/lib/conversationOutput.mjs +14 -0
  15. package/bin/lib/crossCliChild.mjs +327 -0
  16. package/bin/lib/crossCliLaunch.mjs +92 -0
  17. package/bin/lib/crossCliPosture.mjs +55 -0
  18. package/bin/lib/crossCliResearch.mjs +19 -0
  19. package/bin/lib/fixtures/workflow-ultracode.jsonl +22 -0
  20. package/bin/lib/nativeAgentTools.mjs +12 -5
  21. package/bin/lib/orchestrator.mjs +24 -7
  22. package/bin/lib/orchestratorClient.mjs +8 -2
  23. package/bin/lib/orchestratorPrompt.mjs +13 -5
  24. package/bin/lib/orchestratorProtocol.mjs +18 -0
  25. package/bin/lib/outsideCli.mjs +124 -0
  26. package/bin/lib/prompts.mjs +7 -5
  27. package/bin/lib/runTurn.mjs +30 -8
  28. package/bin/lib/runtimeClaude.mjs +7 -3
  29. package/bin/lib/runtimeCodex.mjs +12 -6
  30. package/bin/lib/runtimeLimits.mjs +59 -1
  31. package/bin/lib/splitGate.mjs +115 -0
  32. package/bin/lib/subagentBounds.mjs +26 -0
  33. package/bin/lib/subagentLedger.mjs +110 -0
  34. package/bin/lib/trace.mjs +134 -6
  35. package/bin/lib/turnDeclaration.mjs +4 -4
  36. package/bin/lib/turnGaps.mjs +6 -2
  37. package/bin/lib/turnSubagents.mjs +23 -9
  38. package/bin/lib/wikiDraft.mjs +9 -0
  39. package/bin/lib/workAgentResume.mjs +11 -2
  40. package/bin/lib/workAgentTurnExecution.mjs +31 -7
  41. package/bin/lib/workAgentTurnOutcome.mjs +11 -0
  42. package/bin/lib/workAgentTurnReports.mjs +5 -2
  43. package/package.json +1 -1
@@ -17,6 +17,11 @@
17
17
  * bounds and settles its own records, so only an unowned turn is counted
18
18
  * here; and native transcript reads are readouts, so the drain at close is
19
19
  * bounded and never holds the card's answer.
20
+ *
21
+ * EVERY UPDATE NAMES ITS CLI AND, ONCE KNOWN, ITS KEY (2026-11-06, S4.1):
22
+ * the identity the CLI itself keeps for the child across turns — Claude's
23
+ * native agent id, Codex's child thread id — so a child resumed in a later
24
+ * turn is the same record there (workAgentResume.mjs keeps which turns).
20
25
  */
21
26
 
22
27
  import { handleStreamLine } from './claudeStream.mjs';
@@ -24,35 +29,44 @@ import { createClaudeSubagents } from './claudeSubagents.mjs';
24
29
  import { createClaudeSubagentProgress } from './claudeSubagentProgress.mjs';
25
30
  import { createCodexSubagentReader } from './codexSubagents.mjs';
26
31
  import { humanizeClaudeTool } from './runtimeEvents.mjs';
27
- import { SUBAGENT_CAP } from './subagentBounds.mjs';
32
+ import { SUBAGENT_CAP, SUBAGENT_KEY_MAX } from './subagentBounds.mjs';
33
+
34
+ /** The child's stable identity, `<cli>:<native id>`, or nothing yet. */
35
+ export function subagentKey(runtime, update) {
36
+ const native = runtime === 'codex' ? update.id : runtime === 'claude' ? update.nativeId : null;
37
+ const key = typeof native === 'string' && native ? `${runtime}:${native}` : null;
38
+ return key && key.length <= SUBAGENT_KEY_MAX ? key : null;
39
+ }
28
40
 
29
41
  /**
30
42
  * `sinks` are the turn's own: its console, activity, tool-event and text
31
43
  * sinks, which a Claude child's transcript rows feed as its stream does.
32
44
  */
33
- export function createTurnSubagents({ runtime, cwd, since, env, owned, onSubagent, onSpawnResult, resumeThreadId,
45
+ export function createTurnSubagents({ runtime, cwd, since, env, owned, onSubagent, onWorkflow, onSpawnResult, resumeThreadId, knownSubagents,
34
46
  sinks: { emit, onActivity, onToolEvent, appendText, onDetail } }) {
35
47
  // An orchestrator bounds its own records and settles them at close
36
48
  // (orchestrator.mjs). Counted here by stream id, refused retries used up
37
49
  // the 32 and later children's updates were dropped; the exit sweep
38
50
  // reported 'stopped' off stream state the controller had already settled.
39
51
  const agentStates = new Map();
40
- const reportSubagent = (update) => {
41
- if (!update?.id) return;
42
- if (!owned) {
43
- if (!agentStates.has(update.id) && agentStates.size >= SUBAGENT_CAP) return;
52
+ const reportSubagent = (raw) => {
53
+ if (!raw?.id) return;
54
+ const key = subagentKey(runtime, raw);
55
+ const update = { ...raw, cli: runtime, ...(key ? { key } : {}) };
56
+ // Past the cap a child is still handed on: the trace relay counts it as
57
+ // not recorded (its `notRecorded`), and the sweep does not follow it.
58
+ if (!owned && raw.kind !== 'workflow' && (agentStates.has(update.id) || agentStates.size < SUBAGENT_CAP))
44
59
  agentStates.set(update.id, update.status ?? agentStates.get(update.id) ?? 'running');
45
- }
46
60
  onSubagent?.(update);
47
61
  };
48
- const subagents = createClaudeSubagents(reportSubagent);
62
+ const subagents = createClaudeSubagents(reportSubagent, onWorkflow);
49
63
  const claudeProgress = runtime === 'claude' && onSubagent ? createClaudeSubagentProgress({
50
64
  cwd, since, env,
51
65
  onLine: line => handleStreamLine(line, { cwd, emit, onActivity, onToolEvent, appendText,
52
66
  answerFromResult: true, subagents, onDetail }),
53
67
  }) : null;
54
68
  const codexAgents = runtime === 'codex' && onSubagent ? createCodexSubagentReader({
55
- since, env,
69
+ since, env, knownTargets: knownSubagents ?? null,
56
70
  onSubagent: reportSubagent,
57
71
  onActivity: (activity) => {
58
72
  emit(activity.kind === 'error' ? activity.full ?? activity.label : activity.label + '\n', activity);
@@ -8,6 +8,7 @@ import { ensureRealSubdir } from './ownedDirs.mjs';
8
8
  import { isSafePath, assertWikiDraftFits } from './vaultDiff.mjs';
9
9
 
10
10
  import { wikiScalars as scalars, protectedWikiPage } from './wikiOwnership.mjs';
11
+ import { SYSTEM_ARCHITECTURE_PATH, readSystemArchitecture } from './architectureReport.mjs';
11
12
 
12
13
  /** Never follow links into an imported vault. A partial read cannot be a draft
13
14
  * basis: fail before the CLI starts rather than infer missing pages as deleted. */
@@ -60,6 +61,14 @@ export function beginWikiDraft(vaultDir) {
60
61
  discard: () => rmSync(dir, { recursive: true, force: true }),
61
62
  publish({ sha, repoFullName }) {
62
63
  const candidate = pages(dir, '', new Map(), true);
64
+ const architecture = candidate.get(SYSTEM_ARCHITECTURE_PATH);
65
+ const oldArchitecture = before.get(SYSTEM_ARCHITECTURE_PATH);
66
+ if (!protectedWikiPage(SYSTEM_ARCHITECTURE_PATH, oldArchitecture ?? architecture ?? '')) {
67
+ const parsed = readSystemArchitecture(architecture ?? '');
68
+ if (parsed.status === 'invalid' || (oldArchitecture && readSystemArchitecture(oldArchitecture).status === 'available' && parsed.status !== 'available')) {
69
+ throw new Error('Architecture report is incomplete or invalid; last good wiki retained');
70
+ }
71
+ }
63
72
  const changes = new Map();
64
73
  for (const [path, text] of candidate) {
65
74
  const old = before.get(path);
@@ -25,6 +25,7 @@
25
25
  import { existsSync, writeFileSync } from 'node:fs';
26
26
  import { CLAUDE_SESSION_RE, CODEX_THREAD_RE, RESUME_MARKERS } from './conversationIds.mjs';
27
27
  import { pinId, readPinnedId } from './resumeMarker.mjs';
28
+ import { subagentLedger } from './subagentLedger.mjs';
28
29
 
29
30
  /**
30
31
  * One agent turn's resume. `sessionMetaPath` is the lane's (a path in the
@@ -32,9 +33,11 @@ import { pinId, readPinnedId } from './resumeMarker.mjs';
32
33
  * `resumeThreadId`, the kept `codexHome`, and what the run tells it as the
33
34
  * CLI speaks: `sawSession` (Claude's init), `sawThread` (Codex's
34
35
  * `thread.started`), `forgetThread` (a lost resume retried fresh), `threadNow`
35
- * and, once the turn is over, `persist`.
36
+ * and, once the turn is over, `persist`. `subagents` is the agent's subagent
37
+ * ledger for this turn (subagentLedger.mjs): which turns each child appeared
38
+ * in, written with the markers.
36
39
  */
37
- export function agentTurnResume({ rt, wt, agentId, sessionMetaPath }) {
40
+ export function agentTurnResume({ rt, wt, agentId, sessionMetaPath, turnId }) {
38
41
  // ONE AGENT IS ONE DIRECTORY, so the CLI's own cwd-keyed resume is exactly
39
42
  // right here — the ambiguity that forced per-tab session pinning in the
40
43
  // Workbench (many tabs, one place) cannot arise. The marker is what
@@ -86,8 +89,13 @@ export function agentTurnResume({ rt, wt, agentId, sessionMetaPath }) {
86
89
  const codexAgentHome =
87
90
  rt === 'codex' ? sessionMetaPath(wt, 'flowviant-agent-codex-home', agentId) : null;
88
91
 
92
+ // Its children's turns, kept beside the markers (a child resumes only where
93
+ // its conversation does), per agent and per CLI: keys never cross CLIs.
94
+ const subagents = subagentLedger(sessionMetaPath(wt, `flowviant-agent-subagents-${rt}`, agentId), turnId);
95
+
89
96
  return {
90
97
  resume,
98
+ subagents,
91
99
  resumeThreadId: codexResumeId || claudeResumeId || undefined,
92
100
  codexHome: codexAgentHome,
93
101
  /** Claude's session id from its init, pinned at once (a Claude turn only). */
@@ -116,6 +124,7 @@ export function agentTurnResume({ rt, wt, agentId, sessionMetaPath }) {
116
124
  // id learned mid-turn is only true once the turn that learned it ends.
117
125
  // Best-effort — the next turn runs fresh.
118
126
  pinId(codexThreadMarker, CODEX_THREAD_RE, seenThreadId);
127
+ subagents.persist();
119
128
  },
120
129
  };
121
130
  }
@@ -30,6 +30,7 @@ import { finishRepositoryDelivery, allResolutionsCommitted } from './repositoryD
30
30
  */
31
31
  import { MODEL, PROJECT_ID } from './config.mjs';
32
32
  import { setRuntimeLimit } from './daemonState.mjs';
33
+ import { limitResetAt } from './runtimeLimits.mjs';
33
34
  import { gitOrNull } from './git.mjs';
34
35
  import { assertWorktree } from './worktreeHealth.mjs';
35
36
  import { placeTurnLockedByLivePid, recordTurnLock } from './turnLock.mjs';
@@ -48,6 +49,7 @@ import { scrub as envScrub } from './uplinkScrub.mjs';
48
49
  import { canRun, runtimeById } from './runtimes.mjs';
49
50
  import { toolEventOf, CLAUDE_TOOL_PROSE_KINDS, scrubNativeText } from './runtimeEvents.mjs';
50
51
  import { makeTraceRelay } from './trace.mjs';
52
+ import { createOutsideRuns } from './outsideCli.mjs';
51
53
  import { createWebsiteTrace } from './websiteTrace.mjs';
52
54
  import { prepareCardTools } from './workAgentTools.mjs';
53
55
  import { runTurnResumingOnce } from './resumeLost.mjs';
@@ -62,6 +64,7 @@ import { agentTurnResume } from './workAgentResume.mjs';
62
64
  import { createDeliveryReporter } from './workDelivery.mjs';
63
65
  import { createLiveMessages } from './workLiveMessages.mjs';
64
66
  import { createOrchestrator } from './orchestrator.mjs';
67
+ import { crossCliFor } from './crossCliLaunch.mjs';
65
68
  import { claudeOrchestrationSafe } from './orchestratorHooks.mjs';
66
69
  import { ORCHESTRATED_POSTURES, orchestratesPosture } from './orchestratorPrompt.mjs';
67
70
  import { knownCodexModels } from './runtimeOfferings.mjs';
@@ -78,6 +81,7 @@ import { readEnvironmentRecipe } from './reviewEnvironmentConfig.mjs';
78
81
  import { environmentInstructions, environmentOverlay, holdWorktreeEnvironment } from './reviewEnvironment.mjs';
79
82
  import { environmentCredentialNames } from './reviewDatabase.mjs';
80
83
  import { createRuntimeReview } from './runtimeReview.mjs';
84
+ import { splitBrain, splitPolicy } from './splitGate.mjs';
81
85
 
82
86
  export function createAgentTurnExecution({
83
87
  admit,
@@ -293,7 +297,7 @@ export function createAgentTurnExecution({
293
297
 
294
298
  // Which conversation this turn resumes, and the kept Codex home it is
295
299
  // filed under: the agent's resume across turns (workAgentResume.mjs).
296
- const agentResume = agentTurnResume({ rt, wt, agentId, sessionMetaPath });
300
+ const agentResume = agentTurnResume({ rt, wt, agentId, sessionMetaPath, turnId });
297
301
 
298
302
  /**
299
303
  * THE TURN'S FILES, ON DISK BEFORE THE PROMPT NAMES THEM (0.112.0) — the
@@ -370,7 +374,12 @@ export function createAgentTurnExecution({
370
374
  post: postAgentTrace,
371
375
  orchestrationVersion: orchestrating ? 2 : undefined,
372
376
  scrub: envScrub,
377
+ // Each child's CLI, and the turns a resumed one appeared in (S4.1).
378
+ cli: rt,
379
+ runsOf: agentResume.subagents.runsOf ?? undefined,
373
380
  });
381
+ // The lead's shell-outs to another CLI, shown as Outside rows (outsideCli.mjs).
382
+ const outsideRuns = createOutsideRuns(trace);
374
383
  // A live Send can choose its path as soon as the CLI starts, including a
375
384
  // silent solo turn. Do not leave a two-second first-flush race at spawn.
376
385
  if (orchestrating) await trace.flush(1_000);
@@ -405,6 +414,8 @@ export function createAgentTurnExecution({
405
414
  // run's children and contract: Claude always names --model (the daemon's
406
415
  // pin when the card has none); Codex's unpinned default is its own.
407
416
  const cardBrain = { model: brain.model ?? (rt === 'claude' ? MODEL : null), effort: brain.effort ?? null };
417
+ // The person's split answer or Always switch, when the server asks (splitGate.mjs).
418
+ const split = splitPolicy(job);
408
419
 
409
420
  let out = '';
410
421
  let child = null;
@@ -545,10 +556,15 @@ export function createAgentTurnExecution({
545
556
  assignmentContext: () => agentChildScope(job),
546
557
  // Codex's spawn takes only a catalog model (knownCodexModels, the
547
558
  // measured models_cache.json rows the roster already reports).
548
- children: { access: ORCHESTRATED_POSTURES[posture], ...cardBrain, ...(rt === 'codex' ? { catalog: knownCodexModels() } : {}) },
559
+ children: { access: ORCHESTRATED_POSTURES[posture], ...cardBrain, ...splitBrain(split, rt, cardBrain).brain, split, ...(rt === 'codex' ? { catalog: knownCodexModels() } : {}) },
549
560
  // Whether the words reached the CLI; Codex reads its inbox from hooks.
550
561
  notify: message => liveInput ? run.live?.inject(message) === true : false,
551
- taken: id => run.live?.onTaken(id) });
562
+ taken: id => run.live?.onTaken(id),
563
+ // Subagents on another CLI (crossCliLaunch.mjs), counted as live children while they run.
564
+ crossCli: crossCliFor({ rt, posture, wt, worktreeRepo, ledger: agentResume.subagents, trace, scrub: envScrub, onChild: ch => {
565
+ workChildren.set(ch, agentId); groupKillChildren?.add(ch);
566
+ return () => { workChildren.delete(ch); groupKillChildren?.delete(ch); };
567
+ } }) });
552
568
  }
553
569
  const tools = prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources: repositoryRun?.sources ?? [], repositoryTool: repositoryRun?.tool ?? null, orchestratorConfig: orchestrator?.config ?? null, uiReviewConfig: uiReview?.config, videoConfig: videoRender?.config ?? null, browserConfig: managedBrowser?.config, orchestrated: Boolean(orchestrator), codexHome: agentResume.codexHome,
554
570
  runtimeConfig: runtimeReview?.config,
@@ -579,7 +595,7 @@ export function createAgentTurnExecution({
579
595
  intermediateFiles,
580
596
  researchIdentity: { projectId: PROJECT_ID, agentId, turnId, taskId: job.task?.id ?? job.repositoryTaskId },
581
597
  prompt: agentKickoff(job, { cardFiles: files.card, messageFiles: files.message, intermediateFiles }),
582
- system: agentTurnSystem({ taskKind, taskType: job.task?.taskType, runtimeLabel: runtimeById(rt)?.label, prerequisitesAccepted: job.prerequisitesAccepted === true, knowledgeDir: knowledgeDirFor(repoRoot), artifactsAccepted: getArtifactsAccepted(), toolsNote, artifactInstructions, toolInstructions: projectTools.instructions, repositoryRun, orchestration: orchestrator ? { posture, runtime: rt, ...cardBrain } : null }),
598
+ system: agentTurnSystem({ taskKind, taskType: job.task?.taskType, runtimeLabel: runtimeById(rt)?.label, prerequisitesAccepted: job.prerequisitesAccepted === true, knowledgeDir: knowledgeDirFor(repoRoot), artifactsAccepted: getArtifactsAccepted(), toolsNote, artifactInstructions, toolInstructions: projectTools.instructions, repositoryRun, orchestration: orchestrator ? { posture, runtime: rt, ...cardBrain, split, crossCli: orchestrator.crossCli() } : null }),
583
599
  agentTools: projectTools,
584
600
  writableDirs: repositoryRun?.writableDirs,
585
601
  ...((tools.envNeeded || repositoryRun?.tool || orchestrator) ? { mcpEnv: { ...tools.env, ...(repositoryRun?.tool?.env ?? {}), ...(orchestrator?.env ?? {}) } } : {}),
@@ -597,7 +613,9 @@ export function createAgentTurnExecution({
597
613
  // build for code, the fenced design/research postures otherwise.
598
614
  profile: posture,
599
615
  cardTurn: Boolean(orchestrator),
600
- onSubagent: (update) => orchestrator ? orchestrator.observe(update) : trace.agent(update),
616
+ onWorkflow: row => trace.workflow(row),
617
+ onSubagent: (update) => update.kind === 'workflow' || !orchestrator ? trace.agent(update) : orchestrator.observe(update),
618
+ ...(rt === 'codex' ? { knownSubagents: agentResume.subagents.codexTargets() } : {}),
601
619
  onSpawnResult: result => orchestrator?.spawnResult(result),
602
620
  cwd: wt,
603
621
  runtime: rt,
@@ -705,6 +723,7 @@ export function createAgentTurnExecution({
705
723
  }
706
724
  return;
707
725
  }
726
+ outsideRuns.observe(name, input, meta);
708
727
  brief?.append(meta?.result ? 'tool result' : 'tool call', meta?.result
709
728
  ? { id: meta.id, exitCode: meta.exitCode, isError: meta.isError, output: meta.output }
710
729
  : { name, input });
@@ -714,7 +733,7 @@ export function createAgentTurnExecution({
714
733
  trace.tool(toolEventOf(name, input, wt, envScrub));
715
734
  },
716
735
  // The CLI's own per-turn token counts, off the `result` event. One
717
- // result per CLI invocation. Internal-wait recovery combines those
736
+ // cumulative report per CLI invocation. Internal-wait recovery combines those
718
737
  // counts before this callback; project accounting still happens
719
738
  // SERVER-side, behind the settle's idempotent win,
720
739
  // because a retried settle must not charge the same turn twice.
@@ -972,6 +991,9 @@ export function createAgentTurnExecution({
972
991
  if (body.outcome === 'delivered' && job.task?.taskType === 'tests' && res?.testFindings) {
973
992
  body.testFindings = measureTestFindings(res.testFindings, repositoryRun?.sourcesNow() ?? [{ wt, baseRef: baseRef(), id: job.repos?.length === 1 ? job.repos[0] : null }], envScrub);
974
993
  }
994
+ // The split this turn ran under, when the person approved it or Always is
995
+ // on: the batch's CLI, model and effort, as the job carried them.
996
+ if (orchestrator && split && split.mode !== 'ask') body.subagents = split;
975
997
  if (limit) {
976
998
  setRuntimeLimit(PROJECT_ID, rt, limit);
977
999
  // Every agent ON THIS CLI parks, because that account is shared: one
@@ -979,7 +1001,9 @@ export function createAgentTurnExecution({
979
1001
  // a machine can hold a Claude login and a Codex login, and a Codex
980
1002
  // limit is no reason to stop the Claude agents. The turn itself is
981
1003
  // reported as `nothing` — it did not deliver and it did not ask.
982
- await postAgentParked(limit, rt);
1004
+ // With the CLI's measured reset, when it said one (runtimeLimits.mjs).
1005
+ const codexEnv = agentResume.codexHome ? { ...process.env, CODEX_HOME: agentResume.codexHome } : undefined;
1006
+ await postAgentParked(limit, rt, await limitResetAt(rt, { threadId: agentResume.threadNow(), env: codexEnv }));
983
1007
  }
984
1008
  if (job.kind === 'merge_resolve') {
985
1009
  const resolution = () => repositoryRun
@@ -35,6 +35,7 @@ import { withoutTurnDeclaration } from './turnDeclaration.mjs';
35
35
  import { ANSWER_DELIVERED_USES, researchUseOf, withResearchHeader } from './researchUse.mjs';
36
36
  import { measuredModelExports } from './intermediateArtifacts.mjs';
37
37
  import { gapsMarkdown } from './turnGaps.mjs';
38
+ import { blockedAnswer } from './agentAsk.mjs';
38
39
 
39
40
  /** Materialization runs only for a declared delivery; settlement remains the
40
41
  * sole parser and the sole authority deciding whether it really delivered. */
@@ -130,6 +131,16 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
130
131
  */
131
132
  // Existing document deliveries default to the Markdown proof. Only an
132
133
  // explicit temporary-use declaration can deliver through the stored answer.
134
+ const required = res.gaps?.filter(g => g.disposition === 'required') ?? [];
135
+ if (res.outcome === 'delivered' && required.length && job.gapContinuationAccepted !== true) {
136
+ return report({ turnId, outcome: 'question',
137
+ answer: blockedAnswer('Required work remains. Should I finish it in this agent before delivery?', {
138
+ context: required.map(g => `${g.item}${g.why ? `: ${g.why}` : ''}`).join('\n\n'),
139
+ options: ['Finish the required work', 'Explain the remaining scope'],
140
+ }, envScrub),
141
+ ...(commits.length ? { commits } : {}), ...(usage ? { usage } : {}),
142
+ ...(res.progress ? { progress: envScrub(res.progress) } : {}), branch, worktree: wt });
143
+ }
133
144
  const briefUse = researchUseOf(job.task?.brief);
134
145
  const researchUse = kind.posture === 'research' ? briefUse ?? res.researchUse : null;
135
146
  const temporaryResearch = ANSWER_DELIVERED_USES.includes(researchUse);
@@ -148,7 +148,7 @@ export function createAgentTurnReports({ REJECT_RETRY_MS, baseDir = null }) {
148
148
  }
149
149
  };
150
150
 
151
- const postAgentParked = async (reason, runtime) => {
151
+ const postAgentParked = async (reason, runtime, resetsAt = null) => {
152
152
  try {
153
153
  await fetch(AGENT_PARKED_URL, {
154
154
  method: 'POST',
@@ -156,7 +156,10 @@ export function createAgentTurnReports({ REJECT_RETRY_MS, baseDir = null }) {
156
156
  signal: AbortSignal.timeout(15_000),
157
157
  // `runtime` scopes the park to that CLI's agents; a server that does
158
158
  // not know the key parks every agent, which is what it always did.
159
- body: JSON.stringify({ reason, ...(runtime ? { runtime } : {}) }),
159
+ // `resetsAt` is the CLI's measured reset (`limitResetAt`), sent only
160
+ // when measured: the server retries then, and an older server ignores
161
+ // the key and keeps its cooldown. A report, so no floor.
162
+ body: JSON.stringify({ reason, ...(runtime ? { runtime } : {}), ...(resetsAt ? { resetsAt } : {}) }),
160
163
  });
161
164
  } catch {
162
165
  /* the next turn will hit the same limit and try again */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowviant",
3
- "version": "0.125.30",
3
+ "version": "0.125.31",
4
4
  "description": "Run your own coding CLIs as build agents for Flowviant \u2014 Claude Code, Codex or Antigravity, on your own credentials. Holds your sessions, keeps a worktree per tab, and ships branches on your word.",
5
5
  "type": "module",
6
6
  "bin": {