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

    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, "...")
}