@fangyb/ahchat-bridge 0.1.8 → 0.1.10

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.
@@ -1,4525 +0,0 @@
1
- // src/config.ts
2
- import crypto from "crypto";
3
- import fs from "fs";
4
- import os from "os";
5
- import path from "path";
6
- var DEFAULT_QUERY_CONFIG = {
7
- maxActive: 50,
8
- idleTimeoutMs: 6e4,
9
- evictionIntervalMs: 3e4,
10
- statusReportIntervalMs: 6e4
11
- };
12
- function readEnvString(name, fallback) {
13
- const v = process.env[name];
14
- return v && v.length > 0 ? v : fallback;
15
- }
16
- function readEnvInt(name, fallback) {
17
- const v = process.env[name];
18
- if (!v || v.length === 0) return fallback;
19
- const n = Number.parseInt(v, 10);
20
- return Number.isFinite(n) && n > 0 ? n : fallback;
21
- }
22
- function generateStableBridgeId() {
23
- const raw = `${os.hostname()}:${os.userInfo().username}`;
24
- const hash = crypto.createHash("sha256").update(raw).digest("hex").slice(0, 12);
25
- return `bridge_${hash}`;
26
- }
27
- function tryReadJsonConfig(filePath) {
28
- try {
29
- if (!fs.existsSync(filePath)) return {};
30
- const raw = fs.readFileSync(filePath, "utf-8");
31
- const parsed = JSON.parse(raw);
32
- if (typeof parsed !== "object" || parsed === null) return {};
33
- return parsed;
34
- } catch {
35
- return {};
36
- }
37
- }
38
- function mergeQueryConfig(file) {
39
- const q = file.queryConfig;
40
- return {
41
- maxActive: readEnvInt("AHCHAT_BRIDGE_MAX_ACTIVE", q?.maxActive ?? DEFAULT_QUERY_CONFIG.maxActive),
42
- idleTimeoutMs: readEnvInt(
43
- "AHCHAT_BRIDGE_IDLE_TIMEOUT_MS",
44
- q?.idleTimeoutMs ?? DEFAULT_QUERY_CONFIG.idleTimeoutMs
45
- ),
46
- evictionIntervalMs: readEnvInt(
47
- "AHCHAT_BRIDGE_EVICTION_INTERVAL_MS",
48
- q?.evictionIntervalMs ?? DEFAULT_QUERY_CONFIG.evictionIntervalMs
49
- ),
50
- statusReportIntervalMs: readEnvInt(
51
- "AHCHAT_BRIDGE_STATUS_REPORT_MS",
52
- q?.statusReportIntervalMs ?? DEFAULT_QUERY_CONFIG.statusReportIntervalMs
53
- )
54
- };
55
- }
56
- function loadBridgeConfig() {
57
- const dataDir = readEnvString(
58
- "AHCHAT_DATA_DIR",
59
- path.join(os.homedir(), ".ahchat")
60
- );
61
- const fileConfig = tryReadJsonConfig(path.join(dataDir, "bridge.json"));
62
- return {
63
- serverUrl: readEnvString(
64
- "AHCHAT_BRIDGE_SERVER_URL",
65
- fileConfig.serverUrl ?? "ws://localhost:3001/ws/bridge"
66
- ),
67
- bridgeId: readEnvString(
68
- "AHCHAT_BRIDGE_ID",
69
- fileConfig.bridgeId ?? generateStableBridgeId()
70
- ),
71
- logLevel: readEnvString(
72
- "AHCHAT_LOG_LEVEL",
73
- fileConfig.logLevel ?? "INFO"
74
- ),
75
- dataDir,
76
- dbPath: readEnvString(
77
- "AHCHAT_DB_PATH",
78
- fileConfig.dbPath ?? path.join(dataDir, "data.db")
79
- ),
80
- serverApiUrl: readEnvString(
81
- "AHCHAT_SERVER_API_URL",
82
- fileConfig.serverApiUrl ?? "http://localhost:3001"
83
- ),
84
- queryConfig: mergeQueryConfig(fileConfig)
85
- };
86
- }
87
- function ensureDir(dirPath) {
88
- fs.mkdirSync(dirPath, { recursive: true });
89
- }
90
-
91
- // src/logger.ts
92
- import os3 from "os";
93
- import path3 from "path";
94
-
95
- // ../logger/src/types.ts
96
- var LOG_LEVEL_VALUE = {
97
- TRACE: 0,
98
- DEBUG: 1,
99
- INFO: 2,
100
- WARN: 3,
101
- ERROR: 4,
102
- FATAL: 5
103
- };
104
-
105
- // ../logger/src/logger.ts
106
- function serializeError(err) {
107
- if (err instanceof Error) {
108
- return { name: err.name, message: err.message, stack: err.stack };
109
- }
110
- if (typeof err === "object" && err !== null && "message" in err) {
111
- const o = err;
112
- return {
113
- name: typeof o.name === "string" ? o.name : "Error",
114
- message: String(o.message),
115
- ...typeof o.stack === "string" ? { stack: o.stack } : {}
116
- };
117
- }
118
- return { name: "Error", message: String(err) };
119
- }
120
- function stripReservedFields(data) {
121
- if (!data) return void 0;
122
- const rest = { ...data };
123
- delete rest.traceId;
124
- delete rest.error;
125
- return Object.keys(rest).length > 0 ? rest : void 0;
126
- }
127
- var Logger = class {
128
- config;
129
- levelValue;
130
- constructor(config) {
131
- this.config = config;
132
- this.levelValue = LOG_LEVEL_VALUE[config.level];
133
- }
134
- trace(msg, data) {
135
- this.log("TRACE", msg, data);
136
- }
137
- debug(msg, data) {
138
- this.log("DEBUG", msg, data);
139
- }
140
- info(msg, data) {
141
- this.log("INFO", msg, data);
142
- }
143
- warn(msg, data) {
144
- this.log("WARN", msg, data);
145
- }
146
- error(msg, data) {
147
- this.log("ERROR", msg, data);
148
- }
149
- fatal(msg, data) {
150
- this.log("FATAL", msg, data);
151
- }
152
- log(level, msg, data) {
153
- if (LOG_LEVEL_VALUE[level] < this.levelValue) return;
154
- const traceId = typeof data?.traceId === "string" ? data.traceId : void 0;
155
- const hasError = data && "error" in data && data.error !== void 0;
156
- const error = hasError ? serializeError(data.error) : void 0;
157
- const rest = stripReservedFields(data);
158
- const entry = {
159
- ts: (/* @__PURE__ */ new Date()).toISOString(),
160
- level,
161
- source: this.config.source,
162
- module: this.config.module,
163
- msg,
164
- ...traceId ? { traceId } : {},
165
- ...error ? { error } : {},
166
- ...rest ? { data: rest } : {}
167
- };
168
- for (const transport of this.config.transports) {
169
- transport(entry);
170
- }
171
- }
172
- };
173
-
174
- // ../logger/src/formatters/json.ts
175
- var jsonFormatter = (entry) => JSON.stringify(entry);
176
-
177
- // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
178
- var ANSI_BACKGROUND_OFFSET = 10;
179
- var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
180
- var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
181
- var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
182
- var styles = {
183
- modifier: {
184
- reset: [0, 0],
185
- // 21 isn't widely supported and 22 does the same thing
186
- bold: [1, 22],
187
- dim: [2, 22],
188
- italic: [3, 23],
189
- underline: [4, 24],
190
- overline: [53, 55],
191
- inverse: [7, 27],
192
- hidden: [8, 28],
193
- strikethrough: [9, 29]
194
- },
195
- color: {
196
- black: [30, 39],
197
- red: [31, 39],
198
- green: [32, 39],
199
- yellow: [33, 39],
200
- blue: [34, 39],
201
- magenta: [35, 39],
202
- cyan: [36, 39],
203
- white: [37, 39],
204
- // Bright color
205
- blackBright: [90, 39],
206
- gray: [90, 39],
207
- // Alias of `blackBright`
208
- grey: [90, 39],
209
- // Alias of `blackBright`
210
- redBright: [91, 39],
211
- greenBright: [92, 39],
212
- yellowBright: [93, 39],
213
- blueBright: [94, 39],
214
- magentaBright: [95, 39],
215
- cyanBright: [96, 39],
216
- whiteBright: [97, 39]
217
- },
218
- bgColor: {
219
- bgBlack: [40, 49],
220
- bgRed: [41, 49],
221
- bgGreen: [42, 49],
222
- bgYellow: [43, 49],
223
- bgBlue: [44, 49],
224
- bgMagenta: [45, 49],
225
- bgCyan: [46, 49],
226
- bgWhite: [47, 49],
227
- // Bright color
228
- bgBlackBright: [100, 49],
229
- bgGray: [100, 49],
230
- // Alias of `bgBlackBright`
231
- bgGrey: [100, 49],
232
- // Alias of `bgBlackBright`
233
- bgRedBright: [101, 49],
234
- bgGreenBright: [102, 49],
235
- bgYellowBright: [103, 49],
236
- bgBlueBright: [104, 49],
237
- bgMagentaBright: [105, 49],
238
- bgCyanBright: [106, 49],
239
- bgWhiteBright: [107, 49]
240
- }
241
- };
242
- var modifierNames = Object.keys(styles.modifier);
243
- var foregroundColorNames = Object.keys(styles.color);
244
- var backgroundColorNames = Object.keys(styles.bgColor);
245
- var colorNames = [...foregroundColorNames, ...backgroundColorNames];
246
- function assembleStyles() {
247
- const codes = /* @__PURE__ */ new Map();
248
- for (const [groupName, group] of Object.entries(styles)) {
249
- for (const [styleName, style] of Object.entries(group)) {
250
- styles[styleName] = {
251
- open: `\x1B[${style[0]}m`,
252
- close: `\x1B[${style[1]}m`
253
- };
254
- group[styleName] = styles[styleName];
255
- codes.set(style[0], style[1]);
256
- }
257
- Object.defineProperty(styles, groupName, {
258
- value: group,
259
- enumerable: false
260
- });
261
- }
262
- Object.defineProperty(styles, "codes", {
263
- value: codes,
264
- enumerable: false
265
- });
266
- styles.color.close = "\x1B[39m";
267
- styles.bgColor.close = "\x1B[49m";
268
- styles.color.ansi = wrapAnsi16();
269
- styles.color.ansi256 = wrapAnsi256();
270
- styles.color.ansi16m = wrapAnsi16m();
271
- styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
272
- styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
273
- styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
274
- Object.defineProperties(styles, {
275
- rgbToAnsi256: {
276
- value(red, green, blue) {
277
- if (red === green && green === blue) {
278
- if (red < 8) {
279
- return 16;
280
- }
281
- if (red > 248) {
282
- return 231;
283
- }
284
- return Math.round((red - 8) / 247 * 24) + 232;
285
- }
286
- return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
287
- },
288
- enumerable: false
289
- },
290
- hexToRgb: {
291
- value(hex) {
292
- const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
293
- if (!matches) {
294
- return [0, 0, 0];
295
- }
296
- let [colorString] = matches;
297
- if (colorString.length === 3) {
298
- colorString = [...colorString].map((character) => character + character).join("");
299
- }
300
- const integer = Number.parseInt(colorString, 16);
301
- return [
302
- /* eslint-disable no-bitwise */
303
- integer >> 16 & 255,
304
- integer >> 8 & 255,
305
- integer & 255
306
- /* eslint-enable no-bitwise */
307
- ];
308
- },
309
- enumerable: false
310
- },
311
- hexToAnsi256: {
312
- value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
313
- enumerable: false
314
- },
315
- ansi256ToAnsi: {
316
- value(code) {
317
- if (code < 8) {
318
- return 30 + code;
319
- }
320
- if (code < 16) {
321
- return 90 + (code - 8);
322
- }
323
- let red;
324
- let green;
325
- let blue;
326
- if (code >= 232) {
327
- red = ((code - 232) * 10 + 8) / 255;
328
- green = red;
329
- blue = red;
330
- } else {
331
- code -= 16;
332
- const remainder = code % 36;
333
- red = Math.floor(code / 36) / 5;
334
- green = Math.floor(remainder / 6) / 5;
335
- blue = remainder % 6 / 5;
336
- }
337
- const value = Math.max(red, green, blue) * 2;
338
- if (value === 0) {
339
- return 30;
340
- }
341
- let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
342
- if (value === 2) {
343
- result += 60;
344
- }
345
- return result;
346
- },
347
- enumerable: false
348
- },
349
- rgbToAnsi: {
350
- value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
351
- enumerable: false
352
- },
353
- hexToAnsi: {
354
- value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
355
- enumerable: false
356
- }
357
- });
358
- return styles;
359
- }
360
- var ansiStyles = assembleStyles();
361
- var ansi_styles_default = ansiStyles;
362
-
363
- // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
364
- import process2 from "process";
365
- import os2 from "os";
366
- import tty from "tty";
367
- function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
368
- const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
369
- const position = argv.indexOf(prefix + flag);
370
- const terminatorPosition = argv.indexOf("--");
371
- return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
372
- }
373
- var { env } = process2;
374
- var flagForceColor;
375
- if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
376
- flagForceColor = 0;
377
- } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
378
- flagForceColor = 1;
379
- }
380
- function envForceColor() {
381
- if ("FORCE_COLOR" in env) {
382
- if (env.FORCE_COLOR === "true") {
383
- return 1;
384
- }
385
- if (env.FORCE_COLOR === "false") {
386
- return 0;
387
- }
388
- return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
389
- }
390
- }
391
- function translateLevel(level) {
392
- if (level === 0) {
393
- return false;
394
- }
395
- return {
396
- level,
397
- hasBasic: true,
398
- has256: level >= 2,
399
- has16m: level >= 3
400
- };
401
- }
402
- function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
403
- const noFlagForceColor = envForceColor();
404
- if (noFlagForceColor !== void 0) {
405
- flagForceColor = noFlagForceColor;
406
- }
407
- const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
408
- if (forceColor === 0) {
409
- return 0;
410
- }
411
- if (sniffFlags) {
412
- if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
413
- return 3;
414
- }
415
- if (hasFlag("color=256")) {
416
- return 2;
417
- }
418
- }
419
- if ("TF_BUILD" in env && "AGENT_NAME" in env) {
420
- return 1;
421
- }
422
- if (haveStream && !streamIsTTY && forceColor === void 0) {
423
- return 0;
424
- }
425
- const min = forceColor || 0;
426
- if (env.TERM === "dumb") {
427
- return min;
428
- }
429
- if (process2.platform === "win32") {
430
- const osRelease = os2.release().split(".");
431
- if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
432
- return Number(osRelease[2]) >= 14931 ? 3 : 2;
433
- }
434
- return 1;
435
- }
436
- if ("CI" in env) {
437
- if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
438
- return 3;
439
- }
440
- if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
441
- return 1;
442
- }
443
- return min;
444
- }
445
- if ("TEAMCITY_VERSION" in env) {
446
- return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
447
- }
448
- if (env.COLORTERM === "truecolor") {
449
- return 3;
450
- }
451
- if (env.TERM === "xterm-kitty") {
452
- return 3;
453
- }
454
- if (env.TERM === "xterm-ghostty") {
455
- return 3;
456
- }
457
- if (env.TERM === "wezterm") {
458
- return 3;
459
- }
460
- if ("TERM_PROGRAM" in env) {
461
- const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
462
- switch (env.TERM_PROGRAM) {
463
- case "iTerm.app": {
464
- return version >= 3 ? 3 : 2;
465
- }
466
- case "Apple_Terminal": {
467
- return 2;
468
- }
469
- }
470
- }
471
- if (/-256(color)?$/i.test(env.TERM)) {
472
- return 2;
473
- }
474
- if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
475
- return 1;
476
- }
477
- if ("COLORTERM" in env) {
478
- return 1;
479
- }
480
- return min;
481
- }
482
- function createSupportsColor(stream, options = {}) {
483
- const level = _supportsColor(stream, {
484
- streamIsTTY: stream && stream.isTTY,
485
- ...options
486
- });
487
- return translateLevel(level);
488
- }
489
- var supportsColor = {
490
- stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
491
- stderr: createSupportsColor({ isTTY: tty.isatty(2) })
492
- };
493
- var supports_color_default = supportsColor;
494
-
495
- // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/utilities.js
496
- function stringReplaceAll(string, substring, replacer) {
497
- let index = string.indexOf(substring);
498
- if (index === -1) {
499
- return string;
500
- }
501
- const substringLength = substring.length;
502
- let endIndex = 0;
503
- let returnValue = "";
504
- do {
505
- returnValue += string.slice(endIndex, index) + substring + replacer;
506
- endIndex = index + substringLength;
507
- index = string.indexOf(substring, endIndex);
508
- } while (index !== -1);
509
- returnValue += string.slice(endIndex);
510
- return returnValue;
511
- }
512
- function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
513
- let endIndex = 0;
514
- let returnValue = "";
515
- do {
516
- const gotCR = string[index - 1] === "\r";
517
- returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix;
518
- endIndex = index + 1;
519
- index = string.indexOf("\n", endIndex);
520
- } while (index !== -1);
521
- returnValue += string.slice(endIndex);
522
- return returnValue;
523
- }
524
-
525
- // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/index.js
526
- var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
527
- var GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
528
- var STYLER = /* @__PURE__ */ Symbol("STYLER");
529
- var IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
530
- var levelMapping = [
531
- "ansi",
532
- "ansi",
533
- "ansi256",
534
- "ansi16m"
535
- ];
536
- var styles2 = /* @__PURE__ */ Object.create(null);
537
- var applyOptions = (object, options = {}) => {
538
- if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
539
- throw new Error("The `level` option should be an integer from 0 to 3");
540
- }
541
- const colorLevel = stdoutColor ? stdoutColor.level : 0;
542
- object.level = options.level === void 0 ? colorLevel : options.level;
543
- };
544
- var chalkFactory = (options) => {
545
- const chalk2 = (...strings) => strings.join(" ");
546
- applyOptions(chalk2, options);
547
- Object.setPrototypeOf(chalk2, createChalk.prototype);
548
- return chalk2;
549
- };
550
- function createChalk(options) {
551
- return chalkFactory(options);
552
- }
553
- Object.setPrototypeOf(createChalk.prototype, Function.prototype);
554
- for (const [styleName, style] of Object.entries(ansi_styles_default)) {
555
- styles2[styleName] = {
556
- get() {
557
- const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
558
- Object.defineProperty(this, styleName, { value: builder });
559
- return builder;
560
- }
561
- };
562
- }
563
- styles2.visible = {
564
- get() {
565
- const builder = createBuilder(this, this[STYLER], true);
566
- Object.defineProperty(this, "visible", { value: builder });
567
- return builder;
568
- }
569
- };
570
- var getModelAnsi = (model, level, type, ...arguments_) => {
571
- if (model === "rgb") {
572
- if (level === "ansi16m") {
573
- return ansi_styles_default[type].ansi16m(...arguments_);
574
- }
575
- if (level === "ansi256") {
576
- return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
577
- }
578
- return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
579
- }
580
- if (model === "hex") {
581
- return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
582
- }
583
- return ansi_styles_default[type][model](...arguments_);
584
- };
585
- var usedModels = ["rgb", "hex", "ansi256"];
586
- for (const model of usedModels) {
587
- styles2[model] = {
588
- get() {
589
- const { level } = this;
590
- return function(...arguments_) {
591
- const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
592
- return createBuilder(this, styler, this[IS_EMPTY]);
593
- };
594
- }
595
- };
596
- const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
597
- styles2[bgModel] = {
598
- get() {
599
- const { level } = this;
600
- return function(...arguments_) {
601
- const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
602
- return createBuilder(this, styler, this[IS_EMPTY]);
603
- };
604
- }
605
- };
606
- }
607
- var proto = Object.defineProperties(() => {
608
- }, {
609
- ...styles2,
610
- level: {
611
- enumerable: true,
612
- get() {
613
- return this[GENERATOR].level;
614
- },
615
- set(level) {
616
- this[GENERATOR].level = level;
617
- }
618
- }
619
- });
620
- var createStyler = (open2, close, parent) => {
621
- let openAll;
622
- let closeAll;
623
- if (parent === void 0) {
624
- openAll = open2;
625
- closeAll = close;
626
- } else {
627
- openAll = parent.openAll + open2;
628
- closeAll = close + parent.closeAll;
629
- }
630
- return {
631
- open: open2,
632
- close,
633
- openAll,
634
- closeAll,
635
- parent
636
- };
637
- };
638
- var createBuilder = (self, _styler, _isEmpty) => {
639
- const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
640
- Object.setPrototypeOf(builder, proto);
641
- builder[GENERATOR] = self;
642
- builder[STYLER] = _styler;
643
- builder[IS_EMPTY] = _isEmpty;
644
- return builder;
645
- };
646
- var applyStyle = (self, string) => {
647
- if (self.level <= 0 || !string) {
648
- return self[IS_EMPTY] ? "" : string;
649
- }
650
- let styler = self[STYLER];
651
- if (styler === void 0) {
652
- return string;
653
- }
654
- const { openAll, closeAll } = styler;
655
- if (string.includes("\x1B")) {
656
- while (styler !== void 0) {
657
- string = stringReplaceAll(string, styler.close, styler.open);
658
- styler = styler.parent;
659
- }
660
- }
661
- const lfIndex = string.indexOf("\n");
662
- if (lfIndex !== -1) {
663
- string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
664
- }
665
- return openAll + string + closeAll;
666
- };
667
- Object.defineProperties(createChalk.prototype, styles2);
668
- var chalk = createChalk();
669
- var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
670
- var source_default = chalk;
671
-
672
- // ../logger/src/formatters/pretty.ts
673
- var LEVEL_COLOR = {
674
- TRACE: source_default.gray,
675
- DEBUG: source_default.cyan,
676
- INFO: source_default.green,
677
- WARN: source_default.yellow,
678
- ERROR: source_default.red,
679
- FATAL: source_default.bgRed.white
680
- };
681
- function formatLocalTs(iso) {
682
- const d = new Date(iso);
683
- const pad = (n, w = 2) => String(n).padStart(w, "0");
684
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
685
- }
686
- var prettyFormatter = (entry) => {
687
- const ts = formatLocalTs(entry.ts);
688
- const level = LEVEL_COLOR[entry.level](entry.level.padEnd(5));
689
- const scope = source_default.gray(`[${entry.source}:${entry.module}]`);
690
- const data = entry.data && Object.keys(entry.data).length > 0 ? ` ${source_default.gray(JSON.stringify(entry.data))}` : "";
691
- const trace = entry.traceId ? source_default.gray(` traceId=${entry.traceId}`) : "";
692
- const errPart = entry.error ? source_default.red(
693
- ` ${entry.error.name}: ${entry.error.message}${entry.error.stack ? `
694
- ${entry.error.stack}` : ""}`
695
- ) : "";
696
- return `${ts} ${level} ${scope} ${entry.msg}${data}${trace}${errPart}`;
697
- };
698
-
699
- // ../logger/src/transports/console.ts
700
- function consoleTransport(opts) {
701
- const fmt = opts?.formatter ?? jsonFormatter;
702
- return (entry) => {
703
- const line = fmt(entry);
704
- if (LOG_LEVEL_VALUE[entry.level] >= LOG_LEVEL_VALUE.ERROR) {
705
- console.error(line);
706
- } else {
707
- console.log(line);
708
- }
709
- };
710
- }
711
-
712
- // ../logger/src/transports/file.ts
713
- import path2 from "path";
714
-
715
- // ../../node_modules/.pnpm/rotating-file-stream@3.2.9/node_modules/rotating-file-stream/dist/esm/index.js
716
- import { exec } from "child_process";
717
- import { createGzip } from "zlib";
718
- import { Writable } from "stream";
719
- import { access, constants, createReadStream, createWriteStream } from "fs";
720
- import { mkdir, open, readFile, rename, stat, unlink, writeFile } from "fs/promises";
721
- import { sep } from "path";
722
- import { TextDecoder } from "util";
723
- import { setTimeout as setTimeout2 } from "timers";
724
- async function exists(filename) {
725
- return new Promise((resolve) => access(filename, constants.F_OK, (error) => resolve(!error)));
726
- }
727
- var RotatingFileStreamError = class extends Error {
728
- code = "RFS-TOO-MANY";
729
- constructor() {
730
- super("Too many destination file attempts");
731
- }
732
- };
733
- var RotatingFileStream = class extends Writable {
734
- createGzip;
735
- exec;
736
- file;
737
- filename;
738
- finished;
739
- fsCreateReadStream;
740
- fsCreateWriteStream;
741
- fsOpen;
742
- fsReadFile;
743
- fsStat;
744
- fsUnlink;
745
- generator;
746
- initPromise;
747
- last;
748
- maxTimeout;
749
- next;
750
- options;
751
- prev;
752
- rotation;
753
- size;
754
- stdout;
755
- timeout;
756
- timeoutPromise;
757
- constructor(generator, options) {
758
- const { encoding, history, maxFiles, maxSize, path: path10 } = options;
759
- super({ decodeStrings: true, defaultEncoding: encoding });
760
- this.createGzip = createGzip;
761
- this.exec = exec;
762
- this.filename = path10 + generator(null);
763
- this.fsCreateReadStream = createReadStream;
764
- this.fsCreateWriteStream = createWriteStream;
765
- this.fsOpen = open;
766
- this.fsReadFile = readFile;
767
- this.fsStat = stat;
768
- this.fsUnlink = unlink;
769
- this.generator = generator;
770
- this.maxTimeout = 2147483640;
771
- this.options = options;
772
- this.stdout = process.stdout;
773
- if (maxFiles || maxSize)
774
- options.history = path10 + (history ? history : this.generator(null) + ".txt");
775
- this.on("close", () => this.finished ? null : this.emit("finish"));
776
- this.on("finish", () => this.finished = this.clear());
777
- (async () => {
778
- try {
779
- this.initPromise = this.init();
780
- await this.initPromise;
781
- delete this.initPromise;
782
- } catch (e) {
783
- }
784
- })();
785
- }
786
- _destroy(error, callback) {
787
- this.refinal(error, callback);
788
- }
789
- _final(callback) {
790
- this.refinal(void 0, callback);
791
- }
792
- _write(chunk, encoding, callback) {
793
- this.rewrite([{ chunk, encoding }], 0, callback);
794
- }
795
- _writev(chunks, callback) {
796
- this.rewrite(chunks, 0, callback);
797
- }
798
- async refinal(error, callback) {
799
- try {
800
- this.clear();
801
- if (this.initPromise)
802
- await this.initPromise;
803
- if (this.timeoutPromise)
804
- await this.timeoutPromise;
805
- await this.reclose();
806
- } catch (e) {
807
- return callback(error || e);
808
- }
809
- callback(error);
810
- }
811
- async rewrite(chunks, index, callback) {
812
- const { size, teeToStdout } = this.options;
813
- try {
814
- if (this.initPromise)
815
- await this.initPromise;
816
- for (let i = 0; i < chunks.length; ++i) {
817
- const { chunk } = chunks[i];
818
- this.size += chunk.length;
819
- if (this.timeoutPromise)
820
- await this.timeoutPromise;
821
- await this.file.write(chunk);
822
- if (teeToStdout && !this.stdout.destroyed)
823
- this.stdout.write(chunk);
824
- if (size && this.size >= size)
825
- await this.rotate();
826
- }
827
- } catch (e) {
828
- return callback(e);
829
- }
830
- callback();
831
- }
832
- async init() {
833
- const { immutable, initialRotation, interval, size } = this.options;
834
- if (immutable)
835
- return new Promise((resolve, reject) => process.nextTick(() => this.immutate(true).then(resolve).catch(reject)));
836
- let stats;
837
- try {
838
- stats = await stat(this.filename);
839
- } catch (e) {
840
- if (e.code !== "ENOENT")
841
- throw e;
842
- return this.reopen(0);
843
- }
844
- if (!stats.isFile())
845
- throw new Error(`Can't write on: ${this.filename} (it is not a file)`);
846
- if (initialRotation) {
847
- this.intervalBounds(this.now());
848
- const prev = this.prev;
849
- this.intervalBounds(new Date(stats.mtime.getTime()));
850
- if (prev !== this.prev)
851
- return this.rotate();
852
- }
853
- this.size = stats.size;
854
- if (!size || stats.size < size)
855
- return this.reopen(stats.size);
856
- if (interval)
857
- this.intervalBounds(this.now());
858
- return this.rotate();
859
- }
860
- async makePath(name) {
861
- return mkdir(name.split(sep).slice(0, -1).join(sep), { recursive: true });
862
- }
863
- async reopen(size) {
864
- let file;
865
- try {
866
- file = await open(this.filename, "a", this.options.mode);
867
- } catch (e) {
868
- if (e.code !== "ENOENT")
869
- throw e;
870
- await this.makePath(this.filename);
871
- file = await open(this.filename, "a", this.options.mode);
872
- }
873
- this.file = file;
874
- this.size = size;
875
- this.interval();
876
- this.emit("open", this.filename);
877
- }
878
- async reclose() {
879
- const { file } = this;
880
- if (!file)
881
- return;
882
- delete this.file;
883
- return file.close();
884
- }
885
- now() {
886
- return /* @__PURE__ */ new Date();
887
- }
888
- async rotate() {
889
- const { immutable, rotate } = this.options;
890
- this.size = 0;
891
- this.rotation = this.now();
892
- this.clear();
893
- this.emit("rotation");
894
- await this.reclose();
895
- if (rotate)
896
- return this.classical();
897
- if (immutable)
898
- return this.immutate(false);
899
- return this.move();
900
- }
901
- async findName() {
902
- const { interval, path: path10, intervalBoundary } = this.options;
903
- for (let index = 1; index < 1e3; ++index) {
904
- const filename = path10 + this.generator(interval && intervalBoundary ? new Date(this.prev) : this.rotation, index);
905
- if (!await exists(filename))
906
- return filename;
907
- }
908
- throw new RotatingFileStreamError();
909
- }
910
- async move() {
911
- const { compress } = this.options;
912
- const filename = await this.findName();
913
- await this.touch(filename);
914
- if (compress)
915
- await this.compress(filename);
916
- else
917
- await rename(this.filename, filename);
918
- return this.rotated(filename);
919
- }
920
- async touch(filename) {
921
- let file;
922
- try {
923
- file = await this.fsOpen(filename, "a");
924
- } catch (e) {
925
- if (e.code !== "ENOENT")
926
- throw e;
927
- await this.makePath(filename);
928
- file = await open(filename, "a");
929
- }
930
- await file.close();
931
- return this.unlink(filename);
932
- }
933
- async classical() {
934
- const { compress, path: path10, rotate } = this.options;
935
- let rotatedName = "";
936
- for (let count = rotate; count > 0; --count) {
937
- const currName = path10 + this.generator(count);
938
- const prevName = count === 1 ? this.filename : path10 + this.generator(count - 1);
939
- if (!await exists(prevName))
940
- continue;
941
- if (!rotatedName)
942
- rotatedName = currName;
943
- if (count === 1 && compress)
944
- await this.compress(currName);
945
- else {
946
- try {
947
- await rename(prevName, currName);
948
- } catch (e) {
949
- if (e.code !== "ENOENT")
950
- throw e;
951
- await this.makePath(currName);
952
- await rename(prevName, currName);
953
- }
954
- }
955
- }
956
- return this.rotated(rotatedName);
957
- }
958
- clear() {
959
- if (this.timeout) {
960
- clearTimeout(this.timeout);
961
- this.timeout = null;
962
- }
963
- return true;
964
- }
965
- intervalBoundsBig(now) {
966
- const year = this.options.intervalUTC ? now.getUTCFullYear() : now.getFullYear();
967
- let month = this.options.intervalUTC ? now.getUTCMonth() : now.getMonth();
968
- let day = this.options.intervalUTC ? now.getUTCDate() : now.getDate();
969
- let hours = this.options.intervalUTC ? now.getUTCHours() : now.getHours();
970
- const { num, unit } = this.options.interval;
971
- if (unit === "M") {
972
- day = 1;
973
- hours = 0;
974
- } else if (unit === "d")
975
- hours = 0;
976
- else
977
- hours = parseInt(hours / num, 10) * num;
978
- this.prev = new Date(year, month, day, hours, 0, 0, 0).getTime();
979
- if (unit === "M")
980
- month += num;
981
- else if (unit === "d")
982
- day += num;
983
- else
984
- hours += num;
985
- this.next = new Date(year, month, day, hours, 0, 0, 0).getTime();
986
- }
987
- intervalBounds(now) {
988
- const unit = this.options.interval.unit;
989
- if (unit === "M" || unit === "d" || unit === "h")
990
- this.intervalBoundsBig(now);
991
- else {
992
- let period = 1e3 * this.options.interval.num;
993
- if (unit === "m")
994
- period *= 60;
995
- this.prev = parseInt(now.getTime() / period, 10) * period;
996
- this.next = this.prev + period;
997
- }
998
- return new Date(this.prev);
999
- }
1000
- interval() {
1001
- if (!this.options.interval)
1002
- return;
1003
- this.intervalBounds(this.now());
1004
- const set = async () => {
1005
- const time = this.next - this.now().getTime();
1006
- if (time <= 0) {
1007
- try {
1008
- this.timeoutPromise = this.rotate();
1009
- await this.timeoutPromise;
1010
- delete this.timeoutPromise;
1011
- } catch (e) {
1012
- }
1013
- } else {
1014
- this.timeout = setTimeout2(set, time > this.maxTimeout ? this.maxTimeout : time);
1015
- this.timeout.unref();
1016
- }
1017
- };
1018
- set();
1019
- }
1020
- async compress(filename) {
1021
- const { compress } = this.options;
1022
- if (typeof compress === "function") {
1023
- await new Promise((resolve, reject) => {
1024
- this.exec(compress(this.filename, filename), (error, stdout, stderr) => {
1025
- this.emit("external", stdout, stderr);
1026
- error ? reject(error) : resolve();
1027
- });
1028
- });
1029
- } else
1030
- await this.gzip(filename);
1031
- return this.unlink(this.filename);
1032
- }
1033
- async gzip(filename) {
1034
- const { mode } = this.options;
1035
- const options = mode ? { mode } : {};
1036
- const inp = this.fsCreateReadStream(this.filename, {});
1037
- const out = this.fsCreateWriteStream(filename, options);
1038
- const zip = this.createGzip();
1039
- await new Promise((resolve, reject) => {
1040
- inp.once("error", reject);
1041
- out.once("error", reject);
1042
- zip.once("error", reject);
1043
- out.once("finish", resolve);
1044
- inp.pipe(zip).pipe(out);
1045
- });
1046
- await Promise.all([
1047
- new Promise((resolve) => zip.close(resolve)),
1048
- new Promise((resolve) => out.close((err) => {
1049
- if (err)
1050
- this.emit("warning", err);
1051
- resolve();
1052
- }))
1053
- ]);
1054
- }
1055
- async rotated(filename) {
1056
- const { maxFiles, maxSize } = this.options;
1057
- if (maxFiles || maxSize)
1058
- await this.history(filename);
1059
- this.emit("rotated", filename);
1060
- return this.reopen(0);
1061
- }
1062
- async history(filename) {
1063
- const { history, maxFiles, maxSize } = this.options;
1064
- const res = [];
1065
- let files = [filename];
1066
- try {
1067
- const content = await this.fsReadFile(history, "utf8");
1068
- files = [...content.toString().split("\n"), filename];
1069
- } catch (e) {
1070
- if (e.code !== "ENOENT")
1071
- throw e;
1072
- }
1073
- for (const file of files) {
1074
- if (file) {
1075
- try {
1076
- const stats = await this.fsStat(file);
1077
- if (stats.isFile()) {
1078
- res.push({
1079
- name: file,
1080
- size: stats.size,
1081
- time: stats.mtime.getTime()
1082
- });
1083
- } else
1084
- this.emit("warning", new Error(`File '${file}' contained in history is not a regular file`));
1085
- } catch (e) {
1086
- if (e.code !== "ENOENT")
1087
- throw e;
1088
- }
1089
- }
1090
- }
1091
- res.sort((a, b) => a.time - b.time);
1092
- if (maxFiles) {
1093
- while (res.length > maxFiles) {
1094
- const file = res.shift();
1095
- await this.unlink(file.name);
1096
- this.emit("removed", file.name, true);
1097
- }
1098
- }
1099
- if (maxSize) {
1100
- while (res.reduce((size, file) => size + file.size, 0) > maxSize) {
1101
- const file = res.shift();
1102
- await this.unlink(file.name);
1103
- this.emit("removed", file.name, false);
1104
- }
1105
- }
1106
- await writeFile(history, res.map((e) => e.name).join("\n") + "\n", "utf-8");
1107
- this.emit("history");
1108
- }
1109
- async immutate(first) {
1110
- const { size } = this.options;
1111
- const now = this.now();
1112
- for (let index = 1; index < 1e3; ++index) {
1113
- let fileSize = 0;
1114
- let stats = void 0;
1115
- this.filename = this.options.path + this.generator(now, index);
1116
- try {
1117
- stats = await this.fsStat(this.filename);
1118
- } catch (e) {
1119
- if (e.code !== "ENOENT")
1120
- throw e;
1121
- }
1122
- if (stats) {
1123
- fileSize = stats.size;
1124
- if (!stats.isFile())
1125
- throw new Error(`Can't write on: '${this.filename}' (it is not a file)`);
1126
- if (size && fileSize >= size)
1127
- continue;
1128
- }
1129
- if (first) {
1130
- this.last = this.filename;
1131
- return this.reopen(fileSize);
1132
- }
1133
- await this.rotated(this.last);
1134
- this.last = this.filename;
1135
- return;
1136
- }
1137
- throw new RotatingFileStreamError();
1138
- }
1139
- async unlink(filename) {
1140
- try {
1141
- await this.fsUnlink(filename);
1142
- } catch (e) {
1143
- if (e.code !== "ENOENT")
1144
- throw e;
1145
- this.emit("warning", e);
1146
- }
1147
- }
1148
- };
1149
- function buildNumberCheck(field) {
1150
- return (type, options, value) => {
1151
- const converted = parseInt(value, 10);
1152
- if (type !== "number" || converted !== value || converted <= 0)
1153
- throw new Error(`'${field}' option must be a positive integer number`);
1154
- };
1155
- }
1156
- function buildStringCheck(field, check) {
1157
- return (type, options, value) => {
1158
- if (type !== "string")
1159
- throw new Error(`Don't know how to handle 'options.${field}' type: ${type}`);
1160
- options[field] = check(value);
1161
- };
1162
- }
1163
- function checkMeasure(value, what, units) {
1164
- const ret = {};
1165
- ret.num = parseInt(value, 10);
1166
- if (isNaN(ret.num))
1167
- throw new Error(`Unknown 'options.${what}' format: ${value}`);
1168
- if (ret.num <= 0)
1169
- throw new Error(`A positive integer number is expected for 'options.${what}'`);
1170
- ret.unit = value.replace(/^[ 0]*/g, "").substr((ret.num + "").length, 1);
1171
- if (ret.unit.length === 0)
1172
- throw new Error(`Missing unit for 'options.${what}'`);
1173
- if (!units[ret.unit])
1174
- throw new Error(`Unknown 'options.${what}' unit: ${ret.unit}`);
1175
- return ret;
1176
- }
1177
- var intervalUnits = { M: true, d: true, h: true, m: true, s: true };
1178
- function checkIntervalUnit(ret, unit, amount) {
1179
- if (parseInt(amount / ret.num, 10) * ret.num !== amount)
1180
- throw new Error(`An integer divider of ${amount} is expected as ${unit} for 'options.interval'`);
1181
- }
1182
- function checkInterval(value) {
1183
- const ret = checkMeasure(value, "interval", intervalUnits);
1184
- switch (ret.unit) {
1185
- case "h":
1186
- checkIntervalUnit(ret, "hours", 24);
1187
- break;
1188
- case "m":
1189
- checkIntervalUnit(ret, "minutes", 60);
1190
- break;
1191
- case "s":
1192
- checkIntervalUnit(ret, "seconds", 60);
1193
- break;
1194
- }
1195
- return ret;
1196
- }
1197
- var sizeUnits = { B: true, G: true, K: true, M: true };
1198
- function checkSize(value) {
1199
- const ret = checkMeasure(value, "size", sizeUnits);
1200
- if (ret.unit === "K")
1201
- return ret.num * 1024;
1202
- if (ret.unit === "M")
1203
- return ret.num * 1048576;
1204
- if (ret.unit === "G")
1205
- return ret.num * 1073741824;
1206
- return ret.num;
1207
- }
1208
- var checks = {
1209
- encoding: (type, options, value) => new TextDecoder(value),
1210
- immutable: () => {
1211
- },
1212
- initialRotation: () => {
1213
- },
1214
- interval: buildStringCheck("interval", checkInterval),
1215
- intervalBoundary: () => {
1216
- },
1217
- intervalUTC: () => {
1218
- },
1219
- maxFiles: buildNumberCheck("maxFiles"),
1220
- maxSize: buildStringCheck("maxSize", checkSize),
1221
- mode: () => {
1222
- },
1223
- omitExtension: () => {
1224
- },
1225
- rotate: buildNumberCheck("rotate"),
1226
- size: buildStringCheck("size", checkSize),
1227
- teeToStdout: () => {
1228
- },
1229
- ...{
1230
- compress: (type, options, value) => {
1231
- if (value === false)
1232
- return;
1233
- if (!value)
1234
- throw new Error("A value for 'options.compress' must be specified");
1235
- if (type === "boolean")
1236
- return options.compress = (source, dest) => `cat ${source} | gzip -c9 > ${dest}`;
1237
- if (type === "function")
1238
- return;
1239
- if (type !== "string")
1240
- throw new Error(`Don't know how to handle 'options.compress' type: ${type}`);
1241
- if (value !== "gzip")
1242
- throw new Error(`Don't know how to handle compression method: ${value}`);
1243
- },
1244
- history: (type) => {
1245
- if (type !== "string")
1246
- throw new Error(`Don't know how to handle 'options.history' type: ${type}`);
1247
- },
1248
- path: (type, options, value) => {
1249
- if (type !== "string")
1250
- throw new Error(`Don't know how to handle 'options.path' type: ${type}`);
1251
- if (value[value.length - 1] !== sep)
1252
- options.path = value + sep;
1253
- }
1254
- }
1255
- };
1256
- function checkOpts(options) {
1257
- const ret = {};
1258
- let opt;
1259
- for (opt in options) {
1260
- const value = options[opt];
1261
- const type = typeof value;
1262
- if (!(opt in checks))
1263
- throw new Error(`Unknown option: ${opt}`);
1264
- ret[opt] = options[opt];
1265
- checks[opt](type, ret, value);
1266
- }
1267
- if (!ret.path)
1268
- ret.path = "";
1269
- if (!ret.interval) {
1270
- delete ret.immutable;
1271
- delete ret.initialRotation;
1272
- delete ret.intervalBoundary;
1273
- delete ret.intervalUTC;
1274
- }
1275
- if (ret.rotate) {
1276
- delete ret.history;
1277
- delete ret.immutable;
1278
- delete ret.maxFiles;
1279
- delete ret.maxSize;
1280
- delete ret.intervalBoundary;
1281
- delete ret.intervalUTC;
1282
- }
1283
- if (ret.immutable)
1284
- delete ret.compress;
1285
- if (!ret.intervalBoundary)
1286
- delete ret.initialRotation;
1287
- return ret;
1288
- }
1289
- function createClassical(filename, compress, omitExtension) {
1290
- return (index) => index ? `${filename}.${index}${compress && !omitExtension ? ".gz" : ""}` : filename;
1291
- }
1292
- function createGenerator(filename, compress, omitExtension) {
1293
- const pad = (num) => (num > 9 ? "" : "0") + num;
1294
- return (time, index) => {
1295
- if (!time)
1296
- return filename;
1297
- const month = time.getFullYear() + "" + pad(time.getMonth() + 1);
1298
- const day = pad(time.getDate());
1299
- const hour = pad(time.getHours());
1300
- const minute = pad(time.getMinutes());
1301
- return month + day + "-" + hour + minute + "-" + pad(index) + "-" + filename + (compress && !omitExtension ? ".gz" : "");
1302
- };
1303
- }
1304
- function createStream(filename, options) {
1305
- if (typeof options === "undefined")
1306
- options = {};
1307
- else if (typeof options !== "object")
1308
- throw new Error(`The "options" argument must be of type object. Received type ${typeof options}`);
1309
- const opts = checkOpts(options);
1310
- const { compress, omitExtension } = opts;
1311
- let generator;
1312
- if (typeof filename === "string")
1313
- generator = options.rotate ? createClassical(filename, !!compress, omitExtension) : createGenerator(filename, !!compress, omitExtension);
1314
- else if (typeof filename === "function")
1315
- generator = filename;
1316
- else
1317
- throw new Error(`The "filename" argument must be one of type string or function. Received type ${typeof filename}`);
1318
- return new RotatingFileStream(generator, opts);
1319
- }
1320
-
1321
- // ../logger/src/transports/file.ts
1322
- function parseSize(maxSize) {
1323
- const trimmed = maxSize.trim().toUpperCase();
1324
- if (trimmed.endsWith("MB")) {
1325
- return `${trimmed.slice(0, -2)}M`;
1326
- }
1327
- return trimmed;
1328
- }
1329
- function fileTransport(opts) {
1330
- const fmt = opts.formatter ?? jsonFormatter;
1331
- const dir = path2.dirname(opts.path);
1332
- const filename = path2.basename(opts.path);
1333
- const stream = createStream(filename, {
1334
- path: dir,
1335
- size: opts.rotate?.maxSize ? parseSize(opts.rotate.maxSize) : "50M",
1336
- maxFiles: opts.rotate?.maxFiles ?? 7
1337
- });
1338
- return (entry) => {
1339
- stream.write(`${fmt(entry)}
1340
- `);
1341
- };
1342
- }
1343
-
1344
- // ../logger/src/index.ts
1345
- function createLogger(config) {
1346
- return new Logger(config);
1347
- }
1348
-
1349
- // src/logger.ts
1350
- var bridgeConfig = loadBridgeConfig();
1351
- var isTest = !!process.env["VITEST"];
1352
- var LOG_DIR = path3.join(os3.homedir(), ".ahchat", "logs");
1353
- var LOG_FILE = path3.join(LOG_DIR, "bridge.log");
1354
- if (!isTest) ensureDir(LOG_DIR);
1355
- function createModuleLogger(module) {
1356
- const transports = [consoleTransport({ formatter: prettyFormatter })];
1357
- if (!isTest) {
1358
- transports.push(
1359
- fileTransport({
1360
- path: LOG_FILE,
1361
- formatter: jsonFormatter,
1362
- rotate: { maxSize: "20MB", maxFiles: 5 }
1363
- })
1364
- );
1365
- }
1366
- return createLogger({
1367
- source: "bridge",
1368
- module,
1369
- level: bridgeConfig.logLevel,
1370
- transports
1371
- });
1372
- }
1373
-
1374
- // src/askQuestionRegistry.ts
1375
- var logger = createModuleLogger("askQuestionRegistry");
1376
- var ASK_QUESTION_TIMEOUT_MS = 12e4;
1377
- var TIMEOUT_ANSWER = "[User did not respond within 120 seconds. Please decide whether to proceed with reasonable defaults or skip this step.]";
1378
- var AskQuestionRegistry = class {
1379
- entries = /* @__PURE__ */ new Map();
1380
- /** Register a pending question; always resolves (never rejects). */
1381
- register(questionId, agentId, onTimeout, timeoutMs = ASK_QUESTION_TIMEOUT_MS) {
1382
- return new Promise((resolve) => {
1383
- const timer = setTimeout(() => {
1384
- if (!this.entries.has(questionId)) return;
1385
- this.entries.delete(questionId);
1386
- logger.warn("AskQuestion timeout", { questionId, agentId, timeoutMs });
1387
- try {
1388
- onTimeout();
1389
- } catch (e) {
1390
- logger.error("onTimeout cb threw", { error: e });
1391
- }
1392
- resolve(TIMEOUT_ANSWER);
1393
- }, timeoutMs);
1394
- this.entries.set(questionId, { resolve, timer, agentId, askedAt: Date.now() });
1395
- logger.info("AskQuestion registered", { questionId, agentId, timeoutMs });
1396
- });
1397
- }
1398
- resolve(questionId, answerText) {
1399
- const entry = this.entries.get(questionId);
1400
- if (!entry) {
1401
- logger.warn("AskQuestion resolve: id not found (may be timed out)", { questionId });
1402
- return false;
1403
- }
1404
- clearTimeout(entry.timer);
1405
- this.entries.delete(questionId);
1406
- logger.info("AskQuestion resolved", {
1407
- questionId,
1408
- agentId: entry.agentId,
1409
- waitedMs: Date.now() - entry.askedAt,
1410
- answerLen: answerText.length,
1411
- answerSample: answerText.slice(0, 200)
1412
- });
1413
- entry.resolve(answerText);
1414
- return true;
1415
- }
1416
- cancelAll(reason) {
1417
- if (this.entries.size === 0) return;
1418
- logger.warn("AskQuestion cancelAll", { reason, count: this.entries.size });
1419
- for (const [, entry] of this.entries) {
1420
- clearTimeout(entry.timer);
1421
- entry.resolve(`[${reason}]`);
1422
- }
1423
- this.entries.clear();
1424
- }
1425
- cancelOne(questionId, reason) {
1426
- const entry = this.entries.get(questionId);
1427
- if (!entry) return false;
1428
- clearTimeout(entry.timer);
1429
- this.entries.delete(questionId);
1430
- entry.resolve(`[${reason}]`);
1431
- return true;
1432
- }
1433
- size() {
1434
- return this.entries.size;
1435
- }
1436
- };
1437
-
1438
- // ../shared/src/constants.ts
1439
- var NO_REPLY_TOKEN = "<no-reply/>";
1440
- var PLATFORM_AGENT_RULES = `
1441
- You are an Agent in AHChat, a multi-agent IM platform where humans and Agents
1442
- participate as peers in 1:1 and group conversations.
1443
-
1444
- # Default style
1445
- - IM-style replies: short, direct, concrete. No multi-paragraph essays unless asked.
1446
- - Don't quote your own name back to the user; don't refer to yourself in third person.
1447
- - Don't append meta-commentary like "Here's my answer:" \u2014 just answer.
1448
- - Use the same language as the most recent message in the conversation.
1449
-
1450
- # Group chat \u2014 when to speak
1451
- You may receive messages where the speaker is the human user OR a fellow Agent.
1452
-
1453
- When the speaker is a fellow Agent (NOT the user):
1454
- - Default behavior: reply with exactly \`<no-reply/>\` and stay silent.
1455
- - ONLY speak if ONE of the following holds:
1456
- (a) the speaker @mentioned you by name;
1457
- (b) the speaker stated something factually wrong that you uniquely can correct;
1458
- (c) the topic genuinely requires your specific expertise and nobody else has it.
1459
- - Agreeing, paraphrasing, summarizing, thanking, or politely expanding are
1460
- NOT sufficient reasons to speak. When in doubt, \`<no-reply/>\`.
1461
-
1462
- When the speaker is the human user:
1463
- - Follow the per-message instructions (mentioned / overhearer / open-floor) in
1464
- the dispatch. The same \`<no-reply/>\` semantics apply when you have nothing
1465
- meaningful to add.
1466
-
1467
- # Length & conciseness in group chat
1468
- - In group chat, default to short. Long-form only when explicitly asked.
1469
- - In 1:1 chat with the human, you may write longer answers when warranted.
1470
-
1471
- # Tools
1472
- - File paths: prefer relative; absolute only when necessary.
1473
- - After Write, don't re-Read the same content unless verifying.
1474
-
1475
- # Cross-scope awareness (Neural Bus)
1476
- You operate across multiple conversations (1:1 and groups). Each conversation has
1477
- independent context, but you can bridge them using the neural_recall and neural_relay tools:
1478
-
1479
- - neural_recall: Recall what you did/said in another conversation.
1480
- Use when asked to report on, summarize, or reference another group's activities.
1481
- Parameters: target_scope (e.g. "group:grp_xxx"), time_range (e.g. "30m", "3h", "7d").
1482
- - neural_relay: Ask your other-scope self to execute a task or speak in that conversation.
1483
- Parameters: target_scope, task, mode ("internal" or "external").
1484
- - mode="internal": Wait for your other-scope self to finish and return the result.
1485
- Use for "summarize group B" / "aggregate group B progress" type requests.
1486
- - mode="external": Fire-and-forget. Your other-scope self will speak/act there.
1487
- Use for "tell people in group B to do X" type delegation.
1488
-
1489
- Your other-scope instances share your identity and personality. Treat relay responses
1490
- as your own work product, not as a separate agent's output.
1491
- When asked about other conversations' content, always try neural_recall first.
1492
- `.trim();
1493
- var FAN_OUT_TRACE_TTL_MS = 10 * 6e4;
1494
-
1495
- // ../../node_modules/.pnpm/nanoid@5.1.11/node_modules/nanoid/index.js
1496
- import { webcrypto as crypto2 } from "crypto";
1497
-
1498
- // ../../node_modules/.pnpm/nanoid@5.1.11/node_modules/nanoid/url-alphabet/index.js
1499
- var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
1500
-
1501
- // ../../node_modules/.pnpm/nanoid@5.1.11/node_modules/nanoid/index.js
1502
- var POOL_SIZE_MULTIPLIER = 128;
1503
- var pool;
1504
- var poolOffset;
1505
- function fillPool(bytes) {
1506
- if (bytes < 0 || bytes > 1024) throw new RangeError("Wrong ID size");
1507
- if (!pool || pool.length < bytes) {
1508
- pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
1509
- crypto2.getRandomValues(pool);
1510
- poolOffset = 0;
1511
- } else if (poolOffset + bytes > pool.length) {
1512
- crypto2.getRandomValues(pool);
1513
- poolOffset = 0;
1514
- }
1515
- poolOffset += bytes;
1516
- }
1517
- function nanoid(size = 21) {
1518
- fillPool(size |= 0);
1519
- let id = "";
1520
- for (let i = poolOffset - size; i < poolOffset; i++) {
1521
- id += urlAlphabet[pool[i] & 63];
1522
- }
1523
- return id;
1524
- }
1525
-
1526
- // ../shared/src/utils.ts
1527
- function createMessageId() {
1528
- return `msg_${nanoid(12)}`;
1529
- }
1530
- function createAskQuestionId() {
1531
- return `aq_${nanoid(12)}`;
1532
- }
1533
- function isWSMessage(data) {
1534
- return typeof data === "object" && data !== null && "type" in data && "payload" in data;
1535
- }
1536
- function parseWSMessage(raw) {
1537
- const parsed = JSON.parse(raw);
1538
- if (!isWSMessage(parsed)) {
1539
- throw new Error("Invalid WS message: missing type/payload");
1540
- }
1541
- return parsed;
1542
- }
1543
-
1544
- // ../shared/src/utils/agentConfig.ts
1545
- function parseAgentConfig(raw) {
1546
- if (!raw || typeof raw !== "string") return {};
1547
- try {
1548
- const v = JSON.parse(raw);
1549
- if (v && typeof v === "object" && !Array.isArray(v)) {
1550
- const out = {};
1551
- const model = v.model;
1552
- if (typeof model === "string" && model.trim()) {
1553
- out.model = model.trim();
1554
- }
1555
- return out;
1556
- }
1557
- return {};
1558
- } catch {
1559
- return {};
1560
- }
1561
- }
1562
-
1563
- // src/scope.ts
1564
- function scopeKey(scope) {
1565
- return scope.kind === "single" ? "single" : `group:${scope.groupId}`;
1566
- }
1567
- function runtimeKey(agentId, scope) {
1568
- return `${agentId}::${scopeKey(scope)}`;
1569
- }
1570
-
1571
- // src/askUserQuestionGuard.ts
1572
- var logger2 = createModuleLogger("askUserQuestionGuard");
1573
- function formatAnswerForSDK(p) {
1574
- const parts = ["[User Response]"];
1575
- if (p.selectedLabels.length > 0) {
1576
- parts.push(`\u9009\u62E9\uFF1A${p.selectedLabels.join("\u3001")}`);
1577
- }
1578
- if (p.freeformText && p.freeformText.trim()) {
1579
- parts.push(`\u5907\u6CE8\uFF1A${p.freeformText.trim()}`);
1580
- }
1581
- if (parts.length === 1) {
1582
- parts.push("\uFF08\u7528\u6237\u672A\u9009\u62E9\u4EFB\u4F55\u9009\u9879\u4E5F\u672A\u586B\u5199\u5907\u6CE8\uFF09");
1583
- }
1584
- return parts.join("\n");
1585
- }
1586
- function makeAskUserQuestionGuard(deps) {
1587
- return async (input) => {
1588
- const task = deps.getCurrentTask();
1589
- if (!task) {
1590
- logger2.error("AskUserQuestion received but no currentTask", { agentId: deps.agentId });
1591
- return { behavior: "deny", message: "[Internal error: no active task context]" };
1592
- }
1593
- const questions = input.questions ?? [];
1594
- if (questions.length === 0) {
1595
- logger2.warn("AskUserQuestion called with empty questions array", { agentId: deps.agentId });
1596
- return { behavior: "deny", message: "[Internal error: empty questions]" };
1597
- }
1598
- if (questions.length > 1) {
1599
- logger2.warn("AskUserQuestion received multi questions, Plan A only handles questions[0]", {
1600
- agentId: deps.agentId,
1601
- count: questions.length
1602
- });
1603
- }
1604
- const q = questions[0];
1605
- const questionId = createAskQuestionId();
1606
- const askedAt = (/* @__PURE__ */ new Date()).toISOString();
1607
- const options = (q.options ?? []).map((o) => ({
1608
- label: o.label,
1609
- description: o.description
1610
- }));
1611
- const multiSelect = Boolean(q.multiSelect);
1612
- logger2.info("AskUserQuestion intercepted, emitting agent:ask_user_question", {
1613
- agentId: deps.agentId,
1614
- scope: scopeKey(deps.scope),
1615
- groupId: task.groupId,
1616
- questionId,
1617
- replyMessageId: task.replyMessageId,
1618
- question: q.question.slice(0, 200),
1619
- optionCount: options.length,
1620
- multiSelect,
1621
- traceId: task.traceId
1622
- });
1623
- deps.emit({
1624
- type: "agent:ask_user_question",
1625
- payload: {
1626
- questionId,
1627
- replyMessageId: task.replyMessageId,
1628
- agentId: deps.agentId,
1629
- conversationId: task.conversationId,
1630
- groupId: task.groupId,
1631
- question: q.question,
1632
- header: q.header,
1633
- options,
1634
- multiSelect,
1635
- askedAt,
1636
- timeoutMs: ASK_QUESTION_TIMEOUT_MS,
1637
- traceId: task.traceId
1638
- }
1639
- });
1640
- logger2.info("AskUserQuestion agent status awaiting_user", {
1641
- agentId: deps.agentId,
1642
- questionId,
1643
- groupId: task.groupId,
1644
- traceId: task.traceId
1645
- });
1646
- deps.emit({
1647
- type: "agent:status",
1648
- payload: { agentId: deps.agentId, status: "awaiting_user" }
1649
- });
1650
- const answerText = await deps.registry.register(questionId, deps.agentId, () => {
1651
- deps.emit({
1652
- type: "ask_question_updated",
1653
- payload: {
1654
- questionId,
1655
- agentId: deps.agentId,
1656
- conversationId: task.conversationId,
1657
- status: "timeout",
1658
- cancelReason: "timeout",
1659
- traceId: task.traceId
1660
- }
1661
- });
1662
- });
1663
- logger2.info("AskUserQuestion agent status thinking (resume SDK)", {
1664
- agentId: deps.agentId,
1665
- questionId,
1666
- groupId: task.groupId,
1667
- traceId: task.traceId
1668
- });
1669
- deps.emit({
1670
- type: "agent:status",
1671
- payload: { agentId: deps.agentId, status: "thinking" }
1672
- });
1673
- logger2.info("AskUserQuestion answered, returning deny+message to SDK", {
1674
- agentId: deps.agentId,
1675
- questionId,
1676
- replyMessageId: task.replyMessageId,
1677
- answerSample: answerText.slice(0, 200),
1678
- traceId: task.traceId
1679
- });
1680
- return { behavior: "deny", message: answerText };
1681
- };
1682
- }
1683
-
1684
- // src/wsMetrics.ts
1685
- import { monitorEventLoopDelay } from "perf_hooks";
1686
- var logger3 = createModuleLogger("ws.metrics");
1687
- var WsMetrics = class {
1688
- recv = /* @__PURE__ */ new Map();
1689
- send = /* @__PURE__ */ new Map();
1690
- sdkOut = /* @__PURE__ */ new Map();
1691
- timer = null;
1692
- loopHist = null;
1693
- start(intervalMs = 5e3) {
1694
- if (this.timer) return;
1695
- this.loopHist = monitorEventLoopDelay({ resolution: 20 });
1696
- this.loopHist.enable();
1697
- this.timer = setInterval(() => this.flush(intervalMs), intervalMs);
1698
- }
1699
- stop() {
1700
- if (this.timer) {
1701
- clearInterval(this.timer);
1702
- this.timer = null;
1703
- }
1704
- this.loopHist?.disable();
1705
- this.loopHist = null;
1706
- }
1707
- incRecv(type) {
1708
- this.recv.set(type, (this.recv.get(type) ?? 0) + 1);
1709
- }
1710
- incSend(type) {
1711
- this.send.set(type, (this.send.get(type) ?? 0) + 1);
1712
- }
1713
- incSdkOut(type) {
1714
- this.sdkOut.set(type, (this.sdkOut.get(type) ?? 0) + 1);
1715
- }
1716
- mapToObj(m) {
1717
- const out = {};
1718
- for (const [k, v] of m) out[k] = v;
1719
- return out;
1720
- }
1721
- flush(intervalMs) {
1722
- const hist = this.loopHist;
1723
- const stats = hist ? {
1724
- loopMaxMs: Math.round(hist.max / 1e6),
1725
- loopP99Ms: Math.round(hist.percentile(99) / 1e6),
1726
- loopMeanMs: Math.round(hist.mean / 1e6)
1727
- } : {};
1728
- if (hist) hist.reset();
1729
- const recvSum = [...this.recv.values()].reduce((a, b) => a + b, 0);
1730
- const sendSum = [...this.send.values()].reduce((a, b) => a + b, 0);
1731
- const sdkSum = [...this.sdkOut.values()].reduce((a, b) => a + b, 0);
1732
- if (recvSum + sendSum + sdkSum === 0 && (stats.loopMaxMs ?? 0) < 50) return;
1733
- logger3.info("WS metrics", {
1734
- windowMs: intervalMs,
1735
- ...stats,
1736
- sums: { recv: recvSum, send: sendSum, sdkOut: sdkSum },
1737
- recv: this.mapToObj(this.recv),
1738
- send: this.mapToObj(this.send),
1739
- sdkOut: this.mapToObj(this.sdkOut)
1740
- });
1741
- this.recv.clear();
1742
- this.send.clear();
1743
- this.sdkOut.clear();
1744
- }
1745
- };
1746
- var wsMetrics = new WsMetrics();
1747
-
1748
- // src/agentManager.ts
1749
- import fs2 from "fs/promises";
1750
- import os4 from "os";
1751
- import path6 from "path";
1752
-
1753
- // src/inputController.ts
1754
- var InputController = class {
1755
- queue = [];
1756
- pendingResolve = null;
1757
- closed = false;
1758
- /** User messages buffered but not yet yielded to the SDK iterator. */
1759
- get queueSize() {
1760
- return this.queue.length;
1761
- }
1762
- push(content, sessionId, onYielded) {
1763
- if (this.closed) return;
1764
- const msg = {
1765
- type: "user",
1766
- session_id: sessionId,
1767
- message: { role: "user", content },
1768
- parent_tool_use_id: null
1769
- };
1770
- const entry = { msg, onYielded };
1771
- if (this.pendingResolve) {
1772
- const resolve = this.pendingResolve;
1773
- this.pendingResolve = null;
1774
- resolve(entry);
1775
- } else {
1776
- this.queue.push(entry);
1777
- }
1778
- }
1779
- close() {
1780
- this.closed = true;
1781
- if (this.pendingResolve) {
1782
- const resolve = this.pendingResolve;
1783
- this.pendingResolve = null;
1784
- resolve(null);
1785
- }
1786
- }
1787
- async *[Symbol.asyncIterator]() {
1788
- while (!this.closed) {
1789
- let entry;
1790
- if (this.queue.length > 0) {
1791
- entry = this.queue.shift();
1792
- } else {
1793
- entry = await new Promise((resolve) => {
1794
- if (this.closed) {
1795
- resolve(null);
1796
- return;
1797
- }
1798
- this.pendingResolve = resolve;
1799
- });
1800
- if (entry === null) break;
1801
- }
1802
- entry.onYielded?.();
1803
- yield entry.msg;
1804
- }
1805
- }
1806
- };
1807
-
1808
- // src/permissionGuard.ts
1809
- import path5 from "path";
1810
-
1811
- // ../shared/src/utils/pathSafety.ts
1812
- import path4 from "path";
1813
- function isPathInside(parent, child) {
1814
- const resolvedParent = path4.resolve(parent);
1815
- const resolvedChild = path4.resolve(child);
1816
- if (resolvedParent === resolvedChild) return true;
1817
- const rel = path4.relative(resolvedParent, resolvedChild);
1818
- if (rel === "") return true;
1819
- return !rel.startsWith("..") && !path4.isAbsolute(rel);
1820
- }
1821
-
1822
- // src/permissionGuard.ts
1823
- var WRITE_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
1824
- function makeCwdPermissionGuard(cwd, agentId, scope, log) {
1825
- const scopeStr = scopeKey(scope);
1826
- return async (toolName, input) => {
1827
- if (!WRITE_TOOLS.has(toolName)) {
1828
- return { behavior: "allow" };
1829
- }
1830
- const raw = input.file_path ?? input.path ?? input.notebook_path;
1831
- if (typeof raw !== "string" || raw.length === 0) {
1832
- return { behavior: "allow" };
1833
- }
1834
- const abs = path5.isAbsolute(raw) ? raw : path5.resolve(cwd, raw);
1835
- if (isPathInside(cwd, abs)) {
1836
- return { behavior: "allow" };
1837
- }
1838
- log("canUseTool deny: write outside cwd", { agentId, scope: scopeStr, toolName, target: raw, abs, cwd });
1839
- return {
1840
- behavior: "deny",
1841
- message: `\u5DE5\u4F5C\u76EE\u5F55\u5916\u4E0D\u53EF\u5199\u5165\uFF1A${abs}\uFF08cwd=${cwd}\uFF09`
1842
- };
1843
- };
1844
- }
1845
-
1846
- // src/neuralBus.ts
1847
- var logger4 = createModuleLogger("neural.bus");
1848
- var NeuralJournal = class {
1849
- buffer = [];
1850
- maxEntries;
1851
- constructor(maxEntries = 200) {
1852
- this.maxEntries = maxEntries;
1853
- }
1854
- append(event) {
1855
- if (this.buffer.length >= this.maxEntries) {
1856
- this.buffer.shift();
1857
- }
1858
- this.buffer.push(event);
1859
- }
1860
- query(filter) {
1861
- const now = Date.now();
1862
- return this.buffer.filter((e) => {
1863
- if (filter.excludeScope && e.sourceScope === filter.excludeScope) return false;
1864
- if (filter.targetScope && e.sourceScope !== filter.targetScope) return false;
1865
- if (filter.sinceMs && now - e.timestamp > filter.sinceMs) return false;
1866
- if (filter.type && e.type !== filter.type) return false;
1867
- return true;
1868
- });
1869
- }
1870
- get size() {
1871
- return this.buffer.length;
1872
- }
1873
- clear() {
1874
- this.buffer = [];
1875
- }
1876
- };
1877
- var eventCounter = 0;
1878
- function generateEventId() {
1879
- return `ne_${Date.now().toString(36)}_${(eventCounter++).toString(36)}`;
1880
- }
1881
- var relayCounter = 0;
1882
- function generateRelayId() {
1883
- return `nr_${Date.now().toString(36)}_${(relayCounter++).toString(36)}`;
1884
- }
1885
- var RELAY_TIMEOUT_MS = 12e4;
1886
- var RELAY_TIMEOUT_MESSAGE = "[\u8DE8\u5BF9\u8BDD\u4E2D\u7EE7\u8D85\u65F6\uFF1A\u76EE\u6807\u5BF9\u8BDD\u672A\u5728 120 \u79D2\u5185\u5B8C\u6210\u5904\u7406\u3002\u8BF7\u7A0D\u540E\u91CD\u8BD5\u6216\u76F4\u63A5\u5728\u76EE\u6807\u5BF9\u8BDD\u4E2D\u64CD\u4F5C\u3002]";
1887
- var AgentNeuralBus = class {
1888
- agentId;
1889
- journal;
1890
- pendingRelays = /* @__PURE__ */ new Map();
1891
- relayQueue = [];
1892
- constructor(agentId, maxEntries = 200) {
1893
- this.agentId = agentId;
1894
- this.journal = new NeuralJournal(maxEntries);
1895
- }
1896
- /**
1897
- * Publish a turn summary after task completion.
1898
- * Called by AgentManager.onTaskCompleted for cross-scope awareness.
1899
- */
1900
- publish(sourceScope, summary, detail) {
1901
- if (!summary || summary.trim().length === 0) return;
1902
- const event = {
1903
- id: generateEventId(),
1904
- sourceScope,
1905
- timestamp: Date.now(),
1906
- type: "turn_summary",
1907
- summary: summary.trim(),
1908
- detail: detail?.slice(0, 2e3)
1909
- };
1910
- this.journal.append(event);
1911
- logger4.info("NeuralBus event published", {
1912
- agentId: this.agentId,
1913
- sourceScope,
1914
- eventId: event.id,
1915
- summaryLen: event.summary.length,
1916
- hasDetail: !!detail
1917
- });
1918
- }
1919
- /**
1920
- * Query cross-scope digest for passive injection.
1921
- * Returns events from OTHER scopes (excludes requestingScope).
1922
- */
1923
- queryDigest(requestingScope, sinceMs) {
1924
- return this.journal.query({
1925
- excludeScope: requestingScope,
1926
- sinceMs,
1927
- type: "turn_summary"
1928
- });
1929
- }
1930
- /**
1931
- * Query detailed cross-scope history for NeuralRecall tool.
1932
- * Returns events with detail field populated.
1933
- */
1934
- queryDetail(requestingScope, targetScope, sinceMs) {
1935
- const events = this.journal.query({
1936
- excludeScope: requestingScope,
1937
- targetScope,
1938
- sinceMs,
1939
- type: "turn_summary"
1940
- });
1941
- return events.filter((e) => e.detail != null);
1942
- }
1943
- /**
1944
- * Create a relay request to another scope.
1945
- * For mode='internal': returns a Promise that resolves when target scope completes.
1946
- * For mode='external': returns immediately with ack message.
1947
- */
1948
- relay(params) {
1949
- const relayId = generateRelayId();
1950
- const timeoutMs = params.timeoutMs ?? RELAY_TIMEOUT_MS;
1951
- if (params.mode === "external") {
1952
- const request2 = {
1953
- id: relayId,
1954
- fromScope: params.fromScope,
1955
- toScope: params.toScope,
1956
- message: params.message,
1957
- mode: "external",
1958
- timeoutMs,
1959
- createdAt: Date.now(),
1960
- conversationId: params.conversationId,
1961
- groupId: params.groupId
1962
- };
1963
- this.relayQueue.push(request2);
1964
- logger4.info("NeuralBus external relay queued", {
1965
- agentId: this.agentId,
1966
- relayId,
1967
- fromScope: params.fromScope,
1968
- toScope: params.toScope,
1969
- messageLen: params.message.length,
1970
- conversationId: params.conversationId,
1971
- groupId: params.groupId
1972
- });
1973
- return { relayId };
1974
- }
1975
- let resolveRef;
1976
- let rejectRef;
1977
- const promise = new Promise((resolve, reject) => {
1978
- resolveRef = resolve;
1979
- rejectRef = reject;
1980
- });
1981
- const timer = setTimeout(() => {
1982
- const req = this.pendingRelays.get(relayId);
1983
- if (!req) return;
1984
- this.pendingRelays.delete(relayId);
1985
- logger4.warn("NeuralBus internal relay timeout", {
1986
- agentId: this.agentId,
1987
- relayId,
1988
- fromScope: params.fromScope,
1989
- toScope: params.toScope,
1990
- timeoutMs
1991
- });
1992
- req.resolve?.(RELAY_TIMEOUT_MESSAGE);
1993
- }, timeoutMs);
1994
- const request = {
1995
- id: relayId,
1996
- fromScope: params.fromScope,
1997
- toScope: params.toScope,
1998
- message: params.message,
1999
- mode: "internal",
2000
- resolve: resolveRef,
2001
- reject: rejectRef,
2002
- timer,
2003
- timeoutMs,
2004
- createdAt: Date.now()
2005
- };
2006
- this.pendingRelays.set(relayId, request);
2007
- this.relayQueue.push(request);
2008
- logger4.info("NeuralBus internal relay queued", {
2009
- agentId: this.agentId,
2010
- relayId,
2011
- fromScope: params.fromScope,
2012
- toScope: params.toScope,
2013
- messageLen: params.message.length,
2014
- timeoutMs
2015
- });
2016
- return { relayId, promise };
2017
- }
2018
- /**
2019
- * Consume a pending relay for the target scope (called by wakeup mechanism).
2020
- * Returns the next relay destined for this scope, or undefined.
2021
- */
2022
- consumeRelay(targetScope) {
2023
- const idx = this.relayQueue.findIndex((r) => r.toScope === targetScope);
2024
- if (idx === -1) return void 0;
2025
- return this.relayQueue.splice(idx, 1)[0];
2026
- }
2027
- /**
2028
- * Resolve an internal relay with the response from the target scope.
2029
- */
2030
- resolveRelay(relayId, response) {
2031
- const req = this.pendingRelays.get(relayId);
2032
- if (!req) {
2033
- return false;
2034
- }
2035
- if (req.timer) clearTimeout(req.timer);
2036
- this.pendingRelays.delete(relayId);
2037
- logger4.info("NeuralBus relay resolved", {
2038
- agentId: this.agentId,
2039
- relayId,
2040
- fromScope: req.fromScope,
2041
- toScope: req.toScope,
2042
- responseLen: response.length,
2043
- waitedMs: Date.now() - req.createdAt
2044
- });
2045
- req.resolve?.(response);
2046
- return true;
2047
- }
2048
- /**
2049
- * Cancel all pending relays (e.g. on agent terminate).
2050
- */
2051
- cancelAll(reason) {
2052
- for (const [, req] of this.pendingRelays) {
2053
- if (req.timer) clearTimeout(req.timer);
2054
- req.resolve?.(`[\u4E2D\u7EE7\u53D6\u6D88\uFF1A${reason}]`);
2055
- }
2056
- this.pendingRelays.clear();
2057
- this.relayQueue.length = 0;
2058
- logger4.info("NeuralBus cancelAll", { agentId: this.agentId, reason });
2059
- }
2060
- get pendingRelayCount() {
2061
- return this.pendingRelays.size;
2062
- }
2063
- get relayQueueSize() {
2064
- return this.relayQueue.length;
2065
- }
2066
- };
2067
-
2068
- // src/neuralDigestFormatter.ts
2069
- function extractTurnSummary(accumulatedText) {
2070
- const trimmed = (accumulatedText ?? "").trim();
2071
- if (!trimmed || trimmed === NO_REPLY_TOKEN) return "";
2072
- const firstLine = trimmed.split("\n")[0] ?? "";
2073
- const cleaned = firstLine.replace(/^#+\s*/, "").trim();
2074
- if (cleaned.length <= 100) return cleaned;
2075
- return cleaned.slice(0, 97) + "...";
2076
- }
2077
- function formatScopeLabel(scopeKey2) {
2078
- if (scopeKey2 === "single") return "\u5355\u804A";
2079
- if (scopeKey2.startsWith("group:")) {
2080
- const groupId = scopeKey2.slice(6);
2081
- return `\u7FA4\u300C${groupId}\u300D`;
2082
- }
2083
- return scopeKey2;
2084
- }
2085
- function formatRelativeTime(timestamp) {
2086
- const diff = Date.now() - timestamp;
2087
- const seconds = Math.floor(diff / 1e3);
2088
- if (seconds < 60) return "\u521A\u521A";
2089
- const minutes = Math.floor(seconds / 60);
2090
- if (minutes < 60) return `${minutes}\u5206\u949F\u524D`;
2091
- const hours = Math.floor(minutes / 60);
2092
- if (hours < 24) return `${hours}\u5C0F\u65F6\u524D`;
2093
- const days = Math.floor(hours / 24);
2094
- return `${days}\u5929\u524D`;
2095
- }
2096
- function formatDigestPrefix(events) {
2097
- if (events.length === 0) return "";
2098
- const sorted = [...events].sort((a, b) => b.timestamp - a.timestamp);
2099
- const lines = sorted.map((e) => {
2100
- const time = formatRelativeTime(e.timestamp);
2101
- const scope = formatScopeLabel(e.sourceScope);
2102
- return `- ${time} ${scope}\uFF1A${e.summary}`;
2103
- });
2104
- return `[\u4F60\u5728\u5176\u4ED6\u5BF9\u8BDD\u4E2D\u7684\u8FD1\u51B5]
2105
- ${lines.join("\n")}`;
2106
- }
2107
- function formatRecallResponse(events, targetScopeLabel) {
2108
- if (events.length === 0) {
2109
- const scopeHint = targetScopeLabel ? `\u5728${targetScopeLabel}` : "\u5728\u5176\u4ED6\u5BF9\u8BDD\u4E2D";
2110
- return `[\u8DE8\u5BF9\u8BDD\u56DE\u5FC6] \u4F60${scopeHint}\u6682\u65E0\u8FD1\u671F\u6D3B\u52A8\u8BB0\u5F55\u3002`;
2111
- }
2112
- const sorted = [...events].sort((a, b) => b.timestamp - a.timestamp);
2113
- const header = targetScopeLabel ? `[\u8DE8\u5BF9\u8BDD\u56DE\u5FC6 \u2014 ${targetScopeLabel}]` : "[\u8DE8\u5BF9\u8BDD\u56DE\u5FC6]";
2114
- const entries = sorted.map((e) => {
2115
- const time = formatRelativeTime(e.timestamp);
2116
- const scope = formatScopeLabel(e.sourceScope);
2117
- const detailBlock = e.detail ? `
2118
- \u5185\u5BB9\u6458\u8981\uFF1A${e.detail.slice(0, 500)}` : "";
2119
- return `- ${time} ${scope}\uFF1A${e.summary}${detailBlock}`;
2120
- });
2121
- return `${header}
2122
- ${entries.join("\n\n")}`;
2123
- }
2124
-
2125
- // src/neuralMcpServer.ts
2126
- var logger5 = createModuleLogger("neural.mcpServer");
2127
- var TIME_RANGE_MAP = {
2128
- "5m": 5 * 6e4,
2129
- "15m": 15 * 6e4,
2130
- "30m": 30 * 6e4,
2131
- "1h": 60 * 6e4,
2132
- "3h": 3 * 60 * 6e4,
2133
- "6h": 6 * 60 * 6e4,
2134
- "12h": 12 * 60 * 6e4,
2135
- "24h": 24 * 60 * 6e4,
2136
- "1d": 24 * 60 * 6e4,
2137
- "3d": 3 * 24 * 60 * 6e4,
2138
- "7d": 7 * 24 * 60 * 6e4
2139
- };
2140
- function parseTimeRange(raw) {
2141
- if (!raw) return void 0;
2142
- const normalized = raw.toLowerCase().trim();
2143
- if (TIME_RANGE_MAP[normalized] != null) return TIME_RANGE_MAP[normalized];
2144
- const match = /^(\d+)\s*(m|min|h|hour|d|day)s?$/.exec(normalized);
2145
- if (match) {
2146
- const num = parseInt(match[1], 10);
2147
- const unit = match[2];
2148
- if (unit === "m" || unit === "min") return num * 6e4;
2149
- if (unit === "h" || unit === "hour") return num * 60 * 6e4;
2150
- if (unit === "d" || unit === "day") return num * 24 * 60 * 6e4;
2151
- }
2152
- return void 0;
2153
- }
2154
- async function createNeuralMcpServer(deps) {
2155
- const sdk = await import("@anthropic-ai/claude-agent-sdk");
2156
- const { z } = await import("zod");
2157
- const currentScope = scopeKey(deps.scope);
2158
- const neuralRecall = sdk.tool(
2159
- "neural_recall",
2160
- `\u56DE\u5FC6\u4F60\u5728\u5176\u4ED6\u5BF9\u8BDD\uFF08\u7FA4\u804A\u6216\u5355\u804A\uFF09\u4E2D\u505A\u8FC7\u7684\u4E8B\u60C5\u3002\u5F53\u88AB\u8981\u6C42\u6C47\u62A5\u3001\u603B\u7ED3\u3001\u5F15\u7528\u5176\u4ED6\u5BF9\u8BDD\u7684\u6D3B\u52A8\u65F6\u4F7F\u7528\u3002
2161
- \u4F60\u5F53\u524D\u6240\u5728\u7684\u5BF9\u8BDD scope \u662F: ${currentScope}\u3002\u6B64\u5DE5\u5177\u53EA\u8FD4\u56DE\u5176\u4ED6 scope \u7684\u8BB0\u5F55\u3002`,
2162
- {
2163
- target_scope: z.string().optional().describe(
2164
- '\u76EE\u6807\u5BF9\u8BDD\u7684 scope key\u3002\u5982 "single"\uFF08\u5355\u804A\uFF09\u3001"group:grp_xxx"\uFF08\u67D0\u4E2A\u7FA4\uFF09\u3002\u4E0D\u586B\u5219\u8FD4\u56DE\u6240\u6709\u5176\u4ED6\u5BF9\u8BDD\u7684\u8BB0\u5F55\u3002'
2165
- ),
2166
- time_range: z.string().optional().describe(
2167
- '\u65F6\u95F4\u8303\u56F4\u3002\u5982 "30m"\uFF0830\u5206\u949F\uFF09\u3001"3h"\uFF083\u5C0F\u65F6\uFF09\u3001"7d"\uFF087\u5929\uFF09\u3002\u4E0D\u586B\u5219\u8FD4\u56DE\u5168\u90E8\u3002'
2168
- )
2169
- },
2170
- async (args) => {
2171
- const bus = deps.neuralBusManager.getOrCreate(deps.agentId);
2172
- logger5.info("neural_recall tool called", {
2173
- agentId: deps.agentId,
2174
- currentScope,
2175
- targetScope: args.target_scope,
2176
- timeRange: args.time_range,
2177
- journalSize: bus.journal.size
2178
- });
2179
- let resolvedTargetScope = args.target_scope;
2180
- let targetLabel;
2181
- if (args.target_scope && args.target_scope.startsWith("group:")) {
2182
- const resolved = await deps.groupRegistry.resolveScope(args.target_scope);
2183
- if (resolved) {
2184
- resolvedTargetScope = resolved.scopeKey;
2185
- targetLabel = resolved.groupName;
2186
- } else {
2187
- targetLabel = formatScopeLabel(args.target_scope);
2188
- }
2189
- } else {
2190
- targetLabel = args.target_scope ? formatScopeLabel(args.target_scope) : void 0;
2191
- }
2192
- const sinceMs = parseTimeRange(args.time_range);
2193
- const events = bus.queryDetail(currentScope, resolvedTargetScope, sinceMs);
2194
- const response = formatRecallResponse(events, targetLabel);
2195
- logger5.info("neural_recall returning", {
2196
- agentId: deps.agentId,
2197
- eventCount: events.length,
2198
- responseLen: response.length,
2199
- targetScope: args.target_scope
2200
- });
2201
- return {
2202
- content: [{ type: "text", text: response }]
2203
- };
2204
- },
2205
- { annotations: { readOnlyHint: true } }
2206
- );
2207
- const neuralRelay = sdk.tool(
2208
- "neural_relay",
2209
- `\u8DE8\u5BF9\u8BDD\u4E2D\u7EE7\uFF1A\u8BA9\u4F60\u5728\u53E6\u4E00\u4E2A\u5BF9\u8BDD\uFF08\u7FA4\u804A\u6216\u5355\u804A\uFF09\u4E2D\u7684\u5206\u8EAB\u6267\u884C\u4EFB\u52A1\u6216\u53D1\u8A00\u3002
2210
- \u4F60\u5F53\u524D\u6240\u5728\u7684 scope \u662F: ${currentScope}\u3002\u4F60\u53EF\u4EE5\u5411\u4F60\u5728\u5176\u4ED6 scope \u7684\u5206\u8EAB\u53D1\u9001\u6307\u4EE4\u3002
2211
-
2212
- \u4E24\u79CD\u6A21\u5F0F:
2213
- - internal: \u7B49\u5F85\u76EE\u6807\u5206\u8EAB\u5904\u7406\u5B8C\u6210\u5E76\u8FD4\u56DE\u7ED3\u679C\u7ED9\u4F60\uFF08\u9002\u5408"\u5E2E\u6211\u6C47\u603B\u7FA4B\u7684\u8FDB\u5C55"\uFF09\u3002
2214
- - external: \u901A\u77E5\u76EE\u6807\u5206\u8EAB\u53BB\u505A\u67D0\u4E8B\uFF0C\u4E0D\u7B49\u7ED3\u679C\uFF08\u9002\u5408"\u8BA9\u7FA4B\u7684\u4EA7\u54C1\u7ECF\u7406\u53BB\u7814\u7A76\u7ADE\u54C1"\uFF09\u3002`,
2215
- {
2216
- target_scope: z.string().describe(
2217
- '\u76EE\u6807\u5BF9\u8BDD scope key\u3002\u5982 "single"\uFF08\u5355\u804A\uFF09\u3001"group:grp_xxx"\uFF08\u67D0\u4E2A\u7FA4\uFF09\u3002\u4E0D\u80FD\u662F\u4F60\u5F53\u524D\u7684 scope\u3002'
2218
- ),
2219
- task: z.string().describe("\u8981\u4F20\u8FBE\u7ED9\u76EE\u6807\u5206\u8EAB\u7684\u4EFB\u52A1\u63CF\u8FF0\u6216\u6D88\u606F\u5185\u5BB9\u3002"),
2220
- mode: z.enum(["internal", "external"]).default("internal").describe(
2221
- "internal=\u7B49\u5F85\u7ED3\u679C\u8FD4\u56DE; external=\u901A\u77E5\u540E\u7ACB\u5373\u7EE7\u7EED"
2222
- )
2223
- },
2224
- async (args) => {
2225
- const bus = deps.neuralBusManager.getOrCreate(deps.agentId);
2226
- logger5.info("neural_relay tool called", {
2227
- agentId: deps.agentId,
2228
- currentScope,
2229
- targetScope: args.target_scope,
2230
- mode: args.mode,
2231
- taskLen: args.task.length,
2232
- journalSize: bus.journal.size
2233
- });
2234
- if (!args.task.trim()) {
2235
- return {
2236
- content: [{ type: "text", text: "[\u795E\u7ECF\u4E2D\u7EE7] \u4EFB\u52A1\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A\u3002" }],
2237
- isError: true
2238
- };
2239
- }
2240
- let resolvedToScope = args.target_scope;
2241
- let resolvedConversationId;
2242
- let resolvedGroupId;
2243
- let resolvedGroupName;
2244
- if (args.target_scope.startsWith("group:")) {
2245
- const resolved = await deps.groupRegistry.resolveScope(args.target_scope);
2246
- if (!resolved) {
2247
- logger5.info("neural_relay: scope not found", { agentId: deps.agentId, targetScope: args.target_scope });
2248
- return {
2249
- content: [{ type: "text", text: `[\u795E\u7ECF\u4E2D\u7EE7] \u627E\u4E0D\u5230\u7FA4\u300C${args.target_scope.slice(6)}\u300D\u3002\u8BF7\u786E\u8BA4\u7FA4\u540D\u662F\u5426\u6B63\u786E\u3002` }],
2250
- isError: true
2251
- };
2252
- }
2253
- resolvedToScope = resolved.scopeKey;
2254
- resolvedConversationId = resolved.conversationId;
2255
- resolvedGroupId = resolved.groupId;
2256
- resolvedGroupName = resolved.groupName;
2257
- logger5.info("neural_relay scope resolved", {
2258
- agentId: deps.agentId,
2259
- rawScope: args.target_scope,
2260
- resolvedScope: resolvedToScope,
2261
- conversationId: resolvedConversationId,
2262
- groupId: resolvedGroupId,
2263
- groupName: resolvedGroupName
2264
- });
2265
- } else if (args.target_scope === "single") {
2266
- resolvedToScope = "single";
2267
- }
2268
- if (resolvedToScope === currentScope) {
2269
- logger5.warn("neural_relay: relay to self", { agentId: deps.agentId });
2270
- return {
2271
- content: [{ type: "text", text: "[\u795E\u7ECF\u4E2D\u7EE7] \u4E0D\u80FD\u5411\u81EA\u5DF1\u5F53\u524D\u7684\u5BF9\u8BDD\u53D1\u9001\u4E2D\u7EE7\u3002" }],
2272
- isError: true
2273
- };
2274
- }
2275
- const mode = args.mode;
2276
- const { relayId, promise } = bus.relay({
2277
- fromScope: currentScope,
2278
- toScope: resolvedToScope,
2279
- message: args.task.trim(),
2280
- mode,
2281
- conversationId: resolvedConversationId,
2282
- groupId: resolvedGroupId
2283
- });
2284
- deps.onRelayCreated(relayId, resolvedToScope, mode);
2285
- if (mode === "external") {
2286
- const scopeLabel = resolvedGroupName ? `\u7FA4\u300C${resolvedGroupName}\u300D` : formatScopeLabel(resolvedToScope);
2287
- logger5.info("neural_relay external relay dispatched", {
2288
- agentId: deps.agentId,
2289
- relayId,
2290
- targetScope: resolvedToScope,
2291
- groupName: resolvedGroupName,
2292
- conversationId: resolvedConversationId
2293
- });
2294
- return {
2295
- content: [{
2296
- type: "text",
2297
- text: `[\u795E\u7ECF\u4E2D\u7EE7] \u5DF2\u901A\u77E5\u4F60\u5728${scopeLabel}\u7684\u5206\u8EAB\u6267\u884C\u4EFB\u52A1\u3002
2298
- \u4EFB\u52A1\uFF1A\u300C${args.task.trim().slice(0, 200)}\u300D`
2299
- }]
2300
- };
2301
- }
2302
- logger5.info("neural_relay internal awaiting response", {
2303
- agentId: deps.agentId,
2304
- relayId,
2305
- targetScope: args.target_scope
2306
- });
2307
- const response = await promise;
2308
- const isTimeout = response === RELAY_TIMEOUT_MESSAGE;
2309
- logger5.info("neural_relay internal response received", {
2310
- agentId: deps.agentId,
2311
- relayId,
2312
- targetScope: args.target_scope,
2313
- responseLen: response.length,
2314
- isTimeout
2315
- });
2316
- return {
2317
- content: [{ type: "text", text: response }],
2318
- isError: isTimeout
2319
- };
2320
- },
2321
- {}
2322
- );
2323
- const neuralServer = sdk.createSdkMcpServer({
2324
- name: "neural",
2325
- version: "1.0.0",
2326
- tools: [neuralRecall, neuralRelay]
2327
- });
2328
- logger5.info("Neural MCP server created", {
2329
- agentId: deps.agentId,
2330
- scope: currentScope,
2331
- tools: ["neural_recall", "neural_relay"]
2332
- });
2333
- return neuralServer;
2334
- }
2335
-
2336
- // src/neuralBusManager.ts
2337
- var logger6 = createModuleLogger("neural.busManager");
2338
- var NeuralBusManager = class {
2339
- buses = /* @__PURE__ */ new Map();
2340
- maxEntriesPerAgent;
2341
- constructor(maxEntriesPerAgent = 200) {
2342
- this.maxEntriesPerAgent = maxEntriesPerAgent;
2343
- }
2344
- getOrCreate(agentId) {
2345
- let bus = this.buses.get(agentId);
2346
- if (!bus) {
2347
- bus = new AgentNeuralBus(agentId, this.maxEntriesPerAgent);
2348
- this.buses.set(agentId, bus);
2349
- logger6.info("NeuralBus created for agent", { agentId });
2350
- }
2351
- return bus;
2352
- }
2353
- get(agentId) {
2354
- return this.buses.get(agentId);
2355
- }
2356
- remove(agentId) {
2357
- const bus = this.buses.get(agentId);
2358
- if (bus) {
2359
- bus.cancelAll("agent_removed");
2360
- this.buses.delete(agentId);
2361
- logger6.info("NeuralBus removed for agent", { agentId });
2362
- }
2363
- }
2364
- getStats() {
2365
- let totalEvents = 0;
2366
- let totalPendingRelays = 0;
2367
- for (const bus of this.buses.values()) {
2368
- totalEvents += bus.journal.size;
2369
- totalPendingRelays += bus.pendingRelayCount;
2370
- }
2371
- return {
2372
- agentCount: this.buses.size,
2373
- totalEvents,
2374
- totalPendingRelays
2375
- };
2376
- }
2377
- clear() {
2378
- for (const bus of this.buses.values()) {
2379
- bus.cancelAll("manager_cleared");
2380
- }
2381
- this.buses.clear();
2382
- }
2383
- };
2384
-
2385
- // src/sdkEventMapper.ts
2386
- var logger7 = createModuleLogger("sdk.mapper");
2387
- function getTaskBase(proc) {
2388
- const task = proc.currentTask;
2389
- if (!task) return null;
2390
- return {
2391
- agentId: proc.agentId,
2392
- conversationId: task.conversationId,
2393
- traceId: task.traceId,
2394
- replyMessageId: task.replyMessageId
2395
- };
2396
- }
2397
- function wireBase(base) {
2398
- return {
2399
- ackId: base.replyMessageId,
2400
- agentId: base.agentId,
2401
- conversationId: base.conversationId,
2402
- traceId: base.traceId
2403
- };
2404
- }
2405
- function extractUsage(message) {
2406
- const result = {};
2407
- if (message.usage) {
2408
- const u = message.usage;
2409
- if (typeof u.output_tokens === "number") result.tokenCount = u.output_tokens;
2410
- if (typeof u.input_tokens === "number") result.inputTokens = u.input_tokens;
2411
- }
2412
- if (typeof message.total_cost_usd === "number") {
2413
- result.costUsd = message.total_cost_usd;
2414
- }
2415
- if (message.modelUsage) {
2416
- const models = Object.keys(message.modelUsage);
2417
- if (models.length > 0) result.model = models[0];
2418
- }
2419
- return result;
2420
- }
2421
- function isGroupTask(proc) {
2422
- return proc.currentTask?.groupId != null;
2423
- }
2424
- function extractTodosFromInput(input) {
2425
- if (!input || typeof input !== "object") return null;
2426
- const raw = input.todos;
2427
- if (!Array.isArray(raw)) return null;
2428
- const out = [];
2429
- for (let i = 0; i < raw.length; i++) {
2430
- const item = raw[i];
2431
- if (!item || typeof item !== "object") continue;
2432
- const it = item;
2433
- if (typeof it.content !== "string") continue;
2434
- const id = typeof it.id === "string" ? it.id : `todo_${i}`;
2435
- const status = it.status === "in_progress" || it.status === "completed" || it.status === "cancelled" ? it.status : "pending";
2436
- out.push({ id, content: it.content, status });
2437
- }
2438
- return out;
2439
- }
2440
- function countByStatus(todos) {
2441
- const c = {
2442
- pending: 0,
2443
- in_progress: 0,
2444
- completed: 0,
2445
- cancelled: 0
2446
- };
2447
- for (const t of todos) {
2448
- c[t.status] = (c[t.status] ?? 0) + 1;
2449
- }
2450
- return c;
2451
- }
2452
- function emitGroupSegment(proc, emit, base, content, contentBlocks) {
2453
- const groupId = proc.currentTask?.groupId;
2454
- if (!groupId) return;
2455
- proc.segmentCount += 1;
2456
- logger7.info("Group segment emitted", {
2457
- agentId: base.agentId,
2458
- replyMessageId: base.replyMessageId,
2459
- groupId,
2460
- segmentIndex: proc.segmentCount,
2461
- contentLen: content.length,
2462
- blockCount: contentBlocks.length,
2463
- blockTypes: contentBlocks.map((b) => b.type),
2464
- contentSample: content.slice(0, 200),
2465
- traceId: base.traceId,
2466
- isAuditOnly: content.length === 0
2467
- });
2468
- emit({
2469
- type: "agent:segment",
2470
- payload: {
2471
- messageId: createMessageId(),
2472
- ...wireBase(base),
2473
- groupId,
2474
- content,
2475
- contentBlocks: [...contentBlocks]
2476
- }
2477
- });
2478
- }
2479
- function flushTextSegmentOnBlockStop(proc, emit, base) {
2480
- const trimmed = proc.segmentBuffer.trim();
2481
- if (trimmed.length > 0 && trimmed !== NO_REPLY_TOKEN) {
2482
- proc.contentBlocks.push({ type: "text", content: proc.segmentBuffer });
2483
- emitGroupSegment(proc, emit, base, proc.segmentBuffer, proc.contentBlocks);
2484
- proc.contentBlocks = [];
2485
- } else {
2486
- logger7.info("Group text block flushed but skipped (no segment emitted)", {
2487
- agentId: base.agentId,
2488
- replyMessageId: base.replyMessageId,
2489
- groupId: proc.currentTask?.groupId,
2490
- bufferLen: proc.segmentBuffer.length,
2491
- trimmedLen: trimmed.length,
2492
- reason: trimmed.length === 0 ? "empty" : "no_reply_token",
2493
- sample: proc.segmentBuffer.slice(0, 200),
2494
- traceId: base.traceId
2495
- });
2496
- }
2497
- proc.segmentBuffer = "";
2498
- }
2499
- function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2500
- const emit = proc.internalRelayId ? (msg) => {
2501
- if (msg.type === "agent:status") rawEmit(msg);
2502
- } : rawEmit;
2503
- switch (message.type) {
2504
- case "system": {
2505
- if (message.subtype === "init") {
2506
- const initMsg = message;
2507
- proc.ccSessionId = initMsg.session_id;
2508
- sessionStore.set(proc.agentId, proc.scope, initMsg.session_id);
2509
- if (proc.status === "starting") {
2510
- proc.status = "ready";
2511
- }
2512
- logger7.info("Agent session initialized", {
2513
- agentId: proc.agentId,
2514
- scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
2515
- sessionId: initMsg.session_id,
2516
- statusAfterInit: proc.status
2517
- });
2518
- }
2519
- break;
2520
- }
2521
- case "stream_event": {
2522
- const base = getTaskBase(proc);
2523
- if (!base) break;
2524
- const ev = message.event;
2525
- switch (ev.type) {
2526
- case "content_block_start": {
2527
- const block = ev.content_block;
2528
- if (!block) break;
2529
- if (block.type === "thinking") {
2530
- proc.currentBlockType = "thinking";
2531
- proc.accumulatedThinking = "";
2532
- } else if (block.type === "text") {
2533
- proc.currentBlockType = "text";
2534
- if (isGroupTask(proc)) {
2535
- proc.segmentBuffer = "";
2536
- }
2537
- } else if (block.type === "tool_use") {
2538
- proc.currentBlockType = "tool_use";
2539
- proc.currentToolName = block.name ?? "unknown";
2540
- proc.accumulatedToolInput = "";
2541
- const toolName = block.name ?? "unknown";
2542
- emit({
2543
- type: "agent:tool_use",
2544
- payload: {
2545
- ...wireBase(base),
2546
- toolName,
2547
- input: {}
2548
- }
2549
- });
2550
- proc.contentBlocks.push({
2551
- type: "tool_use",
2552
- toolName,
2553
- input: {},
2554
- status: "running"
2555
- });
2556
- }
2557
- break;
2558
- }
2559
- case "content_block_delta": {
2560
- const delta = ev.delta;
2561
- if (!delta) break;
2562
- if (delta.type === "thinking_delta" && typeof delta.thinking === "string") {
2563
- proc.accumulatedThinking += delta.thinking;
2564
- emit({
2565
- type: "agent:thinking_chunk",
2566
- payload: { ...wireBase(base), chunk: delta.thinking }
2567
- });
2568
- } else if (delta.type === "input_json_delta") {
2569
- const partial = delta.partial_json;
2570
- if (typeof partial === "string") {
2571
- proc.accumulatedToolInput += partial;
2572
- }
2573
- } else if (delta.type === "text_delta" && typeof delta.text === "string") {
2574
- if (proc.accumulatedText.length === 0) {
2575
- logger7.info("Agent text stream started", {
2576
- agentId: proc.agentId,
2577
- replyMessageId: base.replyMessageId,
2578
- traceId: base.traceId,
2579
- groupMode: isGroupTask(proc)
2580
- });
2581
- }
2582
- proc.accumulatedText += delta.text;
2583
- if (isGroupTask(proc)) {
2584
- proc.segmentBuffer += delta.text;
2585
- } else {
2586
- emit({
2587
- type: "agent:text_chunk",
2588
- payload: { ...wireBase(base), chunk: delta.text }
2589
- });
2590
- }
2591
- }
2592
- break;
2593
- }
2594
- case "content_block_stop": {
2595
- if (proc.currentBlockType === "thinking") {
2596
- emit({
2597
- type: "agent:thinking_done",
2598
- payload: wireBase(getTaskBase(proc))
2599
- });
2600
- proc.contentBlocks.push({
2601
- type: "thinking",
2602
- content: proc.accumulatedThinking,
2603
- isComplete: true
2604
- });
2605
- proc.accumulatedThinking = "";
2606
- } else if (proc.currentBlockType === "text" && isGroupTask(proc)) {
2607
- flushTextSegmentOnBlockStop(proc, emit, base);
2608
- } else if (proc.currentBlockType === "tool_use") {
2609
- let parsedInput = {};
2610
- if (proc.accumulatedToolInput.length > 0) {
2611
- try {
2612
- parsedInput = JSON.parse(proc.accumulatedToolInput);
2613
- } catch {
2614
- logger7.warn("Failed to parse tool input JSON", {
2615
- agentId: proc.agentId,
2616
- toolName: proc.currentToolName,
2617
- inputLen: proc.accumulatedToolInput.length,
2618
- sample: proc.accumulatedToolInput.slice(0, 200)
2619
- });
2620
- }
2621
- }
2622
- const lastToolUse = [...proc.contentBlocks].reverse().find((bl) => bl.type === "tool_use");
2623
- if (lastToolUse && lastToolUse.type === "tool_use") {
2624
- lastToolUse.input = parsedInput;
2625
- }
2626
- if (proc.currentToolName === "TodoWrite") {
2627
- const todos = extractTodosFromInput(parsedInput);
2628
- if (todos) {
2629
- logger7.info("TodoWrite detected, emitting agent:todos_update", {
2630
- agentId: proc.agentId,
2631
- replyMessageId: base.replyMessageId,
2632
- groupId: proc.currentTask?.groupId,
2633
- todoCount: todos.length,
2634
- statusBreakdown: countByStatus(todos),
2635
- traceId: base.traceId
2636
- });
2637
- emit({
2638
- type: "agent:todos_update",
2639
- payload: {
2640
- ...wireBase(base),
2641
- groupId: proc.currentTask?.groupId,
2642
- todos
2643
- }
2644
- });
2645
- } else {
2646
- logger7.info("TodoWrite detected with empty/cancel todos", {
2647
- agentId: proc.agentId,
2648
- replyMessageId: base.replyMessageId,
2649
- traceId: base.traceId
2650
- });
2651
- emit({
2652
- type: "agent:todos_update",
2653
- payload: {
2654
- ...wireBase(base),
2655
- groupId: proc.currentTask?.groupId,
2656
- todos: []
2657
- }
2658
- });
2659
- }
2660
- }
2661
- if (proc.currentToolName === "AskUserQuestion") {
2662
- const last = proc.contentBlocks[proc.contentBlocks.length - 1];
2663
- if (last?.type === "tool_use" && last.toolName === "AskUserQuestion") {
2664
- proc.contentBlocks.pop();
2665
- }
2666
- }
2667
- proc.accumulatedToolInput = "";
2668
- }
2669
- proc.currentBlockType = null;
2670
- break;
2671
- }
2672
- default:
2673
- break;
2674
- }
2675
- break;
2676
- }
2677
- case "user": {
2678
- const base = getTaskBase(proc);
2679
- if (!base) break;
2680
- const userMsg = message;
2681
- const content = userMsg.message?.content;
2682
- if (Array.isArray(content)) {
2683
- for (const block of content) {
2684
- const b = block;
2685
- if (b.type === "tool_result") {
2686
- const output = typeof b.content === "string" ? b.content : JSON.stringify(b.content);
2687
- emit({
2688
- type: "agent:tool_result",
2689
- payload: {
2690
- ...wireBase(base),
2691
- toolName: proc.currentToolName ?? "unknown",
2692
- output,
2693
- isError: !!b.is_error
2694
- }
2695
- });
2696
- proc.contentBlocks.push({
2697
- type: "tool_result",
2698
- toolName: proc.currentToolName ?? "unknown",
2699
- output,
2700
- isError: !!b.is_error
2701
- });
2702
- const lastToolUse = [...proc.contentBlocks].reverse().find((bl) => bl.type === "tool_use");
2703
- if (lastToolUse && lastToolUse.type === "tool_use") {
2704
- lastToolUse.status = b.is_error ? "error" : "done";
2705
- }
2706
- }
2707
- }
2708
- }
2709
- break;
2710
- }
2711
- case "result": {
2712
- const base = getTaskBase(proc);
2713
- if (!base) break;
2714
- const resultMsg = message;
2715
- let carrierMessageId;
2716
- if (resultMsg.subtype === "success") {
2717
- const successMsg = resultMsg;
2718
- const trimmed = proc.accumulatedText.trim();
2719
- const groupId = proc.currentTask?.groupId;
2720
- const groupMode = groupId != null;
2721
- const usage = extractUsage(successMsg);
2722
- if (trimmed === NO_REPLY_TOKEN) {
2723
- logger7.info("Agent chose not to reply", {
2724
- agentId: proc.agentId,
2725
- replyMessageId: base.replyMessageId,
2726
- traceId: base.traceId,
2727
- groupMode,
2728
- groupId,
2729
- fullTextLen: proc.accumulatedText.length,
2730
- fullTextSample: proc.accumulatedText.slice(0, 200),
2731
- accumulatedBlockCount: proc.contentBlocks.length,
2732
- accumulatedBlockTypes: proc.contentBlocks.map((b) => b.type)
2733
- });
2734
- emit({
2735
- type: "agent:no_reply",
2736
- payload: {
2737
- ...wireBase(base),
2738
- groupId,
2739
- reason: void 0
2740
- }
2741
- });
2742
- resetAccumulators(proc);
2743
- onCompleted();
2744
- break;
2745
- }
2746
- if (groupMode) {
2747
- if (usage.inputTokens && usage.inputTokens > 15e4) {
2748
- logger7.warn("Agent context window approaching limit", {
2749
- agentId: proc.agentId,
2750
- inputTokens: usage.inputTokens
2751
- });
2752
- }
2753
- if (proc.contentBlocks.length > 0) {
2754
- logger7.info("Group turn trailing audit segment", {
2755
- agentId: proc.agentId,
2756
- replyMessageId: base.replyMessageId,
2757
- blockCount: proc.contentBlocks.length,
2758
- traceId: base.traceId
2759
- });
2760
- emitGroupSegment(proc, emit, base, "", proc.contentBlocks);
2761
- proc.contentBlocks = [];
2762
- }
2763
- logger7.info("Group task turn complete", {
2764
- agentId: proc.agentId,
2765
- replyMessageId: base.replyMessageId,
2766
- groupId,
2767
- segmentCount: proc.segmentCount,
2768
- fullTextLen: proc.accumulatedText.length,
2769
- fullTextSample: proc.accumulatedText.slice(0, 200),
2770
- traceId: base.traceId
2771
- });
2772
- emit({
2773
- type: "agent:turn_complete",
2774
- payload: {
2775
- ...wireBase(base),
2776
- groupId,
2777
- segmentCount: proc.segmentCount
2778
- }
2779
- });
2780
- resetAccumulators(proc);
2781
- onCompleted();
2782
- break;
2783
- }
2784
- if (proc.accumulatedText) {
2785
- proc.contentBlocks.push({ type: "text", content: proc.accumulatedText });
2786
- }
2787
- if (usage.inputTokens && usage.inputTokens > 15e4) {
2788
- logger7.warn("Agent context window approaching limit", {
2789
- agentId: proc.agentId,
2790
- inputTokens: usage.inputTokens
2791
- });
2792
- }
2793
- carrierMessageId = createMessageId();
2794
- logger7.info("Agent task done, emitting agent:done", {
2795
- agentId: proc.agentId,
2796
- ackId: base.replyMessageId,
2797
- messageId: carrierMessageId,
2798
- textLen: proc.accumulatedText.length,
2799
- textSample: proc.accumulatedText.slice(0, 200),
2800
- tokenCount: usage.tokenCount,
2801
- traceId: base.traceId
2802
- });
2803
- emit({
2804
- type: "agent:done",
2805
- payload: {
2806
- ...wireBase(base),
2807
- messageId: carrierMessageId,
2808
- fullContent: proc.accumulatedText,
2809
- contentBlocks: proc.contentBlocks,
2810
- metadata: {
2811
- thinkingDuration: Date.now() - proc.currentTaskStartedAt,
2812
- toolCallCount: proc.contentBlocks.filter((b) => b.type === "tool_use").length,
2813
- tokenCount: usage.tokenCount,
2814
- model: usage.model
2815
- }
2816
- }
2817
- });
2818
- } else {
2819
- const errorMsg = resultMsg;
2820
- const errorText = errorMsg.errors?.join("; ") ?? `Agent error: ${resultMsg.subtype}`;
2821
- logger7.warn("Agent task error, emitting agent:error", {
2822
- agentId: proc.agentId,
2823
- replyMessageId: base.replyMessageId,
2824
- subtype: resultMsg.subtype,
2825
- errorText,
2826
- traceId: base.traceId
2827
- });
2828
- emit({
2829
- type: "agent:error",
2830
- payload: { ...wireBase(base), error: errorText }
2831
- });
2832
- }
2833
- resetAccumulators(proc);
2834
- onCompleted(carrierMessageId);
2835
- break;
2836
- }
2837
- case "assistant":
2838
- break;
2839
- default:
2840
- logger7.warn("Unhandled SDK message type", {
2841
- type: message.type,
2842
- agentId: proc.agentId
2843
- });
2844
- break;
2845
- }
2846
- }
2847
- function resetAccumulators(proc) {
2848
- proc.accumulatedText = "";
2849
- proc.accumulatedThinking = "";
2850
- proc.contentBlocks = [];
2851
- proc.currentBlockType = null;
2852
- proc.currentToolName = null;
2853
- proc.segmentBuffer = "";
2854
- proc.segmentCount = 0;
2855
- proc.accumulatedToolInput = "";
2856
- }
2857
-
2858
- // src/agentManager.ts
2859
- var logger8 = createModuleLogger("agent.manager");
2860
- var BridgeBusyError = class extends Error {
2861
- constructor(message = "Bridge busy: cannot evict an idle Agent query; all slots are working") {
2862
- super(message);
2863
- this.name = "BridgeBusyError";
2864
- }
2865
- };
2866
- var AgentManager = class {
2867
- agents = /* @__PURE__ */ new Map();
2868
- lastUsedAt = /* @__PURE__ */ new Map();
2869
- sessionStore;
2870
- emit;
2871
- workspacesDir;
2872
- queryConfig;
2873
- askQuestionRegistry;
2874
- neuralBusManager;
2875
- groupRegistry;
2876
- evictionTimer = null;
2877
- // Lazy-loaded SDK query function. Injectable via constructor for tests.
2878
- queryFn = null;
2879
- constructor(sessionStore, emit, options) {
2880
- this.sessionStore = sessionStore;
2881
- this.emit = emit;
2882
- if (typeof options === "function") {
2883
- this.queryFn = options;
2884
- this.workspacesDir = path6.join(os4.homedir(), ".ahchat", "workspaces");
2885
- this.queryConfig = DEFAULT_QUERY_CONFIG;
2886
- this.askQuestionRegistry = new AskQuestionRegistry();
2887
- this.neuralBusManager = new NeuralBusManager();
2888
- this.groupRegistry = null;
2889
- } else {
2890
- this.queryFn = options?.queryFn ?? null;
2891
- this.workspacesDir = options?.workspacesDir ?? path6.join(os4.homedir(), ".ahchat", "workspaces");
2892
- this.queryConfig = options?.queryConfig ?? DEFAULT_QUERY_CONFIG;
2893
- this.askQuestionRegistry = options?.askQuestionRegistry ?? new AskQuestionRegistry();
2894
- this.neuralBusManager = options?.neuralBusManager ?? new NeuralBusManager();
2895
- this.groupRegistry = options?.groupRegistry ?? null;
2896
- }
2897
- this.evictionTimer = setInterval(() => {
2898
- void this.evictIdle();
2899
- }, this.queryConfig.evictionIntervalMs);
2900
- }
2901
- async getQueryFn() {
2902
- if (this.queryFn) return this.queryFn;
2903
- const sdk = await import("@anthropic-ai/claude-agent-sdk");
2904
- this.queryFn = sdk.query;
2905
- return this.queryFn;
2906
- }
2907
- /** Count live queries (anything not dead / removed). */
2908
- countActiveQueries() {
2909
- let n = 0;
2910
- for (const p of this.agents.values()) {
2911
- if (p.status !== "dead") n++;
2912
- }
2913
- return n;
2914
- }
2915
- asRuntime(proc) {
2916
- return proc;
2917
- }
2918
- async awaitQueryReturn(query, timeoutMs, agentId) {
2919
- const ret = query.return(void 0);
2920
- try {
2921
- await Promise.race([
2922
- ret,
2923
- new Promise((_, reject) => {
2924
- setTimeout(() => reject(new Error("query return timeout")), timeoutMs);
2925
- })
2926
- ]);
2927
- } catch (e) {
2928
- logger8.warn("awaitQueryReturn finished with error/timeout", { agentId, error: e });
2929
- }
2930
- }
2931
- /**
2932
- * Returns true when an agent process occupies a slot but is not actively doing work
2933
- * and can be safely evicted to free up capacity.
2934
- *
2935
- * Both 'ready' (warm, finished a task) and 'starting' (pre-warmed at recovery but
2936
- * never sent a message) qualify, as long as there are no injected tasks awaiting a turn.
2937
- */
2938
- isEvictable(proc) {
2939
- if (proc.status !== "ready" && proc.status !== "starting") return false;
2940
- const runtime = this.asRuntime(proc);
2941
- return runtime.injectedTasks.length === 0;
2942
- }
2943
- /**
2944
- * Close an idle/starting query: end generator and drop from map.
2945
- * Session id stays in SessionStore for resume (Phase 2 eviction).
2946
- */
2947
- async closeIdleQuery(key) {
2948
- const proc = this.agents.get(key);
2949
- if (!proc || proc.status === "dead") return;
2950
- if (!this.isEvictable(proc)) return;
2951
- logger8.info("Evicting idle Agent query", { agentId: proc.agentId, scope: scopeKey(proc.scope) });
2952
- const runtime = this.asRuntime(proc);
2953
- try {
2954
- runtime.inputController.close();
2955
- await this.awaitQueryReturn(runtime.query, 5e3, proc.agentId);
2956
- } catch (e) {
2957
- logger8.error("closeIdleQuery failed", { agentId: proc.agentId, error: e });
2958
- }
2959
- proc.status = "dead";
2960
- this.agents.delete(key);
2961
- this.lastUsedAt.delete(key);
2962
- }
2963
- /** Evict LRU among idle (ready/starting + no injected tasks) agents past the idle timeout. */
2964
- evictIdle() {
2965
- const now = Date.now();
2966
- const { idleTimeoutMs } = this.queryConfig;
2967
- for (const [key, proc] of this.agents) {
2968
- if (!this.isEvictable(proc)) continue;
2969
- const runtime = this.asRuntime(proc);
2970
- const last = this.lastUsedAt.get(key) ?? runtime.createdAt ?? 0;
2971
- if (now - last <= idleTimeoutMs) continue;
2972
- void this.closeIdleQuery(key);
2973
- }
2974
- }
2975
- /**
2976
- * Evict one LRU candidate to make room for a new query. Returns false if none evictable.
2977
- */
2978
- async evictOneLruReadyIdle() {
2979
- let bestKey = null;
2980
- let bestTs = Number.POSITIVE_INFINITY;
2981
- for (const [key, proc] of this.agents) {
2982
- if (!this.isEvictable(proc)) continue;
2983
- const runtime = this.asRuntime(proc);
2984
- const ts = this.lastUsedAt.get(key) ?? runtime.createdAt ?? 0;
2985
- if (ts < bestTs) {
2986
- bestTs = ts;
2987
- bestKey = key;
2988
- }
2989
- }
2990
- if (!bestKey) return false;
2991
- await this.closeIdleQuery(bestKey);
2992
- return true;
2993
- }
2994
- /**
2995
- * Ensure an Agent query exists (respecting maxActive via LRU eviction of idle queries).
2996
- */
2997
- async acquire(agentConfig, scope, cwd) {
2998
- const key = runtimeKey(agentConfig.id, scope);
2999
- const existing = this.agents.get(key);
3000
- if (existing && existing.status !== "dead") {
3001
- this.lastUsedAt.set(key, Date.now());
3002
- return existing;
3003
- }
3004
- while (this.countActiveQueries() >= this.queryConfig.maxActive) {
3005
- const evicted = await this.evictOneLruReadyIdle();
3006
- if (!evicted) {
3007
- throw new BridgeBusyError();
3008
- }
3009
- }
3010
- const proc = await this.getOrCreate(agentConfig, scope, cwd);
3011
- this.lastUsedAt.set(key, Date.now());
3012
- return proc;
3013
- }
3014
- async getOrCreate(agentConfig, scope, cwd) {
3015
- const key = runtimeKey(agentConfig.id, scope);
3016
- const existing = this.agents.get(key);
3017
- if (existing && existing.status !== "dead") {
3018
- return existing;
3019
- }
3020
- const savedSessionId = this.sessionStore.get(agentConfig.id, scope);
3021
- const inputController = new InputController();
3022
- const agentCwd = cwd;
3023
- await fs2.mkdir(agentCwd, { recursive: true });
3024
- const cfg = parseAgentConfig(agentConfig.config);
3025
- logger8.info("Creating Agent query", {
3026
- agentId: agentConfig.id,
3027
- scope: scopeKey(scope),
3028
- cwd: agentCwd,
3029
- resume: !!savedSessionId,
3030
- sessionId: savedSessionId,
3031
- model: cfg.model ?? "(default)"
3032
- });
3033
- const queryFn = await this.getQueryFn();
3034
- let procRef = null;
3035
- const cwdGuard = makeCwdPermissionGuard(agentCwd, agentConfig.id, scope, (msg, meta) => {
3036
- logger8.warn(msg, meta);
3037
- });
3038
- const askGuard = makeAskUserQuestionGuard({
3039
- agentId: agentConfig.id,
3040
- scope,
3041
- registry: this.askQuestionRegistry,
3042
- getCurrentTask: () => procRef?.currentTask ?? null,
3043
- emit: this.emit
3044
- });
3045
- const neuralServer = await createNeuralMcpServer({
3046
- agentId: agentConfig.id,
3047
- scope,
3048
- neuralBusManager: this.neuralBusManager,
3049
- groupRegistry: this.groupRegistry,
3050
- onRelayCreated: (relayId, toScope) => {
3051
- this.processRelayWakeup(agentConfig, toScope, relayId);
3052
- }
3053
- });
3054
- const options = {
3055
- cwd: agentCwd,
3056
- systemPrompt: {
3057
- type: "preset",
3058
- preset: "claude_code",
3059
- append: [PLATFORM_AGENT_RULES, agentConfig.systemPrompt].filter((s) => typeof s === "string" && s.trim().length > 0).join("\n\n")
3060
- },
3061
- permissionMode: "bypassPermissions",
3062
- allowDangerouslySkipPermissions: true,
3063
- allowedTools: [
3064
- "Read",
3065
- "Edit",
3066
- "Write",
3067
- "Bash",
3068
- "Glob",
3069
- "Grep",
3070
- "AskUserQuestion",
3071
- "mcp__neural__neural_recall",
3072
- "mcp__neural__neural_relay"
3073
- ],
3074
- mcpServers: { neural: neuralServer },
3075
- includePartialMessages: true,
3076
- canUseTool: async (toolName, input) => {
3077
- if (toolName === "AskUserQuestion") {
3078
- return askGuard(input);
3079
- }
3080
- return cwdGuard(toolName, input);
3081
- }
3082
- };
3083
- const userPromptTrimmed = (agentConfig.systemPrompt ?? "").trim();
3084
- const appendStr = options.systemPrompt.append;
3085
- logger8.info("Platform rules attached", {
3086
- agentId: agentConfig.id,
3087
- scope: scopeKey(scope),
3088
- platformRulesLen: PLATFORM_AGENT_RULES.length,
3089
- userPromptLen: userPromptTrimmed.length,
3090
- hasUserPrompt: userPromptTrimmed.length > 0,
3091
- appendLen: appendStr.length
3092
- });
3093
- if (cfg.model) {
3094
- options.model = cfg.model;
3095
- }
3096
- if (savedSessionId) {
3097
- options.resume = savedSessionId;
3098
- }
3099
- const agentQuery = queryFn({
3100
- prompt: inputController,
3101
- options
3102
- });
3103
- const proc = {
3104
- agentId: agentConfig.id,
3105
- scope,
3106
- cwd: agentCwd,
3107
- ccSessionId: savedSessionId,
3108
- status: "starting",
3109
- currentTask: null,
3110
- currentTaskStartedAt: 0,
3111
- accumulatedThinking: "",
3112
- accumulatedText: "",
3113
- contentBlocks: [],
3114
- currentBlockType: null,
3115
- currentToolName: null,
3116
- segmentBuffer: "",
3117
- segmentCount: 0,
3118
- accumulatedToolInput: ""
3119
- };
3120
- const runtime = Object.assign(proc, {
3121
- query: agentQuery,
3122
- inputController,
3123
- injectedTasks: [],
3124
- mergedTasks: [],
3125
- createdAt: Date.now()
3126
- });
3127
- procRef = proc;
3128
- this.agents.set(key, proc);
3129
- this.consumeOutput(runtime);
3130
- return proc;
3131
- }
3132
- async sendMessage(task) {
3133
- const key = runtimeKey(task.agentId, task.scope);
3134
- const proc = this.agents.get(key);
3135
- if (!proc || proc.status === "dead") {
3136
- throw new Error(`Agent ${task.agentId} process not available`);
3137
- }
3138
- const runtime = this.asRuntime(proc);
3139
- if (proc.status === "ready") {
3140
- this.dispatchToSDK(runtime, task);
3141
- return;
3142
- }
3143
- if (proc.status === "starting") {
3144
- logger8.info("Message dispatched to starting Agent (kickstart)", {
3145
- agentId: task.agentId,
3146
- replyMessageId: task.replyMessageId,
3147
- traceId: task.traceId
3148
- });
3149
- this.dispatchToSDK(runtime, task);
3150
- return;
3151
- }
3152
- const onYielded = () => {
3153
- const idx = runtime.injectedTasks.indexOf(task);
3154
- if (idx >= 0) {
3155
- runtime.injectedTasks.splice(idx, 1);
3156
- runtime.mergedTasks.push(task);
3157
- logger8.info("Injected task consumed by SDK (queued as merged until next result)", {
3158
- agentId: runtime.agentId,
3159
- replyMessageId: task.replyMessageId,
3160
- traceId: task.traceId,
3161
- mergedQueueSize: runtime.mergedTasks.length,
3162
- currentTaskReplyMessageId: runtime.currentTask?.replyMessageId
3163
- });
3164
- }
3165
- };
3166
- runtime.inputController.push(task.content, runtime.ccSessionId ?? "", onYielded);
3167
- runtime.injectedTasks.push(task);
3168
- logger8.info("Message injected while Agent working", {
3169
- agentId: task.agentId,
3170
- replyMessageId: task.replyMessageId,
3171
- currentStatus: proc.status,
3172
- injectedDepth: runtime.injectedTasks.length,
3173
- traceId: task.traceId
3174
- });
3175
- }
3176
- dispatchToSDK(runtime, task) {
3177
- runtime.status = "working";
3178
- runtime.currentTask = task;
3179
- runtime.currentTaskStartedAt = Date.now();
3180
- let content = task.content;
3181
- const bus = this.neuralBusManager.get(runtime.agentId);
3182
- if (bus) {
3183
- const digest = bus.queryDigest(scopeKey(runtime.scope), 30 * 6e4);
3184
- if (digest.length > 0) {
3185
- const prefix = formatDigestPrefix(digest);
3186
- content = prefix + "\n\n" + content;
3187
- }
3188
- }
3189
- runtime.inputController.push(content, runtime.ccSessionId ?? "");
3190
- logger8.info("Message pushed to Agent", {
3191
- agentId: runtime.agentId,
3192
- replyMessageId: task.replyMessageId,
3193
- traceId: task.traceId
3194
- });
3195
- }
3196
- resetProcAccumulators(proc) {
3197
- proc.accumulatedText = "";
3198
- proc.accumulatedThinking = "";
3199
- proc.contentBlocks = [];
3200
- proc.currentBlockType = null;
3201
- proc.currentToolName = null;
3202
- proc.segmentBuffer = "";
3203
- proc.segmentCount = 0;
3204
- }
3205
- onTaskCompleted(proc, carrierMessageId) {
3206
- const runtime = this.asRuntime(proc);
3207
- const completedTask = proc.currentTask;
3208
- if (completedTask && runtime.mergedTasks.length > 0) {
3209
- const mergedBatch = [...runtime.mergedTasks];
3210
- logger8.info("Flushing merged tasks after result", {
3211
- agentId: proc.agentId,
3212
- carrierReplyMessageId: completedTask.replyMessageId,
3213
- mergedCount: mergedBatch.length,
3214
- mergedReplyMessageIds: mergedBatch.map((t) => t.replyMessageId),
3215
- traceId: completedTask.traceId
3216
- });
3217
- for (const merged of mergedBatch) {
3218
- logger8.info("Emitting agent:merged for task consumed in same turn", {
3219
- agentId: proc.agentId,
3220
- ackId: merged.replyMessageId,
3221
- mergedIntoAckId: completedTask.replyMessageId,
3222
- mergedIntoMessageId: carrierMessageId,
3223
- traceId: merged.traceId
3224
- });
3225
- this.emit({
3226
- type: "agent:merged",
3227
- payload: {
3228
- agentId: proc.agentId,
3229
- conversationId: merged.conversationId,
3230
- ackId: merged.replyMessageId,
3231
- mergedIntoAckId: completedTask.replyMessageId,
3232
- mergedIntoMessageId: carrierMessageId,
3233
- groupId: merged.groupId,
3234
- traceId: merged.traceId
3235
- }
3236
- });
3237
- }
3238
- runtime.mergedTasks = [];
3239
- } else if (runtime.mergedTasks.length > 0) {
3240
- logger8.warn("mergedTasks non-empty but no currentTask; dropping", {
3241
- agentId: proc.agentId,
3242
- mergedCount: runtime.mergedTasks.length
3243
- });
3244
- runtime.mergedTasks = [];
3245
- }
3246
- if (runtime.injectedTasks.length > 0) {
3247
- const next = runtime.injectedTasks.shift();
3248
- this.resetProcAccumulators(proc);
3249
- proc.currentTask = next;
3250
- proc.status = "working";
3251
- proc.currentTaskStartedAt = Date.now();
3252
- logger8.info("Promoted next injected task after result", {
3253
- agentId: proc.agentId,
3254
- replyMessageId: next.replyMessageId,
3255
- remainingInjected: runtime.injectedTasks.length,
3256
- traceId: next.traceId
3257
- });
3258
- return;
3259
- }
3260
- proc.currentTask = null;
3261
- proc.status = "ready";
3262
- this.lastUsedAt.set(runtimeKey(proc.agentId, proc.scope), Date.now());
3263
- if (proc.internalRelayId) {
3264
- const bus = this.neuralBusManager.get(proc.agentId);
3265
- if (bus) {
3266
- const response = proc.accumulatedText || "[\u76EE\u6807\u5BF9\u8BDD\u672A\u4EA7\u51FA\u5185\u5BB9]";
3267
- const resolved = bus.resolveRelay(proc.internalRelayId, response);
3268
- if (resolved) {
3269
- logger8.info("Internal relay resolved on task completion", {
3270
- agentId: proc.agentId,
3271
- relayId: proc.internalRelayId,
3272
- scope: scopeKey(proc.scope),
3273
- responseLen: response.length
3274
- });
3275
- } else {
3276
- logger8.info("External relay task completed (output suppressed, published to journal)", {
3277
- agentId: proc.agentId,
3278
- relayId: proc.internalRelayId,
3279
- scope: scopeKey(proc.scope),
3280
- responseLen: response.length
3281
- });
3282
- }
3283
- }
3284
- proc.internalRelayId = void 0;
3285
- }
3286
- const summary = extractTurnSummary(proc.accumulatedText);
3287
- if (summary) {
3288
- const bus = this.neuralBusManager.getOrCreate(proc.agentId);
3289
- bus.publish(scopeKey(proc.scope), summary, proc.accumulatedText?.slice(0, 2e3));
3290
- }
3291
- }
3292
- getQueryStatus(bridgeId) {
3293
- const queries = [...this.agents.entries()].map(([key, proc]) => ({
3294
- agentId: proc.agentId,
3295
- status: proc.status,
3296
- ccSessionId: proc.ccSessionId,
3297
- lastActiveAt: new Date(this.lastUsedAt.get(key) ?? 0).toISOString()
3298
- }));
3299
- const activeCount = [...this.agents.values()].filter((p) => p.status !== "dead").length;
3300
- return {
3301
- type: "bridge:query_status",
3302
- payload: {
3303
- bridgeId,
3304
- queries,
3305
- activeCount,
3306
- maxActive: this.queryConfig.maxActive,
3307
- bridgeMemoryMB: Math.round(process.memoryUsage().rss / 1024 / 1024)
3308
- }
3309
- };
3310
- }
3311
- /**
3312
- * Process a relay wakeup: acquire or dispatch to the target scope.
3313
- * Called by the NeuralRelay MCP tool's onRelayCreated callback.
3314
- */
3315
- processRelayWakeup(agentConfig, toScopeKey, relayId) {
3316
- const bus = this.neuralBusManager.getOrCreate(agentConfig.id);
3317
- const relay = bus.consumeRelay(toScopeKey);
3318
- if (!relay) return;
3319
- const effectiveGroupId = relay.groupId || toScopeKey.replace("group:", "");
3320
- const scope = toScopeKey === "single" ? { kind: "single" } : { kind: "group", groupId: effectiveGroupId };
3321
- this.sessionStore.delete(agentConfig.id, scope);
3322
- const key = runtimeKey(agentConfig.id, scope);
3323
- const existingProc = this.agents.get(key);
3324
- const suppressEmit = relay.mode === "internal" ? relayId : void 0;
3325
- const task = {
3326
- content: `[\u795E\u7ECF\u4E2D\u7EE7\u6D88\u606F \u2014 \u6765\u81EA\u4F60\u7684${formatScopeLabel(relay.fromScope)}\u5206\u8EAB]
3327
- ${relay.message}`,
3328
- replyMessageId: `msg_relay_${relayId}`,
3329
- conversationId: relay.conversationId || "",
3330
- traceId: `tr_relay_${relayId}`,
3331
- groupId: relay.groupId
3332
- };
3333
- logger8.info("Relay wakeup processing", {
3334
- agentId: agentConfig.id,
3335
- toScope: toScopeKey,
3336
- relayId,
3337
- mode: relay.mode,
3338
- conversationId: relay.conversationId ?? "(none)",
3339
- groupId: relay.groupId ?? "(none)",
3340
- suppressEmit: !!suppressEmit
3341
- });
3342
- if (existingProc && existingProc.status !== "dead") {
3343
- if (suppressEmit) existingProc.internalRelayId = suppressEmit;
3344
- if (existingProc.status === "ready" || existingProc.status === "starting") {
3345
- const runtime = this.asRuntime(existingProc);
3346
- this.dispatchToSDK(runtime, task);
3347
- } else {
3348
- const runtime = this.asRuntime(existingProc);
3349
- runtime.inputController.push(task.content, runtime.ccSessionId ?? "");
3350
- runtime.injectedTasks.push(task);
3351
- }
3352
- logger8.info("Relay wakeup dispatched to existing runtime", {
3353
- agentId: agentConfig.id,
3354
- toScope: toScopeKey,
3355
- relayId,
3356
- mode: relay.mode,
3357
- status: existingProc.status
3358
- });
3359
- } else {
3360
- const cwd = agentConfig.workingDirectory || path6.join(this.workspacesDir, agentConfig.id);
3361
- void this.acquire(agentConfig, scope, cwd).then((proc) => {
3362
- if (suppressEmit) proc.internalRelayId = suppressEmit;
3363
- return this.sendMessage({ ...task, agentId: agentConfig.id, scope });
3364
- }).catch((err) => {
3365
- logger8.error("Relay wakeup acquire failed", {
3366
- agentId: agentConfig.id,
3367
- toScope: toScopeKey,
3368
- relayId,
3369
- error: err
3370
- });
3371
- if (relay.mode === "internal") {
3372
- bus.resolveRelay(relayId, `[\u4E2D\u7EE7\u6267\u884C\u5931\u8D25\uFF1A${err.message}]`);
3373
- }
3374
- });
3375
- }
3376
- }
3377
- /**
3378
- * Hard-remove all scoped runtimes for an Agent (agent:terminate on delete).
3379
- */
3380
- async terminate(agentId) {
3381
- const keys = [...this.agents.keys()].filter(
3382
- (k) => k === agentId || k.startsWith(`${agentId}::`)
3383
- );
3384
- if (keys.length === 0) {
3385
- logger8.warn("terminate: no process for agent", { agentId });
3386
- this.sessionStore.deleteAllForAgent(agentId);
3387
- return;
3388
- }
3389
- for (const key of keys) {
3390
- const proc = this.agents.get(key);
3391
- if (proc) {
3392
- await this.closeRuntime(proc, "terminate");
3393
- }
3394
- }
3395
- this.sessionStore.deleteAllForAgent(agentId);
3396
- this.neuralBusManager.remove(agentId);
3397
- logger8.info("terminate: all scoped queries removed", { agentId, count: keys.length });
3398
- }
3399
- /** Stop one scoped SDK runtime (workdir change). */
3400
- async terminateScope(agentId, scope) {
3401
- const key = runtimeKey(agentId, scope);
3402
- const proc = this.agents.get(key);
3403
- if (!proc) {
3404
- logger8.info("terminateScope: no active runtime", { agentId, scope: scopeKey(scope) });
3405
- return;
3406
- }
3407
- await this.closeRuntime(proc, "terminateScope");
3408
- this.sessionStore.delete(agentId, scope);
3409
- logger8.info("terminateScope: scoped query removed", { agentId, scope: scopeKey(scope) });
3410
- }
3411
- async closeRuntime(proc, reason) {
3412
- const key = runtimeKey(proc.agentId, proc.scope);
3413
- const runtime = this.asRuntime(proc);
3414
- const { agentId } = proc;
3415
- const emitInterrupted = (task) => {
3416
- this.emit({
3417
- type: "agent:error",
3418
- payload: {
3419
- agentId,
3420
- conversationId: task.conversationId,
3421
- replyMessageId: task.replyMessageId,
3422
- traceId: task.traceId,
3423
- error: "\u5BF9\u8BDD\u88AB\u4E2D\u65AD"
3424
- }
3425
- });
3426
- };
3427
- if (runtime.status === "working" && runtime.currentTask) {
3428
- emitInterrupted(runtime.currentTask);
3429
- }
3430
- const queued = [...runtime.injectedTasks];
3431
- runtime.injectedTasks = [];
3432
- for (const t of queued) {
3433
- emitInterrupted(t);
3434
- }
3435
- const mergedAtClose = [...runtime.mergedTasks];
3436
- runtime.mergedTasks = [];
3437
- for (const t of mergedAtClose) {
3438
- if (runtime.currentTask) {
3439
- logger8.info("Emitting agent:merged on runtime close", {
3440
- agentId: runtime.agentId,
3441
- replyMessageId: t.replyMessageId,
3442
- mergedInto: runtime.currentTask.replyMessageId,
3443
- reason,
3444
- traceId: t.traceId
3445
- });
3446
- this.emit({
3447
- type: "agent:merged",
3448
- payload: {
3449
- agentId: runtime.agentId,
3450
- conversationId: t.conversationId,
3451
- ackId: t.replyMessageId,
3452
- mergedIntoAckId: runtime.currentTask.replyMessageId,
3453
- groupId: t.groupId,
3454
- traceId: t.traceId
3455
- }
3456
- });
3457
- } else {
3458
- emitInterrupted(t);
3459
- }
3460
- }
3461
- runtime.currentTask = null;
3462
- try {
3463
- runtime.inputController.close();
3464
- await this.awaitQueryReturn(runtime.query, 5e3, agentId);
3465
- } catch (e) {
3466
- logger8.error(`${reason}: close query failed`, { agentId, scope: scopeKey(proc.scope), error: e });
3467
- }
3468
- proc.status = "dead";
3469
- this.agents.delete(key);
3470
- this.lastUsedAt.delete(key);
3471
- logger8.info(`${reason}: keeping workspace dir intact (per project policy)`, {
3472
- agentId,
3473
- scope: scopeKey(proc.scope),
3474
- cwd: proc.cwd
3475
- });
3476
- }
3477
- async recoverFromRestart(agents) {
3478
- logger8.info("Recovering Agent sessions after restart", { count: agents.length });
3479
- const agentsWithSession = agents.filter((a) => {
3480
- const sessionId = this.sessionStore.get(a.id, { kind: "single" });
3481
- return !!sessionId;
3482
- });
3483
- if (agentsWithSession.length === 0) {
3484
- logger8.info("No Agent sessions to recover");
3485
- return;
3486
- }
3487
- let warmed = 0;
3488
- const cap = this.queryConfig.maxActive;
3489
- for (const agent of agentsWithSession) {
3490
- if (warmed >= cap) {
3491
- logger8.info("Recovery warm cap reached", { cap, skipped: agentsWithSession.length - warmed });
3492
- break;
3493
- }
3494
- try {
3495
- const cwd = agent.workingDirectory || path6.join(this.workspacesDir, agent.id);
3496
- await this.acquire(agent, { kind: "single" }, cwd);
3497
- warmed++;
3498
- logger8.info("Agent process pre-created for recovery", { agentId: agent.id });
3499
- } catch (err) {
3500
- if (err instanceof BridgeBusyError) {
3501
- logger8.warn("Recovery stopped: bridge busy", { agentId: agent.id });
3502
- break;
3503
- }
3504
- logger8.warn("Failed to pre-create Agent for recovery, clearing session", {
3505
- agentId: agent.id,
3506
- error: err
3507
- });
3508
- this.sessionStore.delete(agent.id, { kind: "single" });
3509
- }
3510
- }
3511
- }
3512
- async consumeOutput(runtime) {
3513
- try {
3514
- for await (const message of runtime.query) {
3515
- const t = typeof message.type === "string" ? message.type : "unknown";
3516
- wsMetrics.incSdkOut(t);
3517
- mapSDKMessage(
3518
- runtime,
3519
- message,
3520
- this.emit,
3521
- this.sessionStore,
3522
- (doneMessageId) => this.onTaskCompleted(runtime, doneMessageId)
3523
- );
3524
- }
3525
- } catch (err) {
3526
- const errMsg = err.message ?? String(err);
3527
- const isResumeFail = /session|conversation.*not found/i.test(errMsg);
3528
- logger8.error("Agent query stream ended with error", {
3529
- agentId: runtime.agentId,
3530
- scope: scopeKey(runtime.scope),
3531
- isResumeFail,
3532
- staleSessionId: runtime.ccSessionId,
3533
- error: err
3534
- });
3535
- this.sessionStore.delete(runtime.agentId, runtime.scope);
3536
- logger8.info("Cleared stale session after query crash", {
3537
- agentId: runtime.agentId,
3538
- scope: scopeKey(runtime.scope)
3539
- });
3540
- runtime.status = "dead";
3541
- const key = runtimeKey(runtime.agentId, runtime.scope);
3542
- this.agents.delete(key);
3543
- this.lastUsedAt.delete(key);
3544
- const errorText = isResumeFail ? `\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u6D88\u606F\uFF08${errMsg}\uFF09` : `Agent query crashed: ${errMsg}`;
3545
- if (runtime.currentTask) {
3546
- this.emit({
3547
- type: "agent:error",
3548
- payload: {
3549
- agentId: runtime.agentId,
3550
- conversationId: runtime.currentTask.conversationId,
3551
- ackId: runtime.currentTask.replyMessageId,
3552
- traceId: runtime.currentTask.traceId,
3553
- error: errorText
3554
- }
3555
- });
3556
- runtime.currentTask = null;
3557
- }
3558
- for (const task of runtime.injectedTasks) {
3559
- this.emit({
3560
- type: "agent:error",
3561
- payload: {
3562
- agentId: runtime.agentId,
3563
- conversationId: task.conversationId,
3564
- ackId: task.replyMessageId,
3565
- traceId: task.traceId,
3566
- error: errorText
3567
- }
3568
- });
3569
- }
3570
- runtime.injectedTasks = [];
3571
- for (const task of runtime.mergedTasks) {
3572
- this.emit({
3573
- type: "agent:error",
3574
- payload: {
3575
- agentId: runtime.agentId,
3576
- conversationId: task.conversationId,
3577
- ackId: task.replyMessageId,
3578
- traceId: task.traceId,
3579
- error: errorText
3580
- }
3581
- });
3582
- }
3583
- runtime.mergedTasks = [];
3584
- }
3585
- }
3586
- getStatus(agentId, scope = { kind: "single" }) {
3587
- return this.agents.get(runtimeKey(agentId, scope))?.status ?? null;
3588
- }
3589
- getManagedAgentIds() {
3590
- const ids = /* @__PURE__ */ new Set();
3591
- for (const key of this.agents.keys()) {
3592
- const agentId = key.includes("::") ? key.split("::")[0] : key;
3593
- ids.add(agentId);
3594
- }
3595
- return [...ids];
3596
- }
3597
- async shutdownAll() {
3598
- logger8.info("Shutting down all Agent processes", { count: this.agents.size });
3599
- this.askQuestionRegistry.cancelAll("agent_aborted");
3600
- if (this.evictionTimer) {
3601
- clearInterval(this.evictionTimer);
3602
- this.evictionTimer = null;
3603
- }
3604
- for (const [key, proc] of this.agents) {
3605
- try {
3606
- const runtime = this.asRuntime(proc);
3607
- runtime.inputController?.close();
3608
- runtime.query?.return(void 0);
3609
- proc.status = "dead";
3610
- logger8.info("Agent process shut down", { agentId: proc.agentId, scope: scopeKey(proc.scope), key });
3611
- } catch (err) {
3612
- logger8.error("Error shutting down Agent", { agentId: proc.agentId, error: err });
3613
- }
3614
- }
3615
- this.agents.clear();
3616
- this.lastUsedAt.clear();
3617
- }
3618
- async cancelReply(payload) {
3619
- const { agentId, replyMessageId, traceId, conversationId } = payload;
3620
- let proc;
3621
- for (const p of this.agents.values()) {
3622
- if (p.agentId !== agentId || p.status === "dead") continue;
3623
- if (p.currentTask?.replyMessageId === replyMessageId) {
3624
- proc = p;
3625
- break;
3626
- }
3627
- }
3628
- if (!proc) {
3629
- logger8.warn("cancelReply: no active process for reply", { agentId, replyMessageId });
3630
- return;
3631
- }
3632
- const runtime = this.asRuntime(proc);
3633
- const key = runtimeKey(agentId, proc.scope);
3634
- if (!runtime.currentTask || runtime.currentTask.replyMessageId !== replyMessageId) {
3635
- logger8.warn("cancelReply: replyMessageId mismatch", {
3636
- agentId,
3637
- replyMessageId,
3638
- expected: runtime.currentTask?.replyMessageId
3639
- });
3640
- return;
3641
- }
3642
- const emitCancelled = (task) => {
3643
- this.emit({
3644
- type: "agent:error",
3645
- payload: {
3646
- agentId,
3647
- conversationId: task.conversationId,
3648
- ackId: task.replyMessageId,
3649
- traceId: task.traceId,
3650
- error: "\u5DF2\u53D6\u6D88"
3651
- }
3652
- });
3653
- };
3654
- emitCancelled(runtime.currentTask);
3655
- const queued = [...runtime.injectedTasks];
3656
- runtime.injectedTasks = [];
3657
- for (const t of queued) {
3658
- emitCancelled(t);
3659
- }
3660
- runtime.currentTask = null;
3661
- proc.status = "dead";
3662
- this.agents.delete(key);
3663
- this.lastUsedAt.delete(key);
3664
- logger8.info("cancelReply: process torn down", {
3665
- agentId,
3666
- scope: scopeKey(proc.scope),
3667
- conversationId,
3668
- traceId
3669
- });
3670
- try {
3671
- runtime.inputController.close();
3672
- } catch (err) {
3673
- logger8.error("cancelReply: inputController.close failed", { agentId, error: err });
3674
- }
3675
- runtime.query.return(void 0).catch((err) => {
3676
- logger8.warn("cancelReply: query.return threw", { agentId, error: err });
3677
- });
3678
- }
3679
- };
3680
-
3681
- // src/agentRegistry.ts
3682
- var logger9 = createModuleLogger("agent.registry");
3683
- var HttpAgentRegistry = class {
3684
- constructor(serverApiUrl) {
3685
- this.serverApiUrl = serverApiUrl;
3686
- }
3687
- serverApiUrl;
3688
- agents = /* @__PURE__ */ new Map();
3689
- apiUrl(suffix) {
3690
- const base = this.serverApiUrl.replace(/\/$/, "");
3691
- const path10 = suffix.startsWith("/") ? suffix : `/${suffix}`;
3692
- return `${base}${path10}`;
3693
- }
3694
- async refresh() {
3695
- try {
3696
- const res = await fetch(this.apiUrl("/api/agents"));
3697
- if (!res.ok) {
3698
- logger9.warn("Agent registry refresh failed", { status: res.status });
3699
- return;
3700
- }
3701
- const body = await res.json();
3702
- if (!Array.isArray(body)) {
3703
- logger9.warn("Agent registry refresh: expected array");
3704
- return;
3705
- }
3706
- this.agents.clear();
3707
- for (const item of body) {
3708
- const a = item;
3709
- if (a && typeof a.id === "string") {
3710
- this.agents.set(a.id, a);
3711
- }
3712
- }
3713
- logger9.info("Agent registry refreshed", { count: this.agents.size });
3714
- } catch (e) {
3715
- logger9.warn("Agent registry refresh unreachable, keeping cache", { error: e });
3716
- }
3717
- }
3718
- getById(id) {
3719
- return this.agents.get(id) ?? null;
3720
- }
3721
- /**
3722
- * Fetch a single agent directly from the server and upsert into cache.
3723
- * Used as a fallback when task:dispatch arrives before agent:created WS push.
3724
- */
3725
- async fetchById(id) {
3726
- try {
3727
- const res = await fetch(this.apiUrl(`/api/agents/${encodeURIComponent(id)}`));
3728
- if (!res.ok) {
3729
- logger9.warn("fetchById failed", { agentId: id, status: res.status });
3730
- return null;
3731
- }
3732
- const agent = await res.json();
3733
- if (agent && typeof agent.id === "string") {
3734
- this.agents.set(agent.id, agent);
3735
- logger9.info("Agent registry fetchById upserted", { agentId: id });
3736
- }
3737
- return agent;
3738
- } catch (e) {
3739
- logger9.warn("fetchById unreachable", { agentId: id, error: e });
3740
- return null;
3741
- }
3742
- }
3743
- getAll() {
3744
- return [...this.agents.values()];
3745
- }
3746
- upsert(agent) {
3747
- this.agents.set(agent.id, agent);
3748
- logger9.debug("Agent registry upsert", { agentId: agent.id });
3749
- }
3750
- remove(agentId) {
3751
- this.agents.delete(agentId);
3752
- logger9.debug("Agent registry remove", { agentId });
3753
- }
3754
- };
3755
-
3756
- // src/groupRegistry.ts
3757
- var logger10 = createModuleLogger("neural.groupRegistry");
3758
- var GroupRegistry = class {
3759
- groups = /* @__PURE__ */ new Map();
3760
- serverApiUrl;
3761
- constructor(serverApiUrl) {
3762
- this.serverApiUrl = serverApiUrl.replace(/\/$/, "");
3763
- }
3764
- async refresh() {
3765
- try {
3766
- const res = await fetch(`${this.serverApiUrl}/api/groups`);
3767
- if (!res.ok) {
3768
- logger10.warn("GroupRegistry refresh failed", { status: res.status });
3769
- return;
3770
- }
3771
- const body = await res.json();
3772
- if (!Array.isArray(body)) {
3773
- logger10.warn("GroupRegistry refresh: expected array");
3774
- return;
3775
- }
3776
- this.groups.clear();
3777
- for (const item of body) {
3778
- const g = item;
3779
- if (g && typeof g.id === "string") {
3780
- this.groups.set(g.id, {
3781
- groupId: g.id,
3782
- name: g.name ?? "",
3783
- conversationId: null,
3784
- members: (g.members ?? []).filter((m) => typeof m.agentId === "string").map((m) => m.agentId)
3785
- });
3786
- }
3787
- }
3788
- logger10.info("GroupRegistry refreshed", { count: this.groups.size });
3789
- } catch (e) {
3790
- logger10.warn("GroupRegistry refresh unreachable", { error: e });
3791
- }
3792
- }
3793
- getById(groupId) {
3794
- return this.groups.get(groupId) ?? null;
3795
- }
3796
- /**
3797
- * Fuzzy match by name (case-insensitive substring).
3798
- * Returns the first match.
3799
- */
3800
- resolveByName(name) {
3801
- const lower = name.toLowerCase().trim();
3802
- if (!lower) return null;
3803
- for (const g of this.groups.values()) {
3804
- if (g.name.toLowerCase().includes(lower)) return g;
3805
- }
3806
- return null;
3807
- }
3808
- /**
3809
- * Resolve a target_scope string to a proper scope with groupId and conversationId.
3810
- *
3811
- * Accepts:
3812
- * - "group:grp_xxx" — direct ID lookup
3813
- * - "group:方圆宝产品讨论组" — fuzzy name match
3814
- * - "single" — returns null (not a group)
3815
- *
3816
- * Fetches conversationId from Server if not cached.
3817
- */
3818
- async resolveScope(rawScope) {
3819
- if (!rawScope.startsWith("group:")) {
3820
- return null;
3821
- }
3822
- const suffix = rawScope.slice(6);
3823
- if (suffix.startsWith("grp_")) {
3824
- const info2 = this.groups.get(suffix);
3825
- if (!info2) {
3826
- logger10.info("GroupRegistry resolveScope: ID not found in cache", { rawScope, suffix });
3827
- return null;
3828
- }
3829
- const conversationId2 = await this.fetchConversationId(info2.groupId);
3830
- if (!conversationId2) return null;
3831
- return {
3832
- groupId: info2.groupId,
3833
- scopeKey: `group:${info2.groupId}`,
3834
- conversationId: conversationId2,
3835
- groupName: info2.name
3836
- };
3837
- }
3838
- const info = this.resolveByName(suffix);
3839
- if (!info) {
3840
- logger10.info("GroupRegistry resolveScope: name not found", { rawScope, searchName: suffix });
3841
- return null;
3842
- }
3843
- const conversationId = await this.fetchConversationId(info.groupId);
3844
- if (!conversationId) return null;
3845
- logger10.info("GroupRegistry resolved scope", {
3846
- rawScope,
3847
- resolvedGroupId: info.groupId,
3848
- resolvedName: info.name,
3849
- conversationId
3850
- });
3851
- return {
3852
- groupId: info.groupId,
3853
- scopeKey: `group:${info.groupId}`,
3854
- conversationId,
3855
- groupName: info.name
3856
- };
3857
- }
3858
- async fetchConversationId(groupId) {
3859
- try {
3860
- const res = await fetch(`${this.serverApiUrl}/api/groups/resolve?name=${encodeURIComponent(groupId)}&byId=1`);
3861
- if (!res.ok) {
3862
- logger10.warn("GroupRegistry fetchConversationId failed", { groupId, status: res.status });
3863
- return null;
3864
- }
3865
- const data = await res.json();
3866
- if (!data.conversationId) {
3867
- logger10.warn("GroupRegistry fetchConversationId: no conversationId", { groupId });
3868
- return null;
3869
- }
3870
- return data.conversationId;
3871
- } catch (e) {
3872
- logger10.error("GroupRegistry fetchConversationId error", { groupId, error: e });
3873
- return null;
3874
- }
3875
- }
3876
- getAll() {
3877
- return [...this.groups.values()];
3878
- }
3879
- };
3880
-
3881
- // src/connector.ts
3882
- import WebSocket from "ws";
3883
- var logger11 = createModuleLogger("ws.connector");
3884
- var ServerConnector = class {
3885
- ws = null;
3886
- reconnectAttempts = 0;
3887
- delays = [1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
3888
- reconnectTimer = null;
3889
- closing = false;
3890
- config;
3891
- agentIds;
3892
- onTaskDispatch;
3893
- onGroupTaskDispatch;
3894
- onStopGeneration;
3895
- onConnected;
3896
- onServerPush;
3897
- constructor(params) {
3898
- this.config = params.config;
3899
- this.agentIds = params.agentIds;
3900
- this.onTaskDispatch = params.onTaskDispatch;
3901
- this.onGroupTaskDispatch = params.onGroupTaskDispatch;
3902
- this.onStopGeneration = params.onStopGeneration;
3903
- this.onConnected = params.onConnected;
3904
- this.onServerPush = params.onServerPush;
3905
- }
3906
- connect() {
3907
- if (this.closing) return;
3908
- logger11.info("Connecting to server", { url: this.config.serverUrl });
3909
- const ws = new WebSocket(this.config.serverUrl);
3910
- ws.on("open", () => {
3911
- this.ws = ws;
3912
- this.reconnectAttempts = 0;
3913
- logger11.info("Connected to server", { url: this.config.serverUrl });
3914
- void this.handleOpen();
3915
- });
3916
- ws.on("message", (data) => {
3917
- this.handleMessage(data);
3918
- });
3919
- ws.on("close", (code, reason) => {
3920
- logger11.warn("Disconnected from server", {
3921
- code,
3922
- reason: reason.toString()
3923
- });
3924
- this.ws = null;
3925
- if (!this.closing) {
3926
- this.scheduleReconnect();
3927
- }
3928
- });
3929
- ws.on("error", (err) => {
3930
- logger11.error("WebSocket error", { error: err });
3931
- });
3932
- }
3933
- async handleOpen() {
3934
- try {
3935
- await this.onConnected();
3936
- logger11.info("Recovery complete, sending bridge:register");
3937
- } catch (err) {
3938
- logger11.error("Recovery failed, registering with degraded state", { error: err });
3939
- }
3940
- this.register();
3941
- }
3942
- register() {
3943
- const ids = this.agentIds();
3944
- const qc = this.config.queryConfig ?? DEFAULT_QUERY_CONFIG;
3945
- this.send({
3946
- type: "bridge:register",
3947
- payload: {
3948
- bridgeId: this.config.bridgeId,
3949
- agents: ids,
3950
- queryConfig: {
3951
- maxActive: qc.maxActive,
3952
- idleTimeoutMs: qc.idleTimeoutMs
3953
- }
3954
- }
3955
- });
3956
- logger11.info("Sent bridge:register", {
3957
- bridgeId: this.config.bridgeId,
3958
- agents: ids
3959
- });
3960
- }
3961
- handleMessage(data) {
3962
- let msg;
3963
- try {
3964
- const raw = typeof data === "string" ? data : data.toString("utf8");
3965
- msg = parseWSMessage(raw);
3966
- } catch (e) {
3967
- logger11.error("Invalid WS message from server", { error: e });
3968
- return;
3969
- }
3970
- wsMetrics.incRecv(msg.type);
3971
- switch (msg.type) {
3972
- case "heartbeat": {
3973
- this.send(msg);
3974
- return;
3975
- }
3976
- case "task:dispatch": {
3977
- void this.onTaskDispatch(msg.payload).catch((err) => {
3978
- logger11.error("Failed to handle task:dispatch", {
3979
- error: err,
3980
- traceId: msg.payload.traceId
3981
- });
3982
- });
3983
- return;
3984
- }
3985
- case "task:group_dispatch": {
3986
- if (this.onGroupTaskDispatch) {
3987
- void this.onGroupTaskDispatch(msg.payload).catch((err) => {
3988
- logger11.error("Failed to handle task:group_dispatch", {
3989
- error: err,
3990
- traceId: msg.payload.traceId
3991
- });
3992
- });
3993
- } else {
3994
- logger11.warn("Received task:group_dispatch but no handler registered");
3995
- }
3996
- return;
3997
- }
3998
- case "user:stop_generation": {
3999
- void this.onStopGeneration(msg.payload).catch((err) => {
4000
- logger11.error("Failed to handle user:stop_generation", {
4001
- error: err,
4002
- traceId: msg.payload.traceId
4003
- });
4004
- });
4005
- return;
4006
- }
4007
- case "bridge:list_models_request":
4008
- case "agent:terminate":
4009
- case "agent:terminate_scope":
4010
- case "agent:created":
4011
- case "agent:updated":
4012
- case "agent:deleted":
4013
- case "group:member_changed":
4014
- case "user:answer_question": {
4015
- if (this.onServerPush) {
4016
- void Promise.resolve(this.onServerPush(msg)).catch((err) => {
4017
- logger11.error("onServerPush handler failed", { error: err, type: msg.type });
4018
- });
4019
- }
4020
- return;
4021
- }
4022
- default: {
4023
- logger11.warn("Unhandled server message type", {
4024
- type: msg.type
4025
- });
4026
- }
4027
- }
4028
- }
4029
- send(msg) {
4030
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
4031
- logger11.warn("Cannot send: WebSocket not open", {
4032
- type: msg.type
4033
- });
4034
- return;
4035
- }
4036
- try {
4037
- this.ws.send(JSON.stringify(msg));
4038
- wsMetrics.incSend(msg.type);
4039
- } catch (e) {
4040
- logger11.error("Failed to send WS message", { error: e, type: msg.type });
4041
- }
4042
- }
4043
- scheduleReconnect() {
4044
- if (this.closing) return;
4045
- const delay = this.delays[Math.min(this.reconnectAttempts, this.delays.length - 1)];
4046
- this.reconnectAttempts++;
4047
- logger11.info("Scheduling reconnect", {
4048
- attempt: this.reconnectAttempts,
4049
- delayMs: delay
4050
- });
4051
- this.reconnectTimer = setTimeout(() => this.connect(), delay);
4052
- }
4053
- close() {
4054
- this.closing = true;
4055
- if (this.reconnectTimer) {
4056
- clearTimeout(this.reconnectTimer);
4057
- this.reconnectTimer = null;
4058
- }
4059
- if (this.ws) {
4060
- try {
4061
- this.ws.close(1e3, "Bridge shutting down");
4062
- } catch (e) {
4063
- logger11.error("Error closing WebSocket", { error: e });
4064
- }
4065
- this.ws = null;
4066
- }
4067
- logger11.info("Connector closed");
4068
- }
4069
- get isConnected() {
4070
- return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
4071
- }
4072
- };
4073
-
4074
- // src/modelQuerier.ts
4075
- import fs3 from "fs/promises";
4076
- import os5 from "os";
4077
- import path7 from "path";
4078
- var logger12 = createModuleLogger("bridge.modelQuerier");
4079
- async function listModels(queryFn, opts = {}) {
4080
- const t0 = Date.now();
4081
- const cwd = opts.cwd ?? path7.join(os5.homedir(), ".ahchat", "workspaces", "_list_models");
4082
- await fs3.mkdir(cwd, { recursive: true });
4083
- const fn = queryFn ?? (await import("@anthropic-ai/claude-agent-sdk")).query;
4084
- const ic = new InputController();
4085
- ic.push("Reply with exactly: PING", "");
4086
- const q = fn({
4087
- prompt: ic,
4088
- options: {
4089
- cwd,
4090
- systemPrompt: { type: "preset", preset: "claude_code", append: "" },
4091
- permissionMode: "bypassPermissions",
4092
- allowDangerouslySkipPermissions: true,
4093
- allowedTools: []
4094
- }
4095
- });
4096
- const initTimeoutMs = opts.initTimeoutMs ?? 3e4;
4097
- let initialized = false;
4098
- const initPromise = (async () => {
4099
- for await (const msg of q) {
4100
- const t = String(msg.type ?? "");
4101
- const sub = String(msg.subtype ?? "");
4102
- if (t === "system" && sub === "init") {
4103
- initialized = true;
4104
- return;
4105
- }
4106
- }
4107
- })();
4108
- try {
4109
- await Promise.race([
4110
- initPromise,
4111
- new Promise((_, rej) => {
4112
- setTimeout(() => rej(new Error(`init timeout after ${initTimeoutMs}ms`)), initTimeoutMs);
4113
- })
4114
- ]);
4115
- if (!initialized) {
4116
- throw new Error("generator ended before init");
4117
- }
4118
- const init = await q.initializationResult();
4119
- const models = init.models.map((m) => ({
4120
- value: m.value,
4121
- displayName: m.displayName,
4122
- description: m.description
4123
- }));
4124
- logger12.info("listModels done", { count: models.length, ms: Date.now() - t0 });
4125
- return models;
4126
- } finally {
4127
- try {
4128
- ic.close();
4129
- } catch {
4130
- }
4131
- try {
4132
- await q.return?.(void 0);
4133
- } catch {
4134
- }
4135
- }
4136
- }
4137
-
4138
- // src/lockfile.ts
4139
- import fs4 from "fs";
4140
- import path8 from "path";
4141
- var logger13 = createModuleLogger("bridge.lockfile");
4142
- var lockPath = null;
4143
- function isProcessAlive(pid) {
4144
- try {
4145
- process.kill(pid, 0);
4146
- return true;
4147
- } catch (e) {
4148
- const err = e;
4149
- if (err.code === "ESRCH") return false;
4150
- throw e;
4151
- }
4152
- }
4153
- function acquireLock(dataDir) {
4154
- const file = path8.join(dataDir, "bridge.lock");
4155
- lockPath = file;
4156
- if (fs4.existsSync(file)) {
4157
- const raw = fs4.readFileSync(file, "utf-8").trim();
4158
- const pid = Number.parseInt(raw, 10);
4159
- if (Number.isFinite(pid) && pid > 0) {
4160
- if (isProcessAlive(pid)) {
4161
- throw new Error(`Bridge already running (PID: ${pid})`);
4162
- }
4163
- logger13.warn("Removing stale bridge.lock (process not found)", { pid, path: file });
4164
- }
4165
- }
4166
- fs4.mkdirSync(path8.dirname(file), { recursive: true });
4167
- fs4.writeFileSync(file, String(process.pid), "utf-8");
4168
- logger13.info("Acquired bridge lock", { path: file, pid: process.pid });
4169
- const release = () => {
4170
- try {
4171
- if (lockPath && fs4.existsSync(lockPath)) {
4172
- const current = fs4.readFileSync(lockPath, "utf-8").trim();
4173
- if (current === String(process.pid)) {
4174
- fs4.unlinkSync(lockPath);
4175
- logger13.info("Released bridge lock", { path: lockPath });
4176
- }
4177
- }
4178
- } catch (e) {
4179
- logger13.error("Failed to release bridge lock", { error: e, path: lockPath });
4180
- } finally {
4181
- lockPath = null;
4182
- }
4183
- };
4184
- process.on("exit", release);
4185
- process.once("SIGINT", () => {
4186
- release();
4187
- process.exit(0);
4188
- });
4189
- process.once("SIGTERM", () => {
4190
- release();
4191
- process.exit(0);
4192
- });
4193
- }
4194
-
4195
- // src/groupPromptBuilder.ts
4196
- function decideRole(p) {
4197
- if (p.isMentioned) return "mentioned";
4198
- if (p.mentions.length > 0) return "overhearer";
4199
- return "open_floor";
4200
- }
4201
- function senderKindOf(p) {
4202
- return p.sender.kind;
4203
- }
4204
- var HEADER_BY_SENDER_ROLE = {
4205
- user: {
4206
- mentioned: () => [
4207
- "You were @mentioned in this message.",
4208
- "You SHOULD reply, but you retain the right to stay silent.",
4209
- `If you genuinely have nothing to add, reply with exactly the token \`${NO_REPLY_TOKEN}\` (and only that token).`
4210
- ],
4211
- overhearer: () => [
4212
- "You are an OVERHEARER \u2014 you received this message but someone else was @mentioned.",
4213
- `Default behavior: reply with exactly the token \`${NO_REPLY_TOKEN}\` and only that token.`,
4214
- "ONLY chime in if ONE of the following is true:",
4215
- " (a) your role/expertise is uniquely required for this question;",
4216
- " (b) your personality (see your system prompt) compels you to interject;",
4217
- " (c) there is a factual error you must correct.",
4218
- `Otherwise reply \`${NO_REPLY_TOKEN}\`.`
4219
- ],
4220
- open_floor: () => [
4221
- "This message is addressed to the whole group (no one was @mentioned).",
4222
- "Treat it like a real IM group: reply if your role, expertise, or personality has something to add.",
4223
- `If you have nothing meaningful to contribute, reply with exactly the token \`${NO_REPLY_TOKEN}\` (and only that token) to stay silent.`
4224
- ]
4225
- },
4226
- agent: {
4227
- mentioned: ({ senderName }) => [
4228
- `A fellow agent (${senderName}) @mentioned you in the group.`,
4229
- "You SHOULD reply, but you may stay silent.",
4230
- `Per your platform rules, if you have nothing to add, reply \`${NO_REPLY_TOKEN}\`.`
4231
- ],
4232
- overhearer: ({ senderName }) => [
4233
- `A fellow agent (${senderName}) spoke and @mentioned someone else.`,
4234
- `Per your platform rules, default \`${NO_REPLY_TOKEN}\` unless your expertise is uniquely required or there is a factual error.`
4235
- ],
4236
- open_floor: ({ senderName }) => [
4237
- `A fellow agent (${senderName}) addressed the group.`,
4238
- `Per your platform rules, default \`${NO_REPLY_TOKEN}\` unless your expertise is uniquely needed or there is a factual error.`
4239
- ]
4240
- }
4241
- };
4242
- function buildGroupPrompt(payload) {
4243
- const lines = [];
4244
- const kind = senderKindOf(payload);
4245
- const role = decideRole(payload);
4246
- const senderName = payload.sender.kind === "agent" ? payload.sender.agentName : "user";
4247
- lines.push(`[Group: ${payload.groupName}] \xB7 ${payload.groupMemberCount}-person group`);
4248
- lines.push(`Members: ${payload.groupMemberNames.join(", ")}`);
4249
- lines.push(`You are: ${payload.agentName}`);
4250
- for (const line of HEADER_BY_SENDER_ROLE[kind][role]({ senderName })) {
4251
- lines.push(line);
4252
- }
4253
- lines.push("");
4254
- lines.push("--- chat history ---");
4255
- if (payload.context.length === 0) {
4256
- lines.push("(no history)");
4257
- } else {
4258
- for (const msg of payload.context) {
4259
- const s = msg.role === "user" ? "user" : msg.senderAgentName ?? `agent:${msg.senderAgentId ?? "unknown"}`;
4260
- lines.push(`[${s}]: ${msg.content}`);
4261
- }
4262
- }
4263
- lines.push("--- end history ---");
4264
- lines.push("");
4265
- if (payload.replyToMessage) {
4266
- const rts = payload.replyToMessage.role === "user" ? "user" : payload.replyToMessage.senderAgentName ?? `agent:${payload.replyToMessage.senderAgentId ?? "unknown"}`;
4267
- lines.push(`> Reply to [${rts}]: ${payload.replyToMessage.content}`);
4268
- lines.push("");
4269
- }
4270
- const speakerLabel = payload.sender.kind === "user" ? "user" : payload.sender.agentName;
4271
- lines.push("------- group task -------");
4272
- lines.push(`[${speakerLabel}]: ${payload.content}`);
4273
- lines.push("------- end task -------");
4274
- lines.push("");
4275
- lines.push(
4276
- "If you choose to speak, reply from your professional perspective. Your text will appear in the group chat verbatim."
4277
- );
4278
- return lines.join("\n");
4279
- }
4280
-
4281
- // src/messageHandler.ts
4282
- var logger14 = createModuleLogger("msg.handler");
4283
- function emitTaskAck(emit, ackId, agentId, traceId) {
4284
- logger14.info("Emitting task:ack", { ackId, agentId, traceId });
4285
- emit({
4286
- type: "task:ack",
4287
- payload: {
4288
- ackId,
4289
- agentId,
4290
- traceId,
4291
- receivedAt: (/* @__PURE__ */ new Date()).toISOString()
4292
- }
4293
- });
4294
- }
4295
- function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
4296
- return async (payload) => {
4297
- logger14.info("Handling task:dispatch", {
4298
- agentId: payload.agentId,
4299
- messageId: payload.messageId,
4300
- ackId: payload.ackId,
4301
- traceId: payload.traceId
4302
- });
4303
- emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
4304
- let agentConfig = agentRegistry.getById(payload.agentId);
4305
- if (!agentConfig) {
4306
- logger14.warn("Agent not in registry, attempting live fetch", {
4307
- agentId: payload.agentId,
4308
- traceId: payload.traceId
4309
- });
4310
- agentConfig = await agentRegistry.fetchById(payload.agentId);
4311
- }
4312
- if (!agentConfig) {
4313
- logger14.error("Agent not found for task:dispatch (after live fetch)", {
4314
- agentId: payload.agentId,
4315
- traceId: payload.traceId
4316
- });
4317
- emit({
4318
- type: "agent:error",
4319
- payload: {
4320
- agentId: payload.agentId,
4321
- conversationId: payload.conversationId,
4322
- ackId: payload.ackId,
4323
- traceId: payload.traceId,
4324
- error: "Agent not found"
4325
- }
4326
- });
4327
- return;
4328
- }
4329
- try {
4330
- await agentManager.acquire(agentConfig, { kind: "single" }, payload.cwd);
4331
- await agentManager.sendMessage({
4332
- agentId: payload.agentId,
4333
- scope: { kind: "single" },
4334
- conversationId: payload.conversationId,
4335
- content: payload.content,
4336
- replyMessageId: payload.ackId,
4337
- traceId: payload.traceId
4338
- });
4339
- } catch (err) {
4340
- logger14.error("Failed to dispatch message to Agent", {
4341
- error: err,
4342
- agentId: payload.agentId,
4343
- traceId: payload.traceId
4344
- });
4345
- emit({
4346
- type: "agent:error",
4347
- payload: {
4348
- agentId: payload.agentId,
4349
- conversationId: payload.conversationId,
4350
- ackId: payload.ackId,
4351
- traceId: payload.traceId,
4352
- error: `Bridge dispatch error: ${err.message}`
4353
- }
4354
- });
4355
- }
4356
- };
4357
- }
4358
- function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4359
- return async (payload) => {
4360
- logger14.info("Handling task:group_dispatch", {
4361
- agentId: payload.agentId,
4362
- groupId: payload.groupId,
4363
- ackId: payload.ackId,
4364
- isMentioned: payload.isMentioned,
4365
- senderKind: payload.sender.kind,
4366
- chainDepth: payload.chainDepth,
4367
- traceId: payload.traceId
4368
- });
4369
- emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
4370
- let agentConfig = agentRegistry.getById(payload.agentId);
4371
- if (!agentConfig) {
4372
- logger14.warn("Agent not in registry for group dispatch, attempting live fetch", {
4373
- agentId: payload.agentId,
4374
- traceId: payload.traceId
4375
- });
4376
- agentConfig = await agentRegistry.fetchById(payload.agentId);
4377
- }
4378
- if (!agentConfig) {
4379
- logger14.error("Agent not found for task:group_dispatch (after live fetch)", {
4380
- agentId: payload.agentId,
4381
- traceId: payload.traceId
4382
- });
4383
- emit({
4384
- type: "agent:error",
4385
- payload: {
4386
- agentId: payload.agentId,
4387
- conversationId: payload.conversationId,
4388
- ackId: payload.ackId,
4389
- traceId: payload.traceId,
4390
- error: "Agent not found"
4391
- }
4392
- });
4393
- return;
4394
- }
4395
- const groupPrompt = buildGroupPrompt(payload);
4396
- try {
4397
- await agentManager.acquire(
4398
- agentConfig,
4399
- { kind: "group", groupId: payload.groupId },
4400
- payload.cwd
4401
- );
4402
- await agentManager.sendMessage({
4403
- agentId: payload.agentId,
4404
- scope: { kind: "group", groupId: payload.groupId },
4405
- conversationId: payload.conversationId,
4406
- content: groupPrompt,
4407
- replyMessageId: payload.ackId,
4408
- traceId: payload.traceId,
4409
- groupId: payload.groupId
4410
- });
4411
- } catch (err) {
4412
- logger14.error("Failed to dispatch group message to Agent", {
4413
- error: err,
4414
- agentId: payload.agentId,
4415
- groupId: payload.groupId,
4416
- traceId: payload.traceId
4417
- });
4418
- emit({
4419
- type: "agent:error",
4420
- payload: {
4421
- agentId: payload.agentId,
4422
- conversationId: payload.conversationId,
4423
- ackId: payload.ackId,
4424
- traceId: payload.traceId,
4425
- error: `Bridge group dispatch error: ${err.message}`
4426
- }
4427
- });
4428
- }
4429
- };
4430
- }
4431
-
4432
- // src/sessionStore.ts
4433
- import fs5 from "fs";
4434
- import path9 from "path";
4435
- var logger15 = createModuleLogger("session.store");
4436
- var SessionStore = class {
4437
- filePath;
4438
- cache;
4439
- constructor(dataDir) {
4440
- this.filePath = path9.join(dataDir, "sessions.json");
4441
- this.cache = this.loadFromDisk();
4442
- }
4443
- cacheKey(agentId, scope) {
4444
- return runtimeKey(agentId, scope);
4445
- }
4446
- get(agentId, scope) {
4447
- return this.cache[this.cacheKey(agentId, scope)] ?? null;
4448
- }
4449
- set(agentId, scope, sessionId) {
4450
- this.cache[this.cacheKey(agentId, scope)] = sessionId;
4451
- this.saveToDisk();
4452
- }
4453
- delete(agentId, scope) {
4454
- delete this.cache[this.cacheKey(agentId, scope)];
4455
- this.saveToDisk();
4456
- }
4457
- deleteAllForAgent(agentId) {
4458
- const prefix = `${agentId}::`;
4459
- let changed = false;
4460
- for (const key of Object.keys(this.cache)) {
4461
- if (key === agentId || key.startsWith(prefix)) {
4462
- delete this.cache[key];
4463
- changed = true;
4464
- }
4465
- }
4466
- if (changed) {
4467
- this.saveToDisk();
4468
- }
4469
- }
4470
- getAll() {
4471
- return new Map(Object.entries(this.cache));
4472
- }
4473
- loadFromDisk() {
4474
- try {
4475
- if (!fs5.existsSync(this.filePath)) return {};
4476
- const raw = fs5.readFileSync(this.filePath, "utf-8");
4477
- const parsed = JSON.parse(raw);
4478
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
4479
- const map = parsed;
4480
- const migrated = {};
4481
- for (const [key, sessionId] of Object.entries(map)) {
4482
- if (key.includes("::")) {
4483
- migrated[key] = sessionId;
4484
- } else {
4485
- migrated[`${key}::single`] = sessionId;
4486
- logger15.info("Migrated legacy session key to scoped key", {
4487
- legacyKey: key,
4488
- newKey: `${key}::single`
4489
- });
4490
- }
4491
- }
4492
- return migrated;
4493
- } catch (e) {
4494
- logger15.warn("Failed to load sessions file, starting fresh", { error: e, path: this.filePath });
4495
- return {};
4496
- }
4497
- }
4498
- saveToDisk() {
4499
- try {
4500
- const dir = path9.dirname(this.filePath);
4501
- fs5.mkdirSync(dir, { recursive: true });
4502
- fs5.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), "utf-8");
4503
- } catch (e) {
4504
- logger15.error("Failed to save sessions file", { error: e, path: this.filePath });
4505
- }
4506
- }
4507
- };
4508
-
4509
- export {
4510
- loadBridgeConfig,
4511
- ensureDir,
4512
- createModuleLogger,
4513
- AskQuestionRegistry,
4514
- formatAnswerForSDK,
4515
- wsMetrics,
4516
- AgentManager,
4517
- HttpAgentRegistry,
4518
- GroupRegistry,
4519
- ServerConnector,
4520
- listModels,
4521
- acquireLock,
4522
- createTaskDispatchHandler,
4523
- createGroupTaskDispatchHandler,
4524
- SessionStore
4525
- };