Merge pull request #1699 from entireio/feat/imported-sessions-in-session-list · Entire
Merge pull request #1699 from entireio/feat/imported-sessions-in-session-list
62a79dc→main·
computermode·1w ago·15 files·+609 added/-7 removed
feat(import): show imported sessions in `entire session list`
Changes
15
cmd/entire/cli
agent/types
Mtoken_usage.go+29
Atoken_usage_test.go+32
agentimport
- Magentimport.go+75 - Asession_state_test.go+231Mresume_picker.go+20
- Mresume_picker_test.go+34
session
- Aprompt.go+15 - Mstate.go+17 - Mstate_test.go+14Msessions.go+12/-1
- Msessions_test.go+42
strategy
- Mcleanup.go+6 - Aimported_session_test.go+70 - Mmanual_commit_session.go+9 - Mmanual_commit_types.go+3/-6
15 unmodified lines
```
// SubagentTokens contains token usage from spawned subagents (if any)
SubagentTokens *TokenUsage `json:"subagent_tokens,omitempty"`
}
// AddTokenUsage returns the sum of a and b, recursing into subagent usage.
// Either operand may be nil (treated as zero); the result is nil only when both
// are. Neither input is mutated.
func AddTokenUsage(a, b *TokenUsage) *TokenUsage {
if a == nil && b == nil {
return nil
}
sum := &TokenUsage{}
var aSub, bSub *TokenUsage
if a != nil {
sum.InputTokens = a.InputTokens
sum.CacheCreationTokens = a.CacheCreationTokens
sum.CacheReadTokens = a.CacheReadTokens
sum.OutputTokens = a.OutputTokens
sum.APICallCount = a.APICallCount
aSub = a.SubagentTokens
}
if b != nil {
sum.InputTokens += b.InputTokens
sum.CacheCreationTokens += b.CacheCreationTokens
sum.CacheReadTokens += b.CacheReadTokens
sum.OutputTokens += b.OutputTokens
sum.APICallCount += b.APICallCount
bSub = b.SubagentTokens
}
sum.SubagentTokens = AddTokenUsage(aSub, bSub)
return sum
}
```
```go
package types
import "testing"
func TestAddTokenUsage(t *testing.T) {
t.Parallel()
if got := AddTokenUsage(nil, nil); got != nil {
t.Errorf("AddTokenUsage(nil, nil) = %+v, want nil", got)
}
only := &TokenUsage{InputTokens: 3}
if got := AddTokenUsage(nil, only); got == nil || got.InputTokens != 3 {
t.Errorf("AddTokenUsage(nil, x) = %+v, want a copy of x", got)
}
if got := AddTokenUsage(only, nil); got == only {
t.Error("AddTokenUsage must not return an input pointer (would alias caller state)")
}
a := &TokenUsage{InputTokens: 1, OutputTokens: 2, APICallCount: 1, SubagentTokens: &TokenUsage{InputTokens: 10}}
b := &TokenUsage{InputTokens: 4, OutputTokens: 5, APICallCount: 2, SubagentTokens: &TokenUsage{InputTokens: 20}}
got := AddTokenUsage(a, b)
if got.InputTokens != 5 || got.OutputTokens != 7 || got.APICallCount != 3 {
t.Errorf("top-level sum = %+v", got)
}
if got.SubagentTokens == nil || got.SubagentTokens.InputTokens != 30 {
t.Errorf("subagent sum = %+v, want InputTokens 30", got.SubagentTokens)
}
if a.InputTokens != 1 || a.SubagentTokens.InputTokens != 10 {
t.Error("AddTokenUsage mutated an input")
}
}
package agentimport
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
"unicode/utf8"
"github.com/go-git/go-git/v6"
"github.com/entireio/cli/cmd/entire/cli/agent"
"github.com/entireio/cli/cmd/entire/cli/agent/types"
"github.com/entireio/cli/cmd/entire/cli/session"
"github.com/entireio/cli/cmd/entire/cli/strategy"
"github.com/entireio/cli/cmd/entire/cli/testutil"
)
// fakeImporter is the minimal Importer needed to exercise writeSessionState.
type fakeImporter struct{}
func (fakeImporter) Name() string { return string(agent.AgentNameClaudeCode) }
func (fakeImporter) AgentType() types.AgentType { return agent.AgentTypeClaudeCode }
func (fakeImporter) Discover(_, _ string, _ time.Time, _ []string) ([]SessionFile, error) {
return nil, nil
}
func (fakeImporter) SplitTurns(_ SessionFile, _ []byte) ([]Turn, error) { return nil, nil }
const (
// logsOnlyScanLimit is the maximum number of commits to scan for logs-only points.
logsOnlyScanLimit = 50
// maxLastPromptRunes is the maximum rune length for LastPrompt stored in session state.
maxLastPromptRunes = 100
)
// truncatePromptForStorage collapses whitespace and truncates a user prompt
// for storage in LastPrompt. It delegates to session.TruncatePromptForStorage
// so imports and live sessions format the field identically.
func truncatePromptForStorage(prompt string) string {
return stringutil.TruncateRunes(stringutil.CollapseWhitespace(prompt), maxLastPromptRunes, "...")
}