@rikaidev/yomi 0.5.0 → 0.6.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/README.md +28 -0
- package/dist/ambient/ambient-event-graph.js +172 -0
- package/dist/ambient/ambient-observer.js +71 -0
- package/dist/ambient/event-graph-reducer.js +300 -0
- package/dist/ambient/ingestion-adapter.js +203 -0
- package/dist/ambient/journal/crypto.js +58 -0
- package/dist/ambient/journal/journal-store.js +284 -0
- package/dist/ambient/journal/types.js +7 -0
- package/dist/ambient/summaries/retriever.js +55 -0
- package/dist/ambient/summaries/summary-engine.js +128 -0
- package/dist/ambient/summaries/types.js +4 -0
- package/dist/attention/policy.js +22 -16
- package/dist/auth/credential-store.js +15 -6
- package/dist/cli/query.js +276 -69
- package/dist/cli/work-item.js +24 -0
- package/dist/line/client/auth-service/client.js +7 -8
- package/dist/line/client/buddy-service/client.js +73 -0
- package/dist/line/client/index.js +28 -1
- package/dist/line/client/obs-media-client.js +2 -1
- package/dist/line/client/square-service/client.js +298 -0
- package/dist/line/client/talk-service/command-client.js +9 -1
- package/dist/line/client/talk-service/message-id-query.js +27 -3
- package/dist/line/client/talk-service/requests.js +9 -0
- package/dist/line/core/auth-session-runtime.js +1 -1
- package/dist/line/core/chat-event.js +2 -0
- package/dist/line/core/chat-runtime-service.js +18 -3
- package/dist/line/core/config.js +1 -0
- package/dist/line/core/flex-message.js +323 -0
- package/dist/line/core/service.js +0 -1
- package/dist/mcp/handlers/contacts.js +12 -6
- package/dist/mcp/handlers/conversations.js +54 -0
- package/dist/mcp/handlers/send.js +8 -11
- package/dist/mcp/media.js +101 -2
- package/dist/mcp/names.js +78 -6
- package/dist/search/default-embedder.js +12 -3
- package/dist/version.js +1 -1
- package/dist/work-item/adapter.js +1 -0
- package/dist/work-item/destination-contracts.js +1 -0
- package/dist/work-item/event-context.js +73 -0
- package/dist/work-item/gold-contract.js +101 -0
- package/dist/work-item/gold-coverage.js +96 -0
- package/dist/work-item/gold-derivation.js +67 -0
- package/dist/work-item/gold-split.js +143 -0
- package/dist/work-item/index.js +23 -0
- package/dist/work-item/line-adapter.js +111 -0
- package/dist/work-item/llama-cpp-work-event-adapter.js +239 -0
- package/dist/work-item/local-provider.js +38 -0
- package/dist/work-item/local-work-event-provider.js +56 -0
- package/dist/work-item/onnx-work-event-adapter.js +223 -0
- package/dist/work-item/provider.js +21 -0
- package/dist/work-item/registry.js +19 -0
- package/dist/work-item/service.js +85 -0
- package/dist/work-item/store.js +97 -0
- package/dist/work-item/types.js +79 -0
- package/dist/work-item/work-event-graph-engine.js +63 -0
- package/dist/work-item/work-event-inference.js +267 -0
- package/dist/work-item/work-event-matcher.js +105 -0
- package/dist/work-item/work-event-model-manifest.js +39 -0
- package/dist/work-item/work-event-store.js +125 -0
- package/dist/work-item/work-event.js +123 -0
- package/package.json +24 -3
- package/run.mjs +162 -9
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
export const CURRENT_SUMMARY_ENGINE_VERSION = 1;
|
|
3
|
+
const EPISODE_GAP_MS = 45 * 60 * 1000; // 45 minutes
|
|
4
|
+
const ROLLUP_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours
|
|
5
|
+
function computeDigest(content) {
|
|
6
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
7
|
+
}
|
|
8
|
+
function inferDialogueActs(events) {
|
|
9
|
+
const acts = new Set();
|
|
10
|
+
for (const event of events) {
|
|
11
|
+
const text = event.text ?? '';
|
|
12
|
+
const isQuestion = /[??]/.test(text);
|
|
13
|
+
if (isQuestion) {
|
|
14
|
+
acts.add('ask');
|
|
15
|
+
}
|
|
16
|
+
if (['image', 'video', 'file'].includes(event.contentType)) {
|
|
17
|
+
acts.add('deliver');
|
|
18
|
+
}
|
|
19
|
+
if (event.mentionsSelf || (event.metadata.mentionMids?.length ?? 0) > 0) {
|
|
20
|
+
acts.add('directive');
|
|
21
|
+
}
|
|
22
|
+
if (event.contentType === 'sticker') {
|
|
23
|
+
acts.add('acknowledge');
|
|
24
|
+
}
|
|
25
|
+
if (text.length > 0 && !isQuestion) {
|
|
26
|
+
acts.add('inform');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return [...acts];
|
|
30
|
+
}
|
|
31
|
+
export function buildEpisodeSummaries(events, engineVersion = CURRENT_SUMMARY_ENGINE_VERSION) {
|
|
32
|
+
if (events.length === 0)
|
|
33
|
+
return [];
|
|
34
|
+
const byChat = new Map();
|
|
35
|
+
for (const event of events) {
|
|
36
|
+
const list = byChat.get(event.chatId) ?? [];
|
|
37
|
+
list.push(event);
|
|
38
|
+
byChat.set(event.chatId, list);
|
|
39
|
+
}
|
|
40
|
+
const summaries = [];
|
|
41
|
+
for (const [chatId, chatEvents] of byChat) {
|
|
42
|
+
const sorted = [...chatEvents].sort((a, b) => a.sentAt - b.sentAt);
|
|
43
|
+
let currentEpisode = [];
|
|
44
|
+
const flush = () => {
|
|
45
|
+
if (currentEpisode.length === 0)
|
|
46
|
+
return;
|
|
47
|
+
const minSentAt = currentEpisode[0].sentAt;
|
|
48
|
+
const maxSentAt = currentEpisode[currentEpisode.length - 1].sentAt;
|
|
49
|
+
const messageIds = currentEpisode.map((e) => e.messageId);
|
|
50
|
+
const participantNames = [
|
|
51
|
+
...new Set(currentEpisode
|
|
52
|
+
.map((e) => e.senderName)
|
|
53
|
+
.filter((n) => Boolean(n))),
|
|
54
|
+
];
|
|
55
|
+
const ownerHints = [
|
|
56
|
+
...new Set(currentEpisode.flatMap((e) => e.metadata.mentionNames ?? [])),
|
|
57
|
+
];
|
|
58
|
+
const hasMentionsSelf = currentEpisode.some((e) => e.mentionsSelf);
|
|
59
|
+
const hasQuestions = currentEpisode.some((e) => /[??]/.test(e.text ?? ''));
|
|
60
|
+
const hasMedia = currentEpisode.some((e) => ['image', 'video', 'file', 'audio'].includes(e.contentType));
|
|
61
|
+
const dialogueActs = inferDialogueActs(currentEpisode);
|
|
62
|
+
const hasSelfQuestion = currentEpisode.some((e) => e.isSelf && /[??]/.test(e.text ?? ''));
|
|
63
|
+
const selfQuestionIdx = currentEpisode.findIndex((e) => e.isSelf && /[??]/.test(e.text ?? ''));
|
|
64
|
+
const hasReplyAfterSelfQuestion = selfQuestionIdx >= 0 &&
|
|
65
|
+
currentEpisode.slice(selfQuestionIdx + 1).some((e) => !e.isSelf);
|
|
66
|
+
const responseState = hasReplyAfterSelfQuestion
|
|
67
|
+
? 'received'
|
|
68
|
+
: hasSelfQuestion
|
|
69
|
+
? 'waiting'
|
|
70
|
+
: 'observing';
|
|
71
|
+
const digest = computeDigest(`${chatId}:${minSentAt}:${messageIds.join(',')}`);
|
|
72
|
+
const episodeId = `ep-${chatId.slice(0, 8)}-${minSentAt}`;
|
|
73
|
+
summaries.push({
|
|
74
|
+
episodeId,
|
|
75
|
+
chatId,
|
|
76
|
+
minSentAt,
|
|
77
|
+
maxSentAt,
|
|
78
|
+
messageIds,
|
|
79
|
+
participantNames,
|
|
80
|
+
dialogueActs,
|
|
81
|
+
ownerHints,
|
|
82
|
+
responseState,
|
|
83
|
+
hasMentionsSelf,
|
|
84
|
+
hasQuestions,
|
|
85
|
+
hasMedia,
|
|
86
|
+
candidateCount: hasMentionsSelf || hasSelfQuestion ? 1 : 0,
|
|
87
|
+
digest,
|
|
88
|
+
engineVersion,
|
|
89
|
+
});
|
|
90
|
+
currentEpisode = [];
|
|
91
|
+
};
|
|
92
|
+
for (const event of sorted) {
|
|
93
|
+
if (currentEpisode.length > 0 &&
|
|
94
|
+
event.sentAt - currentEpisode[currentEpisode.length - 1].sentAt >
|
|
95
|
+
EPISODE_GAP_MS) {
|
|
96
|
+
flush();
|
|
97
|
+
}
|
|
98
|
+
currentEpisode.push(event);
|
|
99
|
+
}
|
|
100
|
+
flush();
|
|
101
|
+
}
|
|
102
|
+
return summaries.sort((a, b) => a.minSentAt - b.minSentAt);
|
|
103
|
+
}
|
|
104
|
+
export function buildRollupSummary(windowStart, windowEnd, episodes, engineVersion = CURRENT_SUMMARY_ENGINE_VERSION) {
|
|
105
|
+
const matching = episodes.filter((ep) => ep.maxSentAt >= windowStart && ep.minSentAt <= windowEnd);
|
|
106
|
+
const activeChatIds = [...new Set(matching.map((ep) => ep.chatId))];
|
|
107
|
+
const episodeIds = matching.map((ep) => ep.episodeId);
|
|
108
|
+
const totalEvents = matching.reduce((sum, ep) => sum + ep.messageIds.length, 0);
|
|
109
|
+
const unresolvedFrontierCount = matching.filter((ep) => ep.responseState === 'waiting' || ep.hasMentionsSelf).length;
|
|
110
|
+
const topics = matching
|
|
111
|
+
.filter((ep) => ep.dialogueActs.length > 0)
|
|
112
|
+
.map((ep) => `${ep.chatId}:${ep.dialogueActs.join('+')}`)
|
|
113
|
+
.slice(0, 10);
|
|
114
|
+
const rollupId = `rollup-${Math.floor(windowStart / ROLLUP_INTERVAL_MS)}`;
|
|
115
|
+
const digest = computeDigest(`${rollupId}:${episodeIds.join(',')}:${totalEvents}`);
|
|
116
|
+
return {
|
|
117
|
+
rollupId,
|
|
118
|
+
windowStart,
|
|
119
|
+
windowEnd,
|
|
120
|
+
activeChatIds,
|
|
121
|
+
episodeIds,
|
|
122
|
+
totalEvents,
|
|
123
|
+
unresolvedFrontierCount,
|
|
124
|
+
topics,
|
|
125
|
+
digest,
|
|
126
|
+
engineVersion,
|
|
127
|
+
};
|
|
128
|
+
}
|
package/dist/attention/policy.js
CHANGED
|
@@ -49,23 +49,29 @@ export async function scoreAttentionBatch(inputs, embedder = getDefaultEmbedder(
|
|
|
49
49
|
const messageVectors = vectors.slice(anchors.length);
|
|
50
50
|
return inputs.map((input, inputIndex) => {
|
|
51
51
|
if (input.isMuted)
|
|
52
|
-
return { score: -100, tier: 'filtered', reason: '
|
|
52
|
+
return { score: -100, tier: 'filtered', reason: 'muted' };
|
|
53
53
|
if (input.explicitPriority)
|
|
54
|
-
return { score: 70, tier: 'now', reason: '
|
|
54
|
+
return { score: 70, tier: 'now', reason: 'manual_priority' };
|
|
55
55
|
if (input.hasMention)
|
|
56
|
-
return { score: 65, tier: 'now', reason: '
|
|
56
|
+
return { score: 65, tier: 'now', reason: 'direct_mention' };
|
|
57
|
+
if (input.isOfficial &&
|
|
58
|
+
/^\[yomi:(?:image|rich|sticker|video|audio|file|extended_image)\]$/.test(input.text.trim())) {
|
|
59
|
+
return { score: 0, tier: 'filtered', reason: 'ordinary' };
|
|
60
|
+
}
|
|
57
61
|
const similarities = new Map();
|
|
58
62
|
anchors.forEach((anchor, anchorIndex) => {
|
|
59
63
|
const similarity = dot(messageVectors[inputIndex], anchorVectors[anchorIndex]);
|
|
60
64
|
similarities.set(anchor.intent, Math.max(similarities.get(anchor.intent) ?? -Infinity, similarity));
|
|
61
65
|
});
|
|
62
66
|
const ranked = [...intents].sort((a, b) => (similarities.get(b) ?? 0) - (similarities.get(a) ?? 0));
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const intent = ranked[0];
|
|
68
|
+
if (input.isOfficial && intent !== 'transaction') {
|
|
69
|
+
return {
|
|
70
|
+
score: -40,
|
|
71
|
+
tier: 'filtered',
|
|
72
|
+
reason: intent === 'promotion' ? 'promotion' : 'ordinary',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
69
75
|
const scoreByIntent = {
|
|
70
76
|
reply: 55,
|
|
71
77
|
deadline: 60,
|
|
@@ -74,7 +80,7 @@ export async function scoreAttentionBatch(inputs, embedder = getDefaultEmbedder(
|
|
|
74
80
|
ordinary: 0,
|
|
75
81
|
promotion: -40,
|
|
76
82
|
};
|
|
77
|
-
const score = scoreByIntent[intent]
|
|
83
|
+
const score = scoreByIntent[intent];
|
|
78
84
|
const tier = score >= 55
|
|
79
85
|
? 'now'
|
|
80
86
|
: score >= 35
|
|
@@ -83,12 +89,12 @@ export async function scoreAttentionBatch(inputs, embedder = getDefaultEmbedder(
|
|
|
83
89
|
? 'know'
|
|
84
90
|
: 'filtered';
|
|
85
91
|
const reasons = {
|
|
86
|
-
reply: '
|
|
87
|
-
deadline: '
|
|
88
|
-
transaction: '
|
|
89
|
-
important: '
|
|
90
|
-
ordinary: '
|
|
91
|
-
promotion: '
|
|
92
|
+
reply: 'reply_expected',
|
|
93
|
+
deadline: 'deadline',
|
|
94
|
+
transaction: 'transaction',
|
|
95
|
+
important: 'important',
|
|
96
|
+
ordinary: 'ordinary',
|
|
97
|
+
promotion: 'promotion',
|
|
92
98
|
};
|
|
93
99
|
return { score, tier, reason: reasons[intent] };
|
|
94
100
|
});
|
|
@@ -86,7 +86,7 @@ class VolatileCacheStore {
|
|
|
86
86
|
cache = new Map();
|
|
87
87
|
loaded = false;
|
|
88
88
|
dirty = false;
|
|
89
|
-
|
|
89
|
+
flushPromise = null;
|
|
90
90
|
constructor(filePath) {
|
|
91
91
|
this.filePath = filePath;
|
|
92
92
|
}
|
|
@@ -139,11 +139,10 @@ class VolatileCacheStore {
|
|
|
139
139
|
/** Coalesce bursts of writes into a single atomic flush cycle. */
|
|
140
140
|
scheduleFlush() {
|
|
141
141
|
this.dirty = true;
|
|
142
|
-
if (this.
|
|
142
|
+
if (this.flushPromise) {
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
|
-
this.
|
|
146
|
-
void (async () => {
|
|
145
|
+
this.flushPromise = (async () => {
|
|
147
146
|
const fs = await import('node:fs/promises');
|
|
148
147
|
while (this.dirty) {
|
|
149
148
|
this.dirty = false;
|
|
@@ -169,8 +168,13 @@ class VolatileCacheStore {
|
|
|
169
168
|
// Best-effort cache persistence; in-memory copy still serves reads.
|
|
170
169
|
}
|
|
171
170
|
}
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
})().finally(() => {
|
|
172
|
+
this.flushPromise = null;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async flush() {
|
|
176
|
+
while (this.flushPromise)
|
|
177
|
+
await this.flushPromise;
|
|
174
178
|
}
|
|
175
179
|
}
|
|
176
180
|
/**
|
|
@@ -361,6 +365,11 @@ export class CredentialStore {
|
|
|
361
365
|
return persisted;
|
|
362
366
|
});
|
|
363
367
|
}
|
|
368
|
+
/** Await durable and volatile write queues, primarily for orderly shutdown. */
|
|
369
|
+
async flush() {
|
|
370
|
+
await this.persistQueue;
|
|
371
|
+
await this.volatile.flush();
|
|
372
|
+
}
|
|
364
373
|
/**
|
|
365
374
|
* Delete a credential and persist.
|
|
366
375
|
* @param key - Credential key to delete.
|