larkway 0.3.26 → 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/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
@@ -118708,9 +118708,9 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
118708
118708
  */
118709
118709
  allowed_threads: external_exports.array(external_exports.string().min(1)).default([]),
118710
118710
  /**
118711
- * Post-first gate. Default true means the bridge starts a lightweight post
118712
- * as the live main surface when post outbound is available, and creates a
118713
- * 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.
118714
118714
  */
118715
118715
  lazy_card_creation: external_exports.boolean().default(true),
118716
118716
  /**
@@ -118720,8 +118720,8 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
118720
118720
  */
118721
118721
  kill_switch: external_exports.boolean().default(false),
118722
118722
  /**
118723
- * Gate for real post outbound. Defaults on, but the runtime still requires
118724
- * 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.
118725
118725
  */
118726
118726
  post_outbound_enabled: external_exports.boolean().default(true),
118727
118727
  /**
@@ -118743,8 +118743,8 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
118743
118743
  */
118744
118744
  allowed_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([]),
118745
118745
  /**
118746
- * Hard cap for surface dispatch. PR4 still keeps production post outbound
118747
- * unavailable, but fake-channel dispatch tests enforce this bounded shape.
118746
+ * Historical post dispatch cap retained for schema compatibility and
118747
+ * isolated dispatcher tests.
118748
118748
  */
118749
118749
  max_posts_per_turn: external_exports.number().int().min(0).max(10).default(1),
118750
118750
  /**
@@ -118763,8 +118763,8 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
118763
118763
  */
118764
118764
  max_post_attempts: external_exports.number().int().min(1).max(5).default(3),
118765
118765
  /**
118766
- * Future channel threshold for lazy card creation. Bounded so config cannot
118767
- * 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.
118768
118768
  */
118769
118769
  text_threshold_chars: external_exports.number().int().min(1).max(2e4).default(1200)
118770
118770
  }).default(responseSurfacePrototypeConfigDefaults);
@@ -118793,9 +118793,6 @@ function shouldProvideResponseSurfaceCardKitClient(config) {
118793
118793
  function isResponseSurfaceCardKitAvailable(config, facts, opts) {
118794
118794
  return !!(opts.cardKitClientAvailable && shouldProvideResponseSurfaceCardKitClient(config) && isResponseSurfacePrototypeAllowlisted(config, facts));
118795
118795
  }
118796
- function isResponseSurfacePostOutboundAvailable(config, facts, opts) {
118797
- return !!(opts.postClientAvailable && shouldProvideResponseSurfacePostClient(config) && isResponseSurfacePrototypeAllowlisted(config, facts));
118798
- }
118799
118796
 
118800
118797
  // src/bridge/stateFile.ts
118801
118798
  var optionalUrl = external_exports.preprocess(
@@ -119755,93 +119752,6 @@ async function finalizeExistingCardKitCard(opts) {
119755
119752
  return sequence;
119756
119753
  }
119757
119754
 
119758
- // src/bridge/surfaceController.ts
119759
- var SurfaceController = class _SurfaceController {
119760
- decision;
119761
- constructor(decision) {
119762
- this.decision = decision;
119763
- }
119764
- static create(input) {
119765
- const cfg = input.prototypeConfig;
119766
- if (!cfg?.enabled) {
119767
- return new _SurfaceController({
119768
- startCardImmediately: true,
119769
- prototypeEnabled: false,
119770
- lazyCardCreationEnabled: false,
119771
- reason: "prototype-disabled"
119772
- });
119773
- }
119774
- if (cfg.kill_switch) {
119775
- return new _SurfaceController({
119776
- startCardImmediately: true,
119777
- prototypeEnabled: false,
119778
- lazyCardCreationEnabled: false,
119779
- reason: "kill-switch-active"
119780
- });
119781
- }
119782
- if (!isResponseSurfacePrototypeAllowlisted(cfg, {
119783
- chatId: input.chatId,
119784
- threadId: input.threadId
119785
- })) {
119786
- return new _SurfaceController({
119787
- startCardImmediately: true,
119788
- prototypeEnabled: false,
119789
- lazyCardCreationEnabled: false,
119790
- reason: "not-allowlisted"
119791
- });
119792
- }
119793
- if (!cfg.lazy_card_creation) {
119794
- return new _SurfaceController({
119795
- startCardImmediately: true,
119796
- prototypeEnabled: true,
119797
- lazyCardCreationEnabled: false,
119798
- reason: "lazy-card-disabled"
119799
- });
119800
- }
119801
- if (!cfg.post_outbound_enabled) {
119802
- return new _SurfaceController({
119803
- startCardImmediately: true,
119804
- prototypeEnabled: true,
119805
- lazyCardCreationEnabled: false,
119806
- reason: "post-outbound-disabled"
119807
- });
119808
- }
119809
- if (!input.postOutboundAvailable) {
119810
- return new _SurfaceController({
119811
- startCardImmediately: true,
119812
- prototypeEnabled: true,
119813
- lazyCardCreationEnabled: false,
119814
- reason: "post-outbound-unavailable-card-fallback"
119815
- });
119816
- }
119817
- if (input.postLedgerAvailable === false) {
119818
- return new _SurfaceController({
119819
- startCardImmediately: true,
119820
- prototypeEnabled: true,
119821
- lazyCardCreationEnabled: false,
119822
- reason: "post-ledger-unavailable-card-fallback"
119823
- });
119824
- }
119825
- if (input.visibleFallbackAvailable === false) {
119826
- return new _SurfaceController({
119827
- startCardImmediately: true,
119828
- prototypeEnabled: true,
119829
- lazyCardCreationEnabled: false,
119830
- reason: "visible-fallback-unavailable-card-fallback"
119831
- });
119832
- }
119833
- return new _SurfaceController({
119834
- startCardImmediately: false,
119835
- prototypeEnabled: true,
119836
- lazyCardCreationEnabled: true,
119837
- reason: "lazy-card-ready"
119838
- });
119839
- }
119840
- shouldStartCardImmediately() {
119841
- return this.decision.startCardImmediately;
119842
- }
119843
- };
119844
-
119845
119755
  // src/lark/postContent.ts
119846
119756
  function assertSafeUserId(userId) {
119847
119757
  if (!/^[A-Za-z0-9_:-]+$/.test(userId)) {
@@ -120609,162 +120519,6 @@ async function dispatchResponseSurface(input) {
120609
120519
  return result;
120610
120520
  }
120611
120521
 
120612
- // src/bridge/postProgress.ts
120613
- var DEFAULT_PLACEHOLDER_TEXT = "\u52AA\u529B\u56DE\u7B54\u4E2D...";
120614
- var DEFAULT_PATCH_INTERVAL_MS2 = 1500;
120615
- var DEFAULT_MAX_PROGRESS_EDITS = 16;
120616
- function normalizeText2(text) {
120617
- return text.replace(/\r\n/g, "\n").trim();
120618
- }
120619
- function progressText(raw, fallback) {
120620
- const text = normalizeText2(raw);
120621
- if (!text) return fallback;
120622
- return text.length > 3500 ? `${text.slice(0, 3500)}
120623
-
120624
- _\u5904\u7406\u4E2D\u5185\u5BB9\u8F83\u957F\uFF0C\u7EC8\u7A3F\u4F1A\u6536\u655B\u3002_` : text;
120625
- }
120626
- var LivePostProgressHandle = class {
120627
- messageId;
120628
- idempotencyKey;
120629
- role = "primary";
120630
- postClient;
120631
- patchIntervalMs;
120632
- maxProgressEdits;
120633
- initialText;
120634
- textBuffer = "";
120635
- lastPatchedText = "";
120636
- progressEdits = 0;
120637
- pendingPatch = null;
120638
- inFlight = Promise.resolve();
120639
- closed = false;
120640
- constructor(opts) {
120641
- this.postClient = opts.postClient;
120642
- this.messageId = opts.messageId;
120643
- this.idempotencyKey = opts.idempotencyKey;
120644
- this.initialText = opts.initialText;
120645
- this.lastPatchedText = opts.initialText;
120646
- this.patchIntervalMs = opts.patchIntervalMs;
120647
- this.maxProgressEdits = opts.maxProgressEdits;
120648
- }
120649
- handle(event) {
120650
- if (this.closed) return;
120651
- if (event.type === "answer_delta") {
120652
- this.textBuffer += event.text;
120653
- } else if (event.type === "answer_snapshot") {
120654
- this.textBuffer = event.text;
120655
- } else {
120656
- return;
120657
- }
120658
- this.schedulePatch();
120659
- }
120660
- async finalize(opts) {
120661
- await this.drain();
120662
- const finalText = normalizeText2(opts.text) || this.initialText;
120663
- await this.update(finalText, opts.mentions);
120664
- }
120665
- async drain() {
120666
- this.closed = true;
120667
- if (this.pendingPatch) {
120668
- clearTimeout(this.pendingPatch);
120669
- this.pendingPatch = null;
120670
- }
120671
- await this.inFlight;
120672
- }
120673
- close() {
120674
- this.closed = true;
120675
- if (this.pendingPatch) {
120676
- clearTimeout(this.pendingPatch);
120677
- this.pendingPatch = null;
120678
- }
120679
- }
120680
- schedulePatch() {
120681
- if (this.pendingPatch || this.progressEdits >= this.maxProgressEdits) return;
120682
- this.pendingPatch = setTimeout(() => {
120683
- this.pendingPatch = null;
120684
- void this.patchProgress();
120685
- }, this.patchIntervalMs);
120686
- this.pendingPatch.unref?.();
120687
- }
120688
- async patchProgress() {
120689
- if (this.closed || this.progressEdits >= this.maxProgressEdits) return;
120690
- const nextText = progressText(this.textBuffer, this.initialText);
120691
- if (nextText === this.lastPatchedText) return;
120692
- this.progressEdits += 1;
120693
- this.inFlight = this.inFlight.then(() => this.update(nextText)).catch((err) => {
120694
- console.warn("[post_progress] progress update failed (continuing):", err);
120695
- });
120696
- await this.inFlight;
120697
- }
120698
- async update(text, mentions = []) {
120699
- const content = buildPostContent({ text, mentions });
120700
- await this.postClient.updatePost(this.messageId, content);
120701
- this.lastPatchedText = text;
120702
- }
120703
- };
120704
- async function createPostProgressHandle(opts) {
120705
- const role = "primary";
120706
- const initialText = normalizeText2(opts.initialText ?? DEFAULT_PLACEHOLDER_TEXT);
120707
- const idempotencyKey2 = derivePostIdempotencyKey({
120708
- botId: opts.facts.botId,
120709
- threadId: opts.facts.threadId,
120710
- triggerMessageId: opts.facts.triggerMessageId,
120711
- role,
120712
- logicalIndex: 0,
120713
- contentDigest: digestPostContent("live-progress-placeholder")
120714
- });
120715
- const sent = await opts.postClient.createPostReply(
120716
- opts.replyToMessageId,
120717
- buildPostContent({ text: initialText }),
120718
- {
120719
- replyInThread: opts.replyInThread,
120720
- idempotencyKey: idempotencyKey2
120721
- }
120722
- );
120723
- return new LivePostProgressHandle({
120724
- postClient: opts.postClient,
120725
- messageId: sent.messageId,
120726
- idempotencyKey: idempotencyKey2,
120727
- initialText,
120728
- patchIntervalMs: opts.patchIntervalMs ?? DEFAULT_PATCH_INTERVAL_MS2,
120729
- maxProgressEdits: opts.maxProgressEdits ?? DEFAULT_MAX_PROGRESS_EDITS
120730
- });
120731
- }
120732
-
120733
- // src/bridge/postBudget.ts
120734
- function scopeKey(scope) {
120735
- return `${scope.botId}\0${scope.chatId}\0${scope.threadId}`;
120736
- }
120737
- var ResponseSurfacePostBudget = class {
120738
- buckets = /* @__PURE__ */ new Map();
120739
- reserve(input) {
120740
- const nowMs = input.nowMs ?? Date.now();
120741
- const limit = Math.max(0, Math.floor(input.maxPosts));
120742
- const windowMs = Math.max(1, Math.floor(input.windowMs));
120743
- const key = scopeKey(input.scope);
120744
- const cutoff = nowMs - windowMs;
120745
- const existing = (this.buckets.get(key) ?? []).filter((ts) => ts > cutoff);
120746
- if (limit < 1 || existing.length >= limit) {
120747
- this.buckets.set(key, existing);
120748
- return {
120749
- allowed: false,
120750
- used: existing.length,
120751
- limit,
120752
- windowMs,
120753
- resetAt: existing.length > 0 ? new Date(Math.min(...existing) + windowMs).toISOString() : void 0,
120754
- reason: "post-window-exhausted"
120755
- };
120756
- }
120757
- const next = [...existing, nowMs];
120758
- this.buckets.set(key, next);
120759
- return {
120760
- allowed: true,
120761
- used: next.length,
120762
- limit,
120763
- windowMs
120764
- };
120765
- }
120766
- };
120767
-
120768
120522
  // src/bridge/handler.ts
120769
120523
  var DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS = 20 * 60 * 1e3;
120770
120524
  function execGit(cwd, args) {
@@ -121021,7 +120775,6 @@ async function createOnlyPostFallback(opts) {
121021
120775
  }
121022
120776
  var BridgeHandler = class {
121023
120777
  deps;
121024
- responseSurfacePostBudget = new ResponseSurfacePostBudget();
121025
120778
  closed = false;
121026
120779
  constructor(deps) {
121027
120780
  this.deps = deps;
@@ -121134,31 +120887,19 @@ var BridgeHandler = class {
121134
120887
  const isTopLevel = !(typeof parsed.raw.root_id === "string" && parsed.raw.root_id);
121135
120888
  const replyInThread = isTopLevel;
121136
120889
  const prototypeConfig = this.deps.botConfig?.response_surface_prototype;
121137
- const postOutboundAvailable = isResponseSurfacePostOutboundAvailable(
121138
- prototypeConfig,
121139
- { chatId: parsed.chatId, threadId },
121140
- { postClientAvailable: !!this.deps.postClient }
121141
- );
121142
120890
  const cardKitAvailable = isResponseSurfaceCardKitAvailable(
121143
120891
  prototypeConfig,
121144
120892
  { chatId: parsed.chatId, threadId },
121145
120893
  { cardKitClientAvailable: !!this.deps.cardKitClient }
121146
120894
  );
121147
- const surfaceController = SurfaceController.create({
121148
- prototypeConfig,
121149
- chatId: parsed.chatId,
121150
- threadId,
121151
- postOutboundAvailable: false,
121152
- postLedgerAvailable: true,
121153
- visibleFallbackAvailable: true
121154
- });
121155
120895
  let card;
121156
120896
  let cardKitProgress;
121157
120897
  let cardKitRecord;
121158
120898
  let cardKitStartFailed = false;
121159
- let progressPost;
121160
- let progressPostStartFailed = false;
121161
- if (!cardKitAvailable && surfaceController.shouldStartCardImmediately()) {
120899
+ let legacyCardStartFailed = false;
120900
+ let legacyCardStartFailureReason;
120901
+ let startFailurePostFallbackSent = false;
120902
+ if (!cardKitAvailable) {
121162
120903
  try {
121163
120904
  card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121164
120905
  await recordEvent({
@@ -121168,6 +120909,8 @@ var BridgeHandler = class {
121168
120909
  reason: "\u5DF2\u4EA4\u7ED9\u672C\u5730 Agent \u5904\u7406\u3002"
121169
120910
  });
121170
120911
  } catch (err) {
120912
+ legacyCardStartFailed = true;
120913
+ legacyCardStartFailureReason = String(err);
121171
120914
  console.error("[bridge.handler] Failed to start card for thread", threadId, err);
121172
120915
  await recordEvent({
121173
120916
  status: "running",
@@ -121183,7 +120926,7 @@ var BridgeHandler = class {
121183
120926
  status: "running",
121184
120927
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
121185
120928
  appendPath: "\u5EF6\u8FDF\u521B\u5EFA\u5361\u7247",
121186
- reason: "response_surface prototype lazy card creation is enabled."
120929
+ reason: "CardKit response surface is available; legacy card is reserved for fallback."
121187
120930
  });
121188
120931
  }
121189
120932
  try {
@@ -121405,72 +121148,34 @@ var BridgeHandler = class {
121405
121148
  }
121406
121149
  }
121407
121150
  }
121408
- if (!card && !cardKitAvailable && postOutboundAvailable && this.deps.postClient) {
121409
- const budget = prototypeConfig ? this.responseSurfacePostBudget.reserve({
121410
- scope: {
121411
- botId: this.deps.botConfig?.id ?? "v1-default",
121412
- chatId: parsed.chatId,
121413
- threadId
121414
- },
121415
- maxPosts: prototypeConfig.max_posts_per_window,
121416
- windowMs: prototypeConfig.post_window_ms
121417
- }) : void 0;
121418
- if (budget?.allowed === false) {
121419
- console.warn("[bridge.handler] post progress budget exhausted; using card fallback");
121420
- progressPostStartFailed = true;
121421
- } else {
121422
- try {
121423
- progressPost = await createPostProgressHandle({
121424
- postClient: this.deps.postClient,
121425
- replyToMessageId: messageId,
121426
- replyInThread,
121427
- facts: {
121428
- botId: this.deps.botConfig?.id ?? "v1-default",
121429
- threadId,
121430
- triggerMessageId: messageId
121431
- },
121432
- initialText: "\u52AA\u529B\u56DE\u7B54\u4E2D..."
121433
- });
121434
- await this.deps.client.removeProcessingReaction?.(messageId);
121435
- await recordEvent({
121436
- status: "running",
121437
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
121438
- appendPath: "\u5DF2\u521B\u5EFA post",
121439
- reason: "response surface \u4F7F\u7528 post \u4F5C\u4E3A\u672C\u8F6E\u4E3B\u56DE\u590D\u9762\u3002"
121440
- });
121441
- } catch (err) {
121442
- progressPostStartFailed = true;
121443
- console.warn("[bridge.handler] create progress post failed; using card fallback:", err);
121444
- }
121445
- }
121446
- }
121447
- if (!card && (progressPostStartFailed || cardKitStartFailed)) {
121151
+ if (!card && cardKitStartFailed) {
121448
121152
  try {
121449
121153
  card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121450
121154
  await this.deps.client.removeProcessingReaction?.(messageId);
121451
121155
  await recordEvent({
121452
121156
  status: "running",
121453
121157
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
121454
- appendPath: cardKitStartFailed ? "CardKit \u5931\u8D25\uFF0C\u5DF2\u521B\u5EFA\u5361\u7247" : "post \u5931\u8D25\uFF0C\u5DF2\u521B\u5EFA\u5361\u7247",
121455
- 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"
121456
121160
  });
121457
121161
  } catch (err) {
121458
121162
  console.error(
121459
121163
  "[bridge.handler] visible card fallback start failed after primary surface start failure:",
121460
121164
  err
121461
121165
  );
121462
- await createOnlyPostFallback({
121166
+ const postFallback = await createOnlyPostFallback({
121463
121167
  postClient: this.deps.postClient,
121464
121168
  replyToMessageId: messageId,
121465
121169
  replyInThread,
121466
121170
  botId: this.deps.botConfig?.id ?? "v1-default",
121467
121171
  threadId,
121468
121172
  triggerMessageId: messageId,
121469
- 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",
121470
121174
  failureReason: String(err),
121471
121175
  title: "Larkway fallback",
121472
121176
  logPrefix: "[bridge.handler]"
121473
121177
  });
121178
+ if (postFallback) startFailurePostFallbackSent = true;
121474
121179
  await this.deps.client.removeProcessingReaction?.(messageId);
121475
121180
  await recordEvent({
121476
121181
  status: "running",
@@ -121560,7 +121265,6 @@ var BridgeHandler = class {
121560
121265
  for await (const ev of handle.events) {
121561
121266
  if (cardKitProgress) cardKitProgress.handle(ev);
121562
121267
  else if (card) card.handle(ev);
121563
- else progressPost?.handle(ev);
121564
121268
  if (ev.type === "system_init") {
121565
121269
  sessionId = ev.sessionId;
121566
121270
  }
@@ -121707,7 +121411,6 @@ var BridgeHandler = class {
121707
121411
  }
121708
121412
  }
121709
121413
  } else {
121710
- await progressPost?.drain();
121711
121414
  const surfaceDispatch = await dispatchResponseSurface({
121712
121415
  state: reportedState,
121713
121416
  prototypeConfig,
@@ -121725,80 +121428,90 @@ var BridgeHandler = class {
121725
121428
  postOutboundAvailable: false,
121726
121429
  postLedgerAvailable: true,
121727
121430
  visibleFallbackAvailable: true,
121728
- postClient: this.deps.postClient,
121729
- livePost: progressPost ? {
121730
- messageId: progressPost.messageId,
121731
- idempotencyKey: progressPost.idempotencyKey,
121732
- role: progressPost.role
121733
- } : void 0,
121734
- postBudget: prototypeConfig ? {
121735
- reserve: () => this.responseSurfacePostBudget.reserve({
121736
- scope: {
121737
- botId: this.deps.botConfig?.id ?? "v1-default",
121738
- chatId: parsed.chatId,
121739
- threadId
121740
- },
121741
- maxPosts: prototypeConfig.max_posts_per_window,
121742
- windowMs: prototypeConfig.post_window_ms
121743
- })
121744
- } : void 0
121431
+ postClient: this.deps.postClient
121745
121432
  });
121746
121433
  if (surfaceDispatch.card) {
121747
121434
  if (!card) {
121748
- card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121749
121435
  try {
121750
- await writeCardFile(worktreePath, {
121751
- messageId: card.messageId,
121752
- chatId: parsed.chatId,
121753
- threadId,
121754
- botId: this.deps.botConfig?.id ?? "",
121755
- replyInThread,
121756
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
121757
- });
121758
- } catch (err) {
121759
- console.warn("[bridge.handler] writeCardFile(late) failed (continuing):", err);
121760
- }
121761
- }
121762
- await card.finalize(surfaceDispatch.card);
121763
- let keepCardFileForRetry = false;
121764
- if (surfaceDispatch.post?.requiresFallbackLedgerMark) {
121765
- try {
121766
- await markPostLedgerFallbackVisible(
121767
- worktreePath,
121768
- surfaceDispatch.post.idempotencyKey,
121769
- {
121770
- fallbackCardMessageId: card.messageId,
121771
- error: surfaceDispatch.post.fallbackError ?? surfaceDispatch.card.failureReason ?? "post outbound failed; visible card fallback used"
121772
- }
121773
- );
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
+ }
121774
121449
  } catch (err) {
121775
- keepCardFileForRetry = true;
121776
- console.warn(
121777
- "[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:",
121778
121452
  err
121779
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;
121780
121471
  }
121781
121472
  }
121782
- if (surfaceDispatch.post?.requiresPolicyLedgerMark) {
121783
- try {
121784
- await markPostLedgerPolicyBlockedVisible(
121785
- worktreePath,
121786
- surfaceDispatch.post.idempotencyKey,
121787
- {
121788
- fallbackCardMessageId: card.messageId,
121789
- error: surfaceDispatch.post.policyError ?? surfaceDispatch.card.failureReason ?? "mention policy blocked; visible card fallback used"
121790
- }
121791
- );
121792
- } catch (err) {
121793
- keepCardFileForRetry = true;
121794
- console.warn(
121795
- "[bridge.handler] policy-blocked ledger mark failed after visible card finalize; keeping card.json for retry:",
121796
- err
121797
- );
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);
121798
121514
  }
121799
- }
121800
- if (!keepCardFileForRetry) {
121801
- await deleteCardFile(worktreePath);
121802
121515
  }
121803
121516
  }
121804
121517
  }
@@ -121877,25 +121590,10 @@ var BridgeHandler = class {
121877
121590
  }
121878
121591
  }
121879
121592
  }
121880
- if (!card && progressPost) {
121881
- try {
121882
- await progressPost.finalize({
121883
- text: `\u6267\u884C\u5931\u8D25: ${String(err)}`
121884
- });
121885
- } catch (postFinalizeErr) {
121886
- console.error(
121887
- "[bridge.handler] progress post failure update failed; creating card fallback:",
121888
- postFinalizeErr
121889
- );
121890
- try {
121891
- card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121892
- } catch (cardStartErr) {
121893
- console.error("[bridge.handler] failure card start also failed:", cardStartErr);
121894
- await createHardFailurePostFallback(
121895
- `progress post failure update failed: ${String(postFinalizeErr)}; legacy visible card fallback also failed: ${String(cardStartErr)}`
121896
- );
121897
- }
121898
- }
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
+ );
121899
121597
  }
121900
121598
  if (card) {
121901
121599
  try {
@@ -126045,127 +125743,104 @@ function buildCodexEnv(botGitIdentity, gitlabToken) {
126045
125743
  }
126046
125744
  return env;
126047
125745
  }
126048
- function sandboxFlag(mode) {
126049
- switch (mode) {
126050
- case "bypassPermissions":
126051
- return ["--dangerously-bypass-approvals-and-sandbox"];
126052
- case "acceptEdits":
126053
- return ["--dangerously-bypass-approvals-and-sandbox"];
126054
- case "ask":
126055
- return ["--sandbox", "read-only"];
126056
- }
125746
+ function buildCodexCommand(opts, codexBinPath = "codex") {
125747
+ void opts;
125748
+ return [codexBinPath, ["app-server", "--stdio"]];
126057
125749
  }
126058
- function resumePermissionFlag(mode) {
126059
- return mode === "bypassPermissions" || mode === "acceptEdits" ? ["--dangerously-bypass-approvals-and-sandbox"] : [];
125750
+ function codexThreadSandboxMode(mode) {
125751
+ return mode === "ask" ? "read-only" : "danger-full-access";
126060
125752
  }
126061
- function buildCodexCommand(opts, codexBinPath = "codex") {
126062
- const mode = opts.permissionMode ?? "acceptEdits";
126063
- const commonFlags = [
126064
- "--json",
126065
- "--skip-git-repo-check",
126066
- ...sandboxFlag(mode)
126067
- ];
126068
- if (opts.resumeSessionId != null) {
126069
- const resumeFlags = [
126070
- "--json",
126071
- "--skip-git-repo-check",
126072
- ...resumePermissionFlag(mode)
126073
- ];
126074
- return [
126075
- codexBinPath,
126076
- ["exec", "resume", opts.resumeSessionId, ...resumeFlags, "-"]
126077
- ];
126078
- }
126079
- const freshFlags = opts.cwd != null ? ["-C", opts.cwd, ...commonFlags] : commonFlags;
126080
- return [codexBinPath, ["exec", ...freshFlags]];
125753
+ function codexTurnSandboxPolicy(mode) {
125754
+ if (mode === "ask") return { type: "readOnly", networkAccess: false };
125755
+ return { type: "dangerFullAccess" };
125756
+ }
125757
+ function codexApprovalPolicy(mode) {
125758
+ return mode === "ask" ? "on-request" : "never";
126081
125759
  }
126082
- var CodexLineParser = class {
125760
+ var CodexAppServerLineParser = class {
126083
125761
  answerExtractor = new AnswerChannelExtractor();
126084
- *parseLine(line) {
126085
- const trimmed = line.trim();
126086
- if (trimmed === "") return;
126087
- let obj;
126088
- try {
126089
- obj = JSON.parse(trimmed);
126090
- } catch {
126091
- yield { type: "raw", raw: trimmed };
126092
- return;
126093
- }
125762
+ *parseMessage(obj) {
126094
125763
  if (typeof obj !== "object" || obj === null) {
126095
125764
  yield { type: "raw", raw: obj };
126096
125765
  return;
126097
125766
  }
126098
125767
  const record = obj;
126099
- const topType = record["type"];
126100
- if (topType === "thread.started" && typeof record["thread_id"] === "string") {
126101
- yield { type: "system_init", sessionId: record["thread_id"], raw: obj };
125768
+ const method = record["method"];
125769
+ if (typeof method !== "string") {
125770
+ yield { type: "raw", raw: obj };
126102
125771
  return;
126103
125772
  }
126104
- if (topType === "turn.completed") {
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") {
126105
125781
  yield { type: "result", stopReason: "end_turn", raw: obj };
126106
125782
  return;
126107
125783
  }
126108
- const agentMessageText = agentMessageTextFrom(record);
126109
- if (agentMessageText !== void 0) {
126110
- if (topType === "item.completed") {
126111
- yield* this.answerExtractor.ingestSnapshot(agentMessageText, obj);
126112
- return;
126113
- }
126114
- yield* this.answerExtractor.ingestGrowingSnapshot(agentMessageText, obj);
125784
+ if (method === "item/agentMessage/delta") {
125785
+ const delta = typeof params?.["delta"] === "string" ? params["delta"] : "";
125786
+ yield* this.answerExtractor.ingestDelta(delta, obj);
126115
125787
  return;
126116
125788
  }
126117
- if (topType === "item.started") {
126118
- const item = record["item"];
126119
- if (typeof item === "object" && item !== null) {
126120
- const itemRecord = item;
126121
- if (itemRecord["type"] === "command_execution" && typeof itemRecord["command"] === "string") {
126122
- yield {
126123
- type: "tool_use",
126124
- toolName: "shell",
126125
- toolInput: { command: itemRecord["command"] },
126126
- raw: obj
126127
- };
126128
- return;
126129
- }
125789
+ if (method === "item/started") {
125790
+ const item = asRecord(params?.["item"]);
125791
+ if (item?.["type"] === "commandExecution" && typeof item["command"] === "string") {
125792
+ yield {
125793
+ type: "tool_use",
125794
+ toolName: "shell",
125795
+ toolInput: { command: item["command"] },
125796
+ raw: obj
125797
+ };
125798
+ return;
126130
125799
  }
126131
- yield { type: "raw", raw: obj };
126132
125800
  return;
126133
125801
  }
126134
- if (topType === "item.completed") {
126135
- const item = record["item"];
126136
- if (typeof item === "object" && item !== null) {
126137
- const itemRecord = item;
126138
- if (itemRecord["type"] === "command_execution") {
126139
- yield { type: "tool_result", raw: obj };
126140
- return;
126141
- }
125802
+ if (method === "item/completed") {
125803
+ const item = asRecord(params?.["item"]);
125804
+ if (item?.["type"] === "commandExecution") {
125805
+ yield { type: "tool_result", raw: obj };
125806
+ return;
125807
+ }
125808
+ if (item?.["type"] === "agentMessage" && typeof item["text"] === "string") {
125809
+ yield* this.answerExtractor.ingestSnapshot(item["text"], obj);
125810
+ return;
126142
125811
  }
126143
- yield { type: "raw", raw: obj };
126144
125812
  return;
126145
125813
  }
126146
125814
  yield { type: "raw", raw: obj };
126147
125815
  }
126148
125816
  };
126149
- function agentMessageTextFrom(record) {
126150
- const item = record["item"];
126151
- if (typeof item !== "object" || item === null) return void 0;
126152
- const itemRecord = item;
126153
- if (itemRecord["type"] !== "agent_message") return void 0;
126154
- const text = itemRecord["text"];
126155
- return typeof text === "string" ? text : void 0;
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;
126156
125826
  }
126157
125827
  function runCodex(opts, codexBinPath = "codex") {
126158
125828
  const timeoutMs = opts.timeoutMs ?? 15 * 60 * 1e3;
126159
125829
  const [bin, args] = buildCodexCommand(opts, codexBinPath);
126160
125830
  const env = buildCodexEnv(opts.botGitIdentity, opts.gitlabToken);
125831
+ const mode = opts.permissionMode ?? "acceptEdits";
125832
+ const requestById = /* @__PURE__ */ new Map();
125833
+ let nextRequestId = 1;
126161
125834
  const child = spawn3(bin, args, {
126162
125835
  env,
126163
125836
  stdio: ["pipe", "pipe", "pipe"],
126164
125837
  ...opts.cwd != null ? { cwd: opts.cwd } : {}
126165
125838
  });
126166
- if (child.stdin != null) {
126167
- child.stdin.write(opts.prompt, "utf8");
126168
- 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;
126169
125844
  }
126170
125845
  let discoveredSessionId;
126171
125846
  let killScheduled = false;
@@ -126179,6 +125854,10 @@ function runCodex(opts, codexBinPath = "codex") {
126179
125854
  }, SIGKILL_GRACE_MS2);
126180
125855
  killTimer.unref();
126181
125856
  }
125857
+ function stopAppServerAfterTurn() {
125858
+ if (child.killed || killScheduled) return;
125859
+ child.kill("SIGTERM");
125860
+ }
126182
125861
  const TOTAL_TIMEOUT_EXTRA_MS = SIGKILL_GRACE_MS2 + 2e3;
126183
125862
  let totalTimeoutFallbackHandle;
126184
125863
  let _forceFinalizeForTimeout = () => {
@@ -126201,6 +125880,8 @@ function runCodex(opts, codexBinPath = "codex") {
126201
125880
  const stderrChunks = [];
126202
125881
  child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
126203
125882
  const rlAbortController = new AbortController();
125883
+ let finishAppServerTurn;
125884
+ let failAppServerTurn;
126204
125885
  const done = new Promise(
126205
125886
  (resolve2, reject) => {
126206
125887
  let settled = false;
@@ -126230,6 +125911,17 @@ stderr: ${stderr}` : "")
126230
125911
  }
126231
125912
  resolve2({ exitCode, sessionId: discoveredSessionId });
126232
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;
126233
125925
  _forceFinalizeForTimeout = () => {
126234
125926
  if (settled) return;
126235
125927
  console.warn(
@@ -126277,15 +125969,76 @@ stderr: ${stderr}` : "")
126277
125969
  crlfDelay: Infinity,
126278
125970
  signal: rlAbortController.signal
126279
125971
  });
126280
- const parser = new CodexLineParser();
125972
+ const parser = new CodexAppServerLineParser();
125973
+ let turnCompleted = false;
125974
+ sendRequest("initialize", {
125975
+ clientInfo: { name: "larkway", version: "0.3" },
125976
+ capabilities: {}
125977
+ });
126281
125978
  try {
126282
125979
  for await (const line of rl) {
126283
- for (const event of parser.parseLine(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)) {
126284
126029
  if (event.type === "system_init") {
126285
126030
  discoveredSessionId = event.sessionId;
126286
126031
  }
126032
+ if (event.type === "result") {
126033
+ turnCompleted = true;
126034
+ }
126287
126035
  yield event;
126288
126036
  }
126037
+ if (turnCompleted) {
126038
+ stopAppServerAfterTurn();
126039
+ finishAppServerTurn?.(0);
126040
+ return;
126041
+ }
126289
126042
  }
126290
126043
  } catch (err) {
126291
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.26",
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)",