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,252 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { countWakeEnvelopes } from "../feishu/host-business-state.mjs";
|
|
3
|
+
import * as defaultReminderStore from "./reminder-store.mjs";
|
|
4
|
+
export class HostReminderOrchestrator {
|
|
5
|
+
options;
|
|
6
|
+
lastSnapshot = new Map();
|
|
7
|
+
redelivered = new Set();
|
|
8
|
+
redeliveryInFlight = new Map();
|
|
9
|
+
started = false;
|
|
10
|
+
store;
|
|
11
|
+
log;
|
|
12
|
+
now;
|
|
13
|
+
fireTimers = new Map();
|
|
14
|
+
watchers = [];
|
|
15
|
+
syncInterval = null;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.options = options;
|
|
18
|
+
this.store = options.reminderStore ?? defaultReminderStore;
|
|
19
|
+
this.log = options.log ?? (() => { });
|
|
20
|
+
this.now = options.now ?? Date.now;
|
|
21
|
+
}
|
|
22
|
+
pushSnapshot(agent, why, force = false) {
|
|
23
|
+
let jobs = [];
|
|
24
|
+
try {
|
|
25
|
+
jobs = this.store.load(this.options.stateStore(agent).paths.reminders).reminders
|
|
26
|
+
.filter((record) => record.status === "scheduled")
|
|
27
|
+
.map((record) => ({
|
|
28
|
+
reminderId: record.reminderId,
|
|
29
|
+
version: Number(record.version),
|
|
30
|
+
ownerAgentId: record.ownerAgentId,
|
|
31
|
+
fireAt: record.fireAt,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
this.log(`reminder 快照读取失败 agent=${agent.name}: ${error.message}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const key = JSON.stringify(jobs);
|
|
39
|
+
if (!force && this.lastSnapshot.get(agent.agentId) === key)
|
|
40
|
+
return;
|
|
41
|
+
this.lastSnapshot.set(agent.agentId, key);
|
|
42
|
+
const wanted = new Set(jobs.map((job) => `${agent.agentId}:${job.reminderId}`));
|
|
43
|
+
for (const [timerKey, timer] of this.fireTimers) {
|
|
44
|
+
if (timerKey.startsWith(`${agent.agentId}:`) && !wanted.has(timerKey)) {
|
|
45
|
+
(this.options.clearTimer ?? clearTimeout)(timer);
|
|
46
|
+
this.fireTimers.delete(timerKey);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (const job of jobs) {
|
|
50
|
+
const timerKey = `${agent.agentId}:${job.reminderId}`;
|
|
51
|
+
const previous = this.fireTimers.get(timerKey);
|
|
52
|
+
if (previous)
|
|
53
|
+
(this.options.clearTimer ?? clearTimeout)(previous);
|
|
54
|
+
const delay = Math.max(0, Math.min(2_147_000_000, Date.parse(job.fireAt) - this.now()));
|
|
55
|
+
const timer = (this.options.setTimer ?? setTimeout)(() => {
|
|
56
|
+
this.fireTimers.delete(timerKey);
|
|
57
|
+
this.handleFire({ agentId: agent.agentId, reminderId: job.reminderId });
|
|
58
|
+
}, delay);
|
|
59
|
+
timer.unref?.();
|
|
60
|
+
this.fireTimers.set(timerKey, timer);
|
|
61
|
+
}
|
|
62
|
+
this.log(`reminder 本地排程 agent=${agent.name} n=${jobs.length}${why ? ` (${why})` : ""}`);
|
|
63
|
+
}
|
|
64
|
+
deliver(agent, reminder, overdueMs) {
|
|
65
|
+
const recurrence = reminder.repeat ? this.store.parseRepeat(reminder.repeat, reminder.tz) : null;
|
|
66
|
+
const envelope = this.options.envelopeProjector.createReminderEnvelope(agent.agentId, {
|
|
67
|
+
reminderId: reminder.reminderId,
|
|
68
|
+
title: reminder.title,
|
|
69
|
+
repeat: reminder.repeat ? String(reminder.repeat) : null,
|
|
70
|
+
fireAt: reminder.fireAt,
|
|
71
|
+
msgRef: reminder.msgRef,
|
|
72
|
+
channel: reminder.channel ? String(reminder.channel) : null,
|
|
73
|
+
}, overdueMs, reminder.repeat && recurrence && !("error" in recurrence) ? (recurrence.description || null) : null);
|
|
74
|
+
try {
|
|
75
|
+
this.options.stateStore(agent).appendNdjson("inbox", envelope);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
this.log("reminder inbox 写失败", error.message);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (this.options.deliveryTarget)
|
|
82
|
+
void this.options.deliveryTarget.deliver(agent.agentId, envelope);
|
|
83
|
+
else
|
|
84
|
+
this.log(`reminder 触发但 Runtime Host 未就绪 id=#${reminder.reminderId.slice(0, 8)}(仅入 inbox)`);
|
|
85
|
+
}
|
|
86
|
+
handleFire(message) {
|
|
87
|
+
const agent = this.options.agents.find((candidate) => candidate.agentId === message.agentId);
|
|
88
|
+
if (!agent) {
|
|
89
|
+
this.log(`reminder fire_attempt 未知 agent=${message.agentId}`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const file = this.options.stateStore(agent).paths.reminders;
|
|
93
|
+
const now = this.now();
|
|
94
|
+
let action = null;
|
|
95
|
+
let fired = null;
|
|
96
|
+
let overdueMs = 0;
|
|
97
|
+
try {
|
|
98
|
+
this.store.mutate(file, (store) => {
|
|
99
|
+
const reminder = store.reminders.find((candidate) => candidate.reminderId === message.reminderId);
|
|
100
|
+
if (!reminder || reminder.status !== "scheduled") {
|
|
101
|
+
this.log(`reminder fire 忽略(不存在或非 scheduled)id=${String(message.reminderId).slice(0, 8)}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const due = Date.parse(reminder.fireAt);
|
|
105
|
+
if (Number.isFinite(due) && due - now > 60_000) {
|
|
106
|
+
action = "rearm";
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
overdueMs = Math.max(0, now - due);
|
|
110
|
+
const recurrence = reminder.repeat ? this.store.parseRepeat(reminder.repeat, reminder.tz) : null;
|
|
111
|
+
if (recurrence && !("error" in recurrence)) {
|
|
112
|
+
const next = recurrence.next(now, due);
|
|
113
|
+
reminder.firedAt = this.store.nowIso(now);
|
|
114
|
+
if (next)
|
|
115
|
+
reminder.fireAt = this.store.nowIso(next);
|
|
116
|
+
else
|
|
117
|
+
reminder.status = "fired";
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
reminder.status = "fired";
|
|
121
|
+
reminder.firedAt = this.store.nowIso(now);
|
|
122
|
+
}
|
|
123
|
+
reminder.version = Number(reminder.version) + 1;
|
|
124
|
+
this.store.appendEvent(reminder, "fired", "system", null, reminder.status === "scheduled" ? reminder.fireAt : null, now);
|
|
125
|
+
store.reminders = store.reminders.filter((candidate) => candidate.status === "scheduled" || now - Date.parse(candidate.firedAt || candidate.createdAt || "") < 30 * 86_400_000);
|
|
126
|
+
action = "deliver";
|
|
127
|
+
fired = { ...reminder };
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
this.log(`reminder fire 处理失败 agent=${agent.name}: ${error.message}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (action === "deliver" && fired) {
|
|
135
|
+
const record = fired;
|
|
136
|
+
this.log(`reminder 触发 agent=${agent.name} id=#${record.reminderId.slice(0, 8)} "${record.title.slice(0, 40)}"${overdueMs > 120_000 ? ` 逾期${Math.round(overdueMs / 60_000)}min` : ""}`);
|
|
137
|
+
this.deliver(agent, record, overdueMs);
|
|
138
|
+
}
|
|
139
|
+
else if (action === "rearm") {
|
|
140
|
+
this.log(`reminder 未到期(daemon 24h 截断),续排 id=${String(message.reminderId).slice(0, 8)}`);
|
|
141
|
+
}
|
|
142
|
+
this.pushSnapshot(agent, action === "rearm" ? "续排" : "fire 后同步", true);
|
|
143
|
+
}
|
|
144
|
+
startSync() {
|
|
145
|
+
if (this.started)
|
|
146
|
+
return;
|
|
147
|
+
this.started = true;
|
|
148
|
+
const setTimer = this.options.setTimer ?? setTimeout;
|
|
149
|
+
const clearTimer = this.options.clearTimer ?? clearTimeout;
|
|
150
|
+
for (const agent of this.options.agents) {
|
|
151
|
+
this.pushSnapshot(agent, "启动");
|
|
152
|
+
if (!agent.stateDir)
|
|
153
|
+
continue;
|
|
154
|
+
const timers = new Map();
|
|
155
|
+
try {
|
|
156
|
+
const watcher = (this.options.watch ?? fs.watch)(agent.stateDir, (_event, filename) => {
|
|
157
|
+
if (filename !== "reminders.json")
|
|
158
|
+
return;
|
|
159
|
+
const timer = timers.get(agent.agentId);
|
|
160
|
+
if (timer)
|
|
161
|
+
clearTimer(timer);
|
|
162
|
+
timers.set(agent.agentId, setTimer(() => this.pushSnapshot(agent, "文件变更"), 300));
|
|
163
|
+
});
|
|
164
|
+
this.watchers.push(watcher);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
this.log(`reminder watch 失败 agent=${agent.name}: ${error.message}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const interval = (this.options.setInterval ?? setInterval)(() => {
|
|
171
|
+
for (const agent of this.options.agents)
|
|
172
|
+
this.pushSnapshot(agent, "定期兜底");
|
|
173
|
+
}, 60_000);
|
|
174
|
+
interval.unref();
|
|
175
|
+
this.syncInterval = interval;
|
|
176
|
+
}
|
|
177
|
+
stopSync() {
|
|
178
|
+
for (const timer of this.fireTimers.values())
|
|
179
|
+
(this.options.clearTimer ?? clearTimeout)(timer);
|
|
180
|
+
this.fireTimers.clear();
|
|
181
|
+
if (this.syncInterval)
|
|
182
|
+
clearInterval(this.syncInterval);
|
|
183
|
+
this.syncInterval = null;
|
|
184
|
+
for (const watcher of this.watchers.splice(0))
|
|
185
|
+
watcher.close();
|
|
186
|
+
this.started = false;
|
|
187
|
+
}
|
|
188
|
+
async redeliverUnread(agent) {
|
|
189
|
+
if (this.redelivered.has(agent.agentId))
|
|
190
|
+
return;
|
|
191
|
+
const existingAttempt = this.redeliveryInFlight.get(agent.agentId);
|
|
192
|
+
if (existingAttempt)
|
|
193
|
+
return existingAttempt;
|
|
194
|
+
const attempt = this.performRedelivery(agent);
|
|
195
|
+
this.redeliveryInFlight.set(agent.agentId, attempt);
|
|
196
|
+
try {
|
|
197
|
+
await attempt;
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
if (this.redeliveryInFlight.get(agent.agentId) === attempt)
|
|
201
|
+
this.redeliveryInFlight.delete(agent.agentId);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async performRedelivery(agent) {
|
|
205
|
+
let lines = [];
|
|
206
|
+
try {
|
|
207
|
+
lines = String((this.options.readFile ?? fs.readFileSync)(this.options.stateStore(agent).paths.inbox, "utf8"))
|
|
208
|
+
.split("\n").filter(Boolean);
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
if (error.code === "ENOENT" && this.options.deliveryTarget) {
|
|
212
|
+
this.redelivered.add(agent.agentId);
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const parsed = lines.map((line) => {
|
|
217
|
+
try {
|
|
218
|
+
return { line, value: JSON.parse(line) };
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return { line, value: null };
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
if (parsed.some(({ value }) => value === null))
|
|
225
|
+
return;
|
|
226
|
+
const existing = parsed.find(({ value }) => typeof value?.message_id === "string" && value.message_id.startsWith("redeliver_"))?.value;
|
|
227
|
+
const wakeCount = countWakeEnvelopes(parsed
|
|
228
|
+
.filter(({ value }) => !(typeof value?.message_id === "string" && value.message_id.startsWith("redeliver_")))
|
|
229
|
+
.map(({ line }) => line));
|
|
230
|
+
if (!wakeCount && !existing) {
|
|
231
|
+
if (this.options.deliveryTarget)
|
|
232
|
+
this.redelivered.add(agent.agentId);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const envelope = existing ?? this.options.envelopeProjector.createRedeliveryEnvelope(agent.agentId, wakeCount);
|
|
236
|
+
if (!existing) {
|
|
237
|
+
try {
|
|
238
|
+
this.options.stateStore(agent).appendNdjson("inbox", envelope);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
this.log("启动补投 inbox 写失败", error.message);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
this.log(`启动补投 agent=${agent.name} 滞留 wake 消息 ${wakeCount} 条`);
|
|
246
|
+
if (this.options.deliveryTarget)
|
|
247
|
+
await this.options.deliveryTarget.deliver(agent.agentId, envelope);
|
|
248
|
+
else
|
|
249
|
+
return;
|
|
250
|
+
this.redelivered.add(agent.agentId);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.targetKeyOfInboxEnvelope = targetKeyOfInboxEnvelope;
|
|
4
|
+
exports.projectInboxEnvelope = projectInboxEnvelope;
|
|
5
|
+
exports.targetOfInboxEnvelope = targetOfInboxEnvelope;
|
|
6
|
+
exports.projectInboxEvents = projectInboxEvents;
|
|
7
|
+
exports.projectInboxCheck = projectInboxCheck;
|
|
8
|
+
function targetKeyOfInboxEnvelope(envelope) {
|
|
9
|
+
if (!envelope)
|
|
10
|
+
return "runtime:unknown";
|
|
11
|
+
if (envelope.kind === "document_comment" && typeof envelope.target === "string" && envelope.target) {
|
|
12
|
+
return envelope.target;
|
|
13
|
+
}
|
|
14
|
+
if (typeof envelope.chat_id === "string" && envelope.chat_id) {
|
|
15
|
+
return typeof envelope.thread_id === "string" && envelope.thread_id
|
|
16
|
+
? `thread:${envelope.chat_id}:${envelope.thread_id}`
|
|
17
|
+
: `chat:${envelope.chat_id}`;
|
|
18
|
+
}
|
|
19
|
+
return targetOfInboxEnvelope(envelope) || "runtime:system";
|
|
20
|
+
}
|
|
21
|
+
/** Enrich the durable local Inbox without changing the canonical Runtime envelope. */
|
|
22
|
+
function projectInboxEnvelope(canonical, locators) {
|
|
23
|
+
return {
|
|
24
|
+
...canonical,
|
|
25
|
+
chat_id: locators.chat_id,
|
|
26
|
+
thread_id: locators.thread_id !== undefined ? locators.thread_id : (typeof canonical.thread_id === "string" ? canonical.thread_id : null),
|
|
27
|
+
...(locators.create_time !== undefined ? { create_time: locators.create_time }
|
|
28
|
+
: (typeof canonical.create_time === "string" ? { create_time: canonical.create_time } : {})),
|
|
29
|
+
...(locators.update_time !== undefined ? { update_time: locators.update_time }
|
|
30
|
+
: (typeof canonical.update_time === "string" ? { update_time: canonical.update_time } : {})),
|
|
31
|
+
sender_id: locators.sender_id !== undefined ? locators.sender_id : (typeof canonical.sender_id === "string" ? canonical.sender_id : null),
|
|
32
|
+
content: locators.content !== undefined ? locators.content : (typeof canonical.content === "string" ? canonical.content : ""),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Preserve the target/thread format consumed by the existing Agent CLI. */
|
|
36
|
+
function targetOfInboxEnvelope(envelope) {
|
|
37
|
+
if (!envelope)
|
|
38
|
+
return null;
|
|
39
|
+
if (envelope.kind === "document_comment") {
|
|
40
|
+
return typeof envelope.target === "string" && envelope.target ? envelope.target : null;
|
|
41
|
+
}
|
|
42
|
+
if (envelope.channel_type === "thread" && envelope.parent_channel_name) {
|
|
43
|
+
const base = envelope.parent_channel_type === "dm"
|
|
44
|
+
? `dm:@${String(envelope.parent_channel_name)}`
|
|
45
|
+
: `#${String(envelope.parent_channel_name)}`;
|
|
46
|
+
return `${base}:${String(envelope.channel_name || "").slice(0, 8)}`;
|
|
47
|
+
}
|
|
48
|
+
return envelope.channel_type === "dm"
|
|
49
|
+
? `dm:@${String(envelope.channel_name)}`
|
|
50
|
+
: `#${String(envelope.channel_name)}`;
|
|
51
|
+
}
|
|
52
|
+
/** Project persisted inbox envelopes into the exact agentApi events response data shape. */
|
|
53
|
+
function projectInboxEvents(envelopes) {
|
|
54
|
+
const last = envelopes[envelopes.length - 1];
|
|
55
|
+
return {
|
|
56
|
+
events: envelopes,
|
|
57
|
+
last_seen_msgId: last ? (last.message_id || null) : null,
|
|
58
|
+
last_seen_seq: last ? (last.seq ?? null) : null,
|
|
59
|
+
reply_target: targetOfInboxEnvelope(last),
|
|
60
|
+
pending_notice_ids: [],
|
|
61
|
+
wake_reason: null,
|
|
62
|
+
has_more: false,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Bounded, content-light and deterministic projection for the read-only check command. */
|
|
66
|
+
function projectInboxCheck(envelopes, onlyTarget) {
|
|
67
|
+
const byTarget = new Map();
|
|
68
|
+
for (const envelope of envelopes) {
|
|
69
|
+
const target = typeof envelope.target === "string" && envelope.target ? envelope.target : targetKeyOfInboxEnvelope(envelope);
|
|
70
|
+
if (onlyTarget && onlyTarget !== target)
|
|
71
|
+
continue;
|
|
72
|
+
const rows = byTarget.get(target) ?? [];
|
|
73
|
+
rows.push(envelope);
|
|
74
|
+
byTarget.set(target, rows);
|
|
75
|
+
}
|
|
76
|
+
const allTargets = [...byTarget.entries()].map(([target, rows]) => ({
|
|
77
|
+
target,
|
|
78
|
+
pending_count: rows.length,
|
|
79
|
+
latest_received_seq: rows.reduce((latest, row, index) => {
|
|
80
|
+
const seq = Number(row.target_seq);
|
|
81
|
+
return Number.isSafeInteger(seq) && seq > 0 ? Math.max(latest, seq) : Math.max(latest, index + 1);
|
|
82
|
+
}, 0),
|
|
83
|
+
first_message_id: typeof rows[0]?.message_id === "string" ? rows[0].message_id : null,
|
|
84
|
+
last_message_id: typeof rows.at(-1)?.message_id === "string" ? rows.at(-1).message_id : null,
|
|
85
|
+
})).sort((left, right) => left.target.localeCompare(right.target, "en"));
|
|
86
|
+
return {
|
|
87
|
+
version: 2,
|
|
88
|
+
targets: allTargets.slice(0, 50),
|
|
89
|
+
pending_total: allTargets.reduce((count, row) => count + row.pending_count, 0),
|
|
90
|
+
has_more: allTargets.length > 50,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export function targetKeyOfInboxEnvelope(envelope) {
|
|
2
|
+
if (!envelope)
|
|
3
|
+
return "runtime:unknown";
|
|
4
|
+
if (envelope.kind === "document_comment" && typeof envelope.target === "string" && envelope.target) {
|
|
5
|
+
return envelope.target;
|
|
6
|
+
}
|
|
7
|
+
if (typeof envelope.chat_id === "string" && envelope.chat_id) {
|
|
8
|
+
return typeof envelope.thread_id === "string" && envelope.thread_id
|
|
9
|
+
? `thread:${envelope.chat_id}:${envelope.thread_id}`
|
|
10
|
+
: `chat:${envelope.chat_id}`;
|
|
11
|
+
}
|
|
12
|
+
return targetOfInboxEnvelope(envelope) || "runtime:system";
|
|
13
|
+
}
|
|
14
|
+
/** Enrich the durable local Inbox without changing the canonical Runtime envelope. */
|
|
15
|
+
export function projectInboxEnvelope(canonical, locators) {
|
|
16
|
+
return {
|
|
17
|
+
...canonical,
|
|
18
|
+
chat_id: locators.chat_id,
|
|
19
|
+
thread_id: locators.thread_id !== undefined ? locators.thread_id : (typeof canonical.thread_id === "string" ? canonical.thread_id : null),
|
|
20
|
+
...(locators.create_time !== undefined ? { create_time: locators.create_time }
|
|
21
|
+
: (typeof canonical.create_time === "string" ? { create_time: canonical.create_time } : {})),
|
|
22
|
+
...(locators.update_time !== undefined ? { update_time: locators.update_time }
|
|
23
|
+
: (typeof canonical.update_time === "string" ? { update_time: canonical.update_time } : {})),
|
|
24
|
+
sender_id: locators.sender_id !== undefined ? locators.sender_id : (typeof canonical.sender_id === "string" ? canonical.sender_id : null),
|
|
25
|
+
content: locators.content !== undefined ? locators.content : (typeof canonical.content === "string" ? canonical.content : ""),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Preserve the target/thread format consumed by the existing Agent CLI. */
|
|
29
|
+
export function targetOfInboxEnvelope(envelope) {
|
|
30
|
+
if (!envelope)
|
|
31
|
+
return null;
|
|
32
|
+
if (envelope.kind === "document_comment") {
|
|
33
|
+
return typeof envelope.target === "string" && envelope.target ? envelope.target : null;
|
|
34
|
+
}
|
|
35
|
+
if (envelope.channel_type === "thread" && envelope.parent_channel_name) {
|
|
36
|
+
const base = envelope.parent_channel_type === "dm"
|
|
37
|
+
? `dm:@${String(envelope.parent_channel_name)}`
|
|
38
|
+
: `#${String(envelope.parent_channel_name)}`;
|
|
39
|
+
return `${base}:${String(envelope.channel_name || "").slice(0, 8)}`;
|
|
40
|
+
}
|
|
41
|
+
return envelope.channel_type === "dm"
|
|
42
|
+
? `dm:@${String(envelope.channel_name)}`
|
|
43
|
+
: `#${String(envelope.channel_name)}`;
|
|
44
|
+
}
|
|
45
|
+
/** Project persisted inbox envelopes into the exact agentApi events response data shape. */
|
|
46
|
+
export function projectInboxEvents(envelopes) {
|
|
47
|
+
const last = envelopes[envelopes.length - 1];
|
|
48
|
+
return {
|
|
49
|
+
events: envelopes,
|
|
50
|
+
last_seen_msgId: last ? (last.message_id || null) : null,
|
|
51
|
+
last_seen_seq: last ? (last.seq ?? null) : null,
|
|
52
|
+
reply_target: targetOfInboxEnvelope(last),
|
|
53
|
+
pending_notice_ids: [],
|
|
54
|
+
wake_reason: null,
|
|
55
|
+
has_more: false,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Bounded, content-light and deterministic projection for the read-only check command. */
|
|
59
|
+
export function projectInboxCheck(envelopes, onlyTarget) {
|
|
60
|
+
const byTarget = new Map();
|
|
61
|
+
for (const envelope of envelopes) {
|
|
62
|
+
const target = typeof envelope.target === "string" && envelope.target ? envelope.target : targetKeyOfInboxEnvelope(envelope);
|
|
63
|
+
if (onlyTarget && onlyTarget !== target)
|
|
64
|
+
continue;
|
|
65
|
+
const rows = byTarget.get(target) ?? [];
|
|
66
|
+
rows.push(envelope);
|
|
67
|
+
byTarget.set(target, rows);
|
|
68
|
+
}
|
|
69
|
+
const allTargets = [...byTarget.entries()].map(([target, rows]) => ({
|
|
70
|
+
target,
|
|
71
|
+
pending_count: rows.length,
|
|
72
|
+
latest_received_seq: rows.reduce((latest, row, index) => {
|
|
73
|
+
const seq = Number(row.target_seq);
|
|
74
|
+
return Number.isSafeInteger(seq) && seq > 0 ? Math.max(latest, seq) : Math.max(latest, index + 1);
|
|
75
|
+
}, 0),
|
|
76
|
+
first_message_id: typeof rows[0]?.message_id === "string" ? rows[0].message_id : null,
|
|
77
|
+
last_message_id: typeof rows.at(-1)?.message_id === "string" ? rows.at(-1).message_id : null,
|
|
78
|
+
})).sort((left, right) => left.target.localeCompare(right.target, "en"));
|
|
79
|
+
return {
|
|
80
|
+
version: 2,
|
|
81
|
+
targets: allTargets.slice(0, 50),
|
|
82
|
+
pending_total: allTargets.reduce((count, row) => count + row.pending_count, 0),
|
|
83
|
+
has_more: allTargets.length > 50,
|
|
84
|
+
};
|
|
85
|
+
}
|