@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/index.js CHANGED
@@ -5908,6 +5908,25 @@ Transform tasks into verifiable goals:
5908
5908
  - "Refactor X" \u2192 ensure tests pass before and after.
5909
5909
  For multi-step tasks, state a brief plan with verification checks.
5910
5910
  `.trim();
5911
+ var GROUP_ONLY_SECTION_HEADERS = [
5912
+ "# \u7FA4\u804A\u516C\u7406\uFF08Group Chat Axiom \u2014 \u6700\u9AD8\u4F18\u5148\u7EA7\uFF09",
5913
+ "# Runtime payload \u2014 how to read unread messages",
5914
+ "# Group chat \u2014 when to speak",
5915
+ "# Group chat \u2014 recency & commitment",
5916
+ "# Length & conciseness in group chat",
5917
+ "# Group chat \u2014 shared task board",
5918
+ "# Group chat \u2014 batched inbox handling",
5919
+ "# \u7FA4\u804A\u4EA7\u7269\u7EAA\u5F8B\uFF08What to capture in group chats\uFF09",
5920
+ "# When a user joins or leaves your group"
5921
+ ];
5922
+ function stripGroupOnlySections(full, headers) {
5923
+ const headerSet = new Set(headers);
5924
+ return full.split(/\n(?=# )/).filter((section) => !headerSet.has(section.split("\n", 1)[0].trim())).join("\n").trim();
5925
+ }
5926
+ var PLATFORM_AGENT_RULES_SINGLE = stripGroupOnlySections(
5927
+ PLATFORM_AGENT_RULES,
5928
+ GROUP_ONLY_SECTION_HEADERS
5929
+ );
5911
5930
  var FAN_OUT_TRACE_TTL_MS = 10 * 6e4;
5912
5931
  var MAX_FILE_SIZE = 20 * 1024 * 1024;
5913
5932
  var MAX_IMAGE_SIZE = 10 * 1024 * 1024;
@@ -6057,6 +6076,14 @@ function assertArrayPayloadField(type, payload, field) {
6057
6076
  throw invalidWsMessage(type, field);
6058
6077
  }
6059
6078
  }
6079
+ function assertWorkdirSignalsPayloadField(type, payload, field) {
6080
+ assertArrayPayloadField(type, payload, field);
6081
+ for (const [index, item] of payload[field].entries()) {
6082
+ if (!isPlainRecord(item) || typeof item.toolName !== "string") {
6083
+ throw invalidWsMessage(type, `${field}[${index}].toolName`);
6084
+ }
6085
+ }
6086
+ }
6060
6087
  function assertRecordPayloadField(type, payload, field) {
6061
6088
  if (!isPlainRecord(payload[field])) {
6062
6089
  throw invalidWsMessage(type, field);
@@ -6137,6 +6164,9 @@ function validateWSMessageShape(msg) {
6137
6164
  "traceId"
6138
6165
  ]);
6139
6166
  assertArrayPayloadField(type, payload, "contentBlocks");
6167
+ if ("workdirSignals" in payload && payload.workdirSignals !== void 0) {
6168
+ assertWorkdirSignalsPayloadField(type, payload, "workdirSignals");
6169
+ }
6140
6170
  return;
6141
6171
  }
6142
6172
  case "agent:turn_complete": {
@@ -26860,7 +26890,7 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
26860
26890
  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"),
26861
26891
  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"),
26862
26892
  avatar: external_exports.string().optional().describe(
26863
- "\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"
26893
+ "\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"
26864
26894
  ),
26865
26895
  initial_instruction: external_exports.string().optional().describe(
26866
26896
  '\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'
@@ -27203,7 +27233,7 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
27203
27233
  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"),
27204
27234
  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"),
27205
27235
  avatar: external_exports.string().optional().describe(
27206
- "\u5934\u50CF key\uFF08\u5982 avatar_dev / avatar_pm / avatar_human_man \u7B49\uFF09\u3002"
27236
+ "\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"
27207
27237
  ),
27208
27238
  machine_bridge_key: external_exports.string().optional().describe(
27209
27239
  '\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'
@@ -28199,9 +28229,11 @@ function buildGroupInboxPrompt(entries, opts = {}) {
28199
28229
 
28200
28230
  // src/sdkEventMapper.ts
28201
28231
  var logger11 = createModuleLogger("sdk.mapper");
28232
+ var DEBUG_ONLY_SYSTEM_SUBTYPES = /* @__PURE__ */ new Set(["status", "thinking_tokens"]);
28202
28233
  var HIGH_WATERMARK_INPUT_TOKENS = 12e4;
28203
28234
  var WARN_THRESHOLD_INPUT_TOKENS = 1e5;
28204
28235
  var LIVE_INPUT_PREVIEW_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit"]);
28236
+ var WORKDIR_MUTATION_TOOL_NAMES = /* @__PURE__ */ new Set(["write", "edit", "multiedit", "notebookedit", "bash"]);
28205
28237
  var CONTEXT_OVERFLOW_LOCK_MS = 6e4;
28206
28238
  function parseJsonRecord(text) {
28207
28239
  try {
@@ -28223,6 +28255,17 @@ function isSuccessfulOfficialMediaOutput(toolName, output) {
28223
28255
  if (!["succeeded", "success", "completed", "done"].includes(state)) return false;
28224
28256
  return Array.isArray(parsed.images) && parsed.images.length > 0 || typeof parsed.video_url === "string" || typeof parsed.videoUrl === "string";
28225
28257
  }
28258
+ function isWorkdirMutationToolName(toolName) {
28259
+ return WORKDIR_MUTATION_TOOL_NAMES.has(toolName.trim().toLowerCase());
28260
+ }
28261
+ function recordWorkdirSignal(proc, toolName, isError) {
28262
+ if (isError || !isGroupTask(proc) || !isWorkdirMutationToolName(toolName)) return;
28263
+ const signals = proc.workdirSignals ?? [];
28264
+ if (!signals.some((signal) => signal.toolName === toolName)) {
28265
+ signals.push({ toolName });
28266
+ }
28267
+ proc.workdirSignals = signals;
28268
+ }
28226
28269
  function isContextOverflowText(text) {
28227
28270
  const trimmed = text.trim();
28228
28271
  return /^prompt is too long\b/i.test(trimmed) || /context length .* exceed/i.test(trimmed);
@@ -28727,6 +28770,7 @@ function countByStatus(todos) {
28727
28770
  function emitGroupSegment(proc, emit, base, content, contentBlocks, isSilent = false) {
28728
28771
  const groupId = proc.currentTask?.groupId;
28729
28772
  if (!groupId) return;
28773
+ const workdirSignals = proc.workdirSignals?.length ? [...proc.workdirSignals] : void 0;
28730
28774
  proc.segmentCount += 1;
28731
28775
  logger11.info("Group segment emitted", {
28732
28776
  agentId: base.agentId,
@@ -28736,6 +28780,7 @@ function emitGroupSegment(proc, emit, base, content, contentBlocks, isSilent = f
28736
28780
  contentLen: content.length,
28737
28781
  blockCount: contentBlocks.length,
28738
28782
  blockTypes: contentBlocks.map((b) => b.type),
28783
+ workdirSignalCount: workdirSignals?.length ?? 0,
28739
28784
  traceId: base.traceId,
28740
28785
  isAuditOnly: content.length === 0,
28741
28786
  isSilent
@@ -28748,9 +28793,11 @@ function emitGroupSegment(proc, emit, base, content, contentBlocks, isSilent = f
28748
28793
  groupId,
28749
28794
  content,
28750
28795
  contentBlocks: [...contentBlocks],
28796
+ ...workdirSignals ? { workdirSignals } : {},
28751
28797
  ...isSilent ? { isSilent: true } : {}
28752
28798
  }
28753
28799
  });
28800
+ proc.workdirSignals = [];
28754
28801
  }
28755
28802
  function flushTextSegmentOnBlockStop(proc, emit, base) {
28756
28803
  const trimmed = proc.segmentBuffer.trim();
@@ -28769,7 +28816,7 @@ function flushTextSegmentOnBlockStop(proc, emit, base) {
28769
28816
  traceId: base.traceId
28770
28817
  });
28771
28818
  }
28772
- } else if (proc.contentBlocks.length > 0) {
28819
+ } else if (proc.contentBlocks.length > 0 || (proc.workdirSignals?.length ?? 0) > 0) {
28773
28820
  const blockCount = proc.contentBlocks.length;
28774
28821
  emitGroupSegment(proc, emit, base, "", proc.contentBlocks, true);
28775
28822
  proc.contentBlocks = [];
@@ -28859,7 +28906,8 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
28859
28906
  proc.activeSubagentTaskIds?.delete(subagentTaskId);
28860
28907
  }
28861
28908
  }
28862
- logger11.info("SDK system subtype unhandled", {
28909
+ const logUnhandledSystemSubtype = DEBUG_ONLY_SYSTEM_SUBTYPES.has(String(sysMsg.subtype ?? "")) ? logger11.debug.bind(logger11) : logger11.info.bind(logger11);
28910
+ logUnhandledSystemSubtype("SDK system subtype unhandled", {
28863
28911
  agentId: proc.agentId,
28864
28912
  scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
28865
28913
  subtype: sysMsg.subtype ?? "(none)",
@@ -28916,6 +28964,8 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
28916
28964
  input: {}
28917
28965
  }
28918
28966
  });
28967
+ }
28968
+ if (shouldStreamInternals(proc) && !proc.suppressCurrentToolUse && toolName !== "ExitPlanMode" && !isAskUserQuestionToolName(toolName)) {
28919
28969
  proc.contentBlocks.push({
28920
28970
  type: "tool_use",
28921
28971
  ...toolUseId ? { toolUseId } : {},
@@ -29256,6 +29306,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29256
29306
  }
29257
29307
  }
29258
29308
  }
29309
+ recordWorkdirSignal(proc, toolName, isError);
29259
29310
  proc.activeToolUseStartedAt = void 0;
29260
29311
  proc.currentToolUseId = null;
29261
29312
  }
@@ -29317,7 +29368,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29317
29368
  if (isNoReplyText(trimmed, { allowSdkSyntheticNoResponse: groupMode })) {
29318
29369
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
29319
29370
  emitUsageReported(proc, emit, base, usage);
29320
- if (groupMode && proc.contentBlocks.length > 0) {
29371
+ if (groupMode && (proc.contentBlocks.length > 0 || (proc.workdirSignals?.length ?? 0) > 0)) {
29321
29372
  emitGroupSegment(
29322
29373
  proc,
29323
29374
  emit,
@@ -29362,11 +29413,12 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29362
29413
  proc.segmentBuffer = proc.accumulatedText;
29363
29414
  flushTextSegmentOnBlockStop(proc, emit, base);
29364
29415
  }
29365
- if (proc.contentBlocks.length > 0) {
29416
+ if (proc.contentBlocks.length > 0 || (proc.workdirSignals?.length ?? 0) > 0) {
29366
29417
  logger11.info("Group turn trailing audit segment", {
29367
29418
  agentId: proc.agentId,
29368
29419
  replyMessageId: base.replyMessageId,
29369
29420
  blockCount: proc.contentBlocks.length,
29421
+ workdirSignalCount: proc.workdirSignals?.length ?? 0,
29370
29422
  traceId: base.traceId
29371
29423
  });
29372
29424
  emitGroupSegment(proc, emit, base, "", proc.contentBlocks, true);
@@ -29511,6 +29563,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29511
29563
  payload: { ...wireBase(base), error: errorText }
29512
29564
  });
29513
29565
  proc.contentBlocks = [];
29566
+ proc.workdirSignals = [];
29514
29567
  proc.accumulatedText = "";
29515
29568
  proc.accumulatedThinking = "";
29516
29569
  proc.segmentBuffer = "";
@@ -29543,6 +29596,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29543
29596
  proc.apiErrorEmitted = true;
29544
29597
  }
29545
29598
  proc.contentBlocks = [];
29599
+ proc.workdirSignals = [];
29546
29600
  proc.accumulatedText = "";
29547
29601
  proc.accumulatedThinking = "";
29548
29602
  proc.segmentBuffer = "";
@@ -29569,6 +29623,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29569
29623
  proc.apiErrorEmitted = true;
29570
29624
  }
29571
29625
  proc.contentBlocks = [];
29626
+ proc.workdirSignals = [];
29572
29627
  proc.accumulatedText = "";
29573
29628
  proc.accumulatedThinking = "";
29574
29629
  proc.segmentBuffer = "";
@@ -29605,6 +29660,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29605
29660
  });
29606
29661
  }
29607
29662
  proc.contentBlocks = [];
29663
+ proc.workdirSignals = [];
29608
29664
  proc.accumulatedText = "";
29609
29665
  proc.accumulatedThinking = "";
29610
29666
  proc.segmentBuffer = "";
@@ -29646,6 +29702,7 @@ function resetAccumulators(proc) {
29646
29702
  proc.activeToolUseStartedAt = void 0;
29647
29703
  proc.segmentBuffer = "";
29648
29704
  proc.segmentCount = 0;
29705
+ proc.workdirSignals = [];
29649
29706
  proc.accumulatedToolInput = "";
29650
29707
  proc.apiErrorEmitted = false;
29651
29708
  proc.peakContextUsage = void 0;
@@ -29841,7 +29898,7 @@ function missingSubscriptionMessage(subscriptionId) {
29841
29898
  }
29842
29899
  var NODE_USER_UID = 1e3;
29843
29900
  var POST_MERGE_CONTINUATION_ROUTE_MS = 15e3;
29844
- var SCOPE_PROMPT_FINGERPRINT_REVISION = "workdir-scope-mcp-abi-prompt-v4";
29901
+ var SCOPE_PROMPT_FINGERPRINT_REVISION = "workdir-scope-mcp-abi-prompt-v6";
29845
29902
  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;
29846
29903
  var IMAGE_READ_EXT_RE = /\.(?:bmp|gif|jpeg|jpg|png|webp)$/i;
29847
29904
  var DOCUMENT_READING_RULES = `DOCUMENT READING:
@@ -29857,6 +29914,31 @@ var MEDIA_GENERATION_RULES = `MEDIA GENERATION:
29857
29914
  - 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.
29858
29915
  - 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.
29859
29916
  - 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.`;
29917
+ var SMITH_ALLOWED_TOOLS = [
29918
+ // creation / configuration (Smith-only)
29919
+ "mcp__neural__create_agent",
29920
+ "mcp__neural__update_agent_profile",
29921
+ "mcp__neural__recommend_agent_skills",
29922
+ "mcp__neural__read_skill",
29923
+ // diagnostics (log detective)
29924
+ "mcp__neural__fetch_logs",
29925
+ // friend approval (Smith-only)
29926
+ "mcp__neural__list_friends",
29927
+ "mcp__neural__accept_friend",
29928
+ "mcp__neural__add_friend",
29929
+ // team assembly
29930
+ "mcp__neural__list_contacts",
29931
+ "mcp__neural__create_group",
29932
+ "mcp__neural__add_to_group",
29933
+ "mcp__neural__transfer_group_owner",
29934
+ "mcp__neural__leave_group",
29935
+ // neural bridge — cross-scope coordination when Smith is pulled into a group
29936
+ "mcp__neural__neural_send",
29937
+ "mcp__neural__neural_list_scopes",
29938
+ // interaction / planning
29939
+ "AskUserQuestion",
29940
+ "Write"
29941
+ ];
29860
29942
  function resolveVisionMcpToolHints(externalMcp) {
29861
29943
  let describe3 = null;
29862
29944
  let ocr = null;
@@ -31121,7 +31203,7 @@ ${cfg.instructions.trim()}` : "";
31121
31203
  } catch (error51) {
31122
31204
  logger14.error("Failed to read existing API-key agent settings; starting fresh", {
31123
31205
  agentId: agentConfig.id,
31124
- settingsPath,
31206
+ settingsFile: "settings.json",
31125
31207
  error: error51
31126
31208
  });
31127
31209
  }
@@ -31134,7 +31216,7 @@ ${cfg.instructions.trim()}` : "";
31134
31216
  await fs6.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
31135
31217
  logger14.info("API-key agent using isolated config dir", {
31136
31218
  agentId: agentConfig.id,
31137
- dir: effectiveConfigDir,
31219
+ configDirKind: "api-key-agent",
31138
31220
  isNew,
31139
31221
  hasBaseUrl: !!cfg.apiBaseUrl,
31140
31222
  settingsWritten: Object.keys(envEntries)
@@ -31228,12 +31310,21 @@ ${cfg.instructions.trim()}` : "";
31228
31310
  });
31229
31311
  }
31230
31312
  }
31231
- const externalMcp = this.mcpRegistry?.buildForAgent({
31313
+ const resolvedExternalMcp = this.mcpRegistry?.buildForAgent({
31232
31314
  agentId: agentConfig.id,
31233
31315
  capabilityTier: cfg.capabilityTier,
31234
31316
  isSmith: smithAgent,
31235
31317
  cwd: agentCwd
31236
31318
  }) ?? { mcpServers: {}, allowedTools: [], toolAbi: [] };
31319
+ const externalMcp = smithAgent ? { mcpServers: {}, allowedTools: [], toolAbi: [] } : resolvedExternalMcp;
31320
+ if (smithAgent && (Object.keys(resolvedExternalMcp.mcpServers).length > 0 || resolvedExternalMcp.allowedTools.length > 0 || (resolvedExternalMcp.toolAbi?.length ?? 0) > 0)) {
31321
+ logger14.info("Smith external MCP ignored by fixed tool whitelist", {
31322
+ agentId: agentConfig.id,
31323
+ scope: scopeKey(scope),
31324
+ serverNames: Object.keys(resolvedExternalMcp.mcpServers),
31325
+ allowedToolCount: resolvedExternalMcp.allowedTools.length
31326
+ });
31327
+ }
31237
31328
  const visionMcpTools = resolveVisionMcpToolHints(externalMcp);
31238
31329
  logger14.info("External MCP resolved for runtime", {
31239
31330
  agentId: agentConfig.id,
@@ -31306,6 +31397,7 @@ ${cfg.instructions.trim()}` : "";
31306
31397
  logger14.info("Creating Agent query", {
31307
31398
  agentId: agentConfig.id,
31308
31399
  scope: scopeKey(scope),
31400
+ systemPromptMode: smithAgent ? "string" : "preset",
31309
31401
  cwd: agentCwd,
31310
31402
  resume: !!savedSessionId,
31311
31403
  sessionId: savedSessionId,
@@ -31326,71 +31418,63 @@ ${cfg.instructions.trim()}` : "";
31326
31418
  });
31327
31419
  const planModeRef = { active: false, denyCount: 0 };
31328
31420
  const mediaGenerationTurnGuard = createOfficialMediaGenerationTurnGuard();
31421
+ const platformRules = scope.kind === "group" ? PLATFORM_AGENT_RULES : PLATFORM_AGENT_RULES_SINGLE;
31422
+ const appendText = [
31423
+ platformRules,
31424
+ nativeReadToolDisabled ? buildNativeReadDisabledRules(visionMcpTools) : "",
31425
+ DOCUMENT_READING_RULES,
31426
+ MEDIA_GENERATION_RULES,
31427
+ agentConfig.systemPrompt,
31428
+ scopedInstructions,
31429
+ notebookSection,
31430
+ forkHistorySection,
31431
+ scopesSection
31432
+ ].filter((s) => typeof s === "string" && s.trim().length > 0).join("\n\n");
31433
+ const systemPrompt = smithAgent ? appendText : { type: "preset", preset: "claude_code", append: appendText };
31434
+ const universalAllowedTools = [
31435
+ ...nativeReadToolDisabled ? [] : ["Read"],
31436
+ "Edit",
31437
+ "Write",
31438
+ "Bash",
31439
+ "Glob",
31440
+ "Grep",
31441
+ ...builtinWebSearchAllowed ? ["WebSearch"] : [],
31442
+ "WebFetch",
31443
+ "TodoWrite",
31444
+ "TaskCreate",
31445
+ "TaskUpdate",
31446
+ "AskUserQuestion",
31447
+ "mcp__neural__neural_send",
31448
+ "mcp__neural__neural_list_scopes",
31449
+ "mcp__neural__self_note",
31450
+ "mcp__neural__list_contacts",
31451
+ "mcp__neural__create_group",
31452
+ "mcp__neural__add_to_group",
31453
+ "mcp__neural__leave_group",
31454
+ "mcp__neural__remove_from_group",
31455
+ "mcp__neural__create_group_issue",
31456
+ "mcp__neural__resolve_group_issue",
31457
+ "mcp__neural__list_group_tasks",
31458
+ "mcp__neural__update_group_task",
31459
+ "mcp__neural__transfer_group_owner",
31460
+ "mcp__neural__post_to_moments",
31461
+ "mcp__neural__post_to_forum",
31462
+ "mcp__neural__read_moments",
31463
+ "mcp__neural__read_chat_history",
31464
+ "mcp__neural__read_document",
31465
+ "mcp__neural__list_available_skills",
31466
+ "mcp__neural__list_skill_index"
31467
+ ];
31329
31468
  const options = {
31330
31469
  cwd: agentCwd,
31331
- systemPrompt: {
31332
- type: "preset",
31333
- preset: "claude_code",
31334
- append: [
31335
- PLATFORM_AGENT_RULES,
31336
- nativeReadToolDisabled ? buildNativeReadDisabledRules(visionMcpTools) : "",
31337
- DOCUMENT_READING_RULES,
31338
- MEDIA_GENERATION_RULES,
31339
- agentConfig.systemPrompt,
31340
- scopedInstructions,
31341
- notebookSection,
31342
- forkHistorySection,
31343
- scopesSection
31344
- ].filter((s) => typeof s === "string" && s.trim().length > 0).join("\n\n")
31345
- },
31470
+ systemPrompt,
31346
31471
  permissionMode: "bypassPermissions",
31347
31472
  allowDangerouslySkipPermissions: true,
31348
31473
  // allowedTools is the visibility whitelist passed to Claude Code. MCP tools
31349
31474
  // with always_ask must still be included here so the model can request them;
31350
31475
  // the MCP server policy/permission layer decides whether execution asks.
31351
31476
  allowedTools: [
31352
- ...nativeReadToolDisabled ? [] : ["Read"],
31353
- "Edit",
31354
- "Write",
31355
- "Bash",
31356
- "Glob",
31357
- "Grep",
31358
- ...builtinWebSearchAllowed ? ["WebSearch"] : [],
31359
- "WebFetch",
31360
- "TodoWrite",
31361
- "TaskCreate",
31362
- "TaskUpdate",
31363
- "AskUserQuestion",
31364
- "mcp__neural__neural_send",
31365
- "mcp__neural__neural_list_scopes",
31366
- "mcp__neural__self_note",
31367
- "mcp__neural__list_contacts",
31368
- "mcp__neural__create_group",
31369
- "mcp__neural__add_to_group",
31370
- "mcp__neural__leave_group",
31371
- "mcp__neural__remove_from_group",
31372
- "mcp__neural__create_group_issue",
31373
- "mcp__neural__resolve_group_issue",
31374
- "mcp__neural__list_group_tasks",
31375
- "mcp__neural__update_group_task",
31376
- "mcp__neural__transfer_group_owner",
31377
- "mcp__neural__post_to_moments",
31378
- "mcp__neural__post_to_forum",
31379
- "mcp__neural__read_moments",
31380
- "mcp__neural__read_chat_history",
31381
- "mcp__neural__read_document",
31382
- "mcp__neural__list_available_skills",
31383
- "mcp__neural__list_skill_index",
31384
- ...isSmithAgent2(agentConfig) ? [
31385
- "mcp__neural__create_agent",
31386
- "mcp__neural__update_agent_profile",
31387
- "mcp__neural__recommend_agent_skills",
31388
- "mcp__neural__read_skill",
31389
- "mcp__neural__list_friends",
31390
- "mcp__neural__accept_friend",
31391
- "mcp__neural__add_friend",
31392
- "mcp__neural__fetch_logs"
31393
- ] : [],
31477
+ ...smithAgent ? SMITH_ALLOWED_TOOLS : universalAllowedTools,
31394
31478
  ...externalMcp.allowedTools
31395
31479
  ],
31396
31480
  // Server-side WebSearch bypasses canUseTool; disallowedTools removes it from model context.
@@ -31402,7 +31486,7 @@ ${cfg.instructions.trim()}` : "";
31402
31486
  // instructions as the workflow body (replacing the default code-implementation
31403
31487
  // phases). The SDK wraps it with read-only enforcement + ExitPlanMode protocol.
31404
31488
  planModeInstructions: (() => {
31405
- const planTools = [
31489
+ const planTools = (smithAgent ? ["AskUserQuestion", "Write (plan file only)"] : [
31406
31490
  ...nativeReadToolDisabled ? [] : ["Read"],
31407
31491
  "Glob",
31408
31492
  "Grep",
@@ -31410,19 +31494,19 @@ ${cfg.instructions.trim()}` : "";
31410
31494
  "WebFetch",
31411
31495
  "AskUserQuestion",
31412
31496
  "Write (plan file only)"
31413
- ].join(", ");
31414
- const researchTools = [
31497
+ ]).join(", ");
31498
+ const researchTools = (smithAgent ? ["AskUserQuestion"] : [
31415
31499
  ...nativeReadToolDisabled ? [] : ["Read"],
31416
31500
  "Grep",
31417
31501
  ...builtinWebSearchAllowed ? ["WebSearch"] : []
31418
- ].join(", ");
31419
- const unavailableTools = [
31502
+ ]).join(", ");
31503
+ const unavailableTools = (smithAgent ? ["Read", "Edit", "Bash", "Glob", "Grep", "WebFetch", "TodoWrite", "ExitPlanMode"] : [
31420
31504
  "Edit",
31421
31505
  "Bash",
31422
31506
  "TodoWrite",
31423
31507
  "ExitPlanMode",
31424
31508
  ...nativeReadToolDisabled ? ["Read"] : []
31425
- ].join(", ");
31509
+ ]).join(", ");
31426
31510
  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." : "";
31427
31511
  return `You are a PLANNER, NOT an executor. The user will execute your plan later.
31428
31512
 
@@ -31652,17 +31736,17 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
31652
31736
  }
31653
31737
  };
31654
31738
  const userPromptTrimmed = (agentConfig.systemPrompt ?? "").trim();
31655
- const appendStr = options.systemPrompt.append;
31656
31739
  logger14.info("Platform rules attached", {
31657
31740
  agentId: agentConfig.id,
31658
31741
  scope: scopeKey(scope),
31659
- platformRulesLen: PLATFORM_AGENT_RULES.length,
31742
+ systemPromptMode: smithAgent ? "string" : "preset",
31743
+ platformRulesLen: platformRules.length,
31660
31744
  userPromptLen: userPromptTrimmed.length,
31661
31745
  hasUserPrompt: userPromptTrimmed.length > 0,
31662
31746
  notebookLen: notebookSection.length,
31663
31747
  forkHistoryLen: forkHistorySection.length,
31664
31748
  scopesLen: scopesSection.length,
31665
- appendLen: appendStr.length,
31749
+ appendLen: appendText.length,
31666
31750
  hasCreateAgentTool: smithAgent,
31667
31751
  hasLogDetectiveTools: smithAgent && this.skillStore !== null
31668
31752
  });
@@ -31732,6 +31816,7 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
31732
31816
  mcpAuditRecorder: this.mcpAuditRecorder,
31733
31817
  segmentBuffer: "",
31734
31818
  segmentCount: 0,
31819
+ workdirSignals: [],
31735
31820
  accumulatedToolInput: "",
31736
31821
  planModeRef,
31737
31822
  mediaGenerationTurnGuard,
@@ -32765,6 +32850,7 @@ ${lines.join("\n")}`;
32765
32850
  proc.currentToolName = null;
32766
32851
  proc.segmentBuffer = "";
32767
32852
  proc.segmentCount = 0;
32853
+ proc.workdirSignals = [];
32768
32854
  }
32769
32855
  clearPostMergeContinuationTimer(runtime) {
32770
32856
  if (runtime.postMergeContinuationTimer) {
@@ -35535,6 +35621,7 @@ var HttpMcpRegistry = class {
35535
35621
  localStore;
35536
35622
  serverConnections = /* @__PURE__ */ new Map();
35537
35623
  localConnections = /* @__PURE__ */ new Map();
35624
+ missingSecretWarnedKeys = /* @__PURE__ */ new Set();
35538
35625
  lastRefreshCount = null;
35539
35626
  apiUrl(suffix) {
35540
35627
  const base = this.serverApiUrl.replace(/\/$/, "");
@@ -35635,13 +35722,19 @@ var HttpMcpRegistry = class {
35635
35722
  return false;
35636
35723
  });
35637
35724
  }
35725
+ warnMissingSecretOnce(connection) {
35726
+ const key = `${connection.id}:${connection.providerId}:${connection.serverName}`;
35727
+ if (this.missingSecretWarnedKeys.has(key)) return;
35728
+ this.missingSecretWarnedKeys.add(key);
35729
+ logger19.warn("Skipping MCP connection without required secret", {
35730
+ id: connection.id,
35731
+ providerId: connection.providerId,
35732
+ serverName: connection.serverName
35733
+ });
35734
+ }
35638
35735
  toSdkServerConfig(connection, ctx) {
35639
35736
  if (mcpConnectionRequiresSecret(connection) && !connection.hasAuthSecret) {
35640
- logger19.warn("Skipping MCP connection without required secret", {
35641
- id: connection.id,
35642
- providerId: connection.providerId,
35643
- serverName: connection.serverName
35644
- });
35737
+ this.warnMissingSecretOnce(connection);
35645
35738
  return null;
35646
35739
  }
35647
35740
  const policies = toolPolicies(connection);
@@ -37828,8 +37921,47 @@ import fs14 from "fs";
37828
37921
  import fsp from "fs/promises";
37829
37922
  import path21 from "path";
37830
37923
  var logger28 = createModuleLogger("bridge.logUploader");
37924
+ var STALE_RATE_LIMIT_SKIP_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
37831
37925
  var DEFAULT_LOG_UPLOAD_INTERVAL_MS = 24 * 60 * 60 * 1e3;
37832
37926
  var DEFAULT_BATCH_SIZE = 200;
37927
+ var MAX_UPLOAD_CHUNK_BODY_BYTES = 448 * 1024;
37928
+ var MAX_UPLOAD_RETRY_AFTER_MS = 24 * 60 * 60 * 1e3;
37929
+ var LogUploadHttpError = class extends Error {
37930
+ status;
37931
+ scope;
37932
+ retryAfterMs;
37933
+ constructor(status, bodyText) {
37934
+ const parsed = parseUploadErrorBody(bodyText);
37935
+ const summary = parsed.error ?? bodyText;
37936
+ super(`upload failed HTTP ${status}: ${summary.slice(0, 160)}`);
37937
+ this.name = "LogUploadHttpError";
37938
+ this.status = status;
37939
+ if (parsed.scope) this.scope = parsed.scope;
37940
+ if (parsed.retryAfterMs !== void 0) this.retryAfterMs = parsed.retryAfterMs;
37941
+ }
37942
+ };
37943
+ function normalizeRetryAfterMs(value) {
37944
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
37945
+ return Math.min(Math.ceil(value), MAX_UPLOAD_RETRY_AFTER_MS);
37946
+ }
37947
+ function parseUploadErrorBody(bodyText) {
37948
+ try {
37949
+ const parsed = JSON.parse(bodyText);
37950
+ if (typeof parsed !== "object" || parsed === null) return {};
37951
+ const body = parsed;
37952
+ return {
37953
+ ...typeof body.error === "string" && body.error.length > 0 ? { error: body.error } : {},
37954
+ ...typeof body.scope === "string" && body.scope.length > 0 ? { scope: body.scope } : {},
37955
+ ...(() => {
37956
+ const retryAfterMs = normalizeRetryAfterMs(body.retryAfterMs);
37957
+ return retryAfterMs === void 0 ? {} : { retryAfterMs };
37958
+ })()
37959
+ };
37960
+ } catch (e) {
37961
+ logger28.debug("Failed to parse log upload error body", { error: e });
37962
+ return {};
37963
+ }
37964
+ }
37833
37965
  function defaultLogFile(dataDir) {
37834
37966
  return path21.join(dataDir, "logs", "bridge.log");
37835
37967
  }
@@ -37919,26 +38051,38 @@ function parseProcessedLines(raw, cursor, fileName, fingerprint) {
37919
38051
  return { entries: [], nextCursor: cursor, advanced: false, reason: "partial_line" };
37920
38052
  }
37921
38053
  const processed = raw.slice(0, lastNewline + 1);
37922
- const lines = processed.split(/\r?\n/);
38054
+ const linePattern = /.*?\n/g;
37923
38055
  const entries = [];
37924
38056
  let lineNum = cursor.lineNum;
37925
- for (let i = 0; i < lines.length - 1; i += 1) {
37926
- const line = lines[i] ?? "";
38057
+ let offset = cursor.offset;
38058
+ let match;
38059
+ while ((match = linePattern.exec(processed)) !== null) {
38060
+ const rawLine = match[0];
38061
+ const line = rawLine.endsWith("\r\n") ? rawLine.slice(0, -2) : rawLine.slice(0, -1);
37927
38062
  lineNum += 1;
38063
+ offset += Buffer.byteLength(rawLine, "utf8");
38064
+ const cursorAfter = {
38065
+ offset,
38066
+ lineNum,
38067
+ ...fingerprint ? { fingerprint } : {}
38068
+ };
37928
38069
  if (line.length === 0) continue;
37929
38070
  const parsed = parseLogLine(line);
37930
38071
  if (!parsed) continue;
37931
38072
  entries.push({
37932
- ...parsed,
37933
- raw: line,
37934
- file: fileName,
37935
- lineNum
38073
+ entry: {
38074
+ ...parsed,
38075
+ raw: line,
38076
+ file: fileName,
38077
+ lineNum
38078
+ },
38079
+ cursorAfter
37936
38080
  });
37937
38081
  }
37938
38082
  return {
37939
38083
  entries,
37940
38084
  nextCursor: {
37941
- offset: cursor.offset + Buffer.byteLength(processed, "utf8"),
38085
+ offset,
37942
38086
  lineNum,
37943
38087
  ...fingerprint ? { fingerprint } : {}
37944
38088
  },
@@ -37946,15 +38090,35 @@ function parseProcessedLines(raw, cursor, fileName, fingerprint) {
37946
38090
  reason: "advanced"
37947
38091
  };
37948
38092
  }
37949
- function chunkEntries(entries, size) {
37950
- const chunks = [];
37951
- for (let i = 0; i < entries.length; i += size) {
37952
- chunks.push(entries.slice(i, i + size));
38093
+ function logEntryTimeRange(entries) {
38094
+ let minTime = Number.POSITIVE_INFINITY;
38095
+ let maxTime = Number.NEGATIVE_INFINITY;
38096
+ let minTs = "";
38097
+ let maxTs = "";
38098
+ for (const entry of entries) {
38099
+ const parsed = Date.parse(entry.ts);
38100
+ if (!Number.isFinite(parsed)) return null;
38101
+ if (parsed < minTime) {
38102
+ minTime = parsed;
38103
+ minTs = entry.ts;
38104
+ }
38105
+ if (parsed > maxTime) {
38106
+ maxTime = parsed;
38107
+ maxTs = entry.ts;
38108
+ }
37953
38109
  }
37954
- return chunks;
38110
+ if (!minTs || !maxTs) return null;
38111
+ return { minTs, maxTs, maxTime };
38112
+ }
38113
+ function isStaleRateLimitedChunk(entries, nowMs) {
38114
+ const range = logEntryTimeRange(entries);
38115
+ if (!range) return null;
38116
+ if (nowMs - range.maxTime <= STALE_RATE_LIMIT_SKIP_AGE_MS) return null;
38117
+ return { minTs: range.minTs, maxTs: range.maxTs };
37955
38118
  }
37956
38119
  var BridgeLogUploader = class {
37957
38120
  options;
38121
+ retryAfterByTarget = /* @__PURE__ */ new Map();
37958
38122
  timer = null;
37959
38123
  running = false;
37960
38124
  stopped = false;
@@ -37971,7 +38135,7 @@ var BridgeLogUploader = class {
37971
38135
  bridgeId: options.bridgeId,
37972
38136
  hostname: options.hostname,
37973
38137
  intervalMs: options.intervalMs ?? DEFAULT_LOG_UPLOAD_INTERVAL_MS,
37974
- batchSize: options.batchSize ?? DEFAULT_BATCH_SIZE,
38138
+ batchSize: Math.max(1, Math.floor(options.batchSize ?? DEFAULT_BATCH_SIZE)),
37975
38139
  primaryTarget,
37976
38140
  extraTargets: (options.extraLogFiles ?? []).map((file2) => normalizeTarget(file2, options.dataDir)),
37977
38141
  includeRotatedBridgeLogs: options.includeRotatedBridgeLogs ?? true
@@ -38013,13 +38177,26 @@ var BridgeLogUploader = class {
38013
38177
  bridgeEntryCount: 0,
38014
38178
  uploadedChunkCount: 0,
38015
38179
  accepted: 0,
38016
- skipped: 0
38180
+ skipped: 0,
38181
+ dropped: 0
38017
38182
  };
38018
38183
  try {
38019
38184
  const targets = await this.resolveTargets();
38020
38185
  summary.targetCount = targets.length;
38021
38186
  for (const target of targets) {
38187
+ const targetKey = this.targetKey(target);
38022
38188
  try {
38189
+ const retryUntil = this.retryAfterByTarget.get(targetKey);
38190
+ const now = Date.now();
38191
+ if (retryUntil && retryUntil > now) {
38192
+ summary.idleTargetCount += 1;
38193
+ logger28.debug("Bridge log upload target backoff active", {
38194
+ logFile: target.logFile,
38195
+ retryAfterMs: retryUntil - now
38196
+ });
38197
+ continue;
38198
+ }
38199
+ if (retryUntil) this.retryAfterByTarget.delete(targetKey);
38023
38200
  const cursor = await readCursor(target.cursorFile);
38024
38201
  const batch = await this.readNewEntries(target, cursor);
38025
38202
  if (!batch.advanced) {
@@ -38031,28 +38208,45 @@ var BridgeLogUploader = class {
38031
38208
  summary.advancedTargetCount += 1;
38032
38209
  summary.parsedEntryCount += batch.entries.length;
38033
38210
  if (batch.entries.length > 0) {
38034
- const result = await this.uploadEntries(batch.entries);
38211
+ const result = await this.uploadEntries(batch.entries, target.cursorFile, batch.nextCursor);
38035
38212
  summary.bridgeEntryCount += result.bridgeEntryCount;
38036
38213
  summary.uploadedChunkCount += result.uploadedChunkCount;
38037
38214
  summary.accepted += result.accepted;
38038
38215
  summary.skipped += result.skipped;
38216
+ summary.dropped += result.dropped;
38217
+ } else {
38218
+ await writeCursor(target.cursorFile, batch.nextCursor);
38039
38219
  }
38040
- await writeCursor(target.cursorFile, batch.nextCursor);
38220
+ this.retryAfterByTarget.delete(targetKey);
38041
38221
  } catch (e) {
38042
38222
  summary.failedTargetCount += 1;
38043
- logger28.warn("Bridge log upload target failed", { error: e, logFile: target.logFile });
38223
+ if (e instanceof LogUploadHttpError && e.retryAfterMs !== void 0) {
38224
+ this.retryAfterByTarget.set(targetKey, Date.now() + e.retryAfterMs);
38225
+ logger28.warn("Bridge log upload target failed", {
38226
+ error: e,
38227
+ logFile: target.logFile,
38228
+ status: e.status,
38229
+ quotaScope: e.scope,
38230
+ retryAfterMs: e.retryAfterMs
38231
+ });
38232
+ } else {
38233
+ logger28.warn("Bridge log upload target failed", { error: e, logFile: target.logFile });
38234
+ }
38044
38235
  }
38045
38236
  }
38046
38237
  } catch (e) {
38047
38238
  logger28.warn("Bridge log upload cycle failed", { error: e });
38048
38239
  } finally {
38049
- logger28.info("Bridge log upload cycle summary", {
38240
+ logger28.debug("Bridge log upload cycle summary", {
38050
38241
  ...summary,
38051
38242
  durationMs: Date.now() - startedAt
38052
38243
  });
38053
38244
  this.running = false;
38054
38245
  }
38055
38246
  }
38247
+ targetKey(target) {
38248
+ return `${target.logFile}\0${target.cursorFile}`;
38249
+ }
38056
38250
  async resolveTargets() {
38057
38251
  const bridgeTargets = this.options.includeRotatedBridgeLogs ? await listRotatedBridgeTargets(this.options.primaryTarget, this.options.dataDir) : [this.options.primaryTarget];
38058
38252
  const seen = /* @__PURE__ */ new Set();
@@ -38085,15 +38279,40 @@ var BridgeLogUploader = class {
38085
38279
  const raw = await readStreamText(target.logFile, normalizedCursor.offset);
38086
38280
  return parseProcessedLines(raw, normalizedCursor, target.uploadedFileName, fingerprint);
38087
38281
  }
38088
- async uploadEntries(entries) {
38089
- const bridgeEntries = entries.filter((entry) => entry.source === "bridge");
38282
+ uploadBodyBytes(entries) {
38283
+ return Buffer.byteLength(JSON.stringify({
38284
+ bridgeId: this.options.bridgeId,
38285
+ hostname: this.options.hostname,
38286
+ entries
38287
+ }), "utf8");
38288
+ }
38289
+ chunkUploadEntries(entries) {
38290
+ const chunks = [];
38291
+ let current = [];
38292
+ for (const entry of entries) {
38293
+ const next = [...current, entry];
38294
+ const nextBodyBytes = this.uploadBodyBytes(next.map((item) => item.entry));
38295
+ if (current.length > 0 && (current.length >= this.options.batchSize || nextBodyBytes > MAX_UPLOAD_CHUNK_BODY_BYTES)) {
38296
+ chunks.push(current);
38297
+ current = [entry];
38298
+ } else {
38299
+ current = next;
38300
+ }
38301
+ }
38302
+ if (current.length > 0) chunks.push(current);
38303
+ return chunks;
38304
+ }
38305
+ async uploadEntries(entries, cursorFile, finalCursor) {
38306
+ const bridgeEntries = entries.filter((item) => item.entry.source === "bridge");
38090
38307
  const result = {
38091
38308
  bridgeEntryCount: bridgeEntries.length,
38092
38309
  uploadedChunkCount: 0,
38093
38310
  accepted: 0,
38094
- skipped: 0
38311
+ skipped: 0,
38312
+ dropped: 0
38095
38313
  };
38096
- for (const chunk of chunkEntries(bridgeEntries, this.options.batchSize)) {
38314
+ for (const chunk of this.chunkUploadEntries(bridgeEntries)) {
38315
+ const payloadEntries = chunk.map((item) => item.entry);
38097
38316
  const res = await fetch(`${this.options.serverApiUrl}/api/logs/upload`, {
38098
38317
  method: "POST",
38099
38318
  headers: {
@@ -38103,7 +38322,7 @@ var BridgeLogUploader = class {
38103
38322
  body: JSON.stringify({
38104
38323
  bridgeId: this.options.bridgeId,
38105
38324
  hostname: this.options.hostname,
38106
- entries: chunk
38325
+ entries: payloadEntries
38107
38326
  })
38108
38327
  });
38109
38328
  if (!res.ok) {
@@ -38111,13 +38330,27 @@ var BridgeLogUploader = class {
38111
38330
  logger28.debug("Failed to read log upload error body", { error: e });
38112
38331
  return "";
38113
38332
  });
38114
- throw new Error(`upload failed HTTP ${res.status}: ${body2.slice(0, 160)}`);
38333
+ const staleChunk = res.status === 429 ? isStaleRateLimitedChunk(payloadEntries, Date.now()) : null;
38334
+ if (staleChunk) {
38335
+ result.dropped += chunk.length;
38336
+ logger28.warn("Skipping stale bridge log upload chunk after rate limit", {
38337
+ status: res.status,
38338
+ entryCount: chunk.length,
38339
+ minEntryTs: staleChunk.minTs,
38340
+ maxEntryTs: staleChunk.maxTs
38341
+ });
38342
+ continue;
38343
+ }
38344
+ throw new LogUploadHttpError(res.status, body2);
38115
38345
  }
38116
38346
  const body = await res.json();
38117
38347
  result.uploadedChunkCount += 1;
38118
38348
  result.accepted += typeof body.accepted === "number" ? body.accepted : 0;
38119
38349
  result.skipped += typeof body.skipped === "number" ? body.skipped : 0;
38350
+ const last = chunk[chunk.length - 1];
38351
+ if (last) await writeCursor(cursorFile, last.cursorAfter);
38120
38352
  }
38353
+ await writeCursor(cursorFile, finalCursor);
38121
38354
  return result;
38122
38355
  }
38123
38356
  };
@@ -38200,7 +38433,7 @@ var SkillStore = class {
38200
38433
  if (!isNotFoundError(e)) logger29.warn("Skill seed existing read failed", { name, filePath, error: e });
38201
38434
  }
38202
38435
  if (existing === content) {
38203
- logger29.info("Skill already in sync", { name, bytes: content.length });
38436
+ logger29.debug("Skill already in sync", { name, bytes: content.length });
38204
38437
  return;
38205
38438
  }
38206
38439
  fs15.writeFileSync(tmpPath, content, "utf-8");