v0.33.0 653c635

dynamic_context.go RawHistoryFolder

1package core
2
3import (
4	"strings"
5	"time"
6
7	"github.com/bnema/zut/packages/provider"
8)
9
10const internalContextMarker = "internal_context"
11
12// appendDynamicContext persists a provider-visible, host-authored context item
13// only when its canonical value changes. It is deliberately separate from the
14// stable top-level instructions so changing time or extension state cannot
15// rewrite the instruction cache prefix.
16func (a *Agent) appendDynamicContext(turnContext string) {
17	parts := make([]string, 0, 2)
18	if timeContext := a.providerTimeContext().developerText(); timeContext != "" {
19		parts = append(parts, timeContext)
20	}
21	if turnContext = boundedTurnContext(turnContext); turnContext != "" {
22		parts = append(parts, "[Extension context]\n"+turnContext)
23	}
24	text := strings.Join(parts, "\n\n")
25	if text == "" {
26		return
27	}
28
29	message := provider.Message{
30		Role:    provider.RoleDeveloper,
31		Content: []provider.Content{provider.TextBlock{Text: text}},
32		Time:    time.Now(),
33		Meta:    map[string]string{internalContextMarker: "true"},
34	}
35
36	a.mu.Lock()
37	for i := len(a.messages) - 1; i >= 0; i-- {
38		if !isInternalContextMessage(a.messages[i]) {
39			continue
40		}
41		if internalContextText(a.messages[i]) == text {
42			a.mu.Unlock()
43			return
44		}
45		break
46	}
47	// The first dynamic item must precede the accepted user task. Persisting it
48	// as an append would reverse that order on session reload, so report the
49	// resulting transcript replacement instead. Later snapshots extend the
50	// history at the next model boundary and remain ordinary appends.
51	first := !hasInternalContextMessage(a.messages)
52	insertAt := len(a.messages)
53	if first {
54		insertAt = 0
55	}
56	a.messages = append(a.messages, provider.Message{})
57	copy(a.messages[insertAt+1:], a.messages[insertAt:])
58	a.messages[insertAt] = message
59	a.rev++
60	onCompacted := a.OnTranscriptCompacted
61	persisted := append([]provider.Message(nil), a.messages...)
62	a.mu.Unlock()
63	if first && onCompacted != nil {
64		onCompacted(persisted)
65		return
66	}
67	a.fireMessageAppended(message)
68}
69
70func hasInternalContextMessage(messages []provider.Message) bool {
71	for _, message := range messages {
72		if isInternalContextMessage(message) {
73			return true
74		}
75	}
76	return false
77}
78
79func isInternalContextMessage(message provider.Message) bool {
80	return message.Role == provider.RoleDeveloper && message.Meta[internalContextMarker] == "true"
81}
82
83func latestInternalContext(messages []provider.Message) (*provider.Message, []provider.Message) {
84	filtered := make([]provider.Message, 0, len(messages))
85	var latest *provider.Message
86	for _, message := range messages {
87		if isInternalContextMessage(message) {
88			copy := message
89			latest = &copy
90			continue
91		}
92		filtered = append(filtered, message)
93	}
94	return latest, filtered
95}
96
97func internalContextText(message provider.Message) string {
98	if len(message.Content) != 1 {
99		return ""
100	}
101	text, _ := message.Content[0].(provider.TextBlock)
102	return text.Text
103}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close