@fangyb/ahchat-bridge 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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,45 @@ 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
+ const cwd = agent.workingDirectory || path7.join(this.workspacesDir, agent.id);
3063
3403
  await this.acquire(agent, { kind: "single" }, cwd);
3064
3404
  warmed++;
3065
- logger6.info("Agent process pre-created for recovery", { agentId: agent.id });
3405
+ logger7.info("Agent process pre-created for recovery", { agentId: agent.id });
3066
3406
  } catch (err) {
3067
3407
  if (err instanceof BridgeBusyError) {
3068
- logger6.warn("Recovery stopped: bridge busy", { agentId: agent.id });
3408
+ logger7.warn("Recovery stopped: bridge busy", { agentId: agent.id });
3069
3409
  break;
3070
3410
  }
3071
- logger6.warn("Failed to pre-create Agent for recovery, clearing session", {
3411
+ logger7.warn("Failed to pre-create Agent for recovery, clearing session", {
3072
3412
  agentId: agent.id,
3073
3413
  error: err
3074
3414
  });
@@ -3092,7 +3432,7 @@ var AgentManager = class {
3092
3432
  } catch (err) {
3093
3433
  const errMsg = err.message ?? String(err);
3094
3434
  const isResumeFail = /session|conversation.*not found/i.test(errMsg);
3095
- logger6.error("Agent query stream ended with error", {
3435
+ logger7.error("Agent query stream ended with error", {
3096
3436
  agentId: runtime.agentId,
3097
3437
  scope: scopeKey(runtime.scope),
3098
3438
  isResumeFail,
@@ -3100,7 +3440,7 @@ var AgentManager = class {
3100
3440
  error: err
3101
3441
  });
3102
3442
  this.sessionStore.delete(runtime.agentId, runtime.scope);
3103
- logger6.info("Cleared stale session after query crash", {
3443
+ logger7.info("Cleared stale session after query crash", {
3104
3444
  agentId: runtime.agentId,
3105
3445
  scope: scopeKey(runtime.scope)
3106
3446
  });
@@ -3161,8 +3501,50 @@ var AgentManager = class {
3161
3501
  }
3162
3502
  return [...ids];
3163
3503
  }
3504
+ /**
3505
+ * Push a system notice to ALL active runtimes of the given agent.
3506
+ *
3507
+ * Working runtimes: injected mid-turn via InputController (merged into
3508
+ * the current turn, no extra result expected).
3509
+ * Ready/starting runtimes: dispatched as a lightweight task so any SDK
3510
+ * output has valid replyMessageId/conversationId to land on.
3511
+ */
3512
+ broadcastScopeNotice(agentId, notice) {
3513
+ let notified = 0;
3514
+ for (const [, proc] of this.agents) {
3515
+ if (proc.agentId !== agentId || proc.status === "dead") continue;
3516
+ const runtime = this.asRuntime(proc);
3517
+ if (proc.status === "working") {
3518
+ runtime.inputController.push(notice, runtime.ccSessionId ?? "");
3519
+ logger7.info("Scope notice injected mid-turn", {
3520
+ agentId,
3521
+ scope: scopeKey(proc.scope),
3522
+ noticeLen: notice.length
3523
+ });
3524
+ } else if (proc.status === "ready" || proc.status === "starting") {
3525
+ const task = {
3526
+ content: notice,
3527
+ replyMessageId: `msg_scopenotice_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
3528
+ conversationId: proc.currentTask?.conversationId ?? "",
3529
+ traceId: `tr_scopenotice_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
3530
+ groupId: proc.scope.kind === "group" ? proc.scope.groupId : void 0
3531
+ };
3532
+ this.dispatchToSDK(runtime, task);
3533
+ logger7.info("Scope notice dispatched to idle runtime", {
3534
+ agentId,
3535
+ scope: scopeKey(proc.scope),
3536
+ replyMessageId: task.replyMessageId
3537
+ });
3538
+ }
3539
+ notified++;
3540
+ }
3541
+ logger7.info("broadcastScopeNotice completed", {
3542
+ agentId,
3543
+ notifiedCount: notified
3544
+ });
3545
+ }
3164
3546
  async shutdownAll() {
3165
- logger6.info("Shutting down all Agent processes", { count: this.agents.size });
3547
+ logger7.info("Shutting down all Agent processes", { count: this.agents.size });
3166
3548
  this.askQuestionRegistry.cancelAll("agent_aborted");
3167
3549
  if (this.evictionTimer) {
3168
3550
  clearInterval(this.evictionTimer);
@@ -3174,9 +3556,9 @@ var AgentManager = class {
3174
3556
  runtime.inputController?.close();
3175
3557
  runtime.query?.return(void 0);
3176
3558
  proc.status = "dead";
3177
- logger6.info("Agent process shut down", { agentId: proc.agentId, scope: scopeKey(proc.scope), key });
3559
+ logger7.info("Agent process shut down", { agentId: proc.agentId, scope: scopeKey(proc.scope), key });
3178
3560
  } catch (err) {
3179
- logger6.error("Error shutting down Agent", { agentId: proc.agentId, error: err });
3561
+ logger7.error("Error shutting down Agent", { agentId: proc.agentId, error: err });
3180
3562
  }
3181
3563
  }
3182
3564
  this.agents.clear();
@@ -3193,13 +3575,13 @@ var AgentManager = class {
3193
3575
  }
3194
3576
  }
3195
3577
  if (!proc) {
3196
- logger6.warn("cancelReply: no active process for reply", { agentId, replyMessageId });
3578
+ logger7.warn("cancelReply: no active process for reply", { agentId, replyMessageId });
3197
3579
  return;
3198
3580
  }
3199
3581
  const runtime = this.asRuntime(proc);
3200
3582
  const key = runtimeKey(agentId, proc.scope);
3201
3583
  if (!runtime.currentTask || runtime.currentTask.replyMessageId !== replyMessageId) {
3202
- logger6.warn("cancelReply: replyMessageId mismatch", {
3584
+ logger7.warn("cancelReply: replyMessageId mismatch", {
3203
3585
  agentId,
3204
3586
  replyMessageId,
3205
3587
  expected: runtime.currentTask?.replyMessageId
@@ -3228,7 +3610,7 @@ var AgentManager = class {
3228
3610
  proc.status = "dead";
3229
3611
  this.agents.delete(key);
3230
3612
  this.lastUsedAt.delete(key);
3231
- logger6.info("cancelReply: process torn down", {
3613
+ logger7.info("cancelReply: process torn down", {
3232
3614
  agentId,
3233
3615
  scope: scopeKey(proc.scope),
3234
3616
  conversationId,
@@ -3237,10 +3619,10 @@ var AgentManager = class {
3237
3619
  try {
3238
3620
  runtime.inputController.close();
3239
3621
  } catch (err) {
3240
- logger6.error("cancelReply: inputController.close failed", { agentId, error: err });
3622
+ logger7.error("cancelReply: inputController.close failed", { agentId, error: err });
3241
3623
  }
3242
3624
  runtime.query.return(void 0).catch((err) => {
3243
- logger6.warn("cancelReply: query.return threw", { agentId, error: err });
3625
+ logger7.warn("cancelReply: query.return threw", { agentId, error: err });
3244
3626
  });
3245
3627
  }
3246
3628
  };
@@ -3257,7 +3639,7 @@ function buildInnerVoiceEnvelope(payload) {
3257
3639
  }
3258
3640
 
3259
3641
  // src/agentRegistry.ts
3260
- var logger7 = createModuleLogger("agent.registry");
3642
+ var logger8 = createModuleLogger("agent.registry");
3261
3643
  var HttpAgentRegistry = class {
3262
3644
  constructor(serverApiUrl) {
3263
3645
  this.serverApiUrl = serverApiUrl;
@@ -3266,8 +3648,8 @@ var HttpAgentRegistry = class {
3266
3648
  agents = /* @__PURE__ */ new Map();
3267
3649
  apiUrl(suffix) {
3268
3650
  const base = this.serverApiUrl.replace(/\/$/, "");
3269
- const path10 = suffix.startsWith("/") ? suffix : `/${suffix}`;
3270
- return `${base}${path10}`;
3651
+ const path12 = suffix.startsWith("/") ? suffix : `/${suffix}`;
3652
+ return `${base}${path12}`;
3271
3653
  }
3272
3654
  async refresh() {
3273
3655
  const attempt = async () => {
@@ -3285,17 +3667,17 @@ var HttpAgentRegistry = class {
3285
3667
  recoveredAfterRetry = res != null;
3286
3668
  }
3287
3669
  if (!res) {
3288
- logger7.warn("Agent registry refresh unreachable after retry, keeping cache");
3670
+ logger8.warn("Agent registry refresh unreachable after retry, keeping cache");
3289
3671
  return;
3290
3672
  }
3291
3673
  if (!res.ok) {
3292
- logger7.warn("Agent registry refresh failed", { status: res.status });
3674
+ logger8.warn("Agent registry refresh failed", { status: res.status });
3293
3675
  return;
3294
3676
  }
3295
3677
  try {
3296
3678
  const body = await res.json();
3297
3679
  if (!Array.isArray(body)) {
3298
- logger7.warn("Agent registry refresh: expected array");
3680
+ logger8.warn("Agent registry refresh: expected array");
3299
3681
  return;
3300
3682
  }
3301
3683
  this.agents.clear();
@@ -3305,9 +3687,9 @@ var HttpAgentRegistry = class {
3305
3687
  this.agents.set(a.id, a);
3306
3688
  }
3307
3689
  }
3308
- logger7.info("Agent registry refreshed", { count: this.agents.size, recoveredAfterRetry });
3690
+ logger8.info("Agent registry refreshed", { count: this.agents.size, recoveredAfterRetry });
3309
3691
  } catch (e) {
3310
- logger7.warn("Agent registry refresh parse failed", { error: e });
3692
+ logger8.warn("Agent registry refresh parse failed", { error: e });
3311
3693
  }
3312
3694
  }
3313
3695
  getById(id) {
@@ -3321,17 +3703,17 @@ var HttpAgentRegistry = class {
3321
3703
  try {
3322
3704
  const res = await fetch(this.apiUrl(`/api/agents/${encodeURIComponent(id)}`));
3323
3705
  if (!res.ok) {
3324
- logger7.warn("fetchById failed", { agentId: id, status: res.status });
3706
+ logger8.warn("fetchById failed", { agentId: id, status: res.status });
3325
3707
  return null;
3326
3708
  }
3327
3709
  const agent = await res.json();
3328
3710
  if (agent && typeof agent.id === "string") {
3329
3711
  this.agents.set(agent.id, agent);
3330
- logger7.info("Agent registry fetchById upserted", { agentId: id });
3712
+ logger8.info("Agent registry fetchById upserted", { agentId: id });
3331
3713
  }
3332
3714
  return agent;
3333
3715
  } catch (e) {
3334
- logger7.warn("fetchById unreachable", { agentId: id, error: e });
3716
+ logger8.warn("fetchById unreachable", { agentId: id, error: e });
3335
3717
  return null;
3336
3718
  }
3337
3719
  }
@@ -3340,16 +3722,16 @@ var HttpAgentRegistry = class {
3340
3722
  }
3341
3723
  upsert(agent) {
3342
3724
  this.agents.set(agent.id, agent);
3343
- logger7.debug("Agent registry upsert", { agentId: agent.id });
3725
+ logger8.debug("Agent registry upsert", { agentId: agent.id });
3344
3726
  }
3345
3727
  remove(agentId) {
3346
3728
  this.agents.delete(agentId);
3347
- logger7.debug("Agent registry remove", { agentId });
3729
+ logger8.debug("Agent registry remove", { agentId });
3348
3730
  }
3349
3731
  };
3350
3732
 
3351
3733
  // src/groupRegistry.ts
3352
- var logger8 = createModuleLogger("neural.groupRegistry");
3734
+ var logger9 = createModuleLogger("neural.groupRegistry");
3353
3735
  var GroupRegistry = class {
3354
3736
  groups = /* @__PURE__ */ new Map();
3355
3737
  serverApiUrl;
@@ -3372,17 +3754,17 @@ var GroupRegistry = class {
3372
3754
  recoveredAfterRetry = res != null;
3373
3755
  }
3374
3756
  if (!res) {
3375
- logger8.warn("GroupRegistry refresh unreachable after retry");
3757
+ logger9.warn("GroupRegistry refresh unreachable after retry");
3376
3758
  return;
3377
3759
  }
3378
3760
  if (!res.ok) {
3379
- logger8.warn("GroupRegistry refresh failed", { status: res.status });
3761
+ logger9.warn("GroupRegistry refresh failed", { status: res.status });
3380
3762
  return;
3381
3763
  }
3382
3764
  try {
3383
3765
  const body = await res.json();
3384
3766
  if (!Array.isArray(body)) {
3385
- logger8.warn("GroupRegistry refresh: expected array");
3767
+ logger9.warn("GroupRegistry refresh: expected array");
3386
3768
  return;
3387
3769
  }
3388
3770
  this.groups.clear();
@@ -3397,14 +3779,25 @@ var GroupRegistry = class {
3397
3779
  });
3398
3780
  }
3399
3781
  }
3400
- logger8.info("GroupRegistry refreshed", { count: this.groups.size, recoveredAfterRetry });
3782
+ logger9.info("GroupRegistry refreshed", { count: this.groups.size, recoveredAfterRetry });
3401
3783
  } catch (e) {
3402
- logger8.warn("GroupRegistry refresh parse failed", { error: e });
3784
+ logger9.warn("GroupRegistry refresh parse failed", { error: e });
3403
3785
  }
3404
3786
  }
3405
3787
  getById(groupId) {
3406
3788
  return this.groups.get(groupId) ?? null;
3407
3789
  }
3790
+ /**
3791
+ * Return the cached groups that the given agent is a member of.
3792
+ * Does NOT refresh — caller decides when to call refresh().
3793
+ */
3794
+ getMyGroups(agentId) {
3795
+ const out = [];
3796
+ for (const g of this.groups.values()) {
3797
+ if (g.members.includes(agentId)) out.push(g);
3798
+ }
3799
+ return out;
3800
+ }
3408
3801
  /**
3409
3802
  * Fuzzy match by name (case-insensitive substring).
3410
3803
  * Returns the first match.
@@ -3438,16 +3831,16 @@ var GroupRegistry = class {
3438
3831
  try {
3439
3832
  const res = await fetch(url);
3440
3833
  if (res.status === 404) {
3441
- logger8.info("GroupRegistry resolveScope: group not found", { rawScope, suffix });
3834
+ logger9.info("GroupRegistry resolveScope: group not found", { rawScope, suffix });
3442
3835
  return null;
3443
3836
  }
3444
3837
  if (!res.ok) {
3445
- logger8.warn("GroupRegistry resolveScope: HTTP error", { rawScope, status: res.status });
3838
+ logger9.warn("GroupRegistry resolveScope: HTTP error", { rawScope, status: res.status });
3446
3839
  return null;
3447
3840
  }
3448
3841
  const data = await res.json();
3449
3842
  if (!data.groupId || !data.conversationId || !data.workingDirectory) {
3450
- logger8.warn("GroupRegistry resolveScope: incomplete response", {
3843
+ logger9.warn("GroupRegistry resolveScope: incomplete response", {
3451
3844
  rawScope,
3452
3845
  hasGroupId: !!data.groupId,
3453
3846
  hasConversationId: !!data.conversationId,
@@ -3455,7 +3848,7 @@ var GroupRegistry = class {
3455
3848
  });
3456
3849
  return null;
3457
3850
  }
3458
- logger8.info("GroupRegistry resolved scope", {
3851
+ logger9.info("GroupRegistry resolved scope", {
3459
3852
  rawScope,
3460
3853
  resolvedGroupId: data.groupId,
3461
3854
  resolvedName: data.name ?? "(none)",
@@ -3470,7 +3863,7 @@ var GroupRegistry = class {
3470
3863
  workingDirectory: data.workingDirectory
3471
3864
  };
3472
3865
  } catch (e) {
3473
- logger8.error("GroupRegistry resolveScope error", { rawScope, error: e });
3866
+ logger9.error("GroupRegistry resolveScope error", { rawScope, error: e });
3474
3867
  return null;
3475
3868
  }
3476
3869
  }
@@ -3491,12 +3884,12 @@ var GroupRegistry = class {
3491
3884
  if (Array.isArray(body)) {
3492
3885
  const single = body.find((c) => c.type === "single" && typeof c.id === "string");
3493
3886
  if (single?.id) {
3494
- logger8.info("GroupRegistry resolved single conv", { agentId, conversationId: single.id });
3887
+ logger9.info("GroupRegistry resolved single conv", { agentId, conversationId: single.id });
3495
3888
  return single.id;
3496
3889
  }
3497
3890
  }
3498
3891
  } else {
3499
- logger8.warn("GroupRegistry resolveSingle: list failed", { agentId, status: res.status });
3892
+ logger9.warn("GroupRegistry resolveSingle: list failed", { agentId, status: res.status });
3500
3893
  }
3501
3894
  const created = await fetch(`${this.serverApiUrl}/api/conversations`, {
3502
3895
  method: "POST",
@@ -3504,26 +3897,27 @@ var GroupRegistry = class {
3504
3897
  body: JSON.stringify({ agentId })
3505
3898
  });
3506
3899
  if (!created.ok) {
3507
- logger8.warn("GroupRegistry resolveSingle: create failed", { agentId, status: created.status });
3900
+ logger9.warn("GroupRegistry resolveSingle: create failed", { agentId, status: created.status });
3508
3901
  return null;
3509
3902
  }
3510
3903
  const conv = await created.json();
3511
3904
  if (typeof conv.id !== "string") {
3512
- logger8.warn("GroupRegistry resolveSingle: created conv missing id", { agentId });
3905
+ logger9.warn("GroupRegistry resolveSingle: created conv missing id", { agentId });
3513
3906
  return null;
3514
3907
  }
3515
- logger8.info("GroupRegistry created single conv", { agentId, conversationId: conv.id });
3908
+ logger9.info("GroupRegistry created single conv", { agentId, conversationId: conv.id });
3516
3909
  return conv.id;
3517
3910
  } catch (e) {
3518
- logger8.error("GroupRegistry resolveSingle error", { agentId, error: e });
3911
+ logger9.error("GroupRegistry resolveSingle error", { agentId, error: e });
3519
3912
  return null;
3520
3913
  }
3521
3914
  }
3522
3915
  };
3523
3916
 
3524
3917
  // src/connector.ts
3918
+ import os5 from "os";
3525
3919
  import WebSocket from "ws";
3526
- var logger9 = createModuleLogger("ws.connector");
3920
+ var logger10 = createModuleLogger("ws.connector");
3527
3921
  var ServerConnector = class {
3528
3922
  ws = null;
3529
3923
  reconnectAttempts = 0;
@@ -3553,19 +3947,19 @@ var ServerConnector = class {
3553
3947
  url.searchParams.set("token", this.config.bridgeToken);
3554
3948
  }
3555
3949
  const wsUrl = url.toString();
3556
- logger9.info("Connecting to server", { url: this.config.serverUrl });
3950
+ logger10.info("Connecting to server", { url: wsUrl });
3557
3951
  const ws = new WebSocket(wsUrl);
3558
3952
  ws.on("open", () => {
3559
3953
  this.ws = ws;
3560
3954
  this.reconnectAttempts = 0;
3561
- logger9.info("Connected to server", { url: this.config.serverUrl });
3955
+ logger10.info("Connected to server", { url: this.config.serverUrl });
3562
3956
  void this.handleOpen();
3563
3957
  });
3564
3958
  ws.on("message", (data) => {
3565
3959
  this.handleMessage(data);
3566
3960
  });
3567
3961
  ws.on("close", (code, reason) => {
3568
- logger9.warn("Disconnected from server", {
3962
+ logger10.warn("Disconnected from server", {
3569
3963
  code,
3570
3964
  reason: reason.toString()
3571
3965
  });
@@ -3575,15 +3969,15 @@ var ServerConnector = class {
3575
3969
  }
3576
3970
  });
3577
3971
  ws.on("error", (err) => {
3578
- logger9.error("WebSocket error", { error: err });
3972
+ logger10.error("WebSocket error", { error: err });
3579
3973
  });
3580
3974
  }
3581
3975
  async handleOpen() {
3582
3976
  try {
3583
3977
  await this.onConnected();
3584
- logger9.info("Recovery complete, sending bridge:register");
3978
+ logger10.info("Recovery complete, sending bridge:register");
3585
3979
  } catch (err) {
3586
- logger9.error("Recovery failed, registering with degraded state", { error: err });
3980
+ logger10.error("Recovery failed, registering with degraded state", { error: err });
3587
3981
  }
3588
3982
  this.register();
3589
3983
  }
@@ -3595,13 +3989,14 @@ var ServerConnector = class {
3595
3989
  payload: {
3596
3990
  bridgeId: this.config.bridgeId,
3597
3991
  agents: ids,
3992
+ hostname: os5.hostname(),
3598
3993
  queryConfig: {
3599
3994
  maxActive: qc.maxActive,
3600
3995
  idleTimeoutMs: qc.idleTimeoutMs
3601
3996
  }
3602
3997
  }
3603
3998
  });
3604
- logger9.info("Sent bridge:register", {
3999
+ logger10.info("Sent bridge:register", {
3605
4000
  bridgeId: this.config.bridgeId,
3606
4001
  agents: ids
3607
4002
  });
@@ -3612,7 +4007,7 @@ var ServerConnector = class {
3612
4007
  const raw = typeof data === "string" ? data : data.toString("utf8");
3613
4008
  msg = parseWSMessage(raw);
3614
4009
  } catch (e) {
3615
- logger9.error("Invalid WS message from server", { error: e });
4010
+ logger10.error("Invalid WS message from server", { error: e });
3616
4011
  return;
3617
4012
  }
3618
4013
  wsMetrics.incRecv(msg.type);
@@ -3623,7 +4018,7 @@ var ServerConnector = class {
3623
4018
  }
3624
4019
  case "task:dispatch": {
3625
4020
  void this.onTaskDispatch(msg.payload).catch((err) => {
3626
- logger9.error("Failed to handle task:dispatch", {
4021
+ logger10.error("Failed to handle task:dispatch", {
3627
4022
  error: err,
3628
4023
  traceId: msg.payload.traceId
3629
4024
  });
@@ -3633,19 +4028,19 @@ var ServerConnector = class {
3633
4028
  case "task:group_dispatch": {
3634
4029
  if (this.onGroupTaskDispatch) {
3635
4030
  void this.onGroupTaskDispatch(msg.payload).catch((err) => {
3636
- logger9.error("Failed to handle task:group_dispatch", {
4031
+ logger10.error("Failed to handle task:group_dispatch", {
3637
4032
  error: err,
3638
4033
  traceId: msg.payload.traceId
3639
4034
  });
3640
4035
  });
3641
4036
  } else {
3642
- logger9.warn("Received task:group_dispatch but no handler registered");
4037
+ logger10.warn("Received task:group_dispatch but no handler registered");
3643
4038
  }
3644
4039
  return;
3645
4040
  }
3646
4041
  case "user:stop_generation": {
3647
4042
  void this.onStopGeneration(msg.payload).catch((err) => {
3648
- logger9.error("Failed to handle user:stop_generation", {
4043
+ logger10.error("Failed to handle user:stop_generation", {
3649
4044
  error: err,
3650
4045
  traceId: msg.payload.traceId
3651
4046
  });
@@ -3659,16 +4054,17 @@ var ServerConnector = class {
3659
4054
  case "agent:updated":
3660
4055
  case "agent:deleted":
3661
4056
  case "group:member_changed":
4057
+ case "group:updated":
3662
4058
  case "user:answer_question": {
3663
4059
  if (this.onServerPush) {
3664
4060
  void Promise.resolve(this.onServerPush(msg)).catch((err) => {
3665
- logger9.error("onServerPush handler failed", { error: err, type: msg.type });
4061
+ logger10.error("onServerPush handler failed", { error: err, type: msg.type });
3666
4062
  });
3667
4063
  }
3668
4064
  return;
3669
4065
  }
3670
4066
  default: {
3671
- logger9.warn("Unhandled server message type", {
4067
+ logger10.warn("Unhandled server message type", {
3672
4068
  type: msg.type
3673
4069
  });
3674
4070
  }
@@ -3676,7 +4072,7 @@ var ServerConnector = class {
3676
4072
  }
3677
4073
  send(msg) {
3678
4074
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
3679
- logger9.warn("Cannot send: WebSocket not open", {
4075
+ logger10.warn("Cannot send: WebSocket not open", {
3680
4076
  type: msg.type
3681
4077
  });
3682
4078
  return;
@@ -3685,14 +4081,14 @@ var ServerConnector = class {
3685
4081
  this.ws.send(JSON.stringify(msg));
3686
4082
  wsMetrics.incSend(msg.type);
3687
4083
  } catch (e) {
3688
- logger9.error("Failed to send WS message", { error: e, type: msg.type });
4084
+ logger10.error("Failed to send WS message", { error: e, type: msg.type });
3689
4085
  }
3690
4086
  }
3691
4087
  scheduleReconnect() {
3692
4088
  if (this.closing) return;
3693
4089
  const delay = this.delays[Math.min(this.reconnectAttempts, this.delays.length - 1)];
3694
4090
  this.reconnectAttempts++;
3695
- logger9.info("Scheduling reconnect", {
4091
+ logger10.info("Scheduling reconnect", {
3696
4092
  attempt: this.reconnectAttempts,
3697
4093
  delayMs: delay
3698
4094
  });
@@ -3708,11 +4104,11 @@ var ServerConnector = class {
3708
4104
  try {
3709
4105
  this.ws.close(1e3, "Bridge shutting down");
3710
4106
  } catch (e) {
3711
- logger9.error("Error closing WebSocket", { error: e });
4107
+ logger10.error("Error closing WebSocket", { error: e });
3712
4108
  }
3713
4109
  this.ws = null;
3714
4110
  }
3715
- logger9.info("Connector closed");
4111
+ logger10.info("Connector closed");
3716
4112
  }
3717
4113
  get isConnected() {
3718
4114
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
@@ -3720,14 +4116,14 @@ var ServerConnector = class {
3720
4116
  };
3721
4117
 
3722
4118
  // 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");
4119
+ import fs4 from "fs/promises";
4120
+ import os6 from "os";
4121
+ import path8 from "path";
4122
+ var logger11 = createModuleLogger("bridge.modelQuerier");
3727
4123
  async function listModels(queryFn, opts = {}) {
3728
4124
  const t0 = Date.now();
3729
- const cwd = opts.cwd ?? path7.join(os5.homedir(), ".ahchat", "workspaces", "_list_models");
3730
- await fs3.mkdir(cwd, { recursive: true });
4125
+ const cwd = opts.cwd ?? path8.join(os6.homedir(), ".ahchat", "workspaces", "_list_models");
4126
+ await fs4.mkdir(cwd, { recursive: true });
3731
4127
  const fn = queryFn ?? (await import("@anthropic-ai/claude-agent-sdk")).query;
3732
4128
  const ic = new InputController();
3733
4129
  ic.push("Reply with exactly: PING", "");
@@ -3769,7 +4165,7 @@ async function listModels(queryFn, opts = {}) {
3769
4165
  displayName: m.displayName,
3770
4166
  description: m.description
3771
4167
  }));
3772
- logger10.info("listModels done", { count: models.length, ms: Date.now() - t0 });
4168
+ logger11.info("listModels done", { count: models.length, ms: Date.now() - t0 });
3773
4169
  return models;
3774
4170
  } finally {
3775
4171
  try {
@@ -3784,9 +4180,9 @@ async function listModels(queryFn, opts = {}) {
3784
4180
  }
3785
4181
 
3786
4182
  // src/lockfile.ts
3787
- import fs4 from "fs";
3788
- import path8 from "path";
3789
- var logger11 = createModuleLogger("bridge.lockfile");
4183
+ import fs5 from "fs";
4184
+ import path9 from "path";
4185
+ var logger12 = createModuleLogger("bridge.lockfile");
3790
4186
  var lockPath = null;
3791
4187
  function isProcessAlive(pid) {
3792
4188
  try {
@@ -3799,32 +4195,32 @@ function isProcessAlive(pid) {
3799
4195
  }
3800
4196
  }
3801
4197
  function acquireLock(dataDir) {
3802
- const file = path8.join(dataDir, "bridge.lock");
4198
+ const file = path9.join(dataDir, "bridge.lock");
3803
4199
  lockPath = file;
3804
- if (fs4.existsSync(file)) {
3805
- const raw = fs4.readFileSync(file, "utf-8").trim();
4200
+ if (fs5.existsSync(file)) {
4201
+ const raw = fs5.readFileSync(file, "utf-8").trim();
3806
4202
  const pid = Number.parseInt(raw, 10);
3807
4203
  if (Number.isFinite(pid) && pid > 0) {
3808
4204
  if (isProcessAlive(pid)) {
3809
4205
  throw new Error(`Bridge already running (PID: ${pid})`);
3810
4206
  }
3811
- logger11.warn("Removing stale bridge.lock (process not found)", { pid, path: file });
4207
+ logger12.warn("Removing stale bridge.lock (process not found)", { pid, path: file });
3812
4208
  }
3813
4209
  }
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 });
4210
+ fs5.mkdirSync(path9.dirname(file), { recursive: true });
4211
+ fs5.writeFileSync(file, String(process.pid), "utf-8");
4212
+ logger12.info("Acquired bridge lock", { path: file, pid: process.pid });
3817
4213
  const release = () => {
3818
4214
  try {
3819
- if (lockPath && fs4.existsSync(lockPath)) {
3820
- const current = fs4.readFileSync(lockPath, "utf-8").trim();
4215
+ if (lockPath && fs5.existsSync(lockPath)) {
4216
+ const current = fs5.readFileSync(lockPath, "utf-8").trim();
3821
4217
  if (current === String(process.pid)) {
3822
- fs4.unlinkSync(lockPath);
3823
- logger11.info("Released bridge lock", { path: lockPath });
4218
+ fs5.unlinkSync(lockPath);
4219
+ logger12.info("Released bridge lock", { path: lockPath });
3824
4220
  }
3825
4221
  }
3826
4222
  } catch (e) {
3827
- logger11.error("Failed to release bridge lock", { error: e, path: lockPath });
4223
+ logger12.error("Failed to release bridge lock", { error: e, path: lockPath });
3828
4224
  } finally {
3829
4225
  lockPath = null;
3830
4226
  }
@@ -3927,9 +4323,9 @@ function buildGroupPrompt(payload) {
3927
4323
  }
3928
4324
 
3929
4325
  // src/messageHandler.ts
3930
- var logger12 = createModuleLogger("msg.handler");
4326
+ var logger13 = createModuleLogger("msg.handler");
3931
4327
  function emitTaskAck(emit, ackId, agentId, traceId) {
3932
- logger12.info("Emitting task:ack", { ackId, agentId, traceId });
4328
+ logger13.info("Emitting task:ack", { ackId, agentId, traceId });
3933
4329
  emit({
3934
4330
  type: "task:ack",
3935
4331
  payload: {
@@ -3942,7 +4338,7 @@ function emitTaskAck(emit, ackId, agentId, traceId) {
3942
4338
  }
3943
4339
  function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
3944
4340
  return async (payload) => {
3945
- logger12.info("Handling task:dispatch", {
4341
+ logger13.info("Handling task:dispatch", {
3946
4342
  agentId: payload.agentId,
3947
4343
  messageId: payload.messageId,
3948
4344
  ackId: payload.ackId,
@@ -3951,14 +4347,14 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
3951
4347
  emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
3952
4348
  let agentConfig = agentRegistry.getById(payload.agentId);
3953
4349
  if (!agentConfig) {
3954
- logger12.warn("Agent not in registry, attempting live fetch", {
4350
+ logger13.warn("Agent not in registry, attempting live fetch", {
3955
4351
  agentId: payload.agentId,
3956
4352
  traceId: payload.traceId
3957
4353
  });
3958
4354
  agentConfig = await agentRegistry.fetchById(payload.agentId);
3959
4355
  }
3960
4356
  if (!agentConfig) {
3961
- logger12.error("Agent not found for task:dispatch (after live fetch)", {
4357
+ logger13.error("Agent not found for task:dispatch (after live fetch)", {
3962
4358
  agentId: payload.agentId,
3963
4359
  traceId: payload.traceId
3964
4360
  });
@@ -3985,7 +4381,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
3985
4381
  traceId: payload.traceId
3986
4382
  });
3987
4383
  } catch (err) {
3988
- logger12.error("Failed to dispatch message to Agent", {
4384
+ logger13.error("Failed to dispatch message to Agent", {
3989
4385
  error: err,
3990
4386
  agentId: payload.agentId,
3991
4387
  traceId: payload.traceId
@@ -4005,7 +4401,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
4005
4401
  }
4006
4402
  function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4007
4403
  return async (payload) => {
4008
- logger12.info("Handling task:group_dispatch", {
4404
+ logger13.info("Handling task:group_dispatch", {
4009
4405
  agentId: payload.agentId,
4010
4406
  groupId: payload.groupId,
4011
4407
  ackId: payload.ackId,
@@ -4017,14 +4413,14 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4017
4413
  emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
4018
4414
  let agentConfig = agentRegistry.getById(payload.agentId);
4019
4415
  if (!agentConfig) {
4020
- logger12.warn("Agent not in registry for group dispatch, attempting live fetch", {
4416
+ logger13.warn("Agent not in registry for group dispatch, attempting live fetch", {
4021
4417
  agentId: payload.agentId,
4022
4418
  traceId: payload.traceId
4023
4419
  });
4024
4420
  agentConfig = await agentRegistry.fetchById(payload.agentId);
4025
4421
  }
4026
4422
  if (!agentConfig) {
4027
- logger12.error("Agent not found for task:group_dispatch (after live fetch)", {
4423
+ logger13.error("Agent not found for task:group_dispatch (after live fetch)", {
4028
4424
  agentId: payload.agentId,
4029
4425
  traceId: payload.traceId
4030
4426
  });
@@ -4057,7 +4453,7 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4057
4453
  groupId: payload.groupId
4058
4454
  });
4059
4455
  } catch (err) {
4060
- logger12.error("Failed to dispatch group message to Agent", {
4456
+ logger13.error("Failed to dispatch group message to Agent", {
4061
4457
  error: err,
4062
4458
  agentId: payload.agentId,
4063
4459
  groupId: payload.groupId,
@@ -4077,15 +4473,84 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4077
4473
  };
4078
4474
  }
4079
4475
 
4476
+ // src/scopePushNotify.ts
4477
+ var logger14 = createModuleLogger("bridge");
4478
+ function buildMemberChangedScopeNotice(params) {
4479
+ const { groupId, groupLabel, action } = params;
4480
+ const verb = action === "added" ? "\u52A0\u5165" : "\u79FB\u51FA";
4481
+ return [
4482
+ `[\u7CFB\u7EDF\u901A\u77E5] \u4F60\u5DF2\u88AB${verb}\u7FA4\u300C${groupLabel}\u300D(group:${groupId})\u3002`,
4483
+ `\u4F60\u7684\u53EF\u8FBE scope \u5DF2\u53D8\u66F4\u3002\u4E0B\u6B21\u4F7F\u7528 neural_send \u524D\u8BF7\u4EE5\u6B64\u4E3A\u51C6\uFF0C\u6216\u8C03 neural_list_scopes() \u786E\u8BA4\u6700\u65B0\u5217\u8868\u3002`,
4484
+ `\u65E0\u9700\u56DE\u590D\u6B64\u901A\u77E5\u3002`
4485
+ ].join("\n");
4486
+ }
4487
+ function buildGroupRenamedScopeNotice(params) {
4488
+ const { groupId, newName } = params;
4489
+ return [
4490
+ `[\u7CFB\u7EDF\u901A\u77E5] \u7FA4 (group:${groupId}) \u5DF2\u66F4\u540D\u4E3A\u300C${newName}\u300D\u3002`,
4491
+ `\u82E5\u4F60\u4E4B\u524D\u7528\u65E7\u7FA4\u540D\u8C03\u8FC7 neural_send\uFF0C\u8BF7\u66F4\u65B0\u4E3A\u65B0\u540D\u79F0\u3002`,
4492
+ `\u65E0\u9700\u56DE\u590D\u6B64\u901A\u77E5\u3002`
4493
+ ].join("\n");
4494
+ }
4495
+ async function handleGroupMemberChangedPush(deps, payload) {
4496
+ const { groupId, action, agentId } = payload;
4497
+ logger14.info("group:member_changed received, refreshing GroupRegistry", {
4498
+ groupId,
4499
+ action,
4500
+ agentId
4501
+ });
4502
+ await deps.groupRegistry.refresh();
4503
+ logger14.info("GroupRegistry refreshed after member_changed", {
4504
+ groupId,
4505
+ action,
4506
+ registryGroupCount: deps.groupRegistry.getAll().length
4507
+ });
4508
+ const group = deps.groupRegistry.getById(groupId);
4509
+ const groupLabel = group?.name ?? groupId;
4510
+ const notice = buildMemberChangedScopeNotice({ groupId, groupLabel, action });
4511
+ deps.agentManager.broadcastScopeNotice(agentId, notice);
4512
+ logger14.info("Scope notice sent for member_changed", {
4513
+ agentId,
4514
+ groupId,
4515
+ groupLabel,
4516
+ action,
4517
+ noticeLen: notice.length
4518
+ });
4519
+ }
4520
+ async function handleGroupUpdatedPush(deps, payload) {
4521
+ const { groupId, name: newName, memberAgentIds } = payload;
4522
+ logger14.info("group:updated received, refreshing GroupRegistry", {
4523
+ groupId,
4524
+ newName,
4525
+ memberCount: memberAgentIds.length
4526
+ });
4527
+ await deps.groupRegistry.refresh();
4528
+ logger14.info("GroupRegistry refreshed after group:updated", {
4529
+ groupId,
4530
+ newName,
4531
+ registryGroupCount: deps.groupRegistry.getAll().length
4532
+ });
4533
+ const notice = buildGroupRenamedScopeNotice({ groupId, newName });
4534
+ for (const aid of memberAgentIds) {
4535
+ deps.agentManager.broadcastScopeNotice(aid, notice);
4536
+ }
4537
+ logger14.info("Scope notices sent for group:updated", {
4538
+ groupId,
4539
+ newName,
4540
+ memberCount: memberAgentIds.length,
4541
+ noticeLen: notice.length
4542
+ });
4543
+ }
4544
+
4080
4545
  // src/sessionStore.ts
4081
- import fs5 from "fs";
4082
- import path9 from "path";
4083
- var logger13 = createModuleLogger("session.store");
4546
+ import fs6 from "fs";
4547
+ import path10 from "path";
4548
+ var logger15 = createModuleLogger("session.store");
4084
4549
  var SessionStore = class {
4085
4550
  filePath;
4086
4551
  cache;
4087
4552
  constructor(dataDir) {
4088
- this.filePath = path9.join(dataDir, "sessions.json");
4553
+ this.filePath = path10.join(dataDir, "sessions.json");
4089
4554
  this.cache = this.loadFromDisk();
4090
4555
  }
4091
4556
  cacheKey(agentId, scope) {
@@ -4120,8 +4585,8 @@ var SessionStore = class {
4120
4585
  }
4121
4586
  loadFromDisk() {
4122
4587
  try {
4123
- if (!fs5.existsSync(this.filePath)) return {};
4124
- const raw = fs5.readFileSync(this.filePath, "utf-8");
4588
+ if (!fs6.existsSync(this.filePath)) return {};
4589
+ const raw = fs6.readFileSync(this.filePath, "utf-8");
4125
4590
  const parsed = JSON.parse(raw);
4126
4591
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
4127
4592
  const map = parsed;
@@ -4131,7 +4596,7 @@ var SessionStore = class {
4131
4596
  migrated[key] = sessionId;
4132
4597
  } else {
4133
4598
  migrated[`${key}::single`] = sessionId;
4134
- logger13.info("Migrated legacy session key to scoped key", {
4599
+ logger15.info("Migrated legacy session key to scoped key", {
4135
4600
  legacyKey: key,
4136
4601
  newKey: `${key}::single`
4137
4602
  });
@@ -4139,29 +4604,29 @@ var SessionStore = class {
4139
4604
  }
4140
4605
  return migrated;
4141
4606
  } catch (e) {
4142
- logger13.warn("Failed to load sessions file, starting fresh", { error: e, path: this.filePath });
4607
+ logger15.warn("Failed to load sessions file, starting fresh", { error: e, path: this.filePath });
4143
4608
  return {};
4144
4609
  }
4145
4610
  }
4146
4611
  saveToDisk() {
4147
4612
  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");
4613
+ const dir = path10.dirname(this.filePath);
4614
+ fs6.mkdirSync(dir, { recursive: true });
4615
+ fs6.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), "utf-8");
4151
4616
  } catch (e) {
4152
- logger13.error("Failed to save sessions file", { error: e, path: this.filePath });
4617
+ logger15.error("Failed to save sessions file", { error: e, path: this.filePath });
4153
4618
  }
4154
4619
  }
4155
4620
  };
4156
4621
 
4157
4622
  // src/start.ts
4158
- var logger14 = createModuleLogger("bridge");
4623
+ var logger16 = createModuleLogger("bridge");
4159
4624
  async function startBridge(config) {
4160
4625
  ensureDir(config.dataDir);
4161
4626
  ensureDir(config.claudeConfigDir);
4162
4627
  process.env.CLAUDE_CONFIG_DIR = config.claudeConfigDir;
4163
4628
  acquireLock(config.dataDir);
4164
- logger14.info("Bridge starting", {
4629
+ logger16.info("Bridge starting", {
4165
4630
  bridgeId: config.bridgeId,
4166
4631
  serverUrl: config.serverUrl,
4167
4632
  serverApiUrl: config.serverApiUrl,
@@ -4169,6 +4634,9 @@ async function startBridge(config) {
4169
4634
  });
4170
4635
  wsMetrics.start(5e3);
4171
4636
  const sessionStore = new SessionStore(config.dataDir);
4637
+ const memoryRoot = path11.join(config.dataDir, "agent-memory");
4638
+ const memoryStore = new AgentMemoryStore(memoryRoot);
4639
+ logger16.info("Agent memory store initialized", { rootDir: memoryRoot });
4172
4640
  const agentRegistry = new HttpAgentRegistry(config.serverApiUrl);
4173
4641
  const groupRegistry = new GroupRegistry(config.serverApiUrl);
4174
4642
  await agentRegistry.refresh();
@@ -4182,7 +4650,8 @@ async function startBridge(config) {
4182
4650
  queryConfig: config.queryConfig,
4183
4651
  claudeConfigDir: config.claudeConfigDir,
4184
4652
  askQuestionRegistry,
4185
- groupRegistry
4653
+ groupRegistry,
4654
+ memoryStore
4186
4655
  });
4187
4656
  const taskDispatchHandler = createTaskDispatchHandler(agentManager, agentRegistry, emit);
4188
4657
  const groupTaskDispatchHandler = createGroupTaskDispatchHandler(agentManager, agentRegistry, emit);
@@ -4204,21 +4673,21 @@ async function startBridge(config) {
4204
4673
  switch (msg.type) {
4205
4674
  case "bridge:list_models_request": {
4206
4675
  const { requestId } = msg.payload;
4207
- logger14.info("list_models request received", { requestId });
4676
+ logger16.info("list_models request received", { requestId });
4208
4677
  try {
4209
4678
  const models = await listModels();
4210
4679
  connector?.send({
4211
4680
  type: "bridge:list_models_response",
4212
4681
  payload: { requestId, models }
4213
4682
  });
4214
- logger14.info("list_models response sent", { requestId, count: models.length });
4683
+ logger16.info("list_models response sent", { requestId, count: models.length });
4215
4684
  } catch (e) {
4216
4685
  const err = e instanceof Error ? e.message : String(e);
4217
4686
  connector?.send({
4218
4687
  type: "bridge:list_models_response",
4219
4688
  payload: { requestId, error: err }
4220
4689
  });
4221
- logger14.error("list_models failed", { requestId, error: e });
4690
+ logger16.error("list_models failed", { requestId, error: e });
4222
4691
  }
4223
4692
  break;
4224
4693
  }
@@ -4226,7 +4695,7 @@ async function startBridge(config) {
4226
4695
  await agentManager.terminate(msg.payload.agentId);
4227
4696
  break;
4228
4697
  case "agent:terminate_scope":
4229
- logger14.info("agent:terminate_scope received", {
4698
+ logger16.info("agent:terminate_scope received", {
4230
4699
  agentId: msg.payload.agentId,
4231
4700
  scope: msg.payload.scope
4232
4701
  });
@@ -4239,11 +4708,31 @@ async function startBridge(config) {
4239
4708
  case "agent:deleted":
4240
4709
  agentRegistry.remove(msg.payload.agentId);
4241
4710
  break;
4711
+ case "group:member_changed":
4712
+ await handleGroupMemberChangedPush(
4713
+ { groupRegistry, agentManager },
4714
+ {
4715
+ groupId: msg.payload.groupId,
4716
+ action: msg.payload.action,
4717
+ agentId: msg.payload.agentId
4718
+ }
4719
+ );
4720
+ break;
4721
+ case "group:updated":
4722
+ await handleGroupUpdatedPush(
4723
+ { groupRegistry, agentManager },
4724
+ {
4725
+ groupId: msg.payload.groupId,
4726
+ name: msg.payload.name,
4727
+ memberAgentIds: msg.payload.memberAgentIds
4728
+ }
4729
+ );
4730
+ break;
4242
4731
  case "user:answer_question": {
4243
4732
  const p = msg.payload;
4244
4733
  const answerText = formatAnswerForSDK(p);
4245
4734
  const ok = askQuestionRegistry.resolve(p.questionId, answerText);
4246
- logger14.info("user:answer_question handled", {
4735
+ logger16.info("user:answer_question handled", {
4247
4736
  questionId: p.questionId,
4248
4737
  agentId: p.agentId,
4249
4738
  resolved: ok,
@@ -4264,7 +4753,7 @@ async function startBridge(config) {
4264
4753
  });
4265
4754
  }, config.queryConfig.statusReportIntervalMs);
4266
4755
  const shutdown = async (signal) => {
4267
- logger14.info("Shutdown signal received", { signal });
4756
+ logger16.info("Shutdown signal received", { signal });
4268
4757
  if (statusInterval) {
4269
4758
  clearInterval(statusInterval);
4270
4759
  statusInterval = null;
@@ -4272,7 +4761,7 @@ async function startBridge(config) {
4272
4761
  wsMetrics.stop();
4273
4762
  connector?.close();
4274
4763
  await agentManager.shutdownAll();
4275
- logger14.info("Bridge stopped");
4764
+ logger16.info("Bridge stopped");
4276
4765
  process.exit(0);
4277
4766
  };
4278
4767
  process.on("SIGINT", () => void shutdown("SIGINT"));
@@ -4280,8 +4769,8 @@ async function startBridge(config) {
4280
4769
  }
4281
4770
 
4282
4771
  // src/index.ts
4283
- var logger15 = createModuleLogger("bridge");
4772
+ var logger17 = createModuleLogger("bridge");
4284
4773
  void startBridge(loadBridgeConfig()).catch((e) => {
4285
- logger15.error("Bridge failed to start", { error: e });
4774
+ logger17.error("Bridge failed to start", { error: e });
4286
4775
  process.exit(1);
4287
4776
  });