team-anya 0.2.0
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/README.md +38 -0
- package/apps/server/dist/broker/cc-broker.js +267 -0
- package/apps/server/dist/cli.js +296 -0
- package/apps/server/dist/config.js +78 -0
- package/apps/server/dist/daemon.js +51 -0
- package/apps/server/dist/franky/context-builder.js +161 -0
- package/apps/server/dist/franky/franky-mcp-server.js +110 -0
- package/apps/server/dist/franky/franky-orchestrator.js +629 -0
- package/apps/server/dist/franky/index.js +5 -0
- package/apps/server/dist/franky/topic-router.js +16 -0
- package/apps/server/dist/gateway/chat-sync.js +135 -0
- package/apps/server/dist/gateway/command-router.js +116 -0
- package/apps/server/dist/gateway/commands/cancel.js +32 -0
- package/apps/server/dist/gateway/commands/help.js +16 -0
- package/apps/server/dist/gateway/commands/index.js +26 -0
- package/apps/server/dist/gateway/commands/restart.js +43 -0
- package/apps/server/dist/gateway/commands/status.js +34 -0
- package/apps/server/dist/gateway/commands/tasks.js +33 -0
- package/apps/server/dist/gateway/feishu-sender.js +508 -0
- package/apps/server/dist/gateway/feishu-ws.js +353 -0
- package/apps/server/dist/gateway/health-monitor.js +154 -0
- package/apps/server/dist/gateway/http.js +1064 -0
- package/apps/server/dist/gateway/media-downloader.js +182 -0
- package/apps/server/dist/gateway/message-events.js +10 -0
- package/apps/server/dist/gateway/message-intake.js +72 -0
- package/apps/server/dist/gateway/message-queue.js +118 -0
- package/apps/server/dist/gateway/session-reader.js +142 -0
- package/apps/server/dist/gateway/ws-push.js +115 -0
- package/apps/server/dist/loid/brain.js +121 -0
- package/apps/server/dist/loid/clarifier.js +162 -0
- package/apps/server/dist/loid/context-builder.js +462 -0
- package/apps/server/dist/loid/mcp-server.js +119 -0
- package/apps/server/dist/loid/memory-settler.js +189 -0
- package/apps/server/dist/loid/opportunity-manager.js +148 -0
- package/apps/server/dist/loid/profile-updater.js +179 -0
- package/apps/server/dist/loid/project-registry.js +192 -0
- package/apps/server/dist/loid/reporter.js +148 -0
- package/apps/server/dist/loid/schemas.js +117 -0
- package/apps/server/dist/loid/self-calibrator.js +314 -0
- package/apps/server/dist/loid/session-manager.js +472 -0
- package/apps/server/dist/loid/session.js +276 -0
- package/apps/server/dist/main.js +528 -0
- package/apps/server/dist/tracing/index.js +2 -0
- package/apps/server/dist/tracing/trace-context.js +92 -0
- package/apps/server/dist/types/message.js +2 -0
- package/apps/server/dist/yor/yor-mcp-server.js +107 -0
- package/apps/server/dist/yor/yor-orchestrator.js +248 -0
- package/apps/web/dist/assets/index-BiiEB0qZ.css +1 -0
- package/apps/web/dist/assets/index-Dnb9LGZd.js +798 -0
- package/apps/web/dist/index.html +13 -0
- package/package.json +42 -0
- package/packages/cc-client/dist/claude-code-backend.js +792 -0
- package/packages/cc-client/dist/index.js +2 -0
- package/packages/cc-client/package.json +11 -0
- package/packages/core/dist/constants.js +60 -0
- package/packages/core/dist/errors.js +35 -0
- package/packages/core/dist/index.js +9 -0
- package/packages/core/dist/office-init.js +190 -0
- package/packages/core/dist/repo-cache.js +70 -0
- package/packages/core/dist/scope/checker.js +114 -0
- package/packages/core/dist/scope/defaults.js +55 -0
- package/packages/core/dist/scope/index.js +3 -0
- package/packages/core/dist/state-machine.js +86 -0
- package/packages/core/dist/types/audit.js +12 -0
- package/packages/core/dist/types/backend.js +2 -0
- package/packages/core/dist/types/commitment.js +17 -0
- package/packages/core/dist/types/communication.js +18 -0
- package/packages/core/dist/types/index.js +9 -0
- package/packages/core/dist/types/opportunity.js +27 -0
- package/packages/core/dist/types/org.js +26 -0
- package/packages/core/dist/types/task.js +46 -0
- package/packages/core/dist/types/workspace.js +39 -0
- package/packages/core/dist/workspace-manager.js +314 -0
- package/packages/core/package.json +10 -0
- package/packages/db/dist/client.js +69 -0
- package/packages/db/dist/index.js +756 -0
- package/packages/db/dist/schema/audit-events.js +13 -0
- package/packages/db/dist/schema/cc-sessions.js +14 -0
- package/packages/db/dist/schema/chats.js +35 -0
- package/packages/db/dist/schema/commitments.js +18 -0
- package/packages/db/dist/schema/communication-events.js +14 -0
- package/packages/db/dist/schema/index.js +14 -0
- package/packages/db/dist/schema/message-log.js +20 -0
- package/packages/db/dist/schema/opportunities.js +23 -0
- package/packages/db/dist/schema/org.js +36 -0
- package/packages/db/dist/schema/projects.js +23 -0
- package/packages/db/dist/schema/tasks.js +51 -0
- package/packages/db/dist/schema/topics.js +22 -0
- package/packages/db/dist/schema/trace-spans.js +19 -0
- package/packages/db/dist/schema/workspaces.js +15 -0
- package/packages/db/package.json +12 -0
- package/packages/db/src/migrations/0000_baseline.sql +251 -0
- package/packages/db/src/migrations/0001_workspaces.sql +19 -0
- package/packages/db/src/migrations/0002_workspace_parent.sql +1 -0
- package/packages/db/src/migrations/0003_chat_context.sql +3 -0
- package/packages/db/src/migrations/meta/_journal.json +34 -0
- package/packages/mcp-tools/dist/index.js +41 -0
- package/packages/mcp-tools/dist/layer1/audit-append.js +38 -0
- package/packages/mcp-tools/dist/layer1/audit-query.js +51 -0
- package/packages/mcp-tools/dist/layer1/memory-brief.js +168 -0
- package/packages/mcp-tools/dist/layer1/memory-context.js +124 -0
- package/packages/mcp-tools/dist/layer1/memory-digest.js +126 -0
- package/packages/mcp-tools/dist/layer1/memory-forget.js +108 -0
- package/packages/mcp-tools/dist/layer1/memory-learn.js +63 -0
- package/packages/mcp-tools/dist/layer1/memory-recall.js +287 -0
- package/packages/mcp-tools/dist/layer1/memory-reflect.js +80 -0
- package/packages/mcp-tools/dist/layer1/memory-remember.js +119 -0
- package/packages/mcp-tools/dist/layer1/memory-search.js +263 -0
- package/packages/mcp-tools/dist/layer1/memory-write.js +21 -0
- package/packages/mcp-tools/dist/layer1/org-lookup.js +47 -0
- package/packages/mcp-tools/dist/layer1/project-get.js +28 -0
- package/packages/mcp-tools/dist/layer1/project-list.js +20 -0
- package/packages/mcp-tools/dist/layer1/report-daily.js +68 -0
- package/packages/mcp-tools/dist/layer1/task-get.js +29 -0
- package/packages/mcp-tools/dist/layer1/task-update.js +34 -0
- package/packages/mcp-tools/dist/layer2/franky/topic-checkpoint.js +43 -0
- package/packages/mcp-tools/dist/layer2/franky/topic-escalate.js +19 -0
- package/packages/mcp-tools/dist/layer2/loid/decision-log.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +30 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +12 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +77 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +18 -0
- package/packages/mcp-tools/dist/layer2/loid/project-remove.js +16 -0
- package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +33 -0
- package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +206 -0
- package/packages/mcp-tools/dist/layer2/loid/task-escalate-to-topic.js +170 -0
- package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +45 -0
- package/packages/mcp-tools/dist/layer2/loid/topic-close.js +22 -0
- package/packages/mcp-tools/dist/layer2/loid/topic-create.js +60 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +8 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +28 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-status.js +8 -0
- package/packages/mcp-tools/dist/layer2/yor/task-block.js +11 -0
- package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +35 -0
- package/packages/mcp-tools/dist/layer2/yor/task-progress.js +21 -0
- package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +203 -0
- package/packages/mcp-tools/dist/layer3/adapters/types.js +28 -0
- package/packages/mcp-tools/dist/layer3/channel-receive.js +11 -0
- package/packages/mcp-tools/dist/layer3/channel-send.js +75 -0
- package/packages/mcp-tools/dist/layer3/file-upload.js +44 -0
- package/packages/mcp-tools/dist/registry.js +911 -0
- package/packages/mcp-tools/package.json +13 -0
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
import { eq, desc, gte, like, and, sql, count, isNull, isNotNull } from 'drizzle-orm';
|
|
2
|
+
import { tasks, taskClarifications, commitments, opportunities, auditEvents, orgMembers, orgOwnership, orgEscalationRules, communicationEvents, messageLog, traceSpans, chats, chatMembers, projects, projectRepos, ccSessions, topics, workspaces } from './schema/index.js';
|
|
3
|
+
export { createDB, createTestDB } from './client.js';
|
|
4
|
+
export * from './schema/index.js';
|
|
5
|
+
// ── Task CRUD ──
|
|
6
|
+
export function createTask(db, data) {
|
|
7
|
+
return db.insert(tasks).values(data).returning().get();
|
|
8
|
+
}
|
|
9
|
+
export function getTask(db, taskId) {
|
|
10
|
+
return db.select().from(tasks).where(eq(tasks.task_id, taskId)).get();
|
|
11
|
+
}
|
|
12
|
+
export function updateTask(db, taskId, data) {
|
|
13
|
+
return db.update(tasks)
|
|
14
|
+
.set({ ...data, updated_at: new Date().toISOString() })
|
|
15
|
+
.where(eq(tasks.task_id, taskId))
|
|
16
|
+
.returning()
|
|
17
|
+
.get();
|
|
18
|
+
}
|
|
19
|
+
export function getTasksByStatus(db, status) {
|
|
20
|
+
return db.select().from(tasks).where(eq(tasks.status, status)).orderBy(desc(tasks.created_at)).all();
|
|
21
|
+
}
|
|
22
|
+
export function getTasksByProject(db, projectId) {
|
|
23
|
+
return db.select().from(tasks).where(eq(tasks.project_id, projectId)).orderBy(desc(tasks.created_at)).all();
|
|
24
|
+
}
|
|
25
|
+
export function getAllTasks(db) {
|
|
26
|
+
return db.select().from(tasks).orderBy(desc(tasks.created_at)).all();
|
|
27
|
+
}
|
|
28
|
+
export function getTasksByAssignee(db, assignee) {
|
|
29
|
+
return db.select().from(tasks).where(eq(tasks.assignee, assignee)).orderBy(desc(tasks.created_at)).all();
|
|
30
|
+
}
|
|
31
|
+
export function getTasksByChatId(db, chatId) {
|
|
32
|
+
return db.select().from(tasks).where(eq(tasks.source_chat_id, chatId)).orderBy(desc(tasks.created_at)).all();
|
|
33
|
+
}
|
|
34
|
+
export function getRecentDoneTasksSince(db, sinceISO) {
|
|
35
|
+
return db.select().from(tasks)
|
|
36
|
+
.where(and(eq(tasks.status, 'DONE'), gte(tasks.updated_at, sinceISO)))
|
|
37
|
+
.orderBy(desc(tasks.updated_at))
|
|
38
|
+
.all();
|
|
39
|
+
}
|
|
40
|
+
// ── Clarification CRUD ──
|
|
41
|
+
export function addClarification(db, data) {
|
|
42
|
+
return db.insert(taskClarifications).values(data).returning().get();
|
|
43
|
+
}
|
|
44
|
+
export function getClarifications(db, taskId) {
|
|
45
|
+
return db.select().from(taskClarifications)
|
|
46
|
+
.where(eq(taskClarifications.task_id, taskId)).all();
|
|
47
|
+
}
|
|
48
|
+
export function answerClarification(db, id, answer, answeredBy) {
|
|
49
|
+
return db.update(taskClarifications)
|
|
50
|
+
.set({
|
|
51
|
+
answer,
|
|
52
|
+
answered_by: answeredBy,
|
|
53
|
+
answered_at: new Date().toISOString(),
|
|
54
|
+
})
|
|
55
|
+
.where(eq(taskClarifications.id, id))
|
|
56
|
+
.returning()
|
|
57
|
+
.get();
|
|
58
|
+
}
|
|
59
|
+
// ── Commitment CRUD ──
|
|
60
|
+
export function createCommitment(db, data) {
|
|
61
|
+
return db.insert(commitments).values(data).returning().get();
|
|
62
|
+
}
|
|
63
|
+
export function getCommitment(db, id) {
|
|
64
|
+
return db.select().from(commitments).where(eq(commitments.id, id)).get();
|
|
65
|
+
}
|
|
66
|
+
export function getCommitmentByTask(db, taskId) {
|
|
67
|
+
return db.select().from(commitments).where(eq(commitments.task_id, taskId)).get();
|
|
68
|
+
}
|
|
69
|
+
export function getCommitmentsByTaskId(db, taskId) {
|
|
70
|
+
return db.select().from(commitments).where(eq(commitments.task_id, taskId)).all();
|
|
71
|
+
}
|
|
72
|
+
export function getActiveCommitments(db) {
|
|
73
|
+
return db.select().from(commitments).where(eq(commitments.status, 'active')).all();
|
|
74
|
+
}
|
|
75
|
+
export function getCommitmentsByStatus(db, status) {
|
|
76
|
+
return db.select().from(commitments).where(eq(commitments.status, status)).all();
|
|
77
|
+
}
|
|
78
|
+
export function getAllCommitments(db) {
|
|
79
|
+
return db.select().from(commitments).all();
|
|
80
|
+
}
|
|
81
|
+
export function updateCommitment(db, id, data) {
|
|
82
|
+
return db.update(commitments)
|
|
83
|
+
.set(data)
|
|
84
|
+
.where(eq(commitments.id, id))
|
|
85
|
+
.returning()
|
|
86
|
+
.get();
|
|
87
|
+
}
|
|
88
|
+
// ── Audit Events ──
|
|
89
|
+
export function insertAuditEvent(db, data) {
|
|
90
|
+
return db.insert(auditEvents).values(data).returning().get();
|
|
91
|
+
}
|
|
92
|
+
export function getAuditEventsByTask(db, taskId) {
|
|
93
|
+
return db.select().from(auditEvents).where(eq(auditEvents.task_id, taskId)).all();
|
|
94
|
+
}
|
|
95
|
+
export function getAuditEventsByType(db, eventType) {
|
|
96
|
+
return db.select().from(auditEvents).where(eq(auditEvents.event_type, eventType)).all();
|
|
97
|
+
}
|
|
98
|
+
export function getAuditEventsByDate(db, date) {
|
|
99
|
+
return db.select().from(auditEvents).where(like(auditEvents.created_at, `${date}%`)).all();
|
|
100
|
+
}
|
|
101
|
+
// ── Opportunity ──
|
|
102
|
+
export function createOpportunity(db, data) {
|
|
103
|
+
return db.insert(opportunities).values(data).returning().get();
|
|
104
|
+
}
|
|
105
|
+
export function getOpenOpportunities(db) {
|
|
106
|
+
return db.select().from(opportunities)
|
|
107
|
+
.where(eq(opportunities.status, 'detected'))
|
|
108
|
+
.all();
|
|
109
|
+
}
|
|
110
|
+
export function getOpportunity(db, id) {
|
|
111
|
+
return db.select().from(opportunities).where(eq(opportunities.id, id)).get();
|
|
112
|
+
}
|
|
113
|
+
export function getAllOpportunities(db) {
|
|
114
|
+
return db.select().from(opportunities).all();
|
|
115
|
+
}
|
|
116
|
+
export function getOpportunitiesByStatus(db, status) {
|
|
117
|
+
return db.select().from(opportunities).where(eq(opportunities.status, status)).all();
|
|
118
|
+
}
|
|
119
|
+
export function updateOpportunity(db, id, data) {
|
|
120
|
+
return db.update(opportunities)
|
|
121
|
+
.set(data)
|
|
122
|
+
.where(eq(opportunities.id, id))
|
|
123
|
+
.returning()
|
|
124
|
+
.get();
|
|
125
|
+
}
|
|
126
|
+
// ── Org ──
|
|
127
|
+
export function getOrgMember(db, memberId) {
|
|
128
|
+
return db.select().from(orgMembers).where(eq(orgMembers.member_id, memberId)).get();
|
|
129
|
+
}
|
|
130
|
+
export function upsertOrgMember(db, data) {
|
|
131
|
+
const setFields = {
|
|
132
|
+
name: data.name,
|
|
133
|
+
updated_at: sql `(strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))`,
|
|
134
|
+
};
|
|
135
|
+
if (data.platform !== undefined)
|
|
136
|
+
setFields.platform = data.platform;
|
|
137
|
+
if (data.union_id !== undefined)
|
|
138
|
+
setFields.union_id = data.union_id;
|
|
139
|
+
if (data.user_id !== undefined)
|
|
140
|
+
setFields.user_id = data.user_id;
|
|
141
|
+
if (data.en_name !== undefined)
|
|
142
|
+
setFields.en_name = data.en_name;
|
|
143
|
+
if (data.email !== undefined)
|
|
144
|
+
setFields.email = data.email;
|
|
145
|
+
if (data.employee_no !== undefined)
|
|
146
|
+
setFields.employee_no = data.employee_no;
|
|
147
|
+
if (data.avatar_url !== undefined)
|
|
148
|
+
setFields.avatar_url = data.avatar_url;
|
|
149
|
+
if (data.metadata !== undefined)
|
|
150
|
+
setFields.metadata = data.metadata;
|
|
151
|
+
if (data.last_synced_at !== undefined)
|
|
152
|
+
setFields.last_synced_at = data.last_synced_at;
|
|
153
|
+
return db.insert(orgMembers)
|
|
154
|
+
.values({
|
|
155
|
+
member_id: data.member_id,
|
|
156
|
+
name: data.name,
|
|
157
|
+
platform: data.platform,
|
|
158
|
+
union_id: data.union_id,
|
|
159
|
+
user_id: data.user_id,
|
|
160
|
+
en_name: data.en_name,
|
|
161
|
+
email: data.email,
|
|
162
|
+
employee_no: data.employee_no,
|
|
163
|
+
avatar_url: data.avatar_url,
|
|
164
|
+
metadata: data.metadata,
|
|
165
|
+
last_synced_at: data.last_synced_at,
|
|
166
|
+
})
|
|
167
|
+
.onConflictDoUpdate({
|
|
168
|
+
target: orgMembers.member_id,
|
|
169
|
+
set: setFields,
|
|
170
|
+
})
|
|
171
|
+
.returning()
|
|
172
|
+
.get();
|
|
173
|
+
}
|
|
174
|
+
export function getOwnershipByTarget(db, target) {
|
|
175
|
+
return db.select().from(orgOwnership).where(eq(orgOwnership.target, target)).all();
|
|
176
|
+
}
|
|
177
|
+
export function getEscalationRules(db, scope = 'global') {
|
|
178
|
+
return db.select().from(orgEscalationRules)
|
|
179
|
+
.where(eq(orgEscalationRules.scope, scope))
|
|
180
|
+
.all();
|
|
181
|
+
}
|
|
182
|
+
// ── 统计 ──
|
|
183
|
+
export function getTaskCountByStatus(db) {
|
|
184
|
+
const all = db.select().from(tasks).all();
|
|
185
|
+
const counts = {};
|
|
186
|
+
for (const task of all) {
|
|
187
|
+
counts[task.status] = (counts[task.status] || 0) + 1;
|
|
188
|
+
}
|
|
189
|
+
return counts;
|
|
190
|
+
}
|
|
191
|
+
// ── CommunicationEvent ──
|
|
192
|
+
export function insertCommunicationEvent(db, data) {
|
|
193
|
+
return db.insert(communicationEvents).values(data).returning().get();
|
|
194
|
+
}
|
|
195
|
+
export function getRecentCommunicationEvents(db, limit = 50, since) {
|
|
196
|
+
if (since) {
|
|
197
|
+
return db.select().from(communicationEvents)
|
|
198
|
+
.where(gte(communicationEvents.created_at, since))
|
|
199
|
+
.orderBy(desc(communicationEvents.created_at))
|
|
200
|
+
.limit(limit)
|
|
201
|
+
.all();
|
|
202
|
+
}
|
|
203
|
+
return db.select().from(communicationEvents)
|
|
204
|
+
.orderBy(desc(communicationEvents.created_at))
|
|
205
|
+
.limit(limit)
|
|
206
|
+
.all();
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* 获取今日新建任务的最大序号(用于生成 task_id)
|
|
210
|
+
*/
|
|
211
|
+
export function getTodayMaxSequence(db) {
|
|
212
|
+
const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
213
|
+
const all = db.select().from(tasks).all();
|
|
214
|
+
let max = 0;
|
|
215
|
+
for (const task of all) {
|
|
216
|
+
if (task.task_id.startsWith(`ANYA-${today}-`)) {
|
|
217
|
+
const seq = parseInt(task.task_id.split('-')[2], 10);
|
|
218
|
+
if (seq > max)
|
|
219
|
+
max = seq;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return max;
|
|
223
|
+
}
|
|
224
|
+
// ── MessageLog CRUD ──
|
|
225
|
+
export function insertMessageLog(db, data) {
|
|
226
|
+
return db.insert(messageLog).values(data).returning().get();
|
|
227
|
+
}
|
|
228
|
+
export function getRecentMessages(db, opts = {}) {
|
|
229
|
+
const { limit = 50, offset = 0, direction, since, message_type, sender, receiver, chat_id, chat_type, sort: sortOrder } = opts;
|
|
230
|
+
const conditions = [];
|
|
231
|
+
if (direction)
|
|
232
|
+
conditions.push(eq(messageLog.direction, direction));
|
|
233
|
+
if (since)
|
|
234
|
+
conditions.push(gte(messageLog.created_at, since));
|
|
235
|
+
if (message_type)
|
|
236
|
+
conditions.push(eq(messageLog.message_type, message_type));
|
|
237
|
+
if (sender)
|
|
238
|
+
conditions.push(eq(messageLog.sender, sender));
|
|
239
|
+
if (receiver)
|
|
240
|
+
conditions.push(eq(messageLog.receiver, receiver));
|
|
241
|
+
if (chat_id)
|
|
242
|
+
conditions.push(eq(messageLog.chat_id, chat_id));
|
|
243
|
+
if (chat_type)
|
|
244
|
+
conditions.push(eq(messageLog.chat_type, chat_type));
|
|
245
|
+
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
|
246
|
+
const orderBy = sortOrder === 'asc' ? messageLog.created_at : desc(messageLog.created_at);
|
|
247
|
+
return db
|
|
248
|
+
.select({
|
|
249
|
+
id: messageLog.id,
|
|
250
|
+
direction: messageLog.direction,
|
|
251
|
+
source_type: messageLog.source_type,
|
|
252
|
+
source_ref: messageLog.source_ref,
|
|
253
|
+
sender: messageLog.sender,
|
|
254
|
+
sender_name: sql `(SELECT name FROM org_members WHERE member_id = ${messageLog.sender})`,
|
|
255
|
+
receiver: messageLog.receiver,
|
|
256
|
+
receiver_name: sql `(SELECT name FROM org_members WHERE member_id = ${messageLog.receiver})`,
|
|
257
|
+
chat_id: messageLog.chat_id,
|
|
258
|
+
chat_type: messageLog.chat_type,
|
|
259
|
+
content: messageLog.content,
|
|
260
|
+
message_type: messageLog.message_type,
|
|
261
|
+
intent_level: messageLog.intent_level,
|
|
262
|
+
related_task_id: messageLog.related_task_id,
|
|
263
|
+
metadata: messageLog.metadata,
|
|
264
|
+
trace_id: messageLog.trace_id,
|
|
265
|
+
created_at: messageLog.created_at,
|
|
266
|
+
chat_name: chats.name,
|
|
267
|
+
})
|
|
268
|
+
.from(messageLog)
|
|
269
|
+
.leftJoin(chats, eq(messageLog.chat_id, chats.chat_id))
|
|
270
|
+
.where(whereClause)
|
|
271
|
+
.orderBy(orderBy)
|
|
272
|
+
.limit(limit)
|
|
273
|
+
.offset(offset)
|
|
274
|
+
.all();
|
|
275
|
+
}
|
|
276
|
+
export function getMessageStats(db) {
|
|
277
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
278
|
+
const todayMessages = db.select().from(messageLog)
|
|
279
|
+
.where(gte(messageLog.created_at, today))
|
|
280
|
+
.all();
|
|
281
|
+
let inbound = 0;
|
|
282
|
+
let outbound = 0;
|
|
283
|
+
for (const msg of todayMessages) {
|
|
284
|
+
if (msg.direction === 'inbound')
|
|
285
|
+
inbound++;
|
|
286
|
+
else if (msg.direction === 'outbound')
|
|
287
|
+
outbound++;
|
|
288
|
+
}
|
|
289
|
+
return { today: today, inbound, outbound, total: inbound + outbound };
|
|
290
|
+
}
|
|
291
|
+
// ── Trace Spans CRUD ──
|
|
292
|
+
export function insertTraceSpan(db, data) {
|
|
293
|
+
return db.insert(traceSpans).values(data).returning().get();
|
|
294
|
+
}
|
|
295
|
+
export function updateTraceSpan(db, spanId, data) {
|
|
296
|
+
return db.update(traceSpans)
|
|
297
|
+
.set(data)
|
|
298
|
+
.where(eq(traceSpans.span_id, spanId))
|
|
299
|
+
.returning()
|
|
300
|
+
.get();
|
|
301
|
+
}
|
|
302
|
+
export function getTraceSpans(db, traceId) {
|
|
303
|
+
return db.select().from(traceSpans)
|
|
304
|
+
.where(eq(traceSpans.trace_id, traceId))
|
|
305
|
+
.orderBy(traceSpans.started_at)
|
|
306
|
+
.all();
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* 查询 trace 列表(按根 span 聚合)
|
|
310
|
+
*/
|
|
311
|
+
export function listTraces(db, opts = {}) {
|
|
312
|
+
const limit = opts.limit ?? 50;
|
|
313
|
+
const offset = opts.offset ?? 0;
|
|
314
|
+
// 用子查询按 trace_id 聚合,取每个 trace 的根 span 信息
|
|
315
|
+
const conditions = [];
|
|
316
|
+
if (opts.operation) {
|
|
317
|
+
conditions.push(like(traceSpans.operation, `%${opts.operation}%`));
|
|
318
|
+
}
|
|
319
|
+
if (opts.status) {
|
|
320
|
+
conditions.push(eq(traceSpans.status, opts.status));
|
|
321
|
+
}
|
|
322
|
+
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
|
323
|
+
// 查总数(按 trace_id 去重)
|
|
324
|
+
const countResult = db
|
|
325
|
+
.select({ count: sql `count(distinct ${traceSpans.trace_id})` })
|
|
326
|
+
.from(traceSpans)
|
|
327
|
+
.where(whereClause)
|
|
328
|
+
.get();
|
|
329
|
+
const total = countResult?.count ?? 0;
|
|
330
|
+
// 查列表:每个 trace_id 取聚合信息
|
|
331
|
+
const rows = db.all(sql `
|
|
332
|
+
SELECT
|
|
333
|
+
${traceSpans.trace_id} as trace_id,
|
|
334
|
+
min(${traceSpans.started_at}) as started_at,
|
|
335
|
+
max(${traceSpans.ended_at}) as ended_at,
|
|
336
|
+
count(*) as span_count,
|
|
337
|
+
group_concat(distinct ${traceSpans.operation}) as operations,
|
|
338
|
+
CASE
|
|
339
|
+
WHEN sum(CASE WHEN ${traceSpans.status} = 'error' THEN 1 ELSE 0 END) > 0 THEN 'error'
|
|
340
|
+
WHEN sum(CASE WHEN ${traceSpans.status} = 'in_progress' THEN 1 ELSE 0 END) > 0 THEN 'in_progress'
|
|
341
|
+
ELSE 'success'
|
|
342
|
+
END as status,
|
|
343
|
+
CASE
|
|
344
|
+
WHEN max(${traceSpans.ended_at}) IS NOT NULL
|
|
345
|
+
THEN (julianday(max(${traceSpans.ended_at})) - julianday(min(${traceSpans.started_at}))) * 86400000
|
|
346
|
+
ELSE NULL
|
|
347
|
+
END as total_duration_ms
|
|
348
|
+
FROM ${traceSpans}
|
|
349
|
+
${whereClause ? sql `WHERE ${whereClause}` : sql ``}
|
|
350
|
+
GROUP BY ${traceSpans.trace_id}
|
|
351
|
+
ORDER BY min(${traceSpans.started_at}) DESC
|
|
352
|
+
LIMIT ${limit}
|
|
353
|
+
OFFSET ${offset}
|
|
354
|
+
`);
|
|
355
|
+
return { data: rows, total };
|
|
356
|
+
}
|
|
357
|
+
export function getMessageLogById(db, id) {
|
|
358
|
+
return db.select().from(messageLog).where(eq(messageLog.id, id)).get();
|
|
359
|
+
}
|
|
360
|
+
export function getMessageLogBySourceRef(db, sourceRef) {
|
|
361
|
+
return db.select().from(messageLog).where(eq(messageLog.source_ref, sourceRef)).get();
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* 合并更新 message_log 的 metadata JSON 字段(仅追加/覆盖 key,不删除已有字段)
|
|
365
|
+
*/
|
|
366
|
+
export function patchMessageLogMetadata(db, sourceRef, patch) {
|
|
367
|
+
const row = db.select().from(messageLog).where(eq(messageLog.source_ref, sourceRef)).get();
|
|
368
|
+
if (!row)
|
|
369
|
+
return;
|
|
370
|
+
const existing = row.metadata ? JSON.parse(row.metadata) : {};
|
|
371
|
+
const merged = { ...existing, ...patch };
|
|
372
|
+
db.update(messageLog)
|
|
373
|
+
.set({ metadata: JSON.stringify(merged) })
|
|
374
|
+
.where(eq(messageLog.source_ref, sourceRef))
|
|
375
|
+
.run();
|
|
376
|
+
}
|
|
377
|
+
// ── Chats CRUD ──
|
|
378
|
+
export function upsertChat(db, data) {
|
|
379
|
+
return db.insert(chats)
|
|
380
|
+
.values(data)
|
|
381
|
+
.onConflictDoUpdate({
|
|
382
|
+
target: chats.chat_id,
|
|
383
|
+
set: {
|
|
384
|
+
name: data.name,
|
|
385
|
+
description: data.description,
|
|
386
|
+
avatar: data.avatar,
|
|
387
|
+
owner_id: data.owner_id,
|
|
388
|
+
chat_mode: data.chat_mode,
|
|
389
|
+
chat_type: data.chat_type,
|
|
390
|
+
chat_tag: data.chat_tag,
|
|
391
|
+
chat_status: data.chat_status,
|
|
392
|
+
external: data.external,
|
|
393
|
+
tenant_key: data.tenant_key,
|
|
394
|
+
user_count: data.user_count,
|
|
395
|
+
bot_count: data.bot_count,
|
|
396
|
+
metadata: data.metadata,
|
|
397
|
+
last_synced_at: data.last_synced_at,
|
|
398
|
+
updated_at: sql `(strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))`,
|
|
399
|
+
},
|
|
400
|
+
})
|
|
401
|
+
.returning()
|
|
402
|
+
.get();
|
|
403
|
+
}
|
|
404
|
+
export function getChat(db, chatId) {
|
|
405
|
+
return db.select().from(chats).where(eq(chats.chat_id, chatId)).get();
|
|
406
|
+
}
|
|
407
|
+
export function getChatContext(db, chatId) {
|
|
408
|
+
return db.select({
|
|
409
|
+
default_project_id: chats.default_project_id,
|
|
410
|
+
custom_context: chats.custom_context,
|
|
411
|
+
}).from(chats).where(eq(chats.chat_id, chatId)).get();
|
|
412
|
+
}
|
|
413
|
+
export function updateChatContext(db, chatId, data) {
|
|
414
|
+
return db.update(chats)
|
|
415
|
+
.set({
|
|
416
|
+
...data,
|
|
417
|
+
updated_at: sql `(strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))`,
|
|
418
|
+
})
|
|
419
|
+
.where(eq(chats.chat_id, chatId))
|
|
420
|
+
.returning()
|
|
421
|
+
.get();
|
|
422
|
+
}
|
|
423
|
+
export function getAllChats(db, opts) {
|
|
424
|
+
const conditions = [];
|
|
425
|
+
if (opts?.platform)
|
|
426
|
+
conditions.push(eq(chats.platform, opts.platform));
|
|
427
|
+
if (opts?.chat_type)
|
|
428
|
+
conditions.push(eq(chats.chat_type, opts.chat_type));
|
|
429
|
+
if (opts?.name)
|
|
430
|
+
conditions.push(like(chats.name, `%${opts.name}%`));
|
|
431
|
+
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
|
432
|
+
return db.select().from(chats).where(whereClause).orderBy(desc(chats.updated_at)).all();
|
|
433
|
+
}
|
|
434
|
+
export function getChatStats(db) {
|
|
435
|
+
const all = db.select().from(chats).all();
|
|
436
|
+
const byType = {};
|
|
437
|
+
for (const chat of all) {
|
|
438
|
+
const t = chat.chat_type ?? 'unknown';
|
|
439
|
+
byType[t] = (byType[t] || 0) + 1;
|
|
440
|
+
}
|
|
441
|
+
return { total: all.length, byType };
|
|
442
|
+
}
|
|
443
|
+
// ── Chat Members CRUD ──
|
|
444
|
+
export function upsertChatMember(db, data) {
|
|
445
|
+
// 先查是否存在
|
|
446
|
+
const existing = db.select().from(chatMembers)
|
|
447
|
+
.where(and(eq(chatMembers.chat_id, data.chat_id), eq(chatMembers.member_id, data.member_id)))
|
|
448
|
+
.get();
|
|
449
|
+
if (existing) {
|
|
450
|
+
return db.update(chatMembers)
|
|
451
|
+
.set({ role: data.role ?? existing.role, synced_at: sql `(strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))` })
|
|
452
|
+
.where(eq(chatMembers.id, existing.id))
|
|
453
|
+
.returning()
|
|
454
|
+
.get();
|
|
455
|
+
}
|
|
456
|
+
return db.insert(chatMembers)
|
|
457
|
+
.values({ chat_id: data.chat_id, member_id: data.member_id, role: data.role ?? 'member' })
|
|
458
|
+
.returning()
|
|
459
|
+
.get();
|
|
460
|
+
}
|
|
461
|
+
export function getChatMembers(db, chatId) {
|
|
462
|
+
return db.select({
|
|
463
|
+
id: chatMembers.id,
|
|
464
|
+
chat_id: chatMembers.chat_id,
|
|
465
|
+
member_id: chatMembers.member_id,
|
|
466
|
+
role: chatMembers.role,
|
|
467
|
+
synced_at: chatMembers.synced_at,
|
|
468
|
+
name: orgMembers.name,
|
|
469
|
+
en_name: orgMembers.en_name,
|
|
470
|
+
email: orgMembers.email,
|
|
471
|
+
employee_no: orgMembers.employee_no,
|
|
472
|
+
avatar_url: orgMembers.avatar_url,
|
|
473
|
+
member_role: orgMembers.role,
|
|
474
|
+
team: orgMembers.team,
|
|
475
|
+
})
|
|
476
|
+
.from(chatMembers)
|
|
477
|
+
.innerJoin(orgMembers, eq(chatMembers.member_id, orgMembers.member_id))
|
|
478
|
+
.where(eq(chatMembers.chat_id, chatId))
|
|
479
|
+
.all();
|
|
480
|
+
}
|
|
481
|
+
export function getMemberChats(db, memberId) {
|
|
482
|
+
return db.select({
|
|
483
|
+
chat_id: chats.chat_id,
|
|
484
|
+
name: chats.name,
|
|
485
|
+
chat_mode: chats.chat_mode,
|
|
486
|
+
chat_tag: chats.chat_tag,
|
|
487
|
+
user_count: chats.user_count,
|
|
488
|
+
role: chatMembers.role,
|
|
489
|
+
})
|
|
490
|
+
.from(chatMembers)
|
|
491
|
+
.innerJoin(chats, eq(chatMembers.chat_id, chats.chat_id))
|
|
492
|
+
.where(eq(chatMembers.member_id, memberId))
|
|
493
|
+
.all();
|
|
494
|
+
}
|
|
495
|
+
export function removeChatMembers(db, chatId) {
|
|
496
|
+
return db.delete(chatMembers).where(eq(chatMembers.chat_id, chatId)).run();
|
|
497
|
+
}
|
|
498
|
+
export function getAllOrgMembers(db) {
|
|
499
|
+
return db.select().from(orgMembers).all();
|
|
500
|
+
}
|
|
501
|
+
// ── Projects CRUD ──
|
|
502
|
+
export function upsertProject(db, data) {
|
|
503
|
+
return db.insert(projects)
|
|
504
|
+
.values(data)
|
|
505
|
+
.onConflictDoUpdate({
|
|
506
|
+
target: projects.project_id,
|
|
507
|
+
set: {
|
|
508
|
+
name: data.name,
|
|
509
|
+
description: data.description,
|
|
510
|
+
platform: data.platform,
|
|
511
|
+
claude_md: data.claude_md,
|
|
512
|
+
updated_at: sql `(strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))`,
|
|
513
|
+
},
|
|
514
|
+
})
|
|
515
|
+
.returning()
|
|
516
|
+
.get();
|
|
517
|
+
}
|
|
518
|
+
export function getProject(db, projectId, includeDeleted = false) {
|
|
519
|
+
const conditions = [eq(projects.project_id, projectId)];
|
|
520
|
+
if (!includeDeleted) {
|
|
521
|
+
conditions.push(isNull(projects.deleted_at));
|
|
522
|
+
}
|
|
523
|
+
return db.select().from(projects).where(and(...conditions)).get();
|
|
524
|
+
}
|
|
525
|
+
export function getAllProjects(db, includeDeleted = false) {
|
|
526
|
+
const where = includeDeleted ? undefined : isNull(projects.deleted_at);
|
|
527
|
+
return db.select().from(projects).where(where).orderBy(desc(projects.updated_at)).all();
|
|
528
|
+
}
|
|
529
|
+
export function softDeleteProject(db, projectId) {
|
|
530
|
+
return db.update(projects)
|
|
531
|
+
.set({ deleted_at: new Date().toISOString() })
|
|
532
|
+
.where(eq(projects.project_id, projectId))
|
|
533
|
+
.run();
|
|
534
|
+
}
|
|
535
|
+
// ── Project Repos CRUD ──
|
|
536
|
+
export function upsertProjectRepo(db, data) {
|
|
537
|
+
return db.insert(projectRepos).values(data).returning().get();
|
|
538
|
+
}
|
|
539
|
+
export function getProjectRepos(db, projectId) {
|
|
540
|
+
return db.select().from(projectRepos)
|
|
541
|
+
.where(eq(projectRepos.project_id, projectId))
|
|
542
|
+
.all();
|
|
543
|
+
}
|
|
544
|
+
export function removeProjectRepos(db, projectId) {
|
|
545
|
+
return db.delete(projectRepos).where(eq(projectRepos.project_id, projectId)).run();
|
|
546
|
+
}
|
|
547
|
+
export function syncProjectRepos(db, projectId, repos) {
|
|
548
|
+
removeProjectRepos(db, projectId);
|
|
549
|
+
return repos.map(r => upsertProjectRepo(db, {
|
|
550
|
+
project_id: projectId,
|
|
551
|
+
name: r.name,
|
|
552
|
+
git_url: r.git_url ?? null,
|
|
553
|
+
repo_path: r.repo_path ?? null,
|
|
554
|
+
default_branch: r.default_branch ?? 'main',
|
|
555
|
+
}));
|
|
556
|
+
}
|
|
557
|
+
export function getProjectWithRepos(db, projectId) {
|
|
558
|
+
const project = getProject(db, projectId);
|
|
559
|
+
if (!project)
|
|
560
|
+
return null;
|
|
561
|
+
const repos = getProjectRepos(db, projectId);
|
|
562
|
+
return { ...project, repos };
|
|
563
|
+
}
|
|
564
|
+
// ── CC Sessions CRUD ──
|
|
565
|
+
export function insertCCSession(db, data) {
|
|
566
|
+
return db.insert(ccSessions).values(data).onConflictDoNothing({ target: ccSessions.session_id }).returning().get();
|
|
567
|
+
}
|
|
568
|
+
export function getCCSessionsByTask(db, taskId) {
|
|
569
|
+
return db.select().from(ccSessions)
|
|
570
|
+
.where(eq(ccSessions.task_id, taskId))
|
|
571
|
+
.orderBy(desc(ccSessions.started_at))
|
|
572
|
+
.all();
|
|
573
|
+
}
|
|
574
|
+
export function getCCSessionBySessionId(db, sessionId) {
|
|
575
|
+
return db.select().from(ccSessions)
|
|
576
|
+
.where(eq(ccSessions.session_id, sessionId))
|
|
577
|
+
.get();
|
|
578
|
+
}
|
|
579
|
+
export function updateCCSessionEnded(db, sessionId) {
|
|
580
|
+
return db.update(ccSessions)
|
|
581
|
+
.set({ ended_at: new Date().toISOString() })
|
|
582
|
+
.where(eq(ccSessions.session_id, sessionId))
|
|
583
|
+
.returning()
|
|
584
|
+
.get();
|
|
585
|
+
}
|
|
586
|
+
export function getAllCCSessions(db, filters = {}) {
|
|
587
|
+
const conditions = [];
|
|
588
|
+
if (filters.role)
|
|
589
|
+
conditions.push(eq(ccSessions.role, filters.role));
|
|
590
|
+
if (filters.taskId)
|
|
591
|
+
conditions.push(eq(ccSessions.task_id, filters.taskId));
|
|
592
|
+
if (filters.chatId)
|
|
593
|
+
conditions.push(eq(ccSessions.chat_id, filters.chatId));
|
|
594
|
+
if (filters.active === true)
|
|
595
|
+
conditions.push(isNull(ccSessions.ended_at));
|
|
596
|
+
if (filters.active === false)
|
|
597
|
+
conditions.push(isNotNull(ccSessions.ended_at));
|
|
598
|
+
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
|
599
|
+
const total = db.select({ count: count() }).from(ccSessions)
|
|
600
|
+
.where(where).get()?.count ?? 0;
|
|
601
|
+
const data = db.select().from(ccSessions)
|
|
602
|
+
.where(where)
|
|
603
|
+
.orderBy(desc(ccSessions.started_at))
|
|
604
|
+
.limit(filters.limit ?? 50)
|
|
605
|
+
.offset(filters.offset ?? 0)
|
|
606
|
+
.all();
|
|
607
|
+
return { data, total };
|
|
608
|
+
}
|
|
609
|
+
export function getCCSessionsByChat(db, chatId) {
|
|
610
|
+
return db.select().from(ccSessions)
|
|
611
|
+
.where(eq(ccSessions.chat_id, chatId))
|
|
612
|
+
.orderBy(desc(ccSessions.started_at))
|
|
613
|
+
.all();
|
|
614
|
+
}
|
|
615
|
+
export function getAllProjectsWithStats(db, includeDeleted = false) {
|
|
616
|
+
const where = includeDeleted ? undefined : isNull(projects.deleted_at);
|
|
617
|
+
const allProjects = db.select().from(projects).where(where).orderBy(desc(projects.updated_at)).all();
|
|
618
|
+
const allTasks = db.select({
|
|
619
|
+
project_id: tasks.project_id,
|
|
620
|
+
status: tasks.status,
|
|
621
|
+
updated_at: tasks.updated_at,
|
|
622
|
+
}).from(tasks).all();
|
|
623
|
+
// 每个项目的仓库数量
|
|
624
|
+
const allRepos = db.select({
|
|
625
|
+
project_id: projectRepos.project_id,
|
|
626
|
+
}).from(projectRepos).all();
|
|
627
|
+
const repoCountByProject = {};
|
|
628
|
+
for (const r of allRepos) {
|
|
629
|
+
repoCountByProject[r.project_id] = (repoCountByProject[r.project_id] || 0) + 1;
|
|
630
|
+
}
|
|
631
|
+
const tasksByProject = {};
|
|
632
|
+
for (const t of allTasks) {
|
|
633
|
+
if (!t.project_id)
|
|
634
|
+
continue;
|
|
635
|
+
if (!tasksByProject[t.project_id]) {
|
|
636
|
+
tasksByProject[t.project_id] = { total: 0, done: 0, in_progress: 0, blocked: 0, last_activity: null };
|
|
637
|
+
}
|
|
638
|
+
const stats = tasksByProject[t.project_id];
|
|
639
|
+
stats.total++;
|
|
640
|
+
if (t.status === 'DONE')
|
|
641
|
+
stats.done++;
|
|
642
|
+
else if (t.status === 'IN_PROGRESS')
|
|
643
|
+
stats.in_progress++;
|
|
644
|
+
else if (t.status === 'BLOCKED')
|
|
645
|
+
stats.blocked++;
|
|
646
|
+
if (!stats.last_activity || t.updated_at > stats.last_activity) {
|
|
647
|
+
stats.last_activity = t.updated_at;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return allProjects.map(p => ({
|
|
651
|
+
...p,
|
|
652
|
+
repo_count: repoCountByProject[p.project_id] ?? 0,
|
|
653
|
+
task_count: tasksByProject[p.project_id]?.total ?? 0,
|
|
654
|
+
done_count: tasksByProject[p.project_id]?.done ?? 0,
|
|
655
|
+
in_progress_count: tasksByProject[p.project_id]?.in_progress ?? 0,
|
|
656
|
+
blocked_count: tasksByProject[p.project_id]?.blocked ?? 0,
|
|
657
|
+
last_activity_at: tasksByProject[p.project_id]?.last_activity ?? p.updated_at,
|
|
658
|
+
}));
|
|
659
|
+
}
|
|
660
|
+
export function upsertTopic(db, input) {
|
|
661
|
+
const now = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
662
|
+
return db.insert(topics).values({
|
|
663
|
+
id: input.id,
|
|
664
|
+
project_id: input.project_id ?? null,
|
|
665
|
+
title: input.title,
|
|
666
|
+
description: input.description ?? null,
|
|
667
|
+
status: input.status,
|
|
668
|
+
thread_id: input.thread_id ?? null,
|
|
669
|
+
root_message_id: input.root_message_id ?? null,
|
|
670
|
+
chat_id: input.chat_id ?? null,
|
|
671
|
+
workspace_path: input.workspace_path ?? null,
|
|
672
|
+
workspace_id: input.workspace_id ?? null,
|
|
673
|
+
escalated_from_task_id: input.escalated_from_task_id ?? null,
|
|
674
|
+
branch_name: input.branch_name ?? null,
|
|
675
|
+
created_by: input.created_by ?? null,
|
|
676
|
+
created_at: now,
|
|
677
|
+
updated_at: now,
|
|
678
|
+
}).onConflictDoUpdate({
|
|
679
|
+
target: topics.id,
|
|
680
|
+
set: {
|
|
681
|
+
title: input.title,
|
|
682
|
+
description: input.description ?? null,
|
|
683
|
+
status: input.status,
|
|
684
|
+
thread_id: input.thread_id ?? null,
|
|
685
|
+
root_message_id: input.root_message_id ?? null,
|
|
686
|
+
chat_id: input.chat_id ?? null,
|
|
687
|
+
workspace_path: input.workspace_path ?? null,
|
|
688
|
+
workspace_id: input.workspace_id ?? null,
|
|
689
|
+
escalated_from_task_id: input.escalated_from_task_id ?? null,
|
|
690
|
+
branch_name: input.branch_name ?? null,
|
|
691
|
+
updated_at: now,
|
|
692
|
+
},
|
|
693
|
+
}).run();
|
|
694
|
+
}
|
|
695
|
+
export function getTopic(db, id) {
|
|
696
|
+
return db.select().from(topics).where(eq(topics.id, id)).get();
|
|
697
|
+
}
|
|
698
|
+
export function getTopicByThreadId(db, threadId) {
|
|
699
|
+
return db.select().from(topics)
|
|
700
|
+
.where(and(eq(topics.thread_id, threadId), eq(topics.status, 'active')))
|
|
701
|
+
.get();
|
|
702
|
+
}
|
|
703
|
+
export function updateTopicStatus(db, id, status) {
|
|
704
|
+
const now = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
705
|
+
return db.update(topics).set({
|
|
706
|
+
status,
|
|
707
|
+
updated_at: now,
|
|
708
|
+
...(status === 'closed' ? { closed_at: now } : {}),
|
|
709
|
+
}).where(eq(topics.id, id)).run();
|
|
710
|
+
}
|
|
711
|
+
export function getActiveTopics(db) {
|
|
712
|
+
return db.select().from(topics).where(eq(topics.status, 'active')).all();
|
|
713
|
+
}
|
|
714
|
+
export function getAllTopics(db) {
|
|
715
|
+
return db.select().from(topics).orderBy(desc(topics.created_at)).all();
|
|
716
|
+
}
|
|
717
|
+
export function getTopicsByStatus(db, status) {
|
|
718
|
+
return db.select().from(topics).where(eq(topics.status, status)).orderBy(desc(topics.created_at)).all();
|
|
719
|
+
}
|
|
720
|
+
export function getTopicsByProject(db, projectId) {
|
|
721
|
+
return db.select().from(topics).where(eq(topics.project_id, projectId)).orderBy(desc(topics.created_at)).all();
|
|
722
|
+
}
|
|
723
|
+
export function getTodayMaxTopicSequence(db) {
|
|
724
|
+
const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
725
|
+
const prefix = `TOPIC-${today}-`;
|
|
726
|
+
const rows = db.select().from(topics).where(like(topics.id, `${prefix}%`)).all();
|
|
727
|
+
let max = 0;
|
|
728
|
+
for (const topic of rows) {
|
|
729
|
+
const seq = parseInt(topic.id.split('-')[2], 10);
|
|
730
|
+
if (seq > max)
|
|
731
|
+
max = seq;
|
|
732
|
+
}
|
|
733
|
+
return max;
|
|
734
|
+
}
|
|
735
|
+
export function generateTopicId(db) {
|
|
736
|
+
const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
737
|
+
const max = getTodayMaxTopicSequence(db);
|
|
738
|
+
return `TOPIC-${today}-${String(max + 1).padStart(3, '0')}`;
|
|
739
|
+
}
|
|
740
|
+
// ── Workspaces ──
|
|
741
|
+
export function createWorkspaceRecord(db, data) {
|
|
742
|
+
return db.insert(workspaces).values(data).returning().get();
|
|
743
|
+
}
|
|
744
|
+
export function getWorkspaceRecord(db, id) {
|
|
745
|
+
return db.select().from(workspaces).where(eq(workspaces.id, id)).get();
|
|
746
|
+
}
|
|
747
|
+
export function updateWorkspaceRecord(db, id, data) {
|
|
748
|
+
return db.update(workspaces).set({ ...data, updated_at: new Date().toISOString() }).where(eq(workspaces.id, id)).returning().get();
|
|
749
|
+
}
|
|
750
|
+
export function getWorkspaceByOwner(db, ownerId) {
|
|
751
|
+
return db.select().from(workspaces).where(eq(workspaces.current_owner_id, ownerId)).get();
|
|
752
|
+
}
|
|
753
|
+
export function getActiveWorkspaces(db) {
|
|
754
|
+
return db.select().from(workspaces).where(eq(workspaces.status, 'active')).all();
|
|
755
|
+
}
|
|
756
|
+
//# sourceMappingURL=index.js.map
|