twinny 0.0.0-dev.260525150705
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +264 -0
- package/README.zh-CN.md +252 -0
- package/configs/banner.png +0 -0
- package/configs/logo.png +0 -0
- package/dist/app/caffeinate.d.ts +28 -0
- package/dist/app/caffeinate.js +96 -0
- package/dist/app/caffeinate.js.map +1 -0
- package/dist/app/daemon.d.ts +1 -0
- package/dist/app/daemon.js +44 -0
- package/dist/app/daemon.js.map +1 -0
- package/dist/app/lark-assets.d.ts +25 -0
- package/dist/app/lark-assets.js +108 -0
- package/dist/app/lark-assets.js.map +1 -0
- package/dist/app/startup-probe.d.ts +17 -0
- package/dist/app/startup-probe.js +90 -0
- package/dist/app/startup-probe.js.map +1 -0
- package/dist/app/wiring.d.ts +122 -0
- package/dist/app/wiring.js +694 -0
- package/dist/app/wiring.js.map +1 -0
- package/dist/cli/commands.d.ts +1 -0
- package/dist/cli/commands.js +47 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/install-wizard.d.ts +41 -0
- package/dist/cli/install-wizard.js +629 -0
- package/dist/cli/install-wizard.js.map +1 -0
- package/dist/codex/appserver.d.ts +109 -0
- package/dist/codex/appserver.js +308 -0
- package/dist/codex/appserver.js.map +1 -0
- package/dist/codex/goal.d.ts +64 -0
- package/dist/codex/goal.js +433 -0
- package/dist/codex/goal.js.map +1 -0
- package/dist/codex/index.d.ts +6 -0
- package/dist/codex/index.js +7 -0
- package/dist/codex/index.js.map +1 -0
- package/dist/codex/protocol.d.ts +95 -0
- package/dist/codex/protocol.js +205 -0
- package/dist/codex/protocol.js.map +1 -0
- package/dist/codex/thread-name.d.ts +3 -0
- package/dist/codex/thread-name.js +27 -0
- package/dist/codex/thread-name.js.map +1 -0
- package/dist/codex/thread.d.ts +76 -0
- package/dist/codex/thread.js +80 -0
- package/dist/codex/thread.js.map +1 -0
- package/dist/codex/turn.d.ts +166 -0
- package/dist/codex/turn.js +746 -0
- package/dist/codex/turn.js.map +1 -0
- package/dist/config/bootstrap.d.ts +14 -0
- package/dist/config/bootstrap.js +56 -0
- package/dist/config/bootstrap.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.js +5 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +49 -0
- package/dist/config/loader.js +467 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/paths.d.ts +11 -0
- package/dist/config/paths.js +43 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/secrets.d.ts +33 -0
- package/dist/config/secrets.js +85 -0
- package/dist/config/secrets.js.map +1 -0
- package/dist/conversation/manager.d.ts +701 -0
- package/dist/conversation/manager.js +7673 -0
- package/dist/conversation/manager.js.map +1 -0
- package/dist/conversation/queue.d.ts +8 -0
- package/dist/conversation/queue.js +28 -0
- package/dist/conversation/queue.js.map +1 -0
- package/dist/conversation/routing.d.ts +11 -0
- package/dist/conversation/routing.js +55 -0
- package/dist/conversation/routing.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/lark/auth.d.ts +41 -0
- package/dist/lark/auth.js +132 -0
- package/dist/lark/auth.js.map +1 -0
- package/dist/lark/browser-auth.d.ts +68 -0
- package/dist/lark/browser-auth.js +258 -0
- package/dist/lark/browser-auth.js.map +1 -0
- package/dist/lark/cards.d.ts +140 -0
- package/dist/lark/cards.js +1150 -0
- package/dist/lark/cards.js.map +1 -0
- package/dist/lark/contact.d.ts +41 -0
- package/dist/lark/contact.js +122 -0
- package/dist/lark/contact.js.map +1 -0
- package/dist/lark/events.d.ts +65 -0
- package/dist/lark/events.js +218 -0
- package/dist/lark/events.js.map +1 -0
- package/dist/lark/files.d.ts +36 -0
- package/dist/lark/files.js +191 -0
- package/dist/lark/files.js.map +1 -0
- package/dist/lark/filters.d.ts +73 -0
- package/dist/lark/filters.js +678 -0
- package/dist/lark/filters.js.map +1 -0
- package/dist/lark/index.d.ts +10 -0
- package/dist/lark/index.js +11 -0
- package/dist/lark/index.js.map +1 -0
- package/dist/lark/messages.d.ts +87 -0
- package/dist/lark/messages.js +428 -0
- package/dist/lark/messages.js.map +1 -0
- package/dist/lark/openapi.d.ts +58 -0
- package/dist/lark/openapi.js +206 -0
- package/dist/lark/openapi.js.map +1 -0
- package/dist/lark/redactor.d.ts +5 -0
- package/dist/lark/redactor.js +68 -0
- package/dist/lark/redactor.js.map +1 -0
- package/dist/lark/types.d.ts +49 -0
- package/dist/lark/types.js +18 -0
- package/dist/lark/types.js.map +1 -0
- package/dist/launchd/install.d.ts +22 -0
- package/dist/launchd/install.js +114 -0
- package/dist/launchd/install.js.map +1 -0
- package/dist/launchd/plist.d.ts +10 -0
- package/dist/launchd/plist.js +61 -0
- package/dist/launchd/plist.js.map +1 -0
- package/dist/lock/index.d.ts +20 -0
- package/dist/lock/index.js +74 -0
- package/dist/lock/index.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +11 -0
- package/dist/main.js.map +1 -0
- package/dist/markdown.d.ts +12 -0
- package/dist/markdown.js +149 -0
- package/dist/markdown.js.map +1 -0
- package/dist/observability/health.d.ts +25 -0
- package/dist/observability/health.js +187 -0
- package/dist/observability/health.js.map +1 -0
- package/dist/observability/logs.d.ts +10 -0
- package/dist/observability/logs.js +34 -0
- package/dist/observability/logs.js.map +1 -0
- package/dist/observability/system-notifications.d.ts +25 -0
- package/dist/observability/system-notifications.js +33 -0
- package/dist/observability/system-notifications.js.map +1 -0
- package/dist/profiles/guest.d.ts +19 -0
- package/dist/profiles/guest.js +241 -0
- package/dist/profiles/guest.js.map +1 -0
- package/dist/profiles/index.d.ts +5 -0
- package/dist/profiles/index.js +14 -0
- package/dist/profiles/index.js.map +1 -0
- package/dist/profiles/owner.d.ts +1 -0
- package/dist/profiles/owner.js +6 -0
- package/dist/profiles/owner.js.map +1 -0
- package/dist/store/db.d.ts +10 -0
- package/dist/store/db.js +29 -0
- package/dist/store/db.js.map +1 -0
- package/dist/store/index.d.ts +3 -0
- package/dist/store/index.js +4 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/migrations.d.ts +13 -0
- package/dist/store/migrations.js +79 -0
- package/dist/store/migrations.js.map +1 -0
- package/dist/store/repositories.d.ts +227 -0
- package/dist/store/repositories.js +1384 -0
- package/dist/store/repositories.js.map +1 -0
- package/dist/telemetry/client.d.ts +60 -0
- package/dist/telemetry/client.js +204 -0
- package/dist/telemetry/client.js.map +1 -0
- package/dist/telemetry/hash.d.ts +1 -0
- package/dist/telemetry/hash.js +8 -0
- package/dist/telemetry/hash.js.map +1 -0
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/index.js +5 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/posthog.d.ts +27 -0
- package/dist/telemetry/posthog.js +45 -0
- package/dist/telemetry/posthog.js.map +1 -0
- package/dist/telemetry/reporter.d.ts +13 -0
- package/dist/telemetry/reporter.js +29 -0
- package/dist/telemetry/reporter.js.map +1 -0
- package/dist/types.d.ts +330 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/dist/version.js.map +1 -0
- package/dist/version.json +3 -0
- package/dist/workspace/index.d.ts +2 -0
- package/dist/workspace/index.js +3 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/manager.d.ts +14 -0
- package/dist/workspace/manager.js +69 -0
- package/dist/workspace/manager.js.map +1 -0
- package/dist/workspace/slug.d.ts +8 -0
- package/dist/workspace/slug.js +59 -0
- package/dist/workspace/slug.js.map +1 -0
- package/migrations/0001_initial.sql +102 -0
- package/package.json +85 -0
|
@@ -0,0 +1,1384 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { TwinnyError } from "../errors.js";
|
|
3
|
+
import { assertValidConversationKey, createGroupConversationKey, createP2PConversationKey } from "../workspace/slug.js";
|
|
4
|
+
export class ConversationRepository {
|
|
5
|
+
db;
|
|
6
|
+
now;
|
|
7
|
+
insertConversation;
|
|
8
|
+
selectByConversationKey;
|
|
9
|
+
selectByTypeAndChatId;
|
|
10
|
+
selectByCodexThreadId;
|
|
11
|
+
selectAll;
|
|
12
|
+
updateSettings;
|
|
13
|
+
updateThread;
|
|
14
|
+
markCodexThreadRollout;
|
|
15
|
+
deleteByKey;
|
|
16
|
+
upsertCodexThreadStatement;
|
|
17
|
+
selectCodexThreadById;
|
|
18
|
+
selectCodexThreadByConversationAndLarkThread;
|
|
19
|
+
replaceCodexThreadForLarkThreadStatement;
|
|
20
|
+
updateCodexThreadUsageStatement;
|
|
21
|
+
updateCodexThreadCardStatement;
|
|
22
|
+
updateCodexThreadModelSettingsStatement;
|
|
23
|
+
updateCodexThreadNameStatement;
|
|
24
|
+
updateCodexThreadModeStatement;
|
|
25
|
+
updateCodexThreadStatusStatement;
|
|
26
|
+
updateCodexThreadGoalStatusStatement;
|
|
27
|
+
selectCodexThreadWorkStats;
|
|
28
|
+
selectCodexThreadStatusStats;
|
|
29
|
+
selectConversationStatusStats;
|
|
30
|
+
insertLarkMessageStatement;
|
|
31
|
+
selectLarkMessageById;
|
|
32
|
+
selectLarkMessageByEventId;
|
|
33
|
+
selectLarkMessageUsageTargetForTurn;
|
|
34
|
+
selectLatestSteeredLarkMessageForTurn;
|
|
35
|
+
selectUnfinishedLarkMessages;
|
|
36
|
+
updateLarkMessageUsageStatement;
|
|
37
|
+
updateLarkMessageProcessingStatement;
|
|
38
|
+
updateLarkMessageSteeredStatement;
|
|
39
|
+
updateLarkMessageQueuedStatement;
|
|
40
|
+
updateQueuedLarkMessageStatement;
|
|
41
|
+
updateLarkMessageSideMetadataStatement;
|
|
42
|
+
updateLarkMessageRecalledStatement;
|
|
43
|
+
updateLarkMessageCompletedStatement;
|
|
44
|
+
updateLarkMessageFailedStatement;
|
|
45
|
+
updateLarkMessageInterruptedStatement;
|
|
46
|
+
updateLarkMessageClearedStatement;
|
|
47
|
+
constructor(db, options = {}) {
|
|
48
|
+
this.db = db;
|
|
49
|
+
this.now = options.now ?? Date.now;
|
|
50
|
+
this.insertConversation = this.db.prepare(`
|
|
51
|
+
INSERT INTO conversations (
|
|
52
|
+
conversation_key,
|
|
53
|
+
type,
|
|
54
|
+
chat_id,
|
|
55
|
+
name,
|
|
56
|
+
response_mode,
|
|
57
|
+
profile,
|
|
58
|
+
thread_id,
|
|
59
|
+
workspace,
|
|
60
|
+
profile_codex_home,
|
|
61
|
+
created_at,
|
|
62
|
+
updated_at
|
|
63
|
+
) VALUES (
|
|
64
|
+
@conversationKey,
|
|
65
|
+
@type,
|
|
66
|
+
@chatId,
|
|
67
|
+
@name,
|
|
68
|
+
@responseMode,
|
|
69
|
+
@profile,
|
|
70
|
+
@codexThreadId,
|
|
71
|
+
@workspace,
|
|
72
|
+
@profileCodexHome,
|
|
73
|
+
@createdAt,
|
|
74
|
+
@updatedAt
|
|
75
|
+
)
|
|
76
|
+
`);
|
|
77
|
+
this.selectByConversationKey = this.db.prepare(`
|
|
78
|
+
SELECT * FROM conversations WHERE conversation_key = ?
|
|
79
|
+
`);
|
|
80
|
+
this.selectByTypeAndChatId = this.db.prepare(`
|
|
81
|
+
SELECT * FROM conversations WHERE type = ? AND chat_id = ?
|
|
82
|
+
`);
|
|
83
|
+
this.selectByCodexThreadId = this.db.prepare(`
|
|
84
|
+
SELECT * FROM conversations WHERE thread_id = ?
|
|
85
|
+
`);
|
|
86
|
+
this.selectAll = this.db.prepare(`
|
|
87
|
+
SELECT * FROM conversations ORDER BY id ASC
|
|
88
|
+
`);
|
|
89
|
+
this.updateSettings = this.db.prepare(`
|
|
90
|
+
UPDATE conversations
|
|
91
|
+
SET type = ?,
|
|
92
|
+
name = ?,
|
|
93
|
+
response_mode = ?,
|
|
94
|
+
updated_at = ?
|
|
95
|
+
WHERE conversation_key = ?
|
|
96
|
+
`);
|
|
97
|
+
this.updateThread = this.db.prepare(`
|
|
98
|
+
UPDATE conversations
|
|
99
|
+
SET thread_id = ?,
|
|
100
|
+
profile = ?,
|
|
101
|
+
profile_codex_home = ?,
|
|
102
|
+
workspace = ?,
|
|
103
|
+
updated_at = ?
|
|
104
|
+
WHERE conversation_key = ?
|
|
105
|
+
`);
|
|
106
|
+
this.markCodexThreadRollout = this.db.prepare(`
|
|
107
|
+
UPDATE threads
|
|
108
|
+
SET thread_has_rollout = 1,
|
|
109
|
+
updated_at = ?
|
|
110
|
+
WHERE conversation_key = ?
|
|
111
|
+
AND thread_id = ?
|
|
112
|
+
`);
|
|
113
|
+
this.deleteByKey = this.db.prepare(`
|
|
114
|
+
DELETE FROM conversations WHERE conversation_key = ?
|
|
115
|
+
`);
|
|
116
|
+
this.upsertCodexThreadStatement = this.db.prepare(`
|
|
117
|
+
INSERT INTO threads (
|
|
118
|
+
thread_id,
|
|
119
|
+
conversation_key,
|
|
120
|
+
name,
|
|
121
|
+
lark_thread_id,
|
|
122
|
+
profile,
|
|
123
|
+
model,
|
|
124
|
+
effort,
|
|
125
|
+
forked_from_thread_id,
|
|
126
|
+
forked_at,
|
|
127
|
+
thread_has_rollout,
|
|
128
|
+
total_tokens,
|
|
129
|
+
token_usage_json,
|
|
130
|
+
created_at,
|
|
131
|
+
updated_at
|
|
132
|
+
) VALUES (
|
|
133
|
+
@codexThreadId,
|
|
134
|
+
@conversationKey,
|
|
135
|
+
COALESCE(@name, '新会话'),
|
|
136
|
+
@larkThreadId,
|
|
137
|
+
@profile,
|
|
138
|
+
@model,
|
|
139
|
+
@effort,
|
|
140
|
+
@forkedFromCodexThreadId,
|
|
141
|
+
@forkedAt,
|
|
142
|
+
@codexThreadHasRollout,
|
|
143
|
+
@totalTokens,
|
|
144
|
+
@tokenUsageJson,
|
|
145
|
+
@createdAt,
|
|
146
|
+
@updatedAt
|
|
147
|
+
)
|
|
148
|
+
ON CONFLICT(thread_id) DO UPDATE SET
|
|
149
|
+
conversation_key = excluded.conversation_key,
|
|
150
|
+
name = COALESCE(@name, threads.name),
|
|
151
|
+
lark_thread_id = COALESCE(excluded.lark_thread_id, threads.lark_thread_id),
|
|
152
|
+
profile = excluded.profile,
|
|
153
|
+
model = COALESCE(excluded.model, threads.model),
|
|
154
|
+
effort = COALESCE(excluded.effort, threads.effort),
|
|
155
|
+
forked_from_thread_id = COALESCE(excluded.forked_from_thread_id, threads.forked_from_thread_id),
|
|
156
|
+
forked_at = COALESCE(excluded.forked_at, threads.forked_at),
|
|
157
|
+
thread_has_rollout = CASE
|
|
158
|
+
WHEN threads.thread_has_rollout = 1 OR excluded.thread_has_rollout = 1 THEN 1
|
|
159
|
+
ELSE 0
|
|
160
|
+
END,
|
|
161
|
+
updated_at = excluded.updated_at
|
|
162
|
+
`);
|
|
163
|
+
this.selectCodexThreadById = this.db.prepare(`
|
|
164
|
+
SELECT * FROM threads WHERE thread_id = ?
|
|
165
|
+
`);
|
|
166
|
+
this.selectCodexThreadByConversationAndLarkThread = this.db.prepare(`
|
|
167
|
+
SELECT * FROM threads
|
|
168
|
+
WHERE conversation_key = ?
|
|
169
|
+
AND lark_thread_id = ?
|
|
170
|
+
`);
|
|
171
|
+
this.replaceCodexThreadForLarkThreadStatement = this.db.prepare(`
|
|
172
|
+
UPDATE threads
|
|
173
|
+
SET thread_id = @codexThreadId,
|
|
174
|
+
profile = @profile,
|
|
175
|
+
model = @model,
|
|
176
|
+
effort = @effort,
|
|
177
|
+
input_tokens = 0,
|
|
178
|
+
output_tokens = 0,
|
|
179
|
+
cached_input_tokens = 0,
|
|
180
|
+
reasoning_output_tokens = 0,
|
|
181
|
+
total_tokens = 0,
|
|
182
|
+
context_tokens = 0,
|
|
183
|
+
context_window = 0,
|
|
184
|
+
thread_has_rollout = @codexThreadHasRollout,
|
|
185
|
+
token_usage_json = '{}',
|
|
186
|
+
goal_status = 'none',
|
|
187
|
+
goal_updated_at = NULL,
|
|
188
|
+
updated_at = @updatedAt
|
|
189
|
+
WHERE conversation_key = @conversationKey
|
|
190
|
+
AND lark_thread_id = @larkThreadId
|
|
191
|
+
`);
|
|
192
|
+
this.updateCodexThreadUsageStatement = this.db.prepare(`
|
|
193
|
+
INSERT INTO threads (
|
|
194
|
+
thread_id,
|
|
195
|
+
conversation_key,
|
|
196
|
+
name,
|
|
197
|
+
lark_thread_id,
|
|
198
|
+
profile,
|
|
199
|
+
model,
|
|
200
|
+
effort,
|
|
201
|
+
input_tokens,
|
|
202
|
+
output_tokens,
|
|
203
|
+
cached_input_tokens,
|
|
204
|
+
reasoning_output_tokens,
|
|
205
|
+
total_tokens,
|
|
206
|
+
context_tokens,
|
|
207
|
+
context_window,
|
|
208
|
+
thread_has_rollout,
|
|
209
|
+
token_usage_json,
|
|
210
|
+
created_at,
|
|
211
|
+
updated_at
|
|
212
|
+
) VALUES (
|
|
213
|
+
@codexThreadId,
|
|
214
|
+
@conversationKey,
|
|
215
|
+
COALESCE(@name, '新会话'),
|
|
216
|
+
NULL,
|
|
217
|
+
@profile,
|
|
218
|
+
@model,
|
|
219
|
+
@effort,
|
|
220
|
+
@inputTokens,
|
|
221
|
+
@outputTokens,
|
|
222
|
+
@cachedInputTokens,
|
|
223
|
+
@reasoningOutputTokens,
|
|
224
|
+
@totalTokens,
|
|
225
|
+
@contextTokens,
|
|
226
|
+
@contextWindow,
|
|
227
|
+
1,
|
|
228
|
+
@tokenUsageJson,
|
|
229
|
+
@createdAt,
|
|
230
|
+
@updatedAt
|
|
231
|
+
)
|
|
232
|
+
ON CONFLICT(thread_id) DO UPDATE SET
|
|
233
|
+
conversation_key = excluded.conversation_key,
|
|
234
|
+
name = COALESCE(@name, threads.name),
|
|
235
|
+
profile = excluded.profile,
|
|
236
|
+
model = COALESCE(excluded.model, threads.model),
|
|
237
|
+
effort = COALESCE(excluded.effort, threads.effort),
|
|
238
|
+
input_tokens = excluded.input_tokens,
|
|
239
|
+
output_tokens = excluded.output_tokens,
|
|
240
|
+
cached_input_tokens = excluded.cached_input_tokens,
|
|
241
|
+
reasoning_output_tokens = excluded.reasoning_output_tokens,
|
|
242
|
+
total_tokens = excluded.total_tokens,
|
|
243
|
+
context_tokens = excluded.context_tokens,
|
|
244
|
+
context_window = excluded.context_window,
|
|
245
|
+
thread_has_rollout = 1,
|
|
246
|
+
token_usage_json = excluded.token_usage_json,
|
|
247
|
+
updated_at = excluded.updated_at
|
|
248
|
+
`);
|
|
249
|
+
this.updateCodexThreadCardStatement = this.db.prepare(`
|
|
250
|
+
INSERT INTO threads (
|
|
251
|
+
thread_id,
|
|
252
|
+
conversation_key,
|
|
253
|
+
name,
|
|
254
|
+
lark_thread_id,
|
|
255
|
+
profile,
|
|
256
|
+
model,
|
|
257
|
+
effort,
|
|
258
|
+
creator_open_id,
|
|
259
|
+
card_message_id,
|
|
260
|
+
thread_has_rollout,
|
|
261
|
+
created_at,
|
|
262
|
+
updated_at
|
|
263
|
+
) VALUES (
|
|
264
|
+
@codexThreadId,
|
|
265
|
+
@conversationKey,
|
|
266
|
+
COALESCE(@name, '新会话'),
|
|
267
|
+
@larkThreadId,
|
|
268
|
+
@profile,
|
|
269
|
+
@model,
|
|
270
|
+
@effort,
|
|
271
|
+
@creatorOpenId,
|
|
272
|
+
@cardMessageId,
|
|
273
|
+
0,
|
|
274
|
+
@createdAt,
|
|
275
|
+
@updatedAt
|
|
276
|
+
)
|
|
277
|
+
ON CONFLICT(thread_id) DO UPDATE SET
|
|
278
|
+
conversation_key = excluded.conversation_key,
|
|
279
|
+
name = COALESCE(@name, threads.name),
|
|
280
|
+
lark_thread_id = COALESCE(excluded.lark_thread_id, threads.lark_thread_id),
|
|
281
|
+
profile = excluded.profile,
|
|
282
|
+
model = COALESCE(excluded.model, threads.model),
|
|
283
|
+
effort = COALESCE(excluded.effort, threads.effort),
|
|
284
|
+
creator_open_id = COALESCE(excluded.creator_open_id, threads.creator_open_id),
|
|
285
|
+
card_message_id = COALESCE(excluded.card_message_id, threads.card_message_id),
|
|
286
|
+
updated_at = excluded.updated_at
|
|
287
|
+
`);
|
|
288
|
+
this.updateCodexThreadModelSettingsStatement = this.db.prepare(`
|
|
289
|
+
UPDATE threads
|
|
290
|
+
SET model = ?,
|
|
291
|
+
effort = ?,
|
|
292
|
+
updated_at = ?
|
|
293
|
+
WHERE thread_id = ?
|
|
294
|
+
`);
|
|
295
|
+
this.updateCodexThreadNameStatement = this.db.prepare(`
|
|
296
|
+
UPDATE threads
|
|
297
|
+
SET name = ?,
|
|
298
|
+
updated_at = ?
|
|
299
|
+
WHERE thread_id = ?
|
|
300
|
+
`);
|
|
301
|
+
this.updateCodexThreadModeStatement = this.db.prepare(`
|
|
302
|
+
UPDATE threads
|
|
303
|
+
SET mode = ?,
|
|
304
|
+
updated_at = ?
|
|
305
|
+
WHERE conversation_key = ?
|
|
306
|
+
AND thread_id = ?
|
|
307
|
+
`);
|
|
308
|
+
this.updateCodexThreadStatusStatement = this.db.prepare(`
|
|
309
|
+
UPDATE threads
|
|
310
|
+
SET status = ?,
|
|
311
|
+
updated_at = ?
|
|
312
|
+
WHERE conversation_key = ?
|
|
313
|
+
AND thread_id = ?
|
|
314
|
+
`);
|
|
315
|
+
this.updateCodexThreadGoalStatusStatement = this.db.prepare(`
|
|
316
|
+
UPDATE threads
|
|
317
|
+
SET goal_status = ?,
|
|
318
|
+
goal_updated_at = ?,
|
|
319
|
+
updated_at = ?
|
|
320
|
+
WHERE thread_id = ?
|
|
321
|
+
`);
|
|
322
|
+
this.selectCodexThreadWorkStats = this.db.prepare(`
|
|
323
|
+
SELECT
|
|
324
|
+
COUNT(*) AS turn_count,
|
|
325
|
+
COALESCE(SUM(CASE
|
|
326
|
+
WHEN terminal_at IS NOT NULL AND started_at IS NOT NULL AND terminal_at > started_at
|
|
327
|
+
THEN terminal_at - started_at
|
|
328
|
+
ELSE 0
|
|
329
|
+
END), 0) AS total_work_duration_ms
|
|
330
|
+
FROM (
|
|
331
|
+
SELECT
|
|
332
|
+
codex_turn_id,
|
|
333
|
+
MIN(processing_started_at) AS started_at,
|
|
334
|
+
MAX(COALESCE(completed_at, failed_at, cleared_at)) AS terminal_at
|
|
335
|
+
FROM lark_messages
|
|
336
|
+
WHERE thread_id = ?
|
|
337
|
+
AND codex_turn_id IS NOT NULL
|
|
338
|
+
AND processing_started_at IS NOT NULL
|
|
339
|
+
AND route_kind <> 'side_message'
|
|
340
|
+
GROUP BY codex_turn_id
|
|
341
|
+
)
|
|
342
|
+
`);
|
|
343
|
+
this.selectCodexThreadStatusStats = this.db.prepare(`
|
|
344
|
+
SELECT
|
|
345
|
+
(
|
|
346
|
+
SELECT COUNT(*)
|
|
347
|
+
FROM lark_messages
|
|
348
|
+
WHERE thread_id = @codexThreadId
|
|
349
|
+
AND route_kind IN ('message', 'goal_message', 'steered_message', 'queued_message', 'side_message')
|
|
350
|
+
) AS user_message_count,
|
|
351
|
+
COUNT(*) AS turn_count,
|
|
352
|
+
COALESCE(SUM(CASE
|
|
353
|
+
WHEN terminal_at IS NOT NULL AND started_at IS NOT NULL AND terminal_at > started_at
|
|
354
|
+
THEN terminal_at - started_at
|
|
355
|
+
ELSE 0
|
|
356
|
+
END), 0) AS total_work_duration_ms
|
|
357
|
+
FROM (
|
|
358
|
+
SELECT
|
|
359
|
+
codex_turn_id,
|
|
360
|
+
MIN(processing_started_at) AS started_at,
|
|
361
|
+
MAX(COALESCE(completed_at, failed_at, cleared_at)) AS terminal_at
|
|
362
|
+
FROM lark_messages
|
|
363
|
+
WHERE thread_id = @codexThreadId
|
|
364
|
+
AND codex_turn_id IS NOT NULL
|
|
365
|
+
AND processing_started_at IS NOT NULL
|
|
366
|
+
AND route_kind <> 'side_message'
|
|
367
|
+
GROUP BY codex_turn_id
|
|
368
|
+
)
|
|
369
|
+
`);
|
|
370
|
+
this.selectConversationStatusStats = this.db.prepare(`
|
|
371
|
+
SELECT
|
|
372
|
+
(
|
|
373
|
+
SELECT COUNT(*)
|
|
374
|
+
FROM threads
|
|
375
|
+
WHERE conversation_key = @conversationKey
|
|
376
|
+
) AS topic_count,
|
|
377
|
+
(
|
|
378
|
+
SELECT COUNT(*)
|
|
379
|
+
FROM lark_messages
|
|
380
|
+
WHERE conversation_key = @conversationKey
|
|
381
|
+
AND route_kind IN ('message', 'goal_message', 'steered_message', 'queued_message', 'side_message')
|
|
382
|
+
) AS user_message_count,
|
|
383
|
+
(
|
|
384
|
+
SELECT COALESCE(SUM(input_tokens), 0)
|
|
385
|
+
FROM threads
|
|
386
|
+
WHERE conversation_key = @conversationKey
|
|
387
|
+
) AS input_tokens,
|
|
388
|
+
(
|
|
389
|
+
SELECT COALESCE(SUM(output_tokens), 0)
|
|
390
|
+
FROM threads
|
|
391
|
+
WHERE conversation_key = @conversationKey
|
|
392
|
+
) AS output_tokens,
|
|
393
|
+
(
|
|
394
|
+
SELECT COALESCE(SUM(cached_input_tokens), 0)
|
|
395
|
+
FROM threads
|
|
396
|
+
WHERE conversation_key = @conversationKey
|
|
397
|
+
) AS cached_input_tokens,
|
|
398
|
+
(
|
|
399
|
+
SELECT COALESCE(SUM(reasoning_output_tokens), 0)
|
|
400
|
+
FROM threads
|
|
401
|
+
WHERE conversation_key = @conversationKey
|
|
402
|
+
) AS reasoning_output_tokens,
|
|
403
|
+
(
|
|
404
|
+
SELECT COALESCE(SUM(total_tokens), 0)
|
|
405
|
+
FROM threads
|
|
406
|
+
WHERE conversation_key = @conversationKey
|
|
407
|
+
) AS total_tokens,
|
|
408
|
+
COALESCE(SUM(CASE
|
|
409
|
+
WHEN terminal_at IS NOT NULL AND started_at IS NOT NULL AND terminal_at > started_at
|
|
410
|
+
THEN terminal_at - started_at
|
|
411
|
+
ELSE 0
|
|
412
|
+
END), 0) AS total_work_duration_ms
|
|
413
|
+
FROM (
|
|
414
|
+
SELECT
|
|
415
|
+
thread_id,
|
|
416
|
+
codex_turn_id,
|
|
417
|
+
MIN(processing_started_at) AS started_at,
|
|
418
|
+
MAX(COALESCE(completed_at, failed_at, cleared_at)) AS terminal_at
|
|
419
|
+
FROM lark_messages
|
|
420
|
+
WHERE conversation_key = @conversationKey
|
|
421
|
+
AND thread_id IS NOT NULL
|
|
422
|
+
AND codex_turn_id IS NOT NULL
|
|
423
|
+
AND processing_started_at IS NOT NULL
|
|
424
|
+
AND route_kind <> 'side_message'
|
|
425
|
+
GROUP BY thread_id, codex_turn_id
|
|
426
|
+
)
|
|
427
|
+
`);
|
|
428
|
+
this.insertLarkMessageStatement = this.db.prepare(`
|
|
429
|
+
INSERT INTO lark_messages (
|
|
430
|
+
lark_message_id,
|
|
431
|
+
event_id,
|
|
432
|
+
lark_user_id,
|
|
433
|
+
lark_group_id,
|
|
434
|
+
lark_thread_id,
|
|
435
|
+
conversation_key,
|
|
436
|
+
thread_id,
|
|
437
|
+
codex_turn_id,
|
|
438
|
+
route_kind,
|
|
439
|
+
status,
|
|
440
|
+
text,
|
|
441
|
+
lark_create_time,
|
|
442
|
+
side_id,
|
|
443
|
+
agent_card_message_id,
|
|
444
|
+
received_at,
|
|
445
|
+
updated_at,
|
|
446
|
+
raw_event_json
|
|
447
|
+
) VALUES (
|
|
448
|
+
@larkMessageId,
|
|
449
|
+
@eventId,
|
|
450
|
+
@larkUserId,
|
|
451
|
+
@larkGroupId,
|
|
452
|
+
@larkThreadId,
|
|
453
|
+
@conversationKey,
|
|
454
|
+
@codexThreadId,
|
|
455
|
+
@codexTurnId,
|
|
456
|
+
@routeKind,
|
|
457
|
+
@status,
|
|
458
|
+
@text,
|
|
459
|
+
@larkCreateTime,
|
|
460
|
+
@sideId,
|
|
461
|
+
@agentCardMessageId,
|
|
462
|
+
@receivedAt,
|
|
463
|
+
@updatedAt,
|
|
464
|
+
@rawEventJson
|
|
465
|
+
)
|
|
466
|
+
ON CONFLICT(event_id) DO NOTHING
|
|
467
|
+
`);
|
|
468
|
+
this.selectLarkMessageById = this.db.prepare(`
|
|
469
|
+
SELECT * FROM lark_messages WHERE lark_message_id = ?
|
|
470
|
+
`);
|
|
471
|
+
this.selectLarkMessageByEventId = this.db.prepare(`
|
|
472
|
+
SELECT * FROM lark_messages
|
|
473
|
+
WHERE event_id = ?
|
|
474
|
+
ORDER BY id ASC
|
|
475
|
+
LIMIT 1
|
|
476
|
+
`);
|
|
477
|
+
this.selectLarkMessageUsageTargetForTurn = this.db.prepare(`
|
|
478
|
+
SELECT * FROM lark_messages
|
|
479
|
+
WHERE thread_id = ?
|
|
480
|
+
AND codex_turn_id = ?
|
|
481
|
+
AND lark_message_id IS NOT NULL
|
|
482
|
+
AND route_kind <> 'steered_message'
|
|
483
|
+
ORDER BY received_at ASC, id ASC
|
|
484
|
+
LIMIT 1
|
|
485
|
+
`);
|
|
486
|
+
this.selectLatestSteeredLarkMessageForTurn = this.db.prepare(`
|
|
487
|
+
SELECT * FROM lark_messages
|
|
488
|
+
WHERE thread_id = ?
|
|
489
|
+
AND codex_turn_id = ?
|
|
490
|
+
AND lark_message_id IS NOT NULL
|
|
491
|
+
AND route_kind = 'steered_message'
|
|
492
|
+
ORDER BY received_at DESC, id DESC
|
|
493
|
+
LIMIT 1
|
|
494
|
+
`);
|
|
495
|
+
this.selectUnfinishedLarkMessages = this.db.prepare(`
|
|
496
|
+
SELECT * FROM lark_messages
|
|
497
|
+
WHERE status IN ('processing', 'queued')
|
|
498
|
+
ORDER BY received_at ASC, id ASC
|
|
499
|
+
`);
|
|
500
|
+
this.updateLarkMessageUsageStatement = this.db.prepare(`
|
|
501
|
+
UPDATE lark_messages
|
|
502
|
+
SET input_tokens = @inputTokens,
|
|
503
|
+
output_tokens = @outputTokens,
|
|
504
|
+
cached_input_tokens = @cachedInputTokens,
|
|
505
|
+
reasoning_output_tokens = @reasoningOutputTokens,
|
|
506
|
+
token_usage_json = @tokenUsageJson,
|
|
507
|
+
updated_at = @updatedAt
|
|
508
|
+
WHERE lark_message_id = @larkMessageId
|
|
509
|
+
`);
|
|
510
|
+
this.updateLarkMessageProcessingStatement = this.db.prepare(`
|
|
511
|
+
UPDATE lark_messages
|
|
512
|
+
SET status = 'processing',
|
|
513
|
+
conversation_key = COALESCE(?, conversation_key),
|
|
514
|
+
thread_id = COALESCE(?, thread_id),
|
|
515
|
+
codex_turn_id = COALESCE(?, codex_turn_id),
|
|
516
|
+
processing_started_at = COALESCE(processing_started_at, ?),
|
|
517
|
+
updated_at = ?
|
|
518
|
+
WHERE lark_message_id = ?
|
|
519
|
+
`);
|
|
520
|
+
this.updateLarkMessageQueuedStatement = this.db.prepare(`
|
|
521
|
+
UPDATE lark_messages
|
|
522
|
+
SET route_kind = 'queued_message',
|
|
523
|
+
status = 'queued',
|
|
524
|
+
thread_id = NULL,
|
|
525
|
+
codex_turn_id = NULL,
|
|
526
|
+
updated_at = ?
|
|
527
|
+
WHERE lark_message_id = ?
|
|
528
|
+
`);
|
|
529
|
+
this.updateQueuedLarkMessageStatement = this.db.prepare(`
|
|
530
|
+
UPDATE lark_messages
|
|
531
|
+
SET text = ?,
|
|
532
|
+
raw_event_json = COALESCE(?, raw_event_json),
|
|
533
|
+
updated_at = ?
|
|
534
|
+
WHERE lark_message_id = ?
|
|
535
|
+
AND status = 'queued'
|
|
536
|
+
`);
|
|
537
|
+
this.updateLarkMessageSideMetadataStatement = this.db.prepare(`
|
|
538
|
+
UPDATE lark_messages
|
|
539
|
+
SET side_id = COALESCE(?, side_id),
|
|
540
|
+
agent_card_message_id = COALESCE(?, agent_card_message_id),
|
|
541
|
+
updated_at = ?
|
|
542
|
+
WHERE lark_message_id = ?
|
|
543
|
+
`);
|
|
544
|
+
this.updateLarkMessageRecalledStatement = this.db.prepare(`
|
|
545
|
+
UPDATE lark_messages
|
|
546
|
+
SET status = 'recalled',
|
|
547
|
+
updated_at = ?
|
|
548
|
+
WHERE lark_message_id = ?
|
|
549
|
+
AND status = 'queued'
|
|
550
|
+
`);
|
|
551
|
+
this.updateLarkMessageSteeredStatement = this.db.prepare(`
|
|
552
|
+
UPDATE lark_messages
|
|
553
|
+
SET status = 'steered',
|
|
554
|
+
conversation_key = COALESCE(?, conversation_key),
|
|
555
|
+
thread_id = COALESCE(?, thread_id),
|
|
556
|
+
codex_turn_id = COALESCE(?, codex_turn_id),
|
|
557
|
+
updated_at = ?
|
|
558
|
+
WHERE lark_message_id = ?
|
|
559
|
+
`);
|
|
560
|
+
this.updateLarkMessageCompletedStatement = this.db.prepare(`
|
|
561
|
+
UPDATE lark_messages
|
|
562
|
+
SET status = 'completed',
|
|
563
|
+
completed_at = COALESCE(completed_at, ?),
|
|
564
|
+
updated_at = ?
|
|
565
|
+
WHERE lark_message_id = ?
|
|
566
|
+
`);
|
|
567
|
+
this.updateLarkMessageFailedStatement = this.db.prepare(`
|
|
568
|
+
UPDATE lark_messages
|
|
569
|
+
SET status = 'failed',
|
|
570
|
+
failed_at = COALESCE(failed_at, ?),
|
|
571
|
+
updated_at = ?
|
|
572
|
+
WHERE lark_message_id = ?
|
|
573
|
+
`);
|
|
574
|
+
this.updateLarkMessageInterruptedStatement = this.db.prepare(`
|
|
575
|
+
UPDATE lark_messages
|
|
576
|
+
SET status = 'interrupted',
|
|
577
|
+
failed_at = COALESCE(failed_at, ?),
|
|
578
|
+
updated_at = ?
|
|
579
|
+
WHERE lark_message_id = ?
|
|
580
|
+
`);
|
|
581
|
+
this.updateLarkMessageClearedStatement = this.db.prepare(`
|
|
582
|
+
UPDATE lark_messages
|
|
583
|
+
SET status = 'cleared',
|
|
584
|
+
cleared_at = COALESCE(cleared_at, ?),
|
|
585
|
+
updated_at = ?
|
|
586
|
+
WHERE lark_message_id = ?
|
|
587
|
+
`);
|
|
588
|
+
}
|
|
589
|
+
create(input) {
|
|
590
|
+
const params = this.toInsertParams(input);
|
|
591
|
+
const create = this.db.transaction(() => {
|
|
592
|
+
this.insertConversation.run(params);
|
|
593
|
+
return this.requireByConversationKey(params.conversationKey);
|
|
594
|
+
});
|
|
595
|
+
return create();
|
|
596
|
+
}
|
|
597
|
+
getOrCreate(input) {
|
|
598
|
+
const params = this.toInsertParams(input);
|
|
599
|
+
const getOrCreate = this.db.transaction(() => {
|
|
600
|
+
const existing = this.getByConversationKey(params.conversationKey);
|
|
601
|
+
if (existing) {
|
|
602
|
+
return existing;
|
|
603
|
+
}
|
|
604
|
+
this.insertConversation.run(params);
|
|
605
|
+
return this.requireByConversationKey(params.conversationKey);
|
|
606
|
+
});
|
|
607
|
+
return getOrCreate();
|
|
608
|
+
}
|
|
609
|
+
getByConversationKey(conversationKey) {
|
|
610
|
+
assertValidConversationKey(conversationKey);
|
|
611
|
+
return mapConversationRow(this.selectByConversationKey.get(conversationKey));
|
|
612
|
+
}
|
|
613
|
+
getByTypeAndChatId(type, chatId) {
|
|
614
|
+
assertValidConversationType(type);
|
|
615
|
+
assertExpectedConversationKey(conversationKeyForTypeAndChatId(type, chatId), type, chatId);
|
|
616
|
+
return mapConversationRow(this.selectByTypeAndChatId.get(type, chatId));
|
|
617
|
+
}
|
|
618
|
+
getByCodexThreadId(codexThreadId) {
|
|
619
|
+
assertNonEmpty(codexThreadId, "codexThreadId");
|
|
620
|
+
return mapConversationRow(this.selectByCodexThreadId.get(codexThreadId));
|
|
621
|
+
}
|
|
622
|
+
list() {
|
|
623
|
+
return this.selectAll.all().map((row) => mapRequiredConversationRow(row));
|
|
624
|
+
}
|
|
625
|
+
updateThreadBinding(conversationKey, update) {
|
|
626
|
+
assertValidConversationKey(conversationKey);
|
|
627
|
+
assertNonEmpty(update.codexThreadId, "codexThreadId");
|
|
628
|
+
const updateProfile = resolveOptionalInputProfile(update);
|
|
629
|
+
if (updateProfile !== undefined) {
|
|
630
|
+
assertValidProfile(updateProfile);
|
|
631
|
+
}
|
|
632
|
+
const updateProfileCodexHome = update.profileCodexHome;
|
|
633
|
+
if (updateProfileCodexHome !== undefined) {
|
|
634
|
+
assertNonEmpty(updateProfileCodexHome, "profileCodexHome");
|
|
635
|
+
}
|
|
636
|
+
if (update.workspace !== undefined) {
|
|
637
|
+
assertAbsolutePath(update.workspace, "workspace");
|
|
638
|
+
}
|
|
639
|
+
const updateBinding = this.db.transaction(() => {
|
|
640
|
+
const existing = this.requireByConversationKey(conversationKey);
|
|
641
|
+
const profile = updateProfile ?? existing.profile;
|
|
642
|
+
const profileCodexHome = updateProfileCodexHome ?? existing.profileCodexHome;
|
|
643
|
+
const workspace = update.workspace ?? existing.workspace;
|
|
644
|
+
this.updateThread.run(update.codexThreadId, profile, profileCodexHome, workspace, this.now(), conversationKey);
|
|
645
|
+
return this.requireByConversationKey(conversationKey);
|
|
646
|
+
});
|
|
647
|
+
return updateBinding();
|
|
648
|
+
}
|
|
649
|
+
updateConversationSettings(conversationKey, update) {
|
|
650
|
+
assertValidConversationKey(conversationKey);
|
|
651
|
+
if (update.type !== undefined) {
|
|
652
|
+
assertValidConversationType(update.type);
|
|
653
|
+
}
|
|
654
|
+
if (update.name !== undefined) {
|
|
655
|
+
assertNonEmpty(update.name, "name");
|
|
656
|
+
}
|
|
657
|
+
if (update.responseMode !== undefined) {
|
|
658
|
+
assertValidResponseMode(update.responseMode);
|
|
659
|
+
}
|
|
660
|
+
const updateSettings = this.db.transaction(() => {
|
|
661
|
+
const existing = this.requireByConversationKey(conversationKey);
|
|
662
|
+
const type = update.type ?? existing.type;
|
|
663
|
+
assertExpectedConversationKey(conversationKey, type, existing.chatId);
|
|
664
|
+
const name = update.name ?? existing.name;
|
|
665
|
+
const responseMode = update.responseMode ?? existing.responseMode;
|
|
666
|
+
this.updateSettings.run(type, name, responseMode, this.now(), conversationKey);
|
|
667
|
+
return this.requireByConversationKey(conversationKey);
|
|
668
|
+
});
|
|
669
|
+
return updateSettings();
|
|
670
|
+
}
|
|
671
|
+
markThreadHasRollout(conversationKey, codexThreadId) {
|
|
672
|
+
assertValidConversationKey(conversationKey);
|
|
673
|
+
assertNonEmpty(codexThreadId, "codexThreadId");
|
|
674
|
+
const now = this.now();
|
|
675
|
+
this.markCodexThreadRollout.run(now, conversationKey, codexThreadId);
|
|
676
|
+
}
|
|
677
|
+
deleteByConversationKey(conversationKey) {
|
|
678
|
+
assertValidConversationKey(conversationKey);
|
|
679
|
+
const remove = this.db.transaction(() => this.deleteByKey.run(conversationKey).changes > 0);
|
|
680
|
+
return remove();
|
|
681
|
+
}
|
|
682
|
+
upsertCodexThread(input) {
|
|
683
|
+
validateCodexThreadInput(input);
|
|
684
|
+
const now = this.now();
|
|
685
|
+
const profile = resolveRequiredInputProfile(input);
|
|
686
|
+
this.upsertCodexThreadStatement.run({
|
|
687
|
+
codexThreadId: input.codexThreadId,
|
|
688
|
+
conversationKey: input.conversationKey,
|
|
689
|
+
name: input.name ?? null,
|
|
690
|
+
larkThreadId: input.larkThreadId ?? null,
|
|
691
|
+
profile,
|
|
692
|
+
model: input.model ?? null,
|
|
693
|
+
effort: input.effort ?? null,
|
|
694
|
+
forkedFromCodexThreadId: input.forkedFromCodexThreadId ?? null,
|
|
695
|
+
forkedAt: input.forkedAt ?? null,
|
|
696
|
+
codexThreadHasRollout: input.codexThreadHasRollout === true ? 1 : 0,
|
|
697
|
+
totalTokens: 0,
|
|
698
|
+
tokenUsageJson: "{}",
|
|
699
|
+
createdAt: now,
|
|
700
|
+
updatedAt: now
|
|
701
|
+
});
|
|
702
|
+
return this.requireCodexThreadById(input.codexThreadId);
|
|
703
|
+
}
|
|
704
|
+
getCodexThreadById(codexThreadId) {
|
|
705
|
+
assertNonEmpty(codexThreadId, "codexThreadId");
|
|
706
|
+
return mapCodexThreadRow(this.selectCodexThreadById.get(codexThreadId));
|
|
707
|
+
}
|
|
708
|
+
getCodexThreadByConversationAndLarkThread(conversationKey, larkThreadId) {
|
|
709
|
+
assertValidConversationKey(conversationKey);
|
|
710
|
+
assertNonEmpty(larkThreadId, "larkThreadId");
|
|
711
|
+
return mapCodexThreadRow(this.selectCodexThreadByConversationAndLarkThread.get(conversationKey, larkThreadId));
|
|
712
|
+
}
|
|
713
|
+
replaceCodexThreadForLarkThread(conversationKey, larkThreadId, update) {
|
|
714
|
+
const input = {
|
|
715
|
+
conversationKey,
|
|
716
|
+
larkThreadId,
|
|
717
|
+
codexThreadId: update.codexThreadId,
|
|
718
|
+
profile: update.profile,
|
|
719
|
+
model: update.model,
|
|
720
|
+
effort: update.effort,
|
|
721
|
+
codexThreadHasRollout: update.codexThreadHasRollout
|
|
722
|
+
};
|
|
723
|
+
validateReplaceCodexThreadForLarkThread(input);
|
|
724
|
+
const profile = resolveRequiredInputProfile(input);
|
|
725
|
+
const now = this.now();
|
|
726
|
+
const codexThreadHasRollout = input.codexThreadHasRollout === true ? 1 : 0;
|
|
727
|
+
const replace = this.db.transaction(() => {
|
|
728
|
+
const result = this.replaceCodexThreadForLarkThreadStatement.run({
|
|
729
|
+
...input,
|
|
730
|
+
profile,
|
|
731
|
+
codexThreadHasRollout,
|
|
732
|
+
updatedAt: now
|
|
733
|
+
});
|
|
734
|
+
if (result.changes === 0) {
|
|
735
|
+
this.upsertCodexThreadStatement.run({
|
|
736
|
+
codexThreadId: input.codexThreadId,
|
|
737
|
+
conversationKey: input.conversationKey,
|
|
738
|
+
name: null,
|
|
739
|
+
larkThreadId: input.larkThreadId,
|
|
740
|
+
profile,
|
|
741
|
+
model: input.model ?? null,
|
|
742
|
+
effort: input.effort ?? null,
|
|
743
|
+
forkedFromCodexThreadId: null,
|
|
744
|
+
forkedAt: null,
|
|
745
|
+
codexThreadHasRollout,
|
|
746
|
+
totalTokens: 0,
|
|
747
|
+
tokenUsageJson: "{}",
|
|
748
|
+
createdAt: now,
|
|
749
|
+
updatedAt: now
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
return this.requireCodexThreadById(input.codexThreadId);
|
|
753
|
+
});
|
|
754
|
+
return replace();
|
|
755
|
+
}
|
|
756
|
+
updateCodexThreadTokenUsage(input) {
|
|
757
|
+
assertNonEmpty(input.codexThreadId, "codexThreadId");
|
|
758
|
+
assertValidConversationKey(input.conversationKey);
|
|
759
|
+
const profile = resolveRequiredInputProfile(input);
|
|
760
|
+
assertValidProfile(profile);
|
|
761
|
+
assertNonNegativeFinite(input.inputTokens, "inputTokens");
|
|
762
|
+
assertNonNegativeFinite(input.outputTokens, "outputTokens");
|
|
763
|
+
assertNonNegativeFinite(input.cachedInputTokens, "cachedInputTokens");
|
|
764
|
+
assertNonNegativeFinite(input.reasoningOutputTokens, "reasoningOutputTokens");
|
|
765
|
+
if (!Number.isFinite(input.totalTokens) || input.totalTokens < 0) {
|
|
766
|
+
throw new TwinnyError("totalTokens must be a non-negative finite number", "CODEX_THREAD_TOKEN_USAGE_INVALID");
|
|
767
|
+
}
|
|
768
|
+
assertNonNegativeFinite(input.contextTokens, "contextTokens");
|
|
769
|
+
assertNonNegativeFinite(input.contextWindow, "contextWindow");
|
|
770
|
+
assertNonEmpty(input.tokenUsageJson, "tokenUsageJson");
|
|
771
|
+
const now = this.now();
|
|
772
|
+
this.updateCodexThreadUsageStatement.run({
|
|
773
|
+
codexThreadId: input.codexThreadId,
|
|
774
|
+
conversationKey: input.conversationKey,
|
|
775
|
+
name: null,
|
|
776
|
+
profile,
|
|
777
|
+
model: null,
|
|
778
|
+
effort: null,
|
|
779
|
+
inputTokens: Math.trunc(input.inputTokens),
|
|
780
|
+
outputTokens: Math.trunc(input.outputTokens),
|
|
781
|
+
cachedInputTokens: Math.trunc(input.cachedInputTokens),
|
|
782
|
+
reasoningOutputTokens: Math.trunc(input.reasoningOutputTokens),
|
|
783
|
+
totalTokens: Math.trunc(input.totalTokens),
|
|
784
|
+
contextTokens: Math.trunc(input.contextTokens),
|
|
785
|
+
contextWindow: Math.trunc(input.contextWindow),
|
|
786
|
+
tokenUsageJson: input.tokenUsageJson,
|
|
787
|
+
createdAt: now,
|
|
788
|
+
updatedAt: now
|
|
789
|
+
});
|
|
790
|
+
return this.requireCodexThreadById(input.codexThreadId);
|
|
791
|
+
}
|
|
792
|
+
updateCodexThreadCard(input) {
|
|
793
|
+
assertNonEmpty(input.codexThreadId, "codexThreadId");
|
|
794
|
+
assertValidConversationKey(input.conversationKey);
|
|
795
|
+
const profile = resolveRequiredInputProfile(input);
|
|
796
|
+
assertValidProfile(profile);
|
|
797
|
+
if (input.model !== undefined) {
|
|
798
|
+
assertNonEmpty(input.model, "model");
|
|
799
|
+
}
|
|
800
|
+
if (input.effort !== undefined) {
|
|
801
|
+
assertNonEmpty(input.effort, "effort");
|
|
802
|
+
}
|
|
803
|
+
if (input.larkThreadId !== undefined) {
|
|
804
|
+
assertNonEmpty(input.larkThreadId, "larkThreadId");
|
|
805
|
+
}
|
|
806
|
+
if (input.name !== undefined) {
|
|
807
|
+
assertNonEmpty(input.name, "name");
|
|
808
|
+
}
|
|
809
|
+
if (input.creatorOpenId !== undefined) {
|
|
810
|
+
assertNonEmpty(input.creatorOpenId, "creatorOpenId");
|
|
811
|
+
}
|
|
812
|
+
if (input.cardMessageId !== undefined) {
|
|
813
|
+
assertNonEmpty(input.cardMessageId, "cardMessageId");
|
|
814
|
+
}
|
|
815
|
+
const now = this.now();
|
|
816
|
+
this.updateCodexThreadCardStatement.run({
|
|
817
|
+
codexThreadId: input.codexThreadId,
|
|
818
|
+
conversationKey: input.conversationKey,
|
|
819
|
+
name: input.name ?? null,
|
|
820
|
+
profile,
|
|
821
|
+
model: input.model ?? null,
|
|
822
|
+
effort: input.effort ?? null,
|
|
823
|
+
larkThreadId: input.larkThreadId ?? null,
|
|
824
|
+
creatorOpenId: input.creatorOpenId ?? null,
|
|
825
|
+
cardMessageId: input.cardMessageId ?? null,
|
|
826
|
+
createdAt: now,
|
|
827
|
+
updatedAt: now
|
|
828
|
+
});
|
|
829
|
+
return this.requireCodexThreadById(input.codexThreadId);
|
|
830
|
+
}
|
|
831
|
+
updateCodexThreadModelSettings(input) {
|
|
832
|
+
assertNonEmpty(input.codexThreadId, "codexThreadId");
|
|
833
|
+
assertNonEmpty(input.model, "model");
|
|
834
|
+
assertNonEmpty(input.effort, "effort");
|
|
835
|
+
const result = this.updateCodexThreadModelSettingsStatement.run(input.model, input.effort, this.now(), input.codexThreadId);
|
|
836
|
+
if (result.changes === 0) {
|
|
837
|
+
throw new TwinnyError(`Codex thread ${input.codexThreadId} was not found`, "CODEX_THREAD_NOT_FOUND");
|
|
838
|
+
}
|
|
839
|
+
return this.requireCodexThreadById(input.codexThreadId);
|
|
840
|
+
}
|
|
841
|
+
updateCodexThreadName(codexThreadId, name) {
|
|
842
|
+
assertNonEmpty(codexThreadId, "codexThreadId");
|
|
843
|
+
assertNonEmpty(name, "name");
|
|
844
|
+
const result = this.updateCodexThreadNameStatement.run(name, this.now(), codexThreadId);
|
|
845
|
+
if (result.changes === 0) {
|
|
846
|
+
return undefined;
|
|
847
|
+
}
|
|
848
|
+
return this.requireCodexThreadById(codexThreadId);
|
|
849
|
+
}
|
|
850
|
+
updateCodexThreadMode(conversationKey, codexThreadId, mode) {
|
|
851
|
+
assertValidConversationKey(conversationKey);
|
|
852
|
+
assertNonEmpty(codexThreadId, "codexThreadId");
|
|
853
|
+
assertValidCodexThreadMode(mode);
|
|
854
|
+
const result = this.updateCodexThreadModeStatement.run(mode, this.now(), conversationKey, codexThreadId);
|
|
855
|
+
if (result.changes === 0) {
|
|
856
|
+
throw new TwinnyError(`Codex thread ${codexThreadId} was not found`, "CODEX_THREAD_NOT_FOUND");
|
|
857
|
+
}
|
|
858
|
+
return this.requireCodexThreadById(codexThreadId);
|
|
859
|
+
}
|
|
860
|
+
updateCodexThreadStatus(conversationKey, codexThreadId, status) {
|
|
861
|
+
assertValidConversationKey(conversationKey);
|
|
862
|
+
assertNonEmpty(codexThreadId, "codexThreadId");
|
|
863
|
+
assertValidCodexThreadStatus(status);
|
|
864
|
+
const result = this.updateCodexThreadStatusStatement.run(status, this.now(), conversationKey, codexThreadId);
|
|
865
|
+
if (result.changes === 0) {
|
|
866
|
+
throw new TwinnyError(`Codex thread ${codexThreadId} was not found`, "CODEX_THREAD_NOT_FOUND");
|
|
867
|
+
}
|
|
868
|
+
return this.requireCodexThreadById(codexThreadId);
|
|
869
|
+
}
|
|
870
|
+
updateCodexThreadGoalStatus(input) {
|
|
871
|
+
assertNonEmpty(input.codexThreadId, "codexThreadId");
|
|
872
|
+
assertValidCodexThreadGoalStatus(input.goalStatus);
|
|
873
|
+
const now = this.now();
|
|
874
|
+
const goalUpdatedAt = input.goalStatus === "none" ? null : Math.trunc(input.goalUpdatedAt ?? now);
|
|
875
|
+
const result = this.updateCodexThreadGoalStatusStatement.run(input.goalStatus, goalUpdatedAt, now, input.codexThreadId);
|
|
876
|
+
if (result.changes === 0) {
|
|
877
|
+
throw new TwinnyError(`Codex thread ${input.codexThreadId} was not found`, "CODEX_THREAD_NOT_FOUND");
|
|
878
|
+
}
|
|
879
|
+
return this.requireCodexThreadById(input.codexThreadId);
|
|
880
|
+
}
|
|
881
|
+
clearCodexThreadGoalStatus(codexThreadId) {
|
|
882
|
+
return this.updateCodexThreadGoalStatus({ codexThreadId, goalStatus: "none" });
|
|
883
|
+
}
|
|
884
|
+
getCodexThreadWorkStats(codexThreadId) {
|
|
885
|
+
assertNonEmpty(codexThreadId, "codexThreadId");
|
|
886
|
+
const row = this.selectCodexThreadWorkStats.get(codexThreadId);
|
|
887
|
+
return {
|
|
888
|
+
turnCount: Math.trunc(row?.turn_count ?? 0),
|
|
889
|
+
totalWorkDurationMs: Math.trunc(row?.total_work_duration_ms ?? 0)
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
getCodexThreadStatusStats(codexThreadId) {
|
|
893
|
+
assertNonEmpty(codexThreadId, "codexThreadId");
|
|
894
|
+
const row = this.selectCodexThreadStatusStats.get({ codexThreadId });
|
|
895
|
+
return {
|
|
896
|
+
userMessageCount: Math.trunc(row?.user_message_count ?? 0),
|
|
897
|
+
turnCount: Math.trunc(row?.turn_count ?? 0),
|
|
898
|
+
totalWorkDurationMs: Math.trunc(row?.total_work_duration_ms ?? 0)
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
getConversationStatusStats(conversationKey) {
|
|
902
|
+
assertValidConversationKey(conversationKey);
|
|
903
|
+
const row = this.selectConversationStatusStats.get({ conversationKey });
|
|
904
|
+
return {
|
|
905
|
+
topicCount: Math.trunc(row?.topic_count ?? 0),
|
|
906
|
+
userMessageCount: Math.trunc(row?.user_message_count ?? 0),
|
|
907
|
+
inputTokens: Math.trunc(row?.input_tokens ?? 0),
|
|
908
|
+
outputTokens: Math.trunc(row?.output_tokens ?? 0),
|
|
909
|
+
cachedInputTokens: Math.trunc(row?.cached_input_tokens ?? 0),
|
|
910
|
+
reasoningOutputTokens: Math.trunc(row?.reasoning_output_tokens ?? 0),
|
|
911
|
+
totalTokens: Math.trunc(row?.total_tokens ?? 0),
|
|
912
|
+
totalWorkDurationMs: Math.trunc(row?.total_work_duration_ms ?? 0)
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
insertLarkMessage(input) {
|
|
916
|
+
validateLarkMessageInput(input);
|
|
917
|
+
const now = this.now();
|
|
918
|
+
this.insertLarkMessageStatement.run({
|
|
919
|
+
larkMessageId: input.larkMessageId ?? null,
|
|
920
|
+
eventId: input.eventId,
|
|
921
|
+
larkUserId: input.larkUserId,
|
|
922
|
+
larkGroupId: input.larkGroupId ?? null,
|
|
923
|
+
larkThreadId: input.larkThreadId ?? null,
|
|
924
|
+
conversationKey: input.conversationKey ?? null,
|
|
925
|
+
codexThreadId: input.codexThreadId ?? null,
|
|
926
|
+
codexTurnId: input.codexTurnId ?? null,
|
|
927
|
+
routeKind: input.routeKind,
|
|
928
|
+
status: input.status,
|
|
929
|
+
text: input.text,
|
|
930
|
+
larkCreateTime: input.larkCreateTime ?? null,
|
|
931
|
+
sideId: input.sideId ?? null,
|
|
932
|
+
agentCardMessageId: input.agentCardMessageId ?? null,
|
|
933
|
+
receivedAt: now,
|
|
934
|
+
updatedAt: now,
|
|
935
|
+
rawEventJson: input.rawEventJson ?? null
|
|
936
|
+
});
|
|
937
|
+
return this.requireLarkMessageByEventId(input.eventId);
|
|
938
|
+
}
|
|
939
|
+
getLarkMessageById(larkMessageId) {
|
|
940
|
+
assertNonEmpty(larkMessageId, "larkMessageId");
|
|
941
|
+
return mapLarkMessageRow(this.selectLarkMessageById.get(larkMessageId));
|
|
942
|
+
}
|
|
943
|
+
getLarkMessageByEventId(eventId) {
|
|
944
|
+
assertNonEmpty(eventId, "eventId");
|
|
945
|
+
return mapLarkMessageRow(this.selectLarkMessageByEventId.get(eventId));
|
|
946
|
+
}
|
|
947
|
+
getLarkMessageUsageTargetForTurn(codexThreadId, codexTurnId) {
|
|
948
|
+
assertNonEmpty(codexThreadId, "codexThreadId");
|
|
949
|
+
assertNonEmpty(codexTurnId, "codexTurnId");
|
|
950
|
+
return mapLarkMessageRow(this.selectLarkMessageUsageTargetForTurn.get(codexThreadId, codexTurnId));
|
|
951
|
+
}
|
|
952
|
+
getLatestSteeredLarkMessageForTurn(codexThreadId, codexTurnId) {
|
|
953
|
+
assertNonEmpty(codexThreadId, "codexThreadId");
|
|
954
|
+
assertNonEmpty(codexTurnId, "codexTurnId");
|
|
955
|
+
return mapLarkMessageRow(this.selectLatestSteeredLarkMessageForTurn.get(codexThreadId, codexTurnId));
|
|
956
|
+
}
|
|
957
|
+
listUnfinishedLarkMessages() {
|
|
958
|
+
return this.selectUnfinishedLarkMessages.all().map((row) => mapRequiredLarkMessageRow(row));
|
|
959
|
+
}
|
|
960
|
+
updateLarkMessageTokenUsage(input) {
|
|
961
|
+
assertNonEmpty(input.larkMessageId, "larkMessageId");
|
|
962
|
+
assertNonNegativeFinite(input.inputTokens, "inputTokens");
|
|
963
|
+
assertNonNegativeFinite(input.outputTokens, "outputTokens");
|
|
964
|
+
assertNonNegativeFinite(input.cachedInputTokens, "cachedInputTokens");
|
|
965
|
+
assertNonNegativeFinite(input.reasoningOutputTokens, "reasoningOutputTokens");
|
|
966
|
+
assertNonEmpty(input.tokenUsageJson, "tokenUsageJson");
|
|
967
|
+
const result = this.updateLarkMessageUsageStatement.run({
|
|
968
|
+
larkMessageId: input.larkMessageId,
|
|
969
|
+
inputTokens: Math.trunc(input.inputTokens),
|
|
970
|
+
outputTokens: Math.trunc(input.outputTokens),
|
|
971
|
+
cachedInputTokens: Math.trunc(input.cachedInputTokens),
|
|
972
|
+
reasoningOutputTokens: Math.trunc(input.reasoningOutputTokens),
|
|
973
|
+
tokenUsageJson: input.tokenUsageJson,
|
|
974
|
+
updatedAt: this.now()
|
|
975
|
+
});
|
|
976
|
+
return result.changes === 0 ? undefined : this.requireLarkMessageById(input.larkMessageId);
|
|
977
|
+
}
|
|
978
|
+
markLarkMessageQueued(larkMessageId) {
|
|
979
|
+
assertNonEmpty(larkMessageId, "larkMessageId");
|
|
980
|
+
this.updateLarkMessageQueuedStatement.run(this.now(), larkMessageId);
|
|
981
|
+
}
|
|
982
|
+
updateQueuedLarkMessage(larkMessageId, update) {
|
|
983
|
+
assertNonEmpty(larkMessageId, "larkMessageId");
|
|
984
|
+
const result = this.updateQueuedLarkMessageStatement.run(update.text, update.rawEventJson ?? null, this.now(), larkMessageId);
|
|
985
|
+
return result.changes > 0;
|
|
986
|
+
}
|
|
987
|
+
updateLarkMessageSideMetadata(larkMessageId, update) {
|
|
988
|
+
assertNonEmpty(larkMessageId, "larkMessageId");
|
|
989
|
+
const result = this.updateLarkMessageSideMetadataStatement.run(update.sideId ?? null, update.agentCardMessageId ?? null, this.now(), larkMessageId);
|
|
990
|
+
return result.changes > 0;
|
|
991
|
+
}
|
|
992
|
+
markLarkMessageRecalled(larkMessageId) {
|
|
993
|
+
assertNonEmpty(larkMessageId, "larkMessageId");
|
|
994
|
+
const result = this.updateLarkMessageRecalledStatement.run(this.now(), larkMessageId);
|
|
995
|
+
return result.changes > 0;
|
|
996
|
+
}
|
|
997
|
+
markLarkMessagesProcessing(larkMessageIds, update = {}) {
|
|
998
|
+
validateLarkMessageIds(larkMessageIds);
|
|
999
|
+
validateLarkMessageBindingUpdate(update);
|
|
1000
|
+
const now = this.now();
|
|
1001
|
+
const mark = this.db.transaction(() => {
|
|
1002
|
+
for (const messageId of larkMessageIds) {
|
|
1003
|
+
this.updateLarkMessageProcessingStatement.run(update.conversationKey ?? null, update.codexThreadId ?? null, update.codexTurnId ?? null, now, now, messageId);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
mark();
|
|
1007
|
+
}
|
|
1008
|
+
markLarkMessagesSteered(larkMessageIds, update = {}) {
|
|
1009
|
+
validateLarkMessageIds(larkMessageIds);
|
|
1010
|
+
validateLarkMessageBindingUpdate(update);
|
|
1011
|
+
const now = this.now();
|
|
1012
|
+
const mark = this.db.transaction(() => {
|
|
1013
|
+
for (const messageId of larkMessageIds) {
|
|
1014
|
+
this.updateLarkMessageSteeredStatement.run(update.conversationKey ?? null, update.codexThreadId ?? null, update.codexTurnId ?? null, now, messageId);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
mark();
|
|
1018
|
+
}
|
|
1019
|
+
markLarkMessagesCompleted(larkMessageIds) {
|
|
1020
|
+
this.markLarkMessagesTerminal(larkMessageIds, this.updateLarkMessageCompletedStatement);
|
|
1021
|
+
}
|
|
1022
|
+
markLarkMessagesFailed(larkMessageIds) {
|
|
1023
|
+
this.markLarkMessagesTerminal(larkMessageIds, this.updateLarkMessageFailedStatement);
|
|
1024
|
+
}
|
|
1025
|
+
markLarkMessagesInterrupted(larkMessageIds) {
|
|
1026
|
+
this.markLarkMessagesTerminal(larkMessageIds, this.updateLarkMessageInterruptedStatement);
|
|
1027
|
+
}
|
|
1028
|
+
markLarkMessagesCleared(larkMessageIds) {
|
|
1029
|
+
this.markLarkMessagesTerminal(larkMessageIds, this.updateLarkMessageClearedStatement);
|
|
1030
|
+
}
|
|
1031
|
+
requireByConversationKey(conversationKey) {
|
|
1032
|
+
const record = this.getByConversationKey(conversationKey);
|
|
1033
|
+
if (!record) {
|
|
1034
|
+
throw new TwinnyError(`Conversation ${conversationKey} was not found`, "CONVERSATION_NOT_FOUND");
|
|
1035
|
+
}
|
|
1036
|
+
return record;
|
|
1037
|
+
}
|
|
1038
|
+
toInsertParams(input) {
|
|
1039
|
+
validateNewConversation(input);
|
|
1040
|
+
const now = this.now();
|
|
1041
|
+
const profile = resolveRequiredInputProfile(input);
|
|
1042
|
+
const profileCodexHome = input.profileCodexHome;
|
|
1043
|
+
return {
|
|
1044
|
+
conversationKey: input.conversationKey,
|
|
1045
|
+
type: input.type,
|
|
1046
|
+
chatId: input.chatId,
|
|
1047
|
+
name: input.name,
|
|
1048
|
+
responseMode: input.responseMode ?? (input.type === "p2p" ? "all" : "none"),
|
|
1049
|
+
profile,
|
|
1050
|
+
codexThreadId: input.codexThreadId,
|
|
1051
|
+
workspace: input.workspace,
|
|
1052
|
+
profileCodexHome: profileCodexHome,
|
|
1053
|
+
createdAt: now,
|
|
1054
|
+
updatedAt: now
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
requireCodexThreadById(codexThreadId) {
|
|
1058
|
+
const record = this.getCodexThreadById(codexThreadId);
|
|
1059
|
+
if (!record) {
|
|
1060
|
+
throw new TwinnyError(`Codex thread ${codexThreadId} was not found`, "CODEX_THREAD_NOT_FOUND");
|
|
1061
|
+
}
|
|
1062
|
+
return record;
|
|
1063
|
+
}
|
|
1064
|
+
requireLarkMessageById(larkMessageId) {
|
|
1065
|
+
const record = this.getLarkMessageById(larkMessageId);
|
|
1066
|
+
if (!record) {
|
|
1067
|
+
throw new TwinnyError(`Lark message ${larkMessageId} was not found`, "LARK_MESSAGE_NOT_FOUND");
|
|
1068
|
+
}
|
|
1069
|
+
return record;
|
|
1070
|
+
}
|
|
1071
|
+
requireLarkMessageByEventId(eventId) {
|
|
1072
|
+
const record = this.getLarkMessageByEventId(eventId);
|
|
1073
|
+
if (!record) {
|
|
1074
|
+
throw new TwinnyError(`Lark event ${eventId} was not found`, "LARK_MESSAGE_NOT_FOUND");
|
|
1075
|
+
}
|
|
1076
|
+
return record;
|
|
1077
|
+
}
|
|
1078
|
+
markLarkMessagesTerminal(larkMessageIds, statement) {
|
|
1079
|
+
for (const messageId of larkMessageIds) {
|
|
1080
|
+
assertNonEmpty(messageId, "larkMessageId");
|
|
1081
|
+
}
|
|
1082
|
+
const now = this.now();
|
|
1083
|
+
const mark = this.db.transaction(() => {
|
|
1084
|
+
for (const messageId of larkMessageIds) {
|
|
1085
|
+
statement.run(now, now, messageId);
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
mark();
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
export function createConversationRepository(db, options = {}) {
|
|
1092
|
+
return new ConversationRepository(db, options);
|
|
1093
|
+
}
|
|
1094
|
+
function mapConversationRow(row) {
|
|
1095
|
+
if (!row) {
|
|
1096
|
+
return undefined;
|
|
1097
|
+
}
|
|
1098
|
+
return mapRequiredConversationRow(row);
|
|
1099
|
+
}
|
|
1100
|
+
function mapRequiredConversationRow(row) {
|
|
1101
|
+
return {
|
|
1102
|
+
id: row.id,
|
|
1103
|
+
conversationKey: row.conversation_key,
|
|
1104
|
+
type: row.type,
|
|
1105
|
+
chatId: row.chat_id,
|
|
1106
|
+
name: row.name,
|
|
1107
|
+
responseMode: row.response_mode,
|
|
1108
|
+
profile: row.profile,
|
|
1109
|
+
codexThreadId: row.thread_id,
|
|
1110
|
+
workspace: row.workspace,
|
|
1111
|
+
profileCodexHome: row.profile_codex_home,
|
|
1112
|
+
createdAt: row.created_at,
|
|
1113
|
+
updatedAt: row.updated_at
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
function mapCodexThreadRow(row) {
|
|
1117
|
+
if (!row) {
|
|
1118
|
+
return undefined;
|
|
1119
|
+
}
|
|
1120
|
+
return {
|
|
1121
|
+
id: row.id,
|
|
1122
|
+
codexThreadId: row.thread_id,
|
|
1123
|
+
conversationKey: row.conversation_key,
|
|
1124
|
+
name: row.name,
|
|
1125
|
+
larkThreadId: row.lark_thread_id ?? undefined,
|
|
1126
|
+
profile: row.profile,
|
|
1127
|
+
model: row.model ?? undefined,
|
|
1128
|
+
effort: row.effort ?? undefined,
|
|
1129
|
+
mode: validCodexThreadMode(row.mode) ? row.mode : "default",
|
|
1130
|
+
status: validCodexThreadStatus(row.status) ? row.status : "idle",
|
|
1131
|
+
goalStatus: validCodexThreadGoalStatus(row.goal_status) ? row.goal_status : "none",
|
|
1132
|
+
goalUpdatedAt: row.goal_updated_at ?? undefined,
|
|
1133
|
+
forkedFromCodexThreadId: row.forked_from_thread_id ?? undefined,
|
|
1134
|
+
forkedAt: row.forked_at ?? undefined,
|
|
1135
|
+
creatorOpenId: row.creator_open_id ?? undefined,
|
|
1136
|
+
cardMessageId: row.card_message_id ?? undefined,
|
|
1137
|
+
codexThreadHasRollout: row.thread_has_rollout === 1,
|
|
1138
|
+
inputTokens: row.input_tokens,
|
|
1139
|
+
outputTokens: row.output_tokens,
|
|
1140
|
+
cachedInputTokens: row.cached_input_tokens,
|
|
1141
|
+
reasoningOutputTokens: row.reasoning_output_tokens,
|
|
1142
|
+
totalTokens: row.total_tokens,
|
|
1143
|
+
contextTokens: row.context_tokens,
|
|
1144
|
+
contextWindow: row.context_window,
|
|
1145
|
+
tokenUsageJson: row.token_usage_json,
|
|
1146
|
+
createdAt: row.created_at,
|
|
1147
|
+
updatedAt: row.updated_at
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
function mapLarkMessageRow(row) {
|
|
1151
|
+
if (!row) {
|
|
1152
|
+
return undefined;
|
|
1153
|
+
}
|
|
1154
|
+
return mapRequiredLarkMessageRow(row);
|
|
1155
|
+
}
|
|
1156
|
+
function mapRequiredLarkMessageRow(row) {
|
|
1157
|
+
return {
|
|
1158
|
+
id: row.id,
|
|
1159
|
+
larkMessageId: row.lark_message_id ?? undefined,
|
|
1160
|
+
eventId: row.event_id,
|
|
1161
|
+
larkUserId: row.lark_user_id,
|
|
1162
|
+
larkGroupId: row.lark_group_id ?? undefined,
|
|
1163
|
+
larkThreadId: row.lark_thread_id ?? undefined,
|
|
1164
|
+
conversationKey: row.conversation_key ?? undefined,
|
|
1165
|
+
codexThreadId: row.thread_id ?? undefined,
|
|
1166
|
+
codexTurnId: row.codex_turn_id ?? undefined,
|
|
1167
|
+
routeKind: row.route_kind,
|
|
1168
|
+
status: row.status,
|
|
1169
|
+
text: row.text,
|
|
1170
|
+
larkCreateTime: row.lark_create_time ?? undefined,
|
|
1171
|
+
receivedAt: row.received_at,
|
|
1172
|
+
updatedAt: row.updated_at,
|
|
1173
|
+
processingStartedAt: row.processing_started_at ?? undefined,
|
|
1174
|
+
completedAt: row.completed_at ?? undefined,
|
|
1175
|
+
failedAt: row.failed_at ?? undefined,
|
|
1176
|
+
clearedAt: row.cleared_at ?? undefined,
|
|
1177
|
+
sideId: row.side_id ?? undefined,
|
|
1178
|
+
agentCardMessageId: row.agent_card_message_id ?? undefined,
|
|
1179
|
+
rawEventJson: row.raw_event_json ?? undefined,
|
|
1180
|
+
inputTokens: row.input_tokens,
|
|
1181
|
+
outputTokens: row.output_tokens,
|
|
1182
|
+
cachedInputTokens: row.cached_input_tokens,
|
|
1183
|
+
reasoningOutputTokens: row.reasoning_output_tokens,
|
|
1184
|
+
tokenUsageJson: row.token_usage_json
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
function validateNewConversation(input) {
|
|
1188
|
+
assertExpectedConversationKey(input.conversationKey, input.type, input.chatId);
|
|
1189
|
+
assertNonEmpty(input.name, "name");
|
|
1190
|
+
if (input.responseMode !== undefined) {
|
|
1191
|
+
assertValidResponseMode(input.responseMode);
|
|
1192
|
+
}
|
|
1193
|
+
assertValidProfile(resolveRequiredInputProfile(input));
|
|
1194
|
+
assertNonEmpty(input.codexThreadId, "codexThreadId");
|
|
1195
|
+
assertAbsolutePath(input.workspace, "workspace");
|
|
1196
|
+
assertNonEmpty(input.profileCodexHome, "profileCodexHome");
|
|
1197
|
+
}
|
|
1198
|
+
function validateCodexThreadInput(input) {
|
|
1199
|
+
assertNonEmpty(input.codexThreadId, "codexThreadId");
|
|
1200
|
+
assertValidConversationKey(input.conversationKey);
|
|
1201
|
+
assertValidProfile(resolveRequiredInputProfile(input));
|
|
1202
|
+
if (input.name !== undefined) {
|
|
1203
|
+
assertNonEmpty(input.name, "name");
|
|
1204
|
+
}
|
|
1205
|
+
if (input.model !== undefined) {
|
|
1206
|
+
assertNonEmpty(input.model, "model");
|
|
1207
|
+
}
|
|
1208
|
+
if (input.effort !== undefined) {
|
|
1209
|
+
assertNonEmpty(input.effort, "effort");
|
|
1210
|
+
}
|
|
1211
|
+
if (input.larkThreadId !== undefined) {
|
|
1212
|
+
assertNonEmpty(input.larkThreadId, "larkThreadId");
|
|
1213
|
+
}
|
|
1214
|
+
if (input.forkedFromCodexThreadId !== undefined) {
|
|
1215
|
+
assertNonEmpty(input.forkedFromCodexThreadId, "forkedFromCodexThreadId");
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
function validateReplaceCodexThreadForLarkThread(input) {
|
|
1219
|
+
assertValidConversationKey(input.conversationKey);
|
|
1220
|
+
assertNonEmpty(input.larkThreadId, "larkThreadId");
|
|
1221
|
+
assertNonEmpty(input.codexThreadId, "codexThreadId");
|
|
1222
|
+
assertValidProfile(resolveRequiredInputProfile(input));
|
|
1223
|
+
if (input.model !== undefined) {
|
|
1224
|
+
assertNonEmpty(input.model, "model");
|
|
1225
|
+
}
|
|
1226
|
+
if (input.effort !== undefined) {
|
|
1227
|
+
assertNonEmpty(input.effort, "effort");
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
function validateLarkMessageInput(input) {
|
|
1231
|
+
if (input.routeKind === "card_action" || input.routeKind === "menu_action") {
|
|
1232
|
+
if (input.larkMessageId !== undefined) {
|
|
1233
|
+
assertNonEmpty(input.larkMessageId, "larkMessageId");
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
else {
|
|
1237
|
+
if (input.larkMessageId === undefined) {
|
|
1238
|
+
throw new TwinnyError("larkMessageId is required", "CONVERSATION_FIELD_EMPTY");
|
|
1239
|
+
}
|
|
1240
|
+
assertNonEmpty(input.larkMessageId, "larkMessageId");
|
|
1241
|
+
}
|
|
1242
|
+
assertNonEmpty(input.eventId, "eventId");
|
|
1243
|
+
assertNonEmpty(input.larkUserId, "larkUserId");
|
|
1244
|
+
assertValidRouteKind(input.routeKind);
|
|
1245
|
+
assertValidMessageStatus(input.status);
|
|
1246
|
+
if (input.conversationKey !== undefined) {
|
|
1247
|
+
assertValidConversationKey(input.conversationKey);
|
|
1248
|
+
}
|
|
1249
|
+
if (input.codexThreadId !== undefined) {
|
|
1250
|
+
assertNonEmpty(input.codexThreadId, "codexThreadId");
|
|
1251
|
+
}
|
|
1252
|
+
if (input.codexTurnId !== undefined) {
|
|
1253
|
+
assertNonEmpty(input.codexTurnId, "codexTurnId");
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
function assertExpectedConversationKey(conversationKey, type, chatId) {
|
|
1257
|
+
assertValidConversationType(type);
|
|
1258
|
+
const expectedKey = conversationKeyForTypeAndChatId(type, chatId);
|
|
1259
|
+
if (conversationKey !== expectedKey) {
|
|
1260
|
+
throw new TwinnyError(`Conversation key for ${type} must be ${expectedKey}, received ${conversationKey}`, "CONVERSATION_KEY_MISMATCH");
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
function conversationKeyForTypeAndChatId(type, chatId) {
|
|
1264
|
+
if (type === "p2p") {
|
|
1265
|
+
return createP2PConversationKey(chatId);
|
|
1266
|
+
}
|
|
1267
|
+
return createGroupConversationKey(chatId);
|
|
1268
|
+
}
|
|
1269
|
+
function assertValidConversationType(type) {
|
|
1270
|
+
if (type !== "p2p" && type !== "group" && type !== "topic_group") {
|
|
1271
|
+
throw new TwinnyError(`Unsupported conversation type: ${type}`, "CONVERSATION_TYPE_INVALID");
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
function assertValidResponseMode(responseMode) {
|
|
1275
|
+
if (responseMode !== "all" &&
|
|
1276
|
+
responseMode !== "all_at" &&
|
|
1277
|
+
responseMode !== "owner" &&
|
|
1278
|
+
responseMode !== "owner_at" &&
|
|
1279
|
+
responseMode !== "none") {
|
|
1280
|
+
throw new TwinnyError(`Unsupported conversation response mode: ${responseMode}`, "CONVERSATION_RESPONSE_MODE_INVALID");
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
function resolveRequiredInputProfile(input) {
|
|
1284
|
+
const profile = resolveOptionalInputProfile(input);
|
|
1285
|
+
if (!profile) {
|
|
1286
|
+
throw new TwinnyError("profile must not be empty", "CONVERSATION_FIELD_EMPTY");
|
|
1287
|
+
}
|
|
1288
|
+
return profile;
|
|
1289
|
+
}
|
|
1290
|
+
function resolveOptionalInputProfile(input) {
|
|
1291
|
+
return input.profile;
|
|
1292
|
+
}
|
|
1293
|
+
function assertValidProfile(profile) {
|
|
1294
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(profile) || profile === "none") {
|
|
1295
|
+
throw new TwinnyError(`Unsupported profile: ${profile}`, "CONVERSATION_PROFILE_INVALID");
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
function assertValidRouteKind(routeKind) {
|
|
1299
|
+
if (routeKind !== "message" &&
|
|
1300
|
+
routeKind !== "steered_message" &&
|
|
1301
|
+
routeKind !== "queued_message" &&
|
|
1302
|
+
routeKind !== "side_message" &&
|
|
1303
|
+
routeKind !== "goal_message" &&
|
|
1304
|
+
routeKind !== "control_message" &&
|
|
1305
|
+
routeKind !== "card_action" &&
|
|
1306
|
+
routeKind !== "menu_action") {
|
|
1307
|
+
throw new TwinnyError(`Unsupported Lark message route kind: ${routeKind}`, "LARK_MESSAGE_ROUTE_KIND_INVALID");
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
function assertValidMessageStatus(status) {
|
|
1311
|
+
if (status !== "queued" &&
|
|
1312
|
+
status !== "recalled" &&
|
|
1313
|
+
status !== "processing" &&
|
|
1314
|
+
status !== "steered" &&
|
|
1315
|
+
status !== "completed" &&
|
|
1316
|
+
status !== "failed" &&
|
|
1317
|
+
status !== "interrupted" &&
|
|
1318
|
+
status !== "cleared") {
|
|
1319
|
+
throw new TwinnyError(`Unsupported Lark message status: ${status}`, "LARK_MESSAGE_STATUS_INVALID");
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
function assertValidCodexThreadStatus(status) {
|
|
1323
|
+
if (!validCodexThreadStatus(status)) {
|
|
1324
|
+
throw new TwinnyError(`Unsupported Codex thread status: ${status}`, "CODEX_THREAD_STATUS_INVALID");
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
function assertValidCodexThreadGoalStatus(status) {
|
|
1328
|
+
if (!validCodexThreadGoalStatus(status)) {
|
|
1329
|
+
throw new TwinnyError(`Unsupported Codex thread goal status: ${status}`, "CODEX_THREAD_GOAL_STATUS_INVALID");
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
function assertValidCodexThreadMode(mode) {
|
|
1333
|
+
if (!validCodexThreadMode(mode)) {
|
|
1334
|
+
throw new TwinnyError(`Unsupported Codex thread mode: ${mode}`, "CODEX_THREAD_MODE_INVALID");
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
function validCodexThreadMode(mode) {
|
|
1338
|
+
return mode === "default" || mode === "plan";
|
|
1339
|
+
}
|
|
1340
|
+
function validCodexThreadStatus(status) {
|
|
1341
|
+
return status === "idle" || status === "working" || status === "waiting";
|
|
1342
|
+
}
|
|
1343
|
+
function validCodexThreadGoalStatus(status) {
|
|
1344
|
+
return (status === "none" ||
|
|
1345
|
+
status === "active" ||
|
|
1346
|
+
status === "paused" ||
|
|
1347
|
+
status === "blocked" ||
|
|
1348
|
+
status === "usageLimited" ||
|
|
1349
|
+
status === "budgetLimited" ||
|
|
1350
|
+
status === "complete");
|
|
1351
|
+
}
|
|
1352
|
+
function validateLarkMessageIds(larkMessageIds) {
|
|
1353
|
+
for (const messageId of larkMessageIds) {
|
|
1354
|
+
assertNonEmpty(messageId, "larkMessageId");
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
function validateLarkMessageBindingUpdate(update) {
|
|
1358
|
+
if (update.conversationKey !== undefined) {
|
|
1359
|
+
assertValidConversationKey(update.conversationKey);
|
|
1360
|
+
}
|
|
1361
|
+
if (update.codexThreadId !== undefined) {
|
|
1362
|
+
assertNonEmpty(update.codexThreadId, "codexThreadId");
|
|
1363
|
+
}
|
|
1364
|
+
if (update.codexTurnId !== undefined) {
|
|
1365
|
+
assertNonEmpty(update.codexTurnId, "codexTurnId");
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
function assertAbsolutePath(value, field) {
|
|
1369
|
+
assertNonEmpty(value, field);
|
|
1370
|
+
if (!path.isAbsolute(value)) {
|
|
1371
|
+
throw new TwinnyError(`${field} must be an absolute path`, "CONVERSATION_PATH_INVALID");
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
function assertNonEmpty(value, field) {
|
|
1375
|
+
if (value.trim() === "") {
|
|
1376
|
+
throw new TwinnyError(`${field} must not be empty`, "CONVERSATION_FIELD_EMPTY");
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
function assertNonNegativeFinite(value, field) {
|
|
1380
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
1381
|
+
throw new TwinnyError(`${field} must be a non-negative finite number`, "CODEX_THREAD_TOKEN_USAGE_INVALID");
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
//# sourceMappingURL=repositories.js.map
|