praxis-agent 0.9.0 → 0.11.0

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 (57) hide show
  1. package/README.md +18 -4
  2. package/dist/application/session-service.d.ts +5 -2
  3. package/dist/application/session-service.js +23 -0
  4. package/dist/application/subagent-service.d.ts +4 -2
  5. package/dist/application/subagent-service.js +4 -0
  6. package/dist/cli/interactive.d.ts +4 -3
  7. package/dist/cli/interactive.js +760 -195
  8. package/dist/cli/tui/claude-command-inventory.d.ts +368 -0
  9. package/dist/cli/tui/claude-command-inventory.js +220 -0
  10. package/dist/cli/tui/claude-style.d.ts +12 -14
  11. package/dist/cli/tui/claude-style.js +45 -67
  12. package/dist/cli/tui/config-dashboard.d.ts +4 -8
  13. package/dist/cli/tui/config-dashboard.js +4 -18
  14. package/dist/cli/tui/config-settings.js +1 -1
  15. package/dist/cli/tui/conversation-export.d.ts +1 -1
  16. package/dist/cli/tui/conversation-export.js +2 -11
  17. package/dist/cli/tui/mcp-elicitation.d.ts +75 -0
  18. package/dist/cli/tui/mcp-elicitation.js +497 -0
  19. package/dist/cli/tui/open-url.d.ts +2 -0
  20. package/dist/cli/tui/open-url.js +13 -0
  21. package/dist/cli/tui/permission-settings.d.ts +6 -0
  22. package/dist/cli/tui/permission-settings.js +84 -0
  23. package/dist/cli/tui/runtime-interactions.d.ts +2 -0
  24. package/dist/cli/tui/runtime-interactions.js +44 -17
  25. package/dist/cli/tui/slash-commands.js +3 -28
  26. package/dist/cli/tui/tool-permission.d.ts +36 -0
  27. package/dist/cli/tui/tool-permission.js +386 -0
  28. package/dist/cli.d.ts +2 -2
  29. package/dist/cli.js +28 -13
  30. package/dist/core/runtime.d.ts +50 -2
  31. package/dist/core/runtime.js +30 -3
  32. package/dist/extensions/claude-extension-tools.d.ts +3 -2
  33. package/dist/extensions/claude-extension-tools.js +15 -5
  34. package/dist/extensions/claude-extensions.d.ts +1 -0
  35. package/dist/extensions/claude-extensions.js +10 -0
  36. package/dist/hooks/claude-hook-tools.js +1 -1
  37. package/dist/mcp/claude-mcp-tools.js +12 -6
  38. package/dist/permissions/bash-ast.d.ts +39 -0
  39. package/dist/permissions/bash-ast.js +1046 -0
  40. package/dist/permissions/bash-normalization.d.ts +5 -0
  41. package/dist/permissions/bash-normalization.js +241 -0
  42. package/dist/permissions/bash-path-safety.d.ts +28 -0
  43. package/dist/permissions/bash-path-safety.js +575 -0
  44. package/dist/permissions/claude-permission-resolver.d.ts +7 -0
  45. package/dist/permissions/claude-permission-resolver.js +351 -40
  46. package/dist/permissions/claude-shell-permission.d.ts +4 -0
  47. package/dist/permissions/claude-shell-permission.js +108 -0
  48. package/dist/permissions/permission-updates.d.ts +16 -0
  49. package/dist/permissions/permission-updates.js +320 -0
  50. package/dist/permissions/sed-safety.d.ts +9 -0
  51. package/dist/permissions/sed-safety.js +172 -0
  52. package/dist/permissions/shell-rule-matching.d.ts +14 -0
  53. package/dist/permissions/shell-rule-matching.js +76 -0
  54. package/dist/tools/claude-interactive-tools.d.ts +10 -1
  55. package/dist/tools/claude-interactive-tools.js +10 -4
  56. package/dist/tools/local-tools.js +22 -2
  57. package/package.json +3 -1
@@ -1,19 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { randomUUID } from 'node:crypto';
3
- import { homedir } from 'node:os';
4
- import { resolve } from 'node:path';
3
+ import { existsSync } from 'node:fs';
4
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
5
+ import { homedir, tmpdir } from 'node:os';
6
+ import { join, resolve } from 'node:path';
5
7
  import { useEffect, useMemo, useRef, useState } from 'react';
6
8
  import { Box, Text, render, useApp, useInput } from 'ink';
9
+ import { permissionRuleValueToString } from '../permissions/permission-updates.js';
7
10
  import { AgentRunCancelledError } from '../core/runtime.js';
8
- import { claudePermissionActionKey, } from '../permissions/claude-permission-resolver.js';
11
+ import { claudePermissionActionKey, claudePermissionRuleMatches, } from '../permissions/claude-permission-resolver.js';
9
12
  import { redactSensitiveText, sensitiveEnvironmentValues, } from '../platform/sensitive-data.js';
10
- import { CommandPalette, BtwPanel, Composer, DiffDashboard, DialogFrame, ExternalEditorWait, HelpMenu, HookDashboard, ListDashboard, MemoryDashboard, MentionPicker, PermissionDashboard, SelectionMenu, SessionPicker, StatusDashboard, ThemePicker, CustomThemeEditor, Transcript, WelcomePanel, useTerminalWidth, } from './tui/claude-style.js';
13
+ import { CommandPalette, BtwPanel, Composer, DiffDashboard, DialogFrame, ExternalEditorWait, HelpMenu, HookDashboard, ListDashboard, MemoryDashboard, MentionPicker, ModelMenu, PermissionDashboard, SelectionMenu, SessionPicker, ThemePicker, CustomThemeEditor, Transcript, WelcomePanel, useTerminalWidth, } from './tui/claude-style.js';
11
14
  import { loadTuiMemoryFiles, openTuiMemoryFolder, } from './tui/memory-files.js';
12
15
  import { loadGitDiff, visiblePatchLines, } from './tui/git-diff.js';
13
16
  import { addTuiPermissionRule, loadTuiPermissionRules, removeTuiPermissionRule, } from './tui/permission-settings.js';
14
17
  import { createRecentlyDeniedStore, } from './tui/recently-denied.js';
15
18
  import { filterTuiSlashCommands, mergeTuiSlashCommands, slashCommandQuery, } from './tui/slash-commands.js';
16
- import { createComposerEditor, deleteComposerToEnd, deleteComposerToStart, deleteComposerWordBackward, insertComposerText, moveComposerCursorByWord, } from './tui/composer-editor.js';
19
+ import { createComposerEditor, deleteComposerBackward, deleteComposerForward, deleteComposerToEnd, deleteComposerToStart, deleteComposerWordBackward, insertComposerText, moveComposerCursor, moveComposerCursorByWord, } from './tui/composer-editor.js';
17
20
  import { applyMentionReference, fileReferenceAtCursor, filterTuiMentionEntries, loadTuiFileEntries, } from './tui/file-picker.js';
18
21
  import { editTuiPrompt, openTuiEditorFile, } from './tui/external-editor.js';
19
22
  import { suspendTuiProcess } from './tui/terminal-suspend.js';
@@ -26,10 +29,13 @@ import { loadTuiThemeSettings, saveTuiThemeSettings, themeSettingsWithCustomThem
26
29
  import { DEFAULT_TUI_THEME_SETTINGS, TUI_THEMES, TuiThemeProvider, tuiPalette, } from './tui/theme.js';
27
30
  import { CUSTOM_THEME_TOKENS, createTuiCustomTheme, deleteTuiCustomTheme, loadTuiCustomThemes, updateTuiCustomTheme, } from './tui/custom-themes.js';
28
31
  import { setupTuiTerminal, terminalSetupTuiSlashCommand, } from './tui/terminal-setup.js';
32
+ import { commitElicitationText, createTuiElicitationForm, elicitationFormIsValid, elicitationTextValue, expandElicitationOptions, focusedElicitationField, McpElicitationForm, McpElicitationUrl, moveElicitationFocus, moveElicitationOption, selectElicitationOption, toggleElicitationBoolean, typeaheadElicitationOption, unsetElicitationField, validateTuiElicitationForm, } from './tui/mcp-elicitation.js';
33
+ import { openTuiUrl } from './tui/open-url.js';
34
+ import { projectTuiToolPermission, ToolPermissionDialog, } from './tui/tool-permission.js';
29
35
  import { ConfigDashboard, projectConfigRows } from './tui/config-dashboard.js';
30
36
  import { loadConfigSettings, saveConfigSetting, configSettingDefinition, resolveConfigSettingsLocation, } from './tui/config-settings.js';
31
37
  import { projectRuntimeSettings, loadRuntimeSettings, } from './tui/runtime-settings.js';
32
- import { autoUpdateTarget, copyCandidates, externalEditorInitialContent, formatTurnDuration, questionTimeoutMilliseconds, sessionRecap, shouldShowCopyPicker, spinnerTip, } from './tui/runtime-interactions.js';
38
+ import { autoUpdateTarget, copyCandidates, externalEditorInitialContent, formatTurnDuration, questionTimeoutMilliseconds, sessionRecap, spinnerTip, shouldShowCopyPicker, } from './tui/runtime-interactions.js';
33
39
  import { notifyTerminal, } from './tui/terminal-notifications.js';
34
40
  import { McpPanel } from './tui/mcp-panel.js';
35
41
  import { McpPanelController, mcpRuntimeFromSession, } from './tui/mcp-panel-controller.js';
@@ -38,6 +44,17 @@ import { TaskPanel, initialTuiTaskPanelState, projectTuiTasks, reconcileTuiTaskP
38
44
  import { ClaudeMcpManagement } from '../mcp/claude-mcp-management.js';
39
45
  const EMPTY_SLASH_COMMANDS = [];
40
46
  const EMPTY_AGENTS = [];
47
+ const estimateFileTokens = async (path) => {
48
+ try {
49
+ const content = await readFile(path, 'utf8');
50
+ if (!content.trim())
51
+ return 0;
52
+ return Math.max(1, Math.round(content.length / 4));
53
+ }
54
+ catch {
55
+ return 0;
56
+ }
57
+ };
41
58
  const EFFORT_OPTIONS = ['low', 'medium', 'high', 'xhigh', 'max'];
42
59
  const PERMISSION_OPTIONS = [
43
60
  {
@@ -215,7 +232,7 @@ function ordinal(value) {
215
232
  return `${value}th`;
216
233
  }
217
234
  }
218
- export function InteractiveApp({ factory, initialSessions, initialPrompt, initialHistory = [], signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader = false, allowNewSession = true, resume, display = { version: 'dev', cwd: process.cwd() }, terminalWidth, slashCommands = EMPTY_SLASH_COMMANDS, agents = EMPTY_AGENTS, allowDangerouslySkipPermissions = false, additionalDirectories = [], diffLoader, fileLoader, externalEditor = editTuiPrompt, keybindingsConfigRoot, keybindingsFile = ensureTuiKeybindingsFile, keybindingsLoader = loadTuiKeybindings, keybindingsEditor = openTuiEditorFile, memoryFilesLoader = (configRoot, cwd) => loadTuiMemoryFiles({ configRoot, cwd }), memoryEditor = openTuiEditorFile, memoryFolderOpener = openTuiMemoryFolder, suspendProcess = suspendTuiProcess, clipboardReader = readTuiClipboard, clipboardWriter = writeTuiClipboard, sideQuestionClipboardWriter = writeTuiOsc52Clipboard, exportWriter = writeConversationExport, permissionRuleStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver = resolveTuiWorkspaceDirectory, workspaceDirectoryCompleter = completeTuiWorkspaceDirectory, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, }) {
235
+ export function InteractiveApp({ factory, initialSessions, initialPrompt, initialHistory = [], signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader = false, allowNewSession = true, resume, display = { version: 'dev', cwd: process.cwd() }, terminalWidth, slashCommands = EMPTY_SLASH_COMMANDS, agents = EMPTY_AGENTS, allowDangerouslySkipPermissions = false, additionalDirectories = [], diffLoader, fileLoader, externalEditor = editTuiPrompt, keybindingsConfigRoot, keybindingsFile = ensureTuiKeybindingsFile, keybindingsLoader = loadTuiKeybindings, keybindingsEditor = openTuiEditorFile, memoryFilesLoader = (configRoot, cwd) => loadTuiMemoryFiles({ configRoot, cwd }), memoryEditor = openTuiEditorFile, memoryFolderOpener = openTuiMemoryFolder, suspendProcess = suspendTuiProcess, clipboardReader = readTuiClipboard, clipboardWriter = writeTuiClipboard, sideQuestionClipboardWriter = writeTuiOsc52Clipboard, exportWriter = writeConversationExport, permissionRuleStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver = resolveTuiWorkspaceDirectory, workspaceDirectoryCompleter = completeTuiWorkspaceDirectory, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, elicitationUrlOpener = openTuiUrl, }) {
219
236
  const { exit, suspendTerminal, waitUntilRenderFlush } = useApp();
220
237
  const width = useTerminalWidth(terminalWidth);
221
238
  const keybindingsRoot = useMemo(() => resolve(keybindingsConfigRoot ??
@@ -271,6 +288,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
271
288
  const [sessionId, setSessionId] = useState(resume?.sessionId ?? null);
272
289
  const sessionIdRef = useRef(resume?.sessionId ?? null);
273
290
  sessionIdRef.current = sessionId;
291
+ const [sessionName, setSessionName] = useState(null);
274
292
  const [activeSessionSummary, setActiveSessionSummary] = useState(() => resume?.sessionId
275
293
  ? initialSessions.find((session) => session.sessionId === resume.sessionId)
276
294
  : undefined);
@@ -354,14 +372,26 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
354
372
  const runtimePreferencesRef = useRef(runtimePreferences);
355
373
  const [permission, setPermission] = useState(null);
356
374
  const permissionRef = useRef(null);
375
+ const [permissionSelection, setPermissionSelection] = useState(0);
376
+ const [permissionFeedbackMode, setPermissionFeedbackMode] = useState(false);
377
+ const [permissionRuleEditor, setPermissionRuleEditor] = useState(null);
378
+ const immediatePermissionRulesRef = useRef([]);
379
+ const toolPermissionModel = useMemo(() => permission?.kind === 'tool'
380
+ ? projectTuiToolPermission(permission.call, runtimeCwd, sensitiveValues, permission.decision)
381
+ : null, [permission, runtimeCwd, sensitiveValues]);
357
382
  const [elicitation, setElicitation] = useState(null);
358
383
  const elicitationRef = useRef(null);
384
+ const [elicitationForm, setElicitationForm] = useState(null);
385
+ const [elicitationUrlWaiting, setElicitationUrlWaiting] = useState(false);
386
+ const elicitationUrlWaitingRef = useRef(null);
359
387
  const [question, setQuestion] = useState(null);
360
388
  const questionRef = useRef(null);
361
389
  const questionTimeoutRef = useRef(null);
362
390
  const recapShownRef = useRef(false);
363
391
  const [planApproval, setPlanApproval] = useState(null);
364
392
  const planApprovalRef = useRef(null);
393
+ const [planApprovalSelection, setPlanApprovalSelection] = useState(0);
394
+ const [planApprovalFeedbackMode, setPlanApprovalFeedbackMode] = useState(false);
365
395
  const serviceRef = useRef(null);
366
396
  const serviceCreationRef = useRef(undefined);
367
397
  const onCleanupRef = useRef(onCleanup);
@@ -422,6 +452,21 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
422
452
  permissionMode: runtimePreferences.permissionMode,
423
453
  ...(contextWindowTokens === undefined ? {} : { contextWindowTokens }),
424
454
  };
455
+ const statusAuthSource = process.env.PRAXIS_API_KEY
456
+ ? 'PRAXIS_API_KEY'
457
+ : process.env.ANTHROPIC_API_KEY
458
+ ? 'ANTHROPIC_API_KEY'
459
+ : undefined;
460
+ const statusBaseUrl = process.env.PRAXIS_BASE_URL;
461
+ const statusProxy = process.env.HTTPS_PROXY ??
462
+ process.env.https_proxy ??
463
+ process.env.ALL_PROXY ??
464
+ process.env.HTTP_PROXY;
465
+ const statusSettingSources = (() => {
466
+ const projectSettings = existsSync(join(runtimeCwd, '.claude', 'settings.json')) ||
467
+ existsSync(join(runtimeCwd, 'settings.json'));
468
+ return projectSettings ? 'User settings, Project settings' : 'User settings';
469
+ })();
425
470
  const workspaceDirectories = [
426
471
  runtimeCwd,
427
472
  ...runtimePreferences.additionalDirectories.filter((path) => path !== runtimeCwd),
@@ -438,23 +483,46 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
438
483
  ]
439
484
  : []),
440
485
  ], [allowDangerouslySkipPermissions]);
441
- const modelOptions = useMemo(() => [
442
- {
443
- label: runtimeDisplay.model
444
- ? `Current: ${runtimeDisplay.model}`
445
- : 'Current: provider default',
446
- description: 'Keep the model selected for this interactive session.',
447
- selected: true,
448
- },
449
- {
450
- label: 'Use invocation default',
451
- description: 'Use the model supplied by the original CLI invocation.',
452
- },
453
- {
486
+ const modelOptions = useMemo(() => {
487
+ const current = runtimePreferences.model;
488
+ const options = [
489
+ {
490
+ label: 'Default (recommended)',
491
+ description: `Use the invocation default (currently ${runtimeDisplay.model ?? 'provider default'})`,
492
+ selected: current === undefined,
493
+ },
494
+ ];
495
+ if ((process.env.PRAXIS_PROVIDER ?? 'openai') === 'anthropic') {
496
+ options.push({
497
+ label: 'Opus',
498
+ model: process.env.ANTHROPIC_DEFAULT_OPUS_MODEL ?? 'opus',
499
+ description: 'Most capable for complex work',
500
+ }, {
501
+ label: 'Sonnet',
502
+ model: process.env.ANTHROPIC_DEFAULT_SONNET_MODEL ?? 'sonnet',
503
+ description: 'Best for everyday tasks',
504
+ }, {
505
+ label: 'Haiku',
506
+ model: process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL ?? 'haiku',
507
+ description: 'Fastest for quick answers',
508
+ });
509
+ }
510
+ else if (current) {
511
+ options.push({
512
+ label: current,
513
+ model: current,
514
+ description: 'Current provider model',
515
+ selected: true,
516
+ });
517
+ }
518
+ for (const option of options)
519
+ option.selected = option.model === current || (!option.model && !current);
520
+ options.push({
454
521
  label: 'Enter a model ID…',
455
522
  description: 'Use any model identifier supported by the configured provider.',
456
- },
457
- ], [runtimeDisplay.model]);
523
+ });
524
+ return options;
525
+ }, [runtimeDisplay.model, runtimePreferences.model]);
458
526
  useEffect(() => {
459
527
  setAvailableSlashCommands(slashCommands);
460
528
  }, [slashCommands]);
@@ -493,7 +561,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
493
561
  permissionRef.current?.resolve(false);
494
562
  elicitationRef.current?.resolve({ action: 'cancel' });
495
563
  questionRef.current?.resolve(null);
496
- planApprovalRef.current?.resolve(false);
564
+ planApprovalRef.current?.resolve({ behavior: 'deny' });
497
565
  exit();
498
566
  };
499
567
  if (signal.aborted)
@@ -507,7 +575,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
507
575
  permissionRef.current?.resolve(false);
508
576
  elicitationRef.current?.resolve({ action: 'cancel' });
509
577
  questionRef.current?.resolve(null);
510
- planApprovalRef.current?.resolve(false);
578
+ planApprovalRef.current?.resolve({ behavior: 'deny' });
511
579
  if (exitConfirmationTimerRef.current)
512
580
  clearTimeout(exitConfirmationTimerRef.current);
513
581
  if (btwCopiedTimerRef.current)
@@ -898,7 +966,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
898
966
  permissionRef.current?.resolve(false);
899
967
  elicitationRef.current?.resolve({ action: 'cancel' });
900
968
  questionRef.current?.resolve(null);
901
- planApprovalRef.current?.resolve(false);
969
+ planApprovalRef.current?.resolve({ behavior: 'deny' });
902
970
  onCancel?.();
903
971
  exit();
904
972
  return;
@@ -1047,6 +1115,16 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1047
1115
  kind: 'notice',
1048
1116
  text: `MCP elicitation completed · ${event.mcpServerName}`,
1049
1117
  });
1118
+ if (elicitationUrlWaitingRef.current?.request.serverName ===
1119
+ event.mcpServerName &&
1120
+ elicitationUrlWaitingRef.current.request.elicitationId ===
1121
+ event.elicitationId) {
1122
+ elicitationUrlWaitingRef.current = null;
1123
+ setElicitationUrlWaiting(false);
1124
+ setElicitation(null);
1125
+ setElicitationForm(null);
1126
+ clearComposerInput();
1127
+ }
1050
1128
  break;
1051
1129
  case 'tool-use-summary':
1052
1130
  append({ kind: 'notice', text: event.summary });
@@ -1076,11 +1154,16 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1076
1154
  }
1077
1155
  }
1078
1156
  };
1079
- const requestApproval = (call, kind) => new Promise((resolveApproval) => {
1157
+ const requestApproval = (call, kind, decision) => new Promise((resolveApproval) => {
1158
+ const projected = kind === 'tool'
1159
+ ? projectTuiToolPermission(call, runtimeCwdRef.current, sensitiveValues, decision)
1160
+ : null;
1161
+ const editableRule = projected?.options.find((option) => option.editableRule)?.editableRule;
1080
1162
  let settled = false;
1081
1163
  const pending = {
1082
1164
  kind,
1083
1165
  call,
1166
+ ...(decision ? { decision } : {}),
1084
1167
  resolve: (approved) => {
1085
1168
  if (settled)
1086
1169
  return;
@@ -1091,28 +1174,44 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1091
1174
  resolveApproval(approved);
1092
1175
  },
1093
1176
  };
1177
+ clearComposerInput();
1178
+ setPermissionSelection(0);
1179
+ setPermissionFeedbackMode(false);
1180
+ setPermissionRuleEditor(editableRule ? createComposerEditor(editableRule.initialValue) : null);
1094
1181
  permissionRef.current = pending;
1095
1182
  setPermission(pending);
1096
1183
  });
1097
- const approveTool = (call) => requestApproval(call, 'tool');
1184
+ const approveTool = (call, _originalCall, decision) => requestApproval(call, 'tool', decision);
1098
1185
  const requestElicitation = (request) => new Promise((resolveResult) => {
1099
1186
  let settled = false;
1100
1187
  const pending = {
1101
1188
  request,
1102
- resolve: (result) => {
1189
+ resolve: (result, options) => {
1103
1190
  if (settled)
1104
1191
  return;
1105
1192
  settled = true;
1106
1193
  if (elicitationRef.current === pending)
1107
1194
  elicitationRef.current = null;
1108
- setElicitation((current) => (current === pending ? null : current));
1195
+ if (!options?.keepUrlDialog) {
1196
+ setElicitation((current) => (current === pending ? null : current));
1197
+ setElicitationForm(null);
1198
+ clearComposerInput();
1199
+ }
1109
1200
  resolveResult(result);
1110
1201
  },
1111
1202
  };
1203
+ const form = createTuiElicitationForm(request.requestedSchema);
1204
+ setElicitationForm(form);
1205
+ setElicitationUrlWaiting(false);
1206
+ elicitationUrlWaitingRef.current = null;
1207
+ updateComposerInput(elicitationTextValue(form));
1112
1208
  elicitationRef.current = pending;
1113
1209
  setElicitation(pending);
1114
1210
  });
1115
- const approveRecovery = (call) => resume?.retryInterruptedTools ? true : requestApproval(call, 'recovery');
1211
+ const approveRecovery = (call) => resume?.retryInterruptedTools
1212
+ ? true
1213
+ : requestApproval(call, 'recovery').then((approval) => approval === true ||
1214
+ (typeof approval === 'object' && approval.behavior === 'allow'));
1116
1215
  const askUser = (questions, signal) => new Promise((resolveResult) => {
1117
1216
  let settled = false;
1118
1217
  const abort = () => pending.resolve(null);
@@ -1153,7 +1252,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1153
1252
  });
1154
1253
  const approvePlan = (request, signal) => new Promise((resolveApproval) => {
1155
1254
  let settled = false;
1156
- const abort = () => pending.resolve(false);
1255
+ const abort = () => pending.resolve({ behavior: 'deny' });
1157
1256
  const pending = {
1158
1257
  request,
1159
1258
  resolve: (approved) => {
@@ -1168,10 +1267,13 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1168
1267
  },
1169
1268
  };
1170
1269
  if (signal?.aborted) {
1171
- pending.resolve(false);
1270
+ pending.resolve({ behavior: 'deny' });
1172
1271
  return;
1173
1272
  }
1174
1273
  signal?.addEventListener('abort', abort, { once: true });
1274
+ clearComposerInput();
1275
+ setPlanApprovalSelection(0);
1276
+ setPlanApprovalFeedbackMode(false);
1175
1277
  planApprovalRef.current = pending;
1176
1278
  setPlanApproval(pending);
1177
1279
  });
@@ -1203,6 +1305,8 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1203
1305
  return next;
1204
1306
  };
1205
1307
  const isSessionActionApproved = (call) => {
1308
+ if (immediatePermissionRulesRef.current.some((rule) => claudePermissionRuleMatches(rule, call, runtimeCwdRef.current)))
1309
+ return true;
1206
1310
  const activeSessionId = sessionIdRef.current;
1207
1311
  return activeSessionId
1208
1312
  ? (approvedSessionActionsRef.current
@@ -1424,6 +1528,39 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1424
1528
  onTurnChange?.(loading);
1425
1529
  void loading.finally(() => onTurnChange?.(null));
1426
1530
  };
1531
+ const writeCopyCandidate = async (candidate) => {
1532
+ const directory = join(tmpdir(), 'claude');
1533
+ await mkdir(directory, { recursive: true });
1534
+ const path = join(directory, candidate.filename);
1535
+ await writeFile(path, candidate.text, 'utf8');
1536
+ return path;
1537
+ };
1538
+ const copyCandidate = (candidate, savePreference = false) => {
1539
+ const copying = (async () => {
1540
+ try {
1541
+ if (savePreference) {
1542
+ const snapshot = await saveConfigSetting('copyFullResponse', true, configTarget);
1543
+ await reloadRuntimeSettings(snapshot);
1544
+ }
1545
+ await clipboardWriter(candidate.text);
1546
+ let result = `Copied to clipboard (${candidate.text.length} characters, ${candidate.text.split('\n').length} lines)`;
1547
+ try {
1548
+ result += `\nAlso written to ${await writeCopyCandidate(candidate)}`;
1549
+ }
1550
+ catch {
1551
+ // Clipboard success is authoritative; the temp file is a fallback.
1552
+ }
1553
+ if (savePreference)
1554
+ result += '\nPreference saved. Use /config to change copyFullResponse';
1555
+ append({ kind: 'local-result', text: result });
1556
+ }
1557
+ catch (error) {
1558
+ warn(error);
1559
+ }
1560
+ })();
1561
+ onTurnChange?.(copying);
1562
+ void copying.finally(() => onTurnChange?.(null));
1563
+ };
1427
1564
  const copyResponse = (position) => {
1428
1565
  const response = [...history]
1429
1566
  .reverse()
@@ -1435,21 +1572,48 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1435
1572
  });
1436
1573
  return;
1437
1574
  }
1438
- if (!runtimeSettingsRef.current.copyFullResponse &&
1439
- shouldShowCopyPicker(response.text)) {
1440
- updateMenu({
1441
- kind: 'copy',
1442
- candidates: copyCandidates(response.text),
1443
- selectedIndex: 0,
1444
- });
1575
+ const candidates = copyCandidates(response.text);
1576
+ const full = candidates[0];
1577
+ if (!full)
1578
+ return;
1579
+ if (runtimeSettingsRef.current.copyFullResponse ||
1580
+ !shouldShowCopyPicker(response.text)) {
1581
+ copyCandidate(full);
1445
1582
  return;
1446
1583
  }
1447
- const copying = clipboardWriter(response.text).then(() => append({
1448
- kind: 'local-result',
1449
- text: `Copied ${position === 1 ? 'last' : `${ordinal(position)}-latest`} response to clipboard.`,
1450
- }), (error) => warn(error));
1451
- onTurnChange?.(copying);
1452
- void copying.finally(() => onTurnChange?.(null));
1584
+ updateMenu({
1585
+ kind: 'copy',
1586
+ candidates,
1587
+ selectedIndex: 0,
1588
+ messageAge: position - 1,
1589
+ });
1590
+ };
1591
+ const openSettings = (tab) => {
1592
+ const initial = {
1593
+ kind: 'config',
1594
+ snapshot: { settings: {}, state: {} },
1595
+ tab,
1596
+ selectedIndex: 0,
1597
+ query: '',
1598
+ searchFocused: tab === 'config',
1599
+ };
1600
+ updateMenu(initial);
1601
+ const loading = (async () => {
1602
+ setBusy(true);
1603
+ try {
1604
+ const snapshot = await loadConfigSettings(configTarget);
1605
+ if (menuRef.current?.kind === 'config')
1606
+ updateMenu({ ...menuRef.current, snapshot });
1607
+ }
1608
+ catch (error) {
1609
+ warn(error);
1610
+ }
1611
+ finally {
1612
+ setBusy(false);
1613
+ }
1614
+ })();
1615
+ onTurnChange?.(loading);
1616
+ void loading.finally(() => onTurnChange?.(null));
1453
1617
  };
1454
1618
  const exportConversation = (method) => {
1455
1619
  if (method === 'file') {
@@ -1808,6 +1972,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1808
1972
  if (!name)
1809
1973
  throw new Error('Could not generate a session name');
1810
1974
  await commands.rename(sessionId, name);
1975
+ setSessionName(name);
1811
1976
  append({ kind: 'local-result', text: `Session renamed to: ${name}` });
1812
1977
  }
1813
1978
  catch (error) {
@@ -2381,20 +2546,270 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
2381
2546
  }
2382
2547
  }
2383
2548
  if (permission) {
2384
- if (lower === 'y' || value === '1') {
2385
- permission.resolve(true);
2386
- }
2387
- else if (lower === 'n' || value === '2' || key.return || key.escape) {
2549
+ const options = permission.kind === 'tool' && toolPermissionModel
2550
+ ? toolPermissionModel.options
2551
+ : [
2552
+ { action: 'allow-once', label: 'Yes' },
2553
+ { action: 'deny', label: 'No' },
2554
+ ];
2555
+ const optionCount = options.length;
2556
+ const resolvePermission = (selectedIndex) => {
2557
+ const feedback = inputRef.current.trim();
2558
+ clearComposerInput();
2559
+ setPermissionFeedbackMode(false);
2560
+ const selected = options[selectedIndex];
2561
+ if (!selected)
2562
+ return;
2563
+ if (selected.action === 'allow-once') {
2564
+ permission.resolve(feedback ? { behavior: 'allow', feedback } : { behavior: 'allow' });
2565
+ return;
2566
+ }
2567
+ if (permission.kind === 'tool' &&
2568
+ selected.action === 'persist-rule' &&
2569
+ (selected.rule || selected.editableRule || selected.updates?.length)) {
2570
+ const editedRule = permissionRuleEditor?.text.trim();
2571
+ const rule = selected.editableRule
2572
+ ? editedRule
2573
+ ? `${selected.editableRule.toolName}(${editedRule})`
2574
+ : undefined
2575
+ : selected.rule;
2576
+ const updates = selected.editableRule
2577
+ ? rule
2578
+ ? [
2579
+ {
2580
+ type: 'addRules',
2581
+ rules: [
2582
+ {
2583
+ toolName: selected.editableRule.toolName,
2584
+ ruleContent: editedRule ?? '',
2585
+ },
2586
+ ],
2587
+ behavior: 'allow',
2588
+ destination: 'localSettings',
2589
+ },
2590
+ ]
2591
+ : []
2592
+ : (selected.updates ??
2593
+ (rule
2594
+ ? [
2595
+ {
2596
+ type: 'addRules',
2597
+ rules: [
2598
+ /^([A-Za-z][\w-]*)(?:\((.*)\))?$/u.exec(rule),
2599
+ ].flatMap((match) => match?.[1]
2600
+ ? [
2601
+ {
2602
+ toolName: match[1],
2603
+ ...(match[2] === undefined
2604
+ ? {}
2605
+ : { ruleContent: match[2] }),
2606
+ },
2607
+ ]
2608
+ : []),
2609
+ behavior: 'allow',
2610
+ destination: 'localSettings',
2611
+ },
2612
+ ]
2613
+ : []));
2614
+ if (!rule && updates.length === 0) {
2615
+ permission.resolve({ behavior: 'allow' });
2616
+ return;
2617
+ }
2618
+ const saving = (async () => {
2619
+ try {
2620
+ for (const update of updates) {
2621
+ if (update.type !== 'addRules' ||
2622
+ update.destination !== 'localSettings') {
2623
+ continue;
2624
+ }
2625
+ for (const value of update.rules) {
2626
+ const savedRule = permissionRuleValueToString(value);
2627
+ await permissionStore.add({
2628
+ behavior: update.behavior,
2629
+ rule: savedRule,
2630
+ scope: 'local',
2631
+ });
2632
+ immediatePermissionRulesRef.current.push(savedRule);
2633
+ }
2634
+ }
2635
+ permission.resolve({
2636
+ behavior: 'allow',
2637
+ updatedPermissions: updates,
2638
+ });
2639
+ }
2640
+ catch (error) {
2641
+ warn(error);
2642
+ }
2643
+ })();
2644
+ onTurnChange?.(saving);
2645
+ void saving.finally(() => onTurnChange?.(null));
2646
+ return;
2647
+ }
2648
+ if (permission.kind === 'tool' &&
2649
+ selected.action === 'allow-session-action' &&
2650
+ (selected.rule || selected.updates?.length)) {
2651
+ if (selected.rule)
2652
+ immediatePermissionRulesRef.current.push(selected.rule);
2653
+ permission.resolve({
2654
+ behavior: 'allow',
2655
+ updatedPermissions: selected.updates ?? [],
2656
+ });
2657
+ return;
2658
+ }
2659
+ if (permission.kind === 'tool' &&
2660
+ selected.action === 'allow-session-edits') {
2661
+ for (const rule of ['Write', 'Edit', 'NotebookEdit']) {
2662
+ if (!immediatePermissionRulesRef.current.includes(rule))
2663
+ immediatePermissionRulesRef.current.push(rule);
2664
+ }
2665
+ updateRuntimePreferences((current) => ({
2666
+ ...current,
2667
+ permissionMode: 'acceptEdits',
2668
+ }));
2669
+ permission.resolve({
2670
+ behavior: 'allow',
2671
+ updatedPermissions: selected.updates?.length
2672
+ ? selected.updates
2673
+ : [
2674
+ {
2675
+ type: 'setMode',
2676
+ mode: 'acceptEdits',
2677
+ destination: 'session',
2678
+ },
2679
+ ],
2680
+ });
2681
+ const activeSessionId = sessionIdRef.current;
2682
+ if (activeSessionId && serviceRef.current?.setPermissionMode) {
2683
+ const saving = serviceRef.current
2684
+ .setPermissionMode(activeSessionId, 'acceptEdits')
2685
+ .catch((error) => warn(error));
2686
+ onTurnChange?.(saving);
2687
+ void saving.finally(() => onTurnChange?.(null));
2688
+ }
2689
+ return;
2690
+ }
2691
+ permission.resolve(feedback ? { behavior: 'deny', message: feedback } : false);
2692
+ };
2693
+ if (key.escape) {
2388
2694
  permission.resolve(false);
2389
2695
  }
2696
+ else if (permissionFeedbackMode) {
2697
+ if (key.tab) {
2698
+ clearComposerInput();
2699
+ setPermissionFeedbackMode(false);
2700
+ }
2701
+ else if (key.return) {
2702
+ resolvePermission(permissionSelection);
2703
+ }
2704
+ else {
2705
+ editComposer();
2706
+ }
2707
+ }
2708
+ else if (key.upArrow) {
2709
+ setPermissionSelection((current) => current === 0 ? optionCount - 1 : current - 1);
2710
+ }
2711
+ else if (key.downArrow) {
2712
+ setPermissionSelection((current) => (current + 1) % optionCount);
2713
+ }
2714
+ else if (key.tab &&
2715
+ (options[permissionSelection]?.action === 'allow-once' ||
2716
+ options[permissionSelection]?.action === 'deny')) {
2717
+ clearComposerInput();
2718
+ setPermissionFeedbackMode(true);
2719
+ }
2720
+ else if (options[permissionSelection]?.editableRule && !key.return) {
2721
+ if (key.leftArrow) {
2722
+ setPermissionRuleEditor((current) => moveComposerCursor(current ?? createComposerEditor(), -1));
2723
+ }
2724
+ else if (key.rightArrow) {
2725
+ setPermissionRuleEditor((current) => moveComposerCursor(current ?? createComposerEditor(), 1));
2726
+ }
2727
+ else if (key.backspace) {
2728
+ setPermissionRuleEditor((current) => deleteComposerBackward(current ?? createComposerEditor()));
2729
+ }
2730
+ else if (key.delete) {
2731
+ setPermissionRuleEditor((current) => deleteComposerForward(current ?? createComposerEditor()));
2732
+ }
2733
+ else if (value && !key.ctrl && !key.meta) {
2734
+ setPermissionRuleEditor((current) => insertComposerText(current ?? createComposerEditor(), value));
2735
+ }
2736
+ }
2737
+ else if (lower === 'y') {
2738
+ resolvePermission(0);
2739
+ }
2740
+ else if (lower === 'n') {
2741
+ resolvePermission(optionCount - 1);
2742
+ }
2743
+ else if (/^[1-9]$/u.test(value)) {
2744
+ const selectedIndex = Number(value) - 1;
2745
+ if (selectedIndex < optionCount)
2746
+ resolvePermission(selectedIndex);
2747
+ }
2748
+ else if (key.return) {
2749
+ resolvePermission(permissionSelection);
2750
+ }
2390
2751
  return;
2391
2752
  }
2392
2753
  if (planApproval) {
2393
- if (lower === 'y' || value === '1') {
2394
- planApproval.resolve(true);
2754
+ const elevatedMode = runtimeSettingsRef.current
2755
+ .useAutoModeDuringPlan
2756
+ ? 'auto'
2757
+ : allowDangerouslySkipPermissions
2758
+ ? 'bypassPermissions'
2759
+ : 'acceptEdits';
2760
+ const resolvePlanApproval = (selectedIndex) => {
2761
+ const feedback = inputRef.current.trim() || undefined;
2762
+ clearComposerInput();
2763
+ setPlanApprovalFeedbackMode(false);
2764
+ if (selectedIndex === 2) {
2765
+ planApproval.resolve({
2766
+ behavior: 'deny',
2767
+ ...(feedback ? { feedback } : {}),
2768
+ });
2769
+ return;
2770
+ }
2771
+ planApproval.resolve({
2772
+ behavior: 'allow',
2773
+ permissionMode: selectedIndex === 0 ? elevatedMode : 'default',
2774
+ ...(feedback ? { feedback } : {}),
2775
+ });
2776
+ };
2777
+ if (key.escape) {
2778
+ planApproval.resolve({ behavior: 'deny' });
2779
+ }
2780
+ else if (planApprovalFeedbackMode) {
2781
+ if (key.tab) {
2782
+ clearComposerInput();
2783
+ setPlanApprovalFeedbackMode(false);
2784
+ }
2785
+ else if (key.return) {
2786
+ resolvePlanApproval(planApprovalSelection);
2787
+ }
2788
+ else {
2789
+ editComposer();
2790
+ }
2395
2791
  }
2396
- else if (lower === 'n' || value === '2' || key.return || key.escape) {
2397
- planApproval.resolve(false);
2792
+ else if (key.upArrow) {
2793
+ setPlanApprovalSelection((current) => (current === 0 ? 2 : current - 1));
2794
+ }
2795
+ else if (key.downArrow) {
2796
+ setPlanApprovalSelection((current) => (current + 1) % 3);
2797
+ }
2798
+ else if (key.tab) {
2799
+ clearComposerInput();
2800
+ setPlanApprovalFeedbackMode(true);
2801
+ }
2802
+ else if (lower === 'y') {
2803
+ resolvePlanApproval(0);
2804
+ }
2805
+ else if (lower === 'n') {
2806
+ resolvePlanApproval(2);
2807
+ }
2808
+ else if (/^[1-3]$/u.test(value)) {
2809
+ resolvePlanApproval(Number(value) - 1);
2810
+ }
2811
+ else if (key.return) {
2812
+ resolvePlanApproval(planApprovalSelection);
2398
2813
  }
2399
2814
  return;
2400
2815
  }
@@ -2432,43 +2847,151 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
2432
2847
  return;
2433
2848
  }
2434
2849
  if (elicitation) {
2435
- if (key.escape) {
2436
- elicitation.resolve({ action: 'cancel' });
2850
+ const form = elicitationForm ??
2851
+ createTuiElicitationForm(elicitation.request.requestedSchema);
2852
+ const currentField = focusedElicitationField(form);
2853
+ const textField = currentField &&
2854
+ ['text', 'number', 'integer'].includes(currentField.kind);
2855
+ const commitCurrent = () => textField ? commitElicitationText(form, inputRef.current) : form;
2856
+ const showForm = (next) => {
2857
+ setElicitationForm(next);
2858
+ updateComposerInput(elicitationTextValue(next));
2859
+ };
2860
+ const move = (direction) => showForm(moveElicitationFocus(commitCurrent(), direction));
2861
+ if (elicitation.request.mode === 'url') {
2862
+ const openUrl = () => {
2863
+ void Promise.resolve()
2864
+ .then(() => elicitationUrlOpener(elicitation.request.url ?? ''))
2865
+ .catch(() => undefined);
2866
+ };
2867
+ const dismissUrlDialog = () => {
2868
+ elicitationUrlWaitingRef.current = null;
2869
+ setElicitationUrlWaiting(false);
2870
+ setElicitation(null);
2871
+ setElicitationForm(null);
2872
+ clearComposerInput();
2873
+ };
2874
+ if (key.escape) {
2875
+ if (elicitationUrlWaiting)
2876
+ dismissUrlDialog();
2877
+ else
2878
+ elicitation.resolve({ action: 'cancel' });
2879
+ }
2880
+ else if (key.leftArrow || key.rightArrow) {
2881
+ setElicitationForm({
2882
+ ...form,
2883
+ focusIndex: form.focusIndex === 0 ? 1 : 0,
2884
+ });
2885
+ }
2886
+ else if (key.return && form.focusIndex === 0) {
2887
+ if (elicitationUrlWaiting) {
2888
+ openUrl();
2889
+ }
2890
+ else {
2891
+ openUrl();
2892
+ elicitationUrlWaitingRef.current = elicitation;
2893
+ setElicitationUrlWaiting(true);
2894
+ elicitation.resolve({ action: 'accept' }, { keepUrlDialog: true });
2895
+ }
2896
+ }
2897
+ else if (key.return && form.focusIndex === 1) {
2898
+ if (elicitationUrlWaiting)
2899
+ dismissUrlDialog();
2900
+ else
2901
+ elicitation.resolve({ action: 'decline' });
2902
+ }
2903
+ return;
2437
2904
  }
2438
- else if (key.return) {
2439
- const answer = inputRef.current.trim();
2440
- clearComposerInput();
2441
- if (!answer || answer.toLowerCase() === 'decline') {
2442
- elicitation.resolve({ action: 'decline' });
2905
+ if (form.expandedField && currentField) {
2906
+ if (key.escape || key.leftArrow) {
2907
+ setElicitationForm({ ...form, expandedField: undefined });
2908
+ }
2909
+ else if (key.upArrow) {
2910
+ showForm(moveElicitationOption(form, -1));
2911
+ }
2912
+ else if (key.downArrow) {
2913
+ showForm(moveElicitationOption(form, 1));
2443
2914
  }
2444
- else if (answer.toLowerCase() === 'cancel') {
2445
- elicitation.resolve({ action: 'cancel' });
2915
+ else if (value === ' ') {
2916
+ setElicitationForm(selectElicitationOption(form, currentField.kind === 'enum'));
2446
2917
  }
2447
- else if (answer.toLowerCase() === 'accept') {
2448
- elicitation.resolve({ action: 'accept' });
2918
+ else if (key.return) {
2919
+ showForm(moveElicitationFocus(selectElicitationOption(form, true, true), 1));
2920
+ }
2921
+ else if (printable && value) {
2922
+ setElicitationForm(typeaheadElicitationOption(form, value));
2923
+ }
2924
+ }
2925
+ else if (key.escape) {
2926
+ elicitation.resolve({ action: 'cancel' });
2927
+ }
2928
+ else if (key.upArrow) {
2929
+ move(-1);
2930
+ }
2931
+ else if (key.downArrow) {
2932
+ move(1);
2933
+ }
2934
+ else if (!currentField && (key.leftArrow || key.rightArrow)) {
2935
+ setElicitationForm({
2936
+ ...form,
2937
+ focusIndex: form.focusIndex === form.fields.length
2938
+ ? form.fields.length + 1
2939
+ : form.fields.length,
2940
+ });
2941
+ }
2942
+ else if (form.focusIndex === form.fields.length && key.return) {
2943
+ const validated = validateTuiElicitationForm(form);
2944
+ if (elicitationFormIsValid(validated)) {
2945
+ elicitation.resolve({
2946
+ action: 'accept',
2947
+ ...(Object.keys(validated.values).length > 0
2948
+ ? { content: { ...validated.values } }
2949
+ : {}),
2950
+ });
2449
2951
  }
2450
2952
  else {
2451
- try {
2452
- const content = JSON.parse(answer);
2453
- if (!content ||
2454
- typeof content !== 'object' ||
2455
- Array.isArray(content))
2456
- throw new Error('elicitation content must be a JSON object');
2457
- elicitation.resolve({
2458
- action: 'accept',
2459
- content: content,
2460
- });
2461
- }
2462
- catch {
2463
- append({
2464
- kind: 'warning',
2465
- text: 'Elicitation response must be accept, decline, cancel, or a JSON object.',
2466
- });
2467
- }
2953
+ showForm(validated);
2468
2954
  }
2469
2955
  }
2470
- else {
2471
- editComposer();
2956
+ else if (form.focusIndex === form.fields.length + 1 && key.return) {
2957
+ elicitation.resolve({ action: 'decline' });
2958
+ }
2959
+ else if (currentField?.kind === 'boolean') {
2960
+ if (value === ' ' || lower === 'y' || lower === 'n') {
2961
+ let next = toggleElicitationBoolean(form);
2962
+ if (lower === 'n' && next.values[currentField.name] === true)
2963
+ next = toggleElicitationBoolean(next);
2964
+ if (lower === 'y' && next.values[currentField.name] === false)
2965
+ next = toggleElicitationBoolean(next);
2966
+ setElicitationForm(next);
2967
+ }
2968
+ else if (key.backspace) {
2969
+ setElicitationForm(unsetElicitationField(form));
2970
+ }
2971
+ else if (key.return) {
2972
+ move(1);
2973
+ }
2974
+ }
2975
+ else if (currentField &&
2976
+ ['enum', 'multi-enum'].includes(currentField.kind)) {
2977
+ if (key.rightArrow) {
2978
+ setElicitationForm(expandElicitationOptions(form));
2979
+ }
2980
+ else if (key.backspace) {
2981
+ setElicitationForm(unsetElicitationField(form));
2982
+ }
2983
+ else if (key.return) {
2984
+ move(1);
2985
+ }
2986
+ else if (printable && value) {
2987
+ setElicitationForm(typeaheadElicitationOption(form, value));
2988
+ }
2989
+ }
2990
+ else if (textField) {
2991
+ if (key.return)
2992
+ move(1);
2993
+ else
2994
+ editComposer();
2472
2995
  }
2473
2996
  return;
2474
2997
  }
@@ -2522,41 +3045,6 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
2522
3045
  return;
2523
3046
  }
2524
3047
  const earlyMenu = menuRef.current;
2525
- if (earlyMenu?.kind === 'copy') {
2526
- if (key.escape || value === '\u001B') {
2527
- updateMenu(null);
2528
- return;
2529
- }
2530
- if (key.upArrow || key.downArrow) {
2531
- updateMenu({
2532
- ...earlyMenu,
2533
- selectedIndex: Math.max(0, Math.min(earlyMenu.candidates.length - 1, earlyMenu.selectedIndex + (key.upArrow ? -1 : 1))),
2534
- });
2535
- return;
2536
- }
2537
- if (/^[1-9]$/u.test(value)) {
2538
- updateMenu({
2539
- ...earlyMenu,
2540
- selectedIndex: Math.min(earlyMenu.candidates.length - 1, Number(value) - 1),
2541
- });
2542
- return;
2543
- }
2544
- if (key.return) {
2545
- const candidate = earlyMenu.candidates[earlyMenu.selectedIndex];
2546
- if (!candidate)
2547
- return;
2548
- const copying = clipboardWriter(candidate.text).then(() => {
2549
- updateMenu(null);
2550
- append({
2551
- kind: 'local-result',
2552
- text: `Copied ${candidate.label.toLowerCase()} to clipboard.`,
2553
- });
2554
- }, (error) => warn(error));
2555
- onTurnChange?.(copying);
2556
- void copying.finally(() => onTurnChange?.(null));
2557
- }
2558
- return;
2559
- }
2560
3048
  if (earlyMenu?.kind === 'agents') {
2561
3049
  if (key.escape || value === '\u001B') {
2562
3050
  updateMenu(null);
@@ -2682,11 +3170,9 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
2682
3170
  const rows = projectConfigRows(earlyMenu.snapshot, earlyMenu.query);
2683
3171
  if (key.leftArrow || key.rightArrow || key.tab) {
2684
3172
  const tabs = [
2685
- 'settings',
2686
3173
  'status',
2687
3174
  'config',
2688
3175
  'usage',
2689
- 'stats',
2690
3176
  ];
2691
3177
  const index = tabs.indexOf(earlyMenu.tab);
2692
3178
  const direction = key.leftArrow || (key.tab && key.shift) ? -1 : 1;
@@ -3433,19 +3919,6 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3433
3919
  }
3434
3920
  return;
3435
3921
  }
3436
- if (activeMenu.kind === 'status') {
3437
- if (key.escape || value === '\u001B') {
3438
- updateMenu(null);
3439
- }
3440
- else if (key.leftArrow || key.rightArrow || key.tab) {
3441
- const direction = key.leftArrow || (key.tab && key.shift) ? -1 : 1;
3442
- updateMenu({
3443
- kind: 'status',
3444
- tabIndex: Math.max(0, Math.min(4, activeMenu.tabIndex + direction)),
3445
- });
3446
- }
3447
- return;
3448
- }
3449
3922
  if (activeMenu.kind === 'hooks') {
3450
3923
  const event = activeMenu.configuration.events[activeMenu.eventIndex];
3451
3924
  const matcher = event?.matchers[activeMenu.matcherIndex];
@@ -3648,6 +4121,10 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3648
4121
  clearComposerInput();
3649
4122
  updateMenu(null);
3650
4123
  changeModel(model);
4124
+ void saveConfigSetting('model', model, configTarget).then(() => append({
4125
+ kind: 'local-result',
4126
+ text: `${model} set as default model for new sessions.`,
4127
+ }), (error) => warn(error));
3651
4128
  }
3652
4129
  }
3653
4130
  else {
@@ -3674,6 +4151,38 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3674
4151
  }
3675
4152
  return;
3676
4153
  }
4154
+ if (activeMenu.kind === 'copy') {
4155
+ if (key.escape || value === '\u001B') {
4156
+ updateMenu(null);
4157
+ }
4158
+ else if (key.upArrow || key.downArrow) {
4159
+ updateMenu({
4160
+ ...activeMenu,
4161
+ selectedIndex: Math.max(0, Math.min(activeMenu.candidates.length - 1, activeMenu.selectedIndex + (key.upArrow ? -1 : 1))),
4162
+ });
4163
+ }
4164
+ else if (/^[1-9]$/u.test(value)) {
4165
+ updateMenu({
4166
+ ...activeMenu,
4167
+ selectedIndex: Math.min(activeMenu.candidates.length - 1, Number(value) - 1),
4168
+ });
4169
+ }
4170
+ else if (key.return || value.toLowerCase() === 'w') {
4171
+ const candidate = activeMenu.candidates[activeMenu.selectedIndex];
4172
+ if (!candidate)
4173
+ return;
4174
+ updateMenu(null);
4175
+ if (value.toLowerCase() === 'w') {
4176
+ const writing = writeCopyCandidate(candidate).then((path) => append({ kind: 'local-result', text: `Written to ${path}` }), (error) => warn(error));
4177
+ onTurnChange?.(writing);
4178
+ void writing.finally(() => onTurnChange?.(null));
4179
+ }
4180
+ else {
4181
+ copyCandidate(candidate, candidate.kind === 'always');
4182
+ }
4183
+ }
4184
+ return;
4185
+ }
3677
4186
  if (activeMenu.kind === 'export') {
3678
4187
  if (key.escape || value === '\u001B') {
3679
4188
  updateMenu(null);
@@ -4040,19 +4549,29 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
4040
4549
  changeEffort(nextEffort);
4041
4550
  return;
4042
4551
  }
4043
- if (!key.return && lower !== 's')
4552
+ if (!key.return)
4044
4553
  return;
4045
4554
  if (activeMenu.kind === 'model') {
4046
- if (activeMenu.selectedIndex === 1) {
4047
- updateMenu(null);
4048
- changeModel(undefined);
4049
- }
4050
- else if (activeMenu.selectedIndex === 2) {
4555
+ if (activeMenu.selectedIndex === modelOptions.length - 1) {
4051
4556
  clearComposerInput();
4052
4557
  updateMenu({ kind: 'model-input' });
4558
+ return;
4053
4559
  }
4054
- else {
4055
- updateMenu(null);
4560
+ const model = modelOptions[activeMenu.selectedIndex]?.model;
4561
+ updateMenu(null);
4562
+ if (activeMenu.selectedIndex === 0) {
4563
+ changeModel(undefined);
4564
+ void saveConfigSetting('model', 'default', configTarget).then(() => append({
4565
+ kind: 'local-result',
4566
+ text: 'Default model set for new sessions.',
4567
+ }), (error) => warn(error));
4568
+ }
4569
+ else if (model) {
4570
+ changeModel(model);
4571
+ void saveConfigSetting('model', model, configTarget).then(() => append({
4572
+ kind: 'local-result',
4573
+ text: `${model} set as default model for new sessions.`,
4574
+ }), (error) => warn(error));
4056
4575
  }
4057
4576
  }
4058
4577
  else {
@@ -4149,11 +4668,14 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
4149
4668
  return;
4150
4669
  }
4151
4670
  const selectedCommand = matchingSlashCommands[selectedSlashCommandIndex];
4671
+ const commandNameQuery = inputRef.current.slice(1).toLocaleLowerCase();
4152
4672
  const exactSelectedCommand = selectedCommand !== undefined &&
4153
- inputRef.current.toLocaleLowerCase() ===
4154
- `/${selectedCommand.name}`.toLocaleLowerCase();
4673
+ commandNameQuery === selectedCommand.name.toLocaleLowerCase();
4674
+ const selectedCommandMatchesName = selectedCommand !== undefined &&
4675
+ selectedCommand.name.toLocaleLowerCase().startsWith(commandNameQuery);
4155
4676
  if (selectedCommand &&
4156
- (key.tab || (key.return && !exactSelectedCommand))) {
4677
+ (key.tab ||
4678
+ (key.return && !exactSelectedCommand && selectedCommandMatchesName))) {
4157
4679
  updateComposerInput(`/${selectedCommand.name} `);
4158
4680
  setCommandPaletteOpen(false);
4159
4681
  return;
@@ -4227,6 +4749,37 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
4227
4749
  : TUI_THEMES.indexOf(themeSettings.theme)),
4228
4750
  });
4229
4751
  }
4752
+ else if (prompt === '/vim') {
4753
+ const saving = (async () => {
4754
+ setBusy(true);
4755
+ try {
4756
+ const mode = runtimeSettingsRef.current.editor === 'vim' ? 'normal' : 'vim';
4757
+ const snapshot = await saveConfigSetting('editor', mode, configTarget);
4758
+ await reloadRuntimeSettings(snapshot);
4759
+ setVimInsertMode(true);
4760
+ append({
4761
+ kind: 'local-result',
4762
+ text: `Editor mode set to ${mode}. ${mode === 'vim'
4763
+ ? 'Use Escape key to toggle between INSERT and NORMAL modes.'
4764
+ : 'Using standard (readline) keyboard bindings.'}`,
4765
+ });
4766
+ }
4767
+ catch (error) {
4768
+ warn(error);
4769
+ }
4770
+ finally {
4771
+ setBusy(false);
4772
+ }
4773
+ })();
4774
+ onTurnChange?.(saving);
4775
+ void saving.finally(() => onTurnChange?.(null));
4776
+ }
4777
+ else if (prompt === '/output-style') {
4778
+ append({
4779
+ kind: 'local-result',
4780
+ text: '/output-style has been deprecated. Use /config to change your output style, or set it in your settings file. Changes take effect on the next session.',
4781
+ });
4782
+ }
4230
4783
  else if (prompt === '/terminal-setup') {
4231
4784
  const setup = (async () => {
4232
4785
  setBusy(true);
@@ -4450,47 +5003,50 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
4450
5003
  }));
4451
5004
  const measuredTokens = (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0);
4452
5005
  const skillTokens = skills.reduce((total, skill) => total + skill.tokens, 0);
5006
+ const contextEntry = {
5007
+ kind: 'context',
5008
+ usedTokens: Math.max(measuredTokens, skillTokens),
5009
+ contextWindowTokens: runtimeDisplay.contextWindowTokens ?? 200_000,
5010
+ model: runtimeDisplay.model ?? 'provider default',
5011
+ skills,
5012
+ memoryFiles: [],
5013
+ };
4453
5014
  setHistory((current) => [
4454
5015
  ...current,
4455
5016
  { kind: 'user', text: '/context' },
4456
- {
4457
- kind: 'context',
4458
- usedTokens: Math.max(measuredTokens, skillTokens),
4459
- contextWindowTokens: runtimeDisplay.contextWindowTokens ?? 200_000,
4460
- skills,
4461
- },
5017
+ contextEntry,
4462
5018
  ]);
4463
- }
4464
- else if (prompt === '/status') {
4465
- updateMenu({ kind: 'status', tabIndex: 1 });
4466
- }
4467
- else if (prompt === '/config') {
4468
- const initial = {
4469
- kind: 'config',
4470
- snapshot: { settings: {}, state: {} },
4471
- tab: 'config',
4472
- selectedIndex: 0,
4473
- query: '',
4474
- searchFocused: true,
4475
- };
4476
- updateMenu(initial);
4477
5019
  const loading = (async () => {
4478
- setBusy(true);
4479
5020
  try {
4480
- const snapshot = await loadConfigSettings(configTarget);
4481
- if (menuRef.current?.kind === 'config')
4482
- updateMenu({ ...menuRef.current, snapshot });
4483
- }
4484
- catch (error) {
4485
- warn(error);
5021
+ const files = await memoryFilesLoader(keybindingsRoot, runtimeCwdRef.current);
5022
+ const memoryFiles = await Promise.all(files.entries
5023
+ .filter((entry) => entry.kind === 'file')
5024
+ .map(async (entry) => ({
5025
+ path: entry.displayPath,
5026
+ tokens: await estimateFileTokens(entry.path),
5027
+ })));
5028
+ setHistory((current) => {
5029
+ const next = [...current];
5030
+ const last = next.at(-1);
5031
+ if (last && last.kind === 'context') {
5032
+ next[next.length - 1] = { ...last, memoryFiles };
5033
+ }
5034
+ return next;
5035
+ });
4486
5036
  }
4487
- finally {
4488
- setBusy(false);
5037
+ catch {
5038
+ // Leave the Memory files section empty if files cannot be read.
4489
5039
  }
4490
5040
  })();
4491
5041
  onTurnChange?.(loading);
4492
5042
  void loading.finally(() => onTurnChange?.(null));
4493
5043
  }
5044
+ else if (prompt === '/status') {
5045
+ openSettings('status');
5046
+ }
5047
+ else if (prompt === '/config') {
5048
+ openSettings('config');
5049
+ }
4494
5050
  else if (tuiCommand) {
4495
5051
  const mode = tuiCommand[1];
4496
5052
  if (!mode) {
@@ -4520,7 +5076,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
4520
5076
  }
4521
5077
  }
4522
5078
  else if (prompt === '/usage') {
4523
- updateMenu({ kind: 'status', tabIndex: 3 });
5079
+ openSettings('usage');
4524
5080
  }
4525
5081
  else if (prompt === '/update') {
4526
5082
  append({
@@ -4600,11 +5156,26 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
4600
5156
  });
4601
5157
  return (_jsx(TuiThemeProvider, { settings: themeSettings, children: _jsx(Box, { flexDirection: "column", children: selectingSession ? (_jsx(SessionPicker, { sessions: filteredPickerChoices, selectedIndex: selectedIndex, screenReader: axScreenReader, query: sessionSearch })) : (_jsxs(_Fragment, { children: [!axScreenReader && history.length === 0 && !sessionId ? (_jsx(WelcomePanel, { display: runtimeDisplay, width: width })) : null, sessionId ? (_jsxs(Text, { dimColor: true, children: ["Session ", sessionId.slice(0, 8)] })) : null, _jsx(Transcript, { items: history, activeText: activeText, activeThinking: activeThinking, thinkingExpanded: thinkingExpanded, detailedTranscript: thinkingExpanded || runtimeSettings.verbose, screenReader: axScreenReader }), externalEditorRequest !== null ||
4602
5158
  keybindingsEditing ||
4603
- memoryEditorRequest !== null ? (_jsx(ExternalEditorWait, { screenReader: axScreenReader })) : permission ? (_jsxs(DialogFrame, { title: permission.kind === 'recovery'
4604
- ? `Retry interrupted ${permission.call.name}?`
4605
- : `Allow ${permission.call.name}?`, screenReader: axScreenReader, children: [_jsx(Text, { bold: true, children: describeTool(permission.call, sensitiveValues) }), _jsxs(Text, { children: [selectionPrefix(true, axScreenReader), "1. Yes"] }), _jsx(Text, { children: " 2. No" }), _jsx(Text, { dimColor: true, children: "Enter/Esc declines \u00B7 y/n quick response" })] })) : planApproval ? (_jsxs(DialogFrame, { title: "Approve this plan and begin implementation?", screenReader: axScreenReader, children: [_jsx(Text, { dimColor: true, children: planApproval.request.planPath }), planApproval.request.plan ? (_jsx(Box, { marginY: 1, children: _jsx(Text, { children: planApproval.request.plan }) })) : null, _jsxs(Text, { children: [selectionPrefix(true, axScreenReader), "1. Yes, implement the plan"] }), _jsx(Text, { children: " 2. No, keep planning" })] })) : question ? (_jsxs(DialogFrame, { title: `${question.questions[question.index]?.header}: ${question.questions[question.index]?.question}`, screenReader: axScreenReader, children: [question.questions[question.index]?.options.map((option, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [index + 1, ". ", option.label, " \u2014 ", option.description] }), option.preview ? (_jsx(Text, { dimColor: true, children: option.preview })) : null] }, `${index}-${option.label}`))), _jsxs(Text, { children: [axScreenReader ? 'Current answer: ' : '› ', input || (axScreenReader ? '(empty)' : '')] }), _jsx(Text, { dimColor: true, children: question.questions[question.index]?.multiSelect
5159
+ memoryEditorRequest !== null ? (_jsx(ExternalEditorWait, { screenReader: axScreenReader })) : permission ? (permission.kind === 'tool' && toolPermissionModel ? (_jsx(ToolPermissionDialog, { model: toolPermissionModel, selection: permissionSelection, feedbackMode: permissionFeedbackMode, feedback: input, ruleEditor: permissionRuleEditor, screenReader: axScreenReader })) : (_jsxs(DialogFrame, { title: `Retry interrupted ${permission.call.name}?`, screenReader: axScreenReader, children: [_jsx(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: _jsx(Text, { bold: true, children: describeTool(permission.call, sensitiveValues) }) }), _jsx(Text, { children: "Do you want to proceed?" }), _jsxs(Text, { inverse: !axScreenReader && permissionSelection === 0, children: [selectionPrefix(permissionSelection === 0, axScreenReader), "1. Yes"] }), _jsxs(Text, { inverse: !axScreenReader && permissionSelection === 1, children: [selectionPrefix(permissionSelection === 1, axScreenReader), "2. No"] }), permissionFeedbackMode ? (_jsxs(Text, { children: ["\u203A", ' ', input ||
5160
+ (permissionSelection === 0
5161
+ ? 'tell Praxis what to do next'
5162
+ : 'tell Praxis what to do differently')] })) : null, _jsx(Text, { dimColor: true, children: permissionFeedbackMode
5163
+ ? 'Enter to submit · Tab to collapse · Esc to cancel'
5164
+ : 'Enter to confirm · Tab to add feedback · Esc to cancel' })] }))) : planApproval ? (_jsxs(DialogFrame, { title: "Ready to code?", screenReader: axScreenReader, children: [_jsx(Text, { children: "Here is Praxis's plan:" }), planApproval.request.plan ? (_jsx(Box, { borderStyle: axScreenReader ? undefined : 'classic', borderLeft: false, borderRight: false, paddingX: axScreenReader ? 0 : 1, marginY: 1, children: _jsx(Text, { children: planApproval.request.plan }) })) : null, _jsx(Text, { dimColor: true, children: "Praxis has written up a plan and is ready to execute. Would you like to proceed?" }), _jsxs(Text, { inverse: !axScreenReader && planApprovalSelection === 0, children: [selectionPrefix(planApprovalSelection === 0, axScreenReader), "1.", ' ', runtimeSettings.useAutoModeDuringPlan
5165
+ ? 'Yes, and use auto mode'
5166
+ : allowDangerouslySkipPermissions
5167
+ ? 'Yes, and bypass permissions'
5168
+ : 'Yes, auto-accept edits'] }), _jsxs(Text, { inverse: !axScreenReader && planApprovalSelection === 1, children: [selectionPrefix(planApprovalSelection === 1, axScreenReader), "2. Yes, manually approve edits"] }), _jsxs(Text, { inverse: !axScreenReader && planApprovalSelection === 2, children: [selectionPrefix(planApprovalSelection === 2, axScreenReader), "3. No, keep planning"] }), planApprovalFeedbackMode ? (_jsxs(Text, { children: ["\u203A", ' ', input ||
5169
+ (planApprovalSelection === 2
5170
+ ? 'Tell Praxis what to change'
5171
+ : 'Add feedback for implementation')] })) : null, _jsx(Text, { dimColor: true, children: planApproval.request.planPath }), _jsx(Text, { dimColor: true, children: planApprovalFeedbackMode
5172
+ ? 'Enter to submit · Tab to collapse · Esc to cancel'
5173
+ : 'Enter to confirm · Tab to add feedback · Esc to cancel' })] })) : question ? (_jsxs(DialogFrame, { title: `${question.questions[question.index]?.header}: ${question.questions[question.index]?.question}`, screenReader: axScreenReader, children: [question.questions[question.index]?.options.map((option, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [index + 1, ". ", option.label, " \u2014 ", option.description] }), option.preview ? (_jsx(Text, { dimColor: true, children: option.preview })) : null] }, `${index}-${option.label}`))), _jsxs(Text, { children: [axScreenReader ? 'Current answer: ' : '› ', input || (axScreenReader ? '(empty)' : '')] }), _jsx(Text, { dimColor: true, children: question.questions[question.index]?.multiSelect
4606
5174
  ? 'Enter comma-separated option numbers or custom text · Esc cancels'
4607
- : 'Enter one option number or custom text · Esc cancels' })] })) : elicitation ? (_jsxs(DialogFrame, { title: `MCP elicitation (${elicitation.request.serverName})`, screenReader: axScreenReader, children: [_jsx(Text, { children: elicitation.request.message }), elicitation.request.url ? (_jsx(Text, { children: elicitation.request.url })) : null, elicitation.request.requestedSchema ? (_jsx(Text, { dimColor: true, children: JSON.stringify(elicitation.request.requestedSchema) })) : null, _jsxs(Text, { children: ["\u203A ", input] }), _jsx(Text, { dimColor: true, children: "Enter JSON object to accept \u00B7 Esc to cancel" })] })) : menu?.kind === 'model-input' ? (_jsxs(DialogFrame, { title: "Enter model ID", screenReader: axScreenReader, children: [_jsx(Text, { dimColor: true, children: "Enter a model ID supported by the configured provider." }), _jsxs(Text, { children: ["\u203A ", input] }), _jsx(Text, { dimColor: true, children: "Enter confirms \u00B7 Esc cancels" })] })) : menu?.kind === 'export-filename' ? (_jsxs(DialogFrame, { title: "Enter filename:", screenReader: axScreenReader, children: [_jsxs(Text, { children: ["> ", input] }), _jsx(Text, { dimColor: true, children: "Enter to save \u00B7 Esc to go back" })] })) : menu?.kind === 'compact-progress' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "\u273B Compacting conversation\u2026" }), _jsxs(Text, { children: ['▰'.repeat(Math.floor(compactProgress / 2)), '▱'.repeat(50 - Math.floor(compactProgress / 2)), ' ', compactProgress, "%"] })] })) : menu?.kind === 'btw' ? (_jsx(BtwPanel, { entries: btwHistory, selectedIndex: menu.selectedIndex, scrollOffset: menu.scrollOffset, copied: btwCopied, width: width, screenReader: axScreenReader })) : menu?.kind === 'rewind' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: " Rewind" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [' ', "Restore the code and/or conversation to the point before\u2026"] }), _jsx(Text, { children: " " }), (() => {
5175
+ : 'Enter one option number or custom text · Esc cancels' })] })) : elicitation ? (elicitation.request.mode === 'url' ? (_jsx(McpElicitationUrl, { serverName: elicitation.request.serverName, message: elicitation.request.message, url: elicitation.request.url ?? '', waiting: elicitationUrlWaiting, actionLabel: elicitation.request.elicitationId
5176
+ ? 'Skip confirmation'
5177
+ : 'Continue without waiting', selection: elicitationForm?.focusIndex ?? 0, screenReader: axScreenReader })) : (_jsx(McpElicitationForm, { serverName: elicitation.request.serverName, message: elicitation.request.message, state: elicitationForm ??
5178
+ createTuiElicitationForm(elicitation.request.requestedSchema), input: input, screenReader: axScreenReader }))) : menu?.kind === 'model-input' ? (_jsxs(DialogFrame, { title: "Enter model ID", screenReader: axScreenReader, children: [_jsx(Text, { dimColor: true, children: "Enter a model ID supported by the configured provider." }), _jsxs(Text, { children: ["\u203A ", input] }), _jsx(Text, { dimColor: true, children: "Enter confirms \u00B7 Esc cancels" })] })) : menu?.kind === 'export-filename' ? (_jsxs(DialogFrame, { title: "Enter filename:", screenReader: axScreenReader, children: [_jsxs(Text, { children: ["> ", input] }), _jsx(Text, { dimColor: true, children: "Enter to save \u00B7 Esc to go back" })] })) : menu?.kind === 'compact-progress' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "\u273B Compacting conversation\u2026" }), _jsxs(Text, { children: ['▰'.repeat(Math.floor(compactProgress / 2)), '▱'.repeat(50 - Math.floor(compactProgress / 2)), ' ', compactProgress, "%"] })] })) : menu?.kind === 'btw' ? (_jsx(BtwPanel, { entries: btwHistory, selectedIndex: menu.selectedIndex, scrollOffset: menu.scrollOffset, copied: btwCopied, width: width, screenReader: axScreenReader })) : menu?.kind === 'rewind' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: " Rewind" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [' ', "Restore the code and/or conversation to the point before\u2026"] }), _jsx(Text, { children: " " }), (() => {
4608
5179
  const window = rewindPointWindow(menu.points, menu.selectedIndex);
4609
5180
  return (_jsxs(_Fragment, { children: [window.start > 0 ? (_jsxs(Text, { dimColor: true, children: [" \u2191 ", window.start, " more above"] })) : null, menu.points
4610
5181
  .slice(window.start, window.end)
@@ -4641,27 +5212,21 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
4641
5212
  label: 'User settings',
4642
5213
  description: 'Saved in at ~/.claude/settings.json',
4643
5214
  },
4644
- ], selectedIndex: menu.selectedIndex, footer: "Enter to confirm \u00B7 Esc to cancel", width: width, screenReader: axScreenReader })) : menu.kind === 'status' ? (_jsx(StatusDashboard, { tabIndex: menu.tabIndex, version: runtimeDisplay.version, sessionId: sessionId, display: runtimeDisplay, ...(usage === undefined ? {} : { usage }), ...(costUsd === undefined ? {} : { costUsd }), turnCount: turnNumberRef.current, toolCount: history.filter((item) => item.kind === 'tool').length, commandCount: allSlashCommands.length, detailedTranscript: thinkingExpanded, width: width, screenReader: axScreenReader })) : menu.kind === 'copy' ? (_jsx(SelectionMenu, { title: "Copy response", description: "Choose the response content to copy.", options: menu.candidates.map((candidate) => ({
4645
- label: candidate.label,
4646
- description: candidate.description,
4647
- })), selectedIndex: menu.selectedIndex, footer: "\u2191/\u2193 select \u00B7 Enter copies \u00B7 Esc cancels", width: width, screenReader: axScreenReader })) : menu.kind === 'agents' ? (_jsx(ListDashboard, { title: "Agents", rows: menu.agents.map((agent) => ({
5215
+ ], selectedIndex: menu.selectedIndex, footer: "Enter to confirm \u00B7 Esc to cancel", width: width, screenReader: axScreenReader })) : menu.kind === 'agents' ? (_jsx(ListDashboard, { title: "Agents", rows: menu.agents.map((agent) => ({
4648
5216
  label: agent.name,
4649
5217
  description: agent.description,
4650
- })), emptyText: "No agents configured", selectedIndex: menu.selectedIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'config' ? (_jsx(ConfigDashboard, { tab: menu.tab, snapshot: menu.snapshot, query: menu.query, selectedIndex: menu.selectedIndex, searchFocused: menu.searchFocused, settings: [
4651
- {
4652
- label: 'Provider',
4653
- value: runtimeDisplay.model ?? 'default',
4654
- },
4655
- {
4656
- label: 'Permission mode',
4657
- value: runtimeDisplay.permissionMode ?? 'default',
4658
- },
4659
- ], status: {
5218
+ })), emptyText: "No agents configured", selectedIndex: menu.selectedIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'config' ? (_jsx(ConfigDashboard, { tab: menu.tab, snapshot: menu.snapshot, query: menu.query, selectedIndex: menu.selectedIndex, searchFocused: menu.searchFocused, status: {
4660
5219
  version: runtimeDisplay.version,
5220
+ ...(sessionName ? { sessionName } : {}),
4661
5221
  sessionId: sessionId ?? 'new',
4662
5222
  cwd: runtimeCwd,
5223
+ ...(statusAuthSource
5224
+ ? { authSource: statusAuthSource }
5225
+ : {}),
5226
+ ...(statusBaseUrl ? { baseUrl: statusBaseUrl } : {}),
5227
+ ...(statusProxy ? { proxy: statusProxy } : {}),
4663
5228
  model: runtimeDisplay.model ?? 'default',
4664
- settingSources: ['user', 'project', 'local'],
5229
+ settingSources: statusSettingSources.split(', '),
4665
5230
  }, usage: {
4666
5231
  costUsd: costUsd ?? 0,
4667
5232
  apiDurationMs: 0,
@@ -4669,7 +5234,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
4669
5234
  linesAdded: 0,
4670
5235
  linesRemoved: 0,
4671
5236
  usage: usage ?? { inputTokens: 0, outputTokens: 0 },
4672
- }, stats: [], width: width, screenReader: axScreenReader })) : menu.kind === 'mcp' ? (_jsx(McpPanel, { model: menu.model, state: menu.state, width: width, screenReader: axScreenReader })) : menu.kind === 'tasks' ? (_jsx(TaskPanel, { tasks: menu.tasks, state: menu.state, width: width, screenReader: axScreenReader })) : menu.kind === 'memory' ? (_jsx(MemoryDashboard, { autoMemoryEnabled: menu.autoMemoryEnabled, entries: menu.entries, selectedIndex: menu.selectedIndex, openedIndex: menu.openedIndex, loading: menu.loading, width: width, screenReader: axScreenReader })) : menu.kind === 'hooks' ? (_jsx(HookDashboard, { configuration: menu.configuration, depth: menu.depth, eventIndex: menu.eventIndex, matcherIndex: menu.matcherIndex, hookIndex: menu.hookIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'list' ? (_jsx(ListDashboard, { title: menu.title, rows: menu.rows, emptyText: menu.emptyText, selectedIndex: menu.selectedIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'model' ? (_jsx(SelectionMenu, { title: "Select model", description: `Effort: ${runtimePreferences.effort}`, options: modelOptions, selectedIndex: menu.selectedIndex, footer: "\u2191/\u2193 select \u00B7 \u2190/\u2192 effort \u00B7 Enter applies to this session \u00B7 Esc cancels", width: width, screenReader: axScreenReader })) : menu.kind === 'effort' ? (_jsx(SelectionMenu, { title: "Select effort", description: "Controls how much reasoning effort the provider should use.", options: EFFORT_OPTIONS.map((option) => ({
5237
+ }, width: width, screenReader: axScreenReader })) : menu.kind === 'mcp' ? (_jsx(McpPanel, { model: menu.model, state: menu.state, width: width, screenReader: axScreenReader })) : menu.kind === 'tasks' ? (_jsx(TaskPanel, { tasks: menu.tasks, state: menu.state, width: width, screenReader: axScreenReader })) : menu.kind === 'memory' ? (_jsx(MemoryDashboard, { autoMemoryEnabled: menu.autoMemoryEnabled, entries: menu.entries, selectedIndex: menu.selectedIndex, openedIndex: menu.openedIndex, loading: menu.loading, width: width, screenReader: axScreenReader })) : menu.kind === 'hooks' ? (_jsx(HookDashboard, { configuration: menu.configuration, depth: menu.depth, eventIndex: menu.eventIndex, matcherIndex: menu.matcherIndex, hookIndex: menu.hookIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'list' ? (_jsx(ListDashboard, { title: menu.title, rows: menu.rows, emptyText: menu.emptyText, selectedIndex: menu.selectedIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'model' ? (_jsx(ModelMenu, { options: modelOptions, effort: runtimePreferences.effort, selectedIndex: menu.selectedIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'effort' ? (_jsx(SelectionMenu, { title: "Select effort", description: "Controls how much reasoning effort the provider should use.", options: EFFORT_OPTIONS.map((option) => ({
4673
5238
  label: option,
4674
5239
  description: option === 'low'
4675
5240
  ? 'Fastest and least deliberative.'
@@ -4686,7 +5251,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
4686
5251
  label: 'Save to file',
4687
5252
  description: 'Save the conversation to a file in the current directory',
4688
5253
  },
4689
- ], selectedIndex: menu.selectedIndex, footer: "Esc to cancel", width: width, screenReader: axScreenReader })) : null) : (_jsxs(_Fragment, { children: [commandPaletteVisible ? (_jsx(CommandPalette, { commands: matchingSlashCommands, selectedIndex: selectedSlashCommandIndex, width: width, screenReader: axScreenReader })) : null, filePickerVisible ? (_jsx(MentionPicker, { entries: matchingMentionEntries, selectedIndex: selectedFileIndex, width: width, screenReader: axScreenReader })) : null, exitConfirmation ? (_jsx(Text, { color: activePalette.warning, children: "Press Ctrl-C again to exit" })) : null, _jsx(Composer, { input: shellMode ? input.slice(1) : input, cursor: shellMode ? Math.max(0, inputCursor - 1) : inputCursor, shellMode: shellMode, busy: busy, clipboardBusy: clipboardBusy, status: status, display: runtimeDisplay, ...(usage === undefined ? {} : { usage }), ...(costUsd === undefined ? {} : { costUsd }), width: width, screenReader: axScreenReader, hasThinking: hasDetailedTranscript, thinkingExpanded: thinkingExpanded, reduceMotion: runtimeSettings.reduceMotion, progressBar: runtimeSettings.progressBar, ...(runtimeSettings.turnDuration
5254
+ ], selectedIndex: menu.selectedIndex, footer: "Esc to cancel", width: width, screenReader: axScreenReader })) : menu.kind === 'copy' ? (_jsx(SelectionMenu, { title: "Copy", description: "Select content to copy:", options: menu.candidates, selectedIndex: menu.selectedIndex, footer: "Enter to copy \u00B7 w to write to /tmp/claude \u00B7 Esc to cancel", width: width, screenReader: axScreenReader })) : null) : (_jsxs(_Fragment, { children: [commandPaletteVisible ? (_jsx(CommandPalette, { commands: matchingSlashCommands, selectedIndex: selectedSlashCommandIndex, width: width, screenReader: axScreenReader })) : null, filePickerVisible ? (_jsx(MentionPicker, { entries: matchingMentionEntries, selectedIndex: selectedFileIndex, width: width, screenReader: axScreenReader })) : null, exitConfirmation ? (_jsx(Text, { color: activePalette.warning, children: "Press Ctrl-C again to exit" })) : null, _jsx(Composer, { input: shellMode ? input.slice(1) : input, cursor: shellMode ? Math.max(0, inputCursor - 1) : inputCursor, shellMode: shellMode, busy: busy, clipboardBusy: clipboardBusy, status: status, display: runtimeDisplay, ...(usage === undefined ? {} : { usage }), ...(costUsd === undefined ? {} : { costUsd }), width: width, screenReader: axScreenReader, hasThinking: hasDetailedTranscript, thinkingExpanded: thinkingExpanded, reduceMotion: runtimeSettings.reduceMotion, progressBar: runtimeSettings.progressBar, ...(runtimeSettings.turnDuration
4690
5255
  ? (() => {
4691
5256
  const duration = formatTurnDuration(turnDuration);
4692
5257
  return duration === undefined