@fangyb/ahchat-bridge 0.1.7 → 0.1.9

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