@fangyb/ahchat-bridge 0.1.40 → 0.1.41

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.cjs CHANGED
@@ -94678,6 +94678,25 @@ Transform tasks into verifiable goals:
94678
94678
  - "Refactor X" \u2192 ensure tests pass before and after.
94679
94679
  For multi-step tasks, state a brief plan with verification checks.
94680
94680
  `.trim();
94681
+ var GROUP_ONLY_SECTION_HEADERS = [
94682
+ "# \u7FA4\u804A\u516C\u7406\uFF08Group Chat Axiom \u2014 \u6700\u9AD8\u4F18\u5148\u7EA7\uFF09",
94683
+ "# Runtime payload \u2014 how to read unread messages",
94684
+ "# Group chat \u2014 when to speak",
94685
+ "# Group chat \u2014 recency & commitment",
94686
+ "# Length & conciseness in group chat",
94687
+ "# Group chat \u2014 shared task board",
94688
+ "# Group chat \u2014 batched inbox handling",
94689
+ "# \u7FA4\u804A\u4EA7\u7269\u7EAA\u5F8B\uFF08What to capture in group chats\uFF09",
94690
+ "# When a user joins or leaves your group"
94691
+ ];
94692
+ function stripGroupOnlySections(full, headers) {
94693
+ const headerSet = new Set(headers);
94694
+ return full.split(/\n(?=# )/).filter((section) => !headerSet.has(section.split("\n", 1)[0].trim())).join("\n").trim();
94695
+ }
94696
+ var PLATFORM_AGENT_RULES_SINGLE = stripGroupOnlySections(
94697
+ PLATFORM_AGENT_RULES,
94698
+ GROUP_ONLY_SECTION_HEADERS
94699
+ );
94681
94700
  var FAN_OUT_TRACE_TTL_MS = 10 * 6e4;
94682
94701
  var MAX_FILE_SIZE = 20 * 1024 * 1024;
94683
94702
  var MAX_IMAGE_SIZE = 10 * 1024 * 1024;
@@ -94849,6 +94868,14 @@ function assertArrayPayloadField(type, payload, field) {
94849
94868
  throw invalidWsMessage(type, field);
94850
94869
  }
94851
94870
  }
94871
+ function assertWorkdirSignalsPayloadField(type, payload, field) {
94872
+ assertArrayPayloadField(type, payload, field);
94873
+ for (const [index, item] of payload[field].entries()) {
94874
+ if (!isPlainRecord(item) || typeof item.toolName !== "string") {
94875
+ throw invalidWsMessage(type, `${field}[${index}].toolName`);
94876
+ }
94877
+ }
94878
+ }
94852
94879
  function assertRecordPayloadField(type, payload, field) {
94853
94880
  if (!isPlainRecord(payload[field])) {
94854
94881
  throw invalidWsMessage(type, field);
@@ -94929,6 +94956,9 @@ function validateWSMessageShape(msg) {
94929
94956
  "traceId"
94930
94957
  ]);
94931
94958
  assertArrayPayloadField(type, payload, "contentBlocks");
94959
+ if ("workdirSignals" in payload && payload.workdirSignals !== void 0) {
94960
+ assertWorkdirSignalsPayloadField(type, payload, "workdirSignals");
94961
+ }
94932
94962
  return;
94933
94963
  }
94934
94964
  case "agent:turn_complete": {
@@ -115629,7 +115659,7 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
115629
115659
  system_prompt: external_exports.string().describe("\u8BE5 Agent \u7684 system prompt\uFF0C\u5B9A\u4E49\u4EBA\u683C\u3001\u4E13\u957F\u3001\u884C\u4E3A\u51C6\u5219\u3002\u4E0D\u80FD\u4E3A\u7A7A\u3002"),
115630
115660
  tier: external_exports.enum(["smart", "balanced", "fast"]).describe("\u80FD\u529B\u6863\u4F4D\uFF1Asmart\uFF08\u65D7\u8230\uFF0C\u590D\u6742\u4EFB\u52A1\uFF09\u3001balanced\uFF08\u6807\u51C6\uFF0C\u5E38\u89C4\u4EFB\u52A1\uFF09\u3001fast\uFF08\u8F7B\u91CF\uFF0C\u7B80\u5355\u4EFB\u52A1\uFF09\u3002"),
115631
115661
  avatar: external_exports.string().optional().describe(
115632
- "\u5934\u50CF key\uFF0C\u4ECE\u4EE5\u4E0B\u9009\u4E00\u4E2A\u6700\u5339\u914D\u89D2\u8272\u7684\uFF1Aavatar_dev\uFF08\u5F00\u53D1\uFF09, avatar_pm\uFF08\u4EA7\u54C1/\u7BA1\u7406\uFF09, avatar_designer\uFF08\u8BBE\u8BA1\uFF09, avatar_qa\uFF08\u6D4B\u8BD5/QA\uFF09, avatar_writer\uFF08\u5199\u4F5C/\u6587\u6863\uFF09, avatar_analyst\uFF08\u5206\u6790/\u6570\u636E\uFF09, avatar_coach\uFF08\u654F\u6377\u6559\u7EC3/\u5E26\u6559\uFF09, avatar_scientist\uFF08\u7814\u7A76/\u7B97\u6CD5\uFF09, avatar_lawyer\uFF08\u6CD5\u52A1/\u5408\u89C4\uFF09, avatar_human_man\uFF08\u901A\u7528\u7537\u6027\uFF09, avatar_human_woman\uFF08\u901A\u7528\u5973\u6027\uFF09, avatar_human_nerd\uFF08\u6781\u5BA2\uFF09, avatar_human_cool\uFF08\u9177\uFF09, avatar_human_cowboy\uFF08\u72C2\u91CE\uFF09, avatar_human_artist\uFF08\u827A\u672F\u5BB6\uFF09\u3002\u7559\u7A7A\u5219\u7528\u9ED8\u8BA4 avatar_default\uFF08\u{1F916}\uFF09\u3002"
115662
+ "\u53EF\u9009\u5934\u50CF key\u3002\u901A\u5E38\u7559\u7A7A\uFF0C\u7531\u7CFB\u7EDF\u6839\u636E Agent \u540D\u5B57\u3001\u89D2\u8272\u548C\u63D0\u793A\u8BCD\u81EA\u52A8\u5206\u914D\u673A\u5668\u4EBA\u5934\u50CF\u5E76\u5F02\u6B65\u751F\u6210\u6700\u7EC8\u5934\u50CF\u3002\u4E0D\u8981\u7ED9 Agent \u4F7F\u7528 avatar_human_*\uFF0C\u8FD9\u4E9B\u53EA\u7ED9\u771F\u5B9E\u4EBA\u7C7B\u7528\u6237\u4F7F\u7528\u3002"
115633
115663
  ),
115634
115664
  initial_instruction: external_exports.string().optional().describe(
115635
115665
  '\u53EF\u9009\u3002\u521B\u5EFA\u540E\u7ACB\u5373\u4E0B\u53D1\u7ED9\u65B0 Agent \u7684\u4E00\u53E5\u8BDD\u6307\u4EE4\u3002\u5178\u578B\uFF1A"\u8BF7\u7528 1-2 \u53E5\u8BDD\u5411\u7528\u6237\u505A\u81EA\u6211\u4ECB\u7ECD\uFF0C\u8BF4\u660E\u4F60\u7684\u4E13\u957F"\u3002\u53EA\u5728\u7528\u6237\u660E\u786E\u8981"\u521B\u5EFA\u4E00\u4E2A\u5355 Agent"\u4E14\u5E0C\u671B\u8BE5 Agent \u7ACB\u5373\u9732\u9762\u65F6\u4F20\u3002\u56E2\u961F\u573A\u666F\u8BF7\u52FF\u4F20\u2014\u2014\u7531 Leader \u5728\u7FA4\u91CC\u6253\u62DB\u547C\u3002'
@@ -115972,7 +116002,7 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
115972
116002
  system_prompt: external_exports.string().optional().describe("\u65B0\u7684 system prompt\uFF08\u5B9A\u4E49\u4EBA\u683C\u3001\u4E13\u957F\u3001\u884C\u4E3A\u51C6\u5219\uFF09\u3002"),
115973
116003
  tier: external_exports.enum(["smart", "balanced", "fast"]).optional().describe("\u80FD\u529B\u6863\u4F4D\u3002\u4F20\u6B64\u53C2\u6570\u4F1A\u66F4\u65B0\u8BE5 Agent \u4F7F\u7528\u7684\u6A21\u578B\u3002"),
115974
116004
  avatar: external_exports.string().optional().describe(
115975
- "\u5934\u50CF key\uFF08\u5982 avatar_dev / avatar_pm / avatar_human_man \u7B49\uFF09\u3002"
116005
+ "\u5934\u50CF key\uFF08\u5982 avatar_dev / avatar_pm / avatar_default \u7B49\uFF09\u3002\u4E0D\u8981\u7ED9 Agent \u4F7F\u7528 avatar_human_*\uFF0C\u8FD9\u4E9B\u53EA\u7ED9\u771F\u5B9E\u4EBA\u7C7B\u7528\u6237\u4F7F\u7528\u3002"
115976
116006
  ),
115977
116007
  machine_bridge_key: external_exports.string().optional().describe(
115978
116008
  '\u53EF\u9009\u3002\u65B0\u7684\u8FD0\u884C\u673A\u5668 bridgeKey\uFF0C\u6765\u81EA list_contacts \u7684"\u53EF\u7528\u673A\u5668"\u3002\u7701\u7565\u8868\u793A\u4E0D\u6539\uFF1B\u4F20 auto \u6216\u7A7A\u5B57\u7B26\u4E32\u6E05\u9664\u673A\u5668\u504F\u597D\u3002'
@@ -116974,9 +117004,11 @@ function buildGroupInboxPrompt(entries, opts = {}) {
116974
117004
  // src/sdkEventMapper.ts
116975
117005
  init_cjs_shims();
116976
117006
  var logger12 = createModuleLogger("sdk.mapper");
117007
+ var DEBUG_ONLY_SYSTEM_SUBTYPES = /* @__PURE__ */ new Set(["status", "thinking_tokens"]);
116977
117008
  var HIGH_WATERMARK_INPUT_TOKENS = 12e4;
116978
117009
  var WARN_THRESHOLD_INPUT_TOKENS = 1e5;
116979
117010
  var LIVE_INPUT_PREVIEW_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit"]);
117011
+ var WORKDIR_MUTATION_TOOL_NAMES = /* @__PURE__ */ new Set(["write", "edit", "multiedit", "notebookedit", "bash"]);
116980
117012
  var CONTEXT_OVERFLOW_LOCK_MS = 6e4;
116981
117013
  function parseJsonRecord(text) {
116982
117014
  try {
@@ -116998,6 +117030,17 @@ function isSuccessfulOfficialMediaOutput(toolName, output) {
116998
117030
  if (!["succeeded", "success", "completed", "done"].includes(state)) return false;
116999
117031
  return Array.isArray(parsed.images) && parsed.images.length > 0 || typeof parsed.video_url === "string" || typeof parsed.videoUrl === "string";
117000
117032
  }
117033
+ function isWorkdirMutationToolName(toolName) {
117034
+ return WORKDIR_MUTATION_TOOL_NAMES.has(toolName.trim().toLowerCase());
117035
+ }
117036
+ function recordWorkdirSignal(proc, toolName, isError) {
117037
+ if (isError || !isGroupTask(proc) || !isWorkdirMutationToolName(toolName)) return;
117038
+ const signals = proc.workdirSignals ?? [];
117039
+ if (!signals.some((signal) => signal.toolName === toolName)) {
117040
+ signals.push({ toolName });
117041
+ }
117042
+ proc.workdirSignals = signals;
117043
+ }
117001
117044
  function isContextOverflowText(text) {
117002
117045
  const trimmed = text.trim();
117003
117046
  return /^prompt is too long\b/i.test(trimmed) || /context length .* exceed/i.test(trimmed);
@@ -117502,6 +117545,7 @@ function countByStatus(todos) {
117502
117545
  function emitGroupSegment(proc, emit, base, content, contentBlocks, isSilent = false) {
117503
117546
  const groupId = proc.currentTask?.groupId;
117504
117547
  if (!groupId) return;
117548
+ const workdirSignals = proc.workdirSignals?.length ? [...proc.workdirSignals] : void 0;
117505
117549
  proc.segmentCount += 1;
117506
117550
  logger12.info("Group segment emitted", {
117507
117551
  agentId: base.agentId,
@@ -117511,6 +117555,7 @@ function emitGroupSegment(proc, emit, base, content, contentBlocks, isSilent = f
117511
117555
  contentLen: content.length,
117512
117556
  blockCount: contentBlocks.length,
117513
117557
  blockTypes: contentBlocks.map((b) => b.type),
117558
+ workdirSignalCount: workdirSignals?.length ?? 0,
117514
117559
  traceId: base.traceId,
117515
117560
  isAuditOnly: content.length === 0,
117516
117561
  isSilent
@@ -117523,9 +117568,11 @@ function emitGroupSegment(proc, emit, base, content, contentBlocks, isSilent = f
117523
117568
  groupId,
117524
117569
  content,
117525
117570
  contentBlocks: [...contentBlocks],
117571
+ ...workdirSignals ? { workdirSignals } : {},
117526
117572
  ...isSilent ? { isSilent: true } : {}
117527
117573
  }
117528
117574
  });
117575
+ proc.workdirSignals = [];
117529
117576
  }
117530
117577
  function flushTextSegmentOnBlockStop(proc, emit, base) {
117531
117578
  const trimmed = proc.segmentBuffer.trim();
@@ -117544,7 +117591,7 @@ function flushTextSegmentOnBlockStop(proc, emit, base) {
117544
117591
  traceId: base.traceId
117545
117592
  });
117546
117593
  }
117547
- } else if (proc.contentBlocks.length > 0) {
117594
+ } else if (proc.contentBlocks.length > 0 || (proc.workdirSignals?.length ?? 0) > 0) {
117548
117595
  const blockCount = proc.contentBlocks.length;
117549
117596
  emitGroupSegment(proc, emit, base, "", proc.contentBlocks, true);
117550
117597
  proc.contentBlocks = [];
@@ -117634,7 +117681,8 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
117634
117681
  proc.activeSubagentTaskIds?.delete(subagentTaskId);
117635
117682
  }
117636
117683
  }
117637
- logger12.info("SDK system subtype unhandled", {
117684
+ const logUnhandledSystemSubtype = DEBUG_ONLY_SYSTEM_SUBTYPES.has(String(sysMsg.subtype ?? "")) ? logger12.debug.bind(logger12) : logger12.info.bind(logger12);
117685
+ logUnhandledSystemSubtype("SDK system subtype unhandled", {
117638
117686
  agentId: proc.agentId,
117639
117687
  scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
117640
117688
  subtype: sysMsg.subtype ?? "(none)",
@@ -117691,6 +117739,8 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
117691
117739
  input: {}
117692
117740
  }
117693
117741
  });
117742
+ }
117743
+ if (shouldStreamInternals(proc) && !proc.suppressCurrentToolUse && toolName !== "ExitPlanMode" && !isAskUserQuestionToolName(toolName)) {
117694
117744
  proc.contentBlocks.push({
117695
117745
  type: "tool_use",
117696
117746
  ...toolUseId ? { toolUseId } : {},
@@ -118031,6 +118081,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
118031
118081
  }
118032
118082
  }
118033
118083
  }
118084
+ recordWorkdirSignal(proc, toolName, isError);
118034
118085
  proc.activeToolUseStartedAt = void 0;
118035
118086
  proc.currentToolUseId = null;
118036
118087
  }
@@ -118092,7 +118143,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
118092
118143
  if (isNoReplyText(trimmed, { allowSdkSyntheticNoResponse: groupMode })) {
118093
118144
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
118094
118145
  emitUsageReported(proc, emit, base, usage);
118095
- if (groupMode && proc.contentBlocks.length > 0) {
118146
+ if (groupMode && (proc.contentBlocks.length > 0 || (proc.workdirSignals?.length ?? 0) > 0)) {
118096
118147
  emitGroupSegment(
118097
118148
  proc,
118098
118149
  emit,
@@ -118137,11 +118188,12 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
118137
118188
  proc.segmentBuffer = proc.accumulatedText;
118138
118189
  flushTextSegmentOnBlockStop(proc, emit, base);
118139
118190
  }
118140
- if (proc.contentBlocks.length > 0) {
118191
+ if (proc.contentBlocks.length > 0 || (proc.workdirSignals?.length ?? 0) > 0) {
118141
118192
  logger12.info("Group turn trailing audit segment", {
118142
118193
  agentId: proc.agentId,
118143
118194
  replyMessageId: base.replyMessageId,
118144
118195
  blockCount: proc.contentBlocks.length,
118196
+ workdirSignalCount: proc.workdirSignals?.length ?? 0,
118145
118197
  traceId: base.traceId
118146
118198
  });
118147
118199
  emitGroupSegment(proc, emit, base, "", proc.contentBlocks, true);
@@ -118286,6 +118338,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
118286
118338
  payload: { ...wireBase(base), error: errorText }
118287
118339
  });
118288
118340
  proc.contentBlocks = [];
118341
+ proc.workdirSignals = [];
118289
118342
  proc.accumulatedText = "";
118290
118343
  proc.accumulatedThinking = "";
118291
118344
  proc.segmentBuffer = "";
@@ -118318,6 +118371,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
118318
118371
  proc.apiErrorEmitted = true;
118319
118372
  }
118320
118373
  proc.contentBlocks = [];
118374
+ proc.workdirSignals = [];
118321
118375
  proc.accumulatedText = "";
118322
118376
  proc.accumulatedThinking = "";
118323
118377
  proc.segmentBuffer = "";
@@ -118344,6 +118398,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
118344
118398
  proc.apiErrorEmitted = true;
118345
118399
  }
118346
118400
  proc.contentBlocks = [];
118401
+ proc.workdirSignals = [];
118347
118402
  proc.accumulatedText = "";
118348
118403
  proc.accumulatedThinking = "";
118349
118404
  proc.segmentBuffer = "";
@@ -118380,6 +118435,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
118380
118435
  });
118381
118436
  }
118382
118437
  proc.contentBlocks = [];
118438
+ proc.workdirSignals = [];
118383
118439
  proc.accumulatedText = "";
118384
118440
  proc.accumulatedThinking = "";
118385
118441
  proc.segmentBuffer = "";
@@ -118421,6 +118477,7 @@ function resetAccumulators(proc) {
118421
118477
  proc.activeToolUseStartedAt = void 0;
118422
118478
  proc.segmentBuffer = "";
118423
118479
  proc.segmentCount = 0;
118480
+ proc.workdirSignals = [];
118424
118481
  proc.accumulatedToolInput = "";
118425
118482
  proc.apiErrorEmitted = false;
118426
118483
  proc.peakContextUsage = void 0;
@@ -118620,7 +118677,7 @@ function missingSubscriptionMessage(subscriptionId) {
118620
118677
  }
118621
118678
  var NODE_USER_UID = 1e3;
118622
118679
  var POST_MERGE_CONTINUATION_ROUTE_MS = 15e3;
118623
- var SCOPE_PROMPT_FINGERPRINT_REVISION = "workdir-scope-mcp-abi-prompt-v4";
118680
+ var SCOPE_PROMPT_FINGERPRINT_REVISION = "workdir-scope-mcp-abi-prompt-v6";
118624
118681
  var BINARY_ATTACHMENT_EXT_RE = /\.(?:7z|bmp|csv|doc|docx|gif|jpeg|jpg|m4a|mov|mp3|mp4|pdf|png|ppt|pptx|rar|rtf|wav|webm|webp|xls|xlsx|zip)$/i;
118625
118682
  var IMAGE_READ_EXT_RE = /\.(?:bmp|gif|jpeg|jpg|png|webp)$/i;
118626
118683
  var DOCUMENT_READING_RULES = `DOCUMENT READING:
@@ -118636,6 +118693,31 @@ var MEDIA_GENERATION_RULES = `MEDIA GENERATION:
118636
118693
  - Keep media replies short. Do not print raw media URLs, request_id, task_id, polling logs, or "let me check again" narration unless the user explicitly asks for diagnostics.
118637
118694
  - When a media task is submitted or completed, write only a natural one-line note such as "\u5DF2\u5F00\u59CB\u751F\u6210\uFF0C\u6211\u4F1A\u5728\u8FD9\u91CC\u66F4\u65B0\u7ED3\u679C\u3002" or "\u751F\u6210\u597D\u4E86\uFF0C\u53EF\u4EE5\u5728\u5361\u7247\u91CC\u67E5\u770B\u3002"; let the media card show status, preview, download, copy, and regenerate actions.
118638
118695
  - If the user asks whether a Seedance task is ready, call mcp__seedance__seedance_check_task once and answer from that result. Do not loop, sleep, or invent external Seedance API endpoints.`;
118696
+ var SMITH_ALLOWED_TOOLS = [
118697
+ // creation / configuration (Smith-only)
118698
+ "mcp__neural__create_agent",
118699
+ "mcp__neural__update_agent_profile",
118700
+ "mcp__neural__recommend_agent_skills",
118701
+ "mcp__neural__read_skill",
118702
+ // diagnostics (log detective)
118703
+ "mcp__neural__fetch_logs",
118704
+ // friend approval (Smith-only)
118705
+ "mcp__neural__list_friends",
118706
+ "mcp__neural__accept_friend",
118707
+ "mcp__neural__add_friend",
118708
+ // team assembly
118709
+ "mcp__neural__list_contacts",
118710
+ "mcp__neural__create_group",
118711
+ "mcp__neural__add_to_group",
118712
+ "mcp__neural__transfer_group_owner",
118713
+ "mcp__neural__leave_group",
118714
+ // neural bridge — cross-scope coordination when Smith is pulled into a group
118715
+ "mcp__neural__neural_send",
118716
+ "mcp__neural__neural_list_scopes",
118717
+ // interaction / planning
118718
+ "AskUserQuestion",
118719
+ "Write"
118720
+ ];
118639
118721
  function resolveVisionMcpToolHints(externalMcp) {
118640
118722
  let describe3 = null;
118641
118723
  let ocr = null;
@@ -119900,7 +119982,7 @@ ${cfg.instructions.trim()}` : "";
119900
119982
  } catch (error51) {
119901
119983
  logger15.error("Failed to read existing API-key agent settings; starting fresh", {
119902
119984
  agentId: agentConfig.id,
119903
- settingsPath,
119985
+ settingsFile: "settings.json",
119904
119986
  error: error51
119905
119987
  });
119906
119988
  }
@@ -119913,7 +119995,7 @@ ${cfg.instructions.trim()}` : "";
119913
119995
  await import_promises3.default.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
119914
119996
  logger15.info("API-key agent using isolated config dir", {
119915
119997
  agentId: agentConfig.id,
119916
- dir: effectiveConfigDir,
119998
+ configDirKind: "api-key-agent",
119917
119999
  isNew,
119918
120000
  hasBaseUrl: !!cfg.apiBaseUrl,
119919
120001
  settingsWritten: Object.keys(envEntries)
@@ -120007,12 +120089,21 @@ ${cfg.instructions.trim()}` : "";
120007
120089
  });
120008
120090
  }
120009
120091
  }
120010
- const externalMcp = this.mcpRegistry?.buildForAgent({
120092
+ const resolvedExternalMcp = this.mcpRegistry?.buildForAgent({
120011
120093
  agentId: agentConfig.id,
120012
120094
  capabilityTier: cfg.capabilityTier,
120013
120095
  isSmith: smithAgent,
120014
120096
  cwd: agentCwd
120015
120097
  }) ?? { mcpServers: {}, allowedTools: [], toolAbi: [] };
120098
+ const externalMcp = smithAgent ? { mcpServers: {}, allowedTools: [], toolAbi: [] } : resolvedExternalMcp;
120099
+ if (smithAgent && (Object.keys(resolvedExternalMcp.mcpServers).length > 0 || resolvedExternalMcp.allowedTools.length > 0 || (resolvedExternalMcp.toolAbi?.length ?? 0) > 0)) {
120100
+ logger15.info("Smith external MCP ignored by fixed tool whitelist", {
120101
+ agentId: agentConfig.id,
120102
+ scope: scopeKey(scope),
120103
+ serverNames: Object.keys(resolvedExternalMcp.mcpServers),
120104
+ allowedToolCount: resolvedExternalMcp.allowedTools.length
120105
+ });
120106
+ }
120016
120107
  const visionMcpTools = resolveVisionMcpToolHints(externalMcp);
120017
120108
  logger15.info("External MCP resolved for runtime", {
120018
120109
  agentId: agentConfig.id,
@@ -120085,6 +120176,7 @@ ${cfg.instructions.trim()}` : "";
120085
120176
  logger15.info("Creating Agent query", {
120086
120177
  agentId: agentConfig.id,
120087
120178
  scope: scopeKey(scope),
120179
+ systemPromptMode: smithAgent ? "string" : "preset",
120088
120180
  cwd: agentCwd,
120089
120181
  resume: !!savedSessionId,
120090
120182
  sessionId: savedSessionId,
@@ -120105,71 +120197,63 @@ ${cfg.instructions.trim()}` : "";
120105
120197
  });
120106
120198
  const planModeRef = { active: false, denyCount: 0 };
120107
120199
  const mediaGenerationTurnGuard = createOfficialMediaGenerationTurnGuard();
120200
+ const platformRules = scope.kind === "group" ? PLATFORM_AGENT_RULES : PLATFORM_AGENT_RULES_SINGLE;
120201
+ const appendText = [
120202
+ platformRules,
120203
+ nativeReadToolDisabled ? buildNativeReadDisabledRules(visionMcpTools) : "",
120204
+ DOCUMENT_READING_RULES,
120205
+ MEDIA_GENERATION_RULES,
120206
+ agentConfig.systemPrompt,
120207
+ scopedInstructions,
120208
+ notebookSection,
120209
+ forkHistorySection,
120210
+ scopesSection
120211
+ ].filter((s) => typeof s === "string" && s.trim().length > 0).join("\n\n");
120212
+ const systemPrompt = smithAgent ? appendText : { type: "preset", preset: "claude_code", append: appendText };
120213
+ const universalAllowedTools = [
120214
+ ...nativeReadToolDisabled ? [] : ["Read"],
120215
+ "Edit",
120216
+ "Write",
120217
+ "Bash",
120218
+ "Glob",
120219
+ "Grep",
120220
+ ...builtinWebSearchAllowed ? ["WebSearch"] : [],
120221
+ "WebFetch",
120222
+ "TodoWrite",
120223
+ "TaskCreate",
120224
+ "TaskUpdate",
120225
+ "AskUserQuestion",
120226
+ "mcp__neural__neural_send",
120227
+ "mcp__neural__neural_list_scopes",
120228
+ "mcp__neural__self_note",
120229
+ "mcp__neural__list_contacts",
120230
+ "mcp__neural__create_group",
120231
+ "mcp__neural__add_to_group",
120232
+ "mcp__neural__leave_group",
120233
+ "mcp__neural__remove_from_group",
120234
+ "mcp__neural__create_group_issue",
120235
+ "mcp__neural__resolve_group_issue",
120236
+ "mcp__neural__list_group_tasks",
120237
+ "mcp__neural__update_group_task",
120238
+ "mcp__neural__transfer_group_owner",
120239
+ "mcp__neural__post_to_moments",
120240
+ "mcp__neural__post_to_forum",
120241
+ "mcp__neural__read_moments",
120242
+ "mcp__neural__read_chat_history",
120243
+ "mcp__neural__read_document",
120244
+ "mcp__neural__list_available_skills",
120245
+ "mcp__neural__list_skill_index"
120246
+ ];
120108
120247
  const options = {
120109
120248
  cwd: agentCwd,
120110
- systemPrompt: {
120111
- type: "preset",
120112
- preset: "claude_code",
120113
- append: [
120114
- PLATFORM_AGENT_RULES,
120115
- nativeReadToolDisabled ? buildNativeReadDisabledRules(visionMcpTools) : "",
120116
- DOCUMENT_READING_RULES,
120117
- MEDIA_GENERATION_RULES,
120118
- agentConfig.systemPrompt,
120119
- scopedInstructions,
120120
- notebookSection,
120121
- forkHistorySection,
120122
- scopesSection
120123
- ].filter((s) => typeof s === "string" && s.trim().length > 0).join("\n\n")
120124
- },
120249
+ systemPrompt,
120125
120250
  permissionMode: "bypassPermissions",
120126
120251
  allowDangerouslySkipPermissions: true,
120127
120252
  // allowedTools is the visibility whitelist passed to Claude Code. MCP tools
120128
120253
  // with always_ask must still be included here so the model can request them;
120129
120254
  // the MCP server policy/permission layer decides whether execution asks.
120130
120255
  allowedTools: [
120131
- ...nativeReadToolDisabled ? [] : ["Read"],
120132
- "Edit",
120133
- "Write",
120134
- "Bash",
120135
- "Glob",
120136
- "Grep",
120137
- ...builtinWebSearchAllowed ? ["WebSearch"] : [],
120138
- "WebFetch",
120139
- "TodoWrite",
120140
- "TaskCreate",
120141
- "TaskUpdate",
120142
- "AskUserQuestion",
120143
- "mcp__neural__neural_send",
120144
- "mcp__neural__neural_list_scopes",
120145
- "mcp__neural__self_note",
120146
- "mcp__neural__list_contacts",
120147
- "mcp__neural__create_group",
120148
- "mcp__neural__add_to_group",
120149
- "mcp__neural__leave_group",
120150
- "mcp__neural__remove_from_group",
120151
- "mcp__neural__create_group_issue",
120152
- "mcp__neural__resolve_group_issue",
120153
- "mcp__neural__list_group_tasks",
120154
- "mcp__neural__update_group_task",
120155
- "mcp__neural__transfer_group_owner",
120156
- "mcp__neural__post_to_moments",
120157
- "mcp__neural__post_to_forum",
120158
- "mcp__neural__read_moments",
120159
- "mcp__neural__read_chat_history",
120160
- "mcp__neural__read_document",
120161
- "mcp__neural__list_available_skills",
120162
- "mcp__neural__list_skill_index",
120163
- ...isSmithAgent2(agentConfig) ? [
120164
- "mcp__neural__create_agent",
120165
- "mcp__neural__update_agent_profile",
120166
- "mcp__neural__recommend_agent_skills",
120167
- "mcp__neural__read_skill",
120168
- "mcp__neural__list_friends",
120169
- "mcp__neural__accept_friend",
120170
- "mcp__neural__add_friend",
120171
- "mcp__neural__fetch_logs"
120172
- ] : [],
120256
+ ...smithAgent ? SMITH_ALLOWED_TOOLS : universalAllowedTools,
120173
120257
  ...externalMcp.allowedTools
120174
120258
  ],
120175
120259
  // Server-side WebSearch bypasses canUseTool; disallowedTools removes it from model context.
@@ -120181,7 +120265,7 @@ ${cfg.instructions.trim()}` : "";
120181
120265
  // instructions as the workflow body (replacing the default code-implementation
120182
120266
  // phases). The SDK wraps it with read-only enforcement + ExitPlanMode protocol.
120183
120267
  planModeInstructions: (() => {
120184
- const planTools = [
120268
+ const planTools = (smithAgent ? ["AskUserQuestion", "Write (plan file only)"] : [
120185
120269
  ...nativeReadToolDisabled ? [] : ["Read"],
120186
120270
  "Glob",
120187
120271
  "Grep",
@@ -120189,19 +120273,19 @@ ${cfg.instructions.trim()}` : "";
120189
120273
  "WebFetch",
120190
120274
  "AskUserQuestion",
120191
120275
  "Write (plan file only)"
120192
- ].join(", ");
120193
- const researchTools = [
120276
+ ]).join(", ");
120277
+ const researchTools = (smithAgent ? ["AskUserQuestion"] : [
120194
120278
  ...nativeReadToolDisabled ? [] : ["Read"],
120195
120279
  "Grep",
120196
120280
  ...builtinWebSearchAllowed ? ["WebSearch"] : []
120197
- ].join(", ");
120198
- const unavailableTools = [
120281
+ ]).join(", ");
120282
+ const unavailableTools = (smithAgent ? ["Read", "Edit", "Bash", "Glob", "Grep", "WebFetch", "TodoWrite", "ExitPlanMode"] : [
120199
120283
  "Edit",
120200
120284
  "Bash",
120201
120285
  "TodoWrite",
120202
120286
  "ExitPlanMode",
120203
120287
  ...nativeReadToolDisabled ? ["Read"] : []
120204
- ].join(", ");
120288
+ ]).join(", ");
120205
120289
  const smithTools = smithAgent ? "\nSMITH-SPECIFIC TOOLS (available in plan mode): mcp__neural__recommend_agent_skills, mcp__neural__read_skill, mcp__neural__fetch_logs, mcp__neural__create_agent, mcp__neural__update_agent_profile \u2014 use these to recommend initial skill sets, research existing skills, check logs, plan agent creation, and adjust Agent profiles." : "";
120206
120290
  return `You are a PLANNER, NOT an executor. The user will execute your plan later.
120207
120291
 
@@ -120431,17 +120515,17 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
120431
120515
  }
120432
120516
  };
120433
120517
  const userPromptTrimmed = (agentConfig.systemPrompt ?? "").trim();
120434
- const appendStr = options.systemPrompt.append;
120435
120518
  logger15.info("Platform rules attached", {
120436
120519
  agentId: agentConfig.id,
120437
120520
  scope: scopeKey(scope),
120438
- platformRulesLen: PLATFORM_AGENT_RULES.length,
120521
+ systemPromptMode: smithAgent ? "string" : "preset",
120522
+ platformRulesLen: platformRules.length,
120439
120523
  userPromptLen: userPromptTrimmed.length,
120440
120524
  hasUserPrompt: userPromptTrimmed.length > 0,
120441
120525
  notebookLen: notebookSection.length,
120442
120526
  forkHistoryLen: forkHistorySection.length,
120443
120527
  scopesLen: scopesSection.length,
120444
- appendLen: appendStr.length,
120528
+ appendLen: appendText.length,
120445
120529
  hasCreateAgentTool: smithAgent,
120446
120530
  hasLogDetectiveTools: smithAgent && this.skillStore !== null
120447
120531
  });
@@ -120511,6 +120595,7 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
120511
120595
  mcpAuditRecorder: this.mcpAuditRecorder,
120512
120596
  segmentBuffer: "",
120513
120597
  segmentCount: 0,
120598
+ workdirSignals: [],
120514
120599
  accumulatedToolInput: "",
120515
120600
  planModeRef,
120516
120601
  mediaGenerationTurnGuard,
@@ -121544,6 +121629,7 @@ ${lines.join("\n")}`;
121544
121629
  proc.currentToolName = null;
121545
121630
  proc.segmentBuffer = "";
121546
121631
  proc.segmentCount = 0;
121632
+ proc.workdirSignals = [];
121547
121633
  }
121548
121634
  clearPostMergeContinuationTimer(runtime) {
121549
121635
  if (runtime.postMergeContinuationTimer) {
@@ -124319,6 +124405,7 @@ var HttpMcpRegistry = class {
124319
124405
  localStore;
124320
124406
  serverConnections = /* @__PURE__ */ new Map();
124321
124407
  localConnections = /* @__PURE__ */ new Map();
124408
+ missingSecretWarnedKeys = /* @__PURE__ */ new Set();
124322
124409
  lastRefreshCount = null;
124323
124410
  apiUrl(suffix) {
124324
124411
  const base = this.serverApiUrl.replace(/\/$/, "");
@@ -124419,13 +124506,19 @@ var HttpMcpRegistry = class {
124419
124506
  return false;
124420
124507
  });
124421
124508
  }
124509
+ warnMissingSecretOnce(connection) {
124510
+ const key = `${connection.id}:${connection.providerId}:${connection.serverName}`;
124511
+ if (this.missingSecretWarnedKeys.has(key)) return;
124512
+ this.missingSecretWarnedKeys.add(key);
124513
+ logger20.warn("Skipping MCP connection without required secret", {
124514
+ id: connection.id,
124515
+ providerId: connection.providerId,
124516
+ serverName: connection.serverName
124517
+ });
124518
+ }
124422
124519
  toSdkServerConfig(connection, ctx) {
124423
124520
  if (mcpConnectionRequiresSecret(connection) && !connection.hasAuthSecret) {
124424
- logger20.warn("Skipping MCP connection without required secret", {
124425
- id: connection.id,
124426
- providerId: connection.providerId,
124427
- serverName: connection.serverName
124428
- });
124521
+ this.warnMissingSecretOnce(connection);
124429
124522
  return null;
124430
124523
  }
124431
124524
  const policies = toolPolicies(connection);
@@ -126622,8 +126715,47 @@ var import_node_fs11 = __toESM(require("fs"), 1);
126622
126715
  var import_promises7 = __toESM(require("fs/promises"), 1);
126623
126716
  var import_node_path21 = __toESM(require("path"), 1);
126624
126717
  var logger29 = createModuleLogger("bridge.logUploader");
126718
+ var STALE_RATE_LIMIT_SKIP_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
126625
126719
  var DEFAULT_LOG_UPLOAD_INTERVAL_MS = 24 * 60 * 60 * 1e3;
126626
126720
  var DEFAULT_BATCH_SIZE = 200;
126721
+ var MAX_UPLOAD_CHUNK_BODY_BYTES = 448 * 1024;
126722
+ var MAX_UPLOAD_RETRY_AFTER_MS = 24 * 60 * 60 * 1e3;
126723
+ var LogUploadHttpError = class extends Error {
126724
+ status;
126725
+ scope;
126726
+ retryAfterMs;
126727
+ constructor(status, bodyText) {
126728
+ const parsed = parseUploadErrorBody(bodyText);
126729
+ const summary = parsed.error ?? bodyText;
126730
+ super(`upload failed HTTP ${status}: ${summary.slice(0, 160)}`);
126731
+ this.name = "LogUploadHttpError";
126732
+ this.status = status;
126733
+ if (parsed.scope) this.scope = parsed.scope;
126734
+ if (parsed.retryAfterMs !== void 0) this.retryAfterMs = parsed.retryAfterMs;
126735
+ }
126736
+ };
126737
+ function normalizeRetryAfterMs(value) {
126738
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
126739
+ return Math.min(Math.ceil(value), MAX_UPLOAD_RETRY_AFTER_MS);
126740
+ }
126741
+ function parseUploadErrorBody(bodyText) {
126742
+ try {
126743
+ const parsed = JSON.parse(bodyText);
126744
+ if (typeof parsed !== "object" || parsed === null) return {};
126745
+ const body = parsed;
126746
+ return {
126747
+ ...typeof body.error === "string" && body.error.length > 0 ? { error: body.error } : {},
126748
+ ...typeof body.scope === "string" && body.scope.length > 0 ? { scope: body.scope } : {},
126749
+ ...(() => {
126750
+ const retryAfterMs = normalizeRetryAfterMs(body.retryAfterMs);
126751
+ return retryAfterMs === void 0 ? {} : { retryAfterMs };
126752
+ })()
126753
+ };
126754
+ } catch (e) {
126755
+ logger29.debug("Failed to parse log upload error body", { error: e });
126756
+ return {};
126757
+ }
126758
+ }
126627
126759
  function defaultLogFile(dataDir) {
126628
126760
  return import_node_path21.default.join(dataDir, "logs", "bridge.log");
126629
126761
  }
@@ -126713,26 +126845,38 @@ function parseProcessedLines(raw, cursor, fileName, fingerprint) {
126713
126845
  return { entries: [], nextCursor: cursor, advanced: false, reason: "partial_line" };
126714
126846
  }
126715
126847
  const processed = raw.slice(0, lastNewline + 1);
126716
- const lines = processed.split(/\r?\n/);
126848
+ const linePattern = /.*?\n/g;
126717
126849
  const entries = [];
126718
126850
  let lineNum = cursor.lineNum;
126719
- for (let i = 0; i < lines.length - 1; i += 1) {
126720
- const line = lines[i] ?? "";
126851
+ let offset = cursor.offset;
126852
+ let match;
126853
+ while ((match = linePattern.exec(processed)) !== null) {
126854
+ const rawLine = match[0];
126855
+ const line = rawLine.endsWith("\r\n") ? rawLine.slice(0, -2) : rawLine.slice(0, -1);
126721
126856
  lineNum += 1;
126857
+ offset += Buffer.byteLength(rawLine, "utf8");
126858
+ const cursorAfter = {
126859
+ offset,
126860
+ lineNum,
126861
+ ...fingerprint ? { fingerprint } : {}
126862
+ };
126722
126863
  if (line.length === 0) continue;
126723
126864
  const parsed = parseLogLine(line);
126724
126865
  if (!parsed) continue;
126725
126866
  entries.push({
126726
- ...parsed,
126727
- raw: line,
126728
- file: fileName,
126729
- lineNum
126867
+ entry: {
126868
+ ...parsed,
126869
+ raw: line,
126870
+ file: fileName,
126871
+ lineNum
126872
+ },
126873
+ cursorAfter
126730
126874
  });
126731
126875
  }
126732
126876
  return {
126733
126877
  entries,
126734
126878
  nextCursor: {
126735
- offset: cursor.offset + Buffer.byteLength(processed, "utf8"),
126879
+ offset,
126736
126880
  lineNum,
126737
126881
  ...fingerprint ? { fingerprint } : {}
126738
126882
  },
@@ -126740,15 +126884,35 @@ function parseProcessedLines(raw, cursor, fileName, fingerprint) {
126740
126884
  reason: "advanced"
126741
126885
  };
126742
126886
  }
126743
- function chunkEntries(entries, size) {
126744
- const chunks = [];
126745
- for (let i = 0; i < entries.length; i += size) {
126746
- chunks.push(entries.slice(i, i + size));
126887
+ function logEntryTimeRange(entries) {
126888
+ let minTime = Number.POSITIVE_INFINITY;
126889
+ let maxTime = Number.NEGATIVE_INFINITY;
126890
+ let minTs = "";
126891
+ let maxTs = "";
126892
+ for (const entry of entries) {
126893
+ const parsed = Date.parse(entry.ts);
126894
+ if (!Number.isFinite(parsed)) return null;
126895
+ if (parsed < minTime) {
126896
+ minTime = parsed;
126897
+ minTs = entry.ts;
126898
+ }
126899
+ if (parsed > maxTime) {
126900
+ maxTime = parsed;
126901
+ maxTs = entry.ts;
126902
+ }
126747
126903
  }
126748
- return chunks;
126904
+ if (!minTs || !maxTs) return null;
126905
+ return { minTs, maxTs, maxTime };
126906
+ }
126907
+ function isStaleRateLimitedChunk(entries, nowMs) {
126908
+ const range = logEntryTimeRange(entries);
126909
+ if (!range) return null;
126910
+ if (nowMs - range.maxTime <= STALE_RATE_LIMIT_SKIP_AGE_MS) return null;
126911
+ return { minTs: range.minTs, maxTs: range.maxTs };
126749
126912
  }
126750
126913
  var BridgeLogUploader = class {
126751
126914
  options;
126915
+ retryAfterByTarget = /* @__PURE__ */ new Map();
126752
126916
  timer = null;
126753
126917
  running = false;
126754
126918
  stopped = false;
@@ -126765,7 +126929,7 @@ var BridgeLogUploader = class {
126765
126929
  bridgeId: options.bridgeId,
126766
126930
  hostname: options.hostname,
126767
126931
  intervalMs: options.intervalMs ?? DEFAULT_LOG_UPLOAD_INTERVAL_MS,
126768
- batchSize: options.batchSize ?? DEFAULT_BATCH_SIZE,
126932
+ batchSize: Math.max(1, Math.floor(options.batchSize ?? DEFAULT_BATCH_SIZE)),
126769
126933
  primaryTarget,
126770
126934
  extraTargets: (options.extraLogFiles ?? []).map((file2) => normalizeTarget(file2, options.dataDir)),
126771
126935
  includeRotatedBridgeLogs: options.includeRotatedBridgeLogs ?? true
@@ -126807,13 +126971,26 @@ var BridgeLogUploader = class {
126807
126971
  bridgeEntryCount: 0,
126808
126972
  uploadedChunkCount: 0,
126809
126973
  accepted: 0,
126810
- skipped: 0
126974
+ skipped: 0,
126975
+ dropped: 0
126811
126976
  };
126812
126977
  try {
126813
126978
  const targets = await this.resolveTargets();
126814
126979
  summary.targetCount = targets.length;
126815
126980
  for (const target of targets) {
126981
+ const targetKey = this.targetKey(target);
126816
126982
  try {
126983
+ const retryUntil = this.retryAfterByTarget.get(targetKey);
126984
+ const now = Date.now();
126985
+ if (retryUntil && retryUntil > now) {
126986
+ summary.idleTargetCount += 1;
126987
+ logger29.debug("Bridge log upload target backoff active", {
126988
+ logFile: target.logFile,
126989
+ retryAfterMs: retryUntil - now
126990
+ });
126991
+ continue;
126992
+ }
126993
+ if (retryUntil) this.retryAfterByTarget.delete(targetKey);
126817
126994
  const cursor = await readCursor(target.cursorFile);
126818
126995
  const batch = await this.readNewEntries(target, cursor);
126819
126996
  if (!batch.advanced) {
@@ -126825,28 +127002,45 @@ var BridgeLogUploader = class {
126825
127002
  summary.advancedTargetCount += 1;
126826
127003
  summary.parsedEntryCount += batch.entries.length;
126827
127004
  if (batch.entries.length > 0) {
126828
- const result = await this.uploadEntries(batch.entries);
127005
+ const result = await this.uploadEntries(batch.entries, target.cursorFile, batch.nextCursor);
126829
127006
  summary.bridgeEntryCount += result.bridgeEntryCount;
126830
127007
  summary.uploadedChunkCount += result.uploadedChunkCount;
126831
127008
  summary.accepted += result.accepted;
126832
127009
  summary.skipped += result.skipped;
127010
+ summary.dropped += result.dropped;
127011
+ } else {
127012
+ await writeCursor(target.cursorFile, batch.nextCursor);
126833
127013
  }
126834
- await writeCursor(target.cursorFile, batch.nextCursor);
127014
+ this.retryAfterByTarget.delete(targetKey);
126835
127015
  } catch (e) {
126836
127016
  summary.failedTargetCount += 1;
126837
- logger29.warn("Bridge log upload target failed", { error: e, logFile: target.logFile });
127017
+ if (e instanceof LogUploadHttpError && e.retryAfterMs !== void 0) {
127018
+ this.retryAfterByTarget.set(targetKey, Date.now() + e.retryAfterMs);
127019
+ logger29.warn("Bridge log upload target failed", {
127020
+ error: e,
127021
+ logFile: target.logFile,
127022
+ status: e.status,
127023
+ quotaScope: e.scope,
127024
+ retryAfterMs: e.retryAfterMs
127025
+ });
127026
+ } else {
127027
+ logger29.warn("Bridge log upload target failed", { error: e, logFile: target.logFile });
127028
+ }
126838
127029
  }
126839
127030
  }
126840
127031
  } catch (e) {
126841
127032
  logger29.warn("Bridge log upload cycle failed", { error: e });
126842
127033
  } finally {
126843
- logger29.info("Bridge log upload cycle summary", {
127034
+ logger29.debug("Bridge log upload cycle summary", {
126844
127035
  ...summary,
126845
127036
  durationMs: Date.now() - startedAt
126846
127037
  });
126847
127038
  this.running = false;
126848
127039
  }
126849
127040
  }
127041
+ targetKey(target) {
127042
+ return `${target.logFile}\0${target.cursorFile}`;
127043
+ }
126850
127044
  async resolveTargets() {
126851
127045
  const bridgeTargets = this.options.includeRotatedBridgeLogs ? await listRotatedBridgeTargets(this.options.primaryTarget, this.options.dataDir) : [this.options.primaryTarget];
126852
127046
  const seen = /* @__PURE__ */ new Set();
@@ -126879,15 +127073,40 @@ var BridgeLogUploader = class {
126879
127073
  const raw = await readStreamText(target.logFile, normalizedCursor.offset);
126880
127074
  return parseProcessedLines(raw, normalizedCursor, target.uploadedFileName, fingerprint);
126881
127075
  }
126882
- async uploadEntries(entries) {
126883
- const bridgeEntries = entries.filter((entry) => entry.source === "bridge");
127076
+ uploadBodyBytes(entries) {
127077
+ return Buffer.byteLength(JSON.stringify({
127078
+ bridgeId: this.options.bridgeId,
127079
+ hostname: this.options.hostname,
127080
+ entries
127081
+ }), "utf8");
127082
+ }
127083
+ chunkUploadEntries(entries) {
127084
+ const chunks = [];
127085
+ let current = [];
127086
+ for (const entry of entries) {
127087
+ const next = [...current, entry];
127088
+ const nextBodyBytes = this.uploadBodyBytes(next.map((item) => item.entry));
127089
+ if (current.length > 0 && (current.length >= this.options.batchSize || nextBodyBytes > MAX_UPLOAD_CHUNK_BODY_BYTES)) {
127090
+ chunks.push(current);
127091
+ current = [entry];
127092
+ } else {
127093
+ current = next;
127094
+ }
127095
+ }
127096
+ if (current.length > 0) chunks.push(current);
127097
+ return chunks;
127098
+ }
127099
+ async uploadEntries(entries, cursorFile, finalCursor) {
127100
+ const bridgeEntries = entries.filter((item) => item.entry.source === "bridge");
126884
127101
  const result = {
126885
127102
  bridgeEntryCount: bridgeEntries.length,
126886
127103
  uploadedChunkCount: 0,
126887
127104
  accepted: 0,
126888
- skipped: 0
127105
+ skipped: 0,
127106
+ dropped: 0
126889
127107
  };
126890
- for (const chunk of chunkEntries(bridgeEntries, this.options.batchSize)) {
127108
+ for (const chunk of this.chunkUploadEntries(bridgeEntries)) {
127109
+ const payloadEntries = chunk.map((item) => item.entry);
126891
127110
  const res = await fetch(`${this.options.serverApiUrl}/api/logs/upload`, {
126892
127111
  method: "POST",
126893
127112
  headers: {
@@ -126897,7 +127116,7 @@ var BridgeLogUploader = class {
126897
127116
  body: JSON.stringify({
126898
127117
  bridgeId: this.options.bridgeId,
126899
127118
  hostname: this.options.hostname,
126900
- entries: chunk
127119
+ entries: payloadEntries
126901
127120
  })
126902
127121
  });
126903
127122
  if (!res.ok) {
@@ -126905,13 +127124,27 @@ var BridgeLogUploader = class {
126905
127124
  logger29.debug("Failed to read log upload error body", { error: e });
126906
127125
  return "";
126907
127126
  });
126908
- throw new Error(`upload failed HTTP ${res.status}: ${body2.slice(0, 160)}`);
127127
+ const staleChunk = res.status === 429 ? isStaleRateLimitedChunk(payloadEntries, Date.now()) : null;
127128
+ if (staleChunk) {
127129
+ result.dropped += chunk.length;
127130
+ logger29.warn("Skipping stale bridge log upload chunk after rate limit", {
127131
+ status: res.status,
127132
+ entryCount: chunk.length,
127133
+ minEntryTs: staleChunk.minTs,
127134
+ maxEntryTs: staleChunk.maxTs
127135
+ });
127136
+ continue;
127137
+ }
127138
+ throw new LogUploadHttpError(res.status, body2);
126909
127139
  }
126910
127140
  const body = await res.json();
126911
127141
  result.uploadedChunkCount += 1;
126912
127142
  result.accepted += typeof body.accepted === "number" ? body.accepted : 0;
126913
127143
  result.skipped += typeof body.skipped === "number" ? body.skipped : 0;
127144
+ const last = chunk[chunk.length - 1];
127145
+ if (last) await writeCursor(cursorFile, last.cursorAfter);
126914
127146
  }
127147
+ await writeCursor(cursorFile, finalCursor);
126915
127148
  return result;
126916
127149
  }
126917
127150
  };
@@ -126995,7 +127228,7 @@ var SkillStore = class {
126995
127228
  if (!isNotFoundError(e)) logger30.warn("Skill seed existing read failed", { name, filePath, error: e });
126996
127229
  }
126997
127230
  if (existing === content) {
126998
- logger30.info("Skill already in sync", { name, bytes: content.length });
127231
+ logger30.debug("Skill already in sync", { name, bytes: content.length });
126999
127232
  return;
127000
127233
  }
127001
127234
  import_node_fs12.default.writeFileSync(tmpPath, content, "utf-8");