@fangyb/ahchat-bridge 0.1.18 → 0.1.19

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 +316 -52
  2. package/dist/index.js +316 -49
  3. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -4361,8 +4361,8 @@ function resolveAgentConfigDir(dataDir) {
4361
4361
  }
4362
4362
  return newPath;
4363
4363
  }
4364
- function loadBridgeConfig() {
4365
- const dataDir = readEnvString(
4364
+ function loadBridgeConfig(opts) {
4365
+ const dataDir = opts?.dataDir ?? readEnvString(
4366
4366
  "AHCHAT_DATA_DIR",
4367
4367
  import_node_path.default.join(import_node_os.default.homedir(), ".ahchat")
4368
4368
  );
@@ -6510,6 +6510,9 @@ var LOG_LEVEL_VALUE2 = {
6510
6510
 
6511
6511
  // ../shared/src/types/onboarding.ts
6512
6512
  init_cjs_shims();
6513
+ function isCapabilityTier(v9) {
6514
+ return v9 === "smart" || v9 === "balanced" || v9 === "fast";
6515
+ }
6513
6516
 
6514
6517
  // ../shared/src/utils.ts
6515
6518
  init_cjs_shims();
@@ -6588,18 +6591,6 @@ function parseWSMessage(raw) {
6588
6591
 
6589
6592
  // ../shared/src/utils/workdir.ts
6590
6593
  init_cjs_shims();
6591
- var SLUG_MAX_LEN = 32;
6592
- function slugifyForFs(name) {
6593
- const trimmed = name.trim();
6594
- if (!trimmed) return "unnamed";
6595
- const slug = trimmed.replace(/[^\w\u4e00-\u9fa5 \-]/gu, "_").replace(/\s+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
6596
- const base = slug.length > 0 ? slug : "unnamed";
6597
- return base.length > SLUG_MAX_LEN ? base.slice(0, SLUG_MAX_LEN) : base;
6598
- }
6599
- function defaultGroupWorkdir(home, name, id) {
6600
- const slug = slugifyForFs(name);
6601
- return `${home}/.ahchat/Group-${slug}-${id}`;
6602
- }
6603
6594
 
6604
6595
  // ../shared/src/utils/groupAuto.ts
6605
6596
  init_cjs_shims();
@@ -6622,6 +6613,7 @@ function parseAgentConfig(raw) {
6622
6613
  const out = {};
6623
6614
  const obj = v9;
6624
6615
  if (typeof obj.model === "string" && obj.model.trim()) out.model = obj.model.trim();
6616
+ if (isCapabilityTier(obj.capabilityTier)) out.capabilityTier = obj.capabilityTier;
6625
6617
  if (typeof obj.subscriptionId === "string") {
6626
6618
  const trimmed = obj.subscriptionId.trim();
6627
6619
  if (/^sub_[A-Za-z0-9_-]{1,64}$/.test(trimmed)) {
@@ -44852,9 +44844,12 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
44852
44844
  const tierRes = await fetch(tierUrl);
44853
44845
  if (tierRes.ok) {
44854
44846
  const tiers = await tierRes.json();
44855
- const tierConfig = tiers.find((t2) => t2.tier === tier);
44847
+ const self2 = deps.agentRegistry?.getById(deps.agentId);
44848
+ const preferredSubscriptionId = parseAgentConfig(self2?.config).subscriptionId;
44849
+ const tierConfig = tiers.find((t2) => t2.tier === tier && t2.subscriptionId === preferredSubscriptionId) ?? tiers.find((t2) => t2.tier === tier);
44856
44850
  if (tierConfig) {
44857
44851
  agentConfig = JSON.stringify({
44852
+ capabilityTier: tier,
44858
44853
  subscriptionId: tierConfig.subscriptionId,
44859
44854
  model: tierConfig.modelName
44860
44855
  });
@@ -45006,9 +45001,11 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
45006
45001
  const tierRes = await fetch(tierUrl);
45007
45002
  if (tierRes.ok) {
45008
45003
  const tiers = await tierRes.json();
45009
- const tierConfig = tiers.find((t2) => t2.tier === tier);
45004
+ const preferredSubscriptionId = parseAgentConfig(existing.config).subscriptionId;
45005
+ const tierConfig = tiers.find((t2) => t2.tier === tier && t2.subscriptionId === preferredSubscriptionId) ?? tiers.find((t2) => t2.tier === tier);
45010
45006
  if (tierConfig) {
45011
45007
  agentConfig = JSON.stringify({
45008
+ capabilityTier: tier,
45012
45009
  subscriptionId: tierConfig.subscriptionId,
45013
45010
  model: tierConfig.modelName
45014
45011
  });
@@ -45743,11 +45740,117 @@ init_cjs_shims();
45743
45740
  var logger7 = createModuleLogger("sdk.mapper");
45744
45741
  var HIGH_WATERMARK_INPUT_TOKENS = 12e4;
45745
45742
  var WARN_THRESHOLD_INPUT_TOKENS = 1e5;
45743
+ var LIVE_INPUT_PREVIEW_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit"]);
45746
45744
  var CONTEXT_OVERFLOW_LOCK_MS = 6e4;
45747
45745
  function isContextOverflowText(text) {
45748
45746
  const trimmed = text.trim();
45749
45747
  return /^prompt is too long\b/i.test(trimmed) || /context length .* exceed/i.test(trimmed);
45750
45748
  }
45749
+ function isAuthFailureText(text, sdkError) {
45750
+ return sdkError === "authentication_failed" || /not logged in|please run \/login/i.test(text);
45751
+ }
45752
+ function buildAuthFailureMessage(errorText) {
45753
+ return `Claude Code SDK \u672A\u767B\u5F55\uFF08${errorText}\uFF09\u3002\u8BF7\u5728 bridge \u4E3B\u673A\u4E0A\u914D\u7F6E\u8BA4\u8BC1\uFF1A\u82E5\u8BE5 Agent \u7528 system \u8BA2\u9605\uFF0C\u5C06\u5176 subscriptionType \u8BBE\u4E3A "system"\uFF08\u8D70 ~/.claude\uFF09\uFF1B\u5426\u5219\u5728 ~/.ahchat/claude-config \u4E0B\u767B\u5F55\uFF0C\u6216\u5728 Agent config \u4E2D\u63D0\u4F9B apiKey / apiBaseUrl\u3002`;
45754
+ }
45755
+ function decodeJsonStringFragment(raw) {
45756
+ let out = "";
45757
+ for (let i = 0; i < raw.length; i++) {
45758
+ const ch2 = raw[i];
45759
+ if (ch2 !== "\\") {
45760
+ out += ch2;
45761
+ continue;
45762
+ }
45763
+ const next = raw[i + 1];
45764
+ if (next === void 0) break;
45765
+ i += 1;
45766
+ switch (next) {
45767
+ case '"':
45768
+ case "\\":
45769
+ case "/":
45770
+ out += next;
45771
+ break;
45772
+ case "b":
45773
+ out += "\b";
45774
+ break;
45775
+ case "f":
45776
+ out += "\f";
45777
+ break;
45778
+ case "n":
45779
+ out += "\n";
45780
+ break;
45781
+ case "r":
45782
+ out += "\r";
45783
+ break;
45784
+ case "t":
45785
+ out += " ";
45786
+ break;
45787
+ case "u": {
45788
+ const hex3 = raw.slice(i + 1, i + 5);
45789
+ if (!/^[0-9a-fA-F]{4}$/.test(hex3)) return out;
45790
+ out += String.fromCharCode(Number.parseInt(hex3, 16));
45791
+ i += 4;
45792
+ break;
45793
+ }
45794
+ default:
45795
+ out += next;
45796
+ }
45797
+ }
45798
+ return out;
45799
+ }
45800
+ function extractJsonStringPrefix(source, key) {
45801
+ const keyToken = `"${key}"`;
45802
+ const keyIndex = source.indexOf(keyToken);
45803
+ if (keyIndex < 0) return null;
45804
+ let i = keyIndex + keyToken.length;
45805
+ while (i < source.length && /\s/.test(source[i] ?? "")) i += 1;
45806
+ if (source[i] !== ":") return null;
45807
+ i += 1;
45808
+ while (i < source.length && /\s/.test(source[i] ?? "")) i += 1;
45809
+ if (source[i] !== '"') return null;
45810
+ i += 1;
45811
+ let raw = "";
45812
+ let escaped = false;
45813
+ for (; i < source.length; i++) {
45814
+ const ch2 = source[i];
45815
+ if (ch2 === void 0) break;
45816
+ if (escaped) {
45817
+ raw += `\\${ch2}`;
45818
+ escaped = false;
45819
+ continue;
45820
+ }
45821
+ if (ch2 === "\\") {
45822
+ escaped = true;
45823
+ continue;
45824
+ }
45825
+ if (ch2 === '"') {
45826
+ return decodeJsonStringFragment(raw);
45827
+ }
45828
+ raw += ch2;
45829
+ }
45830
+ if (escaped) raw += "\\";
45831
+ return decodeJsonStringFragment(raw);
45832
+ }
45833
+ function extractLiveToolInput(toolName, inputJson) {
45834
+ if (toolName === "Write") {
45835
+ const content = extractJsonStringPrefix(inputJson, "content");
45836
+ if (content == null) return null;
45837
+ const filePath = extractJsonStringPrefix(inputJson, "file_path");
45838
+ return filePath == null ? { content } : { file_path: filePath, content };
45839
+ }
45840
+ if (toolName === "Edit") {
45841
+ const newString = extractJsonStringPrefix(inputJson, "new_string");
45842
+ if (newString == null) return null;
45843
+ const oldString = extractJsonStringPrefix(inputJson, "old_string");
45844
+ const filePath = extractJsonStringPrefix(inputJson, "file_path");
45845
+ const input = {
45846
+ new_string: newString
45847
+ };
45848
+ if (filePath != null) input.file_path = filePath;
45849
+ if (oldString != null) input.old_string = oldString;
45850
+ return input;
45851
+ }
45852
+ return null;
45853
+ }
45751
45854
  function recordAssistantContextUsage(proc, am2) {
45752
45855
  const u = am2.message?.usage;
45753
45856
  if (!u) return;
@@ -45815,6 +45918,45 @@ function cleanupPlanMode(proc, emit, base, reason) {
45815
45918
  });
45816
45919
  proc.planModeActive = false;
45817
45920
  }
45921
+ function extractAssistantTextParts(content, depth = 0) {
45922
+ if (depth > 4) return [];
45923
+ if (typeof content === "string") return [content];
45924
+ if (Array.isArray(content)) {
45925
+ return content.flatMap((item) => extractAssistantTextParts(item, depth + 1));
45926
+ }
45927
+ if (typeof content !== "object" || content === null) return [];
45928
+ const block = content;
45929
+ const type = typeof block.type === "string" ? block.type : null;
45930
+ if (type === "tool_use" || type === "tool_result") return [];
45931
+ if (type === "text" || type === "output_text" || type == null) {
45932
+ const textParts = extractAssistantTextParts(block.text, depth + 1);
45933
+ if (textParts.length > 0) return textParts;
45934
+ return extractAssistantTextParts(block.content, depth + 1);
45935
+ }
45936
+ return [];
45937
+ }
45938
+ function describeAssistantContent(content) {
45939
+ if (Array.isArray(content)) {
45940
+ return {
45941
+ contentShape: "array",
45942
+ blockTypes: content.map((item) => {
45943
+ if (typeof item !== "object" || item === null) return typeof item;
45944
+ const type = item.type;
45945
+ return typeof type === "string" ? type : "(no-type)";
45946
+ }).slice(0, 12)
45947
+ };
45948
+ }
45949
+ if (typeof content === "object" && content !== null) {
45950
+ return {
45951
+ contentShape: "object",
45952
+ contentKeys: Object.keys(content).slice(0, 12)
45953
+ };
45954
+ }
45955
+ return { contentShape: typeof content };
45956
+ }
45957
+ function scopeLogLabel(scope) {
45958
+ return scope.kind === "single" ? "single" : scope.groupId;
45959
+ }
45818
45960
  function getTaskBase(proc) {
45819
45961
  if (proc.currentTask) {
45820
45962
  return {
@@ -45824,10 +45966,42 @@ function getTaskBase(proc) {
45824
45966
  replyMessageId: proc.currentTask.replyMessageId
45825
45967
  };
45826
45968
  }
45969
+ const continuation = proc.postMergeContinuationTask;
45970
+ const continuationUntil = proc.postMergeContinuationUntil ?? 0;
45971
+ if (continuation) {
45972
+ if (Date.now() <= continuationUntil) {
45973
+ proc.currentTask = continuation;
45974
+ proc.status = "working";
45975
+ proc.currentTaskStartedAt = Date.now();
45976
+ delete proc.postMergeContinuationTask;
45977
+ delete proc.postMergeContinuationUntil;
45978
+ logger7.info("SDK post-merge continuation routed to merged task", {
45979
+ agentId: proc.agentId,
45980
+ scope: scopeLogLabel(proc.scope),
45981
+ replyMessageId: continuation.replyMessageId,
45982
+ conversationId: continuation.conversationId,
45983
+ traceId: continuation.traceId
45984
+ });
45985
+ return {
45986
+ agentId: proc.agentId,
45987
+ conversationId: continuation.conversationId,
45988
+ traceId: continuation.traceId,
45989
+ replyMessageId: continuation.replyMessageId
45990
+ };
45991
+ }
45992
+ logger7.info("SDK post-merge continuation route expired", {
45993
+ agentId: proc.agentId,
45994
+ scope: scopeLogLabel(proc.scope),
45995
+ replyMessageId: continuation.replyMessageId,
45996
+ routeUntil: new Date(continuationUntil).toISOString()
45997
+ });
45998
+ delete proc.postMergeContinuationTask;
45999
+ delete proc.postMergeContinuationUntil;
46000
+ }
45827
46001
  if (!proc.cachedConversationId) {
45828
46002
  logger7.warn("SDK self-initiated turn without cachedConversationId; dropping", {
45829
46003
  agentId: proc.agentId,
45830
- scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId
46004
+ scope: scopeLogLabel(proc.scope)
45831
46005
  });
45832
46006
  return null;
45833
46007
  }
@@ -45844,7 +46018,7 @@ function getTaskBase(proc) {
45844
46018
  proc.currentTaskStartedAt = Date.now();
45845
46019
  logger7.info("Cron-initiated turn detected, synthesized task", {
45846
46020
  agentId: proc.agentId,
45847
- scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
46021
+ scope: scopeLogLabel(proc.scope),
45848
46022
  replyMessageId,
45849
46023
  conversationId: proc.cachedConversationId,
45850
46024
  traceId
@@ -46088,6 +46262,17 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
46088
46262
  const partial2 = delta.partial_json;
46089
46263
  if (typeof partial2 === "string") {
46090
46264
  proc.accumulatedToolInput += partial2;
46265
+ const liveInput = extractLiveToolInput(proc.currentToolName, proc.accumulatedToolInput);
46266
+ if (liveInput && proc.currentToolName != null) {
46267
+ emit({
46268
+ type: "agent:tool_input_update",
46269
+ payload: {
46270
+ ...wireBase(base),
46271
+ toolName: proc.currentToolName,
46272
+ input: liveInput
46273
+ }
46274
+ });
46275
+ }
46091
46276
  }
46092
46277
  } else if (delta.type === "text_delta" && typeof delta.text === "string") {
46093
46278
  if (proc.accumulatedText.length === 0) {
@@ -46142,6 +46327,16 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
46142
46327
  if (lastToolUse && lastToolUse.type === "tool_use") {
46143
46328
  lastToolUse.input = parsedInput;
46144
46329
  }
46330
+ if (proc.currentToolName != null && LIVE_INPUT_PREVIEW_TOOLS.has(proc.currentToolName) && Object.keys(parsedInput).length > 0) {
46331
+ emit({
46332
+ type: "agent:tool_input_update",
46333
+ payload: {
46334
+ ...wireBase(base),
46335
+ toolName: proc.currentToolName,
46336
+ input: parsedInput
46337
+ }
46338
+ });
46339
+ }
46145
46340
  if (proc.currentToolName === "TodoWrite") {
46146
46341
  const todos = extractTodosFromInput(parsedInput);
46147
46342
  if (todos) {
@@ -46386,6 +46581,29 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
46386
46581
  onCompleted();
46387
46582
  break;
46388
46583
  }
46584
+ if (proc.accumulatedText.length === 0 && proc.contentBlocks.length === 0) {
46585
+ cleanupPlanMode(proc, emit, base, "error");
46586
+ logger7.warn("SDK success produced empty assistant output; emitting agent:error", {
46587
+ agentId: proc.agentId,
46588
+ ackId: base.replyMessageId,
46589
+ tokenCount: usage.tokenCount,
46590
+ inputTokens: usage.inputTokens,
46591
+ cacheReadTokens: usage.cacheReadTokens,
46592
+ cacheCreationTokens: usage.cacheCreationTokens,
46593
+ assistantContent: proc.lastAssistantContentDescription,
46594
+ traceId: base.traceId
46595
+ });
46596
+ emit({
46597
+ type: "agent:error",
46598
+ payload: {
46599
+ ...wireBase(base),
46600
+ error: "Agent \u8FD4\u56DE\u4E86\u7A7A\u7ED3\u679C\uFF0C\u5DF2\u963B\u6B62\u7A7A\u6D88\u606F\u8986\u76D6\u601D\u8003\u6C14\u6CE1\u3002\u8BF7\u91CD\u8BD5\uFF1B\u5982\u679C\u53CD\u590D\u51FA\u73B0\uFF0C\u8BF7\u68C0\u67E5 Claude Code \u767B\u5F55\u72B6\u6001\u6216\u6A21\u578B\u914D\u7F6E\u3002"
46601
+ }
46602
+ });
46603
+ resetAccumulators(proc);
46604
+ onCompleted();
46605
+ break;
46606
+ }
46389
46607
  if (proc.accumulatedText) {
46390
46608
  proc.contentBlocks.push({ type: "text", content: proc.accumulatedText });
46391
46609
  }
@@ -46455,14 +46673,9 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
46455
46673
  if (am2.isApiErrorMessage === true) {
46456
46674
  const base = getTaskBase(proc);
46457
46675
  if (!base) break;
46458
- const errorTexts = [];
46459
- for (const block of am2.message?.content ?? []) {
46460
- if (block?.type === "text" && typeof block.text === "string") {
46461
- errorTexts.push(block.text);
46462
- }
46463
- }
46676
+ const errorTexts = extractAssistantTextParts(am2.message?.content);
46464
46677
  const errorText = errorTexts.join("\n").trim() || `SDK ${am2.error ?? "api_error"}`;
46465
- const isAuthFail = am2.error === "authentication_failed" || /not logged in|please run \/login/i.test(errorText);
46678
+ const isAuthFail = isAuthFailureText(errorText, am2.error);
46466
46679
  if (isAuthFail) {
46467
46680
  sessionStore.delete(proc.agentId, proc.scope);
46468
46681
  }
@@ -46474,7 +46687,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
46474
46687
  isAuthFail,
46475
46688
  traceId: base.traceId
46476
46689
  });
46477
- const friendlyError = isAuthFail ? `Claude Code SDK \u672A\u767B\u5F55\uFF08${errorText}\uFF09\u3002\u8BF7\u5728 bridge \u4E3B\u673A\u4E0A\u914D\u7F6E\u8BA4\u8BC1\uFF1A\u82E5\u8BE5 Agent \u7528 system \u8BA2\u9605\uFF0C\u5C06\u5176 subscriptionType \u8BBE\u4E3A "system"\uFF08\u8D70 ~/.claude\uFF09\uFF1B\u5426\u5219\u5728 ~/.ahchat/claude-config \u4E0B\u767B\u5F55\uFF0C\u6216\u5728 Agent config \u4E2D\u63D0\u4F9B apiKey / apiBaseUrl\u3002` : errorText;
46690
+ const friendlyError = isAuthFail ? buildAuthFailureMessage(errorText) : errorText;
46478
46691
  emit({
46479
46692
  type: "agent:error",
46480
46693
  payload: { ...wireBase(base), error: friendlyError }
@@ -46487,14 +46700,36 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
46487
46700
  break;
46488
46701
  }
46489
46702
  if (proc.accumulatedText.length === 0 && proc.contentBlocks.length === 0) {
46490
- const captured = [];
46491
- for (const block of am2.message?.content ?? []) {
46492
- if (block?.type === "text" && typeof block.text === "string") {
46493
- captured.push(block.text);
46494
- }
46495
- }
46703
+ const captured = extractAssistantTextParts(am2.message?.content);
46496
46704
  if (captured.length > 0) {
46497
46705
  const text = captured.join("");
46706
+ if (isAuthFailureText(text, am2.error)) {
46707
+ const base = getTaskBase(proc);
46708
+ logger7.warn("SDK auth failure assistant detected without api-error flag, emitting agent:error", {
46709
+ agentId: proc.agentId,
46710
+ scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
46711
+ sdkError: am2.error,
46712
+ errorText: text.slice(0, 200),
46713
+ traceId: base?.traceId,
46714
+ hasCurrentTask: base != null
46715
+ });
46716
+ if (base) {
46717
+ sessionStore.delete(proc.agentId, proc.scope);
46718
+ emit({
46719
+ type: "agent:error",
46720
+ payload: {
46721
+ ...wireBase(base),
46722
+ error: buildAuthFailureMessage(text)
46723
+ }
46724
+ });
46725
+ proc.apiErrorEmitted = true;
46726
+ }
46727
+ proc.contentBlocks = [];
46728
+ proc.accumulatedText = "";
46729
+ proc.accumulatedThinking = "";
46730
+ proc.segmentBuffer = "";
46731
+ break;
46732
+ }
46498
46733
  if (isContextOverflowText(text)) {
46499
46734
  const base = getTaskBase(proc);
46500
46735
  logger7.warn("SDK reported context overflow; auto-compact already failed inside SDK", {
@@ -46538,6 +46773,8 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
46538
46773
  textLen: text.length,
46539
46774
  textSample: text.slice(0, 100)
46540
46775
  });
46776
+ } else {
46777
+ proc.lastAssistantContentDescription = describeAssistantContent(am2.message?.content);
46541
46778
  }
46542
46779
  }
46543
46780
  break;
@@ -46561,6 +46798,7 @@ function resetAccumulators(proc) {
46561
46798
  proc.accumulatedToolInput = "";
46562
46799
  proc.apiErrorEmitted = false;
46563
46800
  proc.peakContextUsage = void 0;
46801
+ proc.lastAssistantContentDescription = void 0;
46564
46802
  }
46565
46803
 
46566
46804
  // src/forkHistoryReplay.ts
@@ -46675,8 +46913,15 @@ var wsMetrics = new WsMetrics();
46675
46913
  // src/agentManager.ts
46676
46914
  var logger10 = createModuleLogger("agent.manager");
46677
46915
  var NODE_USER_UID = 1e3;
46916
+ var POST_MERGE_CONTINUATION_ROUTE_MS = 15e3;
46678
46917
  function isSmithAgent(agent) {
46679
- return agent.id === SMITH_AGENT_ID || agent.systemPrompt === SMITH_SYSTEM_PROMPT;
46918
+ if (agent.id === SMITH_AGENT_ID) return true;
46919
+ if (agent.systemPrompt === SMITH_SYSTEM_PROMPT) return true;
46920
+ const prompt = agent.systemPrompt ?? "";
46921
+ if (prompt.includes("\u4F60\u662F\u7279\u5DE5\u53F2\u5BC6\u65AF") && prompt.includes("create_agent")) return true;
46922
+ const name = agent.name?.toLowerCase() ?? "";
46923
+ const role = agent.role?.toLowerCase() ?? "";
46924
+ return role === "system" && (name.includes("\u53F2\u5BC6\u65AF") || name.includes("smith"));
46680
46925
  }
46681
46926
  function isRunningAsRoot() {
46682
46927
  try {
@@ -46685,6 +46930,13 @@ function isRunningAsRoot() {
46685
46930
  return false;
46686
46931
  }
46687
46932
  }
46933
+ async function chownForRootSpawn(targetPath, target) {
46934
+ try {
46935
+ await import_promises8.default.chown(targetPath, NODE_USER_UID, NODE_USER_UID);
46936
+ } catch (error51) {
46937
+ logger10.error("Best-effort root chown failed", { error: error51, target, path: targetPath });
46938
+ }
46939
+ }
46688
46940
  function readCronLockSnapshot() {
46689
46941
  try {
46690
46942
  const lockPath2 = import_node_path8.default.join(import_node_os5.default.homedir(), ".claude", "scheduled_tasks.lock");
@@ -47433,19 +47685,10 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
47433
47685
  options.resume = savedSessionId;
47434
47686
  }
47435
47687
  if (isRunningAsRoot()) {
47436
- try {
47437
- await import_promises8.default.chown(effectiveConfigDir, NODE_USER_UID, NODE_USER_UID);
47438
- } catch {
47439
- }
47440
- try {
47441
- await import_promises8.default.chown(agentCwd, NODE_USER_UID, NODE_USER_UID);
47442
- } catch {
47443
- }
47688
+ await chownForRootSpawn(effectiveConfigDir, "configDir");
47689
+ await chownForRootSpawn(agentCwd, "agentCwd");
47444
47690
  const settingsFilePath = import_node_path8.default.join(effectiveConfigDir, "settings.json");
47445
- try {
47446
- await import_promises8.default.chown(settingsFilePath, NODE_USER_UID, NODE_USER_UID);
47447
- } catch {
47448
- }
47691
+ await chownForRootSpawn(settingsFilePath, "settingsFile");
47449
47692
  options.spawnClaudeCodeProcess = (spawnOptions) => {
47450
47693
  const env2 = { ...spawnOptions.env, HOME: "/home/node" };
47451
47694
  return (0, import_node_child_process.spawn)(spawnOptions.command, spawnOptions.args, {
@@ -47611,7 +47854,7 @@ ${trimmed}`;
47611
47854
  lines.push(` workdir: ${currentCwd}`);
47612
47855
  } else {
47613
47856
  const a = this.agentRegistry?.getById(agentId);
47614
- const singleCwd = a?.workingDirectory || import_node_path8.default.join(import_node_os5.default.homedir(), ".ahchat", `Agent-${a?.name ?? agentId}-${agentId}`);
47857
+ const singleCwd = a?.workingDirectory || import_node_path8.default.join(this.workspacesDir, agentId);
47615
47858
  lines.push(` workdir: ${singleCwd}`);
47616
47859
  }
47617
47860
  let rosterCount = 0;
@@ -47623,7 +47866,7 @@ ${trimmed}`;
47623
47866
  if (key === curKey) {
47624
47867
  lines.push(` workdir: ${currentCwd}`);
47625
47868
  } else {
47626
- const groupCwd = defaultGroupWorkdir(import_node_os5.default.homedir(), g2.name, g2.groupId);
47869
+ const groupCwd = g2.workingDirectory || import_node_path8.default.join(this.workspacesDir, g2.groupId);
47627
47870
  lines.push(` workdir: ${groupCwd}`);
47628
47871
  }
47629
47872
  const others = g2.members.filter((id) => id !== agentId).map((id) => {
@@ -47970,6 +48213,16 @@ ${lines.join("\n")}`;
47970
48213
  }
47971
48214
  }
47972
48215
  async dispatchToSDK(runtime, task) {
48216
+ if (runtime.postMergeContinuationTask) {
48217
+ logger10.info("Clearing stale post-merge continuation route before explicit dispatch", {
48218
+ agentId: runtime.agentId,
48219
+ staleReplyMessageId: runtime.postMergeContinuationTask.replyMessageId,
48220
+ nextReplyMessageId: task.replyMessageId,
48221
+ traceId: task.traceId
48222
+ });
48223
+ delete runtime.postMergeContinuationTask;
48224
+ delete runtime.postMergeContinuationUntil;
48225
+ }
47973
48226
  await this.applyEffortMode(runtime, "high", {
47974
48227
  source: "dispatchToSDK",
47975
48228
  replyMessageId: task.replyMessageId
@@ -48178,6 +48431,19 @@ ${lines.join("\n")}`;
48178
48431
  }
48179
48432
  });
48180
48433
  }
48434
+ const continuationTask = mergedBatch.at(-1);
48435
+ if (continuationTask) {
48436
+ proc.postMergeContinuationTask = continuationTask;
48437
+ proc.postMergeContinuationUntil = Date.now() + POST_MERGE_CONTINUATION_ROUTE_MS;
48438
+ logger10.info("Armed SDK post-merge continuation routing", {
48439
+ agentId: proc.agentId,
48440
+ carrierReplyMessageId: completedTask.replyMessageId,
48441
+ continuationReplyMessageId: continuationTask.replyMessageId,
48442
+ mergedCount: mergedBatch.length,
48443
+ routeUntil: new Date(proc.postMergeContinuationUntil).toISOString(),
48444
+ traceId: completedTask.traceId
48445
+ });
48446
+ }
48181
48447
  runtime.mergedTasks = [];
48182
48448
  } else if (runtime.mergedTasks.length > 0) {
48183
48449
  logger10.warn("mergedTasks non-empty but no currentTask; dropping", {
@@ -51489,6 +51755,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
51489
51755
  agentRegistry,
51490
51756
  subscriptionRegistry,
51491
51757
  serverApiUrl: config2.serverApiUrl,
51758
+ bridgeToken: config2.bridgeToken,
51492
51759
  dataDir: config2.dataDir
51493
51760
  });
51494
51761
  const taskDispatchHandler = createTaskDispatchHandler(agentManager, agentRegistry, emit);
@@ -52096,17 +52363,14 @@ function parseAhchatUrl(url2) {
52096
52363
  }
52097
52364
  }
52098
52365
  async function run(args) {
52099
- let config2 = loadBridgeConfig();
52366
+ const dataDir = args.dataDir ? resolveDataDir(args.dataDir) : void 0;
52367
+ let config2 = loadBridgeConfig(dataDir ? { dataDir } : void 0);
52100
52368
  if (args.serverUrl) {
52101
52369
  const wsUrl = new URL(args.serverUrl);
52102
52370
  const httpBase = `${wsUrl.protocol === "wss:" ? "https" : "http"}://${wsUrl.host}`;
52103
52371
  config2 = { ...config2, serverUrl: args.serverUrl, serverApiUrl: httpBase };
52104
52372
  }
52105
52373
  if (args.token) config2 = { ...config2, bridgeToken: args.token };
52106
- if (args.dataDir) {
52107
- const resolved = resolveDataDir(args.dataDir);
52108
- config2 = { ...config2, dataDir: resolved };
52109
- }
52110
52374
  if (args.logLevel) config2 = { ...config2, logLevel: args.logLevel };
52111
52375
  await startBridge(config2);
52112
52376
  }
package/dist/index.js CHANGED
@@ -3746,8 +3746,8 @@ function resolveAgentConfigDir(dataDir) {
3746
3746
  }
3747
3747
  return newPath;
3748
3748
  }
3749
- function loadBridgeConfig() {
3750
- const dataDir = readEnvString(
3749
+ function loadBridgeConfig(opts) {
3750
+ const dataDir = opts?.dataDir ?? readEnvString(
3751
3751
  "AHCHAT_DATA_DIR",
3752
3752
  path.join(os.homedir(), ".ahchat")
3753
3753
  );
@@ -5851,6 +5851,11 @@ var LOG_LEVEL_VALUE2 = {
5851
5851
  FATAL: 5
5852
5852
  };
5853
5853
 
5854
+ // ../shared/src/types/onboarding.ts
5855
+ function isCapabilityTier(v9) {
5856
+ return v9 === "smart" || v9 === "balanced" || v9 === "fast";
5857
+ }
5858
+
5854
5859
  // ../../node_modules/.pnpm/nanoid@5.1.11/node_modules/nanoid/index.js
5855
5860
  import { webcrypto as crypto2 } from "crypto";
5856
5861
 
@@ -5921,20 +5926,6 @@ function parseWSMessage(raw) {
5921
5926
  return parsed;
5922
5927
  }
5923
5928
 
5924
- // ../shared/src/utils/workdir.ts
5925
- var SLUG_MAX_LEN = 32;
5926
- function slugifyForFs(name) {
5927
- const trimmed = name.trim();
5928
- if (!trimmed) return "unnamed";
5929
- const slug = trimmed.replace(/[^\w\u4e00-\u9fa5 \-]/gu, "_").replace(/\s+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
5930
- const base = slug.length > 0 ? slug : "unnamed";
5931
- return base.length > SLUG_MAX_LEN ? base.slice(0, SLUG_MAX_LEN) : base;
5932
- }
5933
- function defaultGroupWorkdir(home, name, id) {
5934
- const slug = slugifyForFs(name);
5935
- return `${home}/.ahchat/Group-${slug}-${id}`;
5936
- }
5937
-
5938
5929
  // ../shared/src/utils/subscription.ts
5939
5930
  function isSubscriptionType(v9) {
5940
5931
  return v9 === "system" || v9 === "project";
@@ -5949,6 +5940,7 @@ function parseAgentConfig(raw) {
5949
5940
  const out = {};
5950
5941
  const obj = v9;
5951
5942
  if (typeof obj.model === "string" && obj.model.trim()) out.model = obj.model.trim();
5943
+ if (isCapabilityTier(obj.capabilityTier)) out.capabilityTier = obj.capabilityTier;
5952
5944
  if (typeof obj.subscriptionId === "string") {
5953
5945
  const trimmed = obj.subscriptionId.trim();
5954
5946
  if (/^sub_[A-Za-z0-9_-]{1,64}$/.test(trimmed)) {
@@ -44055,9 +44047,12 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
44055
44047
  const tierRes = await fetch(tierUrl);
44056
44048
  if (tierRes.ok) {
44057
44049
  const tiers = await tierRes.json();
44058
- const tierConfig = tiers.find((t2) => t2.tier === tier);
44050
+ const self2 = deps.agentRegistry?.getById(deps.agentId);
44051
+ const preferredSubscriptionId = parseAgentConfig(self2?.config).subscriptionId;
44052
+ const tierConfig = tiers.find((t2) => t2.tier === tier && t2.subscriptionId === preferredSubscriptionId) ?? tiers.find((t2) => t2.tier === tier);
44059
44053
  if (tierConfig) {
44060
44054
  agentConfig = JSON.stringify({
44055
+ capabilityTier: tier,
44061
44056
  subscriptionId: tierConfig.subscriptionId,
44062
44057
  model: tierConfig.modelName
44063
44058
  });
@@ -44209,9 +44204,11 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
44209
44204
  const tierRes = await fetch(tierUrl);
44210
44205
  if (tierRes.ok) {
44211
44206
  const tiers = await tierRes.json();
44212
- const tierConfig = tiers.find((t2) => t2.tier === tier);
44207
+ const preferredSubscriptionId = parseAgentConfig(existing.config).subscriptionId;
44208
+ const tierConfig = tiers.find((t2) => t2.tier === tier && t2.subscriptionId === preferredSubscriptionId) ?? tiers.find((t2) => t2.tier === tier);
44213
44209
  if (tierConfig) {
44214
44210
  agentConfig = JSON.stringify({
44211
+ capabilityTier: tier,
44215
44212
  subscriptionId: tierConfig.subscriptionId,
44216
44213
  model: tierConfig.modelName
44217
44214
  });
@@ -44943,11 +44940,117 @@ function buildGroupInboxPrompt(entries, opts = {}) {
44943
44940
  var logger7 = createModuleLogger("sdk.mapper");
44944
44941
  var HIGH_WATERMARK_INPUT_TOKENS = 12e4;
44945
44942
  var WARN_THRESHOLD_INPUT_TOKENS = 1e5;
44943
+ var LIVE_INPUT_PREVIEW_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit"]);
44946
44944
  var CONTEXT_OVERFLOW_LOCK_MS = 6e4;
44947
44945
  function isContextOverflowText(text) {
44948
44946
  const trimmed = text.trim();
44949
44947
  return /^prompt is too long\b/i.test(trimmed) || /context length .* exceed/i.test(trimmed);
44950
44948
  }
44949
+ function isAuthFailureText(text, sdkError) {
44950
+ return sdkError === "authentication_failed" || /not logged in|please run \/login/i.test(text);
44951
+ }
44952
+ function buildAuthFailureMessage(errorText) {
44953
+ return `Claude Code SDK \u672A\u767B\u5F55\uFF08${errorText}\uFF09\u3002\u8BF7\u5728 bridge \u4E3B\u673A\u4E0A\u914D\u7F6E\u8BA4\u8BC1\uFF1A\u82E5\u8BE5 Agent \u7528 system \u8BA2\u9605\uFF0C\u5C06\u5176 subscriptionType \u8BBE\u4E3A "system"\uFF08\u8D70 ~/.claude\uFF09\uFF1B\u5426\u5219\u5728 ~/.ahchat/claude-config \u4E0B\u767B\u5F55\uFF0C\u6216\u5728 Agent config \u4E2D\u63D0\u4F9B apiKey / apiBaseUrl\u3002`;
44954
+ }
44955
+ function decodeJsonStringFragment(raw) {
44956
+ let out = "";
44957
+ for (let i = 0; i < raw.length; i++) {
44958
+ const ch2 = raw[i];
44959
+ if (ch2 !== "\\") {
44960
+ out += ch2;
44961
+ continue;
44962
+ }
44963
+ const next = raw[i + 1];
44964
+ if (next === void 0) break;
44965
+ i += 1;
44966
+ switch (next) {
44967
+ case '"':
44968
+ case "\\":
44969
+ case "/":
44970
+ out += next;
44971
+ break;
44972
+ case "b":
44973
+ out += "\b";
44974
+ break;
44975
+ case "f":
44976
+ out += "\f";
44977
+ break;
44978
+ case "n":
44979
+ out += "\n";
44980
+ break;
44981
+ case "r":
44982
+ out += "\r";
44983
+ break;
44984
+ case "t":
44985
+ out += " ";
44986
+ break;
44987
+ case "u": {
44988
+ const hex3 = raw.slice(i + 1, i + 5);
44989
+ if (!/^[0-9a-fA-F]{4}$/.test(hex3)) return out;
44990
+ out += String.fromCharCode(Number.parseInt(hex3, 16));
44991
+ i += 4;
44992
+ break;
44993
+ }
44994
+ default:
44995
+ out += next;
44996
+ }
44997
+ }
44998
+ return out;
44999
+ }
45000
+ function extractJsonStringPrefix(source, key) {
45001
+ const keyToken = `"${key}"`;
45002
+ const keyIndex = source.indexOf(keyToken);
45003
+ if (keyIndex < 0) return null;
45004
+ let i = keyIndex + keyToken.length;
45005
+ while (i < source.length && /\s/.test(source[i] ?? "")) i += 1;
45006
+ if (source[i] !== ":") return null;
45007
+ i += 1;
45008
+ while (i < source.length && /\s/.test(source[i] ?? "")) i += 1;
45009
+ if (source[i] !== '"') return null;
45010
+ i += 1;
45011
+ let raw = "";
45012
+ let escaped = false;
45013
+ for (; i < source.length; i++) {
45014
+ const ch2 = source[i];
45015
+ if (ch2 === void 0) break;
45016
+ if (escaped) {
45017
+ raw += `\\${ch2}`;
45018
+ escaped = false;
45019
+ continue;
45020
+ }
45021
+ if (ch2 === "\\") {
45022
+ escaped = true;
45023
+ continue;
45024
+ }
45025
+ if (ch2 === '"') {
45026
+ return decodeJsonStringFragment(raw);
45027
+ }
45028
+ raw += ch2;
45029
+ }
45030
+ if (escaped) raw += "\\";
45031
+ return decodeJsonStringFragment(raw);
45032
+ }
45033
+ function extractLiveToolInput(toolName, inputJson) {
45034
+ if (toolName === "Write") {
45035
+ const content = extractJsonStringPrefix(inputJson, "content");
45036
+ if (content == null) return null;
45037
+ const filePath = extractJsonStringPrefix(inputJson, "file_path");
45038
+ return filePath == null ? { content } : { file_path: filePath, content };
45039
+ }
45040
+ if (toolName === "Edit") {
45041
+ const newString = extractJsonStringPrefix(inputJson, "new_string");
45042
+ if (newString == null) return null;
45043
+ const oldString = extractJsonStringPrefix(inputJson, "old_string");
45044
+ const filePath = extractJsonStringPrefix(inputJson, "file_path");
45045
+ const input = {
45046
+ new_string: newString
45047
+ };
45048
+ if (filePath != null) input.file_path = filePath;
45049
+ if (oldString != null) input.old_string = oldString;
45050
+ return input;
45051
+ }
45052
+ return null;
45053
+ }
44951
45054
  function recordAssistantContextUsage(proc, am2) {
44952
45055
  const u = am2.message?.usage;
44953
45056
  if (!u) return;
@@ -45015,6 +45118,45 @@ function cleanupPlanMode(proc, emit, base, reason) {
45015
45118
  });
45016
45119
  proc.planModeActive = false;
45017
45120
  }
45121
+ function extractAssistantTextParts(content, depth = 0) {
45122
+ if (depth > 4) return [];
45123
+ if (typeof content === "string") return [content];
45124
+ if (Array.isArray(content)) {
45125
+ return content.flatMap((item) => extractAssistantTextParts(item, depth + 1));
45126
+ }
45127
+ if (typeof content !== "object" || content === null) return [];
45128
+ const block = content;
45129
+ const type = typeof block.type === "string" ? block.type : null;
45130
+ if (type === "tool_use" || type === "tool_result") return [];
45131
+ if (type === "text" || type === "output_text" || type == null) {
45132
+ const textParts = extractAssistantTextParts(block.text, depth + 1);
45133
+ if (textParts.length > 0) return textParts;
45134
+ return extractAssistantTextParts(block.content, depth + 1);
45135
+ }
45136
+ return [];
45137
+ }
45138
+ function describeAssistantContent(content) {
45139
+ if (Array.isArray(content)) {
45140
+ return {
45141
+ contentShape: "array",
45142
+ blockTypes: content.map((item) => {
45143
+ if (typeof item !== "object" || item === null) return typeof item;
45144
+ const type = item.type;
45145
+ return typeof type === "string" ? type : "(no-type)";
45146
+ }).slice(0, 12)
45147
+ };
45148
+ }
45149
+ if (typeof content === "object" && content !== null) {
45150
+ return {
45151
+ contentShape: "object",
45152
+ contentKeys: Object.keys(content).slice(0, 12)
45153
+ };
45154
+ }
45155
+ return { contentShape: typeof content };
45156
+ }
45157
+ function scopeLogLabel(scope) {
45158
+ return scope.kind === "single" ? "single" : scope.groupId;
45159
+ }
45018
45160
  function getTaskBase(proc) {
45019
45161
  if (proc.currentTask) {
45020
45162
  return {
@@ -45024,10 +45166,42 @@ function getTaskBase(proc) {
45024
45166
  replyMessageId: proc.currentTask.replyMessageId
45025
45167
  };
45026
45168
  }
45169
+ const continuation = proc.postMergeContinuationTask;
45170
+ const continuationUntil = proc.postMergeContinuationUntil ?? 0;
45171
+ if (continuation) {
45172
+ if (Date.now() <= continuationUntil) {
45173
+ proc.currentTask = continuation;
45174
+ proc.status = "working";
45175
+ proc.currentTaskStartedAt = Date.now();
45176
+ delete proc.postMergeContinuationTask;
45177
+ delete proc.postMergeContinuationUntil;
45178
+ logger7.info("SDK post-merge continuation routed to merged task", {
45179
+ agentId: proc.agentId,
45180
+ scope: scopeLogLabel(proc.scope),
45181
+ replyMessageId: continuation.replyMessageId,
45182
+ conversationId: continuation.conversationId,
45183
+ traceId: continuation.traceId
45184
+ });
45185
+ return {
45186
+ agentId: proc.agentId,
45187
+ conversationId: continuation.conversationId,
45188
+ traceId: continuation.traceId,
45189
+ replyMessageId: continuation.replyMessageId
45190
+ };
45191
+ }
45192
+ logger7.info("SDK post-merge continuation route expired", {
45193
+ agentId: proc.agentId,
45194
+ scope: scopeLogLabel(proc.scope),
45195
+ replyMessageId: continuation.replyMessageId,
45196
+ routeUntil: new Date(continuationUntil).toISOString()
45197
+ });
45198
+ delete proc.postMergeContinuationTask;
45199
+ delete proc.postMergeContinuationUntil;
45200
+ }
45027
45201
  if (!proc.cachedConversationId) {
45028
45202
  logger7.warn("SDK self-initiated turn without cachedConversationId; dropping", {
45029
45203
  agentId: proc.agentId,
45030
- scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId
45204
+ scope: scopeLogLabel(proc.scope)
45031
45205
  });
45032
45206
  return null;
45033
45207
  }
@@ -45044,7 +45218,7 @@ function getTaskBase(proc) {
45044
45218
  proc.currentTaskStartedAt = Date.now();
45045
45219
  logger7.info("Cron-initiated turn detected, synthesized task", {
45046
45220
  agentId: proc.agentId,
45047
- scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
45221
+ scope: scopeLogLabel(proc.scope),
45048
45222
  replyMessageId,
45049
45223
  conversationId: proc.cachedConversationId,
45050
45224
  traceId
@@ -45288,6 +45462,17 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
45288
45462
  const partial2 = delta.partial_json;
45289
45463
  if (typeof partial2 === "string") {
45290
45464
  proc.accumulatedToolInput += partial2;
45465
+ const liveInput = extractLiveToolInput(proc.currentToolName, proc.accumulatedToolInput);
45466
+ if (liveInput && proc.currentToolName != null) {
45467
+ emit({
45468
+ type: "agent:tool_input_update",
45469
+ payload: {
45470
+ ...wireBase(base),
45471
+ toolName: proc.currentToolName,
45472
+ input: liveInput
45473
+ }
45474
+ });
45475
+ }
45291
45476
  }
45292
45477
  } else if (delta.type === "text_delta" && typeof delta.text === "string") {
45293
45478
  if (proc.accumulatedText.length === 0) {
@@ -45342,6 +45527,16 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
45342
45527
  if (lastToolUse && lastToolUse.type === "tool_use") {
45343
45528
  lastToolUse.input = parsedInput;
45344
45529
  }
45530
+ if (proc.currentToolName != null && LIVE_INPUT_PREVIEW_TOOLS.has(proc.currentToolName) && Object.keys(parsedInput).length > 0) {
45531
+ emit({
45532
+ type: "agent:tool_input_update",
45533
+ payload: {
45534
+ ...wireBase(base),
45535
+ toolName: proc.currentToolName,
45536
+ input: parsedInput
45537
+ }
45538
+ });
45539
+ }
45345
45540
  if (proc.currentToolName === "TodoWrite") {
45346
45541
  const todos = extractTodosFromInput(parsedInput);
45347
45542
  if (todos) {
@@ -45586,6 +45781,29 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
45586
45781
  onCompleted();
45587
45782
  break;
45588
45783
  }
45784
+ if (proc.accumulatedText.length === 0 && proc.contentBlocks.length === 0) {
45785
+ cleanupPlanMode(proc, emit, base, "error");
45786
+ logger7.warn("SDK success produced empty assistant output; emitting agent:error", {
45787
+ agentId: proc.agentId,
45788
+ ackId: base.replyMessageId,
45789
+ tokenCount: usage.tokenCount,
45790
+ inputTokens: usage.inputTokens,
45791
+ cacheReadTokens: usage.cacheReadTokens,
45792
+ cacheCreationTokens: usage.cacheCreationTokens,
45793
+ assistantContent: proc.lastAssistantContentDescription,
45794
+ traceId: base.traceId
45795
+ });
45796
+ emit({
45797
+ type: "agent:error",
45798
+ payload: {
45799
+ ...wireBase(base),
45800
+ error: "Agent \u8FD4\u56DE\u4E86\u7A7A\u7ED3\u679C\uFF0C\u5DF2\u963B\u6B62\u7A7A\u6D88\u606F\u8986\u76D6\u601D\u8003\u6C14\u6CE1\u3002\u8BF7\u91CD\u8BD5\uFF1B\u5982\u679C\u53CD\u590D\u51FA\u73B0\uFF0C\u8BF7\u68C0\u67E5 Claude Code \u767B\u5F55\u72B6\u6001\u6216\u6A21\u578B\u914D\u7F6E\u3002"
45801
+ }
45802
+ });
45803
+ resetAccumulators(proc);
45804
+ onCompleted();
45805
+ break;
45806
+ }
45589
45807
  if (proc.accumulatedText) {
45590
45808
  proc.contentBlocks.push({ type: "text", content: proc.accumulatedText });
45591
45809
  }
@@ -45655,14 +45873,9 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
45655
45873
  if (am2.isApiErrorMessage === true) {
45656
45874
  const base = getTaskBase(proc);
45657
45875
  if (!base) break;
45658
- const errorTexts = [];
45659
- for (const block of am2.message?.content ?? []) {
45660
- if (block?.type === "text" && typeof block.text === "string") {
45661
- errorTexts.push(block.text);
45662
- }
45663
- }
45876
+ const errorTexts = extractAssistantTextParts(am2.message?.content);
45664
45877
  const errorText = errorTexts.join("\n").trim() || `SDK ${am2.error ?? "api_error"}`;
45665
- const isAuthFail = am2.error === "authentication_failed" || /not logged in|please run \/login/i.test(errorText);
45878
+ const isAuthFail = isAuthFailureText(errorText, am2.error);
45666
45879
  if (isAuthFail) {
45667
45880
  sessionStore.delete(proc.agentId, proc.scope);
45668
45881
  }
@@ -45674,7 +45887,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
45674
45887
  isAuthFail,
45675
45888
  traceId: base.traceId
45676
45889
  });
45677
- const friendlyError = isAuthFail ? `Claude Code SDK \u672A\u767B\u5F55\uFF08${errorText}\uFF09\u3002\u8BF7\u5728 bridge \u4E3B\u673A\u4E0A\u914D\u7F6E\u8BA4\u8BC1\uFF1A\u82E5\u8BE5 Agent \u7528 system \u8BA2\u9605\uFF0C\u5C06\u5176 subscriptionType \u8BBE\u4E3A "system"\uFF08\u8D70 ~/.claude\uFF09\uFF1B\u5426\u5219\u5728 ~/.ahchat/claude-config \u4E0B\u767B\u5F55\uFF0C\u6216\u5728 Agent config \u4E2D\u63D0\u4F9B apiKey / apiBaseUrl\u3002` : errorText;
45890
+ const friendlyError = isAuthFail ? buildAuthFailureMessage(errorText) : errorText;
45678
45891
  emit({
45679
45892
  type: "agent:error",
45680
45893
  payload: { ...wireBase(base), error: friendlyError }
@@ -45687,14 +45900,36 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
45687
45900
  break;
45688
45901
  }
45689
45902
  if (proc.accumulatedText.length === 0 && proc.contentBlocks.length === 0) {
45690
- const captured = [];
45691
- for (const block of am2.message?.content ?? []) {
45692
- if (block?.type === "text" && typeof block.text === "string") {
45693
- captured.push(block.text);
45694
- }
45695
- }
45903
+ const captured = extractAssistantTextParts(am2.message?.content);
45696
45904
  if (captured.length > 0) {
45697
45905
  const text = captured.join("");
45906
+ if (isAuthFailureText(text, am2.error)) {
45907
+ const base = getTaskBase(proc);
45908
+ logger7.warn("SDK auth failure assistant detected without api-error flag, emitting agent:error", {
45909
+ agentId: proc.agentId,
45910
+ scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
45911
+ sdkError: am2.error,
45912
+ errorText: text.slice(0, 200),
45913
+ traceId: base?.traceId,
45914
+ hasCurrentTask: base != null
45915
+ });
45916
+ if (base) {
45917
+ sessionStore.delete(proc.agentId, proc.scope);
45918
+ emit({
45919
+ type: "agent:error",
45920
+ payload: {
45921
+ ...wireBase(base),
45922
+ error: buildAuthFailureMessage(text)
45923
+ }
45924
+ });
45925
+ proc.apiErrorEmitted = true;
45926
+ }
45927
+ proc.contentBlocks = [];
45928
+ proc.accumulatedText = "";
45929
+ proc.accumulatedThinking = "";
45930
+ proc.segmentBuffer = "";
45931
+ break;
45932
+ }
45698
45933
  if (isContextOverflowText(text)) {
45699
45934
  const base = getTaskBase(proc);
45700
45935
  logger7.warn("SDK reported context overflow; auto-compact already failed inside SDK", {
@@ -45738,6 +45973,8 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
45738
45973
  textLen: text.length,
45739
45974
  textSample: text.slice(0, 100)
45740
45975
  });
45976
+ } else {
45977
+ proc.lastAssistantContentDescription = describeAssistantContent(am2.message?.content);
45741
45978
  }
45742
45979
  }
45743
45980
  break;
@@ -45761,6 +45998,7 @@ function resetAccumulators(proc) {
45761
45998
  proc.accumulatedToolInput = "";
45762
45999
  proc.apiErrorEmitted = false;
45763
46000
  proc.peakContextUsage = void 0;
46001
+ proc.lastAssistantContentDescription = void 0;
45764
46002
  }
45765
46003
 
45766
46004
  // src/forkHistoryReplay.ts
@@ -45873,8 +46111,15 @@ var wsMetrics = new WsMetrics();
45873
46111
  // src/agentManager.ts
45874
46112
  var logger10 = createModuleLogger("agent.manager");
45875
46113
  var NODE_USER_UID = 1e3;
46114
+ var POST_MERGE_CONTINUATION_ROUTE_MS = 15e3;
45876
46115
  function isSmithAgent(agent) {
45877
- return agent.id === SMITH_AGENT_ID || agent.systemPrompt === SMITH_SYSTEM_PROMPT;
46116
+ if (agent.id === SMITH_AGENT_ID) return true;
46117
+ if (agent.systemPrompt === SMITH_SYSTEM_PROMPT) return true;
46118
+ const prompt = agent.systemPrompt ?? "";
46119
+ if (prompt.includes("\u4F60\u662F\u7279\u5DE5\u53F2\u5BC6\u65AF") && prompt.includes("create_agent")) return true;
46120
+ const name = agent.name?.toLowerCase() ?? "";
46121
+ const role = agent.role?.toLowerCase() ?? "";
46122
+ return role === "system" && (name.includes("\u53F2\u5BC6\u65AF") || name.includes("smith"));
45878
46123
  }
45879
46124
  function isRunningAsRoot() {
45880
46125
  try {
@@ -45883,6 +46128,13 @@ function isRunningAsRoot() {
45883
46128
  return false;
45884
46129
  }
45885
46130
  }
46131
+ async function chownForRootSpawn(targetPath, target) {
46132
+ try {
46133
+ await fs4.chown(targetPath, NODE_USER_UID, NODE_USER_UID);
46134
+ } catch (error51) {
46135
+ logger10.error("Best-effort root chown failed", { error: error51, target, path: targetPath });
46136
+ }
46137
+ }
45886
46138
  function readCronLockSnapshot() {
45887
46139
  try {
45888
46140
  const lockPath2 = path8.join(os5.homedir(), ".claude", "scheduled_tasks.lock");
@@ -46631,19 +46883,10 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
46631
46883
  options.resume = savedSessionId;
46632
46884
  }
46633
46885
  if (isRunningAsRoot()) {
46634
- try {
46635
- await fs4.chown(effectiveConfigDir, NODE_USER_UID, NODE_USER_UID);
46636
- } catch {
46637
- }
46638
- try {
46639
- await fs4.chown(agentCwd, NODE_USER_UID, NODE_USER_UID);
46640
- } catch {
46641
- }
46886
+ await chownForRootSpawn(effectiveConfigDir, "configDir");
46887
+ await chownForRootSpawn(agentCwd, "agentCwd");
46642
46888
  const settingsFilePath = path8.join(effectiveConfigDir, "settings.json");
46643
- try {
46644
- await fs4.chown(settingsFilePath, NODE_USER_UID, NODE_USER_UID);
46645
- } catch {
46646
- }
46889
+ await chownForRootSpawn(settingsFilePath, "settingsFile");
46647
46890
  options.spawnClaudeCodeProcess = (spawnOptions) => {
46648
46891
  const env2 = { ...spawnOptions.env, HOME: "/home/node" };
46649
46892
  return nodeSpawn(spawnOptions.command, spawnOptions.args, {
@@ -46809,7 +47052,7 @@ ${trimmed}`;
46809
47052
  lines.push(` workdir: ${currentCwd}`);
46810
47053
  } else {
46811
47054
  const a = this.agentRegistry?.getById(agentId);
46812
- const singleCwd = a?.workingDirectory || path8.join(os5.homedir(), ".ahchat", `Agent-${a?.name ?? agentId}-${agentId}`);
47055
+ const singleCwd = a?.workingDirectory || path8.join(this.workspacesDir, agentId);
46813
47056
  lines.push(` workdir: ${singleCwd}`);
46814
47057
  }
46815
47058
  let rosterCount = 0;
@@ -46821,7 +47064,7 @@ ${trimmed}`;
46821
47064
  if (key === curKey) {
46822
47065
  lines.push(` workdir: ${currentCwd}`);
46823
47066
  } else {
46824
- const groupCwd = defaultGroupWorkdir(os5.homedir(), g2.name, g2.groupId);
47067
+ const groupCwd = g2.workingDirectory || path8.join(this.workspacesDir, g2.groupId);
46825
47068
  lines.push(` workdir: ${groupCwd}`);
46826
47069
  }
46827
47070
  const others = g2.members.filter((id) => id !== agentId).map((id) => {
@@ -47168,6 +47411,16 @@ ${lines.join("\n")}`;
47168
47411
  }
47169
47412
  }
47170
47413
  async dispatchToSDK(runtime, task) {
47414
+ if (runtime.postMergeContinuationTask) {
47415
+ logger10.info("Clearing stale post-merge continuation route before explicit dispatch", {
47416
+ agentId: runtime.agentId,
47417
+ staleReplyMessageId: runtime.postMergeContinuationTask.replyMessageId,
47418
+ nextReplyMessageId: task.replyMessageId,
47419
+ traceId: task.traceId
47420
+ });
47421
+ delete runtime.postMergeContinuationTask;
47422
+ delete runtime.postMergeContinuationUntil;
47423
+ }
47171
47424
  await this.applyEffortMode(runtime, "high", {
47172
47425
  source: "dispatchToSDK",
47173
47426
  replyMessageId: task.replyMessageId
@@ -47376,6 +47629,19 @@ ${lines.join("\n")}`;
47376
47629
  }
47377
47630
  });
47378
47631
  }
47632
+ const continuationTask = mergedBatch.at(-1);
47633
+ if (continuationTask) {
47634
+ proc.postMergeContinuationTask = continuationTask;
47635
+ proc.postMergeContinuationUntil = Date.now() + POST_MERGE_CONTINUATION_ROUTE_MS;
47636
+ logger10.info("Armed SDK post-merge continuation routing", {
47637
+ agentId: proc.agentId,
47638
+ carrierReplyMessageId: completedTask.replyMessageId,
47639
+ continuationReplyMessageId: continuationTask.replyMessageId,
47640
+ mergedCount: mergedBatch.length,
47641
+ routeUntil: new Date(proc.postMergeContinuationUntil).toISOString(),
47642
+ traceId: completedTask.traceId
47643
+ });
47644
+ }
47379
47645
  runtime.mergedTasks = [];
47380
47646
  } else if (runtime.mergedTasks.length > 0) {
47381
47647
  logger10.warn("mergedTasks non-empty but no currentTask; dropping", {
@@ -50665,6 +50931,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
50665
50931
  agentRegistry,
50666
50932
  subscriptionRegistry,
50667
50933
  serverApiUrl: config2.serverApiUrl,
50934
+ bridgeToken: config2.bridgeToken,
50668
50935
  dataDir: config2.dataDir
50669
50936
  });
50670
50937
  const taskDispatchHandler = createTaskDispatchHandler(agentManager, agentRegistry, emit);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fangyb/ahchat-bridge",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "files": [
5
5
  "dist"
6
6
  ],