@elevasis/core 0.17.0 → 0.19.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.
Files changed (44) hide show
  1. package/dist/index.d.ts +82 -1
  2. package/dist/index.js +291 -171
  3. package/dist/knowledge/index.d.ts +43 -0
  4. package/dist/organization-model/index.d.ts +82 -1
  5. package/dist/organization-model/index.js +291 -171
  6. package/dist/test-utils/index.d.ts +41 -12
  7. package/dist/test-utils/index.js +291 -171
  8. package/package.json +2 -1
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +78 -65
  10. package/src/auth/multi-tenancy/organizations/__tests__/api-schemas.test.ts +194 -0
  11. package/src/auth/multi-tenancy/organizations/api-schemas.ts +136 -128
  12. package/src/business/acquisition/api-schemas.test.ts +100 -2
  13. package/src/business/acquisition/api-schemas.ts +81 -43
  14. package/src/business/acquisition/build-templates.test.ts +212 -0
  15. package/src/business/acquisition/types.ts +21 -38
  16. package/src/execution/engine/index.ts +436 -434
  17. package/src/execution/engine/tools/integration/server/adapters/google-calendar/google-calendar-adapter.ts +428 -0
  18. package/src/execution/engine/tools/integration/server/adapters/google-calendar/index.ts +2 -0
  19. package/src/execution/engine/tools/lead-service-types.ts +51 -9
  20. package/src/execution/engine/tools/platform/acquisition/company-tools.ts +7 -6
  21. package/src/execution/engine/tools/platform/acquisition/contact-tools.ts +6 -5
  22. package/src/execution/engine/tools/platform/acquisition/types.ts +20 -9
  23. package/src/execution/engine/tools/registry.ts +700 -698
  24. package/src/execution/engine/tools/tool-maps.ts +10 -0
  25. package/src/execution/external/__tests__/api-schemas.test.ts +127 -0
  26. package/src/integrations/oauth/__tests__/provider-registry.test.ts +7 -6
  27. package/src/integrations/oauth/provider-registry.ts +74 -61
  28. package/src/integrations/oauth/server/credentials.ts +43 -39
  29. package/src/knowledge/__tests__/queries.test.ts +89 -0
  30. package/src/organization-model/__tests__/icons.test.ts +61 -0
  31. package/src/organization-model/__tests__/knowledge.test.ts +118 -1
  32. package/src/organization-model/__tests__/prospecting-ssot.test.ts +94 -0
  33. package/src/organization-model/defaults.ts +8 -0
  34. package/src/organization-model/domains/knowledge.ts +9 -0
  35. package/src/organization-model/domains/prospecting.ts +272 -226
  36. package/src/organization-model/domains/sales.ts +32 -25
  37. package/src/organization-model/icons.ts +3 -0
  38. package/src/organization-model/types.ts +9 -1
  39. package/src/platform/constants/versions.ts +1 -1
  40. package/src/platform/utils/__tests__/validation.test.ts +1084 -1083
  41. package/src/platform/utils/validation.ts +425 -425
  42. package/src/reference/_generated/contracts.md +78 -65
  43. package/src/server.ts +6 -0
  44. package/src/supabase/database.types.ts +6 -12
@@ -0,0 +1,428 @@
1
+ import type { calendar_v3 } from '@googleapis/calendar'
2
+ import type { BaseIntegrationAdapter } from '../../../base-integration-adapter'
3
+ import { ToolingError } from '../../../../types'
4
+ import type { ExecutionContext } from '../../../../../base/types'
5
+ import { getOAuthCredentials } from '../../../../../../../integrations/oauth/server/credentials'
6
+
7
+ /**
8
+ * Google Calendar credentials format
9
+ * Stored in credentials table, encrypted
10
+ */
11
+ interface GoogleCalendarCredentials {
12
+ accessToken: string
13
+ refreshToken?: string
14
+ expiresAt?: string
15
+ }
16
+
17
+ /**
18
+ * Date/time value from Google Calendar API
19
+ */
20
+ export interface CalendarDateTime {
21
+ dateTime?: string // ISO 8601 datetime (when time is specified)
22
+ date?: string // YYYY-MM-DD (for all-day events)
23
+ timeZone?: string
24
+ }
25
+
26
+ /**
27
+ * A Google Calendar event (plausible-use fields only)
28
+ */
29
+ export interface CalendarEvent {
30
+ id: string
31
+ summary?: string
32
+ description?: string
33
+ location?: string
34
+ start: CalendarDateTime
35
+ end: CalendarDateTime
36
+ status?: 'confirmed' | 'tentative' | 'cancelled'
37
+ htmlLink?: string
38
+ created?: string
39
+ updated?: string
40
+ organizer?: { email?: string; displayName?: string }
41
+ attendees?: Array<{ email?: string; displayName?: string; responseStatus?: string }>
42
+ recurringEventId?: string
43
+ }
44
+
45
+ /**
46
+ * listEvents parameters.
47
+ *
48
+ * `credentialName` is required when calling the adapter directly (HTTP-handler
49
+ * path). It is ignored on the dispatch path, where credentials are injected
50
+ * by the execution-engine pipeline.
51
+ */
52
+ interface ListEventsParams {
53
+ organizationId: string
54
+ credentialName?: string
55
+ calendarId?: string
56
+ timeMin?: string
57
+ timeMax?: string
58
+ maxResults?: number
59
+ singleEvents?: boolean
60
+ orderBy?: 'startTime' | 'updated'
61
+ }
62
+
63
+ /**
64
+ * listEvents result
65
+ */
66
+ export interface ListEventsResult {
67
+ items: CalendarEvent[]
68
+ nextPageToken?: string
69
+ summary?: string
70
+ timeZone?: string
71
+ }
72
+
73
+ /**
74
+ * getEvent parameters
75
+ */
76
+ interface GetEventParams {
77
+ organizationId: string
78
+ credentialName?: string
79
+ calendarId?: string
80
+ eventId: string
81
+ }
82
+
83
+ /**
84
+ * Google Calendar adapter for calendar and event operations via Google Calendar API v3
85
+ *
86
+ * Read-only methods:
87
+ * - listEvents: List events from a calendar
88
+ * - getEvent: Get a single event by ID
89
+ *
90
+ * @example
91
+ * const adapter = new GoogleCalendarAdapter()
92
+ * const result = await adapter.listEvents({
93
+ * organizationId: 'org-uuid',
94
+ * calendarId: 'primary',
95
+ * maxResults: 10
96
+ * })
97
+ */
98
+ export class GoogleCalendarAdapter implements BaseIntegrationAdapter {
99
+ readonly name = 'google-calendar'
100
+
101
+ /**
102
+ * Call Google Calendar API method
103
+ * @param method - Method name ('listEvents' | 'getEvent')
104
+ * @param params - Method parameters (must include organizationId)
105
+ * @param credentials - OAuth2 access token
106
+ * @param context - Execution context for logging and tracing
107
+ */
108
+ async call(
109
+ method: string,
110
+ params: unknown,
111
+ credentials: Record<string, unknown>,
112
+ context?: ExecutionContext
113
+ ): Promise<unknown> {
114
+ if (!this.validateCredentials(credentials)) {
115
+ throw new ToolingError('credentials_invalid', 'Invalid Google Calendar credentials', {
116
+ integration: 'google-calendar',
117
+ method
118
+ })
119
+ }
120
+ const calCreds = credentials as unknown as GoogleCalendarCredentials
121
+
122
+ const client = await this.createClient(calCreds)
123
+
124
+ switch (method) {
125
+ case 'listEvents':
126
+ return this.listEvents(client, params as ListEventsParams, context)
127
+ case 'getEvent':
128
+ return this.getEvent(client, params as GetEventParams, context)
129
+ default:
130
+ throw new ToolingError('method_not_found', `Unknown method: ${method}`, {
131
+ integration: 'google-calendar',
132
+ method
133
+ })
134
+ }
135
+ }
136
+
137
+ /**
138
+ * List events from a calendar — callable directly (no method dispatch overhead)
139
+ */
140
+ async listEvents(
141
+ clientOrParams: calendar_v3.Calendar | ListEventsParams,
142
+ paramsOrContext?: ListEventsParams | ExecutionContext,
143
+ maybeContext?: ExecutionContext
144
+ ): Promise<ListEventsResult> {
145
+ // Support both direct call (listEvents(params, context)) and
146
+ // dispatch call (listEvents(client, params, context))
147
+ let client: calendar_v3.Calendar
148
+ let params: ListEventsParams
149
+ let context: ExecutionContext | undefined
150
+
151
+ if ('calendarId' in (clientOrParams as object) || 'organizationId' in (clientOrParams as object)) {
152
+ // Called directly: listEvents(params, context?)
153
+ params = clientOrParams as ListEventsParams
154
+ context = paramsOrContext as ExecutionContext | undefined
155
+ if (!params.credentialName) {
156
+ throw new ToolingError('validation_error', 'Missing required field: credentialName', { params })
157
+ }
158
+ const creds = await this.getCredentialsForOrg(params.organizationId, params.credentialName)
159
+ client = await this.createClient(creds)
160
+ } else {
161
+ // Called via dispatch: listEvents(client, params, context?)
162
+ client = clientOrParams as calendar_v3.Calendar
163
+ params = paramsOrContext as ListEventsParams
164
+ context = maybeContext
165
+ }
166
+
167
+ return this._listEvents(client, params, context)
168
+ }
169
+
170
+ /**
171
+ * Get a single event by ID — callable directly
172
+ */
173
+ async getEvent(
174
+ clientOrParams: calendar_v3.Calendar | GetEventParams,
175
+ paramsOrContext?: GetEventParams | ExecutionContext,
176
+ maybeContext?: ExecutionContext
177
+ ): Promise<CalendarEvent> {
178
+ let client: calendar_v3.Calendar
179
+ let params: GetEventParams
180
+ let context: ExecutionContext | undefined
181
+
182
+ if ('eventId' in (clientOrParams as object) || 'organizationId' in (clientOrParams as object)) {
183
+ // Called directly: getEvent(params, context?)
184
+ params = clientOrParams as GetEventParams
185
+ context = paramsOrContext as ExecutionContext | undefined
186
+ if (!params.credentialName) {
187
+ throw new ToolingError('validation_error', 'Missing required field: credentialName', { params })
188
+ }
189
+ const creds = await this.getCredentialsForOrg(params.organizationId, params.credentialName)
190
+ client = await this.createClient(creds)
191
+ } else {
192
+ // Called via dispatch: getEvent(client, params, context?)
193
+ client = clientOrParams as calendar_v3.Calendar
194
+ params = paramsOrContext as GetEventParams
195
+ context = maybeContext
196
+ }
197
+
198
+ return this._getEvent(client, params, context)
199
+ }
200
+
201
+ /**
202
+ * Create authenticated Google Calendar client (lazy loads @googleapis/calendar SDK)
203
+ * CRITICAL: Use dynamic import to prevent OOM and module-level crashes
204
+ */
205
+ private async createClient(creds: GoogleCalendarCredentials): Promise<calendar_v3.Calendar> {
206
+ const { calendar } = await import('@googleapis/calendar')
207
+ const { OAuth2Client } = await import('google-auth-library')
208
+
209
+ // Get client credentials from environment for token refresh
210
+ const { clientId, clientSecret } = getOAuthCredentials('google-calendar')
211
+
212
+ const oauth2Client = new OAuth2Client(clientId, clientSecret)
213
+ oauth2Client.setCredentials({
214
+ access_token: creds.accessToken,
215
+ refresh_token: creds.refreshToken
216
+ })
217
+
218
+ return calendar({ version: 'v3', auth: oauth2Client })
219
+ }
220
+
221
+ /**
222
+ * Retrieve credentials for an organization from the credentials table.
223
+ * Used when the adapter is called directly (not via the dispatch pipeline).
224
+ * The caller passes the credential name explicitly -- names are user-chosen
225
+ * at OAuth-modal time, so they cannot be hardcoded.
226
+ */
227
+ private async getCredentialsForOrg(
228
+ organizationId: string,
229
+ credentialName: string
230
+ ): Promise<GoogleCalendarCredentials> {
231
+ // Dynamic import avoids pulling Supabase into browser bundles
232
+ const { createClient } = await import('@supabase/supabase-js')
233
+ const { decryptCredentialValue } = await import(
234
+ '../../../../../../../auth/multi-tenancy/credentials/server/service'
235
+ )
236
+
237
+ const supabaseUrl = process.env.SUPABASE_URL
238
+ const supabaseKey = process.env.SUPABASE_SERVICE_KEY
239
+ if (!supabaseUrl || !supabaseKey) {
240
+ throw new ToolingError('credentials_invalid', 'Missing Supabase environment variables', { organizationId })
241
+ }
242
+
243
+ const supabase = createClient(supabaseUrl, supabaseKey)
244
+ const { data, error } = await supabase
245
+ .from('credentials')
246
+ .select('encrypted_value')
247
+ .eq('organization_id', organizationId)
248
+ .eq('name', credentialName)
249
+ .single()
250
+
251
+ if (error || !data) {
252
+ throw new ToolingError('credentials_invalid', 'Google Calendar credential not found', {
253
+ organizationId,
254
+ credentialName
255
+ })
256
+ }
257
+
258
+ return decryptCredentialValue(data.encrypted_value) as unknown as GoogleCalendarCredentials
259
+ }
260
+
261
+ /**
262
+ * Internal list events implementation
263
+ */
264
+ private async _listEvents(
265
+ client: calendar_v3.Calendar,
266
+ params: ListEventsParams,
267
+ context?: ExecutionContext
268
+ ): Promise<ListEventsResult> {
269
+ if (!params.organizationId) {
270
+ throw new ToolingError('validation_error', 'Missing required field: organizationId', { params })
271
+ }
272
+
273
+ const calendarId = params.calendarId ?? 'primary'
274
+ const maxResults = Math.min(params.maxResults ?? 10, 250)
275
+ const singleEvents = params.singleEvents ?? true
276
+ const orderBy = params.orderBy ?? 'startTime'
277
+ const timeMin = params.timeMin ?? new Date().toISOString()
278
+
279
+ try {
280
+ const response = await client.events.list({
281
+ calendarId,
282
+ timeMin,
283
+ timeMax: params.timeMax,
284
+ maxResults,
285
+ singleEvents,
286
+ orderBy
287
+ })
288
+
289
+ const items = (response.data.items ?? []).map(this.transformEvent)
290
+
291
+ if (context?.logger) {
292
+ context.logger.info(
293
+ `[GoogleCalendarAdapter] Events listed: organizationId=${context.organizationId} executionId=${context.executionId} calendarId=${calendarId} count=${items.length}`
294
+ )
295
+ }
296
+
297
+ return {
298
+ items,
299
+ nextPageToken: response.data.nextPageToken ?? undefined,
300
+ summary: response.data.summary ?? undefined,
301
+ timeZone: response.data.timeZone ?? undefined
302
+ }
303
+ } catch (error: any) {
304
+ throw this.handleApiError(error, calendarId)
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Internal get event implementation
310
+ */
311
+ private async _getEvent(
312
+ client: calendar_v3.Calendar,
313
+ params: GetEventParams,
314
+ context?: ExecutionContext
315
+ ): Promise<CalendarEvent> {
316
+ if (!params.organizationId) {
317
+ throw new ToolingError('validation_error', 'Missing required field: organizationId', { params })
318
+ }
319
+ if (!params.eventId) {
320
+ throw new ToolingError('validation_error', 'Missing required field: eventId', { params })
321
+ }
322
+
323
+ const calendarId = params.calendarId ?? 'primary'
324
+
325
+ try {
326
+ const response = await client.events.get({
327
+ calendarId,
328
+ eventId: params.eventId
329
+ })
330
+
331
+ const event = this.transformEvent(response.data)
332
+
333
+ if (context?.logger) {
334
+ context.logger.info(
335
+ `[GoogleCalendarAdapter] Event retrieved: organizationId=${context.organizationId} executionId=${context.executionId} calendarId=${calendarId} eventId=${params.eventId}`
336
+ )
337
+ }
338
+
339
+ return event
340
+ } catch (error: any) {
341
+ throw this.handleApiError(error, calendarId)
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Transform a raw Google Calendar API event into the typed CalendarEvent shape
347
+ */
348
+ private transformEvent(raw: calendar_v3.Schema$Event): CalendarEvent {
349
+ return {
350
+ id: raw.id ?? '',
351
+ summary: raw.summary ?? undefined,
352
+ description: raw.description ?? undefined,
353
+ location: raw.location ?? undefined,
354
+ start: {
355
+ dateTime: raw.start?.dateTime ?? undefined,
356
+ date: raw.start?.date ?? undefined,
357
+ timeZone: raw.start?.timeZone ?? undefined
358
+ },
359
+ end: {
360
+ dateTime: raw.end?.dateTime ?? undefined,
361
+ date: raw.end?.date ?? undefined,
362
+ timeZone: raw.end?.timeZone ?? undefined
363
+ },
364
+ status: (raw.status as CalendarEvent['status']) ?? undefined,
365
+ htmlLink: raw.htmlLink ?? undefined,
366
+ created: raw.created ?? undefined,
367
+ updated: raw.updated ?? undefined,
368
+ organizer: raw.organizer
369
+ ? {
370
+ email: raw.organizer.email ?? undefined,
371
+ displayName: raw.organizer.displayName ?? undefined
372
+ }
373
+ : undefined,
374
+ attendees: raw.attendees?.map((a) => ({
375
+ email: a.email ?? undefined,
376
+ displayName: a.displayName ?? undefined,
377
+ responseStatus: a.responseStatus ?? undefined
378
+ })),
379
+ recurringEventId: raw.recurringEventId ?? undefined
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Handle Google Calendar API errors with meaningful messages
385
+ */
386
+ private handleApiError(error: any, calendarId: string): ToolingError {
387
+ const code = error.code || error.status || error.response?.status
388
+
389
+ if (code === 404) {
390
+ return new ToolingError('api_error', `Calendar or event not found: ${calendarId}`, {
391
+ calendarId,
392
+ statusCode: 404
393
+ })
394
+ }
395
+ if (code === 403) {
396
+ return new ToolingError('permission_denied', 'Access denied. Check calendar sharing permissions.', {
397
+ calendarId,
398
+ statusCode: 403
399
+ })
400
+ }
401
+ if (code === 401) {
402
+ return new ToolingError('credentials_invalid', 'Authentication expired. Token refresh required.', {
403
+ calendarId,
404
+ statusCode: 401
405
+ })
406
+ }
407
+ if (code === 429) {
408
+ return new ToolingError('rate_limit_exceeded', 'Rate limited. Retry after delay.', {
409
+ calendarId,
410
+ statusCode: 429
411
+ })
412
+ }
413
+
414
+ return new ToolingError('api_error', `Google Calendar API error: ${error.message}`, {
415
+ calendarId,
416
+ statusCode: code,
417
+ details: error
418
+ })
419
+ }
420
+
421
+ /**
422
+ * Validate credentials structure
423
+ * Required by BaseIntegrationAdapter interface
424
+ */
425
+ validateCredentials(creds: Record<string, unknown>): boolean {
426
+ return typeof creds.accessToken === 'string' && creds.accessToken.length > 0
427
+ }
428
+ }
@@ -0,0 +1,2 @@
1
+ export { GoogleCalendarAdapter } from './google-calendar-adapter'
2
+ export type { CalendarEvent, CalendarDateTime, ListEventsResult } from './google-calendar-adapter'
@@ -18,6 +18,7 @@ import type {
18
18
  ScrapingConfig,
19
19
  IcpRubric,
20
20
  PipelineConfig,
21
+ ProcessingState,
21
22
  ListTelemetry,
22
23
  DealDetail
23
24
  } from '../../../business/acquisition/types'
@@ -33,6 +34,7 @@ export type {
33
34
  ScrapingConfig,
34
35
  IcpRubric,
35
36
  PipelineConfig,
37
+ ProcessingState,
36
38
  ProcessingStageStatus,
37
39
  ListTelemetry,
38
40
  DealDetail,
@@ -95,6 +97,8 @@ export interface CreateCompanyParams {
95
97
  source?: string
96
98
  batchId?: string
97
99
  verticalResearch?: string
100
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
101
+ pipelineStatus?: unknown
98
102
  }
99
103
 
100
104
  export interface UpdateCompanyParams {
@@ -108,7 +112,9 @@ export interface UpdateCompanyParams {
108
112
  locationState?: string
109
113
  category?: string
110
114
  segment?: string
111
- pipelineStatus?: Record<string, unknown>
115
+ processingState?: ProcessingState
116
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
117
+ pipelineStatus?: unknown
112
118
  enrichmentData?: Record<string, unknown>
113
119
  source?: string
114
120
  batchId?: string
@@ -116,7 +122,7 @@ export interface UpdateCompanyParams {
116
122
  verticalResearch?: string | null
117
123
  /** Track A: flat qualification score column (null until a scoring rubric is defined) */
118
124
  qualificationScore?: number | null
119
- /** Track A: flat qualification signals jsonb — mirrors the former pipeline_status.qualification shape */
125
+ /** Track A: flat qualification signals jsonb */
120
126
  qualificationSignals?: Record<string, unknown> | null
121
127
  /** Track A: key identifying the rubric used for qualification */
122
128
  qualificationRubricKey?: string | null
@@ -132,13 +138,15 @@ export interface CompanyFilters {
132
138
  website?: string
133
139
  segment?: string
134
140
  category?: string
135
- pipelineStatus?: Record<string, unknown>
136
- /** Exclude companies whose pipeline_status contains this value (PostgREST NOT contains) */
137
- pipelineStatusNot?: Record<string, unknown>
141
+ processingState?: ProcessingState
142
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
143
+ pipelineStatus?: unknown
144
+ /** Exclude companies whose processing state contains this value (PostgREST NOT contains) */
145
+ processingStateNot?: ProcessingState
138
146
  batchId?: string
139
147
  status?: 'active' | 'invalid'
140
148
  includeAll?: boolean
141
- excludeColumns?: Array<'enrichmentData' | 'pipelineStatus'>
149
+ excludeColumns?: Array<'enrichmentData' | 'processingState'>
142
150
  limit?: number
143
151
  }
144
152
 
@@ -154,6 +162,8 @@ export interface CreateContactParams {
154
162
  source?: string
155
163
  sourceId?: string
156
164
  batchId?: string
165
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
166
+ pipelineStatus?: unknown
157
167
  }
158
168
 
159
169
  export interface UpdateContactParams {
@@ -166,7 +176,9 @@ export interface UpdateContactParams {
166
176
  headline?: string
167
177
  filterReason?: string
168
178
  openingLine?: string
169
- pipelineStatus?: Record<string, unknown>
179
+ processingState?: ProcessingState
180
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
181
+ pipelineStatus?: unknown
170
182
  enrichmentData?: Record<string, unknown>
171
183
  status?: 'active' | 'invalid'
172
184
  }
@@ -178,7 +190,9 @@ export interface ContactFilters {
178
190
  listId?: string // Filter to contacts in a specific list (via acq_list_members)
179
191
  search?: string
180
192
  openingLineIsNull?: boolean // Filter to contacts without personalization
181
- pipelineStatus?: Record<string, unknown>
193
+ processingState?: ProcessingState
194
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
195
+ pipelineStatus?: unknown
182
196
  batchId?: string
183
197
  contactStatus?: 'active' | 'invalid' // Filter by contact status (soft-delete flag)
184
198
  }
@@ -533,6 +547,20 @@ export interface UpdateContactStageParams {
533
547
  executionId?: string
534
548
  }
535
549
 
550
+ export interface ListPendingCompanyIdsParams {
551
+ organizationId: string
552
+ listId: string
553
+ stageKey: string
554
+ limit?: number
555
+ }
556
+
557
+ export interface ListPendingContactIdsParams {
558
+ organizationId: string
559
+ listId: string
560
+ stageKey: string
561
+ limit?: number
562
+ }
563
+
536
564
  export interface AddCompaniesToListParams {
537
565
  organizationId: string
538
566
  listId: string
@@ -593,7 +621,7 @@ export interface BulkImportCompanyEntry {
593
621
  category?: string
594
622
  source?: string
595
623
  enrichmentData?: Record<string, unknown>
596
- pipelineStatus?: Record<string, unknown>
624
+ processingState?: ProcessingState
597
625
  }
598
626
 
599
627
  export interface BulkImportCompaniesParams {
@@ -694,6 +722,20 @@ export interface ILeadService {
694
722
  */
695
723
  updateContactStage(params: UpdateContactStageParams): Promise<void>
696
724
 
725
+ /**
726
+ * Return the company_ids in acq_list_companies for the given list where
727
+ * processing_state[stageKey].status is NULL or NOT IN ('success', 'no_result').
728
+ * Used by build-pipeline workflows to skip already-processed records.
729
+ */
730
+ listPendingCompanyIds(params: ListPendingCompanyIdsParams): Promise<string[]>
731
+
732
+ /**
733
+ * Return the contact_ids in acq_list_members for the given list where
734
+ * processing_state[stageKey].status is NULL or NOT IN ('success', 'no_result').
735
+ * Used by build-pipeline workflows to skip already-processed records.
736
+ */
737
+ listPendingContactIds(params: ListPendingContactIdsParams): Promise<string[]>
738
+
697
739
  /**
698
740
  * Per-list execution history — reads via the feature-owned
699
741
  * acq_list_executions junction, joined to execution_logs for details.
@@ -35,11 +35,12 @@ function toCompanyOutput(result: any) {
35
35
  foundedYear: result.foundedYear,
36
36
  locationCity: result.locationCity,
37
37
  locationState: result.locationState,
38
- category: result.category,
39
- categoryPain: result.categoryPain ?? null,
40
- segment: result.segment,
41
- pipelineStatus: result.pipelineStatus,
42
- enrichmentData: result.enrichmentData,
38
+ category: result.category,
39
+ categoryPain: result.categoryPain ?? null,
40
+ segment: result.segment,
41
+ processingState: result.processingState,
42
+ pipelineStatus: null,
43
+ enrichmentData: result.enrichmentData,
43
44
  tags: result.tags,
44
45
  source: result.source,
45
46
  batchId: result.batchId ?? null,
@@ -199,7 +200,7 @@ export function createAcqCompanyGetTool(): Tool {
199
200
  export function createAcqCompanyListTool(): Tool {
200
201
  return {
201
202
  name: 'acq_company_list',
202
- description: 'List companies with optional filters for category, segment, or pipeline status.',
203
+ description: 'List companies with optional filters for category, segment, or processing state.',
203
204
  inputSchema: ListCompaniesInputSchema,
204
205
  outputSchema: CompanyArrayOutputSchema,
205
206
  execute: async ({ input, executionContext }) => {
@@ -43,11 +43,12 @@ function toContactOutput(result: any) {
43
43
  title: result.title,
44
44
  headline: result.headline,
45
45
  filterReason: result.filterReason,
46
- openingLine: result.openingLine,
47
- source: result.source,
48
- sourceId: result.sourceId,
49
- pipelineStatus: result.pipelineStatus,
50
- enrichmentData: result.enrichmentData,
46
+ openingLine: result.openingLine,
47
+ source: result.source,
48
+ sourceId: result.sourceId,
49
+ processingState: result.processingState,
50
+ pipelineStatus: null,
51
+ enrichmentData: result.enrichmentData,
51
52
  tags: result.tags,
52
53
  batchId: result.batchId ?? null,
53
54
  status: result.status ?? 'active',