larkway 0.3.23 → 0.3.24
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/main.js +145 -20
- 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.24**
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
package/README.zh.md
CHANGED
package/dist/main.js
CHANGED
|
@@ -117647,6 +117647,7 @@ function deriveTriggerFacts(parsed, isNewThread, larkCliProfile) {
|
|
|
117647
117647
|
// src/agent/answerChannel.ts
|
|
117648
117648
|
var ANSWER_BEGIN_MARKER = "LARKWAY_ANSWER_BEGIN";
|
|
117649
117649
|
var ANSWER_END_MARKER = "LARKWAY_ANSWER_END";
|
|
117650
|
+
var STREAM_HOLD_CHARS = ANSWER_END_MARKER.length + 2;
|
|
117650
117651
|
function stripLeadingNewline(text) {
|
|
117651
117652
|
return text.replace(/^\r?\n/, "");
|
|
117652
117653
|
}
|
|
@@ -117662,6 +117663,9 @@ function markerLineIndex(text, marker) {
|
|
|
117662
117663
|
const after = lineEnd + (match[2]?.length ?? 0);
|
|
117663
117664
|
return { start: lineStart, end: after };
|
|
117664
117665
|
}
|
|
117666
|
+
function hasUsefulText(text) {
|
|
117667
|
+
return text.length > 0;
|
|
117668
|
+
}
|
|
117665
117669
|
function splitAnswerChannelText(text, raw) {
|
|
117666
117670
|
const begin = markerLineIndex(text, ANSWER_BEGIN_MARKER);
|
|
117667
117671
|
if (!begin) return [{ type: "internal_text", text, raw }];
|
|
@@ -117676,6 +117680,71 @@ function splitAnswerChannelText(text, raw) {
|
|
|
117676
117680
|
if (trailing.trim()) events.push({ type: "internal_text", text: trailing, raw });
|
|
117677
117681
|
return events;
|
|
117678
117682
|
}
|
|
117683
|
+
var AnswerChannelExtractor = class {
|
|
117684
|
+
mode = "waiting";
|
|
117685
|
+
buffer = "";
|
|
117686
|
+
visibleText = "";
|
|
117687
|
+
ingestDelta(text, raw) {
|
|
117688
|
+
if (!text || this.mode === "closed") return [];
|
|
117689
|
+
this.buffer += text;
|
|
117690
|
+
return this.drain(raw);
|
|
117691
|
+
}
|
|
117692
|
+
ingestSnapshot(text, raw) {
|
|
117693
|
+
const events = splitAnswerChannelText(text, raw);
|
|
117694
|
+
const out = [];
|
|
117695
|
+
for (const event of events) {
|
|
117696
|
+
if (event.type !== "answer_snapshot") {
|
|
117697
|
+
out.push(event);
|
|
117698
|
+
continue;
|
|
117699
|
+
}
|
|
117700
|
+
if (event.text === this.visibleText) continue;
|
|
117701
|
+
this.visibleText = event.text;
|
|
117702
|
+
out.push(event);
|
|
117703
|
+
if (text.includes(ANSWER_END_MARKER)) this.mode = "closed";
|
|
117704
|
+
}
|
|
117705
|
+
return out;
|
|
117706
|
+
}
|
|
117707
|
+
drain(raw) {
|
|
117708
|
+
const events = [];
|
|
117709
|
+
if (this.mode === "waiting") {
|
|
117710
|
+
const begin = markerLineIndex(this.buffer, ANSWER_BEGIN_MARKER);
|
|
117711
|
+
if (!begin) {
|
|
117712
|
+
this.trimWaitingBuffer();
|
|
117713
|
+
return events;
|
|
117714
|
+
}
|
|
117715
|
+
const before = stripTrailingNewline(this.buffer.slice(0, begin.start));
|
|
117716
|
+
if (before.trim()) events.push({ type: "internal_text", text: before, raw });
|
|
117717
|
+
this.buffer = this.buffer.slice(begin.end);
|
|
117718
|
+
this.mode = "answer";
|
|
117719
|
+
}
|
|
117720
|
+
if (this.mode !== "answer") return events;
|
|
117721
|
+
const end = markerLineIndex(this.buffer, ANSWER_END_MARKER);
|
|
117722
|
+
if (end) {
|
|
117723
|
+
const answerTail = stripTrailingNewline(this.buffer.slice(0, end.start));
|
|
117724
|
+
if (hasUsefulText(answerTail)) events.push(this.answerDelta(answerTail, raw));
|
|
117725
|
+
const trailing = stripLeadingNewline(this.buffer.slice(end.end));
|
|
117726
|
+
if (trailing.trim()) events.push({ type: "internal_text", text: trailing, raw });
|
|
117727
|
+
this.buffer = "";
|
|
117728
|
+
this.mode = "closed";
|
|
117729
|
+
return events;
|
|
117730
|
+
}
|
|
117731
|
+
if (this.buffer.length <= STREAM_HOLD_CHARS) return events;
|
|
117732
|
+
const emitText = this.buffer.slice(0, this.buffer.length - STREAM_HOLD_CHARS);
|
|
117733
|
+
this.buffer = this.buffer.slice(this.buffer.length - STREAM_HOLD_CHARS);
|
|
117734
|
+
if (hasUsefulText(emitText)) events.push(this.answerDelta(emitText, raw));
|
|
117735
|
+
return events;
|
|
117736
|
+
}
|
|
117737
|
+
answerDelta(text, raw) {
|
|
117738
|
+
this.visibleText += text;
|
|
117739
|
+
return { type: "answer_delta", text, raw };
|
|
117740
|
+
}
|
|
117741
|
+
trimWaitingBuffer() {
|
|
117742
|
+
const max = ANSWER_BEGIN_MARKER.length + 2;
|
|
117743
|
+
if (this.buffer.length > max) {
|
|
117744
|
+
this.buffer = this.buffer.slice(this.buffer.length - max);
|
|
117745
|
+
}
|
|
117746
|
+
}
|
|
117747
|
+
};
|
|
117679
117748
|
|
|
117680
117749
|
// src/claude/prompt.ts
|
|
117681
117750
|
var MEMORY_CATEGORY_FILE_NAMES = [
|
|
@@ -117719,8 +117788,7 @@ function renderStateContract(stateFilePath) {
|
|
|
117719
117788
|
"- status: in_progress / ready / failed(bridge \u552F\u4E00\u5F3A\u4F9D\u8D56\u7684\u5B57\u6BB5)",
|
|
117720
117789
|
"- last_message: \u7ED9\u8FD0\u8425\u770B\u7684\u5361\u7247\u6B63\u6587\u3002\u4F60\u81EA\u884C\u7EC4\u7EC7\u5C55\u793A\u5185\u5BB9,\u4F8B\u5982\u8FDB\u5EA6\u3001\u7ED3\u8BBA\u3001\u8BC1\u636E\u3001MR \u94FE\u63A5\u3001dev \u9884\u89C8\u3001\u9700\u8981\u7528\u6237\u8865\u5145\u7684\u4FE1\u606F",
|
|
117721
117790
|
"- error: \u5931\u8D25\u539F\u56E0(\u642D\u914D status=failed)",
|
|
117722
|
-
"- card_title: \
|
|
117723
|
-
"- card_color: \u5361\u7247\u914D\u8272(\u53EF\u9009,success/failure/neutral,\u4E5F\u53EF\u76F4\u63A5\u5199 green/red/grey)",
|
|
117791
|
+
"- card_title/card_color: \u517C\u5BB9\u5B57\u6BB5; \u9ED8\u8BA4 CardKit \u4E0D\u6E32\u67D3\u9876\u90E8\u6807\u9898\u8272\u6761,legacy/fallback \u5361\u7247\u8DEF\u5F84\u53EF\u80FD\u4F7F\u7528",
|
|
117724
117792
|
"- image_blocks: \u53EF\u9009\u56FE\u7247\u9884\u89C8\u5757\u6570\u7EC4,\u6700\u591A 4 \u4E2A\u3002\u6BCF\u9879 `{img_key, alt?, title?, mode?, preview?}`; `img_key` \u5FC5\u987B\u662F\u5DF2\u4E0A\u4F20/\u53EF\u7528\u4E8E\u5361\u7247\u7684 Feishu \u56FE\u7247 key,`alt` \u7701\u7565\u65F6 bridge \u9ED8\u8BA4\u201C\u56FE\u7247\u9884\u89C8\u201D,`mode` \u53EA\u5141\u8BB8 `crop_center`/`fit_horizontal` \u5E76\u6620\u5C04\u5230 Card JSON 2.0 `scale_type`,`preview` \u9ED8\u8BA4 true\u3002bridge \u4E0D\u8D1F\u8D23\u4E0B\u8F7D/\u4E0A\u4F20/\u9009\u62E9\u56FE\u7247;\u8FD9\u4E9B\u7531\u4F60\u7528 lark-cli \u7B49\u5DE5\u5177\u5148\u5B8C\u6210\u3002",
|
|
117725
117793
|
'- content_blocks: \u53EF\u9009\u6709\u5E8F\u6B63\u6587\u5757\u6570\u7EC4,\u6700\u591A 12 \u4E2A block\u3001\u6700\u591A 4 \u4E2A image block\u3002\u53EA\u652F\u6301\u7A84 union:`{type:"markdown", content}` \u548C `{type:"image", img_key, alt?, title?, mode?, preview?}`;\u4E0D\u652F\u6301 raw card JSON\u3002\u7528\u4E8E\u6B63\u6587\u4E0E\u56FE\u7247\u4EA4\u9519\u6392\u7248,\u4F8B\u5982 markdown -> image -> markdown -> image\u3002\u82E5 `content_blocks` \u975E\u7A7A,bridge \u4EE5\u5B83\u4F5C\u4E3A\u4E3B\u6B63\u6587\u5E76\u5FFD\u7565 `last_message` + `image_blocks` \u7684\u6B63\u6587\u6E32\u67D3,\u907F\u514D\u91CD\u590D;\u82E5\u7701\u7565\u5219\u4FDD\u6301\u65E7 `last_message` + `image_blocks` \u884C\u4E3A\u3002',
|
|
117726
117794
|
'- response_surface: \u53EF\u9009\u8986\u76D6\u5B57\u6BB5,\u5F62\u5982 `{mode:"card"|"post"|"hybrid", primary?:"card"|"post", post?:{mentions:[{user_id,label?}]}, card?:{compact?:boolean, capabilities?:[...]}}`\u3002\u9ED8\u8BA4\u53EF\u4E0D\u5199;\u65E0\u663E\u5F0F card \u610F\u56FE\u65F6 bridge \u6309 CardKit \u6D41\u5F0F\u5361\u7247\u5904\u7406,\u6700\u7EC8\u6536\u6210\u5E72\u51C0\u603B\u7ED3\u5361\u3002\u9700\u8981\u771F\u5B9E @ \u65F6\u628A\u76EE\u6807\u5199\u5165 `post.mentions`;\u4E0D\u8981\u5199 raw Feishu post/card JSON\u3002',
|
|
@@ -119136,6 +119204,9 @@ function markdown(content, elementId) {
|
|
|
119136
119204
|
if (elementId) element["element_id"] = elementId;
|
|
119137
119205
|
return element;
|
|
119138
119206
|
}
|
|
119207
|
+
function buildCardKitAnswerElement(content = "") {
|
|
119208
|
+
return markdown(content, CARDKIT_FINAL_ELEMENT_ID);
|
|
119209
|
+
}
|
|
119139
119210
|
function safeAtMention(target) {
|
|
119140
119211
|
const id = target.user_id.trim();
|
|
119141
119212
|
if (!/^[A-Za-z0-9_:-]+$/.test(id)) return "";
|
|
@@ -119187,10 +119258,7 @@ function buildCardKitInitialCard(opts = {}) {
|
|
|
119187
119258
|
}
|
|
119188
119259
|
},
|
|
119189
119260
|
body: {
|
|
119190
|
-
elements: [
|
|
119191
|
-
markdown(" ", CARDKIT_FINAL_ELEMENT_ID),
|
|
119192
|
-
markdown(footerText, CARDKIT_FOOTER_ELEMENT_ID)
|
|
119193
|
-
]
|
|
119261
|
+
elements: [markdown(footerText, CARDKIT_FOOTER_ELEMENT_ID)]
|
|
119194
119262
|
}
|
|
119195
119263
|
};
|
|
119196
119264
|
}
|
|
@@ -119237,7 +119305,7 @@ function finalMarkdown(opts) {
|
|
|
119237
119305
|
return truncateChars([mentionLine, body].filter(Boolean).join("\n\n") || "\u5B8C\u6210\u3002", FINAL_BUDGET_CHARS, "\n\n_\u5185\u5BB9\u8FC7\u957F,\u5DF2\u622A\u65AD\u3002_");
|
|
119238
119306
|
}
|
|
119239
119307
|
function buildCardKitFinalCard(opts) {
|
|
119240
|
-
const elements = [
|
|
119308
|
+
const elements = [buildCardKitAnswerElement(finalMarkdown(opts))];
|
|
119241
119309
|
const images = [];
|
|
119242
119310
|
if (opts.contentBlocks?.length) {
|
|
119243
119311
|
for (const block of opts.contentBlocks) {
|
|
@@ -119263,10 +119331,6 @@ function buildCardKitFinalCard(opts) {
|
|
|
119263
119331
|
streaming_mode: false,
|
|
119264
119332
|
summary: { content: truncateChars(opts.finalText.replace(/\s+/g, " ").trim(), 50, "...") }
|
|
119265
119333
|
},
|
|
119266
|
-
header: {
|
|
119267
|
-
title: plainText2(opts.title ?? "\u5B8C\u6210"),
|
|
119268
|
-
template: "green"
|
|
119269
|
-
},
|
|
119270
119334
|
body: { elements }
|
|
119271
119335
|
});
|
|
119272
119336
|
}
|
|
@@ -119285,6 +119349,11 @@ function idempotencyKey(facts) {
|
|
|
119285
119349
|
function sequenceUuid(cardId, role, sequence) {
|
|
119286
119350
|
return deriveCardKitUuid([cardId, role, String(sequence)].join("\0"));
|
|
119287
119351
|
}
|
|
119352
|
+
function isMissingCardKitElementError(err) {
|
|
119353
|
+
if (!(err instanceof Error)) return false;
|
|
119354
|
+
const message = err.message.toLowerCase();
|
|
119355
|
+
return message.includes("element") && (message.includes("not found") || message.includes("not exist") || message.includes("\u4E0D\u5B58\u5728"));
|
|
119356
|
+
}
|
|
119288
119357
|
var LiveCardKitProgressHandle = class {
|
|
119289
119358
|
cardId;
|
|
119290
119359
|
messageId;
|
|
@@ -119298,6 +119367,7 @@ var LiveCardKitProgressHandle = class {
|
|
|
119298
119367
|
inFlight = Promise.resolve();
|
|
119299
119368
|
closed = false;
|
|
119300
119369
|
progressUpdates = 0;
|
|
119370
|
+
answerElementCreated = false;
|
|
119301
119371
|
sequence = 0;
|
|
119302
119372
|
constructor(opts) {
|
|
119303
119373
|
this.cardKitClient = opts.cardKitClient;
|
|
@@ -119336,6 +119406,7 @@ var LiveCardKitProgressHandle = class {
|
|
|
119336
119406
|
this.closed = true;
|
|
119337
119407
|
const finalMarkdown2 = buildCardKitFinalMarkdown(opts);
|
|
119338
119408
|
if (finalMarkdown2 !== this.answerBuffer) {
|
|
119409
|
+
await this.withAnswerElement(finalMarkdown2);
|
|
119339
119410
|
await this.next(
|
|
119340
119411
|
(sequence) => this.cardKitClient.streamElementContent(
|
|
119341
119412
|
this.cardId,
|
|
@@ -119391,17 +119462,35 @@ var LiveCardKitProgressHandle = class {
|
|
|
119391
119462
|
if (!this.answerBuffer) return;
|
|
119392
119463
|
this.progressUpdates += 1;
|
|
119393
119464
|
this.inFlight = this.inFlight.then(
|
|
119394
|
-
() => this.
|
|
119395
|
-
(
|
|
119396
|
-
sequence,
|
|
119397
|
-
|
|
119398
|
-
|
|
119465
|
+
() => this.withAnswerElement(this.answerBuffer).then(
|
|
119466
|
+
() => this.next(
|
|
119467
|
+
(sequence) => this.cardKitClient.streamElementContent(this.cardId, CARDKIT_FINAL_ELEMENT_ID, this.answerBuffer, {
|
|
119468
|
+
sequence,
|
|
119469
|
+
uuid: sequenceUuid(this.cardId, "answer", sequence)
|
|
119470
|
+
})
|
|
119471
|
+
)
|
|
119399
119472
|
)
|
|
119400
119473
|
).catch((err) => {
|
|
119401
119474
|
console.warn("[cardkit_progress] progress update failed (continuing):", err);
|
|
119402
119475
|
});
|
|
119403
119476
|
await this.inFlight;
|
|
119404
119477
|
}
|
|
119478
|
+
async withAnswerElement(initialContent) {
|
|
119479
|
+
if (this.answerElementCreated) return;
|
|
119480
|
+
await this.next(
|
|
119481
|
+
(sequence) => this.cardKitClient.createElements(
|
|
119482
|
+
this.cardId,
|
|
119483
|
+
[buildCardKitAnswerElement(initialContent)],
|
|
119484
|
+
{
|
|
119485
|
+
sequence,
|
|
119486
|
+
uuid: sequenceUuid(this.cardId, "answer-element", sequence),
|
|
119487
|
+
type: "insert_before",
|
|
119488
|
+
targetElementId: CARDKIT_FOOTER_ELEMENT_ID
|
|
119489
|
+
}
|
|
119490
|
+
)
|
|
119491
|
+
);
|
|
119492
|
+
this.answerElementCreated = true;
|
|
119493
|
+
}
|
|
119405
119494
|
async next(fn) {
|
|
119406
119495
|
this.sequence += 1;
|
|
119407
119496
|
await fn(this.sequence);
|
|
@@ -119451,7 +119540,7 @@ async function finalizeExistingCardKitCard(opts) {
|
|
|
119451
119540
|
await opts.onSequenceCommitted?.(sequence);
|
|
119452
119541
|
};
|
|
119453
119542
|
const finalMarkdown2 = buildCardKitFinalMarkdown(opts.final);
|
|
119454
|
-
|
|
119543
|
+
const streamFinalContent = () => next(
|
|
119455
119544
|
"reconcile-final-content",
|
|
119456
119545
|
(seq2, uuid) => opts.cardKitClient.streamElementContent(
|
|
119457
119546
|
opts.cardId,
|
|
@@ -119460,6 +119549,25 @@ async function finalizeExistingCardKitCard(opts) {
|
|
|
119460
119549
|
{ sequence: seq2, uuid }
|
|
119461
119550
|
)
|
|
119462
119551
|
);
|
|
119552
|
+
try {
|
|
119553
|
+
await streamFinalContent();
|
|
119554
|
+
} catch (err) {
|
|
119555
|
+
if (!isMissingCardKitElementError(err)) throw err;
|
|
119556
|
+
await next(
|
|
119557
|
+
"reconcile-final-element",
|
|
119558
|
+
(seq2, uuid) => opts.cardKitClient.createElements(
|
|
119559
|
+
opts.cardId,
|
|
119560
|
+
[buildCardKitAnswerElement(finalMarkdown2)],
|
|
119561
|
+
{
|
|
119562
|
+
sequence: seq2,
|
|
119563
|
+
uuid,
|
|
119564
|
+
type: "insert_before",
|
|
119565
|
+
targetElementId: CARDKIT_FOOTER_ELEMENT_ID
|
|
119566
|
+
}
|
|
119567
|
+
)
|
|
119568
|
+
);
|
|
119569
|
+
await streamFinalContent();
|
|
119570
|
+
}
|
|
119463
119571
|
await next(
|
|
119464
119572
|
"reconcile-final-card",
|
|
119465
119573
|
(seq2, uuid) => opts.cardKitClient.updateCardEntity(
|
|
@@ -125439,7 +125547,7 @@ function buildCommand(opts) {
|
|
|
125439
125547
|
args.push("-p", opts.prompt);
|
|
125440
125548
|
return [bin, args];
|
|
125441
125549
|
}
|
|
125442
|
-
function* parseLinesMulti(line) {
|
|
125550
|
+
function* parseLinesMulti(line, answerExtractor = new AnswerChannelExtractor()) {
|
|
125443
125551
|
const trimmed = line.trim();
|
|
125444
125552
|
if (trimmed === "") return;
|
|
125445
125553
|
let obj;
|
|
@@ -125473,7 +125581,7 @@ function* parseLinesMulti(line) {
|
|
|
125473
125581
|
if (typeof item !== "object" || item === null) continue;
|
|
125474
125582
|
const block = item;
|
|
125475
125583
|
if (block["type"] === "text" && typeof block["text"] === "string") {
|
|
125476
|
-
yield*
|
|
125584
|
+
yield* answerExtractor.ingestSnapshot(block["text"], obj);
|
|
125477
125585
|
emitted = true;
|
|
125478
125586
|
} else if (block["type"] === "tool_use") {
|
|
125479
125587
|
yield {
|
|
@@ -125491,6 +125599,22 @@ function* parseLinesMulti(line) {
|
|
|
125491
125599
|
yield { type: "raw", raw: obj };
|
|
125492
125600
|
return;
|
|
125493
125601
|
}
|
|
125602
|
+
if (eventType === "stream_event") {
|
|
125603
|
+
const event = record["event"];
|
|
125604
|
+
if (typeof event === "object" && event !== null) {
|
|
125605
|
+
const streamEvent = event;
|
|
125606
|
+
const delta = streamEvent["delta"];
|
|
125607
|
+
if (typeof delta === "object" && delta !== null) {
|
|
125608
|
+
const deltaRecord = delta;
|
|
125609
|
+
if (streamEvent["type"] === "content_block_delta" && deltaRecord["type"] === "text_delta" && typeof deltaRecord["text"] === "string") {
|
|
125610
|
+
yield* answerExtractor.ingestDelta(deltaRecord["text"], obj);
|
|
125611
|
+
return;
|
|
125612
|
+
}
|
|
125613
|
+
}
|
|
125614
|
+
}
|
|
125615
|
+
yield { type: "raw", raw: obj };
|
|
125616
|
+
return;
|
|
125617
|
+
}
|
|
125494
125618
|
if (eventType === "user") {
|
|
125495
125619
|
const message = record["message"];
|
|
125496
125620
|
if (typeof message === "object" && message !== null && Array.isArray(message["content"])) {
|
|
@@ -125664,9 +125788,10 @@ stderr: ${stderr}` : "")
|
|
|
125664
125788
|
// unblock the events loop so handler.ts can reach card.finalize().
|
|
125665
125789
|
signal: rlAbortController.signal
|
|
125666
125790
|
});
|
|
125791
|
+
const answerExtractor = new AnswerChannelExtractor();
|
|
125667
125792
|
try {
|
|
125668
125793
|
for await (const line of rl) {
|
|
125669
|
-
for (const event of parseLinesMulti(line)) {
|
|
125794
|
+
for (const event of parseLinesMulti(line, answerExtractor)) {
|
|
125670
125795
|
if (event.type === "system_init") {
|
|
125671
125796
|
discoveredSessionId = event.sessionId;
|
|
125672
125797
|
}
|