@bbigbang/core 0.1.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/dist/config.js +380 -0
- package/dist/execution/executionDispatcher.js +3810 -0
- package/dist/main.js +90 -0
- package/dist/nodeEventHistory.js +206 -0
- package/dist/scheduler/dreamLogic.js +50 -0
- package/dist/scheduler/dreamScheduler.js +65 -0
- package/dist/services/agentFileAccessService.js +1913 -0
- package/dist/services/agentRuntimeCleanupBroker.js +62 -0
- package/dist/services/agentSkillsBroker.js +118 -0
- package/dist/services/agentSkillsService.js +83 -0
- package/dist/services/agentWorkspaceBroker.js +937 -0
- package/dist/services/agentWorkspaceService.js +70 -0
- package/dist/services/appVersion.js +14 -0
- package/dist/services/auth.js +586 -0
- package/dist/services/claudeControlBroker.js +154 -0
- package/dist/services/claudeTranscriptBroker.js +100 -0
- package/dist/services/claudeTranscriptService.js +359 -0
- package/dist/services/codexAppServerBroker.js +155 -0
- package/dist/services/codexTranscriptBroker.js +98 -0
- package/dist/services/codexTranscriptService.js +961 -0
- package/dist/services/droidMissionBroker.js +124 -0
- package/dist/services/droidMissionImporter.js +630 -0
- package/dist/services/droidModelOptions.js +165 -0
- package/dist/services/hubServerRegistrationService.js +268 -0
- package/dist/services/libraryManifest.js +43 -0
- package/dist/services/libraryScaffold.js +26 -0
- package/dist/services/libraryService.js +2263 -0
- package/dist/services/memoryService.js +386 -0
- package/dist/services/missionEvidence.js +377 -0
- package/dist/services/missionService.js +2361 -0
- package/dist/services/missionTrace.js +158 -0
- package/dist/services/nativeMissionBriefParser.js +120 -0
- package/dist/services/nativeMissionOrchestrator.js +2045 -0
- package/dist/services/nativeMissionReportGenerator.js +227 -0
- package/dist/services/nativeMissionValidationRunner.js +452 -0
- package/dist/services/nativeMissionWorkerBroker.js +190 -0
- package/dist/services/nodeRegistry.js +34 -0
- package/dist/services/nodeStateReconciler.js +97 -0
- package/dist/services/panelMediaScanner.js +119 -0
- package/dist/services/persistentRuntimeJsonlClient.js +153 -0
- package/dist/services/platformAgentPolicy.js +180 -0
- package/dist/services/platformAgentService.js +2041 -0
- package/dist/services/projectAccessResolver.js +93 -0
- package/dist/services/projectService.js +392 -0
- package/dist/services/resourceSpaceService.js +140 -0
- package/dist/services/scenarioRuntimeService.js +1130 -0
- package/dist/services/suggestedPlannerService.js +868 -0
- package/dist/services/workbenchGitBroker.js +161 -0
- package/dist/services/workbenchGitService.js +69 -0
- package/dist/services/workbenchInspectBroker.js +65 -0
- package/dist/services/workbenchNodePathService.js +79 -0
- package/dist/services/workbenchRegistryService.js +240 -0
- package/dist/services/workbenchRootService.js +181 -0
- package/dist/services/workbenchTerminalBroker.js +378 -0
- package/dist/services/workspaceRunOwnership.js +60 -0
- package/dist/services/workspaceScaffold.js +105 -0
- package/dist/services/workspaceSessionRuntimeService.js +576 -0
- package/dist/services/workspaceSessionService.js +245 -0
- package/dist/services/workspaceToolActionRunner.js +1582 -0
- package/dist/services/workspaceToolErrors.js +10 -0
- package/dist/services/workspaceToolExecutionUtils.js +895 -0
- package/dist/services/workspaceToolLatestStateProjector.js +91 -0
- package/dist/services/workspaceToolManifest.js +572 -0
- package/dist/services/workspaceToolMutationQueue.js +43 -0
- package/dist/services/workspaceToolPanelProjection.js +460 -0
- package/dist/services/workspaceToolPromotion.js +255 -0
- package/dist/services/workspaceToolPromotionState.js +224 -0
- package/dist/services/workspaceToolPublishDiagnostics.js +189 -0
- package/dist/services/workspaceToolPublishIdentityResolver.js +146 -0
- package/dist/services/workspaceToolReadModel.js +378 -0
- package/dist/services/workspaceToolRunLedger.js +239 -0
- package/dist/services/workspaceToolService.js +3067 -0
- package/dist/services/workspaceToolSnapshotPanelSync.js +293 -0
- package/dist/services/workspaceToolTerminalLifecycle.js +283 -0
- package/dist/services/workspaceToolTypes.js +1 -0
- package/dist/services/workspaceToolUploadMaterializer.js +228 -0
- package/dist/web/actionCardRoutes.js +129 -0
- package/dist/web/actionCards.js +469 -0
- package/dist/web/activationContext.js +684 -0
- package/dist/web/agentChannelGuards.js +48 -0
- package/dist/web/agentMentionCooldowns.js +32 -0
- package/dist/web/agentReminders.js +1668 -0
- package/dist/web/agentRuntimePresence.js +197 -0
- package/dist/web/agentSelfState.js +494 -0
- package/dist/web/agentTaskLinks.js +26 -0
- package/dist/web/agentVisibility.js +79 -0
- package/dist/web/assets.js +95 -0
- package/dist/web/channelActivationPrompt.js +395 -0
- package/dist/web/channelMemoryNotes.js +127 -0
- package/dist/web/channelMentions.js +10 -0
- package/dist/web/channelMessageSequences.js +19 -0
- package/dist/web/channelSubscriptions.js +26 -0
- package/dist/web/clearedTaskRoots.js +10 -0
- package/dist/web/collaborationPromptGuidance.js +36 -0
- package/dist/web/collaborationSurfaceState.js +140 -0
- package/dist/web/contextBundleRanking.js +154 -0
- package/dist/web/contextBundleResolver.js +488 -0
- package/dist/web/conversationBuiltinSkillRoots.js +50 -0
- package/dist/web/conversationControls.js +232 -0
- package/dist/web/conversationHandoffs.js +612 -0
- package/dist/web/conversationManager.js +2511 -0
- package/dist/web/conversationSummaries.js +876 -0
- package/dist/web/conversationSurfaceKinds.js +17 -0
- package/dist/web/conversationTargets.js +173 -0
- package/dist/web/directActivationPrompt.js +122 -0
- package/dist/web/directReplyTargets.js +69 -0
- package/dist/web/directThreadResolver.js +129 -0
- package/dist/web/dmTaskHandoffPrompt.js +120 -0
- package/dist/web/dmTaskThreadStatusProjection.js +229 -0
- package/dist/web/ftsQuery.js +33 -0
- package/dist/web/internalAgentRouter.js +11341 -0
- package/dist/web/libraryCuratorScheduler.js +58 -0
- package/dist/web/libraryDocumentPromptGuidance.js +8 -0
- package/dist/web/messageCheckpoints.js +19 -0
- package/dist/web/nodeWsHandler.js +2495 -0
- package/dist/web/notificationRounds.js +1061 -0
- package/dist/web/panelActionMessages.js +108 -0
- package/dist/web/panelActivationPrompt.js +18 -0
- package/dist/web/panelAudit.js +273 -0
- package/dist/web/panelLifecycle.js +222 -0
- package/dist/web/panelMediaPolicy.js +43 -0
- package/dist/web/panelPathPolicy.js +63 -0
- package/dist/web/panelPreviews.js +175 -0
- package/dist/web/panelQueryHandles.js +2749 -0
- package/dist/web/panelRoutes.js +2147 -0
- package/dist/web/panels.js +904 -0
- package/dist/web/peerInboxAggregates.js +1247 -0
- package/dist/web/planApprovalState.js +92 -0
- package/dist/web/platformAgentScheduler.js +66 -0
- package/dist/web/proactiveOpportunities.js +452 -0
- package/dist/web/promptContextSections.js +242 -0
- package/dist/web/promptHistorySanitizer.js +26 -0
- package/dist/web/promptSlashCommands.js +158 -0
- package/dist/web/rollingConversationSummary.js +453 -0
- package/dist/web/routeHelpers.js +11 -0
- package/dist/web/routes/handoff.js +288 -0
- package/dist/web/routes/history.js +345 -0
- package/dist/web/routes/memory.js +258 -0
- package/dist/web/routes/selfState.js +171 -0
- package/dist/web/routes/workspace.js +154 -0
- package/dist/web/runSurfaceWatermarks.js +431 -0
- package/dist/web/runtimeCapabilities.js +48 -0
- package/dist/web/sameAgentHandoffs.js +494 -0
- package/dist/web/server.js +15567 -0
- package/dist/web/sharedCollaborationCapsules.js +163 -0
- package/dist/web/soloSessionRelay.js +42 -0
- package/dist/web/soloWsHandler.js +138 -0
- package/dist/web/suggestedPlannerScheduler.js +56 -0
- package/dist/web/surfaceActivationPolicy.js +108 -0
- package/dist/web/surfaceCollaborators.js +61 -0
- package/dist/web/surfaceSystemStatus.js +263 -0
- package/dist/web/targetParticipants.js +77 -0
- package/dist/web/taskEvents.js +49 -0
- package/dist/web/taskLifecycleMessages.js +165 -0
- package/dist/web/taskLoops.js +732 -0
- package/dist/web/taskMemoryNotes.js +224 -0
- package/dist/web/taskNumbers.js +16 -0
- package/dist/web/taskOwnerGuards.js +49 -0
- package/dist/web/taskParticipantResolver.js +42 -0
- package/dist/web/taskParticipants.js +97 -0
- package/dist/web/taskSourceDetails.js +20 -0
- package/dist/web/taskStateViews.js +210 -0
- package/dist/web/taskStatusTransitions.js +9 -0
- package/dist/web/taskThreadFollowups.js +599 -0
- package/dist/web/taskThreadRuntimeClosure.js +685 -0
- package/dist/web/taskUpdateDelivery.js +104 -0
- package/dist/web/threadReplyContentHeuristics.js +30 -0
- package/dist/web/threadRoots.js +61 -0
- package/dist/web/threadTaskBindings.js +365 -0
- package/dist/web/uiPanelPromptGuidance.js +27 -0
- package/dist/web/workspaceMemoryHints.js +143 -0
- package/dist/web/workspaceToolPromptGuidance.js +30 -0
- package/dist/web/wsHandler.js +397 -0
- package/dist/web/wsSink.js +116 -0
- package/package.json +54 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { buildThreadShortId, } from '@bbigbang/protocol';
|
|
2
|
+
import { findConversationIdForReplyTarget } from './conversationTargets.js';
|
|
3
|
+
import { findThreadRootMessageId } from './threadRoots.js';
|
|
4
|
+
export function projectVisibleDmTaskThreadStatus(status) {
|
|
5
|
+
return status === 'awaiting_approval' ? status : null;
|
|
6
|
+
}
|
|
7
|
+
function fetchTaskRootMessageRow(db, channelId, rootMessageId) {
|
|
8
|
+
const row = db.prepare(`SELECT cm.message_id as messageId,
|
|
9
|
+
cm.sender_id as senderId,
|
|
10
|
+
cm.sender_name as senderName,
|
|
11
|
+
cm.sender_type as senderType,
|
|
12
|
+
cm.content as content,
|
|
13
|
+
cm.seq as seq,
|
|
14
|
+
cm.created_at as createdAt,
|
|
15
|
+
cm.attachment_ids as attachmentIds,
|
|
16
|
+
t.task_number as taskNumber,
|
|
17
|
+
t.agent_task_ref as taskRef,
|
|
18
|
+
t.description as taskDescription,
|
|
19
|
+
t.status as taskStatus,
|
|
20
|
+
t.claimed_by_name as taskAssigneeName,
|
|
21
|
+
deleted_assignee.deleted_at as taskAssigneeDeleted
|
|
22
|
+
FROM channel_messages cm
|
|
23
|
+
LEFT JOIN tasks t ON t.message_id = cm.message_id
|
|
24
|
+
LEFT JOIN agents deleted_assignee
|
|
25
|
+
ON deleted_assignee.agent_id = t.claimed_by_agent_id
|
|
26
|
+
AND deleted_assignee.deleted_at IS NOT NULL
|
|
27
|
+
WHERE cm.channel_id = ?
|
|
28
|
+
AND cm.message_id = ?
|
|
29
|
+
LIMIT 1`).get(channelId, rootMessageId);
|
|
30
|
+
return row ?? null;
|
|
31
|
+
}
|
|
32
|
+
function findPrimaryDmConversationId(db, params) {
|
|
33
|
+
const row = params.userId != null
|
|
34
|
+
? db.prepare(`SELECT id
|
|
35
|
+
FROM conversations
|
|
36
|
+
WHERE agent_id = ?
|
|
37
|
+
AND thread_kind = 'direct'
|
|
38
|
+
AND is_primary_thread = 1
|
|
39
|
+
AND user_id = ?
|
|
40
|
+
ORDER BY updated_at DESC
|
|
41
|
+
LIMIT 1`).get(params.agentId, params.userId)
|
|
42
|
+
: db.prepare(`SELECT id
|
|
43
|
+
FROM conversations
|
|
44
|
+
WHERE agent_id = ?
|
|
45
|
+
AND thread_kind = 'direct'
|
|
46
|
+
AND is_primary_thread = 1
|
|
47
|
+
AND user_id IS NULL
|
|
48
|
+
ORDER BY updated_at DESC
|
|
49
|
+
LIMIT 1`).get(params.agentId);
|
|
50
|
+
if (row?.id)
|
|
51
|
+
return row.id;
|
|
52
|
+
if (params.primaryTarget) {
|
|
53
|
+
const byTarget = findConversationIdForReplyTarget(db, params.agentId, params.primaryTarget);
|
|
54
|
+
if (byTarget)
|
|
55
|
+
return byTarget;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
export function getDmTaskThreadStatusForRootMessage(db, params) {
|
|
60
|
+
const row = db.prepare(`SELECT status
|
|
61
|
+
FROM conversations
|
|
62
|
+
WHERE agent_id = ?
|
|
63
|
+
AND thread_kind = 'direct'
|
|
64
|
+
AND is_primary_thread = 0
|
|
65
|
+
AND reply_target = ?
|
|
66
|
+
AND thread_root_id = ?
|
|
67
|
+
ORDER BY updated_at DESC
|
|
68
|
+
LIMIT 1`).get(params.agentId, `${params.primaryTarget}:${buildThreadShortId(params.rootMessageId)}`, buildThreadShortId(params.rootMessageId));
|
|
69
|
+
return projectVisibleDmTaskThreadStatus(row?.status ?? null);
|
|
70
|
+
}
|
|
71
|
+
export function getDmTaskThreadStatusMapForRootMessages(db, params) {
|
|
72
|
+
const rootMessageIds = [...new Set(params.rootMessageIds.map((value) => value.trim()).filter(Boolean))];
|
|
73
|
+
const statuses = new Map();
|
|
74
|
+
if (rootMessageIds.length === 0)
|
|
75
|
+
return statuses;
|
|
76
|
+
for (const rootMessageId of rootMessageIds) {
|
|
77
|
+
statuses.set(rootMessageId, null);
|
|
78
|
+
}
|
|
79
|
+
const threadRootIdToRootMessageId = new Map(rootMessageIds.map((rootMessageId) => [
|
|
80
|
+
buildThreadShortId(rootMessageId),
|
|
81
|
+
rootMessageId,
|
|
82
|
+
]));
|
|
83
|
+
const replyTargets = rootMessageIds.map((rootMessageId) => `${params.primaryTarget}:${buildThreadShortId(rootMessageId)}`);
|
|
84
|
+
const placeholders = replyTargets.map(() => '?').join(', ');
|
|
85
|
+
const rows = db.prepare(`SELECT reply_target as replyTarget,
|
|
86
|
+
thread_root_id as threadRootId,
|
|
87
|
+
status
|
|
88
|
+
FROM conversations
|
|
89
|
+
WHERE agent_id = ?
|
|
90
|
+
AND thread_kind = 'direct'
|
|
91
|
+
AND is_primary_thread = 0
|
|
92
|
+
AND reply_target IN (${placeholders})
|
|
93
|
+
ORDER BY updated_at DESC`).all(params.agentId, ...replyTargets);
|
|
94
|
+
const seenReplyTargets = new Set();
|
|
95
|
+
for (const row of rows) {
|
|
96
|
+
if (seenReplyTargets.has(row.replyTarget))
|
|
97
|
+
continue;
|
|
98
|
+
seenReplyTargets.add(row.replyTarget);
|
|
99
|
+
const rootMessageId = threadRootIdToRootMessageId.get(row.threadRootId);
|
|
100
|
+
if (!rootMessageId)
|
|
101
|
+
continue;
|
|
102
|
+
statuses.set(rootMessageId, projectVisibleDmTaskThreadStatus(row.status));
|
|
103
|
+
}
|
|
104
|
+
return statuses;
|
|
105
|
+
}
|
|
106
|
+
function buildPrimaryDmTaskRootEvent(row, taskThreadStatus, options) {
|
|
107
|
+
if (row.taskNumber == null)
|
|
108
|
+
return null;
|
|
109
|
+
return {
|
|
110
|
+
type: 'channel.message',
|
|
111
|
+
message: {
|
|
112
|
+
id: row.messageId,
|
|
113
|
+
senderId: row.senderId,
|
|
114
|
+
senderName: row.senderName,
|
|
115
|
+
senderType: row.senderType,
|
|
116
|
+
content: row.content,
|
|
117
|
+
createdAt: new Date(row.createdAt).toISOString(),
|
|
118
|
+
seq: row.seq,
|
|
119
|
+
taskNumber: row.taskNumber,
|
|
120
|
+
taskRef: row.taskRef ?? null,
|
|
121
|
+
taskDescription: row.taskDescription ?? null,
|
|
122
|
+
taskStatus: row.taskStatus ?? undefined,
|
|
123
|
+
taskAssigneeName: row.taskAssigneeName ?? null,
|
|
124
|
+
taskAssigneeDeleted: row.taskAssigneeDeleted != null,
|
|
125
|
+
taskThreadStatus,
|
|
126
|
+
...(options?.threadActive ? { threadActive: true } : {}),
|
|
127
|
+
...(row.attachmentIds ? { attachmentIds: JSON.parse(row.attachmentIds) } : {}),
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function hasPrimaryDmThreadConversation(db, params) {
|
|
132
|
+
const row = db.prepare(`SELECT 1
|
|
133
|
+
FROM conversations
|
|
134
|
+
WHERE agent_id = ?
|
|
135
|
+
AND thread_kind = 'direct'
|
|
136
|
+
AND is_primary_thread = 0
|
|
137
|
+
AND reply_target = ?
|
|
138
|
+
LIMIT 1`).get(params.agentId, `${params.primaryTarget}:${buildThreadShortId(params.rootMessageId)}`);
|
|
139
|
+
return Boolean(row);
|
|
140
|
+
}
|
|
141
|
+
export function buildPrimaryDmTaskRootUpdateEvent(db, params) {
|
|
142
|
+
const conversationId = params.primaryConversationId ?? findConversationIdForReplyTarget(db, params.agentId, params.primaryTarget);
|
|
143
|
+
if (!conversationId)
|
|
144
|
+
return null;
|
|
145
|
+
const row = fetchTaskRootMessageRow(db, `dm:${params.agentId}`, params.rootMessageId);
|
|
146
|
+
if (!row)
|
|
147
|
+
return null;
|
|
148
|
+
const taskThreadStatus = params.taskThreadStatus === undefined
|
|
149
|
+
? getDmTaskThreadStatusForRootMessage(db, {
|
|
150
|
+
agentId: params.agentId,
|
|
151
|
+
primaryTarget: params.primaryTarget,
|
|
152
|
+
rootMessageId: params.rootMessageId,
|
|
153
|
+
})
|
|
154
|
+
: projectVisibleDmTaskThreadStatus(params.taskThreadStatus);
|
|
155
|
+
const event = buildPrimaryDmTaskRootEvent(row, taskThreadStatus, {
|
|
156
|
+
threadActive: hasPrimaryDmThreadConversation(db, {
|
|
157
|
+
agentId: params.agentId,
|
|
158
|
+
primaryTarget: params.primaryTarget,
|
|
159
|
+
rootMessageId: params.rootMessageId,
|
|
160
|
+
}),
|
|
161
|
+
});
|
|
162
|
+
if (!event)
|
|
163
|
+
return null;
|
|
164
|
+
return {
|
|
165
|
+
conversationId,
|
|
166
|
+
event,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
export function buildPrimaryDmTaskRootUpdateEventForThreadConversation(db, conversationId, taskThreadStatus) {
|
|
170
|
+
const conversation = db.prepare(`SELECT agent_id as agentId,
|
|
171
|
+
reply_target as replyTarget,
|
|
172
|
+
thread_root_id as threadRootId,
|
|
173
|
+
thread_kind as threadKind,
|
|
174
|
+
is_primary_thread as isPrimaryThread,
|
|
175
|
+
user_id as userId
|
|
176
|
+
FROM conversations
|
|
177
|
+
WHERE id = ?
|
|
178
|
+
LIMIT 1`).get(conversationId);
|
|
179
|
+
if (!conversation)
|
|
180
|
+
return null;
|
|
181
|
+
if (!conversation.agentId
|
|
182
|
+
|| conversation.threadKind !== 'direct'
|
|
183
|
+
|| conversation.isPrimaryThread !== 0
|
|
184
|
+
|| !conversation.threadRootId
|
|
185
|
+
|| !conversation.replyTarget) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const dmChannelId = `dm:${conversation.agentId}`;
|
|
189
|
+
const rootMessageId = db.prepare(`SELECT message_id as messageId
|
|
190
|
+
FROM tasks
|
|
191
|
+
WHERE channel_id = ?
|
|
192
|
+
AND REPLACE(LOWER(message_id), '-', '') LIKE ? || '%'
|
|
193
|
+
ORDER BY updated_at DESC, created_at DESC
|
|
194
|
+
LIMIT 1`).get(dmChannelId, conversation.threadRootId)?.messageId
|
|
195
|
+
?? findThreadRootMessageId(db, dmChannelId, conversation.threadRootId)
|
|
196
|
+
?? db.prepare(`SELECT cm.message_id as messageId
|
|
197
|
+
FROM channel_messages cm
|
|
198
|
+
WHERE cm.channel_id = ?
|
|
199
|
+
AND cm.thread_root_id IS NULL
|
|
200
|
+
AND REPLACE(LOWER(cm.message_id), '-', '') LIKE ? || '%'
|
|
201
|
+
ORDER BY cm.created_at DESC
|
|
202
|
+
LIMIT 1`).get(dmChannelId, conversation.threadRootId)?.messageId
|
|
203
|
+
?? null;
|
|
204
|
+
if (!rootMessageId)
|
|
205
|
+
return null;
|
|
206
|
+
const lastSeparatorIndex = conversation.replyTarget.lastIndexOf(':');
|
|
207
|
+
if (lastSeparatorIndex < 0)
|
|
208
|
+
return null;
|
|
209
|
+
const primaryTarget = conversation.replyTarget.slice(0, lastSeparatorIndex).trim();
|
|
210
|
+
if (!primaryTarget)
|
|
211
|
+
return null;
|
|
212
|
+
const primaryConversationId = findPrimaryDmConversationId(db, {
|
|
213
|
+
agentId: conversation.agentId,
|
|
214
|
+
userId: conversation.userId ?? null,
|
|
215
|
+
primaryTarget,
|
|
216
|
+
});
|
|
217
|
+
if (!primaryConversationId)
|
|
218
|
+
return null;
|
|
219
|
+
const row = fetchTaskRootMessageRow(db, dmChannelId, rootMessageId);
|
|
220
|
+
if (!row)
|
|
221
|
+
return null;
|
|
222
|
+
const event = buildPrimaryDmTaskRootEvent(row, projectVisibleDmTaskThreadStatus(taskThreadStatus), { threadActive: true });
|
|
223
|
+
if (!event)
|
|
224
|
+
return null;
|
|
225
|
+
return {
|
|
226
|
+
conversationId: primaryConversationId,
|
|
227
|
+
event,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function sanitizeSearchTerm(rawTerm) {
|
|
2
|
+
return rawTerm.replace(/[^\p{L}\p{N}_-]+/gu, '');
|
|
3
|
+
}
|
|
4
|
+
function splitMixedSearchTerm(term) {
|
|
5
|
+
return term.match(/[\p{Script=Han}]+|[A-Za-z0-9_-]+|[\p{L}\p{N}]+/gu) ?? [];
|
|
6
|
+
}
|
|
7
|
+
function escapeFtsQuotedTerm(term) {
|
|
8
|
+
return `"${term.replace(/"/g, '""')}"`;
|
|
9
|
+
}
|
|
10
|
+
function buildSingleTermClause(rawTerm, prefix) {
|
|
11
|
+
const sanitized = sanitizeSearchTerm(rawTerm);
|
|
12
|
+
if (!sanitized)
|
|
13
|
+
return null;
|
|
14
|
+
const originalClause = prefix ? `${sanitized}*` : escapeFtsQuotedTerm(sanitized);
|
|
15
|
+
const parts = splitMixedSearchTerm(sanitized);
|
|
16
|
+
if (parts.length <= 1)
|
|
17
|
+
return originalClause;
|
|
18
|
+
const splitClause = parts
|
|
19
|
+
.map((part) => (prefix ? `${part}*` : escapeFtsQuotedTerm(part)))
|
|
20
|
+
.join(' AND ');
|
|
21
|
+
return `(${originalClause} OR (${splitClause}))`;
|
|
22
|
+
}
|
|
23
|
+
export function buildFtsMatchQuery(rawQuery, options) {
|
|
24
|
+
const prefix = options?.prefix ?? false;
|
|
25
|
+
const clauses = rawQuery
|
|
26
|
+
.trim()
|
|
27
|
+
.split(/\s+/)
|
|
28
|
+
.map((term) => buildSingleTermClause(term, prefix))
|
|
29
|
+
.filter(Boolean);
|
|
30
|
+
if (clauses.length === 0)
|
|
31
|
+
return null;
|
|
32
|
+
return clauses.join(' AND ');
|
|
33
|
+
}
|