larkin 0.2.70
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/CONTRIBUTING.md +48 -0
- package/LICENSE +201 -0
- package/README.md +122 -0
- package/SECURITY.md +14 -0
- package/artifacts/release/THIRD_PARTY_NOTICES.txt +5488 -0
- package/dist/agent/agent-cli-capabilities.cjs +43 -0
- package/dist/agent/agent-cli-capabilities.mjs +35 -0
- package/dist/agent/agent-state-store.cjs +961 -0
- package/dist/agent/agent-state-store.mjs +923 -0
- package/dist/agent/agent-transport.cjs +201 -0
- package/dist/agent/agent-transport.mjs +194 -0
- package/dist/agent/config-cli-contract.cjs +19 -0
- package/dist/agent/config-cli-contract.mjs +16 -0
- package/dist/agent/context-prompt.cjs +159 -0
- package/dist/agent/context-prompt.mjs +155 -0
- package/dist/agent/freshness-gate.mjs +24 -0
- package/dist/agent/history-projection.cjs +72 -0
- package/dist/agent/history-projection.mjs +65 -0
- package/dist/agent/host-reminder-orchestrator.cjs +289 -0
- package/dist/agent/host-reminder-orchestrator.mjs +252 -0
- package/dist/agent/inbox-projection.cjs +92 -0
- package/dist/agent/inbox-projection.mjs +85 -0
- package/dist/agent/interaction-state-machine.cjs +710 -0
- package/dist/agent/interaction-state-machine.mjs +705 -0
- package/dist/agent/reminder-routes.cjs +192 -0
- package/dist/agent/reminder-routes.mjs +156 -0
- package/dist/agent/reminder-store.cjs +220 -0
- package/dist/agent/reminder-store.mjs +205 -0
- package/dist/agent/transport-business-context.cjs +486 -0
- package/dist/agent/transport-business-context.mjs +446 -0
- package/dist/agent/transport-shell.cjs +45 -0
- package/dist/agent/transport-shell.mjs +39 -0
- package/dist/app/agent-cli.mjs +596 -0
- package/dist/app/agent-config.mjs +633 -0
- package/dist/app/agent-lark-cli-workspace.cjs +95 -0
- package/dist/app/agent-lark-cli-workspace.mjs +85 -0
- package/dist/app/agent-readiness.cjs +42 -0
- package/dist/app/agent-readiness.mjs +38 -0
- package/dist/app/binary-entry.mjs +86 -0
- package/dist/app/cli.mjs +176 -0
- package/dist/app/dashboard.mjs +205 -0
- package/dist/app/internal-command.cjs +57 -0
- package/dist/app/internal-command.mjs +48 -0
- package/dist/app/lark-channel-secret.mjs +22 -0
- package/dist/app/lark-cli.mjs +887 -0
- package/dist/app/lark.mjs +45 -0
- package/dist/app/local-control.mjs +728 -0
- package/dist/app/official-lark-cli.cjs +138 -0
- package/dist/app/official-lark-cli.mjs +128 -0
- package/dist/app/pi-auth-cli.mjs +67 -0
- package/dist/app/run.mjs +364 -0
- package/dist/app/runtime-agent-config.mjs +416 -0
- package/dist/app/runtime-model-directory.mjs +78 -0
- package/dist/app/runtime-process.mjs +113 -0
- package/dist/app/session-cli.mjs +83 -0
- package/dist/app/setup-dashboard.mjs +26 -0
- package/dist/app/setup.mjs +166 -0
- package/dist/app/telemetry.mjs +83 -0
- package/dist/dashboard/dashboard-assets.mjs +21 -0
- package/dist/dashboard/dashboard-avatar.mjs +128 -0
- package/dist/dashboard/dashboard-config-controller.mjs +525 -0
- package/dist/dashboard/dashboard-lifecycle.cjs +97 -0
- package/dist/dashboard/dashboard-lifecycle.mjs +88 -0
- package/dist/dashboard/dashboard-template.mjs +29 -0
- package/dist/dashboard/dashboard-view-model.mjs +556 -0
- package/dist/dashboard/dashboard-workspace.mjs +104 -0
- package/dist/dashboard/web/assets/dashboard.css +3 -0
- package/dist/dashboard/web/assets/dashboard.js +49 -0
- package/dist/feishu/channel-transport.cjs +151 -0
- package/dist/feishu/channel-transport.mjs +148 -0
- package/dist/feishu/document-comment.cjs +120 -0
- package/dist/feishu/document-comment.mjs +109 -0
- package/dist/feishu/host-business-state.cjs +477 -0
- package/dist/feishu/host-business-state.mjs +462 -0
- package/dist/feishu/host-channel-business.cjs +161 -0
- package/dist/feishu/host-channel-business.mjs +157 -0
- package/dist/feishu/host-processing-eye.cjs +185 -0
- package/dist/feishu/host-processing-eye.mjs +181 -0
- package/dist/feishu/host-shell.cjs +1425 -0
- package/dist/feishu/host-shell.mjs +1385 -0
- package/dist/feishu/im-freshness-adapter.mjs +89 -0
- package/dist/feishu/interaction-orchestrator.cjs +279 -0
- package/dist/feishu/interaction-orchestrator.mjs +242 -0
- package/dist/feishu/lark-passthrough.mjs +70 -0
- package/dist/feishu/message-policy.cjs +111 -0
- package/dist/feishu/message-policy.mjs +98 -0
- package/dist/feishu/outbound-transport.cjs +221 -0
- package/dist/feishu/outbound-transport.mjs +215 -0
- package/dist/platform/build-info.cjs +25 -0
- package/dist/platform/build-info.mjs +18 -0
- package/dist/platform/callback-capability.cjs +301 -0
- package/dist/platform/callback-capability.mjs +252 -0
- package/dist/platform/check-bun-version.cjs +12 -0
- package/dist/platform/config.cjs +751 -0
- package/dist/platform/config.mjs +694 -0
- package/dist/platform/process-inspect.cjs +89 -0
- package/dist/platform/process-state.cjs +237 -0
- package/dist/platform/process-state.mjs +223 -0
- package/dist/platform/release-artifacts.mjs +61 -0
- package/dist/platform/root-layout.cjs +111 -0
- package/dist/platform/root-layout.mjs +72 -0
- package/dist/platform/telemetry-config.cjs +64 -0
- package/dist/platform/telemetry-config.mjs +56 -0
- package/dist/platform/telemetry-spool.cjs +661 -0
- package/dist/platform/telemetry-spool.mjs +653 -0
- package/dist/platform/telemetry-tracing.cjs +602 -0
- package/dist/platform/telemetry-tracing.mjs +596 -0
- package/dist/platform/telemetry-uploader.cjs +150 -0
- package/dist/platform/telemetry-uploader.mjs +146 -0
- package/dist/platform/workspace-service.cjs +469 -0
- package/dist/platform/workspace-service.mjs +430 -0
- package/dist/runtime/builtin-pi-assets.mjs +33 -0
- package/dist/runtime/claude-model-catalog.cjs +102 -0
- package/dist/runtime/claude-model-catalog.mjs +99 -0
- package/dist/runtime/codex-model-catalog.cjs +108 -0
- package/dist/runtime/codex-model-catalog.mjs +105 -0
- package/dist/runtime/pi-bash-timeout-extension.mjs +60 -0
- package/dist/runtime/pi-bash-timeout-injection.mjs +60 -0
- package/dist/runtime/pi-bash-timeout.bundle.js +43 -0
- package/dist/runtime/pi-model-catalog.cjs +116 -0
- package/dist/runtime/pi-model-catalog.mjs +75 -0
- package/dist/runtime/pi-official-auth.mjs +480 -0
- package/dist/runtime/pi-provider-config.cjs +252 -0
- package/dist/runtime/pi-provider-config.mjs +239 -0
- package/dist/runtime/pi-rpc-client.cjs +255 -0
- package/dist/runtime/pi-rpc-client.mjs +251 -0
- package/dist/runtime/pi-subagent-injection.mjs +129 -0
- package/dist/runtime/pi-subagents.bundle.js +8990 -0
- package/dist/runtime/runtime-adapters.mjs +1019 -0
- package/dist/runtime/runtime-contracts.cjs +2 -0
- package/dist/runtime/runtime-contracts.mjs +1 -0
- package/dist/runtime/runtime-host.cjs +930 -0
- package/dist/runtime/runtime-host.mjs +922 -0
- package/dist/runtime/runtime-model-catalog.cjs +43 -0
- package/dist/runtime/runtime-model-catalog.mjs +40 -0
- package/dist/runtime/runtime-readiness.cjs +177 -0
- package/dist/runtime/runtime-readiness.mjs +133 -0
- package/dist/setup/bot-register.mjs +655 -0
- package/dist/setup/grant-scopes.mjs +133 -0
- package/dist/setup/run-credential-preflight.mjs +89 -0
- package/dist/setup/setup-agent-choice.mjs +172 -0
- package/dist/setup/setup-bind.mjs +337 -0
- package/dist/setup/setup-binding.mjs +52 -0
- package/package.json +112 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.HostEnvelopeProjector = exports.SenderIdentityCache = exports.HostStateProjection = void 0;
|
|
7
|
+
exports.safeConversationExcerpt = safeConversationExcerpt;
|
|
8
|
+
exports.inboundSilenceMs = inboundSilenceMs;
|
|
9
|
+
exports.shouldPreventiveReconnect = shouldPreventiveReconnect;
|
|
10
|
+
exports.countWakeEnvelopes = countWakeEnvelopes;
|
|
11
|
+
exports.projectActivityStatus = projectActivityStatus;
|
|
12
|
+
exports.projectSessionStatus = projectSessionStatus;
|
|
13
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
14
|
+
const message_policy_js_1 = require("./message-policy.cjs");
|
|
15
|
+
function asRecord(value) {
|
|
16
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
17
|
+
}
|
|
18
|
+
function safeConversationExcerpt(value, max = 360) {
|
|
19
|
+
let text = String(value || "").replace(/\s+/g, " ").trim();
|
|
20
|
+
text = text
|
|
21
|
+
.replace(/\b(sk-[A-Za-z0-9_-]{12,})\b/g, "[已隐藏凭证]")
|
|
22
|
+
.replace(/\b(token|secret|password|passwd)\s*[:=]\s*[^\s,;]+/gi, "$1=[已隐藏]");
|
|
23
|
+
return text.length > max ? `${text.slice(0, Math.max(0, max - 1))}…` : text;
|
|
24
|
+
}
|
|
25
|
+
/** 距最后一次「连接建立或真实入站事件」的静默毫秒数;未连接返回 null。 */
|
|
26
|
+
function inboundSilenceMs(status, now) {
|
|
27
|
+
const connected = Date.parse(String(status.connectedAt || ""));
|
|
28
|
+
if (!Number.isFinite(connected))
|
|
29
|
+
return null;
|
|
30
|
+
const inbound = Date.parse(String(status.inboundVerifiedAt || ""));
|
|
31
|
+
const base = Number.isFinite(inbound) ? Math.max(connected, inbound) : connected;
|
|
32
|
+
return Math.max(0, now.getTime() - base);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 静默超阈值 → 该做预防性重连。两个不抢的例外:
|
|
36
|
+
* - connectedAt 早于 connectedAfterMs(status.json 里上一轮 daemon 的残留断言,本轮还没真正连上);
|
|
37
|
+
* - SDK 自身正在重连(reconnectingAt 未过期)。
|
|
38
|
+
*/
|
|
39
|
+
function shouldPreventiveReconnect(status, now, thresholdMs, connectedAfterMs = null) {
|
|
40
|
+
if (!(thresholdMs > 0))
|
|
41
|
+
return false;
|
|
42
|
+
if (connectedAfterMs !== null) {
|
|
43
|
+
const connected = Date.parse(String(status.connectedAt || ""));
|
|
44
|
+
if (!Number.isFinite(connected) || connected < connectedAfterMs)
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const silence = inboundSilenceMs(status, now);
|
|
48
|
+
if (silence === null || silence < thresholdMs)
|
|
49
|
+
return false;
|
|
50
|
+
const reconnecting = Date.parse(String(status.reconnectingAt || ""));
|
|
51
|
+
if (Number.isFinite(reconnecting) && now.getTime() - reconnecting < thresholdMs)
|
|
52
|
+
return false;
|
|
53
|
+
// Maintenance reconnecting creates a fresh connectedAt, but that transport fact is not new
|
|
54
|
+
// inbound evidence. Keep the drought cycle closed until a real inbound event arrives later.
|
|
55
|
+
const persistedDroughtReconnect = Math.max(Date.parse(String(status.droughtReconnectAt || "")) || Number.NEGATIVE_INFINITY, Date.parse(String(status.droughtReconnectAbandonedAt || "")) || Number.NEGATIVE_INFINITY);
|
|
56
|
+
// A daemon restart opens a new maintenance cycle. Only a marker written during
|
|
57
|
+
// the current event-source epoch can suppress another attempt in this run.
|
|
58
|
+
const droughtReconnect = connectedAfterMs === null || persistedDroughtReconnect >= connectedAfterMs
|
|
59
|
+
? persistedDroughtReconnect
|
|
60
|
+
: Number.NEGATIVE_INFINITY;
|
|
61
|
+
const inbound = Date.parse(String(status.inboundVerifiedAt || ""));
|
|
62
|
+
if (Number.isFinite(droughtReconnect) && (!Number.isFinite(inbound) || droughtReconnect >= inbound))
|
|
63
|
+
return false;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
class HostStateProjection {
|
|
67
|
+
storeFor;
|
|
68
|
+
log;
|
|
69
|
+
now;
|
|
70
|
+
constructor(storeFor, log = () => { }, now = () => new Date()) {
|
|
71
|
+
this.storeFor = storeFor;
|
|
72
|
+
this.log = log;
|
|
73
|
+
this.now = now;
|
|
74
|
+
}
|
|
75
|
+
stateFiles(agent) { return this.storeFor(agent).paths; }
|
|
76
|
+
readStatus(agent) {
|
|
77
|
+
try {
|
|
78
|
+
return asRecord(this.storeFor(agent).readJson("status", {}));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
updateStatus(agent, patch) {
|
|
85
|
+
const next = { ...this.readStatus(agent), ...patch };
|
|
86
|
+
try {
|
|
87
|
+
this.storeFor(agent).writeJson("status", next);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
this.log(`status 写失败 agent=${agent.name}: ${error.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
appendStatusLog(agent, key, entry, cap) {
|
|
94
|
+
const current = this.readStatus(agent);
|
|
95
|
+
const existing = Array.isArray(current[key]) ? current[key] : [];
|
|
96
|
+
const list = [...existing, entry].slice(-cap);
|
|
97
|
+
const latestKey = key === "activityLog" ? "lastActivity" : key === "deliverLog" ? "lastDeliver" : null;
|
|
98
|
+
this.updateStatus(agent, { [key]: list, ...(latestKey ? { [latestKey]: entry } : {}) });
|
|
99
|
+
}
|
|
100
|
+
recordStatusError(agent, text) {
|
|
101
|
+
this.appendStatusLog(agent, "recentErrors", { at: this.now().toISOString(), text: String(text).slice(0, 200) }, 20);
|
|
102
|
+
}
|
|
103
|
+
appendConversation(agent, item) {
|
|
104
|
+
try {
|
|
105
|
+
this.storeFor(agent).appendNdjson("conversation", { ...item, text: safeConversationExcerpt(item.text) });
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
this.log(`conversation 写失败 agent=${agent.name}: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
loadMap(agent) {
|
|
112
|
+
try {
|
|
113
|
+
return asRecord(this.storeFor(agent).readJson("map", {}));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
saveMap(agent, map) {
|
|
120
|
+
try {
|
|
121
|
+
this.storeFor(agent).writeJson("map", map);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
this.log("saveMap 失败", error.message);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
mapTargets(agent, targets, chatId) {
|
|
128
|
+
const map = this.loadMap(agent);
|
|
129
|
+
for (const target of targets)
|
|
130
|
+
if (target)
|
|
131
|
+
map[target] = chatId;
|
|
132
|
+
this.saveMap(agent, map);
|
|
133
|
+
}
|
|
134
|
+
saveReplyContext(agent, keys, chatId, replyTo, threadId) {
|
|
135
|
+
const aliases = Array.isArray(keys) ? keys : [keys];
|
|
136
|
+
let map = {};
|
|
137
|
+
try {
|
|
138
|
+
map = asRecord(this.storeFor(agent).readJson("replyctx", {}));
|
|
139
|
+
}
|
|
140
|
+
catch { /* first run or corrupt state retains historical fail-open behavior */ }
|
|
141
|
+
const entry = {
|
|
142
|
+
chat_id: chatId || null,
|
|
143
|
+
reply_to: replyTo || null,
|
|
144
|
+
thread_id: threadId || null,
|
|
145
|
+
in_topic: Boolean(threadId),
|
|
146
|
+
};
|
|
147
|
+
for (const alias of aliases)
|
|
148
|
+
if (alias)
|
|
149
|
+
map[alias] = entry;
|
|
150
|
+
if (chatId)
|
|
151
|
+
map[chatId] = entry;
|
|
152
|
+
try {
|
|
153
|
+
this.storeFor(agent).writeJson("replyctx", map);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
this.log("replyctx 写失败", error.message);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
loadSenderProfiles(agent) {
|
|
160
|
+
try {
|
|
161
|
+
return asRecord(this.storeFor(agent).readJson("senderProfiles", {}));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
saveSenderProfile(agent, senderId, profile) {
|
|
168
|
+
const all = this.loadSenderProfiles(agent);
|
|
169
|
+
all[senderId] = profile;
|
|
170
|
+
try {
|
|
171
|
+
this.storeFor(agent).writeJson("senderProfiles", all);
|
|
172
|
+
}
|
|
173
|
+
catch { /* signature cache persistence is best-effort by design */ }
|
|
174
|
+
}
|
|
175
|
+
recordReadReceipts(agent, reader, readAt, ids) {
|
|
176
|
+
let data = { receipts: [] };
|
|
177
|
+
try {
|
|
178
|
+
data = asRecord(this.storeFor(agent).readJson("readReceipts", data));
|
|
179
|
+
}
|
|
180
|
+
catch { /* first run */ }
|
|
181
|
+
data.receipts = [...(Array.isArray(data.receipts) ? data.receipts : []), { ids, reader, readAt }].slice(-200);
|
|
182
|
+
this.storeFor(agent).writeJson("readReceipts", data);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
exports.HostStateProjection = HostStateProjection;
|
|
186
|
+
class SenderIdentityCache {
|
|
187
|
+
options;
|
|
188
|
+
chatNames = new Map();
|
|
189
|
+
signatures = new Map();
|
|
190
|
+
now;
|
|
191
|
+
wait;
|
|
192
|
+
failureTtlMs;
|
|
193
|
+
constructor(options) {
|
|
194
|
+
this.options = options;
|
|
195
|
+
this.now = options.now ?? Date.now;
|
|
196
|
+
this.wait = options.wait ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
197
|
+
this.failureTtlMs = options.signatureFailureTtlMs ?? 10 * 60 * 1000;
|
|
198
|
+
}
|
|
199
|
+
chatKey(agent, chatId) { return `${agent.agentId}:${chatId}`; }
|
|
200
|
+
signatureKey(agent, senderId) { return `${agent.agentId}:${senderId}`; }
|
|
201
|
+
ensureChatNames(agent, chatId, waitMs) {
|
|
202
|
+
if (!chatId || !agent.feishuProfile)
|
|
203
|
+
return Promise.resolve({});
|
|
204
|
+
const key = this.chatKey(agent, chatId);
|
|
205
|
+
const hit = this.chatNames.get(key);
|
|
206
|
+
if (hit && !(hit instanceof Promise))
|
|
207
|
+
return Promise.resolve(hit.names);
|
|
208
|
+
const pending = hit ?? this.options.fetchChatNames(agent, chatId).then((names) => {
|
|
209
|
+
if (names)
|
|
210
|
+
this.chatNames.set(key, { names, at: this.now() });
|
|
211
|
+
else
|
|
212
|
+
this.chatNames.delete(key);
|
|
213
|
+
return names ?? {};
|
|
214
|
+
});
|
|
215
|
+
if (!hit)
|
|
216
|
+
this.chatNames.set(key, pending);
|
|
217
|
+
if (!waitMs)
|
|
218
|
+
return Promise.resolve({});
|
|
219
|
+
return Promise.race([pending, this.wait(waitMs).then(() => ({}))]);
|
|
220
|
+
}
|
|
221
|
+
noteUnknownSender(agent, chatId, senderId) {
|
|
222
|
+
if (!senderId || !/^(ou_|cli_|app_)/.test(senderId))
|
|
223
|
+
return;
|
|
224
|
+
const key = this.chatKey(agent, chatId);
|
|
225
|
+
const hit = this.chatNames.get(key);
|
|
226
|
+
if (!hit || hit instanceof Promise || this.now() - hit.at < 60_000)
|
|
227
|
+
return;
|
|
228
|
+
this.chatNames.delete(key);
|
|
229
|
+
void this.ensureChatNames(agent, chatId, 0);
|
|
230
|
+
}
|
|
231
|
+
ensureSenderSignature(agent, senderId, waitMs) {
|
|
232
|
+
if (!senderId || !senderId.startsWith("ou_") || !agent.feishuProfile)
|
|
233
|
+
return Promise.resolve(null);
|
|
234
|
+
const key = this.signatureKey(agent, senderId);
|
|
235
|
+
const hit = this.signatures.get(key);
|
|
236
|
+
if (hit && !(hit instanceof Promise)) {
|
|
237
|
+
const fresh = this.now() - hit.at < (hit.ttl || this.failureTtlMs);
|
|
238
|
+
if (!fresh) {
|
|
239
|
+
this.signatures.delete(key);
|
|
240
|
+
void this.ensureSenderSignature(agent, senderId, 0);
|
|
241
|
+
}
|
|
242
|
+
return Promise.resolve(hit.desc);
|
|
243
|
+
}
|
|
244
|
+
const pending = hit ?? this.options.fetchSenderSignature(agent, senderId).then((result) => {
|
|
245
|
+
const entry = { ...result, at: this.now() };
|
|
246
|
+
this.signatures.set(key, entry);
|
|
247
|
+
if (result.ok)
|
|
248
|
+
this.options.state.saveSenderProfile(agent, senderId, {
|
|
249
|
+
description: result.desc,
|
|
250
|
+
name: result.name,
|
|
251
|
+
at: entry.at,
|
|
252
|
+
});
|
|
253
|
+
return result.desc;
|
|
254
|
+
});
|
|
255
|
+
if (!hit)
|
|
256
|
+
this.signatures.set(key, pending);
|
|
257
|
+
return waitMs ? Promise.race([pending, this.wait(waitMs).then(() => null)]) : Promise.resolve(null);
|
|
258
|
+
}
|
|
259
|
+
warmSenderProfiles(agent) {
|
|
260
|
+
for (const [senderId, profile] of Object.entries(this.options.state.loadSenderProfiles(agent))) {
|
|
261
|
+
this.signatures.set(this.signatureKey(agent, senderId), {
|
|
262
|
+
desc: profile.description ?? null,
|
|
263
|
+
name: profile.name || null,
|
|
264
|
+
at: profile.at || 0,
|
|
265
|
+
ok: true,
|
|
266
|
+
ttl: 6 * 60 * 60 * 1000,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
exports.SenderIdentityCache = SenderIdentityCache;
|
|
272
|
+
class HostEnvelopeProjector {
|
|
273
|
+
state;
|
|
274
|
+
noteUnknownSender;
|
|
275
|
+
randomHex;
|
|
276
|
+
now;
|
|
277
|
+
agentCliExecutable;
|
|
278
|
+
sequenceByAgent = new Map();
|
|
279
|
+
constructor(state, noteUnknownSender, randomHex = (bytes) => node_crypto_1.default.randomBytes(bytes).toString("hex"), now = () => new Date(), agentCliExecutable = "larkin") {
|
|
280
|
+
this.state = state;
|
|
281
|
+
this.noteUnknownSender = noteUnknownSender;
|
|
282
|
+
this.randomHex = randomHex;
|
|
283
|
+
this.now = now;
|
|
284
|
+
this.agentCliExecutable = agentCliExecutable;
|
|
285
|
+
}
|
|
286
|
+
agentCommand(suffix) {
|
|
287
|
+
return `${this.agentCliExecutable} ${suffix}`;
|
|
288
|
+
}
|
|
289
|
+
larkCommand(suffix) {
|
|
290
|
+
return `${this.agentCliExecutable} ${suffix}`;
|
|
291
|
+
}
|
|
292
|
+
nextSequence(agentId) {
|
|
293
|
+
const next = (this.sequenceByAgent.get(agentId) || 0) + 1;
|
|
294
|
+
this.sequenceByAgent.set(agentId, next);
|
|
295
|
+
return next;
|
|
296
|
+
}
|
|
297
|
+
projectInbound(agent, event, options = {}) {
|
|
298
|
+
const target = (0, message_policy_js_1.targetFor)(event);
|
|
299
|
+
const aliases = (0, message_policy_js_1.targetAliases)(event, target);
|
|
300
|
+
this.state.mapTargets(agent, aliases, event.chat_id);
|
|
301
|
+
const messageId = event.message_id || `m_${this.randomHex(6)}`;
|
|
302
|
+
if (options.anchorReply !== false) {
|
|
303
|
+
this.state.saveReplyContext(agent, aliases, event.chat_id, messageId, event.thread_id || null);
|
|
304
|
+
}
|
|
305
|
+
const names = options.names || {};
|
|
306
|
+
if (!names[event.sender_id])
|
|
307
|
+
this.noteUnknownSender(agent, event.chat_id, event.sender_id);
|
|
308
|
+
return (0, message_policy_js_1.createMessageEnvelope)({
|
|
309
|
+
agent,
|
|
310
|
+
event: { ...event, message_id: messageId },
|
|
311
|
+
seq: this.nextSequence(agent.agentId),
|
|
312
|
+
names,
|
|
313
|
+
signature: options.signature,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
createReminderEnvelope(agentId, reminder, overdueMs, repeatDescription) {
|
|
317
|
+
const seq = this.nextSequence(agentId);
|
|
318
|
+
const anchorMessageId = typeof reminder.msgRef === "string" && /^om_[A-Za-z0-9_-]+$/.test(reminder.msgRef)
|
|
319
|
+
? reminder.msgRef
|
|
320
|
+
: null;
|
|
321
|
+
const lines = [
|
|
322
|
+
`[定时提醒触发] ${reminder.title}`,
|
|
323
|
+
`提醒ID: #${reminder.reminderId.slice(0, 8)}` + (reminder.repeat && repeatDescription
|
|
324
|
+
? ` 重复: ${repeatDescription}(下次已自动排在 ${reminder.fireAt})`
|
|
325
|
+
: " 类型: 一次性"),
|
|
326
|
+
overdueMs > 120_000 ? `注意: 原定时间已过 ${Math.round(overdueMs / 60_000)} 分钟(Runtime Host 离线期间错过,现补触发)` : null,
|
|
327
|
+
anchorMessageId ? `锚定消息: ${anchorMessageId}` : reminder.msgRef ? `历史锚点 ${String(reminder.msgRef)} 不是飞书 om_ message_id,不能用于回复` : null,
|
|
328
|
+
anchorMessageId
|
|
329
|
+
? `回复原会话: ${this.larkCommand(`im +messages-reply --message-id ${anchorMessageId} ...`)}`
|
|
330
|
+
: null,
|
|
331
|
+
reminder.channel && /^oc_[A-Za-z0-9_-]+$/.test(reminder.channel)
|
|
332
|
+
? `发送到原群: ${this.larkCommand(`im +messages-send --chat-id ${reminder.channel} ...`)}`
|
|
333
|
+
: reminder.channel
|
|
334
|
+
? `历史目标 ${reminder.channel} 不是 chat_id;${anchorMessageId ? "若不回复锚定消息," : ""}先用 ${this.larkCommand("im +chat-search")} 查询并确认 oc_ chat_id,禁止按名称猜测发送目标`
|
|
335
|
+
: anchorMessageId
|
|
336
|
+
? null
|
|
337
|
+
: `本条存量提醒缺少可用的飞书 message_id/chat_id,无法安全推断原会话;请先用 ${this.larkCommand("im +chat-search")} 确认目标,禁止猜测发送`,
|
|
338
|
+
`这是你之前用 ${this.agentCommand("reminder schedule")} 设置的提醒,请按标题执行相应动作。管理: ${this.agentCommand("reminder list")} / ${this.agentCommand("reminder snooze")} / ${this.agentCommand("reminder cancel")}`,
|
|
339
|
+
].filter((line) => Boolean(line));
|
|
340
|
+
return {
|
|
341
|
+
message_id: `rem_${reminder.reminderId.slice(0, 16)}_${seq}`,
|
|
342
|
+
seq,
|
|
343
|
+
sender_name: "定时提醒",
|
|
344
|
+
sender_type: "system",
|
|
345
|
+
channel_type: "dm",
|
|
346
|
+
channel_name: "system",
|
|
347
|
+
content: lines.join("\n"),
|
|
348
|
+
timestamp: this.now().toISOString(),
|
|
349
|
+
thread_id: null,
|
|
350
|
+
wake: true,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
createRedeliveryEnvelope(agentId, wakeCount) {
|
|
354
|
+
const seq = this.nextSequence(agentId);
|
|
355
|
+
return {
|
|
356
|
+
message_id: `redeliver_${this.randomHex(6)}`,
|
|
357
|
+
seq,
|
|
358
|
+
sender_name: "系统",
|
|
359
|
+
sender_type: "system",
|
|
360
|
+
channel_type: "dm",
|
|
361
|
+
channel_name: "system",
|
|
362
|
+
content: `[启动补投] 服务重启期间有 ${wakeCount} 条本应唤醒你的消息未被读取(可能包含用户消息、@提及或定时提醒)。请先用 ${this.agentCommand("inbox check")} 看目标摘要,再用 ${this.agentCommand("inbox poll")} 领取完整消息;仅当 message_id 以 om_ 开头时才用 ${this.larkCommand("im +messages-reply")},系统 rem_/redeliver_ ID 不可回复;有 chat_id 时可用 ${this.larkCommand("im +messages-send")},否则先查询确认目标,禁止猜测。`,
|
|
363
|
+
timestamp: this.now().toISOString(),
|
|
364
|
+
thread_id: null,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
exports.HostEnvelopeProjector = HostEnvelopeProjector;
|
|
369
|
+
function countWakeEnvelopes(lines) {
|
|
370
|
+
return lines.filter((line) => {
|
|
371
|
+
try {
|
|
372
|
+
return JSON.parse(line).wake === true;
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
}).length;
|
|
378
|
+
}
|
|
379
|
+
/** Project daemon activity into dashboard state without changing or interpreting Runtime transport frames. */
|
|
380
|
+
function projectActivityStatus(current, message, runtime, resumeSession, now = new Date()) {
|
|
381
|
+
if (message.isHeartbeat)
|
|
382
|
+
return null;
|
|
383
|
+
const activityKind = message.activityKind || message.activity || "unknown";
|
|
384
|
+
const at = Number.isFinite(message.observedAtMs)
|
|
385
|
+
? new Date(message.observedAtMs).toISOString()
|
|
386
|
+
: now.toISOString();
|
|
387
|
+
const entries = Array.isArray(message.entries) ? message.entries : [];
|
|
388
|
+
const tool = [...entries].reverse().find((entry) => entry?.kind === "tool_start" && entry.toolName)?.toolName || null;
|
|
389
|
+
const activityEntry = {
|
|
390
|
+
state: activityKind === "text" ? "working" : (message.activity || activityKind),
|
|
391
|
+
activityKind,
|
|
392
|
+
detail: activityKind === "thinking" ? null : (safeConversationExcerpt(message.detail, 180) || null),
|
|
393
|
+
detailKind: message.detailKind || "other",
|
|
394
|
+
tool,
|
|
395
|
+
at,
|
|
396
|
+
launchId: message.launchId || null,
|
|
397
|
+
clientSeq: Number.isFinite(message.clientSeq) ? message.clientSeq : null,
|
|
398
|
+
};
|
|
399
|
+
const activityLog = Array.isArray(current.activityLog) ? current.activityLog : [];
|
|
400
|
+
const previousActivity = current.lastActivity && typeof current.lastActivity === "object" && !Array.isArray(current.lastActivity)
|
|
401
|
+
? current.lastActivity
|
|
402
|
+
: null;
|
|
403
|
+
const repeatedThinking = activityKind === "thinking"
|
|
404
|
+
&& (previousActivity?.activityKind === "thinking" || previousActivity?.state === "thinking");
|
|
405
|
+
// Runtime adapters emit text/thinking once per streamed delta. Text is only a freshness signal;
|
|
406
|
+
// thinking is durable on transition, then coalesced until the next state. Persisting every frame
|
|
407
|
+
// otherwise evicts meaningful turn/tool/idle history from the Dashboard within milliseconds.
|
|
408
|
+
const appendToHistory = activityKind !== "text" && !repeatedThinking;
|
|
409
|
+
const patch = {
|
|
410
|
+
activityLog: appendToHistory ? [...activityLog, activityEntry].slice(-80) : activityLog,
|
|
411
|
+
lastActivity: activityEntry,
|
|
412
|
+
};
|
|
413
|
+
const entryKinds = new Set(entries.map((entry) => entry?.kind).filter(Boolean));
|
|
414
|
+
const compactStarted = activityEntry.detailKind === "compacting_context" || entryKinds.has("compaction_started");
|
|
415
|
+
const compactFinished = activityEntry.detailKind === "compaction_finished" || entryKinds.has("compaction_finished");
|
|
416
|
+
if (compactStarted || compactFinished) {
|
|
417
|
+
const previous = current.compaction || {};
|
|
418
|
+
const atMs = Date.parse(at);
|
|
419
|
+
const nearPreviousCompact = [previous.startedAt, previous.lastFinishedAt]
|
|
420
|
+
.map((value) => typeof value === "string" ? Date.parse(value) : Number.NaN)
|
|
421
|
+
.some((value) => Number.isFinite(value) && Number.isFinite(atMs) && Math.abs(atMs - value) <= 5000);
|
|
422
|
+
const sameEvent = Boolean(message.producerFactId) && previous.lastEventId === message.producerFactId;
|
|
423
|
+
const duplicateStart = Boolean(previous.active) || sameEvent || nearPreviousCompact;
|
|
424
|
+
const shouldCountStart = compactStarted && !duplicateStart;
|
|
425
|
+
patch.compaction = compactFinished ? {
|
|
426
|
+
...previous,
|
|
427
|
+
sessionId: current.session?.id || previous.sessionId || null,
|
|
428
|
+
active: false,
|
|
429
|
+
count: Number(previous.count || 0) + (shouldCountStart ? 1 : 0),
|
|
430
|
+
startedAt: shouldCountStart ? at : (previous.startedAt || at),
|
|
431
|
+
lastFinishedAt: at,
|
|
432
|
+
lastEventId: message.producerFactId || null,
|
|
433
|
+
} : {
|
|
434
|
+
sessionId: current.session?.id || null,
|
|
435
|
+
active: true,
|
|
436
|
+
count: Number(previous.count || 0) + (duplicateStart ? 0 : 1),
|
|
437
|
+
startedAt: duplicateStart ? (previous.startedAt || at) : at,
|
|
438
|
+
lastFinishedAt: previous.lastFinishedAt || null,
|
|
439
|
+
lastEventId: message.producerFactId || null,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
if (activityEntry.detailKind === "message_received" || activityEntry.detailKind === "turn_started") {
|
|
443
|
+
patch.session = {
|
|
444
|
+
...(current.session || {}),
|
|
445
|
+
runtime,
|
|
446
|
+
id: current.session?.id || resumeSession || null,
|
|
447
|
+
startedAt: current.session?.startedAt || at,
|
|
448
|
+
lastTurnAt: at,
|
|
449
|
+
turns: Number(current.session?.turns || 0) + 1,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
return patch;
|
|
453
|
+
}
|
|
454
|
+
function projectSessionStatus(current, runtime, sessionId, launchId, now = new Date(), effective = {}) {
|
|
455
|
+
const sameSession = current.session?.id === sessionId;
|
|
456
|
+
const at = now.toISOString();
|
|
457
|
+
return {
|
|
458
|
+
session: {
|
|
459
|
+
runtime,
|
|
460
|
+
id: sessionId,
|
|
461
|
+
launchId: launchId || current.session?.launchId || null,
|
|
462
|
+
startedAt: sameSession ? (current.session?.startedAt || at) : at,
|
|
463
|
+
lastSeenAt: at,
|
|
464
|
+
lastTurnAt: sameSession ? (current.session?.lastTurnAt || null) : null,
|
|
465
|
+
turns: sameSession ? Number(current.session?.turns || 0) : 0,
|
|
466
|
+
...((effective.model || (sameSession ? current.session?.model : null))
|
|
467
|
+
? { model: effective.model || current.session?.model }
|
|
468
|
+
: {}),
|
|
469
|
+
...((effective.reasoningEffort || (sameSession ? current.session?.reasoningEffort : null))
|
|
470
|
+
? { reasoningEffort: effective.reasoningEffort || current.session?.reasoningEffort }
|
|
471
|
+
: {}),
|
|
472
|
+
},
|
|
473
|
+
...(sameSession ? {} : {
|
|
474
|
+
compaction: { sessionId, active: false, count: 0, startedAt: null, lastFinishedAt: null, lastEventId: null },
|
|
475
|
+
}),
|
|
476
|
+
};
|
|
477
|
+
}
|