larkway 0.3.34 → 0.3.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/dist/cli/index.js +176 -9
- package/dist/main.js +881 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
You @ the bot in a Feishu thread. It runs on your machine — reading your real codebase, executing commands, opening MRs — and posts the result back. You define what the agent knows and what it can do. Larkway just carries the messages.
|
|
10
10
|
|
|
11
|
-
**Current release: v0.3.
|
|
11
|
+
**Current release: v0.3.35**
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
package/README.zh.md
CHANGED
package/dist/cli/index.js
CHANGED
|
@@ -112381,6 +112381,125 @@ var init_channelPostClient = __esm({
|
|
|
112381
112381
|
}
|
|
112382
112382
|
});
|
|
112383
112383
|
|
|
112384
|
+
// src/lark/channelCotClient.ts
|
|
112385
|
+
async function withTimeout(p, ms, label) {
|
|
112386
|
+
let timer;
|
|
112387
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
112388
|
+
timer = setTimeout(
|
|
112389
|
+
() => reject(new Error(`[channel.cot] ${label} timed out after ${ms}ms`)),
|
|
112390
|
+
ms
|
|
112391
|
+
);
|
|
112392
|
+
timer.unref?.();
|
|
112393
|
+
});
|
|
112394
|
+
try {
|
|
112395
|
+
return await Promise.race([p, timeout]);
|
|
112396
|
+
} finally {
|
|
112397
|
+
if (timer) clearTimeout(timer);
|
|
112398
|
+
}
|
|
112399
|
+
}
|
|
112400
|
+
function assertOk(res, label) {
|
|
112401
|
+
if (res.code !== void 0 && res.code !== 0) {
|
|
112402
|
+
throw new Error(
|
|
112403
|
+
`[channel.cot] ${label} failed: code=${res.code} msg=${res.msg ?? "<no msg>"}`
|
|
112404
|
+
);
|
|
112405
|
+
}
|
|
112406
|
+
}
|
|
112407
|
+
function stringField(data, key) {
|
|
112408
|
+
const value = data?.[key];
|
|
112409
|
+
return typeof value === "string" ? value : void 0;
|
|
112410
|
+
}
|
|
112411
|
+
var COT_REQUEST_TIMEOUT_MS, ChannelCotClient;
|
|
112412
|
+
var init_channelCotClient = __esm({
|
|
112413
|
+
"src/lark/channelCotClient.ts"() {
|
|
112414
|
+
"use strict";
|
|
112415
|
+
COT_REQUEST_TIMEOUT_MS = 8e3;
|
|
112416
|
+
ChannelCotClient = class {
|
|
112417
|
+
resolveChannel;
|
|
112418
|
+
constructor(opts) {
|
|
112419
|
+
this.resolveChannel = opts.resolveChannel;
|
|
112420
|
+
}
|
|
112421
|
+
channel() {
|
|
112422
|
+
const ch = this.resolveChannel();
|
|
112423
|
+
if (!ch) {
|
|
112424
|
+
throw new Error("[channel.cot] outbound called before the Channel SDK connected");
|
|
112425
|
+
}
|
|
112426
|
+
return ch;
|
|
112427
|
+
}
|
|
112428
|
+
/** Every COT call goes through here, so all get the same hard timeout. */
|
|
112429
|
+
request(opts) {
|
|
112430
|
+
return withTimeout(
|
|
112431
|
+
this.channel().rawClient.request(opts),
|
|
112432
|
+
COT_REQUEST_TIMEOUT_MS,
|
|
112433
|
+
`${opts.method} ${opts.url}`
|
|
112434
|
+
);
|
|
112435
|
+
}
|
|
112436
|
+
async create(target) {
|
|
112437
|
+
const receiveIdType = target.threadId ? "thread_id" : "chat_id";
|
|
112438
|
+
const receiveId = target.threadId ?? target.chatId;
|
|
112439
|
+
const res = await this.request({
|
|
112440
|
+
url: "/open-apis/im/v1/message_cot",
|
|
112441
|
+
method: "POST",
|
|
112442
|
+
params: { receive_id_type: receiveIdType },
|
|
112443
|
+
data: {
|
|
112444
|
+
receive_id: receiveId,
|
|
112445
|
+
// origin_message_id only anchors non-thread bubbles; harmless if set,
|
|
112446
|
+
// but omitted for topic threads where the thread id already anchors.
|
|
112447
|
+
...!target.threadId && target.originMessageId ? { origin_message_id: target.originMessageId } : {}
|
|
112448
|
+
}
|
|
112449
|
+
});
|
|
112450
|
+
assertOk(res, "create");
|
|
112451
|
+
const cotId = stringField(res.data, "cot_id");
|
|
112452
|
+
const messageId = stringField(res.data, "message_id");
|
|
112453
|
+
if (!cotId || !messageId) {
|
|
112454
|
+
throw new Error(
|
|
112455
|
+
`[channel.cot] create returned no cot_id/message_id (${JSON.stringify(res.data ?? {}).slice(0, 200)})`
|
|
112456
|
+
);
|
|
112457
|
+
}
|
|
112458
|
+
return { cotId, messageId };
|
|
112459
|
+
}
|
|
112460
|
+
async update(ref, events) {
|
|
112461
|
+
if (events.length === 0) return;
|
|
112462
|
+
const res = await this.request({
|
|
112463
|
+
url: "/open-apis/im/v1/message_cot",
|
|
112464
|
+
method: "PUT",
|
|
112465
|
+
data: { cot_id: ref.cotId, message_id: ref.messageId, events: [...events] }
|
|
112466
|
+
});
|
|
112467
|
+
assertOk(res, "update");
|
|
112468
|
+
}
|
|
112469
|
+
async complete(ref, reason) {
|
|
112470
|
+
const res = await this.request({
|
|
112471
|
+
url: `/open-apis/im/v1/message_cot/complete/${encodeURIComponent(ref.cotId)}`,
|
|
112472
|
+
method: "POST",
|
|
112473
|
+
params: { message_id: ref.messageId, reason }
|
|
112474
|
+
});
|
|
112475
|
+
assertOk(res, "complete");
|
|
112476
|
+
}
|
|
112477
|
+
async resolveThreadId(messageId) {
|
|
112478
|
+
try {
|
|
112479
|
+
const res = await this.request({
|
|
112480
|
+
url: `/open-apis/im/v1/messages/${encodeURIComponent(messageId)}`,
|
|
112481
|
+
method: "GET"
|
|
112482
|
+
});
|
|
112483
|
+
if (res.code !== void 0 && res.code !== 0) return void 0;
|
|
112484
|
+
const items = res.data?.["items"];
|
|
112485
|
+
if (!Array.isArray(items)) return void 0;
|
|
112486
|
+
for (const item of items) {
|
|
112487
|
+
if (item && typeof item === "object") {
|
|
112488
|
+
const threadId = item["thread_id"];
|
|
112489
|
+
if (typeof threadId === "string" && threadId.startsWith("omt_")) {
|
|
112490
|
+
return threadId;
|
|
112491
|
+
}
|
|
112492
|
+
}
|
|
112493
|
+
}
|
|
112494
|
+
return void 0;
|
|
112495
|
+
} catch {
|
|
112496
|
+
return void 0;
|
|
112497
|
+
}
|
|
112498
|
+
}
|
|
112499
|
+
};
|
|
112500
|
+
}
|
|
112501
|
+
});
|
|
112502
|
+
|
|
112384
112503
|
// src/lark/channelClient.ts
|
|
112385
112504
|
var channelClient_exports = {};
|
|
112386
112505
|
__export(channelClient_exports, {
|
|
@@ -112431,13 +112550,13 @@ function arrayField(obj, key) {
|
|
|
112431
112550
|
const value = obj[key];
|
|
112432
112551
|
return Array.isArray(value) ? value : null;
|
|
112433
112552
|
}
|
|
112434
|
-
function
|
|
112553
|
+
function stringField2(obj, key) {
|
|
112435
112554
|
if (!obj || typeof obj !== "object") return void 0;
|
|
112436
112555
|
const value = obj[key];
|
|
112437
112556
|
return typeof value === "string" ? value : void 0;
|
|
112438
112557
|
}
|
|
112439
112558
|
function nonEmptyStringField(obj, key) {
|
|
112440
|
-
const value =
|
|
112559
|
+
const value = stringField2(obj, key);
|
|
112441
112560
|
return value && value.length > 0 ? value : void 0;
|
|
112442
112561
|
}
|
|
112443
112562
|
function parseLarkCliMessages(stdout2) {
|
|
@@ -112540,6 +112659,7 @@ var init_channelClient = __esm({
|
|
|
112540
112659
|
init_channelCardClient();
|
|
112541
112660
|
init_channelCardKitClient();
|
|
112542
112661
|
init_channelPostClient();
|
|
112662
|
+
init_channelCotClient();
|
|
112543
112663
|
execFile4 = promisify4(execFileCallback);
|
|
112544
112664
|
LEARNED_CHATS_LIMIT = 100;
|
|
112545
112665
|
SEEN_MESSAGES_LIMIT = 1e3;
|
|
@@ -112675,6 +112795,8 @@ var init_channelClient = __esm({
|
|
|
112675
112795
|
cardKitClient = null;
|
|
112676
112796
|
/** Lazily built and only requested by main.ts when post outbound gates are configured. */
|
|
112677
112797
|
postClient = null;
|
|
112798
|
+
/** Lazily built COT (思维链) client; only requested when a bot enables cot != off. */
|
|
112799
|
+
cotClient = null;
|
|
112678
112800
|
constructor(opts) {
|
|
112679
112801
|
if (!opts.appId || !opts.appSecret) {
|
|
112680
112802
|
throw new Error(
|
|
@@ -112940,6 +113062,22 @@ var init_channelClient = __esm({
|
|
|
112940
113062
|
}
|
|
112941
113063
|
return this.cardKitClient;
|
|
112942
113064
|
}
|
|
113065
|
+
/**
|
|
113066
|
+
* Return an OutboundCotClient bound to this client's channel handle.
|
|
113067
|
+
*
|
|
113068
|
+
* main.ts only calls this when the bot's `cot` config is not "off". The COT
|
|
113069
|
+
* bubble uses the SDK's generic rawClient.request() (tenant token auto-
|
|
113070
|
+
* injected via the same token manager as every other outbound call), so it
|
|
113071
|
+
* adds no auth surface. Resolves the live channel lazily at call time.
|
|
113072
|
+
*/
|
|
113073
|
+
outboundCotClient() {
|
|
113074
|
+
if (!this.cotClient) {
|
|
113075
|
+
this.cotClient = new ChannelCotClient({
|
|
113076
|
+
resolveChannel: () => this.channel
|
|
113077
|
+
});
|
|
113078
|
+
}
|
|
113079
|
+
return this.cotClient;
|
|
113080
|
+
}
|
|
112943
113081
|
/**
|
|
112944
113082
|
* Return an OutboundPostClient bound to this client's channel handle.
|
|
112945
113083
|
*
|
|
@@ -113243,7 +113381,7 @@ var init_channelClient = __esm({
|
|
|
113243
113381
|
const chats = parseLarkCliMessages(stdout2) ?? [];
|
|
113244
113382
|
fetched += chats.length;
|
|
113245
113383
|
for (const raw of chats) {
|
|
113246
|
-
const chatId =
|
|
113384
|
+
const chatId = stringField2(raw, "chat_id");
|
|
113247
113385
|
if (!chatId?.startsWith("oc_")) continue;
|
|
113248
113386
|
discoveredChatIds.add(chatId);
|
|
113249
113387
|
const before = this.recentlySeenChatIds.size;
|
|
@@ -113257,7 +113395,7 @@ var init_channelClient = __esm({
|
|
|
113257
113395
|
const hasMore = Boolean(
|
|
113258
113396
|
(data && typeof data === "object" && data["has_more"]) ?? (parsed && typeof parsed === "object" && parsed["has_more"])
|
|
113259
113397
|
);
|
|
113260
|
-
pageToken =
|
|
113398
|
+
pageToken = stringField2(data, "page_token") ?? stringField2(parsed, "page_token") ?? "";
|
|
113261
113399
|
if (!hasMore || !pageToken) break;
|
|
113262
113400
|
}
|
|
113263
113401
|
if (newlyLearned > 0) {
|
|
@@ -113571,7 +113709,7 @@ function isTaskRequestTimeoutError(err2) {
|
|
|
113571
113709
|
if (err2 instanceof TaskApiError && err2.cause instanceof TaskRequestTimeoutError) return true;
|
|
113572
113710
|
return false;
|
|
113573
113711
|
}
|
|
113574
|
-
function
|
|
113712
|
+
function withTimeout2(promise, ms, label) {
|
|
113575
113713
|
let timer;
|
|
113576
113714
|
const timeout = new Promise((_, reject) => {
|
|
113577
113715
|
timer = setTimeout(() => reject(new TaskRequestTimeoutError(`${label} timed out after ${ms}ms`)), ms);
|
|
@@ -113615,7 +113753,7 @@ var init_client = __esm({
|
|
|
113615
113753
|
}
|
|
113616
113754
|
/** Single choke point for every outbound call — applies the timeout race uniformly. */
|
|
113617
113755
|
#request(config, label) {
|
|
113618
|
-
return
|
|
113756
|
+
return withTimeout2(this.#requester.request(config), this.#timeoutMs, label);
|
|
113619
113757
|
}
|
|
113620
113758
|
async getTask(taskGuid) {
|
|
113621
113759
|
let resp;
|
|
@@ -125718,6 +125856,31 @@ var BotConfigSchema = external_exports.object({
|
|
|
125718
125856
|
* @default "claude"
|
|
125719
125857
|
*/
|
|
125720
125858
|
backend: external_exports.string().min(1).default("claude"),
|
|
125859
|
+
/**
|
|
125860
|
+
* COT (思维链) 气泡:把 agent 的 thinking 思考过程 + 工具调用摘要实时推到飞书
|
|
125861
|
+
* 原生的可折叠思维链气泡(与最终答案卡片互不干扰,最终答案永远只走卡片)。
|
|
125862
|
+
*
|
|
125863
|
+
* - "off":完全不调用 message_cot API(零网络、零副作用)。
|
|
125864
|
+
* - "brief"(默认):思考过程 + 工具名摘要。
|
|
125865
|
+
* - "detailed":额外推工具入参(TOOL_CALL_ARGS)与截断后的工具结果。
|
|
125866
|
+
*
|
|
125867
|
+
* message_cot 是无公开文档的 API —— 任何一步失败都会在本次会话内自动降级、
|
|
125868
|
+
* 只记 warn,绝不影响卡片和最终答案(见 src/bridge/cotProgress.ts)。
|
|
125869
|
+
*/
|
|
125870
|
+
cot: external_exports.enum(["off", "brief", "detailed"]).default("brief"),
|
|
125871
|
+
/**
|
|
125872
|
+
* COT 展示形态。`cot` 管密度(off/brief/detailed),这个管落点:
|
|
125873
|
+
* - "bubble"(默认):走飞书原生 message_cot 思维链气泡(src/bridge/cotProgress.ts)——
|
|
125874
|
+
* 客户端原生渲染(工具图标列表 + 可折叠头),真机体验最好。已知限制:话题内
|
|
125875
|
+
* 锚定不可用(气泡落群顶层,本租户 thread 通道判死),收尾 bug 已在 main 修复。
|
|
125876
|
+
* - "card":思考过程内嵌进答案卡片里的 collapsible_panel(懒创建、答案上方)。
|
|
125877
|
+
* ⚠️ 实验选项 —— 2026-07-05 真机实测:飞书客户端**不渲染 collapsible_panel 的
|
|
125878
|
+
* 折叠交互**(API 全 code=0,但面板被拍平成普通文本、无折叠箭头、终态也不可折叠;
|
|
125879
|
+
* CardKit 无 GET 读回、只能真机人眼验)。故 card 形态目前仅作纯文本内嵌展示用,
|
|
125880
|
+
* 体验不如气泡,不建议作默认。见 memory feishu-message-cot-api。
|
|
125881
|
+
* cot="off" 时本字段无意义(两种形态都不产出)。
|
|
125882
|
+
*/
|
|
125883
|
+
cotSurface: external_exports.enum(["card", "bubble"]).default("bubble"),
|
|
125721
125884
|
/**
|
|
125722
125885
|
* 话题 ↔ 飞书任务句柄(docs/task-handle.md,v2)。省略此字段不代表关闭,但也
|
|
125723
125886
|
* 不会触发任何自动建清单——main.ts 在 startup 时只做只读解析(yaml 里的
|
|
@@ -127196,6 +127359,8 @@ async function runCreateBot(ctx, creds, basics) {
|
|
|
127196
127359
|
response_surface_prototype: DEFAULT_RESPONSE_SURFACE_PROTOTYPE,
|
|
127197
127360
|
runtime: "agent_workspace",
|
|
127198
127361
|
backend: basics.backend,
|
|
127362
|
+
cot: "brief",
|
|
127363
|
+
cotSurface: "bubble",
|
|
127199
127364
|
memory_file: memoryFile,
|
|
127200
127365
|
...basics.gitlabTokenEnv ? { gitlab_token_env: basics.gitlabTokenEnv } : {},
|
|
127201
127366
|
...basics.gitName && basics.gitEmail ? { git_identity: { name: basics.gitName, email: basics.gitEmail } } : {}
|
|
@@ -130650,6 +130815,8 @@ async function createBotFromCreds(opts) {
|
|
|
130650
130815
|
response_surface_prototype: DEFAULT_RESPONSE_SURFACE_PROTOTYPE,
|
|
130651
130816
|
runtime: "agent_workspace",
|
|
130652
130817
|
backend: form.backend && form.backend.trim() ? form.backend.trim() : "codex",
|
|
130818
|
+
cot: "brief",
|
|
130819
|
+
cotSurface: "bubble",
|
|
130653
130820
|
// Persist the Feishu avatar URL so the Web 管理面 can show an avatar before
|
|
130654
130821
|
// the bridge writes status.json (pre-bridge / central roster). Best-effort:
|
|
130655
130822
|
// only set when resolveBotIdentity returned a valid url.
|
|
@@ -131182,8 +131349,8 @@ async function loadChatNameMap(profile) {
|
|
|
131182
131349
|
try {
|
|
131183
131350
|
const { stdout: stdout2 } = await eventExecFile("lark-cli", args, { timeout: 8e3 });
|
|
131184
131351
|
for (const chat of extractLarkCliChats(stdout2)) {
|
|
131185
|
-
const chatId =
|
|
131186
|
-
const name =
|
|
131352
|
+
const chatId = stringField3(chat, "chat_id");
|
|
131353
|
+
const name = stringField3(chat, "name");
|
|
131187
131354
|
if (chatId && name) names.set(chatId, name);
|
|
131188
131355
|
}
|
|
131189
131356
|
} catch {
|
|
@@ -131210,7 +131377,7 @@ function extractLarkCliChats(stdout2) {
|
|
|
131210
131377
|
function isLarkOpenChatId(value) {
|
|
131211
131378
|
return typeof value === "string" && value.startsWith("oc_");
|
|
131212
131379
|
}
|
|
131213
|
-
function
|
|
131380
|
+
function stringField3(value, key) {
|
|
131214
131381
|
if (!value || typeof value !== "object") return void 0;
|
|
131215
131382
|
const out = value[key];
|
|
131216
131383
|
return typeof out === "string" ? out : void 0;
|