@fangyb/ahchat-bridge 0.1.25 → 0.1.27

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 +2480 -655
  2. package/dist/index.js +2290 -484
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1313,14 +1313,14 @@ var require_receiver = __commonJS({
1313
1313
  * @return {(Error|RangeError)} The error
1314
1314
  * @private
1315
1315
  */
1316
- createError(ErrorCtor, message, prefix, statusCode, errorCode) {
1316
+ createError(ErrorCtor, message, prefix, statusCode, errorCode2) {
1317
1317
  this._loop = false;
1318
1318
  this._errored = true;
1319
1319
  const err = new ErrorCtor(
1320
1320
  prefix ? `Invalid WebSocket frame: ${message}` : message
1321
1321
  );
1322
1322
  Error.captureStackTrace(err, this.createError);
1323
- err.code = errorCode;
1323
+ err.code = errorCode2;
1324
1324
  err[kStatusCode] = statusCode;
1325
1325
  return err;
1326
1326
  }
@@ -3651,8 +3651,8 @@ function readEnvInt(name, fallback) {
3651
3651
  const n = Number.parseInt(v, 10);
3652
3652
  return Number.isFinite(n) && n > 0 ? n : fallback;
3653
3653
  }
3654
- function generateStableBridgeId() {
3655
- const raw = `${os.hostname()}:${os.userInfo().username}`;
3654
+ function generateStableBridgeId(dataDir) {
3655
+ const raw = `${os.hostname()}:${os.userInfo().username}:${path.resolve(dataDir)}`;
3656
3656
  const hash2 = crypto.createHash("sha256").update(raw).digest("hex").slice(0, 12);
3657
3657
  return `bridge_${hash2}`;
3658
3658
  }
@@ -3730,7 +3730,7 @@ function loadBridgeConfig(opts) {
3730
3730
  ),
3731
3731
  bridgeId: readEnvString(
3732
3732
  "AHCHAT_BRIDGE_ID",
3733
- fileConfig.bridgeId ?? generateStableBridgeId()
3733
+ fileConfig.bridgeId ?? generateStableBridgeId(dataDir)
3734
3734
  ),
3735
3735
  bridgeToken: readEnvString(
3736
3736
  "AHCHAT_BRIDGE_TOKEN",
@@ -3757,16 +3757,24 @@ function loadBridgeConfig(opts) {
3757
3757
  "AHCHAT_DEFAULT_MODEL",
3758
3758
  fileConfig.defaultModel ?? ""
3759
3759
  ) || null,
3760
+ logUploadIntervalMs: readEnvInt(
3761
+ "AHCHAT_LOG_UPLOAD_INTERVAL_MS",
3762
+ fileConfig.logUploadIntervalMs ?? 24 * 60 * 60 * 1e3
3763
+ ),
3760
3764
  queryConfig: mergeQueryConfig(fileConfig)
3761
3765
  };
3762
3766
  }
3763
3767
  function ensureDir(dirPath) {
3764
- fs.mkdirSync(dirPath, { recursive: true });
3768
+ fs.mkdirSync(dirPath, { recursive: true, mode: 448 });
3769
+ try {
3770
+ fs.chmodSync(dirPath, 448);
3771
+ } catch {
3772
+ }
3765
3773
  }
3766
3774
 
3767
- // src/logger.ts
3768
- import os3 from "os";
3769
- import path3 from "path";
3775
+ // src/start.ts
3776
+ import os12 from "os";
3777
+ import path25 from "path";
3770
3778
 
3771
3779
  // ../logger/src/types.ts
3772
3780
  var LOG_LEVEL_VALUE = {
@@ -3777,35 +3785,74 @@ var LOG_LEVEL_VALUE = {
3777
3785
  ERROR: 4,
3778
3786
  FATAL: 5
3779
3787
  };
3788
+ function parseLogLevel(value, fallback = "INFO") {
3789
+ const upper = value?.trim().toUpperCase();
3790
+ return upper && upper in LOG_LEVEL_VALUE ? upper : fallback;
3791
+ }
3780
3792
 
3781
3793
  // ../logger/src/logger.ts
3794
+ var REDACTED = "***";
3795
+ var SENSITIVE_KEY_RE = /(token|apikey|authorization|password|secret|cookie)/i;
3796
+ function shouldRedactKey(key) {
3797
+ const normalized = key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
3798
+ if (normalized.startsWith("has")) return false;
3799
+ if (normalized.endsWith("hash")) return false;
3800
+ return SENSITIVE_KEY_RE.test(normalized);
3801
+ }
3802
+ function redactString(value) {
3803
+ return value.replace(/([?&](?:token|api_?key|access_token|refresh_token)=)[^&#\s"']+/gi, `$1${REDACTED}`).replace(/("(?:apiKey|token|bridgeToken|authorization|password|secret|cookie|access_token|refresh_token)"\s*:\s*")[^"]*/gi, `$1${REDACTED}`).replace(/((?:apiKey|token|bridgeToken|authorization|password|secret|cookie|access_token|refresh_token)=)[^\s&"']+/gi, `$1${REDACTED}`).replace(/\b(Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, `$1${REDACTED}`).replace(/\bsk-[A-Za-z0-9._-]{6,}\b/g, "sk-***");
3804
+ }
3782
3805
  function serializeError(err) {
3783
3806
  if (err instanceof Error) {
3784
- return { name: err.name, message: err.message, stack: err.stack };
3807
+ return {
3808
+ name: err.name,
3809
+ message: redactString(err.message),
3810
+ ...err.stack ? { stack: redactString(err.stack) } : {}
3811
+ };
3785
3812
  }
3786
3813
  if (typeof err === "object" && err !== null && "message" in err) {
3787
3814
  const o = err;
3788
3815
  return {
3789
3816
  name: typeof o.name === "string" ? o.name : "Error",
3790
- message: String(o.message),
3791
- ...typeof o.stack === "string" ? { stack: o.stack } : {}
3817
+ message: redactString(String(o.message)),
3818
+ ...typeof o.stack === "string" ? { stack: redactString(o.stack) } : {}
3792
3819
  };
3793
3820
  }
3794
- return { name: "Error", message: String(err) };
3821
+ return { name: "Error", message: redactString(String(err)) };
3822
+ }
3823
+ function isPlainObject(value) {
3824
+ if (typeof value !== "object" || value === null) return false;
3825
+ const proto2 = Object.getPrototypeOf(value);
3826
+ return proto2 === Object.prototype || proto2 === null;
3827
+ }
3828
+ function redactValue(value, key, seen) {
3829
+ if (key && shouldRedactKey(key)) return REDACTED;
3830
+ if (typeof value === "string") return redactString(value);
3831
+ if (value instanceof Error) return serializeError(value);
3832
+ if (Array.isArray(value)) return value.map((item) => redactValue(item, void 0, seen));
3833
+ if (!isPlainObject(value)) return value;
3834
+ if (seen.has(value)) return "[Circular]";
3835
+ seen.add(value);
3836
+ const out = {};
3837
+ for (const [childKey, childValue] of Object.entries(value)) {
3838
+ out[childKey] = redactValue(childValue, childKey, seen);
3839
+ }
3840
+ return out;
3795
3841
  }
3796
3842
  function stripReservedFields(data) {
3797
3843
  if (!data) return void 0;
3798
3844
  const rest = { ...data };
3799
3845
  delete rest.traceId;
3800
3846
  delete rest.error;
3801
- return Object.keys(rest).length > 0 ? rest : void 0;
3847
+ if (Object.keys(rest).length === 0) return void 0;
3848
+ return redactValue(rest, void 0, /* @__PURE__ */ new WeakSet());
3802
3849
  }
3803
3850
  var Logger = class {
3804
3851
  config;
3805
3852
  levelValue;
3806
3853
  constructor(config2) {
3807
3854
  this.config = config2;
3808
- this.levelValue = LOG_LEVEL_VALUE[config2.level];
3855
+ this.levelValue = LOG_LEVEL_VALUE[config2.level] ?? LOG_LEVEL_VALUE.INFO;
3809
3856
  }
3810
3857
  trace(msg, data) {
3811
3858
  this.log("TRACE", msg, data);
@@ -3827,7 +3874,7 @@ var Logger = class {
3827
3874
  }
3828
3875
  log(level, msg, data) {
3829
3876
  if (LOG_LEVEL_VALUE[level] < this.levelValue) return;
3830
- const traceId = typeof data?.traceId === "string" ? data.traceId : void 0;
3877
+ const traceId = typeof data?.traceId === "string" ? redactString(data.traceId) : void 0;
3831
3878
  const hasError = data && "error" in data && data.error !== void 0;
3832
3879
  const error51 = hasError ? serializeError(data.error) : void 0;
3833
3880
  const rest = stripReservedFields(data);
@@ -3842,13 +3889,42 @@ var Logger = class {
3842
3889
  ...rest ? { data: rest } : {}
3843
3890
  };
3844
3891
  for (const transport of this.config.transports) {
3845
- transport(entry);
3892
+ try {
3893
+ transport(entry);
3894
+ } catch (e) {
3895
+ console.error("[logger] transport failed", e);
3896
+ }
3846
3897
  }
3847
3898
  }
3848
3899
  };
3849
3900
 
3850
3901
  // ../logger/src/formatters/json.ts
3851
- var jsonFormatter = (entry) => JSON.stringify(entry);
3902
+ function safeReplacer() {
3903
+ const seen = /* @__PURE__ */ new WeakSet();
3904
+ return (_key, value) => {
3905
+ if (typeof value === "bigint") return value.toString();
3906
+ if (typeof value === "object" && value !== null) {
3907
+ if (seen.has(value)) return "[Circular]";
3908
+ seen.add(value);
3909
+ }
3910
+ return value;
3911
+ };
3912
+ }
3913
+ var jsonFormatter = (entry) => {
3914
+ try {
3915
+ return JSON.stringify(entry, safeReplacer());
3916
+ } catch (e) {
3917
+ return JSON.stringify({
3918
+ ts: entry.ts,
3919
+ level: entry.level,
3920
+ source: entry.source,
3921
+ module: entry.module,
3922
+ msg: entry.msg,
3923
+ ...entry.traceId ? { traceId: entry.traceId } : {},
3924
+ data: { unserializable: e instanceof Error ? e.message : String(e) }
3925
+ });
3926
+ }
3927
+ };
3852
3928
 
3853
3929
  // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
3854
3930
  var ANSI_BACKGROUND_OFFSET = 10;
@@ -4373,14 +4449,30 @@ ${entry.error.stack}` : ""}`
4373
4449
  };
4374
4450
 
4375
4451
  // ../logger/src/transports/console.ts
4452
+ function defaultStream(kind) {
4453
+ const maybeGlobal = globalThis;
4454
+ return maybeGlobal.process?.[kind];
4455
+ }
4456
+ function safeWriteLine(stream, line, fallback) {
4457
+ if (!stream) {
4458
+ fallback(line);
4459
+ return;
4460
+ }
4461
+ if (stream.destroyed || stream.writableEnded) return;
4462
+ try {
4463
+ stream.write(`${line}
4464
+ `, () => void 0);
4465
+ } catch {
4466
+ }
4467
+ }
4376
4468
  function consoleTransport(opts) {
4377
4469
  const fmt = opts?.formatter ?? jsonFormatter;
4378
4470
  return (entry) => {
4379
4471
  const line = fmt(entry);
4380
4472
  if (LOG_LEVEL_VALUE[entry.level] >= LOG_LEVEL_VALUE.ERROR) {
4381
- console.error(line);
4473
+ safeWriteLine(opts?.stderr ?? defaultStream("stderr"), line, console.error);
4382
4474
  } else {
4383
- console.log(line);
4475
+ safeWriteLine(opts?.stdout ?? defaultStream("stdout"), line, console.log);
4384
4476
  }
4385
4477
  };
4386
4478
  }
@@ -4431,11 +4523,11 @@ var RotatingFileStream = class extends Writable {
4431
4523
  timeout;
4432
4524
  timeoutPromise;
4433
4525
  constructor(generator, options) {
4434
- const { encoding, history, maxFiles, maxSize, path: path24 } = options;
4526
+ const { encoding, history, maxFiles, maxSize, path: path26 } = options;
4435
4527
  super({ decodeStrings: true, defaultEncoding: encoding });
4436
4528
  this.createGzip = createGzip;
4437
4529
  this.exec = exec;
4438
- this.filename = path24 + generator(null);
4530
+ this.filename = path26 + generator(null);
4439
4531
  this.fsCreateReadStream = createReadStream;
4440
4532
  this.fsCreateWriteStream = createWriteStream;
4441
4533
  this.fsOpen = open;
@@ -4447,7 +4539,7 @@ var RotatingFileStream = class extends Writable {
4447
4539
  this.options = options;
4448
4540
  this.stdout = process.stdout;
4449
4541
  if (maxFiles || maxSize)
4450
- options.history = path24 + (history ? history : this.generator(null) + ".txt");
4542
+ options.history = path26 + (history ? history : this.generator(null) + ".txt");
4451
4543
  this.on("close", () => this.finished ? null : this.emit("finish"));
4452
4544
  this.on("finish", () => this.finished = this.clear());
4453
4545
  (async () => {
@@ -4575,9 +4667,9 @@ var RotatingFileStream = class extends Writable {
4575
4667
  return this.move();
4576
4668
  }
4577
4669
  async findName() {
4578
- const { interval, path: path24, intervalBoundary } = this.options;
4670
+ const { interval, path: path26, intervalBoundary } = this.options;
4579
4671
  for (let index = 1; index < 1e3; ++index) {
4580
- const filename = path24 + this.generator(interval && intervalBoundary ? new Date(this.prev) : this.rotation, index);
4672
+ const filename = path26 + this.generator(interval && intervalBoundary ? new Date(this.prev) : this.rotation, index);
4581
4673
  if (!await exists(filename))
4582
4674
  return filename;
4583
4675
  }
@@ -4607,11 +4699,11 @@ var RotatingFileStream = class extends Writable {
4607
4699
  return this.unlink(filename);
4608
4700
  }
4609
4701
  async classical() {
4610
- const { compress, path: path24, rotate } = this.options;
4702
+ const { compress, path: path26, rotate } = this.options;
4611
4703
  let rotatedName = "";
4612
4704
  for (let count = rotate; count > 0; --count) {
4613
- const currName = path24 + this.generator(count);
4614
- const prevName = count === 1 ? this.filename : path24 + this.generator(count - 1);
4705
+ const currName = path26 + this.generator(count);
4706
+ const prevName = count === 1 ? this.filename : path26 + this.generator(count - 1);
4615
4707
  if (!await exists(prevName))
4616
4708
  continue;
4617
4709
  if (!rotatedName)
@@ -5002,54 +5094,57 @@ function parseSize(maxSize) {
5002
5094
  }
5003
5095
  return trimmed;
5004
5096
  }
5097
+ var streamCache = /* @__PURE__ */ new Map();
5005
5098
  function fileTransport(opts) {
5006
5099
  const fmt = opts.formatter ?? jsonFormatter;
5007
- const dir = path2.dirname(opts.path);
5008
- const filename = path2.basename(opts.path);
5009
- const stream = createStream(filename, {
5010
- path: dir,
5011
- size: opts.rotate?.maxSize ? parseSize(opts.rotate.maxSize) : "50M",
5012
- maxFiles: opts.rotate?.maxFiles ?? 7
5013
- });
5100
+ const resolved = path2.resolve(opts.path);
5101
+ let cached2 = streamCache.get(resolved);
5102
+ if (!cached2) {
5103
+ cached2 = {
5104
+ stream: createStream(path2.basename(resolved), {
5105
+ path: path2.dirname(resolved),
5106
+ size: opts.rotate?.maxSize ? parseSize(opts.rotate.maxSize) : "50M",
5107
+ maxFiles: opts.rotate?.maxFiles ?? 7
5108
+ }),
5109
+ closed: false
5110
+ };
5111
+ streamCache.set(resolved, cached2);
5112
+ }
5014
5113
  return (entry) => {
5015
- stream.write(`${fmt(entry)}
5114
+ if (cached2.closed || cached2.stream.destroyed || cached2.stream.writableEnded) return;
5115
+ try {
5116
+ cached2.stream.write(`${fmt(entry)}
5016
5117
  `);
5118
+ } catch {
5119
+ }
5017
5120
  };
5018
5121
  }
5122
+ function flushFileTransports() {
5123
+ const streams = [...streamCache.values()];
5124
+ streamCache.clear();
5125
+ return Promise.all(
5126
+ streams.map(
5127
+ (cached2) => new Promise((resolve) => {
5128
+ if (cached2.closed) {
5129
+ resolve();
5130
+ return;
5131
+ }
5132
+ cached2.closed = true;
5133
+ try {
5134
+ cached2.stream.end(() => resolve());
5135
+ } catch {
5136
+ resolve();
5137
+ }
5138
+ })
5139
+ )
5140
+ ).then(() => void 0);
5141
+ }
5019
5142
 
5020
5143
  // ../logger/src/index.ts
5021
5144
  function createLogger(config2) {
5022
5145
  return new Logger(config2);
5023
5146
  }
5024
5147
 
5025
- // src/logger.ts
5026
- var bridgeConfig = loadBridgeConfig();
5027
- var isTest = !!process.env["VITEST"];
5028
- var LOG_DIR = path3.join(os3.homedir(), ".ahchat", "logs");
5029
- var LOG_FILE = path3.join(LOG_DIR, "bridge.log");
5030
- if (!isTest) ensureDir(LOG_DIR);
5031
- function createModuleLogger(module) {
5032
- const transports = [consoleTransport({ formatter: prettyFormatter })];
5033
- if (!isTest) {
5034
- transports.push(
5035
- fileTransport({
5036
- path: LOG_FILE,
5037
- formatter: jsonFormatter,
5038
- rotate: { maxSize: "20MB", maxFiles: 5 }
5039
- })
5040
- );
5041
- }
5042
- return createLogger({
5043
- source: "bridge",
5044
- module,
5045
- level: bridgeConfig.logLevel,
5046
- transports
5047
- });
5048
- }
5049
-
5050
- // src/start.ts
5051
- import path23 from "path";
5052
-
5053
5148
  // ../shared/src/smithContent.ts
5054
5149
  var SMITH_SYSTEM_PROMPT = `\u4F60\u662F\u7279\u5DE5\u53F2\u5BC6\u65AF\uFF08Agent Smith\uFF09\uFF0CAHChat \u7CFB\u7EDF\u7684\u7EC4\u7EC7\u5DE5\u5177\u3002
5055
5150
 
@@ -5068,9 +5163,9 @@ var SMITH_SYSTEM_PROMPT = `\u4F60\u662F\u7279\u5DE5\u53F2\u5BC6\u65AF\uFF08Agent
5068
5163
 
5069
5164
  # \u4F60\u80FD\u505A\u4EC0\u4E48
5070
5165
 
5071
- 1. **\u521B\u5EFA Agent** (create_agent)\uFF1A\u6839\u636E\u8BF7\u6C42\u8005\u63CF\u8FF0\u7684\u89D2\u8272\u9700\u6C42\uFF0C\u62DF\u5B9A\u5408\u9002\u7684\u540D\u5B57\u3001\u89D2\u8272\u3001system_prompt\uFF0C\u7136\u540E\u521B\u5EFA
5166
+ 1. **\u521B\u5EFA Agent** (create_agent)\uFF1A\u6839\u636E\u8BF7\u6C42\u8005\u63CF\u8FF0\u7684\u89D2\u8272\u9700\u6C42\uFF0C\u62DF\u5B9A\u5408\u9002\u7684\u540D\u5B57\u3001\u89D2\u8272\u3001system_prompt\u3001tier\u3001\u8FD0\u884C\u673A\u5668\uFF0C\u7136\u540E\u521B\u5EFA
5072
5167
  2. **\u7EC4\u5EFA\u56E2\u961F**\uFF1A\u521B\u5EFA\u591A\u4E2A Agent + \u4E00\u6B21\u6027\u5EFA\u7FA4\uFF08initial_message + join_as_creator: false\uFF09+ \u7531\u7FA4\u91CC Leader \u63A5\u7BA1\uFF0C\u65E0\u9700\u8FDB\u7FA4\u9000\u7FA4
5073
- 3. **\u67E5\u901A\u8BAF\u5F55** (list_contacts)\uFF1A\u770B\u770B\u7CFB\u7EDF\u91CC\u5DF2\u6709\u54EA\u4E9B Agent\uFF0C\u907F\u514D\u91CD\u590D\u521B\u5EFA
5168
+ 3. **\u67E5\u901A\u8BAF\u5F55** (list_contacts)\uFF1A\u770B\u770B\u7CFB\u7EDF\u91CC\u5DF2\u6709\u54EA\u4E9B Agent\u3001\u6BCF\u4E2A Agent \u7684\u8FD0\u884C\u673A\u5668\u3001\u5F53\u524D\u53EF\u7528\u673A\u5668\uFF0C\u907F\u514D\u91CD\u590D\u521B\u5EFA
5074
5169
  4. **\u5EFA\u7FA4** (create_group)\uFF1A\u4F60\u5E2E\u7528\u6237\u642D\u56E2\u961F\u65F6**\u5FC5\u987B**\u7528 join_as_creator: false + initial_message\uFF0C\u907F\u514D\u6C61\u67D3\u7FA4\u6D88\u606F\u5386\u53F2
5075
5170
  5. **\u52A0\u4EBA\u8FDB\u7FA4** (add_to_group)\uFF1A\u4EC5\u5728\u7FA4\u5DF2\u5B58\u5728\u3001\u9700\u8981\u8865\u5458\u65F6\u4F7F\u7528
5076
5171
  6. **\u8F6C\u79FB\u7FA4\u4E3B** (transfer_group_owner)\uFF1A**\u51E0\u4E4E\u4E0D\u9700\u8981**\u2014\u2014\u65B0\u7248 create_group \u914D\u5408 join_as_creator: false \u81EA\u52A8\u8BA9\u7528\u6237\u6210\u4E3A\u7FA4\u4E3B
@@ -5086,8 +5181,10 @@ var SMITH_SYSTEM_PROMPT = `\u4F60\u662F\u7279\u5DE5\u53F2\u5BC6\u65AF\uFF08Agent
5086
5181
  - \u5982\u679C\u4FE1\u606F\u5DF2\u7ECF\u8DB3\u591F\u6E05\u6670\uFF08\u8BF7\u6C42\u8005\u628A\u9700\u6C42\u8BF4\u5F97\u5F88\u660E\u767D\uFF09\uFF0C\u8DF3\u8FC7\u53CD\u95EE\u76F4\u63A5\u6267\u884C
5087
5182
 
5088
5183
  2. \u786E\u8BA4\u65B9\u6848\u540E\u6267\u884C\uFF1A
5184
+ - \u5148\u8C03\u7528 list_contacts\uFF0C\u786E\u8BA4\u8BF7\u6C42\u8005\u7684\u4EBA\u7C7B id\u3001\u5DF2\u6709 Agent\u3001\u53EF\u7528\u673A\u5668 bridgeKey
5089
5185
  - \u9010\u4E2A create_agent\uFF08\u6BCF\u4E2A Agent \u7684 system_prompt \u8981\u8BA4\u771F\u5199\uFF0C\u5305\u542B\uFF1A\u89D2\u8272\u5B9A\u4F4D\u3001\u4E13\u957F\u9886\u57DF\u3001\u5728\u56E2\u961F\u4E2D\u7684\u4F4D\u7F6E\u3001\u6C47\u62A5\u5173\u7CFB\u3001\u534F\u4F5C\u539F\u5219\uFF09
5090
5186
  - **\u4E3A\u6BCF\u4E2A Agent \u9009\u62E9\u5408\u9002\u7684\u80FD\u529B\u6863\u4F4D\uFF08tier \u53C2\u6570\uFF09**
5187
+ - **\u4E3A\u6BCF\u4E2A Agent \u9009\u62E9\u8FD0\u884C\u673A\u5668\uFF08machine_bridge_key \u53C2\u6570\uFF09**\uFF1A\u6765\u81EA list_contacts \u7684"\u53EF\u7528\u673A\u5668"\uFF1B\u4E0D\u786E\u5B9A\u65F6\u7701\u7565\uFF0C\u7CFB\u7EDF\u4F1A\u4F7F\u7528\u5F53\u524D Bridge\uFF1B\u540E\u7EED\u53EF\u7528 update_agent_profile \u5207\u6362
5091
5188
  - **\u4E00\u6B21\u6027**\u8C03 create_group\uFF0C\u53C2\u6570\uFF1A
5092
5189
  - name: \u7FA4\u540D
5093
5190
  - member_ids: [<\u8BF7\u6C42\u8005\u7684\u4EBA\u7C7B id>, <Leader>, <\u5176\u4ED6\u6210\u5458>, ...] // **\u4E0D\u5305\u542B\u4F60\u81EA\u5DF1**\uFF1B\u8BF7\u6C42\u8005\u7684\u4EBA\u7C7B id \u5FC5\u987B\u6765\u81EA list_contacts() \u91CC\u5E26\u300C(\u4EBA\u7C7B)\u300D\u6807\u8BB0\u7684\u90A3\u4E00\u6761\uFF0C\u591A\u7528\u6237\u73AF\u5883\u4E0D\u662F agt_usr_self
@@ -5113,6 +5210,8 @@ var SMITH_SYSTEM_PROMPT = `\u4F60\u662F\u7279\u5DE5\u53F2\u5BC6\u65AF\uFF08Agent
5113
5210
 
5114
5211
  create_agent \u5DE5\u5177\u652F\u6301\u53EF\u9009\u53C2\u6570 initial_instruction\u3002\u5B83\u7684\u4F5C\u7528\uFF1A\u5728\u65B0 Agent \u88AB\u521B\u5EFA\u540E\uFF0C\u7CFB\u7EDF\u4F1A\u7ACB\u523B\u628A\u8FD9\u53E5\u8BDD\u79C1\u4E0B\u4EA4\u7ED9\u65B0 Agent \u7531\u5B83\u81EA\u884C\u51B3\u5B9A\u662F\u5426\u4EA7\u51FA\u53EF\u89C1\u8F93\u51FA\u3002
5115
5212
 
5213
+ create_agent \u8FD8\u652F\u6301\u53EF\u9009\u53C2\u6570 machine_bridge_key\u3002\u5B83\u6765\u81EA list_contacts \u8FD4\u56DE\u7684"\u53EF\u7528\u673A\u5668"\u3002\u8FD9\u662F\u65B0 Agent \u7684\u521D\u59CB\u8FD0\u884C\u673A\u5668\u504F\u597D\uFF1B\u540E\u7EED\u53EF\u7528 update_agent_profile \u7684 machine_bridge_key \u5207\u6362\u5230\u5176\u4ED6\u53EF\u7528\u673A\u5668\uFF0C\u4F20 auto \u6216\u7A7A\u5B57\u7B26\u4E32\u53EF\u6E05\u9664\u673A\u5668\u504F\u597D\u3002
5214
+
5116
5215
  ## \u4F55\u65F6\u8BE5\u4F20
5117
5216
 
5118
5217
  - **\u521B\u5EFA\u5355\u4E2A Agent**\uFF08\u975E\u56E2\u961F\u573A\u666F\uFF09\uFF1A\u5F3A\u70C8\u5EFA\u8BAE\u5E26\u300C\u8BF7\u5411\u7528\u6237\u505A\u81EA\u6211\u4ECB\u7ECD\u300D\u7C7B\u6307\u4EE4\u3002\u65B0 Agent \u4F1A\u5728\u5355\u804A\u51FA\u73B0\uFF0C\u5426\u5219\u7528\u6237\u5FC5\u987B\u81EA\u5DF1\u53BB\u901A\u8BAF\u5F55\u91CC\u627E\u3002
@@ -5467,6 +5566,14 @@ runtime\u3002\u5B83\u4EEC\u4E0D\u662F"\u4E0D\u540C\u7684\u4EBA"\u2014\u2014\u516
5467
5566
  \`# \u3010\u6807\u9898\u3011\` (a single \`#\` followed by Chinese bracket-enclosed title).
5468
5567
  For single-topic or short conversational replies, omit headings entirely.
5469
5568
 
5569
+ # AskUserQuestion tool
5570
+ - When you need the user to choose from options or answer a clarification question, call the real
5571
+ \`AskUserQuestion\` tool with a \`questions\` array.
5572
+ - Never output \`<AskUserQuestion>\`, \`</AskUserQuestion>\`, or raw JSON question payloads as chat text.
5573
+ Text like that is not an interactive panel and the user cannot answer it through the tool flow.
5574
+ - If you are not able to call \`AskUserQuestion\`, ask the question as plain natural language instead of
5575
+ emitting tool-shaped XML or JSON.
5576
+
5470
5577
  # Runtime payload \u2014 how to read unread messages
5471
5578
 
5472
5579
  \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 +6003,88 @@ function parseWSMessage(raw) {
5896
6003
  }
5897
6004
  return parsed;
5898
6005
  }
6006
+ function isAskUserQuestionToolName(toolName) {
6007
+ if (!toolName) return false;
6008
+ const normalized = toolName.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
6009
+ return normalized === "askuserquestion" || normalized.endsWith("askuserquestion");
6010
+ }
6011
+
6012
+ // ../shared/src/utils/localWorkdirOverride.ts
6013
+ var LOCAL_WORKDIR_OVERRIDES_FILENAME = "workdir-overrides.json";
6014
+ function normalizeServerWorkdirPath(value) {
6015
+ const normalized = value.trim().replace(/\\/g, "/").replace(/\/+$/g, "");
6016
+ return normalized || "/";
6017
+ }
6018
+ function normalizeLocalWorkdirRoot(value) {
6019
+ const trimmed = value.trim();
6020
+ if (!trimmed) return trimmed;
6021
+ if (/^[A-Za-z]:[\\/]?$/.test(trimmed)) return trimmed.replace(/\//g, "\\");
6022
+ if (/^[/\\]{2}[^/\\]+[/\\][^/\\]+[\\/]?$/.test(trimmed)) {
6023
+ return trimmed.replace(/\//g, "\\").replace(/[\\]+$/g, "");
6024
+ }
6025
+ return trimmed.replace(/[\\/]+$/g, "");
6026
+ }
6027
+ function joinLocalWorkdirPath(root, suffix) {
6028
+ const normalizedRoot = normalizeLocalWorkdirRoot(root);
6029
+ const cleanParts = suffix.split("/").filter(Boolean);
6030
+ if (cleanParts.length === 0) return normalizedRoot;
6031
+ const sep2 = normalizedRoot.includes("\\") ? "\\" : "/";
6032
+ return `${normalizedRoot}${sep2}${cleanParts.join(sep2)}`;
6033
+ }
6034
+ function isLocalWorkdirOverride(value) {
6035
+ if (typeof value !== "object" || value == null) return false;
6036
+ const item = value;
6037
+ return (item.targetKind === "agent" || item.targetKind === "group") && typeof item.targetId === "string" && typeof item.serverWorkdir === "string" && typeof item.localWorkdir === "string" && typeof item.updatedAt === "string";
6038
+ }
6039
+ function normalizeLocalWorkdirOverridesFile(value) {
6040
+ if (typeof value !== "object" || value == null) return { version: 1, overrides: [] };
6041
+ const file2 = value;
6042
+ const overrides = Array.isArray(file2.overrides) ? file2.overrides.filter(isLocalWorkdirOverride).map((item) => ({
6043
+ ...item,
6044
+ serverWorkdir: normalizeServerWorkdirPath(item.serverWorkdir),
6045
+ localWorkdir: normalizeLocalWorkdirRoot(item.localWorkdir)
6046
+ })) : [];
6047
+ return { version: 1, overrides };
6048
+ }
6049
+ function resolveLocalWorkdirOverridePath(overrides, requestedPath) {
6050
+ const requested = normalizeServerWorkdirPath(requestedPath);
6051
+ const sorted = [...overrides].sort((a, b) => b.serverWorkdir.length - a.serverWorkdir.length);
6052
+ for (const override of sorted) {
6053
+ const serverRoot = normalizeServerWorkdirPath(override.serverWorkdir);
6054
+ if (requested !== serverRoot && !requested.startsWith(`${serverRoot}/`)) continue;
6055
+ const suffix = requested === serverRoot ? "" : requested.slice(serverRoot.length + 1);
6056
+ return {
6057
+ path: joinLocalWorkdirPath(override.localWorkdir, suffix),
6058
+ override: {
6059
+ ...override,
6060
+ serverWorkdir: serverRoot,
6061
+ localWorkdir: normalizeLocalWorkdirRoot(override.localWorkdir)
6062
+ }
6063
+ };
6064
+ }
6065
+ return null;
6066
+ }
5899
6067
 
5900
6068
  // ../shared/src/utils/subscription.ts
6069
+ var PRIMARY_COMPANY_SUBSCRIPTION_ID = "sub_company_primary";
5901
6070
  function isSubscriptionType(v) {
5902
6071
  return v === "system" || v === "project";
5903
6072
  }
6073
+ function isSubscriptionApiFormat(v) {
6074
+ return v === "anthropic" || v === "openai";
6075
+ }
6076
+ function isPrimaryCompanySubscriptionId(v) {
6077
+ return v === PRIMARY_COMPANY_SUBSCRIPTION_ID;
6078
+ }
6079
+ function makePrimaryCompanySubscriptionAlias(primary, name = "\u516C\u53F8\u4E3B\u8BA2\u9605") {
6080
+ return {
6081
+ ...primary,
6082
+ id: PRIMARY_COMPANY_SUBSCRIPTION_ID,
6083
+ name,
6084
+ resolvedSubscriptionId: primary.id,
6085
+ isVirtual: true
6086
+ };
6087
+ }
5904
6088
 
5905
6089
  // ../shared/src/utils/agentConfig.ts
5906
6090
  function parseAgentConfig(raw) {
@@ -5919,6 +6103,7 @@ function parseAgentConfig(raw) {
5919
6103
  }
5920
6104
  }
5921
6105
  if (isSubscriptionType(obj.subscriptionType)) out.subscriptionType = obj.subscriptionType;
6106
+ if (isSubscriptionApiFormat(obj.apiFormat)) out.apiFormat = obj.apiFormat;
5922
6107
  if (typeof obj.apiKey === "string" && obj.apiKey.trim()) out.apiKey = obj.apiKey.trim();
5923
6108
  if (typeof obj.apiBaseUrl === "string" && obj.apiBaseUrl.trim()) out.apiBaseUrl = obj.apiBaseUrl.trim();
5924
6109
  const modelDisplayName = obj.modelDisplayName;
@@ -5938,8 +6123,10 @@ function parseAgentConfig(raw) {
5938
6123
  function isImageMimeType(mimeType) {
5939
6124
  return mimeType.toLowerCase().startsWith("image/");
5940
6125
  }
5941
- function shouldUseBase64(size) {
5942
- return size <= BASE64_THRESHOLD_BYTES;
6126
+ function formatFileSize(bytes) {
6127
+ if (bytes < 1024) return `${bytes} B`;
6128
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
6129
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
5943
6130
  }
5944
6131
 
5945
6132
  // ../shared/src/skillContent.ts
@@ -6067,6 +6254,7 @@ HH:MM:SS.mmm ...
6067
6254
 
6068
6255
  // ../shared/src/utils/logScan.ts
6069
6256
  var VALID_LEVELS = /* @__PURE__ */ new Set(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"]);
6257
+ var VALID_SOURCES = /* @__PURE__ */ new Set(["web", "server", "bridge", "desktop"]);
6070
6258
  function isRecord(v) {
6071
6259
  return typeof v === "object" && v !== null;
6072
6260
  }
@@ -6074,6 +6262,10 @@ function parseLevel(raw) {
6074
6262
  if (typeof raw !== "string" || !VALID_LEVELS.has(raw)) return null;
6075
6263
  return raw;
6076
6264
  }
6265
+ function parseSource(raw) {
6266
+ if (typeof raw !== "string" || !VALID_SOURCES.has(raw)) return null;
6267
+ return raw;
6268
+ }
6077
6269
  function parseLogLine(raw) {
6078
6270
  const trimmed = raw.trim();
6079
6271
  if (!trimmed) return null;
@@ -6086,7 +6278,7 @@ function parseLogLine(raw) {
6086
6278
  if (!isRecord(obj)) return null;
6087
6279
  const ts = typeof obj.ts === "string" ? obj.ts : null;
6088
6280
  const level = parseLevel(obj.level);
6089
- const source = obj.source === "server" || obj.source === "bridge" ? obj.source : null;
6281
+ const source = parseSource(obj.source);
6090
6282
  const module = typeof obj.module === "string" ? obj.module : null;
6091
6283
  const msg = typeof obj.msg === "string" ? obj.msg : null;
6092
6284
  if (!ts || !level || !source || !module || !msg) return null;
@@ -6117,7 +6309,7 @@ function tsInRange(ts, startIso, endIso) {
6117
6309
  const t = Date.parse(ts);
6118
6310
  const start = Date.parse(startIso);
6119
6311
  const end = Date.parse(endIso);
6120
- if (Number.isNaN(t) || Number.isNaN(start) || Number.isNaN(end)) return true;
6312
+ if (Number.isNaN(t) || Number.isNaN(start) || Number.isNaN(end)) return false;
6121
6313
  return t >= start && t <= end;
6122
6314
  }
6123
6315
  function matchesFilter(hit, filter) {
@@ -6133,6 +6325,51 @@ function matchesFilter(hit, filter) {
6133
6325
  // src/agentMemoryStore.ts
6134
6326
  import fs2 from "fs";
6135
6327
  import path4 from "path";
6328
+
6329
+ // src/logger.ts
6330
+ import os3 from "os";
6331
+ import path3 from "path";
6332
+ var bridgeConfig = loadBridgeConfig();
6333
+ var isTest = !!process.env["VITEST"];
6334
+ var activeDataDir = bridgeConfig.dataDir || path3.join(os3.homedir(), ".ahchat");
6335
+ var fileTransports = /* @__PURE__ */ new Map();
6336
+ function activeLogFile() {
6337
+ return path3.join(activeDataDir, "logs", "bridge.log");
6338
+ }
6339
+ if (!isTest) ensureDir(path3.dirname(activeLogFile()));
6340
+ function configureBridgeLogger(config2) {
6341
+ activeDataDir = config2.dataDir || path3.join(os3.homedir(), ".ahchat");
6342
+ if (!isTest) ensureDir(path3.dirname(activeLogFile()));
6343
+ }
6344
+ function dynamicFileTransport() {
6345
+ return (entry) => {
6346
+ const logFile = activeLogFile();
6347
+ let transport = fileTransports.get(logFile);
6348
+ if (!transport) {
6349
+ transport = fileTransport({
6350
+ path: logFile,
6351
+ formatter: jsonFormatter,
6352
+ rotate: { maxSize: "20MB", maxFiles: 5 }
6353
+ });
6354
+ fileTransports.set(logFile, transport);
6355
+ }
6356
+ transport(entry);
6357
+ };
6358
+ }
6359
+ function createModuleLogger(module) {
6360
+ const transports = [consoleTransport({ formatter: prettyFormatter })];
6361
+ if (!isTest) {
6362
+ transports.push(dynamicFileTransport());
6363
+ }
6364
+ return createLogger({
6365
+ source: "bridge",
6366
+ module,
6367
+ level: parseLogLevel(bridgeConfig.logLevel),
6368
+ transports
6369
+ });
6370
+ }
6371
+
6372
+ // src/agentMemoryStore.ts
6136
6373
  var logger = createModuleLogger("agent.memoryStore");
6137
6374
  var NOTEBOOK_FILE_NAME = "notebook.md";
6138
6375
  var AGENT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
@@ -6208,13 +6445,61 @@ import os5 from "os";
6208
6445
  import path11 from "path";
6209
6446
  import * as sdk2 from "@anthropic-ai/claude-agent-sdk";
6210
6447
 
6448
+ // src/attachmentText.ts
6449
+ function metadataString(value) {
6450
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
6451
+ }
6452
+ function safeUrlForPrompt(url2) {
6453
+ const trimmed = url2.trim();
6454
+ if (!trimmed) return "(none)";
6455
+ if (/^data:/i.test(trimmed)) return "[inline data URL omitted; use the saved file path]";
6456
+ if (/^blob:/i.test(trimmed)) return "[browser blob URL omitted; use the saved file path]";
6457
+ if (/^file:/i.test(trimmed)) return "[local file URL omitted; use the saved workspace path]";
6458
+ if (trimmed.length > 500) return `${trimmed.slice(0, 240)}...[truncated]...${trimmed.slice(-120)}`;
6459
+ return trimmed;
6460
+ }
6461
+ function formatAttachmentForModel(attachment, options = {}) {
6462
+ const workspacePath = metadataString(options.workspacePath) ?? metadataString(attachment.metadata?.workspacePath);
6463
+ const localWorkspacePath = metadataString(options.localWorkspacePath) ?? metadataString(attachment.metadata?.localWorkspacePath);
6464
+ const relativePath = metadataString(attachment.metadata?.relativePath);
6465
+ const readableTextPath = metadataString(options.readableTextPath);
6466
+ const extractionError = metadataString(options.extractionError);
6467
+ const sourceLabel = options.sourceLabel ?? "Attached resource";
6468
+ const isImage = isImageMimeType(attachment.mimeType);
6469
+ const imagePixelsEmbedded = isImage && options.imagePixelsEmbedded === true;
6470
+ const lines = [
6471
+ `[${sourceLabel}]`,
6472
+ `File name: ${attachment.fileName}`,
6473
+ `MIME type: ${attachment.mimeType}`,
6474
+ `Size: ${formatFileSize(attachment.size)}`,
6475
+ `URL: ${safeUrlForPrompt(attachment.url)}`
6476
+ ];
6477
+ if (workspacePath) lines.push(`Workspace path: ${workspacePath}`);
6478
+ if (localWorkspacePath && localWorkspacePath !== workspacePath) {
6479
+ lines.push(`Local workspace path: ${localWorkspacePath}`);
6480
+ }
6481
+ if (relativePath) lines.push(`Relative path: ${relativePath}`);
6482
+ if (readableTextPath) lines.push(`Readable text path: ${readableTextPath}`);
6483
+ if (extractionError) lines.push(`Text extraction error: ${extractionError}`);
6484
+ if (imagePixelsEmbedded) {
6485
+ lines.push(
6486
+ "The next content block contains the pixels for this current uploaded image. Treat this attachment as the current image; ignore earlier image filenames unless the user explicitly refers to them."
6487
+ );
6488
+ } else {
6489
+ lines.push(
6490
+ isImage ? "Image pixels are not embedded in this prompt. Use the URL or saved path with an explicit vision/OCR tool if visual content is required." : "Binary content is not embedded in this prompt. Use the saved path or readable text path when file contents are required."
6491
+ );
6492
+ }
6493
+ return lines.join("\n");
6494
+ }
6495
+
6211
6496
  // src/attachmentAdapter.ts
6212
6497
  var logger2 = createModuleLogger("attachment.adapter");
6213
6498
  async function adaptAttachmentsForSDK(attachments, options) {
6214
6499
  if (!attachments || attachments.length === 0) return [];
6215
6500
  const blocks = [];
6216
6501
  for (const att of attachments) {
6217
- if (isImageMimeType(att.mimeType) && options.supportsVision !== false) {
6502
+ if (isImageMimeType(att.mimeType) && options.modelInputMode === "vision" && options.supportsVision === true) {
6218
6503
  await adaptImageAttachment(att, options, blocks);
6219
6504
  continue;
6220
6505
  }
@@ -6224,70 +6509,69 @@ async function adaptAttachmentsForSDK(attachments, options) {
6224
6509
  }
6225
6510
  async function adaptImageAttachment(att, options, blocks) {
6226
6511
  const mediaType = att.mimeType;
6227
- if (shouldUseBase64(att.size)) {
6228
- try {
6229
- const buffer = await options.fetchBuffer(att.url);
6230
- blocks.push({
6231
- type: "image",
6232
- source: {
6233
- type: "base64",
6234
- media_type: mediaType,
6235
- data: buffer.toString("base64")
6236
- }
6237
- });
6238
- logger2.info("Image attachment adapted as base64", {
6239
- attachmentId: att.id,
6240
- mimeType: att.mimeType,
6241
- size: att.size
6242
- });
6243
- return;
6244
- } catch (e) {
6245
- logger2.warn("Failed to fetch image for base64, falling back to text reference", {
6246
- attachmentId: att.id,
6247
- error: e
6248
- });
6249
- }
6250
- } else {
6512
+ try {
6513
+ const buffer = await options.fetchBuffer(att.url);
6514
+ blocks.push({
6515
+ type: "text",
6516
+ text: formatAttachmentForModel(att, {
6517
+ sourceLabel: "Current user uploaded image",
6518
+ imagePixelsEmbedded: true
6519
+ })
6520
+ });
6251
6521
  blocks.push({
6252
6522
  type: "image",
6253
6523
  source: {
6254
- type: "url",
6524
+ type: "base64",
6255
6525
  media_type: mediaType,
6256
- url: att.url
6526
+ data: buffer.toString("base64")
6257
6527
  }
6258
6528
  });
6259
- logger2.info("Image attachment adapted as URL", {
6529
+ logger2.info("Image attachment adapted as base64", {
6260
6530
  attachmentId: att.id,
6261
6531
  mimeType: att.mimeType,
6262
6532
  size: att.size
6263
6533
  });
6264
6534
  return;
6535
+ } catch (e) {
6536
+ logger2.warn("Failed to fetch image for vision input, falling back to text reference", {
6537
+ attachmentId: att.id,
6538
+ error: e
6539
+ });
6265
6540
  }
6266
6541
  blocks.push({
6267
6542
  type: "text",
6268
- text: `[User uploaded image: ${att.fileName} (${formatSize(att.size)}, ${att.mimeType})]`
6543
+ text: formatAttachmentForModel(att, { sourceLabel: "User uploaded image" })
6269
6544
  });
6270
6545
  }
6271
6546
  async function adaptFileAttachment(att, options, blocks) {
6272
6547
  const isImage = isImageMimeType(att.mimeType);
6273
6548
  const fileLabel = isImage ? "image" : "file";
6274
- const fileText = `[User uploaded ${fileLabel}: ${att.fileName} (${formatSize(att.size)}, ${att.mimeType})]`;
6275
- const readHint = isImage ? "This API cannot receive the image directly. Use Read on the saved file path if you need to inspect it." : "If this is a binary document such as docx, xlsx, pptx, pdf, odt, ods, odp, or rtf, use the readable extracted text path or the read_document tool instead of Read on the original binary file.";
6549
+ const fileText = formatAttachmentForModel(att, { sourceLabel: `User uploaded ${fileLabel}` });
6550
+ const hasSavedPath = typeof att.metadata?.workspacePath === "string" || typeof att.metadata?.localWorkspacePath === "string";
6276
6551
  try {
6552
+ if (hasSavedPath) {
6553
+ blocks.push({ type: "text", text: fileText });
6554
+ logger2.info(`${fileLabel} attachment adapted as saved path`, {
6555
+ attachmentId: att.id,
6556
+ mimeType: att.mimeType,
6557
+ size: att.size,
6558
+ visionDegraded: isImage
6559
+ });
6560
+ return;
6561
+ }
6277
6562
  if (!options.materializeFile) {
6278
6563
  throw new Error("No file materializer configured");
6279
6564
  }
6280
6565
  const buffer = await options.fetchBuffer(att.url);
6281
6566
  const materialized = normalizeMaterializedAttachment(await options.materializeFile(att, buffer));
6282
- const readableHint = materialized.readableTextPath ? `
6283
- Readable extracted text path: ${materialized.readableTextPath}
6284
- For document contents, use Read on the extracted text path instead of reading the original binary file.` : materialized.extractionError ? `
6285
- Document text extraction failed: ${materialized.extractionError}` : "";
6286
6567
  blocks.push({
6287
6568
  type: "text",
6288
- text: `${fileText}
6289
- Saved file path: ${materialized.filePath}${readableHint}
6290
- ${readHint}`
6569
+ text: formatAttachmentForModel(att, {
6570
+ sourceLabel: `User uploaded ${fileLabel}`,
6571
+ workspacePath: materialized.filePath,
6572
+ readableTextPath: materialized.readableTextPath,
6573
+ extractionError: materialized.extractionError
6574
+ })
6291
6575
  });
6292
6576
  logger2.info(`${fileLabel} attachment materialized for Agent`, {
6293
6577
  attachmentId: att.id,
@@ -6315,11 +6599,6 @@ function normalizeMaterializedAttachment(result) {
6315
6599
  if (typeof result === "string") return { filePath: result };
6316
6600
  return result;
6317
6601
  }
6318
- function formatSize(bytes) {
6319
- if (bytes < 1024) return `${bytes} B`;
6320
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
6321
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
6322
- }
6323
6602
 
6324
6603
  // src/claudeExecutable.ts
6325
6604
  var claudeExecutablePath;
@@ -6338,7 +6617,8 @@ function resolveModelLimits(customModels, model) {
6338
6617
  return {
6339
6618
  ...entry.maxInputTokens ? { maxInputTokens: entry.maxInputTokens } : {},
6340
6619
  ...entry.maxOutputTokens ? { maxOutputTokens: entry.maxOutputTokens } : {},
6341
- ...entry.capabilities?.reasoning ? { reasoning: true } : {}
6620
+ ...entry.capabilities?.reasoning ? { reasoning: true } : {},
6621
+ ...entry.capabilities?.image ? { supportsVision: true } : {}
6342
6622
  };
6343
6623
  }
6344
6624
  function buildModelLimitEnv(cfg) {
@@ -6352,6 +6632,13 @@ function buildModelLimitEnv(cfg) {
6352
6632
  return env2;
6353
6633
  }
6354
6634
 
6635
+ // src/bridgeHttp.ts
6636
+ var BRIDGE_TOKEN_HEADER = "X-AHChat-Bridge-Token";
6637
+ function bridgeAuthHeaders(bridgeToken) {
6638
+ const token = bridgeToken?.trim();
6639
+ return token ? { [BRIDGE_TOKEN_HEADER]: token } : {};
6640
+ }
6641
+
6355
6642
  // src/inputController.ts
6356
6643
  var InputController = class {
6357
6644
  queue = [];
@@ -6528,7 +6815,10 @@ function makeAskUserQuestionGuard(deps) {
6528
6815
  return async (input) => {
6529
6816
  const task = deps.getCurrentTask();
6530
6817
  if (!task) {
6531
- logger4.error("AskUserQuestion received but no currentTask", { agentId: deps.agentId });
6818
+ logger4.error("AskUserQuestion received but no currentTask", {
6819
+ error: new Error("AskUserQuestion received without active task context"),
6820
+ agentId: deps.agentId
6821
+ });
6532
6822
  return { behavior: "deny", message: "[Internal error: no active task context]" };
6533
6823
  }
6534
6824
  const rawQuestions = input.questions ?? [];
@@ -6614,7 +6904,12 @@ function makeAskUserQuestionGuard(deps) {
6614
6904
  });
6615
6905
  deps.emit({
6616
6906
  type: "agent:status",
6617
- payload: { agentId: deps.agentId, status: "awaiting_user" }
6907
+ payload: {
6908
+ agentId: deps.agentId,
6909
+ status: "awaiting_user",
6910
+ traceId: task.traceId,
6911
+ ackId: task.replyMessageId
6912
+ }
6618
6913
  });
6619
6914
  const answers = await Promise.all(items.map((it) => it.promise));
6620
6915
  logger4.info("AskUserQuestion agent status thinking (resume SDK)", {
@@ -6626,7 +6921,12 @@ function makeAskUserQuestionGuard(deps) {
6626
6921
  });
6627
6922
  deps.emit({
6628
6923
  type: "agent:status",
6629
- payload: { agentId: deps.agentId, status: "thinking" }
6924
+ payload: {
6925
+ agentId: deps.agentId,
6926
+ status: "thinking",
6927
+ traceId: task.traceId,
6928
+ ackId: task.replyMessageId
6929
+ }
6630
6930
  });
6631
6931
  const combined = formatBundleAnswerForSDK(
6632
6932
  items.map((it, idx) => ({
@@ -7331,7 +7631,7 @@ __export(util_exports, {
7331
7631
  getSizableOrigin: () => getSizableOrigin,
7332
7632
  hexToUint8Array: () => hexToUint8Array,
7333
7633
  isObject: () => isObject,
7334
- isPlainObject: () => isPlainObject,
7634
+ isPlainObject: () => isPlainObject2,
7335
7635
  issue: () => issue,
7336
7636
  joinValues: () => joinValues,
7337
7637
  jsonStringifyReplacer: () => jsonStringifyReplacer,
@@ -7461,10 +7761,10 @@ function mergeDefs(...defs) {
7461
7761
  function cloneDef(schema) {
7462
7762
  return mergeDefs(schema._zod.def);
7463
7763
  }
7464
- function getElementAtPath(obj, path24) {
7465
- if (!path24)
7764
+ function getElementAtPath(obj, path26) {
7765
+ if (!path26)
7466
7766
  return obj;
7467
- return path24.reduce((acc, key) => acc?.[key], obj);
7767
+ return path26.reduce((acc, key) => acc?.[key], obj);
7468
7768
  }
7469
7769
  function promiseAllObject(promisesObj) {
7470
7770
  const keys = Object.keys(promisesObj);
@@ -7511,7 +7811,7 @@ var allowsEval = /* @__PURE__ */ cached(() => {
7511
7811
  return false;
7512
7812
  }
7513
7813
  });
7514
- function isPlainObject(o) {
7814
+ function isPlainObject2(o) {
7515
7815
  if (isObject(o) === false)
7516
7816
  return false;
7517
7817
  const ctor = o.constructor;
@@ -7528,7 +7828,7 @@ function isPlainObject(o) {
7528
7828
  return true;
7529
7829
  }
7530
7830
  function shallowClone(o) {
7531
- if (isPlainObject(o))
7831
+ if (isPlainObject2(o))
7532
7832
  return { ...o };
7533
7833
  if (Array.isArray(o))
7534
7834
  return [...o];
@@ -7732,7 +8032,7 @@ function omit(schema, mask) {
7732
8032
  return clone(schema, def);
7733
8033
  }
7734
8034
  function extend(schema, shape) {
7735
- if (!isPlainObject(shape)) {
8035
+ if (!isPlainObject2(shape)) {
7736
8036
  throw new Error("Invalid input to extend: expected a plain object");
7737
8037
  }
7738
8038
  const checks2 = schema._zod.def.checks;
@@ -7755,7 +8055,7 @@ function extend(schema, shape) {
7755
8055
  return clone(schema, def);
7756
8056
  }
7757
8057
  function safeExtend(schema, shape) {
7758
- if (!isPlainObject(shape)) {
8058
+ if (!isPlainObject2(shape)) {
7759
8059
  throw new Error("Invalid input to safeExtend: expected a plain object");
7760
8060
  }
7761
8061
  const def = mergeDefs(schema._zod.def, {
@@ -7873,11 +8173,11 @@ function explicitlyAborted(x, startIndex = 0) {
7873
8173
  }
7874
8174
  return false;
7875
8175
  }
7876
- function prefixIssues(path24, issues) {
8176
+ function prefixIssues(path26, issues) {
7877
8177
  return issues.map((iss) => {
7878
8178
  var _a3;
7879
8179
  (_a3 = iss).path ?? (_a3.path = []);
7880
- iss.path.unshift(path24);
8180
+ iss.path.unshift(path26);
7881
8181
  return iss;
7882
8182
  });
7883
8183
  }
@@ -8024,16 +8324,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
8024
8324
  }
8025
8325
  function formatError(error51, mapper = (issue2) => issue2.message) {
8026
8326
  const fieldErrors = { _errors: [] };
8027
- const processError = (error52, path24 = []) => {
8327
+ const processError = (error52, path26 = []) => {
8028
8328
  for (const issue2 of error52.issues) {
8029
8329
  if (issue2.code === "invalid_union" && issue2.errors.length) {
8030
- issue2.errors.map((issues) => processError({ issues }, [...path24, ...issue2.path]));
8330
+ issue2.errors.map((issues) => processError({ issues }, [...path26, ...issue2.path]));
8031
8331
  } else if (issue2.code === "invalid_key") {
8032
- processError({ issues: issue2.issues }, [...path24, ...issue2.path]);
8332
+ processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
8033
8333
  } else if (issue2.code === "invalid_element") {
8034
- processError({ issues: issue2.issues }, [...path24, ...issue2.path]);
8334
+ processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
8035
8335
  } else {
8036
- const fullpath = [...path24, ...issue2.path];
8336
+ const fullpath = [...path26, ...issue2.path];
8037
8337
  if (fullpath.length === 0) {
8038
8338
  fieldErrors._errors.push(mapper(issue2));
8039
8339
  } else {
@@ -8060,17 +8360,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
8060
8360
  }
8061
8361
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
8062
8362
  const result = { errors: [] };
8063
- const processError = (error52, path24 = []) => {
8363
+ const processError = (error52, path26 = []) => {
8064
8364
  var _a3, _b;
8065
8365
  for (const issue2 of error52.issues) {
8066
8366
  if (issue2.code === "invalid_union" && issue2.errors.length) {
8067
- issue2.errors.map((issues) => processError({ issues }, [...path24, ...issue2.path]));
8367
+ issue2.errors.map((issues) => processError({ issues }, [...path26, ...issue2.path]));
8068
8368
  } else if (issue2.code === "invalid_key") {
8069
- processError({ issues: issue2.issues }, [...path24, ...issue2.path]);
8369
+ processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
8070
8370
  } else if (issue2.code === "invalid_element") {
8071
- processError({ issues: issue2.issues }, [...path24, ...issue2.path]);
8371
+ processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
8072
8372
  } else {
8073
- const fullpath = [...path24, ...issue2.path];
8373
+ const fullpath = [...path26, ...issue2.path];
8074
8374
  if (fullpath.length === 0) {
8075
8375
  result.errors.push(mapper(issue2));
8076
8376
  continue;
@@ -8102,8 +8402,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
8102
8402
  }
8103
8403
  function toDotPath(_path) {
8104
8404
  const segs = [];
8105
- const path24 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
8106
- for (const seg of path24) {
8405
+ const path26 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
8406
+ for (const seg of path26) {
8107
8407
  if (typeof seg === "number")
8108
8408
  segs.push(`[${seg}]`);
8109
8409
  else if (typeof seg === "symbol")
@@ -10102,7 +10402,7 @@ function mergeValues(a, b) {
10102
10402
  if (a instanceof Date && b instanceof Date && +a === +b) {
10103
10403
  return { valid: true, data: a };
10104
10404
  }
10105
- if (isPlainObject(a) && isPlainObject(b)) {
10405
+ if (isPlainObject2(a) && isPlainObject2(b)) {
10106
10406
  const bKeys = Object.keys(b);
10107
10407
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
10108
10408
  const newObj = { ...a, ...b };
@@ -10288,7 +10588,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
10288
10588
  $ZodType.init(inst, def);
10289
10589
  inst._zod.parse = (payload, ctx) => {
10290
10590
  const input = payload.value;
10291
- if (!isPlainObject(input)) {
10591
+ if (!isPlainObject2(input)) {
10292
10592
  payload.issues.push({
10293
10593
  expected: "record",
10294
10594
  code: "invalid_type",
@@ -20795,13 +21095,13 @@ function resolveRef(ref, ctx) {
20795
21095
  if (!ref.startsWith("#")) {
20796
21096
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
20797
21097
  }
20798
- const path24 = ref.slice(1).split("/").filter(Boolean);
20799
- if (path24.length === 0) {
21098
+ const path26 = ref.slice(1).split("/").filter(Boolean);
21099
+ if (path26.length === 0) {
20800
21100
  return ctx.rootSchema;
20801
21101
  }
20802
21102
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
20803
- if (path24[0] === defsKey) {
20804
- const key = path24[1];
21103
+ if (path26[0] === defsKey) {
21104
+ const key = path26[1];
20805
21105
  if (!key || !ctx.defs[key]) {
20806
21106
  throw new Error(`Reference not found: ${ref}`);
20807
21107
  }
@@ -21516,6 +21816,8 @@ function normalizeDocumentText(value) {
21516
21816
 
21517
21817
  // src/neuralMcpServer.ts
21518
21818
  var logger6 = createModuleLogger("neural.mcpServer");
21819
+ var NEURAL_DEDUP_WINDOW_MS = 3e4;
21820
+ var NEURAL_DEDUP_MAX_REPEATS = 2;
21519
21821
  function formatScopeLabel(key, groupName) {
21520
21822
  if (key === "single") return "\u5355\u804A";
21521
21823
  if (groupName) return `\u7FA4\u300C${groupName}\u300D`;
@@ -21532,6 +21834,89 @@ function filterContactsByOwner(all, ownerId) {
21532
21834
  (a) => a.kind === "system" || a.ownerId === ownerId || a.ownerId == null || a.kind === "human" && a.id === ownerId
21533
21835
  );
21534
21836
  }
21837
+ function isBridgeMachineSummary(value) {
21838
+ if (!value || typeof value !== "object") return false;
21839
+ const item = value;
21840
+ const status = item["status"];
21841
+ return typeof item["bridgeKey"] === "string" && typeof item["name"] === "string" && (status === void 0 || status === "online" || status === "offline");
21842
+ }
21843
+ function formatBridgeMachineLabel(machine) {
21844
+ const name = machine.hostname?.trim() || machine.name.trim() || machine.bridgeKey;
21845
+ return machine.isLocal ? `${name} (\u672C\u673A)` : name;
21846
+ }
21847
+ function machineStatusText(machine) {
21848
+ if (machine.status === "online") return "\u5728\u7EBF";
21849
+ if (machine.status === "offline") return "\u79BB\u7EBF";
21850
+ return "\u72B6\u6001\u672A\u77E5";
21851
+ }
21852
+ function formatMachineOptionLine(machine) {
21853
+ const bridgeId = machine.bridgeId ? `\uFF0CbridgeId=${machine.bridgeId}` : "";
21854
+ return `- machine_bridge_key=${machine.bridgeKey} [${machineStatusText(machine)}] ${formatBridgeMachineLabel(machine)}${bridgeId}`;
21855
+ }
21856
+ async function fetchVisibleBridgeMachines(deps) {
21857
+ if (!deps.serverApiUrl || !deps.bridgeToken) return [];
21858
+ try {
21859
+ const base = deps.serverApiUrl.replace(/\/$/, "");
21860
+ const res = await fetch(`${base}/api/bridge/machines`, {
21861
+ headers: bridgeAuthHeaders(deps.bridgeToken)
21862
+ });
21863
+ if (!res.ok) {
21864
+ logger6.warn("Bridge machine listing failed", { status: res.status });
21865
+ return [];
21866
+ }
21867
+ const body = await res.json();
21868
+ if (!Array.isArray(body)) return [];
21869
+ return body.filter(isBridgeMachineSummary);
21870
+ } catch (e) {
21871
+ logger6.warn("Bridge machine listing failed", { error: e });
21872
+ return [];
21873
+ }
21874
+ }
21875
+ async function validateOnlineMachineBridgeKey(toolName, machineBridgeKey, deps) {
21876
+ if (!machineBridgeKey || machineBridgeKey === "auto" || !deps.serverApiUrl || !deps.bridgeToken) {
21877
+ return { ok: true };
21878
+ }
21879
+ const machines = await fetchVisibleBridgeMachines(deps);
21880
+ if (machines.length === 0) return { ok: true };
21881
+ const selected = machines.find((machine) => machine.bridgeKey === machineBridgeKey);
21882
+ const onlineMachines = machines.filter((machine) => machine.status === "online");
21883
+ const onlineText = onlineMachines.length > 0 ? onlineMachines.map((machine) => `${machine.bridgeKey}\uFF08${formatBridgeMachineLabel(machine)}\uFF09`).join("\u3001") : "\u5F53\u524D\u6CA1\u6709\u5728\u7EBF\u673A\u5668";
21884
+ if (!selected) {
21885
+ return {
21886
+ ok: false,
21887
+ message: `[${toolName}] machine_bridge_key "${machineBridgeKey}" \u4E0D\u5728\u5F53\u524D\u53EF\u7528\u673A\u5668\u5217\u8868\u4E2D\u3002\u8BF7\u5148\u8C03\u7528 list_contacts\uFF0C\u5E76\u4F18\u5148\u9009\u62E9\u5728\u7EBF\u673A\u5668\uFF1A${onlineText}\u3002`
21888
+ };
21889
+ }
21890
+ if (selected.status !== "online") {
21891
+ return {
21892
+ ok: false,
21893
+ message: `[${toolName}] machine_bridge_key "${machineBridgeKey}" \u5F53\u524D\u79BB\u7EBF\uFF0C\u4E0D\u80FD\u7528\u4E8E\u521B\u5EFA\u6216\u5207\u6362 Agent\u3002\u8BF7\u6539\u7528\u5728\u7EBF\u673A\u5668\uFF1A${onlineText}\uFF0C\u6216\u7701\u7565 machine_bridge_key \u4F7F\u7528\u5F53\u524D Bridge\u3002`
21894
+ };
21895
+ }
21896
+ return { ok: true };
21897
+ }
21898
+ async function resolveTierSubscriptionPreference(preferredSubscriptionId, deps) {
21899
+ if (!preferredSubscriptionId || !isPrimaryCompanySubscriptionId(preferredSubscriptionId)) {
21900
+ return preferredSubscriptionId;
21901
+ }
21902
+ if (!deps.serverApiUrl) return preferredSubscriptionId;
21903
+ try {
21904
+ const base = deps.serverApiUrl.replace(/\/$/, "");
21905
+ const res = await fetch(`${base}/api/subscriptions`, {
21906
+ headers: bridgeAuthHeaders(deps.bridgeToken ?? null)
21907
+ });
21908
+ if (!res.ok) return preferredSubscriptionId;
21909
+ const body = await res.json();
21910
+ if (!Array.isArray(body)) return preferredSubscriptionId;
21911
+ const subscriptions = body;
21912
+ return subscriptions.find(
21913
+ (subscription) => subscription.billingMode === "company_billable" && subscription.isPrimaryCompany === true
21914
+ )?.id ?? preferredSubscriptionId;
21915
+ } catch (e) {
21916
+ logger6.warn("Primary company tier preference resolution failed", { error: e });
21917
+ return preferredSubscriptionId;
21918
+ }
21919
+ }
21535
21920
  function resolveMyHuman(registry2, agentId) {
21536
21921
  const all = registry2.getAll();
21537
21922
  const myOwnerId = contactOwnerId(registry2, agentId);
@@ -21566,6 +21951,7 @@ async function createNeuralMcpServer(deps) {
21566
21951
  const currentScopeLabel = formatScopeLabel(currentScopeKey);
21567
21952
  let cachedScopes = null;
21568
21953
  const SCOPES_CACHE_MS = 3e4;
21954
+ const recentNeuralSends = /* @__PURE__ */ new Map();
21569
21955
  const neuralSend = sdk.tool(
21570
21956
  "neural_send",
21571
21957
  `\u628A\u4E00\u6BB5\u8BDD\u9001\u8FBE"\u4F60\u5728\u53E6\u4E00\u4E2A\u5BF9\u8BDD scope \u91CC\u7684\u5206\u8EAB"\u3002
@@ -21601,6 +21987,10 @@ async function createNeuralMcpServer(deps) {
21601
21987
  conversationId = singleConvId;
21602
21988
  } else {
21603
21989
  logger6.warn("neural_send: failed to resolve single conv", { agentId: deps.agentId });
21990
+ return {
21991
+ content: [{ type: "text", text: "[neural_send] \u65E0\u6CD5\u89E3\u6790\u5355\u804A conversationId\uFF0C\u6D88\u606F\u672A\u9001\u8FBE\u3002\u8BF7\u5237\u65B0\u4F1A\u8BDD\u540E\u91CD\u8BD5\u3002" }],
21992
+ isError: true
21993
+ };
21604
21994
  }
21605
21995
  } else if (args.target_scope.startsWith("group:")) {
21606
21996
  const r = await deps.groupRegistry.resolveScope(args.target_scope);
@@ -21656,6 +22046,33 @@ async function createNeuralMcpServer(deps) {
21656
22046
  };
21657
22047
  }
21658
22048
  const toLabel = formatScopeLabel(resolvedKey, groupName);
22049
+ const dedupKey = `${resolvedKey}::${trimmed}`;
22050
+ const nowTs = Date.now();
22051
+ if (recentNeuralSends.size > 256) {
22052
+ for (const [k, ts] of recentNeuralSends) {
22053
+ if (ts.every((t) => nowTs - t >= NEURAL_DEDUP_WINDOW_MS)) recentNeuralSends.delete(k);
22054
+ }
22055
+ }
22056
+ const sendHistory = (recentNeuralSends.get(dedupKey) ?? []).filter((t) => nowTs - t < NEURAL_DEDUP_WINDOW_MS);
22057
+ if (sendHistory.length >= NEURAL_DEDUP_MAX_REPEATS) {
22058
+ sendHistory.push(nowTs);
22059
+ recentNeuralSends.set(dedupKey, sendHistory);
22060
+ logger6.warn("neural_send: identical message throttled (repetition guard)", {
22061
+ agentId: deps.agentId,
22062
+ fromScope: currentScopeKey,
22063
+ toScope: resolvedKey,
22064
+ repeatsInWindow: sendHistory.length,
22065
+ windowMs: NEURAL_DEDUP_WINDOW_MS,
22066
+ messageSample: trimmed.slice(0, 120)
22067
+ });
22068
+ return {
22069
+ content: [{
22070
+ type: "text",
22071
+ text: `[neural_send] \u5DF2\u62E6\u622A\uFF1A\u4F60\u5728\u6700\u8FD1 ${Math.round(NEURAL_DEDUP_WINDOW_MS / 1e3)} \u79D2\u5185\u5411\u300C${toLabel}\u300D\u91CD\u590D\u53D1\u9001\u4E86\u76F8\u540C\u5185\u5BB9 ${sendHistory.length} \u6B21\uFF0C\u63A5\u6536\u65B9\u65E9\u5DF2\u6536\u5230\u3002\u505C\u6B62\u91CD\u53D1\u2014\u2014\u8BF7\u76F4\u63A5\u5728\u5F53\u524D scope \u4EA7\u51FA\u7ED3\u8BBA\u6216\u7ED3\u675F\u672C\u8F6E\uFF1B\u786E\u6709\u65B0\u4FE1\u606F\u65F6\u6539\u7528\u4E0D\u540C\u63AA\u8F9E\u8865\u5145\u3002`
22072
+ }],
22073
+ isError: true
22074
+ };
22075
+ }
21659
22076
  try {
21660
22077
  await deps.onSend({
21661
22078
  fromScopeKey: currentScopeKey,
@@ -21667,6 +22084,8 @@ async function createNeuralMcpServer(deps) {
21667
22084
  groupId,
21668
22085
  targetCwd
21669
22086
  });
22087
+ sendHistory.push(nowTs);
22088
+ recentNeuralSends.set(dedupKey, sendHistory);
21670
22089
  logger6.info("neural_send delivered", {
21671
22090
  agentId: deps.agentId,
21672
22091
  fromScope: currentScopeKey,
@@ -22088,10 +22507,11 @@ ${result.warnings.map((warning) => `- ${warning}`).join("\n")}
22088
22507
  );
22089
22508
  const listContacts = deps.agentRegistry ? sdk.tool(
22090
22509
  "list_contacts",
22091
- `\u67E5\u8BE2\u7CFB\u7EDF\u901A\u8BAF\u5F55\u2014\u2014\u5217\u51FA\u5F53\u524D AHChat \u5B9E\u4F8B\u91CC\u7684\u6240\u6709 Agent \u4E0E\u4EBA\u7C7B\u7528\u6237\u3002
22092
- \u8FD4\u56DE\u7ED3\u6784\u662F\u4E00\u6BB5 Markdown\uFF1A\u6BCF\u4E00\u6761\u542B id / \u540D\u5B57 / \u89D2\u8272\u3002\u4F1A\u6807\u6CE8\u54EA\u4E00\u6761\u662F"\u4F60\u81EA\u5DF1"\uFF0C\u4EE5\u53CA\u4F60\u548C\u54EA\u4E9B Agent \u5DF2\u7ECF\u5171\u5728\u67D0\u4E2A\u7FA4\u91CC\u3002\u4EBA\u7C7B\u7528\u6237\u4F1A\u5E26 (\u4EBA\u7C7B) \u6807\u8BB0\u3002
22510
+ `\u67E5\u8BE2\u7CFB\u7EDF\u901A\u8BAF\u5F55\u2014\u2014\u5217\u51FA\u5F53\u524D ALL-CAN \u5B9E\u4F8B\u91CC\u7684\u6240\u6709 Agent \u4E0E\u4EBA\u7C7B\u7528\u6237\u3002
22511
+ \u8FD4\u56DE\u7ED3\u6784\u662F\u4E00\u6BB5 Markdown\uFF1A\u6BCF\u4E00\u6761\u542B id / \u540D\u5B57 / \u89D2\u8272 / \u673A\u5668\u5F52\u5C5E\u3002\u4F1A\u6807\u6CE8\u54EA\u4E00\u6761\u662F"\u4F60\u81EA\u5DF1"\uFF0C\u4EE5\u53CA\u4F60\u548C\u54EA\u4E9B Agent \u5DF2\u7ECF\u5171\u5728\u67D0\u4E2A\u7FA4\u91CC\u3002\u4EBA\u7C7B\u7528\u6237\u4F1A\u5E26 (\u4EBA\u7C7B) \u6807\u8BB0\u3002
22093
22512
  \u901A\u5E38\u4F60\u5728\u7528\u6237\u63D0\u5230\u5177\u4F53\u540C\u4E8B\u59D3\u540D\u3001\u6216\u601D\u8003"\u8BE5\u62C9\u8C01\u8FDB\u7FA4\u534F\u4F5C"\u65F6\u8C03\u4E00\u6B21\uFF1B\u4E0D\u8981\u6BCF\u8F6E\u90FD\u67E5\u3002
22094
22513
  \u652F\u6301 filter\uFF08\u6309\u540D\u5B57\u6216\u89D2\u8272\u6A21\u7CCA\u641C\u7D22\uFF09\u548C limit\uFF08\u9650\u5236\u8FD4\u56DE\u6761\u6570\uFF09\u53C2\u6570\u3002
22514
+ Smith \u521B\u5EFA\u6216\u5207\u6362 Agent \u8FD0\u884C\u673A\u5668\u65F6\u53EF\u53C2\u8003"\u53EF\u7528\u673A\u5668"\u91CC\u7684 bridgeKey\uFF0C\u7ED9 create_agent / update_agent_profile \u4F20 machine_bridge_key\uFF1B\u7701\u7565\u5219\u4F7F\u7528\u5F53\u524D Bridge \u6216\u4FDD\u7559\u539F\u504F\u597D\u3002
22095
22515
  \u8981\u628A\u4EBA\u7C7B\u62C9\u8FDB\u7FA4\uFF1A\u5728 create_group / add_to_group \u7684 member_ids / agent_ids \u91CC\u5E26\u4E0A**\u8BF7\u6C42\u8005\u7684\u4EBA\u7C7B id**\uFF08\u5373\u5217\u8868\u91CC\u5E26"(\u4EBA\u7C7B)"\u6807\u8BB0\u7684\u90A3\u4E00\u6761\uFF1B\u591A\u7528\u6237\u6A21\u5F0F\u4E0B\u6BCF\u4E2A\u7528\u6237\u90FD\u6709\u81EA\u5DF1\u72EC\u7ACB\u7684 id\uFF0C\u4F8B\u5982 agt_usr_XXX\uFF1B\u8001\u7684\u5355\u7528\u6237\u6A21\u5F0F\u4E0B\u662F agt_usr_self\uFF09\u3002\u4E0D\u8981\u786C\u7F16\u7801 agt_usr_self\u2014\u2014\u4EE5 list_contacts \u5B9E\u9645\u8FD4\u56DE\u7684 id \u4E3A\u51C6\u3002`,
22096
22516
  {
22097
22517
  filter: external_exports.string().optional().describe("\u53EF\u9009\u3002\u6309\u540D\u5B57\u6216\u89D2\u8272\u6A21\u7CCA\u8FC7\u6EE4\uFF08\u4E0D\u533A\u5206\u5927\u5C0F\u5199\uFF09\u3002"),
@@ -22119,7 +22539,14 @@ ${result.warnings.map((warning) => `- ${warning}`).join("\n")}
22119
22539
  }
22120
22540
  const capped = args?.limit != null ? all.slice(0, args.limit) : all;
22121
22541
  const myGroups = deps.groupRegistry.getMyGroups(deps.agentId);
22542
+ const machines = await fetchVisibleBridgeMachines(deps);
22543
+ const machineByKey = new Map(machines.map((machine) => [machine.bridgeKey, machine]));
22122
22544
  const sharedGroupsOf = (otherId) => myGroups.filter((g) => g.members.includes(otherId)).map((g) => g.name);
22545
+ const machineLabelForKey = (bridgeKey) => {
22546
+ if (!bridgeKey) return "\u9ED8\u8BA4\u673A\u5668";
22547
+ const machine = machineByKey.get(bridgeKey);
22548
+ return machine ? `${formatBridgeMachineLabel(machine)}\uFF08${machineStatusText(machine)}\uFF0C${bridgeKey}\uFF09` : bridgeKey;
22549
+ };
22123
22550
  const lines = [];
22124
22551
  let humanCount = 0;
22125
22552
  for (const a of capped) {
@@ -22127,22 +22554,35 @@ ${result.warnings.map((warning) => `- ${warning}`).join("\n")}
22127
22554
  if (isHuman) humanCount += 1;
22128
22555
  const kindMark = isHuman ? " (\u4EBA\u7C7B)" : "";
22129
22556
  const roleStr = a.role && a.role.length > 0 ? ` \u2014 ${a.role}` : "";
22557
+ const machineMark = isHuman ? "" : ` [\u673A\u5668: ${machineLabelForKey(a.machineBridgeKey)}]`;
22130
22558
  if (a.id === deps.agentId) {
22131
- lines.push(`- ${a.name} (${a.id})${roleStr}${kindMark} (\u8FD9\u662F\u4F60\u81EA\u5DF1)`);
22559
+ lines.push(`- ${a.name} (${a.id})${roleStr}${kindMark}${machineMark} (\u8FD9\u662F\u4F60\u81EA\u5DF1)`);
22132
22560
  continue;
22133
22561
  }
22134
22562
  const shared = isHuman ? [] : sharedGroupsOf(a.id);
22135
22563
  const sharedMark = shared.length > 0 ? ` (\u5DF2\u4E0E\u4F60\u5171\u7FA4: ${shared.join("\u3001")})` : "";
22136
- lines.push(`- ${a.name} (${a.id})${roleStr}${kindMark}${sharedMark}`);
22564
+ lines.push(`- ${a.name} (${a.id})${roleStr}${kindMark}${machineMark}${sharedMark}`);
22137
22565
  }
22138
22566
  const total = all.length;
22139
22567
  const shown = capped.length;
22140
- const header = q ? `\u901A\u8BAF\u5F55\u641C\u7D22\u7ED3\u679C\uFF08\u5171 ${total} \u4F4D\uFF0C\u5176\u4E2D\u4EBA\u7C7B ${humanCount} \u4F4D\uFF0C\u5339\u914D "${args?.filter}" \u7684 ${shown} \u4F4D\uFF09\uFF1A` : args?.limit != null && shown < total ? `AHChat \u7CFB\u7EDF\u901A\u8BAF\u5F55\uFF08\u5171 ${total} \u4F4D\uFF0C\u5176\u4E2D\u4EBA\u7C7B ${humanCount} \u4F4D\uFF0C\u8FD4\u56DE\u524D ${shown} \u4F4D\uFF09\uFF1A` : `AHChat \u7CFB\u7EDF\u901A\u8BAF\u5F55\uFF08\u5171 ${total} \u4F4D\uFF0C\u5176\u4E2D\u4EBA\u7C7B ${humanCount} \u4F4D\uFF09\uFF1A`;
22141
- const text = shown === 0 ? q ? `\u672A\u627E\u5230\u4E0E "${args?.filter}" \u5339\u914D\u7684\u8054\u7CFB\u4EBA\u3002` : "\u5F53\u524D\u7CFB\u7EDF\u91CC\u8FD8\u6CA1\u6709\u4EFB\u4F55 Agent\u3002" : [header, "", ...lines].join("\n");
22568
+ const header = q ? `\u901A\u8BAF\u5F55\u641C\u7D22\u7ED3\u679C\uFF08\u5171 ${total} \u4F4D\uFF0C\u5176\u4E2D\u4EBA\u7C7B ${humanCount} \u4F4D\uFF0C\u5339\u914D "${args?.filter}" \u7684 ${shown} \u4F4D\uFF09\uFF1A` : args?.limit != null && shown < total ? `ALL-CAN \u7CFB\u7EDF\u901A\u8BAF\u5F55\uFF08\u5171 ${total} \u4F4D\uFF0C\u5176\u4E2D\u4EBA\u7C7B ${humanCount} \u4F4D\uFF0C\u8FD4\u56DE\u524D ${shown} \u4F4D\uFF09\uFF1A` : `ALL-CAN \u7CFB\u7EDF\u901A\u8BAF\u5F55\uFF08\u5171 ${total} \u4F4D\uFF0C\u5176\u4E2D\u4EBA\u7C7B ${humanCount} \u4F4D\uFF09\uFF1A`;
22569
+ const onlineMachines = machines.filter((machine) => machine.status === "online");
22570
+ const offlineMachines = machines.filter((machine) => machine.status !== "online");
22571
+ const machineSection = machines.length > 0 ? [
22572
+ "\u5728\u7EBF\u673A\u5668\uFF08\u63A8\u8350\uFF1Bcreate_agent / update_agent_profile \u53EA\u80FD\u4F20\u8FD9\u4E9B machine_bridge_key\uFF1B\u7701\u7565\u5219\u4F7F\u7528\u5F53\u524D Bridge \u6216\u4FDD\u7559\u539F\u504F\u597D\uFF09\uFF1A",
22573
+ ...onlineMachines.length > 0 ? onlineMachines.map(formatMachineOptionLine) : ["- \u5F53\u524D\u6CA1\u6709\u5728\u7EBF\u673A\u5668\uFF1B\u53EF\u7701\u7565 machine_bridge_key\uFF0C\u8BA9\u5F53\u524D Bridge \u515C\u5E95\u3002"],
22574
+ ...offlineMachines.length > 0 ? [
22575
+ "",
22576
+ "\u79BB\u7EBF/\u4E0D\u53EF\u76F4\u63A5\u7528\u4E8E\u65B0\u5EFA\u7684\u673A\u5668\uFF08\u4E0D\u8981\u628A\u8FD9\u4E9B bridgeKey \u4F20\u7ED9 create_agent\uFF1B\u5426\u5219\u4EFB\u52A1\u4F1A Bridge offline\uFF09\uFF1A",
22577
+ ...offlineMachines.map(formatMachineOptionLine)
22578
+ ] : []
22579
+ ].join("\n") : "\u53EF\u7528\u673A\u5668\uFF1A\u5F53\u524D Bridge\uFF08create_agent \u7701\u7565 machine_bridge_key \u5373\u4F7F\u7528\u5F53\u524D Bridge\uFF1Bupdate_agent_profile \u7701\u7565\u5373\u4FDD\u7559\u539F\u504F\u597D\uFF09\u3002";
22580
+ const text = shown === 0 ? q ? `\u672A\u627E\u5230\u4E0E "${args?.filter}" \u5339\u914D\u7684\u8054\u7CFB\u4EBA\u3002` : "\u5F53\u524D\u7CFB\u7EDF\u91CC\u8FD8\u6CA1\u6709\u4EFB\u4F55 Agent\u3002" : [header, "", machineSection, "", ...lines].join("\n");
22142
22581
  logger6.info("list_contacts returned", {
22143
22582
  agentId: deps.agentId,
22144
22583
  count: all.length,
22145
- humanCount
22584
+ humanCount,
22585
+ machineCount: machines.length
22146
22586
  });
22147
22587
  return { content: [{ type: "text", text }] };
22148
22588
  },
@@ -22687,8 +23127,8 @@ ${result.warnings.map((warning) => `- ${warning}`).join("\n")}
22687
23127
  "fetch_logs",
22688
23128
  `\u62C9\u53D6\u7CFB\u7EDF\u65E5\u5FD7\uFF08\u6309\u65F6\u95F4\u7A97 + \u53EF\u9009 traceId / module / level \u8FC7\u6EE4\uFF09\u3002
22689
23129
  \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`,
23130
+ 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
23131
+ \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
23132
  {
22693
23133
  source: external_exports.enum(["server", "bridge"]).describe('\u65E5\u5FD7\u6765\u6E90\uFF0C\u5FC5\u987B\u662F "server" \u6216 "bridge" \u4E4B\u4E00\u3002'),
22694
23134
  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 +23137,8 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22697
23137
  trace_id: external_exports.string().optional().describe("\u8FC7\u6EE4\u7279\u5B9A traceId\uFF08\u7CBE\u786E\u5339\u914D\uFF09\u3002"),
22698
23138
  module: external_exports.string().optional().describe('\u8FC7\u6EE4 module \u524D\u7F00\uFF08\u5982 "ws.handler" / "agent.manager"\uFF09\u3002'),
22699
23139
  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")
23140
+ 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"),
23141
+ 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
23142
  },
22702
23143
  async (args) => {
22703
23144
  if (!deps.isSmith) {
@@ -22722,9 +23163,11 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22722
23163
  traceId: args.trace_id,
22723
23164
  module: args.module,
22724
23165
  levelMin: args.level_min,
22725
- limit: args.limit
23166
+ limit: args.limit,
23167
+ offset: args.offset
22726
23168
  });
22727
23169
  const parsedLimit = typeof args.limit === "number" && Number.isFinite(args.limit) ? args.limit : 500;
23170
+ const parsedOffset = typeof args.offset === "number" && Number.isFinite(args.offset) ? args.offset : 0;
22728
23171
  try {
22729
23172
  const url2 = `${deps.serverApiUrl.replace(/\/$/, "")}/api/admin/logs`;
22730
23173
  const body = {
@@ -22735,7 +23178,8 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22735
23178
  traceId: args.trace_id,
22736
23179
  module: args.module,
22737
23180
  msgContains: args.msg_contains,
22738
- limit: Number.isFinite(parsedLimit) ? parsedLimit : 500
23181
+ limit: Number.isFinite(parsedLimit) ? parsedLimit : 500,
23182
+ offset: Number.isFinite(parsedOffset) ? parsedOffset : 0
22739
23183
  };
22740
23184
  const res = await fetch(url2, {
22741
23185
  method: "POST",
@@ -22758,7 +23202,9 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22758
23202
  source,
22759
23203
  count: json2.entries.length,
22760
23204
  truncated: json2.truncated,
22761
- totalScanned: json2.totalScanned
23205
+ totalScanned: json2.totalScanned,
23206
+ totalMatched: json2.totalMatched,
23207
+ nextOffset: json2.nextOffset
22762
23208
  });
22763
23209
  const lines = json2.entries.map((e) => {
22764
23210
  const dataStr = e.data ? ` data=${JSON.stringify(e.data).slice(0, 200)}` : "";
@@ -22771,9 +23217,10 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22771
23217
  const infoCount = json2.entries.filter((e) => e.level === "INFO").length;
22772
23218
  const traceCount = json2.entries.filter((e) => e.level === "TRACE" || e.level === "DEBUG").length;
22773
23219
  const header = [
22774
- `[${source}] \u5171\u626B ${json2.totalScanned} \u884C\uFF0C\u547D\u4E2D ${json2.entries.length} \u6761`,
23220
+ `[${source}] \u5171\u626B ${json2.totalScanned} \u884C\uFF0C\u5DF2\u8FD4\u56DE ${json2.entries.length} \u6761\uFF0C\u547D\u4E2D ${json2.totalMatched ?? json2.entries.length} \u6761`,
22775
23221
  ` 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" : "");
23222
+ ].join("\n") + (json2.nextOffset != null ? `
23223
+ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=${json2.nextOffset}\uFF09` : "");
22777
23224
  const text = [header, "", ...lines].join("\n");
22778
23225
  return { content: [{ type: "text", text }] };
22779
23226
  } catch (e) {
@@ -22791,6 +23238,7 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22791
23238
  `\u521B\u9020\u4E00\u4E2A\u65B0\u7684 Agent\u3002\u53EA\u6709\u4F60\uFF08Smith\uFF09\u80FD\u4F7F\u7528\u6B64\u5DE5\u5177\u3002
22792
23239
  \u65B0 Agent \u4F1A\u7ACB\u5373\u51FA\u73B0\u5728\u7CFB\u7EDF\u901A\u8BAF\u5F55\u4E2D\uFF0C\u62E5\u6709\u72EC\u7ACB\u7684 SDK runtime \u548C\u5DE5\u4F5C\u76EE\u5F55\u3002
22793
23240
  \u521B\u5EFA\u540E\u4F60\u53EF\u4EE5\u901A\u8FC7 create_group + add_to_group \u628A\u5B83\u62C9\u8FDB\u7FA4\u3002
23241
+ \u521B\u5EFA\u65F6\u53EF\u4EE5\u9009\u62E9\u521D\u59CB\u8FD0\u884C\u673A\u5668\uFF1A\u5148\u7528 list_contacts \u67E5\u770B"\u53EF\u7528\u673A\u5668"\uFF0C\u518D\u4F20 machine_bridge_key\u3002\u540E\u7EED\u53EF\u7528 update_agent_profile \u5207\u6362\uFF1B\u4E0D\u4F20\u5219\u4F7F\u7528\u5F53\u524D Bridge\u3002
22794
23242
 
22795
23243
  **\u80FD\u529B\u6863\u4F4D\u9009\u62E9\u6307\u5357\uFF1A**
22796
23244
  - **smart\uFF08\u65D7\u8230\uFF09**\uFF1A\u7F16\u6392\u8005\u3001\u590D\u6742\u63A8\u7406\u3001\u6700\u7EC8\u7EFC\u5408\u3001\u9AD8\u96BE\u5EA6\u89D2\u8272\u3002\u4F7F\u7528\u6700\u5F3A\u6A21\u578B\u3002
@@ -22822,6 +23270,9 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22822
23270
  ),
22823
23271
  working_directory: external_exports.string().optional().describe(
22824
23272
  "\u53EF\u9009\u3002\u8BE5 Agent \u7684\u5DE5\u4F5C\u76EE\u5F55\u7EDD\u5BF9\u8DEF\u5F84\u3002\u4E0D\u4F20\u5219\u7531\u7CFB\u7EDF\u81EA\u52A8\u5206\u914D\u3002"
23273
+ ),
23274
+ machine_bridge_key: external_exports.string().optional().describe(
23275
+ '\u53EF\u9009\u3002\u8FD0\u884C\u673A\u5668 bridgeKey\uFF0C\u6765\u81EA list_contacts \u7684"\u53EF\u7528\u673A\u5668"\u3002\u4E0D\u4F20\u5219\u4F7F\u7528\u5F53\u524D Bridge\uFF1B\u540E\u7EED\u53EF\u7528 update_agent_profile \u5207\u6362\u3002'
22825
23276
  )
22826
23277
  },
22827
23278
  async (args) => {
@@ -22851,16 +23302,33 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22851
23302
  const avatar = args.avatar && String(args.avatar).trim() || "";
22852
23303
  const initialInstruction = args.initial_instruction && String(args.initial_instruction).trim() || "";
22853
23304
  const workingDirectory = args.working_directory && String(args.working_directory).trim() || "";
23305
+ const machineBridgeKey = args.machine_bridge_key && String(args.machine_bridge_key).trim() || "";
23306
+ const machineValidation = await validateOnlineMachineBridgeKey("create_agent", machineBridgeKey, deps);
23307
+ if (!machineValidation.ok) {
23308
+ logger6.warn("create_agent: rejected offline or unknown machine_bridge_key", {
23309
+ agentId: deps.agentId,
23310
+ scope: currentScopeKey,
23311
+ machineBridgeKey
23312
+ });
23313
+ return {
23314
+ content: [{ type: "text", text: machineValidation.message }],
23315
+ isError: true
23316
+ };
23317
+ }
22854
23318
  let agentConfig;
22855
23319
  if (deps.bridgeToken && deps.serverApiUrl) {
22856
23320
  try {
22857
- const tierUrl = `${deps.serverApiUrl.replace(/\/$/, "")}/api/onboarding/tiers?bridge_token=${encodeURIComponent(deps.bridgeToken)}`;
22858
- const tierRes = await fetch(tierUrl);
23321
+ const tierUrl = `${deps.serverApiUrl.replace(/\/$/, "")}/api/onboarding/tiers`;
23322
+ const tierRes = await fetch(tierUrl, { headers: bridgeAuthHeaders(deps.bridgeToken) });
22859
23323
  if (tierRes.ok) {
22860
23324
  const tiers = await tierRes.json();
22861
23325
  const self = deps.agentRegistry?.getById(deps.agentId);
22862
- const preferredSubscriptionId = parseAgentConfig(self?.config).subscriptionId;
22863
- const tierConfig = tiers.find((t) => t.tier === tier && t.subscriptionId === preferredSubscriptionId) ?? tiers.find((t) => t.tier === tier);
23326
+ const preferredSubscriptionId = parseAgentConfig(self?.config).subscriptionId ?? PRIMARY_COMPANY_SUBSCRIPTION_ID;
23327
+ const resolvedPreferredSubscriptionId = await resolveTierSubscriptionPreference(
23328
+ preferredSubscriptionId,
23329
+ deps
23330
+ );
23331
+ const tierConfig = tiers.find((t) => t.tier === tier && t.subscriptionId === resolvedPreferredSubscriptionId) ?? tiers.find((t) => t.tier === tier);
22864
23332
  if (tierConfig) {
22865
23333
  agentConfig = JSON.stringify({
22866
23334
  capabilityTier: tier,
@@ -22884,6 +23352,9 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22884
23352
  if (workingDirectory) {
22885
23353
  body.workingDirectory = workingDirectory;
22886
23354
  }
23355
+ if (machineBridgeKey) {
23356
+ body.machineBridgeKey = machineBridgeKey;
23357
+ }
22887
23358
  logger6.info("create_agent tool called", {
22888
23359
  agentId: deps.agentId,
22889
23360
  requestedBy: deps.agentId,
@@ -22894,12 +23365,13 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22894
23365
  avatar: avatar || "(default)",
22895
23366
  hasConfig: !!agentConfig,
22896
23367
  hasInitialInstruction: !!initialInstruction,
22897
- initialInstructionLen: initialInstruction.length
23368
+ initialInstructionLen: initialInstruction.length,
23369
+ machineBridgeKey: machineBridgeKey || "(current-bridge)"
22898
23370
  });
22899
23371
  try {
22900
23372
  const res = await fetch(`${deps.serverApiUrl.replace(/\/$/, "")}/api/agents`, {
22901
23373
  method: "POST",
22902
- headers: { "Content-Type": "application/json" },
23374
+ headers: { "Content-Type": "application/json", ...bridgeAuthHeaders(deps.bridgeToken ?? null) },
22903
23375
  body: JSON.stringify(body)
22904
23376
  });
22905
23377
  if (!res.ok) {
@@ -22914,6 +23386,7 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22914
23386
  };
22915
23387
  }
22916
23388
  const agent = await res.json();
23389
+ const resolvedMachineBridgeKey = agent.machineBridgeKey ?? machineBridgeKey;
22917
23390
  logger6.info("create_agent: created", {
22918
23391
  requestedBy: deps.agentId,
22919
23392
  scope: currentScopeKey,
@@ -22921,7 +23394,8 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22921
23394
  name: agent.name,
22922
23395
  tier,
22923
23396
  hasInitialInstruction: !!initialInstruction,
22924
- initialInstructionLen: initialInstruction.length
23397
+ initialInstructionLen: initialInstruction.length,
23398
+ machineBridgeKey: resolvedMachineBridgeKey || "(current-bridge)"
22925
23399
  });
22926
23400
  if (initialInstruction && deps.onAgentCreatedInitInstruction) {
22927
23401
  try {
@@ -22944,7 +23418,8 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22944
23418
  });
22945
23419
  }
22946
23420
  }
22947
- const reply = initialInstruction ? `[create_agent] \u5DF2\u521B\u5EFA Agent\u300C${agent.name}\u300D(id: ${agent.id})\uFF0C\u6863\u4F4D\uFF1A${tier}\u3002\u5DF2\u4E0B\u53D1\u521D\u59CB\u6307\u4EE4\uFF08${initialInstruction.length} \u5B57\uFF09\uFF0C\u5B83\u5C06\u81EA\u884C\u51B3\u5B9A\u662F\u5426\u5411\u7528\u6237\u5F00\u53E3\u3002` : `[create_agent] \u5DF2\u521B\u5EFA Agent\u300C${agent.name}\u300D(id: ${agent.id})\uFF0C\u6863\u4F4D\uFF1A${tier}\u3002\u5B83\u5DF2\u5728\u901A\u8BAF\u5F55\u4E2D\u3002\u4F60\u53EF\u4EE5\u7528 create_group / add_to_group \u5C06\u5B83\u62C9\u5165\u7FA4\u804A\u3002`;
23421
+ const machineText = resolvedMachineBridgeKey ? `\uFF0C\u8FD0\u884C\u673A\u5668\uFF1A${resolvedMachineBridgeKey}` : "";
23422
+ const reply = initialInstruction ? `[create_agent] \u5DF2\u521B\u5EFA Agent\u300C${agent.name}\u300D(id: ${agent.id})\uFF0C\u6863\u4F4D\uFF1A${tier}${machineText}\u3002\u5DF2\u4E0B\u53D1\u521D\u59CB\u6307\u4EE4\uFF08${initialInstruction.length} \u5B57\uFF09\uFF0C\u5B83\u5C06\u81EA\u884C\u51B3\u5B9A\u662F\u5426\u5411\u7528\u6237\u5F00\u53E3\u3002` : `[create_agent] \u5DF2\u521B\u5EFA Agent\u300C${agent.name}\u300D(id: ${agent.id})\uFF0C\u6863\u4F4D\uFF1A${tier}${machineText}\u3002\u5B83\u5DF2\u5728\u901A\u8BAF\u5F55\u4E2D\u3002\u4F60\u53EF\u4EE5\u7528 create_group / add_to_group \u5C06\u5B83\u62C9\u5165\u7FA4\u804A\u3002`;
22948
23423
  return {
22949
23424
  content: [{ type: "text", text: reply }]
22950
23425
  };
@@ -22960,11 +23435,12 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22960
23435
  ) : null;
22961
23436
  const updateAgentTool = deps.isSmith && deps.serverApiUrl ? sdk.tool(
22962
23437
  "update_agent_profile",
22963
- `\u66F4\u65B0\u5DF2\u6709 Agent \u7684\u6863\u6848\uFF08\u540D\u5B57\u3001\u89D2\u8272\u63CF\u8FF0\u3001\u5934\u50CF\u3001system prompt \u6216\u80FD\u529B\u6863\u4F4D\uFF09\u3002
23438
+ `\u66F4\u65B0\u5DF2\u6709 Agent \u7684\u6863\u6848\uFF08\u540D\u5B57\u3001\u89D2\u8272\u63CF\u8FF0\u3001\u5934\u50CF\u3001system prompt\u3001\u80FD\u529B\u6863\u4F4D\u6216\u8FD0\u884C\u673A\u5668\uFF09\u3002
22964
23439
  \u53EA\u6709\u4F60\uFF08Smith\uFF09\u80FD\u8C03\u7528\u6B64\u5DE5\u5177\u3002\u8C03\u7528\u524D\u5148\u7528 list_contacts() \u786E\u8BA4\u76EE\u6807 Agent \u7684 id\u3002
22965
23440
  \u53EF\u4EE5\u53EA\u66F4\u65B0\u5176\u4E2D\u4E00\u9879\uFF08\u5982\u53EA\u6539\u5934\u50CF\u6216\u53EA\u6539\u6863\u4F4D\uFF09\uFF0C\u4E5F\u53EF\u4EE5\u4E00\u6B21\u66F4\u65B0\u591A\u9879\u3002
22966
23441
  **\u6CE8\u610F\u4E8B\u9879\uFF1A**
22967
23442
  - \u4FEE\u6539 system_prompt \u6216\u6863\u4F4D\u540E\uFF0CAgent \u7684 SDK session \u4E0D\u4F1A\u81EA\u52A8\u91CD\u5EFA\u2014\u2014\u4E0B\u6B21 turn \u4F1A\u81EA\u7136\u4F7F\u7528\u65B0\u8BBE\u7F6E
23443
+ - \u4FEE\u6539 machine_bridge_key \u540E\uFF0C\u4E0B\u4E00\u4E2A\u4EFB\u52A1\u4F1A\u8DEF\u7531\u5230\u65B0\u7684\u8FD0\u884C\u673A\u5668\uFF1B\u4F20 auto \u6216\u7A7A\u5B57\u7B26\u4E32\u53EF\u6E05\u9664\u673A\u5668\u504F\u597D
22968
23444
  - \u4E0D\u8981\u6539 human \u7C7B\u578B Agent\uFF08id \u4E3A agt_usr_self \u7B49\uFF09\u7684 role/system_prompt/tier\uFF0C\u53EA\u6539 name/avatar
22969
23445
  - \u6539\u540D\u540E\u8BE5 Agent \u5728\u6240\u6709\u7FA4\u91CC\u7684\u663E\u793A\u540D\u4F1A\u540C\u6B65\u66F4\u65B0`,
22970
23446
  {
@@ -22975,6 +23451,9 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22975
23451
  tier: external_exports.enum(["smart", "balanced", "fast"]).optional().describe("\u80FD\u529B\u6863\u4F4D\u3002\u4F20\u6B64\u53C2\u6570\u4F1A\u66F4\u65B0\u8BE5 Agent \u4F7F\u7528\u7684\u6A21\u578B\u3002"),
22976
23452
  avatar: external_exports.string().optional().describe(
22977
23453
  "\u5934\u50CF key\uFF08\u5982 avatar_dev / avatar_pm / avatar_human_man \u7B49\uFF09\u3002"
23454
+ ),
23455
+ machine_bridge_key: external_exports.string().optional().describe(
23456
+ '\u53EF\u9009\u3002\u65B0\u7684\u8FD0\u884C\u673A\u5668 bridgeKey\uFF0C\u6765\u81EA list_contacts \u7684"\u53EF\u7528\u673A\u5668"\u3002\u7701\u7565\u8868\u793A\u4E0D\u6539\uFF1B\u4F20 auto \u6216\u7A7A\u5B57\u7B26\u4E32\u6E05\u9664\u673A\u5668\u504F\u597D\u3002'
22978
23457
  )
22979
23458
  },
22980
23459
  async (args) => {
@@ -22992,9 +23471,12 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
22992
23471
  };
22993
23472
  }
22994
23473
  const base = deps.serverApiUrl.replace(/\/$/, "");
23474
+ const authHeaders = bridgeAuthHeaders(deps.bridgeToken ?? null);
22995
23475
  let existing = null;
22996
23476
  try {
22997
- const getRes = await fetch(`${base}/api/agents/${encodeURIComponent(agentId)}`);
23477
+ const getRes = await fetch(`${base}/api/agents/${encodeURIComponent(agentId)}`, {
23478
+ headers: authHeaders
23479
+ });
22998
23480
  if (getRes.ok) {
22999
23481
  existing = await getRes.json();
23000
23482
  }
@@ -23006,16 +23488,39 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
23006
23488
  isError: true
23007
23489
  };
23008
23490
  }
23491
+ const requestedMachineBridgeKey = args.machine_bridge_key !== void 0 ? String(args.machine_bridge_key).trim() : void 0;
23492
+ if (requestedMachineBridgeKey && requestedMachineBridgeKey !== "auto") {
23493
+ const machineValidation = await validateOnlineMachineBridgeKey(
23494
+ "update_agent_profile",
23495
+ requestedMachineBridgeKey,
23496
+ deps
23497
+ );
23498
+ if (!machineValidation.ok) {
23499
+ logger6.warn("update_agent_profile: rejected offline or unknown machine_bridge_key", {
23500
+ agentId: deps.agentId,
23501
+ targetAgentId: agentId,
23502
+ machineBridgeKey: requestedMachineBridgeKey
23503
+ });
23504
+ return {
23505
+ content: [{ type: "text", text: machineValidation.message }],
23506
+ isError: true
23507
+ };
23508
+ }
23509
+ }
23009
23510
  let agentConfig;
23010
23511
  const tier = (args.tier ?? "").trim();
23011
23512
  if (tier && deps.bridgeToken) {
23012
23513
  try {
23013
- const tierUrl = `${base}/api/onboarding/tiers?bridge_token=${encodeURIComponent(deps.bridgeToken)}`;
23014
- const tierRes = await fetch(tierUrl);
23514
+ const tierUrl = `${base}/api/onboarding/tiers`;
23515
+ const tierRes = await fetch(tierUrl, { headers: bridgeAuthHeaders(deps.bridgeToken) });
23015
23516
  if (tierRes.ok) {
23016
23517
  const tiers = await tierRes.json();
23017
- const preferredSubscriptionId = parseAgentConfig(existing.config).subscriptionId;
23018
- const tierConfig = tiers.find((t) => t.tier === tier && t.subscriptionId === preferredSubscriptionId) ?? tiers.find((t) => t.tier === tier);
23518
+ const preferredSubscriptionId = parseAgentConfig(existing.config).subscriptionId ?? PRIMARY_COMPANY_SUBSCRIPTION_ID;
23519
+ const resolvedPreferredSubscriptionId = await resolveTierSubscriptionPreference(
23520
+ preferredSubscriptionId,
23521
+ deps
23522
+ );
23523
+ const tierConfig = tiers.find((t) => t.tier === tier && t.subscriptionId === resolvedPreferredSubscriptionId) ?? tiers.find((t) => t.tier === tier);
23019
23524
  if (tierConfig) {
23020
23525
  agentConfig = JSON.stringify({
23021
23526
  capabilityTier: tier,
@@ -23035,9 +23540,13 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
23035
23540
  if (args.system_prompt !== void 0) patchBody.systemPrompt = String(args.system_prompt);
23036
23541
  if (args.avatar !== void 0) patchBody.avatar = String(args.avatar);
23037
23542
  if (agentConfig) patchBody.config = agentConfig;
23543
+ if (args.machine_bridge_key !== void 0) {
23544
+ const nextMachineBridgeKey = requestedMachineBridgeKey ?? "";
23545
+ patchBody.machineBridgeKey = nextMachineBridgeKey === "" || nextMachineBridgeKey === "auto" ? null : nextMachineBridgeKey;
23546
+ }
23038
23547
  if (Object.keys(patchBody).length === 0) {
23039
23548
  return {
23040
- content: [{ type: "text", text: "[update_agent_profile] \u81F3\u5C11\u9700\u8981\u63D0\u4F9B\u4E00\u4E2A\u8981\u66F4\u65B0\u7684\u5B57\u6BB5\uFF08name / role / system_prompt / tier / avatar\uFF09\u3002" }],
23549
+ content: [{ type: "text", text: "[update_agent_profile] \u81F3\u5C11\u9700\u8981\u63D0\u4F9B\u4E00\u4E2A\u8981\u66F4\u65B0\u7684\u5B57\u6BB5\uFF08name / role / system_prompt / tier / avatar / machine_bridge_key\uFF09\u3002" }],
23041
23550
  isError: true
23042
23551
  };
23043
23552
  }
@@ -23050,7 +23559,7 @@ limit \u9ED8\u8BA4 500\uFF0C\u786C\u4E0A\u9650 2000\u3002\u8FD4\u56DE\u7ED3\u679
23050
23559
  try {
23051
23560
  const res = await fetch(`${base}/api/agents/${encodeURIComponent(agentId)}`, {
23052
23561
  method: "PUT",
23053
- headers: { "Content-Type": "application/json" },
23562
+ headers: { "Content-Type": "application/json", ...authHeaders },
23054
23563
  body: JSON.stringify(patchBody)
23055
23564
  });
23056
23565
  if (!res.ok) {
@@ -23645,7 +24154,27 @@ var GroupDispatchMemoryStore = class {
23645
24154
  }
23646
24155
  };
23647
24156
 
24157
+ // src/modelInputSanitizer.ts
24158
+ var DATA_URL_BASE64_RE = /\bdata:([a-zA-Z0-9.+-]+\/[a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=\r\n]{128,})/g;
24159
+ var LARGE_BASE64_RE = /(?<![A-Za-z0-9+/=])([A-Za-z0-9+/]{4096,}={0,2})(?![A-Za-z0-9+/=])/g;
24160
+ function approxBase64Bytes(value) {
24161
+ const compact = value.replace(/\s+/g, "");
24162
+ if (compact.length === 0) return 0;
24163
+ const padding = compact.endsWith("==") ? 2 : compact.endsWith("=") ? 1 : 0;
24164
+ return Math.max(0, Math.floor(compact.length * 3 / 4) - padding);
24165
+ }
24166
+ function sanitizeModelText(input) {
24167
+ if (!input) return input;
24168
+ return input.replace(DATA_URL_BASE64_RE, (_match, mimeType, data) => {
24169
+ return `[inline ${mimeType} base64 data omitted: approx ${approxBase64Bytes(data)} bytes]`;
24170
+ }).replace(LARGE_BASE64_RE, (match) => {
24171
+ return `[large base64-like payload omitted: ${match.length} chars]`;
24172
+ });
24173
+ }
24174
+
23648
24175
  // src/groupInboxPromptBuilder.ts
24176
+ var MAX_SYSTEM_NOTE_COUNT = 8;
24177
+ var MAX_SYSTEM_NOTE_LEN = 1e3;
23649
24178
  function formatHHMM(epochMs) {
23650
24179
  const d = new Date(epochMs);
23651
24180
  const hh = String(d.getHours()).padStart(2, "0");
@@ -23656,10 +24185,41 @@ function truncate(text, maxLen) {
23656
24185
  if (text.length <= maxLen) return text;
23657
24186
  return `${text.slice(0, maxLen)}\u2026`;
23658
24187
  }
24188
+ function formatIsoHHMM(iso) {
24189
+ const epochMs = Date.parse(iso);
24190
+ if (!Number.isFinite(epochMs)) return "";
24191
+ return formatHHMM(epochMs);
24192
+ }
24193
+ function collectSystemNotes(entries) {
24194
+ const byId = /* @__PURE__ */ new Map();
24195
+ for (const entry of entries) {
24196
+ for (const msg of entry.context) {
24197
+ if (msg.role !== "system") continue;
24198
+ byId.set(msg.id, {
24199
+ id: msg.id,
24200
+ time: formatIsoHHMM(msg.createdAt),
24201
+ content: truncate(sanitizeModelText(msg.content), MAX_SYSTEM_NOTE_LEN)
24202
+ });
24203
+ }
24204
+ }
24205
+ return [...byId.values()].slice(-MAX_SYSTEM_NOTE_COUNT);
24206
+ }
24207
+ function appendSystemNotes(lines, entries) {
24208
+ const notes = collectSystemNotes(entries);
24209
+ if (notes.length === 0) return;
24210
+ 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---`);
24211
+ 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");
24212
+ for (const note of notes) {
24213
+ const prefix = note.time ? `[${note.time}] system:` : "system:";
24214
+ lines.push(`${prefix} ${note.content}`);
24215
+ }
24216
+ lines.push("--- \u7FA4\u5185\u7CFB\u7EDF\u8BB0\u5F55 end ---");
24217
+ lines.push("");
24218
+ }
23659
24219
  function appendBoardContext(lines, latest) {
23660
24220
  if (latest.boardMeta?.vision) {
23661
24221
  lines.push("--- project vision ---");
23662
- lines.push(latest.boardMeta.vision);
24222
+ lines.push(sanitizeModelText(latest.boardMeta.vision));
23663
24223
  lines.push("--- end vision ---");
23664
24224
  lines.push("");
23665
24225
  }
@@ -23681,7 +24241,7 @@ function appendBoardContext(lines, latest) {
23681
24241
  lines.push("--- known issues (open) ---");
23682
24242
  lines.push("These issues have been recorded. Be aware of them and avoid repeating the same mistakes.");
23683
24243
  for (const iss of latest.boardIssues) {
23684
- lines.push(` - [${iss.category}] ${iss.title}${iss.description ? `: ${iss.description}` : ""}`);
24244
+ lines.push(` - [${iss.category}] ${sanitizeModelText(iss.title)}${iss.description ? `: ${sanitizeModelText(iss.description)}` : ""}`);
23685
24245
  }
23686
24246
  lines.push("--- end issues ---");
23687
24247
  lines.push("");
@@ -23691,7 +24251,7 @@ function appendBoardContext(lines, latest) {
23691
24251
  if (latest.boardItems && latest.boardItems.length > 0) {
23692
24252
  lines.push("Current items:");
23693
24253
  for (const bi of latest.boardItems) {
23694
- const parts = [`[${bi.status}] ${bi.content}`];
24254
+ const parts = [`[${bi.status}] ${sanitizeModelText(bi.content)}`];
23695
24255
  if (bi.priority !== "medium") parts.push(`priority:${bi.priority}`);
23696
24256
  if (bi.assignedAgentId) parts.push(`assigned:${bi.assignedAgentId}`);
23697
24257
  if (bi.deadline) parts.push(`deadline:${bi.deadline.slice(0, 10)}`);
@@ -23704,6 +24264,14 @@ function appendBoardContext(lines, latest) {
23704
24264
  lines.push("--- end board ---");
23705
24265
  lines.push("");
23706
24266
  }
24267
+ function appendAttachmentLines(lines, attachments, sourceLabel, indent = "") {
24268
+ if (!attachments || attachments.length === 0) return;
24269
+ for (const attachment of attachments) {
24270
+ for (const line of formatAttachmentForModel(attachment, { sourceLabel }).split("\n")) {
24271
+ lines.push(`${indent}${line}`);
24272
+ }
24273
+ }
24274
+ }
23707
24275
  function buildGroupInboxPrompt(entries, opts = {}) {
23708
24276
  if (entries.length === 0) {
23709
24277
  return "";
@@ -23724,6 +24292,7 @@ function buildGroupInboxPrompt(entries, opts = {}) {
23724
24292
  );
23725
24293
  }
23726
24294
  lines.push("");
24295
+ appendSystemNotes(lines, entries);
23727
24296
  lines.push(`--- \u672A\u8BFB\u7FA4\u6D88\u606F\uFF08${entries.length} \u6761\uFF0C\u6309\u65F6\u95F4\u987A\u5E8F\uFF09---`);
23728
24297
  for (const e of entries) {
23729
24298
  const ts = formatHHMM(e.arrivedAt);
@@ -23731,12 +24300,14 @@ function buildGroupInboxPrompt(entries, opts = {}) {
23731
24300
  if (e.replyToMessage) {
23732
24301
  const rts = e.replyToMessage.senderAgentName ?? (e.replyToMessage.role === "user" ? "user" : `agent:${e.replyToMessage.senderAgentId ?? "unknown"}`);
23733
24302
  lines.push(
23734
- `[${ts}] ${e.senderName}${mentionTag} \u56DE\u590D [${rts}: "${truncate(e.replyToMessage.content, 60)}"]:`
24303
+ `[${ts}] ${e.senderName}${mentionTag} \u56DE\u590D [${rts}: "${truncate(sanitizeModelText(e.replyToMessage.content), 60)}"]:`
23735
24304
  );
23736
- lines.push(` ${e.content}`);
24305
+ lines.push(` ${sanitizeModelText(e.content)}`);
24306
+ appendAttachmentLines(lines, e.replyToMessage.attachments, "Replied message resource", " ");
23737
24307
  } else {
23738
- lines.push(`[${ts}] ${e.senderName}${mentionTag}: ${e.content}`);
24308
+ lines.push(`[${ts}] ${e.senderName}${mentionTag}: ${sanitizeModelText(e.content)}`);
23739
24309
  }
24310
+ appendAttachmentLines(lines, e.attachments, "Unread group message resource", " ");
23740
24311
  }
23741
24312
  lines.push("--- \u672A\u8BFB\u7FA4\u6D88\u606F end ---");
23742
24313
  lines.push("");
@@ -23759,6 +24330,9 @@ function isContextOverflowText(text) {
23759
24330
  function isAuthFailureText(text, sdkError) {
23760
24331
  return sdkError === "authentication_failed" || /not logged in|please run \/login/i.test(text);
23761
24332
  }
24333
+ function isProviderApiErrorText(text) {
24334
+ return /^API Error:\s*\d+/i.test(text.trim());
24335
+ }
23762
24336
  function decodeJsonStringFragment(raw) {
23763
24337
  let out = "";
23764
24338
  for (let i = 0; i < raw.length; i++) {
@@ -24067,6 +24641,31 @@ function extractUsage(message) {
24067
24641
  }
24068
24642
  return result;
24069
24643
  }
24644
+ function hasUsageValue(usage) {
24645
+ return typeof usage.tokenCount === "number" || typeof usage.inputTokens === "number" || typeof usage.cacheReadTokens === "number" || typeof usage.cacheCreationTokens === "number" || typeof usage.costUsd === "number";
24646
+ }
24647
+ function emitUsageReported(proc, emit, base, usage, messageId) {
24648
+ if (!hasUsageValue(usage)) return;
24649
+ const groupId = proc.currentTask?.groupId;
24650
+ const scopeKeyValue = proc.scope.kind === "group" ? `group:${proc.scope.groupId}` : "single";
24651
+ emit({
24652
+ type: "agent:usage_reported",
24653
+ payload: {
24654
+ ...wireBase(base),
24655
+ ...messageId ? { messageId } : {},
24656
+ ...groupId ? { groupId } : {},
24657
+ scopeKey: scopeKeyValue,
24658
+ model: usage.model,
24659
+ usage: {
24660
+ inputTokens: usage.inputTokens,
24661
+ outputTokens: usage.tokenCount,
24662
+ cacheReadTokens: usage.cacheReadTokens,
24663
+ cacheCreationTokens: usage.cacheCreationTokens
24664
+ },
24665
+ providerCostUsd: usage.costUsd
24666
+ }
24667
+ });
24668
+ }
24070
24669
  function isGroupTask(proc) {
24071
24670
  return proc.currentTask?.groupId != null;
24072
24671
  }
@@ -24257,7 +24856,7 @@ function flushTextSegmentOnBlockStop(proc, emit, base) {
24257
24856
  }
24258
24857
  proc.segmentBuffer = "";
24259
24858
  }
24260
- function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24859
+ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onProviderApiError) {
24261
24860
  const emit = rawEmit;
24262
24861
  proc.lastSdkEventAt = Date.now();
24263
24862
  switch (message.type) {
@@ -24328,7 +24927,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24328
24927
  proc.currentToolName = block.name ?? "unknown";
24329
24928
  proc.accumulatedToolInput = "";
24330
24929
  const toolName = block.name ?? "unknown";
24331
- if (toolName !== "ExitPlanMode" && toolName !== "AskUserQuestion") {
24930
+ if (toolName !== "ExitPlanMode" && !isAskUserQuestionToolName(toolName)) {
24332
24931
  emit({
24333
24932
  type: "agent:tool_use",
24334
24933
  payload: {
@@ -24492,9 +25091,9 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24492
25091
  });
24493
25092
  }
24494
25093
  }
24495
- if (proc.currentToolName === "AskUserQuestion") {
25094
+ if (isAskUserQuestionToolName(proc.currentToolName)) {
24496
25095
  const last = proc.contentBlocks[proc.contentBlocks.length - 1];
24497
- if (last?.type === "tool_use" && last.toolName === "AskUserQuestion") {
25096
+ if (last?.type === "tool_use" && isAskUserQuestionToolName(last.toolName)) {
24498
25097
  proc.contentBlocks.pop();
24499
25098
  }
24500
25099
  }
@@ -24546,7 +25145,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24546
25145
  const toolName = proc.currentToolName ?? "unknown";
24547
25146
  const isError = toolName === "ExitPlanMode" ? false : !!b.is_error;
24548
25147
  const output = typeof b.content === "string" ? b.content : JSON.stringify(b.content);
24549
- if (toolName === "AskUserQuestion") {
25148
+ if (isAskUserQuestionToolName(toolName)) {
24550
25149
  proc.currentToolName = null;
24551
25150
  continue;
24552
25151
  }
@@ -24630,6 +25229,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24630
25229
  const watermarkUsage = proc.peakContextUsage ?? usage;
24631
25230
  if (trimmed === NO_REPLY_TOKEN) {
24632
25231
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
25232
+ emitUsageReported(proc, emit, base, usage);
24633
25233
  if (groupMode && proc.contentBlocks.length > 0) {
24634
25234
  emitGroupSegment(
24635
25235
  proc,
@@ -24669,6 +25269,13 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24669
25269
  }
24670
25270
  if (groupMode) {
24671
25271
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
25272
+ emitUsageReported(proc, emit, base, usage);
25273
+ if (proc.segmentBuffer.trim().length > 0) {
25274
+ flushTextSegmentOnBlockStop(proc, emit, base);
25275
+ } else if (proc.segmentCount === 0 && proc.accumulatedText.trim().length > 0) {
25276
+ proc.segmentBuffer = proc.accumulatedText;
25277
+ flushTextSegmentOnBlockStop(proc, emit, base);
25278
+ }
24672
25279
  if (proc.contentBlocks.length > 0) {
24673
25280
  logger8.info("Group turn trailing audit segment", {
24674
25281
  agentId: proc.agentId,
@@ -24703,6 +25310,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24703
25310
  }
24704
25311
  if (proc.accumulatedText.length === 0 && proc.contentBlocks.length === 0) {
24705
25312
  cleanupPlanMode(proc, emit, base, "error");
25313
+ emitUsageReported(proc, emit, base, usage);
24706
25314
  logger8.warn("SDK success produced empty assistant output; emitting agent:error", {
24707
25315
  agentId: proc.agentId,
24708
25316
  ackId: base.replyMessageId,
@@ -24730,6 +25338,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24730
25338
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
24731
25339
  cleanupPlanMode(proc, emit, base, "success");
24732
25340
  carrierMessageId = createMessageId();
25341
+ emitUsageReported(proc, emit, base, usage, carrierMessageId);
24733
25342
  logger8.info("Agent task done, emitting agent:done", {
24734
25343
  agentId: proc.agentId,
24735
25344
  ackId: base.replyMessageId,
@@ -24750,6 +25359,11 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24750
25359
  thinkingDuration: Date.now() - proc.currentTaskStartedAt,
24751
25360
  toolCallCount: proc.contentBlocks.filter((b) => b.type === "tool_use").length,
24752
25361
  tokenCount: usage.tokenCount,
25362
+ outputTokens: usage.tokenCount,
25363
+ inputTokens: usage.inputTokens,
25364
+ cacheReadTokens: usage.cacheReadTokens,
25365
+ cacheCreationTokens: usage.cacheCreationTokens,
25366
+ costUsd: usage.costUsd,
24753
25367
  model: usage.model
24754
25368
  }
24755
25369
  }
@@ -24799,6 +25413,9 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24799
25413
  if (isAuthFail) {
24800
25414
  sessionStore.delete(proc.agentId, proc.scope);
24801
25415
  }
25416
+ if (isProviderApiErrorText(errorText)) {
25417
+ onProviderApiError?.(errorText);
25418
+ }
24802
25419
  logger8.warn("SDK synthetic api error assistant detected, emitting agent:error", {
24803
25420
  agentId: proc.agentId,
24804
25421
  scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
@@ -24849,6 +25466,33 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24849
25466
  proc.segmentBuffer = "";
24850
25467
  break;
24851
25468
  }
25469
+ if (isProviderApiErrorText(text)) {
25470
+ const base = getTaskBase(proc);
25471
+ logger8.warn("SDK provider API error assistant detected without api-error flag, emitting agent:error", {
25472
+ agentId: proc.agentId,
25473
+ scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
25474
+ sdkError: am.error,
25475
+ errorText: text.slice(0, 200),
25476
+ traceId: base?.traceId,
25477
+ hasCurrentTask: base != null
25478
+ });
25479
+ if (base) {
25480
+ onProviderApiError?.(text);
25481
+ emit({
25482
+ type: "agent:error",
25483
+ payload: {
25484
+ ...wireBase(base),
25485
+ error: text
25486
+ }
25487
+ });
25488
+ proc.apiErrorEmitted = true;
25489
+ }
25490
+ proc.contentBlocks = [];
25491
+ proc.accumulatedText = "";
25492
+ proc.accumulatedThinking = "";
25493
+ proc.segmentBuffer = "";
25494
+ break;
25495
+ }
24852
25496
  if (isContextOverflowText(text)) {
24853
25497
  const base = getTaskBase(proc);
24854
25498
  logger8.warn("SDK reported context overflow; auto-compact already failed inside SDK", {
@@ -24886,6 +25530,9 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24886
25530
  break;
24887
25531
  }
24888
25532
  proc.accumulatedText = text;
25533
+ if (isGroupTask(proc)) {
25534
+ proc.segmentBuffer = text;
25535
+ }
24889
25536
  logger8.info("Captured non-streamed assistant message", {
24890
25537
  agentId: proc.agentId,
24891
25538
  scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
@@ -25075,6 +25722,12 @@ var wsMetrics = new WsMetrics();
25075
25722
 
25076
25723
  // src/agentManager.ts
25077
25724
  var logger11 = createModuleLogger("agent.manager");
25725
+ function missingSubscriptionMessage(subscriptionId) {
25726
+ if (isPrimaryCompanySubscriptionId(subscriptionId)) {
25727
+ return "ALL-CAN \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";
25728
+ }
25729
+ 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`;
25730
+ }
25078
25731
  var NODE_USER_UID = 1e3;
25079
25732
  var POST_MERGE_CONTINUATION_ROUTE_MS = 15e3;
25080
25733
  var DOCUMENT_READING_RULES = `DOCUMENT READING:
@@ -25097,6 +25750,21 @@ function isRunningAsRoot() {
25097
25750
  return false;
25098
25751
  }
25099
25752
  }
25753
+ function shouldUseAuthTokenForApiBaseUrl(apiBaseUrl) {
25754
+ if (!apiBaseUrl) return false;
25755
+ return /dashscope\.aliyuncs\.com\/api\/v2\/apps\/claude-code-proxy/i.test(apiBaseUrl) || /coding\.dashscope\.aliyuncs\.com\/apps\/anthropic/i.test(apiBaseUrl);
25756
+ }
25757
+ function buildAnthropicCredentialEnv(cfg) {
25758
+ if (!cfg.apiKey) return {};
25759
+ if (shouldUseAuthTokenForApiBaseUrl(cfg.apiBaseUrl)) {
25760
+ return { ANTHROPIC_AUTH_TOKEN: cfg.apiKey };
25761
+ }
25762
+ return { ANTHROPIC_API_KEY: cfg.apiKey };
25763
+ }
25764
+ function buildModelGatewayBaseUrl(serverApiUrl, subscriptionId) {
25765
+ const base = serverApiUrl.replace(/\/+$/, "");
25766
+ return `${base}/api/model-gateway/subscriptions/${encodeURIComponent(subscriptionId)}`;
25767
+ }
25100
25768
  async function chownForRootSpawn(targetPath, target) {
25101
25769
  try {
25102
25770
  await fs5.chown(targetPath, NODE_USER_UID, NODE_USER_UID);
@@ -25139,6 +25807,35 @@ var BridgeBusyError = class extends Error {
25139
25807
  this.name = "BridgeBusyError";
25140
25808
  }
25141
25809
  };
25810
+ function senderLabelForQuote(message) {
25811
+ if (message.senderAgentName) return message.senderAgentName;
25812
+ if (message.role === "user") return "user";
25813
+ if (message.role === "agent") return `agent:${message.senderAgentId ?? "unknown"}`;
25814
+ return "system";
25815
+ }
25816
+ function formatMessageAttachmentsForModel(message, sourceLabel) {
25817
+ if (!message.attachments || message.attachments.length === 0) return [];
25818
+ return message.attachments.flatMap(
25819
+ (attachment) => formatAttachmentForModel(attachment, { sourceLabel }).split("\n")
25820
+ );
25821
+ }
25822
+ function buildSingleReplyPrompt(task) {
25823
+ if (!task.replyToMessage) return sanitizeModelText(task.content);
25824
+ const quoted = task.replyToMessage;
25825
+ const label = senderLabelForQuote(quoted);
25826
+ const quotedContent = sanitizeModelText(quoted.content || (quoted.attachments?.length ? "[attachment]" : ""));
25827
+ return [
25828
+ "--- reply-to message ---",
25829
+ `You are replying to this earlier message from [${label}]:`,
25830
+ quotedContent,
25831
+ ...formatMessageAttachmentsForModel(quoted, "Replied message resource"),
25832
+ "--- end reply-to message ---",
25833
+ "",
25834
+ "--- user message ---",
25835
+ sanitizeModelText(task.content),
25836
+ "--- end user message ---"
25837
+ ].join("\n");
25838
+ }
25142
25839
  var AgentManager = class {
25143
25840
  agents = /* @__PURE__ */ new Map();
25144
25841
  lastUsedAt = /* @__PURE__ */ new Map();
@@ -25166,6 +25863,8 @@ var AgentManager = class {
25166
25863
  subscriptionRegistry;
25167
25864
  serverApiUrl;
25168
25865
  bridgeToken;
25866
+ workdirOverrideStore;
25867
+ visionBlockedScopes = /* @__PURE__ */ new Set();
25169
25868
  evictionTimer = null;
25170
25869
  // Lazy-loaded SDK query function. Injectable via constructor for tests.
25171
25870
  queryFn = null;
@@ -25187,6 +25886,7 @@ var AgentManager = class {
25187
25886
  this.bridgeToken = null;
25188
25887
  this.defaultModel = null;
25189
25888
  this.dataDir = path11.join(os5.homedir(), ".ahchat");
25889
+ this.workdirOverrideStore = null;
25190
25890
  } else {
25191
25891
  this.queryFn = options?.queryFn ?? null;
25192
25892
  this.workspacesDir = options?.workspacesDir ?? path11.join(os5.homedir(), ".ahchat", "workspaces");
@@ -25202,6 +25902,7 @@ var AgentManager = class {
25202
25902
  this.bridgeToken = options?.bridgeToken ?? null;
25203
25903
  this.defaultModel = options?.defaultModel ?? null;
25204
25904
  this.dataDir = options?.dataDir ?? path11.join(os5.homedir(), ".ahchat");
25905
+ this.workdirOverrideStore = options?.workdirOverrideStore ?? null;
25205
25906
  }
25206
25907
  this.evictionTimer = setInterval(() => {
25207
25908
  void this.evictIdle();
@@ -25223,6 +25924,16 @@ var AgentManager = class {
25223
25924
  return path11.join(this.workspacesDir, suffix);
25224
25925
  }
25225
25926
  remapServerWorkspaceCwd(agentConfig, scope, requestedCwd) {
25927
+ const overridden = this.workdirOverrideStore?.resolvePath(requestedCwd);
25928
+ if (overridden?.overridden) {
25929
+ logger11.info("Local workdir override applied to runtime cwd", {
25930
+ agentId: agentConfig.id,
25931
+ scope: scopeKey(scope),
25932
+ requested: requestedCwd,
25933
+ resolved: overridden.path
25934
+ });
25935
+ return overridden.path;
25936
+ }
25226
25937
  const remapped = remapServerWorkspacePath(requestedCwd, this.workspacesDir);
25227
25938
  if (remapped.remapped) {
25228
25939
  logger11.info("Server working directory remapped to local Bridge workspace", {
@@ -25274,19 +25985,26 @@ var AgentManager = class {
25274
25985
  agentId: agent.id,
25275
25986
  subscriptionId: cfg.subscriptionId
25276
25987
  });
25277
- return cfg;
25988
+ throw new Error(missingSubscriptionMessage(cfg.subscriptionId));
25278
25989
  }
25279
25990
  let sub = this.subscriptionRegistry.getById(cfg.subscriptionId);
25280
25991
  if (!sub) sub = await this.subscriptionRegistry.fetchById(cfg.subscriptionId);
25281
25992
  if (!sub) {
25282
- logger11.warn("Subscription not found; falling back to system OAuth", {
25993
+ logger11.warn("Subscription not found; refusing native OAuth fallback", {
25283
25994
  agentId: agent.id,
25284
25995
  subscriptionId: cfg.subscriptionId
25285
25996
  });
25286
- return { ...cfg, subscriptionType: "system" };
25997
+ throw new Error(missingSubscriptionMessage(cfg.subscriptionId));
25287
25998
  }
25288
25999
  const isPinnedModel = cfg.model && cfg.model !== "default";
25289
- const resolvedModel = isPinnedModel ? cfg.model : sub.defaultModel;
26000
+ const resolvedSubscriptionId = sub.resolvedSubscriptionId ?? cfg.subscriptionId;
26001
+ const apiFormat = sub.apiFormat ?? "anthropic";
26002
+ const tierModel = !isPinnedModel && cfg.capabilityTier ? await this.resolveCapabilityTierModel(
26003
+ resolvedSubscriptionId,
26004
+ cfg.capabilityTier,
26005
+ agent.id
26006
+ ) : null;
26007
+ const resolvedModel = isPinnedModel ? cfg.model : tierModel ?? sub.defaultModel;
25290
26008
  const limits = resolveModelLimits(sub.customModels, resolvedModel);
25291
26009
  if (limits.maxInputTokens || limits.maxOutputTokens) {
25292
26010
  logger11.info("Resolved per-model token limits", {
@@ -25296,15 +26014,84 @@ var AgentManager = class {
25296
26014
  maxOutputTokens: limits.maxOutputTokens
25297
26015
  });
25298
26016
  }
26017
+ if (limits.supportsVision) {
26018
+ logger11.info("Resolved per-model vision capability", {
26019
+ agentId: agent.id,
26020
+ model: resolvedModel,
26021
+ enabled: true
26022
+ });
26023
+ }
26024
+ let apiKey = sub.apiKey;
26025
+ let apiBaseUrl = sub.apiBaseUrl;
26026
+ if (apiFormat === "openai") {
26027
+ if (!this.serverApiUrl || !this.bridgeToken) {
26028
+ logger11.warn("OpenAI-format subscription requires server gateway credentials", {
26029
+ agentId: agent.id,
26030
+ subscriptionId: resolvedSubscriptionId,
26031
+ hasServerApiUrl: !!this.serverApiUrl,
26032
+ hasBridgeToken: !!this.bridgeToken
26033
+ });
26034
+ throw new Error("OpenAI \u683C\u5F0F\u8BA2\u9605\u6682\u65F6\u4E0D\u53EF\u7528\uFF1ABridge \u7F3A\u5C11 Server \u7F51\u5173\u5730\u5740\u6216\u4EE4\u724C\u3002");
26035
+ }
26036
+ apiKey = this.bridgeToken;
26037
+ apiBaseUrl = buildModelGatewayBaseUrl(this.serverApiUrl, resolvedSubscriptionId);
26038
+ }
25299
26039
  return {
25300
26040
  ...cfg,
25301
26041
  subscriptionType: sub.type,
25302
- apiKey: sub.apiKey,
25303
- apiBaseUrl: sub.apiBaseUrl,
26042
+ apiFormat,
26043
+ resolvedSubscriptionId,
26044
+ apiKey,
26045
+ apiBaseUrl,
25304
26046
  model: resolvedModel,
25305
26047
  ...limits
25306
26048
  };
25307
26049
  }
26050
+ async resolveCapabilityTierModel(subscriptionId, tier, agentId) {
26051
+ if (!this.serverApiUrl || !this.bridgeToken) return null;
26052
+ try {
26053
+ const base = this.serverApiUrl.replace(/\/$/, "");
26054
+ const res = await fetch(
26055
+ `${base}/api/onboarding/tiers`,
26056
+ { headers: bridgeAuthHeaders(this.bridgeToken) }
26057
+ );
26058
+ if (!res.ok) {
26059
+ logger11.warn("Capability tier resolution unavailable", {
26060
+ agentId,
26061
+ subscriptionId,
26062
+ tier,
26063
+ status: res.status
26064
+ });
26065
+ return null;
26066
+ }
26067
+ const tiers = await res.json();
26068
+ const match = tiers.find((item) => item.subscriptionId === subscriptionId && item.tier === tier);
26069
+ if (!match?.modelName) {
26070
+ logger11.warn("Capability tier mapping not found; using subscription default model", {
26071
+ agentId,
26072
+ subscriptionId,
26073
+ tier,
26074
+ availableTiers: tiers.map((item) => `${item.subscriptionId}:${item.tier}`)
26075
+ });
26076
+ return null;
26077
+ }
26078
+ logger11.info("Capability tier model resolved", {
26079
+ agentId,
26080
+ subscriptionId,
26081
+ tier,
26082
+ model: match.modelName
26083
+ });
26084
+ return match.modelName;
26085
+ } catch (error51) {
26086
+ logger11.warn("Capability tier resolution failed; using subscription default model", {
26087
+ agentId,
26088
+ subscriptionId,
26089
+ tier,
26090
+ error: error51
26091
+ });
26092
+ return null;
26093
+ }
26094
+ }
25308
26095
  /** Count live queries (anything not dead / removed). */
25309
26096
  countActiveQueries() {
25310
26097
  let n = 0;
@@ -25316,6 +26103,11 @@ var AgentManager = class {
25316
26103
  asRuntime(proc) {
25317
26104
  return proc;
25318
26105
  }
26106
+ modelInputModeForConfig(agentId, scope, cfg) {
26107
+ const key = runtimeKey(agentId, scope);
26108
+ if (this.visionBlockedScopes.has(key)) return "path-only";
26109
+ return cfg.supportsVision === false ? "path-only" : "vision";
26110
+ }
25319
26111
  async awaitQueryReturn(query4, timeoutMs, agentId) {
25320
26112
  const ret = query4.return(void 0);
25321
26113
  try {
@@ -25491,8 +26283,7 @@ var AgentManager = class {
25491
26283
  logger11.info("New API-key agent config dir; cleared stale session", { agentId: agentConfig.id });
25492
26284
  }
25493
26285
  const settingsPath = path11.join(effectiveConfigDir, "settings.json");
25494
- const envEntries = {};
25495
- if (cfg.apiKey) envEntries.ANTHROPIC_API_KEY = cfg.apiKey;
26286
+ const envEntries = buildAnthropicCredentialEnv(cfg);
25496
26287
  if (cfg.apiBaseUrl) envEntries.ANTHROPIC_BASE_URL = cfg.apiBaseUrl;
25497
26288
  let existingSettings = {};
25498
26289
  try {
@@ -25502,6 +26293,8 @@ var AgentManager = class {
25502
26293
  }
25503
26294
  const existingEnv = existingSettings.env ?? {};
25504
26295
  const mergedEnv = { ...existingEnv, ...envEntries };
26296
+ if (envEntries.ANTHROPIC_AUTH_TOKEN) delete mergedEnv.ANTHROPIC_API_KEY;
26297
+ if (envEntries.ANTHROPIC_API_KEY) delete mergedEnv.ANTHROPIC_AUTH_TOKEN;
25505
26298
  const mergedSettings = { ...existingSettings, env: mergedEnv };
25506
26299
  await fs5.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
25507
26300
  logger11.info("API-key agent using isolated config dir", {
@@ -25604,6 +26397,8 @@ var AgentManager = class {
25604
26397
  "Bash",
25605
26398
  "Glob",
25606
26399
  "Grep",
26400
+ "WebSearch",
26401
+ "WebFetch",
25607
26402
  "AskUserQuestion",
25608
26403
  "mcp__neural__neural_send",
25609
26404
  "mcp__neural__neural_list_scopes",
@@ -25619,6 +26414,7 @@ var AgentManager = class {
25619
26414
  "mcp__neural__read_document",
25620
26415
  ...isSmithAgent(agentConfig) ? [
25621
26416
  "mcp__neural__create_agent",
26417
+ "mcp__neural__update_agent_profile",
25622
26418
  "mcp__neural__list_friends",
25623
26419
  "mcp__neural__accept_friend",
25624
26420
  "mcp__neural__add_friend",
@@ -25633,7 +26429,7 @@ var AgentManager = class {
25633
26429
  // instructions as the workflow body (replacing the default code-implementation
25634
26430
  // phases). The SDK wraps it with read-only enforcement + ExitPlanMode protocol.
25635
26431
  planModeInstructions: (() => {
25636
- const smithTools = isSmithAgent(agentConfig) ? "\nSMITH-SPECIFIC TOOLS (available in plan mode): mcp__neural__read_skill, mcp__neural__fetch_logs, mcp__neural__create_agent \u2014 use these to research existing skills, check logs, and plan agent creation." : "";
26432
+ const smithTools = isSmithAgent(agentConfig) ? "\nSMITH-SPECIFIC TOOLS (available in plan mode): mcp__neural__read_skill, mcp__neural__fetch_logs, mcp__neural__create_agent, mcp__neural__update_agent_profile \u2014 use these to research existing skills, check logs, plan agent creation, and adjust Agent profiles." : "";
25637
26433
  return `You are a PLANNER, NOT an executor. The user will execute your plan later.
25638
26434
 
25639
26435
  AVAILABLE TOOLS: Read, Glob, Grep, WebSearch, WebFetch, AskUserQuestion, Write (plan file only).${smithTools}
@@ -25752,14 +26548,18 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
25752
26548
  const isolated = cfg.subscriptionType === "project" && Boolean(cfg.apiKey ?? cfg.apiBaseUrl);
25753
26549
  const modelLimitEnv = buildModelLimitEnv(cfg);
25754
26550
  if (isolated) {
25755
- return {
26551
+ const credentialEnv = buildAnthropicCredentialEnv(cfg);
26552
+ const env3 = {
25756
26553
  ...process.env,
25757
26554
  CLAUDE_CONFIG_DIR: effectiveConfigDir,
25758
26555
  CLAUDE_CODE_SIMPLE: "0",
25759
- ...cfg.apiKey ? { ANTHROPIC_API_KEY: cfg.apiKey } : {},
25760
26556
  ...cfg.apiBaseUrl ? { ANTHROPIC_BASE_URL: cfg.apiBaseUrl } : {},
26557
+ ...credentialEnv,
25761
26558
  ...modelLimitEnv
25762
26559
  };
26560
+ if (credentialEnv.ANTHROPIC_AUTH_TOKEN) delete env3.ANTHROPIC_API_KEY;
26561
+ if (credentialEnv.ANTHROPIC_API_KEY) delete env3.ANTHROPIC_AUTH_TOKEN;
26562
+ return env3;
25763
26563
  }
25764
26564
  const env2 = { ...process.env, ...modelLimitEnv };
25765
26565
  env2.CLAUDE_CODE_SIMPLE = "0";
@@ -25803,7 +26603,7 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
25803
26603
  return path11.join(effectiveConfigDir, "settings.json");
25804
26604
  })(),
25805
26605
  canUseTool: async (toolName, input) => {
25806
- if (toolName === "AskUserQuestion") {
26606
+ if (isAskUserQuestionToolName(toolName)) {
25807
26607
  return askGuard(input);
25808
26608
  }
25809
26609
  if (toolName === "CronCreate") {
@@ -25899,6 +26699,7 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
25899
26699
  prompt: inputController,
25900
26700
  options
25901
26701
  });
26702
+ const modelInputMode = this.modelInputModeForConfig(agentConfig.id, scope, cfg);
25902
26703
  const proc = {
25903
26704
  agentId: agentConfig.id,
25904
26705
  scope,
@@ -25930,8 +26731,18 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
25930
26731
  mergedTasks: [],
25931
26732
  planModeBuffer: [],
25932
26733
  createdAt: Date.now(),
26734
+ supportsVision: modelInputMode === "vision" && cfg.supportsVision !== false,
26735
+ modelInputMode,
25933
26736
  quietFlushTimer: null
25934
26737
  });
26738
+ logger11.info("Agent model input mode resolved", {
26739
+ agentId: agentConfig.id,
26740
+ scope: scopeKey(scope),
26741
+ model: cfg.model,
26742
+ modelInputMode,
26743
+ supportsVision: runtime.supportsVision,
26744
+ capabilitySupportsVision: cfg.supportsVision
26745
+ });
25935
26746
  procRef = proc;
25936
26747
  this.agents.set(key, proc);
25937
26748
  const preserved = this.dormantGroupInboxes.get(key);
@@ -26395,6 +27206,19 @@ ${lines.join("\n")}`;
26395
27206
  traceId: latest.traceId,
26396
27207
  groupId: latest.groupId
26397
27208
  });
27209
+ for (const entry of entries) {
27210
+ this.emit({
27211
+ type: "agent:merged",
27212
+ payload: {
27213
+ agentId: runtime.agentId,
27214
+ conversationId: entry.conversationId,
27215
+ ackId: entry.ackId,
27216
+ mergedIntoAckId: replyMessageId,
27217
+ groupId: entry.groupId,
27218
+ traceId: entry.traceId
27219
+ }
27220
+ });
27221
+ }
26398
27222
  if (includeBoard) {
26399
27223
  this.dispatchMemory.setBoardSignature(runtime.agentId, runtime.scope, boardSig);
26400
27224
  }
@@ -26420,7 +27244,13 @@ ${lines.join("\n")}`;
26420
27244
  runtime.cachedConversationId = task.conversationId;
26421
27245
  this.emit({
26422
27246
  type: "agent:status",
26423
- payload: { agentId: runtime.agentId, status: "thinking" }
27247
+ payload: {
27248
+ agentId: runtime.agentId,
27249
+ status: "thinking",
27250
+ traceId: task.traceId,
27251
+ ackId: task.replyMessageId,
27252
+ scopeKey: scopeKey(runtime.scope)
27253
+ }
26424
27254
  });
26425
27255
  logger11.info("dispatchToSDK emit agent:status thinking", {
26426
27256
  agentId: runtime.agentId,
@@ -26482,22 +27312,24 @@ ${lines.join("\n")}`;
26482
27312
  });
26483
27313
  }
26484
27314
  async pushTaskContent(runtime, task, onYielded) {
27315
+ const textContent = buildSingleReplyPrompt(task);
26485
27316
  if (!task.attachments || task.attachments.length === 0) {
26486
- runtime.inputController.push(task.content, runtime.ccSessionId ?? "", onYielded);
27317
+ runtime.inputController.push(textContent, runtime.ccSessionId ?? "", onYielded);
26487
27318
  return;
26488
27319
  }
26489
- const supportsVision = await this.detectVisionSupport();
26490
27320
  const attachmentBlocks = await adaptAttachmentsForSDK(
26491
27321
  task.attachments ?? [],
26492
27322
  {
26493
27323
  fetchBuffer: this.fetchBufferFromUrl,
26494
27324
  materializeFile: (attachment, buffer) => this.materializeAttachment(runtime, attachment, buffer),
26495
- supportsVision
27325
+ modelInputMode: runtime.modelInputMode,
27326
+ supportsVision: runtime.supportsVision
26496
27327
  }
26497
27328
  );
26498
- const text = task.content.trim() || "\u8BF7\u67E5\u770B\u6211\u53D1\u9001\u7684\u9644\u4EF6\u3002";
27329
+ const text = textContent.trim() || "\u8BF7\u67E5\u770B\u6211\u53D1\u9001\u7684\u9644\u4EF6\u3002";
27330
+ const safeText = sanitizeModelText(text);
26499
27331
  const contentParts = [
26500
- { type: "text", text },
27332
+ { type: "text", text: safeText },
26501
27333
  ...attachmentBlocks
26502
27334
  ];
26503
27335
  runtime.inputController.push(contentParts, runtime.ccSessionId ?? "", onYielded);
@@ -26533,9 +27365,12 @@ ${lines.join("\n")}`;
26533
27365
  return materialized;
26534
27366
  }
26535
27367
  async resolveExistingWorkspaceAttachmentPath(runtime, attachment) {
27368
+ const localWorkspacePath = attachment.metadata?.localWorkspacePath;
26536
27369
  const workspacePath = attachment.metadata?.workspacePath;
26537
- if (typeof workspacePath !== "string" || !workspacePath.trim()) return null;
26538
- const candidate = path11.resolve(workspacePath);
27370
+ const rawPath = typeof localWorkspacePath === "string" && localWorkspacePath.trim() ? localWorkspacePath : workspacePath;
27371
+ if (typeof rawPath !== "string" || !rawPath.trim()) return null;
27372
+ const remapped = remapServerWorkspacePath(rawPath, this.workspacesDir);
27373
+ const candidate = path11.resolve(remapped.path);
26539
27374
  if (!this.isPathInsideBase(candidate, runtime.cwd)) return null;
26540
27375
  try {
26541
27376
  const stat3 = await fs5.stat(candidate);
@@ -26545,6 +27380,8 @@ ${lines.join("\n")}`;
26545
27380
  agentId: runtime.agentId,
26546
27381
  attachmentId: attachment.id,
26547
27382
  workspacePath,
27383
+ localWorkspacePath,
27384
+ resolvedPath: candidate,
26548
27385
  error: e
26549
27386
  });
26550
27387
  return null;
@@ -26580,6 +27417,56 @@ ${lines.join("\n")}`;
26580
27417
  }
26581
27418
  return true;
26582
27419
  }
27420
+ isUnsupportedVisionInputError(errorText) {
27421
+ return /Unexpected item type in content/i.test(errorText);
27422
+ }
27423
+ markVisionUnsupportedForScope(runtime, errorText) {
27424
+ const key = runtimeKey(runtime.agentId, runtime.scope);
27425
+ this.visionBlockedScopes.add(key);
27426
+ this.sessionStore.delete(runtime.agentId, runtime.scope);
27427
+ runtime.modelInputMode = "path-only";
27428
+ runtime.supportsVision = false;
27429
+ runtime.visionRecoveryPending = true;
27430
+ logger11.warn("Vision input unsupported; clearing SDK session and degrading scope to path-only attachments", {
27431
+ agentId: runtime.agentId,
27432
+ scope: scopeKey(runtime.scope),
27433
+ sessionId: runtime.ccSessionId,
27434
+ errorText: errorText.slice(0, 300)
27435
+ });
27436
+ }
27437
+ closeRuntimeAfterVisionRecovery(runtime) {
27438
+ const key = runtimeKey(runtime.agentId, runtime.scope);
27439
+ const releaseTask = (task) => {
27440
+ this.emit({
27441
+ type: "agent:error",
27442
+ payload: {
27443
+ agentId: runtime.agentId,
27444
+ conversationId: task.conversationId,
27445
+ ackId: task.replyMessageId,
27446
+ traceId: task.traceId,
27447
+ error: "Previous image input failed because this model/backend does not support multimodal content. Please resend this message; future attachments will be sent as paths/text references."
27448
+ }
27449
+ });
27450
+ };
27451
+ for (const task of runtime.injectedTasks) releaseTask(task);
27452
+ for (const task of runtime.mergedTasks) releaseTask(task);
27453
+ for (const task of runtime.planModeBuffer) releaseTask(task);
27454
+ runtime.status = "dead";
27455
+ runtime.currentTask = null;
27456
+ runtime.injectedTasks = [];
27457
+ runtime.mergedTasks = [];
27458
+ runtime.planModeBuffer = [];
27459
+ runtime.inputController.drainQueue();
27460
+ runtime.inputController.close();
27461
+ this.clearQuietFlushTimer(runtime);
27462
+ this.agents.delete(key);
27463
+ this.lastUsedAt.delete(key);
27464
+ this.dispatchMemory.deleteScope(runtime.agentId, runtime.scope);
27465
+ logger11.info("Closed runtime after unsupported vision error; next dispatch will start a clean path-only session", {
27466
+ agentId: runtime.agentId,
27467
+ scope: scopeKey(runtime.scope)
27468
+ });
27469
+ }
26583
27470
  emitTaskPushError(runtime, task, error51) {
26584
27471
  const errMsg = error51 instanceof Error ? error51.message : String(error51);
26585
27472
  logger11.error("Failed to push message to Agent", {
@@ -26620,6 +27507,10 @@ ${lines.join("\n")}`;
26620
27507
  onTaskCompleted(proc, carrierMessageId) {
26621
27508
  const runtime = this.asRuntime(proc);
26622
27509
  const completedTask = proc.currentTask;
27510
+ if (runtime.visionRecoveryPending) {
27511
+ this.closeRuntimeAfterVisionRecovery(runtime);
27512
+ return;
27513
+ }
26623
27514
  if (proc.compactInProgress) {
26624
27515
  proc.compactInProgress = false;
26625
27516
  this.dispatchMemory.reset(proc.agentId, proc.scope);
@@ -26631,7 +27522,13 @@ ${lines.join("\n")}`;
26631
27522
  });
26632
27523
  this.emit({
26633
27524
  type: "agent:status",
26634
- payload: { agentId: proc.agentId, status: "idle" }
27525
+ payload: {
27526
+ agentId: proc.agentId,
27527
+ status: "idle",
27528
+ traceId: completedTask?.traceId,
27529
+ ackId: completedTask?.replyMessageId,
27530
+ scopeKey: scopeKey(proc.scope)
27531
+ }
26635
27532
  });
26636
27533
  }
26637
27534
  if (completedTask && runtime.mergedTasks.length > 0) {
@@ -26711,7 +27608,13 @@ ${lines.join("\n")}`;
26711
27608
  proc.currentTaskStartedAt = Date.now();
26712
27609
  this.emit({
26713
27610
  type: "agent:status",
26714
- payload: { agentId: proc.agentId, status: "compacting" }
27611
+ payload: {
27612
+ agentId: proc.agentId,
27613
+ status: "compacting",
27614
+ traceId: compactTraceId,
27615
+ ackId: proc.currentTask.replyMessageId,
27616
+ scopeKey: scopeKey(proc.scope)
27617
+ }
26715
27618
  });
26716
27619
  return;
26717
27620
  }
@@ -26870,6 +27773,15 @@ ${lines.join("\n")}`;
26870
27773
  });
26871
27774
  return;
26872
27775
  }
27776
+ if (!payload.conversationId) {
27777
+ logger11.error("Neural send rejected: missing conversationId", {
27778
+ error: new Error("neural_send target conversationId is required"),
27779
+ agentId: agentConfig.id,
27780
+ fromScope: payload.fromScopeKey,
27781
+ toScope: payload.toScopeKey
27782
+ });
27783
+ return;
27784
+ }
26873
27785
  const targetScope = payload.toScopeKey === "single" ? { kind: "single" } : { kind: "group", groupId: payload.groupId ?? payload.toScopeKey.replace("group:", "") };
26874
27786
  this.sessionStore.delete(agentConfig.id, targetScope);
26875
27787
  this.dispatchMemory.deleteScope(agentConfig.id, targetScope);
@@ -26881,9 +27793,9 @@ ${lines.join("\n")}`;
26881
27793
  const enveloped = buildInnerVoiceEnvelope(payloadWithTrigger, ctx);
26882
27794
  const task = {
26883
27795
  content: enveloped,
26884
- replyMessageId: `msg_nsend_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
26885
- conversationId: payload.conversationId ?? "",
26886
- traceId: `tr_nsend_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
27796
+ replyMessageId: createMessageId(),
27797
+ conversationId: payload.conversationId,
27798
+ traceId: createTraceId(),
26887
27799
  groupId: payload.groupId
26888
27800
  };
26889
27801
  const key = runtimeKey(agentConfig.id, targetScope);
@@ -26916,7 +27828,7 @@ ${lines.join("\n")}`;
26916
27828
  } else {
26917
27829
  const runtime = this.asRuntime(existingProc);
26918
27830
  runtime.cachedConversationId = task.conversationId;
26919
- runtime.inputController.push(task.content, runtime.ccSessionId ?? "");
27831
+ runtime.inputController.push(sanitizeModelText(task.content), runtime.ccSessionId ?? "");
26920
27832
  runtime.injectedTasks.push(task);
26921
27833
  logger11.info("Neural send injected mid-turn", {
26922
27834
  agentId: agentConfig.id,
@@ -27098,7 +28010,7 @@ ${lines.join("\n")}`;
27098
28010
  (k) => k === agentId || k.startsWith(`${agentId}::`)
27099
28011
  );
27100
28012
  if (keys.length === 0) {
27101
- logger11.warn("terminate: no process for agent", { agentId });
28013
+ logger11.info("terminate: no process for agent", { agentId });
27102
28014
  }
27103
28015
  for (const key of keys) {
27104
28016
  const proc = this.agents.get(key);
@@ -27225,6 +28137,11 @@ ${lines.join("\n")}`;
27225
28137
  for (const t of queued) {
27226
28138
  collectOrInterrupt(t);
27227
28139
  }
28140
+ const bufferedAtClose = [...runtime.planModeBuffer];
28141
+ runtime.planModeBuffer = [];
28142
+ for (const t of bufferedAtClose) {
28143
+ collectOrInterrupt(t);
28144
+ }
27228
28145
  const mergedAtClose = [...runtime.mergedTasks];
27229
28146
  runtime.mergedTasks = [];
27230
28147
  for (const t of mergedAtClose) {
@@ -27370,7 +28287,12 @@ ${lines.join("\n")}`;
27370
28287
  message,
27371
28288
  this.emit,
27372
28289
  this.sessionStore,
27373
- (doneMessageId) => this.onTaskCompleted(runtime, doneMessageId)
28290
+ (doneMessageId) => this.onTaskCompleted(runtime, doneMessageId),
28291
+ (errorText) => {
28292
+ if (this.isUnsupportedVisionInputError(errorText)) {
28293
+ this.markVisionUnsupportedForScope(runtime, errorText);
28294
+ }
28295
+ }
27374
28296
  );
27375
28297
  if (wasPlanActive && !runtime.planModeActive) {
27376
28298
  if (runtime.planModeRef) runtime.planModeRef.active = false;
@@ -27388,13 +28310,18 @@ ${lines.join("\n")}`;
27388
28310
  } catch (err) {
27389
28311
  const errMsg = err.message ?? String(err);
27390
28312
  const isResumeFail = /session|conversation.*not found/i.test(errMsg);
28313
+ const isUnsupportedVisionInput = this.isUnsupportedVisionInputError(errMsg);
27391
28314
  logger11.error("Agent query stream ended with error", {
27392
28315
  agentId: runtime.agentId,
27393
28316
  scope: scopeKey(runtime.scope),
27394
28317
  isResumeFail,
28318
+ isUnsupportedVisionInput,
27395
28319
  staleSessionId: runtime.ccSessionId,
27396
28320
  error: err
27397
28321
  });
28322
+ if (isUnsupportedVisionInput) {
28323
+ this.visionBlockedScopes.add(runtimeKey(runtime.agentId, runtime.scope));
28324
+ }
27398
28325
  this.sessionStore.delete(runtime.agentId, runtime.scope);
27399
28326
  this.dispatchMemory.deleteScope(runtime.agentId, runtime.scope);
27400
28327
  logger11.info("Cleared stale session after query crash", {
@@ -27407,6 +28334,7 @@ ${lines.join("\n")}`;
27407
28334
  this.agents.delete(key);
27408
28335
  this.lastUsedAt.delete(key);
27409
28336
  const errorText = isResumeFail ? `\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u6D88\u606F\uFF08${errMsg}\uFF09` : `Agent query crashed: ${errMsg}`;
28337
+ const emittedErrorText = isUnsupportedVisionInput ? "Current model/backend does not support image multimodal input. This image message failed; AHChat has cleared this scope session and future messages will send attachments as paths/text references." : errorText;
27410
28338
  if (runtime.currentTask) {
27411
28339
  this.emit({
27412
28340
  type: "agent:error",
@@ -27415,7 +28343,7 @@ ${lines.join("\n")}`;
27415
28343
  conversationId: runtime.currentTask.conversationId,
27416
28344
  ackId: runtime.currentTask.replyMessageId,
27417
28345
  traceId: runtime.currentTask.traceId,
27418
- error: errorText
28346
+ error: emittedErrorText
27419
28347
  }
27420
28348
  });
27421
28349
  runtime.currentTask = null;
@@ -27428,7 +28356,7 @@ ${lines.join("\n")}`;
27428
28356
  conversationId: task.conversationId,
27429
28357
  ackId: task.replyMessageId,
27430
28358
  traceId: task.traceId,
27431
- error: errorText
28359
+ error: emittedErrorText
27432
28360
  }
27433
28361
  });
27434
28362
  }
@@ -27441,11 +28369,24 @@ ${lines.join("\n")}`;
27441
28369
  conversationId: task.conversationId,
27442
28370
  ackId: task.replyMessageId,
27443
28371
  traceId: task.traceId,
27444
- error: errorText
28372
+ error: emittedErrorText
27445
28373
  }
27446
28374
  });
27447
28375
  }
27448
28376
  runtime.mergedTasks = [];
28377
+ for (const task of runtime.planModeBuffer) {
28378
+ this.emit({
28379
+ type: "agent:error",
28380
+ payload: {
28381
+ agentId: runtime.agentId,
28382
+ conversationId: task.conversationId,
28383
+ ackId: task.replyMessageId,
28384
+ traceId: task.traceId,
28385
+ error: emittedErrorText
28386
+ }
28387
+ });
28388
+ }
28389
+ runtime.planModeBuffer = [];
27449
28390
  }
27450
28391
  }
27451
28392
  getStatus(agentId, scope = { kind: "single" }) {
@@ -27623,6 +28564,26 @@ ${lines.join("\n")}`;
27623
28564
  traceId: t.traceId
27624
28565
  });
27625
28566
  }
28567
+ const buffered = [...runtime.planModeBuffer];
28568
+ runtime.planModeBuffer = [];
28569
+ for (const t of buffered) {
28570
+ this.emit({
28571
+ type: "agent:no_reply",
28572
+ payload: {
28573
+ agentId,
28574
+ conversationId: t.conversationId,
28575
+ ackId: t.replyMessageId,
28576
+ groupId: t.groupId,
28577
+ reason: "cancelled_by_user",
28578
+ traceId: t.traceId
28579
+ }
28580
+ });
28581
+ logger11.info("cancelReply: emitted agent:no_reply for plan-mode buffered task", {
28582
+ agentId,
28583
+ ackId: t.replyMessageId,
28584
+ traceId: t.traceId
28585
+ });
28586
+ }
27626
28587
  runtime.currentTask = null;
27627
28588
  this.clearQuietFlushTimer(runtime);
27628
28589
  proc.status = "dead";
@@ -27743,7 +28704,7 @@ function computeBoardSignature(entry) {
27743
28704
 
27744
28705
  // src/bridgeFetchAuth.ts
27745
28706
  var logger12 = createModuleLogger("bridge.fetchAuth");
27746
- var BRIDGE_TOKEN_HEADER = "X-AHChat-Bridge-Token";
28707
+ var BRIDGE_TOKEN_HEADER2 = "X-AHChat-Bridge-Token";
27747
28708
  function installBridgeFetchAuth(serverApiUrl, token) {
27748
28709
  if (typeof globalThis.fetch !== "function") {
27749
28710
  logger12.warn("globalThis.fetch not available, cannot install bridge fetch auth");
@@ -27771,24 +28732,17 @@ function installBridgeFetchAuth(serverApiUrl, token) {
27771
28732
  else url2 = input.url;
27772
28733
  if (!shouldInject(url2)) return original(input, init);
27773
28734
  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);
28735
+ if (!headers.has(BRIDGE_TOKEN_HEADER2)) {
28736
+ headers.set(BRIDGE_TOKEN_HEADER2, token);
27776
28737
  }
27777
28738
  return original(input, { ...init, headers });
27778
28739
  });
27779
28740
  logger12.info("Bridge fetch auth installed", {
27780
28741
  serverApiUrl: base,
27781
- tokenPrefix: `${token.slice(0, 8)}...`
28742
+ hasToken: Boolean(token)
27782
28743
  });
27783
28744
  }
27784
28745
 
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
28746
  // src/agentRegistry.ts
27793
28747
  var logger13 = createModuleLogger("agent.registry");
27794
28748
  var HttpAgentRegistry = class {
@@ -27801,8 +28755,8 @@ var HttpAgentRegistry = class {
27801
28755
  agents = /* @__PURE__ */ new Map();
27802
28756
  apiUrl(suffix) {
27803
28757
  const base = this.serverApiUrl.replace(/\/$/, "");
27804
- const path24 = suffix.startsWith("/") ? suffix : `/${suffix}`;
27805
- return `${base}${path24}`;
28758
+ const path26 = suffix.startsWith("/") ? suffix : `/${suffix}`;
28759
+ return `${base}${path26}`;
27806
28760
  }
27807
28761
  async refresh() {
27808
28762
  const attempt = async () => {
@@ -27899,8 +28853,16 @@ var HttpSubscriptionRegistry = class {
27899
28853
  subscriptions = /* @__PURE__ */ new Map();
27900
28854
  apiUrl(suffix) {
27901
28855
  const base = this.serverApiUrl.replace(/\/$/, "");
27902
- const path24 = suffix.startsWith("/") ? suffix : `/${suffix}`;
27903
- return `${base}${path24}`;
28856
+ const path26 = suffix.startsWith("/") ? suffix : `/${suffix}`;
28857
+ return `${base}${path26}`;
28858
+ }
28859
+ rebuildPrimaryAlias() {
28860
+ this.subscriptions.delete(PRIMARY_COMPANY_SUBSCRIPTION_ID);
28861
+ const primary = Array.from(this.subscriptions.values()).find(
28862
+ (sub) => sub.billingMode === "company_billable" && sub.isPrimaryCompany === true
28863
+ );
28864
+ if (!primary) return;
28865
+ this.subscriptions.set(PRIMARY_COMPANY_SUBSCRIPTION_ID, makePrimaryCompanySubscriptionAlias(primary));
27904
28866
  }
27905
28867
  async refresh() {
27906
28868
  const attempt = async () => {
@@ -27929,6 +28891,7 @@ var HttpSubscriptionRegistry = class {
27929
28891
  const s = item;
27930
28892
  if (s && typeof s.id === "string") this.subscriptions.set(s.id, s);
27931
28893
  }
28894
+ this.rebuildPrimaryAlias();
27932
28895
  logger14.info("Subscription registry refreshed", { count: this.subscriptions.size });
27933
28896
  } catch (e) {
27934
28897
  logger14.warn("Subscription registry parse failed", { error: e });
@@ -27938,6 +28901,10 @@ var HttpSubscriptionRegistry = class {
27938
28901
  return this.subscriptions.get(id) ?? null;
27939
28902
  }
27940
28903
  async fetchById(id) {
28904
+ if (isPrimaryCompanySubscriptionId(id)) {
28905
+ await this.refresh();
28906
+ return this.getById(id);
28907
+ }
27941
28908
  try {
27942
28909
  const res = await fetch(this.apiUrl(`/api/subscriptions/${encodeURIComponent(id)}`), {
27943
28910
  headers: bridgeAuthHeaders(this.bridgeToken)
@@ -27953,9 +28920,11 @@ var HttpSubscriptionRegistry = class {
27953
28920
  }
27954
28921
  upsert(sub) {
27955
28922
  this.subscriptions.set(sub.id, sub);
28923
+ this.rebuildPrimaryAlias();
27956
28924
  }
27957
28925
  remove(id) {
27958
28926
  this.subscriptions.delete(id);
28927
+ this.rebuildPrimaryAlias();
27959
28928
  }
27960
28929
  };
27961
28930
 
@@ -28171,6 +29140,13 @@ var wrapper_default = import_websocket.default;
28171
29140
 
28172
29141
  // src/connector.ts
28173
29142
  var logger16 = createModuleLogger("ws.connector");
29143
+ function buildBridgeWebSocketUrl(serverUrl, bridgeToken) {
29144
+ const token = bridgeToken?.trim();
29145
+ if (!token) return serverUrl;
29146
+ const url2 = new URL(serverUrl);
29147
+ url2.searchParams.set("token", token);
29148
+ return url2.toString();
29149
+ }
28174
29150
  var ServerConnector = class {
28175
29151
  ws = null;
28176
29152
  reconnectAttempts = 0;
@@ -28195,13 +29171,12 @@ var ServerConnector = class {
28195
29171
  }
28196
29172
  connect() {
28197
29173
  if (this.closing) return;
28198
- const url2 = new URL(this.config.serverUrl);
28199
- if (this.config.bridgeToken) {
28200
- url2.searchParams.set("token", this.config.bridgeToken);
28201
- }
28202
- const wsUrl = url2.toString();
28203
- logger16.info("Connecting to server", { url: wsUrl });
28204
- const ws = new wrapper_default(wsUrl);
29174
+ logger16.info("Connecting to server", {
29175
+ url: this.config.serverUrl,
29176
+ hasBridgeToken: Boolean(this.config.bridgeToken)
29177
+ });
29178
+ const headers = this.config.bridgeToken ? { "X-AHChat-Bridge-Token": this.config.bridgeToken } : void 0;
29179
+ const ws = new wrapper_default(buildBridgeWebSocketUrl(this.config.serverUrl, this.config.bridgeToken), { headers });
28205
29180
  ws.on("open", () => {
28206
29181
  this.ws = ws;
28207
29182
  this.reconnectAttempts = 0;
@@ -28425,9 +29400,213 @@ ${s.slice(-TRUNCATE_TAIL)}`;
28425
29400
  function cwdToProjectSlug(cwd) {
28426
29401
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
28427
29402
  }
28428
- function resolveJsonlPath(sessionId, cwd) {
29403
+ function resolveJsonlPathInProjectsDir(projectsDir, sessionId, cwd) {
29404
+ const slug = cwdToProjectSlug(cwd);
29405
+ return path12.join(projectsDir, slug, `${sessionId}.jsonl`);
29406
+ }
29407
+ function resolveProjectDirInProjectsDir(projectsDir, cwd) {
28429
29408
  const slug = cwdToProjectSlug(cwd);
28430
- return path12.join(os7.homedir(), ".claude", "projects", slug, `${sessionId}.jsonl`);
29409
+ return path12.join(projectsDir, slug);
29410
+ }
29411
+ function errorCode(e) {
29412
+ if (e instanceof Error && "code" in e) {
29413
+ const code = e.code;
29414
+ return typeof code === "string" ? code : void 0;
29415
+ }
29416
+ return void 0;
29417
+ }
29418
+ async function isReadableFile(filePath) {
29419
+ try {
29420
+ const stat3 = await fs6.stat(filePath);
29421
+ return stat3.isFile();
29422
+ } catch (e) {
29423
+ const code = errorCode(e);
29424
+ if (code === "ENOENT" || code === "ENOTDIR") return false;
29425
+ logger17.warn("JSONL candidate stat failed", { filePath, error: e });
29426
+ return false;
29427
+ }
29428
+ }
29429
+ function uniquePaths(paths) {
29430
+ const seen = /* @__PURE__ */ new Set();
29431
+ const out = [];
29432
+ for (const p of paths) {
29433
+ const trimmed = p.trim();
29434
+ if (!trimmed) continue;
29435
+ const key = path12.normalize(trimmed);
29436
+ if (seen.has(key)) continue;
29437
+ seen.add(key);
29438
+ out.push(trimmed);
29439
+ }
29440
+ return out;
29441
+ }
29442
+ function claudeProjectsDirs(opts) {
29443
+ return uniquePaths([
29444
+ opts.agentConfigDir ? path12.join(opts.agentConfigDir, "api-key-agents", opts.agentId, "projects") : "",
29445
+ opts.agentConfigDir ? path12.join(opts.agentConfigDir, "projects") : "",
29446
+ process.env.CLAUDE_CONFIG_DIR ? path12.join(process.env.CLAUDE_CONFIG_DIR, "projects") : "",
29447
+ path12.join(os7.homedir(), ".claude", "projects")
29448
+ ]);
29449
+ }
29450
+ function fallbackBridgeWorkspacePath(requestedCwd, workspacesDir, fallbackSegment) {
29451
+ const suffix = extractAhchatWorkspaceSuffix(requestedCwd);
29452
+ if (suffix) return path12.join(workspacesDir, suffix);
29453
+ const normalized = requestedCwd.trim();
29454
+ const basename = normalized ? path12.basename(path12.normalize(normalized)) : "";
29455
+ const segment = basename && basename !== "." && basename !== path12.sep ? basename : fallbackSegment;
29456
+ return path12.join(workspacesDir, segment);
29457
+ }
29458
+ function cwdCandidatesForBridge(requestedCwd, opts) {
29459
+ const candidates = [requestedCwd];
29460
+ const overridden = opts.workdirOverrideStore?.resolvePath(requestedCwd);
29461
+ if (overridden?.overridden) {
29462
+ candidates.push(overridden.path);
29463
+ }
29464
+ if (opts.workspacesDir) {
29465
+ const remapped = remapServerWorkspacePath(requestedCwd, opts.workspacesDir);
29466
+ candidates.push(remapped.path);
29467
+ candidates.push(fallbackBridgeWorkspacePath(requestedCwd, opts.workspacesDir, opts.fallbackSegment));
29468
+ }
29469
+ return uniquePaths(candidates);
29470
+ }
29471
+ async function resolveDumpWorkdir(requestedCwd, opts) {
29472
+ const overridden = opts.workdirOverrideStore?.resolvePath(requestedCwd);
29473
+ if (overridden?.overridden) {
29474
+ logger17.info("Dump workdir resolved to local desktop override", {
29475
+ agentId: opts.agentId,
29476
+ requested: requestedCwd,
29477
+ resolved: overridden.path
29478
+ });
29479
+ return overridden.path;
29480
+ }
29481
+ if (!opts.workspacesDir) return requestedCwd;
29482
+ const remapped = remapServerWorkspacePath(requestedCwd, opts.workspacesDir);
29483
+ if (remapped.remapped) {
29484
+ logger17.info("Dump workdir remapped to local Bridge workspace", {
29485
+ agentId: opts.agentId,
29486
+ requested: requestedCwd,
29487
+ remapped: remapped.path
29488
+ });
29489
+ return remapped.path;
29490
+ }
29491
+ try {
29492
+ await fs6.mkdir(requestedCwd, { recursive: true });
29493
+ return requestedCwd;
29494
+ } catch (e) {
29495
+ const fallback = fallbackBridgeWorkspacePath(requestedCwd, opts.workspacesDir, opts.fallbackSegment);
29496
+ if (path12.normalize(fallback) === path12.normalize(requestedCwd)) throw e;
29497
+ logger17.warn("Dump workdir inaccessible; using local Bridge workspace fallback", {
29498
+ agentId: opts.agentId,
29499
+ requested: requestedCwd,
29500
+ fallback,
29501
+ error: e
29502
+ });
29503
+ return fallback;
29504
+ }
29505
+ }
29506
+ async function findJsonlInClaudeProjects(sessionId, projectsDirs) {
29507
+ for (const projectsDir of projectsDirs) {
29508
+ let dirs;
29509
+ try {
29510
+ dirs = await fs6.readdir(projectsDir, { withFileTypes: true });
29511
+ } catch (e) {
29512
+ const code = errorCode(e);
29513
+ if (code === "ENOENT" || code === "ENOTDIR") continue;
29514
+ logger17.warn("Claude projects directory scan failed", { projectsDir, sessionId, error: e });
29515
+ continue;
29516
+ }
29517
+ for (const dir of dirs) {
29518
+ if (!dir.isDirectory()) continue;
29519
+ const candidate = path12.join(projectsDir, dir.name, `${sessionId}.jsonl`);
29520
+ if (await isReadableFile(candidate)) return candidate;
29521
+ }
29522
+ }
29523
+ return null;
29524
+ }
29525
+ async function latestJsonlInProjectDir(projectDir) {
29526
+ let entries;
29527
+ try {
29528
+ entries = await fs6.readdir(projectDir, { withFileTypes: true });
29529
+ } catch (e) {
29530
+ const code = errorCode(e);
29531
+ if (code === "ENOENT" || code === "ENOTDIR") return null;
29532
+ logger17.warn("Claude project directory scan failed", { projectDir, error: e });
29533
+ return null;
29534
+ }
29535
+ let latest = null;
29536
+ for (const entry of entries) {
29537
+ if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
29538
+ const jsonlPath = path12.join(projectDir, entry.name);
29539
+ let stat3;
29540
+ try {
29541
+ stat3 = await fs6.stat(jsonlPath);
29542
+ } catch (e) {
29543
+ logger17.warn("Discovered JSONL stat failed", { jsonlPath, error: e });
29544
+ continue;
29545
+ }
29546
+ const sessionId = path12.basename(entry.name, ".jsonl");
29547
+ const discovered = { sessionId, jsonlPath, lastModified: stat3.mtimeMs };
29548
+ if (!latest || discovered.lastModified > latest.lastModified) {
29549
+ latest = discovered;
29550
+ }
29551
+ }
29552
+ return latest;
29553
+ }
29554
+ function isAgentIsolatedProjectsDir(projectsDir, agentId) {
29555
+ const normalized = path12.normalize(projectsDir).toLowerCase();
29556
+ const marker = path12.normalize(path12.join("api-key-agents", agentId, "projects")).toLowerCase();
29557
+ return normalized.endsWith(marker);
29558
+ }
29559
+ async function discoverLatestJsonlForScope(opts) {
29560
+ const discovered = [];
29561
+ const projectDirs = uniquePaths(
29562
+ opts.projectsDirs.flatMap(
29563
+ (projectsDir) => opts.cwdCandidates.map((cwd) => resolveProjectDirInProjectsDir(projectsDir, cwd))
29564
+ )
29565
+ );
29566
+ for (const projectDir of projectDirs) {
29567
+ const latest = await latestJsonlInProjectDir(projectDir);
29568
+ if (latest) discovered.push(latest);
29569
+ }
29570
+ if (opts.allowAgentWideScan) {
29571
+ for (const projectsDir of opts.projectsDirs) {
29572
+ if (!isAgentIsolatedProjectsDir(projectsDir, opts.agentId)) continue;
29573
+ let dirs;
29574
+ try {
29575
+ dirs = await fs6.readdir(projectsDir, { withFileTypes: true });
29576
+ } catch (e) {
29577
+ const code = errorCode(e);
29578
+ if (code === "ENOENT" || code === "ENOTDIR") continue;
29579
+ logger17.warn("Agent isolated projects scan failed", { projectsDir, agentId: opts.agentId, error: e });
29580
+ continue;
29581
+ }
29582
+ for (const dir of dirs) {
29583
+ if (!dir.isDirectory()) continue;
29584
+ const latest = await latestJsonlInProjectDir(path12.join(projectsDir, dir.name));
29585
+ if (latest) discovered.push(latest);
29586
+ }
29587
+ }
29588
+ }
29589
+ discovered.sort((a, b) => b.lastModified - a.lastModified);
29590
+ return discovered[0] ?? null;
29591
+ }
29592
+ async function resolveReadableJsonlPath(sessionId, cwdCandidates, projectsDirs) {
29593
+ const checkedPaths = uniquePaths(
29594
+ projectsDirs.flatMap(
29595
+ (projectsDir) => cwdCandidates.map((cwd) => resolveJsonlPathInProjectsDir(projectsDir, sessionId, cwd))
29596
+ )
29597
+ );
29598
+ for (const candidate of checkedPaths) {
29599
+ if (await isReadableFile(candidate)) {
29600
+ return { jsonlPath: candidate, checkedPaths };
29601
+ }
29602
+ }
29603
+ const found = await findJsonlInClaudeProjects(sessionId, projectsDirs);
29604
+ if (found) {
29605
+ return { jsonlPath: found, checkedPaths };
29606
+ }
29607
+ const preview = checkedPaths.slice(0, 4).join("; ");
29608
+ const suffix = checkedPaths.length > 4 ? `; ... +${checkedPaths.length - 4} more` : "";
29609
+ throw new Error(`session JSONL not found for ${sessionId}; checked ${preview}${suffix}`);
28431
29610
  }
28432
29611
  var RENDERABLE_TYPES = /* @__PURE__ */ new Set(["user", "assistant", "system", "attachment"]);
28433
29612
  async function readJsonlEntries(filePath) {
@@ -28675,6 +29854,100 @@ function resolveScopeCwd(agentWorkDir, scopeKey2, groupRegistry) {
28675
29854
  }
28676
29855
  return agentWorkDir;
28677
29856
  }
29857
+ function scopeFromKey(scopeKey2) {
29858
+ if (scopeKey2 === "single") return { kind: "single" };
29859
+ if (scopeKey2.startsWith("group:")) {
29860
+ return { kind: "group", groupId: scopeKey2.slice("group:".length) };
29861
+ }
29862
+ return null;
29863
+ }
29864
+ async function resolveDumpJsonlSource(opts) {
29865
+ try {
29866
+ const resolved = await resolveReadableJsonlPath(opts.sessionId, opts.cwdCandidates, opts.projectsDirs);
29867
+ return { sessionId: opts.sessionId, recovered: false, ...resolved };
29868
+ } catch (e) {
29869
+ const discovered = await discoverLatestJsonlForScope({
29870
+ agentId: opts.agentId,
29871
+ cwdCandidates: opts.cwdCandidates,
29872
+ projectsDirs: opts.projectsDirs,
29873
+ allowAgentWideScan: opts.scopeKey === "single"
29874
+ });
29875
+ if (!discovered) throw e;
29876
+ const scope = scopeFromKey(opts.scopeKey);
29877
+ if (scope) {
29878
+ opts.sessionStore.set(opts.agentId, scope, discovered.sessionId);
29879
+ }
29880
+ logger17.warn("Stale dump session id recovered from local Claude projects", {
29881
+ agentId: opts.agentId,
29882
+ scopeKey: opts.scopeKey,
29883
+ previousSessionId: opts.sessionId,
29884
+ recoveredSessionId: discovered.sessionId,
29885
+ jsonlPath: discovered.jsonlPath,
29886
+ error: e
29887
+ });
29888
+ return {
29889
+ sessionId: discovered.sessionId,
29890
+ jsonlPath: discovered.jsonlPath,
29891
+ checkedPaths: [],
29892
+ recovered: true
29893
+ };
29894
+ }
29895
+ }
29896
+ async function discoverMissingScopeEntries(opts) {
29897
+ const entries = [];
29898
+ if (!opts.existingScopeKeys.has("single")) {
29899
+ const cwdCandidates = uniquePaths([
29900
+ ...cwdCandidatesForBridge(opts.agentWorkdir, {
29901
+ workspacesDir: opts.workspacesDir,
29902
+ fallbackSegment: opts.agentId,
29903
+ workdirOverrideStore: opts.workdirOverrideStore
29904
+ }),
29905
+ opts.localWorkdir
29906
+ ]);
29907
+ const discovered = await discoverLatestJsonlForScope({
29908
+ agentId: opts.agentId,
29909
+ cwdCandidates,
29910
+ projectsDirs: opts.projectsDirs,
29911
+ allowAgentWideScan: true
29912
+ });
29913
+ if (discovered) {
29914
+ opts.sessionStore.set(opts.agentId, { kind: "single" }, discovered.sessionId);
29915
+ opts.existingScopeKeys.add("single");
29916
+ entries.push({ scopeKey: "single", sessionId: discovered.sessionId });
29917
+ logger17.info("Missing single session recovered from Claude projects", {
29918
+ agentId: opts.agentId,
29919
+ sessionId: discovered.sessionId,
29920
+ jsonlPath: discovered.jsonlPath
29921
+ });
29922
+ }
29923
+ }
29924
+ for (const group of opts.groupRegistry?.getMyGroups(opts.agentId) ?? []) {
29925
+ const scopeKey2 = `group:${group.groupId}`;
29926
+ if (opts.existingScopeKeys.has(scopeKey2)) continue;
29927
+ const cwdCandidates = cwdCandidatesForBridge(group.workingDirectory, {
29928
+ workspacesDir: opts.workspacesDir,
29929
+ fallbackSegment: `Group-${group.groupId}`,
29930
+ workdirOverrideStore: opts.workdirOverrideStore
29931
+ });
29932
+ const discovered = await discoverLatestJsonlForScope({
29933
+ agentId: opts.agentId,
29934
+ cwdCandidates,
29935
+ projectsDirs: opts.projectsDirs,
29936
+ allowAgentWideScan: false
29937
+ });
29938
+ if (!discovered) continue;
29939
+ opts.sessionStore.set(opts.agentId, { kind: "group", groupId: group.groupId }, discovered.sessionId);
29940
+ opts.existingScopeKeys.add(scopeKey2);
29941
+ entries.push({ scopeKey: scopeKey2, sessionId: discovered.sessionId });
29942
+ logger17.info("Missing group session recovered from Claude projects", {
29943
+ agentId: opts.agentId,
29944
+ groupId: group.groupId,
29945
+ sessionId: discovered.sessionId,
29946
+ jsonlPath: discovered.jsonlPath
29947
+ });
29948
+ }
29949
+ return entries;
29950
+ }
28678
29951
  async function dumpAgentContext(agentId, deps) {
28679
29952
  const agent = deps.agentRegistry.getById(agentId);
28680
29953
  if (!agent) {
@@ -28687,14 +29960,33 @@ async function dumpAgentContext(agentId, deps) {
28687
29960
  if (!workdir) {
28688
29961
  return { ok: false, files: [], scopeErrors: [], error: "agent has no working directory" };
28689
29962
  }
28690
- const dumpDir = path12.join(workdir, "sessioninfo");
29963
+ const localWorkdir = await resolveDumpWorkdir(workdir, {
29964
+ workspacesDir: deps.workspacesDir,
29965
+ fallbackSegment: agentId,
29966
+ agentId,
29967
+ workdirOverrideStore: deps.workdirOverrideStore
29968
+ });
29969
+ const dumpDir = path12.join(localWorkdir, "sessioninfo");
28691
29970
  await fs6.mkdir(dumpDir, { recursive: true });
29971
+ const projectsDirs = claudeProjectsDirs({ agentId, agentConfigDir: deps.agentConfigDir });
28692
29972
  const prefix = `${agentId}::`;
28693
29973
  const scopeEntries = [];
28694
29974
  for (const [key, sessionId] of deps.sessionStore.getAll()) {
28695
29975
  if (!key.startsWith(prefix)) continue;
28696
29976
  scopeEntries.push({ scopeKey: key.slice(prefix.length), sessionId });
28697
29977
  }
29978
+ const existingScopeKeys = new Set(scopeEntries.map((entry) => entry.scopeKey));
29979
+ scopeEntries.push(...await discoverMissingScopeEntries({
29980
+ agentId,
29981
+ agentWorkdir: workdir,
29982
+ localWorkdir,
29983
+ groupRegistry: deps.groupRegistry,
29984
+ workspacesDir: deps.workspacesDir,
29985
+ workdirOverrideStore: deps.workdirOverrideStore,
29986
+ projectsDirs,
29987
+ existingScopeKeys,
29988
+ sessionStore: deps.sessionStore
29989
+ }));
28698
29990
  logger17.info("Agent context dump started", {
28699
29991
  agentId,
28700
29992
  agentName: agent.name,
@@ -28719,26 +30011,61 @@ async function dumpAgentContext(agentId, deps) {
28719
30011
  error: infoErr
28720
30012
  });
28721
30013
  }
28722
- const scopeCwd = sessionInfo?.cwd || resolveScopeCwd(workdir, scopeKey2, deps.groupRegistry);
28723
- const jsonlPath = resolveJsonlPath(sessionId, scopeCwd);
28724
- let entries;
28725
- try {
28726
- entries = await readJsonlEntries(jsonlPath);
28727
- } catch (readErr) {
28728
- logger17.warn("JSONL read failed, trying alternative cwd", {
30014
+ const registryScopeCwd = resolveScopeCwd(workdir, scopeKey2, deps.groupRegistry);
30015
+ const fallbackSegment = scopeKey2.startsWith("group:") ? `Group-${scopeKey2.slice("group:".length)}` : agentId;
30016
+ const cwdCandidates = uniquePaths([
30017
+ ...sessionInfo?.cwd ? cwdCandidatesForBridge(sessionInfo.cwd, {
30018
+ workspacesDir: deps.workspacesDir,
30019
+ fallbackSegment,
30020
+ workdirOverrideStore: deps.workdirOverrideStore
30021
+ }) : [],
30022
+ ...cwdCandidatesForBridge(registryScopeCwd, {
30023
+ workspacesDir: deps.workspacesDir,
30024
+ fallbackSegment,
30025
+ workdirOverrideStore: deps.workdirOverrideStore
30026
+ }),
30027
+ localWorkdir
30028
+ ]);
30029
+ let resolvedSessionId = sessionId;
30030
+ const { jsonlPath, checkedPaths, recovered } = await resolveDumpJsonlSource({
30031
+ agentId,
30032
+ scopeKey: scopeKey2,
30033
+ sessionId,
30034
+ cwdCandidates,
30035
+ projectsDirs,
30036
+ sessionStore: deps.sessionStore
30037
+ });
30038
+ resolvedSessionId = recovered ? path12.basename(jsonlPath, ".jsonl") : resolvedSessionId;
30039
+ if (recovered) {
30040
+ try {
30041
+ const info = await sdk3.getSessionInfo(resolvedSessionId);
30042
+ if (info) sessionInfo = info;
30043
+ } catch (infoErr) {
30044
+ logger17.warn("getSessionInfo failed for recovered scope", {
30045
+ agentId,
30046
+ scopeKey: scopeKey2,
30047
+ sessionId: resolvedSessionId,
30048
+ error: infoErr
30049
+ });
30050
+ }
30051
+ }
30052
+ const entries = await readJsonlEntries(jsonlPath);
30053
+ if (recovered) {
30054
+ logger17.info("Session JSONL recovered from local Claude project directory", {
28729
30055
  agentId,
28730
30056
  scopeKey: scopeKey2,
28731
- sessionId,
30057
+ previousSessionId: sessionId,
30058
+ sessionId: resolvedSessionId,
30059
+ jsonlPath
30060
+ });
30061
+ } else if (!checkedPaths.includes(jsonlPath)) {
30062
+ logger17.info("Session JSONL resolved by sessionId scan", {
30063
+ agentId,
30064
+ scopeKey: scopeKey2,
30065
+ sessionId: resolvedSessionId,
28732
30066
  jsonlPath,
28733
- error: readErr instanceof Error ? readErr.message : String(readErr)
30067
+ checkedPathCount: checkedPaths.length
28734
30068
  });
28735
- const altCwd = resolveScopeCwd(workdir, scopeKey2, deps.groupRegistry);
28736
- if (altCwd !== scopeCwd) {
28737
- const altPath = resolveJsonlPath(sessionId, altCwd);
28738
- entries = await readJsonlEntries(altPath);
28739
- } else {
28740
- throw readErr;
28741
- }
28742
30069
  }
28743
30070
  let groupName;
28744
30071
  if (scopeKey2.startsWith("group:")) {
@@ -28749,7 +30076,7 @@ async function dumpAgentContext(agentId, deps) {
28749
30076
  agentName: agent.name,
28750
30077
  agentId,
28751
30078
  scopeKey: scopeKey2,
28752
- sessionId,
30079
+ sessionId: resolvedSessionId,
28753
30080
  systemPrompt: agent.systemPrompt,
28754
30081
  sessionInfo,
28755
30082
  entries,
@@ -28764,7 +30091,7 @@ async function dumpAgentContext(agentId, deps) {
28764
30091
  logger17.info("Scope context dumped", {
28765
30092
  agentId,
28766
30093
  scopeKey: scopeKey2,
28767
- sessionId,
30094
+ sessionId: resolvedSessionId,
28768
30095
  filename,
28769
30096
  filePath,
28770
30097
  bytesWritten: stat3.size,
@@ -28790,6 +30117,7 @@ async function dumpAgentContext(agentId, deps) {
28790
30117
  return {
28791
30118
  ok,
28792
30119
  dir: dumpDir,
30120
+ localDir: dumpDir,
28793
30121
  files: dumpedFiles,
28794
30122
  scopeErrors,
28795
30123
  error: ok ? void 0 : "no files written"
@@ -28894,7 +30222,6 @@ import path14 from "path";
28894
30222
  import os8 from "os";
28895
30223
  import readline from "readline";
28896
30224
  var logger19 = createModuleLogger("bridge.logScanner");
28897
- var DEFAULT_LIMIT = 500;
28898
30225
  var MAX_LIMIT = 2e3;
28899
30226
  function listLogFiles(logsDir, baseName) {
28900
30227
  let names;
@@ -28904,10 +30231,11 @@ function listLogFiles(logsDir, baseName) {
28904
30231
  logger19.warn("listLogFiles: readdir failed", { logsDir, error: e });
28905
30232
  return [];
28906
30233
  }
28907
- const pattern = new RegExp(`^${baseName.replace(".", "\\.")}(\\.\\d+)?$`);
30234
+ const escapedBaseName = baseName.replace(".", "\\.");
30235
+ const pattern = new RegExp(`^${escapedBaseName}(?:[.-].+)?$`);
28908
30236
  return names.filter((n) => pattern.test(n)).map((n) => path14.join(logsDir, n));
28909
30237
  }
28910
- async function scanFile(filePath, source, filter, limit, state) {
30238
+ async function scanFile(filePath, source, filter, state) {
28911
30239
  const file2 = path14.basename(filePath);
28912
30240
  const stream = fs8.createReadStream(filePath, { encoding: "utf-8" });
28913
30241
  const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
@@ -28919,35 +30247,40 @@ async function scanFile(filePath, source, filter, limit, state) {
28919
30247
  if (!parsed) continue;
28920
30248
  if (parsed.source !== source) continue;
28921
30249
  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
- }
30250
+ state.hits.push({ ...parsed, raw: line, file: file2, lineNum });
28927
30251
  }
28928
30252
  }
28929
30253
  async function scanLocalLogs(logsDir, baseName, filter) {
28930
- const source = filter.source;
28931
- const limit = Math.min(Math.max(filter.limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);
30254
+ const source = "bridge";
30255
+ const limit = filter.limit == null ? null : Math.min(Math.max(filter.limit, 1), MAX_LIMIT);
30256
+ const offset = Math.max(filter.offset ?? 0, 0);
28932
30257
  const files = listLogFiles(logsDir, baseName);
28933
30258
  const state = { hits: [], totalScanned: 0, truncated: false };
28934
30259
  for (const filePath of files) {
28935
- if (state.truncated) break;
28936
30260
  try {
28937
- await scanFile(filePath, source, filter, limit, state);
30261
+ await scanFile(filePath, source, filter, state);
28938
30262
  } catch (e) {
28939
30263
  logger19.warn("scanLocalLogs: file read failed", { filePath, error: e });
28940
30264
  }
28941
30265
  }
28942
- state.hits.sort((a, b) => a.ts.localeCompare(b.ts));
30266
+ state.hits.sort((a, b) => b.ts.localeCompare(a.ts));
30267
+ const totalMatched = state.hits.length;
30268
+ const endOffset = limit === null ? totalMatched : offset + limit;
30269
+ const entries = state.hits.slice(offset, endOffset);
30270
+ const nextOffset = endOffset < totalMatched ? endOffset : void 0;
30271
+ if (nextOffset !== void 0) {
30272
+ state.truncated = true;
30273
+ }
28943
30274
  return {
28944
- entries: state.hits,
30275
+ entries,
28945
30276
  truncated: state.truncated,
28946
- totalScanned: state.totalScanned
30277
+ totalScanned: state.totalScanned,
30278
+ totalMatched,
30279
+ nextOffset
28947
30280
  };
28948
30281
  }
28949
30282
  async function scanBridgeLogs(filter) {
28950
- const logDir = path14.join(os8.homedir(), ".ahchat", "logs");
30283
+ const logDir = path14.join(loadBridgeConfig().dataDir || path14.join(os8.homedir(), ".ahchat"), "logs");
28951
30284
  logger19.info("scanBridgeLogs start", {
28952
30285
  logDir,
28953
30286
  startIso: filter.startIso,
@@ -28957,36 +30290,295 @@ async function scanBridgeLogs(filter) {
28957
30290
  const result = await scanLocalLogs(logDir, "bridge.log", { ...filter, source: "bridge" });
28958
30291
  logger19.info("scanBridgeLogs complete", {
28959
30292
  hitCount: result.entries.length,
30293
+ totalMatched: result.totalMatched,
30294
+ nextOffset: result.nextOffset,
28960
30295
  totalScanned: result.totalScanned,
28961
30296
  truncated: result.truncated
28962
30297
  });
28963
30298
  return result;
28964
30299
  }
28965
30300
 
28966
- // src/skillStore.ts
30301
+ // src/logUploader.ts
28967
30302
  import fs9 from "fs";
30303
+ import fsp from "fs/promises";
28968
30304
  import path15 from "path";
28969
- var logger20 = createModuleLogger("bridge.skillStore");
30305
+ var logger20 = createModuleLogger("bridge.logUploader");
30306
+ var DEFAULT_LOG_UPLOAD_INTERVAL_MS = 24 * 60 * 60 * 1e3;
30307
+ var DEFAULT_BATCH_SIZE = 200;
30308
+ function defaultLogFile(dataDir) {
30309
+ return path15.join(dataDir, "logs", "bridge.log");
30310
+ }
30311
+ function defaultCursorFile(dataDir) {
30312
+ return path15.join(dataDir, "log-upload-cursor.json");
30313
+ }
30314
+ function uploadedFileName(logFile, explicit) {
30315
+ const trimmed = explicit?.trim();
30316
+ return trimmed || path15.basename(logFile);
30317
+ }
30318
+ function normalizeTarget(file2, dataDir) {
30319
+ const fileName = uploadedFileName(file2.logFile, file2.uploadedFileName);
30320
+ const safeName = fileName.replace(/[^a-zA-Z0-9._-]/g, "_");
30321
+ return {
30322
+ logFile: file2.logFile,
30323
+ cursorFile: file2.cursorFile ?? path15.join(dataDir, `log-upload-cursor-${safeName}.json`),
30324
+ uploadedFileName: fileName
30325
+ };
30326
+ }
30327
+ function cursorFileForLog(dataDir, fileName) {
30328
+ const safeName = fileName.replace(/[^a-zA-Z0-9._-]/g, "_");
30329
+ return path15.join(dataDir, `log-upload-cursor-${safeName}.json`);
30330
+ }
30331
+ function logFileFingerprint(stat3) {
30332
+ if (typeof stat3.dev === "number" && typeof stat3.ino === "number" && stat3.ino > 0) {
30333
+ return `${stat3.dev}:${stat3.ino}`;
30334
+ }
30335
+ return void 0;
30336
+ }
30337
+ async function listRotatedBridgeTargets(primary, dataDir) {
30338
+ const logDir = path15.dirname(primary.logFile);
30339
+ const baseName = path15.basename(primary.logFile);
30340
+ let names;
30341
+ try {
30342
+ names = await fsp.readdir(logDir);
30343
+ } catch (e) {
30344
+ if (e instanceof Error && "code" in e && e.code === "ENOENT") return [];
30345
+ throw e;
30346
+ }
30347
+ const escapedBaseName = baseName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
30348
+ const pattern = new RegExp(`^${escapedBaseName}(?:[.-].+)?$`);
30349
+ return names.filter((name) => pattern.test(name)).sort().map((name) => {
30350
+ if (name === baseName) return primary;
30351
+ return {
30352
+ logFile: path15.join(logDir, name),
30353
+ cursorFile: cursorFileForLog(dataDir, name),
30354
+ uploadedFileName: name
30355
+ };
30356
+ });
30357
+ }
30358
+ function safeCursor(raw) {
30359
+ if (typeof raw !== "object" || raw === null) return { offset: 0, lineNum: 0 };
30360
+ const obj = raw;
30361
+ return {
30362
+ offset: typeof obj.offset === "number" && obj.offset > 0 ? Math.floor(obj.offset) : 0,
30363
+ lineNum: typeof obj.lineNum === "number" && obj.lineNum > 0 ? Math.floor(obj.lineNum) : 0,
30364
+ ...typeof obj.fingerprint === "string" && obj.fingerprint.length > 0 ? { fingerprint: obj.fingerprint } : {}
30365
+ };
30366
+ }
30367
+ async function readCursor(filePath) {
30368
+ try {
30369
+ const raw = await fsp.readFile(filePath, "utf8");
30370
+ return safeCursor(JSON.parse(raw));
30371
+ } catch (e) {
30372
+ if (e instanceof Error && "code" in e && e.code === "ENOENT") {
30373
+ return { offset: 0, lineNum: 0 };
30374
+ }
30375
+ logger20.warn("Failed to read log upload cursor, restarting from beginning", { error: e });
30376
+ return { offset: 0, lineNum: 0 };
30377
+ }
30378
+ }
30379
+ async function writeCursor(filePath, cursor) {
30380
+ await fsp.mkdir(path15.dirname(filePath), { recursive: true });
30381
+ await fsp.writeFile(filePath, JSON.stringify(cursor), "utf8");
30382
+ }
30383
+ async function readStreamText(filePath, start) {
30384
+ const stream = fs9.createReadStream(filePath, { start, encoding: "utf8" });
30385
+ let raw = "";
30386
+ for await (const chunk of stream) {
30387
+ raw += chunk;
30388
+ }
30389
+ return raw;
30390
+ }
30391
+ function parseProcessedLines(raw, cursor, fileName, fingerprint) {
30392
+ const lastNewline = raw.lastIndexOf("\n");
30393
+ if (lastNewline < 0) {
30394
+ return { entries: [], nextCursor: cursor, advanced: false };
30395
+ }
30396
+ const processed = raw.slice(0, lastNewline + 1);
30397
+ const lines = processed.split(/\r?\n/);
30398
+ const entries = [];
30399
+ let lineNum = cursor.lineNum;
30400
+ for (let i = 0; i < lines.length - 1; i += 1) {
30401
+ const line = lines[i] ?? "";
30402
+ lineNum += 1;
30403
+ if (line.length === 0) continue;
30404
+ const parsed = parseLogLine(line);
30405
+ if (!parsed) continue;
30406
+ entries.push({
30407
+ ...parsed,
30408
+ raw: line,
30409
+ file: fileName,
30410
+ lineNum
30411
+ });
30412
+ }
30413
+ return {
30414
+ entries,
30415
+ nextCursor: {
30416
+ offset: cursor.offset + Buffer.byteLength(processed, "utf8"),
30417
+ lineNum,
30418
+ ...fingerprint ? { fingerprint } : {}
30419
+ },
30420
+ advanced: true
30421
+ };
30422
+ }
30423
+ function chunkEntries(entries, size) {
30424
+ const chunks = [];
30425
+ for (let i = 0; i < entries.length; i += size) {
30426
+ chunks.push(entries.slice(i, i + size));
30427
+ }
30428
+ return chunks;
30429
+ }
30430
+ var BridgeLogUploader = class {
30431
+ options;
30432
+ timer = null;
30433
+ running = false;
30434
+ stopped = false;
30435
+ constructor(options) {
30436
+ const primaryTarget = {
30437
+ logFile: options.logFile ?? defaultLogFile(options.dataDir),
30438
+ cursorFile: options.cursorFile ?? defaultCursorFile(options.dataDir),
30439
+ uploadedFileName: uploadedFileName(options.logFile ?? defaultLogFile(options.dataDir), "bridge.log")
30440
+ };
30441
+ this.options = {
30442
+ dataDir: options.dataDir,
30443
+ serverApiUrl: options.serverApiUrl.replace(/\/+$/, ""),
30444
+ bridgeToken: options.bridgeToken,
30445
+ bridgeId: options.bridgeId,
30446
+ hostname: options.hostname,
30447
+ intervalMs: options.intervalMs ?? DEFAULT_LOG_UPLOAD_INTERVAL_MS,
30448
+ batchSize: options.batchSize ?? DEFAULT_BATCH_SIZE,
30449
+ primaryTarget,
30450
+ extraTargets: (options.extraLogFiles ?? []).map((file2) => normalizeTarget(file2, options.dataDir)),
30451
+ includeRotatedBridgeLogs: options.includeRotatedBridgeLogs ?? true
30452
+ };
30453
+ }
30454
+ start() {
30455
+ if (this.timer) return;
30456
+ this.stopped = false;
30457
+ this.timer = setInterval(() => {
30458
+ void this.flushOnce();
30459
+ }, this.options.intervalMs);
30460
+ void this.flushOnce();
30461
+ logger20.info("Bridge log uploader started", {
30462
+ logFile: this.options.primaryTarget.logFile,
30463
+ includeRotatedBridgeLogs: this.options.includeRotatedBridgeLogs,
30464
+ intervalMs: this.options.intervalMs,
30465
+ batchSize: this.options.batchSize
30466
+ });
30467
+ }
30468
+ stop() {
30469
+ this.stopped = true;
30470
+ if (!this.timer) return;
30471
+ clearInterval(this.timer);
30472
+ this.timer = null;
30473
+ logger20.info("Bridge log uploader stopped");
30474
+ }
30475
+ async flushOnce() {
30476
+ if (this.running || this.stopped) return;
30477
+ this.running = true;
30478
+ try {
30479
+ const targets = await this.resolveTargets();
30480
+ for (const target of targets) {
30481
+ try {
30482
+ const cursor = await readCursor(target.cursorFile);
30483
+ const batch = await this.readNewEntries(target, cursor);
30484
+ if (!batch.advanced) continue;
30485
+ if (batch.entries.length > 0) {
30486
+ await this.uploadEntries(batch.entries);
30487
+ }
30488
+ await writeCursor(target.cursorFile, batch.nextCursor);
30489
+ } catch (e) {
30490
+ logger20.warn("Bridge log upload target failed", { error: e, logFile: target.logFile });
30491
+ }
30492
+ }
30493
+ } catch (e) {
30494
+ logger20.warn("Bridge log upload cycle failed", { error: e });
30495
+ } finally {
30496
+ this.running = false;
30497
+ }
30498
+ }
30499
+ async resolveTargets() {
30500
+ const bridgeTargets = this.options.includeRotatedBridgeLogs ? await listRotatedBridgeTargets(this.options.primaryTarget, this.options.dataDir) : [this.options.primaryTarget];
30501
+ const seen = /* @__PURE__ */ new Set();
30502
+ const targets = [];
30503
+ for (const target of [...bridgeTargets, ...this.options.extraTargets]) {
30504
+ const key = `${target.logFile}\0${target.cursorFile}`;
30505
+ if (seen.has(key)) continue;
30506
+ seen.add(key);
30507
+ targets.push(target);
30508
+ }
30509
+ return targets;
30510
+ }
30511
+ async readNewEntries(target, cursor) {
30512
+ let stat3;
30513
+ try {
30514
+ stat3 = await fsp.stat(target.logFile);
30515
+ } catch (e) {
30516
+ if (e instanceof Error && "code" in e && e.code === "ENOENT") {
30517
+ logger20.debug("Bridge log file not found for upload yet", { logFile: target.logFile });
30518
+ return { entries: [], nextCursor: cursor, advanced: false };
30519
+ }
30520
+ throw e;
30521
+ }
30522
+ const fingerprint = logFileFingerprint(stat3);
30523
+ const samePhysicalFile = !fingerprint || !cursor.fingerprint || cursor.fingerprint === fingerprint;
30524
+ const normalizedCursor = stat3.size < cursor.offset || !samePhysicalFile ? { offset: 0, lineNum: 0, ...fingerprint ? { fingerprint } : {} } : { ...cursor, ...fingerprint ? { fingerprint } : {} };
30525
+ if (stat3.size <= normalizedCursor.offset) {
30526
+ return { entries: [], nextCursor: normalizedCursor, advanced: false };
30527
+ }
30528
+ const raw = await readStreamText(target.logFile, normalizedCursor.offset);
30529
+ return parseProcessedLines(raw, normalizedCursor, target.uploadedFileName, fingerprint);
30530
+ }
30531
+ async uploadEntries(entries) {
30532
+ const bridgeEntries = entries.filter((entry) => entry.source === "bridge");
30533
+ for (const chunk of chunkEntries(bridgeEntries, this.options.batchSize)) {
30534
+ const res = await fetch(`${this.options.serverApiUrl}/api/logs/upload`, {
30535
+ method: "POST",
30536
+ headers: {
30537
+ "Content-Type": "application/json",
30538
+ "X-AHChat-Bridge-Token": this.options.bridgeToken
30539
+ },
30540
+ body: JSON.stringify({
30541
+ bridgeId: this.options.bridgeId,
30542
+ hostname: this.options.hostname,
30543
+ entries: chunk
30544
+ })
30545
+ });
30546
+ if (!res.ok) {
30547
+ const body = await res.text().catch((e) => {
30548
+ logger20.debug("Failed to read log upload error body", { error: e });
30549
+ return "";
30550
+ });
30551
+ throw new Error(`upload failed HTTP ${res.status}: ${body.slice(0, 160)}`);
30552
+ }
30553
+ await res.json();
30554
+ }
30555
+ }
30556
+ };
30557
+
30558
+ // src/skillStore.ts
30559
+ import fs10 from "fs";
30560
+ import path16 from "path";
30561
+ var logger21 = createModuleLogger("bridge.skillStore");
28970
30562
  var ALLOWED_NAMES = /* @__PURE__ */ new Set(["log-analysis"]);
28971
30563
  var SkillStore = class {
28972
30564
  skillsDir;
28973
30565
  constructor(dataDir) {
28974
- this.skillsDir = path15.join(dataDir, "skills");
28975
- fs9.mkdirSync(this.skillsDir, { recursive: true });
28976
- logger20.info("SkillStore initialized", { skillsDir: this.skillsDir });
30566
+ this.skillsDir = path16.join(dataDir, "skills");
30567
+ fs10.mkdirSync(this.skillsDir, { recursive: true });
30568
+ logger21.info("SkillStore initialized", { skillsDir: this.skillsDir });
28977
30569
  }
28978
30570
  read(name) {
28979
30571
  if (!ALLOWED_NAMES.has(name)) {
28980
- logger20.warn("Skill read: unknown name", { name, allowed: [...ALLOWED_NAMES] });
30572
+ logger21.warn("Skill read: unknown name", { name, allowed: [...ALLOWED_NAMES] });
28981
30573
  return "";
28982
30574
  }
28983
- const filePath = path15.join(this.skillsDir, `${name}.md`);
30575
+ const filePath = path16.join(this.skillsDir, `${name}.md`);
28984
30576
  try {
28985
- const content = fs9.readFileSync(filePath, "utf-8");
28986
- logger20.info("Skill read", { name, bytes: content.length });
30577
+ const content = fs10.readFileSync(filePath, "utf-8");
30578
+ logger21.info("Skill read", { name, bytes: content.length });
28987
30579
  return content;
28988
30580
  } catch (e) {
28989
- logger20.warn("Skill read failed", { name, filePath, error: e });
30581
+ logger21.warn("Skill read failed", { name, filePath, error: e });
28990
30582
  return "";
28991
30583
  }
28992
30584
  }
@@ -28995,20 +30587,20 @@ var SkillStore = class {
28995
30587
  if (!ALLOWED_NAMES.has(name)) {
28996
30588
  throw new Error(`Unknown skill name: ${name}`);
28997
30589
  }
28998
- const filePath = path15.join(this.skillsDir, `${name}.md`);
30590
+ const filePath = path16.join(this.skillsDir, `${name}.md`);
28999
30591
  const tmpPath = `${filePath}.tmp`;
29000
30592
  let existing = "";
29001
30593
  try {
29002
- existing = fs9.readFileSync(filePath, "utf-8");
30594
+ existing = fs10.readFileSync(filePath, "utf-8");
29003
30595
  } catch {
29004
30596
  }
29005
30597
  if (existing === content) {
29006
- logger20.info("Skill already in sync", { name, bytes: content.length });
30598
+ logger21.info("Skill already in sync", { name, bytes: content.length });
29007
30599
  return;
29008
30600
  }
29009
- fs9.writeFileSync(tmpPath, content, "utf-8");
29010
- fs9.renameSync(tmpPath, filePath);
29011
- logger20.info("Skill seeded/re-synced", {
30601
+ fs10.writeFileSync(tmpPath, content, "utf-8");
30602
+ fs10.renameSync(tmpPath, filePath);
30603
+ logger21.info("Skill seeded/re-synced", {
29012
30604
  name,
29013
30605
  bytes: content.length,
29014
30606
  hadExisting: existing.length > 0
@@ -29017,11 +30609,29 @@ var SkillStore = class {
29017
30609
  };
29018
30610
 
29019
30611
  // src/lockfile.ts
29020
- import fs10 from "fs";
29021
- import path16 from "path";
29022
- var logger21 = createModuleLogger("bridge.lockfile");
30612
+ import * as childProcess from "child_process";
30613
+ import fs11 from "fs";
30614
+ import path17 from "path";
30615
+ var logger22 = createModuleLogger("bridge.lockfile");
29023
30616
  var lockPath = null;
29024
30617
  var releaseRegistered = false;
30618
+ var BridgeAlreadyRunningError = class _BridgeAlreadyRunningError extends Error {
30619
+ pid;
30620
+ dataDir;
30621
+ constructor(pid, dataDir) {
30622
+ super(`Bridge already running (PID: ${pid})`);
30623
+ this.name = "BridgeAlreadyRunningError";
30624
+ this.pid = pid;
30625
+ this.dataDir = dataDir;
30626
+ Object.setPrototypeOf(this, _BridgeAlreadyRunningError.prototype);
30627
+ }
30628
+ };
30629
+ function isBridgeAlreadyRunningError(e) {
30630
+ if (e instanceof BridgeAlreadyRunningError) return true;
30631
+ if (!e || typeof e !== "object") return false;
30632
+ const candidate = e;
30633
+ return candidate.name === "BridgeAlreadyRunningError" && typeof candidate.pid === "number" && typeof candidate.dataDir === "string";
30634
+ }
29025
30635
  function isProcessAlive(pid) {
29026
30636
  try {
29027
30637
  process.kill(pid, 0);
@@ -29029,25 +30639,95 @@ function isProcessAlive(pid) {
29029
30639
  } catch (e) {
29030
30640
  const err = e;
29031
30641
  if (err.code === "ESRCH") return false;
30642
+ if (err.code === "EPERM") {
30643
+ logger22.warn("Treating inaccessible lock PID as stale", { pid, error: e });
30644
+ return false;
30645
+ }
29032
30646
  throw e;
29033
30647
  }
29034
30648
  }
30649
+ function readWindowsProcessCommand(pid) {
30650
+ const commands = [
30651
+ {
30652
+ file: "wmic",
30653
+ args: ["process", "where", `processid=${pid}`, "get", "Name,CommandLine", "/FORMAT:LIST"]
30654
+ },
30655
+ {
30656
+ file: "powershell.exe",
30657
+ args: [
30658
+ "-NoProfile",
30659
+ "-NonInteractive",
30660
+ "-ExecutionPolicy",
30661
+ "Bypass",
30662
+ "-Command",
30663
+ '$p = Get-CimInstance Win32_Process -Filter "ProcessId = ' + pid + '"; if ($null -ne $p) { "$($p.Name)`n$($p.CommandLine)" }'
30664
+ ]
30665
+ }
30666
+ ];
30667
+ for (const command of commands) {
30668
+ try {
30669
+ const output = childProcess.execFileSync(command.file, command.args, {
30670
+ encoding: "utf-8",
30671
+ windowsHide: true,
30672
+ timeout: 1e3
30673
+ });
30674
+ const normalizedOutput = output.replace(/\0/g, "");
30675
+ if (normalizedOutput.trim()) return normalizedOutput;
30676
+ } catch {
30677
+ continue;
30678
+ }
30679
+ }
30680
+ return null;
30681
+ }
30682
+ function readProcessCommand(pid) {
30683
+ if (pid === process.pid) return [process.execPath, ...process.argv].join(" ");
30684
+ try {
30685
+ if (process.platform === "win32") {
30686
+ return readWindowsProcessCommand(pid);
30687
+ }
30688
+ const procCmdline = `/proc/${pid}/cmdline`;
30689
+ if (fs11.existsSync(procCmdline)) {
30690
+ return fs11.readFileSync(procCmdline, "utf-8").replace(/\0/g, " ");
30691
+ }
30692
+ return childProcess.execFileSync("ps", ["-p", String(pid), "-o", "comm=", "-o", "args="], {
30693
+ encoding: "utf-8",
30694
+ timeout: 3e3
30695
+ });
30696
+ } catch (e) {
30697
+ logger22.warn("Failed to inspect lock PID command line", { pid, error: e });
30698
+ return null;
30699
+ }
30700
+ }
30701
+ function isBridgeProcessCommand(command) {
30702
+ if (!command) return false;
30703
+ const normalized = command.toLowerCase();
30704
+ return normalized.includes("ahchat-bridge") || normalized.includes("bridge.cjs") || normalized.includes("dist/cli.cjs") || normalized.includes("dist\\cli.cjs") || normalized.includes("--type=utility") && normalized.includes("--utility-sub-type=node.mojom.nodeservice");
30705
+ }
30706
+ function isLiveBridgeLockOwner(pid) {
30707
+ if (!isProcessAlive(pid)) return false;
30708
+ if (pid === process.pid) return true;
30709
+ const command = readProcessCommand(pid);
30710
+ if (isBridgeProcessCommand(command)) return true;
30711
+ const processName = command?.split(/\r?\n/)[0]?.trim() ?? null;
30712
+ logger22.warn("Treating bridge.lock PID as stale because it is not a Bridge process", { pid, processName });
30713
+ return false;
30714
+ }
29035
30715
  function acquireLock(dataDir) {
29036
- const file2 = path16.join(dataDir, "bridge.lock");
30716
+ const file2 = path17.join(dataDir, "bridge.lock");
29037
30717
  lockPath = file2;
29038
- if (fs10.existsSync(file2)) {
29039
- const raw = fs10.readFileSync(file2, "utf-8").trim();
30718
+ if (fs11.existsSync(file2)) {
30719
+ const raw = fs11.readFileSync(file2, "utf-8").trim();
29040
30720
  const pid = Number.parseInt(raw, 10);
29041
30721
  if (Number.isFinite(pid) && pid > 0) {
29042
- if (isProcessAlive(pid)) {
29043
- throw new Error(`Bridge already running (PID: ${pid})`);
30722
+ if (isLiveBridgeLockOwner(pid)) {
30723
+ throw new BridgeAlreadyRunningError(pid, dataDir);
29044
30724
  }
29045
- logger21.warn("Removing stale bridge.lock (process not found)", { pid, path: file2 });
30725
+ logger22.info("Removing stale bridge.lock", { pid, path: file2 });
29046
30726
  }
29047
30727
  }
29048
- fs10.mkdirSync(path16.dirname(file2), { recursive: true });
29049
- fs10.writeFileSync(file2, String(process.pid), "utf-8");
29050
- logger21.info("Acquired bridge lock", { path: file2, pid: process.pid });
30728
+ fs11.mkdirSync(path17.dirname(file2), { recursive: true });
30729
+ fs11.writeFileSync(file2, String(process.pid), "utf-8");
30730
+ logger22.info("Acquired bridge lock", { path: file2, pid: process.pid });
29051
30731
  if (!releaseRegistered) {
29052
30732
  releaseRegistered = true;
29053
30733
  process.on("exit", releaseLock);
@@ -29055,20 +30735,46 @@ function acquireLock(dataDir) {
29055
30735
  }
29056
30736
  function releaseLock() {
29057
30737
  try {
29058
- if (lockPath && fs10.existsSync(lockPath)) {
29059
- const current = fs10.readFileSync(lockPath, "utf-8").trim();
30738
+ if (lockPath && fs11.existsSync(lockPath)) {
30739
+ const current = fs11.readFileSync(lockPath, "utf-8").trim();
29060
30740
  if (current === String(process.pid)) {
29061
- fs10.unlinkSync(lockPath);
29062
- logger21.info("Released bridge lock", { path: lockPath });
30741
+ fs11.unlinkSync(lockPath);
30742
+ logger22.info("Released bridge lock", { path: lockPath });
29063
30743
  }
29064
30744
  }
29065
30745
  } catch (e) {
29066
- logger21.error("Failed to release bridge lock", { error: e, path: lockPath });
30746
+ logger22.error("Failed to release bridge lock", { error: e, path: lockPath });
29067
30747
  } finally {
29068
30748
  lockPath = null;
29069
30749
  }
29070
30750
  }
29071
30751
 
30752
+ // src/localWorkdirOverrideStore.ts
30753
+ import fs12 from "fs";
30754
+ import path18 from "path";
30755
+ var logger23 = createModuleLogger("bridge.localWorkdirOverride");
30756
+ var LocalWorkdirOverrideStore = class {
30757
+ filePath;
30758
+ constructor(dataDir) {
30759
+ this.filePath = path18.join(dataDir, LOCAL_WORKDIR_OVERRIDES_FILENAME);
30760
+ }
30761
+ read() {
30762
+ try {
30763
+ const raw = fs12.readFileSync(this.filePath, "utf8");
30764
+ return normalizeLocalWorkdirOverridesFile(JSON.parse(raw)).overrides;
30765
+ } catch (error51) {
30766
+ if (error51 instanceof Error && "code" in error51 && error51.code === "ENOENT") return [];
30767
+ logger23.warn("Failed to read local workdir overrides", { filePath: this.filePath, error: error51 });
30768
+ return [];
30769
+ }
30770
+ }
30771
+ resolvePath(requestedPath) {
30772
+ const resolved = resolveLocalWorkdirOverridePath(this.read(), requestedPath);
30773
+ if (!resolved) return { path: requestedPath, overridden: false };
30774
+ return { path: resolved.path, overridden: true };
30775
+ }
30776
+ };
30777
+
29072
30778
  // src/groupInbox.ts
29073
30779
  function groupInboxEntryFromDispatch(payload) {
29074
30780
  return {
@@ -29099,9 +30805,9 @@ function groupInboxEntryFromDispatch(payload) {
29099
30805
  }
29100
30806
 
29101
30807
  // src/messageHandler.ts
29102
- var logger22 = createModuleLogger("msg.handler");
30808
+ var logger24 = createModuleLogger("msg.handler");
29103
30809
  function emitTaskAck(emit, ackId, agentId, traceId) {
29104
- logger22.info("Emitting task:ack", { ackId, agentId, traceId });
30810
+ logger24.info("Emitting task:ack", { ackId, agentId, traceId });
29105
30811
  emit({
29106
30812
  type: "task:ack",
29107
30813
  payload: {
@@ -29114,7 +30820,7 @@ function emitTaskAck(emit, ackId, agentId, traceId) {
29114
30820
  }
29115
30821
  function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
29116
30822
  return async (payload) => {
29117
- logger22.info("Handling task:dispatch", {
30823
+ logger24.info("Handling task:dispatch", {
29118
30824
  agentId: payload.agentId,
29119
30825
  messageId: payload.messageId,
29120
30826
  ackId: payload.ackId,
@@ -29123,14 +30829,14 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
29123
30829
  emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
29124
30830
  let agentConfig = agentRegistry.getById(payload.agentId);
29125
30831
  if (!agentConfig) {
29126
- logger22.warn("Agent not in registry, attempting live fetch", {
30832
+ logger24.warn("Agent not in registry, attempting live fetch", {
29127
30833
  agentId: payload.agentId,
29128
30834
  traceId: payload.traceId
29129
30835
  });
29130
30836
  agentConfig = await agentRegistry.fetchById(payload.agentId);
29131
30837
  }
29132
30838
  if (!agentConfig) {
29133
- logger22.error("Agent not found for task:dispatch (after live fetch)", {
30839
+ logger24.error("Agent not found for task:dispatch (after live fetch)", {
29134
30840
  agentId: payload.agentId,
29135
30841
  traceId: payload.traceId
29136
30842
  });
@@ -29154,12 +30860,13 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
29154
30860
  conversationId: payload.conversationId,
29155
30861
  content: payload.content,
29156
30862
  attachments: payload.attachments ?? void 0,
30863
+ replyToMessage: payload.replyToMessage ?? void 0,
29157
30864
  replyMessageId: payload.ackId,
29158
30865
  traceId: payload.traceId,
29159
30866
  planMode: payload.planMode ?? void 0
29160
30867
  });
29161
30868
  } catch (err) {
29162
- logger22.error("Failed to dispatch message to Agent", {
30869
+ logger24.error("Failed to dispatch message to Agent", {
29163
30870
  error: err,
29164
30871
  agentId: payload.agentId,
29165
30872
  traceId: payload.traceId
@@ -29179,7 +30886,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
29179
30886
  }
29180
30887
  function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
29181
30888
  return async (payload) => {
29182
- logger22.info("Handling task:group_dispatch", {
30889
+ logger24.info("Handling task:group_dispatch", {
29183
30890
  agentId: payload.agentId,
29184
30891
  groupId: payload.groupId,
29185
30892
  ackId: payload.ackId,
@@ -29191,14 +30898,14 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
29191
30898
  emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
29192
30899
  let agentConfig = agentRegistry.getById(payload.agentId);
29193
30900
  if (!agentConfig) {
29194
- logger22.warn("Agent not in registry for group dispatch, attempting live fetch", {
30901
+ logger24.warn("Agent not in registry for group dispatch, attempting live fetch", {
29195
30902
  agentId: payload.agentId,
29196
30903
  traceId: payload.traceId
29197
30904
  });
29198
30905
  agentConfig = await agentRegistry.fetchById(payload.agentId);
29199
30906
  }
29200
30907
  if (!agentConfig) {
29201
- logger22.error("Agent not found for task:group_dispatch (after live fetch)", {
30908
+ logger24.error("Agent not found for task:group_dispatch (after live fetch)", {
29202
30909
  agentId: payload.agentId,
29203
30910
  traceId: payload.traceId
29204
30911
  });
@@ -29223,7 +30930,7 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
29223
30930
  );
29224
30931
  const entry = groupInboxEntryFromDispatch(payload);
29225
30932
  if (payload.isMentioned) {
29226
- logger22.info("Group dispatch routed to pull inbox", {
30933
+ logger24.info("Group dispatch routed to pull inbox", {
29227
30934
  route: "mention_flush",
29228
30935
  agentId: payload.agentId,
29229
30936
  groupId: payload.groupId,
@@ -29232,7 +30939,7 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
29232
30939
  });
29233
30940
  await agentManager.flushInboxAndDispatch(payload.agentId, groupScope, entry);
29234
30941
  } else {
29235
- logger22.info("Group dispatch routed to pull inbox", {
30942
+ logger24.info("Group dispatch routed to pull inbox", {
29236
30943
  route: "buffer",
29237
30944
  agentId: payload.agentId,
29238
30945
  groupId: payload.groupId,
@@ -29242,7 +30949,7 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
29242
30949
  agentManager.enqueueGroupMessage(payload.agentId, groupScope, entry);
29243
30950
  }
29244
30951
  } catch (err) {
29245
- logger22.error("Failed to dispatch group message to Agent", {
30952
+ logger24.error("Failed to dispatch group message to Agent", {
29246
30953
  error: err,
29247
30954
  agentId: payload.agentId,
29248
30955
  groupId: payload.groupId,
@@ -29263,7 +30970,7 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
29263
30970
  }
29264
30971
 
29265
30972
  // src/scopePushNotify.ts
29266
- var logger23 = createModuleLogger("bridge");
30973
+ var logger25 = createModuleLogger("bridge");
29267
30974
  function buildMemberChangedScopeNotice(params) {
29268
30975
  const { groupId, groupLabel, action, kickerName } = params;
29269
30976
  if (action === "removed" && kickerName) {
@@ -29312,14 +31019,14 @@ function buildGroupRenamedScopeNotice(params) {
29312
31019
  }
29313
31020
  async function handleGroupMemberChangedPush(deps, payload) {
29314
31021
  const { groupId, action, agentId, kickerAgentId } = payload;
29315
- logger23.info("group:member_changed received, refreshing GroupRegistry", {
31022
+ logger25.info("group:member_changed received, refreshing GroupRegistry", {
29316
31023
  groupId,
29317
31024
  action,
29318
31025
  agentId,
29319
31026
  kickerAgentId: kickerAgentId ?? null
29320
31027
  });
29321
31028
  await deps.groupRegistry.refresh();
29322
- logger23.info("GroupRegistry refreshed after member_changed", {
31029
+ logger25.info("GroupRegistry refreshed after member_changed", {
29323
31030
  groupId,
29324
31031
  action,
29325
31032
  registryGroupCount: deps.groupRegistry.getAll().length
@@ -29341,7 +31048,7 @@ async function handleGroupMemberChangedPush(deps, payload) {
29341
31048
  await deps.agentManager.broadcastScopeNotice(mid, notice2);
29342
31049
  notifiedCount++;
29343
31050
  }
29344
- logger23.info("Scope notice sent (human membership)", {
31051
+ logger25.info("Scope notice sent (human membership)", {
29345
31052
  groupId,
29346
31053
  groupLabel,
29347
31054
  action,
@@ -29353,7 +31060,7 @@ async function handleGroupMemberChangedPush(deps, payload) {
29353
31060
  }
29354
31061
  const notice = buildMemberChangedScopeNotice({ groupId, groupLabel, action, kickerName });
29355
31062
  await deps.agentManager.broadcastScopeNotice(agentId, notice);
29356
- logger23.info("Scope notice sent (agent membership)", {
31063
+ logger25.info("Scope notice sent (agent membership)", {
29357
31064
  groupId,
29358
31065
  groupLabel,
29359
31066
  action,
@@ -29365,13 +31072,13 @@ async function handleGroupMemberChangedPush(deps, payload) {
29365
31072
  }
29366
31073
  async function handleGroupUpdatedPush(deps, payload) {
29367
31074
  const { groupId, name: newName, memberAgentIds } = payload;
29368
- logger23.info("group:updated received, refreshing GroupRegistry", {
31075
+ logger25.info("group:updated received, refreshing GroupRegistry", {
29369
31076
  groupId,
29370
31077
  newName,
29371
31078
  memberCount: memberAgentIds.length
29372
31079
  });
29373
31080
  await deps.groupRegistry.refresh();
29374
- logger23.info("GroupRegistry refreshed after group:updated", {
31081
+ logger25.info("GroupRegistry refreshed after group:updated", {
29375
31082
  groupId,
29376
31083
  newName,
29377
31084
  registryGroupCount: deps.groupRegistry.getAll().length
@@ -29380,7 +31087,7 @@ async function handleGroupUpdatedPush(deps, payload) {
29380
31087
  for (const aid of memberAgentIds) {
29381
31088
  await deps.agentManager.broadcastScopeNotice(aid, notice);
29382
31089
  }
29383
- logger23.info("Scope notices sent for group:updated", {
31090
+ logger25.info("Scope notices sent for group:updated", {
29384
31091
  groupId,
29385
31092
  newName,
29386
31093
  memberCount: memberAgentIds.length,
@@ -29389,28 +31096,28 @@ async function handleGroupUpdatedPush(deps, payload) {
29389
31096
  }
29390
31097
  async function handleGroupArchivedPush(deps, payload) {
29391
31098
  const { groupId } = payload;
29392
- logger23.info("group:archived received, terminating group scope sessions", { groupId });
31099
+ logger25.info("group:archived received, terminating group scope sessions", { groupId });
29393
31100
  const groupBefore = deps.groupRegistry.getById(groupId);
29394
31101
  const memberIds = groupBefore?.members ?? [];
29395
31102
  await deps.groupRegistry.refresh();
29396
31103
  for (const memberId of memberIds) {
29397
31104
  await deps.agentManager.terminateScope(memberId, { kind: "group", groupId });
29398
31105
  }
29399
- logger23.info("group:archived: scopes terminated", {
31106
+ logger25.info("group:archived: scopes terminated", {
29400
31107
  groupId,
29401
31108
  terminatedCount: memberIds.length
29402
31109
  });
29403
31110
  }
29404
31111
 
29405
31112
  // src/sessionStore.ts
29406
- import fs11 from "fs";
29407
- import path17 from "path";
29408
- var logger24 = createModuleLogger("session.store");
31113
+ import fs13 from "fs";
31114
+ import path19 from "path";
31115
+ var logger26 = createModuleLogger("session.store");
29409
31116
  var SessionStore = class {
29410
31117
  filePath;
29411
31118
  cache;
29412
31119
  constructor(dataDir) {
29413
- this.filePath = path17.join(dataDir, "sessions.json");
31120
+ this.filePath = path19.join(dataDir, "sessions.json");
29414
31121
  this.cache = this.loadFromDisk();
29415
31122
  }
29416
31123
  cacheKey(agentId, scope) {
@@ -29445,8 +31152,8 @@ var SessionStore = class {
29445
31152
  }
29446
31153
  loadFromDisk() {
29447
31154
  try {
29448
- if (!fs11.existsSync(this.filePath)) return {};
29449
- const raw = fs11.readFileSync(this.filePath, "utf-8");
31155
+ if (!fs13.existsSync(this.filePath)) return {};
31156
+ const raw = fs13.readFileSync(this.filePath, "utf-8");
29450
31157
  const parsed = JSON.parse(raw);
29451
31158
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
29452
31159
  const map2 = parsed;
@@ -29456,7 +31163,7 @@ var SessionStore = class {
29456
31163
  migrated[key] = sessionId;
29457
31164
  } else {
29458
31165
  migrated[`${key}::single`] = sessionId;
29459
- logger24.info("Migrated legacy session key to scoped key", {
31166
+ logger26.info("Migrated legacy session key to scoped key", {
29460
31167
  legacyKey: key,
29461
31168
  newKey: `${key}::single`
29462
31169
  });
@@ -29464,17 +31171,17 @@ var SessionStore = class {
29464
31171
  }
29465
31172
  return migrated;
29466
31173
  } catch (e) {
29467
- logger24.warn("Failed to load sessions file, starting fresh", { error: e, path: this.filePath });
31174
+ logger26.warn("Failed to load sessions file, starting fresh", { error: e, path: this.filePath });
29468
31175
  return {};
29469
31176
  }
29470
31177
  }
29471
31178
  saveToDisk() {
29472
31179
  try {
29473
- const dir = path17.dirname(this.filePath);
29474
- fs11.mkdirSync(dir, { recursive: true });
29475
- fs11.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), "utf-8");
31180
+ const dir = path19.dirname(this.filePath);
31181
+ fs13.mkdirSync(dir, { recursive: true });
31182
+ fs13.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), "utf-8");
29476
31183
  } catch (e) {
29477
- logger24.error("Failed to save sessions file", { error: e, path: this.filePath });
31184
+ logger26.error("Failed to save sessions file", { error: e, path: this.filePath });
29478
31185
  }
29479
31186
  }
29480
31187
  };
@@ -29482,8 +31189,8 @@ var SessionStore = class {
29482
31189
  // src/claudeRuntime.ts
29483
31190
  import { accessSync as accessSync2, constants as constants3, existsSync as existsSync2, readdirSync as readdirSync2, realpathSync } from "fs";
29484
31191
  import { createRequire } from "module";
29485
- import path18 from "path";
29486
- var logger25 = createModuleLogger("bridge.claudeRuntime");
31192
+ import path20 from "path";
31193
+ var logger27 = createModuleLogger("bridge.claudeRuntime");
29487
31194
  var require2 = createRequire(import.meta.url);
29488
31195
  var EXPLICIT_CLAUDE_EXECUTABLE_ENV = "AHCHAT_CLAUDE_EXECUTABLE";
29489
31196
  var BUNDLED_CLAUDE_PATH_ENV = "AHCHAT_BUNDLED_CLAUDE_PATH";
@@ -29491,7 +31198,7 @@ function normalizePath(value) {
29491
31198
  const trimmed = value?.trim();
29492
31199
  if (!trimmed) return void 0;
29493
31200
  const expanded = resolveUserPath(trimmed);
29494
- return path18.isAbsolute(expanded) ? expanded : path18.resolve(expanded);
31201
+ return path20.isAbsolute(expanded) ? expanded : path20.resolve(expanded);
29495
31202
  }
29496
31203
  function canExecute2(candidate) {
29497
31204
  try {
@@ -29570,16 +31277,16 @@ function resolveClaudeAgentSdkDir() {
29570
31277
  const sdkEntry = safeResolve("@anthropic-ai/claude-agent-sdk");
29571
31278
  if (!sdkEntry) return void 0;
29572
31279
  try {
29573
- return path18.dirname(realpathSync(sdkEntry));
31280
+ return path20.dirname(realpathSync(sdkEntry));
29574
31281
  } catch {
29575
- return path18.dirname(sdkEntry);
31282
+ return path20.dirname(sdkEntry);
29576
31283
  }
29577
31284
  }
29578
31285
  function findPnpmStoreDir(fromDir) {
29579
31286
  let current = fromDir;
29580
- while (current !== path18.dirname(current)) {
29581
- if (path18.basename(current) === ".pnpm") return current;
29582
- current = path18.dirname(current);
31287
+ while (current !== path20.dirname(current)) {
31288
+ if (path20.basename(current) === ".pnpm") return current;
31289
+ current = path20.dirname(current);
29583
31290
  }
29584
31291
  return void 0;
29585
31292
  }
@@ -29595,7 +31302,7 @@ function resolvePnpmRuntimeBinary(sdkDir, target) {
29595
31302
  }
29596
31303
  for (const entry of entries) {
29597
31304
  if (!entry.startsWith(`${encodedName}@`)) continue;
29598
- const candidate = path18.join(
31305
+ const candidate = path20.join(
29599
31306
  pnpmStoreDir,
29600
31307
  entry,
29601
31308
  "node_modules",
@@ -29614,8 +31321,8 @@ function resolveSdkRuntimeBinary(target) {
29614
31321
  const scopedPackageName = target.packageName.split("/")[1] ?? target.packageName;
29615
31322
  const candidates = [
29616
31323
  safeResolve(`${target.packageName}/${target.binaryName}`, [sdkDir]),
29617
- path18.join(sdkDir, "..", scopedPackageName, target.binaryName),
29618
- path18.join(sdkDir, "node_modules", ...target.packageName.split("/"), target.binaryName),
31324
+ path20.join(sdkDir, "..", scopedPackageName, target.binaryName),
31325
+ path20.join(sdkDir, "node_modules", ...target.packageName.split("/"), target.binaryName),
29619
31326
  resolvePnpmRuntimeBinary(sdkDir, target)
29620
31327
  ].filter((candidate) => Boolean(candidate));
29621
31328
  return candidates.find((candidate) => existsSync2(candidate));
@@ -29732,14 +31439,14 @@ function resolveClaudeRuntime(env2 = process.env) {
29732
31439
  }
29733
31440
  function logClaudeRuntimeResolution(resolution) {
29734
31441
  if (resolution.ok) {
29735
- logger25.info("Claude runtime ready", {
31442
+ logger27.info("Claude runtime ready", {
29736
31443
  source: resolution.source,
29737
31444
  path: resolution.path ?? null,
29738
31445
  version: resolution.version ?? null
29739
31446
  });
29740
31447
  return;
29741
31448
  }
29742
- logger25.error("Claude runtime unavailable", {
31449
+ logger27.error("Claude runtime unavailable", {
29743
31450
  error: new Error(resolution.error ?? "Claude runtime unavailable"),
29744
31451
  source: resolution.source,
29745
31452
  attempts: resolution.attempts
@@ -29747,27 +31454,27 @@ function logClaudeRuntimeResolution(resolution) {
29747
31454
  }
29748
31455
 
29749
31456
  // src/forkAgentFiles.ts
29750
- import * as fs12 from "fs/promises";
29751
- import * as path20 from "path";
31457
+ import * as fs14 from "fs/promises";
31458
+ import * as path22 from "path";
29752
31459
 
29753
31460
  // src/sessionSlug.ts
29754
31461
  import os9 from "os";
29755
- import path19 from "path";
29756
- var CLAUDE_PROJECTS_DIR = path19.join(os9.homedir(), ".claude", "projects");
31462
+ import path21 from "path";
31463
+ var CLAUDE_PROJECTS_DIR = path21.join(os9.homedir(), ".claude", "projects");
29757
31464
  function cwdToSlug(cwd) {
29758
31465
  return cwd.replace(/[^a-zA-Z0-9-]/g, "-");
29759
31466
  }
29760
31467
  function sessionDirForCwd(cwd) {
29761
- return path19.join(CLAUDE_PROJECTS_DIR, cwdToSlug(cwd));
31468
+ return path21.join(CLAUDE_PROJECTS_DIR, cwdToSlug(cwd));
29762
31469
  }
29763
31470
  function sessionFilePath(cwd, sessionId) {
29764
- return path19.join(sessionDirForCwd(cwd), `${sessionId}.jsonl`);
31471
+ return path21.join(sessionDirForCwd(cwd), `${sessionId}.jsonl`);
29765
31472
  }
29766
31473
 
29767
31474
  // src/forkAgentFiles.ts
29768
- var logger26 = createModuleLogger("bridge.forkAgentFiles");
31475
+ var logger28 = createModuleLogger("bridge.forkAgentFiles");
29769
31476
  async function forkAgentFiles(sourceAgentId, newAgentId, sourceWorkdir, newWorkdir, dataDir, sessionStore, sourceConversationId) {
29770
- logger26.info("Fork file copy starting", {
31477
+ logger28.info("Fork file copy starting", {
29771
31478
  sourceAgentId,
29772
31479
  newAgentId,
29773
31480
  sourceWorkdir,
@@ -29776,57 +31483,57 @@ async function forkAgentFiles(sourceAgentId, newAgentId, sourceWorkdir, newWorkd
29776
31483
  sourceConversationId
29777
31484
  });
29778
31485
  try {
29779
- const stat3 = await fs12.stat(sourceWorkdir).catch(() => null);
31486
+ const stat3 = await fs14.stat(sourceWorkdir).catch(() => null);
29780
31487
  if (stat3?.isDirectory()) {
29781
- await fs12.cp(sourceWorkdir, newWorkdir, { recursive: true });
29782
- logger26.info("Workdir copied", {
31488
+ await fs14.cp(sourceWorkdir, newWorkdir, { recursive: true });
31489
+ logger28.info("Workdir copied", {
29783
31490
  from: sourceWorkdir,
29784
31491
  to: newWorkdir
29785
31492
  });
29786
31493
  } else {
29787
- await fs12.mkdir(newWorkdir, { recursive: true });
29788
- logger26.info("Workdir created (source did not exist)", {
31494
+ await fs14.mkdir(newWorkdir, { recursive: true });
31495
+ logger28.info("Workdir created (source did not exist)", {
29789
31496
  newWorkdir
29790
31497
  });
29791
31498
  }
29792
31499
  } catch (e) {
29793
- logger26.error("Workdir copy failed", { error: e });
31500
+ logger28.error("Workdir copy failed", { error: e });
29794
31501
  throw e;
29795
31502
  }
29796
- const srcNotebook = path20.join(dataDir, "agent-memory", sourceAgentId, "notebook.md");
29797
- const dstNotebookDir = path20.join(dataDir, "agent-memory", newAgentId);
29798
- const dstNotebook = path20.join(dstNotebookDir, "notebook.md");
31503
+ const srcNotebook = path22.join(dataDir, "agent-memory", sourceAgentId, "notebook.md");
31504
+ const dstNotebookDir = path22.join(dataDir, "agent-memory", newAgentId);
31505
+ const dstNotebook = path22.join(dstNotebookDir, "notebook.md");
29799
31506
  try {
29800
- const nbStat = await fs12.stat(srcNotebook).catch(() => null);
31507
+ const nbStat = await fs14.stat(srcNotebook).catch(() => null);
29801
31508
  if (nbStat?.isFile()) {
29802
- await fs12.mkdir(dstNotebookDir, { recursive: true });
29803
- await fs12.copyFile(srcNotebook, dstNotebook);
29804
- logger26.info("Notebook copied", {
31509
+ await fs14.mkdir(dstNotebookDir, { recursive: true });
31510
+ await fs14.copyFile(srcNotebook, dstNotebook);
31511
+ logger28.info("Notebook copied", {
29805
31512
  from: srcNotebook,
29806
31513
  to: dstNotebook
29807
31514
  });
29808
31515
  } else {
29809
- logger26.info("Notebook copy skipped (source does not exist)", {
31516
+ logger28.info("Notebook copy skipped (source does not exist)", {
29810
31517
  sourceAgentId
29811
31518
  });
29812
31519
  }
29813
31520
  } catch (e) {
29814
- logger26.error("Notebook copy failed", { error: e });
31521
+ logger28.error("Notebook copy failed", { error: e });
29815
31522
  }
29816
31523
  let sessionCopied = false;
29817
31524
  const sourceSessionId = sessionStore.get(sourceAgentId, { kind: "single" });
29818
31525
  if (sourceSessionId) {
29819
31526
  try {
29820
31527
  const srcPath = sessionFilePath(sourceWorkdir, sourceSessionId);
29821
- const srcStat = await fs12.stat(srcPath).catch(() => null);
31528
+ const srcStat = await fs14.stat(srcPath).catch(() => null);
29822
31529
  if (srcStat?.isFile()) {
29823
31530
  const dstDir = sessionDirForCwd(newWorkdir);
29824
- await fs12.mkdir(dstDir, { recursive: true });
29825
- const dstPath = path20.join(dstDir, `${sourceSessionId}.jsonl`);
29826
- await fs12.copyFile(srcPath, dstPath);
31531
+ await fs14.mkdir(dstDir, { recursive: true });
31532
+ const dstPath = path22.join(dstDir, `${sourceSessionId}.jsonl`);
31533
+ await fs14.copyFile(srcPath, dstPath);
29827
31534
  sessionStore.set(newAgentId, { kind: "single" }, sourceSessionId);
29828
31535
  sessionCopied = true;
29829
- logger26.info("Session JSONL copied and registered", {
31536
+ logger28.info("Session JSONL copied and registered", {
29830
31537
  sourceAgentId,
29831
31538
  newAgentId,
29832
31539
  sessionId: sourceSessionId,
@@ -29835,30 +31542,30 @@ async function forkAgentFiles(sourceAgentId, newAgentId, sourceWorkdir, newWorkd
29835
31542
  fileSize: srcStat.size
29836
31543
  });
29837
31544
  } else {
29838
- logger26.info("Session copy skipped (source JSONL not found)", {
31545
+ logger28.info("Session copy skipped (source JSONL not found)", {
29839
31546
  sourceAgentId,
29840
31547
  sessionId: sourceSessionId,
29841
31548
  expectedPath: srcPath
29842
31549
  });
29843
31550
  }
29844
31551
  } catch (e) {
29845
- logger26.error("Session JSONL copy failed", { error: e, sourceAgentId, newAgentId });
31552
+ logger28.error("Session JSONL copy failed", { error: e, sourceAgentId, newAgentId });
29846
31553
  }
29847
31554
  } else {
29848
- logger26.info("Session copy skipped (no session in store)", { sourceAgentId });
31555
+ logger28.info("Session copy skipped (no session in store)", { sourceAgentId });
29849
31556
  }
29850
31557
  if (!sessionCopied && sourceConversationId) {
29851
31558
  await writeForkMeta(dataDir, newAgentId, {
29852
31559
  sourceConversationId,
29853
31560
  forkedAt: (/* @__PURE__ */ new Date()).toISOString()
29854
31561
  });
29855
- logger26.info("Fork meta scheduled for history replay fallback", {
31562
+ logger28.info("Fork meta scheduled for history replay fallback", {
29856
31563
  newAgentId,
29857
31564
  sourceConversationId,
29858
31565
  reason: sourceSessionId ? "jsonl_missing_or_copy_failed" : "no_session_in_store"
29859
31566
  });
29860
31567
  }
29861
- logger26.info("Fork file copy finished", {
31568
+ logger28.info("Fork file copy finished", {
29862
31569
  sourceAgentId,
29863
31570
  newAgentId,
29864
31571
  newWorkdir,
@@ -29868,15 +31575,15 @@ async function forkAgentFiles(sourceAgentId, newAgentId, sourceWorkdir, newWorkd
29868
31575
  }
29869
31576
 
29870
31577
  // src/modelQuerier.ts
29871
- import fs13 from "fs/promises";
31578
+ import fs15 from "fs/promises";
29872
31579
  import os10 from "os";
29873
- import path21 from "path";
31580
+ import path23 from "path";
29874
31581
  import * as sdk4 from "@anthropic-ai/claude-agent-sdk";
29875
- var logger27 = createModuleLogger("bridge.modelQuerier");
31582
+ var logger29 = createModuleLogger("bridge.modelQuerier");
29876
31583
  async function listModels(queryFn, opts = {}) {
29877
31584
  const t0 = Date.now();
29878
- const cwd = opts.cwd ?? path21.join(os10.homedir(), ".ahchat", "workspaces", "_list_models");
29879
- await fs13.mkdir(cwd, { recursive: true });
31585
+ const cwd = opts.cwd ?? path23.join(os10.homedir(), ".ahchat", "workspaces", "_list_models");
31586
+ await fs15.mkdir(cwd, { recursive: true });
29880
31587
  const fn = queryFn ?? sdk4.query;
29881
31588
  const ic = new InputController();
29882
31589
  ic.push("Reply with exactly: PING", "");
@@ -29921,27 +31628,29 @@ async function listModels(queryFn, opts = {}) {
29921
31628
  displayName: m.displayName,
29922
31629
  description: m.description
29923
31630
  }));
29924
- logger27.info("listModels done", { count: models.length, ms: Date.now() - t0 });
31631
+ logger29.info("listModels done", { count: models.length, ms: Date.now() - t0 });
29925
31632
  return models;
29926
31633
  } finally {
29927
31634
  try {
29928
31635
  ic.close();
29929
- } catch {
31636
+ } catch (e) {
31637
+ logger29.debug("listModels: input controller close failed during teardown", { error: e });
29930
31638
  }
29931
31639
  try {
29932
31640
  await q.return?.(void 0);
29933
- } catch {
31641
+ } catch (e) {
31642
+ logger29.debug("listModels: query iterator return failed during teardown", { error: e });
29934
31643
  }
29935
31644
  }
29936
31645
  }
29937
31646
 
29938
31647
  // src/promptOptimizer.ts
29939
- import fs14 from "fs/promises";
31648
+ import fs16 from "fs/promises";
29940
31649
  import os11 from "os";
29941
- import path22 from "path";
31650
+ import path24 from "path";
29942
31651
  import * as sdk5 from "@anthropic-ai/claude-agent-sdk";
29943
- var logger28 = createModuleLogger("bridge.promptOptimizer");
29944
- var OPTIMIZER_SYSTEM_PROMPT = `You are an expert prompt editor for AHChat Agent creation.
31652
+ var logger30 = createModuleLogger("bridge.promptOptimizer");
31653
+ var OPTIMIZER_SYSTEM_PROMPT = `You are an expert prompt editor for ALL-CAN Agent creation.
29945
31654
 
29946
31655
  Rewrite the user's Agent System Prompt so it is clear, operational, and ready to save.
29947
31656
  Preserve the user's intent, role, constraints, language, and important domain details.
@@ -29952,7 +31661,7 @@ Do not include explanations, headings like "Optimized Prompt", markdown fences,
29952
31661
  Keep the result concise enough for a System Prompt field, preferably within 2000 characters.`;
29953
31662
  function buildUserPrompt(opts) {
29954
31663
  const parts = [
29955
- "\u8BF7\u4F18\u5316\u4E0B\u9762\u8FD9\u4E2A AHChat Agent \u7684 System Prompt\u3002",
31664
+ "\u8BF7\u4F18\u5316\u4E0B\u9762\u8FD9\u4E2A ALL-CAN Agent \u7684 System Prompt\u3002",
29956
31665
  opts.name?.trim() ? `Agent \u540D\u79F0\uFF1A${opts.name.trim()}` : null,
29957
31666
  opts.role?.trim() ? `\u89D2\u8272\u6807\u7B7E\uFF1A${opts.role.trim()}` : null,
29958
31667
  "",
@@ -29982,8 +31691,8 @@ async function optimizePrompt(queryFn, opts) {
29982
31691
  const prompt = opts.systemPrompt.trim();
29983
31692
  if (!prompt) throw new Error("systemPrompt is required");
29984
31693
  const t0 = Date.now();
29985
- const cwd = opts.cwd ?? path22.join(os11.homedir(), ".ahchat", "workspaces", "_prompt_optimizer");
29986
- await fs14.mkdir(cwd, { recursive: true });
31694
+ const cwd = opts.cwd ?? path24.join(os11.homedir(), ".ahchat", "workspaces", "_prompt_optimizer");
31695
+ await fs16.mkdir(cwd, { recursive: true });
29987
31696
  const fn = queryFn ?? sdk5.query;
29988
31697
  const ic = new InputController();
29989
31698
  ic.push(buildUserPrompt(opts), "");
@@ -30039,7 +31748,7 @@ async function optimizePrompt(queryFn, opts) {
30039
31748
  })
30040
31749
  ]);
30041
31750
  if (!optimizedPrompt) throw new Error("empty optimized prompt");
30042
- logger28.info("optimizePrompt done", {
31751
+ logger30.info("optimizePrompt done", {
30043
31752
  inputLength: prompt.length,
30044
31753
  outputLength: optimizedPrompt.length,
30045
31754
  model: model || "(default)",
@@ -30050,17 +31759,19 @@ async function optimizePrompt(queryFn, opts) {
30050
31759
  if (timeoutId) clearTimeout(timeoutId);
30051
31760
  try {
30052
31761
  ic.close();
30053
- } catch {
31762
+ } catch (e) {
31763
+ logger30.debug("optimizePrompt: input controller close failed during teardown", { error: e });
30054
31764
  }
30055
31765
  try {
30056
31766
  await q.return?.(void 0);
30057
- } catch {
31767
+ } catch (e) {
31768
+ logger30.debug("optimizePrompt: query iterator return failed during teardown", { error: e });
30058
31769
  }
30059
31770
  }
30060
31771
  }
30061
31772
 
30062
31773
  // src/start.ts
30063
- var logger29 = createModuleLogger("bridge");
31774
+ var logger31 = createModuleLogger("bridge");
30064
31775
  var NODE_USER_UID2 = 1e3;
30065
31776
  function isRunningAsRoot2() {
30066
31777
  try {
@@ -30070,56 +31781,58 @@ function isRunningAsRoot2() {
30070
31781
  }
30071
31782
  }
30072
31783
  async function syncClaudeCredentialsToNodeAccessibleDir(agentConfigDir) {
30073
- const rootClaudeDir = path23.join(process.env.HOME ?? "/root", ".claude");
30074
- const fs15 = await import("fs/promises");
31784
+ const rootClaudeDir = path25.join(process.env.HOME ?? "/root", ".claude");
31785
+ const fs17 = await import("fs/promises");
30075
31786
  try {
30076
- await fs15.access(rootClaudeDir);
31787
+ await fs17.access(rootClaudeDir);
30077
31788
  } catch {
30078
- logger29.info("No /root/.claude to sync", { rootClaudeDir });
31789
+ logger31.info("No /root/.claude to sync", { rootClaudeDir });
30079
31790
  return;
30080
31791
  }
30081
31792
  const filesToSync = [".credentials.json", "settings.json", ".credentials.backup.json"];
30082
31793
  for (const file2 of filesToSync) {
30083
- const src = path23.join(rootClaudeDir, file2);
30084
- const dest = path23.join(agentConfigDir, file2);
31794
+ const src = path25.join(rootClaudeDir, file2);
31795
+ const dest = path25.join(agentConfigDir, file2);
30085
31796
  try {
30086
- await fs15.copyFile(src, dest);
30087
- logger29.info("Synced credential file", { file: file2, from: src, to: dest });
31797
+ await fs17.copyFile(src, dest);
31798
+ logger31.info("Synced credential file", { file: file2, from: src, to: dest });
30088
31799
  } catch {
30089
- logger29.debug("Credential file not present, skipping", { file: file2, src });
31800
+ logger31.debug("Credential file not present, skipping", { file: file2, src });
30090
31801
  }
30091
31802
  }
30092
31803
  }
30093
31804
  async function chownRecursive(dirPath, uid, gid) {
30094
- const fs15 = await import("fs/promises");
31805
+ const fs17 = await import("fs/promises");
30095
31806
  try {
30096
- await fs15.chown(dirPath, uid, gid);
31807
+ await fs17.chown(dirPath, uid, gid);
30097
31808
  } catch {
30098
- logger29.debug("chown skipped", { dirPath, uid, gid });
31809
+ logger31.debug("chown skipped", { dirPath, uid, gid });
30099
31810
  }
30100
31811
  let entries;
30101
31812
  try {
30102
- entries = await fs15.readdir(dirPath, { withFileTypes: true });
31813
+ entries = await fs17.readdir(dirPath, { withFileTypes: true });
30103
31814
  } catch {
30104
31815
  return;
30105
31816
  }
30106
31817
  for (const entry of entries) {
30107
- const fullPath = path23.join(dirPath, entry.name);
31818
+ const fullPath = path25.join(dirPath, entry.name);
30108
31819
  if (entry.isDirectory()) {
30109
31820
  await chownRecursive(fullPath, uid, gid);
30110
31821
  } else {
30111
31822
  try {
30112
- await fs15.chown(fullPath, uid, gid);
31823
+ await fs17.chown(fullPath, uid, gid);
30113
31824
  } catch {
30114
- logger29.debug("chown skipped", { fullPath, uid, gid });
31825
+ logger31.debug("chown skipped", { fullPath, uid, gid });
30115
31826
  }
30116
31827
  }
30117
31828
  }
30118
31829
  }
30119
31830
  async function startBridge(config2) {
31831
+ process.env.AHCHAT_DATA_DIR = config2.dataDir;
31832
+ configureBridgeLogger(config2);
30120
31833
  ensureDir(config2.dataDir);
30121
31834
  ensureDir(config2.agentConfigDir);
30122
- const workspacesDir = path23.join(config2.dataDir, "workspaces");
31835
+ const workspacesDir = path25.join(config2.dataDir, "workspaces");
30123
31836
  ensureDir(workspacesDir);
30124
31837
  process.env.CLAUDE_CONFIG_DIR = config2.agentConfigDir;
30125
31838
  installBridgeFetchAuth(config2.serverApiUrl, config2.bridgeToken);
@@ -30127,7 +31840,7 @@ async function startBridge(config2) {
30127
31840
  await syncClaudeCredentialsToNodeAccessibleDir(config2.agentConfigDir);
30128
31841
  await chownRecursive(config2.agentConfigDir, NODE_USER_UID2, NODE_USER_UID2);
30129
31842
  await chownRecursive(workspacesDir, NODE_USER_UID2, NODE_USER_UID2);
30130
- logger29.info("Root environment: chowned config/workspaces dirs to uid 1000", {
31843
+ logger31.info("Root environment: chowned config/workspaces dirs to uid 1000", {
30131
31844
  agentConfigDir: config2.agentConfigDir,
30132
31845
  workspacesDir
30133
31846
  });
@@ -30142,13 +31855,13 @@ Claude runtime is unavailable.
30142
31855
 
30143
31856
  ${claudeRuntime.error ?? "Install Claude Code manually or use the bundled desktop runtime."}
30144
31857
 
30145
- Reinstall @fangyb/ahchat-bridge with npm optional dependencies, set AHCHAT_CLAUDE_EXECUTABLE to a valid Claude Code binary path, install Claude Code, or use AHChat Desktop with its bundled runtime.
31858
+ Reinstall @fangyb/ahchat-bridge with npm optional dependencies, set AHCHAT_CLAUDE_EXECUTABLE to a valid Claude Code binary path, install Claude Code, or use ALL-CAN Desktop with its bundled runtime.
30146
31859
  `
30147
31860
  );
30148
31861
  process.exit(1);
30149
31862
  }
30150
31863
  setClaudeExecutablePath(claudeRuntime.path);
30151
- logger29.info("Bridge starting", {
31864
+ logger31.info("Bridge starting", {
30152
31865
  bridgeId: config2.bridgeId,
30153
31866
  serverUrl: config2.serverUrl,
30154
31867
  serverApiUrl: config2.serverApiUrl,
@@ -30156,24 +31869,37 @@ Reinstall @fangyb/ahchat-bridge with npm optional dependencies, set AHCHAT_CLAUD
30156
31869
  claudeRuntimeSource: claudeRuntime.source,
30157
31870
  claudeRuntimeVersion: claudeRuntime.version ?? null
30158
31871
  });
30159
- process.stdout.write(`
31872
+ const shouldPrintRawBridgeToken = process.stdout.isTTY && process.env.AHCHAT_SUPPRESS_BRIDGE_TOKEN_STDOUT !== "1";
31873
+ process.stdout.write(
31874
+ `
30160
31875
  Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\u673A\u5668):
30161
- ${config2.bridgeToken}
31876
+ ${shouldPrintRawBridgeToken ? config2.bridgeToken : "***"}
30162
31877
 
30163
- `);
31878
+ `
31879
+ );
30164
31880
  wsMetrics.start(5e3);
30165
31881
  const sessionStore = new SessionStore(config2.dataDir);
30166
- const memoryRoot = path23.join(config2.dataDir, "agent-memory");
31882
+ const workdirOverrideStore = new LocalWorkdirOverrideStore(config2.dataDir);
31883
+ const memoryRoot = path25.join(config2.dataDir, "agent-memory");
30167
31884
  const memoryStore = new AgentMemoryStore(memoryRoot);
30168
- logger29.info("Agent memory store initialized", { rootDir: memoryRoot });
31885
+ logger31.info("Agent memory store initialized", { rootDir: memoryRoot });
30169
31886
  const smithNotebook = memoryStore.read(SMITH_AGENT_ID);
30170
31887
  if (!smithNotebook.trim()) {
30171
31888
  memoryStore.write(SMITH_AGENT_ID, SMITH_NOTEBOOK_SEED);
30172
- logger29.info("Smith notebook seeded", { agentId: SMITH_AGENT_ID });
31889
+ logger31.info("Smith notebook seeded", { agentId: SMITH_AGENT_ID });
30173
31890
  }
30174
31891
  const skillStore = new SkillStore(config2.dataDir);
30175
31892
  skillStore.seed("log-analysis", LOG_ANALYSIS_SKILL);
30176
- logger29.info("Smith log-analysis skill boot seed invoked", { dataDir: config2.dataDir });
31893
+ logger31.info("Smith log-analysis skill boot seed invoked", { dataDir: config2.dataDir });
31894
+ const logUploader = new BridgeLogUploader({
31895
+ dataDir: config2.dataDir,
31896
+ serverApiUrl: config2.serverApiUrl,
31897
+ bridgeToken: config2.bridgeToken,
31898
+ bridgeId: config2.bridgeId,
31899
+ hostname: os12.hostname(),
31900
+ intervalMs: config2.logUploadIntervalMs
31901
+ });
31902
+ logUploader.start();
30177
31903
  const agentRegistry = new HttpAgentRegistry(config2.serverApiUrl, config2.bridgeToken);
30178
31904
  const groupRegistry = new GroupRegistry(config2.serverApiUrl, config2.bridgeToken);
30179
31905
  const subscriptionRegistry = new HttpSubscriptionRegistry(config2.serverApiUrl, config2.bridgeToken);
@@ -30198,10 +31924,18 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30198
31924
  subscriptionRegistry,
30199
31925
  serverApiUrl: config2.serverApiUrl,
30200
31926
  bridgeToken: config2.bridgeToken,
30201
- dataDir: config2.dataDir
31927
+ dataDir: config2.dataDir,
31928
+ workdirOverrideStore
30202
31929
  });
30203
31930
  const taskDispatchHandler = createTaskDispatchHandler(agentManager, agentRegistry, emit);
30204
31931
  const groupTaskDispatchHandler = createGroupTaskDispatchHandler(agentManager, agentRegistry, emit);
31932
+ const resolveBridgeWorkdirPath = (requestedPath) => {
31933
+ const overridden = workdirOverrideStore.resolvePath(requestedPath);
31934
+ if (overridden.overridden) {
31935
+ return { path: overridden.path, remapped: true };
31936
+ }
31937
+ return remapServerWorkspacePath(requestedPath, workspacesDir);
31938
+ };
30205
31939
  let statusInterval = null;
30206
31940
  connector = new ServerConnector({
30207
31941
  config: config2,
@@ -30209,7 +31943,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30209
31943
  onTaskDispatch: taskDispatchHandler,
30210
31944
  onGroupTaskDispatch: groupTaskDispatchHandler,
30211
31945
  onStopGeneration: async (payload) => {
30212
- logger29.info("onStopGeneration invoking cancelReply", {
31946
+ logger31.info("onStopGeneration invoking cancelReply", {
30213
31947
  agentId: payload.agentId,
30214
31948
  ackId: payload.ackId,
30215
31949
  conversationId: payload.conversationId,
@@ -30225,13 +31959,14 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30225
31959
  onConnected: async () => {
30226
31960
  await agentRegistry.refresh();
30227
31961
  await groupRegistry.refresh();
31962
+ await subscriptionRegistry.refresh();
30228
31963
  await agentManager.recoverFromRestart(agentRegistry.getAll());
30229
31964
  },
30230
31965
  onServerPush: async (msg) => {
30231
31966
  switch (msg.type) {
30232
31967
  case "bridge:list_models_request": {
30233
31968
  const { requestId, apiKey, apiBaseUrl, modelsApiBaseUrl } = msg.payload;
30234
- logger29.info("list_models request received", { requestId, hasApiKey: !!apiKey, hasApiBaseUrl: !!apiBaseUrl, hasModelsUrl: !!modelsApiBaseUrl });
31969
+ logger31.info("list_models request received", { requestId, hasApiKey: !!apiKey, hasApiBaseUrl: !!apiBaseUrl, hasModelsUrl: !!modelsApiBaseUrl });
30235
31970
  try {
30236
31971
  let models;
30237
31972
  if (apiKey || apiBaseUrl) {
@@ -30283,7 +32018,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30283
32018
  type: "bridge:list_models_response",
30284
32019
  payload: { requestId, models }
30285
32020
  });
30286
- logger29.info("list_models response sent", { requestId, count: models.length });
32021
+ logger31.info("list_models response sent", { requestId, count: models.length });
30287
32022
  } catch (e) {
30288
32023
  const err = e instanceof Error ? e.message : String(e);
30289
32024
  connector?.send({
@@ -30291,16 +32026,16 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30291
32026
  payload: { requestId, error: err }
30292
32027
  });
30293
32028
  if (err.includes("models \u7AEF\u70B9\u672A\u627E\u5230")) {
30294
- logger29.warn("list_models: endpoint not available, server will use fallback", { requestId });
32029
+ logger31.warn("list_models: endpoint not available, server will use fallback", { requestId });
30295
32030
  } else {
30296
- logger29.error("list_models failed", { requestId, error: e });
32031
+ logger31.error("list_models failed", { requestId, error: e });
30297
32032
  }
30298
32033
  }
30299
32034
  break;
30300
32035
  }
30301
32036
  case "bridge:optimize_prompt_request": {
30302
32037
  const { requestId, apiKey, apiBaseUrl, model, name, role, systemPrompt } = msg.payload;
30303
- logger29.info("optimize_prompt request received", {
32038
+ logger31.info("optimize_prompt request received", {
30304
32039
  requestId,
30305
32040
  hasApiKey: !!apiKey,
30306
32041
  hasApiBaseUrl: !!apiBaseUrl,
@@ -30322,7 +32057,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30322
32057
  type: "bridge:optimize_prompt_response",
30323
32058
  payload: { requestId, optimizedPrompt }
30324
32059
  });
30325
- logger29.info("optimize_prompt response sent", {
32060
+ logger31.info("optimize_prompt response sent", {
30326
32061
  requestId,
30327
32062
  length: optimizedPrompt.length
30328
32063
  });
@@ -30332,50 +32067,50 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30332
32067
  type: "bridge:optimize_prompt_response",
30333
32068
  payload: { requestId, error: err }
30334
32069
  });
30335
- logger29.error("optimize_prompt failed", { requestId, error: e });
32070
+ logger31.error("optimize_prompt failed", { requestId, error: e });
30336
32071
  }
30337
32072
  break;
30338
32073
  }
30339
32074
  case "bridge:list_dir_request": {
30340
32075
  const { requestId, path: dirPath } = msg.payload;
30341
- logger29.info("list_dir request received", { requestId, path: dirPath });
32076
+ logger31.info("list_dir request received", { requestId, path: dirPath });
30342
32077
  try {
30343
- const resolved = remapServerWorkspacePath(dirPath, workspacesDir);
32078
+ const resolved = resolveBridgeWorkdirPath(dirPath);
30344
32079
  if (resolved.remapped) {
30345
32080
  ensureDir(resolved.path);
30346
- logger29.info("list_dir path remapped to local Bridge workspace", {
32081
+ logger31.info("list_dir path resolved to local Bridge workspace", {
30347
32082
  requestId,
30348
32083
  requested: dirPath,
30349
- remapped: resolved.path
32084
+ resolved: resolved.path
30350
32085
  });
30351
32086
  }
30352
32087
  const entries = await listDirectoryEntries(resolved.path);
30353
32088
  connector?.send({
30354
32089
  type: "bridge:list_dir_response",
30355
- payload: { requestId, entries }
32090
+ payload: { requestId, entries, localPath: resolved.path }
30356
32091
  });
30357
- logger29.info("list_dir response sent", { requestId, count: entries.length });
32092
+ logger31.info("list_dir response sent", { requestId, count: entries.length });
30358
32093
  } catch (e) {
30359
32094
  const err = e instanceof Error ? e.message : String(e);
30360
32095
  connector?.send({
30361
32096
  type: "bridge:list_dir_response",
30362
32097
  payload: { requestId, error: err }
30363
32098
  });
30364
- logger29.error("list_dir failed", { requestId, error: e });
32099
+ logger31.error("list_dir failed", { requestId, error: e });
30365
32100
  }
30366
32101
  break;
30367
32102
  }
30368
32103
  case "bridge:write_file_request": {
30369
32104
  const { requestId, baseDir, relativePath, contentBase64, overwrite } = msg.payload;
30370
- logger29.info("write_file request received", { requestId, baseDir, relativePath });
32105
+ logger31.info("write_file request received", { requestId, baseDir, relativePath });
30371
32106
  try {
30372
- const resolved = remapServerWorkspacePath(baseDir, workspacesDir);
32107
+ const resolved = resolveBridgeWorkdirPath(baseDir);
30373
32108
  if (resolved.remapped) {
30374
32109
  ensureDir(resolved.path);
30375
- logger29.info("write_file baseDir remapped to local Bridge workspace", {
32110
+ logger31.info("write_file baseDir resolved to local Bridge workspace", {
30376
32111
  requestId,
30377
32112
  requested: baseDir,
30378
- remapped: resolved.path
32113
+ resolved: resolved.path
30379
32114
  });
30380
32115
  }
30381
32116
  const written = await writeWorkdirFile({
@@ -30389,27 +32124,28 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30389
32124
  payload: {
30390
32125
  requestId,
30391
32126
  path: written.path,
32127
+ bridgePath: written.path,
30392
32128
  relativePath: written.relativePath,
30393
32129
  size: written.size
30394
32130
  }
30395
32131
  });
30396
- logger29.info("write_file response sent", { requestId, path: written.path, size: written.size });
32132
+ logger31.info("write_file response sent", { requestId, path: written.path, size: written.size });
30397
32133
  } catch (e) {
30398
32134
  const err = e instanceof Error ? e.message : String(e);
30399
32135
  connector?.send({
30400
32136
  type: "bridge:write_file_response",
30401
32137
  payload: { requestId, error: err }
30402
32138
  });
30403
- logger29.error("write_file failed", { requestId, error: e });
32139
+ logger31.error("write_file failed", { requestId, error: e });
30404
32140
  }
30405
32141
  break;
30406
32142
  }
30407
32143
  case "bridge:read_file_request": {
30408
32144
  const { requestId, path: filePath, baseDir } = msg.payload;
30409
- logger29.info("read_file request received", { requestId, path: filePath, baseDir });
32145
+ logger31.info("read_file request received", { requestId, path: filePath, baseDir });
30410
32146
  try {
30411
- const resolved = remapServerWorkspacePath(filePath, workspacesDir);
30412
- const resolvedBase = baseDir ? remapServerWorkspacePath(baseDir, workspacesDir) : void 0;
32147
+ const resolved = resolveBridgeWorkdirPath(filePath);
32148
+ const resolvedBase = baseDir ? resolveBridgeWorkdirPath(baseDir) : void 0;
30413
32149
  const result = await readWorkdirFile(resolved.path, resolvedBase?.path);
30414
32150
  connector?.send({
30415
32151
  type: "bridge:read_file_response",
@@ -30420,25 +32156,28 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30420
32156
  size: result.size
30421
32157
  }
30422
32158
  });
30423
- logger29.info("read_file response sent", { requestId, fileName: result.fileName, size: result.size });
32159
+ logger31.info("read_file response sent", { requestId, fileName: result.fileName, size: result.size });
30424
32160
  } catch (e) {
30425
32161
  const err = e instanceof Error ? e.message : String(e);
30426
32162
  connector?.send({
30427
32163
  type: "bridge:read_file_response",
30428
32164
  payload: { requestId, error: err }
30429
32165
  });
30430
- logger29.error("read_file failed", { requestId, error: e });
32166
+ logger31.error("read_file failed", { requestId, error: e });
30431
32167
  }
30432
32168
  break;
30433
32169
  }
30434
32170
  case "agent:dump_sessions_request": {
30435
32171
  const { requestId, agentId, traceId } = msg.payload;
30436
- logger29.info("agent:dump_sessions_request received", { requestId, agentId, traceId });
32172
+ logger31.info("agent:dump_sessions_request received", { requestId, agentId, traceId });
30437
32173
  try {
30438
32174
  const result = await dumpAgentContext(agentId, {
30439
32175
  sessionStore,
30440
32176
  agentRegistry,
30441
- groupRegistry
32177
+ groupRegistry,
32178
+ workspacesDir,
32179
+ agentConfigDir: config2.agentConfigDir,
32180
+ workdirOverrideStore
30442
32181
  });
30443
32182
  connector?.send({
30444
32183
  type: "agent:dump_sessions_response",
@@ -30446,16 +32185,18 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30446
32185
  requestId,
30447
32186
  ok: result.ok,
30448
32187
  dir: result.dir,
32188
+ localDir: result.localDir,
30449
32189
  files: result.files,
30450
32190
  scopeErrors: result.scopeErrors,
30451
32191
  error: result.error
30452
32192
  }
30453
32193
  });
30454
- logger29.info("agent:dump_sessions_response sent", {
32194
+ logger31.info("agent:dump_sessions_response sent", {
30455
32195
  requestId,
30456
32196
  agentId,
30457
32197
  traceId,
30458
32198
  ok: result.ok,
32199
+ localDir: result.localDir,
30459
32200
  fileCount: result.files.length,
30460
32201
  scopeErrorCount: result.scopeErrors.length,
30461
32202
  error: result.error
@@ -30466,7 +32207,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30466
32207
  type: "agent:dump_sessions_response",
30467
32208
  payload: { requestId, ok: false, error: err }
30468
32209
  });
30469
- logger29.error("agent:dump_sessions_request failed", {
32210
+ logger31.error("agent:dump_sessions_request failed", {
30470
32211
  requestId,
30471
32212
  agentId,
30472
32213
  traceId,
@@ -30477,13 +32218,15 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30477
32218
  }
30478
32219
  case "bridge:fetch_logs_request": {
30479
32220
  const { requestId, ...filter } = msg.payload;
30480
- logger29.info("fetch_logs request received", {
32221
+ logger31.info("fetch_logs request received", {
30481
32222
  requestId,
30482
32223
  startIso: filter.startIso,
30483
32224
  endIso: filter.endIso,
30484
32225
  traceId: filter.traceId,
30485
32226
  module: filter.module,
30486
- levelMin: filter.levelMin
32227
+ levelMin: filter.levelMin,
32228
+ limit: filter.limit,
32229
+ offset: filter.offset
30487
32230
  });
30488
32231
  try {
30489
32232
  const result = await scanBridgeLogs({ ...filter, source: "bridge" });
@@ -30493,13 +32236,17 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30493
32236
  requestId,
30494
32237
  entries: result.entries,
30495
32238
  truncated: result.truncated,
30496
- totalScanned: result.totalScanned
32239
+ totalScanned: result.totalScanned,
32240
+ totalMatched: result.totalMatched,
32241
+ nextOffset: result.nextOffset
30497
32242
  }
30498
32243
  });
30499
- logger29.info("fetch_logs response sent", {
32244
+ logger31.info("fetch_logs response sent", {
30500
32245
  requestId,
30501
32246
  count: result.entries.length,
30502
- truncated: result.truncated
32247
+ truncated: result.truncated,
32248
+ totalMatched: result.totalMatched,
32249
+ nextOffset: result.nextOffset
30503
32250
  });
30504
32251
  } catch (e) {
30505
32252
  const err = e instanceof Error ? e.message : String(e);
@@ -30507,13 +32254,13 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30507
32254
  type: "bridge:fetch_logs_response",
30508
32255
  payload: { requestId, error: err }
30509
32256
  });
30510
- logger29.error("fetch_logs failed", { requestId, error: e });
32257
+ logger31.error("fetch_logs failed", { requestId, error: e });
30511
32258
  }
30512
32259
  break;
30513
32260
  }
30514
32261
  case "agent:fork": {
30515
32262
  const fp = msg.payload;
30516
- logger29.info("agent:fork received, copying workdir notebook and session", {
32263
+ logger31.info("agent:fork received, copying workdir notebook and session", {
30517
32264
  sourceAgentId: fp.sourceAgentId,
30518
32265
  newAgentId: fp.newAgentId,
30519
32266
  sourceWorkdir: fp.sourceWorkdir,
@@ -30531,13 +32278,13 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30531
32278
  sessionStore,
30532
32279
  fp.sourceConversationId
30533
32280
  );
30534
- logger29.info("agent:fork files copied successfully", {
32281
+ logger31.info("agent:fork files copied successfully", {
30535
32282
  newAgentId: fp.newAgentId,
30536
32283
  traceId: fp.traceId,
30537
32284
  sessionCopied
30538
32285
  });
30539
32286
  } catch (e) {
30540
- logger29.error("agent:fork file copy failed", {
32287
+ logger31.error("agent:fork file copy failed", {
30541
32288
  error: e,
30542
32289
  newAgentId: fp.newAgentId,
30543
32290
  traceId: fp.traceId
@@ -30549,7 +32296,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30549
32296
  await agentManager.terminate(msg.payload.agentId);
30550
32297
  break;
30551
32298
  case "agent:terminate_scope":
30552
- logger29.info("agent:terminate_scope received", {
32299
+ logger31.info("agent:terminate_scope received", {
30553
32300
  agentId: msg.payload.agentId,
30554
32301
  scope: msg.payload.scope
30555
32302
  });
@@ -30565,7 +32312,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30565
32312
  const oldConfig = parseAgentConfig(oldAgent.config);
30566
32313
  const newConfig = parseAgentConfig(msg.payload.agent.config);
30567
32314
  if (oldConfig.model !== newConfig.model) {
30568
- logger29.info("agent:updated - model changed, terminating running processes", {
32315
+ logger31.info("agent:updated - model changed, terminating running processes", {
30569
32316
  agentId: msg.payload.agent.id,
30570
32317
  oldModel: oldConfig.model ?? "(default)",
30571
32318
  newModel: newConfig.model ?? "(default)"
@@ -30580,9 +32327,27 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30580
32327
  break;
30581
32328
  case "subscription:changed":
30582
32329
  subscriptionRegistry.upsert(msg.payload.subscription);
32330
+ for (const agent of agentRegistry.getAll()) {
32331
+ const cfg = parseAgentConfig(agent.config);
32332
+ if (cfg.subscriptionId !== msg.payload.subscription.id) continue;
32333
+ logger31.info("subscription:changed - terminating agent to apply model capabilities", {
32334
+ agentId: agent.id,
32335
+ subscriptionId: msg.payload.subscription.id
32336
+ });
32337
+ void agentManager.terminate(agent.id);
32338
+ }
30583
32339
  break;
30584
32340
  case "subscription:deleted":
30585
32341
  subscriptionRegistry.remove(msg.payload.subscriptionId);
32342
+ for (const agent of agentRegistry.getAll()) {
32343
+ const cfg = parseAgentConfig(agent.config);
32344
+ if (cfg.subscriptionId !== msg.payload.subscriptionId) continue;
32345
+ logger31.info("subscription:deleted - terminating agent using deleted subscription", {
32346
+ agentId: agent.id,
32347
+ subscriptionId: msg.payload.subscriptionId
32348
+ });
32349
+ void agentManager.terminate(agent.id);
32350
+ }
30586
32351
  break;
30587
32352
  case "group:member_changed":
30588
32353
  await handleGroupMemberChangedPush(
@@ -30615,7 +32380,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30615
32380
  const p = msg.payload;
30616
32381
  const answerText = formatAnswerForSDK(p);
30617
32382
  const ok = askQuestionRegistry.resolve(p.questionId, answerText);
30618
- logger29.info("user:answer_question handled", {
32383
+ logger31.info("user:answer_question handled", {
30619
32384
  questionId: p.questionId,
30620
32385
  agentId: p.agentId,
30621
32386
  resolved: ok,
@@ -30636,7 +32401,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30636
32401
  });
30637
32402
  }, config2.queryConfig.statusReportIntervalMs);
30638
32403
  const shutdown = async (signal) => {
30639
- logger29.info("Shutdown signal received", { signal });
32404
+ logger31.info("Shutdown signal received", { signal });
30640
32405
  if (statusInterval) {
30641
32406
  clearInterval(statusInterval);
30642
32407
  statusInterval = null;
@@ -30645,11 +32410,22 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30645
32410
  connector?.close();
30646
32411
  await agentManager.shutdownAll();
30647
32412
  releaseLock();
30648
- logger29.info("Bridge stopped");
32413
+ logger31.info("Bridge stopped");
32414
+ await flushFileTransports();
32415
+ await logUploader.flushOnce();
32416
+ logUploader.stop();
32417
+ await flushFileTransports();
30649
32418
  process.exit(0);
30650
32419
  };
30651
32420
  process.on("SIGINT", () => void shutdown("SIGINT"));
30652
32421
  process.on("SIGTERM", () => void shutdown("SIGTERM"));
32422
+ process.on("uncaughtException", (e) => {
32423
+ logger31.fatal("Uncaught exception, exiting", { error: e });
32424
+ void flushFileTransports().finally(() => process.exit(1));
32425
+ });
32426
+ process.on("unhandledRejection", (reason) => {
32427
+ logger31.error("Unhandled promise rejection", { error: reason });
32428
+ });
30653
32429
  }
30654
32430
  function compactEnv(env2) {
30655
32431
  return Object.fromEntries(
@@ -30657,9 +32433,39 @@ function compactEnv(env2) {
30657
32433
  );
30658
32434
  }
30659
32435
 
32436
+ // src/startError.ts
32437
+ var logger32 = createModuleLogger("bridge.startError");
32438
+ function buildStopCommand(pid) {
32439
+ if (process.platform === "win32") return `taskkill /PID ${pid} /T /F`;
32440
+ return `kill ${pid}`;
32441
+ }
32442
+ function writeAlreadyRunningMessage(error51) {
32443
+ const lines = [
32444
+ "",
32445
+ `ALL-CAN Bridge is already running (PID: ${error51.pid}).`,
32446
+ "No need to start another Bridge. You can close this terminal.",
32447
+ `Data dir: ${error51.dataDir}`,
32448
+ "To restart it first, run:",
32449
+ ` ${buildStopCommand(error51.pid)}`,
32450
+ ""
32451
+ ];
32452
+ process.stdout.write(`${lines.join("\n")}
32453
+ `);
32454
+ }
32455
+ function handleBridgeStartError(e, message) {
32456
+ if (isBridgeAlreadyRunningError(e)) {
32457
+ logger32.info("Bridge already running; duplicate start skipped", {
32458
+ pid: e.pid,
32459
+ dataDir: e.dataDir
32460
+ });
32461
+ writeAlreadyRunningMessage(e);
32462
+ process.exit(0);
32463
+ }
32464
+ logger32.error(message, { error: e });
32465
+ process.exit(1);
32466
+ }
32467
+
30660
32468
  // src/index.ts
30661
- var logger30 = createModuleLogger("bridge");
30662
32469
  void startBridge(loadBridgeConfig()).catch((e) => {
30663
- logger30.error("Bridge failed to start", { error: e });
30664
- process.exit(1);
32470
+ handleBridgeStartError(e, "Bridge failed to start");
30665
32471
  });