clay-server 2.17.0 → 2.18.0-beta.2
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/lib/mates.js +85 -6
- package/lib/project.js +339 -2
- package/lib/public/app.js +50 -14
- package/lib/public/css/base.css +1 -0
- package/lib/public/css/input.css +5 -0
- package/lib/public/css/mates.css +3 -8
- package/lib/public/css/mention.css +226 -0
- package/lib/public/css/sidebar.css +7 -0
- package/lib/public/index.html +1 -0
- package/lib/public/modules/input.js +40 -0
- package/lib/public/modules/mention.js +650 -0
- package/lib/public/modules/notifications.js +9 -3
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +185 -0
- package/lib/sessions.js +13 -0
- package/package.json +1 -1
package/lib/sdk-bridge.js
CHANGED
|
@@ -1115,6 +1115,14 @@ function createSDKBridge(opts) {
|
|
|
1115
1115
|
return Promise.resolve({ behavior: "allow", updatedInput: input });
|
|
1116
1116
|
}
|
|
1117
1117
|
|
|
1118
|
+
// Mate sessions (DM): auto-approve read-only tools
|
|
1119
|
+
if (mateDisplayName) {
|
|
1120
|
+
var mateReadTools = { Read: true, Glob: true, Grep: true };
|
|
1121
|
+
if (mateReadTools[toolName]) {
|
|
1122
|
+
return Promise.resolve({ behavior: "allow", updatedInput: input });
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1118
1126
|
// AskUserQuestion: wait for user answers via WebSocket
|
|
1119
1127
|
if (toolName === "AskUserQuestion") {
|
|
1120
1128
|
return new Promise(function(resolve) {
|
|
@@ -1729,6 +1737,182 @@ function createSDKBridge(opts) {
|
|
|
1729
1737
|
}
|
|
1730
1738
|
}
|
|
1731
1739
|
|
|
1740
|
+
// --- @Mention: persistent read-only session for a mentioned Mate ---
|
|
1741
|
+
// Creates a mention session that can be reused across multiple mentions
|
|
1742
|
+
// within a conversation flow (session continuity).
|
|
1743
|
+
async function createMentionSession(opts) {
|
|
1744
|
+
// opts: { claudeMd, initialContext, initialMessage, onDelta, onDone, onError, onActivity }
|
|
1745
|
+
var sdk;
|
|
1746
|
+
try {
|
|
1747
|
+
sdk = await getSDK();
|
|
1748
|
+
} catch (e) {
|
|
1749
|
+
opts.onError("Failed to load Claude SDK: " + (e.message || e));
|
|
1750
|
+
return null;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
var mq = createMessageQueue();
|
|
1754
|
+
var abortController = new AbortController();
|
|
1755
|
+
|
|
1756
|
+
// Current response callbacks (swapped on each pushMessage)
|
|
1757
|
+
var currentOnDelta = opts.onDelta;
|
|
1758
|
+
var currentOnDone = opts.onDone;
|
|
1759
|
+
var currentOnError = opts.onError;
|
|
1760
|
+
var currentOnActivity = opts.onActivity || null;
|
|
1761
|
+
var responseFullText = "";
|
|
1762
|
+
var responseStreamedText = false;
|
|
1763
|
+
var mentionBlocks = {};
|
|
1764
|
+
var alive = true;
|
|
1765
|
+
|
|
1766
|
+
var query;
|
|
1767
|
+
try {
|
|
1768
|
+
query = sdk.query({
|
|
1769
|
+
prompt: mq,
|
|
1770
|
+
options: {
|
|
1771
|
+
cwd: cwd,
|
|
1772
|
+
systemPrompt: opts.claudeMd,
|
|
1773
|
+
settingSources: ["user"],
|
|
1774
|
+
includePartialMessages: true,
|
|
1775
|
+
abortController: abortController,
|
|
1776
|
+
canUseTool: function (toolName, input) {
|
|
1777
|
+
var allowed = { Read: true, Glob: true, Grep: true };
|
|
1778
|
+
if (allowed[toolName]) {
|
|
1779
|
+
return Promise.resolve({ behavior: "allow", updatedInput: input });
|
|
1780
|
+
}
|
|
1781
|
+
return Promise.resolve({
|
|
1782
|
+
behavior: "deny",
|
|
1783
|
+
message: "Read-only access. You cannot make changes via @mention.",
|
|
1784
|
+
});
|
|
1785
|
+
},
|
|
1786
|
+
},
|
|
1787
|
+
});
|
|
1788
|
+
} catch (e) {
|
|
1789
|
+
opts.onError("Failed to create mention query: " + (e.message || e));
|
|
1790
|
+
return null;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// Push the initial message (context + question)
|
|
1794
|
+
var initialPrompt = opts.initialContext + "\n\n" + opts.initialMessage;
|
|
1795
|
+
mq.push({
|
|
1796
|
+
type: "user",
|
|
1797
|
+
message: { role: "user", content: [{ type: "text", text: initialPrompt }] },
|
|
1798
|
+
});
|
|
1799
|
+
|
|
1800
|
+
// Background stream processing loop
|
|
1801
|
+
(async function () {
|
|
1802
|
+
try {
|
|
1803
|
+
for await (var sdkMsg of query) {
|
|
1804
|
+
if (sdkMsg.type === "stream_event" && sdkMsg.event) {
|
|
1805
|
+
var evt = sdkMsg.event;
|
|
1806
|
+
|
|
1807
|
+
// Track content blocks for activity reporting
|
|
1808
|
+
if (evt.type === "content_block_start") {
|
|
1809
|
+
var block = evt.content_block;
|
|
1810
|
+
var idx = evt.index;
|
|
1811
|
+
if (block.type === "thinking") {
|
|
1812
|
+
mentionBlocks[idx] = { type: "thinking" };
|
|
1813
|
+
if (currentOnActivity) currentOnActivity("thinking");
|
|
1814
|
+
} else if (block.type === "tool_use") {
|
|
1815
|
+
mentionBlocks[idx] = { type: "tool_use", name: block.name, inputJson: "" };
|
|
1816
|
+
var toolLabel = block.name;
|
|
1817
|
+
if (toolLabel === "Read") toolLabel = "Reading file...";
|
|
1818
|
+
else if (toolLabel === "Grep") toolLabel = "Searching code...";
|
|
1819
|
+
else if (toolLabel === "Glob") toolLabel = "Finding files...";
|
|
1820
|
+
if (currentOnActivity) currentOnActivity(toolLabel);
|
|
1821
|
+
} else if (block.type === "text") {
|
|
1822
|
+
mentionBlocks[idx] = { type: "text" };
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
if (evt.type === "content_block_delta" && evt.delta) {
|
|
1827
|
+
if (evt.delta.type === "text_delta" && typeof evt.delta.text === "string") {
|
|
1828
|
+
responseStreamedText = true;
|
|
1829
|
+
responseFullText += evt.delta.text;
|
|
1830
|
+
if (currentOnActivity) currentOnActivity(null); // clear activity on text
|
|
1831
|
+
if (currentOnDelta) currentOnDelta(evt.delta.text);
|
|
1832
|
+
} else if (evt.delta.type === "input_json_delta" && mentionBlocks[evt.index]) {
|
|
1833
|
+
mentionBlocks[evt.index].inputJson += evt.delta.partial_json;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
if (evt.type === "content_block_stop") {
|
|
1838
|
+
var blk = mentionBlocks[evt.index];
|
|
1839
|
+
if (blk && blk.type === "tool_use") {
|
|
1840
|
+
// Show what file is being read
|
|
1841
|
+
var toolInput = {};
|
|
1842
|
+
try { toolInput = JSON.parse(blk.inputJson); } catch (e) {}
|
|
1843
|
+
if (blk.name === "Read" && toolInput.file_path) {
|
|
1844
|
+
var fname = toolInput.file_path.split(/[/\\]/).pop();
|
|
1845
|
+
if (currentOnActivity) currentOnActivity("Reading " + fname + "...");
|
|
1846
|
+
} else if (blk.name === "Grep" && toolInput.pattern) {
|
|
1847
|
+
if (currentOnActivity) currentOnActivity("Searching: " + toolInput.pattern.substring(0, 30) + "...");
|
|
1848
|
+
} else if (blk.name === "Glob" && toolInput.pattern) {
|
|
1849
|
+
if (currentOnActivity) currentOnActivity("Finding: " + toolInput.pattern.substring(0, 30) + "...");
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
delete mentionBlocks[evt.index];
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
} else if (sdkMsg.type === "assistant" && !responseStreamedText && sdkMsg.message && sdkMsg.message.content) {
|
|
1856
|
+
// Fallback: if text was not streamed via deltas, extract from assistant message
|
|
1857
|
+
var content = sdkMsg.message.content;
|
|
1858
|
+
if (Array.isArray(content)) {
|
|
1859
|
+
for (var ci = 0; ci < content.length; ci++) {
|
|
1860
|
+
if (content[ci].type === "text" && content[ci].text) {
|
|
1861
|
+
responseFullText += content[ci].text;
|
|
1862
|
+
if (currentOnDelta) currentOnDelta(content[ci].text);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
} else if (sdkMsg.type === "result") {
|
|
1867
|
+
// One response complete. Signal done and reset for next message.
|
|
1868
|
+
if (currentOnActivity) currentOnActivity(null);
|
|
1869
|
+
if (currentOnDone) {
|
|
1870
|
+
currentOnDone(responseFullText);
|
|
1871
|
+
}
|
|
1872
|
+
currentOnDelta = null;
|
|
1873
|
+
currentOnDone = null;
|
|
1874
|
+
currentOnError = null;
|
|
1875
|
+
currentOnActivity = null;
|
|
1876
|
+
mentionBlocks = {};
|
|
1877
|
+
responseFullText = "";
|
|
1878
|
+
responseStreamedText = false;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
} catch (err) {
|
|
1882
|
+
if (currentOnError) {
|
|
1883
|
+
if (err.name === "AbortError" || (abortController && abortController.signal.aborted)) {
|
|
1884
|
+
currentOnError("Mention query was cancelled.");
|
|
1885
|
+
} else {
|
|
1886
|
+
currentOnError(err.message || String(err));
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
alive = false;
|
|
1891
|
+
})();
|
|
1892
|
+
|
|
1893
|
+
return {
|
|
1894
|
+
// Push a follow-up message to the existing mention session
|
|
1895
|
+
pushMessage: function (text, callbacks) {
|
|
1896
|
+
currentOnDelta = callbacks.onDelta;
|
|
1897
|
+
currentOnDone = callbacks.onDone;
|
|
1898
|
+
currentOnError = callbacks.onError;
|
|
1899
|
+
currentOnActivity = callbacks.onActivity || null;
|
|
1900
|
+
mentionBlocks = {};
|
|
1901
|
+
responseFullText = "";
|
|
1902
|
+
responseStreamedText = false;
|
|
1903
|
+
mq.push({
|
|
1904
|
+
type: "user",
|
|
1905
|
+
message: { role: "user", content: [{ type: "text", text: text }] },
|
|
1906
|
+
});
|
|
1907
|
+
},
|
|
1908
|
+
close: function () {
|
|
1909
|
+
alive = false;
|
|
1910
|
+
try { mq.end(); } catch (e) {}
|
|
1911
|
+
},
|
|
1912
|
+
isAlive: function () { return alive; },
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1732
1916
|
return {
|
|
1733
1917
|
createMessageQueue: createMessageQueue,
|
|
1734
1918
|
processSDKMessage: processSDKMessage,
|
|
@@ -1746,6 +1930,7 @@ function createSDKBridge(opts) {
|
|
|
1746
1930
|
permissionPushBody: permissionPushBody,
|
|
1747
1931
|
warmup: warmup,
|
|
1748
1932
|
stopTask: stopTask,
|
|
1933
|
+
createMentionSession: createMentionSession,
|
|
1749
1934
|
};
|
|
1750
1935
|
}
|
|
1751
1936
|
|
package/lib/sessions.js
CHANGED
|
@@ -395,6 +395,16 @@ function createSessionManager(opts) {
|
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
+
function cleanupMentionSessions(session) {
|
|
399
|
+
if (session._mentionSessions) {
|
|
400
|
+
var mateIds = Object.keys(session._mentionSessions);
|
|
401
|
+
for (var mi = 0; mi < mateIds.length; mi++) {
|
|
402
|
+
try { session._mentionSessions[mateIds[mi]].close(); } catch (e) {}
|
|
403
|
+
}
|
|
404
|
+
session._mentionSessions = {};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
398
408
|
function deleteSession(localId, targetWs) {
|
|
399
409
|
var session = sessions.get(localId);
|
|
400
410
|
if (!session) return;
|
|
@@ -402,6 +412,8 @@ function createSessionManager(opts) {
|
|
|
402
412
|
// Clean up unread tracking
|
|
403
413
|
delete singleUserUnread[localId];
|
|
404
414
|
|
|
415
|
+
cleanupMentionSessions(session);
|
|
416
|
+
|
|
405
417
|
if (session.abortController) {
|
|
406
418
|
try { session.abortController.abort(); } catch(e) {}
|
|
407
419
|
}
|
|
@@ -431,6 +443,7 @@ function createSessionManager(opts) {
|
|
|
431
443
|
var session = sessions.get(localId);
|
|
432
444
|
if (!session) return;
|
|
433
445
|
delete singleUserUnread[localId];
|
|
446
|
+
cleanupMentionSessions(session);
|
|
434
447
|
if (session.abortController) {
|
|
435
448
|
try { session.abortController.abort(); } catch(e) {}
|
|
436
449
|
}
|