gencode-ai 0.1.3 → 0.3.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 +2 -1
- package/dist/agent/agent.d.ts +44 -2
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +130 -11
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/types.d.ts +11 -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 +171 -14
- 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 +12 -3
- 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/ModelSelector.d.ts +4 -3
- package/dist/cli/components/ModelSelector.d.ts.map +1 -1
- package/dist/cli/components/ModelSelector.js +54 -37
- package/dist/cli/components/ModelSelector.js.map +1 -1
- 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/ProviderManager.d.ts +2 -2
- package/dist/cli/components/ProviderManager.d.ts.map +1 -1
- package/dist/cli/components/ProviderManager.js +137 -156
- package/dist/cli/components/ProviderManager.js.map +1 -1
- package/dist/cli/components/theme.d.ts +2 -0
- package/dist/cli/components/theme.d.ts.map +1 -1
- package/dist/cli/components/theme.js +3 -0
- package/dist/cli/components/theme.js.map +1 -1
- package/dist/cli/index.js +30 -13
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/levels.d.ts +5 -5
- package/dist/config/levels.d.ts.map +1 -1
- package/dist/config/levels.js +20 -20
- package/dist/config/levels.js.map +1 -1
- package/dist/config/merger.js +1 -1
- package/dist/config/merger.js.map +1 -1
- package/dist/config/providers-config.d.ts +8 -5
- package/dist/config/providers-config.d.ts.map +1 -1
- package/dist/config/providers-config.js +19 -22
- package/dist/config/providers-config.js.map +1 -1
- package/dist/config/test-utils.d.ts +2 -2
- package/dist/config/test-utils.d.ts.map +1 -1
- package/dist/config/test-utils.js +4 -4
- package/dist/config/test-utils.js.map +1 -1
- package/dist/config/types.d.ts +23 -17
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +14 -14
- package/dist/config/types.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/memory/memory-manager.d.ts +25 -12
- package/dist/memory/memory-manager.d.ts.map +1 -1
- package/dist/memory/memory-manager.js +241 -112
- package/dist/memory/memory-manager.js.map +1 -1
- package/dist/memory/test-utils.d.ts +1 -1
- package/dist/memory/test-utils.d.ts.map +1 -1
- package/dist/memory/test-utils.js +3 -3
- package/dist/memory/test-utils.js.map +1 -1
- package/dist/memory/types.d.ts +20 -10
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/memory/types.js +13 -13
- package/dist/memory/types.js.map +1 -1
- package/dist/migration/migrate.d.ts +24 -0
- package/dist/migration/migrate.d.ts.map +1 -0
- package/dist/migration/migrate.js +164 -0
- package/dist/migration/migrate.js.map +1 -0
- package/dist/permissions/persistence.d.ts +2 -2
- package/dist/permissions/persistence.js +4 -4
- package/dist/permissions/persistence.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/prompts/index.d.ts +5 -4
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +11 -8
- package/dist/prompts/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts +2 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +24 -10
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/gemini.d.ts +2 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +28 -14
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/index.d.ts +20 -10
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +48 -24
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openai.d.ts +2 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +19 -8
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/registry.d.ts +48 -34
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +72 -88
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/store.d.ts +43 -17
- package/dist/providers/store.d.ts.map +1 -1
- package/dist/providers/store.js +112 -19
- package/dist/providers/store.js.map +1 -1
- package/dist/providers/types.d.ts +25 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/vertex-ai.d.ts +15 -7
- package/dist/providers/vertex-ai.d.ts.map +1 -1
- package/dist/providers/vertex-ai.js +63 -23
- 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/session/types.js +1 -1
- package/dist/session/types.js.map +1 -1
- package/dist/tools/index.d.ts +7 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +7 -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/docs/config-system-comparison.md +50 -50
- package/docs/cost-tracking-comparison.md +904 -0
- package/docs/memory-system.md +124 -31
- package/docs/operating-modes.md +96 -0
- package/docs/permissions.md +2 -2
- package/docs/proposals/0006-memory-system.md +4 -4
- package/docs/proposals/0008-checkpointing.md +109 -2
- package/docs/proposals/0011-custom-commands.md +2 -1
- package/docs/proposals/0021-skills-system.md +2 -1
- package/docs/proposals/0023-permission-enhancements.md +2 -2
- package/docs/proposals/0025-cost-tracking.md +60 -2
- package/docs/proposals/0033-enterprise-deployment.md +1 -1
- package/docs/proposals/0041-configuration-system.md +17 -19
- package/docs/proposals/0042-prompt-optimization.md +17 -9
- package/docs/proposals/README.md +6 -6
- package/docs/providers.md +94 -9
- 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 +3 -2
- package/scripts/migrate.ts +449 -0
- package/src/agent/agent.ts +161 -12
- package/src/agent/types.ts +11 -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 +221 -13
- package/src/cli/components/CommandSuggestions.tsx +5 -0
- package/src/cli/components/Messages.tsx +24 -5
- package/src/cli/components/ModeIndicator.tsx +174 -0
- package/src/cli/components/ModelSelector.tsx +62 -43
- package/src/cli/components/PlanApproval.tsx +327 -0
- package/src/cli/components/ProviderManager.tsx +278 -323
- package/src/cli/components/theme.ts +3 -0
- package/src/cli/index.tsx +36 -17
- package/src/config/index.ts +5 -3
- package/src/config/levels.test.ts +22 -22
- package/src/config/levels.ts +22 -22
- package/src/config/loader.test.ts +14 -14
- package/src/config/manager.test.ts +19 -19
- package/src/config/merger.test.ts +23 -23
- package/src/config/merger.ts +1 -1
- package/src/config/providers-config.ts +23 -21
- package/src/config/test-utils.ts +6 -6
- package/src/config/types.ts +30 -20
- package/src/index.ts +15 -0
- package/src/memory/memory-manager.test.ts +242 -24
- package/src/memory/memory-manager.ts +270 -141
- package/src/memory/test-utils.ts +4 -4
- package/src/memory/types.ts +28 -17
- package/src/permissions/persistence.ts +4 -4
- 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/index.ts +13 -9
- package/src/providers/anthropic.ts +30 -10
- package/src/providers/gemini.ts +34 -14
- package/src/providers/index.ts +76 -33
- package/src/providers/openai.ts +26 -8
- package/src/providers/registry.ts +116 -111
- package/src/providers/store.ts +130 -28
- package/src/providers/types.ts +36 -1
- package/src/providers/vertex-ai.ts +70 -23
- package/src/session/manager.ts +9 -0
- package/src/session/types.ts +1 -1
- package/src/tools/index.ts +8 -0
- package/src/tools/registry.ts +95 -2
- package/.gencode/settings.local.json +0 -7
- package/CLAUDE.md +0 -86
|
@@ -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,
|
|
@@ -35,6 +37,11 @@ import type { Question, QuestionAnswer } from '../../tools/types.js';
|
|
|
35
37
|
import type { ProviderName } from '../../providers/index.js';
|
|
36
38
|
import type { ApprovalAction, ApprovalSuggestion } from '../../permissions/types.js';
|
|
37
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';
|
|
38
45
|
|
|
39
46
|
// Types
|
|
40
47
|
interface HistoryItem {
|
|
@@ -56,6 +63,14 @@ interface QuestionState {
|
|
|
56
63
|
resolve: (answers: QuestionAnswer[]) => void;
|
|
57
64
|
}
|
|
58
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
|
+
|
|
59
74
|
interface SettingsManager {
|
|
60
75
|
save: (settings: { model?: string }) => Promise<void>;
|
|
61
76
|
getCwd?: () => string;
|
|
@@ -108,6 +123,9 @@ const formatRelativeTime = (dateStr: string) => {
|
|
|
108
123
|
// ============================================================================
|
|
109
124
|
function HelpPanel() {
|
|
110
125
|
const commands: [string, string][] = [
|
|
126
|
+
['/plan [desc]', 'Enter plan mode'],
|
|
127
|
+
['/normal', 'Exit to normal mode'],
|
|
128
|
+
['/accept', 'Enter auto-accept mode'],
|
|
111
129
|
['/model [name]', 'Switch model'],
|
|
112
130
|
['/provider', 'Manage providers'],
|
|
113
131
|
['/sessions', 'List sessions'],
|
|
@@ -115,8 +133,10 @@ function HelpPanel() {
|
|
|
115
133
|
['/new', 'New session'],
|
|
116
134
|
['/save', 'Save session'],
|
|
117
135
|
['/clear', 'Clear chat'],
|
|
118
|
-
['/init', 'Generate
|
|
136
|
+
['/init', 'Generate GEN.md'],
|
|
119
137
|
['/memory', 'Show memory files'],
|
|
138
|
+
['/changes', 'List file changes'],
|
|
139
|
+
['/rewind [n|all]', 'Undo file changes'],
|
|
120
140
|
];
|
|
121
141
|
|
|
122
142
|
return (
|
|
@@ -246,12 +266,23 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
246
266
|
const [showModelSelector, setShowModelSelector] = useState(false);
|
|
247
267
|
const [showProviderManager, setShowProviderManager] = useState(false);
|
|
248
268
|
const [currentModel, setCurrentModel] = useState(config.model);
|
|
269
|
+
const [currentProvider, setCurrentProvider] = useState(config.provider);
|
|
249
270
|
const [cmdSuggestionIndex, setCmdSuggestionIndex] = useState(0);
|
|
250
271
|
const [inputKey, setInputKey] = useState(0); // Force cursor to end after autocomplete
|
|
251
272
|
const [pendingTool, setPendingTool] = useState<{ name: string; input: Record<string, unknown> } | null>(null);
|
|
252
273
|
const pendingToolRef = useRef<{ name: string; input: Record<string, unknown> } | null>(null);
|
|
253
274
|
const [todos, setTodos] = useState<ReturnType<typeof getTodos>>([]);
|
|
254
275
|
|
|
276
|
+
// Operating mode state (normal → plan → accept → normal)
|
|
277
|
+
const [currentMode, setCurrentMode] = useState<ModeType>('normal');
|
|
278
|
+
const currentModeRef = useRef<ModeType>('normal'); // Track mode for confirm callback
|
|
279
|
+
const [planApprovalState, setPlanApprovalState] = useState<PlanApprovalState | null>(null);
|
|
280
|
+
|
|
281
|
+
// Keep ref in sync with state
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
currentModeRef.current = currentMode;
|
|
284
|
+
}, [currentMode]);
|
|
285
|
+
|
|
255
286
|
// Check if showing command suggestions
|
|
256
287
|
const showCmdSuggestions = input.startsWith('/') && !isProcessing;
|
|
257
288
|
const cmdSuggestions = showCmdSuggestions ? getFilteredCommands(input) : [];
|
|
@@ -274,6 +305,11 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
274
305
|
|
|
275
306
|
// Set enhanced confirm callback with approval options
|
|
276
307
|
agent.setEnhancedConfirmCallback(async (tool, toolInput, suggestions) => {
|
|
308
|
+
// Auto-approve in accept mode
|
|
309
|
+
if (currentModeRef.current === 'accept') {
|
|
310
|
+
return 'allow_once';
|
|
311
|
+
}
|
|
312
|
+
|
|
277
313
|
return new Promise<ApprovalAction>((resolve) => {
|
|
278
314
|
setConfirmState({
|
|
279
315
|
tool,
|
|
@@ -345,9 +381,14 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
345
381
|
};
|
|
346
382
|
|
|
347
383
|
// Handle model selection
|
|
348
|
-
const handleModelSelect = async (model: string, providerId?: ProviderName) => {
|
|
349
|
-
agent.setModel(model);
|
|
384
|
+
const handleModelSelect = async (model: string, providerId?: ProviderName, authMethod?: string) => {
|
|
385
|
+
agent.setModel(model, providerId, authMethod);
|
|
350
386
|
setCurrentModel(model);
|
|
387
|
+
|
|
388
|
+
// Update provider state to keep UI in sync
|
|
389
|
+
const newProvider = agent.getProvider();
|
|
390
|
+
setCurrentProvider(newProvider);
|
|
391
|
+
|
|
351
392
|
setShowModelSelector(false);
|
|
352
393
|
|
|
353
394
|
if (providerId) {
|
|
@@ -442,6 +483,7 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
442
483
|
// Direct model switch: /model gpt-4o
|
|
443
484
|
agent.setModel(arg);
|
|
444
485
|
setCurrentModel(arg);
|
|
486
|
+
setCurrentProvider(agent.getProvider());
|
|
445
487
|
addHistory({ type: 'info', content: `Model: ${arg}` });
|
|
446
488
|
} else {
|
|
447
489
|
// Show interactive model selector
|
|
@@ -505,13 +547,13 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
505
547
|
}
|
|
506
548
|
|
|
507
549
|
case 'init': {
|
|
508
|
-
// Gather context files and generate
|
|
550
|
+
// Gather context files and generate GEN.md
|
|
509
551
|
addHistory({ type: 'info', content: 'Analyzing codebase...' });
|
|
510
552
|
|
|
511
553
|
const contextFiles = await gatherContextFiles(cwd);
|
|
512
554
|
addHistory({ type: 'info', content: getContextSummary(contextFiles) });
|
|
513
555
|
|
|
514
|
-
// Check if
|
|
556
|
+
// Check if GEN.md already exists
|
|
515
557
|
const memoryManager = agent.getMemoryManager();
|
|
516
558
|
const existingPath = await memoryManager.getExistingProjectMemoryPath(cwd);
|
|
517
559
|
let existingContent: string | undefined;
|
|
@@ -531,7 +573,7 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
531
573
|
|
|
532
574
|
// Build init prompt and run through agent
|
|
533
575
|
const initPrompt = buildInitPrompt(contextFiles, existingContent);
|
|
534
|
-
addHistory({ type: 'info', content: 'Generating
|
|
576
|
+
addHistory({ type: 'info', content: 'Generating GEN.md...' });
|
|
535
577
|
addHistory({ type: 'user', content: '/init' });
|
|
536
578
|
await runAgent(initPrompt);
|
|
537
579
|
return true;
|
|
@@ -565,6 +607,110 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
565
607
|
return true;
|
|
566
608
|
}
|
|
567
609
|
|
|
610
|
+
case 'plan': {
|
|
611
|
+
// Enter plan mode
|
|
612
|
+
await agent.enterPlanMode(arg);
|
|
613
|
+
setCurrentMode('plan');
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
case 'normal': {
|
|
618
|
+
// Exit to normal mode
|
|
619
|
+
if (agent.isPlanModeActive()) {
|
|
620
|
+
agent.exitPlanMode(false);
|
|
621
|
+
}
|
|
622
|
+
setCurrentMode('normal');
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
case 'accept': {
|
|
627
|
+
// Enter auto-accept mode
|
|
628
|
+
if (agent.isPlanModeActive()) {
|
|
629
|
+
agent.exitPlanMode(false);
|
|
630
|
+
}
|
|
631
|
+
setCurrentMode('accept');
|
|
632
|
+
return true;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
case 'changes': {
|
|
636
|
+
// List file changes (checkpoints)
|
|
637
|
+
const checkpointManager = getCheckpointManager();
|
|
638
|
+
if (!checkpointManager.hasCheckpoints()) {
|
|
639
|
+
addHistory({ type: 'info', content: '\nNo file changes in this session' });
|
|
640
|
+
} else {
|
|
641
|
+
addHistory({ type: 'info', content: '\n' + checkpointManager.formatCheckpointList(false) });
|
|
642
|
+
}
|
|
643
|
+
return true;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
case 'rewind': {
|
|
647
|
+
// Rewind file changes
|
|
648
|
+
const checkpointManager = getCheckpointManager();
|
|
649
|
+
|
|
650
|
+
if (!checkpointManager.hasCheckpoints()) {
|
|
651
|
+
addHistory({ type: 'info', content: 'No file changes to rewind' });
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (arg === 'all') {
|
|
656
|
+
// Rewind all changes
|
|
657
|
+
const result = await checkpointManager.rewind({ all: true });
|
|
658
|
+
|
|
659
|
+
// Build output message showing both successes and failures
|
|
660
|
+
const messages: string[] = ['']; // Start with empty line for spacing
|
|
661
|
+
|
|
662
|
+
if (result.revertedFiles.length > 0) {
|
|
663
|
+
const files = result.revertedFiles.map((f) => {
|
|
664
|
+
const fileName = f.path.split('/').pop() || f.path;
|
|
665
|
+
return ` • ${fileName} (${f.action})`;
|
|
666
|
+
}).join('\n');
|
|
667
|
+
messages.push(`Reverted ${result.revertedFiles.length} file(s):\n${files}`);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (result.errors.length > 0) {
|
|
671
|
+
const errors = result.errors.map((e) => {
|
|
672
|
+
const fileName = e.path.split('/').pop() || e.path;
|
|
673
|
+
return ` • ${fileName}: ${e.error}`;
|
|
674
|
+
}).join('\n');
|
|
675
|
+
messages.push(`\nFailed to revert ${result.errors.length} file(s):\n${errors}`);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (messages.length > 1) {
|
|
679
|
+
addHistory({ type: 'info', content: messages.join('\n') });
|
|
680
|
+
} else {
|
|
681
|
+
addHistory({ type: 'info', content: '\nNo changes to rewind' });
|
|
682
|
+
}
|
|
683
|
+
} else if (arg) {
|
|
684
|
+
// Rewind specific checkpoint by index
|
|
685
|
+
const index = parseInt(arg, 10);
|
|
686
|
+
if (!isNaN(index) && index >= 1) {
|
|
687
|
+
const checkpoints = checkpointManager.getCheckpoints();
|
|
688
|
+
if (index <= checkpoints.length) {
|
|
689
|
+
const checkpoint = checkpoints[index - 1];
|
|
690
|
+
const result = await checkpointManager.rewind({ checkpointId: checkpoint.id });
|
|
691
|
+
if (result.success && result.revertedFiles.length > 0) {
|
|
692
|
+
const f = result.revertedFiles[0];
|
|
693
|
+
const fileName = f.path.split('/').pop() || f.path;
|
|
694
|
+
addHistory({ type: 'info', content: `\nReverted: ${fileName} (${f.action})` });
|
|
695
|
+
} else if (result.errors.length > 0) {
|
|
696
|
+
const fileName = result.errors[0].path.split('/').pop() || result.errors[0].path;
|
|
697
|
+
addHistory({ type: 'info', content: `\nFailed: ${fileName} - ${result.errors[0].error}` });
|
|
698
|
+
} else {
|
|
699
|
+
addHistory({ type: 'info', content: '\nFailed to rewind change' });
|
|
700
|
+
}
|
|
701
|
+
} else {
|
|
702
|
+
addHistory({ type: 'info', content: '\nInvalid index: ${index}' });
|
|
703
|
+
}
|
|
704
|
+
} else {
|
|
705
|
+
addHistory({ type: 'info', content: '\nUsage: /rewind [n|all]' });
|
|
706
|
+
}
|
|
707
|
+
} else {
|
|
708
|
+
// Show changes and usage in one message
|
|
709
|
+
addHistory({ type: 'info', content: '\n' + checkpointManager.formatCheckpointList(true) });
|
|
710
|
+
}
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
|
|
568
714
|
default:
|
|
569
715
|
return false;
|
|
570
716
|
}
|
|
@@ -661,9 +807,13 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
661
807
|
streamingTextRef.current = '';
|
|
662
808
|
setStreamingText('');
|
|
663
809
|
}
|
|
664
|
-
// Add completion message with duration
|
|
810
|
+
// Add completion message with duration and cost info
|
|
665
811
|
const durationMs = Date.now() - startTime;
|
|
666
|
-
addHistory({
|
|
812
|
+
addHistory({
|
|
813
|
+
type: 'completion',
|
|
814
|
+
content: '',
|
|
815
|
+
meta: { durationMs, usage: event.usage, cost: event.cost },
|
|
816
|
+
});
|
|
667
817
|
setProcessingStartTime(undefined);
|
|
668
818
|
break;
|
|
669
819
|
}
|
|
@@ -707,8 +857,8 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
707
857
|
}
|
|
708
858
|
|
|
709
859
|
// Handle # prefix for quick memory adds
|
|
710
|
-
// ## note -> user memory (~/.
|
|
711
|
-
// # note -> project memory (./
|
|
860
|
+
// ## note -> user memory (~/.gen/GEN.md)
|
|
861
|
+
// # note -> project memory (./GEN.md)
|
|
712
862
|
if (trimmed.startsWith('#') && !trimmed.startsWith('#!/')) {
|
|
713
863
|
const memoryManager = agent.getMemoryManager();
|
|
714
864
|
let level: 'user' | 'project';
|
|
@@ -774,9 +924,33 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
774
924
|
// Clear pending tool (stop spinner)
|
|
775
925
|
pendingToolRef.current = null;
|
|
776
926
|
setPendingTool(null);
|
|
927
|
+
// Clean up incomplete tool_use messages to prevent API errors
|
|
928
|
+
agent.cleanupIncompleteMessages();
|
|
777
929
|
addHistory({ type: 'info', content: 'Interrupted' });
|
|
778
930
|
}
|
|
779
931
|
|
|
932
|
+
// Shift+Tab to cycle modes: normal → plan → accept → normal
|
|
933
|
+
if (key.shift && key.tab && !isProcessing && !confirmState && !questionState && !planApprovalState) {
|
|
934
|
+
const cycleMode = async () => {
|
|
935
|
+
const nextMode: Record<ModeType, ModeType> = {
|
|
936
|
+
normal: 'plan',
|
|
937
|
+
plan: 'accept',
|
|
938
|
+
accept: 'normal',
|
|
939
|
+
};
|
|
940
|
+
const newMode = nextMode[currentMode];
|
|
941
|
+
|
|
942
|
+
// Handle plan mode transitions
|
|
943
|
+
if (newMode === 'plan') {
|
|
944
|
+
await agent.enterPlanMode();
|
|
945
|
+
} else if (currentMode === 'plan') {
|
|
946
|
+
agent.exitPlanMode(false);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
setCurrentMode(newMode);
|
|
950
|
+
};
|
|
951
|
+
cycleMode();
|
|
952
|
+
}
|
|
953
|
+
|
|
780
954
|
// Command suggestion navigation
|
|
781
955
|
if (showCmdSuggestions && cmdSuggestions.length > 0) {
|
|
782
956
|
if (key.upArrow) {
|
|
@@ -853,7 +1027,13 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
853
1027
|
}
|
|
854
1028
|
return <InfoMessage text={item.content} />;
|
|
855
1029
|
case 'completion':
|
|
856
|
-
return
|
|
1030
|
+
return (
|
|
1031
|
+
<CompletionMessage
|
|
1032
|
+
durationMs={(item.meta?.durationMs as number) || 0}
|
|
1033
|
+
usage={item.meta?.usage as { inputTokens: number; outputTokens: number } | undefined}
|
|
1034
|
+
cost={item.meta?.cost as CostEstimate | undefined}
|
|
1035
|
+
/>
|
|
1036
|
+
);
|
|
857
1037
|
case 'todos':
|
|
858
1038
|
return <TodoList todos={item.meta?.todos as ReturnType<typeof getTodos>} />;
|
|
859
1039
|
default:
|
|
@@ -862,7 +1042,7 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
862
1042
|
};
|
|
863
1043
|
|
|
864
1044
|
return (
|
|
865
|
-
<Box flexDirection="column">
|
|
1045
|
+
<Box flexDirection="column" paddingBottom={2}>
|
|
866
1046
|
<Static items={history}>
|
|
867
1047
|
{(item) => <Box key={item.id}>{renderHistoryItem(item)}</Box>}
|
|
868
1048
|
</Static>
|
|
@@ -889,10 +1069,27 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
889
1069
|
/>
|
|
890
1070
|
)}
|
|
891
1071
|
|
|
1072
|
+
{/* Plan approval UI */}
|
|
1073
|
+
{planApprovalState && (
|
|
1074
|
+
<Box marginTop={1}>
|
|
1075
|
+
<PlanApproval
|
|
1076
|
+
planSummary={planApprovalState.planSummary}
|
|
1077
|
+
requestedPermissions={planApprovalState.requestedPermissions}
|
|
1078
|
+
filesToChange={planApprovalState.filesToChange}
|
|
1079
|
+
planFilePath={planApprovalState.planFilePath}
|
|
1080
|
+
onDecision={(option, customInput) => {
|
|
1081
|
+
planApprovalState.resolve(option, customInput);
|
|
1082
|
+
setPlanApprovalState(null);
|
|
1083
|
+
}}
|
|
1084
|
+
/>
|
|
1085
|
+
</Box>
|
|
1086
|
+
)}
|
|
1087
|
+
|
|
892
1088
|
{showModelSelector && (
|
|
893
1089
|
<Box marginTop={1}>
|
|
894
1090
|
<ModelSelector
|
|
895
1091
|
currentModel={currentModel}
|
|
1092
|
+
currentProvider={currentProvider}
|
|
896
1093
|
onSelect={handleModelSelect}
|
|
897
1094
|
onCancel={handleModelCancel}
|
|
898
1095
|
listModels={() => agent.listModels()}
|
|
@@ -907,6 +1104,7 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
907
1104
|
onProviderChange={(providerId, model) => {
|
|
908
1105
|
agent.setModel(model);
|
|
909
1106
|
setCurrentModel(model);
|
|
1107
|
+
setCurrentProvider(agent.getProvider());
|
|
910
1108
|
addHistory({ type: 'info', content: `Switched to ${providerId}: ${model}` });
|
|
911
1109
|
}}
|
|
912
1110
|
/>
|
|
@@ -914,7 +1112,7 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
914
1112
|
)}
|
|
915
1113
|
|
|
916
1114
|
{!confirmState && !questionState && !showModelSelector && !showProviderManager && (
|
|
917
|
-
<Box flexDirection="column" marginTop={
|
|
1115
|
+
<Box flexDirection="column" marginTop={2}>
|
|
918
1116
|
<PromptInput
|
|
919
1117
|
key={inputKey}
|
|
920
1118
|
value={input}
|
|
@@ -924,6 +1122,16 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
924
1122
|
{showCmdSuggestions && cmdSuggestions.length > 0 && (
|
|
925
1123
|
<CommandSuggestions input={input} selectedIndex={cmdSuggestionIndex} />
|
|
926
1124
|
)}
|
|
1125
|
+
{currentMode === 'plan' && !isProcessing && (
|
|
1126
|
+
<Text color={colors.warning} dimColor>
|
|
1127
|
+
{icons.modePlan} plan mode on (shift+tab to cycle)
|
|
1128
|
+
</Text>
|
|
1129
|
+
)}
|
|
1130
|
+
{currentMode === 'accept' && !isProcessing && (
|
|
1131
|
+
<Text color={colors.success} dimColor>
|
|
1132
|
+
{icons.modeAccept} accept edits on (shift+tab to cycle)
|
|
1133
|
+
</Text>
|
|
1134
|
+
)}
|
|
927
1135
|
</Box>
|
|
928
1136
|
)}
|
|
929
1137
|
|
|
@@ -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) =>
|
|
@@ -286,7 +288,7 @@ export function InfoMessage({ text, type = 'info' }: InfoMessageProps) {
|
|
|
286
288
|
const { color, icon } = config[type];
|
|
287
289
|
|
|
288
290
|
return (
|
|
289
|
-
<Box>
|
|
291
|
+
<Box marginTop={1}>
|
|
290
292
|
<Text color={color}>{icon} </Text>
|
|
291
293
|
<Text color={colors.textSecondary}>{text}</Text>
|
|
292
294
|
</Box>
|
|
@@ -346,16 +348,33 @@ function formatDuration(ms: number): string {
|
|
|
346
348
|
|
|
347
349
|
interface CompletionMessageProps {
|
|
348
350
|
durationMs: number;
|
|
351
|
+
usage?: {
|
|
352
|
+
inputTokens: number;
|
|
353
|
+
outputTokens: number;
|
|
354
|
+
};
|
|
355
|
+
cost?: CostEstimate;
|
|
349
356
|
}
|
|
350
357
|
|
|
351
|
-
export function CompletionMessage({ durationMs }: CompletionMessageProps) {
|
|
358
|
+
export function CompletionMessage({ durationMs, usage, cost }: CompletionMessageProps) {
|
|
352
359
|
// Pick a random verb (stable per render via useMemo would be better, but keep simple)
|
|
353
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
|
+
|
|
354
375
|
return (
|
|
355
376
|
<Box marginTop={1}>
|
|
356
|
-
<Text color={colors.textMuted}>
|
|
357
|
-
✻ {verb} for {formatDuration(durationMs)}
|
|
358
|
-
</Text>
|
|
377
|
+
<Text color={colors.textMuted}>{parts.join(' • ')}</Text>
|
|
359
378
|
</Box>
|
|
360
379
|
);
|
|
361
380
|
}
|