@troykelly/openclaw-projects 0.0.11 → 0.0.13
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.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -0
- package/dist/config.js.map +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +22 -3
- package/dist/hooks.js.map +1 -1
- package/dist/register-openclaw.d.ts +3 -1
- package/dist/register-openclaw.d.ts.map +1 -1
- package/dist/register-openclaw.js +640 -55
- package/dist/register-openclaw.js.map +1 -1
- package/dist/tools/context-search.d.ts +79 -0
- package/dist/tools/context-search.d.ts.map +1 -0
- package/dist/tools/context-search.js +267 -0
- package/dist/tools/context-search.js.map +1 -0
- package/dist/tools/email-send.d.ts.map +1 -1
- package/dist/tools/email-send.js +1 -14
- package/dist/tools/email-send.js.map +1 -1
- package/dist/tools/entity-links.d.ts +117 -0
- package/dist/tools/entity-links.d.ts.map +1 -0
- package/dist/tools/entity-links.js +446 -0
- package/dist/tools/entity-links.js.map +1 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/memory-recall.d.ts +28 -0
- package/dist/tools/memory-recall.d.ts.map +1 -1
- package/dist/tools/memory-recall.js +42 -3
- package/dist/tools/memory-recall.js.map +1 -1
- package/dist/tools/memory-store.d.ts +57 -0
- package/dist/tools/memory-store.d.ts.map +1 -1
- package/dist/tools/memory-store.js +29 -2
- package/dist/tools/memory-store.js.map +1 -1
- package/dist/tools/message-search.d.ts +2 -2
- package/dist/tools/message-search.d.ts.map +1 -1
- package/dist/tools/message-search.js +36 -3
- package/dist/tools/message-search.js.map +1 -1
- package/dist/tools/notes.d.ts +2 -2
- package/dist/tools/project-search.d.ts +92 -0
- package/dist/tools/project-search.d.ts.map +1 -0
- package/dist/tools/project-search.js +160 -0
- package/dist/tools/project-search.js.map +1 -0
- package/dist/tools/skill-store.d.ts +6 -6
- package/dist/tools/threads.d.ts +3 -3
- package/dist/tools/threads.d.ts.map +1 -1
- package/dist/tools/threads.js +45 -7
- package/dist/tools/threads.js.map +1 -1
- package/dist/tools/todo-search.d.ts +95 -0
- package/dist/tools/todo-search.d.ts.map +1 -0
- package/dist/tools/todo-search.js +164 -0
- package/dist/tools/todo-search.js.map +1 -0
- package/dist/types/openclaw-api.d.ts +15 -0
- package/dist/types/openclaw-api.d.ts.map +1 -1
- package/dist/utils/auto-linker.d.ts +66 -0
- package/dist/utils/auto-linker.d.ts.map +1 -0
- package/dist/utils/auto-linker.js +354 -0
- package/dist/utils/auto-linker.js.map +1 -0
- package/dist/utils/geo.d.ts +24 -0
- package/dist/utils/geo.d.ts.map +1 -0
- package/dist/utils/geo.js +38 -0
- package/dist/utils/geo.js.map +1 -0
- package/dist/utils/inbound-gate.d.ts +85 -0
- package/dist/utils/inbound-gate.d.ts.map +1 -0
- package/dist/utils/inbound-gate.js +133 -0
- package/dist/utils/inbound-gate.js.map +1 -0
- package/dist/utils/injection-log-rate-limiter.d.ts +62 -0
- package/dist/utils/injection-log-rate-limiter.d.ts.map +1 -0
- package/dist/utils/injection-log-rate-limiter.js +106 -0
- package/dist/utils/injection-log-rate-limiter.js.map +1 -0
- package/dist/utils/injection-protection.d.ts +133 -0
- package/dist/utils/injection-protection.d.ts.map +1 -0
- package/dist/utils/injection-protection.js +252 -0
- package/dist/utils/injection-protection.js.map +1 -0
- package/dist/utils/nominatim.d.ts +18 -0
- package/dist/utils/nominatim.d.ts.map +1 -0
- package/dist/utils/nominatim.js +56 -0
- package/dist/utils/nominatim.js.map +1 -0
- package/dist/utils/prompt-guard-client.d.ts +59 -0
- package/dist/utils/prompt-guard-client.d.ts.map +1 -0
- package/dist/utils/prompt-guard-client.js +99 -0
- package/dist/utils/prompt-guard-client.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +81 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +188 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/spam-filter.d.ts +79 -0
- package/dist/utils/spam-filter.d.ts.map +1 -0
- package/dist/utils/spam-filter.js +237 -0
- package/dist/utils/spam-filter.js.map +1 -0
- package/dist/utils/token-budget.d.ts +68 -0
- package/dist/utils/token-budget.d.ts.map +1 -0
- package/dist/utils/token-budget.js +142 -0
- package/dist/utils/token-budget.js.map +1 -0
- package/openclaw.plugin.json +25 -3
- package/package.json +12 -11
- package/LICENSE +0 -21
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* todo_search tool implementation.
|
|
3
|
+
* Provides semantic search through work items (todos and projects).
|
|
4
|
+
* Uses the unified search API with work_item type filter.
|
|
5
|
+
* Geo-contextual ranking added in Issue #1218.
|
|
6
|
+
*
|
|
7
|
+
* Part of Issue #1216.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { sanitizeErrorMessage } from '../utils/sanitize.js';
|
|
11
|
+
import { reverseGeocode } from '../utils/nominatim.js';
|
|
12
|
+
/** Parameters for todo_search tool */
|
|
13
|
+
export const TodoSearchParamsSchema = z.object({
|
|
14
|
+
query: z.string().min(1, 'Query cannot be empty').max(1000, 'Query must be 1000 characters or less'),
|
|
15
|
+
limit: z.number().int().min(1).max(50).optional(),
|
|
16
|
+
kind: z.enum(['task', 'project', 'initiative', 'epic', 'issue']).optional(),
|
|
17
|
+
status: z.string().max(50).optional(),
|
|
18
|
+
location: z.object({
|
|
19
|
+
lat: z.number().min(-90).max(90),
|
|
20
|
+
lng: z.number().min(-180).max(180),
|
|
21
|
+
}).optional(),
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Sanitize query input to prevent injection and remove control characters.
|
|
25
|
+
*/
|
|
26
|
+
function sanitizeQuery(query) {
|
|
27
|
+
return query.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '').trim();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates the todo_search tool.
|
|
31
|
+
*/
|
|
32
|
+
export function createTodoSearchTool(options) {
|
|
33
|
+
const { client, logger, config, userId } = options;
|
|
34
|
+
return {
|
|
35
|
+
name: 'todo_search',
|
|
36
|
+
description: 'Search todos and work items by natural language query. Uses semantic and text search to find relevant items. ' +
|
|
37
|
+
'Optionally filter by kind (task, project, initiative, epic, issue) or status. ' +
|
|
38
|
+
'Provide location (lat/lng) to boost results relevant to the current location.',
|
|
39
|
+
parameters: TodoSearchParamsSchema,
|
|
40
|
+
async execute(params) {
|
|
41
|
+
const parseResult = TodoSearchParamsSchema.safeParse(params);
|
|
42
|
+
if (!parseResult.success) {
|
|
43
|
+
const errorMessage = parseResult.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
|
|
44
|
+
return { success: false, error: errorMessage };
|
|
45
|
+
}
|
|
46
|
+
const { query, limit = 10, kind, status, location } = parseResult.data;
|
|
47
|
+
const sanitizedQuery = sanitizeQuery(query);
|
|
48
|
+
if (sanitizedQuery.length === 0) {
|
|
49
|
+
return { success: false, error: 'Query cannot be empty after sanitization' };
|
|
50
|
+
}
|
|
51
|
+
logger.info('todo_search invoked', {
|
|
52
|
+
userId,
|
|
53
|
+
queryLength: sanitizedQuery.length,
|
|
54
|
+
limit,
|
|
55
|
+
kind: kind ?? 'all',
|
|
56
|
+
status: status ?? 'all',
|
|
57
|
+
hasLocation: !!location,
|
|
58
|
+
});
|
|
59
|
+
try {
|
|
60
|
+
// Augment query with location context for geo-contextual ranking (Issue #1218)
|
|
61
|
+
let searchQuery = sanitizedQuery;
|
|
62
|
+
if (location && config.nominatimUrl) {
|
|
63
|
+
try {
|
|
64
|
+
const geo = await reverseGeocode(location.lat, location.lng, config.nominatimUrl);
|
|
65
|
+
if (geo?.placeLabel) {
|
|
66
|
+
const augmented = `${sanitizedQuery} near ${geo.placeLabel}`;
|
|
67
|
+
if (augmented.length <= 1000) {
|
|
68
|
+
searchQuery = augmented;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
logger.warn('Reverse geocode failed, proceeding without location augmentation', { userId });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Over-fetch by 3x to compensate for client-side kind/status filtering (Issue #1216 review fix)
|
|
77
|
+
const fetchLimit = (kind || status || location) ? Math.min(limit * 3, 50) : limit;
|
|
78
|
+
const queryParams = new URLSearchParams({
|
|
79
|
+
q: searchQuery,
|
|
80
|
+
types: 'work_item',
|
|
81
|
+
limit: String(fetchLimit),
|
|
82
|
+
semantic: 'true',
|
|
83
|
+
user_email: userId, // Issue #1216: scope results to current user
|
|
84
|
+
});
|
|
85
|
+
const response = await client.get(`/api/search?${queryParams.toString()}`, { userId });
|
|
86
|
+
if (!response.success) {
|
|
87
|
+
logger.error('todo_search API error', {
|
|
88
|
+
userId,
|
|
89
|
+
status: response.error.status,
|
|
90
|
+
code: response.error.code,
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
error: response.error.message || 'Failed to search work items',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
let results = response.data.results ?? [];
|
|
98
|
+
// Client-side filtering by kind and status if specified, then truncate to requested limit
|
|
99
|
+
if (kind) {
|
|
100
|
+
results = results.filter((r) => r.metadata?.kind === kind);
|
|
101
|
+
}
|
|
102
|
+
if (status) {
|
|
103
|
+
results = results.filter((r) => r.metadata?.status === status);
|
|
104
|
+
}
|
|
105
|
+
results = results.slice(0, limit);
|
|
106
|
+
const items = results.map((r) => ({
|
|
107
|
+
id: r.id,
|
|
108
|
+
title: r.title,
|
|
109
|
+
snippet: r.snippet,
|
|
110
|
+
score: r.score,
|
|
111
|
+
kind: r.metadata?.kind,
|
|
112
|
+
status: r.metadata?.status,
|
|
113
|
+
}));
|
|
114
|
+
if (items.length === 0) {
|
|
115
|
+
return {
|
|
116
|
+
success: true,
|
|
117
|
+
data: {
|
|
118
|
+
content: 'No matching work items found.',
|
|
119
|
+
details: {
|
|
120
|
+
count: 0,
|
|
121
|
+
results: [],
|
|
122
|
+
searchType: response.data.search_type,
|
|
123
|
+
userId,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const content = items
|
|
129
|
+
.map((item) => {
|
|
130
|
+
const kindStr = item.kind ? `[${item.kind}]` : '';
|
|
131
|
+
const statusStr = item.status ? ` (${item.status})` : '';
|
|
132
|
+
const snippetStr = item.snippet ? ` - ${item.snippet}` : '';
|
|
133
|
+
return `- ${kindStr} **${item.title}**${statusStr}${snippetStr}`;
|
|
134
|
+
})
|
|
135
|
+
.join('\n');
|
|
136
|
+
logger.debug('todo_search completed', {
|
|
137
|
+
userId,
|
|
138
|
+
resultCount: items.length,
|
|
139
|
+
searchType: response.data.search_type,
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
data: {
|
|
144
|
+
content,
|
|
145
|
+
details: {
|
|
146
|
+
count: items.length,
|
|
147
|
+
results: items,
|
|
148
|
+
searchType: response.data.search_type,
|
|
149
|
+
userId,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
logger.error('todo_search failed', {
|
|
156
|
+
userId,
|
|
157
|
+
error: error instanceof Error ? error.message : String(error),
|
|
158
|
+
});
|
|
159
|
+
return { success: false, error: sanitizeErrorMessage(error) };
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=todo-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"todo-search.js","sourceRoot":"","sources":["../../src/tools/todo-search.ts"],"names":[],"mappings":"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"}
|
|
@@ -125,6 +125,21 @@ export interface PluginHookBeforeAgentStartResult {
|
|
|
125
125
|
/** Prepend to conversation context */
|
|
126
126
|
prependContext?: string;
|
|
127
127
|
}
|
|
128
|
+
/** Event payload for message_received hook (Issue #1223) */
|
|
129
|
+
export interface PluginHookMessageReceivedEvent {
|
|
130
|
+
/** Thread ID for the message */
|
|
131
|
+
threadId?: string;
|
|
132
|
+
/** Sender identifier (email or phone) */
|
|
133
|
+
sender?: string;
|
|
134
|
+
/** Sender email address */
|
|
135
|
+
senderEmail?: string;
|
|
136
|
+
/** Sender phone number */
|
|
137
|
+
senderPhone?: string;
|
|
138
|
+
/** Message body content */
|
|
139
|
+
content?: string;
|
|
140
|
+
/** Channel the message was received on (sms, email, etc.) */
|
|
141
|
+
channel?: string;
|
|
142
|
+
}
|
|
128
143
|
/** Event payload for agent_end hook */
|
|
129
144
|
export interface PluginHookAgentEndEvent {
|
|
130
145
|
/** Full conversation messages */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openclaw-api.d.ts","sourceRoot":"","sources":["../../src/types/openclaw-api.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"openclaw-api.d.ts","sourceRoot":"","sources":["../../src/types/openclaw-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-linker utility for inbound messages (Issue #1223).
|
|
3
|
+
*
|
|
4
|
+
* Automatically creates entity links when inbound SMS/email arrives:
|
|
5
|
+
* 1. Sender -> Contact matching: matches sender email/phone to existing contacts
|
|
6
|
+
* 2. Content -> Project/Todo matching: semantic search for related work items
|
|
7
|
+
* (only runs when sender is a known contact — safety decision to prevent
|
|
8
|
+
* untrusted senders from creating spurious links)
|
|
9
|
+
*
|
|
10
|
+
* Links are created via the skill_store API using the same pattern as
|
|
11
|
+
* entity-links.ts tools. All operations are failure-isolated so that
|
|
12
|
+
* auto-linking never crashes inbound message processing.
|
|
13
|
+
*/
|
|
14
|
+
import type { ApiClient } from '../api-client.js';
|
|
15
|
+
import type { Logger } from '../logger.js';
|
|
16
|
+
/** Default similarity threshold for content matching */
|
|
17
|
+
export declare const DEFAULT_SIMILARITY_THRESHOLD = 0.75;
|
|
18
|
+
/** Inbound message data for auto-linking */
|
|
19
|
+
export interface AutoLinkMessage {
|
|
20
|
+
/** Thread ID (UUID) for the inbound message thread */
|
|
21
|
+
threadId: string;
|
|
22
|
+
/** Sender email address, if known */
|
|
23
|
+
senderEmail?: string;
|
|
24
|
+
/** Sender phone number, if known */
|
|
25
|
+
senderPhone?: string;
|
|
26
|
+
/** Message body content */
|
|
27
|
+
content: string;
|
|
28
|
+
}
|
|
29
|
+
/** Options for the auto-link function */
|
|
30
|
+
export interface AutoLinkOptions {
|
|
31
|
+
/** API client for backend calls */
|
|
32
|
+
client: ApiClient;
|
|
33
|
+
/** Logger instance */
|
|
34
|
+
logger: Logger;
|
|
35
|
+
/** User ID for scoping API calls */
|
|
36
|
+
userId: string;
|
|
37
|
+
/** Inbound message data */
|
|
38
|
+
message: AutoLinkMessage;
|
|
39
|
+
/** Similarity threshold for content matching (default: 0.75) */
|
|
40
|
+
similarityThreshold?: number;
|
|
41
|
+
}
|
|
42
|
+
/** Result of auto-linking */
|
|
43
|
+
export interface AutoLinkResult {
|
|
44
|
+
/** Number of entity links created */
|
|
45
|
+
linksCreated: number;
|
|
46
|
+
/** IDs of matched entities by type */
|
|
47
|
+
matches: {
|
|
48
|
+
contacts: string[];
|
|
49
|
+
projects: string[];
|
|
50
|
+
todos: string[];
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Auto-link an inbound message to related entities.
|
|
55
|
+
*
|
|
56
|
+
* Performs sender matching first. Content matching only runs when at least
|
|
57
|
+
* one contact matched — this prevents untrusted/unknown senders from
|
|
58
|
+
* creating spurious links to projects and todos.
|
|
59
|
+
*
|
|
60
|
+
* Never throws - all errors are caught and logged.
|
|
61
|
+
*
|
|
62
|
+
* @param options - Auto-link configuration and message data
|
|
63
|
+
* @returns Summary of links created and entities matched
|
|
64
|
+
*/
|
|
65
|
+
export declare function autoLinkInboundMessage(options: AutoLinkOptions): Promise<AutoLinkResult>;
|
|
66
|
+
//# sourceMappingURL=auto-linker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-linker.d.ts","sourceRoot":"","sources":["../../src/utils/auto-linker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAM3C,wDAAwD;AACxD,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAmBjD,4CAA4C;AAC5C,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,yCAAyC;AACzC,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,MAAM,EAAE,SAAS,CAAC;IAClB,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,OAAO,EAAE,eAAe,CAAC;IACzB,gEAAgE;IAChE,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,6BAA6B;AAC7B,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,OAAO,EAAE;QACP,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,KAAK,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;CACH;AA+XD;;;;;;;;;;;GAWG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA6F9F"}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-linker utility for inbound messages (Issue #1223).
|
|
3
|
+
*
|
|
4
|
+
* Automatically creates entity links when inbound SMS/email arrives:
|
|
5
|
+
* 1. Sender -> Contact matching: matches sender email/phone to existing contacts
|
|
6
|
+
* 2. Content -> Project/Todo matching: semantic search for related work items
|
|
7
|
+
* (only runs when sender is a known contact — safety decision to prevent
|
|
8
|
+
* untrusted senders from creating spurious links)
|
|
9
|
+
*
|
|
10
|
+
* Links are created via the skill_store API using the same pattern as
|
|
11
|
+
* entity-links.ts tools. All operations are failure-isolated so that
|
|
12
|
+
* auto-linking never crashes inbound message processing.
|
|
13
|
+
*/
|
|
14
|
+
import { sanitizeExternalMessage } from './injection-protection.js';
|
|
15
|
+
import { sanitizeErrorMessage } from './sanitize.js';
|
|
16
|
+
// ==================== Constants ====================
|
|
17
|
+
/** Default similarity threshold for content matching */
|
|
18
|
+
export const DEFAULT_SIMILARITY_THRESHOLD = 0.75;
|
|
19
|
+
/** Skill ID for entity link storage (matches entity-links.ts) */
|
|
20
|
+
const SKILL_ID = 'entity-links';
|
|
21
|
+
/** Collection name for entity links (matches entity-links.ts) */
|
|
22
|
+
const COLLECTION = 'entity_links';
|
|
23
|
+
/** Maximum content length to send to search API */
|
|
24
|
+
const MAX_SEARCH_QUERY_LENGTH = 500;
|
|
25
|
+
/** Work item kinds that map to "project" in results */
|
|
26
|
+
const PROJECT_KINDS = new Set(['project']);
|
|
27
|
+
/** Work item kinds that map to "todo" in results */
|
|
28
|
+
const TODO_KINDS = new Set(['task', 'issue', 'epic', 'initiative']);
|
|
29
|
+
// ==================== Helpers ====================
|
|
30
|
+
/**
|
|
31
|
+
* Build a URI-style thread reference for use as target_ref.
|
|
32
|
+
* Uses 'url' entity type since the entity-links schema has a fixed enum
|
|
33
|
+
* and does not yet include a 'message_thread' type.
|
|
34
|
+
* TODO(#1223): Add a proper 'message_thread' entity type to entity-links schema.
|
|
35
|
+
*/
|
|
36
|
+
function buildThreadRef(threadId) {
|
|
37
|
+
return `thread:${threadId}`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build a composite key for a link (matches entity-links.ts pattern).
|
|
41
|
+
*/
|
|
42
|
+
function buildLinkKey(sourceType, sourceRef, targetType, targetRef) {
|
|
43
|
+
return `${sourceType}:${sourceRef}:${targetType}:${targetRef}`;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build a tag for source-entity lookup (matches entity-links.ts pattern).
|
|
47
|
+
*/
|
|
48
|
+
function buildSourceTag(entityType, entityRef) {
|
|
49
|
+
return `src:${entityType}:${entityRef}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create a bidirectional entity link via skill_store.
|
|
53
|
+
* Returns true if both directions were created successfully.
|
|
54
|
+
*/
|
|
55
|
+
async function createEntityLink(client, userId, logger, sourceType, sourceId, targetType, targetRef, label) {
|
|
56
|
+
const now = new Date().toISOString();
|
|
57
|
+
const forwardKey = buildLinkKey(sourceType, sourceId, targetType, targetRef);
|
|
58
|
+
const reverseKey = buildLinkKey(targetType, targetRef, sourceType, sourceId);
|
|
59
|
+
const forwardData = {
|
|
60
|
+
source_type: sourceType,
|
|
61
|
+
source_id: sourceId,
|
|
62
|
+
target_type: targetType,
|
|
63
|
+
target_ref: targetRef,
|
|
64
|
+
label: label ?? null,
|
|
65
|
+
created_at: now,
|
|
66
|
+
auto_linked: true,
|
|
67
|
+
};
|
|
68
|
+
const reverseData = {
|
|
69
|
+
source_type: targetType,
|
|
70
|
+
source_id: targetRef,
|
|
71
|
+
target_type: sourceType,
|
|
72
|
+
target_ref: sourceId,
|
|
73
|
+
label: label ?? null,
|
|
74
|
+
created_at: now,
|
|
75
|
+
auto_linked: true,
|
|
76
|
+
};
|
|
77
|
+
// Create forward link
|
|
78
|
+
const forwardResponse = await client.post('/api/skill-store/items', {
|
|
79
|
+
skill_id: SKILL_ID,
|
|
80
|
+
collection: COLLECTION,
|
|
81
|
+
key: forwardKey,
|
|
82
|
+
data: forwardData,
|
|
83
|
+
tags: [buildSourceTag(sourceType, sourceId)],
|
|
84
|
+
}, { userId });
|
|
85
|
+
if (!forwardResponse.success) {
|
|
86
|
+
logger.error('auto-linker: forward link creation failed', {
|
|
87
|
+
userId,
|
|
88
|
+
sourceType,
|
|
89
|
+
targetType,
|
|
90
|
+
status: forwardResponse.error.status,
|
|
91
|
+
});
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// Create reverse link
|
|
95
|
+
const reverseResponse = await client.post('/api/skill-store/items', {
|
|
96
|
+
skill_id: SKILL_ID,
|
|
97
|
+
collection: COLLECTION,
|
|
98
|
+
key: reverseKey,
|
|
99
|
+
data: reverseData,
|
|
100
|
+
tags: [buildSourceTag(targetType, targetRef)],
|
|
101
|
+
}, { userId });
|
|
102
|
+
if (!reverseResponse.success) {
|
|
103
|
+
logger.error('auto-linker: reverse link creation failed', {
|
|
104
|
+
userId,
|
|
105
|
+
sourceType,
|
|
106
|
+
targetType,
|
|
107
|
+
status: reverseResponse.error.status,
|
|
108
|
+
});
|
|
109
|
+
// Best-effort rollback of orphaned forward link
|
|
110
|
+
const rollbackResponse = await client.delete(`/api/skill-store/items/${forwardResponse.data.id}`, { userId });
|
|
111
|
+
if (!rollbackResponse.success) {
|
|
112
|
+
logger.error('auto-linker: rollback of orphaned forward link failed — partial state', {
|
|
113
|
+
userId,
|
|
114
|
+
forwardId: forwardResponse.data.id,
|
|
115
|
+
rollbackStatus: rollbackResponse.error.status,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
// ==================== Sender matching ====================
|
|
123
|
+
/**
|
|
124
|
+
* Search for contacts matching the sender's email and/or phone.
|
|
125
|
+
* Searches both identifiers separately when both are present,
|
|
126
|
+
* then deduplicates matched contact IDs.
|
|
127
|
+
* Returns matched contact IDs and creates links for each match.
|
|
128
|
+
*/
|
|
129
|
+
async function matchSenderToContacts(client, logger, userId, threadId, senderEmail, senderPhone) {
|
|
130
|
+
if (!senderEmail && !senderPhone) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
// Search both email and phone separately to catch all matches
|
|
134
|
+
const searchQueries = [];
|
|
135
|
+
if (senderEmail)
|
|
136
|
+
searchQueries.push(senderEmail);
|
|
137
|
+
if (senderPhone)
|
|
138
|
+
searchQueries.push(senderPhone);
|
|
139
|
+
// Collect all contacts from all searches
|
|
140
|
+
const allContacts = [];
|
|
141
|
+
for (const searchQuery of searchQueries) {
|
|
142
|
+
const queryParams = new URLSearchParams({
|
|
143
|
+
search: searchQuery,
|
|
144
|
+
limit: '5',
|
|
145
|
+
user_email: userId,
|
|
146
|
+
});
|
|
147
|
+
const response = await client.get(`/api/contacts?${queryParams.toString()}`, { userId });
|
|
148
|
+
if (!response.success) {
|
|
149
|
+
logger.error('auto-linker: contact search failed', {
|
|
150
|
+
userId,
|
|
151
|
+
status: response.error.status,
|
|
152
|
+
code: response.error.code,
|
|
153
|
+
});
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const contacts = response.data.contacts ?? response.data.items ?? [];
|
|
157
|
+
allContacts.push(...contacts);
|
|
158
|
+
}
|
|
159
|
+
if (allContacts.length === 0) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
// Filter to exact matches on email or phone, deduplicate by ID
|
|
163
|
+
const seen = new Set();
|
|
164
|
+
const matchedContacts = [];
|
|
165
|
+
for (const c of allContacts) {
|
|
166
|
+
if (seen.has(c.id))
|
|
167
|
+
continue;
|
|
168
|
+
const emailMatch = senderEmail != null && c.email?.toLowerCase() === senderEmail.toLowerCase();
|
|
169
|
+
const phoneMatch = senderPhone != null && c.phone === senderPhone;
|
|
170
|
+
if (emailMatch || phoneMatch) {
|
|
171
|
+
seen.add(c.id);
|
|
172
|
+
matchedContacts.push(c);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const threadRef = buildThreadRef(threadId);
|
|
176
|
+
const linkedContactIds = [];
|
|
177
|
+
for (const contact of matchedContacts) {
|
|
178
|
+
try {
|
|
179
|
+
// Use 'url' type with thread: URI since entity-links schema does not have
|
|
180
|
+
// a 'message_thread' type yet. See buildThreadRef() for details.
|
|
181
|
+
const linked = await createEntityLink(client, userId, logger, 'contact', contact.id, 'url', threadRef, 'inbound-message-sender');
|
|
182
|
+
if (linked) {
|
|
183
|
+
linkedContactIds.push(contact.id);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
logger.error('auto-linker: failed to create contact link', {
|
|
188
|
+
userId,
|
|
189
|
+
contactId: contact.id,
|
|
190
|
+
error: sanitizeErrorMessage(error),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return linkedContactIds;
|
|
195
|
+
}
|
|
196
|
+
// ==================== Content matching ====================
|
|
197
|
+
/**
|
|
198
|
+
* Search for projects and todos matching the message content.
|
|
199
|
+
* Returns matched entity IDs by type and creates links for each match.
|
|
200
|
+
*/
|
|
201
|
+
async function matchContentToWorkItems(client, logger, userId, threadId, content, similarityThreshold) {
|
|
202
|
+
// Sanitize external content before using it in search queries.
|
|
203
|
+
// Removes control characters, unicode invisibles, and potential injection payloads.
|
|
204
|
+
const sanitizedContent = sanitizeExternalMessage(content);
|
|
205
|
+
if (sanitizedContent.length === 0) {
|
|
206
|
+
return { projects: [], todos: [] };
|
|
207
|
+
}
|
|
208
|
+
// Truncate for search
|
|
209
|
+
const searchQuery = sanitizedContent.substring(0, MAX_SEARCH_QUERY_LENGTH);
|
|
210
|
+
const queryParams = new URLSearchParams({
|
|
211
|
+
q: searchQuery,
|
|
212
|
+
types: 'work_item',
|
|
213
|
+
limit: '10',
|
|
214
|
+
semantic: 'true',
|
|
215
|
+
user_email: userId,
|
|
216
|
+
});
|
|
217
|
+
const response = await client.get(`/api/search?${queryParams.toString()}`, { userId });
|
|
218
|
+
if (!response.success) {
|
|
219
|
+
logger.error('auto-linker: content search failed', {
|
|
220
|
+
userId,
|
|
221
|
+
status: response.error.status,
|
|
222
|
+
code: response.error.code,
|
|
223
|
+
});
|
|
224
|
+
return { projects: [], todos: [] };
|
|
225
|
+
}
|
|
226
|
+
const results = response.data.results ?? [];
|
|
227
|
+
// Filter to items above the similarity threshold
|
|
228
|
+
const highConfidenceResults = results.filter((r) => r.score >= similarityThreshold);
|
|
229
|
+
if (highConfidenceResults.length === 0) {
|
|
230
|
+
logger.debug('auto-linker: no content matches above threshold', {
|
|
231
|
+
userId,
|
|
232
|
+
threshold: similarityThreshold,
|
|
233
|
+
totalResults: results.length,
|
|
234
|
+
topScore: results[0]?.score ?? 0,
|
|
235
|
+
});
|
|
236
|
+
return { projects: [], todos: [] };
|
|
237
|
+
}
|
|
238
|
+
const threadRef = buildThreadRef(threadId);
|
|
239
|
+
const linkedProjects = [];
|
|
240
|
+
const linkedTodos = [];
|
|
241
|
+
for (const item of highConfidenceResults) {
|
|
242
|
+
const kind = item.metadata?.kind;
|
|
243
|
+
const isProject = kind !== undefined && PROJECT_KINDS.has(kind);
|
|
244
|
+
const isTodo = kind !== undefined && TODO_KINDS.has(kind);
|
|
245
|
+
if (!isProject && !isTodo) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const sourceType = isProject ? 'project' : 'todo';
|
|
249
|
+
try {
|
|
250
|
+
// Use 'url' type with thread: URI since entity-links schema does not have
|
|
251
|
+
// a 'message_thread' type yet. See buildThreadRef() for details.
|
|
252
|
+
const linked = await createEntityLink(client, userId, logger, sourceType, item.id, 'url', threadRef, `auto-linked:${item.title.substring(0, 50)}`);
|
|
253
|
+
if (linked) {
|
|
254
|
+
if (isProject) {
|
|
255
|
+
linkedProjects.push(item.id);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
linkedTodos.push(item.id);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
logger.error('auto-linker: failed to create work item link', {
|
|
264
|
+
userId,
|
|
265
|
+
itemId: item.id,
|
|
266
|
+
itemKind: kind,
|
|
267
|
+
error: sanitizeErrorMessage(error),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return { projects: linkedProjects, todos: linkedTodos };
|
|
272
|
+
}
|
|
273
|
+
// ==================== Main function ====================
|
|
274
|
+
/**
|
|
275
|
+
* Auto-link an inbound message to related entities.
|
|
276
|
+
*
|
|
277
|
+
* Performs sender matching first. Content matching only runs when at least
|
|
278
|
+
* one contact matched — this prevents untrusted/unknown senders from
|
|
279
|
+
* creating spurious links to projects and todos.
|
|
280
|
+
*
|
|
281
|
+
* Never throws - all errors are caught and logged.
|
|
282
|
+
*
|
|
283
|
+
* @param options - Auto-link configuration and message data
|
|
284
|
+
* @returns Summary of links created and entities matched
|
|
285
|
+
*/
|
|
286
|
+
export async function autoLinkInboundMessage(options) {
|
|
287
|
+
const { client, logger, userId, message, similarityThreshold = DEFAULT_SIMILARITY_THRESHOLD, } = options;
|
|
288
|
+
const emptyResult = {
|
|
289
|
+
linksCreated: 0,
|
|
290
|
+
matches: { contacts: [], projects: [], todos: [] },
|
|
291
|
+
};
|
|
292
|
+
try {
|
|
293
|
+
logger.info('auto-linker: processing inbound message', {
|
|
294
|
+
userId,
|
|
295
|
+
threadId: message.threadId,
|
|
296
|
+
hasSenderEmail: !!message.senderEmail,
|
|
297
|
+
hasSenderPhone: !!message.senderPhone,
|
|
298
|
+
contentLength: message.content.length,
|
|
299
|
+
});
|
|
300
|
+
// Step 1: Sender matching — always runs when sender info is available
|
|
301
|
+
const contactMatches = await matchSenderToContacts(client, logger, userId, message.threadId, message.senderEmail, message.senderPhone).catch((error) => {
|
|
302
|
+
logger.error('auto-linker: sender matching failed', {
|
|
303
|
+
userId,
|
|
304
|
+
error: sanitizeErrorMessage(error),
|
|
305
|
+
});
|
|
306
|
+
return [];
|
|
307
|
+
});
|
|
308
|
+
// Step 2: Content matching — only runs when sender is a known contact.
|
|
309
|
+
// Safety decision: untrusted/unknown senders should not trigger content-based
|
|
310
|
+
// linking to prevent spam or malicious messages from creating spurious links
|
|
311
|
+
// to the user's projects and todos.
|
|
312
|
+
let contentMatches = { projects: [], todos: [] };
|
|
313
|
+
if (contactMatches.length > 0) {
|
|
314
|
+
contentMatches = await matchContentToWorkItems(client, logger, userId, message.threadId, message.content, similarityThreshold).catch((error) => {
|
|
315
|
+
logger.error('auto-linker: content matching failed', {
|
|
316
|
+
userId,
|
|
317
|
+
error: sanitizeErrorMessage(error),
|
|
318
|
+
});
|
|
319
|
+
return { projects: [], todos: [] };
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
logger.debug('auto-linker: skipping content matching — no known sender contact', {
|
|
324
|
+
userId,
|
|
325
|
+
threadId: message.threadId,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
const result = {
|
|
329
|
+
linksCreated: contactMatches.length + contentMatches.projects.length + contentMatches.todos.length,
|
|
330
|
+
matches: {
|
|
331
|
+
contacts: contactMatches,
|
|
332
|
+
projects: contentMatches.projects,
|
|
333
|
+
todos: contentMatches.todos,
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
logger.info('auto-linker: completed', {
|
|
337
|
+
userId,
|
|
338
|
+
threadId: message.threadId,
|
|
339
|
+
linksCreated: result.linksCreated,
|
|
340
|
+
contactMatches: result.matches.contacts.length,
|
|
341
|
+
projectMatches: result.matches.projects.length,
|
|
342
|
+
todoMatches: result.matches.todos.length,
|
|
343
|
+
});
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
logger.error('auto-linker: unexpected failure', {
|
|
348
|
+
userId,
|
|
349
|
+
error: sanitizeErrorMessage(error),
|
|
350
|
+
});
|
|
351
|
+
return emptyResult;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
//# sourceMappingURL=auto-linker.js.map
|