mixdog 0.9.85 → 0.9.87

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 (102) hide show
  1. package/README.md +7 -1
  2. package/package.json +4 -3
  3. package/src/defaults/agents.json +12 -0
  4. package/src/defaults/skills/setup/SKILL.md +44 -12
  5. package/src/help.mjs +40 -9
  6. package/src/lib/keychain-cjs.cjs +11 -1
  7. package/src/rules/agent/43-title-agent.md +22 -0
  8. package/src/rules/shared/01-tool.md +3 -3
  9. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +4 -2
  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/oauth-usage.mjs +125 -16
  13. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -2
  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/tool-helpers.mjs +13 -0
  18. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +135 -9
  19. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +31 -12
  20. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +4 -0
  21. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +14 -0
  22. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +170 -0
  23. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +45 -6
  24. package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
  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 +9 -2
  31. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +24 -0
  32. package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +8 -0
  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/patch-tool-defs.mjs +4 -3
  37. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +152 -18
  38. package/src/runtime/agent/orchestrator/tools/token-binary-fetcher.mjs +201 -0
  39. package/src/runtime/agent/orchestrator/tools/token-manifest.json +26 -0
  40. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +3 -3
  41. package/src/runtime/media/adapters/gemini-video.mjs +1 -1
  42. package/src/runtime/media/renditions.mjs +1 -1
  43. package/src/runtime/media/store.mjs +1 -1
  44. package/src/runtime/memory/lib/memory-action-handlers.mjs +4 -1
  45. package/src/runtime/memory/tool-defs.mjs +19 -3
  46. package/src/runtime/shared/atomic-file.mjs +25 -14
  47. package/src/runtime/shared/automation-attachments.mjs +3 -3
  48. package/src/runtime/shared/child-guardian.mjs +15 -5
  49. package/src/runtime/shared/child-spawn-gate.mjs +6 -12
  50. package/src/runtime/shared/resource-admission.mjs +7 -3
  51. package/src/runtime/shared/turn-snapshot.mjs +0 -3
  52. package/src/session-runtime/lifecycle-api.mjs +49 -9
  53. package/src/session-runtime/prewarm.mjs +35 -0
  54. package/src/session-runtime/provider-auth-api.mjs +8 -0
  55. package/src/session-runtime/provider-usage.mjs +30 -5
  56. package/src/session-runtime/remote-control.mjs +8 -47
  57. package/src/session-runtime/remote-transcript.mjs +3 -7
  58. package/src/session-runtime/route-preparation.mjs +24 -0
  59. package/src/session-runtime/runtime-core.mjs +286 -548
  60. package/src/session-runtime/session-lifecycle.mjs +378 -0
  61. package/src/session-runtime/session-turn-api.mjs +9 -2
  62. package/src/session-runtime/workflow-agents-api.mjs +3 -0
  63. package/src/standalone/agent-shard/shard-child.mjs +300 -0
  64. package/src/standalone/agent-shard/shard-pool.mjs +443 -0
  65. package/src/standalone/agent-tool/job-views.mjs +329 -0
  66. package/src/standalone/agent-tool/spawn-flow.mjs +629 -0
  67. package/src/standalone/agent-tool/tag-registry.mjs +340 -0
  68. package/src/standalone/agent-tool.mjs +112 -941
  69. package/src/standalone/channel-daemon-transport.mjs +126 -185
  70. package/src/standalone/channel-daemon.mjs +9 -10
  71. package/src/standalone/usage-dashboard.mjs +23 -1
  72. package/src/tui/App.jsx +362 -2569
  73. package/src/tui/app/app-view.jsx +504 -0
  74. package/src/tui/app/channel-pickers.mjs +3 -8
  75. package/src/tui/app/create-app-pickers.mjs +313 -0
  76. package/src/tui/app/prompt-submit.mjs +501 -0
  77. package/src/tui/app/route-pickers.mjs +117 -152
  78. package/src/tui/app/settings-picker.mjs +90 -89
  79. package/src/tui/app/shell-layout.mjs +563 -0
  80. package/src/tui/app/slash-commands.mjs +7 -7
  81. package/src/tui/app/slash-dispatch.mjs +0 -45
  82. package/src/tui/app/usage-context-panels.mjs +288 -0
  83. package/src/tui/app/use-copy-selection.mjs +56 -0
  84. package/src/tui/app/use-global-key-input.mjs +151 -0
  85. package/src/tui/app/use-pasted-buffers.mjs +115 -0
  86. package/src/tui/app/use-prompt-draft-flow.mjs +167 -0
  87. package/src/tui/app/use-prompt-hint.mjs +58 -0
  88. package/src/tui/app/use-prompt-queue-history.mjs +100 -0
  89. package/src/tui/app/use-terminal-chrome.mjs +72 -0
  90. package/src/tui/app/use-transcript-activity.mjs +131 -0
  91. package/src/tui/app/use-welcome-prompt-hint.mjs +96 -0
  92. package/src/tui/components/StatusLine.jsx +1 -1
  93. package/src/tui/components/tool-execution/ResultBody.jsx +1 -1
  94. package/src/tui/dist/index.mjs +9857 -9159
  95. package/src/tui/engine/session-api-ext.mjs +47 -9
  96. package/src/tui/engine.mjs +7 -1
  97. package/src/tui/figures.mjs +0 -1
  98. package/src/ui/statusline-format.mjs +0 -1
  99. package/src/hooks/lib/permission-evaluator.cjs +0 -24
  100. package/src/lib/config-cjs.cjs +0 -61
  101. package/src/runtime/shared/launcher-control.mjs +0 -258
  102. package/src/runtime/shared/workspace-router.mjs +0 -259
@@ -27,7 +27,6 @@ export function createRoutePickers({
27
27
  setContextPanel,
28
28
  closeUsagePanel,
29
29
  clean,
30
- copyToClipboard,
31
30
  routeLabel,
32
31
  agentModelParts,
33
32
  agentModelProfile,
@@ -158,28 +157,134 @@ export function createRoutePickers({
158
157
  closeUsagePanel();
159
158
  setPicker({
160
159
  title: 'Workflow',
161
- description: 'Select active workflow.',
162
- help: returnTo ? '↑/↓ Select · Enter Choose · Esc Settings' : '↑/↓ Select · Enter Choose · Esc Back',
160
+ description: 'Active workflow and its agents.',
161
+ help: returnTo ? '↑/↓ Select · Enter Open · Esc Settings' : '↑/↓ Select · Enter Open · Esc Back',
163
162
  labelWidth: 18,
163
+ initialIndex: Math.max(0, items.findIndex((item) => item.value === (options.initialWorkflowId || ''))),
164
164
  items,
165
165
  onSelect: (_value, item) => {
166
166
  const workflow = item?._workflow;
167
167
  if (!workflow) return;
168
+ // Depth mirrors the system model: a workflow pack owns the agent set
169
+ // it delegates to, so Enter opens the pack (activate + its agents)
170
+ // instead of switching immediately.
171
+ openWorkflowDetailPicker(workflow.id, { returnTo });
172
+ },
173
+ onCancel: () => {
168
174
  setPicker(null);
169
- void store.setWorkflow?.(workflow.id)
170
- .then((result) => {
175
+ if (returnTo) returnTo();
176
+ },
177
+ });
178
+ };
179
+
180
+ // Workflow detail: activation row plus the agents this pack delegates to
181
+ // (route editing inline). Packs without an `agents:` key delegate to all
182
+ // fixed agents; a configured-but-empty list means Lead works alone.
183
+ const openWorkflowDetailPicker = (workflowId, options = {}) => {
184
+ const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
185
+ let workflows = [];
186
+ try {
187
+ workflows = store.listWorkflows?.() || [];
188
+ } catch (e) {
189
+ store.pushNotice(`could not list workflows: ${e?.message || e}`, 'error');
190
+ return;
191
+ }
192
+ const workflow = workflows.find((item) => item.id === workflowId);
193
+ if (!workflow) {
194
+ store.pushNotice(`workflow "${workflowId}" not found`, 'warn');
195
+ openWorkflowPicker({ returnTo });
196
+ return;
197
+ }
198
+ let agents = [];
199
+ try {
200
+ agents = store.listAgents?.() || [];
201
+ } catch (e) {
202
+ store.pushNotice(`could not list agents: ${e?.message || e}`, 'error');
203
+ agents = [];
204
+ }
205
+ const packAgents = workflow.agentsConfigured === true
206
+ ? (workflow.agents || [])
207
+ .map((id) => agents.find((agent) => agent.id === id) || { id, label: id, route: null, description: '' })
208
+ : agents.filter((agent) => agent.custom !== true);
209
+ const routeOverrides = options.routeOverrides && typeof options.routeOverrides === 'object' ? options.routeOverrides : {};
210
+ const items = [
211
+ {
212
+ value: '__activate',
213
+ label: workflow.active ? 'Active' : 'Activate',
214
+ marker: workflow.active ? '✓' : '',
215
+ markerColor: theme.success,
216
+ description: workflow.active
217
+ ? 'This workflow is active.'
218
+ : 'Make this the active workflow.',
219
+ },
220
+ ...(packAgents.length
221
+ ? packAgents.map((agent) => ({
222
+ value: agent.id,
223
+ label: agent.label,
224
+ metaParts: agentModelParts(routeOverrides[agent.id] || agent.route),
225
+ description: agent.description || agent.definition?.description || '',
226
+ _agent: agent,
227
+ }))
228
+ : [{
229
+ value: '__lead-only',
230
+ label: 'Lead only',
231
+ description: 'This workflow delegates to no agents.',
232
+ }]),
233
+ ];
234
+ setPicker({
235
+ title: workflow.name,
236
+ description: workflow.description || 'Workflow activation and agents.',
237
+ help: '↑/↓ Select · Enter Choose · Esc Back',
238
+ indexMode: 'always',
239
+ labelWidth: 18,
240
+ metaWidth: 33,
241
+ initialIndex: Math.max(0, items.findIndex((item) => item.value === (options.initialValue || '__activate'))),
242
+ items,
243
+ onSelect: (_value, item) => {
244
+ if (item?.value === '__activate') {
245
+ if (workflow.active) {
246
+ store.pushNotice(`${workflow.name} is already the active workflow`, 'info');
247
+ return;
248
+ }
249
+ setPicker(null);
250
+ void store.setWorkflow?.(workflow.id)
251
+ .then((result) => {
252
+ if (!result) {
253
+ store.pushNotice('Workflow switch is already running.', 'warn');
254
+ return;
255
+ }
256
+ store.pushNotice(workflowSwitchNotice(result), 'info');
257
+ openWorkflowPicker({ returnTo, initialWorkflowId: workflow.id });
258
+ })
259
+ .catch((e) => store.pushNotice(`Couldn’t switch workflow: ${e?.message || e}`, 'error'));
260
+ return;
261
+ }
262
+ const agent = item?._agent;
263
+ if (!agent) return;
264
+ void openModelPicker({
265
+ title: `${agent.label} Model`,
266
+ providerDescription: 'Choose a provider for this agent.',
267
+ currentRoute: agent.route || null,
268
+ returnTo: () => openWorkflowDetailPicker(workflow.id, { returnTo, initialValue: agent.id }),
269
+ onImmediateSelect: (routeInput) => {
270
+ openWorkflowDetailPicker(workflow.id, {
271
+ returnTo,
272
+ routeOverrides: { [agent.id]: routeInput },
273
+ initialValue: agent.id,
274
+ });
275
+ },
276
+ onSelectRoute: async (routeInput) => {
277
+ const result = await store.setAgentRoute?.(agent.id, routeInput);
171
278
  if (!result) {
172
- store.pushNotice('Workflow switch is already running.', 'warn');
279
+ store.pushNotice('Agent model save is already running.', 'warn');
173
280
  return;
174
281
  }
175
- store.pushNotice(workflowSwitchNotice(result), 'info');
176
- if (returnTo) returnTo();
177
- })
178
- .catch((e) => store.pushNotice(`Couldn’t switch workflow: ${e?.message || e}`, 'error'));
282
+ store.pushNotice(`${agent.label} model set to ${agentModelProfile(result)}`, 'info');
283
+ },
284
+ });
179
285
  },
180
286
  onCancel: () => {
181
- setPicker(null);
182
- if (returnTo) returnTo();
287
+ openWorkflowPicker({ returnTo, initialWorkflowId: workflow.id });
183
288
  },
184
289
  });
185
290
  };
@@ -274,150 +379,10 @@ export function createRoutePickers({
274
379
  });
275
380
  };
276
381
 
277
- const openBridgePicker = () => {
278
- setProviderPrompt(null);
279
- setChannelPrompt(null);
280
- setHookPrompt(null);
281
- setSettingsPrompt(null);
282
- setPicker({
283
- title: 'Agent Tasks',
284
- description: 'Inspect or clean up background agent tasks.',
285
- items: [
286
- {
287
- value: 'list',
288
- label: 'List agents/tasks',
289
- description: 'show active agents and async tasks',
290
- _action: 'control',
291
- _args: { type: 'list' },
292
- },
293
- {
294
- value: 'cleanup',
295
- label: 'Cleanup finished tasks',
296
- description: 'remove completed agent task records',
297
- _action: 'control',
298
- _args: { type: 'cleanup' },
299
- },
300
- ],
301
- onSelect: (_value, item) => {
302
- setPicker(null);
303
- if (item._action === 'control') {
304
- void store.agentControl?.(item._args)
305
- .catch((e) => store.pushNotice(`agent failed: ${e?.message || e}`, 'error'));
306
- }
307
- },
308
- onCancel: () => {
309
- setPicker(null);
310
- },
311
- });
312
- };
313
-
314
- const openToolsPicker = (query = '') => {
315
- let status;
316
- try {
317
- status = store.toolsStatus?.(query) || { tools: [] };
318
- } catch (e) {
319
- store.pushNotice(`tools status failed: ${e?.message || e}`, 'error');
320
- return;
321
- }
322
- const tools = status.tools || [];
323
- const items = [
324
- {
325
- value: 'summary',
326
- label: 'Tool surface',
327
- description: `${status.activeCount || 0}/${status.count || 0} active · mode ${status.mode || state.toolMode}`,
328
- _action: 'summary',
329
- },
330
- ...(tools.length ? tools.map((tool) => ({
331
- value: tool.name,
332
- label: tool.name,
333
- marker: tool.active ? '●' : '○',
334
- markerColor: tool.active ? theme.success : theme.inactive,
335
- description: `${tool.kind || 'tool'} · usage ${tool.usage || 0}${tool.description ? ` · ${tool.description}` : ''}`,
336
- _action: tool.active ? 'tool' : 'enable',
337
- _tool: tool,
338
- })) : [{
339
- value: 'empty',
340
- label: 'No tools',
341
- description: query ? `no matches for "${query}"` : 'tool catalog is empty',
342
- _action: 'noop',
343
- }]),
344
- ];
345
- setProviderPrompt(null);
346
- setChannelPrompt(null);
347
- setHookPrompt(null);
348
- setSettingsPrompt(null);
349
- setPicker({
350
- title: query ? `Tools · ${query}` : 'Tools',
351
- description: 'Browse active and deferred tools.',
352
- items,
353
- onSelect: (_value, item) => {
354
- setPicker(null);
355
- if (item._action === 'summary') {
356
- store.pushNotice([
357
- `mode: ${status.mode || state.toolMode}`,
358
- `active: ${status.activeCount || 0}/${status.count || 0}`,
359
- `active tools: ${(status.activeTools || []).join(', ') || '(none)'}`,
360
- ].join('\n'), 'info');
361
- return;
362
- }
363
- if (item._action === 'enable') {
364
- store.selectTools?.([item._tool.name]);
365
- void openToolsPicker(query);
366
- return;
367
- }
368
- if (item._action === 'tool') {
369
- const tool = item._tool;
370
- setPicker({
371
- title: `Tool · ${tool.name}`,
372
- description: 'Tool details and quick actions.',
373
- items: [
374
- {
375
- value: 'info',
376
- label: 'Tool info',
377
- description: `${tool.kind || 'tool'} · ${tool.active ? 'active' : 'deferred'}`,
378
- _action: 'info',
379
- },
380
- {
381
- value: 'copy-name',
382
- label: 'Copy name',
383
- description: tool.name,
384
- _action: 'copy-name',
385
- },
386
- ],
387
- onSelect: (_detailValue, detail) => {
388
- setPicker(null);
389
- if (detail._action === 'info') {
390
- store.pushNotice([
391
- tool.name,
392
- `kind: ${tool.kind || 'tool'}`,
393
- `state: ${tool.active ? 'active' : 'deferred'}`,
394
- `usage: ${tool.usage || 0}`,
395
- tool.description || '',
396
- ].filter(Boolean).join('\n'), 'info');
397
- return;
398
- }
399
- if (detail._action === 'copy-name') {
400
- void copyToClipboard(tool.name)
401
- .then(() => store.pushNotice(`copied tool name: ${tool.name}`, 'plain'))
402
- .catch((e) => store.pushNotice(`copy failed: ${e?.message || e}`, 'error'));
403
- }
404
- },
405
- onCancel: () => openToolsPicker(query),
406
- });
407
- }
408
- },
409
- onCancel: () => {
410
- setPicker(null);
411
- },
412
- });
413
- };
414
-
415
382
  return {
416
383
  openSearchPicker,
417
384
  openAgentsPicker,
418
385
  openWorkflowPicker,
419
386
  openOutputStylePicker,
420
- openBridgePicker,
421
- openToolsPicker,
422
387
  };
423
388
  }
@@ -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({