@wirecat/cli-core 0.550.0 → 0.551.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 +176 -0
- package/THIRD_PARTY_NOTICES +41 -0
- package/dist/calendar/ics.d.ts +1 -1
- package/dist/calendar/ics.js +5 -2
- package/dist/calendar/windows-zones.generated.d.ts +1 -0
- package/dist/calendar/windows-zones.generated.js +146 -0
- package/dist/cli/compact.js +2 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/meeting-findings-input.js +4 -3
- package/dist/cli/messenger/backfill-command.js +28 -1
- package/dist/cli/messenger/context.js +1 -0
- package/dist/cli/messenger/history-gaps.js +4 -4
- package/dist/cli/messenger/mcp-command.js +1 -0
- package/dist/cli/messenger/messages-delete-command.js +7 -8
- package/dist/cli/messenger/projects-command.js +47 -2
- package/dist/cli/payment-query.d.ts +8 -0
- package/dist/cli/payment-query.js +27 -0
- package/dist/cli/project-status-rows.d.ts +8 -0
- package/dist/cli/project-status-rows.js +102 -0
- package/dist/cli/project-status.d.ts +132 -0
- package/dist/cli/project-status.js +248 -0
- package/dist/cli/steps.js +4 -1
- package/dist/domain/knowledge.d.ts +1 -1
- package/dist/domain/knowledge.js +1 -1
- package/dist/domain/status-words.d.ts +3 -0
- package/dist/domain/status-words.js +44 -0
- package/dist/entities/fact.d.ts +12 -1
- package/dist/entities/fact.js +13 -0
- package/dist/entities/index.d.ts +18 -2
- package/dist/entities/meeting-findings.d.ts +1 -1
- package/dist/entities/message-transfer.d.ts +16 -0
- package/dist/entities/message-transfer.js +6 -0
- package/dist/mcp/app-server.js +1 -0
- package/dist/mcp/http/oauth.d.ts +2 -1
- package/dist/mcp/http/oauth.js +2 -2
- package/dist/mcp/http/serve.d.ts +2 -0
- package/dist/mcp/http/serve.js +2 -1
- package/dist/mcp/server.js +1 -0
- package/dist/mcp/surface.js +1 -1
- package/dist/meetings/findings-rules.d.ts +14 -3
- package/dist/meetings/findings-rules.js +16 -2
- package/dist/meetings/index.d.ts +3 -2
- package/dist/meetings/index.js +2 -1
- package/dist/meetings/meeting-context.d.ts +2 -3
- package/dist/meetings/meeting-context.js +3 -4
- package/dist/meetings/payment.d.ts +27 -0
- package/dist/meetings/payment.js +81 -0
- package/dist/services/archive.d.ts +13 -0
- package/dist/services/archive.js +56 -5
- package/dist/services/deps.d.ts +3 -0
- package/dist/services/history-limits.d.ts +2 -0
- package/dist/services/index.d.ts +4 -1
- package/dist/services/index.js +4 -1
- package/dist/services/job-query.d.ts +5 -2
- package/dist/services/job-query.js +5 -2
- package/dist/services/link-scorers/alias-hits.d.ts +7 -0
- package/dist/services/link-scorers/alias-hits.js +54 -0
- package/dist/services/link-scorers/chat-senders.d.ts +6 -0
- package/dist/services/link-scorers/chat-senders.js +56 -0
- package/dist/services/link-scorers/chat-title.d.ts +6 -0
- package/dist/services/link-scorers/chat-title.js +44 -0
- package/dist/services/link-scorers/email-domain.d.ts +6 -0
- package/dist/services/link-scorers/email-domain.js +64 -0
- package/dist/services/link-scorers/index.d.ts +9 -0
- package/dist/services/link-scorers/index.js +8 -0
- package/dist/services/link-scorers/mail-thread.d.ts +3 -0
- package/dist/services/link-scorers/mail-thread.js +74 -0
- package/dist/services/link-scorers/member-chats.d.ts +3 -0
- package/dist/services/link-scorers/member-chats.js +40 -0
- package/dist/services/link-scorers/member-dialog.d.ts +3 -0
- package/dist/services/link-scorers/member-dialog.js +34 -0
- package/dist/services/link-scorers/scan.d.ts +41 -0
- package/dist/services/link-scorers/scan.js +244 -0
- package/dist/services/link-scorers/shared.d.ts +26 -0
- package/dist/services/link-scorers/shared.js +48 -0
- package/dist/services/link-scorers/types.d.ts +77 -0
- package/dist/services/link-scorers/types.js +1 -0
- package/dist/services/project-status/index.d.ts +71 -0
- package/dist/services/project-status/index.js +196 -0
- package/dist/services/project-status/items.d.ts +10 -0
- package/dist/services/project-status/items.js +23 -0
- package/dist/services/project-status/signals/amounts.d.ts +20 -0
- package/dist/services/project-status/signals/amounts.js +91 -0
- package/dist/services/project-status/signals/children.d.ts +13 -0
- package/dist/services/project-status/signals/children.js +28 -0
- package/dist/services/project-status/signals/coverage.d.ts +13 -0
- package/dist/services/project-status/signals/coverage.js +30 -0
- package/dist/services/project-status/signals/index.d.ts +3 -0
- package/dist/services/project-status/signals/index.js +21 -0
- package/dist/services/project-status/signals/last-message.d.ts +4 -0
- package/dist/services/project-status/signals/last-message.js +22 -0
- package/dist/services/project-status/signals/payments.d.ts +9 -0
- package/dist/services/project-status/signals/payments.js +24 -0
- package/dist/services/project-status/signals/promises.d.ts +17 -0
- package/dist/services/project-status/signals/promises.js +109 -0
- package/dist/services/project-status/signals/questions.d.ts +8 -0
- package/dist/services/project-status/signals/questions.js +38 -0
- package/dist/services/project-status/signals/reply-next.d.ts +19 -0
- package/dist/services/project-status/signals/reply-next.js +75 -0
- package/dist/services/project-status/signals/topics.d.ts +10 -0
- package/dist/services/project-status/signals/topics.js +50 -0
- package/dist/services/project-status/types.d.ts +84 -0
- package/dist/services/project-status/types.js +1 -0
- package/dist/services/proposals/index.d.ts +1 -0
- package/dist/services/proposals/index.js +1 -0
- package/dist/services/proposals/kinds/findings.d.ts +3 -0
- package/dist/services/proposals/kinds/findings.js +8 -1
- package/dist/store/decisions.d.ts +5 -1
- package/dist/store/decisions.js +5 -1
- package/dist/store/evidence-findings.js +1 -0
- package/dist/store/index.d.ts +2 -1
- package/dist/store/index.js +1 -0
- package/dist/store/project-transfer/request.d.ts +1 -1
- package/dist/store/project-transfer/schema.d.ts +2 -2
- package/dist/store/project-transfer/snapshot-model.d.ts +2 -2
- package/dist/store/sqlite/evidence-findings.js +5 -1
- package/dist/store/sqlite/facts.d.ts +13 -0
- package/dist/store/sqlite/facts.js +64 -6
- package/dist/store/sqlite/findings-core.js +14 -1
- package/dist/store/sqlite/knowledge.d.ts +16 -2
- package/dist/store/sqlite/knowledge.js +1 -0
- package/dist/store/sqlite/project-transfer/snapshot.d.ts +1 -1
- package/dist/store/sqlite/relation-proposals.d.ts +29 -2
- package/dist/store/sqlite/relation-proposals.js +75 -4
- package/dist/store/store.d.ts +1 -0
- package/package.json +3 -3
- package/parity.json +11 -0
|
@@ -6,6 +6,7 @@ import { type AccountKey, type ChatStats, type MessageStore, type Range } from "
|
|
|
6
6
|
import { type Estimate } from "./backfill-estimate.js";
|
|
7
7
|
import { batchProgress } from "./batch.js";
|
|
8
8
|
import type { ServiceDeps } from "./deps.js";
|
|
9
|
+
import { type ChatShape } from "./history-limits.js";
|
|
9
10
|
import { type CatchUpOptions, type CatchUpResult } from "./search-catchup.js";
|
|
10
11
|
/** The most messages a provider hands out per history request — Telegram's cap. */
|
|
11
12
|
export declare const PAGE = 100;
|
|
@@ -45,6 +46,10 @@ export interface FetchOptions {
|
|
|
45
46
|
done: number;
|
|
46
47
|
fetched: number;
|
|
47
48
|
}) => void;
|
|
49
|
+
/** Past the history limits for this chat (`--full-history`). */
|
|
50
|
+
fullHistory?: boolean;
|
|
51
|
+
/** The chat's kind and size as the messenger lists it, for a chat the store does not hold yet. */
|
|
52
|
+
shape?: ChatShape;
|
|
48
53
|
}
|
|
49
54
|
/** A type, not an interface: a job keeps it as a plain record. */
|
|
50
55
|
export type Fetched = {
|
|
@@ -62,6 +67,8 @@ export type Fetched = {
|
|
|
62
67
|
reachedSince?: true;
|
|
63
68
|
reachedLast?: true;
|
|
64
69
|
stopped?: true;
|
|
70
|
+
/** The history limits stopped this read: a group too large to read, or the window's start reached. */
|
|
71
|
+
limited?: "group_too_large" | "older_than_window";
|
|
65
72
|
};
|
|
66
73
|
export type FetchedAll = {
|
|
67
74
|
batch?: ReturnType<ReturnType<typeof batchProgress>["result"]>;
|
|
@@ -80,7 +87,13 @@ export type FetchedAll = {
|
|
|
80
87
|
stopped?: true;
|
|
81
88
|
error?: string;
|
|
82
89
|
issue?: ActionableError;
|
|
90
|
+
limited?: "group_too_large" | "older_than_window";
|
|
83
91
|
}[];
|
|
92
|
+
/** How many chats the history limits left unread (big groups) or read only to the window's start. */
|
|
93
|
+
limited?: {
|
|
94
|
+
groupTooLarge: number;
|
|
95
|
+
olderThanWindow: number;
|
|
96
|
+
};
|
|
84
97
|
};
|
|
85
98
|
/** The local store of messages: what it holds, filling it from the messenger, and reading it out. */
|
|
86
99
|
export interface ArchiveService {
|
package/dist/services/archive.js
CHANGED
|
@@ -5,6 +5,7 @@ import { typedCommand } from "../runs/scope.js";
|
|
|
5
5
|
import { fetchedKey, historyStartKey, } from "../store/store.js";
|
|
6
6
|
import { estimateBackfill } from "./backfill-estimate.js";
|
|
7
7
|
import { actionable, batchProgress } from "./batch.js";
|
|
8
|
+
import { historyWindow } from "./history-limits.js";
|
|
8
9
|
import { storedChatId } from "./message-resolution.js";
|
|
9
10
|
import { patiently } from "./patience.js";
|
|
10
11
|
import { catchUpSearch, validateCatchUp } from "./search-catchup.js";
|
|
@@ -114,13 +115,28 @@ export const archiveService = (deps) => {
|
|
|
114
115
|
throw new CliError("authentication_error", "the connection belongs to another account");
|
|
115
116
|
if (options.stop.aborted)
|
|
116
117
|
return { chat: null, fetched: 0, complete: false, ranges: [], stopped: true };
|
|
117
|
-
const
|
|
118
|
+
const store = await deps.store();
|
|
119
|
+
const account = { provider: deps.messenger.provider, account: self };
|
|
120
|
+
const limit = await limitOf(deps, store, account, chat, options);
|
|
121
|
+
if (limit?.skip) {
|
|
122
|
+
await recordSkip(store, account, limit.chatId, { reason: "group_too_large", members: limit.skip.members });
|
|
123
|
+
return { chat: limit.chatId ?? null, fetched: 0, complete: false, ranges: [], limited: "group_too_large" };
|
|
124
|
+
}
|
|
125
|
+
const windowed = limit?.keepFrom !== undefined && !(limit.keepFrom <= (options.sinceMs ?? Number.NEGATIVE_INFINITY));
|
|
126
|
+
const fetched = await fetchInto({
|
|
118
127
|
history: (window) => history(chat, { ...window, reactions: false }),
|
|
119
|
-
store
|
|
120
|
-
account
|
|
128
|
+
store,
|
|
129
|
+
account,
|
|
121
130
|
fetching: deps.messenger.fetching ?? FETCHING,
|
|
122
131
|
...options,
|
|
132
|
+
...(windowed ? { sinceMs: limit.keepFrom } : {}),
|
|
123
133
|
});
|
|
134
|
+
const result = windowed && fetched.reachedSince ? { ...fetched, limited: "older_than_window" } : fetched;
|
|
135
|
+
if (result.limited && limit?.keepFrom !== undefined)
|
|
136
|
+
await recordSkip(store, account, result.chat ?? limit.chatId, {
|
|
137
|
+
reason: "older_than_window",
|
|
138
|
+
keptFrom: new Date(limit.keepFrom).toISOString(),
|
|
139
|
+
});
|
|
124
140
|
return requested && result.chat
|
|
125
141
|
? { ...result, prepared: await catchUpSearch(deps, result.chat, requested, options.stop) }
|
|
126
142
|
: result;
|
|
@@ -161,7 +177,17 @@ export const archiveService = (deps) => {
|
|
|
161
177
|
continue;
|
|
162
178
|
}
|
|
163
179
|
try {
|
|
164
|
-
const one = await own.fetch(chat.id,
|
|
180
|
+
const one = await own.fetch(chat.id, {
|
|
181
|
+
...options,
|
|
182
|
+
shape: { kind: chat.kind, participantsCount: chat.participantsCount ?? null },
|
|
183
|
+
});
|
|
184
|
+
if (one.limited) {
|
|
185
|
+
answer.limited ??= { groupTooLarge: 0, olderThanWindow: 0 };
|
|
186
|
+
if (one.limited === "group_too_large")
|
|
187
|
+
answer.limited.groupTooLarge += 1;
|
|
188
|
+
else
|
|
189
|
+
answer.limited.olderThanWindow += 1;
|
|
190
|
+
}
|
|
165
191
|
if (one.issue)
|
|
166
192
|
batch.fail(chat.id, "history", one.issue);
|
|
167
193
|
else
|
|
@@ -172,7 +198,8 @@ export const archiveService = (deps) => {
|
|
|
172
198
|
title: chat.title,
|
|
173
199
|
fetched: one.fetched,
|
|
174
200
|
// Reaching the window is what --all asked for; older history was not.
|
|
175
|
-
complete: one.complete || one.reachedSince === true || one.reachedLast === true,
|
|
201
|
+
complete: one.complete || one.reachedSince === true || one.reachedLast === true || one.limited !== undefined,
|
|
202
|
+
...(one.limited ? { limited: one.limited } : {}),
|
|
176
203
|
...(one.issue ? { error: one.issue.code, issue: one.issue } : {}),
|
|
177
204
|
...(one.stopped ? { stopped: true } : {}),
|
|
178
205
|
});
|
|
@@ -195,6 +222,30 @@ export const archiveService = (deps) => {
|
|
|
195
222
|
},
|
|
196
223
|
};
|
|
197
224
|
};
|
|
225
|
+
/**
|
|
226
|
+
* Where the history limits stop this chat's read: not at all for a group above the cap, else at the window's start.
|
|
227
|
+
* None for a lifted chat or without limits. A chat the store does not hold yet is judged by the shape the list gave.
|
|
228
|
+
*/
|
|
229
|
+
const limitOf = async (deps, store, account, chat, options) => {
|
|
230
|
+
if (!deps.historyLimits || options.fullHistory)
|
|
231
|
+
return undefined;
|
|
232
|
+
const chatId = await storedChatId(deps.messenger, chat, store, account, { includeLeft: true }).catch(() => undefined);
|
|
233
|
+
const stored = chatId === undefined ? undefined : (await store.chats.historyShapes(account, [chatId])).get(chatId);
|
|
234
|
+
if (stored?.included)
|
|
235
|
+
return undefined;
|
|
236
|
+
const window = historyWindow(stored ?? options.shape, deps.historyLimits, Date.now());
|
|
237
|
+
return {
|
|
238
|
+
...(chatId === undefined ? {} : { chatId }),
|
|
239
|
+
...("skip" in window ? { skip: { members: window.members } } : { keepFrom: window.keepFrom }),
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
/** A read the limits stopped, for `store status` and the answers that say not all is there; the count is unknown. */
|
|
243
|
+
const recordSkip = async (store, account, chatId, skip) => {
|
|
244
|
+
if (!chatId)
|
|
245
|
+
return;
|
|
246
|
+
const at = new Date().toISOString();
|
|
247
|
+
await store.chats.recordHistorySkips(account, new Map([[chatId, { ...skip, messages: 0, oldestAt: at, newestAt: at, unread: true }]]));
|
|
248
|
+
};
|
|
198
249
|
/** The fetch loop, over any history and store: the personal account's service and a bot's command share it. */
|
|
199
250
|
export const abortable = async (read, signal) => {
|
|
200
251
|
signal.throwIfAborted();
|
package/dist/services/deps.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { MessengerTraits } from "../ports/traits.js";
|
|
|
5
5
|
import type { SendGuard } from "../sends/guard.js";
|
|
6
6
|
import type { AccountStore } from "../store/account-store.js";
|
|
7
7
|
import type { AccountKey, MessageStore } from "../store/store.js";
|
|
8
|
+
import type { HistoryLimits } from "./history-limits.js";
|
|
8
9
|
import type { Override } from "./index.js";
|
|
9
10
|
export interface ServiceMessenger extends MessengerTraits {
|
|
10
11
|
/** Replaces shared use cases for this messenger; its commands and MCP tools both get the replacement. */
|
|
@@ -39,6 +40,8 @@ export interface ServiceDeps {
|
|
|
39
40
|
/** `false` keeps no search history: recording was turned off by name. Kept when unset. */
|
|
40
41
|
history?: boolean;
|
|
41
42
|
searchCatchUp?: boolean;
|
|
43
|
+
/** How far back `messages sync` reads each kind of chat, and which groups it does not read at all. */
|
|
44
|
+
historyLimits?: HistoryLimits;
|
|
42
45
|
/** This account has picture search on (`pictureSearch`): its images are embedded and searched by their pixels. */
|
|
43
46
|
pictures?: boolean;
|
|
44
47
|
/** Which reranker orders `--discover`'s first hits (`searchReranker`), or `off`; the default when unset. */
|
|
@@ -22,6 +22,8 @@ export interface HistorySkip {
|
|
|
22
22
|
members?: number;
|
|
23
23
|
/** For `older_than_window`: the window's start when the push was saved. */
|
|
24
24
|
keptFrom?: string;
|
|
25
|
+
/** `messages sync` stopped there, so how many messages were left out is not known (`messages` is 0). */
|
|
26
|
+
unread?: true;
|
|
25
27
|
}
|
|
26
28
|
/** The kind and size of a chat, as the batch names it or the store holds it. */
|
|
27
29
|
export interface ChatShape {
|
package/dist/services/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type BotCheckService } from "../botcheck/service.js";
|
|
2
|
+
export { paymentTitle } from "../meetings/index.js";
|
|
2
3
|
export { amountOf, amountsOf, type TaskAmount } from "../search/task-signature.js";
|
|
3
4
|
export { AGENDA_DEFAULTS } from "./agenda.js";
|
|
4
5
|
export { type ChatCoverage, chatCoverage } from "./chat-coverage.js";
|
|
5
6
|
export type { ContextEvidenceItem, ContextEvidencePage, ContextEvidenceReader, ContextEvidenceRequest, ContextEvidenceSender, ContextIndex, ContextReadLimits, ContextReadUsage, ContextRecordReader, ContextSource, ScopedContextEvidenceReader, } from "./context-evidence.js";
|
|
6
7
|
export { contextEvidenceFingerprint, createContextEvidenceReader } from "./context-evidence.js";
|
|
7
8
|
export { excerpt } from "./excerpt.js";
|
|
9
|
+
export { aliasHits, BUILT_IN_PURPOSES, chatSenders, chatTitle, combinedScore, DEFAULT_LIMIT as DEFAULT_LINK_SCAN_LIMIT, DEFAULT_THRESHOLD as DEFAULT_LINK_SCAN_THRESHOLD, emailDomain, type LinkCandidate, type LinkScorer, mailThread, memberChats, memberDialog, PERSON_SCORERS, PERSON_THRESHOLD as PERSON_LINK_SCAN_THRESHOLD, SCAN_BOT, SCORERS, type ScanInput as LinkScanInput, type ScannedProject, type ScannedProposal, type ScanResult as LinkScanResult, type ScorerInput, type Signal as LinkSignal, scanProjectLinks, } from "./link-scorers/index.js";
|
|
8
10
|
export type { MediaArchiveOptions, MediaArchivePorts } from "./media-archive.js";
|
|
9
11
|
export { archiveMedia, checkMediaArchive } from "./media-archive.js";
|
|
10
12
|
export type { MediaArchiveEntry, MediaIdentity } from "./media-archive-manifest.js";
|
|
@@ -19,6 +21,7 @@ export { documentPassage, storedMessageNeighbours, storedNeighbours } from "./pr
|
|
|
19
21
|
export type { EvidenceMention, EvidenceMentions } from "./project-gathering-mentions.js";
|
|
20
22
|
export { findEvidenceMentions } from "./project-gathering-mentions.js";
|
|
21
23
|
export type { ProjectChatFetched, ProjectGatheringBudgets, ProjectGatheringRequest, ProjectLane, ProjectLaneKind, ProjectLinkedChat, ProjectPerson, ProjectSender, ProjectUnitState, } from "./project-gathering-request.js";
|
|
24
|
+
export { DEFAULT_PURPOSES, type MoneyTotal, type ProjectStatus, type ProjectStatusInput, projectStatus, STATUS_CHATS, STATUS_SIGNALS, STATUS_WINDOW, type StatusAttention, type StatusChat, type StatusChatSummary, type StatusContext, type StatusItem, type StatusSignal, } from "./project-status/index.js";
|
|
22
25
|
export { questions } from "./questions.js";
|
|
23
26
|
export { canonicalProjectRef, gatherStoredProject, type LinkedChat, linkedChatsOf, type StoredProjectGathering, type StoredProjectInput, } from "./stored-project-gathering.js";
|
|
24
27
|
import { type AccountService } from "./account.js";
|
|
@@ -181,7 +184,7 @@ export { type PackageUpgradeOutcome, type PackageUpgradePorts, type PackageUpgra
|
|
|
181
184
|
export { type PersonMeetingContext, type PersonMeetingContextItem, type PersonMeetingContextOptions, type PersonMeetingReadStore, personMeetingContext, } from "./person-meeting-context.js";
|
|
182
185
|
export { privatePeopleService } from "./private-people.js";
|
|
183
186
|
export { approveAsTask, type ProposalTaskStore, proposalSource } from "./proposal-tasks.js";
|
|
184
|
-
export { type Decided, type Decision, emptyProposalRegistry, type ProposalFilter, type ProposalKind, type ProposalRegistry, type ProposalRow, parseProposalIds, proposalRegistry, } from "./proposals/index.js";
|
|
187
|
+
export { type Decided, type Decision, emptyProposalRegistry, FACT_SUMMARIES, type ProposalFilter, type ProposalKind, type ProposalRegistry, type ProposalRow, parseProposalIds, proposalRegistry, } from "./proposals/index.js";
|
|
185
188
|
export type { RetentionQuery, RetentionService } from "./retention.js";
|
|
186
189
|
export { retentionService } from "./retention.js";
|
|
187
190
|
export { RESOURCES_SEARCHED, type SearchAllFound, type SearchAllItem, type SearchAllMessenger, type SearchAllRequest, type SearchedResource, searchAll, } from "./search-all.js";
|
package/dist/services/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { botCheckService } from "../botcheck/service.js";
|
|
2
|
+
export { paymentTitle } from "../meetings/index.js";
|
|
2
3
|
export { amountOf, amountsOf } from "../search/task-signature.js";
|
|
3
4
|
export { AGENDA_DEFAULTS } from "./agenda.js";
|
|
4
5
|
export { chatCoverage } from "./chat-coverage.js";
|
|
5
6
|
export { contextEvidenceFingerprint, createContextEvidenceReader } from "./context-evidence.js";
|
|
6
7
|
export { excerpt } from "./excerpt.js";
|
|
8
|
+
export { aliasHits, BUILT_IN_PURPOSES, chatSenders, chatTitle, combinedScore, DEFAULT_LIMIT as DEFAULT_LINK_SCAN_LIMIT, DEFAULT_THRESHOLD as DEFAULT_LINK_SCAN_THRESHOLD, emailDomain, mailThread, memberChats, memberDialog, PERSON_SCORERS, PERSON_THRESHOLD as PERSON_LINK_SCAN_THRESHOLD, SCAN_BOT, SCORERS, scanProjectLinks, } from "./link-scorers/index.js";
|
|
7
9
|
export { archiveMedia, checkMediaArchive } from "./media-archive.js";
|
|
8
10
|
export { moveMediaArchive } from "./media-archive-move.js";
|
|
9
11
|
export { downloadImageModel, downloadTextModel, } from "./model-downloads.js";
|
|
@@ -11,6 +13,7 @@ export { MONEY_DONE, MONEY_REQUEST, MONEY_WORDS, moneyClassesOf, PROMISE } from
|
|
|
11
13
|
export { gatherProjectEvidence } from "./project-gathering.js";
|
|
12
14
|
export { documentPassage, storedMessageNeighbours, storedNeighbours } from "./project-gathering-around.js";
|
|
13
15
|
export { findEvidenceMentions } from "./project-gathering-mentions.js";
|
|
16
|
+
export { DEFAULT_PURPOSES, projectStatus, STATUS_CHATS, STATUS_SIGNALS, STATUS_WINDOW, } from "./project-status/index.js";
|
|
14
17
|
export { questions } from "./questions.js";
|
|
15
18
|
export { canonicalProjectRef, gatherStoredProject, linkedChatsOf, } from "./stored-project-gathering.js";
|
|
16
19
|
import { accountService } from "./account.js";
|
|
@@ -139,7 +142,7 @@ export { upgradePackage, } from "./package-upgrade.js";
|
|
|
139
142
|
export { personMeetingContext, } from "./person-meeting-context.js";
|
|
140
143
|
export { privatePeopleService } from "./private-people.js";
|
|
141
144
|
export { approveAsTask, proposalSource } from "./proposal-tasks.js";
|
|
142
|
-
export { emptyProposalRegistry, parseProposalIds, proposalRegistry, } from "./proposals/index.js";
|
|
145
|
+
export { emptyProposalRegistry, FACT_SUMMARIES, parseProposalIds, proposalRegistry, } from "./proposals/index.js";
|
|
143
146
|
export { retentionService } from "./retention.js";
|
|
144
147
|
export { RESOURCES_SEARCHED, searchAll, } from "./search-all.js";
|
|
145
148
|
export { RESOURCES_SEARCHED_WITH_MEETINGS, searchAllIncludingMeetings, searchAllWithMeetings, } from "./search-all-meetings.js";
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { type QueryAst } from "@wirecat/cli-base/query";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* The job folder is the list's own source: every file is read whole, so it answers each field. `sql`, not `native`:
|
|
4
|
+
* `evaluate` takes the whole language, OR included, and the help and the MCP description say so.
|
|
5
|
+
*/
|
|
3
6
|
export declare const JOBS_QUERY: {
|
|
4
7
|
readonly entity: "job";
|
|
5
|
-
readonly backend: "
|
|
8
|
+
readonly backend: "sql";
|
|
6
9
|
readonly fields: import("@wirecat/cli-base/query").FieldSpec[];
|
|
7
10
|
};
|
|
8
11
|
/** One profile's jobs as `store jobs list` answers them, newest first; the query is checked as each file is read. */
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { evaluate, queryFieldsOf } from "@wirecat/cli-base/query";
|
|
2
2
|
import { job } from "../entities/job.js";
|
|
3
3
|
import { jobItem, listJobs } from "./backfill-jobs.js";
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The job folder is the list's own source: every file is read whole, so it answers each field. `sql`, not `native`:
|
|
6
|
+
* `evaluate` takes the whole language, OR included, and the help and the MCP description say so.
|
|
7
|
+
*/
|
|
5
8
|
export const JOBS_QUERY = {
|
|
6
9
|
entity: "job",
|
|
7
|
-
backend: "
|
|
10
|
+
backend: "sql",
|
|
8
11
|
fields: queryFieldsOf(job, {
|
|
9
12
|
native: Object.keys(job.entries),
|
|
10
13
|
examples: { state: "state:failed", started_at: "started_at>=7d", chat: "chat:7", fetched: "fetched>0" },
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { LinkScorer } from "./types.js";
|
|
2
|
+
export declare const aliasWeight: (hits: number) => number;
|
|
3
|
+
/**
|
|
4
|
+
* Messages in the window that name the project as whole words, grouped by chat: 3–9 of them, or 10 and more. A
|
|
5
|
+
* text search finds a word inside another, so each hit is checked again by the whole-word rule.
|
|
6
|
+
*/
|
|
7
|
+
export declare const aliasHits: LinkScorer;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { formatReference } from "../../domain/references.js";
|
|
2
|
+
import { chatRef, projectTerms, wholeWords } from "./shared.js";
|
|
3
|
+
const SEARCH_LIMIT = 500;
|
|
4
|
+
export const aliasWeight = (hits) => (hits >= 10 ? 0.55 : hits >= 3 ? 0.35 : 0);
|
|
5
|
+
/**
|
|
6
|
+
* Messages in the window that name the project as whole words, grouped by chat: 3–9 of them, or 10 and more. A
|
|
7
|
+
* text search finds a word inside another, so each hit is checked again by the whole-word rule.
|
|
8
|
+
*/
|
|
9
|
+
export const aliasHits = {
|
|
10
|
+
id: "alias-hits",
|
|
11
|
+
version: 1,
|
|
12
|
+
candidates: async ({ store, project, sources, since, signal }) => {
|
|
13
|
+
if (!project)
|
|
14
|
+
return [];
|
|
15
|
+
// The trigram index answers a shorter query with nothing.
|
|
16
|
+
const terms = projectTerms(project).filter((term) => [...term.trim()].length >= 3);
|
|
17
|
+
const found = [];
|
|
18
|
+
for (const key of sources) {
|
|
19
|
+
const chats = new Map();
|
|
20
|
+
for (const term of terms) {
|
|
21
|
+
signal?.throwIfAborted();
|
|
22
|
+
const pattern = wholeWords(term);
|
|
23
|
+
const hits = await store.messages.find({ account: key, text: term, limit: SEARCH_LIMIT });
|
|
24
|
+
for (const hit of hits.items) {
|
|
25
|
+
if (hit.timestamp < since || !pattern.test(hit.text.normalize("NFKC").toLowerCase()))
|
|
26
|
+
continue;
|
|
27
|
+
const messages = chats.get(String(hit.chatId)) ?? new Map();
|
|
28
|
+
messages.set(String(hit.id), formatReference({
|
|
29
|
+
type: "message",
|
|
30
|
+
provider: key.provider,
|
|
31
|
+
account: String(key.account),
|
|
32
|
+
chat: String(hit.chatId),
|
|
33
|
+
message: String(hit.id),
|
|
34
|
+
}));
|
|
35
|
+
chats.set(String(hit.chatId), messages);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const [chat, messages] of chats) {
|
|
39
|
+
const weight = aliasWeight(messages.size);
|
|
40
|
+
if (!weight)
|
|
41
|
+
continue;
|
|
42
|
+
found.push({
|
|
43
|
+
from: chatRef(key, chat),
|
|
44
|
+
to: project.ref,
|
|
45
|
+
kind: "member-of",
|
|
46
|
+
weight,
|
|
47
|
+
why: `${messages.size} messages since ${since.slice(0, 10)} name the project`,
|
|
48
|
+
evidence: [...messages.values()].slice(0, 3),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return found;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { LinkScorer } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* A person who wrote 5 or more messages in the window in one of the project's confirmed chats, found by the sender's
|
|
4
|
+
* identity, never by the name a message shows. In a group of more than 30 people writing there says less.
|
|
5
|
+
*/
|
|
6
|
+
export declare const chatSenders: LinkScorer;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { formatReference, parseReference } from "../../domain/references.js";
|
|
2
|
+
import { contactRef, personRefOf, sameAccount } from "./shared.js";
|
|
3
|
+
const WEIGHT = 0.5;
|
|
4
|
+
const LARGE_GROUP_WEIGHT = 0.2;
|
|
5
|
+
const LARGE_GROUP = 30;
|
|
6
|
+
const MIN_MESSAGES = 5;
|
|
7
|
+
const READ_LIMIT = 2000;
|
|
8
|
+
/**
|
|
9
|
+
* A person who wrote 5 or more messages in the window in one of the project's confirmed chats, found by the sender's
|
|
10
|
+
* identity, never by the name a message shows. In a group of more than 30 people writing there says less.
|
|
11
|
+
*/
|
|
12
|
+
export const chatSenders = {
|
|
13
|
+
id: "chat-senders",
|
|
14
|
+
version: 1,
|
|
15
|
+
candidates: async ({ store, project, confirmed, sources, since, owner, signal }) => {
|
|
16
|
+
if (!project)
|
|
17
|
+
return [];
|
|
18
|
+
const found = [];
|
|
19
|
+
for (const chat of confirmed.chats) {
|
|
20
|
+
const reference = parseReference(chat);
|
|
21
|
+
if (reference.type !== "chat")
|
|
22
|
+
continue;
|
|
23
|
+
const key = sources.find((source) => sameAccount(source, reference));
|
|
24
|
+
if (!key)
|
|
25
|
+
continue;
|
|
26
|
+
signal?.throwIfAborted();
|
|
27
|
+
const page = await store.messages.messages(key, reference.chat, { limit: READ_LIMIT, since, outgoing: false });
|
|
28
|
+
const senders = new Map();
|
|
29
|
+
for (const message of page.items) {
|
|
30
|
+
if (message.senderId === null || message.senderIsChat)
|
|
31
|
+
continue;
|
|
32
|
+
const id = String(message.senderId);
|
|
33
|
+
senders.set(id, { count: (senders.get(id)?.count ?? 0) + 1, last: String(message.id) });
|
|
34
|
+
}
|
|
35
|
+
const size = Math.max((await store.roster.members(key, reference.chat)).length, senders.size);
|
|
36
|
+
const weight = size > LARGE_GROUP ? LARGE_GROUP_WEIGHT : WEIGHT;
|
|
37
|
+
for (const [id, { count, last }] of senders) {
|
|
38
|
+
if (count < MIN_MESSAGES)
|
|
39
|
+
continue;
|
|
40
|
+
const person = await personRefOf(store, key.provider, id);
|
|
41
|
+
if (!person || person === owner)
|
|
42
|
+
continue;
|
|
43
|
+
found.push({
|
|
44
|
+
from: person,
|
|
45
|
+
to: project.ref,
|
|
46
|
+
kind: "member-of",
|
|
47
|
+
weight,
|
|
48
|
+
why: `${count} messages since ${since.slice(0, 10)} in a project chat (${chat})${size > LARGE_GROUP ? `, a group of ${size}` : ""}`,
|
|
49
|
+
evidence: [formatReference({ ...reference, type: "message", message: last })],
|
|
50
|
+
identity: contactRef(key.provider, id),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return found;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { LinkScorer } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* A chat whose title names the project, its key or an alias as whole words. A purpose the owner configured that the
|
|
4
|
+
* title also names («HRB sales») is proposed with it. Mail threads are the `mail-thread` scorer's.
|
|
5
|
+
*/
|
|
6
|
+
export declare const chatTitle: LinkScorer;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { chatRef, namesProject, wholeWords } from "./shared.js";
|
|
2
|
+
const WEIGHT = 0.7;
|
|
3
|
+
/**
|
|
4
|
+
* A chat whose title names the project, its key or an alias as whole words. A purpose the owner configured that the
|
|
5
|
+
* title also names («HRB sales») is proposed with it. Mail threads are the `mail-thread` scorer's.
|
|
6
|
+
*/
|
|
7
|
+
export const chatTitle = {
|
|
8
|
+
id: "chat-title",
|
|
9
|
+
version: 1,
|
|
10
|
+
candidates: async ({ store, project, sources, purposes, signal }) => {
|
|
11
|
+
if (!project)
|
|
12
|
+
return [];
|
|
13
|
+
const names = namesProject(project);
|
|
14
|
+
const purposeOf = purposes
|
|
15
|
+
.filter((purpose) => purpose !== "work")
|
|
16
|
+
.map((purpose) => ({ purpose, pattern: wholeWords(purpose) }));
|
|
17
|
+
const found = [];
|
|
18
|
+
for (const key of sources) {
|
|
19
|
+
if (key.provider === "email")
|
|
20
|
+
continue;
|
|
21
|
+
signal?.throwIfAborted();
|
|
22
|
+
const chats = await store.chats.chats(key, {});
|
|
23
|
+
for (const chat of chats.items) {
|
|
24
|
+
if (chat.kind === "saved")
|
|
25
|
+
continue;
|
|
26
|
+
const term = names(chat.title);
|
|
27
|
+
if (!term)
|
|
28
|
+
continue;
|
|
29
|
+
const title = (chat.title ?? "").normalize("NFKC").toLowerCase();
|
|
30
|
+
const purpose = purposeOf.find(({ pattern }) => pattern.test(title))?.purpose;
|
|
31
|
+
found.push({
|
|
32
|
+
from: chatRef(key, chat.id),
|
|
33
|
+
to: project.ref,
|
|
34
|
+
kind: "member-of",
|
|
35
|
+
weight: WEIGHT,
|
|
36
|
+
why: `the chat title names the project${term === project.name ? "" : ` (${term === project.key ? "key" : "alias"})`}`,
|
|
37
|
+
evidence: [],
|
|
38
|
+
...(purpose ? { purpose } : {}),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return found;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { LinkScorer } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* People whose mail addresses share a company domain, related to each other, weakly: a shared domain does not prove
|
|
4
|
+
* they work together. Needs no project; the rule `relationships scan` ran before scorers existed.
|
|
5
|
+
*/
|
|
6
|
+
export declare const emailDomain: LinkScorer;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { contactRef } from "./shared.js";
|
|
2
|
+
const WEIGHT = 0.3;
|
|
3
|
+
const CONTACTS = 500;
|
|
4
|
+
const MOST_PAIRS = 200;
|
|
5
|
+
const FREE = new Set([
|
|
6
|
+
"gmail.com",
|
|
7
|
+
"outlook.com",
|
|
8
|
+
"hotmail.com",
|
|
9
|
+
"yahoo.com",
|
|
10
|
+
"icloud.com",
|
|
11
|
+
"proton.me",
|
|
12
|
+
"protonmail.com",
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* People whose mail addresses share a company domain, related to each other, weakly: a shared domain does not prove
|
|
16
|
+
* they work together. Needs no project; the rule `relationships scan` ran before scorers existed.
|
|
17
|
+
*/
|
|
18
|
+
export const emailDomain = {
|
|
19
|
+
id: "email-domain",
|
|
20
|
+
version: 1,
|
|
21
|
+
candidates: async ({ store, sources, owner, signal }) => {
|
|
22
|
+
const domains = new Map();
|
|
23
|
+
for (const key of sources) {
|
|
24
|
+
if (key.provider !== "email")
|
|
25
|
+
continue;
|
|
26
|
+
signal?.throwIfAborted();
|
|
27
|
+
const contacts = await store.people.contacts(key, { order: "name", limit: CONTACTS, offset: 0 });
|
|
28
|
+
for (const contact of contacts.items) {
|
|
29
|
+
const address = String(contact.id).toLowerCase();
|
|
30
|
+
const domain = address.split("@")[1];
|
|
31
|
+
if (!domain || FREE.has(domain))
|
|
32
|
+
continue;
|
|
33
|
+
const person = await store.people.personOf({ provider: key.provider, id: contact.id });
|
|
34
|
+
if (!person || `person:${person.uid}` === owner)
|
|
35
|
+
continue;
|
|
36
|
+
const people = domains.get(domain) ?? new Map();
|
|
37
|
+
if (!people.has(`person:${person.uid}`))
|
|
38
|
+
people.set(`person:${person.uid}`, contactRef(key.provider, contact.id));
|
|
39
|
+
domains.set(domain, people);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const found = [];
|
|
43
|
+
for (const [domain, people] of domains) {
|
|
44
|
+
const sorted = [...people].sort(([a], [b]) => a.localeCompare(b));
|
|
45
|
+
for (let left = 0; left < sorted.length; left++)
|
|
46
|
+
for (let right = left + 1; right < sorted.length; right++) {
|
|
47
|
+
if (found.length >= MOST_PAIRS)
|
|
48
|
+
return found;
|
|
49
|
+
const [from, identity] = sorted[left];
|
|
50
|
+
const [to] = sorted[right];
|
|
51
|
+
found.push({
|
|
52
|
+
from,
|
|
53
|
+
to,
|
|
54
|
+
kind: "related-to",
|
|
55
|
+
weight: WEIGHT,
|
|
56
|
+
why: `addresses share the domain ${domain}; this does not prove they work together`,
|
|
57
|
+
evidence: [],
|
|
58
|
+
identity,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return found;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { aliasHits } from "./alias-hits.js";
|
|
2
|
+
export { chatSenders } from "./chat-senders.js";
|
|
3
|
+
export { chatTitle } from "./chat-title.js";
|
|
4
|
+
export { emailDomain } from "./email-domain.js";
|
|
5
|
+
export { mailThread } from "./mail-thread.js";
|
|
6
|
+
export { memberChats } from "./member-chats.js";
|
|
7
|
+
export { memberDialog } from "./member-dialog.js";
|
|
8
|
+
export { BUILT_IN_PURPOSES, combinedScore, DEFAULT_LIMIT, DEFAULT_THRESHOLD, PERSON_SCORERS, PERSON_THRESHOLD, SCAN_BOT, SCORERS, type ScanInput, scanProjectLinks, } from "./scan.js";
|
|
9
|
+
export type { LinkCandidate, LinkScorer, ScannedProject, ScannedProposal, ScanResult, ScorerInput, Signal, } from "./types.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { aliasHits } from "./alias-hits.js";
|
|
2
|
+
export { chatSenders } from "./chat-senders.js";
|
|
3
|
+
export { chatTitle } from "./chat-title.js";
|
|
4
|
+
export { emailDomain } from "./email-domain.js";
|
|
5
|
+
export { mailThread } from "./mail-thread.js";
|
|
6
|
+
export { memberChats } from "./member-chats.js";
|
|
7
|
+
export { memberDialog } from "./member-dialog.js";
|
|
8
|
+
export { BUILT_IN_PURPOSES, combinedScore, DEFAULT_LIMIT, DEFAULT_THRESHOLD, PERSON_SCORERS, PERSON_THRESHOLD, SCAN_BOT, SCORERS, scanProjectLinks, } from "./scan.js";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { formatReference } from "../../domain/references.js";
|
|
2
|
+
import { identitiesIn, namesProject } from "./shared.js";
|
|
3
|
+
const NAMED = 0.7;
|
|
4
|
+
const MEMBER_ONLY = 0.3;
|
|
5
|
+
const READ_LIMIT = 500;
|
|
6
|
+
/** A mail thread a confirmed member writes in or is written to: strong when its subject names the project. */
|
|
7
|
+
export const mailThread = {
|
|
8
|
+
id: "mail-thread",
|
|
9
|
+
version: 1,
|
|
10
|
+
candidates: async ({ store, project, confirmed, sources, since, owner, signal }) => {
|
|
11
|
+
if (!project)
|
|
12
|
+
return [];
|
|
13
|
+
const names = namesProject(project);
|
|
14
|
+
const people = confirmed.people.filter((person) => person !== owner);
|
|
15
|
+
const found = [];
|
|
16
|
+
for (const key of sources) {
|
|
17
|
+
if (key.provider !== "email")
|
|
18
|
+
continue;
|
|
19
|
+
const account = await store.accounts.storedAccount(key).catch(() => undefined);
|
|
20
|
+
if (!account)
|
|
21
|
+
continue;
|
|
22
|
+
const threads = new Map();
|
|
23
|
+
for (const { person, id } of await identitiesIn(store, people, "email")) {
|
|
24
|
+
signal?.throwIfAborted();
|
|
25
|
+
const emails = await store.mail.emails({
|
|
26
|
+
accountId: account.id,
|
|
27
|
+
participant: id,
|
|
28
|
+
since: Date.parse(since),
|
|
29
|
+
limit: READ_LIMIT,
|
|
30
|
+
});
|
|
31
|
+
for (const email of emails) {
|
|
32
|
+
const held = threads.get(email.emailThreadId) ?? {
|
|
33
|
+
members: new Set(),
|
|
34
|
+
subject: email.subject,
|
|
35
|
+
email: email.externalId,
|
|
36
|
+
};
|
|
37
|
+
held.members.add(person);
|
|
38
|
+
threads.set(email.emailThreadId, held);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const [id, { members, subject, email }] of threads) {
|
|
42
|
+
const thread = (await store.mail.thread(id))?.thread;
|
|
43
|
+
if (!thread)
|
|
44
|
+
continue;
|
|
45
|
+
const named = names(thread.subject ?? subject) !== undefined;
|
|
46
|
+
const chat = formatReference({
|
|
47
|
+
type: "chat",
|
|
48
|
+
provider: "email",
|
|
49
|
+
account: String(key.account),
|
|
50
|
+
chat: thread.externalId,
|
|
51
|
+
});
|
|
52
|
+
const who = [...members].sort();
|
|
53
|
+
found.push({
|
|
54
|
+
from: chat,
|
|
55
|
+
to: project.ref,
|
|
56
|
+
kind: "member-of",
|
|
57
|
+
weight: named ? NAMED : MEMBER_ONLY,
|
|
58
|
+
why: `a thread with ${who.length > 1 ? `${who.length} members` : `a member (${who[0]})`}${named ? "; its subject names the project" : ""}`,
|
|
59
|
+
evidence: [
|
|
60
|
+
formatReference({
|
|
61
|
+
type: "message",
|
|
62
|
+
provider: "email",
|
|
63
|
+
account: String(key.account),
|
|
64
|
+
chat: thread.externalId,
|
|
65
|
+
message: email,
|
|
66
|
+
}),
|
|
67
|
+
...who,
|
|
68
|
+
].slice(0, 5),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return found;
|
|
73
|
+
},
|
|
74
|
+
};
|