Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/opencode/src/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -80,6 +81,7 @@ export namespace App {
data,
root,
cwd: input.cwd,
promptHistory: path.join(data, "prompt-history"),
},
}
const app = {
Expand Down
28 changes: 15 additions & 13 deletions packages/sdk/go/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
54 changes: 37 additions & 17 deletions packages/tui/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type App struct {
Providers []opencode.Provider
Version string
StatePath string
PromptHistoryPath string
Config *opencode.Config
Client *opencode.Client
State *State
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
Expand Down
34 changes: 34 additions & 0 deletions packages/tui/internal/app/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Loading