ccmanager 2.11.6 โ†’ 3.0.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 (34) hide show
  1. package/dist/components/Configuration.js +14 -0
  2. package/dist/components/ConfigureCustomCommand.d.ts +9 -0
  3. package/dist/components/ConfigureCustomCommand.js +44 -0
  4. package/dist/components/ConfigureOther.d.ts +6 -0
  5. package/dist/components/ConfigureOther.js +87 -0
  6. package/dist/components/ConfigureOther.test.d.ts +1 -0
  7. package/dist/components/ConfigureOther.test.js +80 -0
  8. package/dist/components/ConfigureStatusHooks.js +7 -1
  9. package/dist/components/CustomCommandSummary.d.ts +6 -0
  10. package/dist/components/CustomCommandSummary.js +10 -0
  11. package/dist/components/Menu.recent-projects.test.js +2 -0
  12. package/dist/components/Menu.test.js +2 -0
  13. package/dist/components/Session.d.ts +2 -2
  14. package/dist/components/Session.js +67 -4
  15. package/dist/constants/statusIcons.d.ts +3 -1
  16. package/dist/constants/statusIcons.js +3 -0
  17. package/dist/services/autoApprovalVerifier.d.ts +25 -0
  18. package/dist/services/autoApprovalVerifier.js +265 -0
  19. package/dist/services/autoApprovalVerifier.test.d.ts +1 -0
  20. package/dist/services/autoApprovalVerifier.test.js +120 -0
  21. package/dist/services/configurationManager.d.ts +7 -0
  22. package/dist/services/configurationManager.js +35 -0
  23. package/dist/services/sessionManager.autoApproval.test.d.ts +1 -0
  24. package/dist/services/sessionManager.autoApproval.test.js +160 -0
  25. package/dist/services/sessionManager.d.ts +5 -0
  26. package/dist/services/sessionManager.js +149 -1
  27. package/dist/services/sessionManager.statePersistence.test.js +2 -0
  28. package/dist/services/sessionManager.test.js +6 -0
  29. package/dist/types/index.d.ts +14 -1
  30. package/dist/utils/hookExecutor.test.js +8 -0
  31. package/dist/utils/logger.d.ts +83 -14
  32. package/dist/utils/logger.js +218 -17
  33. package/dist/utils/worktreeUtils.test.js +1 -0
  34. package/package.json +1 -1
@@ -6,6 +6,7 @@ import ConfigureStatusHooks from './ConfigureStatusHooks.js';
6
6
  import ConfigureWorktreeHooks from './ConfigureWorktreeHooks.js';
7
7
  import ConfigureWorktree from './ConfigureWorktree.js';
8
8
  import ConfigureCommand from './ConfigureCommand.js';
9
+ import ConfigureOther from './ConfigureOther.js';
9
10
  import { shortcutManager } from '../services/shortcutManager.js';
10
11
  const Configuration = ({ onComplete }) => {
11
12
  const [view, setView] = useState('menu');
@@ -30,6 +31,10 @@ const Configuration = ({ onComplete }) => {
30
31
  label: 'C ๐Ÿš€ Configure Command Presets',
31
32
  value: 'presets',
32
33
  },
34
+ {
35
+ label: 'O ๐Ÿงช Other & Experimental',
36
+ value: 'other',
37
+ },
33
38
  {
34
39
  label: 'B โ† Back to Main Menu',
35
40
  value: 'back',
@@ -54,6 +59,9 @@ const Configuration = ({ onComplete }) => {
54
59
  else if (item.value === 'presets') {
55
60
  setView('presets');
56
61
  }
62
+ else if (item.value === 'other') {
63
+ setView('other');
64
+ }
57
65
  };
58
66
  const handleSubMenuComplete = () => {
59
67
  setView('menu');
@@ -79,6 +87,9 @@ const Configuration = ({ onComplete }) => {
79
87
  case 'c':
80
88
  setView('presets');
81
89
  break;
90
+ case 'o':
91
+ setView('other');
92
+ break;
82
93
  case 'b':
83
94
  onComplete();
84
95
  break;
@@ -103,6 +114,9 @@ const Configuration = ({ onComplete }) => {
103
114
  if (view === 'presets') {
104
115
  return React.createElement(ConfigureCommand, { onComplete: handleSubMenuComplete });
105
116
  }
117
+ if (view === 'other') {
118
+ return React.createElement(ConfigureOther, { onComplete: handleSubMenuComplete });
119
+ }
106
120
  return (React.createElement(Box, { flexDirection: "column" },
107
121
  React.createElement(Box, { marginBottom: 1 },
108
122
  React.createElement(Text, { bold: true, color: "green" }, "Configuration")),
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ interface ConfigureCustomCommandProps {
3
+ value: string;
4
+ onChange: (value: string) => void;
5
+ onSubmit: (value: string) => void;
6
+ onCancel: () => void;
7
+ }
8
+ declare const ConfigureCustomCommand: React.FC<ConfigureCustomCommandProps>;
9
+ export default ConfigureCustomCommand;
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import TextInputWrapper from './TextInputWrapper.js';
4
+ import { shortcutManager } from '../services/shortcutManager.js';
5
+ const ConfigureCustomCommand = ({ value, onChange, onSubmit, onCancel, }) => {
6
+ const shouldIgnoreNextChange = React.useRef(false);
7
+ const handleChange = (newValue) => {
8
+ if (shouldIgnoreNextChange.current) {
9
+ shouldIgnoreNextChange.current = false;
10
+ return;
11
+ }
12
+ onChange(newValue);
13
+ };
14
+ useInput((input, key) => {
15
+ if (shortcutManager.matchesShortcut('cancel', input, key)) {
16
+ onCancel();
17
+ return;
18
+ }
19
+ // Ctrl+K clears the current input
20
+ if (key.ctrl && input.toLowerCase() === 'k') {
21
+ // Ignore the TextInput change event that will fire for the same key
22
+ shouldIgnoreNextChange.current = true;
23
+ onChange('');
24
+ }
25
+ });
26
+ return (React.createElement(Box, { flexDirection: "column" },
27
+ React.createElement(Box, { marginBottom: 1 },
28
+ React.createElement(Text, { bold: true, color: "green" }, "Custom Auto-Approval Command")),
29
+ React.createElement(Box, { marginBottom: 1 },
30
+ React.createElement(Text, null,
31
+ "Enter the command that returns ",
32
+ '{needsPermission:boolean}',
33
+ " JSON:")),
34
+ React.createElement(Box, { marginBottom: 1 },
35
+ React.createElement(TextInputWrapper, { value: value, onChange: handleChange, onSubmit: () => onSubmit(value), placeholder: `e.g. jq -n '{"needsPermission":true}'`, focus: true })),
36
+ React.createElement(Box, { marginBottom: 1 },
37
+ React.createElement(Text, { dimColor: true }, "Env provided: $DEFAULT_PROMPT, $TERMINAL_OUTPUT")),
38
+ React.createElement(Box, null,
39
+ React.createElement(Text, { dimColor: true },
40
+ "Press Enter to save, ",
41
+ shortcutManager.getShortcutDisplay('cancel'),
42
+ " to go back, Ctrl+K to clear input"))));
43
+ };
44
+ export default ConfigureCustomCommand;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface ConfigureOtherProps {
3
+ onComplete: () => void;
4
+ }
5
+ declare const ConfigureOther: React.FC<ConfigureOtherProps>;
6
+ export default ConfigureOther;
@@ -0,0 +1,87 @@
1
+ import React, { useState } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import SelectInput from 'ink-select-input';
4
+ import { configurationManager } from '../services/configurationManager.js';
5
+ import { shortcutManager } from '../services/shortcutManager.js';
6
+ import ConfigureCustomCommand from './ConfigureCustomCommand.js';
7
+ import CustomCommandSummary from './CustomCommandSummary.js';
8
+ const ConfigureOther = ({ onComplete }) => {
9
+ const autoApprovalConfig = configurationManager.getAutoApprovalConfig();
10
+ const [view, setView] = useState('main');
11
+ const [autoApprovalEnabled, setAutoApprovalEnabled] = useState(autoApprovalConfig.enabled);
12
+ const [customCommand, setCustomCommand] = useState(autoApprovalConfig.customCommand ?? '');
13
+ const [customCommandDraft, setCustomCommandDraft] = useState(customCommand);
14
+ useInput((input, key) => {
15
+ if (shortcutManager.matchesShortcut('cancel', input, key)) {
16
+ if (view === 'customCommand') {
17
+ setCustomCommandDraft(customCommand);
18
+ setView('main');
19
+ return;
20
+ }
21
+ onComplete();
22
+ }
23
+ });
24
+ const menuItems = [
25
+ {
26
+ label: `Auto Approval (experimental): ${autoApprovalEnabled ? 'โœ… Enabled' : 'โŒ Disabled'}`,
27
+ value: 'toggleAutoApproval',
28
+ },
29
+ {
30
+ label: 'โœ๏ธ Edit Custom Command',
31
+ value: 'customCommand',
32
+ },
33
+ {
34
+ label: '๐Ÿ’พ Save Changes',
35
+ value: 'save',
36
+ },
37
+ {
38
+ label: 'โ† Cancel',
39
+ value: 'cancel',
40
+ },
41
+ ];
42
+ const handleSelect = (item) => {
43
+ switch (item.value) {
44
+ case 'toggleAutoApproval':
45
+ setAutoApprovalEnabled(!autoApprovalEnabled);
46
+ break;
47
+ case 'customCommand':
48
+ setCustomCommandDraft(customCommand);
49
+ setView('customCommand');
50
+ break;
51
+ case 'save':
52
+ configurationManager.setAutoApprovalConfig({
53
+ enabled: autoApprovalEnabled,
54
+ customCommand: customCommand.trim() || undefined,
55
+ });
56
+ onComplete();
57
+ break;
58
+ case 'cancel':
59
+ onComplete();
60
+ break;
61
+ default:
62
+ break;
63
+ }
64
+ };
65
+ if (view === 'customCommand') {
66
+ return (React.createElement(ConfigureCustomCommand, { value: customCommandDraft, onChange: setCustomCommandDraft, onCancel: () => {
67
+ setCustomCommandDraft(customCommand);
68
+ setView('main');
69
+ }, onSubmit: value => {
70
+ setCustomCommand(value);
71
+ setView('main');
72
+ } }));
73
+ }
74
+ return (React.createElement(Box, { flexDirection: "column" },
75
+ React.createElement(Box, { marginBottom: 1 },
76
+ React.createElement(Text, { bold: true, color: "green" }, "Other & Experimental Settings")),
77
+ React.createElement(Box, { marginBottom: 1 },
78
+ React.createElement(Text, { dimColor: true }, "Toggle experimental capabilities and other miscellaneous options.")),
79
+ React.createElement(CustomCommandSummary, { command: customCommand }),
80
+ React.createElement(SelectInput, { items: menuItems, onSelect: handleSelect, isFocused: true }),
81
+ React.createElement(Box, { marginTop: 1 },
82
+ React.createElement(Text, { dimColor: true },
83
+ "Press ",
84
+ shortcutManager.getShortcutDisplay('cancel'),
85
+ " to return without saving"))));
86
+ };
87
+ export default ConfigureOther;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,80 @@
1
+ import React from 'react';
2
+ import { render } from 'ink-testing-library';
3
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
4
+ import ConfigureOther from './ConfigureOther.js';
5
+ import { configurationManager } from '../services/configurationManager.js';
6
+ // Mock ink to avoid stdin issues during tests
7
+ vi.mock('ink', async () => {
8
+ const actual = await vi.importActual('ink');
9
+ return {
10
+ ...actual,
11
+ useInput: vi.fn(),
12
+ };
13
+ });
14
+ // Mock SelectInput to render labels directly
15
+ vi.mock('ink-select-input', async () => {
16
+ const React = await vi.importActual('react');
17
+ const { Text, Box } = await vi.importActual('ink');
18
+ return {
19
+ default: ({ items }) => React.createElement(Box, { flexDirection: 'column' }, items.map((item, index) => React.createElement(Text, { key: index }, item.label))),
20
+ };
21
+ });
22
+ vi.mock('../services/configurationManager.js', () => ({
23
+ configurationManager: {
24
+ getAutoApprovalConfig: vi.fn(),
25
+ setAutoApprovalConfig: vi.fn(),
26
+ },
27
+ }));
28
+ vi.mock('../services/shortcutManager.js', () => ({
29
+ shortcutManager: {
30
+ matchesShortcut: vi.fn().mockReturnValue(false),
31
+ getShortcutDisplay: vi.fn().mockReturnValue('Esc'),
32
+ },
33
+ }));
34
+ vi.mock('./TextInputWrapper.js', async () => {
35
+ const React = await vi.importActual('react');
36
+ return {
37
+ default: ({ value }) => React.createElement('input', { value, 'data-testid': 'text-input' }),
38
+ };
39
+ });
40
+ vi.mock('./ConfigureCustomCommand.js', async () => {
41
+ const React = await vi.importActual('react');
42
+ return {
43
+ default: () => React.createElement('div', { 'data-testid': 'custom-command-editor' }),
44
+ };
45
+ });
46
+ vi.mock('./CustomCommandSummary.js', async () => {
47
+ const React = await vi.importActual('react');
48
+ const { Text } = await vi.importActual('ink');
49
+ return {
50
+ default: ({ command }) => React.createElement(Text, null, `Custom auto-approval command: ${command || 'Empty'}`),
51
+ };
52
+ });
53
+ const mockedConfigurationManager = configurationManager;
54
+ describe('ConfigureOther', () => {
55
+ beforeEach(() => {
56
+ vi.clearAllMocks();
57
+ });
58
+ it('renders experimental settings with auto-approval status', () => {
59
+ mockedConfigurationManager.getAutoApprovalConfig.mockReturnValue({
60
+ enabled: true,
61
+ customCommand: '',
62
+ });
63
+ const { lastFrame } = render(React.createElement(ConfigureOther, { onComplete: vi.fn() }));
64
+ expect(lastFrame()).toContain('Other & Experimental Settings');
65
+ expect(lastFrame()).toContain('Auto Approval (experimental): โœ… Enabled');
66
+ expect(lastFrame()).toContain('Custom auto-approval command: Empty');
67
+ expect(lastFrame()).toContain('Edit Custom Command');
68
+ expect(lastFrame()).toContain('Save Changes');
69
+ });
70
+ it('shows current custom command summary', () => {
71
+ mockedConfigurationManager.getAutoApprovalConfig.mockReturnValue({
72
+ enabled: false,
73
+ customCommand: 'jq -n \'{"needsPermission":true}\'',
74
+ });
75
+ const { lastFrame } = render(React.createElement(ConfigureOther, { onComplete: vi.fn() }));
76
+ expect(lastFrame()).toContain('Custom auto-approval command:');
77
+ expect(lastFrame()).toContain('jq -n');
78
+ expect(lastFrame()).toContain('Edit Custom Command');
79
+ });
80
+ });
@@ -7,6 +7,7 @@ const STATUS_LABELS = {
7
7
  idle: 'Idle',
8
8
  busy: 'Busy',
9
9
  waiting_input: 'Waiting for Input',
10
+ pending_auto_approval: 'Pending Auto Approval',
10
11
  };
11
12
  const ConfigureStatusHooks = ({ onComplete, }) => {
12
13
  const [view, setView] = useState('menu');
@@ -31,7 +32,12 @@ const ConfigureStatusHooks = ({ onComplete, }) => {
31
32
  const getMenuItems = () => {
32
33
  const items = [];
33
34
  // Add status hook items
34
- ['idle', 'busy', 'waiting_input'].forEach(status => {
35
+ [
36
+ 'idle',
37
+ 'busy',
38
+ 'waiting_input',
39
+ 'pending_auto_approval',
40
+ ].forEach(status => {
35
41
  const hook = statusHooks[status];
36
42
  const enabled = hook?.enabled ? 'โœ“' : 'โœ—';
37
43
  const command = hook?.command || '(not set)';
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface CustomCommandSummaryProps {
3
+ command: string;
4
+ }
5
+ declare const CustomCommandSummary: React.FC<CustomCommandSummaryProps>;
6
+ export default CustomCommandSummary;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ const CustomCommandSummary = ({ command, }) => {
4
+ const displayValue = command.trim() ? command : 'Empty';
5
+ return (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
6
+ React.createElement(Text, null, "Custom auto-approval command:"),
7
+ React.createElement(Text, { dimColor: true }, displayValue),
8
+ React.createElement(Text, { dimColor: true }, "Env provided: $DEFAULT_PROMPT, $TERMINAL_OUTPUT")));
9
+ };
10
+ export default CustomCommandSummary;
@@ -99,6 +99,7 @@ describe('Menu - Recent Projects', () => {
99
99
  idle: 0,
100
100
  busy: 0,
101
101
  waiting_input: 0,
102
+ pending_auto_approval: 0,
102
103
  total: 0,
103
104
  });
104
105
  vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
@@ -128,6 +129,7 @@ describe('Menu - Recent Projects', () => {
128
129
  idle: 0,
129
130
  busy: 0,
130
131
  waiting_input: 0,
132
+ pending_auto_approval: 0,
131
133
  total: 0,
132
134
  });
133
135
  vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
@@ -272,6 +272,7 @@ describe('Menu component rendering', () => {
272
272
  idle: 0,
273
273
  busy: 0,
274
274
  waiting_input: 0,
275
+ pending_auto_approval: 0,
275
276
  total: 0,
276
277
  });
277
278
  vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
@@ -312,6 +313,7 @@ describe('Menu component rendering', () => {
312
313
  idle: 0,
313
314
  busy: 0,
314
315
  waiting_input: 0,
316
+ pending_auto_approval: 0,
315
317
  total: 0,
316
318
  });
317
319
  vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
- import { Session as SessionType } from '../types/index.js';
2
+ import { Session as ISession } from '../types/index.js';
3
3
  import { SessionManager } from '../services/sessionManager.js';
4
4
  interface SessionProps {
5
- session: SessionType;
5
+ session: ISession;
6
6
  sessionManager: SessionManager;
7
7
  onReturnToMenu: () => void;
8
8
  }
@@ -1,9 +1,67 @@
1
- import { useEffect, useState } from 'react';
2
- import { useStdout } from 'ink';
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import { Box, Text, useStdout } from 'ink';
3
3
  import { shortcutManager } from '../services/shortcutManager.js';
4
4
  const Session = ({ session, sessionManager, onReturnToMenu, }) => {
5
5
  const { stdout } = useStdout();
6
6
  const [isExiting, setIsExiting] = useState(false);
7
+ const deriveStatus = (currentSession) => {
8
+ // Always prioritize showing the manual approval notice when verification failed
9
+ if (currentSession.autoApprovalFailed) {
10
+ const reason = currentSession.autoApprovalReason
11
+ ? ` Reason: ${currentSession.autoApprovalReason}.`
12
+ : '';
13
+ return {
14
+ message: `Auto-approval failed.${reason} Manual approval requiredโ€”respond to the prompt.`,
15
+ variant: 'error',
16
+ };
17
+ }
18
+ if (currentSession.state === 'pending_auto_approval') {
19
+ return {
20
+ message: 'Auto-approval pending... verifying permissions (press any key to cancel)',
21
+ variant: 'pending',
22
+ };
23
+ }
24
+ return { message: null, variant: null };
25
+ };
26
+ const initialStatus = deriveStatus(session);
27
+ const [statusMessage, setStatusMessage] = useState(initialStatus.message);
28
+ const [statusVariant, setStatusVariant] = useState(initialStatus.variant);
29
+ const [columns, setColumns] = useState(() => stdout?.columns ?? process.stdout.columns ?? 80);
30
+ const { statusLineText, backgroundColor, textColor } = useMemo(() => {
31
+ if (!statusMessage || !statusVariant) {
32
+ return {
33
+ statusLineText: null,
34
+ backgroundColor: undefined,
35
+ textColor: undefined,
36
+ };
37
+ }
38
+ const maxContentWidth = Math.max(columns - 4, 0);
39
+ const prefix = statusVariant === 'error'
40
+ ? '[AUTO-APPROVAL REQUIRED]'
41
+ : '[AUTO-APPROVAL]';
42
+ const prefixed = `${prefix} ${statusMessage}`;
43
+ const trimmed = prefixed.length > maxContentWidth
44
+ ? prefixed.slice(0, maxContentWidth)
45
+ : prefixed;
46
+ return {
47
+ statusLineText: ` ${trimmed}`.padEnd(columns, ' '),
48
+ backgroundColor: statusVariant === 'error' ? '#d90429' : '#ffd166',
49
+ textColor: statusVariant === 'error' ? 'white' : '#1c1c1c',
50
+ };
51
+ }, [columns, statusMessage, statusVariant]);
52
+ useEffect(() => {
53
+ const handleSessionStateChange = (updatedSession) => {
54
+ if (updatedSession.id !== session.id)
55
+ return;
56
+ const { message, variant } = deriveStatus(updatedSession);
57
+ setStatusMessage(message);
58
+ setStatusVariant(variant);
59
+ };
60
+ sessionManager.on('sessionStateChanged', handleSessionStateChange);
61
+ return () => {
62
+ sessionManager.off('sessionStateChanged', handleSessionStateChange);
63
+ };
64
+ }, [session.id, sessionManager]);
7
65
  const stripOscColorSequences = (input) => {
8
66
  // Remove default foreground/background color OSC sequences that Codex emits
9
67
  // These sequences leak as literal text when replaying buffered output
@@ -91,6 +149,7 @@ const Session = ({ session, sessionManager, onReturnToMenu, }) => {
91
149
  const handleSessionExit = (exitedSession) => {
92
150
  if (exitedSession.id === session.id) {
93
151
  setIsExiting(true);
152
+ setStatusMessage(null);
94
153
  // Don't call onReturnToMenu here - App component handles it
95
154
  }
96
155
  };
@@ -100,6 +159,7 @@ const Session = ({ session, sessionManager, onReturnToMenu, }) => {
100
159
  const handleResize = () => {
101
160
  const cols = process.stdout.columns || 80;
102
161
  const rows = process.stdout.rows || 24;
162
+ setColumns(cols);
103
163
  session.process.resize(cols, rows);
104
164
  // Also resize the virtual terminal
105
165
  if (session.terminal) {
@@ -132,6 +192,9 @@ const Session = ({ session, sessionManager, onReturnToMenu, }) => {
132
192
  onReturnToMenu();
133
193
  return;
134
194
  }
195
+ if (session.state === 'pending_auto_approval') {
196
+ sessionManager.cancelAutoApproval(session.worktreePath, 'User input received during auto-approval');
197
+ }
135
198
  // Pass all other input directly to the PTY
136
199
  session.process.write(data);
137
200
  };
@@ -162,7 +225,7 @@ const Session = ({ session, sessionManager, onReturnToMenu, }) => {
162
225
  stdout.off('resize', handleResize);
163
226
  };
164
227
  }, [session, sessionManager, stdout, onReturnToMenu, isExiting]);
165
- // Return null to render nothing (PTY output goes directly to stdout)
166
- return null;
228
+ return statusLineText ? (React.createElement(Box, { width: "100%" },
229
+ React.createElement(Text, { backgroundColor: backgroundColor, color: textColor, bold: true }, statusLineText))) : null;
167
230
  };
168
231
  export default Session;
@@ -1,3 +1,4 @@
1
+ import { SessionState } from '../types/index.js';
1
2
  export declare const STATUS_ICONS: {
2
3
  readonly BUSY: "โ—";
3
4
  readonly WAITING: "โ—";
@@ -6,6 +7,7 @@ export declare const STATUS_ICONS: {
6
7
  export declare const STATUS_LABELS: {
7
8
  readonly BUSY: "Busy";
8
9
  readonly WAITING: "Waiting";
10
+ readonly PENDING_AUTO_APPROVAL: "Pending Auto Approval";
9
11
  readonly IDLE: "Idle";
10
12
  };
11
13
  export declare const MENU_ICONS: {
@@ -15,4 +17,4 @@ export declare const MENU_ICONS: {
15
17
  readonly CONFIGURE_SHORTCUTS: "โŒจ";
16
18
  readonly EXIT: "โป";
17
19
  };
18
- export declare const getStatusDisplay: (status: "busy" | "waiting_input" | "idle") => string;
20
+ export declare const getStatusDisplay: (status: SessionState) => string;
@@ -6,6 +6,7 @@ export const STATUS_ICONS = {
6
6
  export const STATUS_LABELS = {
7
7
  BUSY: 'Busy',
8
8
  WAITING: 'Waiting',
9
+ PENDING_AUTO_APPROVAL: 'Pending Auto Approval',
9
10
  IDLE: 'Idle',
10
11
  };
11
12
  export const MENU_ICONS = {
@@ -21,6 +22,8 @@ export const getStatusDisplay = (status) => {
21
22
  return `${STATUS_ICONS.BUSY} ${STATUS_LABELS.BUSY}`;
22
23
  case 'waiting_input':
23
24
  return `${STATUS_ICONS.WAITING} ${STATUS_LABELS.WAITING}`;
25
+ case 'pending_auto_approval':
26
+ return `${STATUS_ICONS.WAITING} ${STATUS_LABELS.PENDING_AUTO_APPROVAL}`;
24
27
  case 'idle':
25
28
  return `${STATUS_ICONS.IDLE} ${STATUS_LABELS.IDLE}`;
26
29
  }
@@ -0,0 +1,25 @@
1
+ import { Effect } from 'effect';
2
+ import { ProcessError } from '../types/errors.js';
3
+ import { AutoApprovalResponse } from '../types/index.js';
4
+ /**
5
+ * Service to verify if auto-approval should be granted for pending states
6
+ * Uses Claude Haiku model to analyze terminal output and determine if
7
+ * user permission is required before proceeding
8
+ */
9
+ export declare class AutoApprovalVerifier {
10
+ private readonly model;
11
+ private createExecOptions;
12
+ private runClaudePrompt;
13
+ private runCustomCommand;
14
+ /**
15
+ * Verify if the current terminal output requires user permission
16
+ * before proceeding with auto-approval
17
+ *
18
+ * @param terminalOutput - Current terminal output to analyze
19
+ * @returns Effect that resolves to true if permission needed, false if can auto-approve
20
+ */
21
+ verifyNeedsPermission(terminalOutput: string, options?: {
22
+ signal?: AbortSignal;
23
+ }): Effect.Effect<AutoApprovalResponse, ProcessError, never>;
24
+ }
25
+ export declare const autoApprovalVerifier: AutoApprovalVerifier;