@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 +765 -276
- package/dist/index.js +709 -220
- package/package.json +32 -30
- package/dist/chunk-7SODRWIG.js +0 -4525
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:
|
|
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 =
|
|
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 =
|
|
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:
|
|
919
|
+
const { interval, path: path13, intervalBoundary } = this.options;
|
|
920
920
|
for (let index = 1; index < 1e3; ++index) {
|
|
921
|
-
const filename =
|
|
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:
|
|
951
|
+
const { compress, path: path13, rotate } = this.options;
|
|
952
952
|
let rotatedName = "";
|
|
953
953
|
for (let count = rotate; count > 0; --count) {
|
|
954
|
-
const currName =
|
|
955
|
-
const prevName = count === 1 ? this.filename :
|
|
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
|
|
1464
|
+
import fs3 from "fs/promises";
|
|
1393
1465
|
import os4 from "os";
|
|
1394
|
-
import
|
|
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
|
|
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
|
-
|
|
1701
|
+
logger2.warn("AskQuestion timeout", { questionId, agentId, timeoutMs });
|
|
1597
1702
|
try {
|
|
1598
1703
|
onTimeout();
|
|
1599
1704
|
} catch (e) {
|
|
1600
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1875
|
+
import path6 from "path";
|
|
1771
1876
|
|
|
1772
1877
|
// ../shared/src/utils/pathSafety.ts
|
|
1773
|
-
import
|
|
1878
|
+
import path5 from "path";
|
|
1774
1879
|
function isPathInside(parent, child) {
|
|
1775
|
-
const resolvedParent =
|
|
1776
|
-
const resolvedChild =
|
|
1880
|
+
const resolvedParent = path5.resolve(parent);
|
|
1881
|
+
const resolvedChild = path5.resolve(child);
|
|
1777
1882
|
if (resolvedParent === resolvedChild) return true;
|
|
1778
|
-
const rel =
|
|
1883
|
+
const rel = path5.relative(resolvedParent, resolvedChild);
|
|
1779
1884
|
if (rel === "") return true;
|
|
1780
|
-
return !rel.startsWith("..") && !
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2180
|
+
tools
|
|
1917
2181
|
});
|
|
1918
|
-
|
|
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:
|
|
2187
|
+
tools: toolNames
|
|
1922
2188
|
});
|
|
1923
2189
|
return neuralServer;
|
|
1924
2190
|
}
|
|
1925
2191
|
|
|
1926
2192
|
// src/sdkEventMapper.ts
|
|
1927
|
-
var
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
2488
|
-
this.claudeConfigDir =
|
|
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 ??
|
|
2495
|
-
this.claudeConfigDir = options?.claudeConfigDir ??
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2907
|
+
await fs3.mkdir(agentCwd, { recursive: true });
|
|
2627
2908
|
const cfg = parseAgentConfig(agentConfig.config);
|
|
2628
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
3271
|
+
cwd = agentConfig.workingDirectory || path7.join(this.workspacesDir, agentConfig.id);
|
|
2932
3272
|
}
|
|
2933
3273
|
void this.acquire(agentConfig, targetScope, cwd).then(() => {
|
|
2934
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
3407
|
+
const cwd = agent.workingDirectory || path7.join(this.workspacesDir, agent.id);
|
|
3068
3408
|
await this.acquire(agent, { kind: "single" }, cwd);
|
|
3069
3409
|
warmed++;
|
|
3070
|
-
|
|
3410
|
+
logger7.info("Agent process pre-created for recovery", { agentId: agent.id });
|
|
3071
3411
|
} catch (err) {
|
|
3072
3412
|
if (err instanceof BridgeBusyError) {
|
|
3073
|
-
|
|
3413
|
+
logger7.warn("Recovery stopped: bridge busy", { agentId: agent.id });
|
|
3074
3414
|
break;
|
|
3075
3415
|
}
|
|
3076
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3564
|
+
logger7.info("Agent process shut down", { agentId: proc.agentId, scope: scopeKey(proc.scope), key });
|
|
3183
3565
|
} catch (err) {
|
|
3184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3627
|
+
logger7.error("cancelReply: inputController.close failed", { agentId, error: err });
|
|
3246
3628
|
}
|
|
3247
3629
|
runtime.query.return(void 0).catch((err) => {
|
|
3248
|
-
|
|
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
|
|
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
|
|
3275
|
-
return `${base}${
|
|
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
|
-
|
|
3675
|
+
logger8.warn("Agent registry refresh unreachable after retry, keeping cache");
|
|
3294
3676
|
return;
|
|
3295
3677
|
}
|
|
3296
3678
|
if (!res.ok) {
|
|
3297
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3695
|
+
logger8.info("Agent registry refreshed", { count: this.agents.size, recoveredAfterRetry });
|
|
3314
3696
|
} catch (e) {
|
|
3315
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3717
|
+
logger8.info("Agent registry fetchById upserted", { agentId: id });
|
|
3336
3718
|
}
|
|
3337
3719
|
return agent;
|
|
3338
3720
|
} catch (e) {
|
|
3339
|
-
|
|
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
|
-
|
|
3730
|
+
logger8.debug("Agent registry upsert", { agentId: agent.id });
|
|
3349
3731
|
}
|
|
3350
3732
|
remove(agentId) {
|
|
3351
3733
|
this.agents.delete(agentId);
|
|
3352
|
-
|
|
3734
|
+
logger8.debug("Agent registry remove", { agentId });
|
|
3353
3735
|
}
|
|
3354
3736
|
};
|
|
3355
3737
|
|
|
3356
3738
|
// src/groupRegistry.ts
|
|
3357
|
-
var
|
|
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
|
-
|
|
3762
|
+
logger9.warn("GroupRegistry refresh unreachable after retry");
|
|
3381
3763
|
return;
|
|
3382
3764
|
}
|
|
3383
3765
|
if (!res.ok) {
|
|
3384
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3787
|
+
logger9.info("GroupRegistry refreshed", { count: this.groups.size, recoveredAfterRetry });
|
|
3406
3788
|
} catch (e) {
|
|
3407
|
-
|
|
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
|
-
|
|
3839
|
+
logger9.info("GroupRegistry resolveScope: group not found", { rawScope, suffix });
|
|
3447
3840
|
return null;
|
|
3448
3841
|
}
|
|
3449
3842
|
if (!res.ok) {
|
|
3450
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3892
|
+
logger9.info("GroupRegistry resolved single conv", { agentId, conversationId: single.id });
|
|
3500
3893
|
return single.id;
|
|
3501
3894
|
}
|
|
3502
3895
|
}
|
|
3503
3896
|
} else {
|
|
3504
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3910
|
+
logger9.warn("GroupRegistry resolveSingle: created conv missing id", { agentId });
|
|
3518
3911
|
return null;
|
|
3519
3912
|
}
|
|
3520
|
-
|
|
3913
|
+
logger9.info("GroupRegistry created single conv", { agentId, conversationId: conv.id });
|
|
3521
3914
|
return conv.id;
|
|
3522
3915
|
} catch (e) {
|
|
3523
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3977
|
+
logger10.error("WebSocket error", { error: err });
|
|
3584
3978
|
});
|
|
3585
3979
|
}
|
|
3586
3980
|
async handleOpen() {
|
|
3587
3981
|
try {
|
|
3588
3982
|
await this.onConnected();
|
|
3589
|
-
|
|
3983
|
+
logger10.info("Recovery complete, sending bridge:register");
|
|
3590
3984
|
} catch (err) {
|
|
3591
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4066
|
+
logger10.error("onServerPush handler failed", { error: err, type: msg.type });
|
|
3671
4067
|
});
|
|
3672
4068
|
}
|
|
3673
4069
|
return;
|
|
3674
4070
|
}
|
|
3675
4071
|
default: {
|
|
3676
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4112
|
+
logger10.error("Error closing WebSocket", { error: e });
|
|
3717
4113
|
}
|
|
3718
4114
|
this.ws = null;
|
|
3719
4115
|
}
|
|
3720
|
-
|
|
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
|
|
3729
|
-
import
|
|
3730
|
-
import
|
|
3731
|
-
var
|
|
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 ??
|
|
3735
|
-
await
|
|
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
|
-
|
|
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
|
|
3793
|
-
import
|
|
3794
|
-
var
|
|
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 =
|
|
4203
|
+
const file = path9.join(dataDir, "bridge.lock");
|
|
3808
4204
|
lockPath = file;
|
|
3809
|
-
if (
|
|
3810
|
-
const raw =
|
|
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
|
-
|
|
4212
|
+
logger12.warn("Removing stale bridge.lock (process not found)", { pid, path: file });
|
|
3817
4213
|
}
|
|
3818
4214
|
}
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
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 &&
|
|
3825
|
-
const current =
|
|
4220
|
+
if (lockPath && fs5.existsSync(lockPath)) {
|
|
4221
|
+
const current = fs5.readFileSync(lockPath, "utf-8").trim();
|
|
3826
4222
|
if (current === String(process.pid)) {
|
|
3827
|
-
|
|
3828
|
-
|
|
4223
|
+
fs5.unlinkSync(lockPath);
|
|
4224
|
+
logger12.info("Released bridge lock", { path: lockPath });
|
|
3829
4225
|
}
|
|
3830
4226
|
}
|
|
3831
4227
|
} catch (e) {
|
|
3832
|
-
|
|
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
|
|
4331
|
+
var logger13 = createModuleLogger("msg.handler");
|
|
3936
4332
|
function emitTaskAck(emit, ackId, agentId, traceId) {
|
|
3937
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4087
|
-
import
|
|
4088
|
-
var
|
|
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 =
|
|
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 (!
|
|
4129
|
-
const raw =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
4154
|
-
|
|
4155
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4290
|
-
import
|
|
4291
|
-
import
|
|
4778
|
+
import fs7 from "fs";
|
|
4779
|
+
import os7 from "os";
|
|
4780
|
+
import path12 from "path";
|
|
4292
4781
|
import { fileURLToPath } from "url";
|
|
4293
|
-
var
|
|
4782
|
+
var logger17 = createModuleLogger("bridge.protocol");
|
|
4294
4783
|
var __filename = fileURLToPath(import.meta.url);
|
|
4295
|
-
var __dirname =
|
|
4784
|
+
var __dirname = path12.dirname(__filename);
|
|
4296
4785
|
function getBridgeExePath() {
|
|
4297
|
-
const pkgDir =
|
|
4298
|
-
return
|
|
4786
|
+
const pkgDir = path12.resolve(__dirname, "..");
|
|
4787
|
+
return path12.join(pkgDir, "dist", "cli.js");
|
|
4299
4788
|
}
|
|
4300
4789
|
function registerProtocolHandler() {
|
|
4301
|
-
const 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
|
-
|
|
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 =
|
|
4315
|
-
const urlFilePath =
|
|
4316
|
-
|
|
4317
|
-
const psScriptPath =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4835
|
+
logger17.info("Windows protocol handler registered", { psScriptPath });
|
|
4347
4836
|
}
|
|
4348
4837
|
function registerMacOS() {
|
|
4349
|
-
const appDir =
|
|
4350
|
-
const contentsDir =
|
|
4351
|
-
const macosDir =
|
|
4352
|
-
const resourcesDir =
|
|
4353
|
-
|
|
4354
|
-
|
|
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
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
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 =
|
|
4400
|
-
|
|
4401
|
-
|
|
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
|
-
|
|
4895
|
+
logger17.info("Linux protocol handler registered", { desktopPath });
|
|
4407
4896
|
}
|
|
4408
4897
|
function unregisterProtocolHandler() {
|
|
4409
|
-
const 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 =
|
|
4414
|
-
const urlFilePath =
|
|
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
|
-
|
|
4905
|
+
fs7.unlinkSync(psScriptPath);
|
|
4417
4906
|
} catch {
|
|
4418
4907
|
}
|
|
4419
4908
|
try {
|
|
4420
|
-
|
|
4909
|
+
fs7.unlinkSync(urlFilePath);
|
|
4421
4910
|
} catch {
|
|
4422
4911
|
}
|
|
4423
|
-
|
|
4912
|
+
logger17.info("Windows protocol handler unregistered");
|
|
4424
4913
|
} catch (e) {
|
|
4425
|
-
|
|
4914
|
+
logger17.warn("Failed to unregister Windows protocol handler", { error: e });
|
|
4426
4915
|
}
|
|
4427
4916
|
} else if (platform === "darwin") {
|
|
4428
|
-
const appDir =
|
|
4917
|
+
const appDir = path12.join(os7.homedir(), "Applications", "AHChatBridge.app");
|
|
4429
4918
|
try {
|
|
4430
|
-
|
|
4431
|
-
|
|
4919
|
+
fs7.rmSync(appDir, { recursive: true, force: true });
|
|
4920
|
+
logger17.info("macOS protocol handler unregistered");
|
|
4432
4921
|
} catch (e) {
|
|
4433
|
-
|
|
4922
|
+
logger17.warn("Failed to unregister macOS protocol handler", { error: e });
|
|
4434
4923
|
}
|
|
4435
4924
|
} else {
|
|
4436
|
-
const desktopPath =
|
|
4925
|
+
const desktopPath = path12.join(os7.homedir(), ".local", "share", "applications", "ahchat-bridge.desktop");
|
|
4437
4926
|
try {
|
|
4438
|
-
|
|
4439
|
-
|
|
4927
|
+
fs7.unlinkSync(desktopPath);
|
|
4928
|
+
logger17.info("Linux protocol handler unregistered");
|
|
4440
4929
|
} catch (e) {
|
|
4441
|
-
|
|
4930
|
+
logger17.warn("Failed to unregister Linux protocol handler", { error: e });
|
|
4442
4931
|
}
|
|
4443
4932
|
}
|
|
4444
4933
|
}
|
|
4445
4934
|
function isProtocolRegistered() {
|
|
4446
|
-
const 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 =
|
|
4456
|
-
return
|
|
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 =
|
|
4459
|
-
return
|
|
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
|
|
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)
|
|
4485
|
-
|
|
4486
|
-
const
|
|
4487
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4997
|
+
logger18.error("Bridge failed to start from URL", { error: e });
|
|
4509
4998
|
process.exit(1);
|
|
4510
4999
|
});
|
|
4511
5000
|
});
|