@wirecat/cli-core 0.596.0 → 0.598.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/CHANGELOG.md +79 -0
- package/dist/background/deliver.d.ts +7 -1
- package/dist/background/deliver.js +66 -10
- package/dist/background/telegram-html.d.ts +2 -0
- package/dist/background/telegram-html.js +66 -0
- package/dist/calendar/cli/index.js +4 -11
- package/dist/calendar/mcp/index.d.ts +12 -12
- package/dist/cli/context.d.ts +12 -0
- package/dist/cli/context.js +17 -3
- package/dist/cli/contract-templates.js +1 -0
- package/dist/cli/early-refusal.js +7 -5
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/permission-keys.d.ts +10 -2
- package/dist/cli/preview.d.ts +3 -0
- package/dist/cli/preview.js +9 -5
- package/dist/cli/program.js +3 -0
- package/dist/crm/cli/index.js +76 -17
- package/dist/crm/index.d.ts +1 -1
- package/dist/crm/index.js +1 -1
- package/dist/crm/source.d.ts +6 -1
- package/dist/crm/sync.d.ts +5 -4
- package/dist/crm/sync.js +22 -4
- package/dist/crm/testing/index.d.ts +1 -1
- package/dist/crm/testing/index.js +1 -1
- package/dist/crm/testing/source-contract.js +28 -0
- package/dist/crm/testing/source.d.ts +8 -1
- package/dist/crm/testing/source.js +53 -9
- package/dist/crm/testing/sync-contract.js +14 -3
- package/dist/crm/writer.d.ts +11 -2
- package/dist/crm/writer.js +31 -0
- package/dist/crm/writes.d.ts +12 -2
- package/dist/crm/writes.js +93 -8
- package/dist/domain/crm.d.ts +29 -1
- package/dist/entities/deal.d.ts +16 -0
- package/dist/entities/deal.js +8 -0
- package/dist/entities/index.d.ts +12 -0
- package/dist/errors/reasons.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/kit/index.d.ts +1 -0
- package/dist/kit/index.js +1 -0
- package/dist/kit/run-scope.d.ts +34 -0
- package/dist/kit/run-scope.js +112 -0
- package/dist/mcp/app-server.js +7 -2
- package/dist/meetings/cli/index.d.ts +5 -0
- package/dist/meetings/cli/index.js +4 -3
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/store/driver.d.ts +2 -0
- package/dist/store/driver.js +2 -0
- package/dist/store/search-index.js +6 -1
- package/dist/store/sqlite/accounts.js +1 -0
- package/dist/store/sqlite/crm.d.ts +5 -1
- package/dist/store/sqlite/crm.js +52 -0
- package/dist/store/sqlite/deals.d.ts +5 -0
- package/dist/store/sqlite/deals.js +13 -0
- package/dist/store/sqlite/manifest.js +1 -0
- package/dist/store/sqlite/migrations.generated.js +6 -0
- package/dist/store/sqlite/note-index.d.ts +1 -1
- package/dist/store/sqlite/note-index.js +3 -0
- package/dist/store/sqlite/schema.d.ts +217 -0
- package/dist/store/sqlite/schema.js +18 -0
- package/package.json +2 -2
|
@@ -38,6 +38,7 @@ export declare const meetingTools: ({
|
|
|
38
38
|
max_read_bytes?: undefined;
|
|
39
39
|
cursor?: undefined;
|
|
40
40
|
} | {
|
|
41
|
+
limit?: undefined;
|
|
41
42
|
event_id?: undefined;
|
|
42
43
|
series_id?: undefined;
|
|
43
44
|
page?: undefined;
|
|
@@ -53,11 +54,11 @@ export declare const meetingTools: ({
|
|
|
53
54
|
format?: undefined;
|
|
54
55
|
transcript_id?: undefined;
|
|
55
56
|
query?: undefined;
|
|
56
|
-
limit?: undefined;
|
|
57
57
|
scan_limit?: undefined;
|
|
58
58
|
max_read_bytes?: undefined;
|
|
59
59
|
cursor?: undefined;
|
|
60
60
|
} | {
|
|
61
|
+
limit?: undefined;
|
|
61
62
|
event_id?: undefined;
|
|
62
63
|
series_id?: undefined;
|
|
63
64
|
page?: undefined;
|
|
@@ -75,11 +76,11 @@ export declare const meetingTools: ({
|
|
|
75
76
|
format?: undefined;
|
|
76
77
|
transcript_id?: undefined;
|
|
77
78
|
query?: undefined;
|
|
78
|
-
limit?: undefined;
|
|
79
79
|
scan_limit?: undefined;
|
|
80
80
|
max_read_bytes?: undefined;
|
|
81
81
|
cursor?: undefined;
|
|
82
82
|
} | {
|
|
83
|
+
limit?: undefined;
|
|
83
84
|
event_id?: undefined;
|
|
84
85
|
series_id?: undefined;
|
|
85
86
|
page?: undefined;
|
|
@@ -101,7 +102,6 @@ export declare const meetingTools: ({
|
|
|
101
102
|
minimum: number;
|
|
102
103
|
};
|
|
103
104
|
query?: undefined;
|
|
104
|
-
limit?: undefined;
|
|
105
105
|
scan_limit?: undefined;
|
|
106
106
|
max_read_bytes?: undefined;
|
|
107
107
|
cursor?: undefined;
|
|
@@ -188,6 +188,7 @@ export declare const meetingTools: ({
|
|
|
188
188
|
max_read_bytes?: undefined;
|
|
189
189
|
cursor?: undefined;
|
|
190
190
|
} | {
|
|
191
|
+
limit?: undefined;
|
|
191
192
|
event_id?: undefined;
|
|
192
193
|
series_id?: undefined;
|
|
193
194
|
page?: undefined;
|
|
@@ -203,11 +204,11 @@ export declare const meetingTools: ({
|
|
|
203
204
|
format?: undefined;
|
|
204
205
|
transcript_id?: undefined;
|
|
205
206
|
query?: undefined;
|
|
206
|
-
limit?: undefined;
|
|
207
207
|
scan_limit?: undefined;
|
|
208
208
|
max_read_bytes?: undefined;
|
|
209
209
|
cursor?: undefined;
|
|
210
210
|
} | {
|
|
211
|
+
limit?: undefined;
|
|
211
212
|
event_id?: undefined;
|
|
212
213
|
series_id?: undefined;
|
|
213
214
|
page?: undefined;
|
|
@@ -225,11 +226,11 @@ export declare const meetingTools: ({
|
|
|
225
226
|
format?: undefined;
|
|
226
227
|
transcript_id?: undefined;
|
|
227
228
|
query?: undefined;
|
|
228
|
-
limit?: undefined;
|
|
229
229
|
scan_limit?: undefined;
|
|
230
230
|
max_read_bytes?: undefined;
|
|
231
231
|
cursor?: undefined;
|
|
232
232
|
} | {
|
|
233
|
+
limit?: undefined;
|
|
233
234
|
event_id?: undefined;
|
|
234
235
|
series_id?: undefined;
|
|
235
236
|
page?: undefined;
|
|
@@ -251,7 +252,6 @@ export declare const meetingTools: ({
|
|
|
251
252
|
minimum: number;
|
|
252
253
|
};
|
|
253
254
|
query?: undefined;
|
|
254
|
-
limit?: undefined;
|
|
255
255
|
scan_limit?: undefined;
|
|
256
256
|
max_read_bytes?: undefined;
|
|
257
257
|
cursor?: undefined;
|
|
@@ -338,6 +338,7 @@ export declare const meetingTools: ({
|
|
|
338
338
|
max_read_bytes?: undefined;
|
|
339
339
|
cursor?: undefined;
|
|
340
340
|
} | {
|
|
341
|
+
limit?: undefined;
|
|
341
342
|
event_id?: undefined;
|
|
342
343
|
series_id?: undefined;
|
|
343
344
|
page?: undefined;
|
|
@@ -353,11 +354,11 @@ export declare const meetingTools: ({
|
|
|
353
354
|
format?: undefined;
|
|
354
355
|
transcript_id?: undefined;
|
|
355
356
|
query?: undefined;
|
|
356
|
-
limit?: undefined;
|
|
357
357
|
scan_limit?: undefined;
|
|
358
358
|
max_read_bytes?: undefined;
|
|
359
359
|
cursor?: undefined;
|
|
360
360
|
} | {
|
|
361
|
+
limit?: undefined;
|
|
361
362
|
event_id?: undefined;
|
|
362
363
|
series_id?: undefined;
|
|
363
364
|
page?: undefined;
|
|
@@ -375,11 +376,11 @@ export declare const meetingTools: ({
|
|
|
375
376
|
format?: undefined;
|
|
376
377
|
transcript_id?: undefined;
|
|
377
378
|
query?: undefined;
|
|
378
|
-
limit?: undefined;
|
|
379
379
|
scan_limit?: undefined;
|
|
380
380
|
max_read_bytes?: undefined;
|
|
381
381
|
cursor?: undefined;
|
|
382
382
|
} | {
|
|
383
|
+
limit?: undefined;
|
|
383
384
|
event_id?: undefined;
|
|
384
385
|
series_id?: undefined;
|
|
385
386
|
page?: undefined;
|
|
@@ -401,7 +402,6 @@ export declare const meetingTools: ({
|
|
|
401
402
|
minimum: number;
|
|
402
403
|
};
|
|
403
404
|
query?: undefined;
|
|
404
|
-
limit?: undefined;
|
|
405
405
|
scan_limit?: undefined;
|
|
406
406
|
max_read_bytes?: undefined;
|
|
407
407
|
cursor?: undefined;
|
|
@@ -488,6 +488,7 @@ export declare const meetingTools: ({
|
|
|
488
488
|
max_read_bytes?: undefined;
|
|
489
489
|
cursor?: undefined;
|
|
490
490
|
} | {
|
|
491
|
+
limit?: undefined;
|
|
491
492
|
event_id?: undefined;
|
|
492
493
|
series_id?: undefined;
|
|
493
494
|
page?: undefined;
|
|
@@ -503,11 +504,11 @@ export declare const meetingTools: ({
|
|
|
503
504
|
format?: undefined;
|
|
504
505
|
transcript_id?: undefined;
|
|
505
506
|
query?: undefined;
|
|
506
|
-
limit?: undefined;
|
|
507
507
|
scan_limit?: undefined;
|
|
508
508
|
max_read_bytes?: undefined;
|
|
509
509
|
cursor?: undefined;
|
|
510
510
|
} | {
|
|
511
|
+
limit?: undefined;
|
|
511
512
|
event_id?: undefined;
|
|
512
513
|
series_id?: undefined;
|
|
513
514
|
page?: undefined;
|
|
@@ -525,11 +526,11 @@ export declare const meetingTools: ({
|
|
|
525
526
|
format?: undefined;
|
|
526
527
|
transcript_id?: undefined;
|
|
527
528
|
query?: undefined;
|
|
528
|
-
limit?: undefined;
|
|
529
529
|
scan_limit?: undefined;
|
|
530
530
|
max_read_bytes?: undefined;
|
|
531
531
|
cursor?: undefined;
|
|
532
532
|
} | {
|
|
533
|
+
limit?: undefined;
|
|
533
534
|
event_id?: undefined;
|
|
534
535
|
series_id?: undefined;
|
|
535
536
|
page?: undefined;
|
|
@@ -551,7 +552,6 @@ export declare const meetingTools: ({
|
|
|
551
552
|
minimum: number;
|
|
552
553
|
};
|
|
553
554
|
query?: undefined;
|
|
554
|
-
limit?: undefined;
|
|
555
555
|
scan_limit?: undefined;
|
|
556
556
|
max_read_bytes?: undefined;
|
|
557
557
|
cursor?: undefined;
|
|
@@ -638,6 +638,7 @@ export declare const meetingTools: ({
|
|
|
638
638
|
max_read_bytes?: undefined;
|
|
639
639
|
cursor?: undefined;
|
|
640
640
|
} | {
|
|
641
|
+
limit?: undefined;
|
|
641
642
|
event_id?: undefined;
|
|
642
643
|
series_id?: undefined;
|
|
643
644
|
page?: undefined;
|
|
@@ -653,11 +654,11 @@ export declare const meetingTools: ({
|
|
|
653
654
|
format?: undefined;
|
|
654
655
|
transcript_id?: undefined;
|
|
655
656
|
query?: undefined;
|
|
656
|
-
limit?: undefined;
|
|
657
657
|
scan_limit?: undefined;
|
|
658
658
|
max_read_bytes?: undefined;
|
|
659
659
|
cursor?: undefined;
|
|
660
660
|
} | {
|
|
661
|
+
limit?: undefined;
|
|
661
662
|
event_id?: undefined;
|
|
662
663
|
series_id?: undefined;
|
|
663
664
|
page?: undefined;
|
|
@@ -675,11 +676,11 @@ export declare const meetingTools: ({
|
|
|
675
676
|
format?: undefined;
|
|
676
677
|
transcript_id?: undefined;
|
|
677
678
|
query?: undefined;
|
|
678
|
-
limit?: undefined;
|
|
679
679
|
scan_limit?: undefined;
|
|
680
680
|
max_read_bytes?: undefined;
|
|
681
681
|
cursor?: undefined;
|
|
682
682
|
} | {
|
|
683
|
+
limit?: undefined;
|
|
683
684
|
event_id?: undefined;
|
|
684
685
|
series_id?: undefined;
|
|
685
686
|
page?: undefined;
|
|
@@ -701,7 +702,6 @@ export declare const meetingTools: ({
|
|
|
701
702
|
minimum: number;
|
|
702
703
|
};
|
|
703
704
|
query?: undefined;
|
|
704
|
-
limit?: undefined;
|
|
705
705
|
scan_limit?: undefined;
|
|
706
706
|
max_read_bytes?: undefined;
|
|
707
707
|
cursor?: undefined;
|
package/dist/store/driver.d.ts
CHANGED
|
@@ -46,3 +46,5 @@ export type OpenDatabase = (path: string) => CacheDatabase;
|
|
|
46
46
|
* `foreign_keys` is off by default in SQLite, which surprises everyone once.
|
|
47
47
|
*/
|
|
48
48
|
export declare const PRAGMAS = "\n PRAGMA busy_timeout = 5000;\n PRAGMA journal_mode = WAL;\n PRAGMA foreign_keys = ON;\n";
|
|
49
|
+
/** A read-only open sets `query_only`: a search over it reads what is indexed and leaves building it to a writable open. */
|
|
50
|
+
export declare const isQueryOnly: (database: CacheDatabase) => boolean;
|
package/dist/store/driver.js
CHANGED
|
@@ -36,3 +36,5 @@ export const PRAGMAS = `
|
|
|
36
36
|
PRAGMA journal_mode = WAL;
|
|
37
37
|
PRAGMA foreign_keys = ON;
|
|
38
38
|
`;
|
|
39
|
+
/** A read-only open sets `query_only`: a search over it reads what is indexed and leaves building it to a writable open. */
|
|
40
|
+
export const isQueryOnly = (database) => Number(Object.values(database.prepare("PRAGMA query_only").get() ?? {})[0]) === 1;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isQueryOnly } from "./driver.js";
|
|
1
2
|
import { CORPORA, corpusIndexState, drainCorpus } from "./sqlite/note-index.js";
|
|
2
3
|
import * as searchIndex from "./sqlite/search-index.js";
|
|
3
4
|
import * as stems from "./sqlite/stems.js";
|
|
@@ -11,9 +12,13 @@ export const searchIndexStoreOver = (context) => {
|
|
|
11
12
|
knownTerms: async (terms) => words.knownTerms(context, terms),
|
|
12
13
|
termCandidates: async (trigrams, lengths) => words.termCandidates(context, trigrams, lengths),
|
|
13
14
|
searchIndexState: async () => searchIndex.searchIndexState(database),
|
|
14
|
-
fillSearchIndex: async ({ until } = {}) =>
|
|
15
|
+
fillSearchIndex: async ({ until } = {}) => isQueryOnly(database)
|
|
16
|
+
? { normalized: 0, indexed: 0, terms: 0 }
|
|
17
|
+
: searchIndex.fillSearchIndex(database, { now: context.now, ...(until ? { until } : {}) }),
|
|
15
18
|
stemsState: async () => stems.stemsState(database),
|
|
16
19
|
fillStems: async ({ until } = {}) => {
|
|
20
|
+
if (isQueryOnly(database))
|
|
21
|
+
return { stemmed: 0, drained: 0 };
|
|
17
22
|
const filled = fillStems(database, { now: context.now, ...(until ? { until } : {}) });
|
|
18
23
|
// Read first: every search asks, and an idle drain must not take the write lock.
|
|
19
24
|
const transcripts = corpusIndexState(database, CORPORA.transcript);
|
|
@@ -114,6 +114,7 @@ export const purgeAccount = ({ orm, database }, accountKey) => {
|
|
|
114
114
|
// The inbox project and its tasks are the owner's; only the tie to the account goes.
|
|
115
115
|
orm.update(projects).set({ accountId: null }).where(eq(projects.accountId, accountKey)).run();
|
|
116
116
|
orm.run(sql `DELETE FROM files WHERE account_id = ${accountKey}`);
|
|
117
|
+
orm.run(sql `DELETE FROM crm_fields WHERE account_id = ${accountKey}`);
|
|
117
118
|
// An entity made from a record is the owner's and stays; only what the source said goes.
|
|
118
119
|
const recordsOf = sql `(SELECT id FROM records WHERE account_id = ${accountKey})`;
|
|
119
120
|
orm.run(sql `DELETE FROM record_relations WHERE record_id IN ${recordsOf} OR target_id IN ${recordsOf}`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CrmItem, CrmKind, CrmOrganization, CrmPerson, CrmPipeline } from "../../domain/crm.js";
|
|
1
|
+
import type { CrmField, CrmItem, CrmKind, CrmOrganization, CrmPerson, CrmPipeline } from "../../domain/crm.js";
|
|
2
2
|
import { type DealKind } from "../../domain/deals.js";
|
|
3
3
|
import type { AccountPeople } from "../people.js";
|
|
4
4
|
import type { AccountKey } from "../types.js";
|
|
@@ -27,6 +27,10 @@ export interface CrmCopies {
|
|
|
27
27
|
savePeople(key: AccountKey, people: CrmPerson[]): Promise<CrmSaved>;
|
|
28
28
|
/** Companies, each an organization of its own made on first sight, kept apart from any of the same name. */
|
|
29
29
|
saveOrganizations(key: AccountKey, organizations: CrmOrganization[]): Promise<CrmSaved>;
|
|
30
|
+
/** One kind's whole list of the CRM's own fields: one it lacks is marked deleted, one listed again comes back. */
|
|
31
|
+
saveFields(key: AccountKey, kind: CrmKind, fields: CrmField[]): Promise<CrmSaved>;
|
|
32
|
+
/** The kind's fields as last synced, in the CRM's order; none before the first sync. */
|
|
33
|
+
fields(key: AccountKey, kind: CrmKind): Promise<CrmField[]>;
|
|
30
34
|
/** Marks deleted what the CRM said it deleted. Contacts stay: an identity has no deleted mark. Answers how many. */
|
|
31
35
|
remove(key: AccountKey, kind: CrmKind, externalIds: string[]): Promise<number>;
|
|
32
36
|
/**
|
package/dist/store/sqlite/crm.js
CHANGED
|
@@ -114,6 +114,55 @@ export const crmCopiesOver = (context, people) => {
|
|
|
114
114
|
.run(pipelineId, stage.name, stage.folded, stage.position, stage.outcome, stage.externalId, at, at);
|
|
115
115
|
}
|
|
116
116
|
};
|
|
117
|
+
// Unlike pipelines, an empty list does mark every field deleted: a CRM with no custom fields is an ordinary answer.
|
|
118
|
+
const saveFields = (key, kind, fields) => write(database, () => {
|
|
119
|
+
const accountId = account(key);
|
|
120
|
+
const at = now();
|
|
121
|
+
const skipped = [];
|
|
122
|
+
const listed = new Set();
|
|
123
|
+
const upsert = database.prepare("INSERT INTO crm_fields (account_id, kind, code, label, type, multiple, filterable, options, position, " +
|
|
124
|
+
"created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (account_id, kind, code) DO " +
|
|
125
|
+
"UPDATE SET label = excluded.label, type = excluded.type, multiple = excluded.multiple, " +
|
|
126
|
+
"filterable = excluded.filterable, options = excluded.options, position = excluded.position, " +
|
|
127
|
+
"updated_at = excluded.updated_at, deleted_at = NULL");
|
|
128
|
+
for (const field of fields) {
|
|
129
|
+
const code = typeof field.code === "string" ? field.code.trim() : "";
|
|
130
|
+
if (!code || listed.has(code)) {
|
|
131
|
+
skipped.push({
|
|
132
|
+
externalId: code,
|
|
133
|
+
reason: "crm.item-invalid",
|
|
134
|
+
message: code ? `${kind} field ${code} is listed twice` : `a ${kind} field has no code`,
|
|
135
|
+
});
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
listed.add(code);
|
|
139
|
+
const options = field.options?.map((option) => ({ id: String(option.id), label: String(option.label) }));
|
|
140
|
+
upsert.run(accountId, kind, code, textOf(field.label, 200), textOf(field.type, 80) ?? "string", field.multiple ? 1 : 0, field.filterable ? 1 : 0, options ? JSON.stringify(options) : null, listed.size, at, at);
|
|
141
|
+
}
|
|
142
|
+
const mark = database.prepare("UPDATE crm_fields SET deleted_at = ?, updated_at = ? WHERE id = ?");
|
|
143
|
+
for (const row of database
|
|
144
|
+
.prepare("SELECT id, code FROM crm_fields WHERE account_id = ? AND kind = ? AND deleted_at IS NULL")
|
|
145
|
+
.all(accountId, kind))
|
|
146
|
+
if (!listed.has(String(row.code)))
|
|
147
|
+
mark.run(at, at, Number(row.id));
|
|
148
|
+
return { saved: listed.size, skipped };
|
|
149
|
+
});
|
|
150
|
+
const fieldsOf = (key, kind) => {
|
|
151
|
+
const accountId = findAccountPk(context, key);
|
|
152
|
+
if (accountId === undefined)
|
|
153
|
+
return [];
|
|
154
|
+
return database
|
|
155
|
+
.prepare("SELECT * FROM crm_fields WHERE account_id = ? AND kind = ? AND deleted_at IS NULL ORDER BY position, id")
|
|
156
|
+
.all(accountId, kind)
|
|
157
|
+
.map((row) => ({
|
|
158
|
+
code: String(row.code),
|
|
159
|
+
label: row.label == null ? null : String(row.label),
|
|
160
|
+
type: String(row.type),
|
|
161
|
+
multiple: Number(row.multiple) === 1,
|
|
162
|
+
filterable: Number(row.filterable) === 1,
|
|
163
|
+
...(row.options == null ? {} : { options: JSON.parse(String(row.options)) }),
|
|
164
|
+
}));
|
|
165
|
+
};
|
|
117
166
|
const personIdOf = (key, externalId) => {
|
|
118
167
|
const row = database
|
|
119
168
|
.prepare("SELECT l.person_id FROM identities i JOIN identity_links l ON l.identity_id = i.id " +
|
|
@@ -158,6 +207,7 @@ export const crmCopiesOver = (context, people) => {
|
|
|
158
207
|
const metadata = {
|
|
159
208
|
...(item.metadata ?? {}),
|
|
160
209
|
...(item.url ? { url: item.url } : {}),
|
|
210
|
+
...(item.assignee ? { assignee: crmIdentityId(key, item.assignee, "user") } : {}),
|
|
161
211
|
...(item.closedReason && status !== "open" ? { closed_reason: textOf(item.closedReason, 2000) } : {}),
|
|
162
212
|
};
|
|
163
213
|
const values = {
|
|
@@ -304,6 +354,8 @@ export const crmCopiesOver = (context, people) => {
|
|
|
304
354
|
return { saved: contacts.length, skipped: [] };
|
|
305
355
|
},
|
|
306
356
|
saveOrganizations: async (key, organizations) => saveOrganizations(key, organizations),
|
|
357
|
+
saveFields: async (key, kind, fields) => saveFields(key, kind, fields),
|
|
358
|
+
fields: async (key, kind) => fieldsOf(key, kind),
|
|
307
359
|
remove: async (key, kind, externalIds) => write(database, () => {
|
|
308
360
|
const gone = new Set(externalIds);
|
|
309
361
|
return markDeleted(kind, stored(key, kind)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CrmValue } from "../../domain/crm.js";
|
|
1
2
|
import { type DealKind, type DealRole, type DealStatus, type StageOutcome } from "../../domain/deals.js";
|
|
2
3
|
import type { AccountKey } from "../types.js";
|
|
3
4
|
import { type ProposedByFilter } from "./actors.js";
|
|
@@ -77,6 +78,10 @@ export interface Deal {
|
|
|
77
78
|
account: string;
|
|
78
79
|
} | null;
|
|
79
80
|
externalId: string | null;
|
|
81
|
+
/** A CRM copy's assignee, as the identity `<account>/user/<id>`. */
|
|
82
|
+
assignee: string | null;
|
|
83
|
+
/** A CRM copy's own fields by code, as the CRM labels them; empty on the owner's own. */
|
|
84
|
+
providerMetadata: Record<string, CrmValue>;
|
|
80
85
|
people: DealPerson[];
|
|
81
86
|
author: {
|
|
82
87
|
type: "person" | "bot";
|
|
@@ -27,6 +27,17 @@ const jsonField = (metadata, key) => {
|
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
|
+
const isValue = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && "label" in value && "value" in value;
|
|
31
|
+
const providerMetadataOf = (metadata) => {
|
|
32
|
+
if (metadata == null)
|
|
33
|
+
return {};
|
|
34
|
+
try {
|
|
35
|
+
return Object.fromEntries(Object.entries(JSON.parse(String(metadata))).filter(([, value]) => isValue(value)));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
};
|
|
30
41
|
const accountOf = (provider, external) => provider == null ? null : { provider: String(provider), account: String(external) };
|
|
31
42
|
export const dealOperationsOver = (context) => {
|
|
32
43
|
const { database, now } = context;
|
|
@@ -68,6 +79,8 @@ export const dealOperationsOver = (context) => {
|
|
|
68
79
|
deal: row.deal_id == null ? null : `deal:${row.deal_id}`,
|
|
69
80
|
account: accountOf(row.account_provider, row.account_external_id),
|
|
70
81
|
externalId: row.external_id == null ? null : String(row.external_id),
|
|
82
|
+
assignee: jsonField(row.metadata, "assignee"),
|
|
83
|
+
providerMetadata: providerMetadataOf(row.metadata),
|
|
71
84
|
people: peopleOf(kind, fields.id),
|
|
72
85
|
author: authorOf(row),
|
|
73
86
|
model: row.model == null ? null : String(row.model),
|
|
@@ -110,6 +110,7 @@ export const MANIFEST = [
|
|
|
110
110
|
{ name: "20261106010504_version-52-deals", version: 52, minCompatible: 34 },
|
|
111
111
|
{ name: "20261106010507_version-52-deals-rules", version: 52, minCompatible: 34 },
|
|
112
112
|
{ name: "20261106165009_version-53-crm-pipelines", version: 53, minCompatible: 34 },
|
|
113
|
+
{ name: "20261106225547_version-54-crm-fields", version: 54, minCompatible: 34 },
|
|
113
114
|
];
|
|
114
115
|
export const generatedMigrations = (manifest = MANIFEST, generated = GENERATED) => {
|
|
115
116
|
const migrations = [];
|
|
@@ -1042,5 +1042,11 @@ export const GENERATED = [
|
|
|
1042
1042
|
"DROP INDEX IF EXISTS `pipelines_by_account_id_external_id`;",
|
|
1043
1043
|
"CREATE UNIQUE INDEX `pipelines_by_account_id_kind_external_id` ON `pipelines` (`account_id`,`kind`,`external_id`) WHERE account_id IS NOT NULL AND external_id IS NOT NULL;"
|
|
1044
1044
|
]
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
"name": "20261106225547_version-54-crm-fields",
|
|
1048
|
+
"statements": [
|
|
1049
|
+
"CREATE TABLE `crm_fields` (\n\t`id` integer PRIMARY KEY,\n\t`account_id` integer NOT NULL,\n\t`kind` text NOT NULL,\n\t`code` text NOT NULL,\n\t`label` text,\n\t`type` text NOT NULL,\n\t`multiple` integer NOT NULL,\n\t`filterable` integer NOT NULL,\n\t`options` text,\n\t`position` integer NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`updated_at` integer NOT NULL,\n\t`deleted_at` integer,\n\tCONSTRAINT `fk_crm_fields_account_id_accounts_id_fk` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`),\n\tCONSTRAINT `crm_fields_account_id_kind_code_unique` UNIQUE(`account_id`,`kind`,`code`)\n);"
|
|
1050
|
+
]
|
|
1045
1051
|
}
|
|
1046
1052
|
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Stemmer, type Stemmers } from "../../search/stem.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type CacheDatabase } from "../driver.js";
|
|
3
3
|
import type { Where } from "./query-filter.js";
|
|
4
4
|
export interface NoteIndexState {
|
|
5
5
|
/** Rows written since they were indexed. */
|
|
@@ -2,6 +2,7 @@ import { CliError } from "@wirecat/cli-base";
|
|
|
2
2
|
import { CHUNK_CHARS, chunkHash, splitSections, splitText } from "../../conversations/chunks.js";
|
|
3
3
|
import { analyzerIdentity, DEFAULT_STEMMERS } from "../../search/stem.js";
|
|
4
4
|
import { taskStemsOf, taskWordsOf } from "../../search/task-similarity.js";
|
|
5
|
+
import { isQueryOnly } from "../driver.js";
|
|
5
6
|
import { normalize } from "../normalize.js";
|
|
6
7
|
import { dropDocumentPicture } from "./document-pictures.js";
|
|
7
8
|
import { int } from "./events.js";
|
|
@@ -248,6 +249,8 @@ const writer = (database, corpus, stemmer) => {
|
|
|
248
249
|
* small, so a change of stemmer choices simply queues every row again instead of waiting for `store rebuild`.
|
|
249
250
|
*/
|
|
250
251
|
export const drainCorpus = (database, corpus, stemmerFor, { batch = 200, until = () => false } = {}) => {
|
|
252
|
+
if (isQueryOnly(database))
|
|
253
|
+
return 0;
|
|
251
254
|
// Pieces written before they carried their account get it, once: purge and vector search filter by it.
|
|
252
255
|
if (corpus.type === "email" || corpus.type === "document")
|
|
253
256
|
database
|
|
@@ -16542,6 +16542,223 @@ export declare const pipelineStageChanges: import("drizzle-orm/sqlite-core").SQL
|
|
|
16542
16542
|
dialect: 'sqlite';
|
|
16543
16543
|
isAlias: false;
|
|
16544
16544
|
}>;
|
|
16545
|
+
/** A CRM account's own fields on one kind; their values stay in each row's `metadata`, keyed by code. */
|
|
16546
|
+
export declare const crmFields: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
16547
|
+
name: "crm_fields";
|
|
16548
|
+
schema: undefined;
|
|
16549
|
+
columns: {
|
|
16550
|
+
id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16551
|
+
name: string;
|
|
16552
|
+
tableName: "crm_fields";
|
|
16553
|
+
dataType: "number int53";
|
|
16554
|
+
data: number;
|
|
16555
|
+
driverParam: number;
|
|
16556
|
+
notNull: true;
|
|
16557
|
+
hasDefault: true;
|
|
16558
|
+
isPrimaryKey: true;
|
|
16559
|
+
isAutoincrement: false;
|
|
16560
|
+
hasRuntimeDefault: false;
|
|
16561
|
+
enumValues: undefined;
|
|
16562
|
+
baseColumn: never;
|
|
16563
|
+
identity: undefined;
|
|
16564
|
+
generated: undefined;
|
|
16565
|
+
}, {}>;
|
|
16566
|
+
accountId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16567
|
+
name: string;
|
|
16568
|
+
tableName: "crm_fields";
|
|
16569
|
+
dataType: "number int53";
|
|
16570
|
+
data: number;
|
|
16571
|
+
driverParam: number;
|
|
16572
|
+
notNull: true;
|
|
16573
|
+
hasDefault: false;
|
|
16574
|
+
isPrimaryKey: false;
|
|
16575
|
+
isAutoincrement: false;
|
|
16576
|
+
hasRuntimeDefault: false;
|
|
16577
|
+
enumValues: undefined;
|
|
16578
|
+
baseColumn: never;
|
|
16579
|
+
identity: undefined;
|
|
16580
|
+
generated: undefined;
|
|
16581
|
+
}, {}>;
|
|
16582
|
+
kind: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16583
|
+
name: string;
|
|
16584
|
+
tableName: "crm_fields";
|
|
16585
|
+
dataType: "string";
|
|
16586
|
+
data: string;
|
|
16587
|
+
driverParam: string;
|
|
16588
|
+
notNull: true;
|
|
16589
|
+
hasDefault: false;
|
|
16590
|
+
isPrimaryKey: false;
|
|
16591
|
+
isAutoincrement: false;
|
|
16592
|
+
hasRuntimeDefault: false;
|
|
16593
|
+
enumValues: [string, ...string[]];
|
|
16594
|
+
baseColumn: never;
|
|
16595
|
+
identity: undefined;
|
|
16596
|
+
generated: undefined;
|
|
16597
|
+
}, {}>;
|
|
16598
|
+
code: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16599
|
+
name: string;
|
|
16600
|
+
tableName: "crm_fields";
|
|
16601
|
+
dataType: "string";
|
|
16602
|
+
data: string;
|
|
16603
|
+
driverParam: string;
|
|
16604
|
+
notNull: true;
|
|
16605
|
+
hasDefault: false;
|
|
16606
|
+
isPrimaryKey: false;
|
|
16607
|
+
isAutoincrement: false;
|
|
16608
|
+
hasRuntimeDefault: false;
|
|
16609
|
+
enumValues: [string, ...string[]];
|
|
16610
|
+
baseColumn: never;
|
|
16611
|
+
identity: undefined;
|
|
16612
|
+
generated: undefined;
|
|
16613
|
+
}, {}>;
|
|
16614
|
+
label: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16615
|
+
name: string;
|
|
16616
|
+
tableName: "crm_fields";
|
|
16617
|
+
dataType: "string";
|
|
16618
|
+
data: string;
|
|
16619
|
+
driverParam: string;
|
|
16620
|
+
notNull: false;
|
|
16621
|
+
hasDefault: false;
|
|
16622
|
+
isPrimaryKey: false;
|
|
16623
|
+
isAutoincrement: false;
|
|
16624
|
+
hasRuntimeDefault: false;
|
|
16625
|
+
enumValues: [string, ...string[]];
|
|
16626
|
+
baseColumn: never;
|
|
16627
|
+
identity: undefined;
|
|
16628
|
+
generated: undefined;
|
|
16629
|
+
}, {}>;
|
|
16630
|
+
type: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16631
|
+
name: string;
|
|
16632
|
+
tableName: "crm_fields";
|
|
16633
|
+
dataType: "string";
|
|
16634
|
+
data: string;
|
|
16635
|
+
driverParam: string;
|
|
16636
|
+
notNull: true;
|
|
16637
|
+
hasDefault: false;
|
|
16638
|
+
isPrimaryKey: false;
|
|
16639
|
+
isAutoincrement: false;
|
|
16640
|
+
hasRuntimeDefault: false;
|
|
16641
|
+
enumValues: [string, ...string[]];
|
|
16642
|
+
baseColumn: never;
|
|
16643
|
+
identity: undefined;
|
|
16644
|
+
generated: undefined;
|
|
16645
|
+
}, {}>;
|
|
16646
|
+
multiple: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16647
|
+
name: string;
|
|
16648
|
+
tableName: "crm_fields";
|
|
16649
|
+
dataType: "number int53";
|
|
16650
|
+
data: number;
|
|
16651
|
+
driverParam: number;
|
|
16652
|
+
notNull: true;
|
|
16653
|
+
hasDefault: false;
|
|
16654
|
+
isPrimaryKey: false;
|
|
16655
|
+
isAutoincrement: false;
|
|
16656
|
+
hasRuntimeDefault: false;
|
|
16657
|
+
enumValues: undefined;
|
|
16658
|
+
baseColumn: never;
|
|
16659
|
+
identity: undefined;
|
|
16660
|
+
generated: undefined;
|
|
16661
|
+
}, {}>;
|
|
16662
|
+
filterable: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16663
|
+
name: string;
|
|
16664
|
+
tableName: "crm_fields";
|
|
16665
|
+
dataType: "number int53";
|
|
16666
|
+
data: number;
|
|
16667
|
+
driverParam: number;
|
|
16668
|
+
notNull: true;
|
|
16669
|
+
hasDefault: false;
|
|
16670
|
+
isPrimaryKey: false;
|
|
16671
|
+
isAutoincrement: false;
|
|
16672
|
+
hasRuntimeDefault: false;
|
|
16673
|
+
enumValues: undefined;
|
|
16674
|
+
baseColumn: never;
|
|
16675
|
+
identity: undefined;
|
|
16676
|
+
generated: undefined;
|
|
16677
|
+
}, {}>;
|
|
16678
|
+
options: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16679
|
+
name: string;
|
|
16680
|
+
tableName: "crm_fields";
|
|
16681
|
+
dataType: "string";
|
|
16682
|
+
data: string;
|
|
16683
|
+
driverParam: string;
|
|
16684
|
+
notNull: false;
|
|
16685
|
+
hasDefault: false;
|
|
16686
|
+
isPrimaryKey: false;
|
|
16687
|
+
isAutoincrement: false;
|
|
16688
|
+
hasRuntimeDefault: false;
|
|
16689
|
+
enumValues: [string, ...string[]];
|
|
16690
|
+
baseColumn: never;
|
|
16691
|
+
identity: undefined;
|
|
16692
|
+
generated: undefined;
|
|
16693
|
+
}, {}>;
|
|
16694
|
+
position: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16695
|
+
name: string;
|
|
16696
|
+
tableName: "crm_fields";
|
|
16697
|
+
dataType: "number int53";
|
|
16698
|
+
data: number;
|
|
16699
|
+
driverParam: number;
|
|
16700
|
+
notNull: true;
|
|
16701
|
+
hasDefault: false;
|
|
16702
|
+
isPrimaryKey: false;
|
|
16703
|
+
isAutoincrement: false;
|
|
16704
|
+
hasRuntimeDefault: false;
|
|
16705
|
+
enumValues: undefined;
|
|
16706
|
+
baseColumn: never;
|
|
16707
|
+
identity: undefined;
|
|
16708
|
+
generated: undefined;
|
|
16709
|
+
}, {}>;
|
|
16710
|
+
createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16711
|
+
name: string;
|
|
16712
|
+
tableName: "crm_fields";
|
|
16713
|
+
dataType: "number int53";
|
|
16714
|
+
data: number;
|
|
16715
|
+
driverParam: number;
|
|
16716
|
+
notNull: true;
|
|
16717
|
+
hasDefault: false;
|
|
16718
|
+
isPrimaryKey: false;
|
|
16719
|
+
isAutoincrement: false;
|
|
16720
|
+
hasRuntimeDefault: false;
|
|
16721
|
+
enumValues: undefined;
|
|
16722
|
+
baseColumn: never;
|
|
16723
|
+
identity: undefined;
|
|
16724
|
+
generated: undefined;
|
|
16725
|
+
}, {}>;
|
|
16726
|
+
updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16727
|
+
name: string;
|
|
16728
|
+
tableName: "crm_fields";
|
|
16729
|
+
dataType: "number int53";
|
|
16730
|
+
data: number;
|
|
16731
|
+
driverParam: number;
|
|
16732
|
+
notNull: true;
|
|
16733
|
+
hasDefault: false;
|
|
16734
|
+
isPrimaryKey: false;
|
|
16735
|
+
isAutoincrement: false;
|
|
16736
|
+
hasRuntimeDefault: false;
|
|
16737
|
+
enumValues: undefined;
|
|
16738
|
+
baseColumn: never;
|
|
16739
|
+
identity: undefined;
|
|
16740
|
+
generated: undefined;
|
|
16741
|
+
}, {}>;
|
|
16742
|
+
deletedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
16743
|
+
name: string;
|
|
16744
|
+
tableName: "crm_fields";
|
|
16745
|
+
dataType: "number int53";
|
|
16746
|
+
data: number;
|
|
16747
|
+
driverParam: number;
|
|
16748
|
+
notNull: false;
|
|
16749
|
+
hasDefault: false;
|
|
16750
|
+
isPrimaryKey: false;
|
|
16751
|
+
isAutoincrement: false;
|
|
16752
|
+
hasRuntimeDefault: false;
|
|
16753
|
+
enumValues: undefined;
|
|
16754
|
+
baseColumn: never;
|
|
16755
|
+
identity: undefined;
|
|
16756
|
+
generated: undefined;
|
|
16757
|
+
}, {}>;
|
|
16758
|
+
};
|
|
16759
|
+
dialect: 'sqlite';
|
|
16760
|
+
isAlias: false;
|
|
16761
|
+
}>;
|
|
16545
16762
|
/** What a source said about one of its records, as it said it: the layer under the entity an import makes. */
|
|
16546
16763
|
export declare const records: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
16547
16764
|
name: "records";
|