@cryptiklemur/lattice 5.10.0 → 5.11.0
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/client/assets/{angular-html-BDIcxkJq.js → angular-html-BoFzmWT8.js} +1 -1
- package/dist/client/assets/{angular-ts-Bt22ouNH.js → angular-ts-DZnI8rKE.js} +1 -1
- package/dist/client/assets/{apl-p8qkxzEK.js → apl-DstVmncE.js} +1 -1
- package/dist/client/assets/{astro-CIaMc49M.js → astro-DTPCjzEx.js} +1 -1
- package/dist/client/assets/{blade-BR56EAMD.js → blade-6q42Ss3F.js} +1 -1
- package/dist/client/assets/{c-Dli0HzAh.js → c-BQDGJ-nQ.js} +1 -1
- package/dist/client/assets/{cobol-Cad15ECy.js → cobol-Dlh0WvsZ.js} +1 -1
- package/dist/client/assets/{coffee-DpyATEbF.js → coffee-DdQv129j.js} +1 -1
- package/dist/client/assets/{cpp-KN8_NFsf.js → cpp-DhbQJIv4.js} +1 -1
- package/dist/client/assets/{crystal-CuyGv0kh.js → crystal-C22kERUB.js} +1 -1
- package/dist/client/assets/{css-Cm3q4bxn.js → css-n31O5kHj.js} +1 -1
- package/dist/client/assets/{dist-BjxsMc4u.js → dist-D8okl7lw.js} +2 -2
- package/dist/client/assets/{edge-B6S7CSbx.js → edge-Cgwx-o_7.js} +1 -1
- package/dist/client/assets/{elixir-CNUy9H8T.js → elixir-DAGM2WKD.js} +1 -1
- package/dist/client/assets/{elm-CNfcWmb9.js → elm-BLw_7oO9.js} +1 -1
- package/dist/client/assets/{erb-DWebzDaI.js → erb-DCaNhYa7.js} +1 -1
- package/dist/client/assets/{git-rebase-B_Pt2ZBK.js → git-rebase-CNNhb8-g.js} +1 -1
- package/dist/client/assets/{glimmer-js-CVwoOd72.js → glimmer-js-BnZd88Wi.js} +1 -1
- package/dist/client/assets/{glimmer-ts-CjtFSxjz.js → glimmer-ts-DvFNbZu-.js} +1 -1
- package/dist/client/assets/{glsl-CP4rggAA.js → glsl-Dnrk_Jnx.js} +1 -1
- package/dist/client/assets/{graphql-Dbm6sAtp.js → graphql-DlWTPvCG.js} +1 -1
- package/dist/client/assets/{hack-Bj9y3SGf.js → hack-DQg1Ek33.js} +1 -1
- package/dist/client/assets/{haml-DRGrdf3f.js → haml-DSk45qIE.js} +1 -1
- package/dist/client/assets/{handlebars-CFKjcBMg.js → handlebars-DuLvATB2.js} +1 -1
- package/dist/client/assets/{html-Vcd4eHHg.js → html-D4DiUnLg.js} +1 -1
- package/dist/client/assets/{html-derivative-BF0YbD4L.js → html-derivative-CS5MZ6d9.js} +1 -1
- package/dist/client/assets/{http-CGVTa2NT.js → http-CkDncfer.js} +1 -1
- package/dist/client/assets/{hurl-B0GrsGqd.js → hurl-DU39oO3U.js} +1 -1
- package/dist/client/assets/{index-CX1tudsF.js → index-CHPfE1Zl.js} +129 -129
- package/dist/client/assets/index-DHUKmLLC.css +2 -0
- package/dist/client/assets/{java-BJHQqHsm.js → java-lntACKEu.js} +1 -1
- package/dist/client/assets/{javascript-CmuMsKrc.js → javascript-CxkFc6nV.js} +1 -1
- package/dist/client/assets/{jinja-JxCLeq1j.js → jinja-DolO2zO7.js} +1 -1
- package/dist/client/assets/{jison-BdgAUhei.js → jison-Cok5FPev.js} +1 -1
- package/dist/client/assets/{json-DtPissHL.js → json-BebuQPrq.js} +1 -1
- package/dist/client/assets/{jsx-DUAxxDkP.js → jsx-iLBaUyXr.js} +1 -1
- package/dist/client/assets/{julia-DxDlbL6e.js → julia-C5Dsc7cH.js} +1 -1
- package/dist/client/assets/{just-CVmAAx2R.js → just-DJYqq_9R.js} +1 -1
- package/dist/client/assets/{latex-uwxggTWA.js → latex-BTTYiKj1.js} +1 -1
- package/dist/client/assets/{liquid-xsETAJJy.js → liquid-DpAKCrOB.js} +1 -1
- package/dist/client/assets/{lua-B2Hh8PgD.js → lua-BZ6b1hko.js} +1 -1
- package/dist/client/assets/{marko-yDeGxD87.js → marko-D8VK6iGt.js} +1 -1
- package/dist/client/assets/{mdc-QMp4ieYR.js → mdc-Paa3XzwY.js} +1 -1
- package/dist/client/assets/{nginx-7gmRmcqz.js → nginx-C5k9mWtJ.js} +1 -1
- package/dist/client/assets/{nim-CA8SNY_7.js → nim-Dst6YSnE.js} +1 -1
- package/dist/client/assets/{perl-lx5nW4VC.js → perl-XhiCjgBp.js} +1 -1
- package/dist/client/assets/{php-DgHiW953.js → php-BcsPLnLU.js} +1 -1
- package/dist/client/assets/{pug-CbbB1vwb.js → pug-GLH9-eAJ.js} +1 -1
- package/dist/client/assets/{qml-COrzwCIh.js → qml-Cj_lJioE.js} +1 -1
- package/dist/client/assets/{r-Dv7pZJDH.js → r-B70aGYK5.js} +1 -1
- package/dist/client/assets/{razor-D2m8EDP5.js → razor-R3gub_zy.js} +1 -1
- package/dist/client/assets/{regexp-BXLT-jPc.js → regexp-itC0dIUJ.js} +1 -1
- package/dist/client/assets/{rst-_S6rrUYh.js → rst-DdyoV8E2.js} +1 -1
- package/dist/client/assets/{ruby-C3XO7tYY.js → ruby-BYBZsv66.js} +1 -1
- package/dist/client/assets/{sas-DP2k4iuN.js → sas-fqfqXqj1.js} +1 -1
- package/dist/client/assets/{scss-lhLFMXGn.js → scss-B-ELv6mu.js} +1 -1
- package/dist/client/assets/{shellscript-BYlBPHen.js → shellscript-BgB8TNw6.js} +1 -1
- package/dist/client/assets/{shellsession-CbVyQKWZ.js → shellsession-BLK2Dgkm.js} +1 -1
- package/dist/client/assets/{soy-Be8a0lHq.js → soy-C7_RmNrp.js} +1 -1
- package/dist/client/assets/{sql-2KxvU9YS.js → sql-AUgbUJq4.js} +1 -1
- package/dist/client/assets/{stata-BxlWftTS.js → stata-CIVqSIOr.js} +1 -1
- package/dist/client/assets/{surrealql-CJ-q86nR.js → surrealql-BzRQzc5S.js} +1 -1
- package/dist/client/assets/{svelte-Q1ml0OiY.js → svelte-BCIwEwtb.js} +1 -1
- package/dist/client/assets/{templ-BbfPZhtu.js → templ-C1hbwe4u.js} +1 -1
- package/dist/client/assets/{tex-Dcth4Gi6.js → tex-CI4tIsaP.js} +1 -1
- package/dist/client/assets/{ts-tags-BKhSOXI3.js → ts-tags-SUeikhEp.js} +1 -1
- package/dist/client/assets/{tsx-CS6iQ0XH.js → tsx-xkp7aIZs.js} +1 -1
- package/dist/client/assets/{twig-BHp31ZxS.js → twig-CGgBSAyc.js} +1 -1
- package/dist/client/assets/{typescript-16YJBTaO.js → typescript-O2YMTl_s.js} +1 -1
- package/dist/client/assets/{vue-CMKwTi4r.js → vue-DsNRxos1.js} +1 -1
- package/dist/client/assets/{vue-html-Dr8VUA2G.js → vue-html-CuY3t7bs.js} +1 -1
- package/dist/client/assets/{vue-vine-DZUqDerl.js → vue-vine-C6kSCKwY.js} +1 -1
- package/dist/client/assets/{xml-CBbBKKDC.js → xml-DafwzOLY.js} +1 -1
- package/dist/client/assets/{xsl-DWEX6PKX.js → xsl-1SGGZibr.js} +1 -1
- package/dist/client/assets/{yaml-DvKvvh3X.js → yaml-DSVhzmhr.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/analytics/engine.js +241 -241
- package/dist/server/assets.js +4 -4
- package/dist/server/auth/passphrase.js +13 -13
- package/dist/server/config.js +7 -7
- package/dist/server/daemon.js +93 -93
- package/dist/server/features/brainstorm.js +42 -42
- package/dist/server/features/ralph-loop.js +33 -33
- package/dist/server/features/scheduler.js +53 -53
- package/dist/server/features/specs.js +54 -54
- package/dist/server/features/sticky-notes.js +17 -17
- package/dist/server/features/superpowers.js +24 -24
- package/dist/server/handlers/analytics.js +1 -1
- package/dist/server/handlers/attachment.js +32 -32
- package/dist/server/handlers/bookmarks.js +4 -4
- package/dist/server/handlers/brainstorm.js +4 -4
- package/dist/server/handlers/chat.js +54 -54
- package/dist/server/handlers/editor.js +13 -13
- package/dist/server/handlers/fs.js +51 -51
- package/dist/server/handlers/hooks.js +20 -20
- package/dist/server/handlers/loop.js +6 -6
- package/dist/server/handlers/memory.js +44 -44
- package/dist/server/handlers/mesh.js +60 -60
- package/dist/server/handlers/notes.js +7 -7
- package/dist/server/handlers/plugins.js +174 -174
- package/dist/server/handlers/project-settings.js +26 -26
- package/dist/server/handlers/scheduler.js +6 -6
- package/dist/server/handlers/session.js +24 -24
- package/dist/server/handlers/settings.js +21 -21
- package/dist/server/handlers/skills.js +91 -91
- package/dist/server/handlers/specs.js +51 -28
- package/dist/server/handlers/terminal.js +13 -13
- package/dist/server/handlers/themes.js +21 -21
- package/dist/server/handlers/update.js +17 -17
- package/dist/server/hooks/event_forward.sh +34 -0
- package/dist/server/hooks/post_tool_use.sh +26 -0
- package/dist/server/hooks/statusline.sh +26 -0
- package/dist/server/identity.js +6 -6
- package/dist/server/index.js +111 -111
- package/dist/server/logger.js +1 -1
- package/dist/server/mesh/connector.js +78 -78
- package/dist/server/mesh/crypto.js +20 -20
- package/dist/server/mesh/discovery.js +14 -14
- package/dist/server/mesh/pairing.js +30 -30
- package/dist/server/mesh/peers.js +10 -10
- package/dist/server/mesh/proxy.js +14 -14
- package/dist/server/mesh/session-sync.js +23 -23
- package/dist/server/project/bookmarks.js +11 -11
- package/dist/server/project/context-breakdown.js +70 -70
- package/dist/server/project/file-browser.js +17 -17
- package/dist/server/project/project-files.js +68 -68
- package/dist/server/project/registry.js +10 -10
- package/dist/server/project/sdk-bridge.js +157 -157
- package/dist/server/project/session.js +201 -199
- package/dist/server/project/terminal.js +15 -15
- package/dist/server/project/warmup.js +37 -37
- package/dist/server/push.js +11 -11
- package/dist/server/runtime.js +1 -1
- package/dist/server/tls.js +15 -15
- package/dist/server/tui.js +15 -15
- package/dist/server/update-checker.js +21 -21
- package/dist/server/ws/broadcast.js +18 -18
- package/dist/server/ws/router.js +17 -17
- package/dist/shared/constants.js +8 -8
- package/package.json +2 -2
- package/dist/client/assets/index-DlfI20Gn.css +0 -2
|
@@ -13,12 +13,12 @@ function formatSdkRule(rule) {
|
|
|
13
13
|
if (!rule.ruleContent)
|
|
14
14
|
return rule.toolName;
|
|
15
15
|
if (rule.toolName === "Bash") {
|
|
16
|
-
|
|
16
|
+
const firstWord = rule.ruleContent.split(/\s+/)[0].replace(/:.*$/, "");
|
|
17
17
|
if (firstWord === "curl" || firstWord === "wget") {
|
|
18
|
-
|
|
18
|
+
const urlMatch = rule.ruleContent.match(/https?:\/\/[^\s"']+/);
|
|
19
19
|
if (urlMatch) {
|
|
20
20
|
try {
|
|
21
|
-
|
|
21
|
+
const parsed = new URL(urlMatch[0]);
|
|
22
22
|
return rule.toolName + "(" + firstWord + ":" + parsed.hostname + ")";
|
|
23
23
|
}
|
|
24
24
|
catch { }
|
|
@@ -29,9 +29,9 @@ function formatSdkRule(rule) {
|
|
|
29
29
|
return rule.toolName + "(" + rule.ruleContent + ")";
|
|
30
30
|
}
|
|
31
31
|
function addProjectAllowRules(projectPath, suggestions, fallbackToolName, fallbackInput) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const claudeDir = join(projectPath, ".claude");
|
|
33
|
+
const settingsPath = join(claudeDir, "settings.json");
|
|
34
|
+
let settings = {};
|
|
35
35
|
if (existsSync(settingsPath)) {
|
|
36
36
|
try {
|
|
37
37
|
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
@@ -43,26 +43,26 @@ function addProjectAllowRules(projectPath, suggestions, fallbackToolName, fallba
|
|
|
43
43
|
if (!settings.permissions) {
|
|
44
44
|
settings.permissions = {};
|
|
45
45
|
}
|
|
46
|
-
|
|
46
|
+
const permissions = settings.permissions;
|
|
47
47
|
if (!Array.isArray(permissions.allow)) {
|
|
48
48
|
permissions.allow = [];
|
|
49
49
|
}
|
|
50
50
|
if (!Array.isArray(permissions.additionalDirectories)) {
|
|
51
51
|
permissions.additionalDirectories = [];
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const allowList = permissions.allow;
|
|
54
|
+
const additionalDirs = permissions.additionalDirectories;
|
|
55
55
|
if (suggestions && suggestions.length > 0) {
|
|
56
|
-
for (
|
|
57
|
-
|
|
56
|
+
for (let si = 0; si < suggestions.length; si++) {
|
|
57
|
+
const suggestion = suggestions[si];
|
|
58
58
|
if (suggestion.type === "addRules" && suggestion.behavior === "allow" && suggestion.rules) {
|
|
59
|
-
for (
|
|
60
|
-
|
|
59
|
+
for (let ri = 0; ri < suggestion.rules.length; ri++) {
|
|
60
|
+
const rule = formatSdkRule(suggestion.rules[ri]);
|
|
61
61
|
if (!allowList.includes(rule)) {
|
|
62
62
|
allowList.push(rule);
|
|
63
63
|
}
|
|
64
64
|
if (suggestion.rules[ri].ruleContent) {
|
|
65
|
-
|
|
65
|
+
const ruleDir = suggestion.rules[ri].ruleContent.replace(/\/\*\*$/, "").replace(/^\//, "");
|
|
66
66
|
if (ruleDir.startsWith("/") && !additionalDirs.includes(ruleDir)) {
|
|
67
67
|
additionalDirs.push(ruleDir);
|
|
68
68
|
}
|
|
@@ -70,7 +70,7 @@ function addProjectAllowRules(projectPath, suggestions, fallbackToolName, fallba
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
if (suggestion.type === "addDirectories" && suggestion.directories) {
|
|
73
|
-
for (
|
|
73
|
+
for (let di = 0; di < suggestion.directories.length; di++) {
|
|
74
74
|
if (!additionalDirs.includes(suggestion.directories[di])) {
|
|
75
75
|
additionalDirs.push(suggestion.directories[di]);
|
|
76
76
|
}
|
|
@@ -79,7 +79,7 @@ function addProjectAllowRules(projectPath, suggestions, fallbackToolName, fallba
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
else {
|
|
82
|
-
|
|
82
|
+
const fallbackRule = buildPermissionRule(fallbackToolName, fallbackInput);
|
|
83
83
|
if (!allowList.includes(fallbackRule)) {
|
|
84
84
|
allowList.push(fallbackRule);
|
|
85
85
|
}
|
|
@@ -89,13 +89,13 @@ function addProjectAllowRules(projectPath, suggestions, fallbackToolName, fallba
|
|
|
89
89
|
}
|
|
90
90
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
const activeSessionByClient = new Map();
|
|
93
|
+
const pendingBudgetOverride = new Map();
|
|
94
94
|
export function sendBudgetStatus(clientId) {
|
|
95
|
-
|
|
95
|
+
const config = loadConfig();
|
|
96
96
|
if (!config.costBudget)
|
|
97
97
|
return;
|
|
98
|
-
|
|
98
|
+
const dailySpend = getDailySpend();
|
|
99
99
|
sendTo(clientId, {
|
|
100
100
|
type: "budget:status",
|
|
101
101
|
dailySpend: dailySpend,
|
|
@@ -114,11 +114,11 @@ export function getActiveSession(clientId) {
|
|
|
114
114
|
}
|
|
115
115
|
registerHandler("budget", function (clientId, message) {
|
|
116
116
|
if (message.type === "budget:override") {
|
|
117
|
-
|
|
117
|
+
const pending = pendingBudgetOverride.get(clientId);
|
|
118
118
|
if (!pending)
|
|
119
119
|
return;
|
|
120
120
|
pendingBudgetOverride.delete(clientId);
|
|
121
|
-
|
|
121
|
+
const overrideAttachments = pending.sendMsg.attachmentIds
|
|
122
122
|
? getAttachments(clientId, pending.sendMsg.attachmentIds)
|
|
123
123
|
: [];
|
|
124
124
|
startChatStream({
|
|
@@ -137,21 +137,21 @@ registerHandler("budget", function (clientId, message) {
|
|
|
137
137
|
});
|
|
138
138
|
registerHandler("chat", function (clientId, message) {
|
|
139
139
|
if (message.type === "chat:send") {
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
const sendMsg = message;
|
|
141
|
+
const active = activeSessionByClient.get(clientId);
|
|
142
142
|
if (!active) {
|
|
143
143
|
sendTo(clientId, { type: "chat:error", message: "No active session. Activate a session first." });
|
|
144
144
|
return;
|
|
145
145
|
}
|
|
146
|
-
|
|
146
|
+
const project = getProjectBySlug(active.projectSlug);
|
|
147
147
|
if (!project) {
|
|
148
148
|
sendTo(clientId, { type: "chat:error", message: `Project not found: ${active.projectSlug}` });
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
const config = loadConfig();
|
|
152
|
+
const env = Object.assign({}, config.globalEnv, project.env);
|
|
153
153
|
if (config.costBudget && config.costBudget.dailyLimit > 0) {
|
|
154
|
-
|
|
154
|
+
const dailySpend = getDailySpend();
|
|
155
155
|
if (dailySpend >= config.costBudget.dailyLimit) {
|
|
156
156
|
if (config.costBudget.enforcement === "hard-block") {
|
|
157
157
|
sendTo(clientId, { type: "chat:error", message: "Daily cost budget exceeded ($" + dailySpend.toFixed(2) + " / $" + config.costBudget.dailyLimit.toFixed(2) + "). Sending is blocked until tomorrow." });
|
|
@@ -174,10 +174,10 @@ registerHandler("chat", function (clientId, message) {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
-
|
|
177
|
+
const attachments = sendMsg.attachmentIds
|
|
178
178
|
? getAttachments(clientId, sendMsg.attachmentIds)
|
|
179
179
|
: [];
|
|
180
|
-
|
|
180
|
+
const linkedSpec = findSpecBySession(active.sessionId);
|
|
181
181
|
startChatStream({
|
|
182
182
|
projectSlug: active.projectSlug,
|
|
183
183
|
sessionId: active.sessionId,
|
|
@@ -194,12 +194,12 @@ registerHandler("chat", function (clientId, message) {
|
|
|
194
194
|
return;
|
|
195
195
|
}
|
|
196
196
|
if (message.type === "chat:cancel") {
|
|
197
|
-
|
|
197
|
+
const active = activeSessionByClient.get(clientId);
|
|
198
198
|
if (!active) {
|
|
199
199
|
sendTo(clientId, { type: "chat:error", message: "No active session." });
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
202
|
-
|
|
202
|
+
const stream = getActiveStream(active.sessionId);
|
|
203
203
|
if (!stream) {
|
|
204
204
|
sendTo(clientId, { type: "chat:error", message: "No active stream to cancel." });
|
|
205
205
|
return;
|
|
@@ -208,18 +208,18 @@ registerHandler("chat", function (clientId, message) {
|
|
|
208
208
|
return;
|
|
209
209
|
}
|
|
210
210
|
if (message.type === "chat:permission_response") {
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
const permMsg = message;
|
|
212
|
+
const pending = getPendingPermission(permMsg.requestId);
|
|
213
213
|
if (!pending) {
|
|
214
214
|
return;
|
|
215
215
|
}
|
|
216
|
-
|
|
216
|
+
const active = activeSessionByClient.get(clientId);
|
|
217
217
|
if (permMsg.allow) {
|
|
218
218
|
if (permMsg.alwaysAllow && permMsg.alwaysAllowScope === "session" && active) {
|
|
219
219
|
addAutoApprovedTool(active.sessionId, pending.toolName);
|
|
220
220
|
}
|
|
221
221
|
if (permMsg.alwaysAllow && permMsg.alwaysAllowScope === "project" && active) {
|
|
222
|
-
|
|
222
|
+
const project = getProjectBySlug(active.projectSlug);
|
|
223
223
|
if (project) {
|
|
224
224
|
addProjectAllowRules(project.path, pending.suggestions, pending.toolName, pending.input);
|
|
225
225
|
}
|
|
@@ -228,7 +228,7 @@ registerHandler("chat", function (clientId, message) {
|
|
|
228
228
|
else {
|
|
229
229
|
pending.resolve({ behavior: "allow", updatedInput: pending.input, toolUseID: pending.toolUseID });
|
|
230
230
|
}
|
|
231
|
-
|
|
231
|
+
const resolvedStatus = permMsg.alwaysAllow ? "always_allowed" : "allowed";
|
|
232
232
|
sendTo(clientId, { type: "chat:permission_resolved", requestId: permMsg.requestId, status: resolvedStatus });
|
|
233
233
|
}
|
|
234
234
|
else {
|
|
@@ -239,12 +239,12 @@ registerHandler("chat", function (clientId, message) {
|
|
|
239
239
|
return;
|
|
240
240
|
}
|
|
241
241
|
if (message.type === "chat:prompt_response") {
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
const promptRespMsg = message;
|
|
243
|
+
const pendingPrompt = getPendingPermission(promptRespMsg.requestId);
|
|
244
244
|
if (!pendingPrompt || pendingPrompt.promptType !== "question") {
|
|
245
245
|
return;
|
|
246
246
|
}
|
|
247
|
-
|
|
247
|
+
const updatedInput = Object.assign({}, pendingPrompt.input, {
|
|
248
248
|
answers: promptRespMsg.answers,
|
|
249
249
|
});
|
|
250
250
|
if (promptRespMsg.annotations) {
|
|
@@ -260,8 +260,8 @@ registerHandler("chat", function (clientId, message) {
|
|
|
260
260
|
return;
|
|
261
261
|
}
|
|
262
262
|
if (message.type === "chat:elicitation_response") {
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
const elicitMsg = message;
|
|
264
|
+
const pendingElicit = getPendingElicitation(elicitMsg.requestId);
|
|
265
265
|
if (!pendingElicit)
|
|
266
266
|
return;
|
|
267
267
|
resolveElicitation(elicitMsg.requestId, {
|
|
@@ -271,11 +271,11 @@ registerHandler("chat", function (clientId, message) {
|
|
|
271
271
|
return;
|
|
272
272
|
}
|
|
273
273
|
if (message.type === "chat:rewind_preview") {
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
const rewindMsg = message;
|
|
275
|
+
const activeForRewind = activeSessionByClient.get(clientId);
|
|
276
276
|
if (!activeForRewind)
|
|
277
277
|
return;
|
|
278
|
-
|
|
278
|
+
const sessionStreamForRewind = getSessionStream(activeForRewind.sessionId);
|
|
279
279
|
if (!sessionStreamForRewind) {
|
|
280
280
|
sendTo(clientId, { type: "chat:rewind_preview_result", messageUuid: rewindMsg.messageUuid, canRewind: false, error: "No active stream for rewind" });
|
|
281
281
|
return;
|
|
@@ -296,11 +296,11 @@ registerHandler("chat", function (clientId, message) {
|
|
|
296
296
|
return;
|
|
297
297
|
}
|
|
298
298
|
if (message.type === "chat:rewind_execute") {
|
|
299
|
-
|
|
300
|
-
|
|
299
|
+
const execRewindMsg = message;
|
|
300
|
+
const activeForExec = activeSessionByClient.get(clientId);
|
|
301
301
|
if (!activeForExec)
|
|
302
302
|
return;
|
|
303
|
-
|
|
303
|
+
const sessionStreamForExec = getSessionStream(activeForExec.sessionId);
|
|
304
304
|
if (!sessionStreamForExec) {
|
|
305
305
|
sendTo(clientId, { type: "chat:rewind_execute_result", messageUuid: execRewindMsg.messageUuid, success: false, error: "No active stream" });
|
|
306
306
|
return;
|
|
@@ -323,11 +323,11 @@ registerHandler("chat", function (clientId, message) {
|
|
|
323
323
|
return;
|
|
324
324
|
}
|
|
325
325
|
if (message.type === "chat:set_model") {
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
const modelMsg = message;
|
|
327
|
+
const activeSession = activeSessionByClient.get(clientId);
|
|
328
328
|
if (!activeSession)
|
|
329
329
|
return;
|
|
330
|
-
|
|
330
|
+
const sessionStream = getSessionStream(activeSession.sessionId);
|
|
331
331
|
if (sessionStream) {
|
|
332
332
|
void sessionStream.queryInstance.setModel(modelMsg.model === "default" ? undefined : modelMsg.model).catch(function (err) {
|
|
333
333
|
log.chat("Failed to switch model: %O", err);
|
|
@@ -337,12 +337,12 @@ registerHandler("chat", function (clientId, message) {
|
|
|
337
337
|
return;
|
|
338
338
|
}
|
|
339
339
|
if (message.type === "chat:set_permission_mode") {
|
|
340
|
-
|
|
341
|
-
|
|
340
|
+
const modeMsg = message;
|
|
341
|
+
const activeSession = activeSessionByClient.get(clientId);
|
|
342
342
|
if (!activeSession) {
|
|
343
343
|
return;
|
|
344
344
|
}
|
|
345
|
-
|
|
345
|
+
const stream = getActiveStream(activeSession.sessionId);
|
|
346
346
|
if (stream) {
|
|
347
347
|
void stream.setPermissionMode(modeMsg.mode);
|
|
348
348
|
}
|
|
@@ -7,7 +7,7 @@ import { loadConfig } from "../config.js";
|
|
|
7
7
|
import { loadOrCreateIdentity } from "../identity.js";
|
|
8
8
|
import { broadcast } from "../ws/broadcast.js";
|
|
9
9
|
import { detectIdeProjectName } from "./settings.js";
|
|
10
|
-
|
|
10
|
+
const binaryNames = {
|
|
11
11
|
"vscode": ["code"],
|
|
12
12
|
"vscode-insiders": ["code-insiders"],
|
|
13
13
|
"cursor": ["cursor"],
|
|
@@ -19,12 +19,12 @@ var binaryNames = {
|
|
|
19
19
|
"sublime": ["subl", "sublime_text"],
|
|
20
20
|
};
|
|
21
21
|
function detectEditorPath(editorType) {
|
|
22
|
-
|
|
22
|
+
const names = binaryNames[editorType];
|
|
23
23
|
if (!names)
|
|
24
24
|
return null;
|
|
25
|
-
for (
|
|
25
|
+
for (let i = 0; i < names.length; i++) {
|
|
26
26
|
try {
|
|
27
|
-
|
|
27
|
+
const result = execSync("which " + names[i], { encoding: "utf-8", timeout: 3000 }).trim();
|
|
28
28
|
if (result)
|
|
29
29
|
return result;
|
|
30
30
|
}
|
|
@@ -35,11 +35,11 @@ function detectEditorPath(editorType) {
|
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
37
37
|
function ensureIdeaProject(projectPath, projectTitle) {
|
|
38
|
-
|
|
38
|
+
const ideaDir = join(projectPath, ".idea");
|
|
39
39
|
if (!existsSync(ideaDir)) {
|
|
40
40
|
mkdirSync(ideaDir, { recursive: true });
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
const nameFile = join(ideaDir, ".name");
|
|
43
43
|
if (!existsSync(nameFile)) {
|
|
44
44
|
writeFileSync(nameFile, projectTitle, "utf-8");
|
|
45
45
|
}
|
|
@@ -47,19 +47,19 @@ function ensureIdeaProject(projectPath, projectTitle) {
|
|
|
47
47
|
}
|
|
48
48
|
registerHandler("editor", function (clientId, message) {
|
|
49
49
|
if (message.type === "editor:detect") {
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const detectMsg = message;
|
|
51
|
+
const detectedPath = detectEditorPath(detectMsg.editorType);
|
|
52
52
|
sendTo(clientId, { type: "editor:detect_result", editorType: detectMsg.editorType, path: detectedPath });
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
if (message.type === "editor:ensure-project") {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
const ensureMsg = message;
|
|
57
|
+
const config = loadConfig();
|
|
58
|
+
const project = config.projects.find(function (p) { return p.slug === ensureMsg.projectSlug; });
|
|
59
59
|
if (project) {
|
|
60
|
-
|
|
60
|
+
const name = ensureIdeaProject(project.path, project.title);
|
|
61
61
|
sendTo(clientId, { type: "editor:ensure-project_result", projectSlug: ensureMsg.projectSlug, ideProjectName: name });
|
|
62
|
-
|
|
62
|
+
const identity = loadOrCreateIdentity();
|
|
63
63
|
broadcast({
|
|
64
64
|
type: "projects:list",
|
|
65
65
|
projects: config.projects.map(function (p) {
|
|
@@ -7,7 +7,7 @@ import { existsSync } from "node:fs";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { loadConfig } from "../config.js";
|
|
10
|
-
|
|
10
|
+
const activeProjectByClient = new Map();
|
|
11
11
|
export function setActiveProject(clientId, projectSlug) {
|
|
12
12
|
activeProjectByClient.set(clientId, projectSlug);
|
|
13
13
|
subscribeClientToProject(clientId, projectSlug);
|
|
@@ -20,8 +20,8 @@ export function getActiveProjectForClient(clientId) {
|
|
|
20
20
|
}
|
|
21
21
|
registerHandler("fs", async function (clientId, message) {
|
|
22
22
|
if (message.type === "fs:list") {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const listMsg = message;
|
|
24
|
+
let projectSlug = activeProjectByClient.get(clientId) || listMsg.projectSlug;
|
|
25
25
|
if (listMsg.projectSlug) {
|
|
26
26
|
setActiveProject(clientId, listMsg.projectSlug);
|
|
27
27
|
projectSlug = listMsg.projectSlug;
|
|
@@ -30,18 +30,18 @@ registerHandler("fs", async function (clientId, message) {
|
|
|
30
30
|
sendTo(clientId, { type: "chat:error", message: "No active project for fs:list" });
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
const project = getProjectBySlug(projectSlug);
|
|
34
34
|
if (!project) {
|
|
35
35
|
sendTo(clientId, { type: "chat:error", message: "Project not found: " + projectSlug });
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
const entries = await listDirectory(project.path, listMsg.path);
|
|
39
39
|
sendTo(clientId, { type: "fs:list_result", path: listMsg.path, entries });
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
if (message.type === "fs:read") {
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const readMsg = message;
|
|
44
|
+
let projectSlugRead = activeProjectByClient.get(clientId) || readMsg.projectSlug;
|
|
45
45
|
if (readMsg.projectSlug) {
|
|
46
46
|
setActiveProject(clientId, readMsg.projectSlug);
|
|
47
47
|
projectSlugRead = readMsg.projectSlug;
|
|
@@ -50,12 +50,12 @@ registerHandler("fs", async function (clientId, message) {
|
|
|
50
50
|
sendTo(clientId, { type: "chat:error", message: "No active project for fs:read" });
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
const projectRead = getProjectBySlug(projectSlugRead);
|
|
54
54
|
if (!projectRead) {
|
|
55
55
|
sendTo(clientId, { type: "chat:error", message: "Project not found: " + projectSlugRead });
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
const content = await readFile(projectRead.path, readMsg.path);
|
|
59
59
|
if (content === null) {
|
|
60
60
|
sendTo(clientId, { type: "chat:error", message: "Cannot read file: " + readMsg.path });
|
|
61
61
|
return;
|
|
@@ -64,18 +64,18 @@ registerHandler("fs", async function (clientId, message) {
|
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
if (message.type === "fs:write") {
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const writeMsg = message;
|
|
68
|
+
const projectSlugWrite = activeProjectByClient.get(clientId);
|
|
69
69
|
if (!projectSlugWrite) {
|
|
70
70
|
sendTo(clientId, { type: "chat:error", message: "No active project for fs:write" });
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
const projectWrite = getProjectBySlug(projectSlugWrite);
|
|
74
74
|
if (!projectWrite) {
|
|
75
75
|
sendTo(clientId, { type: "chat:error", message: "Project not found: " + projectSlugWrite });
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
|
-
|
|
78
|
+
const ok = await writeFile(projectWrite.path, writeMsg.path, writeMsg.content);
|
|
79
79
|
if (!ok) {
|
|
80
80
|
sendTo(clientId, { type: "chat:error", message: "Cannot write file: " + writeMsg.path });
|
|
81
81
|
return;
|
|
@@ -93,48 +93,48 @@ function resolvePath(path) {
|
|
|
93
93
|
}
|
|
94
94
|
async function detectProjectName(dirPath) {
|
|
95
95
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
const pkgPath = join(dirPath, "package.json");
|
|
97
|
+
const pkg = JSON.parse(await fsReadFile(pkgPath, "utf-8"));
|
|
98
98
|
if (pkg.name)
|
|
99
99
|
return pkg.name;
|
|
100
100
|
}
|
|
101
101
|
catch { }
|
|
102
102
|
try {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
const cargoPath = join(dirPath, "Cargo.toml");
|
|
104
|
+
const cargo = await fsReadFile(cargoPath, "utf-8");
|
|
105
|
+
const cargoMatch = cargo.match(/\[package\][\s\S]*?name\s*=\s*"([^"]+)"/);
|
|
106
106
|
if (cargoMatch)
|
|
107
107
|
return cargoMatch[1];
|
|
108
108
|
}
|
|
109
109
|
catch { }
|
|
110
110
|
try {
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
const composerPath = join(dirPath, "composer.json");
|
|
112
|
+
const composer = JSON.parse(await fsReadFile(composerPath, "utf-8"));
|
|
113
113
|
if (composer.name)
|
|
114
114
|
return composer.name;
|
|
115
115
|
}
|
|
116
116
|
catch { }
|
|
117
117
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
const pyprojectPath = join(dirPath, "pyproject.toml");
|
|
119
|
+
const pyproject = await fsReadFile(pyprojectPath, "utf-8");
|
|
120
|
+
const pyMatch = pyproject.match(/\[project\][\s\S]*?name\s*=\s*"([^"]+)"/);
|
|
121
121
|
if (pyMatch)
|
|
122
122
|
return pyMatch[1];
|
|
123
123
|
}
|
|
124
124
|
catch { }
|
|
125
125
|
try {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
const goModPath = join(dirPath, "go.mod");
|
|
127
|
+
const goMod = await fsReadFile(goModPath, "utf-8");
|
|
128
|
+
const goMatch = goMod.match(/^module\s+(\S+)/m);
|
|
129
129
|
if (goMatch) {
|
|
130
|
-
|
|
130
|
+
const parts = goMatch[1].split("/");
|
|
131
131
|
return parts[parts.length - 1];
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
catch { }
|
|
135
135
|
try {
|
|
136
|
-
|
|
137
|
-
for (
|
|
136
|
+
const entries = await readdir(dirPath);
|
|
137
|
+
for (let i = 0; i < entries.length; i++) {
|
|
138
138
|
if (entries[i].endsWith(".sln") || entries[i].endsWith(".csproj")) {
|
|
139
139
|
return entries[i].replace(/\.[^.]+$/, "");
|
|
140
140
|
}
|
|
@@ -145,11 +145,11 @@ async function detectProjectName(dirPath) {
|
|
|
145
145
|
}
|
|
146
146
|
registerHandler("browse", async function (clientId, message) {
|
|
147
147
|
if (message.type === "browse:list") {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
const browseMsg = message;
|
|
149
|
+
const resolvedPath = resolvePath(browseMsg.path);
|
|
150
|
+
const home = homedir();
|
|
151
151
|
try {
|
|
152
|
-
|
|
152
|
+
const pathStat = await stat(resolvedPath);
|
|
153
153
|
if (!pathStat.isDirectory()) {
|
|
154
154
|
sendTo(clientId, { type: "browse:list_result", path: resolvedPath, homedir: home, entries: [] });
|
|
155
155
|
return;
|
|
@@ -160,15 +160,15 @@ registerHandler("browse", async function (clientId, message) {
|
|
|
160
160
|
return;
|
|
161
161
|
}
|
|
162
162
|
try {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
for (
|
|
166
|
-
|
|
163
|
+
const dirEntries = await readdir(resolvedPath, { withFileTypes: true });
|
|
164
|
+
const results = [];
|
|
165
|
+
for (let i = 0; i < dirEntries.length; i++) {
|
|
166
|
+
const entry = dirEntries[i];
|
|
167
167
|
if (!entry.isDirectory())
|
|
168
168
|
continue;
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
const entryPath = join(resolvedPath, entry.name);
|
|
170
|
+
const hasClaudeMd = existsSync(join(entryPath, "CLAUDE.md"));
|
|
171
|
+
const projectName = await detectProjectName(entryPath);
|
|
172
172
|
results.push({
|
|
173
173
|
name: entry.name,
|
|
174
174
|
path: entryPath,
|
|
@@ -185,29 +185,29 @@ registerHandler("browse", async function (clientId, message) {
|
|
|
185
185
|
return;
|
|
186
186
|
}
|
|
187
187
|
if (message.type === "browse:suggestions") {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
const claudeProjectsDir = join(homedir(), ".claude", "projects");
|
|
189
|
+
const config = loadConfig();
|
|
190
|
+
const existingPaths = new Set(config.projects.map(function (p) { return p.path; }));
|
|
191
|
+
const suggestions = [];
|
|
192
192
|
try {
|
|
193
|
-
|
|
194
|
-
for (
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
const hashDirs = await readdir(claudeProjectsDir);
|
|
194
|
+
for (let i = 0; i < hashDirs.length; i++) {
|
|
195
|
+
const hashDir = hashDirs[i];
|
|
196
|
+
const candidatePath = "/" + hashDir.slice(1).replace(/-/g, "/");
|
|
197
197
|
if (!existsSync(candidatePath))
|
|
198
198
|
continue;
|
|
199
199
|
if (existingPaths.has(candidatePath))
|
|
200
200
|
continue;
|
|
201
201
|
try {
|
|
202
|
-
|
|
202
|
+
const candidateStat = await stat(candidatePath);
|
|
203
203
|
if (!candidateStat.isDirectory())
|
|
204
204
|
continue;
|
|
205
205
|
}
|
|
206
206
|
catch {
|
|
207
207
|
continue;
|
|
208
208
|
}
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
const hasClaudeMd = existsSync(join(candidatePath, "CLAUDE.md"));
|
|
210
|
+
const name = candidatePath.split("/").pop() || hashDir;
|
|
211
211
|
suggestions.push({
|
|
212
212
|
path: candidatePath,
|
|
213
213
|
name: name,
|