diff --git a/packages/opencode/src/app/app.ts b/packages/opencode/src/app/app.ts index fc7f49cb951..459785de87e 100644 --- a/packages/opencode/src/app/app.ts +++ b/packages/opencode/src/app/app.ts @@ -20,6 +20,7 @@ export namespace App { root: z.string(), cwd: z.string(), state: z.string(), + promptHistory: z.string(), }), time: z.object({ initialized: z.number().optional(), @@ -80,6 +81,7 @@ export namespace App { data, root, cwd: input.cwd, + promptHistory: path.join(data, "prompt-history"), }, } const app = { diff --git a/packages/sdk/go/app.go b/packages/sdk/go/app.go index 36d5be77fbf..1f22efac689 100644 --- a/packages/sdk/go/app.go +++ b/packages/sdk/go/app.go @@ -248,23 +248,25 @@ func (r appJSON) RawJSON() string { } type AppPath struct { - Config string `json:"config,required"` - Cwd string `json:"cwd,required"` - Data string `json:"data,required"` - Root string `json:"root,required"` - State string `json:"state,required"` - JSON appPathJSON `json:"-"` + Config string `json:"config,required"` + Cwd string `json:"cwd,required"` + Data string `json:"data,required"` + Root string `json:"root,required"` + State string `json:"state,required"` + PromptHistory string `json:"promptHistory,required"` + JSON appPathJSON `json:"-"` } // appPathJSON contains the JSON metadata for the struct [AppPath] type appPathJSON struct { - Config apijson.Field - Cwd apijson.Field - Data apijson.Field - Root apijson.Field - State apijson.Field - raw string - ExtraFields map[string]apijson.Field + Config apijson.Field + Cwd apijson.Field + Data apijson.Field + Root apijson.Field + State apijson.Field + PromptHistory apijson.Field + raw string + ExtraFields map[string]apijson.Field } func (r *AppPath) UnmarshalJSON(data []byte) (err error) { diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go index af8157adcde..232dce2ebef 100644 --- a/packages/tui/internal/app/app.go +++ b/packages/tui/internal/app/app.go @@ -32,6 +32,7 @@ type App struct { Providers []opencode.Provider Version string StatePath string + PromptHistoryPath string Config *opencode.Config Client *opencode.Client State *State @@ -124,6 +125,14 @@ func New( SaveState(appStatePath, appState) } + // Load project-specific prompt history + promptHistoryPath := appInfo.Path.PromptHistory + promptHistory, err := LoadPromptHistory(promptHistoryPath) + if err != nil { + promptHistory = make([]Prompt, 0) + } + appState.MessageHistory = promptHistory + if appState.AgentModel == nil { appState.AgentModel = make(map[string]AgentModel) } @@ -184,22 +193,23 @@ func New( slog.Debug("Loaded config", "config", configInfo) app := &App{ - Info: appInfo, - Agents: agents, - Version: version, - StatePath: appStatePath, - Config: configInfo, - State: appState, - Client: httpClient, - AgentIndex: agentIndex, - Session: &opencode.Session{}, - Messages: []Message{}, - Commands: commands.LoadFromConfig(configInfo), - InitialModel: initialModel, - InitialPrompt: initialPrompt, - InitialAgent: initialAgent, - InitialSession: initialSession, - ScrollSpeed: int(configInfo.Tui.ScrollSpeed), + Info: appInfo, + Agents: agents, + Version: version, + StatePath: appStatePath, + PromptHistoryPath: appInfo.Path.PromptHistory, + Config: configInfo, + State: appState, + Client: httpClient, + AgentIndex: agentIndex, + Session: &opencode.Session{}, + Messages: []Message{}, + Commands: commands.LoadFromConfig(configInfo), + InitialModel: initialModel, + InitialPrompt: initialPrompt, + InitialAgent: initialAgent, + InitialSession: initialSession, + ScrollSpeed: int(configInfo.Tui.ScrollSpeed), } return app, nil @@ -671,10 +681,20 @@ func (a *App) HasAnimatingWork() bool { func (a *App) SaveState() tea.Cmd { return func() tea.Msg { - err := SaveState(a.StatePath, a.State) + // Save main state (without prompt history) + stateToSave := *a.State + stateToSave.MessageHistory = make([]Prompt, 0) // Don't save prompt history in main state + err := SaveState(a.StatePath, &stateToSave) if err != nil { slog.Error("Failed to save state", "error", err) } + + // Save prompt history separately + err = SavePromptHistory(a.PromptHistoryPath, a.State.MessageHistory) + if err != nil { + slog.Error("Failed to save prompt history", "error", err) + } + return nil } } diff --git a/packages/tui/internal/app/state.go b/packages/tui/internal/app/state.go index cc65eea5eee..736eec5e73c 100644 --- a/packages/tui/internal/app/state.go +++ b/packages/tui/internal/app/state.go @@ -172,3 +172,37 @@ func LoadState(filePath string) (*State, error) { return &state, nil } + +// LoadPromptHistory loads prompt history from a separate project-specific file +func LoadPromptHistory(filePath string) ([]Prompt, error) { + var history struct { + MessageHistory []Prompt `toml:"message_history"` + } + if _, err := toml.DecodeFile(filePath, &history); err != nil { + return make([]Prompt, 0), nil // Return empty on any error + } + for _, prompt := range history.MessageHistory { + for _, att := range prompt.Attachments { + att.RestoreSourceType() + } + } + return history.MessageHistory, nil +} + +// SavePromptHistory saves prompt history to a separate project-specific file +func SavePromptHistory(filePath string, history []Prompt) error { + file, err := os.Create(filePath) + if err != nil { + return err + } + defer file.Close() + + writer := bufio.NewWriter(file) + err = toml.NewEncoder(writer).Encode(struct { + MessageHistory []Prompt `toml:"message_history"` + }{history}) + if err != nil { + return err + } + return writer.Flush() +}