@fangyb/ahchat-bridge 0.1.26 → 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 +2260 -629
  2. package/dist/index.js +2071 -461
  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
@@ -5910,14 +6009,82 @@ function isAskUserQuestionToolName(toolName) {
5910
6009
  return normalized === "askuserquestion" || normalized.endsWith("askuserquestion");
5911
6010
  }
5912
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
+ }
6067
+
5913
6068
  // ../shared/src/utils/subscription.ts
5914
6069
  var PRIMARY_COMPANY_SUBSCRIPTION_ID = "sub_company_primary";
5915
6070
  function isSubscriptionType(v) {
5916
6071
  return v === "system" || v === "project";
5917
6072
  }
6073
+ function isSubscriptionApiFormat(v) {
6074
+ return v === "anthropic" || v === "openai";
6075
+ }
5918
6076
  function isPrimaryCompanySubscriptionId(v) {
5919
6077
  return v === PRIMARY_COMPANY_SUBSCRIPTION_ID;
5920
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
+ }
5921
6088
 
5922
6089
  // ../shared/src/utils/agentConfig.ts
5923
6090
  function parseAgentConfig(raw) {
@@ -5936,6 +6103,7 @@ function parseAgentConfig(raw) {
5936
6103
  }
5937
6104
  }
5938
6105
  if (isSubscriptionType(obj.subscriptionType)) out.subscriptionType = obj.subscriptionType;
6106
+ if (isSubscriptionApiFormat(obj.apiFormat)) out.apiFormat = obj.apiFormat;
5939
6107
  if (typeof obj.apiKey === "string" && obj.apiKey.trim()) out.apiKey = obj.apiKey.trim();
5940
6108
  if (typeof obj.apiBaseUrl === "string" && obj.apiBaseUrl.trim()) out.apiBaseUrl = obj.apiBaseUrl.trim();
5941
6109
  const modelDisplayName = obj.modelDisplayName;
@@ -5955,8 +6123,10 @@ function parseAgentConfig(raw) {
5955
6123
  function isImageMimeType(mimeType) {
5956
6124
  return mimeType.toLowerCase().startsWith("image/");
5957
6125
  }
5958
- function shouldUseBase64(size) {
5959
- 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`;
5960
6130
  }
5961
6131
 
5962
6132
  // ../shared/src/skillContent.ts
@@ -6084,6 +6254,7 @@ HH:MM:SS.mmm ...
6084
6254
 
6085
6255
  // ../shared/src/utils/logScan.ts
6086
6256
  var VALID_LEVELS = /* @__PURE__ */ new Set(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"]);
6257
+ var VALID_SOURCES = /* @__PURE__ */ new Set(["web", "server", "bridge", "desktop"]);
6087
6258
  function isRecord(v) {
6088
6259
  return typeof v === "object" && v !== null;
6089
6260
  }
@@ -6091,6 +6262,10 @@ function parseLevel(raw) {
6091
6262
  if (typeof raw !== "string" || !VALID_LEVELS.has(raw)) return null;
6092
6263
  return raw;
6093
6264
  }
6265
+ function parseSource(raw) {
6266
+ if (typeof raw !== "string" || !VALID_SOURCES.has(raw)) return null;
6267
+ return raw;
6268
+ }
6094
6269
  function parseLogLine(raw) {
6095
6270
  const trimmed = raw.trim();
6096
6271
  if (!trimmed) return null;
@@ -6103,7 +6278,7 @@ function parseLogLine(raw) {
6103
6278
  if (!isRecord(obj)) return null;
6104
6279
  const ts = typeof obj.ts === "string" ? obj.ts : null;
6105
6280
  const level = parseLevel(obj.level);
6106
- const source = obj.source === "server" || obj.source === "bridge" ? obj.source : null;
6281
+ const source = parseSource(obj.source);
6107
6282
  const module = typeof obj.module === "string" ? obj.module : null;
6108
6283
  const msg = typeof obj.msg === "string" ? obj.msg : null;
6109
6284
  if (!ts || !level || !source || !module || !msg) return null;
@@ -6134,7 +6309,7 @@ function tsInRange(ts, startIso, endIso) {
6134
6309
  const t = Date.parse(ts);
6135
6310
  const start = Date.parse(startIso);
6136
6311
  const end = Date.parse(endIso);
6137
- 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;
6138
6313
  return t >= start && t <= end;
6139
6314
  }
6140
6315
  function matchesFilter(hit, filter) {
@@ -6150,6 +6325,51 @@ function matchesFilter(hit, filter) {
6150
6325
  // src/agentMemoryStore.ts
6151
6326
  import fs2 from "fs";
6152
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
6153
6373
  var logger = createModuleLogger("agent.memoryStore");
6154
6374
  var NOTEBOOK_FILE_NAME = "notebook.md";
6155
6375
  var AGENT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
@@ -6225,13 +6445,61 @@ import os5 from "os";
6225
6445
  import path11 from "path";
6226
6446
  import * as sdk2 from "@anthropic-ai/claude-agent-sdk";
6227
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
+
6228
6496
  // src/attachmentAdapter.ts
6229
6497
  var logger2 = createModuleLogger("attachment.adapter");
6230
6498
  async function adaptAttachmentsForSDK(attachments, options) {
6231
6499
  if (!attachments || attachments.length === 0) return [];
6232
6500
  const blocks = [];
6233
6501
  for (const att of attachments) {
6234
- if (isImageMimeType(att.mimeType) && options.supportsVision !== false) {
6502
+ if (isImageMimeType(att.mimeType) && options.modelInputMode === "vision" && options.supportsVision === true) {
6235
6503
  await adaptImageAttachment(att, options, blocks);
6236
6504
  continue;
6237
6505
  }
@@ -6241,70 +6509,69 @@ async function adaptAttachmentsForSDK(attachments, options) {
6241
6509
  }
6242
6510
  async function adaptImageAttachment(att, options, blocks) {
6243
6511
  const mediaType = att.mimeType;
6244
- if (shouldUseBase64(att.size)) {
6245
- try {
6246
- const buffer = await options.fetchBuffer(att.url);
6247
- blocks.push({
6248
- type: "image",
6249
- source: {
6250
- type: "base64",
6251
- media_type: mediaType,
6252
- data: buffer.toString("base64")
6253
- }
6254
- });
6255
- logger2.info("Image attachment adapted as base64", {
6256
- attachmentId: att.id,
6257
- mimeType: att.mimeType,
6258
- size: att.size
6259
- });
6260
- return;
6261
- } catch (e) {
6262
- logger2.warn("Failed to fetch image for base64, falling back to text reference", {
6263
- attachmentId: att.id,
6264
- error: e
6265
- });
6266
- }
6267
- } 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
+ });
6268
6521
  blocks.push({
6269
6522
  type: "image",
6270
6523
  source: {
6271
- type: "url",
6524
+ type: "base64",
6272
6525
  media_type: mediaType,
6273
- url: att.url
6526
+ data: buffer.toString("base64")
6274
6527
  }
6275
6528
  });
6276
- logger2.info("Image attachment adapted as URL", {
6529
+ logger2.info("Image attachment adapted as base64", {
6277
6530
  attachmentId: att.id,
6278
6531
  mimeType: att.mimeType,
6279
6532
  size: att.size
6280
6533
  });
6281
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
+ });
6282
6540
  }
6283
6541
  blocks.push({
6284
6542
  type: "text",
6285
- text: `[User uploaded image: ${att.fileName} (${formatSize(att.size)}, ${att.mimeType})]`
6543
+ text: formatAttachmentForModel(att, { sourceLabel: "User uploaded image" })
6286
6544
  });
6287
6545
  }
6288
6546
  async function adaptFileAttachment(att, options, blocks) {
6289
6547
  const isImage = isImageMimeType(att.mimeType);
6290
6548
  const fileLabel = isImage ? "image" : "file";
6291
- const fileText = `[User uploaded ${fileLabel}: ${att.fileName} (${formatSize(att.size)}, ${att.mimeType})]`;
6292
- 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";
6293
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
+ }
6294
6562
  if (!options.materializeFile) {
6295
6563
  throw new Error("No file materializer configured");
6296
6564
  }
6297
6565
  const buffer = await options.fetchBuffer(att.url);
6298
6566
  const materialized = normalizeMaterializedAttachment(await options.materializeFile(att, buffer));
6299
- const readableHint = materialized.readableTextPath ? `
6300
- Readable extracted text path: ${materialized.readableTextPath}
6301
- For document contents, use Read on the extracted text path instead of reading the original binary file.` : materialized.extractionError ? `
6302
- Document text extraction failed: ${materialized.extractionError}` : "";
6303
6567
  blocks.push({
6304
6568
  type: "text",
6305
- text: `${fileText}
6306
- Saved file path: ${materialized.filePath}${readableHint}
6307
- ${readHint}`
6569
+ text: formatAttachmentForModel(att, {
6570
+ sourceLabel: `User uploaded ${fileLabel}`,
6571
+ workspacePath: materialized.filePath,
6572
+ readableTextPath: materialized.readableTextPath,
6573
+ extractionError: materialized.extractionError
6574
+ })
6308
6575
  });
6309
6576
  logger2.info(`${fileLabel} attachment materialized for Agent`, {
6310
6577
  attachmentId: att.id,
@@ -6332,11 +6599,6 @@ function normalizeMaterializedAttachment(result) {
6332
6599
  if (typeof result === "string") return { filePath: result };
6333
6600
  return result;
6334
6601
  }
6335
- function formatSize(bytes) {
6336
- if (bytes < 1024) return `${bytes} B`;
6337
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
6338
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
6339
- }
6340
6602
 
6341
6603
  // src/claudeExecutable.ts
6342
6604
  var claudeExecutablePath;
@@ -6355,7 +6617,8 @@ function resolveModelLimits(customModels, model) {
6355
6617
  return {
6356
6618
  ...entry.maxInputTokens ? { maxInputTokens: entry.maxInputTokens } : {},
6357
6619
  ...entry.maxOutputTokens ? { maxOutputTokens: entry.maxOutputTokens } : {},
6358
- ...entry.capabilities?.reasoning ? { reasoning: true } : {}
6620
+ ...entry.capabilities?.reasoning ? { reasoning: true } : {},
6621
+ ...entry.capabilities?.image ? { supportsVision: true } : {}
6359
6622
  };
6360
6623
  }
6361
6624
  function buildModelLimitEnv(cfg) {
@@ -6369,6 +6632,13 @@ function buildModelLimitEnv(cfg) {
6369
6632
  return env2;
6370
6633
  }
6371
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
+
6372
6642
  // src/inputController.ts
6373
6643
  var InputController = class {
6374
6644
  queue = [];
@@ -6545,7 +6815,10 @@ function makeAskUserQuestionGuard(deps) {
6545
6815
  return async (input) => {
6546
6816
  const task = deps.getCurrentTask();
6547
6817
  if (!task) {
6548
- 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
+ });
6549
6822
  return { behavior: "deny", message: "[Internal error: no active task context]" };
6550
6823
  }
6551
6824
  const rawQuestions = input.questions ?? [];
@@ -6631,7 +6904,12 @@ function makeAskUserQuestionGuard(deps) {
6631
6904
  });
6632
6905
  deps.emit({
6633
6906
  type: "agent:status",
6634
- 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
+ }
6635
6913
  });
6636
6914
  const answers = await Promise.all(items.map((it) => it.promise));
6637
6915
  logger4.info("AskUserQuestion agent status thinking (resume SDK)", {
@@ -6643,7 +6921,12 @@ function makeAskUserQuestionGuard(deps) {
6643
6921
  });
6644
6922
  deps.emit({
6645
6923
  type: "agent:status",
6646
- payload: { agentId: deps.agentId, status: "thinking" }
6924
+ payload: {
6925
+ agentId: deps.agentId,
6926
+ status: "thinking",
6927
+ traceId: task.traceId,
6928
+ ackId: task.replyMessageId
6929
+ }
6647
6930
  });
6648
6931
  const combined = formatBundleAnswerForSDK(
6649
6932
  items.map((it, idx) => ({
@@ -7348,7 +7631,7 @@ __export(util_exports, {
7348
7631
  getSizableOrigin: () => getSizableOrigin,
7349
7632
  hexToUint8Array: () => hexToUint8Array,
7350
7633
  isObject: () => isObject,
7351
- isPlainObject: () => isPlainObject,
7634
+ isPlainObject: () => isPlainObject2,
7352
7635
  issue: () => issue,
7353
7636
  joinValues: () => joinValues,
7354
7637
  jsonStringifyReplacer: () => jsonStringifyReplacer,
@@ -7478,10 +7761,10 @@ function mergeDefs(...defs) {
7478
7761
  function cloneDef(schema) {
7479
7762
  return mergeDefs(schema._zod.def);
7480
7763
  }
7481
- function getElementAtPath(obj, path24) {
7482
- if (!path24)
7764
+ function getElementAtPath(obj, path26) {
7765
+ if (!path26)
7483
7766
  return obj;
7484
- return path24.reduce((acc, key) => acc?.[key], obj);
7767
+ return path26.reduce((acc, key) => acc?.[key], obj);
7485
7768
  }
7486
7769
  function promiseAllObject(promisesObj) {
7487
7770
  const keys = Object.keys(promisesObj);
@@ -7528,7 +7811,7 @@ var allowsEval = /* @__PURE__ */ cached(() => {
7528
7811
  return false;
7529
7812
  }
7530
7813
  });
7531
- function isPlainObject(o) {
7814
+ function isPlainObject2(o) {
7532
7815
  if (isObject(o) === false)
7533
7816
  return false;
7534
7817
  const ctor = o.constructor;
@@ -7545,7 +7828,7 @@ function isPlainObject(o) {
7545
7828
  return true;
7546
7829
  }
7547
7830
  function shallowClone(o) {
7548
- if (isPlainObject(o))
7831
+ if (isPlainObject2(o))
7549
7832
  return { ...o };
7550
7833
  if (Array.isArray(o))
7551
7834
  return [...o];
@@ -7749,7 +8032,7 @@ function omit(schema, mask) {
7749
8032
  return clone(schema, def);
7750
8033
  }
7751
8034
  function extend(schema, shape) {
7752
- if (!isPlainObject(shape)) {
8035
+ if (!isPlainObject2(shape)) {
7753
8036
  throw new Error("Invalid input to extend: expected a plain object");
7754
8037
  }
7755
8038
  const checks2 = schema._zod.def.checks;
@@ -7772,7 +8055,7 @@ function extend(schema, shape) {
7772
8055
  return clone(schema, def);
7773
8056
  }
7774
8057
  function safeExtend(schema, shape) {
7775
- if (!isPlainObject(shape)) {
8058
+ if (!isPlainObject2(shape)) {
7776
8059
  throw new Error("Invalid input to safeExtend: expected a plain object");
7777
8060
  }
7778
8061
  const def = mergeDefs(schema._zod.def, {
@@ -7890,11 +8173,11 @@ function explicitlyAborted(x, startIndex = 0) {
7890
8173
  }
7891
8174
  return false;
7892
8175
  }
7893
- function prefixIssues(path24, issues) {
8176
+ function prefixIssues(path26, issues) {
7894
8177
  return issues.map((iss) => {
7895
8178
  var _a3;
7896
8179
  (_a3 = iss).path ?? (_a3.path = []);
7897
- iss.path.unshift(path24);
8180
+ iss.path.unshift(path26);
7898
8181
  return iss;
7899
8182
  });
7900
8183
  }
@@ -8041,16 +8324,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
8041
8324
  }
8042
8325
  function formatError(error51, mapper = (issue2) => issue2.message) {
8043
8326
  const fieldErrors = { _errors: [] };
8044
- const processError = (error52, path24 = []) => {
8327
+ const processError = (error52, path26 = []) => {
8045
8328
  for (const issue2 of error52.issues) {
8046
8329
  if (issue2.code === "invalid_union" && issue2.errors.length) {
8047
- issue2.errors.map((issues) => processError({ issues }, [...path24, ...issue2.path]));
8330
+ issue2.errors.map((issues) => processError({ issues }, [...path26, ...issue2.path]));
8048
8331
  } else if (issue2.code === "invalid_key") {
8049
- processError({ issues: issue2.issues }, [...path24, ...issue2.path]);
8332
+ processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
8050
8333
  } else if (issue2.code === "invalid_element") {
8051
- processError({ issues: issue2.issues }, [...path24, ...issue2.path]);
8334
+ processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
8052
8335
  } else {
8053
- const fullpath = [...path24, ...issue2.path];
8336
+ const fullpath = [...path26, ...issue2.path];
8054
8337
  if (fullpath.length === 0) {
8055
8338
  fieldErrors._errors.push(mapper(issue2));
8056
8339
  } else {
@@ -8077,17 +8360,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
8077
8360
  }
8078
8361
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
8079
8362
  const result = { errors: [] };
8080
- const processError = (error52, path24 = []) => {
8363
+ const processError = (error52, path26 = []) => {
8081
8364
  var _a3, _b;
8082
8365
  for (const issue2 of error52.issues) {
8083
8366
  if (issue2.code === "invalid_union" && issue2.errors.length) {
8084
- issue2.errors.map((issues) => processError({ issues }, [...path24, ...issue2.path]));
8367
+ issue2.errors.map((issues) => processError({ issues }, [...path26, ...issue2.path]));
8085
8368
  } else if (issue2.code === "invalid_key") {
8086
- processError({ issues: issue2.issues }, [...path24, ...issue2.path]);
8369
+ processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
8087
8370
  } else if (issue2.code === "invalid_element") {
8088
- processError({ issues: issue2.issues }, [...path24, ...issue2.path]);
8371
+ processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
8089
8372
  } else {
8090
- const fullpath = [...path24, ...issue2.path];
8373
+ const fullpath = [...path26, ...issue2.path];
8091
8374
  if (fullpath.length === 0) {
8092
8375
  result.errors.push(mapper(issue2));
8093
8376
  continue;
@@ -8119,8 +8402,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
8119
8402
  }
8120
8403
  function toDotPath(_path) {
8121
8404
  const segs = [];
8122
- const path24 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
8123
- for (const seg of path24) {
8405
+ const path26 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
8406
+ for (const seg of path26) {
8124
8407
  if (typeof seg === "number")
8125
8408
  segs.push(`[${seg}]`);
8126
8409
  else if (typeof seg === "symbol")
@@ -10119,7 +10402,7 @@ function mergeValues(a, b) {
10119
10402
  if (a instanceof Date && b instanceof Date && +a === +b) {
10120
10403
  return { valid: true, data: a };
10121
10404
  }
10122
- if (isPlainObject(a) && isPlainObject(b)) {
10405
+ if (isPlainObject2(a) && isPlainObject2(b)) {
10123
10406
  const bKeys = Object.keys(b);
10124
10407
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
10125
10408
  const newObj = { ...a, ...b };
@@ -10305,7 +10588,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
10305
10588
  $ZodType.init(inst, def);
10306
10589
  inst._zod.parse = (payload, ctx) => {
10307
10590
  const input = payload.value;
10308
- if (!isPlainObject(input)) {
10591
+ if (!isPlainObject2(input)) {
10309
10592
  payload.issues.push({
10310
10593
  expected: "record",
10311
10594
  code: "invalid_type",
@@ -20812,13 +21095,13 @@ function resolveRef(ref, ctx) {
20812
21095
  if (!ref.startsWith("#")) {
20813
21096
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
20814
21097
  }
20815
- const path24 = ref.slice(1).split("/").filter(Boolean);
20816
- if (path24.length === 0) {
21098
+ const path26 = ref.slice(1).split("/").filter(Boolean);
21099
+ if (path26.length === 0) {
20817
21100
  return ctx.rootSchema;
20818
21101
  }
20819
21102
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
20820
- if (path24[0] === defsKey) {
20821
- const key = path24[1];
21103
+ if (path26[0] === defsKey) {
21104
+ const key = path26[1];
20822
21105
  if (!key || !ctx.defs[key]) {
20823
21106
  throw new Error(`Reference not found: ${ref}`);
20824
21107
  }
@@ -21531,15 +21814,10 @@ function normalizeDocumentText(value) {
21531
21814
  return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n{4,}/g, "\n\n\n").trim();
21532
21815
  }
21533
21816
 
21534
- // src/bridgeHttp.ts
21535
- var BRIDGE_TOKEN_HEADER = "X-AHChat-Bridge-Token";
21536
- function bridgeAuthHeaders(bridgeToken) {
21537
- const token = bridgeToken?.trim();
21538
- return token ? { [BRIDGE_TOKEN_HEADER]: token } : {};
21539
- }
21540
-
21541
21817
  // src/neuralMcpServer.ts
21542
21818
  var logger6 = createModuleLogger("neural.mcpServer");
21819
+ var NEURAL_DEDUP_WINDOW_MS = 3e4;
21820
+ var NEURAL_DEDUP_MAX_REPEATS = 2;
21543
21821
  function formatScopeLabel(key, groupName) {
21544
21822
  if (key === "single") return "\u5355\u804A";
21545
21823
  if (groupName) return `\u7FA4\u300C${groupName}\u300D`;
@@ -21556,6 +21834,67 @@ function filterContactsByOwner(all, ownerId) {
21556
21834
  (a) => a.kind === "system" || a.ownerId === ownerId || a.ownerId == null || a.kind === "human" && a.id === ownerId
21557
21835
  );
21558
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
+ }
21559
21898
  async function resolveTierSubscriptionPreference(preferredSubscriptionId, deps) {
21560
21899
  if (!preferredSubscriptionId || !isPrimaryCompanySubscriptionId(preferredSubscriptionId)) {
21561
21900
  return preferredSubscriptionId;
@@ -21567,7 +21906,9 @@ async function resolveTierSubscriptionPreference(preferredSubscriptionId, deps)
21567
21906
  headers: bridgeAuthHeaders(deps.bridgeToken ?? null)
21568
21907
  });
21569
21908
  if (!res.ok) return preferredSubscriptionId;
21570
- const subscriptions = await res.json();
21909
+ const body = await res.json();
21910
+ if (!Array.isArray(body)) return preferredSubscriptionId;
21911
+ const subscriptions = body;
21571
21912
  return subscriptions.find(
21572
21913
  (subscription) => subscription.billingMode === "company_billable" && subscription.isPrimaryCompany === true
21573
21914
  )?.id ?? preferredSubscriptionId;
@@ -21610,6 +21951,7 @@ async function createNeuralMcpServer(deps) {
21610
21951
  const currentScopeLabel = formatScopeLabel(currentScopeKey);
21611
21952
  let cachedScopes = null;
21612
21953
  const SCOPES_CACHE_MS = 3e4;
21954
+ const recentNeuralSends = /* @__PURE__ */ new Map();
21613
21955
  const neuralSend = sdk.tool(
21614
21956
  "neural_send",
21615
21957
  `\u628A\u4E00\u6BB5\u8BDD\u9001\u8FBE"\u4F60\u5728\u53E6\u4E00\u4E2A\u5BF9\u8BDD scope \u91CC\u7684\u5206\u8EAB"\u3002
@@ -21645,6 +21987,10 @@ async function createNeuralMcpServer(deps) {
21645
21987
  conversationId = singleConvId;
21646
21988
  } else {
21647
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
+ };
21648
21994
  }
21649
21995
  } else if (args.target_scope.startsWith("group:")) {
21650
21996
  const r = await deps.groupRegistry.resolveScope(args.target_scope);
@@ -21700,6 +22046,33 @@ async function createNeuralMcpServer(deps) {
21700
22046
  };
21701
22047
  }
21702
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
+ }
21703
22076
  try {
21704
22077
  await deps.onSend({
21705
22078
  fromScopeKey: currentScopeKey,
@@ -21711,6 +22084,8 @@ async function createNeuralMcpServer(deps) {
21711
22084
  groupId,
21712
22085
  targetCwd
21713
22086
  });
22087
+ sendHistory.push(nowTs);
22088
+ recentNeuralSends.set(dedupKey, sendHistory);
21714
22089
  logger6.info("neural_send delivered", {
21715
22090
  agentId: deps.agentId,
21716
22091
  fromScope: currentScopeKey,
@@ -22132,10 +22507,11 @@ ${result.warnings.map((warning) => `- ${warning}`).join("\n")}
22132
22507
  );
22133
22508
  const listContacts = deps.agentRegistry ? sdk.tool(
22134
22509
  "list_contacts",
22135
- `\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
22136
- \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
22137
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
22138
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
22139
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`,
22140
22516
  {
22141
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"),
@@ -22163,7 +22539,14 @@ ${result.warnings.map((warning) => `- ${warning}`).join("\n")}
22163
22539
  }
22164
22540
  const capped = args?.limit != null ? all.slice(0, args.limit) : all;
22165
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]));
22166
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
+ };
22167
22550
  const lines = [];
22168
22551
  let humanCount = 0;
22169
22552
  for (const a of capped) {
@@ -22171,22 +22554,35 @@ ${result.warnings.map((warning) => `- ${warning}`).join("\n")}
22171
22554
  if (isHuman) humanCount += 1;
22172
22555
  const kindMark = isHuman ? " (\u4EBA\u7C7B)" : "";
22173
22556
  const roleStr = a.role && a.role.length > 0 ? ` \u2014 ${a.role}` : "";
22557
+ const machineMark = isHuman ? "" : ` [\u673A\u5668: ${machineLabelForKey(a.machineBridgeKey)}]`;
22174
22558
  if (a.id === deps.agentId) {
22175
- 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)`);
22176
22560
  continue;
22177
22561
  }
22178
22562
  const shared = isHuman ? [] : sharedGroupsOf(a.id);
22179
22563
  const sharedMark = shared.length > 0 ? ` (\u5DF2\u4E0E\u4F60\u5171\u7FA4: ${shared.join("\u3001")})` : "";
22180
- lines.push(`- ${a.name} (${a.id})${roleStr}${kindMark}${sharedMark}`);
22564
+ lines.push(`- ${a.name} (${a.id})${roleStr}${kindMark}${machineMark}${sharedMark}`);
22181
22565
  }
22182
22566
  const total = all.length;
22183
22567
  const shown = capped.length;
22184
- 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`;
22185
- 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");
22186
22581
  logger6.info("list_contacts returned", {
22187
22582
  agentId: deps.agentId,
22188
22583
  count: all.length,
22189
- humanCount
22584
+ humanCount,
22585
+ machineCount: machines.length
22190
22586
  });
22191
22587
  return { content: [{ type: "text", text }] };
22192
22588
  },
@@ -22842,6 +23238,7 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
22842
23238
  `\u521B\u9020\u4E00\u4E2A\u65B0\u7684 Agent\u3002\u53EA\u6709\u4F60\uFF08Smith\uFF09\u80FD\u4F7F\u7528\u6B64\u5DE5\u5177\u3002
22843
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
22844
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
22845
23242
 
22846
23243
  **\u80FD\u529B\u6863\u4F4D\u9009\u62E9\u6307\u5357\uFF1A**
22847
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
@@ -22873,6 +23270,9 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
22873
23270
  ),
22874
23271
  working_directory: external_exports.string().optional().describe(
22875
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'
22876
23276
  )
22877
23277
  },
22878
23278
  async (args) => {
@@ -22902,15 +23302,28 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
22902
23302
  const avatar = args.avatar && String(args.avatar).trim() || "";
22903
23303
  const initialInstruction = args.initial_instruction && String(args.initial_instruction).trim() || "";
22904
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
+ }
22905
23318
  let agentConfig;
22906
23319
  if (deps.bridgeToken && deps.serverApiUrl) {
22907
23320
  try {
22908
- const tierUrl = `${deps.serverApiUrl.replace(/\/$/, "")}/api/onboarding/tiers?bridge_token=${encodeURIComponent(deps.bridgeToken)}`;
22909
- const tierRes = await fetch(tierUrl);
23321
+ const tierUrl = `${deps.serverApiUrl.replace(/\/$/, "")}/api/onboarding/tiers`;
23322
+ const tierRes = await fetch(tierUrl, { headers: bridgeAuthHeaders(deps.bridgeToken) });
22910
23323
  if (tierRes.ok) {
22911
23324
  const tiers = await tierRes.json();
22912
23325
  const self = deps.agentRegistry?.getById(deps.agentId);
22913
- const preferredSubscriptionId = parseAgentConfig(self?.config).subscriptionId;
23326
+ const preferredSubscriptionId = parseAgentConfig(self?.config).subscriptionId ?? PRIMARY_COMPANY_SUBSCRIPTION_ID;
22914
23327
  const resolvedPreferredSubscriptionId = await resolveTierSubscriptionPreference(
22915
23328
  preferredSubscriptionId,
22916
23329
  deps
@@ -22939,6 +23352,9 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
22939
23352
  if (workingDirectory) {
22940
23353
  body.workingDirectory = workingDirectory;
22941
23354
  }
23355
+ if (machineBridgeKey) {
23356
+ body.machineBridgeKey = machineBridgeKey;
23357
+ }
22942
23358
  logger6.info("create_agent tool called", {
22943
23359
  agentId: deps.agentId,
22944
23360
  requestedBy: deps.agentId,
@@ -22949,12 +23365,13 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
22949
23365
  avatar: avatar || "(default)",
22950
23366
  hasConfig: !!agentConfig,
22951
23367
  hasInitialInstruction: !!initialInstruction,
22952
- initialInstructionLen: initialInstruction.length
23368
+ initialInstructionLen: initialInstruction.length,
23369
+ machineBridgeKey: machineBridgeKey || "(current-bridge)"
22953
23370
  });
22954
23371
  try {
22955
23372
  const res = await fetch(`${deps.serverApiUrl.replace(/\/$/, "")}/api/agents`, {
22956
23373
  method: "POST",
22957
- headers: { "Content-Type": "application/json" },
23374
+ headers: { "Content-Type": "application/json", ...bridgeAuthHeaders(deps.bridgeToken ?? null) },
22958
23375
  body: JSON.stringify(body)
22959
23376
  });
22960
23377
  if (!res.ok) {
@@ -22969,6 +23386,7 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
22969
23386
  };
22970
23387
  }
22971
23388
  const agent = await res.json();
23389
+ const resolvedMachineBridgeKey = agent.machineBridgeKey ?? machineBridgeKey;
22972
23390
  logger6.info("create_agent: created", {
22973
23391
  requestedBy: deps.agentId,
22974
23392
  scope: currentScopeKey,
@@ -22976,7 +23394,8 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
22976
23394
  name: agent.name,
22977
23395
  tier,
22978
23396
  hasInitialInstruction: !!initialInstruction,
22979
- initialInstructionLen: initialInstruction.length
23397
+ initialInstructionLen: initialInstruction.length,
23398
+ machineBridgeKey: resolvedMachineBridgeKey || "(current-bridge)"
22980
23399
  });
22981
23400
  if (initialInstruction && deps.onAgentCreatedInitInstruction) {
22982
23401
  try {
@@ -22999,7 +23418,8 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
22999
23418
  });
23000
23419
  }
23001
23420
  }
23002
- 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`;
23003
23423
  return {
23004
23424
  content: [{ type: "text", text: reply }]
23005
23425
  };
@@ -23015,11 +23435,12 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
23015
23435
  ) : null;
23016
23436
  const updateAgentTool = deps.isSmith && deps.serverApiUrl ? sdk.tool(
23017
23437
  "update_agent_profile",
23018
- `\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
23019
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
23020
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
23021
23441
  **\u6CE8\u610F\u4E8B\u9879\uFF1A**
23022
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
23023
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
23024
23445
  - \u6539\u540D\u540E\u8BE5 Agent \u5728\u6240\u6709\u7FA4\u91CC\u7684\u663E\u793A\u540D\u4F1A\u540C\u6B65\u66F4\u65B0`,
23025
23446
  {
@@ -23030,6 +23451,9 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
23030
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"),
23031
23452
  avatar: external_exports.string().optional().describe(
23032
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'
23033
23457
  )
23034
23458
  },
23035
23459
  async (args) => {
@@ -23047,9 +23471,12 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
23047
23471
  };
23048
23472
  }
23049
23473
  const base = deps.serverApiUrl.replace(/\/$/, "");
23474
+ const authHeaders = bridgeAuthHeaders(deps.bridgeToken ?? null);
23050
23475
  let existing = null;
23051
23476
  try {
23052
- const getRes = await fetch(`${base}/api/agents/${encodeURIComponent(agentId)}`);
23477
+ const getRes = await fetch(`${base}/api/agents/${encodeURIComponent(agentId)}`, {
23478
+ headers: authHeaders
23479
+ });
23053
23480
  if (getRes.ok) {
23054
23481
  existing = await getRes.json();
23055
23482
  }
@@ -23061,15 +23488,34 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
23061
23488
  isError: true
23062
23489
  };
23063
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
+ }
23064
23510
  let agentConfig;
23065
23511
  const tier = (args.tier ?? "").trim();
23066
23512
  if (tier && deps.bridgeToken) {
23067
23513
  try {
23068
- const tierUrl = `${base}/api/onboarding/tiers?bridge_token=${encodeURIComponent(deps.bridgeToken)}`;
23069
- const tierRes = await fetch(tierUrl);
23514
+ const tierUrl = `${base}/api/onboarding/tiers`;
23515
+ const tierRes = await fetch(tierUrl, { headers: bridgeAuthHeaders(deps.bridgeToken) });
23070
23516
  if (tierRes.ok) {
23071
23517
  const tiers = await tierRes.json();
23072
- const preferredSubscriptionId = parseAgentConfig(existing.config).subscriptionId;
23518
+ const preferredSubscriptionId = parseAgentConfig(existing.config).subscriptionId ?? PRIMARY_COMPANY_SUBSCRIPTION_ID;
23073
23519
  const resolvedPreferredSubscriptionId = await resolveTierSubscriptionPreference(
23074
23520
  preferredSubscriptionId,
23075
23521
  deps
@@ -23094,9 +23540,13 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
23094
23540
  if (args.system_prompt !== void 0) patchBody.systemPrompt = String(args.system_prompt);
23095
23541
  if (args.avatar !== void 0) patchBody.avatar = String(args.avatar);
23096
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
+ }
23097
23547
  if (Object.keys(patchBody).length === 0) {
23098
23548
  return {
23099
- 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" }],
23100
23550
  isError: true
23101
23551
  };
23102
23552
  }
@@ -23109,7 +23559,7 @@ nextOffset=${json2.nextOffset}\uFF08\u7EE7\u7EED\u67E5\u8BE2\u65F6\u4F20 offset=
23109
23559
  try {
23110
23560
  const res = await fetch(`${base}/api/agents/${encodeURIComponent(agentId)}`, {
23111
23561
  method: "PUT",
23112
- headers: { "Content-Type": "application/json" },
23562
+ headers: { "Content-Type": "application/json", ...authHeaders },
23113
23563
  body: JSON.stringify(patchBody)
23114
23564
  });
23115
23565
  if (!res.ok) {
@@ -23704,6 +24154,24 @@ var GroupDispatchMemoryStore = class {
23704
24154
  }
23705
24155
  };
23706
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
+
23707
24175
  // src/groupInboxPromptBuilder.ts
23708
24176
  var MAX_SYSTEM_NOTE_COUNT = 8;
23709
24177
  var MAX_SYSTEM_NOTE_LEN = 1e3;
@@ -23730,7 +24198,7 @@ function collectSystemNotes(entries) {
23730
24198
  byId.set(msg.id, {
23731
24199
  id: msg.id,
23732
24200
  time: formatIsoHHMM(msg.createdAt),
23733
- content: truncate(msg.content, MAX_SYSTEM_NOTE_LEN)
24201
+ content: truncate(sanitizeModelText(msg.content), MAX_SYSTEM_NOTE_LEN)
23734
24202
  });
23735
24203
  }
23736
24204
  }
@@ -23751,7 +24219,7 @@ function appendSystemNotes(lines, entries) {
23751
24219
  function appendBoardContext(lines, latest) {
23752
24220
  if (latest.boardMeta?.vision) {
23753
24221
  lines.push("--- project vision ---");
23754
- lines.push(latest.boardMeta.vision);
24222
+ lines.push(sanitizeModelText(latest.boardMeta.vision));
23755
24223
  lines.push("--- end vision ---");
23756
24224
  lines.push("");
23757
24225
  }
@@ -23773,7 +24241,7 @@ function appendBoardContext(lines, latest) {
23773
24241
  lines.push("--- known issues (open) ---");
23774
24242
  lines.push("These issues have been recorded. Be aware of them and avoid repeating the same mistakes.");
23775
24243
  for (const iss of latest.boardIssues) {
23776
- lines.push(` - [${iss.category}] ${iss.title}${iss.description ? `: ${iss.description}` : ""}`);
24244
+ lines.push(` - [${iss.category}] ${sanitizeModelText(iss.title)}${iss.description ? `: ${sanitizeModelText(iss.description)}` : ""}`);
23777
24245
  }
23778
24246
  lines.push("--- end issues ---");
23779
24247
  lines.push("");
@@ -23783,7 +24251,7 @@ function appendBoardContext(lines, latest) {
23783
24251
  if (latest.boardItems && latest.boardItems.length > 0) {
23784
24252
  lines.push("Current items:");
23785
24253
  for (const bi of latest.boardItems) {
23786
- const parts = [`[${bi.status}] ${bi.content}`];
24254
+ const parts = [`[${bi.status}] ${sanitizeModelText(bi.content)}`];
23787
24255
  if (bi.priority !== "medium") parts.push(`priority:${bi.priority}`);
23788
24256
  if (bi.assignedAgentId) parts.push(`assigned:${bi.assignedAgentId}`);
23789
24257
  if (bi.deadline) parts.push(`deadline:${bi.deadline.slice(0, 10)}`);
@@ -23796,6 +24264,14 @@ function appendBoardContext(lines, latest) {
23796
24264
  lines.push("--- end board ---");
23797
24265
  lines.push("");
23798
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
+ }
23799
24275
  function buildGroupInboxPrompt(entries, opts = {}) {
23800
24276
  if (entries.length === 0) {
23801
24277
  return "";
@@ -23824,12 +24300,14 @@ function buildGroupInboxPrompt(entries, opts = {}) {
23824
24300
  if (e.replyToMessage) {
23825
24301
  const rts = e.replyToMessage.senderAgentName ?? (e.replyToMessage.role === "user" ? "user" : `agent:${e.replyToMessage.senderAgentId ?? "unknown"}`);
23826
24302
  lines.push(
23827
- `[${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)}"]:`
23828
24304
  );
23829
- lines.push(` ${e.content}`);
24305
+ lines.push(` ${sanitizeModelText(e.content)}`);
24306
+ appendAttachmentLines(lines, e.replyToMessage.attachments, "Replied message resource", " ");
23830
24307
  } else {
23831
- lines.push(`[${ts}] ${e.senderName}${mentionTag}: ${e.content}`);
24308
+ lines.push(`[${ts}] ${e.senderName}${mentionTag}: ${sanitizeModelText(e.content)}`);
23832
24309
  }
24310
+ appendAttachmentLines(lines, e.attachments, "Unread group message resource", " ");
23833
24311
  }
23834
24312
  lines.push("--- \u672A\u8BFB\u7FA4\u6D88\u606F end ---");
23835
24313
  lines.push("");
@@ -23852,6 +24330,9 @@ function isContextOverflowText(text) {
23852
24330
  function isAuthFailureText(text, sdkError) {
23853
24331
  return sdkError === "authentication_failed" || /not logged in|please run \/login/i.test(text);
23854
24332
  }
24333
+ function isProviderApiErrorText(text) {
24334
+ return /^API Error:\s*\d+/i.test(text.trim());
24335
+ }
23855
24336
  function decodeJsonStringFragment(raw) {
23856
24337
  let out = "";
23857
24338
  for (let i = 0; i < raw.length; i++) {
@@ -24375,7 +24856,7 @@ function flushTextSegmentOnBlockStop(proc, emit, base) {
24375
24856
  }
24376
24857
  proc.segmentBuffer = "";
24377
24858
  }
24378
- function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24859
+ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted, onProviderApiError) {
24379
24860
  const emit = rawEmit;
24380
24861
  proc.lastSdkEventAt = Date.now();
24381
24862
  switch (message.type) {
@@ -24789,6 +25270,12 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24789
25270
  if (groupMode) {
24790
25271
  checkInputTokenWatermark(proc, watermarkUsage, base.traceId);
24791
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
+ }
24792
25279
  if (proc.contentBlocks.length > 0) {
24793
25280
  logger8.info("Group turn trailing audit segment", {
24794
25281
  agentId: proc.agentId,
@@ -24926,6 +25413,9 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24926
25413
  if (isAuthFail) {
24927
25414
  sessionStore.delete(proc.agentId, proc.scope);
24928
25415
  }
25416
+ if (isProviderApiErrorText(errorText)) {
25417
+ onProviderApiError?.(errorText);
25418
+ }
24929
25419
  logger8.warn("SDK synthetic api error assistant detected, emitting agent:error", {
24930
25420
  agentId: proc.agentId,
24931
25421
  scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
@@ -24976,7 +25466,34 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
24976
25466
  proc.segmentBuffer = "";
24977
25467
  break;
24978
25468
  }
24979
- if (isContextOverflowText(text)) {
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
+ }
25496
+ if (isContextOverflowText(text)) {
24980
25497
  const base = getTaskBase(proc);
24981
25498
  logger8.warn("SDK reported context overflow; auto-compact already failed inside SDK", {
24982
25499
  agentId: proc.agentId,
@@ -25013,6 +25530,9 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
25013
25530
  break;
25014
25531
  }
25015
25532
  proc.accumulatedText = text;
25533
+ if (isGroupTask(proc)) {
25534
+ proc.segmentBuffer = text;
25535
+ }
25016
25536
  logger8.info("Captured non-streamed assistant message", {
25017
25537
  agentId: proc.agentId,
25018
25538
  scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
@@ -25204,7 +25724,7 @@ var wsMetrics = new WsMetrics();
25204
25724
  var logger11 = createModuleLogger("agent.manager");
25205
25725
  function missingSubscriptionMessage(subscriptionId) {
25206
25726
  if (isPrimaryCompanySubscriptionId(subscriptionId)) {
25207
- return "AHChat \u9ED8\u8BA4\u6A21\u578B\u6682\u65F6\u4E0D\u53EF\u7528\uFF1A\u8FD9\u53F0\u673A\u5668\u8FD8\u6CA1\u6709\u540C\u6B65\u5230\u7BA1\u7406\u5458\u542F\u7528\u7684\u9ED8\u8BA4\u6A21\u578B\uFF0C\u8BF7\u66F4\u65B0\u5E76\u91CD\u542F Bridge \u540E\u91CD\u8BD5\u3002";
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";
25208
25728
  }
25209
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`;
25210
25730
  }
@@ -25230,6 +25750,21 @@ function isRunningAsRoot() {
25230
25750
  return false;
25231
25751
  }
25232
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
+ }
25233
25768
  async function chownForRootSpawn(targetPath, target) {
25234
25769
  try {
25235
25770
  await fs5.chown(targetPath, NODE_USER_UID, NODE_USER_UID);
@@ -25278,18 +25813,26 @@ function senderLabelForQuote(message) {
25278
25813
  if (message.role === "agent") return `agent:${message.senderAgentId ?? "unknown"}`;
25279
25814
  return "system";
25280
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
+ }
25281
25822
  function buildSingleReplyPrompt(task) {
25282
- if (!task.replyToMessage) return task.content;
25823
+ if (!task.replyToMessage) return sanitizeModelText(task.content);
25283
25824
  const quoted = task.replyToMessage;
25284
25825
  const label = senderLabelForQuote(quoted);
25826
+ const quotedContent = sanitizeModelText(quoted.content || (quoted.attachments?.length ? "[attachment]" : ""));
25285
25827
  return [
25286
25828
  "--- reply-to message ---",
25287
25829
  `You are replying to this earlier message from [${label}]:`,
25288
- quoted.content || (quoted.attachments?.length ? "[attachment]" : ""),
25830
+ quotedContent,
25831
+ ...formatMessageAttachmentsForModel(quoted, "Replied message resource"),
25289
25832
  "--- end reply-to message ---",
25290
25833
  "",
25291
25834
  "--- user message ---",
25292
- task.content,
25835
+ sanitizeModelText(task.content),
25293
25836
  "--- end user message ---"
25294
25837
  ].join("\n");
25295
25838
  }
@@ -25320,6 +25863,8 @@ var AgentManager = class {
25320
25863
  subscriptionRegistry;
25321
25864
  serverApiUrl;
25322
25865
  bridgeToken;
25866
+ workdirOverrideStore;
25867
+ visionBlockedScopes = /* @__PURE__ */ new Set();
25323
25868
  evictionTimer = null;
25324
25869
  // Lazy-loaded SDK query function. Injectable via constructor for tests.
25325
25870
  queryFn = null;
@@ -25341,6 +25886,7 @@ var AgentManager = class {
25341
25886
  this.bridgeToken = null;
25342
25887
  this.defaultModel = null;
25343
25888
  this.dataDir = path11.join(os5.homedir(), ".ahchat");
25889
+ this.workdirOverrideStore = null;
25344
25890
  } else {
25345
25891
  this.queryFn = options?.queryFn ?? null;
25346
25892
  this.workspacesDir = options?.workspacesDir ?? path11.join(os5.homedir(), ".ahchat", "workspaces");
@@ -25356,6 +25902,7 @@ var AgentManager = class {
25356
25902
  this.bridgeToken = options?.bridgeToken ?? null;
25357
25903
  this.defaultModel = options?.defaultModel ?? null;
25358
25904
  this.dataDir = options?.dataDir ?? path11.join(os5.homedir(), ".ahchat");
25905
+ this.workdirOverrideStore = options?.workdirOverrideStore ?? null;
25359
25906
  }
25360
25907
  this.evictionTimer = setInterval(() => {
25361
25908
  void this.evictIdle();
@@ -25377,6 +25924,16 @@ var AgentManager = class {
25377
25924
  return path11.join(this.workspacesDir, suffix);
25378
25925
  }
25379
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
+ }
25380
25937
  const remapped = remapServerWorkspacePath(requestedCwd, this.workspacesDir);
25381
25938
  if (remapped.remapped) {
25382
25939
  logger11.info("Server working directory remapped to local Bridge workspace", {
@@ -25440,7 +25997,14 @@ var AgentManager = class {
25440
25997
  throw new Error(missingSubscriptionMessage(cfg.subscriptionId));
25441
25998
  }
25442
25999
  const isPinnedModel = cfg.model && cfg.model !== "default";
25443
- 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;
25444
26008
  const limits = resolveModelLimits(sub.customModels, resolvedModel);
25445
26009
  if (limits.maxInputTokens || limits.maxOutputTokens) {
25446
26010
  logger11.info("Resolved per-model token limits", {
@@ -25450,15 +26014,84 @@ var AgentManager = class {
25450
26014
  maxOutputTokens: limits.maxOutputTokens
25451
26015
  });
25452
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
+ }
25453
26039
  return {
25454
26040
  ...cfg,
25455
26041
  subscriptionType: sub.type,
25456
- apiKey: sub.apiKey,
25457
- apiBaseUrl: sub.apiBaseUrl,
26042
+ apiFormat,
26043
+ resolvedSubscriptionId,
26044
+ apiKey,
26045
+ apiBaseUrl,
25458
26046
  model: resolvedModel,
25459
26047
  ...limits
25460
26048
  };
25461
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
+ }
25462
26095
  /** Count live queries (anything not dead / removed). */
25463
26096
  countActiveQueries() {
25464
26097
  let n = 0;
@@ -25470,6 +26103,11 @@ var AgentManager = class {
25470
26103
  asRuntime(proc) {
25471
26104
  return proc;
25472
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
+ }
25473
26111
  async awaitQueryReturn(query4, timeoutMs, agentId) {
25474
26112
  const ret = query4.return(void 0);
25475
26113
  try {
@@ -25645,8 +26283,7 @@ var AgentManager = class {
25645
26283
  logger11.info("New API-key agent config dir; cleared stale session", { agentId: agentConfig.id });
25646
26284
  }
25647
26285
  const settingsPath = path11.join(effectiveConfigDir, "settings.json");
25648
- const envEntries = {};
25649
- if (cfg.apiKey) envEntries.ANTHROPIC_API_KEY = cfg.apiKey;
26286
+ const envEntries = buildAnthropicCredentialEnv(cfg);
25650
26287
  if (cfg.apiBaseUrl) envEntries.ANTHROPIC_BASE_URL = cfg.apiBaseUrl;
25651
26288
  let existingSettings = {};
25652
26289
  try {
@@ -25656,6 +26293,8 @@ var AgentManager = class {
25656
26293
  }
25657
26294
  const existingEnv = existingSettings.env ?? {};
25658
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;
25659
26298
  const mergedSettings = { ...existingSettings, env: mergedEnv };
25660
26299
  await fs5.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
25661
26300
  logger11.info("API-key agent using isolated config dir", {
@@ -25758,6 +26397,8 @@ var AgentManager = class {
25758
26397
  "Bash",
25759
26398
  "Glob",
25760
26399
  "Grep",
26400
+ "WebSearch",
26401
+ "WebFetch",
25761
26402
  "AskUserQuestion",
25762
26403
  "mcp__neural__neural_send",
25763
26404
  "mcp__neural__neural_list_scopes",
@@ -25773,6 +26414,7 @@ var AgentManager = class {
25773
26414
  "mcp__neural__read_document",
25774
26415
  ...isSmithAgent(agentConfig) ? [
25775
26416
  "mcp__neural__create_agent",
26417
+ "mcp__neural__update_agent_profile",
25776
26418
  "mcp__neural__list_friends",
25777
26419
  "mcp__neural__accept_friend",
25778
26420
  "mcp__neural__add_friend",
@@ -25787,7 +26429,7 @@ var AgentManager = class {
25787
26429
  // instructions as the workflow body (replacing the default code-implementation
25788
26430
  // phases). The SDK wraps it with read-only enforcement + ExitPlanMode protocol.
25789
26431
  planModeInstructions: (() => {
25790
- 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." : "";
25791
26433
  return `You are a PLANNER, NOT an executor. The user will execute your plan later.
25792
26434
 
25793
26435
  AVAILABLE TOOLS: Read, Glob, Grep, WebSearch, WebFetch, AskUserQuestion, Write (plan file only).${smithTools}
@@ -25906,14 +26548,18 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
25906
26548
  const isolated = cfg.subscriptionType === "project" && Boolean(cfg.apiKey ?? cfg.apiBaseUrl);
25907
26549
  const modelLimitEnv = buildModelLimitEnv(cfg);
25908
26550
  if (isolated) {
25909
- return {
26551
+ const credentialEnv = buildAnthropicCredentialEnv(cfg);
26552
+ const env3 = {
25910
26553
  ...process.env,
25911
26554
  CLAUDE_CONFIG_DIR: effectiveConfigDir,
25912
26555
  CLAUDE_CODE_SIMPLE: "0",
25913
- ...cfg.apiKey ? { ANTHROPIC_API_KEY: cfg.apiKey } : {},
25914
26556
  ...cfg.apiBaseUrl ? { ANTHROPIC_BASE_URL: cfg.apiBaseUrl } : {},
26557
+ ...credentialEnv,
25915
26558
  ...modelLimitEnv
25916
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;
25917
26563
  }
25918
26564
  const env2 = { ...process.env, ...modelLimitEnv };
25919
26565
  env2.CLAUDE_CODE_SIMPLE = "0";
@@ -25957,7 +26603,7 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
25957
26603
  return path11.join(effectiveConfigDir, "settings.json");
25958
26604
  })(),
25959
26605
  canUseTool: async (toolName, input) => {
25960
- if (toolName === "AskUserQuestion") {
26606
+ if (isAskUserQuestionToolName(toolName)) {
25961
26607
  return askGuard(input);
25962
26608
  }
25963
26609
  if (toolName === "CronCreate") {
@@ -26053,6 +26699,7 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
26053
26699
  prompt: inputController,
26054
26700
  options
26055
26701
  });
26702
+ const modelInputMode = this.modelInputModeForConfig(agentConfig.id, scope, cfg);
26056
26703
  const proc = {
26057
26704
  agentId: agentConfig.id,
26058
26705
  scope,
@@ -26084,8 +26731,18 @@ Do NOT use "..." as content \u2014 write specific, project-relevant content.`;
26084
26731
  mergedTasks: [],
26085
26732
  planModeBuffer: [],
26086
26733
  createdAt: Date.now(),
26734
+ supportsVision: modelInputMode === "vision" && cfg.supportsVision !== false,
26735
+ modelInputMode,
26087
26736
  quietFlushTimer: null
26088
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
+ });
26089
26746
  procRef = proc;
26090
26747
  this.agents.set(key, proc);
26091
26748
  const preserved = this.dormantGroupInboxes.get(key);
@@ -26549,6 +27206,19 @@ ${lines.join("\n")}`;
26549
27206
  traceId: latest.traceId,
26550
27207
  groupId: latest.groupId
26551
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
+ }
26552
27222
  if (includeBoard) {
26553
27223
  this.dispatchMemory.setBoardSignature(runtime.agentId, runtime.scope, boardSig);
26554
27224
  }
@@ -26574,7 +27244,13 @@ ${lines.join("\n")}`;
26574
27244
  runtime.cachedConversationId = task.conversationId;
26575
27245
  this.emit({
26576
27246
  type: "agent:status",
26577
- 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
+ }
26578
27254
  });
26579
27255
  logger11.info("dispatchToSDK emit agent:status thinking", {
26580
27256
  agentId: runtime.agentId,
@@ -26641,18 +27317,19 @@ ${lines.join("\n")}`;
26641
27317
  runtime.inputController.push(textContent, runtime.ccSessionId ?? "", onYielded);
26642
27318
  return;
26643
27319
  }
26644
- const supportsVision = await this.detectVisionSupport();
26645
27320
  const attachmentBlocks = await adaptAttachmentsForSDK(
26646
27321
  task.attachments ?? [],
26647
27322
  {
26648
27323
  fetchBuffer: this.fetchBufferFromUrl,
26649
27324
  materializeFile: (attachment, buffer) => this.materializeAttachment(runtime, attachment, buffer),
26650
- supportsVision
27325
+ modelInputMode: runtime.modelInputMode,
27326
+ supportsVision: runtime.supportsVision
26651
27327
  }
26652
27328
  );
26653
27329
  const text = textContent.trim() || "\u8BF7\u67E5\u770B\u6211\u53D1\u9001\u7684\u9644\u4EF6\u3002";
27330
+ const safeText = sanitizeModelText(text);
26654
27331
  const contentParts = [
26655
- { type: "text", text },
27332
+ { type: "text", text: safeText },
26656
27333
  ...attachmentBlocks
26657
27334
  ];
26658
27335
  runtime.inputController.push(contentParts, runtime.ccSessionId ?? "", onYielded);
@@ -26740,6 +27417,56 @@ ${lines.join("\n")}`;
26740
27417
  }
26741
27418
  return true;
26742
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
+ }
26743
27470
  emitTaskPushError(runtime, task, error51) {
26744
27471
  const errMsg = error51 instanceof Error ? error51.message : String(error51);
26745
27472
  logger11.error("Failed to push message to Agent", {
@@ -26780,6 +27507,10 @@ ${lines.join("\n")}`;
26780
27507
  onTaskCompleted(proc, carrierMessageId) {
26781
27508
  const runtime = this.asRuntime(proc);
26782
27509
  const completedTask = proc.currentTask;
27510
+ if (runtime.visionRecoveryPending) {
27511
+ this.closeRuntimeAfterVisionRecovery(runtime);
27512
+ return;
27513
+ }
26783
27514
  if (proc.compactInProgress) {
26784
27515
  proc.compactInProgress = false;
26785
27516
  this.dispatchMemory.reset(proc.agentId, proc.scope);
@@ -26791,7 +27522,13 @@ ${lines.join("\n")}`;
26791
27522
  });
26792
27523
  this.emit({
26793
27524
  type: "agent:status",
26794
- 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
+ }
26795
27532
  });
26796
27533
  }
26797
27534
  if (completedTask && runtime.mergedTasks.length > 0) {
@@ -26871,7 +27608,13 @@ ${lines.join("\n")}`;
26871
27608
  proc.currentTaskStartedAt = Date.now();
26872
27609
  this.emit({
26873
27610
  type: "agent:status",
26874
- 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
+ }
26875
27618
  });
26876
27619
  return;
26877
27620
  }
@@ -27030,6 +27773,15 @@ ${lines.join("\n")}`;
27030
27773
  });
27031
27774
  return;
27032
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
+ }
27033
27785
  const targetScope = payload.toScopeKey === "single" ? { kind: "single" } : { kind: "group", groupId: payload.groupId ?? payload.toScopeKey.replace("group:", "") };
27034
27786
  this.sessionStore.delete(agentConfig.id, targetScope);
27035
27787
  this.dispatchMemory.deleteScope(agentConfig.id, targetScope);
@@ -27041,9 +27793,9 @@ ${lines.join("\n")}`;
27041
27793
  const enveloped = buildInnerVoiceEnvelope(payloadWithTrigger, ctx);
27042
27794
  const task = {
27043
27795
  content: enveloped,
27044
- replyMessageId: `msg_nsend_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
27045
- conversationId: payload.conversationId ?? "",
27046
- traceId: `tr_nsend_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
27796
+ replyMessageId: createMessageId(),
27797
+ conversationId: payload.conversationId,
27798
+ traceId: createTraceId(),
27047
27799
  groupId: payload.groupId
27048
27800
  };
27049
27801
  const key = runtimeKey(agentConfig.id, targetScope);
@@ -27076,7 +27828,7 @@ ${lines.join("\n")}`;
27076
27828
  } else {
27077
27829
  const runtime = this.asRuntime(existingProc);
27078
27830
  runtime.cachedConversationId = task.conversationId;
27079
- runtime.inputController.push(task.content, runtime.ccSessionId ?? "");
27831
+ runtime.inputController.push(sanitizeModelText(task.content), runtime.ccSessionId ?? "");
27080
27832
  runtime.injectedTasks.push(task);
27081
27833
  logger11.info("Neural send injected mid-turn", {
27082
27834
  agentId: agentConfig.id,
@@ -27258,7 +28010,7 @@ ${lines.join("\n")}`;
27258
28010
  (k) => k === agentId || k.startsWith(`${agentId}::`)
27259
28011
  );
27260
28012
  if (keys.length === 0) {
27261
- logger11.warn("terminate: no process for agent", { agentId });
28013
+ logger11.info("terminate: no process for agent", { agentId });
27262
28014
  }
27263
28015
  for (const key of keys) {
27264
28016
  const proc = this.agents.get(key);
@@ -27385,6 +28137,11 @@ ${lines.join("\n")}`;
27385
28137
  for (const t of queued) {
27386
28138
  collectOrInterrupt(t);
27387
28139
  }
28140
+ const bufferedAtClose = [...runtime.planModeBuffer];
28141
+ runtime.planModeBuffer = [];
28142
+ for (const t of bufferedAtClose) {
28143
+ collectOrInterrupt(t);
28144
+ }
27388
28145
  const mergedAtClose = [...runtime.mergedTasks];
27389
28146
  runtime.mergedTasks = [];
27390
28147
  for (const t of mergedAtClose) {
@@ -27530,7 +28287,12 @@ ${lines.join("\n")}`;
27530
28287
  message,
27531
28288
  this.emit,
27532
28289
  this.sessionStore,
27533
- (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
+ }
27534
28296
  );
27535
28297
  if (wasPlanActive && !runtime.planModeActive) {
27536
28298
  if (runtime.planModeRef) runtime.planModeRef.active = false;
@@ -27548,13 +28310,18 @@ ${lines.join("\n")}`;
27548
28310
  } catch (err) {
27549
28311
  const errMsg = err.message ?? String(err);
27550
28312
  const isResumeFail = /session|conversation.*not found/i.test(errMsg);
28313
+ const isUnsupportedVisionInput = this.isUnsupportedVisionInputError(errMsg);
27551
28314
  logger11.error("Agent query stream ended with error", {
27552
28315
  agentId: runtime.agentId,
27553
28316
  scope: scopeKey(runtime.scope),
27554
28317
  isResumeFail,
28318
+ isUnsupportedVisionInput,
27555
28319
  staleSessionId: runtime.ccSessionId,
27556
28320
  error: err
27557
28321
  });
28322
+ if (isUnsupportedVisionInput) {
28323
+ this.visionBlockedScopes.add(runtimeKey(runtime.agentId, runtime.scope));
28324
+ }
27558
28325
  this.sessionStore.delete(runtime.agentId, runtime.scope);
27559
28326
  this.dispatchMemory.deleteScope(runtime.agentId, runtime.scope);
27560
28327
  logger11.info("Cleared stale session after query crash", {
@@ -27567,6 +28334,7 @@ ${lines.join("\n")}`;
27567
28334
  this.agents.delete(key);
27568
28335
  this.lastUsedAt.delete(key);
27569
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;
27570
28338
  if (runtime.currentTask) {
27571
28339
  this.emit({
27572
28340
  type: "agent:error",
@@ -27575,7 +28343,7 @@ ${lines.join("\n")}`;
27575
28343
  conversationId: runtime.currentTask.conversationId,
27576
28344
  ackId: runtime.currentTask.replyMessageId,
27577
28345
  traceId: runtime.currentTask.traceId,
27578
- error: errorText
28346
+ error: emittedErrorText
27579
28347
  }
27580
28348
  });
27581
28349
  runtime.currentTask = null;
@@ -27588,7 +28356,7 @@ ${lines.join("\n")}`;
27588
28356
  conversationId: task.conversationId,
27589
28357
  ackId: task.replyMessageId,
27590
28358
  traceId: task.traceId,
27591
- error: errorText
28359
+ error: emittedErrorText
27592
28360
  }
27593
28361
  });
27594
28362
  }
@@ -27601,11 +28369,24 @@ ${lines.join("\n")}`;
27601
28369
  conversationId: task.conversationId,
27602
28370
  ackId: task.replyMessageId,
27603
28371
  traceId: task.traceId,
27604
- error: errorText
28372
+ error: emittedErrorText
27605
28373
  }
27606
28374
  });
27607
28375
  }
27608
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 = [];
27609
28390
  }
27610
28391
  }
27611
28392
  getStatus(agentId, scope = { kind: "single" }) {
@@ -27783,6 +28564,26 @@ ${lines.join("\n")}`;
27783
28564
  traceId: t.traceId
27784
28565
  });
27785
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
+ }
27786
28587
  runtime.currentTask = null;
27787
28588
  this.clearQuietFlushTimer(runtime);
27788
28589
  proc.status = "dead";
@@ -27938,7 +28739,7 @@ function installBridgeFetchAuth(serverApiUrl, token) {
27938
28739
  });
27939
28740
  logger12.info("Bridge fetch auth installed", {
27940
28741
  serverApiUrl: base,
27941
- tokenPrefix: `${token.slice(0, 8)}...`
28742
+ hasToken: Boolean(token)
27942
28743
  });
27943
28744
  }
27944
28745
 
@@ -27954,8 +28755,8 @@ var HttpAgentRegistry = class {
27954
28755
  agents = /* @__PURE__ */ new Map();
27955
28756
  apiUrl(suffix) {
27956
28757
  const base = this.serverApiUrl.replace(/\/$/, "");
27957
- const path24 = suffix.startsWith("/") ? suffix : `/${suffix}`;
27958
- return `${base}${path24}`;
28758
+ const path26 = suffix.startsWith("/") ? suffix : `/${suffix}`;
28759
+ return `${base}${path26}`;
27959
28760
  }
27960
28761
  async refresh() {
27961
28762
  const attempt = async () => {
@@ -28052,17 +28853,8 @@ var HttpSubscriptionRegistry = class {
28052
28853
  subscriptions = /* @__PURE__ */ new Map();
28053
28854
  apiUrl(suffix) {
28054
28855
  const base = this.serverApiUrl.replace(/\/$/, "");
28055
- const path24 = suffix.startsWith("/") ? suffix : `/${suffix}`;
28056
- return `${base}${path24}`;
28057
- }
28058
- primaryAliasFrom(sub) {
28059
- return {
28060
- ...sub,
28061
- id: PRIMARY_COMPANY_SUBSCRIPTION_ID,
28062
- name: "\u516C\u53F8\u4E3B\u8BA2\u9605",
28063
- resolvedSubscriptionId: sub.id,
28064
- isVirtual: true
28065
- };
28856
+ const path26 = suffix.startsWith("/") ? suffix : `/${suffix}`;
28857
+ return `${base}${path26}`;
28066
28858
  }
28067
28859
  rebuildPrimaryAlias() {
28068
28860
  this.subscriptions.delete(PRIMARY_COMPANY_SUBSCRIPTION_ID);
@@ -28070,7 +28862,7 @@ var HttpSubscriptionRegistry = class {
28070
28862
  (sub) => sub.billingMode === "company_billable" && sub.isPrimaryCompany === true
28071
28863
  );
28072
28864
  if (!primary) return;
28073
- this.subscriptions.set(PRIMARY_COMPANY_SUBSCRIPTION_ID, this.primaryAliasFrom(primary));
28865
+ this.subscriptions.set(PRIMARY_COMPANY_SUBSCRIPTION_ID, makePrimaryCompanySubscriptionAlias(primary));
28074
28866
  }
28075
28867
  async refresh() {
28076
28868
  const attempt = async () => {
@@ -28348,6 +29140,13 @@ var wrapper_default = import_websocket.default;
28348
29140
 
28349
29141
  // src/connector.ts
28350
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
+ }
28351
29150
  var ServerConnector = class {
28352
29151
  ws = null;
28353
29152
  reconnectAttempts = 0;
@@ -28372,13 +29171,12 @@ var ServerConnector = class {
28372
29171
  }
28373
29172
  connect() {
28374
29173
  if (this.closing) return;
28375
- const url2 = new URL(this.config.serverUrl);
28376
- if (this.config.bridgeToken) {
28377
- url2.searchParams.set("token", this.config.bridgeToken);
28378
- }
28379
- const wsUrl = url2.toString();
28380
- logger16.info("Connecting to server", { url: wsUrl });
28381
- 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 });
28382
29180
  ws.on("open", () => {
28383
29181
  this.ws = ws;
28384
29182
  this.reconnectAttempts = 0;
@@ -28602,9 +29400,213 @@ ${s.slice(-TRUNCATE_TAIL)}`;
28602
29400
  function cwdToProjectSlug(cwd) {
28603
29401
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
28604
29402
  }
28605
- 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) {
28606
29408
  const slug = cwdToProjectSlug(cwd);
28607
- 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}`);
28608
29610
  }
28609
29611
  var RENDERABLE_TYPES = /* @__PURE__ */ new Set(["user", "assistant", "system", "attachment"]);
28610
29612
  async function readJsonlEntries(filePath) {
@@ -28852,6 +29854,100 @@ function resolveScopeCwd(agentWorkDir, scopeKey2, groupRegistry) {
28852
29854
  }
28853
29855
  return agentWorkDir;
28854
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
+ }
28855
29951
  async function dumpAgentContext(agentId, deps) {
28856
29952
  const agent = deps.agentRegistry.getById(agentId);
28857
29953
  if (!agent) {
@@ -28864,14 +29960,33 @@ async function dumpAgentContext(agentId, deps) {
28864
29960
  if (!workdir) {
28865
29961
  return { ok: false, files: [], scopeErrors: [], error: "agent has no working directory" };
28866
29962
  }
28867
- 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");
28868
29970
  await fs6.mkdir(dumpDir, { recursive: true });
29971
+ const projectsDirs = claudeProjectsDirs({ agentId, agentConfigDir: deps.agentConfigDir });
28869
29972
  const prefix = `${agentId}::`;
28870
29973
  const scopeEntries = [];
28871
29974
  for (const [key, sessionId] of deps.sessionStore.getAll()) {
28872
29975
  if (!key.startsWith(prefix)) continue;
28873
29976
  scopeEntries.push({ scopeKey: key.slice(prefix.length), sessionId });
28874
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
+ }));
28875
29990
  logger17.info("Agent context dump started", {
28876
29991
  agentId,
28877
29992
  agentName: agent.name,
@@ -28896,26 +30011,61 @@ async function dumpAgentContext(agentId, deps) {
28896
30011
  error: infoErr
28897
30012
  });
28898
30013
  }
28899
- const scopeCwd = sessionInfo?.cwd || resolveScopeCwd(workdir, scopeKey2, deps.groupRegistry);
28900
- const jsonlPath = resolveJsonlPath(sessionId, scopeCwd);
28901
- let entries;
28902
- try {
28903
- entries = await readJsonlEntries(jsonlPath);
28904
- } catch (readErr) {
28905
- 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", {
28906
30055
  agentId,
28907
30056
  scopeKey: scopeKey2,
28908
- 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,
28909
30066
  jsonlPath,
28910
- error: readErr instanceof Error ? readErr.message : String(readErr)
30067
+ checkedPathCount: checkedPaths.length
28911
30068
  });
28912
- const altCwd = resolveScopeCwd(workdir, scopeKey2, deps.groupRegistry);
28913
- if (altCwd !== scopeCwd) {
28914
- const altPath = resolveJsonlPath(sessionId, altCwd);
28915
- entries = await readJsonlEntries(altPath);
28916
- } else {
28917
- throw readErr;
28918
- }
28919
30069
  }
28920
30070
  let groupName;
28921
30071
  if (scopeKey2.startsWith("group:")) {
@@ -28926,7 +30076,7 @@ async function dumpAgentContext(agentId, deps) {
28926
30076
  agentName: agent.name,
28927
30077
  agentId,
28928
30078
  scopeKey: scopeKey2,
28929
- sessionId,
30079
+ sessionId: resolvedSessionId,
28930
30080
  systemPrompt: agent.systemPrompt,
28931
30081
  sessionInfo,
28932
30082
  entries,
@@ -28941,7 +30091,7 @@ async function dumpAgentContext(agentId, deps) {
28941
30091
  logger17.info("Scope context dumped", {
28942
30092
  agentId,
28943
30093
  scopeKey: scopeKey2,
28944
- sessionId,
30094
+ sessionId: resolvedSessionId,
28945
30095
  filename,
28946
30096
  filePath,
28947
30097
  bytesWritten: stat3.size,
@@ -28967,6 +30117,7 @@ async function dumpAgentContext(agentId, deps) {
28967
30117
  return {
28968
30118
  ok,
28969
30119
  dir: dumpDir,
30120
+ localDir: dumpDir,
28970
30121
  files: dumpedFiles,
28971
30122
  scopeErrors,
28972
30123
  error: ok ? void 0 : "no files written"
@@ -29080,7 +30231,8 @@ function listLogFiles(logsDir, baseName) {
29080
30231
  logger19.warn("listLogFiles: readdir failed", { logsDir, error: e });
29081
30232
  return [];
29082
30233
  }
29083
- const pattern = new RegExp(`^${baseName.replace(".", "\\.")}(\\.\\d+)?$`);
30234
+ const escapedBaseName = baseName.replace(".", "\\.");
30235
+ const pattern = new RegExp(`^${escapedBaseName}(?:[.-].+)?$`);
29084
30236
  return names.filter((n) => pattern.test(n)).map((n) => path14.join(logsDir, n));
29085
30237
  }
29086
30238
  async function scanFile(filePath, source, filter, state) {
@@ -29099,7 +30251,7 @@ async function scanFile(filePath, source, filter, state) {
29099
30251
  }
29100
30252
  }
29101
30253
  async function scanLocalLogs(logsDir, baseName, filter) {
29102
- const source = filter.source;
30254
+ const source = "bridge";
29103
30255
  const limit = filter.limit == null ? null : Math.min(Math.max(filter.limit, 1), MAX_LIMIT);
29104
30256
  const offset = Math.max(filter.offset ?? 0, 0);
29105
30257
  const files = listLogFiles(logsDir, baseName);
@@ -29128,7 +30280,7 @@ async function scanLocalLogs(logsDir, baseName, filter) {
29128
30280
  };
29129
30281
  }
29130
30282
  async function scanBridgeLogs(filter) {
29131
- const logDir = path14.join(os8.homedir(), ".ahchat", "logs");
30283
+ const logDir = path14.join(loadBridgeConfig().dataDir || path14.join(os8.homedir(), ".ahchat"), "logs");
29132
30284
  logger19.info("scanBridgeLogs start", {
29133
30285
  logDir,
29134
30286
  startIso: filter.startIso,
@@ -29146,30 +30298,287 @@ async function scanBridgeLogs(filter) {
29146
30298
  return result;
29147
30299
  }
29148
30300
 
29149
- // src/skillStore.ts
30301
+ // src/logUploader.ts
29150
30302
  import fs9 from "fs";
30303
+ import fsp from "fs/promises";
29151
30304
  import path15 from "path";
29152
- 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");
29153
30562
  var ALLOWED_NAMES = /* @__PURE__ */ new Set(["log-analysis"]);
29154
30563
  var SkillStore = class {
29155
30564
  skillsDir;
29156
30565
  constructor(dataDir) {
29157
- this.skillsDir = path15.join(dataDir, "skills");
29158
- fs9.mkdirSync(this.skillsDir, { recursive: true });
29159
- 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 });
29160
30569
  }
29161
30570
  read(name) {
29162
30571
  if (!ALLOWED_NAMES.has(name)) {
29163
- logger20.warn("Skill read: unknown name", { name, allowed: [...ALLOWED_NAMES] });
30572
+ logger21.warn("Skill read: unknown name", { name, allowed: [...ALLOWED_NAMES] });
29164
30573
  return "";
29165
30574
  }
29166
- const filePath = path15.join(this.skillsDir, `${name}.md`);
30575
+ const filePath = path16.join(this.skillsDir, `${name}.md`);
29167
30576
  try {
29168
- const content = fs9.readFileSync(filePath, "utf-8");
29169
- 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 });
29170
30579
  return content;
29171
30580
  } catch (e) {
29172
- logger20.warn("Skill read failed", { name, filePath, error: e });
30581
+ logger21.warn("Skill read failed", { name, filePath, error: e });
29173
30582
  return "";
29174
30583
  }
29175
30584
  }
@@ -29178,20 +30587,20 @@ var SkillStore = class {
29178
30587
  if (!ALLOWED_NAMES.has(name)) {
29179
30588
  throw new Error(`Unknown skill name: ${name}`);
29180
30589
  }
29181
- const filePath = path15.join(this.skillsDir, `${name}.md`);
30590
+ const filePath = path16.join(this.skillsDir, `${name}.md`);
29182
30591
  const tmpPath = `${filePath}.tmp`;
29183
30592
  let existing = "";
29184
30593
  try {
29185
- existing = fs9.readFileSync(filePath, "utf-8");
30594
+ existing = fs10.readFileSync(filePath, "utf-8");
29186
30595
  } catch {
29187
30596
  }
29188
30597
  if (existing === content) {
29189
- logger20.info("Skill already in sync", { name, bytes: content.length });
30598
+ logger21.info("Skill already in sync", { name, bytes: content.length });
29190
30599
  return;
29191
30600
  }
29192
- fs9.writeFileSync(tmpPath, content, "utf-8");
29193
- fs9.renameSync(tmpPath, filePath);
29194
- 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", {
29195
30604
  name,
29196
30605
  bytes: content.length,
29197
30606
  hadExisting: existing.length > 0
@@ -29200,11 +30609,29 @@ var SkillStore = class {
29200
30609
  };
29201
30610
 
29202
30611
  // src/lockfile.ts
29203
- import fs10 from "fs";
29204
- import path16 from "path";
29205
- 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");
29206
30616
  var lockPath = null;
29207
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
+ }
29208
30635
  function isProcessAlive(pid) {
29209
30636
  try {
29210
30637
  process.kill(pid, 0);
@@ -29213,28 +30640,94 @@ function isProcessAlive(pid) {
29213
30640
  const err = e;
29214
30641
  if (err.code === "ESRCH") return false;
29215
30642
  if (err.code === "EPERM") {
29216
- logger21.warn("Treating inaccessible lock PID as stale", { pid, error: e });
30643
+ logger22.warn("Treating inaccessible lock PID as stale", { pid, error: e });
29217
30644
  return false;
29218
30645
  }
29219
30646
  throw e;
29220
30647
  }
29221
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
+ }
29222
30715
  function acquireLock(dataDir) {
29223
- const file2 = path16.join(dataDir, "bridge.lock");
30716
+ const file2 = path17.join(dataDir, "bridge.lock");
29224
30717
  lockPath = file2;
29225
- if (fs10.existsSync(file2)) {
29226
- const raw = fs10.readFileSync(file2, "utf-8").trim();
30718
+ if (fs11.existsSync(file2)) {
30719
+ const raw = fs11.readFileSync(file2, "utf-8").trim();
29227
30720
  const pid = Number.parseInt(raw, 10);
29228
30721
  if (Number.isFinite(pid) && pid > 0) {
29229
- if (isProcessAlive(pid)) {
29230
- throw new Error(`Bridge already running (PID: ${pid})`);
30722
+ if (isLiveBridgeLockOwner(pid)) {
30723
+ throw new BridgeAlreadyRunningError(pid, dataDir);
29231
30724
  }
29232
- logger21.warn("Removing stale bridge.lock (process not found)", { pid, path: file2 });
30725
+ logger22.info("Removing stale bridge.lock", { pid, path: file2 });
29233
30726
  }
29234
30727
  }
29235
- fs10.mkdirSync(path16.dirname(file2), { recursive: true });
29236
- fs10.writeFileSync(file2, String(process.pid), "utf-8");
29237
- 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 });
29238
30731
  if (!releaseRegistered) {
29239
30732
  releaseRegistered = true;
29240
30733
  process.on("exit", releaseLock);
@@ -29242,20 +30735,46 @@ function acquireLock(dataDir) {
29242
30735
  }
29243
30736
  function releaseLock() {
29244
30737
  try {
29245
- if (lockPath && fs10.existsSync(lockPath)) {
29246
- const current = fs10.readFileSync(lockPath, "utf-8").trim();
30738
+ if (lockPath && fs11.existsSync(lockPath)) {
30739
+ const current = fs11.readFileSync(lockPath, "utf-8").trim();
29247
30740
  if (current === String(process.pid)) {
29248
- fs10.unlinkSync(lockPath);
29249
- logger21.info("Released bridge lock", { path: lockPath });
30741
+ fs11.unlinkSync(lockPath);
30742
+ logger22.info("Released bridge lock", { path: lockPath });
29250
30743
  }
29251
30744
  }
29252
30745
  } catch (e) {
29253
- logger21.error("Failed to release bridge lock", { error: e, path: lockPath });
30746
+ logger22.error("Failed to release bridge lock", { error: e, path: lockPath });
29254
30747
  } finally {
29255
30748
  lockPath = null;
29256
30749
  }
29257
30750
  }
29258
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
+
29259
30778
  // src/groupInbox.ts
29260
30779
  function groupInboxEntryFromDispatch(payload) {
29261
30780
  return {
@@ -29286,9 +30805,9 @@ function groupInboxEntryFromDispatch(payload) {
29286
30805
  }
29287
30806
 
29288
30807
  // src/messageHandler.ts
29289
- var logger22 = createModuleLogger("msg.handler");
30808
+ var logger24 = createModuleLogger("msg.handler");
29290
30809
  function emitTaskAck(emit, ackId, agentId, traceId) {
29291
- logger22.info("Emitting task:ack", { ackId, agentId, traceId });
30810
+ logger24.info("Emitting task:ack", { ackId, agentId, traceId });
29292
30811
  emit({
29293
30812
  type: "task:ack",
29294
30813
  payload: {
@@ -29301,7 +30820,7 @@ function emitTaskAck(emit, ackId, agentId, traceId) {
29301
30820
  }
29302
30821
  function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
29303
30822
  return async (payload) => {
29304
- logger22.info("Handling task:dispatch", {
30823
+ logger24.info("Handling task:dispatch", {
29305
30824
  agentId: payload.agentId,
29306
30825
  messageId: payload.messageId,
29307
30826
  ackId: payload.ackId,
@@ -29310,14 +30829,14 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
29310
30829
  emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
29311
30830
  let agentConfig = agentRegistry.getById(payload.agentId);
29312
30831
  if (!agentConfig) {
29313
- logger22.warn("Agent not in registry, attempting live fetch", {
30832
+ logger24.warn("Agent not in registry, attempting live fetch", {
29314
30833
  agentId: payload.agentId,
29315
30834
  traceId: payload.traceId
29316
30835
  });
29317
30836
  agentConfig = await agentRegistry.fetchById(payload.agentId);
29318
30837
  }
29319
30838
  if (!agentConfig) {
29320
- logger22.error("Agent not found for task:dispatch (after live fetch)", {
30839
+ logger24.error("Agent not found for task:dispatch (after live fetch)", {
29321
30840
  agentId: payload.agentId,
29322
30841
  traceId: payload.traceId
29323
30842
  });
@@ -29347,7 +30866,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
29347
30866
  planMode: payload.planMode ?? void 0
29348
30867
  });
29349
30868
  } catch (err) {
29350
- logger22.error("Failed to dispatch message to Agent", {
30869
+ logger24.error("Failed to dispatch message to Agent", {
29351
30870
  error: err,
29352
30871
  agentId: payload.agentId,
29353
30872
  traceId: payload.traceId
@@ -29367,7 +30886,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
29367
30886
  }
29368
30887
  function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
29369
30888
  return async (payload) => {
29370
- logger22.info("Handling task:group_dispatch", {
30889
+ logger24.info("Handling task:group_dispatch", {
29371
30890
  agentId: payload.agentId,
29372
30891
  groupId: payload.groupId,
29373
30892
  ackId: payload.ackId,
@@ -29379,14 +30898,14 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
29379
30898
  emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
29380
30899
  let agentConfig = agentRegistry.getById(payload.agentId);
29381
30900
  if (!agentConfig) {
29382
- 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", {
29383
30902
  agentId: payload.agentId,
29384
30903
  traceId: payload.traceId
29385
30904
  });
29386
30905
  agentConfig = await agentRegistry.fetchById(payload.agentId);
29387
30906
  }
29388
30907
  if (!agentConfig) {
29389
- 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)", {
29390
30909
  agentId: payload.agentId,
29391
30910
  traceId: payload.traceId
29392
30911
  });
@@ -29411,7 +30930,7 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
29411
30930
  );
29412
30931
  const entry = groupInboxEntryFromDispatch(payload);
29413
30932
  if (payload.isMentioned) {
29414
- logger22.info("Group dispatch routed to pull inbox", {
30933
+ logger24.info("Group dispatch routed to pull inbox", {
29415
30934
  route: "mention_flush",
29416
30935
  agentId: payload.agentId,
29417
30936
  groupId: payload.groupId,
@@ -29420,7 +30939,7 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
29420
30939
  });
29421
30940
  await agentManager.flushInboxAndDispatch(payload.agentId, groupScope, entry);
29422
30941
  } else {
29423
- logger22.info("Group dispatch routed to pull inbox", {
30942
+ logger24.info("Group dispatch routed to pull inbox", {
29424
30943
  route: "buffer",
29425
30944
  agentId: payload.agentId,
29426
30945
  groupId: payload.groupId,
@@ -29430,7 +30949,7 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
29430
30949
  agentManager.enqueueGroupMessage(payload.agentId, groupScope, entry);
29431
30950
  }
29432
30951
  } catch (err) {
29433
- logger22.error("Failed to dispatch group message to Agent", {
30952
+ logger24.error("Failed to dispatch group message to Agent", {
29434
30953
  error: err,
29435
30954
  agentId: payload.agentId,
29436
30955
  groupId: payload.groupId,
@@ -29451,7 +30970,7 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
29451
30970
  }
29452
30971
 
29453
30972
  // src/scopePushNotify.ts
29454
- var logger23 = createModuleLogger("bridge");
30973
+ var logger25 = createModuleLogger("bridge");
29455
30974
  function buildMemberChangedScopeNotice(params) {
29456
30975
  const { groupId, groupLabel, action, kickerName } = params;
29457
30976
  if (action === "removed" && kickerName) {
@@ -29500,14 +31019,14 @@ function buildGroupRenamedScopeNotice(params) {
29500
31019
  }
29501
31020
  async function handleGroupMemberChangedPush(deps, payload) {
29502
31021
  const { groupId, action, agentId, kickerAgentId } = payload;
29503
- logger23.info("group:member_changed received, refreshing GroupRegistry", {
31022
+ logger25.info("group:member_changed received, refreshing GroupRegistry", {
29504
31023
  groupId,
29505
31024
  action,
29506
31025
  agentId,
29507
31026
  kickerAgentId: kickerAgentId ?? null
29508
31027
  });
29509
31028
  await deps.groupRegistry.refresh();
29510
- logger23.info("GroupRegistry refreshed after member_changed", {
31029
+ logger25.info("GroupRegistry refreshed after member_changed", {
29511
31030
  groupId,
29512
31031
  action,
29513
31032
  registryGroupCount: deps.groupRegistry.getAll().length
@@ -29529,7 +31048,7 @@ async function handleGroupMemberChangedPush(deps, payload) {
29529
31048
  await deps.agentManager.broadcastScopeNotice(mid, notice2);
29530
31049
  notifiedCount++;
29531
31050
  }
29532
- logger23.info("Scope notice sent (human membership)", {
31051
+ logger25.info("Scope notice sent (human membership)", {
29533
31052
  groupId,
29534
31053
  groupLabel,
29535
31054
  action,
@@ -29541,7 +31060,7 @@ async function handleGroupMemberChangedPush(deps, payload) {
29541
31060
  }
29542
31061
  const notice = buildMemberChangedScopeNotice({ groupId, groupLabel, action, kickerName });
29543
31062
  await deps.agentManager.broadcastScopeNotice(agentId, notice);
29544
- logger23.info("Scope notice sent (agent membership)", {
31063
+ logger25.info("Scope notice sent (agent membership)", {
29545
31064
  groupId,
29546
31065
  groupLabel,
29547
31066
  action,
@@ -29553,13 +31072,13 @@ async function handleGroupMemberChangedPush(deps, payload) {
29553
31072
  }
29554
31073
  async function handleGroupUpdatedPush(deps, payload) {
29555
31074
  const { groupId, name: newName, memberAgentIds } = payload;
29556
- logger23.info("group:updated received, refreshing GroupRegistry", {
31075
+ logger25.info("group:updated received, refreshing GroupRegistry", {
29557
31076
  groupId,
29558
31077
  newName,
29559
31078
  memberCount: memberAgentIds.length
29560
31079
  });
29561
31080
  await deps.groupRegistry.refresh();
29562
- logger23.info("GroupRegistry refreshed after group:updated", {
31081
+ logger25.info("GroupRegistry refreshed after group:updated", {
29563
31082
  groupId,
29564
31083
  newName,
29565
31084
  registryGroupCount: deps.groupRegistry.getAll().length
@@ -29568,7 +31087,7 @@ async function handleGroupUpdatedPush(deps, payload) {
29568
31087
  for (const aid of memberAgentIds) {
29569
31088
  await deps.agentManager.broadcastScopeNotice(aid, notice);
29570
31089
  }
29571
- logger23.info("Scope notices sent for group:updated", {
31090
+ logger25.info("Scope notices sent for group:updated", {
29572
31091
  groupId,
29573
31092
  newName,
29574
31093
  memberCount: memberAgentIds.length,
@@ -29577,28 +31096,28 @@ async function handleGroupUpdatedPush(deps, payload) {
29577
31096
  }
29578
31097
  async function handleGroupArchivedPush(deps, payload) {
29579
31098
  const { groupId } = payload;
29580
- logger23.info("group:archived received, terminating group scope sessions", { groupId });
31099
+ logger25.info("group:archived received, terminating group scope sessions", { groupId });
29581
31100
  const groupBefore = deps.groupRegistry.getById(groupId);
29582
31101
  const memberIds = groupBefore?.members ?? [];
29583
31102
  await deps.groupRegistry.refresh();
29584
31103
  for (const memberId of memberIds) {
29585
31104
  await deps.agentManager.terminateScope(memberId, { kind: "group", groupId });
29586
31105
  }
29587
- logger23.info("group:archived: scopes terminated", {
31106
+ logger25.info("group:archived: scopes terminated", {
29588
31107
  groupId,
29589
31108
  terminatedCount: memberIds.length
29590
31109
  });
29591
31110
  }
29592
31111
 
29593
31112
  // src/sessionStore.ts
29594
- import fs11 from "fs";
29595
- import path17 from "path";
29596
- var logger24 = createModuleLogger("session.store");
31113
+ import fs13 from "fs";
31114
+ import path19 from "path";
31115
+ var logger26 = createModuleLogger("session.store");
29597
31116
  var SessionStore = class {
29598
31117
  filePath;
29599
31118
  cache;
29600
31119
  constructor(dataDir) {
29601
- this.filePath = path17.join(dataDir, "sessions.json");
31120
+ this.filePath = path19.join(dataDir, "sessions.json");
29602
31121
  this.cache = this.loadFromDisk();
29603
31122
  }
29604
31123
  cacheKey(agentId, scope) {
@@ -29633,8 +31152,8 @@ var SessionStore = class {
29633
31152
  }
29634
31153
  loadFromDisk() {
29635
31154
  try {
29636
- if (!fs11.existsSync(this.filePath)) return {};
29637
- const raw = fs11.readFileSync(this.filePath, "utf-8");
31155
+ if (!fs13.existsSync(this.filePath)) return {};
31156
+ const raw = fs13.readFileSync(this.filePath, "utf-8");
29638
31157
  const parsed = JSON.parse(raw);
29639
31158
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
29640
31159
  const map2 = parsed;
@@ -29644,7 +31163,7 @@ var SessionStore = class {
29644
31163
  migrated[key] = sessionId;
29645
31164
  } else {
29646
31165
  migrated[`${key}::single`] = sessionId;
29647
- logger24.info("Migrated legacy session key to scoped key", {
31166
+ logger26.info("Migrated legacy session key to scoped key", {
29648
31167
  legacyKey: key,
29649
31168
  newKey: `${key}::single`
29650
31169
  });
@@ -29652,17 +31171,17 @@ var SessionStore = class {
29652
31171
  }
29653
31172
  return migrated;
29654
31173
  } catch (e) {
29655
- 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 });
29656
31175
  return {};
29657
31176
  }
29658
31177
  }
29659
31178
  saveToDisk() {
29660
31179
  try {
29661
- const dir = path17.dirname(this.filePath);
29662
- fs11.mkdirSync(dir, { recursive: true });
29663
- 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");
29664
31183
  } catch (e) {
29665
- 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 });
29666
31185
  }
29667
31186
  }
29668
31187
  };
@@ -29670,8 +31189,8 @@ var SessionStore = class {
29670
31189
  // src/claudeRuntime.ts
29671
31190
  import { accessSync as accessSync2, constants as constants3, existsSync as existsSync2, readdirSync as readdirSync2, realpathSync } from "fs";
29672
31191
  import { createRequire } from "module";
29673
- import path18 from "path";
29674
- var logger25 = createModuleLogger("bridge.claudeRuntime");
31192
+ import path20 from "path";
31193
+ var logger27 = createModuleLogger("bridge.claudeRuntime");
29675
31194
  var require2 = createRequire(import.meta.url);
29676
31195
  var EXPLICIT_CLAUDE_EXECUTABLE_ENV = "AHCHAT_CLAUDE_EXECUTABLE";
29677
31196
  var BUNDLED_CLAUDE_PATH_ENV = "AHCHAT_BUNDLED_CLAUDE_PATH";
@@ -29679,7 +31198,7 @@ function normalizePath(value) {
29679
31198
  const trimmed = value?.trim();
29680
31199
  if (!trimmed) return void 0;
29681
31200
  const expanded = resolveUserPath(trimmed);
29682
- return path18.isAbsolute(expanded) ? expanded : path18.resolve(expanded);
31201
+ return path20.isAbsolute(expanded) ? expanded : path20.resolve(expanded);
29683
31202
  }
29684
31203
  function canExecute2(candidate) {
29685
31204
  try {
@@ -29758,16 +31277,16 @@ function resolveClaudeAgentSdkDir() {
29758
31277
  const sdkEntry = safeResolve("@anthropic-ai/claude-agent-sdk");
29759
31278
  if (!sdkEntry) return void 0;
29760
31279
  try {
29761
- return path18.dirname(realpathSync(sdkEntry));
31280
+ return path20.dirname(realpathSync(sdkEntry));
29762
31281
  } catch {
29763
- return path18.dirname(sdkEntry);
31282
+ return path20.dirname(sdkEntry);
29764
31283
  }
29765
31284
  }
29766
31285
  function findPnpmStoreDir(fromDir) {
29767
31286
  let current = fromDir;
29768
- while (current !== path18.dirname(current)) {
29769
- if (path18.basename(current) === ".pnpm") return current;
29770
- current = path18.dirname(current);
31287
+ while (current !== path20.dirname(current)) {
31288
+ if (path20.basename(current) === ".pnpm") return current;
31289
+ current = path20.dirname(current);
29771
31290
  }
29772
31291
  return void 0;
29773
31292
  }
@@ -29783,7 +31302,7 @@ function resolvePnpmRuntimeBinary(sdkDir, target) {
29783
31302
  }
29784
31303
  for (const entry of entries) {
29785
31304
  if (!entry.startsWith(`${encodedName}@`)) continue;
29786
- const candidate = path18.join(
31305
+ const candidate = path20.join(
29787
31306
  pnpmStoreDir,
29788
31307
  entry,
29789
31308
  "node_modules",
@@ -29802,8 +31321,8 @@ function resolveSdkRuntimeBinary(target) {
29802
31321
  const scopedPackageName = target.packageName.split("/")[1] ?? target.packageName;
29803
31322
  const candidates = [
29804
31323
  safeResolve(`${target.packageName}/${target.binaryName}`, [sdkDir]),
29805
- path18.join(sdkDir, "..", scopedPackageName, target.binaryName),
29806
- 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),
29807
31326
  resolvePnpmRuntimeBinary(sdkDir, target)
29808
31327
  ].filter((candidate) => Boolean(candidate));
29809
31328
  return candidates.find((candidate) => existsSync2(candidate));
@@ -29920,14 +31439,14 @@ function resolveClaudeRuntime(env2 = process.env) {
29920
31439
  }
29921
31440
  function logClaudeRuntimeResolution(resolution) {
29922
31441
  if (resolution.ok) {
29923
- logger25.info("Claude runtime ready", {
31442
+ logger27.info("Claude runtime ready", {
29924
31443
  source: resolution.source,
29925
31444
  path: resolution.path ?? null,
29926
31445
  version: resolution.version ?? null
29927
31446
  });
29928
31447
  return;
29929
31448
  }
29930
- logger25.error("Claude runtime unavailable", {
31449
+ logger27.error("Claude runtime unavailable", {
29931
31450
  error: new Error(resolution.error ?? "Claude runtime unavailable"),
29932
31451
  source: resolution.source,
29933
31452
  attempts: resolution.attempts
@@ -29935,27 +31454,27 @@ function logClaudeRuntimeResolution(resolution) {
29935
31454
  }
29936
31455
 
29937
31456
  // src/forkAgentFiles.ts
29938
- import * as fs12 from "fs/promises";
29939
- import * as path20 from "path";
31457
+ import * as fs14 from "fs/promises";
31458
+ import * as path22 from "path";
29940
31459
 
29941
31460
  // src/sessionSlug.ts
29942
31461
  import os9 from "os";
29943
- import path19 from "path";
29944
- 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");
29945
31464
  function cwdToSlug(cwd) {
29946
31465
  return cwd.replace(/[^a-zA-Z0-9-]/g, "-");
29947
31466
  }
29948
31467
  function sessionDirForCwd(cwd) {
29949
- return path19.join(CLAUDE_PROJECTS_DIR, cwdToSlug(cwd));
31468
+ return path21.join(CLAUDE_PROJECTS_DIR, cwdToSlug(cwd));
29950
31469
  }
29951
31470
  function sessionFilePath(cwd, sessionId) {
29952
- return path19.join(sessionDirForCwd(cwd), `${sessionId}.jsonl`);
31471
+ return path21.join(sessionDirForCwd(cwd), `${sessionId}.jsonl`);
29953
31472
  }
29954
31473
 
29955
31474
  // src/forkAgentFiles.ts
29956
- var logger26 = createModuleLogger("bridge.forkAgentFiles");
31475
+ var logger28 = createModuleLogger("bridge.forkAgentFiles");
29957
31476
  async function forkAgentFiles(sourceAgentId, newAgentId, sourceWorkdir, newWorkdir, dataDir, sessionStore, sourceConversationId) {
29958
- logger26.info("Fork file copy starting", {
31477
+ logger28.info("Fork file copy starting", {
29959
31478
  sourceAgentId,
29960
31479
  newAgentId,
29961
31480
  sourceWorkdir,
@@ -29964,57 +31483,57 @@ async function forkAgentFiles(sourceAgentId, newAgentId, sourceWorkdir, newWorkd
29964
31483
  sourceConversationId
29965
31484
  });
29966
31485
  try {
29967
- const stat3 = await fs12.stat(sourceWorkdir).catch(() => null);
31486
+ const stat3 = await fs14.stat(sourceWorkdir).catch(() => null);
29968
31487
  if (stat3?.isDirectory()) {
29969
- await fs12.cp(sourceWorkdir, newWorkdir, { recursive: true });
29970
- logger26.info("Workdir copied", {
31488
+ await fs14.cp(sourceWorkdir, newWorkdir, { recursive: true });
31489
+ logger28.info("Workdir copied", {
29971
31490
  from: sourceWorkdir,
29972
31491
  to: newWorkdir
29973
31492
  });
29974
31493
  } else {
29975
- await fs12.mkdir(newWorkdir, { recursive: true });
29976
- logger26.info("Workdir created (source did not exist)", {
31494
+ await fs14.mkdir(newWorkdir, { recursive: true });
31495
+ logger28.info("Workdir created (source did not exist)", {
29977
31496
  newWorkdir
29978
31497
  });
29979
31498
  }
29980
31499
  } catch (e) {
29981
- logger26.error("Workdir copy failed", { error: e });
31500
+ logger28.error("Workdir copy failed", { error: e });
29982
31501
  throw e;
29983
31502
  }
29984
- const srcNotebook = path20.join(dataDir, "agent-memory", sourceAgentId, "notebook.md");
29985
- const dstNotebookDir = path20.join(dataDir, "agent-memory", newAgentId);
29986
- 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");
29987
31506
  try {
29988
- const nbStat = await fs12.stat(srcNotebook).catch(() => null);
31507
+ const nbStat = await fs14.stat(srcNotebook).catch(() => null);
29989
31508
  if (nbStat?.isFile()) {
29990
- await fs12.mkdir(dstNotebookDir, { recursive: true });
29991
- await fs12.copyFile(srcNotebook, dstNotebook);
29992
- logger26.info("Notebook copied", {
31509
+ await fs14.mkdir(dstNotebookDir, { recursive: true });
31510
+ await fs14.copyFile(srcNotebook, dstNotebook);
31511
+ logger28.info("Notebook copied", {
29993
31512
  from: srcNotebook,
29994
31513
  to: dstNotebook
29995
31514
  });
29996
31515
  } else {
29997
- logger26.info("Notebook copy skipped (source does not exist)", {
31516
+ logger28.info("Notebook copy skipped (source does not exist)", {
29998
31517
  sourceAgentId
29999
31518
  });
30000
31519
  }
30001
31520
  } catch (e) {
30002
- logger26.error("Notebook copy failed", { error: e });
31521
+ logger28.error("Notebook copy failed", { error: e });
30003
31522
  }
30004
31523
  let sessionCopied = false;
30005
31524
  const sourceSessionId = sessionStore.get(sourceAgentId, { kind: "single" });
30006
31525
  if (sourceSessionId) {
30007
31526
  try {
30008
31527
  const srcPath = sessionFilePath(sourceWorkdir, sourceSessionId);
30009
- const srcStat = await fs12.stat(srcPath).catch(() => null);
31528
+ const srcStat = await fs14.stat(srcPath).catch(() => null);
30010
31529
  if (srcStat?.isFile()) {
30011
31530
  const dstDir = sessionDirForCwd(newWorkdir);
30012
- await fs12.mkdir(dstDir, { recursive: true });
30013
- const dstPath = path20.join(dstDir, `${sourceSessionId}.jsonl`);
30014
- 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);
30015
31534
  sessionStore.set(newAgentId, { kind: "single" }, sourceSessionId);
30016
31535
  sessionCopied = true;
30017
- logger26.info("Session JSONL copied and registered", {
31536
+ logger28.info("Session JSONL copied and registered", {
30018
31537
  sourceAgentId,
30019
31538
  newAgentId,
30020
31539
  sessionId: sourceSessionId,
@@ -30023,30 +31542,30 @@ async function forkAgentFiles(sourceAgentId, newAgentId, sourceWorkdir, newWorkd
30023
31542
  fileSize: srcStat.size
30024
31543
  });
30025
31544
  } else {
30026
- logger26.info("Session copy skipped (source JSONL not found)", {
31545
+ logger28.info("Session copy skipped (source JSONL not found)", {
30027
31546
  sourceAgentId,
30028
31547
  sessionId: sourceSessionId,
30029
31548
  expectedPath: srcPath
30030
31549
  });
30031
31550
  }
30032
31551
  } catch (e) {
30033
- logger26.error("Session JSONL copy failed", { error: e, sourceAgentId, newAgentId });
31552
+ logger28.error("Session JSONL copy failed", { error: e, sourceAgentId, newAgentId });
30034
31553
  }
30035
31554
  } else {
30036
- logger26.info("Session copy skipped (no session in store)", { sourceAgentId });
31555
+ logger28.info("Session copy skipped (no session in store)", { sourceAgentId });
30037
31556
  }
30038
31557
  if (!sessionCopied && sourceConversationId) {
30039
31558
  await writeForkMeta(dataDir, newAgentId, {
30040
31559
  sourceConversationId,
30041
31560
  forkedAt: (/* @__PURE__ */ new Date()).toISOString()
30042
31561
  });
30043
- logger26.info("Fork meta scheduled for history replay fallback", {
31562
+ logger28.info("Fork meta scheduled for history replay fallback", {
30044
31563
  newAgentId,
30045
31564
  sourceConversationId,
30046
31565
  reason: sourceSessionId ? "jsonl_missing_or_copy_failed" : "no_session_in_store"
30047
31566
  });
30048
31567
  }
30049
- logger26.info("Fork file copy finished", {
31568
+ logger28.info("Fork file copy finished", {
30050
31569
  sourceAgentId,
30051
31570
  newAgentId,
30052
31571
  newWorkdir,
@@ -30056,15 +31575,15 @@ async function forkAgentFiles(sourceAgentId, newAgentId, sourceWorkdir, newWorkd
30056
31575
  }
30057
31576
 
30058
31577
  // src/modelQuerier.ts
30059
- import fs13 from "fs/promises";
31578
+ import fs15 from "fs/promises";
30060
31579
  import os10 from "os";
30061
- import path21 from "path";
31580
+ import path23 from "path";
30062
31581
  import * as sdk4 from "@anthropic-ai/claude-agent-sdk";
30063
- var logger27 = createModuleLogger("bridge.modelQuerier");
31582
+ var logger29 = createModuleLogger("bridge.modelQuerier");
30064
31583
  async function listModels(queryFn, opts = {}) {
30065
31584
  const t0 = Date.now();
30066
- const cwd = opts.cwd ?? path21.join(os10.homedir(), ".ahchat", "workspaces", "_list_models");
30067
- 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 });
30068
31587
  const fn = queryFn ?? sdk4.query;
30069
31588
  const ic = new InputController();
30070
31589
  ic.push("Reply with exactly: PING", "");
@@ -30109,27 +31628,29 @@ async function listModels(queryFn, opts = {}) {
30109
31628
  displayName: m.displayName,
30110
31629
  description: m.description
30111
31630
  }));
30112
- logger27.info("listModels done", { count: models.length, ms: Date.now() - t0 });
31631
+ logger29.info("listModels done", { count: models.length, ms: Date.now() - t0 });
30113
31632
  return models;
30114
31633
  } finally {
30115
31634
  try {
30116
31635
  ic.close();
30117
- } catch {
31636
+ } catch (e) {
31637
+ logger29.debug("listModels: input controller close failed during teardown", { error: e });
30118
31638
  }
30119
31639
  try {
30120
31640
  await q.return?.(void 0);
30121
- } catch {
31641
+ } catch (e) {
31642
+ logger29.debug("listModels: query iterator return failed during teardown", { error: e });
30122
31643
  }
30123
31644
  }
30124
31645
  }
30125
31646
 
30126
31647
  // src/promptOptimizer.ts
30127
- import fs14 from "fs/promises";
31648
+ import fs16 from "fs/promises";
30128
31649
  import os11 from "os";
30129
- import path22 from "path";
31650
+ import path24 from "path";
30130
31651
  import * as sdk5 from "@anthropic-ai/claude-agent-sdk";
30131
- var logger28 = createModuleLogger("bridge.promptOptimizer");
30132
- 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.
30133
31654
 
30134
31655
  Rewrite the user's Agent System Prompt so it is clear, operational, and ready to save.
30135
31656
  Preserve the user's intent, role, constraints, language, and important domain details.
@@ -30140,7 +31661,7 @@ Do not include explanations, headings like "Optimized Prompt", markdown fences,
30140
31661
  Keep the result concise enough for a System Prompt field, preferably within 2000 characters.`;
30141
31662
  function buildUserPrompt(opts) {
30142
31663
  const parts = [
30143
- "\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",
30144
31665
  opts.name?.trim() ? `Agent \u540D\u79F0\uFF1A${opts.name.trim()}` : null,
30145
31666
  opts.role?.trim() ? `\u89D2\u8272\u6807\u7B7E\uFF1A${opts.role.trim()}` : null,
30146
31667
  "",
@@ -30170,8 +31691,8 @@ async function optimizePrompt(queryFn, opts) {
30170
31691
  const prompt = opts.systemPrompt.trim();
30171
31692
  if (!prompt) throw new Error("systemPrompt is required");
30172
31693
  const t0 = Date.now();
30173
- const cwd = opts.cwd ?? path22.join(os11.homedir(), ".ahchat", "workspaces", "_prompt_optimizer");
30174
- 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 });
30175
31696
  const fn = queryFn ?? sdk5.query;
30176
31697
  const ic = new InputController();
30177
31698
  ic.push(buildUserPrompt(opts), "");
@@ -30227,7 +31748,7 @@ async function optimizePrompt(queryFn, opts) {
30227
31748
  })
30228
31749
  ]);
30229
31750
  if (!optimizedPrompt) throw new Error("empty optimized prompt");
30230
- logger28.info("optimizePrompt done", {
31751
+ logger30.info("optimizePrompt done", {
30231
31752
  inputLength: prompt.length,
30232
31753
  outputLength: optimizedPrompt.length,
30233
31754
  model: model || "(default)",
@@ -30238,17 +31759,19 @@ async function optimizePrompt(queryFn, opts) {
30238
31759
  if (timeoutId) clearTimeout(timeoutId);
30239
31760
  try {
30240
31761
  ic.close();
30241
- } catch {
31762
+ } catch (e) {
31763
+ logger30.debug("optimizePrompt: input controller close failed during teardown", { error: e });
30242
31764
  }
30243
31765
  try {
30244
31766
  await q.return?.(void 0);
30245
- } catch {
31767
+ } catch (e) {
31768
+ logger30.debug("optimizePrompt: query iterator return failed during teardown", { error: e });
30246
31769
  }
30247
31770
  }
30248
31771
  }
30249
31772
 
30250
31773
  // src/start.ts
30251
- var logger29 = createModuleLogger("bridge");
31774
+ var logger31 = createModuleLogger("bridge");
30252
31775
  var NODE_USER_UID2 = 1e3;
30253
31776
  function isRunningAsRoot2() {
30254
31777
  try {
@@ -30258,56 +31781,58 @@ function isRunningAsRoot2() {
30258
31781
  }
30259
31782
  }
30260
31783
  async function syncClaudeCredentialsToNodeAccessibleDir(agentConfigDir) {
30261
- const rootClaudeDir = path23.join(process.env.HOME ?? "/root", ".claude");
30262
- const fs15 = await import("fs/promises");
31784
+ const rootClaudeDir = path25.join(process.env.HOME ?? "/root", ".claude");
31785
+ const fs17 = await import("fs/promises");
30263
31786
  try {
30264
- await fs15.access(rootClaudeDir);
31787
+ await fs17.access(rootClaudeDir);
30265
31788
  } catch {
30266
- logger29.info("No /root/.claude to sync", { rootClaudeDir });
31789
+ logger31.info("No /root/.claude to sync", { rootClaudeDir });
30267
31790
  return;
30268
31791
  }
30269
31792
  const filesToSync = [".credentials.json", "settings.json", ".credentials.backup.json"];
30270
31793
  for (const file2 of filesToSync) {
30271
- const src = path23.join(rootClaudeDir, file2);
30272
- const dest = path23.join(agentConfigDir, file2);
31794
+ const src = path25.join(rootClaudeDir, file2);
31795
+ const dest = path25.join(agentConfigDir, file2);
30273
31796
  try {
30274
- await fs15.copyFile(src, dest);
30275
- 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 });
30276
31799
  } catch {
30277
- logger29.debug("Credential file not present, skipping", { file: file2, src });
31800
+ logger31.debug("Credential file not present, skipping", { file: file2, src });
30278
31801
  }
30279
31802
  }
30280
31803
  }
30281
31804
  async function chownRecursive(dirPath, uid, gid) {
30282
- const fs15 = await import("fs/promises");
31805
+ const fs17 = await import("fs/promises");
30283
31806
  try {
30284
- await fs15.chown(dirPath, uid, gid);
31807
+ await fs17.chown(dirPath, uid, gid);
30285
31808
  } catch {
30286
- logger29.debug("chown skipped", { dirPath, uid, gid });
31809
+ logger31.debug("chown skipped", { dirPath, uid, gid });
30287
31810
  }
30288
31811
  let entries;
30289
31812
  try {
30290
- entries = await fs15.readdir(dirPath, { withFileTypes: true });
31813
+ entries = await fs17.readdir(dirPath, { withFileTypes: true });
30291
31814
  } catch {
30292
31815
  return;
30293
31816
  }
30294
31817
  for (const entry of entries) {
30295
- const fullPath = path23.join(dirPath, entry.name);
31818
+ const fullPath = path25.join(dirPath, entry.name);
30296
31819
  if (entry.isDirectory()) {
30297
31820
  await chownRecursive(fullPath, uid, gid);
30298
31821
  } else {
30299
31822
  try {
30300
- await fs15.chown(fullPath, uid, gid);
31823
+ await fs17.chown(fullPath, uid, gid);
30301
31824
  } catch {
30302
- logger29.debug("chown skipped", { fullPath, uid, gid });
31825
+ logger31.debug("chown skipped", { fullPath, uid, gid });
30303
31826
  }
30304
31827
  }
30305
31828
  }
30306
31829
  }
30307
31830
  async function startBridge(config2) {
31831
+ process.env.AHCHAT_DATA_DIR = config2.dataDir;
31832
+ configureBridgeLogger(config2);
30308
31833
  ensureDir(config2.dataDir);
30309
31834
  ensureDir(config2.agentConfigDir);
30310
- const workspacesDir = path23.join(config2.dataDir, "workspaces");
31835
+ const workspacesDir = path25.join(config2.dataDir, "workspaces");
30311
31836
  ensureDir(workspacesDir);
30312
31837
  process.env.CLAUDE_CONFIG_DIR = config2.agentConfigDir;
30313
31838
  installBridgeFetchAuth(config2.serverApiUrl, config2.bridgeToken);
@@ -30315,7 +31840,7 @@ async function startBridge(config2) {
30315
31840
  await syncClaudeCredentialsToNodeAccessibleDir(config2.agentConfigDir);
30316
31841
  await chownRecursive(config2.agentConfigDir, NODE_USER_UID2, NODE_USER_UID2);
30317
31842
  await chownRecursive(workspacesDir, NODE_USER_UID2, NODE_USER_UID2);
30318
- logger29.info("Root environment: chowned config/workspaces dirs to uid 1000", {
31843
+ logger31.info("Root environment: chowned config/workspaces dirs to uid 1000", {
30319
31844
  agentConfigDir: config2.agentConfigDir,
30320
31845
  workspacesDir
30321
31846
  });
@@ -30330,13 +31855,13 @@ Claude runtime is unavailable.
30330
31855
 
30331
31856
  ${claudeRuntime.error ?? "Install Claude Code manually or use the bundled desktop runtime."}
30332
31857
 
30333
- 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.
30334
31859
  `
30335
31860
  );
30336
31861
  process.exit(1);
30337
31862
  }
30338
31863
  setClaudeExecutablePath(claudeRuntime.path);
30339
- logger29.info("Bridge starting", {
31864
+ logger31.info("Bridge starting", {
30340
31865
  bridgeId: config2.bridgeId,
30341
31866
  serverUrl: config2.serverUrl,
30342
31867
  serverApiUrl: config2.serverApiUrl,
@@ -30344,24 +31869,37 @@ Reinstall @fangyb/ahchat-bridge with npm optional dependencies, set AHCHAT_CLAUD
30344
31869
  claudeRuntimeSource: claudeRuntime.source,
30345
31870
  claudeRuntimeVersion: claudeRuntime.version ?? null
30346
31871
  });
30347
- process.stdout.write(`
31872
+ const shouldPrintRawBridgeToken = process.stdout.isTTY && process.env.AHCHAT_SUPPRESS_BRIDGE_TOKEN_STDOUT !== "1";
31873
+ process.stdout.write(
31874
+ `
30348
31875
  Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\u673A\u5668):
30349
- ${config2.bridgeToken}
31876
+ ${shouldPrintRawBridgeToken ? config2.bridgeToken : "***"}
30350
31877
 
30351
- `);
31878
+ `
31879
+ );
30352
31880
  wsMetrics.start(5e3);
30353
31881
  const sessionStore = new SessionStore(config2.dataDir);
30354
- const memoryRoot = path23.join(config2.dataDir, "agent-memory");
31882
+ const workdirOverrideStore = new LocalWorkdirOverrideStore(config2.dataDir);
31883
+ const memoryRoot = path25.join(config2.dataDir, "agent-memory");
30355
31884
  const memoryStore = new AgentMemoryStore(memoryRoot);
30356
- logger29.info("Agent memory store initialized", { rootDir: memoryRoot });
31885
+ logger31.info("Agent memory store initialized", { rootDir: memoryRoot });
30357
31886
  const smithNotebook = memoryStore.read(SMITH_AGENT_ID);
30358
31887
  if (!smithNotebook.trim()) {
30359
31888
  memoryStore.write(SMITH_AGENT_ID, SMITH_NOTEBOOK_SEED);
30360
- logger29.info("Smith notebook seeded", { agentId: SMITH_AGENT_ID });
31889
+ logger31.info("Smith notebook seeded", { agentId: SMITH_AGENT_ID });
30361
31890
  }
30362
31891
  const skillStore = new SkillStore(config2.dataDir);
30363
31892
  skillStore.seed("log-analysis", LOG_ANALYSIS_SKILL);
30364
- 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();
30365
31903
  const agentRegistry = new HttpAgentRegistry(config2.serverApiUrl, config2.bridgeToken);
30366
31904
  const groupRegistry = new GroupRegistry(config2.serverApiUrl, config2.bridgeToken);
30367
31905
  const subscriptionRegistry = new HttpSubscriptionRegistry(config2.serverApiUrl, config2.bridgeToken);
@@ -30386,10 +31924,18 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30386
31924
  subscriptionRegistry,
30387
31925
  serverApiUrl: config2.serverApiUrl,
30388
31926
  bridgeToken: config2.bridgeToken,
30389
- dataDir: config2.dataDir
31927
+ dataDir: config2.dataDir,
31928
+ workdirOverrideStore
30390
31929
  });
30391
31930
  const taskDispatchHandler = createTaskDispatchHandler(agentManager, agentRegistry, emit);
30392
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
+ };
30393
31939
  let statusInterval = null;
30394
31940
  connector = new ServerConnector({
30395
31941
  config: config2,
@@ -30397,7 +31943,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30397
31943
  onTaskDispatch: taskDispatchHandler,
30398
31944
  onGroupTaskDispatch: groupTaskDispatchHandler,
30399
31945
  onStopGeneration: async (payload) => {
30400
- logger29.info("onStopGeneration invoking cancelReply", {
31946
+ logger31.info("onStopGeneration invoking cancelReply", {
30401
31947
  agentId: payload.agentId,
30402
31948
  ackId: payload.ackId,
30403
31949
  conversationId: payload.conversationId,
@@ -30420,7 +31966,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30420
31966
  switch (msg.type) {
30421
31967
  case "bridge:list_models_request": {
30422
31968
  const { requestId, apiKey, apiBaseUrl, modelsApiBaseUrl } = msg.payload;
30423
- 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 });
30424
31970
  try {
30425
31971
  let models;
30426
31972
  if (apiKey || apiBaseUrl) {
@@ -30472,7 +32018,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30472
32018
  type: "bridge:list_models_response",
30473
32019
  payload: { requestId, models }
30474
32020
  });
30475
- logger29.info("list_models response sent", { requestId, count: models.length });
32021
+ logger31.info("list_models response sent", { requestId, count: models.length });
30476
32022
  } catch (e) {
30477
32023
  const err = e instanceof Error ? e.message : String(e);
30478
32024
  connector?.send({
@@ -30480,16 +32026,16 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30480
32026
  payload: { requestId, error: err }
30481
32027
  });
30482
32028
  if (err.includes("models \u7AEF\u70B9\u672A\u627E\u5230")) {
30483
- 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 });
30484
32030
  } else {
30485
- logger29.error("list_models failed", { requestId, error: e });
32031
+ logger31.error("list_models failed", { requestId, error: e });
30486
32032
  }
30487
32033
  }
30488
32034
  break;
30489
32035
  }
30490
32036
  case "bridge:optimize_prompt_request": {
30491
32037
  const { requestId, apiKey, apiBaseUrl, model, name, role, systemPrompt } = msg.payload;
30492
- logger29.info("optimize_prompt request received", {
32038
+ logger31.info("optimize_prompt request received", {
30493
32039
  requestId,
30494
32040
  hasApiKey: !!apiKey,
30495
32041
  hasApiBaseUrl: !!apiBaseUrl,
@@ -30511,7 +32057,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30511
32057
  type: "bridge:optimize_prompt_response",
30512
32058
  payload: { requestId, optimizedPrompt }
30513
32059
  });
30514
- logger29.info("optimize_prompt response sent", {
32060
+ logger31.info("optimize_prompt response sent", {
30515
32061
  requestId,
30516
32062
  length: optimizedPrompt.length
30517
32063
  });
@@ -30521,21 +32067,21 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30521
32067
  type: "bridge:optimize_prompt_response",
30522
32068
  payload: { requestId, error: err }
30523
32069
  });
30524
- logger29.error("optimize_prompt failed", { requestId, error: e });
32070
+ logger31.error("optimize_prompt failed", { requestId, error: e });
30525
32071
  }
30526
32072
  break;
30527
32073
  }
30528
32074
  case "bridge:list_dir_request": {
30529
32075
  const { requestId, path: dirPath } = msg.payload;
30530
- logger29.info("list_dir request received", { requestId, path: dirPath });
32076
+ logger31.info("list_dir request received", { requestId, path: dirPath });
30531
32077
  try {
30532
- const resolved = remapServerWorkspacePath(dirPath, workspacesDir);
32078
+ const resolved = resolveBridgeWorkdirPath(dirPath);
30533
32079
  if (resolved.remapped) {
30534
32080
  ensureDir(resolved.path);
30535
- logger29.info("list_dir path remapped to local Bridge workspace", {
32081
+ logger31.info("list_dir path resolved to local Bridge workspace", {
30536
32082
  requestId,
30537
32083
  requested: dirPath,
30538
- remapped: resolved.path
32084
+ resolved: resolved.path
30539
32085
  });
30540
32086
  }
30541
32087
  const entries = await listDirectoryEntries(resolved.path);
@@ -30543,28 +32089,28 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30543
32089
  type: "bridge:list_dir_response",
30544
32090
  payload: { requestId, entries, localPath: resolved.path }
30545
32091
  });
30546
- logger29.info("list_dir response sent", { requestId, count: entries.length });
32092
+ logger31.info("list_dir response sent", { requestId, count: entries.length });
30547
32093
  } catch (e) {
30548
32094
  const err = e instanceof Error ? e.message : String(e);
30549
32095
  connector?.send({
30550
32096
  type: "bridge:list_dir_response",
30551
32097
  payload: { requestId, error: err }
30552
32098
  });
30553
- logger29.error("list_dir failed", { requestId, error: e });
32099
+ logger31.error("list_dir failed", { requestId, error: e });
30554
32100
  }
30555
32101
  break;
30556
32102
  }
30557
32103
  case "bridge:write_file_request": {
30558
32104
  const { requestId, baseDir, relativePath, contentBase64, overwrite } = msg.payload;
30559
- logger29.info("write_file request received", { requestId, baseDir, relativePath });
32105
+ logger31.info("write_file request received", { requestId, baseDir, relativePath });
30560
32106
  try {
30561
- const resolved = remapServerWorkspacePath(baseDir, workspacesDir);
32107
+ const resolved = resolveBridgeWorkdirPath(baseDir);
30562
32108
  if (resolved.remapped) {
30563
32109
  ensureDir(resolved.path);
30564
- logger29.info("write_file baseDir remapped to local Bridge workspace", {
32110
+ logger31.info("write_file baseDir resolved to local Bridge workspace", {
30565
32111
  requestId,
30566
32112
  requested: baseDir,
30567
- remapped: resolved.path
32113
+ resolved: resolved.path
30568
32114
  });
30569
32115
  }
30570
32116
  const written = await writeWorkdirFile({
@@ -30583,23 +32129,23 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30583
32129
  size: written.size
30584
32130
  }
30585
32131
  });
30586
- 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 });
30587
32133
  } catch (e) {
30588
32134
  const err = e instanceof Error ? e.message : String(e);
30589
32135
  connector?.send({
30590
32136
  type: "bridge:write_file_response",
30591
32137
  payload: { requestId, error: err }
30592
32138
  });
30593
- logger29.error("write_file failed", { requestId, error: e });
32139
+ logger31.error("write_file failed", { requestId, error: e });
30594
32140
  }
30595
32141
  break;
30596
32142
  }
30597
32143
  case "bridge:read_file_request": {
30598
32144
  const { requestId, path: filePath, baseDir } = msg.payload;
30599
- logger29.info("read_file request received", { requestId, path: filePath, baseDir });
32145
+ logger31.info("read_file request received", { requestId, path: filePath, baseDir });
30600
32146
  try {
30601
- const resolved = remapServerWorkspacePath(filePath, workspacesDir);
30602
- const resolvedBase = baseDir ? remapServerWorkspacePath(baseDir, workspacesDir) : void 0;
32147
+ const resolved = resolveBridgeWorkdirPath(filePath);
32148
+ const resolvedBase = baseDir ? resolveBridgeWorkdirPath(baseDir) : void 0;
30603
32149
  const result = await readWorkdirFile(resolved.path, resolvedBase?.path);
30604
32150
  connector?.send({
30605
32151
  type: "bridge:read_file_response",
@@ -30610,25 +32156,28 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30610
32156
  size: result.size
30611
32157
  }
30612
32158
  });
30613
- 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 });
30614
32160
  } catch (e) {
30615
32161
  const err = e instanceof Error ? e.message : String(e);
30616
32162
  connector?.send({
30617
32163
  type: "bridge:read_file_response",
30618
32164
  payload: { requestId, error: err }
30619
32165
  });
30620
- logger29.error("read_file failed", { requestId, error: e });
32166
+ logger31.error("read_file failed", { requestId, error: e });
30621
32167
  }
30622
32168
  break;
30623
32169
  }
30624
32170
  case "agent:dump_sessions_request": {
30625
32171
  const { requestId, agentId, traceId } = msg.payload;
30626
- logger29.info("agent:dump_sessions_request received", { requestId, agentId, traceId });
32172
+ logger31.info("agent:dump_sessions_request received", { requestId, agentId, traceId });
30627
32173
  try {
30628
32174
  const result = await dumpAgentContext(agentId, {
30629
32175
  sessionStore,
30630
32176
  agentRegistry,
30631
- groupRegistry
32177
+ groupRegistry,
32178
+ workspacesDir,
32179
+ agentConfigDir: config2.agentConfigDir,
32180
+ workdirOverrideStore
30632
32181
  });
30633
32182
  connector?.send({
30634
32183
  type: "agent:dump_sessions_response",
@@ -30636,16 +32185,18 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30636
32185
  requestId,
30637
32186
  ok: result.ok,
30638
32187
  dir: result.dir,
32188
+ localDir: result.localDir,
30639
32189
  files: result.files,
30640
32190
  scopeErrors: result.scopeErrors,
30641
32191
  error: result.error
30642
32192
  }
30643
32193
  });
30644
- logger29.info("agent:dump_sessions_response sent", {
32194
+ logger31.info("agent:dump_sessions_response sent", {
30645
32195
  requestId,
30646
32196
  agentId,
30647
32197
  traceId,
30648
32198
  ok: result.ok,
32199
+ localDir: result.localDir,
30649
32200
  fileCount: result.files.length,
30650
32201
  scopeErrorCount: result.scopeErrors.length,
30651
32202
  error: result.error
@@ -30656,7 +32207,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30656
32207
  type: "agent:dump_sessions_response",
30657
32208
  payload: { requestId, ok: false, error: err }
30658
32209
  });
30659
- logger29.error("agent:dump_sessions_request failed", {
32210
+ logger31.error("agent:dump_sessions_request failed", {
30660
32211
  requestId,
30661
32212
  agentId,
30662
32213
  traceId,
@@ -30667,7 +32218,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30667
32218
  }
30668
32219
  case "bridge:fetch_logs_request": {
30669
32220
  const { requestId, ...filter } = msg.payload;
30670
- logger29.info("fetch_logs request received", {
32221
+ logger31.info("fetch_logs request received", {
30671
32222
  requestId,
30672
32223
  startIso: filter.startIso,
30673
32224
  endIso: filter.endIso,
@@ -30690,7 +32241,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30690
32241
  nextOffset: result.nextOffset
30691
32242
  }
30692
32243
  });
30693
- logger29.info("fetch_logs response sent", {
32244
+ logger31.info("fetch_logs response sent", {
30694
32245
  requestId,
30695
32246
  count: result.entries.length,
30696
32247
  truncated: result.truncated,
@@ -30703,13 +32254,13 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30703
32254
  type: "bridge:fetch_logs_response",
30704
32255
  payload: { requestId, error: err }
30705
32256
  });
30706
- logger29.error("fetch_logs failed", { requestId, error: e });
32257
+ logger31.error("fetch_logs failed", { requestId, error: e });
30707
32258
  }
30708
32259
  break;
30709
32260
  }
30710
32261
  case "agent:fork": {
30711
32262
  const fp = msg.payload;
30712
- logger29.info("agent:fork received, copying workdir notebook and session", {
32263
+ logger31.info("agent:fork received, copying workdir notebook and session", {
30713
32264
  sourceAgentId: fp.sourceAgentId,
30714
32265
  newAgentId: fp.newAgentId,
30715
32266
  sourceWorkdir: fp.sourceWorkdir,
@@ -30727,13 +32278,13 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30727
32278
  sessionStore,
30728
32279
  fp.sourceConversationId
30729
32280
  );
30730
- logger29.info("agent:fork files copied successfully", {
32281
+ logger31.info("agent:fork files copied successfully", {
30731
32282
  newAgentId: fp.newAgentId,
30732
32283
  traceId: fp.traceId,
30733
32284
  sessionCopied
30734
32285
  });
30735
32286
  } catch (e) {
30736
- logger29.error("agent:fork file copy failed", {
32287
+ logger31.error("agent:fork file copy failed", {
30737
32288
  error: e,
30738
32289
  newAgentId: fp.newAgentId,
30739
32290
  traceId: fp.traceId
@@ -30745,7 +32296,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30745
32296
  await agentManager.terminate(msg.payload.agentId);
30746
32297
  break;
30747
32298
  case "agent:terminate_scope":
30748
- logger29.info("agent:terminate_scope received", {
32299
+ logger31.info("agent:terminate_scope received", {
30749
32300
  agentId: msg.payload.agentId,
30750
32301
  scope: msg.payload.scope
30751
32302
  });
@@ -30761,7 +32312,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30761
32312
  const oldConfig = parseAgentConfig(oldAgent.config);
30762
32313
  const newConfig = parseAgentConfig(msg.payload.agent.config);
30763
32314
  if (oldConfig.model !== newConfig.model) {
30764
- logger29.info("agent:updated - model changed, terminating running processes", {
32315
+ logger31.info("agent:updated - model changed, terminating running processes", {
30765
32316
  agentId: msg.payload.agent.id,
30766
32317
  oldModel: oldConfig.model ?? "(default)",
30767
32318
  newModel: newConfig.model ?? "(default)"
@@ -30776,9 +32327,27 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30776
32327
  break;
30777
32328
  case "subscription:changed":
30778
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
+ }
30779
32339
  break;
30780
32340
  case "subscription:deleted":
30781
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
+ }
30782
32351
  break;
30783
32352
  case "group:member_changed":
30784
32353
  await handleGroupMemberChangedPush(
@@ -30811,7 +32380,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30811
32380
  const p = msg.payload;
30812
32381
  const answerText = formatAnswerForSDK(p);
30813
32382
  const ok = askQuestionRegistry.resolve(p.questionId, answerText);
30814
- logger29.info("user:answer_question handled", {
32383
+ logger31.info("user:answer_question handled", {
30815
32384
  questionId: p.questionId,
30816
32385
  agentId: p.agentId,
30817
32386
  resolved: ok,
@@ -30832,7 +32401,7 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30832
32401
  });
30833
32402
  }, config2.queryConfig.statusReportIntervalMs);
30834
32403
  const shutdown = async (signal) => {
30835
- logger29.info("Shutdown signal received", { signal });
32404
+ logger31.info("Shutdown signal received", { signal });
30836
32405
  if (statusInterval) {
30837
32406
  clearInterval(statusInterval);
30838
32407
  statusInterval = null;
@@ -30841,11 +32410,22 @@ Bridge token (register this machine at Settings \u2192 \u5DF2\u8FDE\u63A5\u7684\
30841
32410
  connector?.close();
30842
32411
  await agentManager.shutdownAll();
30843
32412
  releaseLock();
30844
- logger29.info("Bridge stopped");
32413
+ logger31.info("Bridge stopped");
32414
+ await flushFileTransports();
32415
+ await logUploader.flushOnce();
32416
+ logUploader.stop();
32417
+ await flushFileTransports();
30845
32418
  process.exit(0);
30846
32419
  };
30847
32420
  process.on("SIGINT", () => void shutdown("SIGINT"));
30848
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
+ });
30849
32429
  }
30850
32430
  function compactEnv(env2) {
30851
32431
  return Object.fromEntries(
@@ -30853,9 +32433,39 @@ function compactEnv(env2) {
30853
32433
  );
30854
32434
  }
30855
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
+
30856
32468
  // src/index.ts
30857
- var logger30 = createModuleLogger("bridge");
30858
32469
  void startBridge(loadBridgeConfig()).catch((e) => {
30859
- logger30.error("Bridge failed to start", { error: e });
30860
- process.exit(1);
32470
+ handleBridgeStartError(e, "Bridge failed to start");
30861
32471
  });