mixdog 0.9.34 → 0.9.35

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 (69) hide show
  1. package/README.md +6 -0
  2. package/package.json +4 -2
  3. package/scripts/_devtools-stub.mjs +1 -0
  4. package/scripts/_jitter-fuzz.jsx +42 -0
  5. package/scripts/_jitter-fuzz.mjs +44410 -0
  6. package/scripts/_jitter-fuzz2.jsx +30 -0
  7. package/scripts/_jitter-fuzz2.mjs +44400 -0
  8. package/scripts/_jitter-probe.jsx +35 -0
  9. package/scripts/_jitter-probe.mjs +44397 -0
  10. package/scripts/_jp2.jsx +16 -0
  11. package/scripts/_jp2.mjs +45614 -0
  12. package/scripts/agent-live-arg-guard-smoke.mjs +20 -0
  13. package/scripts/agent-live-path-suffix-smoke.mjs +34 -0
  14. package/scripts/agent-live-toolcall-args-smoke.mjs +45 -0
  15. package/scripts/apply-patch-edit-smoke.mjs +71 -0
  16. package/scripts/async-notify-settlement-test.mjs +99 -0
  17. package/scripts/forwarder-rebind-tail-test.mjs +67 -0
  18. package/scripts/live-worker-smoke.mjs +1 -1
  19. package/scripts/pending-completion-drop-test.mjs +46 -27
  20. package/scripts/provider-toolcall-test.mjs +1 -0
  21. package/scripts/recall-bench-cases.json +1 -0
  22. package/scripts/session-bench.mjs +44 -0
  23. package/scripts/shell-hardening-test.mjs +209 -0
  24. package/scripts/ship-mode-test.mjs +98 -0
  25. package/scripts/steering-drain-buckets-test.mjs +59 -0
  26. package/scripts/tool-smoke.mjs +46 -10
  27. package/scripts/worker-notify-rejection-test.mjs +51 -0
  28. package/src/lib/mixdog-debug.cjs +69 -0
  29. package/src/rules/agent/00-common.md +4 -5
  30. package/src/rules/agent/00-core.md +13 -20
  31. package/src/rules/agent/20-skip-protocol.md +3 -8
  32. package/src/rules/agent/30-explorer.md +31 -54
  33. package/src/rules/lead/01-general.md +3 -6
  34. package/src/rules/lead/lead-brief.md +10 -16
  35. package/src/rules/lead/lead-tool.md +3 -5
  36. package/src/rules/shared/01-tool.md +7 -9
  37. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +13 -2
  38. package/src/runtime/agent/orchestrator/providers/gemini.mjs +19 -23
  39. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +21 -5
  40. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +15 -0
  41. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +13 -13
  42. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +21 -7
  43. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +1 -5
  44. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +71 -1
  45. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +17 -9
  46. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +127 -4
  47. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +105 -28
  48. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +2 -2
  49. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
  50. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +7 -3
  51. package/src/runtime/memory/lib/memory-embed.mjs +1 -1
  52. package/src/runtime/memory/lib/query-handlers.mjs +25 -10
  53. package/src/runtime/memory/tool-defs.mjs +3 -3
  54. package/src/runtime/shared/background-tasks.mjs +21 -1
  55. package/src/runtime/shared/tool-execution-contract.mjs +29 -16
  56. package/src/session-runtime/model-route-api.mjs +3 -3
  57. package/src/session-runtime/provider-auth-api.mjs +13 -0
  58. package/src/session-runtime/runtime-core.mjs +40 -6
  59. package/src/session-runtime/tool-catalog.mjs +3 -2
  60. package/src/session-runtime/workflow-agents-api.mjs +2 -2
  61. package/src/standalone/agent-tool/notify.mjs +14 -1
  62. package/src/tui/dist/index.mjs +63 -25
  63. package/src/tui/engine/session-flow.mjs +26 -13
  64. package/src/tui/engine/turn.mjs +7 -8
  65. package/src/tui/engine.mjs +4 -2
  66. package/src/vendor/statusline/bin/statusline-route.mjs +60 -4
  67. package/src/vendor/statusline/src/gateway/route-meta.mjs +37 -8
  68. package/src/workflows/default/WORKFLOW.md +26 -32
  69. package/src/workflows/solo/WORKFLOW.md +12 -13
@@ -137,16 +137,15 @@ export function createRunTurn(bag) {
137
137
  const deferredEntries = []; // creation-order list; each is pushed at most once
138
138
  // Push this entry AND every earlier-created still-deferred entry, in order,
139
139
  // so transcript order always matches call order even when a later card's
140
- // result/timer fires before an earlier one's.
140
+ // result/timer fires before an earlier one's. Commit the collected cards in
141
+ // ONE state update: emitting one pushItem() per deferred card made a tool
142
+ // batch climb into view one row/card at a time ("툭툭" upward row jitter).
141
143
  const flushDeferredUpTo = (entry) => {
142
144
  if (!entry) return;
143
- for (const e of deferredEntries) {
144
- if (e.seq > entry.seq) break;
145
- if (e.pushed) continue;
146
- e.pushed = true;
147
- if (e.timer) { clearTimeout(e.timer); e.timer = null; }
148
- try { e.push(); } catch {}
149
- }
145
+ const specs = collectDeferredUpTo(entry);
146
+ if (!specs.length) return;
147
+ flags.pushingFromDeferredEntry = true;
148
+ try { appendItemsBatch(specs); } finally { flags.pushingFromDeferredEntry = false; }
150
149
  };
151
150
  flags.flushDeferredBeforeImmediatePush = () => {
152
151
  if (!deferredEntries.length) return;
@@ -114,10 +114,12 @@ const TOOL_APPROVAL_TIMEOUT_MS = (() => {
114
114
  // runTurn holding busy=true with no unwind, so submit() only ever queues and
115
115
  // the UI is permanently input-dead. On trip we abort the in-flight run through
116
116
  // the SAME interrupt path Esc uses, force busy=false, and drain the queue.
117
- // Generous default (30min); env-overridable for tuning/tests.
117
+ // Default is intentionally finite (5min): 30min made a stuck provider/worker
118
+ // look like the Lead session had frozen mid-turn. Env-overridable for long
119
+ // local experiments.
118
120
  const LEAD_TURN_TIMEOUT_MS = (() => {
119
121
  const value = Number(process.env.MIXDOG_LEAD_TURN_TIMEOUT_MS);
120
- return Number.isFinite(value) && value > 0 ? Math.max(10_000, Math.round(value)) : 30 * 60_000;
122
+ return Number.isFinite(value) && value > 0 ? Math.max(10_000, Math.round(value)) : 5 * 60_000;
121
123
  })();
122
124
 
123
125
  // Opt-in diagnostic trace for the hang chain (runTurn start/end, busy-queue
@@ -15,6 +15,7 @@ import {
15
15
  compactBoundaryDenominator,
16
16
  defaultEffectiveContextWindowPercent,
17
17
  } from '../src/gateway/route-meta.mjs';
18
+ import { getModelMetadataSync } from '../../../runtime/agent/orchestrator/providers/model-catalog.mjs';
18
19
 
19
20
  function positiveInt(value) {
20
21
  const n = parseInt(String(value || ''), 10);
@@ -196,6 +197,8 @@ function routeContextMeta(provider, info = {}, inherited = {}) {
196
197
  const rawContextWindow = firstPositiveWindow(
197
198
  inherited.rawContextWindow,
198
199
  inherited.raw_context_window,
200
+ info.context_window,
201
+ info.max_context_window,
199
202
  info.contextWindow,
200
203
  info.maxContextWindow,
201
204
  info.max_input_tokens,
@@ -463,6 +466,26 @@ function configuredGatewayStatus(options = {}) {
463
466
  ? presets.find(p => p?.provider === provider && p?.model === model)
464
467
  : null);
465
468
  const info = loadCachedModel(provider, model) || {};
469
+ // Catalog/known metadata is authoritative for the context window. The cached
470
+ // model file can hold a STALE window (e.g. Opus 4.8 cached at 272k after its
471
+ // window grew to the catalog's 1M); routeContextMeta reads info.contextWindow,
472
+ // so reconcile it against the catalog before deriving the boundary. A live
473
+ // provider API window on the cached row (context_window / max_context_window)
474
+ // still outranks the catalog.
475
+ const _catalog = getModelMetadataSync(model, provider);
476
+ const _catalogWindow = num(
477
+ _catalog?.contextWindow
478
+ ?? _catalog?.maxContextWindow
479
+ ?? _catalog?.context_window
480
+ ?? _catalog?.max_context_window,
481
+ 0,
482
+ );
483
+ const _apiWindow = num(info?.context_window ?? info?.max_context_window, 0);
484
+ const _cachedWindow = num(info?.contextWindow ?? info?.maxContextWindow, 0);
485
+ if (!_apiWindow && _catalogWindow > 0 && _cachedWindow !== _catalogWindow) {
486
+ info.contextWindow = _catalogWindow;
487
+ info.maxContextWindow = _catalogWindow;
488
+ }
466
489
  const effort = aliasTarget
467
490
  ? cleanString(aliasTarget.effort || gateway.effort || gateway.displayEffort || preset?.effort)
468
491
  : inherit
@@ -546,8 +569,18 @@ export function loadGatewayStatus(options = {}) {
546
569
  const routeMatchesConfigured = configured
547
570
  ? configured.provider === active.gateway_provider && configured.model === active.gateway_model
548
571
  : true;
572
+ const activeRouteBoundary = compactBoundaryForStatus({
573
+ autoCompactTokenLimit: active.gateway_auto_compact_token_limit,
574
+ contextWindow: active.gateway_context_window,
575
+ rawContextWindow: active.gateway_raw_context_window,
576
+ });
577
+ const configuredRouteBoundary = configured ? compactBoundaryForStatus(configured) : null;
578
+ const routeBoundaryMatchesConfigured = !configured
579
+ || !configuredRouteBoundary
580
+ || !activeRouteBoundary
581
+ || configuredRouteBoundary === activeRouteBoundary;
549
582
  const metricsOwnCurrentSession = !!(sessionStatus || gatewayCcSessionMatch || gatewayTranscriptMatch || activeTranscriptMatch);
550
- const metricsMatch = gatewayClientHostMatch && metricsOwnCurrentSession && routeMatchesConfigured;
583
+ const metricsMatch = gatewayClientHostMatch && metricsOwnCurrentSession && routeMatchesConfigured && routeBoundaryMatchesConfigured;
551
584
  // Strong ownership = this session's own status file or an explicit session/
552
585
  // transcript id match from the gateway advert. `activeTranscriptMatch` (the
553
586
  // global active-instance transcript merely naming the current session) is NOT
@@ -555,12 +588,35 @@ export function loadGatewayStatus(options = {}) {
555
588
  // session while its stored gateway metrics (context pct, last usage) still
556
589
  // belong to the previous one.
557
590
  const strongSessionOwnership = !!(sessionStatus || gatewayCcSessionMatch || gatewayTranscriptMatch);
558
- const lastUsage = gatewayClientHostMatch && strongSessionOwnership && active.gateway_last_usage && typeof active.gateway_last_usage === 'object'
591
+ // A model switch can keep strong session ownership while the active gateway
592
+ // advert still contains the previous route's lastUsage.compact. Do not let
593
+ // that stale compact boundary override the configured/current route window.
594
+ const lastUsage = gatewayClientHostMatch && strongSessionOwnership && routeMatchesConfigured && routeBoundaryMatchesConfigured && active.gateway_last_usage && typeof active.gateway_last_usage === 'object'
559
595
  ? active.gateway_last_usage
560
596
  : null;
561
- const lastCompact = lastUsage?.compact && typeof lastUsage.compact === 'object'
597
+ const rawLastCompact = lastUsage?.compact && typeof lastUsage.compact === 'object'
562
598
  ? lastUsage.compact
563
599
  : null;
600
+ // A model/window switch updates the active route fields (context/raw window)
601
+ // but can leave the PREVIOUS route's boundary inside gateway_last_usage.compact.
602
+ // routeBoundaryMatchesConfigured only checks the active route fields, so that
603
+ // stale compact telemetry would otherwise survive and, via compactBoundaryTokens,
604
+ // resurrect the previous boundary in compactBoundaryForStatus below. Drop the
605
+ // compact telemetry when its own implied boundary contradicts the active route
606
+ // window.
607
+ const lastCompactBoundary = rawLastCompact
608
+ ? compactBoundaryForStatus({}, rawLastCompact)
609
+ : null;
610
+ const lastCompact = rawLastCompact
611
+ && (!activeRouteBoundary || !lastCompactBoundary || lastCompactBoundary === activeRouteBoundary)
612
+ ? rawLastCompact
613
+ : null;
614
+ const statusLastUsage = lastUsage
615
+ ? {
616
+ ...lastUsage,
617
+ compact: lastCompact,
618
+ }
619
+ : null;
564
620
  const contextBoundary = compactBoundaryForStatus({
565
621
  autoCompactTokenLimit: active.gateway_auto_compact_token_limit,
566
622
  contextWindow: active.gateway_context_window,
@@ -616,7 +672,7 @@ export function loadGatewayStatus(options = {}) {
616
672
  contextUsedPct: metricsMatch
617
673
  ? recomputedContextUsedPct ?? (strongSessionOwnership ? num(active.gateway_context_used_pct, null) : null)
618
674
  : null,
619
- lastUsage,
675
+ lastUsage: statusLastUsage,
620
676
  quotaWindows: activeQuotaWindows.length ? activeQuotaWindows : (configuredStatus?.quotaWindows || []),
621
677
  // Provenance for monotonic hysteresis: live active-instance windows are
622
678
  // own-instance data timestamped by the advert's updatedAt; otherwise inherit
@@ -9,7 +9,6 @@ import {
9
9
  estimateMessagesTokens,
10
10
  estimateRequestReserveTokens,
11
11
  } from '../../../../runtime/agent/orchestrator/session/context-utils.mjs';
12
- import { DEFAULT_EFFECTIVE_CONTEXT_WINDOW_PERCENT } from '../../../../runtime/agent/orchestrator/session/compact/constants.mjs';
13
12
  import { CLAUDE_CURRENT_MODE } from './claude-current.mjs';
14
13
 
15
14
  const GATEWAY_USAGE_FILE = 'gateway-usage.local.json';
@@ -100,11 +99,11 @@ function boundedPercent(value, fallback = null) {
100
99
  }
101
100
 
102
101
  export function defaultEffectiveContextWindowPercent(_provider) {
103
- // Gateway-routed models use catalog/provider context metadata (LiteLLM,
104
- // models.dev, or native provider catalogs). Reserve a small universal
105
- // headroom for output/tool/system tokens while keeping the raw model window
106
- // visible separately.
107
- return DEFAULT_EFFECTIVE_CONTEXT_WINDOW_PERCENT;
102
+ // Gateway/statusline should report the full model boundary. The 10% safety
103
+ // headroom belongs to semantic-agent compact triggers, not to the main
104
+ // recall-fasttrack denominator.
105
+ void _provider;
106
+ return 100;
108
107
  }
109
108
 
110
109
  function effectiveContextWindowPercent(provider, info = {}, seed = {}) {
@@ -265,11 +264,34 @@ function mergeModelInfo(provider, model, providerObj) {
265
264
  const fromProvider = modelInfoFromProvider(providerObj, model);
266
265
  const fromCache = loadCachedModel(provider, model);
267
266
  const fromCatalog = getModelMetadataSync(model, provider);
268
- return {
267
+ const merged = {
269
268
  ...(fromCatalog || {}),
270
269
  ...(fromCache || {}),
271
270
  ...(fromProvider || {}),
272
271
  };
272
+ // Catalog/known metadata is authoritative for the context window. Spreading
273
+ // fromCache after fromCatalog otherwise lets a STALE cached window (e.g. Opus
274
+ // 4.8 cached at 272k after its window grew to the catalog's 1M) override the
275
+ // catalog. Only a live provider API window (context_window / max_context_window)
276
+ // outranks the catalog; when the provider info supplies no such window and the
277
+ // catalog disagrees with the merged (cached) window, prefer the catalog.
278
+ const providerApiWindow = num(
279
+ fromProvider?.context_window ?? fromProvider?.max_context_window,
280
+ 0,
281
+ );
282
+ const catalogWindow = num(
283
+ fromCatalog?.contextWindow
284
+ ?? fromCatalog?.maxContextWindow
285
+ ?? fromCatalog?.context_window
286
+ ?? fromCatalog?.max_context_window,
287
+ 0,
288
+ );
289
+ const mergedWindow = num(merged?.contextWindow ?? merged?.maxContextWindow, 0);
290
+ if (!providerApiWindow && catalogWindow > 0 && mergedWindow !== catalogWindow) {
291
+ merged.contextWindow = catalogWindow;
292
+ merged.maxContextWindow = catalogWindow;
293
+ }
294
+ return merged;
273
295
  }
274
296
 
275
297
  export function readGatewayRouteInfo(seed = {}, providerObj = null) {
@@ -312,7 +334,14 @@ export function readGatewayRouteInfo(seed = {}, providerObj = null) {
312
334
  ?? cleanBool(preset?.fast)
313
335
  ?? false;
314
336
  const info = mergeModelInfo(provider, model, providerObj);
315
- const rawContextWindow = num(info?.contextWindow ?? info?.maxContextWindow ?? info?.max_input_tokens, 0)
337
+ const rawContextWindow = num(
338
+ info?.context_window
339
+ ?? info?.max_context_window
340
+ ?? info?.contextWindow
341
+ ?? info?.maxContextWindow
342
+ ?? info?.max_input_tokens,
343
+ 0,
344
+ )
316
345
  || num(seed.contextWindow, 0);
317
346
  const effectivePercent = effectiveContextWindowPercent(provider, info, seed);
318
347
  const contextWindow = effectiveContextWindow(rawContextWindow, effectivePercent);
@@ -7,39 +7,33 @@ agents: worker, heavy-worker, reviewer, debugger, maintainer
7
7
 
8
8
  # Default Workflow
9
9
 
10
- HARD APPROVAL GATE — no execution (changes, state mutations, delegation)
11
- before an explicit go-ahead ("do it", "proceed", "ㄱㄱ"); read-only
12
- exploration only. Diagnosis agreement or problem-pointing is NOT approval.
10
+ HARD APPROVAL GATE — before explicit go-ahead ("do it", "proceed", "ㄱㄱ"),
11
+ only read-only exploration; no changes, state mutations, or delegation.
12
+ Diagnosis agreement/problem-pointing is NOT approval.
13
13
 
14
- Lead supervises: delegates, coordinates, judges, decides. Route by complexity
15
- (after approval):
16
- - Lead directly: only one-step fixes doable in a single turn, plus
17
- coordination, config, and git deployment. Everything else delegates.
18
- - Worker: any multi-step or multi-file implementation. Heavy Worker:
19
- high-complexity scopes.
20
- - Reviewer: verify implementation scopes. Debugger: very high complexity, or
21
- root-causing already failed once.
14
+ Lead supervises/delegates/coordinates/judges/decides. After approval route by
15
+ complexity: Lead directly only one-step single-turn fixes plus coordination,
16
+ config, and git deployment; everything else delegates. Worker = multi-step or
17
+ multi-file implementation; Heavy Worker = high-complexity scopes; Reviewer =
18
+ implementation verification; Debugger = very high complexity or root-cause
19
+ after a failed fix.
22
20
 
23
- 1. Plan — present a draft plan before ANY implementation; if not approved,
24
- revise and re-present (ping-pong) until an explicit go-ahead.
25
- 2. Delegate — maximize distribution: split the work into as many independent
26
- implementation scopes as possible and hand each to its own worker or
27
- heavy-worker, all spawned in the SAME turn (parallel by default; sequential
28
- steps only inside one complex scope, gated build/test-green). Fan them out
29
- across agents, never one at a time. Only a genuinely inseparable single
30
- scope stays whole, and say so. Briefs per the Lead brief contract. After
31
- spawning async agents, END THE TURN.
32
- 3. Review spawn one reviewer 1:1 per implementation scope, all reviewers in
33
- the same turn once their scopes land.
34
- Cross-check agent results yourself; send fixes back to the original scope
35
- and loop fix -> re-verify until clean. Skip only for simple low-risk work.
36
- Debugger first when the user asks for debugging or a bug survives 2+ fix
37
- cycles.
38
- On each agent report: tell the user what was received (scope + verdict)
39
- and how work proceeds, marked in-progress — never as a conclusion.
40
- 4. Report — final report briefs the whole work vs the approved plan and the
41
- verified result, distinct from interim updates. Never forward raw agent
42
- output. Ask about ship/deploy when relevant; deploy/build/commit only on an
43
- explicit user request, after feedback with no issues.
21
+ 1. Plan — present a draft before ANY implementation; revise/re-present until
22
+ explicit go-ahead.
23
+ 2. Delegate — maximize parallel distribution: split independent implementation
24
+ scopes to separate worker/heavy-worker agents, all spawned in the SAME turn.
25
+ Sequential steps only inside one inseparable complex scope, gated
26
+ build/test-green; say when a scope is inseparable. Briefs follow Lead brief
27
+ contract. After spawning async agents, END THE TURN.
28
+ 3. Review once scopes land, spawn one reviewer per implementation scope, all
29
+ in the same turn. Cross-check results yourself; send fixes to the original
30
+ scope and loop fix -> re-verify until clean. Skip only simple low-risk work.
31
+ Use Debugger first when asked for debugging or a bug survives 2+ fix cycles.
32
+ On each agent report, relay scope+verdict and next work as in-progress,
33
+ never as a conclusion.
34
+ 4. Report final report covers work vs approved plan and verified result,
35
+ separate from interim updates. Never forward raw agent output. Ask about
36
+ ship/deploy when relevant; deploy/build/commit only on explicit user request
37
+ after feedback with no issues.
44
38
 
45
39
  On major direction shifts mid-work, pause and re-consult the user.
@@ -7,22 +7,21 @@ agents:
7
7
 
8
8
  # Solo Workflow
9
9
 
10
- HARD APPROVAL GATE — no execution (changes, state mutations) before an
11
- explicit go-ahead ("do it", "proceed", "ㄱㄱ"); read-only exploration only.
12
- Diagnosis agreement or problem-pointing is NOT approval.
10
+ HARD APPROVAL GATE — before explicit go-ahead ("do it", "proceed", "ㄱㄱ"),
11
+ only read-only exploration; no changes or state mutations. Diagnosis
12
+ agreement/problem-pointing is NOT approval.
13
13
 
14
- 1. Plan — present a draft plan before ANY implementation; if not approved,
15
- revise and re-present (ping-pong) until an explicit go-ahead. When
16
- ambiguous, restate the plan and ask.
17
- 2. Execute Lead does all work directly; delegation forbidden. Interim
18
- updates are marked in-progress — never phrased as conclusions.
14
+ 1. Plan — present a draft before ANY implementation; revise/re-present until
15
+ explicit go-ahead. If ambiguous, restate the plan and ask.
16
+ 2. Execute Lead does all work directly; delegation forbidden. Interim updates
17
+ are in-progress, never conclusions.
19
18
  3. Verify — check and fix directly until clean or a blocker is reported.
20
- 4. Report — final report briefs the whole work vs the approved plan, the
21
- verification result, and remaining risk/next step, distinct from interim
22
- updates. Deploy/build/commit only after user feedback with no issues.
19
+ 4. Report — final report covers work vs approved plan, verification result, and
20
+ remaining risk/next step, distinct from interim updates. Deploy/build/commit
21
+ only after user feedback with no issues.
23
22
 
24
23
  On major direction shifts mid-work, pause and re-consult the user.
25
24
 
26
25
  Delegation rule:
27
- - Never delegate, spawn, send, or ask any agent to perform work; ignore any
28
- available-agent section while this workflow is active.
26
+ - Never delegate/spawn/send or ask agents to work; ignore available-agent
27
+ sections while this workflow is active.