mixdog 1.3.4 → 1.3.5

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 (197) hide show
  1. package/LICENSES/oh-my-pi-MIT.txt +23 -0
  2. package/NOTICE.md +15 -0
  3. package/README.md +11 -0
  4. package/package.json +2 -1
  5. package/scripts/embed-google-oauth-client.mjs +49 -0
  6. package/scripts/lib/test-spawn-ledger.mjs +46 -1
  7. package/scripts/test.mjs +5 -0
  8. package/src/defaults/shipped-history.json +8 -0
  9. package/src/defaults/skills/browser-use/SKILL.md +21 -9
  10. package/src/defaults/skills/setup/SKILL.md +7 -2
  11. package/src/defaults/skills/setup/references/actions.md +82 -27
  12. package/src/defaults/skills/setup/references/surfaces.md +67 -34
  13. package/src/defaults/skills/setup-guide/SKILL.md +3 -3
  14. package/src/lib/keychain-cjs.cjs +359 -29
  15. package/src/rules/agent-mode/AGENT_MODE.md +10 -2
  16. package/src/rules/agent-mode/MIXY.md +12 -0
  17. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
  18. package/src/runtime/agent/orchestrator/config.mjs +18 -0
  19. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +2 -2
  20. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  21. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
  22. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
  23. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
  24. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
  25. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
  26. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
  27. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +68 -23
  28. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +22 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  31. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -3
  32. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
  33. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
  34. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
  35. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
  36. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
  37. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +19 -2
  38. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
  40. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
  41. package/src/runtime/agent/orchestrator/providers/cursor.mjs +7 -0
  42. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
  43. package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
  44. package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
  45. package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
  46. package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
  47. package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
  48. package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
  49. package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
  50. package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
  51. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
  52. package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
  53. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
  54. package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
  55. package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
  56. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
  57. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
  58. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
  59. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
  60. package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
  61. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
  63. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
  64. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +17 -0
  65. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  67. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
  69. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
  70. package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
  71. package/src/runtime/agent/orchestrator/providers/registry.mjs +17 -3
  72. package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
  73. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
  75. package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  77. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
  78. package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
  79. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
  80. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
  81. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
  82. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
  83. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
  84. package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
  85. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +15 -3
  86. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +5 -0
  87. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +36 -4
  88. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -0
  89. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +15 -3
  90. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
  91. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
  92. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
  93. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +50 -2
  94. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
  95. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +11 -2
  96. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +11 -2
  97. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
  98. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
  99. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
  100. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
  101. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
  102. package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
  103. package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
  104. package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
  105. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
  106. package/src/runtime/agent/orchestrator/session/routine-result.mjs +11 -2
  107. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
  108. package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
  109. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +125 -12
  110. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
  111. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
  112. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
  113. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +7 -1
  114. package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
  115. package/src/runtime/channels/lib/scheduler.mjs +109 -14
  116. package/src/runtime/channels/lib/schedules-db.mjs +49 -0
  117. package/src/runtime/channels/lib/tool-dispatch.mjs +13 -0
  118. package/src/runtime/channels/lib/worker-main.mjs +9 -0
  119. package/src/runtime/office/com/com-adapter.mjs +17 -1
  120. package/src/runtime/office/com/office-com-host.ps1 +51 -1
  121. package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
  122. package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
  123. package/src/runtime/office/pdf/document-preview.mjs +326 -8
  124. package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
  125. package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
  126. package/src/runtime/office/portable/portable-soffice.mjs +45 -5
  127. package/src/runtime/shared/bot-hops.mjs +62 -2
  128. package/src/runtime/shared/config.mjs +49 -22
  129. package/src/runtime/shared/connector-catalog.d.mts +4 -2
  130. package/src/runtime/shared/connector-catalog.mjs +16 -8
  131. package/src/runtime/shared/developer-options.mjs +32 -1
  132. package/src/runtime/shared/google-oauth-client.generated.json +4 -0
  133. package/src/runtime/shared/google-oauth-client.mjs +34 -9
  134. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
  135. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
  136. package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
  137. package/src/runtime/shared/llm/well-formed.mjs +66 -0
  138. package/src/runtime/shared/provider-accounts.mjs +224 -58
  139. package/src/runtime/shared/provider-api-key.mjs +19 -8
  140. package/src/runtime/shared/routine-trigger.mjs +42 -0
  141. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  142. package/src/runtime/shared/transcript-metadata.mjs +38 -0
  143. package/src/runtime/shared/user-data-guard.mjs +30 -2
  144. package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
  145. package/src/session-runtime/agent-home-delivery.mjs +57 -13
  146. package/src/session-runtime/agent-room-delivery.mjs +28 -1
  147. package/src/session-runtime/agent-task-sessions.mjs +292 -79
  148. package/src/session-runtime/boot/begin.mjs +3 -2
  149. package/src/session-runtime/boot/tools.mjs +2 -0
  150. package/src/session-runtime/bot-chain.mjs +32 -0
  151. package/src/session-runtime/bot-status.mjs +7 -4
  152. package/src/session-runtime/channel-config-api.mjs +63 -17
  153. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
  154. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
  155. package/src/session-runtime/internal-tool-executor.mjs +4 -0
  156. package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -1
  157. package/src/session-runtime/mixy-heartbeat.mjs +807 -0
  158. package/src/session-runtime/model-route/route-tuning.mjs +10 -6
  159. package/src/session-runtime/owned-automation-run.mjs +33 -12
  160. package/src/session-runtime/provider-auth/login.mjs +7 -0
  161. package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
  162. package/src/session-runtime/routine-home-turn.mjs +33 -6
  163. package/src/session-runtime/schedule-session-run.mjs +7 -3
  164. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
  165. package/src/session-runtime/services/channel-admin.mjs +66 -18
  166. package/src/session-runtime/services/channel-worker.mjs +1 -1
  167. package/src/session-runtime/services/provider-admin.mjs +128 -25
  168. package/src/session-runtime/services/schedule-access.mjs +14 -0
  169. package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
  170. package/src/session-runtime/session-create/session-options.mjs +7 -4
  171. package/src/session-runtime/settings-system-api.mjs +16 -1
  172. package/src/session-runtime/setup-tool/executor.mjs +49 -14
  173. package/src/session-runtime/setup-tool/extended-actions.mjs +29 -3
  174. package/src/session-runtime/setup-tool/settings-contract.mjs +65 -4
  175. package/src/session-runtime/setup-tool/tool-defs.mjs +30 -3
  176. package/src/session-runtime/task-session-route.mjs +99 -0
  177. package/src/session-runtime/tool-policy/policy-stage.mjs +5 -2
  178. package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
  179. package/src/session-runtime/tool-surface.mjs +4 -0
  180. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
  181. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
  182. package/src/session-runtime/workspace/transitions.mjs +10 -2
  183. package/src/standalone/daemon-session-control.mjs +88 -20
  184. package/src/standalone/daemon.mjs +8 -0
  185. package/src/standalone/session-protocol.mjs +5 -0
  186. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
  187. package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
  188. package/src/standalone/session-service.mjs +13 -0
  189. package/src/standalone/session-tool-output.mjs +8 -5
  190. package/src/tui/app/model-options.mjs +2 -0
  191. package/src/tui/dist/index.mjs +24 -2
  192. package/src/tui/session/notice-surface.mjs +17 -1
  193. package/src/tui/session/session-api/integrations.mjs +1 -0
  194. package/src/tui/session/session-api/lifecycle.mjs +8 -0
  195. package/src/tui/session/session-api/settings.mjs +14 -2
  196. package/src/tui/session/turn-stream.mjs +17 -1
  197. package/src/tui/session/turn.mjs +22 -2
@@ -38,7 +38,7 @@ export const SETUP_EXTENDED_ACTION_FIELDS = Object.freeze({
38
38
  add_hook_rule: 'hookRule',
39
39
  set_hook_rule_enabled: 'ruleIndex enabled',
40
40
  delete_hook_rule: 'ruleIndex',
41
- set_media_default: 'kind lane model?',
41
+ set_media_default: 'kind lane model? accountId?',
42
42
  });
43
43
 
44
44
  /** Mirrors RULE_ACTIONS in services/hook-bus/rule-store.mjs. */
@@ -177,6 +177,10 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
177
177
  entry: object(
178
178
  {
179
179
  name: text,
180
+ newName: {
181
+ ...text,
182
+ description: 'Schedule only, with overwrite: rename the schedule `name` to this; its history is kept.',
183
+ },
180
184
  description: text,
181
185
  instructions: text,
182
186
  time: { ...text, description: 'Schedule cron expression. Mutually exclusive with at.' },
@@ -234,6 +238,30 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
234
238
  maximum: 400,
235
239
  description: 'Windows only: notification sound volume percent.',
236
240
  },
241
+ botOverlay: object(
242
+ {
243
+ enabled: {
244
+ ...boolean,
245
+ description: 'Show the representative bot overlay; refused while no representative bot exists.',
246
+ },
247
+ position: {
248
+ type: 'string',
249
+ enum: ['pinned', 'pinnedHere', 'free', 'onScreen'],
250
+ description:
251
+ 'pinned = pin above the message box (default); pinnedHere = pin here; free = free in the app window; onScreen = free on screen.',
252
+ },
253
+ keepAbove: { ...boolean, description: 'Keep above other windows (default off).' },
254
+ collapsed: { ...boolean, description: 'Collapse the bubble so only the bot shows (default off).' },
255
+ autoAdvance: { ...boolean, description: 'Auto-advance long replies (default on).' },
256
+ autoClose: { ...boolean, description: 'Close replies automatically once read (default on).' },
257
+ scale: {
258
+ type: 'integer',
259
+ enum: [50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200],
260
+ description: 'Bot size in percent (default 100).',
261
+ },
262
+ },
263
+ []
264
+ ),
237
265
  }),
238
266
  appearance: object({
239
267
  theme: { type: 'string', enum: ['system', 'dark', 'white'] },
@@ -242,6 +270,34 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
242
270
  description: 'Desktop UI language id from status appearance; independent of profile language.',
243
271
  },
244
272
  sidePanels: { type: 'string', enum: ['close-left', 'close-right', 'close-both', 'keep-open'] },
273
+ sidePanelPosition: {
274
+ type: 'string',
275
+ enum: ['right', 'bottom', 'auto'],
276
+ description: 'Where the side dock sits on desktop: beside the chat, below it, or by window shape (default right).',
277
+ },
278
+ timeFormat: {
279
+ type: 'string',
280
+ enum: ['system', '12h', '24h'],
281
+ description: 'Clock style for every time of day shown (default system = the UI language own clock).',
282
+ },
283
+ uiScale: {
284
+ type: 'number',
285
+ minimum: 0.85,
286
+ maximum: 1.2,
287
+ description: 'UI size multiplier, 0.85-1.2 in steps of 0.05 (default 1).',
288
+ },
289
+ codeSize: {
290
+ type: 'integer',
291
+ minimum: 10,
292
+ maximum: 20,
293
+ description: 'Code text size in px, 10-20 (default 13).',
294
+ },
295
+ codeLigatures: { ...boolean, description: 'Join symbols such as => into one glyph in code (default on).' },
296
+ chatTextSize: {
297
+ type: 'string',
298
+ enum: ['small', 'default', 'large', 'extra-large'],
299
+ description: 'Conversation and composer text size (default default).',
300
+ },
245
301
  markdownDefaultView: { type: 'string', enum: ['preview', 'rich', 'source'] },
246
302
  linkPreview: boolean,
247
303
  toolActivityExpansion: { type: 'string', enum: ['collapsed', 'commands', 'all'] },
@@ -290,10 +346,15 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
290
346
  minimum: 0,
291
347
  description: 'set_hook_rule_enabled/delete_hook_rule: rule index from status hooks.',
292
348
  },
293
- lane: { ...nonBlank, description: 'set_media_default: lane id from the media catalog.' },
349
+ lane: {
350
+ ...text,
351
+ description:
352
+ 'set_media_default: lane id from the media catalog to pin as the default generator for the kind; "" clears the pin (back to Auto).',
353
+ },
294
354
  model: {
295
355
  ...nonBlank,
296
- description: 'set_media_default: model id offered by the lane for the kind; omitted = the lane default.',
356
+ description:
357
+ 'set_media_default: model id offered by the lane for the kind; omitted = the lane default. set_model_preferences: model id of the provider.',
297
358
  },
298
359
  project: object({ path: text, alias: text }, ['path']),
299
360
  projectPath: { ...text, description: 'Exact registered Project root.' },
@@ -306,7 +367,7 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
306
367
  riskAccepted: {
307
368
  ...boolean,
308
369
  description:
309
- "set_developer_option: the user's explicit acceptance of the option warning; required to enable an option that has one.",
370
+ "set_developer_option: the user's explicit acceptance of the option warning; required to enable an option that has one. Exception: allowAllActions (allow all actions except payments and permanent deletes) can only be turned on by the person in the desktop app; it is rejected regardless of riskAccepted, and remote enablement is rejected too.",
310
371
  },
311
372
  });
312
373
 
@@ -9,6 +9,7 @@ import { INSTRUCTION_FILES_MODES } from '../../runtime/shared/instruction-files-
9
9
  import { AUTO_EFFORT_BASES } from '../../runtime/agent/orchestrator/providers/auto-effort.mjs';
10
10
  import { WORKTREE_NEW_TASK_DEFAULTS } from '../../runtime/shared/worktrees/settings.mjs';
11
11
  import { CONNECTOR_IDS } from '../../runtime/shared/connector-catalog.mjs';
12
+ import { COMPACTION_SUMMARY_MODES } from '../../runtime/agent/orchestrator/session/compact/summary-mode.mjs';
12
13
 
13
14
  /** Built-in `setup` tool: the model's handle on a Mixdog user's persisted
14
15
  * configuration. The description is a contract only; how to use each action
@@ -99,6 +100,11 @@ export const SETUP_ACTION_FIELDS = Object.freeze({
99
100
  delete_local_model: 'confirmationToken',
100
101
  set_system_shell: 'command',
101
102
  set_auto_update: 'enabled',
103
+ set_ai_session_titles: 'enabled',
104
+ set_quota_auto_resume: 'enabled',
105
+ set_model_preferences: 'provider model hidden? favorite?',
106
+ set_heartbeat_enabled: 'enabled',
107
+ set_reactions_enabled: 'enabled',
102
108
  forget_provider_auth: 'name accountId?',
103
109
  set_provider_account: 'name providerAccount',
104
110
  add_mcp_server: 'server',
@@ -130,6 +136,7 @@ export const SETUP_BUILTIN_TOGGLE_FEATURES = Object.freeze([
130
136
  'computer',
131
137
  'voice',
132
138
  'visualize',
139
+ 'media',
133
140
  ]);
134
141
 
135
142
  const ROUTE_SCHEMA = {
@@ -248,13 +255,33 @@ export const SETUP_TOOL_DEFS = Object.freeze([
248
255
  properties: {
249
256
  mainBufferTokens: { type: 'integer', minimum: 0 },
250
257
  mainBufferPercent: { type: 'number', minimum: 0, maximum: 100 },
258
+ onModelChange: {
259
+ type: 'boolean',
260
+ description: 'Auto-compact a conversation on its current model before it switches to another one.',
261
+ },
262
+ summaryMode: {
263
+ type: 'string',
264
+ enum: [...COMPACTION_SUMMARY_MODES],
265
+ description:
266
+ 'Which model writes compaction summaries: smart (default), maintainer (the Maintainer), self (the conversation model).',
267
+ },
251
268
  },
252
- description: 'set_compaction: reserved Main context budget. Give tokens OR percent, not both.',
269
+ description:
270
+ 'set_compaction: reserved Main context budget (give tokens OR percent, not both), onModelChange and summaryMode; read back with status compaction.',
253
271
  },
254
272
  enabled: {
255
273
  type: 'boolean',
256
274
  description:
257
- 'Boolean setters (set_*_enabled, set_compaction, set_auto_update, set_mcp_enabled, set_plugin_enabled).',
275
+ 'Boolean setters (set_*_enabled, set_compaction, set_auto_update, set_ai_session_titles, set_quota_auto_resume, set_heartbeat_enabled, set_mcp_enabled, set_plugin_enabled).',
276
+ },
277
+ provider: { type: 'string', description: 'set_model_preferences: provider id.' },
278
+ hidden: {
279
+ type: 'boolean',
280
+ description: 'set_model_preferences: false lists the model in /model, true hides it; omitted = unchanged.',
281
+ },
282
+ favorite: {
283
+ type: 'boolean',
284
+ description: 'set_model_preferences: favorite star on or off; omitted = unchanged.',
258
285
  },
259
286
  instructionFiles: {
260
287
  type: 'string',
@@ -275,7 +302,7 @@ export const SETUP_TOOL_DEFS = Object.freeze([
275
302
  accountId: {
276
303
  type: 'string',
277
304
  description:
278
- 'forget_provider_auth: OAuth account id from status providers with detail:true; required when the provider has several accounts.',
305
+ 'forget_provider_auth: OAuth account id from status providers with detail:true; required when the provider has several accounts. set_media_default: optional account of the OAuth lane to pin; omitted = the lane provider\'s selected account.',
279
306
  },
280
307
  providerAccount: {
281
308
  type: 'object',
@@ -0,0 +1,99 @@
1
+ /**
2
+ * task-session-route.mjs — the model and reasoning effort a bot asks of a
3
+ * task session (the session tool's `model` / `effort`), checked against the
4
+ * provider catalog the desktop model menu lists (listProviderModels rows:
5
+ * provider, id, display, effortOptions).
6
+ */
7
+ import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
8
+ import { normalizeEffortInput } from '../runtime/agent/orchestrator/runtime-core/effort.mjs';
9
+
10
+ // Choices an error names: a short list, never the whole catalog.
11
+ const CHOICE_LIMIT = 6;
12
+
13
+ const lower = (value) => clean(value).toLowerCase();
14
+ const rowId = (row) => clean(row.id || row.model);
15
+ const sameModel = (row, route) => row.provider === route.provider && lower(rowId(row)) === lower(route.model);
16
+ const effortsOf = (row) => (Array.isArray(row?.effortOptions) ? row.effortOptions.map((option) => option.value) : []);
17
+ const choiceLabel = (row) => `${row.display || rowId(row)} (${row.provider}/${rowId(row)})`;
18
+
19
+ function choices(rows) {
20
+ const shown = rows.slice(0, CHOICE_LIMIT).map(choiceLabel).join('; ');
21
+ return rows.length > CHOICE_LIMIT ? `${shown}; … (${rows.length} total)` : shown;
22
+ }
23
+
24
+ /** An effort level word ("max") or null; "auto" is no level. */
25
+ function effortWord(word) {
26
+ try {
27
+ return normalizeEffortInput(word);
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ /** The catalog rows of the model menu, read through session `sessionId`'s runtime. */
34
+ export async function readModelCatalog(service, sessionId) {
35
+ const result = await service.readSession({ sessionId, action: 'listProviderModels', args: [{}] });
36
+ return Array.isArray(result?.value) ? result.value : [];
37
+ }
38
+
39
+ /** The route of a catalog display name (case-insensitive, exact), with a
40
+ * trailing effort word ("Claude Opus 5.5 max", or auto) read as the effort
41
+ * only when the whole text names no model. With `explicitEffort` the word
42
+ * only selects the name: the caller applies its own effort. Throws with the
43
+ * choices when the text names no model or several. */
44
+ export function routeByDisplayName(text, rows, explicitEffort = false) {
45
+ const wanted = lower(text);
46
+ const cut = wanted.lastIndexOf(' ');
47
+ const word = cut > 0 ? wanted.slice(cut + 1) : '';
48
+ const suffix = word === 'auto' || effortWord(word) ? word : null;
49
+ let hits = rows.filter((row) => lower(row.display) === wanted);
50
+ let effort = null;
51
+ if (!hits.length && suffix) {
52
+ const base = wanted.slice(0, cut).trim();
53
+ hits = rows.filter((row) => lower(row.display) === base);
54
+ effort = suffix;
55
+ }
56
+ if (!hits.length) {
57
+ const words = wanted.split(/\s+/).filter(Boolean);
58
+ const near = rows.filter((row) => words.every((word) => lower(row.display).includes(word)));
59
+ throw new Error(
60
+ `session: model "${clean(text)}" is neither a provider/model route, a preset name nor a model display name. ` +
61
+ `Choices: ${choices(near.length ? near : rows)}${suffix ? '. To set the reasoning effort, pass it as the effort field' : ''}`
62
+ );
63
+ }
64
+ const distinct = [...new Map(hits.map((row) => [`${row.provider}/${rowId(row)}`, row])).values()];
65
+ if (distinct.length > 1) {
66
+ throw new Error(`session: model "${clean(text)}" is ambiguous; use provider/model: ${choices(distinct)}`);
67
+ }
68
+ const route = { provider: distinct[0].provider, model: rowId(distinct[0]) };
69
+ return effort && !explicitEffort ? withEffort(route, effort, rows) : route;
70
+ }
71
+
72
+ /** `route` with the effort level `value`, checked against the levels its
73
+ * model supports ("auto" clears it). A chosen level turns the route's Auto
74
+ * effort off, as the menu's effort pick does, or Auto would replace it. */
75
+ export function withEffort(route, value, rows) {
76
+ const level = checkEffort(value, route, rows);
77
+ const { effort: _previous, ...rest } = route;
78
+ return level ? { ...rest, effort: level, autoEffort: false } : rest;
79
+ }
80
+
81
+ /** The effort level `value` names for `route` ("auto" is null); throws with
82
+ * the model's levels when it is unknown or unsupported. */
83
+ export function checkEffort(value, route, rows) {
84
+ const row = rows.find((candidate) => sameModel(candidate, route));
85
+ const allowed = row ? effortsOf(row) : null;
86
+ const offer = allowed
87
+ ? `; ${route.provider}/${route.model} supports: auto${allowed.map((level) => `, ${level}`).join('')}`
88
+ : '';
89
+ let level;
90
+ try {
91
+ level = normalizeEffortInput(value);
92
+ } catch (error) {
93
+ throw new Error(`session: ${error.message}${offer}`);
94
+ }
95
+ if (level && allowed && !allowed.includes(level)) {
96
+ throw new Error(`session: effort "${level}" is not supported${offer}`);
97
+ }
98
+ return level;
99
+ }
@@ -18,7 +18,9 @@ import { sessionRolePromptFields } from '../../runtime/agent/orchestrator/sessio
18
18
  import {
19
19
  delegationDisabled,
20
20
  honoredSchemaAllowlist,
21
+ reactionDisabled,
21
22
  } from '../../runtime/agent/orchestrator/session/manager/session-tool-surface.mjs';
23
+ import { REACTION_TOOL_NAME } from '../../runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs';
22
24
  import { finalizeSessionToolList } from '../../runtime/agent/orchestrator/session/manager/tool-resolution.mjs';
23
25
  import { isAgentOwner } from '../../runtime/agent/orchestrator/agent-owner.mjs';
24
26
  import { AGENT_MODE, sessionModeOf } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
@@ -40,14 +42,15 @@ export function createPolicyStage({
40
42
  activeWorkflowContext,
41
43
  }) {
42
44
  // The tools `session` is denied now: its caller's denials, the feature gates
43
- // as they stand, and an agent-mode Lead's agent tool when its workflow
45
+ // as they stand, an agent-mode Lead's agent tool when its workflow
44
46
  // delegates to no one (a coding Lead keeps it: workflow.mjs
45
- // workflowDisallowsAgentTool).
47
+ // workflowDisallowsAgentTool), and react outside a bot's chat with the user.
46
48
  function liveDeniedTools(session, workflow) {
47
49
  return [
48
50
  ...session.callerDisallowedTools,
49
51
  ...featureDisallowedTools(),
50
52
  ...(workflowDisallowsAgentTool(workflow, { agentMode: sessionModeOf(session) === AGENT_MODE }) ? ['agent'] : []),
53
+ ...(reactionDisabled(session, isAgentOwner(session)) ? [REACTION_TOOL_NAME] : []),
51
54
  ]
52
55
  .map((name) => String(name || ''))
53
56
  .filter(Boolean);
@@ -2,7 +2,6 @@
2
2
  // rewriting its system blocks (BP1 heading, BP3 core), and the configs a
3
3
  // session's workflow and orchestration mode give.
4
4
  import { AGENT_MODE, resolveSessionMode, sessionModeOf } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
5
- import { MIXY_AGENT_ID } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
6
5
  import { refreshSessionBp3Environment } from '../../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
7
6
  import { promptOrchestrationModeOf } from '../../runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs';
8
7
  import { sessionOrchestrationMode } from '../../runtime/shared/orchestration.mjs';
@@ -77,11 +76,11 @@ export function rewriteBp3Core(session, nextCore) {
77
76
  }
78
77
 
79
78
  // A Lead's workflow slices (orchestration, agent and contact catalogs) for its
80
- // mode, Mixy's with the contacts.
79
+ // mode; an agent-mode Lead's contacts are every bot but itself.
81
80
  export function leadPartsFor(session, leadPromptParts) {
82
81
  return leadPromptParts({
83
82
  agentMode: sessionModeOf(session) === AGENT_MODE,
84
- mixy: resolveSessionMode({ session }).agentId === MIXY_AGENT_ID,
83
+ agentId: resolveSessionMode({ session }).agentId,
85
84
  });
86
85
  }
87
86
 
@@ -18,6 +18,9 @@ import { workflowDisallowsAgentTool } from '../runtime/agent/orchestrator/runtim
18
18
  import { promptWorkflowOf } from '../runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs';
19
19
  import { configuredOrchestrationMode } from '../runtime/shared/orchestration.mjs';
20
20
  import { isAgentModeSession } from '../runtime/agent/orchestrator/session/agent-mode.mjs';
21
+ import { isAgentOwner } from '../runtime/agent/orchestrator/agent-owner.mjs';
22
+ import { reactionDisabled } from '../runtime/agent/orchestrator/session/manager/session-tool-surface.mjs';
23
+ import { REACTION_TOOL_NAME } from '../runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs';
21
24
 
22
25
  export function createToolSurface({
23
26
  mgr,
@@ -78,6 +81,7 @@ export function createToolSurface({
78
81
  ...disallowedModelToolNamesForProfile(profileCandidates, getToolProfile()),
79
82
  ...getFeatureDisallowedTools(),
80
83
  ...(workflowAllowsAgents() ? [] : [...agentToolNames]),
84
+ ...(reactionDisabled(session, isAgentOwner(session)) ? [REACTION_TOOL_NAME] : []),
81
85
  ];
82
86
  }
83
87
 
@@ -1,4 +1,4 @@
1
- import { join } from 'node:path';
1
+ import { dirname, isAbsolute, join, relative, sep } from 'node:path';
2
2
  import { createHash } from 'node:crypto';
3
3
  import {
4
4
  accessSync,
@@ -7,18 +7,19 @@ import {
7
7
  mkdirSync,
8
8
  readFileSync,
9
9
  readdirSync,
10
+ realpathSync,
10
11
  renameSync,
11
12
  rmSync,
12
13
  statSync,
13
14
  } from 'node:fs';
14
15
  import {
15
16
  holdsNoData,
16
- parseJsonText,
17
17
  readJsonStateSync,
18
18
  writeFileAtomicSync,
19
19
  writeJsonAtomicSync,
20
20
  } from '../../runtime/shared/atomic-file.mjs';
21
21
  import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
22
+ import { agentEntryFile, agentEntryPath, parseManifestText } from '../../runtime/agent/orchestrator/runtime-core/workflow/agents.mjs';
22
23
  import { hasOwn } from '../../runtime/shared/object.mjs';
23
24
  import { serializeFrontmatterDoc } from '../../runtime/shared/markdown-frontmatter.mjs';
24
25
  import { isHiddenAgent } from '../../runtime/agent/orchestrator/internal-agents.mjs';
@@ -79,9 +80,43 @@ import {
79
80
 
80
81
  const isFixedAgent = (id) => FIXED_AGENT_SLOTS.some((agent) => agent.id === id);
81
82
  const userDir = userDefinitionDir;
82
- const hasUserAgentFile = (dataDir, id) => existsSync(join(userDir(dataDir, id), 'AGENT.md'));
83
- // An archived (deleted) agent's AGENT.md, moved aside beside the tombstone:
84
- // no loader reads it, and restoring moves it back.
83
+ // The entry file of the agent folder `dir`, exactly as the loader reads it
84
+ // (agent.json `entry`, AGENT.md by default).
85
+ const entryIn = (dir) => agentEntryPath(dir, readManifest(dir));
86
+ const userEntryPath = (dataDir, id) => entryIn(userDir(dataDir, id));
87
+ // An agent exists where its folder has an AGENT.md, as discovery lists it.
88
+ const hasEntryFile = (dir) => existsSync(join(dir, 'AGENT.md'));
89
+ const hasUserAgentFile = (dataDir, id) => hasEntryFile(userDir(dataDir, id));
90
+ const leavesFolder = (rel) => rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel);
91
+ // The file delete moves aside and restore moves back: the agent's entry when
92
+ // it is named inside `dir` and its folder resolves inside too (a symlink as the
93
+ // last part moves as the link alone), else HEAD's AGENT.md. An entry outside the
94
+ // folder (`..`, or through a linked folder) is shared and never moved.
95
+ const archivableEntry = (dir, entry) => {
96
+ try {
97
+ const rel = relative(dir, entry);
98
+ if (rel && !leavesFolder(rel) && !leavesFolder(relative(realpathSync(dir), realpathSync(dirname(entry))))) {
99
+ return entry;
100
+ }
101
+ } catch {
102
+ // an unresolvable folder holds no entry to move
103
+ }
104
+ return join(dir, 'AGENT.md');
105
+ };
106
+ // The entry file the editor may hand out to edit in place: it resolves (symlinks
107
+ // followed) to a file inside its own agent folder, else null. The loader still
108
+ // reads an entry outside; only the editable path is contained.
109
+ const editablePathIn = (dir, path) => {
110
+ try {
111
+ return leavesFolder(relative(realpathSync(dir), realpathSync(path))) ? null : path;
112
+ } catch {
113
+ return null;
114
+ }
115
+ };
116
+ // An archived (deleted) agent's entry file, moved aside beside the tombstone
117
+ // as `<entry>.archived`: no loader reads it, and restoring moves it back.
118
+ const archivedEntryPath = (entryPath) => `${entryPath}.archived`;
119
+ // The default entry's archive (all that HEAD wrote); a save clears it.
85
120
  const ARCHIVED_AGENT_FILE = 'AGENT.md.archived';
86
121
 
87
122
  // Frontmatter the editor does not show (e.g. `permission`), taken from the
@@ -123,15 +158,19 @@ const writeManifest = (file, manifest) => writeJsonAtomicSync(file, manifest, DE
123
158
  // no data (empty, or NUL-filled by a crash) or is not a JSON object.
124
159
  function readManifestStrict(file) {
125
160
  try {
126
- const text = readFileSync(file, 'utf8');
127
- if (holdsNoData(text)) return null;
128
- const value = parseJsonText(text);
129
- return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
161
+ return readManifestFile(file);
130
162
  } catch {
131
163
  return null;
132
164
  }
133
165
  }
134
166
 
167
+ // The same, but a file that cannot be read throws its error.
168
+ function readManifestFile(file) {
169
+ const text = readFileSync(file, 'utf8');
170
+ if (holdsNoData(text)) return null;
171
+ return parseManifestText(text);
172
+ }
173
+
135
174
  // A read-only agent.json is a standing refusal, not a transient lock: the
136
175
  // atomic write's rename onto it fails with EPERM and is retried for seconds,
137
176
  // synchronously on the host's event loop. Refuse it up front (one stat, no wait),
@@ -559,10 +598,16 @@ function definitionPresence({ dataDir, rootDir, stat }, agentIds) {
559
598
  // A definition folder's revision: a hash of the three files an editor writes.
560
599
  function definitionDirRevision(dir) {
561
600
  const hash = createHash('sha256');
562
- for (const name of ['agent.json', 'AGENT.md', PERSONA_FILE]) {
601
+ const manifest = readManifest(dir);
602
+ const files = [
603
+ ['agent.json', join(dir, 'agent.json')],
604
+ [agentEntryFile(manifest), agentEntryPath(dir, manifest)],
605
+ [PERSONA_FILE, join(dir, PERSONA_FILE)],
606
+ ];
607
+ for (const [name, file] of files) {
563
608
  hash.update(`${name}\0`);
564
609
  try {
565
- hash.update(readFileSync(join(dir, name)));
610
+ hash.update(readFileSync(file));
566
611
  } catch {
567
612
  hash.update('\u0001absent');
568
613
  }
@@ -660,13 +705,27 @@ function createAgentArchiveApi({
660
705
  await announceBotChange(dataDir);
661
706
  return { id, deleted: true, revertedToBuiltIn: Boolean(sourceFallback) };
662
707
  }
663
- // A custom agent is archived: its AGENT.md (what makes it an agent
664
- // anywhere) moves aside, the tombstone keeps the packaged copy of a
665
- // starter from coming back, and the rest (agent.json: name and face,
666
- // persona, memory, its model and on/off) stays for its archived chat —
667
- // until restoreAgentDefinition brings it back or purgeAgentDefinition.
708
+ // A custom agent is archived: its entry file (AGENT.md, or the one its
709
+ // agent.json names; what makes it an agent anywhere) moves aside, the
710
+ // tombstone keeps the packaged copy of a starter from coming back, and the
711
+ // rest (agent.json: name and face, persona, memory, its model and on/off)
712
+ // stays for its archived chat — until restoreAgentDefinition brings it back
713
+ // or purgeAgentDefinition.
714
+ // AGENT.md always moves aside; an AGENT.md over an existing archive is
715
+ // refused, since the move would overwrite it. Another selected entry
716
+ // moves too when its own archive is free, and otherwise stays in place
717
+ // under the tombstone.
718
+ const agentFile = join(dir, 'AGENT.md');
719
+ const agentArchive = archivedEntryPath(agentFile);
720
+ if (existsSync(agentFile) && existsSync(agentArchive)) {
721
+ throw new Error(`agent "${id}" cannot be deleted: ${agentArchive} already exists; move or remove it first`);
722
+ }
723
+ const entry = archivableEntry(dir, entryIn(dir));
668
724
  mkdirSync(dir, { recursive: true });
669
- if (existsSync(join(dir, 'AGENT.md'))) renameSync(join(dir, 'AGENT.md'), join(dir, ARCHIVED_AGENT_FILE));
725
+ if (existsSync(agentFile)) renameSync(agentFile, agentArchive);
726
+ if (entry !== agentFile && existsSync(entry) && !existsSync(archivedEntryPath(entry))) {
727
+ renameSync(entry, archivedEntryPath(entry));
728
+ }
670
729
  writeFileAtomicSync(join(dir, AGENT_DELETED_MARKER), 'deleted\n');
671
730
  clearAgentDefinitionCache(id);
672
731
  // The Leads listing it drop it from their roster from their next turn.
@@ -683,11 +742,22 @@ function createAgentArchiveApi({
683
742
  const dataDir = dataDirOf();
684
743
  if (!isArchivedAgent(dataDir, id)) throw new Error(`agent "${id}" is not deleted`);
685
744
  const dir = userDir(dataDir, id);
686
- const kept = join(dir, ARCHIVED_AGENT_FILE);
687
- if (!existsSync(kept) && !existsSync(join(shippedDir(id), 'AGENT.md'))) {
745
+ // AGENT.md.archived back to AGENT.md, and the selected entry's archive back
746
+ // to that entry. Nothing is overwritten: an archive whose file exists again
747
+ // stays as it is, and when every kept archive collides the restore is refused.
748
+ const agentFile = join(dir, 'AGENT.md');
749
+ const entry = archivableEntry(dir, entryIn(dir));
750
+ const moves = [{ from: join(dir, ARCHIVED_AGENT_FILE), to: agentFile }];
751
+ if (entry !== agentFile) moves.push({ from: archivedEntryPath(entry), to: entry });
752
+ const kept = moves.filter((move) => existsSync(move.from));
753
+ if (kept.length === 0 && !hasEntryFile(shippedDir(id))) {
688
754
  throw new Error(`agent "${id}" was deleted before deleted agents were kept, so it cannot be restored`);
689
755
  }
690
- if (existsSync(kept)) renameSync(kept, join(dir, 'AGENT.md'));
756
+ const free = kept.filter((move) => !existsSync(move.to));
757
+ if (kept.length > 0 && free.length === 0) {
758
+ throw new Error(`agent "${id}" cannot be restored: ${kept[0].to} already exists; move or remove it first`);
759
+ }
760
+ for (const move of free) renameSync(move.from, move.to);
691
761
  rmSync(join(dir, AGENT_DELETED_MARKER), { force: true });
692
762
  clearAgentDefinitionCache(id);
693
763
  // Back in the roster of the Leads that list it, from their next turn.
@@ -795,13 +865,20 @@ export function createAgentEditorApi(deps) {
795
865
  const dataDir = resolveDataDir(deps);
796
866
  const definition = loadAgentDefinition(dataDir, id);
797
867
  if (!definition) throw new Error(`agent "${agentId}" not found`);
868
+ const userOverride = hasUserAgentFile(dataDir, id);
798
869
  return {
799
870
  id,
800
871
  name: definition.name,
801
872
  description: definition.description,
802
873
  body: definition.body,
803
874
  custom: !isFixedAgent(id),
804
- userOverride: hasUserAgentFile(dataDir, id),
875
+ userOverride,
876
+ // The user copy's entry file, editable in place; a shipped-only agent has
877
+ // none (an install-dir edit would be lost on update), nor has an entry
878
+ // that resolves outside its folder (the body is still returned).
879
+ ...(userOverride && editablePathIn(userDir(dataDir, id), userEntryPath(dataDir, id))
880
+ ? { path: userEntryPath(dataDir, id) }
881
+ : {}),
805
882
  route: routeFor(getConfig(), id),
806
883
  routeConfigured: routeConfigured(getConfig(), id),
807
884
  kind: kindOf(definition),
@@ -77,13 +77,18 @@ export function createWorkflowPacksApi(deps) {
77
77
  const dataDir = resolveDataDir(deps);
78
78
  const pack = loadWorkflowPack(dataDir, id);
79
79
  if (!pack || pack.id !== id) throw new Error(`workflow "${workflowId}" not found`);
80
+ // The user copy's file, so a part of a long body can be edited in place;
81
+ // a shipped-only pack has none (its install copy is replaced on update).
82
+ const file = join(dataDir, 'workflows', id, 'WORKFLOW.md');
83
+ const userOverride = existsSync(file);
80
84
  return {
81
85
  id: pack.id,
82
86
  name: pack.name,
83
87
  description: pack.description,
84
88
  source: pack.source,
85
89
  body: pack.body,
86
- userOverride: existsSync(join(dataDir, 'workflows', id, 'WORKFLOW.md')),
90
+ userOverride,
91
+ ...(userOverride ? { path: file } : {}),
87
92
  };
88
93
  }
89
94
 
@@ -426,7 +426,7 @@ export function createWorkspaceTransitions({
426
426
  * refuses while a session or worker runs in an affected folder, or a
427
427
  * background command does; `removing` refuses while any session still uses
428
428
  * one. Then holds the folders — Mixdog checkouts only; an external checkout
429
- * is never lock-blocked — so new Mixdog work there waits until `run`
429
+ * takes no lease from this profile — so new Mixdog work there waits until `run`
430
430
  * settles. `actor`: the session acting on its own checkout, whose own turn
431
431
  * and workers its caller already checked (assertLeadIdle).
432
432
  */
@@ -985,7 +985,15 @@ export function createWorkspaceTransitions({
985
985
  * `ownTurn` (internal: the Lead's own tool call) tolerates the calling turn.
986
986
  */
987
987
  async function requestWorktree(
988
- { name = null, confirmLarge = false, provision = false, carryChanges, prompt = '', txId = '', expectedRevision } = {},
988
+ {
989
+ name = null,
990
+ confirmLarge = false,
991
+ provision = false,
992
+ carryChanges,
993
+ prompt = '',
994
+ txId = '',
995
+ expectedRevision,
996
+ } = {},
989
997
  { ownTurn = false } = {}
990
998
  ) {
991
999
  const nameHint = validName(name);