larkway 0.3.21 → 0.3.23

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 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.21**
11
+ **Current release: v0.3.23**
12
12
 
13
13
  ---
14
14
 
package/README.zh.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  你在飞书话题里 @ bot,它在你的机器上运行——读真实代码库、执行命令、开 MR——把结果贴回飞书。你定义 agent 知道什么、能做什么。Larkway 只负责传递消息。
10
10
 
11
- **当前版本:v0.3.21**
11
+ **当前版本:v0.3.23**
12
12
 
13
13
  ---
14
14
 
package/dist/cli/index.js CHANGED
@@ -20506,7 +20506,7 @@ var require_websocket = __commonJS({
20506
20506
  var http2 = __require("http");
20507
20507
  var net = __require("net");
20508
20508
  var tls = __require("tls");
20509
- var { randomBytes: randomBytes2, createHash } = __require("crypto");
20509
+ var { randomBytes: randomBytes2, createHash: createHash2 } = __require("crypto");
20510
20510
  var { Duplex, Readable } = __require("stream");
20511
20511
  var { URL: URL2 } = __require("url");
20512
20512
  var PerMessageDeflate = require_permessage_deflate();
@@ -21174,7 +21174,7 @@ var require_websocket = __commonJS({
21174
21174
  abortHandshake(websocket, socket, "Invalid Upgrade header");
21175
21175
  return;
21176
21176
  }
21177
- const digest = createHash("sha1").update(key + GUID).digest("base64");
21177
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
21178
21178
  if (res.headers["sec-websocket-accept"] !== digest) {
21179
21179
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
21180
21180
  return;
@@ -21543,7 +21543,7 @@ var require_websocket_server = __commonJS({
21543
21543
  var EventEmitter = __require("events");
21544
21544
  var http2 = __require("http");
21545
21545
  var { Duplex } = __require("stream");
21546
- var { createHash } = __require("crypto");
21546
+ var { createHash: createHash2 } = __require("crypto");
21547
21547
  var extension = require_extension();
21548
21548
  var PerMessageDeflate = require_permessage_deflate();
21549
21549
  var subprotocol = require_subprotocol();
@@ -21850,7 +21850,7 @@ var require_websocket_server = __commonJS({
21850
21850
  );
21851
21851
  }
21852
21852
  if (this._state > RUNNING) return abortHandshake(socket, 503);
21853
- const digest = createHash("sha1").update(key + GUID).digest("base64");
21853
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
21854
21854
  const headers = [
21855
21855
  "HTTP/1.1 101 Switching Protocols",
21856
21856
  "Upgrade: websocket",
@@ -108741,7 +108741,7 @@ ${block}
108741
108741
  const raw = err2;
108742
108742
  return ((_b = (_a = raw === null || raw === void 0 ? void 0 : raw.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.msg) || ((_d = (_c = raw === null || raw === void 0 ? void 0 : raw.response) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.message) || (raw === null || raw === void 0 ? void 0 : raw.message) || String(err2);
108743
108743
  }
108744
- function isRetryable2(err2) {
108744
+ function isRetryable3(err2) {
108745
108745
  return err2.code === "rate_limited" || err2.code === "unknown";
108746
108746
  }
108747
108747
  function isFormatError(err2) {
@@ -108762,7 +108762,7 @@ ${block}
108762
108762
  } catch (raw) {
108763
108763
  const err2 = classifyError(raw, { attempt });
108764
108764
  lastErr = err2;
108765
- if (attempt >= max || !isRetryable2(err2)) {
108765
+ if (attempt >= max || !isRetryable3(err2)) {
108766
108766
  throw err2;
108767
108767
  }
108768
108768
  const delay = base * Math.pow(3, attempt - 1);
@@ -111182,6 +111182,254 @@ var init_channelCardClient = __esm({
111182
111182
  }
111183
111183
  });
111184
111184
 
111185
+ // src/lark/channelCardKitClient.ts
111186
+ import { createHash } from "node:crypto";
111187
+ function isRetryable2(err2) {
111188
+ if (err2 instanceof CardKitReplyConversionError) return true;
111189
+ if (!(err2 instanceof Error)) return false;
111190
+ const msg = err2.message.toLowerCase();
111191
+ if (msg.includes("200810")) return true;
111192
+ if (msg.includes("user callback")) return true;
111193
+ if (msg.includes("interaction")) return true;
111194
+ if (msg.includes("socket hang up")) return true;
111195
+ if (msg.includes("econnreset")) return true;
111196
+ if (msg.includes("etimedout")) return true;
111197
+ if (msg.includes("econnrefused")) return true;
111198
+ if (msg.includes("connect timeout")) return true;
111199
+ const anyErr = err2;
111200
+ const status = anyErr["status"];
111201
+ if (typeof status === "number" && status >= 500) return true;
111202
+ const code = anyErr["code"];
111203
+ if (code === 200810 || code === "200810") return true;
111204
+ const response = anyErr["response"];
111205
+ if (response && typeof response === "object") {
111206
+ const data = response["data"];
111207
+ if (data && typeof data === "object") {
111208
+ const responseCode = data["code"];
111209
+ if (responseCode === 200810 || responseCode === "200810") return true;
111210
+ }
111211
+ }
111212
+ return code === "ECONNRESET" || code === "ETIMEDOUT";
111213
+ }
111214
+ async function withCardKitRetry(label, fn, opts) {
111215
+ let lastErr;
111216
+ for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
111217
+ try {
111218
+ return await fn();
111219
+ } catch (err2) {
111220
+ lastErr = err2;
111221
+ if (attempt === opts.maxAttempts || !isRetryable2(err2)) throw err2;
111222
+ const delay = opts.baseDelayMs * Math.pow(2, attempt - 1);
111223
+ console.warn(
111224
+ `[channel.cardkit] ${label} attempt ${attempt} failed (${err2.message}), retrying in ${delay}ms`
111225
+ );
111226
+ await new Promise((r) => setTimeout(r, delay));
111227
+ }
111228
+ }
111229
+ throw lastErr;
111230
+ }
111231
+ var CardKitReplyConversionError, ChannelCardKitClient;
111232
+ var init_channelCardKitClient = __esm({
111233
+ "src/lark/channelCardKitClient.ts"() {
111234
+ "use strict";
111235
+ CardKitReplyConversionError = class extends Error {
111236
+ messageId;
111237
+ constructor(messageId, cause) {
111238
+ super(
111239
+ `[channel.cardkit] card.idConvert returned no card_id (messageId=${messageId})`
111240
+ );
111241
+ this.name = "CardKitReplyConversionError";
111242
+ this.messageId = messageId;
111243
+ this.cause = cause;
111244
+ }
111245
+ };
111246
+ ChannelCardKitClient = class {
111247
+ resolveChannel;
111248
+ cardThreads;
111249
+ maxAttempts;
111250
+ baseDelayMs;
111251
+ constructor(opts) {
111252
+ this.resolveChannel = opts.resolveChannel;
111253
+ this.cardThreads = opts.cardThreads;
111254
+ this.maxAttempts = opts.maxAttempts ?? 3;
111255
+ this.baseDelayMs = opts.baseDelayMs ?? 300;
111256
+ }
111257
+ channel() {
111258
+ const ch = this.resolveChannel();
111259
+ if (!ch) {
111260
+ throw new Error("[channel.cardkit] outbound called before the Channel SDK connected");
111261
+ }
111262
+ return ch;
111263
+ }
111264
+ async createCardEntity(card) {
111265
+ const res = await withCardKitRetry(
111266
+ "createCardEntity",
111267
+ () => this.channel().rawClient.cardkit.v1.card.create({
111268
+ data: { type: "card_json", data: JSON.stringify(card) }
111269
+ }),
111270
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111271
+ );
111272
+ const cardId = res.data?.card_id;
111273
+ if (!cardId) throw new Error("[channel.cardkit] card.create returned no card_id");
111274
+ return { cardId };
111275
+ }
111276
+ async createCardReply(replyToMessageId, card, opts) {
111277
+ const res = await withCardKitRetry(
111278
+ "createCardReply",
111279
+ () => this.channel().rawClient.im.v1.message.reply({
111280
+ path: { message_id: replyToMessageId },
111281
+ data: {
111282
+ content: JSON.stringify(card),
111283
+ msg_type: "interactive",
111284
+ reply_in_thread: opts.replyInThread,
111285
+ uuid: opts.idempotencyKey
111286
+ }
111287
+ }),
111288
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111289
+ );
111290
+ const messageId = res.data?.message_id;
111291
+ if (!messageId) {
111292
+ throw new Error(
111293
+ `[channel.cardkit] im.v1.message.reply returned no message_id (replyTo=${replyToMessageId})`
111294
+ );
111295
+ }
111296
+ const converted = await withCardKitRetry(
111297
+ "idConvert",
111298
+ async () => {
111299
+ const converted2 = await this.channel().rawClient.cardkit.v1.card.idConvert({
111300
+ data: { message_id: messageId }
111301
+ });
111302
+ if (!converted2.data?.card_id) {
111303
+ throw new CardKitReplyConversionError(messageId);
111304
+ }
111305
+ return converted2;
111306
+ },
111307
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111308
+ );
111309
+ const cardId = converted.data?.card_id;
111310
+ if (!cardId) throw new CardKitReplyConversionError(messageId);
111311
+ this.cardThreads.set(messageId, opts.threadId ?? replyToMessageId);
111312
+ return { cardId, messageId };
111313
+ }
111314
+ async replyCardEntity(replyToMessageId, cardId, opts) {
111315
+ const res = await withCardKitRetry(
111316
+ "replyCardEntity",
111317
+ () => this.channel().rawClient.im.v1.message.reply({
111318
+ path: { message_id: replyToMessageId },
111319
+ data: {
111320
+ content: JSON.stringify({ type: "card", data: { card_id: cardId } }),
111321
+ msg_type: "interactive",
111322
+ reply_in_thread: opts.replyInThread,
111323
+ uuid: opts.idempotencyKey
111324
+ }
111325
+ }),
111326
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111327
+ );
111328
+ const messageId = res.data?.message_id;
111329
+ if (!messageId) {
111330
+ throw new Error(
111331
+ `[channel.cardkit] im.v1.message.reply returned no message_id (replyTo=${replyToMessageId})`
111332
+ );
111333
+ }
111334
+ this.cardThreads.set(messageId, opts.threadId ?? replyToMessageId);
111335
+ return { messageId };
111336
+ }
111337
+ async updateCardEntity(cardId, card, opts) {
111338
+ await withCardKitRetry(
111339
+ "updateCardEntity",
111340
+ () => this.channel().rawClient.cardkit.v1.card.update({
111341
+ path: { card_id: cardId },
111342
+ data: {
111343
+ card: { type: "card_json", data: JSON.stringify(card) },
111344
+ sequence: opts.sequence,
111345
+ uuid: opts.uuid
111346
+ }
111347
+ }),
111348
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111349
+ );
111350
+ }
111351
+ async streamElementContent(cardId, elementId, content, opts) {
111352
+ await withCardKitRetry(
111353
+ "streamElementContent",
111354
+ () => this.channel().rawClient.cardkit.v1.cardElement.content({
111355
+ path: { card_id: cardId, element_id: elementId },
111356
+ data: { content, sequence: opts.sequence, uuid: opts.uuid }
111357
+ }),
111358
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111359
+ );
111360
+ }
111361
+ async createElements(cardId, elements, opts) {
111362
+ await withCardKitRetry(
111363
+ "createElements",
111364
+ () => this.channel().rawClient.cardkit.v1.cardElement.create({
111365
+ path: { card_id: cardId },
111366
+ data: {
111367
+ type: opts.type ?? "append",
111368
+ target_element_id: opts.targetElementId,
111369
+ elements: JSON.stringify(elements),
111370
+ sequence: opts.sequence,
111371
+ uuid: opts.uuid
111372
+ }
111373
+ }),
111374
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111375
+ );
111376
+ }
111377
+ async deleteElement(cardId, elementId, opts) {
111378
+ await withCardKitRetry(
111379
+ "deleteElement",
111380
+ () => this.channel().rawClient.cardkit.v1.cardElement.delete({
111381
+ path: { card_id: cardId, element_id: elementId },
111382
+ data: { sequence: opts.sequence, uuid: opts.uuid }
111383
+ }),
111384
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111385
+ );
111386
+ }
111387
+ async patchElement(cardId, elementId, partialElement, opts) {
111388
+ await withCardKitRetry(
111389
+ "patchElement",
111390
+ () => this.channel().rawClient.cardkit.v1.cardElement.patch({
111391
+ path: { card_id: cardId, element_id: elementId },
111392
+ data: {
111393
+ partial_element: JSON.stringify(partialElement),
111394
+ sequence: opts.sequence,
111395
+ uuid: opts.uuid
111396
+ }
111397
+ }),
111398
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111399
+ );
111400
+ }
111401
+ async updateElement(cardId, elementId, element, opts) {
111402
+ await withCardKitRetry(
111403
+ "updateElement",
111404
+ () => this.channel().rawClient.cardkit.v1.cardElement.update({
111405
+ path: { card_id: cardId, element_id: elementId },
111406
+ data: {
111407
+ element: JSON.stringify(element),
111408
+ sequence: opts.sequence,
111409
+ uuid: opts.uuid
111410
+ }
111411
+ }),
111412
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111413
+ );
111414
+ }
111415
+ async updateCardSettings(cardId, settings, opts) {
111416
+ await withCardKitRetry(
111417
+ "updateCardSettings",
111418
+ () => this.channel().rawClient.cardkit.v1.card.settings({
111419
+ path: { card_id: cardId },
111420
+ data: {
111421
+ settings: JSON.stringify(settings),
111422
+ sequence: opts.sequence,
111423
+ uuid: opts.uuid
111424
+ }
111425
+ }),
111426
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111427
+ );
111428
+ }
111429
+ };
111430
+ }
111431
+ });
111432
+
111185
111433
  // src/lark/outboundPostClient.ts
111186
111434
  function numericStatus(err2) {
111187
111435
  if (!err2 || typeof err2 !== "object") return void 0;
@@ -111442,6 +111690,7 @@ var init_channelClient = __esm({
111442
111690
  import_node_sdk = __toESM(require_lib2(), 1);
111443
111691
  init_transport();
111444
111692
  init_channelCardClient();
111693
+ init_channelCardKitClient();
111445
111694
  init_channelPostClient();
111446
111695
  execFile4 = promisify4(execFileCallback);
111447
111696
  LEARNED_CHATS_LIMIT = 100;
@@ -111552,6 +111801,8 @@ var init_channelClient = __esm({
111552
111801
  cardThreads = /* @__PURE__ */ new Map();
111553
111802
  /** Lazily built (after connect) so it can bind the live channel handle. */
111554
111803
  cardClient = null;
111804
+ /** Lazily built CardKit client; shares cardThreads with interactive callbacks. */
111805
+ cardKitClient = null;
111555
111806
  /** Lazily built and only requested by main.ts when post outbound gates are configured. */
111556
111807
  postClient = null;
111557
111808
  constructor(opts) {
@@ -111754,6 +112005,22 @@ var init_channelClient = __esm({
111754
112005
  }
111755
112006
  return this.cardClient;
111756
112007
  }
112008
+ /**
112009
+ * Return an OutboundCardKitClient bound to this client's channel handle.
112010
+ *
112011
+ * CardKit messages share the same messageId -> threadId map as legacy cards,
112012
+ * so final-area choice buttons synthesize normal turns in the originating
112013
+ * Feishu topic.
112014
+ */
112015
+ outboundCardKitClient() {
112016
+ if (!this.cardKitClient) {
112017
+ this.cardKitClient = new ChannelCardKitClient({
112018
+ resolveChannel: () => this.channel,
112019
+ cardThreads: this.cardThreads
112020
+ });
112021
+ }
112022
+ return this.cardKitClient;
112023
+ }
111757
112024
  /**
111758
112025
  * Return an OutboundPostClient bound to this client's channel handle.
111759
112026
  *
@@ -123697,6 +123964,7 @@ function defaultResponseSurfacePrototypeConfig() {
123697
123964
  lazy_card_creation: true,
123698
123965
  kill_switch: false,
123699
123966
  post_outbound_enabled: true,
123967
+ cardkit_streaming_enabled: true,
123700
123968
  allow_agent_mentions: true,
123701
123969
  allowed_mention_open_ids: [],
123702
123970
  max_posts_per_turn: 1,
@@ -123714,6 +123982,7 @@ var responseSurfacePrototypeConfigDefaults = () => ({
123714
123982
  lazy_card_creation: true,
123715
123983
  kill_switch: false,
123716
123984
  post_outbound_enabled: true,
123985
+ cardkit_streaming_enabled: true,
123717
123986
  allow_agent_mentions: true,
123718
123987
  allowed_mention_open_ids: [],
123719
123988
  max_posts_per_turn: 1,
@@ -123794,6 +124063,13 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
123794
124063
  * an injected post client and all safety gates before any post path.
123795
124064
  */
123796
124065
  post_outbound_enabled: external_exports.boolean().default(true),
124066
+ /**
124067
+ * CardKit streaming response surface gate. Defaults on: normal turns use a
124068
+ * streaming CardKit card during execution and finalize that same card into
124069
+ * a clean answer + interaction surface. Disabling this gate rolls back to
124070
+ * the legacy visible card path, not post in-place editing.
124071
+ */
124072
+ cardkit_streaming_enabled: external_exports.boolean().default(true),
123797
124073
  /**
123798
124074
  * Allows Agent-authored post mentions. This powers handoff to peer bots.
123799
124075
  * Keep this false only when the operator wants to suppress every real @.