@fangyb/ahchat-bridge 0.1.25 → 0.1.26

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.
Files changed (3) hide show
  1. package/dist/cli.cjs +245 -51
  2. package/dist/index.js +244 -48
  3. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -62227,6 +62227,14 @@ runtime\u3002\u5B83\u4EEC\u4E0D\u662F"\u4E0D\u540C\u7684\u4EBA"\u2014\u2014\u516
62227
62227
  \`# \u3010\u6807\u9898\u3011\` (a single \`#\` followed by Chinese bracket-enclosed title).
62228
62228
  For single-topic or short conversational replies, omit headings entirely.
62229
62229
 
62230
+ # AskUserQuestion tool
62231
+ - When you need the user to choose from options or answer a clarification question, call the real
62232
+ \`AskUserQuestion\` tool with a \`questions\` array.
62233
+ - Never output \`<AskUserQuestion>\`, \`</AskUserQuestion>\`, or raw JSON question payloads as chat text.
62234
+ Text like that is not an interactive panel and the user cannot answer it through the tool flow.
62235
+ - If you are not able to call \`AskUserQuestion\`, ask the question as plain natural language instead of
62236
+ emitting tool-shaped XML or JSON.
62237
+
62230
62238
  # Runtime payload \u2014 how to read unread messages
62231
62239
 
62232
62240
  \u6BCF\u4E2A turn \u91CC runtime \u4F1A\u7ED9\u4F60\u300C\u672A\u8BFB\u7FA4\u6D88\u606F\uFF08N \u6761\uFF0C\u6309\u65F6\u95F4\u987A\u5E8F\uFF09\u300D\u6BB5\u3002\u8BFB\u6CD5\uFF1A
@@ -62675,6 +62683,11 @@ function parseWSMessage(raw) {
62675
62683
  }
62676
62684
  return parsed;
62677
62685
  }
62686
+ function isAskUserQuestionToolName(toolName) {
62687
+ if (!toolName) return false;
62688
+ const normalized = toolName.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
62689
+ return normalized === "askuserquestion" || normalized.endsWith("askuserquestion");
62690
+ }
62678
62691
 
62679
62692
  // ../shared/src/utils/workdir.ts
62680
62693
  init_cjs_shims();
@@ -62687,9 +62700,13 @@ init_cjs_shims();
62687
62700
 
62688
62701
  // ../shared/src/utils/subscription.ts
62689
62702
  init_cjs_shims();
62703
+ var PRIMARY_COMPANY_SUBSCRIPTION_ID = "sub_company_primary";
62690
62704
  function isSubscriptionType(v) {
62691
62705
  return v === "system" || v === "project";
62692
62706
  }
62707
+ function isPrimaryCompanySubscriptionId(v) {
62708
+ return v === PRIMARY_COMPANY_SUBSCRIPTION_ID;
62709
+ }
62693
62710
 
62694
62711
  // ../shared/src/utils/agentConfig.ts
62695
62712
  function parseAgentConfig(raw) {
@@ -78285,6 +78302,14 @@ function normalizeDocumentText(value) {
78285
78302
  return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n{4,}/g, "\n\n\n").trim();
78286
78303
  }
78287
78304
 
78305
+ // src/bridgeHttp.ts
78306
+ init_cjs_shims();
78307
+ var BRIDGE_TOKEN_HEADER = "X-AHChat-Bridge-Token";
78308
+ function bridgeAuthHeaders(bridgeToken) {
78309
+ const token = bridgeToken?.trim();
78310
+ return token ? { [BRIDGE_TOKEN_HEADER]: token } : {};
78311
+ }
78312
+
78288
78313
  // src/neuralMcpServer.ts
78289
78314
  var logger7 = createModuleLogger("neural.mcpServer");
78290
78315
  function formatScopeLabel(key, groupName) {
@@ -78303,6 +78328,26 @@ function filterContactsByOwner(all, ownerId) {
78303
78328
  (a) => a.kind === "system" || a.ownerId === ownerId || a.ownerId == null || a.kind === "human" && a.id === ownerId
78304
78329
  );
78305
78330
  }
78331
+ async function resolveTierSubscriptionPreference(preferredSubscriptionId, deps) {
78332
+ if (!preferredSubscriptionId || !isPrimaryCompanySubscriptionId(preferredSubscriptionId)) {
78333
+ return preferredSubscriptionId;
78334
+ }
78335
+ if (!deps.serverApiUrl) return preferredSubscriptionId;
78336
+ try {
78337
+ const base = deps.serverApiUrl.replace(/\/$/, "");
78338
+ const res = await fetch(`${base}/api/subscriptions`, {
78339
+ headers: bridgeAuthHeaders(deps.bridgeToken ?? null)
78340
+ });
78341
+ if (!res.ok) return preferredSubscriptionId;
78342
+ const subscriptions = await res.json();
78343
+ return subscriptions.find(
78344
+ (subscription) => subscription.billingMode === "company_billable" && subscription.isPrimaryCompany === true
78345
+ )?.id ?? preferredSubscriptionId;
78346
+ } catch (e) {
78347
+ logger7.warn("Primary company tier preference resolution failed", { error: e });
78348
+ return preferredSubscriptionId;
78349
+ }
78350
+ }
78306
78351
  function resolveMyHuman(registry2, agentId) {
78307
78352
  const all = registry2.getAll();
78308
78353
  const myOwnerId = contactOwnerId(registry2, agentId);
@@ -79458,8 +79503,8 @@ ${result.warnings.map((warning) => `- ${warning}`).join("\n")}
79458
79503
  "fetch_logs",
79459
79504
  `\u62C9\u53D6\u7CFB\u7EDF\u65E5\u5FD7\uFF08\u6309\u65F6\u95F4\u7A97 + \u53EF\u9009 traceId / module / level \u8FC7\u6EE4\uFF09\u3002
79460
79505
  \u8BFB SKILL "log-analysis" \u540E\u624D\u7528\u8FD9\u4E2A\u5DE5\u5177\u3002\u4E00\u6B21\u8C03\u7528\u53EA\u80FD\u62C9\u4E00\u4E2A source\uFF08server \u6216 bridge\uFF09\uFF0C\u5168\u666F\u9700\u8981\u4E24\u6B21\u3002
79461
- limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679C\u9876\u90E8\u5E26\u5206\u7EA7\u7EDF\u8BA1\uFF08ERROR/WARN/INFO \u5404\u591A\u5C11\u6761\uFF09\uFF0C\u6309\u65F6\u95F4\u5347\u5E8F\u6392\u5217\u3002\u6BCF\u884C\u5E26 file / lineNum\uFF0C\u5F15\u7528\u65E5\u5FD7\u65F6\u5FC5\u987B\u7CBE\u786E\u5199 [<file>:L<lineNum>]\u3002
79462
- \u6CE8\u610F\uFF1A\u5982\u679C\u7ED3\u679C\u8FC7\u957F\u4F1A\u88AB\u622A\u65AD\uFF0Cheader \u4F1A\u6807\u6CE8"\u5DF2\u622A\u65AD"\u2014\u2014\u6B64\u65F6\u7F29\u5C0F\u65F6\u95F4\u7A97\u6216\u52A0 module/traceId \u8FC7\u6EE4\u518D\u67E5\u3002`,
79506
+ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\uFF1B\u652F\u6301 offset \u5206\u9875\u3002\u8FD4\u56DE\u7ED3\u679C\u9876\u90E8\u5E26\u5206\u7EA7\u7EDF\u8BA1\uFF08ERROR/WARN/INFO \u5404\u591A\u5C11\u6761\uFF09\uFF0C\u6309\u65F6\u95F4\u5012\u5E8F\u6392\u5217\uFF0C\u6700\u65B0\u65E5\u5FD7\u5728\u524D\u3002\u6BCF\u884C\u5E26 file / lineNum\uFF0C\u5F15\u7528\u65E5\u5FD7\u65F6\u5FC5\u987B\u7CBE\u786E\u5199 [<file>:L<lineNum>]\u3002
79507
+ \u6CE8\u610F\uFF1A\u5982\u679C\u8FD8\u6709\u66F4\u591A\u7ED3\u679C\uFF0Cheader \u4F1A\u6807\u6CE8 nextOffset\uFF1B\u7EE7\u7EED\u67E5\u8BE2\u65F6\u5E26\u4E0A offset=nextOffset\u3002`,
79463
79508
  {
79464
79509
  source: external_exports.enum(["server", "bridge"]).describe('\u65E5\u5FD7\u6765\u6E90\uFF0C\u5FC5\u987B\u662F "server" \u6216 "bridge" \u4E4B\u4E00\u3002'),
79465
79510
  start_iso: external_exports.string().describe('\u8D77\u59CB\u65F6\u95F4\uFF0CISO 8601 \u6216\u76F8\u5BF9\u683C\u5F0F\uFF1A"now-5m" / "now-1h" / "now-24h"\u3002'),
@@ -79468,7 +79513,8 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
79468
79513
  trace_id: external_exports.string().optional().describe("\u8FC7\u6EE4\u7279\u5B9A traceId\uFF08\u7CBE\u786E\u5339\u914D\uFF09\u3002"),
79469
79514
  module: external_exports.string().optional().describe('\u8FC7\u6EE4 module \u524D\u7F00\uFF08\u5982 "ws.handler" / "agent.manager"\uFF09\u3002'),
79470
79515
  msg_contains: external_exports.string().optional().describe("msg \u5B50\u4E32\u8FC7\u6EE4\uFF08\u533A\u5206\u5927\u5C0F\u5199\uFF09\u3002"),
79471
- limit: external_exports.number().int().min(1).max(2e3).optional().describe("\u8FD4\u56DE\u6761\u6570\u4E0A\u9650\uFF0C\u9ED8\u8BA4 500\uFF0C\u6700\u5927 2000\u3002")
79516
+ limit: external_exports.number().int().min(1).max(2e3).optional().describe("\u8FD4\u56DE\u6761\u6570\u4E0A\u9650\uFF0C\u9ED8\u8BA4 500\uFF0C\u6700\u5927 2000\u3002"),
79517
+ offset: external_exports.number().int().min(0).optional().describe("\u5206\u9875\u504F\u79FB\u91CF\uFF1B\u9996\u6B21\u67E5\u8BE2\u4E0D\u4F20\uFF0C\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20\u4E0A\u6B21\u8FD4\u56DE\u7684 nextOffset\u3002")
79472
79518
  },
79473
79519
  async (args) => {
79474
79520
  if (!deps.isSmith) {
@@ -79493,9 +79539,11 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
79493
79539
  traceId: args.trace_id,
79494
79540
  module: args.module,
79495
79541
  levelMin: args.level_min,
79496
- limit: args.limit
79542
+ limit: args.limit,
79543
+ offset: args.offset
79497
79544
  });
79498
79545
  const parsedLimit = typeof args.limit === "number" && Number.isFinite(args.limit) ? args.limit : 500;
79546
+ const parsedOffset = typeof args.offset === "number" && Number.isFinite(args.offset) ? args.offset : 0;
79499
79547
  try {
79500
79548
  const url2 = `${deps.serverApiUrl.replace(/\/$/, "")}/api/admin/logs`;
79501
79549
  const body = {
@@ -79506,7 +79554,8 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
79506
79554
  traceId: args.trace_id,
79507
79555
  module: args.module,
79508
79556
  msgContains: args.msg_contains,
79509
- limit: Number.isFinite(parsedLimit) ? parsedLimit : 500
79557
+ limit: Number.isFinite(parsedLimit) ? parsedLimit : 500,
79558
+ offset: Number.isFinite(parsedOffset) ? parsedOffset : 0
79510
79559
  };
79511
79560
  const res = await fetch(url2, {
79512
79561
  method: "POST",
@@ -79529,7 +79578,9 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
79529
79578
  source,
79530
79579
  count: json2.entries.length,
79531
79580
  truncated: json2.truncated,
79532
- totalScanned: json2.totalScanned
79581
+ totalScanned: json2.totalScanned,
79582
+ totalMatched: json2.totalMatched,
79583
+ nextOffset: json2.nextOffset
79533
79584
  });
79534
79585
  const lines = json2.entries.map((e) => {
79535
79586
  const dataStr = e.data ? ` data=${JSON.stringify(e.data).slice(0, 200)}` : "";
@@ -79542,9 +79593,10 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
79542
79593
  const infoCount = json2.entries.filter((e) => e.level === "INFO").length;
79543
79594
  const traceCount = json2.entries.filter((e) => e.level === "TRACE" || e.level === "DEBUG").length;
79544
79595
  const header = [
79545
- `[${source}] \u5171\u626B ${json2.totalScanned} \u884C\uFF0C\u547D\u4E2D ${json2.entries.length} \u6761`,
79596
+ `[${source}] \u5171\u626B ${json2.totalScanned} \u884C\uFF0C\u5DF2\u8FD4\u56DE ${json2.entries.length} \u6761\uFF0C\u547D\u4E2D ${json2.totalMatched ?? json2.entries.length} \u6761`,
79546
79597
  ` ERROR/FATAL: ${errorCount} | WARN: ${warnCount} | INFO: ${infoCount} | TRACE/DEBUG: ${traceCount}`
79547
- ].join("\n") + (json2.truncated ? "\n\uFF08\u5DF2\u622A\u65AD\uFF0C\u8BF7\u7F29\u5C0F\u65F6\u95F4\u7A97\u6216\u52A0\u8FC7\u6EE4\u6761\u4EF6\u518D\u67E5\uFF09" : "");
79598
+ ].join("\n") + (json2.nextOffset != null ? `
79599
+ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=${json2.nextOffset}\uFF09` : "");
79548
79600
  const text = [header, "", ...lines].join("\n");
79549
79601
  return { content: [{ type: "text", text }] };
79550
79602
  } catch (e) {
@@ -79631,7 +79683,11 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
79631
79683
  const tiers = await tierRes.json();
79632
79684
  const self = deps.agentRegistry?.getById(deps.agentId);
79633
79685
  const preferredSubscriptionId = parseAgentConfig(self?.config).subscriptionId;
79634
- const tierConfig = tiers.find((t) => t.tier === tier && t.subscriptionId === preferredSubscriptionId) ?? tiers.find((t) => t.tier === tier);
79686
+ const resolvedPreferredSubscriptionId = await resolveTierSubscriptionPreference(
79687
+ preferredSubscriptionId,
79688
+ deps
79689
+ );
79690
+ const tierConfig = tiers.find((t) => t.tier === tier && t.subscriptionId === resolvedPreferredSubscriptionId) ?? tiers.find((t) => t.tier === tier);
79635
79691
  if (tierConfig) {
79636
79692
  agentConfig = JSON.stringify({
79637
79693
  capabilityTier: tier,
@@ -79786,7 +79842,11 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
79786
79842
  if (tierRes.ok) {
79787
79843
  const tiers = await tierRes.json();
79788
79844
  const preferredSubscriptionId = parseAgentConfig(existing.config).subscriptionId;
79789
- const tierConfig = tiers.find((t) => t.tier === tier && t.subscriptionId === preferredSubscriptionId) ?? tiers.find((t) => t.tier === tier);
79845
+ const resolvedPreferredSubscriptionId = await resolveTierSubscriptionPreference(
79846
+ preferredSubscriptionId,
79847
+ deps
79848
+ );
79849
+ const tierConfig = tiers.find((t) => t.tier === tier && t.subscriptionId === resolvedPreferredSubscriptionId) ?? tiers.find((t) => t.tier === tier);
79790
79850
  if (tierConfig) {
79791
79851
  agentConfig = JSON.stringify({
79792
79852
  capabilityTier: tier,
@@ -80419,6 +80479,8 @@ var GroupDispatchMemoryStore = class {
80419
80479
 
80420
80480
  // src/groupInboxPromptBuilder.ts
80421
80481
  init_cjs_shims();
80482
+ var MAX_SYSTEM_NOTE_COUNT = 8;
80483
+ var MAX_SYSTEM_NOTE_LEN = 1e3;
80422
80484
  function formatHHMM(epochMs) {
80423
80485
  const d = new Date(epochMs);
80424
80486
  const hh = String(d.getHours()).padStart(2, "0");
@@ -80429,6 +80491,37 @@ function truncate(text, maxLen) {
80429
80491
  if (text.length <= maxLen) return text;
80430
80492
  return `${text.slice(0, maxLen)}\u2026`;
80431
80493
  }
80494
+ function formatIsoHHMM(iso) {
80495
+ const epochMs = Date.parse(iso);
80496
+ if (!Number.isFinite(epochMs)) return "";
80497
+ return formatHHMM(epochMs);
80498
+ }
80499
+ function collectSystemNotes(entries) {
80500
+ const byId = /* @__PURE__ */ new Map();
80501
+ for (const entry of entries) {
80502
+ for (const msg of entry.context) {
80503
+ if (msg.role !== "system") continue;
80504
+ byId.set(msg.id, {
80505
+ id: msg.id,
80506
+ time: formatIsoHHMM(msg.createdAt),
80507
+ content: truncate(msg.content, MAX_SYSTEM_NOTE_LEN)
80508
+ });
80509
+ }
80510
+ }
80511
+ return [...byId.values()].slice(-MAX_SYSTEM_NOTE_COUNT);
80512
+ }
80513
+ function appendSystemNotes(lines, entries) {
80514
+ const notes = collectSystemNotes(entries);
80515
+ if (notes.length === 0) return;
80516
+ lines.push(`--- \u7FA4\u5185\u7CFB\u7EDF\u8BB0\u5F55\uFF08${notes.length} \u6761\uFF0C\u4F9B\u4E0A\u4E0B\u6587\uFF1B\u975E\u666E\u901A\u804A\u5929\u5386\u53F2\uFF09---`);
80517
+ lines.push("\u8FD9\u4E9B\u8BB0\u5F55\u53EA\u7528\u4E8E\u7406\u89E3\u7528\u6237\u5DF2\u4F5C\u51FA\u7684 AskUserQuestion/\u7CFB\u7EDF\u51B3\u7B56\uFF1B\u4E0D\u8981\u76F4\u63A5\u56DE\u590D\u7CFB\u7EDF\u8BB0\u5F55\u672C\u8EAB\u3002");
80518
+ for (const note of notes) {
80519
+ const prefix = note.time ? `[${note.time}] system:` : "system:";
80520
+ lines.push(`${prefix} ${note.content}`);
80521
+ }
80522
+ lines.push("--- \u7FA4\u5185\u7CFB\u7EDF\u8BB0\u5F55 end ---");
80523
+ lines.push("");
80524
+ }
80432
80525
  function appendBoardContext(lines, latest) {
80433
80526
  if (latest.boardMeta?.vision) {
80434
80527
  lines.push("--- project vision ---");
@@ -80497,6 +80590,7 @@ function buildGroupInboxPrompt(entries, opts = {}) {
80497
80590
  );
80498
80591
  }
80499
80592
  lines.push("");
80593
+ appendSystemNotes(lines, entries);
80500
80594
  lines.push(`--- \u672A\u8BFB\u7FA4\u6D88\u606F\uFF08${entries.length} \u6761\uFF0C\u6309\u65F6\u95F4\u987A\u5E8F\uFF09---`);
80501
80595
  for (const e of entries) {
80502
80596
  const ts = formatHHMM(e.arrivedAt);
@@ -80841,6 +80935,31 @@ function extractUsage(message) {
80841
80935
  }
80842
80936
  return result;
80843
80937
  }
80938
+ function hasUsageValue(usage) {
80939
+ return typeof usage.tokenCount === "number" || typeof usage.inputTokens === "number" || typeof usage.cacheReadTokens === "number" || typeof usage.cacheCreationTokens === "number" || typeof usage.costUsd === "number";
80940
+ }
80941
+ function emitUsageReported(proc, emit, base, usage, messageId) {
80942
+ if (!hasUsageValue(usage)) return;
80943
+ const groupId = proc.currentTask?.groupId;
80944
+ const scopeKeyValue = proc.scope.kind === "group" ? `group:${proc.scope.groupId}` : "single";
80945
+ emit({
80946
+ type: "agent:usage_reported",
80947
+ payload: {
80948
+ ...wireBase(base),
80949
+ ...messageId ? { messageId } : {},
80950
+ ...groupId ? { groupId } : {},
80951
+ scopeKey: scopeKeyValue,
80952
+ model: usage.model,
80953
+ usage: {
80954
+ inputTokens: usage.inputTokens,
80955
+ outputTokens: usage.tokenCount,
80956
+ cacheReadTokens: usage.cacheReadTokens,
80957
+ cacheCreationTokens: usage.cacheCreationTokens
80958
+ },
80959
+ providerCostUsd: usage.costUsd
80960
+ }
80961
+ });
80962
+ }
80844
80963
  function isGroupTask(proc) {
80845
80964
  return proc.currentTask?.groupId != null;
80846
80965
  }
@@ -81102,7 +81221,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
81102
81221
  proc.currentToolName = block.name ?? "unknown";
81103
81222
  proc.accumulatedToolInput = "";
81104
81223
  const toolName = block.name ?? "unknown";
81105
- if (toolName !== "ExitPlanMode" && toolName !== "AskUserQuestion") {
81224
+ if (toolName !== "ExitPlanMode" && !isAskUserQuestionToolName(toolName)) {
81106
81225
  emit({
81107
81226
  type: "agent:tool_use",
81108
81227
  payload: {
@@ -81266,9 +81385,9 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
81266
81385
  });
81267
81386
  }
81268
81387
  }
81269
- if (proc.currentToolName === "AskUserQuestion") {
81388
+ if (isAskUserQuestionToolName(proc.currentToolName)) {
81270
81389
  const last = proc.contentBlocks[proc.contentBlocks.length - 1];
81271
- if (last?.type === "tool_use" && last.toolName === "AskUserQuestion") {
81390
+ if (last?.type === "tool_use" && isAskUserQuestionToolName(last.toolName)) {
81272
81391
  proc.contentBlocks.pop();
81273
81392
  }
81274
81393
  }
@@ -81320,7 +81439,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
81320
81439
  const toolName = proc.currentToolName ?? "unknown";
81321
81440
  const isError = toolName === "ExitPlanMode" ? false : !!b.is_error;
81322
81441
  const output = typeof b.content === "string" ? b.content : JSON.stringify(b.content);
81323
- if (toolName === "AskUserQuestion") {
81442
+ if (isAskUserQuestionToolName(toolName)) {
81324
81443
  proc.currentToolName = null;
81325
81444
  continue;
81326
81445
  }
@@ -81404,6 +81523,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
81404
81523
  const watermarkUsage = proc.peakContextUsage ?? usage;
81405
81524
  if (trimmed === NO_REPLY_TOKEN) {
81406
81525
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
81526
+ emitUsageReported(proc, emit, base, usage);
81407
81527
  if (groupMode && proc.contentBlocks.length > 0) {
81408
81528
  emitGroupSegment(
81409
81529
  proc,
@@ -81443,6 +81563,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
81443
81563
  }
81444
81564
  if (groupMode) {
81445
81565
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
81566
+ emitUsageReported(proc, emit, base, usage);
81446
81567
  if (proc.contentBlocks.length > 0) {
81447
81568
  logger9.info("Group turn trailing audit segment", {
81448
81569
  agentId: proc.agentId,
@@ -81477,6 +81598,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
81477
81598
  }
81478
81599
  if (proc.accumulatedText.length === 0 && proc.contentBlocks.length === 0) {
81479
81600
  cleanupPlanMode(proc, emit, base, "error");
81601
+ emitUsageReported(proc, emit, base, usage);
81480
81602
  logger9.warn("SDK success produced empty assistant output; emitting agent:error", {
81481
81603
  agentId: proc.agentId,
81482
81604
  ackId: base.replyMessageId,
@@ -81504,6 +81626,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
81504
81626
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
81505
81627
  cleanupPlanMode(proc, emit, base, "success");
81506
81628
  carrierMessageId = createMessageId();
81629
+ emitUsageReported(proc, emit, base, usage, carrierMessageId);
81507
81630
  logger9.info("Agent task done, emitting agent:done", {
81508
81631
  agentId: proc.agentId,
81509
81632
  ackId: base.replyMessageId,
@@ -81524,6 +81647,11 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
81524
81647
  thinkingDuration: Date.now() - proc.currentTaskStartedAt,
81525
81648
  toolCallCount: proc.contentBlocks.filter((b) => b.type === "tool_use").length,
81526
81649
  tokenCount: usage.tokenCount,
81650
+ outputTokens: usage.tokenCount,
81651
+ inputTokens: usage.inputTokens,
81652
+ cacheReadTokens: usage.cacheReadTokens,
81653
+ cacheCreationTokens: usage.cacheCreationTokens,
81654
+ costUsd: usage.costUsd,
81527
81655
  model: usage.model
81528
81656
  }
81529
81657
  }
@@ -81852,6 +81980,12 @@ var wsMetrics = new WsMetrics();
81852
81980
 
81853
81981
  // src/agentManager.ts
81854
81982
  var logger12 = createModuleLogger("agent.manager");
81983
+ function missingSubscriptionMessage(subscriptionId) {
81984
+ if (isPrimaryCompanySubscriptionId(subscriptionId)) {
81985
+ return "AHChat \u9ED8\u8BA4\u6A21\u578B\u6682\u65F6\u4E0D\u53EF\u7528\uFF1A\u8FD9\u53F0\u673A\u5668\u8FD8\u6CA1\u6709\u540C\u6B65\u5230\u7BA1\u7406\u5458\u542F\u7528\u7684\u9ED8\u8BA4\u6A21\u578B\uFF0C\u8BF7\u66F4\u65B0\u5E76\u91CD\u542F Bridge \u540E\u91CD\u8BD5\u3002";
81986
+ }
81987
+ return `\u8BA2\u9605\u6E90\u4E0D\u53EF\u7528\uFF1A${subscriptionId}\u3002\u8BF7\u91CD\u65B0\u9009\u62E9\u6A21\u578B\u6765\u6E90\uFF0C\u6216\u91CD\u542F Bridge \u540E\u91CD\u8BD5\u3002`;
81988
+ }
81855
81989
  var NODE_USER_UID = 1e3;
81856
81990
  var POST_MERGE_CONTINUATION_ROUTE_MS = 15e3;
81857
81991
  var DOCUMENT_READING_RULES = `DOCUMENT READING:
@@ -81916,6 +82050,27 @@ var BridgeBusyError = class extends Error {
81916
82050
  this.name = "BridgeBusyError";
81917
82051
  }
81918
82052
  };
82053
+ function senderLabelForQuote(message) {
82054
+ if (message.senderAgentName) return message.senderAgentName;
82055
+ if (message.role === "user") return "user";
82056
+ if (message.role === "agent") return `agent:${message.senderAgentId ?? "unknown"}`;
82057
+ return "system";
82058
+ }
82059
+ function buildSingleReplyPrompt(task) {
82060
+ if (!task.replyToMessage) return task.content;
82061
+ const quoted = task.replyToMessage;
82062
+ const label = senderLabelForQuote(quoted);
82063
+ return [
82064
+ "--- reply-to message ---",
82065
+ `You are replying to this earlier message from [${label}]:`,
82066
+ quoted.content || (quoted.attachments?.length ? "[attachment]" : ""),
82067
+ "--- end reply-to message ---",
82068
+ "",
82069
+ "--- user message ---",
82070
+ task.content,
82071
+ "--- end user message ---"
82072
+ ].join("\n");
82073
+ }
81919
82074
  var AgentManager = class {
81920
82075
  agents = /* @__PURE__ */ new Map();
81921
82076
  lastUsedAt = /* @__PURE__ */ new Map();
@@ -82051,16 +82206,16 @@ var AgentManager = class {
82051
82206
  agentId: agent.id,
82052
82207
  subscriptionId: cfg.subscriptionId
82053
82208
  });
82054
- return cfg;
82209
+ throw new Error(missingSubscriptionMessage(cfg.subscriptionId));
82055
82210
  }
82056
82211
  let sub = this.subscriptionRegistry.getById(cfg.subscriptionId);
82057
82212
  if (!sub) sub = await this.subscriptionRegistry.fetchById(cfg.subscriptionId);
82058
82213
  if (!sub) {
82059
- logger12.warn("Subscription not found; falling back to system OAuth", {
82214
+ logger12.warn("Subscription not found; refusing native OAuth fallback", {
82060
82215
  agentId: agent.id,
82061
82216
  subscriptionId: cfg.subscriptionId
82062
82217
  });
82063
- return { ...cfg, subscriptionType: "system" };
82218
+ throw new Error(missingSubscriptionMessage(cfg.subscriptionId));
82064
82219
  }
82065
82220
  const isPinnedModel = cfg.model && cfg.model !== "default";
82066
82221
  const resolvedModel = isPinnedModel ? cfg.model : sub.defaultModel;
@@ -83259,8 +83414,9 @@ ${lines.join("\n")}`;
83259
83414
  });
83260
83415
  }
83261
83416
  async pushTaskContent(runtime, task, onYielded) {
83417
+ const textContent = buildSingleReplyPrompt(task);
83262
83418
  if (!task.attachments || task.attachments.length === 0) {
83263
- runtime.inputController.push(task.content, runtime.ccSessionId ?? "", onYielded);
83419
+ runtime.inputController.push(textContent, runtime.ccSessionId ?? "", onYielded);
83264
83420
  return;
83265
83421
  }
83266
83422
  const supportsVision = await this.detectVisionSupport();
@@ -83272,7 +83428,7 @@ ${lines.join("\n")}`;
83272
83428
  supportsVision
83273
83429
  }
83274
83430
  );
83275
- const text = task.content.trim() || "\u8BF7\u67E5\u770B\u6211\u53D1\u9001\u7684\u9644\u4EF6\u3002";
83431
+ const text = textContent.trim() || "\u8BF7\u67E5\u770B\u6211\u53D1\u9001\u7684\u9644\u4EF6\u3002";
83276
83432
  const contentParts = [
83277
83433
  { type: "text", text },
83278
83434
  ...attachmentBlocks
@@ -83310,9 +83466,12 @@ ${lines.join("\n")}`;
83310
83466
  return materialized;
83311
83467
  }
83312
83468
  async resolveExistingWorkspaceAttachmentPath(runtime, attachment) {
83469
+ const localWorkspacePath = attachment.metadata?.localWorkspacePath;
83313
83470
  const workspacePath = attachment.metadata?.workspacePath;
83314
- if (typeof workspacePath !== "string" || !workspacePath.trim()) return null;
83315
- const candidate = import_node_path11.default.resolve(workspacePath);
83471
+ const rawPath = typeof localWorkspacePath === "string" && localWorkspacePath.trim() ? localWorkspacePath : workspacePath;
83472
+ if (typeof rawPath !== "string" || !rawPath.trim()) return null;
83473
+ const remapped = remapServerWorkspacePath(rawPath, this.workspacesDir);
83474
+ const candidate = import_node_path11.default.resolve(remapped.path);
83316
83475
  if (!this.isPathInsideBase(candidate, runtime.cwd)) return null;
83317
83476
  try {
83318
83477
  const stat3 = await import_promises3.default.stat(candidate);
@@ -83322,6 +83481,8 @@ ${lines.join("\n")}`;
83322
83481
  agentId: runtime.agentId,
83323
83482
  attachmentId: attachment.id,
83324
83483
  workspacePath,
83484
+ localWorkspacePath,
83485
+ resolvedPath: candidate,
83325
83486
  error: e
83326
83487
  });
83327
83488
  return null;
@@ -84521,7 +84682,7 @@ function computeBoardSignature(entry) {
84521
84682
  // src/bridgeFetchAuth.ts
84522
84683
  init_cjs_shims();
84523
84684
  var logger13 = createModuleLogger("bridge.fetchAuth");
84524
- var BRIDGE_TOKEN_HEADER = "X-AHChat-Bridge-Token";
84685
+ var BRIDGE_TOKEN_HEADER2 = "X-AHChat-Bridge-Token";
84525
84686
  function installBridgeFetchAuth(serverApiUrl, token) {
84526
84687
  if (typeof globalThis.fetch !== "function") {
84527
84688
  logger13.warn("globalThis.fetch not available, cannot install bridge fetch auth");
@@ -84549,8 +84710,8 @@ function installBridgeFetchAuth(serverApiUrl, token) {
84549
84710
  else url2 = input.url;
84550
84711
  if (!shouldInject(url2)) return original(input, init);
84551
84712
  const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
84552
- if (!headers.has(BRIDGE_TOKEN_HEADER)) {
84553
- headers.set(BRIDGE_TOKEN_HEADER, token);
84713
+ if (!headers.has(BRIDGE_TOKEN_HEADER2)) {
84714
+ headers.set(BRIDGE_TOKEN_HEADER2, token);
84554
84715
  }
84555
84716
  return original(input, { ...init, headers });
84556
84717
  });
@@ -84562,16 +84723,6 @@ function installBridgeFetchAuth(serverApiUrl, token) {
84562
84723
 
84563
84724
  // src/agentRegistry.ts
84564
84725
  init_cjs_shims();
84565
-
84566
- // src/bridgeHttp.ts
84567
- init_cjs_shims();
84568
- var BRIDGE_TOKEN_HEADER2 = "X-AHChat-Bridge-Token";
84569
- function bridgeAuthHeaders(bridgeToken) {
84570
- const token = bridgeToken?.trim();
84571
- return token ? { [BRIDGE_TOKEN_HEADER2]: token } : {};
84572
- }
84573
-
84574
- // src/agentRegistry.ts
84575
84726
  var logger14 = createModuleLogger("agent.registry");
84576
84727
  var HttpAgentRegistry = class {
84577
84728
  constructor(serverApiUrl, bridgeToken = null) {
@@ -84685,6 +84836,23 @@ var HttpSubscriptionRegistry = class {
84685
84836
  const path26 = suffix.startsWith("/") ? suffix : `/${suffix}`;
84686
84837
  return `${base}${path26}`;
84687
84838
  }
84839
+ primaryAliasFrom(sub) {
84840
+ return {
84841
+ ...sub,
84842
+ id: PRIMARY_COMPANY_SUBSCRIPTION_ID,
84843
+ name: "\u516C\u53F8\u4E3B\u8BA2\u9605",
84844
+ resolvedSubscriptionId: sub.id,
84845
+ isVirtual: true
84846
+ };
84847
+ }
84848
+ rebuildPrimaryAlias() {
84849
+ this.subscriptions.delete(PRIMARY_COMPANY_SUBSCRIPTION_ID);
84850
+ const primary = Array.from(this.subscriptions.values()).find(
84851
+ (sub) => sub.billingMode === "company_billable" && sub.isPrimaryCompany === true
84852
+ );
84853
+ if (!primary) return;
84854
+ this.subscriptions.set(PRIMARY_COMPANY_SUBSCRIPTION_ID, this.primaryAliasFrom(primary));
84855
+ }
84688
84856
  async refresh() {
84689
84857
  const attempt = async () => {
84690
84858
  try {
@@ -84712,6 +84880,7 @@ var HttpSubscriptionRegistry = class {
84712
84880
  const s = item;
84713
84881
  if (s && typeof s.id === "string") this.subscriptions.set(s.id, s);
84714
84882
  }
84883
+ this.rebuildPrimaryAlias();
84715
84884
  logger15.info("Subscription registry refreshed", { count: this.subscriptions.size });
84716
84885
  } catch (e) {
84717
84886
  logger15.warn("Subscription registry parse failed", { error: e });
@@ -84721,6 +84890,10 @@ var HttpSubscriptionRegistry = class {
84721
84890
  return this.subscriptions.get(id) ?? null;
84722
84891
  }
84723
84892
  async fetchById(id) {
84893
+ if (isPrimaryCompanySubscriptionId(id)) {
84894
+ await this.refresh();
84895
+ return this.getById(id);
84896
+ }
84724
84897
  try {
84725
84898
  const res = await fetch(this.apiUrl(`/api/subscriptions/${encodeURIComponent(id)}`), {
84726
84899
  headers: bridgeAuthHeaders(this.bridgeToken)
@@ -84736,9 +84909,11 @@ var HttpSubscriptionRegistry = class {
84736
84909
  }
84737
84910
  upsert(sub) {
84738
84911
  this.subscriptions.set(sub.id, sub);
84912
+ this.rebuildPrimaryAlias();
84739
84913
  }
84740
84914
  remove(id) {
84741
84915
  this.subscriptions.delete(id);
84916
+ this.rebuildPrimaryAlias();
84742
84917
  }
84743
84918
  };
84744
84919
 
@@ -85683,7 +85858,6 @@ var import_node_path14 = __toESM(require("path"), 1);
85683
85858
  var import_node_os8 = __toESM(require("os"), 1);
85684
85859
  var import_node_readline = __toESM(require("readline"), 1);
85685
85860
  var logger20 = createModuleLogger("bridge.logScanner");
85686
- var DEFAULT_LIMIT = 500;
85687
85861
  var MAX_LIMIT = 2e3;
85688
85862
  function listLogFiles(logsDir, baseName) {
85689
85863
  let names;
@@ -85696,7 +85870,7 @@ function listLogFiles(logsDir, baseName) {
85696
85870
  const pattern = new RegExp(`^${baseName.replace(".", "\\.")}(\\.\\d+)?$`);
85697
85871
  return names.filter((n) => pattern.test(n)).map((n) => import_node_path14.default.join(logsDir, n));
85698
85872
  }
85699
- async function scanFile(filePath, source, filter, limit, state) {
85873
+ async function scanFile(filePath, source, filter, state) {
85700
85874
  const file2 = import_node_path14.default.basename(filePath);
85701
85875
  const stream = import_node_fs6.default.createReadStream(filePath, { encoding: "utf-8" });
85702
85876
  const rl = import_node_readline.default.createInterface({ input: stream, crlfDelay: Infinity });
@@ -85708,31 +85882,36 @@ async function scanFile(filePath, source, filter, limit, state) {
85708
85882
  if (!parsed) continue;
85709
85883
  if (parsed.source !== source) continue;
85710
85884
  if (!matchesFilter(parsed, filter)) continue;
85711
- state.hits.push({ ...parsed, file: file2, lineNum });
85712
- if (state.hits.length > limit) {
85713
- state.truncated = true;
85714
- state.hits.length = limit;
85715
- }
85885
+ state.hits.push({ ...parsed, raw: line, file: file2, lineNum });
85716
85886
  }
85717
85887
  }
85718
85888
  async function scanLocalLogs(logsDir, baseName, filter) {
85719
85889
  const source = filter.source;
85720
- const limit = Math.min(Math.max(filter.limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);
85890
+ const limit = filter.limit == null ? null : Math.min(Math.max(filter.limit, 1), MAX_LIMIT);
85891
+ const offset = Math.max(filter.offset ?? 0, 0);
85721
85892
  const files = listLogFiles(logsDir, baseName);
85722
85893
  const state = { hits: [], totalScanned: 0, truncated: false };
85723
85894
  for (const filePath of files) {
85724
- if (state.truncated) break;
85725
85895
  try {
85726
- await scanFile(filePath, source, filter, limit, state);
85896
+ await scanFile(filePath, source, filter, state);
85727
85897
  } catch (e) {
85728
85898
  logger20.warn("scanLocalLogs: file read failed", { filePath, error: e });
85729
85899
  }
85730
85900
  }
85731
- state.hits.sort((a, b) => a.ts.localeCompare(b.ts));
85901
+ state.hits.sort((a, b) => b.ts.localeCompare(a.ts));
85902
+ const totalMatched = state.hits.length;
85903
+ const endOffset = limit === null ? totalMatched : offset + limit;
85904
+ const entries = state.hits.slice(offset, endOffset);
85905
+ const nextOffset = endOffset < totalMatched ? endOffset : void 0;
85906
+ if (nextOffset !== void 0) {
85907
+ state.truncated = true;
85908
+ }
85732
85909
  return {
85733
- entries: state.hits,
85910
+ entries,
85734
85911
  truncated: state.truncated,
85735
- totalScanned: state.totalScanned
85912
+ totalScanned: state.totalScanned,
85913
+ totalMatched,
85914
+ nextOffset
85736
85915
  };
85737
85916
  }
85738
85917
  async function scanBridgeLogs(filter) {
@@ -85746,6 +85925,8 @@ async function scanBridgeLogs(filter) {
85746
85925
  const result = await scanLocalLogs(logDir, "bridge.log", { ...filter, source: "bridge" });
85747
85926
  logger20.info("scanBridgeLogs complete", {
85748
85927
  hitCount: result.entries.length,
85928
+ totalMatched: result.totalMatched,
85929
+ nextOffset: result.nextOffset,
85749
85930
  totalScanned: result.totalScanned,
85750
85931
  truncated: result.truncated
85751
85932
  });
@@ -85820,6 +86001,10 @@ function isProcessAlive(pid) {
85820
86001
  } catch (e) {
85821
86002
  const err = e;
85822
86003
  if (err.code === "ESRCH") return false;
86004
+ if (err.code === "EPERM") {
86005
+ logger22.warn("Treating inaccessible lock PID as stale", { pid, error: e });
86006
+ return false;
86007
+ }
85823
86008
  throw e;
85824
86009
  }
85825
86010
  }
@@ -85949,6 +86134,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
85949
86134
  conversationId: payload.conversationId,
85950
86135
  content: payload.content,
85951
86136
  attachments: payload.attachments ?? void 0,
86137
+ replyToMessage: payload.replyToMessage ?? void 0,
85952
86138
  replyMessageId: payload.ackId,
85953
86139
  traceId: payload.traceId,
85954
86140
  planMode: payload.planMode ?? void 0
@@ -86759,6 +86945,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
86759
86945
  onConnected: async () => {
86760
86946
  await agentRegistry.refresh();
86761
86947
  await groupRegistry.refresh();
86948
+ await subscriptionRegistry.refresh();
86762
86949
  await agentManager.recoverFromRestart(agentRegistry.getAll());
86763
86950
  },
86764
86951
  onServerPush: async (msg) => {
@@ -86886,7 +87073,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
86886
87073
  const entries = await listDirectoryEntries(resolved.path);
86887
87074
  connector?.send({
86888
87075
  type: "bridge:list_dir_response",
86889
- payload: { requestId, entries }
87076
+ payload: { requestId, entries, localPath: resolved.path }
86890
87077
  });
86891
87078
  logger29.info("list_dir response sent", { requestId, count: entries.length });
86892
87079
  } catch (e) {
@@ -86923,6 +87110,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
86923
87110
  payload: {
86924
87111
  requestId,
86925
87112
  path: written.path,
87113
+ bridgePath: written.path,
86926
87114
  relativePath: written.relativePath,
86927
87115
  size: written.size
86928
87116
  }
@@ -87017,7 +87205,9 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
87017
87205
  endIso: filter.endIso,
87018
87206
  traceId: filter.traceId,
87019
87207
  module: filter.module,
87020
- levelMin: filter.levelMin
87208
+ levelMin: filter.levelMin,
87209
+ limit: filter.limit,
87210
+ offset: filter.offset
87021
87211
  });
87022
87212
  try {
87023
87213
  const result = await scanBridgeLogs({ ...filter, source: "bridge" });
@@ -87027,13 +87217,17 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
87027
87217
  requestId,
87028
87218
  entries: result.entries,
87029
87219
  truncated: result.truncated,
87030
- totalScanned: result.totalScanned
87220
+ totalScanned: result.totalScanned,
87221
+ totalMatched: result.totalMatched,
87222
+ nextOffset: result.nextOffset
87031
87223
  }
87032
87224
  });
87033
87225
  logger29.info("fetch_logs response sent", {
87034
87226
  requestId,
87035
87227
  count: result.entries.length,
87036
- truncated: result.truncated
87228
+ truncated: result.truncated,
87229
+ totalMatched: result.totalMatched,
87230
+ nextOffset: result.nextOffset
87037
87231
  });
87038
87232
  } catch (e) {
87039
87233
  const err = e instanceof Error ? e.message : String(e);
package/dist/index.js CHANGED
@@ -5467,6 +5467,14 @@ runtime\u3002\u5B83\u4EEC\u4E0D\u662F"\u4E0D\u540C\u7684\u4EBA"\u2014\u2014\u516
5467
5467
  \`# \u3010\u6807\u9898\u3011\` (a single \`#\` followed by Chinese bracket-enclosed title).
5468
5468
  For single-topic or short conversational replies, omit headings entirely.
5469
5469
 
5470
+ # AskUserQuestion tool
5471
+ - When you need the user to choose from options or answer a clarification question, call the real
5472
+ \`AskUserQuestion\` tool with a \`questions\` array.
5473
+ - Never output \`<AskUserQuestion>\`, \`</AskUserQuestion>\`, or raw JSON question payloads as chat text.
5474
+ Text like that is not an interactive panel and the user cannot answer it through the tool flow.
5475
+ - If you are not able to call \`AskUserQuestion\`, ask the question as plain natural language instead of
5476
+ emitting tool-shaped XML or JSON.
5477
+
5470
5478
  # Runtime payload \u2014 how to read unread messages
5471
5479
 
5472
5480
  \u6BCF\u4E2A turn \u91CC runtime \u4F1A\u7ED9\u4F60\u300C\u672A\u8BFB\u7FA4\u6D88\u606F\uFF08N \u6761\uFF0C\u6309\u65F6\u95F4\u987A\u5E8F\uFF09\u300D\u6BB5\u3002\u8BFB\u6CD5\uFF1A
@@ -5896,11 +5904,20 @@ function parseWSMessage(raw) {
5896
5904
  }
5897
5905
  return parsed;
5898
5906
  }
5907
+ function isAskUserQuestionToolName(toolName) {
5908
+ if (!toolName) return false;
5909
+ const normalized = toolName.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
5910
+ return normalized === "askuserquestion" || normalized.endsWith("askuserquestion");
5911
+ }
5899
5912
 
5900
5913
  // ../shared/src/utils/subscription.ts
5914
+ var PRIMARY_COMPANY_SUBSCRIPTION_ID = "sub_company_primary";
5901
5915
  function isSubscriptionType(v) {
5902
5916
  return v === "system" || v === "project";
5903
5917
  }
5918
+ function isPrimaryCompanySubscriptionId(v) {
5919
+ return v === PRIMARY_COMPANY_SUBSCRIPTION_ID;
5920
+ }
5904
5921
 
5905
5922
  // ../shared/src/utils/agentConfig.ts
5906
5923
  function parseAgentConfig(raw) {
@@ -21514,6 +21531,13 @@ function normalizeDocumentText(value) {
21514
21531
  return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n{4,}/g, "\n\n\n").trim();
21515
21532
  }
21516
21533
 
21534
+ // src/bridgeHttp.ts
21535
+ var BRIDGE_TOKEN_HEADER = "X-AHChat-Bridge-Token";
21536
+ function bridgeAuthHeaders(bridgeToken) {
21537
+ const token = bridgeToken?.trim();
21538
+ return token ? { [BRIDGE_TOKEN_HEADER]: token } : {};
21539
+ }
21540
+
21517
21541
  // src/neuralMcpServer.ts
21518
21542
  var logger6 = createModuleLogger("neural.mcpServer");
21519
21543
  function formatScopeLabel(key, groupName) {
@@ -21532,6 +21556,26 @@ function filterContactsByOwner(all, ownerId) {
21532
21556
  (a) => a.kind === "system" || a.ownerId === ownerId || a.ownerId == null || a.kind === "human" && a.id === ownerId
21533
21557
  );
21534
21558
  }
21559
+ async function resolveTierSubscriptionPreference(preferredSubscriptionId, deps) {
21560
+ if (!preferredSubscriptionId || !isPrimaryCompanySubscriptionId(preferredSubscriptionId)) {
21561
+ return preferredSubscriptionId;
21562
+ }
21563
+ if (!deps.serverApiUrl) return preferredSubscriptionId;
21564
+ try {
21565
+ const base = deps.serverApiUrl.replace(/\/$/, "");
21566
+ const res = await fetch(`${base}/api/subscriptions`, {
21567
+ headers: bridgeAuthHeaders(deps.bridgeToken ?? null)
21568
+ });
21569
+ if (!res.ok) return preferredSubscriptionId;
21570
+ const subscriptions = await res.json();
21571
+ return subscriptions.find(
21572
+ (subscription) => subscription.billingMode === "company_billable" && subscription.isPrimaryCompany === true
21573
+ )?.id ?? preferredSubscriptionId;
21574
+ } catch (e) {
21575
+ logger6.warn("Primary company tier preference resolution failed", { error: e });
21576
+ return preferredSubscriptionId;
21577
+ }
21578
+ }
21535
21579
  function resolveMyHuman(registry2, agentId) {
21536
21580
  const all = registry2.getAll();
21537
21581
  const myOwnerId = contactOwnerId(registry2, agentId);
@@ -22687,8 +22731,8 @@ ${result.warnings.map((warning) => `- ${warning}`).join("\n")}
22687
22731
  "fetch_logs",
22688
22732
  `\u62C9\u53D6\u7CFB\u7EDF\u65E5\u5FD7\uFF08\u6309\u65F6\u95F4\u7A97 + \u53EF\u9009 traceId / module / level \u8FC7\u6EE4\uFF09\u3002
22689
22733
  \u8BFB SKILL "log-analysis" \u540E\u624D\u7528\u8FD9\u4E2A\u5DE5\u5177\u3002\u4E00\u6B21\u8C03\u7528\u53EA\u80FD\u62C9\u4E00\u4E2A source\uFF08server \u6216 bridge\uFF09\uFF0C\u5168\u666F\u9700\u8981\u4E24\u6B21\u3002
22690
- limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679C\u9876\u90E8\u5E26\u5206\u7EA7\u7EDF\u8BA1\uFF08ERROR/WARN/INFO \u5404\u591A\u5C11\u6761\uFF09\uFF0C\u6309\u65F6\u95F4\u5347\u5E8F\u6392\u5217\u3002\u6BCF\u884C\u5E26 file / lineNum\uFF0C\u5F15\u7528\u65E5\u5FD7\u65F6\u5FC5\u987B\u7CBE\u786E\u5199 [<file>:L<lineNum>]\u3002
22691
- \u6CE8\u610F\uFF1A\u5982\u679C\u7ED3\u679C\u8FC7\u957F\u4F1A\u88AB\u622A\u65AD\uFF0Cheader \u4F1A\u6807\u6CE8"\u5DF2\u622A\u65AD"\u2014\u2014\u6B64\u65F6\u7F29\u5C0F\u65F6\u95F4\u7A97\u6216\u52A0 module/traceId \u8FC7\u6EE4\u518D\u67E5\u3002`,
22734
+ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\uFF1B\u652F\u6301 offset \u5206\u9875\u3002\u8FD4\u56DE\u7ED3\u679C\u9876\u90E8\u5E26\u5206\u7EA7\u7EDF\u8BA1\uFF08ERROR/WARN/INFO \u5404\u591A\u5C11\u6761\uFF09\uFF0C\u6309\u65F6\u95F4\u5012\u5E8F\u6392\u5217\uFF0C\u6700\u65B0\u65E5\u5FD7\u5728\u524D\u3002\u6BCF\u884C\u5E26 file / lineNum\uFF0C\u5F15\u7528\u65E5\u5FD7\u65F6\u5FC5\u987B\u7CBE\u786E\u5199 [<file>:L<lineNum>]\u3002
22735
+ \u6CE8\u610F\uFF1A\u5982\u679C\u8FD8\u6709\u66F4\u591A\u7ED3\u679C\uFF0Cheader \u4F1A\u6807\u6CE8 nextOffset\uFF1B\u7EE7\u7EED\u67E5\u8BE2\u65F6\u5E26\u4E0A offset=nextOffset\u3002`,
22692
22736
  {
22693
22737
  source: external_exports.enum(["server", "bridge"]).describe('\u65E5\u5FD7\u6765\u6E90\uFF0C\u5FC5\u987B\u662F "server" \u6216 "bridge" \u4E4B\u4E00\u3002'),
22694
22738
  start_iso: external_exports.string().describe('\u8D77\u59CB\u65F6\u95F4\uFF0CISO 8601 \u6216\u76F8\u5BF9\u683C\u5F0F\uFF1A"now-5m" / "now-1h" / "now-24h"\u3002'),
@@ -22697,7 +22741,8 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22697
22741
  trace_id: external_exports.string().optional().describe("\u8FC7\u6EE4\u7279\u5B9A traceId\uFF08\u7CBE\u786E\u5339\u914D\uFF09\u3002"),
22698
22742
  module: external_exports.string().optional().describe('\u8FC7\u6EE4 module \u524D\u7F00\uFF08\u5982 "ws.handler" / "agent.manager"\uFF09\u3002'),
22699
22743
  msg_contains: external_exports.string().optional().describe("msg \u5B50\u4E32\u8FC7\u6EE4\uFF08\u533A\u5206\u5927\u5C0F\u5199\uFF09\u3002"),
22700
- limit: external_exports.number().int().min(1).max(2e3).optional().describe("\u8FD4\u56DE\u6761\u6570\u4E0A\u9650\uFF0C\u9ED8\u8BA4 500\uFF0C\u6700\u5927 2000\u3002")
22744
+ limit: external_exports.number().int().min(1).max(2e3).optional().describe("\u8FD4\u56DE\u6761\u6570\u4E0A\u9650\uFF0C\u9ED8\u8BA4 500\uFF0C\u6700\u5927 2000\u3002"),
22745
+ offset: external_exports.number().int().min(0).optional().describe("\u5206\u9875\u504F\u79FB\u91CF\uFF1B\u9996\u6B21\u67E5\u8BE2\u4E0D\u4F20\uFF0C\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20\u4E0A\u6B21\u8FD4\u56DE\u7684 nextOffset\u3002")
22701
22746
  },
22702
22747
  async (args) => {
22703
22748
  if (!deps.isSmith) {
@@ -22722,9 +22767,11 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22722
22767
  traceId: args.trace_id,
22723
22768
  module: args.module,
22724
22769
  levelMin: args.level_min,
22725
- limit: args.limit
22770
+ limit: args.limit,
22771
+ offset: args.offset
22726
22772
  });
22727
22773
  const parsedLimit = typeof args.limit === "number" && Number.isFinite(args.limit) ? args.limit : 500;
22774
+ const parsedOffset = typeof args.offset === "number" && Number.isFinite(args.offset) ? args.offset : 0;
22728
22775
  try {
22729
22776
  const url2 = `${deps.serverApiUrl.replace(/\/$/, "")}/api/admin/logs`;
22730
22777
  const body = {
@@ -22735,7 +22782,8 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22735
22782
  traceId: args.trace_id,
22736
22783
  module: args.module,
22737
22784
  msgContains: args.msg_contains,
22738
- limit: Number.isFinite(parsedLimit) ? parsedLimit : 500
22785
+ limit: Number.isFinite(parsedLimit) ? parsedLimit : 500,
22786
+ offset: Number.isFinite(parsedOffset) ? parsedOffset : 0
22739
22787
  };
22740
22788
  const res = await fetch(url2, {
22741
22789
  method: "POST",
@@ -22758,7 +22806,9 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22758
22806
  source,
22759
22807
  count: json2.entries.length,
22760
22808
  truncated: json2.truncated,
22761
- totalScanned: json2.totalScanned
22809
+ totalScanned: json2.totalScanned,
22810
+ totalMatched: json2.totalMatched,
22811
+ nextOffset: json2.nextOffset
22762
22812
  });
22763
22813
  const lines = json2.entries.map((e) => {
22764
22814
  const dataStr = e.data ? ` data=${JSON.stringify(e.data).slice(0, 200)}` : "";
@@ -22771,9 +22821,10 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22771
22821
  const infoCount = json2.entries.filter((e) => e.level === "INFO").length;
22772
22822
  const traceCount = json2.entries.filter((e) => e.level === "TRACE" || e.level === "DEBUG").length;
22773
22823
  const header = [
22774
- `[${source}] \u5171\u626B ${json2.totalScanned} \u884C\uFF0C\u547D\u4E2D ${json2.entries.length} \u6761`,
22824
+ `[${source}] \u5171\u626B ${json2.totalScanned} \u884C\uFF0C\u5DF2\u8FD4\u56DE ${json2.entries.length} \u6761\uFF0C\u547D\u4E2D ${json2.totalMatched ?? json2.entries.length} \u6761`,
22775
22825
  ` ERROR/FATAL: ${errorCount} | WARN: ${warnCount} | INFO: ${infoCount} | TRACE/DEBUG: ${traceCount}`
22776
- ].join("\n") + (json2.truncated ? "\n\uFF08\u5DF2\u622A\u65AD\uFF0C\u8BF7\u7F29\u5C0F\u65F6\u95F4\u7A97\u6216\u52A0\u8FC7\u6EE4\u6761\u4EF6\u518D\u67E5\uFF09" : "");
22826
+ ].join("\n") + (json2.nextOffset != null ? `
22827
+ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=${json2.nextOffset}\uFF09` : "");
22777
22828
  const text = [header, "", ...lines].join("\n");
22778
22829
  return { content: [{ type: "text", text }] };
22779
22830
  } catch (e) {
@@ -22860,7 +22911,11 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22860
22911
  const tiers = await tierRes.json();
22861
22912
  const self = deps.agentRegistry?.getById(deps.agentId);
22862
22913
  const preferredSubscriptionId = parseAgentConfig(self?.config).subscriptionId;
22863
- const tierConfig = tiers.find((t) => t.tier === tier && t.subscriptionId === preferredSubscriptionId) ?? tiers.find((t) => t.tier === tier);
22914
+ const resolvedPreferredSubscriptionId = await resolveTierSubscriptionPreference(
22915
+ preferredSubscriptionId,
22916
+ deps
22917
+ );
22918
+ const tierConfig = tiers.find((t) => t.tier === tier && t.subscriptionId === resolvedPreferredSubscriptionId) ?? tiers.find((t) => t.tier === tier);
22864
22919
  if (tierConfig) {
22865
22920
  agentConfig = JSON.stringify({
22866
22921
  capabilityTier: tier,
@@ -23015,7 +23070,11 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
23015
23070
  if (tierRes.ok) {
23016
23071
  const tiers = await tierRes.json();
23017
23072
  const preferredSubscriptionId = parseAgentConfig(existing.config).subscriptionId;
23018
- const tierConfig = tiers.find((t) => t.tier === tier && t.subscriptionId === preferredSubscriptionId) ?? tiers.find((t) => t.tier === tier);
23073
+ const resolvedPreferredSubscriptionId = await resolveTierSubscriptionPreference(
23074
+ preferredSubscriptionId,
23075
+ deps
23076
+ );
23077
+ const tierConfig = tiers.find((t) => t.tier === tier && t.subscriptionId === resolvedPreferredSubscriptionId) ?? tiers.find((t) => t.tier === tier);
23019
23078
  if (tierConfig) {
23020
23079
  agentConfig = JSON.stringify({
23021
23080
  capabilityTier: tier,
@@ -23646,6 +23705,8 @@ var GroupDispatchMemoryStore = class {
23646
23705
  };
23647
23706
 
23648
23707
  // src/groupInboxPromptBuilder.ts
23708
+ var MAX_SYSTEM_NOTE_COUNT = 8;
23709
+ var MAX_SYSTEM_NOTE_LEN = 1e3;
23649
23710
  function formatHHMM(epochMs) {
23650
23711
  const d = new Date(epochMs);
23651
23712
  const hh = String(d.getHours()).padStart(2, "0");
@@ -23656,6 +23717,37 @@ function truncate(text, maxLen) {
23656
23717
  if (text.length <= maxLen) return text;
23657
23718
  return `${text.slice(0, maxLen)}\u2026`;
23658
23719
  }
23720
+ function formatIsoHHMM(iso) {
23721
+ const epochMs = Date.parse(iso);
23722
+ if (!Number.isFinite(epochMs)) return "";
23723
+ return formatHHMM(epochMs);
23724
+ }
23725
+ function collectSystemNotes(entries) {
23726
+ const byId = /* @__PURE__ */ new Map();
23727
+ for (const entry of entries) {
23728
+ for (const msg of entry.context) {
23729
+ if (msg.role !== "system") continue;
23730
+ byId.set(msg.id, {
23731
+ id: msg.id,
23732
+ time: formatIsoHHMM(msg.createdAt),
23733
+ content: truncate(msg.content, MAX_SYSTEM_NOTE_LEN)
23734
+ });
23735
+ }
23736
+ }
23737
+ return [...byId.values()].slice(-MAX_SYSTEM_NOTE_COUNT);
23738
+ }
23739
+ function appendSystemNotes(lines, entries) {
23740
+ const notes = collectSystemNotes(entries);
23741
+ if (notes.length === 0) return;
23742
+ lines.push(`--- \u7FA4\u5185\u7CFB\u7EDF\u8BB0\u5F55\uFF08${notes.length} \u6761\uFF0C\u4F9B\u4E0A\u4E0B\u6587\uFF1B\u975E\u666E\u901A\u804A\u5929\u5386\u53F2\uFF09---`);
23743
+ lines.push("\u8FD9\u4E9B\u8BB0\u5F55\u53EA\u7528\u4E8E\u7406\u89E3\u7528\u6237\u5DF2\u4F5C\u51FA\u7684 AskUserQuestion/\u7CFB\u7EDF\u51B3\u7B56\uFF1B\u4E0D\u8981\u76F4\u63A5\u56DE\u590D\u7CFB\u7EDF\u8BB0\u5F55\u672C\u8EAB\u3002");
23744
+ for (const note of notes) {
23745
+ const prefix = note.time ? `[${note.time}] system:` : "system:";
23746
+ lines.push(`${prefix} ${note.content}`);
23747
+ }
23748
+ lines.push("--- \u7FA4\u5185\u7CFB\u7EDF\u8BB0\u5F55 end ---");
23749
+ lines.push("");
23750
+ }
23659
23751
  function appendBoardContext(lines, latest) {
23660
23752
  if (latest.boardMeta?.vision) {
23661
23753
  lines.push("--- project vision ---");
@@ -23724,6 +23816,7 @@ function buildGroupInboxPrompt(entries, opts = {}) {
23724
23816
  );
23725
23817
  }
23726
23818
  lines.push("");
23819
+ appendSystemNotes(lines, entries);
23727
23820
  lines.push(`--- \u672A\u8BFB\u7FA4\u6D88\u606F\uFF08${entries.length} \u6761\uFF0C\u6309\u65F6\u95F4\u987A\u5E8F\uFF09---`);
23728
23821
  for (const e of entries) {
23729
23822
  const ts = formatHHMM(e.arrivedAt);
@@ -24067,6 +24160,31 @@ function extractUsage(message) {
24067
24160
  }
24068
24161
  return result;
24069
24162
  }
24163
+ function hasUsageValue(usage) {
24164
+ return typeof usage.tokenCount === "number" || typeof usage.inputTokens === "number" || typeof usage.cacheReadTokens === "number" || typeof usage.cacheCreationTokens === "number" || typeof usage.costUsd === "number";
24165
+ }
24166
+ function emitUsageReported(proc, emit, base, usage, messageId) {
24167
+ if (!hasUsageValue(usage)) return;
24168
+ const groupId = proc.currentTask?.groupId;
24169
+ const scopeKeyValue = proc.scope.kind === "group" ? `group:${proc.scope.groupId}` : "single";
24170
+ emit({
24171
+ type: "agent:usage_reported",
24172
+ payload: {
24173
+ ...wireBase(base),
24174
+ ...messageId ? { messageId } : {},
24175
+ ...groupId ? { groupId } : {},
24176
+ scopeKey: scopeKeyValue,
24177
+ model: usage.model,
24178
+ usage: {
24179
+ inputTokens: usage.inputTokens,
24180
+ outputTokens: usage.tokenCount,
24181
+ cacheReadTokens: usage.cacheReadTokens,
24182
+ cacheCreationTokens: usage.cacheCreationTokens
24183
+ },
24184
+ providerCostUsd: usage.costUsd
24185
+ }
24186
+ });
24187
+ }
24070
24188
  function isGroupTask(proc) {
24071
24189
  return proc.currentTask?.groupId != null;
24072
24190
  }
@@ -24328,7 +24446,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24328
24446
  proc.currentToolName = block.name ?? "unknown";
24329
24447
  proc.accumulatedToolInput = "";
24330
24448
  const toolName = block.name ?? "unknown";
24331
- if (toolName !== "ExitPlanMode" && toolName !== "AskUserQuestion") {
24449
+ if (toolName !== "ExitPlanMode" && !isAskUserQuestionToolName(toolName)) {
24332
24450
  emit({
24333
24451
  type: "agent:tool_use",
24334
24452
  payload: {
@@ -24492,9 +24610,9 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24492
24610
  });
24493
24611
  }
24494
24612
  }
24495
- if (proc.currentToolName === "AskUserQuestion") {
24613
+ if (isAskUserQuestionToolName(proc.currentToolName)) {
24496
24614
  const last = proc.contentBlocks[proc.contentBlocks.length - 1];
24497
- if (last?.type === "tool_use" && last.toolName === "AskUserQuestion") {
24615
+ if (last?.type === "tool_use" && isAskUserQuestionToolName(last.toolName)) {
24498
24616
  proc.contentBlocks.pop();
24499
24617
  }
24500
24618
  }
@@ -24546,7 +24664,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24546
24664
  const toolName = proc.currentToolName ?? "unknown";
24547
24665
  const isError = toolName === "ExitPlanMode" ? false : !!b.is_error;
24548
24666
  const output = typeof b.content === "string" ? b.content : JSON.stringify(b.content);
24549
- if (toolName === "AskUserQuestion") {
24667
+ if (isAskUserQuestionToolName(toolName)) {
24550
24668
  proc.currentToolName = null;
24551
24669
  continue;
24552
24670
  }
@@ -24630,6 +24748,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24630
24748
  const watermarkUsage = proc.peakContextUsage ?? usage;
24631
24749
  if (trimmed === NO_REPLY_TOKEN) {
24632
24750
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
24751
+ emitUsageReported(proc, emit, base, usage);
24633
24752
  if (groupMode && proc.contentBlocks.length > 0) {
24634
24753
  emitGroupSegment(
24635
24754
  proc,
@@ -24669,6 +24788,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24669
24788
  }
24670
24789
  if (groupMode) {
24671
24790
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
24791
+ emitUsageReported(proc, emit, base, usage);
24672
24792
  if (proc.contentBlocks.length > 0) {
24673
24793
  logger8.info("Group turn trailing audit segment", {
24674
24794
  agentId: proc.agentId,
@@ -24703,6 +24823,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24703
24823
  }
24704
24824
  if (proc.accumulatedText.length === 0 && proc.contentBlocks.length === 0) {
24705
24825
  cleanupPlanMode(proc, emit, base, "error");
24826
+ emitUsageReported(proc, emit, base, usage);
24706
24827
  logger8.warn("SDK success produced empty assistant output; emitting agent:error", {
24707
24828
  agentId: proc.agentId,
24708
24829
  ackId: base.replyMessageId,
@@ -24730,6 +24851,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24730
24851
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
24731
24852
  cleanupPlanMode(proc, emit, base, "success");
24732
24853
  carrierMessageId = createMessageId();
24854
+ emitUsageReported(proc, emit, base, usage, carrierMessageId);
24733
24855
  logger8.info("Agent task done, emitting agent:done", {
24734
24856
  agentId: proc.agentId,
24735
24857
  ackId: base.replyMessageId,
@@ -24750,6 +24872,11 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24750
24872
  thinkingDuration: Date.now() - proc.currentTaskStartedAt,
24751
24873
  toolCallCount: proc.contentBlocks.filter((b) => b.type === "tool_use").length,
24752
24874
  tokenCount: usage.tokenCount,
24875
+ outputTokens: usage.tokenCount,
24876
+ inputTokens: usage.inputTokens,
24877
+ cacheReadTokens: usage.cacheReadTokens,
24878
+ cacheCreationTokens: usage.cacheCreationTokens,
24879
+ costUsd: usage.costUsd,
24753
24880
  model: usage.model
24754
24881
  }
24755
24882
  }
@@ -25075,6 +25202,12 @@ var wsMetrics = new WsMetrics();
25075
25202
 
25076
25203
  // src/agentManager.ts
25077
25204
  var logger11 = createModuleLogger("agent.manager");
25205
+ function missingSubscriptionMessage(subscriptionId) {
25206
+ if (isPrimaryCompanySubscriptionId(subscriptionId)) {
25207
+ return "AHChat \u9ED8\u8BA4\u6A21\u578B\u6682\u65F6\u4E0D\u53EF\u7528\uFF1A\u8FD9\u53F0\u673A\u5668\u8FD8\u6CA1\u6709\u540C\u6B65\u5230\u7BA1\u7406\u5458\u542F\u7528\u7684\u9ED8\u8BA4\u6A21\u578B\uFF0C\u8BF7\u66F4\u65B0\u5E76\u91CD\u542F Bridge \u540E\u91CD\u8BD5\u3002";
25208
+ }
25209
+ return `\u8BA2\u9605\u6E90\u4E0D\u53EF\u7528\uFF1A${subscriptionId}\u3002\u8BF7\u91CD\u65B0\u9009\u62E9\u6A21\u578B\u6765\u6E90\uFF0C\u6216\u91CD\u542F Bridge \u540E\u91CD\u8BD5\u3002`;
25210
+ }
25078
25211
  var NODE_USER_UID = 1e3;
25079
25212
  var POST_MERGE_CONTINUATION_ROUTE_MS = 15e3;
25080
25213
  var DOCUMENT_READING_RULES = `DOCUMENT READING:
@@ -25139,6 +25272,27 @@ var BridgeBusyError = class extends Error {
25139
25272
  this.name = "BridgeBusyError";
25140
25273
  }
25141
25274
  };
25275
+ function senderLabelForQuote(message) {
25276
+ if (message.senderAgentName) return message.senderAgentName;
25277
+ if (message.role === "user") return "user";
25278
+ if (message.role === "agent") return `agent:${message.senderAgentId ?? "unknown"}`;
25279
+ return "system";
25280
+ }
25281
+ function buildSingleReplyPrompt(task) {
25282
+ if (!task.replyToMessage) return task.content;
25283
+ const quoted = task.replyToMessage;
25284
+ const label = senderLabelForQuote(quoted);
25285
+ return [
25286
+ "--- reply-to message ---",
25287
+ `You are replying to this earlier message from [${label}]:`,
25288
+ quoted.content || (quoted.attachments?.length ? "[attachment]" : ""),
25289
+ "--- end reply-to message ---",
25290
+ "",
25291
+ "--- user message ---",
25292
+ task.content,
25293
+ "--- end user message ---"
25294
+ ].join("\n");
25295
+ }
25142
25296
  var AgentManager = class {
25143
25297
  agents = /* @__PURE__ */ new Map();
25144
25298
  lastUsedAt = /* @__PURE__ */ new Map();
@@ -25274,16 +25428,16 @@ var AgentManager = class {
25274
25428
  agentId: agent.id,
25275
25429
  subscriptionId: cfg.subscriptionId
25276
25430
  });
25277
- return cfg;
25431
+ throw new Error(missingSubscriptionMessage(cfg.subscriptionId));
25278
25432
  }
25279
25433
  let sub = this.subscriptionRegistry.getById(cfg.subscriptionId);
25280
25434
  if (!sub) sub = await this.subscriptionRegistry.fetchById(cfg.subscriptionId);
25281
25435
  if (!sub) {
25282
- logger11.warn("Subscription not found; falling back to system OAuth", {
25436
+ logger11.warn("Subscription not found; refusing native OAuth fallback", {
25283
25437
  agentId: agent.id,
25284
25438
  subscriptionId: cfg.subscriptionId
25285
25439
  });
25286
- return { ...cfg, subscriptionType: "system" };
25440
+ throw new Error(missingSubscriptionMessage(cfg.subscriptionId));
25287
25441
  }
25288
25442
  const isPinnedModel = cfg.model && cfg.model !== "default";
25289
25443
  const resolvedModel = isPinnedModel ? cfg.model : sub.defaultModel;
@@ -26482,8 +26636,9 @@ ${lines.join("\n")}`;
26482
26636
  });
26483
26637
  }
26484
26638
  async pushTaskContent(runtime, task, onYielded) {
26639
+ const textContent = buildSingleReplyPrompt(task);
26485
26640
  if (!task.attachments || task.attachments.length === 0) {
26486
- runtime.inputController.push(task.content, runtime.ccSessionId ?? "", onYielded);
26641
+ runtime.inputController.push(textContent, runtime.ccSessionId ?? "", onYielded);
26487
26642
  return;
26488
26643
  }
26489
26644
  const supportsVision = await this.detectVisionSupport();
@@ -26495,7 +26650,7 @@ ${lines.join("\n")}`;
26495
26650
  supportsVision
26496
26651
  }
26497
26652
  );
26498
- const text = task.content.trim() || "\u8BF7\u67E5\u770B\u6211\u53D1\u9001\u7684\u9644\u4EF6\u3002";
26653
+ const text = textContent.trim() || "\u8BF7\u67E5\u770B\u6211\u53D1\u9001\u7684\u9644\u4EF6\u3002";
26499
26654
  const contentParts = [
26500
26655
  { type: "text", text },
26501
26656
  ...attachmentBlocks
@@ -26533,9 +26688,12 @@ ${lines.join("\n")}`;
26533
26688
  return materialized;
26534
26689
  }
26535
26690
  async resolveExistingWorkspaceAttachmentPath(runtime, attachment) {
26691
+ const localWorkspacePath = attachment.metadata?.localWorkspacePath;
26536
26692
  const workspacePath = attachment.metadata?.workspacePath;
26537
- if (typeof workspacePath !== "string" || !workspacePath.trim()) return null;
26538
- const candidate = path11.resolve(workspacePath);
26693
+ const rawPath = typeof localWorkspacePath === "string" && localWorkspacePath.trim() ? localWorkspacePath : workspacePath;
26694
+ if (typeof rawPath !== "string" || !rawPath.trim()) return null;
26695
+ const remapped = remapServerWorkspacePath(rawPath, this.workspacesDir);
26696
+ const candidate = path11.resolve(remapped.path);
26539
26697
  if (!this.isPathInsideBase(candidate, runtime.cwd)) return null;
26540
26698
  try {
26541
26699
  const stat3 = await fs5.stat(candidate);
@@ -26545,6 +26703,8 @@ ${lines.join("\n")}`;
26545
26703
  agentId: runtime.agentId,
26546
26704
  attachmentId: attachment.id,
26547
26705
  workspacePath,
26706
+ localWorkspacePath,
26707
+ resolvedPath: candidate,
26548
26708
  error: e
26549
26709
  });
26550
26710
  return null;
@@ -27743,7 +27903,7 @@ function computeBoardSignature(entry) {
27743
27903
 
27744
27904
  // src/bridgeFetchAuth.ts
27745
27905
  var logger12 = createModuleLogger("bridge.fetchAuth");
27746
- var BRIDGE_TOKEN_HEADER = "X-AHChat-Bridge-Token";
27906
+ var BRIDGE_TOKEN_HEADER2 = "X-AHChat-Bridge-Token";
27747
27907
  function installBridgeFetchAuth(serverApiUrl, token) {
27748
27908
  if (typeof globalThis.fetch !== "function") {
27749
27909
  logger12.warn("globalThis.fetch not available, cannot install bridge fetch auth");
@@ -27771,8 +27931,8 @@ function installBridgeFetchAuth(serverApiUrl, token) {
27771
27931
  else url2 = input.url;
27772
27932
  if (!shouldInject(url2)) return original(input, init);
27773
27933
  const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
27774
- if (!headers.has(BRIDGE_TOKEN_HEADER)) {
27775
- headers.set(BRIDGE_TOKEN_HEADER, token);
27934
+ if (!headers.has(BRIDGE_TOKEN_HEADER2)) {
27935
+ headers.set(BRIDGE_TOKEN_HEADER2, token);
27776
27936
  }
27777
27937
  return original(input, { ...init, headers });
27778
27938
  });
@@ -27782,13 +27942,6 @@ function installBridgeFetchAuth(serverApiUrl, token) {
27782
27942
  });
27783
27943
  }
27784
27944
 
27785
- // src/bridgeHttp.ts
27786
- var BRIDGE_TOKEN_HEADER2 = "X-AHChat-Bridge-Token";
27787
- function bridgeAuthHeaders(bridgeToken) {
27788
- const token = bridgeToken?.trim();
27789
- return token ? { [BRIDGE_TOKEN_HEADER2]: token } : {};
27790
- }
27791
-
27792
27945
  // src/agentRegistry.ts
27793
27946
  var logger13 = createModuleLogger("agent.registry");
27794
27947
  var HttpAgentRegistry = class {
@@ -27902,6 +28055,23 @@ var HttpSubscriptionRegistry = class {
27902
28055
  const path24 = suffix.startsWith("/") ? suffix : `/${suffix}`;
27903
28056
  return `${base}${path24}`;
27904
28057
  }
28058
+ primaryAliasFrom(sub) {
28059
+ return {
28060
+ ...sub,
28061
+ id: PRIMARY_COMPANY_SUBSCRIPTION_ID,
28062
+ name: "\u516C\u53F8\u4E3B\u8BA2\u9605",
28063
+ resolvedSubscriptionId: sub.id,
28064
+ isVirtual: true
28065
+ };
28066
+ }
28067
+ rebuildPrimaryAlias() {
28068
+ this.subscriptions.delete(PRIMARY_COMPANY_SUBSCRIPTION_ID);
28069
+ const primary = Array.from(this.subscriptions.values()).find(
28070
+ (sub) => sub.billingMode === "company_billable" && sub.isPrimaryCompany === true
28071
+ );
28072
+ if (!primary) return;
28073
+ this.subscriptions.set(PRIMARY_COMPANY_SUBSCRIPTION_ID, this.primaryAliasFrom(primary));
28074
+ }
27905
28075
  async refresh() {
27906
28076
  const attempt = async () => {
27907
28077
  try {
@@ -27929,6 +28099,7 @@ var HttpSubscriptionRegistry = class {
27929
28099
  const s = item;
27930
28100
  if (s && typeof s.id === "string") this.subscriptions.set(s.id, s);
27931
28101
  }
28102
+ this.rebuildPrimaryAlias();
27932
28103
  logger14.info("Subscription registry refreshed", { count: this.subscriptions.size });
27933
28104
  } catch (e) {
27934
28105
  logger14.warn("Subscription registry parse failed", { error: e });
@@ -27938,6 +28109,10 @@ var HttpSubscriptionRegistry = class {
27938
28109
  return this.subscriptions.get(id) ?? null;
27939
28110
  }
27940
28111
  async fetchById(id) {
28112
+ if (isPrimaryCompanySubscriptionId(id)) {
28113
+ await this.refresh();
28114
+ return this.getById(id);
28115
+ }
27941
28116
  try {
27942
28117
  const res = await fetch(this.apiUrl(`/api/subscriptions/${encodeURIComponent(id)}`), {
27943
28118
  headers: bridgeAuthHeaders(this.bridgeToken)
@@ -27953,9 +28128,11 @@ var HttpSubscriptionRegistry = class {
27953
28128
  }
27954
28129
  upsert(sub) {
27955
28130
  this.subscriptions.set(sub.id, sub);
28131
+ this.rebuildPrimaryAlias();
27956
28132
  }
27957
28133
  remove(id) {
27958
28134
  this.subscriptions.delete(id);
28135
+ this.rebuildPrimaryAlias();
27959
28136
  }
27960
28137
  };
27961
28138
 
@@ -28894,7 +29071,6 @@ import path14 from "path";
28894
29071
  import os8 from "os";
28895
29072
  import readline from "readline";
28896
29073
  var logger19 = createModuleLogger("bridge.logScanner");
28897
- var DEFAULT_LIMIT = 500;
28898
29074
  var MAX_LIMIT = 2e3;
28899
29075
  function listLogFiles(logsDir, baseName) {
28900
29076
  let names;
@@ -28907,7 +29083,7 @@ function listLogFiles(logsDir, baseName) {
28907
29083
  const pattern = new RegExp(`^${baseName.replace(".", "\\.")}(\\.\\d+)?$`);
28908
29084
  return names.filter((n) => pattern.test(n)).map((n) => path14.join(logsDir, n));
28909
29085
  }
28910
- async function scanFile(filePath, source, filter, limit, state) {
29086
+ async function scanFile(filePath, source, filter, state) {
28911
29087
  const file2 = path14.basename(filePath);
28912
29088
  const stream = fs8.createReadStream(filePath, { encoding: "utf-8" });
28913
29089
  const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
@@ -28919,31 +29095,36 @@ async function scanFile(filePath, source, filter, limit, state) {
28919
29095
  if (!parsed) continue;
28920
29096
  if (parsed.source !== source) continue;
28921
29097
  if (!matchesFilter(parsed, filter)) continue;
28922
- state.hits.push({ ...parsed, file: file2, lineNum });
28923
- if (state.hits.length > limit) {
28924
- state.truncated = true;
28925
- state.hits.length = limit;
28926
- }
29098
+ state.hits.push({ ...parsed, raw: line, file: file2, lineNum });
28927
29099
  }
28928
29100
  }
28929
29101
  async function scanLocalLogs(logsDir, baseName, filter) {
28930
29102
  const source = filter.source;
28931
- const limit = Math.min(Math.max(filter.limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);
29103
+ const limit = filter.limit == null ? null : Math.min(Math.max(filter.limit, 1), MAX_LIMIT);
29104
+ const offset = Math.max(filter.offset ?? 0, 0);
28932
29105
  const files = listLogFiles(logsDir, baseName);
28933
29106
  const state = { hits: [], totalScanned: 0, truncated: false };
28934
29107
  for (const filePath of files) {
28935
- if (state.truncated) break;
28936
29108
  try {
28937
- await scanFile(filePath, source, filter, limit, state);
29109
+ await scanFile(filePath, source, filter, state);
28938
29110
  } catch (e) {
28939
29111
  logger19.warn("scanLocalLogs: file read failed", { filePath, error: e });
28940
29112
  }
28941
29113
  }
28942
- state.hits.sort((a, b) => a.ts.localeCompare(b.ts));
29114
+ state.hits.sort((a, b) => b.ts.localeCompare(a.ts));
29115
+ const totalMatched = state.hits.length;
29116
+ const endOffset = limit === null ? totalMatched : offset + limit;
29117
+ const entries = state.hits.slice(offset, endOffset);
29118
+ const nextOffset = endOffset < totalMatched ? endOffset : void 0;
29119
+ if (nextOffset !== void 0) {
29120
+ state.truncated = true;
29121
+ }
28943
29122
  return {
28944
- entries: state.hits,
29123
+ entries,
28945
29124
  truncated: state.truncated,
28946
- totalScanned: state.totalScanned
29125
+ totalScanned: state.totalScanned,
29126
+ totalMatched,
29127
+ nextOffset
28947
29128
  };
28948
29129
  }
28949
29130
  async function scanBridgeLogs(filter) {
@@ -28957,6 +29138,8 @@ async function scanBridgeLogs(filter) {
28957
29138
  const result = await scanLocalLogs(logDir, "bridge.log", { ...filter, source: "bridge" });
28958
29139
  logger19.info("scanBridgeLogs complete", {
28959
29140
  hitCount: result.entries.length,
29141
+ totalMatched: result.totalMatched,
29142
+ nextOffset: result.nextOffset,
28960
29143
  totalScanned: result.totalScanned,
28961
29144
  truncated: result.truncated
28962
29145
  });
@@ -29029,6 +29212,10 @@ function isProcessAlive(pid) {
29029
29212
  } catch (e) {
29030
29213
  const err = e;
29031
29214
  if (err.code === "ESRCH") return false;
29215
+ if (err.code === "EPERM") {
29216
+ logger21.warn("Treating inaccessible lock PID as stale", { pid, error: e });
29217
+ return false;
29218
+ }
29032
29219
  throw e;
29033
29220
  }
29034
29221
  }
@@ -29154,6 +29341,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
29154
29341
  conversationId: payload.conversationId,
29155
29342
  content: payload.content,
29156
29343
  attachments: payload.attachments ?? void 0,
29344
+ replyToMessage: payload.replyToMessage ?? void 0,
29157
29345
  replyMessageId: payload.ackId,
29158
29346
  traceId: payload.traceId,
29159
29347
  planMode: payload.planMode ?? void 0
@@ -30225,6 +30413,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30225
30413
  onConnected: async () => {
30226
30414
  await agentRegistry.refresh();
30227
30415
  await groupRegistry.refresh();
30416
+ await subscriptionRegistry.refresh();
30228
30417
  await agentManager.recoverFromRestart(agentRegistry.getAll());
30229
30418
  },
30230
30419
  onServerPush: async (msg) => {
@@ -30352,7 +30541,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30352
30541
  const entries = await listDirectoryEntries(resolved.path);
30353
30542
  connector?.send({
30354
30543
  type: "bridge:list_dir_response",
30355
- payload: { requestId, entries }
30544
+ payload: { requestId, entries, localPath: resolved.path }
30356
30545
  });
30357
30546
  logger29.info("list_dir response sent", { requestId, count: entries.length });
30358
30547
  } catch (e) {
@@ -30389,6 +30578,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30389
30578
  payload: {
30390
30579
  requestId,
30391
30580
  path: written.path,
30581
+ bridgePath: written.path,
30392
30582
  relativePath: written.relativePath,
30393
30583
  size: written.size
30394
30584
  }
@@ -30483,7 +30673,9 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30483
30673
  endIso: filter.endIso,
30484
30674
  traceId: filter.traceId,
30485
30675
  module: filter.module,
30486
- levelMin: filter.levelMin
30676
+ levelMin: filter.levelMin,
30677
+ limit: filter.limit,
30678
+ offset: filter.offset
30487
30679
  });
30488
30680
  try {
30489
30681
  const result = await scanBridgeLogs({ ...filter, source: "bridge" });
@@ -30493,13 +30685,17 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30493
30685
  requestId,
30494
30686
  entries: result.entries,
30495
30687
  truncated: result.truncated,
30496
- totalScanned: result.totalScanned
30688
+ totalScanned: result.totalScanned,
30689
+ totalMatched: result.totalMatched,
30690
+ nextOffset: result.nextOffset
30497
30691
  }
30498
30692
  });
30499
30693
  logger29.info("fetch_logs response sent", {
30500
30694
  requestId,
30501
30695
  count: result.entries.length,
30502
- truncated: result.truncated
30696
+ truncated: result.truncated,
30697
+ totalMatched: result.totalMatched,
30698
+ nextOffset: result.nextOffset
30503
30699
  });
30504
30700
  } catch (e) {
30505
30701
  const err = e instanceof Error ? e.message : String(e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fangyb/ahchat-bridge",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "files": [
5
5
  "dist"
6
6
  ],