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,205 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
export const nowIso = (ms) => new Date(ms ?? Date.now()).toISOString();
|
|
4
|
+
export const newId = () => crypto.randomUUID().replace(/-/g, "");
|
|
5
|
+
// Host and Agent transport are separate processes. mkdir is the cross-process mutex;
|
|
6
|
+
// the timeout and stale-lock ages intentionally preserve the established production contract.
|
|
7
|
+
export function withLock(file, fn, timeoutMs = 2_000) {
|
|
8
|
+
const lockDir = `${file}.lock`;
|
|
9
|
+
const deadline = Date.now() + timeoutMs;
|
|
10
|
+
for (;;) {
|
|
11
|
+
try {
|
|
12
|
+
fs.mkdirSync(lockDir);
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (error.code !== "EEXIST")
|
|
17
|
+
throw error;
|
|
18
|
+
try {
|
|
19
|
+
if (Date.now() - fs.statSync(lockDir).mtimeMs > 10_000) {
|
|
20
|
+
fs.rmdirSync(lockDir);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch { /* another process may be reclaiming the same stale directory */ }
|
|
25
|
+
if (Date.now() > deadline)
|
|
26
|
+
throw new Error("reminders.json 锁等待超时");
|
|
27
|
+
const until = Date.now() + 15;
|
|
28
|
+
while (Date.now() < until) { /* preserve the existing short synchronous critical section */ }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return fn();
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
try {
|
|
36
|
+
fs.rmdirSync(lockDir);
|
|
37
|
+
}
|
|
38
|
+
catch { /* best effort: preserve existing crash-recovery semantics */ }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function load(file) {
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
44
|
+
if (parsed && Array.isArray(parsed.reminders))
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
catch { /* missing and damaged stores both project to an empty store */ }
|
|
48
|
+
return { reminders: [] };
|
|
49
|
+
}
|
|
50
|
+
export function save(file, store) {
|
|
51
|
+
const temporary = `${file}.tmp`;
|
|
52
|
+
fs.writeFileSync(temporary, `${JSON.stringify(store, null, 2)}\n`);
|
|
53
|
+
fs.renameSync(temporary, file);
|
|
54
|
+
}
|
|
55
|
+
export function mutate(file, fn, lockTimeoutMs = 2_000) {
|
|
56
|
+
return withLock(file, () => {
|
|
57
|
+
const store = load(file);
|
|
58
|
+
const result = fn(store);
|
|
59
|
+
save(file, store);
|
|
60
|
+
return result;
|
|
61
|
+
}, lockTimeoutMs);
|
|
62
|
+
}
|
|
63
|
+
const WEEKDAYS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
64
|
+
function validTz(tz) {
|
|
65
|
+
try {
|
|
66
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function wallParts(ms, tz) {
|
|
74
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
75
|
+
timeZone: tz,
|
|
76
|
+
year: "numeric",
|
|
77
|
+
month: "2-digit",
|
|
78
|
+
day: "2-digit",
|
|
79
|
+
hour: "2-digit",
|
|
80
|
+
minute: "2-digit",
|
|
81
|
+
second: "2-digit",
|
|
82
|
+
weekday: "short",
|
|
83
|
+
hour12: false,
|
|
84
|
+
});
|
|
85
|
+
const parts = {};
|
|
86
|
+
for (const part of formatter.formatToParts(ms))
|
|
87
|
+
parts[part.type] = part.value;
|
|
88
|
+
return {
|
|
89
|
+
y: Number(parts.year),
|
|
90
|
+
mo: Number(parts.month),
|
|
91
|
+
d: Number(parts.day),
|
|
92
|
+
hh: parts.hour === "24" ? 0 : Number(parts.hour),
|
|
93
|
+
mm: Number(parts.minute),
|
|
94
|
+
ss: Number(parts.second),
|
|
95
|
+
wd: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].indexOf(parts.weekday),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function utcOffsetMs(ms, tz) {
|
|
99
|
+
const wall = wallParts(ms, tz);
|
|
100
|
+
return Date.UTC(wall.y, wall.mo - 1, wall.d, wall.hh, wall.mm, wall.ss) - Math.floor(ms / 1_000) * 1_000;
|
|
101
|
+
}
|
|
102
|
+
function wallToUtc(y, mo, d, hh, mm, tz) {
|
|
103
|
+
const firstGuess = Date.UTC(y, mo - 1, d, hh, mm);
|
|
104
|
+
const secondGuess = firstGuess - utcOffsetMs(firstGuess, tz);
|
|
105
|
+
return firstGuess - utcOffsetMs(secondGuess, tz);
|
|
106
|
+
}
|
|
107
|
+
export function parseRepeat(rule, tz) {
|
|
108
|
+
const normalized = String(rule || "").trim();
|
|
109
|
+
const normalizedTz = tz || undefined;
|
|
110
|
+
const zone = validTz(normalizedTz) ? normalizedTz : Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
111
|
+
let match = normalized.match(/^every:(\d+)(s|m|h|d)$/);
|
|
112
|
+
if (match) {
|
|
113
|
+
const multipliers = { s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 };
|
|
114
|
+
const unit = match[2];
|
|
115
|
+
const period = Number(match[1]) * multipliers[unit];
|
|
116
|
+
if (period < 60_000)
|
|
117
|
+
return { error: "间隔太短:every 至少 1 分钟(如 every:15m)" };
|
|
118
|
+
return {
|
|
119
|
+
kind: "interval",
|
|
120
|
+
description: `every ${match[1]}${unit}`,
|
|
121
|
+
next: (afterMs, anchorMs) => {
|
|
122
|
+
const base = Number.isFinite(anchorMs) ? Number(anchorMs) : afterMs;
|
|
123
|
+
const count = Math.max(1, Math.floor((afterMs - base) / period) + 1);
|
|
124
|
+
return base + count * period;
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
match = normalized.match(/^daily@(\d{1,2}):(\d{2})$/);
|
|
129
|
+
if (match) {
|
|
130
|
+
const [hours, minutes] = [Number(match[1]), Number(match[2])];
|
|
131
|
+
if (hours > 23 || minutes > 59)
|
|
132
|
+
return { error: `无效时间 ${match[1]}:${match[2]}` };
|
|
133
|
+
return {
|
|
134
|
+
kind: "daily",
|
|
135
|
+
description: `daily@${String(hours).padStart(2, "0")}:${match[2]} (${zone})`,
|
|
136
|
+
next: (afterMs) => {
|
|
137
|
+
const wall = wallParts(afterMs, zone);
|
|
138
|
+
for (let offset = 0; offset <= 2; offset += 1) {
|
|
139
|
+
const candidate = wallToUtc(wall.y, wall.mo, wall.d + offset, hours, minutes, zone);
|
|
140
|
+
if (candidate > afterMs)
|
|
141
|
+
return candidate;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
match = normalized.match(/^weekly:([a-z,]+)@(\d{1,2}):(\d{2})$/);
|
|
148
|
+
if (match) {
|
|
149
|
+
const days = match[1].split(",").map((day) => WEEKDAYS.indexOf(day.trim()));
|
|
150
|
+
const [hours, minutes] = [Number(match[2]), Number(match[3])];
|
|
151
|
+
if (days.some((day) => day < 0))
|
|
152
|
+
return { error: `无效星期(可用: ${WEEKDAYS.join(",")}):${match[1]}` };
|
|
153
|
+
if (hours > 23 || minutes > 59)
|
|
154
|
+
return { error: `无效时间 ${match[2]}:${match[3]}` };
|
|
155
|
+
return {
|
|
156
|
+
kind: "weekly",
|
|
157
|
+
description: `weekly:${match[1]}@${String(hours).padStart(2, "0")}:${match[3]} (${zone})`,
|
|
158
|
+
next: (afterMs) => {
|
|
159
|
+
const wall = wallParts(afterMs, zone);
|
|
160
|
+
for (let offset = 0; offset <= 8; offset += 1) {
|
|
161
|
+
const candidate = wallToUtc(wall.y, wall.mo, wall.d + offset, hours, minutes, zone);
|
|
162
|
+
if (candidate > afterMs && days.includes(wallParts(candidate, zone).wd))
|
|
163
|
+
return candidate;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return { error: `无法识别的 repeat 规则:${JSON.stringify(normalized)}。支持 every:<N><s|m|h|d>、daily@HH:MM、weekly:mon,fri@HH:MM` };
|
|
170
|
+
}
|
|
171
|
+
export function toSummary(reminder) {
|
|
172
|
+
let recurrence = null;
|
|
173
|
+
if (reminder.repeat) {
|
|
174
|
+
const parsed = parseRepeat(reminder.repeat, reminder.tz || undefined);
|
|
175
|
+
recurrence = "error" in parsed
|
|
176
|
+
? { kind: "unsupported", description: String(reminder.repeat) }
|
|
177
|
+
: { kind: parsed.kind, description: parsed.description };
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
reminderId: reminder.reminderId,
|
|
181
|
+
ownerAgentId: reminder.ownerAgentId,
|
|
182
|
+
title: reminder.title,
|
|
183
|
+
fireAt: reminder.fireAt,
|
|
184
|
+
firedAt: reminder.firedAt ?? null,
|
|
185
|
+
createdAt: reminder.createdAt,
|
|
186
|
+
status: reminder.status,
|
|
187
|
+
msgRef: reminder.msgRef ?? null,
|
|
188
|
+
msgPermalink: reminder.msgPermalink ?? null,
|
|
189
|
+
recurrence,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
export function appendEvent(reminder, eventType, actorType, actorId, nextFireAt, ms) {
|
|
193
|
+
if (!Array.isArray(reminder.events))
|
|
194
|
+
reminder.events = [];
|
|
195
|
+
reminder.events.push({
|
|
196
|
+
eventId: newId(),
|
|
197
|
+
reminderId: reminder.reminderId,
|
|
198
|
+
eventType,
|
|
199
|
+
actorType,
|
|
200
|
+
actorId: actorId ?? null,
|
|
201
|
+
occurredAt: nowIso(ms),
|
|
202
|
+
nextFireAt: nextFireAt ?? null,
|
|
203
|
+
metadata: null,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.formatFeishuError = formatFeishuError;
|
|
37
|
+
exports.createTransportBusinessContext = createTransportBusinessContext;
|
|
38
|
+
const node_child_process_1 = require("node:child_process");
|
|
39
|
+
const fs = __importStar(require("node:fs"));
|
|
40
|
+
const path = __importStar(require("node:path"));
|
|
41
|
+
const agent_state_store_js_1 = require("./agent-state-store.cjs");
|
|
42
|
+
const channel_transport_js_1 = require("../feishu/channel-transport.cjs");
|
|
43
|
+
const larkinConfig = __importStar(require("../platform/config.cjs"));
|
|
44
|
+
const history_projection_js_1 = require("./history-projection.cjs");
|
|
45
|
+
const inbox_projection_js_1 = require("./inbox-projection.cjs");
|
|
46
|
+
const message_policy_js_1 = require("../feishu/message-policy.cjs");
|
|
47
|
+
const outbound_transport_js_1 = require("../feishu/outbound-transport.cjs");
|
|
48
|
+
const reminder_routes_js_1 = require("./reminder-routes.cjs");
|
|
49
|
+
const transport_shell_js_1 = require("./transport-shell.cjs");
|
|
50
|
+
const agent_lark_cli_workspace_js_1 = require("../app/agent-lark-cli-workspace.cjs");
|
|
51
|
+
function formatFeishuError(error, grantLink = null) {
|
|
52
|
+
if (!error)
|
|
53
|
+
return null;
|
|
54
|
+
const parts = [];
|
|
55
|
+
if (error.message)
|
|
56
|
+
parts.push(error.message);
|
|
57
|
+
if (error.missing_scopes?.length)
|
|
58
|
+
parts.push(`missing_scopes: ${error.missing_scopes.join(", ")}`);
|
|
59
|
+
const tag = [error.type, error.subtype].filter(Boolean).join("/");
|
|
60
|
+
const meta = [tag, error.code != null ? `code ${error.code}` : "", error.log_id ? `log_id ${error.log_id}` : ""].filter(Boolean).join(" | ");
|
|
61
|
+
if (meta)
|
|
62
|
+
parts.push(`(${meta})`);
|
|
63
|
+
let output = `飞书接口报错:${parts.join(" ")}`;
|
|
64
|
+
const isScope = error.code !== 230027
|
|
65
|
+
&& (error.subtype === "app_scope_not_applied" || error.code === 99991672 || Boolean(error.missing_scopes?.length));
|
|
66
|
+
if (error.code === 230027) {
|
|
67
|
+
output += "\n\n[系统提示] 请检查 bot/app 是否已加入目标 chat/thread(membership),并确认该目标允许当前 app 执行操作(target authorization);这不是 OAuth scope apply 错误。";
|
|
68
|
+
}
|
|
69
|
+
if (isScope) {
|
|
70
|
+
output += grantLink
|
|
71
|
+
? `\n\n授权链接(请 app owner 本人打开确认,约50分钟内有效,确认后即生效):\n${grantLink}`
|
|
72
|
+
: "\n\n[系统提示] 当前没有可用的授权链接。请勿自行拼造或猜测任何 scope-apply / 开发者后台链接(那类链接对本应用无效)。只需如实说明缺少上述 scope、需有开发者后台权限者去补充;有可用链接时系统会在此处给出。";
|
|
73
|
+
}
|
|
74
|
+
return output;
|
|
75
|
+
}
|
|
76
|
+
function createTransportBusinessContext(env = process.env) {
|
|
77
|
+
const { config } = larkinConfig.loadConfig(env);
|
|
78
|
+
const selectedAgent = larkinConfig.selectAgent(config, env);
|
|
79
|
+
const stateStore = (0, agent_state_store_js_1.createAgentStateStore)(config.larkinHome, selectedAgent.agentId);
|
|
80
|
+
if (path.resolve(stateStore.paths.root) !== path.resolve(selectedAgent.stateDir)) {
|
|
81
|
+
throw new Error(`Agent ${selectedAgent.agentId} state store 路径不一致`);
|
|
82
|
+
}
|
|
83
|
+
if (!config.serverId)
|
|
84
|
+
throw new Error("config.serverId 未配置");
|
|
85
|
+
const paths = stateStore.paths;
|
|
86
|
+
const attachmentDir = path.join(paths.root, "attachments");
|
|
87
|
+
const attachmentIndexFile = path.join(paths.root, "attachments.json");
|
|
88
|
+
const transportLogFile = path.join(paths.root, "transport.log");
|
|
89
|
+
const dryRun = env.LARKIN_FEISHU_DRYRUN === "1";
|
|
90
|
+
const agentId = selectedAgent.agentId;
|
|
91
|
+
const agentName = selectedAgent.name;
|
|
92
|
+
const agentRuntime = selectedAgent.runtime;
|
|
93
|
+
const agentModel = selectedAgent.model;
|
|
94
|
+
const agentEffort = selectedAgent.effort ?? null;
|
|
95
|
+
const serverId = config.serverId;
|
|
96
|
+
const unknownCreatedAt = "1970-01-01T00:00:00.000Z";
|
|
97
|
+
const log = (...parts) => {
|
|
98
|
+
const line = `[feishu-transport] ${parts.join(" ")}\n`;
|
|
99
|
+
process.stderr.write(line);
|
|
100
|
+
try {
|
|
101
|
+
fs.appendFileSync(transportLogFile, `${new Date().toISOString()} ${line}`);
|
|
102
|
+
}
|
|
103
|
+
catch { /* best effort */ }
|
|
104
|
+
};
|
|
105
|
+
const logRequest = (method, requestPath, body) => {
|
|
106
|
+
try {
|
|
107
|
+
fs.appendFileSync(transportLogFile, `${new Date().toISOString()} REQ ${method} ${requestPath} body=${JSON.stringify(body).slice(0, 300)}\n`);
|
|
108
|
+
}
|
|
109
|
+
catch { /* best effort */ }
|
|
110
|
+
};
|
|
111
|
+
let signatureCache = null;
|
|
112
|
+
const senderSignature = (senderId) => {
|
|
113
|
+
if (!senderId)
|
|
114
|
+
return null;
|
|
115
|
+
try {
|
|
116
|
+
const mtime = fs.statSync(paths.senderProfiles).mtimeMs;
|
|
117
|
+
if (!signatureCache || signatureCache.mtime !== mtime) {
|
|
118
|
+
signatureCache = { mtime, data: JSON.parse(fs.readFileSync(paths.senderProfiles, "utf8")) };
|
|
119
|
+
}
|
|
120
|
+
return signatureCache.data[senderId]?.description || null;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
let identityCache;
|
|
127
|
+
const botIdentity = () => {
|
|
128
|
+
if (identityCache !== undefined)
|
|
129
|
+
return identityCache;
|
|
130
|
+
try {
|
|
131
|
+
identityCache = JSON.parse(fs.readFileSync(paths.botIdentity, "utf8"));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
identityCache = null;
|
|
135
|
+
}
|
|
136
|
+
return identityCache;
|
|
137
|
+
};
|
|
138
|
+
const appendConversation = (item) => {
|
|
139
|
+
try {
|
|
140
|
+
fs.appendFileSync(paths.conversation, `${JSON.stringify({ ...item, text: (0, transport_shell_js_1.safeConversationExcerpt)(item.text) })}\n`);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
log(`conversation 写失败: ${error.message}`);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const loadMap = () => {
|
|
147
|
+
try {
|
|
148
|
+
return JSON.parse(fs.readFileSync(paths.map, "utf8"));
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return {};
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const resolveChatId = (target) => (0, transport_shell_js_1.resolveMappedChatId)(loadMap(), target);
|
|
155
|
+
const larkArgs = (...rest) => rest;
|
|
156
|
+
const larkCli = (args, cwd) => {
|
|
157
|
+
const managed = (0, agent_lark_cli_workspace_js_1.managedOfficialLarkCli)(selectedAgent, process.env);
|
|
158
|
+
const options = {
|
|
159
|
+
encoding: "utf8",
|
|
160
|
+
env: managed.env,
|
|
161
|
+
...(cwd ? { cwd } : {}),
|
|
162
|
+
};
|
|
163
|
+
const result = (0, node_child_process_1.spawnSync)(managed.command.command, [...managed.command.argsPrefix, ...larkArgs(...args)], options);
|
|
164
|
+
return { code: result.status, stdout: result.stdout || "", stderr: result.stderr || "" };
|
|
165
|
+
};
|
|
166
|
+
const larkJsonOut = (args) => {
|
|
167
|
+
let result;
|
|
168
|
+
try {
|
|
169
|
+
result = larkCli(args);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const clean = (value) => value.split("\n").filter((line) => !/^\[page|fetching/.test(line.trim())).join("\n");
|
|
175
|
+
for (const value of [result.stdout, result.stderr]) {
|
|
176
|
+
try {
|
|
177
|
+
const parsed = JSON.parse(clean(value));
|
|
178
|
+
if (parsed && typeof parsed === "object")
|
|
179
|
+
return parsed;
|
|
180
|
+
}
|
|
181
|
+
catch { /* try next stream */ }
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
};
|
|
185
|
+
const memberData = (chatId) => {
|
|
186
|
+
const result = larkJsonOut(["im", "+chat-members-list", "--chat-id", chatId, "--json"]);
|
|
187
|
+
return result?.ok && result.data ? result.data : null;
|
|
188
|
+
};
|
|
189
|
+
const userIds = new Map();
|
|
190
|
+
const chatUserIdSet = (chatId) => {
|
|
191
|
+
if (userIds.has(chatId))
|
|
192
|
+
return userIds.get(chatId) ?? null;
|
|
193
|
+
const result = larkJsonOut(["im", "+chat-members-list", "--chat-id", chatId, "--member-id-type", "user_id", "--json"]);
|
|
194
|
+
const set = result?.ok && result.data
|
|
195
|
+
? new Set((result.data.users || []).flatMap((user) => user.member_id ? [user.member_id] : []))
|
|
196
|
+
: null;
|
|
197
|
+
userIds.set(chatId, set);
|
|
198
|
+
return set;
|
|
199
|
+
};
|
|
200
|
+
const memberNames = (chatId) => {
|
|
201
|
+
const names = {};
|
|
202
|
+
const data = memberData(chatId);
|
|
203
|
+
if (!data)
|
|
204
|
+
return names;
|
|
205
|
+
for (const user of data.users || [])
|
|
206
|
+
if (user.member_id)
|
|
207
|
+
names[user.member_id] = user.name || user.member_id;
|
|
208
|
+
for (const bot of data.bots || []) {
|
|
209
|
+
const name = bot.name || bot.member_id || "bot";
|
|
210
|
+
if (bot.member_id)
|
|
211
|
+
names[bot.member_id] = name;
|
|
212
|
+
if (bot.app_id)
|
|
213
|
+
names[bot.app_id] = name;
|
|
214
|
+
}
|
|
215
|
+
return names;
|
|
216
|
+
};
|
|
217
|
+
const pairCache = new Map();
|
|
218
|
+
const memberNameIdPairs = (chatId) => {
|
|
219
|
+
const cached = pairCache.get(chatId);
|
|
220
|
+
if (cached)
|
|
221
|
+
return cached;
|
|
222
|
+
const byName = {};
|
|
223
|
+
for (const [id, name] of Object.entries(memberNames(chatId))) {
|
|
224
|
+
if (!name || name === id)
|
|
225
|
+
continue;
|
|
226
|
+
if (!byName[name] || (id.startsWith("ou_") && !byName[name].startsWith("ou_")))
|
|
227
|
+
byName[name] = id;
|
|
228
|
+
}
|
|
229
|
+
const pairs = Object.entries(byName).sort((left, right) => right[0].length - left[0].length);
|
|
230
|
+
pairCache.set(chatId, pairs);
|
|
231
|
+
return pairs;
|
|
232
|
+
};
|
|
233
|
+
const renderMentions = (content, chatId) => {
|
|
234
|
+
let mentioned = false;
|
|
235
|
+
let output = String(content).replace(/@(ou_[A-Za-z0-9]+|on_[A-Za-z0-9]+)/g, (_whole, id) => {
|
|
236
|
+
mentioned = true;
|
|
237
|
+
return `<at id="${id}"></at>`;
|
|
238
|
+
});
|
|
239
|
+
if (chatId && output.includes("@")) {
|
|
240
|
+
const pairs = memberNameIdPairs(chatId);
|
|
241
|
+
let rendered = "";
|
|
242
|
+
for (let index = 0; index < output.length;) {
|
|
243
|
+
if (output[index] === "@") {
|
|
244
|
+
const rest = output.slice(index + 1);
|
|
245
|
+
const hit = pairs.find(([name]) => rest.startsWith(name) && !/^[\p{L}\p{N}_]/u.test(rest.slice(name.length)));
|
|
246
|
+
if (hit) {
|
|
247
|
+
mentioned = true;
|
|
248
|
+
rendered += `<at id="${hit[1]}"></at>`;
|
|
249
|
+
index += hit[0].length + 1;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
rendered += output[index];
|
|
254
|
+
index += 1;
|
|
255
|
+
}
|
|
256
|
+
output = rendered;
|
|
257
|
+
}
|
|
258
|
+
const ids = chatId ? chatUserIdSet(chatId) : null;
|
|
259
|
+
if (ids?.size) {
|
|
260
|
+
output = output.replace(/@([A-Za-z0-9]{2,})/g, (whole, token) => {
|
|
261
|
+
if (!ids.has(token))
|
|
262
|
+
return whole;
|
|
263
|
+
mentioned = true;
|
|
264
|
+
return `<at id="${token}"></at>`;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return { content: output, mentioned };
|
|
268
|
+
};
|
|
269
|
+
const larkSend = (chatId, content, idempotencyKey) => {
|
|
270
|
+
const rendered = renderMentions(content, chatId);
|
|
271
|
+
const args = ["im", "+messages-send", "--chat-id", chatId, rendered.mentioned || (0, transport_shell_js_1.looksLikeMarkdown)(rendered.content) ? "--markdown" : "--text", rendered.content, "--json"];
|
|
272
|
+
if (idempotencyKey)
|
|
273
|
+
args.push("--idempotency-key", String(idempotencyKey));
|
|
274
|
+
if (dryRun)
|
|
275
|
+
args.push("--dry-run");
|
|
276
|
+
return larkCli(args);
|
|
277
|
+
};
|
|
278
|
+
const larkReply = (messageId, content, idempotencyKey, chatId) => {
|
|
279
|
+
const rendered = renderMentions(content, chatId);
|
|
280
|
+
const args = ["im", "+messages-reply", "--message-id", messageId, "--reply-in-thread", rendered.mentioned || (0, transport_shell_js_1.looksLikeMarkdown)(rendered.content) ? "--markdown" : "--text", rendered.content, "--json"];
|
|
281
|
+
if (idempotencyKey)
|
|
282
|
+
args.push("--idempotency-key", String(idempotencyKey));
|
|
283
|
+
if (dryRun)
|
|
284
|
+
args.push("--dry-run");
|
|
285
|
+
return larkCli(args);
|
|
286
|
+
};
|
|
287
|
+
const larkSendMedia = (chatId, reply, attachment, idempotencyKey) => {
|
|
288
|
+
const flag = (0, transport_shell_js_1.isImageMime)(attachment.mimeType) ? "--image" : "--file";
|
|
289
|
+
const replyTo = reply?.in_topic && reply.reply_to ? reply.reply_to : null;
|
|
290
|
+
const args = replyTo
|
|
291
|
+
? ["im", "+messages-reply", "--message-id", replyTo, "--reply-in-thread", flag, attachment.file, "--json"]
|
|
292
|
+
: ["im", "+messages-send", "--chat-id", chatId, flag, attachment.file, "--json"];
|
|
293
|
+
if (idempotencyKey)
|
|
294
|
+
args.push("--idempotency-key", idempotencyKey);
|
|
295
|
+
if (dryRun)
|
|
296
|
+
args.push("--dry-run");
|
|
297
|
+
return larkCli(args, attachmentDir);
|
|
298
|
+
};
|
|
299
|
+
const replyContextFor = (target) => {
|
|
300
|
+
try {
|
|
301
|
+
return JSON.parse(fs.readFileSync(paths.replyctx, "utf8"))[target] || null;
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
const outbound = (0, outbound_transport_js_1.createOutboundTransport)({
|
|
308
|
+
attachmentDir, attachmentIndexFile, resolveChatId, replyContextFor,
|
|
309
|
+
sendText: larkSend, replyText: larkReply, sendMedia: larkSendMedia,
|
|
310
|
+
appendConversation, botDisplayName: () => botIdentity()?.name, agentName, dryRun, log,
|
|
311
|
+
});
|
|
312
|
+
const liveGrantLink = () => {
|
|
313
|
+
try {
|
|
314
|
+
const file = env.LARKIN_GRANT_URL_FILE || "/tmp/larkin-grant-url.txt";
|
|
315
|
+
if (!fs.existsSync(file))
|
|
316
|
+
return null;
|
|
317
|
+
const alive = (0, node_child_process_1.spawnSync)("pgrep", ["-f", "grant-scopes.mjs"], { encoding: "utf8" });
|
|
318
|
+
if (!alive.stdout.trim())
|
|
319
|
+
return null;
|
|
320
|
+
const url = fs.readFileSync(file, "utf8").trim();
|
|
321
|
+
return /^https?:\/\//.test(url) ? url : null;
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
const feishuError = (result) => {
|
|
328
|
+
return formatFeishuError(result?.error, liveGrantLink());
|
|
329
|
+
};
|
|
330
|
+
const query = (requestPath, name) => {
|
|
331
|
+
try {
|
|
332
|
+
return new URL(`http://x${requestPath.startsWith("/") ? requestPath : `/${requestPath}`}`).searchParams.get(name);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
const channelContext = (target) => {
|
|
339
|
+
const value = String(target || "");
|
|
340
|
+
const base = value.split(":").slice(0, 2).join(":");
|
|
341
|
+
return {
|
|
342
|
+
chat_id: resolveChatId(value),
|
|
343
|
+
channel_type: value.startsWith("dm:") ? "dm" : "channel",
|
|
344
|
+
channel_name: base.replace(/^#/, "").replace(/^dm:@/, ""),
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
const chatMetaCache = {};
|
|
348
|
+
const chatMeta = (chatId) => {
|
|
349
|
+
if (!chatId)
|
|
350
|
+
return null;
|
|
351
|
+
if (Object.hasOwn(chatMetaCache, chatId))
|
|
352
|
+
return chatMetaCache[chatId];
|
|
353
|
+
const result = larkJsonOut(["im", "chats", "get", "--chat-id", chatId, "--json"]);
|
|
354
|
+
const meta = result?.ok && result.data
|
|
355
|
+
? { name: result.data.name || "", chat_mode: result.data.chat_mode || "", chat_type: result.data.chat_type || "" }
|
|
356
|
+
: null;
|
|
357
|
+
chatMetaCache[chatId] = meta;
|
|
358
|
+
return meta;
|
|
359
|
+
};
|
|
360
|
+
const topicShort = (target) => {
|
|
361
|
+
const body = String(target || "").replace(/^#/, "").replace(/^dm:@/, "");
|
|
362
|
+
const index = body.indexOf(":");
|
|
363
|
+
return index >= 0 ? body.slice(index + 1, index + 9) : "";
|
|
364
|
+
};
|
|
365
|
+
const topicDescriptor = (thread) => {
|
|
366
|
+
const result = larkJsonOut(["im", "+threads-messages-list", "--thread", thread, "--order", "asc", "--page-size", "1", "--json"]);
|
|
367
|
+
const first = result?.ok && result.data ? (result.data.messages || result.data.items || [])[0] : undefined;
|
|
368
|
+
if (!first)
|
|
369
|
+
return null;
|
|
370
|
+
let content = typeof first.content === "string" ? first.content : JSON.stringify(first.content || "");
|
|
371
|
+
try {
|
|
372
|
+
const parsed = JSON.parse(content);
|
|
373
|
+
content = typeof parsed.text === "string" ? parsed.text : (typeof parsed.content === "string" ? parsed.content : content);
|
|
374
|
+
}
|
|
375
|
+
catch { /* plain text */ }
|
|
376
|
+
const normalized = content.replace(/\[([^\]]+)\]\((https?:[^)]+)\)/g, "$1").replace(/https?:\/\/\S+/g, "").replace(/\s+/g, " ").trim();
|
|
377
|
+
return normalized ? normalized.slice(0, 48) : null;
|
|
378
|
+
};
|
|
379
|
+
const channelLabel = (target) => {
|
|
380
|
+
const chatId = resolveChatId(target);
|
|
381
|
+
const meta = chatMeta(chatId);
|
|
382
|
+
let label = meta?.name || target.replace(/^#/, "").replace(/^dm:@/, "");
|
|
383
|
+
const short = topicShort(target);
|
|
384
|
+
const context = replyContextFor(target);
|
|
385
|
+
if (short) {
|
|
386
|
+
const thread = context && (context.thread_id || context.reply_to);
|
|
387
|
+
const descriptor = thread ? topicDescriptor(thread) : null;
|
|
388
|
+
if (descriptor)
|
|
389
|
+
label += ` › ${descriptor}`;
|
|
390
|
+
}
|
|
391
|
+
return { label, chatId, meta, ctx: context, topicShort: short };
|
|
392
|
+
};
|
|
393
|
+
let appInfoCache;
|
|
394
|
+
const feishuAppInfo = () => {
|
|
395
|
+
if (appInfoCache !== undefined)
|
|
396
|
+
return appInfoCache;
|
|
397
|
+
appInfoCache = null;
|
|
398
|
+
const result = larkJsonOut(["api", "GET", `/open-apis/application/v6/applications/${selectedAgent.feishuAppId}`, "--params", '{"lang":"zh_cn"}']);
|
|
399
|
+
const app = result?.ok && result.data?.app;
|
|
400
|
+
if (app)
|
|
401
|
+
appInfoCache = { name: app.app_name || null, description: app.description || null, avatarUrl: app.avatar_url || null };
|
|
402
|
+
return appInfoCache;
|
|
403
|
+
};
|
|
404
|
+
let botNameCache;
|
|
405
|
+
const botDisplayName = () => {
|
|
406
|
+
if (botNameCache !== undefined)
|
|
407
|
+
return botNameCache;
|
|
408
|
+
botNameCache = null;
|
|
409
|
+
const identity = botIdentity();
|
|
410
|
+
if (identity?.name)
|
|
411
|
+
return (botNameCache = identity.name);
|
|
412
|
+
const knownChat = Object.values(loadMap()).find((value) => value.startsWith("oc_"));
|
|
413
|
+
if (knownChat) {
|
|
414
|
+
const bots = memberData(knownChat)?.bots || [];
|
|
415
|
+
const mine = bots.find((bot) => bot.app_id === selectedAgent.feishuAppId || (identity?.open_id && bot.member_id === identity.open_id)) || (bots.length === 1 ? bots[0] : null);
|
|
416
|
+
if (mine?.name)
|
|
417
|
+
botNameCache = mine.name;
|
|
418
|
+
}
|
|
419
|
+
return botNameCache;
|
|
420
|
+
};
|
|
421
|
+
const resolveMember = (chatId, raw) => {
|
|
422
|
+
const handle = String(raw || "").replace(/^@/, "").trim();
|
|
423
|
+
if (!handle)
|
|
424
|
+
return null;
|
|
425
|
+
if (/^ou_[A-Za-z0-9]+$/.test(handle))
|
|
426
|
+
return { ids: [handle], type: "open_id" };
|
|
427
|
+
if (/^on_[A-Za-z0-9]+$/.test(handle))
|
|
428
|
+
return { ids: [handle], type: "union_id" };
|
|
429
|
+
if (/^(cli_|app_)[A-Za-z0-9]+$/.test(handle))
|
|
430
|
+
return { ids: [handle], type: "app_id" };
|
|
431
|
+
const data = memberData(chatId);
|
|
432
|
+
const user = data?.users?.find((candidate) => candidate.name === handle && candidate.member_id);
|
|
433
|
+
if (user?.member_id)
|
|
434
|
+
return { ids: [user.member_id], type: "open_id" };
|
|
435
|
+
const bot = data?.bots?.find((candidate) => candidate.name === handle);
|
|
436
|
+
if (bot?.app_id)
|
|
437
|
+
return { ids: [bot.app_id], type: "app_id" };
|
|
438
|
+
if (bot?.member_id)
|
|
439
|
+
return { ids: [bot.member_id], type: "open_id" };
|
|
440
|
+
return /^[A-Za-z0-9._-]{2,}$/.test(handle) ? { ids: [handle], type: "user_id" } : null;
|
|
441
|
+
};
|
|
442
|
+
const projectMessage = (message, channelType, channelName, names) => {
|
|
443
|
+
const senderId = String(message.sender?.id || "");
|
|
444
|
+
const isBot = Boolean(message.sender && (message.sender.sender_type === "app" || message.sender.id_type === "app_id"));
|
|
445
|
+
const self = Boolean(isBot && senderId && senderId === selectedAgent.feishuAppId);
|
|
446
|
+
return (0, history_projection_js_1.projectFeishuHistoryEnvelope)({
|
|
447
|
+
message, channelType, channelName, names,
|
|
448
|
+
selectedAppId: selectedAgent.feishuAppId,
|
|
449
|
+
botIdentity: (0, history_projection_js_1.parseFeishuText)(message) !== null || self ? botIdentity() : null,
|
|
450
|
+
senderDescription: self ? null : (0, message_policy_js_1.signatureDescription)(senderSignature(senderId)),
|
|
451
|
+
});
|
|
452
|
+
};
|
|
453
|
+
const channelTransport = (0, channel_transport_js_1.createChannelTransport)({
|
|
454
|
+
dryRun, selectedAgent, agentId, agentName, agentRuntime, agentModel, agentEffort, serverId, unknownCreatedAt,
|
|
455
|
+
query, resolveChatId, resolveMemberTo: resolveMember, channelContext, channelLabel, chatMeta,
|
|
456
|
+
invalidateChatMeta: (chatId) => { delete chatMetaCache[chatId]; },
|
|
457
|
+
rememberChannel: (target, chatId, meta) => {
|
|
458
|
+
try {
|
|
459
|
+
const map = loadMap();
|
|
460
|
+
map[target] = chatId;
|
|
461
|
+
stateStore.writeJson("map", map);
|
|
462
|
+
chatMetaCache[chatId] = {
|
|
463
|
+
name: typeof meta.name === "string" ? meta.name : "",
|
|
464
|
+
chat_mode: typeof meta.chat_mode === "string" ? meta.chat_mode : "",
|
|
465
|
+
chat_type: typeof meta.chat_type === "string" ? meta.chat_type : "",
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
catch { /* preserve best-effort map update */ }
|
|
469
|
+
},
|
|
470
|
+
mappedChannels: loadMap, larkJsonOut, feishuError, botDisplayName, feishuAppInfo, log,
|
|
471
|
+
});
|
|
472
|
+
const reminderRoutes = (0, reminder_routes_js_1.createReminderRoutes)({ stateFile: paths.reminders, agentId, query, log });
|
|
473
|
+
try {
|
|
474
|
+
fs.appendFileSync(transportLogFile, `${new Date().toISOString()} INIT profile=${selectedAgent.feishuProfile} larkCfgDir=${selectedAgent.larkConfigDir} inbox=${paths.inbox}\n`);
|
|
475
|
+
}
|
|
476
|
+
catch { /* best effort */ }
|
|
477
|
+
return {
|
|
478
|
+
stateStore, dryRun, agentId, agentName, serverId, transportLogFile,
|
|
479
|
+
outbound, channelTransport, reminderRoutes,
|
|
480
|
+
loadMap, larkJsonOut, feishuError, query, channelContext, chatMeta, topicShort,
|
|
481
|
+
memberNames, projectMessage,
|
|
482
|
+
projectInboxEvents: inbox_projection_js_1.projectInboxEvents,
|
|
483
|
+
sortVisibleHistory: history_projection_js_1.sortVisibleHistory,
|
|
484
|
+
logRequest, log,
|
|
485
|
+
};
|
|
486
|
+
}
|