mixdog 1.3.2 → 1.3.4

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 (182) hide show
  1. package/package.json +8 -8
  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/llm/usage-rollup.mjs +4 -383
  97. package/src/runtime/shared/redact-directories.mjs +18 -0
  98. package/src/runtime/shared/remove-directory.mjs +28 -0
  99. package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
  100. package/src/runtime/shared/session-sidebar-host.mjs +16 -3
  101. package/src/runtime/shared/tool-execution-contract.mjs +4 -0
  102. package/src/runtime/shared/tool-work-units.mjs +12 -9
  103. package/src/runtime/shared/turn-snapshot.mjs +233 -122
  104. package/src/runtime/shared/user-data-guard.mjs +51 -19
  105. package/src/runtime/shared/worktrees/activity-gate.mjs +209 -227
  106. package/src/runtime/shared/worktrees/contract.mjs +11 -3
  107. package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
  108. package/src/runtime/shared/worktrees/manager.mjs +419 -52
  109. package/src/runtime/shared/worktrees/registry.mjs +3 -1
  110. package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
  111. package/src/runtime/tidy/process.mjs +41 -8
  112. package/src/runtime/tidy/resolve.mjs +10 -7
  113. package/src/session-runtime/agent-home-delivery.mjs +73 -91
  114. package/src/session-runtime/agent-profile-changes.mjs +16 -0
  115. package/src/session-runtime/agent-room-delivery.mjs +2 -63
  116. package/src/session-runtime/agent-task-sessions.mjs +194 -64
  117. package/src/session-runtime/agent-tool-routing.mjs +49 -4
  118. package/src/session-runtime/boot/apis.mjs +3 -0
  119. package/src/session-runtime/boot/facade.mjs +5 -6
  120. package/src/session-runtime/boot/tools.mjs +23 -1
  121. package/src/session-runtime/held-requests.mjs +20 -25
  122. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +5 -22
  123. package/src/session-runtime/model-recency.mjs +1 -0
  124. package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
  125. package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
  126. package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
  127. package/src/session-runtime/runtime-review-api.mjs +4 -1
  128. package/src/session-runtime/services/agent-tool/close-flow.mjs +14 -2
  129. package/src/session-runtime/services/agent-tool/execute.mjs +6 -1
  130. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +44 -20
  131. package/src/session-runtime/services/agent-tool/render.mjs +3 -1
  132. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -0
  133. package/src/session-runtime/services/agent-tool/tool-def.mjs +1 -1
  134. package/src/session-runtime/services/provider-admin.mjs +12 -2
  135. package/src/session-runtime/services/worktree-access.mjs +27 -0
  136. package/src/session-runtime/session-hooks.mjs +9 -17
  137. package/src/session-runtime/settings-builtin-tools-api.mjs +2 -0
  138. package/src/session-runtime/setup-tool/executor.mjs +4 -0
  139. package/src/session-runtime/setup-tool/settings-contract.mjs +3 -0
  140. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +98 -14
  141. package/src/session-runtime/tool-policy/policy-stage.mjs +23 -5
  142. package/src/session-runtime/tool-policy-refresh.mjs +1 -0
  143. package/src/session-runtime/tool-surface.mjs +9 -5
  144. package/src/session-runtime/turn/session-ops.mjs +8 -0
  145. package/src/session-runtime/turn/turn-run.mjs +5 -1
  146. package/src/session-runtime/usage-stats-api.mjs +1 -1
  147. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +10 -2
  148. package/src/session-runtime/workflow-agents-api/agent-route.mjs +39 -17
  149. package/src/session-runtime/workspace/transition-steps.mjs +1 -18
  150. package/src/session-runtime/workspace/transitions.mjs +279 -67
  151. package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
  152. package/src/standalone/daemon-lag-profiler.mjs +3 -1
  153. package/src/standalone/daemon-session-control.mjs +140 -23
  154. package/src/standalone/daemon-telemetry.mjs +9 -0
  155. package/src/standalone/daemon.mjs +5 -0
  156. package/src/standalone/local-session-runtime.mjs +11 -2
  157. package/src/standalone/session-protocol.mjs +1 -0
  158. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
  159. package/src/standalone/session-service/agent-route-follow.mjs +69 -0
  160. package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
  161. package/src/standalone/session-service.mjs +10 -0
  162. package/src/standalone/session-tool-output.mjs +1 -0
  163. package/src/standalone/staged-import.mjs +47 -0
  164. package/src/tui/App.jsx +2 -0
  165. package/src/tui/app/message-selector.mjs +15 -2
  166. package/src/tui/app/model-options.mjs +16 -2
  167. package/src/tui/app/model-picker.mjs +2 -1
  168. package/src/tui/app/route-pickers.mjs +3 -1
  169. package/src/tui/dist/index.mjs +403 -210
  170. package/src/tui/paste-attachments.mjs +0 -1
  171. package/src/tui/paste-text-policy.mjs +4 -5
  172. package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
  173. package/src/tui/session/notice-surface.mjs +3 -0
  174. package/src/tui/session/session-api/intake/abort.mjs +33 -17
  175. package/src/tui/session/session-api/intake/rewind.mjs +39 -4
  176. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  177. package/src/tui/session/tool-approval.mjs +11 -48
  178. package/src/vendor/statusline/src/gateway/claude-current.mjs +0 -58
  179. package/src/vendor/statusline/src/gateway/route-meta.mjs +1 -361
  180. package/src/vendor/statusline/src/gateway/session-routes.mjs +0 -55
  181. package/src/session-runtime/room-member-gate.mjs +0 -61
  182. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +0 -285
@@ -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?.();
@@ -203,7 +203,7 @@ export function createUsageStatsApi({
203
203
  // beyond a clock captured before the import started.
204
204
  const now = Date.now();
205
205
  const period = usagePeriodFor(options, now);
206
- // The rollup query runs off the event loop (usage-rollup-worker.mjs).
206
+ // The rollup query runs off the event loop (usage-ledger-worker.mjs).
207
207
  const rollup = await store.rollupAsync(usageRollupQuery(period));
208
208
  // The ledger returns the same rollup object while nothing was committed
209
209
  // (and while a rolling window lost no record), so identical back-to-back
@@ -420,7 +420,7 @@ function customAvatarImages(dir, avatar) {
420
420
  // from Mixy's identity folder: bots/mixy once the bot store is active,
421
421
  // agents/lead before (mixyIdentityDir, agent-store.mjs). Saved empty, the
422
422
  // file goes. `dataDirOf()` resolves the data folder on each call.
423
- function createLeadIdentityApi(dataDirOf) {
423
+ function createLeadIdentityApi(dataDirOf, mainRoute) {
424
424
  const leadDir = () => mixyIdentityDir(dataDirOf());
425
425
  function readLeadFile(file) {
426
426
  return readTextOrEmpty(join(leadDir(), file));
@@ -462,6 +462,8 @@ function createLeadIdentityApi(dataDirOf) {
462
462
  route: readMixyRoute(dataDirOf()),
463
463
  };
464
464
  }
465
+ // The route Mixy's thread follows: its own, else Main's (an override cleared).
466
+ const getLeadHomeRoute = () => readMixyRoute(dataDirOf()) ?? mainRoute();
465
467
  // Mixy's name and face, chosen in the 대표 에이전트 dialog or its profile:
466
468
  // a name '' and an avatar null go back to the defaults; absent keys are kept.
467
469
  function saveLeadIdentity(payload = {}) {
@@ -511,6 +513,7 @@ function createLeadIdentityApi(dataDirOf) {
511
513
  getLeadAgentBody: () => readLeadFile('AGENT.md'),
512
514
  saveLeadAgentBody: (text) => saveLeadFile('AGENT.md', text),
513
515
  getLeadStatus,
516
+ getLeadHomeRoute,
514
517
  saveLeadIdentity,
515
518
  setLeadCreated,
516
519
  // Mixy's connector policy, in its agent.json (`next` null removes it).
@@ -932,11 +935,15 @@ export function createAgentEditorApi(deps) {
932
935
  getLeadAgentBody,
933
936
  saveLeadAgentBody,
934
937
  getLeadStatus,
938
+ getLeadHomeRoute,
935
939
  saveLeadIdentity,
936
940
  setLeadCreated,
937
941
  getLeadConnectorPolicy,
938
942
  setLeadConnectorPolicy,
939
- } = createLeadIdentityApi(() => resolveDataDir(deps));
943
+ } = createLeadIdentityApi(
944
+ () => resolveDataDir(deps),
945
+ () => normalizeWorkflowRoute(deps.resolveRoute(getConfig(), {}))
946
+ );
940
947
 
941
948
  // The agent's connector policy (see runtime/.../mcp/agent-connectors.mjs),
942
949
  // or null when its definition has none: every connector is on. Read as the
@@ -979,6 +986,7 @@ export function createAgentEditorApi(deps) {
979
986
  getLeadAgentBody,
980
987
  saveLeadAgentBody,
981
988
  getLeadStatus,
989
+ getLeadHomeRoute,
982
990
  setLeadCreated,
983
991
  saveLeadIdentity,
984
992
  saveAgentDefinition,
@@ -1,3 +1,4 @@
1
+ import { isDeepStrictEqual } from 'node:util';
1
2
  import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
2
3
  import { hasOwn } from '../../runtime/shared/object.mjs';
3
4
  import { isHiddenAgent } from '../../runtime/agent/orchestrator/internal-agents.mjs';
@@ -10,6 +11,7 @@ import {
10
11
  withAgentDisabled,
11
12
  } from '../../runtime/shared/agent-route-config.mjs';
12
13
  import { agentEditorId, resolveDataDir } from './shared.mjs';
14
+ import { publishAgentRouteChange } from '../agent-profile-changes.mjs';
13
15
  import {
14
16
  isStoreBot,
15
17
  readBotRoute,
@@ -101,21 +103,38 @@ export function createAgentRouteApi(deps) {
101
103
  saveConfigAndAdopt(canonicalizeAgentRouteStorage(nextConfig));
102
104
  }
103
105
 
106
+ // The route a bot saved of its own ({} when none).
107
+ function ownRoute(id) {
108
+ if (isMixy(id)) return readMixyRoute(dataDir()) || {};
109
+ if (storeBot(id)) return readBotRoute(dataDir(), id) || {};
110
+ return agentRouteFromConfig(getConfig(), id) || {};
111
+ }
112
+ // The route a bot's chats run on: its own, else Main's.
113
+ const chatRoute = (id) => {
114
+ const own = ownRoute(id);
115
+ return normalizeWorkflowRoute(own.provider ? own : resolveRoute(getConfig(), {}));
116
+ };
117
+ // Structural, so equivalent parameter objects in another key order are the same route.
118
+ const plainRoute = (route) => JSON.parse(JSON.stringify(normalizeWorkflowRoute(route) ?? null));
119
+ const sameRoute = (a, b) => isDeepStrictEqual(plainRoute(a), plainRoute(b));
120
+
104
121
  // Only an explicitly empty provider removes an override: the agent inherits Main.
105
- function clearOverride(id) {
122
+ async function clearOverride(id) {
123
+ const before = chatRoute(id);
106
124
  if (isMixy(id) || storeBot(id)) {
107
125
  if (isMixy(id)) writeMixyRoute(dataDir(), null);
108
126
  else writeBotRoute(dataDir(), id, null);
109
- const inherited = normalizeWorkflowRoute(resolveRoute(getConfig(), {}));
110
- return { ...(inherited || {}), inherited: true };
127
+ } else {
128
+ const nextConfig = { ...getConfig() };
129
+ const agents = { ...(nextConfig.agents || {}) };
130
+ const hadOverride = Object.hasOwn(agents, id);
131
+ delete agents[id];
132
+ nextConfig.agents = agents;
133
+ if (hadOverride) saveConfigAndAdopt(canonicalizeAgentRouteStorage(nextConfig));
111
134
  }
112
- const nextConfig = { ...getConfig() };
113
- const agents = { ...(nextConfig.agents || {}) };
114
- const hadOverride = Object.hasOwn(agents, id);
115
- delete agents[id];
116
- nextConfig.agents = agents;
117
- if (hadOverride) saveConfigAndAdopt(canonicalizeAgentRouteStorage(nextConfig));
118
135
  const inherited = normalizeWorkflowRoute(resolveRoute(getConfig(), {}));
136
+ // The bot's chats follow Main from their next turn (unless they already do).
137
+ if (inherited && !sameRoute(before, inherited)) await publishAgentRouteChange(id, inherited);
119
138
  return { ...(inherited || {}), inherited: true };
120
139
  }
121
140
 
@@ -128,16 +147,16 @@ export function createAgentRouteApi(deps) {
128
147
  // picked while the model resolved below stays (answered with `skipped`).
129
148
  const onlyIfEmpty = requested.onlyIfEmpty === true;
130
149
  delete requested.onlyIfEmpty;
131
- const readStored = () => {
132
- if (isMixy(id)) return readMixyRoute(dataDir()) || {};
133
- if (storeBot(id)) return readBotRoute(dataDir(), id) || {};
134
- return agentRouteFromConfig(getConfig(), id) || {};
135
- };
150
+ const readStored = () => ownRoute(id);
136
151
  const kept = () => ({ ...readStored(), id, skipped: true });
137
152
  const stored = readStored();
138
153
  if (onlyIfEmpty && clean(stored.provider)) return kept();
139
- // Mixy is never off: its route request is a model pick alone.
140
- if (isMixy(id)) delete requested.disabled;
154
+ // Mixy is never off: its route request is a model pick alone, and an
155
+ // on/off request alone changes nothing.
156
+ if (isMixy(id) && hasOwn(requested, 'disabled')) {
157
+ delete requested.disabled;
158
+ if (!Object.keys(requested).length) return { ...stored, id };
159
+ }
141
160
  // Off is an explicit state, stored apart from the route so the model the
142
161
  // user picked survives and comes back when the agent is switched on. An
143
162
  // off request that also carries a model saves that model and stays off.
@@ -147,7 +166,7 @@ export function createAgentRouteApi(deps) {
147
166
  // An explicitly empty provider still asks to drop the override, as it
148
167
  // does for an agent that stays on: off and inheriting Main.
149
168
  if (hasOwn(requested, 'provider') && !clean(requested.provider)) {
150
- return { ...clearOverride(id), id, disabled: true };
169
+ return { ...(await clearOverride(id)), id, disabled: true };
151
170
  }
152
171
  return { ...stored, id, disabled: true };
153
172
  }
@@ -189,7 +208,10 @@ export function createAgentRouteApi(deps) {
189
208
  if (onlyIfEmpty && clean(readStored().provider)) return kept();
190
209
  const stayOff = onlyIfEmpty ? isAgentDisabled(getConfig(), id) : keepOff;
191
210
  // Picking a model is also the "on" switch unless the request keeps it off.
211
+ const before = chatRoute(id);
192
212
  saveRoute(id, routeToSave, stayOff);
213
+ // The bot's existing chats move onto it from their next turn (unless they already run it).
214
+ if (!sameRoute(before, routeToSave)) await publishAgentRouteChange(id, routeToSave);
193
215
  return stayOff ? { ...routeToSave, id, disabled: true } : routeToSave;
194
216
  }
195
217
 
@@ -251,7 +251,6 @@ export function createTransitionSteps({
251
251
  const projectPath = previous.projectPath;
252
252
  const project = await inspectProject(projectPath, { signal });
253
253
  let entry = await backendApi.getSessionWorktree(session.id);
254
- const created = !entry;
255
254
  if (entry) {
256
255
  if (!existsSync(entry.path)) {
257
256
  throw worktreeError('WORKTREE_MISSING', undefined, { worktreeId: entry.id, path: entry.path });
@@ -333,18 +332,9 @@ export function createTransitionSteps({
333
332
  await backendApi.dropCarryRecoveryRef({ projectPath, recoveryRef: entry.changes.recoveryRef }).catch(() => {});
334
333
  }
335
334
  // Creation notes (submodules, include entries missing or already present).
336
- // `created`: a new checkout, whose first-turn preparation still awaits
337
- // (`prepared`: what creation did and what the turn still has to run).
335
+ // A new checkout's first-turn preparation stays pending in its registry entry.
338
336
  return {
339
337
  ...entered,
340
- ...(created
341
- ? {
342
- created: {
343
- prepared: entry.prepared ?? null,
344
- root: entry.prepared?.root || project.topLevel || projectPath,
345
- },
346
- }
347
- : {}),
348
338
  warnings: Array.isArray(entry.warnings) ? [...entry.warnings] : [],
349
339
  ...(entry.changes ? { changes: { mode: entry.changes.mode, count: entry.changes.count } } : {}),
350
340
  };
@@ -395,13 +385,6 @@ export function createTransitionSteps({
395
385
  journal.push(() => backendApi.claimWorktreeOwner(previous.worktreeId, owner(session.id)));
396
386
  await releaseOwner(backendApi, previous.worktreeId, session.id, { forgetReference: true });
397
387
  }
398
- // A managed worktree made for the branch: its first-turn preparation awaits.
399
- if (resolved.created === true && entry) {
400
- return {
401
- ...result,
402
- created: { prepared: entry.prepared ?? null, root: entry.prepared?.root || project.topLevel || projectPath },
403
- };
404
- }
405
388
  return result;
406
389
  }
407
390