mixdog 1.3.2 → 1.3.3

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 (171) hide show
  1. package/package.json +7 -7
  2. package/scripts/verify-runtime-dependency-versions.mjs +158 -0
  3. package/src/agents/reviewer/AGENT.md +5 -7
  4. package/src/agents/reviewer/agent.json +1 -1
  5. package/src/defaults/shipped-history.json +4 -0
  6. package/src/defaults/skills/browser-use/SKILL.md +6 -3
  7. package/src/defaults/skills/local-provider/SKILL.md +33 -6
  8. package/src/rules/lead/LEAD.md +5 -8
  9. package/src/rules/shared/45-destructive.md +5 -1
  10. package/src/rules/shared/60-verification.md +6 -4
  11. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +4 -1
  12. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
  13. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
  14. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  15. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +17 -4
  16. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
  17. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  18. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
  19. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  20. package/src/runtime/agent/orchestrator/providers/registry.mjs +10 -0
  21. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
  22. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
  23. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
  24. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +40 -5
  25. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +32 -11
  26. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -3
  27. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
  28. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +13 -0
  29. package/src/runtime/agent/orchestrator/session/agent-room.mjs +68 -12
  30. package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
  31. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +31 -8
  32. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
  33. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +117 -19
  34. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +1 -1
  35. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +3 -0
  36. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +147 -137
  37. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +37 -74
  38. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +0 -2
  39. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +2 -2
  40. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +13 -7
  41. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
  42. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
  43. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
  45. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
  46. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -0
  47. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +12 -20
  48. package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
  49. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +11 -5
  50. package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
  51. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
  52. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
  53. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +24 -15
  54. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +8 -0
  55. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
  56. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
  57. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +17 -1
  58. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
  59. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
  60. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
  61. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +7 -5
  63. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
  64. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +10 -1
  65. package/src/runtime/agent/orchestrator/session/routine-result.mjs +7 -4
  66. package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
  67. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +11 -2
  68. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
  69. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
  70. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
  71. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +7 -2
  72. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +13 -0
  73. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +76 -0
  74. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
  75. package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
  76. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
  77. package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
  78. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +18 -7
  79. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +2 -0
  80. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
  81. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
  82. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -0
  83. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +8 -2
  84. package/src/runtime/attachments/legacy-office.mjs +110 -0
  85. package/src/runtime/attachments/prompt-restore.mjs +70 -0
  86. package/src/runtime/attachments/store.mjs +60 -3
  87. package/src/runtime/effort-judge/judge-client.mjs +61 -14
  88. package/src/runtime/effort-judge/model-install.mjs +34 -2
  89. package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
  90. package/src/runtime/office/portable/portable-soffice.mjs +132 -30
  91. package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
  92. package/src/runtime/shared/agent-avatar.mjs +3 -2
  93. package/src/runtime/shared/agent-room-host.d.mts +31 -0
  94. package/src/runtime/shared/agent-room-host.mjs +58 -0
  95. package/src/runtime/shared/bot-hops.mjs +16 -11
  96. package/src/runtime/shared/redact-directories.mjs +18 -0
  97. package/src/runtime/shared/remove-directory.mjs +28 -0
  98. package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
  99. package/src/runtime/shared/session-sidebar-host.mjs +16 -3
  100. package/src/runtime/shared/tool-execution-contract.mjs +4 -0
  101. package/src/runtime/shared/tool-work-units.mjs +12 -9
  102. package/src/runtime/shared/turn-snapshot.mjs +233 -122
  103. package/src/runtime/shared/user-data-guard.mjs +51 -19
  104. package/src/runtime/shared/worktrees/activity-gate.mjs +209 -227
  105. package/src/runtime/shared/worktrees/contract.mjs +11 -3
  106. package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
  107. package/src/runtime/shared/worktrees/manager.mjs +419 -52
  108. package/src/runtime/shared/worktrees/registry.mjs +3 -1
  109. package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
  110. package/src/runtime/tidy/process.mjs +41 -8
  111. package/src/runtime/tidy/resolve.mjs +10 -7
  112. package/src/session-runtime/agent-home-delivery.mjs +73 -91
  113. package/src/session-runtime/agent-profile-changes.mjs +16 -0
  114. package/src/session-runtime/agent-room-delivery.mjs +2 -63
  115. package/src/session-runtime/agent-task-sessions.mjs +194 -64
  116. package/src/session-runtime/boot/apis.mjs +3 -0
  117. package/src/session-runtime/boot/facade.mjs +5 -6
  118. package/src/session-runtime/boot/tools.mjs +21 -1
  119. package/src/session-runtime/held-requests.mjs +20 -25
  120. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +5 -22
  121. package/src/session-runtime/model-recency.mjs +1 -0
  122. package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
  123. package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
  124. package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
  125. package/src/session-runtime/runtime-review-api.mjs +4 -1
  126. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
  127. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -0
  128. package/src/session-runtime/services/provider-admin.mjs +12 -2
  129. package/src/session-runtime/services/worktree-access.mjs +27 -0
  130. package/src/session-runtime/session-hooks.mjs +9 -17
  131. package/src/session-runtime/settings-builtin-tools-api.mjs +2 -0
  132. package/src/session-runtime/setup-tool/executor.mjs +4 -0
  133. package/src/session-runtime/setup-tool/settings-contract.mjs +3 -0
  134. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +98 -14
  135. package/src/session-runtime/tool-policy/policy-stage.mjs +23 -5
  136. package/src/session-runtime/tool-policy-refresh.mjs +1 -0
  137. package/src/session-runtime/tool-surface.mjs +9 -5
  138. package/src/session-runtime/turn/session-ops.mjs +8 -0
  139. package/src/session-runtime/turn/turn-run.mjs +5 -1
  140. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +10 -2
  141. package/src/session-runtime/workflow-agents-api/agent-route.mjs +39 -17
  142. package/src/session-runtime/workspace/transition-steps.mjs +1 -18
  143. package/src/session-runtime/workspace/transitions.mjs +279 -67
  144. package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
  145. package/src/standalone/daemon-lag-profiler.mjs +3 -1
  146. package/src/standalone/daemon-session-control.mjs +140 -23
  147. package/src/standalone/daemon-telemetry.mjs +9 -0
  148. package/src/standalone/daemon.mjs +5 -0
  149. package/src/standalone/local-session-runtime.mjs +11 -2
  150. package/src/standalone/session-protocol.mjs +1 -0
  151. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
  152. package/src/standalone/session-service/agent-route-follow.mjs +69 -0
  153. package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
  154. package/src/standalone/session-service.mjs +10 -0
  155. package/src/standalone/session-tool-output.mjs +1 -0
  156. package/src/standalone/staged-import.mjs +47 -0
  157. package/src/tui/App.jsx +2 -0
  158. package/src/tui/app/message-selector.mjs +15 -2
  159. package/src/tui/app/model-options.mjs +16 -2
  160. package/src/tui/app/model-picker.mjs +2 -1
  161. package/src/tui/app/route-pickers.mjs +3 -1
  162. package/src/tui/dist/index.mjs +92 -40
  163. package/src/tui/paste-attachments.mjs +0 -1
  164. package/src/tui/paste-text-policy.mjs +4 -5
  165. package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
  166. package/src/tui/session/notice-surface.mjs +3 -0
  167. package/src/tui/session/session-api/intake/abort.mjs +33 -17
  168. package/src/tui/session/session-api/intake/rewind.mjs +39 -4
  169. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  170. package/src/tui/session/tool-approval.mjs +11 -48
  171. package/src/session-runtime/room-member-gate.mjs +0 -61
@@ -5,10 +5,12 @@ import { getModelMetadataSync } from '../../runtime/agent/orchestrator/providers
5
5
  import { isCustomProviderName } from '../../runtime/agent/orchestrator/providers/custom-reasoning.mjs';
6
6
 
7
7
  const CUSTOM_DECLARED_FIELDS = [
8
+ 'supportsVision',
8
9
  'supportsReasoning',
9
10
  'reasoningOptions',
10
11
  'reasoningLevels',
11
12
  'reasoningContentField',
13
+ 'providerName',
12
14
  'hidden',
13
15
  'manual',
14
16
  'customReasoning',
@@ -22,7 +22,10 @@ function customProviderModels(provider, providerConfig, discovered = providerCac
22
22
  } catch {
23
23
  return [];
24
24
  }
25
- return effectiveCustomModelRows(config, discovered, { providerId: provider });
25
+ const providerName = clean(config.name);
26
+ return effectiveCustomModelRows(config, discovered, { providerId: provider }).map((row) =>
27
+ providerName ? { ...row, providerName } : row
28
+ );
26
29
  }
27
30
 
28
31
  export function createQuickProviderRows({
@@ -53,6 +56,8 @@ export function createQuickProviderRows({
53
56
  display: model.display || model.name || modelId,
54
57
  contextWindow: model.contextWindow || meta.contextWindow || null,
55
58
  outputTokens: model.outputTokens || meta.outputTokens || null,
59
+ // An explicit value (an override's false included) wins; the catalog fills the rest.
60
+ supportsVision: model.supportsVision ?? meta.supportsVision,
56
61
  supportsReasoning: model.supportsReasoning === true || meta.supportsReasoning === true,
57
62
  supportsFunctionCalling: model.supportsFunctionCalling === true || meta.supportsFunctionCalling === true,
58
63
  supportsPromptCaching: model.supportsPromptCaching === true || meta.supportsPromptCaching === true,
@@ -78,6 +83,7 @@ export function createQuickProviderRows({
78
83
  contextWindow: meta.contextWindow || null,
79
84
  outputTokens: meta.outputTokens || null,
80
85
  latest: routeLike.latest === true,
86
+ supportsVision: meta.supportsVision,
81
87
  supportsReasoning: !!routeLike.effort || meta.supportsReasoning === true,
82
88
  supportsFunctionCalling: meta.supportsFunctionCalling === true,
83
89
  supportsPromptCaching: meta.supportsPromptCaching === true,
@@ -2,6 +2,7 @@ import { registerCacheInvalidationListener } from '../runtime/agent/orchestrator
2
2
  import { turnReviewReply } from '../runtime/shared/turn-review-reply.mjs';
3
3
  import {
4
4
  clearSessionChangeHistory,
5
+ forgetTurnSnapshotSession,
5
6
  getSessionReviewDiff,
6
7
  getSessionReviewFilePatch,
7
8
  getTurnChanges,
@@ -19,8 +20,10 @@ import { registerSessionPurgeHook } from '../runtime/agent/orchestrator/session/
19
20
 
20
21
  // A deleted history stays deleted for as long as its session exists; the
21
22
  // session's own deletion takes the marker — and its stored first-seen file
22
- // contents — with it.
23
+ // contents — with it, and its in-memory turn, parked worker reviews and
24
+ // bound worker trackers.
23
25
  registerSessionPurgeHook((sessionId) => {
26
+ forgetTurnSnapshotSession(sessionId);
24
27
  void forgetSessionChangeHistoryMarker(sessionId);
25
28
  void forgetSessionBaselines(sessionId);
26
29
  });
@@ -25,11 +25,16 @@ export function isActiveWorkerRow(row = {}) {
25
25
  return !isTerminalWorkerStatus(effective);
26
26
  }
27
27
 
28
- function agentRow({ tag, session, runtime, progress, now, indexRow = null }) {
28
+ function rowState(session, runtime) {
29
29
  const status = session.closed === true ? 'closed' : session.status || 'idle';
30
30
  const stage =
31
31
  session.stage ||
32
32
  (status === 'idle' || status === 'error' || status === 'closed' ? status : runtime?.stage || status);
33
+ return { status, stage };
34
+ }
35
+
36
+ function agentRow({ tag, session, runtime, progress, now, indexRow = null }) {
37
+ const { status, stage } = rowState(session, runtime);
33
38
  // Additive: the current turn's start (worker index, stamped at dispatch) so
34
39
  // surfaces time a continued worker from its turn, not from createdAt.
35
40
  const turnStartedAt = indexRow?.turnStartedAt || session.turnStartedAt || null;
@@ -76,24 +81,34 @@ export function createAgentList({ mgr, refreshTagsFromSessions, agentSessionEntr
76
81
  return function list({ scanSessions = false, context = {}, includeTerminal = false } = {}) {
77
82
  refreshTagsFromSessions({ scanSessions, context });
78
83
  const now = Date.now();
79
- const rows = agentSessionEntries({ scanSessions, context }).map(({ tag, session, indexRow, loaded }) =>
80
- agentRow({
81
- tag,
82
- session,
83
- indexRow,
84
- runtime: mgr.getSessionRuntime?.(session.id),
85
- // A tag-bound entry is the getSession answer itself; an index row or
86
- // a scanned summary is only a projection and still reads the session.
87
- progress: progress.sessionProgressExtras(
88
- session.id,
89
- session.agent || null,
84
+ const rows = [];
85
+ for (const { tag, session, indexRow, loaded } of agentSessionEntries({ scanSessions, context })) {
86
+ const runtime = mgr.getSessionRuntime?.(session.id);
87
+ // Whether a row is listed depends on its status and stage alone, so a
88
+ // finished worker is dropped before its progress is read: that read
89
+ // loads the worker's stored session, and a Lead's status pulse (every
90
+ // 2 s) would otherwise read every worker it ever ran, whole transcripts
91
+ // on first sight, synchronously on the daemon loop.
92
+ if (!includeTerminal && !isActiveWorkerRow(rowState(session, runtime))) continue;
93
+ rows.push(
94
+ agentRow({
95
+ tag,
96
+ session,
97
+ indexRow,
98
+ runtime,
99
+ // A tag-bound entry is the getSession answer itself; an index row or
100
+ // a scanned summary is only a projection and still reads the session.
101
+ progress: progress.sessionProgressExtras(
102
+ session.id,
103
+ session.agent || null,
104
+ now,
105
+ null,
106
+ loaded ? { loaded: session } : {}
107
+ ),
90
108
  now,
91
- null,
92
- loaded ? { loaded: session } : {}
93
- ),
94
- now,
95
- })
96
- );
97
- return includeTerminal ? rows : rows.filter((row) => isActiveWorkerRow(row));
109
+ })
110
+ );
111
+ }
112
+ return rows;
98
113
  };
99
114
  }
@@ -9,6 +9,7 @@ import { resourceAdmission } from '../../../../runtime/shared/resource-admission
9
9
  import { clean, terminalPidForContext } from '../helpers.mjs';
10
10
  import { renderResult } from '../render.mjs';
11
11
  import { closeManagedSession } from '../../../lifecycle/shared.mjs';
12
+ import { hopOf } from '../../../../runtime/shared/bot-hops.mjs';
12
13
 
13
14
  /** The completion callback that hands a worker's result back to its caller session; null without a handoff. */
14
15
  function handoffNotifier(handoffSessionId, notifySessionCompletion) {
@@ -30,6 +31,8 @@ export function createJobStarter({ mgr, notifyStatusChange, notifySessionComplet
30
31
  ownerSessionId: ownerSessionId || null,
31
32
  clientHostPid: clientHostPid || null,
32
33
  notifyFn: handoffNotifier(handoffSessionId, notifySessionCompletion),
34
+ // The turn that started the job: its completion returns at that bot-chain hop (bot-hops.mjs).
35
+ ...(hopOf(notifyContext?.hop) ? { hop: notifyContext.hop } : {}),
33
36
  };
34
37
  const jobMeta = sanitizeTaskMeta({
35
38
  ...(meta || {}),
@@ -442,6 +442,7 @@ function publicCustomModelRows(rows) {
442
442
  model.hidden = item.hidden === true;
443
443
  model.manual = item.manual === true;
444
444
  for (const key of [
445
+ 'supportsVision',
445
446
  'supportsReasoning',
446
447
  'reasoningOptions',
447
448
  'reasoningLevels',
@@ -588,7 +589,8 @@ async function modelProxyProviderSetup(options) {
588
589
  });
589
590
  const custom = Object.entries(providers)
590
591
  .filter(([id, entry]) => isCustomProviderId(id) && entry?.type === 'custom')
591
- .map(([id]) => ({ id, name: id }));
592
+ // Older desktops advertise no name: the id is the fallback.
593
+ .map(([id, entry]) => ({ id, name: (typeof entry.name === 'string' && entry.name.trim()) || id }));
592
594
  return {
593
595
  managedByClient: true,
594
596
  api: [...project(API_PROVIDERS, 'api', 'api-key'), ...project(custom, 'api', 'custom')],
@@ -1111,7 +1113,15 @@ export async function saveCustomProvider(cfgMod, rawInput) {
1111
1113
  return typed ? serializeCustomKeySave(id, commit) : commit();
1112
1114
  }
1113
1115
 
1114
- const MODEL_PATCH_KEYS = new Set(['manual', 'hidden', 'name', 'contextWindow', 'maxOutputTokens', 'reasoning']);
1116
+ const MODEL_PATCH_KEYS = new Set([
1117
+ 'manual',
1118
+ 'hidden',
1119
+ 'name',
1120
+ 'contextWindow',
1121
+ 'maxOutputTokens',
1122
+ 'supportsVision',
1123
+ 'reasoning',
1124
+ ]);
1115
1125
  const REASONING_PATCH_KEYS = new Set(['levels', 'wireFormat', 'wireValues', 'replay']);
1116
1126
 
1117
1127
  // Tri-state merge: undefined preserves, null deletes the property, a value replaces it.
@@ -0,0 +1,27 @@
1
+ // Who may change worktrees from a chat (the git tool's managed-worktree
2
+ // operations, the daemon's cross-session `worktree` control). A worktree
3
+ // mutation — entering, returning, merging, removing, pruning, `git worktree
4
+ // add/remove`, a snapshot — runs only in a conversation session: a Lead
5
+ // session of the desktop or TUI, or a bot's chat. Unattended runs (schedules,
6
+ // webhooks, routines, room seats) are refused by the run's trusted identity;
7
+ // delegated workers have no worktree host at all. Listing stays open.
8
+ //
9
+ // The scope is the session-level caller scope (schedule-access.mjs
10
+ // scheduleCallerScope: the session's provenance and its unattended-run mark),
11
+ // never anything the model or a message supplies.
12
+ import { scheduleCallerScope } from './schedule-access.mjs';
13
+
14
+ /** `{ kind, agentId?, unattended?, attended }` of a calling session record. */
15
+ export function worktreeCallerScope(session) {
16
+ const scope = scheduleCallerScope(session);
17
+ const attended = (scope.kind === 'human' || scope.kind === 'agent') && scope.unattended !== true;
18
+ return { ...scope, attended };
19
+ }
20
+
21
+ /** Throws unless `scope` (worktreeCallerScope) may change worktrees; `what` names the refused action. */
22
+ export function assertWorktreeMutationAllowed(scope, what) {
23
+ if (scope?.attended === true) return;
24
+ throw new Error(
25
+ `${what} changes worktrees only in a conversation session (a Lead session or a bot chat); schedule, webhook, routine and room runs may only list them`
26
+ );
27
+ }
@@ -5,9 +5,6 @@
5
5
  // facade's hookCommonPayload / currentCwd accessor + the session itself. All
6
6
  // properties are non-enumerable/configurable/writable so the loop/manager can
7
7
  // read them by name without changing session enumeration.
8
- import { roomMemberOfSession } from '../runtime/agent/orchestrator/session/agent-room.mjs';
9
- import { roomMemberToolDecision } from './room-member-gate.mjs';
10
-
11
8
  export function attachSessionHooks(session, { hooks, hookCommonPayload, getCwd }) {
12
9
  const identity = (input) => ({
13
10
  session_id: input?.sessionId || input?.session_id || session?.id,
@@ -28,26 +25,21 @@ export function attachSessionHooks(session, { hooks, hookCommonPayload, getCwd }
28
25
  // rejection cannot escape the tool loop or the compaction flow.
29
26
  const dispatcher = (event, payload) => (input) => hooks.dispatch(event, hookCommonPayload(payload(input)));
30
27
 
31
- // PreToolUse bridge. A group-room member's mutating calls always ask the
32
- // user (room-member-gate.mjs), whatever the hooks decided short of a deny.
33
- define('beforeToolHook', async (input, options = {}) => {
34
- const name = input?.name || input?.tool_name;
35
- const args = input?.args || input?.tool_input;
36
- const cwd = input?.cwd || getCwd();
37
- const decision = await hooks.beforeTool(
28
+ // PreToolUse bridge. A group-room member follows the same rules as its
29
+ // agent's home thread: no room-only approval on top.
30
+ define('beforeToolHook', async (input, options = {}) =>
31
+ hooks.beforeTool(
38
32
  hookCommonPayload({
39
33
  ...input,
40
34
  session_id: input?.sessionId || input?.session_id || session?.id,
41
- tool_name: name,
42
- tool_input: args,
35
+ tool_name: input?.name || input?.tool_name,
36
+ tool_input: input?.args || input?.tool_input,
43
37
  tool_use_id: input?.toolCallId || input?.tool_use_id,
44
- cwd,
38
+ cwd: input?.cwd || getCwd(),
45
39
  }),
46
40
  options
47
- );
48
- if (!roomMemberOfSession(session?.id)) return decision;
49
- return roomMemberToolDecision({ name, args, cwd }, decision);
50
- });
41
+ )
42
+ );
51
43
  // PostToolUse: bridge runtime tool completions to the standard hook bus.
52
44
  define('afterToolHook', dispatcher('PostToolUse', toolPayload));
53
45
  // PostToolUseFailure: dispatched by loop.mjs only when a tool execution
@@ -19,6 +19,7 @@ import {
19
19
  effortJudgeAvailable,
20
20
  effortJudgeInfo,
21
21
  effortJudgeInstalling,
22
+ effortJudgeLastError,
22
23
  effortJudgeReady,
23
24
  shutdownEffortJudge,
24
25
  } from '../runtime/effort-judge/judge-client.mjs';
@@ -178,6 +179,7 @@ export function createBuiltinToolSettings(
178
179
  installed: effortJudgeAvailable(),
179
180
  installing: effortJudgeInstalling(),
180
181
  ready: effortJudgeReady(),
182
+ lastError: effortJudgeLastError(),
181
183
  info: effortJudgeInfo(),
182
184
  },
183
185
  };
@@ -38,6 +38,10 @@ const PROFILE_VALUE_IDS = Object.freeze({
38
38
  });
39
39
 
40
40
  const ACTION_APPLIES_TO = {
41
+ set_agent_route:
42
+ "the bot's chats from their next turn (a running turn finishes on the old model); an agent switched on or off reaches open conversations from their next turn as an agent list reminder",
43
+ set_orchestration_mode:
44
+ "new sessions and an empty current one (the default mode); a conversation keeps its own mode until it is switched with the composer's mode selector",
41
45
  set_recap_enabled: 'background Memory cycles; no restart required',
42
46
  set_auto_update: 'subsequent automatic update checks',
43
47
  set_worktree_settings: 'new tasks and worktrees created afterwards; existing worktrees are unchanged',
@@ -223,6 +223,9 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
223
223
  turnNotifications: boolean,
224
224
  usagePinned: boolean,
225
225
  usageRailShortLong: boolean,
226
+ usageAccountsExpanded: boolean,
227
+ usageRailSlotsThree: boolean,
228
+ usageRailSlotsAll: boolean,
226
229
  computerObserveOnly: boolean,
227
230
  browserAutoSignIn: boolean,
228
231
  notificationVolume: {
@@ -2,6 +2,8 @@
2
2
  // changes (a switched workflow or mode, moved tool gates, changed shipped
3
3
  // rules) in where the provider cache is cold anyway.
4
4
  import { sessionHasConversationMessages } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
5
+ import { ORCHESTRATION_MODES } from '../../runtime/shared/orchestration.mjs';
6
+ import { orchestrationModeFromPrompt } from '../../runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs';
5
7
  import { promptRulesRevision } from '../../runtime/agent/orchestrator/session/manager/rules-cache.mjs';
6
8
  import { isAgentOwner } from '../../runtime/agent/orchestrator/agent-owner.mjs';
7
9
  import { AGENT_MODE, sessionModeOf } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
@@ -15,6 +17,7 @@ import {
15
17
  agentMcpToolPermission,
16
18
  connectorPolicyAgentOf,
17
19
  } from '../../runtime/agent/orchestrator/mcp/agent-connectors.mjs';
20
+ import { agentListEntries } from '../../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
18
21
  import { markToolSurfaceMigrated } from '../../runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs';
19
22
  import {
20
23
  agentProfileRevision,
@@ -50,6 +53,27 @@ const promptPrefix = (session) =>
50
53
  const rulesStale = (session) =>
51
54
  sessionModeOf(session) === AGENT_MODE && session.rulesRevision !== promptRulesRevision();
52
55
 
56
+ // Before session-local modes were used by profile refreshes, changing the
57
+ // roster could replace a coding Lead's mode rules with the configured default
58
+ // while leaving its selected mode intact. This is not a normal mode overlay:
59
+ // repair only a known generated mismatch, with no user switch still pending.
60
+ function orchestrationRecoveryNeeded(session) {
61
+ if (
62
+ !session?.promptIdentity ||
63
+ session.promptIdentity.skipAgentRules ||
64
+ isAgentOwner(session) ||
65
+ sessionModeOf(session) === AGENT_MODE ||
66
+ !ORCHESTRATION_MODES.includes(session.orchestrationMode) ||
67
+ session.workflowOverlay ||
68
+ session.orchestrationModeOverlay ||
69
+ session.pendingWorkflowId !== undefined ||
70
+ session.pendingOrchestrationMode !== undefined
71
+ )
72
+ return false;
73
+ const presented = orchestrationModeFromPrompt(session);
74
+ return presented !== null && presented !== session.orchestrationMode;
75
+ }
76
+
53
77
  // Changes the prompt does not carry yet: a workflow switched mid-conversation,
54
78
  // a bot's tool gates that moved, or shipped rules a release changed.
55
79
  const foldPending = (session) =>
@@ -130,6 +154,35 @@ function stageLiveToolChanges({ liveDeniedTools, modelStandaloneTools }, session
130
154
  return true;
131
155
  }
132
156
 
157
+ // A coding Lead keeps `agent` whichever agents exist (workflow.mjs
158
+ // workflowDisallowsAgentTool); a denial a session recorded under the older
159
+ // rule, because no agent was enabled, is lifted as the live policy no longer
160
+ // holds it: the tool becomes reachable by deferred loading and the next
161
+ // turn's deferred_tools_delta names it. Nothing in the request prefix moves.
162
+ // Returns true when the session changed.
163
+ function liftAgentDenial({ liveDeniedTools, modelStandaloneTools }, session) {
164
+ const disallowed = (session.disallowedTools || []).map(String);
165
+ const overlayDenied = (session.workflowOverlay?.deniedTools || []).includes('agent');
166
+ if (!disallowed.includes('agent') && !overlayDenied) return false;
167
+ if (liveDeniedTools(session, session.workflow).includes('agent')) return false;
168
+ const known = new Map(schemaTools(session).map((tool) => [toolName(tool), tool]));
169
+ if (!known.has('agent')) {
170
+ const fresh = modelStandaloneTools().filter((tool) => toolName(tool) === 'agent');
171
+ if (!fresh.length) return false;
172
+ session.deferredToolCatalog = [...(session.deferredToolCatalog || []), ...fresh];
173
+ known.set('agent', fresh[0]);
174
+ }
175
+ session.disallowedTools = disallowed.filter((name) => name !== 'agent');
176
+ if (overlayDenied) {
177
+ session.workflowOverlay.deniedTools = session.workflowOverlay.deniedTools.filter((name) => name !== 'agent');
178
+ }
179
+ mergePendingDeferredToolDelta(session, {
180
+ added: [{ name: 'agent', description: known.get('agent')?.description || '' }],
181
+ });
182
+ session.updatedAt = Date.now();
183
+ return true;
184
+ }
185
+
133
186
  export function createAgentProfileFold({
134
187
  getSession,
135
188
  getConfig,
@@ -189,9 +242,11 @@ export function createAgentProfileFold({
189
242
  * anything it embeds changed; a delegated worker keeps its prompt. The core
190
243
  * is rebuilt like a workflow switch's (the session's own workflow, the
191
244
  * workspace slices the model was told) and replaces the stored one, so the
192
- * next turn sends it with one provider cache break; nothing is re-read per
193
- * turn. That rewrite is immediate (one tagged cache break) and takes every
194
- * pending fold with it (foldPrompt), moving all stamps.
245
+ * next turn sends it with one provider cache break. The prompt is not re-embedded
246
+ * per turn, but currentAgents() does resolve the agent catalog at each
247
+ * turn's preparation (for the listing reminder). That rewrite is immediate
248
+ * (one tagged cache break) and takes every pending fold with it
249
+ * (foldPrompt), moving all stamps.
195
250
  * What is not an edit never touches the prefix: a bot thread's feature gates
196
251
  * and connectors are staged live (stageLiveToolChanges), and shipped rules a
197
252
  * release changed wait silently; both are folded in at the next cold point
@@ -210,22 +265,29 @@ export function createAgentProfileFold({
210
265
  const workflowConfig = configForSession(getConfig(), promptWorkflowOf(session)?.id, promptMode(session));
211
266
  const { leadPromptParts, summary: workflow } = activeWorkflowContext(workflowConfig, getDataDir());
212
267
  const roster = leadRosterRevision(leadPartsFor(session, leadPromptParts));
213
- const edited = session.agentProfileRevision !== revision || session.agentRosterRevision !== roster;
268
+ const recoverMode = orchestrationRecoveryNeeded(session);
269
+ // A coding Lead hears of its agents through the listing reminder each
270
+ // turn carries (agent-listing-notice.mjs), never a rewritten prompt;
271
+ // syncAgentListing only lifts a stale `agent` denial.
272
+ const edited =
273
+ recoverMode || session.agentProfileRevision !== revision || (agentMode && session.agentRosterRevision !== roster);
214
274
  // At a turn's start its own turn is the one counted; nothing runs yet.
215
275
  const busy = () => getSession?.() !== session || (trigger !== 'turn' && turnInFlight(session));
216
276
  if (busy()) {
217
277
  agentProfilePending = true;
218
278
  return false;
219
279
  }
220
- const toolsChanged =
221
- agentMode &&
222
- stageLiveToolChanges({ liveDeniedTools, modelStandaloneTools }, session, workflow, {
223
- edited: session.agentProfileRevision !== revision,
224
- });
280
+ const toolsChanged = agentMode
281
+ ? stageLiveToolChanges({ liveDeniedTools, modelStandaloneTools }, session, workflow, {
282
+ edited: session.agentProfileRevision !== revision,
283
+ })
284
+ : trigger === 'resume' && syncAgentListing(session);
225
285
  if (!edited) return (trigger === 'resume' && (await foldPromptIfCold())) || toolsChanged;
226
286
  // A coding session from before profile stamps keeps its prompt when it
227
287
  // is reopened; a saved edit still reaches it.
228
- if (session.agentProfileRevision === undefined && trigger === 'resume' && !agentMode) return toolsChanged;
288
+ if (session.agentProfileRevision === undefined && trigger === 'resume' && !agentMode && !recoverMode) {
289
+ return toolsChanged;
290
+ }
229
291
  if (foldPending(session)) {
230
292
  await foldPrompt(session, AGENT_PROFILE_INTENT);
231
293
  return true;
@@ -241,7 +303,7 @@ export function createAgentProfileFold({
241
303
  if (staged.core === presentedBp3Core(session)) return toolsChanged;
242
304
  rewriteConversationBp3Core(session, staged.core);
243
305
  // The request prefix changed: one cache break, tagged on the next send.
244
- markToolSurfaceMigrated(session, AGENT_PROFILE_INTENT);
306
+ markToolSurfaceMigrated(session, recoverMode ? PROMPT_FOLD_INTENT : AGENT_PROFILE_INTENT);
245
307
  return true;
246
308
  }
247
309
 
@@ -254,12 +316,34 @@ export function createAgentProfileFold({
254
316
  if (session?.id && sessionModeOf(session) === AGENT_MODE && !Array.isArray(session.agentConnectorTools)) {
255
317
  session.agentConnectorTools = connectorTools(session, schemaTools(session).map(toolName));
256
318
  }
257
- if (agentProfilePending) {
319
+ if (agentProfilePending || orchestrationRecoveryNeeded(session)) {
258
320
  agentProfilePending = false;
259
321
  changed = await refreshAgentProfile({ trigger: 'turn' });
260
322
  }
261
- return (await foldPromptIfCold()) || changed;
323
+ changed = (await foldPromptIfCold()) || changed;
324
+ return syncAgentListing(session) || changed;
325
+ }
326
+
327
+ // A coding Lead hears of its agents through the agent list reminder.
328
+ const codingLead = (session) =>
329
+ Boolean(session?.id && session.promptIdentity && !isAgentOwner(session) && sessionModeOf(session) !== AGENT_MODE);
330
+
331
+ /** A coding Lead keeps `agent` reachable (liftAgentDenial). Returns true
332
+ * when the session changed. */
333
+ function syncAgentListing(session) {
334
+ return codingLead(session) && liftAgentDenial({ liveDeniedTools, modelStandaloneTools }, session);
335
+ }
336
+
337
+ /** The agents a coding Lead may delegate to now, as its prompt would list
338
+ * them: the turn's input to the agent list reminder (manager/
339
+ * agent-listing-notice.mjs); undefined for any other session. */
340
+ function currentAgents() {
341
+ const session = getSession?.();
342
+ if (!codingLead(session)) return undefined;
343
+ const config = configForSession(getConfig(), session.workflow?.id, currentMode(session));
344
+ const { leadPromptParts } = activeWorkflowContext(config, getDataDir());
345
+ return agentListEntries(leadPartsFor(session, leadPromptParts).agentCatalogContext) || [];
262
346
  }
263
347
 
264
- return { refreshAgentProfile, applyPendingAgentProfile };
348
+ return { refreshAgentProfile, applyPendingAgentProfile, currentAgents };
265
349
  }
@@ -1,6 +1,9 @@
1
1
  // Staging and applying the tool/prompt policy a config gives a Lead session:
2
2
  // the tools it is denied, its BP1 rules and its BP3 core.
3
- import { toSessionWorkflowMeta } from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
3
+ import {
4
+ toSessionWorkflowMeta,
5
+ workflowDisallowsAgentTool,
6
+ } from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
4
7
  import {
5
8
  applyDeferredToolSurface,
6
9
  filterDisallowedTools,
@@ -20,7 +23,10 @@ import { finalizeSessionToolList } from '../../runtime/agent/orchestrator/sessio
20
23
  import { isAgentOwner } from '../../runtime/agent/orchestrator/agent-owner.mjs';
21
24
  import { AGENT_MODE, sessionModeOf } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
22
25
  import { unusedModelEditToolName } from '../../runtime/shared/edit-tool-dialect.mjs';
23
- import { withPresentedWorkspaceSlices } from '../../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
26
+ import {
27
+ presentedAgentCatalogSlice,
28
+ withPresentedWorkspaceSlices,
29
+ } from '../../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
24
30
  import { leadPartsFor, rewriteSystemHeading, toolNames } from './surface-helpers.mjs';
25
31
 
26
32
  export function createPolicyStage({
@@ -34,12 +40,14 @@ export function createPolicyStage({
34
40
  activeWorkflowContext,
35
41
  }) {
36
42
  // The tools `session` is denied now: its caller's denials, the feature gates
37
- // as they stand, and the agent tool when its workflow delegates to no one.
43
+ // as they stand, and an agent-mode Lead's agent tool when its workflow
44
+ // delegates to no one (a coding Lead keeps it: workflow.mjs
45
+ // workflowDisallowsAgentTool).
38
46
  function liveDeniedTools(session, workflow) {
39
47
  return [
40
48
  ...session.callerDisallowedTools,
41
49
  ...featureDisallowedTools(),
42
- ...(workflow?.delegatesAgents === false ? ['agent'] : []),
50
+ ...(workflowDisallowsAgentTool(workflow, { agentMode: sessionModeOf(session) === AGENT_MODE }) ? ['agent'] : []),
43
51
  ]
44
52
  .map((name) => String(name || ''))
45
53
  .filter(Boolean);
@@ -100,7 +108,17 @@ export function createPolicyStage({
100
108
  } = activeWorkflowContext(config, getDataDir());
101
109
  const sessionMode = sessionModeOf(session);
102
110
  const agentMode = sessionMode === AGENT_MODE;
103
- const leadWorkflowParts = ownerIsAgent ? {} : leadPartsFor(session, leadPromptParts);
111
+ // A coding Lead's conversation keeps the agent list it was built with, as
112
+ // it keeps the workspace slices: agents switched on or off since reach it
113
+ // as reminders on top of that list (agent-listing-notice.mjs). A slice
114
+ // that is not one generated list is rebuilt as before.
115
+ const presentedCatalog = conversation && !agentMode ? presentedAgentCatalogSlice(session) : null;
116
+ const leadWorkflowParts = ownerIsAgent
117
+ ? {}
118
+ : {
119
+ ...leadPartsFor(session, leadPromptParts),
120
+ ...(presentedCatalog !== null ? { agentCatalogContext: presentedCatalog } : {}),
121
+ };
104
122
  const denied = liveDeniedTools(session, workflow);
105
123
 
106
124
  const baseRules =
@@ -101,5 +101,6 @@ export function createToolPolicyRefresh({
101
101
  applyPendingSessionSwitches: switches.applyPendingSessionSwitches,
102
102
  refreshAgentProfile: profile.refreshAgentProfile,
103
103
  applyPendingAgentProfile: profile.applyPendingAgentProfile,
104
+ currentAgents: profile.currentAgents,
104
105
  };
105
106
  }
@@ -17,6 +17,7 @@ import {
17
17
  import { workflowDisallowsAgentTool } from '../runtime/agent/orchestrator/runtime-core/workflow.mjs';
18
18
  import { promptWorkflowOf } from '../runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs';
19
19
  import { configuredOrchestrationMode } from '../runtime/shared/orchestration.mjs';
20
+ import { isAgentModeSession } from '../runtime/agent/orchestrator/session/agent-mode.mjs';
20
21
 
21
22
  export function createToolSurface({
22
23
  mgr,
@@ -36,15 +37,18 @@ export function createToolSurface({
36
37
  }) {
37
38
  let preSessionSurface = null;
38
39
 
39
- // A live session keeps its frozen capability (the workflow its prompt was
40
- // built with, even after a mid-conversation switch); a new session uses
41
- // current agents. Solo still offers the tool (deferred) for explicit user requests.
40
+ // A coding Lead keeps `agent` whichever agents exist (the agents reach it as
41
+ // a listing reminder); Solo still offers it (deferred) for explicit user
42
+ // requests. An agent-mode Lead keeps its frozen capability once live (the
43
+ // workflow its prompt was built with, even after a mid-conversation switch)
44
+ // and a new one gets it with current agents.
42
45
  function workflowAllowsAgents() {
43
46
  const session = getSession();
47
+ const agentMode = isAgentModeSession(session || {});
44
48
  if (session?.id || session?.workflow || session?.orchestrationMode) {
45
- return !workflowDisallowsAgentTool(promptWorkflowOf(session));
49
+ return !workflowDisallowsAgentTool(promptWorkflowOf(session), { agentMode });
46
50
  }
47
- return (delegatableAgentIds?.(getConfig(), cfgMod.getPluginData?.() || dataDir).length ?? 1) > 0;
51
+ return !agentMode || (delegatableAgentIds?.(getConfig(), cfgMod.getPluginData?.() || dataDir).length ?? 1) > 0;
48
52
  }
49
53
 
50
54
  function modelStandaloneTools() {
@@ -5,6 +5,7 @@
5
5
  import { normalizeToolMode } from '../../runtime/agent/orchestrator/runtime-core/effort.mjs';
6
6
  import { interruptTaskWaitForSession } from '../../runtime/agent/orchestrator/session/task-wait-control.mjs';
7
7
  import { markPendingGoalReminder } from '../../runtime/agent/orchestrator/runtime-core/goal-reminder.mjs';
8
+ import { rearmLateDeferredTools } from '../../runtime/agent/orchestrator/runtime-core/tool-catalog.mjs';
8
9
 
9
10
  export function createSessionOps(deps) {
10
11
  const {
@@ -55,6 +56,7 @@ export function createSessionOps(deps) {
55
56
  if (!session?.id) return null;
56
57
  const result = await mgr.rewindSessionMessagesTo?.(session.id, options);
57
58
  if (!result) return null;
59
+ if (options.dryRun === true) return result;
58
60
  setSession(mgr.getSession(session.id) || session);
59
61
  invalidateContextStatusCache();
60
62
  return result;
@@ -96,6 +98,12 @@ export function createSessionOps(deps) {
96
98
  } catch {
97
99
  /* best-effort: a Goal reminder must never break manual compact */
98
100
  }
101
+ // The same compaction strips late MCP tool announcements from older turns.
102
+ try {
103
+ rearmLateDeferredTools(getSession());
104
+ } catch {
105
+ /* best-effort: a tool reminder must never break manual compact */
106
+ }
99
107
  if (options.recoverAgent === true) recoverAgentWorkers();
100
108
  invalidateContextStatusCache();
101
109
  return result;
@@ -28,6 +28,7 @@ export function createTurnRunner(deps) {
28
28
  completeTurnSnapshotForTurn = completeTurnSnapshot,
29
29
  turnCleanupSettleMs = 2_000,
30
30
  getConfig = () => null,
31
+ currentAgents,
31
32
  } = deps;
32
33
  const transcript = createTurnTranscript(deps);
33
34
  const openTurn = createTurnOpener({
@@ -81,7 +82,10 @@ export function createTurnRunner(deps) {
81
82
  plannedToolCalls(session0, options),
82
83
  getCurrentCwd(),
83
84
  options.prefetch || null,
84
- callbacks
85
+ // The agents enabled now (coding Leads), asked for each user turn
86
+ // the ask prepares, for its agent list reminder
87
+ // (manager/agent-listing-notice.mjs).
88
+ { ...callbacks, agentsNow: currentAgents }
85
89
  );
86
90
  } finally {
87
91
  releaseWebSearchUsage?.();