mixdog 0.9.84 → 0.9.86

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 (91) hide show
  1. package/README.md +7 -1
  2. package/package.json +3 -2
  3. package/scripts/channel-daemon-stub.mjs +2 -1
  4. package/src/defaults/agents.json +12 -0
  5. package/src/defaults/skills/setup/SKILL.md +44 -12
  6. package/src/help.mjs +40 -9
  7. package/src/lib/keychain-cjs.cjs +11 -1
  8. package/src/rules/agent/43-title-agent.md +22 -0
  9. package/src/rules/shared/01-tool.md +3 -3
  10. package/src/runtime/agent/orchestrator/mcp/client.mjs +24 -0
  11. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +84 -3
  12. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -2
  13. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +30 -3
  14. package/src/runtime/agent/orchestrator/session/context-utils.mjs +73 -45
  15. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +15 -18
  16. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +1 -1
  17. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +38 -4
  18. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -0
  19. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +183 -41
  20. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +31 -12
  21. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +12 -2
  22. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +14 -0
  23. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +170 -0
  24. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +45 -6
  25. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +26 -0
  26. package/src/runtime/agent/orchestrator/session/token-bpe.mjs +42 -0
  27. package/src/runtime/agent/orchestrator/session/token-native.mjs +186 -0
  28. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +15 -5
  29. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +20 -0
  30. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +1 -1
  31. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +24 -0
  32. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +44 -73
  33. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  34. package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +286 -0
  35. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  36. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +152 -18
  37. package/src/runtime/agent/orchestrator/tools/token-binary-fetcher.mjs +201 -0
  38. package/src/runtime/agent/orchestrator/tools/token-manifest.json +5 -0
  39. package/src/runtime/channels/lib/owned-runtime.mjs +6 -8
  40. package/src/runtime/channels/lib/parent-bridge.mjs +27 -15
  41. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +3 -3
  42. package/src/runtime/channels/lib/worker-ipc.mjs +17 -4
  43. package/src/runtime/media/adapters/gemini-video.mjs +1 -1
  44. package/src/runtime/media/renditions.mjs +1 -1
  45. package/src/runtime/media/store.mjs +1 -1
  46. package/src/runtime/memory/index.mjs +14 -5
  47. package/src/runtime/memory/lib/agent-ipc.mjs +11 -12
  48. package/src/runtime/shared/atomic-file.mjs +76 -38
  49. package/src/runtime/shared/automation-attachments.mjs +3 -3
  50. package/src/runtime/shared/child-guardian.mjs +15 -5
  51. package/src/runtime/shared/child-spawn-gate.mjs +6 -12
  52. package/src/runtime/shared/err-text.mjs +1 -0
  53. package/src/runtime/shared/resource-admission.mjs +7 -3
  54. package/src/runtime/shared/safe-ipc-send.mjs +34 -0
  55. package/src/runtime/shared/turn-snapshot.mjs +0 -3
  56. package/src/session-runtime/lifecycle-api.mjs +43 -12
  57. package/src/session-runtime/prewarm.mjs +35 -0
  58. package/src/session-runtime/remote-control.mjs +8 -47
  59. package/src/session-runtime/remote-transcript.mjs +3 -7
  60. package/src/session-runtime/route-preparation.mjs +24 -0
  61. package/src/session-runtime/runtime-core.mjs +42 -86
  62. package/src/session-runtime/session-turn-api.mjs +9 -2
  63. package/src/session-runtime/workflow-agents-api.mjs +3 -0
  64. package/src/standalone/agent-shard/shard-child.mjs +300 -0
  65. package/src/standalone/agent-shard/shard-pool.mjs +442 -0
  66. package/src/standalone/agent-tool.mjs +222 -0
  67. package/src/standalone/channel-daemon-transport.mjs +126 -185
  68. package/src/standalone/channel-daemon.mjs +11 -11
  69. package/src/standalone/channel-worker.mjs +21 -19
  70. package/src/standalone/memory-runtime-proxy.mjs +98 -23
  71. package/src/tui/App.jsx +16 -9
  72. package/src/tui/app/channel-pickers.mjs +3 -8
  73. package/src/tui/app/route-pickers.mjs +117 -152
  74. package/src/tui/app/settings-picker.mjs +90 -89
  75. package/src/tui/app/slash-commands.mjs +7 -7
  76. package/src/tui/app/slash-dispatch.mjs +23 -58
  77. package/src/tui/app/theme-effort-pickers.mjs +3 -1
  78. package/src/tui/components/StatusLine.jsx +1 -1
  79. package/src/tui/components/tool-execution/ResultBody.jsx +1 -1
  80. package/src/tui/dist/index.mjs +358 -380
  81. package/src/tui/engine/live-share.mjs +2 -2
  82. package/src/tui/engine/session-api-ext.mjs +24 -9
  83. package/src/tui/engine/session-flow.mjs +20 -10
  84. package/src/tui/engine/turn.mjs +28 -16
  85. package/src/tui/engine.mjs +24 -2
  86. package/src/tui/figures.mjs +0 -1
  87. package/src/ui/statusline-format.mjs +0 -1
  88. package/src/hooks/lib/permission-evaluator.cjs +0 -24
  89. package/src/lib/config-cjs.cjs +0 -61
  90. package/src/runtime/shared/launcher-control.mjs +0 -258
  91. package/src/runtime/shared/workspace-router.mjs +0 -259
@@ -30,8 +30,6 @@ export function createSettingsPicker({
30
30
  openAgentsPicker,
31
31
  openWorkflowPicker,
32
32
  openOutputStylePicker,
33
- openBridgePicker,
34
- openToolsPicker,
35
33
  openProviderSetupPicker,
36
34
  openThemePicker,
37
35
  openAutoClearPicker,
@@ -40,6 +38,7 @@ export function createSettingsPicker({
40
38
  openPluginsPicker,
41
39
  openHooksPicker,
42
40
  openSkillsPicker,
41
+ openMemoryCorePicker,
43
42
  openUpdatePicker,
44
43
  openChannelSettingTypePicker,
45
44
  }) {
@@ -81,8 +80,7 @@ export function createSettingsPicker({
81
80
  const remoteEnabled = store.isRemoteEnabled?.() === true;
82
81
  const remoteRuntimeDescription = channelWorker?.running
83
82
  ? `runtime running · pid ${channelWorker.pid}`
84
- : 'runtime stopped';
85
- const compactType = compaction.compactType || compaction.type || 'semantic';
83
+ : 'Stopped. Manual ON claims remote from any other session.';
86
84
  const compactTypeLabel = 'Fast-track (fixed)';
87
85
  const outputStyleLabel = outputStyle?.current?.label || outputStyle?.current?.id || outputStyle?.configured || 'Default';
88
86
  const workflowLabel = workflowDisplayName(workflow);
@@ -223,7 +221,38 @@ export function createSettingsPicker({
223
221
  }
224
222
  openSettingsPicker({ light: true, overrides: { channelBackend: chosen } });
225
223
  };
224
+ // Row order groups by concern — routing/model first, then session
225
+ // behavior, integrations, channels/remote, system — and must stay in
226
+ // sync with desktop SETTINGS_ITEMS (tui-parity test, minus system-shell).
226
227
  const items = [
228
+ {
229
+ value: 'model',
230
+ label: 'Model',
231
+ meta: displayModelName(state.model, state.provider),
232
+ description: 'Main chat model.',
233
+ _action: 'model',
234
+ },
235
+ {
236
+ value: 'search',
237
+ label: 'Search model',
238
+ meta: routeModelLabel(store.getSearchRoute?.()),
239
+ description: 'Native search model.',
240
+ _action: 'search',
241
+ },
242
+ {
243
+ value: 'workflow',
244
+ label: 'Workflow',
245
+ meta: workflowLabel,
246
+ description: 'Active agent routing profile.',
247
+ _action: 'workflow',
248
+ },
249
+ {
250
+ value: 'output-style',
251
+ label: 'Output style',
252
+ meta: outputStyleLabel,
253
+ description: 'Response tone and format.',
254
+ _action: 'output-style',
255
+ },
227
256
  {
228
257
  value: 'profile',
229
258
  label: 'Profile',
@@ -238,13 +267,17 @@ export function createSettingsPicker({
238
267
  _action: 'profile',
239
268
  },
240
269
  {
241
- value: 'autoclear',
242
- label: 'Auto-clear',
243
- meta: autoClearEnabled ? `On (${formatDuration(autoClear.idleMs)})` : 'Off',
244
- description: autoClearEnabled
245
- ? `Clear idle sessions after ${formatDuration(autoClear.idleMs)}${autoClear.custom ? '' : ` (${autoClear.provider || 'default'} default)`}. Enter for options.`
246
- : 'Idle auto-clear disabled. Enter for options.',
247
- _action: 'autoclear',
270
+ value: 'theme',
271
+ label: 'Theme',
272
+ meta: (() => {
273
+ try {
274
+ const id = store.getTheme?.();
275
+ const entry = (store.listThemes?.() || []).find((t) => t.id === id);
276
+ return entry?.label || id || 'Default';
277
+ } catch { return 'Default'; }
278
+ })(),
279
+ description: 'TUI color theme.',
280
+ _action: 'theme',
248
281
  },
249
282
  {
250
283
  value: 'autocompact',
@@ -261,74 +294,22 @@ export function createSettingsPicker({
261
294
  _action: null,
262
295
  },
263
296
  {
264
- value: 'channels',
265
- label: 'Channels enabled',
266
- meta: boolLabel(channels.enabled !== false),
267
- description: channels.enabled === false
268
- ? 'Channel tools disabled.'
269
- : 'Discord, schedules, and webhooks.',
270
- _action: 'channels',
271
- },
272
- {
273
- value: 'remote-runtime',
274
- label: 'Remote Runtime',
275
- meta: boolLabel(remoteEnabled),
276
- description: remoteRuntimeDescription,
277
- _action: 'remote-runtime',
278
- },
279
- {
280
- value: 'channel-backend',
281
- label: 'Channel',
282
- meta: channelBackendLabel,
283
- description: 'Left/Right or Enter changes channel type (Discord or Telegram).',
284
- _action: 'channel-backend',
285
- },
286
- {
287
- value: 'channel-setting',
288
- label: 'Setting',
289
- description: 'Configure credentials and main channel/chat for the active type.',
290
- _action: 'channel-setting',
291
- },
292
- {
293
- value: 'output-style',
294
- label: 'Output style',
295
- meta: outputStyleLabel,
296
- description: 'Response tone and format.',
297
- _action: 'output-style',
298
- },
299
- {
300
- value: 'theme',
301
- label: 'Theme',
302
- meta: (() => {
303
- try {
304
- const id = store.getTheme?.();
305
- const entry = (store.listThemes?.() || []).find((t) => t.id === id);
306
- return entry?.label || id || 'Default';
307
- } catch { return 'Default'; }
308
- })(),
309
- description: 'TUI color theme.',
310
- _action: 'theme',
311
- },
312
- {
313
- value: 'workflow',
314
- label: 'Workflow',
315
- meta: workflowLabel,
316
- description: 'Active agent routing profile.',
317
- _action: 'workflow',
318
- },
319
- {
320
- value: 'model',
321
- label: 'Model',
322
- meta: displayModelName(state.model, state.provider),
323
- description: 'Main chat model.',
324
- _action: 'model',
297
+ value: 'autoclear',
298
+ label: 'Auto-clear',
299
+ meta: autoClearEnabled ? `On (${formatDuration(autoClear.idleMs)})` : 'Off',
300
+ description: autoClearEnabled
301
+ ? `Clear idle sessions after ${formatDuration(autoClear.idleMs)}${autoClear.custom ? '' : ` (${autoClear.provider || 'default'} default)`}. Enter for options.`
302
+ : 'Idle auto-clear disabled. Enter for options.',
303
+ _action: 'autoclear',
325
304
  },
326
305
  {
327
- value: 'search',
328
- label: 'Search model',
329
- meta: routeModelLabel(store.getSearchRoute?.()),
330
- description: 'Native search model.',
331
- _action: 'search',
306
+ value: 'memory',
307
+ label: 'Memory',
308
+ meta: boolLabel(memory.enabled !== false),
309
+ description: memory.enabled === false
310
+ ? 'Memory off. Core memories stay editable.'
311
+ : 'Session memory and core memories.',
312
+ _action: 'memory',
332
313
  },
333
314
  {
334
315
  value: 'providers',
@@ -360,6 +341,37 @@ export function createSettingsPicker({
360
341
  description: `${skills.count || 0} available`,
361
342
  _action: 'skills',
362
343
  },
344
+ {
345
+ value: 'channels',
346
+ label: 'Channels enabled',
347
+ meta: boolLabel(channels.enabled !== false),
348
+ // Messaging-only toggle: schedules/webhooks run through the automation
349
+ // runtime and no longer depend on this switch (desktop parity).
350
+ description: channels.enabled === false
351
+ ? 'Messaging disabled. Schedules and webhooks keep running.'
352
+ : 'Discord and Telegram messaging.',
353
+ _action: 'channels',
354
+ },
355
+ {
356
+ value: 'channel-backend',
357
+ label: 'Channel',
358
+ meta: channelBackendLabel,
359
+ description: 'Left/Right or Enter changes channel type (Discord or Telegram).',
360
+ _action: 'channel-backend',
361
+ },
362
+ {
363
+ value: 'channel-setting',
364
+ label: 'Setting',
365
+ description: 'Configure credentials and main channel/chat for the active type.',
366
+ _action: 'channel-setting',
367
+ },
368
+ {
369
+ value: 'remote-runtime',
370
+ label: 'Remote Runtime',
371
+ meta: boolLabel(remoteEnabled),
372
+ description: remoteRuntimeDescription,
373
+ _action: 'remote-runtime',
374
+ },
363
375
  {
364
376
  value: 'system-shell',
365
377
  label: 'System shell',
@@ -401,10 +413,6 @@ export function createSettingsPicker({
401
413
  onLeft: (item) => {
402
414
  if (item?._action === 'autoclear') toggleAutoClear();
403
415
  else if (item?._action === 'autocompact') applyCompaction({ auto: !(compaction.auto !== false) });
404
- else if (item?._action === 'compact-type') {
405
- const nextType = compactType === 'recall-fasttrack' ? 'semantic' : 'recall-fasttrack';
406
- applyCompaction({ compactType: nextType });
407
- }
408
416
  else if (item?._action === 'channels') applyChannels(!(channels.enabled !== false));
409
417
  else if (item?._action === 'remote-runtime') applyRemoteRuntime();
410
418
  else if (item?._action === 'channel-backend') cycleChannelBackend(-1);
@@ -415,10 +423,6 @@ export function createSettingsPicker({
415
423
  onRight: (item) => {
416
424
  if (item?._action === 'autoclear') toggleAutoClear();
417
425
  else if (item?._action === 'autocompact') applyCompaction({ auto: !(compaction.auto !== false) });
418
- else if (item?._action === 'compact-type') {
419
- const nextType = compactType === 'recall-fasttrack' ? 'semantic' : 'recall-fasttrack';
420
- applyCompaction({ compactType: nextType });
421
- }
422
426
  else if (item?._action === 'channels') applyChannels(!(channels.enabled !== false));
423
427
  else if (item?._action === 'remote-runtime') applyRemoteRuntime();
424
428
  else if (item?._action === 'channel-backend') cycleChannelBackend(1);
@@ -430,10 +434,6 @@ export function createSettingsPicker({
430
434
  if (item._action === 'autoclear') openAutoClearPicker({ returnTo: openSettingsPicker });
431
435
  else if (item._action === 'profile') openProfilePicker({ returnTo: openSettingsPicker });
432
436
  else if (item._action === 'autocompact') applyCompaction({ auto: !(compaction.auto !== false) });
433
- else if (item._action === 'compact-type') {
434
- const nextType = compactType === 'recall-fasttrack' ? 'semantic' : 'recall-fasttrack';
435
- applyCompaction({ compactType: nextType });
436
- }
437
437
  else if (item._action === 'channels') applyChannels(!(channels.enabled !== false));
438
438
  else if (item._action === 'remote-runtime') applyRemoteRuntime();
439
439
  else if (item._action === 'channel-backend') cycleChannelBackend(1);
@@ -462,6 +462,7 @@ export function createSettingsPicker({
462
462
  else if (item._action === 'plugins') openPluginsPicker();
463
463
  else if (item._action === 'hooks') openHooksPicker();
464
464
  else if (item._action === 'skills') openSkillsPicker();
465
+ else if (item._action === 'memory') openMemoryCorePicker({ returnTo: openSettingsPicker });
465
466
  else if (item._action === 'system-shell') {
466
467
  setPicker(null);
467
468
  setSettingsPrompt({
@@ -5,23 +5,23 @@
5
5
  */
6
6
  export const SLASH_COMMANDS = [
7
7
  { name: 'clear', usage: '/clear', aliases: ['new'], aliasUsage: ['new'], description: 'Start a fresh chat' },
8
- { name: 'project', usage: '/project', aliases: ['projects'], aliasUsage: ['projects'], showAliasUsage: false, description: 'Switch working directory (project)' },
8
+ { name: 'project', usage: '/project', aliases: ['projects'], aliasUsage: ['projects'], showAliasUsage: false, params: '[path]', description: 'Switch working directory (project)' },
9
9
  { name: 'compact', usage: '/compact', description: 'Compact older conversation context' },
10
- { name: 'autoclear', usage: '/autoclear', description: 'Reduce cache-miss cost after long idle gaps' },
11
- { name: 'resume', usage: '/resume', description: 'Resume a saved chat' },
10
+ { name: 'autoclear', usage: '/autoclear', params: '[on|off|duration]', description: 'Reduce cache-miss cost after long idle gaps' },
11
+ { name: 'resume', usage: '/resume', params: '[id]', description: 'Resume a saved chat' },
12
12
  { name: 'context', usage: '/context', description: 'Show current context surface' },
13
13
  { name: 'usage', usage: '/usage', params: '[refresh]', description: 'Show total provider quota / balance' },
14
- { name: 'model', usage: '/model', description: 'Switch model for subsequent turns' },
14
+ { name: 'model', usage: '/model', params: '[name|refresh]', description: 'Switch model for subsequent turns' },
15
15
  { name: 'search', usage: '/search', description: 'Set the web search provider/model' },
16
- { name: 'workflow', usage: '/workflow', description: 'Switch the active workflow' },
16
+ { name: 'workflow', usage: '/workflow', params: '[name]', description: 'Switch the active workflow' },
17
17
  { name: 'outputstyle', usage: '/OutputStyle', aliases: ['output-style', 'style'], aliasUsage: ['style'], showAliasUsage: false, params: '[name]', description: 'Switch Lead output style' },
18
18
  { name: 'theme', usage: '/theme', params: '[id]', description: 'Change the TUI color theme' },
19
- { name: 'agents', usage: '/agents', description: 'Show available workflow agents' },
19
+ { name: 'agents', usage: '/agents', params: '[refresh]', description: 'Show available workflow agents' },
20
20
  { name: 'effort', usage: '/effort', params: '[level]', description: 'Set reasoning effort for the current model' },
21
21
  { name: 'fast', usage: '/fast', params: '[on|off]', description: 'Toggle Fast mode for the current model' },
22
22
  { name: 'mcp', usage: '/mcp', description: 'Manage MCP servers and tools' },
23
23
  { name: 'skills', usage: '/skills', description: 'Choose a skill for the next request' },
24
- { name: 'memory', usage: '/memory', description: 'List and edit core memories' },
24
+ { name: 'memory', usage: '/memory', params: '[status|core ...]', description: 'List and edit core memories' },
25
25
  { name: 'plugins', usage: '/plugins', description: 'Manage local plugin integrations' },
26
26
  { name: 'hooks', usage: '/hooks', description: 'Manage before-tool hook rules and events' },
27
27
  { name: 'providers', usage: '/providers', description: 'Manage auth, API keys, OAuth, and local endpoints' },
@@ -9,6 +9,15 @@
9
9
  * defined later in the App factory zone are passed as lazy getters so their
10
10
  * live binding is used at call time.
11
11
  */
12
+ import { presentErrorText } from '../../runtime/shared/err-text.mjs';
13
+
14
+ export function compactFailureNotice(error) {
15
+ if (error == null || String(error).trim() === '') return 'Compact failed.';
16
+ const reason = presentErrorText(error, { surface: 'compact', max: 320 });
17
+ if (!reason || reason === 'Unknown error') return 'Compact failed.';
18
+ return /^compact(?:ion)? failed\b/i.test(reason) ? reason : `Compact failed: ${reason}`;
19
+ }
20
+
12
21
  export function createSlashDispatch({
13
22
  state,
14
23
  store,
@@ -26,10 +35,8 @@ export function createSlashDispatch({
26
35
  openThemePicker,
27
36
  themeNotice,
28
37
  openEffortPicker,
29
- projectNameFromPath,
30
38
  enterProject,
31
39
  openProjectPicker,
32
- openToolsPicker,
33
40
  openMcpPicker,
34
41
  openSkillsPicker,
35
42
  openPluginsPicker,
@@ -179,24 +186,21 @@ export function createSlashDispatch({
179
186
  }
180
187
  return true;
181
188
  }
182
- case 'effort':
183
- if (state.busy) {
184
- store.pushNotice('wait for the current turn to finish before /effort', 'warn');
185
- return false;
186
- }
189
+ case 'effort': {
190
+ // A running turn no longer blocks the switch: the in-flight turn keeps
191
+ // the effort it started with, and the new level applies from the next
192
+ // turn. Same for /fast below.
193
+ const pendingTurn = state.busy ? ' (applies from the next turn)' : '';
187
194
  if (!arg) {
188
195
  openEffortPicker();
189
196
  return true;
190
197
  }
191
198
  void store.setEffort(arg)
192
- .then(result => store.pushNotice(result ? `Effort set to ${result}` : 'Effort switch is already running.', result ? 'info' : 'warn'))
199
+ .then(result => store.pushNotice(result ? `Effort set to ${result}${pendingTurn}` : 'Effort switch is already running.', result ? 'info' : 'warn'))
193
200
  .catch((e) => store.pushNotice(`Couldn’t switch effort: ${e?.message || e}`, 'error'));
194
201
  return true;
202
+ }
195
203
  case 'fast': {
196
- if (state.busy) {
197
- store.pushNotice('wait for the current turn to finish before /fast', 'warn');
198
- return false;
199
- }
200
204
  const value = String(arg || '').trim().toLowerCase();
201
205
  const setTo = value
202
206
  ? ['1', 'true', 'yes', 'on', 'enable', 'enabled'].includes(value)
@@ -216,24 +220,15 @@ export function createSlashDispatch({
216
220
  store.pushNotice('Fast mode switch is already running.', 'warn');
217
221
  return;
218
222
  }
219
- store.pushNotice(`Fast mode ${enabled ? 'on' : 'off'} for ${state.provider}/${state.model}`, 'info');
223
+ store.pushNotice(
224
+ `Fast mode ${enabled ? 'on' : 'off'} for ${state.provider}/${state.model}`
225
+ + (state.busy ? ' (applies from the next turn)' : ''),
226
+ 'info',
227
+ );
220
228
  })
221
229
  .catch((e) => store.pushNotice(`Couldn’t update fast mode: ${e?.message || e}`, 'error'));
222
230
  return true;
223
231
  }
224
- case 'cwd': {
225
- const nextPath = arg.trim();
226
- if (!nextPath) {
227
- store.pushNotice(`Project path: ${state.cwd}`, 'info');
228
- return true;
229
- }
230
- try {
231
- store.setCwd?.(nextPath, { message: `Project set: ${projectNameFromPath(nextPath)}` });
232
- } catch (e) {
233
- store.pushNotice(`project switch failed: ${e?.message || e}`, 'error');
234
- }
235
- return true;
236
- }
237
232
  case 'project': {
238
233
  const target = arg.trim();
239
234
  if (target) {
@@ -243,9 +238,6 @@ export function createSlashDispatch({
243
238
  openProjectPicker();
244
239
  return true;
245
240
  }
246
- case 'tools':
247
- openToolsPicker(arg.trim());
248
- return true;
249
241
  case 'mcp':
250
242
  openMcpPicker();
251
243
  return true;
@@ -270,23 +262,6 @@ export function createSlashDispatch({
270
262
  // palette, and a typed command answers instead of opening a picker.
271
263
  store.pushNotice('Schedules and webhooks are managed in the Mixdog desktop app', 'info');
272
264
  return true;
273
- case 'auth':
274
- store.pushNotice('/auth moved to /providers', 'info');
275
- void openProviderSetupPicker();
276
- return true;
277
- case 'auth-forget': {
278
- const providerId = arg.trim();
279
- if (!providerId) {
280
- store.pushNotice('usage: /auth-forget <provider>', 'warn');
281
- return true;
282
- }
283
- try {
284
- store.forgetProviderAuth(providerId);
285
- } catch (e) {
286
- store.pushNotice(`auth-forget failed: ${e?.message || e}`, 'error');
287
- }
288
- return true;
289
- }
290
265
  case 'memory': {
291
266
  if (!arg.trim()) {
292
267
  openMemoryCorePicker({ returnTo: null });
@@ -296,16 +271,6 @@ export function createSlashDispatch({
296
271
  .catch((e) => store.pushNotice(`memory failed: ${e?.message || e}`, 'error'));
297
272
  return true;
298
273
  }
299
- case 'recall': {
300
- const query = arg.trim();
301
- if (!query) {
302
- store.pushNotice('usage: /recall <query>', 'warn');
303
- return true;
304
- }
305
- void store.recall?.(query)
306
- .catch((e) => store.pushNotice(`recall failed: ${e?.message || e}`, 'error'));
307
- return true;
308
- }
309
274
  case 'autoclear': {
310
275
  const value = arg.trim().toLowerCase();
311
276
  if (!value) {
@@ -345,7 +310,7 @@ export function createSlashDispatch({
345
310
  return;
346
311
  }
347
312
  if (r.error) {
348
- store.pushNotice('Compact failed.', 'error');
313
+ store.pushNotice(compactFailureNotice(r.error), 'error');
349
314
  return;
350
315
  }
351
316
  if (r.changed === false && r.reason) {
@@ -358,7 +323,7 @@ export function createSlashDispatch({
358
323
  }
359
324
  store.pushNotice('Compact done.', 'info');
360
325
  })
361
- .catch(() => store.pushNotice('Compact failed.', 'error'));
326
+ .catch((error) => store.pushNotice(compactFailureNotice(error), 'error'));
362
327
  return true;
363
328
  case 'resume':
364
329
  if (state.busy) {
@@ -140,8 +140,10 @@ export function createThemeEffortPickers({
140
140
  items: pickerItems,
141
141
  onSelect: (value) => {
142
142
  setPicker(null);
143
+ // Mid-turn picks are allowed; the running turn keeps its own effort.
144
+ const pendingTurn = state.busy ? ' (applies from the next turn)' : '';
143
145
  void store.setEffort(value)
144
- .then(result => store.pushNotice(result ? `Effort set to ${result}` : 'Effort switch is already running.', result ? 'info' : 'warn'))
146
+ .then(result => store.pushNotice(result ? `Effort set to ${result}${pendingTurn}` : 'Effort switch is already running.', result ? 'info' : 'warn'))
145
147
  .catch((e) => store.pushNotice(`Couldn’t switch effort: ${e?.message || e}`, 'error'));
146
148
  },
147
149
  onCancel: () => {
@@ -405,7 +405,7 @@ function localBootStatusLine(args = {}) {
405
405
  return l2 ? `${l1}\n${l2}` : l1;
406
406
  }
407
407
 
408
- export function normalizeStatusLine(text) {
408
+ function normalizeStatusLine(text) {
409
409
  return normalizeStatuslineAnsi(text, statusColors(), { reset: RESET });
410
410
  }
411
411
 
@@ -9,7 +9,7 @@ import { theme, RESULT_GUTTER, RESULT_GUTTER_CONT } from '../../theme.mjs';
9
9
  import { formatExpandedResult, wrapExpandedResultLines } from '../tool-output-format.mjs';
10
10
  import { deltaTextParts, fitResultLine } from './text-format.mjs';
11
11
 
12
- export function renderDeltaText(text) {
12
+ function renderDeltaText(text) {
13
13
  return deltaTextParts(text).map((part, index) => (
14
14
  part.color ? <Text key={index} color={part.color}>{part.text}</Text> : part.text
15
15
  ));