import: set factory CreatedAt from transcript modtime · Entire
import: set factory CreatedAt from transcript modtime
983cd32→main·
computermode·3w ago·2 files·+19 added/-2 removed
Droid envelopes carry no per-message timestamp (the agent stamps events with time.Now() at hook time), so imported factory turns had a zero CreatedAt. Fall back to the transcript file's modtime, matching the Gemini importer.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Sessions
7897bb6707d0View transcript
Changes
2
cmd/entire/cli/agentimport
Mfactory.go+10/-2
Mfactory_test.go+9
2 unmodified lines
3
4
5
6
7
8
9
30 unmodified lines
40
41
42
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
10 unmodified lines
67
68
69
62
70
71
72
73
2 unmodified lines
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
30 unmodified lines
// SplitTurns produces one Turn per user-prompt envelope, bounded by the next.
// Token usage (including spawned subagents) is delegated to the Factory agent;
// the model is read once from the session's adjacent settings file.
// the model is read once from the session's adjacent settings file. Droid
// envelopes carry no per-message timestamp (the agent stamps events with
// time.Now() at hook time), so every turn falls back to the transcript file's
// modtime — the same fallback the Gemini importer uses.
func (factoryImporter) SplitTurns(sf SessionFile, full []byte) ([]Turn, error) {
subagentsDir := filepath.Join(filepath.Dir(sf.Path), sf.SessionID, "subagents")
model := factoryaidroid.ExtractModelFromTranscript(sf.Path)
var createdAt time.Time
if info, statErr := os.Stat(sf.Path); statErr == nil {
createdAt = info.ModTime()
}
ag := &factoryaidroid.FactoryAIDroidAgent{}
return splitLineTurns(splitRawLines(full),
func(raw []byte) bool { _, ok := factoryPromptText(raw); return ok },
10 unmodified lines
return nil, nil
}
prompt, _ := factoryPromptText(rawLines[start])
return &Turn{UUID: env.ID, Prompt: prompt, Model: model, Tokens: tokens}, nil
return &Turn{UUID: env.ID, Prompt: prompt, Model: model, CreatedAt: createdAt, Tokens: tokens}, nil
})
}
Mcmd/entire/cli/agentimport/factory.go+10/-2
56 unmodified lines
57
58
59
60
61
62
63
64
65
66
67
68
18 unmodified lines
87
88
89
90
91
92
93
94
95
56 unmodified lines
if err := os.WriteFile(sessPath, full, 0o644); err != nil {
t.Fatal(err)
}
// Droid carries no per-message timestamp, so turns fall back to the file
// modtime; pin it so we can assert CreatedAt is populated from it.
modTime := time.Date(2026, 6, 24, 9, 30, 0, 0, time.UTC)
if err := os.Chtimes(sessPath, modTime, modTime); err != nil {
t.Fatal(err)
}
// Model comes from the adjacent <session>.settings.json, not the transcript.
if err := os.WriteFile(filepath.Join(dir, "sess.settings.json"), []byte(`{"model":"custom:Gemini-2.5-Pro-0"}`), 0o644); err != nil {
t.Fatal(err)
}
18 unmodified lines
if turns[0].Model != "Gemini-2.5-Pro-0" {
t.Errorf("turn0 model = %q, want Gemini-2.5-Pro-0 (cleaned from settings)", turns[0].Model)
}
if !turns[0].CreatedAt.Equal(modTime) {
t.Errorf("turn0 CreatedAt = %v, want file modtime %v", turns[0].CreatedAt, modTime)
}
if turns[0].Tokens == nil || turns[0].Tokens.OutputTokens != 5 {
t.Errorf("turn0 tokens not bounded to its own turn: %+v", turns[0].Tokens)
}