| 1 | package historycatalog |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | |
| 6 | "reasonix/internal/agent" |
| 7 | "reasonix/internal/store" |
| 8 | ) |
| 9 | |
| 10 | func sourceProjectionUnchanged(queryErr error, oldContent, content, oldMeta, meta, oldDigest, digest string) bool { |
| 11 | return queryErr == nil && oldContent == content && oldMeta == meta && (digest == "" || digest == oldDigest) |
| 12 | } |
| 13 | |
| 14 | func (c *Catalog) tryAppendPath(ctx context.Context, root Root, path string, generation int64, appendFrom, |
| 15 | oldMessageCount int, oldRevision, revision int64, digest, contentFingerprint, metaFingerprint string) (bool, error) { |
| 16 | if appendFrom != oldMessageCount || revision != oldRevision+1 { |
| 17 | return false, nil |
| 18 | } |
| 19 | index, err := agent.LoadSessionDisplayIndex(store.SessionDisplayIndex(path)) |
| 20 | if err != nil || !index.RevisionKnown || index.Revision != revision || index.ContentDigest != digest || index.MessageCount < appendFrom { |
| 21 | return false, nil |
| 22 | } |
| 23 | tail, checkedIndex, err := agent.LoadSessionDisplayMessageRange(path, appendFrom, index.MessageCount) |
| 24 | if err != nil || checkedIndex.ContentDigest != digest || checkedIndex.Revision != revision { |
| 25 | return false, nil |
| 26 | } |
| 27 | meta, _, _ := agent.LoadBranchMeta(path) |
| 28 | lastActivity := max(int64(0), agent.SessionContentModTime(path).UnixMilli()) |
| 29 | tx, err := c.db.BeginTx(ctx, nil) |
| 30 | if err != nil { |
| 31 | return false, err |
| 32 | } |
| 33 | result, err := tx.ExecContext(ctx, `UPDATE history_sources SET root=?,source=?,scope=?,workspace_root=?,content_revision=?, |
| 34 | content_digest=?,content_fingerprint=?,meta_fingerprint=?,message_count=?,indexed_message_count=?,custom_title=?,topic_id=?, |
| 35 | topic_title=?,preview=?,created_at=?,last_activity_at=?,health='ok',missing_since=0, |
| 36 | seen_generation=CASE WHEN ?>0 THEN ? ELSE seen_generation END,last_error='' |
| 37 | WHERE path=? AND content_revision=? AND indexed_message_count=?`, root.Path, root.Source, root.Scope, root.WorkspaceRoot, |
| 38 | revision, digest, contentFingerprint, metaFingerprint, index.MessageCount, index.MessageCount, meta.CustomTitle, meta.TopicID, |
| 39 | meta.TopicTitle, meta.Preview, meta.CreatedAt.UnixMilli(), lastActivity, generation, generation, path, oldRevision, oldMessageCount) |
| 40 | if err != nil { |
| 41 | _ = tx.Rollback() |
| 42 | return false, err |
| 43 | } |
| 44 | updated, _ := result.RowsAffected() |
| 45 | if updated != 1 { |
| 46 | _ = tx.Rollback() |
| 47 | return false, nil |
| 48 | } |
| 49 | for _, doc := range documents(tail) { |
| 50 | result, err := tx.ExecContext(ctx, `INSERT INTO history_documents(source_path,message_index,part_index,role,kind,tool_name,token_count) |
| 51 | VALUES(?,?,?,?,?,?,?)`, path, appendFrom+doc.message, doc.part, doc.role, doc.kind, doc.tool, doc.count) |
| 52 | if err != nil { |
| 53 | _ = tx.Rollback() |
| 54 | return false, err |
| 55 | } |
| 56 | rowID, err := result.LastInsertId() |
| 57 | if err != nil { |
| 58 | _ = tx.Rollback() |
| 59 | return false, err |
| 60 | } |
| 61 | if _, err := tx.ExecContext(ctx, `INSERT INTO history_fts(rowid,terms) VALUES(?,?)`, rowID, doc.terms); err != nil { |
| 62 | _ = tx.Rollback() |
| 63 | return false, err |
| 64 | } |
| 65 | } |
| 66 | newRevision, err := bump(ctx, tx) |
| 67 | if err != nil { |
| 68 | _ = tx.Rollback() |
| 69 | return false, err |
| 70 | } |
| 71 | if err := tx.Commit(); err != nil { |
| 72 | return false, err |
| 73 | } |
| 74 | c.publish(newRevision, []string{root.Path}, "source-appended") |
| 75 | return true, nil |
| 76 | } |
| 77 |