larkway 0.3.25 → 0.3.27

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.25**
11
+ **Current release: v0.3.26**
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.25**
11
+ **当前版本:v0.3.26**
12
12
 
13
13
  ---
14
14
 
package/dist/cli/index.js CHANGED
@@ -124047,9 +124047,9 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
124047
124047
  */
124048
124048
  allowed_threads: external_exports.array(external_exports.string().min(1)).default([]),
124049
124049
  /**
124050
- * Post-first gate. Default true means the bridge starts a lightweight post
124051
- * as the live main surface when post outbound is available, and creates a
124052
- * card only for fallback or card-only capabilities.
124050
+ * Historical post-first gate retained for config compatibility. The default
124051
+ * runtime no longer uses post-first lazy card creation; CardKit is the main
124052
+ * surface and legacy cards remain the visible fallback.
124053
124053
  */
124054
124054
  lazy_card_creation: external_exports.boolean().default(true),
124055
124055
  /**
@@ -124059,8 +124059,8 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
124059
124059
  */
124060
124060
  kill_switch: external_exports.boolean().default(false),
124061
124061
  /**
124062
- * Gate for real post outbound. Defaults on, but the runtime still requires
124063
- * an injected post client and all safety gates before any post path.
124062
+ * Gate for real post outbound. Defaults on so create-only visible fallback
124063
+ * posts remain available when CardKit and legacy card rendering both fail.
124064
124064
  */
124065
124065
  post_outbound_enabled: external_exports.boolean().default(true),
124066
124066
  /**
@@ -124082,8 +124082,8 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
124082
124082
  */
124083
124083
  allowed_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([]),
124084
124084
  /**
124085
- * Hard cap for surface dispatch. PR4 still keeps production post outbound
124086
- * unavailable, but fake-channel dispatch tests enforce this bounded shape.
124085
+ * Historical post dispatch cap retained for schema compatibility and
124086
+ * isolated dispatcher tests.
124087
124087
  */
124088
124088
  max_posts_per_turn: external_exports.number().int().min(0).max(10).default(1),
124089
124089
  /**
@@ -124102,8 +124102,8 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
124102
124102
  */
124103
124103
  max_post_attempts: external_exports.number().int().min(1).max(5).default(3),
124104
124104
  /**
124105
- * Future channel threshold for lazy card creation. Bounded so config cannot
124106
- * grow unbounded or encode business rules.
124105
+ * Historical threshold for removed lazy card creation experiments. Bounded
124106
+ * so older config remains parseable without growing arbitrary business rules.
124107
124107
  */
124108
124108
  text_threshold_chars: external_exports.number().int().min(1).max(2e4).default(1200)
124109
124109
  }).default(responseSurfacePrototypeConfigDefaults);
package/dist/main.js CHANGED
@@ -117684,12 +117684,14 @@ var AnswerChannelExtractor = class {
117684
117684
  mode = "waiting";
117685
117685
  buffer = "";
117686
117686
  visibleText = "";
117687
+ lastSnapshotText = "";
117687
117688
  ingestDelta(text, raw) {
117688
117689
  if (!text || this.mode === "closed") return [];
117689
117690
  this.buffer += text;
117690
117691
  return this.drain(raw);
117691
117692
  }
117692
117693
  ingestSnapshot(text, raw) {
117694
+ this.lastSnapshotText = text;
117693
117695
  const events = splitAnswerChannelText(text, raw);
117694
117696
  const out = [];
117695
117697
  for (const event of events) {
@@ -117697,13 +117699,30 @@ var AnswerChannelExtractor = class {
117697
117699
  out.push(event);
117698
117700
  continue;
117699
117701
  }
117700
- if (event.text === this.visibleText) continue;
117702
+ if (event.text === this.visibleText) {
117703
+ if (text.includes(ANSWER_END_MARKER)) this.mode = "closed";
117704
+ continue;
117705
+ }
117701
117706
  this.visibleText = event.text;
117702
117707
  out.push(event);
117703
117708
  if (text.includes(ANSWER_END_MARKER)) this.mode = "closed";
117704
117709
  }
117705
117710
  return out;
117706
117711
  }
117712
+ ingestGrowingSnapshot(text, raw) {
117713
+ if (!text || this.mode === "closed") return [];
117714
+ if (this.lastSnapshotText && text.startsWith(this.lastSnapshotText)) {
117715
+ const delta = text.slice(this.lastSnapshotText.length);
117716
+ this.lastSnapshotText = text;
117717
+ return delta ? this.ingestDelta(delta, raw) : [];
117718
+ }
117719
+ if (this.lastSnapshotText === "") {
117720
+ this.lastSnapshotText = text;
117721
+ return this.ingestDelta(text, raw);
117722
+ }
117723
+ this.lastSnapshotText = text;
117724
+ return this.ingestSnapshot(text, raw);
117725
+ }
117707
117726
  drain(raw) {
117708
117727
  const events = [];
117709
117728
  if (this.mode === "waiting") {
@@ -118689,9 +118708,9 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
118689
118708
  */
118690
118709
  allowed_threads: external_exports.array(external_exports.string().min(1)).default([]),
118691
118710
  /**
118692
- * Post-first gate. Default true means the bridge starts a lightweight post
118693
- * as the live main surface when post outbound is available, and creates a
118694
- * card only for fallback or card-only capabilities.
118711
+ * Historical post-first gate retained for config compatibility. The default
118712
+ * runtime no longer uses post-first lazy card creation; CardKit is the main
118713
+ * surface and legacy cards remain the visible fallback.
118695
118714
  */
118696
118715
  lazy_card_creation: external_exports.boolean().default(true),
118697
118716
  /**
@@ -118701,8 +118720,8 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
118701
118720
  */
118702
118721
  kill_switch: external_exports.boolean().default(false),
118703
118722
  /**
118704
- * Gate for real post outbound. Defaults on, but the runtime still requires
118705
- * an injected post client and all safety gates before any post path.
118723
+ * Gate for real post outbound. Defaults on so create-only visible fallback
118724
+ * posts remain available when CardKit and legacy card rendering both fail.
118706
118725
  */
118707
118726
  post_outbound_enabled: external_exports.boolean().default(true),
118708
118727
  /**
@@ -118724,8 +118743,8 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
118724
118743
  */
118725
118744
  allowed_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([]),
118726
118745
  /**
118727
- * Hard cap for surface dispatch. PR4 still keeps production post outbound
118728
- * unavailable, but fake-channel dispatch tests enforce this bounded shape.
118746
+ * Historical post dispatch cap retained for schema compatibility and
118747
+ * isolated dispatcher tests.
118729
118748
  */
118730
118749
  max_posts_per_turn: external_exports.number().int().min(0).max(10).default(1),
118731
118750
  /**
@@ -118744,8 +118763,8 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
118744
118763
  */
118745
118764
  max_post_attempts: external_exports.number().int().min(1).max(5).default(3),
118746
118765
  /**
118747
- * Future channel threshold for lazy card creation. Bounded so config cannot
118748
- * grow unbounded or encode business rules.
118766
+ * Historical threshold for removed lazy card creation experiments. Bounded
118767
+ * so older config remains parseable without growing arbitrary business rules.
118749
118768
  */
118750
118769
  text_threshold_chars: external_exports.number().int().min(1).max(2e4).default(1200)
118751
118770
  }).default(responseSurfacePrototypeConfigDefaults);
@@ -118774,9 +118793,6 @@ function shouldProvideResponseSurfaceCardKitClient(config) {
118774
118793
  function isResponseSurfaceCardKitAvailable(config, facts, opts) {
118775
118794
  return !!(opts.cardKitClientAvailable && shouldProvideResponseSurfaceCardKitClient(config) && isResponseSurfacePrototypeAllowlisted(config, facts));
118776
118795
  }
118777
- function isResponseSurfacePostOutboundAvailable(config, facts, opts) {
118778
- return !!(opts.postClientAvailable && shouldProvideResponseSurfacePostClient(config) && isResponseSurfacePrototypeAllowlisted(config, facts));
118779
- }
118780
118796
 
118781
118797
  // src/bridge/stateFile.ts
118782
118798
  var optionalUrl = external_exports.preprocess(
@@ -119736,93 +119752,6 @@ async function finalizeExistingCardKitCard(opts) {
119736
119752
  return sequence;
119737
119753
  }
119738
119754
 
119739
- // src/bridge/surfaceController.ts
119740
- var SurfaceController = class _SurfaceController {
119741
- decision;
119742
- constructor(decision) {
119743
- this.decision = decision;
119744
- }
119745
- static create(input) {
119746
- const cfg = input.prototypeConfig;
119747
- if (!cfg?.enabled) {
119748
- return new _SurfaceController({
119749
- startCardImmediately: true,
119750
- prototypeEnabled: false,
119751
- lazyCardCreationEnabled: false,
119752
- reason: "prototype-disabled"
119753
- });
119754
- }
119755
- if (cfg.kill_switch) {
119756
- return new _SurfaceController({
119757
- startCardImmediately: true,
119758
- prototypeEnabled: false,
119759
- lazyCardCreationEnabled: false,
119760
- reason: "kill-switch-active"
119761
- });
119762
- }
119763
- if (!isResponseSurfacePrototypeAllowlisted(cfg, {
119764
- chatId: input.chatId,
119765
- threadId: input.threadId
119766
- })) {
119767
- return new _SurfaceController({
119768
- startCardImmediately: true,
119769
- prototypeEnabled: false,
119770
- lazyCardCreationEnabled: false,
119771
- reason: "not-allowlisted"
119772
- });
119773
- }
119774
- if (!cfg.lazy_card_creation) {
119775
- return new _SurfaceController({
119776
- startCardImmediately: true,
119777
- prototypeEnabled: true,
119778
- lazyCardCreationEnabled: false,
119779
- reason: "lazy-card-disabled"
119780
- });
119781
- }
119782
- if (!cfg.post_outbound_enabled) {
119783
- return new _SurfaceController({
119784
- startCardImmediately: true,
119785
- prototypeEnabled: true,
119786
- lazyCardCreationEnabled: false,
119787
- reason: "post-outbound-disabled"
119788
- });
119789
- }
119790
- if (!input.postOutboundAvailable) {
119791
- return new _SurfaceController({
119792
- startCardImmediately: true,
119793
- prototypeEnabled: true,
119794
- lazyCardCreationEnabled: false,
119795
- reason: "post-outbound-unavailable-card-fallback"
119796
- });
119797
- }
119798
- if (input.postLedgerAvailable === false) {
119799
- return new _SurfaceController({
119800
- startCardImmediately: true,
119801
- prototypeEnabled: true,
119802
- lazyCardCreationEnabled: false,
119803
- reason: "post-ledger-unavailable-card-fallback"
119804
- });
119805
- }
119806
- if (input.visibleFallbackAvailable === false) {
119807
- return new _SurfaceController({
119808
- startCardImmediately: true,
119809
- prototypeEnabled: true,
119810
- lazyCardCreationEnabled: false,
119811
- reason: "visible-fallback-unavailable-card-fallback"
119812
- });
119813
- }
119814
- return new _SurfaceController({
119815
- startCardImmediately: false,
119816
- prototypeEnabled: true,
119817
- lazyCardCreationEnabled: true,
119818
- reason: "lazy-card-ready"
119819
- });
119820
- }
119821
- shouldStartCardImmediately() {
119822
- return this.decision.startCardImmediately;
119823
- }
119824
- };
119825
-
119826
119755
  // src/lark/postContent.ts
119827
119756
  function assertSafeUserId(userId) {
119828
119757
  if (!/^[A-Za-z0-9_:-]+$/.test(userId)) {
@@ -120590,162 +120519,6 @@ async function dispatchResponseSurface(input) {
120590
120519
  return result;
120591
120520
  }
120592
120521
 
120593
- // src/bridge/postProgress.ts
120594
- var DEFAULT_PLACEHOLDER_TEXT = "\u52AA\u529B\u56DE\u7B54\u4E2D...";
120595
- var DEFAULT_PATCH_INTERVAL_MS2 = 1500;
120596
- var DEFAULT_MAX_PROGRESS_EDITS = 16;
120597
- function normalizeText2(text) {
120598
- return text.replace(/\r\n/g, "\n").trim();
120599
- }
120600
- function progressText(raw, fallback) {
120601
- const text = normalizeText2(raw);
120602
- if (!text) return fallback;
120603
- return text.length > 3500 ? `${text.slice(0, 3500)}
120604
-
120605
- _\u5904\u7406\u4E2D\u5185\u5BB9\u8F83\u957F\uFF0C\u7EC8\u7A3F\u4F1A\u6536\u655B\u3002_` : text;
120606
- }
120607
- var LivePostProgressHandle = class {
120608
- messageId;
120609
- idempotencyKey;
120610
- role = "primary";
120611
- postClient;
120612
- patchIntervalMs;
120613
- maxProgressEdits;
120614
- initialText;
120615
- textBuffer = "";
120616
- lastPatchedText = "";
120617
- progressEdits = 0;
120618
- pendingPatch = null;
120619
- inFlight = Promise.resolve();
120620
- closed = false;
120621
- constructor(opts) {
120622
- this.postClient = opts.postClient;
120623
- this.messageId = opts.messageId;
120624
- this.idempotencyKey = opts.idempotencyKey;
120625
- this.initialText = opts.initialText;
120626
- this.lastPatchedText = opts.initialText;
120627
- this.patchIntervalMs = opts.patchIntervalMs;
120628
- this.maxProgressEdits = opts.maxProgressEdits;
120629
- }
120630
- handle(event) {
120631
- if (this.closed) return;
120632
- if (event.type === "answer_delta") {
120633
- this.textBuffer += event.text;
120634
- } else if (event.type === "answer_snapshot") {
120635
- this.textBuffer = event.text;
120636
- } else {
120637
- return;
120638
- }
120639
- this.schedulePatch();
120640
- }
120641
- async finalize(opts) {
120642
- await this.drain();
120643
- const finalText = normalizeText2(opts.text) || this.initialText;
120644
- await this.update(finalText, opts.mentions);
120645
- }
120646
- async drain() {
120647
- this.closed = true;
120648
- if (this.pendingPatch) {
120649
- clearTimeout(this.pendingPatch);
120650
- this.pendingPatch = null;
120651
- }
120652
- await this.inFlight;
120653
- }
120654
- close() {
120655
- this.closed = true;
120656
- if (this.pendingPatch) {
120657
- clearTimeout(this.pendingPatch);
120658
- this.pendingPatch = null;
120659
- }
120660
- }
120661
- schedulePatch() {
120662
- if (this.pendingPatch || this.progressEdits >= this.maxProgressEdits) return;
120663
- this.pendingPatch = setTimeout(() => {
120664
- this.pendingPatch = null;
120665
- void this.patchProgress();
120666
- }, this.patchIntervalMs);
120667
- this.pendingPatch.unref?.();
120668
- }
120669
- async patchProgress() {
120670
- if (this.closed || this.progressEdits >= this.maxProgressEdits) return;
120671
- const nextText = progressText(this.textBuffer, this.initialText);
120672
- if (nextText === this.lastPatchedText) return;
120673
- this.progressEdits += 1;
120674
- this.inFlight = this.inFlight.then(() => this.update(nextText)).catch((err) => {
120675
- console.warn("[post_progress] progress update failed (continuing):", err);
120676
- });
120677
- await this.inFlight;
120678
- }
120679
- async update(text, mentions = []) {
120680
- const content = buildPostContent({ text, mentions });
120681
- await this.postClient.updatePost(this.messageId, content);
120682
- this.lastPatchedText = text;
120683
- }
120684
- };
120685
- async function createPostProgressHandle(opts) {
120686
- const role = "primary";
120687
- const initialText = normalizeText2(opts.initialText ?? DEFAULT_PLACEHOLDER_TEXT);
120688
- const idempotencyKey2 = derivePostIdempotencyKey({
120689
- botId: opts.facts.botId,
120690
- threadId: opts.facts.threadId,
120691
- triggerMessageId: opts.facts.triggerMessageId,
120692
- role,
120693
- logicalIndex: 0,
120694
- contentDigest: digestPostContent("live-progress-placeholder")
120695
- });
120696
- const sent = await opts.postClient.createPostReply(
120697
- opts.replyToMessageId,
120698
- buildPostContent({ text: initialText }),
120699
- {
120700
- replyInThread: opts.replyInThread,
120701
- idempotencyKey: idempotencyKey2
120702
- }
120703
- );
120704
- return new LivePostProgressHandle({
120705
- postClient: opts.postClient,
120706
- messageId: sent.messageId,
120707
- idempotencyKey: idempotencyKey2,
120708
- initialText,
120709
- patchIntervalMs: opts.patchIntervalMs ?? DEFAULT_PATCH_INTERVAL_MS2,
120710
- maxProgressEdits: opts.maxProgressEdits ?? DEFAULT_MAX_PROGRESS_EDITS
120711
- });
120712
- }
120713
-
120714
- // src/bridge/postBudget.ts
120715
- function scopeKey(scope) {
120716
- return `${scope.botId}\0${scope.chatId}\0${scope.threadId}`;
120717
- }
120718
- var ResponseSurfacePostBudget = class {
120719
- buckets = /* @__PURE__ */ new Map();
120720
- reserve(input) {
120721
- const nowMs = input.nowMs ?? Date.now();
120722
- const limit = Math.max(0, Math.floor(input.maxPosts));
120723
- const windowMs = Math.max(1, Math.floor(input.windowMs));
120724
- const key = scopeKey(input.scope);
120725
- const cutoff = nowMs - windowMs;
120726
- const existing = (this.buckets.get(key) ?? []).filter((ts) => ts > cutoff);
120727
- if (limit < 1 || existing.length >= limit) {
120728
- this.buckets.set(key, existing);
120729
- return {
120730
- allowed: false,
120731
- used: existing.length,
120732
- limit,
120733
- windowMs,
120734
- resetAt: existing.length > 0 ? new Date(Math.min(...existing) + windowMs).toISOString() : void 0,
120735
- reason: "post-window-exhausted"
120736
- };
120737
- }
120738
- const next = [...existing, nowMs];
120739
- this.buckets.set(key, next);
120740
- return {
120741
- allowed: true,
120742
- used: next.length,
120743
- limit,
120744
- windowMs
120745
- };
120746
- }
120747
- };
120748
-
120749
120522
  // src/bridge/handler.ts
120750
120523
  var DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS = 20 * 60 * 1e3;
120751
120524
  function execGit(cwd, args) {
@@ -121002,7 +120775,6 @@ async function createOnlyPostFallback(opts) {
121002
120775
  }
121003
120776
  var BridgeHandler = class {
121004
120777
  deps;
121005
- responseSurfacePostBudget = new ResponseSurfacePostBudget();
121006
120778
  closed = false;
121007
120779
  constructor(deps) {
121008
120780
  this.deps = deps;
@@ -121115,31 +120887,19 @@ var BridgeHandler = class {
121115
120887
  const isTopLevel = !(typeof parsed.raw.root_id === "string" && parsed.raw.root_id);
121116
120888
  const replyInThread = isTopLevel;
121117
120889
  const prototypeConfig = this.deps.botConfig?.response_surface_prototype;
121118
- const postOutboundAvailable = isResponseSurfacePostOutboundAvailable(
121119
- prototypeConfig,
121120
- { chatId: parsed.chatId, threadId },
121121
- { postClientAvailable: !!this.deps.postClient }
121122
- );
121123
120890
  const cardKitAvailable = isResponseSurfaceCardKitAvailable(
121124
120891
  prototypeConfig,
121125
120892
  { chatId: parsed.chatId, threadId },
121126
120893
  { cardKitClientAvailable: !!this.deps.cardKitClient }
121127
120894
  );
121128
- const surfaceController = SurfaceController.create({
121129
- prototypeConfig,
121130
- chatId: parsed.chatId,
121131
- threadId,
121132
- postOutboundAvailable: false,
121133
- postLedgerAvailable: true,
121134
- visibleFallbackAvailable: true
121135
- });
121136
120895
  let card;
121137
120896
  let cardKitProgress;
121138
120897
  let cardKitRecord;
121139
120898
  let cardKitStartFailed = false;
121140
- let progressPost;
121141
- let progressPostStartFailed = false;
121142
- if (!cardKitAvailable && surfaceController.shouldStartCardImmediately()) {
120899
+ let legacyCardStartFailed = false;
120900
+ let legacyCardStartFailureReason;
120901
+ let startFailurePostFallbackSent = false;
120902
+ if (!cardKitAvailable) {
121143
120903
  try {
121144
120904
  card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121145
120905
  await recordEvent({
@@ -121149,6 +120909,8 @@ var BridgeHandler = class {
121149
120909
  reason: "\u5DF2\u4EA4\u7ED9\u672C\u5730 Agent \u5904\u7406\u3002"
121150
120910
  });
121151
120911
  } catch (err) {
120912
+ legacyCardStartFailed = true;
120913
+ legacyCardStartFailureReason = String(err);
121152
120914
  console.error("[bridge.handler] Failed to start card for thread", threadId, err);
121153
120915
  await recordEvent({
121154
120916
  status: "running",
@@ -121164,7 +120926,7 @@ var BridgeHandler = class {
121164
120926
  status: "running",
121165
120927
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
121166
120928
  appendPath: "\u5EF6\u8FDF\u521B\u5EFA\u5361\u7247",
121167
- reason: "response_surface prototype lazy card creation is enabled."
120929
+ reason: "CardKit response surface is available; legacy card is reserved for fallback."
121168
120930
  });
121169
120931
  }
121170
120932
  try {
@@ -121386,72 +121148,34 @@ var BridgeHandler = class {
121386
121148
  }
121387
121149
  }
121388
121150
  }
121389
- if (!card && !cardKitAvailable && postOutboundAvailable && this.deps.postClient) {
121390
- const budget = prototypeConfig ? this.responseSurfacePostBudget.reserve({
121391
- scope: {
121392
- botId: this.deps.botConfig?.id ?? "v1-default",
121393
- chatId: parsed.chatId,
121394
- threadId
121395
- },
121396
- maxPosts: prototypeConfig.max_posts_per_window,
121397
- windowMs: prototypeConfig.post_window_ms
121398
- }) : void 0;
121399
- if (budget?.allowed === false) {
121400
- console.warn("[bridge.handler] post progress budget exhausted; using card fallback");
121401
- progressPostStartFailed = true;
121402
- } else {
121403
- try {
121404
- progressPost = await createPostProgressHandle({
121405
- postClient: this.deps.postClient,
121406
- replyToMessageId: messageId,
121407
- replyInThread,
121408
- facts: {
121409
- botId: this.deps.botConfig?.id ?? "v1-default",
121410
- threadId,
121411
- triggerMessageId: messageId
121412
- },
121413
- initialText: "\u52AA\u529B\u56DE\u7B54\u4E2D..."
121414
- });
121415
- await this.deps.client.removeProcessingReaction?.(messageId);
121416
- await recordEvent({
121417
- status: "running",
121418
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
121419
- appendPath: "\u5DF2\u521B\u5EFA post",
121420
- reason: "response surface \u4F7F\u7528 post \u4F5C\u4E3A\u672C\u8F6E\u4E3B\u56DE\u590D\u9762\u3002"
121421
- });
121422
- } catch (err) {
121423
- progressPostStartFailed = true;
121424
- console.warn("[bridge.handler] create progress post failed; using card fallback:", err);
121425
- }
121426
- }
121427
- }
121428
- if (!card && (progressPostStartFailed || cardKitStartFailed)) {
121151
+ if (!card && cardKitStartFailed) {
121429
121152
  try {
121430
121153
  card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121431
121154
  await this.deps.client.removeProcessingReaction?.(messageId);
121432
121155
  await recordEvent({
121433
121156
  status: "running",
121434
121157
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
121435
- appendPath: cardKitStartFailed ? "CardKit \u5931\u8D25\uFF0C\u5DF2\u521B\u5EFA\u5361\u7247" : "post \u5931\u8D25\uFF0C\u5DF2\u521B\u5EFA\u5361\u7247",
121436
- reason: cardKitStartFailed ? "CardKit \u4E3B\u9762\u521B\u5EFA\u5931\u8D25\uFF0Cbridge \u4F7F\u7528\u53EF\u89C1\u5361\u7247\u515C\u5E95\u3002" : "post \u4E3B\u9762\u521B\u5EFA\u5931\u8D25\uFF0Cbridge \u4F7F\u7528\u53EF\u89C1\u5361\u7247\u515C\u5E95\u3002"
121158
+ appendPath: "CardKit \u5931\u8D25\uFF0C\u5DF2\u521B\u5EFA\u5361\u7247",
121159
+ reason: "CardKit \u4E3B\u9762\u521B\u5EFA\u5931\u8D25\uFF0Cbridge \u4F7F\u7528\u53EF\u89C1\u5361\u7247\u515C\u5E95\u3002"
121437
121160
  });
121438
121161
  } catch (err) {
121439
121162
  console.error(
121440
121163
  "[bridge.handler] visible card fallback start failed after primary surface start failure:",
121441
121164
  err
121442
121165
  );
121443
- await createOnlyPostFallback({
121166
+ const postFallback = await createOnlyPostFallback({
121444
121167
  postClient: this.deps.postClient,
121445
121168
  replyToMessageId: messageId,
121446
121169
  replyInThread,
121447
121170
  botId: this.deps.botConfig?.id ?? "v1-default",
121448
121171
  threadId,
121449
121172
  triggerMessageId: messageId,
121450
- finalText: cardKitStartFailed ? "CardKit \u4E3B\u56DE\u590D\u9762\u521B\u5EFA\u5931\u8D25, legacy \u53EF\u89C1\u5361\u7247\u515C\u5E95\u4E5F\u521B\u5EFA\u5931\u8D25\u3002" : "post \u4E3B\u56DE\u590D\u9762\u521B\u5EFA\u5931\u8D25, legacy \u53EF\u89C1\u5361\u7247\u515C\u5E95\u4E5F\u521B\u5EFA\u5931\u8D25\u3002",
121173
+ finalText: "CardKit \u4E3B\u56DE\u590D\u9762\u521B\u5EFA\u5931\u8D25, legacy \u53EF\u89C1\u5361\u7247\u515C\u5E95\u4E5F\u521B\u5EFA\u5931\u8D25\u3002",
121451
121174
  failureReason: String(err),
121452
121175
  title: "Larkway fallback",
121453
121176
  logPrefix: "[bridge.handler]"
121454
121177
  });
121178
+ if (postFallback) startFailurePostFallbackSent = true;
121455
121179
  await this.deps.client.removeProcessingReaction?.(messageId);
121456
121180
  await recordEvent({
121457
121181
  status: "running",
@@ -121541,7 +121265,6 @@ var BridgeHandler = class {
121541
121265
  for await (const ev of handle.events) {
121542
121266
  if (cardKitProgress) cardKitProgress.handle(ev);
121543
121267
  else if (card) card.handle(ev);
121544
- else progressPost?.handle(ev);
121545
121268
  if (ev.type === "system_init") {
121546
121269
  sessionId = ev.sessionId;
121547
121270
  }
@@ -121688,7 +121411,6 @@ var BridgeHandler = class {
121688
121411
  }
121689
121412
  }
121690
121413
  } else {
121691
- await progressPost?.drain();
121692
121414
  const surfaceDispatch = await dispatchResponseSurface({
121693
121415
  state: reportedState,
121694
121416
  prototypeConfig,
@@ -121706,80 +121428,90 @@ var BridgeHandler = class {
121706
121428
  postOutboundAvailable: false,
121707
121429
  postLedgerAvailable: true,
121708
121430
  visibleFallbackAvailable: true,
121709
- postClient: this.deps.postClient,
121710
- livePost: progressPost ? {
121711
- messageId: progressPost.messageId,
121712
- idempotencyKey: progressPost.idempotencyKey,
121713
- role: progressPost.role
121714
- } : void 0,
121715
- postBudget: prototypeConfig ? {
121716
- reserve: () => this.responseSurfacePostBudget.reserve({
121717
- scope: {
121718
- botId: this.deps.botConfig?.id ?? "v1-default",
121719
- chatId: parsed.chatId,
121720
- threadId
121721
- },
121722
- maxPosts: prototypeConfig.max_posts_per_window,
121723
- windowMs: prototypeConfig.post_window_ms
121724
- })
121725
- } : void 0
121431
+ postClient: this.deps.postClient
121726
121432
  });
121727
121433
  if (surfaceDispatch.card) {
121728
121434
  if (!card) {
121729
- card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121730
121435
  try {
121731
- await writeCardFile(worktreePath, {
121732
- messageId: card.messageId,
121733
- chatId: parsed.chatId,
121734
- threadId,
121735
- botId: this.deps.botConfig?.id ?? "",
121736
- replyInThread,
121737
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
121738
- });
121739
- } catch (err) {
121740
- console.warn("[bridge.handler] writeCardFile(late) failed (continuing):", err);
121741
- }
121742
- }
121743
- await card.finalize(surfaceDispatch.card);
121744
- let keepCardFileForRetry = false;
121745
- if (surfaceDispatch.post?.requiresFallbackLedgerMark) {
121746
- try {
121747
- await markPostLedgerFallbackVisible(
121748
- worktreePath,
121749
- surfaceDispatch.post.idempotencyKey,
121750
- {
121751
- fallbackCardMessageId: card.messageId,
121752
- error: surfaceDispatch.post.fallbackError ?? surfaceDispatch.card.failureReason ?? "post outbound failed; visible card fallback used"
121753
- }
121754
- );
121436
+ card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121437
+ try {
121438
+ await writeCardFile(worktreePath, {
121439
+ messageId: card.messageId,
121440
+ chatId: parsed.chatId,
121441
+ threadId,
121442
+ botId: this.deps.botConfig?.id ?? "",
121443
+ replyInThread,
121444
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
121445
+ });
121446
+ } catch (err) {
121447
+ console.warn("[bridge.handler] writeCardFile(late) failed (continuing):", err);
121448
+ }
121755
121449
  } catch (err) {
121756
- keepCardFileForRetry = true;
121757
- console.warn(
121758
- "[bridge.handler] fallback ledger mark failed after visible card finalize; keeping card.json for retry:",
121450
+ console.error(
121451
+ "[bridge.handler] late visible card fallback start failed; creating post fallback:",
121759
121452
  err
121760
121453
  );
121454
+ const failureReason2 = [
121455
+ legacyCardStartFailed ? `initial legacy visible card start failed: ${legacyCardStartFailureReason ?? "unknown"}` : void 0,
121456
+ `late legacy visible card fallback start failed: ${String(err)}`
121457
+ ].filter((part) => !!part).join("; ");
121458
+ const postFallback = await createOnlyPostFallback({
121459
+ postClient: this.deps.postClient,
121460
+ replyToMessageId: messageId,
121461
+ replyInThread,
121462
+ botId: this.deps.botConfig?.id ?? "v1-default",
121463
+ threadId,
121464
+ triggerMessageId: messageId,
121465
+ finalText: surfaceDispatch.card.finalText ?? "Larkway completed but no visible card was available.",
121466
+ failureReason: failureReason2,
121467
+ title: surfaceDispatch.card.titleOverride ?? "Larkway fallback",
121468
+ logPrefix: "[bridge.handler]"
121469
+ });
121470
+ if (!postFallback) throw err;
121761
121471
  }
121762
121472
  }
121763
- if (surfaceDispatch.post?.requiresPolicyLedgerMark) {
121764
- try {
121765
- await markPostLedgerPolicyBlockedVisible(
121766
- worktreePath,
121767
- surfaceDispatch.post.idempotencyKey,
121768
- {
121769
- fallbackCardMessageId: card.messageId,
121770
- error: surfaceDispatch.post.policyError ?? surfaceDispatch.card.failureReason ?? "mention policy blocked; visible card fallback used"
121771
- }
121772
- );
121773
- } catch (err) {
121774
- keepCardFileForRetry = true;
121775
- console.warn(
121776
- "[bridge.handler] policy-blocked ledger mark failed after visible card finalize; keeping card.json for retry:",
121777
- err
121778
- );
121473
+ if (card) {
121474
+ await card.finalize(surfaceDispatch.card);
121475
+ let keepCardFileForRetry = false;
121476
+ if (surfaceDispatch.post?.requiresFallbackLedgerMark) {
121477
+ try {
121478
+ await markPostLedgerFallbackVisible(
121479
+ worktreePath,
121480
+ surfaceDispatch.post.idempotencyKey,
121481
+ {
121482
+ fallbackCardMessageId: card.messageId,
121483
+ error: surfaceDispatch.post.fallbackError ?? surfaceDispatch.card.failureReason ?? "post outbound failed; visible card fallback used"
121484
+ }
121485
+ );
121486
+ } catch (err) {
121487
+ keepCardFileForRetry = true;
121488
+ console.warn(
121489
+ "[bridge.handler] fallback ledger mark failed after visible card finalize; keeping card.json for retry:",
121490
+ err
121491
+ );
121492
+ }
121493
+ }
121494
+ if (surfaceDispatch.post?.requiresPolicyLedgerMark) {
121495
+ try {
121496
+ await markPostLedgerPolicyBlockedVisible(
121497
+ worktreePath,
121498
+ surfaceDispatch.post.idempotencyKey,
121499
+ {
121500
+ fallbackCardMessageId: card.messageId,
121501
+ error: surfaceDispatch.post.policyError ?? surfaceDispatch.card.failureReason ?? "mention policy blocked; visible card fallback used"
121502
+ }
121503
+ );
121504
+ } catch (err) {
121505
+ keepCardFileForRetry = true;
121506
+ console.warn(
121507
+ "[bridge.handler] policy-blocked ledger mark failed after visible card finalize; keeping card.json for retry:",
121508
+ err
121509
+ );
121510
+ }
121511
+ }
121512
+ if (!keepCardFileForRetry) {
121513
+ await deleteCardFile(worktreePath);
121779
121514
  }
121780
- }
121781
- if (!keepCardFileForRetry) {
121782
- await deleteCardFile(worktreePath);
121783
121515
  }
121784
121516
  }
121785
121517
  }
@@ -121858,25 +121590,10 @@ var BridgeHandler = class {
121858
121590
  }
121859
121591
  }
121860
121592
  }
121861
- if (!card && progressPost) {
121862
- try {
121863
- await progressPost.finalize({
121864
- text: `\u6267\u884C\u5931\u8D25: ${String(err)}`
121865
- });
121866
- } catch (postFinalizeErr) {
121867
- console.error(
121868
- "[bridge.handler] progress post failure update failed; creating card fallback:",
121869
- postFinalizeErr
121870
- );
121871
- try {
121872
- card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121873
- } catch (cardStartErr) {
121874
- console.error("[bridge.handler] failure card start also failed:", cardStartErr);
121875
- await createHardFailurePostFallback(
121876
- `progress post failure update failed: ${String(postFinalizeErr)}; legacy visible card fallback also failed: ${String(cardStartErr)}`
121877
- );
121878
- }
121879
- }
121593
+ if (!card && !cardKitProgress && !startFailurePostFallbackSent) {
121594
+ await createHardFailurePostFallback(
121595
+ legacyCardStartFailed ? `legacy visible card was unavailable before agent failure: ${legacyCardStartFailureReason ?? "unknown"}` : "no visible response surface was available before agent failure"
121596
+ );
121880
121597
  }
121881
121598
  if (card) {
121882
121599
  try {
@@ -125748,7 +125465,7 @@ function* parseLinesMulti(line, answerExtractor = new AnswerChannelExtractor())
125748
125465
  if (typeof item !== "object" || item === null) continue;
125749
125466
  const block = item;
125750
125467
  if (block["type"] === "text" && typeof block["text"] === "string") {
125751
- yield* answerExtractor.ingestSnapshot(block["text"], obj);
125468
+ yield* answerExtractor.ingestGrowingSnapshot(block["text"], obj);
125752
125469
  emitted = true;
125753
125470
  } else if (block["type"] === "tool_use") {
125754
125471
  yield {
@@ -126026,111 +125743,104 @@ function buildCodexEnv(botGitIdentity, gitlabToken) {
126026
125743
  }
126027
125744
  return env;
126028
125745
  }
126029
- function sandboxFlag(mode) {
126030
- switch (mode) {
126031
- case "bypassPermissions":
126032
- return ["--dangerously-bypass-approvals-and-sandbox"];
126033
- case "acceptEdits":
126034
- return ["--dangerously-bypass-approvals-and-sandbox"];
126035
- case "ask":
126036
- return ["--sandbox", "read-only"];
126037
- }
125746
+ function buildCodexCommand(opts, codexBinPath = "codex") {
125747
+ void opts;
125748
+ return [codexBinPath, ["app-server", "--stdio"]];
126038
125749
  }
126039
- function resumePermissionFlag(mode) {
126040
- return mode === "bypassPermissions" || mode === "acceptEdits" ? ["--dangerously-bypass-approvals-and-sandbox"] : [];
125750
+ function codexThreadSandboxMode(mode) {
125751
+ return mode === "ask" ? "read-only" : "danger-full-access";
126041
125752
  }
126042
- function buildCodexCommand(opts, codexBinPath = "codex") {
126043
- const mode = opts.permissionMode ?? "acceptEdits";
126044
- const commonFlags = [
126045
- "--json",
126046
- "--skip-git-repo-check",
126047
- ...sandboxFlag(mode)
126048
- ];
126049
- if (opts.resumeSessionId != null) {
126050
- const resumeFlags = [
126051
- "--json",
126052
- "--skip-git-repo-check",
126053
- ...resumePermissionFlag(mode)
126054
- ];
126055
- return [
126056
- codexBinPath,
126057
- ["exec", "resume", opts.resumeSessionId, ...resumeFlags, "-"]
126058
- ];
126059
- }
126060
- const freshFlags = opts.cwd != null ? ["-C", opts.cwd, ...commonFlags] : commonFlags;
126061
- return [codexBinPath, ["exec", ...freshFlags]];
125753
+ function codexTurnSandboxPolicy(mode) {
125754
+ if (mode === "ask") return { type: "readOnly", networkAccess: false };
125755
+ return { type: "dangerFullAccess" };
126062
125756
  }
126063
- function* parseCodexLine(line) {
126064
- const trimmed = line.trim();
126065
- if (trimmed === "") return;
126066
- let obj;
126067
- try {
126068
- obj = JSON.parse(trimmed);
126069
- } catch {
126070
- yield { type: "raw", raw: trimmed };
126071
- return;
126072
- }
126073
- if (typeof obj !== "object" || obj === null) {
126074
- yield { type: "raw", raw: obj };
126075
- return;
126076
- }
126077
- const record = obj;
126078
- const topType = record["type"];
126079
- if (topType === "thread.started" && typeof record["thread_id"] === "string") {
126080
- yield { type: "system_init", sessionId: record["thread_id"], raw: obj };
126081
- return;
126082
- }
126083
- if (topType === "turn.completed") {
126084
- yield { type: "result", stopReason: "end_turn", raw: obj };
126085
- return;
126086
- }
126087
- if (topType === "item.started") {
126088
- const item = record["item"];
126089
- if (typeof item === "object" && item !== null) {
126090
- const itemRecord = item;
126091
- if (itemRecord["type"] === "command_execution" && typeof itemRecord["command"] === "string") {
125757
+ function codexApprovalPolicy(mode) {
125758
+ return mode === "ask" ? "on-request" : "never";
125759
+ }
125760
+ var CodexAppServerLineParser = class {
125761
+ answerExtractor = new AnswerChannelExtractor();
125762
+ *parseMessage(obj) {
125763
+ if (typeof obj !== "object" || obj === null) {
125764
+ yield { type: "raw", raw: obj };
125765
+ return;
125766
+ }
125767
+ const record = obj;
125768
+ const method = record["method"];
125769
+ if (typeof method !== "string") {
125770
+ yield { type: "raw", raw: obj };
125771
+ return;
125772
+ }
125773
+ const params = asRecord(record["params"]);
125774
+ if (method === "thread/started") {
125775
+ const thread = asRecord(params?.["thread"]);
125776
+ const threadId = typeof thread?.["id"] === "string" ? thread["id"] : void 0;
125777
+ if (threadId) yield { type: "system_init", sessionId: threadId, raw: obj };
125778
+ return;
125779
+ }
125780
+ if (method === "turn/completed") {
125781
+ yield { type: "result", stopReason: "end_turn", raw: obj };
125782
+ return;
125783
+ }
125784
+ if (method === "item/agentMessage/delta") {
125785
+ const delta = typeof params?.["delta"] === "string" ? params["delta"] : "";
125786
+ yield* this.answerExtractor.ingestDelta(delta, obj);
125787
+ return;
125788
+ }
125789
+ if (method === "item/started") {
125790
+ const item = asRecord(params?.["item"]);
125791
+ if (item?.["type"] === "commandExecution" && typeof item["command"] === "string") {
126092
125792
  yield {
126093
125793
  type: "tool_use",
126094
125794
  toolName: "shell",
126095
- toolInput: { command: itemRecord["command"] },
125795
+ toolInput: { command: item["command"] },
126096
125796
  raw: obj
126097
125797
  };
126098
125798
  return;
126099
125799
  }
125800
+ return;
126100
125801
  }
126101
- yield { type: "raw", raw: obj };
126102
- return;
126103
- }
126104
- if (topType === "item.completed") {
126105
- const item = record["item"];
126106
- if (typeof item === "object" && item !== null) {
126107
- const itemRecord = item;
126108
- if (itemRecord["type"] === "command_execution") {
125802
+ if (method === "item/completed") {
125803
+ const item = asRecord(params?.["item"]);
125804
+ if (item?.["type"] === "commandExecution") {
126109
125805
  yield { type: "tool_result", raw: obj };
126110
125806
  return;
126111
125807
  }
126112
- if (itemRecord["type"] === "agent_message" && typeof itemRecord["text"] === "string") {
126113
- yield* splitAnswerChannelText(itemRecord["text"], obj);
125808
+ if (item?.["type"] === "agentMessage" && typeof item["text"] === "string") {
125809
+ yield* this.answerExtractor.ingestSnapshot(item["text"], obj);
126114
125810
  return;
126115
125811
  }
125812
+ return;
126116
125813
  }
126117
125814
  yield { type: "raw", raw: obj };
126118
- return;
126119
125815
  }
126120
- yield { type: "raw", raw: obj };
125816
+ };
125817
+ function asRecord(value) {
125818
+ return typeof value === "object" && value !== null ? value : void 0;
125819
+ }
125820
+ function extractThreadIdFromThreadResponse(obj) {
125821
+ const record = asRecord(obj);
125822
+ const result = asRecord(record?.["result"]);
125823
+ const thread = asRecord(result?.["thread"]);
125824
+ const threadId = thread?.["id"];
125825
+ return typeof threadId === "string" ? threadId : void 0;
126121
125826
  }
126122
125827
  function runCodex(opts, codexBinPath = "codex") {
126123
125828
  const timeoutMs = opts.timeoutMs ?? 15 * 60 * 1e3;
126124
125829
  const [bin, args] = buildCodexCommand(opts, codexBinPath);
126125
125830
  const env = buildCodexEnv(opts.botGitIdentity, opts.gitlabToken);
125831
+ const mode = opts.permissionMode ?? "acceptEdits";
125832
+ const requestById = /* @__PURE__ */ new Map();
125833
+ let nextRequestId = 1;
126126
125834
  const child = spawn3(bin, args, {
126127
125835
  env,
126128
125836
  stdio: ["pipe", "pipe", "pipe"],
126129
125837
  ...opts.cwd != null ? { cwd: opts.cwd } : {}
126130
125838
  });
126131
- if (child.stdin != null) {
126132
- child.stdin.write(opts.prompt, "utf8");
126133
- child.stdin.end();
125839
+ function sendRequest(method, params) {
125840
+ const id = nextRequestId++;
125841
+ requestById.set(id, method);
125842
+ child.stdin?.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
125843
+ return id;
126134
125844
  }
126135
125845
  let discoveredSessionId;
126136
125846
  let killScheduled = false;
@@ -126144,6 +125854,10 @@ function runCodex(opts, codexBinPath = "codex") {
126144
125854
  }, SIGKILL_GRACE_MS2);
126145
125855
  killTimer.unref();
126146
125856
  }
125857
+ function stopAppServerAfterTurn() {
125858
+ if (child.killed || killScheduled) return;
125859
+ child.kill("SIGTERM");
125860
+ }
126147
125861
  const TOTAL_TIMEOUT_EXTRA_MS = SIGKILL_GRACE_MS2 + 2e3;
126148
125862
  let totalTimeoutFallbackHandle;
126149
125863
  let _forceFinalizeForTimeout = () => {
@@ -126166,6 +125880,8 @@ function runCodex(opts, codexBinPath = "codex") {
126166
125880
  const stderrChunks = [];
126167
125881
  child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
126168
125882
  const rlAbortController = new AbortController();
125883
+ let finishAppServerTurn;
125884
+ let failAppServerTurn;
126169
125885
  const done = new Promise(
126170
125886
  (resolve2, reject) => {
126171
125887
  let settled = false;
@@ -126195,6 +125911,17 @@ stderr: ${stderr}` : "")
126195
125911
  }
126196
125912
  resolve2({ exitCode, sessionId: discoveredSessionId });
126197
125913
  };
125914
+ const finalizeReject = (err) => {
125915
+ if (settled) return;
125916
+ settled = true;
125917
+ clearTimeout(timeoutHandle);
125918
+ clearTimeout(killTimer);
125919
+ clearTimeout(totalTimeoutFallbackHandle);
125920
+ rlAbortController.abort();
125921
+ reject(err);
125922
+ };
125923
+ finishAppServerTurn = finalizeResolve;
125924
+ failAppServerTurn = finalizeReject;
126198
125925
  _forceFinalizeForTimeout = () => {
126199
125926
  if (settled) return;
126200
125927
  console.warn(
@@ -126242,14 +125969,76 @@ stderr: ${stderr}` : "")
126242
125969
  crlfDelay: Infinity,
126243
125970
  signal: rlAbortController.signal
126244
125971
  });
125972
+ const parser = new CodexAppServerLineParser();
125973
+ let turnCompleted = false;
125974
+ sendRequest("initialize", {
125975
+ clientInfo: { name: "larkway", version: "0.3" },
125976
+ capabilities: {}
125977
+ });
126245
125978
  try {
126246
125979
  for await (const line of rl) {
126247
- for (const event of parseCodexLine(line)) {
125980
+ const trimmed = line.trim();
125981
+ if (!trimmed) continue;
125982
+ let obj;
125983
+ try {
125984
+ obj = JSON.parse(trimmed);
125985
+ } catch {
125986
+ yield { type: "raw", raw: trimmed };
125987
+ continue;
125988
+ }
125989
+ const response = asRecord(obj);
125990
+ if (typeof response?.["id"] === "number") {
125991
+ const id = response["id"];
125992
+ const method = requestById.get(id);
125993
+ requestById.delete(id);
125994
+ const error = asRecord(response["error"]);
125995
+ if (error) {
125996
+ const message = typeof error["message"] === "string" ? error["message"] : JSON.stringify(error);
125997
+ failAppServerTurn?.(new Error(`codex app-server ${method ?? "request"} failed: ${message}`));
125998
+ stopAppServerAfterTurn();
125999
+ return;
126000
+ }
126001
+ if (method === "initialize") {
126002
+ const threadMethod = opts.resumeSessionId != null ? "thread/resume" : "thread/start";
126003
+ const threadParams = opts.resumeSessionId != null ? { threadId: opts.resumeSessionId } : { ephemeral: false, sessionStartSource: "startup" };
126004
+ if (opts.cwd != null) threadParams["cwd"] = opts.cwd;
126005
+ threadParams["approvalPolicy"] = codexApprovalPolicy(mode);
126006
+ threadParams["sandbox"] = codexThreadSandboxMode(mode);
126007
+ sendRequest(threadMethod, threadParams);
126008
+ continue;
126009
+ }
126010
+ if (method === "thread/start" || method === "thread/resume") {
126011
+ const threadId = extractThreadIdFromThreadResponse(obj);
126012
+ if (threadId) {
126013
+ discoveredSessionId = threadId;
126014
+ yield { type: "system_init", sessionId: threadId, raw: obj };
126015
+ const turnParams = {
126016
+ threadId,
126017
+ input: [{ type: "text", text: opts.prompt, text_elements: [] }],
126018
+ approvalPolicy: codexApprovalPolicy(mode),
126019
+ sandboxPolicy: codexTurnSandboxPolicy(mode)
126020
+ };
126021
+ if (opts.cwd != null) turnParams["cwd"] = opts.cwd;
126022
+ sendRequest("turn/start", turnParams);
126023
+ }
126024
+ continue;
126025
+ }
126026
+ continue;
126027
+ }
126028
+ for (const event of parser.parseMessage(obj)) {
126248
126029
  if (event.type === "system_init") {
126249
126030
  discoveredSessionId = event.sessionId;
126250
126031
  }
126032
+ if (event.type === "result") {
126033
+ turnCompleted = true;
126034
+ }
126251
126035
  yield event;
126252
126036
  }
126037
+ if (turnCompleted) {
126038
+ stopAppServerAfterTurn();
126039
+ finishAppServerTurn?.(0);
126040
+ return;
126041
+ }
126253
126042
  }
126254
126043
  } catch (err) {
126255
126044
  const isAbort = err instanceof Error && (err.name === "AbortError" || err.code === "ABORT_ERR");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkway",
3
- "version": "0.3.25",
3
+ "version": "0.3.27",
4
4
  "description": "Thin bridge: Feishu thread to local Claude Code CLI",
5
5
  "license": "MIT",
6
6
  "author": "Chuck Wu (chuckwu0)",