evolcore 0.0.3 → 0.0.5
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 +83 -0
- package/README.md +45 -10
- package/bin/ec-safe-output.js +89 -24
- package/dist/agents/baseagent.js +46 -0
- package/dist/agents/claude-runner.js +1 -31
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +157 -5
- package/dist/agents/runner-types.js +2 -2
- package/dist/aun/aid/agentmd.js +79 -0
- package/dist/aun/aid/encryption-seed-policy.js +29 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +2 -5
- package/dist/channels/aun.js +220 -112
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +52 -137
- package/dist/channels/feishu.js +56 -4
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +383 -7
- package/dist/cli/aun-commands.js +11 -11
- package/dist/cli/daemon-commands.js +46 -15
- package/dist/cli/handoff-command.js +2 -1
- package/dist/cli/init-channel.js +185 -67
- package/dist/cli/init.js +49 -27
- package/dist/cli/trigger-command.js +92 -11
- package/dist/config/access-policy-domain.js +18 -0
- package/dist/config/access-policy.js +110 -0
- package/dist/config/builtin-role-templates.js +27 -14
- package/dist/config/builtin-roles.js +25 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +198 -22
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +173 -5
- package/dist/config/contact-book.js +32 -0
- package/dist/config/contact-operation-service.js +9 -3
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +3 -1
- package/dist/config/schema-registry.js +49 -24
- package/dist/config-store.js +14 -2
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +6 -0
- package/dist/core/auth/operation-authorizer.js +8 -0
- package/dist/core/auth/operation-catalog.js +51 -3
- package/dist/core/auth/trigger-authorization.js +15 -0
- package/dist/core/bootstrap-service.js +2 -9
- package/dist/core/channel-loader.js +6 -2
- package/dist/core/command/command-handler.js +10 -13
- package/dist/core/command/connect-menu.js +229 -26
- package/dist/core/command/evol-menu-version-gate.js +38 -0
- package/dist/core/command/group-menu.js +421 -0
- package/dist/core/command/menu-handler.js +269 -85
- package/dist/core/command/menu-protocol.js +8 -2
- package/dist/core/command/menu-token-store.js +102 -0
- package/dist/core/command/role-menu.js +16 -0
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +158 -31
- package/dist/core/daemon-file-cache.js +28 -0
- package/dist/core/event-catalog.js +29 -0
- package/dist/core/evolagent-registry.js +4 -39
- package/dist/core/handoff/runtime.js +162 -37
- package/dist/core/handoff/store.js +46 -2
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +16 -74
- package/dist/core/message/file-markers.js +7 -0
- package/dist/core/message/im-renderer.js +18 -14
- package/dist/core/message/inbound-admission.js +134 -0
- package/dist/core/message/message-bridge.js +616 -74
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +185 -10
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +381 -79
- package/dist/core/permission/approval-gateway.js +17 -10
- package/dist/core/permission/ec-command-parser.js +101 -46
- package/dist/core/permission/tool-policy.js +69 -24
- package/dist/core/protected-paths.js +36 -11
- package/dist/core/session/session-fs-store.js +19 -4
- package/dist/core/session/session-manager.js +232 -4
- package/dist/core/session/session-mapper.js +2 -0
- package/dist/core/session/session-renew.js +125 -69
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/eck/kit-renderer.js +12 -1
- package/dist/eck/message-renderer.js +79 -1
- package/dist/index.js +224 -89
- package/dist/ipc.js +81 -4
- package/dist/paths.js +3 -0
- package/dist/response-system/config-resolver.js +29 -0
- package/dist/response-system/coordinator.js +8 -32
- package/dist/response-system/engines/v1/proactive-flow.js +1 -1
- package/dist/response-system/index.js +1 -0
- package/dist/response-system/modes/single-session/index.js +7 -4
- package/dist/trigger/parser.js +55 -15
- package/dist/trigger/patch.js +4 -1
- package/dist/trigger/scheduler.js +441 -39
- package/dist/utils/cross-platform.js +8 -2
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/logger.js +2 -2
- package/dist/utils/process-introspect.js +19 -3
- package/dist/utils/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/kits/docs/channels/aun.md +4 -13
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/config.md +47 -2
- package/kits/docs/evolcore/contact.md +8 -3
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +4 -4
- package/kits/docs/evolcore/msg.md +5 -5
- package/kits/docs/evolcore/trigger.md +25 -8
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +7 -0
- package/kits/rules/01-overview.md +17 -7
- package/kits/rules/02-navigation.md +38 -18
- package/kits/rules/03-identity.md +28 -24
- package/kits/rules/04-relation.md +44 -28
- package/kits/rules/05-venue.md +31 -15
- package/kits/rules/06-channel.md +11 -7
- package/kits/schemas/_meta.json +18 -7
- package/kits/schemas/agent-config.schema.7.json +303 -0
- package/kits/schemas/agent-config.schema.8.json +304 -0
- package/kits/schemas/agent-config.schema.9.json +364 -0
- package/kits/schemas/contact-book.schema.3.json +67 -0
- package/kits/schemas/daemon.schema.1.json +3 -2
- package/kits/schemas/daemon.schema.2.json +101 -0
- package/kits/schemas/daemon.schema.3.json +123 -0
- package/kits/schemas/defaults.schema.2.json +85 -0
- package/kits/schemas/defaults.schema.3.json +73 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/single-session.schema.2.json +57 -0
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
- package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
- package/kits/templates/roles/admin.json +46 -0
- package/kits/templates/roles/member.json +4 -0
- package/kits/templates/roles/visitor.json +4 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/identity.md +3 -1
- package/kits/templates/system-fragments/relation.md +1 -1
- package/kits/templates/system-fragments/session.md +6 -2
- package/package.json +4 -2
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- package/dist/channels/contact-bind-code.js +0 -134
- package/dist/channels/wecom-card.js +0 -101
- package/dist/channels/wecom-onboarding.js +0 -82
- package/dist/channels/wecom-state.js +0 -191
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
function truncate(value, max) {
|
|
2
|
-
if (!value)
|
|
3
|
-
return undefined;
|
|
4
|
-
const chars = [...value.trim()];
|
|
5
|
-
return chars.length <= max ? value.trim() : `${chars.slice(0, Math.max(1, max - 1)).join('')}...`;
|
|
6
|
-
}
|
|
7
|
-
function buttonStyle(style) {
|
|
8
|
-
if (style === 'primary')
|
|
9
|
-
return 2;
|
|
10
|
-
if (style === 'danger')
|
|
11
|
-
return 3;
|
|
12
|
-
return 1;
|
|
13
|
-
}
|
|
14
|
-
export function buildWecomInteractionCard(interaction, taskId) {
|
|
15
|
-
const bodyParts = [];
|
|
16
|
-
if (interaction.kind.body)
|
|
17
|
-
bodyParts.push(interaction.kind.body);
|
|
18
|
-
if (interaction.kind.kind === 'action' && interaction.kind.checkers?.length) {
|
|
19
|
-
bodyParts.push(interaction.kind.checkers.map((item, index) => `${index + 1}. ${item.label}`).join('\n'));
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
card_type: 'button_interaction',
|
|
23
|
-
task_id: taskId,
|
|
24
|
-
main_title: {
|
|
25
|
-
title: truncate(interaction.kind.title, 26),
|
|
26
|
-
desc: truncate(bodyParts.join('\n\n'), 120),
|
|
27
|
-
},
|
|
28
|
-
button_list: interaction.kind.buttons
|
|
29
|
-
.map((button, index) => ({ button, index }))
|
|
30
|
-
.filter(({ button }) => !('disabled' in button) || !button.disabled)
|
|
31
|
-
.slice(0, 6)
|
|
32
|
-
.map(({ button, index }) => ({
|
|
33
|
-
text: truncate(button.label, 10) || `选项 ${index + 1}`,
|
|
34
|
-
style: buttonStyle(button.style),
|
|
35
|
-
key: interaction.kind.kind === 'command-card' ? `cmd_${index}` : `act_${index}`,
|
|
36
|
-
})),
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
export function parseWecomCardResponse(record, event, operatorId) {
|
|
40
|
-
const eventKey = String(event.event_key ?? '').trim();
|
|
41
|
-
const match = /^(cmd|act)_(\d+)$/.exec(eventKey);
|
|
42
|
-
if (!match)
|
|
43
|
-
return null;
|
|
44
|
-
const index = Number(match[2]);
|
|
45
|
-
if (record.interaction.kind.kind === 'command-card') {
|
|
46
|
-
const button = record.interaction.kind.buttons[index];
|
|
47
|
-
if (!button || button.disabled)
|
|
48
|
-
return null;
|
|
49
|
-
return { command: button.command };
|
|
50
|
-
}
|
|
51
|
-
const button = record.interaction.kind.buttons[index];
|
|
52
|
-
if (!button)
|
|
53
|
-
return null;
|
|
54
|
-
const values = {};
|
|
55
|
-
const selectedItems = event.selected_items?.selected_item;
|
|
56
|
-
if (Array.isArray(selectedItems)) {
|
|
57
|
-
for (const selected of selectedItems) {
|
|
58
|
-
const questionKey = String(selected?.question_key ?? '').trim();
|
|
59
|
-
const optionIds = selected?.option_ids?.option_id;
|
|
60
|
-
if (questionKey && Array.isArray(optionIds))
|
|
61
|
-
values[questionKey] = optionIds.filter(Boolean);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
response: {
|
|
66
|
-
type: 'interaction.response',
|
|
67
|
-
id: record.interaction.id,
|
|
68
|
-
action: button.key,
|
|
69
|
-
values: Object.keys(values).length > 0 ? values : undefined,
|
|
70
|
-
operatorId,
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
export function buildWecomTerminalCard(record, status, detail) {
|
|
75
|
-
const statusText = status === 'resolved'
|
|
76
|
-
? `已处理${detail ? `:${detail}` : ''}`
|
|
77
|
-
: status === 'rejected'
|
|
78
|
-
? '仅卡片发起者可操作'
|
|
79
|
-
: status === 'unavailable'
|
|
80
|
-
? '处理服务暂不可用,请重新发起'
|
|
81
|
-
: `卡片已失效${detail ? `:${detail}` : ''}`;
|
|
82
|
-
return {
|
|
83
|
-
card_type: 'text_notice',
|
|
84
|
-
task_id: record.taskId,
|
|
85
|
-
main_title: {
|
|
86
|
-
title: truncate(record.interaction.kind.title, 26),
|
|
87
|
-
desc: truncate(statusText, 120),
|
|
88
|
-
},
|
|
89
|
-
sub_title_text: truncate(record.interaction.kind.body, 120),
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
export function buildWecomUnknownCard(taskId) {
|
|
93
|
-
return {
|
|
94
|
-
card_type: 'text_notice',
|
|
95
|
-
task_id: taskId,
|
|
96
|
-
main_title: {
|
|
97
|
-
title: '卡片已失效',
|
|
98
|
-
desc: '本地状态不存在,请重新发起操作',
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
const WECOM_QR_GENERATE_URL = 'https://work.weixin.qq.com/ai/qc/generate';
|
|
3
|
-
const WECOM_QR_QUERY_URL = 'https://work.weixin.qq.com/ai/qc/query_result';
|
|
4
|
-
const WECOM_QR_PAGE_URL = 'https://work.weixin.qq.com/ai/qc/gen';
|
|
5
|
-
function platformCode() {
|
|
6
|
-
if (os.platform() === 'darwin')
|
|
7
|
-
return 1;
|
|
8
|
-
if (os.platform() === 'win32')
|
|
9
|
-
return 2;
|
|
10
|
-
if (os.platform() === 'linux')
|
|
11
|
-
return 3;
|
|
12
|
-
return 0;
|
|
13
|
-
}
|
|
14
|
-
async function fetchJson(url, fetchImpl, signal) {
|
|
15
|
-
const timeoutSignal = AbortSignal.timeout(15_000);
|
|
16
|
-
const response = await fetchImpl(url, {
|
|
17
|
-
headers: { Accept: 'application/json' },
|
|
18
|
-
signal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,
|
|
19
|
-
});
|
|
20
|
-
if (!response.ok)
|
|
21
|
-
throw new Error(`企微扫码服务请求失败: HTTP ${response.status}`);
|
|
22
|
-
return response.json();
|
|
23
|
-
}
|
|
24
|
-
export async function createWecomQrSession(fetchImpl = fetch) {
|
|
25
|
-
const url = new URL(WECOM_QR_GENERATE_URL);
|
|
26
|
-
url.searchParams.set('source', 'wecom-cli');
|
|
27
|
-
url.searchParams.set('plat', String(platformCode()));
|
|
28
|
-
const payload = await fetchJson(url, fetchImpl);
|
|
29
|
-
const scode = String(payload?.data?.scode ?? '').trim();
|
|
30
|
-
const authUrl = String(payload?.data?.auth_url ?? '').trim();
|
|
31
|
-
if (!scode || !authUrl)
|
|
32
|
-
throw new Error('企微扫码服务未返回 scode 或授权地址');
|
|
33
|
-
const browserUrl = new URL(WECOM_QR_PAGE_URL);
|
|
34
|
-
browserUrl.searchParams.set('source', 'wecom-cli');
|
|
35
|
-
browserUrl.searchParams.set('scode', scode);
|
|
36
|
-
return { scode, authUrl, browserUrl: browserUrl.toString() };
|
|
37
|
-
}
|
|
38
|
-
export async function pollWecomQrCredentials(scode, opts) {
|
|
39
|
-
const fetchImpl = opts?.fetchImpl ?? fetch;
|
|
40
|
-
const timeoutMs = opts?.timeoutMs ?? 5 * 60 * 1000;
|
|
41
|
-
const intervalMs = opts?.intervalMs ?? 3_000;
|
|
42
|
-
const deadline = Date.now() + timeoutMs;
|
|
43
|
-
const url = new URL(WECOM_QR_QUERY_URL);
|
|
44
|
-
url.searchParams.set('scode', scode);
|
|
45
|
-
let previousStatus = '';
|
|
46
|
-
while (Date.now() < deadline) {
|
|
47
|
-
if (opts?.signal?.aborted)
|
|
48
|
-
throw new Error('企微扫码已取消');
|
|
49
|
-
const payload = await fetchJson(url, fetchImpl, opts?.signal);
|
|
50
|
-
const status = String(payload?.data?.status ?? 'waiting');
|
|
51
|
-
if (status !== previousStatus) {
|
|
52
|
-
opts?.onStatus?.(status);
|
|
53
|
-
previousStatus = status;
|
|
54
|
-
}
|
|
55
|
-
if (status === 'success') {
|
|
56
|
-
const botId = String(payload?.data?.bot_info?.botid ?? '').trim();
|
|
57
|
-
const secret = String(payload?.data?.bot_info?.secret ?? '').trim();
|
|
58
|
-
if (!botId || !secret)
|
|
59
|
-
throw new Error('扫码成功,但企微未返回 Bot ID 或 Secret');
|
|
60
|
-
return { botId, secret };
|
|
61
|
-
}
|
|
62
|
-
if (status === 'expired' || status === 'cancelled' || status === 'rejected') {
|
|
63
|
-
throw new Error(`企微扫码未完成: ${status}`);
|
|
64
|
-
}
|
|
65
|
-
await new Promise((resolve, reject) => {
|
|
66
|
-
const finish = () => {
|
|
67
|
-
opts?.signal?.removeEventListener('abort', onAbort);
|
|
68
|
-
resolve();
|
|
69
|
-
};
|
|
70
|
-
const timer = setTimeout(finish, intervalMs);
|
|
71
|
-
const onAbort = () => {
|
|
72
|
-
clearTimeout(timer);
|
|
73
|
-
opts?.signal?.removeEventListener('abort', onAbort);
|
|
74
|
-
reject(new Error('企微扫码已取消'));
|
|
75
|
-
};
|
|
76
|
-
opts?.signal?.addEventListener('abort', onAbort, { once: true });
|
|
77
|
-
if (opts?.signal?.aborted)
|
|
78
|
-
onAbort();
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
throw new Error('企微扫码超时,请重新生成二维码');
|
|
82
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { agentDataDir } from '../paths.js';
|
|
5
|
-
import { atomicReadJson, atomicWriteJson } from '../utils/atomic-write.js';
|
|
6
|
-
import { logger } from '../utils/logger.js';
|
|
7
|
-
const STORE_VERSION = 1;
|
|
8
|
-
const DEFAULT_DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
|
|
9
|
-
const DEFAULT_CARD_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
10
|
-
function writePrivateJson(filePath, value) {
|
|
11
|
-
atomicWriteJson(filePath, value);
|
|
12
|
-
try {
|
|
13
|
-
fs.chmodSync(filePath, 0o600);
|
|
14
|
-
}
|
|
15
|
-
catch { /* best effort on non-POSIX filesystems */ }
|
|
16
|
-
}
|
|
17
|
-
export class PersistentWecomDeduper {
|
|
18
|
-
filePath;
|
|
19
|
-
channelKey;
|
|
20
|
-
entries = new Map();
|
|
21
|
-
ttlMs;
|
|
22
|
-
maxEntries;
|
|
23
|
-
constructor(filePath, channelKey, opts) {
|
|
24
|
-
this.filePath = filePath;
|
|
25
|
-
this.channelKey = channelKey;
|
|
26
|
-
this.ttlMs = opts?.ttlMs ?? DEFAULT_DEDUP_TTL_MS;
|
|
27
|
-
this.maxEntries = opts?.maxEntries ?? 20_000;
|
|
28
|
-
this.load();
|
|
29
|
-
}
|
|
30
|
-
checkAndMark(messageId, now = Date.now()) {
|
|
31
|
-
this.evict(now);
|
|
32
|
-
const seenAt = this.entries.get(messageId);
|
|
33
|
-
if (seenAt !== undefined && now - seenAt <= this.ttlMs)
|
|
34
|
-
return true;
|
|
35
|
-
this.entries.set(messageId, now);
|
|
36
|
-
this.trimToLimit();
|
|
37
|
-
this.flush();
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
get size() {
|
|
41
|
-
return this.entries.size;
|
|
42
|
-
}
|
|
43
|
-
load() {
|
|
44
|
-
if (!this.filePath)
|
|
45
|
-
return;
|
|
46
|
-
try {
|
|
47
|
-
const stored = atomicReadJson(this.filePath);
|
|
48
|
-
if (!stored || stored.version !== STORE_VERSION || stored.channelKey !== this.channelKey)
|
|
49
|
-
return;
|
|
50
|
-
for (const [id, timestamp] of Object.entries(stored.entries ?? {})) {
|
|
51
|
-
if (id && Number.isFinite(timestamp))
|
|
52
|
-
this.entries.set(id, timestamp);
|
|
53
|
-
}
|
|
54
|
-
const before = this.entries.size;
|
|
55
|
-
this.evict(Date.now());
|
|
56
|
-
this.trimToLimit();
|
|
57
|
-
if (this.entries.size !== before)
|
|
58
|
-
this.flush();
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
logger.warn(`[WeCom] Ignoring unreadable dedup store for ${this.channelKey}: ${String(error)}`);
|
|
62
|
-
this.entries.clear();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
evict(now) {
|
|
66
|
-
for (const [id, timestamp] of this.entries) {
|
|
67
|
-
if (now - timestamp > this.ttlMs)
|
|
68
|
-
this.entries.delete(id);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
trimToLimit() {
|
|
72
|
-
if (this.entries.size <= this.maxEntries)
|
|
73
|
-
return;
|
|
74
|
-
const oldest = [...this.entries.entries()].sort((a, b) => a[1] - b[1]);
|
|
75
|
-
for (let i = 0; i < oldest.length - this.maxEntries; i++) {
|
|
76
|
-
this.entries.delete(oldest[i][0]);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
flush() {
|
|
80
|
-
writePrivateJson(this.filePath, {
|
|
81
|
-
version: STORE_VERSION,
|
|
82
|
-
channelKey: this.channelKey,
|
|
83
|
-
entries: Object.fromEntries(this.entries),
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
export class PersistentWecomCardStore {
|
|
88
|
-
filePath;
|
|
89
|
-
channelKey;
|
|
90
|
-
records = new Map();
|
|
91
|
-
constructor(filePath, channelKey) {
|
|
92
|
-
this.filePath = filePath;
|
|
93
|
-
this.channelKey = channelKey;
|
|
94
|
-
if (filePath)
|
|
95
|
-
this.load();
|
|
96
|
-
}
|
|
97
|
-
set(record) {
|
|
98
|
-
this.records.set(record.taskId, record);
|
|
99
|
-
this.cleanup();
|
|
100
|
-
this.flush();
|
|
101
|
-
}
|
|
102
|
-
get(taskId) {
|
|
103
|
-
this.cleanup();
|
|
104
|
-
return this.records.get(taskId);
|
|
105
|
-
}
|
|
106
|
-
findByInteractionId(interactionId) {
|
|
107
|
-
this.cleanup();
|
|
108
|
-
return [...this.records.values()].find(record => record.interaction.id === interactionId);
|
|
109
|
-
}
|
|
110
|
-
listPendingByChat(chatId) {
|
|
111
|
-
this.cleanup();
|
|
112
|
-
return [...this.records.values()].filter(record => (record.chatId === chatId
|
|
113
|
-
&& record.status === 'pending'
|
|
114
|
-
&& (!record.expiresAt || record.expiresAt >= Date.now())));
|
|
115
|
-
}
|
|
116
|
-
resolve(taskId, action) {
|
|
117
|
-
const record = this.records.get(taskId);
|
|
118
|
-
if (!record)
|
|
119
|
-
return undefined;
|
|
120
|
-
record.status = 'resolved';
|
|
121
|
-
record.resolution = action;
|
|
122
|
-
record.updatedAt = Date.now();
|
|
123
|
-
this.flush();
|
|
124
|
-
return record;
|
|
125
|
-
}
|
|
126
|
-
invalidateByInteractionId(interactionId, reason) {
|
|
127
|
-
const record = this.findByInteractionId(interactionId);
|
|
128
|
-
if (!record || record.status === 'resolved')
|
|
129
|
-
return record;
|
|
130
|
-
record.status = 'invalidated';
|
|
131
|
-
record.invalidationReason = reason;
|
|
132
|
-
record.updatedAt = Date.now();
|
|
133
|
-
this.flush();
|
|
134
|
-
return record;
|
|
135
|
-
}
|
|
136
|
-
load() {
|
|
137
|
-
if (!this.filePath)
|
|
138
|
-
return;
|
|
139
|
-
try {
|
|
140
|
-
const stored = atomicReadJson(this.filePath);
|
|
141
|
-
if (!stored || stored.version !== STORE_VERSION || stored.channelKey !== this.channelKey)
|
|
142
|
-
return;
|
|
143
|
-
for (const record of stored.records ?? []) {
|
|
144
|
-
if (record?.taskId && record?.interaction?.id)
|
|
145
|
-
this.records.set(record.taskId, record);
|
|
146
|
-
}
|
|
147
|
-
this.cleanup(true);
|
|
148
|
-
}
|
|
149
|
-
catch (error) {
|
|
150
|
-
logger.warn(`[WeCom] Ignoring unreadable card store for ${this.channelKey}: ${String(error)}`);
|
|
151
|
-
this.records.clear();
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
cleanup(flushWhenChanged = false) {
|
|
155
|
-
const now = Date.now();
|
|
156
|
-
let changed = false;
|
|
157
|
-
for (const [taskId, record] of this.records) {
|
|
158
|
-
const terminalExpiry = record.updatedAt + DEFAULT_CARD_RETENTION_MS;
|
|
159
|
-
const pendingExpiry = (record.expiresAt ?? record.createdAt + 7 * DEFAULT_CARD_RETENTION_MS)
|
|
160
|
-
+ DEFAULT_CARD_RETENTION_MS;
|
|
161
|
-
const expiresAt = record.status === 'pending' ? pendingExpiry : terminalExpiry;
|
|
162
|
-
if (now > expiresAt) {
|
|
163
|
-
this.records.delete(taskId);
|
|
164
|
-
changed = true;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (changed && flushWhenChanged)
|
|
168
|
-
this.flush();
|
|
169
|
-
}
|
|
170
|
-
flush() {
|
|
171
|
-
if (!this.filePath)
|
|
172
|
-
return;
|
|
173
|
-
writePrivateJson(this.filePath, {
|
|
174
|
-
version: STORE_VERSION,
|
|
175
|
-
channelKey: this.channelKey,
|
|
176
|
-
records: [...this.records.values()],
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
export function wecomChannelDataPaths(agentAid, channelKey) {
|
|
181
|
-
const safeKey = encodeURIComponent(channelKey);
|
|
182
|
-
const dir = path.join(agentDataDir(agentAid), 'channels', safeKey);
|
|
183
|
-
return {
|
|
184
|
-
dedupFile: path.join(dir, 'wecom-message-dedup.json'),
|
|
185
|
-
cardFile: path.join(dir, 'wecom-cards.json'),
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
export function createWecomCardTaskId(channelKey, interactionId) {
|
|
189
|
-
const hash = crypto.createHash('sha256').update(`${channelKey}\0${interactionId}`).digest('hex').slice(0, 32);
|
|
190
|
-
return `ec_${hash}`;
|
|
191
|
-
}
|