gencode-ai 0.1.2 → 0.2.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.
- package/README.md +15 -17
- package/dist/agent/agent.d.ts +43 -0
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +107 -4
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/types.d.ts +20 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/checkpointing/checkpoint-manager.d.ts +87 -0
- package/dist/checkpointing/checkpoint-manager.d.ts.map +1 -0
- package/dist/checkpointing/checkpoint-manager.js +281 -0
- package/dist/checkpointing/checkpoint-manager.js.map +1 -0
- package/dist/checkpointing/index.d.ts +29 -0
- package/dist/checkpointing/index.d.ts.map +1 -0
- package/dist/checkpointing/index.js +29 -0
- package/dist/checkpointing/index.js.map +1 -0
- package/dist/checkpointing/types.d.ts +98 -0
- package/dist/checkpointing/types.d.ts.map +1 -0
- package/dist/checkpointing/types.js +7 -0
- package/dist/checkpointing/types.js.map +1 -0
- package/dist/cli/components/App.d.ts.map +1 -1
- package/dist/cli/components/App.js +193 -7
- package/dist/cli/components/App.js.map +1 -1
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
- package/dist/cli/components/CommandSuggestions.js +5 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -1
- package/dist/cli/components/Messages.d.ts +7 -1
- package/dist/cli/components/Messages.d.ts.map +1 -1
- package/dist/cli/components/Messages.js +28 -2
- package/dist/cli/components/Messages.js.map +1 -1
- package/dist/cli/components/ModeIndicator.d.ts +42 -0
- package/dist/cli/components/ModeIndicator.d.ts.map +1 -0
- package/dist/cli/components/ModeIndicator.js +52 -0
- package/dist/cli/components/ModeIndicator.js.map +1 -0
- package/dist/cli/components/PlanApproval.d.ts +36 -0
- package/dist/cli/components/PlanApproval.d.ts.map +1 -0
- package/dist/cli/components/PlanApproval.js +154 -0
- package/dist/cli/components/PlanApproval.js.map +1 -0
- package/dist/cli/components/QuestionPrompt.d.ts +23 -0
- package/dist/cli/components/QuestionPrompt.d.ts.map +1 -0
- package/dist/cli/components/QuestionPrompt.js +231 -0
- package/dist/cli/components/QuestionPrompt.js.map +1 -0
- package/dist/cli/components/index.d.ts +1 -0
- package/dist/cli/components/index.d.ts.map +1 -1
- package/dist/cli/components/index.js +1 -0
- package/dist/cli/components/index.js.map +1 -1
- package/dist/cli/components/theme.d.ts +9 -0
- package/dist/cli/components/theme.d.ts.map +1 -1
- package/dist/cli/components/theme.js +14 -1
- package/dist/cli/components/theme.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/permissions/types.d.ts.map +1 -1
- package/dist/permissions/types.js +2 -0
- package/dist/permissions/types.js.map +1 -1
- package/dist/planning/index.d.ts +13 -0
- package/dist/planning/index.d.ts.map +1 -0
- package/dist/planning/index.js +15 -0
- package/dist/planning/index.js.map +1 -0
- package/dist/planning/plan-file.d.ts +59 -0
- package/dist/planning/plan-file.d.ts.map +1 -0
- package/dist/planning/plan-file.js +278 -0
- package/dist/planning/plan-file.js.map +1 -0
- package/dist/planning/state.d.ts +127 -0
- package/dist/planning/state.d.ts.map +1 -0
- package/dist/planning/state.js +261 -0
- package/dist/planning/state.js.map +1 -0
- package/dist/planning/tools/enter-plan-mode.d.ts +25 -0
- package/dist/planning/tools/enter-plan-mode.d.ts.map +1 -0
- package/dist/planning/tools/enter-plan-mode.js +98 -0
- package/dist/planning/tools/enter-plan-mode.js.map +1 -0
- package/dist/planning/tools/exit-plan-mode.d.ts +24 -0
- package/dist/planning/tools/exit-plan-mode.d.ts.map +1 -0
- package/dist/planning/tools/exit-plan-mode.js +149 -0
- package/dist/planning/tools/exit-plan-mode.js.map +1 -0
- package/dist/planning/types.d.ts +100 -0
- package/dist/planning/types.d.ts.map +1 -0
- package/dist/planning/types.js +28 -0
- package/dist/planning/types.js.map +1 -0
- package/dist/pricing/calculator.d.ts +21 -0
- package/dist/pricing/calculator.d.ts.map +1 -0
- package/dist/pricing/calculator.js +59 -0
- package/dist/pricing/calculator.js.map +1 -0
- package/dist/pricing/index.d.ts +7 -0
- package/dist/pricing/index.d.ts.map +1 -0
- package/dist/pricing/index.js +7 -0
- package/dist/pricing/index.js.map +1 -0
- package/dist/pricing/models.d.ts +20 -0
- package/dist/pricing/models.d.ts.map +1 -0
- package/dist/pricing/models.js +322 -0
- package/dist/pricing/models.js.map +1 -0
- package/dist/pricing/types.d.ts +30 -0
- package/dist/pricing/types.d.ts.map +1 -0
- package/dist/pricing/types.js +5 -0
- package/dist/pricing/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +17 -10
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +21 -14
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +12 -8
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/types.d.ts +2 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/vertex-ai.d.ts.map +1 -1
- package/dist/providers/vertex-ai.js +17 -10
- package/dist/providers/vertex-ai.js.map +1 -1
- package/dist/session/manager.d.ts +4 -0
- package/dist/session/manager.d.ts.map +1 -1
- package/dist/session/manager.js +8 -0
- package/dist/session/manager.js.map +1 -1
- package/dist/tools/builtin/ask-user.d.ts +64 -0
- package/dist/tools/builtin/ask-user.d.ts.map +1 -0
- package/dist/tools/builtin/ask-user.js +148 -0
- package/dist/tools/builtin/ask-user.js.map +1 -0
- package/dist/tools/index.d.ts +19 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +11 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/registry.d.ts +13 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +79 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/types.d.ts +17 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js.map +1 -1
- package/docs/cost-tracking-comparison.md +904 -0
- package/docs/operating-modes.md +96 -0
- package/docs/proposals/0012-ask-user-question.md +66 -1
- package/docs/proposals/0025-cost-tracking.md +60 -2
- package/docs/proposals/README.md +2 -2
- package/examples/test-ask-user.ts +167 -0
- package/examples/test-checkpointing.ts +121 -0
- package/examples/test-cost-tracking.ts +77 -0
- package/examples/test-interrupt-cleanup.ts +94 -0
- package/package.json +1 -1
- package/src/agent/agent.ts +130 -4
- package/src/agent/index.ts +1 -0
- package/src/agent/types.ts +19 -1
- package/src/checkpointing/checkpoint-manager.ts +327 -0
- package/src/checkpointing/index.ts +45 -0
- package/src/checkpointing/types.ts +104 -0
- package/src/cli/components/App.tsx +259 -8
- package/src/cli/components/CommandSuggestions.tsx +5 -0
- package/src/cli/components/Messages.tsx +66 -4
- package/src/cli/components/ModeIndicator.tsx +174 -0
- package/src/cli/components/PlanApproval.tsx +327 -0
- package/src/cli/components/QuestionPrompt.tsx +462 -0
- package/src/cli/components/index.ts +1 -0
- package/src/cli/components/theme.ts +14 -1
- package/src/index.ts +15 -0
- package/src/permissions/types.ts +2 -0
- package/src/planning/index.ts +53 -0
- package/src/planning/plan-file.ts +326 -0
- package/src/planning/state.ts +305 -0
- package/src/planning/tools/enter-plan-mode.ts +111 -0
- package/src/planning/tools/exit-plan-mode.ts +170 -0
- package/src/planning/types.ts +150 -0
- package/src/pricing/calculator.ts +71 -0
- package/src/pricing/index.ts +7 -0
- package/src/pricing/models.ts +334 -0
- package/src/pricing/types.ts +32 -0
- package/src/prompts/system/base.txt +42 -0
- package/src/prompts/tools/ask-user.txt +110 -0
- package/src/providers/anthropic.ts +21 -10
- package/src/providers/gemini.ts +25 -14
- package/src/providers/openai.ts +17 -8
- package/src/providers/types.ts +3 -0
- package/src/providers/vertex-ai.ts +21 -10
- package/src/session/manager.ts +9 -0
- package/src/tools/builtin/ask-user.ts +185 -0
- package/src/tools/index.ts +23 -0
- package/src/tools/registry.ts +95 -2
- package/src/tools/types.ts +18 -0
- package/.gencode/settings.local.json +0 -7
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkpointing Module
|
|
3
|
+
*
|
|
4
|
+
* Provides automatic tracking of file changes with undo/rewind capabilities.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* import { getCheckpointManager, initCheckpointManager } from './checkpointing';
|
|
8
|
+
*
|
|
9
|
+
* // Initialize at session start
|
|
10
|
+
* const manager = initCheckpointManager('session-123');
|
|
11
|
+
*
|
|
12
|
+
* // Record changes (done automatically by ToolRegistry)
|
|
13
|
+
* manager.recordChange({
|
|
14
|
+
* path: '/path/to/file.ts',
|
|
15
|
+
* changeType: 'modify',
|
|
16
|
+
* previousContent: 'old content',
|
|
17
|
+
* newContent: 'new content',
|
|
18
|
+
* toolName: 'Edit'
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // List changes
|
|
22
|
+
* console.log(manager.formatCheckpointList());
|
|
23
|
+
*
|
|
24
|
+
* // Rewind changes
|
|
25
|
+
* await manager.rewind({ all: true });
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// Type exports
|
|
29
|
+
export type {
|
|
30
|
+
ChangeType,
|
|
31
|
+
FileCheckpoint,
|
|
32
|
+
CheckpointSession,
|
|
33
|
+
RewindOptions,
|
|
34
|
+
RewindResult,
|
|
35
|
+
CheckpointSummary,
|
|
36
|
+
RecordChangeInput,
|
|
37
|
+
} from './types.js';
|
|
38
|
+
|
|
39
|
+
// Manager exports
|
|
40
|
+
export {
|
|
41
|
+
CheckpointManager,
|
|
42
|
+
getCheckpointManager,
|
|
43
|
+
initCheckpointManager,
|
|
44
|
+
resetCheckpointManager,
|
|
45
|
+
} from './checkpoint-manager.js';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkpointing System Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Provides automatic tracking of file changes with undo/rewind capabilities.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Type of file change
|
|
9
|
+
*/
|
|
10
|
+
export type ChangeType = 'create' | 'modify' | 'delete';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A single file checkpoint recording a change
|
|
14
|
+
*/
|
|
15
|
+
export interface FileCheckpoint {
|
|
16
|
+
/** Unique identifier for this checkpoint */
|
|
17
|
+
id: string;
|
|
18
|
+
/** Path to the file that was changed */
|
|
19
|
+
path: string;
|
|
20
|
+
/** Type of change made */
|
|
21
|
+
changeType: ChangeType;
|
|
22
|
+
/** When the change was made */
|
|
23
|
+
timestamp: Date;
|
|
24
|
+
/** File content before the change (null for create) */
|
|
25
|
+
previousContent: string | null;
|
|
26
|
+
/** File content after the change (null for delete) */
|
|
27
|
+
newContent: string | null;
|
|
28
|
+
/** Which tool made the change */
|
|
29
|
+
toolName: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A checkpoint session containing all checkpoints for a session
|
|
34
|
+
*/
|
|
35
|
+
export interface CheckpointSession {
|
|
36
|
+
/** Session ID this checkpoint session belongs to */
|
|
37
|
+
sessionId: string;
|
|
38
|
+
/** All checkpoints in order */
|
|
39
|
+
checkpoints: FileCheckpoint[];
|
|
40
|
+
/** When this checkpoint session was created */
|
|
41
|
+
createdAt: Date;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Options for rewinding changes
|
|
46
|
+
*/
|
|
47
|
+
export interface RewindOptions {
|
|
48
|
+
/** Rewind a specific checkpoint by ID */
|
|
49
|
+
checkpointId?: string;
|
|
50
|
+
/** Rewind changes to a specific file path */
|
|
51
|
+
path?: string;
|
|
52
|
+
/** Rewind all changes */
|
|
53
|
+
all?: boolean;
|
|
54
|
+
/** Rewind the last N changes */
|
|
55
|
+
count?: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Result of a rewind operation
|
|
60
|
+
*/
|
|
61
|
+
export interface RewindResult {
|
|
62
|
+
/** Whether the rewind was successful */
|
|
63
|
+
success: boolean;
|
|
64
|
+
/** Files that were successfully reverted */
|
|
65
|
+
revertedFiles: Array<{
|
|
66
|
+
path: string;
|
|
67
|
+
action: 'restored' | 'deleted' | 'recreated';
|
|
68
|
+
}>;
|
|
69
|
+
/** Any errors that occurred during rewind */
|
|
70
|
+
errors: Array<{
|
|
71
|
+
path: string;
|
|
72
|
+
error: string;
|
|
73
|
+
}>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Summary of changes in a checkpoint session
|
|
78
|
+
*/
|
|
79
|
+
export interface CheckpointSummary {
|
|
80
|
+
/** Number of files created */
|
|
81
|
+
created: number;
|
|
82
|
+
/** Number of files modified */
|
|
83
|
+
modified: number;
|
|
84
|
+
/** Number of files deleted */
|
|
85
|
+
deleted: number;
|
|
86
|
+
/** Total number of checkpoints */
|
|
87
|
+
total: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Input for recording a file change
|
|
92
|
+
*/
|
|
93
|
+
export interface RecordChangeInput {
|
|
94
|
+
/** Path to the file */
|
|
95
|
+
path: string;
|
|
96
|
+
/** Type of change */
|
|
97
|
+
changeType: ChangeType;
|
|
98
|
+
/** Content before change (null for create) */
|
|
99
|
+
previousContent: string | null;
|
|
100
|
+
/** Content after change (null for delete) */
|
|
101
|
+
newContent: string | null;
|
|
102
|
+
/** Tool that made the change */
|
|
103
|
+
toolName: string;
|
|
104
|
+
}
|
|
@@ -6,6 +6,8 @@ import { useState, useEffect, useCallback, useRef } from 'react';
|
|
|
6
6
|
import { Box, Text, useApp, useInput, Static } from 'ink';
|
|
7
7
|
import { Agent } from '../../agent/index.js';
|
|
8
8
|
import type { AgentConfig } from '../../agent/types.js';
|
|
9
|
+
import { formatTokens, formatCost } from '../../pricing/calculator.js';
|
|
10
|
+
import type { CostEstimate } from '../../pricing/types.js';
|
|
9
11
|
import {
|
|
10
12
|
UserMessage,
|
|
11
13
|
AssistantMessage,
|
|
@@ -28,11 +30,18 @@ import {
|
|
|
28
30
|
PermissionAuditDisplay,
|
|
29
31
|
} from './PermissionPrompt.js';
|
|
30
32
|
import { TodoList } from './TodoList.js';
|
|
33
|
+
import { QuestionPrompt, AnswerDisplay } from './QuestionPrompt.js';
|
|
31
34
|
import { colors, icons } from './theme.js';
|
|
32
|
-
import { getTodos } from '../../tools/index.js';
|
|
35
|
+
import { getTodos, formatAnswersForDisplay } from '../../tools/index.js';
|
|
36
|
+
import type { Question, QuestionAnswer } from '../../tools/types.js';
|
|
33
37
|
import type { ProviderName } from '../../providers/index.js';
|
|
34
38
|
import type { ApprovalAction, ApprovalSuggestion } from '../../permissions/types.js';
|
|
35
39
|
import { gatherContextFiles, buildInitPrompt, getContextSummary } from '../../memory/index.js';
|
|
40
|
+
// ModeIndicator kept for potential future use
|
|
41
|
+
import { PlanApproval } from './PlanApproval.js';
|
|
42
|
+
import type { ModeType, PlanApprovalOption, AllowedPrompt } from '../../planning/types.js';
|
|
43
|
+
// Planning utilities kept for potential future use
|
|
44
|
+
import { getCheckpointManager } from '../../checkpointing/index.js';
|
|
36
45
|
|
|
37
46
|
// Types
|
|
38
47
|
interface HistoryItem {
|
|
@@ -49,6 +58,19 @@ interface ConfirmState {
|
|
|
49
58
|
resolve: (action: ApprovalAction) => void;
|
|
50
59
|
}
|
|
51
60
|
|
|
61
|
+
interface QuestionState {
|
|
62
|
+
questions: Question[];
|
|
63
|
+
resolve: (answers: QuestionAnswer[]) => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface PlanApprovalState {
|
|
67
|
+
planSummary: string;
|
|
68
|
+
requestedPermissions: AllowedPrompt[];
|
|
69
|
+
filesToChange: Array<{ path: string; action: 'create' | 'modify' | 'delete' }>;
|
|
70
|
+
planFilePath: string;
|
|
71
|
+
resolve: (option: PlanApprovalOption, customInput?: string) => void;
|
|
72
|
+
}
|
|
73
|
+
|
|
52
74
|
interface SettingsManager {
|
|
53
75
|
save: (settings: { model?: string }) => Promise<void>;
|
|
54
76
|
getCwd?: () => string;
|
|
@@ -101,6 +123,9 @@ const formatRelativeTime = (dateStr: string) => {
|
|
|
101
123
|
// ============================================================================
|
|
102
124
|
function HelpPanel() {
|
|
103
125
|
const commands: [string, string][] = [
|
|
126
|
+
['/plan [desc]', 'Enter plan mode'],
|
|
127
|
+
['/normal', 'Exit to normal mode'],
|
|
128
|
+
['/accept', 'Enter auto-accept mode'],
|
|
104
129
|
['/model [name]', 'Switch model'],
|
|
105
130
|
['/provider', 'Manage providers'],
|
|
106
131
|
['/sessions', 'List sessions'],
|
|
@@ -110,6 +135,8 @@ function HelpPanel() {
|
|
|
110
135
|
['/clear', 'Clear chat'],
|
|
111
136
|
['/init', 'Generate AGENT.md'],
|
|
112
137
|
['/memory', 'Show memory files'],
|
|
138
|
+
['/changes', 'List file changes'],
|
|
139
|
+
['/rewind [n|all]', 'Undo file changes'],
|
|
113
140
|
];
|
|
114
141
|
|
|
115
142
|
return (
|
|
@@ -235,6 +262,7 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
235
262
|
const [processingStartTime, setProcessingStartTime] = useState<number | undefined>(undefined);
|
|
236
263
|
const [tokenCount, setTokenCount] = useState(0);
|
|
237
264
|
const [confirmState, setConfirmState] = useState<ConfirmState | null>(null);
|
|
265
|
+
const [questionState, setQuestionState] = useState<QuestionState | null>(null);
|
|
238
266
|
const [showModelSelector, setShowModelSelector] = useState(false);
|
|
239
267
|
const [showProviderManager, setShowProviderManager] = useState(false);
|
|
240
268
|
const [currentModel, setCurrentModel] = useState(config.model);
|
|
@@ -244,6 +272,16 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
244
272
|
const pendingToolRef = useRef<{ name: string; input: Record<string, unknown> } | null>(null);
|
|
245
273
|
const [todos, setTodos] = useState<ReturnType<typeof getTodos>>([]);
|
|
246
274
|
|
|
275
|
+
// Operating mode state (normal → plan → accept → normal)
|
|
276
|
+
const [currentMode, setCurrentMode] = useState<ModeType>('normal');
|
|
277
|
+
const currentModeRef = useRef<ModeType>('normal'); // Track mode for confirm callback
|
|
278
|
+
const [planApprovalState, setPlanApprovalState] = useState<PlanApprovalState | null>(null);
|
|
279
|
+
|
|
280
|
+
// Keep ref in sync with state
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
currentModeRef.current = currentMode;
|
|
283
|
+
}, [currentMode]);
|
|
284
|
+
|
|
247
285
|
// Check if showing command suggestions
|
|
248
286
|
const showCmdSuggestions = input.startsWith('/') && !isProcessing;
|
|
249
287
|
const cmdSuggestions = showCmdSuggestions ? getFilteredCommands(input) : [];
|
|
@@ -266,6 +304,11 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
266
304
|
|
|
267
305
|
// Set enhanced confirm callback with approval options
|
|
268
306
|
agent.setEnhancedConfirmCallback(async (tool, toolInput, suggestions) => {
|
|
307
|
+
// Auto-approve in accept mode
|
|
308
|
+
if (currentModeRef.current === 'accept') {
|
|
309
|
+
return 'allow_once';
|
|
310
|
+
}
|
|
311
|
+
|
|
269
312
|
return new Promise<ApprovalAction>((resolve) => {
|
|
270
313
|
setConfirmState({
|
|
271
314
|
tool,
|
|
@@ -276,6 +319,13 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
276
319
|
});
|
|
277
320
|
});
|
|
278
321
|
|
|
322
|
+
// Set askUser callback for AskUserQuestion tool
|
|
323
|
+
agent.setAskUserCallback(async (questions) => {
|
|
324
|
+
return new Promise<QuestionAnswer[]>((resolve) => {
|
|
325
|
+
setQuestionState({ questions, resolve });
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
279
329
|
// Set callback to save permission rules to settings.local.json
|
|
280
330
|
if (settingsManager?.addPermissionRule) {
|
|
281
331
|
agent.setSaveRuleCallback(async (tool, pattern) => {
|
|
@@ -295,6 +345,32 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
295
345
|
init();
|
|
296
346
|
}, [agent, resumeLatest, addHistory, permissionSettings, settingsManager]);
|
|
297
347
|
|
|
348
|
+
// Handle question answers (AskUserQuestion)
|
|
349
|
+
const handleQuestionComplete = useCallback((answers: QuestionAnswer[]) => {
|
|
350
|
+
if (questionState) {
|
|
351
|
+
// Show confirmation in history
|
|
352
|
+
addHistory({
|
|
353
|
+
type: 'info',
|
|
354
|
+
content: formatAnswersForDisplay(answers),
|
|
355
|
+
});
|
|
356
|
+
questionState.resolve(answers);
|
|
357
|
+
setQuestionState(null);
|
|
358
|
+
}
|
|
359
|
+
}, [questionState, addHistory]);
|
|
360
|
+
|
|
361
|
+
// Handle question cancel
|
|
362
|
+
const handleQuestionCancel = useCallback(() => {
|
|
363
|
+
if (questionState) {
|
|
364
|
+
// Clear pending tool display (no more spinner)
|
|
365
|
+
pendingToolRef.current = null;
|
|
366
|
+
setPendingTool(null);
|
|
367
|
+
// Add canceled message to history
|
|
368
|
+
addHistory({ type: 'info', content: 'Question canceled' });
|
|
369
|
+
questionState.resolve([]); // Return empty answers on cancel
|
|
370
|
+
setQuestionState(null);
|
|
371
|
+
}
|
|
372
|
+
}, [questionState, addHistory]);
|
|
373
|
+
|
|
298
374
|
// Handle permission decision
|
|
299
375
|
const handlePermissionDecision = (action: ApprovalAction) => {
|
|
300
376
|
if (confirmState) {
|
|
@@ -524,6 +600,110 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
524
600
|
return true;
|
|
525
601
|
}
|
|
526
602
|
|
|
603
|
+
case 'plan': {
|
|
604
|
+
// Enter plan mode
|
|
605
|
+
await agent.enterPlanMode(arg);
|
|
606
|
+
setCurrentMode('plan');
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
case 'normal': {
|
|
611
|
+
// Exit to normal mode
|
|
612
|
+
if (agent.isPlanModeActive()) {
|
|
613
|
+
agent.exitPlanMode(false);
|
|
614
|
+
}
|
|
615
|
+
setCurrentMode('normal');
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
case 'accept': {
|
|
620
|
+
// Enter auto-accept mode
|
|
621
|
+
if (agent.isPlanModeActive()) {
|
|
622
|
+
agent.exitPlanMode(false);
|
|
623
|
+
}
|
|
624
|
+
setCurrentMode('accept');
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
case 'changes': {
|
|
629
|
+
// List file changes (checkpoints)
|
|
630
|
+
const checkpointManager = getCheckpointManager();
|
|
631
|
+
if (!checkpointManager.hasCheckpoints()) {
|
|
632
|
+
addHistory({ type: 'info', content: '\nNo file changes in this session' });
|
|
633
|
+
} else {
|
|
634
|
+
addHistory({ type: 'info', content: '\n' + checkpointManager.formatCheckpointList(false) });
|
|
635
|
+
}
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
case 'rewind': {
|
|
640
|
+
// Rewind file changes
|
|
641
|
+
const checkpointManager = getCheckpointManager();
|
|
642
|
+
|
|
643
|
+
if (!checkpointManager.hasCheckpoints()) {
|
|
644
|
+
addHistory({ type: 'info', content: 'No file changes to rewind' });
|
|
645
|
+
return true;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (arg === 'all') {
|
|
649
|
+
// Rewind all changes
|
|
650
|
+
const result = await checkpointManager.rewind({ all: true });
|
|
651
|
+
|
|
652
|
+
// Build output message showing both successes and failures
|
|
653
|
+
const messages: string[] = ['']; // Start with empty line for spacing
|
|
654
|
+
|
|
655
|
+
if (result.revertedFiles.length > 0) {
|
|
656
|
+
const files = result.revertedFiles.map((f) => {
|
|
657
|
+
const fileName = f.path.split('/').pop() || f.path;
|
|
658
|
+
return ` • ${fileName} (${f.action})`;
|
|
659
|
+
}).join('\n');
|
|
660
|
+
messages.push(`Reverted ${result.revertedFiles.length} file(s):\n${files}`);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (result.errors.length > 0) {
|
|
664
|
+
const errors = result.errors.map((e) => {
|
|
665
|
+
const fileName = e.path.split('/').pop() || e.path;
|
|
666
|
+
return ` • ${fileName}: ${e.error}`;
|
|
667
|
+
}).join('\n');
|
|
668
|
+
messages.push(`\nFailed to revert ${result.errors.length} file(s):\n${errors}`);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (messages.length > 1) {
|
|
672
|
+
addHistory({ type: 'info', content: messages.join('\n') });
|
|
673
|
+
} else {
|
|
674
|
+
addHistory({ type: 'info', content: '\nNo changes to rewind' });
|
|
675
|
+
}
|
|
676
|
+
} else if (arg) {
|
|
677
|
+
// Rewind specific checkpoint by index
|
|
678
|
+
const index = parseInt(arg, 10);
|
|
679
|
+
if (!isNaN(index) && index >= 1) {
|
|
680
|
+
const checkpoints = checkpointManager.getCheckpoints();
|
|
681
|
+
if (index <= checkpoints.length) {
|
|
682
|
+
const checkpoint = checkpoints[index - 1];
|
|
683
|
+
const result = await checkpointManager.rewind({ checkpointId: checkpoint.id });
|
|
684
|
+
if (result.success && result.revertedFiles.length > 0) {
|
|
685
|
+
const f = result.revertedFiles[0];
|
|
686
|
+
const fileName = f.path.split('/').pop() || f.path;
|
|
687
|
+
addHistory({ type: 'info', content: `\nReverted: ${fileName} (${f.action})` });
|
|
688
|
+
} else if (result.errors.length > 0) {
|
|
689
|
+
const fileName = result.errors[0].path.split('/').pop() || result.errors[0].path;
|
|
690
|
+
addHistory({ type: 'info', content: `\nFailed: ${fileName} - ${result.errors[0].error}` });
|
|
691
|
+
} else {
|
|
692
|
+
addHistory({ type: 'info', content: '\nFailed to rewind change' });
|
|
693
|
+
}
|
|
694
|
+
} else {
|
|
695
|
+
addHistory({ type: 'info', content: '\nInvalid index: ${index}' });
|
|
696
|
+
}
|
|
697
|
+
} else {
|
|
698
|
+
addHistory({ type: 'info', content: '\nUsage: /rewind [n|all]' });
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
// Show changes and usage in one message
|
|
702
|
+
addHistory({ type: 'info', content: '\n' + checkpointManager.formatCheckpointList(true) });
|
|
703
|
+
}
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
|
|
527
707
|
default:
|
|
528
708
|
return false;
|
|
529
709
|
}
|
|
@@ -620,9 +800,13 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
620
800
|
streamingTextRef.current = '';
|
|
621
801
|
setStreamingText('');
|
|
622
802
|
}
|
|
623
|
-
// Add completion message with duration
|
|
803
|
+
// Add completion message with duration and cost info
|
|
624
804
|
const durationMs = Date.now() - startTime;
|
|
625
|
-
addHistory({
|
|
805
|
+
addHistory({
|
|
806
|
+
type: 'completion',
|
|
807
|
+
content: '',
|
|
808
|
+
meta: { durationMs, usage: event.usage, cost: event.cost },
|
|
809
|
+
});
|
|
626
810
|
setProcessingStartTime(undefined);
|
|
627
811
|
break;
|
|
628
812
|
}
|
|
@@ -730,9 +914,36 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
730
914
|
setIsProcessing(false);
|
|
731
915
|
setStreamingText('');
|
|
732
916
|
streamingTextRef.current = '';
|
|
917
|
+
// Clear pending tool (stop spinner)
|
|
918
|
+
pendingToolRef.current = null;
|
|
919
|
+
setPendingTool(null);
|
|
920
|
+
// Clean up incomplete tool_use messages to prevent API errors
|
|
921
|
+
agent.cleanupIncompleteMessages();
|
|
733
922
|
addHistory({ type: 'info', content: 'Interrupted' });
|
|
734
923
|
}
|
|
735
924
|
|
|
925
|
+
// Shift+Tab to cycle modes: normal → plan → accept → normal
|
|
926
|
+
if (key.shift && key.tab && !isProcessing && !confirmState && !questionState && !planApprovalState) {
|
|
927
|
+
const cycleMode = async () => {
|
|
928
|
+
const nextMode: Record<ModeType, ModeType> = {
|
|
929
|
+
normal: 'plan',
|
|
930
|
+
plan: 'accept',
|
|
931
|
+
accept: 'normal',
|
|
932
|
+
};
|
|
933
|
+
const newMode = nextMode[currentMode];
|
|
934
|
+
|
|
935
|
+
// Handle plan mode transitions
|
|
936
|
+
if (newMode === 'plan') {
|
|
937
|
+
await agent.enterPlanMode();
|
|
938
|
+
} else if (currentMode === 'plan') {
|
|
939
|
+
agent.exitPlanMode(false);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
setCurrentMode(newMode);
|
|
943
|
+
};
|
|
944
|
+
cycleMode();
|
|
945
|
+
}
|
|
946
|
+
|
|
736
947
|
// Command suggestion navigation
|
|
737
948
|
if (showCmdSuggestions && cmdSuggestions.length > 0) {
|
|
738
949
|
if (key.upArrow) {
|
|
@@ -809,7 +1020,13 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
809
1020
|
}
|
|
810
1021
|
return <InfoMessage text={item.content} />;
|
|
811
1022
|
case 'completion':
|
|
812
|
-
return
|
|
1023
|
+
return (
|
|
1024
|
+
<CompletionMessage
|
|
1025
|
+
durationMs={(item.meta?.durationMs as number) || 0}
|
|
1026
|
+
usage={item.meta?.usage as { inputTokens: number; outputTokens: number } | undefined}
|
|
1027
|
+
cost={item.meta?.cost as CostEstimate | undefined}
|
|
1028
|
+
/>
|
|
1029
|
+
);
|
|
813
1030
|
case 'todos':
|
|
814
1031
|
return <TodoList todos={item.meta?.todos as ReturnType<typeof getTodos>} />;
|
|
815
1032
|
default:
|
|
@@ -818,7 +1035,7 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
818
1035
|
};
|
|
819
1036
|
|
|
820
1037
|
return (
|
|
821
|
-
<Box flexDirection="column">
|
|
1038
|
+
<Box flexDirection="column" paddingBottom={2}>
|
|
822
1039
|
<Static items={history}>
|
|
823
1040
|
{(item) => <Box key={item.id}>{renderHistoryItem(item)}</Box>}
|
|
824
1041
|
</Static>
|
|
@@ -837,6 +1054,30 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
837
1054
|
/>
|
|
838
1055
|
)}
|
|
839
1056
|
|
|
1057
|
+
{questionState && (
|
|
1058
|
+
<QuestionPrompt
|
|
1059
|
+
questions={questionState.questions}
|
|
1060
|
+
onComplete={handleQuestionComplete}
|
|
1061
|
+
onCancel={handleQuestionCancel}
|
|
1062
|
+
/>
|
|
1063
|
+
)}
|
|
1064
|
+
|
|
1065
|
+
{/* Plan approval UI */}
|
|
1066
|
+
{planApprovalState && (
|
|
1067
|
+
<Box marginTop={1}>
|
|
1068
|
+
<PlanApproval
|
|
1069
|
+
planSummary={planApprovalState.planSummary}
|
|
1070
|
+
requestedPermissions={planApprovalState.requestedPermissions}
|
|
1071
|
+
filesToChange={planApprovalState.filesToChange}
|
|
1072
|
+
planFilePath={planApprovalState.planFilePath}
|
|
1073
|
+
onDecision={(option, customInput) => {
|
|
1074
|
+
planApprovalState.resolve(option, customInput);
|
|
1075
|
+
setPlanApprovalState(null);
|
|
1076
|
+
}}
|
|
1077
|
+
/>
|
|
1078
|
+
</Box>
|
|
1079
|
+
)}
|
|
1080
|
+
|
|
840
1081
|
{showModelSelector && (
|
|
841
1082
|
<Box marginTop={1}>
|
|
842
1083
|
<ModelSelector
|
|
@@ -861,8 +1102,8 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
861
1102
|
</Box>
|
|
862
1103
|
)}
|
|
863
1104
|
|
|
864
|
-
{!confirmState && !showModelSelector && !showProviderManager && (
|
|
865
|
-
<Box flexDirection="column" marginTop={
|
|
1105
|
+
{!confirmState && !questionState && !showModelSelector && !showProviderManager && (
|
|
1106
|
+
<Box flexDirection="column" marginTop={2}>
|
|
866
1107
|
<PromptInput
|
|
867
1108
|
key={inputKey}
|
|
868
1109
|
value={input}
|
|
@@ -872,10 +1113,20 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
872
1113
|
{showCmdSuggestions && cmdSuggestions.length > 0 && (
|
|
873
1114
|
<CommandSuggestions input={input} selectedIndex={cmdSuggestionIndex} />
|
|
874
1115
|
)}
|
|
1116
|
+
{currentMode === 'plan' && !isProcessing && (
|
|
1117
|
+
<Text color={colors.warning} dimColor>
|
|
1118
|
+
{icons.modePlan} plan mode on (shift+tab to cycle)
|
|
1119
|
+
</Text>
|
|
1120
|
+
)}
|
|
1121
|
+
{currentMode === 'accept' && !isProcessing && (
|
|
1122
|
+
<Text color={colors.success} dimColor>
|
|
1123
|
+
{icons.modeAccept} accept edits on (shift+tab to cycle)
|
|
1124
|
+
</Text>
|
|
1125
|
+
)}
|
|
875
1126
|
</Box>
|
|
876
1127
|
)}
|
|
877
1128
|
|
|
878
|
-
{isProcessing && !confirmState ? (
|
|
1129
|
+
{isProcessing && !confirmState && !questionState ? (
|
|
879
1130
|
<ProgressBar startTime={processingStartTime} tokenCount={tokenCount} isThinking={isThinking} />
|
|
880
1131
|
) : showCmdSuggestions && cmdSuggestions.length > 0 ? (
|
|
881
1132
|
<Box marginTop={1}>
|
|
@@ -7,6 +7,9 @@ interface Command {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export const COMMANDS: Command[] = [
|
|
10
|
+
{ name: '/plan', description: 'Enter plan mode (Shift+Tab to cycle)' },
|
|
11
|
+
{ name: '/normal', description: 'Exit to normal mode' },
|
|
12
|
+
{ name: '/accept', description: 'Enter auto-accept mode' },
|
|
10
13
|
{ name: '/model', description: 'Switch model' },
|
|
11
14
|
{ name: '/provider', description: 'Manage providers' },
|
|
12
15
|
{ name: '/permissions', description: 'View permission rules' },
|
|
@@ -20,6 +23,8 @@ export const COMMANDS: Command[] = [
|
|
|
20
23
|
{ name: '/help', description: 'Show help' },
|
|
21
24
|
{ name: '/init', description: 'Generate AGENT.md' },
|
|
22
25
|
{ name: '/memory', description: 'Show memory files' },
|
|
26
|
+
{ name: '/changes', description: 'List file changes' },
|
|
27
|
+
{ name: '/rewind', description: 'Undo file changes' },
|
|
23
28
|
];
|
|
24
29
|
|
|
25
30
|
interface CommandSuggestionsProps {
|
|
@@ -3,6 +3,8 @@ import { Box, Text } from 'ink';
|
|
|
3
3
|
import InkSpinner from 'ink-spinner';
|
|
4
4
|
import { colors, icons } from './theme.js';
|
|
5
5
|
import { renderMarkdown } from './markdown.js';
|
|
6
|
+
import { formatTokens, formatCost } from '../../pricing/calculator.js';
|
|
7
|
+
import type { CostEstimate } from '../../pricing/types.js';
|
|
6
8
|
|
|
7
9
|
// Truncate string with ellipsis
|
|
8
10
|
const truncate = (str: string, maxLen: number) =>
|
|
@@ -126,6 +128,11 @@ function formatToolInput(name: string, input: Record<string, unknown>): string {
|
|
|
126
128
|
const todos = input.todos as Array<{ content: string; status: string }> || [];
|
|
127
129
|
return `${todos.length} task${todos.length !== 1 ? 's' : ''}`;
|
|
128
130
|
}
|
|
131
|
+
case 'AskUserQuestion': {
|
|
132
|
+
// Show collapsed JSON preview
|
|
133
|
+
const json = JSON.stringify(input);
|
|
134
|
+
return truncate(json, 60);
|
|
135
|
+
}
|
|
129
136
|
default:
|
|
130
137
|
return truncate(JSON.stringify(input), 40);
|
|
131
138
|
}
|
|
@@ -135,6 +142,25 @@ export function ToolCall({ name, input }: ToolCallProps) {
|
|
|
135
142
|
// Hide TodoWrite (shown in TodoList component)
|
|
136
143
|
if (name === 'TodoWrite') return null;
|
|
137
144
|
|
|
145
|
+
// Special display for AskUserQuestion (Claude Code style with expand hint)
|
|
146
|
+
if (name === 'AskUserQuestion') {
|
|
147
|
+
const json = JSON.stringify(input);
|
|
148
|
+
const displayJson = truncate(json, 70);
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<Box marginTop={1} flexDirection="column">
|
|
152
|
+
<Box>
|
|
153
|
+
<Text color={colors.tool}>{icons.tool}</Text>
|
|
154
|
+
<Text> </Text>
|
|
155
|
+
<Text bold>{name}</Text>
|
|
156
|
+
<Text color={colors.textMuted}> </Text>
|
|
157
|
+
<Text color={colors.textSecondary}>{displayJson}</Text>
|
|
158
|
+
<Text color={colors.textMuted}> ctrl+o</Text>
|
|
159
|
+
</Box>
|
|
160
|
+
</Box>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
138
164
|
const displayInput = formatToolInput(name, input);
|
|
139
165
|
|
|
140
166
|
return (
|
|
@@ -162,6 +188,25 @@ export function PendingToolCall({ name, input }: PendingToolCallProps) {
|
|
|
162
188
|
// Hide TodoWrite (shown in TodoList component)
|
|
163
189
|
if (name === 'TodoWrite') return null;
|
|
164
190
|
|
|
191
|
+
// Special display for AskUserQuestion
|
|
192
|
+
if (name === 'AskUserQuestion') {
|
|
193
|
+
const json = JSON.stringify(input);
|
|
194
|
+
const displayJson = truncate(json, 70);
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<Box marginTop={1}>
|
|
198
|
+
<Text color={colors.tool}>
|
|
199
|
+
<InkSpinner type="dots" />
|
|
200
|
+
</Text>
|
|
201
|
+
<Text> </Text>
|
|
202
|
+
<Text bold>{name}</Text>
|
|
203
|
+
<Text color={colors.textMuted}> </Text>
|
|
204
|
+
<Text color={colors.textSecondary}>{displayJson}</Text>
|
|
205
|
+
<Text color={colors.textMuted}> ctrl+o</Text>
|
|
206
|
+
</Box>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
165
210
|
const displayInput = formatToolInput(name, input);
|
|
166
211
|
|
|
167
212
|
return (
|
|
@@ -303,16 +348,33 @@ function formatDuration(ms: number): string {
|
|
|
303
348
|
|
|
304
349
|
interface CompletionMessageProps {
|
|
305
350
|
durationMs: number;
|
|
351
|
+
usage?: {
|
|
352
|
+
inputTokens: number;
|
|
353
|
+
outputTokens: number;
|
|
354
|
+
};
|
|
355
|
+
cost?: CostEstimate;
|
|
306
356
|
}
|
|
307
357
|
|
|
308
|
-
export function CompletionMessage({ durationMs }: CompletionMessageProps) {
|
|
358
|
+
export function CompletionMessage({ durationMs, usage, cost }: CompletionMessageProps) {
|
|
309
359
|
// Pick a random verb (stable per render via useMemo would be better, but keep simple)
|
|
310
360
|
const verb = COMPLETION_VERBS[Math.floor(Math.random() * COMPLETION_VERBS.length)];
|
|
361
|
+
|
|
362
|
+
// Build the message parts
|
|
363
|
+
const parts = [`✻ ${verb} for ${formatDuration(durationMs)}`];
|
|
364
|
+
|
|
365
|
+
if (usage) {
|
|
366
|
+
parts.push(
|
|
367
|
+
`Tokens: ${formatTokens(usage.inputTokens)} in / ${formatTokens(usage.outputTokens)} out`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (cost) {
|
|
372
|
+
parts.push(`(~${formatCost(cost.totalCost)})`);
|
|
373
|
+
}
|
|
374
|
+
|
|
311
375
|
return (
|
|
312
376
|
<Box marginTop={1}>
|
|
313
|
-
<Text color={colors.textMuted}>
|
|
314
|
-
✻ {verb} for {formatDuration(durationMs)}
|
|
315
|
-
</Text>
|
|
377
|
+
<Text color={colors.textMuted}>{parts.join(' • ')}</Text>
|
|
316
378
|
</Box>
|
|
317
379
|
);
|
|
318
380
|
}
|