@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.
- package/dist/cli.cjs +2260 -629
- package/dist/index.js +2071 -461
- 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,
|
|
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 =
|
|
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/
|
|
3768
|
-
import
|
|
3769
|
-
import
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4473
|
+
safeWriteLine(opts?.stderr ?? defaultStream("stderr"), line, console.error);
|
|
4382
4474
|
} else {
|
|
4383
|
-
console.log
|
|
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:
|
|
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 =
|
|
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 =
|
|
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:
|
|
4670
|
+
const { interval, path: path26, intervalBoundary } = this.options;
|
|
4579
4671
|
for (let index = 1; index < 1e3; ++index) {
|
|
4580
|
-
const filename =
|
|
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:
|
|
4702
|
+
const { compress, path: path26, rotate } = this.options;
|
|
4611
4703
|
let rotatedName = "";
|
|
4612
4704
|
for (let count = rotate; count > 0; --count) {
|
|
4613
|
-
const currName =
|
|
4614
|
-
const prevName = count === 1 ? this.filename :
|
|
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
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
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.
|
|
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
|
|
5959
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
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: "
|
|
6524
|
+
type: "base64",
|
|
6272
6525
|
media_type: mediaType,
|
|
6273
|
-
|
|
6526
|
+
data: buffer.toString("base64")
|
|
6274
6527
|
}
|
|
6275
6528
|
});
|
|
6276
|
-
logger2.info("Image attachment adapted as
|
|
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:
|
|
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 = `
|
|
6292
|
-
const
|
|
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:
|
|
6306
|
-
|
|
6307
|
-
|
|
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", {
|
|
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: {
|
|
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: {
|
|
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: () =>
|
|
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,
|
|
7482
|
-
if (!
|
|
7764
|
+
function getElementAtPath(obj, path26) {
|
|
7765
|
+
if (!path26)
|
|
7483
7766
|
return obj;
|
|
7484
|
-
return
|
|
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
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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(
|
|
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(
|
|
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,
|
|
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 }, [...
|
|
8330
|
+
issue2.errors.map((issues) => processError({ issues }, [...path26, ...issue2.path]));
|
|
8048
8331
|
} else if (issue2.code === "invalid_key") {
|
|
8049
|
-
processError({ issues: issue2.issues }, [...
|
|
8332
|
+
processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
|
|
8050
8333
|
} else if (issue2.code === "invalid_element") {
|
|
8051
|
-
processError({ issues: issue2.issues }, [...
|
|
8334
|
+
processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
|
|
8052
8335
|
} else {
|
|
8053
|
-
const fullpath = [...
|
|
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,
|
|
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 }, [...
|
|
8367
|
+
issue2.errors.map((issues) => processError({ issues }, [...path26, ...issue2.path]));
|
|
8085
8368
|
} else if (issue2.code === "invalid_key") {
|
|
8086
|
-
processError({ issues: issue2.issues }, [...
|
|
8369
|
+
processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
|
|
8087
8370
|
} else if (issue2.code === "invalid_element") {
|
|
8088
|
-
processError({ issues: issue2.issues }, [...
|
|
8371
|
+
processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
|
|
8089
8372
|
} else {
|
|
8090
|
-
const fullpath = [...
|
|
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
|
|
8123
|
-
for (const seg of
|
|
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 (
|
|
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 (!
|
|
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
|
|
20816
|
-
if (
|
|
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 (
|
|
20821
|
-
const key =
|
|
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
|
|
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
|
|
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 ? `
|
|
22185
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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 "
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
25457
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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: {
|
|
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
|
-
|
|
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: {
|
|
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: {
|
|
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:
|
|
27045
|
-
conversationId: payload.conversationId
|
|
27046
|
-
traceId:
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
27958
|
-
return `${base}${
|
|
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
|
|
28056
|
-
return `${base}${
|
|
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,
|
|
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
|
-
|
|
28376
|
-
|
|
28377
|
-
|
|
28378
|
-
}
|
|
28379
|
-
const
|
|
28380
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
28900
|
-
const
|
|
28901
|
-
|
|
28902
|
-
|
|
28903
|
-
|
|
28904
|
-
|
|
28905
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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/
|
|
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.
|
|
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 =
|
|
29158
|
-
|
|
29159
|
-
|
|
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
|
-
|
|
30572
|
+
logger21.warn("Skill read: unknown name", { name, allowed: [...ALLOWED_NAMES] });
|
|
29164
30573
|
return "";
|
|
29165
30574
|
}
|
|
29166
|
-
const filePath =
|
|
30575
|
+
const filePath = path16.join(this.skillsDir, `${name}.md`);
|
|
29167
30576
|
try {
|
|
29168
|
-
const content =
|
|
29169
|
-
|
|
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
|
-
|
|
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 =
|
|
30590
|
+
const filePath = path16.join(this.skillsDir, `${name}.md`);
|
|
29182
30591
|
const tmpPath = `${filePath}.tmp`;
|
|
29183
30592
|
let existing = "";
|
|
29184
30593
|
try {
|
|
29185
|
-
existing =
|
|
30594
|
+
existing = fs10.readFileSync(filePath, "utf-8");
|
|
29186
30595
|
} catch {
|
|
29187
30596
|
}
|
|
29188
30597
|
if (existing === content) {
|
|
29189
|
-
|
|
30598
|
+
logger21.info("Skill already in sync", { name, bytes: content.length });
|
|
29190
30599
|
return;
|
|
29191
30600
|
}
|
|
29192
|
-
|
|
29193
|
-
|
|
29194
|
-
|
|
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
|
|
29204
|
-
import
|
|
29205
|
-
|
|
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
|
-
|
|
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 =
|
|
30716
|
+
const file2 = path17.join(dataDir, "bridge.lock");
|
|
29224
30717
|
lockPath = file2;
|
|
29225
|
-
if (
|
|
29226
|
-
const raw =
|
|
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 (
|
|
29230
|
-
throw new
|
|
30722
|
+
if (isLiveBridgeLockOwner(pid)) {
|
|
30723
|
+
throw new BridgeAlreadyRunningError(pid, dataDir);
|
|
29231
30724
|
}
|
|
29232
|
-
|
|
30725
|
+
logger22.info("Removing stale bridge.lock", { pid, path: file2 });
|
|
29233
30726
|
}
|
|
29234
30727
|
}
|
|
29235
|
-
|
|
29236
|
-
|
|
29237
|
-
|
|
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 &&
|
|
29246
|
-
const current =
|
|
30738
|
+
if (lockPath && fs11.existsSync(lockPath)) {
|
|
30739
|
+
const current = fs11.readFileSync(lockPath, "utf-8").trim();
|
|
29247
30740
|
if (current === String(process.pid)) {
|
|
29248
|
-
|
|
29249
|
-
|
|
30741
|
+
fs11.unlinkSync(lockPath);
|
|
30742
|
+
logger22.info("Released bridge lock", { path: lockPath });
|
|
29250
30743
|
}
|
|
29251
30744
|
}
|
|
29252
30745
|
} catch (e) {
|
|
29253
|
-
|
|
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
|
|
30808
|
+
var logger24 = createModuleLogger("msg.handler");
|
|
29290
30809
|
function emitTaskAck(emit, ackId, agentId, traceId) {
|
|
29291
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
29595
|
-
import
|
|
29596
|
-
var
|
|
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 =
|
|
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 (!
|
|
29637
|
-
const raw =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
29662
|
-
|
|
29663
|
-
|
|
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
|
-
|
|
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
|
|
29674
|
-
var
|
|
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
|
|
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
|
|
31280
|
+
return path20.dirname(realpathSync(sdkEntry));
|
|
29762
31281
|
} catch {
|
|
29763
|
-
return
|
|
31282
|
+
return path20.dirname(sdkEntry);
|
|
29764
31283
|
}
|
|
29765
31284
|
}
|
|
29766
31285
|
function findPnpmStoreDir(fromDir) {
|
|
29767
31286
|
let current = fromDir;
|
|
29768
|
-
while (current !==
|
|
29769
|
-
if (
|
|
29770
|
-
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 =
|
|
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
|
-
|
|
29806
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
29939
|
-
import * as
|
|
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
|
|
29944
|
-
var CLAUDE_PROJECTS_DIR =
|
|
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
|
|
31468
|
+
return path21.join(CLAUDE_PROJECTS_DIR, cwdToSlug(cwd));
|
|
29950
31469
|
}
|
|
29951
31470
|
function sessionFilePath(cwd, sessionId) {
|
|
29952
|
-
return
|
|
31471
|
+
return path21.join(sessionDirForCwd(cwd), `${sessionId}.jsonl`);
|
|
29953
31472
|
}
|
|
29954
31473
|
|
|
29955
31474
|
// src/forkAgentFiles.ts
|
|
29956
|
-
var
|
|
31475
|
+
var logger28 = createModuleLogger("bridge.forkAgentFiles");
|
|
29957
31476
|
async function forkAgentFiles(sourceAgentId, newAgentId, sourceWorkdir, newWorkdir, dataDir, sessionStore, sourceConversationId) {
|
|
29958
|
-
|
|
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
|
|
31486
|
+
const stat3 = await fs14.stat(sourceWorkdir).catch(() => null);
|
|
29968
31487
|
if (stat3?.isDirectory()) {
|
|
29969
|
-
await
|
|
29970
|
-
|
|
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
|
|
29976
|
-
|
|
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
|
-
|
|
31500
|
+
logger28.error("Workdir copy failed", { error: e });
|
|
29982
31501
|
throw e;
|
|
29983
31502
|
}
|
|
29984
|
-
const srcNotebook =
|
|
29985
|
-
const dstNotebookDir =
|
|
29986
|
-
const dstNotebook =
|
|
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
|
|
31507
|
+
const nbStat = await fs14.stat(srcNotebook).catch(() => null);
|
|
29989
31508
|
if (nbStat?.isFile()) {
|
|
29990
|
-
await
|
|
29991
|
-
await
|
|
29992
|
-
|
|
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
|
-
|
|
31516
|
+
logger28.info("Notebook copy skipped (source does not exist)", {
|
|
29998
31517
|
sourceAgentId
|
|
29999
31518
|
});
|
|
30000
31519
|
}
|
|
30001
31520
|
} catch (e) {
|
|
30002
|
-
|
|
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
|
|
31528
|
+
const srcStat = await fs14.stat(srcPath).catch(() => null);
|
|
30010
31529
|
if (srcStat?.isFile()) {
|
|
30011
31530
|
const dstDir = sessionDirForCwd(newWorkdir);
|
|
30012
|
-
await
|
|
30013
|
-
const dstPath =
|
|
30014
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31552
|
+
logger28.error("Session JSONL copy failed", { error: e, sourceAgentId, newAgentId });
|
|
30034
31553
|
}
|
|
30035
31554
|
} else {
|
|
30036
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
31578
|
+
import fs15 from "fs/promises";
|
|
30060
31579
|
import os10 from "os";
|
|
30061
|
-
import
|
|
31580
|
+
import path23 from "path";
|
|
30062
31581
|
import * as sdk4 from "@anthropic-ai/claude-agent-sdk";
|
|
30063
|
-
var
|
|
31582
|
+
var logger29 = createModuleLogger("bridge.modelQuerier");
|
|
30064
31583
|
async function listModels(queryFn, opts = {}) {
|
|
30065
31584
|
const t0 = Date.now();
|
|
30066
|
-
const cwd = opts.cwd ??
|
|
30067
|
-
await
|
|
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
|
-
|
|
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
|
|
31648
|
+
import fs16 from "fs/promises";
|
|
30128
31649
|
import os11 from "os";
|
|
30129
|
-
import
|
|
31650
|
+
import path24 from "path";
|
|
30130
31651
|
import * as sdk5 from "@anthropic-ai/claude-agent-sdk";
|
|
30131
|
-
var
|
|
30132
|
-
var OPTIMIZER_SYSTEM_PROMPT = `You are an expert prompt editor for
|
|
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
|
|
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 ??
|
|
30174
|
-
await
|
|
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
|
-
|
|
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
|
|
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 =
|
|
30262
|
-
const
|
|
31784
|
+
const rootClaudeDir = path25.join(process.env.HOME ?? "/root", ".claude");
|
|
31785
|
+
const fs17 = await import("fs/promises");
|
|
30263
31786
|
try {
|
|
30264
|
-
await
|
|
31787
|
+
await fs17.access(rootClaudeDir);
|
|
30265
31788
|
} catch {
|
|
30266
|
-
|
|
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 =
|
|
30272
|
-
const dest =
|
|
31794
|
+
const src = path25.join(rootClaudeDir, file2);
|
|
31795
|
+
const dest = path25.join(agentConfigDir, file2);
|
|
30273
31796
|
try {
|
|
30274
|
-
await
|
|
30275
|
-
|
|
31797
|
+
await fs17.copyFile(src, dest);
|
|
31798
|
+
logger31.info("Synced credential file", { file: file2, from: src, to: dest });
|
|
30276
31799
|
} catch {
|
|
30277
|
-
|
|
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
|
|
31805
|
+
const fs17 = await import("fs/promises");
|
|
30283
31806
|
try {
|
|
30284
|
-
await
|
|
31807
|
+
await fs17.chown(dirPath, uid, gid);
|
|
30285
31808
|
} catch {
|
|
30286
|
-
|
|
31809
|
+
logger31.debug("chown skipped", { dirPath, uid, gid });
|
|
30287
31810
|
}
|
|
30288
31811
|
let entries;
|
|
30289
31812
|
try {
|
|
30290
|
-
entries = await
|
|
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 =
|
|
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
|
|
31823
|
+
await fs17.chown(fullPath, uid, gid);
|
|
30301
31824
|
} catch {
|
|
30302
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
31882
|
+
const workdirOverrideStore = new LocalWorkdirOverrideStore(config2.dataDir);
|
|
31883
|
+
const memoryRoot = path25.join(config2.dataDir, "agent-memory");
|
|
30355
31884
|
const memoryStore = new AgentMemoryStore(memoryRoot);
|
|
30356
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32029
|
+
logger31.warn("list_models: endpoint not available, server will use fallback", { requestId });
|
|
30484
32030
|
} else {
|
|
30485
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32076
|
+
logger31.info("list_dir request received", { requestId, path: dirPath });
|
|
30531
32077
|
try {
|
|
30532
|
-
const resolved =
|
|
32078
|
+
const resolved = resolveBridgeWorkdirPath(dirPath);
|
|
30533
32079
|
if (resolved.remapped) {
|
|
30534
32080
|
ensureDir(resolved.path);
|
|
30535
|
-
|
|
32081
|
+
logger31.info("list_dir path resolved to local Bridge workspace", {
|
|
30536
32082
|
requestId,
|
|
30537
32083
|
requested: dirPath,
|
|
30538
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32105
|
+
logger31.info("write_file request received", { requestId, baseDir, relativePath });
|
|
30560
32106
|
try {
|
|
30561
|
-
const resolved =
|
|
32107
|
+
const resolved = resolveBridgeWorkdirPath(baseDir);
|
|
30562
32108
|
if (resolved.remapped) {
|
|
30563
32109
|
ensureDir(resolved.path);
|
|
30564
|
-
|
|
32110
|
+
logger31.info("write_file baseDir resolved to local Bridge workspace", {
|
|
30565
32111
|
requestId,
|
|
30566
32112
|
requested: baseDir,
|
|
30567
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32145
|
+
logger31.info("read_file request received", { requestId, path: filePath, baseDir });
|
|
30600
32146
|
try {
|
|
30601
|
-
const resolved =
|
|
30602
|
-
const resolvedBase = baseDir ?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30860
|
-
process.exit(1);
|
|
32470
|
+
handleBridgeStartError(e, "Bridge failed to start");
|
|
30861
32471
|
});
|