@fangyb/ahchat-bridge 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,45 @@ 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
+ const cwd = agent.workingDirectory || path7.join(this.workspacesDir, agent.id);
3068
3408
  await this.acquire(agent, { kind: "single" }, cwd);
3069
3409
  warmed++;
3070
- logger6.info("Agent process pre-created for recovery", { agentId: agent.id });
3410
+ logger7.info("Agent process pre-created for recovery", { agentId: agent.id });
3071
3411
  } catch (err) {
3072
3412
  if (err instanceof BridgeBusyError) {
3073
- logger6.warn("Recovery stopped: bridge busy", { agentId: agent.id });
3413
+ logger7.warn("Recovery stopped: bridge busy", { agentId: agent.id });
3074
3414
  break;
3075
3415
  }
3076
- logger6.warn("Failed to pre-create Agent for recovery, clearing session", {
3416
+ logger7.warn("Failed to pre-create Agent for recovery, clearing session", {
3077
3417
  agentId: agent.id,
3078
3418
  error: err
3079
3419
  });
@@ -3097,7 +3437,7 @@ var AgentManager = class {
3097
3437
  } catch (err) {
3098
3438
  const errMsg = err.message ?? String(err);
3099
3439
  const isResumeFail = /session|conversation.*not found/i.test(errMsg);
3100
- logger6.error("Agent query stream ended with error", {
3440
+ logger7.error("Agent query stream ended with error", {
3101
3441
  agentId: runtime.agentId,
3102
3442
  scope: scopeKey(runtime.scope),
3103
3443
  isResumeFail,
@@ -3105,7 +3445,7 @@ var AgentManager = class {
3105
3445
  error: err
3106
3446
  });
3107
3447
  this.sessionStore.delete(runtime.agentId, runtime.scope);
3108
- logger6.info("Cleared stale session after query crash", {
3448
+ logger7.info("Cleared stale session after query crash", {
3109
3449
  agentId: runtime.agentId,
3110
3450
  scope: scopeKey(runtime.scope)
3111
3451
  });
@@ -3166,8 +3506,50 @@ var AgentManager = class {
3166
3506
  }
3167
3507
  return [...ids];
3168
3508
  }
3509
+ /**
3510
+ * Push a system notice to ALL active runtimes of the given agent.
3511
+ *
3512
+ * Working runtimes: injected mid-turn via InputController (merged into
3513
+ * the current turn, no extra result expected).
3514
+ * Ready/starting runtimes: dispatched as a lightweight task so any SDK
3515
+ * output has valid replyMessageId/conversationId to land on.
3516
+ */
3517
+ broadcastScopeNotice(agentId, notice) {
3518
+ let notified = 0;
3519
+ for (const [, proc] of this.agents) {
3520
+ if (proc.agentId !== agentId || proc.status === "dead") continue;
3521
+ const runtime = this.asRuntime(proc);
3522
+ if (proc.status === "working") {
3523
+ runtime.inputController.push(notice, runtime.ccSessionId ?? "");
3524
+ logger7.info("Scope notice injected mid-turn", {
3525
+ agentId,
3526
+ scope: scopeKey(proc.scope),
3527
+ noticeLen: notice.length
3528
+ });
3529
+ } else if (proc.status === "ready" || proc.status === "starting") {
3530
+ const task = {
3531
+ content: notice,
3532
+ replyMessageId: `msg_scopenotice_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
3533
+ conversationId: proc.currentTask?.conversationId ?? "",
3534
+ traceId: `tr_scopenotice_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
3535
+ groupId: proc.scope.kind === "group" ? proc.scope.groupId : void 0
3536
+ };
3537
+ this.dispatchToSDK(runtime, task);
3538
+ logger7.info("Scope notice dispatched to idle runtime", {
3539
+ agentId,
3540
+ scope: scopeKey(proc.scope),
3541
+ replyMessageId: task.replyMessageId
3542
+ });
3543
+ }
3544
+ notified++;
3545
+ }
3546
+ logger7.info("broadcastScopeNotice completed", {
3547
+ agentId,
3548
+ notifiedCount: notified
3549
+ });
3550
+ }
3169
3551
  async shutdownAll() {
3170
- logger6.info("Shutting down all Agent processes", { count: this.agents.size });
3552
+ logger7.info("Shutting down all Agent processes", { count: this.agents.size });
3171
3553
  this.askQuestionRegistry.cancelAll("agent_aborted");
3172
3554
  if (this.evictionTimer) {
3173
3555
  clearInterval(this.evictionTimer);
@@ -3179,9 +3561,9 @@ var AgentManager = class {
3179
3561
  runtime.inputController?.close();
3180
3562
  runtime.query?.return(void 0);
3181
3563
  proc.status = "dead";
3182
- logger6.info("Agent process shut down", { agentId: proc.agentId, scope: scopeKey(proc.scope), key });
3564
+ logger7.info("Agent process shut down", { agentId: proc.agentId, scope: scopeKey(proc.scope), key });
3183
3565
  } catch (err) {
3184
- logger6.error("Error shutting down Agent", { agentId: proc.agentId, error: err });
3566
+ logger7.error("Error shutting down Agent", { agentId: proc.agentId, error: err });
3185
3567
  }
3186
3568
  }
3187
3569
  this.agents.clear();
@@ -3198,13 +3580,13 @@ var AgentManager = class {
3198
3580
  }
3199
3581
  }
3200
3582
  if (!proc) {
3201
- logger6.warn("cancelReply: no active process for reply", { agentId, replyMessageId });
3583
+ logger7.warn("cancelReply: no active process for reply", { agentId, replyMessageId });
3202
3584
  return;
3203
3585
  }
3204
3586
  const runtime = this.asRuntime(proc);
3205
3587
  const key = runtimeKey(agentId, proc.scope);
3206
3588
  if (!runtime.currentTask || runtime.currentTask.replyMessageId !== replyMessageId) {
3207
- logger6.warn("cancelReply: replyMessageId mismatch", {
3589
+ logger7.warn("cancelReply: replyMessageId mismatch", {
3208
3590
  agentId,
3209
3591
  replyMessageId,
3210
3592
  expected: runtime.currentTask?.replyMessageId
@@ -3233,7 +3615,7 @@ var AgentManager = class {
3233
3615
  proc.status = "dead";
3234
3616
  this.agents.delete(key);
3235
3617
  this.lastUsedAt.delete(key);
3236
- logger6.info("cancelReply: process torn down", {
3618
+ logger7.info("cancelReply: process torn down", {
3237
3619
  agentId,
3238
3620
  scope: scopeKey(proc.scope),
3239
3621
  conversationId,
@@ -3242,10 +3624,10 @@ var AgentManager = class {
3242
3624
  try {
3243
3625
  runtime.inputController.close();
3244
3626
  } catch (err) {
3245
- logger6.error("cancelReply: inputController.close failed", { agentId, error: err });
3627
+ logger7.error("cancelReply: inputController.close failed", { agentId, error: err });
3246
3628
  }
3247
3629
  runtime.query.return(void 0).catch((err) => {
3248
- logger6.warn("cancelReply: query.return threw", { agentId, error: err });
3630
+ logger7.warn("cancelReply: query.return threw", { agentId, error: err });
3249
3631
  });
3250
3632
  }
3251
3633
  };
@@ -3262,7 +3644,7 @@ function buildInnerVoiceEnvelope(payload) {
3262
3644
  }
3263
3645
 
3264
3646
  // src/agentRegistry.ts
3265
- var logger7 = createModuleLogger("agent.registry");
3647
+ var logger8 = createModuleLogger("agent.registry");
3266
3648
  var HttpAgentRegistry = class {
3267
3649
  constructor(serverApiUrl) {
3268
3650
  this.serverApiUrl = serverApiUrl;
@@ -3271,8 +3653,8 @@ var HttpAgentRegistry = class {
3271
3653
  agents = /* @__PURE__ */ new Map();
3272
3654
  apiUrl(suffix) {
3273
3655
  const base = this.serverApiUrl.replace(/\/$/, "");
3274
- const path11 = suffix.startsWith("/") ? suffix : `/${suffix}`;
3275
- return `${base}${path11}`;
3656
+ const path13 = suffix.startsWith("/") ? suffix : `/${suffix}`;
3657
+ return `${base}${path13}`;
3276
3658
  }
3277
3659
  async refresh() {
3278
3660
  const attempt = async () => {
@@ -3290,17 +3672,17 @@ var HttpAgentRegistry = class {
3290
3672
  recoveredAfterRetry = res != null;
3291
3673
  }
3292
3674
  if (!res) {
3293
- logger7.warn("Agent registry refresh unreachable after retry, keeping cache");
3675
+ logger8.warn("Agent registry refresh unreachable after retry, keeping cache");
3294
3676
  return;
3295
3677
  }
3296
3678
  if (!res.ok) {
3297
- logger7.warn("Agent registry refresh failed", { status: res.status });
3679
+ logger8.warn("Agent registry refresh failed", { status: res.status });
3298
3680
  return;
3299
3681
  }
3300
3682
  try {
3301
3683
  const body = await res.json();
3302
3684
  if (!Array.isArray(body)) {
3303
- logger7.warn("Agent registry refresh: expected array");
3685
+ logger8.warn("Agent registry refresh: expected array");
3304
3686
  return;
3305
3687
  }
3306
3688
  this.agents.clear();
@@ -3310,9 +3692,9 @@ var HttpAgentRegistry = class {
3310
3692
  this.agents.set(a.id, a);
3311
3693
  }
3312
3694
  }
3313
- logger7.info("Agent registry refreshed", { count: this.agents.size, recoveredAfterRetry });
3695
+ logger8.info("Agent registry refreshed", { count: this.agents.size, recoveredAfterRetry });
3314
3696
  } catch (e) {
3315
- logger7.warn("Agent registry refresh parse failed", { error: e });
3697
+ logger8.warn("Agent registry refresh parse failed", { error: e });
3316
3698
  }
3317
3699
  }
3318
3700
  getById(id) {
@@ -3326,17 +3708,17 @@ var HttpAgentRegistry = class {
3326
3708
  try {
3327
3709
  const res = await fetch(this.apiUrl(`/api/agents/${encodeURIComponent(id)}`));
3328
3710
  if (!res.ok) {
3329
- logger7.warn("fetchById failed", { agentId: id, status: res.status });
3711
+ logger8.warn("fetchById failed", { agentId: id, status: res.status });
3330
3712
  return null;
3331
3713
  }
3332
3714
  const agent = await res.json();
3333
3715
  if (agent && typeof agent.id === "string") {
3334
3716
  this.agents.set(agent.id, agent);
3335
- logger7.info("Agent registry fetchById upserted", { agentId: id });
3717
+ logger8.info("Agent registry fetchById upserted", { agentId: id });
3336
3718
  }
3337
3719
  return agent;
3338
3720
  } catch (e) {
3339
- logger7.warn("fetchById unreachable", { agentId: id, error: e });
3721
+ logger8.warn("fetchById unreachable", { agentId: id, error: e });
3340
3722
  return null;
3341
3723
  }
3342
3724
  }
@@ -3345,16 +3727,16 @@ var HttpAgentRegistry = class {
3345
3727
  }
3346
3728
  upsert(agent) {
3347
3729
  this.agents.set(agent.id, agent);
3348
- logger7.debug("Agent registry upsert", { agentId: agent.id });
3730
+ logger8.debug("Agent registry upsert", { agentId: agent.id });
3349
3731
  }
3350
3732
  remove(agentId) {
3351
3733
  this.agents.delete(agentId);
3352
- logger7.debug("Agent registry remove", { agentId });
3734
+ logger8.debug("Agent registry remove", { agentId });
3353
3735
  }
3354
3736
  };
3355
3737
 
3356
3738
  // src/groupRegistry.ts
3357
- var logger8 = createModuleLogger("neural.groupRegistry");
3739
+ var logger9 = createModuleLogger("neural.groupRegistry");
3358
3740
  var GroupRegistry = class {
3359
3741
  groups = /* @__PURE__ */ new Map();
3360
3742
  serverApiUrl;
@@ -3377,17 +3759,17 @@ var GroupRegistry = class {
3377
3759
  recoveredAfterRetry = res != null;
3378
3760
  }
3379
3761
  if (!res) {
3380
- logger8.warn("GroupRegistry refresh unreachable after retry");
3762
+ logger9.warn("GroupRegistry refresh unreachable after retry");
3381
3763
  return;
3382
3764
  }
3383
3765
  if (!res.ok) {
3384
- logger8.warn("GroupRegistry refresh failed", { status: res.status });
3766
+ logger9.warn("GroupRegistry refresh failed", { status: res.status });
3385
3767
  return;
3386
3768
  }
3387
3769
  try {
3388
3770
  const body = await res.json();
3389
3771
  if (!Array.isArray(body)) {
3390
- logger8.warn("GroupRegistry refresh: expected array");
3772
+ logger9.warn("GroupRegistry refresh: expected array");
3391
3773
  return;
3392
3774
  }
3393
3775
  this.groups.clear();
@@ -3402,14 +3784,25 @@ var GroupRegistry = class {
3402
3784
  });
3403
3785
  }
3404
3786
  }
3405
- logger8.info("GroupRegistry refreshed", { count: this.groups.size, recoveredAfterRetry });
3787
+ logger9.info("GroupRegistry refreshed", { count: this.groups.size, recoveredAfterRetry });
3406
3788
  } catch (e) {
3407
- logger8.warn("GroupRegistry refresh parse failed", { error: e });
3789
+ logger9.warn("GroupRegistry refresh parse failed", { error: e });
3408
3790
  }
3409
3791
  }
3410
3792
  getById(groupId) {
3411
3793
  return this.groups.get(groupId) ?? null;
3412
3794
  }
3795
+ /**
3796
+ * Return the cached groups that the given agent is a member of.
3797
+ * Does NOT refresh — caller decides when to call refresh().
3798
+ */
3799
+ getMyGroups(agentId) {
3800
+ const out = [];
3801
+ for (const g of this.groups.values()) {
3802
+ if (g.members.includes(agentId)) out.push(g);
3803
+ }
3804
+ return out;
3805
+ }
3413
3806
  /**
3414
3807
  * Fuzzy match by name (case-insensitive substring).
3415
3808
  * Returns the first match.
@@ -3443,16 +3836,16 @@ var GroupRegistry = class {
3443
3836
  try {
3444
3837
  const res = await fetch(url);
3445
3838
  if (res.status === 404) {
3446
- logger8.info("GroupRegistry resolveScope: group not found", { rawScope, suffix });
3839
+ logger9.info("GroupRegistry resolveScope: group not found", { rawScope, suffix });
3447
3840
  return null;
3448
3841
  }
3449
3842
  if (!res.ok) {
3450
- logger8.warn("GroupRegistry resolveScope: HTTP error", { rawScope, status: res.status });
3843
+ logger9.warn("GroupRegistry resolveScope: HTTP error", { rawScope, status: res.status });
3451
3844
  return null;
3452
3845
  }
3453
3846
  const data = await res.json();
3454
3847
  if (!data.groupId || !data.conversationId || !data.workingDirectory) {
3455
- logger8.warn("GroupRegistry resolveScope: incomplete response", {
3848
+ logger9.warn("GroupRegistry resolveScope: incomplete response", {
3456
3849
  rawScope,
3457
3850
  hasGroupId: !!data.groupId,
3458
3851
  hasConversationId: !!data.conversationId,
@@ -3460,7 +3853,7 @@ var GroupRegistry = class {
3460
3853
  });
3461
3854
  return null;
3462
3855
  }
3463
- logger8.info("GroupRegistry resolved scope", {
3856
+ logger9.info("GroupRegistry resolved scope", {
3464
3857
  rawScope,
3465
3858
  resolvedGroupId: data.groupId,
3466
3859
  resolvedName: data.name ?? "(none)",
@@ -3475,7 +3868,7 @@ var GroupRegistry = class {
3475
3868
  workingDirectory: data.workingDirectory
3476
3869
  };
3477
3870
  } catch (e) {
3478
- logger8.error("GroupRegistry resolveScope error", { rawScope, error: e });
3871
+ logger9.error("GroupRegistry resolveScope error", { rawScope, error: e });
3479
3872
  return null;
3480
3873
  }
3481
3874
  }
@@ -3496,12 +3889,12 @@ var GroupRegistry = class {
3496
3889
  if (Array.isArray(body)) {
3497
3890
  const single = body.find((c) => c.type === "single" && typeof c.id === "string");
3498
3891
  if (single?.id) {
3499
- logger8.info("GroupRegistry resolved single conv", { agentId, conversationId: single.id });
3892
+ logger9.info("GroupRegistry resolved single conv", { agentId, conversationId: single.id });
3500
3893
  return single.id;
3501
3894
  }
3502
3895
  }
3503
3896
  } else {
3504
- logger8.warn("GroupRegistry resolveSingle: list failed", { agentId, status: res.status });
3897
+ logger9.warn("GroupRegistry resolveSingle: list failed", { agentId, status: res.status });
3505
3898
  }
3506
3899
  const created = await fetch(`${this.serverApiUrl}/api/conversations`, {
3507
3900
  method: "POST",
@@ -3509,26 +3902,27 @@ var GroupRegistry = class {
3509
3902
  body: JSON.stringify({ agentId })
3510
3903
  });
3511
3904
  if (!created.ok) {
3512
- logger8.warn("GroupRegistry resolveSingle: create failed", { agentId, status: created.status });
3905
+ logger9.warn("GroupRegistry resolveSingle: create failed", { agentId, status: created.status });
3513
3906
  return null;
3514
3907
  }
3515
3908
  const conv = await created.json();
3516
3909
  if (typeof conv.id !== "string") {
3517
- logger8.warn("GroupRegistry resolveSingle: created conv missing id", { agentId });
3910
+ logger9.warn("GroupRegistry resolveSingle: created conv missing id", { agentId });
3518
3911
  return null;
3519
3912
  }
3520
- logger8.info("GroupRegistry created single conv", { agentId, conversationId: conv.id });
3913
+ logger9.info("GroupRegistry created single conv", { agentId, conversationId: conv.id });
3521
3914
  return conv.id;
3522
3915
  } catch (e) {
3523
- logger8.error("GroupRegistry resolveSingle error", { agentId, error: e });
3916
+ logger9.error("GroupRegistry resolveSingle error", { agentId, error: e });
3524
3917
  return null;
3525
3918
  }
3526
3919
  }
3527
3920
  };
3528
3921
 
3529
3922
  // src/connector.ts
3923
+ import os5 from "os";
3530
3924
  import WebSocket from "ws";
3531
- var logger9 = createModuleLogger("ws.connector");
3925
+ var logger10 = createModuleLogger("ws.connector");
3532
3926
  var ServerConnector = class {
3533
3927
  ws = null;
3534
3928
  reconnectAttempts = 0;
@@ -3558,19 +3952,19 @@ var ServerConnector = class {
3558
3952
  url.searchParams.set("token", this.config.bridgeToken);
3559
3953
  }
3560
3954
  const wsUrl = url.toString();
3561
- logger9.info("Connecting to server", { url: this.config.serverUrl });
3955
+ logger10.info("Connecting to server", { url: wsUrl });
3562
3956
  const ws = new WebSocket(wsUrl);
3563
3957
  ws.on("open", () => {
3564
3958
  this.ws = ws;
3565
3959
  this.reconnectAttempts = 0;
3566
- logger9.info("Connected to server", { url: this.config.serverUrl });
3960
+ logger10.info("Connected to server", { url: this.config.serverUrl });
3567
3961
  void this.handleOpen();
3568
3962
  });
3569
3963
  ws.on("message", (data) => {
3570
3964
  this.handleMessage(data);
3571
3965
  });
3572
3966
  ws.on("close", (code, reason) => {
3573
- logger9.warn("Disconnected from server", {
3967
+ logger10.warn("Disconnected from server", {
3574
3968
  code,
3575
3969
  reason: reason.toString()
3576
3970
  });
@@ -3580,15 +3974,15 @@ var ServerConnector = class {
3580
3974
  }
3581
3975
  });
3582
3976
  ws.on("error", (err) => {
3583
- logger9.error("WebSocket error", { error: err });
3977
+ logger10.error("WebSocket error", { error: err });
3584
3978
  });
3585
3979
  }
3586
3980
  async handleOpen() {
3587
3981
  try {
3588
3982
  await this.onConnected();
3589
- logger9.info("Recovery complete, sending bridge:register");
3983
+ logger10.info("Recovery complete, sending bridge:register");
3590
3984
  } catch (err) {
3591
- logger9.error("Recovery failed, registering with degraded state", { error: err });
3985
+ logger10.error("Recovery failed, registering with degraded state", { error: err });
3592
3986
  }
3593
3987
  this.register();
3594
3988
  }
@@ -3600,13 +3994,14 @@ var ServerConnector = class {
3600
3994
  payload: {
3601
3995
  bridgeId: this.config.bridgeId,
3602
3996
  agents: ids,
3997
+ hostname: os5.hostname(),
3603
3998
  queryConfig: {
3604
3999
  maxActive: qc.maxActive,
3605
4000
  idleTimeoutMs: qc.idleTimeoutMs
3606
4001
  }
3607
4002
  }
3608
4003
  });
3609
- logger9.info("Sent bridge:register", {
4004
+ logger10.info("Sent bridge:register", {
3610
4005
  bridgeId: this.config.bridgeId,
3611
4006
  agents: ids
3612
4007
  });
@@ -3617,7 +4012,7 @@ var ServerConnector = class {
3617
4012
  const raw = typeof data === "string" ? data : data.toString("utf8");
3618
4013
  msg = parseWSMessage(raw);
3619
4014
  } catch (e) {
3620
- logger9.error("Invalid WS message from server", { error: e });
4015
+ logger10.error("Invalid WS message from server", { error: e });
3621
4016
  return;
3622
4017
  }
3623
4018
  wsMetrics.incRecv(msg.type);
@@ -3628,7 +4023,7 @@ var ServerConnector = class {
3628
4023
  }
3629
4024
  case "task:dispatch": {
3630
4025
  void this.onTaskDispatch(msg.payload).catch((err) => {
3631
- logger9.error("Failed to handle task:dispatch", {
4026
+ logger10.error("Failed to handle task:dispatch", {
3632
4027
  error: err,
3633
4028
  traceId: msg.payload.traceId
3634
4029
  });
@@ -3638,19 +4033,19 @@ var ServerConnector = class {
3638
4033
  case "task:group_dispatch": {
3639
4034
  if (this.onGroupTaskDispatch) {
3640
4035
  void this.onGroupTaskDispatch(msg.payload).catch((err) => {
3641
- logger9.error("Failed to handle task:group_dispatch", {
4036
+ logger10.error("Failed to handle task:group_dispatch", {
3642
4037
  error: err,
3643
4038
  traceId: msg.payload.traceId
3644
4039
  });
3645
4040
  });
3646
4041
  } else {
3647
- logger9.warn("Received task:group_dispatch but no handler registered");
4042
+ logger10.warn("Received task:group_dispatch but no handler registered");
3648
4043
  }
3649
4044
  return;
3650
4045
  }
3651
4046
  case "user:stop_generation": {
3652
4047
  void this.onStopGeneration(msg.payload).catch((err) => {
3653
- logger9.error("Failed to handle user:stop_generation", {
4048
+ logger10.error("Failed to handle user:stop_generation", {
3654
4049
  error: err,
3655
4050
  traceId: msg.payload.traceId
3656
4051
  });
@@ -3664,16 +4059,17 @@ var ServerConnector = class {
3664
4059
  case "agent:updated":
3665
4060
  case "agent:deleted":
3666
4061
  case "group:member_changed":
4062
+ case "group:updated":
3667
4063
  case "user:answer_question": {
3668
4064
  if (this.onServerPush) {
3669
4065
  void Promise.resolve(this.onServerPush(msg)).catch((err) => {
3670
- logger9.error("onServerPush handler failed", { error: err, type: msg.type });
4066
+ logger10.error("onServerPush handler failed", { error: err, type: msg.type });
3671
4067
  });
3672
4068
  }
3673
4069
  return;
3674
4070
  }
3675
4071
  default: {
3676
- logger9.warn("Unhandled server message type", {
4072
+ logger10.warn("Unhandled server message type", {
3677
4073
  type: msg.type
3678
4074
  });
3679
4075
  }
@@ -3681,7 +4077,7 @@ var ServerConnector = class {
3681
4077
  }
3682
4078
  send(msg) {
3683
4079
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
3684
- logger9.warn("Cannot send: WebSocket not open", {
4080
+ logger10.warn("Cannot send: WebSocket not open", {
3685
4081
  type: msg.type
3686
4082
  });
3687
4083
  return;
@@ -3690,14 +4086,14 @@ var ServerConnector = class {
3690
4086
  this.ws.send(JSON.stringify(msg));
3691
4087
  wsMetrics.incSend(msg.type);
3692
4088
  } catch (e) {
3693
- logger9.error("Failed to send WS message", { error: e, type: msg.type });
4089
+ logger10.error("Failed to send WS message", { error: e, type: msg.type });
3694
4090
  }
3695
4091
  }
3696
4092
  scheduleReconnect() {
3697
4093
  if (this.closing) return;
3698
4094
  const delay = this.delays[Math.min(this.reconnectAttempts, this.delays.length - 1)];
3699
4095
  this.reconnectAttempts++;
3700
- logger9.info("Scheduling reconnect", {
4096
+ logger10.info("Scheduling reconnect", {
3701
4097
  attempt: this.reconnectAttempts,
3702
4098
  delayMs: delay
3703
4099
  });
@@ -3713,11 +4109,11 @@ var ServerConnector = class {
3713
4109
  try {
3714
4110
  this.ws.close(1e3, "Bridge shutting down");
3715
4111
  } catch (e) {
3716
- logger9.error("Error closing WebSocket", { error: e });
4112
+ logger10.error("Error closing WebSocket", { error: e });
3717
4113
  }
3718
4114
  this.ws = null;
3719
4115
  }
3720
- logger9.info("Connector closed");
4116
+ logger10.info("Connector closed");
3721
4117
  }
3722
4118
  get isConnected() {
3723
4119
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
@@ -3725,14 +4121,14 @@ var ServerConnector = class {
3725
4121
  };
3726
4122
 
3727
4123
  // 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");
4124
+ import fs4 from "fs/promises";
4125
+ import os6 from "os";
4126
+ import path8 from "path";
4127
+ var logger11 = createModuleLogger("bridge.modelQuerier");
3732
4128
  async function listModels(queryFn, opts = {}) {
3733
4129
  const t0 = Date.now();
3734
- const cwd = opts.cwd ?? path7.join(os5.homedir(), ".ahchat", "workspaces", "_list_models");
3735
- await fs3.mkdir(cwd, { recursive: true });
4130
+ const cwd = opts.cwd ?? path8.join(os6.homedir(), ".ahchat", "workspaces", "_list_models");
4131
+ await fs4.mkdir(cwd, { recursive: true });
3736
4132
  const fn = queryFn ?? (await import("@anthropic-ai/claude-agent-sdk")).query;
3737
4133
  const ic = new InputController();
3738
4134
  ic.push("Reply with exactly: PING", "");
@@ -3774,7 +4170,7 @@ async function listModels(queryFn, opts = {}) {
3774
4170
  displayName: m.displayName,
3775
4171
  description: m.description
3776
4172
  }));
3777
- logger10.info("listModels done", { count: models.length, ms: Date.now() - t0 });
4173
+ logger11.info("listModels done", { count: models.length, ms: Date.now() - t0 });
3778
4174
  return models;
3779
4175
  } finally {
3780
4176
  try {
@@ -3789,9 +4185,9 @@ async function listModels(queryFn, opts = {}) {
3789
4185
  }
3790
4186
 
3791
4187
  // src/lockfile.ts
3792
- import fs4 from "fs";
3793
- import path8 from "path";
3794
- var logger11 = createModuleLogger("bridge.lockfile");
4188
+ import fs5 from "fs";
4189
+ import path9 from "path";
4190
+ var logger12 = createModuleLogger("bridge.lockfile");
3795
4191
  var lockPath = null;
3796
4192
  function isProcessAlive(pid) {
3797
4193
  try {
@@ -3804,32 +4200,32 @@ function isProcessAlive(pid) {
3804
4200
  }
3805
4201
  }
3806
4202
  function acquireLock(dataDir) {
3807
- const file = path8.join(dataDir, "bridge.lock");
4203
+ const file = path9.join(dataDir, "bridge.lock");
3808
4204
  lockPath = file;
3809
- if (fs4.existsSync(file)) {
3810
- const raw = fs4.readFileSync(file, "utf-8").trim();
4205
+ if (fs5.existsSync(file)) {
4206
+ const raw = fs5.readFileSync(file, "utf-8").trim();
3811
4207
  const pid = Number.parseInt(raw, 10);
3812
4208
  if (Number.isFinite(pid) && pid > 0) {
3813
4209
  if (isProcessAlive(pid)) {
3814
4210
  throw new Error(`Bridge already running (PID: ${pid})`);
3815
4211
  }
3816
- logger11.warn("Removing stale bridge.lock (process not found)", { pid, path: file });
4212
+ logger12.warn("Removing stale bridge.lock (process not found)", { pid, path: file });
3817
4213
  }
3818
4214
  }
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 });
4215
+ fs5.mkdirSync(path9.dirname(file), { recursive: true });
4216
+ fs5.writeFileSync(file, String(process.pid), "utf-8");
4217
+ logger12.info("Acquired bridge lock", { path: file, pid: process.pid });
3822
4218
  const release = () => {
3823
4219
  try {
3824
- if (lockPath && fs4.existsSync(lockPath)) {
3825
- const current = fs4.readFileSync(lockPath, "utf-8").trim();
4220
+ if (lockPath && fs5.existsSync(lockPath)) {
4221
+ const current = fs5.readFileSync(lockPath, "utf-8").trim();
3826
4222
  if (current === String(process.pid)) {
3827
- fs4.unlinkSync(lockPath);
3828
- logger11.info("Released bridge lock", { path: lockPath });
4223
+ fs5.unlinkSync(lockPath);
4224
+ logger12.info("Released bridge lock", { path: lockPath });
3829
4225
  }
3830
4226
  }
3831
4227
  } catch (e) {
3832
- logger11.error("Failed to release bridge lock", { error: e, path: lockPath });
4228
+ logger12.error("Failed to release bridge lock", { error: e, path: lockPath });
3833
4229
  } finally {
3834
4230
  lockPath = null;
3835
4231
  }
@@ -3932,9 +4328,9 @@ function buildGroupPrompt(payload) {
3932
4328
  }
3933
4329
 
3934
4330
  // src/messageHandler.ts
3935
- var logger12 = createModuleLogger("msg.handler");
4331
+ var logger13 = createModuleLogger("msg.handler");
3936
4332
  function emitTaskAck(emit, ackId, agentId, traceId) {
3937
- logger12.info("Emitting task:ack", { ackId, agentId, traceId });
4333
+ logger13.info("Emitting task:ack", { ackId, agentId, traceId });
3938
4334
  emit({
3939
4335
  type: "task:ack",
3940
4336
  payload: {
@@ -3947,7 +4343,7 @@ function emitTaskAck(emit, ackId, agentId, traceId) {
3947
4343
  }
3948
4344
  function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
3949
4345
  return async (payload) => {
3950
- logger12.info("Handling task:dispatch", {
4346
+ logger13.info("Handling task:dispatch", {
3951
4347
  agentId: payload.agentId,
3952
4348
  messageId: payload.messageId,
3953
4349
  ackId: payload.ackId,
@@ -3956,14 +4352,14 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
3956
4352
  emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
3957
4353
  let agentConfig = agentRegistry.getById(payload.agentId);
3958
4354
  if (!agentConfig) {
3959
- logger12.warn("Agent not in registry, attempting live fetch", {
4355
+ logger13.warn("Agent not in registry, attempting live fetch", {
3960
4356
  agentId: payload.agentId,
3961
4357
  traceId: payload.traceId
3962
4358
  });
3963
4359
  agentConfig = await agentRegistry.fetchById(payload.agentId);
3964
4360
  }
3965
4361
  if (!agentConfig) {
3966
- logger12.error("Agent not found for task:dispatch (after live fetch)", {
4362
+ logger13.error("Agent not found for task:dispatch (after live fetch)", {
3967
4363
  agentId: payload.agentId,
3968
4364
  traceId: payload.traceId
3969
4365
  });
@@ -3990,7 +4386,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
3990
4386
  traceId: payload.traceId
3991
4387
  });
3992
4388
  } catch (err) {
3993
- logger12.error("Failed to dispatch message to Agent", {
4389
+ logger13.error("Failed to dispatch message to Agent", {
3994
4390
  error: err,
3995
4391
  agentId: payload.agentId,
3996
4392
  traceId: payload.traceId
@@ -4010,7 +4406,7 @@ function createTaskDispatchHandler(agentManager, agentRegistry, emit) {
4010
4406
  }
4011
4407
  function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4012
4408
  return async (payload) => {
4013
- logger12.info("Handling task:group_dispatch", {
4409
+ logger13.info("Handling task:group_dispatch", {
4014
4410
  agentId: payload.agentId,
4015
4411
  groupId: payload.groupId,
4016
4412
  ackId: payload.ackId,
@@ -4022,14 +4418,14 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4022
4418
  emitTaskAck(emit, payload.ackId, payload.agentId, payload.traceId);
4023
4419
  let agentConfig = agentRegistry.getById(payload.agentId);
4024
4420
  if (!agentConfig) {
4025
- logger12.warn("Agent not in registry for group dispatch, attempting live fetch", {
4421
+ logger13.warn("Agent not in registry for group dispatch, attempting live fetch", {
4026
4422
  agentId: payload.agentId,
4027
4423
  traceId: payload.traceId
4028
4424
  });
4029
4425
  agentConfig = await agentRegistry.fetchById(payload.agentId);
4030
4426
  }
4031
4427
  if (!agentConfig) {
4032
- logger12.error("Agent not found for task:group_dispatch (after live fetch)", {
4428
+ logger13.error("Agent not found for task:group_dispatch (after live fetch)", {
4033
4429
  agentId: payload.agentId,
4034
4430
  traceId: payload.traceId
4035
4431
  });
@@ -4062,7 +4458,7 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4062
4458
  groupId: payload.groupId
4063
4459
  });
4064
4460
  } catch (err) {
4065
- logger12.error("Failed to dispatch group message to Agent", {
4461
+ logger13.error("Failed to dispatch group message to Agent", {
4066
4462
  error: err,
4067
4463
  agentId: payload.agentId,
4068
4464
  groupId: payload.groupId,
@@ -4082,15 +4478,84 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4082
4478
  };
4083
4479
  }
4084
4480
 
4481
+ // src/scopePushNotify.ts
4482
+ var logger14 = createModuleLogger("bridge");
4483
+ function buildMemberChangedScopeNotice(params) {
4484
+ const { groupId, groupLabel, action } = params;
4485
+ const verb = action === "added" ? "\u52A0\u5165" : "\u79FB\u51FA";
4486
+ return [
4487
+ `[\u7CFB\u7EDF\u901A\u77E5] \u4F60\u5DF2\u88AB${verb}\u7FA4\u300C${groupLabel}\u300D(group:${groupId})\u3002`,
4488
+ `\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`,
4489
+ `\u65E0\u9700\u56DE\u590D\u6B64\u901A\u77E5\u3002`
4490
+ ].join("\n");
4491
+ }
4492
+ function buildGroupRenamedScopeNotice(params) {
4493
+ const { groupId, newName } = params;
4494
+ return [
4495
+ `[\u7CFB\u7EDF\u901A\u77E5] \u7FA4 (group:${groupId}) \u5DF2\u66F4\u540D\u4E3A\u300C${newName}\u300D\u3002`,
4496
+ `\u82E5\u4F60\u4E4B\u524D\u7528\u65E7\u7FA4\u540D\u8C03\u8FC7 neural_send\uFF0C\u8BF7\u66F4\u65B0\u4E3A\u65B0\u540D\u79F0\u3002`,
4497
+ `\u65E0\u9700\u56DE\u590D\u6B64\u901A\u77E5\u3002`
4498
+ ].join("\n");
4499
+ }
4500
+ async function handleGroupMemberChangedPush(deps, payload) {
4501
+ const { groupId, action, agentId } = payload;
4502
+ logger14.info("group:member_changed received, refreshing GroupRegistry", {
4503
+ groupId,
4504
+ action,
4505
+ agentId
4506
+ });
4507
+ await deps.groupRegistry.refresh();
4508
+ logger14.info("GroupRegistry refreshed after member_changed", {
4509
+ groupId,
4510
+ action,
4511
+ registryGroupCount: deps.groupRegistry.getAll().length
4512
+ });
4513
+ const group = deps.groupRegistry.getById(groupId);
4514
+ const groupLabel = group?.name ?? groupId;
4515
+ const notice = buildMemberChangedScopeNotice({ groupId, groupLabel, action });
4516
+ deps.agentManager.broadcastScopeNotice(agentId, notice);
4517
+ logger14.info("Scope notice sent for member_changed", {
4518
+ agentId,
4519
+ groupId,
4520
+ groupLabel,
4521
+ action,
4522
+ noticeLen: notice.length
4523
+ });
4524
+ }
4525
+ async function handleGroupUpdatedPush(deps, payload) {
4526
+ const { groupId, name: newName, memberAgentIds } = payload;
4527
+ logger14.info("group:updated received, refreshing GroupRegistry", {
4528
+ groupId,
4529
+ newName,
4530
+ memberCount: memberAgentIds.length
4531
+ });
4532
+ await deps.groupRegistry.refresh();
4533
+ logger14.info("GroupRegistry refreshed after group:updated", {
4534
+ groupId,
4535
+ newName,
4536
+ registryGroupCount: deps.groupRegistry.getAll().length
4537
+ });
4538
+ const notice = buildGroupRenamedScopeNotice({ groupId, newName });
4539
+ for (const aid of memberAgentIds) {
4540
+ deps.agentManager.broadcastScopeNotice(aid, notice);
4541
+ }
4542
+ logger14.info("Scope notices sent for group:updated", {
4543
+ groupId,
4544
+ newName,
4545
+ memberCount: memberAgentIds.length,
4546
+ noticeLen: notice.length
4547
+ });
4548
+ }
4549
+
4085
4550
  // src/sessionStore.ts
4086
- import fs5 from "fs";
4087
- import path9 from "path";
4088
- var logger13 = createModuleLogger("session.store");
4551
+ import fs6 from "fs";
4552
+ import path10 from "path";
4553
+ var logger15 = createModuleLogger("session.store");
4089
4554
  var SessionStore = class {
4090
4555
  filePath;
4091
4556
  cache;
4092
4557
  constructor(dataDir) {
4093
- this.filePath = path9.join(dataDir, "sessions.json");
4558
+ this.filePath = path10.join(dataDir, "sessions.json");
4094
4559
  this.cache = this.loadFromDisk();
4095
4560
  }
4096
4561
  cacheKey(agentId, scope) {
@@ -4125,8 +4590,8 @@ var SessionStore = class {
4125
4590
  }
4126
4591
  loadFromDisk() {
4127
4592
  try {
4128
- if (!fs5.existsSync(this.filePath)) return {};
4129
- const raw = fs5.readFileSync(this.filePath, "utf-8");
4593
+ if (!fs6.existsSync(this.filePath)) return {};
4594
+ const raw = fs6.readFileSync(this.filePath, "utf-8");
4130
4595
  const parsed = JSON.parse(raw);
4131
4596
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
4132
4597
  const map = parsed;
@@ -4136,7 +4601,7 @@ var SessionStore = class {
4136
4601
  migrated[key] = sessionId;
4137
4602
  } else {
4138
4603
  migrated[`${key}::single`] = sessionId;
4139
- logger13.info("Migrated legacy session key to scoped key", {
4604
+ logger15.info("Migrated legacy session key to scoped key", {
4140
4605
  legacyKey: key,
4141
4606
  newKey: `${key}::single`
4142
4607
  });
@@ -4144,29 +4609,29 @@ var SessionStore = class {
4144
4609
  }
4145
4610
  return migrated;
4146
4611
  } catch (e) {
4147
- logger13.warn("Failed to load sessions file, starting fresh", { error: e, path: this.filePath });
4612
+ logger15.warn("Failed to load sessions file, starting fresh", { error: e, path: this.filePath });
4148
4613
  return {};
4149
4614
  }
4150
4615
  }
4151
4616
  saveToDisk() {
4152
4617
  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");
4618
+ const dir = path10.dirname(this.filePath);
4619
+ fs6.mkdirSync(dir, { recursive: true });
4620
+ fs6.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), "utf-8");
4156
4621
  } catch (e) {
4157
- logger13.error("Failed to save sessions file", { error: e, path: this.filePath });
4622
+ logger15.error("Failed to save sessions file", { error: e, path: this.filePath });
4158
4623
  }
4159
4624
  }
4160
4625
  };
4161
4626
 
4162
4627
  // src/start.ts
4163
- var logger14 = createModuleLogger("bridge");
4628
+ var logger16 = createModuleLogger("bridge");
4164
4629
  async function startBridge(config) {
4165
4630
  ensureDir(config.dataDir);
4166
4631
  ensureDir(config.claudeConfigDir);
4167
4632
  process.env.CLAUDE_CONFIG_DIR = config.claudeConfigDir;
4168
4633
  acquireLock(config.dataDir);
4169
- logger14.info("Bridge starting", {
4634
+ logger16.info("Bridge starting", {
4170
4635
  bridgeId: config.bridgeId,
4171
4636
  serverUrl: config.serverUrl,
4172
4637
  serverApiUrl: config.serverApiUrl,
@@ -4174,6 +4639,9 @@ async function startBridge(config) {
4174
4639
  });
4175
4640
  wsMetrics.start(5e3);
4176
4641
  const sessionStore = new SessionStore(config.dataDir);
4642
+ const memoryRoot = path11.join(config.dataDir, "agent-memory");
4643
+ const memoryStore = new AgentMemoryStore(memoryRoot);
4644
+ logger16.info("Agent memory store initialized", { rootDir: memoryRoot });
4177
4645
  const agentRegistry = new HttpAgentRegistry(config.serverApiUrl);
4178
4646
  const groupRegistry = new GroupRegistry(config.serverApiUrl);
4179
4647
  await agentRegistry.refresh();
@@ -4187,7 +4655,8 @@ async function startBridge(config) {
4187
4655
  queryConfig: config.queryConfig,
4188
4656
  claudeConfigDir: config.claudeConfigDir,
4189
4657
  askQuestionRegistry,
4190
- groupRegistry
4658
+ groupRegistry,
4659
+ memoryStore
4191
4660
  });
4192
4661
  const taskDispatchHandler = createTaskDispatchHandler(agentManager, agentRegistry, emit);
4193
4662
  const groupTaskDispatchHandler = createGroupTaskDispatchHandler(agentManager, agentRegistry, emit);
@@ -4209,21 +4678,21 @@ async function startBridge(config) {
4209
4678
  switch (msg.type) {
4210
4679
  case "bridge:list_models_request": {
4211
4680
  const { requestId } = msg.payload;
4212
- logger14.info("list_models request received", { requestId });
4681
+ logger16.info("list_models request received", { requestId });
4213
4682
  try {
4214
4683
  const models = await listModels();
4215
4684
  connector?.send({
4216
4685
  type: "bridge:list_models_response",
4217
4686
  payload: { requestId, models }
4218
4687
  });
4219
- logger14.info("list_models response sent", { requestId, count: models.length });
4688
+ logger16.info("list_models response sent", { requestId, count: models.length });
4220
4689
  } catch (e) {
4221
4690
  const err = e instanceof Error ? e.message : String(e);
4222
4691
  connector?.send({
4223
4692
  type: "bridge:list_models_response",
4224
4693
  payload: { requestId, error: err }
4225
4694
  });
4226
- logger14.error("list_models failed", { requestId, error: e });
4695
+ logger16.error("list_models failed", { requestId, error: e });
4227
4696
  }
4228
4697
  break;
4229
4698
  }
@@ -4231,7 +4700,7 @@ async function startBridge(config) {
4231
4700
  await agentManager.terminate(msg.payload.agentId);
4232
4701
  break;
4233
4702
  case "agent:terminate_scope":
4234
- logger14.info("agent:terminate_scope received", {
4703
+ logger16.info("agent:terminate_scope received", {
4235
4704
  agentId: msg.payload.agentId,
4236
4705
  scope: msg.payload.scope
4237
4706
  });
@@ -4244,11 +4713,31 @@ async function startBridge(config) {
4244
4713
  case "agent:deleted":
4245
4714
  agentRegistry.remove(msg.payload.agentId);
4246
4715
  break;
4716
+ case "group:member_changed":
4717
+ await handleGroupMemberChangedPush(
4718
+ { groupRegistry, agentManager },
4719
+ {
4720
+ groupId: msg.payload.groupId,
4721
+ action: msg.payload.action,
4722
+ agentId: msg.payload.agentId
4723
+ }
4724
+ );
4725
+ break;
4726
+ case "group:updated":
4727
+ await handleGroupUpdatedPush(
4728
+ { groupRegistry, agentManager },
4729
+ {
4730
+ groupId: msg.payload.groupId,
4731
+ name: msg.payload.name,
4732
+ memberAgentIds: msg.payload.memberAgentIds
4733
+ }
4734
+ );
4735
+ break;
4247
4736
  case "user:answer_question": {
4248
4737
  const p = msg.payload;
4249
4738
  const answerText = formatAnswerForSDK(p);
4250
4739
  const ok = askQuestionRegistry.resolve(p.questionId, answerText);
4251
- logger14.info("user:answer_question handled", {
4740
+ logger16.info("user:answer_question handled", {
4252
4741
  questionId: p.questionId,
4253
4742
  agentId: p.agentId,
4254
4743
  resolved: ok,
@@ -4269,7 +4758,7 @@ async function startBridge(config) {
4269
4758
  });
4270
4759
  }, config.queryConfig.statusReportIntervalMs);
4271
4760
  const shutdown = async (signal) => {
4272
- logger14.info("Shutdown signal received", { signal });
4761
+ logger16.info("Shutdown signal received", { signal });
4273
4762
  if (statusInterval) {
4274
4763
  clearInterval(statusInterval);
4275
4764
  statusInterval = null;
@@ -4277,7 +4766,7 @@ async function startBridge(config) {
4277
4766
  wsMetrics.stop();
4278
4767
  connector?.close();
4279
4768
  await agentManager.shutdownAll();
4280
- logger14.info("Bridge stopped");
4769
+ logger16.info("Bridge stopped");
4281
4770
  process.exit(0);
4282
4771
  };
4283
4772
  process.on("SIGINT", () => void shutdown("SIGINT"));
@@ -4286,19 +4775,19 @@ async function startBridge(config) {
4286
4775
 
4287
4776
  // src/protocol.ts
4288
4777
  import { execSync } from "child_process";
4289
- import fs6 from "fs";
4290
- import os6 from "os";
4291
- import path10 from "path";
4778
+ import fs7 from "fs";
4779
+ import os7 from "os";
4780
+ import path12 from "path";
4292
4781
  import { fileURLToPath } from "url";
4293
- var logger15 = createModuleLogger("bridge.protocol");
4782
+ var logger17 = createModuleLogger("bridge.protocol");
4294
4783
  var __filename = fileURLToPath(import.meta.url);
4295
- var __dirname = path10.dirname(__filename);
4784
+ var __dirname = path12.dirname(__filename);
4296
4785
  function getBridgeExePath() {
4297
- const pkgDir = path10.resolve(__dirname, "..");
4298
- return path10.join(pkgDir, "dist", "cli.js");
4786
+ const pkgDir = path12.resolve(__dirname, "..");
4787
+ return path12.join(pkgDir, "dist", "cli.js");
4299
4788
  }
4300
4789
  function registerProtocolHandler() {
4301
- const platform = os6.platform();
4790
+ const platform = os7.platform();
4302
4791
  if (platform === "win32") {
4303
4792
  registerWindows();
4304
4793
  } else if (platform === "darwin") {
@@ -4306,15 +4795,15 @@ function registerProtocolHandler() {
4306
4795
  } else {
4307
4796
  registerLinux();
4308
4797
  }
4309
- logger15.info("Protocol handler registered", { platform });
4798
+ logger17.info("Protocol handler registered", { platform });
4310
4799
  }
4311
4800
  function registerWindows() {
4312
4801
  const exe = getBridgeExePath();
4313
4802
  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");
4803
+ const ahchatDir = path12.join(os7.homedir(), ".ahchat");
4804
+ const urlFilePath = path12.join(ahchatDir, ".bridge-launch-url");
4805
+ fs7.mkdirSync(ahchatDir, { recursive: true });
4806
+ const psScriptPath = path12.join(ahchatDir, "launch-bridge.ps1");
4318
4807
  const psContent = `param([string]$url)
4319
4808
  if (-not $url) {
4320
4809
  if (Test-Path '${urlFilePath}') {
@@ -4327,7 +4816,7 @@ if (-not $url) {
4327
4816
  }
4328
4817
  & '${nodeExe}' '${exe}' launch --url $url
4329
4818
  `;
4330
- fs6.writeFileSync(psScriptPath, psContent);
4819
+ fs7.writeFileSync(psScriptPath, psContent);
4331
4820
  const handler = `powershell -ExecutionPolicy Bypass -File "${psScriptPath}" -url "%1"`;
4332
4821
  const regCommands = [
4333
4822
  `REG ADD "HKCU\\Software\\Classes\\ahchat" /ve /d "URL:ahchat" /f`,
@@ -4339,19 +4828,19 @@ if (-not $url) {
4339
4828
  try {
4340
4829
  execSync(cmd, { stdio: "pipe" });
4341
4830
  } catch (e) {
4342
- logger15.error("Failed to register Windows protocol handler", { error: e, cmd });
4831
+ logger17.error("Failed to register Windows protocol handler", { error: e, cmd });
4343
4832
  throw new Error(`Failed to register protocol handler: ${cmd}`);
4344
4833
  }
4345
4834
  }
4346
- logger15.info("Windows protocol handler registered", { psScriptPath });
4835
+ logger17.info("Windows protocol handler registered", { psScriptPath });
4347
4836
  }
4348
4837
  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 });
4838
+ const appDir = path12.join(os7.homedir(), "Applications", "AHChatBridge.app");
4839
+ const contentsDir = path12.join(appDir, "Contents");
4840
+ const macosDir = path12.join(contentsDir, "MacOS");
4841
+ const resourcesDir = path12.join(contentsDir, "Resources");
4842
+ fs7.mkdirSync(macosDir, { recursive: true });
4843
+ fs7.mkdirSync(resourcesDir, { recursive: true });
4355
4844
  const infoPlist = `<?xml version="1.0" encoding="UTF-8"?>
4356
4845
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4357
4846
  <plist version="1.0">
@@ -4384,10 +4873,10 @@ function registerMacOS() {
4384
4873
  const launchScript = `#!/bin/bash
4385
4874
  URL="$1"
4386
4875
  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 });
4876
+ fs7.writeFileSync(path12.join(contentsDir, "Info.plist"), infoPlist);
4877
+ fs7.writeFileSync(path12.join(macosDir, "launch.sh"), launchScript);
4878
+ fs7.chmodSync(path12.join(macosDir, "launch.sh"), 493);
4879
+ logger17.info("macOS protocol handler registered", { appDir });
4391
4880
  }
4392
4881
  function registerLinux() {
4393
4882
  const desktopFile = `[Desktop Entry]
@@ -4396,54 +4885,54 @@ Exec=${process.execPath} ${getBridgeExePath()} launch --url %u
4396
4885
  Type=Application
4397
4886
  NoDisplay=true
4398
4887
  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);
4888
+ const desktopPath = path12.join(os7.homedir(), ".local", "share", "applications", "ahchat-bridge.desktop");
4889
+ fs7.mkdirSync(path12.dirname(desktopPath), { recursive: true });
4890
+ fs7.writeFileSync(desktopPath, desktopFile);
4402
4891
  try {
4403
4892
  execSync("update-desktop-database ~/.local/share/applications/", { stdio: "pipe" });
4404
4893
  } catch {
4405
4894
  }
4406
- logger15.info("Linux protocol handler registered", { desktopPath });
4895
+ logger17.info("Linux protocol handler registered", { desktopPath });
4407
4896
  }
4408
4897
  function unregisterProtocolHandler() {
4409
- const platform = os6.platform();
4898
+ const platform = os7.platform();
4410
4899
  if (platform === "win32") {
4411
4900
  try {
4412
4901
  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");
4902
+ const psScriptPath = path12.join(os7.homedir(), ".ahchat", "launch-bridge.ps1");
4903
+ const urlFilePath = path12.join(os7.homedir(), ".ahchat", ".bridge-launch-url");
4415
4904
  try {
4416
- fs6.unlinkSync(psScriptPath);
4905
+ fs7.unlinkSync(psScriptPath);
4417
4906
  } catch {
4418
4907
  }
4419
4908
  try {
4420
- fs6.unlinkSync(urlFilePath);
4909
+ fs7.unlinkSync(urlFilePath);
4421
4910
  } catch {
4422
4911
  }
4423
- logger15.info("Windows protocol handler unregistered");
4912
+ logger17.info("Windows protocol handler unregistered");
4424
4913
  } catch (e) {
4425
- logger15.warn("Failed to unregister Windows protocol handler", { error: e });
4914
+ logger17.warn("Failed to unregister Windows protocol handler", { error: e });
4426
4915
  }
4427
4916
  } else if (platform === "darwin") {
4428
- const appDir = path10.join(os6.homedir(), "Applications", "AHChatBridge.app");
4917
+ const appDir = path12.join(os7.homedir(), "Applications", "AHChatBridge.app");
4429
4918
  try {
4430
- fs6.rmSync(appDir, { recursive: true, force: true });
4431
- logger15.info("macOS protocol handler unregistered");
4919
+ fs7.rmSync(appDir, { recursive: true, force: true });
4920
+ logger17.info("macOS protocol handler unregistered");
4432
4921
  } catch (e) {
4433
- logger15.warn("Failed to unregister macOS protocol handler", { error: e });
4922
+ logger17.warn("Failed to unregister macOS protocol handler", { error: e });
4434
4923
  }
4435
4924
  } else {
4436
- const desktopPath = path10.join(os6.homedir(), ".local", "share", "applications", "ahchat-bridge.desktop");
4925
+ const desktopPath = path12.join(os7.homedir(), ".local", "share", "applications", "ahchat-bridge.desktop");
4437
4926
  try {
4438
- fs6.unlinkSync(desktopPath);
4439
- logger15.info("Linux protocol handler unregistered");
4927
+ fs7.unlinkSync(desktopPath);
4928
+ logger17.info("Linux protocol handler unregistered");
4440
4929
  } catch (e) {
4441
- logger15.warn("Failed to unregister Linux protocol handler", { error: e });
4930
+ logger17.warn("Failed to unregister Linux protocol handler", { error: e });
4442
4931
  }
4443
4932
  }
4444
4933
  }
4445
4934
  function isProtocolRegistered() {
4446
- const platform = os6.platform();
4935
+ const platform = os7.platform();
4447
4936
  if (platform === "win32") {
4448
4937
  try {
4449
4938
  execSync('REG QUERY "HKCU\\Software\\Classes\\ahchat" /ve', { stdio: "pipe" });
@@ -4452,16 +4941,16 @@ function isProtocolRegistered() {
4452
4941
  return false;
4453
4942
  }
4454
4943
  } else if (platform === "darwin") {
4455
- const appDir = path10.join(os6.homedir(), "Applications", "AHChatBridge.app");
4456
- return fs6.existsSync(path10.join(appDir, "Contents", "Info.plist"));
4944
+ const appDir = path12.join(os7.homedir(), "Applications", "AHChatBridge.app");
4945
+ return fs7.existsSync(path12.join(appDir, "Contents", "Info.plist"));
4457
4946
  } else {
4458
- const desktopPath = path10.join(os6.homedir(), ".local", "share", "applications", "ahchat-bridge.desktop");
4459
- return fs6.existsSync(desktopPath);
4947
+ const desktopPath = path12.join(os7.homedir(), ".local", "share", "applications", "ahchat-bridge.desktop");
4948
+ return fs7.existsSync(desktopPath);
4460
4949
  }
4461
4950
  }
4462
4951
 
4463
4952
  // src/cli.ts
4464
- var logger16 = createModuleLogger("bridge");
4953
+ var logger18 = createModuleLogger("bridge");
4465
4954
  function parseAhchatUrl(url) {
4466
4955
  try {
4467
4956
  if (!url.startsWith("ahchat://")) return null;
@@ -4481,12 +4970,12 @@ function parseAhchatUrl(url) {
4481
4970
  }
4482
4971
  async function run(args) {
4483
4972
  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() };
4973
+ if (args.serverUrl) {
4974
+ const wsUrl = new URL(args.serverUrl);
4975
+ const httpBase = `${wsUrl.protocol === "wss:" ? "https" : "http"}://${wsUrl.host}`;
4976
+ config = { ...config, serverUrl: args.serverUrl, serverApiUrl: httpBase };
4489
4977
  }
4978
+ if (args.token) config = { ...config, bridgeToken: args.token };
4490
4979
  if (args.dataDir) config = { ...config, dataDir: args.dataDir };
4491
4980
  if (args.logLevel) config = { ...config, logLevel: args.logLevel };
4492
4981
  await startBridge(config);
@@ -4494,7 +4983,7 @@ async function run(args) {
4494
4983
  var cli = cac("ahchat-bridge");
4495
4984
  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
4985
  void run(args).catch((e) => {
4497
- logger16.error("Bridge failed to start", { error: e });
4986
+ logger18.error("Bridge failed to start", { error: e });
4498
4987
  process.exit(1);
4499
4988
  });
4500
4989
  });
@@ -4505,7 +4994,7 @@ cli.command("launch", "Launch bridge from ahchat:// URL (called by OS)").option(
4505
4994
  process.exit(1);
4506
4995
  }
4507
4996
  void run({ serverUrl: parsed.serverUrl, token: parsed.token }).catch((e) => {
4508
- logger16.error("Bridge failed to start from URL", { error: e });
4997
+ logger18.error("Bridge failed to start from URL", { error: e });
4509
4998
  process.exit(1);
4510
4999
  });
4511
5000
  });