@fangyb/ahchat-bridge 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -665,11 +665,11 @@ var RotatingFileStream = class extends Writable {
665
665
  timeout;
666
666
  timeoutPromise;
667
667
  constructor(generator, options) {
668
- const { encoding, history, maxFiles, maxSize, path: path10 } = options;
668
+ const { encoding, history, maxFiles, maxSize, path: path12 } = options;
669
669
  super({ decodeStrings: true, defaultEncoding: encoding });
670
670
  this.createGzip = createGzip;
671
671
  this.exec = exec;
672
- this.filename = path10 + generator(null);
672
+ this.filename = path12 + generator(null);
673
673
  this.fsCreateReadStream = createReadStream;
674
674
  this.fsCreateWriteStream = createWriteStream;
675
675
  this.fsOpen = open;
@@ -681,7 +681,7 @@ var RotatingFileStream = class extends Writable {
681
681
  this.options = options;
682
682
  this.stdout = process.stdout;
683
683
  if (maxFiles || maxSize)
684
- options.history = path10 + (history ? history : this.generator(null) + ".txt");
684
+ options.history = path12 + (history ? history : this.generator(null) + ".txt");
685
685
  this.on("close", () => this.finished ? null : this.emit("finish"));
686
686
  this.on("finish", () => this.finished = this.clear());
687
687
  (async () => {
@@ -809,9 +809,9 @@ var RotatingFileStream = class extends Writable {
809
809
  return this.move();
810
810
  }
811
811
  async findName() {
812
- const { interval, path: path10, intervalBoundary } = this.options;
812
+ const { interval, path: path12, intervalBoundary } = this.options;
813
813
  for (let index = 1; index < 1e3; ++index) {
814
- const filename = path10 + this.generator(interval && intervalBoundary ? new Date(this.prev) : this.rotation, index);
814
+ const filename = path12 + this.generator(interval && intervalBoundary ? new Date(this.prev) : this.rotation, index);
815
815
  if (!await exists(filename))
816
816
  return filename;
817
817
  }
@@ -841,11 +841,11 @@ var RotatingFileStream = class extends Writable {
841
841
  return this.unlink(filename);
842
842
  }
843
843
  async classical() {
844
- const { compress, path: path10, rotate } = this.options;
844
+ const { compress, path: path12, rotate } = this.options;
845
845
  let rotatedName = "";
846
846
  for (let count = rotate; count > 0; --count) {
847
- const currName = path10 + this.generator(count);
848
- const prevName = count === 1 ? this.filename : path10 + this.generator(count - 1);
847
+ const currName = path12 + this.generator(count);
848
+ const prevName = count === 1 ? this.filename : path12 + this.generator(count - 1);
849
849
  if (!await exists(prevName))
850
850
  continue;
851
851
  if (!rotatedName)
@@ -1383,10 +1383,82 @@ function createModuleLogger(module) {
1383
1383
  });
1384
1384
  }
1385
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
+
1386
1458
  // src/agentManager.ts
1387
- import fs2 from "fs/promises";
1459
+ import fs3 from "fs/promises";
1388
1460
  import os4 from "os";
1389
- import path6 from "path";
1461
+ import path7 from "path";
1390
1462
 
1391
1463
  // ../shared/src/constants.ts
1392
1464
  var NO_REPLY_TOKEN = "<no-reply/>";
@@ -1437,6 +1509,14 @@ with its own context, but they are all you. You have one tool to talk between th
1437
1509
  - Returns immediately with a delivery receipt. You do NOT wait for a reply.
1438
1510
  - Whether/how the other-scope self responds is its own decision.
1439
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
+
1440
1520
  When YOU receive a message wrapped as "[\u5185\u5FC3\u72EC\u767D \u2014 \u6765\u81EA\u4F60\u5728\u300C<scope>\u300D\u7684\u5206\u8EAB]":
1441
1521
  - That is literally you, talking to yourself from another scope. It's private \u2014
1442
1522
  nobody else in this scope hears it. Do NOT echo or quote the envelope text.
@@ -1450,6 +1530,31 @@ Pick neural_send whenever the user asks you to "tell people in group X ...", "as
1450
1530
  me in group X ...", "let me know what you've been doing in X", or anything that
1451
1531
  requires the you-in-another-scope to do something or share something. There is no
1452
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.
1453
1558
  `.trim();
1454
1559
  var FAN_OUT_TRACE_TTL_MS = 10 * 6e4;
1455
1560
 
@@ -1577,7 +1682,7 @@ var InputController = class {
1577
1682
  };
1578
1683
 
1579
1684
  // src/askQuestionRegistry.ts
1580
- var logger = createModuleLogger("askQuestionRegistry");
1685
+ var logger2 = createModuleLogger("askQuestionRegistry");
1581
1686
  var ASK_QUESTION_TIMEOUT_MS = 12e4;
1582
1687
  var TIMEOUT_ANSWER = "[User did not respond within 120 seconds. Please decide whether to proceed with reasonable defaults or skip this step.]";
1583
1688
  var AskQuestionRegistry = class {
@@ -1588,27 +1693,27 @@ var AskQuestionRegistry = class {
1588
1693
  const timer = setTimeout(() => {
1589
1694
  if (!this.entries.has(questionId)) return;
1590
1695
  this.entries.delete(questionId);
1591
- logger.warn("AskQuestion timeout", { questionId, agentId, timeoutMs });
1696
+ logger2.warn("AskQuestion timeout", { questionId, agentId, timeoutMs });
1592
1697
  try {
1593
1698
  onTimeout();
1594
1699
  } catch (e) {
1595
- logger.error("onTimeout cb threw", { error: e });
1700
+ logger2.error("onTimeout cb threw", { error: e });
1596
1701
  }
1597
1702
  resolve(TIMEOUT_ANSWER);
1598
1703
  }, timeoutMs);
1599
1704
  this.entries.set(questionId, { resolve, timer, agentId, askedAt: Date.now() });
1600
- logger.info("AskQuestion registered", { questionId, agentId, timeoutMs });
1705
+ logger2.info("AskQuestion registered", { questionId, agentId, timeoutMs });
1601
1706
  });
1602
1707
  }
1603
1708
  resolve(questionId, answerText) {
1604
1709
  const entry = this.entries.get(questionId);
1605
1710
  if (!entry) {
1606
- logger.warn("AskQuestion resolve: id not found (may be timed out)", { questionId });
1711
+ logger2.warn("AskQuestion resolve: id not found (may be timed out)", { questionId });
1607
1712
  return false;
1608
1713
  }
1609
1714
  clearTimeout(entry.timer);
1610
1715
  this.entries.delete(questionId);
1611
- logger.info("AskQuestion resolved", {
1716
+ logger2.info("AskQuestion resolved", {
1612
1717
  questionId,
1613
1718
  agentId: entry.agentId,
1614
1719
  waitedMs: Date.now() - entry.askedAt,
@@ -1620,7 +1725,7 @@ var AskQuestionRegistry = class {
1620
1725
  }
1621
1726
  cancelAll(reason) {
1622
1727
  if (this.entries.size === 0) return;
1623
- logger.warn("AskQuestion cancelAll", { reason, count: this.entries.size });
1728
+ logger2.warn("AskQuestion cancelAll", { reason, count: this.entries.size });
1624
1729
  for (const [, entry] of this.entries) {
1625
1730
  clearTimeout(entry.timer);
1626
1731
  entry.resolve(`[${reason}]`);
@@ -1649,7 +1754,7 @@ function runtimeKey(agentId, scope) {
1649
1754
  }
1650
1755
 
1651
1756
  // src/askUserQuestionGuard.ts
1652
- var logger2 = createModuleLogger("askUserQuestionGuard");
1757
+ var logger3 = createModuleLogger("askUserQuestionGuard");
1653
1758
  function formatAnswerForSDK(p) {
1654
1759
  const parts = ["[User Response]"];
1655
1760
  if (p.selectedLabels.length > 0) {
@@ -1667,16 +1772,16 @@ function makeAskUserQuestionGuard(deps) {
1667
1772
  return async (input) => {
1668
1773
  const task = deps.getCurrentTask();
1669
1774
  if (!task) {
1670
- logger2.error("AskUserQuestion received but no currentTask", { agentId: deps.agentId });
1775
+ logger3.error("AskUserQuestion received but no currentTask", { agentId: deps.agentId });
1671
1776
  return { behavior: "deny", message: "[Internal error: no active task context]" };
1672
1777
  }
1673
1778
  const questions = input.questions ?? [];
1674
1779
  if (questions.length === 0) {
1675
- logger2.warn("AskUserQuestion called with empty questions array", { agentId: deps.agentId });
1780
+ logger3.warn("AskUserQuestion called with empty questions array", { agentId: deps.agentId });
1676
1781
  return { behavior: "deny", message: "[Internal error: empty questions]" };
1677
1782
  }
1678
1783
  if (questions.length > 1) {
1679
- logger2.warn("AskUserQuestion received multi questions, Plan A only handles questions[0]", {
1784
+ logger3.warn("AskUserQuestion received multi questions, Plan A only handles questions[0]", {
1680
1785
  agentId: deps.agentId,
1681
1786
  count: questions.length
1682
1787
  });
@@ -1689,7 +1794,7 @@ function makeAskUserQuestionGuard(deps) {
1689
1794
  description: o.description
1690
1795
  }));
1691
1796
  const multiSelect = Boolean(q.multiSelect);
1692
- logger2.info("AskUserQuestion intercepted, emitting agent:ask_user_question", {
1797
+ logger3.info("AskUserQuestion intercepted, emitting agent:ask_user_question", {
1693
1798
  agentId: deps.agentId,
1694
1799
  scope: scopeKey(deps.scope),
1695
1800
  groupId: task.groupId,
@@ -1717,7 +1822,7 @@ function makeAskUserQuestionGuard(deps) {
1717
1822
  traceId: task.traceId
1718
1823
  }
1719
1824
  });
1720
- logger2.info("AskUserQuestion agent status awaiting_user", {
1825
+ logger3.info("AskUserQuestion agent status awaiting_user", {
1721
1826
  agentId: deps.agentId,
1722
1827
  questionId,
1723
1828
  groupId: task.groupId,
@@ -1740,7 +1845,7 @@ function makeAskUserQuestionGuard(deps) {
1740
1845
  }
1741
1846
  });
1742
1847
  });
1743
- logger2.info("AskUserQuestion agent status thinking (resume SDK)", {
1848
+ logger3.info("AskUserQuestion agent status thinking (resume SDK)", {
1744
1849
  agentId: deps.agentId,
1745
1850
  questionId,
1746
1851
  groupId: task.groupId,
@@ -1750,7 +1855,7 @@ function makeAskUserQuestionGuard(deps) {
1750
1855
  type: "agent:status",
1751
1856
  payload: { agentId: deps.agentId, status: "thinking" }
1752
1857
  });
1753
- logger2.info("AskUserQuestion answered, returning deny+message to SDK", {
1858
+ logger3.info("AskUserQuestion answered, returning deny+message to SDK", {
1754
1859
  agentId: deps.agentId,
1755
1860
  questionId,
1756
1861
  replyMessageId: task.replyMessageId,
@@ -1762,17 +1867,17 @@ function makeAskUserQuestionGuard(deps) {
1762
1867
  }
1763
1868
 
1764
1869
  // src/permissionGuard.ts
1765
- import path5 from "path";
1870
+ import path6 from "path";
1766
1871
 
1767
1872
  // ../shared/src/utils/pathSafety.ts
1768
- import path4 from "path";
1873
+ import path5 from "path";
1769
1874
  function isPathInside(parent, child) {
1770
- const resolvedParent = path4.resolve(parent);
1771
- const resolvedChild = path4.resolve(child);
1875
+ const resolvedParent = path5.resolve(parent);
1876
+ const resolvedChild = path5.resolve(child);
1772
1877
  if (resolvedParent === resolvedChild) return true;
1773
- const rel = path4.relative(resolvedParent, resolvedChild);
1878
+ const rel = path5.relative(resolvedParent, resolvedChild);
1774
1879
  if (rel === "") return true;
1775
- return !rel.startsWith("..") && !path4.isAbsolute(rel);
1880
+ return !rel.startsWith("..") && !path5.isAbsolute(rel);
1776
1881
  }
1777
1882
 
1778
1883
  // src/permissionGuard.ts
@@ -1787,7 +1892,7 @@ function makeCwdPermissionGuard(cwd, agentId, scope, log) {
1787
1892
  if (typeof raw !== "string" || raw.length === 0) {
1788
1893
  return { behavior: "allow" };
1789
1894
  }
1790
- const abs = path5.isAbsolute(raw) ? raw : path5.resolve(cwd, raw);
1895
+ const abs = path6.isAbsolute(raw) ? raw : path6.resolve(cwd, raw);
1791
1896
  if (isPathInside(cwd, abs)) {
1792
1897
  return { behavior: "allow" };
1793
1898
  }
@@ -1800,7 +1905,7 @@ function makeCwdPermissionGuard(cwd, agentId, scope, log) {
1800
1905
  }
1801
1906
 
1802
1907
  // src/neuralMcpServer.ts
1803
- var logger3 = createModuleLogger("neural.mcpServer");
1908
+ var logger4 = createModuleLogger("neural.mcpServer");
1804
1909
  function formatScopeLabel(key, groupName) {
1805
1910
  if (key === "single") return "\u5355\u804A";
1806
1911
  if (groupName) return `\u7FA4\u300C${groupName}\u300D`;
@@ -1824,7 +1929,7 @@ async function createNeuralMcpServer(deps) {
1824
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')
1825
1930
  },
1826
1931
  async (args) => {
1827
- logger3.info("neural_send tool called", {
1932
+ logger4.info("neural_send tool called", {
1828
1933
  agentId: deps.agentId,
1829
1934
  fromScope: currentScopeKey,
1830
1935
  rawTargetScope: args.target_scope,
@@ -1845,12 +1950,12 @@ async function createNeuralMcpServer(deps) {
1845
1950
  if (singleConvId) {
1846
1951
  conversationId = singleConvId;
1847
1952
  } else {
1848
- logger3.warn("neural_send: failed to resolve single conv", { agentId: deps.agentId });
1953
+ logger4.warn("neural_send: failed to resolve single conv", { agentId: deps.agentId });
1849
1954
  }
1850
1955
  } else if (args.target_scope.startsWith("group:")) {
1851
1956
  const r = await deps.groupRegistry.resolveScope(args.target_scope);
1852
1957
  if (!r) {
1853
- logger3.info("neural_send: target scope not found", { rawTargetScope: args.target_scope });
1958
+ logger4.info("neural_send: target scope not found", { rawTargetScope: args.target_scope });
1854
1959
  return {
1855
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` }],
1856
1961
  isError: true
@@ -1861,6 +1966,28 @@ async function createNeuralMcpServer(deps) {
1861
1966
  groupId = r.groupId;
1862
1967
  groupName = r.groupName;
1863
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
+ });
1864
1991
  } else {
1865
1992
  return {
1866
1993
  content: [{ type: "text", text: '[neural_send] target_scope \u5FC5\u987B\u662F "single" \u6216 "group:<\u7FA4\u540D\u6216 ID>"\u3002' }],
@@ -1868,7 +1995,7 @@ async function createNeuralMcpServer(deps) {
1868
1995
  };
1869
1996
  }
1870
1997
  if (resolvedKey === currentScopeKey) {
1871
- logger3.warn("neural_send: self-send refused", { agentId: deps.agentId, scope: currentScopeKey });
1998
+ logger4.warn("neural_send: self-send refused", { agentId: deps.agentId, scope: currentScopeKey });
1872
1999
  return {
1873
2000
  content: [{ type: "text", text: "[neural_send] \u4E0D\u80FD\u628A\u6D88\u606F\u9001\u7ED9\u81EA\u5DF1\u5F53\u524D\u6240\u5728\u7684 scope\u3002" }],
1874
2001
  isError: true
@@ -1886,7 +2013,7 @@ async function createNeuralMcpServer(deps) {
1886
2013
  groupId,
1887
2014
  targetCwd
1888
2015
  });
1889
- logger3.info("neural_send delivered", {
2016
+ logger4.info("neural_send delivered", {
1890
2017
  agentId: deps.agentId,
1891
2018
  fromScope: currentScopeKey,
1892
2019
  toScope: resolvedKey,
@@ -1896,7 +2023,7 @@ async function createNeuralMcpServer(deps) {
1896
2023
  content: [{ type: "text", text: `[neural_send] \u5DF2\u9001\u8FBE\u5230\u300C${toLabel}\u300D(scope: ${resolvedKey})\u3002` }]
1897
2024
  };
1898
2025
  } catch (err) {
1899
- logger3.error("neural_send dispatch failed", { agentId: deps.agentId, error: err });
2026
+ logger4.error("neural_send dispatch failed", { agentId: deps.agentId, error: err });
1900
2027
  return {
1901
2028
  content: [{ type: "text", text: `[neural_send] \u9001\u8FBE\u5931\u8D25\uFF1A${err.message}` }],
1902
2029
  isError: true
@@ -1905,21 +2032,160 @@ async function createNeuralMcpServer(deps) {
1905
2032
  },
1906
2033
  {}
1907
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);
1908
2172
  const neuralServer = sdk.createSdkMcpServer({
1909
2173
  name: "neural",
1910
2174
  version: "2.0.0",
1911
- tools: [neuralSend]
2175
+ tools
1912
2176
  });
1913
- logger3.info("Neural MCP server created", {
2177
+ const toolNames = ["neural_send", "neural_list_scopes"];
2178
+ if (selfNote) toolNames.push("self_note");
2179
+ logger4.info("Neural MCP server created", {
1914
2180
  agentId: deps.agentId,
1915
2181
  scope: currentScopeKey,
1916
- tools: ["neural_send"]
2182
+ tools: toolNames
1917
2183
  });
1918
2184
  return neuralServer;
1919
2185
  }
1920
2186
 
1921
2187
  // src/sdkEventMapper.ts
1922
- var logger4 = createModuleLogger("sdk.mapper");
2188
+ var logger5 = createModuleLogger("sdk.mapper");
1923
2189
  function getTaskBase(proc) {
1924
2190
  const task = proc.currentTask;
1925
2191
  if (!task) return null;
@@ -1989,7 +2255,7 @@ function emitGroupSegment(proc, emit, base, content, contentBlocks) {
1989
2255
  const groupId = proc.currentTask?.groupId;
1990
2256
  if (!groupId) return;
1991
2257
  proc.segmentCount += 1;
1992
- logger4.info("Group segment emitted", {
2258
+ logger5.info("Group segment emitted", {
1993
2259
  agentId: base.agentId,
1994
2260
  replyMessageId: base.replyMessageId,
1995
2261
  groupId,
@@ -2019,7 +2285,7 @@ function flushTextSegmentOnBlockStop(proc, emit, base) {
2019
2285
  emitGroupSegment(proc, emit, base, proc.segmentBuffer, proc.contentBlocks);
2020
2286
  proc.contentBlocks = [];
2021
2287
  } else {
2022
- logger4.info("Group text block flushed but skipped (no segment emitted)", {
2288
+ logger5.info("Group text block flushed but skipped (no segment emitted)", {
2023
2289
  agentId: base.agentId,
2024
2290
  replyMessageId: base.replyMessageId,
2025
2291
  groupId: proc.currentTask?.groupId,
@@ -2043,7 +2309,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2043
2309
  if (proc.status === "starting") {
2044
2310
  proc.status = "ready";
2045
2311
  }
2046
- logger4.info("Agent session initialized", {
2312
+ logger5.info("Agent session initialized", {
2047
2313
  agentId: proc.agentId,
2048
2314
  scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
2049
2315
  sessionId: initMsg.session_id,
@@ -2106,7 +2372,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2106
2372
  }
2107
2373
  } else if (delta.type === "text_delta" && typeof delta.text === "string") {
2108
2374
  if (proc.accumulatedText.length === 0) {
2109
- logger4.info("Agent text stream started", {
2375
+ logger5.info("Agent text stream started", {
2110
2376
  agentId: proc.agentId,
2111
2377
  replyMessageId: base.replyMessageId,
2112
2378
  traceId: base.traceId,
@@ -2145,7 +2411,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2145
2411
  try {
2146
2412
  parsedInput = JSON.parse(proc.accumulatedToolInput);
2147
2413
  } catch {
2148
- logger4.warn("Failed to parse tool input JSON", {
2414
+ logger5.warn("Failed to parse tool input JSON", {
2149
2415
  agentId: proc.agentId,
2150
2416
  toolName: proc.currentToolName,
2151
2417
  inputLen: proc.accumulatedToolInput.length,
@@ -2160,7 +2426,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2160
2426
  if (proc.currentToolName === "TodoWrite") {
2161
2427
  const todos = extractTodosFromInput(parsedInput);
2162
2428
  if (todos) {
2163
- logger4.info("TodoWrite detected, emitting agent:todos_update", {
2429
+ logger5.info("TodoWrite detected, emitting agent:todos_update", {
2164
2430
  agentId: proc.agentId,
2165
2431
  replyMessageId: base.replyMessageId,
2166
2432
  groupId: proc.currentTask?.groupId,
@@ -2177,7 +2443,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2177
2443
  }
2178
2444
  });
2179
2445
  } else {
2180
- logger4.info("TodoWrite detected with empty/cancel todos", {
2446
+ logger5.info("TodoWrite detected with empty/cancel todos", {
2181
2447
  agentId: proc.agentId,
2182
2448
  replyMessageId: base.replyMessageId,
2183
2449
  traceId: base.traceId
@@ -2254,7 +2520,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2254
2520
  const groupMode = groupId != null;
2255
2521
  const usage = extractUsage(successMsg);
2256
2522
  if (trimmed === NO_REPLY_TOKEN) {
2257
- logger4.info("Agent chose not to reply", {
2523
+ logger5.info("Agent chose not to reply", {
2258
2524
  agentId: proc.agentId,
2259
2525
  replyMessageId: base.replyMessageId,
2260
2526
  traceId: base.traceId,
@@ -2279,13 +2545,13 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2279
2545
  }
2280
2546
  if (groupMode) {
2281
2547
  if (usage.inputTokens && usage.inputTokens > 15e4) {
2282
- logger4.warn("Agent context window approaching limit", {
2548
+ logger5.warn("Agent context window approaching limit", {
2283
2549
  agentId: proc.agentId,
2284
2550
  inputTokens: usage.inputTokens
2285
2551
  });
2286
2552
  }
2287
2553
  if (proc.contentBlocks.length > 0) {
2288
- logger4.info("Group turn trailing audit segment", {
2554
+ logger5.info("Group turn trailing audit segment", {
2289
2555
  agentId: proc.agentId,
2290
2556
  replyMessageId: base.replyMessageId,
2291
2557
  blockCount: proc.contentBlocks.length,
@@ -2294,7 +2560,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2294
2560
  emitGroupSegment(proc, emit, base, "", proc.contentBlocks);
2295
2561
  proc.contentBlocks = [];
2296
2562
  }
2297
- logger4.info("Group task turn complete", {
2563
+ logger5.info("Group task turn complete", {
2298
2564
  agentId: proc.agentId,
2299
2565
  replyMessageId: base.replyMessageId,
2300
2566
  groupId,
@@ -2319,13 +2585,13 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2319
2585
  proc.contentBlocks.push({ type: "text", content: proc.accumulatedText });
2320
2586
  }
2321
2587
  if (usage.inputTokens && usage.inputTokens > 15e4) {
2322
- logger4.warn("Agent context window approaching limit", {
2588
+ logger5.warn("Agent context window approaching limit", {
2323
2589
  agentId: proc.agentId,
2324
2590
  inputTokens: usage.inputTokens
2325
2591
  });
2326
2592
  }
2327
2593
  carrierMessageId = createMessageId();
2328
- logger4.info("Agent task done, emitting agent:done", {
2594
+ logger5.info("Agent task done, emitting agent:done", {
2329
2595
  agentId: proc.agentId,
2330
2596
  ackId: base.replyMessageId,
2331
2597
  messageId: carrierMessageId,
@@ -2352,7 +2618,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2352
2618
  } else {
2353
2619
  const errorMsg = resultMsg;
2354
2620
  const errorText = errorMsg.errors?.join("; ") ?? `Agent error: ${resultMsg.subtype}`;
2355
- logger4.warn("Agent task error, emitting agent:error", {
2621
+ logger5.warn("Agent task error, emitting agent:error", {
2356
2622
  agentId: proc.agentId,
2357
2623
  replyMessageId: base.replyMessageId,
2358
2624
  subtype: resultMsg.subtype,
@@ -2371,7 +2637,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2371
2637
  case "assistant":
2372
2638
  break;
2373
2639
  default:
2374
- logger4.warn("Unhandled SDK message type", {
2640
+ logger5.warn("Unhandled SDK message type", {
2375
2641
  type: message.type,
2376
2642
  agentId: proc.agentId
2377
2643
  });
@@ -2391,7 +2657,7 @@ function resetAccumulators(proc) {
2391
2657
 
2392
2658
  // src/wsMetrics.ts
2393
2659
  import { monitorEventLoopDelay } from "perf_hooks";
2394
- var logger5 = createModuleLogger("ws.metrics");
2660
+ var logger6 = createModuleLogger("ws.metrics");
2395
2661
  var WsMetrics = class {
2396
2662
  recv = /* @__PURE__ */ new Map();
2397
2663
  send = /* @__PURE__ */ new Map();
@@ -2438,7 +2704,7 @@ var WsMetrics = class {
2438
2704
  const sendSum = [...this.send.values()].reduce((a, b) => a + b, 0);
2439
2705
  const sdkSum = [...this.sdkOut.values()].reduce((a, b) => a + b, 0);
2440
2706
  if (recvSum + sendSum + sdkSum === 0 && (stats.loopMaxMs ?? 0) < 50) return;
2441
- logger5.info("WS metrics", {
2707
+ logger6.info("WS metrics", {
2442
2708
  windowMs: intervalMs,
2443
2709
  ...stats,
2444
2710
  sums: { recv: recvSum, send: sendSum, sdkOut: sdkSum },
@@ -2454,7 +2720,19 @@ var WsMetrics = class {
2454
2720
  var wsMetrics = new WsMetrics();
2455
2721
 
2456
2722
  // src/agentManager.ts
2457
- var logger6 = createModuleLogger("agent.manager");
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.`;
2458
2736
  var BridgeBusyError = class extends Error {
2459
2737
  constructor(message = "Bridge busy: cannot evict an idle Agent query; all slots are working") {
2460
2738
  super(message);
@@ -2468,6 +2746,7 @@ var AgentManager = class {
2468
2746
  emit;
2469
2747
  workspacesDir;
2470
2748
  claudeConfigDir;
2749
+ memoryStore;
2471
2750
  queryConfig;
2472
2751
  askQuestionRegistry;
2473
2752
  groupRegistry;
@@ -2479,18 +2758,20 @@ var AgentManager = class {
2479
2758
  this.emit = emit;
2480
2759
  if (typeof options === "function") {
2481
2760
  this.queryFn = options;
2482
- this.workspacesDir = path6.join(os4.homedir(), ".ahchat", "workspaces");
2483
- this.claudeConfigDir = path6.join(os4.homedir(), ".ahchat", "claude-config");
2761
+ this.workspacesDir = path7.join(os4.homedir(), ".ahchat", "workspaces");
2762
+ this.claudeConfigDir = path7.join(os4.homedir(), ".ahchat", "claude-config");
2484
2763
  this.queryConfig = DEFAULT_QUERY_CONFIG;
2485
2764
  this.askQuestionRegistry = new AskQuestionRegistry();
2486
2765
  this.groupRegistry = null;
2766
+ this.memoryStore = null;
2487
2767
  } else {
2488
2768
  this.queryFn = options?.queryFn ?? null;
2489
- this.workspacesDir = options?.workspacesDir ?? path6.join(os4.homedir(), ".ahchat", "workspaces");
2490
- this.claudeConfigDir = options?.claudeConfigDir ?? path6.join(os4.homedir(), ".ahchat", "claude-config");
2769
+ this.workspacesDir = options?.workspacesDir ?? path7.join(os4.homedir(), ".ahchat", "workspaces");
2770
+ this.claudeConfigDir = options?.claudeConfigDir ?? path7.join(os4.homedir(), ".ahchat", "claude-config");
2491
2771
  this.queryConfig = options?.queryConfig ?? DEFAULT_QUERY_CONFIG;
2492
2772
  this.askQuestionRegistry = options?.askQuestionRegistry ?? new AskQuestionRegistry();
2493
2773
  this.groupRegistry = options?.groupRegistry ?? null;
2774
+ this.memoryStore = options?.memoryStore ?? null;
2494
2775
  }
2495
2776
  this.evictionTimer = setInterval(() => {
2496
2777
  void this.evictIdle();
@@ -2523,7 +2804,7 @@ var AgentManager = class {
2523
2804
  })
2524
2805
  ]);
2525
2806
  } catch (e) {
2526
- logger6.warn("awaitQueryReturn finished with error/timeout", { agentId, error: e });
2807
+ logger7.warn("awaitQueryReturn finished with error/timeout", { agentId, error: e });
2527
2808
  }
2528
2809
  }
2529
2810
  /**
@@ -2546,13 +2827,13 @@ var AgentManager = class {
2546
2827
  const proc = this.agents.get(key);
2547
2828
  if (!proc || proc.status === "dead") return;
2548
2829
  if (!this.isEvictable(proc)) return;
2549
- logger6.info("Evicting idle Agent query", { agentId: proc.agentId, scope: scopeKey(proc.scope) });
2830
+ logger7.info("Evicting idle Agent query", { agentId: proc.agentId, scope: scopeKey(proc.scope) });
2550
2831
  const runtime = this.asRuntime(proc);
2551
2832
  try {
2552
2833
  runtime.inputController.close();
2553
2834
  await this.awaitQueryReturn(runtime.query, 5e3, proc.agentId);
2554
2835
  } catch (e) {
2555
- logger6.error("closeIdleQuery failed", { agentId: proc.agentId, error: e });
2836
+ logger7.error("closeIdleQuery failed", { agentId: proc.agentId, error: e });
2556
2837
  }
2557
2838
  proc.status = "dead";
2558
2839
  this.agents.delete(key);
@@ -2618,9 +2899,9 @@ var AgentManager = class {
2618
2899
  const savedSessionId = this.sessionStore.get(agentConfig.id, scope);
2619
2900
  const inputController = new InputController();
2620
2901
  const agentCwd = cwd;
2621
- await fs2.mkdir(agentCwd, { recursive: true });
2902
+ await fs3.mkdir(agentCwd, { recursive: true });
2622
2903
  const cfg = parseAgentConfig(agentConfig.config);
2623
- logger6.info("Creating Agent query", {
2904
+ logger7.info("Creating Agent query", {
2624
2905
  agentId: agentConfig.id,
2625
2906
  scope: scopeKey(scope),
2626
2907
  cwd: agentCwd,
@@ -2631,7 +2912,7 @@ var AgentManager = class {
2631
2912
  const queryFn = await this.getQueryFn();
2632
2913
  let procRef = null;
2633
2914
  const cwdGuard = makeCwdPermissionGuard(agentCwd, agentConfig.id, scope, (msg, meta) => {
2634
- logger6.warn(msg, meta);
2915
+ logger7.warn(msg, meta);
2635
2916
  });
2636
2917
  const askGuard = makeAskUserQuestionGuard({
2637
2918
  agentId: agentConfig.id,
@@ -2644,14 +2925,17 @@ var AgentManager = class {
2644
2925
  agentId: agentConfig.id,
2645
2926
  scope,
2646
2927
  groupRegistry: this.groupRegistry,
2647
- onSend: (payload) => this.deliverNeuralSend(agentConfig, payload)
2928
+ onSend: (payload) => this.deliverNeuralSend(agentConfig, payload),
2929
+ memoryStore: this.memoryStore
2648
2930
  });
2931
+ const notebookSection = this.buildNotebookSection(agentConfig.id);
2932
+ const scopesSection = this.buildScopesSection(agentConfig.id, scope);
2649
2933
  const options = {
2650
2934
  cwd: agentCwd,
2651
2935
  systemPrompt: {
2652
2936
  type: "preset",
2653
2937
  preset: "claude_code",
2654
- append: [PLATFORM_AGENT_RULES, agentConfig.systemPrompt].filter((s) => typeof s === "string" && s.trim().length > 0).join("\n\n")
2938
+ append: [PLATFORM_AGENT_RULES, agentConfig.systemPrompt, notebookSection, scopesSection].filter((s) => typeof s === "string" && s.trim().length > 0).join("\n\n")
2655
2939
  },
2656
2940
  permissionMode: "bypassPermissions",
2657
2941
  allowDangerouslySkipPermissions: true,
@@ -2663,7 +2947,9 @@ var AgentManager = class {
2663
2947
  "Glob",
2664
2948
  "Grep",
2665
2949
  "AskUserQuestion",
2666
- "mcp__neural__neural_send"
2950
+ "mcp__neural__neural_send",
2951
+ "mcp__neural__neural_list_scopes",
2952
+ "mcp__neural__self_note"
2667
2953
  ],
2668
2954
  mcpServers: { neural: neuralServer },
2669
2955
  includePartialMessages: true,
@@ -2677,12 +2963,14 @@ var AgentManager = class {
2677
2963
  };
2678
2964
  const userPromptTrimmed = (agentConfig.systemPrompt ?? "").trim();
2679
2965
  const appendStr = options.systemPrompt.append;
2680
- logger6.info("Platform rules attached", {
2966
+ logger7.info("Platform rules attached", {
2681
2967
  agentId: agentConfig.id,
2682
2968
  scope: scopeKey(scope),
2683
2969
  platformRulesLen: PLATFORM_AGENT_RULES.length,
2684
2970
  userPromptLen: userPromptTrimmed.length,
2685
2971
  hasUserPrompt: userPromptTrimmed.length > 0,
2972
+ notebookLen: notebookSection.length,
2973
+ scopesLen: scopesSection.length,
2686
2974
  appendLen: appendStr.length
2687
2975
  });
2688
2976
  if (cfg.model) {
@@ -2724,6 +3012,58 @@ var AgentManager = class {
2724
3012
  this.consumeOutput(runtime);
2725
3013
  return proc;
2726
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
+ }
2727
3067
  async sendMessage(task) {
2728
3068
  const key = runtimeKey(task.agentId, task.scope);
2729
3069
  const proc = this.agents.get(key);
@@ -2736,7 +3076,7 @@ var AgentManager = class {
2736
3076
  return;
2737
3077
  }
2738
3078
  if (proc.status === "starting") {
2739
- logger6.info("Message dispatched to starting Agent (kickstart)", {
3079
+ logger7.info("Message dispatched to starting Agent (kickstart)", {
2740
3080
  agentId: task.agentId,
2741
3081
  replyMessageId: task.replyMessageId,
2742
3082
  traceId: task.traceId
@@ -2749,7 +3089,7 @@ var AgentManager = class {
2749
3089
  if (idx >= 0) {
2750
3090
  runtime.injectedTasks.splice(idx, 1);
2751
3091
  runtime.mergedTasks.push(task);
2752
- logger6.info("Injected task consumed by SDK (queued as merged until next result)", {
3092
+ logger7.info("Injected task consumed by SDK (queued as merged until next result)", {
2753
3093
  agentId: runtime.agentId,
2754
3094
  replyMessageId: task.replyMessageId,
2755
3095
  traceId: task.traceId,
@@ -2760,7 +3100,7 @@ var AgentManager = class {
2760
3100
  };
2761
3101
  runtime.inputController.push(task.content, runtime.ccSessionId ?? "", onYielded);
2762
3102
  runtime.injectedTasks.push(task);
2763
- logger6.info("Message injected while Agent working", {
3103
+ logger7.info("Message injected while Agent working", {
2764
3104
  agentId: task.agentId,
2765
3105
  replyMessageId: task.replyMessageId,
2766
3106
  currentStatus: proc.status,
@@ -2773,7 +3113,7 @@ var AgentManager = class {
2773
3113
  runtime.currentTask = task;
2774
3114
  runtime.currentTaskStartedAt = Date.now();
2775
3115
  runtime.inputController.push(task.content, runtime.ccSessionId ?? "");
2776
- logger6.info("Message pushed to Agent", {
3116
+ logger7.info("Message pushed to Agent", {
2777
3117
  agentId: runtime.agentId,
2778
3118
  replyMessageId: task.replyMessageId,
2779
3119
  traceId: task.traceId
@@ -2793,7 +3133,7 @@ var AgentManager = class {
2793
3133
  const completedTask = proc.currentTask;
2794
3134
  if (completedTask && runtime.mergedTasks.length > 0) {
2795
3135
  const mergedBatch = [...runtime.mergedTasks];
2796
- logger6.info("Flushing merged tasks after result", {
3136
+ logger7.info("Flushing merged tasks after result", {
2797
3137
  agentId: proc.agentId,
2798
3138
  carrierReplyMessageId: completedTask.replyMessageId,
2799
3139
  mergedCount: mergedBatch.length,
@@ -2801,7 +3141,7 @@ var AgentManager = class {
2801
3141
  traceId: completedTask.traceId
2802
3142
  });
2803
3143
  for (const merged of mergedBatch) {
2804
- logger6.info("Emitting agent:merged for task consumed in same turn", {
3144
+ logger7.info("Emitting agent:merged for task consumed in same turn", {
2805
3145
  agentId: proc.agentId,
2806
3146
  ackId: merged.replyMessageId,
2807
3147
  mergedIntoAckId: completedTask.replyMessageId,
@@ -2823,7 +3163,7 @@ var AgentManager = class {
2823
3163
  }
2824
3164
  runtime.mergedTasks = [];
2825
3165
  } else if (runtime.mergedTasks.length > 0) {
2826
- logger6.warn("mergedTasks non-empty but no currentTask; dropping", {
3166
+ logger7.warn("mergedTasks non-empty but no currentTask; dropping", {
2827
3167
  agentId: proc.agentId,
2828
3168
  mergedCount: runtime.mergedTasks.length
2829
3169
  });
@@ -2835,7 +3175,7 @@ var AgentManager = class {
2835
3175
  proc.currentTask = next;
2836
3176
  proc.status = "working";
2837
3177
  proc.currentTaskStartedAt = Date.now();
2838
- logger6.info("Promoted next injected task after result", {
3178
+ logger7.info("Promoted next injected task after result", {
2839
3179
  agentId: proc.agentId,
2840
3180
  replyMessageId: next.replyMessageId,
2841
3181
  remainingInjected: runtime.injectedTasks.length,
@@ -2879,7 +3219,7 @@ var AgentManager = class {
2879
3219
  };
2880
3220
  const key = runtimeKey(agentConfig.id, targetScope);
2881
3221
  const existingProc = this.agents.get(key);
2882
- logger6.info("Neural send dispatching", {
3222
+ logger7.info("Neural send dispatching", {
2883
3223
  agentId: agentConfig.id,
2884
3224
  fromScope: payload.fromScopeKey,
2885
3225
  toScope: payload.toScopeKey,
@@ -2892,7 +3232,7 @@ var AgentManager = class {
2892
3232
  });
2893
3233
  if (existingProc && existingProc.status !== "dead") {
2894
3234
  if (existingProc.status === "ready" || existingProc.status === "starting") {
2895
- logger6.info("Neural send dispatched to idle runtime", {
3235
+ logger7.info("Neural send dispatched to idle runtime", {
2896
3236
  agentId: agentConfig.id,
2897
3237
  toScope: payload.toScopeKey,
2898
3238
  replyMessageId: task.replyMessageId,
@@ -2903,7 +3243,7 @@ var AgentManager = class {
2903
3243
  const runtime = this.asRuntime(existingProc);
2904
3244
  runtime.inputController.push(task.content, runtime.ccSessionId ?? "");
2905
3245
  runtime.injectedTasks.push(task);
2906
- logger6.info("Neural send injected mid-turn", {
3246
+ logger7.info("Neural send injected mid-turn", {
2907
3247
  agentId: agentConfig.id,
2908
3248
  toScope: payload.toScopeKey,
2909
3249
  replyMessageId: task.replyMessageId,
@@ -2915,7 +3255,7 @@ var AgentManager = class {
2915
3255
  let cwd;
2916
3256
  if (targetScope.kind === "group") {
2917
3257
  if (!payload.targetCwd) {
2918
- logger6.error("Neural send abort: group target missing targetCwd", {
3258
+ logger7.error("Neural send abort: group target missing targetCwd", {
2919
3259
  agentId: agentConfig.id,
2920
3260
  toScope: payload.toScopeKey
2921
3261
  });
@@ -2923,10 +3263,10 @@ var AgentManager = class {
2923
3263
  }
2924
3264
  cwd = payload.targetCwd;
2925
3265
  } else {
2926
- cwd = agentConfig.workingDirectory || path6.join(this.workspacesDir, agentConfig.id);
3266
+ cwd = agentConfig.workingDirectory || path7.join(this.workspacesDir, agentConfig.id);
2927
3267
  }
2928
3268
  void this.acquire(agentConfig, targetScope, cwd).then(() => {
2929
- logger6.info("Neural send new runtime acquired", {
3269
+ logger7.info("Neural send new runtime acquired", {
2930
3270
  agentId: agentConfig.id,
2931
3271
  toScope: payload.toScopeKey,
2932
3272
  cwd,
@@ -2934,7 +3274,7 @@ var AgentManager = class {
2934
3274
  });
2935
3275
  return this.sendMessage({ ...task, agentId: agentConfig.id, scope: targetScope });
2936
3276
  }).catch((err) => {
2937
- logger6.error("Neural send acquire failed", {
3277
+ logger7.error("Neural send acquire failed", {
2938
3278
  agentId: agentConfig.id,
2939
3279
  toScope: payload.toScopeKey,
2940
3280
  cwd,
@@ -2950,7 +3290,7 @@ var AgentManager = class {
2950
3290
  (k) => k === agentId || k.startsWith(`${agentId}::`)
2951
3291
  );
2952
3292
  if (keys.length === 0) {
2953
- logger6.warn("terminate: no process for agent", { agentId });
3293
+ logger7.warn("terminate: no process for agent", { agentId });
2954
3294
  this.sessionStore.deleteAllForAgent(agentId);
2955
3295
  return;
2956
3296
  }
@@ -2961,19 +3301,19 @@ var AgentManager = class {
2961
3301
  }
2962
3302
  }
2963
3303
  this.sessionStore.deleteAllForAgent(agentId);
2964
- logger6.info("terminate: all scoped queries removed", { agentId, count: keys.length });
3304
+ logger7.info("terminate: all scoped queries removed", { agentId, count: keys.length });
2965
3305
  }
2966
3306
  /** Stop one scoped SDK runtime (workdir change). */
2967
3307
  async terminateScope(agentId, scope) {
2968
3308
  const key = runtimeKey(agentId, scope);
2969
3309
  const proc = this.agents.get(key);
2970
3310
  if (!proc) {
2971
- logger6.info("terminateScope: no active runtime", { agentId, scope: scopeKey(scope) });
3311
+ logger7.info("terminateScope: no active runtime", { agentId, scope: scopeKey(scope) });
2972
3312
  return;
2973
3313
  }
2974
3314
  await this.closeRuntime(proc, "terminateScope");
2975
3315
  this.sessionStore.delete(agentId, scope);
2976
- logger6.info("terminateScope: scoped query removed", { agentId, scope: scopeKey(scope) });
3316
+ logger7.info("terminateScope: scoped query removed", { agentId, scope: scopeKey(scope) });
2977
3317
  }
2978
3318
  async closeRuntime(proc, reason) {
2979
3319
  const key = runtimeKey(proc.agentId, proc.scope);
@@ -3003,7 +3343,7 @@ var AgentManager = class {
3003
3343
  runtime.mergedTasks = [];
3004
3344
  for (const t of mergedAtClose) {
3005
3345
  if (runtime.currentTask) {
3006
- logger6.info("Emitting agent:merged on runtime close", {
3346
+ logger7.info("Emitting agent:merged on runtime close", {
3007
3347
  agentId: runtime.agentId,
3008
3348
  replyMessageId: t.replyMessageId,
3009
3349
  mergedInto: runtime.currentTask.replyMessageId,
@@ -3030,45 +3370,57 @@ var AgentManager = class {
3030
3370
  runtime.inputController.close();
3031
3371
  await this.awaitQueryReturn(runtime.query, 5e3, agentId);
3032
3372
  } catch (e) {
3033
- logger6.error(`${reason}: close query failed`, { agentId, scope: scopeKey(proc.scope), error: e });
3373
+ logger7.error(`${reason}: close query failed`, { agentId, scope: scopeKey(proc.scope), error: e });
3034
3374
  }
3035
3375
  proc.status = "dead";
3036
3376
  this.agents.delete(key);
3037
3377
  this.lastUsedAt.delete(key);
3038
- logger6.info(`${reason}: keeping workspace dir intact (per project policy)`, {
3378
+ logger7.info(`${reason}: keeping workspace dir intact (per project policy)`, {
3039
3379
  agentId,
3040
3380
  scope: scopeKey(proc.scope),
3041
3381
  cwd: proc.cwd
3042
3382
  });
3043
3383
  }
3044
3384
  async recoverFromRestart(agents) {
3045
- logger6.info("Recovering Agent sessions after restart", { count: agents.length });
3385
+ logger7.info("Recovering Agent sessions after restart", { count: agents.length });
3046
3386
  const agentsWithSession = agents.filter((a) => {
3047
3387
  const sessionId = this.sessionStore.get(a.id, { kind: "single" });
3048
3388
  return !!sessionId;
3049
3389
  });
3050
3390
  if (agentsWithSession.length === 0) {
3051
- logger6.info("No Agent sessions to recover");
3391
+ logger7.info("No Agent sessions to recover");
3052
3392
  return;
3053
3393
  }
3054
3394
  let warmed = 0;
3055
3395
  const cap = this.queryConfig.maxActive;
3056
3396
  for (const agent of agentsWithSession) {
3057
3397
  if (warmed >= cap) {
3058
- logger6.info("Recovery warm cap reached", { cap, skipped: agentsWithSession.length - warmed });
3398
+ logger7.info("Recovery warm cap reached", { cap, skipped: agentsWithSession.length - warmed });
3059
3399
  break;
3060
3400
  }
3061
3401
  try {
3062
- const cwd = agent.workingDirectory || path6.join(this.workspacesDir, agent.id);
3402
+ let cwd = agent.workingDirectory || path7.join(this.workspacesDir, agent.id);
3403
+ if (agent.workingDirectory) {
3404
+ try {
3405
+ await fs3.mkdir(cwd, { recursive: true });
3406
+ } catch {
3407
+ cwd = path7.join(this.workspacesDir, agent.id);
3408
+ logger7.warn("Stored workingDirectory inaccessible, falling back", {
3409
+ agentId: agent.id,
3410
+ stored: agent.workingDirectory,
3411
+ fallback: cwd
3412
+ });
3413
+ }
3414
+ }
3063
3415
  await this.acquire(agent, { kind: "single" }, cwd);
3064
3416
  warmed++;
3065
- logger6.info("Agent process pre-created for recovery", { agentId: agent.id });
3417
+ logger7.info("Agent process pre-created for recovery", { agentId: agent.id });
3066
3418
  } catch (err) {
3067
3419
  if (err instanceof BridgeBusyError) {
3068
- logger6.warn("Recovery stopped: bridge busy", { agentId: agent.id });
3420
+ logger7.warn("Recovery stopped: bridge busy", { agentId: agent.id });
3069
3421
  break;
3070
3422
  }
3071
- logger6.warn("Failed to pre-create Agent for recovery, clearing session", {
3423
+ logger7.warn("Failed to pre-create Agent for recovery, clearing session", {
3072
3424
  agentId: agent.id,
3073
3425
  error: err
3074
3426
  });
@@ -3092,7 +3444,7 @@ var AgentManager = class {
3092
3444
  } catch (err) {
3093
3445
  const errMsg = err.message ?? String(err);
3094
3446
  const isResumeFail = /session|conversation.*not found/i.test(errMsg);
3095
- logger6.error("Agent query stream ended with error", {
3447
+ logger7.error("Agent query stream ended with error", {
3096
3448
  agentId: runtime.agentId,
3097
3449
  scope: scopeKey(runtime.scope),
3098
3450
  isResumeFail,
@@ -3100,7 +3452,7 @@ var AgentManager = class {
3100
3452
  error: err
3101
3453
  });
3102
3454
  this.sessionStore.delete(runtime.agentId, runtime.scope);
3103
- logger6.info("Cleared stale session after query crash", {
3455
+ logger7.info("Cleared stale session after query crash", {
3104
3456
  agentId: runtime.agentId,
3105
3457
  scope: scopeKey(runtime.scope)
3106
3458
  });
@@ -3161,8 +3513,50 @@ var AgentManager = class {
3161
3513
  }
3162
3514
  return [...ids];
3163
3515
  }
3516
+ /**
3517
+ * Push a system notice to ALL active runtimes of the given agent.
3518
+ *
3519
+ * Working runtimes: injected mid-turn via InputController (merged into
3520
+ * the current turn, no extra result expected).
3521
+ * Ready/starting runtimes: dispatched as a lightweight task so any SDK
3522
+ * output has valid replyMessageId/conversationId to land on.
3523
+ */
3524
+ broadcastScopeNotice(agentId, notice) {
3525
+ let notified = 0;
3526
+ for (const [, proc] of this.agents) {
3527
+ if (proc.agentId !== agentId || proc.status === "dead") continue;
3528
+ const runtime = this.asRuntime(proc);
3529
+ if (proc.status === "working") {
3530
+ runtime.inputController.push(notice, runtime.ccSessionId ?? "");
3531
+ logger7.info("Scope notice injected mid-turn", {
3532
+ agentId,
3533
+ scope: scopeKey(proc.scope),
3534
+ noticeLen: notice.length
3535
+ });
3536
+ } else if (proc.status === "ready" || proc.status === "starting") {
3537
+ const task = {
3538
+ content: notice,
3539
+ replyMessageId: `msg_scopenotice_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
3540
+ conversationId: proc.currentTask?.conversationId ?? "",
3541
+ traceId: `tr_scopenotice_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
3542
+ groupId: proc.scope.kind === "group" ? proc.scope.groupId : void 0
3543
+ };
3544
+ this.dispatchToSDK(runtime, task);
3545
+ logger7.info("Scope notice dispatched to idle runtime", {
3546
+ agentId,
3547
+ scope: scopeKey(proc.scope),
3548
+ replyMessageId: task.replyMessageId
3549
+ });
3550
+ }
3551
+ notified++;
3552
+ }
3553
+ logger7.info("broadcastScopeNotice completed", {
3554
+ agentId,
3555
+ notifiedCount: notified
3556
+ });
3557
+ }
3164
3558
  async shutdownAll() {
3165
- logger6.info("Shutting down all Agent processes", { count: this.agents.size });
3559
+ logger7.info("Shutting down all Agent processes", { count: this.agents.size });
3166
3560
  this.askQuestionRegistry.cancelAll("agent_aborted");
3167
3561
  if (this.evictionTimer) {
3168
3562
  clearInterval(this.evictionTimer);
@@ -3174,9 +3568,9 @@ var AgentManager = class {
3174
3568
  runtime.inputController?.close();
3175
3569
  runtime.query?.return(void 0);
3176
3570
  proc.status = "dead";
3177
- logger6.info("Agent process shut down", { agentId: proc.agentId, scope: scopeKey(proc.scope), key });
3571
+ logger7.info("Agent process shut down", { agentId: proc.agentId, scope: scopeKey(proc.scope), key });
3178
3572
  } catch (err) {
3179
- logger6.error("Error shutting down Agent", { agentId: proc.agentId, error: err });
3573
+ logger7.error("Error shutting down Agent", { agentId: proc.agentId, error: err });
3180
3574
  }
3181
3575
  }
3182
3576
  this.agents.clear();
@@ -3193,13 +3587,13 @@ var AgentManager = class {
3193
3587
  }
3194
3588
  }
3195
3589
  if (!proc) {
3196
- logger6.warn("cancelReply: no active process for reply", { agentId, replyMessageId });
3590
+ logger7.warn("cancelReply: no active process for reply", { agentId, replyMessageId });
3197
3591
  return;
3198
3592
  }
3199
3593
  const runtime = this.asRuntime(proc);
3200
3594
  const key = runtimeKey(agentId, proc.scope);
3201
3595
  if (!runtime.currentTask || runtime.currentTask.replyMessageId !== replyMessageId) {
3202
- logger6.warn("cancelReply: replyMessageId mismatch", {
3596
+ logger7.warn("cancelReply: replyMessageId mismatch", {
3203
3597
  agentId,
3204
3598
  replyMessageId,
3205
3599
  expected: runtime.currentTask?.replyMessageId
@@ -3228,7 +3622,7 @@ var AgentManager = class {
3228
3622
  proc.status = "dead";
3229
3623
  this.agents.delete(key);
3230
3624
  this.lastUsedAt.delete(key);
3231
- logger6.info("cancelReply: process torn down", {
3625
+ logger7.info("cancelReply: process torn down", {
3232
3626
  agentId,
3233
3627
  scope: scopeKey(proc.scope),
3234
3628
  conversationId,
@@ -3237,10 +3631,10 @@ var AgentManager = class {
3237
3631
  try {
3238
3632
  runtime.inputController.close();
3239
3633
  } catch (err) {
3240
- logger6.error("cancelReply: inputController.close failed", { agentId, error: err });
3634
+ logger7.error("cancelReply: inputController.close failed", { agentId, error: err });
3241
3635
  }
3242
3636
  runtime.query.return(void 0).catch((err) => {
3243
- logger6.warn("cancelReply: query.return threw", { agentId, error: err });
3637
+ logger7.warn("cancelReply: query.return threw", { agentId, error: err });
3244
3638
  });
3245
3639
  }
3246
3640
  };
@@ -3257,7 +3651,7 @@ function buildInnerVoiceEnvelope(payload) {
3257
3651
  }
3258
3652
 
3259
3653
  // src/agentRegistry.ts
3260
- var logger7 = createModuleLogger("agent.registry");
3654
+ var logger8 = createModuleLogger("agent.registry");
3261
3655
  var HttpAgentRegistry = class {
3262
3656
  constructor(serverApiUrl) {
3263
3657
  this.serverApiUrl = serverApiUrl;
@@ -3266,8 +3660,8 @@ var HttpAgentRegistry = class {
3266
3660
  agents = /* @__PURE__ */ new Map();
3267
3661
  apiUrl(suffix) {
3268
3662
  const base = this.serverApiUrl.replace(/\/$/, "");
3269
- const path10 = suffix.startsWith("/") ? suffix : `/${suffix}`;
3270
- return `${base}${path10}`;
3663
+ const path12 = suffix.startsWith("/") ? suffix : `/${suffix}`;
3664
+ return `${base}${path12}`;
3271
3665
  }
3272
3666
  async refresh() {
3273
3667
  const attempt = async () => {
@@ -3285,17 +3679,17 @@ var HttpAgentRegistry = class {
3285
3679
  recoveredAfterRetry = res != null;
3286
3680
  }
3287
3681
  if (!res) {
3288
- logger7.warn("Agent registry refresh unreachable after retry, keeping cache");
3682
+ logger8.warn("Agent registry refresh unreachable after retry, keeping cache");
3289
3683
  return;
3290
3684
  }
3291
3685
  if (!res.ok) {
3292
- logger7.warn("Agent registry refresh failed", { status: res.status });
3686
+ logger8.warn("Agent registry refresh failed", { status: res.status });
3293
3687
  return;
3294
3688
  }
3295
3689
  try {
3296
3690
  const body = await res.json();
3297
3691
  if (!Array.isArray(body)) {
3298
- logger7.warn("Agent registry refresh: expected array");
3692
+ logger8.warn("Agent registry refresh: expected array");
3299
3693
  return;
3300
3694
  }
3301
3695
  this.agents.clear();
@@ -3305,9 +3699,9 @@ var HttpAgentRegistry = class {
3305
3699
  this.agents.set(a.id, a);
3306
3700
  }
3307
3701
  }
3308
- logger7.info("Agent registry refreshed", { count: this.agents.size, recoveredAfterRetry });
3702
+ logger8.info("Agent registry refreshed", { count: this.agents.size, recoveredAfterRetry });
3309
3703
  } catch (e) {
3310
- logger7.warn("Agent registry refresh parse failed", { error: e });
3704
+ logger8.warn("Agent registry refresh parse failed", { error: e });
3311
3705
  }
3312
3706
  }
3313
3707
  getById(id) {
@@ -3321,17 +3715,17 @@ var HttpAgentRegistry = class {
3321
3715
  try {
3322
3716
  const res = await fetch(this.apiUrl(`/api/agents/${encodeURIComponent(id)}`));
3323
3717
  if (!res.ok) {
3324
- logger7.warn("fetchById failed", { agentId: id, status: res.status });
3718
+ logger8.warn("fetchById failed", { agentId: id, status: res.status });
3325
3719
  return null;
3326
3720
  }
3327
3721
  const agent = await res.json();
3328
3722
  if (agent && typeof agent.id === "string") {
3329
3723
  this.agents.set(agent.id, agent);
3330
- logger7.info("Agent registry fetchById upserted", { agentId: id });
3724
+ logger8.info("Agent registry fetchById upserted", { agentId: id });
3331
3725
  }
3332
3726
  return agent;
3333
3727
  } catch (e) {
3334
- logger7.warn("fetchById unreachable", { agentId: id, error: e });
3728
+ logger8.warn("fetchById unreachable", { agentId: id, error: e });
3335
3729
  return null;
3336
3730
  }
3337
3731
  }
@@ -3340,16 +3734,16 @@ var HttpAgentRegistry = class {
3340
3734
  }
3341
3735
  upsert(agent) {
3342
3736
  this.agents.set(agent.id, agent);
3343
- logger7.debug("Agent registry upsert", { agentId: agent.id });
3737
+ logger8.debug("Agent registry upsert", { agentId: agent.id });
3344
3738
  }
3345
3739
  remove(agentId) {
3346
3740
  this.agents.delete(agentId);
3347
- logger7.debug("Agent registry remove", { agentId });
3741
+ logger8.debug("Agent registry remove", { agentId });
3348
3742
  }
3349
3743
  };
3350
3744
 
3351
3745
  // src/groupRegistry.ts
3352
- var logger8 = createModuleLogger("neural.groupRegistry");
3746
+ var logger9 = createModuleLogger("neural.groupRegistry");
3353
3747
  var GroupRegistry = class {
3354
3748
  groups = /* @__PURE__ */ new Map();
3355
3749
  serverApiUrl;
@@ -3372,17 +3766,17 @@ var GroupRegistry = class {
3372
3766
  recoveredAfterRetry = res != null;
3373
3767
  }
3374
3768
  if (!res) {
3375
- logger8.warn("GroupRegistry refresh unreachable after retry");
3769
+ logger9.warn("GroupRegistry refresh unreachable after retry");
3376
3770
  return;
3377
3771
  }
3378
3772
  if (!res.ok) {
3379
- logger8.warn("GroupRegistry refresh failed", { status: res.status });
3773
+ logger9.warn("GroupRegistry refresh failed", { status: res.status });
3380
3774
  return;
3381
3775
  }
3382
3776
  try {
3383
3777
  const body = await res.json();
3384
3778
  if (!Array.isArray(body)) {
3385
- logger8.warn("GroupRegistry refresh: expected array");
3779
+ logger9.warn("GroupRegistry refresh: expected array");
3386
3780
  return;
3387
3781
  }
3388
3782
  this.groups.clear();
@@ -3397,14 +3791,25 @@ var GroupRegistry = class {
3397
3791
  });
3398
3792
  }
3399
3793
  }
3400
- logger8.info("GroupRegistry refreshed", { count: this.groups.size, recoveredAfterRetry });
3794
+ logger9.info("GroupRegistry refreshed", { count: this.groups.size, recoveredAfterRetry });
3401
3795
  } catch (e) {
3402
- logger8.warn("GroupRegistry refresh parse failed", { error: e });
3796
+ logger9.warn("GroupRegistry refresh parse failed", { error: e });
3403
3797
  }
3404
3798
  }
3405
3799
  getById(groupId) {
3406
3800
  return this.groups.get(groupId) ?? null;
3407
3801
  }
3802
+ /**
3803
+ * Return the cached groups that the given agent is a member of.
3804
+ * Does NOT refresh — caller decides when to call refresh().
3805
+ */
3806
+ getMyGroups(agentId) {
3807
+ const out = [];
3808
+ for (const g of this.groups.values()) {
3809
+ if (g.members.includes(agentId)) out.push(g);
3810
+ }
3811
+ return out;
3812
+ }
3408
3813
  /**
3409
3814
  * Fuzzy match by name (case-insensitive substring).
3410
3815
  * Returns the first match.
@@ -3438,16 +3843,16 @@ var GroupRegistry = class {
3438
3843
  try {
3439
3844
  const res = await fetch(url);
3440
3845
  if (res.status === 404) {
3441
- logger8.info("GroupRegistry resolveScope: group not found", { rawScope, suffix });
3846
+ logger9.info("GroupRegistry resolveScope: group not found", { rawScope, suffix });
3442
3847
  return null;
3443
3848
  }
3444
3849
  if (!res.ok) {
3445
- logger8.warn("GroupRegistry resolveScope: HTTP error", { rawScope, status: res.status });
3850
+ logger9.warn("GroupRegistry resolveScope: HTTP error", { rawScope, status: res.status });
3446
3851
  return null;
3447
3852
  }
3448
3853
  const data = await res.json();
3449
3854
  if (!data.groupId || !data.conversationId || !data.workingDirectory) {
3450
- logger8.warn("GroupRegistry resolveScope: incomplete response", {
3855
+ logger9.warn("GroupRegistry resolveScope: incomplete response", {
3451
3856
  rawScope,
3452
3857
  hasGroupId: !!data.groupId,
3453
3858
  hasConversationId: !!data.conversationId,
@@ -3455,7 +3860,7 @@ var GroupRegistry = class {
3455
3860
  });
3456
3861
  return null;
3457
3862
  }
3458
- logger8.info("GroupRegistry resolved scope", {
3863
+ logger9.info("GroupRegistry resolved scope", {
3459
3864
  rawScope,
3460
3865
  resolvedGroupId: data.groupId,
3461
3866
  resolvedName: data.name ?? "(none)",
@@ -3470,7 +3875,7 @@ var GroupRegistry = class {
3470
3875
  workingDirectory: data.workingDirectory
3471
3876
  };
3472
3877
  } catch (e) {
3473
- logger8.error("GroupRegistry resolveScope error", { rawScope, error: e });
3878
+ logger9.error("GroupRegistry resolveScope error", { rawScope, error: e });
3474
3879
  return null;
3475
3880
  }
3476
3881
  }
@@ -3491,12 +3896,12 @@ var GroupRegistry = class {
3491
3896
  if (Array.isArray(body)) {
3492
3897
  const single = body.find((c) => c.type === "single" && typeof c.id === "string");
3493
3898
  if (single?.id) {
3494
- logger8.info("GroupRegistry resolved single conv", { agentId, conversationId: single.id });
3899
+ logger9.info("GroupRegistry resolved single conv", { agentId, conversationId: single.id });
3495
3900
  return single.id;
3496
3901
  }
3497
3902
  }
3498
3903
  } else {
3499
- logger8.warn("GroupRegistry resolveSingle: list failed", { agentId, status: res.status });
3904
+ logger9.warn("GroupRegistry resolveSingle: list failed", { agentId, status: res.status });
3500
3905
  }
3501
3906
  const created = await fetch(`${this.serverApiUrl}/api/conversations`, {
3502
3907
  method: "POST",
@@ -3504,26 +3909,27 @@ var GroupRegistry = class {
3504
3909
  body: JSON.stringify({ agentId })
3505
3910
  });
3506
3911
  if (!created.ok) {
3507
- logger8.warn("GroupRegistry resolveSingle: create failed", { agentId, status: created.status });
3912
+ logger9.warn("GroupRegistry resolveSingle: create failed", { agentId, status: created.status });
3508
3913
  return null;
3509
3914
  }
3510
3915
  const conv = await created.json();
3511
3916
  if (typeof conv.id !== "string") {
3512
- logger8.warn("GroupRegistry resolveSingle: created conv missing id", { agentId });
3917
+ logger9.warn("GroupRegistry resolveSingle: created conv missing id", { agentId });
3513
3918
  return null;
3514
3919
  }
3515
- logger8.info("GroupRegistry created single conv", { agentId, conversationId: conv.id });
3920
+ logger9.info("GroupRegistry created single conv", { agentId, conversationId: conv.id });
3516
3921
  return conv.id;
3517
3922
  } catch (e) {
3518
- logger8.error("GroupRegistry resolveSingle error", { agentId, error: e });
3923
+ logger9.error("GroupRegistry resolveSingle error", { agentId, error: e });
3519
3924
  return null;
3520
3925
  }
3521
3926
  }
3522
3927
  };
3523
3928
 
3524
3929
  // src/connector.ts
3930
+ import os5 from "os";
3525
3931
  import WebSocket from "ws";
3526
- var logger9 = createModuleLogger("ws.connector");
3932
+ var logger10 = createModuleLogger("ws.connector");
3527
3933
  var ServerConnector = class {
3528
3934
  ws = null;
3529
3935
  reconnectAttempts = 0;
@@ -3553,19 +3959,19 @@ var ServerConnector = class {
3553
3959
  url.searchParams.set("token", this.config.bridgeToken);
3554
3960
  }
3555
3961
  const wsUrl = url.toString();
3556
- logger9.info("Connecting to server", { url: this.config.serverUrl });
3962
+ logger10.info("Connecting to server", { url: wsUrl });
3557
3963
  const ws = new WebSocket(wsUrl);
3558
3964
  ws.on("open", () => {
3559
3965
  this.ws = ws;
3560
3966
  this.reconnectAttempts = 0;
3561
- logger9.info("Connected to server", { url: this.config.serverUrl });
3967
+ logger10.info("Connected to server", { url: this.config.serverUrl });
3562
3968
  void this.handleOpen();
3563
3969
  });
3564
3970
  ws.on("message", (data) => {
3565
3971
  this.handleMessage(data);
3566
3972
  });
3567
3973
  ws.on("close", (code, reason) => {
3568
- logger9.warn("Disconnected from server", {
3974
+ logger10.warn("Disconnected from server", {
3569
3975
  code,
3570
3976
  reason: reason.toString()
3571
3977
  });
@@ -3575,15 +3981,15 @@ var ServerConnector = class {
3575
3981
  }
3576
3982
  });
3577
3983
  ws.on("error", (err) => {
3578
- logger9.error("WebSocket error", { error: err });
3984
+ logger10.error("WebSocket error", { error: err });
3579
3985
  });
3580
3986
  }
3581
3987
  async handleOpen() {
3582
3988
  try {
3583
3989
  await this.onConnected();
3584
- logger9.info("Recovery complete, sending bridge:register");
3990
+ logger10.info("Recovery complete, sending bridge:register");
3585
3991
  } catch (err) {
3586
- logger9.error("Recovery failed, registering with degraded state", { error: err });
3992
+ logger10.error("Recovery failed, registering with degraded state", { error: err });
3587
3993
  }
3588
3994
  this.register();
3589
3995
  }
@@ -3595,13 +4001,14 @@ var ServerConnector = class {
3595
4001
  payload: {
3596
4002
  bridgeId: this.config.bridgeId,
3597
4003
  agents: ids,
4004
+ hostname: os5.hostname(),
3598
4005
  queryConfig: {
3599
4006
  maxActive: qc.maxActive,
3600
4007
  idleTimeoutMs: qc.idleTimeoutMs
3601
4008
  }
3602
4009
  }
3603
4010
  });
3604
- logger9.info("Sent bridge:register", {
4011
+ logger10.info("Sent bridge:register", {
3605
4012
  bridgeId: this.config.bridgeId,
3606
4013
  agents: ids
3607
4014
  });
@@ -3612,7 +4019,7 @@ var ServerConnector = class {
3612
4019
  const raw = typeof data === "string" ? data : data.toString("utf8");
3613
4020
  msg = parseWSMessage(raw);
3614
4021
  } catch (e) {
3615
- logger9.error("Invalid WS message from server", { error: e });
4022
+ logger10.error("Invalid WS message from server", { error: e });
3616
4023
  return;
3617
4024
  }
3618
4025
  wsMetrics.incRecv(msg.type);
@@ -3623,7 +4030,7 @@ var ServerConnector = class {
3623
4030
  }
3624
4031
  case "task:dispatch": {
3625
4032
  void this.onTaskDispatch(msg.payload).catch((err) => {
3626
- logger9.error("Failed to handle task:dispatch", {
4033
+ logger10.error("Failed to handle task:dispatch", {
3627
4034
  error: err,
3628
4035
  traceId: msg.payload.traceId
3629
4036
  });
@@ -3633,19 +4040,19 @@ var ServerConnector = class {
3633
4040
  case "task:group_dispatch": {
3634
4041
  if (this.onGroupTaskDispatch) {
3635
4042
  void this.onGroupTaskDispatch(msg.payload).catch((err) => {
3636
- logger9.error("Failed to handle task:group_dispatch", {
4043
+ logger10.error("Failed to handle task:group_dispatch", {
3637
4044
  error: err,
3638
4045
  traceId: msg.payload.traceId
3639
4046
  });
3640
4047
  });
3641
4048
  } else {
3642
- logger9.warn("Received task:group_dispatch but no handler registered");
4049
+ logger10.warn("Received task:group_dispatch but no handler registered");
3643
4050
  }
3644
4051
  return;
3645
4052
  }
3646
4053
  case "user:stop_generation": {
3647
4054
  void this.onStopGeneration(msg.payload).catch((err) => {
3648
- logger9.error("Failed to handle user:stop_generation", {
4055
+ logger10.error("Failed to handle user:stop_generation", {
3649
4056
  error: err,
3650
4057
  traceId: msg.payload.traceId
3651
4058
  });
@@ -3659,16 +4066,17 @@ var ServerConnector = class {
3659
4066
  case "agent:updated":
3660
4067
  case "agent:deleted":
3661
4068
  case "group:member_changed":
4069
+ case "group:updated":
3662
4070
  case "user:answer_question": {
3663
4071
  if (this.onServerPush) {
3664
4072
  void Promise.resolve(this.onServerPush(msg)).catch((err) => {
3665
- logger9.error("onServerPush handler failed", { error: err, type: msg.type });
4073
+ logger10.error("onServerPush handler failed", { error: err, type: msg.type });
3666
4074
  });
3667
4075
  }
3668
4076
  return;
3669
4077
  }
3670
4078
  default: {
3671
- logger9.warn("Unhandled server message type", {
4079
+ logger10.warn("Unhandled server message type", {
3672
4080
  type: msg.type
3673
4081
  });
3674
4082
  }
@@ -3676,7 +4084,7 @@ var ServerConnector = class {
3676
4084
  }
3677
4085
  send(msg) {
3678
4086
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
3679
- logger9.warn("Cannot send: WebSocket not open", {
4087
+ logger10.warn("Cannot send: WebSocket not open", {
3680
4088
  type: msg.type
3681
4089
  });
3682
4090
  return;
@@ -3685,14 +4093,14 @@ var ServerConnector = class {
3685
4093
  this.ws.send(JSON.stringify(msg));
3686
4094
  wsMetrics.incSend(msg.type);
3687
4095
  } catch (e) {
3688
- logger9.error("Failed to send WS message", { error: e, type: msg.type });
4096
+ logger10.error("Failed to send WS message", { error: e, type: msg.type });
3689
4097
  }
3690
4098
  }
3691
4099
  scheduleReconnect() {
3692
4100
  if (this.closing) return;
3693
4101
  const delay = this.delays[Math.min(this.reconnectAttempts, this.delays.length - 1)];
3694
4102
  this.reconnectAttempts++;
3695
- logger9.info("Scheduling reconnect", {
4103
+ logger10.info("Scheduling reconnect", {
3696
4104
  attempt: this.reconnectAttempts,
3697
4105
  delayMs: delay
3698
4106
  });
@@ -3708,11 +4116,11 @@ var ServerConnector = class {
3708
4116
  try {
3709
4117
  this.ws.close(1e3, "Bridge shutting down");
3710
4118
  } catch (e) {
3711
- logger9.error("Error closing WebSocket", { error: e });
4119
+ logger10.error("Error closing WebSocket", { error: e });
3712
4120
  }
3713
4121
  this.ws = null;
3714
4122
  }
3715
- logger9.info("Connector closed");
4123
+ logger10.info("Connector closed");
3716
4124
  }
3717
4125
  get isConnected() {
3718
4126
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
@@ -3720,14 +4128,14 @@ var ServerConnector = class {
3720
4128
  };
3721
4129
 
3722
4130
  // src/modelQuerier.ts
3723
- import fs3 from "fs/promises";
3724
- import os5 from "os";
3725
- import path7 from "path";
3726
- var logger10 = createModuleLogger("bridge.modelQuerier");
4131
+ import fs4 from "fs/promises";
4132
+ import os6 from "os";
4133
+ import path8 from "path";
4134
+ var logger11 = createModuleLogger("bridge.modelQuerier");
3727
4135
  async function listModels(queryFn, opts = {}) {
3728
4136
  const t0 = Date.now();
3729
- const cwd = opts.cwd ?? path7.join(os5.homedir(), ".ahchat", "workspaces", "_list_models");
3730
- await fs3.mkdir(cwd, { recursive: true });
4137
+ const cwd = opts.cwd ?? path8.join(os6.homedir(), ".ahchat", "workspaces", "_list_models");
4138
+ await fs4.mkdir(cwd, { recursive: true });
3731
4139
  const fn = queryFn ?? (await import("@anthropic-ai/claude-agent-sdk")).query;
3732
4140
  const ic = new InputController();
3733
4141
  ic.push("Reply with exactly: PING", "");
@@ -3769,7 +4177,7 @@ async function listModels(queryFn, opts = {}) {
3769
4177
  displayName: m.displayName,
3770
4178
  description: m.description
3771
4179
  }));
3772
- logger10.info("listModels done", { count: models.length, ms: Date.now() - t0 });
4180
+ logger11.info("listModels done", { count: models.length, ms: Date.now() - t0 });
3773
4181
  return models;
3774
4182
  } finally {
3775
4183
  try {
@@ -3784,9 +4192,9 @@ async function listModels(queryFn, opts = {}) {
3784
4192
  }
3785
4193
 
3786
4194
  // src/lockfile.ts
3787
- import fs4 from "fs";
3788
- import path8 from "path";
3789
- var logger11 = createModuleLogger("bridge.lockfile");
4195
+ import fs5 from "fs";
4196
+ import path9 from "path";
4197
+ var logger12 = createModuleLogger("bridge.lockfile");
3790
4198
  var lockPath = null;
3791
4199
  function isProcessAlive(pid) {
3792
4200
  try {
@@ -3799,32 +4207,32 @@ function isProcessAlive(pid) {
3799
4207
  }
3800
4208
  }
3801
4209
  function acquireLock(dataDir) {
3802
- const file = path8.join(dataDir, "bridge.lock");
4210
+ const file = path9.join(dataDir, "bridge.lock");
3803
4211
  lockPath = file;
3804
- if (fs4.existsSync(file)) {
3805
- const raw = fs4.readFileSync(file, "utf-8").trim();
4212
+ if (fs5.existsSync(file)) {
4213
+ const raw = fs5.readFileSync(file, "utf-8").trim();
3806
4214
  const pid = Number.parseInt(raw, 10);
3807
4215
  if (Number.isFinite(pid) && pid > 0) {
3808
4216
  if (isProcessAlive(pid)) {
3809
4217
  throw new Error(`Bridge already running (PID: ${pid})`);
3810
4218
  }
3811
- logger11.warn("Removing stale bridge.lock (process not found)", { pid, path: file });
4219
+ logger12.warn("Removing stale bridge.lock (process not found)", { pid, path: file });
3812
4220
  }
3813
4221
  }
3814
- fs4.mkdirSync(path8.dirname(file), { recursive: true });
3815
- fs4.writeFileSync(file, String(process.pid), "utf-8");
3816
- logger11.info("Acquired bridge lock", { path: file, pid: process.pid });
4222
+ fs5.mkdirSync(path9.dirname(file), { recursive: true });
4223
+ fs5.writeFileSync(file, String(process.pid), "utf-8");
4224
+ logger12.info("Acquired bridge lock", { path: file, pid: process.pid });
3817
4225
  const release = () => {
3818
4226
  try {
3819
- if (lockPath && fs4.existsSync(lockPath)) {
3820
- const current = fs4.readFileSync(lockPath, "utf-8").trim();
4227
+ if (lockPath && fs5.existsSync(lockPath)) {
4228
+ const current = fs5.readFileSync(lockPath, "utf-8").trim();
3821
4229
  if (current === String(process.pid)) {
3822
- fs4.unlinkSync(lockPath);
3823
- logger11.info("Released bridge lock", { path: lockPath });
4230
+ fs5.unlinkSync(lockPath);
4231
+ logger12.info("Released bridge lock", { path: lockPath });
3824
4232
  }
3825
4233
  }
3826
4234
  } catch (e) {
3827
- logger11.error("Failed to release bridge lock", { error: e, path: lockPath });
4235
+ logger12.error("Failed to release bridge lock", { error: e, path: lockPath });
3828
4236
  } finally {
3829
4237
  lockPath = null;
3830
4238
  }
@@ -3927,9 +4335,9 @@ function buildGroupPrompt(payload) {
3927
4335
  }
3928
4336
 
3929
4337
  // src/messageHandler.ts
3930
- var logger12 = createModuleLogger("msg.handler");
4338
+ var logger13 = createModuleLogger("msg.handler");
3931
4339
  function emitTaskAck(emit, ackId, agentId, traceId) {
3932
- logger12.info("Emitting task:ack", { ackId, agentId, traceId });
4340
+ logger13.info("Emitting task:ack", { ackId, agentId, traceId });
3933
4341
  emit({
3934
4342
  type: "task:ack",
3935
4343
  payload: {
@@ -3942,7 +4350,7 @@ function emitTaskAck(emit, ackId, agentId, traceId) {
3942
4350
  }
3943
4351
  function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
3944
4352
  return async (payload) => {
3945
- logger12.info("Handling task:dispatch", {
4353
+ logger13.info("Handling task:dispatch", {
3946
4354
  agentId: payload.agentId,
3947
4355
  messageId: payload.messageId,
3948
4356
  ackId: payload.ackId,
@@ -3951,14 +4359,14 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
3951
4359
  emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
3952
4360
  let agentConfig = agentRegistry.getById(payload.agentId);
3953
4361
  if (!agentConfig) {
3954
- logger12.warn("Agent not in registry, attempting live fetch", {
4362
+ logger13.warn("Agent not in registry, attempting live fetch", {
3955
4363
  agentId: payload.agentId,
3956
4364
  traceId: payload.traceId
3957
4365
  });
3958
4366
  agentConfig = await agentRegistry.fetchById(payload.agentId);
3959
4367
  }
3960
4368
  if (!agentConfig) {
3961
- logger12.error("Agent not found for task:dispatch (after live fetch)", {
4369
+ logger13.error("Agent not found for task:dispatch (after live fetch)", {
3962
4370
  agentId: payload.agentId,
3963
4371
  traceId: payload.traceId
3964
4372
  });
@@ -3985,7 +4393,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
3985
4393
  traceId: payload.traceId
3986
4394
  });
3987
4395
  } catch (err) {
3988
- logger12.error("Failed to dispatch message to Agent", {
4396
+ logger13.error("Failed to dispatch message to Agent", {
3989
4397
  error: err,
3990
4398
  agentId: payload.agentId,
3991
4399
  traceId: payload.traceId
@@ -4005,7 +4413,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
4005
4413
  }
4006
4414
  function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4007
4415
  return async (payload) => {
4008
- logger12.info("Handling task:group_dispatch", {
4416
+ logger13.info("Handling task:group_dispatch", {
4009
4417
  agentId: payload.agentId,
4010
4418
  groupId: payload.groupId,
4011
4419
  ackId: payload.ackId,
@@ -4017,14 +4425,14 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4017
4425
  emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
4018
4426
  let agentConfig = agentRegistry.getById(payload.agentId);
4019
4427
  if (!agentConfig) {
4020
- logger12.warn("Agent not in registry for group dispatch, attempting live fetch", {
4428
+ logger13.warn("Agent not in registry for group dispatch, attempting live fetch", {
4021
4429
  agentId: payload.agentId,
4022
4430
  traceId: payload.traceId
4023
4431
  });
4024
4432
  agentConfig = await agentRegistry.fetchById(payload.agentId);
4025
4433
  }
4026
4434
  if (!agentConfig) {
4027
- logger12.error("Agent not found for task:group_dispatch (after live fetch)", {
4435
+ logger13.error("Agent not found for task:group_dispatch (after live fetch)", {
4028
4436
  agentId: payload.agentId,
4029
4437
  traceId: payload.traceId
4030
4438
  });
@@ -4057,7 +4465,7 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4057
4465
  groupId: payload.groupId
4058
4466
  });
4059
4467
  } catch (err) {
4060
- logger12.error("Failed to dispatch group message to Agent", {
4468
+ logger13.error("Failed to dispatch group message to Agent", {
4061
4469
  error: err,
4062
4470
  agentId: payload.agentId,
4063
4471
  groupId: payload.groupId,
@@ -4077,15 +4485,84 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4077
4485
  };
4078
4486
  }
4079
4487
 
4488
+ // src/scopePushNotify.ts
4489
+ var logger14 = createModuleLogger("bridge");
4490
+ function buildMemberChangedScopeNotice(params) {
4491
+ const { groupId, groupLabel, action } = params;
4492
+ const verb = action === "added" ? "\u52A0\u5165" : "\u79FB\u51FA";
4493
+ return [
4494
+ `[\u7CFB\u7EDF\u901A\u77E5] \u4F60\u5DF2\u88AB${verb}\u7FA4\u300C${groupLabel}\u300D(group:${groupId})\u3002`,
4495
+ `\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`,
4496
+ `\u65E0\u9700\u56DE\u590D\u6B64\u901A\u77E5\u3002`
4497
+ ].join("\n");
4498
+ }
4499
+ function buildGroupRenamedScopeNotice(params) {
4500
+ const { groupId, newName } = params;
4501
+ return [
4502
+ `[\u7CFB\u7EDF\u901A\u77E5] \u7FA4 (group:${groupId}) \u5DF2\u66F4\u540D\u4E3A\u300C${newName}\u300D\u3002`,
4503
+ `\u82E5\u4F60\u4E4B\u524D\u7528\u65E7\u7FA4\u540D\u8C03\u8FC7 neural_send\uFF0C\u8BF7\u66F4\u65B0\u4E3A\u65B0\u540D\u79F0\u3002`,
4504
+ `\u65E0\u9700\u56DE\u590D\u6B64\u901A\u77E5\u3002`
4505
+ ].join("\n");
4506
+ }
4507
+ async function handleGroupMemberChangedPush(deps, payload) {
4508
+ const { groupId, action, agentId } = payload;
4509
+ logger14.info("group:member_changed received, refreshing GroupRegistry", {
4510
+ groupId,
4511
+ action,
4512
+ agentId
4513
+ });
4514
+ await deps.groupRegistry.refresh();
4515
+ logger14.info("GroupRegistry refreshed after member_changed", {
4516
+ groupId,
4517
+ action,
4518
+ registryGroupCount: deps.groupRegistry.getAll().length
4519
+ });
4520
+ const group = deps.groupRegistry.getById(groupId);
4521
+ const groupLabel = group?.name ?? groupId;
4522
+ const notice = buildMemberChangedScopeNotice({ groupId, groupLabel, action });
4523
+ deps.agentManager.broadcastScopeNotice(agentId, notice);
4524
+ logger14.info("Scope notice sent for member_changed", {
4525
+ agentId,
4526
+ groupId,
4527
+ groupLabel,
4528
+ action,
4529
+ noticeLen: notice.length
4530
+ });
4531
+ }
4532
+ async function handleGroupUpdatedPush(deps, payload) {
4533
+ const { groupId, name: newName, memberAgentIds } = payload;
4534
+ logger14.info("group:updated received, refreshing GroupRegistry", {
4535
+ groupId,
4536
+ newName,
4537
+ memberCount: memberAgentIds.length
4538
+ });
4539
+ await deps.groupRegistry.refresh();
4540
+ logger14.info("GroupRegistry refreshed after group:updated", {
4541
+ groupId,
4542
+ newName,
4543
+ registryGroupCount: deps.groupRegistry.getAll().length
4544
+ });
4545
+ const notice = buildGroupRenamedScopeNotice({ groupId, newName });
4546
+ for (const aid of memberAgentIds) {
4547
+ deps.agentManager.broadcastScopeNotice(aid, notice);
4548
+ }
4549
+ logger14.info("Scope notices sent for group:updated", {
4550
+ groupId,
4551
+ newName,
4552
+ memberCount: memberAgentIds.length,
4553
+ noticeLen: notice.length
4554
+ });
4555
+ }
4556
+
4080
4557
  // src/sessionStore.ts
4081
- import fs5 from "fs";
4082
- import path9 from "path";
4083
- var logger13 = createModuleLogger("session.store");
4558
+ import fs6 from "fs";
4559
+ import path10 from "path";
4560
+ var logger15 = createModuleLogger("session.store");
4084
4561
  var SessionStore = class {
4085
4562
  filePath;
4086
4563
  cache;
4087
4564
  constructor(dataDir) {
4088
- this.filePath = path9.join(dataDir, "sessions.json");
4565
+ this.filePath = path10.join(dataDir, "sessions.json");
4089
4566
  this.cache = this.loadFromDisk();
4090
4567
  }
4091
4568
  cacheKey(agentId, scope) {
@@ -4120,8 +4597,8 @@ var SessionStore = class {
4120
4597
  }
4121
4598
  loadFromDisk() {
4122
4599
  try {
4123
- if (!fs5.existsSync(this.filePath)) return {};
4124
- const raw = fs5.readFileSync(this.filePath, "utf-8");
4600
+ if (!fs6.existsSync(this.filePath)) return {};
4601
+ const raw = fs6.readFileSync(this.filePath, "utf-8");
4125
4602
  const parsed = JSON.parse(raw);
4126
4603
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
4127
4604
  const map = parsed;
@@ -4131,7 +4608,7 @@ var SessionStore = class {
4131
4608
  migrated[key] = sessionId;
4132
4609
  } else {
4133
4610
  migrated[`${key}::single`] = sessionId;
4134
- logger13.info("Migrated legacy session key to scoped key", {
4611
+ logger15.info("Migrated legacy session key to scoped key", {
4135
4612
  legacyKey: key,
4136
4613
  newKey: `${key}::single`
4137
4614
  });
@@ -4139,29 +4616,29 @@ var SessionStore = class {
4139
4616
  }
4140
4617
  return migrated;
4141
4618
  } catch (e) {
4142
- logger13.warn("Failed to load sessions file, starting fresh", { error: e, path: this.filePath });
4619
+ logger15.warn("Failed to load sessions file, starting fresh", { error: e, path: this.filePath });
4143
4620
  return {};
4144
4621
  }
4145
4622
  }
4146
4623
  saveToDisk() {
4147
4624
  try {
4148
- const dir = path9.dirname(this.filePath);
4149
- fs5.mkdirSync(dir, { recursive: true });
4150
- fs5.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), "utf-8");
4625
+ const dir = path10.dirname(this.filePath);
4626
+ fs6.mkdirSync(dir, { recursive: true });
4627
+ fs6.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), "utf-8");
4151
4628
  } catch (e) {
4152
- logger13.error("Failed to save sessions file", { error: e, path: this.filePath });
4629
+ logger15.error("Failed to save sessions file", { error: e, path: this.filePath });
4153
4630
  }
4154
4631
  }
4155
4632
  };
4156
4633
 
4157
4634
  // src/start.ts
4158
- var logger14 = createModuleLogger("bridge");
4635
+ var logger16 = createModuleLogger("bridge");
4159
4636
  async function startBridge(config) {
4160
4637
  ensureDir(config.dataDir);
4161
4638
  ensureDir(config.claudeConfigDir);
4162
4639
  process.env.CLAUDE_CONFIG_DIR = config.claudeConfigDir;
4163
4640
  acquireLock(config.dataDir);
4164
- logger14.info("Bridge starting", {
4641
+ logger16.info("Bridge starting", {
4165
4642
  bridgeId: config.bridgeId,
4166
4643
  serverUrl: config.serverUrl,
4167
4644
  serverApiUrl: config.serverApiUrl,
@@ -4169,6 +4646,9 @@ async function startBridge(config) {
4169
4646
  });
4170
4647
  wsMetrics.start(5e3);
4171
4648
  const sessionStore = new SessionStore(config.dataDir);
4649
+ const memoryRoot = path11.join(config.dataDir, "agent-memory");
4650
+ const memoryStore = new AgentMemoryStore(memoryRoot);
4651
+ logger16.info("Agent memory store initialized", { rootDir: memoryRoot });
4172
4652
  const agentRegistry = new HttpAgentRegistry(config.serverApiUrl);
4173
4653
  const groupRegistry = new GroupRegistry(config.serverApiUrl);
4174
4654
  await agentRegistry.refresh();
@@ -4182,7 +4662,8 @@ async function startBridge(config) {
4182
4662
  queryConfig: config.queryConfig,
4183
4663
  claudeConfigDir: config.claudeConfigDir,
4184
4664
  askQuestionRegistry,
4185
- groupRegistry
4665
+ groupRegistry,
4666
+ memoryStore
4186
4667
  });
4187
4668
  const taskDispatchHandler = createTaskDispatchHandler(agentManager, agentRegistry, emit);
4188
4669
  const groupTaskDispatchHandler = createGroupTaskDispatchHandler(agentManager, agentRegistry, emit);
@@ -4204,21 +4685,21 @@ async function startBridge(config) {
4204
4685
  switch (msg.type) {
4205
4686
  case "bridge:list_models_request": {
4206
4687
  const { requestId } = msg.payload;
4207
- logger14.info("list_models request received", { requestId });
4688
+ logger16.info("list_models request received", { requestId });
4208
4689
  try {
4209
4690
  const models = await listModels();
4210
4691
  connector?.send({
4211
4692
  type: "bridge:list_models_response",
4212
4693
  payload: { requestId, models }
4213
4694
  });
4214
- logger14.info("list_models response sent", { requestId, count: models.length });
4695
+ logger16.info("list_models response sent", { requestId, count: models.length });
4215
4696
  } catch (e) {
4216
4697
  const err = e instanceof Error ? e.message : String(e);
4217
4698
  connector?.send({
4218
4699
  type: "bridge:list_models_response",
4219
4700
  payload: { requestId, error: err }
4220
4701
  });
4221
- logger14.error("list_models failed", { requestId, error: e });
4702
+ logger16.error("list_models failed", { requestId, error: e });
4222
4703
  }
4223
4704
  break;
4224
4705
  }
@@ -4226,7 +4707,7 @@ async function startBridge(config) {
4226
4707
  await agentManager.terminate(msg.payload.agentId);
4227
4708
  break;
4228
4709
  case "agent:terminate_scope":
4229
- logger14.info("agent:terminate_scope received", {
4710
+ logger16.info("agent:terminate_scope received", {
4230
4711
  agentId: msg.payload.agentId,
4231
4712
  scope: msg.payload.scope
4232
4713
  });
@@ -4239,11 +4720,31 @@ async function startBridge(config) {
4239
4720
  case "agent:deleted":
4240
4721
  agentRegistry.remove(msg.payload.agentId);
4241
4722
  break;
4723
+ case "group:member_changed":
4724
+ await handleGroupMemberChangedPush(
4725
+ { groupRegistry, agentManager },
4726
+ {
4727
+ groupId: msg.payload.groupId,
4728
+ action: msg.payload.action,
4729
+ agentId: msg.payload.agentId
4730
+ }
4731
+ );
4732
+ break;
4733
+ case "group:updated":
4734
+ await handleGroupUpdatedPush(
4735
+ { groupRegistry, agentManager },
4736
+ {
4737
+ groupId: msg.payload.groupId,
4738
+ name: msg.payload.name,
4739
+ memberAgentIds: msg.payload.memberAgentIds
4740
+ }
4741
+ );
4742
+ break;
4242
4743
  case "user:answer_question": {
4243
4744
  const p = msg.payload;
4244
4745
  const answerText = formatAnswerForSDK(p);
4245
4746
  const ok = askQuestionRegistry.resolve(p.questionId, answerText);
4246
- logger14.info("user:answer_question handled", {
4747
+ logger16.info("user:answer_question handled", {
4247
4748
  questionId: p.questionId,
4248
4749
  agentId: p.agentId,
4249
4750
  resolved: ok,
@@ -4264,7 +4765,7 @@ async function startBridge(config) {
4264
4765
  });
4265
4766
  }, config.queryConfig.statusReportIntervalMs);
4266
4767
  const shutdown = async (signal) => {
4267
- logger14.info("Shutdown signal received", { signal });
4768
+ logger16.info("Shutdown signal received", { signal });
4268
4769
  if (statusInterval) {
4269
4770
  clearInterval(statusInterval);
4270
4771
  statusInterval = null;
@@ -4272,7 +4773,7 @@ async function startBridge(config) {
4272
4773
  wsMetrics.stop();
4273
4774
  connector?.close();
4274
4775
  await agentManager.shutdownAll();
4275
- logger14.info("Bridge stopped");
4776
+ logger16.info("Bridge stopped");
4276
4777
  process.exit(0);
4277
4778
  };
4278
4779
  process.on("SIGINT", () => void shutdown("SIGINT"));
@@ -4280,8 +4781,8 @@ async function startBridge(config) {
4280
4781
  }
4281
4782
 
4282
4783
  // src/index.ts
4283
- var logger15 = createModuleLogger("bridge");
4784
+ var logger17 = createModuleLogger("bridge");
4284
4785
  void startBridge(loadBridgeConfig()).catch((e) => {
4285
- logger15.error("Bridge failed to start", { error: e });
4786
+ logger17.error("Bridge failed to start", { error: e });
4286
4787
  process.exit(1);
4287
4788
  });