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