Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions config.example.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"provider_runtime": {
"current_provider": "deepseek",
"default_provider": "deepseek",
"default_model": "deepseek-v4-flash",
"allow_fallback": false,
"providers": {
"deepseek": {
"type": "openai-compatible",
Expand All @@ -22,6 +25,12 @@
"gpt-5.4"
]
}
},
"health": {
"fail_threshold": 3,
"recover_probe_sec": 30,
"recover_success_threshold": 2,
"window_size": 60
}
},
"approval_policy": "on-request",
Expand All @@ -32,16 +41,5 @@
"warning_ratio": 0.85,
"critical_ratio": 0.95,
"max_reactive_retry": 1
},
"token_usage": {
"storage_type": "file",
"storage_path": "./.bytemind/token_usage.json",
"backup_interval": "1m",
"max_sessions": 10000,
"alert_threshold": 1000000,
"enable_realtime": true,
"retention_days": 30,
"monitor_interval": "30s",
"database_driver": "sqlite3"
}
}
453 changes: 453 additions & 0 deletions docs/user-stories.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion tui/input_paste.go
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,7 @@ func (m *model) resolvePromptPastedInput(raw string) (string, error) {
}

func (m *model) ensurePastedContentState() {
if m == nil || m.pastedStateLoaded {
if m == nil {
return
}
if m.pastedContents == nil {
Expand All @@ -914,6 +914,9 @@ func (m *model) ensurePastedContentState() {
if m.nextPasteID <= 0 {
m.nextPasteID = 1
}
if m.pastedStateLoaded {
return
}
m.pastedStateLoaded = true

if m.sess == nil || m.sess.Conversation.Meta == nil {
Expand Down
304 changes: 304 additions & 0 deletions tui/input_paste_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,48 @@ func TestSubmitPromptExpandsPasteReferenceForDisplayedChatBodyAndClearsPasteStat
}
}

func TestClipboardPasteCaptureAfterSubmittedPasteReinitializesState(t *testing.T) {
m := newImagePipelineModel(t)
_, stored, err := m.compressPastedText("old1\nold2\nold3\nold4\nold5\nold6\nold7\nold8\nold9\nold10\nold11")
if err != nil {
t.Fatalf("compress pasted text: %v", err)
}
got, _ := m.submitPrompt("inspect [Paste #" + stored.ID + " ~11 lines]")
updated := got.(model)
if updated.pastedContents != nil || updated.pastedOrder != nil {
t.Fatalf("expected submit to clear pasted state, got contents=%v order=%v", updated.pastedContents, updated.pastedOrder)
}

clipboardText := strings.Join([]string{
"abcd first pasted line",
"second pasted line",
"third pasted line",
"fourth pasted line",
"fifth pasted line",
"sixth pasted line",
"seventh pasted line",
"eighth pasted line",
"ninth pasted line",
"tenth pasted line",
"eleventh pasted line",
"twelfth pasted line",
}, "\n")
updated.clipboardRead = fakeClipboardTextReader{text: clipboardText}
updated.clipboardCaptureArmedUntil = time.Now().Add(time.Second)
updated.input.SetValue("abcd")

result := updated.handleInputMutation("abc", "abcd", "")
if !regexp.MustCompile(`^\[Paste #\d+ ~\d+ lines\]$`).MatchString(result) {
t.Fatalf("expected clipboard capture to compress into a paste marker, got %q", result)
}
if updated.pastedContents == nil || len(updated.pastedContents) != 1 {
t.Fatalf("expected pasted contents to be reinitialized with one entry, got %#v", updated.pastedContents)
}
if updated.pastedOrder == nil || len(updated.pastedOrder) != 1 {
t.Fatalf("expected pasted order to be reinitialized with one entry, got %#v", updated.pastedOrder)
}
}

func TestStorePastedContentKeepsRecentLimit(t *testing.T) {
m := newImagePipelineModel(t)
for i := 0; i < maxStoredPastedContents+2; i++ {
Expand Down Expand Up @@ -1096,3 +1138,265 @@ func TestShouldMergeIntoLatestMarkerRequiresPasteEvidence(t *testing.T) {
t.Fatalf("expected stale transaction window to skip merge")
}
}

func TestResetPasteBurstTracking(t *testing.T) {
m := newImagePipelineModel(t)
m.inputBurstBaseValue = "test-base"
m.pasteBurstCandidate = pasteBurstCandidateState{
active: true,
baseInput: "test-base",
startedAt: time.Now(),
}
m.resetPasteBurstTracking()
if m.inputBurstBaseValue != "" {
t.Fatalf("expected inputBurstBaseValue to be cleared, got %q", m.inputBurstBaseValue)
}
if m.pasteBurstCandidate.active {
t.Fatalf("expected pasteBurstCandidate to be cleared after reset")
}
}

func TestResetPasteBurstTrackingNilModel(t *testing.T) {
var m *model
m.resetPasteBurstTracking()
// should not panic
}

func TestCaptureImplicitPasteCandidateNilModel(t *testing.T) {
var m *model
msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'a'}}
cmd := m.captureImplicitPasteCandidate(msg)
if cmd != nil {
t.Fatalf("expected nil command from nil model")
}
}

func TestCaptureImplicitPasteCandidateNonPromotableKey(t *testing.T) {
m := newImagePipelineModel(t)
m.input.SetValue("hello")
m.lastInputAt = time.Now().Add(-50 * time.Millisecond)

// Use Escape which implicitPasteCandidateFragment returns ok=false for
msg := tea.KeyMsg{Type: tea.KeyEscape}
cmd := m.captureImplicitPasteCandidate(msg)
if cmd != nil {
t.Fatalf("expected nil command for non-fragment key (Escape)")
}
}

func TestCaptureImplicitPasteCandidateWithPasteMsg(t *testing.T) {
m := newImagePipelineModel(t)
msg := tea.KeyMsg{Paste: true}
cmd := m.captureImplicitPasteCandidate(msg)
if cmd != nil {
t.Fatalf("expected nil command when msg.Paste is true")
}
}

func TestCaptureImplicitPasteSpecialKeyNilModel(t *testing.T) {
var m *model
msg := tea.KeyMsg{Type: tea.KeyEnter}
cmd := m.captureImplicitPasteSpecialKey(msg)
if cmd != nil {
t.Fatalf("expected nil command from nil model")
}
}

func TestCaptureImplicitPasteSpecialKeyEnterStartsSession(t *testing.T) {
m := newImagePipelineModel(t)
msg := tea.KeyMsg{Type: tea.KeyEnter}
cmd := m.captureImplicitPasteSpecialKey(msg)
if cmd == nil {
t.Fatalf("expected non-nil command for Enter key (starts implicit paste session)")
}
if !m.pasteSession.active {
t.Fatalf("expected paste session to be active after implicit special key capture")
}
}

func TestCaptureImplicitPasteSpecialKeyTabStartsSession(t *testing.T) {
m := newImagePipelineModel(t)
msg := tea.KeyMsg{Type: tea.KeyTab}
cmd := m.captureImplicitPasteSpecialKey(msg)
if cmd == nil {
t.Fatalf("expected non-nil command for Tab key (starts implicit paste session)")
}
if m.pasteSession.sourceKind != "implicit-tab" {
t.Fatalf("expected implicit-tab source kind, got %q", m.pasteSession.sourceKind)
}
}

func TestIsSplitPasteContinuationEmptyInput(t *testing.T) {
if isSplitPasteContinuation(" ", "paste-key", time.Now()) {
t.Fatalf("expected empty trimmed input to not be a split continuation")
}
}

func TestIsSplitPasteContinuationPathInput(t *testing.T) {
if isSplitPasteContinuation(`C:\Users\test\file`, "paste-key", time.Now()) {
t.Fatalf("expected path-like input to not be a split continuation")
}
}

func TestIsSplitPasteContinuationNonPasteSource(t *testing.T) {
if isSplitPasteContinuation("some long text that is not paste", "rune", time.Now()) {
t.Fatalf("expected non-paste source to not be a split continuation")
}
}

func TestIsSplitPasteContinuationContainsMarker(t *testing.T) {
if isSplitPasteContinuation("[Paste #1 ~11 lines]", "paste-key", time.Now()) {
t.Fatalf("expected input containing paste marker to not be a split continuation")
}
}

func TestIsSplitPasteContinuationWithinWindow(t *testing.T) {
if !isSplitPasteContinuation("some quick text", "paste-key", time.Now().Add(-500*time.Millisecond)) {
t.Fatalf("expected split continuation within paste continuation window")
}
}

func TestIsSplitPasteContinuationOutsideWindowButMultiLine(t *testing.T) {
if !isSplitPasteContinuation("line1\nline2\nline3", "paste-key", time.Now().Add(-3*time.Second)) {
t.Fatalf("expected multi-line paste to be a split continuation even outside window")
}
}

func TestIsSplitPasteContinuationOutsideWindowButLong(t *testing.T) {
longText := strings.Repeat("a", pasteQuickCharThreshold)
if !isSplitPasteContinuation(longText, "paste-key", time.Now().Add(-3*time.Second)) {
t.Fatalf("expected long paste to be a split continuation even outside window")
}
}

func TestIsSplitPasteContinuationZeroLastPasteAt(t *testing.T) {
if isSplitPasteContinuation("short", "paste-key", time.Time{}) {
t.Fatalf("expected short single line with zero lastPasteAt to not be a split continuation")
}
}

func TestLooksLikePastedFragmentWithWhitespace(t *testing.T) {
if !looksLikePastedFragment("text with spaces") {
t.Fatalf("expected text with spaces to look like a pasted fragment")
}
if !looksLikePastedFragment("text\twith\ttabs") {
t.Fatalf("expected text with tabs to look like a pasted fragment")
}
}

func TestLooksLikePastedFragmentPlain(t *testing.T) {
short := strings.Repeat("x", 63)
if looksLikePastedFragment(short) {
t.Fatalf("expected short text without whitespace under 64 chars to not look like a pasted fragment")
}
long := strings.Repeat("x", 64)
if !looksLikePastedFragment(long) {
t.Fatalf("expected text of 64 chars to look like a pasted fragment")
}
}

func TestShouldMergeIntoLatestMarkerNilModel(t *testing.T) {
var m *model
if m.shouldMergeIntoLatestMarker("rune") {
t.Fatalf("expected nil model to return false")
}
}

func TestImplicitPasteCandidateFragmentPasteMsg(t *testing.T) {
m := newImagePipelineModel(t)
msg := tea.KeyMsg{Paste: true}
frag, source, ok := m.implicitPasteCandidateFragment(msg)
if ok {
t.Fatalf("expected paste message to not be a candidate fragment, got frag=%q source=%q", frag, source)
}
}

func TestImplicitPasteCandidateFragmentEmptyRunes(t *testing.T) {
m := newImagePipelineModel(t)
msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{}}
_, _, ok := m.implicitPasteCandidateFragment(msg)
if ok {
t.Fatalf("expected empty runes to not be a candidate fragment")
}
}

func TestImplicitPasteCandidateFragmentUnhandledKey(t *testing.T) {
m := newImagePipelineModel(t)
msg := tea.KeyMsg{Type: tea.KeyEscape}
_, _, ok := m.implicitPasteCandidateFragment(msg)
if ok {
t.Fatalf("expected unhandled key type to not be a candidate fragment")
}
}

func TestShouldPromoteImplicitPasteCandidateNilModel(t *testing.T) {
var m *model
msg := tea.KeyMsg{Type: tea.KeyEnter}
if m.shouldPromoteImplicitPasteCandidate(msg) {
t.Fatalf("expected nil model to return false for promote")
}
}

func TestShouldCaptureImplicitPasteSpecialKeyNilModel(t *testing.T) {
var m *model
msg := tea.KeyMsg{Type: tea.KeyEnter}
if m.shouldCaptureImplicitPasteSpecialKey(msg) {
t.Fatalf("expected nil model to return false")
}
}

func TestShouldCaptureImplicitPasteSpecialKeyPasteMsg(t *testing.T) {
m := newImagePipelineModel(t)
msg := tea.KeyMsg{Paste: true}
if m.shouldCaptureImplicitPasteSpecialKey(msg) {
t.Fatalf("expected paste message to not be captured as special key")
}
}

func TestShouldCaptureImplicitPasteSpecialKeyNonEnterTab(t *testing.T) {
m := newImagePipelineModel(t)
msg := tea.KeyMsg{Type: tea.KeyEscape}
if m.shouldCaptureImplicitPasteSpecialKey(msg) {
t.Fatalf("expected non-enter/tab key to not be captured")
}
}

func TestCountCompressedMarkersHandlesVariousInputs(t *testing.T) {
if got := countCompressedMarkers(""); got != 0 {
t.Fatalf("expected empty string count to be 0, got %d", got)
}
if got := countCompressedMarkers("[Paste #1 ~11 lines]"); got != 1 {
t.Fatalf("expected single marker count to be 1, got %d", got)
}
if got := countCompressedMarkers("[Pasted #3 ~20 lines]"); got != 1 {
t.Fatalf("expected Pasted marker count to be 1, got %d", got)
}
}

func TestDropLatestCompressedMarkerNoMarker(t *testing.T) {
result := dropLatestCompressedMarker("plain text with no markers")
if result != "plain text with no markers" {
t.Fatalf("expected plain text unchanged, got %q", result)
}
}

func TestDropLatestCompressedMarkerSingleMarker(t *testing.T) {
result := dropLatestCompressedMarker("[Paste #1 ~11 lines]")
if result != "" {
t.Fatalf("expected single marker to be dropped to empty, got %q", result)
}
}

func TestDropLatestCompressedMarkerMultipleMarkers(t *testing.T) {
result := dropLatestCompressedMarker("[Paste #1 ~11 lines] [Paste #2 ~15 lines]")
if result != "[Paste #1 ~11 lines]" {
t.Fatalf("expected only latest marker dropped, got %q", result)
}
}

func TestDropLatestCompressedMarkerWithTextBefore(t *testing.T) {
result := dropLatestCompressedMarker("before text [Paste #1 ~11 lines]")
if result != "before text" {
t.Fatalf("expected marker removed with surrounding text preserved, got %q", result)
}
}
2 changes: 2 additions & 0 deletions www/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const enSidebar = [
{ text: 'Fix a Bug', link: '/examples/fix-bug' },
{ text: 'Refactor Code', link: '/examples/refactor' },
{ text: 'Generate Documentation', link: '/examples/doc-generation' },
{ text: 'User Stories', link: '/examples/user-stories' },
],
},
{
Expand Down Expand Up @@ -87,6 +88,7 @@ const zhSidebar = [
{ text: '修复 Bug', link: '/zh/examples/fix-bug' },
{ text: '代码重构', link: '/zh/examples/refactor' },
{ text: '文档生成', link: '/zh/examples/doc-generation' },
{ text: '用户故事', link: '/zh/examples/user-stories' },
],
},
{
Expand Down
Loading
Loading