@fangyb/ahchat-bridge 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -772,11 +772,11 @@ var RotatingFileStream = class extends Writable {
772
772
  timeout;
773
773
  timeoutPromise;
774
774
  constructor(generator, options) {
775
- const { encoding, history, maxFiles, maxSize, path: path11 } = options;
775
+ const { encoding, history, maxFiles, maxSize, path: path13 } = options;
776
776
  super({ decodeStrings: true, defaultEncoding: encoding });
777
777
  this.createGzip = createGzip;
778
778
  this.exec = exec;
779
- this.filename = path11 + generator(null);
779
+ this.filename = path13 + generator(null);
780
780
  this.fsCreateReadStream = createReadStream;
781
781
  this.fsCreateWriteStream = createWriteStream;
782
782
  this.fsOpen = open;
@@ -788,7 +788,7 @@ var RotatingFileStream = class extends Writable {
788
788
  this.options = options;
789
789
  this.stdout = process.stdout;
790
790
  if (maxFiles || maxSize)
791
- options.history = path11 + (history ? history : this.generator(null) + ".txt");
791
+ options.history = path13 + (history ? history : this.generator(null) + ".txt");
792
792
  this.on("close", () => this.finished ? null : this.emit("finish"));
793
793
  this.on("finish", () => this.finished = this.clear());
794
794
  (async () => {
@@ -916,9 +916,9 @@ var RotatingFileStream = class extends Writable {
916
916
  return this.move();
917
917
  }
918
918
  async findName() {
919
- const { interval, path: path11, intervalBoundary } = this.options;
919
+ const { interval, path: path13, intervalBoundary } = this.options;
920
920
  for (let index = 1; index < 1e3; ++index) {
921
- const filename = path11 + this.generator(interval && intervalBoundary ? new Date(this.prev) : this.rotation, index);
921
+ const filename = path13 + this.generator(interval && intervalBoundary ? new Date(this.prev) : this.rotation, index);
922
922
  if (!await exists(filename))
923
923
  return filename;
924
924
  }
@@ -948,11 +948,11 @@ var RotatingFileStream = class extends Writable {
948
948
  return this.unlink(filename);
949
949
  }
950
950
  async classical() {
951
- const { compress, path: path11, rotate } = this.options;
951
+ const { compress, path: path13, rotate } = this.options;
952
952
  let rotatedName = "";
953
953
  for (let count = rotate; count > 0; --count) {
954
- const currName = path11 + this.generator(count);
955
- const prevName = count === 1 ? this.filename : path11 + this.generator(count - 1);
954
+ const currName = path13 + this.generator(count);
955
+ const prevName = count === 1 ? this.filename : path13 + this.generator(count - 1);
956
956
  if (!await exists(prevName))
957
957
  continue;
958
958
  if (!rotatedName)
@@ -1388,10 +1388,82 @@ function createModuleLogger(module) {
1388
1388
  });
1389
1389
  }
1390
1390
 
1391
+ // src/start.ts
1392
+ import path11 from "path";
1393
+
1394
+ // src/agentMemoryStore.ts
1395
+ import fs2 from "fs";
1396
+ import path4 from "path";
1397
+ var logger = createModuleLogger("agent.memoryStore");
1398
+ var NOTEBOOK_FILE_NAME = "notebook.md";
1399
+ var AGENT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
1400
+ var AgentMemoryStore = class {
1401
+ rootDir;
1402
+ constructor(rootDir) {
1403
+ this.rootDir = rootDir;
1404
+ }
1405
+ read(agentId) {
1406
+ this.validateAgentId(agentId);
1407
+ const filePath = this.notebookPath(agentId);
1408
+ try {
1409
+ if (!fs2.existsSync(filePath)) {
1410
+ logger.info("Notebook read", { agentId, exists: false, bytes: 0 });
1411
+ return "";
1412
+ }
1413
+ const content = fs2.readFileSync(filePath, "utf-8");
1414
+ logger.info("Notebook read", { agentId, exists: true, bytes: content.length });
1415
+ return content;
1416
+ } catch (e) {
1417
+ logger.error("Failed to read notebook, returning empty", {
1418
+ agentId,
1419
+ path: filePath,
1420
+ error: e
1421
+ });
1422
+ return "";
1423
+ }
1424
+ }
1425
+ write(agentId, content) {
1426
+ this.validateAgentId(agentId);
1427
+ const dir = this.notebookDir(agentId);
1428
+ const filePath = this.notebookPath(agentId);
1429
+ const tmpPath = `${filePath}.tmp`;
1430
+ try {
1431
+ fs2.mkdirSync(dir, { recursive: true });
1432
+ fs2.writeFileSync(tmpPath, content, "utf-8");
1433
+ fs2.renameSync(tmpPath, filePath);
1434
+ logger.info("Notebook written", { agentId, bytes: content.length });
1435
+ } catch (e) {
1436
+ logger.error("Failed to write notebook", {
1437
+ agentId,
1438
+ path: filePath,
1439
+ error: e
1440
+ });
1441
+ throw e;
1442
+ }
1443
+ }
1444
+ append(agentId, snippet) {
1445
+ if (snippet.length === 0) return;
1446
+ const existing = this.read(agentId);
1447
+ const joiner = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
1448
+ this.write(agentId, existing + joiner + snippet);
1449
+ }
1450
+ notebookDir(agentId) {
1451
+ return path4.join(this.rootDir, agentId);
1452
+ }
1453
+ notebookPath(agentId) {
1454
+ return path4.join(this.notebookDir(agentId), NOTEBOOK_FILE_NAME);
1455
+ }
1456
+ validateAgentId(agentId) {
1457
+ if (!AGENT_ID_PATTERN.test(agentId)) {
1458
+ throw new Error(`AgentMemoryStore: unsafe agentId "${agentId}"`);
1459
+ }
1460
+ }
1461
+ };
1462
+
1391
1463
  // src/agentManager.ts
1392
- import fs2 from "fs/promises";
1464
+ import fs3 from "fs/promises";
1393
1465
  import os4 from "os";
1394
- import path6 from "path";
1466
+ import path7 from "path";
1395
1467
 
1396
1468
  // ../shared/src/constants.ts
1397
1469
  var NO_REPLY_TOKEN = "<no-reply/>";
@@ -1442,6 +1514,14 @@ with its own context, but they are all you. You have one tool to talk between th
1442
1514
  - Returns immediately with a delivery receipt. You do NOT wait for a reply.
1443
1515
  - Whether/how the other-scope self responds is its own decision.
1444
1516
 
1517
+ - neural_list_scopes(): Return the list of scopes where "you" exist (the only
1518
+ ones neural_send can reach). This list is also injected at the top of your
1519
+ system prompt at runtime start; only call this tool if you suspect it's
1520
+ stale (e.g., the user mentions a group that's not in your snapshot).
1521
+
1522
+ You are ONLY a member of the scopes shown in your "# Your scopes" section
1523
+ (injected at runtime start). neural_send to any other group will be rejected.
1524
+
1445
1525
  When YOU receive a message wrapped as "[\u5185\u5FC3\u72EC\u767D \u2014 \u6765\u81EA\u4F60\u5728\u300C<scope>\u300D\u7684\u5206\u8EAB]":
1446
1526
  - That is literally you, talking to yourself from another scope. It's private \u2014
1447
1527
  nobody else in this scope hears it. Do NOT echo or quote the envelope text.
@@ -1455,6 +1535,31 @@ Pick neural_send whenever the user asks you to "tell people in group X ...", "as
1455
1535
  me in group X ...", "let me know what you've been doing in X", or anything that
1456
1536
  requires the you-in-another-scope to do something or share something. There is no
1457
1537
  separate "recall" or "relay" tool \u2014 neural_send is the only one.
1538
+
1539
+ # Personal notebook (self_note)
1540
+ You have a personal notebook that travels with you across every scope (your 1:1 with
1541
+ the user AND every group you're in). Whatever you write to it now will appear at the
1542
+ top of your system prompt on your next turn, in any scope. Treat it as your long-term
1543
+ memory \u2014 the only thing about "you" that survives across conversations.
1544
+
1545
+ - self_note(action, content?):
1546
+ - "append" \u2014 add a new entry at the bottom of the notebook (most common).
1547
+ - "write" \u2014 replace the whole notebook (use to compact, reorganize, or correct).
1548
+ - "read" \u2014 fetch current contents. Your notebook is already at the top of this
1549
+ prompt, so you rarely need this; use only when you want to verify what's actually
1550
+ persisted (e.g., you suspect the prompt copy is stale after you just wrote).
1551
+
1552
+ Write to your notebook when:
1553
+ - You make a commitment that will outlive this conversation.
1554
+ - The user shares a stable preference or fact about themselves.
1555
+ - You form a position on a recurring topic that you want to keep consistent across
1556
+ every group and 1:1.
1557
+
1558
+ Do NOT write to your notebook for:
1559
+ - Throwaway calculations, small talk, or one-shot Q&A.
1560
+ - Anything that won't matter tomorrow.
1561
+ - Verbose minutes of a conversation \u2014 your future self has to re-read this every
1562
+ turn forever, so keep it lean. Quality over quantity.
1458
1563
  `.trim();
1459
1564
  var FAN_OUT_TRACE_TTL_MS = 10 * 6e4;
1460
1565
 
@@ -1582,7 +1687,7 @@ var InputController = class {
1582
1687
  };
1583
1688
 
1584
1689
  // src/askQuestionRegistry.ts
1585
- var logger = createModuleLogger("askQuestionRegistry");
1690
+ var logger2 = createModuleLogger("askQuestionRegistry");
1586
1691
  var ASK_QUESTION_TIMEOUT_MS = 12e4;
1587
1692
  var TIMEOUT_ANSWER = "[User did not respond within 120 seconds. Please decide whether to proceed with reasonable defaults or skip this step.]";
1588
1693
  var AskQuestionRegistry = class {
@@ -1593,27 +1698,27 @@ var AskQuestionRegistry = class {
1593
1698
  const timer = setTimeout(() => {
1594
1699
  if (!this.entries.has(questionId)) return;
1595
1700
  this.entries.delete(questionId);
1596
- logger.warn("AskQuestion timeout", { questionId, agentId, timeoutMs });
1701
+ logger2.warn("AskQuestion timeout", { questionId, agentId, timeoutMs });
1597
1702
  try {
1598
1703
  onTimeout();
1599
1704
  } catch (e) {
1600
- logger.error("onTimeout cb threw", { error: e });
1705
+ logger2.error("onTimeout cb threw", { error: e });
1601
1706
  }
1602
1707
  resolve(TIMEOUT_ANSWER);
1603
1708
  }, timeoutMs);
1604
1709
  this.entries.set(questionId, { resolve, timer, agentId, askedAt: Date.now() });
1605
- logger.info("AskQuestion registered", { questionId, agentId, timeoutMs });
1710
+ logger2.info("AskQuestion registered", { questionId, agentId, timeoutMs });
1606
1711
  });
1607
1712
  }
1608
1713
  resolve(questionId, answerText) {
1609
1714
  const entry = this.entries.get(questionId);
1610
1715
  if (!entry) {
1611
- logger.warn("AskQuestion resolve: id not found (may be timed out)", { questionId });
1716
+ logger2.warn("AskQuestion resolve: id not found (may be timed out)", { questionId });
1612
1717
  return false;
1613
1718
  }
1614
1719
  clearTimeout(entry.timer);
1615
1720
  this.entries.delete(questionId);
1616
- logger.info("AskQuestion resolved", {
1721
+ logger2.info("AskQuestion resolved", {
1617
1722
  questionId,
1618
1723
  agentId: entry.agentId,
1619
1724
  waitedMs: Date.now() - entry.askedAt,
@@ -1625,7 +1730,7 @@ var AskQuestionRegistry = class {
1625
1730
  }
1626
1731
  cancelAll(reason) {
1627
1732
  if (this.entries.size === 0) return;
1628
- logger.warn("AskQuestion cancelAll", { reason, count: this.entries.size });
1733
+ logger2.warn("AskQuestion cancelAll", { reason, count: this.entries.size });
1629
1734
  for (const [, entry] of this.entries) {
1630
1735
  clearTimeout(entry.timer);
1631
1736
  entry.resolve(`[${reason}]`);
@@ -1654,7 +1759,7 @@ function runtimeKey(agentId, scope) {
1654
1759
  }
1655
1760
 
1656
1761
  // src/askUserQuestionGuard.ts
1657
- var logger2 = createModuleLogger("askUserQuestionGuard");
1762
+ var logger3 = createModuleLogger("askUserQuestionGuard");
1658
1763
  function formatAnswerForSDK(p) {
1659
1764
  const parts = ["[User Response]"];
1660
1765
  if (p.selectedLabels.length > 0) {
@@ -1672,16 +1777,16 @@ function makeAskUserQuestionGuard(deps) {
1672
1777
  return async (input) => {
1673
1778
  const task = deps.getCurrentTask();
1674
1779
  if (!task) {
1675
- logger2.error("AskUserQuestion received but no currentTask", { agentId: deps.agentId });
1780
+ logger3.error("AskUserQuestion received but no currentTask", { agentId: deps.agentId });
1676
1781
  return { behavior: "deny", message: "[Internal error: no active task context]" };
1677
1782
  }
1678
1783
  const questions = input.questions ?? [];
1679
1784
  if (questions.length === 0) {
1680
- logger2.warn("AskUserQuestion called with empty questions array", { agentId: deps.agentId });
1785
+ logger3.warn("AskUserQuestion called with empty questions array", { agentId: deps.agentId });
1681
1786
  return { behavior: "deny", message: "[Internal error: empty questions]" };
1682
1787
  }
1683
1788
  if (questions.length > 1) {
1684
- logger2.warn("AskUserQuestion received multi questions, Plan A only handles questions[0]", {
1789
+ logger3.warn("AskUserQuestion received multi questions, Plan A only handles questions[0]", {
1685
1790
  agentId: deps.agentId,
1686
1791
  count: questions.length
1687
1792
  });
@@ -1694,7 +1799,7 @@ function makeAskUserQuestionGuard(deps) {
1694
1799
  description: o.description
1695
1800
  }));
1696
1801
  const multiSelect = Boolean(q.multiSelect);
1697
- logger2.info("AskUserQuestion intercepted, emitting agent:ask_user_question", {
1802
+ logger3.info("AskUserQuestion intercepted, emitting agent:ask_user_question", {
1698
1803
  agentId: deps.agentId,
1699
1804
  scope: scopeKey(deps.scope),
1700
1805
  groupId: task.groupId,
@@ -1722,7 +1827,7 @@ function makeAskUserQuestionGuard(deps) {
1722
1827
  traceId: task.traceId
1723
1828
  }
1724
1829
  });
1725
- logger2.info("AskUserQuestion agent status awaiting_user", {
1830
+ logger3.info("AskUserQuestion agent status awaiting_user", {
1726
1831
  agentId: deps.agentId,
1727
1832
  questionId,
1728
1833
  groupId: task.groupId,
@@ -1745,7 +1850,7 @@ function makeAskUserQuestionGuard(deps) {
1745
1850
  }
1746
1851
  });
1747
1852
  });
1748
- logger2.info("AskUserQuestion agent status thinking (resume SDK)", {
1853
+ logger3.info("AskUserQuestion agent status thinking (resume SDK)", {
1749
1854
  agentId: deps.agentId,
1750
1855
  questionId,
1751
1856
  groupId: task.groupId,
@@ -1755,7 +1860,7 @@ function makeAskUserQuestionGuard(deps) {
1755
1860
  type: "agent:status",
1756
1861
  payload: { agentId: deps.agentId, status: "thinking" }
1757
1862
  });
1758
- logger2.info("AskUserQuestion answered, returning deny+message to SDK", {
1863
+ logger3.info("AskUserQuestion answered, returning deny+message to SDK", {
1759
1864
  agentId: deps.agentId,
1760
1865
  questionId,
1761
1866
  replyMessageId: task.replyMessageId,
@@ -1767,17 +1872,17 @@ function makeAskUserQuestionGuard(deps) {
1767
1872
  }
1768
1873
 
1769
1874
  // src/permissionGuard.ts
1770
- import path5 from "path";
1875
+ import path6 from "path";
1771
1876
 
1772
1877
  // ../shared/src/utils/pathSafety.ts
1773
- import path4 from "path";
1878
+ import path5 from "path";
1774
1879
  function isPathInside(parent, child) {
1775
- const resolvedParent = path4.resolve(parent);
1776
- const resolvedChild = path4.resolve(child);
1880
+ const resolvedParent = path5.resolve(parent);
1881
+ const resolvedChild = path5.resolve(child);
1777
1882
  if (resolvedParent === resolvedChild) return true;
1778
- const rel = path4.relative(resolvedParent, resolvedChild);
1883
+ const rel = path5.relative(resolvedParent, resolvedChild);
1779
1884
  if (rel === "") return true;
1780
- return !rel.startsWith("..") && !path4.isAbsolute(rel);
1885
+ return !rel.startsWith("..") && !path5.isAbsolute(rel);
1781
1886
  }
1782
1887
 
1783
1888
  // src/permissionGuard.ts
@@ -1792,7 +1897,7 @@ function makeCwdPermissionGuard(cwd, agentId, scope, log) {
1792
1897
  if (typeof raw !== "string" || raw.length === 0) {
1793
1898
  return { behavior: "allow" };
1794
1899
  }
1795
- const abs = path5.isAbsolute(raw) ? raw : path5.resolve(cwd, raw);
1900
+ const abs = path6.isAbsolute(raw) ? raw : path6.resolve(cwd, raw);
1796
1901
  if (isPathInside(cwd, abs)) {
1797
1902
  return { behavior: "allow" };
1798
1903
  }
@@ -1805,7 +1910,7 @@ function makeCwdPermissionGuard(cwd, agentId, scope, log) {
1805
1910
  }
1806
1911
 
1807
1912
  // src/neuralMcpServer.ts
1808
- var logger3 = createModuleLogger("neural.mcpServer");
1913
+ var logger4 = createModuleLogger("neural.mcpServer");
1809
1914
  function formatScopeLabel(key, groupName) {
1810
1915
  if (key === "single") return "\u5355\u804A";
1811
1916
  if (groupName) return `\u7FA4\u300C${groupName}\u300D`;
@@ -1829,7 +1934,7 @@ async function createNeuralMcpServer(deps) {
1829
1934
  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')
1830
1935
  },
1831
1936
  async (args) => {
1832
- logger3.info("neural_send tool called", {
1937
+ logger4.info("neural_send tool called", {
1833
1938
  agentId: deps.agentId,
1834
1939
  fromScope: currentScopeKey,
1835
1940
  rawTargetScope: args.target_scope,
@@ -1850,12 +1955,12 @@ async function createNeuralMcpServer(deps) {
1850
1955
  if (singleConvId) {
1851
1956
  conversationId = singleConvId;
1852
1957
  } else {
1853
- logger3.warn("neural_send: failed to resolve single conv", { agentId: deps.agentId });
1958
+ logger4.warn("neural_send: failed to resolve single conv", { agentId: deps.agentId });
1854
1959
  }
1855
1960
  } else if (args.target_scope.startsWith("group:")) {
1856
1961
  const r = await deps.groupRegistry.resolveScope(args.target_scope);
1857
1962
  if (!r) {
1858
- logger3.info("neural_send: target scope not found", { rawTargetScope: args.target_scope });
1963
+ logger4.info("neural_send: target scope not found", { rawTargetScope: args.target_scope });
1859
1964
  return {
1860
1965
  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` }],
1861
1966
  isError: true
@@ -1866,6 +1971,28 @@ async function createNeuralMcpServer(deps) {
1866
1971
  groupId = r.groupId;
1867
1972
  groupName = r.groupName;
1868
1973
  targetCwd = r.workingDirectory;
1974
+ const cached = deps.groupRegistry.getById(r.groupId);
1975
+ if (!cached || !cached.members.includes(deps.agentId)) {
1976
+ logger4.info("neural_send: not a member of target group", {
1977
+ agentId: deps.agentId,
1978
+ groupId: r.groupId,
1979
+ groupName: r.groupName,
1980
+ cacheHit: !!cached,
1981
+ memberCount: cached?.members.length ?? 0
1982
+ });
1983
+ return {
1984
+ content: [{
1985
+ type: "text",
1986
+ 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`
1987
+ }],
1988
+ isError: true
1989
+ };
1990
+ }
1991
+ logger4.info("neural_send: member check passed", {
1992
+ agentId: deps.agentId,
1993
+ groupId: r.groupId,
1994
+ groupName: r.groupName
1995
+ });
1869
1996
  } else {
1870
1997
  return {
1871
1998
  content: [{ type: "text", text: '[neural_send] target_scope \u5FC5\u987B\u662F "single" \u6216 "group:<\u7FA4\u540D\u6216 ID>"\u3002' }],
@@ -1873,7 +2000,7 @@ async function createNeuralMcpServer(deps) {
1873
2000
  };
1874
2001
  }
1875
2002
  if (resolvedKey === currentScopeKey) {
1876
- logger3.warn("neural_send: self-send refused", { agentId: deps.agentId, scope: currentScopeKey });
2003
+ logger4.warn("neural_send: self-send refused", { agentId: deps.agentId, scope: currentScopeKey });
1877
2004
  return {
1878
2005
  content: [{ type: "text", text: "[neural_send] \u4E0D\u80FD\u628A\u6D88\u606F\u9001\u7ED9\u81EA\u5DF1\u5F53\u524D\u6240\u5728\u7684 scope\u3002" }],
1879
2006
  isError: true
@@ -1891,7 +2018,7 @@ async function createNeuralMcpServer(deps) {
1891
2018
  groupId,
1892
2019
  targetCwd
1893
2020
  });
1894
- logger3.info("neural_send delivered", {
2021
+ logger4.info("neural_send delivered", {
1895
2022
  agentId: deps.agentId,
1896
2023
  fromScope: currentScopeKey,
1897
2024
  toScope: resolvedKey,
@@ -1901,7 +2028,7 @@ async function createNeuralMcpServer(deps) {
1901
2028
  content: [{ type: "text", text: `[neural_send] \u5DF2\u9001\u8FBE\u5230\u300C${toLabel}\u300D(scope: ${resolvedKey})\u3002` }]
1902
2029
  };
1903
2030
  } catch (err) {
1904
- logger3.error("neural_send dispatch failed", { agentId: deps.agentId, error: err });
2031
+ logger4.error("neural_send dispatch failed", { agentId: deps.agentId, error: err });
1905
2032
  return {
1906
2033
  content: [{ type: "text", text: `[neural_send] \u9001\u8FBE\u5931\u8D25\uFF1A${err.message}` }],
1907
2034
  isError: true
@@ -1910,21 +2037,160 @@ async function createNeuralMcpServer(deps) {
1910
2037
  },
1911
2038
  {}
1912
2039
  );
2040
+ const selfNote = deps.memoryStore ? sdk.tool(
2041
+ "self_note",
2042
+ `\u5728\u4F60\u7684"\u968F\u8EAB\u7B14\u8BB0\u672C"\u4E0A\u5199/\u8BFB\u4E00\u6BB5\u5185\u5BB9\u3002
2043
+ \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
2044
+ 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
2045
+ \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`,
2046
+ {
2047
+ action: z.string().describe(
2048
+ '\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'
2049
+ ),
2050
+ content: z.string().optional().describe(
2051
+ '\u8981\u5199\u5165\u7684\u5185\u5BB9\u3002action="append" \u6216 "write" \u65F6\u5FC5\u586B\uFF1Baction="read" \u65F6\u5FFD\u7565\u3002'
2052
+ )
2053
+ },
2054
+ async (args) => {
2055
+ const action = args.action;
2056
+ const content = args.content;
2057
+ logger4.info("self_note tool called", {
2058
+ agentId: deps.agentId,
2059
+ scope: currentScopeKey,
2060
+ action,
2061
+ contentLen: typeof content === "string" ? content.length : 0
2062
+ });
2063
+ if (action === "read") {
2064
+ const current = deps.memoryStore.read(deps.agentId);
2065
+ if (current.length === 0) {
2066
+ logger4.info("self_note read empty", {
2067
+ agentId: deps.agentId,
2068
+ scope: currentScopeKey
2069
+ });
2070
+ return {
2071
+ content: [{ type: "text", text: "[self_note] \u4F60\u7684\u7B14\u8BB0\u672C\u76EE\u524D\u662F\u7A7A\u7684\u3002" }]
2072
+ };
2073
+ }
2074
+ logger4.info("self_note read ok", {
2075
+ agentId: deps.agentId,
2076
+ scope: currentScopeKey,
2077
+ notebookBytes: current.length
2078
+ });
2079
+ return {
2080
+ content: [{ type: "text", text: current }]
2081
+ };
2082
+ }
2083
+ if (action === "append" || action === "write") {
2084
+ if (typeof content !== "string" || content.length === 0) {
2085
+ logger4.warn("self_note missing content", {
2086
+ agentId: deps.agentId,
2087
+ scope: currentScopeKey,
2088
+ action
2089
+ });
2090
+ return {
2091
+ content: [{ type: "text", text: `[self_note] action="${action}" \u65F6 content \u5FC5\u586B\u4E14\u975E\u7A7A\u3002` }],
2092
+ isError: true
2093
+ };
2094
+ }
2095
+ try {
2096
+ if (action === "append") {
2097
+ deps.memoryStore.append(deps.agentId, content);
2098
+ } else {
2099
+ deps.memoryStore.write(deps.agentId, content);
2100
+ }
2101
+ const after = deps.memoryStore.read(deps.agentId);
2102
+ logger4.info("self_note persisted", {
2103
+ agentId: deps.agentId,
2104
+ scope: currentScopeKey,
2105
+ action,
2106
+ contentLen: content.length,
2107
+ notebookBytesAfter: after.length
2108
+ });
2109
+ return {
2110
+ 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` }]
2111
+ };
2112
+ } catch (err) {
2113
+ logger4.error("self_note write failed", { agentId: deps.agentId, action, error: err });
2114
+ return {
2115
+ content: [{ type: "text", text: `[self_note] \u5199\u5165\u5931\u8D25\uFF1A${err.message}` }],
2116
+ isError: true
2117
+ };
2118
+ }
2119
+ }
2120
+ logger4.warn("self_note invalid action", {
2121
+ agentId: deps.agentId,
2122
+ scope: currentScopeKey,
2123
+ action: String(action)
2124
+ });
2125
+ return {
2126
+ content: [{ type: "text", text: `[self_note] \u672A\u77E5 action "${String(action)}"\u3002\u5FC5\u987B\u662F "read" / "append" / "write" \u4E4B\u4E00\u3002` }],
2127
+ isError: true
2128
+ };
2129
+ },
2130
+ {}
2131
+ ) : null;
2132
+ const neuralListScopes = sdk.tool(
2133
+ "neural_list_scopes",
2134
+ `\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
2135
+ \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
2136
+ \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`,
2137
+ {},
2138
+ async () => {
2139
+ logger4.info("neural_list_scopes tool called", {
2140
+ agentId: deps.agentId,
2141
+ scope: currentScopeKey
2142
+ });
2143
+ await deps.groupRegistry.refresh();
2144
+ const myGroups = deps.groupRegistry.getMyGroups(deps.agentId);
2145
+ logger4.info("neural_list_scopes: registry refreshed", {
2146
+ agentId: deps.agentId,
2147
+ scope: currentScopeKey,
2148
+ myGroupCount: myGroups.length
2149
+ });
2150
+ const lines = [];
2151
+ const isCurSingle = currentScopeKey === "single";
2152
+ lines.push(`- single \u2014 \u4F60\u548C\u7528\u6237\u7684 1:1 \u5355\u804A${isCurSingle ? " (\u4F60\u5F53\u524D\u6240\u5728)" : ""}`);
2153
+ for (const g of myGroups) {
2154
+ const key = `group:${g.groupId}`;
2155
+ const here = key === currentScopeKey ? " (\u4F60\u5F53\u524D\u6240\u5728)" : "";
2156
+ lines.push(`- ${key} \u2014 ${g.name}${here}`);
2157
+ }
2158
+ const text = [
2159
+ `\u4F60\u76EE\u524D\u6709 ${1 + myGroups.length} \u4E2A"\u5206\u8EAB scope"\uFF1A`,
2160
+ "",
2161
+ ...lines,
2162
+ "",
2163
+ '\u8C03 neural_send(target_scope="...") \u65F6\u8BF7\u7528\u4E0A\u9762\u5217\u51FA\u7684 scope \u5B57\u7B26\u4E32\u3002',
2164
+ 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"
2165
+ ].join("\n");
2166
+ logger4.info("neural_list_scopes returned", {
2167
+ agentId: deps.agentId,
2168
+ scope: currentScopeKey,
2169
+ groupCount: myGroups.length
2170
+ });
2171
+ return { content: [{ type: "text", text }] };
2172
+ },
2173
+ {}
2174
+ );
2175
+ const tools = [neuralSend, neuralListScopes];
2176
+ if (selfNote) tools.push(selfNote);
1913
2177
  const neuralServer = sdk.createSdkMcpServer({
1914
2178
  name: "neural",
1915
2179
  version: "2.0.0",
1916
- tools: [neuralSend]
2180
+ tools
1917
2181
  });
1918
- logger3.info("Neural MCP server created", {
2182
+ const toolNames = ["neural_send", "neural_list_scopes"];
2183
+ if (selfNote) toolNames.push("self_note");
2184
+ logger4.info("Neural MCP server created", {
1919
2185
  agentId: deps.agentId,
1920
2186
  scope: currentScopeKey,
1921
- tools: ["neural_send"]
2187
+ tools: toolNames
1922
2188
  });
1923
2189
  return neuralServer;
1924
2190
  }
1925
2191
 
1926
2192
  // src/sdkEventMapper.ts
1927
- var logger4 = createModuleLogger("sdk.mapper");
2193
+ var logger5 = createModuleLogger("sdk.mapper");
1928
2194
  function getTaskBase(proc) {
1929
2195
  const task = proc.currentTask;
1930
2196
  if (!task) return null;
@@ -1994,7 +2260,7 @@ function emitGroupSegment(proc, emit, base, content, contentBlocks) {
1994
2260
  const groupId = proc.currentTask?.groupId;
1995
2261
  if (!groupId) return;
1996
2262
  proc.segmentCount += 1;
1997
- logger4.info("Group segment emitted", {
2263
+ logger5.info("Group segment emitted", {
1998
2264
  agentId: base.agentId,
1999
2265
  replyMessageId: base.replyMessageId,
2000
2266
  groupId,
@@ -2024,7 +2290,7 @@ function flushTextSegmentOnBlockStop(proc, emit, base) {
2024
2290
  emitGroupSegment(proc, emit, base, proc.segmentBuffer, proc.contentBlocks);
2025
2291
  proc.contentBlocks = [];
2026
2292
  } else {
2027
- logger4.info("Group text block flushed but skipped (no segment emitted)", {
2293
+ logger5.info("Group text block flushed but skipped (no segment emitted)", {
2028
2294
  agentId: base.agentId,
2029
2295
  replyMessageId: base.replyMessageId,
2030
2296
  groupId: proc.currentTask?.groupId,
@@ -2048,7 +2314,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2048
2314
  if (proc.status === "starting") {
2049
2315
  proc.status = "ready";
2050
2316
  }
2051
- logger4.info("Agent session initialized", {
2317
+ logger5.info("Agent session initialized", {
2052
2318
  agentId: proc.agentId,
2053
2319
  scope: proc.scope.kind === "single" ? "single" : proc.scope.groupId,
2054
2320
  sessionId: initMsg.session_id,
@@ -2111,7 +2377,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2111
2377
  }
2112
2378
  } else if (delta.type === "text_delta" && typeof delta.text === "string") {
2113
2379
  if (proc.accumulatedText.length === 0) {
2114
- logger4.info("Agent text stream started", {
2380
+ logger5.info("Agent text stream started", {
2115
2381
  agentId: proc.agentId,
2116
2382
  replyMessageId: base.replyMessageId,
2117
2383
  traceId: base.traceId,
@@ -2150,7 +2416,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2150
2416
  try {
2151
2417
  parsedInput = JSON.parse(proc.accumulatedToolInput);
2152
2418
  } catch {
2153
- logger4.warn("Failed to parse tool input JSON", {
2419
+ logger5.warn("Failed to parse tool input JSON", {
2154
2420
  agentId: proc.agentId,
2155
2421
  toolName: proc.currentToolName,
2156
2422
  inputLen: proc.accumulatedToolInput.length,
@@ -2165,7 +2431,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2165
2431
  if (proc.currentToolName === "TodoWrite") {
2166
2432
  const todos = extractTodosFromInput(parsedInput);
2167
2433
  if (todos) {
2168
- logger4.info("TodoWrite detected, emitting agent:todos_update", {
2434
+ logger5.info("TodoWrite detected, emitting agent:todos_update", {
2169
2435
  agentId: proc.agentId,
2170
2436
  replyMessageId: base.replyMessageId,
2171
2437
  groupId: proc.currentTask?.groupId,
@@ -2182,7 +2448,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2182
2448
  }
2183
2449
  });
2184
2450
  } else {
2185
- logger4.info("TodoWrite detected with empty/cancel todos", {
2451
+ logger5.info("TodoWrite detected with empty/cancel todos", {
2186
2452
  agentId: proc.agentId,
2187
2453
  replyMessageId: base.replyMessageId,
2188
2454
  traceId: base.traceId
@@ -2259,7 +2525,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2259
2525
  const groupMode = groupId != null;
2260
2526
  const usage = extractUsage(successMsg);
2261
2527
  if (trimmed === NO_REPLY_TOKEN) {
2262
- logger4.info("Agent chose not to reply", {
2528
+ logger5.info("Agent chose not to reply", {
2263
2529
  agentId: proc.agentId,
2264
2530
  replyMessageId: base.replyMessageId,
2265
2531
  traceId: base.traceId,
@@ -2284,13 +2550,13 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2284
2550
  }
2285
2551
  if (groupMode) {
2286
2552
  if (usage.inputTokens && usage.inputTokens > 15e4) {
2287
- logger4.warn("Agent context window approaching limit", {
2553
+ logger5.warn("Agent context window approaching limit", {
2288
2554
  agentId: proc.agentId,
2289
2555
  inputTokens: usage.inputTokens
2290
2556
  });
2291
2557
  }
2292
2558
  if (proc.contentBlocks.length > 0) {
2293
- logger4.info("Group turn trailing audit segment", {
2559
+ logger5.info("Group turn trailing audit segment", {
2294
2560
  agentId: proc.agentId,
2295
2561
  replyMessageId: base.replyMessageId,
2296
2562
  blockCount: proc.contentBlocks.length,
@@ -2299,7 +2565,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2299
2565
  emitGroupSegment(proc, emit, base, "", proc.contentBlocks);
2300
2566
  proc.contentBlocks = [];
2301
2567
  }
2302
- logger4.info("Group task turn complete", {
2568
+ logger5.info("Group task turn complete", {
2303
2569
  agentId: proc.agentId,
2304
2570
  replyMessageId: base.replyMessageId,
2305
2571
  groupId,
@@ -2324,13 +2590,13 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2324
2590
  proc.contentBlocks.push({ type: "text", content: proc.accumulatedText });
2325
2591
  }
2326
2592
  if (usage.inputTokens && usage.inputTokens > 15e4) {
2327
- logger4.warn("Agent context window approaching limit", {
2593
+ logger5.warn("Agent context window approaching limit", {
2328
2594
  agentId: proc.agentId,
2329
2595
  inputTokens: usage.inputTokens
2330
2596
  });
2331
2597
  }
2332
2598
  carrierMessageId = createMessageId();
2333
- logger4.info("Agent task done, emitting agent:done", {
2599
+ logger5.info("Agent task done, emitting agent:done", {
2334
2600
  agentId: proc.agentId,
2335
2601
  ackId: base.replyMessageId,
2336
2602
  messageId: carrierMessageId,
@@ -2357,7 +2623,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2357
2623
  } else {
2358
2624
  const errorMsg = resultMsg;
2359
2625
  const errorText = errorMsg.errors?.join("; ") ?? `Agent error: ${resultMsg.subtype}`;
2360
- logger4.warn("Agent task error, emitting agent:error", {
2626
+ logger5.warn("Agent task error, emitting agent:error", {
2361
2627
  agentId: proc.agentId,
2362
2628
  replyMessageId: base.replyMessageId,
2363
2629
  subtype: resultMsg.subtype,
@@ -2376,7 +2642,7 @@ function mapSDKMessage(proc, message, rawEmit, sessionStore, onCompleted) {
2376
2642
  case "assistant":
2377
2643
  break;
2378
2644
  default:
2379
- logger4.warn("Unhandled SDK message type", {
2645
+ logger5.warn("Unhandled SDK message type", {
2380
2646
  type: message.type,
2381
2647
  agentId: proc.agentId
2382
2648
  });
@@ -2396,7 +2662,7 @@ function resetAccumulators(proc) {
2396
2662
 
2397
2663
  // src/wsMetrics.ts
2398
2664
  import { monitorEventLoopDelay } from "perf_hooks";
2399
- var logger5 = createModuleLogger("ws.metrics");
2665
+ var logger6 = createModuleLogger("ws.metrics");
2400
2666
  var WsMetrics = class {
2401
2667
  recv = /* @__PURE__ */ new Map();
2402
2668
  send = /* @__PURE__ */ new Map();
@@ -2443,7 +2709,7 @@ var WsMetrics = class {
2443
2709
  const sendSum = [...this.send.values()].reduce((a, b) => a + b, 0);
2444
2710
  const sdkSum = [...this.sdkOut.values()].reduce((a, b) => a + b, 0);
2445
2711
  if (recvSum + sendSum + sdkSum === 0 && (stats.loopMaxMs ?? 0) < 50) return;
2446
- logger5.info("WS metrics", {
2712
+ logger6.info("WS metrics", {
2447
2713
  windowMs: intervalMs,
2448
2714
  ...stats,
2449
2715
  sums: { recv: recvSum, send: sendSum, sdkOut: sdkSum },
@@ -2459,7 +2725,19 @@ var WsMetrics = class {
2459
2725
  var wsMetrics = new WsMetrics();
2460
2726
 
2461
2727
  // src/agentManager.ts
2462
- var logger6 = createModuleLogger("agent.manager");
2728
+ var logger7 = createModuleLogger("agent.manager");
2729
+ var NOTEBOOK_SECTION_HEADER = `# Your personal notebook
2730
+ The following is your own running notebook. It is private to you and follows
2731
+ you across every scope (single chat and every group). Treat it as your memory
2732
+ of what you've decided, learned, or committed to.`;
2733
+ var SCOPES_SECTION_HEADER = `# Your scopes (where "you" exist)
2734
+ You currently have a presence in the following conversations. Each is a separate
2735
+ runtime of "you" with its own context. neural_send can ONLY reach scopes in this
2736
+ list \u2014 you have no "self" anywhere else.
2737
+
2738
+ This snapshot was taken when this runtime started. If you suspect it's stale
2739
+ (e.g., the user just told you they pulled you into a new group), call
2740
+ neural_list_scopes() once to refresh.`;
2463
2741
  var BridgeBusyError = class extends Error {
2464
2742
  constructor(message = "Bridge busy: cannot evict an idle Agent query; all slots are working") {
2465
2743
  super(message);
@@ -2473,6 +2751,7 @@ var AgentManager = class {
2473
2751
  emit;
2474
2752
  workspacesDir;
2475
2753
  claudeConfigDir;
2754
+ memoryStore;
2476
2755
  queryConfig;
2477
2756
  askQuestionRegistry;
2478
2757
  groupRegistry;
@@ -2484,18 +2763,20 @@ var AgentManager = class {
2484
2763
  this.emit = emit;
2485
2764
  if (typeof options === "function") {
2486
2765
  this.queryFn = options;
2487
- this.workspacesDir = path6.join(os4.homedir(), ".ahchat", "workspaces");
2488
- this.claudeConfigDir = path6.join(os4.homedir(), ".ahchat", "claude-config");
2766
+ this.workspacesDir = path7.join(os4.homedir(), ".ahchat", "workspaces");
2767
+ this.claudeConfigDir = path7.join(os4.homedir(), ".ahchat", "claude-config");
2489
2768
  this.queryConfig = DEFAULT_QUERY_CONFIG;
2490
2769
  this.askQuestionRegistry = new AskQuestionRegistry();
2491
2770
  this.groupRegistry = null;
2771
+ this.memoryStore = null;
2492
2772
  } else {
2493
2773
  this.queryFn = options?.queryFn ?? null;
2494
- this.workspacesDir = options?.workspacesDir ?? path6.join(os4.homedir(), ".ahchat", "workspaces");
2495
- this.claudeConfigDir = options?.claudeConfigDir ?? path6.join(os4.homedir(), ".ahchat", "claude-config");
2774
+ this.workspacesDir = options?.workspacesDir ?? path7.join(os4.homedir(), ".ahchat", "workspaces");
2775
+ this.claudeConfigDir = options?.claudeConfigDir ?? path7.join(os4.homedir(), ".ahchat", "claude-config");
2496
2776
  this.queryConfig = options?.queryConfig ?? DEFAULT_QUERY_CONFIG;
2497
2777
  this.askQuestionRegistry = options?.askQuestionRegistry ?? new AskQuestionRegistry();
2498
2778
  this.groupRegistry = options?.groupRegistry ?? null;
2779
+ this.memoryStore = options?.memoryStore ?? null;
2499
2780
  }
2500
2781
  this.evictionTimer = setInterval(() => {
2501
2782
  void this.evictIdle();
@@ -2528,7 +2809,7 @@ var AgentManager = class {
2528
2809
  })
2529
2810
  ]);
2530
2811
  } catch (e) {
2531
- logger6.warn("awaitQueryReturn finished with error/timeout", { agentId, error: e });
2812
+ logger7.warn("awaitQueryReturn finished with error/timeout", { agentId, error: e });
2532
2813
  }
2533
2814
  }
2534
2815
  /**
@@ -2551,13 +2832,13 @@ var AgentManager = class {
2551
2832
  const proc = this.agents.get(key);
2552
2833
  if (!proc || proc.status === "dead") return;
2553
2834
  if (!this.isEvictable(proc)) return;
2554
- logger6.info("Evicting idle Agent query", { agentId: proc.agentId, scope: scopeKey(proc.scope) });
2835
+ logger7.info("Evicting idle Agent query", { agentId: proc.agentId, scope: scopeKey(proc.scope) });
2555
2836
  const runtime = this.asRuntime(proc);
2556
2837
  try {
2557
2838
  runtime.inputController.close();
2558
2839
  await this.awaitQueryReturn(runtime.query, 5e3, proc.agentId);
2559
2840
  } catch (e) {
2560
- logger6.error("closeIdleQuery failed", { agentId: proc.agentId, error: e });
2841
+ logger7.error("closeIdleQuery failed", { agentId: proc.agentId, error: e });
2561
2842
  }
2562
2843
  proc.status = "dead";
2563
2844
  this.agents.delete(key);
@@ -2623,9 +2904,9 @@ var AgentManager = class {
2623
2904
  const savedSessionId = this.sessionStore.get(agentConfig.id, scope);
2624
2905
  const inputController = new InputController();
2625
2906
  const agentCwd = cwd;
2626
- await fs2.mkdir(agentCwd, { recursive: true });
2907
+ await fs3.mkdir(agentCwd, { recursive: true });
2627
2908
  const cfg = parseAgentConfig(agentConfig.config);
2628
- logger6.info("Creating Agent query", {
2909
+ logger7.info("Creating Agent query", {
2629
2910
  agentId: agentConfig.id,
2630
2911
  scope: scopeKey(scope),
2631
2912
  cwd: agentCwd,
@@ -2636,7 +2917,7 @@ var AgentManager = class {
2636
2917
  const queryFn = await this.getQueryFn();
2637
2918
  let procRef = null;
2638
2919
  const cwdGuard = makeCwdPermissionGuard(agentCwd, agentConfig.id, scope, (msg, meta) => {
2639
- logger6.warn(msg, meta);
2920
+ logger7.warn(msg, meta);
2640
2921
  });
2641
2922
  const askGuard = makeAskUserQuestionGuard({
2642
2923
  agentId: agentConfig.id,
@@ -2649,14 +2930,17 @@ var AgentManager = class {
2649
2930
  agentId: agentConfig.id,
2650
2931
  scope,
2651
2932
  groupRegistry: this.groupRegistry,
2652
- onSend: (payload) => this.deliverNeuralSend(agentConfig, payload)
2933
+ onSend: (payload) => this.deliverNeuralSend(agentConfig, payload),
2934
+ memoryStore: this.memoryStore
2653
2935
  });
2936
+ const notebookSection = this.buildNotebookSection(agentConfig.id);
2937
+ const scopesSection = this.buildScopesSection(agentConfig.id, scope);
2654
2938
  const options = {
2655
2939
  cwd: agentCwd,
2656
2940
  systemPrompt: {
2657
2941
  type: "preset",
2658
2942
  preset: "claude_code",
2659
- append: [PLATFORM_AGENT_RULES, agentConfig.systemPrompt].filter((s) => typeof s === "string" && s.trim().length > 0).join("\n\n")
2943
+ append: [PLATFORM_AGENT_RULES, agentConfig.systemPrompt, notebookSection, scopesSection].filter((s) => typeof s === "string" && s.trim().length > 0).join("\n\n")
2660
2944
  },
2661
2945
  permissionMode: "bypassPermissions",
2662
2946
  allowDangerouslySkipPermissions: true,
@@ -2668,7 +2952,9 @@ var AgentManager = class {
2668
2952
  "Glob",
2669
2953
  "Grep",
2670
2954
  "AskUserQuestion",
2671
- "mcp__neural__neural_send"
2955
+ "mcp__neural__neural_send",
2956
+ "mcp__neural__neural_list_scopes",
2957
+ "mcp__neural__self_note"
2672
2958
  ],
2673
2959
  mcpServers: { neural: neuralServer },
2674
2960
  includePartialMessages: true,
@@ -2682,12 +2968,14 @@ var AgentManager = class {
2682
2968
  };
2683
2969
  const userPromptTrimmed = (agentConfig.systemPrompt ?? "").trim();
2684
2970
  const appendStr = options.systemPrompt.append;
2685
- logger6.info("Platform rules attached", {
2971
+ logger7.info("Platform rules attached", {
2686
2972
  agentId: agentConfig.id,
2687
2973
  scope: scopeKey(scope),
2688
2974
  platformRulesLen: PLATFORM_AGENT_RULES.length,
2689
2975
  userPromptLen: userPromptTrimmed.length,
2690
2976
  hasUserPrompt: userPromptTrimmed.length > 0,
2977
+ notebookLen: notebookSection.length,
2978
+ scopesLen: scopesSection.length,
2691
2979
  appendLen: appendStr.length
2692
2980
  });
2693
2981
  if (cfg.model) {
@@ -2729,6 +3017,58 @@ var AgentManager = class {
2729
3017
  this.consumeOutput(runtime);
2730
3018
  return proc;
2731
3019
  }
3020
+ buildNotebookSection(agentId) {
3021
+ if (!this.memoryStore) {
3022
+ logger7.info("Notebook injection skipped", { agentId, reason: "no_memory_store" });
3023
+ return "";
3024
+ }
3025
+ const raw = this.memoryStore.read(agentId);
3026
+ const trimmed = raw.trim();
3027
+ if (trimmed.length === 0) {
3028
+ logger7.info("Notebook injection skipped", {
3029
+ agentId,
3030
+ reason: raw.length === 0 ? "empty_or_missing" : "whitespace_only",
3031
+ rawBytes: raw.length
3032
+ });
3033
+ return "";
3034
+ }
3035
+ const section = `${NOTEBOOK_SECTION_HEADER}
3036
+
3037
+ ${trimmed}`;
3038
+ logger7.info("Notebook injected into system prompt", {
3039
+ agentId,
3040
+ rawBytes: raw.length,
3041
+ trimmedBytes: trimmed.length,
3042
+ sectionLen: section.length
3043
+ });
3044
+ return section;
3045
+ }
3046
+ buildScopesSection(agentId, scope) {
3047
+ if (!this.groupRegistry) {
3048
+ logger7.info("Scopes injection skipped", { agentId, reason: "no_group_registry" });
3049
+ return "";
3050
+ }
3051
+ const myGroups = this.groupRegistry.getMyGroups(agentId);
3052
+ const curKey = scopeKey(scope);
3053
+ const lines = [];
3054
+ const isCurSingle = curKey === "single";
3055
+ lines.push(`- single \u2014 1:1 chat with the user${isCurSingle ? " (you are here)" : ""}`);
3056
+ for (const g of myGroups) {
3057
+ const key = `group:${g.groupId}`;
3058
+ const here = key === curKey ? " (you are here)" : "";
3059
+ lines.push(`- ${key} \u2014 ${g.name}${here}`);
3060
+ }
3061
+ const section = `${SCOPES_SECTION_HEADER}
3062
+
3063
+ ${lines.join("\n")}`;
3064
+ logger7.info("Scopes injected into system prompt", {
3065
+ agentId,
3066
+ scope: curKey,
3067
+ groupCount: myGroups.length,
3068
+ sectionLen: section.length
3069
+ });
3070
+ return section;
3071
+ }
2732
3072
  async sendMessage(task) {
2733
3073
  const key = runtimeKey(task.agentId, task.scope);
2734
3074
  const proc = this.agents.get(key);
@@ -2741,7 +3081,7 @@ var AgentManager = class {
2741
3081
  return;
2742
3082
  }
2743
3083
  if (proc.status === "starting") {
2744
- logger6.info("Message dispatched to starting Agent (kickstart)", {
3084
+ logger7.info("Message dispatched to starting Agent (kickstart)", {
2745
3085
  agentId: task.agentId,
2746
3086
  replyMessageId: task.replyMessageId,
2747
3087
  traceId: task.traceId
@@ -2754,7 +3094,7 @@ var AgentManager = class {
2754
3094
  if (idx >= 0) {
2755
3095
  runtime.injectedTasks.splice(idx, 1);
2756
3096
  runtime.mergedTasks.push(task);
2757
- logger6.info("Injected task consumed by SDK (queued as merged until next result)", {
3097
+ logger7.info("Injected task consumed by SDK (queued as merged until next result)", {
2758
3098
  agentId: runtime.agentId,
2759
3099
  replyMessageId: task.replyMessageId,
2760
3100
  traceId: task.traceId,
@@ -2765,7 +3105,7 @@ var AgentManager = class {
2765
3105
  };
2766
3106
  runtime.inputController.push(task.content, runtime.ccSessionId ?? "", onYielded);
2767
3107
  runtime.injectedTasks.push(task);
2768
- logger6.info("Message injected while Agent working", {
3108
+ logger7.info("Message injected while Agent working", {
2769
3109
  agentId: task.agentId,
2770
3110
  replyMessageId: task.replyMessageId,
2771
3111
  currentStatus: proc.status,
@@ -2778,7 +3118,7 @@ var AgentManager = class {
2778
3118
  runtime.currentTask = task;
2779
3119
  runtime.currentTaskStartedAt = Date.now();
2780
3120
  runtime.inputController.push(task.content, runtime.ccSessionId ?? "");
2781
- logger6.info("Message pushed to Agent", {
3121
+ logger7.info("Message pushed to Agent", {
2782
3122
  agentId: runtime.agentId,
2783
3123
  replyMessageId: task.replyMessageId,
2784
3124
  traceId: task.traceId
@@ -2798,7 +3138,7 @@ var AgentManager = class {
2798
3138
  const completedTask = proc.currentTask;
2799
3139
  if (completedTask && runtime.mergedTasks.length > 0) {
2800
3140
  const mergedBatch = [...runtime.mergedTasks];
2801
- logger6.info("Flushing merged tasks after result", {
3141
+ logger7.info("Flushing merged tasks after result", {
2802
3142
  agentId: proc.agentId,
2803
3143
  carrierReplyMessageId: completedTask.replyMessageId,
2804
3144
  mergedCount: mergedBatch.length,
@@ -2806,7 +3146,7 @@ var AgentManager = class {
2806
3146
  traceId: completedTask.traceId
2807
3147
  });
2808
3148
  for (const merged of mergedBatch) {
2809
- logger6.info("Emitting agent:merged for task consumed in same turn", {
3149
+ logger7.info("Emitting agent:merged for task consumed in same turn", {
2810
3150
  agentId: proc.agentId,
2811
3151
  ackId: merged.replyMessageId,
2812
3152
  mergedIntoAckId: completedTask.replyMessageId,
@@ -2828,7 +3168,7 @@ var AgentManager = class {
2828
3168
  }
2829
3169
  runtime.mergedTasks = [];
2830
3170
  } else if (runtime.mergedTasks.length > 0) {
2831
- logger6.warn("mergedTasks non-empty but no currentTask; dropping", {
3171
+ logger7.warn("mergedTasks non-empty but no currentTask; dropping", {
2832
3172
  agentId: proc.agentId,
2833
3173
  mergedCount: runtime.mergedTasks.length
2834
3174
  });
@@ -2840,7 +3180,7 @@ var AgentManager = class {
2840
3180
  proc.currentTask = next;
2841
3181
  proc.status = "working";
2842
3182
  proc.currentTaskStartedAt = Date.now();
2843
- logger6.info("Promoted next injected task after result", {
3183
+ logger7.info("Promoted next injected task after result", {
2844
3184
  agentId: proc.agentId,
2845
3185
  replyMessageId: next.replyMessageId,
2846
3186
  remainingInjected: runtime.injectedTasks.length,
@@ -2884,7 +3224,7 @@ var AgentManager = class {
2884
3224
  };
2885
3225
  const key = runtimeKey(agentConfig.id, targetScope);
2886
3226
  const existingProc = this.agents.get(key);
2887
- logger6.info("Neural send dispatching", {
3227
+ logger7.info("Neural send dispatching", {
2888
3228
  agentId: agentConfig.id,
2889
3229
  fromScope: payload.fromScopeKey,
2890
3230
  toScope: payload.toScopeKey,
@@ -2897,7 +3237,7 @@ var AgentManager = class {
2897
3237
  });
2898
3238
  if (existingProc && existingProc.status !== "dead") {
2899
3239
  if (existingProc.status === "ready" || existingProc.status === "starting") {
2900
- logger6.info("Neural send dispatched to idle runtime", {
3240
+ logger7.info("Neural send dispatched to idle runtime", {
2901
3241
  agentId: agentConfig.id,
2902
3242
  toScope: payload.toScopeKey,
2903
3243
  replyMessageId: task.replyMessageId,
@@ -2908,7 +3248,7 @@ var AgentManager = class {
2908
3248
  const runtime = this.asRuntime(existingProc);
2909
3249
  runtime.inputController.push(task.content, runtime.ccSessionId ?? "");
2910
3250
  runtime.injectedTasks.push(task);
2911
- logger6.info("Neural send injected mid-turn", {
3251
+ logger7.info("Neural send injected mid-turn", {
2912
3252
  agentId: agentConfig.id,
2913
3253
  toScope: payload.toScopeKey,
2914
3254
  replyMessageId: task.replyMessageId,
@@ -2920,7 +3260,7 @@ var AgentManager = class {
2920
3260
  let cwd;
2921
3261
  if (targetScope.kind === "group") {
2922
3262
  if (!payload.targetCwd) {
2923
- logger6.error("Neural send abort: group target missing targetCwd", {
3263
+ logger7.error("Neural send abort: group target missing targetCwd", {
2924
3264
  agentId: agentConfig.id,
2925
3265
  toScope: payload.toScopeKey
2926
3266
  });
@@ -2928,10 +3268,10 @@ var AgentManager = class {
2928
3268
  }
2929
3269
  cwd = payload.targetCwd;
2930
3270
  } else {
2931
- cwd = agentConfig.workingDirectory || path6.join(this.workspacesDir, agentConfig.id);
3271
+ cwd = agentConfig.workingDirectory || path7.join(this.workspacesDir, agentConfig.id);
2932
3272
  }
2933
3273
  void this.acquire(agentConfig, targetScope, cwd).then(() => {
2934
- logger6.info("Neural send new runtime acquired", {
3274
+ logger7.info("Neural send new runtime acquired", {
2935
3275
  agentId: agentConfig.id,
2936
3276
  toScope: payload.toScopeKey,
2937
3277
  cwd,
@@ -2939,7 +3279,7 @@ var AgentManager = class {
2939
3279
  });
2940
3280
  return this.sendMessage({ ...task, agentId: agentConfig.id, scope: targetScope });
2941
3281
  }).catch((err) => {
2942
- logger6.error("Neural send acquire failed", {
3282
+ logger7.error("Neural send acquire failed", {
2943
3283
  agentId: agentConfig.id,
2944
3284
  toScope: payload.toScopeKey,
2945
3285
  cwd,
@@ -2955,7 +3295,7 @@ var AgentManager = class {
2955
3295
  (k) => k === agentId || k.startsWith(`${agentId}::`)
2956
3296
  );
2957
3297
  if (keys.length === 0) {
2958
- logger6.warn("terminate: no process for agent", { agentId });
3298
+ logger7.warn("terminate: no process for agent", { agentId });
2959
3299
  this.sessionStore.deleteAllForAgent(agentId);
2960
3300
  return;
2961
3301
  }
@@ -2966,19 +3306,19 @@ var AgentManager = class {
2966
3306
  }
2967
3307
  }
2968
3308
  this.sessionStore.deleteAllForAgent(agentId);
2969
- logger6.info("terminate: all scoped queries removed", { agentId, count: keys.length });
3309
+ logger7.info("terminate: all scoped queries removed", { agentId, count: keys.length });
2970
3310
  }
2971
3311
  /** Stop one scoped SDK runtime (workdir change). */
2972
3312
  async terminateScope(agentId, scope) {
2973
3313
  const key = runtimeKey(agentId, scope);
2974
3314
  const proc = this.agents.get(key);
2975
3315
  if (!proc) {
2976
- logger6.info("terminateScope: no active runtime", { agentId, scope: scopeKey(scope) });
3316
+ logger7.info("terminateScope: no active runtime", { agentId, scope: scopeKey(scope) });
2977
3317
  return;
2978
3318
  }
2979
3319
  await this.closeRuntime(proc, "terminateScope");
2980
3320
  this.sessionStore.delete(agentId, scope);
2981
- logger6.info("terminateScope: scoped query removed", { agentId, scope: scopeKey(scope) });
3321
+ logger7.info("terminateScope: scoped query removed", { agentId, scope: scopeKey(scope) });
2982
3322
  }
2983
3323
  async closeRuntime(proc, reason) {
2984
3324
  const key = runtimeKey(proc.agentId, proc.scope);
@@ -3008,7 +3348,7 @@ var AgentManager = class {
3008
3348
  runtime.mergedTasks = [];
3009
3349
  for (const t of mergedAtClose) {
3010
3350
  if (runtime.currentTask) {
3011
- logger6.info("Emitting agent:merged on runtime close", {
3351
+ logger7.info("Emitting agent:merged on runtime close", {
3012
3352
  agentId: runtime.agentId,
3013
3353
  replyMessageId: t.replyMessageId,
3014
3354
  mergedInto: runtime.currentTask.replyMessageId,
@@ -3035,45 +3375,57 @@ var AgentManager = class {
3035
3375
  runtime.inputController.close();
3036
3376
  await this.awaitQueryReturn(runtime.query, 5e3, agentId);
3037
3377
  } catch (e) {
3038
- logger6.error(`${reason}: close query failed`, { agentId, scope: scopeKey(proc.scope), error: e });
3378
+ logger7.error(`${reason}: close query failed`, { agentId, scope: scopeKey(proc.scope), error: e });
3039
3379
  }
3040
3380
  proc.status = "dead";
3041
3381
  this.agents.delete(key);
3042
3382
  this.lastUsedAt.delete(key);
3043
- logger6.info(`${reason}: keeping workspace dir intact (per project policy)`, {
3383
+ logger7.info(`${reason}: keeping workspace dir intact (per project policy)`, {
3044
3384
  agentId,
3045
3385
  scope: scopeKey(proc.scope),
3046
3386
  cwd: proc.cwd
3047
3387
  });
3048
3388
  }
3049
3389
  async recoverFromRestart(agents) {
3050
- logger6.info("Recovering Agent sessions after restart", { count: agents.length });
3390
+ logger7.info("Recovering Agent sessions after restart", { count: agents.length });
3051
3391
  const agentsWithSession = agents.filter((a) => {
3052
3392
  const sessionId = this.sessionStore.get(a.id, { kind: "single" });
3053
3393
  return !!sessionId;
3054
3394
  });
3055
3395
  if (agentsWithSession.length === 0) {
3056
- logger6.info("No Agent sessions to recover");
3396
+ logger7.info("No Agent sessions to recover");
3057
3397
  return;
3058
3398
  }
3059
3399
  let warmed = 0;
3060
3400
  const cap = this.queryConfig.maxActive;
3061
3401
  for (const agent of agentsWithSession) {
3062
3402
  if (warmed >= cap) {
3063
- logger6.info("Recovery warm cap reached", { cap, skipped: agentsWithSession.length - warmed });
3403
+ logger7.info("Recovery warm cap reached", { cap, skipped: agentsWithSession.length - warmed });
3064
3404
  break;
3065
3405
  }
3066
3406
  try {
3067
- const cwd = agent.workingDirectory || path6.join(this.workspacesDir, agent.id);
3407
+ let cwd = agent.workingDirectory || path7.join(this.workspacesDir, agent.id);
3408
+ if (agent.workingDirectory) {
3409
+ try {
3410
+ await fs3.mkdir(cwd, { recursive: true });
3411
+ } catch {
3412
+ cwd = path7.join(this.workspacesDir, agent.id);
3413
+ logger7.warn("Stored workingDirectory inaccessible, falling back", {
3414
+ agentId: agent.id,
3415
+ stored: agent.workingDirectory,
3416
+ fallback: cwd
3417
+ });
3418
+ }
3419
+ }
3068
3420
  await this.acquire(agent, { kind: "single" }, cwd);
3069
3421
  warmed++;
3070
- logger6.info("Agent process pre-created for recovery", { agentId: agent.id });
3422
+ logger7.info("Agent process pre-created for recovery", { agentId: agent.id });
3071
3423
  } catch (err) {
3072
3424
  if (err instanceof BridgeBusyError) {
3073
- logger6.warn("Recovery stopped: bridge busy", { agentId: agent.id });
3425
+ logger7.warn("Recovery stopped: bridge busy", { agentId: agent.id });
3074
3426
  break;
3075
3427
  }
3076
- logger6.warn("Failed to pre-create Agent for recovery, clearing session", {
3428
+ logger7.warn("Failed to pre-create Agent for recovery, clearing session", {
3077
3429
  agentId: agent.id,
3078
3430
  error: err
3079
3431
  });
@@ -3097,7 +3449,7 @@ var AgentManager = class {
3097
3449
  } catch (err) {
3098
3450
  const errMsg = err.message ?? String(err);
3099
3451
  const isResumeFail = /session|conversation.*not found/i.test(errMsg);
3100
- logger6.error("Agent query stream ended with error", {
3452
+ logger7.error("Agent query stream ended with error", {
3101
3453
  agentId: runtime.agentId,
3102
3454
  scope: scopeKey(runtime.scope),
3103
3455
  isResumeFail,
@@ -3105,7 +3457,7 @@ var AgentManager = class {
3105
3457
  error: err
3106
3458
  });
3107
3459
  this.sessionStore.delete(runtime.agentId, runtime.scope);
3108
- logger6.info("Cleared stale session after query crash", {
3460
+ logger7.info("Cleared stale session after query crash", {
3109
3461
  agentId: runtime.agentId,
3110
3462
  scope: scopeKey(runtime.scope)
3111
3463
  });
@@ -3166,8 +3518,50 @@ var AgentManager = class {
3166
3518
  }
3167
3519
  return [...ids];
3168
3520
  }
3521
+ /**
3522
+ * Push a system notice to ALL active runtimes of the given agent.
3523
+ *
3524
+ * Working runtimes: injected mid-turn via InputController (merged into
3525
+ * the current turn, no extra result expected).
3526
+ * Ready/starting runtimes: dispatched as a lightweight task so any SDK
3527
+ * output has valid replyMessageId/conversationId to land on.
3528
+ */
3529
+ broadcastScopeNotice(agentId, notice) {
3530
+ let notified = 0;
3531
+ for (const [, proc] of this.agents) {
3532
+ if (proc.agentId !== agentId || proc.status === "dead") continue;
3533
+ const runtime = this.asRuntime(proc);
3534
+ if (proc.status === "working") {
3535
+ runtime.inputController.push(notice, runtime.ccSessionId ?? "");
3536
+ logger7.info("Scope notice injected mid-turn", {
3537
+ agentId,
3538
+ scope: scopeKey(proc.scope),
3539
+ noticeLen: notice.length
3540
+ });
3541
+ } else if (proc.status === "ready" || proc.status === "starting") {
3542
+ const task = {
3543
+ content: notice,
3544
+ replyMessageId: `msg_scopenotice_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
3545
+ conversationId: proc.currentTask?.conversationId ?? "",
3546
+ traceId: `tr_scopenotice_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
3547
+ groupId: proc.scope.kind === "group" ? proc.scope.groupId : void 0
3548
+ };
3549
+ this.dispatchToSDK(runtime, task);
3550
+ logger7.info("Scope notice dispatched to idle runtime", {
3551
+ agentId,
3552
+ scope: scopeKey(proc.scope),
3553
+ replyMessageId: task.replyMessageId
3554
+ });
3555
+ }
3556
+ notified++;
3557
+ }
3558
+ logger7.info("broadcastScopeNotice completed", {
3559
+ agentId,
3560
+ notifiedCount: notified
3561
+ });
3562
+ }
3169
3563
  async shutdownAll() {
3170
- logger6.info("Shutting down all Agent processes", { count: this.agents.size });
3564
+ logger7.info("Shutting down all Agent processes", { count: this.agents.size });
3171
3565
  this.askQuestionRegistry.cancelAll("agent_aborted");
3172
3566
  if (this.evictionTimer) {
3173
3567
  clearInterval(this.evictionTimer);
@@ -3179,9 +3573,9 @@ var AgentManager = class {
3179
3573
  runtime.inputController?.close();
3180
3574
  runtime.query?.return(void 0);
3181
3575
  proc.status = "dead";
3182
- logger6.info("Agent process shut down", { agentId: proc.agentId, scope: scopeKey(proc.scope), key });
3576
+ logger7.info("Agent process shut down", { agentId: proc.agentId, scope: scopeKey(proc.scope), key });
3183
3577
  } catch (err) {
3184
- logger6.error("Error shutting down Agent", { agentId: proc.agentId, error: err });
3578
+ logger7.error("Error shutting down Agent", { agentId: proc.agentId, error: err });
3185
3579
  }
3186
3580
  }
3187
3581
  this.agents.clear();
@@ -3198,13 +3592,13 @@ var AgentManager = class {
3198
3592
  }
3199
3593
  }
3200
3594
  if (!proc) {
3201
- logger6.warn("cancelReply: no active process for reply", { agentId, replyMessageId });
3595
+ logger7.warn("cancelReply: no active process for reply", { agentId, replyMessageId });
3202
3596
  return;
3203
3597
  }
3204
3598
  const runtime = this.asRuntime(proc);
3205
3599
  const key = runtimeKey(agentId, proc.scope);
3206
3600
  if (!runtime.currentTask || runtime.currentTask.replyMessageId !== replyMessageId) {
3207
- logger6.warn("cancelReply: replyMessageId mismatch", {
3601
+ logger7.warn("cancelReply: replyMessageId mismatch", {
3208
3602
  agentId,
3209
3603
  replyMessageId,
3210
3604
  expected: runtime.currentTask?.replyMessageId
@@ -3233,7 +3627,7 @@ var AgentManager = class {
3233
3627
  proc.status = "dead";
3234
3628
  this.agents.delete(key);
3235
3629
  this.lastUsedAt.delete(key);
3236
- logger6.info("cancelReply: process torn down", {
3630
+ logger7.info("cancelReply: process torn down", {
3237
3631
  agentId,
3238
3632
  scope: scopeKey(proc.scope),
3239
3633
  conversationId,
@@ -3242,10 +3636,10 @@ var AgentManager = class {
3242
3636
  try {
3243
3637
  runtime.inputController.close();
3244
3638
  } catch (err) {
3245
- logger6.error("cancelReply: inputController.close failed", { agentId, error: err });
3639
+ logger7.error("cancelReply: inputController.close failed", { agentId, error: err });
3246
3640
  }
3247
3641
  runtime.query.return(void 0).catch((err) => {
3248
- logger6.warn("cancelReply: query.return threw", { agentId, error: err });
3642
+ logger7.warn("cancelReply: query.return threw", { agentId, error: err });
3249
3643
  });
3250
3644
  }
3251
3645
  };
@@ -3262,7 +3656,7 @@ function buildInnerVoiceEnvelope(payload) {
3262
3656
  }
3263
3657
 
3264
3658
  // src/agentRegistry.ts
3265
- var logger7 = createModuleLogger("agent.registry");
3659
+ var logger8 = createModuleLogger("agent.registry");
3266
3660
  var HttpAgentRegistry = class {
3267
3661
  constructor(serverApiUrl) {
3268
3662
  this.serverApiUrl = serverApiUrl;
@@ -3271,8 +3665,8 @@ var HttpAgentRegistry = class {
3271
3665
  agents = /* @__PURE__ */ new Map();
3272
3666
  apiUrl(suffix) {
3273
3667
  const base = this.serverApiUrl.replace(/\/$/, "");
3274
- const path11 = suffix.startsWith("/") ? suffix : `/${suffix}`;
3275
- return `${base}${path11}`;
3668
+ const path13 = suffix.startsWith("/") ? suffix : `/${suffix}`;
3669
+ return `${base}${path13}`;
3276
3670
  }
3277
3671
  async refresh() {
3278
3672
  const attempt = async () => {
@@ -3290,17 +3684,17 @@ var HttpAgentRegistry = class {
3290
3684
  recoveredAfterRetry = res != null;
3291
3685
  }
3292
3686
  if (!res) {
3293
- logger7.warn("Agent registry refresh unreachable after retry, keeping cache");
3687
+ logger8.warn("Agent registry refresh unreachable after retry, keeping cache");
3294
3688
  return;
3295
3689
  }
3296
3690
  if (!res.ok) {
3297
- logger7.warn("Agent registry refresh failed", { status: res.status });
3691
+ logger8.warn("Agent registry refresh failed", { status: res.status });
3298
3692
  return;
3299
3693
  }
3300
3694
  try {
3301
3695
  const body = await res.json();
3302
3696
  if (!Array.isArray(body)) {
3303
- logger7.warn("Agent registry refresh: expected array");
3697
+ logger8.warn("Agent registry refresh: expected array");
3304
3698
  return;
3305
3699
  }
3306
3700
  this.agents.clear();
@@ -3310,9 +3704,9 @@ var HttpAgentRegistry = class {
3310
3704
  this.agents.set(a.id, a);
3311
3705
  }
3312
3706
  }
3313
- logger7.info("Agent registry refreshed", { count: this.agents.size, recoveredAfterRetry });
3707
+ logger8.info("Agent registry refreshed", { count: this.agents.size, recoveredAfterRetry });
3314
3708
  } catch (e) {
3315
- logger7.warn("Agent registry refresh parse failed", { error: e });
3709
+ logger8.warn("Agent registry refresh parse failed", { error: e });
3316
3710
  }
3317
3711
  }
3318
3712
  getById(id) {
@@ -3326,17 +3720,17 @@ var HttpAgentRegistry = class {
3326
3720
  try {
3327
3721
  const res = await fetch(this.apiUrl(`/api/agents/${encodeURIComponent(id)}`));
3328
3722
  if (!res.ok) {
3329
- logger7.warn("fetchById failed", { agentId: id, status: res.status });
3723
+ logger8.warn("fetchById failed", { agentId: id, status: res.status });
3330
3724
  return null;
3331
3725
  }
3332
3726
  const agent = await res.json();
3333
3727
  if (agent && typeof agent.id === "string") {
3334
3728
  this.agents.set(agent.id, agent);
3335
- logger7.info("Agent registry fetchById upserted", { agentId: id });
3729
+ logger8.info("Agent registry fetchById upserted", { agentId: id });
3336
3730
  }
3337
3731
  return agent;
3338
3732
  } catch (e) {
3339
- logger7.warn("fetchById unreachable", { agentId: id, error: e });
3733
+ logger8.warn("fetchById unreachable", { agentId: id, error: e });
3340
3734
  return null;
3341
3735
  }
3342
3736
  }
@@ -3345,16 +3739,16 @@ var HttpAgentRegistry = class {
3345
3739
  }
3346
3740
  upsert(agent) {
3347
3741
  this.agents.set(agent.id, agent);
3348
- logger7.debug("Agent registry upsert", { agentId: agent.id });
3742
+ logger8.debug("Agent registry upsert", { agentId: agent.id });
3349
3743
  }
3350
3744
  remove(agentId) {
3351
3745
  this.agents.delete(agentId);
3352
- logger7.debug("Agent registry remove", { agentId });
3746
+ logger8.debug("Agent registry remove", { agentId });
3353
3747
  }
3354
3748
  };
3355
3749
 
3356
3750
  // src/groupRegistry.ts
3357
- var logger8 = createModuleLogger("neural.groupRegistry");
3751
+ var logger9 = createModuleLogger("neural.groupRegistry");
3358
3752
  var GroupRegistry = class {
3359
3753
  groups = /* @__PURE__ */ new Map();
3360
3754
  serverApiUrl;
@@ -3377,17 +3771,17 @@ var GroupRegistry = class {
3377
3771
  recoveredAfterRetry = res != null;
3378
3772
  }
3379
3773
  if (!res) {
3380
- logger8.warn("GroupRegistry refresh unreachable after retry");
3774
+ logger9.warn("GroupRegistry refresh unreachable after retry");
3381
3775
  return;
3382
3776
  }
3383
3777
  if (!res.ok) {
3384
- logger8.warn("GroupRegistry refresh failed", { status: res.status });
3778
+ logger9.warn("GroupRegistry refresh failed", { status: res.status });
3385
3779
  return;
3386
3780
  }
3387
3781
  try {
3388
3782
  const body = await res.json();
3389
3783
  if (!Array.isArray(body)) {
3390
- logger8.warn("GroupRegistry refresh: expected array");
3784
+ logger9.warn("GroupRegistry refresh: expected array");
3391
3785
  return;
3392
3786
  }
3393
3787
  this.groups.clear();
@@ -3402,14 +3796,25 @@ var GroupRegistry = class {
3402
3796
  });
3403
3797
  }
3404
3798
  }
3405
- logger8.info("GroupRegistry refreshed", { count: this.groups.size, recoveredAfterRetry });
3799
+ logger9.info("GroupRegistry refreshed", { count: this.groups.size, recoveredAfterRetry });
3406
3800
  } catch (e) {
3407
- logger8.warn("GroupRegistry refresh parse failed", { error: e });
3801
+ logger9.warn("GroupRegistry refresh parse failed", { error: e });
3408
3802
  }
3409
3803
  }
3410
3804
  getById(groupId) {
3411
3805
  return this.groups.get(groupId) ?? null;
3412
3806
  }
3807
+ /**
3808
+ * Return the cached groups that the given agent is a member of.
3809
+ * Does NOT refresh — caller decides when to call refresh().
3810
+ */
3811
+ getMyGroups(agentId) {
3812
+ const out = [];
3813
+ for (const g of this.groups.values()) {
3814
+ if (g.members.includes(agentId)) out.push(g);
3815
+ }
3816
+ return out;
3817
+ }
3413
3818
  /**
3414
3819
  * Fuzzy match by name (case-insensitive substring).
3415
3820
  * Returns the first match.
@@ -3443,16 +3848,16 @@ var GroupRegistry = class {
3443
3848
  try {
3444
3849
  const res = await fetch(url);
3445
3850
  if (res.status === 404) {
3446
- logger8.info("GroupRegistry resolveScope: group not found", { rawScope, suffix });
3851
+ logger9.info("GroupRegistry resolveScope: group not found", { rawScope, suffix });
3447
3852
  return null;
3448
3853
  }
3449
3854
  if (!res.ok) {
3450
- logger8.warn("GroupRegistry resolveScope: HTTP error", { rawScope, status: res.status });
3855
+ logger9.warn("GroupRegistry resolveScope: HTTP error", { rawScope, status: res.status });
3451
3856
  return null;
3452
3857
  }
3453
3858
  const data = await res.json();
3454
3859
  if (!data.groupId || !data.conversationId || !data.workingDirectory) {
3455
- logger8.warn("GroupRegistry resolveScope: incomplete response", {
3860
+ logger9.warn("GroupRegistry resolveScope: incomplete response", {
3456
3861
  rawScope,
3457
3862
  hasGroupId: !!data.groupId,
3458
3863
  hasConversationId: !!data.conversationId,
@@ -3460,7 +3865,7 @@ var GroupRegistry = class {
3460
3865
  });
3461
3866
  return null;
3462
3867
  }
3463
- logger8.info("GroupRegistry resolved scope", {
3868
+ logger9.info("GroupRegistry resolved scope", {
3464
3869
  rawScope,
3465
3870
  resolvedGroupId: data.groupId,
3466
3871
  resolvedName: data.name ?? "(none)",
@@ -3475,7 +3880,7 @@ var GroupRegistry = class {
3475
3880
  workingDirectory: data.workingDirectory
3476
3881
  };
3477
3882
  } catch (e) {
3478
- logger8.error("GroupRegistry resolveScope error", { rawScope, error: e });
3883
+ logger9.error("GroupRegistry resolveScope error", { rawScope, error: e });
3479
3884
  return null;
3480
3885
  }
3481
3886
  }
@@ -3496,12 +3901,12 @@ var GroupRegistry = class {
3496
3901
  if (Array.isArray(body)) {
3497
3902
  const single = body.find((c) => c.type === "single" && typeof c.id === "string");
3498
3903
  if (single?.id) {
3499
- logger8.info("GroupRegistry resolved single conv", { agentId, conversationId: single.id });
3904
+ logger9.info("GroupRegistry resolved single conv", { agentId, conversationId: single.id });
3500
3905
  return single.id;
3501
3906
  }
3502
3907
  }
3503
3908
  } else {
3504
- logger8.warn("GroupRegistry resolveSingle: list failed", { agentId, status: res.status });
3909
+ logger9.warn("GroupRegistry resolveSingle: list failed", { agentId, status: res.status });
3505
3910
  }
3506
3911
  const created = await fetch(`${this.serverApiUrl}/api/conversations`, {
3507
3912
  method: "POST",
@@ -3509,26 +3914,27 @@ var GroupRegistry = class {
3509
3914
  body: JSON.stringify({ agentId })
3510
3915
  });
3511
3916
  if (!created.ok) {
3512
- logger8.warn("GroupRegistry resolveSingle: create failed", { agentId, status: created.status });
3917
+ logger9.warn("GroupRegistry resolveSingle: create failed", { agentId, status: created.status });
3513
3918
  return null;
3514
3919
  }
3515
3920
  const conv = await created.json();
3516
3921
  if (typeof conv.id !== "string") {
3517
- logger8.warn("GroupRegistry resolveSingle: created conv missing id", { agentId });
3922
+ logger9.warn("GroupRegistry resolveSingle: created conv missing id", { agentId });
3518
3923
  return null;
3519
3924
  }
3520
- logger8.info("GroupRegistry created single conv", { agentId, conversationId: conv.id });
3925
+ logger9.info("GroupRegistry created single conv", { agentId, conversationId: conv.id });
3521
3926
  return conv.id;
3522
3927
  } catch (e) {
3523
- logger8.error("GroupRegistry resolveSingle error", { agentId, error: e });
3928
+ logger9.error("GroupRegistry resolveSingle error", { agentId, error: e });
3524
3929
  return null;
3525
3930
  }
3526
3931
  }
3527
3932
  };
3528
3933
 
3529
3934
  // src/connector.ts
3935
+ import os5 from "os";
3530
3936
  import WebSocket from "ws";
3531
- var logger9 = createModuleLogger("ws.connector");
3937
+ var logger10 = createModuleLogger("ws.connector");
3532
3938
  var ServerConnector = class {
3533
3939
  ws = null;
3534
3940
  reconnectAttempts = 0;
@@ -3558,19 +3964,19 @@ var ServerConnector = class {
3558
3964
  url.searchParams.set("token", this.config.bridgeToken);
3559
3965
  }
3560
3966
  const wsUrl = url.toString();
3561
- logger9.info("Connecting to server", { url: this.config.serverUrl });
3967
+ logger10.info("Connecting to server", { url: wsUrl });
3562
3968
  const ws = new WebSocket(wsUrl);
3563
3969
  ws.on("open", () => {
3564
3970
  this.ws = ws;
3565
3971
  this.reconnectAttempts = 0;
3566
- logger9.info("Connected to server", { url: this.config.serverUrl });
3972
+ logger10.info("Connected to server", { url: this.config.serverUrl });
3567
3973
  void this.handleOpen();
3568
3974
  });
3569
3975
  ws.on("message", (data) => {
3570
3976
  this.handleMessage(data);
3571
3977
  });
3572
3978
  ws.on("close", (code, reason) => {
3573
- logger9.warn("Disconnected from server", {
3979
+ logger10.warn("Disconnected from server", {
3574
3980
  code,
3575
3981
  reason: reason.toString()
3576
3982
  });
@@ -3580,15 +3986,15 @@ var ServerConnector = class {
3580
3986
  }
3581
3987
  });
3582
3988
  ws.on("error", (err) => {
3583
- logger9.error("WebSocket error", { error: err });
3989
+ logger10.error("WebSocket error", { error: err });
3584
3990
  });
3585
3991
  }
3586
3992
  async handleOpen() {
3587
3993
  try {
3588
3994
  await this.onConnected();
3589
- logger9.info("Recovery complete, sending bridge:register");
3995
+ logger10.info("Recovery complete, sending bridge:register");
3590
3996
  } catch (err) {
3591
- logger9.error("Recovery failed, registering with degraded state", { error: err });
3997
+ logger10.error("Recovery failed, registering with degraded state", { error: err });
3592
3998
  }
3593
3999
  this.register();
3594
4000
  }
@@ -3600,13 +4006,14 @@ var ServerConnector = class {
3600
4006
  payload: {
3601
4007
  bridgeId: this.config.bridgeId,
3602
4008
  agents: ids,
4009
+ hostname: os5.hostname(),
3603
4010
  queryConfig: {
3604
4011
  maxActive: qc.maxActive,
3605
4012
  idleTimeoutMs: qc.idleTimeoutMs
3606
4013
  }
3607
4014
  }
3608
4015
  });
3609
- logger9.info("Sent bridge:register", {
4016
+ logger10.info("Sent bridge:register", {
3610
4017
  bridgeId: this.config.bridgeId,
3611
4018
  agents: ids
3612
4019
  });
@@ -3617,7 +4024,7 @@ var ServerConnector = class {
3617
4024
  const raw = typeof data === "string" ? data : data.toString("utf8");
3618
4025
  msg = parseWSMessage(raw);
3619
4026
  } catch (e) {
3620
- logger9.error("Invalid WS message from server", { error: e });
4027
+ logger10.error("Invalid WS message from server", { error: e });
3621
4028
  return;
3622
4029
  }
3623
4030
  wsMetrics.incRecv(msg.type);
@@ -3628,7 +4035,7 @@ var ServerConnector = class {
3628
4035
  }
3629
4036
  case "task:dispatch": {
3630
4037
  void this.onTaskDispatch(msg.payload).catch((err) => {
3631
- logger9.error("Failed to handle task:dispatch", {
4038
+ logger10.error("Failed to handle task:dispatch", {
3632
4039
  error: err,
3633
4040
  traceId: msg.payload.traceId
3634
4041
  });
@@ -3638,19 +4045,19 @@ var ServerConnector = class {
3638
4045
  case "task:group_dispatch": {
3639
4046
  if (this.onGroupTaskDispatch) {
3640
4047
  void this.onGroupTaskDispatch(msg.payload).catch((err) => {
3641
- logger9.error("Failed to handle task:group_dispatch", {
4048
+ logger10.error("Failed to handle task:group_dispatch", {
3642
4049
  error: err,
3643
4050
  traceId: msg.payload.traceId
3644
4051
  });
3645
4052
  });
3646
4053
  } else {
3647
- logger9.warn("Received task:group_dispatch but no handler registered");
4054
+ logger10.warn("Received task:group_dispatch but no handler registered");
3648
4055
  }
3649
4056
  return;
3650
4057
  }
3651
4058
  case "user:stop_generation": {
3652
4059
  void this.onStopGeneration(msg.payload).catch((err) => {
3653
- logger9.error("Failed to handle user:stop_generation", {
4060
+ logger10.error("Failed to handle user:stop_generation", {
3654
4061
  error: err,
3655
4062
  traceId: msg.payload.traceId
3656
4063
  });
@@ -3664,16 +4071,17 @@ var ServerConnector = class {
3664
4071
  case "agent:updated":
3665
4072
  case "agent:deleted":
3666
4073
  case "group:member_changed":
4074
+ case "group:updated":
3667
4075
  case "user:answer_question": {
3668
4076
  if (this.onServerPush) {
3669
4077
  void Promise.resolve(this.onServerPush(msg)).catch((err) => {
3670
- logger9.error("onServerPush handler failed", { error: err, type: msg.type });
4078
+ logger10.error("onServerPush handler failed", { error: err, type: msg.type });
3671
4079
  });
3672
4080
  }
3673
4081
  return;
3674
4082
  }
3675
4083
  default: {
3676
- logger9.warn("Unhandled server message type", {
4084
+ logger10.warn("Unhandled server message type", {
3677
4085
  type: msg.type
3678
4086
  });
3679
4087
  }
@@ -3681,7 +4089,7 @@ var ServerConnector = class {
3681
4089
  }
3682
4090
  send(msg) {
3683
4091
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
3684
- logger9.warn("Cannot send: WebSocket not open", {
4092
+ logger10.warn("Cannot send: WebSocket not open", {
3685
4093
  type: msg.type
3686
4094
  });
3687
4095
  return;
@@ -3690,14 +4098,14 @@ var ServerConnector = class {
3690
4098
  this.ws.send(JSON.stringify(msg));
3691
4099
  wsMetrics.incSend(msg.type);
3692
4100
  } catch (e) {
3693
- logger9.error("Failed to send WS message", { error: e, type: msg.type });
4101
+ logger10.error("Failed to send WS message", { error: e, type: msg.type });
3694
4102
  }
3695
4103
  }
3696
4104
  scheduleReconnect() {
3697
4105
  if (this.closing) return;
3698
4106
  const delay = this.delays[Math.min(this.reconnectAttempts, this.delays.length - 1)];
3699
4107
  this.reconnectAttempts++;
3700
- logger9.info("Scheduling reconnect", {
4108
+ logger10.info("Scheduling reconnect", {
3701
4109
  attempt: this.reconnectAttempts,
3702
4110
  delayMs: delay
3703
4111
  });
@@ -3713,11 +4121,11 @@ var ServerConnector = class {
3713
4121
  try {
3714
4122
  this.ws.close(1e3, "Bridge shutting down");
3715
4123
  } catch (e) {
3716
- logger9.error("Error closing WebSocket", { error: e });
4124
+ logger10.error("Error closing WebSocket", { error: e });
3717
4125
  }
3718
4126
  this.ws = null;
3719
4127
  }
3720
- logger9.info("Connector closed");
4128
+ logger10.info("Connector closed");
3721
4129
  }
3722
4130
  get isConnected() {
3723
4131
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
@@ -3725,14 +4133,14 @@ var ServerConnector = class {
3725
4133
  };
3726
4134
 
3727
4135
  // src/modelQuerier.ts
3728
- import fs3 from "fs/promises";
3729
- import os5 from "os";
3730
- import path7 from "path";
3731
- var logger10 = createModuleLogger("bridge.modelQuerier");
4136
+ import fs4 from "fs/promises";
4137
+ import os6 from "os";
4138
+ import path8 from "path";
4139
+ var logger11 = createModuleLogger("bridge.modelQuerier");
3732
4140
  async function listModels(queryFn, opts = {}) {
3733
4141
  const t0 = Date.now();
3734
- const cwd = opts.cwd ?? path7.join(os5.homedir(), ".ahchat", "workspaces", "_list_models");
3735
- await fs3.mkdir(cwd, { recursive: true });
4142
+ const cwd = opts.cwd ?? path8.join(os6.homedir(), ".ahchat", "workspaces", "_list_models");
4143
+ await fs4.mkdir(cwd, { recursive: true });
3736
4144
  const fn = queryFn ?? (await import("@anthropic-ai/claude-agent-sdk")).query;
3737
4145
  const ic = new InputController();
3738
4146
  ic.push("Reply with exactly: PING", "");
@@ -3774,7 +4182,7 @@ async function listModels(queryFn, opts = {}) {
3774
4182
  displayName: m.displayName,
3775
4183
  description: m.description
3776
4184
  }));
3777
- logger10.info("listModels done", { count: models.length, ms: Date.now() - t0 });
4185
+ logger11.info("listModels done", { count: models.length, ms: Date.now() - t0 });
3778
4186
  return models;
3779
4187
  } finally {
3780
4188
  try {
@@ -3789,9 +4197,9 @@ async function listModels(queryFn, opts = {}) {
3789
4197
  }
3790
4198
 
3791
4199
  // src/lockfile.ts
3792
- import fs4 from "fs";
3793
- import path8 from "path";
3794
- var logger11 = createModuleLogger("bridge.lockfile");
4200
+ import fs5 from "fs";
4201
+ import path9 from "path";
4202
+ var logger12 = createModuleLogger("bridge.lockfile");
3795
4203
  var lockPath = null;
3796
4204
  function isProcessAlive(pid) {
3797
4205
  try {
@@ -3804,32 +4212,32 @@ function isProcessAlive(pid) {
3804
4212
  }
3805
4213
  }
3806
4214
  function acquireLock(dataDir) {
3807
- const file = path8.join(dataDir, "bridge.lock");
4215
+ const file = path9.join(dataDir, "bridge.lock");
3808
4216
  lockPath = file;
3809
- if (fs4.existsSync(file)) {
3810
- const raw = fs4.readFileSync(file, "utf-8").trim();
4217
+ if (fs5.existsSync(file)) {
4218
+ const raw = fs5.readFileSync(file, "utf-8").trim();
3811
4219
  const pid = Number.parseInt(raw, 10);
3812
4220
  if (Number.isFinite(pid) && pid > 0) {
3813
4221
  if (isProcessAlive(pid)) {
3814
4222
  throw new Error(`Bridge already running (PID: ${pid})`);
3815
4223
  }
3816
- logger11.warn("Removing stale bridge.lock (process not found)", { pid, path: file });
4224
+ logger12.warn("Removing stale bridge.lock (process not found)", { pid, path: file });
3817
4225
  }
3818
4226
  }
3819
- fs4.mkdirSync(path8.dirname(file), { recursive: true });
3820
- fs4.writeFileSync(file, String(process.pid), "utf-8");
3821
- logger11.info("Acquired bridge lock", { path: file, pid: process.pid });
4227
+ fs5.mkdirSync(path9.dirname(file), { recursive: true });
4228
+ fs5.writeFileSync(file, String(process.pid), "utf-8");
4229
+ logger12.info("Acquired bridge lock", { path: file, pid: process.pid });
3822
4230
  const release = () => {
3823
4231
  try {
3824
- if (lockPath && fs4.existsSync(lockPath)) {
3825
- const current = fs4.readFileSync(lockPath, "utf-8").trim();
4232
+ if (lockPath && fs5.existsSync(lockPath)) {
4233
+ const current = fs5.readFileSync(lockPath, "utf-8").trim();
3826
4234
  if (current === String(process.pid)) {
3827
- fs4.unlinkSync(lockPath);
3828
- logger11.info("Released bridge lock", { path: lockPath });
4235
+ fs5.unlinkSync(lockPath);
4236
+ logger12.info("Released bridge lock", { path: lockPath });
3829
4237
  }
3830
4238
  }
3831
4239
  } catch (e) {
3832
- logger11.error("Failed to release bridge lock", { error: e, path: lockPath });
4240
+ logger12.error("Failed to release bridge lock", { error: e, path: lockPath });
3833
4241
  } finally {
3834
4242
  lockPath = null;
3835
4243
  }
@@ -3932,9 +4340,9 @@ function buildGroupPrompt(payload) {
3932
4340
  }
3933
4341
 
3934
4342
  // src/messageHandler.ts
3935
- var logger12 = createModuleLogger("msg.handler");
4343
+ var logger13 = createModuleLogger("msg.handler");
3936
4344
  function emitTaskAck(emit, ackId, agentId, traceId) {
3937
- logger12.info("Emitting task:ack", { ackId, agentId, traceId });
4345
+ logger13.info("Emitting task:ack", { ackId, agentId, traceId });
3938
4346
  emit({
3939
4347
  type: "task:ack",
3940
4348
  payload: {
@@ -3947,7 +4355,7 @@ function emitTaskAck(emit, ackId, agentId, traceId) {
3947
4355
  }
3948
4356
  function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
3949
4357
  return async (payload) => {
3950
- logger12.info("Handling task:dispatch", {
4358
+ logger13.info("Handling task:dispatch", {
3951
4359
  agentId: payload.agentId,
3952
4360
  messageId: payload.messageId,
3953
4361
  ackId: payload.ackId,
@@ -3956,14 +4364,14 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
3956
4364
  emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
3957
4365
  let agentConfig = agentRegistry.getById(payload.agentId);
3958
4366
  if (!agentConfig) {
3959
- logger12.warn("Agent not in registry, attempting live fetch", {
4367
+ logger13.warn("Agent not in registry, attempting live fetch", {
3960
4368
  agentId: payload.agentId,
3961
4369
  traceId: payload.traceId
3962
4370
  });
3963
4371
  agentConfig = await agentRegistry.fetchById(payload.agentId);
3964
4372
  }
3965
4373
  if (!agentConfig) {
3966
- logger12.error("Agent not found for task:dispatch (after live fetch)", {
4374
+ logger13.error("Agent not found for task:dispatch (after live fetch)", {
3967
4375
  agentId: payload.agentId,
3968
4376
  traceId: payload.traceId
3969
4377
  });
@@ -3990,7 +4398,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
3990
4398
  traceId: payload.traceId
3991
4399
  });
3992
4400
  } catch (err) {
3993
- logger12.error("Failed to dispatch message to Agent", {
4401
+ logger13.error("Failed to dispatch message to Agent", {
3994
4402
  error: err,
3995
4403
  agentId: payload.agentId,
3996
4404
  traceId: payload.traceId
@@ -4010,7 +4418,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
4010
4418
  }
4011
4419
  function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4012
4420
  return async (payload) => {
4013
- logger12.info("Handling task:group_dispatch", {
4421
+ logger13.info("Handling task:group_dispatch", {
4014
4422
  agentId: payload.agentId,
4015
4423
  groupId: payload.groupId,
4016
4424
  ackId: payload.ackId,
@@ -4022,14 +4430,14 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4022
4430
  emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
4023
4431
  let agentConfig = agentRegistry.getById(payload.agentId);
4024
4432
  if (!agentConfig) {
4025
- logger12.warn("Agent not in registry for group dispatch, attempting live fetch", {
4433
+ logger13.warn("Agent not in registry for group dispatch, attempting live fetch", {
4026
4434
  agentId: payload.agentId,
4027
4435
  traceId: payload.traceId
4028
4436
  });
4029
4437
  agentConfig = await agentRegistry.fetchById(payload.agentId);
4030
4438
  }
4031
4439
  if (!agentConfig) {
4032
- logger12.error("Agent not found for task:group_dispatch (after live fetch)", {
4440
+ logger13.error("Agent not found for task:group_dispatch (after live fetch)", {
4033
4441
  agentId: payload.agentId,
4034
4442
  traceId: payload.traceId
4035
4443
  });
@@ -4062,7 +4470,7 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4062
4470
  groupId: payload.groupId
4063
4471
  });
4064
4472
  } catch (err) {
4065
- logger12.error("Failed to dispatch group message to Agent", {
4473
+ logger13.error("Failed to dispatch group message to Agent", {
4066
4474
  error: err,
4067
4475
  agentId: payload.agentId,
4068
4476
  groupId: payload.groupId,
@@ -4082,15 +4490,84 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4082
4490
  };
4083
4491
  }
4084
4492
 
4493
+ // src/scopePushNotify.ts
4494
+ var logger14 = createModuleLogger("bridge");
4495
+ function buildMemberChangedScopeNotice(params) {
4496
+ const { groupId, groupLabel, action } = params;
4497
+ const verb = action === "added" ? "\u52A0\u5165" : "\u79FB\u51FA";
4498
+ return [
4499
+ `[\u7CFB\u7EDF\u901A\u77E5] \u4F60\u5DF2\u88AB${verb}\u7FA4\u300C${groupLabel}\u300D(group:${groupId})\u3002`,
4500
+ `\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`,
4501
+ `\u65E0\u9700\u56DE\u590D\u6B64\u901A\u77E5\u3002`
4502
+ ].join("\n");
4503
+ }
4504
+ function buildGroupRenamedScopeNotice(params) {
4505
+ const { groupId, newName } = params;
4506
+ return [
4507
+ `[\u7CFB\u7EDF\u901A\u77E5] \u7FA4 (group:${groupId}) \u5DF2\u66F4\u540D\u4E3A\u300C${newName}\u300D\u3002`,
4508
+ `\u82E5\u4F60\u4E4B\u524D\u7528\u65E7\u7FA4\u540D\u8C03\u8FC7 neural_send\uFF0C\u8BF7\u66F4\u65B0\u4E3A\u65B0\u540D\u79F0\u3002`,
4509
+ `\u65E0\u9700\u56DE\u590D\u6B64\u901A\u77E5\u3002`
4510
+ ].join("\n");
4511
+ }
4512
+ async function handleGroupMemberChangedPush(deps, payload) {
4513
+ const { groupId, action, agentId } = payload;
4514
+ logger14.info("group:member_changed received, refreshing GroupRegistry", {
4515
+ groupId,
4516
+ action,
4517
+ agentId
4518
+ });
4519
+ await deps.groupRegistry.refresh();
4520
+ logger14.info("GroupRegistry refreshed after member_changed", {
4521
+ groupId,
4522
+ action,
4523
+ registryGroupCount: deps.groupRegistry.getAll().length
4524
+ });
4525
+ const group = deps.groupRegistry.getById(groupId);
4526
+ const groupLabel = group?.name ?? groupId;
4527
+ const notice = buildMemberChangedScopeNotice({ groupId, groupLabel, action });
4528
+ deps.agentManager.broadcastScopeNotice(agentId, notice);
4529
+ logger14.info("Scope notice sent for member_changed", {
4530
+ agentId,
4531
+ groupId,
4532
+ groupLabel,
4533
+ action,
4534
+ noticeLen: notice.length
4535
+ });
4536
+ }
4537
+ async function handleGroupUpdatedPush(deps, payload) {
4538
+ const { groupId, name: newName, memberAgentIds } = payload;
4539
+ logger14.info("group:updated received, refreshing GroupRegistry", {
4540
+ groupId,
4541
+ newName,
4542
+ memberCount: memberAgentIds.length
4543
+ });
4544
+ await deps.groupRegistry.refresh();
4545
+ logger14.info("GroupRegistry refreshed after group:updated", {
4546
+ groupId,
4547
+ newName,
4548
+ registryGroupCount: deps.groupRegistry.getAll().length
4549
+ });
4550
+ const notice = buildGroupRenamedScopeNotice({ groupId, newName });
4551
+ for (const aid of memberAgentIds) {
4552
+ deps.agentManager.broadcastScopeNotice(aid, notice);
4553
+ }
4554
+ logger14.info("Scope notices sent for group:updated", {
4555
+ groupId,
4556
+ newName,
4557
+ memberCount: memberAgentIds.length,
4558
+ noticeLen: notice.length
4559
+ });
4560
+ }
4561
+
4085
4562
  // src/sessionStore.ts
4086
- import fs5 from "fs";
4087
- import path9 from "path";
4088
- var logger13 = createModuleLogger("session.store");
4563
+ import fs6 from "fs";
4564
+ import path10 from "path";
4565
+ var logger15 = createModuleLogger("session.store");
4089
4566
  var SessionStore = class {
4090
4567
  filePath;
4091
4568
  cache;
4092
4569
  constructor(dataDir) {
4093
- this.filePath = path9.join(dataDir, "sessions.json");
4570
+ this.filePath = path10.join(dataDir, "sessions.json");
4094
4571
  this.cache = this.loadFromDisk();
4095
4572
  }
4096
4573
  cacheKey(agentId, scope) {
@@ -4125,8 +4602,8 @@ var SessionStore = class {
4125
4602
  }
4126
4603
  loadFromDisk() {
4127
4604
  try {
4128
- if (!fs5.existsSync(this.filePath)) return {};
4129
- const raw = fs5.readFileSync(this.filePath, "utf-8");
4605
+ if (!fs6.existsSync(this.filePath)) return {};
4606
+ const raw = fs6.readFileSync(this.filePath, "utf-8");
4130
4607
  const parsed = JSON.parse(raw);
4131
4608
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
4132
4609
  const map = parsed;
@@ -4136,7 +4613,7 @@ var SessionStore = class {
4136
4613
  migrated[key] = sessionId;
4137
4614
  } else {
4138
4615
  migrated[`${key}::single`] = sessionId;
4139
- logger13.info("Migrated legacy session key to scoped key", {
4616
+ logger15.info("Migrated legacy session key to scoped key", {
4140
4617
  legacyKey: key,
4141
4618
  newKey: `${key}::single`
4142
4619
  });
@@ -4144,29 +4621,29 @@ var SessionStore = class {
4144
4621
  }
4145
4622
  return migrated;
4146
4623
  } catch (e) {
4147
- logger13.warn("Failed to load sessions file, starting fresh", { error: e, path: this.filePath });
4624
+ logger15.warn("Failed to load sessions file, starting fresh", { error: e, path: this.filePath });
4148
4625
  return {};
4149
4626
  }
4150
4627
  }
4151
4628
  saveToDisk() {
4152
4629
  try {
4153
- const dir = path9.dirname(this.filePath);
4154
- fs5.mkdirSync(dir, { recursive: true });
4155
- fs5.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), "utf-8");
4630
+ const dir = path10.dirname(this.filePath);
4631
+ fs6.mkdirSync(dir, { recursive: true });
4632
+ fs6.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), "utf-8");
4156
4633
  } catch (e) {
4157
- logger13.error("Failed to save sessions file", { error: e, path: this.filePath });
4634
+ logger15.error("Failed to save sessions file", { error: e, path: this.filePath });
4158
4635
  }
4159
4636
  }
4160
4637
  };
4161
4638
 
4162
4639
  // src/start.ts
4163
- var logger14 = createModuleLogger("bridge");
4640
+ var logger16 = createModuleLogger("bridge");
4164
4641
  async function startBridge(config) {
4165
4642
  ensureDir(config.dataDir);
4166
4643
  ensureDir(config.claudeConfigDir);
4167
4644
  process.env.CLAUDE_CONFIG_DIR = config.claudeConfigDir;
4168
4645
  acquireLock(config.dataDir);
4169
- logger14.info("Bridge starting", {
4646
+ logger16.info("Bridge starting", {
4170
4647
  bridgeId: config.bridgeId,
4171
4648
  serverUrl: config.serverUrl,
4172
4649
  serverApiUrl: config.serverApiUrl,
@@ -4174,6 +4651,9 @@ async function startBridge(config) {
4174
4651
  });
4175
4652
  wsMetrics.start(5e3);
4176
4653
  const sessionStore = new SessionStore(config.dataDir);
4654
+ const memoryRoot = path11.join(config.dataDir, "agent-memory");
4655
+ const memoryStore = new AgentMemoryStore(memoryRoot);
4656
+ logger16.info("Agent memory store initialized", { rootDir: memoryRoot });
4177
4657
  const agentRegistry = new HttpAgentRegistry(config.serverApiUrl);
4178
4658
  const groupRegistry = new GroupRegistry(config.serverApiUrl);
4179
4659
  await agentRegistry.refresh();
@@ -4187,7 +4667,8 @@ async function startBridge(config) {
4187
4667
  queryConfig: config.queryConfig,
4188
4668
  claudeConfigDir: config.claudeConfigDir,
4189
4669
  askQuestionRegistry,
4190
- groupRegistry
4670
+ groupRegistry,
4671
+ memoryStore
4191
4672
  });
4192
4673
  const taskDispatchHandler = createTaskDispatchHandler(agentManager, agentRegistry, emit);
4193
4674
  const groupTaskDispatchHandler = createGroupTaskDispatchHandler(agentManager, agentRegistry, emit);
@@ -4209,21 +4690,21 @@ async function startBridge(config) {
4209
4690
  switch (msg.type) {
4210
4691
  case "bridge:list_models_request": {
4211
4692
  const { requestId } = msg.payload;
4212
- logger14.info("list_models request received", { requestId });
4693
+ logger16.info("list_models request received", { requestId });
4213
4694
  try {
4214
4695
  const models = await listModels();
4215
4696
  connector?.send({
4216
4697
  type: "bridge:list_models_response",
4217
4698
  payload: { requestId, models }
4218
4699
  });
4219
- logger14.info("list_models response sent", { requestId, count: models.length });
4700
+ logger16.info("list_models response sent", { requestId, count: models.length });
4220
4701
  } catch (e) {
4221
4702
  const err = e instanceof Error ? e.message : String(e);
4222
4703
  connector?.send({
4223
4704
  type: "bridge:list_models_response",
4224
4705
  payload: { requestId, error: err }
4225
4706
  });
4226
- logger14.error("list_models failed", { requestId, error: e });
4707
+ logger16.error("list_models failed", { requestId, error: e });
4227
4708
  }
4228
4709
  break;
4229
4710
  }
@@ -4231,7 +4712,7 @@ async function startBridge(config) {
4231
4712
  await agentManager.terminate(msg.payload.agentId);
4232
4713
  break;
4233
4714
  case "agent:terminate_scope":
4234
- logger14.info("agent:terminate_scope received", {
4715
+ logger16.info("agent:terminate_scope received", {
4235
4716
  agentId: msg.payload.agentId,
4236
4717
  scope: msg.payload.scope
4237
4718
  });
@@ -4244,11 +4725,31 @@ async function startBridge(config) {
4244
4725
  case "agent:deleted":
4245
4726
  agentRegistry.remove(msg.payload.agentId);
4246
4727
  break;
4728
+ case "group:member_changed":
4729
+ await handleGroupMemberChangedPush(
4730
+ { groupRegistry, agentManager },
4731
+ {
4732
+ groupId: msg.payload.groupId,
4733
+ action: msg.payload.action,
4734
+ agentId: msg.payload.agentId
4735
+ }
4736
+ );
4737
+ break;
4738
+ case "group:updated":
4739
+ await handleGroupUpdatedPush(
4740
+ { groupRegistry, agentManager },
4741
+ {
4742
+ groupId: msg.payload.groupId,
4743
+ name: msg.payload.name,
4744
+ memberAgentIds: msg.payload.memberAgentIds
4745
+ }
4746
+ );
4747
+ break;
4247
4748
  case "user:answer_question": {
4248
4749
  const p = msg.payload;
4249
4750
  const answerText = formatAnswerForSDK(p);
4250
4751
  const ok = askQuestionRegistry.resolve(p.questionId, answerText);
4251
- logger14.info("user:answer_question handled", {
4752
+ logger16.info("user:answer_question handled", {
4252
4753
  questionId: p.questionId,
4253
4754
  agentId: p.agentId,
4254
4755
  resolved: ok,
@@ -4269,7 +4770,7 @@ async function startBridge(config) {
4269
4770
  });
4270
4771
  }, config.queryConfig.statusReportIntervalMs);
4271
4772
  const shutdown = async (signal) => {
4272
- logger14.info("Shutdown signal received", { signal });
4773
+ logger16.info("Shutdown signal received", { signal });
4273
4774
  if (statusInterval) {
4274
4775
  clearInterval(statusInterval);
4275
4776
  statusInterval = null;
@@ -4277,7 +4778,7 @@ async function startBridge(config) {
4277
4778
  wsMetrics.stop();
4278
4779
  connector?.close();
4279
4780
  await agentManager.shutdownAll();
4280
- logger14.info("Bridge stopped");
4781
+ logger16.info("Bridge stopped");
4281
4782
  process.exit(0);
4282
4783
  };
4283
4784
  process.on("SIGINT", () => void shutdown("SIGINT"));
@@ -4286,19 +4787,19 @@ async function startBridge(config) {
4286
4787
 
4287
4788
  // src/protocol.ts
4288
4789
  import { execSync } from "child_process";
4289
- import fs6 from "fs";
4290
- import os6 from "os";
4291
- import path10 from "path";
4790
+ import fs7 from "fs";
4791
+ import os7 from "os";
4792
+ import path12 from "path";
4292
4793
  import { fileURLToPath } from "url";
4293
- var logger15 = createModuleLogger("bridge.protocol");
4794
+ var logger17 = createModuleLogger("bridge.protocol");
4294
4795
  var __filename = fileURLToPath(import.meta.url);
4295
- var __dirname = path10.dirname(__filename);
4796
+ var __dirname = path12.dirname(__filename);
4296
4797
  function getBridgeExePath() {
4297
- const pkgDir = path10.resolve(__dirname, "..");
4298
- return path10.join(pkgDir, "dist", "cli.js");
4798
+ const pkgDir = path12.resolve(__dirname, "..");
4799
+ return path12.join(pkgDir, "dist", "cli.js");
4299
4800
  }
4300
4801
  function registerProtocolHandler() {
4301
- const platform = os6.platform();
4802
+ const platform = os7.platform();
4302
4803
  if (platform === "win32") {
4303
4804
  registerWindows();
4304
4805
  } else if (platform === "darwin") {
@@ -4306,15 +4807,15 @@ function registerProtocolHandler() {
4306
4807
  } else {
4307
4808
  registerLinux();
4308
4809
  }
4309
- logger15.info("Protocol handler registered", { platform });
4810
+ logger17.info("Protocol handler registered", { platform });
4310
4811
  }
4311
4812
  function registerWindows() {
4312
4813
  const exe = getBridgeExePath();
4313
4814
  const nodeExe = process.execPath;
4314
- const ahchatDir = path10.join(os6.homedir(), ".ahchat");
4315
- const urlFilePath = path10.join(ahchatDir, ".bridge-launch-url");
4316
- fs6.mkdirSync(ahchatDir, { recursive: true });
4317
- const psScriptPath = path10.join(ahchatDir, "launch-bridge.ps1");
4815
+ const ahchatDir = path12.join(os7.homedir(), ".ahchat");
4816
+ const urlFilePath = path12.join(ahchatDir, ".bridge-launch-url");
4817
+ fs7.mkdirSync(ahchatDir, { recursive: true });
4818
+ const psScriptPath = path12.join(ahchatDir, "launch-bridge.ps1");
4318
4819
  const psContent = `param([string]$url)
4319
4820
  if (-not $url) {
4320
4821
  if (Test-Path '${urlFilePath}') {
@@ -4327,7 +4828,7 @@ if (-not $url) {
4327
4828
  }
4328
4829
  & '${nodeExe}' '${exe}' launch --url $url
4329
4830
  `;
4330
- fs6.writeFileSync(psScriptPath, psContent);
4831
+ fs7.writeFileSync(psScriptPath, psContent);
4331
4832
  const handler = `powershell -ExecutionPolicy Bypass -File "${psScriptPath}" -url "%1"`;
4332
4833
  const regCommands = [
4333
4834
  `REG ADD "HKCU\\Software\\Classes\\ahchat" /ve /d "URL:ahchat" /f`,
@@ -4339,19 +4840,19 @@ if (-not $url) {
4339
4840
  try {
4340
4841
  execSync(cmd, { stdio: "pipe" });
4341
4842
  } catch (e) {
4342
- logger15.error("Failed to register Windows protocol handler", { error: e, cmd });
4843
+ logger17.error("Failed to register Windows protocol handler", { error: e, cmd });
4343
4844
  throw new Error(`Failed to register protocol handler: ${cmd}`);
4344
4845
  }
4345
4846
  }
4346
- logger15.info("Windows protocol handler registered", { psScriptPath });
4847
+ logger17.info("Windows protocol handler registered", { psScriptPath });
4347
4848
  }
4348
4849
  function registerMacOS() {
4349
- const appDir = path10.join(os6.homedir(), "Applications", "AHChatBridge.app");
4350
- const contentsDir = path10.join(appDir, "Contents");
4351
- const macosDir = path10.join(contentsDir, "MacOS");
4352
- const resourcesDir = path10.join(contentsDir, "Resources");
4353
- fs6.mkdirSync(macosDir, { recursive: true });
4354
- fs6.mkdirSync(resourcesDir, { recursive: true });
4850
+ const appDir = path12.join(os7.homedir(), "Applications", "AHChatBridge.app");
4851
+ const contentsDir = path12.join(appDir, "Contents");
4852
+ const macosDir = path12.join(contentsDir, "MacOS");
4853
+ const resourcesDir = path12.join(contentsDir, "Resources");
4854
+ fs7.mkdirSync(macosDir, { recursive: true });
4855
+ fs7.mkdirSync(resourcesDir, { recursive: true });
4355
4856
  const infoPlist = `<?xml version="1.0" encoding="UTF-8"?>
4356
4857
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4357
4858
  <plist version="1.0">
@@ -4384,10 +4885,10 @@ function registerMacOS() {
4384
4885
  const launchScript = `#!/bin/bash
4385
4886
  URL="$1"
4386
4887
  exec "${process.execPath}" "${getBridgeExePath()}" launch --url "$URL"`;
4387
- fs6.writeFileSync(path10.join(contentsDir, "Info.plist"), infoPlist);
4388
- fs6.writeFileSync(path10.join(macosDir, "launch.sh"), launchScript);
4389
- fs6.chmodSync(path10.join(macosDir, "launch.sh"), 493);
4390
- logger15.info("macOS protocol handler registered", { appDir });
4888
+ fs7.writeFileSync(path12.join(contentsDir, "Info.plist"), infoPlist);
4889
+ fs7.writeFileSync(path12.join(macosDir, "launch.sh"), launchScript);
4890
+ fs7.chmodSync(path12.join(macosDir, "launch.sh"), 493);
4891
+ logger17.info("macOS protocol handler registered", { appDir });
4391
4892
  }
4392
4893
  function registerLinux() {
4393
4894
  const desktopFile = `[Desktop Entry]
@@ -4396,54 +4897,54 @@ Exec=${process.execPath} ${getBridgeExePath()} launch --url %u
4396
4897
  Type=Application
4397
4898
  NoDisplay=true
4398
4899
  MimeType=x-scheme-handler/ahchat;`;
4399
- const desktopPath = path10.join(os6.homedir(), ".local", "share", "applications", "ahchat-bridge.desktop");
4400
- fs6.mkdirSync(path10.dirname(desktopPath), { recursive: true });
4401
- fs6.writeFileSync(desktopPath, desktopFile);
4900
+ const desktopPath = path12.join(os7.homedir(), ".local", "share", "applications", "ahchat-bridge.desktop");
4901
+ fs7.mkdirSync(path12.dirname(desktopPath), { recursive: true });
4902
+ fs7.writeFileSync(desktopPath, desktopFile);
4402
4903
  try {
4403
4904
  execSync("update-desktop-database ~/.local/share/applications/", { stdio: "pipe" });
4404
4905
  } catch {
4405
4906
  }
4406
- logger15.info("Linux protocol handler registered", { desktopPath });
4907
+ logger17.info("Linux protocol handler registered", { desktopPath });
4407
4908
  }
4408
4909
  function unregisterProtocolHandler() {
4409
- const platform = os6.platform();
4910
+ const platform = os7.platform();
4410
4911
  if (platform === "win32") {
4411
4912
  try {
4412
4913
  execSync('REG DELETE "HKCU\\Software\\Classes\\ahchat" /f', { stdio: "pipe" });
4413
- const psScriptPath = path10.join(os6.homedir(), ".ahchat", "launch-bridge.ps1");
4414
- const urlFilePath = path10.join(os6.homedir(), ".ahchat", ".bridge-launch-url");
4914
+ const psScriptPath = path12.join(os7.homedir(), ".ahchat", "launch-bridge.ps1");
4915
+ const urlFilePath = path12.join(os7.homedir(), ".ahchat", ".bridge-launch-url");
4415
4916
  try {
4416
- fs6.unlinkSync(psScriptPath);
4917
+ fs7.unlinkSync(psScriptPath);
4417
4918
  } catch {
4418
4919
  }
4419
4920
  try {
4420
- fs6.unlinkSync(urlFilePath);
4921
+ fs7.unlinkSync(urlFilePath);
4421
4922
  } catch {
4422
4923
  }
4423
- logger15.info("Windows protocol handler unregistered");
4924
+ logger17.info("Windows protocol handler unregistered");
4424
4925
  } catch (e) {
4425
- logger15.warn("Failed to unregister Windows protocol handler", { error: e });
4926
+ logger17.warn("Failed to unregister Windows protocol handler", { error: e });
4426
4927
  }
4427
4928
  } else if (platform === "darwin") {
4428
- const appDir = path10.join(os6.homedir(), "Applications", "AHChatBridge.app");
4929
+ const appDir = path12.join(os7.homedir(), "Applications", "AHChatBridge.app");
4429
4930
  try {
4430
- fs6.rmSync(appDir, { recursive: true, force: true });
4431
- logger15.info("macOS protocol handler unregistered");
4931
+ fs7.rmSync(appDir, { recursive: true, force: true });
4932
+ logger17.info("macOS protocol handler unregistered");
4432
4933
  } catch (e) {
4433
- logger15.warn("Failed to unregister macOS protocol handler", { error: e });
4934
+ logger17.warn("Failed to unregister macOS protocol handler", { error: e });
4434
4935
  }
4435
4936
  } else {
4436
- const desktopPath = path10.join(os6.homedir(), ".local", "share", "applications", "ahchat-bridge.desktop");
4937
+ const desktopPath = path12.join(os7.homedir(), ".local", "share", "applications", "ahchat-bridge.desktop");
4437
4938
  try {
4438
- fs6.unlinkSync(desktopPath);
4439
- logger15.info("Linux protocol handler unregistered");
4939
+ fs7.unlinkSync(desktopPath);
4940
+ logger17.info("Linux protocol handler unregistered");
4440
4941
  } catch (e) {
4441
- logger15.warn("Failed to unregister Linux protocol handler", { error: e });
4942
+ logger17.warn("Failed to unregister Linux protocol handler", { error: e });
4442
4943
  }
4443
4944
  }
4444
4945
  }
4445
4946
  function isProtocolRegistered() {
4446
- const platform = os6.platform();
4947
+ const platform = os7.platform();
4447
4948
  if (platform === "win32") {
4448
4949
  try {
4449
4950
  execSync('REG QUERY "HKCU\\Software\\Classes\\ahchat" /ve', { stdio: "pipe" });
@@ -4452,16 +4953,16 @@ function isProtocolRegistered() {
4452
4953
  return false;
4453
4954
  }
4454
4955
  } else if (platform === "darwin") {
4455
- const appDir = path10.join(os6.homedir(), "Applications", "AHChatBridge.app");
4456
- return fs6.existsSync(path10.join(appDir, "Contents", "Info.plist"));
4956
+ const appDir = path12.join(os7.homedir(), "Applications", "AHChatBridge.app");
4957
+ return fs7.existsSync(path12.join(appDir, "Contents", "Info.plist"));
4457
4958
  } else {
4458
- const desktopPath = path10.join(os6.homedir(), ".local", "share", "applications", "ahchat-bridge.desktop");
4459
- return fs6.existsSync(desktopPath);
4959
+ const desktopPath = path12.join(os7.homedir(), ".local", "share", "applications", "ahchat-bridge.desktop");
4960
+ return fs7.existsSync(desktopPath);
4460
4961
  }
4461
4962
  }
4462
4963
 
4463
4964
  // src/cli.ts
4464
- var logger16 = createModuleLogger("bridge");
4965
+ var logger18 = createModuleLogger("bridge");
4465
4966
  function parseAhchatUrl(url) {
4466
4967
  try {
4467
4968
  if (!url.startsWith("ahchat://")) return null;
@@ -4481,12 +4982,12 @@ function parseAhchatUrl(url) {
4481
4982
  }
4482
4983
  async function run(args) {
4483
4984
  let config = loadBridgeConfig();
4484
- if (args.serverUrl) config = { ...config, serverUrl: args.serverUrl };
4485
- if (args.token) {
4486
- const url = new URL(config.serverUrl);
4487
- url.searchParams.set("token", args.token);
4488
- config = { ...config, serverUrl: url.toString() };
4985
+ if (args.serverUrl) {
4986
+ const wsUrl = new URL(args.serverUrl);
4987
+ const httpBase = `${wsUrl.protocol === "wss:" ? "https" : "http"}://${wsUrl.host}`;
4988
+ config = { ...config, serverUrl: args.serverUrl, serverApiUrl: httpBase };
4489
4989
  }
4990
+ if (args.token) config = { ...config, bridgeToken: args.token };
4490
4991
  if (args.dataDir) config = { ...config, dataDir: args.dataDir };
4491
4992
  if (args.logLevel) config = { ...config, logLevel: args.logLevel };
4492
4993
  await startBridge(config);
@@ -4494,7 +4995,7 @@ async function run(args) {
4494
4995
  var cli = cac("ahchat-bridge");
4495
4996
  cli.command("run", "Start the bridge and connect to server").option("--server-url <url>", "WebSocket URL of the AHChat server").option("--token <token>", "Auth token for server registration").option("--data-dir <dir>", "Data directory (default: ~/.ahchat)").option("--log-level <level>", "Log level (default: INFO)").action((args) => {
4496
4997
  void run(args).catch((e) => {
4497
- logger16.error("Bridge failed to start", { error: e });
4998
+ logger18.error("Bridge failed to start", { error: e });
4498
4999
  process.exit(1);
4499
5000
  });
4500
5001
  });
@@ -4505,7 +5006,7 @@ cli.command("launch", "Launch bridge from ahchat:// URL (called by OS)").option(
4505
5006
  process.exit(1);
4506
5007
  }
4507
5008
  void run({ serverUrl: parsed.serverUrl, token: parsed.token }).catch((e) => {
4508
- logger16.error("Bridge failed to start from URL", { error: e });
5009
+ logger18.error("Bridge failed to start from URL", { error: e });
4509
5010
  process.exit(1);
4510
5011
  });
4511
5012
  });