• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacklok / toolhive-studio / 29571051485

17 Jul 2026 09:44AM UTC coverage: 71.713% (+0.5%) from 71.204%
29571051485

Pull #2475

github

samuv
fix(chat): harden Effect runtime adapters after external review

Make readiness and runtime retrieval atomic, type adapter programs against
ChatServices, and tighten domain-error narrowing plus agent/settings edge cases.

Co-authored-by: Cursor <cursoragent@cursor.com>
Pull Request #2475: refactor(chat): migrate main-process chat to Effect domain services

5476 of 8187 branches covered (66.89%)

871 of 1363 new or added lines in 42 files covered. (63.9%)

1 existing line in 1 file now uncovered.

8153 of 11369 relevant lines covered (71.71%)

137.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.22
/main/src/chat/threads/threads-repository.ts
1
import { Effect } from 'effect'
2
import {
3
  writeThread,
4
  deleteThreadFromDb,
5
  clearAllThreadsFromDb,
6
  writeActiveThread,
7
  writeThreadSelectedModel,
8
  writeThreadEnabledMcpTools,
9
  writeThreadEnabledSkills,
10
} from '../../db/writers/threads-writer'
11
import { writeThreadAgentId } from '../../db/writers/agents-writer'
12
import {
13
  readThread,
14
  readAllThreads,
15
  readActiveThreadId,
16
  readThreadCount,
17
  readThreadSelectedModel,
18
  readThreadEnabledMcpTools,
19
  readThreadEnabledSkills,
20
} from '../../db/readers/threads-reader'
21
import { readThreadAgentId } from '../../db/readers/agents-reader'
22
import { StorageError } from '../runtime/errors'
23
import type { ChatSettingsThread, ThreadMessage } from './types'
24

25
function wrapSync<A>(
26
  operation: string,
27
  fn: () => A
28
): Effect.Effect<A, StorageError> {
29
  return Effect.try({
141✔
30
    try: fn,
31
    catch: (cause) =>
32
      new StorageError({
9✔
33
        operation,
34
        cause,
35
        userMessage:
36
          cause instanceof Error
9!
37
            ? cause.message
38
            : 'A storage operation failed.',
39
      }),
40
  })
41
}
42

43
export class ThreadsRepository extends Effect.Service<ThreadsRepository>()(
44
  'chat/ThreadsRepository',
45
  {
46
    accessors: true,
47
    sync: () => ({
121✔
48
      readThread: (threadId: string) =>
49
        wrapSync('readThread', () => readThread(threadId)),
43✔
50

NEW
51
      readAllThreads: () => wrapSync('readAllThreads', () => readAllThreads()),
×
52

53
      readActiveThreadId: () =>
NEW
54
        wrapSync('readActiveThreadId', () => readActiveThreadId() ?? undefined),
×
55

56
      readThreadCount: () =>
NEW
57
        wrapSync('readThreadCount', () => readThreadCount()),
×
58

59
      writeThread: (thread: ChatSettingsThread) =>
60
        wrapSync('writeThread', () => {
15✔
61
          writeThread(thread)
15✔
62
        }),
63

64
      deleteThread: (threadId: string) =>
NEW
65
        wrapSync('deleteThread', () => {
×
NEW
66
          deleteThreadFromDb(threadId)
×
67
        }),
68

69
      clearAllThreads: () =>
NEW
70
        wrapSync('clearAllThreads', () => {
×
NEW
71
          clearAllThreadsFromDb()
×
72
        }),
73

74
      writeActiveThread: (threadId: string | undefined) =>
75
        wrapSync('writeActiveThread', () => {
5✔
76
          writeActiveThread(threadId)
5✔
77
        }),
78

79
      readThreadSelectedModel: (threadId: string) =>
80
        wrapSync('readThreadSelectedModel', () =>
3✔
81
          readThreadSelectedModel(threadId)
3✔
82
        ),
83

84
      writeThreadSelectedModel: (
85
        threadId: string,
86
        provider: string | null,
87
        model: string | null
88
      ) =>
89
        wrapSync('writeThreadSelectedModel', () => {
4✔
90
          writeThreadSelectedModel(threadId, provider, model)
4✔
91
        }),
92

93
      readThreadEnabledMcpTools: (threadId: string) =>
94
        wrapSync('readThreadEnabledMcpTools', () =>
5✔
95
          readThreadEnabledMcpTools(threadId)
5✔
96
        ),
97

98
      writeThreadEnabledMcpTools: (
99
        threadId: string,
100
        tools: Record<string, string[]>
101
      ) =>
102
        wrapSync('writeThreadEnabledMcpTools', () => {
3✔
103
          writeThreadEnabledMcpTools(threadId, tools)
3✔
104
        }),
105

106
      readThreadEnabledSkills: (threadId: string) =>
107
        wrapSync('readThreadEnabledSkills', () =>
7✔
108
          readThreadEnabledSkills(threadId)
7✔
109
        ),
110

111
      writeThreadEnabledSkills: (threadId: string, skills: string[]) =>
112
        wrapSync('writeThreadEnabledSkills', () => {
5✔
113
          writeThreadEnabledSkills(threadId, skills)
5✔
114
        }),
115

116
      readThreadAgentId: (threadId: string) =>
117
        wrapSync('readThreadAgentId', () => readThreadAgentId(threadId)),
4✔
118

119
      writeThreadAgentId: (threadId: string, agentId: string | null) =>
120
        wrapSync('writeThreadAgentId', () => {
2✔
121
          writeThreadAgentId(threadId, agentId)
2✔
122
        }),
123

124
      ensureRow: (threadId: string) =>
125
        Effect.gen(function* () {
14✔
126
          const existing = yield* wrapSync('readThread', () =>
14✔
127
            readThread(threadId)
14✔
128
          )
129
          if (existing) return
14✔
130
          const now = Date.now()
5✔
131
          yield* wrapSync('writeThread', () => {
5✔
132
            writeThread({
5✔
133
              id: threadId,
134
              messages: [],
135
              lastEditTimestamp: now,
136
              createdAt: now,
137
            })
138
          })
139
        }),
140

141
      updateMessages: (threadId: string, messages: ThreadMessage[]) =>
142
        Effect.gen(function* () {
13✔
143
          const existing = yield* wrapSync('readThread', () =>
13✔
144
            readThread(threadId)
13✔
145
          )
146
          if (!existing) {
13!
NEW
147
            return yield* Effect.fail(
×
148
              new StorageError({
149
                operation: 'updateMessages',
150
                userMessage: 'Thread not found',
151
              })
152
            )
153
          }
154
          const updated: ChatSettingsThread = {
13✔
155
            ...existing,
156
            messages,
157
            lastEditTimestamp: Date.now(),
158
          }
159
          yield* wrapSync('writeThread', () => {
13✔
160
            writeThread(updated)
13✔
161
          })
162
        }),
163
    }),
164
  }
165
) {}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc