host

package
v1.6.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 5, 2026 License: Apache-2.0 Imports: 45 Imported by: 0

Documentation

Index

Constants

View Source
const (
	CoCreateProgressThinking = "thinking"
	CoCreateProgressReply    = "reply"
)

CoCreateProgressKind identifies the content type of a streaming callback.

View Source
const StreamClearSentinel = "\x00\x00CLEAR\x00\x00"

StreamClearSentinel gửi một mục qua streamCh để báo hiệu "xoá round đang stream". Không dùng clearCh riêng nữa — hai kênh không có thứ tự khiến header ✻ hay rơi xuống cuối round trước.

Variables

View Source
var ErrBookExists = errors.New("truyện đã tồn tại")

ErrBookExists báo thư mục truyện đã tồn tại. Tách riêng để phía gọi (đặt tên ngẫu nhiên) phân biệt "trùng tên, thử tên khác" với lỗi thật bằng errors.Is thay vì so chuỗi thông báo.

View Source
var ErrBookInUse = errors.New("Thư mục truyện đã bị một instance " + buildversion.AppName + " khác chiếm")

ErrBookInUse nghĩa là cùng thư mục truyện đã bị một tiến trình khác chiếm. Nêu đúng tên chương trình từ AppName, vì đây là thông báo người dùng đọc được và phải khớp với tên binary họ sẽ chạy.

View Source
var ErrNeedBookLanguage = errors.New("book language not set")

ErrNeedBookLanguage báo truyện chưa khoá ngôn ngữ sáng tác và không thể hỏi (headless). Tách riêng để lớp trên biết đây là "cần người dùng chỉ định", không phải lỗi hệ thống — nên thông báo phải khác, và phải nêu cách chỉ định.

Functions

func ErrNeedBookLanguageMessage added in v1.5.3

func ErrNeedBookLanguageMessage(bookDir string) error

ErrNeedBookLanguageMessage là câu chữ cho trường hợp headless gặp truyện chưa khoá ngôn ngữ. Thân câu chữ nằm ở bootstrap vì main.go cần gọi trước khi dựng Host, không import host được.

func LoadBundleFor added in v1.5.3

func LoadBundleFor(cfg bootstrap.Config, outDir, lang string) assets.Bundle

LoadBundleFor dựng bộ tài nguyên theo ngôn ngữ đã quyết.

Tách hàm để dùng chung cho lúc khởi động và lúc chuyển truyện: nếu hai chỗ tự dựng khác nhau thì một chỗ sẽ quên ApplyLanguage — đúng loại lỗi từng xảy ra với Arbiter. Truyền LANG ĐÃ QUYẾT vào, không tự suy ra, để người gọi là nơi duy nhất quyết định (khoá theo truyện hay mặc định cấu hình).

func LogEvent

func LogEvent(ev Event)

LogEvent records one UI / runtime event through the process logger. Summary is the display text; Detail is the full diagnostic payload and is used as the log body whenever it is present.

func MaskAPIKey

func MaskAPIKey(value string) string

MaskAPIKey chỉ giữ đầu-cuối vừa đủ nhận diện chứng thực; key ngắn che hết. TUI chỉ nhận kết quả này, không bao giờ giữ API Key đầy đủ trong cấu hình.

func StreamToolHeader added in v1.5.2

func StreamToolHeader(tool string) string

StreamToolHeader trả tiêu đề khối mà TUI dựng cho một tool ("✻ …"), hoặc rỗng nếu tool không có tiêu đề riêng (ví dụ draft_chapter chỉ hiện nội dung). Dùng để TUI biết trước chuỗi nào sẽ tới, phục vụ test; vòng đời thật vẫn do extractor ghi.

Types

type APIKeyAction

type APIKeyAction string
const (
	APIKeyKeep    APIKeyAction = "keep"
	APIKeyReplace APIKeyAction = "replace"
	APIKeyClear   APIKeyAction = "clear"
)

type AgentCacheStat

type AgentCacheStat struct {
	Role            string
	Model           string
	Input           int
	Output          int
	CacheRead       int
	CacheWrite      int
	Cost            float64
	Saved           float64
	CacheCapable    bool
	RecentCacheRead int
	RecentInput     int
	RecentSamples   int
}

AgentCacheStat is a single agent's cumulative cache hits (projected into the left column). HitRate = CacheRead / Input; Input already carries the "includes CacheRead" semantics at the litellm layer.

CacheCapable distinguishes the two kinds of 0% hit rate:

  • true -> the model supports prompt cache, 0% means a badly designed prompt or an unstable prefix, and needs optimizing
  • false -> the model/provider does not support prompt cache, 0% is expected and needs no investigation

Recent* is the hit data of the sliding window (the last N calls); comparing it with the cumulative total identifies "an early-drag problem" vs "a steady low hit rate".

type AgentContextSnapshot

type AgentContextSnapshot struct {
	Tokens          int
	ContextWindow   int
	Percent         float64
	Scope           string
	Strategy        string
	ActiveMessages  int
	SummaryMessages int
	CompactedCount  int
	KeptCount       int
}

AgentContextSnapshot is the Agent context usage.

type AgentSnapshot

type AgentSnapshot struct {
	Name      string
	State     string
	TaskID    string
	TaskKind  string
	Summary   string
	Tool      string
	Turn      int
	Context   AgentContextSnapshot
	UpdatedAt time.Time
}

AgentSnapshot is the display projection of an Agent's state.

type AgentUsage

type AgentUsage struct {
	Role            string
	Model           string
	Input           int
	Output          int
	CacheRead       int
	CacheWrite      int
	Cost            float64
	Saved           float64
	CacheCapable    bool
	RecentCacheRead int
	RecentInput     int
	RecentSamples   int
}

AgentUsage is a snapshot of one agent's cumulative usage (exposed to the UI).

type Book added in v1.5.2

type Book struct {
	// Dir là đường dẫn tuyệt đối tới thư mục truyện.
	Dir string
	// Name là tên lấy từ meta/book.json, rơi về tên thư mục khi chưa có sách.
	Name string
	// Chapters là số chương đã chốt.
	Chapters int
	// Words là tổng số chữ đã viết.
	Words int
	// HasOutline cho biết đã có tiền đề/dàn ý chưa.
	HasOutline bool
}

Book là một thư mục truyện trong cây đầu ra.

type BudgetSentinel

type BudgetSentinel struct {
	// contains filtered or unexported fields
}

BudgetSentinel theo dõi chi phí tích luỹ và thực thi chính sách ngân sách của người dùng (khối budget trong cấu hình).

Định vị hợp lý (architecture.md §8.3/§10): không đánh giá hành vi model — dừng khi vượt hạn tương đương Abort thủ công của người dùng vào đúng thời điểm đó, Host chỉ thay họ thực thi một chỉ lệnh đã ký trước. Nó ảnh hưởng dòng điều khiển nên không phải quan sát viên, mà là thành phần chính sách của Host ngang hàng với flow.Dispatcher; tầng Route/công cụ không biết tới.

Thời điểm dừng: mặc định ở ranh giới tác vụ con (Host gọi đồng bộ HandleBoundary), không phí chương đang chạy dở; hardStop=true thì dừng ngay khi vượt. Xử lý ranh giới diễn ra trước khi flow.Dispatcher phát việc kế tiếp; tầng Route/công cụ không biết tới ngân sách.

func NewBudgetSentinel

func NewBudgetSentinel(cfg bootstrap.BudgetConfig, costNow func() float64, abort func(reason string), report func(level, summary string)) *BudgetSentinel

NewBudgetSentinel tạo cảnh báo ngân sách; trả nil khi chính sách không bật (mọi method đều nil-safe).

func (*BudgetSentinel) HandleBoundary

func (s *BudgetSentinel) HandleBoundary() bool

func (*BudgetSentinel) HandleEvent

func (s *BudgetSentinel) HandleEvent(ev agentcore.Event)

HandleEvent thực hiện lệnh dừng đang chờ ở ranh giới tác vụ con. Phải đăng ký trước Dispatcher. Không bỏ qua IsError — trả về lỗi cũng là một ranh giới, việc dừng không nên bị hoãn vì tác vụ con thất bại.

func (*BudgetSentinel) Limit

func (s *BudgetSentinel) Limit() float64

Limit trả về trần ngân sách (để TUI hiển thị); trả 0 khi không bật.

func (*BudgetSentinel) OnCost

func (s *BudgetSentinel) OnCost(total float64)

OnCost do UsageTracker gọi sau mỗi lần ghi sổ, mang theo chi phí tích luỹ mới nhất (ngoài khoá). Một lần callback có thể nhảy qua hai cấp (normal→warned→stopPending), mỗi tác dụng phụ kích hoạt đúng một lần.

func (*BudgetSentinel) Refuse

func (s *BudgetSentinel) Refuse() error

Refuse là kiểm tra trước khi chạy: trả lỗi từ chối nếu đã vượt ngân sách (được gọi ở các đường Start/Resume/Continue). Người dùng tăng ngân sách = cấp quyền lại, với cấu hình mới thì Refuse tự nhiên cho qua.

type ChapterAdvanceGate

type ChapterAdvanceGate struct {
	// contains filtered or unexported fields
}

ChapterAdvanceGate là thành phần chính sách tiến viết duy nhất của Host:

  • AdvanceHold: thực hiện lần tạm dừng một lần mà can thiệp này đã ký;
  • review permit: chặn chương mới tiến về phía trước khi chưa được cấp quyền.

Nó không tham gia Route, không diễn giải Task/Reason, cũng không đưa ra phán đoán văn chương.

func NewChapterAdvanceGate

func NewChapterAdvanceGate(s *store.Store, pause func(reason string), report func(level, summary string)) *ChapterAdvanceGate

func (*ChapterAdvanceGate) Allow

func (g *ChapterAdvanceGate) Allow(inst *flow.Instruction) (bool, error)

Allow thực hiện kiểm tra giấy phép cuối cùng trước khi phát việc cho Worker.

func (*ChapterAdvanceGate) HandleBoundary

func (g *ChapterAdvanceGate) HandleBoundary() bool

HandleBoundary tiêu thụ hold trúng và đối soát giấy phép chương. Trả true nghĩa là Engine phải dừng. Khi ở auto và không có hold thì chỉ đọc RunMeta một lần, không đụng Progress/PendingCommit/checkpoint.

type ChapterText added in v1.5.2

type ChapterText struct {
	Chapter   int
	Title     string
	Body      string
	WordCount int
	// Exists phân biệt "chương chưa viết" với "chương viết rỗng". Cả hai đều cho
	// Body rỗng, nhưng người đọc cần biết để không tưởng sách bị hỏng.
	Exists bool
	// Committed phân biệt đã chốt với còn là bản nháp. Người đọc cần biết để
	// không tưởng bản nháp là bản cuối.
	Committed bool
}

ChapterText là nội dung một chương đã chốt, kèm tiêu đề lấy từ dàn ý.

type CoCreateMessage

type CoCreateMessage struct {
	Role    string `json:"role"`
	Content string `json:"content"`
}

CoCreateMessage is a message of the co-create conversation.

type CoCreateReply

type CoCreateReply struct {
	Message     string
	Prompt      string
	Ready       bool
	Suggestions []string
	Raw         string
}

CoCreateReply is the LLM reply of the co-create conversation. Raw keeps the model's complete four-part original text, which is written back into history so the next round sees its own previous [DRAFT], and therefore really accumulates updates on top of the existing draft (with only Message, the model would re-summarise from the conversation every round). Suggestions is the AI's proactive "here is what you might want to say next"; when the user is stuck, a number key fills it into the input box in one keystroke.

type ConfiguredModel

type ConfiguredModel struct {
	Name          string
	ContextWindow int
	ContextSource bootstrap.ContextWindowSource
}

type DeleteBookResult added in v1.5.2

type DeleteBookResult struct {
	Dir       string
	Name      string
	Chapters  int
	Words     int
	SizeBytes int64
}

DeleteBookResult báo kết quả xoá, đủ để hiển thị xác nhận trước khi xoá.

type Event

type Event struct {
	ID         string    // shared by the start and the end of one call; empty for non-call events
	Time       time.Time // when it was first emitted (the start moment)
	FinishedAt time.Time // zero value = in progress; non-zero = finished
	Failed     bool      // finished but failed (only meaningful in the finished state)
	Category   string    // DISPATCH / MODEL / TOOL / DECISION / SYSTEM / REVIEW / CHECK / ERROR / CONTEXT
	Agent      string    // the agent that produced the event
	Summary    string
	// SummaryMsg là bản CHƯA dịch của Summary, dùng khi Summary là thông điệp hiển thị
	// có tham số: TUI dịch Key rồi mới điền Args, nên hiện đúng ở cả vi/en/zh.
	// Summary vẫn được giữ (đã điền sẵn) cho log và cho code đọc trực tiếp.
	// Nil nghĩa là Summary là dữ liệu thô, không dịch — ví dụ tên tool, thông điệp provider.
	SummaryMsg *Msg
	Detail     string        // full text, written to the log untruncated for troubleshooting; falls back to Summary when empty. The UI only reads Summary
	Kind       string        // error classification (e.g. stream_idle), emitted with the log for filtering/alerting; not emitted when empty
	Level      string        // info / warn / error / success
	Depth      int           // 0 = Engine layer, 1 = Worker layer
	Duration   time.Duration // how long the execution took, once finished
	RetryAt    time.Time     // retry events: the deadline of the next retry; the UI counts down second by second from it and clears at zero (the request is already in flight)
}

Event is a structured event consumed by the TUI.

For call events (MODEL / TOOL / DISPATCH / DECISION) the start and the end of one call share a single ID: at the start an event with a zero FinishedAt is emitted first (the TUI renders it in the "in progress" style); at the end another event with the same ID is emitted, filling in FinishedAt + Duration (+ Failed), and the TUI locates the original row by ID and updates it in place, avoiding the redundancy of

"a row for the start and another row for the finish". Non-call events (SYSTEM / ERROR / CONTEXT) have an

func (Event) Running

func (e Event) Running() bool

empty ID and are appended as independent rows. Running reports whether the event is in progress. Only call events (MODEL / TOOL / DISPATCH / DECISION, the ones with an ID) can be in progress; every other type always returns false.

type Host

type Host struct {
	// contains filtered or unexported fields
}

Host là vỏ runtime: vòng đời / cửa vào can thiệp / chiếu sự kiện / quản lý model. Lập lịch và thực thi nằm ở engine (vòng lặp tất định); định đoạn ngữ nghĩa nằm ở arbiter (LLM-as-function).

func New

func New(cfg bootstrap.Config, bundle assets.Bundle, options ...NewOption) (*Host, error)

New tạo Host.

func (*Host) Abort

func (h *Host) Abort() bool

Abort tạm dừng vòng lặp Engine hiện tại.

func (*Host) AdvanceOneChapter

func (h *Host) AdvanceOneChapter() error

AdvanceOneChapter cấp quyền cho đúng một chương ở chế độ duyệt từng chương rồi khởi động Engine.

func (*Host) AskStoryPartner added in v1.5.3

func (h *Host) AskStoryPartner(ctx context.Context, req sp.Request) (sp.Result, error)

AskStoryPartner chạy một lượt hỏi advisor trên runtime thật.

Luồng: resolve advisor model (failover) → sp.Service → accounting sidecar → audit logs/sp. Không pause Engine, không interMu, không exclusive slot, không chạm lifecycle — Engine đang viết thì /sp vẫn chạy song song.

Single-flight: mỗi thời điểm một request advisor. Request mới hủy request cũ qua context riêng của sidecar; ctx của caller vẫn được tôn trọng (cancel từ phía nào cũng về context.Canceled).

func (*Host) AvailableThinking

func (h *Host) AvailableThinking(role string) []agentcore.ThinkingLevel

func (*Host) BookLanguage added in v1.5.3

func (h *Host) BookLanguage() (string, error)

BookLanguage trả ngôn ngữ sáng tác đang khoá cho truyện này. Trả ("", nil) khi truyện chưa khoá: đó là trường hợp bình thường với truyện tạo trước tính năng này, không phải lỗi.

func (*Host) Books added in v1.5.2

func (h *Host) Books() ([]Book, error)

Books liệt kê các truyện đang tồn tại trong cây đầu ra. base là thư mục gốc (thường là nơi kietnovel chạy, hoặc thư mục cha của cây output).

func (*Host) CancelCoCreate

func (h *Host) CancelCoCreate()

CancelCoCreate bỏ dở đồng sáng tác: xóa cờ chiếm, giữ trạng thái tạm dừng (người dùng có thể nhập tiếp ở ô nhập hoặc khởi động lại để Resume).

func (*Host) CancelStoryPartner added in v1.5.3

func (h *Host) CancelStoryPartner()

CancelStoryPartner hủy advisor request đang chạy, nếu có. Không có request thì không làm gì. Không bao giờ động vào Engine.

func (*Host) CheckChapterRevisions

func (h *Host) CheckChapterRevisions() ([]int, error)

func (*Host) Close

func (h *Host) Close()

Close dừng Engine và đóng kênh sự kiện.

Ngữ nghĩa lưu bền của Usage: trước hết hủy autoSaveLoop (nó tự flush lần trạng thái dirty cuối), rồi gọi thêm một SaveNow đồng bộ để chốt. Sau khi dừng, vài trăm token cuối của lệnh gọi LLM đang dở có thể mất, nhưng sẽ được bù tự động bằng replay session jsonl ở lần khởi động sau.

func (*Host) CoCreateStream

func (h *Host) CoCreateStream(ctx context.Context, history []CoCreateMessage, onProgress func(kind, text string)) (CoCreateReply, error)

CoCreateStream đồng sáng tác khởi đầu từ số 0: làm rõ yêu cầu và sinh chỉ đạo sáng tác cho cả cuốn.

func (*Host) CompletedChapters added in v1.5.2

func (h *Host) CompletedChapters() ([]int, error)

CompletedChapters liệt kê số chương đã chốt, tăng dần. Trả rỗng khi chưa có sách.

func (*Host) ConfigureModels

func (h *Host) ConfigureModels(draft ModelConfigurationDraft) error

ConfigureModels kiểm tra, lưu và áp nóng kho model của một provider.

func (*Host) ConfiguredModelOptions

func (h *Host) ConfiguredModelOptions(provider string) []ConfiguredModel

func (*Host) ConfiguredModels

func (h *Host) ConfiguredModels(provider string) []string

func (*Host) ConfiguredProviders

func (h *Host) ConfiguredProviders() []string

func (*Host) Continue

func (h *Host) Continue(text string) error

Continue is called when the user types in the input box after pausing: it locks in the intervention and makes sure the Engine runs again.

func (*Host) CurrentModelSelection

func (h *Host) CurrentModelSelection(role string) (string, string, bool)

func (*Host) CurrentThinking

func (h *Host) CurrentThinking(role string) string

CurrentThinking trả về chuỗi mức suy luận đang có hiệu lực của một vai trò (để /model đồng bộ giá trị hiện tại).

func (*Host) DeleteBook added in v1.5.2

func (h *Host) DeleteBook(dir string) error

DeleteBook xoá vĩnh viễn thư mục truyện. Không có tham số "đổi ý" — vì vậy phía TUI bắt buộc phải gọi InspectBook trước để lấy thông tin xác nhận, rồi mới gọi hàm này.

Chặn xoá thư mục đang mở: người dùng đang viết truyện đó, xoá đi thì Engine đang chạy giữa chừng sẽ ghi vào thư mục biến mất.

func (*Host) Dir

func (h *Host) Dir() string

func (*Host) Done

func (h *Host) Done() <-chan struct{}

func (*Host) Events

func (h *Host) Events() <-chan Event

func (*Host) Export

func (h *Host) Export(ctx context.Context, opts exp.Options) (*exp.Result, error)

Export xuất các chương đã hoàn thành ra file ngoài (hiện chỉ hỗ trợ TXT).

Khác với ImportFrom: xuất là thao tác chỉ đọc (không đụng Progress / Checkpoint), nên **không yêu cầu Engine dừng** — kể cả khi đang viết cũng xuất được "thành phẩm tại thời điểm này". Chỉ đọc một snapshot nhất quán gồm Progress.CompletedChapters + bản chính thức chương + dàn ý + premise.

func (*Host) FileLogError

func (h *Host) FileLogError() error

FileLogError trả về lỗi khởi tạo log file lúc dựng; không đổi trong suốt vòng đời của Host.

func (*Host) ImportFrom

func (h *Host) ImportFrom(ctx context.Context, opts imp.Options) (<-chan imp.Event, error)

ImportFrom chạy một lần nhập biên dịch ngữ nghĩa truyện ngoài: ingest → segment → analyze → synthesize → publish. Model chỉ định đoạn phần ngữ nghĩa mở (biên/độ kiện/tổng hợp), Go phụ trách toạ độ/ghi đè/idempotent; loại trừ với Engine đang chạy, sau khi nhập xong thì AdvanceHold quyết định có viết tiếp hay không. Kênh sự kiện trả về do imp.Run đóng; bên gọi chịu trách nhiệm tiêu thụ (đầy thì bỏ để không chặn goroutine pipeline).

func (*Host) ImportResumeHint

func (h *Host) ImportResumeHint() string

ImportResumeHint trả về một dòng nhắc về lần nhập chưa xong (rỗng nếu không có), để TUI chủ động báo lúc khởi động (RFC §18.2). Chỉ gọi một lần lúc khởi động: bên trong tính lại InputDigest của từng artifact trong workspace, không hợp đặt vào vòng poll snapshot.

func (*Host) ImportSimulationProfile

func (h *Host) ImportSimulationProfile(ctx context.Context, path string) (<-chan sim.Event, error)

ImportSimulationProfile nhập hồ sơ văn phong đã sinh từ trước.

func (*Host) InspectBook added in v1.5.2

func (h *Host) InspectBook(dir string) (DeleteBookResult, error)

InspectBook dựng thông tin thư mục sẽ bị xoá, KHÔNG xoá gì. Dùng cho bước xác nhận: người dùng phải thấy rõ đang xoá cái gì, bao nhiêu chương, trước khi đồng ý.

func (*Host) ModelConfiguration

func (h *Host) ModelConfiguration() ModelConfigurationSnapshot

ModelConfiguration trả về cấu hình đã che, đích ghi và tham chiếu model, không bao giờ lộ API Key hiện có.

func (*Host) NewBookDir added in v1.5.2

func (h *Host) NewBookDir(name string) (string, error)

NewBookDir tạo thư mục truyện mới trong <base>/output/<name> và trả về đường dẫn. Không tạo sẵn meta/: store sẽ khởi tạo đúng cấu trúc khi mở.

func (*Host) PauseForCoCreate

func (h *Host) PauseForCoCreate() bool

PauseForCoCreate vào đồng sáng tác giai đoạn: đặt cờ chiếm đồng sáng tác, nếu đang chạy thì tạm dừng Engine luôn. Trả false nghĩa là không vào được (đã xong sách hoặc đang trong đồng sáng tác), bên gọi có thể bỏ qua. Cờ chiếm trong cửa sổ đồng sáng tác chặn can thiệp chồng nhau từ import/simulate/start/resume/continue — khi tạm dừng lúc đang chạy thì lifecycle=paused, ràng buộc loại trừ ==running mất hiệu lực, nên phải có cờ này bù; đã dừng (idle/paused) vẫn cho vào, xong phần quy hoạch sẽ chạy tiếp qua Continue.

func (*Host) PrepareUserRules

func (h *Host) PrepareUserRules(rawPrompt string) error

PrepareUserRules sinh snapshot quy tắc người dùng cho cuốn sách này ở chế độ tạo mới (tất định phía khởi động, không đi vào Run sáng tác chính).

Đầu vào là yêu cầu sáng tác **gốc** của người dùng (chưa bọc qua BuildStartPrompt) — thứ cần chuẩn hóa là chính quy tắc của người dùng, không phải khung khởi động. Cửa vào phải được gọi một lần trước StartPrepared (cả hai đường tạo mới quick/cocreate đều đi qua đây).

Chuẩn hóa hỏng chỉ hạ cấp, không báo lỗi (đường tăng cường); chỉ khi không ghi được snapshot mới trả error để dừng việc tạo truyện — nếu không thì các lần chạy sau sẽ không có nguồn dữ kiện ổn định (xem thiết kế §thất bại và hạ cấp).

func (*Host) ReadChapter added in v1.5.2

func (h *Host) ReadChapter(chapter int) (ChapterText, error)

ReadChapter đọc một chương: ưu tiên bản đã chốt (chapters/), không có thì lấy bản nháp (drafts/).

Vì sao phải đọc được cả nháp: bản nháp là thứ người viết CẦN đọc để quyết định có chốt hay không. Chỉ đọc được bản chốt thì tạo ra vòng luẩn quẩn — không đọc được nháp thì không biết có nên chốt, mà chưa chốt thì đọc đâu.

Chương chưa viết trả Exists=false, Body rỗng, KHÔNG phải lỗi. Chỉ lỗi đĩa và dàn ý hỏng mới thành error.

Tiêu đề lấy từ dàn ý, không lấy từ tiêu đề markdown trong file chương: tên file chỉ là %02d.md, còn dàn ý là nơi duy nhất giữ tên chương sau khi engine commit.

func (*Host) ReadableChapters added in v1.5.2

func (h *Host) ReadableChapters() ([]int, error)

ReadableChapters liệt kê chương nào đọc được: đã chốt thì chắc chắn có; đang làm dở thì lấy theo danh sách bản nháp. Tăng dần, không trùng.

func (*Host) Reopen

func (h *Host) Reopen(direction string) error

Reopen mở lại cuốn đã hoàn thành thành trạng thái đang viết. Xong sách và mở lại đều là quyết định nặng: xong sách có thể do kiến trúc sư định đoạn, còn mở lại chỉ người dùng chủ động yêu cầu (/reopen), không qua định đoạn của model. direction khác rỗng được ghi là can thiệp chờ xử lý; khi khôi phục, Arbiter sẽ định đoạn rồi chèn (cùng kênh với can thiệp lúc dừng), sau đó Engine chạy tiếp (định tuyến cuối tập sẽ phát ra tập tiếp theo).

func (*Host) ReplayQueue

func (h *Host) ReplayQueue(afterSeq int64) ([]domain.RuntimeQueueItem, error)

func (*Host) Resume

func (h *Host) Resume() (Msg, error)

Resume chế độ khôi phục: sinh resume prompt từ checkpoint + progress rồi khởi động. Resume khôi phục phiên viết từ store. Trả về Msg (chưa dịch) để TUI hiển thị đúng ngôn ngữ; dùng resumeLabel.String() nếu cần chuỗi thô cho log.

func (*Host) ResumeFromCoCreate

func (h *Host) ResumeFromCoCreate(draft string) error

ResumeFromCoCreate kết thúc đồng sáng tác: chèn hướng đi tiếp do đồng sáng tác sinh ra thành can thiệp rồi khôi phục việc viết. Sau khi xóa cờ chiếm, tái dùng đường chèn của Continue khi đã dừng (vẫn chịu ràng buộc ngân sách trước). Lưu ý: draft rỗng thì trả về sớm mà không xóa cờ là cố ý (đồng sáng tác chưa kết thúc); phía TUI dùng cùng một tiêu chí "không rỗng" ở canStart(), nên đường này không tới được và cocreating không bị rò.

func (*Host) SetAdvanceMode

func (h *Host) SetAdvanceMode(mode domain.ChapterAdvanceMode) error

SetAdvanceMode chuyển chế độ đẩy chương theo cách tất định. Nó chỉ ghi ý định chạy của người dùng, không gọi Arbiter và không tự khởi động Engine đang tạm dừng.

func (*Host) SetBookLanguage added in v1.5.3

func (h *Host) SetBookLanguage(lang string) error

SetBookLanguage khoá ngôn ngữ sáng tác cho truyện này.

func (*Host) SetRoleThinking

func (h *Host) SetRoleThinking(role, level string) error

SetRoleThinking đặt mức suy luận cho một vai trò (hoặc default): kiểm tra → lưu bền → liên kết agent đang chạy → sự kiện. Cấu trúc đối xứng với SwitchModel; độc lập với việc chọn model nên chỉnh riêng được. level rỗng = không ghi đè (kế thừa).

func (*Host) Simulate

func (h *Host) Simulate(ctx context.Context) (<-chan sim.Event, error)

Simulate đọc thư mục simulate rồi sinh hoặc cập nhật dần hồ sơ văn phong.

func (*Host) Snapshot

func (h *Host) Snapshot() UISnapshot

func (*Host) StageCoCreateStream

func (h *Host) StageCoCreateStream(ctx context.Context, history []CoCreateMessage, onProgress func(kind, text string)) (CoCreateReply, error)

StageCoCreateStream đồng sáng tác theo giai đoạn: quy hoạch hướng đi tiếp dựa trên phần đã viết. Prompt hệ thống = prompt giai đoạn + tóm tắt trạng thái câu chuyện hiện tại, để trợ lý biết "đã viết đến đâu".

func (*Host) StartPrepared

func (h *Host) StartPrepared(rawRequirement string) error

StartPrepared bắt đầu viết từ yêu cầu sáng tác **gốc** của người dùng: định đoạn plan_start chọn kiến trúc sư và mở rộng yêu cầu, kết quả định đoạn được cố thành dữ kiện trước (PlanStartRecord) rồi mới khởi động Engine — mọi lần khôi phục luôn dựa trên dữ kiện đã ghi đĩa, không làm lại định đoạn đã có. Dữ kiện đầu vào (StartPrompt) được ghi đĩa trước khi định đoạn: nếu định đoạn hỏng thì đây là căn cứ để Engine bù định đoạn, nên khởi động hỏng vẫn tự lành từ mọi cửa khôi phục (Resume/tiếp tục), không phải ngõ cụt.

func (*Host) Steer

func (h *Host) Steer(text string) error

Steer submits a user intervention (allowed at any point while running; once paused, whether the Engine is pulled back up afterwards depends on the action). The TUI waits on the result through a tea.Cmd, so it sees the real lock-in/persist error without blocking the interface.

func (*Host) Stream

func (h *Host) Stream() <-chan string

func (*Host) SwitchModel

func (h *Host) SwitchModel(role, provider, model string) error

func (*Host) SyncChapterRevisions

func (h *Host) SyncChapterRevisions(ctx context.Context) (*revision.Result, error)

func (*Host) TestModelConnection

func (h *Host) TestModelConnection(ctx context.Context, draft ModelConfigurationDraft, modelName string) error

TestModelConnection dùng bản nháp hiện tại dựng một client model thật và gửi request tối thiểu. Không lưu cấu hình, không chuyển model runtime, thất bại cũng không rớt sang Provider khác.

type ModelConfigurationDraft

type ModelConfigurationDraft struct {
	Provider     string
	Type         string
	API          string
	BaseURL      string
	Models       []bootstrap.ModelConfig
	Renames      []ModelRename
	APIKeyAction APIKeyAction
	APIKey       string
}

ModelConfigurationDraft là bản nháp một provider mà /config gửi cho Host. Chỉ mô tả định nghĩa provider (giao thức/chứng thực/kho model), không gồm "đang dùng cái nào" — chuyển model là việc của /model.

type ModelConfigurationSnapshot

type ModelConfigurationSnapshot struct {
	Providers       []ProviderSnapshot
	DefaultProvider string
	DefaultModel    string
	ConfigPath      string
	References      map[string][]string
}

func (ModelConfigurationSnapshot) ReferencesFor

func (s ModelConfigurationSnapshot) ReferencesFor(provider, model string) []string

type ModelRename

type ModelRename struct {
	From string
	To   string
}

ModelRename mô tả việc đổi ID của cùng một cấu hình model. Nó không phải phỏng đoán "xóa cũ thêm mới" — Host chỉ chuyển default, vai trò và tham chiếu fallback khi TUI gửi tường minh quan hệ này.

type Msg added in v1.5.2

type Msg struct {
	Key  string
	Args []any
}

Msg là thông điệp hiển thị CHƯA dịch, đi kèm qua ranh giới host → TUI.

Key là nguồn tiếng Việt theo đúng quy ước của internal/i18n (chuỗi tiếng Việt là nguồn sự thật), thường ở dạng format string. Args là tham số sẽ điền vào lúc hiển thị.

Vì sao không dựng sẵn ở host rồi gửi chuỗi đã điền: "Khôi phục: chương %d" và "Khôi phục: chương 3" là hai chuỗi khác nhau, mà bảng dịch chỉ có thể chứa mẫu chứa %d. Điền số ở host thì TUI không còn gì để dịch, và mọi ngôn ngữ đều hiện tiếng Việt — đúng cái lỗi "vá triệu chứng" mà Msg này loại bỏ.

TUI dịch Key trước rồi mới điền Args (i18n.Tf), y hệt cách TUI vẫn làm với chuỗi của riêng nó, nên một thông điệp hiện đúng ở cả vi/en/zh.

func Ptr added in v1.5.2

func Ptr(m Msg) *Msg

Ptr trả con trỏ tới bản sao, để gán vào Event.SummaryMsg (*Msg). Dùng khi thông điệp được tính tại chỗ gọi: cần địa chỉ ổn định chứ không phải con trỏ tới biến cục bộ.

func (Msg) Empty added in v1.5.2

func (m Msg) Empty() bool

Empty báo thông điệp rỗng, để TUI bỏ qua thay vì vẽ một dòng trống.

func (Msg) String added in v1.5.2

func (m Msg) String() string

String dựng bản tiếng Việt. Dùng cho log và cho code cũ còn cần chuỗi thô; phía hiển thị thì gọi i18n.Tf(Key, Args...) để dịch.

type NewOption

type NewOption func(*newOptions)

NewOption configures the Host construction process; the runtime resources are still owned by Host.

func WithFileLog

func WithFileLog(filename string, alsoStderr bool, attrs ...slog.Attr) NewOption

WithFileLog gives Host a runtime log session. The log is opened only after the novel-directory lease is taken, and closed once Host has closed all logs. If opening fails the current process logger keeps being used, and the caller must handle that error explicitly through FileLogError.

type OutlineSnapshot

type OutlineSnapshot struct {
	Chapter   int
	Title     string
	CoreEvent string
}

OutlineSnapshot is the display summary of an outline entry.

type ProviderSnapshot

type ProviderSnapshot struct {
	Name           string
	Type           string
	API            string
	BaseURL        string
	Models         []bootstrap.ModelConfig
	HasAPIKey      bool
	APIKeyHint     string
	RequiresAPIKey bool
}

ProviderSnapshot là cấu hình provider đã che API key, để TUI hiển thị.

type UISnapshot

type UISnapshot struct {
	Provider             string
	BookTitle            string
	ModelName            string
	ModelContextWindow   int // context window of the current default model (resolved live on /model switch)
	ThinkingLevel        string
	Style                string
	RuntimeState         string // idle / running / pausing / paused / completed
	StatusLabel          string
	Phase                string
	Flow                 string
	CurrentChapter       int
	TotalChapters        int
	CompletedCount       int
	TotalWordCount       int
	InProgressChapter    int
	PendingRewrites      []int
	RewriteReason        string
	PendingSteer         string
	AdvanceMode          string
	AdvancePermitChapter int
	HasAdvanceHold       bool
	AdvanceHoldReason    string
	RecoveryLabel        Msg // chưa dịch; TUI gọi i18n.Tf(RecoveryLabel.Key, RecoveryLabel.Args...)
	IsRunning            bool
	Agents               []AgentSnapshot

	// cumulative usage (whole session, across all agents and model switches)
	TotalInputTokens      int
	TotalOutputTokens     int
	TotalCacheReadTokens  int
	TotalCacheWriteTokens int
	TotalCostUSD          float64
	TotalSavedUSD         float64 // USD saved thanks to CacheRead hits (relative to billing every input at the non-cached input price)
	BudgetLimitUSD        float64 // budget cap (config budget.book_usd); 0 = not enabled

	// cache diagnostics
	OverallCacheCapable    bool // at least one role has run a model that supports prompt cache (distinguishes "not enabled" from "0% hit rate")
	OverallRecentCacheRead int  // sum of cacheRead over the most recent N samples in the sliding window
	OverallRecentInput     int  // sum of input over the most recent N samples in the sliding window
	OverallRecentSamples   int  // number of samples in the sliding window (<= recentSampleCap)
	TotalCacheBreaks       int  // number of cache-chain breaks detected live (the prefix did not shorten while the hit rate dropped), see noteCacheBreak in usage.go

	// MissingAssistantUsage > 0 usually means the upstream streaming did not send the final usage
	// chunk per the OpenAI stream_options.include_usage protocol (common with self-hosted proxies),
	// so the UsageTracker receives no cumulative data at all. The UI tells the user to inspect the backend,
	// rather than letting them think the cache module itself is broken.
	MissingAssistantUsage int

	// cache per-role dimension, sorted by CacheRead descending, with roles that never consumed tokens filtered out
	CachePerAgent []AgentCacheStat
	CachePerModel []AgentCacheStat

	// base settings
	Synopsis         string
	Premise          string
	Outline          []OutlineSnapshot
	Characters       []string
	SupportingCount  int      // total number of supporting characters recorded for the chapter
	RecentSupporting []string // most recently active supporting characters (at most 5, sorted by LastSeenChapter descending)
	Layered          bool
	CurrentVolumeArc string
	NextVolumeTitle  string
	CompassDirection string
	CompassScale     string

	// details
	LastCommitSummary  string
	LastReviewSummary  string
	LastCheckpointName string
	RecentSummaries    []string
}

UISnapshot is the aggregate state snapshot the TUI needs for rendering.

type UsageTracker

type UsageTracker struct {
	// contains filtered or unexported fields
}

UsageTracker accumulates the LLM input/output tokens and dollar cost of every agent for the whole session.

How it works:

  • Record(agentName, msg) is called on every agent OnMessage callback
  • agentName maps to a role (architect_* is normalized to architect), then the model currently bound to that role is looked up in ModelSet
  • models.DefaultRegistry supplies the model price, multiplied across four items: non-cached input / output / cache read / cache write
  • when the model is not in the registry, fall back to msg.Usage.Cost.Total (whatever the provider reported, possibly 0)
  • after a hot model switch (/model) later messages are priced with the new model while old messages keep their old cost

It also maintains a per-role dimension (writer/editor/architect):

  • cumulative hit data -> the overall optimization effect
  • the sliding window of the last N calls -> tells early drag apart from a steady low hit rate
  • the CacheCapable flag -> tells "not enabled" apart from "really a 0% hit rate"

Thread-safe.

func NewUsageTracker

func NewUsageTracker(set *bootstrap.ModelSet, store *storepkg.Store) *UsageTracker

func (*UsageTracker) LoadFromStore

func (t *UsageTracker) LoadFromStore() (bool, error)

LoadFromStore reads the persisted snapshot from store.Usage and back-fills it into memory. true means a non-empty (schema matching) state was loaded successfully; false means there is no file or it is unusable, and the caller should keep going with a one-off back-fill from the session replay.

func (*UsageTracker) MissingAssistantUsage

func (t *UsageTracker) MissingAssistantUsage() int

MissingAssistantUsage returns the cumulative count of "an assistant message was received but Usage is nil". Greater than 0 usually means the upstream streaming did not send OpenAI's final usage chunk, and the UI shows a hint instead of letting the user think the cache module itself is broken.

func (*UsageTracker) OverallCacheBreaks

func (t *UsageTracker) OverallCacheBreaks() int

OverallCacheBreaks returns the total number of cache-chain breaks detected live.

func (*UsageTracker) OverallCacheCapable

func (t *UsageTracker) OverallCacheCapable() bool

OverallCacheCapable reports whether the whole run has been through a model known to support cache at least once.

func (*UsageTracker) OverallRecent

func (t *UsageTracker) OverallRecent() (cacheRead, input, samples int)

OverallRecent returns the cacheRead total, the input total and the sample count inside the sliding window (at most recentSampleCap calls).

func (*UsageTracker) PerAgent

func (t *UsageTracker) PerAgent() []AgentUsage

PerAgent returns the cumulative usage per role, sorted by CacheRead descending, skipping roles that never consumed tokens.

func (*UsageTracker) PerModel

func (t *UsageTracker) PerModel() []AgentUsage

PerModel returns the cumulative usage per model, sorted by cost descending and then by input volume descending.

func (*UsageTracker) Record

func (t *UsageTracker) Record(agentName, task string, msg agentcore.AgentMessage)

Record dispatches one agent message onto two paths: accumulation and diagnostics.

Accumulation only looks at whether Usage exists - "which messages carry Usage" is an agentcore/litellm adapter assembly detail (the upstream protocol puts usage at the top level of the response), so if the assembly rules ever change, nothing here has to change. Diagnostics require Role=Assistant and non-empty Content so that AbortMsg / abnormal recovery / tool / user messages do not pollute the missingAssistantUsage counter.

func (*UsageTracker) RecordSidecar added in v1.5.3

func (t *UsageTracker) RecordSidecar(agentName, task string, u agentcore.Usage, provider, modelName string)

RecordSidecar ghi accounting cho tác vụ phụ (sidecar) như /sp hỏi.

Khác Record ở đúng một điểm: KHÔNG cộng vào overall và KHÔNG gọi onCost. overall là số BudgetSentinel dùng để quyết định abort Engine; một câu hỏi phụ mà đẩy tổng vượt trần rồi dừng cả máy đang viết là sai nguyên tắc "sidecar không can thiệp Engine".

Nhưng vẫn cộng vào perAgent["advisor"] + perModel và vẫn notifyDirty để persist: tiền thật đã đốt thì phải còn dấu vết, chỉ là không được dùng để giết Engine.

task là lineage của cache detector: mỗi /sp request là một prompt lineage mới (snapshot/question khác nhau, model có thể đổi), nên mỗi request phải có task riêng. Dùng chung một task cho mọi request thì detector so B với baseline của A và báo "đứt cache" oan cho một lineage hoàn toàn mới.

func (*UsageTracker) ReplaySessions

func (t *UsageTracker) ReplaySessions(rootDir string) (int, error)

ReplaySessions scans meta/sessions/agents/*.jsonl, re-accumulates the usage of every assistant message into the tracker, and returns the number of back-filled records.

Call constraint: called exactly once for back-filling, only when meta/usage.json is missing. Regular persistence goes through SaveNow / autoSaveLoop.

The accuracy relies on the three-level fallback described in the sessionRecord comment - the third level (both Usage and _meta missing) only triggers on older logs or upstream anomalies.

func (*UsageTracker) SaveNow

func (t *UsageTracker) SaveNow() error

SaveNow writes the current snapshot to disk immediately. The autoSaveLoop / Close paths both write through it.

func (*UsageTracker) SavedUSD

func (t *UsageTracker) SavedUSD() float64

SavedUSD returns the cumulative dollars saved by cache hits.

func (*UsageTracker) SetOnCost

func (t *UsageTracker) SetOnCost(cb func(total float64))

SetOnCost registers the accounting callback (carrying the latest cumulative cost, called outside the lock). It must be called once during Host construction, before concurrent Record calls start.

func (*UsageTracker) SetOnMissingUsage

func (t *UsageTracker) SetOnMissingUsage(cb func())

SetOnMissingUsage registers the one-shot callback for the first time usage is found missing. It must be called once during Host construction, before concurrent Record calls start.

func (*UsageTracker) Snapshot

func (t *UsageTracker) Snapshot() domain.UsageState

Snapshot copies the current cumulative state into a serializable domain.UsageState. The sliding-window samples do not go into the snapshot - it is a short-term diagnostic window and little of it carries across processes.

func (*UsageTracker) StartAutoSave

func (t *UsageTracker) StartAutoSave(ctx context.Context)

StartAutoSave starts a goroutine that listens on saveCh and writes to disk with a debounce. Before ctx is done it flushes the last unsaved state. Close triggers that flush and the exit by cancelling ctx.

func (*UsageTracker) Totals

func (t *UsageTracker) Totals() (cost float64, input, output, cacheRead, cacheWrite int)

Totals returns a snapshot of the cumulative totals.

func (*UsageTracker) WaitAutoSave

func (t *UsageTracker) WaitAutoSave()

WaitAutoSave waits for the final flush after cancellation. Host.Close calls cancel first and then waits here, so autoSaveLoop does not race the pre-exit SaveNow over the same snapshot.

Directories

Path Synopsis
Package exp implements the export of completed chapters.
Package exp implements the export of completed chapters.
Package imp implements the staged semantic import pipeline for external novels (docs/import-pipeline.md).
Package imp implements the staged semantic import pipeline for external novels (docs/import-pipeline.md).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL