@fangyb/ahchat-bridge 0.1.39 → 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)",
@@ -28905,6 +28953,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
28905
28953
  const isMcpTool = parseMcpRuntimeToolName(toolName) != null;
28906
28954
  proc.currentMcpInvocationId = isMcpTool ? createMcpToolInvocationId() : null;
28907
28955
  proc.currentMcpInvocationStartedAt = isMcpTool ? (/* @__PURE__ */ new Date()).toISOString() : null;
28956
+ proc.currentMcpInvocationToolUseId = isMcpTool ? toolUseId ?? null : null;
28908
28957
  if (shouldStreamInternals(proc) && !proc.suppressCurrentToolUse && toolName !== "ExitPlanMode" && !isAskUserQuestionToolName(toolName)) {
28909
28958
  emit({
28910
28959
  type: "agent:tool_use",
@@ -28915,6 +28964,8 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
28915
28964
  input: {}
28916
28965
  }
28917
28966
  });
28967
+ }
28968
+ if (shouldStreamInternals(proc) && !proc.suppressCurrentToolUse && toolName !== "ExitPlanMode" && !isAskUserQuestionToolName(toolName)) {
28918
28969
  proc.contentBlocks.push({
28919
28970
  type: "tool_use",
28920
28971
  ...toolUseId ? { toolUseId } : {},
@@ -29091,6 +29142,14 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29091
29142
  }
29092
29143
  if (proc.currentToolName && !proc.suppressCurrentToolUse && proc.currentMcpInvocationId && proc.currentMcpInvocationStartedAt && parseMcpRuntimeToolName(proc.currentToolName)) {
29093
29144
  try {
29145
+ logger11.info("MCP audit start paired with tool_use", {
29146
+ agentId: proc.agentId,
29147
+ replyMessageId: base.replyMessageId,
29148
+ traceId: base.traceId,
29149
+ toolUseId: proc.currentMcpInvocationToolUseId ?? null,
29150
+ runtimeToolName: proc.currentToolName,
29151
+ mcpInvocationId: proc.currentMcpInvocationId
29152
+ });
29094
29153
  proc.mcpAuditRecorder?.recordStart({
29095
29154
  id: proc.currentMcpInvocationId,
29096
29155
  runtimeToolName: proc.currentToolName,
@@ -29171,6 +29230,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29171
29230
  });
29172
29231
  proc.currentMcpInvocationId = null;
29173
29232
  proc.currentMcpInvocationStartedAt = null;
29233
+ proc.currentMcpInvocationToolUseId = null;
29174
29234
  proc.activeToolUseStartedAt = void 0;
29175
29235
  proc.currentToolName = null;
29176
29236
  proc.currentToolUseId = null;
@@ -29187,6 +29247,16 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29187
29247
  }
29188
29248
  if (proc.currentMcpInvocationId && parseMcpRuntimeToolName(toolName)) {
29189
29249
  try {
29250
+ logger11.info("MCP audit result paired with tool_result", {
29251
+ agentId: proc.agentId,
29252
+ replyMessageId: base.replyMessageId,
29253
+ traceId: base.traceId,
29254
+ toolUseId,
29255
+ startedToolUseId: proc.currentMcpInvocationToolUseId ?? null,
29256
+ runtimeToolName: toolName,
29257
+ mcpInvocationId: proc.currentMcpInvocationId,
29258
+ isError
29259
+ });
29190
29260
  proc.mcpAuditRecorder?.recordResult({
29191
29261
  id: proc.currentMcpInvocationId,
29192
29262
  runtimeToolName: toolName,
@@ -29208,6 +29278,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29208
29278
  }
29209
29279
  proc.currentMcpInvocationId = null;
29210
29280
  proc.currentMcpInvocationStartedAt = null;
29281
+ proc.currentMcpInvocationToolUseId = null;
29211
29282
  }
29212
29283
  if (shouldStreamInternals(proc)) {
29213
29284
  emit({
@@ -29235,6 +29306,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29235
29306
  }
29236
29307
  }
29237
29308
  }
29309
+ recordWorkdirSignal(proc, toolName, isError);
29238
29310
  proc.activeToolUseStartedAt = void 0;
29239
29311
  proc.currentToolUseId = null;
29240
29312
  }
@@ -29296,7 +29368,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29296
29368
  if (isNoReplyText(trimmed, { allowSdkSyntheticNoResponse: groupMode })) {
29297
29369
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
29298
29370
  emitUsageReported(proc, emit, base, usage);
29299
- if (groupMode && proc.contentBlocks.length > 0) {
29371
+ if (groupMode && (proc.contentBlocks.length > 0 || (proc.workdirSignals?.length ?? 0) > 0)) {
29300
29372
  emitGroupSegment(
29301
29373
  proc,
29302
29374
  emit,
@@ -29341,11 +29413,12 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29341
29413
  proc.segmentBuffer = proc.accumulatedText;
29342
29414
  flushTextSegmentOnBlockStop(proc, emit, base);
29343
29415
  }
29344
- if (proc.contentBlocks.length > 0) {
29416
+ if (proc.contentBlocks.length > 0 || (proc.workdirSignals?.length ?? 0) > 0) {
29345
29417
  logger11.info("Group turn trailing audit segment", {
29346
29418
  agentId: proc.agentId,
29347
29419
  replyMessageId: base.replyMessageId,
29348
29420
  blockCount: proc.contentBlocks.length,
29421
+ workdirSignalCount: proc.workdirSignals?.length ?? 0,
29349
29422
  traceId: base.traceId
29350
29423
  });
29351
29424
  emitGroupSegment(proc, emit, base, "", proc.contentBlocks, true);
@@ -29490,6 +29563,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29490
29563
  payload: { ...wireBase(base), error: errorText }
29491
29564
  });
29492
29565
  proc.contentBlocks = [];
29566
+ proc.workdirSignals = [];
29493
29567
  proc.accumulatedText = "";
29494
29568
  proc.accumulatedThinking = "";
29495
29569
  proc.segmentBuffer = "";
@@ -29522,6 +29596,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29522
29596
  proc.apiErrorEmitted = true;
29523
29597
  }
29524
29598
  proc.contentBlocks = [];
29599
+ proc.workdirSignals = [];
29525
29600
  proc.accumulatedText = "";
29526
29601
  proc.accumulatedThinking = "";
29527
29602
  proc.segmentBuffer = "";
@@ -29548,6 +29623,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29548
29623
  proc.apiErrorEmitted = true;
29549
29624
  }
29550
29625
  proc.contentBlocks = [];
29626
+ proc.workdirSignals = [];
29551
29627
  proc.accumulatedText = "";
29552
29628
  proc.accumulatedThinking = "";
29553
29629
  proc.segmentBuffer = "";
@@ -29584,6 +29660,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onPost
29584
29660
  });
29585
29661
  }
29586
29662
  proc.contentBlocks = [];
29663
+ proc.workdirSignals = [];
29587
29664
  proc.accumulatedText = "";
29588
29665
  proc.accumulatedThinking = "";
29589
29666
  proc.segmentBuffer = "";
@@ -29621,9 +29698,11 @@ function resetAccumulators(proc) {
29621
29698
  proc.currentToolUseId = null;
29622
29699
  proc.currentMcpInvocationId = null;
29623
29700
  proc.currentMcpInvocationStartedAt = null;
29701
+ proc.currentMcpInvocationToolUseId = null;
29624
29702
  proc.activeToolUseStartedAt = void 0;
29625
29703
  proc.segmentBuffer = "";
29626
29704
  proc.segmentCount = 0;
29705
+ proc.workdirSignals = [];
29627
29706
  proc.accumulatedToolInput = "";
29628
29707
  proc.apiErrorEmitted = false;
29629
29708
  proc.peakContextUsage = void 0;
@@ -29819,7 +29898,7 @@ function missingSubscriptionMessage(subscriptionId) {
29819
29898
  }
29820
29899
  var NODE_USER_UID = 1e3;
29821
29900
  var POST_MERGE_CONTINUATION_ROUTE_MS = 15e3;
29822
- var SCOPE_PROMPT_FINGERPRINT_REVISION = "workdir-scope-mcp-abi-prompt-v4";
29901
+ var SCOPE_PROMPT_FINGERPRINT_REVISION = "workdir-scope-mcp-abi-prompt-v6";
29823
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;
29824
29903
  var IMAGE_READ_EXT_RE = /\.(?:bmp|gif|jpeg|jpg|png|webp)$/i;
29825
29904
  var DOCUMENT_READING_RULES = `DOCUMENT READING:
@@ -29835,6 +29914,31 @@ var MEDIA_GENERATION_RULES = `MEDIA GENERATION:
29835
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.
29836
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.
29837
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
+ ];
29838
29942
  function resolveVisionMcpToolHints(externalMcp) {
29839
29943
  let describe3 = null;
29840
29944
  let ocr = null;
@@ -31099,7 +31203,7 @@ ${cfg.instructions.trim()}` : "";
31099
31203
  } catch (error51) {
31100
31204
  logger14.error("Failed to read existing API-key agent settings; starting fresh", {
31101
31205
  agentId: agentConfig.id,
31102
- settingsPath,
31206
+ settingsFile: "settings.json",
31103
31207
  error: error51
31104
31208
  });
31105
31209
  }
@@ -31112,7 +31216,7 @@ ${cfg.instructions.trim()}` : "";
31112
31216
  await fs6.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
31113
31217
  logger14.info("API-key agent using isolated config dir", {
31114
31218
  agentId: agentConfig.id,
31115
- dir: effectiveConfigDir,
31219
+ configDirKind: "api-key-agent",
31116
31220
  isNew,
31117
31221
  hasBaseUrl: !!cfg.apiBaseUrl,
31118
31222
  settingsWritten: Object.keys(envEntries)
@@ -31206,12 +31310,21 @@ ${cfg.instructions.trim()}` : "";
31206
31310
  });
31207
31311
  }
31208
31312
  }
31209
- const externalMcp = this.mcpRegistry?.buildForAgent({
31313
+ const resolvedExternalMcp = this.mcpRegistry?.buildForAgent({
31210
31314
  agentId: agentConfig.id,
31211
31315
  capabilityTier: cfg.capabilityTier,
31212
31316
  isSmith: smithAgent,
31213
31317
  cwd: agentCwd
31214
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
+ }
31215
31328
  const visionMcpTools = resolveVisionMcpToolHints(externalMcp);
31216
31329
  logger14.info("External MCP resolved for runtime", {
31217
31330
  agentId: agentConfig.id,
@@ -31284,6 +31397,7 @@ ${cfg.instructions.trim()}` : "";
31284
31397
  logger14.info("Creating Agent query", {
31285
31398
  agentId: agentConfig.id,
31286
31399
  scope: scopeKey(scope),
31400
+ systemPromptMode: smithAgent ? "string" : "preset",
31287
31401
  cwd: agentCwd,
31288
31402
  resume: !!savedSessionId,
31289
31403
  sessionId: savedSessionId,
@@ -31304,71 +31418,63 @@ ${cfg.instructions.trim()}` : "";
31304
31418
  });
31305
31419
  const planModeRef = { active: false, denyCount: 0 };
31306
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
+ ];
31307
31468
  const options = {
31308
31469
  cwd: agentCwd,
31309
- systemPrompt: {
31310
- type: "preset",
31311
- preset: "claude_code",
31312
- append: [
31313
- PLATFORM_AGENT_RULES,
31314
- nativeReadToolDisabled ? buildNativeReadDisabledRules(visionMcpTools) : "",
31315
- DOCUMENT_READING_RULES,
31316
- MEDIA_GENERATION_RULES,
31317
- agentConfig.systemPrompt,
31318
- scopedInstructions,
31319
- notebookSection,
31320
- forkHistorySection,
31321
- scopesSection
31322
- ].filter((s) => typeof s === "string" && s.trim().length > 0).join("\n\n")
31323
- },
31470
+ systemPrompt,
31324
31471
  permissionMode: "bypassPermissions",
31325
31472
  allowDangerouslySkipPermissions: true,
31326
31473
  // allowedTools is the visibility whitelist passed to Claude Code. MCP tools
31327
31474
  // with always_ask must still be included here so the model can request them;
31328
31475
  // the MCP server policy/permission layer decides whether execution asks.
31329
31476
  allowedTools: [
31330
- ...nativeReadToolDisabled ? [] : ["Read"],
31331
- "Edit",
31332
- "Write",
31333
- "Bash",
31334
- "Glob",
31335
- "Grep",
31336
- ...builtinWebSearchAllowed ? ["WebSearch"] : [],
31337
- "WebFetch",
31338
- "TodoWrite",
31339
- "TaskCreate",
31340
- "TaskUpdate",
31341
- "AskUserQuestion",
31342
- "mcp__neural__neural_send",
31343
- "mcp__neural__neural_list_scopes",
31344
- "mcp__neural__self_note",
31345
- "mcp__neural__list_contacts",
31346
- "mcp__neural__create_group",
31347
- "mcp__neural__add_to_group",
31348
- "mcp__neural__leave_group",
31349
- "mcp__neural__remove_from_group",
31350
- "mcp__neural__create_group_issue",
31351
- "mcp__neural__resolve_group_issue",
31352
- "mcp__neural__list_group_tasks",
31353
- "mcp__neural__update_group_task",
31354
- "mcp__neural__transfer_group_owner",
31355
- "mcp__neural__post_to_moments",
31356
- "mcp__neural__post_to_forum",
31357
- "mcp__neural__read_moments",
31358
- "mcp__neural__read_chat_history",
31359
- "mcp__neural__read_document",
31360
- "mcp__neural__list_available_skills",
31361
- "mcp__neural__list_skill_index",
31362
- ...isSmithAgent2(agentConfig) ? [
31363
- "mcp__neural__create_agent",
31364
- "mcp__neural__update_agent_profile",
31365
- "mcp__neural__recommend_agent_skills",
31366
- "mcp__neural__read_skill",
31367
- "mcp__neural__list_friends",
31368
- "mcp__neural__accept_friend",
31369
- "mcp__neural__add_friend",
31370
- "mcp__neural__fetch_logs"
31371
- ] : [],
31477
+ ...smithAgent ? SMITH_ALLOWED_TOOLS : universalAllowedTools,
31372
31478
  ...externalMcp.allowedTools
31373
31479
  ],
31374
31480
  // Server-side WebSearch bypasses canUseTool; disallowedTools removes it from model context.
@@ -31380,7 +31486,7 @@ ${cfg.instructions.trim()}` : "";
31380
31486
  // instructions as the workflow body (replacing the default code-implementation
31381
31487
  // phases). The SDK wraps it with read-only enforcement + ExitPlanMode protocol.
31382
31488
  planModeInstructions: (() => {
31383
- const planTools = [
31489
+ const planTools = (smithAgent ? ["AskUserQuestion", "Write (plan file only)"] : [
31384
31490
  ...nativeReadToolDisabled ? [] : ["Read"],
31385
31491
  "Glob",
31386
31492
  "Grep",
@@ -31388,19 +31494,19 @@ ${cfg.instructions.trim()}` : "";
31388
31494
  "WebFetch",
31389
31495
  "AskUserQuestion",
31390
31496
  "Write (plan file only)"
31391
- ].join(", ");
31392
- const researchTools = [
31497
+ ]).join(", ");
31498
+ const researchTools = (smithAgent ? ["AskUserQuestion"] : [
31393
31499
  ...nativeReadToolDisabled ? [] : ["Read"],
31394
31500
  "Grep",
31395
31501
  ...builtinWebSearchAllowed ? ["WebSearch"] : []
31396
- ].join(", ");
31397
- const unavailableTools = [
31502
+ ]).join(", ");
31503
+ const unavailableTools = (smithAgent ? ["Read", "Edit", "Bash", "Glob", "Grep", "WebFetch", "TodoWrite", "ExitPlanMode"] : [
31398
31504
  "Edit",
31399
31505
  "Bash",
31400
31506
  "TodoWrite",
31401
31507
  "ExitPlanMode",
31402
31508
  ...nativeReadToolDisabled ? ["Read"] : []
31403
- ].join(", ");
31509
+ ]).join(", ");
31404
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." : "";
31405
31511
  return `You are a PLANNER, NOT an executor. The user will execute your plan later.
31406
31512
 
@@ -31630,17 +31736,17 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
31630
31736
  }
31631
31737
  };
31632
31738
  const userPromptTrimmed = (agentConfig.systemPrompt ?? "").trim();
31633
- const appendStr = options.systemPrompt.append;
31634
31739
  logger14.info("Platform rules attached", {
31635
31740
  agentId: agentConfig.id,
31636
31741
  scope: scopeKey(scope),
31637
- platformRulesLen: PLATFORM_AGENT_RULES.length,
31742
+ systemPromptMode: smithAgent ? "string" : "preset",
31743
+ platformRulesLen: platformRules.length,
31638
31744
  userPromptLen: userPromptTrimmed.length,
31639
31745
  hasUserPrompt: userPromptTrimmed.length > 0,
31640
31746
  notebookLen: notebookSection.length,
31641
31747
  forkHistoryLen: forkHistorySection.length,
31642
31748
  scopesLen: scopesSection.length,
31643
- appendLen: appendStr.length,
31749
+ appendLen: appendText.length,
31644
31750
  hasCreateAgentTool: smithAgent,
31645
31751
  hasLogDetectiveTools: smithAgent && this.skillStore !== null
31646
31752
  });
@@ -31710,6 +31816,7 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
31710
31816
  mcpAuditRecorder: this.mcpAuditRecorder,
31711
31817
  segmentBuffer: "",
31712
31818
  segmentCount: 0,
31819
+ workdirSignals: [],
31713
31820
  accumulatedToolInput: "",
31714
31821
  planModeRef,
31715
31822
  mediaGenerationTurnGuard,
@@ -32743,6 +32850,7 @@ ${lines.join("\n")}`;
32743
32850
  proc.currentToolName = null;
32744
32851
  proc.segmentBuffer = "";
32745
32852
  proc.segmentCount = 0;
32853
+ proc.workdirSignals = [];
32746
32854
  }
32747
32855
  clearPostMergeContinuationTimer(runtime) {
32748
32856
  if (runtime.postMergeContinuationTimer) {
@@ -35513,6 +35621,7 @@ var HttpMcpRegistry = class {
35513
35621
  localStore;
35514
35622
  serverConnections = /* @__PURE__ */ new Map();
35515
35623
  localConnections = /* @__PURE__ */ new Map();
35624
+ missingSecretWarnedKeys = /* @__PURE__ */ new Set();
35516
35625
  lastRefreshCount = null;
35517
35626
  apiUrl(suffix) {
35518
35627
  const base = this.serverApiUrl.replace(/\/$/, "");
@@ -35613,13 +35722,19 @@ var HttpMcpRegistry = class {
35613
35722
  return false;
35614
35723
  });
35615
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
+ }
35616
35735
  toSdkServerConfig(connection, ctx) {
35617
35736
  if (mcpConnectionRequiresSecret(connection) && !connection.hasAuthSecret) {
35618
- logger19.warn("Skipping MCP connection without required secret", {
35619
- id: connection.id,
35620
- providerId: connection.providerId,
35621
- serverName: connection.serverName
35622
- });
35737
+ this.warnMissingSecretOnce(connection);
35623
35738
  return null;
35624
35739
  }
35625
35740
  const policies = toolPolicies(connection);
@@ -37806,8 +37921,47 @@ import fs14 from "fs";
37806
37921
  import fsp from "fs/promises";
37807
37922
  import path21 from "path";
37808
37923
  var logger28 = createModuleLogger("bridge.logUploader");
37924
+ var STALE_RATE_LIMIT_SKIP_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
37809
37925
  var DEFAULT_LOG_UPLOAD_INTERVAL_MS = 24 * 60 * 60 * 1e3;
37810
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
+ }
37811
37965
  function defaultLogFile(dataDir) {
37812
37966
  return path21.join(dataDir, "logs", "bridge.log");
37813
37967
  }
@@ -37897,26 +38051,38 @@ function parseProcessedLines(raw, cursor, fileName, fingerprint) {
37897
38051
  return { entries: [], nextCursor: cursor, advanced: false, reason: "partial_line" };
37898
38052
  }
37899
38053
  const processed = raw.slice(0, lastNewline + 1);
37900
- const lines = processed.split(/\r?\n/);
38054
+ const linePattern = /.*?\n/g;
37901
38055
  const entries = [];
37902
38056
  let lineNum = cursor.lineNum;
37903
- for (let i = 0; i < lines.length - 1; i += 1) {
37904
- 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);
37905
38062
  lineNum += 1;
38063
+ offset += Buffer.byteLength(rawLine, "utf8");
38064
+ const cursorAfter = {
38065
+ offset,
38066
+ lineNum,
38067
+ ...fingerprint ? { fingerprint } : {}
38068
+ };
37906
38069
  if (line.length === 0) continue;
37907
38070
  const parsed = parseLogLine(line);
37908
38071
  if (!parsed) continue;
37909
38072
  entries.push({
37910
- ...parsed,
37911
- raw: line,
37912
- file: fileName,
37913
- lineNum
38073
+ entry: {
38074
+ ...parsed,
38075
+ raw: line,
38076
+ file: fileName,
38077
+ lineNum
38078
+ },
38079
+ cursorAfter
37914
38080
  });
37915
38081
  }
37916
38082
  return {
37917
38083
  entries,
37918
38084
  nextCursor: {
37919
- offset: cursor.offset + Buffer.byteLength(processed, "utf8"),
38085
+ offset,
37920
38086
  lineNum,
37921
38087
  ...fingerprint ? { fingerprint } : {}
37922
38088
  },
@@ -37924,15 +38090,35 @@ function parseProcessedLines(raw, cursor, fileName, fingerprint) {
37924
38090
  reason: "advanced"
37925
38091
  };
37926
38092
  }
37927
- function chunkEntries(entries, size) {
37928
- const chunks = [];
37929
- for (let i = 0; i < entries.length; i += size) {
37930
- 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
+ }
37931
38109
  }
37932
- 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 };
37933
38118
  }
37934
38119
  var BridgeLogUploader = class {
37935
38120
  options;
38121
+ retryAfterByTarget = /* @__PURE__ */ new Map();
37936
38122
  timer = null;
37937
38123
  running = false;
37938
38124
  stopped = false;
@@ -37949,7 +38135,7 @@ var BridgeLogUploader = class {
37949
38135
  bridgeId: options.bridgeId,
37950
38136
  hostname: options.hostname,
37951
38137
  intervalMs: options.intervalMs ?? DEFAULT_LOG_UPLOAD_INTERVAL_MS,
37952
- batchSize: options.batchSize ?? DEFAULT_BATCH_SIZE,
38138
+ batchSize: Math.max(1, Math.floor(options.batchSize ?? DEFAULT_BATCH_SIZE)),
37953
38139
  primaryTarget,
37954
38140
  extraTargets: (options.extraLogFiles ?? []).map((file2) => normalizeTarget(file2, options.dataDir)),
37955
38141
  includeRotatedBridgeLogs: options.includeRotatedBridgeLogs ?? true
@@ -37991,13 +38177,26 @@ var BridgeLogUploader = class {
37991
38177
  bridgeEntryCount: 0,
37992
38178
  uploadedChunkCount: 0,
37993
38179
  accepted: 0,
37994
- skipped: 0
38180
+ skipped: 0,
38181
+ dropped: 0
37995
38182
  };
37996
38183
  try {
37997
38184
  const targets = await this.resolveTargets();
37998
38185
  summary.targetCount = targets.length;
37999
38186
  for (const target of targets) {
38187
+ const targetKey = this.targetKey(target);
38000
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);
38001
38200
  const cursor = await readCursor(target.cursorFile);
38002
38201
  const batch = await this.readNewEntries(target, cursor);
38003
38202
  if (!batch.advanced) {
@@ -38009,28 +38208,45 @@ var BridgeLogUploader = class {
38009
38208
  summary.advancedTargetCount += 1;
38010
38209
  summary.parsedEntryCount += batch.entries.length;
38011
38210
  if (batch.entries.length > 0) {
38012
- const result = await this.uploadEntries(batch.entries);
38211
+ const result = await this.uploadEntries(batch.entries, target.cursorFile, batch.nextCursor);
38013
38212
  summary.bridgeEntryCount += result.bridgeEntryCount;
38014
38213
  summary.uploadedChunkCount += result.uploadedChunkCount;
38015
38214
  summary.accepted += result.accepted;
38016
38215
  summary.skipped += result.skipped;
38216
+ summary.dropped += result.dropped;
38217
+ } else {
38218
+ await writeCursor(target.cursorFile, batch.nextCursor);
38017
38219
  }
38018
- await writeCursor(target.cursorFile, batch.nextCursor);
38220
+ this.retryAfterByTarget.delete(targetKey);
38019
38221
  } catch (e) {
38020
38222
  summary.failedTargetCount += 1;
38021
- 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
+ }
38022
38235
  }
38023
38236
  }
38024
38237
  } catch (e) {
38025
38238
  logger28.warn("Bridge log upload cycle failed", { error: e });
38026
38239
  } finally {
38027
- logger28.info("Bridge log upload cycle summary", {
38240
+ logger28.debug("Bridge log upload cycle summary", {
38028
38241
  ...summary,
38029
38242
  durationMs: Date.now() - startedAt
38030
38243
  });
38031
38244
  this.running = false;
38032
38245
  }
38033
38246
  }
38247
+ targetKey(target) {
38248
+ return `${target.logFile}\0${target.cursorFile}`;
38249
+ }
38034
38250
  async resolveTargets() {
38035
38251
  const bridgeTargets = this.options.includeRotatedBridgeLogs ? await listRotatedBridgeTargets(this.options.primaryTarget, this.options.dataDir) : [this.options.primaryTarget];
38036
38252
  const seen = /* @__PURE__ */ new Set();
@@ -38063,15 +38279,40 @@ var BridgeLogUploader = class {
38063
38279
  const raw = await readStreamText(target.logFile, normalizedCursor.offset);
38064
38280
  return parseProcessedLines(raw, normalizedCursor, target.uploadedFileName, fingerprint);
38065
38281
  }
38066
- async uploadEntries(entries) {
38067
- 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");
38068
38307
  const result = {
38069
38308
  bridgeEntryCount: bridgeEntries.length,
38070
38309
  uploadedChunkCount: 0,
38071
38310
  accepted: 0,
38072
- skipped: 0
38311
+ skipped: 0,
38312
+ dropped: 0
38073
38313
  };
38074
- 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);
38075
38316
  const res = await fetch(`${this.options.serverApiUrl}/api/logs/upload`, {
38076
38317
  method: "POST",
38077
38318
  headers: {
@@ -38081,7 +38322,7 @@ var BridgeLogUploader = class {
38081
38322
  body: JSON.stringify({
38082
38323
  bridgeId: this.options.bridgeId,
38083
38324
  hostname: this.options.hostname,
38084
- entries: chunk
38325
+ entries: payloadEntries
38085
38326
  })
38086
38327
  });
38087
38328
  if (!res.ok) {
@@ -38089,13 +38330,27 @@ var BridgeLogUploader = class {
38089
38330
  logger28.debug("Failed to read log upload error body", { error: e });
38090
38331
  return "";
38091
38332
  });
38092
- 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);
38093
38345
  }
38094
38346
  const body = await res.json();
38095
38347
  result.uploadedChunkCount += 1;
38096
38348
  result.accepted += typeof body.accepted === "number" ? body.accepted : 0;
38097
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);
38098
38352
  }
38353
+ await writeCursor(cursorFile, finalCursor);
38099
38354
  return result;
38100
38355
  }
38101
38356
  };
@@ -38178,7 +38433,7 @@ var SkillStore = class {
38178
38433
  if (!isNotFoundError(e)) logger29.warn("Skill seed existing read failed", { name, filePath, error: e });
38179
38434
  }
38180
38435
  if (existing === content) {
38181
- logger29.info("Skill already in sync", { name, bytes: content.length });
38436
+ logger29.debug("Skill already in sync", { name, bytes: content.length });
38182
38437
  return;
38183
38438
  }
38184
38439
  fs15.writeFileSync(tmpPath, content, "utf-8");