Documentation
¶
Index ¶
- type ChatContextManager
- func (m *ChatContextManager) AppendMessages(ctx context.Context, msgs ...*schema.Message) error
- func (m *ChatContextManager) BeginRun(ctx context.Context, userMessages ...*schema.Message) (bool, error)
- func (m *ChatContextManager) CountMessages(ctx context.Context) (int, error)
- func (m *ChatContextManager) LoadAllMessages(ctx context.Context) ([]*schema.Message, error)
- func (m *ChatContextManager) MessagesFingerprint(ctx context.Context) (repository.MessagesFingerprint, error)
- func (m *ChatContextManager) ReplacePlaceholderToolResult(ctx context.Context, toolCallID string, real *schema.Message) (bool, error)
- type SessionToucher
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ChatContextManager ¶
type ChatContextManager struct {
// contains filtered or unexported fields
}
func New ¶
func New(repo repository.ChatContextRepo, toucher SessionToucher, sessionID string) *ChatContextManager
New builds a manager bound to one session. toucher + sessionID are used by BeginRun to bump session meta's UpdatedAt; passing nil / "" disables the touch (used by unit tests that stub only ChatContextRepo).
func (*ChatContextManager) AppendMessages ¶
func (*ChatContextManager) BeginRun ¶
func (m *ChatContextManager) BeginRun(ctx context.Context, userMessages ...*schema.Message) (bool, error)
BeginRun is the shared "user message entry" hook for every agent path (acp / shell-persist / future). It performs three disk operations before handing control back to the caller:
- Scans messages.jsonl in full and truncates the earliest orphan round — any assistant message whose ToolCalls contain ids without a matching role=tool result (real or placeholder). Truncation is atomic via repo.ReplaceMessages (write-to-temp + rename). No-op when the tail is clean.
- Appends the user messages — UNLESS every supplied user message carries Extra[msgextra.KeyPrePersisted], meaning an upstream caller (graph Prompt/评估 nodes) already wrote it to disk at enqueue time. In that case the append is skipped (the row exists) but step 1's truncate still runs, so the message is persisted exactly once.
- Touches session meta's UpdatedAt so the UI's "last active" field reflects the new turn. No-op when no SessionRepo was wired.
This cleans up residue from non-graceful exits (SIGKILL / panic / flush write failures) before the new Run sees the on-disk history, keeping tool_use / tool_result paired in every flush downstream.
Returns truncated=true iff step 1 actually rewrote the file. ACP callers use this to discard their in-memory subprocess session — once disk history diverges from the subprocess's view of the conversation, continuing to prompt the old session would feed the model an inconsistent tail. Callers that don't own a subprocess session can ignore the flag.
This is intentionally kept in manager.go (not in each path's Run) so the invariant "append user message only after orphan tail is gone" cannot drift between paths. Read-side LoadMessagesForLLM does no further tail scrubbing.
Truncation failure is fatal and propagates back to the caller WITHOUT appending the user message. Appending on top of a still-orphaned tail is a data-loss hazard: the next run's truncate would succeed from the same orphan position and cut the freshly-appended user message along with it. Returning here lets the caller surface the error (the Run entry points both wrap it as "persist user messages failed") and the user can retry, at which point the next BeginRun will re-attempt the truncate from a clean starting point.
Concurrency: truncate+append run inside repo.WithLock so independent ChatContextRepo instances pointed at the same session directory (acp / shell-persist) cannot interleave a write between the two steps and corrupt history. The lock is owned by the repository layer (repository/session_locks.go) so every write entry-point on the same sessionDir shares it, regardless of which package built the repo instance.
func (*ChatContextManager) CountMessages ¶
func (m *ChatContextManager) CountMessages(ctx context.Context) (int, error)
CountMessages returns the on-disk message count. Errors are surfaced to the caller rather than silenced, because every call site feeds a correctness-sensitive ACP drift / sync decision: a swallowed I/O or parse failure would be indistinguishable from "empty history" and trigger a spurious subprocess reset that drops conversation context.
func (*ChatContextManager) LoadAllMessages ¶
LoadAllMessages returns the full on-disk mirror history for this session. The mirror is rebuilt from ACP events; any history compression happens inside the agent subprocess and is invisible to this read path.
func (*ChatContextManager) MessagesFingerprint ¶
func (m *ChatContextManager) MessagesFingerprint(ctx context.Context) (repository.MessagesFingerprint, error)
MessagesFingerprint returns the (count, content-hash) pair the ACP drift check uses to detect cross-path mutations. Wraps the repo so errors carry a consistent prefix; ctx gates both the lock acquisition (via the repo's ctx-aware mutex) and pre-IO short-circuit checks.
func (*ChatContextManager) ReplacePlaceholderToolResult ¶
func (m *ChatContextManager) ReplacePlaceholderToolResult(ctx context.Context, toolCallID string, real *schema.Message) (bool, error)
ReplacePlaceholderToolResult swaps a previously-persisted placeholder tool_result (written by the round builder on an eager superseded flush) with the real terminal result that arrived late. Logs at debug on success, info when the placeholder is gone (already stitched or cut by a truncate), and error on I/O failure. Callers typically pair this with an in-memory fix-up of the builder's completedMessages so a subsequent CollectMessages sees the real result too.
type SessionToucher ¶
SessionToucher is the narrow interface chatctx uses to bump a session's UpdatedAt without directly owning session metadata storage. Satisfied by services/session.Service so every UpdatedAt write funnels through the single in-memory-and-disk writer.